feat: telephony overhaul + appointment availability + Force Ready

Telephony:
- Track UCID from SIP headers and ManualDial response
- Submit disposition to Ozonetel via Set Disposition API (ends ACW)
- Fix outboundPending flag lifecycle to prevent inbound poisoning
- Fix render order: post-call UI takes priority over active state
- Pre-select disposition when appointment booked during call

Appointment form:
- Convert from slideout to inline collapsible below call card
- Fetch real doctors from platform, filter by department
- Show time slot availability grid (booked slots greyed + strikethrough)
- Double-check availability before booking
- Support edit and cancel existing appointments

UI:
- Add Force Ready button to profile menu (logout+login to clear ACW)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 20:24:58 +05:30
parent f13decefc4
commit 99bca1e008
7 changed files with 1328 additions and 284 deletions

View File

@@ -1,7 +1,7 @@
import type { FC, HTMLAttributes } from "react";
import { useCallback, useEffect, useRef } from "react";
import type { Placement } from "@react-types/overlays";
import { ChevronSelectorVertical, LogOut01, Settings01, User01 } from "@untitledui/icons";
import { ChevronSelectorVertical, LogOut01, PhoneCall01, Settings01, User01 } from "@untitledui/icons";
import { useFocusManager } from "react-aria";
import type { DialogProps as AriaDialogProps } from "react-aria-components";
import { Button as AriaButton, Dialog as AriaDialog, DialogTrigger as AriaDialogTrigger, Popover as AriaPopover } from "react-aria-components";
@@ -26,8 +26,9 @@ type NavAccountType = {
export const NavAccountMenu = ({
className,
onSignOut,
onForceReady,
...dialogProps
}: AriaDialogProps & { className?: string; accounts?: NavAccountType[]; selectedAccountId?: string; onSignOut?: () => void }) => {
}: AriaDialogProps & { className?: string; accounts?: NavAccountType[]; selectedAccountId?: string; onSignOut?: () => void; onForceReady?: () => void }) => {
const focusManager = useFocusManager();
const dialogRef = useRef<HTMLDivElement>(null);
@@ -68,6 +69,7 @@ export const NavAccountMenu = ({
<div className="flex flex-col gap-0.5 py-1.5">
<NavAccountCardMenuItem label="View profile" icon={User01} shortcut="⌘K->P" />
<NavAccountCardMenuItem label="Account settings" icon={Settings01} shortcut="⌘S" />
<NavAccountCardMenuItem label="Force Ready" icon={PhoneCall01} onClick={onForceReady} />
</div>
</div>
@@ -114,11 +116,13 @@ export const NavAccountCard = ({
selectedAccountId,
items = [],
onSignOut,
onForceReady,
}: {
popoverPlacement?: Placement;
selectedAccountId?: string;
items?: NavAccountType[];
onSignOut?: () => void;
onForceReady?: () => void;
}) => {
const triggerRef = useRef<HTMLDivElement>(null);
const isDesktop = useBreakpoint("lg");
@@ -159,7 +163,7 @@ export const NavAccountCard = ({
)
}
>
<NavAccountMenu selectedAccountId={selectedAccountId} accounts={items} onSignOut={onSignOut} />
<NavAccountMenu selectedAccountId={selectedAccountId} accounts={items} onSignOut={onSignOut} onForceReady={onForceReady} />
</AriaPopover>
</AriaDialogTrigger>
</div>