217 lines
8.2 KiB
Vue
217 lines
8.2 KiB
Vue
<script setup>
|
|
const props = defineProps({
|
|
goals: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
query: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
form: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
editingGoalId: {
|
|
type: Number,
|
|
default: null,
|
|
},
|
|
busy: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
message: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
selectedDateKey: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits([
|
|
'update:query',
|
|
'update:form-field',
|
|
'submit:create',
|
|
'start-edit',
|
|
'cancel-edit',
|
|
'submit:update',
|
|
'delete-goal',
|
|
])
|
|
|
|
function updateField(field, event) {
|
|
emit('update:form-field', {
|
|
field,
|
|
value: event.target.value,
|
|
})
|
|
}
|
|
|
|
function isActiveOnSelectedDate(goal) {
|
|
if (!goal.activeFrom || !goal.activeUntil || !props.selectedDateKey) {
|
|
return false
|
|
}
|
|
|
|
return props.selectedDateKey >= goal.activeFrom && props.selectedDateKey <= goal.activeUntil
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<section class="grid gap-6 xl:grid-cols-[380px_minmax(0,1fr)]">
|
|
<form class="rounded-[28px] border border-white/60 bg-white/75 p-6" @submit.prevent="emit(editingGoalId ? 'submit:update' : 'submit:create')">
|
|
<p class="text-[11px] font-bold uppercase tracking-[0.24em] text-stone-500">
|
|
{{ editingGoalId ? 'Edit Goal' : 'Create Goal' }}
|
|
</p>
|
|
<div class="mt-5 space-y-4">
|
|
<div class="space-y-2">
|
|
<label class="text-[11px] font-bold tracking-[0.16em] text-stone-600">목표 이름</label>
|
|
<input
|
|
:value="form.title"
|
|
type="text"
|
|
class="w-full rounded-2xl border border-stone-300 bg-white px-4 py-3 text-sm font-semibold text-stone-800 outline-none transition focus:border-stone-500"
|
|
placeholder="예: 자격증 시험 / 프로젝트 런칭 / 운동 루틴"
|
|
@input="updateField('title', $event)"
|
|
/>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label class="text-[11px] font-bold tracking-[0.16em] text-stone-600">목표일</label>
|
|
<input
|
|
:value="form.targetDate"
|
|
type="date"
|
|
class="w-full rounded-2xl border border-stone-300 bg-white px-4 py-3 text-sm font-semibold text-stone-800 outline-none transition focus:border-stone-500"
|
|
@input="updateField('targetDate', $event)"
|
|
/>
|
|
</div>
|
|
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<div class="space-y-2">
|
|
<label class="text-[11px] font-bold tracking-[0.16em] text-stone-600">표시 시작일</label>
|
|
<input
|
|
:value="form.activeFrom"
|
|
type="date"
|
|
class="w-full rounded-2xl border border-stone-300 bg-white px-4 py-3 text-sm font-semibold text-stone-800 outline-none transition focus:border-stone-500"
|
|
@input="updateField('activeFrom', $event)"
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<label class="text-[11px] font-bold tracking-[0.16em] text-stone-600">표시 종료일</label>
|
|
<input
|
|
:value="form.activeUntil"
|
|
type="date"
|
|
class="w-full rounded-2xl border border-stone-300 bg-white px-4 py-3 text-sm font-semibold text-stone-800 outline-none transition focus:border-stone-500"
|
|
@input="updateField('activeUntil', $event)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="rounded-2xl border border-stone-200 bg-[#fbf7f0] px-4 py-3 text-[11px] font-semibold leading-5 tracking-[0.06em] text-stone-600">
|
|
여기서 목표와 표시 기간을 설정해 두면, 플래너 작성 화면에서는 해당 날짜에 보여줄지 여부만 간단히 ON/OFF 할 수 있습니다.
|
|
</p>
|
|
|
|
<p
|
|
v-if="message"
|
|
class="rounded-2xl border border-stone-300 bg-white/80 px-4 py-3 text-sm font-semibold leading-6 text-stone-700"
|
|
>
|
|
{{ message }}
|
|
</p>
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
<button
|
|
type="submit"
|
|
class="rounded-full bg-stone-900 px-5 py-3 text-xs font-bold tracking-[0.18em] text-white transition hover:bg-stone-700 disabled:cursor-not-allowed disabled:bg-stone-400"
|
|
:disabled="busy"
|
|
>
|
|
{{ busy ? '저장 중...' : editingGoalId ? '목표 수정' : '목표 추가' }}
|
|
</button>
|
|
<button
|
|
v-if="editingGoalId"
|
|
type="button"
|
|
class="rounded-full border border-stone-300 px-5 py-3 text-xs font-bold tracking-[0.18em] text-stone-600 transition hover:border-stone-500 hover:text-stone-900"
|
|
@click="emit('cancel-edit')"
|
|
>
|
|
취소
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<section class="rounded-[28px] border border-white/60 bg-white/75 p-6">
|
|
<div class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
|
<div>
|
|
<p class="text-[11px] font-bold uppercase tracking-[0.24em] text-stone-500">Goal Library</p>
|
|
<p class="mt-2 text-sm font-semibold leading-6 text-stone-600">
|
|
목표가 많아져도 플래너 작성 화면이 길어지지 않도록, 전체 관리는 이 화면에서 처리합니다.
|
|
</p>
|
|
</div>
|
|
<div class="grid gap-2 sm:grid-cols-[220px]">
|
|
<input
|
|
:value="query"
|
|
type="text"
|
|
class="rounded-2xl border border-stone-300 bg-white px-4 py-3 text-sm font-semibold text-stone-800 outline-none transition focus:border-stone-500"
|
|
placeholder="목표 검색"
|
|
@input="emit('update:query', $event.target.value)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-5 grid gap-4">
|
|
<article
|
|
v-for="goal in goals"
|
|
:key="goal.id"
|
|
class="rounded-[24px] border px-5 py-5 transition"
|
|
:class="editingGoalId === goal.id ? 'border-stone-900 bg-[#f7f1e7] shadow-[0_18px_40px_rgba(28,25,23,0.10)]' : 'border-stone-200 bg-white'"
|
|
>
|
|
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
|
<div class="space-y-2">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<p class="text-lg font-semibold tracking-[-0.03em] text-stone-900">{{ goal.title }}</p>
|
|
<span
|
|
v-if="editingGoalId === goal.id"
|
|
class="rounded-full bg-stone-900 px-3 py-1 text-[10px] font-bold tracking-[0.16em] text-white"
|
|
>
|
|
수정 중
|
|
</span>
|
|
<span
|
|
v-if="isActiveOnSelectedDate(goal)"
|
|
class="rounded-full bg-amber-100 px-3 py-1 text-[10px] font-bold tracking-[0.16em] text-amber-700"
|
|
>
|
|
현재 날짜에 표시 중
|
|
</span>
|
|
</div>
|
|
<p class="text-sm font-semibold text-stone-600">목표일 {{ goal.targetDate }}</p>
|
|
<p class="text-[11px] font-semibold tracking-[0.06em] text-stone-500">
|
|
{{ goal.activeFrom && goal.activeUntil ? `표시 기간 ${goal.activeFrom} ~ ${goal.activeUntil}` : '표시 기간 미설정' }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
<button
|
|
type="button"
|
|
class="rounded-full border border-stone-300 px-4 py-2 text-[11px] font-bold tracking-[0.16em] text-stone-600 transition hover:border-stone-500 hover:text-stone-900"
|
|
@click="emit('start-edit', goal)"
|
|
>
|
|
수정
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="rounded-full border border-red-200 px-4 py-2 text-[11px] font-bold tracking-[0.16em] text-red-500 transition hover:border-red-400 hover:bg-red-50"
|
|
@click="emit('delete-goal', goal)"
|
|
>
|
|
삭제
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<div
|
|
v-if="goals.length === 0"
|
|
class="rounded-[24px] border border-dashed border-stone-300 bg-white px-5 py-8 text-center"
|
|
>
|
|
<p class="text-sm font-semibold text-stone-600">조건에 맞는 목표가 없습니다.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</section>
|
|
</template>
|