haixunMaster/components/product-profile/product-item-form-dialog.tsx

179 lines
5.6 KiB
TypeScript
Raw Normal View History

2026-06-21 12:50:31 +00:00
"use client";
import { useEffect, useState } from "react";
import { ProductContextForm } from "@/components/product-context-form";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { InlineAlert } from "@/components/ui/inline-alert";
import type { ActionFeedback } from "@/lib/use-action-feedback";
import { parseFetchJson } from "@/lib/utils";
import { formatMatchTagsInput, parseMatchTagsInput } from "@/lib/types/product-match";
import { hasProductContext, parseProductContext, serializeProductContext } from "@/lib/types/product-context";
export interface ProductItemRow {
id: string;
label: string;
context: string;
matchTags: string[];
}
interface ProductItemFormDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
brandId: string;
brandName: string;
product?: ProductItemRow | null;
onSaved?: (product: ProductItemRow) => void;
}
export function ProductItemFormDialog({
open,
onOpenChange,
brandId,
brandName,
product,
onSaved,
}: ProductItemFormDialogProps) {
const [label, setLabel] = useState("");
const [context, setContext] = useState("");
const [matchTagsInput, setMatchTagsInput] = useState("");
const [submitting, setSubmitting] = useState(false);
const [formFeedback, setFormFeedback] = useState<ActionFeedback | null>(null);
useEffect(() => {
if (!open) return;
setLabel(product?.label ?? "");
setContext(product?.context ?? "");
setMatchTagsInput(product ? formatMatchTagsInput(product.matchTags) : "");
setFormFeedback(null);
}, [open, product]);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!label.trim()) {
setFormFeedback({ type: "warning", title: "請填寫產品名稱", message: "產品名稱為必填。" });
return;
}
const fields = parseProductContext(context);
const merged = serializeProductContext({
...fields,
brand: brandName,
product: fields.product?.trim() || label.trim(),
});
if (!hasProductContext(merged)) {
setFormFeedback({ type: "warning", title: "請填寫產品特色", message: "至少需填寫一項產品特色。" });
return;
}
const matchTags = parseMatchTagsInput(matchTagsInput);
setFormFeedback(null);
setSubmitting(true);
const res = await fetch("/api/brands/products", {
method: product ? "PATCH" : "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(
product
? { id: product.id, label: label.trim(), context: merged, matchTags }
: { brandId, label: label.trim(), context: merged, matchTags }
),
});
let data: {
error?: string;
product?: { id: string; label: string; context: string; matchTags?: string[] };
} = {};
try {
data = await parseFetchJson(res);
} catch (err) {
setSubmitting(false);
setFormFeedback({
type: "error",
title: "儲存失敗",
message: err instanceof Error ? err.message : "伺服器回應異常",
});
return;
}
setSubmitting(false);
if (!res.ok || !data.product) {
setFormFeedback({
type: "error",
title: "儲存失敗",
message: data.error ?? "伺服器未回傳產品資料",
});
return;
}
const saved: ProductItemRow = {
id: data.product.id,
label: data.product.label,
context: data.product.context,
matchTags: data.product.matchTags ?? matchTags,
};
onSaved?.(saved);
onOpenChange(false);
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[min(100vw-2rem,36rem)]">
<DialogHeader>
<DialogTitle>{product ? "編輯產品" : "新增產品"}</DialogTitle>
<DialogDescription>
{brandName} ·
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
{formFeedback && (
<InlineAlert
type={formFeedback.type}
title={formFeedback.title}
message={formFeedback.message}
onDismiss={() => setFormFeedback(null)}
/>
)}
<div className="space-y-1.5">
<Label></Label>
<Input value={label} onChange={(e) => setLabel(e.target.value)} placeholder="溫和洗毛精" autoFocus />
</div>
<div className="space-y-1.5">
<Label>AI </Label>
<Input
value={matchTagsInput}
onChange={(e) => setMatchTagsInput(e.target.value)}
placeholder="洗毛精、敏感肌、狗洗澡(頓號或逗號分隔)"
/>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<ProductContextForm value={context} onChange={setContext} compact productOnly />
<DialogFooter>
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
</Button>
<Button type="submit" disabled={submitting}>
{submitting ? "儲存中…" : "儲存"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}