Files
photoai/src/renderer/components/AnniversaryManager.tsx
T
koriweb 9b044449a0 Birthday & anniversary photo collections
- profiles get an optional birthday (MM-DD); photos of that person taken on the
  date are also copied into Birthdays/<person>/<year>/
- app-wide anniversaries (label + MM-DD); any photo taken on the date is copied
  into Anniversaries/<label>/<year>/ (including faceless photos and videos)
- copy (not move) so normal person/date sorting is preserved
- CaptureDate gains day; new collection path builder; scanner skips the new folders
- UI: birthday input in profile create/edit + new Anniversaries manager

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 20:11:29 +09:00

82 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { useStore } from '../store'
import { useT } from '../i18n'
import type { Anniversary } from '@shared/types'
/** 기념일(앱 전체 공통) 등록/삭제. 해당 날짜 사진을 Anniversaries/이름/연도 폴더에 모음 */
export function AnniversaryManager(): JSX.Element {
const t = useT()
const anniversaries = useStore((s) => s.anniversaries)
const updateSettings = useStore((s) => s.updateSettings)
const [label, setLabel] = useState('')
const [date, setDate] = useState('') // YYYY-MM-DD
const add = async () => {
const l = label.trim()
if (!l || !date) return
const item: Anniversary = { id: crypto.randomUUID(), label: l, date: date.slice(5) } // MM-DD
await updateSettings({ anniversaries: [...anniversaries, item] })
setLabel('')
setDate('')
}
const remove = async (id: string) => {
await updateSettings({ anniversaries: anniversaries.filter((a) => a.id !== id) })
}
// MM-DD → 보기용 "MM.DD"
const fmt = (mmdd: string): string => mmdd.replace('-', '.')
return (
<div className="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-4">
<h2 className="font-semibold dark:text-slate-100 mb-1">🎉 {t('anniv.section')}</h2>
<p className="text-[11px] text-slate-400 mb-3">{t('anniv.hint')}</p>
<div className="flex gap-2 mb-3">
<input
className="flex-1 border border-slate-300 dark:border-slate-600 dark:bg-slate-700 dark:text-slate-100 rounded-lg px-3 py-2 text-sm"
placeholder={t('anniv.labelPlaceholder')}
value={label}
onChange={(e) => setLabel(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && add()}
/>
<input
type="date"
className="border border-slate-300 dark:border-slate-600 dark:bg-slate-700 dark:text-slate-100 rounded-lg px-2 py-2 text-sm"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
<button
className="bg-brand text-white rounded-lg px-4 text-sm font-medium disabled:opacity-40"
onClick={add}
disabled={!label.trim() || !date}
>
{t('common.add')}
</button>
</div>
{anniversaries.length === 0 ? (
<p className="text-xs text-slate-400">{t('anniv.empty')}</p>
) : (
<div className="flex flex-wrap gap-2">
{anniversaries.map((a) => (
<div
key={a.id}
className="flex items-center gap-2 pl-3 pr-2 py-1 rounded-full border border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-700/50"
>
<span className="text-xs font-medium dark:text-slate-200">{a.label}</span>
<span className="text-[11px] text-slate-400 tabular-nums">{fmt(a.date)}</span>
<button
className="text-slate-400 hover:text-red-500 text-xs leading-none"
onClick={() => remove(a.id)}
title="삭제"
>
×
</button>
</div>
))}
</div>
)}
</div>
)
}