@coventia/softphone 0.3.0 → 0.5.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.
- package/dist/{call-CaPDWVk3.d.cts → call-FqcvDg1t.d.cts} +168 -12
- package/dist/{call-CaPDWVk3.d.ts → call-FqcvDg1t.d.ts} +168 -12
- package/dist/chunk-5E5OEXS2.js +14 -0
- package/dist/chunk-5E5OEXS2.js.map +1 -0
- package/dist/{chunk-EYEDPD7Y.js → chunk-6RR6G35E.js} +92 -17
- package/dist/chunk-6RR6G35E.js.map +1 -0
- package/dist/{chunk-2ZSOGGPO.js → chunk-FRXHEHQA.js} +15 -2
- package/dist/chunk-FRXHEHQA.js.map +1 -0
- package/dist/{chunk-UHNLEBBO.js → chunk-I3RSC52R.js} +28 -3
- package/dist/chunk-I3RSC52R.js.map +1 -0
- package/dist/chunk-M76SAXXM.js +13 -0
- package/dist/chunk-M76SAXXM.js.map +1 -0
- package/dist/{chunk-7R6LOOAI.js → chunk-N4PLEYFJ.js} +4 -2
- package/dist/chunk-N4PLEYFJ.js.map +1 -0
- package/dist/element/index.d.ts +198 -6
- package/dist/element/index.js +1160 -489
- package/dist/element/index.js.map +1 -1
- package/dist/{engine-BgBsIcEa.d.ts → engine-CsNRD7B7.d.ts} +1 -1
- package/dist/{engine-CU2rEo3g.d.cts → engine-CzOCrfY9.d.cts} +1 -1
- package/dist/index.cjs +675 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -6
- package/dist/index.d.ts +16 -6
- package/dist/index.js +453 -37
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +26 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +24 -5
- package/dist/react/index.d.ts +24 -5
- package/dist/react/index.js +2 -2
- package/dist/{softphone-DEt0TtQw.d.ts → softphone-By9sizlV.d.ts} +44 -3
- package/dist/{softphone-dZMkW8V1.d.cts → softphone-JZvJdzU3.d.cts} +44 -3
- package/dist/softphone.css +254 -23
- package/dist/{sources-M3GYMS6V.js → sources-QL2LRZVH.js} +93 -8
- package/dist/sources-QL2LRZVH.js.map +1 -0
- package/dist/testing/index.cjs +13 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +1 -1
- package/dist/ui/index.cjs +670 -20
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +2 -2
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +642 -22
- package/dist/ui/index.js.map +1 -1
- package/dist/zadarma/index.cjs +111 -14
- package/dist/zadarma/index.cjs.map +1 -1
- package/dist/zadarma/index.d.cts +5 -6
- package/dist/zadarma/index.d.ts +5 -6
- package/dist/zadarma/index.js +3 -2
- package/dist/zadarma-engine-TIUE7PZ7.js +6 -0
- package/dist/{zadarma-engine-L5DEFXUS.js.map → zadarma-engine-TIUE7PZ7.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-2ZSOGGPO.js.map +0 -1
- package/dist/chunk-7R6LOOAI.js.map +0 -1
- package/dist/chunk-EYEDPD7Y.js.map +0 -1
- package/dist/chunk-UHNLEBBO.js.map +0 -1
- package/dist/sources-M3GYMS6V.js.map +0 -1
- package/dist/zadarma-engine-L5DEFXUS.js +0 -5
package/dist/ui/index.cjs
CHANGED
|
@@ -13263,6 +13263,19 @@ var Pause = createLucideIcon("Pause", [
|
|
|
13263
13263
|
["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
|
|
13264
13264
|
]);
|
|
13265
13265
|
|
|
13266
|
+
// ../../node_modules/lucide-react/dist/esm/icons/phone-forwarded.js
|
|
13267
|
+
var PhoneForwarded = createLucideIcon("PhoneForwarded", [
|
|
13268
|
+
["polyline", { points: "18 2 22 6 18 10", key: "6vjanh" }],
|
|
13269
|
+
["line", { x1: "14", x2: "22", y1: "6", y2: "6", key: "1jsywh" }],
|
|
13270
|
+
[
|
|
13271
|
+
"path",
|
|
13272
|
+
{
|
|
13273
|
+
d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z",
|
|
13274
|
+
key: "foiqr5"
|
|
13275
|
+
}
|
|
13276
|
+
]
|
|
13277
|
+
]);
|
|
13278
|
+
|
|
13266
13279
|
// ../../node_modules/lucide-react/dist/esm/icons/phone-incoming.js
|
|
13267
13280
|
var PhoneIncoming = createLucideIcon("PhoneIncoming", [
|
|
13268
13281
|
["polyline", { points: "16 2 16 8 22 8", key: "1ygljm" }],
|
|
@@ -13439,6 +13452,31 @@ function useIsLeader() {
|
|
|
13439
13452
|
React.useCallback(() => phone.isLeader, [phone])
|
|
13440
13453
|
);
|
|
13441
13454
|
}
|
|
13455
|
+
function useLine() {
|
|
13456
|
+
const phone = useSoftphone();
|
|
13457
|
+
const cache = React.useRef(null);
|
|
13458
|
+
return React.useSyncExternalStore(
|
|
13459
|
+
React.useCallback(
|
|
13460
|
+
(cb) => {
|
|
13461
|
+
const offLine = phone.on("lineChanged" /* LineChanged */, () => cb());
|
|
13462
|
+
const offClaim = phone.on("lineClaimChanged" /* LineClaimChanged */, () => cb());
|
|
13463
|
+
return () => {
|
|
13464
|
+
offLine();
|
|
13465
|
+
offClaim();
|
|
13466
|
+
};
|
|
13467
|
+
},
|
|
13468
|
+
[phone]
|
|
13469
|
+
),
|
|
13470
|
+
React.useCallback(() => {
|
|
13471
|
+
const { line, lineWhere: where, lineClaim: claim } = phone;
|
|
13472
|
+
const cached = cache.current;
|
|
13473
|
+
if (cached?.line === line && cached.where === where && cached.claim === claim) return cached;
|
|
13474
|
+
const view = { line, where, claim };
|
|
13475
|
+
cache.current = view;
|
|
13476
|
+
return view;
|
|
13477
|
+
}, [phone])
|
|
13478
|
+
);
|
|
13479
|
+
}
|
|
13442
13480
|
function useActiveCall() {
|
|
13443
13481
|
const phone = useSoftphone();
|
|
13444
13482
|
return React.useSyncExternalStore(
|
|
@@ -13540,6 +13578,36 @@ var PACKED = "AC247 AD376 AE971 AF93 AG1 AI1 AL355 AM374 AO244 AR54 AS1 AT43 AU6
|
|
|
13540
13578
|
var DIAL_CODES = new Map(
|
|
13541
13579
|
PACKED.split(" ").map((token) => [token.slice(0, 2), Number(token.slice(2))])
|
|
13542
13580
|
);
|
|
13581
|
+
var REGIONS_BY_CODE = (() => {
|
|
13582
|
+
const map = /* @__PURE__ */ new Map();
|
|
13583
|
+
for (const [region, code] of DIAL_CODES) map.set(code, [...map.get(code) ?? [], region]);
|
|
13584
|
+
return map;
|
|
13585
|
+
})();
|
|
13586
|
+
var MAIN_REGION = {
|
|
13587
|
+
1: "US",
|
|
13588
|
+
7: "RU",
|
|
13589
|
+
39: "IT",
|
|
13590
|
+
44: "GB",
|
|
13591
|
+
47: "NO",
|
|
13592
|
+
61: "AU",
|
|
13593
|
+
212: "MA",
|
|
13594
|
+
262: "RE",
|
|
13595
|
+
290: "SH",
|
|
13596
|
+
358: "FI",
|
|
13597
|
+
590: "GP",
|
|
13598
|
+
599: "CW"
|
|
13599
|
+
};
|
|
13600
|
+
function regionOfInternational(typed) {
|
|
13601
|
+
const text = String(typed ?? "").trim();
|
|
13602
|
+
if (!text.startsWith("+")) return null;
|
|
13603
|
+
const digits = text.slice(1).replace(/\D/g, "");
|
|
13604
|
+
for (let length = 1; length <= Math.min(3, digits.length); length++) {
|
|
13605
|
+
const code = Number(digits.slice(0, length));
|
|
13606
|
+
const regions = REGIONS_BY_CODE.get(code);
|
|
13607
|
+
if (regions) return MAIN_REGION[code] ?? regions[0] ?? null;
|
|
13608
|
+
}
|
|
13609
|
+
return null;
|
|
13610
|
+
}
|
|
13543
13611
|
var DEFAULT_COUNTRY = "ES";
|
|
13544
13612
|
var REGION_NAMES = (() => {
|
|
13545
13613
|
try {
|
|
@@ -13591,12 +13659,16 @@ function CountryMark({ code }) {
|
|
|
13591
13659
|
}
|
|
13592
13660
|
function CountryPicker({
|
|
13593
13661
|
value,
|
|
13594
|
-
onChange
|
|
13662
|
+
onChange,
|
|
13663
|
+
fromNumber = false
|
|
13595
13664
|
}) {
|
|
13596
13665
|
const [open, setOpen] = React.useState(false);
|
|
13597
13666
|
const [query, setQuery] = React.useState("");
|
|
13598
13667
|
const rootRef = React.useRef(null);
|
|
13599
13668
|
const inputRef = React.useRef(null);
|
|
13669
|
+
React.useEffect(() => {
|
|
13670
|
+
if (fromNumber) setOpen(false);
|
|
13671
|
+
}, [fromNumber]);
|
|
13600
13672
|
const results = React.useMemo(() => {
|
|
13601
13673
|
const q = query.trim().toLowerCase().replace(/^\+/, "");
|
|
13602
13674
|
if (!q) return COUNTRIES;
|
|
@@ -13625,6 +13697,12 @@ function CountryPicker({
|
|
|
13625
13697
|
else setQuery("");
|
|
13626
13698
|
}, [open]);
|
|
13627
13699
|
const dial = DIAL_CODES.get(value);
|
|
13700
|
+
if (fromNumber) {
|
|
13701
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-cp", ref: rootRef, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-cp-chip", "data-from-number": true, title: "El prefijo viene del n\xFAmero marcado", part: "country-chip", children: [
|
|
13702
|
+
/* @__PURE__ */ jsxRuntime.jsx(CountryMark, { code: value || "+" }),
|
|
13703
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-sr-only", children: dial ? `Prefijo del n\xFAmero marcado: ${countryName(value)} (+${dial})` : "Prefijo del n\xFAmero marcado" })
|
|
13704
|
+
] }) });
|
|
13705
|
+
}
|
|
13628
13706
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-cp", ref: rootRef, children: [
|
|
13629
13707
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
13630
13708
|
"button",
|
|
@@ -14191,18 +14269,384 @@ function playRingback() {
|
|
|
14191
14269
|
}
|
|
14192
14270
|
};
|
|
14193
14271
|
}
|
|
14272
|
+
|
|
14273
|
+
// src/core/outbound-numbers.ts
|
|
14274
|
+
function isOutboundNumberRefused(value) {
|
|
14275
|
+
const refusal = value?.refusal;
|
|
14276
|
+
return value instanceof Error && !!refusal && (refusal.reason === "platform" || refusal.reason === "user") && typeof refusal.retryAfterSeconds === "number";
|
|
14277
|
+
}
|
|
14278
|
+
var stores = /* @__PURE__ */ new WeakMap();
|
|
14279
|
+
function outboundNumberStore(softphone) {
|
|
14280
|
+
const source = softphone.sources?.outboundNumbers;
|
|
14281
|
+
if (!source) return null;
|
|
14282
|
+
let store = stores.get(softphone);
|
|
14283
|
+
if (!store) {
|
|
14284
|
+
store = createOutboundNumberStore(source);
|
|
14285
|
+
stores.set(softphone, store);
|
|
14286
|
+
}
|
|
14287
|
+
return store;
|
|
14288
|
+
}
|
|
14289
|
+
function createOutboundNumberStore(source) {
|
|
14290
|
+
let settings = null;
|
|
14291
|
+
let inFlight = null;
|
|
14292
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
14293
|
+
const apply = (next) => {
|
|
14294
|
+
settings = next;
|
|
14295
|
+
for (const l of listeners) l();
|
|
14296
|
+
};
|
|
14297
|
+
return {
|
|
14298
|
+
getSettings: () => settings,
|
|
14299
|
+
refresh() {
|
|
14300
|
+
inFlight ??= (async () => {
|
|
14301
|
+
if (!settings && source.cached) {
|
|
14302
|
+
const early = await source.cached().catch(() => null);
|
|
14303
|
+
if (early && !settings) apply(early);
|
|
14304
|
+
}
|
|
14305
|
+
try {
|
|
14306
|
+
apply(await source.get());
|
|
14307
|
+
} catch (error) {
|
|
14308
|
+
console.warn("[softphone] outbound numbers could not be loaded; keeping the previous ones", error);
|
|
14309
|
+
} finally {
|
|
14310
|
+
inFlight = null;
|
|
14311
|
+
}
|
|
14312
|
+
})();
|
|
14313
|
+
return inFlight;
|
|
14314
|
+
},
|
|
14315
|
+
async setMine(id) {
|
|
14316
|
+
apply(await source.setMine(id));
|
|
14317
|
+
},
|
|
14318
|
+
subscribe(listener) {
|
|
14319
|
+
listeners.add(listener);
|
|
14320
|
+
return () => listeners.delete(listener);
|
|
14321
|
+
}
|
|
14322
|
+
};
|
|
14323
|
+
}
|
|
14324
|
+
var noSubscribe = () => () => {
|
|
14325
|
+
};
|
|
14326
|
+
var noSettings = () => null;
|
|
14327
|
+
function useOutboundNumbers(store) {
|
|
14328
|
+
const settings = React.useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
|
|
14329
|
+
const [failed, setFailed] = React.useState(false);
|
|
14330
|
+
const load = React.useCallback(() => {
|
|
14331
|
+
if (!store) return () => {
|
|
14332
|
+
};
|
|
14333
|
+
let live = true;
|
|
14334
|
+
setFailed(false);
|
|
14335
|
+
void store.refresh().then(() => {
|
|
14336
|
+
if (live) setFailed(store.getSettings() === null);
|
|
14337
|
+
});
|
|
14338
|
+
return () => {
|
|
14339
|
+
live = false;
|
|
14340
|
+
};
|
|
14341
|
+
}, [store]);
|
|
14342
|
+
React.useEffect(load, [load]);
|
|
14343
|
+
return { settings, failed, retry: load };
|
|
14344
|
+
}
|
|
14345
|
+
function useKnownOutboundNumbers(store) {
|
|
14346
|
+
return React.useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
|
|
14347
|
+
}
|
|
14348
|
+
function useCountdown(until) {
|
|
14349
|
+
const [, setTick] = React.useState(0);
|
|
14350
|
+
React.useEffect(() => {
|
|
14351
|
+
if (!until) return void 0;
|
|
14352
|
+
const timer = setInterval(() => {
|
|
14353
|
+
setTick((n) => n + 1);
|
|
14354
|
+
if (secondsLeft(until) === 0) clearInterval(timer);
|
|
14355
|
+
}, 1e3);
|
|
14356
|
+
return () => clearInterval(timer);
|
|
14357
|
+
}, [until]);
|
|
14358
|
+
return secondsLeft(until);
|
|
14359
|
+
}
|
|
14360
|
+
function secondsLeft(until) {
|
|
14361
|
+
return until ? Math.max(0, Math.ceil((until - Date.now()) / 1e3)) : 0;
|
|
14362
|
+
}
|
|
14363
|
+
function numberName(n, format = formatIntl) {
|
|
14364
|
+
if (!n) return "N\xFAmero de la organizaci\xF3n";
|
|
14365
|
+
return n.label || format(`+${n.number}`);
|
|
14366
|
+
}
|
|
14367
|
+
function effectiveNumber(settings) {
|
|
14368
|
+
return settings?.numbers.find((n) => n.id === settings.effective);
|
|
14369
|
+
}
|
|
14370
|
+
|
|
14371
|
+
// src/ui/outbound/outbound-change.ts
|
|
14372
|
+
var KIND_TAG = {
|
|
14373
|
+
mobile: { tag: "M\xF3vil", hint: "No usar para atenci\xF3n al cliente ni para llamadas comerciales no solicitadas." },
|
|
14374
|
+
commercial: { tag: "Comercial (400)", hint: "Solo para llamadas comerciales. No recibe llamadas." }
|
|
14375
|
+
};
|
|
14376
|
+
function noticeText(situation, seconds, current) {
|
|
14377
|
+
switch (situation) {
|
|
14378
|
+
case "platform":
|
|
14379
|
+
return `El cambio de n\xFAmero de salida no est\xE1 disponible temporalmente. Vuelve a intentarlo en ${seconds} s. Hasta entonces, tus llamadas seguir\xE1n saliendo desde ${current}.`;
|
|
14380
|
+
case "user":
|
|
14381
|
+
return `Acabas de cambiar el n\xFAmero de salida. Podr\xE1s cambiarlo de nuevo en ${seconds} s. Hasta entonces, tus llamadas saldr\xE1n desde ${current}.`;
|
|
14382
|
+
case "wait":
|
|
14383
|
+
return `Podr\xE1s cambiar el n\xFAmero de salida en ${seconds} s.`;
|
|
14384
|
+
case "pending":
|
|
14385
|
+
return seconds > 0 ? `Tu n\xFAmero de salida todav\xEDa no se ha aplicado. Podr\xE1s volver a elegirlo en ${seconds} s.` : "Tu n\xFAmero de salida todav\xEDa no se ha aplicado. Vuelve a elegirlo para aplicarlo.";
|
|
14386
|
+
}
|
|
14387
|
+
}
|
|
14388
|
+
function useOutboundNumberChange(store, settings, current) {
|
|
14389
|
+
const [busy, setBusy] = React.useState(false);
|
|
14390
|
+
const [notice, setNotice] = React.useState(null);
|
|
14391
|
+
const [waitUntil, setWaitUntil] = React.useState(null);
|
|
14392
|
+
React.useEffect(() => {
|
|
14393
|
+
const s = settings?.changeAvailableInSeconds ?? 0;
|
|
14394
|
+
setWaitUntil(s > 0 ? Date.now() + s * 1e3 : null);
|
|
14395
|
+
}, [settings]);
|
|
14396
|
+
const refusedUntil = notice?.kind === "refused" ? notice.until : null;
|
|
14397
|
+
const blockedFor = useCountdown(refusedUntil ?? waitUntil);
|
|
14398
|
+
React.useEffect(() => {
|
|
14399
|
+
if (notice?.kind === "refused" && blockedFor === 0) setNotice(null);
|
|
14400
|
+
}, [notice, blockedFor]);
|
|
14401
|
+
const locked = busy || blockedFor > 0;
|
|
14402
|
+
const choose = async (id) => {
|
|
14403
|
+
if (!store || !settings || locked) return false;
|
|
14404
|
+
if (id === settings.mine) return true;
|
|
14405
|
+
setBusy(true);
|
|
14406
|
+
setNotice(null);
|
|
14407
|
+
try {
|
|
14408
|
+
await store.setMine(id);
|
|
14409
|
+
return true;
|
|
14410
|
+
} catch (error) {
|
|
14411
|
+
setNotice(
|
|
14412
|
+
isOutboundNumberRefused(error) ? { kind: "refused", reason: error.refusal.reason, until: Date.now() + error.refusal.retryAfterSeconds * 1e3 } : { kind: "error", text: errorText(error) }
|
|
14413
|
+
);
|
|
14414
|
+
return false;
|
|
14415
|
+
} finally {
|
|
14416
|
+
setBusy(false);
|
|
14417
|
+
}
|
|
14418
|
+
};
|
|
14419
|
+
return {
|
|
14420
|
+
busy,
|
|
14421
|
+
locked,
|
|
14422
|
+
choose,
|
|
14423
|
+
/** What to say about the choice right now, if anything. */
|
|
14424
|
+
message: settings ? currentMessage(notice, settings.state, blockedFor, current) : null,
|
|
14425
|
+
/** The message is an error (the change failed), not information (wait, pending). */
|
|
14426
|
+
isError: notice?.kind === "error"
|
|
14427
|
+
};
|
|
14428
|
+
}
|
|
14429
|
+
function currentMessage(notice, state, blockedFor, current) {
|
|
14430
|
+
if (notice?.kind === "refused") return blockedFor > 0 ? noticeText(notice.reason, blockedFor, current) : null;
|
|
14431
|
+
if (notice?.kind === "error") return notice.text;
|
|
14432
|
+
if (state === "pending" || state === "failed") return noticeText("pending", blockedFor, current);
|
|
14433
|
+
return blockedFor > 0 ? noticeText("wait", blockedFor, current) : null;
|
|
14434
|
+
}
|
|
14435
|
+
function errorText(error) {
|
|
14436
|
+
const message = error instanceof Error ? error.message : "";
|
|
14437
|
+
return message && !/HTTP \d+/.test(message) ? message : "No se ha podido cambiar el n\xFAmero de salida. Int\xE9ntalo de nuevo.";
|
|
14438
|
+
}
|
|
14439
|
+
var CALLOUT_MS = 6e3;
|
|
14440
|
+
function OutboundNumberPicker({ onBusyChange }) {
|
|
14441
|
+
const softphone = useSoftphone();
|
|
14442
|
+
const store = outboundNumberStore(softphone);
|
|
14443
|
+
const { settings } = useOutboundNumbers(store);
|
|
14444
|
+
const formatIntl2 = useIntlFormatter();
|
|
14445
|
+
const current = settings ? numberName(effectiveNumber(settings), formatIntl2) : "";
|
|
14446
|
+
const { busy, locked, choose: apply, message, isError } = useOutboundNumberChange(store, settings, current);
|
|
14447
|
+
const [open, setOpen] = React.useState(false);
|
|
14448
|
+
const [applying, setApplying] = React.useState(void 0);
|
|
14449
|
+
const [callout, setCallout] = React.useState(false);
|
|
14450
|
+
const rootRef = React.useRef(null);
|
|
14451
|
+
const chipRef = React.useRef(null);
|
|
14452
|
+
const keys = useListKeyboard(open);
|
|
14453
|
+
React.useEffect(() => onBusyChange?.(busy), [busy, onBusyChange]);
|
|
14454
|
+
React.useEffect(() => {
|
|
14455
|
+
if (!message) setCallout(false);
|
|
14456
|
+
}, [message]);
|
|
14457
|
+
React.useEffect(() => {
|
|
14458
|
+
if (!callout) return void 0;
|
|
14459
|
+
const timer = setTimeout(() => setCallout(false), CALLOUT_MS);
|
|
14460
|
+
return () => clearTimeout(timer);
|
|
14461
|
+
}, [callout]);
|
|
14462
|
+
const floating = open || callout;
|
|
14463
|
+
React.useEffect(() => {
|
|
14464
|
+
if (!floating) return;
|
|
14465
|
+
const close = () => {
|
|
14466
|
+
setOpen(false);
|
|
14467
|
+
setCallout(false);
|
|
14468
|
+
};
|
|
14469
|
+
const root = rootRef.current?.getRootNode() ?? document;
|
|
14470
|
+
const onDown = (e) => {
|
|
14471
|
+
if (!rootRef.current?.contains(e.target)) close();
|
|
14472
|
+
};
|
|
14473
|
+
const onKey = (e) => {
|
|
14474
|
+
if (e.key !== "Escape") return;
|
|
14475
|
+
if (rootRef.current?.contains(e.target)) chipRef.current?.focus();
|
|
14476
|
+
close();
|
|
14477
|
+
};
|
|
14478
|
+
root.addEventListener("pointerdown", onDown);
|
|
14479
|
+
document.addEventListener("keydown", onKey);
|
|
14480
|
+
return () => {
|
|
14481
|
+
root.removeEventListener("pointerdown", onDown);
|
|
14482
|
+
document.removeEventListener("keydown", onKey);
|
|
14483
|
+
};
|
|
14484
|
+
}, [floating]);
|
|
14485
|
+
if (!store || !settings || settings.numbers.length < 2) return null;
|
|
14486
|
+
const orgDefault = settings.numbers.find((n) => n.id === settings.orgDefault);
|
|
14487
|
+
const applyingNumber = applying === null ? orgDefault : settings.numbers.find((n) => n.id === applying);
|
|
14488
|
+
const shown = applying === void 0 ? current : numberName(applyingNumber, formatIntl2);
|
|
14489
|
+
const choose = async (id) => {
|
|
14490
|
+
if (locked) return;
|
|
14491
|
+
setOpen(false);
|
|
14492
|
+
chipRef.current?.focus();
|
|
14493
|
+
if (id === settings.mine) return;
|
|
14494
|
+
setCallout(false);
|
|
14495
|
+
setApplying(id);
|
|
14496
|
+
const applied = await apply(id);
|
|
14497
|
+
setApplying(void 0);
|
|
14498
|
+
if (!applied) setCallout(true);
|
|
14499
|
+
};
|
|
14500
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-on", ref: rootRef, children: [
|
|
14501
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
14502
|
+
"button",
|
|
14503
|
+
{
|
|
14504
|
+
ref: chipRef,
|
|
14505
|
+
type: "button",
|
|
14506
|
+
className: "cv-on-chip",
|
|
14507
|
+
onClick: () => {
|
|
14508
|
+
setCallout(false);
|
|
14509
|
+
setOpen((o) => !o);
|
|
14510
|
+
},
|
|
14511
|
+
"aria-haspopup": "listbox",
|
|
14512
|
+
"aria-expanded": open,
|
|
14513
|
+
"aria-label": `Llamar desde: ${shown}${busy ? " (aplicando)" : ""}. Cambiar n\xFAmero de salida`,
|
|
14514
|
+
"aria-busy": busy,
|
|
14515
|
+
part: "outbound-number-chip",
|
|
14516
|
+
children: [
|
|
14517
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-label", children: "Llamar desde:" }),
|
|
14518
|
+
busy ? /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { size: 13, className: "cv-on-icon cv-on-spin", "aria-hidden": true }) : null,
|
|
14519
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-current", children: shown }),
|
|
14520
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChevronDown, { size: 13, className: "cv-cp-caret", "data-open": open, "aria-hidden": true })
|
|
14521
|
+
]
|
|
14522
|
+
}
|
|
14523
|
+
),
|
|
14524
|
+
open ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14525
|
+
"div",
|
|
14526
|
+
{
|
|
14527
|
+
className: "cv-cp-pop cv-on-pop",
|
|
14528
|
+
role: "dialog",
|
|
14529
|
+
"aria-label": "Llamar desde",
|
|
14530
|
+
part: "outbound-number-list",
|
|
14531
|
+
onBlur: (e) => {
|
|
14532
|
+
if (!e.currentTarget.contains(e.relatedTarget)) setOpen(false);
|
|
14533
|
+
},
|
|
14534
|
+
children: [
|
|
14535
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-on-title", children: "Llamar desde" }),
|
|
14536
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-cp-list", role: "listbox", "aria-disabled": locked, ref: keys.listRef, onKeyDown: keys.onKeyDown, children: [
|
|
14537
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14538
|
+
OptionRow,
|
|
14539
|
+
{
|
|
14540
|
+
selected: settings.mine === null,
|
|
14541
|
+
disabled: locked,
|
|
14542
|
+
title: "Por defecto de la organizaci\xF3n",
|
|
14543
|
+
sub: orgDefault ? numberName(orgDefault, formatIntl2) : "Sin n\xFAmero por defecto",
|
|
14544
|
+
onSelect: () => void choose(null)
|
|
14545
|
+
}
|
|
14546
|
+
),
|
|
14547
|
+
settings.numbers.map((n) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
14548
|
+
OptionRow,
|
|
14549
|
+
{
|
|
14550
|
+
selected: settings.mine === n.id,
|
|
14551
|
+
disabled: locked,
|
|
14552
|
+
title: numberName(n, formatIntl2),
|
|
14553
|
+
sub: n.label ? formatIntl2(`+${n.number}`) : null,
|
|
14554
|
+
tag: KIND_TAG[n.kind],
|
|
14555
|
+
onSelect: () => void choose(n.id)
|
|
14556
|
+
},
|
|
14557
|
+
n.id
|
|
14558
|
+
))
|
|
14559
|
+
] }),
|
|
14560
|
+
message ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-on-notice", role: "status", "data-kind": isError ? "error" : "info", children: message }) : null
|
|
14561
|
+
]
|
|
14562
|
+
}
|
|
14563
|
+
) : null,
|
|
14564
|
+
callout && !open && message ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-on-callout", role: "status", "data-kind": isError ? "error" : "info", part: "outbound-number-notice", children: message }) : null
|
|
14565
|
+
] });
|
|
14566
|
+
}
|
|
14567
|
+
function OptionRow({
|
|
14568
|
+
selected,
|
|
14569
|
+
disabled,
|
|
14570
|
+
title,
|
|
14571
|
+
sub,
|
|
14572
|
+
tag,
|
|
14573
|
+
onSelect
|
|
14574
|
+
}) {
|
|
14575
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14576
|
+
"button",
|
|
14577
|
+
{
|
|
14578
|
+
type: "button",
|
|
14579
|
+
role: "option",
|
|
14580
|
+
"aria-selected": selected,
|
|
14581
|
+
"aria-disabled": disabled,
|
|
14582
|
+
className: "cv-cp-item cv-on-item",
|
|
14583
|
+
tabIndex: -1,
|
|
14584
|
+
onClick: onSelect,
|
|
14585
|
+
children: [
|
|
14586
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-on-text", children: [
|
|
14587
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-on-line", children: [
|
|
14588
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-cp-name", children: title }),
|
|
14589
|
+
tag ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-tag", children: tag.tag }) : null
|
|
14590
|
+
] }),
|
|
14591
|
+
sub ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-sub", children: sub }) : null,
|
|
14592
|
+
tag ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-hint", children: tag.hint }) : null
|
|
14593
|
+
] }),
|
|
14594
|
+
/* @__PURE__ */ jsxRuntime.jsx(Check, { size: 14, className: "cv-cp-check", "data-on": selected, "aria-hidden": true })
|
|
14595
|
+
]
|
|
14596
|
+
}
|
|
14597
|
+
);
|
|
14598
|
+
}
|
|
14599
|
+
var optionsOf = (list) => list ? [...list.querySelectorAll('[role="option"]')] : [];
|
|
14600
|
+
var fold = (text) => text.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
|
|
14601
|
+
var TYPEAHEAD_MS = 700;
|
|
14602
|
+
function useListKeyboard(open) {
|
|
14603
|
+
const listRef = React.useRef(null);
|
|
14604
|
+
const typed = React.useRef({ text: "", at: 0 });
|
|
14605
|
+
React.useEffect(() => {
|
|
14606
|
+
if (!open) return;
|
|
14607
|
+
const options = optionsOf(listRef.current);
|
|
14608
|
+
(options.find((o) => o.getAttribute("aria-selected") === "true") ?? options[0])?.focus();
|
|
14609
|
+
}, [open]);
|
|
14610
|
+
const onKeyDown = (e) => {
|
|
14611
|
+
const options = optionsOf(listRef.current);
|
|
14612
|
+
const at = options.indexOf(e.target);
|
|
14613
|
+
const move = {
|
|
14614
|
+
ArrowDown: Math.min(at + 1, options.length - 1),
|
|
14615
|
+
ArrowUp: Math.max(at - 1, 0),
|
|
14616
|
+
Home: 0,
|
|
14617
|
+
End: options.length - 1
|
|
14618
|
+
};
|
|
14619
|
+
let next = move[e.key];
|
|
14620
|
+
if (next === void 0 && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
14621
|
+
const now = Date.now();
|
|
14622
|
+
const text = now - typed.current.at < TYPEAHEAD_MS ? typed.current.text + e.key : e.key;
|
|
14623
|
+
typed.current = { text, at: now };
|
|
14624
|
+
const wanted = fold(text);
|
|
14625
|
+
const match = options.findIndex((o) => fold(o.querySelector(".cv-cp-name")?.textContent ?? "").startsWith(wanted));
|
|
14626
|
+
next = match >= 0 ? match : void 0;
|
|
14627
|
+
}
|
|
14628
|
+
if (next === void 0) return;
|
|
14629
|
+
e.preventDefault();
|
|
14630
|
+
options[next]?.focus();
|
|
14631
|
+
};
|
|
14632
|
+
return { listRef, onKeyDown };
|
|
14633
|
+
}
|
|
14194
14634
|
var NOT_DIALABLE = /* @__PURE__ */ new Set(["*", "#"]);
|
|
14635
|
+
var DISPLAY_SIZES = ["1.375rem", "1.25rem", "1.125rem", "1rem"];
|
|
14195
14636
|
function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
14196
14637
|
const softphone = useSoftphone();
|
|
14197
14638
|
const state = useSoftphoneState();
|
|
14198
14639
|
const [value, setValue] = React.useState("");
|
|
14199
14640
|
const [country2, setCountry] = React.useState(DEFAULT_COUNTRY);
|
|
14641
|
+
const [changingNumber, setChangingNumber] = React.useState(false);
|
|
14200
14642
|
const format = useNumberFormatter();
|
|
14201
14643
|
const inputRef = React.useRef(null);
|
|
14202
14644
|
const caretRef = React.useRef(null);
|
|
14203
14645
|
const display = format(country2, value);
|
|
14204
14646
|
const target = toE164(country2, value);
|
|
14205
|
-
const canCall = state === "online" && target.length > 1;
|
|
14647
|
+
const canCall = state === "online" && target.length > 1 && !changingNumber;
|
|
14648
|
+
const typedInternational = value.trim().startsWith("+");
|
|
14649
|
+
const numberRegion = regionOfInternational(value);
|
|
14206
14650
|
React.useLayoutEffect(() => {
|
|
14207
14651
|
const caret = caretRef.current;
|
|
14208
14652
|
if (caret === null || !inputRef.current) return;
|
|
@@ -14210,6 +14654,14 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
|
14210
14654
|
const pos = caretAfterDigits(display, caret);
|
|
14211
14655
|
inputRef.current.setSelectionRange(pos, pos);
|
|
14212
14656
|
}, [display]);
|
|
14657
|
+
React.useLayoutEffect(() => {
|
|
14658
|
+
const input = inputRef.current;
|
|
14659
|
+
if (!input) return;
|
|
14660
|
+
for (const size of DISPLAY_SIZES) {
|
|
14661
|
+
input.style.fontSize = size;
|
|
14662
|
+
if (input.scrollWidth <= input.clientWidth) break;
|
|
14663
|
+
}
|
|
14664
|
+
}, [display]);
|
|
14213
14665
|
const append = (key) => {
|
|
14214
14666
|
onIntent?.();
|
|
14215
14667
|
setValue((v) => v + key);
|
|
@@ -14225,9 +14677,16 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
|
14225
14677
|
void softphone.call(target);
|
|
14226
14678
|
};
|
|
14227
14679
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-view", children: [
|
|
14228
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14680
|
+
/* @__PURE__ */ jsxRuntime.jsx(OutboundNumberPicker, { onBusyChange: setChangingNumber }),
|
|
14229
14681
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-dial-head", children: [
|
|
14230
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14682
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14683
|
+
CountryPicker,
|
|
14684
|
+
{
|
|
14685
|
+
value: numberRegion ?? country2,
|
|
14686
|
+
onChange: setCountry,
|
|
14687
|
+
fromNumber: typedInternational
|
|
14688
|
+
}
|
|
14689
|
+
),
|
|
14231
14690
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14232
14691
|
"input",
|
|
14233
14692
|
{
|
|
@@ -14251,6 +14710,8 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
|
14251
14710
|
type: "button",
|
|
14252
14711
|
className: "cv-dial-side",
|
|
14253
14712
|
"data-hidden": value.length === 0,
|
|
14713
|
+
tabIndex: value.length === 0 ? -1 : void 0,
|
|
14714
|
+
"aria-hidden": value.length === 0 || void 0,
|
|
14254
14715
|
onClick: backspace,
|
|
14255
14716
|
"aria-label": "Borrar",
|
|
14256
14717
|
children: /* @__PURE__ */ jsxRuntime.jsx(Delete, { size: 19 })
|
|
@@ -14424,6 +14885,22 @@ var ERROR_TEXT = {
|
|
|
14424
14885
|
// thinks they dropped it will hang up on a client to "fix" it.
|
|
14425
14886
|
"call/transfer-failed": { title: "No se pudo transferir. Sigues en la llamada" },
|
|
14426
14887
|
"call/unsupported": { title: "Esta funci\xF3n no est\xE1 disponible" },
|
|
14888
|
+
// ── Line owner ──────────────────────────────────────────────────────────────
|
|
14889
|
+
/*
|
|
14890
|
+
Not call outcomes: they answer "Recibir aquí" on `LineGate`, which has ONE line for them where the
|
|
14891
|
+
explanation was, so it reads the pair as two sentences — `${title}. ${body}`. Split anyway, so the
|
|
14892
|
+
title stands alone wherever there is only room for a headline (a host's own toast), under the same
|
|
14893
|
+
one-line rules as every other non-fatal title. "Allí" because the gate has just said where: the
|
|
14894
|
+
other softphone, the one that has the calls.
|
|
14895
|
+
*/
|
|
14896
|
+
"line/claim-refused": {
|
|
14897
|
+
title: "Est\xE1s en una llamada all\xED",
|
|
14898
|
+
body: "Podr\xE1s recibirlas aqu\xED cuando cuelgues."
|
|
14899
|
+
},
|
|
14900
|
+
"line/unavailable": {
|
|
14901
|
+
title: "No se ha podido cambiar",
|
|
14902
|
+
body: "Int\xE9ntalo de nuevo."
|
|
14903
|
+
},
|
|
14427
14904
|
// ── Provider ────────────────────────────────────────────────────────────────
|
|
14428
14905
|
"provider/provision-failed": {
|
|
14429
14906
|
title: "No se pudo activar el tel\xE9fono",
|
|
@@ -14675,6 +15152,9 @@ function CallScreen({
|
|
|
14675
15152
|
}) {
|
|
14676
15153
|
const view = useCall(call);
|
|
14677
15154
|
const softphone = useSoftphone();
|
|
15155
|
+
const outbound = useOutboundNumbers(call.direction === "outbound" ? outboundNumberStore(softphone) : null).settings;
|
|
15156
|
+
const formatIntl2 = useIntlFormatter();
|
|
15157
|
+
const fromName = outbound && outbound.numbers.length > 1 ? numberName(effectiveNumber(outbound), formatIntl2) : null;
|
|
14678
15158
|
const [panel, setPanel] = React.useState(null);
|
|
14679
15159
|
const showTransfer = panel === "transfer";
|
|
14680
15160
|
const showKeypad = panel === "keypad";
|
|
@@ -14885,6 +15365,13 @@ function CallScreen({
|
|
|
14885
15365
|
] }),
|
|
14886
15366
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-call-name", children: name }),
|
|
14887
15367
|
showNumber ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-call-number", children: number }) : null,
|
|
15368
|
+
fromName ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-call-from", children: [
|
|
15369
|
+
/* @__PURE__ */ jsxRuntime.jsx(PhoneOutgoing, { size: 11, "aria-hidden": true }),
|
|
15370
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
15371
|
+
"Desde ",
|
|
15372
|
+
fromName
|
|
15373
|
+
] })
|
|
15374
|
+
] }) : null,
|
|
14888
15375
|
s === "active" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-timer", "aria-label": "Duraci\xF3n de la llamada", children: [
|
|
14889
15376
|
/* @__PURE__ */ jsxRuntime.jsx("b", { children: mm }),
|
|
14890
15377
|
/* @__PURE__ */ jsxRuntime.jsx("i", { children: ":" }),
|
|
@@ -15300,12 +15787,23 @@ function HandlerChain({ handlers }) {
|
|
|
15300
15787
|
] }, `${h.extension}-${i}`);
|
|
15301
15788
|
}) });
|
|
15302
15789
|
}
|
|
15790
|
+
function outboundFrom(entry, known, formatIntl2) {
|
|
15791
|
+
if (entry.direction !== "outbound" || !entry.from) return null;
|
|
15792
|
+
const number = formatIntl2(`+${entry.from}`);
|
|
15793
|
+
const label = known?.numbers.find((n) => n.number === entry.from)?.label;
|
|
15794
|
+
if (!label) return number;
|
|
15795
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
15796
|
+
label,
|
|
15797
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-detail-row-sub", children: number })
|
|
15798
|
+
] });
|
|
15799
|
+
}
|
|
15303
15800
|
function HistoryDetail({
|
|
15304
15801
|
entry,
|
|
15305
15802
|
onBack
|
|
15306
15803
|
}) {
|
|
15307
15804
|
const softphone = useSoftphone();
|
|
15308
15805
|
const formatIntl2 = useIntlFormatter();
|
|
15806
|
+
const knownNumbers = useKnownOutboundNumbers(outboundNumberStore(softphone));
|
|
15309
15807
|
const [detail, setDetail] = React.useState(null);
|
|
15310
15808
|
const [loadingDetail, setLoadingDetail] = React.useState(false);
|
|
15311
15809
|
React.useEffect(() => {
|
|
@@ -15321,6 +15819,7 @@ function HistoryDetail({
|
|
|
15321
15819
|
}, [entry]);
|
|
15322
15820
|
const formatted = formatIntl2(entry.number);
|
|
15323
15821
|
const name = entry.name ?? formatted;
|
|
15822
|
+
const from = outboundFrom(entry, knownNumbers, formatIntl2);
|
|
15324
15823
|
const handledBy = detail?.handledBy ?? entry.handledBy;
|
|
15325
15824
|
const recordingUrls = detail?.recordingUrls ?? [];
|
|
15326
15825
|
const loadingRecording = entry.recorded && loadingDetail && recordingUrls.length === 0;
|
|
@@ -15357,7 +15856,8 @@ function HistoryDetail({
|
|
|
15357
15856
|
entry.durationSec > 0 ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Timer, eyebrow: "Duraci\xF3n", text: formatDuration(entry.durationSec) }) : null,
|
|
15358
15857
|
/* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Phone, eyebrow: "N\xFAmero", text: formatted }),
|
|
15359
15858
|
handledBy.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: User, eyebrow: handlerLabel, text: /* @__PURE__ */ jsxRuntime.jsx(HandlerChain, { handlers: handledBy }) }) : null,
|
|
15360
|
-
entry.direction === "inbound" && entry.did ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Hash, eyebrow: "N\xFAmero marcado (DID)", text: formatIntl2(entry.did) }) : null
|
|
15859
|
+
entry.direction === "inbound" && entry.did ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Hash, eyebrow: "N\xFAmero marcado (DID)", text: formatIntl2(entry.did) }) : null,
|
|
15860
|
+
from ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: PhoneOutgoing, eyebrow: "Llamada desde", text: from }) : null
|
|
15361
15861
|
] })
|
|
15362
15862
|
] }),
|
|
15363
15863
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-detail-foot", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -15552,14 +16052,14 @@ function HistoryTab({ source }) {
|
|
|
15552
16052
|
}
|
|
15553
16053
|
|
|
15554
16054
|
// src/ui/audio/ringtone-store.ts
|
|
15555
|
-
var
|
|
16055
|
+
var stores2 = /* @__PURE__ */ new WeakMap();
|
|
15556
16056
|
function ringtoneStore(softphone) {
|
|
15557
16057
|
const source = softphone.sources?.ringtones;
|
|
15558
16058
|
if (!source) return null;
|
|
15559
|
-
let store =
|
|
16059
|
+
let store = stores2.get(softphone);
|
|
15560
16060
|
if (!store) {
|
|
15561
16061
|
store = createRingtoneStore(source);
|
|
15562
|
-
|
|
16062
|
+
stores2.set(softphone, store);
|
|
15563
16063
|
}
|
|
15564
16064
|
return store;
|
|
15565
16065
|
}
|
|
@@ -15685,17 +16185,19 @@ function ChoiceList({
|
|
|
15685
16185
|
choices,
|
|
15686
16186
|
value,
|
|
15687
16187
|
onSelect,
|
|
15688
|
-
onReselect
|
|
16188
|
+
onReselect,
|
|
16189
|
+
disabled = false
|
|
15689
16190
|
}) {
|
|
15690
16191
|
const name = React.useId();
|
|
15691
16192
|
const pointer = React.useRef(false);
|
|
15692
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-choices", role: "radiogroup", "aria-label": label, children: choices.map((c) => {
|
|
16193
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-choices", role: "radiogroup", "aria-label": label, "aria-disabled": disabled || void 0, children: choices.map((c) => {
|
|
15693
16194
|
const checked = c.value === value;
|
|
15694
16195
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
15695
16196
|
"label",
|
|
15696
16197
|
{
|
|
15697
16198
|
className: "cv-choice",
|
|
15698
16199
|
"data-checked": checked || void 0,
|
|
16200
|
+
"data-disabled": disabled || void 0,
|
|
15699
16201
|
onPointerDown: () => {
|
|
15700
16202
|
pointer.current = true;
|
|
15701
16203
|
},
|
|
@@ -15711,19 +16213,23 @@ function ChoiceList({
|
|
|
15711
16213
|
onChange: () => {
|
|
15712
16214
|
const via = pointer.current ? "pointer" : "keyboard";
|
|
15713
16215
|
pointer.current = false;
|
|
15714
|
-
onSelect(c.value, via);
|
|
16216
|
+
if (!disabled) onSelect(c.value, via);
|
|
15715
16217
|
},
|
|
15716
16218
|
onClick: () => {
|
|
15717
16219
|
if (c.value === value) {
|
|
15718
16220
|
pointer.current = false;
|
|
15719
|
-
onReselect?.(c.value);
|
|
16221
|
+
if (!disabled) onReselect?.(c.value);
|
|
15720
16222
|
}
|
|
15721
16223
|
}
|
|
15722
16224
|
}
|
|
15723
16225
|
),
|
|
15724
16226
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-choice-text", children: [
|
|
15725
|
-
/* @__PURE__ */ jsxRuntime.
|
|
15726
|
-
|
|
16227
|
+
c.tag ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-choice-line", children: [
|
|
16228
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-label", children: c.label }),
|
|
16229
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-tag", children: c.tag })
|
|
16230
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-label", children: c.label }),
|
|
16231
|
+
c.sub ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-sub", children: c.sub }) : null,
|
|
16232
|
+
c.hint ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-hint", children: c.hint }) : null
|
|
15727
16233
|
] }),
|
|
15728
16234
|
c.trailing,
|
|
15729
16235
|
/* @__PURE__ */ jsxRuntime.jsx(Check, { size: 16, className: "cv-choice-check", "aria-hidden": true })
|
|
@@ -15885,11 +16391,11 @@ function previewRingtone(ringtone, options = {}) {
|
|
|
15885
16391
|
}
|
|
15886
16392
|
|
|
15887
16393
|
// src/ui/views/settings/ringtone-state.ts
|
|
15888
|
-
var
|
|
16394
|
+
var noSubscribe2 = () => () => {
|
|
15889
16395
|
};
|
|
15890
|
-
var
|
|
16396
|
+
var noSettings2 = () => null;
|
|
15891
16397
|
function useRingtoneSettings(store) {
|
|
15892
|
-
const settings = React.useSyncExternalStore(store?.subscribe ??
|
|
16398
|
+
const settings = React.useSyncExternalStore(store?.subscribe ?? noSubscribe2, store?.getSettings ?? noSettings2);
|
|
15893
16399
|
const [failed, setFailed] = React.useState(false);
|
|
15894
16400
|
const load = React.useCallback(() => {
|
|
15895
16401
|
if (!store) return () => {
|
|
@@ -16090,11 +16596,109 @@ function useSerialSave(store, handlers) {
|
|
|
16090
16596
|
});
|
|
16091
16597
|
return save;
|
|
16092
16598
|
}
|
|
16599
|
+
var ORG_DEFAULT2 = "";
|
|
16600
|
+
var KEYBOARD_SETTLE_MS2 = 800;
|
|
16601
|
+
function OutboundNumberPage({
|
|
16602
|
+
store,
|
|
16603
|
+
settings,
|
|
16604
|
+
onBack
|
|
16605
|
+
}) {
|
|
16606
|
+
const formatIntl2 = useIntlFormatter();
|
|
16607
|
+
const current = numberName(effectiveNumber(settings), formatIntl2);
|
|
16608
|
+
const change = useOutboundNumberChange(store, settings, current);
|
|
16609
|
+
const [pending, setPending] = React.useState(void 0);
|
|
16610
|
+
const settle = React.useRef(null);
|
|
16611
|
+
const choose = React.useRef(change.choose);
|
|
16612
|
+
choose.current = change.choose;
|
|
16613
|
+
React.useEffect(
|
|
16614
|
+
() => () => {
|
|
16615
|
+
if (settle.current) clearTimeout(settle.current);
|
|
16616
|
+
},
|
|
16617
|
+
[]
|
|
16618
|
+
);
|
|
16619
|
+
const notice = React.useRef(null);
|
|
16620
|
+
const apply = async (value) => {
|
|
16621
|
+
setPending(value);
|
|
16622
|
+
const applied = await choose.current(value || null);
|
|
16623
|
+
setPending(void 0);
|
|
16624
|
+
if (!applied) requestAnimationFrame(() => notice.current?.scrollIntoView?.({ block: "nearest" }));
|
|
16625
|
+
};
|
|
16626
|
+
const onSelect = (value, via) => {
|
|
16627
|
+
if (settle.current) clearTimeout(settle.current);
|
|
16628
|
+
settle.current = null;
|
|
16629
|
+
if (via === "pointer") {
|
|
16630
|
+
void apply(value);
|
|
16631
|
+
return;
|
|
16632
|
+
}
|
|
16633
|
+
setPending(value);
|
|
16634
|
+
settle.current = setTimeout(() => {
|
|
16635
|
+
settle.current = null;
|
|
16636
|
+
void apply(value);
|
|
16637
|
+
}, KEYBOARD_SETTLE_MS2);
|
|
16638
|
+
};
|
|
16639
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16640
|
+
SettingsPage,
|
|
16641
|
+
{
|
|
16642
|
+
title: "N\xFAmero de salida",
|
|
16643
|
+
intro: "El n\xFAmero que ve la persona a la que llamas. Es el mismo en todos tus dispositivos y tambi\xE9n puedes cambiarlo desde el teclado.",
|
|
16644
|
+
onBack,
|
|
16645
|
+
children: [
|
|
16646
|
+
change.message ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-settings-notice", role: "status", "data-kind": change.isError ? "error" : "info", ref: notice, children: change.message }) : null,
|
|
16647
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16648
|
+
ChoiceList,
|
|
16649
|
+
{
|
|
16650
|
+
label: "N\xFAmero de salida",
|
|
16651
|
+
choices: choicesFor2(settings, formatIntl2, change.busy ? pending : void 0),
|
|
16652
|
+
value: pending ?? currentValue2(settings),
|
|
16653
|
+
onSelect,
|
|
16654
|
+
disabled: change.locked
|
|
16655
|
+
}
|
|
16656
|
+
)
|
|
16657
|
+
]
|
|
16658
|
+
}
|
|
16659
|
+
);
|
|
16660
|
+
}
|
|
16661
|
+
function currentValue2(settings) {
|
|
16662
|
+
return settings.mine && settings.numbers.some((n) => n.id === settings.mine) ? settings.mine : ORG_DEFAULT2;
|
|
16663
|
+
}
|
|
16664
|
+
function choicesFor2(settings, formatIntl2, applying) {
|
|
16665
|
+
const orgDefault = settings.numbers.find((n) => n.id === settings.orgDefault);
|
|
16666
|
+
const spinner = (value) => applying === value ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-choice-applying", children: [
|
|
16667
|
+
/* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { size: 15, className: "cv-choice-applying-icon", "aria-hidden": true }),
|
|
16668
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-sr-only", children: "Aplicando" })
|
|
16669
|
+
] }) : void 0;
|
|
16670
|
+
return [
|
|
16671
|
+
{
|
|
16672
|
+
value: ORG_DEFAULT2,
|
|
16673
|
+
label: "Por defecto de la organizaci\xF3n",
|
|
16674
|
+
sub: orgDefault ? numberName(orgDefault, formatIntl2) : "Sin n\xFAmero por defecto",
|
|
16675
|
+
trailing: spinner(ORG_DEFAULT2)
|
|
16676
|
+
},
|
|
16677
|
+
...settings.numbers.map((n) => {
|
|
16678
|
+
const kind = KIND_TAG[n.kind];
|
|
16679
|
+
return {
|
|
16680
|
+
value: n.id,
|
|
16681
|
+
label: numberName(n, formatIntl2),
|
|
16682
|
+
...n.label ? { sub: formatIntl2(`+${n.number}`) } : {},
|
|
16683
|
+
...kind ? { tag: kind.tag, hint: kind.hint } : {},
|
|
16684
|
+
trailing: spinner(n.id)
|
|
16685
|
+
};
|
|
16686
|
+
})
|
|
16687
|
+
];
|
|
16688
|
+
}
|
|
16689
|
+
function outboundSummary(settings, formatIntl2) {
|
|
16690
|
+
const effective = effectiveNumber(settings);
|
|
16691
|
+
const name = numberName(effective, formatIntl2);
|
|
16692
|
+
return settings.mine || !effective ? name : `${name} \xB7 organizaci\xF3n`;
|
|
16693
|
+
}
|
|
16093
16694
|
function SettingsTab() {
|
|
16094
16695
|
const softphone = useSoftphone();
|
|
16095
16696
|
const devices = useDevices();
|
|
16096
16697
|
const store = ringtoneStore(softphone);
|
|
16097
16698
|
const ringtone = useRingtoneSettings(store);
|
|
16699
|
+
const outStore = outboundNumberStore(softphone);
|
|
16700
|
+
const outbound = useOutboundNumbers(outStore).settings;
|
|
16701
|
+
const formatIntl2 = useIntlFormatter();
|
|
16098
16702
|
const [page, setPage] = React.useState(null);
|
|
16099
16703
|
const list = React.useRef(null);
|
|
16100
16704
|
const rows = React.useRef({});
|
|
@@ -16129,6 +16733,16 @@ function SettingsTab() {
|
|
|
16129
16733
|
rowRef: rowRef("tone")
|
|
16130
16734
|
}
|
|
16131
16735
|
) }) : null,
|
|
16736
|
+
outStore && outbound && outbound.numbers.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(SettingsSection, { title: "Llamadas salientes", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
16737
|
+
SettingsNavRow,
|
|
16738
|
+
{
|
|
16739
|
+
icon: PhoneOutgoing,
|
|
16740
|
+
title: "N\xFAmero de salida",
|
|
16741
|
+
value: outboundSummary(outbound, formatIntl2),
|
|
16742
|
+
onOpen: open("outbound"),
|
|
16743
|
+
rowRef: rowRef("outbound")
|
|
16744
|
+
}
|
|
16745
|
+
) }) : null,
|
|
16132
16746
|
/* @__PURE__ */ jsxRuntime.jsxs(SettingsSection, { title: "Dispositivos", children: [
|
|
16133
16747
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16134
16748
|
SettingsNavRow,
|
|
@@ -16182,7 +16796,7 @@ function SettingsTab() {
|
|
|
16182
16796
|
onRetry: ringtone.retry,
|
|
16183
16797
|
onBack: back
|
|
16184
16798
|
}
|
|
16185
|
-
) : page !== null && page !== "tone" ? /* @__PURE__ */ jsxRuntime.jsx(DevicePage, { kind: page, onBack: back }) : null
|
|
16799
|
+
) : page === "outbound" && outStore && outbound ? /* @__PURE__ */ jsxRuntime.jsx(OutboundNumberPage, { store: outStore, settings: outbound, onBack: back }) : page !== null && page !== "tone" && page !== "outbound" ? /* @__PURE__ */ jsxRuntime.jsx(DevicePage, { kind: page, onBack: back }) : null
|
|
16186
16800
|
] });
|
|
16187
16801
|
}
|
|
16188
16802
|
|
|
@@ -16379,6 +16993,40 @@ function LeaderGate() {
|
|
|
16379
16993
|
/* @__PURE__ */ jsxRuntime.jsx("p", { part: "leader-body", children: "Solo una pesta\xF1a puede usar el tel\xE9fono a la vez. Cierra la otra para activar esta." })
|
|
16380
16994
|
] });
|
|
16381
16995
|
}
|
|
16996
|
+
var BODY = "Las llamadas entran all\xED. Si prefieres recibirlas aqu\xED, pulsa \xABRecibir aqu\xED\xBB.";
|
|
16997
|
+
var sentence = (code) => `${ERROR_TEXT[code].title}. ${ERROR_TEXT[code].body ?? ""}`.trim();
|
|
16998
|
+
function LineGate() {
|
|
16999
|
+
const softphone = useSoftphone();
|
|
17000
|
+
const { where, claim } = useLine();
|
|
17001
|
+
const take = () => {
|
|
17002
|
+
softphone.takeLine().catch(() => {
|
|
17003
|
+
});
|
|
17004
|
+
};
|
|
17005
|
+
const body = claim === "refused" ? sentence("line/claim-refused") : claim === "failed" ? sentence("line/unavailable") : BODY;
|
|
17006
|
+
return (
|
|
17007
|
+
/*
|
|
17008
|
+
`part` on all four, like `ErrorGate`: it replaces the whole dialpad, and inside `<coventia-softphone>`
|
|
17009
|
+
a shadow root makes it otherwise unreachable from the host's CSS. `data-claim` is for the light-DOM
|
|
17010
|
+
`/ui` path and for debugging — it cannot cross the shadow boundary either.
|
|
17011
|
+
*/
|
|
17012
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-gate", "data-claim": claim, part: "line-gate", children: [
|
|
17013
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-gate-mark", children: /* @__PURE__ */ jsxRuntime.jsx(PhoneForwarded, { size: 22 }) }),
|
|
17014
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "cv-gate-title", part: "line-title", children: where === "desktop" ? "Tu tel\xE9fono est\xE1 activo en la app de escritorio" : "Tu tel\xE9fono est\xE1 activo en otro sitio" }),
|
|
17015
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", part: "line-body", children: body }),
|
|
17016
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17017
|
+
"button",
|
|
17018
|
+
{
|
|
17019
|
+
className: "cv-btn cv-btn--accent",
|
|
17020
|
+
onClick: take,
|
|
17021
|
+
disabled: claim === "claiming",
|
|
17022
|
+
"aria-busy": claim === "claiming",
|
|
17023
|
+
part: "line-claim",
|
|
17024
|
+
children: claim === "claiming" ? "Cambiando\u2026" : "Recibir aqu\xED"
|
|
17025
|
+
}
|
|
17026
|
+
)
|
|
17027
|
+
] })
|
|
17028
|
+
);
|
|
17029
|
+
}
|
|
16382
17030
|
var BACKSTOP_MS = 6e3;
|
|
16383
17031
|
function useCallOutcome() {
|
|
16384
17032
|
const softphone = useSoftphone();
|
|
@@ -16610,11 +17258,12 @@ function SoftphoneWidget(props) {
|
|
|
16610
17258
|
const isLeader = useIsLeader();
|
|
16611
17259
|
const softphoneState = useSoftphoneState();
|
|
16612
17260
|
const isFollower = !isLeader && softphoneState === "starting";
|
|
17261
|
+
const isStandby = softphoneState === "standby";
|
|
16613
17262
|
const mic = useMicPermission();
|
|
16614
17263
|
useCallAudio(sounds);
|
|
16615
17264
|
const blocked = requireMic && mic.state !== "ready";
|
|
16616
17265
|
const fatal = useLastError();
|
|
16617
|
-
const screenIsFree = !call && !blocked && !fatal && !isFollower;
|
|
17266
|
+
const screenIsFree = !call && !blocked && !fatal && !isFollower && !isStandby;
|
|
16618
17267
|
const { outcome, clear: clearOutcome } = useCallOutcome();
|
|
16619
17268
|
const { notices: transferNotices, dismiss: dismissTransferNotice } = useTransferNotices();
|
|
16620
17269
|
const retry = () => {
|
|
@@ -16641,7 +17290,7 @@ function SoftphoneWidget(props) {
|
|
|
16641
17290
|
...theme === "system" ? {} : { "data-cv-theme": theme },
|
|
16642
17291
|
children: [
|
|
16643
17292
|
screenIsFree ? /* @__PURE__ */ jsxRuntime.jsx(TransferNotices, { notices: transferNotices, dismiss: dismissTransferNotice }) : null,
|
|
16644
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ jsxRuntime.jsx(LeaderGate, {}) : blocked ? /* @__PURE__ */ jsxRuntime.jsx(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ jsxRuntime.jsx(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ jsxRuntime.jsx(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ jsxRuntime.jsx(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ jsxRuntime.jsx(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ jsxRuntime.jsx(SettingsTab, {}) : /* @__PURE__ */ jsxRuntime.jsx(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
|
|
17293
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ jsxRuntime.jsx(LeaderGate, {}) : isStandby && !call ? /* @__PURE__ */ jsxRuntime.jsx(LineGate, {}) : blocked ? /* @__PURE__ */ jsxRuntime.jsx(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ jsxRuntime.jsx(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ jsxRuntime.jsx(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ jsxRuntime.jsx(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ jsxRuntime.jsx(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ jsxRuntime.jsx(SettingsTab, {}) : /* @__PURE__ */ jsxRuntime.jsx(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
|
|
16645
17294
|
screenIsFree ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-tabs", role: "tablist", children: tabs.map(({ id, icon: Icon3, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16646
17295
|
"button",
|
|
16647
17296
|
{
|
|
@@ -16687,6 +17336,7 @@ lucide-react/dist/esm/icons/mic.js:
|
|
|
16687
17336
|
lucide-react/dist/esm/icons/monitor-smartphone.js:
|
|
16688
17337
|
lucide-react/dist/esm/icons/music.js:
|
|
16689
17338
|
lucide-react/dist/esm/icons/pause.js:
|
|
17339
|
+
lucide-react/dist/esm/icons/phone-forwarded.js:
|
|
16690
17340
|
lucide-react/dist/esm/icons/phone-incoming.js:
|
|
16691
17341
|
lucide-react/dist/esm/icons/phone-missed.js:
|
|
16692
17342
|
lucide-react/dist/esm/icons/phone-off.js:
|