From fb92da113e4c702921d3206ef67d7f68e4568722 Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Fri, 10 Apr 2026 14:29:32 +0530 Subject: [PATCH] fix: setup wizard role guard, Doctor.clinic removal, dial sends agent config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Defect 1: Setup wizard (/setup) now guarded by AdminSetupGuard — CC agents and other non-admin roles are redirected to / instead of seeing the setup wizard they can't complete. Defect 3: Removed all references to Doctor.clinic (relation was replaced by DoctorVisitSlot entity). Updated queries.ts, appointments.tsx, transforms.ts, doctors.tsx, appointment-form.tsx. Defect 6 (frontend side): Dial request now sends agentId and campaignName from localStorage agent config so the sidecar dials with the correct per-agent credentials, not global defaults. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/call-desk/appointment-form.tsx | 2 +- src/lib/queries.ts | 2 +- src/lib/transforms.ts | 2 +- src/main.tsx | 16 ++++++++++++---- src/pages/appointments.tsx | 6 +++--- src/pages/doctors.tsx | 4 ++-- src/providers/sip-provider.tsx | 8 +++++++- 7 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/components/call-desk/appointment-form.tsx b/src/components/call-desk/appointment-form.tsx index e2cad43..5bfbfb6 100644 --- a/src/components/call-desk/appointment-form.tsx +++ b/src/components/call-desk/appointment-form.tsx @@ -144,7 +144,7 @@ export const AppointmentForm = ({ const clinicNames = Array.from( new Set( slotEdges - .map((se) => se.node.clinic?.clinicName) + .map((se) => se.node.clinic?.clinicName ?? se.node.clinicId) .filter((n): n is string => !!n), ), ); diff --git a/src/lib/queries.ts b/src/lib/queries.ts index d7242ca..86ed50e 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -71,7 +71,7 @@ export const APPOINTMENTS_QUERY = `{ appointments(first: 100, orderBy: [{ schedu scheduledAt durationMin appointmentType status doctorName department reasonForVisit patient { id fullName { firstName lastName } phones { primaryPhoneNumber } } - doctor { id clinic { clinicName } } + doctor { id } } } } }`; export const PATIENTS_QUERY = `{ patients(first: 50) { edges { node { diff --git a/src/lib/transforms.ts b/src/lib/transforms.ts index 150e413..6255c87 100644 --- a/src/lib/transforms.ts +++ b/src/lib/transforms.ts @@ -168,7 +168,7 @@ export function transformAppointments(data: any): Appointment[] { patientId: n.patient?.id ?? null, patientName: n.patient?.fullName ? `${n.patient.fullName.firstName} ${n.patient.fullName.lastName}`.trim() : null, patientPhone: n.patient?.phones?.primaryPhoneNumber ?? null, - clinicName: n.doctor?.clinic?.clinicName ?? null, + clinicName: n.department ?? null, })); } diff --git a/src/main.tsx b/src/main.tsx index 98a0ca9..76680e7 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,8 +1,15 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { BrowserRouter, Outlet, Route, Routes } from "react-router"; +import { BrowserRouter, Navigate, Outlet, Route, Routes } from "react-router"; import { AppShell } from "@/components/layout/app-shell"; import { AuthGuard } from "@/components/layout/auth-guard"; +import { useAuth } from "@/providers/auth-provider"; +import { SetupWizardPage } from "@/pages/setup-wizard"; + +const AdminSetupGuard = () => { + const { isAdmin } = useAuth(); + return isAdmin ? : ; +}; import { RoleRouter } from "@/components/layout/role-router"; import { NotFound } from "@/pages/not-found"; import { AllLeadsPage } from "@/pages/all-leads"; @@ -31,7 +38,6 @@ import { AccountSettingsPage } from "@/pages/account-settings"; import { RulesSettingsPage } from "@/pages/rules-settings"; import { BrandingSettingsPage } from "@/pages/branding-settings"; import { TeamSettingsPage } from "@/pages/team-settings"; -import { SetupWizardPage } from "@/pages/setup-wizard"; import { ClinicsPage } from "@/pages/clinics"; import { DoctorsPage } from "@/pages/doctors"; import { TelephonySettingsPage } from "@/pages/telephony-settings"; @@ -56,8 +62,10 @@ createRoot(document.getElementById("root")!).render( } /> }> - {/* Setup wizard — fullscreen, no AppShell */} - } /> + {/* Setup wizard — admin only, fullscreen, no AppShell. + CC agents and other non-admin roles are redirected to + the call desk — they can't complete setup anyway. */} + } /> formatDateOnly(iso); @@ -103,7 +103,7 @@ export const AppointmentsPage = () => { const phone = a.patient?.phones?.primaryPhoneNumber ?? ''; const doctor = (a.doctorName ?? '').toLowerCase(); const dept = (a.department ?? '').toLowerCase(); - const branch = (a.doctor?.clinic?.clinicName ?? '').toLowerCase(); + const branch = (a.department ?? '').toLowerCase(); return patientName.includes(q) || phone.includes(q) || doctor.includes(q) || dept.includes(q) || branch.includes(q); }); } @@ -177,7 +177,7 @@ export const AppointmentsPage = () => { ? `${appt.patient.fullName?.firstName ?? ''} ${appt.patient.fullName?.lastName ?? ''}`.trim() || 'Unknown' : 'Unknown'; const phone = appt.patient?.phones?.primaryPhoneNumber ?? ''; - const branch = appt.doctor?.clinic?.clinicName ?? '—'; + const branch = appt.department ?? '—'; const statusLabel = STATUS_LABELS[appt.status ?? ''] ?? appt.status ?? '—'; const statusColor = STATUS_COLORS[appt.status ?? ''] ?? 'gray'; diff --git a/src/pages/doctors.tsx b/src/pages/doctors.tsx index 22ee3c2..0149200 100644 --- a/src/pages/doctors.tsx +++ b/src/pages/doctors.tsx @@ -133,7 +133,7 @@ const toFormValues = (doctor: DoctorNode): DoctorFormValues => ({ doctor.doctorVisitSlots?.edges.map( (e): DoctorVisitSlotEntry => ({ id: e.node.id, - clinicId: e.node.clinicId ?? e.node.clinic?.id ?? '', + clinicId: e.node.clinicId ?? e.node.clinicId ?? '', dayOfWeek: e.node.dayOfWeek ?? '', startTime: e.node.startTime ?? null, endTime: e.node.endTime ?? null, @@ -156,7 +156,7 @@ const summariseVisitSlots = ( if (edges.length === 0) return 'No slots'; const byClinic = new Map(); for (const e of edges) { - const cid = e.node.clinicId ?? e.node.clinic?.id; + const cid = e.node.clinicId ?? e.node.clinicId; if (!cid || !e.node.dayOfWeek) continue; if (!byClinic.has(cid)) byClinic.set(cid, []); byClinic.get(cid)!.push(e.node.dayOfWeek); diff --git a/src/providers/sip-provider.tsx b/src/providers/sip-provider.tsx index d5c9ca3..8213265 100644 --- a/src/providers/sip-provider.tsx +++ b/src/providers/sip-provider.tsx @@ -157,7 +157,13 @@ export const useSip = () => { }, 30000); try { - const result = await apiClient.post<{ status: string; ucid?: string }>('/api/ozonetel/dial', { phoneNumber }); + // Send agent config so the sidecar dials with the correct agent ID + campaign + const agentConfig = JSON.parse(localStorage.getItem('helix_agent_config') ?? '{}'); + const result = await apiClient.post<{ status: string; ucid?: string }>('/api/ozonetel/dial', { + phoneNumber, + agentId: agentConfig.ozonetelAgentId, + campaignName: agentConfig.campaignName, + }); console.log('[DIAL] Dial API response:', result); clearTimeout(safetyTimeout); // Store UCID from dial response — SIP bridge doesn't carry X-UCID for outbound