diff --git a/src/app/(dashboard)/jobs/[jobId]/page.tsx b/src/app/(dashboard)/jobs/[jobId]/page.tsx index 5eab726..26144b5 100644 --- a/src/app/(dashboard)/jobs/[jobId]/page.tsx +++ b/src/app/(dashboard)/jobs/[jobId]/page.tsx @@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { listJobFiles } from "@/lib/appwrite/job-file-queries"; import { listJobHistory } from "@/lib/appwrite/job-history-queries"; +import { getPatient } from "@/lib/appwrite/patient-queries"; import { toPlain } from "@/lib/appwrite/serialize"; import { JOB_STATUS_LABELS, @@ -85,6 +86,12 @@ export default async function JobDetailPage({ const currentStepIdx = job.currentStep ? JOB_STEP_ORDER.indexOf(job.currentStep) : -1; const side = job.clinicTenantId === ctx.tenantId ? "clinic" : "lab"; + // Patient record only resolves on the clinic side — labs see the code only. + const patient = + side === "clinic" && job.patientId + ? await getPatient(job.patientId, ctx.tenantId) + : null; + return (
@@ -93,9 +100,14 @@ export default async function JobDetailPage({ {counterpartLabel}: {counterpart?.companyName ?? "—"}

- Hasta {job.patientCode} + {patient ? `${patient.firstName} ${patient.lastName}` : `Hasta ${job.patientCode}`}

+ {patient && ( + <> + {job.patientCode} ·{" "} + + )} {PROSTHETIC_TYPE_LABELS[job.prostheticType]} · {job.memberCount} üye

@@ -180,6 +192,37 @@ export default async function JobDetailPage({
+ {patient && ( + + + Hasta Bilgileri + + Bu alan yalnızca kliniğinize görünür — laboratuvar hasta kodu + dışında bir veri görmez. + + + + + {patient.firstName} {patient.lastName} + + {patient.phone || "—"} + + {patient.dateOfBirth + ? dateFormatter.format(new Date(patient.dateOfBirth)) + : "—"} + + {patient.notes && ( +
+

+ Notlar +

+

{patient.notes}

+
+ )} +
+
+ )} + Taranan Dosyalar ve Görseller diff --git a/src/app/(dashboard)/jobs/new/components/new-job-form.tsx b/src/app/(dashboard)/jobs/new/components/new-job-form.tsx index 0062bc4..4890f48 100644 --- a/src/app/(dashboard)/jobs/new/components/new-job-form.tsx +++ b/src/app/(dashboard)/jobs/new/components/new-job-form.tsx @@ -1,6 +1,7 @@ "use client"; -import { useActionState, useEffect } from "react"; +import { useActionState, useEffect, useMemo, useState } from "react"; +import Link from "next/link"; import { useRouter } from "next/navigation"; import { Loader2, Send } from "lucide-react"; import { toast } from "sonner"; @@ -24,6 +25,8 @@ import { import type { JobCounterpart } from "@/lib/appwrite/job-queries"; import type { ProstheticType } from "@/lib/appwrite/schema"; +const NONE_PATIENT = "__none__"; + const PROSTHETIC_TYPES: ProstheticType[] = [ "metal_porselen", "zirkonyum", @@ -33,15 +36,28 @@ const PROSTHETIC_TYPES: ProstheticType[] = [ "diger", ]; +type PatientOption = { id: string; code: string; label: string }; + export function NewJobForm({ labs, + patients, defaultCurrency, }: { labs: JobCounterpart[]; + patients: PatientOption[]; defaultCurrency: string; }) { const router = useRouter(); const [state, action, pending] = useActionState(createJobAction, initialJobFormState); + const [patientId, setPatientId] = useState( + patients.length > 0 ? patients[0].id : NONE_PATIENT, + ); + + const patientById = useMemo( + () => new Map(patients.map((p) => [p.id, p])), + [patients], + ); + const selectedPatient = patientId !== NONE_PATIENT ? patientById.get(patientId) : undefined; useEffect(() => { if (state.ok) { @@ -74,14 +90,62 @@ export function NewJobForm({ )} +
+
+ + + + Yeni hasta ekle + +
+ + + {state.fieldErrors?.patientId && ( +

{state.fieldErrors.patientId}

+ )} +
+
- + {state.fieldErrors?.patientCode && (

{state.fieldErrors.patientCode}

diff --git a/src/app/(dashboard)/jobs/new/page.tsx b/src/app/(dashboard)/jobs/new/page.tsx index f5a670d..dc47a76 100644 --- a/src/app/(dashboard)/jobs/new/page.tsx +++ b/src/app/(dashboard)/jobs/new/page.tsx @@ -4,6 +4,7 @@ import { redirect } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { listApprovedLabsForClinic } from "@/lib/appwrite/job-queries"; +import { listPatients } from "@/lib/appwrite/patient-queries"; import { requireTenant, requireTenantKind } from "@/lib/appwrite/tenant-guard"; import { NewJobForm } from "./components/new-job-form"; @@ -20,7 +21,10 @@ export default async function NewJobPage() { redirect("/dashboard"); } - const labs = await listApprovedLabsForClinic(ctx.tenantId); + const [labs, patients] = await Promise.all([ + listApprovedLabsForClinic(ctx.tenantId), + listPatients(ctx.tenantId, { includeArchived: false }), + ]); const defaultCurrency = ctx.settings?.defaultCurrency ?? "TRY"; return ( @@ -55,7 +59,15 @@ export default async function NewJobPage() { - + ({ + id: p.$id, + code: p.patientCode, + label: `${p.firstName} ${p.lastName}`, + }))} + defaultCurrency={defaultCurrency} + /> )} diff --git a/src/app/(dashboard)/patients/components/patient-form.tsx b/src/app/(dashboard)/patients/components/patient-form.tsx new file mode 100644 index 0000000..79ea77c --- /dev/null +++ b/src/app/(dashboard)/patients/components/patient-form.tsx @@ -0,0 +1,109 @@ +"use client"; + +import { useActionState, useEffect, useRef } from "react"; +import { Loader2, UserPlus } from "lucide-react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { createPatientAction } from "@/lib/appwrite/patient-actions"; +import { initialPatientFormState } from "@/lib/appwrite/patient-types"; + +export function PatientForm() { + const [state, action, pending] = useActionState( + createPatientAction, + initialPatientFormState, + ); + const formRef = useRef(null); + + useEffect(() => { + if (state.ok) { + toast.success("Hasta eklendi."); + formRef.current?.reset(); + } else if (state.error) { + toast.error(state.error); + } + }, [state]); + + return ( +
+
+ + + {state.fieldErrors?.patientCode && ( +

{state.fieldErrors.patientCode}

+ )} +
+ +
+
+ + + {state.fieldErrors?.firstName && ( +

{state.fieldErrors.firstName}

+ )} +
+
+ + + {state.fieldErrors?.lastName && ( +

{state.fieldErrors.lastName}

+ )} +
+
+ +
+ + +
+ +
+ + +
+ +
+ +