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
65 lines
1.5 KiB
TypeScript
65 lines
1.5 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import type { NextRequest } from "next/server";
|
|
|
|
const AUTH_COOKIE = "lab-session";
|
|
|
|
const PUBLIC_AUTH_PATHS = [
|
|
"/sign-in",
|
|
"/sign-in-2",
|
|
"/sign-in-3",
|
|
"/sign-up",
|
|
"/sign-up-2",
|
|
"/sign-up-3",
|
|
"/forgot-password",
|
|
"/forgot-password-2",
|
|
"/forgot-password-3",
|
|
"/reset-password",
|
|
];
|
|
|
|
const PROTECTED_PREFIXES = [
|
|
"/dashboard",
|
|
"/onboarding",
|
|
"/settings",
|
|
"/jobs",
|
|
"/products",
|
|
"/finance",
|
|
"/connections",
|
|
"/notifications",
|
|
];
|
|
|
|
export function middleware(request: NextRequest) {
|
|
const { pathname } = request.nextUrl;
|
|
const session = request.cookies.get(AUTH_COOKIE)?.value;
|
|
|
|
// Legacy redirects
|
|
if (pathname === "/login") {
|
|
return NextResponse.redirect(new URL("/sign-in", request.url));
|
|
}
|
|
if (pathname === "/register") {
|
|
return NextResponse.redirect(new URL("/sign-up", request.url));
|
|
}
|
|
|
|
const isAuthPath = PUBLIC_AUTH_PATHS.some(
|
|
(p) => pathname === p || pathname.startsWith(`${p}/`),
|
|
);
|
|
const isProtected = PROTECTED_PREFIXES.some(
|
|
(p) => pathname === p || pathname.startsWith(`${p}/`),
|
|
);
|
|
|
|
if (isProtected && !session) {
|
|
const url = new URL("/sign-in", request.url);
|
|
url.searchParams.set("redirect", pathname);
|
|
return NextResponse.redirect(url);
|
|
}
|
|
|
|
if (isAuthPath && session) {
|
|
return NextResponse.redirect(new URL("/dashboard", request.url));
|
|
}
|
|
|
|
return NextResponse.next();
|
|
}
|
|
|
|
export const config = {
|
|
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
|
};
|