Files
butcem/src/features/shopping/components/lists-view.tsx
T
suphicakir fa70168622 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>
2026-11-06 23:28:54 +03:00

116 lines
4.1 KiB
TypeScript

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