summaryrefslogtreecommitdiff
path: root/lib/basic-contract
diff options
context:
space:
mode:
Diffstat (limited to 'lib/basic-contract')
-rw-r--r--lib/basic-contract/actions.ts135
-rw-r--r--lib/basic-contract/template/basic-contract-template.tsx12
-rw-r--r--lib/basic-contract/template/basicContract-table-toolbar-actions.tsx34
-rw-r--r--lib/basic-contract/template/dispose-documents-dialog.tsx122
4 files changed, 298 insertions, 5 deletions
diff --git a/lib/basic-contract/actions.ts b/lib/basic-contract/actions.ts
new file mode 100644
index 00000000..0af9b948
--- /dev/null
+++ b/lib/basic-contract/actions.ts
@@ -0,0 +1,135 @@
+"use server"
+
+import { revalidateTag, revalidatePath } from 'next/cache'
+import db from '@/db/db'
+import { basicContractTemplates } from '@/db/schema'
+import { eq, inArray } from 'drizzle-orm'
+
+export async function disposeDocuments(documentIds: number[]) {
+ try {
+ if (!documentIds || !Array.isArray(documentIds) || documentIds.length === 0) {
+ throw new Error('폐기할 문서 ID가 필요합니다.')
+ }
+
+ // 문서들을 DISPOSED 상태로 업데이트하고 폐기일시 설정
+ await db
+ .update(basicContractTemplates)
+ .set({
+ status: 'DISPOSED',
+ disposedAt: new Date(),
+ updatedAt: new Date(),
+ })
+ .where(inArray(basicContractTemplates.id, documentIds))
+
+ // 캐시 무효화
+ revalidateTag('basic-contract-templates')
+ revalidatePath('/evcp/basic-contract-template')
+
+ return {
+ success: true,
+ message: `${documentIds.length}개의 문서가 폐기되었습니다.`,
+ disposedCount: documentIds.length
+ }
+
+ } catch (error) {
+ console.error('문서 폐기 처리 오류:', error)
+ throw new Error('문서 폐기 처리 중 오류가 발생했습니다.')
+ }
+}
+
+export async function restoreDocuments(documentIds: number[]) {
+ try {
+ if (!documentIds || !Array.isArray(documentIds) || documentIds.length === 0) {
+ throw new Error('복구할 문서 ID가 필요합니다.')
+ }
+
+ // 문서들을 ACTIVE 상태로 업데이트하고 폐기일시 제거
+ await db
+ .update(basicContractTemplates)
+ .set({
+ status: 'ACTIVE',
+ disposedAt: null,
+ updatedAt: new Date(),
+ })
+ .where(inArray(basicContractTemplates.id, documentIds))
+
+ // 캐시 무효화
+ revalidateTag('basic-contract-templates')
+ revalidatePath('/evcp/basic-contract-template')
+
+ return {
+ success: true,
+ message: `${documentIds.length}개의 문서가 복구되었습니다.`,
+ restoredCount: documentIds.length
+ }
+
+ } catch (error) {
+ console.error('문서 복구 처리 오류:', error)
+ throw new Error('문서 복구 처리 중 오류가 발생했습니다.')
+ }
+}
+
+export async function createDocumentRevisionAction(input: {
+ baseDocumentId: number;
+ contractTemplateName: string;
+ contractTemplateType: string;
+ revision: number;
+ legalReviewRequired: boolean;
+ fileName: string;
+ filePath: string;
+}) {
+ try {
+ const { createBasicContractTemplateRevision } = await import('./service');
+
+ const { data, error } = await createBasicContractTemplateRevision({
+ ...input,
+ status: 'ACTIVE' as const
+ });
+
+ if (error) {
+ throw new Error(error);
+ }
+
+ return {
+ success: true,
+ data,
+ message: `${input.contractTemplateName} v${input.revision} 리비전이 성공적으로 생성되었습니다.`
+ };
+
+ } catch (error) {
+ console.error('문서 리비전 생성 오류:', error);
+ throw new Error(error instanceof Error ? error.message : '문서 리비전 생성 중 오류가 발생했습니다.');
+ }
+}
+
+// 업로드 완료 후 문서 생성 (클라이언트에서 직접 호출 가능한 서버 액션)
+export async function createDocumentFromUpload(input: {
+ contractTemplateType: string
+ contractTemplateName: string
+ legalReviewRequired: boolean
+ fileName: string
+ filePath: string
+}) {
+ try {
+ const { createBasicContractTemplate } = await import('./service');
+
+ const { data, error } = await createBasicContractTemplate({
+ contractTemplateType: input.contractTemplateType,
+ contractTemplateName: input.contractTemplateName,
+ revision: 1,
+ status: 'ACTIVE',
+ legalReviewRequired: input.legalReviewRequired,
+ fileName: input.fileName,
+ filePath: input.filePath,
+ } as any)
+
+ if (error) throw new Error(error)
+
+ revalidateTag('basic-contract-templates')
+ revalidatePath('/evcp/basic-contract-template')
+
+ return { success: true, id: data?.id }
+ } catch (e: any) {
+ return { success: false, error: e?.message || '문서 생성 실패' }
+ }
+}
diff --git a/lib/basic-contract/template/basic-contract-template.tsx b/lib/basic-contract/template/basic-contract-template.tsx
index 8ac421f5..ba24187b 100644
--- a/lib/basic-contract/template/basic-contract-template.tsx
+++ b/lib/basic-contract/template/basic-contract-template.tsx
@@ -14,6 +14,7 @@ import { getColumns } from "./basic-contract-template-columns";
import { DeleteTemplatesDialog } from "./delete-basicContract-dialog";
import { UpdateTemplateSheet } from "./update-basicContract-sheet";
import { CreateRevisionDialog } from "./create-revision-dialog";
+import { DisposeDocumentsDialog } from "./dispose-documents-dialog";
import { TemplateTableToolbarActions } from "./basicContract-table-toolbar-actions";
import { BasicContractTemplate } from "@/db/schema";
@@ -104,6 +105,17 @@ export function BasicContractTemplateTable({ promises }: BasicTemplateTableProps
router.refresh();
}}
/>
+
+ <DisposeDocumentsDialog
+ open={rowAction?.type === "dispose" || rowAction?.type === "restore"}
+ onOpenChange={() => setRowAction(null)}
+ documents={rowAction?.row.original ? [rowAction?.row.original] : []}
+ showTrigger={false}
+ onSuccess={() => {
+ setRowAction(null);
+ router.refresh();
+ }}
+ />
</>
);
} \ No newline at end of file
diff --git a/lib/basic-contract/template/basicContract-table-toolbar-actions.tsx b/lib/basic-contract/template/basicContract-table-toolbar-actions.tsx
index 439fea26..850dc0a5 100644
--- a/lib/basic-contract/template/basicContract-table-toolbar-actions.tsx
+++ b/lib/basic-contract/template/basicContract-table-toolbar-actions.tsx
@@ -9,6 +9,7 @@ import { exportTableToExcel } from "@/lib/export"
import { Button } from "@/components/ui/button"
import { DeleteTemplatesDialog } from "./delete-basicContract-dialog"
import { AddTemplateDialog } from "./add-basic-contract-template-dialog"
+import { DisposeDocumentsDialog } from "./dispose-documents-dialog"
import { BasicContractTemplate } from "@/db/schema"
interface TemplateTableToolbarActionsProps {
@@ -17,16 +18,39 @@ interface TemplateTableToolbarActionsProps {
export function TemplateTableToolbarActions({ table }: TemplateTableToolbarActionsProps) {
// 파일 input을 숨기고, 버튼 클릭 시 참조해 클릭하는 방식
-
+ const selectedRows = table.getFilteredSelectedRowModel().rows
+ const selectedDocuments = selectedRows.map((row) => row.original)
+
+ // 선택된 문서들의 상태 확인
+ const hasActiveDocuments = selectedDocuments.some(doc => doc.status === 'ACTIVE')
+ const hasDisposedDocuments = selectedDocuments.some(doc => doc.status === 'DISPOSED')
return (
<div className="flex items-center gap-2">
{/** 1) 선택된 로우가 있으면 삭제 다이얼로그 */}
- {table.getFilteredSelectedRowModel().rows.length > 0 ? (
+ {selectedRows.length > 0 ? (
<DeleteTemplatesDialog
- templates={table
- .getFilteredSelectedRowModel()
- .rows.map((row) => row.original)}
+ templates={selectedDocuments}
+ onSuccess={() => table.toggleAllRowsSelected(false)}
+ />
+ ) : null}
+
+ {/** 2) 선택된 활성 문서가 있으면 폐기 다이얼로그 */}
+ {selectedRows.length > 0 && hasActiveDocuments ? (
+ <DisposeDocumentsDialog
+ open={false}
+ onOpenChange={() => {}}
+ documents={selectedDocuments.filter(doc => doc.status === 'ACTIVE')}
+ onSuccess={() => table.toggleAllRowsSelected(false)}
+ />
+ ) : null}
+
+ {/** 3) 선택된 폐기 문서가 있으면 복구 다이얼로그 */}
+ {selectedRows.length > 0 && hasDisposedDocuments ? (
+ <DisposeDocumentsDialog
+ open={false}
+ onOpenChange={() => {}}
+ documents={selectedDocuments.filter(doc => doc.status === 'DISPOSED')}
onSuccess={() => table.toggleAllRowsSelected(false)}
/>
) : null}
diff --git a/lib/basic-contract/template/dispose-documents-dialog.tsx b/lib/basic-contract/template/dispose-documents-dialog.tsx
new file mode 100644
index 00000000..1154c246
--- /dev/null
+++ b/lib/basic-contract/template/dispose-documents-dialog.tsx
@@ -0,0 +1,122 @@
+"use client"
+
+import * as React from "react"
+import { toast } from "sonner"
+import { Trash2, RotateCcw } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { BasicContractTemplate } from "@/db/schema"
+import { disposeDocuments, restoreDocuments } from "../actions"
+
+interface DisposeDocumentsDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ documents: BasicContractTemplate[]
+ showTrigger?: boolean
+ onSuccess?: () => void
+}
+
+export function DisposeDocumentsDialog({
+ open,
+ onOpenChange,
+ documents,
+ showTrigger = true,
+ onSuccess,
+}: DisposeDocumentsDialogProps) {
+ const [isLoading, setIsLoading] = React.useState(false)
+
+ const isDisposeAction = documents.some(doc => doc.status === 'ACTIVE')
+ const actionText = isDisposeAction ? '폐기' : '복구'
+ const actionIcon = isDisposeAction ? Trash2 : RotateCcw
+
+ const handleAction = async () => {
+ if (documents.length === 0) return
+
+ setIsLoading(true)
+ try {
+ const documentIds = documents.map(doc => doc.id)
+
+ if (isDisposeAction) {
+ await disposeDocuments(documentIds)
+ toast.success(`${documents.length}개의 문서가 폐기되었습니다.`)
+ } else {
+ await restoreDocuments(documentIds)
+ toast.success(`${documents.length}개의 문서가 복구되었습니다.`)
+ }
+
+ onSuccess?.()
+ onOpenChange(false)
+ } catch (error) {
+ console.error(`${actionText} 처리 오류:`, error)
+ toast.error(`${actionText} 처리 중 오류가 발생했습니다.`)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ {showTrigger && (
+ <Button variant="outline" size="sm">
+ {React.createElement(actionIcon, { className: "mr-2 h-4 w-4" })}
+ {actionText}하기
+ </Button>
+ )}
+ <DialogContent className="sm:max-w-[425px]">
+ <DialogHeader>
+ <DialogTitle className="flex items-center gap-2">
+ {React.createElement(actionIcon, { className: "h-5 w-5" })}
+ 문서 {actionText}
+ </DialogTitle>
+ <DialogDescription>
+ 선택한 {documents.length}개의 문서를 {actionText}하시겠습니까?
+ {isDisposeAction
+ ? ' 폐기된 문서는 복구할 수 있습니다.'
+ : ' 복구된 문서는 다시 사용할 수 있습니다.'
+ }
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="py-4">
+ <div className="space-y-2">
+ {documents.map((doc) => (
+ <div key={doc.id} className="flex items-center p-2 bg-muted rounded">
+ <div>
+ <p className="font-medium">{doc.templateName}</p>
+ <p className="text-sm text-muted-foreground">
+ {doc.fileName} • v{doc.revision}
+ </p>
+ </div>
+ </div>
+ ))}
+ </div>
+ </div>
+
+ <DialogFooter>
+ <Button
+ variant="outline"
+ onClick={() => onOpenChange(false)}
+ disabled={isLoading}
+ >
+ 취소
+ </Button>
+ <Button
+ onClick={handleAction}
+ disabled={isLoading}
+ variant={isDisposeAction ? "destructive" : "default"}
+ >
+ {isLoading ? "처리 중..." : `${actionText}하기`}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}