feat(ui): alışveriş listeleri, akıllı öneri paneli ve alışveriş modu
- /alisveris, /alisveris/:id ve /alisveris/:id/mod sayfaları; odak ve 30 sn yenileme - stok önerileri paneli, kalem ekle/düzenle/taşı, mağazaya gruplu kontrol listesi - dashboard Alışveriş kartı, kenar menü, ürün tipi formunda hacimli işareti Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { ResponsiveDialog } from "@/components/responsive-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Field, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { tr } from "@/i18n/tr";
|
||||
import { saveShoppingListAction } from "../actions";
|
||||
|
||||
const t = tr.shopping;
|
||||
|
||||
/** Yeni liste / listeyi düzenle. Yeni liste açılınca detayına gider. */
|
||||
export function ShoppingListDialog({
|
||||
list,
|
||||
onClose,
|
||||
}: {
|
||||
list: { id: string; name: string; note: string } | null;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [v, setV] = useState({ name: list?.name ?? "", note: list?.note ?? "" });
|
||||
const [errors, setErrors] = useState<Record<string, string | undefined>>({});
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
async function submit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setBusy(true);
|
||||
const r = await saveShoppingListAction(list?.id ?? null, v);
|
||||
setBusy(false);
|
||||
if (!r.ok) {
|
||||
setErrors({ ...r.fieldErrors, root: r.fieldErrors ? undefined : r.error });
|
||||
return;
|
||||
}
|
||||
toast.success(t.toast.saved);
|
||||
onClose();
|
||||
if (!list && r.data && typeof r.data === "object" && "id" in r.data)
|
||||
router.push(`/alisveris/${r.data.id}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveDialog
|
||||
open
|
||||
onOpenChange={(o) => !o && onClose()}
|
||||
title={list ? t.editTitle : t.createTitle}
|
||||
footer={
|
||||
<>
|
||||
<Button variant="outline" type="button" onClick={onClose}>
|
||||
{tr.common.cancel}
|
||||
</Button>
|
||||
<Button type="submit" form="shopping-list-form" disabled={busy}>
|
||||
{tr.common.save}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<form id="shopping-list-form" onSubmit={submit} noValidate>
|
||||
<FieldGroup>
|
||||
<Field data-invalid={Boolean(errors.name)}>
|
||||
<FieldLabel htmlFor="sl-name">{t.fields.name}</FieldLabel>
|
||||
<Input
|
||||
id="sl-name"
|
||||
value={v.name}
|
||||
placeholder={t.fields.namePlaceholder}
|
||||
onChange={(e) => setV((s) => ({ ...s, name: e.target.value }))}
|
||||
aria-invalid={Boolean(errors.name)}
|
||||
autoFocus
|
||||
/>
|
||||
{errors.name ? <FieldError>{errors.name}</FieldError> : null}
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="sl-note">{t.fields.note}</FieldLabel>
|
||||
<Input
|
||||
id="sl-note"
|
||||
value={v.note}
|
||||
onChange={(e) => setV((s) => ({ ...s, note: e.target.value }))}
|
||||
/>
|
||||
</Field>
|
||||
{errors.root ? <FieldError>{errors.root}</FieldError> : null}
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user