Files
sam-react-prod/src/components/business/CEODashboard/modals/ScheduleDetailModal.tsx
byeongcheolryu 29e7b41615 chore(WEB): 다수 컴포넌트 개선 및 CEO 대시보드 추가
- CEO 대시보드 컴포넌트 추가
- AuthenticatedLayout 개선
- 각 모듈 actions.ts 에러 핸들링 개선
- API fetch-wrapper, refresh-token 로직 개선
- ReceivablesStatus 컴포넌트 업데이트
- globals.css 스타일 업데이트
- 기타 다수 컴포넌트 수정

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-08 17:15:42 +09:00

289 lines
9.2 KiB
TypeScript

'use client';
import { useState, useCallback, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Checkbox } from '@/components/ui/checkbox';
import { TimePicker } from '@/components/ui/time-picker';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import type { CalendarScheduleItem } from '../types';
// 색상 옵션
const COLOR_OPTIONS = [
{ value: 'green', label: '녹색', className: 'bg-green-500' },
{ value: 'blue', label: '파란색', className: 'bg-blue-500' },
{ value: 'red', label: '빨간색', className: 'bg-red-500' },
{ value: 'yellow', label: '노란색', className: 'bg-yellow-500' },
{ value: 'purple', label: '보라색', className: 'bg-purple-500' },
];
// 부서 목록 (목업)
const DEPARTMENT_OPTIONS = [
{ value: 'all', label: '전체' },
{ value: 'sales', label: '영업부' },
{ value: 'production', label: '생산부' },
{ value: 'quality', label: '품질부' },
{ value: 'management', label: '경영지원부' },
];
interface ScheduleFormData {
title: string;
department: string;
startDate: string;
endDate: string;
isAllDay: boolean; // 종일 여부 (true: 종일, false: 시간 지정)
startTime: string;
endTime: string;
color: string;
content: string;
}
interface ScheduleDetailModalProps {
isOpen: boolean;
onClose: () => void;
schedule: CalendarScheduleItem | null;
onSave: (data: ScheduleFormData) => void;
onDelete?: (id: string) => void;
}
export function ScheduleDetailModal({
isOpen,
onClose,
schedule,
onSave,
onDelete,
}: ScheduleDetailModalProps) {
const isEditMode = schedule && schedule.id !== '';
const [formData, setFormData] = useState<ScheduleFormData>({
title: '',
department: 'all',
startDate: '',
endDate: '',
isAllDay: true, // 기본값: 종일
startTime: '09:00',
endTime: '10:00',
color: 'green',
content: '',
});
// schedule이 변경될 때 폼 데이터 초기화
useEffect(() => {
if (schedule) {
// 시간이 있으면 종일 아님, 없으면 종일
const hasTimeValue = !!(schedule.startTime || schedule.endTime);
setFormData({
title: schedule.title || '',
department: schedule.department || 'all',
startDate: schedule.startDate || '',
endDate: schedule.endDate || schedule.startDate || '',
isAllDay: !hasTimeValue, // 시간이 없으면 종일
startTime: schedule.startTime || '09:00',
endTime: schedule.endTime || '10:00',
color: schedule.color || 'green',
content: '',
});
}
}, [schedule]);
const handleFieldChange = useCallback(
(field: keyof ScheduleFormData, value: string | boolean) => {
setFormData((prev) => ({ ...prev, [field]: value }));
},
[]
);
const handleSave = useCallback(() => {
onSave(formData);
onClose();
}, [formData, onSave, onClose]);
const handleDelete = useCallback(() => {
if (schedule?.id && onDelete) {
onDelete(schedule.id);
onClose();
}
}, [schedule, onDelete, onClose]);
const handleCancel = useCallback(() => {
onClose();
}, [onClose]);
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && handleCancel()}>
<DialogContent className="w-[95vw] max-w-[480px] sm:max-w-[480px] p-6">
<DialogHeader className="pb-2">
<DialogTitle className="text-lg font-bold"> </DialogTitle>
</DialogHeader>
<div className="space-y-6 py-4">
{/* 제목 */}
<div className="flex items-center gap-6">
<label className="w-10 text-sm font-medium text-gray-700 shrink-0">
</label>
<Input
value={formData.title}
onChange={(e) => handleFieldChange('title', e.target.value)}
placeholder="제목"
className="flex-1"
/>
</div>
{/* 대상 (부서) */}
<div className="flex items-center gap-6">
<label className="w-10 text-sm font-medium text-gray-700 shrink-0">
</label>
<Select
value={formData.department}
onValueChange={(value) => handleFieldChange('department', value)}
>
<SelectTrigger className="flex-1">
<SelectValue placeholder="부서명" />
</SelectTrigger>
<SelectContent>
{DEPARTMENT_OPTIONS.map((dept) => (
<SelectItem key={dept.value} value={dept.value}>
{dept.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 기간 */}
<div className="flex items-center gap-6">
<label className="w-10 text-sm font-medium text-gray-700 shrink-0">
</label>
<div className="flex-1 flex items-center gap-2">
<Input
type="date"
value={formData.startDate}
onChange={(e) => handleFieldChange('startDate', e.target.value)}
className="flex-1"
/>
<span className="text-gray-400 px-1">~</span>
<Input
type="date"
value={formData.endDate}
onChange={(e) => handleFieldChange('endDate', e.target.value)}
className="flex-1"
/>
</div>
</div>
{/* 시간 */}
<div className="flex items-start gap-6">
<label className="w-10 text-sm font-medium text-gray-700 shrink-0 pt-2">
</label>
<div className="flex-1 space-y-3">
{/* 종일 체크박스 */}
<div className="flex items-center gap-2">
<Checkbox
id="isAllDay"
checked={formData.isAllDay}
onCheckedChange={(checked) =>
handleFieldChange('isAllDay', checked === true)
}
/>
<label htmlFor="isAllDay" className="text-sm text-gray-600 cursor-pointer">
</label>
</div>
{/* 시간 선택 (종일 체크 해제 시 표시) */}
{!formData.isAllDay && (
<div className="flex items-center gap-2">
<TimePicker
value={formData.startTime}
onChange={(value) => handleFieldChange('startTime', value)}
placeholder="시작 시간"
className="flex-1"
minuteStep={5}
/>
<span className="text-gray-400 px-1">~</span>
<TimePicker
value={formData.endTime}
onChange={(value) => handleFieldChange('endTime', value)}
placeholder="종료 시간"
className="flex-1"
minuteStep={5}
/>
</div>
)}
</div>
</div>
{/* 색상 */}
<div className="flex items-center gap-6">
<label className="w-10 text-sm font-medium text-gray-700 shrink-0">
</label>
<div className="flex gap-3">
{COLOR_OPTIONS.map((color) => (
<button
key={color.value}
type="button"
className={`w-8 h-8 rounded-full ${color.className} transition-all ${
formData.color === color.value
? 'ring-2 ring-offset-2 ring-gray-400'
: 'hover:scale-110'
}`}
onClick={() => handleFieldChange('color', color.value)}
title={color.label}
/>
))}
</div>
</div>
{/* 내용 */}
<div className="flex items-start gap-6">
<label className="w-10 text-sm font-medium text-gray-700 shrink-0 pt-2">
</label>
<Textarea
value={formData.content}
onChange={(e) => handleFieldChange('content', e.target.value)}
placeholder="내용"
className="flex-1 min-h-[120px] resize-none"
/>
</div>
</div>
<DialogFooter className="flex gap-2 pt-2">
{isEditMode && onDelete && (
<Button
variant="outline"
onClick={handleDelete}
className="bg-gray-800 text-white hover:bg-gray-900"
>
</Button>
)}
<Button
onClick={handleSave}
className="bg-gray-800 text-white hover:bg-gray-900"
>
{isEditMode ? '수정' : '등록'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}