151 lines
4.4 KiB
TypeScript
151 lines
4.4 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
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 { Textarea } from "@/components/ui/textarea";
|
||
import { InlineAlert } from "@/components/ui/inline-alert";
|
||
import type { ActionFeedback } from "@/lib/use-action-feedback";
|
||
import { parseFetchJson } from "@/lib/utils";
|
||
|
||
export interface BrandRow {
|
||
id: string;
|
||
name: string;
|
||
notes: string | null;
|
||
}
|
||
|
||
interface BrandFormDialogProps {
|
||
open: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
brand?: BrandRow | null;
|
||
onSaved?: (brand: BrandRow) => void;
|
||
}
|
||
|
||
export function BrandFormDialog({ open, onOpenChange, brand, onSaved }: BrandFormDialogProps) {
|
||
const [name, setName] = useState("");
|
||
const [notes, setNotes] = useState("");
|
||
const [submitting, setSubmitting] = useState(false);
|
||
const [formFeedback, setFormFeedback] = useState<ActionFeedback | null>(null);
|
||
|
||
useEffect(() => {
|
||
if (!open) return;
|
||
setName(brand?.name ?? "");
|
||
setNotes(brand?.notes ?? "");
|
||
setFormFeedback(null);
|
||
}, [open, brand]);
|
||
|
||
async function handleSubmit(e: React.FormEvent) {
|
||
e.preventDefault();
|
||
if (!name.trim()) {
|
||
setFormFeedback({ type: "warning", title: "請填寫品牌名稱", message: "品牌名稱為必填。" });
|
||
return;
|
||
}
|
||
|
||
setFormFeedback(null);
|
||
setSubmitting(true);
|
||
const res = await fetch("/api/brands", {
|
||
method: brand ? "PATCH" : "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(
|
||
brand
|
||
? { id: brand.id, name: name.trim(), notes: notes.trim() || null }
|
||
: { name: name.trim(), notes: notes.trim() || undefined }
|
||
),
|
||
});
|
||
let data: { error?: string; brand?: { id: string; name: string; notes: string | null } } = {};
|
||
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) {
|
||
setFormFeedback({
|
||
type: "error",
|
||
title: "儲存失敗",
|
||
message: data.error ?? "無法儲存品牌",
|
||
});
|
||
return;
|
||
}
|
||
|
||
const saved = brand
|
||
? { id: brand.id, name: name.trim(), notes: notes.trim() || null }
|
||
: data.brand
|
||
? {
|
||
id: data.brand.id,
|
||
name: data.brand.name,
|
||
notes: data.brand.notes ?? null,
|
||
}
|
||
: null;
|
||
|
||
if (!saved) {
|
||
setFormFeedback({
|
||
type: "error",
|
||
title: "儲存失敗",
|
||
message: "伺服器未回傳品牌資料",
|
||
});
|
||
return;
|
||
}
|
||
|
||
onSaved?.(saved);
|
||
onOpenChange(false);
|
||
}
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>{brand ? "編輯品牌" : "新增品牌"}</DialogTitle>
|
||
<DialogDescription>一個品牌可有多個產品,置入時 AI 會依標籤推薦。</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={name} onChange={(e) => setName(e.target.value)} placeholder="kahu" autoFocus />
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<Label>品牌備註(選填)</Label>
|
||
<Textarea
|
||
value={notes}
|
||
onChange={(e) => setNotes(e.target.value)}
|
||
rows={2}
|
||
placeholder="例:寵物清潔用品、溫和無添加"
|
||
/>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
|
||
取消
|
||
</Button>
|
||
<Button type="submit" disabled={submitting}>
|
||
{submitting ? "儲存中…" : "儲存"}
|
||
</Button>
|
||
</DialogFooter>
|
||
</form>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
} |