'use client'; import { useState, useCallback, useRef, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { AlertTriangle, List, Mic, X, Undo2, Upload } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { PageLayout } from '@/components/organisms/PageLayout'; import { PageHeader } from '@/components/organisms/PageHeader'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { toast } from 'sonner'; import type { Issue, IssueFormData, IssueImage, IssueStatus, IssueCategory, IssuePriority } from './types'; import { ISSUE_STATUS_FORM_OPTIONS, ISSUE_PRIORITY_FORM_OPTIONS, ISSUE_CATEGORY_FORM_OPTIONS, MOCK_CONSTRUCTION_NUMBERS, MOCK_ISSUE_PARTNERS, MOCK_ISSUE_SITES, MOCK_ISSUE_REPORTERS, MOCK_ISSUE_ASSIGNEES, } from './types'; import { createIssue, updateIssue, withdrawIssue } from './actions'; interface IssueDetailFormProps { issue?: Issue; mode?: 'view' | 'edit' | 'create'; } export default function IssueDetailForm({ issue, mode = 'view' }: IssueDetailFormProps) { const router = useRouter(); const isEditMode = mode === 'edit'; const isCreateMode = mode === 'create'; const isViewMode = mode === 'view'; // 이미지 업로드 ref const imageInputRef = useRef(null); // 철회 다이얼로그 const [withdrawDialogOpen, setWithdrawDialogOpen] = useState(false); // 폼 상태 const [formData, setFormData] = useState({ issueNumber: issue?.issueNumber || '', constructionNumber: issue?.constructionNumber || '', partnerName: issue?.partnerName || '', siteName: issue?.siteName || '', constructionPM: issue?.constructionPM || '', constructionManagers: issue?.constructionManagers || '', reporter: issue?.reporter || '', assignee: issue?.assignee || '', reportDate: issue?.reportDate || new Date().toISOString().split('T')[0], resolvedDate: issue?.resolvedDate || '', status: issue?.status || 'received', category: issue?.category || 'material', priority: issue?.priority || 'normal', title: issue?.title || '', content: issue?.content || '', images: issue?.images || [], }); const [isSubmitting, setIsSubmitting] = useState(false); // 시공번호 변경 시 관련 정보 자동 채움 useEffect(() => { if (formData.constructionNumber) { const construction = MOCK_CONSTRUCTION_NUMBERS.find( (c) => c.value === formData.constructionNumber ); if (construction) { setFormData((prev) => ({ ...prev, partnerName: construction.partnerName, siteName: construction.siteName, constructionPM: construction.pm, constructionManagers: construction.managers, })); } } }, [formData.constructionNumber]); // 담당자 지정 시 상태를 처리중으로 자동 변경 const handleAssigneeChange = useCallback((value: string) => { setFormData((prev) => ({ ...prev, assignee: value, // 담당자가 지정되고 현재 상태가 '접수'이면 '처리중'으로 변경 status: value && prev.status === 'received' ? 'in_progress' : prev.status, })); if (value && formData.status === 'received') { toast.info('담당자가 지정되어 상태가 "처리중"으로 변경되었습니다.'); } }, [formData.status]); // 중요도 변경 시 긴급이면 알림 표시 const handlePriorityChange = useCallback((value: string) => { setFormData((prev) => ({ ...prev, priority: value as IssuePriority })); if (value === 'urgent') { toast.warning('긴급 이슈로 설정되었습니다. 공사PM과 대표에게 알림이 발송됩니다.'); } }, []); // 입력 핸들러 const handleInputChange = useCallback( (field: keyof IssueFormData) => (e: React.ChangeEvent) => { setFormData((prev) => ({ ...prev, [field]: e.target.value })); }, [] ); const handleSelectChange = useCallback((field: keyof IssueFormData) => (value: string) => { setFormData((prev) => ({ ...prev, [field]: value })); }, []); // 수정 버튼 클릭 const handleEditClick = useCallback(() => { if (issue?.id) { router.push(`/ko/construction/project/issue-management/${issue.id}/edit`); } }, [router, issue?.id]); // 저장 const handleSubmit = useCallback(async () => { if (!formData.title.trim()) { toast.error('제목을 입력해주세요.'); return; } if (!formData.constructionNumber) { toast.error('시공번호를 선택해주세요.'); return; } setIsSubmitting(true); try { if (isCreateMode) { const result = await createIssue({ issueNumber: `ISS-${Date.now()}`, constructionNumber: formData.constructionNumber, partnerName: formData.partnerName, siteName: formData.siteName, constructionPM: formData.constructionPM, constructionManagers: formData.constructionManagers, category: formData.category, title: formData.title, content: formData.content, reporter: formData.reporter, reportDate: formData.reportDate, resolvedDate: formData.resolvedDate || null, assignee: formData.assignee, priority: formData.priority, status: formData.status, images: formData.images, }); if (result.success) { toast.success('이슈가 등록되었습니다.'); router.push('/ko/construction/project/issue-management'); } else { toast.error(result.error || '이슈 등록에 실패했습니다.'); } } else { const result = await updateIssue(issue!.id, { constructionNumber: formData.constructionNumber, partnerName: formData.partnerName, siteName: formData.siteName, constructionPM: formData.constructionPM, constructionManagers: formData.constructionManagers, category: formData.category, title: formData.title, content: formData.content, reporter: formData.reporter, reportDate: formData.reportDate, resolvedDate: formData.resolvedDate || null, assignee: formData.assignee, priority: formData.priority, status: formData.status, images: formData.images, }); if (result.success) { toast.success('이슈가 수정되었습니다.'); router.push('/ko/construction/project/issue-management'); } else { toast.error(result.error || '이슈 수정에 실패했습니다.'); } } } catch { toast.error('저장에 실패했습니다.'); } finally { setIsSubmitting(false); } }, [formData, isCreateMode, issue, router]); // 취소 const handleCancel = useCallback(() => { router.back(); }, [router]); // 철회 const handleWithdraw = useCallback(async () => { if (!issue?.id) return; try { const result = await withdrawIssue(issue.id); if (result.success) { toast.success('이슈가 철회되었습니다.'); router.push('/ko/construction/project/issue-management'); } else { toast.error(result.error || '이슈 철회에 실패했습니다.'); } } catch { toast.error('이슈 철회에 실패했습니다.'); } finally { setWithdrawDialogOpen(false); } }, [issue?.id, router]); // 이미지 업로드 핸들러 const handleImageUpload = useCallback((e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0) return; const newImages: IssueImage[] = Array.from(files).map((file, index) => ({ id: `img-${Date.now()}-${index}`, url: URL.createObjectURL(file), fileName: file.name, uploadedAt: new Date().toISOString(), })); setFormData((prev) => ({ ...prev, images: [...prev.images, ...newImages], })); toast.success(`${files.length}개의 이미지가 추가되었습니다.`); // 입력 초기화 if (imageInputRef.current) { imageInputRef.current.value = ''; } }, []); // 이미지 삭제 const handleImageRemove = useCallback((imageId: string) => { setFormData((prev) => ({ ...prev, images: prev.images.filter((img) => img.id !== imageId), })); toast.success('이미지가 삭제되었습니다.'); }, []); // 녹음 버튼 (UI만) const handleRecordClick = useCallback(() => { toast.info('녹음 기능은 준비 중입니다.'); }, []); // 읽기 전용 여부 const isReadOnly = isViewMode; return ( ) : (
) } />
{/* 이슈 정보 카드 */} 이슈 정보
{/* 이슈번호 */}
{/* 시공번호 */}
{/* 거래처 */}
{/* 현장 */}
{/* 공사PM (자동) */}
{/* 공사담당자 (자동) */}
{/* 보고자 */}
{/* 담당자 */}
{/* 이슈보고일 */}
{/* 이슈해결일 */}
{/* 상태 */}
{/* 이슈 보고 카드 */} 이슈 보고
{/* 구분 & 중요도 */}
{/* 구분 */}
{/* 중요도 */}
{/* 제목 */}
{/* 내용 */}
{!isReadOnly && ( )}