callix-dialer-widget 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
6
6
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
7
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
8
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
9
- import React, { useEffect, useState, useMemo, useRef, useCallback, forwardRef, useLayoutEffect, createContext, useImperativeHandle, StrictMode, useContext } from "react";
9
+ import React, { useState, useMemo, useRef, useEffect, useCallback, forwardRef, useLayoutEffect, useId, createContext, useImperativeHandle, StrictMode, useContext } from "react";
10
10
  import require$$0$2, { createPortal } from "react-dom";
11
11
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
12
12
  function getAugmentedNamespace(n) {
@@ -101,9 +101,9 @@ function requireAxios() {
101
101
  const { toString } = Object.prototype;
102
102
  const { getPrototypeOf } = Object;
103
103
  const { iterator, toStringTag } = Symbol;
104
- const kindOf = /* @__PURE__ */ ((cache) => (thing) => {
104
+ const kindOf = /* @__PURE__ */ ((cache2) => (thing) => {
105
105
  const str = toString.call(thing);
106
- return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
106
+ return cache2[str] || (cache2[str] = str.slice(8, -1).toLowerCase());
107
107
  })(/* @__PURE__ */ Object.create(null));
108
108
  const kindOfTest = (type) => {
109
109
  type = type.toLowerCase();
@@ -307,10 +307,10 @@ function requireAxios() {
307
307
  }
308
308
  };
309
309
  const matchAll = (regExp, str) => {
310
- let matches;
310
+ let matches2;
311
311
  const arr = [];
312
- while ((matches = regExp.exec(str)) !== null) {
313
- arr.push(matches);
312
+ while ((matches2 = regExp.exec(str)) !== null) {
313
+ arr.push(matches2);
314
314
  }
315
315
  return arr;
316
316
  };
@@ -5774,11 +5774,11 @@ var StateNode = (
5774
5774
  if (!afterConfig) {
5775
5775
  return [];
5776
5776
  }
5777
- var mutateEntryExit = function(delay, i2) {
5778
- var delayRef = isFunction(delay) ? "".concat(_this.id, ":delay[").concat(i2, "]") : delay;
5777
+ var mutateEntryExit = function(delay2, i2) {
5778
+ var delayRef = isFunction(delay2) ? "".concat(_this.id, ":delay[").concat(i2, "]") : delay2;
5779
5779
  var eventType = after(delayRef, _this.id);
5780
5780
  _this.onEntry.push(send$1(eventType, {
5781
- delay
5781
+ delay: delay2
5782
5782
  }));
5783
5783
  _this.onExit.push(cancel$1(eventType));
5784
5784
  return eventType;
@@ -5788,12 +5788,12 @@ var StateNode = (
5788
5788
  return __assign(__assign({}, transition), {
5789
5789
  event: eventType
5790
5790
  });
5791
- }) : flatten(Object.keys(afterConfig).map(function(delay, i2) {
5792
- var configTransition = afterConfig[delay];
5791
+ }) : flatten(Object.keys(afterConfig).map(function(delay2, i2) {
5792
+ var configTransition = afterConfig[delay2];
5793
5793
  var resolvedTransition = isString(configTransition) ? {
5794
5794
  target: configTransition
5795
5795
  } : configTransition;
5796
- var resolvedDelay = !isNaN(+delay) ? +delay : delay;
5796
+ var resolvedDelay = !isNaN(+delay2) ? +delay2 : delay2;
5797
5797
  var eventType = mutateEntryExit(resolvedDelay, i2);
5798
5798
  return toArray(resolvedTransition).map(function(transition) {
5799
5799
  return __assign(__assign({}, transition), {
@@ -5803,9 +5803,9 @@ var StateNode = (
5803
5803
  });
5804
5804
  }));
5805
5805
  return delayedTransitions.map(function(delayedTransition) {
5806
- var delay = delayedTransition.delay;
5806
+ var delay2 = delayedTransition.delay;
5807
5807
  return __assign(__assign({}, _this.formatTransition(delayedTransition)), {
5808
- delay
5808
+ delay: delay2
5809
5809
  });
5810
5810
  });
5811
5811
  };
@@ -7785,13 +7785,13 @@ function requireExtractExtension() {
7785
7785
  return {};
7786
7786
  }
7787
7787
  var numberWithoutExtension = number.slice(0, start2);
7788
- var matches = number.match(EXTN_PATTERN2);
7788
+ var matches2 = number.match(EXTN_PATTERN2);
7789
7789
  var i2 = 1;
7790
- while (i2 < matches.length) {
7791
- if (matches[i2]) {
7790
+ while (i2 < matches2.length) {
7791
+ if (matches2[i2]) {
7792
7792
  return {
7793
7793
  number: numberWithoutExtension,
7794
- ext: matches[i2]
7794
+ ext: matches2[i2]
7795
7795
  };
7796
7796
  }
7797
7797
  i2++;
@@ -10797,10 +10797,10 @@ function requirePhoneNumberMatcher() {
10797
10797
  return _createClass2(PhoneNumberMatcher2, [{
10798
10798
  key: "find",
10799
10799
  value: function find() {
10800
- var matches;
10801
- while (this.maxTries > 0 && (matches = this.PATTERN.exec(this.text)) !== null) {
10802
- var candidate = matches[0];
10803
- var offset = matches.index;
10800
+ var matches2;
10801
+ while (this.maxTries > 0 && (matches2 = this.PATTERN.exec(this.text)) !== null) {
10802
+ var candidate = matches2[0];
10803
+ var offset = matches2.index;
10804
10804
  candidate = (0, _parsePreCandidate["default"])(candidate);
10805
10805
  if ((0, _isValidPreCandidate["default"])(candidate, offset, this.text)) {
10806
10806
  var match2 = (
@@ -14024,12 +14024,12 @@ function requireMemoize() {
14024
14024
  throw new TypeError(FUNC_ERROR_TEXT);
14025
14025
  }
14026
14026
  var memoized = function() {
14027
- var args = arguments, key = resolver ? resolver.apply(this, args) : args[0], cache = memoized.cache;
14028
- if (cache.has(key)) {
14029
- return cache.get(key);
14027
+ var args = arguments, key = resolver ? resolver.apply(this, args) : args[0], cache2 = memoized.cache;
14028
+ if (cache2.has(key)) {
14029
+ return cache2.get(key);
14030
14030
  }
14031
14031
  var result2 = func.apply(this, args);
14032
- memoized.cache = cache.set(key, result2) || cache;
14032
+ memoized.cache = cache2.set(key, result2) || cache2;
14033
14033
  return result2;
14034
14034
  };
14035
14035
  memoized.cache = new (memoize.Cache || MapCache)();
@@ -14503,14 +14503,14 @@ function requirePhoneNumberSearch() {
14503
14503
  return _createClass2(PhoneNumberSearch2, [{
14504
14504
  key: "find",
14505
14505
  value: function find() {
14506
- var matches = this.regexp.exec(this.text);
14507
- if (!matches) {
14506
+ var matches2 = this.regexp.exec(this.text);
14507
+ if (!matches2) {
14508
14508
  return;
14509
14509
  }
14510
- var number = matches[0];
14511
- var startsAt = matches.index;
14510
+ var number = matches2[0];
14511
+ var startsAt = matches2.index;
14512
14512
  number = number.replace(WHITESPACE_IN_THE_BEGINNING_PATTERN, "");
14513
- startsAt += matches[0].length - number.length;
14513
+ startsAt += matches2[0].length - number.length;
14514
14514
  number = number.replace(PUNCTUATION_IN_THE_END_PATTERN, "");
14515
14515
  number = (0, _parsePreCandidate["default"])(number);
14516
14516
  var result2 = this.parseCandidate(number, startsAt);
@@ -42753,73 +42753,73 @@ function requireChunk52ALM2Y4() {
42753
42753
  function isInjector(value) {
42754
42754
  return __isInternalType(value, Injector);
42755
42755
  }
42756
- var getWeakCacheItem = (cache, deps) => {
42756
+ var getWeakCacheItem = (cache2, deps) => {
42757
42757
  while (true) {
42758
42758
  const [dep, ...rest] = deps;
42759
- const entry = cache.get(dep);
42759
+ const entry = cache2.get(dep);
42760
42760
  if (!entry) {
42761
42761
  return;
42762
42762
  }
42763
42763
  if (!rest.length) {
42764
42764
  return entry[1];
42765
42765
  }
42766
- cache = entry[0];
42766
+ cache2 = entry[0];
42767
42767
  deps = rest;
42768
42768
  }
42769
42769
  };
42770
- var setWeakCacheItem = (cache, deps, item) => {
42770
+ var setWeakCacheItem = (cache2, deps, item) => {
42771
42771
  while (true) {
42772
42772
  const [dep, ...rest] = deps;
42773
- let entry = cache.get(dep);
42773
+ let entry = cache2.get(dep);
42774
42774
  if (!entry) {
42775
42775
  entry = [/* @__PURE__ */ new WeakMap()];
42776
- cache.set(dep, entry);
42776
+ cache2.set(dep, entry);
42777
42777
  }
42778
42778
  if (!rest.length) {
42779
42779
  entry[1] = item;
42780
42780
  return;
42781
42781
  }
42782
- cache = entry[0];
42782
+ cache2 = entry[0];
42783
42783
  deps = rest;
42784
42784
  }
42785
42785
  };
42786
42786
  var createDeepCache = () => {
42787
- let cache = /* @__PURE__ */ new WeakMap();
42787
+ let cache2 = /* @__PURE__ */ new WeakMap();
42788
42788
  const deepCache = (createFn, foundFn, deps) => {
42789
42789
  if (!deps.length)
42790
42790
  throw new Error("Dependencies need to exist.");
42791
- const cachedValue = getWeakCacheItem(cache, deps);
42791
+ const cachedValue = getWeakCacheItem(cache2, deps);
42792
42792
  if (cachedValue) {
42793
42793
  foundFn(cachedValue);
42794
42794
  return cachedValue;
42795
42795
  }
42796
42796
  const newObject = createFn();
42797
- setWeakCacheItem(cache, deps, newObject);
42797
+ setWeakCacheItem(cache2, deps, newObject);
42798
42798
  return newObject;
42799
42799
  };
42800
42800
  const upsert = (createFn, deps) => {
42801
42801
  if (!deps.length)
42802
42802
  throw new Error("Dependencies need to exist.");
42803
- const cachedValue = getWeakCacheItem(cache, deps);
42803
+ const cachedValue = getWeakCacheItem(cache2, deps);
42804
42804
  const newObject = createFn(cachedValue);
42805
- setWeakCacheItem(cache, deps, newObject);
42805
+ setWeakCacheItem(cache2, deps, newObject);
42806
42806
  };
42807
42807
  const remove = (...deps) => {
42808
- removeWeakCacheItem(cache, deps);
42808
+ removeWeakCacheItem(cache2, deps);
42809
42809
  };
42810
- const get = (deps) => getWeakCacheItem(cache, deps);
42810
+ const get = (deps) => getWeakCacheItem(cache2, deps);
42811
42811
  return {
42812
42812
  get,
42813
- cache,
42813
+ cache: cache2,
42814
42814
  deepCache,
42815
42815
  upsert,
42816
42816
  remove
42817
42817
  };
42818
42818
  };
42819
- var removeWeakCacheItem = (cache, deps) => {
42819
+ var removeWeakCacheItem = (cache2, deps) => {
42820
42820
  while (true) {
42821
42821
  const [dep, ...rest] = deps;
42822
- let entry = cache.get(dep);
42822
+ let entry = cache2.get(dep);
42823
42823
  if (!entry) {
42824
42824
  return;
42825
42825
  }
@@ -42828,7 +42828,7 @@ function requireChunk52ALM2Y4() {
42828
42828
  entry[1] = void 0;
42829
42829
  return;
42830
42830
  } else {
42831
- cache = entry[0];
42831
+ cache2 = entry[0];
42832
42832
  deps = rest;
42833
42833
  }
42834
42834
  }
@@ -44185,11 +44185,11 @@ var directive = (factory, data) => {
44185
44185
  const key = stringify(data);
44186
44186
  let directive2;
44187
44187
  if (key) {
44188
- var cache = cacheByFactory.get(factory);
44189
- if (!cache) {
44190
- cacheByFactory.set(factory, cache = /* @__PURE__ */ new Map());
44188
+ var cache2 = cacheByFactory.get(factory);
44189
+ if (!cache2) {
44190
+ cacheByFactory.set(factory, cache2 = /* @__PURE__ */ new Map());
44191
44191
  }
44192
- directive2 = cache.get(key);
44192
+ directive2 = cache2.get(key);
44193
44193
  }
44194
44194
  if (!directive2) {
44195
44195
  directive2 = Object.defineProperty((params, context) => {
@@ -44198,9 +44198,9 @@ var directive = (factory, data) => {
44198
44198
  }, "toJSON", {
44199
44199
  value: () => key || data
44200
44200
  });
44201
- if (cache) {
44202
- cache.set(key, directive2);
44203
- ensureMaxSize(cache, 1e4);
44201
+ if (cache2) {
44202
+ cache2.set(key, directive2);
44203
+ ensureMaxSize(cache2, 1e4);
44204
44204
  }
44205
44205
  }
44206
44206
  return directive2;
@@ -45472,7 +45472,7 @@ var resolveContext = {
45472
45472
  };
45473
45473
  var handleArbitraryValues = (section, key) => (key = key[0] == "[" && key.slice(-1) == "]" && key.slice(1, -1)) && includes(section, "olor") == /^(#|(hsl|rgb)a?\(|[a-z]+$)/.test(key) && (includes(key, "calc(") ? key.replace(/(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, "$1 $2 ") : key);
45474
45474
  var makeThemeResolver = (config) => {
45475
- const cache = /* @__PURE__ */ new Map();
45475
+ const cache2 = /* @__PURE__ */ new Map();
45476
45476
  const theme2 = { ...defaultTheme, ...config };
45477
45477
  const deref = (theme3, section) => {
45478
45478
  const base = theme3 && theme3[section];
@@ -45486,9 +45486,9 @@ var makeThemeResolver = (config) => {
45486
45486
  defaultValue = key;
45487
45487
  key = join$1(tail(keypath), ".");
45488
45488
  }
45489
- let base = cache.get(section);
45489
+ let base = cache2.get(section);
45490
45490
  if (!base) {
45491
- cache.set(section, base = { ...deref(theme2, section) });
45491
+ cache2.set(section, base = { ...deref(theme2, section) });
45492
45492
  Object.assign(base, deref(theme2.extend, section));
45493
45493
  }
45494
45494
  if (key != null) {
@@ -48472,96 +48472,146 @@ function getAuthHeaders() {
48472
48472
  }
48473
48473
  return headers;
48474
48474
  }
48475
- async function getCampaignConfig$2(contactId) {
48476
- const url = `${API_BASE_URL}/api/campaign-config/${contactId}`;
48477
- const response = await fetch(url, {
48478
- method: "GET",
48479
- headers: getAuthHeaders(),
48480
- credentials: "omit"
48481
- });
48482
- if (!response.ok) {
48483
- const error2 = await response.json().catch(() => ({ error: "Erro desconhecido" }));
48484
- throw new Error(error2.error || `Falha ao buscar config: ${response.status}`);
48485
- }
48486
- const json = await response.json();
48487
- return json;
48488
- }
48489
- async function getCodeToNameMapping(campaignModelId) {
48490
- const url = `${API_BASE_URL}/api/campaign-fields/${campaignModelId}`;
48491
- const response = await fetch(url, {
48492
- method: "GET",
48493
- headers: getAuthHeaders(),
48494
- credentials: "omit"
48495
- });
48496
- if (!response.ok) {
48497
- const error2 = await response.json().catch(() => ({ error: "Erro desconhecido" }));
48498
- throw new Error(error2.error || `Falha ao buscar mapeamento: ${response.status}`);
48499
- }
48500
- const json = await response.json();
48501
- return json;
48502
- }
48503
- async function getCustomerCodeToNameMapping(formId) {
48504
- const url = `${API_BASE_URL}/api/campaign-contact/0?source=customer&formId=${encodeURIComponent(String(formId))}`;
48505
- const response = await fetch(url, {
48506
- method: "GET",
48507
- headers: getAuthHeaders(),
48508
- credentials: "omit"
48509
- });
48510
- if (!response.ok) {
48511
- const error2 = await response.json().catch(() => ({ error: "Erro desconhecido" }));
48512
- throw new Error(error2.error || `Falha ao buscar campos (cliente): ${response.status}`);
48475
+ const CAMPAIGN_FORM_CACHE_TTL_MS = 5 * 60 * 1e3;
48476
+ const DEFAULT_RETRIES = 2;
48477
+ const RETRY_DELAY_MS = 800;
48478
+ const REQUEST_TIMEOUT_MS$1 = 15e3;
48479
+ const cache = /* @__PURE__ */ new Map();
48480
+ function campaignFormCacheKey(request) {
48481
+ return `${request.source}:${request.id}`;
48482
+ }
48483
+ function campaignFormRequestFromCallInfo(info, phoneId) {
48484
+ var _a2, _b;
48485
+ const contact = info == null ? void 0 : info.campaignContact;
48486
+ if (!(contact == null ? void 0 : contact.id)) {
48487
+ return null;
48513
48488
  }
48514
- const json = await response.json();
48515
- const mapping = {};
48516
- if (json && Array.isArray(json.rows)) {
48517
- for (const row of json.rows) {
48518
- for (const field of row) {
48519
- if (field && field.code && field.name) {
48520
- mapping[field.code] = field.name;
48521
- }
48489
+ const customer = contact.customer;
48490
+ if ((customer == null ? void 0 : customer.id) && (customer == null ? void 0 : customer.formId)) {
48491
+ return { source: "customer", id: customer.id, formId: customer.formId, campaignId: ((_a2 = info == null ? void 0 : info.campaign) == null ? void 0 : _a2.id) ?? null, phoneId: phoneId ?? null };
48492
+ }
48493
+ return { source: "campaign", id: contact.id, campaignId: ((_b = info == null ? void 0 : info.campaign) == null ? void 0 : _b.id) ?? null, phoneId: phoneId ?? null };
48494
+ }
48495
+ function buildCampaignFormUrl(apiBaseUrl, request) {
48496
+ const params = new URLSearchParams();
48497
+ if (request.source === "customer") params.set("source", "customer");
48498
+ if (request.campaignId) params.set("campaignId", String(request.campaignId));
48499
+ if (request.formId) params.set("formId", String(request.formId));
48500
+ if (request.phoneId) params.set("phoneId", String(request.phoneId));
48501
+ if (request.signature && request.expiresAt) {
48502
+ params.set("sig", request.signature);
48503
+ params.set("exp", String(request.expiresAt));
48504
+ }
48505
+ const query = params.toString();
48506
+ return `${apiBaseUrl}/api/campaign-form/${encodeURIComponent(String(request.id))}${query ? `?${query}` : ""}`;
48507
+ }
48508
+ function isFormResponse(value) {
48509
+ if (!value || typeof value !== "object") return false;
48510
+ const form = value;
48511
+ return typeof form.id === "number" && !!form.values && typeof form.values === "object" && Array.isArray(form.fields);
48512
+ }
48513
+ const NETWORK_ERROR_MESSAGE = "Não foi possível conectar ao servidor do discador. Verifique a conexão ou se o back-end está atualizado.";
48514
+ const ROUTE_MISSING_MESSAGE = "Rota do formulário não encontrada no servidor do discador. Atualize o back-end (deploy) para esta versão do widget.";
48515
+ function describeHttpError(status, body) {
48516
+ if (status === 401) return "Sessão expirada. Entre novamente no discador.";
48517
+ const fromBody = body.message || body.error;
48518
+ if (fromBody) return fromBody;
48519
+ if (status === 404) return ROUTE_MISSING_MESSAGE;
48520
+ return `Erro ${status}`;
48521
+ }
48522
+ async function requestOnce(url, headers) {
48523
+ const controller = new AbortController();
48524
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS$1);
48525
+ try {
48526
+ const response = await fetch(url, { method: "GET", headers, credentials: "omit", signal: controller.signal });
48527
+ if (!response.ok) {
48528
+ const body = await response.json().catch(() => ({}));
48529
+ const error2 = new Error(describeHttpError(response.status, body));
48530
+ error2.status = response.status;
48531
+ throw error2;
48532
+ }
48533
+ const data = await response.json();
48534
+ if (!isFormResponse(data)) {
48535
+ throw new Error("Resposta inesperada do servidor");
48536
+ }
48537
+ return data;
48538
+ } catch (error2) {
48539
+ if (error2 instanceof Error && error2.name === "AbortError") {
48540
+ throw new Error("Tempo esgotado ao buscar o formulário");
48541
+ }
48542
+ if (error2 instanceof TypeError) {
48543
+ const wrapped = new Error(NETWORK_ERROR_MESSAGE);
48544
+ wrapped.cause = error2;
48545
+ throw wrapped;
48546
+ }
48547
+ throw error2;
48548
+ } finally {
48549
+ clearTimeout(timer);
48550
+ }
48551
+ }
48552
+ function shouldRetry(error2) {
48553
+ const status = error2 == null ? void 0 : error2.status;
48554
+ if (status === void 0) return true;
48555
+ return status >= 500 || status === 408 || status === 429;
48556
+ }
48557
+ function fetchCampaignForm(request, options = {}) {
48558
+ const key = campaignFormCacheKey(request);
48559
+ const now = Date.now();
48560
+ const cached = cache.get(key);
48561
+ if (cached && cached.until > now && !options.force) {
48562
+ return cached.promise;
48563
+ }
48564
+ const url = buildCampaignFormUrl(options.apiBaseUrl ?? API_BASE_URL, request);
48565
+ const headers = options.headers ?? getAuthHeaders();
48566
+ const retries = Math.max(0, options.retries ?? DEFAULT_RETRIES);
48567
+ const promise = (async () => {
48568
+ let lastError;
48569
+ for (let attempt = 0; attempt <= retries; attempt++) {
48570
+ try {
48571
+ return await requestOnce(url, headers);
48572
+ } catch (error2) {
48573
+ lastError = error2;
48574
+ if (attempt === retries || !shouldRetry(error2)) break;
48575
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS * 2 ** attempt));
48522
48576
  }
48523
48577
  }
48524
- }
48525
- return mapping;
48526
- }
48527
- async function getContactValues$2(contactId) {
48528
- const url = `${API_BASE_URL}/api/campaign-contact/${contactId}`;
48529
- const response = await fetch(url, {
48530
- method: "GET",
48531
- headers: getAuthHeaders(),
48532
- credentials: "omit"
48533
- });
48534
- if (!response.ok) {
48535
- const error2 = await response.json().catch(() => ({ error: "Erro desconhecido" }));
48536
- throw new Error(error2.error || `Falha ao buscar valores: ${response.status}`);
48537
- }
48538
- const json = await response.json();
48539
- return json;
48540
- }
48541
- async function getCustomerContactValues$1(customerId) {
48542
- const url = `${API_BASE_URL}/api/campaign-fields/0?source=customer&customerId=${encodeURIComponent(String(customerId))}`;
48543
- const response = await fetch(url, {
48544
- method: "GET",
48545
- headers: getAuthHeaders(),
48546
- credentials: "omit"
48578
+ throw lastError instanceof Error ? lastError : new Error("Erro ao buscar o formulário");
48579
+ })();
48580
+ cache.set(key, { promise, until: now + CAMPAIGN_FORM_CACHE_TTL_MS });
48581
+ promise.then((form) => {
48582
+ var _a2;
48583
+ if (form.partial && ((_a2 = cache.get(key)) == null ? void 0 : _a2.promise) === promise) cache.delete(key);
48584
+ }).catch(() => {
48585
+ var _a2;
48586
+ if (((_a2 = cache.get(key)) == null ? void 0 : _a2.promise) === promise) cache.delete(key);
48547
48587
  });
48548
- if (!response.ok) {
48549
- const error2 = await response.json().catch(() => ({ error: "Erro desconhecido" }));
48550
- throw new Error(error2.error || `Falha ao buscar valores (cliente): ${response.status}`);
48551
- }
48552
- const json = await response.json();
48553
- return json;
48588
+ return promise;
48554
48589
  }
48555
- function findValue$1(finalData, candidates) {
48556
- const entries = Object.entries(finalData);
48557
- const lowerCandidates = candidates.map((item) => item.toLowerCase());
48558
- for (const [key, value] of entries) {
48559
- if (lowerCandidates.includes(key.toLowerCase())) {
48590
+ function normalizeFormKey(value) {
48591
+ return String(value ?? "").normalize("NFC").trim().toLowerCase().replace(/\s+/g, " ");
48592
+ }
48593
+ function findFormValue(form, candidates) {
48594
+ if (!(form == null ? void 0 : form.values)) {
48595
+ return void 0;
48596
+ }
48597
+ const byNormalizedKey = /* @__PURE__ */ new Map();
48598
+ for (const [key, value] of Object.entries(form.values)) {
48599
+ const normalized = stripAccents(normalizeFormKey(key));
48600
+ if (!byNormalizedKey.has(normalized) && value !== void 0 && value !== null && String(value).trim() !== "") {
48601
+ byNormalizedKey.set(normalized, String(value));
48602
+ }
48603
+ }
48604
+ for (const candidate of candidates) {
48605
+ const value = byNormalizedKey.get(stripAccents(normalizeFormKey(candidate)));
48606
+ if (value !== void 0) {
48560
48607
  return value;
48561
48608
  }
48562
48609
  }
48563
48610
  return void 0;
48564
48611
  }
48612
+ function stripAccents(value) {
48613
+ return value.normalize("NFD").replace(/[̀-ͯ]/g, "");
48614
+ }
48565
48615
  function formatPhoneValue(raw) {
48566
48616
  if (!raw) return "-";
48567
48617
  const digits = raw.replace(/\D/g, "");
@@ -48575,18 +48625,12 @@ function formatPhoneValue(raw) {
48575
48625
  return raw.trim();
48576
48626
  }
48577
48627
  function CallInfoDisplay() {
48628
+ var _a2;
48578
48629
  const callInfo = clientSdkReactExports.useCallOperatorCurrentCallInfo();
48579
- useEffect(() => {
48580
- console.log("[CallInfoDisplay] Call info atualizado:", {
48581
- callInfo,
48582
- type: callInfo == null ? void 0 : callInfo.type,
48583
- hasCampaignInfo: (callInfo == null ? void 0 : callInfo.type) === "campaign",
48584
- info: (callInfo == null ? void 0 : callInfo.type) === "campaign" ? callInfo.info : null,
48585
- campaignContact: (callInfo == null ? void 0 : callInfo.type) === "campaign" ? callInfo.info.campaignContact : null
48586
- });
48587
- }, [callInfo]);
48630
+ const currentCall = clientSdkReactExports.useCallOperatorCurrentCall();
48631
+ const campaignContactPhoneId = ((_a2 = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _a2.campaignContactPhoneId) ?? null;
48588
48632
  if ((callInfo == null ? void 0 : callInfo.type) === "campaign") {
48589
- return /* @__PURE__ */ jsxRuntimeExports.jsx(CampaignCallInfoDisplay, { campaignCallInfo: callInfo.info });
48633
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(CampaignCallInfoDisplay, { campaignCallInfo: callInfo.info, campaignContactPhoneId });
48590
48634
  }
48591
48635
  if (callInfo) {
48592
48636
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-4 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs font-semibold text-amber-700", children: "Chamada manual (sem informações de campanha)" }) });
@@ -48596,83 +48640,63 @@ function CallInfoDisplay() {
48596
48640
  function CampaignCallInfoDisplay(props) {
48597
48641
  const { campaign, campaignContact } = props.campaignCallInfo;
48598
48642
  const [contactData, setContactData] = useState({
48599
- finalData: {},
48643
+ form: null,
48600
48644
  isLoading: false,
48601
48645
  error: null
48602
48646
  });
48603
- const [fetchedContactId, setFetchedContactId] = useState(null);
48647
+ const [retry, setRetry] = useState({ key: null, n: 0 });
48648
+ const request = useMemo(
48649
+ () => campaignFormRequestFromCallInfo(props.campaignCallInfo, props.campaignContactPhoneId),
48650
+ [props.campaignCallInfo, props.campaignContactPhoneId]
48651
+ );
48652
+ const requestKey = request ? campaignFormCacheKey(request) : null;
48653
+ const requestRef = useRef(request);
48654
+ requestRef.current = request;
48655
+ const partialRefreshKeyRef = useRef(null);
48604
48656
  useEffect(() => {
48605
- if (!(campaignContact == null ? void 0 : campaignContact.id)) {
48606
- return;
48607
- }
48608
- if (fetchedContactId === campaignContact.id) {
48657
+ const current = requestRef.current;
48658
+ if (!current || !requestKey) {
48659
+ setContactData({ form: null, isLoading: false, error: null });
48609
48660
  return;
48610
48661
  }
48611
48662
  let cancelled = false;
48612
- const fetchContactData = async () => {
48613
- setContactData({ finalData: {}, isLoading: true, error: null });
48614
- try {
48615
- const customer = campaignContact.customer;
48616
- let codeToNameMap = {};
48617
- let contactValues = {};
48618
- if ((customer == null ? void 0 : customer.id) && (customer == null ? void 0 : customer.formId)) {
48619
- [codeToNameMap, contactValues] = await Promise.all([
48620
- getCustomerCodeToNameMapping(customer.formId),
48621
- getCustomerContactValues$1(customer.id)
48622
- ]);
48623
- } else {
48624
- const config = await getCampaignConfig$2(campaignContact.id);
48625
- if (!config.campaignModelId) {
48626
- throw new Error("campaignModelId não encontrado");
48627
- }
48628
- [codeToNameMap, contactValues] = await Promise.all([
48629
- getCodeToNameMapping(config.campaignModelId),
48630
- getContactValues$2(campaignContact.id)
48631
- ]);
48663
+ let refreshTimer = null;
48664
+ setContactData({ form: null, isLoading: true, error: null });
48665
+ fetchCampaignForm(current, { force: retry.key === requestKey && retry.n > 0 }).then((form) => {
48666
+ if (!cancelled) {
48667
+ setContactData({ form, isLoading: false, error: null });
48668
+ if (form.partial && partialRefreshKeyRef.current !== requestKey) {
48669
+ partialRefreshKeyRef.current = requestKey;
48670
+ refreshTimer = setTimeout(() => setRetry((prev) => ({ key: requestKey, n: prev.n + 1 })), 3e3);
48632
48671
  }
48633
- const finalData = {};
48634
- Object.entries(contactValues).forEach(([code, value]) => {
48635
- if (code in codeToNameMap) {
48636
- finalData[codeToNameMap[code]] = value;
48637
- }
48672
+ }
48673
+ }).catch((error2) => {
48674
+ if (!cancelled) {
48675
+ console.error("[CampaignCallInfoDisplay] Erro ao buscar dados:", error2);
48676
+ setContactData({
48677
+ form: null,
48678
+ isLoading: false,
48679
+ error: error2 instanceof Error ? error2.message : "Erro ao buscar dados do contato"
48638
48680
  });
48639
- if (!cancelled) {
48640
- setContactData({
48641
- finalData,
48642
- isLoading: false,
48643
- error: null
48644
- });
48645
- setFetchedContactId(campaignContact.id);
48646
- }
48647
- } catch (error2) {
48648
- if (!cancelled) {
48649
- console.error("[CampaignCallInfoDisplay] Erro ao buscar dados:", error2);
48650
- const errorMessage = error2 instanceof Error ? error2.message : "Erro ao buscar dados do contato";
48651
- setContactData({
48652
- finalData: {},
48653
- isLoading: false,
48654
- error: errorMessage
48655
- });
48656
- }
48657
48681
  }
48658
- };
48659
- void fetchContactData();
48682
+ });
48660
48683
  return () => {
48661
48684
  cancelled = true;
48685
+ if (refreshTimer) clearTimeout(refreshTimer);
48662
48686
  };
48663
- }, [campaignContact, campaignContact == null ? void 0 : campaignContact.id, fetchedContactId]);
48687
+ }, [requestKey, retry]);
48664
48688
  const prioritizedFields = useMemo(
48665
48689
  () => [
48666
- { label: "Decisor", keys: ["DECISOR"], icon: /* @__PURE__ */ jsxRuntimeExports.jsx(FiUser, { className: "text-sm" }) },
48667
- { label: "Empresa", keys: ["EMPRESA", "RAZAO SOCIAL"], icon: /* @__PURE__ */ jsxRuntimeExports.jsx(FiBriefcase, { className: "text-sm" }) },
48668
- { label: "Dívida ativa", keys: ["DIVIDA ATIVA", "DÍVIDA ATIVA"], icon: /* @__PURE__ */ jsxRuntimeExports.jsx(FiHash, { className: "text-sm" }) },
48690
+ { label: "Decisor", keys: ["DECISOR", "CONTATO", "NOME"], icon: /* @__PURE__ */ jsxRuntimeExports.jsx(FiUser, { className: "text-sm" }) },
48691
+ { label: "Empresa", keys: ["EMPRESA", "RAZAO SOCIAL", "RAZÃO SOCIAL", "CLIENTE"], icon: /* @__PURE__ */ jsxRuntimeExports.jsx(FiBriefcase, { className: "text-sm" }) },
48692
+ { label: "Dívida ativa", keys: ["DIVIDA ATIVA", "DÍVIDA ATIVA", "VALOR TOTAL DEVIDO"], icon: /* @__PURE__ */ jsxRuntimeExports.jsx(FiHash, { className: "text-sm" }) },
48669
48693
  { label: "Telefone", keys: ["TELEFONE", "CELULAR", "WHATSAPP"], icon: /* @__PURE__ */ jsxRuntimeExports.jsx(FiSmartphone, { className: "text-sm" }) }
48670
48694
  ],
48671
48695
  []
48672
48696
  );
48673
48697
  const highlightData = useMemo(() => {
48674
48698
  return prioritizedFields.map((field) => {
48675
- const rawValue = (field.label === "Telefone" ? findValue$1(contactData.finalData, field.keys) ?? (campaignContact == null ? void 0 : campaignContact.phoneNumber) : findValue$1(contactData.finalData, field.keys)) || "";
48699
+ const rawValue = (field.label === "Telefone" ? findFormValue(contactData.form, field.keys) ?? (campaignContact == null ? void 0 : campaignContact.phoneNumber) : findFormValue(contactData.form, field.keys)) || "";
48676
48700
  const display2 = field.label === "Telefone" ? formatPhoneValue(rawValue) : (rawValue || "-").toString().trim() || "-";
48677
48701
  return {
48678
48702
  ...field,
@@ -48680,7 +48704,7 @@ function CampaignCallInfoDisplay(props) {
48680
48704
  isEmpty: !rawValue
48681
48705
  };
48682
48706
  });
48683
- }, [campaignContact == null ? void 0 : campaignContact.phoneNumber, contactData.finalData, prioritizedFields]);
48707
+ }, [campaignContact == null ? void 0 : campaignContact.phoneNumber, contactData.form, prioritizedFields]);
48684
48708
  const hasData = highlightData.some((item) => !item.isEmpty);
48685
48709
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4 rounded-2xl border border-slate-200 bg-white/90 p-3 shadow-sm", children: [
48686
48710
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [
@@ -48696,7 +48720,7 @@ function CampaignCallInfoDisplay(props) {
48696
48720
  "Em chamada"
48697
48721
  ] })
48698
48722
  ] }),
48699
- contactData.isLoading && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 p-4 text-center", children: [
48723
+ contactData.isLoading && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 p-4 text-center", role: "status", children: [
48700
48724
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-auto mb-2 inline-block h-7 w-7 animate-spin rounded-full border-2 border-emerald-500 border-t-transparent" }),
48701
48725
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs font-medium text-slate-600", children: "Carregando dados do contato..." })
48702
48726
  ] }),
@@ -48706,10 +48730,11 @@ function CampaignCallInfoDisplay(props) {
48706
48730
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs font-semibold text-rose-700", children: "Erro ao carregar dados" })
48707
48731
  ] }),
48708
48732
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "ml-6 text-[11px] text-rose-600", children: contactData.error }),
48709
- contactData.error.includes("Muitas requisições") && /* @__PURE__ */ jsxRuntimeExports.jsx(
48733
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
48710
48734
  "button",
48711
48735
  {
48712
- onClick: () => setFetchedContactId(null),
48736
+ type: "button",
48737
+ onClick: () => setRetry((prev) => ({ key: requestKey, n: prev.n + 1 })),
48713
48738
  className: "mt-2 ml-6 text-[11px] font-semibold text-rose-700 underline hover:text-rose-800",
48714
48739
  children: "Tentar novamente"
48715
48740
  }
@@ -50237,13 +50262,13 @@ function extractExtension(number) {
50237
50262
  return {};
50238
50263
  }
50239
50264
  var numberWithoutExtension = number.slice(0, start2);
50240
- var matches = number.match(EXTN_PATTERN);
50265
+ var matches2 = number.match(EXTN_PATTERN);
50241
50266
  var i2 = 1;
50242
- while (i2 < matches.length) {
50243
- if (matches[i2]) {
50267
+ while (i2 < matches2.length) {
50268
+ if (matches2[i2]) {
50244
50269
  return {
50245
50270
  number: numberWithoutExtension,
50246
- ext: matches[i2]
50271
+ ext: matches2[i2]
50247
50272
  };
50248
50273
  }
50249
50274
  i2++;
@@ -51141,6 +51166,266 @@ function buildDialPlan(rawValue, defaultPrefix = DEFAULT_COUNTRY_PREFIX) {
51141
51166
  const differs = Boolean(e164) && onlyDigits(e164 ?? "") !== typedDigits;
51142
51167
  return { raw, display: display2, e164, dialDisplay, isValid: isValid2, differs };
51143
51168
  }
51169
+ const QUERIES = {
51170
+ fine: "(pointer: fine)",
51171
+ coarse: "(pointer: coarse)",
51172
+ hover: "(hover: hover)"
51173
+ };
51174
+ function matches(query) {
51175
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
51176
+ return null;
51177
+ }
51178
+ try {
51179
+ return window.matchMedia(query).matches;
51180
+ } catch {
51181
+ return null;
51182
+ }
51183
+ }
51184
+ function getInputProfile() {
51185
+ const fine = matches(QUERIES.fine);
51186
+ const coarse = matches(QUERIES.coarse);
51187
+ const hover = matches(QUERIES.hover);
51188
+ const pointer = coarse ? "coarse" : fine ? "fine" : "unknown";
51189
+ const touchLike = pointer === "coarse" || pointer !== "fine" && hover === false;
51190
+ return { pointer, hover: hover === true, touchLike };
51191
+ }
51192
+ function resolveDensity(density, profile) {
51193
+ if (density === "compact" || density === "comfortable") {
51194
+ return density;
51195
+ }
51196
+ return profile.touchLike ? "comfortable" : "compact";
51197
+ }
51198
+ function parseDensity(value) {
51199
+ const normalized = (value ?? "").trim().toLowerCase();
51200
+ if (normalized === "compact" || normalized === "compacta" || normalized === "compacto") return "compact";
51201
+ if (normalized === "comfortable" || normalized === "confortavel" || normalized === "confortável" || normalized === "touch") return "comfortable";
51202
+ if (normalized === "auto") return "auto";
51203
+ return void 0;
51204
+ }
51205
+ function useInputProfile() {
51206
+ const [profile, setProfile] = useState(() => getInputProfile());
51207
+ useEffect(() => {
51208
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
51209
+ return;
51210
+ }
51211
+ const update2 = () => setProfile(getInputProfile());
51212
+ const lists = [];
51213
+ for (const query of Object.values(QUERIES)) {
51214
+ try {
51215
+ const list = window.matchMedia(query);
51216
+ if (typeof list.addEventListener === "function") {
51217
+ list.addEventListener("change", update2);
51218
+ } else if (typeof list.addListener === "function") {
51219
+ list.addListener(update2);
51220
+ }
51221
+ lists.push(list);
51222
+ } catch {
51223
+ }
51224
+ }
51225
+ update2();
51226
+ return () => {
51227
+ for (const list of lists) {
51228
+ if (typeof list.removeEventListener === "function") {
51229
+ list.removeEventListener("change", update2);
51230
+ } else if (typeof list.removeListener === "function") {
51231
+ list.removeListener(update2);
51232
+ }
51233
+ }
51234
+ };
51235
+ }, []);
51236
+ return profile;
51237
+ }
51238
+ const CLICK_TO_CALL_EVENT = "callix:click-to-call";
51239
+ function presenceFromOperatorState(state) {
51240
+ if (!state) {
51241
+ return "unknown";
51242
+ }
51243
+ switch (state) {
51244
+ case "idle":
51245
+ return "available";
51246
+ case "onBreak":
51247
+ return "on_break";
51248
+ case "afterCall":
51249
+ case "manualCallFailed":
51250
+ return "after_call";
51251
+ case "offline":
51252
+ case "stopped":
51253
+ case "startupError":
51254
+ case "startup.error":
51255
+ case "networkDown":
51256
+ return "offline";
51257
+ case "starting":
51258
+ return "unknown";
51259
+ case "callRinging":
51260
+ case "manualCallSetup":
51261
+ case "manualCallRinging":
51262
+ case "callInProgress":
51263
+ return "in_call";
51264
+ default:
51265
+ return /call|ringing|dial/i.test(state) ? "in_call" : "unknown";
51266
+ }
51267
+ }
51268
+ function canDispatchTo(presence) {
51269
+ return presence === "available" || presence === "on_break";
51270
+ }
51271
+ const REQUEST_TIMEOUT_MS = 1e4;
51272
+ const DEFAULT_PRESENCE_TTL_SECONDS = 45;
51273
+ function withTimeout(signal) {
51274
+ const controller = new AbortController();
51275
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
51276
+ if (signal) {
51277
+ if (signal.aborted) {
51278
+ controller.abort();
51279
+ } else {
51280
+ signal.addEventListener("abort", () => controller.abort(), { once: true });
51281
+ }
51282
+ }
51283
+ return { signal: controller.signal, clear: () => clearTimeout(timer) };
51284
+ }
51285
+ async function pollClickToCall(options) {
51286
+ const { signal, clear } = withTimeout(options.signal);
51287
+ try {
51288
+ const response = await fetch(`${options.apiBaseUrl}/api/click-to-call/poll`, {
51289
+ method: "POST",
51290
+ headers: options.headers,
51291
+ body: JSON.stringify({ state: options.state ?? null, claim: options.claim }),
51292
+ credentials: "omit",
51293
+ signal
51294
+ });
51295
+ if (!response.ok) {
51296
+ let code = null;
51297
+ try {
51298
+ const data2 = await response.json();
51299
+ code = typeof (data2 == null ? void 0 : data2.error) === "string" ? data2.error : null;
51300
+ } catch {
51301
+ }
51302
+ return { ok: false, status: response.status, code };
51303
+ }
51304
+ const data = await response.json();
51305
+ return {
51306
+ ok: true,
51307
+ presence: data.presence ?? "unknown",
51308
+ task: data.task && typeof data.task === "object" && typeof data.task.phone === "string" ? data.task : null,
51309
+ pollIntervalMs: typeof data.pollIntervalMs === "number" ? data.pollIntervalMs : 0,
51310
+ presenceTtlSeconds: typeof data.presenceTtlSeconds === "number" && data.presenceTtlSeconds > 0 ? data.presenceTtlSeconds : DEFAULT_PRESENCE_TTL_SECONDS
51311
+ };
51312
+ } finally {
51313
+ clear();
51314
+ }
51315
+ }
51316
+ async function ackClickToCall(options) {
51317
+ const { signal, clear } = withTimeout();
51318
+ try {
51319
+ const response = await fetch(`${options.apiBaseUrl}/api/click-to-call/${encodeURIComponent(options.taskId)}`, {
51320
+ method: "PATCH",
51321
+ headers: options.headers,
51322
+ body: JSON.stringify({ status: options.status, error: options.error, retry: options.retry === true }),
51323
+ credentials: "omit",
51324
+ keepalive: true,
51325
+ signal
51326
+ });
51327
+ if (!response.ok) {
51328
+ let code = null;
51329
+ try {
51330
+ const data2 = await response.json();
51331
+ code = typeof (data2 == null ? void 0 : data2.error) === "string" ? data2.error : null;
51332
+ } catch {
51333
+ }
51334
+ return { ok: false, status: response.status, code };
51335
+ }
51336
+ const data = await response.json();
51337
+ return { ok: true, task: data.task ?? null };
51338
+ } finally {
51339
+ clear();
51340
+ }
51341
+ }
51342
+ function reportClickToCallOffline(config) {
51343
+ try {
51344
+ void fetch(`${config.apiBaseUrl}/api/click-to-call/poll`, {
51345
+ method: "POST",
51346
+ headers: config.headers,
51347
+ body: JSON.stringify({ state: "offline", claim: false }),
51348
+ credentials: "omit",
51349
+ keepalive: true
51350
+ }).catch(() => void 0);
51351
+ } catch {
51352
+ }
51353
+ }
51354
+ const WORKER_SOURCE = `
51355
+ let timer = null;
51356
+ self.onmessage = (event) => {
51357
+ const interval = Number(event && event.data && event.data.interval) || 0;
51358
+ if (timer) { clearInterval(timer); timer = null; }
51359
+ if (interval > 0) { timer = setInterval(() => self.postMessage('tick'), interval); }
51360
+ };
51361
+ `;
51362
+ function createBackgroundTicker(callback, intervalMs) {
51363
+ let current = Math.max(250, Math.round(intervalMs));
51364
+ let fallbackTimer = null;
51365
+ let worker = null;
51366
+ let objectUrl = null;
51367
+ let stopped = false;
51368
+ const startFallback = () => {
51369
+ if (stopped) return;
51370
+ if (fallbackTimer) clearInterval(fallbackTimer);
51371
+ fallbackTimer = setInterval(callback, current);
51372
+ };
51373
+ try {
51374
+ if (typeof Worker !== "undefined" && typeof Blob !== "undefined" && typeof URL !== "undefined" && URL.createObjectURL) {
51375
+ objectUrl = URL.createObjectURL(new Blob([WORKER_SOURCE], { type: "application/javascript" }));
51376
+ worker = new Worker(objectUrl);
51377
+ worker.onmessage = () => {
51378
+ if (!stopped) callback();
51379
+ };
51380
+ worker.onerror = () => {
51381
+ worker == null ? void 0 : worker.terminate();
51382
+ worker = null;
51383
+ startFallback();
51384
+ };
51385
+ worker.postMessage({ interval: current });
51386
+ } else {
51387
+ startFallback();
51388
+ }
51389
+ } catch {
51390
+ worker = null;
51391
+ startFallback();
51392
+ }
51393
+ return {
51394
+ setInterval(ms2) {
51395
+ if (stopped) return;
51396
+ const next = Math.max(250, Math.round(ms2));
51397
+ if (next === current) return;
51398
+ current = next;
51399
+ if (worker) {
51400
+ worker.postMessage({ interval: current });
51401
+ } else {
51402
+ startFallback();
51403
+ }
51404
+ },
51405
+ stop() {
51406
+ stopped = true;
51407
+ if (worker) {
51408
+ worker.terminate();
51409
+ worker = null;
51410
+ }
51411
+ if (objectUrl) {
51412
+ URL.revokeObjectURL(objectUrl);
51413
+ objectUrl = null;
51414
+ }
51415
+ if (fallbackTimer) {
51416
+ clearInterval(fallbackTimer);
51417
+ fallbackTimer = null;
51418
+ }
51419
+ }
51420
+ };
51421
+ }
51422
+ function nationalDigitsFromE164(e164) {
51423
+ const digits = (e164 ?? "").replace(/\D/g, "");
51424
+ if (digits.startsWith("55") && (digits.length === 12 || digits.length === 13)) {
51425
+ return digits.slice(2);
51426
+ }
51427
+ return e164.startsWith("+") ? `+${digits}` : digits;
51428
+ }
51144
51429
  const DEFAULT_DIALER_DIMENSIONS = { width: 448, height: 640 };
51145
51430
  const MIN_DIALER_WIDTH = 280;
51146
51431
  const MIN_DIALER_HEIGHT = 420;
@@ -51344,6 +51629,17 @@ function FloatingDialer({
51344
51629
  }
51345
51630
  prevAuthRef.current = isAuthenticated;
51346
51631
  }, [isAuthenticated]);
51632
+ useEffect(() => {
51633
+ const onClickToCall = (event) => {
51634
+ const task = event.detail;
51635
+ setIsOpen(true);
51636
+ if (task == null ? void 0 : task.phone) {
51637
+ updateDialValue(nationalDigitsFromE164(task.phone).slice(0, MAX_DIAL_LENGTH));
51638
+ }
51639
+ };
51640
+ window.addEventListener(CLICK_TO_CALL_EVENT, onClickToCall);
51641
+ return () => window.removeEventListener(CLICK_TO_CALL_EVENT, onClickToCall);
51642
+ }, [updateDialValue]);
51347
51643
  const prevLoginSignalRef = useRef(false);
51348
51644
  useEffect(() => {
51349
51645
  const hasLoginSignal = !isAuthenticated && Boolean(loginConfirmation || sessionError);
@@ -51845,7 +52141,8 @@ function AuthenticatedDialer({
51845
52141
  typeof window !== "undefined" ? window.innerWidth <= 640 : false
51846
52142
  );
51847
52143
  const isMobileVariant = variant === "mobile";
51848
- const isMobileLayout = isMobileVariant || isMobileViewport;
52144
+ const inputProfile = useInputProfile();
52145
+ const isMobileLayout = isMobileVariant || isMobileViewport && inputProfile.touchLike;
51849
52146
  const breaks = useSignal(operator == null ? void 0 : operator.breaks) || [];
51850
52147
  const { goOnBreak, goOffBreak } = useOperatorBreak();
51851
52148
  const sanitizedDialValue = useMemo(() => dialValue.slice(0, MAX_DIAL_LENGTH), [dialValue]);
@@ -51879,11 +52176,16 @@ function AuthenticatedDialer({
51879
52176
  [becomeAvailable]
51880
52177
  );
51881
52178
  useEffect(() => {
51882
- if (!hasRequestedAvailabilityRef.current) {
51883
- hasRequestedAvailabilityRef.current = true;
51884
- void requestAvailability("Nao foi possivel ficar disponivel automaticamente.");
52179
+ if (hasRequestedAvailabilityRef.current) {
52180
+ return;
51885
52181
  }
51886
- }, [requestAvailability]);
52182
+ hasRequestedAvailabilityRef.current = true;
52183
+ const busyStates = ["onBreak", "manualCallSetup", "manualCallRinging", "manualCallFailed", "callRinging", "callInProgress", "afterCall"];
52184
+ if (callState && busyStates.includes(callState)) {
52185
+ return;
52186
+ }
52187
+ void requestAvailability("Nao foi possivel ficar disponivel automaticamente.");
52188
+ }, [requestAvailability, callState]);
51887
52189
  useEffect(() => {
51888
52190
  if (callState === "idle") {
51889
52191
  setAvailabilityError(null);
@@ -51968,8 +52270,14 @@ function AuthenticatedDialer({
51968
52270
  }, 8e3);
51969
52271
  return () => window.clearTimeout(timer);
51970
52272
  }, [callState]);
52273
+ const normalizedNumberRef = useRef(normalizedNumber);
52274
+ normalizedNumberRef.current = normalizedNumber;
51971
52275
  useEffect(() => {
51972
52276
  if (callState === "manualCallRinging" || callState === "callInProgress") {
52277
+ const dialed = normalizedNumberRef.current;
52278
+ if (dialed) {
52279
+ setLastDialedNumber((previous) => previous ?? dialed);
52280
+ }
51973
52281
  setDialValue("");
51974
52282
  }
51975
52283
  if (callState === "idle") {
@@ -52368,6 +52676,16 @@ function AuthenticatedDialer({
52368
52676
  return newValue;
52369
52677
  });
52370
52678
  }, [setDialValue, cursorPosition]);
52679
+ useEffect(() => {
52680
+ const onClickToCall = (event) => {
52681
+ const task = event.detail;
52682
+ if (!(task == null ? void 0 : task.phone)) return;
52683
+ setCallError(null);
52684
+ setLastDialedNumber(task.phone);
52685
+ };
52686
+ window.addEventListener(CLICK_TO_CALL_EVENT, onClickToCall);
52687
+ return () => window.removeEventListener(CLICK_TO_CALL_EVENT, onClickToCall);
52688
+ }, []);
52371
52689
  const handleCall = useCallback(async () => {
52372
52690
  if (!isIdle && !isOnBreak) {
52373
52691
  return;
@@ -54056,30 +54374,65 @@ function historyPhoneDigits(item) {
54056
54374
  }
54057
54375
  return digits;
54058
54376
  }
54377
+ function normalizeSearchText(value) {
54378
+ return (value ?? "").toString().normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/\s+/g, " ").trim();
54379
+ }
54380
+ function classifyHistoryQuery(query) {
54381
+ const raw = (query ?? "").trim();
54382
+ if (!raw) {
54383
+ return { kind: "empty", digits: "", text: "" };
54384
+ }
54385
+ const digits = onlyDigits(raw);
54386
+ const nonPhoneChars = raw.replace(/[\d\s()+\-.]/g, "");
54387
+ if (digits.length >= 3 && nonPhoneChars.length === 0) {
54388
+ return { kind: "phone", digits, text: normalizeSearchText(raw) };
54389
+ }
54390
+ return { kind: "text", digits, text: normalizeSearchText(raw) };
54391
+ }
54392
+ function historySearchHaystack(item) {
54393
+ const parts = [
54394
+ item.formattedPhone,
54395
+ historyPhoneDigits(item),
54396
+ item.callTypeLabel,
54397
+ item.campaignName,
54398
+ item.contactLabel,
54399
+ item.qualificationName,
54400
+ item.failureCauseLabel,
54401
+ item.hangupCauseLabel,
54402
+ item.formattedDate,
54403
+ item.note,
54404
+ item.protocol !== void 0 ? String(item.protocol) : void 0
54405
+ ];
54406
+ if (item.fields) {
54407
+ for (const value of Object.values(item.fields)) {
54408
+ if (value !== void 0 && value !== null) {
54409
+ parts.push(String(value));
54410
+ }
54411
+ }
54412
+ }
54413
+ return normalizeSearchText(parts.filter(Boolean).join(" | "));
54414
+ }
54415
+ function itemMatchesHistoryQuery(item, query) {
54416
+ if (query.kind === "empty") {
54417
+ return true;
54418
+ }
54419
+ if (query.kind === "phone") {
54420
+ return historyPhoneDigits(item).includes(query.digits) || onlyDigits(item.phoneNumber).includes(query.digits) || item.protocol !== void 0 && String(item.protocol).includes(query.digits);
54421
+ }
54422
+ const haystack = historySearchHaystack(item);
54423
+ const terms = query.text.split(" ").filter(Boolean);
54424
+ return terms.every((term) => haystack.includes(term));
54425
+ }
54426
+ function filterHistoryItems(items, query) {
54427
+ const parsed = typeof query === "string" ? classifyHistoryQuery(query) : query;
54428
+ if (parsed.kind === "empty") {
54429
+ return items;
54430
+ }
54431
+ return items.filter((item) => itemMatchesHistoryQuery(item, parsed));
54432
+ }
54059
54433
  const DEFAULT_QUALIFICATION = "chamada sem qualificação";
54434
+ const SEARCH_DEBOUNCE_MS = 350;
54060
54435
  const DETAILS_TITLE = "Ver detalhes da chamada";
54061
- async function fetchJson(url, headers) {
54062
- const response = await fetch(url, { method: "GET", headers, credentials: "omit" });
54063
- if (!response.ok) {
54064
- const body = await response.json().catch(() => ({}));
54065
- throw new Error(body.error || `Erro ${response.status}`);
54066
- }
54067
- return await response.json();
54068
- }
54069
- async function fetchCampaignForm(apiBaseUrl, contactId, headers) {
54070
- const config = await fetchJson(`${apiBaseUrl}/api/campaign-config/${contactId}`, headers);
54071
- if (!(config == null ? void 0 : config.campaignModelId)) {
54072
- throw new Error("Formulário da campanha não encontrado");
54073
- }
54074
- const [fields, values] = await Promise.all([
54075
- fetchJson(`${apiBaseUrl}/api/campaign-fields/${config.campaignModelId}`, headers),
54076
- fetchJson(`${apiBaseUrl}/api/campaign-contact/${contactId}`, headers)
54077
- ]);
54078
- return {
54079
- rows: Array.isArray(fields == null ? void 0 : fields.rows) ? fields.rows.filter((row) => Array.isArray(row) && row.length > 0) : [],
54080
- values: values && typeof values === "object" ? values : {}
54081
- };
54082
- }
54083
54436
  function useRecordings(apiBaseUrl, getAuthHeaders2) {
54084
54437
  const [states, setStates] = useState({});
54085
54438
  const statesRef = useRef(states);
@@ -54335,7 +54688,7 @@ function CallDetailsSheet({
54335
54688
  onCall,
54336
54689
  onWhatsApp
54337
54690
  }) {
54338
- const [form, setForm] = useState({ status: "idle", rows: [], values: {} });
54691
+ const [form, setForm] = useState({ status: "idle", form: null });
54339
54692
  const closeButtonRef = useRef(null);
54340
54693
  const isCampaign = item.callType === "campaign";
54341
54694
  useEffect(() => {
@@ -54361,65 +54714,79 @@ function CallDetailsSheet({
54361
54714
  }, [onClose]);
54362
54715
  useEffect(() => {
54363
54716
  if (!isCampaign || !item.campaignContactId) {
54364
- setForm({ status: "idle", rows: [], values: {} });
54717
+ setForm({ status: "idle", form: null });
54365
54718
  return;
54366
54719
  }
54367
54720
  let cancelled = false;
54368
- setForm({ status: "loading", rows: [], values: {} });
54369
- fetchCampaignForm(apiBaseUrl, item.campaignContactId, getAuthHeaders2()).then((result2) => {
54721
+ setForm({ status: "loading", form: null });
54722
+ fetchCampaignForm(
54723
+ {
54724
+ source: "campaign",
54725
+ id: item.campaignContactId,
54726
+ campaignId: item.campaignId ?? null,
54727
+ signature: item.formSignature ?? null,
54728
+ expiresAt: item.formExpiresAt ?? null
54729
+ },
54730
+ { apiBaseUrl, headers: getAuthHeaders2(), retries: 0 }
54731
+ ).then((result2) => {
54370
54732
  if (!cancelled) {
54371
- setForm({ status: "ready", rows: result2.rows, values: result2.values });
54733
+ setForm({ status: "ready", form: result2 });
54372
54734
  }
54373
54735
  }).catch((error2) => {
54374
54736
  if (!cancelled) {
54375
54737
  console.warn("[CallHistoryList] Formulário da campanha indisponível, usando campos do histórico:", error2);
54376
- setForm({
54377
- status: "error",
54378
- rows: [],
54379
- values: {},
54380
- error: error2 instanceof Error ? error2.message : "Erro ao carregar formulário"
54381
- });
54738
+ setForm({ status: "error", form: null, error: error2 instanceof Error ? error2.message : "Erro ao carregar formulário" });
54382
54739
  }
54383
54740
  });
54384
54741
  return () => {
54385
54742
  cancelled = true;
54386
54743
  };
54387
- }, [apiBaseUrl, getAuthHeaders2, isCampaign, item.campaignContactId, item.id]);
54744
+ }, [apiBaseUrl, getAuthHeaders2, isCampaign, item.campaignContactId, item.campaignId, item.id]);
54388
54745
  const fallbackFields = useMemo(
54389
54746
  () => Object.entries(item.fields ?? {}).filter(
54390
54747
  ([key, value]) => value !== void 0 && value !== null && String(value).trim() !== "" && !isInternalFieldKey(key)
54391
54748
  ),
54392
54749
  [item.fields]
54393
54750
  );
54751
+ const fallbackByKey = useMemo(() => {
54752
+ const map = /* @__PURE__ */ new Map();
54753
+ for (const [key, value] of Object.entries(item.fields ?? {})) {
54754
+ if (value === void 0 || value === null || String(value).trim() === "") continue;
54755
+ map.set(stripAccents(normalizeFormKey(key)), String(value));
54756
+ }
54757
+ return map;
54758
+ }, [item.fields]);
54394
54759
  const resolveFieldValue = (field) => {
54395
- var _a2;
54396
- const byCode = form.values[field.code];
54397
- if (byCode !== void 0 && byCode !== null && String(byCode).trim() !== "") {
54398
- return String(byCode);
54399
- }
54400
- const byName = (_a2 = item.fields) == null ? void 0 : _a2[field.name];
54401
- if (byName !== void 0 && byName !== null && String(byName).trim() !== "") {
54402
- return String(byName);
54760
+ if (field.value && field.value.trim() !== "") {
54761
+ return field.value;
54403
54762
  }
54404
- return "-";
54763
+ const byName = fallbackByKey.get(stripAccents(normalizeFormKey(field.label))) ?? fallbackByKey.get(stripAccents(normalizeFormKey(field.key)));
54764
+ return byName ?? "-";
54405
54765
  };
54406
- const hasFormRows = form.status === "ready" && form.rows.length > 0;
54766
+ const formRows = form.status === "ready" && form.form ? form.form.rows && form.form.rows.length > 0 ? form.form.rows : form.form.fields.length > 0 ? [form.form.fields] : [] : [];
54767
+ const hasFormRows = formRows.length > 0;
54407
54768
  let formContent;
54408
- if (form.status === "loading") {
54769
+ if (form.status === "loading" && fallbackFields.length === 0) {
54409
54770
  formContent = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "details-loading", role: "status", children: [
54410
54771
  /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-spinner fa-spin", "aria-hidden": "true" }),
54411
54772
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Carregando formulário..." })
54412
54773
  ] });
54413
54774
  } else if (hasFormRows) {
54414
- formContent = form.rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-form-row", children: row.map((field, fieldIndex) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "details-field", children: [
54415
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-field-label", title: field.name, children: field.name }),
54775
+ formContent = formRows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-form-row", children: row.map((field, fieldIndex) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "details-field", children: [
54776
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-field-label", title: field.label, children: field.label }),
54416
54777
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-field-value", children: resolveFieldValue(field) })
54417
- ] }, `${rowIndex}-${fieldIndex}-${field.code}`)) }, rowIndex));
54778
+ ] }, `${rowIndex}-${fieldIndex}-${field.key}`)) }, rowIndex));
54418
54779
  } else if (fallbackFields.length > 0) {
54419
- formContent = /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-form-row", children: fallbackFields.map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "details-field", children: [
54420
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-field-label", title: key, children: key }),
54421
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-field-value", children: value })
54422
- ] }, key)) });
54780
+ formContent = /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
54781
+ form.status === "loading" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "details-loading details-loading-inline", role: "status", children: [
54782
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-spinner fa-spin", "aria-hidden": "true" }),
54783
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Atualizando formulário..." })
54784
+ ] }),
54785
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-form-row", children: fallbackFields.map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "details-field", children: [
54786
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-field-label", title: key, children: key }),
54787
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "details-field-value", children: value })
54788
+ ] }, key)) })
54789
+ ] });
54423
54790
  } else {
54424
54791
  formContent = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "details-empty", children: [
54425
54792
  /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-user", "aria-hidden": "true" }),
@@ -54528,12 +54895,52 @@ function CallHistoryList({
54528
54895
  onCall,
54529
54896
  onWhatsApp,
54530
54897
  apiBaseUrl,
54531
- getAuthHeaders: getAuthHeaders2
54898
+ getAuthHeaders: getAuthHeaders2,
54899
+ query,
54900
+ onQueryChange,
54901
+ isSearching = false,
54902
+ searchHint = null
54532
54903
  }) {
54533
54904
  const [openPlayerId, setOpenPlayerId] = useState(null);
54534
54905
  const [selectedId, setSelectedId] = useState(null);
54535
54906
  const recordings = useRecordings(apiBaseUrl, getAuthHeaders2);
54536
54907
  const { retain } = recordings;
54908
+ const [draft, setDraft] = useState(query ?? "");
54909
+ const lastEmittedRef = useRef(query ?? "");
54910
+ const debounceRef = useRef(null);
54911
+ useEffect(() => {
54912
+ const external = query ?? "";
54913
+ if (external !== lastEmittedRef.current) {
54914
+ lastEmittedRef.current = external;
54915
+ setDraft(external);
54916
+ }
54917
+ }, [query]);
54918
+ useEffect(() => {
54919
+ return () => {
54920
+ if (debounceRef.current) clearTimeout(debounceRef.current);
54921
+ };
54922
+ }, []);
54923
+ const handleDraftChange = useCallback(
54924
+ (value) => {
54925
+ setDraft(value);
54926
+ if (!onQueryChange) return;
54927
+ if (debounceRef.current) clearTimeout(debounceRef.current);
54928
+ debounceRef.current = setTimeout(() => {
54929
+ lastEmittedRef.current = value;
54930
+ onQueryChange(value);
54931
+ }, SEARCH_DEBOUNCE_MS);
54932
+ },
54933
+ [onQueryChange]
54934
+ );
54935
+ const clearSearch = useCallback(() => {
54936
+ setDraft("");
54937
+ if (debounceRef.current) clearTimeout(debounceRef.current);
54938
+ lastEmittedRef.current = "";
54939
+ onQueryChange == null ? void 0 : onQueryChange("");
54940
+ }, [onQueryChange]);
54941
+ const parsedQuery = useMemo(() => classifyHistoryQuery(draft), [draft]);
54942
+ const visibleItems = useMemo(() => filterHistoryItems(items, parsedQuery), [items, parsedQuery]);
54943
+ const hasQuery = parsedQuery.kind !== "empty";
54537
54944
  const dayLabels = useMemo(() => {
54538
54945
  const now = /* @__PURE__ */ new Date();
54539
54946
  const labels = {};
@@ -54583,6 +54990,28 @@ function CallHistoryList({
54583
54990
  /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "header-title", children: "Recentes" }),
54584
54991
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "history-refresh-btn", onClick: onRefresh, disabled: isLoading, "aria-label": "Atualizar histórico", children: /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: `fas fa-arrows-rotate ${isLoading ? "fa-spin" : ""}`, "aria-hidden": "true" }) })
54585
54992
  ] }),
54993
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "history-search-bar", children: [
54994
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "history-search", children: [
54995
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-magnifying-glass history-search-icon", "aria-hidden": "true" }),
54996
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54997
+ "input",
54998
+ {
54999
+ type: "search",
55000
+ className: "history-search-input",
55001
+ placeholder: "Buscar número, campanha, cliente...",
55002
+ "aria-label": "Buscar no histórico",
55003
+ autoComplete: "off",
55004
+ autoCapitalize: "none",
55005
+ autoCorrect: "off",
55006
+ spellCheck: false,
55007
+ value: draft,
55008
+ onChange: (event) => handleDraftChange(event.target.value)
55009
+ }
55010
+ ),
55011
+ isSearching ? /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-spinner fa-spin history-search-spinner", "aria-hidden": "true", title: "Buscando..." }) : draft ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "history-search-clear", onClick: clearSearch, "aria-label": "Limpar busca", children: /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-xmark", "aria-hidden": "true" }) }) : null
55012
+ ] }),
55013
+ hasQuery && searchHint && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "history-search-hint", children: searchHint })
55014
+ ] }),
54586
55015
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "call-history-list", id: "callHistoryList", children: isLoading && items.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "history-loading", role: "status", children: [
54587
55016
  /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-spinner fa-spin", "aria-hidden": "true" }),
54588
55017
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Carregando..." })
@@ -54593,7 +55022,18 @@ function CallHistoryList({
54593
55022
  ] }) : items.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "history-empty", children: [
54594
55023
  /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-phone-slash", "aria-hidden": "true" }),
54595
55024
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Nenhuma ligação recente" })
54596
- ] }) : items.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx(
55025
+ ] }) : visibleItems.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "history-empty history-empty-search", children: [
55026
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-magnifying-glass", "aria-hidden": "true" }),
55027
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
55028
+ "Nenhuma chamada encontrada para ",
55029
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { children: [
55030
+ "“",
55031
+ draft.trim(),
55032
+ "”"
55033
+ ] })
55034
+ ] }),
55035
+ isSearching && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "history-search-status", children: "Buscando no servidor..." })
55036
+ ] }) : visibleItems.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx(
54597
55037
  HistoryRow,
54598
55038
  {
54599
55039
  item,
@@ -56061,9 +56501,55 @@ function DebugPanel({ onClose }) {
56061
56501
  ] });
