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,115 @@
|
||||
"use client";
|
||||
|
||||
import { CheckCircle2, ListChecks, Plus, Sparkles } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAutoRefresh } from "@/hooks/use-auto-refresh";
|
||||
import { tr } from "@/i18n/tr";
|
||||
import { formatDate } from "@/shared/format";
|
||||
import { SHOPPING_REFRESH_MS } from "@/shared/shopping";
|
||||
import type { ShoppingListSummary } from "../service";
|
||||
import { ShoppingListDialog } from "./list-dialog";
|
||||
|
||||
const t = tr.shopping;
|
||||
|
||||
function ListCard({ list }: { list: ShoppingListSummary }) {
|
||||
const done = list.status === "completed";
|
||||
const Icon = done ? CheckCircle2 : ListChecks;
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
href={`/alisveris/${list.id}`}
|
||||
className="flex h-full flex-col gap-2 rounded-xl border bg-card p-4 transition-colors hover:bg-accent/40 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-muted">
|
||||
<Icon className="size-4.5 text-muted-foreground" aria-hidden />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate font-medium">{list.name}</p>
|
||||
<p className="text-xs text-muted-foreground tabular-nums">
|
||||
{t.itemCount(list.itemCount)}
|
||||
{list.checkedCount && !done
|
||||
? ` · ${t.checkedCount(list.checkedCount, list.itemCount)}`
|
||||
: ""}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{list.preview.length ? (
|
||||
<p className="line-clamp-2 text-sm text-muted-foreground">
|
||||
{list.preview.join(", ")}
|
||||
{list.itemCount > list.preview.length ? "…" : ""}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">{t.emptyList}</p>
|
||||
)}
|
||||
<p className="mt-auto text-xs text-muted-foreground">
|
||||
{done && list.completedAt
|
||||
? t.completedAt(formatDate(list.completedAt))
|
||||
: t.updated(formatDate(list.updatedAt))}
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
/** /alisveris: aktif ve tamamlanan listeler, yeni liste, bekleyen stok önerileri. */
|
||||
export function ShoppingListsView({
|
||||
active,
|
||||
completed,
|
||||
suggestions,
|
||||
canWrite,
|
||||
}: {
|
||||
active: ShoppingListSummary[];
|
||||
completed: ShoppingListSummary[];
|
||||
suggestions: number;
|
||||
canWrite: boolean;
|
||||
}) {
|
||||
const [creating, setCreating] = useState(false);
|
||||
useAutoRefresh(SHOPPING_REFRESH_MS);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
{suggestions > 0 ? (
|
||||
<p className="flex items-start gap-2 rounded-lg border border-amber-500/40 bg-amber-500/5 p-3 text-sm">
|
||||
<Sparkles className="mt-0.5 size-4 shrink-0 text-amber-600" aria-hidden />
|
||||
{t.suggestionsBanner(suggestions)}
|
||||
</p>
|
||||
) : null}
|
||||
<section className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<h2 className="text-sm font-medium text-muted-foreground">{t.active}</h2>
|
||||
{canWrite ? (
|
||||
<Button size="sm" className="ml-auto" onClick={() => setCreating(true)}>
|
||||
<Plus />
|
||||
{t.newList}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
{active.length ? (
|
||||
<ul className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
{active.map((l) => (
|
||||
<ListCard key={l.id} list={l} />
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="rounded-lg border border-dashed p-6 text-center text-sm text-muted-foreground">
|
||||
{t.emptyActive}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
{completed.length ? (
|
||||
<section className="flex flex-col gap-3">
|
||||
<h2 className="text-sm font-medium text-muted-foreground">{t.completed}</h2>
|
||||
<ul className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
{completed.map((l) => (
|
||||
<ListCard key={l.id} list={l} />
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
) : null}
|
||||
{creating ? <ShoppingListDialog list={null} onClose={() => setCreating(false)} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user