2c6c074a06
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
46 lines
1.3 KiB
TypeScript
46 lines
1.3 KiB
TypeScript
import { redirect } from "next/navigation";
|
||
|
||
import { getActiveContext } from "@/lib/appwrite/active-context";
|
||
import { countUnreadNotifications } from "@/lib/appwrite/notification-helpers";
|
||
import { getLogoUrl } from "@/lib/appwrite/storage";
|
||
import { getUserPrefs } from "@/lib/appwrite/user-prefs-actions";
|
||
import type { UserPrefs as ThemePrefs } from "@/lib/appwrite/user-prefs-actions";
|
||
import { DashboardShell } from "./dashboard-shell";
|
||
|
||
export default async function DashboardLayout({
|
||
children,
|
||
}: {
|
||
children: React.ReactNode;
|
||
}) {
|
||
const ctx = await getActiveContext();
|
||
if (!ctx) redirect("/onboarding");
|
||
|
||
const [themePrefs, unreadCount] = (await Promise.all([
|
||
getUserPrefs(),
|
||
countUnreadNotifications(ctx.tenantId),
|
||
])) as [ThemePrefs, number];
|
||
|
||
const company = {
|
||
id: ctx.tenantId,
|
||
name: ctx.settings?.companyName ?? "Çalışma alanı",
|
||
logoUrl: getLogoUrl(ctx.settings?.logo) ?? null,
|
||
kind: (ctx.settings?.kind ?? "lab") as "lab" | "clinic",
|
||
};
|
||
const user = {
|
||
id: ctx.user.id,
|
||
name: ctx.user.name || ctx.user.email,
|
||
email: ctx.user.email,
|
||
};
|
||
|
||
return (
|
||
<DashboardShell
|
||
user={user}
|
||
company={company}
|
||
initialPrefs={themePrefs}
|
||
unreadCount={unreadCount}
|
||
>
|
||
{children}
|
||
</DashboardShell>
|
||
);
|
||
}
|