56062
56502
  }
56063
56503
  const VIEWPORT_MARGIN$1 = 16;
56504
+ const BASE_WIDTH = 520;
56505
+ const FALLBACK_HEIGHT = 600;
56506
+ const FORM_SCALE_STORAGE_KEY = "callix_dialer_form_scale";
56507
+ const FORM_SCALE_MIN = 70;
56508
+ const FORM_SCALE_MAX = 180;
56509
+ const FORM_SCALE_STEP = 10;
56510
+ const FORM_SCALE_DEFAULT = 100;
56511
+ const CONTROL_MIN_PX = 28;
56512
+ const CONTROL_BUTTON_CLASS = "flex flex-shrink-0 items-center justify-center rounded-md transition hover:bg-black hover:bg-opacity-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white";
56513
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
56064
56514
  function clamp$1(value, min2, max2) {
56065
56515
  return Math.min(Math.max(value, min2), max2);
56066
56516
  }
56517
+ function clampFormScale(percent) {
56518
+ if (!Number.isFinite(percent)) return FORM_SCALE_DEFAULT;
56519
+ const rounded = Math.round(percent / FORM_SCALE_STEP) * FORM_SCALE_STEP;
56520
+ return clamp$1(rounded, FORM_SCALE_MIN, FORM_SCALE_MAX);
56521
+ }
56522
+ function readStoredFormScale() {
56523
+ if (typeof window === "undefined") return FORM_SCALE_DEFAULT;
56524
+ try {
56525
+ const raw = window.localStorage.getItem(FORM_SCALE_STORAGE_KEY);
56526
+ if (raw === null || raw.trim() === "") return FORM_SCALE_DEFAULT;
56527
+ return clampFormScale(Number(raw));
56528
+ } catch {
56529
+ return FORM_SCALE_DEFAULT;
56530
+ }
56531
+ }
56532
+ function storeFormScale(percent) {
56533
+ try {
56534
+ window.localStorage.setItem(FORM_SCALE_STORAGE_KEY, String(percent));
56535
+ } catch {
56536
+ }
56537
+ }
56538
+ function detectZoomSupport() {
56539
+ if (typeof CSS === "undefined" || typeof CSS.supports !== "function") return true;
56540
+ try {
56541
+ if (!CSS.supports("zoom", "1.5")) return false;
56542
+ } catch {
56543
+ return true;
56544
+ }
56545
+ const firefox = typeof navigator !== "undefined" ? /\bFirefox\/(\d+)/.exec(navigator.userAgent) : null;
56546
+ if (firefox && Number(firefox[1]) < 131) return false;
56547
+ return true;
56548
+ }
56549
+ function readViewport() {
56550
+ if (typeof window === "undefined") return { width: 1024, height: 768 };
56551
+ return { width: window.innerWidth, height: window.innerHeight };
56552
+ }
56067
56553
  function formatDuration(seconds) {
56068
56554
  const hrs = Math.floor(seconds / 3600);
56069
56555
  const mins = Math.floor(seconds % 3600 / 60);
@@ -56073,147 +56559,91 @@ function formatDuration(seconds) {
56073
56559
  }
56074
56560
  return `${mins}:${secs.toString().padStart(2, "0")}`;
56075
56561
  }
