mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
fix: UUID type mismatch, slot conflict, appt/enquiry tabs, dialler in header
- Changed $id: ID! to $id: UUID! in all update mutations (4 files) - Removed redundant slot availability check (UI already disables booked slots) - Book Appt and Enquiry act as toggle tabs — one closes the other - Dialler moved from FAB to header dropdown next to status toggle Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -272,12 +272,12 @@ export const ActiveCallCard = ({ lead, callerPhone, missedCallId, onCallComplete
|
|||||||
<div className="w-px h-6 bg-secondary mx-0.5" />
|
<div className="w-px h-6 bg-secondary mx-0.5" />
|
||||||
|
|
||||||
{/* Text+Icon primary actions */}
|
{/* Text+Icon primary actions */}
|
||||||
<Button size="sm" color="secondary"
|
<Button size="sm" color={appointmentOpen ? 'primary' : 'secondary'}
|
||||||
iconLeading={({ className, ...rest }: any) => <FontAwesomeIcon icon={faCalendarPlus} className={className} {...rest} />}
|
iconLeading={({ className, ...rest }: any) => <FontAwesomeIcon icon={faCalendarPlus} className={className} {...rest} />}
|
||||||
onClick={() => setAppointmentOpen(true)}>Book Appt</Button>
|
onClick={() => { setAppointmentOpen(!appointmentOpen); setEnquiryOpen(false); }}>Book Appt</Button>
|
||||||
<Button size="sm" color="secondary"
|
<Button size="sm" color={enquiryOpen ? 'primary' : 'secondary'}
|
||||||
iconLeading={({ className, ...rest }: any) => <FontAwesomeIcon icon={faClipboardQuestion} className={className} {...rest} />}
|
iconLeading={({ className, ...rest }: any) => <FontAwesomeIcon icon={faClipboardQuestion} className={className} {...rest} />}
|
||||||
onClick={() => setEnquiryOpen(!enquiryOpen)}>Enquiry</Button>
|
onClick={() => { setEnquiryOpen(!enquiryOpen); setAppointmentOpen(false); }}>Enquiry</Button>
|
||||||
<Button size="sm" color="secondary"
|
<Button size="sm" color="secondary"
|
||||||
iconLeading={({ className, ...rest }: any) => <FontAwesomeIcon icon={faPhoneArrowRight} className={className} {...rest} />}
|
iconLeading={({ className, ...rest }: any) => <FontAwesomeIcon icon={faPhoneArrowRight} className={className} {...rest} />}
|
||||||
onClick={() => setTransferOpen(!transferOpen)}>Transfer</Button>
|
onClick={() => setTransferOpen(!transferOpen)}>Transfer</Button>
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export const AppointmentForm = ({
|
|||||||
if (isEditMode && existingAppointment) {
|
if (isEditMode && existingAppointment) {
|
||||||
// Update existing appointment
|
// Update existing appointment
|
||||||
await apiClient.graphql(
|
await apiClient.graphql(
|
||||||
`mutation UpdateAppointment($id: ID!, $data: AppointmentUpdateInput!) {
|
`mutation UpdateAppointment($id: UUID!, $data: AppointmentUpdateInput!) {
|
||||||
updateAppointment(id: $id, data: $data) { id }
|
updateAppointment(id: $id, data: $data) { id }
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
@@ -224,22 +224,6 @@ export const AppointmentForm = ({
|
|||||||
);
|
);
|
||||||
notify.success('Appointment Updated');
|
notify.success('Appointment Updated');
|
||||||
} else {
|
} else {
|
||||||
// Double-check slot availability before booking
|
|
||||||
const checkResult = await apiClient.graphql<{ appointments: { edges: Array<{ node: { status: string } }> } }>(
|
|
||||||
`{ appointments(filter: {
|
|
||||||
doctorId: { eq: "${doctor}" },
|
|
||||||
scheduledAt: { gte: "${date}T${timeSlot}:00", lte: "${date}T${timeSlot}:00" }
|
|
||||||
}) { edges { node { status } } } }`,
|
|
||||||
);
|
|
||||||
const activeBookings = checkResult.appointments.edges.filter(e =>
|
|
||||||
e.node.status !== 'CANCELLED' && e.node.status !== 'NO_SHOW',
|
|
||||||
);
|
|
||||||
if (activeBookings.length > 0) {
|
|
||||||
setError('This slot was just booked by someone else. Please select a different time.');
|
|
||||||
setIsSaving(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create appointment
|
// Create appointment
|
||||||
await apiClient.graphql(
|
await apiClient.graphql(
|
||||||
`mutation CreateAppointment($data: AppointmentCreateInput!) {
|
`mutation CreateAppointment($data: AppointmentCreateInput!) {
|
||||||
@@ -263,7 +247,7 @@ export const AppointmentForm = ({
|
|||||||
// Update lead status if we have a matched lead
|
// Update lead status if we have a matched lead
|
||||||
if (leadId) {
|
if (leadId) {
|
||||||
await apiClient.graphql(
|
await apiClient.graphql(
|
||||||
`mutation UpdateLead($id: ID!, $data: LeadUpdateInput!) {
|
`mutation UpdateLead($id: UUID!, $data: LeadUpdateInput!) {
|
||||||
updateLead(id: $id, data: $data) { id }
|
updateLead(id: $id, data: $data) { id }
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
@@ -291,7 +275,7 @@ export const AppointmentForm = ({
|
|||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
try {
|
try {
|
||||||
await apiClient.graphql(
|
await apiClient.graphql(
|
||||||
`mutation CancelAppointment($id: ID!, $data: AppointmentUpdateInput!) {
|
`mutation CancelAppointment($id: UUID!, $data: AppointmentUpdateInput!) {
|
||||||
updateAppointment(id: $id, data: $data) { id }
|
updateAppointment(id: $id, data: $data) { id }
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ export const CallWidget = () => {
|
|||||||
const newStatus = statusMap[disposition];
|
const newStatus = statusMap[disposition];
|
||||||
if (newStatus) {
|
if (newStatus) {
|
||||||
await apiClient.graphql(
|
await apiClient.graphql(
|
||||||
`mutation UpdateLead($id: ID!, $data: LeadUpdateInput!) {
|
`mutation UpdateLead($id: UUID!, $data: LeadUpdateInput!) {
|
||||||
updateLead(id: $id, data: $data) { id }
|
updateLead(id: $id, data: $data) { id }
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const CampaignEditSlideout = ({ isOpen, onOpenChange, campaign, onSaved }
|
|||||||
const budgetMicros = budget ? Number(budget) * 1_000_000 : null;
|
const budgetMicros = budget ? Number(budget) * 1_000_000 : null;
|
||||||
|
|
||||||
await apiClient.graphql(
|
await apiClient.graphql(
|
||||||
`mutation UpdateCampaign($id: ID!, $data: CampaignUpdateInput!) {
|
`mutation UpdateCampaign($id: UUID!, $data: CampaignUpdateInput!) {
|
||||||
updateCampaign(id: $id, data: $data) { id }
|
updateCampaign(id: $id, data: $data) { id }
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -72,6 +72,61 @@ export const CallDeskPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
{!isInCall && (
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
onClick={() => setDiallerOpen(!diallerOpen)}
|
||||||
|
className={cx(
|
||||||
|
'flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition duration-100 ease-linear',
|
||||||
|
diallerOpen
|
||||||
|
? 'bg-brand-solid text-white'
|
||||||
|
: 'bg-secondary text-secondary hover:bg-secondary_hover',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPhone} className="size-3" />
|
||||||
|
Dial
|
||||||
|
</button>
|
||||||
|
{diallerOpen && (
|
||||||
|
<div className="absolute top-full right-0 mt-2 w-72 rounded-xl bg-primary shadow-xl ring-1 ring-secondary p-4 z-50">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<span className="text-sm font-semibold text-primary">Dial</span>
|
||||||
|
<button onClick={() => setDiallerOpen(false)} className="text-fg-quaternary hover:text-fg-secondary">
|
||||||
|
<FontAwesomeIcon icon={faXmark} className="size-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 mb-3 px-3 py-2.5 rounded-lg bg-secondary min-h-[40px]">
|
||||||
|
<span className="flex-1 text-lg font-semibold text-primary tracking-wider text-center">
|
||||||
|
{dialNumber || <span className="text-placeholder font-normal text-sm">Enter number</span>}
|
||||||
|
</span>
|
||||||
|
{dialNumber && (
|
||||||
|
<button onClick={() => setDialNumber(dialNumber.slice(0, -1))} className="text-fg-quaternary hover:text-fg-secondary shrink-0">
|
||||||
|
<FontAwesomeIcon icon={faDeleteLeft} className="size-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-3 gap-1.5 mb-3">
|
||||||
|
{['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'].map(key => (
|
||||||
|
<button
|
||||||
|
key={key}
|
||||||
|
onClick={() => setDialNumber(prev => prev + key)}
|
||||||
|
className="flex items-center justify-center h-11 rounded-lg text-sm font-semibold text-primary bg-primary hover:bg-secondary border border-secondary transition duration-100 ease-linear active:scale-95"
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleDial}
|
||||||
|
disabled={dialling || dialNumber.replace(/[^0-9]/g, '').length < 10}
|
||||||
|
className="w-full flex items-center justify-center gap-2 rounded-lg bg-success-solid py-2.5 text-sm font-medium text-white hover:opacity-90 disabled:bg-disabled disabled:cursor-not-allowed transition duration-100 ease-linear"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPhone} className="size-3.5" />
|
||||||
|
{dialling ? 'Dialling...' : 'Call'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<AgentStatusToggle isRegistered={isRegistered} connectionStatus={connectionStatus} />
|
<AgentStatusToggle isRegistered={isRegistered} connectionStatus={connectionStatus} />
|
||||||
{totalPending > 0 && (
|
{totalPending > 0 && (
|
||||||
<Badge size="sm" color="brand" type="pill-color">{totalPending} pending</Badge>
|
<Badge size="sm" color="brand" type="pill-color">{totalPending} pending</Badge>
|
||||||
@@ -127,68 +182,6 @@ export const CallDeskPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Dialler FAB */}
|
|
||||||
{!isInCall && (
|
|
||||||
<div className="fixed bottom-6 right-6 z-40 flex flex-col items-end gap-3">
|
|
||||||
{diallerOpen && (
|
|
||||||
<div className="w-72 rounded-xl bg-primary shadow-xl ring-1 ring-secondary p-4">
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
|
||||||
<span className="text-sm font-semibold text-primary">Dial</span>
|
|
||||||
<button onClick={() => setDiallerOpen(false)} className="text-fg-quaternary hover:text-fg-secondary">
|
|
||||||
<FontAwesomeIcon icon={faXmark} className="size-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Number display */}
|
|
||||||
<div className="flex items-center gap-2 mb-3 px-3 py-2.5 rounded-lg bg-secondary min-h-[40px]">
|
|
||||||
<span className="flex-1 text-lg font-semibold text-primary tracking-wider text-center">
|
|
||||||
{dialNumber || <span className="text-placeholder font-normal text-sm">Enter number</span>}
|
|
||||||
</span>
|
|
||||||
{dialNumber && (
|
|
||||||
<button onClick={() => setDialNumber(dialNumber.slice(0, -1))} className="text-fg-quaternary hover:text-fg-secondary shrink-0">
|
|
||||||
<FontAwesomeIcon icon={faDeleteLeft} className="size-4" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Numpad */}
|
|
||||||
<div className="grid grid-cols-3 gap-1.5 mb-3">
|
|
||||||
{['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'].map(key => (
|
|
||||||
<button
|
|
||||||
key={key}
|
|
||||||
onClick={() => setDialNumber(prev => prev + key)}
|
|
||||||
className="flex items-center justify-center h-11 rounded-lg text-sm font-semibold text-primary bg-primary hover:bg-secondary border border-secondary transition duration-100 ease-linear active:scale-95"
|
|
||||||
>
|
|
||||||
{key}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Call button */}
|
|
||||||
<button
|
|
||||||
onClick={handleDial}
|
|
||||||
disabled={dialling || dialNumber.replace(/[^0-9]/g, '').length < 10}
|
|
||||||
className="w-full flex items-center justify-center gap-2 rounded-lg bg-success-solid py-2.5 text-sm font-medium text-white hover:opacity-90 disabled:bg-disabled disabled:cursor-not-allowed transition duration-100 ease-linear"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faPhone} className="size-3.5" />
|
|
||||||
{dialling ? 'Dialling...' : 'Call'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={() => setDiallerOpen(!diallerOpen)}
|
|
||||||
className={cx(
|
|
||||||
'flex size-14 items-center justify-center rounded-full shadow-lg transition duration-100 ease-linear',
|
|
||||||
diallerOpen
|
|
||||||
? 'bg-secondary text-fg-secondary'
|
|
||||||
: 'bg-brand-solid text-white hover:bg-brand-solid_hover',
|
|
||||||
)}
|
|
||||||
title="Quick dial"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={diallerOpen ? faXmark : faPhone} className="size-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user