"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 { cn, stripHtml } from "@/lib/utils";
import { HtmlDisplay } from "@/components/html-display";
import { FormattedText } from "@/components/formatted-text";
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 } from "lucide-react";
import { signOut } from "next-auth/react";
import Image from "next/image";
import { z } from "zod";
import { useForm, Controller } 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;
}) {
const [imgError, setImgError] = useState(false);
return (
{userImage && !imgError && (
setImgError(true)}
unoptimized
/>
)}
{userName}
);
}
interface Question {
id: string;
formId: string;
type: "text" | "textarea" | "radio" | "checkbox";
label: string;
imageUrl: string | null;
displayOrder: number;
required: boolean;
options: string[] | null;
allowOther: boolean;
}
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 buildSchema = (questions: Question[]) => {
const shape: Record = {};
questions.forEach(q => {
const plainLabel = stripHtml(q.label);
if (q.type === "checkbox") {
shape[q.id] = q.required
? z.array(z.string()).min(1, `จำเป็นต้องตอบ "${plainLabel}"`)
: z.array(z.string()).optional();
} else {
shape[q.id] = q.required
? z.string().min(1, `จำเป็นต้องตอบ "${plainLabel}"`)
: 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, initialOtherValues } = 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 = {};
const others: Record = {};
questions.forEach(q => {
const ans = merged[q.id];
if (q.type === "checkbox") {
let arr = ans ? (Array.isArray(ans) ? ans : ans.split(", ").filter(Boolean)) : [];
if (q.allowOther && q.options) {
const standardOptions = q.options;
const customAnswers = arr.filter((x: string) => !standardOptions.includes(x) && x !== "__OTHER__");
if (customAnswers.length > 0) {
others[q.id] = customAnswers.join(", ");
}
// If the draft had __OTHER__ or if there's a custom answer from server, ensure __OTHER__ is selected
if (arr.includes("__OTHER__") || customAnswers.length > 0) {
arr = arr.filter((x: string) => standardOptions.includes(x));
arr.push("__OTHER__");
}
}
vals[q.id] = arr;
} else if (q.type === "radio") {
let val = ans || "";
if (q.allowOther && q.options && val !== "" && !q.options.includes(val)) {
if (val !== "__OTHER__") {
others[q.id] = val;
}
val = "__OTHER__";
}
vals[q.id] = val;
} else {
vals[q.id] = ans || "";
}
});
return { defaultValues: vals, initialOtherValues: others };
}, [form.id, initialAnswers, questions]);
const [otherValues, setOtherValues] = useState>(initialOtherValues);
const [showSuccessDialog, setShowSuccessDialog] = useState(false);
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 onSubmit = async (values: z.infer) => {
if (!form.isOpen) return;
try {
const formattedAnswers = questions.map((q) => {
let val = values[q.id] as string | string[] | undefined;
if (q.allowOther) {
const customText = otherValues[q.id] || "";
if (Array.isArray(val)) {
val = val.map(v => v === "__OTHER__" ? customText : v);
} else if (val === "__OTHER__") {
val = customText;
}
}
const finalString = Array.isArray(val) ? val.join(", ") : (val || "");
if (q.required && !finalString.trim()) {
throw new Error(`จำเป็นต้องตอบ "${stripHtml(q.label)}" (สำหรับตัวเลือก 'อื่นๆ')`);
}
return {
questionId: q.id,
value: finalString,
};
});
const res = await submitForm(form.id, formattedAnswers);
if (res.ok) {
if (isUpdating) {
toast.success("อัปเดตฟอร์มสำเร็จ!");
} else {
setShowSuccessDialog(true);
router.refresh();
}
}
} 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 && (
)}
ส่งฟอร์มสำเร็จ!
กดอีกครั้งเพื่ออัพเดทคำตอบของคุณ!
setShowSuccessDialog(false)} className="w-full font-bold">
ตกลง
);
}