56076
- async function fetchWithRetry(url, options, retries = 3, backoff = 1e3) {
56077
- console.log(`[DraggableClientInfoModal] 🚀 Iniciando fetch: ${url}`);
56078
- for (let i2 = 0; i2 < retries; i2++) {
56079
- try {
56080
- console.log(`[DraggableClientInfoModal] 📡 Tentativa ${i2 + 1}/${retries}`);
56081
- const startTime = Date.now();
56082
- const response = await fetch(url, options);
56083
- const duration = Date.now() - startTime;
56084
- console.log(`[DraggableClientInfoModal] ⏱️ Resposta em ${duration}ms - Status: ${response.status}`);
56085
- if (!response.ok) {
56086
- if (response.status >= 400 && response.status < 500) {
56087
- const error2 = await response.json().catch(() => ({ error: "Erro desconhecido" }));
56088
- console.error(`[DraggableClientInfoModal] ❌ Erro do cliente (${response.status}):`, error2);
56089
- throw new Error(error2.error || `Falha: ${response.status}`);
56090
- }
56091
- console.warn(`[DraggableClientInfoModal] ⚠️ Erro do servidor (${response.status}), tentando novamente...`);
56092
- throw new Error(`HTTP ${response.status}`);
56093
- }
56094
- const json = await response.json();
56095
- console.log(`[DraggableClientInfoModal] ✅ Dados recebidos com sucesso`);
56096
- return json;
56097
- } catch (error2) {
56098
- const isLastAttempt = i2 === retries - 1;
56099
- if (isLastAttempt) {
56100
- console.error(`[DraggableClientInfoModal] 💥 Falha final após ${retries} tentativas:`, error2);
56101
- throw error2;
56102
- }
56103
- const delay = backoff * Math.pow(2, i2);
56104
- console.warn(`[DraggableClientInfoModal] ⏳ Aguardando ${delay}ms antes da próxima tentativa...`);
56105
- await new Promise((resolve) => setTimeout(resolve, delay));
56106
- }
56107
- }
56108
- throw new Error("Todas as tentativas falharam");
56109
- }
56110
- async function getCampaignConfig$1(contactId) {
56111
- const url = `${API_BASE_URL}/api/campaign-config/${contactId}`;
56112
- return fetchWithRetry(url, {
56113
- method: "GET",
56114
- headers: getAuthHeaders(),
56115
- credentials: "omit"
56116
- });
56117
- }
56118
- async function getCampaignFields(campaignModelId) {
56119
- const url = `${API_BASE_URL}/api/campaign-fields/${campaignModelId}`;
56120
- const json = await fetchWithRetry(url, {
56121
- method: "GET",
56122
- headers: getAuthHeaders(),
56123
- credentials: "omit"
56124
- });
56125
- console.log("[DraggableClientInfoModal] Estrutura de campos recebida:", json);
56126
- if (!json.rows || !Array.isArray(json.rows)) {
56127
- console.warn("[DraggableClientInfoModal] Estrutura de campos inválida, usando fallback");
56128
- return {
56129
- rows: [],
56130
- labelField: null,
56131
- phoneFields: []
56132
- };
56133
- }
56134
- return json;
56135
- }
56136
- async function getCustomerFields(formId) {
56137
- const url = `${API_BASE_URL}/api/campaign-contact/0?source=customer&formId=${encodeURIComponent(String(formId))}`;
56138
- const json = await fetchWithRetry(url, {
56139
- method: "GET",
56140
- headers: getAuthHeaders(),
56141
- credentials: "omit"
56142
- });
56143
- console.log("[DraggableClientInfoModal] (cliente) Estrutura de campos recebida:", json);
56144
- if (!json.rows || !Array.isArray(json.rows)) {
56145
- console.warn("[DraggableClientInfoModal] (cliente) Estrutura de campos inválida, usando fallback");
56146
- return { rows: [], labelField: null, phoneFields: [] };
56147
- }
56148
- return json;
56149
- }
56150
- async function getContactValues$1(contactId) {
56151
- const url = `${API_BASE_URL}/api/campaign-contact/${contactId}`;
56152
- return fetchWithRetry(url, {
56153
- method: "GET",
56154
- headers: getAuthHeaders(),
56155
- credentials: "omit"
56156
- });
56157
- }
56158
- async function getCustomerContactValues(customerId) {
56159
- const url = `${API_BASE_URL}/api/campaign-fields/0?source=customer&customerId=${encodeURIComponent(String(customerId))}`;
56160
- return fetchWithRetry(url, {
56161
- method: "GET",
56162
- headers: getAuthHeaders(),
56163
- credentials: "omit"
56164
- });
56562
+ function gridColsFor(count) {
56563
+ if (count === 2) return "grid-cols-2";
56564
+ if (count === 3) return "grid-cols-3";
56565
+ if (count === 4) return "grid-cols-4";
56566
+ if (count >= 5) return "grid-cols-5";
56567
+ return "grid-cols-1";
56165
56568
  }
56166
56569
  function DraggableClientInfoModal({
56167
56570
  isOpen,
56168
56571
  campaignCallInfo,
56572
+ campaignContactPhoneId,
56169
56573
  callDuration,
56170
56574
  isCallActive,
56171
- onClose
56575
+ onClose,
56576
+ portalTarget
56172
56577
  }) {
56173
56578
  const containerRef = useRef(null);
56174
56579
  const pointerRef = useRef({ pointerId: null, offsetX: 0, offsetY: 0 });
56175
56580
  const dragMovedRef = useRef(false);
56581
+ const focusWithinRef = useRef(false);
56582
+ const previouslyFocusedRef = useRef(null);
56583
+ const titleId = useId();
56176
56584
  const [position2, setPosition] = useState({ x: 100, y: 100 });
56177
56585
  const [isDragging, setIsDragging] = useState(false);
56178
- const [contactData, setContactData] = useState({});
56179
- const [fieldRows, setFieldRows] = useState([]);
56586
+ const [viewport, setViewport] = useState(readViewport);
56587
+ const zoomSupported = useMemo(detectZoomSupport, []);
56588
+ const [scalePercent, setScalePercent] = useState(readStoredFormScale);
56589
+ const [announcement, setAnnouncement] = useState("");
56590
+ const scale = zoomSupported ? scalePercent / 100 : 1;
56591
+ const scaleRef = useRef(scale);
56592
+ scaleRef.current = scale;
56593
+ const [form, setForm] = useState(null);
56180
56594
  const [isLoading, setIsLoading] = useState(false);
56181
56595
  const [error2, setError] = useState(null);
56182
- const [fetchedContactId, setFetchedContactId] = useState(null);
56596
+ const [retry, setRetry] = useState({ key: null, n: 0 });
56183
56597
  const campaignContact = campaignCallInfo == null ? void 0 : campaignCallInfo.campaignContact;
56184
- const clampPositionToViewport = useCallback((proposed) => {
56185
- if (typeof window === "undefined") {
56186
- return proposed;
56187
- }
56188
- const viewportWidth = window.innerWidth;
56189
- const viewportHeight = window.innerHeight;
56190
- const element = containerRef.current;
56191
- const width = (element == null ? void 0 : element.offsetWidth) ?? 500;
56192
- const height = (element == null ? void 0 : element.offsetHeight) ?? 600;
56193
- const minX = VIEWPORT_MARGIN$1;
56194
- const minY = VIEWPORT_MARGIN$1;
56195
- const maxX = Math.max(minX, viewportWidth - width - VIEWPORT_MARGIN$1);
56196
- const maxY = Math.max(minY, viewportHeight - height - VIEWPORT_MARGIN$1);
56598
+ const request = useMemo(
56599
+ () => campaignFormRequestFromCallInfo(campaignCallInfo, campaignContactPhoneId),
56600
+ [campaignCallInfo, campaignContactPhoneId]
56601
+ );
56602
+ const requestKey = request ? campaignFormCacheKey(request) : null;
56603
+ const requestRef = useRef(request);
56604
+ requestRef.current = request;
56605
+ const partialRefreshKeyRef = useRef(null);
56606
+ const measure = useCallback(() => {
56607
+ var _a2;
56608
+ const rect = (_a2 = containerRef.current) == null ? void 0 : _a2.getBoundingClientRect();
56609
+ const currentScale = scaleRef.current;
56197
56610
  return {
56198
- x: clamp$1(proposed.x, minX, maxX),
56199
- y: clamp$1(proposed.y, minY, maxY)
56611
+ width: rect && rect.width > 0 ? rect.width : BASE_WIDTH * currentScale,
56612
+ height: rect && rect.height > 0 ? rect.height : FALLBACK_HEIGHT * currentScale
56200
56613
  };
56201
56614
  }, []);
56202
- useEffect(() => {
56615
+ const clampPositionToViewport = useCallback(
56616
+ (proposed) => {
56617
+ if (typeof window === "undefined") {
56618
+ return proposed;
56619
+ }
56620
+ const { width, height } = measure();
56621
+ const minX = VIEWPORT_MARGIN$1;
56622
+ const minY = VIEWPORT_MARGIN$1;
56623
+ const maxX = Math.max(minX, window.innerWidth - width - VIEWPORT_MARGIN$1);
56624
+ const maxY = Math.max(minY, window.innerHeight - height - VIEWPORT_MARGIN$1);
56625
+ return {
56626
+ x: clamp$1(proposed.x, minX, maxX),
56627
+ y: clamp$1(proposed.y, minY, maxY)
56628
+ };
56629
+ },
56630
+ [measure]
56631
+ );
56632
+ useIsomorphicLayoutEffect(() => {
56203
56633
  if (isOpen && typeof window !== "undefined") {
56204
- const element = containerRef.current;
56205
- const width = (element == null ? void 0 : element.offsetWidth) ?? 500;
56206
- const height = (element == null ? void 0 : element.offsetHeight) ?? 600;
56634
+ setViewport(readViewport());
56635
+ const { width, height } = measure();
56207
56636
  const centerX = (window.innerWidth - width) / 2;
56208
56637
  const centerY = (window.innerHeight - height) / 2;
56209
56638
  setPosition(clampPositionToViewport({ x: centerX, y: centerY }));
56210
56639
  }
56211
- }, [isOpen, clampPositionToViewport]);
56640
+ }, [isOpen, measure, clampPositionToViewport]);
56212
56641
  useEffect(() => {
56213
56642
  if (!isOpen || typeof window === "undefined") {
56214
56643
  return;
56215
56644
  }
56216
56645
  const handleViewportChange = () => {
56646
+ setViewport(readViewport());
56217
56647
  setPosition((prev) => clampPositionToViewport(prev));
56218
56648
  };
56219
56649
  window.addEventListener("resize", handleViewportChange);
@@ -56223,75 +56653,72 @@ function DraggableClientInfoModal({
56223
56653
  window.removeEventListener("orientationchange", handleViewportChange);
56224
56654
  };
56225
56655
  }, [isOpen, clampPositionToViewport]);
56656
+ const keepInsideViewport = useCallback(() => {
56657
+ setPosition((prev) => {
56658
+ const next = clampPositionToViewport(prev);
56659
+ return next.x === prev.x && next.y === prev.y ? prev : next;
56660
+ });
56661
+ }, [clampPositionToViewport]);
56226
56662
  useEffect(() => {
56227
- if (!(campaignContact == null ? void 0 : campaignContact.id)) {
56663
+ if (!isOpen) {
56228
56664
  return;
56229
56665
  }
56230
- if (fetchedContactId === campaignContact.id) {
56666
+ keepInsideViewport();
56667
+ }, [isOpen, scalePercent, keepInsideViewport]);
56668
+ useEffect(() => {
56669
+ const element = containerRef.current;
56670
+ if (!isOpen || !element || typeof ResizeObserver === "undefined") {
56231
56671
  return;
56232
56672
  }
56233
- let cancelled = false;
56234
- const fetchContactData = async () => {
56235
- setIsLoading(true);
56673
+ const observer = new ResizeObserver(() => keepInsideViewport());
56674
+ observer.observe(element);
56675
+ return () => observer.disconnect();
56676
+ }, [isOpen, keepInsideViewport]);
56677
+ useEffect(() => {
56678
+ const current = requestRef.current;
56679
+ if (!current || !requestKey) {
56680
+ setForm(null);
56236
56681
  setError(null);
56237
- try {
56238
- const customer = campaignContact.customer;
56239
- console.log("[DraggableClientInfoModal] Verificando customer em campaignContact:", customer);
56240
- let campaignFields;
56241
- let contactValues;
56242
- if ((customer == null ? void 0 : customer.id) && (customer == null ? void 0 : customer.formId)) {
56243
- console.log("[DraggableClientInfoModal] Modo CUSTOMER: carregando form-rows (formId) e form-data (customerId)");
56244
- [campaignFields, contactValues] = await Promise.all([
56245
- getCustomerFields(customer.formId),
56246
- getCustomerContactValues(customer.id)
56247
- ]);
56248
- } else {
56249
- console.log("[DraggableClientInfoModal] Modo CAMPANHA: buscando config, campos e valores");
56250
- const config = await getCampaignConfig$1(campaignContact.id);
56251
- console.log("[DraggableClientInfoModal] Config recebida:", config);
56252
- if (!config.campaignModelId) {
56253
- throw new Error("campaignModelId não encontrado");
56254
- }
56255
- [campaignFields, contactValues] = await Promise.all([
56256
- getCampaignFields(config.campaignModelId),
56257
- getContactValues$1(campaignContact.id)
56258
- ]);
56259
- }
56260
- console.log("[DraggableClientInfoModal] Campos recebidos:", campaignFields);
56261
- console.log("[DraggableClientInfoModal] Valores recebidos:", contactValues);
56262
- if (!cancelled) {
56263
- console.log("[DraggableClientInfoModal] ✅ Dados carregados com sucesso");
56264
- setContactData(contactValues);
56265
- setFieldRows(campaignFields.rows || []);
56266
- setFetchedContactId(campaignContact.id);
56267
- setIsLoading(false);
56268
- }
56269
- } catch (err) {
56270
- if (!cancelled) {
56271
- console.error("[DraggableClientInfoModal] ❌ Erro ao buscar dados:", err);
56272
- const errorMessage = err instanceof Error ? err.message : "Erro ao buscar dados do contato";
56273
- setError(errorMessage);
56274
- setIsLoading(false);
56682
+ setIsLoading(false);
56683
+ return;
56684
+ }
56685
+ let cancelled = false;
56686
+ let refreshTimer = null;
56687
+ setIsLoading(true);
56688
+ setError(null);
56689
+ fetchCampaignForm(current, { force: retry.key === requestKey && retry.n > 0 }).then((result2) => {
56690
+ if (!cancelled) {
56691
+ console.log("[DraggableClientInfoModal] ✅ Formulário carregado:", result2.valuesSource, result2.layoutSource, result2.fields.length, "campos", result2.partial ? "(parcial)" : "");
56692
+ setForm(result2);
56693
+ setIsLoading(false);
56694
+ if (result2.partial && partialRefreshKeyRef.current !== requestKey) {
56695
+ partialRefreshKeyRef.current = requestKey;
56696
+ refreshTimer = setTimeout(() => setRetry((prev) => ({ key: requestKey, n: prev.n + 1 })), 3e3);
56275
56697
  }
56276
56698
  }
56277
- };
56278
- void fetchContactData();
56699
+ }).catch((err) => {
56700
+ if (!cancelled) {
56701
+ console.error("[DraggableClientInfoModal] ❌ Erro ao buscar o formulário:", err);
56702
+ setForm(null);
56703
+ setError(err instanceof Error ? err.message : "Erro ao buscar dados do contato");
56704
+ setIsLoading(false);
56705
+ }
56706
+ });
56279
56707
  return () => {
56280
56708
  cancelled = true;
56709
+ if (refreshTimer) clearTimeout(refreshTimer);
56281
56710
  };
56282
- }, [campaignContact, campaignContact == null ? void 0 : campaignContact.id, fetchedContactId]);
56711
+ }, [requestKey, retry]);
56283
56712
  useEffect(() => {
56284
56713
  if (!isDragging) {
56285
56714
  return;
56286
56715
  }
56287
56716
  const handlePointerMove = (event) => {
56288
- var _a2, _b;
56289
56717
  if (pointerRef.current.pointerId !== event.pointerId) {
56290
56718
  return;
56291
56719
  }
56292
56720
  dragMovedRef.current = true;
56293
- const modalWidth = ((_a2 = containerRef.current) == null ? void 0 : _a2.offsetWidth) ?? 0;
56294
- const modalHeight = ((_b = containerRef.current) == null ? void 0 : _b.offsetHeight) ?? 0;
56721
+ const { width: modalWidth, height: modalHeight } = measure();
56295
56722
  const nextX = event.clientX - pointerRef.current.offsetX;
56296
56723
  const nextY = event.clientY - pointerRef.current.offsetY;
56297
56724
  const viewportWidth = typeof window !== "undefined" ? window.innerWidth : 0;
@@ -56314,19 +56741,32 @@ function DraggableClientInfoModal({
56314
56741
  };
56315
56742
  window.addEventListener("pointermove", handlePointerMove);
56316
56743
  window.addEventListener("pointerup", handlePointerUp);
56744
+ window.addEventListener("pointercancel", handlePointerUp);
56317
56745
  return () => {
56318
56746
  window.removeEventListener("pointermove", handlePointerMove);
56319
56747
  window.removeEventListener("pointerup", handlePointerUp);
56748
+ window.removeEventListener("pointercancel", handlePointerUp);
56320
56749
  };
56321
- }, [isDragging]);
56750
+ }, [isDragging, measure]);
56322
56751
  const beginDrag = useCallback((event) => {
56323
56752
  if (event.button !== 0 && event.pointerType !== "touch") {
56324
56753
  return;
56325
56754
  }
56755
+ if (event.target instanceof Element && event.target.closest("button")) {
56756
+ return;
56757
+ }
56326
56758
  const element = containerRef.current;
56327
56759
  if (!element) {
56328
56760
  return;
56329
56761
  }
56762
+ event.preventDefault();
56763
+ const handle = event.currentTarget;
56764
+ if (typeof handle.setPointerCapture === "function") {
56765
+ try {
56766
+ handle.setPointerCapture(event.pointerId);
56767
+ } catch {
56768
+ }
56769
+ }
56330
56770
  const rect = element.getBoundingClientRect();
56331
56771
  pointerRef.current = {
56332
56772
  pointerId: event.pointerId,
@@ -56341,119 +56781,243 @@ function DraggableClientInfoModal({
56341
56781
  onClose();
56342
56782
  }
56343
56783
  }, [isCallActive, onClose]);
56784
+ const handleRetry = useCallback(() => {
56785
+ setRetry((prev) => ({ key: requestKey, n: prev.n + 1 }));
56786
+ }, [requestKey]);
56787
+ const applyScale = useCallback((next) => {
56788
+ const value = clampFormScale(next);
56789
+ setScalePercent(value);
56790
+ storeFormScale(value);
56791
+ return value;
56792
+ }, []);
56793
+ const canDecrease = scalePercent > FORM_SCALE_MIN;
56794
+ const canIncrease = scalePercent < FORM_SCALE_MAX;
56795
+ const handleDecrease = useCallback(() => {
56796
+ if (canDecrease) setAnnouncement(`Tamanho do formulário: ${applyScale(scalePercent - FORM_SCALE_STEP)}%`);
56797
+ }, [applyScale, canDecrease, scalePercent]);
56798
+ const handleIncrease = useCallback(() => {
56799
+ if (canIncrease) setAnnouncement(`Tamanho do formulário: ${applyScale(scalePercent + FORM_SCALE_STEP)}%`);
56800
+ }, [applyScale, canIncrease, scalePercent]);
56801
+ const handleResetScale = useCallback(() => {
56802
+ applyScale(FORM_SCALE_DEFAULT);
56803
+ setAnnouncement("");
56804
+ }, [applyScale]);
56805
+ useEffect(() => {
56806
+ if (!isOpen || typeof document === "undefined") {
56807
+ return;
56808
+ }
56809
+ const container = containerRef.current;
56810
+ const active = document.activeElement;
56811
+ previouslyFocusedRef.current = active && active !== document.body ? active : null;
56812
+ if (container && (!active || active === document.body)) {
56813
+ container.focus({ preventScroll: true });
56814
+ }
56815
+ return () => {
56816
+ const previous = previouslyFocusedRef.current;
56817
+ if (focusWithinRef.current && previous && previous.isConnected) {
56818
+ previous.focus({ preventScroll: true });
56819
+ }
56820
+ focusWithinRef.current = false;
56821
+ };
56822
+ }, [isOpen]);
56823
+ const handleFocusWithin = useCallback(() => {
56824
+ focusWithinRef.current = true;
56825
+ }, []);
56826
+ const handleBlurWithin = useCallback((event) => {
56827
+ if (!event.currentTarget.contains(event.relatedTarget)) {
56828
+ focusWithinRef.current = false;
56829
+ }
56830
+ }, []);
56831
+ const handleKeyDown = useCallback(
56832
+ (event) => {
56833
+ if (event.key === "Escape" && !isCallActive) {
56834
+ event.stopPropagation();
56835
+ handleCloseClick();
56836
+ }
56837
+ },
56838
+ [handleCloseClick, isCallActive]
56839
+ );
56344
56840
  if (!isOpen) {
56345
56841
  return null;
56346
56842
  }
56347
- const hasData = Object.keys(contactData).length > 0;
56348
- const hasFieldStructure = fieldRows && fieldRows.length > 0;
56349
- const getFieldValue = (fieldCode) => {
56350
- return contactData[fieldCode] || "-";
56843
+ const rows = (form == null ? void 0 : form.rows) && form.rows.length > 0 ? form.rows : [];
56844
+ const hasRows = rows.length > 0;
56845
+ const hasFields = Boolean(form && form.fields.length > 0);
56846
+ const hasData = hasRows || hasFields;
56847
+ const scaledStyle = {
56848
+ zoom: scale,
56849
+ width: `${BASE_WIDTH}px`,
56850
+ maxWidth: `${Math.max(160, Math.floor((viewport.width - 2 * VIEWPORT_MARGIN$1) / scale))}px`,
56851
+ maxHeight: `${Math.max(120, Math.floor((viewport.height - 2 * VIEWPORT_MARGIN$1) / scale))}px`
56351
56852
  };
56352
- const modalContent = /* @__PURE__ */ jsxRuntimeExports.jsxs(
56853
+ const controlPx = scale < 1 ? Math.ceil(CONTROL_MIN_PX / scale) : CONTROL_MIN_PX;
56854
+ const controlStyle = { width: `${controlPx}px`, height: `${controlPx}px` };
56855
+ const percentStyle = { height: `${controlPx}px`, minWidth: `${Math.ceil(48 / Math.min(scale, 1))}px` };
56856
+ const modalContent = /* @__PURE__ */ jsxRuntimeExports.jsx(
56353
56857
  "div",
56354
56858
  {
56355
56859
  ref: containerRef,
56356
- className: tw`fixed z-[9999] flex flex-col overflow-hidden rounded-xl border border-slate-300 bg-white shadow-2xl`,
56860
+ role: "dialog",
56861
+ "aria-labelledby": titleId,
56862
+ tabIndex: -1,
56863
+ onKeyDown: handleKeyDown,
56864
+ onFocus: handleFocusWithin,
56865
+ onBlur: handleBlurWithin,
56866
+ className: tw`fixed z-[9999] overflow-hidden rounded-xl border border-gray-300 bg-white text-gray-900 shadow-2xl focus:outline-none`,
56357
56867
  style: {
56358
56868
  left: `${position2.x}px`,
56359
56869
  top: `${position2.y}px`,
56360
- width: "520px",
56361
- maxWidth: "calc(100vw - 32px)",
56362
- maxHeight: "calc(100vh - 32px)"
56870
+ maxWidth: `calc(100vw - ${2 * VIEWPORT_MARGIN$1}px)`,
56871
+ maxHeight: `calc(100vh - ${2 * VIEWPORT_MARGIN$1}px)`
56363
56872
  },
56364
- children: [
56365
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
56366
- "div",
56367
- {
56368
- onPointerDown: beginDrag,
56369
- className: tw`flex cursor-move items-center justify-between border-b border-slate-200 bg-gradient-to-r from-blue-500 to-blue-600 px-4 py-3 text-white`,
56370
- children: [
56371
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex items-center gap-2`, children: [
56372
- /* @__PURE__ */ jsxRuntimeExports.jsx(FiUser, { className: tw`h-5 w-5` }),
56373
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: tw`text-sm font-bold`, children: "Informações da Campanha" })
56374
- ] }),
56375
- /* @__PURE__ */ jsxRuntimeExports.jsx(
56376
- "button",
56377
- {
56378
- type: "button",
56379
- onClick: handleCloseClick,
56380
- disabled: isCallActive,
56381
- className: tw`rounded-full p-1 transition hover:bg-white/20 disabled:cursor-not-allowed disabled:opacity-40`,
56382
- title: isCallActive ? "Aguarde o fim da chamada para fechar" : "Fechar",
56383
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(FiX, { className: tw`h-5 w-5` })
56384
- }
56385
- )
56386
- ]
56387
- }
56388
- ),
56389
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex-1 space-y-3 overflow-y-auto p-4`, children: [
56390
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`grid grid-cols-2 gap-3`, children: [
56391
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2`, children: [
56392
- /* @__PURE__ */ jsxRuntimeExports.jsx(FiClock, { className: tw`h-4 w-4 text-slate-500` }),
56393
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
56394
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-[10px] text-slate-500 uppercase tracking-wide font-medium`, children: "Duração" }),
56395
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-sm font-bold text-slate-800`, children: formatDuration(callDuration) })
56396
- ] })
56397
- ] }),
56398
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2`, children: [
56399
- /* @__PURE__ */ jsxRuntimeExports.jsx(FiUser, { className: tw`h-4 w-4 text-slate-500` }),
56400
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`min-w-0 flex-1`, children: [
56401
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-[10px] text-slate-500 uppercase tracking-wide font-medium`, children: "Cliente" }),
56402
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`truncate text-sm font-bold text-slate-800`, children: (campaignContact == null ? void 0 : campaignContact.label) || "N/A" })
56403
- ] })
56404
- ] })
56405
- ] }),
56406
- isLoading && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`rounded-lg border border-blue-200 bg-blue-50 p-6 text-center`, children: [
56407
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: tw`mx-auto mb-2 inline-block h-6 w-6 animate-spin rounded-full border-2 border-blue-500 border-t-transparent` }),
56408
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-xs font-medium text-blue-700`, children: "Carregando dados do contato..." })
56409
- ] }),
56410
- error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: tw`rounded-lg border border-red-200 bg-red-50 p-3`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex items-center gap-2`, children: [
56411
- /* @__PURE__ */ jsxRuntimeExports.jsx(FiAlertCircle, { className: tw`h-4 w-4 flex-shrink-0 text-red-600` }),
56412
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
56413
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-xs font-semibold text-red-700`, children: "Erro ao carregar dados" }),
56414
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-[10px] text-red-600`, children: error2 })
56415
- ] })
56416
- ] }) }),
56417
- !isLoading && !error2 && hasData && hasFieldStructure && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: tw`space-y-2`, children: fieldRows.map((row, rowIndex) => {
56418
- const numFields = row.length;
56419
- let gridCols = "grid-cols-1";
56420
- if (numFields === 2) {
56421
- gridCols = "grid-cols-2";
56422
- } else if (numFields === 3) {
56423
- gridCols = "grid-cols-3";
56424
- } else if (numFields === 4) {
56425
- gridCols = "grid-cols-4";
56426
- } else if (numFields === 5) {
56427
- gridCols = "grid-cols-5";
56428
- }
56429
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
56873
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
56874
+ "div",
56875
+ {
56876
+ "data-testid": "campaign-form-scaled",
56877
+ "data-scale": zoomSupported ? scalePercent : FORM_SCALE_DEFAULT,
56878
+ className: tw`flex flex-col`,
56879
+ style: scaledStyle,
56880
+ children: [
56881
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
56430
56882
  "div",
56431
56883
  {
56432
- className: tw`grid gap-2 ${gridCols}`,
56433
- children: row.map((field, fieldIndex) => /* @__PURE__ */ jsxRuntimeExports.jsx(
56434
- Field,
56435
- {
56436
- label: field.name,
56437
- value: getFieldValue(field.code),
56438
- fullWidth: field.width === "100%"
56439
- },
56440
- `${rowIndex}-${fieldIndex}-${field.code}`
56441
- ))
56442
- },
56443
- rowIndex
56444
- );
56445
- }) }),
56446
- !isLoading && !error2 && !hasData && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex flex-col items-center justify-center rounded-lg border border-slate-200 bg-slate-50 px-4 py-8 text-center`, children: [
56447
- /* @__PURE__ */ jsxRuntimeExports.jsx(FiUser, { className: tw`mb-2 h-8 w-8 text-slate-400` }),
56448
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-sm text-slate-500`, children: "Nenhum dado disponível" })
56449
- ] })
56450
- ] }),
56451
- isCallActive && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: tw`border-t border-slate-200 bg-slate-50 px-4 py-2`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-xs text-slate-500`, children: "Aguarde o fim da chamada para fechar" }) })
56452
- ]
56884
+ onPointerDown: beginDrag,
56885
+ className: tw`flex flex-wrap cursor-move select-none items-center gap-2 border-b border-gray-200 bg-gradient-to-r from-blue-600 to-blue-700 px-3 py-2.5 text-white`,
56886
+ style: { touchAction: "none" },
56887
+ children: [
56888
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex min-w-[7rem] flex-1 items-center gap-2`, children: [
56889
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FiUser, { "aria-hidden": true, className: tw`h-5 w-5 flex-shrink-0` }),
56890
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { id: titleId, className: tw`truncate text-sm font-bold`, children: "Informações da Campanha" })
56891
+ ] }),
56892
+ zoomSupported && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { role: "group", "aria-label": "Tamanho do formulário", className: tw`ml-auto flex flex-shrink-0 items-center gap-0.5`, children: [
56893
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
56894
+ "button",
56895
+ {
56896
+ type: "button",
56897
+ onClick: handleDecrease,
56898
+ "aria-disabled": !canDecrease,
56899
+ "aria-label": "Diminuir o tamanho do formulário",
56900
+ title: "Diminuir o formulário e o texto",
56901
+ className: tw`${CONTROL_BUTTON_CLASS} ${canDecrease ? "" : "cursor-not-allowed"}`,
56902
+ style: controlStyle,
56903
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(FiMinus, { "aria-hidden": true, className: tw`h-4 w-4 ${canDecrease ? "" : "opacity-40"}` })
56904
+ }
56905
+ ),
56906
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
56907
+ "button",
56908
+ {
56909
+ type: "button",
56910
+ onClick: handleResetScale,
56911
+ "aria-label": `Tamanho atual ${scalePercent}%. Restaurar o tamanho padrão`,
56912
+ title: "Restaurar o tamanho padrão (100%)",
56913
+ className: tw`flex-shrink-0 rounded-md px-1 text-center text-xs font-semibold tabular-nums transition hover:bg-black hover:bg-opacity-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white`,
56914
+ style: percentStyle,
56915
+ children: [
56916
+ scalePercent,
56917
+ "%"
56918
+ ]
56919
+ }
56920
+ ),
56921
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
56922
+ "button",
56923
+ {
56924
+ type: "button",
56925
+ onClick: handleIncrease,
56926
+ "aria-disabled": !canIncrease,
56927
+ "aria-label": "Aumentar o tamanho do formulário",
56928
+ title: "Aumentar o formulário e o texto",
56929
+ className: tw`${CONTROL_BUTTON_CLASS} ${canIncrease ? "" : "cursor-not-allowed"}`,
56930
+ style: controlStyle,
56931
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(FiPlus, { "aria-hidden": true, className: tw`h-4 w-4 ${canIncrease ? "" : "opacity-40"}` })
56932
+ }
56933
+ ),
56934
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { role: "status", className: tw`sr-only`, children: announcement })
56935
+ ] }),
56936
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
56937
+ "button",
56938
+ {
56939
+ type: "button",
56940
+ onClick: handleCloseClick,
56941
+ disabled: isCallActive,
56942
+ "aria-label": isCallActive ? "Aguarde o fim da chamada para fechar" : "Fechar",
56943
+ className: tw`${CONTROL_BUTTON_CLASS} rounded-full disabled:cursor-not-allowed disabled:opacity-40`,
56944
+ style: controlStyle,
56945
+ title: isCallActive ? "Aguarde o fim da chamada para fechar" : "Fechar",
56946
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(FiX, { "aria-hidden": true, className: tw`h-5 w-5` })
56947
+ }
56948
+ )
56949
+ ]
56950
+ }
56951
+ ),
56952
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
56953
+ "div",
56954
+ {
56955
+ tabIndex: 0,
56956
+ role: "region",
56957
+ "aria-label": "Dados do contato",
56958
+ className: tw`min-h-0 flex-1 space-y-3 overflow-y-auto p-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500`,
56959
+ children: [
56960
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`grid grid-cols-2 gap-3`, children: [
56961
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-2`, children: [
56962
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FiClock, { "aria-hidden": true, className: tw`h-4 w-4 text-gray-500` }),
56963
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
56964
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-[10px] text-gray-500 uppercase tracking-wide font-medium`, children: "Duração" }),
56965
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-sm font-bold text-gray-800`, children: formatDuration(callDuration) })
56966
+ ] })
56967
+ ] }),
56968
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-2`, children: [
56969
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FiUser, { "aria-hidden": true, className: tw`h-4 w-4 text-gray-500` }),
56970
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`min-w-0 flex-1`, children: [
56971
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-[10px] text-gray-500 uppercase tracking-wide font-medium`, children: "Cliente" }),
56972
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`truncate text-sm font-bold text-gray-800`, children: (campaignContact == null ? void 0 : campaignContact.label) || "N/A" })
56973
+ ] })
56974
+ ] })
56975
+ ] }),
56976
+ isLoading && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`rounded-lg border border-blue-200 bg-blue-50 p-6 text-center`, role: "status", children: [
56977
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: tw`mx-auto mb-2 inline-block h-6 w-6 animate-spin rounded-full border-2 border-blue-500 border-t-transparent` }),
56978
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-xs font-medium text-blue-700`, children: "Carregando dados do contato..." })
56979
+ ] }),
56980
+ error2 && !isLoading && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: tw`rounded-lg border border-red-200 bg-red-50 p-3`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex items-center gap-2`, children: [
56981
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FiAlertCircle, { "aria-hidden": true, className: tw`h-4 w-4 flex-shrink-0 text-red-600` }),
56982
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`min-w-0 flex-1`, children: [
56983
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-xs font-semibold text-red-700`, children: "Erro ao carregar dados" }),
56984
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-[10px] text-red-600`, children: error2 })
56985
+ ] }),
56986
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
56987
+ "button",
56988
+ {
56989
+ type: "button",
56990
+ onClick: handleRetry,
56991
+ className: tw`rounded-md border border-red-300 bg-white px-2 py-1 text-[11px] font-semibold text-red-700 transition hover:bg-red-100`,
56992
+ children: "Tentar novamente"
56993
+ }
56994
+ )
56995
+ ] }) }),
56996
+ !isLoading && !error2 && hasRows && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: tw`space-y-2`, children: rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: tw`grid gap-2 ${gridColsFor(row.length)}`, children: row.map((field, fieldIndex) => /* @__PURE__ */ jsxRuntimeExports.jsx(
56997
+ Field,
56998
+ {
56999
+ label: field.label,
57000
+ value: field.value || "-",
57001
+ fullWidth: field.width === "100%"
57002
+ },
57003
+ `${rowIndex}-${fieldIndex}-${field.key}`
57004
+ )) }, rowIndex)) }),
57005
+ !isLoading && !error2 && !hasRows && hasFields && form && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: tw`grid grid-cols-2 gap-2`, children: form.fields.map((field) => /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: field.label, value: field.value || "-" }, field.key)) }),
57006
+ !isLoading && !error2 && !hasData && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`flex flex-col items-center justify-center rounded-lg border border-gray-200 bg-gray-50 px-4 py-8 text-center`, children: [
57007
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FiUser, { "aria-hidden": true, className: tw`mb-2 h-8 w-8 text-gray-400` }),
57008
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-sm text-gray-500`, children: "Nenhum dado disponível" })
57009
+ ] })
57010
+ ]
57011
+ }
57012
+ ),
57013
+ isCallActive && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: tw`border-t border-gray-200 bg-gray-50 px-4 py-2`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-xs text-gray-500`, children: "Aguarde o fim da chamada para fechar" }) })
57014
+ ]
57015
+ }
57016
+ )
56453
57017
  }
