mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: call desk redesign — 2-panel layout, collapsible sidebar, inline AI, ringtone
- Collapsible sidebar with Jotai atom (icon-only mode, persisted to localStorage) - 2-panel call desk: worklist (60%) + context panel (40%) with AI + Lead 360 tabs - Inline AI call prep card — known lead summary or unknown caller script - Active call card with compact Answer/Decline buttons - Worklist panel with human-readable labels, priority badges, click-to-select - Context panel auto-switches to Lead 360 when lead selected or call incoming - Browser ringtone via Web Audio API on incoming calls - Sonner + Untitled UI IconNotification for toast system - apiClient pattern: centralized post/get/graphql with auto-toast on errors - Remove duplicate avatar from top bar, hide floating widget on call desk - Fix Link routing in collapsed sidebar (was using <a> causing full page reload) - Fix GraphQL field names: adStatus→status, platformUrl needs subfield selection - Silent mode for DataProvider queries to prevent toast spam Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -75,53 +75,43 @@ export const useWorklist = (): UseWorklistResult => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchWorklist = useCallback(async () => {
|
||||
if (!apiClient.isAuthenticated()) {
|
||||
setError('Not authenticated');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = apiClient.getStoredToken();
|
||||
if (!token) {
|
||||
setError('Not authenticated');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const json = await apiClient.get<any>('/api/worklist', { silent: true });
|
||||
|
||||
const apiUrl = import.meta.env.VITE_API_URL ?? 'http://localhost:4100';
|
||||
const response = await fetch(`${apiUrl}/api/worklist`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const json = await response.json();
|
||||
// Transform platform field shapes to frontend types
|
||||
const transformed: WorklistData = {
|
||||
...json,
|
||||
marketingLeads: (json.marketingLeads ?? []).map((lead: any) => ({
|
||||
...lead,
|
||||
leadSource: lead.source ?? lead.leadSource,
|
||||
leadStatus: lead.status ?? lead.leadStatus,
|
||||
contactPhone: lead.contactPhone?.primaryPhoneNumber
|
||||
? [{ number: lead.contactPhone.primaryPhoneNumber, callingCode: lead.contactPhone.primaryPhoneCallingCode ?? '+91' }]
|
||||
: lead.contactPhone,
|
||||
contactEmail: lead.contactEmail?.primaryEmail
|
||||
? [{ address: lead.contactEmail.primaryEmail }]
|
||||
: lead.contactEmail,
|
||||
})),
|
||||
missedCalls: (json.missedCalls ?? []).map((call: any) => ({
|
||||
...call,
|
||||
callDirection: call.direction ?? call.callDirection,
|
||||
durationSeconds: call.durationSec ?? call.durationSeconds ?? 0,
|
||||
})),
|
||||
followUps: (json.followUps ?? []).map((fu: any) => ({
|
||||
...fu,
|
||||
followUpType: fu.typeCustom ?? fu.followUpType,
|
||||
followUpStatus: fu.status ?? fu.followUpStatus,
|
||||
})),
|
||||
};
|
||||
setData(transformed);
|
||||
setError(null);
|
||||
} else {
|
||||
setError(`Worklist API returned ${response.status}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Worklist fetch failed:', err);
|
||||
// Transform platform field shapes to frontend types
|
||||
const transformed: WorklistData = {
|
||||
...json,
|
||||
marketingLeads: (json.marketingLeads ?? []).map((lead: any) => ({
|
||||
...lead,
|
||||
leadSource: lead.source ?? lead.leadSource,
|
||||
leadStatus: lead.status ?? lead.leadStatus,
|
||||
contactPhone: lead.contactPhone?.primaryPhoneNumber
|
||||
? [{ number: lead.contactPhone.primaryPhoneNumber, callingCode: lead.contactPhone.primaryPhoneCallingCode ?? '+91' }]
|
||||
: lead.contactPhone,
|
||||
contactEmail: lead.contactEmail?.primaryEmail
|
||||
? [{ address: lead.contactEmail.primaryEmail }]
|
||||
: lead.contactEmail,
|
||||
})),
|
||||
missedCalls: (json.missedCalls ?? []).map((call: any) => ({
|
||||
...call,
|
||||
callDirection: call.direction ?? call.callDirection,
|
||||
durationSeconds: call.durationSec ?? call.durationSeconds ?? 0,
|
||||
})),
|
||||
followUps: (json.followUps ?? []).map((fu: any) => ({
|
||||
...fu,
|
||||
followUpType: fu.typeCustom ?? fu.followUpType,
|
||||
followUpStatus: fu.status ?? fu.followUpStatus,
|
||||
})),
|
||||
};
|
||||
setData(transformed);
|
||||
setError(null);
|
||||
} catch {
|
||||
setError('Sidecar not reachable');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user