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

179 lines
5.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
}