56454
57018
  );
56455
57019
  if (typeof document !== "undefined") {
56456
- return createPortal(modalContent, document.body);
57020
+ return createPortal(modalContent, portalTarget ?? document.body);
56457
57021
  }
56458
57022
  return null;
56459
57023
  }
@@ -56463,9 +57027,16 @@ function Field({
56463
57027
  fullWidth
56464
57028
  }) {
56465
57029
  const widthClass = fullWidth ? "col-span-full" : "";
56466
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`rounded-lg border border-slate-200 bg-white p-2 ${widthClass}`, children: [
56467
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`mb-0.5 truncate text-[9px] font-semibold uppercase tracking-wide text-slate-500`, children: label }),
56468
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`break-words text-xs font-semibold text-slate-800 line-clamp-2`, children: value })
57030
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`rounded-lg border border-gray-200 bg-white p-2 ${widthClass}`, children: [
57031
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`mb-0.5 truncate text-[9px] font-semibold uppercase tracking-wide text-gray-500`, children: label }),
57032
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
57033
+ "p",
57034
+ {
57035
+ className: tw`overflow-hidden break-words text-xs font-semibold text-gray-800`,
57036
+ style: { display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical" },
57037
+ children: value
57038
+ }
57039
+ )
56469
57040
  ] });
