{"version":3,"sources":["../call-control-sdk/lib/pages/predictivePacing/index.tsx","../call-control-sdk/lib/pages/campaigns/LivePacingCard.tsx","../call-control-sdk/lib/pages/campaigns/useLivePacing.ts","../call-control-sdk/lib/pages/campaigns/pacingTypes.ts","../call-control-sdk/lib/pages/campaigns/PacingAlerts.tsx","../call-control-sdk/lib/pages/campaigns/usePacingEvents.ts","../call-control-sdk/lib/pages/campaigns/PacingHistoryCharts.tsx","../call-control-sdk/lib/pages/campaigns/usePacingHistory.ts","../call-control-sdk/lib/pages/abandonRateReport/index.tsx","../call-control-sdk/lib/pages/abandonRateReport/RowDetailDrawer.tsx","../call-control-sdk/lib/pages/abandonRateReport/useAbandonRateReport.ts","../call-control-sdk/lib/pages/abandonRateReport/types.ts","../call-control-sdk/lib/pages/predictivePacing/usePacingCampaigns.ts"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useState } from \"react\";\r\nimport {\r\n  Alert,\r\n  Box,\r\n  Chip,\r\n  CircularProgress,\r\n  FormControl,\r\n  InputLabel,\r\n  MenuItem,\r\n  Select,\r\n  Tab,\r\n  Tabs,\r\n  Typography,\r\n} from \"@mui/material\";\r\n\r\nimport PageHeader from \"../common/components/PageHeader\";\r\nimport { SDKProvider } from \"../../components/SDKProvider\";\r\nimport { useSDKPermissions } from \"../../hooks/useSDKPermissions\";\r\nimport { SDK_PERMISSIONS } from \"../../permissions\";\r\nimport { useGetAuthorizationToken } from \"../../hooks/useGetAuthorizationToken\";\r\n\r\nimport LivePacingCard from \"../campaigns/LivePacingCard\";\r\nimport PacingAlerts from \"../campaigns/PacingAlerts\";\r\nimport PacingHistoryCharts from \"../campaigns/PacingHistoryCharts\";\r\nimport { usePacingAlertToasts } from \"../campaigns/usePacingAlertToasts\";\r\nimport { AbandonRateReportContent } from \"../abandonRateReport\";\r\nimport { usePacingCampaigns, type PacingCampaign } from \"./usePacingCampaigns\";\r\n\r\nconst FONT =\r\n  \"'Inter', 'Segoe UI', 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif\";\r\n\r\nconst COLORS = {\r\n  teal: \"#1A5F6C\",\r\n  green: \"#0a9a62\",\r\n  grey: \"#7a8599\",\r\n} as const;\r\n\r\nconst tabsSx = {\r\n  minHeight: 36,\r\n  px: 1,\r\n  // The page header already carries 14px of its own below the subtitle.\r\n  mt: -1,\r\n  borderBottom: \"1px solid #eef1f6\",\r\n  \"& .MuiTab-root\": {\r\n    textTransform: \"none\",\r\n    fontWeight: 600,\r\n    fontSize: \"0.78rem\",\r\n    fontFamily: FONT,\r\n    minHeight: 36,\r\n    py: 0,\r\n    px: 1.5,\r\n    color: \"#4a5568\",\r\n  },\r\n  \"& .Mui-selected\": { color: `${COLORS.teal} !important` },\r\n  \"& .MuiTabs-indicator\": {\r\n    backgroundColor: COLORS.teal,\r\n    height: 2,\r\n    borderRadius: \"2px 2px 0 0\",\r\n  },\r\n};\r\n\r\ntype TabKey = \"live\" | \"compliance\" | \"queues\";\r\n\r\n/** Remembers the campaign a supervisor was last watching, across visits. */\r\nconst LAST_CAMPAIGN_KEY = \"ahs-cti.predictivePacing.campaignId\";\r\n\r\nfunction readStoredCampaign(): number | null {\r\n  try {\r\n    const raw = window.localStorage.getItem(LAST_CAMPAIGN_KEY);\r\n    const id = Number(raw);\r\n    return Number.isFinite(id) && id > 0 ? id : null;\r\n  } catch {\r\n    return null;\r\n  }\r\n}\r\n\r\nfunction storeCampaign(id: number | null): void {\r\n  try {\r\n    if (id == null) window.localStorage.removeItem(LAST_CAMPAIGN_KEY);\r\n    else window.localStorage.setItem(LAST_CAMPAIGN_KEY, String(id));\r\n  } catch {\r\n    /* Private browsing, or storage full — the page works without it. */\r\n  }\r\n}\r\n\r\n/* ═══════════════════════ DEEP LINK ═══════════════════════\r\n * ?tab=live|compliance|queues&campaignId=54, so a link opens the same view. Read and\r\n * written through the address bar directly: this page lives in the SDK, which\r\n * has no router of its own, and replaceState changes nothing the host router\r\n * routes on.\r\n */\r\n\r\nfunction readQuery(): { tab: TabKey | null; campaignId: number | null } {\r\n  const q = new URLSearchParams(window.location.search);\r\n  const tab = q.get(\"tab\");\r\n  const id = Number(q.get(\"campaignId\"));\r\n  return {\r\n    tab: tab === \"live\" || tab === \"compliance\" || tab === \"queues\" ? tab : null,\r\n    campaignId: Number.isFinite(id) && id > 0 ? id : null,\r\n  };\r\n}\r\n\r\nfunction writeQuery(tab: TabKey, campaignId: number | null): void {\r\n  const q = new URLSearchParams(window.location.search);\r\n  q.set(\"tab\", tab);\r\n  if (campaignId != null) q.set(\"campaignId\", String(campaignId));\r\n  else q.delete(\"campaignId\");\r\n  window.history.replaceState(\r\n    null,\r\n    \"\",\r\n    `${window.location.pathname}?${q.toString()}`,\r\n  );\r\n}\r\n\r\n/* ═══════════════════════ LIVE TAB ═══════════════════════ */\r\n\r\ninterface LiveTabProps {\r\n  campaigns: PacingCampaign[];\r\n  loading: boolean;\r\n  error: string;\r\n  selected: PacingCampaign | null;\r\n  onSelect: (id: number) => void;\r\n}\r\n\r\nfunction LiveTab({\r\n  campaigns,\r\n  loading,\r\n  error,\r\n  selected,\r\n  onSelect,\r\n}: LiveTabProps) {\r\n  if (error) {\r\n    return (\r\n      <Alert\r\n        severity=\"error\"\r\n        sx={{ m: 1, fontFamily: FONT, fontSize: \"0.78rem\" }}>\r\n        {error}\r\n      </Alert>\r\n    );\r\n  }\r\n\r\n  if (loading && campaigns.length === 0) {\r\n    return (\r\n      <Box sx={{ display: \"flex\", justifyContent: \"center\", py: 6 }}>\r\n        <CircularProgress size={28} sx={{ color: COLORS.teal }} />\r\n      </Box>\r\n    );\r\n  }\r\n\r\n  if (campaigns.length === 0) {\r\n    return (\r\n      <Box sx={{ py: 6, textAlign: \"center\" }}>\r\n        <Typography\r\n          sx={{\r\n            fontFamily: FONT,\r\n            fontSize: \"0.85rem\",\r\n            fontWeight: 600,\r\n            color: \"#4a5568\",\r\n          }}>\r\n          No predictive campaigns yet.\r\n        </Typography>\r\n        <Typography\r\n          sx={{\r\n            fontFamily: FONT,\r\n            fontSize: \"0.76rem\",\r\n            color: COLORS.grey,\r\n            mt: 0.5,\r\n          }}>\r\n          Pacing applies to predictive campaigns run by human agents.\r\n        </Typography>\r\n      </Box>\r\n    );\r\n  }\r\n\r\n  return (\r\n    <Box>\r\n      <Box\r\n        sx={{\r\n          display: \"flex\",\r\n          alignItems: \"center\",\r\n          gap: 1.5,\r\n          flexWrap: \"wrap\",\r\n          px: 1,\r\n          mb: 2,\r\n        }}>\r\n        <FormControl size=\"small\" sx={{ minWidth: 260 }}>\r\n          <InputLabel sx={{ fontSize: \"0.76rem\" }}>Campaign</InputLabel>\r\n          <Select\r\n            label=\"Campaign\"\r\n            value={selected ? String(selected.id) : \"\"}\r\n            onChange={(e) => onSelect(Number(e.target.value))}\r\n            sx={{ fontSize: \"0.8rem\", height: 36, borderRadius: \"6px\" }}\r\n            MenuProps={{ PaperProps: { sx: { maxHeight: 320 } } }}>\r\n            {campaigns.map((c) => (\r\n              <MenuItem\r\n                key={c.id}\r\n                value={String(c.id)}\r\n                sx={{ fontSize: \"0.8rem\", gap: 1 }}>\r\n                <Box\r\n                  component=\"span\"\r\n                  sx={{\r\n                    flex: 1,\r\n                    minWidth: 0,\r\n                    overflow: \"hidden\",\r\n                    textOverflow: \"ellipsis\",\r\n                  }}>\r\n                  {c.name}\r\n                </Box>\r\n                <Chip\r\n                  size=\"small\"\r\n                  label={c.adaptivePacingEnabled ? \"Adaptive\" : \"Shadow\"}\r\n                  sx={{\r\n                    height: 18,\r\n                    fontSize: \"0.62rem\",\r\n                    fontWeight: 700,\r\n                    color: c.adaptivePacingEnabled ? COLORS.green : COLORS.grey,\r\n                    backgroundColor: c.adaptivePacingEnabled\r\n                      ? \"#e3f3e8\"\r\n                      : \"#eceeed\",\r\n                  }}\r\n                />\r\n              </MenuItem>\r\n            ))}\r\n          </Select>\r\n        </FormControl>\r\n\r\n        {selected && selected.status !== \"ACTIVE\" && (\r\n          <Chip\r\n            size=\"small\"\r\n            label={selected.status === \"PAUSED\" ? \"Paused\" : selected.status}\r\n            sx={{\r\n              height: 20,\r\n              fontSize: \"0.66rem\",\r\n              fontWeight: 700,\r\n              color: COLORS.grey,\r\n              backgroundColor: \"#eceeed\",\r\n            }}\r\n          />\r\n        )}\r\n      </Box>\r\n\r\n      {selected && (\r\n        // The panels carry no outer margin of their own — the gap is set here.\r\n        <Box\r\n          sx={{\r\n            display: \"flex\",\r\n            flexDirection: \"column\",\r\n            gap: 1.5,\r\n            px: 1,\r\n            pb: 2,\r\n          }}>\r\n          <LivePacingCard\r\n            campaignId={selected.id}\r\n            pacingWindowMinutes={selected.pacingWindowMinutes}\r\n          />\r\n          <PacingAlerts campaignId={selected.id} />\r\n          <PacingHistoryCharts campaignId={selected.id} />\r\n        </Box>\r\n      )}\r\n    </Box>\r\n  );\r\n}\r\n\r\n/* ═══════════════════════ PAGE ═══════════════════════ */\r\n\r\nfunction PredictivePacingContent() {\r\n  // SDK initialization finishes after this route can mount — wait for the token so\r\n  // the first request goes out authenticated.\r\n  const authorizationToken = useGetAuthorizationToken();\r\n  const { hasPermission } = useSDKPermissions();\r\n  const canReadPacing = hasPermission(SDK_PERMISSIONS.PREDICTIVE_PACING_READ);\r\n  // The daily report has a code of its own. A login without it still gets the\r\n  // Live tab rather than an empty page.\r\n  const canReadReports = hasPermission(\r\n    SDK_PERMISSIONS.REPORTS_PREDICTIVE_PACING_READ,\r\n  );\r\n\r\n  // A breach is announced wherever a supervisor is — here and on the campaigns page.\r\n  usePacingAlertToasts(canReadPacing);\r\n\r\n  const [tab, setTab] = useState<TabKey>(() => readQuery().tab ?? \"live\");\r\n  const [campaignId, setCampaignId] = useState<number | null>(\r\n    () => readQuery().campaignId ?? readStoredCampaign(),\r\n  );\r\n\r\n  const { campaigns, loading, error } =\r\n    usePacingCampaigns(!!authorizationToken);\r\n\r\n  const visibleTabs = useMemo(\r\n    () => [\r\n      { key: \"live\" as TabKey, label: \"Live\" },\r\n      ...(canReadReports\r\n        ? [\r\n            { key: \"compliance\" as TabKey, label: \"Daily compliance\" },\r\n            { key: \"queues\" as TabKey, label: \"Queue KPIs\" },\r\n          ]\r\n        : []),\r\n    ],\r\n    [canReadReports],\r\n  );\r\n\r\n  // Permissions can arrive after mount, so a deep link to a report tab is\r\n  // only turned away once it is known the login cannot read it.\r\n  useEffect(() => {\r\n    if (tab !== \"live\" && !canReadReports) setTab(\"live\");\r\n  }, [tab, canReadReports]);\r\n\r\n  /*\r\n   * Fall back to the first campaign — running ones sort first — whenever the\r\n   * remembered or linked one is not in this tenant's list.\r\n   */\r\n  useEffect(() => {\r\n    if (campaigns.length === 0) return;\r\n    if (campaignId != null && campaigns.some((c) => c.id === campaignId))\r\n      return;\r\n    setCampaignId(campaigns[0].id);\r\n  }, [campaigns, campaignId]);\r\n\r\n  const selected = useMemo(\r\n    () => campaigns.find((c) => c.id === campaignId) ?? null,\r\n    [campaigns, campaignId],\r\n  );\r\n\r\n  // Keep the address bar and the remembered choice in step with what is shown.\r\n  useEffect(() => {\r\n    writeQuery(tab, selected?.id ?? null);\r\n    if (selected) storeCampaign(selected.id);\r\n  }, [tab, selected]);\r\n\r\n  const handleSelect = useCallback((id: number) => setCampaignId(id), []);\r\n\r\n  return (\r\n    <Box\r\n      sx={{\r\n        height: \"100%\",\r\n        display: \"flex\",\r\n        flexDirection: \"column\",\r\n        fontFamily: FONT,\r\n      }}>\r\n      <PageHeader\r\n        title=\"Predictive Pacing & Compliance\"\r\n        subtitle=\"Live dialling pace and the daily abandon rate against each campaign's target\"\r\n      />\r\n\r\n      {/* `false` while a deep-linked tab this login cannot read is being corrected —\r\n\t\t\t    MUI warns about a value with no matching tab. */}\r\n      <Tabs\r\n        value={visibleTabs.some((t) => t.key === tab) ? tab : false}\r\n        onChange={(_, v: TabKey) => setTab(v)}\r\n        sx={tabsSx}>\r\n        {visibleTabs.map((t) => (\r\n          <Tab key={t.key} value={t.key} label={t.label} />\r\n        ))}\r\n      </Tabs>\r\n\r\n      <Box sx={{ flex: 1, minHeight: 0, overflow: \"auto\", pt: 2 }}>\r\n        {tab !== \"live\" && canReadReports ? (\r\n          // Keyed so each tab starts with its own filters and data.\r\n          <AbandonRateReportContent key={tab} embedded view={tab} />\r\n        ) : (\r\n          <LiveTab\r\n            campaigns={campaigns}\r\n            loading={loading}\r\n            error={error}\r\n            selected={selected}\r\n            onSelect={handleSelect}\r\n          />\r\n        )}\r\n      </Box>\r\n    </Box>\r\n  );\r\n}\r\n\r\nconst PredictivePacing: React.FC = () => (\r\n  <SDKProvider>\r\n    <PredictivePacingContent />\r\n  </SDKProvider>\r\n);\r\n\r\nexport default PredictivePacing;\r\n","import React from \"react\";\r\nimport { Box, Chip, CircularProgress, IconButton, Paper, Tooltip, Typography } from \"@mui/material\";\r\nimport RefreshIcon from \"@mui/icons-material/Refresh\";\r\nimport WarningAmberIcon from \"@mui/icons-material/WarningAmber\";\r\nimport dayjs from \"dayjs\";\r\nimport { useLivePacing } from \"./useLivePacing\";\r\nimport {\r\n\tABANDON_STATUS_LABEL,\r\n\tPACING_REASON_LABEL,\r\n\ttype AbandonRateStatus,\r\n\ttype PacingStats,\r\n} from \"./pacingTypes\";\r\n\r\nconst FONT = \"poppins, Arial, sans-serif\";\r\nconst DASH = \"–\";\r\n\r\n/** A pacing decision older than this is history, not what the dialer is doing now. */\r\nconst DECISION_STALE_MS = 90_000;\r\n\r\nconst COLORS = {\r\n\tteal: \"#1A5F6C\",\r\n\tnavy: \"#0d2a56\",\r\n\tgreen: \"#0a9a62\",\r\n\tamber: \"#c47c00\",\r\n\tred: \"#cc2a2a\",\r\n\tgrey: \"#7a8599\",\r\n} as const;\r\n\r\n/** Tile colour and chip styling per abandon-rate status. */\r\nconst STATUS_STYLE: Record<AbandonRateStatus, { color: string; bg: string }> = {\r\n\tNO_DATA: { color: COLORS.grey, bg: \"#eceeed\" },\r\n\tOK: { color: COLORS.green, bg: \"#e3f3e8\" },\r\n\tWARNING: { color: COLORS.amber, bg: \"#fbf0d6\" },\r\n\tBREACHED: { color: COLORS.red, bg: \"#f9e3e4\" },\r\n};\r\n\r\nconst pct = (v: number | null | undefined) =>\r\n\ttypeof v === \"number\" ? `${(v * 100).toFixed(1)}%` : DASH;\r\nconst secs = (v: number | null | undefined) =>\r\n\ttypeof v === \"number\" ? `${v.toFixed(1)} s` : DASH;\r\nconst ratio = (v: number | null | undefined) =>\r\n\ttypeof v === \"number\" ? v.toFixed(1) : DASH;\r\n\r\nfunction Tile({ label, value, sub, color }: {\r\n\tlabel: string; value: string; sub?: string; color?: string;\r\n}) {\r\n\treturn (\r\n\t\t<Box sx={{ border: \"1px solid #e6ebf0\", borderRadius: \"8px\", px: 1.25, py: 1, minWidth: 0 }}>\r\n\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.65rem\", fontWeight: 600, letterSpacing: 0.4, textTransform: \"uppercase\", color: COLORS.grey }}>\r\n\t\t\t\t{label}\r\n\t\t\t</Typography>\r\n\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"1.25rem\", fontWeight: 700, lineHeight: 1.3, fontVariantNumeric: \"tabular-nums\", color: color || COLORS.navy }}>\r\n\t\t\t\t{value}\r\n\t\t\t</Typography>\r\n\t\t\t{sub && (\r\n\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.68rem\", color: COLORS.grey, whiteSpace: \"normal\" }}>\r\n\t\t\t\t\t{sub}\r\n\t\t\t\t</Typography>\r\n\t\t\t)}\r\n\t\t</Box>\r\n\t);\r\n}\r\n\r\n/** \"4 of 5 dialed\" — only when there is something to count. */\r\nfunction answeredSub(stats: PacingStats | null): string | undefined {\r\n\tif (!stats || stats.dialed === 0) return undefined;\r\n\treturn `${stats.answered} of ${stats.dialed} dialed`;\r\n}\r\n\r\n/**\r\n * Live pacing for the selected predictive campaign: the rolling abandon and\r\n * answer rates, how hard the dialer is currently dialing, and how the agents are\r\n * split between outbound and the inbound reserve.\r\n *\r\n * Rendered only for PREDICTIVE campaigns run by human agents — nothing else has\r\n * pacing. Polls while mounted; the caller unmounts it when the panel closes.\r\n */\r\nexport default function LivePacingCard({\r\n\tcampaignId, pacingWindowMinutes,\r\n}: {\r\n\tcampaignId: number;\r\n\t/** The campaign's configured window, so the card reports what the dialer decides on. */\r\n\tpacingWindowMinutes?: number | null;\r\n}) {\r\n\tconst { stats, state, hasData, loading, error, refresh } =\r\n\t\tuseLivePacing(campaignId, true, pacingWindowMinutes);\r\n\r\n\tconst status: AbandonRateStatus = stats?.abandonRateStatus ?? \"NO_DATA\";\r\n\tconst statusStyle = STATUS_STYLE[status];\r\n\r\n\t// Adaptive is either driving the dialer or only recommending (shadow mode).\r\n\tconst adaptiveApplied = state?.adaptiveApplied === true;\r\n\t// A decision that is no longer current means the campaign is paused or idle.\r\n\tconst stale = !!state && state.isCurrent === false;\r\n\r\n\t/*\r\n\t * An idle campaign keeps answering with its last decision, so \"safe mode\"\r\n\t * can sit on screen long after the dialing that caused it stopped. Past this\r\n\t * age the card says when the decision was taken rather than presenting it as\r\n\t * the dialer's current behaviour.\r\n\t */\r\n\tconst decidedAt = state?.decidedAt ? dayjs(state.decidedAt) : null;\r\n\tconst decisionStale = !!decidedAt && dayjs().diff(decidedAt) > DECISION_STALE_MS;\r\n\r\n\tconst applied = state?.appliedPacingRatio ?? stats?.appliedPacingRatio ?? null;\r\n\tconst recommended = state?.recommendedPacingRatio ?? stats?.recommendedPacingRatio ?? null;\r\n\t/** \"ceiling 1.4 (automatic)\" — the cap in force, and where it came from. */\r\n\tconst ceiling = typeof state?.pacingCeiling === \"number\"\r\n\t\t? `ceiling ${ratio(state.pacingCeiling)} (${state.pacingCeilingAuto ? \"automatic\" : \"fixed\"})`\r\n\t\t: null;\r\n\r\n\tconst forPredictive = state?.agentsForPredictive ?? stats?.agentsForPredictive ?? 0;\r\n\tconst reserved = state?.agentsReserved ?? stats?.agentsReserved ?? 0;\r\n\tconst totalAgents = (forPredictive || 0) + (reserved || 0);\r\n\r\n\tconst header = (\r\n\t\t<Box sx={{ display: \"flex\", alignItems: \"center\", gap: 1, flexWrap: \"wrap\", mb: 1.25 }}>\r\n\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.95rem\", fontWeight: 700, color: \"#1a1a1a\" }}>\r\n\t\t\t\tLive pacing\r\n\t\t\t</Typography>\r\n\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.72rem\", color: COLORS.grey }}>\r\n\t\t\t\tlast {stats?.windowMinutes ?? 15} min\r\n\t\t\t</Typography>\r\n\t\t\t<Box sx={{ flex: 1 }} />\r\n\t\t\t{stale && (\r\n\t\t\t\t<Tooltip arrow title=\"The campaign is paused or idle, so these are the last figures rather than live ones\">\r\n\t\t\t\t\t<Chip\r\n\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\tlabel=\"Not dialing\"\r\n\t\t\t\t\t\tsx={{ height: 20, fontSize: \"0.68rem\", fontWeight: 700, backgroundColor: \"#eceeed\", color: COLORS.grey }}\r\n\t\t\t\t\t/>\r\n\t\t\t\t</Tooltip>\r\n\t\t\t)}\r\n\t\t\t{state && (\r\n\t\t\t\t<Tooltip\r\n\t\t\t\t\tarrow\r\n\t\t\t\t\ttitle={adaptiveApplied\r\n\t\t\t\t\t\t? \"The dial ratio is being set automatically from the live rates\"\r\n\t\t\t\t\t\t: \"Adaptive is recommending a ratio but not applying it yet\"}\r\n\t\t\t\t>\r\n\t\t\t\t\t<Chip\r\n\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\tlabel={adaptiveApplied ? \"Adaptive\" : \"Shadow\"}\r\n\t\t\t\t\t\tsx={{\r\n\t\t\t\t\t\t\theight: 20, fontSize: \"0.68rem\", fontWeight: 700,\r\n\t\t\t\t\t\t\tbackgroundColor: adaptiveApplied ? \"#e3f3e8\" : \"#eceeed\",\r\n\t\t\t\t\t\t\tcolor: adaptiveApplied ? COLORS.green : COLORS.grey,\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t/>\r\n\t\t\t\t</Tooltip>\r\n\t\t\t)}\r\n\t\t\t<Chip\r\n\t\t\t\tsize=\"small\"\r\n\t\t\t\tlabel={ABANDON_STATUS_LABEL[status]}\r\n\t\t\t\tsx={{ height: 20, fontSize: \"0.68rem\", fontWeight: 700, backgroundColor: statusStyle.bg, color: statusStyle.color }}\r\n\t\t\t/>\r\n\t\t\t<Tooltip title=\"Refresh\" arrow>\r\n\t\t\t\t<span>\r\n\t\t\t\t\t<IconButton size=\"small\" onClick={refresh} disabled={loading}>\r\n\t\t\t\t\t\t{loading ? <CircularProgress size={14} /> : <RefreshIcon sx={{ fontSize: 16 }} />}\r\n\t\t\t\t\t</IconButton>\r\n\t\t\t\t</span>\r\n\t\t\t</Tooltip>\r\n\t\t</Box>\r\n\t);\r\n\r\n\t/**\r\n\t * Safe mode: the abandon rate went over target, so the dialer dropped to one\r\n\t * call per free agent. Amber rather than red, and no action offered — the\r\n\t * campaign is still dialing and lifts itself out when the rate recovers.\r\n\t */\r\n\tconst safeMode = state?.complianceStatus === \"LIMIT_EXCEEDED\";\r\n\r\n\tconst safeModeBanner = safeMode && (\r\n\t\t<Box\r\n\t\t\tsx={{\r\n\t\t\t\tdisplay: \"flex\",\r\n\t\t\t\talignItems: \"center\",\r\n\t\t\t\tgap: 1,\r\n\t\t\t\tmb: 1.25,\r\n\t\t\t\tpx: 1.25,\r\n\t\t\t\tpy: 1,\r\n\t\t\t\tborderRadius: \"8px\",\r\n\t\t\t\tborder: `1px solid ${COLORS.amber}`,\r\n\t\t\t\tbackgroundColor: \"#fbf0d6\",\r\n\t\t\t}}\r\n\t\t>\r\n\t\t\t<WarningAmberIcon sx={{ color: COLORS.amber, fontSize: 18 }} />\r\n\t\t\t<Box>\r\n\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.78rem\", fontWeight: 700, color: COLORS.amber }}>\r\n\t\t\t\t\t{decisionStale\r\n\t\t\t\t\t\t? `Safe mode (as of ${decidedAt!.format(\"h:mm A\")}) — was dialing one call per free agent`\r\n\t\t\t\t\t\t: \"Abandon rate over target — dialing one call per free agent\"}\r\n\t\t\t\t</Typography>\r\n\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.7rem\", color: COLORS.grey }}>\r\n\t\t\t\t\t{decisionStale\r\n\t\t\t\t\t\t? \"Nothing has been dialed since, so this is the last decision rather than a live reading.\"\r\n\t\t\t\t\t\t: \"The campaign keeps running. Adaptive pacing resumes on its own once the rate recovers.\"}\r\n\t\t\t\t</Typography>\r\n\t\t\t</Box>\r\n\t\t</Box>\r\n\t);\r\n\r\n\tconst body = () => {\r\n\t\tif (!hasData) {\r\n\t\t\treturn (\r\n\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.78rem\", color: COLORS.grey }}>\r\n\t\t\t\t\tNo pacing data yet — it appears once the campaign has run.\r\n\t\t\t\t</Typography>\r\n\t\t\t);\r\n\t\t}\r\n\t\tif (error) {\r\n\t\t\treturn (\r\n\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.78rem\", color: COLORS.red }}>{error}</Typography>\r\n\t\t\t);\r\n\t\t}\r\n\t\tif (!stats && loading) {\r\n\t\t\treturn <CircularProgress size={20} sx={{ color: COLORS.teal, my: 1 }} />;\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t<>\r\n\t\t\t\t<Box sx={{ display: \"grid\", gridTemplateColumns: { xs: \"1fr 1fr\", md: \"repeat(5, 1fr)\" }, gap: 1 }}>\r\n\t\t\t\t\t<Tile\r\n\t\t\t\t\t\tlabel=\"Abandon rate\"\r\n\t\t\t\t\t\tvalue={pct(stats?.abandonRate)}\r\n\t\t\t\t\t\tcolor={statusStyle.color}\r\n\t\t\t\t\t\tsub={[\r\n\t\t\t\t\t\t\tstats?.targetAbandonRate != null ? `target ${pct(stats.targetAbandonRate)}` : null,\r\n\t\t\t\t\t\t\t// One abandoned call out of three is 33% and means nothing yet — say so.\r\n\t\t\t\t\t\t\tstats?.lowSample ? \"few calls yet\" : null,\r\n\t\t\t\t\t\t].filter(Boolean).join(\" · \") || undefined}\r\n\t\t\t\t\t/>\r\n\t\t\t\t\t<Tile label=\"Answer rate\" value={pct(stats?.answerRate)} color={COLORS.green} sub={answeredSub(stats)} />\r\n\t\t\t\t\t<Tile label=\"Time to agent\" value={secs(stats?.avgTimeToAgentSeconds)} sub=\"average\" />\r\n\t\t\t\t\t<Tile\r\n\t\t\t\t\t\tlabel=\"Dial ratio\"\r\n\t\t\t\t\t\tvalue={ratio(applied)}\r\n\t\t\t\t\t\tsub={[\r\n\t\t\t\t\t\t\t// In shadow mode the useful comparison is what adaptive would do.\r\n\t\t\t\t\t\t\t!adaptiveApplied && recommended != null ? `adaptive would use ${ratio(recommended)}` : null,\r\n\t\t\t\t\t\t\t// The cap it may never exceed, and whether the backend worked that\r\n\t\t\t\t\t\t\t// out from the live rates or an admin fixed it.\r\n\t\t\t\t\t\t\tceiling,\r\n\t\t\t\t\t\t].filter(Boolean).join(\" · \") || undefined}\r\n\t\t\t\t\t/>\r\n\t\t\t\t\t<Tile\r\n\t\t\t\t\t\tlabel=\"On calls / waiting\"\r\n\t\t\t\t\t\tvalue={`${stats?.activeCalls ?? 0} / ${stats?.waitingForAgent ?? 0}`}\r\n\t\t\t\t\t\tsub=\"right now\"\r\n\t\t\t\t\t/>\r\n\t\t\t\t</Box>\r\n\r\n\t\t\t\t{/* Agent split: what predictive may dial for, and what is held back for inbound. */}\r\n\t\t\t\t<Box sx={{ mt: 1.25 }}>\r\n\t\t\t\t\t<Box sx={{ display: \"flex\", height: 12, borderRadius: \"6px\", overflow: \"hidden\", border: \"1px solid #e6ebf0\" }}>\r\n\t\t\t\t\t\t{totalAgents > 0 ? (\r\n\t\t\t\t\t\t\t<>\r\n\t\t\t\t\t\t\t\t<Box sx={{ width: `${(forPredictive / totalAgents) * 100}%`, backgroundColor: COLORS.teal }} />\r\n\t\t\t\t\t\t\t\t<Box sx={{ width: `${(reserved / totalAgents) * 100}%`, backgroundColor: COLORS.amber }} />\r\n\t\t\t\t\t\t\t</>\r\n\t\t\t\t\t\t) : (\r\n\t\t\t\t\t\t\t<Box sx={{ width: \"100%\", backgroundColor: \"#eceeed\" }} />\r\n\t\t\t\t\t\t)}\r\n\t\t\t\t\t</Box>\r\n\t\t\t\t\t<Box sx={{ display: \"flex\", flexWrap: \"wrap\", gap: \"4px 14px\", mt: 0.6, fontFamily: FONT, fontSize: \"0.7rem\", color: COLORS.grey }}>\r\n\t\t\t\t\t\t<span><Box component=\"span\" sx={{ display: \"inline-block\", width: 9, height: 9, borderRadius: \"2px\", backgroundColor: COLORS.teal, mr: 0.6 }} />Outbound {forPredictive}</span>\r\n\t\t\t\t\t\t<span><Box component=\"span\" sx={{ display: \"inline-block\", width: 9, height: 9, borderRadius: \"2px\", backgroundColor: COLORS.amber, mr: 0.6 }} />Held for inbound {reserved}</span>\r\n\t\t\t\t\t\t{state?.pacingReason && <span>{PACING_REASON_LABEL[state.pacingReason] ?? state.pacingReason}</span>}\r\n\t\t\t\t\t</Box>\r\n\t\t\t\t</Box>\r\n\r\n\t\t\t\t{stale && (\r\n\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.7rem\", color: COLORS.grey, mt: 0.75 }}>\r\n\t\t\t\t\t\tCampaign is not dialing — last decision {state?.decidedAt ? dayjs(state.decidedAt).format(\"D MMM, h:mm A\") : \"unknown\"}\r\n\t\t\t\t\t</Typography>\r\n\t\t\t\t)}\r\n\t\t\t</>\r\n\t\t);\r\n\t};\r\n\r\n\treturn (\r\n\t\t<Paper\r\n\t\t\televation={0}\r\n\t\t\tsx={{\r\n\t\t\t\tborderRadius: \"16px\",\r\n\t\t\t\tborder: \"1px solid #e0e0e0\",\r\n\t\t\t\tboxShadow: \"0 1px 3px rgba(0,0,0,0.08)\",\r\n\t\t\t\tp: { xs: 1.5, sm: 2 },\r\n\t\t\t}}\r\n\t\t>\r\n\t\t\t{header}\r\n\t\t\t{safeModeBanner}\r\n\t\t\t{body()}\r\n\t\t</Paper>\r\n\t);\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport axiosInstance from \"../../services/axios\";\r\nimport { END_POINT } from \"../../services/endPoint\";\r\nimport type { PacingStats, PacingState } from \"./pacingTypes\";\r\n\r\n/** The server caches live stats for 10s, so polling faster than this buys nothing. */\r\nconst POLL_MS = 12_000;\r\n\r\n/** Only used when the campaign has no window of its own. */\r\nconst DEFAULT_WINDOW_MINUTES = 15;\r\n\r\nexport interface LivePacing {\r\n\tstats: PacingStats | null;\r\n\tstate: PacingState | null;\r\n\t/** True once a campaign has been active: 404 from pacing-state means it never has. */\r\n\thasData: boolean;\r\n\tloading: boolean;\r\n\terror: string;\r\n\trefresh: () => void;\r\n}\r\n\r\n/**\r\n * Live pacing for one campaign, polled while `enabled`.\r\n *\r\n * Both endpoints are read together because the card needs them together — the\r\n * rolling rates from `pacing-stats` and the current dial decision from\r\n * `pacing-state`. Polling stops when the caller disables it AND when the tab is\r\n * hidden, so a supervisor who leaves the page open all day isn't quietly making\r\n * a request every few seconds forever.\r\n */\r\nexport function useLivePacing(\r\n\tcampaignId: number | null,\r\n\tenabled: boolean,\r\n\t/**\r\n\t * The campaign's own pacing window. The dialer decides on this many minutes,\r\n\t * so the card has to report the same span — showing 15 minutes beside a\r\n\t * decision made on 5 invites the reader to draw the wrong conclusion.\r\n\t */\r\n\twindowMinutes?: number | null,\r\n): LivePacing {\r\n\tconst [stats, setStats] = useState<PacingStats | null>(null);\r\n\tconst [state, setState] = useState<PacingState | null>(null);\r\n\tconst [hasData, setHasData] = useState(true);\r\n\tconst [loading, setLoading] = useState(false);\r\n\tconst [error, setError] = useState(\"\");\r\n\r\n\t// Lets a poll skip a tick while one is still in flight on a slow connection.\r\n\t// Keyed by campaign so switching campaigns is never the request that gets\r\n\t// dropped — that would leave the previous campaign's numbers on screen.\r\n\tconst inFlightFor = useRef<number | null>(null);\r\n\r\n\t/** The campaign the caller is asking about now — a reply for any other one is\r\n\t *  a late response from a campaign that has since been deselected. */\r\n\tconst wanted = useRef<number | null>(campaignId);\r\n\twanted.current = campaignId;\r\n\r\n\tconst load = useCallback(async (showSpinner: boolean) => {\r\n\t\tif (campaignId == null || inFlightFor.current === campaignId) return;\r\n\t\tinFlightFor.current = campaignId;\r\n\t\tif (showSpinner) setLoading(true);\r\n\t\ttry {\r\n\t\t\tconst [statsRes, stateRes] = await Promise.allSettled([\r\n\t\t\t\taxiosInstance.get(END_POINT.PACING_STATS(campaignId), {\r\n\t\t\t\t\tparams: { window_minutes: windowMinutes || DEFAULT_WINDOW_MINUTES },\r\n\t\t\t\t}),\r\n\t\t\t\taxiosInstance.get(END_POINT.PACING_STATE(campaignId)),\r\n\t\t\t]);\r\n\r\n\t\t\t// Dropped rather than rendered: the selection moved on while this was\r\n\t\t\t// in the air, and these numbers belong to the previous campaign.\r\n\t\t\tif (wanted.current !== campaignId) return;\r\n\r\n\t\t\tif (statsRes.status === \"fulfilled\") setStats(statsRes.value.data ?? null);\r\n\t\t\telse setStats(null);\r\n\r\n\t\t\tif (stateRes.status === \"fulfilled\") {\r\n\t\t\t\tsetState(stateRes.value.data ?? null);\r\n\t\t\t\tsetHasData(true);\r\n\t\t\t} else {\r\n\t\t\t\t// 404 is the documented \"this campaign has never run\" answer, not a failure.\r\n\t\t\t\tconst status = (stateRes.reason as any)?.response?.status;\r\n\t\t\t\tsetState(null);\r\n\t\t\t\tsetHasData(status !== 404);\r\n\t\t\t}\r\n\r\n\t\t\t// Any real failure is reported. Only a 404 from pacing-state is exempt:\r\n\t\t\t// that is the documented \"this campaign has never run\" answer, handled\r\n\t\t\t// as `hasData` above. Reporting only when BOTH calls failed let a 500 on\r\n\t\t\t// pacing-stats render as an idle campaign — a broken backend that looks\r\n\t\t\t// like a quiet one is the worst way to be wrong here.\r\n\t\t\tconst failed = (res: PromiseSettledResult<unknown>, allow404: boolean) => {\r\n\t\t\t\tif (res.status !== \"rejected\") return false;\r\n\t\t\t\tconst status = (res.reason as any)?.response?.status;\r\n\t\t\t\treturn !(allow404 && status === 404);\r\n\t\t\t};\r\n\t\t\tconst anyFailed = failed(statsRes, false) || failed(stateRes, true);\r\n\t\t\tsetError(anyFailed ? \"Could not load live pacing\" : \"\");\r\n\t\t} finally {\r\n\t\t\tif (inFlightFor.current === campaignId) inFlightFor.current = null;\r\n\t\t\tif (showSpinner) setLoading(false);\r\n\t\t}\r\n\t}, [campaignId, windowMinutes]);\r\n\r\n\tuseEffect(() => {\r\n\t\t// Clear on every change of campaign, not just on disable: otherwise the\r\n\t\t// previous campaign's rates and dial ratio sit under the new one's name\r\n\t\t// until the first response lands.\r\n\t\tsetStats(null);\r\n\t\tsetState(null);\r\n\t\tsetHasData(true);\r\n\t\tsetError(\"\");\r\n\r\n\t\tif (!enabled || campaignId == null) return;\r\n\r\n\t\tload(true);\r\n\t\tlet timer = window.setInterval(() => {\r\n\t\t\tif (document.visibilityState === \"visible\") load(false);\r\n\t\t}, POLL_MS);\r\n\r\n\t\t// Coming back to the tab should show current numbers, not wait for the next tick.\r\n\t\tconst onVisible = () => { if (document.visibilityState === \"visible\") load(false); };\r\n\t\tdocument.addEventListener(\"visibilitychange\", onVisible);\r\n\r\n\t\treturn () => {\r\n\t\t\twindow.clearInterval(timer);\r\n\t\t\ttimer = 0;\r\n\t\t\tdocument.removeEventListener(\"visibilitychange\", onVisible);\r\n\t\t};\r\n\t}, [enabled, campaignId, load]);\r\n\r\n\treturn { stats, state, hasData, loading, error, refresh: () => load(true) };\r\n}\r\n","/**\r\n * Live pacing for a running predictive campaign.\r\n *\r\n * Rates are FRACTIONS (0.025 is 2.5%). `null` means \"not known yet\" and must\r\n * read as a dash, never as zero — a campaign that has answered nothing has an\r\n * unknown abandon rate, not a perfect one.\r\n */\r\n\r\n/** How the rolling abandon rate sits against the campaign's target. */\r\nexport type AbandonRateStatus = \"NO_DATA\" | \"OK\" | \"WARNING\" | \"BREACHED\";\r\n\r\n/** Why the dialer is using the ratio it is using. */\r\nexport type PacingReason =\r\n\t| \"ADAPTIVE\"\r\n\t| \"THROTTLED\"\r\n\t| \"LEARNING\"\r\n\t| \"FEW_AGENTS\"\r\n\t| \"INSUFFICIENT_DATA\"\r\n\t| \"SAFE_MODE\";\r\n\r\n/**\r\n * The system never pauses a campaign. Over target it drops to safe mode — one\r\n * call per free agent, which almost never abandons — and keeps dialing until the\r\n * rate recovers on its own.\r\n */\r\nexport type ComplianceStatus = \"OK\" | \"THROTTLED\" | \"LIMIT_EXCEEDED\";\r\n\r\n/** GET /dialer/campaigns/{id}/pacing-stats — a rolling window, default 15 minutes. */\r\nexport interface PacingStats {\r\n\tcampaignId: number;\r\n\twindowStart: string;\r\n\twindowEnd: string;\r\n\twindowMinutes: number;\r\n\tdialed: number;\r\n\tanswered: number;\r\n\tagentConnected: number;\r\n\tabandoned: number;\r\n\tabandonedWaitTimeout: number;\r\n\tabandonedCustomerHangup: number;\r\n\tsystemErrors: number;\r\n\tanswerRate: number | null;\r\n\tabandonRate: number | null;\r\n\ttargetAbandonRate: number | null;\r\n\tabandonRateStatus: AbandonRateStatus;\r\n\t/** Too few calls for the rate to mean much — say so rather than alarm anyone. */\r\n\tlowSample: boolean;\r\n\tavgTimeToAgentSeconds: number | null;\r\n\tavgAbandonWaitSeconds: number | null;\r\n\tavgTalkSeconds: number | null;\r\n\tavgWrapupSeconds: number | null;\r\n\tavgHandleSeconds: number | null;\r\n\tactiveCalls: number;\r\n\twaitingForAgent: number;\r\n\tappliedPacingRatio: number | null;\r\n\trecommendedPacingRatio: number | null;\r\n\tagentsForPredictive: number | null;\r\n\tagentsReserved: number | null;\r\n}\r\n\r\n/** GET /dialer/campaigns/{id}/pacing-state — the most recent dial decision. */\r\nexport interface PacingState {\r\n\tcampaignId: number;\r\n\tadaptivePacingEnabled: boolean;\r\n\t/** False while adaptive is only recommending — shadow mode. */\r\n\tadaptiveApplied: boolean;\r\n\t/** False when the campaign is paused or idle: the decision is stale. */\r\n\tisCurrent: boolean;\r\n\tdecidedAt: string | null;\r\n\tstaticPacingRatio: number | null;\r\n\trecommendedPacingRatio: number | null;\r\n\tappliedPacingRatio: number | null;\r\n\t/** The highest ratio the dialer may use right now. */\r\n\tpacingCeiling: number | null;\r\n\t/** True when that ceiling was derived from the live rates rather than configured. */\r\n\tpacingCeilingAuto: boolean | null;\r\n\tpacingReason: PacingReason | null;\r\n\tagentsIdle: number | null;\r\n\tagentsReserved: number | null;\r\n\tagentsForPredictive: number | null;\r\n\tpredictedInboundNextMinute: number | null;\r\n\tcomplianceStatus: ComplianceStatus | null;\r\n\tcomplianceAbandonRate: number | null;\r\n\tcomplianceAnswered: number | null;\r\n}\r\n\r\n/**\r\n * GET /dialer/campaigns/{id}/pacing-stats/history — the same shape as\r\n * pacing-stats, one point per bucket.\r\n *\r\n * Points only exist for buckets the campaign was active in, so an idle night is\r\n * simply missing rather than a run of zeros. Plot on a time axis, not one slot\r\n * per point.\r\n */\r\nexport interface PacingHistory {\r\n\t/** Spacing between points, chosen by the server to keep the series under ~500. */\r\n\tbucketMinutes: number;\r\n\tpoints: PacingStats[];\r\n}\r\n\r\n/**\r\n * What the compliance monitor did.\r\n *\r\n * THROTTLE_* is the 10% slow-down near the target; LIMIT_* is safe mode going on\r\n * and off. `AUTO_PAUSE` was removed when the backend stopped pausing campaigns.\r\n */\r\nexport type PacingEventType = \"THROTTLE_ON\" | \"THROTTLE_OFF\" | \"LIMIT_EXCEEDED\" | \"LIMIT_CLEARED\";\r\n\r\n/** GET /dialer/campaigns/{id}/pacing-events — newest first. */\r\nexport interface PacingEvent {\r\n\tid: number;\r\n\tcampaignId: number;\r\n\teventType: PacingEventType;\r\n\tcreatedAt: string;\r\n\twindowStart: string | null;\r\n\twindowEnd: string | null;\r\n\tanswered: number | null;\r\n\tabandoned: number | null;\r\n\tabandonRate: number | null;\r\n\ttargetAbandonRate: number | null;\r\n\tratioBefore: number | null;\r\n\tratioAfter: number | null;\r\n\t/** Written for supervisors — show it as it comes. */\r\n\tmessage: string;\r\n}\r\n\r\n/**\r\n * GET /dialer/pacing/alerts — the same events across every campaign in the\r\n * tenant, for the supervisor toasts. Only LIMIT_EXCEEDED and LIMIT_CLEARED\r\n * appear here, and each carries the campaign's name so a toast can say which.\r\n */\r\nexport interface PacingAlert extends PacingEvent {\r\n\tcampaignName: string;\r\n}\r\n\r\nexport const ABANDON_STATUS_LABEL: Record<AbandonRateStatus, string> = {\r\n\tNO_DATA: \"No calls yet\",\r\n\tOK: \"Within target\",\r\n\tWARNING: \"Near target\",\r\n\tBREACHED: \"Over target\",\r\n};\r\n\r\nexport const PACING_REASON_LABEL: Record<PacingReason, string> = {\r\n\tADAPTIVE: \"Adapting to the live answer rate\",\r\n\tTHROTTLED: \"Slowed down: abandon rate near target\",\r\n\tLEARNING: \"Learning period: dialing 1:1\",\r\n\tFEW_AGENTS: \"Fewer than 3 agents free: dialing 1:1\",\r\n\tINSUFFICIENT_DATA: \"Not enough calls yet: using the standard ratio\",\r\n\tSAFE_MODE: \"Abandon rate over target: dialing 1:1 until it recovers\",\r\n};\r\n\r\nexport const COMPLIANCE_STATUS_LABEL: Record<ComplianceStatus, string> = {\r\n\tOK: \"OK\",\r\n\tTHROTTLED: \"Throttled\",\r\n\tLIMIT_EXCEEDED: \"Over target: safe mode\",\r\n};\r\n","import { useState } from \"react\";\r\nimport { Box, Chip, Collapse, Paper, Typography } from \"@mui/material\";\r\nimport KeyboardArrowDownIcon from \"@mui/icons-material/KeyboardArrowDown\";\r\nimport KeyboardArrowRightIcon from \"@mui/icons-material/KeyboardArrowRight\";\r\nimport dayjs from \"dayjs\";\r\nimport { usePacingEvents } from \"./usePacingEvents\";\r\nimport type { PacingEvent, PacingEventType } from \"./pacingTypes\";\r\n\r\nconst FONT = \"poppins, Arial, sans-serif\";\r\n\r\nconst COLORS = {\r\n\tred: \"#cc2a2a\",\r\n\tredBg: \"#fdecea\",\r\n\tamber: \"#c47c00\",\r\n\tamberBg: \"#fbf0d6\",\r\n\tgreen: \"#0a9a62\",\r\n\tgreenBg: \"#e3f3e8\",\r\n\tgrey: \"#7a8599\",\r\n} as const;\r\n\r\nconst EVENT_STYLE: Record<PacingEventType, { label: string; color: string; bg: string }> = {\r\n\t// A 10% slow-down as the rate approaches the target, and its release.\r\n\tTHROTTLE_ON: { label: \"Throttled\", color: COLORS.amber, bg: COLORS.amberBg },\r\n\tTHROTTLE_OFF: { label: \"Throttle lifted\", color: COLORS.green, bg: COLORS.greenBg },\r\n\t// Safe mode: over target, dialing 1:1 — the campaign keeps running throughout.\r\n\tLIMIT_EXCEEDED: { label: \"Safe mode on\", color: COLORS.red, bg: COLORS.redBg },\r\n\tLIMIT_CLEARED: { label: \"Recovered\", color: COLORS.green, bg: COLORS.greenBg },\r\n};\r\n\r\nconst when = (value: string | null) => (value ? dayjs(value).format(\"D MMM, h:mm A\") : \"\");\r\n\r\nfunction EventRow({ event }: { event: PacingEvent }) {\r\n\tconst style = EVENT_STYLE[event.eventType] ?? {\r\n\t\tlabel: event.eventType, color: COLORS.grey, bg: \"#eceeed\",\r\n\t};\r\n\treturn (\r\n\t\t<Box sx={{ display: \"flex\", alignItems: \"flex-start\", gap: 1, py: 0.6, borderTop: \"1px solid #f0f3f5\" }}>\r\n\t\t\t<Chip\r\n\t\t\t\tsize=\"small\"\r\n\t\t\t\tlabel={style.label}\r\n\t\t\t\tsx={{ height: 20, fontSize: \"0.65rem\", fontWeight: 700, backgroundColor: style.bg, color: style.color, flexShrink: 0 }}\r\n\t\t\t/>\r\n\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.73rem\", color: \"#4a5568\", flex: 1, minWidth: 0 }}>\r\n\t\t\t\t{event.message}\r\n\t\t\t</Typography>\r\n\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.68rem\", color: COLORS.grey, whiteSpace: \"nowrap\", flexShrink: 0 }}>\r\n\t\t\t\t{when(event.createdAt)}\r\n\t\t\t</Typography>\r\n\t\t</Box>\r\n\t);\r\n}\r\n\r\n/**\r\n * What the compliance monitor has done to this campaign, newest first.\r\n *\r\n * Nothing here needs anyone to act: throttles release themselves, and safe mode\r\n * lifts when the abandon rate recovers. The campaign keeps dialing throughout —\r\n * the system no longer pauses one, so there is no Resume. While safe mode is on,\r\n * the live pacing card carries the banner, driven by the current compliance\r\n * status rather than by an event.\r\n */\r\nexport default function PacingAlerts({ campaignId }: { campaignId: number }) {\r\n\tconst { events, loading } = usePacingEvents(campaignId, true);\r\n\t// Open by default: on a page devoted to pacing, the events are what the\r\n\t// supervisor came to read, not a detail to go looking for.\r\n\tconst [showHistory, setShowHistory] = useState(true);\r\n\r\n\tif (loading && events.length === 0) return null;\r\n\tif (events.length === 0) return null;\r\n\r\n\tconst history = events;\r\n\r\n\treturn (\r\n\t\t<Box sx={{ display: \"flex\", flexDirection: \"column\", gap: 1 }}>\r\n\t\t\t{history.length > 0 && (\r\n\t\t\t\t<Paper\r\n\t\t\t\t\televation={0}\r\n\t\t\t\t\tsx={{ borderRadius: \"12px\", border: \"1px solid #e0e0e0\", px: { xs: 1.5, sm: 2 }, py: 1 }}\r\n\t\t\t\t>\r\n\t\t\t\t\t<Box\r\n\t\t\t\t\t\tonClick={() => setShowHistory((v) => !v)}\r\n\t\t\t\t\t\tsx={{ display: \"flex\", alignItems: \"center\", gap: 0.5, cursor: \"pointer\", userSelect: \"none\" }}\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t{showHistory ? <KeyboardArrowDownIcon sx={{ fontSize: 18, color: COLORS.grey }} /> : <KeyboardArrowRightIcon sx={{ fontSize: 18, color: COLORS.grey }} />}\r\n\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.78rem\", fontWeight: 700, color: \"#1a1a1a\" }}>\r\n\t\t\t\t\t\t\tPacing events\r\n\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.72rem\", color: COLORS.grey }}>\r\n\t\t\t\t\t\t\t{history.length} in the last 24 hours\r\n\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t</Box>\r\n\t\t\t\t\t<Collapse in={showHistory} unmountOnExit>\r\n\t\t\t\t\t\t<Box sx={{ mt: 0.5 }}>\r\n\t\t\t\t\t\t\t{history.map((e) => <EventRow key={e.id} event={e} />)}\r\n\t\t\t\t\t\t</Box>\r\n\t\t\t\t\t</Collapse>\r\n\t\t\t\t</Paper>\r\n\t\t\t)}\r\n\t\t</Box>\r\n\t);\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport axiosInstance from \"../../services/axios\";\r\nimport { END_POINT } from \"../../services/endPoint\";\r\nimport type { PacingEvent } from \"./pacingTypes\";\r\n\r\n/** There is no push channel for supervisors yet, so these are polled. */\r\nconst POLL_MS = 30_000;\r\n\r\nconst LIMIT = 100;\r\n\r\n/**\r\n * Throttle and auto-pause events for one campaign, newest first.\r\n *\r\n * Defaults to the API's own window (the last 24 hours), which is the right span\r\n * for \"what has this campaign done to itself today\". Like the live pacing poll,\r\n * it stops while the tab is hidden.\r\n */\r\nexport function usePacingEvents(campaignId: number | null, enabled: boolean) {\r\n\tconst [events, setEvents] = useState<PacingEvent[]>([]);\r\n\tconst [loading, setLoading] = useState(false);\r\n\t// Keyed by campaign, so switching campaigns is never the request that gets\r\n\t// skipped — that would leave the previous campaign's events listed.\r\n\tconst inFlightFor = useRef<number | null>(null);\r\n\t/** The campaign being asked about now; anything else is a late reply. */\r\n\tconst wanted = useRef<number | null>(campaignId);\r\n\twanted.current = campaignId;\r\n\r\n\tconst load = useCallback(async (showSpinner: boolean) => {\r\n\t\tif (campaignId == null || inFlightFor.current === campaignId) return;\r\n\t\tinFlightFor.current = campaignId;\r\n\t\tif (showSpinner) setLoading(true);\r\n\t\ttry {\r\n\t\t\tconst res = await axiosInstance.get(END_POINT.PACING_EVENTS(campaignId), {\r\n\t\t\t\tparams: { limit: LIMIT },\r\n\t\t\t});\r\n\t\t\tif (wanted.current !== campaignId) return;\r\n\t\t\tconst body = res.data;\r\n\t\t\tsetEvents(Array.isArray(body) ? body : (body?.data ?? []));\r\n\t\t} catch {\r\n\t\t\t// A campaign that has never been throttled simply has nothing to show;\r\n\t\t\t// an error here shouldn't put a red banner over a healthy campaign.\r\n\t\t\tsetEvents([]);\r\n\t\t} finally {\r\n\t\t\tif (inFlightFor.current === campaignId) inFlightFor.current = null;\r\n\t\t\tif (showSpinner) setLoading(false);\r\n\t\t}\r\n\t}, [campaignId]);\r\n\r\n\tuseEffect(() => {\r\n\t\t// Clear on every campaign change, not just on disable.\r\n\t\tsetEvents([]);\r\n\r\n\t\tif (!enabled || campaignId == null) return;\r\n\r\n\t\tload(true);\r\n\t\tconst timer = window.setInterval(() => {\r\n\t\t\tif (document.visibilityState === \"visible\") load(false);\r\n\t\t}, POLL_MS);\r\n\t\tconst onVisible = () => { if (document.visibilityState === \"visible\") load(false); };\r\n\t\tdocument.addEventListener(\"visibilitychange\", onVisible);\r\n\r\n\t\treturn () => {\r\n\t\t\twindow.clearInterval(timer);\r\n\t\t\tdocument.removeEventListener(\"visibilitychange\", onVisible);\r\n\t\t};\r\n\t}, [enabled, campaignId, load]);\r\n\r\n\treturn { events, loading, refresh: () => load(true) };\r\n}\r\n","import { useMemo, useState } from \"react\";\r\nimport { Box, CircularProgress, Paper, Typography } from \"@mui/material\";\r\nimport {\r\n\tCartesianGrid,\r\n\tLegend,\r\n\tLine,\r\n\tLineChart,\r\n\tReferenceLine,\r\n\tResponsiveContainer,\r\n\tTooltip,\r\n\tXAxis,\r\n\tYAxis,\r\n} from \"recharts\";\r\nimport dayjs from \"dayjs\";\r\nimport { SDKButton } from \"../../components/SDKButton\";\r\nimport { HISTORY_RANGES, usePacingHistory, type HistoryRangeKey } from \"./usePacingHistory\";\r\n\r\nconst FONT = \"poppins, Arial, sans-serif\";\r\n\r\nconst COLORS = {\r\n\tteal: \"#1A5F6C\",\r\n\tred: \"#cc2a2a\",\r\n\tamber: \"#c47c00\",\r\n\tgrey: \"#7a8599\",\r\n\tgrid: \"#eef1f6\",\r\n} as const;\r\n\r\ninterface ChartPoint {\r\n\tt: number;\r\n\tabandonPct: number | null;\r\n\tapplied: number | null;\r\n\trecommended: number | null;\r\n}\r\n\r\n/** Axis ticks: a time of day within a day, a date beyond it. */\r\nfunction tickFormat(value: number, wideRange: boolean): string {\r\n\treturn dayjs(value).format(wideRange ? \"D MMM HH:mm\" : \"HH:mm\");\r\n}\r\n\r\nfunction ChartTooltip({ active, payload, label }: any) {\r\n\tif (!active || !payload?.length) return null;\r\n\treturn (\r\n\t\t<Box sx={{ backgroundColor: \"#fff\", border: \"1px solid #e2e8f0\", borderRadius: \"8px\", px: 1.25, py: 0.75, boxShadow: \"0 6px 18px rgba(15,23,42,0.12)\" }}>\r\n\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.7rem\", color: COLORS.grey, mb: 0.3 }}>\r\n\t\t\t\t{dayjs(label).format(\"D MMM YYYY, HH:mm\")}\r\n\t\t\t</Typography>\r\n\t\t\t{payload.map((entry: any) => (\r\n\t\t\t\t<Typography key={entry.dataKey} sx={{ fontFamily: FONT, fontSize: \"0.73rem\", fontWeight: 600, color: entry.color }}>\r\n\t\t\t\t\t{entry.name}: {entry.value == null ? \"–\" : entry.dataKey === \"abandonPct\" ? `${entry.value.toFixed(2)}%` : entry.value.toFixed(1)}\r\n\t\t\t\t</Typography>\r\n\t\t\t))}\r\n\t\t</Box>\r\n\t);\r\n}\r\n\r\n/**\r\n * What the pacing has actually done over time.\r\n *\r\n * Two charts: the abandon rate against its target, and the dial ratio the dialer\r\n * applied against the one adaptive recommended. The second is the shadow-mode\r\n * comparison — the evidence a pilot is signed off on, showing what adaptive\r\n * would have done before it is allowed to do it.\r\n *\r\n * Read on demand rather than polled, and only for predictive campaigns with\r\n * human agents (the caller decides that).\r\n */\r\nexport default function PacingHistoryCharts({ campaignId }: { campaignId: number }) {\r\n\t// The last hour, matching the API's own default. A wider window compresses a\r\n\t// short burst of dialing into a couple of pixels, which reads as an artifact\r\n\t// rather than as data.\r\n\tconst [range, setRange] = useState<HistoryRangeKey>(\"1h\");\r\n\tconst { history, loading, error } = usePacingHistory(campaignId, range, true);\r\n\r\n\tconst wideRange = range === \"24h\" || range === \"7d\";\r\n\r\n\tconst data = useMemo<ChartPoint[]>(() => {\r\n\t\tconst points = history.points\r\n\t\t\t.map((p) => ({\r\n\t\t\t\t// Plot at windowEnd: the minute the snapshot was taken. windowStart is\r\n\t\t\t\t// windowEnd minus the look-back (15 min), so it would draw every point\r\n\t\t\t\t// that much early. A wall-clock stamp with no timezone, which dayjs\r\n\t\t\t\t// reads as local — which is what it is.\r\n\t\t\t\tt: dayjs(p.windowEnd).valueOf(),\r\n\t\t\t\t// Null stays null so the line breaks: an idle gap is not a 0% abandon rate.\r\n\t\t\t\tabandonPct: typeof p.abandonRate === \"number\" ? p.abandonRate * 100 : null,\r\n\t\t\t\tapplied: p.appliedPacingRatio ?? null,\r\n\t\t\t\trecommended: p.recommendedPacingRatio ?? null,\r\n\t\t\t}))\r\n\t\t\t.filter((p) => Number.isFinite(p.t))\r\n\t\t\t.sort((a, b) => a.t - b.t);\r\n\r\n\t\t/*\r\n\t\t * Break the line across idle periods.\r\n\t\t *\r\n\t\t * The API only returns buckets the campaign was active in, so an idle night\r\n\t\t * is absent rather than null — and a line chart happily joins the last point\r\n\t\t * before it to the first point after, drawing a long diagonal (or a vertical\r\n\t\t * jump) across hours of nothing. Inserting an all-null spacer wherever the\r\n\t\t * gap exceeds one bucket makes the break explicit, which `connectNulls={false}`\r\n\t\t * then honours.\r\n\t\t */\r\n\t\tconst gapMs = Math.max(history.bucketMinutes, 1) * 60_000 * 1.5;\r\n\t\tconst withGaps: ChartPoint[] = [];\r\n\t\tpoints.forEach((point, i) => {\r\n\t\t\tconst previous = points[i - 1];\r\n\t\t\tif (previous && point.t - previous.t > gapMs) {\r\n\t\t\t\twithGaps.push({ t: previous.t + 1, abandonPct: null, applied: null, recommended: null });\r\n\t\t\t}\r\n\t\t\twithGaps.push(point);\r\n\t\t});\r\n\t\treturn withGaps;\r\n\t}, [history.points, history.bucketMinutes]);\r\n\r\n\t/** The target line: the most recent one the series carries. */\r\n\tconst targetPct = useMemo(() => {\r\n\t\tfor (let i = history.points.length - 1; i >= 0; i -= 1) {\r\n\t\t\tconst t = history.points[i].targetAbandonRate;\r\n\t\t\tif (typeof t === \"number\") return t * 100;\r\n\t\t}\r\n\t\treturn null;\r\n\t}, [history.points]);\r\n\r\n\tconst axisProps = {\r\n\t\tstroke: COLORS.grey,\r\n\t\ttick: { fontSize: 10, fontFamily: FONT, fill: COLORS.grey },\r\n\t};\r\n\r\n\tconst xAxis = (\r\n\t\t<XAxis\r\n\t\t\tdataKey=\"t\"\r\n\t\t\ttype=\"number\"\r\n\t\t\tscale=\"time\"\r\n\t\t\tdomain={[\"dataMin\", \"dataMax\"]}\r\n\t\t\ttickFormatter={(v: number) => tickFormat(v, wideRange)}\r\n\t\t\t{...axisProps}\r\n\t\t/>\r\n\t);\r\n\r\n\tconst body = () => {\r\n\t\tif (loading && data.length === 0) {\r\n\t\t\treturn <Box sx={{ display: \"flex\", justifyContent: \"center\", py: 4 }}><CircularProgress size={22} sx={{ color: COLORS.teal }} /></Box>;\r\n\t\t}\r\n\t\tif (error) {\r\n\t\t\treturn <Typography sx={{ fontFamily: FONT, fontSize: \"0.78rem\", color: COLORS.red, py: 2 }}>{error}</Typography>;\r\n\t\t}\r\n\t\tif (data.length === 0) {\r\n\t\t\treturn (\r\n\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.78rem\", color: COLORS.grey, py: 2 }}>\r\n\t\t\t\t\tNothing dialed in this range — history only has points for the minutes a campaign was active.\r\n\t\t\t\t</Typography>\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t<Box sx={{ display: \"grid\", gridTemplateColumns: { xs: \"1fr\", lg: \"1fr 1fr\" }, gap: 2 }}>\r\n\t\t\t\t<Box>\r\n\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.78rem\", fontWeight: 700, color: \"#1a1a1a\", mb: 0.5 }}>\r\n\t\t\t\t\t\tAbandon rate\r\n\t\t\t\t\t</Typography>\r\n\t\t\t\t\t<ResponsiveContainer width=\"100%\" height={200}>\r\n\t\t\t\t\t\t<LineChart data={data} margin={{ top: 5, right: 12, bottom: 0, left: -12 }}>\r\n\t\t\t\t\t\t\t<CartesianGrid stroke={COLORS.grid} vertical={false} />\r\n\t\t\t\t\t\t\t{xAxis}\r\n\t\t\t\t\t\t\t<YAxis unit=\"%\" {...axisProps} />\r\n\t\t\t\t\t\t\t<Tooltip content={<ChartTooltip />} />\r\n\t\t\t\t\t\t\t{targetPct != null && (\r\n\t\t\t\t\t\t\t\t<ReferenceLine\r\n\t\t\t\t\t\t\t\t\ty={targetPct}\r\n\t\t\t\t\t\t\t\t\tstroke={COLORS.red}\r\n\t\t\t\t\t\t\t\t\tstrokeDasharray=\"4 4\"\r\n\t\t\t\t\t\t\t\t\tlabel={{ value: `target ${targetPct.toFixed(1)}%`, position: \"insideTopRight\", fontSize: 10, fill: COLORS.red, fontFamily: FONT }}\r\n\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t<Line\r\n\t\t\t\t\t\t\t\t// Straight, not curved: a smoothed line invents values between\r\n\t\t\t\t\t\t\t\t// buckets and can dip below zero or overshoot a peak.\r\n\t\t\t\t\t\t\t\ttype=\"linear\"\r\n\t\t\t\t\t\t\t\tdataKey=\"abandonPct\"\r\n\t\t\t\t\t\t\t\tname=\"Abandon rate\"\r\n\t\t\t\t\t\t\t\tstroke={COLORS.red}\r\n\t\t\t\t\t\t\t\tstrokeWidth={2}\r\n\t\t\t\t\t\t\t\tdot={false}\r\n\t\t\t\t\t\t\t\t// Gaps stay gaps: joining across them would invent data.\r\n\t\t\t\t\t\t\t\tconnectNulls={false}\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t</LineChart>\r\n\t\t\t\t\t</ResponsiveContainer>\r\n\t\t\t\t</Box>\r\n\r\n\t\t\t\t<Box>\r\n\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.78rem\", fontWeight: 700, color: \"#1a1a1a\", mb: 0.5 }}>\r\n\t\t\t\t\t\tDial ratio — applied vs recommended\r\n\t\t\t\t\t</Typography>\r\n\t\t\t\t\t<ResponsiveContainer width=\"100%\" height={200}>\r\n\t\t\t\t\t\t<LineChart data={data} margin={{ top: 5, right: 12, bottom: 0, left: -12 }}>\r\n\t\t\t\t\t\t\t<CartesianGrid stroke={COLORS.grid} vertical={false} />\r\n\t\t\t\t\t\t\t{xAxis}\r\n\t\t\t\t\t\t\t<YAxis {...axisProps} />\r\n\t\t\t\t\t\t\t<Tooltip content={<ChartTooltip />} />\r\n\t\t\t\t\t\t\t<Legend wrapperStyle={{ fontFamily: FONT, fontSize: \"0.7rem\" }} />\r\n\t\t\t\t\t\t\t{/* Stepped: a dial ratio is a decision that holds until the next one,\r\n\t\t\t\t\t\t\t    not a value that drifts smoothly between readings. */}\r\n\t\t\t\t\t\t\t<Line type=\"stepAfter\" dataKey=\"applied\" name=\"Applied\" stroke={COLORS.teal} strokeWidth={2} dot={false} connectNulls={false} />\r\n\t\t\t\t\t\t\t<Line type=\"stepAfter\" dataKey=\"recommended\" name=\"Adaptive would use\" stroke={COLORS.amber} strokeWidth={2} strokeDasharray=\"5 4\" dot={false} connectNulls={false} />\r\n\t\t\t\t\t\t</LineChart>\r\n\t\t\t\t\t</ResponsiveContainer>\r\n\t\t\t\t</Box>\r\n\t\t\t</Box>\r\n\t\t);\r\n\t};\r\n\r\n\treturn (\r\n\t\t<Paper\r\n\t\t\televation={0}\r\n\t\t\tsx={{ borderRadius: \"16px\", border: \"1px solid #e0e0e0\", boxShadow: \"0 1px 3px rgba(0,0,0,0.08)\", p: { xs: 1.5, sm: 2 } }}\r\n\t\t>\r\n\t\t\t<Box sx={{ display: \"flex\", alignItems: \"center\", gap: 1, flexWrap: \"wrap\", mb: 1.25 }}>\r\n\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.95rem\", fontWeight: 700, color: \"#1a1a1a\" }}>\r\n\t\t\t\t\tPacing history\r\n\t\t\t\t</Typography>\r\n\t\t\t\t{history.points.length > 0 && (\r\n\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.72rem\", color: COLORS.grey }}>\r\n\t\t\t\t\t\t{history.bucketMinutes} min buckets\r\n\t\t\t\t\t</Typography>\r\n\t\t\t\t)}\r\n\t\t\t\t<Box sx={{ flex: 1 }} />\r\n\t\t\t\t{HISTORY_RANGES.map((r) => (\r\n\t\t\t\t\t<SDKButton\r\n\t\t\t\t\t\tkey={r.key}\r\n\t\t\t\t\t\tvariant={range === r.key ? \"primary\" : \"outlined\"}\r\n\t\t\t\t\t\tonClick={() => setRange(r.key)}\r\n\t\t\t\t\t\tsx={{ height: 28, minWidth: 0, px: 1.25 }}\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t{r.label}\r\n\t\t\t\t\t</SDKButton>\r\n\t\t\t\t))}\r\n\t\t\t</Box>\r\n\t\t\t{body()}\r\n\t\t</Paper>\r\n\t);\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport dayjs from \"dayjs\";\r\nimport axiosInstance from \"../../services/axios\";\r\nimport { END_POINT } from \"../../services/endPoint\";\r\nimport type { PacingHistory } from \"./pacingTypes\";\r\n\r\n/** The ranges the guide asks for. Anything past 7 days is refused by the API. */\r\nexport const HISTORY_RANGES = [\r\n\t{ key: \"1h\", label: \"1 h\", hours: 1 },\r\n\t{ key: \"6h\", label: \"6 h\", hours: 6 },\r\n\t{ key: \"24h\", label: \"24 h\", hours: 24 },\r\n\t{ key: \"7d\", label: \"7 days\", hours: 24 * 7 },\r\n] as const;\r\n\r\nexport type HistoryRangeKey = (typeof HISTORY_RANGES)[number][\"key\"];\r\n\r\nconst EMPTY: PacingHistory = { bucketMinutes: 5, points: [] };\r\n\r\n/**\r\n * Pacing history for one campaign over the chosen range.\r\n *\r\n * Fetched on demand rather than polled: this is the record of what happened, and\r\n * it is read while reviewing rather than watched. The server downsamples, so the\r\n * series stays a few hundred points however wide the range.\r\n */\r\nexport function usePacingHistory(campaignId: number | null, range: HistoryRangeKey, enabled: boolean) {\r\n\tconst [history, setHistory] = useState<PacingHistory>(EMPTY);\r\n\tconst [loading, setLoading] = useState(false);\r\n\tconst [error, setError] = useState(\"\");\r\n\r\n\t/**\r\n\t * Which request is the current one. A wider range takes longer to answer, so\r\n\t * clicking 7 days and then 1 h can land the 7-day data last — under the 1-hour\r\n\t * label. Only the newest request is allowed to write.\r\n\t */\r\n\tconst requestId = useRef(0);\r\n\r\n\tconst load = useCallback(async () => {\r\n\t\tif (campaignId == null || !enabled) return;\r\n\t\tconst hours = HISTORY_RANGES.find((r) => r.key === range)?.hours ?? 1;\r\n\t\tconst id = ++requestId.current;\r\n\t\tsetLoading(true);\r\n\t\tsetError(\"\");\r\n\t\ttry {\r\n\t\t\tconst res = await axiosInstance.get(END_POINT.PACING_STATS_HISTORY(campaignId), {\r\n\t\t\t\tparams: {\r\n\t\t\t\t\t// Wall-clock, no timezone suffix — the convention the rest of these APIs use.\r\n\t\t\t\t\tstart: dayjs().subtract(hours, \"hour\").format(\"YYYY-MM-DDTHH:mm:ss\"),\r\n\t\t\t\t\tend: dayjs().format(\"YYYY-MM-DDTHH:mm:ss\"),\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t\tif (id !== requestId.current) return;\r\n\t\t\tconst body = res.data || {};\r\n\t\t\tsetHistory({\r\n\t\t\t\tbucketMinutes: body.bucketMinutes ?? 5,\r\n\t\t\t\tpoints: Array.isArray(body.points) ? body.points : [],\r\n\t\t\t});\r\n\t\t} catch (err: any) {\r\n\t\t\tif (id !== requestId.current) return;\r\n\t\t\tsetHistory(EMPTY);\r\n\t\t\tsetError(err?.response?.data?.message || \"Could not load pacing history\");\r\n\t\t} finally {\r\n\t\t\t// Only the newest request owns the spinner; an overtaken one clearing it\r\n\t\t\t// would hide that a fetch is still running.\r\n\t\t\tif (id === requestId.current) setLoading(false);\r\n\t\t}\r\n\t}, [campaignId, range, enabled]);\r\n\r\n\tuseEffect(() => { load(); }, [load]);\r\n\r\n\treturn { history, loading, error, refresh: load };\r\n}\r\n","import React, { useCallback, useEffect, useMemo, useState } from \"react\";\r\nimport {\r\n\tAlert, Box, Checkbox, Chip, CircularProgress, FormControl, FormControlLabel,\r\n\tInputLabel, MenuItem, Select, Switch, Tooltip, Typography,\r\n} from \"@mui/material\";\r\nimport type { MRT_Column, MRT_ColumnDef } from \"material-react-table\";\r\nimport DownloadIcon from \"@mui/icons-material/Download\";\r\nimport InfoOutlinedIcon from \"@mui/icons-material/InfoOutlined\";\r\nimport RefreshIcon from \"@mui/icons-material/Refresh\";\r\nimport dayjs, { Dayjs } from \"dayjs\";\r\nimport { LocalizationProvider } from \"@mui/x-date-pickers/LocalizationProvider\";\r\nimport { AdapterDayjs } from \"@mui/x-date-pickers/AdapterDayjs\";\r\n\r\nimport PageHeader from \"../common/components/PageHeader\";\r\nimport ReportFilterBar, { type QuickRange } from \"../common/components/ReportFilterBar\";\r\nimport MenuSearchField from \"../common/components/MenuSearchField\";\r\nimport AppButton from \"../common/components/AppButton\";\r\nimport AppTable from \"../common/components/AppTable\";\r\nimport { headCellSx } from \"../common/utils/tableStyles\";\r\nimport { SDKProvider } from \"../../components/SDKProvider\";\r\nimport { useSDKPermissions } from \"../../hooks/useSDKPermissions\";\r\nimport { SDK_PERMISSIONS } from \"../../permissions\";\r\nimport { useGetAuthorizationToken } from \"../../hooks/useGetAuthorizationToken\";\r\nimport RowDetailDrawer from \"./RowDetailDrawer\";\r\nimport { apiErrorMessage, exportAbandonRateReportToExcel, getAbandonRateReport, getCampaignOptions, getQueueKpis, getQueueOptions } from \"./useAbandonRateReport\";\r\nimport { ABANDON_REPORT_LIMITS, type AbandonRateRow, type CampaignOption, type QueueKpiRow, type QueueOption } from \"./types\";\r\n\r\n/* ═══════════════════════ STYLES (match the summary / CDR reports) ═══════════════════════ */\r\n\r\nconst FONT = \"'Inter', 'Segoe UI', 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif\";\r\n\r\nconst COLORS = {\r\n\tnavy: \"#0d2a56\",\r\n\tblue: \"#1565c8\",\r\n\tgreen: \"#0a9a62\",\r\n\tamber: \"#c47c00\",\r\n\tred: \"#cc2a2a\",\r\n\tteal: \"#1A5F6C\",\r\n} as const;\r\n\r\nconst FILTER_HEIGHT = 32;\r\n\r\nconst cellBaseSx = {\r\n\tfontFamily: FONT,\r\n\tfontSize: \"0.75rem\",\r\n\tborderBottom: \"1px solid #eef1f6\",\r\n\twhiteSpace: \"nowrap\",\r\n\t\"&&\": { px: 1.5, py: 1.25 },\r\n};\r\nconst headSx = {\r\n\t...(headCellSx as object),\r\n\t...cellBaseSx,\r\n\tfontSize: \"13px\",\r\n\tverticalAlign: \"middle\",\r\n\ttextAlign: \"center\",\r\n\tborderBottom: \"1px solid #dfe5ee\",\r\n\t\"& .Mui-TableHeadCell-Content\": { justifyContent: \"center\" },\r\n\t\"&&\": { px: 1.5, py: 0.6 },\r\n};\r\nconst tabularSx = { fontSize: \"0.75rem\", fontVariantNumeric: \"tabular-nums\" as const };\r\n\r\nconst PAGE_SIZE_OPTIONS = [10, 15, 25, 50, 100];\r\nconst DEFAULT_PAGE_SIZE = 10;\r\n\r\n/* ═══════════════════════ FORMATTING ═══════════════════════ */\r\n\r\nconst DASH = \"–\";\r\n\r\n/** Rates arrive as fractions. Null is \"no data\" and must never read as 0%. */\r\nfunction pct(value: number | null | undefined): string {\r\n\treturn typeof value === \"number\" ? `${(value * 100).toFixed(2)}%` : DASH;\r\n}\r\n\r\nfunction seconds(value: number | null | undefined): string {\r\n\treturn typeof value === \"number\" ? `${value.toFixed(1)}s` : DASH;\r\n}\r\n\r\nfunction count(value: number | null | undefined): string {\r\n\treturn typeof value === \"number\" ? value.toLocaleString() : DASH;\r\n}\r\n\r\n/** Estimates get two decimals — the difference being judged is often fractions of a percent. */\r\nfunction pct2(value: number | null | undefined): string {\r\n\treturn typeof value === \"number\" ? `${(value * 100).toFixed(2)}%` : DASH;\r\n}\r\n\r\nfunction ratio1(value: number | null | undefined): string {\r\n\treturn typeof value === \"number\" ? value.toFixed(1) : DASH;\r\n}\r\n\r\n/** 0.46 → \"+46%\", -0.2 → \"−20%\". Signed, because the direction is the point. */\r\nfunction signedPct(value: number | null | undefined): string {\r\n\tif (typeof value !== \"number\") return DASH;\r\n\tconst whole = Math.round(value * 100);\r\n\tif (whole > 0) return `+${whole}%`;\r\n\tif (whole < 0) return `−${Math.abs(whole)}%`;\r\n\treturn \"0%\";\r\n}\r\n\r\n/** A day adaptive actually drove has nothing to compare against. */\r\nconst isLiveAdaptive = (row: AbandonRateRow) =>\r\n\t(row.shadowMinutes ?? 0) === 0 && (row.adaptiveMinutes ?? 0) > 0;\r\n\r\n/**\r\n * Occupancy is a band, not a target: too low wastes staffed agents, too high\r\n * burns them out and lengthens wrap-up. Anything between the bands is ordinary.\r\n */\r\nfunction occupancyColor(value: number | null | undefined): string {\r\n\tif (typeof value !== \"number\") return \"#4a5568\";\r\n\tconst percent = value * 100;\r\n\tif (percent > 90) return COLORS.red;\r\n\tif (percent < 60) return COLORS.amber;\r\n\tif (percent >= 75 && percent <= 85) return COLORS.green;\r\n\treturn COLORS.navy;\r\n}\r\n\r\n/** Service level is a floor, so it only reads one way. */\r\nfunction slaColor(value: number | null | undefined): string {\r\n\tif (typeof value !== \"number\") return \"#4a5568\";\r\n\tconst percent = value * 100;\r\n\tif (percent >= 80) return COLORS.green;\r\n\tif (percent >= 70) return COLORS.amber;\r\n\treturn COLORS.red;\r\n}\r\n\r\nconst QUEUE_NOTE =\r\n\t\"Occupancy = talk + wrap-up time on this queue's calls ÷ staffed time of the queue's agents \"\r\n\t+ \"(breaks excluded). Calls are counted only in the queue they belong to. For queue details \"\r\n\t+ \"(answered, abandoned, hourly), see the Queue report.\";\r\n\r\n/** Header tooltips for the Queue KPIs table. The SLA one names the queue's own threshold. */\r\nconst QUEUE_TIPS = {\r\n\tday: \"The day these figures cover (00:00–24:00).\",\r\n\tqueueName: \"The inbound queue. Each call is counted only in the queue it belongs to: inbound calls \"\r\n\t\t+ \"in the queue that received them, campaign calls in the campaign's queue.\",\r\n\tagents: \"Number of agents in this queue who were logged in and not on break at some point during \"\r\n\t\t+ \"the day. An agent in several queues is counted in each.\",\r\n\toccupancy: \"Share of the agents' staffed time spent on this queue's calls (talk + wrap-up). Staffed \"\r\n\t\t+ \"time is logged-in time excluding breaks. Healthy range: 75–85%. Below 60%, agents are mostly \"\r\n\t\t+ \"waiting; above 90%, there is a risk of burnout.\",\r\n\tinboundTalkShare: \"Share of staffed time spent talking on inbound calls for this queue.\",\r\n\tcampaignTalkShare: \"Share of staffed time spent talking on campaign (outbound) calls for this queue's campaigns.\",\r\n\tinboundOffered: \"Inbound calls that entered this queue and finished (answered, abandoned, timed out or transferred).\",\r\n\tinboundSla: (slaSeconds: number | null) =>\r\n\t\t`Share of inbound calls answered within ${slaSeconds != null ? `${slaSeconds} seconds` : \"the queue's SLA threshold\"} `\r\n\t\t+ \"of entering the queue. Calls that were abandoned or timed out count as missed. Target: 80% or more. \"\r\n\t\t+ \"Shows \\\"–\\\" when no inbound calls arrived.\",\r\n};\r\n\r\nconst SHADOW_TOOLTIP =\r\n\t\"Estimates. They assume nobody frees up while answered customers wait, so they lean high. \"\r\n\t+ \"Compare 'Est. abandon (used)' with the real Abandon % on the same row: close means the \"\r\n\t+ \"estimate is reliable, much higher means it is pessimistic.\";\r\n\r\n/* ═══════════════════════ MAIN CONTENT ═══════════════════════ */\r\n\r\ninterface AbandonRateReportContentProps {\r\n\t/**\r\n\t * Rendered as a tab of another page rather than as a page of its own: the\r\n\t * page header belongs to the host, so the title is dropped and the Export\r\n\t * button moves down into the filter bar.\r\n\t */\r\n\tembedded?: boolean;\r\n\t/** Which table to show under the shared filters: campaign compliance, or the per-queue KPIs. */\r\n\tview?: \"compliance\" | \"queues\";\r\n}\r\n\r\nexport function AbandonRateReportContent({ embedded = false, view = \"compliance\" }: AbandonRateReportContentProps) {\r\n\tconst isQueues = view === \"queues\";\r\n\t// SDK initialization finishes after this route can mount — wait for the token so\r\n\t// the first request goes out authenticated.\r\n\tconst authorizationToken = useGetAuthorizationToken();\r\n\tconst { hasPermission } = useSDKPermissions();\r\n\tconst canExport = hasPermission(SDK_PERMISSIONS.REPORTS_PREDICTIVE_PACING_EXPORT);\r\n\r\n\tconst [startDate, setStartDate] = useState<Dayjs | null>(dayjs().startOf(\"month\"));\r\n\tconst [endDate, setEndDate] = useState<Dayjs | null>(dayjs());\r\n\tconst [quickRange, setQuickRange] = useState(\"month\");\r\n\r\n\tconst [campaigns, setCampaigns] = useState<CampaignOption[]>([]);\r\n\tconst [selectedCampaigns, setSelectedCampaigns] = useState<CampaignOption[]>([]);\r\n\tconst [campaignSearch, setCampaignSearch] = useState(\"\");\r\n\r\n\tconst [rows, setRows] = useState<AbandonRateRow[]>([]);\r\n\tconst [pagination, setPagination] = useState({ pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE });\r\n\tconst [loading, setLoading] = useState(false);\r\n\tconst [exporting, setExporting] = useState(false);\r\n\tconst [error, setError] = useState(\"\");\r\n\r\n\t/* Queue KPIs — the same days read per queue, shown in their own view. */\r\n\tconst [queueRows, setQueueRows] = useState<QueueKpiRow[]>([]);\r\n\tconst [queuePagination, setQueuePagination] = useState({ pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE });\r\n\tconst [queueOptions, setQueueOptions] = useState<QueueOption[]>([]);\r\n\tconst [selectedQueueIds, setSelectedQueueIds] = useState<number[]>([]);\r\n\tconst [queueSearch, setQueueSearch] = useState(\"\");\r\n\r\n\tuseEffect(() => {\r\n\t\tif (!authorizationToken) return;\r\n\t\tgetCampaignOptions().then(setCampaigns).catch(() => setCampaigns([]));\r\n\t}, [authorizationToken]);\r\n\r\n\t/** Null when the filters are usable, otherwise why they aren't. */\r\n\tconst filterError = useMemo(() => {\r\n\t\tif (!startDate || !endDate) return \"Pick a start and end date\";\r\n\t\tif (startDate.isAfter(endDate)) return \"Start date must be before end date\";\r\n\t\tif (endDate.diff(startDate, \"day\") + 1 > ABANDON_REPORT_LIMITS.maxDays) {\r\n\t\t\treturn `The range cannot be longer than ${ABANDON_REPORT_LIMITS.maxDays} days`;\r\n\t\t}\r\n\t\tif (selectedCampaigns.length > ABANDON_REPORT_LIMITS.maxCampaigns) {\r\n\t\t\treturn `Pick at most ${ABANDON_REPORT_LIMITS.maxCampaigns} campaigns`;\r\n\t\t}\r\n\t\tif (isQueues && selectedQueueIds.length > ABANDON_REPORT_LIMITS.maxQueues) {\r\n\t\t\treturn `Pick at most ${ABANDON_REPORT_LIMITS.maxQueues} queues`;\r\n\t\t}\r\n\t\treturn null;\r\n\t}, [startDate, endDate, selectedCampaigns.length, isQueues, selectedQueueIds.length]);\r\n\r\n\tconst buildParams = useCallback(() => ({\r\n\t\tstartDate: startDate!.format(\"YYYY-MM-DD\"),\r\n\t\tendDate: endDate!.format(\"YYYY-MM-DD\"),\r\n\t\t// Every campaign selected is the same as none — let the API cover them all.\r\n\t\tcampaignIds: selectedCampaigns.length > 0 && selectedCampaigns.length < campaigns.length\r\n\t\t\t? selectedCampaigns.map((c) => c.id)\r\n\t\t\t: undefined,\r\n\t\t// Same for queues — \"All queues\" leaves the param out.\r\n\t\tqueueIds: isQueues && selectedQueueIds.length > 0 && selectedQueueIds.length < queueOptions.length\r\n\t\t\t? selectedQueueIds\r\n\t\t\t: undefined,\r\n\t}), [startDate, endDate, selectedCampaigns, campaigns.length, isQueues, selectedQueueIds, queueOptions.length]);\r\n\r\n\t/*\r\n\t * Queue options follow the campaign filter, so the list only offers queues the\r\n\t * chosen campaigns dial through. A selected queue that drops out of the list\r\n\t * is dropped from the selection too.\r\n\t */\r\n\tconst optionCampaignKey = selectedCampaigns.length > 0 && selectedCampaigns.length < campaigns.length\r\n\t\t? selectedCampaigns.map((c) => c.id).join(\",\")\r\n\t\t: \"\";\r\n\tuseEffect(() => {\r\n\t\tif (!authorizationToken || !isQueues) return;\r\n\t\tlet cancelled = false;\r\n\t\tgetQueueOptions(optionCampaignKey ? optionCampaignKey.split(\",\").map(Number) : undefined)\r\n\t\t\t.then((opts) => {\r\n\t\t\t\tif (cancelled) return;\r\n\t\t\t\tsetQueueOptions(opts);\r\n\t\t\t\tsetSelectedQueueIds((ids) => {\r\n\t\t\t\t\tconst kept = ids.filter((id) => opts.some((o) => o.queueId === id));\r\n\t\t\t\t\treturn kept.length === ids.length ? ids : kept;\r\n\t\t\t\t});\r\n\t\t\t})\r\n\t\t\t.catch(async (e) => {\r\n\t\t\t\tif (cancelled) return;\r\n\t\t\t\tsetQueueOptions([]);\r\n\t\t\t\tsetError(await apiErrorMessage(e, \"Could not load the queue list\"));\r\n\t\t\t});\r\n\t\treturn () => { cancelled = true; };\r\n\t}, [authorizationToken, isQueues, optionCampaignKey]);\r\n\r\n\tconst fetchReport = useCallback(async () => {\r\n\t\tif (!authorizationToken) return;\r\n\t\tif (filterError) {\r\n\t\t\tsetError(filterError);\r\n\t\t\tsetRows([]);\r\n\t\t\tsetQueueRows([]);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tsetLoading(true);\r\n\t\tsetError(\"\");\r\n\t\tconst params = buildParams();\r\n\t\t// Each view loads only the table it shows.\r\n\t\tif (isQueues) {\r\n\t\t\ttry {\r\n\t\t\t\tsetQueueRows(await getQueueKpis(params));\r\n\t\t\t} catch (e) {\r\n\t\t\t\tsetQueueRows([]);\r\n\t\t\t\tsetError(await apiErrorMessage(e, \"Could not load the queue KPIs\"));\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\ttry {\r\n\t\t\t\tsetRows(await getAbandonRateReport(params));\r\n\t\t\t} catch (e) {\r\n\t\t\t\tsetRows([]);\r\n\t\t\t\tsetError(await apiErrorMessage(e, \"Failed to fetch the abandon rate report\"));\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tsetLoading(false);\r\n\t}, [authorizationToken, filterError, buildParams, isQueues]);\r\n\r\n\tuseEffect(() => { fetchReport(); }, [fetchReport]);\r\n\r\n\t/* Any filter change goes back to page 1, in the same update as the filter. */\r\n\tconst resetPage = () => {\r\n\t\tsetPagination((p) => (p.pageIndex === 0 ? p : { ...p, pageIndex: 0 }));\r\n\t\tsetQueuePagination((p) => (p.pageIndex === 0 ? p : { ...p, pageIndex: 0 }));\r\n\t};\r\n\tconst handleStartChange = (v: Dayjs | null) => { setStartDate(v); resetPage(); };\r\n\tconst handleEndChange = (v: Dayjs | null) => { setEndDate(v); resetPage(); };\r\n\r\n\tconst handleExport = async () => {\r\n\t\tif (filterError) { setError(filterError); return; }\r\n\t\tsetExporting(true);\r\n\t\ttry {\r\n\t\t\tawait exportAbandonRateReportToExcel(buildParams());\r\n\t\t} catch (e) {\r\n\t\t\tsetError(await apiErrorMessage(e, \"Failed to export the abandon rate report\"));\r\n\t\t} finally {\r\n\t\t\tsetExporting(false);\r\n\t\t}\r\n\t};\r\n\r\n\tconst breachedDays = useMemo(() => rows.filter((r) => r.targetBreached).length, [rows]);\r\n\r\n\t/* ── Shadow comparison ── */\r\n\tconst shadowRows = useMemo(() => rows.filter((r) => (r.shadowMinutes ?? 0) > 0), [rows]);\r\n\tconst shadowWithinTarget = useMemo(\r\n\t\t() => shadowRows.filter((r) => r.adaptiveWithinTarget === true).length,\r\n\t\t[shadowRows],\r\n\t);\r\n\r\n\t// Off by default, but a report containing shadow days is one someone opened to\r\n\t// read them — so show the group rather than making them find the switch.\r\n\t/** The row whose detail drawer is open. */\r\n\tconst [detailRow, setDetailRow] = useState<AbandonRateRow | null>(null);\r\n\r\n\tconst [showShadow, setShowShadow] = useState(false);\r\n\tconst [shadowTouched, setShadowTouched] = useState(false);\r\n\tuseEffect(() => {\r\n\t\tif (shadowTouched) return;\r\n\t\tsetShowShadow(shadowRows.length > 0);\r\n\t}, [shadowRows.length, shadowTouched]);\r\n\r\n\t/* ── Campaign filter ── */\r\n\tconst campaignQuery = campaignSearch.trim().toLowerCase();\r\n\tconst visibleCampaigns = campaignQuery\r\n\t\t? campaigns.filter((c) => c.name.toLowerCase().includes(campaignQuery))\r\n\t\t: campaigns;\r\n\tconst allVisibleSelected = visibleCampaigns.length > 0\r\n\t\t&& visibleCampaigns.every((c) => selectedCampaigns.some((sc) => sc.id === c.id));\r\n\r\n\tconst campaignFilter = (\r\n\t\t<FormControl size=\"small\" sx={{ minWidth: 150 }}>\r\n\t\t\t<InputLabel sx={{ fontSize: \"0.76rem\" }}>All Campaigns</InputLabel>\r\n\t\t\t<Select\r\n\t\t\t\tmultiple\r\n\t\t\t\tlabel=\"All Campaigns\"\r\n\t\t\t\tsx={{ fontSize: \"0.76rem\", height: FILTER_HEIGHT, borderRadius: \"6px\" }}\r\n\t\t\t\tMenuProps={{\r\n\t\t\t\t\tvariant: \"menu\",\r\n\t\t\t\t\tautoFocus: false,\r\n\t\t\t\t\tPaperProps: { sx: { maxHeight: 280, \"& .MuiMenuItem-root\": { fontSize: \"0.76rem\", minHeight: 32, py: 0.3 } } },\r\n\t\t\t\t}}\r\n\t\t\t\tvalue={selectedCampaigns.map((c) => c.id)}\r\n\t\t\t\tonClose={() => setCampaignSearch(\"\")}\r\n\t\t\t\tonChange={(e) => {\r\n\t\t\t\t\tconst ids = e.target.value as number[];\r\n\t\t\t\t\t// -1 is the \"select all visible\" row, so it toggles the filtered set.\r\n\t\t\t\t\tif (ids.includes(-1)) {\r\n\t\t\t\t\t\tsetSelectedCampaigns(allVisibleSelected\r\n\t\t\t\t\t\t\t? selectedCampaigns.filter((sc) => !visibleCampaigns.some((c) => c.id === sc.id))\r\n\t\t\t\t\t\t\t: [...selectedCampaigns, ...visibleCampaigns.filter((c) => !selectedCampaigns.some((sc) => sc.id === c.id))]);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tsetSelectedCampaigns(campaigns.filter((c) => ids.includes(c.id)));\r\n\t\t\t\t\t}\r\n\t\t\t\t\tresetPage();\r\n\t\t\t\t}}\r\n\t\t\t\trenderValue={(selected) => {\r\n\t\t\t\t\tconst n = (selected as number[]).length;\r\n\t\t\t\t\tif (n === 0 || n === campaigns.length) return \"All Campaigns\";\r\n\t\t\t\t\treturn `${n} Campaign(s)`;\r\n\t\t\t\t}}\r\n\t\t\t>\r\n\t\t\t\t<MenuSearchField value={campaignSearch} onChange={setCampaignSearch} placeholder=\"Search campaign...\" />\r\n\t\t\t\t{visibleCampaigns.length > 0 && (\r\n\t\t\t\t\t<MenuItem value={-1}>\r\n\t\t\t\t\t\t<Checkbox\r\n\t\t\t\t\t\t\tchecked={allVisibleSelected}\r\n\t\t\t\t\t\t\tindeterminate={visibleCampaigns.some((c) => selectedCampaigns.some((sc) => sc.id === c.id)) && !allVisibleSelected}\r\n\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t\tSelect all\r\n\t\t\t\t\t</MenuItem>\r\n\t\t\t\t)}\r\n\t\t\t\t{visibleCampaigns.map((c) => (\r\n\t\t\t\t\t<MenuItem key={c.id} value={c.id}>\r\n\t\t\t\t\t\t<Checkbox checked={selectedCampaigns.some((sc) => sc.id === c.id)} size=\"small\" />\r\n\t\t\t\t\t\t{c.name}\r\n\t\t\t\t\t</MenuItem>\r\n\t\t\t\t))}\r\n\t\t\t</Select>\r\n\t\t</FormControl>\r\n\t);\r\n\r\n\t/* ── Queue filter (Queue KPIs view only) ── */\r\n\tconst queueQuery = queueSearch.trim().toLowerCase();\r\n\tconst visibleQueues = queueQuery\r\n\t\t? queueOptions.filter((q) => q.queueName.toLowerCase().includes(queueQuery))\r\n\t\t: queueOptions;\r\n\tconst allVisibleQueuesSelected = visibleQueues.length > 0\r\n\t\t&& visibleQueues.every((q) => selectedQueueIds.includes(q.queueId));\r\n\r\n\tconst queueFilter = isQueues && (\r\n\t\t<FormControl size=\"small\" sx={{ minWidth: 140 }}>\r\n\t\t\t<InputLabel sx={{ fontSize: \"0.76rem\" }}>All Queues</InputLabel>\r\n\t\t\t<Select\r\n\t\t\t\tmultiple\r\n\t\t\t\tlabel=\"All Queues\"\r\n\t\t\t\tsx={{ fontSize: \"0.76rem\", height: FILTER_HEIGHT, borderRadius: \"6px\" }}\r\n\t\t\t\tMenuProps={{\r\n\t\t\t\t\tvariant: \"menu\",\r\n\t\t\t\t\tautoFocus: false,\r\n\t\t\t\t\tPaperProps: { sx: { maxHeight: 280, \"& .MuiMenuItem-root\": { fontSize: \"0.76rem\", minHeight: 32, py: 0.3 } } },\r\n\t\t\t\t}}\r\n\t\t\t\tvalue={selectedQueueIds}\r\n\t\t\t\tonClose={() => setQueueSearch(\"\")}\r\n\t\t\t\tonChange={(e) => {\r\n\t\t\t\t\tconst ids = e.target.value as number[];\r\n\t\t\t\t\t// -1 is the \"select all visible\" row, so it toggles the filtered set.\r\n\t\t\t\t\tif (ids.includes(-1)) {\r\n\t\t\t\t\t\tconst visibleIds = visibleQueues.map((q) => q.queueId);\r\n\t\t\t\t\t\tsetSelectedQueueIds(allVisibleQueuesSelected\r\n\t\t\t\t\t\t\t? selectedQueueIds.filter((id) => !visibleIds.includes(id))\r\n\t\t\t\t\t\t\t: [...selectedQueueIds, ...visibleIds.filter((id) => !selectedQueueIds.includes(id))]);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tsetSelectedQueueIds(ids);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tresetPage();\r\n\t\t\t\t}}\r\n\t\t\t\trenderValue={(selected) => {\r\n\t\t\t\t\tconst n = (selected as number[]).length;\r\n\t\t\t\t\tif (n === 0 || n === queueOptions.length) return \"All Queues\";\r\n\t\t\t\t\treturn `${n} Queue(s)`;\r\n\t\t\t\t}}\r\n\t\t\t>\r\n\t\t\t\t<MenuSearchField value={queueSearch} onChange={setQueueSearch} placeholder=\"Search queue...\" />\r\n\t\t\t\t{visibleQueues.length > 0 && (\r\n\t\t\t\t\t<MenuItem value={-1}>\r\n\t\t\t\t\t\t<Checkbox\r\n\t\t\t\t\t\t\tchecked={allVisibleQueuesSelected}\r\n\t\t\t\t\t\t\tindeterminate={visibleQueues.some((q) => selectedQueueIds.includes(q.queueId)) && !allVisibleQueuesSelected}\r\n\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t\tSelect all\r\n\t\t\t\t\t</MenuItem>\r\n\t\t\t\t)}\r\n\t\t\t\t{visibleQueues.map((q) => (\r\n\t\t\t\t\t<MenuItem key={q.queueId} value={q.queueId}>\r\n\t\t\t\t\t\t<Checkbox checked={selectedQueueIds.includes(q.queueId)} size=\"small\" />\r\n\t\t\t\t\t\t{q.queueName}\r\n\t\t\t\t\t</MenuItem>\r\n\t\t\t\t))}\r\n\t\t\t</Select>\r\n\t\t</FormControl>\r\n\t);\r\n\r\n\t/* ── Columns ── */\r\n\tconst columns = useMemo<MRT_ColumnDef<AbandonRateRow>[]>(() => {\r\n\t\tconst cell = (sx: Record<string, unknown> = {}) => ({\r\n\t\t\tmuiTableHeadCellProps: { align: \"center\" as const, sx: headSx },\r\n\t\t\tmuiTableBodyCellProps: { align: \"center\" as const, sx: { ...cellBaseSx, textAlign: \"center\", ...sx } },\r\n\t\t});\r\n\t\tconst countCol = (id: keyof AbandonRateRow, header: string, color: string = COLORS.navy): MRT_ColumnDef<AbandonRateRow> => ({\r\n\t\t\tid,\r\n\t\t\theader,\r\n\t\t\taccessorFn: (r) => r[id],\r\n\t\t\tCell: ({ row }) => count(row.original[id] as number),\r\n\t\t\t...cell({ ...tabularSx, fontWeight: 600, color }),\r\n\t\t});\r\n\r\n\t\tconst base: MRT_ColumnDef<AbandonRateRow>[] = [\r\n\t\t\t{\r\n\t\t\t\tid: \"day\",\r\n\t\t\t\theader: \"Date\",\r\n\t\t\t\taccessorFn: (r) => r.day,\r\n\t\t\t\tCell: ({ row }) => (row.original.day ? dayjs(row.original.day).format(\"D MMM YYYY\") : DASH),\r\n\t\t\t\t...cell({ fontSize: \"0.73rem\", color: \"#4a5568\" }),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"campaignName\",\r\n\t\t\t\theader: \"Campaign\",\r\n\t\t\t\taccessorFn: (r) => r.campaignName,\r\n\t\t\t\tCell: ({ row }) => row.original.campaignName || DASH,\r\n\t\t\t\t...cell({ fontSize: \"0.73rem\", color: \"#4a5568\", whiteSpace: \"normal\", textAlign: \"left\" }),\r\n\t\t\t\tmuiTableBodyCellProps: { align: \"left\" as const, sx: { ...cellBaseSx, fontSize: \"0.73rem\", color: \"#4a5568\", whiteSpace: \"normal\" } },\r\n\t\t\t},\r\n\t\t\tcountCol(\"dialed\", \"Dialed\"),\r\n\t\t\tcountCol(\"answered\", \"Answered\", COLORS.green),\r\n\t\t\tcountCol(\"agentConnected\", \"Connected\", COLORS.green),\r\n\t\t\tcountCol(\"abandoned\", \"Abandoned\", COLORS.red),\r\n\t\t\tcountCol(\"abandonedWaitTimeout\", \"No Agent Free\", COLORS.red),\r\n\t\t\tcountCol(\"abandonedCustomerHangup\", \"Customer Hung Up\", COLORS.amber),\r\n\t\t\t{\r\n\t\t\t\tid: \"answerRate\",\r\n\t\t\t\theader: \"Answer %\",\r\n\t\t\t\taccessorFn: (r) => r.answerRate,\r\n\t\t\t\tCell: ({ row }) => pct(row.original.answerRate),\r\n\t\t\t\t...cell({ ...tabularSx, fontWeight: 600, color: COLORS.green }),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"abandonRate\",\r\n\t\t\t\theader: \"Abandon %\",\r\n\t\t\t\taccessorFn: (r) => r.abandonRate,\r\n\t\t\t\tCell: ({ row }) => (\r\n\t\t\t\t\t<Box sx={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 0.3 }}>\r\n\t\t\t\t\t\t<span>{pct(row.original.abandonRate)}</span>\r\n\t\t\t\t\t\t{row.original.targetBreached && (\r\n\t\t\t\t\t\t\t<Chip\r\n\t\t\t\t\t\t\t\tlabel=\"Over target\"\r\n\t\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t\t\tsx={{ height: 18, fontSize: \"0.65rem\", fontWeight: 700, backgroundColor: \"#fdecea\", color: COLORS.red }}\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t)}\r\n\t\t\t\t\t</Box>\r\n\t\t\t\t),\r\n\t\t\t\t...cell({ ...tabularSx, fontWeight: 700, color: COLORS.red }),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"targetAbandonRate\",\r\n\t\t\t\theader: \"Target %\",\r\n\t\t\t\taccessorFn: (r) => r.targetAbandonRate,\r\n\t\t\t\tCell: ({ row }) => pct(row.original.targetAbandonRate),\r\n\t\t\t\t...cell({ ...tabularSx, fontWeight: 500, color: \"#4a5568\" }),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"avgTimeToAgentSeconds\",\r\n\t\t\t\theader: \"Avg Time To Agent\",\r\n\t\t\t\taccessorFn: (r) => r.avgTimeToAgentSeconds,\r\n\t\t\t\tCell: ({ row }) => seconds(row.original.avgTimeToAgentSeconds),\r\n\t\t\t\t...cell({ ...tabularSx, fontWeight: 500, color: COLORS.blue }),\r\n\t\t\t},\r\n\t\t];\r\n\r\n\t\tif (!showShadow) return base;\r\n\r\n\t\t/** A comparison cell: dashes out on a day adaptive actually drove. */\r\n\t\tconst compare = (\r\n\t\t\tid: keyof AbandonRateRow,\r\n\t\t\theader: string,\r\n\t\t\trender: (row: AbandonRateRow) => string,\r\n\t\t\tsx: Record<string, unknown>,\r\n\t\t): MRT_ColumnDef<AbandonRateRow> => ({\r\n\t\t\tid,\r\n\t\t\theader,\r\n\t\t\taccessorFn: (r) => r[id],\r\n\t\t\tCell: ({ row }) => (isLiveAdaptive(row.original) ? DASH : render(row.original)),\r\n\t\t\t...cell(sx),\r\n\t\t});\r\n\r\n\t\treturn [\r\n\t\t\t...base,\r\n\t\t\t{\r\n\t\t\t\tid: \"shadowComparison\",\r\n\t\t\t\theader: \"Shadow comparison\",\r\n\t\t\t\tmuiTableHeadCellProps: {\r\n\t\t\t\t\talign: \"center\" as const,\r\n\t\t\t\t\tsx: { ...headSx, backgroundColor: \"#e6ecf2\", color: COLORS.teal, fontWeight: 700 },\r\n\t\t\t\t},\r\n\t\t\t\t// The tooltip is the honest caveat: these numbers lean high, and the row's\r\n\t\t\t\t// own Abandon % is the yardstick for how much.\r\n\t\t\t\tHeader: () => (\r\n\t\t\t\t\t<Tooltip arrow title={SHADOW_TOOLTIP}>\r\n\t\t\t\t\t\t<Box sx={{ display: \"inline-flex\", alignItems: \"center\", gap: 0.4, cursor: \"default\" }}>\r\n\t\t\t\t\t\t\tShadow comparison\r\n\t\t\t\t\t\t\t<InfoOutlinedIcon sx={{ fontSize: 14 }} />\r\n\t\t\t\t\t\t</Box>\r\n\t\t\t\t\t</Tooltip>\r\n\t\t\t\t),\r\n\t\t\t\tcolumns: [\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tid: \"shadowMinutes\",\r\n\t\t\t\t\t\theader: \"Shadow min\",\r\n\t\t\t\t\t\taccessorFn: (r) => r.shadowMinutes,\r\n\t\t\t\t\t\tCell: ({ row }) => (\r\n\t\t\t\t\t\t\tisLiveAdaptive(row.original)\r\n\t\t\t\t\t\t\t\t? <Chip\r\n\t\t\t\t\t\t\t\t\tlabel=\"Live adaptive\"\r\n\t\t\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t\t\t\tsx={{ height: 18, fontSize: \"0.62rem\", fontWeight: 700, backgroundColor: \"#e3f3e8\", color: COLORS.green }}\r\n\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t\t: count(row.original.shadowMinutes)\r\n\t\t\t\t\t\t),\r\n\t\t\t\t\t\t...cell({ ...tabularSx, fontWeight: 600, color: \"#4a5568\" }),\r\n\t\t\t\t\t},\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tid: \"adaptiveMinutes\",\r\n\t\t\t\t\t\theader: \"Adaptive min\",\r\n\t\t\t\t\t\taccessorFn: (r) => r.adaptiveMinutes,\r\n\t\t\t\t\t\tCell: ({ row }) => count(row.original.adaptiveMinutes),\r\n\t\t\t\t\t\t...cell({ ...tabularSx, fontWeight: 600, color: \"#4a5568\" }),\r\n\t\t\t\t\t},\r\n\t\t\t\t\tcompare(\"avgAppliedRatio\", \"Ratio Used\", (r) => ratio1(r.avgAppliedRatio),\r\n\t\t\t\t\t\t{ ...tabularSx, fontWeight: 600, color: COLORS.teal }),\r\n\t\t\t\t\tcompare(\"avgRecommendedRatio\", \"Ratio Recommended\", (r) => ratio1(r.avgRecommendedRatio),\r\n\t\t\t\t\t\t{ ...tabularSx, fontWeight: 600, color: COLORS.amber }),\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tid: \"adaptiveCallChange\",\r\n\t\t\t\t\t\theader: \"Call Change\",\r\n\t\t\t\t\t\taccessorFn: (r) => r.adaptiveCallChange,\r\n\t\t\t\t\t\tCell: ({ row }) => (isLiveAdaptive(row.original) ? DASH : signedPct(row.original.adaptiveCallChange)),\r\n\t\t\t\t\t\tmuiTableHeadCellProps: { align: \"center\" as const, sx: headSx },\r\n\t\t\t\t\t\tmuiTableBodyCellProps: ({ row }) => ({\r\n\t\t\t\t\t\t\talign: \"center\" as const,\r\n\t\t\t\t\t\t\tsx: {\r\n\t\t\t\t\t\t\t\t...cellBaseSx,\r\n\t\t\t\t\t\t\t\t...tabularSx,\r\n\t\t\t\t\t\t\t\ttextAlign: \"center\",\r\n\t\t\t\t\t\t\t\tfontWeight: 700,\r\n\t\t\t\t\t\t\t\t// More calls is the upside adaptive is being judged on; fewer is not.\r\n\t\t\t\t\t\t\t\tcolor: (row.original.adaptiveCallChange ?? 0) >= 0 ? COLORS.green : COLORS.amber,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t}),\r\n\t\t\t\t\t},\r\n\t\t\t\t\tcompare(\"estAbandonRateApplied\", \"Est. Abandon (Used)\", (r) => pct2(r.estAbandonRateApplied),\r\n\t\t\t\t\t\t{ ...tabularSx, fontWeight: 500, color: \"#4a5568\" }),\r\n\t\t\t\t\tcompare(\"estAbandonRateAdaptive\", \"Est. Abandon (Adaptive)\", (r) => pct2(r.estAbandonRateAdaptive),\r\n\t\t\t\t\t\t{ ...tabularSx, fontWeight: 600, color: \"#4a5568\" }),\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tid: \"adaptiveWithinTarget\",\r\n\t\t\t\t\t\theader: \"Verdict\",\r\n\t\t\t\t\t\taccessorFn: (r) => r.adaptiveWithinTarget,\r\n\t\t\t\t\t\tCell: ({ row }) => {\r\n\t\t\t\t\t\t\tconst verdict = row.original.adaptiveWithinTarget;\r\n\t\t\t\t\t\t\tif (isLiveAdaptive(row.original) || verdict == null) return DASH;\r\n\t\t\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\t\t<Chip\r\n\t\t\t\t\t\t\t\t\tlabel={verdict ? \"Within target\" : \"Would exceed\"}\r\n\t\t\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t\t\t\tsx={{\r\n\t\t\t\t\t\t\t\t\t\theight: 18, fontSize: \"0.65rem\", fontWeight: 700,\r\n\t\t\t\t\t\t\t\t\t\tbackgroundColor: verdict ? \"#e3f3e8\" : \"#fdecea\",\r\n\t\t\t\t\t\t\t\t\t\tcolor: verdict ? COLORS.green : COLORS.red,\r\n\t\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\t...cell({}),\r\n\t\t\t\t\t},\r\n\t\t\t\t],\r\n\t\t\t},\r\n\t\t];\r\n\t\t// Rebuilds when the toggle moves — the column set itself depends on it.\r\n\t}, [showShadow]);\r\n\r\n\t/* ── Queue KPI columns ── */\r\n\r\n\t/**\r\n\t * The SLA threshold is the queue's own, so it goes in the header rather than\r\n\t * being hard-coded. Queues can be configured differently, and a single number\r\n\t * over a mixed table would be a lie — so it is only named when they agree.\r\n\t */\r\n\tconst slaSeconds = useMemo(() => {\r\n\t\tconst values = new Set(\r\n\t\t\tqueueRows.map((r) => r.slaSeconds).filter((s): s is number => typeof s === \"number\"),\r\n\t\t);\r\n\t\treturn values.size === 1 ? [...values][0] : null;\r\n\t}, [queueRows]);\r\n\r\n\tconst queueColumns = useMemo<MRT_ColumnDef<QueueKpiRow>[]>(() => {\r\n\t\tconst cell = (sx: Record<string, unknown> = {}, tip?: string) => ({\r\n\t\t\tmuiTableHeadCellProps: { align: \"center\" as const, sx: headSx },\r\n\t\t\tmuiTableBodyCellProps: { align: \"center\" as const, sx: { ...cellBaseSx, textAlign: \"center\", ...sx } },\r\n\t\t\t...(tip && {\r\n\t\t\t\tHeader: ({ column }: { column: MRT_Column<QueueKpiRow> }) => (\r\n\t\t\t\t\t<Tooltip arrow title={tip}>\r\n\t\t\t\t\t\t<Box sx={{ display: \"inline-flex\", alignItems: \"center\", gap: 0.4, cursor: \"default\" }}>\r\n\t\t\t\t\t\t\t{column.columnDef.header}\r\n\t\t\t\t\t\t\t<InfoOutlinedIcon sx={{ fontSize: 14 }} />\r\n\t\t\t\t\t\t</Box>\r\n\t\t\t\t\t</Tooltip>\r\n\t\t\t\t),\r\n\t\t\t}),\r\n\t\t});\r\n\r\n\t\treturn [\r\n\t\t\t{\r\n\t\t\t\tid: \"day\",\r\n\t\t\t\theader: \"Date\",\r\n\t\t\t\taccessorFn: (r) => r.day,\r\n\t\t\t\tCell: ({ row }) => (row.original.day ? dayjs(row.original.day).format(\"D MMM YYYY\") : DASH),\r\n\t\t\t\t...cell({ fontSize: \"0.73rem\", color: \"#4a5568\" }, QUEUE_TIPS.day),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"queueName\",\r\n\t\t\t\theader: \"Queue\",\r\n\t\t\t\taccessorFn: (r) => r.queueName,\r\n\t\t\t\tCell: ({ row }) => row.original.queueName || DASH,\r\n\t\t\t\t...cell({ fontSize: \"0.73rem\", color: \"#4a5568\", whiteSpace: \"normal\" }, QUEUE_TIPS.queueName),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"agents\",\r\n\t\t\t\theader: \"Agents\",\r\n\t\t\t\taccessorFn: (r) => r.agents,\r\n\t\t\t\tCell: ({ row }) => count(row.original.agents),\r\n\t\t\t\t...cell({ ...tabularSx, fontWeight: 600, color: COLORS.navy }, QUEUE_TIPS.agents),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"occupancy\",\r\n\t\t\t\theader: \"Occupancy %\",\r\n\t\t\t\taccessorFn: (r) => r.occupancy,\r\n\t\t\t\tCell: ({ row }) => (\r\n\t\t\t\t\t<Box component=\"span\" sx={{ color: occupancyColor(row.original.occupancy), fontWeight: 700 }}>\r\n\t\t\t\t\t\t{pct(row.original.occupancy)}\r\n\t\t\t\t\t</Box>\r\n\t\t\t\t),\r\n\t\t\t\t...cell(tabularSx, QUEUE_TIPS.occupancy),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"inboundTalkShare\",\r\n\t\t\t\theader: \"Inbound talk %\",\r\n\t\t\t\taccessorFn: (r) => r.inboundTalkShare,\r\n\t\t\t\tCell: ({ row }) => pct(row.original.inboundTalkShare),\r\n\t\t\t\t...cell({ ...tabularSx, fontWeight: 600, color: COLORS.blue }, QUEUE_TIPS.inboundTalkShare),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"campaignTalkShare\",\r\n\t\t\t\theader: \"Campaign talk %\",\r\n\t\t\t\taccessorFn: (r) => r.campaignTalkShare,\r\n\t\t\t\tCell: ({ row }) => pct(row.original.campaignTalkShare),\r\n\t\t\t\t...cell({ ...tabularSx, fontWeight: 600, color: COLORS.teal }, QUEUE_TIPS.campaignTalkShare),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"inboundOffered\",\r\n\t\t\t\theader: \"Inbound calls\",\r\n\t\t\t\taccessorFn: (r) => r.inboundOffered,\r\n\t\t\t\tCell: ({ row }) => count(row.original.inboundOffered),\r\n\t\t\t\t...cell({ ...tabularSx, fontWeight: 600, color: COLORS.navy }, QUEUE_TIPS.inboundOffered),\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tid: \"inboundSla\",\r\n\t\t\t\theader: slaSeconds != null ? `Inbound SLA (≤ ${slaSeconds} s)` : \"Inbound SLA %\",\r\n\t\t\t\taccessorFn: (r) => r.inboundSla,\r\n\t\t\t\tCell: ({ row }) => (\r\n\t\t\t\t\t<Box component=\"span\" sx={{ color: slaColor(row.original.inboundSla), fontWeight: 700 }}>\r\n\t\t\t\t\t\t{pct(row.original.inboundSla)}\r\n\t\t\t\t\t</Box>\r\n\t\t\t\t),\r\n\t\t\t\t...cell(tabularSx, QUEUE_TIPS.inboundSla(slaSeconds)),\r\n\t\t\t},\r\n\t\t];\r\n\t}, [slaSeconds]);\r\n\r\n\tconst exportButton = canExport && (\r\n\t\t<AppButton\r\n\t\t\tstartIcon={exporting ? <CircularProgress size={14} sx={{ color: \"#fff\" }} /> : <DownloadIcon sx={{ fontSize: \"0.95rem !important\" }} />}\r\n\t\t\tdisabled={exporting || loading || (isQueues ? queueRows : rows).length === 0}\r\n\t\t\tonClick={handleExport}\r\n\t\t\tsx={{ height: embedded ? FILTER_HEIGHT : 36 }}\r\n\t\t>\r\n\t\t\tExport Excel\r\n\t\t</AppButton>\r\n\t);\r\n\r\n\treturn (\r\n\t\t<LocalizationProvider dateAdapter={AdapterDayjs}>\r\n\t\t\t<Box sx={{ height: \"100%\", display: \"flex\", flexDirection: \"column\", fontFamily: FONT }}>\r\n\t\t\t\t{!embedded && <PageHeader title=\"Abandon Rate Report\" actions={exportButton} />}\r\n\r\n\t\t\t\t<Box sx={{ flex: 1, overflow: \"auto\", py: 0.8, px: 0 }}>\r\n\t\t\t\t\t<Box sx={{ mb: 0.8 }}>\r\n\t\t\t\t\t\t<ReportFilterBar\r\n\t\t\t\t\t\t\tstartDate={startDate}\r\n\t\t\t\t\t\t\tendDate={endDate}\r\n\t\t\t\t\t\t\tonStartChange={handleStartChange}\r\n\t\t\t\t\t\t\tonEndChange={handleEndChange}\r\n\t\t\t\t\t\t\t// A daily report — the time of day would only be misleading.\r\n\t\t\t\t\t\t\tshowTime={false}\r\n\t\t\t\t\t\t\tquickRange={quickRange}\r\n\t\t\t\t\t\t\tonQuickRangeChange={(r: QuickRange) => setQuickRange(r)}\r\n\t\t\t\t\t\t\textra={\r\n\t\t\t\t\t\t\t\t<>\r\n\t\t\t\t\t\t\t\t\t{campaignFilter}\r\n\t\t\t\t\t\t\t\t\t{queueFilter}\r\n\t\t\t\t\t\t\t\t\t<AppButton\r\n\t\t\t\t\t\t\t\t\t\tstartIcon={<RefreshIcon sx={{ fontSize: \"0.9rem !important\" }} />}\r\n\t\t\t\t\t\t\t\t\t\tdisabled={loading}\r\n\t\t\t\t\t\t\t\t\t\tonClick={fetchReport}\r\n\t\t\t\t\t\t\t\t\t\tsx={{ height: FILTER_HEIGHT }}\r\n\t\t\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t\t\tRefresh\r\n\t\t\t\t\t\t\t\t\t</AppButton>\r\n\t\t\t\t\t\t\t\t\t{/* Export sits at the far right of the filter row, where the page\r\n\t\t\t\t\t\t\t\t\t    header would have put it. The spacer takes the slack in the\r\n\t\t\t\t\t\t\t\t\t    flex row; on a narrow screen the row wraps and it follows. */}\r\n\t\t\t\t\t\t\t\t\t{embedded && canExport && (\r\n\t\t\t\t\t\t\t\t\t\t<>\r\n\t\t\t\t\t\t\t\t\t\t\t<Box sx={{ flex: 1, minWidth: 8 }} />\r\n\t\t\t\t\t\t\t\t\t\t\t{exportButton}\r\n\t\t\t\t\t\t\t\t\t\t</>\r\n\t\t\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t\t</>\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t/>\r\n\r\n\t\t\t\t\t\t{error && (\r\n\t\t\t\t\t\t\t<Alert severity=\"error\" sx={{ mt: 1, fontFamily: FONT, fontSize: \"0.78rem\" }} onClose={() => setError(\"\")}>\r\n\t\t\t\t\t\t\t\t{error}\r\n\t\t\t\t\t\t\t</Alert>\r\n\t\t\t\t\t\t)}\r\n\r\n\t\t\t\t\t\t{!isQueues && !error && rows.length > 0 && (\r\n\t\t\t\t\t\t\t<Box sx={{ display: \"flex\", alignItems: \"center\", gap: 2, flexWrap: \"wrap\", mt: 1, mx: 1 }}>\r\n\t\t\t\t\t\t\t\t<Typography\r\n\t\t\t\t\t\t\t\t\tsx={{\r\n\t\t\t\t\t\t\t\t\t\tfontFamily: FONT, fontSize: \"0.78rem\", fontWeight: 600,\r\n\t\t\t\t\t\t\t\t\t\tcolor: breachedDays > 0 ? COLORS.red : COLORS.green,\r\n\t\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t\t{breachedDays > 0\r\n\t\t\t\t\t\t\t\t\t\t? `${breachedDays} of ${rows.length} campaign-days over target`\r\n\t\t\t\t\t\t\t\t\t\t: `All ${rows.length} campaign-days within target`}\r\n\t\t\t\t\t\t\t\t</Typography>\r\n\r\n\t\t\t\t\t\t\t\t{/* The pilot's daily question: would adaptive have held the line? */}\r\n\t\t\t\t\t\t\t\t{shadowRows.length > 0 && (\r\n\t\t\t\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.78rem\", fontWeight: 600, color: COLORS.teal }}>\r\n\t\t\t\t\t\t\t\t\t\tAdaptive would stay within target on {shadowWithinTarget} of {shadowRows.length} shadow days\r\n\t\t\t\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t\t\t)}\r\n\r\n\t\t\t\t\t\t\t\t<Box sx={{ flex: 1 }} />\r\n\r\n\t\t\t\t\t\t\t\t<FormControlLabel\r\n\t\t\t\t\t\t\t\t\tcontrol={\r\n\t\t\t\t\t\t\t\t\t\t<Switch\r\n\t\t\t\t\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t\t\t\t\t\tchecked={showShadow}\r\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => { setShadowTouched(true); setShowShadow(e.target.checked); }}\r\n\t\t\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\tlabel={\r\n\t\t\t\t\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.75rem\", color: \"#4a5568\" }}>\r\n\t\t\t\t\t\t\t\t\t\t\tShow shadow comparison\r\n\t\t\t\t\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t</Box>\r\n\t\t\t\t\t\t)}\r\n\t\t\t\t\t</Box>\r\n\r\n\t\t\t\t\t{!isQueues && (\r\n\t\t\t\t\t<AppTable\r\n\t\t\t\t\t\t// MRT holds on to the column order it first saw, so adding or removing\r\n\t\t\t\t\t\t// the shadow group needs a remount rather than a re-render.\r\n\t\t\t\t\t\tkey={showShadow ? \"with-shadow\" : \"base\"}\r\n\t\t\t\t\t\tcolumns={columns}\r\n\t\t\t\t\t\tdata={rows}\r\n\t\t\t\t\t\tlayoutMode=\"semantic\"\r\n\t\t\t\t\t\tenableTopToolbar={false}\r\n\t\t\t\t\t\tenableColumnActions={false}\r\n\t\t\t\t\t\tenableColumnFilters={false}\r\n\t\t\t\t\t\tenableColumnDragging={false}\r\n\t\t\t\t\t\tenableHiding={false}\r\n\t\t\t\t\t\tenableDensityToggle={false}\r\n\t\t\t\t\t\tenableFullScreenToggle={false}\r\n\t\t\t\t\t\tenableStickyHeader\r\n\t\t\t\t\t\tonPaginationChange={setPagination}\r\n\t\t\t\t\t\tmuiPaginationProps={{ rowsPerPageOptions: PAGE_SIZE_OPTIONS }}\r\n\t\t\t\t\t\tstate={{ pagination, isLoading: loading }}\r\n\t\t\t\t\t\tlocalization={{\r\n\t\t\t\t\t\t\tnoRecordsToDisplay: \"No data for the selected range — the report only covers days since pacing measurement was deployed\",\r\n\t\t\t\t\t\t} as any}\r\n\t\t\t\t\t\tmuiCircularProgressProps={{ sx: { color: COLORS.blue }, size: 28 }}\r\n\t\t\t\t\t\tmuiTablePaperProps={{ elevation: 0, sx: { borderRadius: \"8px\", border: \"1px solid #e0e7ef\", overflow: \"hidden\", boxShadow: \"none\", mb: 2 } }}\r\n\t\t\t\t\t\tmuiTableProps={{ sx: { fontFamily: FONT, border: \"none\", borderRadius: 0, boxShadow: \"none\" } }}\r\n\t\t\t\t\t\t// Embedded, the host page's header and tab strip sit above the table too.\r\n\t\t\t\t\t\tmuiTableContainerProps={{ sx: { maxHeight: `calc(100vh - ${embedded ? 340 : 300}px)` } }}\r\n\t\t\t\t\t\tmuiTableHeadProps={{ sx: { boxShadow: \"none\" } }}\r\n\t\t\t\t\t\tmuiTableHeadRowProps={{ sx: { boxShadow: \"none\" } }}\r\n\t\t\t\t\t\t// Days over target are tinted, so a compliance breach is visible without\r\n\t\t\t\t\t\t// reading every percentage. A click opens everything the row holds —\r\n\t\t\t\t\t\t// twelve columns is already a lot to scan, so the rest lives in a drawer.\r\n\t\t\t\t\t\tmuiTableBodyRowProps={({ row }) => ({\r\n\t\t\t\t\t\t\tonClick: () => setDetailRow(row.original),\r\n\t\t\t\t\t\t\tsx: {\r\n\t\t\t\t\t\t\t\tcursor: \"pointer\",\r\n\t\t\t\t\t\t\t\tbackgroundColor: row.original.targetBreached ? \"#fdf4f4\" : \"#fff\",\r\n\t\t\t\t\t\t\t\t\"&:hover\": { backgroundColor: row.original.targetBreached ? \"#fbe9e9\" : \"#f0f7f8\" },\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t})}\r\n\t\t\t\t\t\tmuiBottomToolbarProps={{\r\n\t\t\t\t\t\t\tsx: {\r\n\t\t\t\t\t\t\t\tborderTop: \"1px solid #eef1f6\",\r\n\t\t\t\t\t\t\t\tboxShadow: \"none\",\r\n\t\t\t\t\t\t\t\tfontFamily: FONT,\r\n\t\t\t\t\t\t\t\t\"& .MuiTablePagination-displayedRows, & .MuiTablePagination-selectLabel, & .MuiInputBase-root\": { fontFamily: FONT, fontSize: \"0.73rem\" },\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t/>\r\n\t\t\t\t\t)}\r\n\r\n\t\t\t\t\t{/* ── Queue KPIs ───────────────────────────────────────────────\r\n\t\t\t\t\t    Staffing and service level for the same days. Per queue, not\r\n\t\t\t\t\t    per campaign: the agents are shared, so a campaign's share of\r\n\t\t\t\t\t    them can only be read here. */}\r\n\t\t\t\t\t{isQueues && (\r\n\t\t\t\t\t<>\r\n\t\t\t\t\t<AppTable\r\n\t\t\t\t\t\tcolumns={queueColumns}\r\n\t\t\t\t\t\tdata={queueRows}\r\n\t\t\t\t\t\tlayoutMode=\"semantic\"\r\n\t\t\t\t\t\tenableTopToolbar={false}\r\n\t\t\t\t\t\tenableColumnActions={false}\r\n\t\t\t\t\t\tenableColumnFilters={false}\r\n\t\t\t\t\t\tenableColumnDragging={false}\r\n\t\t\t\t\t\tenableHiding={false}\r\n\t\t\t\t\t\tenableDensityToggle={false}\r\n\t\t\t\t\t\tenableFullScreenToggle={false}\r\n\t\t\t\t\t\tenableStickyHeader\r\n\t\t\t\t\t\tonPaginationChange={setQueuePagination}\r\n\t\t\t\t\t\tmuiPaginationProps={{ rowsPerPageOptions: PAGE_SIZE_OPTIONS }}\r\n\t\t\t\t\t\tstate={{ pagination: queuePagination, isLoading: loading }}\r\n\t\t\t\t\t\tlocalization={{ noRecordsToDisplay: \"No queue activity in the selected range\" } as any}\r\n\t\t\t\t\t\tmuiCircularProgressProps={{ sx: { color: COLORS.blue }, size: 28 }}\r\n\t\t\t\t\t\tmuiTablePaperProps={{ elevation: 0, sx: { borderRadius: \"8px\", border: \"1px solid #e0e7ef\", overflow: \"hidden\", boxShadow: \"none\", mb: 1 } }}\r\n\t\t\t\t\t\tmuiTableProps={{ sx: { fontFamily: FONT, border: \"none\", borderRadius: 0, boxShadow: \"none\" } }}\r\n\t\t\t\t\t\tmuiTableContainerProps={{ sx: { maxHeight: \"calc(100vh - 340px)\" } }}\r\n\t\t\t\t\t\tmuiTableHeadProps={{ sx: { boxShadow: \"none\" } }}\r\n\t\t\t\t\t\tmuiTableHeadRowProps={{ sx: { boxShadow: \"none\" } }}\r\n\t\t\t\t\t\tmuiTableBodyRowProps={{ sx: { backgroundColor: \"#fff\", \"&:hover\": { backgroundColor: \"#f0f7f8\" } } }}\r\n\t\t\t\t\t\tmuiBottomToolbarProps={{\r\n\t\t\t\t\t\t\tsx: {\r\n\t\t\t\t\t\t\t\tborderTop: \"1px solid #eef1f6\",\r\n\t\t\t\t\t\t\t\tboxShadow: \"none\",\r\n\t\t\t\t\t\t\t\tfontFamily: FONT,\r\n\t\t\t\t\t\t\t\t\"& .MuiTablePagination-displayedRows, & .MuiTablePagination-selectLabel, & .MuiInputBase-root\": { fontFamily: FONT, fontSize: \"0.73rem\" },\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t/>\r\n\r\n\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.72rem\", color: \"#6b7a90\", mx: 1, mb: 2, lineHeight: 1.6 }}>\r\n\t\t\t\t\t\t{QUEUE_NOTE}\r\n\t\t\t\t\t</Typography>\r\n\t\t\t\t\t</>\r\n\t\t\t\t\t)}\r\n\t\t\t\t</Box>\r\n\r\n\t\t\t\t<RowDetailDrawer row={detailRow} onClose={() => setDetailRow(null)} showShadow={showShadow} />\r\n\t\t\t</Box>\r\n\t\t</LocalizationProvider>\r\n\t);\r\n}\r\n\r\nconst AbandonRateReport: React.FC = () => (\r\n\t<SDKProvider>\r\n\t\t<AbandonRateReportContent />\r\n\t</SDKProvider>\r\n);\r\n\r\nexport default AbandonRateReport;\r\n","import { useMemo, useState } from \"react\";\r\nimport {\r\n\tAccordion, AccordionDetails, AccordionSummary,\r\n\tBox, Chip, Drawer, FormControlLabel, IconButton, Switch, Tooltip, Typography,\r\n} from \"@mui/material\";\r\nimport { alpha } from \"@mui/material/styles\";\r\nimport CloseIcon from \"@mui/icons-material/Close\";\r\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\r\nimport dayjs from \"dayjs\";\r\nimport type { AbandonRateRow } from \"./types\";\r\n\r\nconst FONT = \"'Inter', 'Segoe UI', 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif\";\r\nconst DASH = \"–\";\r\n\r\n/** Sits below the app bar, like the form submissions panel. */\r\nconst APP_HEADER_H = 60;\r\n\r\nconst COLORS = {\r\n\tnavy: \"#0d2a56\",\r\n\tblue: \"#1565c8\",\r\n\tgreen: \"#0a9a62\",\r\n\tamber: \"#c47c00\",\r\n\tred: \"#cc2a2a\",\r\n\tteal: \"#1A5F6C\",\r\n\tgrey: \"#7a8599\",\r\n} as const;\r\n\r\ninterface Field {\r\n\tlabel: string;\r\n\tvalue: string;\r\n\t/** Empty fields are the ones the toggle hides. */\r\n\tempty: boolean;\r\n\tcolor?: string;\r\n\thint?: string;\r\n}\r\n\r\ninterface Section {\r\n\ttitle: string;\r\n\taccent: string;\r\n\tfields: Field[];\r\n}\r\n\r\n/** Named so the drawer can hide it with the table's shadow columns. */\r\nconst SHADOW_SECTION = \"Shadow comparison\";\r\n\r\nconst pct = (v: number | null | undefined, decimals = 2) =>\r\n\ttypeof v === \"number\" ? `${(v * 100).toFixed(decimals)}%` : DASH;\r\nconst secs = (v: number | null | undefined) =>\r\n\ttypeof v === \"number\" ? `${v.toFixed(1)} s` : DASH;\r\nconst num = (v: number | null | undefined) =>\r\n\ttypeof v === \"number\" ? v.toLocaleString() : DASH;\r\nconst ratio = (v: number | null | undefined) =>\r\n\ttypeof v === \"number\" ? v.toFixed(1) : DASH;\r\nconst signed = (v: number | null | undefined) => {\r\n\tif (typeof v !== \"number\") return DASH;\r\n\tconst whole = Math.round(v * 100);\r\n\tif (whole > 0) return `+${whole}%`;\r\n\tif (whole < 0) return `−${Math.abs(whole)}%`;\r\n\treturn \"0%\";\r\n};\r\n\r\n/** A value counts as empty when the API had nothing to say — not when it said zero. */\r\nconst field = (label: string, raw: unknown, value: string, extra: Partial<Field> = {}): Field => ({\r\n\tlabel,\r\n\tvalue,\r\n\tempty: raw === null || raw === undefined || value === DASH,\r\n\t...extra,\r\n});\r\n\r\nfunction buildSections(row: AbandonRateRow): Section[] {\r\n\tconst shadowRan = (row.shadowMinutes ?? 0) > 0;\r\n\tconst liveAdaptive = (row.shadowMinutes ?? 0) === 0 && (row.adaptiveMinutes ?? 0) > 0;\r\n\r\n\treturn [\r\n\t\t{\r\n\t\t\ttitle: \"Calls\",\r\n\t\t\taccent: COLORS.navy,\r\n\t\t\tfields: [\r\n\t\t\t\tfield(\"Dialed\", row.dialed, num(row.dialed)),\r\n\t\t\t\tfield(\"Answered\", row.answered, num(row.answered), { color: COLORS.green }),\r\n\t\t\t\tfield(\"Connected to an agent\", row.agentConnected, num(row.agentConnected), { color: COLORS.green }),\r\n\t\t\t\tfield(\"Abandoned\", row.abandoned, num(row.abandoned), { color: COLORS.red }),\r\n\t\t\t\tfield(\"No agent free\", row.abandonedWaitTimeout, num(row.abandonedWaitTimeout), {\r\n\t\t\t\t\tcolor: COLORS.red,\r\n\t\t\t\t\thint: \"Answered, then dropped because nobody came free. This is what regulators count.\",\r\n\t\t\t\t}),\r\n\t\t\t\tfield(\"Customer hung up\", row.abandonedCustomerHangup, num(row.abandonedCustomerHangup), { color: COLORS.amber }),\r\n\t\t\t\tfield(\"System errors\", row.systemErrors, num(row.systemErrors)),\r\n\t\t\t],\r\n\t\t},\r\n\t\t{\r\n\t\t\ttitle: \"Rates\",\r\n\t\t\taccent: COLORS.teal,\r\n\t\t\tfields: [\r\n\t\t\t\tfield(\"Answer rate\", row.answerRate, pct(row.answerRate), { color: COLORS.green }),\r\n\t\t\t\tfield(\"Abandon rate\", row.abandonRate, pct(row.abandonRate), {\r\n\t\t\t\t\tcolor: row.targetBreached ? COLORS.red : COLORS.navy,\r\n\t\t\t\t}),\r\n\t\t\t\tfield(\"Target\", row.targetAbandonRate, pct(row.targetAbandonRate)),\r\n\t\t\t\tfield(\"Within target\", row.targetBreached, row.targetBreached ? \"No — over target\" : \"Yes\", {\r\n\t\t\t\t\tcolor: row.targetBreached ? COLORS.red : COLORS.green,\r\n\t\t\t\t}),\r\n\t\t\t],\r\n\t\t},\r\n\t\t{\r\n\t\t\ttitle: \"Waiting\",\r\n\t\t\taccent: COLORS.blue,\r\n\t\t\tfields: [\r\n\t\t\t\tfield(\"Average time to an agent\", row.avgTimeToAgentSeconds, secs(row.avgTimeToAgentSeconds), {\r\n\t\t\t\t\tcolor: COLORS.blue,\r\n\t\t\t\t}),\r\n\t\t\t\tfield(\"Average wait before abandoning\", row.avgAbandonWaitSeconds, secs(row.avgAbandonWaitSeconds), {\r\n\t\t\t\t\tcolor: COLORS.amber,\r\n\t\t\t\t\thint: \"How long the people who gave up had waited.\",\r\n\t\t\t\t}),\r\n\t\t\t],\r\n\t\t},\r\n\t\t{\r\n\t\t\ttitle: SHADOW_SECTION,\r\n\t\t\taccent: COLORS.amber,\r\n\t\t\tfields: [\r\n\t\t\t\tfield(\"Minutes in shadow mode\", row.shadowMinutes, num(row.shadowMinutes)),\r\n\t\t\t\tfield(\"Minutes adaptive was in control\", row.adaptiveMinutes, num(row.adaptiveMinutes)),\r\n\t\t\t\t// Nothing to compare on a day adaptive actually drove.\r\n\t\t\t\t...(liveAdaptive ? [] : [\r\n\t\t\t\t\tfield(\"Ratio used\", row.avgAppliedRatio, ratio(row.avgAppliedRatio), { color: COLORS.teal }),\r\n\t\t\t\t\tfield(\"Ratio adaptive recommended\", row.avgRecommendedRatio, ratio(row.avgRecommendedRatio), { color: COLORS.amber }),\r\n\t\t\t\t\tfield(\"Calls adaptive would have placed\", row.adaptiveCallChange, signed(row.adaptiveCallChange), {\r\n\t\t\t\t\t\tcolor: (row.adaptiveCallChange ?? 0) >= 0 ? COLORS.green : COLORS.amber,\r\n\t\t\t\t\t\thint: \"Against what was actually dialed that day.\",\r\n\t\t\t\t\t}),\r\n\t\t\t\t\tfield(\"Estimated abandon rate, ratio used\", row.estAbandonRateApplied, pct(row.estAbandonRateApplied)),\r\n\t\t\t\t\tfield(\"Estimated abandon rate, adaptive\", row.estAbandonRateAdaptive, pct(row.estAbandonRateAdaptive)),\r\n\t\t\t\t\tfield(\r\n\t\t\t\t\t\t\"Verdict\",\r\n\t\t\t\t\t\trow.adaptiveWithinTarget,\r\n\t\t\t\t\t\trow.adaptiveWithinTarget === true ? \"Adaptive would stay within target\"\r\n\t\t\t\t\t\t\t: row.adaptiveWithinTarget === false ? \"Adaptive would exceed the target\"\r\n\t\t\t\t\t\t\t\t: DASH,\r\n\t\t\t\t\t\t{ color: row.adaptiveWithinTarget ? COLORS.green : COLORS.red },\r\n\t\t\t\t\t),\r\n\t\t\t\t]),\r\n\t\t\t\t...(liveAdaptive ? [field(\"Comparison\", null, DASH, {\r\n\t\t\t\t\tvalue: \"Adaptive was live — nothing to compare\",\r\n\t\t\t\t\tempty: false,\r\n\t\t\t\t} as Partial<Field>)] : []),\r\n\t\t\t\t...(shadowRan ? [] : []),\r\n\t\t\t],\r\n\t\t},\r\n\t];\r\n}\r\n\r\n/**\r\n * Everything the report holds about one campaign-day.\r\n *\r\n * The table is deliberately narrow — twelve columns is already a lot to scan —\r\n * so the fields that don't earn a column live here instead, grouped the way a\r\n * supervisor reads them: what was dialed, how it went, how long people waited,\r\n * and what adaptive would have done differently.\r\n */\r\nexport default function RowDetailDrawer({\r\n\trow, onClose, showShadow,\r\n}: {\r\n\trow: AbandonRateRow | null;\r\n\tonClose: () => void;\r\n\t/** Follows the table's toggle, so the drawer shows the same set of facts. */\r\n\tshowShadow: boolean;\r\n}) {\r\n\t// Off by default: a day with no shadow data is mostly dashes, and hiding them\r\n\t// leaves the handful of numbers that matter.\r\n\tconst [showEmpty, setShowEmpty] = useState(false);\r\n\r\n\t/** Sections are open unless collapsed, so nothing is hidden until asked. */\r\n\tconst [collapsed, setCollapsed] = useState<Record<string, boolean>>({});\r\n\tconst isOpen = (title: string) => !collapsed[title];\r\n\tconst toggleSection = (title: string) =>\r\n\t\tsetCollapsed((prev) => ({ ...prev, [title]: !prev[title] }));\r\n\r\n\tconst sections = useMemo(() => {\r\n\t\tif (!row) return [];\r\n\t\tconst all = buildSections(row);\r\n\t\t// The shadow group is hidden alongside the table's columns, not separately —\r\n\t\t// one switch decides whether this report is being read for the pilot.\r\n\t\treturn showShadow ? all : all.filter((s) => s.title !== SHADOW_SECTION);\r\n\t}, [row, showShadow]);\r\n\r\n\tconst visible = sections\r\n\t\t.map((s) => ({ ...s, fields: showEmpty ? s.fields : s.fields.filter((f) => !f.empty) }))\r\n\t\t.filter((s) => s.fields.length > 0);\r\n\r\n\tconst hiddenCount = sections.reduce((n, s) => n + s.fields.filter((f) => f.empty).length, 0);\r\n\r\n\treturn (\r\n\t\t<Drawer\r\n\t\t\tanchor=\"right\"\r\n\t\t\topen={!!row}\r\n\t\t\tonClose={onClose}\r\n\t\t\tslotProps={{\r\n\t\t\t\tpaper: {\r\n\t\t\t\t\tsx: {\r\n\t\t\t\t\t\twidth: { xs: \"100%\", sm: 460 },\r\n\t\t\t\t\t\ttop: `${APP_HEADER_H}px`,\r\n\t\t\t\t\t\theight: `calc(100% - ${APP_HEADER_H}px)`,\r\n\t\t\t\t\t\tboxShadow: \"-4px 0 24px rgba(0,0,0,0.12)\",\r\n\t\t\t\t\t\tborderLeft: \"1px solid #e0e6ed\",\r\n\t\t\t\t\t},\r\n\t\t\t\t},\r\n\t\t\t}}\r\n\t\t>\r\n\t\t\t{row && (\r\n\t\t\t\t<Box sx={{ display: \"flex\", flexDirection: \"column\", height: \"100%\", bgcolor: \"#fafbfc\", fontFamily: FONT }}>\r\n\t\t\t\t\t{/* Header */}\r\n\t\t\t\t\t<Box sx={{ px: 2.5, pt: 2.5, pb: 2, borderBottom: \"1px solid #eee\", bgcolor: \"#fff\" }}>\r\n\t\t\t\t\t\t<Box sx={{ display: \"flex\", alignItems: \"flex-start\", justifyContent: \"space-between\", gap: 1 }}>\r\n\t\t\t\t\t\t\t<Box sx={{ minWidth: 0, flex: 1 }}>\r\n\t\t\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontWeight: 700, fontSize: \"1.05rem\", color: \"#1a1a1a\", lineHeight: 1.25 }}>\r\n\t\t\t\t\t\t\t\t\t{row.campaignName || `Campaign ${row.campaignId}`}\r\n\t\t\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.78rem\", color: COLORS.grey, mt: 0.3 }}>\r\n\t\t\t\t\t\t\t\t\t{row.day ? dayjs(row.day).format(\"dddd, D MMMM YYYY\") : DASH}\r\n\t\t\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t\t</Box>\r\n\t\t\t\t\t\t\t<Tooltip title=\"Close\">\r\n\t\t\t\t\t\t\t\t<IconButton\r\n\t\t\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t\t\t\tonClick={onClose}\r\n\t\t\t\t\t\t\t\t\tsx={{ color: \"#555\", bgcolor: \"#f2f4f7\", \"&:hover\": { bgcolor: \"#e4e7eb\", color: \"#111\" } }}\r\n\t\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t\t<CloseIcon fontSize=\"small\" />\r\n\t\t\t\t\t\t\t\t</IconButton>\r\n\t\t\t\t\t\t\t</Tooltip>\r\n\t\t\t\t\t\t</Box>\r\n\r\n\t\t\t\t\t\t{/* Chips and the toggle share a row — the toggle on its own line pushed\r\n\t\t\t\t\t\t    the first section below the fold. */}\r\n\t\t\t\t\t\t<Box sx={{ display: \"flex\", alignItems: \"center\", flexWrap: \"wrap\", gap: 0.75, mt: 1.25 }}>\r\n\t\t\t\t\t\t\t<Chip\r\n\t\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t\t\tlabel={row.targetBreached ? \"Over target\" : \"Within target\"}\r\n\t\t\t\t\t\t\t\tsx={{\r\n\t\t\t\t\t\t\t\t\theight: 22, fontSize: \"0.68rem\", fontWeight: 700,\r\n\t\t\t\t\t\t\t\t\tbackgroundColor: row.targetBreached ? \"#fdecea\" : \"#e3f3e8\",\r\n\t\t\t\t\t\t\t\t\tcolor: row.targetBreached ? COLORS.red : COLORS.green,\r\n\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t{(row.shadowMinutes ?? 0) > 0 && (\r\n\t\t\t\t\t\t\t\t<Chip\r\n\t\t\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t\t\t\tlabel=\"Shadow mode\"\r\n\t\t\t\t\t\t\t\t\tsx={{ height: 22, fontSize: \"0.68rem\", fontWeight: 700, backgroundColor: \"#fbf0d6\", color: COLORS.amber }}\r\n\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t{(row.shadowMinutes ?? 0) === 0 && (row.adaptiveMinutes ?? 0) > 0 && (\r\n\t\t\t\t\t\t\t\t<Chip\r\n\t\t\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t\t\t\tlabel=\"Live adaptive\"\r\n\t\t\t\t\t\t\t\t\tsx={{ height: 22, fontSize: \"0.68rem\", fontWeight: 700, backgroundColor: \"#e3f3e8\", color: COLORS.green }}\r\n\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t)}\r\n\r\n\t\t\t\t\t\t\t<Box sx={{ flex: 1 }} />\r\n\r\n\t\t\t\t\t\t\t<FormControlLabel\r\n\t\t\t\t\t\t\t\tsx={{ mr: 0 }}\r\n\t\t\t\t\t\t\t\tcontrol={\r\n\t\t\t\t\t\t\t\t\t<Switch\r\n\t\t\t\t\t\t\t\t\t\tsize=\"small\"\r\n\t\t\t\t\t\t\t\t\t\tchecked={showEmpty}\r\n\t\t\t\t\t\t\t\t\t\tonChange={(e) => setShowEmpty(e.target.checked)}\r\n\t\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tlabel={\r\n\t\t\t\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.73rem\", color: \"#4a5568\", whiteSpace: \"nowrap\" }}>\r\n\t\t\t\t\t\t\t\t\t\tShow empty{hiddenCount > 0 && !showEmpty ? ` (${hiddenCount})` : \"\"}\r\n\t\t\t\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t</Box>\r\n\t\t\t\t\t</Box>\r\n\r\n\t\t\t\t\t{/* Sections */}\r\n\t\t\t\t\t<Box sx={{ flex: 1, overflowY: \"auto\", px: 2, py: 2, display: \"flex\", flexDirection: \"column\", gap: 1.5 }}>\r\n\t\t\t\t\t\t{visible.length === 0 && (\r\n\t\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.8rem\", color: COLORS.grey }}>\r\n\t\t\t\t\t\t\t\tNothing recorded for this day.\r\n\t\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t{visible.map((section) => (\r\n\t\t\t\t\t\t\t/* Same shape as the form submission drawer's sections: a tinted\r\n\t\t\t\t\t\t\t   header bar, an accent edge, and the chevron on the right. */\r\n\t\t\t\t\t\t\t<Accordion\r\n\t\t\t\t\t\t\t\tkey={section.title}\r\n\t\t\t\t\t\t\t\texpanded={isOpen(section.title)}\r\n\t\t\t\t\t\t\t\tonChange={() => toggleSection(section.title)}\r\n\t\t\t\t\t\t\t\tdisableGutters\r\n\t\t\t\t\t\t\t\televation={0}\r\n\t\t\t\t\t\t\t\tsx={{\r\n\t\t\t\t\t\t\t\t\t\"&:before\": { display: \"none\" },\r\n\t\t\t\t\t\t\t\t\tborder: \"1px solid #eef2f3\",\r\n\t\t\t\t\t\t\t\t\tborderLeft: `4px solid ${section.accent}`,\r\n\t\t\t\t\t\t\t\t\tborderRadius: \"8px !important\",\r\n\t\t\t\t\t\t\t\t\toverflow: \"hidden\",\r\n\t\t\t\t\t\t\t\t\t// Keep full height and let the drawer scroll. In a flex column an\r\n\t\t\t\t\t\t\t\t\t// Accordion is shrunk to fit instead, which clips its own rows.\r\n\t\t\t\t\t\t\t\t\tflexShrink: 0,\r\n\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t<AccordionSummary\r\n\t\t\t\t\t\t\t\t\texpandIcon={<ExpandMoreIcon sx={{ color: section.accent }} />}\r\n\t\t\t\t\t\t\t\t\tsx={{\r\n\t\t\t\t\t\t\t\t\t\tminHeight: 42,\r\n\t\t\t\t\t\t\t\t\t\tbgcolor: alpha(section.accent, 0.06),\r\n\t\t\t\t\t\t\t\t\t\t\"&:hover\": { bgcolor: alpha(section.accent, 0.1) },\r\n\t\t\t\t\t\t\t\t\t\t\"& .MuiAccordionSummary-content\": { my: 0.75, alignItems: \"center\", gap: 1 },\r\n\t\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontWeight: 700, fontSize: \"0.85rem\", color: section.accent }}>\r\n\t\t\t\t\t\t\t\t\t\t{section.title}\r\n\t\t\t\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t\t\t\t{/* A collapsed section still says how much it is holding. */}\r\n\t\t\t\t\t\t\t\t\t{!isOpen(section.title) && (\r\n\t\t\t\t\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.72rem\", color: COLORS.grey }}>\r\n\t\t\t\t\t\t\t\t\t\t\t{section.fields.length} {section.fields.length === 1 ? \"field\" : \"fields\"}\r\n\t\t\t\t\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t\t</AccordionSummary>\r\n\t\t\t\t\t\t\t\t<AccordionDetails sx={{ bgcolor: \"#fff\", px: 2, py: 1.5 }}>\r\n\t\t\t\t\t\t\t\t<Box sx={{ display: \"grid\", gridTemplateColumns: \"1fr auto\", rowGap: 0.85, columnGap: 2 }}>\r\n\t\t\t\t\t\t\t\t\t{section.fields.map((f) => (\r\n\t\t\t\t\t\t\t\t\t\t<Box key={f.label} sx={{ display: \"contents\" }}>\r\n\t\t\t\t\t\t\t\t\t\t\t<Typography sx={{ fontFamily: FONT, fontSize: \"0.76rem\", color: COLORS.grey }}>\r\n\t\t\t\t\t\t\t\t\t\t\t\t{f.hint ? (\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Tooltip arrow title={f.hint}>\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Box component=\"span\" sx={{ borderBottom: \"1px dotted #c3cbd4\", cursor: \"help\" }}>\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{f.label}\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Box>\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Tooltip>\r\n\t\t\t\t\t\t\t\t\t\t\t\t) : f.label}\r\n\t\t\t\t\t\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t\t\t\t\t\t<Typography\r\n\t\t\t\t\t\t\t\t\t\t\t\tsx={{\r\n\t\t\t\t\t\t\t\t\t\t\t\t\tfontFamily: FONT, fontSize: \"0.8rem\", fontWeight: 600,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\tfontVariantNumeric: \"tabular-nums\",\r\n\t\t\t\t\t\t\t\t\t\t\t\t\tcolor: f.empty ? COLORS.grey : (f.color || \"#1a1a1a\"),\r\n\t\t\t\t\t\t\t\t\t\t\t\t\ttextAlign: \"right\",\r\n\t\t\t\t\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t\t\t\t\t{f.value}\r\n\t\t\t\t\t\t\t\t\t\t\t</Typography>\r\n\t\t\t\t\t\t\t\t\t\t</Box>\r\n\t\t\t\t\t\t\t\t\t))}\r\n\t\t\t\t\t\t\t\t</Box>\r\n\t\t\t\t\t\t\t\t</AccordionDetails>\r\n\t\t\t\t\t\t\t</Accordion>\r\n\t\t\t\t\t\t))}\r\n\t\t\t\t\t</Box>\r\n\t\t\t\t</Box>\r\n\t\t\t)}\r\n\t\t</Drawer>\r\n\t);\r\n}\r\n","import axiosInstance from \"../../services/axios\";\r\nimport { END_POINT } from \"../../services/endPoint\";\r\nimport type { AbandonRateParams, AbandonRateRow, CampaignOption, QueueKpiRow, QueueOption } from \"./types\";\r\n\r\n/**\r\n * Query params are snake_case, like the rest of this API's query strings — only\r\n * JSON bodies are camelCase. The handoff guide documents `startDate`/`endDate`,\r\n * but the endpoint answers 422 \"Field required\" for those.\r\n *\r\n * `campaign_ids` repeats once per campaign; the API reads them as a list.\r\n */\r\nfunction buildQs(params: AbandonRateParams): string {\r\n\tconst qs = new URLSearchParams();\r\n\tqs.append(\"start_date\", params.startDate);\r\n\tqs.append(\"end_date\", params.endDate);\r\n\tfor (const id of params.campaignIds ?? []) qs.append(\"campaign_ids\", String(id));\r\n\tfor (const id of params.queueIds ?? []) qs.append(\"queue_ids\", String(id));\r\n\treturn `?${qs.toString()}`;\r\n}\r\n\r\n/**\r\n * The API's own reason for refusing a request — a 422 carries a readable\r\n * `detail` (\"Report range cannot exceed 92 days\"). Export responses are blobs,\r\n * so the JSON has to be read out of them first.\r\n */\r\nexport async function apiErrorMessage(err: unknown, fallback: string): Promise<string> {\r\n\tconst e = err as any;\r\n\tlet data = e?.response?.data;\r\n\tif (data instanceof Blob) {\r\n\t\ttry { data = JSON.parse(await data.text()); } catch { data = null; }\r\n\t}\r\n\tconst detail = data?.detail ?? data?.message;\r\n\tif (typeof detail === \"string\" && detail) return detail;\r\n\t// FastAPI validation errors: a list of { msg }.\r\n\tif (Array.isArray(detail) && detail.length > 0) {\r\n\t\treturn detail.map((d: any) => d?.msg).filter(Boolean).join(\"; \") || fallback;\r\n\t}\r\n\treturn fallback;\r\n}\r\n\r\n/** Queues for the filter, sorted by name. Limited to these campaigns' queues when given. */\r\nexport async function getQueueOptions(campaignIds?: number[]): Promise<QueueOption[]> {\r\n\tconst qs = new URLSearchParams();\r\n\tfor (const id of campaignIds ?? []) qs.append(\"campaign_ids\", String(id));\r\n\tconst q = qs.toString();\r\n\tconst res = await axiosInstance.get(`${END_POINT.ABANDON_RATE_REPORT_QUEUE_OPTIONS}${q ? `?${q}` : \"\"}`);\r\n\tconst list = Array.isArray(res.data) ? res.data : (res.data?.data ?? []);\r\n\treturn list.filter((o: any) => o && typeof o.queueId === \"number\");\r\n}\r\n\r\nexport async function getAbandonRateReport(params: AbandonRateParams): Promise<AbandonRateRow[]> {\r\n\tconst res = await axiosInstance.get(`${END_POINT.ABANDON_RATE_REPORT}${buildQs(params)}`);\r\n\tconst body = res.data;\r\n\treturn Array.isArray(body) ? body : (body?.data ?? []);\r\n}\r\n\r\n/**\r\n * The same days, read per queue. Takes the same filters: leaving campaigns out\r\n * covers every queue.\r\n */\r\nexport async function getQueueKpis(params: AbandonRateParams): Promise<QueueKpiRow[]> {\r\n\tconst res = await axiosInstance.get(`${END_POINT.ABANDON_RATE_REPORT_QUEUES}${buildQs(params)}`);\r\n\tconst body = res.data;\r\n\treturn Array.isArray(body) ? body : (body?.data ?? []);\r\n}\r\n\r\n/**\r\n * Download the report as Excel. Goes through the SDK's axios instance so the\r\n * Authorization header is attached — a plain link would arrive unauthenticated.\r\n * The file already holds percentages, so nothing is converted here, and the\r\n * server adds the Queue KPIs sheet itself — same call, same params.\r\n */\r\nexport async function exportAbandonRateReportToExcel(params: AbandonRateParams): Promise<void> {\r\n\tconst res = await axiosInstance.get(`${END_POINT.ABANDON_RATE_REPORT_EXPORT_EXCEL}${buildQs(params)}`, {\r\n\t\tresponseType: \"blob\",\r\n\t});\r\n\tconst url = URL.createObjectURL(new Blob([res.data]));\r\n\tconst a = document.createElement(\"a\");\r\n\ta.href = url;\r\n\ta.download = `abandon-rate-report-${params.startDate}-to-${params.endDate}.xlsx`;\r\n\ta.click();\r\n\tURL.revokeObjectURL(url);\r\n}\r\n\r\n/** Campaigns for the filter dropdown. The report itself covers all of them when none is picked. */\r\nexport async function getCampaignOptions(): Promise<CampaignOption[]> {\r\n\tconst res = await axiosInstance.get(END_POINT.CAMPAIGNS_GET_ALL);\r\n\tconst list = Array.isArray(res.data) ? res.data : (res.data?.data ?? []);\r\n\treturn list\r\n\t\t.filter((c: any) => c && typeof c.id === \"number\")\r\n\t\t.map((c: any) => ({ id: c.id, name: c.name || `Campaign ${c.id}` }));\r\n}\r\n","/**\r\n * One row of GET /dialer/pacing-stats/daily-report — a campaign's dialling for\r\n * one day.\r\n *\r\n * Rates are FRACTIONS (0.025 is 2.5%); multiply by 100 to display. `null` means\r\n * \"no data\", which is not the same as zero: a campaign that answered nothing has\r\n * an unknown abandon rate, not a perfect one.\r\n */\r\nexport interface AbandonRateRow {\r\n\tday: string;\r\n\tcampaignId: number;\r\n\tcampaignName: string;\r\n\tdialed: number;\r\n\tanswered: number;\r\n\t/** Answered calls that actually reached an agent. */\r\n\tagentConnected: number;\r\n\tabandoned: number;\r\n\t/** Abandoned because no agent came free in time — what regulators count. */\r\n\tabandonedWaitTimeout: number;\r\n\t/** Abandoned because the customer hung up first. */\r\n\tabandonedCustomerHangup: number;\r\n\tsystemErrors: number;\r\n\tanswerRate: number | null;\r\n\tabandonRate: number | null;\r\n\ttargetAbandonRate: number | null;\r\n\ttargetBreached: boolean;\r\n\tavgTimeToAgentSeconds: number | null;\r\n\tavgAbandonWaitSeconds: number | null;\r\n\r\n\t/*\r\n\t * Shadow comparison — the go-live gate.\r\n\t *\r\n\t * While a campaign runs in shadow mode adaptive pacing only recommends, and\r\n\t * each day someone checks how many more calls it would have placed and what\r\n\t * abandon rate that would likely have caused. Five clean days and the campaign\r\n\t * can be switched to adaptive for real.\r\n\t */\r\n\t/** Minutes the campaign ran in shadow mode that day. */\r\n\tshadowMinutes: number | null;\r\n\t/** Minutes adaptive was actually in control — nothing to compare on those. */\r\n\tadaptiveMinutes: number | null;\r\n\tavgAppliedRatio: number | null;\r\n\tavgRecommendedRatio: number | null;\r\n\t/** Fraction, signed: 0.46 means adaptive would have placed 46% more calls. */\r\n\tadaptiveCallChange: number | null;\r\n\t/** Fractions. Estimates, and they lean high — see the column group's tooltip. */\r\n\testAbandonRateApplied: number | null;\r\n\testAbandonRateAdaptive: number | null;\r\n\t/** Whether adaptive would have stayed within target. Null when unknown. */\r\n\tadaptiveWithinTarget: boolean | null;\r\n}\r\n\r\n/**\r\n * One row of GET /dialer/pacing-stats/daily-report/queues — a queue's staffing\r\n * and service level for one day.\r\n *\r\n * Per QUEUE, not per campaign: the same agents take inbound calls and campaign\r\n * calls, so occupancy and SLA can only be read for the queue as a whole. Rates\r\n * are fractions, and `null` means \"no data\" rather than zero.\r\n */\r\nexport interface QueueKpiRow {\r\n\tday: string;\r\n\tqueueName: string;\r\n\t/** Agents staffed on the queue that day. */\r\n\tagents: number;\r\n\t/** On calls, ringing or in wrap-up ÷ staffed time, breaks excluded. */\r\n\toccupancy: number | null;\r\n\tinboundTalkShare: number | null;\r\n\tcampaignTalkShare: number | null;\r\n\tinboundOffered: number;\r\n\t/** Share of inbound calls answered within `slaSeconds`. */\r\n\tinboundSla: number | null;\r\n\t/** The queue's own SLA threshold — shown in the header, never hard-coded. */\r\n\tslaSeconds: number | null;\r\n}\r\n\r\nexport interface AbandonRateParams {\r\n\t/** YYYY-MM-DD. */\r\n\tstartDate: string;\r\n\tendDate: string;\r\n\t/** Omit for every campaign. Sent as repeated `campaignIds` params. */\r\n\tcampaignIds?: number[];\r\n\t/** Omit for every queue. Sent as repeated `queue_ids`; narrows the Queue KPIs table and sheet. */\r\n\tqueueIds?: number[];\r\n}\r\n\r\n/** Server limits, mirrored so the filters can refuse before the round trip. */\r\nexport const ABANDON_REPORT_LIMITS = {\r\n\tmaxDays: 92,\r\n\tmaxCampaigns: 100,\r\n\tmaxQueues: 100,\r\n} as const;\r\n\r\n/** A queue as the Queue KPIs filter needs it — GET …/daily-report/queues/options. */\r\nexport interface QueueOption {\r\n\tqueueId: number;\r\n\tqueueName: string;\r\n}\r\n\r\n/** A campaign as the filter dropdown needs it. */\r\nexport interface CampaignOption {\r\n\tid: number;\r\n\tname: string;\r\n}\r\n","import { useCallback, useEffect, useState } from \"react\";\r\nimport axiosInstance from \"../../services/axios\";\r\nimport { END_POINT } from \"../../services/endPoint\";\r\n\r\n/** A campaign as the Live tab's picker needs it. */\r\nexport interface PacingCampaign {\r\n\tid: number;\r\n\tname: string;\r\n\tstatus: string;\r\n\t/** Adaptive is in control; false means it only recommends (shadow mode). */\r\n\tadaptivePacingEnabled: boolean;\r\n\t/** The live card averages over this window — null lets the card use its default. */\r\n\tpacingWindowMinutes: number | null;\r\n}\r\n\r\n/** Only these campaigns have a dial ratio and an abandon rate to watch. */\r\nfunction supportsPacing(c: any): boolean {\r\n\treturn String(c?.dialingMode || \"\").toUpperCase() === \"PREDICTIVE\"\r\n\t\t&& String(c?.agentType || \"\").toUpperCase() === \"HUMAN\";\r\n}\r\n\r\n/**\r\n * The predictive campaigns a supervisor can watch, running ones first.\r\n *\r\n * Same list endpoint the campaigns page uses. It needs no permission of its own\r\n * beyond the dialer entitlement, so a supervisor without `campaigns:read` still\r\n * gets a picker.\r\n */\r\nexport function usePacingCampaigns(enabled: boolean) {\r\n\tconst [campaigns, setCampaigns] = useState<PacingCampaign[]>([]);\r\n\tconst [loading, setLoading] = useState(false);\r\n\tconst [error, setError] = useState(\"\");\r\n\r\n\tconst fetchCampaigns = useCallback(async () => {\r\n\t\tif (!enabled) return;\r\n\t\tsetLoading(true);\r\n\t\tsetError(\"\");\r\n\t\ttry {\r\n\t\t\tconst res = await axiosInstance.get(END_POINT.CAMPAIGNS_GET_ALL);\r\n\t\t\tconst list = Array.isArray(res.data) ? res.data : (res.data?.data ?? []);\r\n\t\t\tconst mapped: PacingCampaign[] = list\r\n\t\t\t\t.filter((c: any) => c && typeof c.id === \"number\" && supportsPacing(c))\r\n\t\t\t\t.map((c: any) => ({\r\n\t\t\t\t\tid: c.id,\r\n\t\t\t\t\tname: c.name || `Campaign ${c.id}`,\r\n\t\t\t\t\tstatus: String(c.status || \"\").toUpperCase(),\r\n\t\t\t\t\tadaptivePacingEnabled: c.adaptivePacingEnabled === true,\r\n\t\t\t\t\tpacingWindowMinutes: typeof c.pacingWindowMinutes === \"number\" ? c.pacingWindowMinutes : null,\r\n\t\t\t\t}));\r\n\t\t\t// Running campaigns first — they are the ones with live numbers.\r\n\t\t\tmapped.sort((a, b) => {\r\n\t\t\t\tconst aActive = a.status === \"ACTIVE\" ? 0 : 1;\r\n\t\t\t\tconst bActive = b.status === \"ACTIVE\" ? 0 : 1;\r\n\t\t\t\treturn aActive - bActive || a.name.localeCompare(b.name);\r\n\t\t\t});\r\n\t\t\tsetCampaigns(mapped);\r\n\t\t} catch (err: any) {\r\n\t\t\tsetCampaigns([]);\r\n\t\t\tsetError(err?.response?.data?.message || err?.response?.data?.detail || \"Failed to load campaigns\");\r\n\t\t} finally {\r\n\t\t\tsetLoading(false);\r\n\t\t}\r\n\t}, [enabled]);\r\n\r\n\tuseEffect(() => { fetchCampaigns(); }, [fetchCampaigns]);\r\n\r\n\treturn { campaigns, loading, error, refresh: fetchCampaigns };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAgB,eAAAA,cAAa,aAAAC,YAAW,WAAAC,UAAS,YAAAC,iBAAgB;;;ACIjE,mBAAkB;;;ACJlB,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AAMzD,IAAM,UAAU;AAGhB,IAAM,yBAAyB;AAqBxB,SAAS,cACf,YACA,SAMA,eACa;AACb,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA6B,IAAI;AAC3D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA6B,IAAI;AAC3D,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AAKrC,QAAM,cAAc,OAAsB,IAAI;AAI9C,QAAM,SAAS,OAAsB,UAAU;AAC/C,SAAO,UAAU;AAEjB,QAAM,OAAO,YAAY,OAAO,gBAAyB;AAxD1D;AAyDE,QAAI,cAAc,QAAQ,YAAY,YAAY,WAAY;AAC9D,gBAAY,UAAU;AACtB,QAAI,YAAa,YAAW,IAAI;AAChC,QAAI;AACH,YAAM,CAAC,UAAU,QAAQ,IAAI,MAAM,QAAQ,WAAW;AAAA,QACrD,cAAc,IAAI,UAAU,aAAa,UAAU,GAAG;AAAA,UACrD,QAAQ,EAAE,gBAAgB,iBAAiB,uBAAuB;AAAA,QACnE,CAAC;AAAA,QACD,cAAc,IAAI,UAAU,aAAa,UAAU,CAAC;AAAA,MACrD,CAAC;AAID,UAAI,OAAO,YAAY,WAAY;AAEnC,UAAI,SAAS,WAAW,YAAa,WAAS,cAAS,MAAM,SAAf,YAAuB,IAAI;AAAA,UACpE,UAAS,IAAI;AAElB,UAAI,SAAS,WAAW,aAAa;AACpC,kBAAS,cAAS,MAAM,SAAf,YAAuB,IAAI;AACpC,mBAAW,IAAI;AAAA,MAChB,OAAO;AAEN,cAAM,UAAU,oBAAS,WAAT,mBAAyB,aAAzB,mBAAmC;AACnD,iBAAS,IAAI;AACb,mBAAW,WAAW,GAAG;AAAA,MAC1B;AAOA,YAAM,SAAS,CAAC,KAAoC,aAAsB;AA1F7E,YAAAC,KAAAC;AA2FI,YAAI,IAAI,WAAW,WAAY,QAAO;AACtC,cAAM,UAAUA,OAAAD,MAAA,IAAI,WAAJ,gBAAAA,IAAoB,aAApB,gBAAAC,IAA8B;AAC9C,eAAO,EAAE,YAAY,WAAW;AAAA,MACjC;AACA,YAAM,YAAY,OAAO,UAAU,KAAK,KAAK,OAAO,UAAU,IAAI;AAClE,eAAS,YAAY,+BAA+B,EAAE;AAAA,IACvD,UAAE;AACD,UAAI,YAAY,YAAY,WAAY,aAAY,UAAU;AAC9D,UAAI,YAAa,YAAW,KAAK;AAAA,IAClC;AAAA,EACD,GAAG,CAAC,YAAY,aAAa,CAAC;AAE9B,YAAU,MAAM;AAIf,aAAS,IAAI;AACb,aAAS,IAAI;AACb,eAAW,IAAI;AACf,aAAS,EAAE;AAEX,QAAI,CAAC,WAAW,cAAc,KAAM;AAEpC,SAAK,IAAI;AACT,QAAI,QAAQ,OAAO,YAAY,MAAM;AACpC,UAAI,SAAS,oBAAoB,UAAW,MAAK,KAAK;AAAA,IACvD,GAAG,OAAO;AAGV,UAAM,YAAY,MAAM;AAAE,UAAI,SAAS,oBAAoB,UAAW,MAAK,KAAK;AAAA,IAAG;AACnF,aAAS,iBAAiB,oBAAoB,SAAS;AAEvD,WAAO,MAAM;AACZ,aAAO,cAAc,KAAK;AAC1B,cAAQ;AACR,eAAS,oBAAoB,oBAAoB,SAAS;AAAA,IAC3D;AAAA,EACD,GAAG,CAAC,SAAS,YAAY,IAAI,CAAC;AAE9B,SAAO,EAAE,OAAO,OAAO,SAAS,SAAS,OAAO,SAAS,MAAM,KAAK,IAAI,EAAE;AAC3E;;;ACGO,IAAM,uBAA0D;AAAA,EACtE,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,UAAU;AACX;AAEO,IAAM,sBAAoD;AAAA,EAChE,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,WAAW;AACZ;;;AFrGE,SAkNK,UAjNJ,KADD;AAlCF,IAAM,OAAO;AACb,IAAM,OAAO;AAGb,IAAM,oBAAoB;AAE1B,IAAM,SAAS;AAAA,EACd,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AACP;AAGA,IAAM,eAAyE;AAAA,EAC9E,SAAS,EAAE,OAAO,OAAO,MAAM,IAAI,UAAU;AAAA,EAC7C,IAAI,EAAE,OAAO,OAAO,OAAO,IAAI,UAAU;AAAA,EACzC,SAAS,EAAE,OAAO,OAAO,OAAO,IAAI,UAAU;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,KAAK,IAAI,UAAU;AAC9C;AAEA,IAAM,MAAM,CAAC,MACZ,OAAO,MAAM,WAAW,IAAI,IAAI,KAAK,QAAQ,CAAC,CAAC,MAAM;AACtD,IAAM,OAAO,CAAC,MACb,OAAO,MAAM,WAAW,GAAG,EAAE,QAAQ,CAAC,CAAC,OAAO;AAC/C,IAAM,QAAQ,CAAC,MACd,OAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,IAAI;AAExC,SAAS,KAAK,EAAE,OAAO,OAAO,KAAK,MAAM,GAEtC;AACF,SACC,qBAAC,eAAI,IAAI,EAAE,QAAQ,qBAAqB,cAAc,OAAO,IAAI,MAAM,IAAI,GAAG,UAAU,EAAE,GACzF;AAAA,wBAAC,sBAAW,IAAI,EAAE,YAAY,MAAM,UAAU,WAAW,YAAY,KAAK,eAAe,KAAK,eAAe,aAAa,OAAO,OAAO,KAAK,GAC3I,iBACF;AAAA,IACA,oBAAC,sBAAW,IAAI,EAAE,YAAY,MAAM,UAAU,WAAW,YAAY,KAAK,YAAY,KAAK,oBAAoB,gBAAgB,OAAO,SAAS,OAAO,KAAK,GACzJ,iBACF;AAAA,IACC,OACA,oBAAC,sBAAW,IAAI,EAAE,YAAY,MAAM,UAAU,WAAW,OAAO,OAAO,MAAM,YAAY,SAAS,GAChG,eACF;AAAA,KAEF;AAEF;AAGA,SAAS,YAAY,OAA+C;AACnE,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AACzC,SAAO,GAAG,MAAM,QAAQ,OAAO,MAAM,MAAM;AAC5C;AAUe,SAAR,eAAgC;AAAA,EACtC;AAAA,EAAY;AACb,GAIG;AAnFH;AAoFC,QAAM,EAAE,OAAO,OAAO,SAAS,SAAS,OAAO,QAAQ,IACtD,cAAc,YAAY,MAAM,mBAAmB;AAEpD,QAAM,UAA4B,oCAAO,sBAAP,YAA4B;AAC9D,QAAM,cAAc,aAAa,MAAM;AAGvC,QAAM,mBAAkB,+BAAO,qBAAoB;AAEnD,QAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,cAAc;AAQ7C,QAAM,aAAY,+BAAO,iBAAY,aAAAC,SAAM,MAAM,SAAS,IAAI;AAC9D,QAAM,gBAAgB,CAAC,CAAC,iBAAa,aAAAA,SAAM,EAAE,KAAK,SAAS,IAAI;AAE/D,QAAM,WAAU,0CAAO,uBAAP,YAA6B,+BAAO,uBAApC,YAA0D;AAC1E,QAAM,eAAc,0CAAO,2BAAP,YAAiC,+BAAO,2BAAxC,YAAkE;AAEtF,QAAM,UAAU,QAAO,+BAAO,mBAAkB,WAC7C,WAAW,MAAM,MAAM,aAAa,CAAC,KAAK,MAAM,oBAAoB,cAAc,OAAO,MACzF;AAEH,QAAM,iBAAgB,0CAAO,wBAAP,YAA8B,+BAAO,wBAArC,YAA4D;AAClF,QAAM,YAAW,0CAAO,mBAAP,YAAyB,+BAAO,mBAAhC,YAAkD;AACnE,QAAM,eAAe,iBAAiB,MAAM,YAAY;AAExD,QAAM,SACL,qBAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,YAAY,UAAU,KAAK,GAAG,UAAU,QAAQ,IAAI,KAAK,GACpF;AAAA,wBAAC,sBAAW,IAAI,EAAE,YAAY,MAAM,UAAU,WAAW,YAAY,KAAK,OAAO,UAAU,GAAG,yBAE9F;AAAA,IACA,qBAAC,sBAAW,IAAI,EAAE,YAAY,MAAM,UAAU,WAAW,OAAO,OAAO,KAAK,GAAG;AAAA;AAAA,OACxE,oCAAO,kBAAP,YAAwB;AAAA,MAAG;AAAA,OAClC;AAAA,IACA,oBAAC,eAAI,IAAI,EAAE,MAAM,EAAE,GAAG;AAAA,IACrB,SACA,oBAAC,mBAAQ,OAAK,MAAC,OAAM,uFACpB;AAAA,MAAC;AAAA;AAAA,QACA,MAAK;AAAA,QACL,OAAM;AAAA,QACN,IAAI,EAAE,QAAQ,IAAI,UAAU,WAAW,YAAY,KAAK,iBAAiB,WAAW,OAAO,OAAO,KAAK;AAAA;AAAA,IACxG,GACD;AAAA,IAEA,SACA;AAAA,MAAC;AAAA;AAAA,QACA,OAAK;AAAA,QACL,OAAO,kBACJ,kEACA;AAAA,QAEH;AAAA,UAAC;AAAA;AAAA,YACA,MAAK;AAAA,YACL,OAAO,kBAAkB,aAAa;AAAA,YACtC,IAAI;AAAA,cACH,QAAQ;AAAA,cAAI,UAAU;AAAA,cAAW,YAAY;AAAA,cAC7C,iBAAiB,kBAAkB,YAAY;AAAA,cAC/C,OAAO,kBAAkB,OAAO,QAAQ,OAAO;AAAA,YAChD;AAAA;AAAA,QACD;AAAA;AAAA,IACD;AAAA,IAED;AAAA,MAAC;AAAA;AAAA,QACA,MAAK;AAAA,QACL,OAAO,qBAAqB,MAAM;AAAA,QAClC,IAAI,EAAE,QAAQ,IAAI,UAAU,WAAW,YAAY,KAAK,iBAAiB,YAAY,IAAI,OAAO,YAAY,MAAM;AAAA;AAAA,IACnH;AAAA,IACA,oBAAC,mBAAQ,OAAM,WAAU,OAAK,MAC7B,8BAAC,UACA,8BAAC,sBAAW,MAAK,SAAQ,SAAS,SAAS,UAAU,SACnD,oBAAU,oBAAC,4BAAiB,MAAM,IAAI,IAAK,oBAAC,mBAAY,IAAI,EAAE,UAAU,GAAG,GAAG,GAChF,GACD,GACD;AAAA,KACD;AAQD,QAAM,YAAW,+BAAO,sBAAqB;AAE7C,QAAM,iBAAiB,YACtB;AAAA,IAAC;AAAA;AAAA,MACA,IAAI;AAAA,QACH,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,KAAK;AAAA,QACL,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,cAAc;AAAA,QACd,QAAQ,aAAa,OAAO,KAAK;AAAA,QACjC,iBAAiB;AAAA,MAClB;AAAA,MAEA;AAAA,4BAAC,wBAAiB,IAAI,EAAE,OAAO,OAAO,OAAO,UAAU,GAAG,GAAG;AAAA,QAC7D,qBAAC,eACA;AAAA,8BAAC,sBAAW,IAAI,EAAE,YAAY,MAAM,UAAU,WAAW,YAAY,KAAK,OAAO,OAAO,MAAM,GAC5F,0BACE,oBAAoB,UAAW,OAAO,QAAQ,CAAC,iDAC/C,mEACJ;AAAA,UACA,oBAAC,sBAAW,IAAI,EAAE,YAAY,MAAM,UAAU,UAAU,OAAO,OAAO,KAAK,GACzE,0BACE,4FACA,0FACJ;AAAA,WACD;AAAA;AAAA;AAAA,EACD;AAGD,QAAM,OAAO,MAAM;AA3MpB,QAAAC,KAAAC,KAAAC;AA4ME,QAAI,CAAC,SAAS;AACb,aACC,oBAAC,sBAAW,IAAI,EAAE,YAAY,MAAM,UAAU,WAAW,OAAO,OAAO,KAAK,GAAG,6EAE/E;AAAA,IAEF;AACA,QAAI,OAAO;AACV,aACC,oBAAC,sBAAW,IAAI,EAAE,YAAY,MAAM,UAAU,WAAW,OAAO,OAAO,IAAI,GAAI,iBAAM;AAAA,IAEvF;AACA,QAAI,CAAC,SAAS,SAAS;AACtB,aAAO,oBAAC,4BAAiB,MAAM,IAAI,IAAI,EAAE,OAAO,OAAO,MAAM,IAAI,EAAE,GAAG;AAAA,IACvE;AAEA,WACC,iCACC;AAAA,2BAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,qBAAqB,EAAE,IAAI,WAAW,IAAI,iBAAiB,GAAG,KAAK,EAAE,GAChG;AAAA;AAAA,UAAC;AAAA;AAAA,YACA,OAAM;AAAA,YACN,OAAO,IAAI,+BAAO,WAAW;AAAA,YAC7B,OAAO,YAAY;AAAA,YACnB,KAAK;AAAA,eACJ,+BAAO,sBAAqB,OAAO,UAAU,IAAI,MAAM,iBAAiB,CAAC,KAAK;AAAA;AAAA,eAE9E,+BAAO,aAAY,kBAAkB;AAAA,YACtC,EAAE,OAAO,OAAO,EAAE,KAAK,QAAK,KAAK;AAAA;AAAA,QAClC;AAAA,QACA,oBAAC,QAAK,OAAM,eAAc,OAAO,IAAI,+BAAO,UAAU,GAAG,OAAO,OAAO,OAAO,KAAK,YAAY,KAAK,GAAG;AAAA,QACvG,oBAAC,QAAK,OAAM,iBAAgB,OAAO,KAAK,+BAAO,qBAAqB,GAAG,KAAI,WAAU;AAAA,QACrF;AAAA,UAAC;AAAA;AAAA,YACA,OAAM;AAAA,YACN,OAAO,MAAM,OAAO;AAAA,YACpB,KAAK;AAAA;AAAA,cAEJ,CAAC,mBAAmB,eAAe,OAAO,sBAAsB,MAAM,WAAW,CAAC,KAAK;AAAA;AAAA;AAAA,cAGvF;AAAA,YACD,EAAE,OAAO,OAAO,EAAE,KAAK,QAAK,KAAK;AAAA;AAAA,QAClC;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACA,OAAM;AAAA,YACN,OAAO,IAAGF,MAAA,+BAAO,gBAAP,OAAAA,MAAsB,CAAC,OAAMC,MAAA,+BAAO,oBAAP,OAAAA,MAA0B,CAAC;AAAA,YAClE,KAAI;AAAA;AAAA,QACL;AAAA,SACD;AAAA,MAGA,qBAAC,eAAI,IAAI,EAAE,IAAI,KAAK,GACnB;AAAA,4BAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,QAAQ,IAAI,cAAc,OAAO,UAAU,UAAU,QAAQ,oBAAoB,GAC3G,wBAAc,IACd,iCACC;AAAA,8BAAC,eAAI,IAAI,EAAE,OAAO,GAAI,gBAAgB,cAAe,GAAG,KAAK,iBAAiB,OAAO,KAAK,GAAG;AAAA,UAC7F,oBAAC,eAAI,IAAI,EAAE,OAAO,GAAI,WAAW,cAAe,GAAG,KAAK,iBAAiB,OAAO,MAAM,GAAG;AAAA,WAC1F,IAEA,oBAAC,eAAI,IAAI,EAAE,OAAO,QAAQ,iBAAiB,UAAU,GAAG,GAE1D;AAAA,QACA,qBAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,UAAU,QAAQ,KAAK,YAAY,IAAI,KAAK,YAAY,MAAM,UAAU,UAAU,OAAO,OAAO,KAAK,GAChI;AAAA,+BAAC,UAAK;AAAA,gCAAC,eAAI,WAAU,QAAO,IAAI,EAAE,SAAS,gBAAgB,OAAO,GAAG,QAAQ,GAAG,cAAc,OAAO,iBAAiB,OAAO,MAAM,IAAI,IAAI,GAAG;AAAA,YAAE;AAAA,YAAU;AAAA,aAAc;AAAA,UACxK,qBAAC,UAAK;AAAA,gCAAC,eAAI,WAAU,QAAO,IAAI,EAAE,SAAS,gBAAgB,OAAO,GAAG,QAAQ,GAAG,cAAc,OAAO,iBAAiB,OAAO,OAAO,IAAI,IAAI,GAAG;AAAA,YAAE;AAAA,YAAkB;AAAA,aAAS;AAAA,WAC3K,+BAAO,iBAAgB,oBAAC,UAAM,WAAAC,MAAA,oBAAoB,MAAM,YAAY,MAAtC,OAAAA,MAA2C,MAAM,cAAa;AAAA,WAC9F;AAAA,SACD;AAAA,MAEC,SACA,qBAAC,sBAAW,IAAI,EAAE,YAAY,MAAM,UAAU,UAAU,OAAO,OAAO,MAAM,IAAI,KAAK,GAAG;AAAA;AAAA,SAC9C,+BAAO,iBAAY,aAAAH,SAAM,MAAM,SAAS,EAAE,OAAO,eAAe,IAAI;AAAA,SAC9G;AAAA,OAEF;AAAA,EAEF;AAEA,SACC;AAAA,IAAC;AAAA;AAAA,MACA,WAAW;AAAA,MACX,IAAI;AAAA,QACH,cAAc;AAAA,QACd,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,GAAG,EAAE,IAAI,KAAK,IAAI,EAAE;AAAA,MACrB;AAAA,MAEC;AAAA;AAAA,QACA;AAAA,QACA,KAAK;AAAA;AAAA;AAAA,EACP;AAEF;;;AGxSA,SAAS,YAAAI,iBAAgB;AAIzB,IAAAC,gBAAkB;;;ACJlB,SAAS,eAAAC,cAAa,aAAAC,YAAW,UAAAC,SAAQ,YAAAC,iBAAgB;AAMzD,IAAMC,WAAU;AAEhB,IAAM,QAAQ;AASP,SAAS,gBAAgB,YAA2B,SAAkB;AAC5E,QAAM,CAAC,QAAQ,SAAS,IAAIC,UAAwB,CAAC,CAAC;AACtD,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,KAAK;AAG5C,QAAM,cAAcC,QAAsB,IAAI;AAE9C,QAAM,SAASA,QAAsB,UAAU;AAC/C,SAAO,UAAU;AAEjB,QAAM,OAAOC,aAAY,OAAO,gBAAyB;AA3B1D;AA4BE,QAAI,cAAc,QAAQ,YAAY,YAAY,WAAY;AAC9D,gBAAY,UAAU;AACtB,QAAI,YAAa,YAAW,IAAI;AAChC,QAAI;AACH,YAAM,MAAM,MAAM,cAAc,IAAI,UAAU,cAAc,UAAU,GAAG;AAAA,QACxE,QAAQ,EAAE,OAAO,MAAM;AAAA,MACxB,CAAC;AACD,UAAI,OAAO,YAAY,WAAY;AACnC,YAAM,OAAO,IAAI;AACjB,gBAAU,MAAM,QAAQ,IAAI,IAAI,QAAQ,kCAAM,SAAN,YAAc,CAAC,CAAE;AAAA,IAC1D,SAAQ;AAGP,gBAAU,CAAC,CAAC;AAAA,IACb,UAAE;AACD,UAAI,YAAY,YAAY,WAAY,aAAY,UAAU;AAC9D,UAAI,YAAa,YAAW,KAAK;AAAA,IAClC;AAAA,EACD,GAAG,CAAC,UAAU,CAAC;AAEf,EAAAC,WAAU,MAAM;AAEf,cAAU,CAAC,CAAC;AAEZ,QAAI,CAAC,WAAW,cAAc,KAAM;AAEpC,SAAK,IAAI;AACT,UAAM,QAAQ,OAAO,YAAY,MAAM;AACtC,UAAI,SAAS,oBAAoB,UAAW,MAAK,KAAK;AAAA,IACvD,GAAGJ,QAAO;AACV,UAAM,YAAY,MAAM;AAAE,UAAI,SAAS,oBAAoB,UAAW,MAAK,KAAK;AAAA,IAAG;AACnF,aAAS,iBAAiB,oBAAoB,SAAS;AAEvD,WAAO,MAAM;AACZ,aAAO,cAAc,KAAK;AAC1B,eAAS,oBAAoB,oBAAoB,SAAS;AAAA,IAC3D;AAAA,EACD,GAAG,CAAC,SAAS,YAAY,IAAI,CAAC;AAE9B,SAAO,EAAE,QAAQ,SAAS,SAAS,MAAM,KAAK,IAAI,EAAE;AACrD;;;ADhCE,SACC,OAAAK,MADD,QAAAC,aAAA;AA5BF,IAAMC,QAAO;AAEb,IAAMC,UAAS;AAAA,EACd,KAAK;AAAA,EACL,OAAO;AAAA,EACP,OAAO;AAAA,EACP,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AACP;AAEA,IAAM,cAAqF;AAAA;AAAA,EAE1F,aAAa,EAAE,OAAO,aAAa,OAAOA,QAAO,OAAO,IAAIA,QAAO,QAAQ;AAAA,EAC3E,cAAc,EAAE,OAAO,mBAAmB,OAAOA,QAAO,OAAO,IAAIA,QAAO,QAAQ;AAAA;AAAA,EAElF,gBAAgB,EAAE,OAAO,gBAAgB,OAAOA,QAAO,KAAK,IAAIA,QAAO,MAAM;AAAA,EAC7E,eAAe,EAAE,OAAO,aAAa,OAAOA,QAAO,OAAO,IAAIA,QAAO,QAAQ;AAC9E;AAEA,IAAM,OAAO,CAAC,UAA0B,YAAQ,cAAAC,SAAM,KAAK,EAAE,OAAO,eAAe,IAAI;AAEvF,SAAS,SAAS,EAAE,MAAM,GAA2B;AA/BrD;AAgCC,QAAM,SAAQ,iBAAY,MAAM,SAAS,MAA3B,YAAgC;AAAA,IAC7C,OAAO,MAAM;AAAA,IAAW,OAAOD,QAAO;AAAA,IAAM,IAAI;AAAA,EACjD;AACA,SACC,gBAAAF,MAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,YAAY,cAAc,KAAK,GAAG,IAAI,KAAK,WAAW,oBAAoB,GACrG;AAAA,oBAAAD;AAAA,MAAC;AAAA;AAAA,QACA,MAAK;AAAA,QACL,OAAO,MAAM;AAAA,QACb,IAAI,EAAE,QAAQ,IAAI,UAAU,WAAW,YAAY,KAAK,iBAAiB,MAAM,IAAI,OAAO,MAAM,OAAO,YAAY,EAAE;AAAA;AAAA,IACtH;AAAA,IACA,gBAAAA,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,OAAO,WAAW,MAAM,GAAG,UAAU,EAAE,GAC9F,gBAAM,SACR;AAAA,IACA,gBAAAF,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,OAAOC,QAAO,MAAM,YAAY,UAAU,YAAY,EAAE,GAC/G,eAAK,MAAM,SAAS,GACtB;AAAA,KACD;AAEF;AAWe,SAAR,aAA8B,EAAE,WAAW,GAA2B;AAC5E,QAAM,EAAE,QAAQ,QAAQ,IAAI,gBAAgB,YAAY,IAAI;AAG5D,QAAM,CAAC,aAAa,cAAc,IAAIE,UAAS,IAAI;AAEnD,MAAI,WAAW,OAAO,WAAW,EAAG,QAAO;AAC3C,MAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAM,UAAU;AAEhB,SACC,gBAAAL,KAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,EAAE,GAC1D,kBAAQ,SAAS,KACjB,gBAAAC;AAAA,IAAC;AAAA;AAAA,MACA,WAAW;AAAA,MACX,IAAI,EAAE,cAAc,QAAQ,QAAQ,qBAAqB,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE,GAAG,IAAI,EAAE;AAAA,MAEvF;AAAA,wBAAAA;AAAA,UAAC;AAAA;AAAA,YACA,SAAS,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;AAAA,YACvC,IAAI,EAAE,SAAS,QAAQ,YAAY,UAAU,KAAK,KAAK,QAAQ,WAAW,YAAY,OAAO;AAAA,YAE5F;AAAA,4BAAc,gBAAAD,KAAC,6BAAsB,IAAI,EAAE,UAAU,IAAI,OAAOG,QAAO,KAAK,GAAG,IAAK,gBAAAH,KAAC,8BAAuB,IAAI,EAAE,UAAU,IAAI,OAAOG,QAAO,KAAK,GAAG;AAAA,cACvJ,gBAAAH,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,YAAY,KAAK,OAAO,UAAU,GAAG,2BAE9F;AAAA,cACA,gBAAAD,MAAC,sBAAW,IAAI,EAAE,YAAYC,OAAM,UAAU,WAAW,OAAOC,QAAO,KAAK,GAC1E;AAAA,wBAAQ;AAAA,gBAAO;AAAA,iBACjB;AAAA;AAAA;AAAA,QACD;AAAA,QACA,gBAAAH,KAAC,oBAAS,IAAI,aAAa,eAAa,MACvC,0BAAAA,KAAC,eAAI,IAAI,EAAE,IAAI,IAAI,GACjB,kBAAQ,IAAI,CAAC,MAAM,gBAAAA,KAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAAE,GACtD,GACD;AAAA;AAAA;AAAA,EACD,GAEF;AAEF;;;AEpGA,SAAS,SAAS,YAAAM,iBAAgB;AAalC,IAAAC,gBAAkB;;;ACZlB,IAAAC,gBAAkB;AADlB,SAAS,eAAAC,cAAa,aAAAC,YAAW,UAAAC,SAAQ,YAAAC,iBAAgB;AAOlD,IAAM,iBAAiB;AAAA,EAC7B,EAAE,KAAK,MAAM,OAAO,OAAO,OAAO,EAAE;AAAA,EACpC,EAAE,KAAK,MAAM,OAAO,OAAO,OAAO,EAAE;AAAA,EACpC,EAAE,KAAK,OAAO,OAAO,QAAQ,OAAO,GAAG;AAAA,EACvC,EAAE,KAAK,MAAM,OAAO,UAAU,OAAO,KAAK,EAAE;AAC7C;AAIA,IAAM,QAAuB,EAAE,eAAe,GAAG,QAAQ,CAAC,EAAE;AASrD,SAAS,iBAAiB,YAA2B,OAAwB,SAAkB;AACrG,QAAM,CAAC,SAAS,UAAU,IAAIC,UAAwB,KAAK;AAC3D,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAS,EAAE;AAOrC,QAAM,YAAYC,QAAO,CAAC;AAE1B,QAAM,OAAOC,aAAY,YAAY;AArCtC;AAsCE,QAAI,cAAc,QAAQ,CAAC,QAAS;AACpC,UAAM,SAAQ,0BAAe,KAAK,CAAC,MAAM,EAAE,QAAQ,KAAK,MAA1C,mBAA6C,UAA7C,YAAsD;AACpE,UAAM,KAAK,EAAE,UAAU;AACvB,eAAW,IAAI;AACf,aAAS,EAAE;AACX,QAAI;AACH,YAAM,MAAM,MAAM,cAAc,IAAI,UAAU,qBAAqB,UAAU,GAAG;AAAA,QAC/E,QAAQ;AAAA;AAAA,UAEP,WAAO,cAAAC,SAAM,EAAE,SAAS,OAAO,MAAM,EAAE,OAAO,qBAAqB;AAAA,UACnE,SAAK,cAAAA,SAAM,EAAE,OAAO,qBAAqB;AAAA,QAC1C;AAAA,MACD,CAAC;AACD,UAAI,OAAO,UAAU,QAAS;AAC9B,YAAM,OAAO,IAAI,QAAQ,CAAC;AAC1B,iBAAW;AAAA,QACV,gBAAe,UAAK,kBAAL,YAAsB;AAAA,QACrC,QAAQ,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;AAAA,MACrD,CAAC;AAAA,IACF,SAAS,KAAU;AAClB,UAAI,OAAO,UAAU,QAAS;AAC9B,iBAAW,KAAK;AAChB,iBAAS,sCAAK,aAAL,mBAAe,SAAf,mBAAqB,YAAW,+BAA+B;AAAA,IACzE,UAAE;AAGD,UAAI,OAAO,UAAU,QAAS,YAAW,KAAK;AAAA,IAC/C;AAAA,EACD,GAAG,CAAC,YAAY,OAAO,OAAO,CAAC;AAE/B,EAAAC,WAAU,MAAM;AAAE,SAAK;AAAA,EAAG,GAAG,CAAC,IAAI,CAAC;AAEnC,SAAO,EAAE,SAAS,SAAS,OAAO,SAAS,KAAK;AACjD;;;AD5BG,gBAAAC,MAIC,QAAAC,aAJD;AA1BH,IAAMC,QAAO;AAEb,IAAMC,UAAS;AAAA,EACd,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AACP;AAUA,SAAS,WAAW,OAAe,WAA4B;AAC9D,aAAO,cAAAC,SAAM,KAAK,EAAE,OAAO,YAAY,gBAAgB,OAAO;AAC/D;AAEA,SAAS,aAAa,EAAE,QAAQ,SAAS,MAAM,GAAQ;AACtD,MAAI,CAAC,UAAU,EAAC,mCAAS,QAAQ,QAAO;AACxC,SACC,gBAAAH,MAAC,eAAI,IAAI,EAAE,iBAAiB,QAAQ,QAAQ,qBAAqB,cAAc,OAAO,IAAI,MAAM,IAAI,MAAM,WAAW,iCAAiC,GACrJ;AAAA,oBAAAD,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,UAAU,OAAOC,QAAO,MAAM,IAAI,IAAI,GAClF,4BAAAC,SAAM,KAAK,EAAE,OAAO,mBAAmB,GACzC;AAAA,IACC,QAAQ,IAAI,CAAC,UACb,gBAAAH,MAAC,sBAA+B,IAAI,EAAE,YAAYC,OAAM,UAAU,WAAW,YAAY,KAAK,OAAO,MAAM,MAAM,GAC/G;AAAA,YAAM;AAAA,MAAK;AAAA,MAAG,MAAM,SAAS,OAAO,WAAM,MAAM,YAAY,eAAe,GAAG,MAAM,MAAM,QAAQ,CAAC,CAAC,MAAM,MAAM,MAAM,QAAQ,CAAC;AAAA,SADhH,MAAM,OAEvB,CACA;AAAA,KACF;AAEF;AAae,SAAR,oBAAqC,EAAE,WAAW,GAA2B;AAInF,QAAM,CAAC,OAAO,QAAQ,IAAIG,UAA0B,IAAI;AACxD,QAAM,EAAE,SAAS,SAAS,MAAM,IAAI,iBAAiB,YAAY,OAAO,IAAI;AAE5E,QAAM,YAAY,UAAU,SAAS,UAAU;AAE/C,QAAM,OAAO,QAAsB,MAAM;AACxC,UAAM,SAAS,QAAQ,OACrB,IAAI,CAAC,MAAG;AA7EZ;AA6EgB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKZ,OAAG,cAAAD,SAAM,EAAE,SAAS,EAAE,QAAQ;AAAA;AAAA,QAE9B,YAAY,OAAO,EAAE,gBAAgB,WAAW,EAAE,cAAc,MAAM;AAAA,QACtE,UAAS,OAAE,uBAAF,YAAwB;AAAA,QACjC,cAAa,OAAE,2BAAF,YAA4B;AAAA,MAC1C;AAAA,KAAE,EACD,OAAO,CAAC,MAAM,OAAO,SAAS,EAAE,CAAC,CAAC,EAClC,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAY1B,UAAM,QAAQ,KAAK,IAAI,QAAQ,eAAe,CAAC,IAAI,MAAS;AAC5D,UAAM,WAAyB,CAAC;AAChC,WAAO,QAAQ,CAAC,OAAO,MAAM;AAC5B,YAAM,WAAW,OAAO,IAAI,CAAC;AAC7B,UAAI,YAAY,MAAM,IAAI,SAAS,IAAI,OAAO;AAC7C,iBAAS,KAAK,EAAE,GAAG,SAAS,IAAI,GAAG,YAAY,MAAM,SAAS,MAAM,aAAa,KAAK,CAAC;AAAA,MACxF;AACA,eAAS,KAAK,KAAK;AAAA,IACpB,CAAC;AACD,WAAO;AAAA,EACR,GAAG,CAAC,QAAQ,QAAQ,QAAQ,aAAa,CAAC;AAG1C,QAAM,YAAY,QAAQ,MAAM;AAC/B,aAAS,IAAI,QAAQ,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;AACvD,YAAM,IAAI,QAAQ,OAAO,CAAC,EAAE;AAC5B,UAAI,OAAO,MAAM,SAAU,QAAO,IAAI;AAAA,IACvC;AACA,WAAO;AAAA,EACR,GAAG,CAAC,QAAQ,MAAM,CAAC;AAEnB,QAAM,YAAY;AAAA,IACjB,QAAQD,QAAO;AAAA,IACf,MAAM,EAAE,UAAU,IAAI,YAAYD,OAAM,MAAMC,QAAO,KAAK;AAAA,EAC3D;AAEA,QAAM,QACL,gBAAAH;AAAA,IAAC;AAAA;AAAA,MACA,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,OAAM;AAAA,MACN,QAAQ,CAAC,WAAW,SAAS;AAAA,MAC7B,eAAe,CAAC,MAAc,WAAW,GAAG,SAAS;AAAA,OACjD;AAAA,EACL;AAGD,QAAM,OAAO,MAAM;AAClB,QAAI,WAAW,KAAK,WAAW,GAAG;AACjC,aAAO,gBAAAA,KAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,gBAAgB,UAAU,IAAI,EAAE,GAAG,0BAAAA,KAAC,4BAAiB,MAAM,IAAI,IAAI,EAAE,OAAOG,QAAO,KAAK,GAAG,GAAE;AAAA,IACjI;AACA,QAAI,OAAO;AACV,aAAO,gBAAAH,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,OAAOC,QAAO,KAAK,IAAI,EAAE,GAAI,iBAAM;AAAA,IACpG;AACA,QAAI,KAAK,WAAW,GAAG;AACtB,aACC,gBAAAH,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,OAAOC,QAAO,MAAM,IAAI,EAAE,GAAG,gHAEtF;AAAA,IAEF;AAEA,WACC,gBAAAF,MAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,qBAAqB,EAAE,IAAI,OAAO,IAAI,UAAU,GAAG,KAAK,EAAE,GACrF;AAAA,sBAAAA,MAAC,eACA;AAAA,wBAAAD,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,YAAY,KAAK,OAAO,WAAW,IAAI,IAAI,GAAG,0BAEvG;AAAA,QACA,gBAAAF,KAAC,uBAAoB,OAAM,QAAO,QAAQ,KACzC,0BAAAC,MAAC,aAAU,MAAY,QAAQ,EAAE,KAAK,GAAG,OAAO,IAAI,QAAQ,GAAG,MAAM,IAAI,GACxE;AAAA,0BAAAD,KAAC,iBAAc,QAAQG,QAAO,MAAM,UAAU,OAAO;AAAA,UACpD;AAAA,UACD,gBAAAH,KAAC,wBAAM,MAAK,OAAQ,UAAW;AAAA,UAC/B,gBAAAA,KAAC,WAAQ,SAAS,gBAAAA,KAAC,gBAAa,GAAI;AAAA,UACnC,aAAa,QACb,gBAAAA;AAAA,YAAC;AAAA;AAAA,cACA,GAAG;AAAA,cACH,QAAQG,QAAO;AAAA,cACf,iBAAgB;AAAA,cAChB,OAAO,EAAE,OAAO,UAAU,UAAU,QAAQ,CAAC,CAAC,KAAK,UAAU,kBAAkB,UAAU,IAAI,MAAMA,QAAO,KAAK,YAAYD,MAAK;AAAA;AAAA,UACjI;AAAA,UAED,gBAAAF;AAAA,YAAC;AAAA;AAAA,cAGA,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,QAAQG,QAAO;AAAA,cACf,aAAa;AAAA,cACb,KAAK;AAAA,cAEL,cAAc;AAAA;AAAA,UACf;AAAA,WACD,GACD;AAAA,SACD;AAAA,MAEA,gBAAAF,MAAC,eACA;AAAA,wBAAAD,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,YAAY,KAAK,OAAO,WAAW,IAAI,IAAI,GAAG,sDAEvG;AAAA,QACA,gBAAAF,KAAC,uBAAoB,OAAM,QAAO,QAAQ,KACzC,0BAAAC,MAAC,aAAU,MAAY,QAAQ,EAAE,KAAK,GAAG,OAAO,IAAI,QAAQ,GAAG,MAAM,IAAI,GACxE;AAAA,0BAAAD,KAAC,iBAAc,QAAQG,QAAO,MAAM,UAAU,OAAO;AAAA,UACpD;AAAA,UACD,gBAAAH,KAAC,0BAAU,UAAW;AAAA,UACtB,gBAAAA,KAAC,WAAQ,SAAS,gBAAAA,KAAC,gBAAa,GAAI;AAAA,UACpC,gBAAAA,KAAC,UAAO,cAAc,EAAE,YAAYE,OAAM,UAAU,SAAS,GAAG;AAAA,UAGhE,gBAAAF,KAAC,QAAK,MAAK,aAAY,SAAQ,WAAU,MAAK,WAAU,QAAQG,QAAO,MAAM,aAAa,GAAG,KAAK,OAAO,cAAc,OAAO;AAAA,UAC9H,gBAAAH,KAAC,QAAK,MAAK,aAAY,SAAQ,eAAc,MAAK,sBAAqB,QAAQG,QAAO,OAAO,aAAa,GAAG,iBAAgB,OAAM,KAAK,OAAO,cAAc,OAAO;AAAA,WACrK,GACD;AAAA,SACD;AAAA,OACD;AAAA,EAEF;AAEA,SACC,gBAAAF;AAAA,IAAC;AAAA;AAAA,MACA,WAAW;AAAA,MACX,IAAI,EAAE,cAAc,QAAQ,QAAQ,qBAAqB,WAAW,8BAA8B,GAAG,EAAE,IAAI,KAAK,IAAI,EAAE,EAAE;AAAA,MAExH;AAAA,wBAAAA,MAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,YAAY,UAAU,KAAK,GAAG,UAAU,QAAQ,IAAI,KAAK,GACpF;AAAA,0BAAAD,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,YAAY,KAAK,OAAO,UAAU,GAAG,4BAE9F;AAAA,UACC,QAAQ,OAAO,SAAS,KACxB,gBAAAD,MAAC,sBAAW,IAAI,EAAE,YAAYC,OAAM,UAAU,WAAW,OAAOC,QAAO,KAAK,GAC1E;AAAA,oBAAQ;AAAA,YAAc;AAAA,aACxB;AAAA,UAED,gBAAAH,KAAC,eAAI,IAAI,EAAE,MAAM,EAAE,GAAG;AAAA,UACrB,eAAe,IAAI,CAAC,MACpB,gBAAAA;AAAA,YAAC;AAAA;AAAA,cAEA,SAAS,UAAU,EAAE,MAAM,YAAY;AAAA,cACvC,SAAS,MAAM,SAAS,EAAE,GAAG;AAAA,cAC7B,IAAI,EAAE,QAAQ,IAAI,UAAU,GAAG,IAAI,KAAK;AAAA,cAEvC,YAAE;AAAA;AAAA,YALE,EAAE;AAAA,UAMR,CACA;AAAA,WACF;AAAA,QACC,KAAK;AAAA;AAAA;AAAA,EACP;AAEF;;;AEhPA,SAAgB,eAAAM,cAAa,aAAAC,YAAW,WAAAC,UAAS,YAAAC,iBAAgB;AASjE,IAAAC,gBAA6B;;;ACT7B,SAAS,WAAAC,UAAS,YAAAC,iBAAgB;AAQlC,IAAAC,gBAAkB;AA8MX,SACC,OAAAC,MADD,QAAAC,aAAA;AA3MP,IAAMC,QAAO;AACb,IAAMC,QAAO;AAGb,IAAM,eAAe;AAErB,IAAMC,UAAS;AAAA,EACd,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AAAA,EACN,MAAM;AACP;AAkBA,IAAM,iBAAiB;AAEvB,IAAMC,OAAM,CAAC,GAA8B,WAAW,MACrD,OAAO,MAAM,WAAW,IAAI,IAAI,KAAK,QAAQ,QAAQ,CAAC,MAAMF;AAC7D,IAAMG,QAAO,CAAC,MACb,OAAO,MAAM,WAAW,GAAG,EAAE,QAAQ,CAAC,CAAC,OAAOH;AAC/C,IAAM,MAAM,CAAC,MACZ,OAAO,MAAM,WAAW,EAAE,eAAe,IAAIA;AAC9C,IAAMI,SAAQ,CAAC,MACd,OAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,IAAIJ;AACxC,IAAM,SAAS,CAAC,MAAiC;AAChD,MAAI,OAAO,MAAM,SAAU,QAAOA;AAClC,QAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;AAChC,MAAI,QAAQ,EAAG,QAAO,IAAI,KAAK;AAC/B,MAAI,QAAQ,EAAG,QAAO,SAAI,KAAK,IAAI,KAAK,CAAC;AACzC,SAAO;AACR;AAGA,IAAM,QAAQ,CAAC,OAAe,KAAc,OAAe,QAAwB,CAAC,MAAc;AAAA,EACjG;AAAA,EACA;AAAA,EACA,OAAO,QAAQ,QAAQ,QAAQ,UAAa,UAAUA;AAAA,GACnD;AAGJ,SAAS,cAAc,KAAgC;AArEvD;AAsEC,QAAM,cAAa,SAAI,kBAAJ,YAAqB,KAAK;AAC7C,QAAM,iBAAgB,SAAI,kBAAJ,YAAqB,OAAO,OAAM,SAAI,oBAAJ,YAAuB,KAAK;AAEpF,SAAO;AAAA,IACN;AAAA,MACC,OAAO;AAAA,MACP,QAAQC,QAAO;AAAA,MACf,QAAQ;AAAA,QACP,MAAM,UAAU,IAAI,QAAQ,IAAI,IAAI,MAAM,CAAC;AAAA,QAC3C,MAAM,YAAY,IAAI,UAAU,IAAI,IAAI,QAAQ,GAAG,EAAE,OAAOA,QAAO,MAAM,CAAC;AAAA,QAC1E,MAAM,yBAAyB,IAAI,gBAAgB,IAAI,IAAI,cAAc,GAAG,EAAE,OAAOA,QAAO,MAAM,CAAC;AAAA,QACnG,MAAM,aAAa,IAAI,WAAW,IAAI,IAAI,SAAS,GAAG,EAAE,OAAOA,QAAO,IAAI,CAAC;AAAA,QAC3E,MAAM,iBAAiB,IAAI,sBAAsB,IAAI,IAAI,oBAAoB,GAAG;AAAA,UAC/E,OAAOA,QAAO;AAAA,UACd,MAAM;AAAA,QACP,CAAC;AAAA,QACD,MAAM,oBAAoB,IAAI,yBAAyB,IAAI,IAAI,uBAAuB,GAAG,EAAE,OAAOA,QAAO,MAAM,CAAC;AAAA,QAChH,MAAM,iBAAiB,IAAI,cAAc,IAAI,IAAI,YAAY,CAAC;AAAA,MAC/D;AAAA,IACD;AAAA,IACA;AAAA,MACC,OAAO;AAAA,MACP,QAAQA,QAAO;AAAA,MACf,QAAQ;AAAA,QACP,MAAM,eAAe,IAAI,YAAYC,KAAI,IAAI,UAAU,GAAG,EAAE,OAAOD,QAAO,MAAM,CAAC;AAAA,QACjF,MAAM,gBAAgB,IAAI,aAAaC,KAAI,IAAI,WAAW,GAAG;AAAA,UAC5D,OAAO,IAAI,iBAAiBD,QAAO,MAAMA,QAAO;AAAA,QACjD,CAAC;AAAA,QACD,MAAM,UAAU,IAAI,mBAAmBC,KAAI,IAAI,iBAAiB,CAAC;AAAA,QACjE,MAAM,iBAAiB,IAAI,gBAAgB,IAAI,iBAAiB,0BAAqB,OAAO;AAAA,UAC3F,OAAO,IAAI,iBAAiBD,QAAO,MAAMA,QAAO;AAAA,QACjD,CAAC;AAAA,MACF;AAAA,IACD;AAAA,IACA;AAAA,MACC,OAAO;AAAA,MACP,QAAQA,QAAO;AAAA,MACf,QAAQ;AAAA,QACP,MAAM,4BAA4B,IAAI,uBAAuBE,MAAK,IAAI,qBAAqB,GAAG;AAAA,UAC7F,OAAOF,QAAO;AAAA,QACf,CAAC;AAAA,QACD,MAAM,kCAAkC,IAAI,uBAAuBE,MAAK,IAAI,qBAAqB,GAAG;AAAA,UACnG,OAAOF,QAAO;AAAA,UACd,MAAM;AAAA,QACP,CAAC;AAAA,MACF;AAAA,IACD;AAAA,IACA;AAAA,MACC,OAAO;AAAA,MACP,QAAQA,QAAO;AAAA,MACf,QAAQ;AAAA,QACP,MAAM,0BAA0B,IAAI,eAAe,IAAI,IAAI,aAAa,CAAC;AAAA,QACzE,MAAM,mCAAmC,IAAI,iBAAiB,IAAI,IAAI,eAAe,CAAC;AAAA;AAAA,QAEtF,GAAI,eAAe,CAAC,IAAI;AAAA,UACvB,MAAM,cAAc,IAAI,iBAAiBG,OAAM,IAAI,eAAe,GAAG,EAAE,OAAOH,QAAO,KAAK,CAAC;AAAA,UAC3F,MAAM,8BAA8B,IAAI,qBAAqBG,OAAM,IAAI,mBAAmB,GAAG,EAAE,OAAOH,QAAO,MAAM,CAAC;AAAA,UACpH,MAAM,oCAAoC,IAAI,oBAAoB,OAAO,IAAI,kBAAkB,GAAG;AAAA,YACjG,SAAQ,SAAI,uBAAJ,YAA0B,MAAM,IAAIA,QAAO,QAAQA,QAAO;AAAA,YAClE,MAAM;AAAA,UACP,CAAC;AAAA,UACD,MAAM,sCAAsC,IAAI,uBAAuBC,KAAI,IAAI,qBAAqB,CAAC;AAAA,UACrG,MAAM,oCAAoC,IAAI,wBAAwBA,KAAI,IAAI,sBAAsB,CAAC;AAAA,UACrG;AAAA,YACC;AAAA,YACA,IAAI;AAAA,YACJ,IAAI,yBAAyB,OAAO,sCACjC,IAAI,yBAAyB,QAAQ,qCACpCF;AAAA,YACJ,EAAE,OAAO,IAAI,uBAAuBC,QAAO,QAAQA,QAAO,IAAI;AAAA,UAC/D;AAAA,QACD;AAAA,QACA,GAAI,eAAe,CAAC,MAAM,cAAc,MAAMD,OAAM;AAAA,UACnD,OAAO;AAAA,UACP,OAAO;AAAA,QACR,CAAmB,CAAC,IAAI,CAAC;AAAA,QACzB,GAAI,YAAY,CAAC,IAAI,CAAC;AAAA,MACvB;AAAA,IACD;AAAA,EACD;AACD;AAUe,SAAR,gBAAiC;AAAA,EACvC;AAAA,EAAK;AAAA,EAAS;AACf,GAKG;AAvKH;AA0KC,QAAM,CAAC,WAAW,YAAY,IAAIK,UAAS,KAAK;AAGhD,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAkC,CAAC,CAAC;AACtE,QAAM,SAAS,CAAC,UAAkB,CAAC,UAAU,KAAK;AAClD,QAAM,gBAAgB,CAAC,UACtB,aAAa,CAAC,SAAU,iCAAK,OAAL,EAAW,CAAC,KAAK,GAAG,CAAC,KAAK,KAAK,EAAE,EAAE;AAE5D,QAAM,WAAWC,SAAQ,MAAM;AAC9B,QAAI,CAAC,IAAK,QAAO,CAAC;AAClB,UAAM,MAAM,cAAc,GAAG;AAG7B,WAAO,aAAa,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,UAAU,cAAc;AAAA,EACvE,GAAG,CAAC,KAAK,UAAU,CAAC;AAEpB,QAAM,UAAU,SACd,IAAI,CAAC,MAAO,iCAAK,IAAL,EAAQ,QAAQ,YAAY,EAAE,SAAS,EAAE,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EACtF,OAAO,CAAC,MAAM,EAAE,OAAO,SAAS,CAAC;AAEnC,QAAM,cAAc,SAAS,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC;AAE3F,SACC,gBAAAT;AAAA,IAAC;AAAA;AAAA,MACA,QAAO;AAAA,MACP,MAAM,CAAC,CAAC;AAAA,MACR;AAAA,MACA,WAAW;AAAA,QACV,OAAO;AAAA,UACN,IAAI;AAAA,YACH,OAAO,EAAE,IAAI,QAAQ,IAAI,IAAI;AAAA,YAC7B,KAAK,GAAG,YAAY;AAAA,YACpB,QAAQ,eAAe,YAAY;AAAA,YACnC,WAAW;AAAA,YACX,YAAY;AAAA,UACb;AAAA,QACD;AAAA,MACD;AAAA,MAEC,iBACA,gBAAAC,MAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,eAAe,UAAU,QAAQ,QAAQ,SAAS,WAAW,YAAYC,MAAK,GAEzG;AAAA,wBAAAD,MAAC,eAAI,IAAI,EAAE,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,cAAc,kBAAkB,SAAS,OAAO,GACnF;AAAA,0BAAAA,MAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,YAAY,cAAc,gBAAgB,iBAAiB,KAAK,EAAE,GAC7F;AAAA,4BAAAA,MAAC,eAAI,IAAI,EAAE,UAAU,GAAG,MAAM,EAAE,GAC/B;AAAA,8BAAAD,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,YAAY,KAAK,UAAU,WAAW,OAAO,WAAW,YAAY,KAAK,GAC3G,cAAI,gBAAgB,YAAY,IAAI,UAAU,IAChD;AAAA,cACA,gBAAAF,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,OAAOE,QAAO,MAAM,IAAI,IAAI,GACnF,cAAI,UAAM,cAAAM,SAAM,IAAI,GAAG,EAAE,OAAO,mBAAmB,IAAIP,OACzD;AAAA,eACD;AAAA,YACA,gBAAAH,KAAC,mBAAQ,OAAM,SACd,0BAAAA;AAAA,cAAC;AAAA;AAAA,gBACA,MAAK;AAAA,gBACL,SAAS;AAAA,gBACT,IAAI,EAAE,OAAO,QAAQ,SAAS,WAAW,WAAW,EAAE,SAAS,WAAW,OAAO,OAAO,EAAE;AAAA,gBAE1F,0BAAAA,KAAC,iBAAU,UAAS,SAAQ;AAAA;AAAA,YAC7B,GACD;AAAA,aACD;AAAA,UAIA,gBAAAC,MAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,YAAY,UAAU,UAAU,QAAQ,KAAK,MAAM,IAAI,KAAK,GACvF;AAAA,4BAAAD;AAAA,cAAC;AAAA;AAAA,gBACA,MAAK;AAAA,gBACL,OAAO,IAAI,iBAAiB,gBAAgB;AAAA,gBAC5C,IAAI;AAAA,kBACH,QAAQ;AAAA,kBAAI,UAAU;AAAA,kBAAW,YAAY;AAAA,kBAC7C,iBAAiB,IAAI,iBAAiB,YAAY;AAAA,kBAClD,OAAO,IAAI,iBAAiBI,QAAO,MAAMA,QAAO;AAAA,gBACjD;AAAA;AAAA,YACD;AAAA,cACE,SAAI,kBAAJ,YAAqB,KAAK,KAC3B,gBAAAJ;AAAA,cAAC;AAAA;AAAA,gBACA,MAAK;AAAA,gBACL,OAAM;AAAA,gBACN,IAAI,EAAE,QAAQ,IAAI,UAAU,WAAW,YAAY,KAAK,iBAAiB,WAAW,OAAOI,QAAO,MAAM;AAAA;AAAA,YACzG;AAAA,cAEC,SAAI,kBAAJ,YAAqB,OAAO,OAAM,SAAI,oBAAJ,YAAuB,KAAK,KAC/D,gBAAAJ;AAAA,cAAC;AAAA;AAAA,gBACA,MAAK;AAAA,gBACL,OAAM;AAAA,gBACN,IAAI,EAAE,QAAQ,IAAI,UAAU,WAAW,YAAY,KAAK,iBAAiB,WAAW,OAAOI,QAAO,MAAM;AAAA;AAAA,YACzG;AAAA,YAGD,gBAAAJ,KAAC,eAAI,IAAI,EAAE,MAAM,EAAE,GAAG;AAAA,YAEtB,gBAAAA;AAAA,cAAC;AAAA;AAAA,gBACA,IAAI,EAAE,IAAI,EAAE;AAAA,gBACZ,SACC,gBAAAA;AAAA,kBAAC;AAAA;AAAA,oBACA,MAAK;AAAA,oBACL,SAAS;AAAA,oBACT,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,OAAO;AAAA;AAAA,gBAC/C;AAAA,gBAED,OACC,gBAAAC,MAAC,sBAAW,IAAI,EAAE,YAAYC,OAAM,UAAU,WAAW,OAAO,WAAW,YAAY,SAAS,GAAG;AAAA;AAAA,kBACvF,cAAc,KAAK,CAAC,YAAY,KAAK,WAAW,MAAM;AAAA,mBAClE;AAAA;AAAA,YAEF;AAAA,aACD;AAAA,WACD;AAAA,QAGA,gBAAAD,MAAC,eAAI,IAAI,EAAE,MAAM,GAAG,WAAW,QAAQ,IAAI,GAAG,IAAI,GAAG,SAAS,QAAQ,eAAe,UAAU,KAAK,IAAI,GACtG;AAAA,kBAAQ,WAAW,KACnB,gBAAAD,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,UAAU,OAAOE,QAAO,KAAK,GAAG,4CAE9E;AAAA,UAEA,QAAQ,IAAI,CAAC;AAAA;AAAA;AAAA,YAGb,gBAAAH;AAAA,cAAC;AAAA;AAAA,gBAEA,UAAU,OAAO,QAAQ,KAAK;AAAA,gBAC9B,UAAU,MAAM,cAAc,QAAQ,KAAK;AAAA,gBAC3C,gBAAc;AAAA,gBACd,WAAW;AAAA,gBACX,IAAI;AAAA,kBACH,YAAY,EAAE,SAAS,OAAO;AAAA,kBAC9B,QAAQ;AAAA,kBACR,YAAY,aAAa,QAAQ,MAAM;AAAA,kBACvC,cAAc;AAAA,kBACd,UAAU;AAAA;AAAA;AAAA,kBAGV,YAAY;AAAA,gBACb;AAAA,gBAEA;AAAA,kCAAAA;AAAA,oBAAC;AAAA;AAAA,sBACA,YAAY,gBAAAD,KAAC,sBAAe,IAAI,EAAE,OAAO,QAAQ,OAAO,GAAG;AAAA,sBAC3D,IAAI;AAAA,wBACH,WAAW;AAAA,wBACX,SAAS,MAAM,QAAQ,QAAQ,IAAI;AAAA,wBACnC,WAAW,EAAE,SAAS,MAAM,QAAQ,QAAQ,GAAG,EAAE;AAAA,wBACjD,kCAAkC,EAAE,IAAI,MAAM,YAAY,UAAU,KAAK,EAAE;AAAA,sBAC5E;AAAA,sBAEA;AAAA,wCAAAA,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,YAAY,KAAK,UAAU,WAAW,OAAO,QAAQ,OAAO,GAC9F,kBAAQ,OACV;AAAA,wBAEC,CAAC,OAAO,QAAQ,KAAK,KACrB,gBAAAD,MAAC,sBAAW,IAAI,EAAE,YAAYC,OAAM,UAAU,WAAW,OAAOE,QAAO,KAAK,GAC1E;AAAA,kCAAQ,OAAO;AAAA,0BAAO;AAAA,0BAAE,QAAQ,OAAO,WAAW,IAAI,UAAU;AAAA,2BAClE;AAAA;AAAA;AAAA,kBAEF;AAAA,kBACA,gBAAAJ,KAAC,4BAAiB,IAAI,EAAE,SAAS,QAAQ,IAAI,GAAG,IAAI,IAAI,GACxD,0BAAAA,KAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,qBAAqB,YAAY,QAAQ,MAAM,WAAW,EAAE,GACtF,kBAAQ,OAAO,IAAI,CAAC,MACpB,gBAAAC,MAAC,eAAkB,IAAI,EAAE,SAAS,WAAW,GAC5C;AAAA,oCAAAD,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,OAAOE,QAAO,KAAK,GAC1E,YAAE,OACF,gBAAAJ,KAAC,mBAAQ,OAAK,MAAC,OAAO,EAAE,MACvB,0BAAAA,KAAC,eAAI,WAAU,QAAO,IAAI,EAAE,cAAc,sBAAsB,QAAQ,OAAO,GAC7E,YAAE,OACJ,GACD,IACG,EAAE,OACP;AAAA,oBACA,gBAAAA;AAAA,sBAAC;AAAA;AAAA,wBACA,IAAI;AAAA,0BACH,YAAYE;AAAA,0BAAM,UAAU;AAAA,0BAAU,YAAY;AAAA,0BAClD,oBAAoB;AAAA,0BACpB,OAAO,EAAE,QAAQE,QAAO,OAAQ,EAAE,SAAS;AAAA,0BAC3C,WAAW;AAAA,wBACZ;AAAA,wBAEC,YAAE;AAAA;AAAA,oBACJ;AAAA,uBAnBS,EAAE,KAoBZ,CACA,GACF,GACA;AAAA;AAAA;AAAA,cA7DK,QAAQ;AAAA,YA8Dd;AAAA,WACA;AAAA,WACF;AAAA,SACD;AAAA;AAAA,EAEF;AAEF;;;AC7VA,SAAS,QAAQ,QAAmC;AAXpD;AAYC,QAAM,KAAK,IAAI,gBAAgB;AAC/B,KAAG,OAAO,cAAc,OAAO,SAAS;AACxC,KAAG,OAAO,YAAY,OAAO,OAAO;AACpC,aAAW,OAAM,YAAO,gBAAP,YAAsB,CAAC,EAAG,IAAG,OAAO,gBAAgB,OAAO,EAAE,CAAC;AAC/E,aAAW,OAAM,YAAO,aAAP,YAAmB,CAAC,EAAG,IAAG,OAAO,aAAa,OAAO,EAAE,CAAC;AACzE,SAAO,IAAI,GAAG,SAAS,CAAC;AACzB;AAOA,eAAsB,gBAAgB,KAAc,UAAmC;AAzBvF;AA0BC,QAAM,IAAI;AACV,MAAI,QAAO,4BAAG,aAAH,mBAAa;AACxB,MAAI,gBAAgB,MAAM;AACzB,QAAI;AAAE,aAAO,KAAK,MAAM,MAAM,KAAK,KAAK,CAAC;AAAA,IAAG,SAAQO,IAAA;AAAE,aAAO;AAAA,IAAM;AAAA,EACpE;AACA,QAAM,UAAS,kCAAM,WAAN,YAAgB,6BAAM;AACrC,MAAI,OAAO,WAAW,YAAY,OAAQ,QAAO;AAEjD,MAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,GAAG;AAC/C,WAAO,OAAO,IAAI,CAAC,MAAW,uBAAG,GAAG,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI,KAAK;AAAA,EACrE;AACA,SAAO;AACR;AAGA,eAAsB,gBAAgB,aAAgD;AAzCtF;AA0CC,QAAM,KAAK,IAAI,gBAAgB;AAC/B,aAAW,MAAM,oCAAe,CAAC,EAAG,IAAG,OAAO,gBAAgB,OAAO,EAAE,CAAC;AACxE,QAAM,IAAI,GAAG,SAAS;AACtB,QAAM,MAAM,MAAM,cAAc,IAAI,GAAG,UAAU,iCAAiC,GAAG,IAAI,IAAI,CAAC,KAAK,EAAE,EAAE;AACvG,QAAM,OAAO,MAAM,QAAQ,IAAI,IAAI,IAAI,IAAI,QAAQ,eAAI,SAAJ,mBAAU,SAAV,YAAkB,CAAC;AACtE,SAAO,KAAK,OAAO,CAAC,MAAW,KAAK,OAAO,EAAE,YAAY,QAAQ;AAClE;AAEA,eAAsB,qBAAqB,QAAsD;AAlDjG;AAmDC,QAAM,MAAM,MAAM,cAAc,IAAI,GAAG,UAAU,mBAAmB,GAAG,QAAQ,MAAM,CAAC,EAAE;AACxF,QAAM,OAAO,IAAI;AACjB,SAAO,MAAM,QAAQ,IAAI,IAAI,QAAQ,kCAAM,SAAN,YAAc,CAAC;AACrD;AAMA,eAAsB,aAAa,QAAmD;AA5DtF;AA6DC,QAAM,MAAM,MAAM,cAAc,IAAI,GAAG,UAAU,0BAA0B,GAAG,QAAQ,MAAM,CAAC,EAAE;AAC/F,QAAM,OAAO,IAAI;AACjB,SAAO,MAAM,QAAQ,IAAI,IAAI,QAAQ,kCAAM,SAAN,YAAc,CAAC;AACrD;AAQA,eAAsB,+BAA+B,QAA0C;AAC9F,QAAM,MAAM,MAAM,cAAc,IAAI,GAAG,UAAU,gCAAgC,GAAG,QAAQ,MAAM,CAAC,IAAI;AAAA,IACtG,cAAc;AAAA,EACf,CAAC;AACD,QAAM,MAAM,IAAI,gBAAgB,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;AACpD,QAAM,IAAI,SAAS,cAAc,GAAG;AACpC,IAAE,OAAO;AACT,IAAE,WAAW,uBAAuB,OAAO,SAAS,OAAO,OAAO,OAAO;AACzE,IAAE,MAAM;AACR,MAAI,gBAAgB,GAAG;AACxB;AAGA,eAAsB,qBAAgD;AArFtE;AAsFC,QAAM,MAAM,MAAM,cAAc,IAAI,UAAU,iBAAiB;AAC/D,QAAM,OAAO,MAAM,QAAQ,IAAI,IAAI,IAAI,IAAI,QAAQ,eAAI,SAAJ,mBAAU,SAAV,YAAkB,CAAC;AACtE,SAAO,KACL,OAAO,CAAC,MAAW,KAAK,OAAO,EAAE,OAAO,QAAQ,EAChD,IAAI,CAAC,OAAY,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,QAAQ,YAAY,EAAE,EAAE,GAAG,EAAE;AACrE;;;ACJO,IAAM,wBAAwB;AAAA,EACpC,SAAS;AAAA,EACT,cAAc;AAAA,EACd,WAAW;AACZ;;;AH2PG,SA0bO,YAAAC,WA1bP,OAAAC,MAgCE,QAAAC,aAhCF;AAzTH,IAAMC,QAAO;AAEb,IAAMC,UAAS;AAAA,EACd,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AACP;AAEA,IAAM,gBAAgB;AAEtB,IAAM,aAAa;AAAA,EAClB,YAAYD;AAAA,EACZ,UAAU;AAAA,EACV,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,MAAM,EAAE,IAAI,KAAK,IAAI,KAAK;AAC3B;AACA,IAAM,SAAS,gDACV,aACD,aAFW;AAAA,EAGd,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AAAA,EACX,cAAc;AAAA,EACd,gCAAgC,EAAE,gBAAgB,SAAS;AAAA,EAC3D,MAAM,EAAE,IAAI,KAAK,IAAI,IAAI;AAC1B;AACA,IAAM,YAAY,EAAE,UAAU,WAAW,oBAAoB,eAAwB;AAErF,IAAM,oBAAoB,CAAC,IAAI,IAAI,IAAI,IAAI,GAAG;AAC9C,IAAM,oBAAoB;AAI1B,IAAME,QAAO;AAGb,SAASC,KAAI,OAA0C;AACtD,SAAO,OAAO,UAAU,WAAW,IAAI,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAMD;AACrE;AAEA,SAAS,QAAQ,OAA0C;AAC1D,SAAO,OAAO,UAAU,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC,MAAMA;AAC7D;AAEA,SAAS,MAAM,OAA0C;AACxD,SAAO,OAAO,UAAU,WAAW,MAAM,eAAe,IAAIA;AAC7D;AAGA,SAASE,MAAK,OAA0C;AACvD,SAAO,OAAO,UAAU,WAAW,IAAI,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAMF;AACrE;AAEA,SAAS,OAAO,OAA0C;AACzD,SAAO,OAAO,UAAU,WAAW,MAAM,QAAQ,CAAC,IAAIA;AACvD;AAGA,SAAS,UAAU,OAA0C;AAC5D,MAAI,OAAO,UAAU,SAAU,QAAOA;AACtC,QAAM,QAAQ,KAAK,MAAM,QAAQ,GAAG;AACpC,MAAI,QAAQ,EAAG,QAAO,IAAI,KAAK;AAC/B,MAAI,QAAQ,EAAG,QAAO,SAAI,KAAK,IAAI,KAAK,CAAC;AACzC,SAAO;AACR;AAGA,IAAM,iBAAiB,CAAC,QAAqB;AApG7C;AAqGE,oBAAI,kBAAJ,YAAqB,OAAO,OAAM,SAAI,oBAAJ,YAAuB,KAAK;AAAA;AAMhE,SAAS,eAAe,OAA0C;AACjE,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,QAAQ;AACxB,MAAI,UAAU,GAAI,QAAOD,QAAO;AAChC,MAAI,UAAU,GAAI,QAAOA,QAAO;AAChC,MAAI,WAAW,MAAM,WAAW,GAAI,QAAOA,QAAO;AAClD,SAAOA,QAAO;AACf;AAGA,SAAS,SAAS,OAA0C;AAC3D,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,QAAQ;AACxB,MAAI,WAAW,GAAI,QAAOA,QAAO;AACjC,MAAI,WAAW,GAAI,QAAOA,QAAO;AACjC,SAAOA,QAAO;AACf;AAEA,IAAM,aACL;AAKD,IAAM,aAAa;AAAA,EAClB,KAAK;AAAA,EACL,WAAW;AAAA,EAEX,QAAQ;AAAA,EAER,WAAW;AAAA,EAGX,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,YAAY,CAAC,eACZ,0CAA0C,cAAc,OAAO,GAAG,UAAU,aAAa,2BAA2B;AAGtH;AAEA,IAAM,iBACL;AAiBM,SAAS,yBAAyB,EAAE,WAAW,OAAO,OAAO,aAAa,GAAkC;AAClH,QAAM,WAAW,SAAS;AAG1B,QAAM,qBAAqB,yBAAyB;AACpD,QAAM,EAAE,cAAc,IAAI,kBAAkB;AAC5C,QAAM,YAAY,cAAc,gBAAgB,gCAAgC;AAEhF,QAAM,CAAC,WAAW,YAAY,IAAII,cAAuB,cAAAC,SAAM,EAAE,QAAQ,OAAO,CAAC;AACjF,QAAM,CAAC,SAAS,UAAU,IAAID,cAAuB,cAAAC,SAAM,CAAC;AAC5D,QAAM,CAAC,YAAY,aAAa,IAAID,UAAS,OAAO;AAEpD,QAAM,CAAC,WAAW,YAAY,IAAIA,UAA2B,CAAC,CAAC;AAC/D,QAAM,CAAC,mBAAmB,oBAAoB,IAAIA,UAA2B,CAAC,CAAC;AAC/E,QAAM,CAAC,gBAAgB,iBAAiB,IAAIA,UAAS,EAAE;AAEvD,QAAM,CAAC,MAAM,OAAO,IAAIA,UAA2B,CAAC,CAAC;AACrD,QAAM,CAAC,YAAY,aAAa,IAAIA,UAAS,EAAE,WAAW,GAAG,UAAU,kBAAkB,CAAC;AAC1F,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,KAAK;AAC5C,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAS,KAAK;AAChD,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAS,EAAE;AAGrC,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAwB,CAAC,CAAC;AAC5D,QAAM,CAAC,iBAAiB,kBAAkB,IAAIA,UAAS,EAAE,WAAW,GAAG,UAAU,kBAAkB,CAAC;AACpG,QAAM,CAAC,cAAc,eAAe,IAAIA,UAAwB,CAAC,CAAC;AAClE,QAAM,CAAC,kBAAkB,mBAAmB,IAAIA,UAAmB,CAAC,CAAC;AACrE,QAAM,CAAC,aAAa,cAAc,IAAIA,UAAS,EAAE;AAEjD,EAAAE,WAAU,MAAM;AACf,QAAI,CAAC,mBAAoB;AACzB,uBAAmB,EAAE,KAAK,YAAY,EAAE,MAAM,MAAM,aAAa,CAAC,CAAC,CAAC;AAAA,EACrE,GAAG,CAAC,kBAAkB,CAAC;AAGvB,QAAM,cAAcC,SAAQ,MAAM;AACjC,QAAI,CAAC,aAAa,CAAC,QAAS,QAAO;AACnC,QAAI,UAAU,QAAQ,OAAO,EAAG,QAAO;AACvC,QAAI,QAAQ,KAAK,WAAW,KAAK,IAAI,IAAI,sBAAsB,SAAS;AACvE,aAAO,mCAAmC,sBAAsB,OAAO;AAAA,IACxE;AACA,QAAI,kBAAkB,SAAS,sBAAsB,cAAc;AAClE,aAAO,gBAAgB,sBAAsB,YAAY;AAAA,IAC1D;AACA,QAAI,YAAY,iBAAiB,SAAS,sBAAsB,WAAW;AAC1E,aAAO,gBAAgB,sBAAsB,SAAS;AAAA,IACvD;AACA,WAAO;AAAA,EACR,GAAG,CAAC,WAAW,SAAS,kBAAkB,QAAQ,UAAU,iBAAiB,MAAM,CAAC;AAEpF,QAAM,cAAcC,aAAY,OAAO;AAAA,IACtC,WAAW,UAAW,OAAO,YAAY;AAAA,IACzC,SAAS,QAAS,OAAO,YAAY;AAAA;AAAA,IAErC,aAAa,kBAAkB,SAAS,KAAK,kBAAkB,SAAS,UAAU,SAC/E,kBAAkB,IAAI,CAAC,MAAM,EAAE,EAAE,IACjC;AAAA;AAAA,IAEH,UAAU,YAAY,iBAAiB,SAAS,KAAK,iBAAiB,SAAS,aAAa,SACzF,mBACA;AAAA,EACJ,IAAI,CAAC,WAAW,SAAS,mBAAmB,UAAU,QAAQ,UAAU,kBAAkB,aAAa,MAAM,CAAC;AAO9G,QAAM,oBAAoB,kBAAkB,SAAS,KAAK,kBAAkB,SAAS,UAAU,SAC5F,kBAAkB,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,GAAG,IAC3C;AACH,EAAAF,WAAU,MAAM;AACf,QAAI,CAAC,sBAAsB,CAAC,SAAU;AACtC,QAAI,YAAY;AAChB,oBAAgB,oBAAoB,kBAAkB,MAAM,GAAG,EAAE,IAAI,MAAM,IAAI,MAAS,EACtF,KAAK,CAAC,SAAS;AACf,UAAI,UAAW;AACf,sBAAgB,IAAI;AACpB,0BAAoB,CAAC,QAAQ;AAC5B,cAAM,OAAO,IAAI,OAAO,CAAC,OAAO,KAAK,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC;AAClE,eAAO,KAAK,WAAW,IAAI,SAAS,MAAM;AAAA,MAC3C,CAAC;AAAA,IACF,CAAC,EACA,MAAM,OAAO,MAAM;AACnB,UAAI,UAAW;AACf,sBAAgB,CAAC,CAAC;AAClB,eAAS,MAAM,gBAAgB,GAAG,+BAA+B,CAAC;AAAA,IACnE,CAAC;AACF,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAM;AAAA,EAClC,GAAG,CAAC,oBAAoB,UAAU,iBAAiB,CAAC;AAEpD,QAAM,cAAcE,aAAY,YAAY;AAC3C,QAAI,CAAC,mBAAoB;AACzB,QAAI,aAAa;AAChB,eAAS,WAAW;AACpB,cAAQ,CAAC,CAAC;AACV,mBAAa,CAAC,CAAC;AACf;AAAA,IACD;AACA,eAAW,IAAI;AACf,aAAS,EAAE;AACX,UAAM,SAAS,YAAY;AAE3B,QAAI,UAAU;AACb,UAAI;AACH,qBAAa,MAAM,aAAa,MAAM,CAAC;AAAA,MACxC,SAAS,GAAG;AACX,qBAAa,CAAC,CAAC;AACf,iBAAS,MAAM,gBAAgB,GAAG,+BAA+B,CAAC;AAAA,MACnE;AAAA,IACD,OAAO;AACN,UAAI;AACH,gBAAQ,MAAM,qBAAqB,MAAM,CAAC;AAAA,MAC3C,SAAS,GAAG;AACX,gBAAQ,CAAC,CAAC;AACV,iBAAS,MAAM,gBAAgB,GAAG,yCAAyC,CAAC;AAAA,MAC7E;AAAA,IACD;AAEA,eAAW,KAAK;AAAA,EACjB,GAAG,CAAC,oBAAoB,aAAa,aAAa,QAAQ,CAAC;AAE3D,EAAAF,WAAU,MAAM;AAAE,gBAAY;AAAA,EAAG,GAAG,CAAC,WAAW,CAAC;AAGjD,QAAM,YAAY,MAAM;AACvB,kBAAc,CAAC,MAAO,EAAE,cAAc,IAAI,IAAI,iCAAK,IAAL,EAAQ,WAAW,EAAE,EAAE;AACrE,uBAAmB,CAAC,MAAO,EAAE,cAAc,IAAI,IAAI,iCAAK,IAAL,EAAQ,WAAW,EAAE,EAAE;AAAA,EAC3E;AACA,QAAM,oBAAoB,CAAC,MAAoB;AAAE,iBAAa,CAAC;AAAG,cAAU;AAAA,EAAG;AAC/E,QAAM,kBAAkB,CAAC,MAAoB;AAAE,eAAW,CAAC;AAAG,cAAU;AAAA,EAAG;AAE3E,QAAM,eAAe,YAAY;AAChC,QAAI,aAAa;AAAE,eAAS,WAAW;AAAG;AAAA,IAAQ;AAClD,iBAAa,IAAI;AACjB,QAAI;AACH,YAAM,+BAA+B,YAAY,CAAC;AAAA,IACnD,SAAS,GAAG;AACX,eAAS,MAAM,gBAAgB,GAAG,0CAA0C,CAAC;AAAA,IAC9E,UAAE;AACD,mBAAa,KAAK;AAAA,IACnB;AAAA,EACD;AAEA,QAAM,eAAeC,SAAQ,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,CAAC,IAAI,CAAC;AAGtF,QAAM,aAAaA,SAAQ,MAAM,KAAK,OAAO,CAAC,MAAG;AA1TlD;AA0TsD,oBAAE,kBAAF,YAAmB,KAAK;AAAA,GAAC,GAAG,CAAC,IAAI,CAAC;AACvF,QAAM,qBAAqBA;AAAA,IAC1B,MAAM,WAAW,OAAO,CAAC,MAAM,EAAE,yBAAyB,IAAI,EAAE;AAAA,IAChE,CAAC,UAAU;AAAA,EACZ;AAKA,QAAM,CAAC,WAAW,YAAY,IAAIH,UAAgC,IAAI;AAEtE,QAAM,CAAC,YAAY,aAAa,IAAIA,UAAS,KAAK;AAClD,QAAM,CAAC,eAAe,gBAAgB,IAAIA,UAAS,KAAK;AACxD,EAAAE,WAAU,MAAM;AACf,QAAI,cAAe;AACnB,kBAAc,WAAW,SAAS,CAAC;AAAA,EACpC,GAAG,CAAC,WAAW,QAAQ,aAAa,CAAC;AAGrC,QAAM,gBAAgB,eAAe,KAAK,EAAE,YAAY;AACxD,QAAM,mBAAmB,gBACtB,UAAU,OAAO,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,aAAa,CAAC,IACpE;AACH,QAAM,qBAAqB,iBAAiB,SAAS,KACjD,iBAAiB,MAAM,CAAC,MAAM,kBAAkB,KAAK,CAAC,OAAO,GAAG,OAAO,EAAE,EAAE,CAAC;AAEhF,QAAM,iBACL,gBAAAR,MAAC,uBAAY,MAAK,SAAQ,IAAI,EAAE,UAAU,IAAI,GAC7C;AAAA,oBAAAD,KAAC,sBAAW,IAAI,EAAE,UAAU,UAAU,GAAG,2BAAa;AAAA,IACtD,gBAAAC;AAAA,MAAC;AAAA;AAAA,QACA,UAAQ;AAAA,QACR,OAAM;AAAA,QACN,IAAI,EAAE,UAAU,WAAW,QAAQ,eAAe,cAAc,MAAM;AAAA,QACtE,WAAW;AAAA,UACV,SAAS;AAAA,UACT,WAAW;AAAA,UACX,YAAY,EAAE,IAAI,EAAE,WAAW,KAAK,uBAAuB,EAAE,UAAU,WAAW,WAAW,IAAI,IAAI,IAAI,EAAE,EAAE;AAAA,QAC9G;AAAA,QACA,OAAO,kBAAkB,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,QACxC,SAAS,MAAM,kBAAkB,EAAE;AAAA,QACnC,UAAU,CAAC,MAAM;AAChB,gBAAM,MAAM,EAAE,OAAO;AAErB,cAAI,IAAI,SAAS,EAAE,GAAG;AACrB,iCAAqB,qBAClB,kBAAkB,OAAO,CAAC,OAAO,CAAC,iBAAiB,KAAK,CAAC,MAAM,EAAE,OAAO,GAAG,EAAE,CAAC,IAC9E,CAAC,GAAG,mBAAmB,GAAG,iBAAiB,OAAO,CAAC,MAAM,CAAC,kBAAkB,KAAK,CAAC,OAAO,GAAG,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAAA,UAC9G,OAAO;AACN,iCAAqB,UAAU,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,EAAE,CAAC,CAAC;AAAA,UACjE;AACA,oBAAU;AAAA,QACX;AAAA,QACA,aAAa,CAAC,aAAa;AAC1B,gBAAM,IAAK,SAAsB;AACjC,cAAI,MAAM,KAAK,MAAM,UAAU,OAAQ,QAAO;AAC9C,iBAAO,GAAG,CAAC;AAAA,QACZ;AAAA,QAEA;AAAA,0BAAAD,KAAC,mBAAgB,OAAO,gBAAgB,UAAU,mBAAmB,aAAY,sBAAqB;AAAA,UACrG,iBAAiB,SAAS,KAC1B,gBAAAC,MAAC,oBAAS,OAAO,IAChB;AAAA,4BAAAD;AAAA,cAAC;AAAA;AAAA,gBACA,SAAS;AAAA,gBACT,eAAe,iBAAiB,KAAK,CAAC,MAAM,kBAAkB,KAAK,CAAC,OAAO,GAAG,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC;AAAA,gBAChG,MAAK;AAAA;AAAA,YACN;AAAA,YAAE;AAAA,aAEH;AAAA,UAEA,iBAAiB,IAAI,CAAC,MACtB,gBAAAC,MAAC,oBAAoB,OAAO,EAAE,IAC7B;AAAA,4BAAAD,KAAC,oBAAS,SAAS,kBAAkB,KAAK,CAAC,OAAO,GAAG,OAAO,EAAE,EAAE,GAAG,MAAK,SAAQ;AAAA,YAC/E,EAAE;AAAA,eAFW,EAAE,EAGjB,CACA;AAAA;AAAA;AAAA,IACF;AAAA,KACD;AAID,QAAM,aAAa,YAAY,KAAK,EAAE,YAAY;AAClD,QAAM,gBAAgB,aACnB,aAAa,OAAO,CAAC,MAAM,EAAE,UAAU,YAAY,EAAE,SAAS,UAAU,CAAC,IACzE;AACH,QAAM,2BAA2B,cAAc,SAAS,KACpD,cAAc,MAAM,CAAC,MAAM,iBAAiB,SAAS,EAAE,OAAO,CAAC;AAEnE,QAAM,cAAc,YACnB,gBAAAC,MAAC,uBAAY,MAAK,SAAQ,IAAI,EAAE,UAAU,IAAI,GAC7C;AAAA,oBAAAD,KAAC,sBAAW,IAAI,EAAE,UAAU,UAAU,GAAG,wBAAU;AAAA,IACnD,gBAAAC;AAAA,MAAC;AAAA;AAAA,QACA,UAAQ;AAAA,QACR,OAAM;AAAA,QACN,IAAI,EAAE,UAAU,WAAW,QAAQ,eAAe,cAAc,MAAM;AAAA,QACtE,WAAW;AAAA,UACV,SAAS;AAAA,UACT,WAAW;AAAA,UACX,YAAY,EAAE,IAAI,EAAE,WAAW,KAAK,uBAAuB,EAAE,UAAU,WAAW,WAAW,IAAI,IAAI,IAAI,EAAE,EAAE;AAAA,QAC9G;AAAA,QACA,OAAO;AAAA,QACP,SAAS,MAAM,eAAe,EAAE;AAAA,QAChC,UAAU,CAAC,MAAM;AAChB,gBAAM,MAAM,EAAE,OAAO;AAErB,cAAI,IAAI,SAAS,EAAE,GAAG;AACrB,kBAAM,aAAa,cAAc,IAAI,CAAC,MAAM,EAAE,OAAO;AACrD,gCAAoB,2BACjB,iBAAiB,OAAO,CAAC,OAAO,CAAC,WAAW,SAAS,EAAE,CAAC,IACxD,CAAC,GAAG,kBAAkB,GAAG,WAAW,OAAO,CAAC,OAAO,CAAC,iBAAiB,SAAS,EAAE,CAAC,CAAC,CAAC;AAAA,UACvF,OAAO;AACN,gCAAoB,GAAG;AAAA,UACxB;AACA,oBAAU;AAAA,QACX;AAAA,QACA,aAAa,CAAC,aAAa;AAC1B,gBAAM,IAAK,SAAsB;AACjC,cAAI,MAAM,KAAK,MAAM,aAAa,OAAQ,QAAO;AACjD,iBAAO,GAAG,CAAC;AAAA,QACZ;AAAA,QAEA;AAAA,0BAAAD,KAAC,mBAAgB,OAAO,aAAa,UAAU,gBAAgB,aAAY,mBAAkB;AAAA,UAC5F,cAAc,SAAS,KACvB,gBAAAC,MAAC,oBAAS,OAAO,IAChB;AAAA,4BAAAD;AAAA,cAAC;AAAA;AAAA,gBACA,SAAS;AAAA,gBACT,eAAe,cAAc,KAAK,CAAC,MAAM,iBAAiB,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC;AAAA,gBACnF,MAAK;AAAA;AAAA,YACN;AAAA,YAAE;AAAA,aAEH;AAAA,UAEA,cAAc,IAAI,CAAC,MACnB,gBAAAC,MAAC,oBAAyB,OAAO,EAAE,SAClC;AAAA,4BAAAD,KAAC,oBAAS,SAAS,iBAAiB,SAAS,EAAE,OAAO,GAAG,MAAK,SAAQ;AAAA,YACrE,EAAE;AAAA,eAFW,EAAE,OAGjB,CACA;AAAA;AAAA;AAAA,IACF;AAAA,KACD;AAID,QAAM,UAAUU,SAAyC,MAAM;AAC9D,UAAM,OAAO,CAAC,KAA8B,CAAC,OAAO;AAAA,MACnD,uBAAuB,EAAE,OAAO,UAAmB,IAAI,OAAO;AAAA,MAC9D,uBAAuB,EAAE,OAAO,UAAmB,IAAI,gDAAK,aAAL,EAAiB,WAAW,aAAa,IAAK;AAAA,IACtG;AACA,UAAM,WAAW,CAAC,IAA0B,QAAgB,QAAgBP,QAAO,SAAyC;AAAA,MAC3H;AAAA,MACA;AAAA,MACA,YAAY,CAAC,MAAM,EAAE,EAAE;AAAA,MACvB,MAAM,CAAC,EAAE,IAAI,MAAM,MAAM,IAAI,SAAS,EAAE,CAAW;AAAA,OAChD,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,MAAM,EAAC;AAGjD,UAAM,OAAwC;AAAA,MAC7C;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAO,IAAI,SAAS,UAAM,cAAAK,SAAM,IAAI,SAAS,GAAG,EAAE,OAAO,YAAY,IAAIJ;AAAA,SACnF,KAAK,EAAE,UAAU,WAAW,OAAO,UAAU,CAAC;AAAA,MAElD;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,gBAAgBA;AAAA,SAC7C,KAAK,EAAE,UAAU,WAAW,OAAO,WAAW,YAAY,UAAU,WAAW,OAAO,CAAC,IAL3F;AAAA,QAMC,uBAAuB,EAAE,OAAO,QAAiB,IAAI,iCAAK,aAAL,EAAiB,UAAU,WAAW,OAAO,WAAW,YAAY,SAAS,GAAE;AAAA,MACrI;AAAA,MACA,SAAS,UAAU,QAAQ;AAAA,MAC3B,SAAS,YAAY,YAAYD,QAAO,KAAK;AAAA,MAC7C,SAAS,kBAAkB,aAAaA,QAAO,KAAK;AAAA,MACpD,SAAS,aAAa,aAAaA,QAAO,GAAG;AAAA,MAC7C,SAAS,wBAAwB,iBAAiBA,QAAO,GAAG;AAAA,MAC5D,SAAS,2BAA2B,oBAAoBA,QAAO,KAAK;AAAA,MACpE;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAME,KAAI,IAAI,SAAS,UAAU;AAAA,SAC3C,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAOF,QAAO,MAAM,EAAC;AAAA,MAE/D;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MACZ,gBAAAF,MAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,eAAe,UAAU,YAAY,UAAU,KAAK,IAAI,GACnF;AAAA,0BAAAD,KAAC,UAAM,UAAAK,KAAI,IAAI,SAAS,WAAW,GAAE;AAAA,UACpC,IAAI,SAAS,kBACb,gBAAAL;AAAA,YAAC;AAAA;AAAA,cACA,OAAM;AAAA,cACN,MAAK;AAAA,cACL,IAAI,EAAE,QAAQ,IAAI,UAAU,WAAW,YAAY,KAAK,iBAAiB,WAAW,OAAOG,QAAO,IAAI;AAAA;AAAA,UACvG;AAAA,WAEF;AAAA,SAEE,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAOA,QAAO,IAAI,EAAC;AAAA,MAE7D;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAME,KAAI,IAAI,SAAS,iBAAiB;AAAA,SAClD,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAO,UAAU,EAAC;AAAA,MAE5D;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAM,QAAQ,IAAI,SAAS,qBAAqB;AAAA,SAC1D,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAOF,QAAO,KAAK,EAAC;AAAA,IAE/D;AAEA,QAAI,CAAC,WAAY,QAAO;AAGxB,UAAM,UAAU,CACf,IACA,QACA,QACA,OACoC;AAAA,MACpC;AAAA,MACA;AAAA,MACA,YAAY,CAAC,MAAM,EAAE,EAAE;AAAA,MACvB,MAAM,CAAC,EAAE,IAAI,MAAO,eAAe,IAAI,QAAQ,IAAIC,QAAO,OAAO,IAAI,QAAQ;AAAA,OAC1E,KAAK,EAAE;AAGX,WAAO;AAAA,MACN,GAAG;AAAA,MACH;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,uBAAuB;AAAA,UACtB,OAAO;AAAA,UACP,IAAI,iCAAK,SAAL,EAAa,iBAAiB,WAAW,OAAOD,QAAO,MAAM,YAAY,IAAI;AAAA,QAClF;AAAA;AAAA;AAAA,QAGA,QAAQ,MACP,gBAAAH,KAAC,mBAAQ,OAAK,MAAC,OAAO,gBACrB,0BAAAC,MAAC,eAAI,IAAI,EAAE,SAAS,eAAe,YAAY,UAAU,KAAK,KAAK,QAAQ,UAAU,GAAG;AAAA;AAAA,UAEvF,gBAAAD,KAAC,wBAAiB,IAAI,EAAE,UAAU,GAAG,GAAG;AAAA,WACzC,GACD;AAAA,QAED,SAAS;AAAA,UACR;AAAA,YACC,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,YAAY,CAAC,MAAM,EAAE;AAAA,YACrB,MAAM,CAAC,EAAE,IAAI,MACZ,eAAe,IAAI,QAAQ,IACxB,gBAAAA;AAAA,cAAC;AAAA;AAAA,gBACF,OAAM;AAAA,gBACN,MAAK;AAAA,gBACL,IAAI,EAAE,QAAQ,IAAI,UAAU,WAAW,YAAY,KAAK,iBAAiB,WAAW,OAAOG,QAAO,MAAM;AAAA;AAAA,YACzG,IACE,MAAM,IAAI,SAAS,aAAa;AAAA,aAEjC,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAO,UAAU,EAAC;AAAA,UAE5D;AAAA,YACC,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,YAAY,CAAC,MAAM,EAAE;AAAA,YACrB,MAAM,CAAC,EAAE,IAAI,MAAM,MAAM,IAAI,SAAS,eAAe;AAAA,aAClD,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAO,UAAU,EAAC;AAAA,UAE5D;AAAA,YAAQ;AAAA,YAAmB;AAAA,YAAc,CAAC,MAAM,OAAO,EAAE,eAAe;AAAA,YACvE,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAOA,QAAO,KAAK;AAAA,UAAC;AAAA,UACtD;AAAA,YAAQ;AAAA,YAAuB;AAAA,YAAqB,CAAC,MAAM,OAAO,EAAE,mBAAmB;AAAA,YACtF,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAOA,QAAO,MAAM;AAAA,UAAC;AAAA,UACvD;AAAA,YACC,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,YAAY,CAAC,MAAM,EAAE;AAAA,YACrB,MAAM,CAAC,EAAE,IAAI,MAAO,eAAe,IAAI,QAAQ,IAAIC,QAAO,UAAU,IAAI,SAAS,kBAAkB;AAAA,YACnG,uBAAuB,EAAE,OAAO,UAAmB,IAAI,OAAO;AAAA,YAC9D,uBAAuB,CAAC,EAAE,IAAI,MAAG;AAxlBvC;AAwlB2C;AAAA,gBACpC,OAAO;AAAA,gBACP,IAAI,gDACA,aACA,YAFA;AAAA,kBAGH,WAAW;AAAA,kBACX,YAAY;AAAA;AAAA,kBAEZ,SAAQ,SAAI,SAAS,uBAAb,YAAmC,MAAM,IAAID,QAAO,QAAQA,QAAO;AAAA,gBAC5E;AAAA,cACD;AAAA;AAAA,UACD;AAAA,UACA;AAAA,YAAQ;AAAA,YAAyB;AAAA,YAAuB,CAAC,MAAMG,MAAK,EAAE,qBAAqB;AAAA,YAC1F,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAO,UAAU;AAAA,UAAC;AAAA,UACpD;AAAA,YAAQ;AAAA,YAA0B;AAAA,YAA2B,CAAC,MAAMA,MAAK,EAAE,sBAAsB;AAAA,YAChG,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAO,UAAU;AAAA,UAAC;AAAA,UACpD;AAAA,YACC,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,YAAY,CAAC,MAAM,EAAE;AAAA,YACrB,MAAM,CAAC,EAAE,IAAI,MAAM;AAClB,oBAAM,UAAU,IAAI,SAAS;AAC7B,kBAAI,eAAe,IAAI,QAAQ,KAAK,WAAW,KAAM,QAAOF;AAC5D,qBACC,gBAAAJ;AAAA,gBAAC;AAAA;AAAA,kBACA,OAAO,UAAU,kBAAkB;AAAA,kBACnC,MAAK;AAAA,kBACL,IAAI;AAAA,oBACH,QAAQ;AAAA,oBAAI,UAAU;AAAA,oBAAW,YAAY;AAAA,oBAC7C,iBAAiB,UAAU,YAAY;AAAA,oBACvC,OAAO,UAAUG,QAAO,QAAQA,QAAO;AAAA,kBACxC;AAAA;AAAA,cACD;AAAA,YAEF;AAAA,aACG,KAAK,CAAC,CAAC;AAAA,QAEZ;AAAA,MACD;AAAA,IACD;AAAA,EAED,GAAG,CAAC,UAAU,CAAC;AASf,QAAM,aAAaO,SAAQ,MAAM;AAChC,UAAM,SAAS,IAAI;AAAA,MAClB,UAAU,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;AAAA,IACpF;AACA,WAAO,OAAO,SAAS,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI;AAAA,EAC7C,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,eAAeA,SAAsC,MAAM;AAChE,UAAM,OAAO,CAAC,KAA8B,CAAC,GAAG,QAAkB;AAAA,MACjE,uBAAuB,EAAE,OAAO,UAAmB,IAAI,OAAO;AAAA,MAC9D,uBAAuB,EAAE,OAAO,UAAmB,IAAI,gDAAK,aAAL,EAAiB,WAAW,aAAa,IAAK;AAAA,OACjG,OAAO;AAAA,MACV,QAAQ,CAAC,EAAE,OAAO,MACjB,gBAAAV,KAAC,mBAAQ,OAAK,MAAC,OAAO,KACrB,0BAAAC,MAAC,eAAI,IAAI,EAAE,SAAS,eAAe,YAAY,UAAU,KAAK,KAAK,QAAQ,UAAU,GACnF;AAAA,eAAO,UAAU;AAAA,QAClB,gBAAAD,KAAC,wBAAiB,IAAI,EAAE,UAAU,GAAG,GAAG;AAAA,SACzC,GACD;AAAA,IAEF;AAGD,WAAO;AAAA,MACN;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAO,IAAI,SAAS,UAAM,cAAAQ,SAAM,IAAI,SAAS,GAAG,EAAE,OAAO,YAAY,IAAIJ;AAAA,SACnF,KAAK,EAAE,UAAU,WAAW,OAAO,UAAU,GAAG,WAAW,GAAG;AAAA,MAElE;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,aAAaA;AAAA,SAC1C,KAAK,EAAE,UAAU,WAAW,OAAO,WAAW,YAAY,SAAS,GAAG,WAAW,SAAS;AAAA,MAE9F;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAM,MAAM,IAAI,SAAS,MAAM;AAAA,SACzC,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAOD,QAAO,KAAK,IAAG,WAAW,MAAM;AAAA,MAEjF;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MACZ,gBAAAH,KAAC,eAAI,WAAU,QAAO,IAAI,EAAE,OAAO,eAAe,IAAI,SAAS,SAAS,GAAG,YAAY,IAAI,GACzF,UAAAK,KAAI,IAAI,SAAS,SAAS,GAC5B;AAAA,SAEE,KAAK,WAAW,WAAW,SAAS;AAAA,MAExC;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAMA,KAAI,IAAI,SAAS,gBAAgB;AAAA,SACjD,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAOF,QAAO,KAAK,IAAG,WAAW,gBAAgB;AAAA,MAE3F;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAME,KAAI,IAAI,SAAS,iBAAiB;AAAA,SAClD,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAOF,QAAO,KAAK,IAAG,WAAW,iBAAiB;AAAA,MAE5F;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MAAM,MAAM,IAAI,SAAS,cAAc;AAAA,SACjD,KAAK,iCAAK,YAAL,EAAgB,YAAY,KAAK,OAAOA,QAAO,KAAK,IAAG,WAAW,cAAc;AAAA,MAEzF;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ,cAAc,OAAO,uBAAkB,UAAU,QAAQ;AAAA,QACjE,YAAY,CAAC,MAAM,EAAE;AAAA,QACrB,MAAM,CAAC,EAAE,IAAI,MACZ,gBAAAH,KAAC,eAAI,WAAU,QAAO,IAAI,EAAE,OAAO,SAAS,IAAI,SAAS,UAAU,GAAG,YAAY,IAAI,GACpF,UAAAK,KAAI,IAAI,SAAS,UAAU,GAC7B;AAAA,SAEE,KAAK,WAAW,WAAW,WAAW,UAAU,CAAC;AAAA,IAEtD;AAAA,EACD,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,eAAe,aACpB,gBAAAL;AAAA,IAAC;AAAA;AAAA,MACA,WAAW,YAAY,gBAAAA,KAAC,4BAAiB,MAAM,IAAI,IAAI,EAAE,OAAO,OAAO,GAAG,IAAK,gBAAAA,KAAC,oBAAa,IAAI,EAAE,UAAU,qBAAqB,GAAG;AAAA,MACrI,UAAU,aAAa,YAAY,WAAW,YAAY,MAAM,WAAW;AAAA,MAC3E,SAAS;AAAA,MACT,IAAI,EAAE,QAAQ,WAAW,gBAAgB,GAAG;AAAA,MAC5C;AAAA;AAAA,EAED;AAGD,SACC,gBAAAA,KAAC,wBAAqB,aAAa,cAClC,0BAAAC,MAAC,eAAI,IAAI,EAAE,QAAQ,QAAQ,SAAS,QAAQ,eAAe,UAAU,YAAYC,MAAK,GACpF;AAAA,KAAC,YAAY,gBAAAF,KAAC,cAAW,OAAM,uBAAsB,SAAS,cAAc;AAAA,IAE7E,gBAAAC,MAAC,eAAI,IAAI,EAAE,MAAM,GAAG,UAAU,QAAQ,IAAI,KAAK,IAAI,EAAE,GACpD;AAAA,sBAAAA,MAAC,eAAI,IAAI,EAAE,IAAI,IAAI,GAClB;AAAA,wBAAAD;AAAA,UAAC;AAAA;AAAA,YACA;AAAA,YACA;AAAA,YACA,eAAe;AAAA,YACf,aAAa;AAAA,YAEb,UAAU;AAAA,YACV;AAAA,YACA,oBAAoB,CAAC,MAAkB,cAAc,CAAC;AAAA,YACtD,OACC,gBAAAC,MAAAF,WAAA,EACE;AAAA;AAAA,cACA;AAAA,cACD,gBAAAC;AAAA,gBAAC;AAAA;AAAA,kBACA,WAAW,gBAAAA,KAAC,mBAAY,IAAI,EAAE,UAAU,oBAAoB,GAAG;AAAA,kBAC/D,UAAU;AAAA,kBACV,SAAS;AAAA,kBACT,IAAI,EAAE,QAAQ,cAAc;AAAA,kBAC5B;AAAA;AAAA,cAED;AAAA,cAIC,YAAY,aACZ,gBAAAC,MAAAF,WAAA,EACC;AAAA,gCAAAC,KAAC,eAAI,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,GAAG;AAAA,gBAClC;AAAA,iBACF;AAAA,eAEF;AAAA;AAAA,QAEF;AAAA,QAEC,SACA,gBAAAA,KAAC,iBAAM,UAAS,SAAQ,IAAI,EAAE,IAAI,GAAG,YAAYE,OAAM,UAAU,UAAU,GAAG,SAAS,MAAM,SAAS,EAAE,GACtG,iBACF;AAAA,QAGA,CAAC,YAAY,CAAC,SAAS,KAAK,SAAS,KACrC,gBAAAD,MAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,YAAY,UAAU,KAAK,GAAG,UAAU,QAAQ,IAAI,GAAG,IAAI,EAAE,GACxF;AAAA,0BAAAD;AAAA,YAAC;AAAA;AAAA,cACA,IAAI;AAAA,gBACH,YAAYE;AAAA,gBAAM,UAAU;AAAA,gBAAW,YAAY;AAAA,gBACnD,OAAO,eAAe,IAAIC,QAAO,MAAMA,QAAO;AAAA,cAC/C;AAAA,cAEC,yBAAe,IACb,GAAG,YAAY,OAAO,KAAK,MAAM,+BACjC,OAAO,KAAK,MAAM;AAAA;AAAA,UACtB;AAAA,UAGC,WAAW,SAAS,KACpB,gBAAAF,MAAC,sBAAW,IAAI,EAAE,YAAYC,OAAM,UAAU,WAAW,YAAY,KAAK,OAAOC,QAAO,KAAK,GAAG;AAAA;AAAA,YACzD;AAAA,YAAmB;AAAA,YAAK,WAAW;AAAA,YAAO;AAAA,aACjF;AAAA,UAGD,gBAAAH,KAAC,eAAI,IAAI,EAAE,MAAM,EAAE,GAAG;AAAA,UAEtB,gBAAAA;AAAA,YAAC;AAAA;AAAA,cACA,SACC,gBAAAA;AAAA,gBAAC;AAAA;AAAA,kBACA,MAAK;AAAA,kBACL,SAAS;AAAA,kBACT,UAAU,CAAC,MAAM;AAAE,qCAAiB,IAAI;AAAG,kCAAc,EAAE,OAAO,OAAO;AAAA,kBAAG;AAAA;AAAA,cAC7E;AAAA,cAED,OACC,gBAAAA,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,OAAO,UAAU,GAAG,oCAE7E;AAAA;AAAA,UAEF;AAAA,WACD;AAAA,SAEF;AAAA,MAEC,CAAC,YACF,gBAAAF;AAAA,QAAC;AAAA;AAAA,UAIA;AAAA,UACA,MAAM;AAAA,UACN,YAAW;AAAA,UACX,kBAAkB;AAAA,UAClB,qBAAqB;AAAA,UACrB,qBAAqB;AAAA,UACrB,sBAAsB;AAAA,UACtB,cAAc;AAAA,UACd,qBAAqB;AAAA,UACrB,wBAAwB;AAAA,UACxB,oBAAkB;AAAA,UAClB,oBAAoB;AAAA,UACpB,oBAAoB,EAAE,oBAAoB,kBAAkB;AAAA,UAC5D,OAAO,EAAE,YAAY,WAAW,QAAQ;AAAA,UACxC,cAAc;AAAA,YACb,oBAAoB;AAAA,UACrB;AAAA,UACA,0BAA0B,EAAE,IAAI,EAAE,OAAOG,QAAO,KAAK,GAAG,MAAM,GAAG;AAAA,UACjE,oBAAoB,EAAE,WAAW,GAAG,IAAI,EAAE,cAAc,OAAO,QAAQ,qBAAqB,UAAU,UAAU,WAAW,QAAQ,IAAI,EAAE,EAAE;AAAA,UAC3I,eAAe,EAAE,IAAI,EAAE,YAAYD,OAAM,QAAQ,QAAQ,cAAc,GAAG,WAAW,OAAO,EAAE;AAAA,UAE9F,wBAAwB,EAAE,IAAI,EAAE,WAAW,gBAAgB,WAAW,MAAM,GAAG,MAAM,EAAE;AAAA,UACvF,mBAAmB,EAAE,IAAI,EAAE,WAAW,OAAO,EAAE;AAAA,UAC/C,sBAAsB,EAAE,IAAI,EAAE,WAAW,OAAO,EAAE;AAAA,UAIlD,sBAAsB,CAAC,EAAE,IAAI,OAAO;AAAA,YACnC,SAAS,MAAM,aAAa,IAAI,QAAQ;AAAA,YACxC,IAAI;AAAA,cACH,QAAQ;AAAA,cACR,iBAAiB,IAAI,SAAS,iBAAiB,YAAY;AAAA,cAC3D,WAAW,EAAE,iBAAiB,IAAI,SAAS,iBAAiB,YAAY,UAAU;AAAA,YACnF;AAAA,UACD;AAAA,UACA,uBAAuB;AAAA,YACtB,IAAI;AAAA,cACH,WAAW;AAAA,cACX,WAAW;AAAA,cACX,YAAYA;AAAA,cACZ,gGAAgG,EAAE,YAAYA,OAAM,UAAU,UAAU;AAAA,YACzI;AAAA,UACD;AAAA;AAAA,QA3CK,aAAa,gBAAgB;AAAA,MA4CnC;AAAA,MAOC,YACD,gBAAAD,MAAAF,WAAA,EACA;AAAA,wBAAAC;AAAA,UAAC;AAAA;AAAA,YACA,SAAS;AAAA,YACT,MAAM;AAAA,YACN,YAAW;AAAA,YACX,kBAAkB;AAAA,YAClB,qBAAqB;AAAA,YACrB,qBAAqB;AAAA,YACrB,sBAAsB;AAAA,YACtB,cAAc;AAAA,YACd,qBAAqB;AAAA,YACrB,wBAAwB;AAAA,YACxB,oBAAkB;AAAA,YAClB,oBAAoB;AAAA,YACpB,oBAAoB,EAAE,oBAAoB,kBAAkB;AAAA,YAC5D,OAAO,EAAE,YAAY,iBAAiB,WAAW,QAAQ;AAAA,YACzD,cAAc,EAAE,oBAAoB,0CAA0C;AAAA,YAC9E,0BAA0B,EAAE,IAAI,EAAE,OAAOG,QAAO,KAAK,GAAG,MAAM,GAAG;AAAA,YACjE,oBAAoB,EAAE,WAAW,GAAG,IAAI,EAAE,cAAc,OAAO,QAAQ,qBAAqB,UAAU,UAAU,WAAW,QAAQ,IAAI,EAAE,EAAE;AAAA,YAC3I,eAAe,EAAE,IAAI,EAAE,YAAYD,OAAM,QAAQ,QAAQ,cAAc,GAAG,WAAW,OAAO,EAAE;AAAA,YAC9F,wBAAwB,EAAE,IAAI,EAAE,WAAW,sBAAsB,EAAE;AAAA,YACnE,mBAAmB,EAAE,IAAI,EAAE,WAAW,OAAO,EAAE;AAAA,YAC/C,sBAAsB,EAAE,IAAI,EAAE,WAAW,OAAO,EAAE;AAAA,YAClD,sBAAsB,EAAE,IAAI,EAAE,iBAAiB,QAAQ,WAAW,EAAE,iBAAiB,UAAU,EAAE,EAAE;AAAA,YACnG,uBAAuB;AAAA,cACtB,IAAI;AAAA,gBACH,WAAW;AAAA,gBACX,WAAW;AAAA,gBACX,YAAYA;AAAA,gBACZ,gGAAgG,EAAE,YAAYA,OAAM,UAAU,UAAU;AAAA,cACzI;AAAA,YACD;AAAA;AAAA,QACD;AAAA,QAEA,gBAAAF,KAAC,sBAAW,IAAI,EAAE,YAAYE,OAAM,UAAU,WAAW,OAAO,WAAW,IAAI,GAAG,IAAI,GAAG,YAAY,IAAI,GACvG,sBACF;AAAA,SACA;AAAA,OAED;AAAA,IAEA,gBAAAF,KAAC,mBAAgB,KAAK,WAAW,SAAS,MAAM,aAAa,IAAI,GAAG,YAAwB;AAAA,KAC7F,GACD;AAEF;;;AI56BA,SAAS,eAAAY,cAAa,aAAAC,YAAW,YAAAC,iBAAgB;AAgBjD,SAAS,eAAe,GAAiB;AACxC,SAAO,QAAO,uBAAG,gBAAe,EAAE,EAAE,YAAY,MAAM,gBAClD,QAAO,uBAAG,cAAa,EAAE,EAAE,YAAY,MAAM;AAClD;AASO,SAAS,mBAAmB,SAAkB;AACpD,QAAM,CAAC,WAAW,YAAY,IAAIC,UAA2B,CAAC,CAAC;AAC/D,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAS,EAAE;AAErC,QAAM,iBAAiBC,aAAY,YAAY;AAjChD;AAkCE,QAAI,CAAC,QAAS;AACd,eAAW,IAAI;AACf,aAAS,EAAE;AACX,QAAI;AACH,YAAM,MAAM,MAAM,cAAc,IAAI,UAAU,iBAAiB;AAC/D,YAAM,OAAO,MAAM,QAAQ,IAAI,IAAI,IAAI,IAAI,QAAQ,eAAI,SAAJ,mBAAU,SAAV,YAAkB,CAAC;AACtE,YAAM,SAA2B,KAC/B,OAAO,CAAC,MAAW,KAAK,OAAO,EAAE,OAAO,YAAY,eAAe,CAAC,CAAC,EACrE,IAAI,CAAC,OAAY;AAAA,QACjB,IAAI,EAAE;AAAA,QACN,MAAM,EAAE,QAAQ,YAAY,EAAE,EAAE;AAAA,QAChC,QAAQ,OAAO,EAAE,UAAU,EAAE,EAAE,YAAY;AAAA,QAC3C,uBAAuB,EAAE,0BAA0B;AAAA,QACnD,qBAAqB,OAAO,EAAE,wBAAwB,WAAW,EAAE,sBAAsB;AAAA,MAC1F,EAAE;AAEH,aAAO,KAAK,CAAC,GAAG,MAAM;AACrB,cAAM,UAAU,EAAE,WAAW,WAAW,IAAI;AAC5C,cAAM,UAAU,EAAE,WAAW,WAAW,IAAI;AAC5C,eAAO,UAAU,WAAW,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,MACxD,CAAC;AACD,mBAAa,MAAM;AAAA,IACpB,SAAS,KAAU;AAClB,mBAAa,CAAC,CAAC;AACf,iBAAS,sCAAK,aAAL,mBAAe,SAAf,mBAAqB,cAAW,sCAAK,aAAL,mBAAe,SAAf,mBAAqB,WAAU,0BAA0B;AAAA,IACnG,UAAE;AACD,iBAAW,KAAK;AAAA,IACjB;AAAA,EACD,GAAG,CAAC,OAAO,CAAC;AAEZ,EAAAC,WAAU,MAAM;AAAE,mBAAe;AAAA,EAAG,GAAG,CAAC,cAAc,CAAC;AAEvD,SAAO,EAAE,WAAW,SAAS,OAAO,SAAS,eAAe;AAC7D;;;AZkEM,gBAAAC,MAkBA,QAAAC,aAlBA;AAzGN,IAAMC,QACJ;AAEF,IAAMC,UAAS;AAAA,EACb,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAEA,IAAM,SAAS;AAAA,EACb,WAAW;AAAA,EACX,IAAI;AAAA;AAAA,EAEJ,IAAI;AAAA,EACJ,cAAc;AAAA,EACd,kBAAkB;AAAA,IAChB,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,YAAYD;AAAA,IACZ,WAAW;AAAA,IACX,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,EACT;AAAA,EACA,mBAAmB,EAAE,OAAO,GAAGC,QAAO,IAAI,cAAc;AAAA,EACxD,wBAAwB;AAAA,IACtB,iBAAiBA,QAAO;AAAA,IACxB,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AACF;AAKA,IAAM,oBAAoB;AAE1B,SAAS,qBAAoC;AAC3C,MAAI;AACF,UAAM,MAAM,OAAO,aAAa,QAAQ,iBAAiB;AACzD,UAAM,KAAK,OAAO,GAAG;AACrB,WAAO,OAAO,SAAS,EAAE,KAAK,KAAK,IAAI,KAAK;AAAA,EAC9C,SAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,cAAc,IAAyB;AAC9C,MAAI;AACF,QAAI,MAAM,KAAM,QAAO,aAAa,WAAW,iBAAiB;AAAA,QAC3D,QAAO,aAAa,QAAQ,mBAAmB,OAAO,EAAE,CAAC;AAAA,EAChE,SAAQ;AAAA,EAER;AACF;AASA,SAAS,YAA+D;AACtE,QAAM,IAAI,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACpD,QAAM,MAAM,EAAE,IAAI,KAAK;AACvB,QAAM,KAAK,OAAO,EAAE,IAAI,YAAY,CAAC;AACrC,SAAO;AAAA,IACL,KAAK,QAAQ,UAAU,QAAQ,gBAAgB,QAAQ,WAAW,MAAM;AAAA,IACxE,YAAY,OAAO,SAAS,EAAE,KAAK,KAAK,IAAI,KAAK;AAAA,EACnD;AACF;AAEA,SAAS,WAAW,KAAa,YAAiC;AAChE,QAAM,IAAI,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACpD,IAAE,IAAI,OAAO,GAAG;AAChB,MAAI,cAAc,KAAM,GAAE,IAAI,cAAc,OAAO,UAAU,CAAC;AAAA,MACzD,GAAE,OAAO,YAAY;AAC1B,SAAO,QAAQ;AAAA,IACb;AAAA,IACA;AAAA,IACA,GAAG,OAAO,SAAS,QAAQ,IAAI,EAAE,SAAS,CAAC;AAAA,EAC7C;AACF;AAYA,SAAS,QAAQ;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiB;AACf,MAAI,OAAO;AACT,WACE,gBAAAH;AAAA,MAAC;AAAA;AAAA,QACC,UAAS;AAAA,QACT,IAAI,EAAE,GAAG,GAAG,YAAYE,OAAM,UAAU,UAAU;AAAA,QACjD;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,MAAI,WAAW,UAAU,WAAW,GAAG;AACrC,WACE,gBAAAF,KAAC,eAAI,IAAI,EAAE,SAAS,QAAQ,gBAAgB,UAAU,IAAI,EAAE,GAC1D,0BAAAA,KAAC,4BAAiB,MAAM,IAAI,IAAI,EAAE,OAAOG,QAAO,KAAK,GAAG,GAC1D;AAAA,EAEJ;AAEA,MAAI,UAAU,WAAW,GAAG;AAC1B,WACE,gBAAAF,MAAC,eAAI,IAAI,EAAE,IAAI,GAAG,WAAW,SAAS,GACpC;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,IAAI;AAAA,YACF,YAAYE;AAAA,YACZ,UAAU;AAAA,YACV,YAAY;AAAA,YACZ,OAAO;AAAA,UACT;AAAA,UAAG;AAAA;AAAA,MAEL;AAAA,MACA,gBAAAF;AAAA,QAAC;AAAA;AAAA,UACC,IAAI;AAAA,YACF,YAAYE;AAAA,YACZ,UAAU;AAAA,YACV,OAAOC,QAAO;AAAA,YACd,IAAI;AAAA,UACN;AAAA,UAAG;AAAA;AAAA,MAEL;AAAA,OACF;AAAA,EAEJ;AAEA,SACE,gBAAAF,MAAC,eACC;AAAA,oBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,IAAI;AAAA,UACF,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,KAAK;AAAA,UACL,UAAU;AAAA,UACV,IAAI;AAAA,UACJ,IAAI;AAAA,QACN;AAAA,QACA;AAAA,0BAAAA,MAAC,uBAAY,MAAK,SAAQ,IAAI,EAAE,UAAU,IAAI,GAC5C;AAAA,4BAAAD,KAAC,sBAAW,IAAI,EAAE,UAAU,UAAU,GAAG,sBAAQ;AAAA,YACjD,gBAAAA;AAAA,cAAC;AAAA;AAAA,gBACC,OAAM;AAAA,gBACN,OAAO,WAAW,OAAO,SAAS,EAAE,IAAI;AAAA,gBACxC,UAAU,CAAC,MAAM,SAAS,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,gBAChD,IAAI,EAAE,UAAU,UAAU,QAAQ,IAAI,cAAc,MAAM;AAAA,gBAC1D,WAAW,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,IAAI,EAAE,EAAE;AAAA,gBACnD,oBAAU,IAAI,CAAC,MACd,gBAAAC;AAAA,kBAAC;AAAA;AAAA,oBAEC,OAAO,OAAO,EAAE,EAAE;AAAA,oBAClB,IAAI,EAAE,UAAU,UAAU,KAAK,EAAE;AAAA,oBACjC;AAAA,sCAAAD;AAAA,wBAAC;AAAA;AAAA,0BACC,WAAU;AAAA,0BACV,IAAI;AAAA,4BACF,MAAM;AAAA,4BACN,UAAU;AAAA,4BACV,UAAU;AAAA,4BACV,cAAc;AAAA,0BAChB;AAAA,0BACC,YAAE;AAAA;AAAA,sBACL;AAAA,sBACA,gBAAAA;AAAA,wBAAC;AAAA;AAAA,0BACC,MAAK;AAAA,0BACL,OAAO,EAAE,wBAAwB,aAAa;AAAA,0BAC9C,IAAI;AAAA,4BACF,QAAQ;AAAA,4BACR,UAAU;AAAA,4BACV,YAAY;AAAA,4BACZ,OAAO,EAAE,wBAAwBG,QAAO,QAAQA,QAAO;AAAA,4BACvD,iBAAiB,EAAE,wBACf,YACA;AAAA,0BACN;AAAA;AAAA,sBACF;AAAA;AAAA;AAAA,kBAzBK,EAAE;AAAA,gBA0BT,CACD;AAAA;AAAA,YACH;AAAA,aACF;AAAA,UAEC,YAAY,SAAS,WAAW,YAC/B,gBAAAH;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,OAAO,SAAS,WAAW,WAAW,WAAW,SAAS;AAAA,cAC1D,IAAI;AAAA,gBACF,QAAQ;AAAA,gBACR,UAAU;AAAA,gBACV,YAAY;AAAA,gBACZ,OAAOG,QAAO;AAAA,gBACd,iBAAiB;AAAA,cACnB;AAAA;AAAA,UACF;AAAA;AAAA;AAAA,IAEJ;AAAA,IAEC;AAAA,IAEC,gBAAAF;AAAA,MAAC;AAAA;AAAA,QACC,IAAI;AAAA,UACF,SAAS;AAAA,UACT,eAAe;AAAA,UACf,KAAK;AAAA,UACL,IAAI;AAAA,UACJ,IAAI;AAAA,QACN;AAAA,QACA;AAAA,0BAAAD;AAAA,YAAC;AAAA;AAAA,cACC,YAAY,SAAS;AAAA,cACrB,qBAAqB,SAAS;AAAA;AAAA,UAChC;AAAA,UACA,gBAAAA,KAAC,gBAAa,YAAY,SAAS,IAAI;AAAA,UACvC,gBAAAA,KAAC,uBAAoB,YAAY,SAAS,IAAI;AAAA;AAAA;AAAA,IAChD;AAAA,KAEJ;AAEJ;AAIA,SAAS,0BAA0B;AAGjC,QAAM,qBAAqB,yBAAyB;AACpD,QAAM,EAAE,cAAc,IAAI,kBAAkB;AAC5C,QAAM,gBAAgB,cAAc,gBAAgB,sBAAsB;AAG1E,QAAM,iBAAiB;AAAA,IACrB,gBAAgB;AAAA,EAClB;AAGA,uBAAqB,aAAa;AAElC,QAAM,CAAC,KAAK,MAAM,IAAII,UAAiB,MAAG;AAxR5C;AAwR+C,2BAAU,EAAE,QAAZ,YAAmB;AAAA,GAAM;AACtE,QAAM,CAAC,YAAY,aAAa,IAAIA;AAAA,IAClC,MAAG;AA1RP;AA0RU,6BAAU,EAAE,eAAZ,YAA0B,mBAAmB;AAAA;AAAA,EACrD;AAEA,QAAM,EAAE,WAAW,SAAS,MAAM,IAChC,mBAAmB,CAAC,CAAC,kBAAkB;AAEzC,QAAM,cAAcC;AAAA,IAClB,MAAM;AAAA,MACJ,EAAE,KAAK,QAAkB,OAAO,OAAO;AAAA,MACvC,GAAI,iBACA;AAAA,QACE,EAAE,KAAK,cAAwB,OAAO,mBAAmB;AAAA,QACzD,EAAE,KAAK,UAAoB,OAAO,aAAa;AAAA,MACjD,IACA,CAAC;AAAA,IACP;AAAA,IACA,CAAC,cAAc;AAAA,EACjB;AAIA,EAAAC,WAAU,MAAM;AACd,QAAI,QAAQ,UAAU,CAAC,eAAgB,QAAO,MAAM;AAAA,EACtD,GAAG,CAAC,KAAK,cAAc,CAAC;AAMxB,EAAAA,WAAU,MAAM;AACd,QAAI,UAAU,WAAW,EAAG;AAC5B,QAAI,cAAc,QAAQ,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU;AACjE;AACF,kBAAc,UAAU,CAAC,EAAE,EAAE;AAAA,EAC/B,GAAG,CAAC,WAAW,UAAU,CAAC;AAE1B,QAAM,WAAWD;AAAA,IACf,MAAG;AA/TP;AA+TU,6BAAU,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU,MAAzC,YAA8C;AAAA;AAAA,IACpD,CAAC,WAAW,UAAU;AAAA,EACxB;AAGA,EAAAC,WAAU,MAAM;AApUlB;AAqUI,eAAW,MAAK,0CAAU,OAAV,YAAgB,IAAI;AACpC,QAAI,SAAU,eAAc,SAAS,EAAE;AAAA,EACzC,GAAG,CAAC,KAAK,QAAQ,CAAC;AAElB,QAAM,eAAeC,aAAY,CAAC,OAAe,cAAc,EAAE,GAAG,CAAC,CAAC;AAEtE,SACE,gBAAAN;AAAA,IAAC;AAAA;AAAA,MACC,IAAI;AAAA,QACF,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,eAAe;AAAA,QACf,YAAYC;AAAA,MACd;AAAA,MACA;AAAA,wBAAAF;AAAA,UAAC;AAAA;AAAA,YACC,OAAM;AAAA,YACN,UAAS;AAAA;AAAA,QACX;AAAA,QAIA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,YAAY,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,MAAM;AAAA,YACtD,UAAU,CAAC,GAAG,MAAc,OAAO,CAAC;AAAA,YACpC,IAAI;AAAA,YACH,sBAAY,IAAI,CAAC,MAChB,gBAAAA,KAAC,eAAgB,OAAO,EAAE,KAAK,OAAO,EAAE,SAA9B,EAAE,GAAmC,CAChD;AAAA;AAAA,QACH;AAAA,QAEA,gBAAAA,KAAC,eAAI,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,QAAQ,IAAI,EAAE,GACvD,kBAAQ,UAAU;AAAA;AAAA,UAEjB,gBAAAA,KAAC,4BAAmC,UAAQ,MAAC,MAAM,OAApB,GAAyB;AAAA,YAExD,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,UAAU;AAAA;AAAA,QACZ,GAEJ;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,IAAM,mBAA6B,MACjC,gBAAAA,KAAC,eACC,0BAAAA,KAAC,2BAAwB,GAC3B;AAGF,IAAO,2BAAQ;","names":["useCallback","useEffect","useMemo","useState","_a","_b","dayjs","_a","_b","_c","useState","import_dayjs","useCallback","useEffect","useRef","useState","POLL_MS","useState","useRef","useCallback","useEffect","jsx","jsxs","FONT","COLORS","dayjs","useState","useState","import_dayjs","import_dayjs","useCallback","useEffect","useRef","useState","useState","useRef","useCallback","dayjs","useEffect","jsx","jsxs","FONT","COLORS","dayjs","useState","useCallback","useEffect","useMemo","useState","import_dayjs","useMemo","useState","import_dayjs","jsx","jsxs","FONT","DASH","COLORS","pct","secs","ratio","useState","useMemo","dayjs","e","Fragment","jsx","jsxs","FONT","COLORS","DASH","pct","pct2","useState","dayjs","useEffect","useMemo","useCallback","useCallback","useEffect","useState","useState","useCallback","useEffect","jsx","jsxs","FONT","COLORS","useState","useMemo","useEffect","useCallback"]}