Files
helix-engage/src/components/application/slideout-menus/slideout-menu.tsx
saridsa2 c3c3f4b3d7 feat: worklist sorting, contextual disposition, context panel redesign, notifications
- Worklist default sort descending (newest first), sortable column headers (PRIORITY, PATIENT, SLA) via React Aria
- Contextual disposition: auto-selects based on in-call actions (appointment → APPOINTMENT_BOOKED, enquiry → INFO_PROVIDED, transfer → FOLLOW_UP_SCHEDULED)
- Context panel redesign: collapsible AI Insight, Upcoming (appointments + follow-ups + linked patient), Recent (calls + activities) sections; auto-collapse on AI chat start
- Appointments added to DataProvider with APPOINTMENTS_QUERY, Appointment type, transform
- Notification bell for admin/supervisor: performance alerts (idle time, NPS, conversion thresholds) with toast on load + bell dropdown with dismiss; demo alerts as fallback
- Slideout z-index fix: added z-50 to slideout ModalOverlay matching modal component

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 14:45:52 +05:30

121 lines
4.5 KiB
TypeScript

import { type ComponentPropsWithRef, type ReactNode, type RefAttributes } from "react";
import type {
DialogProps as AriaDialogProps,
ModalOverlayProps as AriaModalOverlayProps,
ModalRenderProps as AriaModalRenderProps,
} from "react-aria-components";
import { Dialog as AriaDialog, DialogTrigger as AriaDialogTrigger, Modal as AriaModal, ModalOverlay as AriaModalOverlay } from "react-aria-components";
import { CloseButton } from "@/components/base/buttons/close-button";
import { cx } from "@/utils/cx";
interface ModalOverlayProps extends AriaModalOverlayProps, RefAttributes<HTMLDivElement> {}
export const ModalOverlay = (props: ModalOverlayProps) => {
return (
<AriaModalOverlay
{...props}
className={(state) =>
cx(
"fixed inset-0 z-50 flex min-h-dvh w-full items-center justify-end bg-overlay/70 pl-6 outline-hidden ease-linear md:pl-10",
state.isEntering && "duration-300 animate-in fade-in",
state.isExiting && "duration-500 animate-out fade-out",
typeof props.className === "function" ? props.className(state) : props.className,
)
}
/>
);
};
ModalOverlay.displayName = "ModalOverlay";
interface ModalProps extends AriaModalOverlayProps, RefAttributes<HTMLDivElement> {}
export const Modal = (props: ModalProps) => (
<AriaModal
{...props}
className={(state) =>
cx(
"inset-y-0 right-0 h-full w-full max-w-100 shadow-xl transition",
state.isEntering && "duration-300 animate-in slide-in-from-right",
state.isExiting && "duration-500 animate-out slide-out-to-right",
typeof props.className === "function" ? props.className(state) : props.className,
)
}
/>
);
Modal.displayName = "Modal";
interface DialogProps extends AriaDialogProps, RefAttributes<HTMLElement> {}
export const Dialog = (props: DialogProps) => (
<AriaDialog
role="dialog"
{...props}
className={cx(
"relative flex size-full flex-col items-start gap-6 overflow-y-auto bg-primary ring-1 ring-secondary_alt outline-hidden",
props.className,
)}
/>
);
Dialog.displayName = "Dialog";
interface SlideoutMenuProps extends Omit<AriaModalOverlayProps, "children">, RefAttributes<HTMLDivElement> {
children: ReactNode | ((children: AriaModalRenderProps & { close: () => void }) => ReactNode);
dialogClassName?: string;
}
const Menu = ({ children, dialogClassName, ...props }: SlideoutMenuProps) => {
return (
<ModalOverlay {...props}>
<Modal className={(state) => cx(typeof props.className === "function" ? props.className(state) : props.className)}>
{(state) => (
<Dialog className={dialogClassName}>
{({ close }) => {
return typeof children === "function" ? children({ ...state, close }) : children;
}}
</Dialog>
)}
</Modal>
</ModalOverlay>
);
};
Menu.displayName = "SlideoutMenu";
const Content = ({ role = "main", ...props }: ComponentPropsWithRef<"div">) => {
return <div role={role} {...props} className={cx("flex flex-1 min-h-0 flex-col gap-6 overflow-y-auto overscroll-auto px-4 md:px-6", props.className)} />;
};
Content.displayName = "SlideoutContent";
interface SlideoutHeaderProps extends ComponentPropsWithRef<"header"> {
onClose?: () => void;
}
const Header = ({ className, children, onClose, ...props }: SlideoutHeaderProps) => {
return (
<header {...props} className={cx("relative z-1 w-full px-4 pt-6 md:px-6", className)}>
{children}
<CloseButton size="md" className="absolute top-3 right-3 shrink-0" onClick={onClose} />
</header>
);
};
Header.displayName = "SlideoutHeader";
const Footer = (props: ComponentPropsWithRef<"footer">) => {
return <footer {...props} className={cx("w-full p-4 shadow-[inset_0px_1px_0px_0px] shadow-border-secondary md:px-6", props.className)} />;
};
Footer.displayName = "SlideoutFooter";
const SlideoutMenu = Menu as typeof Menu & {
Trigger: typeof AriaDialogTrigger;
Content: typeof Content;
Header: typeof Header;
Footer: typeof Footer;
};
SlideoutMenu.displayName = "SlideoutMenu";
SlideoutMenu.Trigger = AriaDialogTrigger;
SlideoutMenu.Content = Content;
SlideoutMenu.Header = Header;
SlideoutMenu.Footer = Footer;
export { SlideoutMenu };