fix: 5 bug fixes — #533 #531 #529 #527 #547

#533: Remove redundant Call History top header (duplicate TopBar)
#531: Block logout during active call (confirm dialog + UCID check)
#529: Block outbound calls when agent is on Break/Training
#527: Remove updatePatient during appointment creation (was mutating
      shared Patient entity, affecting all past appointments)
#547: SLA rules seeded via API (config issue, not code)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 19:26:55 +05:30
parent 951acf59c5
commit c4b6f9a438
4 changed files with 34 additions and 22 deletions

View File

@@ -283,23 +283,13 @@ export const AppointmentForm = ({
const trimmedName = patientName.trim();
const nameChanged = isNameEditable && trimmedName.length > 0 && trimmedName !== initialLeadName;
// Update patient name ONLY if the agent explicitly renamed.
// This guard is the fix for the long-standing bug where the
// form silently overwrote existing patients' names with
// whatever happened to be in the input.
if (nameChanged && patientId) {
await apiClient.graphql(
`mutation UpdatePatient($id: UUID!, $data: PatientUpdateInput!) {
updatePatient(id: $id, data: $data) { id }
}`,
{
id: patientId,
data: {
fullName: { firstName: trimmedName.split(' ')[0], lastName: trimmedName.split(' ').slice(1).join(' ') || '' },
},
},
).catch((err: unknown) => console.warn('Failed to update patient name:', err));
}
// DO NOT update the shared Patient entity when name changes
// during appointment creation. The Patient record is shared
// across all appointments — modifying it here would
// retroactively change the name on all past appointments.
// The patient name for THIS appointment is stored on the
// Appointment entity itself (via doctorName/department).
// Bug #527: removed updatePatient() call.
// Update lead status/lastContacted on every appointment book
// (those are genuinely about this appointment), but only

View File

@@ -16,7 +16,6 @@ import { Badge } from '@/components/base/badges/badges';
import { Button } from '@/components/base/buttons/button';
import { Input } from '@/components/base/input/input';
import { Select } from '@/components/base/select/select';
import { TopBar } from '@/components/layout/top-bar';
import { ClickToCallButton } from '@/components/call-desk/click-to-call-button';
import { formatShortDate, formatPhone } from '@/lib/format';
import { computeSlaStatus } from '@/lib/scoring';
@@ -174,8 +173,6 @@ export const CallHistoryPage = () => {
return (
<div className="flex flex-1 flex-col overflow-hidden">
<TopBar title="Call History" subtitle={`${filteredCalls.length} total calls`} />
<div className="flex flex-1 flex-col overflow-hidden p-7">
<TableCard.Root size="md" className="flex-1 min-h-0">
<TableCard.Header

View File

@@ -96,10 +96,19 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
}, []);
const logout = useCallback(async () => {
// Block logout during active call
const { isOutboundPending, disconnectSip } = await import('@/state/sip-manager');
const activeUcid = localStorage.getItem('helix_active_ucid');
if (isOutboundPending() || activeUcid) {
const confirmed = window.confirm(
'You have an active call. Logging out will disconnect the call. Are you sure?',
);
if (!confirmed) return;
}
// Disconnect SIP before logout
try {
const { disconnectSip } = await import('@/state/sip-manager');
disconnectSip();
disconnectSip(true);
} catch {}
// Notify sidecar to unlock Redis session + Ozonetel logout — await before clearing tokens

View File

@@ -156,6 +156,22 @@ export const useSip = () => {
// Ozonetel outbound dial — single path for all outbound calls
const dialOutbound = useCallback(async (phoneNumber: string): Promise<void> => {
// Block outbound calls when agent is on Break or Training
const agentCfg = localStorage.getItem('helix_agent_config');
if (agentCfg) {
const { useAgentState: _ } = await import('@/hooks/use-agent-state');
// Read state from the SSE endpoint directly (can't use hook here)
const agentId = JSON.parse(agentCfg).ozonetelAgentId;
try {
const stateRes = await fetch(`/api/supervisor/agent-state?agentId=${agentId}`);
const stateData = await stateRes.json();
if (stateData.state === 'break' || stateData.state === 'training') {
const { notify } = await import('@/lib/toast');
notify.info('Status: ' + stateData.state, 'Change status to Ready before placing calls');
return;
}
} catch {}
}
console.log(`[DIAL] Outbound dial started: phone=${phoneNumber}`);
setCallState('ringing-out');
setCallerNumber(phoneNumber);