07ba531d31
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
107 lines
4.0 KiB
TypeScript
107 lines
4.0 KiB
TypeScript
import { ClipboardCheck, Clock } from "lucide-react";
|
||
import Link from "next/link";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||
import { tr } from "@/i18n/tr";
|
||
import { formatStock } from "@/shared/stock";
|
||
import { getStock } from "../service";
|
||
|
||
const t = tr.stock;
|
||
const LIMIT = 5;
|
||
|
||
/** Dashboard: eşiğin altındaki ürünler, SKT'si yaklaşan partiler, sayımı gelen kategoriler. */
|
||
export async function LowStockCard({
|
||
userId,
|
||
householdId,
|
||
}: {
|
||
userId: string;
|
||
householdId: string;
|
||
}) {
|
||
const data = await getStock(userId, householdId);
|
||
if (!data || (!data.suggestions.length && !data.expiring.length && !data.countDue.length))
|
||
return null;
|
||
const low = data.suggestions.slice(0, LIMIT);
|
||
const expiring = data.expiring.slice(0, Math.max(0, LIMIT - low.length) || 2);
|
||
return (
|
||
<Card size="sm">
|
||
<CardHeader>
|
||
<CardTitle className="text-base">{t.dashboard.title}</CardTitle>
|
||
<CardDescription>
|
||
{t.dashboard.description(
|
||
data.suggestions.length,
|
||
data.expiring.length,
|
||
data.countDue.length,
|
||
)}
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="flex flex-col gap-3">
|
||
<ul className="divide-y text-sm">
|
||
{low.map((i) => (
|
||
<li key={i.id} className="flex items-center justify-between gap-3 py-1.5">
|
||
<Link href={`/stok/${i.id}`} className="min-w-0 truncate hover:underline">
|
||
{i.name}
|
||
</Link>
|
||
<span className="shrink-0 text-xs tabular-nums">
|
||
<span className="text-expense">{formatStock(Math.max(0, i.current), i.unit)}</span>
|
||
<span className="text-muted-foreground">
|
||
{" · "}
|
||
{t.suggestions.suggested(formatStock(i.suggestedQuantity, i.unit))}
|
||
</span>
|
||
</span>
|
||
</li>
|
||
))}
|
||
{expiring.map(({ item, lot }) => (
|
||
<li
|
||
key={`${item.id}-${lot.index}`}
|
||
className="flex items-center justify-between gap-3 py-1.5"
|
||
>
|
||
<Link href={`/stok/${item.id}`} className="min-w-0 truncate hover:underline">
|
||
{item.name}
|
||
</Link>
|
||
<span
|
||
className={
|
||
lot.status === "expired"
|
||
? "flex shrink-0 items-center gap-1 text-xs text-expense"
|
||
: "flex shrink-0 items-center gap-1 text-xs text-amber-700 dark:text-amber-400"
|
||
}
|
||
>
|
||
<Clock className="size-3" />
|
||
{t.expiring.on(
|
||
new Intl.DateTimeFormat("tr-TR", { day: "numeric", month: "short" }).format(
|
||
new Date(`${lot.expiryDate}T12:00:00+03:00`),
|
||
),
|
||
)}
|
||
</span>
|
||
</li>
|
||
))}
|
||
{data.countDue.slice(0, 3).map((g) => (
|
||
<li
|
||
key={`count-${g.categoryId}`}
|
||
className="flex items-center justify-between gap-3 py-1.5"
|
||
>
|
||
<Link
|
||
href={`/stok/sayim?kategori=${g.categoryId}`}
|
||
className="min-w-0 truncate hover:underline"
|
||
>
|
||
{g.path}
|
||
</Link>
|
||
<span className="flex shrink-0 items-center gap-1 text-xs text-amber-700 dark:text-amber-400">
|
||
<ClipboardCheck className="size-3" />
|
||
{t.count.dueBadge(g.overdueDays)}
|
||
</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
{data.suggestions.length > low.length ? (
|
||
<p className="text-xs text-muted-foreground">
|
||
{t.dashboard.more(data.suggestions.length - low.length)}
|
||
</p>
|
||
) : null}
|
||
<Button size="sm" variant="outline" className="w-fit" asChild>
|
||
<Link href="/stok">{t.dashboard.open}</Link>
|
||
</Button>
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|