mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
- 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>
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import type { ToasterProps } from "sonner";
|
|
import { Toaster as SonnerToaster, useSonner } from "sonner";
|
|
import { cx } from "@/utils/cx";
|
|
|
|
export const DEFAULT_TOAST_POSITION = "bottom-right";
|
|
|
|
export const ToastsOverlay = () => {
|
|
const { toasts } = useSonner();
|
|
|
|
const styles = {
|
|
"top-right": {
|
|
className: "top-0 right-0",
|
|
background: "linear-gradient(215deg, rgba(0, 0, 0, 0.10) 0%, rgba(0, 0, 0, 0.00) 50%)",
|
|
},
|
|
"top-left": {
|
|
className: "top-0 left-0",
|
|
background: "linear-gradient(139deg, rgba(0, 0, 0, 0.10) 0%, rgba(0, 0, 0, 0.00) 40.64%)",
|
|
},
|
|
"bottom-right": {
|
|
className: "bottom-0 right-0",
|
|
background: "linear-gradient(148deg, rgba(0, 0, 0, 0.00) 58.58%, rgba(0, 0, 0, 0.10) 97.86%)",
|
|
},
|
|
"bottom-left": {
|
|
className: "bottom-0 left-0",
|
|
background: "linear-gradient(214deg, rgba(0, 0, 0, 0.00) 54.54%, rgba(0, 0, 0, 0.10) 95.71%)",
|
|
},
|
|
};
|
|
|
|
// Deduplicated list of positions
|
|
const positions = toasts.reduce<NonNullable<ToasterProps["position"]>[]>((acc, t) => {
|
|
acc.push(t.position || DEFAULT_TOAST_POSITION);
|
|
return acc;
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
{Object.entries(styles).map(([position, style]) => (
|
|
<div
|
|
key={position}
|
|
className={cx(
|
|
"pointer-events-none fixed z-40 hidden h-72.5 w-130 transition duration-500 xs:block",
|
|
style.className,
|
|
positions.includes(position as keyof typeof styles) ? "visible opacity-100" : "invisible opacity-0",
|
|
)}
|
|
style={{
|
|
background: style.background,
|
|
}}
|
|
/>
|
|
))}
|
|
<div
|
|
className={cx(
|
|
"pointer-events-none fixed right-0 bottom-0 left-0 z-40 h-67.5 w-full bg-linear-to-t from-black/10 to-transparent transition duration-500 xs:hidden",
|
|
positions.length > 0 ? "visible opacity-100" : "invisible opacity-0",
|
|
)}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export const Toaster = () => (
|
|
<>
|
|
<SonnerToaster
|
|
position={DEFAULT_TOAST_POSITION}
|
|
style={
|
|
{
|
|
"--width": "400px",
|
|
} as React.CSSProperties
|
|
}
|
|
/>
|
|
<ToastsOverlay />
|
|
</>
|
|
);
|