feat(jobs): due-date awareness — DueBadge + dashboard 'Geciken İşler' widget

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.
This commit is contained in:
kovakmedya
2026-05-22 16:02:13 +03:00
parent d3977a5dcf
commit d7d2ac557b
6 changed files with 166 additions and 7 deletions
+7 -3
View File
@@ -3,6 +3,7 @@ import { notFound, redirect } from "next/navigation";
import { Query } from "node-appwrite";
import { Badge } from "@/components/ui/badge";
import { DueBadge } from "@/components/due-badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { listJobFiles } from "@/lib/appwrite/job-file-queries";
@@ -116,9 +117,12 @@ export default async function JobDetailPage({
</p>
</div>
<div className="flex flex-col items-end gap-3">
<Badge variant="secondary" className="text-sm">
{JOB_STATUS_LABELS[job.status]}
</Badge>
<div className="flex items-center gap-2">
<DueBadge job={job} />
<Badge variant="secondary" className="text-sm">
{JOB_STATUS_LABELS[job.status]}
</Badge>
</div>
<JobActionsPanel job={job} side={side} kind={ctx.kind} />
</div>
</div>