"use client"; import React, { useState, useEffect, useMemo } from "react"; import { useRouter } from "next/navigation"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { Dialog, DialogContent, DialogTrigger, DialogTitle } from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; import { toast } from "sonner"; import { submitForm, deleteOwnSubmission } from "@/app/actions/submissions"; import { Loader2, LogOut, ChevronRight, ChevronLeft } from "lucide-react"; import { signOut } from "next-auth/react"; import Image from "next/image"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; export function FormLayoutClient({ userName, userImage, }: { userName: string; userImage: string; }) { return (
{userImage && ( {userName} )} {userName}
); } interface Question { id: string; formId: string; type: "text" | "textarea" | "radio" | "checkbox"; label: string; imageUrl: string | null; displayOrder: number; required: boolean; options: string[] | null; } interface FormFillerClientProps { form: { id: string; title: string; description: string | null; isOpen: boolean; }; questions: Question[]; initialAnswers?: Record; } const LS_KEY = (formId: string) => `form-draft-${formId}`; const QUESTIONS_PER_STEP = 3; const buildSchema = (questions: Question[]) => { const shape: Record = {}; questions.forEach(q => { if (q.type === "checkbox") { shape[q.id] = q.required ? z.array(z.string()).min(1, `จำเป็นต้องตอบ "${q.label}"`) : z.array(z.string()).optional(); } else { shape[q.id] = q.required ? z.string().min(1, `จำเป็นต้องตอบ "${q.label}"`) : z.string().optional(); } }); return z.object(shape); }; export function FormFillerClient({ form, questions, initialAnswers = {} }: FormFillerClientProps) { const router = useRouter(); const isUpdating = Object.keys(initialAnswers).length > 0; const formSchema = useMemo(() => buildSchema(questions), [questions]); const defaultValues = useMemo(() => { const serverAnswers = { ...initialAnswers }; let draft: Record = {}; try { const raw = localStorage.getItem(LS_KEY(form.id)); if (raw) draft = JSON.parse(raw); } catch {} const merged = { ...draft, ...serverAnswers }; const vals: Record = {}; questions.forEach(q => { if (q.type === "checkbox") { vals[q.id] = merged[q.id] ? (Array.isArray(merged[q.id]) ? merged[q.id] : merged[q.id].split(", ").filter(Boolean)) : []; } else { vals[q.id] = merged[q.id] || ""; } }); return vals; }, [form.id, initialAnswers, questions]); const methods = useForm>({ resolver: zodResolver(formSchema), defaultValues, mode: "onTouched", }); const { handleSubmit, control, watch, formState: { isSubmitting }, trigger, reset } = methods; useEffect(() => { const subscription = watch((value) => { try { localStorage.setItem(LS_KEY(form.id), JSON.stringify(value)); } catch {} }); return () => subscription.unsubscribe(); }, [watch, form.id]); const [currentStep, setCurrentStep] = useState(0); const totalSteps = Math.ceil(questions.length / QUESTIONS_PER_STEP); const currentQuestions = questions.slice( currentStep * QUESTIONS_PER_STEP, (currentStep + 1) * QUESTIONS_PER_STEP ); const nextStep = async () => { const fieldsToValidate = currentQuestions.map(q => q.id); const isStepValid = await trigger(fieldsToValidate); if (isStepValid) { setCurrentStep(prev => Math.min(prev + 1, totalSteps - 1)); window.scrollTo(0, 0); } }; const prevStep = () => { setCurrentStep(prev => Math.max(prev - 1, 0)); window.scrollTo(0, 0); }; const onSubmit = async (values: z.infer) => { if (!form.isOpen) return; try { const formattedAnswers = questions.map((q) => { const val = values[q.id] as string | string[] | undefined; return { questionId: q.id, value: Array.isArray(val) ? val.join(", ") : (val || ""), }; }); const res = await submitForm(form.id, formattedAnswers); if (res.ok) { toast.success(isUpdating ? "อัปเดตฟอร์มสำเร็จ!" : "ส่งฟอร์มสำเร็จ!"); } } catch (err: any) { console.error(err); toast.error(err.message || "ไม่สามารถส่งฟอร์มได้"); } }; const handleDelete = async () => { try { const res = await deleteOwnSubmission(form.id); if (res.ok) { toast.success("ลบข้อมูลสำเร็จ!"); localStorage.removeItem(LS_KEY(form.id)); reset({}); router.refresh(); } } catch (err: any) { console.error(err); toast.error(err.message || "ไม่สามารถลบข้อมูลได้"); } }; return (
{!form.isOpen && (
ฟอร์มนี้ถูกปิดใช้งานชั่วคราว
)} {form.title} {form.description && ( {form.description} )}
{currentQuestions.map((q) => (
{q.imageUrl && (
{q.label}
Enlarged image for {q.label}
{q.label}
)}
( {q.type === "text" && ( )} {q.type === "textarea" && (