276 lines
10 KiB
Vue
276 lines
10 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-4 py-4 text-[10px] font-bold tracking-[0.16em] text-ink shadow-paper sm:px-8 sm:py-8 lg:px-12 lg:py-12"
|
|
>
|
|
<div class="planner-sheet__meta flex flex-col gap-4 py-3 sm:py-[18px]">
|
|
<div class="planner-sheet__meta-top flex flex-col gap-3 sm:gap-4" :class="props.showDday ? 'sm:flex-row' : ''">
|
|
<div class="relative min-h-[82px] border-t border-ink px-[10px] pt-[10px]" :class="props.showDday ? 'w-full sm:w-[394px] sm: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-5 text-[11px] tracking-[0.2em] text-ink sm:pt-6 sm:text-sm">
|
|
<span>{{ dateMain }}</span>
|
|
<span class="ml-1" :class="dateWeekdayTone">{{ dateWeekday }}</span>
|
|
</p>
|
|
</div>
|
|
<div v-if="props.showDday" class="relative min-h-[82px] w-full border-t border-ink px-[10px] pt-[10px] sm:w-[210px]">
|
|
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">D-DAY</span>
|
|
<p
|
|
class="pt-5 text-[11px] tracking-[0.14em] text-ink sm:pt-6 sm:text-sm"
|
|
style="
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 3;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
word-break: break-word;
|
|
"
|
|
>
|
|
{{ dday }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="planner-sheet__meta-bottom flex flex-col gap-3 border-b border-ink pb-3 sm:gap-4 sm:pb-[18px] lg:flex-row">
|
|
<div class="relative min-h-[82px] w-full flex-1 border-t border-ink px-[10px] pt-[10px] lg:w-[394px]">
|
|
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">COMMENT</span>
|
|
<textarea
|
|
:value="comment"
|
|
rows="3"
|
|
class="mt-3 h-[54px] w-full resize-none bg-transparent pt-2 text-[11px] font-semibold normal-case tracking-[0.06em] text-stone-700 outline-none placeholder:text-stone-400 sm:mt-4 sm:text-xs"
|
|
placeholder="오늘의 코멘트를 적어 주세요."
|
|
@input="emit('update:comment', $event.target.value)"
|
|
/>
|
|
</div>
|
|
<div class="relative min-h-[82px] w-full border-t border-ink px-[10px] pt-[10px] lg:w-[210px]">
|
|
<span class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">총 시간</span>
|
|
<p class="pt-5 text-[11px] tracking-[0.2em] text-ink sm:pt-6 sm:text-sm">{{ totalTime }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="planner-sheet__body flex flex-col gap-5 py-[10px] lg:flex-row lg:gap-4">
|
|
<div class="planner-sheet__lists flex w-full flex-1 flex-col gap-7 sm:gap-9 lg:w-[394px]">
|
|
<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 min-h-[38px] items-center border-b"
|
|
:class="index % 5 === 4 || index === tasks.length - 1 ? 'border-ink' : 'border-line'"
|
|
>
|
|
<div class="h-full w-[52px] shrink-0 border-r border-dashed border-ink px-1.5 py-[7px] sm:w-[62px] sm:px-2">
|
|
<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-2 sm:px-3">
|
|
<input
|
|
:value="task.title"
|
|
type="text"
|
|
class="w-full truncate bg-transparent text-[10px] font-semibold normal-case tracking-[0.04em] text-stone-800 outline-none placeholder:text-stone-400 sm:text-[11px] sm:tracking-[0.06em]"
|
|
:placeholder="shouldShowTaskPlaceholder(index) ? '할 일을 입력해 주세요.' : ''"
|
|
@input="emit('update:task-title', { index, value: $event.target.value })"
|
|
/>
|
|
</div>
|
|
<div class="flex h-full w-[36px] shrink-0 items-center justify-center p-[8px] sm:w-[42px] sm: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 min-h-[38px] items-center border-b"
|
|
:class="index === memo.length - 1 ? 'border-ink' : 'border-line'"
|
|
>
|
|
<div class="h-full w-[52px] shrink-0 border-r border-dashed border-ink px-1.5 py-[7px] sm:w-[62px] sm:px-2">
|
|
<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-2 sm:px-3">
|
|
<input
|
|
:value="memoItem.text"
|
|
type="text"
|
|
class="w-full bg-transparent text-[10px] font-semibold normal-case tracking-[0.04em] text-stone-700 outline-none placeholder:text-stone-400 sm:text-[11px] sm:tracking-[0.06em]"
|
|
@input="emit('update:memo', { index, value: $event.target.value })"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<section class="planner-sheet__timetable relative w-full shrink-0 lg:w-[210px]">
|
|
<div class="absolute -top-2 left-0 bg-paper px-[2px] text-muted">TIME TABLE</div>
|
|
<div class="planner-sheet__timetable-scroll overflow-x-auto pb-1">
|
|
<div class="planner-sheet__timetable-grid min-w-[210px] border-t border-ink">
|
|
<div
|
|
v-for="(hour, index) in hours"
|
|
:key="`${hour}-${index}`"
|
|
class="flex h-[26px] border-b sm:h-[30px]"
|
|
: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>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="flex justify-end">
|
|
<p class="text-[10px] tracking-[0.18em] text-ink">{{ brand }}</p>
|
|
</div>
|
|
</article>
|
|
</template>
|