From e0dfb55c5457aec489fc084c4567e791b4c65eb1 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 26 Mar 2025 00:37:41 +0000
Subject: 3/25 까지의 대표님 작업사항
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/pq/pq-input-tabs.tsx | 780 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 780 insertions(+)
 create mode 100644 components/pq/pq-input-tabs.tsx

(limited to 'components/pq/pq-input-tabs.tsx')

diff --git a/components/pq/pq-input-tabs.tsx b/components/pq/pq-input-tabs.tsx
new file mode 100644
index 00000000..743e1729
--- /dev/null
+++ b/components/pq/pq-input-tabs.tsx
@@ -0,0 +1,780 @@
+"use client"
+
+import * as React from "react"
+import { useForm } from "react-hook-form"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { z } from "zod"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import {
+  Card,
+  CardHeader,
+  CardTitle,
+  CardDescription,
+  CardContent,
+} from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Textarea } from "@/components/ui/textarea"
+import { X, Save, CheckCircle2, AlertTriangle, ChevronsUpDown } from "lucide-react"
+import prettyBytes from "pretty-bytes"
+import { useToast } from "@/hooks/use-toast"
+import {
+  Collapsible,
+  CollapsibleContent,
+  CollapsibleTrigger,
+} from "@/components/ui/collapsible"
+
+// Form components
+import {
+  Form,
+  FormControl,
+  FormField,
+  FormItem,
+  FormLabel,
+  FormMessage,
+  FormDescription,
+} from "@/components/ui/form"
+
+// Custom Dropzone, FileList components
+import {
+  Dropzone,
+  DropzoneDescription,
+  DropzoneInput,
+  DropzoneTitle,
+  DropzoneUploadIcon,
+  DropzoneZone,
+} from "@/components/ui/dropzone"
+import {
+  FileList,
+  FileListAction,
+  FileListDescription,
+  FileListHeader,
+  FileListIcon,
+  FileListInfo,
+  FileListItem,
+  FileListName,
+} from "@/components/ui/file-list"
+
+// Dialog components from shadcn/ui
+import {
+  Dialog,
+  DialogContent,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+  DialogDescription,
+} from "@/components/ui/dialog"
+
+// Additional UI
+import { Separator } from "../ui/separator"
+
+// Server actions (adjust to your actual code)
+import {
+  uploadFileAction,
+  savePQAnswersAction,
+  submitPQAction,
+} from "@/lib/pq/service"
+import { PQGroupData } from "@/lib/pq/service"
+
+// ----------------------------------------------------------------------
+// 1) Define client-side file shapes
+// ----------------------------------------------------------------------
+interface UploadedFileState {
+  fileName: string
+  url: string
+  size?: number
+}
+
+interface LocalFileState {
+  fileObj: File
+  uploaded: boolean
+}
+
+// ----------------------------------------------------------------------
+// 2) Zod schema for the entire form
+// ----------------------------------------------------------------------
+const pqFormSchema = z.object({
+  answers: z.array(
+    z.object({
+      criteriaId: z.number(),
+      // Must have at least 1 char
+      answer: z.string().min(1, "Answer is required"),
+
+      // Existing, uploaded files
+      uploadedFiles: z
+        .array(
+          z.object({
+            fileName: z.string(),
+            url: z.string(),
+            size: z.number().optional(),
+          })
+        )
+        .min(1, "At least one file attachment is required"),
+
+      // Local (not-yet-uploaded) files
+      newUploads: z.array(
+        z.object({
+          fileObj: z.any(),
+          uploaded: z.boolean().default(false),
+        })
+      ),
+
+      // track saved state
+      saved: z.boolean().default(false),
+    })
+  ),
+})
+
+type PQFormValues = z.infer<typeof pqFormSchema>
+
+// ----------------------------------------------------------------------
+// 3) Main Component: PQInputTabs
+// ----------------------------------------------------------------------
+export function PQInputTabs({
+  data,
+  vendorId,
+}: {
+  data: PQGroupData[]
+  vendorId: number
+}) {
+  const [isSaving, setIsSaving] = React.useState(false)
+  const [isSubmitting, setIsSubmitting] = React.useState(false)
+  const [allSaved, setAllSaved] = React.useState(false)
+  const [showConfirmDialog, setShowConfirmDialog] = React.useState(false)
+
+  const { toast } = useToast()
+
+  // ----------------------------------------------------------------------
+  // A) Create initial form values
+  //     Mark items as "saved" if they have existing answer or attachments
+  // ----------------------------------------------------------------------
+  function createInitialFormValues(): PQFormValues {
+    const answers: PQFormValues["answers"] = []
+
+    data.forEach((group) => {
+      group.items.forEach((item) => {
+        // Check if the server item is already “complete”
+        const hasExistingAnswer = item.answer && item.answer.trim().length > 0
+        const hasExistingAttachments = item.attachments && item.attachments.length > 0
+
+        // If either is present, we consider it "saved" initially
+        const isAlreadySaved = hasExistingAnswer || hasExistingAttachments
+
+        answers.push({
+          criteriaId: item.criteriaId,
+          answer: item.answer || "",
+          uploadedFiles: item.attachments.map((attach) => ({
+            fileName: attach.fileName,
+            url: attach.filePath,
+            size: attach.fileSize,
+          })),
+          newUploads: [],
+          saved: isAlreadySaved,
+        })
+      })
+    })
+
+    return { answers }
+  }
+
+  // ----------------------------------------------------------------------
+  // B) Set up react-hook-form
+  // ----------------------------------------------------------------------
+  const form = useForm<PQFormValues>({
+    resolver: zodResolver(pqFormSchema),
+    defaultValues: createInitialFormValues(),
+    mode: "onChange",
+  })
+
+  // ----------------------------------------------------------------------
+  // C) Track if all items are saved => controls Submit PQ button
+  // ----------------------------------------------------------------------
+  React.useEffect(() => {
+    const values = form.getValues()
+    // We consider items “saved” if `saved===true` AND they have an answer or attachments
+    const allItemsSaved = values.answers.every(
+      (answer) => answer.saved && (answer.answer || answer.uploadedFiles.length > 0)
+    )
+    setAllSaved(allItemsSaved)
+  }, [form.watch()])
+
+  // Helper to find the array index by criteriaId
+  const getAnswerIndex = (criteriaId: number): number => {
+    return form.getValues().answers.findIndex((a) => a.criteriaId === criteriaId)
+  }
+
+  // ----------------------------------------------------------------------
+  // D) Handling File Drops, Removal
+  // ----------------------------------------------------------------------
+  const handleDropAccepted = (criteriaId: number, files: File[]) => {
+    const answerIndex = getAnswerIndex(criteriaId)
+    if (answerIndex === -1) return
+
+    // Convert each dropped file into a LocalFileState
+    const newLocalFiles: LocalFileState[] = files.map((f) => ({
+      fileObj: f,
+      uploaded: false,
+    }))
+
+    const current = form.getValues(`answers.${answerIndex}.newUploads`)
+    form.setValue(`answers.${answerIndex}.newUploads`, [...current, ...newLocalFiles], {
+      shouldDirty: true,
+    })
+
+    // Mark unsaved
+    form.setValue(`answers.${answerIndex}.saved`, false, { shouldDirty: true })
+  }
+
+  const handleDropRejected = () => {
+    toast({
+      title: "File upload rejected",
+      description: "Please check file size and type.",
+      variant: "destructive",
+    })
+  }
+
+  const removeNewUpload = (answerIndex: number, fileIndex: number) => {
+    const current = [...form.getValues(`answers.${answerIndex}.newUploads`)]
+    current.splice(fileIndex, 1)
+    form.setValue(`answers.${answerIndex}.newUploads`, current, { shouldDirty: true })
+
+    form.setValue(`answers.${answerIndex}.saved`, false, { shouldDirty: true })
+  }
+
+  const removeUploadedFile = (answerIndex: number, fileIndex: number) => {
+    const current = [...form.getValues(`answers.${answerIndex}.uploadedFiles`)]
+    current.splice(fileIndex, 1)
+    form.setValue(`answers.${answerIndex}.uploadedFiles`, current, { shouldDirty: true })
+
+    form.setValue(`answers.${answerIndex}.saved`, false, { shouldDirty: true })
+  }
+
+  // ----------------------------------------------------------------------
+  // E) Saving a Single Item
+  // ----------------------------------------------------------------------
+  const handleSaveItem = async (answerIndex: number) => {
+    try {
+      const answerData = form.getValues(`answers.${answerIndex}`)
+
+      // Validation
+      if (!answerData.answer) {
+        toast({
+          title: "Validation Error",
+          description: "Answer is required",
+          variant: "destructive",
+        })
+        return
+      }
+
+      // Upload new files (if any)
+      if (answerData.newUploads.length > 0) {
+        setIsSaving(true)
+
+        for (const localFile of answerData.newUploads) {
+          try {
+            const uploadResult = await uploadFileAction(localFile.fileObj)
+            const currentUploaded = form.getValues(`answers.${answerIndex}.uploadedFiles`)
+            currentUploaded.push({
+              fileName: uploadResult.fileName,
+              url: uploadResult.url,
+              size: uploadResult.size,
+            })
+            form.setValue(`answers.${answerIndex}.uploadedFiles`, currentUploaded, {
+              shouldDirty: true,
+            })
+          } catch (error) {
+            console.error("File upload error:", error)
+            toast({
+              title: "Upload Error",
+              description: "Failed to upload file",
+              variant: "destructive",
+            })
+          }
+        }
+
+        // Clear newUploads
+        form.setValue(`answers.${answerIndex}.newUploads`, [], { shouldDirty: true })
+      }
+
+      // Save to DB
+      const updatedAnswer = form.getValues(`answers.${answerIndex}`)
+      const saveResult = await savePQAnswersAction({
+        vendorId,
+        answers: [
+          {
+            criteriaId: updatedAnswer.criteriaId,
+            answer: updatedAnswer.answer,
+            attachments: updatedAnswer.uploadedFiles.map((f) => ({
+              fileName: f.fileName,
+              url: f.url,
+              size: f.size,
+            })),
+          },
+        ],
+      })
+
+      if (saveResult.ok) {
+        // Mark as saved
+        form.setValue(`answers.${answerIndex}.saved`, true, { shouldDirty: false })
+        toast({
+          title: "Saved",
+          description: "Item saved successfully",
+        })
+      }
+    } catch (error) {
+      console.error("Save error:", error)
+      toast({
+        title: "Save Error",
+        description: "Failed to save item",
+        variant: "destructive",
+      })
+    } finally {
+      setIsSaving(false)
+    }
+  }
+
+  // For convenience
+  const answers = form.getValues().answers
+  const dirtyFields = form.formState.dirtyFields.answers
+
+  // Check if any item is dirty or has new uploads
+  const isAnyItemDirty = answers.some((answer, i) => {
+    const itemDirty = !!dirtyFields?.[i]
+    const hasNewUploads = answer.newUploads.length > 0
+    return itemDirty || hasNewUploads
+  })
+
+  // ----------------------------------------------------------------------
+  // F) Save All Items
+  // ----------------------------------------------------------------------
+  const handleSaveAll = async () => {
+    try {
+      setIsSaving(true)
+      const answers = form.getValues().answers
+
+      // Only save items that are dirty or have new uploads
+      for (let i = 0; i < answers.length; i++) {
+        const itemDirty = !!dirtyFields?.[i]
+        const hasNewUploads = answers[i].newUploads.length > 0
+        if (!itemDirty && !hasNewUploads) continue
+
+        await handleSaveItem(i)
+      }
+
+      toast({
+        title: "All Saved",
+        description: "All items saved successfully",
+      })
+    } catch (error) {
+      console.error("Save all error:", error)
+      toast({
+        title: "Save Error",
+        description: "Failed to save all items",
+        variant: "destructive",
+      })
+    } finally {
+      setIsSaving(false)
+    }
+  }
+
+  // ----------------------------------------------------------------------
+  // G) Submission with Confirmation Dialog
+  // ----------------------------------------------------------------------
+  const handleSubmitPQ = () => {
+    if (!allSaved) {
+      toast({
+        title: "Cannot Submit",
+        description: "Please save all items before submitting",
+        variant: "destructive",
+      })
+      return
+    }
+    setShowConfirmDialog(true)
+  }
+
+  const handleConfirmSubmission = async () => {
+    try {
+      setIsSubmitting(true)
+      setShowConfirmDialog(false)
+
+      const result = await submitPQAction(vendorId)
+      if (result.ok) {
+        toast({
+          title: "PQ Submitted",
+          description: "Your PQ information has been submitted successfully",
+        })
+        // Optionally redirect
+      } else {
+        toast({
+          title: "Submit Error",
+          description: result.error || "Failed to submit PQ",
+          variant: "destructive",
+        })
+      }
+    } catch (error) {
+      console.error("Submit error:", error)
+      toast({
+        title: "Submit Error",
+        description: "Failed to submit PQ information",
+        variant: "destructive",
+      })
+    } finally {
+      setIsSubmitting(false)
+    }
+  }
+
+  // ----------------------------------------------------------------------
+  // H) Render
+  // ----------------------------------------------------------------------
+  return (
+    <Form {...form}>
+      <form>
+        <Tabs defaultValue={data[0]?.groupName || ""} className="w-full">
+          {/* Top Controls */}
+          <div className="flex justify-between items-center mb-4">
+            <TabsList className="grid grid-cols-4">
+              {data.map((group) => (
+                <TabsTrigger
+                  key={group.groupName}
+                  value={group.groupName}
+                  className="truncate"
+                >
+                  <div className="flex items-center gap-2">
+                    {/* Mobile: truncated version */}
+                    <span className="block sm:hidden">
+                      {group.groupName.length > 5
+                        ? group.groupName.slice(0, 5) + "..."
+                        : group.groupName}
+                    </span>
+                    {/* Desktop: full text */}
+                    <span className="hidden sm:block">{group.groupName}</span>
+                    <span className="inline-flex items-center justify-center h-5 min-w-5 px-1 rounded-full bg-muted text-xs font-medium">
+                      {group.items.length}
+                    </span>
+                  </div>
+                </TabsTrigger>
+              ))}
+            </TabsList>
+
+            <div className="flex gap-2">
+              {/* Save All button */}
+              <Button
+                type="button"
+                variant="outline"
+                disabled={isSaving || !isAnyItemDirty}
+                onClick={handleSaveAll}
+              >
+                {isSaving ? "Saving..." : "Save All"}
+                <Save className="ml-2 h-4 w-4" />
+              </Button>
+
+              {/* Submit PQ button */}
+              <Button
+                type="button"
+                disabled={!allSaved || isSubmitting}
+                onClick={handleSubmitPQ}
+              >
+                {isSubmitting ? "Submitting..." : "Submit PQ"}
+                <CheckCircle2 className="ml-2 h-4 w-4" />
+              </Button>
+            </div>
+          </div>
+
+          {/* Render each group */}
+          {data.map((group) => (
+            <TabsContent key={group.groupName} value={group.groupName}>
+              {/* 2-column grid */}
+              <div className="grid grid-cols-1 md:grid-cols-2 gap-4 pb-4">
+                {group.items.map((item) => {
+                  const { criteriaId, code, checkPoint, description } = item
+                  const answerIndex = getAnswerIndex(criteriaId)
+                  if (answerIndex === -1) return null
+
+                  const isSaved = form.watch(`answers.${answerIndex}.saved`)
+                  const hasAnswer = form.watch(`answers.${answerIndex}.answer`)
+                  const newUploads = form.watch(`answers.${answerIndex}.newUploads`)
+                  const dirtyFieldsItem = form.formState.dirtyFields.answers?.[answerIndex]
+
+                  const isItemDirty = !!dirtyFieldsItem
+                  const hasNewUploads = newUploads.length > 0
+                  const canSave = isItemDirty || hasNewUploads
+
+                  // For “Not Saved” vs. “Saved” status label
+                  const hasUploads =
+                    form.watch(`answers.${answerIndex}.uploadedFiles`).length > 0 ||
+                    newUploads.length > 0
+                  const isValid = !!hasAnswer || hasUploads
+
+                  return (
+                    <Collapsible key={criteriaId} defaultOpen={!isSaved} className="w-full">
+                      <Card className={isSaved ? "border-green-200" : ""}>
+                        <CardHeader className="pb-1">
+                          <div className="flex justify-between">
+                            <div className="flex-1">
+                              <div className="flex items-center gap-2">
+                                <CollapsibleTrigger asChild>
+                                  <Button variant="ghost" size="sm" className="p-0 h-7 w-7">
+                                    <ChevronsUpDown className="h-4 w-4" />
+                                    <span className="sr-only">Toggle</span>
+                                  </Button>
+                                </CollapsibleTrigger>
+                                <CardTitle className="text-md">
+                                  {code} - {checkPoint}
+                                </CardTitle>
+                              </div>
+                              {description && (
+                                <CardDescription className="mt-1 whitespace-pre-wrap">
+                                  {description}
+                                </CardDescription>
+                              )}
+                            </div>
+
+                            {/* Save Status & Button */}
+                            <div className="flex items-center gap-2">
+                              {!isSaved && canSave && (
+                                <span className="text-amber-600 text-xs flex items-center">
+                                  <AlertTriangle className="h-4 w-4 mr-1" />
+                                  Not Saved
+                                </span>
+                              )}
+                              {isSaved && (
+                                <span className="text-green-600 text-xs flex items-center">
+                                  <CheckCircle2 className="h-4 w-4 mr-1" />
+                                  Saved
+                                </span>
+                              )}
+
+                              <Button
+                                size="sm"
+                                variant="outline"
+                                disabled={isSaving || !canSave}
+                                onClick={() => handleSaveItem(answerIndex)}
+                              >
+                                Save
+                              </Button>
+                            </div>
+                          </div>
+                        </CardHeader>
+
+                        <CollapsibleContent>
+                          {/* Answer Field */}
+                          <CardHeader className="pt-0 pb-3">
+                            <FormField
+                              control={form.control}
+                              name={`answers.${answerIndex}.answer`}
+                              render={({ field }) => (
+                                <FormItem className="mt-3">
+                                  <FormLabel>Answer</FormLabel>
+                                  <FormControl>
+                                    <Textarea
+                                      {...field}
+                                      className="min-h-24"
+                                      placeholder="Enter your answer here"
+                                      onChange={(e) => {
+                                        field.onChange(e)
+                                        form.setValue(
+                                          `answers.${answerIndex}.saved`,
+                                          false,
+                                          { shouldDirty: true }
+                                        )
+                                      }}
+                                    />
+                                  </FormControl>
+                                  <FormMessage />
+                                </FormItem>
+                              )}
+                            />
+                          </CardHeader>
+
+                          {/* Attachments / Dropzone */}
+                          <CardContent>
+                            <div className="grid gap-2">
+                              <FormLabel>Attachments</FormLabel>
+                              <Dropzone
+                                maxSize={6e8} // 600MB
+                                onDropAccepted={(files) =>
+                                  handleDropAccepted(criteriaId, files)
+                                }
+                                onDropRejected={handleDropRejected}
+                              >
+                                {() => (
+                                  <FormItem>
+                                    <DropzoneZone className="flex justify-center h-24">
+                                      <FormControl>
+                                        <DropzoneInput />
+                                      </FormControl>
+                                      <div className="flex items-center gap-6">
+                                        <DropzoneUploadIcon />
+                                        <div className="grid gap-0.5">
+                                          <DropzoneTitle>Drop files here</DropzoneTitle>
+                                          <DropzoneDescription>
+                                            Max size: 600MB
+                                          </DropzoneDescription>
+                                        </div>
+                                      </div>
+                                    </DropzoneZone>
+                                    <FormDescription>
+                                      Or click to browse files
+                                    </FormDescription>
+                                    <FormMessage />
+                                  </FormItem>
+                                )}
+                              </Dropzone>
+                            </div>
+
+                            {/* Existing + Pending Files */}
+                            <div className="mt-4 space-y-4">
+                              {/* 1) Not-yet-uploaded files */}
+                              {newUploads.length > 0 && (
+                                <div className="grid gap-2">
+                                  <h6 className="text-sm font-medium">
+                                    Pending Files ({newUploads.length})
+                                  </h6>
+                                  <FileList>
+                                    {newUploads.map((f, fileIndex) => {
+                                      const fileObj = f.fileObj
+                                      if (!fileObj) return null
+
+                                      return (
+                                        <FileListItem key={fileIndex}>
+                                          <FileListHeader>
+                                            <FileListIcon />
+                                            <FileListInfo>
+                                              <FileListName>{fileObj.name}</FileListName>
+                                              <FileListDescription>
+                                                {prettyBytes(fileObj.size)}
+                                              </FileListDescription>
+                                            </FileListInfo>
+                                            <FileListAction
+                                              onClick={() =>
+                                                removeNewUpload(answerIndex, fileIndex)
+                                              }
+                                            >
+                                              <X className="h-4 w-4" />
+                                              <span className="sr-only">Remove</span>
+                                            </FileListAction>
+                                          </FileListHeader>
+                                        </FileListItem>
+                                      )
+                                    })}
+                                  </FileList>
+                                </div>
+                              )}
+
+                              {/* 2) Already uploaded files */}
+                              {form
+                                .watch(`answers.${answerIndex}.uploadedFiles`)
+                                .map((file, fileIndex) => (
+                                  <FileListItem key={fileIndex}>
+                                    <FileListHeader>
+                                      <FileListIcon />
+                                      <FileListInfo>
+                                        <FileListName>{file.fileName}</FileListName>
+                                        {/* If you want to display the path: 
+                                            <FileListDescription>{file.url}</FileListDescription> 
+                                        */}
+                                      </FileListInfo>
+                                      {file.size && (
+                                        <span className="text-xs text-muted-foreground">
+                                          {prettyBytes(file.size)}
+                                        </span>
+                                      )}
+                                      <FileListAction
+                                        onClick={() =>
+                                          removeUploadedFile(answerIndex, fileIndex)
+                                        }
+                                      >
+                                        <X className="h-4 w-4" />
+                                        <span className="sr-only">Remove</span>
+                                      </FileListAction>
+                                    </FileListHeader>
+                                  </FileListItem>
+                                ))}
+                            </div>
+                          </CardContent>
+                        </CollapsibleContent>
+                      </Card>
+                    </Collapsible>
+                  )
+                })}
+              </div>
+            </TabsContent>
+          ))}
+        </Tabs>
+      </form>
+
+      {/* Confirmation Dialog */}
+      <Dialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
+        <DialogContent>
+          <DialogHeader>
+            <DialogTitle>Confirm Submission</DialogTitle>
+            <DialogDescription>
+              Review your answers before final submission.
+            </DialogDescription>
+          </DialogHeader>
+
+          <div className="space-y-4 max-h-[600px] overflow-y-auto ">
+            {data.map((group) => (
+              <Collapsible key={group.groupName} defaultOpen>
+                <CollapsibleTrigger asChild>
+                  <div className="flex justify-between items-center p-2 mb-1 cursor-pointer ">
+                    <p className="font-semibold">{group.groupName}</p>
+                    <ChevronsUpDown className="h-4 w-4 ml-2" />
+                  </div>
+                </CollapsibleTrigger>
+
+                <CollapsibleContent>
+                  {group.items.map((item) => {
+                    const answerObj = form
+                      .getValues()
+                      .answers.find((a) => a.criteriaId === item.criteriaId)
+
+                    if (!answerObj) return null
+
+                    return (
+                      <div key={item.criteriaId} className="mb-2 p-2 ml-2 border rounded-md text-sm">
+                        {/* code & checkPoint */}
+                        <p className="font-semibold">
+                          {item.code} - {item.checkPoint}
+                        </p>
+
+                        {/* user's typed answer */}
+                        <p className="text-sm font-medium mt-2">Answer:</p>
+                        <p className="whitespace-pre-wrap text-sm">
+                          {answerObj.answer || "(no answer)"}
+                        </p>
+                        {/* attachments */}
+                        <p>Attachments:</p>
+                        {answerObj.uploadedFiles.length > 0 ? (
+                          <ul className="list-disc list-inside ml-4 text-xs">
+                            {answerObj.uploadedFiles.map((file, idx) => (
+                              <li key={idx}>{file.fileName}</li>
+                            ))}
+                          </ul>
+                        ) : (
+                          <p className="text-xs text-muted-foreground">(none)</p>
+                        )}
+                      </div>
+                    )
+                  })}
+                </CollapsibleContent>
+              </Collapsible>
+            ))}
+          </div>
+
+          <DialogFooter>
+            <Button
+              variant="outline"
+              onClick={() => setShowConfirmDialog(false)}
+              disabled={isSubmitting}
+            >
+              Cancel
+            </Button>
+            <Button onClick={handleConfirmSubmission} disabled={isSubmitting}>
+              {isSubmitting ? "Submitting..." : "Confirm Submit"}
+            </Button>
+          </DialogFooter>
+        </DialogContent>
+      </Dialog>
+    </Form>
+  )
+}
\ No newline at end of file
-- 
cgit v1.2.3

