feat: job status/step flow, file upload, finance sync, notifications
Job lifecycle
- acceptJobAction (lab): pending → in_progress + currentStep=olcu
- advanceStepAction (lab): step ilerletir, son adım sonrası status=sent
- markDeliveredAction (clinic): sent → delivered
- cancelJobAction: pending iş iptali (her iki taraf)
- job_status_history her step transition'da idempotent kayıt
- Detay sayfası interactive panel + Aşama Geçmişi kartı
Job files (Appwrite Storage job-files bucket, 30MB/file)
- uploadJobFilesAction: çoklu dosya, mimeType'tan kind sınıflandırma
(scan/image/document), her iki team'e read permission, partial-fail
rollback (storage + row temizliği)
- deleteJobFileAction: yetkilendirilmiş silme, file + row birlikte
- JobFilesPanel: client-side select + upload + liste + indir + sil
- next.config bodySizeLimit 3mb → 100mb (toplu yükleme için)
Finance sync (idempotent)
- syncFinanceForJob helper: sent/delivered transition'larında klinik
payable + lab receivable rows (jobId+tenantId+type unique kontrolü,
her tarafta tek satır garanti)
- markFinancePaidAction / reopenFinanceAction: manuel ödendi/geri al
- /finance sayfası: stat kartlar (bekleyen alacak/borç, aylık gelir/gider)
+ hareketler tablosu, role-aware kopyalar
- Memory rule [[feedback_cross_entity_sync_helpers]]: best-effort, never
re-throws
Notifications
- createNotification helper, connection (request/approve) ve job
(create/accept/sent/delivered) eventlerinde tetikleniyor
- /notifications sayfası + tek tek / hepsi okundu işaretle
- Header'a Bell ikonu + okunmamış count badge (layout SSR'de besler)
- Middleware PROTECTED_PREFIXES'e /notifications ekli
This commit is contained in:
@@ -1,17 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Building2, Plus } from "lucide-react";
|
||||
import { Bell, Building2, Plus } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { ModeToggle } from "@/components/mode-toggle";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||
|
||||
import type { ShellCompany } from "@/app/(dashboard)/dashboard-shell";
|
||||
|
||||
export function SiteHeader({ company }: { company?: ShellCompany }) {
|
||||
export function SiteHeader({
|
||||
company,
|
||||
unreadCount = 0,
|
||||
}: {
|
||||
company?: ShellCompany;
|
||||
unreadCount?: number;
|
||||
}) {
|
||||
const showNewJobCta = company?.kind === "clinic";
|
||||
|
||||
return (
|
||||
@@ -39,6 +46,19 @@ export function SiteHeader({ company }: { company?: ShellCompany }) {
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
<Button asChild variant="ghost" size="icon" className="relative">
|
||||
<Link href="/notifications" aria-label="Bildirimler">
|
||||
<Bell className="size-4" />
|
||||
{unreadCount > 0 && (
|
||||
<Badge
|
||||
variant="destructive"
|
||||
className="absolute -right-1 -top-1 size-4 min-w-4 justify-center rounded-full p-0 text-[10px]"
|
||||
>
|
||||
{unreadCount > 99 ? "99+" : unreadCount}
|
||||
</Badge>
|
||||
)}
|
||||
</Link>
|
||||
</Button>
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user