diff --git a/src/app/(dashboard)/jobs/inbound/components/bulk-accept-button.tsx b/src/app/(dashboard)/jobs/inbound/components/bulk-accept-button.tsx
new file mode 100644
index 0000000..d166296
--- /dev/null
+++ b/src/app/(dashboard)/jobs/inbound/components/bulk-accept-button.tsx
@@ -0,0 +1,68 @@
+"use client";
+
+import { useState, useTransition } from "react";
+import { useRouter } from "next/navigation";
+import { CheckCheck, Loader2 } from "lucide-react";
+import { toast } from "sonner";
+
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { bulkAcceptPendingJobsAction } from "@/lib/appwrite/job-actions";
+
+export function BulkAcceptButton({ count }: { count: number }) {
+ const router = useRouter();
+ const [open, setOpen] = useState(false);
+ const [pending, startTransition] = useTransition();
+
+ if (count === 0) return null;
+
+ function onConfirm() {
+ startTransition(async () => {
+ const res = await bulkAcceptPendingJobsAction();
+ if (res.ok) {
+ toast.success(`${res.accepted ?? 0} iş işleme alındı.`);
+ setOpen(false);
+ router.refresh();
+ } else {
+ toast.error(res.error ?? "İşlem başarısız.");
+ }
+ });
+ }
+
+ return (
+
+ );
+}
diff --git a/src/app/(dashboard)/jobs/inbound/page.tsx b/src/app/(dashboard)/jobs/inbound/page.tsx
index 6482507..a29e662 100644
--- a/src/app/(dashboard)/jobs/inbound/page.tsx
+++ b/src/app/(dashboard)/jobs/inbound/page.tsx
@@ -5,6 +5,7 @@ import { listInboundJobs } from "@/lib/appwrite/job-queries";
import { requireTenant } from "@/lib/appwrite/tenant-guard";
import { JobsFilterBar } from "../_components/jobs-filter-bar";
import { JobsTable } from "../_components/jobs-table";
+import { BulkAcceptButton } from "./components/bulk-accept-button";
export const metadata = {
title: "DLS — Gelen İşler",
@@ -29,14 +30,18 @@ export default async function InboundJobsPage({
q: sp.q,
};
const rows = ctx.kind === "lab" ? await listInboundJobs(ctx.tenantId, filters) : [];
+ const pendingCount = rows.filter((j) => j.status === "pending").length;
return (
-
-
Gelen İşler
-
- Bağlı kliniklerden size yönlendirilmiş protez işleri.
-
+
+
+
Gelen İşler
+
+ Bağlı kliniklerden size yönlendirilmiş protez işleri.
+
+
+ {ctx.kind === "lab" &&
}
diff --git a/src/lib/appwrite/job-actions.ts b/src/lib/appwrite/job-actions.ts
index 2ebffaf..e19e9d6 100644
--- a/src/lib/appwrite/job-actions.ts
+++ b/src/lib/appwrite/job-actions.ts
@@ -333,6 +333,68 @@ export async function acceptJobAction(
return { ok: true };
}
+/**
+ * Lab takes all currently-pending jobs in one go. Same effect as calling
+ * acceptJobAction for each row individually — status flips to in_progress,
+ * step jumps to alt_yapi_prova, location lands at_lab. Partial failures
+ * are tolerated; we return how many actually moved.
+ */
+export async function bulkAcceptPendingJobsAction(): Promise<
+ JobActionState & { accepted?: number }
+> {
+ let ctx;
+ try {
+ ctx = await requireTenant();
+ requireRole(ctx, ["owner", "admin", "member"]);
+ requireTenantKind(ctx, ["lab"]);
+ } catch {
+ return { ok: false, error: "Bu işlemi yalnızca laboratuvar yapabilir." };
+ }
+
+ const { tablesDB } = createAdminClient();
+ const result = await tablesDB.listRows({
+ databaseId: DATABASE_ID,
+ tableId: TABLES.jobs,
+ queries: [
+ Query.equal("labTenantId", ctx.tenantId),
+ Query.equal("status", "pending"),
+ Query.limit(200),
+ ],
+ });
+ const rows = result.rows as unknown as Job[];
+ if (rows.length === 0) return { ok: true, accepted: 0 };
+
+ const outcomes = await Promise.allSettled(
+ rows.map(async (job) => {
+ await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, job.$id, {
+ status: "in_progress",
+ currentStep: "alt_yapi_prova",
+ location: "at_lab",
+ });
+ void appendJobHistory({ job, step: "olcu", completedBy: ctx.user.id });
+ void createNotification({
+ tenantId: job.clinicTenantId,
+ jobId: job.$id,
+ message: `${ctx.settings?.companyName ?? "Lab"} hasta ${job.patientCode} işini işleme aldı, alt yapı üretiminde.`,
+ });
+ }),
+ );
+ const accepted = outcomes.filter((o) => o.status === "fulfilled").length;
+
+ void logAudit({
+ tenantId: ctx.tenantId,
+ userId: ctx.user.id,
+ action: "update",
+ entityType: "job",
+ entityId: "bulk",
+ changes: { bulk: "accept_pending", count: accepted },
+ });
+
+ revalidatePath("/jobs/inbound");
+ revalidatePath("/dashboard");
+ return { ok: true, accepted };
+}
+
/**
* Lab hands the work back to the clinic for the next physical step
* (prova or final delivery). The current step stays the same — only the