Refine transaction date input and header
This commit is contained in:
121
components/BottomSheetDatePicker.vue
Normal file
121
components/BottomSheetDatePicker.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="field flex min-h-[50px] items-center text-left font-semibold"
|
||||
aria-haspopup="dialog"
|
||||
:aria-expanded="open"
|
||||
@click="open = true"
|
||||
>
|
||||
{{ displayValue }}
|
||||
</button>
|
||||
|
||||
<Teleport to="body">
|
||||
<Transition
|
||||
enter-active-class="transition duration-200 ease-out"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition duration-150 ease-in"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="open" class="fixed inset-0 z-40 bg-ink/35" @click="open = false" />
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
enter-active-class="transition duration-200 ease-out"
|
||||
enter-from-class="translate-y-full"
|
||||
enter-to-class="translate-y-0"
|
||||
leave-active-class="transition duration-150 ease-in"
|
||||
leave-from-class="translate-y-0"
|
||||
leave-to-class="translate-y-full"
|
||||
>
|
||||
<section
|
||||
v-if="open"
|
||||
class="fixed inset-x-0 bottom-0 z-50 mx-auto max-w-lg overflow-hidden rounded-t-2xl bg-white shadow-2xl"
|
||||
role="dialog"
|
||||
aria-label="날짜 선택"
|
||||
>
|
||||
<div class="border-b border-line px-4 pb-3 pt-2">
|
||||
<div class="mx-auto mb-3 h-1 w-10 rounded-full bg-line" />
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-base font-bold">날짜 선택</h2>
|
||||
<button type="button" class="rounded-md px-2 py-1 text-sm font-semibold text-muted" @click="open = false">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between px-4 py-4">
|
||||
<button type="button" class="flex h-9 w-9 items-center justify-center rounded-md active:bg-paper" aria-label="이전 달" @click="moveMonth(-1)">
|
||||
<img class="h-6 w-6 opacity-70" src="~/assets/icons/keyboard_arrow_left.svg" alt="">
|
||||
</button>
|
||||
<strong class="text-lg">{{ calendarYear }}년 {{ calendarMonth + 1 }}월</strong>
|
||||
<button type="button" class="flex h-9 w-9 items-center justify-center rounded-md active:bg-paper" aria-label="다음 달" @click="moveMonth(1)">
|
||||
<img class="h-6 w-6 opacity-70" src="~/assets/icons/keyboard_arrow_right.svg" alt="">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 px-4 text-center text-xs font-bold text-muted">
|
||||
<span v-for="day in weekdays" :key="day" class="py-2">{{ day }}</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 gap-y-1 px-4 pb-6 text-center">
|
||||
<span v-for="blank in firstWeekday" :key="`blank-${blank}`" />
|
||||
<button
|
||||
v-for="day in daysInMonth"
|
||||
:key="day"
|
||||
type="button"
|
||||
class="mx-auto flex h-10 w-10 items-center justify-center rounded-full text-sm font-bold"
|
||||
:class="dateValue(day) === modelValue ? 'bg-ink text-white' : 'active:bg-paper'"
|
||||
@click="select(day)"
|
||||
>
|
||||
{{ day }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { addMonths, format, parse } from 'date-fns'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
const weekdays = ['일', '월', '화', '수', '목', '금', '토']
|
||||
const open = ref(false)
|
||||
const calendarDate = ref(parse(props.modelValue, 'yyyy-MM-dd', new Date()))
|
||||
|
||||
const calendarYear = computed(() => calendarDate.value.getFullYear())
|
||||
const calendarMonth = computed(() => calendarDate.value.getMonth())
|
||||
const firstWeekday = computed(() => new Date(calendarYear.value, calendarMonth.value, 1).getDay())
|
||||
const daysInMonth = computed(() => new Date(calendarYear.value, calendarMonth.value + 1, 0).getDate())
|
||||
const displayValue = computed(() => {
|
||||
const date = parse(props.modelValue, 'yyyy-MM-dd', new Date())
|
||||
const weekday = new Intl.DateTimeFormat('ko-KR', { weekday: 'short' }).format(date)
|
||||
return `${format(date, 'yy/M/d')} (${weekday})`
|
||||
})
|
||||
|
||||
watch(open, (value) => {
|
||||
if (value) calendarDate.value = parse(props.modelValue, 'yyyy-MM-dd', new Date())
|
||||
})
|
||||
|
||||
function moveMonth(amount: number) {
|
||||
calendarDate.value = addMonths(calendarDate.value, amount)
|
||||
}
|
||||
|
||||
function dateValue(day: number) {
|
||||
return `${calendarYear.value}-${String(calendarMonth.value + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function select(day: number) {
|
||||
emit('update:modelValue', dateValue(day))
|
||||
open.value = false
|
||||
}
|
||||
</script>
|
||||
@@ -41,7 +41,7 @@ const props = defineProps<{
|
||||
|
||||
const categoryLabel = computed(() => {
|
||||
const category = props.transaction.category
|
||||
return category.parent ? `${category.parent.name} > ${category.name}` : category.name
|
||||
return category.name
|
||||
})
|
||||
|
||||
const categoryInitial = computed(() => {
|
||||
@@ -51,8 +51,7 @@ const categoryInitial = computed(() => {
|
||||
})
|
||||
|
||||
const metaLine = computed(() => {
|
||||
const time = new Intl.DateTimeFormat('ko-KR', { hour: '2-digit', minute: '2-digit' }).format(new Date(props.transaction.occurredAt))
|
||||
return [time, props.transaction.asset.name, categoryLabel.value].filter(Boolean).join(' · ')
|
||||
return [props.transaction.asset.name, categoryLabel.value].filter(Boolean).join(' · ')
|
||||
})
|
||||
|
||||
const memoPreview = computed(() => props.transaction.memo?.trim().slice(0, 60) || '')
|
||||
|
||||
Reference in New Issue
Block a user