262 lines
9.1 KiB
Vue
262 lines
9.1 KiB
Vue
<script setup>
|
|
import { onBeforeUnmount } from 'vue'
|
|
|
|
const props = defineProps({
|
|
dateMain: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
dateWeekday: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
dateWeekdayTone: {
|
|
type: String,
|
|
default: 'text-ink',
|
|
},
|
|
dday: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
showDday: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
comment: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
totalTime: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
tasks: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
memo: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
hours: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
timetable: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
brand: {
|
|
type: String,
|
|
default: 'SORI.STUDIO',
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits([
|
|
'update:comment',
|
|
'update:task-label',
|
|
'update:task-title',
|
|
'toggle:task',
|
|
'update:memo-label',
|
|
'update:memo',
|
|
'update:timetable',
|
|
])
|
|
|
|
let dragState = null
|
|
|
|
function shouldShowTaskPlaceholder(index) {
|
|
return index === 0 && props.tasks.every((task) => !task.title.trim())
|
|
}
|
|
|
|
function buildTimedRange(baseTimetable, startIndex, endIndex, nextValue) {
|
|
const nextTimetable = [...baseTimetable]
|
|
const rangeStart = Math.min(startIndex, endIndex)
|
|
const rangeEnd = Math.max(startIndex, endIndex)
|
|
|
|
for (let index = rangeStart; index <= rangeEnd; index += 1) {
|
|
nextTimetable[index] = nextValue
|
|
}
|
|
|
|
return nextTimetable
|
|
}
|
|
|
|
function startTimetableDrag(index, event) {
|
|
if (event.button !== 0 && event.button !== 2) {
|
|
return
|
|
}
|
|
|
|
const shouldFill = event.button === 2 ? false : !props.timetable[index]
|
|
|
|
dragState = {
|
|
startIndex: index,
|
|
baseTimetable: [...props.timetable],
|
|
nextValue: shouldFill,
|
|
}
|
|
|
|
emit('update:timetable', buildTimedRange(dragState.baseTimetable, index, index, dragState.nextValue))
|
|
}
|
|
|
|
function moveTimetableDrag(index) {
|
|
if (!dragState) {
|
|
return
|
|
}
|
|
|
|
emit(
|
|
'update:timetable',
|
|
buildTimedRange(dragState.baseTimetable, dragState.startIndex, index, dragState.nextValue),
|
|
)
|
|
}
|
|
|
|
function stopTimetableDrag() {
|
|
dragState = null
|
|
}
|
|
|
|
window.addEventListener('pointerup', stopTimetableDrag)
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('pointerup', stopTimetableDrag)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<article
|
|
class="planner-sheet flex w-full max-w-[762px] flex-col gap-3 bg-paper px-6 py-6 text-[10px] font-bold tracking-[0.16em] text-ink shadow-paper sm:px-12 sm:py-12"
|
|
>
|
|
<div class="flex flex-col gap-4 py-[18px]">
|
|
<div class="flex gap-4">
|
|
<div class="relative h-[90px] border-t border-ink px-[10px] pt-[10px]" :class="props.showDday ? 'w-[394px] flex-1' : 'w-full flex-1'">
|
|
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">YEAR / MONTH / DAY</span>
|
|
<p class="pt-6 text-xs tracking-[0.24em] text-ink sm:text-sm">
|
|
<span>{{ dateMain }}</span>
|
|
<span class="ml-1" :class="dateWeekdayTone">{{ dateWeekday }}</span>
|
|
</p>
|
|
</div>
|
|
<div v-if="props.showDday" class="relative h-[90px] w-[210px] border-t border-ink px-[10px] pt-[10px]">
|
|
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">D-DAY</span>
|
|
<p class="pt-6 text-xs tracking-[0.24em] text-ink sm:text-sm">{{ dday }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-4 border-b border-ink pb-[18px]">
|
|
<div class="relative h-[90px] w-[394px] flex-1 border-t border-ink px-[10px] pt-[10px]">
|
|
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">COMMENT</span>
|
|
<textarea
|
|
:value="comment"
|
|
rows="3"
|
|
class="mt-4 h-[56px] w-full resize-none bg-transparent pt-2 text-[11px] font-semibold normal-case tracking-[0.08em] text-stone-700 outline-none placeholder:text-stone-400 sm:text-xs"
|
|
placeholder="오늘의 코멘트를 적어 주세요."
|
|
@input="emit('update:comment', $event.target.value)"
|
|
/>
|
|
</div>
|
|
<div class="relative h-[90px] w-[210px] border-t border-ink px-[10px] pt-[10px]">
|
|
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">TOTAL TIME</span>
|
|
<p class="pt-6 text-xs tracking-[0.24em] text-ink sm:text-sm">{{ totalTime }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-4 py-[10px]">
|
|
<div class="flex w-[394px] flex-1 flex-col gap-9">
|
|
<section class="relative">
|
|
<div class="absolute -top-[9px] left-0 bg-paper px-[2px] text-muted">TASKS</div>
|
|
<div class="border-t border-ink">
|
|
<div
|
|
v-for="(task, index) in tasks"
|
|
:key="task.id"
|
|
class="flex h-[38px] items-center border-b"
|
|
:class="index % 5 === 4 || index === tasks.length - 1 ? 'border-ink' : 'border-line'"
|
|
>
|
|
<div class="h-full w-[62px] border-r border-dashed border-ink px-2 py-[7px]">
|
|
<input
|
|
:value="task.label"
|
|
type="text"
|
|
class="w-full bg-transparent text-center text-[9px] font-semibold tracking-[0.08em] text-stone-500 outline-none placeholder:text-stone-300"
|
|
@input="emit('update:task-label', { index, value: $event.target.value })"
|
|
/>
|
|
</div>
|
|
<div class="flex min-w-0 flex-1 items-center px-3">
|
|
<input
|
|
:value="task.title"
|
|
type="text"
|
|
class="w-full truncate bg-transparent text-[11px] font-semibold normal-case tracking-[0.06em] text-stone-800 outline-none placeholder:text-stone-400"
|
|
:placeholder="shouldShowTaskPlaceholder(index) ? '할 일을 입력해 주세요.' : ''"
|
|
@input="emit('update:task-title', { index, value: $event.target.value })"
|
|
/>
|
|
</div>
|
|
<div class="flex h-full w-[42px] items-center justify-center p-[10px]">
|
|
<button
|
|
type="button"
|
|
class="flex h-full w-full items-center justify-center border border-dashed transition"
|
|
:class="task.checked ? 'border-ink bg-stone-100 text-ink' : 'border-ink/60 text-transparent'"
|
|
@click="emit('toggle:task', index)"
|
|
>
|
|
<span class="text-sm leading-none">✓</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="relative">
|
|
<div class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">MEMO</div>
|
|
<div class="border-t border-ink">
|
|
<div
|
|
v-for="(memoItem, index) in memo"
|
|
:key="`memo-${index}`"
|
|
class="flex h-[38px] items-center border-b"
|
|
:class="index === memo.length - 1 ? 'border-ink' : 'border-line'"
|
|
>
|
|
<div class="h-full w-[62px] border-r border-dashed border-ink px-2 py-[7px]">
|
|
<input
|
|
:value="memoItem.label"
|
|
type="text"
|
|
class="w-full bg-transparent text-center text-[9px] font-semibold tracking-[0.08em] text-stone-500 outline-none placeholder:text-stone-300"
|
|
@input="emit('update:memo-label', { index, value: $event.target.value })"
|
|
/>
|
|
</div>
|
|
<div class="flex flex-1 items-center px-3">
|
|
<input
|
|
:value="memoItem.text"
|
|
type="text"
|
|
class="w-full bg-transparent text-[11px] font-semibold normal-case tracking-[0.06em] text-stone-700 outline-none placeholder:text-stone-400"
|
|
@input="emit('update:memo', { index, value: $event.target.value })"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<section class="relative w-[210px] shrink-0">
|
|
<div class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">TIME TABLE</div>
|
|
<div class="border-t border-ink">
|
|
<div
|
|
v-for="(hour, index) in hours"
|
|
:key="`${hour}-${index}`"
|
|
class="flex h-[30px] border-b"
|
|
:class="index === hours.length - 1 ? 'border-ink' : 'border-line'"
|
|
>
|
|
<div
|
|
class="flex h-full w-[30px] touch-none select-none items-center justify-center border-r border-ink text-[9px] text-ink"
|
|
@pointerdown.prevent
|
|
>
|
|
{{ hour }}
|
|
</div>
|
|
<div
|
|
v-for="quarter in 6"
|
|
:key="quarter"
|
|
:class="props.timetable[index * 6 + quarter - 1] ? 'bg-stone-800/90' : 'bg-transparent'"
|
|
class="h-full w-[30px] cursor-crosshair border-r border-dashed border-line transition-colors last:border-r-0 touch-none select-none"
|
|
@contextmenu.prevent
|
|
@pointerdown.prevent="startTimetableDrag(index * 6 + quarter - 1, $event)"
|
|
@pointerenter="moveTimetableDrag(index * 6 + quarter - 1)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="flex justify-end">
|
|
<p class="text-[10px] tracking-[0.18em] text-ink">{{ brand }}</p>
|
|
</div>
|
|
</article>
|
|
</template>
|