From 18954df6565108a469fb1608ea3715dd9bb1b02d Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 1 Sep 2025 09:12:09 +0000 Subject: (대표님) 구매 기본계약, gtc 개발 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gtc-vendor/bulk-update-gtc-clauses-dialog.tsx | 276 +++++++++ .../gtc-vendor/clause-preview-viewer.tsx | 570 +++++++++++++++++++ lib/basic-contract/gtc-vendor/clause-table.tsx | 261 +++++++++ .../gtc-vendor/clause-variable-settings-dialog.tsx | 364 ++++++++++++ .../gtc-vendor/create-gtc-clause-dialog.tsx | 625 +++++++++++++++++++++ .../gtc-vendor/delete-gtc-clauses-dialog.tsx | 175 ++++++ .../gtc-vendor/duplicate-gtc-clause-dialog.tsx | 372 ++++++++++++ lib/basic-contract/gtc-vendor/excel-import.tsx | 340 +++++++++++ .../gtc-vendor/generate-variable-names-dialog.tsx | 348 ++++++++++++ .../gtc-vendor/gtc-clauses-table-columns.tsx | 409 ++++++++++++++ .../gtc-vendor/gtc-clauses-table-floating-bar.tsx | 239 ++++++++ .../gtc-clauses-table-toolbar-actions.tsx | 350 ++++++++++++ .../gtc-vendor/import-excel-dialog.tsx | 381 +++++++++++++ .../gtc-vendor/markdown-image-editor.tsx | 360 ++++++++++++ .../gtc-vendor/preview-document-dialog.tsx | 272 +++++++++ .../gtc-vendor/reorder-gtc-clauses-dialog.tsx | 540 ++++++++++++++++++ .../gtc-vendor/update-gtc-clause-sheet.tsx | 522 +++++++++++++++++ .../gtc-vendor/view-clause-variables-dialog.tsx | 231 ++++++++ lib/basic-contract/service.ts | 556 ++++++++++++++++-- ...basic-contract-detail-table-toolbar-actions.tsx | 10 +- .../basic-contracts-detail-columns.tsx | 72 ++- .../status-detail/basic-contracts-detail-table.tsx | 66 ++- lib/basic-contract/validations.ts | 10 + .../vendor-table/basic-contract-sign-dialog.tsx | 237 ++++---- .../update-vendor-document-status-button.tsx | 145 +++++ lib/basic-contract/viewer/GtcClausesComponent.tsx | 262 +++++++-- .../viewer/basic-contract-sign-viewer.tsx | 256 ++++++--- 27 files changed, 7924 insertions(+), 325 deletions(-) create mode 100644 lib/basic-contract/gtc-vendor/bulk-update-gtc-clauses-dialog.tsx create mode 100644 lib/basic-contract/gtc-vendor/clause-preview-viewer.tsx create mode 100644 lib/basic-contract/gtc-vendor/clause-table.tsx create mode 100644 lib/basic-contract/gtc-vendor/clause-variable-settings-dialog.tsx create mode 100644 lib/basic-contract/gtc-vendor/create-gtc-clause-dialog.tsx create mode 100644 lib/basic-contract/gtc-vendor/delete-gtc-clauses-dialog.tsx create mode 100644 lib/basic-contract/gtc-vendor/duplicate-gtc-clause-dialog.tsx create mode 100644 lib/basic-contract/gtc-vendor/excel-import.tsx create mode 100644 lib/basic-contract/gtc-vendor/generate-variable-names-dialog.tsx create mode 100644 lib/basic-contract/gtc-vendor/gtc-clauses-table-columns.tsx create mode 100644 lib/basic-contract/gtc-vendor/gtc-clauses-table-floating-bar.tsx create mode 100644 lib/basic-contract/gtc-vendor/gtc-clauses-table-toolbar-actions.tsx create mode 100644 lib/basic-contract/gtc-vendor/import-excel-dialog.tsx create mode 100644 lib/basic-contract/gtc-vendor/markdown-image-editor.tsx create mode 100644 lib/basic-contract/gtc-vendor/preview-document-dialog.tsx create mode 100644 lib/basic-contract/gtc-vendor/reorder-gtc-clauses-dialog.tsx create mode 100644 lib/basic-contract/gtc-vendor/update-gtc-clause-sheet.tsx create mode 100644 lib/basic-contract/gtc-vendor/view-clause-variables-dialog.tsx create mode 100644 lib/basic-contract/vendor-table/update-vendor-document-status-button.tsx (limited to 'lib/basic-contract') diff --git a/lib/basic-contract/gtc-vendor/bulk-update-gtc-clauses-dialog.tsx b/lib/basic-contract/gtc-vendor/bulk-update-gtc-clauses-dialog.tsx new file mode 100644 index 00000000..a9ef0f0e --- /dev/null +++ b/lib/basic-contract/gtc-vendor/bulk-update-gtc-clauses-dialog.tsx @@ -0,0 +1,276 @@ +"use client" + +import * as React from "react" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { Badge } from "@/components/ui/badge" +import { Switch } from "@/components/ui/switch" + +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + FormDescription, +} from "@/components/ui/form" +import { Loader, Edit, AlertCircle } from "lucide-react" +import { toast } from "sonner" + +import { bulkUpdateGtcClausesSchema, type BulkUpdateGtcClausesSchema } from "@/lib/gtc-contract/gtc-clauses/validations" +import { bulkUpdateGtcClauses } from "@/lib/gtc-contract/gtc-clauses/service" +import { type GtcClauseTreeView } from "@/db/schema/gtc" +import { useSession } from "next-auth/react" + +interface BulkUpdateGtcClausesDialogProps + extends React.ComponentPropsWithRef { + selectedClauses: GtcClauseTreeView[] +} + +export function BulkUpdateGtcClausesDialog({ + selectedClauses, + ...props +}: BulkUpdateGtcClausesDialogProps) { + const [isUpdatePending, startUpdateTransition] = React.useTransition() + const { data: session } = useSession() + + const currentUserId = React.useMemo(() => { + return session?.user?.id ? Number(session.user.id) : null + }, [session]) + + const form = useForm({ + resolver: zodResolver(bulkUpdateGtcClausesSchema), + defaultValues: { + clauseIds: selectedClauses.map(clause => clause.id), + updates: { + category: "", + isActive: true, + }, + editReason: "", + }, + }) + + React.useEffect(() => { + if (selectedClauses.length > 0) { + form.setValue("clauseIds", selectedClauses.map(clause => clause.id)) + } + }, [selectedClauses, form]) + + async function onSubmit(data: BulkUpdateGtcClausesSchema) { + startUpdateTransition(async () => { + if (!currentUserId) { + toast.error("로그인이 필요합니다") + return + } + + try { + const result = await bulkUpdateGtcClauses({ + ...data, + updatedById: currentUserId + }) + + if (result.error) { + toast.error(`에러: ${result.error}`) + return + } + + form.reset() + props.onOpenChange?.(false) + toast.success(`${selectedClauses.length}개의 조항이 수정되었습니다.`) + } catch (error) { + toast.error("조항 일괄 수정 중 오류가 발생했습니다.") + } + }) + } + + function handleDialogOpenChange(nextOpen: boolean) { + if (!nextOpen) { + form.reset() + } + props.onOpenChange?.(nextOpen) + } + + // 선택된 조항들의 통계 + const categoryCounts = React.useMemo(() => { + const counts: Record = {} + selectedClauses.forEach(clause => { + const category = clause.category || "미분류" + counts[category] = (counts[category] || 0) + 1 + }) + return counts + }, [selectedClauses]) + + const activeCount = selectedClauses.filter(clause => clause.isActive).length + const inactiveCount = selectedClauses.length - activeCount + + if (selectedClauses.length === 0) { + return null + } + + return ( + + + + + + 조항 일괄 수정 + + + 선택한 {selectedClauses.length}개 조항의 공통 속성을 일괄 수정합니다. + + + + {/* 선택된 조항 요약 */} +
+
+ + 선택된 조항 정보 +
+ +
+
+
총 조항 수
+
{selectedClauses.length}개
+
+ +
+
상태
+
+ {activeCount}개 활성 + {inactiveCount > 0 && ( + {inactiveCount}개 비활성 + )} +
+
+
+ + {/* 분류별 통계 */} +
+
현재 분류 현황
+
+ {Object.entries(categoryCounts).map(([category, count]) => ( + + {category}: {count}개 + + ))} +
+
+ + {/* 조항 미리보기 (최대 5개) */} +
+
포함된 조항 (일부)
+
+ {selectedClauses.slice(0, 5).map(clause => ( +
+ {clause.itemNumber} + {clause.subtitle} +
+ ))} + {selectedClauses.length > 5 && ( +
+ ... 외 {selectedClauses.length - 5}개 조항 +
+ )} +
+
+
+ +
+ +
+ {/* 분류 수정 */} + ( + + 분류 변경 (선택사항) + + + + + 모든 선택된 조항의 분류가 동일한 값으로 변경됩니다. + + + + )} + /> + + {/* 활성 상태 변경 */} + ( + +
+ 활성 상태 + + 선택된 모든 조항의 활성 상태를 설정합니다. + +
+ + + +
+ )} + /> + + {/* 편집 사유 */} + ( + + 편집 사유 * + +