import { useEffect, useState } from 'react' import { scheduleCalendar, createSchedule, scheduleDetail, deleteSchedule } from '../../api/uiws' import { PageHeader, Panel, Button, Modal, FormField, Input, Spinner } from '../../components/uiws/ui' interface Sched { scheduleId: number; title: string; startDt: string; endDt: string; scheType: string; importanceCd?: string } const WEEK = ['일', '월', '화', '수', '목', '금', '토'] export default function ScheduleCalendar() { const [base, setBase] = useState(new Date()) const [type, setType] = useState<'PERSONAL' | 'DEPT'>('PERSONAL') const [items, setItems] = useState([]) const [loading, setLoading] = useState(false) const [showForm, setShowForm] = useState(null) // date string const [detail, setDetail] = useState(null) const ym = `${base.getFullYear()}-${String(base.getMonth() + 1).padStart(2, '0')}` const load = async () => { setLoading(true) try { const res = await scheduleCalendar({ type, view: 'month', baseDate: `${ym}-01` }) setItems(res.data.data ?? []) } finally { setLoading(false) } } useEffect(() => { load() }, [ym, type]) const first = new Date(base.getFullYear(), base.getMonth(), 1) const startPad = first.getDay() const daysInMonth = new Date(base.getFullYear(), base.getMonth() + 1, 0).getDate() const cells: (number | null)[] = [...Array(startPad).fill(null), ...Array.from({ length: daysInMonth }, (_, i) => i + 1)] const byDay = (d: number) => { const ds = `${ym}-${String(d).padStart(2, '0')}` return items.filter(s => (s.startDt ?? '').slice(0, 10) <= ds && ds <= (s.endDt ?? '').slice(0, 10)) } const move = (delta: number) => setBase(new Date(base.getFullYear(), base.getMonth() + delta, 1)) return (
setShowForm(`${ym}-01`)}>+ 일정 등록} />
{ym}
{loading ? : (
{WEEK.map((w, i) => (
{w}
))} {cells.map((d, i) => (
d && setShowForm(`${ym}-${String(d).padStart(2, '0')}`)} style={{ minHeight: 96, padding: 6, borderRight: '1px solid var(--uiws-border)', borderBottom: '1px solid var(--uiws-border)', cursor: d ? 'pointer' : 'default' }}> {d &&
{d}
} {d && byDay(d).slice(0, 3).map(s => (
{ e.stopPropagation(); scheduleDetail(s.scheduleId).then(r => setDetail(r.data.data)) }} style={{ fontSize: 11, padding: '2px 6px', borderRadius: 6, marginBottom: 3, background: 'var(--uiws-primary-soft)', color: 'var(--uiws-primary)', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}> {s.title}
))}
))}
)} {showForm && setShowForm(null)} onSaved={() => { setShowForm(null); load() }} />} {detail && setDetail(null)} onDeleted={() => { setDetail(null); load() }} />}
) } // datetime-local(yyyy-MM-ddTHH:mm) → API 계약(yyyy-MM-ddTHH:mm:ss) 보정: 초 없으면 :00 부가 function toApiDt(v: string): string { if (!v) return v return v.length === 16 ? `${v}:00` : v } function ScheduleForm({ date, type, onClose, onSaved }: { date: string; type: string; onClose: () => void; onSaved: () => void }) { const [title, setTitle] = useState('') const [startDt, setStartDt] = useState(`${date}T09:00`) const [endDt, setEndDt] = useState(`${date}T18:00`) const [content, setContent] = useState('') const [err, setErr] = useState('') const save = async () => { setErr('') try { await createSchedule({ scheType: type, title, startDt: toApiDt(startDt), endDt: toApiDt(endDt), content, attachmentIds: [] }) onSaved() } catch (e: any) { setErr(e?.response?.data?.message || '저장 실패') } } return ( }> {err &&
{err}
}
) } function ScheduleDetailModal({ detail, onClose, onDeleted }: { detail: any; onClose: () => void; onDeleted: () => void }) { const remove = async () => { await deleteSchedule(detail.scheduleId); onDeleted() } return ( }>
{detail.scheType} · {detail.startDt} ~ {detail.endDt}
{detail.content || '내용 없음'}
) }