d7d2ac557b
dueDate was sitting on every job but the UI never warned anyone about
it. Added a small badge primitive and surfaced it everywhere a job is
listed plus a dedicated dashboard card.
- lib/appwrite/due-date.ts: dueState() buckets a job into overdue /
today / soon (1-3 days) / future / none, with delivered + cancelled
jobs always resolving to none. dueLabel() returns the Turkish text.
- components/due-badge.tsx: renders nothing for future/none, a
secondary badge for today/soon, a destructive badge for overdue.
Drop-in (job, className).
- JobsTable (inbound + outbound): new 'Termin' column shows the date
and the DueBadge stacked. Sorting still on createdAt for now —
explicit ordering by dueDate will come with the filter task.
- Job detail header: badge stack now has the DueBadge before the
status pill so a glance at the page shows whether the case is
behind schedule.
- Dashboard: getDashboardData fetches up to 10 overdue jobs in
parallel (lessThan('dueDate', now), excluding delivered/cancelled).
Added a destructive-tinted 'Geciken İşler' card above the rest of
the widgets when the list is non-empty, with quick links into each
job's detail page.
109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import Link from "next/link";
|
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { DueBadge } from "@/components/due-badge";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import {
|
|
JOB_STATUS_LABELS,
|
|
PROSTHETIC_TYPE_LABELS,
|
|
} from "@/lib/appwrite/job-types";
|
|
import type { JobWithCounterpart } from "@/lib/appwrite/job-queries";
|
|
import type { JobStatus } from "@/lib/appwrite/schema";
|
|
|
|
const dateFormatter = new Intl.DateTimeFormat("tr-TR", {
|
|
day: "2-digit",
|
|
month: "2-digit",
|
|
year: "numeric",
|
|
});
|
|
|
|
function statusVariant(status: JobStatus): "default" | "secondary" | "outline" | "destructive" {
|
|
switch (status) {
|
|
case "delivered":
|
|
return "default";
|
|
case "sent":
|
|
return "secondary";
|
|
case "in_progress":
|
|
return "secondary";
|
|
case "cancelled":
|
|
return "destructive";
|
|
default:
|
|
return "outline";
|
|
}
|
|
}
|
|
|
|
export function JobsTable({
|
|
rows,
|
|
counterpartLabel,
|
|
emptyMessage,
|
|
}: {
|
|
rows: JobWithCounterpart[];
|
|
counterpartLabel: string;
|
|
emptyMessage: string;
|
|
}) {
|
|
if (rows.length === 0) {
|
|
return (
|
|
<p className="text-muted-foreground py-6 text-center text-sm">{emptyMessage}</p>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>{counterpartLabel}</TableHead>
|
|
<TableHead>Hasta Kodu</TableHead>
|
|
<TableHead>Üye</TableHead>
|
|
<TableHead>Renk</TableHead>
|
|
<TableHead>Tür</TableHead>
|
|
<TableHead>Durum</TableHead>
|
|
<TableHead>Termin</TableHead>
|
|
<TableHead>Tarih</TableHead>
|
|
<TableHead className="text-right">İşlem</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{rows.map((j) => (
|
|
<TableRow key={j.$id}>
|
|
<TableCell className="font-medium">{j.counterpart?.companyName ?? "—"}</TableCell>
|
|
<TableCell className="font-mono text-xs">{j.patientCode}</TableCell>
|
|
<TableCell className="tabular-nums">{j.memberCount}</TableCell>
|
|
<TableCell className="text-muted-foreground">{j.color || "—"}</TableCell>
|
|
<TableCell className="text-muted-foreground">
|
|
{PROSTHETIC_TYPE_LABELS[j.prostheticType] ?? j.prostheticType}
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge variant={statusVariant(j.status)}>{JOB_STATUS_LABELS[j.status]}</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-muted-foreground text-xs">
|
|
{j.dueDate ? (
|
|
<div className="flex flex-col gap-1">
|
|
<span>{dateFormatter.format(new Date(j.dueDate))}</span>
|
|
<DueBadge job={j} />
|
|
</div>
|
|
) : (
|
|
"—"
|
|
)}
|
|
</TableCell>
|
|
<TableCell className="text-muted-foreground text-xs">
|
|
{dateFormatter.format(new Date(j.$createdAt))}
|
|
</TableCell>
|
|
<TableCell className="text-right">
|
|
<Button asChild size="sm" variant="outline">
|
|
<Link href={`/jobs/${j.$id}`}>Detay</Link>
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
);
|
|
}
|