56470
57041
  }
56471
57042
  if (typeof window !== "undefined" && !window.__fetchIntercepted__) {
@@ -56563,104 +57134,8 @@ if (typeof window !== "undefined" && !window.__fetchIntercepted__) {
56563
57134
  };
56564
57135
  console.log("%c[FETCH INTERCEPTOR] ✅ Ativo - todas as requisições serão logadas", "color: #2196F3; font-weight: bold; font-size: 12px");
56565
57136
  }
56566
- async function getCampaignConfig(contactId) {
56567
- const url = `${API_BASE_URL}/api/campaign-config/${contactId}`;
56568
- const headers = getAuthHeaders();
56569
- console.log("[getCampaignConfig] URL:", url);
56570
- console.log("[getCampaignConfig] Headers:", JSON.stringify(headers));
56571
- const response = await fetch(url, { method: "GET", headers, credentials: "omit" });
56572
- if (!response.ok) {
56573
- const errorBody = await response.json().catch(() => ({ error: "Erro desconhecido" }));
56574
- console.error("[getCampaignConfig] Erro:", response.status, errorBody);
56575
- if (response.status === 401) {
56576
- const token = getSessionToken();
56577
- console.error("[getCampaignConfig] 401 - Token atual:", token ? `${token.substring(0, 20)}...` : "NULL");
56578
- console.error("[getCampaignConfig] 401 - Resposta completa:", JSON.stringify(errorBody));
56579
- }
56580
- throw new Error(errorBody.error || `Falha ao buscar config: ${response.status}`);
56581
- }
56582
- return response.json();
56583
- }
56584
- function processRowsToMapping(apiResponse) {
56585
- const mapping = {};
56586
- console.log("[processRowsToMapping] Processando estrutura:", typeof apiResponse);
56587
- if (!apiResponse || typeof apiResponse !== "object") {
56588
- console.warn("[processRowsToMapping] Resposta inválida:", apiResponse);
56589
- return mapping;
56590
- }
56591
- const data = apiResponse;
56592
- if (!data.rows || !Array.isArray(data.rows)) {
56593
- console.warn("[processRowsToMapping] rows não encontrado ou não é array:", data);
56594
- return mapping;
56595
- }
56596
- console.log("[processRowsToMapping] Total de linhas (rows):", data.rows.length);
56597
- for (let rowIndex = 0; rowIndex < data.rows.length; rowIndex++) {
56598
- const row = data.rows[rowIndex];
56599
- if (!Array.isArray(row)) {
56600
- console.warn(`[processRowsToMapping] Row ${rowIndex} não é array:`, row);
56601
- continue;
56602
- }
56603
- for (const field of row) {
56604
- if (field && typeof field === "object" && "code" in field && "name" in field) {
56605
- const fieldObj = field;
56606
- if (fieldObj.code && fieldObj.name) {
56607
- console.log(`[processRowsToMapping] ✓ Mapeando: "${fieldObj.code}" -> "${fieldObj.name}"`);
56608
- mapping[fieldObj.code] = fieldObj.name;
56609
- }
56610
- }
56611
- }
56612
- }
56613
- console.log("[processRowsToMapping] Mapeamento final:", mapping);
56614
- console.log("[processRowsToMapping] Total de campos mapeados:", Object.keys(mapping).length);
56615
- return mapping;
56616
- }
56617
- function applyMappingToValues(mapping, values) {
56618
- const finalData = {};
56619
- console.log("[applyMappingToValues] Aplicando mapeamento...");
56620
- console.log("[applyMappingToValues] Mapping keys:", Object.keys(mapping));
56621
- console.log("[applyMappingToValues] Values keys:", Object.keys(values));
56622
- for (const [code, value] of Object.entries(values)) {
56623
- const name = mapping[code];
56624
- if (name) {
56625
- console.log(`[applyMappingToValues] ✓ "${code}" -> "${name}" = "${value}"`);
56626
- finalData[name] = value;
56627
- } else {
56628
- console.log(`[applyMappingToValues] ✗ "${code}" não tem mapeamento (valor: "${value}")`);
56629
- }
56630
- }
56631
- console.log("[applyMappingToValues] Final data:", finalData);
56632
- return finalData;
56633
- }
56634
- async function fetchCampaignFieldsRaw(campaignModelId) {
56635
- const url = `${API_BASE_URL}/api/campaign-fields/${campaignModelId}`;
56636
- console.log("[fetchCampaignFieldsRaw] URL:", url);
56637
- const response = await fetch(url, { method: "GET", headers: getAuthHeaders(), credentials: "omit" });
56638
- if (!response.ok) {
56639
- const error2 = await response.json().catch(() => ({ error: "Erro desconhecido" }));
56640
- throw new Error(error2.error || `Falha ao buscar campos: ${response.status}`);
56641
- }
56642
- const json = await response.json();
56643
- console.log("[fetchCampaignFieldsRaw] Resposta recebida:", JSON.stringify(json, null, 2).substring(0, 500));
56644
- return json;
56645
- }
56646
- async function getContactValues(contactId) {
56647
- const url = `${API_BASE_URL}/api/campaign-contact/${contactId}`;
56648
- const response = await fetch(url, { method: "GET", headers: getAuthHeaders(), credentials: "omit" });
56649
- if (!response.ok) {
56650
- const error2 = await response.json().catch(() => ({ error: "Erro desconhecido" }));
56651
- throw new Error(error2.error || `Falha ao buscar valores: ${response.status}`);
56652
- }
56653
- return response.json();
56654
- }
56655
57137
  function findValue(finalData, candidates) {
56656
- const entries = Object.entries(finalData);
56657
- const lowerCandidates = candidates.map((item) => item.toLowerCase());
56658
- for (const [key, value] of entries) {
56659
- if (lowerCandidates.includes(key.toLowerCase())) {
56660
- return value;
56661
- }
56662
- }
56663
- return void 0;
57138
+ return findFormValue({ values: finalData }, candidates);
56664
57139
  }
