{"version":3,"sources":["../call-control-sdk/lib/pages/campaigns/usePacingAlertToasts.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from \"react\";\r\nimport axiosInstance from \"../../services/axios\";\r\nimport { END_POINT } from \"../../services/endPoint\";\r\nimport { useToast } from \"../../services/toastMessage\";\r\nimport type { PacingAlert } from \"./pacingTypes\";\r\n\r\n/** There is no push channel for supervisors yet, so the feed is polled. */\r\nconst POLL_MS = 30_000;\r\n\r\nconst LIMIT = 50;\r\n\r\n/**\r\n * Tenant-wide safe-mode alerts, surfaced as toasts on the campaigns page.\r\n *\r\n * A supervisor watching one campaign still needs to hear that a different one\r\n * went over target, so this feed covers every campaign rather than the selected\r\n * one. It is separate from the per-campaign event history.\r\n *\r\n * The first poll is silent: it records the newest timestamp it sees and toasts\r\n * nothing, so opening the page doesn't replay a day of alerts. Everything after\r\n * that is asked for with `since`, so each alert is announced once.\r\n */\r\nexport function usePacingAlertToasts(enabled: boolean) {\r\n\tconst { showToast } = useToast();\r\n\r\n\t/** The newest `createdAt` already accounted for. Sent back verbatim — it is\r\n\t *  the server's own string, and reformatting it risks losing precision. */\r\n\tconst since = useRef<string | null>(null);\r\n\tconst primed = useRef(false);\r\n\tconst inFlight = useRef(false);\r\n\r\n\tconst poll = useCallback(async () => {\r\n\t\tif (inFlight.current) return;\r\n\t\tinFlight.current = true;\r\n\t\ttry {\r\n\t\t\tconst res = await axiosInstance.get(END_POINT.PACING_ALERTS, {\r\n\t\t\t\tparams: {\r\n\t\t\t\t\tlimit: LIMIT,\r\n\t\t\t\t\t...(since.current ? { since: since.current } : {}),\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t\tconst body = res.data;\r\n\t\t\tconst alerts: PacingAlert[] = Array.isArray(body) ? body : (body?.data ?? []);\r\n\r\n\t\t\t// An empty first poll still counts as catching up — the common case, since\r\n\t\t\t// most tenants have no alerts. Priming after the length check left the hook\r\n\t\t\t// waiting for a backlog that never came, and the first real alert was then\r\n\t\t\t// swallowed as history instead of being announced.\r\n\t\t\tconst wasPrimed = primed.current;\r\n\t\t\tprimed.current = true;\r\n\r\n\t\t\tif (alerts.length === 0) return;\r\n\r\n\t\t\t// Newest first, so the head is the watermark for the next request.\r\n\t\t\tconst newest = alerts[0]?.createdAt;\r\n\t\t\tif (newest) since.current = newest;\r\n\r\n\t\t\t// First pass with a backlog: adopt the watermark without replaying it.\r\n\t\t\tif (!wasPrimed) return;\r\n\r\n\t\t\t// Oldest first, so a campaign that went over target and recovered in the\r\n\t\t\t// same window reads in the order it happened.\r\n\t\t\t[...alerts].reverse().forEach((alert) => {\r\n\t\t\t\tconst name = alert.campaignName || \"A campaign\";\r\n\t\t\t\tif (alert.eventType === \"LIMIT_EXCEEDED\") {\r\n\t\t\t\t\tshowToast(`${name}: ${alert.message}`, \"warning\");\r\n\t\t\t\t} else if (alert.eventType === \"LIMIT_CLEARED\") {\r\n\t\t\t\t\tshowToast(`${name} recovered — abandon rate is back within target`, \"info\");\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t} catch {\r\n\t\t\t// A failed poll is not worth telling a supervisor about; the next one\r\n\t\t\t// picks up from the same watermark.\r\n\t\t} finally {\r\n\t\t\tinFlight.current = false;\r\n\t\t}\r\n\t}, [showToast]);\r\n\r\n\tuseEffect(() => {\r\n\t\tif (!enabled) return;\r\n\r\n\t\tpoll();\r\n\t\tconst timer = window.setInterval(() => {\r\n\t\t\tif (document.visibilityState === \"visible\") poll();\r\n\t\t}, POLL_MS);\r\n\t\tconst onVisible = () => { if (document.visibilityState === \"visible\") poll(); };\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, poll]);\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa,WAAW,cAAc;AAO/C,IAAM,UAAU;AAEhB,IAAM,QAAQ;AAaP,SAAS,qBAAqB,SAAkB;AACtD,QAAM,EAAE,UAAU,IAAI,SAAS;AAI/B,QAAM,QAAQ,OAAsB,IAAI;AACxC,QAAM,SAAS,OAAO,KAAK;AAC3B,QAAM,WAAW,OAAO,KAAK;AAE7B,QAAM,OAAO,YAAY,YAAY;AA/BtC;AAgCE,QAAI,SAAS,QAAS;AACtB,aAAS,UAAU;AACnB,QAAI;AACH,YAAM,MAAM,MAAM,cAAc,IAAI,UAAU,eAAe;AAAA,QAC5D,QAAQ;AAAA,UACP,OAAO;AAAA,WACH,MAAM,UAAU,EAAE,OAAO,MAAM,QAAQ,IAAI,CAAC;AAAA,MAElD,CAAC;AACD,YAAM,OAAO,IAAI;AACjB,YAAM,SAAwB,MAAM,QAAQ,IAAI,IAAI,QAAQ,kCAAM,SAAN,YAAc,CAAC;AAM3E,YAAM,YAAY,OAAO;AACzB,aAAO,UAAU;AAEjB,UAAI,OAAO,WAAW,EAAG;AAGzB,YAAM,UAAS,YAAO,CAAC,MAAR,mBAAW;AAC1B,UAAI,OAAQ,OAAM,UAAU;AAG5B,UAAI,CAAC,UAAW;AAIhB,OAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,UAAU;AACxC,cAAM,OAAO,MAAM,gBAAgB;AACnC,YAAI,MAAM,cAAc,kBAAkB;AACzC,oBAAU,GAAG,IAAI,KAAK,MAAM,OAAO,IAAI,SAAS;AAAA,QACjD,WAAW,MAAM,cAAc,iBAAiB;AAC/C,oBAAU,GAAG,IAAI,wDAAmD,MAAM;AAAA,QAC3E;AAAA,MACD,CAAC;AAAA,IACF,SAAQ;AAAA,IAGR,UAAE;AACD,eAAS,UAAU;AAAA,IACpB;AAAA,EACD,GAAG,CAAC,SAAS,CAAC;AAEd,YAAU,MAAM;AACf,QAAI,CAAC,QAAS;AAEd,SAAK;AACL,UAAM,QAAQ,OAAO,YAAY,MAAM;AACtC,UAAI,SAAS,oBAAoB,UAAW,MAAK;AAAA,IAClD,GAAG,OAAO;AACV,UAAM,YAAY,MAAM;AAAE,UAAI,SAAS,oBAAoB,UAAW,MAAK;AAAA,IAAG;AAC9E,aAAS,iBAAiB,oBAAoB,SAAS;AAEvD,WAAO,MAAM;AACZ,aAAO,cAAc,KAAK;AAC1B,eAAS,oBAAoB,oBAAoB,SAAS;AAAA,IAC3D;AAAA,EACD,GAAG,CAAC,SAAS,IAAI,CAAC;AACnB;","names":[]}