summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/table/add-doc-dialog.tsx
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-03-25 15:55:45 +0900
committerjoonhoekim <26rote@gmail.com>2025-03-25 15:55:45 +0900
commit1a2241c40e10193c5ff7008a7b7b36cc1d855d96 (patch)
tree8a5587f10ca55b162d7e3254cb088b323a34c41b /lib/vendor-document-list/table/add-doc-dialog.tsx
initial commit
Diffstat (limited to 'lib/vendor-document-list/table/add-doc-dialog.tsx')
-rw-r--r--lib/vendor-document-list/table/add-doc-dialog.tsx299
1 files changed, 299 insertions, 0 deletions
diff --git a/lib/vendor-document-list/table/add-doc-dialog.tsx b/lib/vendor-document-list/table/add-doc-dialog.tsx
new file mode 100644
index 00000000..b108721c
--- /dev/null
+++ b/lib/vendor-document-list/table/add-doc-dialog.tsx
@@ -0,0 +1,299 @@
+"use client"
+
+import * as React from "react"
+import { useForm } from "react-hook-form"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { z } from "zod"
+import { Plus, X } from "lucide-react"
+import { useRouter } from "next/navigation"
+
+import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import { useToast } from "@/hooks/use-toast"
+import { createDocument, CreateDocumentInputType, invalidateDocumentCache } from "../service"
+
+// Zod 스키마 정의 - 빈 문자열 방지 로직 추가
+const createDocumentSchema = z.object({
+ docNumber: z.string().min(1, "Document number is required"),
+ title: z.string().min(1, "Title is required"),
+ stages: z.array(z.string().min(1, "Stage name cannot be empty"))
+ .min(1, "At least one stage is required")
+ .refine(stages => !stages.some(stage => stage.trim() === ""), {
+ message: "Stage names cannot be empty"
+ })
+});
+
+type CreateDocumentSchema = z.infer<typeof createDocumentSchema>;
+
+interface AddDocumentListDialogProps {
+ projectType: "ship" | "plant";
+ contractId: number;
+}
+
+export function AddDocumentListDialog({ projectType, contractId }: AddDocumentListDialogProps) {
+ const [open, setOpen] = React.useState(false);
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
+ const router = useRouter();
+ const { toast } = useToast()
+
+ // 기본 스테이지 설정
+ const defaultStages = projectType === "ship"
+ ? ["For Approval", "For Working"]
+ : [""];
+
+ // react-hook-form 설정
+ const form = useForm<CreateDocumentSchema>({
+ resolver: zodResolver(createDocumentSchema),
+ defaultValues: {
+ docNumber: "",
+ title: "",
+ stages: defaultStages
+ },
+ });
+
+ // 식물 유형일 때 단계 추가 기능
+ const addStage = () => {
+ const currentStages = form.getValues().stages;
+ form.setValue('stages', [...currentStages, ""], { shouldValidate: true });
+ };
+
+ // 식물 유형일 때 단계 제거 기능
+ const removeStage = (index: number) => {
+ const currentStages = form.getValues().stages;
+ const newStages = currentStages.filter((_, i) => i !== index);
+ form.setValue('stages', newStages, { shouldValidate: true });
+ };
+
+ async function onSubmit(data: CreateDocumentSchema) {
+ try {
+ setIsSubmitting(true);
+
+ // 빈 문자열 필터링 (추가 안전장치)
+ const filteredStages = data.stages.filter(stage => stage.trim() !== "");
+
+ if (filteredStages.length === 0) {
+ toast({
+ title: "Error",
+ description: "At least one valid stage name is required",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ // 서버 액션 호출 - status를 "pending"으로 설정
+ const result = await createDocument({
+ ...data,
+ stages: filteredStages, // 필터링된 단계 사용
+ status: "pending", // status 필드 추가
+ contractId, // 계약 ID 추가
+ } as CreateDocumentInputType);
+
+ if (result.success) {
+ // 성공 시 캐시 무효화
+ await invalidateDocumentCache(contractId);
+
+ // 토스트 메시지
+ toast({
+ title: "Success",
+ description: "Document created successfully",
+ variant: "default",
+ });
+
+ // 모달 닫기 및 폼 리셋
+ form.reset();
+ setOpen(false);
+
+ router.refresh();
+ } else {
+ // 실패 시 에러 토스트
+ toast({
+ title: "Error",
+ description: result.message || "Failed to create document",
+ variant: "destructive",
+ });
+ }
+ } catch (error) {
+ console.error('Error creating document:', error);
+ toast({
+ title: "Error",
+ description: "An unexpected error occurred",
+ variant: "destructive",
+ });
+ } finally {
+ setIsSubmitting(false);
+ }
+ }
+
+ // 제출 전 유효성 검사
+ const validateBeforeSubmit = async () => {
+ // 빈 스테이지 검사
+ const stages = form.getValues().stages;
+ const hasEmptyStage = stages.some(stage => stage.trim() === "");
+
+ if (hasEmptyStage) {
+ form.setError("stages", {
+ type: "manual",
+ message: "Stage names cannot be empty"
+ });
+ return false;
+ }
+
+ return true;
+ };
+
+ function handleDialogOpenChange(nextOpen: boolean) {
+ if (!nextOpen) {
+ form.reset({
+ docNumber: "",
+ title: "",
+ stages: defaultStages
+ });
+ }
+ setOpen(nextOpen);
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={handleDialogOpenChange}>
+ {/* 모달을 열기 위한 버튼 */}
+ <DialogTrigger asChild>
+ <Button variant="default" size="sm">
+ <Plus className="size-4 mr-1"/>
+ Add Document
+ </Button>
+ </DialogTrigger>
+
+ <DialogContent className="sm:max-w-[500px]">
+ <DialogHeader>
+ <DialogTitle>Create New Document</DialogTitle>
+ <DialogDescription>
+ 새 문서 정보를 입력하고 <b>Create</b> 버튼을 누르세요.
+ </DialogDescription>
+ </DialogHeader>
+
+ {/* shadcn/ui Form을 이용해 react-hook-form과 연결 */}
+ <Form {...form}>
+ <form onSubmit={form.handleSubmit(onSubmit, async (errors) => {
+ // 추가 유효성 검사 수행
+ console.error("Form errors:", errors);
+ const stages = form.getValues().stages;
+ if (stages.some(stage => stage.trim() === "")) {
+ toast({
+ title: "Error",
+ description: "Stage names cannot be empty",
+ variant: "destructive",
+ });
+ }
+ })} className="space-y-4">
+ {/* 문서 번호 필드 */}
+ <FormField
+ control={form.control}
+ name="docNumber"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Document Number</FormLabel>
+ <FormControl>
+ <Input placeholder="Enter document number" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 문서 제목 필드 */}
+ <FormField
+ control={form.control}
+ name="title"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Title</FormLabel>
+ <FormControl>
+ <Input placeholder="Enter document title" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 스테이지 섹션 */}
+ <div>
+ <div className="flex items-center justify-between mb-2">
+ <FormLabel>Stages</FormLabel>
+ {projectType === "plant" && (
+ <Button
+ type="button"
+ variant="outline"
+ size="sm"
+ onClick={addStage}
+ className="h-8 px-2"
+ >
+ <Plus className="h-4 w-4 mr-1" /> Add Stage
+ </Button>
+ )}
+ </div>
+
+ {form.watch("stages").map((stage, index) => (
+ <div key={index} className="flex items-center gap-2 mb-2">
+ <FormField
+ control={form.control}
+ name={`stages.${index}`}
+ render={({ field }) => (
+ <FormItem className="flex-1">
+ <FormControl>
+ <Input
+ placeholder="Enter stage name"
+ {...field}
+ disabled={projectType === "ship"}
+ />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ {projectType === "plant" && index > 0 && (
+ <Button
+ type="button"
+ variant="ghost"
+ size="sm"
+ onClick={() => removeStage(index)}
+ className="h-8 w-8 p-0"
+ >
+ <X className="h-4 w-4" />
+ </Button>
+ )}
+ </div>
+ ))}
+ <FormMessage>
+ {form.formState.errors.stages?.message}
+ </FormMessage>
+ </div>
+
+ <DialogFooter>
+ <Button
+ type="button"
+ variant="outline"
+ onClick={() => setOpen(false)}
+ >
+ Cancel
+ </Button>
+ <Button
+ type="submit"
+ disabled={isSubmitting || form.formState.isSubmitting}
+ >
+ {isSubmitting ? "Creating..." : "Create"}
+ </Button>
+ </DialogFooter>
+ </form>
+ </Form>
+ </DialogContent>
+ </Dialog>
+ );
+} \ No newline at end of file