56665
57140
  function MobileDialer(props) {
56666
57141
  useEffect(() => {
@@ -56673,10 +57148,12 @@ function MobileDialer(props) {
56673
57148
  document.head.appendChild(link);
56674
57149
  }
56675
57150
  }, []);
57151
+ const inputProfile = useInputProfile();
57152
+ const density = resolveDensity(props.density, inputProfile);
56676
57153
  if (!props.isAuthenticated) {
56677
- return /* @__PURE__ */ jsxRuntimeExports.jsx(MobileDialerLogin, { ...props });
57154
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(MobileDialerLogin, { ...props, density });
56678
57155
  }
56679
- return /* @__PURE__ */ jsxRuntimeExports.jsx(AuthenticatedMobileDialer, { ...props });
57156
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(AuthenticatedMobileDialer, { ...props, density });
56680
57157
  }
56681
57158
  function MobileDialerLogin({
56682
57159
  username,
@@ -56685,7 +57162,8 @@ function MobileDialerLogin({
56685
57162
  sessionError,
56686
57163
  sessionStatus,
56687
57164
  loginConfirmation,
56688
- onCancelLoginConfirmation
57165
+ onCancelLoginConfirmation,
57166
+ density
56689
57167
  }) {
56690
57168
  const [loginInput, setLoginInput] = useState(username ?? "");
56691
57169
  const [loginError, setLoginError] = useState(null);
@@ -56712,7 +57190,7 @@ function MobileDialerLogin({
56712
57190
  const handleLogin = useCallback(() => submitLogin(loginInput), [submitLogin, loginInput]);
56713
57191
  const showConfirmation = Boolean(loginConfirmation) && !isLoadingSession;
56714
57192
  const loadingUser = ((loginConfirmation == null ? void 0 : loginConfirmation.username) || loginInput || username || "").trim();
56715
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "phone-container", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "screen dialer-screen login-screen active", id: "dialerScreen", children: [
57193
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "phone-container", "data-density": density === "compact" ? "compact" : "comfortable", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "screen dialer-screen login-screen active", id: "dialerScreen", children: [
56716
57194
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "dialer-header", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "status-container", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: `status-badge ${isLoadingSession ? "connecting" : "available"}`, children: [
56717
57195
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "status-dot" }),
56718
57196
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "status-text", children: isLoadingSession ? "Conectando" : "Login" })
@@ -56826,9 +57304,10 @@ Gostaríamos de fazer um *diagnóstico gratuito* para te apresentar os valores r
56826
57304
  }
56827
57305
  function AuthenticatedMobileDialer({
56828
57306
  username,
56829
- showClientInfoModal = false
57307
+ showClientInfoModal = false,
57308
+ density
56830
57309
  }) {
56831
- var _a2, _b, _c;
57310
+ var _a2, _b, _c, _d;
56832
57311
  const { skipManualQualification } = useDialerConfig();
56833
57312
  const [dialValue, setDialValue] = useState("");
56834
57313
  const [activeScreen, setActiveScreen] = useState("dialer");
@@ -56850,10 +57329,14 @@ function AuthenticatedMobileDialer({
56850
57329
  isLoading: false,
56851
57330
  error: null
56852
57331
  });
56853
- const [fetchedContactId, setFetchedContactId] = useState(null);
56854
57332
  const [callHistory, setCallHistory] = useState([]);
56855
57333
  const [isLoadingHistory, setIsLoadingHistory] = useState(false);
56856
57334
  const [historyError, setHistoryError] = useState(null);
57335
+ const [historyQuery, setHistoryQuery] = useState("");
57336
+ const [historySearchResults, setHistorySearchResults] = useState(null);
57337
+ const [isSearchingHistory, setIsSearchingHistory] = useState(false);
57338
+ const historyTextPoolRef = useRef(null);
57339
+ const historySearchSeqRef = useRef(0);
56857
57340
  const [isSpeakerOn, setIsSpeakerOn] = useState(false);
56858
57341
  const [audioOutputDevices, setAudioOutputDevices] = useState([]);
56859
57342
  const [speakerDeviceId, setSpeakerDeviceId] = useState(null);
@@ -57030,90 +57513,54 @@ function AuthenticatedMobileDialer({
57030
57513
  });
57031
57514
  }
57032
57515
  }, [callInfo]);
57033
- const campaignContact = (callInfo == null ? void 0 : callInfo.type) === "campaign" ? callInfo.info.campaignContact : null;
57034
- const campaignContactId = campaignContact == null ? void 0 : campaignContact.id;
57516
+ const campaignFormRequest = useMemo(
57517
+ () => {
57518
+ var _a3;
57519
+ return (callInfo == null ? void 0 : callInfo.type) === "campaign" ? campaignFormRequestFromCallInfo(callInfo.info, ((_a3 = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _a3.campaignContactPhoneId) ?? null) : null;
57520
+ },
57521
+ [callInfo, currentCall]
57522
+ );
57523
+ const campaignFormKey = campaignFormRequest ? campaignFormCacheKey(campaignFormRequest) : null;
57524
+ const campaignFormRequestRef = useRef(campaignFormRequest);
57525
+ campaignFormRequestRef.current = campaignFormRequest;
57035
57526
  useEffect(() => {
57036
- if (!campaignContactId) {
57527
+ const request = campaignFormRequestRef.current;
57528
+ if (!request || !campaignFormKey) {
57037
57529
  setContactData({ finalData: {}, isLoading: false, error: null });
57038
- setFetchedContactId(null);
57039
- return;
57040
- }
57041
- if (fetchedContactId === campaignContactId) {
57042
57530
  return;
57043
57531
  }
57044
57532
  let cancelled = false;
57045
- const fetchContactData = async () => {
57046
- console.log("[MobileDialer] ========================================");
57047
- console.log("[MobileDialer] Buscando dados do contato:", campaignContactId);
57048
- const tokenAtFetch = getSessionToken();
57049
- console.log("[MobileDialer] Token no momento da busca:", tokenAtFetch ? `${tokenAtFetch.substring(0, 20)}...` : "NULL");
57050
- console.log("[MobileDialer] localStorage length:", typeof window !== "undefined" ? localStorage.length : "N/A");
57051
- addLog("debug", "FetchContact", `Token: ${tokenAtFetch ? "presente" : "AUSENTE"}`, {
57052
- contactId: campaignContactId,
57053
- hasToken: !!tokenAtFetch,
57054
- localStorageLength: typeof window !== "undefined" ? localStorage.length : 0
57533
+ let refreshTimer = null;
57534
+ setContactData({ finalData: {}, isLoading: true, error: null });
57535
+ addLog("debug", "FetchContact", "Buscando formulário do contato", { contactId: request.id, source: request.source });
57536
+ const apply2 = (form) => {
57537
+ setContactData({ finalData: form.values, isLoading: false, error: null });
57538
+ addLog("info", "FetchContact", `✅ Formulário: ${form.fields.length} campos (${form.valuesSource}, layout ${form.layoutSource})`, {
57539
+ contactId: request.id,
57540
+ partial: form.partial === true
57055
57541
  });
57056
- console.log("[MobileDialer] ========================================");
57057
- setContactData({ finalData: {}, isLoading: true, error: null });
57058
- try {
57059
- const customer = campaignContact == null ? void 0 : campaignContact.customer;
57060
- let fieldsRawData;
57061
- let contactValues = {};
57062
- if ((customer == null ? void 0 : customer.id) && (customer == null ? void 0 : customer.formId)) {
57063
- console.log("[MobileDialer] 📋 Modo CLIENTE detectado:", customer);
57064
- const customerFieldsUrl = `${API_BASE_URL}/api/campaign-contact/0?source=customer&formId=${encodeURIComponent(String(customer.formId))}`;
57065
- const customerValuesUrl = `${API_BASE_URL}/api/campaign-fields/0?source=customer&customerId=${encodeURIComponent(String(customer.id))}`;
57066
- const [fieldsResponse, valuesResponse] = await Promise.all([
57067
- fetch(customerFieldsUrl, { method: "GET", headers: getAuthHeaders(), credentials: "omit" }),
57068
- fetch(customerValuesUrl, { method: "GET", headers: getAuthHeaders(), credentials: "omit" })
57069
- ]);
57070
- if (!fieldsResponse.ok) throw new Error(`Erro ao buscar campos cliente: ${fieldsResponse.status}`);
57071
- if (!valuesResponse.ok) throw new Error(`Erro ao buscar valores cliente: ${valuesResponse.status}`);
57072
- fieldsRawData = await fieldsResponse.json();
57073
- contactValues = await valuesResponse.json();
57074
- } else {
57075
- console.log("[MobileDialer] 📋 Modo CAMPANHA detectado");
57076
- const config = await getCampaignConfig(campaignContactId);
57077
- console.log("[MobileDialer] Config recebida:", config);
57078
- if (!config.campaignModelId) {
57079
- throw new Error("campaignModelId não encontrado");
57080
- }
57081
- const [rawFields, rawValues] = await Promise.all([
57082
- fetchCampaignFieldsRaw(config.campaignModelId),
57083
- getContactValues(campaignContactId)
57084
- ]);
57085
- fieldsRawData = rawFields;
57086
- contactValues = rawValues;
57087
- }
57088
- console.log("[MobileDialer] ======= DADOS BRUTOS =======");
57089
- console.log("[MobileDialer] fieldsRawData:", JSON.stringify(fieldsRawData, null, 2).substring(0, 1e3));
57090
- console.log("[MobileDialer] contactValues:", JSON.stringify(contactValues, null, 2));
57091
- const codeToNameMap = processRowsToMapping(fieldsRawData);
57092
- console.log("[MobileDialer] ======= MAPEAMENTO PROCESSADO =======");
57093
- console.log("[MobileDialer] codeToNameMap:", JSON.stringify(codeToNameMap, null, 2));
57094
- const finalData = applyMappingToValues(codeToNameMap, contactValues);
57095
- console.log("[MobileDialer] ======= RESULTADO FINAL =======");
57096
- console.log("[MobileDialer] finalData:", JSON.stringify(finalData, null, 2));
57097
- console.log("[MobileDialer] finalData keys:", Object.keys(finalData));
57098
- console.log("[MobileDialer] ================================");
57099
- if (!cancelled) {
57100
- setContactData({ finalData, isLoading: false, error: null });
57101
- setFetchedContactId(campaignContactId);
57102
- }
57103
- } catch (error2) {
57104
- if (!cancelled) {
57105
- console.error("[MobileDialer] Erro ao buscar dados:", error2);
57106
- const errorMessage = error2 instanceof Error ? error2.message : "Erro ao buscar dados";
57107
- setContactData({ finalData: {}, isLoading: false, error: errorMessage });
57108
- setFetchedContactId(campaignContactId);
57109
- }
57110
- }
57111
57542
  };
57112
- void fetchContactData();
57543
+ fetchCampaignForm(request).then((form) => {
57544
+ if (cancelled) return;
57545
+ apply2(form);
57546
+ if (form.partial) {
57547
+ refreshTimer = setTimeout(() => {
57548
+ if (cancelled) return;
57549
+ fetchCampaignForm(request, { force: true }).then((refreshed) => {
57550
+ if (!cancelled) apply2(refreshed);
57551
+ }).catch(() => void 0);
57552
+ }, 3e3);
57553
+ }
57554
+ }).catch((error2) => {
57555
+ if (cancelled) return;
57556
+ console.error("[MobileDialer] Erro ao buscar o formulário:", error2);
57557
+ setContactData({ finalData: {}, isLoading: false, error: error2 instanceof Error ? error2.message : "Erro ao buscar dados" });
57558
+ });
57113
57559
  return () => {
57114
57560
  cancelled = true;
57561
+ if (refreshTimer) clearTimeout(refreshTimer);
57115
57562
  };
57116
- }, [campaignContactId, fetchedContactId]);
57563
+ }, [campaignFormKey]);
57117
57564
  const callStateStr = callState;
57118
57565
  const isError = callStateStr === "startup.error" || callStateStr === "offline";
57119
57566
  const isIdle = !callStateStr || callStateStr === "starting" || callStateStr === "idle" || callStateStr === "manualCallSetup" || callStateStr === "onBreak";
@@ -57165,6 +57612,18 @@ function AuthenticatedMobileDialer({
57165
57612
  setPauseDurationSeconds(0);
57166
57613
  }
57167
57614
  }, [isOnBreak, currentBreakName]);
57615
+ useEffect(() => {
57616
+ const onClickToCall = (event) => {
57617
+ const task = event.detail;
57618
+ if (!(task == null ? void 0 : task.phone)) return;
57619
+ setCallError(null);
57620
+ setDialValue(nationalDigitsFromE164(task.phone).slice(0, 32));
57621
+ setActiveScreen("dialer");
57622
+ addLog("event", "Call", `🖱️ Click-to-call recebido: ${task.phoneDisplay}`, { taskId: task.id, source: task.source });
57623
+ };
57624
+ window.addEventListener(CLICK_TO_CALL_EVENT, onClickToCall);
57625
+ return () => window.removeEventListener(CLICK_TO_CALL_EVENT, onClickToCall);
57626
+ }, []);
57168
57627
  useEffect(() => {
57169
57628
  console.log("[MobileDialer] Screen control effect - callState:", callState, "isInCall:", isInCall, "isConnecting:", isConnecting, "isAfterCall:", isAfterCall);
57170
57629
  if (isInCall || isConnecting) {
@@ -57356,16 +57815,17 @@ function AuthenticatedMobileDialer({
57356
57815
  haptic.notificationError();
57357
57816
  }
57358
57817
  }, [haptic, speakerSupported, speakerDeviceId, defaultDeviceId, isSpeakerOn, operator]);
57359
- const fetchCallHistory = useCallback(async () => {
57360
- const agentId = userData == null ? void 0 : userData.id;
57361
- if (!agentId) {
57362
- console.log("[MobileDialer] Aguardando ID do usuário para buscar histórico");
57363
- return;
57364
- }
57365
- setIsLoadingHistory(true);
57366
- setHistoryError(null);
57367
- try {
57368
- const response = await fetch(`${API_BASE_URL}/api/call-history?agent_id=${encodeURIComponent(agentId)}&limit=50`, {
57818
+ const requestHistory = useCallback(
57819
+ async ({ phone, limit }) => {
57820
+ const agentId = userData == null ? void 0 : userData.id;
57821
+ if (!agentId) {
57822
+ throw new Error("Aguardando ID do usuário");
57823
+ }
57824
+ const params = new URLSearchParams({ agent_id: String(agentId), limit: String(limit) });
57825
+ if (phone) {
57826
+ params.set("phone", phone);
57827
+ }
57828
+ const response = await fetch(`${API_BASE_URL}/api/call-history?${params.toString()}`, {
57369
57829
  method: "GET",
57370
57830
  headers: getAuthHeaders(),
57371
57831
  credentials: "omit"
@@ -57376,8 +57836,22 @@ function AuthenticatedMobileDialer({
57376
57836
  );
57377
57837
  }
57378
57838
  const data = await response.json();
57379
- console.log("[MobileDialer] Histórico recebido:", data.total, "registros");
57380
- setCallHistory(data.items || []);
57839
+ return data.items || [];
57840
+ },
57841
+ [userData == null ? void 0 : userData.id]
57842
+ );
57843
+ const fetchCallHistory = useCallback(async () => {
57844
+ if (!(userData == null ? void 0 : userData.id)) {
57845
+ console.log("[MobileDialer] Aguardando ID do usuário para buscar histórico");
57846
+ return;
57847
+ }
57848
+ setIsLoadingHistory(true);
57849
+ setHistoryError(null);
57850
+ historyTextPoolRef.current = null;
57851
+ try {
57852
+ const items = await requestHistory({ limit: 50 });
57853
+ console.log("[MobileDialer] Histórico recebido:", items.length, "registros");
57854
+ setCallHistory(items);
57381
57855
  } catch (error2) {
57382
57856
  console.error("[MobileDialer] Erro ao buscar histórico:", error2);
57383
57857
  setHistoryError(error2 instanceof Error && error2.message.startsWith("Sessão") ? error2.message : "Erro ao carregar histórico");
@@ -57385,7 +57859,54 @@ function AuthenticatedMobileDialer({
57385
57859
  } finally {
57386
57860
  setIsLoadingHistory(false);
57387
57861
  }
57388
- }, [userData == null ? void 0 : userData.id]);
57862
+ }, [requestHistory, userData == null ? void 0 : userData.id]);
57863
+ const handleHistoryQueryChange = useCallback(
57864
+ async (query) => {
57865
+ setHistoryQuery(query);
57866
+ const parsed = classifyHistoryQuery(query);
57867
+ const seq = ++historySearchSeqRef.current;
57868
+ if (parsed.kind === "empty") {
57869
+ setHistorySearchResults(null);
57870
+ setIsSearchingHistory(false);
57871
+ return;
57872
+ }
57873
+ setIsSearchingHistory(true);
57874
+ try {
57875
+ let results;
57876
+ if (parsed.kind === "phone") {
57877
+ results = await requestHistory({ phone: parsed.digits, limit: 100 });
57878
+ } else {
57879
+ if (!historyTextPoolRef.current) {
57880
+ historyTextPoolRef.current = await requestHistory({ limit: 200 });
57881
+ }
57882
+ results = historyTextPoolRef.current;
57883
+ }
57884
+ if (seq === historySearchSeqRef.current) {
57885
+ setHistorySearchResults(results);
57886
+ }
57887
+ } catch (error2) {
57888
+ console.error("[MobileDialer] Erro na busca do histórico:", error2);
57889
+ if (seq === historySearchSeqRef.current) {
57890
+ setHistorySearchResults(null);
57891
+ }
57892
+ } finally {
57893
+ if (seq === historySearchSeqRef.current) {
57894
+ setIsSearchingHistory(false);
57895
+ }
57896
+ }
57897
+ },
57898
+ [requestHistory]
57899
+ );
57900
+ const historySearchHint = useMemo(() => {
57901
+ const parsed = classifyHistoryQuery(historyQuery);
57902
+ if (parsed.kind === "phone") {
57903
+ return "Busca por número em todo o histórico";
57904
+ }
57905
+ if (parsed.kind === "text") {
57906
+ return historySearchResults ? "Busca por texto nas últimas 200 chamadas" : "Busca por texto nas chamadas carregadas";
57907
+ }
57908
+ return null;
57909
+ }, [historyQuery, historySearchResults]);
57389
57910
  useEffect(() => {
57390
57911
  if (activeScreen === "history") {
57391
57912
  void fetchCallHistory();
@@ -57536,7 +58057,7 @@ function AuthenticatedMobileDialer({
57536
58057
  addLog("info", "Debug", "🔒 Painel de debug fechado");
57537
58058
  } });
57538
58059
  }
57539
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "phone-container", children: [
58060
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "phone-container", "data-density": density === "compact" ? "compact" : "comfortable", children: [
57540
58061
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `screen dialer-screen ${activeScreen === "dialer" ? "active" : ""}`, id: "dialerScreen", children: [
57541
58062
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "dialer-header", children: [
57542
58063
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "status-container", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: `status-badge ${statusBadgeClass}`, id: "statusBadge", children: [
@@ -57699,10 +58220,14 @@ function AuthenticatedMobileDialer({
57699
58220
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `screen call-history-screen ${activeScreen === "history" ? "active" : ""}`, id: "callHistoryScreen", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
57700
58221
  CallHistoryList,
57701
58222
  {
57702
- items: callHistory,
58223
+ items: historySearchResults ?? callHistory,
57703
58224
  isLoading: isLoadingHistory,
57704
58225
  error: historyError,
57705
58226
  onRefresh: fetchCallHistory,
58227
+ query: historyQuery,
58228
+ onQueryChange: handleHistoryQueryChange,
58229
+ isSearching: isSearchingHistory,
58230
+ searchHint: historySearchHint,
57706
58231
  onCall: (call) => {
57707
58232
  haptic.impactLight();
57708
58233
  setDialValue(historyPhoneDigits(call));
@@ -57820,6 +58345,7 @@ function AuthenticatedMobileDialer({
57820
58345
  {
57821
58346
  isOpen: isClientInfoModalOpen,
57822
58347
  campaignCallInfo,
58348
+ campaignContactPhoneId: ((_d = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _d.campaignContactPhoneId) ?? null,
57823
58349
  callDuration: callDurationSeconds,
57824
58350
  isCallActive: isInCall || isConnecting,
57825
58351
  onClose: () => setIsClientInfoModalOpen(false)
@@ -58087,18 +58613,21 @@ function InlineDialer({ variant = "default", portalTarget, isAuthenticated, mode
58087
58613
  if (!isAuthenticated || !isMiniMode) {
58088
58614
  return /* @__PURE__ */ jsxRuntimeExports.jsx(FloatingDialer, { variant, portalTarget, isAuthenticated, ...props });
58089
58615
  }
58090
- return /* @__PURE__ */ jsxRuntimeExports.jsx(AuthenticatedMiniDialer, { ...props });
58616
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(AuthenticatedMiniDialer, { portalTarget, ...props });
58091
58617
  }
58092
58618
  function AuthenticatedMiniDialer({
58093
58619
  username,
58094
58620
  onLogin,
58095
58621
  onLogout,
58096
58622
  isLoadingSession,
58097
- sessionError
58623
+ sessionError,
58624
+ portalTarget
58098
58625
  }) {
58626
+ var _a2;
58099
58627
  const { skipManualQualification } = useDialerConfig();
58100
58628
  const operatorState = clientSdkReactExports.useCallOperatorState();
58101
58629
  const currentCallInfo = clientSdkReactExports.useCallOperatorCurrentCallInfo();
58630
+ const currentCall = clientSdkReactExports.useCallOperatorCurrentCall();
58102
58631
  const rawSuccessQualifications = clientSdkReactExports.useCallOperatorSuccessQualifications();
58103
58632
  const rawDiscardQualifications = clientSdkReactExports.useCallOperatorDiscardQualifications();
58104
58633
  const successQualifications = useMemo(() => rawSuccessQualifications ?? [], [rawSuccessQualifications]);
@@ -58354,9 +58883,11 @@ function AuthenticatedMiniDialer({
58354
58883
  {
58355
58884
  isOpen: isClientInfoModalOpen,
58356
58885
  campaignCallInfo,
58886
+ campaignContactPhoneId: ((_a2 = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _a2.campaignContactPhoneId) ?? null,
58357
58887
  callDuration: callDurationSeconds,
58358
58888
  isCallActive: isInCall || isConnecting,
58359
- onClose: () => setIsClientInfoModalOpen(false)
58889
+ onClose: () => setIsClientInfoModalOpen(false),
58890
+ portalTarget
58360
58891
  }
58361
58892
  ),
58362
58893
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -58376,7 +58907,229 @@ function AuthenticatedMiniDialer({
58376
58907
  )
58377
58908
  ] });
58378
58909
  }
58379
- const WIDGET_VERSION = "2.2.0";
58910
+ const DEFAULT_CLICK_TO_CALL_INTERVAL_MS = 4e3;
58911
+ const BUSY_INTERVAL_FACTOR = 3;
58912
+ const DISPATCH_COOLDOWN_MS = 6e3;
58913
+ const CALL_START_TIMEOUT_MS = 4e3;
58914
+ const CALL_START_CHECK_MS = 100;
58915
+ const MAX_BACKOFF_MS = 6e4;
58916
+ const NOT_CONFIGURED_INTERVAL_MS = 5 * 6e4;
58917
+ const NOT_CONFIGURED_CODES = /* @__PURE__ */ new Set(["STORE_NOT_CONFIGURED", "SERVER_NOT_CONFIGURED"]);
58918
+ const ACK_ATTEMPTS = 3;
58919
+ const ACK_RETRY_DELAY_MS = 1500;
58920
+ const CALL_STARTED_STATES = /* @__PURE__ */ new Set(["manualCallSetup", "manualCallRinging", "callInProgress", "afterCall"]);
58921
+ function buildHeaders(sessionToken) {
58922
+ if (sessionToken) {
58923
+ return { "Content-Type": "application/json", "x-session-token": sessionToken, Authorization: `Bearer ${sessionToken}` };
58924
+ }
58925
+ return getAuthHeaders();
58926
+ }
58927
+ const delay = (ms2) => new Promise((resolve) => setTimeout(resolve, ms2));
58928
+ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask }) {
58929
+ const operatorState = clientSdkReactExports.useCallOperatorState();
58930
+ const { makeManualCall } = clientSdkReactExports.useCallOperatorControls();
58931
+ const stateRef = useRef(operatorState == null ? void 0 : operatorState.state);
58932
+ stateRef.current = operatorState == null ? void 0 : operatorState.state;
58933
+ const makeManualCallRef = useRef(makeManualCall);
58934
+ makeManualCallRef.current = makeManualCall;
58935
+ const onTaskRef = useRef(onTask);
58936
+ onTaskRef.current = onTask;
58937
+ const tickerRef = useRef(null);
58938
+ const applyIntervalRef = useRef(null);
58939
+ useEffect(() => {
58940
+ var _a2;
58941
+ (_a2 = applyIntervalRef.current) == null ? void 0 : _a2.call(applyIntervalRef);
58942
+ }, [operatorState == null ? void 0 : operatorState.state]);
58943
+ useEffect(() => {
58944
+ if (!enabled) {
58945
+ return void 0;
58946
+ }
58947
+ let disposed = false;
58948
+ let inFlight = false;
58949
+ let cooldownUntil = 0;
58950
+ let failureStreak = 0;
58951
+ let mode2 = "normal";
58952
+ let notConfiguredWarned = false;
58953
+ let presenceTtlMs = 45e3;
58954
+ let baseInterval = Math.max(1e3, pollIntervalMs ?? DEFAULT_CLICK_TO_CALL_INTERVAL_MS);
58955
+ const config = { apiBaseUrl: API_BASE_URL, headers: buildHeaders(sessionToken) };
58956
+ const computeInterval = () => {
58957
+ if (mode2 === "notConfigured") {
58958
+ return NOT_CONFIGURED_INTERVAL_MS;
58959
+ }
58960
+ const ceiling = Math.max(1e3, Math.floor(presenceTtlMs / 2));
58961
+ const base = Math.min(baseInterval, ceiling);
58962
+ if (mode2 === "backoff") {
58963
+ return Math.min(base * 2 ** failureStreak, MAX_BACKOFF_MS);
58964
+ }
58965
+ const busy = Math.min(base * BUSY_INTERVAL_FACTOR, ceiling);
58966
+ return canDispatchTo(presenceFromOperatorState(stateRef.current)) ? base : busy;
58967
+ };
58968
+ const applyInterval = () => {
58969
+ var _a2;
58970
+ return (_a2 = tickerRef.current) == null ? void 0 : _a2.setInterval(computeInterval());
58971
+ };
58972
+ applyIntervalRef.current = applyInterval;
58973
+ const ack = async (options) => {
58974
+ for (let attempt = 0; attempt < ACK_ATTEMPTS; attempt++) {
58975
+ try {
58976
+ const result2 = await ackClickToCall({ ...config, ...options });
58977
+ if (result2.ok) {
58978
+ return true;
58979
+ }
58980
+ const retryable = result2.status >= 500 || result2.status === 408 || result2.status === 429;
58981
+ if (!retryable) {
58982
+ console.warn("[ClickToCall] confirmação recusada pelo servidor:", result2.status, result2.code ?? "", "(tarefa", options.taskId, ")");
58983
+ return false;
58984
+ }
58985
+ } catch (error2) {
58986
+ console.warn("[ClickToCall] confirmação falhou (tentativa", attempt + 1, "):", error2 instanceof Error ? error2.message : error2);
58987
+ }
58988
+ if (disposed || attempt === ACK_ATTEMPTS - 1) {
58989
+ break;
58990
+ }
58991
+ await delay(ACK_RETRY_DELAY_MS * (attempt + 1));
58992
+ }
58993
+ console.error("[ClickToCall] não foi possível confirmar a tarefa", options.taskId, "; o servidor a marcará como não confirmada");
58994
+ return false;
58995
+ };
58996
+ const waitForCallStart = () => new Promise((resolve) => {
58997
+ const deadline = Date.now() + CALL_START_TIMEOUT_MS;
58998
+ const check = () => {
58999
+ if (disposed) return resolve("disposed");
59000
+ const state = stateRef.current;
59001
+ if (state && CALL_STARTED_STATES.has(state)) return resolve("started");
59002
+ if (state === "manualCallFailed") return resolve("manual_failed");
59003
+ if (state === "callRinging" || presenceFromOperatorState(state) === "offline") return resolve("interrupted");
59004
+ if (Date.now() >= deadline) return resolve("not_started");
59005
+ setTimeout(check, CALL_START_CHECK_MS);
59006
+ };
59007
+ check();
59008
+ });
59009
+ const dispatch = async (task) => {
59010
+ var _a2;
59011
+ const state = stateRef.current;
59012
+ cooldownUntil = Date.now() + DISPATCH_COOLDOWN_MS;
59013
+ if (!canDispatchTo(presenceFromOperatorState(state))) {
59014
+ console.warn("[ClickToCall] tarefa", task.id, "recebida com operador ocupado (", state, "): devolvendo à fila");
59015
+ await ack({ taskId: task.id, status: "failed", error: `Operador indisponível (${state ?? "desconhecido"})`, retry: true });
59016
+ return;
59017
+ }
59018
+ console.log("[ClickToCall] 🖱️ ligação pedida pela API:", task.phoneDisplay, "(tarefa", task.id, ")");
59019
+ try {
59020
+ window.dispatchEvent(new CustomEvent(CLICK_TO_CALL_EVENT, { detail: task }));
59021
+ } catch (error2) {
59022
+ console.warn("[ClickToCall] falha ao notificar o discador:", error2);
59023
+ }
59024
+ try {
59025
+ (_a2 = onTaskRef.current) == null ? void 0 : _a2.call(onTaskRef, task);
59026
+ } catch (error2) {
59027
+ console.warn("[ClickToCall] onTask lançou erro:", error2);
59028
+ }
59029
+ try {
59030
+ makeManualCallRef.current(task.phone);
59031
+ } catch (error2) {
59032
+ const message = error2 instanceof Error ? error2.message : String(error2);
59033
+ console.error("[ClickToCall] makeManualCall falhou:", message);
59034
+ await ack({ taskId: task.id, status: "failed", error: message, retry: false });
59035
+ return;
59036
+ }
59037
+ const outcome = await waitForCallStart();
59038
+ switch (outcome) {
59039
+ case "started":
59040
+ await ack({ taskId: task.id, status: "calling" });
59041
+ return;
59042
+ case "manual_failed":
59043
+ console.error("[ClickToCall] o SDK não conseguiu completar a chamada manual");
59044
+ await ack({ taskId: task.id, status: "failed", error: "Chamada manual falhou no SDK", retry: false });
59045
+ return;
59046
+ case "interrupted":
59047
+ console.warn("[ClickToCall] chamada manual não aconteceu (estado", stateRef.current, "): devolvendo à fila");
59048
+ await ack({ taskId: task.id, status: "failed", error: `Discador interrompido (${stateRef.current ?? "desconhecido"})`, retry: true });
59049
+ return;
59050
+ case "disposed":
59051
+ await ack({ taskId: task.id, status: "failed", error: "Discador encerrado durante a discagem", retry: true });
59052
+ return;
59053
+ default: {
59054
+ const message = `SDK não iniciou a chamada (estado continua ${stateRef.current ?? "desconhecido"})`;
59055
+ console.error("[ClickToCall]", message);
59056
+ await ack({ taskId: task.id, status: "failed", error: message, retry: false });
59057
+ }
59058
+ }
59059
+ };
59060
+ const tick = async () => {
59061
+ if (disposed || inFlight) {
59062
+ return;
59063
+ }
59064
+ inFlight = true;
59065
+ try {
59066
+ const state = stateRef.current;
59067
+ const claim = canDispatchTo(presenceFromOperatorState(state)) && Date.now() >= cooldownUntil;
59068
+ let result2;
59069
+ try {
59070
+ result2 = await pollClickToCall({ ...config, state, claim });
59071
+ } catch (error2) {
59072
+ result2 = { ok: false, status: 0, code: error2 instanceof Error && error2.name === "AbortError" ? "TIMEOUT" : "NETWORK" };
59073
+ }
59074
+ if (disposed) {
59075
+ if (result2.ok && result2.task) {
59076
+ await ack({ taskId: result2.task.id, status: "failed", error: "Discador encerrado antes de discar", retry: true });
59077
+ }
59078
+ return;
59079
+ }
59080
+ if (!result2.ok) {
59081
+ failureStreak += 1;
59082
+ const notConfigured = result2.code !== null && NOT_CONFIGURED_CODES.has(result2.code);
59083
+ mode2 = notConfigured ? "notConfigured" : "backoff";
59084
+ if (notConfigured) {
59085
+ if (!notConfiguredWarned) {
59086
+ notConfiguredWarned = true;
59087
+ console.warn("[ClickToCall] backend sem click-to-call configurado (", result2.code, "); nova tentativa em 5 min");
59088
+ }
59089
+ } else if (failureStreak === 1) {
59090
+ console.warn("[ClickToCall] polling falhou (HTTP", result2.status, result2.code ?? "", "); aplicando backoff");
59091
+ }
59092
+ applyInterval();
59093
+ return;
59094
+ }
59095
+ if (mode2 !== "normal") {
59096
+ mode2 = "normal";
59097
+ failureStreak = 0;
59098
+ }
59099
+ presenceTtlMs = result2.presenceTtlSeconds * 1e3;
59100
+ if (pollIntervalMs === void 0 && result2.pollIntervalMs > 0) {
59101
+ baseInterval = Math.max(1e3, result2.pollIntervalMs);
59102
+ }
59103
+ applyInterval();
59104
+ if (result2.task) {
59105
+ try {
59106
+ await dispatch(result2.task);
59107
+ } catch (error2) {
59108
+ console.error("[ClickToCall] erro ao tratar a tarefa", result2.task.id, ":", error2 instanceof Error ? error2.message : error2);
59109
+ }
59110
+ }
59111
+ } finally {
59112
+ inFlight = false;
59113
+ }
59114
+ };
59115
+ const ticker = createBackgroundTicker(() => void tick(), computeInterval());
59116
+ tickerRef.current = ticker;
59117
+ void tick();
59118
+ return () => {
59119
+ disposed = true;
59120
+ ticker.stop();
59121
+ if (tickerRef.current === ticker) {
59122
+ tickerRef.current = null;
59123
+ }
59124
+ if (applyIntervalRef.current === applyInterval) {
59125
+ applyIntervalRef.current = null;
59126
+ }
59127
+ reportClickToCallOffline(config);
59128
+ };
59129
+ }, [enabled, pollIntervalMs, sessionToken]);
59130
+ return null;
59131
+ }
59132
+ const WIDGET_VERSION = "2.4.0";
58380
59133
  let currentSipWebSocket = null;
58381
59134
  function sendSipUnregisterIfPossible(ws) {
58382
59135
  if (ws.readyState !== WebSocket.OPEN) return;
@@ -58576,8 +59329,25 @@ function deriveOptionsFromElement(element) {
58576
59329
  if (datasetAutoLogin !== void 0 && datasetAutoLogin !== null) {
58577
59330
  result2.autoLogin = ["true", "1", "yes", ""].includes(datasetAutoLogin.trim().toLowerCase());
58578
59331
  }
59332
+ const datasetDensity = parseDensity(element.getAttribute("data-callix-density") ?? element.dataset.callixDensity);
59333
+ if (datasetDensity) {
59334
+ result2.density = datasetDensity;
59335
+ }
59336
+ const datasetClickToCall = element.getAttribute("data-callix-click-to-call") ?? element.dataset.callixClickToCall;
59337
+ if (datasetClickToCall !== void 0 && datasetClickToCall !== null) {
59338
+ result2.clickToCall = !["false", "0", "no", "off"].includes(datasetClickToCall.trim().toLowerCase());
59339
+ }
58579
59340
  return result2;
58580
59341
  }
59342
+ function normalizeClickToCallOptions(value) {
59343
+ if (value === false) {
59344
+ return { enabled: false };
59345
+ }
59346
+ if (value === true || value === void 0 || value === null) {
59347
+ return { enabled: true };
59348
+ }
59349
+ return { ...value, enabled: value.enabled !== false };
59350
+ }
58581
59351
  class CallixDialerWidget {
58582
59352
  constructor(options) {
58583
59353
  this.root = null;
@@ -58608,9 +59378,11 @@ class CallixDialerWidget {
58608
59378
  if (this.options.autoLogin === void 0 && derived.autoLogin !== void 0) {
58609
59379
  this.options.autoLogin = derived.autoLogin;
58610
59380
  }
58611
- if (this.options.uiVariant === "mobile" && !this.container.style.getPropertyValue("container-type")) {
58612
- this.container.style.setProperty("container-type", "inline-size");
58613
- this.container.style.setProperty("container-name", "dialercontainer");
59381
+ if (this.options.density === void 0 && derived.density !== void 0) {
59382
+ this.options.density = derived.density;
59383
+ }
59384
+ if (this.options.clickToCall === void 0 && derived.clickToCall !== void 0) {
59385
+ this.options.clickToCall = derived.clickToCall;
58614
59386
  }
58615
59387
  this.root = clientExports.createRoot(this.container);
58616
59388
  this.root.render(
@@ -58954,6 +59726,7 @@ function DialerWrapper({ options, containerEl }) {
58954
59726
  }, []);
58955
59727
  const DialerComponent = options.mode === "inline" || uiVariant === "mobile" ? InlineDialer : FloatingDialer;
58956
59728
  const dialerMode = options.mode;
59729
+ const clickToCall = normalizeClickToCallOptions(options.clickToCall);
58957
59730
  if (isDisconnecting) {
58958
59731
  return /* @__PURE__ */ jsxRuntimeExports.jsx(DialerConfigContext.Provider, { value: { skipManualQualification }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
58959
59732
  DialerComponent,
@@ -58967,6 +59740,7 @@ function DialerWrapper({ options, containerEl }) {
58967
59740
  sessionStatus: "Encerrando sessão anterior...",
58968
59741
  variant: uiVariant,
58969
59742
  portalTarget,
59743
+ density: options.density,
58970
59744
  mode: dialerMode
58971
59745
  }
58972
59746
  ) });
@@ -58986,6 +59760,7 @@ function DialerWrapper({ options, containerEl }) {
58986
59760
  onCancelLoginConfirmation: handleCancelLoginConfirmation,
58987
59761
  variant: uiVariant,
58988
59762
  portalTarget,
59763
+ density: options.density,
58989
59764
  mode: dialerMode
58990
59765
  }
58991
59766
  ) });
@@ -58995,21 +59770,33 @@ function DialerWrapper({ options, containerEl }) {
58995
59770
  {
58996
59771
  domain: callixDomain,
58997
59772
  userSessionToken: sessionToken,
58998
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(SdkCleanupManager, { ref: cleanupRef, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
58999
- DialerComponent,
59000
- {
59001
- isAuthenticated,
59002
- username,
59003
- onLogin: handleLogin,
59004
- onLogout: handleLogout,
59005
- isLoadingSession: isLoading,
59006
- sessionError: error2,
59007
- sessionStatus: loginStatus,
59008
- variant: uiVariant,
59009
- portalTarget,
59010
- mode: dialerMode
59011
- }
59012
- ) })
59773
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(SdkCleanupManager, { ref: cleanupRef, children: [
59774
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
59775
+ DialerComponent,
59776
+ {
59777
+ isAuthenticated,
59778
+ username,
59779
+ onLogin: handleLogin,
59780
+ onLogout: handleLogout,
59781
+ isLoadingSession: isLoading,
59782
+ sessionError: error2,
59783
+ sessionStatus: loginStatus,
59784
+ variant: uiVariant,
59785
+ portalTarget,
59786
+ density: options.density,
59787
+ mode: dialerMode
59788
+ }
59789
+ ),
59790
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
59791
+ ClickToCallAgent,
59792
+ {
59793
+ enabled: clickToCall.enabled,
59794
+ pollIntervalMs: clickToCall.pollIntervalMs,
59795
+ sessionToken,
59796
+ onTask: clickToCall.onTask
59797
+ }
59798
+ )
59799
+ ] })
59013
59800
  },
59014
59801
  providerKey
59015
59802
  ) });