From 18ca4ad784aeeab9ab7a13bbc8b3c13b42ca5e49 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Fri, 7 Nov 2025 12:01:16 +0900 Subject: (김준회) 결재 미리보기 공통컴포넌트 중복 제거, 기존 코드의 미리보기 호출부 수정, 템플릿 작성 가이드 간략히 추가, 결재 미리보기시 첨부파일 편집 처리 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/approval/approval-preview-dialog.tsx | 156 +++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) (limited to 'lib/approval/approval-preview-dialog.tsx') diff --git a/lib/approval/approval-preview-dialog.tsx b/lib/approval/approval-preview-dialog.tsx index a91e146c..8bb7ba0f 100644 --- a/lib/approval/approval-preview-dialog.tsx +++ b/lib/approval/approval-preview-dialog.tsx @@ -25,6 +25,29 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useMediaQuery } from "@/hooks/use-media-query"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Paperclip } from "lucide-react"; +import { Separator } from "@/components/ui/separator"; +import prettyBytes from "pretty-bytes"; +import { + Dropzone, + DropzoneDescription, + DropzoneInput, + DropzoneTitle, + DropzoneUploadIcon, + DropzoneZone, +} from "@/components/ui/dropzone"; +import { + FileList, + FileListAction, + FileListDescription, + FileListHeader, + FileListIcon, + FileListInfo, + FileListItem, + FileListName, + FileListSize, +} from "@/components/ui/file-list"; import { ApprovalLineSelector, @@ -63,9 +86,16 @@ export interface ApprovalPreviewDialogProps { onConfirm: (data: { approvers: string[]; title: string; + attachments?: File[]; }) => Promise; /** 제목 수정 가능 여부 (기본: true) */ allowTitleEdit?: boolean; + /** 첨부파일 UI 활성화 여부 (기본: false) */ + enableAttachments?: boolean; + /** 최대 첨부파일 개수 (기본: 10) */ + maxAttachments?: number; + /** 최대 파일 크기 (기본: 100MB) */ + maxFileSize?: number; } /** @@ -102,6 +132,9 @@ export function ApprovalPreviewDialog({ defaultApprovers = [], onConfirm, allowTitleEdit = true, + enableAttachments = false, + maxAttachments = 10, + maxFileSize = 100 * 1024 * 1024, // 100MB }: ApprovalPreviewDialogProps) { const isDesktop = useMediaQuery("(min-width: 768px)"); @@ -113,6 +146,7 @@ export function ApprovalPreviewDialog({ const [title, setTitle] = React.useState(initialTitle); const [approvalLines, setApprovalLines] = React.useState([]); const [previewHtml, setPreviewHtml] = React.useState(""); + const [attachments, setAttachments] = React.useState([]); // 템플릿 로딩 및 미리보기 생성 React.useEffect(() => { @@ -155,6 +189,7 @@ export function ApprovalPreviewDialog({ setTitle(initialTitle); setApprovalLines([]); setPreviewHtml(""); + setAttachments([]); return; } @@ -195,6 +230,36 @@ export function ApprovalPreviewDialog({ setApprovalLines(lines); }; + // 파일 드롭 핸들러 + const handleDropAccepted = React.useCallback( + (files: File[]) => { + if (attachments.length + files.length > maxAttachments) { + toast.error(`최대 ${maxAttachments}개의 파일만 첨부할 수 있습니다.`); + return; + } + + // 중복 파일 체크 + const newFiles = files.filter( + (file) => !attachments.some((existing) => existing.name === file.name && existing.size === file.size) + ); + + if (newFiles.length !== files.length) { + toast.warning("일부 중복된 파일은 제외되었습니다."); + } + + setAttachments((prev) => [...prev, ...newFiles]); + }, + [attachments, maxAttachments] + ); + + const handleDropRejected = React.useCallback(() => { + toast.error(`파일 크기는 ${prettyBytes(maxFileSize)} 이하여야 합니다.`); + }, [maxFileSize]); + + const handleRemoveFile = React.useCallback((index: number) => { + setAttachments((prev) => prev.filter((_, i) => i !== index)); + }, []); + // 제출 핸들러 const handleSubmit = async () => { try { @@ -225,6 +290,7 @@ export function ApprovalPreviewDialog({ await onConfirm({ approvers: approverEpIds, title: title.trim(), + attachments: enableAttachments ? attachments : undefined, }); // 성공 시 다이얼로그 닫기 @@ -275,6 +341,96 @@ export function ApprovalPreviewDialog({ /> + {/* 첨부파일 섹션 (enableAttachments가 true일 때만 표시) */} + {enableAttachments && ( + <> + + + + + + + 첨부파일 + {attachments.length > 0 && ( + + ({attachments.length}/{maxAttachments}) + + )} + + + 결재 문서에 첨부할 파일을 추가하세요 (최대 {maxAttachments}개, 파일당 최대 {prettyBytes(maxFileSize)}) + + + + {/* 파일 드롭존 */} + {attachments.length < maxAttachments && ( + + {() => ( + + +
+ +
+ 파일을 드래그하거나 클릭하여 업로드 + + 모든 형식의 파일을 첨부할 수 있습니다 + +
+
+
+ )} +
+ )} + + {/* 첨부된 파일 목록 */} + {attachments.length > 0 && ( + + {attachments.map((file, index) => ( + + + + +
+ {file.name} + + {file.size} + {file.type && ( + <> + + {file.type} + + )} + +
+
+
+ handleRemoveFile(index)} + disabled={isSubmitting} + title="파일 제거" + > + + +
+ ))} +
+ )} + + {attachments.length === 0 && ( +

+ 첨부된 파일이 없습니다 +

+ )} +
+
+ + )} + {/* 템플릿 미리보기 */}
-- cgit v1.2.3