guardia-mall/frontend/src/pages/uiws/ScheduleCalendar.tsx
DESKTOP-TKLFCPR\ython b33337c6ee feat(uiws): UIWS 업무모듈(업무일지·일정·쪽지·통계)+2FA 이식
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:27:13 +09:00

122 lines
6.5 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 { 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<Sched[]>([])
const [loading, setLoading] = useState(false)
const [showForm, setShowForm] = useState<string | null>(null) // date string
const [detail, setDetail] = useState<any>(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 (
<div className="uiws-scope">
<PageHeader title="일정" subtitle="UIWS 이식 · 개인/부서 일정 달력"
actions={<Button onClick={() => setShowForm(`${ym}-01`)}>+ </Button>} />
<Panel style={{ display: 'flex', gap: 10, alignItems: 'center', marginBottom: 16 }}>
<Button variant="ghost" onClick={() => move(-1)}> </Button>
<div style={{ fontWeight: 700, minWidth: 130, textAlign: 'center', color: 'var(--uiws-text)' }}>{ym}</div>
<Button variant="ghost" onClick={() => move(1)}> </Button>
<div style={{ flex: 1 }} />
<Button variant={type === 'PERSONAL' ? 'primary' : 'ghost'} onClick={() => setType('PERSONAL')}></Button>
<Button variant={type === 'DEPT' ? 'primary' : 'ghost'} onClick={() => setType('DEPT')}></Button>
</Panel>
{loading ? <Spinner /> : (
<div style={{ background: 'var(--uiws-surface)', border: '1px solid var(--uiws-border)', borderRadius: 12, overflow: 'hidden' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7,1fr)' }}>
{WEEK.map((w, i) => (
<div key={w} style={{ padding: '10px 0', textAlign: 'center', fontSize: 12, fontWeight: 600,
color: i === 0 ? 'var(--uiws-danger)' : 'var(--uiws-text-muted)', borderBottom: '1px solid var(--uiws-border)' }}>{w}</div>
))}
{cells.map((d, i) => (
<div key={i} onClick={() => 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 && <div style={{ fontSize: 12, color: 'var(--uiws-text-muted)', marginBottom: 4 }}>{d}</div>}
{d && byDay(d).slice(0, 3).map(s => (
<div key={s.scheduleId} onClick={e => { 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}
</div>
))}
</div>
))}
</div>
</div>
)}
{showForm && <ScheduleForm date={showForm} type={type} onClose={() => setShowForm(null)} onSaved={() => { setShowForm(null); load() }} />}
{detail && <ScheduleDetailModal detail={detail} onClose={() => setDetail(null)} onDeleted={() => { setDetail(null); load() }} />}
</div>
)
}
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:00`)
const [endDt, setEndDt] = useState(`${date}T18:00:00`)
const [content, setContent] = useState('')
const [err, setErr] = useState('')
const save = async () => {
setErr('')
try {
await createSchedule({ scheType: type, title, startDt, endDt, content, attachmentIds: [] })
onSaved()
} catch (e: any) { setErr(e?.response?.data?.message || '저장 실패') }
}
return (
<Modal title="일정 등록" onClose={onClose}
footer={<><Button variant="ghost" onClick={onClose}></Button><Button onClick={save}></Button></>}>
<FormField label="제목"><Input value={title} onChange={setTitle} style={{ width: '100%' }} /></FormField>
<FormField label="시작(yyyy-MM-ddTHH:mm:ss)"><Input value={startDt} onChange={setStartDt} style={{ width: '100%' }} /></FormField>
<FormField label="종료"><Input value={endDt} onChange={setEndDt} style={{ width: '100%' }} /></FormField>
<FormField label="내용"><Input value={content} onChange={setContent} style={{ width: '100%' }} /></FormField>
{err && <div style={{ color: 'var(--uiws-danger)', fontSize: 13 }}>{err}</div>}
</Modal>
)
}
function ScheduleDetailModal({ detail, onClose, onDeleted }: { detail: any; onClose: () => void; onDeleted: () => void }) {
const remove = async () => { await deleteSchedule(detail.scheduleId); onDeleted() }
return (
<Modal title={detail.title} onClose={onClose}
footer={<><Button variant="danger" onClick={remove}></Button><Button variant="ghost" onClick={onClose}></Button></>}>
<div style={{ fontSize: 13, color: 'var(--uiws-text-muted)', marginBottom: 8 }}>{detail.scheType} · {detail.startDt} ~ {detail.endDt}</div>
<div style={{ fontSize: 14, color: 'var(--uiws-text)' }}>{detail.content || '내용 없음'}</div>
</Modal>
)
}