@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.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isWebRtcSupported } from '../chunk-KAXEMJ7E.js';
|
|
2
|
-
import { useSoftphone, useSoftphoneState, useCall, usePresence, useActiveCall, useIsLeader, useLastError, useDevices } from '../chunk-
|
|
3
|
-
import '../chunk-
|
|
2
|
+
import { useSoftphone, useSoftphoneState, useCall, usePresence, useActiveCall, useIsLeader, useLastError, useDevices, useLine } from '../chunk-I3RSC52R.js';
|
|
3
|
+
import '../chunk-N4PLEYFJ.js';
|
|
4
|
+
import { isOutboundNumberRefused } from '../chunk-5E5OEXS2.js';
|
|
4
5
|
import { forwardRef, createElement, useState, useRef, useLayoutEffect, useEffect, useCallback, useMemo, useSyncExternalStore, useId } from 'react';
|
|
5
6
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
7
|
|
|
@@ -224,6 +225,19 @@ var Pause = createLucideIcon("Pause", [
|
|
|
224
225
|
["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
|
|
225
226
|
]);
|
|
226
227
|
|
|
228
|
+
// ../../node_modules/lucide-react/dist/esm/icons/phone-forwarded.js
|
|
229
|
+
var PhoneForwarded = createLucideIcon("PhoneForwarded", [
|
|
230
|
+
["polyline", { points: "18 2 22 6 18 10", key: "6vjanh" }],
|
|
231
|
+
["line", { x1: "14", x2: "22", y1: "6", y2: "6", key: "1jsywh" }],
|
|
232
|
+
[
|
|
233
|
+
"path",
|
|
234
|
+
{
|
|
235
|
+
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",
|
|
236
|
+
key: "foiqr5"
|
|
237
|
+
}
|
|
238
|
+
]
|
|
239
|
+
]);
|
|
240
|
+
|
|
227
241
|
// ../../node_modules/lucide-react/dist/esm/icons/phone-incoming.js
|
|
228
242
|
var PhoneIncoming = createLucideIcon("PhoneIncoming", [
|
|
229
243
|
["polyline", { points: "16 2 16 8 22 8", key: "1ygljm" }],
|
|
@@ -367,6 +381,36 @@ var PACKED = "AC247 AD376 AE971 AF93 AG1 AI1 AL355 AM374 AO244 AR54 AS1 AT43 AU6
|
|
|
367
381
|
var DIAL_CODES = new Map(
|
|
368
382
|
PACKED.split(" ").map((token) => [token.slice(0, 2), Number(token.slice(2))])
|
|
369
383
|
);
|
|
384
|
+
var REGIONS_BY_CODE = (() => {
|
|
385
|
+
const map = /* @__PURE__ */ new Map();
|
|
386
|
+
for (const [region, code] of DIAL_CODES) map.set(code, [...map.get(code) ?? [], region]);
|
|
387
|
+
return map;
|
|
388
|
+
})();
|
|
389
|
+
var MAIN_REGION = {
|
|
390
|
+
1: "US",
|
|
391
|
+
7: "RU",
|
|
392
|
+
39: "IT",
|
|
393
|
+
44: "GB",
|
|
394
|
+
47: "NO",
|
|
395
|
+
61: "AU",
|
|
396
|
+
212: "MA",
|
|
397
|
+
262: "RE",
|
|
398
|
+
290: "SH",
|
|
399
|
+
358: "FI",
|
|
400
|
+
590: "GP",
|
|
401
|
+
599: "CW"
|
|
402
|
+
};
|
|
403
|
+
function regionOfInternational(typed) {
|
|
404
|
+
const text = String(typed ?? "").trim();
|
|
405
|
+
if (!text.startsWith("+")) return null;
|
|
406
|
+
const digits = text.slice(1).replace(/\D/g, "");
|
|
407
|
+
for (let length = 1; length <= Math.min(3, digits.length); length++) {
|
|
408
|
+
const code = Number(digits.slice(0, length));
|
|
409
|
+
const regions = REGIONS_BY_CODE.get(code);
|
|
410
|
+
if (regions) return MAIN_REGION[code] ?? regions[0] ?? null;
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
370
414
|
var DEFAULT_COUNTRY = "ES";
|
|
371
415
|
var REGION_NAMES = (() => {
|
|
372
416
|
try {
|
|
@@ -418,12 +462,16 @@ function CountryMark({ code }) {
|
|
|
418
462
|
}
|
|
419
463
|
function CountryPicker({
|
|
420
464
|
value,
|
|
421
|
-
onChange
|
|
465
|
+
onChange,
|
|
466
|
+
fromNumber = false
|
|
422
467
|
}) {
|
|
423
468
|
const [open, setOpen] = useState(false);
|
|
424
469
|
const [query, setQuery] = useState("");
|
|
425
470
|
const rootRef = useRef(null);
|
|
426
471
|
const inputRef = useRef(null);
|
|
472
|
+
useEffect(() => {
|
|
473
|
+
if (fromNumber) setOpen(false);
|
|
474
|
+
}, [fromNumber]);
|
|
427
475
|
const results = useMemo(() => {
|
|
428
476
|
const q = query.trim().toLowerCase().replace(/^\+/, "");
|
|
429
477
|
if (!q) return COUNTRIES;
|
|
@@ -452,6 +500,12 @@ function CountryPicker({
|
|
|
452
500
|
else setQuery("");
|
|
453
501
|
}, [open]);
|
|
454
502
|
const dial = DIAL_CODES.get(value);
|
|
503
|
+
if (fromNumber) {
|
|
504
|
+
return /* @__PURE__ */ jsx("div", { className: "cv-cp", ref: rootRef, children: /* @__PURE__ */ jsxs("span", { className: "cv-cp-chip", "data-from-number": true, title: "El prefijo viene del n\xFAmero marcado", part: "country-chip", children: [
|
|
505
|
+
/* @__PURE__ */ jsx(CountryMark, { code: value || "+" }),
|
|
506
|
+
/* @__PURE__ */ jsx("span", { className: "cv-sr-only", children: dial ? `Prefijo del n\xFAmero marcado: ${countryName(value)} (+${dial})` : "Prefijo del n\xFAmero marcado" })
|
|
507
|
+
] }) });
|
|
508
|
+
}
|
|
455
509
|
return /* @__PURE__ */ jsxs("div", { className: "cv-cp", ref: rootRef, children: [
|
|
456
510
|
/* @__PURE__ */ jsxs(
|
|
457
511
|
"button",
|
|
@@ -1018,18 +1072,378 @@ function playRingback() {
|
|
|
1018
1072
|
}
|
|
1019
1073
|
};
|
|
1020
1074
|
}
|
|
1075
|
+
var stores = /* @__PURE__ */ new WeakMap();
|
|
1076
|
+
function outboundNumberStore(softphone) {
|
|
1077
|
+
const source = softphone.sources?.outboundNumbers;
|
|
1078
|
+
if (!source) return null;
|
|
1079
|
+
let store = stores.get(softphone);
|
|
1080
|
+
if (!store) {
|
|
1081
|
+
store = createOutboundNumberStore(source);
|
|
1082
|
+
stores.set(softphone, store);
|
|
1083
|
+
}
|
|
1084
|
+
return store;
|
|
1085
|
+
}
|
|
1086
|
+
function createOutboundNumberStore(source) {
|
|
1087
|
+
let settings = null;
|
|
1088
|
+
let inFlight = null;
|
|
1089
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1090
|
+
const apply = (next) => {
|
|
1091
|
+
settings = next;
|
|
1092
|
+
for (const l of listeners) l();
|
|
1093
|
+
};
|
|
1094
|
+
return {
|
|
1095
|
+
getSettings: () => settings,
|
|
1096
|
+
refresh() {
|
|
1097
|
+
inFlight ??= (async () => {
|
|
1098
|
+
if (!settings && source.cached) {
|
|
1099
|
+
const early = await source.cached().catch(() => null);
|
|
1100
|
+
if (early && !settings) apply(early);
|
|
1101
|
+
}
|
|
1102
|
+
try {
|
|
1103
|
+
apply(await source.get());
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
console.warn("[softphone] outbound numbers could not be loaded; keeping the previous ones", error);
|
|
1106
|
+
} finally {
|
|
1107
|
+
inFlight = null;
|
|
1108
|
+
}
|
|
1109
|
+
})();
|
|
1110
|
+
return inFlight;
|
|
1111
|
+
},
|
|
1112
|
+
async setMine(id) {
|
|
1113
|
+
apply(await source.setMine(id));
|
|
1114
|
+
},
|
|
1115
|
+
subscribe(listener) {
|
|
1116
|
+
listeners.add(listener);
|
|
1117
|
+
return () => listeners.delete(listener);
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
var noSubscribe = () => () => {
|
|
1122
|
+
};
|
|
1123
|
+
var noSettings = () => null;
|
|
1124
|
+
function useOutboundNumbers(store) {
|
|
1125
|
+
const settings = useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
|
|
1126
|
+
const [failed, setFailed] = useState(false);
|
|
1127
|
+
const load = useCallback(() => {
|
|
1128
|
+
if (!store) return () => {
|
|
1129
|
+
};
|
|
1130
|
+
let live = true;
|
|
1131
|
+
setFailed(false);
|
|
1132
|
+
void store.refresh().then(() => {
|
|
1133
|
+
if (live) setFailed(store.getSettings() === null);
|
|
1134
|
+
});
|
|
1135
|
+
return () => {
|
|
1136
|
+
live = false;
|
|
1137
|
+
};
|
|
1138
|
+
}, [store]);
|
|
1139
|
+
useEffect(load, [load]);
|
|
1140
|
+
return { settings, failed, retry: load };
|
|
1141
|
+
}
|
|
1142
|
+
function useKnownOutboundNumbers(store) {
|
|
1143
|
+
return useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
|
|
1144
|
+
}
|
|
1145
|
+
function useCountdown(until) {
|
|
1146
|
+
const [, setTick] = useState(0);
|
|
1147
|
+
useEffect(() => {
|
|
1148
|
+
if (!until) return void 0;
|
|
1149
|
+
const timer = setInterval(() => {
|
|
1150
|
+
setTick((n) => n + 1);
|
|
1151
|
+
if (secondsLeft(until) === 0) clearInterval(timer);
|
|
1152
|
+
}, 1e3);
|
|
1153
|
+
return () => clearInterval(timer);
|
|
1154
|
+
}, [until]);
|
|
1155
|
+
return secondsLeft(until);
|
|
1156
|
+
}
|
|
1157
|
+
function secondsLeft(until) {
|
|
1158
|
+
return until ? Math.max(0, Math.ceil((until - Date.now()) / 1e3)) : 0;
|
|
1159
|
+
}
|
|
1160
|
+
function numberName(n, format = formatIntl) {
|
|
1161
|
+
if (!n) return "N\xFAmero de la organizaci\xF3n";
|
|
1162
|
+
return n.label || format(`+${n.number}`);
|
|
1163
|
+
}
|
|
1164
|
+
function effectiveNumber(settings) {
|
|
1165
|
+
return settings?.numbers.find((n) => n.id === settings.effective);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/ui/outbound/outbound-change.ts
|
|
1169
|
+
var KIND_TAG = {
|
|
1170
|
+
mobile: { tag: "M\xF3vil", hint: "No usar para atenci\xF3n al cliente ni para llamadas comerciales no solicitadas." },
|
|
1171
|
+
commercial: { tag: "Comercial (400)", hint: "Solo para llamadas comerciales. No recibe llamadas." }
|
|
1172
|
+
};
|
|
1173
|
+
function noticeText(situation, seconds, current) {
|
|
1174
|
+
switch (situation) {
|
|
1175
|
+
case "platform":
|
|
1176
|
+
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}.`;
|
|
1177
|
+
case "user":
|
|
1178
|
+
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}.`;
|
|
1179
|
+
case "wait":
|
|
1180
|
+
return `Podr\xE1s cambiar el n\xFAmero de salida en ${seconds} s.`;
|
|
1181
|
+
case "pending":
|
|
1182
|
+
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.";
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
function useOutboundNumberChange(store, settings, current) {
|
|
1186
|
+
const [busy, setBusy] = useState(false);
|
|
1187
|
+
const [notice, setNotice] = useState(null);
|
|
1188
|
+
const [waitUntil, setWaitUntil] = useState(null);
|
|
1189
|
+
useEffect(() => {
|
|
1190
|
+
const s = settings?.changeAvailableInSeconds ?? 0;
|
|
1191
|
+
setWaitUntil(s > 0 ? Date.now() + s * 1e3 : null);
|
|
1192
|
+
}, [settings]);
|
|
1193
|
+
const refusedUntil = notice?.kind === "refused" ? notice.until : null;
|
|
1194
|
+
const blockedFor = useCountdown(refusedUntil ?? waitUntil);
|
|
1195
|
+
useEffect(() => {
|
|
1196
|
+
if (notice?.kind === "refused" && blockedFor === 0) setNotice(null);
|
|
1197
|
+
}, [notice, blockedFor]);
|
|
1198
|
+
const locked = busy || blockedFor > 0;
|
|
1199
|
+
const choose = async (id) => {
|
|
1200
|
+
if (!store || !settings || locked) return false;
|
|
1201
|
+
if (id === settings.mine) return true;
|
|
1202
|
+
setBusy(true);
|
|
1203
|
+
setNotice(null);
|
|
1204
|
+
try {
|
|
1205
|
+
await store.setMine(id);
|
|
1206
|
+
return true;
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
setNotice(
|
|
1209
|
+
isOutboundNumberRefused(error) ? { kind: "refused", reason: error.refusal.reason, until: Date.now() + error.refusal.retryAfterSeconds * 1e3 } : { kind: "error", text: errorText(error) }
|
|
1210
|
+
);
|
|
1211
|
+
return false;
|
|
1212
|
+
} finally {
|
|
1213
|
+
setBusy(false);
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
return {
|
|
1217
|
+
busy,
|
|
1218
|
+
locked,
|
|
1219
|
+
choose,
|
|
1220
|
+
/** What to say about the choice right now, if anything. */
|
|
1221
|
+
message: settings ? currentMessage(notice, settings.state, blockedFor, current) : null,
|
|
1222
|
+
/** The message is an error (the change failed), not information (wait, pending). */
|
|
1223
|
+
isError: notice?.kind === "error"
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
function currentMessage(notice, state, blockedFor, current) {
|
|
1227
|
+
if (notice?.kind === "refused") return blockedFor > 0 ? noticeText(notice.reason, blockedFor, current) : null;
|
|
1228
|
+
if (notice?.kind === "error") return notice.text;
|
|
1229
|
+
if (state === "pending" || state === "failed") return noticeText("pending", blockedFor, current);
|
|
1230
|
+
return blockedFor > 0 ? noticeText("wait", blockedFor, current) : null;
|
|
1231
|
+
}
|
|
1232
|
+
function errorText(error) {
|
|
1233
|
+
const message = error instanceof Error ? error.message : "";
|
|
1234
|
+
return message && !/HTTP \d+/.test(message) ? message : "No se ha podido cambiar el n\xFAmero de salida. Int\xE9ntalo de nuevo.";
|
|
1235
|
+
}
|
|
1236
|
+
var CALLOUT_MS = 6e3;
|
|
1237
|
+
function OutboundNumberPicker({ onBusyChange }) {
|
|
1238
|
+
const softphone = useSoftphone();
|
|
1239
|
+
const store = outboundNumberStore(softphone);
|
|
1240
|
+
const { settings } = useOutboundNumbers(store);
|
|
1241
|
+
const formatIntl2 = useIntlFormatter();
|
|
1242
|
+
const current = settings ? numberName(effectiveNumber(settings), formatIntl2) : "";
|
|
1243
|
+
const { busy, locked, choose: apply, message, isError } = useOutboundNumberChange(store, settings, current);
|
|
1244
|
+
const [open, setOpen] = useState(false);
|
|
1245
|
+
const [applying, setApplying] = useState(void 0);
|
|
1246
|
+
const [callout, setCallout] = useState(false);
|
|
1247
|
+
const rootRef = useRef(null);
|
|
1248
|
+
const chipRef = useRef(null);
|
|
1249
|
+
const keys = useListKeyboard(open);
|
|
1250
|
+
useEffect(() => onBusyChange?.(busy), [busy, onBusyChange]);
|
|
1251
|
+
useEffect(() => {
|
|
1252
|
+
if (!message) setCallout(false);
|
|
1253
|
+
}, [message]);
|
|
1254
|
+
useEffect(() => {
|
|
1255
|
+
if (!callout) return void 0;
|
|
1256
|
+
const timer = setTimeout(() => setCallout(false), CALLOUT_MS);
|
|
1257
|
+
return () => clearTimeout(timer);
|
|
1258
|
+
}, [callout]);
|
|
1259
|
+
const floating = open || callout;
|
|
1260
|
+
useEffect(() => {
|
|
1261
|
+
if (!floating) return;
|
|
1262
|
+
const close = () => {
|
|
1263
|
+
setOpen(false);
|
|
1264
|
+
setCallout(false);
|
|
1265
|
+
};
|
|
1266
|
+
const root = rootRef.current?.getRootNode() ?? document;
|
|
1267
|
+
const onDown = (e) => {
|
|
1268
|
+
if (!rootRef.current?.contains(e.target)) close();
|
|
1269
|
+
};
|
|
1270
|
+
const onKey = (e) => {
|
|
1271
|
+
if (e.key !== "Escape") return;
|
|
1272
|
+
if (rootRef.current?.contains(e.target)) chipRef.current?.focus();
|
|
1273
|
+
close();
|
|
1274
|
+
};
|
|
1275
|
+
root.addEventListener("pointerdown", onDown);
|
|
1276
|
+
document.addEventListener("keydown", onKey);
|
|
1277
|
+
return () => {
|
|
1278
|
+
root.removeEventListener("pointerdown", onDown);
|
|
1279
|
+
document.removeEventListener("keydown", onKey);
|
|
1280
|
+
};
|
|
1281
|
+
}, [floating]);
|
|
1282
|
+
if (!store || !settings || settings.numbers.length < 2) return null;
|
|
1283
|
+
const orgDefault = settings.numbers.find((n) => n.id === settings.orgDefault);
|
|
1284
|
+
const applyingNumber = applying === null ? orgDefault : settings.numbers.find((n) => n.id === applying);
|
|
1285
|
+
const shown = applying === void 0 ? current : numberName(applyingNumber, formatIntl2);
|
|
1286
|
+
const choose = async (id) => {
|
|
1287
|
+
if (locked) return;
|
|
1288
|
+
setOpen(false);
|
|
1289
|
+
chipRef.current?.focus();
|
|
1290
|
+
if (id === settings.mine) return;
|
|
1291
|
+
setCallout(false);
|
|
1292
|
+
setApplying(id);
|
|
1293
|
+
const applied = await apply(id);
|
|
1294
|
+
setApplying(void 0);
|
|
1295
|
+
if (!applied) setCallout(true);
|
|
1296
|
+
};
|
|
1297
|
+
return /* @__PURE__ */ jsxs("div", { className: "cv-on", ref: rootRef, children: [
|
|
1298
|
+
/* @__PURE__ */ jsxs(
|
|
1299
|
+
"button",
|
|
1300
|
+
{
|
|
1301
|
+
ref: chipRef,
|
|
1302
|
+
type: "button",
|
|
1303
|
+
className: "cv-on-chip",
|
|
1304
|
+
onClick: () => {
|
|
1305
|
+
setCallout(false);
|
|
1306
|
+
setOpen((o) => !o);
|
|
1307
|
+
},
|
|
1308
|
+
"aria-haspopup": "listbox",
|
|
1309
|
+
"aria-expanded": open,
|
|
1310
|
+
"aria-label": `Llamar desde: ${shown}${busy ? " (aplicando)" : ""}. Cambiar n\xFAmero de salida`,
|
|
1311
|
+
"aria-busy": busy,
|
|
1312
|
+
part: "outbound-number-chip",
|
|
1313
|
+
children: [
|
|
1314
|
+
/* @__PURE__ */ jsx("span", { className: "cv-on-label", children: "Llamar desde:" }),
|
|
1315
|
+
busy ? /* @__PURE__ */ jsx(LoaderCircle, { size: 13, className: "cv-on-icon cv-on-spin", "aria-hidden": true }) : null,
|
|
1316
|
+
/* @__PURE__ */ jsx("span", { className: "cv-on-current", children: shown }),
|
|
1317
|
+
/* @__PURE__ */ jsx(ChevronDown, { size: 13, className: "cv-cp-caret", "data-open": open, "aria-hidden": true })
|
|
1318
|
+
]
|
|
1319
|
+
}
|
|
1320
|
+
),
|
|
1321
|
+
open ? /* @__PURE__ */ jsxs(
|
|
1322
|
+
"div",
|
|
1323
|
+
{
|
|
1324
|
+
className: "cv-cp-pop cv-on-pop",
|
|
1325
|
+
role: "dialog",
|
|
1326
|
+
"aria-label": "Llamar desde",
|
|
1327
|
+
part: "outbound-number-list",
|
|
1328
|
+
onBlur: (e) => {
|
|
1329
|
+
if (!e.currentTarget.contains(e.relatedTarget)) setOpen(false);
|
|
1330
|
+
},
|
|
1331
|
+
children: [
|
|
1332
|
+
/* @__PURE__ */ jsx("p", { className: "cv-on-title", children: "Llamar desde" }),
|
|
1333
|
+
/* @__PURE__ */ jsxs("div", { className: "cv-cp-list", role: "listbox", "aria-disabled": locked, ref: keys.listRef, onKeyDown: keys.onKeyDown, children: [
|
|
1334
|
+
/* @__PURE__ */ jsx(
|
|
1335
|
+
OptionRow,
|
|
1336
|
+
{
|
|
1337
|
+
selected: settings.mine === null,
|
|
1338
|
+
disabled: locked,
|
|
1339
|
+
title: "Por defecto de la organizaci\xF3n",
|
|
1340
|
+
sub: orgDefault ? numberName(orgDefault, formatIntl2) : "Sin n\xFAmero por defecto",
|
|
1341
|
+
onSelect: () => void choose(null)
|
|
1342
|
+
}
|
|
1343
|
+
),
|
|
1344
|
+
settings.numbers.map((n) => /* @__PURE__ */ jsx(
|
|
1345
|
+
OptionRow,
|
|
1346
|
+
{
|
|
1347
|
+
selected: settings.mine === n.id,
|
|
1348
|
+
disabled: locked,
|
|
1349
|
+
title: numberName(n, formatIntl2),
|
|
1350
|
+
sub: n.label ? formatIntl2(`+${n.number}`) : null,
|
|
1351
|
+
tag: KIND_TAG[n.kind],
|
|
1352
|
+
onSelect: () => void choose(n.id)
|
|
1353
|
+
},
|
|
1354
|
+
n.id
|
|
1355
|
+
))
|
|
1356
|
+
] }),
|
|
1357
|
+
message ? /* @__PURE__ */ jsx("p", { className: "cv-on-notice", role: "status", "data-kind": isError ? "error" : "info", children: message }) : null
|
|
1358
|
+
]
|
|
1359
|
+
}
|
|
1360
|
+
) : null,
|
|
1361
|
+
callout && !open && message ? /* @__PURE__ */ jsx("p", { className: "cv-on-callout", role: "status", "data-kind": isError ? "error" : "info", part: "outbound-number-notice", children: message }) : null
|
|
1362
|
+
] });
|
|
1363
|
+
}
|
|
1364
|
+
function OptionRow({
|
|
1365
|
+
selected,
|
|
1366
|
+
disabled,
|
|
1367
|
+
title,
|
|
1368
|
+
sub,
|
|
1369
|
+
tag,
|
|
1370
|
+
onSelect
|
|
1371
|
+
}) {
|
|
1372
|
+
return /* @__PURE__ */ jsxs(
|
|
1373
|
+
"button",
|
|
1374
|
+
{
|
|
1375
|
+
type: "button",
|
|
1376
|
+
role: "option",
|
|
1377
|
+
"aria-selected": selected,
|
|
1378
|
+
"aria-disabled": disabled,
|
|
1379
|
+
className: "cv-cp-item cv-on-item",
|
|
1380
|
+
tabIndex: -1,
|
|
1381
|
+
onClick: onSelect,
|
|
1382
|
+
children: [
|
|
1383
|
+
/* @__PURE__ */ jsxs("span", { className: "cv-on-text", children: [
|
|
1384
|
+
/* @__PURE__ */ jsxs("span", { className: "cv-on-line", children: [
|
|
1385
|
+
/* @__PURE__ */ jsx("span", { className: "cv-cp-name", children: title }),
|
|
1386
|
+
tag ? /* @__PURE__ */ jsx("span", { className: "cv-on-tag", children: tag.tag }) : null
|
|
1387
|
+
] }),
|
|
1388
|
+
sub ? /* @__PURE__ */ jsx("span", { className: "cv-on-sub", children: sub }) : null,
|
|
1389
|
+
tag ? /* @__PURE__ */ jsx("span", { className: "cv-on-hint", children: tag.hint }) : null
|
|
1390
|
+
] }),
|
|
1391
|
+
/* @__PURE__ */ jsx(Check, { size: 14, className: "cv-cp-check", "data-on": selected, "aria-hidden": true })
|
|
1392
|
+
]
|
|
1393
|
+
}
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
var optionsOf = (list) => list ? [...list.querySelectorAll('[role="option"]')] : [];
|
|
1397
|
+
var fold = (text) => text.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
|
|
1398
|
+
var TYPEAHEAD_MS = 700;
|
|
1399
|
+
function useListKeyboard(open) {
|
|
1400
|
+
const listRef = useRef(null);
|
|
1401
|
+
const typed = useRef({ text: "", at: 0 });
|
|
1402
|
+
useEffect(() => {
|
|
1403
|
+
if (!open) return;
|
|
1404
|
+
const options = optionsOf(listRef.current);
|
|
1405
|
+
(options.find((o) => o.getAttribute("aria-selected") === "true") ?? options[0])?.focus();
|
|
1406
|
+
}, [open]);
|
|
1407
|
+
const onKeyDown = (e) => {
|
|
1408
|
+
const options = optionsOf(listRef.current);
|
|
1409
|
+
const at = options.indexOf(e.target);
|
|
1410
|
+
const move = {
|
|
1411
|
+
ArrowDown: Math.min(at + 1, options.length - 1),
|
|
1412
|
+
ArrowUp: Math.max(at - 1, 0),
|
|
1413
|
+
Home: 0,
|
|
1414
|
+
End: options.length - 1
|
|
1415
|
+
};
|
|
1416
|
+
let next = move[e.key];
|
|
1417
|
+
if (next === void 0 && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
1418
|
+
const now = Date.now();
|
|
1419
|
+
const text = now - typed.current.at < TYPEAHEAD_MS ? typed.current.text + e.key : e.key;
|
|
1420
|
+
typed.current = { text, at: now };
|
|
1421
|
+
const wanted = fold(text);
|
|
1422
|
+
const match = options.findIndex((o) => fold(o.querySelector(".cv-cp-name")?.textContent ?? "").startsWith(wanted));
|
|
1423
|
+
next = match >= 0 ? match : void 0;
|
|
1424
|
+
}
|
|
1425
|
+
if (next === void 0) return;
|
|
1426
|
+
e.preventDefault();
|
|
1427
|
+
options[next]?.focus();
|
|
1428
|
+
};
|
|
1429
|
+
return { listRef, onKeyDown };
|
|
1430
|
+
}
|
|
1021
1431
|
var NOT_DIALABLE = /* @__PURE__ */ new Set(["*", "#"]);
|
|
1432
|
+
var DISPLAY_SIZES = ["1.375rem", "1.25rem", "1.125rem", "1rem"];
|
|
1022
1433
|
function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
1023
1434
|
const softphone = useSoftphone();
|
|
1024
1435
|
const state = useSoftphoneState();
|
|
1025
1436
|
const [value, setValue] = useState("");
|
|
1026
1437
|
const [country, setCountry] = useState(DEFAULT_COUNTRY);
|
|
1438
|
+
const [changingNumber, setChangingNumber] = useState(false);
|
|
1027
1439
|
const format = useNumberFormatter();
|
|
1028
1440
|
const inputRef = useRef(null);
|
|
1029
1441
|
const caretRef = useRef(null);
|
|
1030
1442
|
const display = format(country, value);
|
|
1031
1443
|
const target = toE164(country, value);
|
|
1032
|
-
const canCall = state === "online" && target.length > 1;
|
|
1444
|
+
const canCall = state === "online" && target.length > 1 && !changingNumber;
|
|
1445
|
+
const typedInternational = value.trim().startsWith("+");
|
|
1446
|
+
const numberRegion = regionOfInternational(value);
|
|
1033
1447
|
useLayoutEffect(() => {
|
|
1034
1448
|
const caret = caretRef.current;
|
|
1035
1449
|
if (caret === null || !inputRef.current) return;
|
|
@@ -1037,6 +1451,14 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
|
1037
1451
|
const pos = caretAfterDigits(display, caret);
|
|
1038
1452
|
inputRef.current.setSelectionRange(pos, pos);
|
|
1039
1453
|
}, [display]);
|
|
1454
|
+
useLayoutEffect(() => {
|
|
1455
|
+
const input = inputRef.current;
|
|
1456
|
+
if (!input) return;
|
|
1457
|
+
for (const size of DISPLAY_SIZES) {
|
|
1458
|
+
input.style.fontSize = size;
|
|
1459
|
+
if (input.scrollWidth <= input.clientWidth) break;
|
|
1460
|
+
}
|
|
1461
|
+
}, [display]);
|
|
1040
1462
|
const append = (key) => {
|
|
1041
1463
|
onIntent?.();
|
|
1042
1464
|
setValue((v) => v + key);
|
|
@@ -1052,9 +1474,16 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
|
1052
1474
|
void softphone.call(target);
|
|
1053
1475
|
};
|
|
1054
1476
|
return /* @__PURE__ */ jsxs("div", { className: "cv-view", children: [
|
|
1055
|
-
/* @__PURE__ */ jsx(
|
|
1477
|
+
/* @__PURE__ */ jsx(OutboundNumberPicker, { onBusyChange: setChangingNumber }),
|
|
1056
1478
|
/* @__PURE__ */ jsxs("div", { className: "cv-dial-head", children: [
|
|
1057
|
-
/* @__PURE__ */ jsx(
|
|
1479
|
+
/* @__PURE__ */ jsx(
|
|
1480
|
+
CountryPicker,
|
|
1481
|
+
{
|
|
1482
|
+
value: numberRegion ?? country,
|
|
1483
|
+
onChange: setCountry,
|
|
1484
|
+
fromNumber: typedInternational
|
|
1485
|
+
}
|
|
1486
|
+
),
|
|
1058
1487
|
/* @__PURE__ */ jsx(
|
|
1059
1488
|
"input",
|
|
1060
1489
|
{
|
|
@@ -1078,6 +1507,8 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
|
1078
1507
|
type: "button",
|
|
1079
1508
|
className: "cv-dial-side",
|
|
1080
1509
|
"data-hidden": value.length === 0,
|
|
1510
|
+
tabIndex: value.length === 0 ? -1 : void 0,
|
|
1511
|
+
"aria-hidden": value.length === 0 || void 0,
|
|
1081
1512
|
onClick: backspace,
|
|
1082
1513
|
"aria-label": "Borrar",
|
|
1083
1514
|
children: /* @__PURE__ */ jsx(Delete, { size: 19 })
|
|
@@ -1251,6 +1682,22 @@ var ERROR_TEXT = {
|
|
|
1251
1682
|
// thinks they dropped it will hang up on a client to "fix" it.
|
|
1252
1683
|
"call/transfer-failed": { title: "No se pudo transferir. Sigues en la llamada" },
|
|
1253
1684
|
"call/unsupported": { title: "Esta funci\xF3n no est\xE1 disponible" },
|
|
1685
|
+
// ── Line owner ──────────────────────────────────────────────────────────────
|
|
1686
|
+
/*
|
|
1687
|
+
Not call outcomes: they answer "Recibir aquí" on `LineGate`, which has ONE line for them where the
|
|
1688
|
+
explanation was, so it reads the pair as two sentences — `${title}. ${body}`. Split anyway, so the
|
|
1689
|
+
title stands alone wherever there is only room for a headline (a host's own toast), under the same
|
|
1690
|
+
one-line rules as every other non-fatal title. "Allí" because the gate has just said where: the
|
|
1691
|
+
other softphone, the one that has the calls.
|
|
1692
|
+
*/
|
|
1693
|
+
"line/claim-refused": {
|
|
1694
|
+
title: "Est\xE1s en una llamada all\xED",
|
|
1695
|
+
body: "Podr\xE1s recibirlas aqu\xED cuando cuelgues."
|
|
1696
|
+
},
|
|
1697
|
+
"line/unavailable": {
|
|
1698
|
+
title: "No se ha podido cambiar",
|
|
1699
|
+
body: "Int\xE9ntalo de nuevo."
|
|
1700
|
+
},
|
|
1254
1701
|
// ── Provider ────────────────────────────────────────────────────────────────
|
|
1255
1702
|
"provider/provision-failed": {
|
|
1256
1703
|
title: "No se pudo activar el tel\xE9fono",
|
|
@@ -1502,6 +1949,9 @@ function CallScreen({
|
|
|
1502
1949
|
}) {
|
|
1503
1950
|
const view = useCall(call);
|
|
1504
1951
|
const softphone = useSoftphone();
|
|
1952
|
+
const outbound = useOutboundNumbers(call.direction === "outbound" ? outboundNumberStore(softphone) : null).settings;
|
|
1953
|
+
const formatIntl2 = useIntlFormatter();
|
|
1954
|
+
const fromName = outbound && outbound.numbers.length > 1 ? numberName(effectiveNumber(outbound), formatIntl2) : null;
|
|
1505
1955
|
const [panel, setPanel] = useState(null);
|
|
1506
1956
|
const showTransfer = panel === "transfer";
|
|
1507
1957
|
const showKeypad = panel === "keypad";
|
|
@@ -1712,6 +2162,13 @@ function CallScreen({
|
|
|
1712
2162
|
] }),
|
|
1713
2163
|
/* @__PURE__ */ jsx("div", { className: "cv-call-name", children: name }),
|
|
1714
2164
|
showNumber ? /* @__PURE__ */ jsx("div", { className: "cv-call-number", children: number }) : null,
|
|
2165
|
+
fromName ? /* @__PURE__ */ jsxs("div", { className: "cv-call-from", children: [
|
|
2166
|
+
/* @__PURE__ */ jsx(PhoneOutgoing, { size: 11, "aria-hidden": true }),
|
|
2167
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
2168
|
+
"Desde ",
|
|
2169
|
+
fromName
|
|
2170
|
+
] })
|
|
2171
|
+
] }) : null,
|
|
1715
2172
|
s === "active" ? /* @__PURE__ */ jsxs("div", { className: "cv-timer", "aria-label": "Duraci\xF3n de la llamada", children: [
|
|
1716
2173
|
/* @__PURE__ */ jsx("b", { children: mm }),
|
|
1717
2174
|
/* @__PURE__ */ jsx("i", { children: ":" }),
|
|
@@ -2127,12 +2584,23 @@ function HandlerChain({ handlers }) {
|
|
|
2127
2584
|
] }, `${h.extension}-${i}`);
|
|
2128
2585
|
}) });
|
|
2129
2586
|
}
|
|
2587
|
+
function outboundFrom(entry, known, formatIntl2) {
|
|
2588
|
+
if (entry.direction !== "outbound" || !entry.from) return null;
|
|
2589
|
+
const number = formatIntl2(`+${entry.from}`);
|
|
2590
|
+
const label = known?.numbers.find((n) => n.number === entry.from)?.label;
|
|
2591
|
+
if (!label) return number;
|
|
2592
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2593
|
+
label,
|
|
2594
|
+
/* @__PURE__ */ jsx("span", { className: "cv-detail-row-sub", children: number })
|
|
2595
|
+
] });
|
|
2596
|
+
}
|
|
2130
2597
|
function HistoryDetail({
|
|
2131
2598
|
entry,
|
|
2132
2599
|
onBack
|
|
2133
2600
|
}) {
|
|
2134
2601
|
const softphone = useSoftphone();
|
|
2135
2602
|
const formatIntl2 = useIntlFormatter();
|
|
2603
|
+
const knownNumbers = useKnownOutboundNumbers(outboundNumberStore(softphone));
|
|
2136
2604
|
const [detail, setDetail] = useState(null);
|
|
2137
2605
|
const [loadingDetail, setLoadingDetail] = useState(false);
|
|
2138
2606
|
useEffect(() => {
|
|
@@ -2148,6 +2616,7 @@ function HistoryDetail({
|
|
|
2148
2616
|
}, [entry]);
|
|
2149
2617
|
const formatted = formatIntl2(entry.number);
|
|
2150
2618
|
const name = entry.name ?? formatted;
|
|
2619
|
+
const from = outboundFrom(entry, knownNumbers, formatIntl2);
|
|
2151
2620
|
const handledBy = detail?.handledBy ?? entry.handledBy;
|
|
2152
2621
|
const recordingUrls = detail?.recordingUrls ?? [];
|
|
2153
2622
|
const loadingRecording = entry.recorded && loadingDetail && recordingUrls.length === 0;
|
|
@@ -2184,7 +2653,8 @@ function HistoryDetail({
|
|
|
2184
2653
|
entry.durationSec > 0 ? /* @__PURE__ */ jsx(DetailRow, { icon: Timer, eyebrow: "Duraci\xF3n", text: formatDuration(entry.durationSec) }) : null,
|
|
2185
2654
|
/* @__PURE__ */ jsx(DetailRow, { icon: Phone, eyebrow: "N\xFAmero", text: formatted }),
|
|
2186
2655
|
handledBy.length > 0 ? /* @__PURE__ */ jsx(DetailRow, { icon: User, eyebrow: handlerLabel, text: /* @__PURE__ */ jsx(HandlerChain, { handlers: handledBy }) }) : null,
|
|
2187
|
-
entry.direction === "inbound" && entry.did ? /* @__PURE__ */ jsx(DetailRow, { icon: Hash, eyebrow: "N\xFAmero marcado (DID)", text: formatIntl2(entry.did) }) : null
|
|
2656
|
+
entry.direction === "inbound" && entry.did ? /* @__PURE__ */ jsx(DetailRow, { icon: Hash, eyebrow: "N\xFAmero marcado (DID)", text: formatIntl2(entry.did) }) : null,
|
|
2657
|
+
from ? /* @__PURE__ */ jsx(DetailRow, { icon: PhoneOutgoing, eyebrow: "Llamada desde", text: from }) : null
|
|
2188
2658
|
] })
|
|
2189
2659
|
] }),
|
|
2190
2660
|
/* @__PURE__ */ jsx("div", { className: "cv-detail-foot", children: /* @__PURE__ */ jsxs(
|
|
@@ -2379,14 +2849,14 @@ function HistoryTab({ source }) {
|
|
|
2379
2849
|
}
|
|
2380
2850
|
|
|
2381
2851
|
// src/ui/audio/ringtone-store.ts
|
|
2382
|
-
var
|
|
2852
|
+
var stores2 = /* @__PURE__ */ new WeakMap();
|
|
2383
2853
|
function ringtoneStore(softphone) {
|
|
2384
2854
|
const source = softphone.sources?.ringtones;
|
|
2385
2855
|
if (!source) return null;
|
|
2386
|
-
let store =
|
|
2856
|
+
let store = stores2.get(softphone);
|
|
2387
2857
|
if (!store) {
|
|
2388
2858
|
store = createRingtoneStore(source);
|
|
2389
|
-
|
|
2859
|
+
stores2.set(softphone, store);
|
|
2390
2860
|
}
|
|
2391
2861
|
return store;
|
|
2392
2862
|
}
|
|
@@ -2512,17 +2982,19 @@ function ChoiceList({
|
|
|
2512
2982
|
choices,
|
|
2513
2983
|
value,
|
|
2514
2984
|
onSelect,
|
|
2515
|
-
onReselect
|
|
2985
|
+
onReselect,
|
|
2986
|
+
disabled = false
|
|
2516
2987
|
}) {
|
|
2517
2988
|
const name = useId();
|
|
2518
2989
|
const pointer = useRef(false);
|
|
2519
|
-
return /* @__PURE__ */ jsx("div", { className: "cv-choices", role: "radiogroup", "aria-label": label, children: choices.map((c) => {
|
|
2990
|
+
return /* @__PURE__ */ jsx("div", { className: "cv-choices", role: "radiogroup", "aria-label": label, "aria-disabled": disabled || void 0, children: choices.map((c) => {
|
|
2520
2991
|
const checked = c.value === value;
|
|
2521
2992
|
return /* @__PURE__ */ jsxs(
|
|
2522
2993
|
"label",
|
|
2523
2994
|
{
|
|
2524
2995
|
className: "cv-choice",
|
|
2525
2996
|
"data-checked": checked || void 0,
|
|
2997
|
+
"data-disabled": disabled || void 0,
|
|
2526
2998
|
onPointerDown: () => {
|
|
2527
2999
|
pointer.current = true;
|
|
2528
3000
|
},
|
|
@@ -2538,19 +3010,23 @@ function ChoiceList({
|
|
|
2538
3010
|
onChange: () => {
|
|
2539
3011
|
const via = pointer.current ? "pointer" : "keyboard";
|
|
2540
3012
|
pointer.current = false;
|
|
2541
|
-
onSelect(c.value, via);
|
|
3013
|
+
if (!disabled) onSelect(c.value, via);
|
|
2542
3014
|
},
|
|
2543
3015
|
onClick: () => {
|
|
2544
3016
|
if (c.value === value) {
|
|
2545
3017
|
pointer.current = false;
|
|
2546
|
-
onReselect?.(c.value);
|
|
3018
|
+
if (!disabled) onReselect?.(c.value);
|
|
2547
3019
|
}
|
|
2548
3020
|
}
|
|
2549
3021
|
}
|
|
2550
3022
|
),
|
|
2551
3023
|
/* @__PURE__ */ jsxs("span", { className: "cv-choice-text", children: [
|
|
2552
|
-
/* @__PURE__ */
|
|
2553
|
-
|
|
3024
|
+
c.tag ? /* @__PURE__ */ jsxs("span", { className: "cv-choice-line", children: [
|
|
3025
|
+
/* @__PURE__ */ jsx("span", { className: "cv-choice-label", children: c.label }),
|
|
3026
|
+
/* @__PURE__ */ jsx("span", { className: "cv-choice-tag", children: c.tag })
|
|
3027
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "cv-choice-label", children: c.label }),
|
|
3028
|
+
c.sub ? /* @__PURE__ */ jsx("span", { className: "cv-choice-sub", children: c.sub }) : null,
|
|
3029
|
+
c.hint ? /* @__PURE__ */ jsx("span", { className: "cv-choice-hint", children: c.hint }) : null
|
|
2554
3030
|
] }),
|
|
2555
3031
|
c.trailing,
|
|
2556
3032
|
/* @__PURE__ */ jsx(Check, { size: 16, className: "cv-choice-check", "aria-hidden": true })
|
|
@@ -2712,11 +3188,11 @@ function previewRingtone(ringtone, options = {}) {
|
|
|
2712
3188
|
}
|
|
2713
3189
|
|
|
2714
3190
|
// src/ui/views/settings/ringtone-state.ts
|
|
2715
|
-
var
|
|
3191
|
+
var noSubscribe2 = () => () => {
|
|
2716
3192
|
};
|
|
2717
|
-
var
|
|
3193
|
+
var noSettings2 = () => null;
|
|
2718
3194
|
function useRingtoneSettings(store) {
|
|
2719
|
-
const settings = useSyncExternalStore(store?.subscribe ??
|
|
3195
|
+
const settings = useSyncExternalStore(store?.subscribe ?? noSubscribe2, store?.getSettings ?? noSettings2);
|
|
2720
3196
|
const [failed, setFailed] = useState(false);
|
|
2721
3197
|
const load = useCallback(() => {
|
|
2722
3198
|
if (!store) return () => {
|
|
@@ -2917,11 +3393,109 @@ function useSerialSave(store, handlers) {
|
|
|
2917
3393
|
});
|
|
2918
3394
|
return save;
|
|
2919
3395
|
}
|
|
3396
|
+
var ORG_DEFAULT2 = "";
|
|
3397
|
+
var KEYBOARD_SETTLE_MS2 = 800;
|
|
3398
|
+
function OutboundNumberPage({
|
|
3399
|
+
store,
|
|
3400
|
+
settings,
|
|
3401
|
+
onBack
|
|
3402
|
+
}) {
|
|
3403
|
+
const formatIntl2 = useIntlFormatter();
|
|
3404
|
+
const current = numberName(effectiveNumber(settings), formatIntl2);
|
|
3405
|
+
const change = useOutboundNumberChange(store, settings, current);
|
|
3406
|
+
const [pending, setPending] = useState(void 0);
|
|
3407
|
+
const settle = useRef(null);
|
|
3408
|
+
const choose = useRef(change.choose);
|
|
3409
|
+
choose.current = change.choose;
|
|
3410
|
+
useEffect(
|
|
3411
|
+
() => () => {
|
|
3412
|
+
if (settle.current) clearTimeout(settle.current);
|
|
3413
|
+
},
|
|
3414
|
+
[]
|
|
3415
|
+
);
|
|
3416
|
+
const notice = useRef(null);
|
|
3417
|
+
const apply = async (value) => {
|
|
3418
|
+
setPending(value);
|
|
3419
|
+
const applied = await choose.current(value || null);
|
|
3420
|
+
setPending(void 0);
|
|
3421
|
+
if (!applied) requestAnimationFrame(() => notice.current?.scrollIntoView?.({ block: "nearest" }));
|
|
3422
|
+
};
|
|
3423
|
+
const onSelect = (value, via) => {
|
|
3424
|
+
if (settle.current) clearTimeout(settle.current);
|
|
3425
|
+
settle.current = null;
|
|
3426
|
+
if (via === "pointer") {
|
|
3427
|
+
void apply(value);
|
|
3428
|
+
return;
|
|
3429
|
+
}
|
|
3430
|
+
setPending(value);
|
|
3431
|
+
settle.current = setTimeout(() => {
|
|
3432
|
+
settle.current = null;
|
|
3433
|
+
void apply(value);
|
|
3434
|
+
}, KEYBOARD_SETTLE_MS2);
|
|
3435
|
+
};
|
|
3436
|
+
return /* @__PURE__ */ jsxs(
|
|
3437
|
+
SettingsPage,
|
|
3438
|
+
{
|
|
3439
|
+
title: "N\xFAmero de salida",
|
|
3440
|
+
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.",
|
|
3441
|
+
onBack,
|
|
3442
|
+
children: [
|
|
3443
|
+
change.message ? /* @__PURE__ */ jsx("p", { className: "cv-settings-notice", role: "status", "data-kind": change.isError ? "error" : "info", ref: notice, children: change.message }) : null,
|
|
3444
|
+
/* @__PURE__ */ jsx(
|
|
3445
|
+
ChoiceList,
|
|
3446
|
+
{
|
|
3447
|
+
label: "N\xFAmero de salida",
|
|
3448
|
+
choices: choicesFor2(settings, formatIntl2, change.busy ? pending : void 0),
|
|
3449
|
+
value: pending ?? currentValue2(settings),
|
|
3450
|
+
onSelect,
|
|
3451
|
+
disabled: change.locked
|
|
3452
|
+
}
|
|
3453
|
+
)
|
|
3454
|
+
]
|
|
3455
|
+
}
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3458
|
+
function currentValue2(settings) {
|
|
3459
|
+
return settings.mine && settings.numbers.some((n) => n.id === settings.mine) ? settings.mine : ORG_DEFAULT2;
|
|
3460
|
+
}
|
|
3461
|
+
function choicesFor2(settings, formatIntl2, applying) {
|
|
3462
|
+
const orgDefault = settings.numbers.find((n) => n.id === settings.orgDefault);
|
|
3463
|
+
const spinner = (value) => applying === value ? /* @__PURE__ */ jsxs("span", { className: "cv-choice-applying", children: [
|
|
3464
|
+
/* @__PURE__ */ jsx(LoaderCircle, { size: 15, className: "cv-choice-applying-icon", "aria-hidden": true }),
|
|
3465
|
+
/* @__PURE__ */ jsx("span", { className: "cv-sr-only", children: "Aplicando" })
|
|
3466
|
+
] }) : void 0;
|
|
3467
|
+
return [
|
|
3468
|
+
{
|
|
3469
|
+
value: ORG_DEFAULT2,
|
|
3470
|
+
label: "Por defecto de la organizaci\xF3n",
|
|
3471
|
+
sub: orgDefault ? numberName(orgDefault, formatIntl2) : "Sin n\xFAmero por defecto",
|
|
3472
|
+
trailing: spinner(ORG_DEFAULT2)
|
|
3473
|
+
},
|
|
3474
|
+
...settings.numbers.map((n) => {
|
|
3475
|
+
const kind = KIND_TAG[n.kind];
|
|
3476
|
+
return {
|
|
3477
|
+
value: n.id,
|
|
3478
|
+
label: numberName(n, formatIntl2),
|
|
3479
|
+
...n.label ? { sub: formatIntl2(`+${n.number}`) } : {},
|
|
3480
|
+
...kind ? { tag: kind.tag, hint: kind.hint } : {},
|
|
3481
|
+
trailing: spinner(n.id)
|
|
3482
|
+
};
|
|
3483
|
+
})
|
|
3484
|
+
];
|
|
3485
|
+
}
|
|
3486
|
+
function outboundSummary(settings, formatIntl2) {
|
|
3487
|
+
const effective = effectiveNumber(settings);
|
|
3488
|
+
const name = numberName(effective, formatIntl2);
|
|
3489
|
+
return settings.mine || !effective ? name : `${name} \xB7 organizaci\xF3n`;
|
|
3490
|
+
}
|
|
2920
3491
|
function SettingsTab() {
|
|
2921
3492
|
const softphone = useSoftphone();
|
|
2922
3493
|
const devices = useDevices();
|
|
2923
3494
|
const store = ringtoneStore(softphone);
|
|
2924
3495
|
const ringtone = useRingtoneSettings(store);
|
|
3496
|
+
const outStore = outboundNumberStore(softphone);
|
|
3497
|
+
const outbound = useOutboundNumbers(outStore).settings;
|
|
3498
|
+
const formatIntl2 = useIntlFormatter();
|
|
2925
3499
|
const [page, setPage] = useState(null);
|
|
2926
3500
|
const list = useRef(null);
|
|
2927
3501
|
const rows = useRef({});
|
|
@@ -2956,6 +3530,16 @@ function SettingsTab() {
|
|
|
2956
3530
|
rowRef: rowRef("tone")
|
|
2957
3531
|
}
|
|
2958
3532
|
) }) : null,
|
|
3533
|
+
outStore && outbound && outbound.numbers.length > 1 ? /* @__PURE__ */ jsx(SettingsSection, { title: "Llamadas salientes", children: /* @__PURE__ */ jsx(
|
|
3534
|
+
SettingsNavRow,
|
|
3535
|
+
{
|
|
3536
|
+
icon: PhoneOutgoing,
|
|
3537
|
+
title: "N\xFAmero de salida",
|
|
3538
|
+
value: outboundSummary(outbound, formatIntl2),
|
|
3539
|
+
onOpen: open("outbound"),
|
|
3540
|
+
rowRef: rowRef("outbound")
|
|
3541
|
+
}
|
|
3542
|
+
) }) : null,
|
|
2959
3543
|
/* @__PURE__ */ jsxs(SettingsSection, { title: "Dispositivos", children: [
|
|
2960
3544
|
/* @__PURE__ */ jsx(
|
|
2961
3545
|
SettingsNavRow,
|
|
@@ -3009,7 +3593,7 @@ function SettingsTab() {
|
|
|
3009
3593
|
onRetry: ringtone.retry,
|
|
3010
3594
|
onBack: back
|
|
3011
3595
|
}
|
|
3012
|
-
) : page !== null && page !== "tone" ? /* @__PURE__ */ jsx(DevicePage, { kind: page, onBack: back }) : null
|
|
3596
|
+
) : page === "outbound" && outStore && outbound ? /* @__PURE__ */ jsx(OutboundNumberPage, { store: outStore, settings: outbound, onBack: back }) : page !== null && page !== "tone" && page !== "outbound" ? /* @__PURE__ */ jsx(DevicePage, { kind: page, onBack: back }) : null
|
|
3013
3597
|
] });
|
|
3014
3598
|
}
|
|
3015
3599
|
function useMicPermission() {
|
|
@@ -3196,6 +3780,40 @@ function LeaderGate() {
|
|
|
3196
3780
|
/* @__PURE__ */ jsx("p", { part: "leader-body", children: "Solo una pesta\xF1a puede usar el tel\xE9fono a la vez. Cierra la otra para activar esta." })
|
|
3197
3781
|
] });
|
|
3198
3782
|
}
|
|
3783
|
+
var BODY = "Las llamadas entran all\xED. Si prefieres recibirlas aqu\xED, pulsa \xABRecibir aqu\xED\xBB.";
|
|
3784
|
+
var sentence = (code) => `${ERROR_TEXT[code].title}. ${ERROR_TEXT[code].body ?? ""}`.trim();
|
|
3785
|
+
function LineGate() {
|
|
3786
|
+
const softphone = useSoftphone();
|
|
3787
|
+
const { where, claim } = useLine();
|
|
3788
|
+
const take = () => {
|
|
3789
|
+
softphone.takeLine().catch(() => {
|
|
3790
|
+
});
|
|
3791
|
+
};
|
|
3792
|
+
const body = claim === "refused" ? sentence("line/claim-refused") : claim === "failed" ? sentence("line/unavailable") : BODY;
|
|
3793
|
+
return (
|
|
3794
|
+
/*
|
|
3795
|
+
`part` on all four, like `ErrorGate`: it replaces the whole dialpad, and inside `<coventia-softphone>`
|
|
3796
|
+
a shadow root makes it otherwise unreachable from the host's CSS. `data-claim` is for the light-DOM
|
|
3797
|
+
`/ui` path and for debugging — it cannot cross the shadow boundary either.
|
|
3798
|
+
*/
|
|
3799
|
+
/* @__PURE__ */ jsxs("div", { className: "cv-gate", "data-claim": claim, part: "line-gate", children: [
|
|
3800
|
+
/* @__PURE__ */ jsx("span", { className: "cv-gate-mark", children: /* @__PURE__ */ jsx(PhoneForwarded, { size: 22 }) }),
|
|
3801
|
+
/* @__PURE__ */ 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" }),
|
|
3802
|
+
/* @__PURE__ */ jsx("p", { role: "status", part: "line-body", children: body }),
|
|
3803
|
+
/* @__PURE__ */ jsx(
|
|
3804
|
+
"button",
|
|
3805
|
+
{
|
|
3806
|
+
className: "cv-btn cv-btn--accent",
|
|
3807
|
+
onClick: take,
|
|
3808
|
+
disabled: claim === "claiming",
|
|
3809
|
+
"aria-busy": claim === "claiming",
|
|
3810
|
+
part: "line-claim",
|
|
3811
|
+
children: claim === "claiming" ? "Cambiando\u2026" : "Recibir aqu\xED"
|
|
3812
|
+
}
|
|
3813
|
+
)
|
|
3814
|
+
] })
|
|
3815
|
+
);
|
|
3816
|
+
}
|
|
3199
3817
|
var BACKSTOP_MS = 6e3;
|
|
3200
3818
|
function useCallOutcome() {
|
|
3201
3819
|
const softphone = useSoftphone();
|
|
@@ -3427,11 +4045,12 @@ function SoftphoneWidget(props) {
|
|
|
3427
4045
|
const isLeader = useIsLeader();
|
|
3428
4046
|
const softphoneState = useSoftphoneState();
|
|
3429
4047
|
const isFollower = !isLeader && softphoneState === "starting";
|
|
4048
|
+
const isStandby = softphoneState === "standby";
|
|
3430
4049
|
const mic = useMicPermission();
|
|
3431
4050
|
useCallAudio(sounds);
|
|
3432
4051
|
const blocked = requireMic && mic.state !== "ready";
|
|
3433
4052
|
const fatal = useLastError();
|
|
3434
|
-
const screenIsFree = !call && !blocked && !fatal && !isFollower;
|
|
4053
|
+
const screenIsFree = !call && !blocked && !fatal && !isFollower && !isStandby;
|
|
3435
4054
|
const { outcome, clear: clearOutcome } = useCallOutcome();
|
|
3436
4055
|
const { notices: transferNotices, dismiss: dismissTransferNotice } = useTransferNotices();
|
|
3437
4056
|
const retry = () => {
|
|
@@ -3458,7 +4077,7 @@ function SoftphoneWidget(props) {
|
|
|
3458
4077
|
...theme === "system" ? {} : { "data-cv-theme": theme },
|
|
3459
4078
|
children: [
|
|
3460
4079
|
screenIsFree ? /* @__PURE__ */ jsx(TransferNotices, { notices: transferNotices, dismiss: dismissTransferNotice }) : null,
|
|
3461
|
-
/* @__PURE__ */ jsx("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ jsx(LeaderGate, {}) : blocked ? /* @__PURE__ */ jsx(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ jsx(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ jsx(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ jsx(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ jsx(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ jsx(SettingsTab, {}) : /* @__PURE__ */ jsx(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
|
|
4080
|
+
/* @__PURE__ */ jsx("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ jsx(LeaderGate, {}) : isStandby && !call ? /* @__PURE__ */ jsx(LineGate, {}) : blocked ? /* @__PURE__ */ jsx(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ jsx(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ jsx(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ jsx(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ jsx(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ jsx(SettingsTab, {}) : /* @__PURE__ */ jsx(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
|
|
3462
4081
|
screenIsFree ? /* @__PURE__ */ jsx("div", { className: "cv-tabs", role: "tablist", children: tabs.map(({ id, icon: Icon3, label }) => /* @__PURE__ */ jsxs(
|
|
3463
4082
|
"button",
|
|
3464
4083
|
{
|
|
@@ -3504,6 +4123,7 @@ lucide-react/dist/esm/icons/mic.js:
|
|
|
3504
4123
|
lucide-react/dist/esm/icons/monitor-smartphone.js:
|
|
3505
4124
|
lucide-react/dist/esm/icons/music.js:
|
|
3506
4125
|
lucide-react/dist/esm/icons/pause.js:
|
|
4126
|
+
lucide-react/dist/esm/icons/phone-forwarded.js:
|
|
3507
4127
|
lucide-react/dist/esm/icons/phone-incoming.js:
|
|
3508
4128
|
lucide-react/dist/esm/icons/phone-missed.js:
|
|
3509
4129
|
lucide-react/dist/esm/icons/phone-off.js:
|