@visns-studio/visns-components 6.27.0 → 6.28.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/README.md
CHANGED
|
@@ -2212,6 +2212,7 @@ endpoint nor an Echo instance present it renders nothing and logs nothing.
|
|
|
2212
2212
|
| `calendarPath` | `'/calendar'` | |
|
|
2213
2213
|
| `callWorkspacePath` | `'/call/{number}'` | Template, or `(workspaceId, call) => path`. `{number}` is the `61…` form the workspace route expects. |
|
|
2214
2214
|
| `syncChannelName` | `'throughlife-call-queue-pop'` | The `BroadcastChannel` that keeps every open tab's stack in step. Falsy switches cross-tab sync off. |
|
|
2215
|
+
| `clientDetailFields` | `CLIENT_DETAIL_FIELDS` (adviser / coding / age / city) | `[{ key, label, demo? }]` — the client-block rows, in card order; rows with an empty value are dropped; `demo` seeds the demo card. Pass your CRM's own fields so the card never names a field it does not have. Must be referentially stable (a module-scope constant, not an inline literal) — it sits in the demo effect's dependency list. |
|
|
2215
2216
|
| `demoEnabled` | `true` | Registers `window.callPopDemo()` / `window.callPopClear()` for reviewing the UI without a backend. |
|
|
2216
2217
|
|
|
2217
2218
|
**Payload contract.** Snake_case and camelCase are both accepted, so a Laravel
|
|
@@ -2226,6 +2227,11 @@ Named exports for testing: `toLocalDigits`, `formatAuPhone`, `formatEventDate`,
|
|
|
2226
2227
|
`formatDueDate`, `toCallWorkspaceId`, `normaliseCall`, `normalisePickupCodes`,
|
|
2227
2228
|
`formatElapsed`, `hasMonitorPermission`, `clientDetails`.
|
|
2228
2229
|
|
|
2230
|
+
`CLIENT_DETAIL_FIELDS`, `clientDetails` and `demoClientDetails` are exported
|
|
2231
|
+
from the package itself (`@visns-studio/visns-components`), so a host can build
|
|
2232
|
+
its own field list beside the default or reuse the demo seeding; the helpers
|
|
2233
|
+
module is not re-exported wholesale, only those three alongside the component.
|
|
2234
|
+
|
|
2229
2235
|
#### CallQueueSettings
|
|
2230
2236
|
|
|
2231
2237
|
The admin table behind the pop: one row per Zoom call queue, carrying the
|
package/package.json
CHANGED
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
94
94
|
},
|
|
95
95
|
"name": "@visns-studio/visns-components",
|
|
96
|
-
"version": "6.
|
|
96
|
+
"version": "6.28.0",
|
|
97
97
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
98
98
|
"main": "src/index.js",
|
|
99
99
|
"files": [
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
defaultClientTasksUrl,
|
|
41
41
|
defaultClientUrl,
|
|
42
42
|
defaultTaskUrl,
|
|
43
|
+
demoClientDetails,
|
|
43
44
|
directRingingLine,
|
|
44
45
|
formatAuPhone,
|
|
45
46
|
formatDueDate,
|
|
@@ -62,6 +63,7 @@ export {
|
|
|
62
63
|
callBadgeLabel,
|
|
63
64
|
calleeLabel,
|
|
64
65
|
clientDetails,
|
|
66
|
+
demoClientDetails,
|
|
65
67
|
directRingingLine,
|
|
66
68
|
formatAuPhone,
|
|
67
69
|
formatDueDate,
|
|
@@ -286,6 +288,16 @@ const CallQueuePop = ({
|
|
|
286
288
|
calendarPath = '/calendar',
|
|
287
289
|
callWorkspacePath = '/call/{number}',
|
|
288
290
|
syncChannelName = SYNC_CHANNEL,
|
|
291
|
+
// The client-block rows, in card order — the host's own fields, so the
|
|
292
|
+
// card never names one its CRM does not have. The default is this
|
|
293
|
+
// component's original financial-planning shape (adviser / coding / age /
|
|
294
|
+
// city) and the demo card seeds itself from the same list, so a host that
|
|
295
|
+
// passes its own fields also demos its own fields.
|
|
296
|
+
//
|
|
297
|
+
// MUST be referentially stable — a module-scope constant, not an inline
|
|
298
|
+
// literal — because it sits in the demo effect's dependency list, and a
|
|
299
|
+
// fresh array per render would re-register the demo helpers every time.
|
|
300
|
+
clientDetailFields = CLIENT_DETAIL_FIELDS,
|
|
289
301
|
demoEnabled = true,
|
|
290
302
|
missedGraceMs = DEFAULT_MISSED_GRACE_MS,
|
|
291
303
|
}) => {
|
|
@@ -680,20 +692,22 @@ const CallQueuePop = ({
|
|
|
680
692
|
queueName: 'Test Call Queue',
|
|
681
693
|
callerNumber: '+61298765432',
|
|
682
694
|
callerName: 'Margaret Chen',
|
|
683
|
-
//
|
|
684
|
-
//
|
|
695
|
+
// The rich card reviewed without a real match. The detail
|
|
696
|
+
// rows are seeded from `clientDetailFields` itself, so the
|
|
697
|
+
// demo shows exactly the host's own fields with the host's
|
|
698
|
+
// own sample values rather than another CRM's. id 0
|
|
685
699
|
// renders the name as plain text, not a link.
|
|
686
700
|
client: {
|
|
687
701
|
id: 0,
|
|
688
702
|
name: 'Chen, Margaret (Mrs)',
|
|
689
703
|
matched_on: 'home',
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
704
|
+
// Before the spread, so a host listing `email` among
|
|
705
|
+
// its own fields demos its own sample value while a
|
|
706
|
+
// host that does not still gets one.
|
|
693
707
|
email: 'margaret.chen@example.com',
|
|
694
|
-
|
|
708
|
+
...demoClientDetails(clientDetailFields),
|
|
695
709
|
next_event: {
|
|
696
|
-
title: 'Client
|
|
710
|
+
title: 'Client review',
|
|
697
711
|
date: new Date(
|
|
698
712
|
Date.now() + 2 * 24 * 60 * 60 * 1000
|
|
699
713
|
).toISOString(),
|
|
@@ -705,19 +719,19 @@ const CallQueuePop = ({
|
|
|
705
719
|
tasks: [
|
|
706
720
|
{
|
|
707
721
|
id: 90001,
|
|
708
|
-
label: 'Confirm
|
|
722
|
+
label: 'Confirm the site visit time',
|
|
709
723
|
due_date: new Date(
|
|
710
724
|
Date.now() + 3 * 24 * 60 * 60 * 1000
|
|
711
725
|
).toISOString(),
|
|
712
|
-
task_type: '
|
|
726
|
+
task_type: 'Ticket',
|
|
713
727
|
},
|
|
714
728
|
{
|
|
715
729
|
id: 90002,
|
|
716
|
-
label: 'Send updated
|
|
730
|
+
label: 'Send the updated quote',
|
|
717
731
|
due_date: new Date(
|
|
718
732
|
Date.now() + 9 * 24 * 60 * 60 * 1000
|
|
719
733
|
).toISOString(),
|
|
720
|
-
task_type: '
|
|
734
|
+
task_type: 'Quote',
|
|
721
735
|
},
|
|
722
736
|
],
|
|
723
737
|
startedAt: new Date().toISOString(),
|
|
@@ -745,6 +759,7 @@ const CallQueuePop = ({
|
|
|
745
759
|
addDemoPickupCode,
|
|
746
760
|
broadcastSync,
|
|
747
761
|
clearCalls,
|
|
762
|
+
clientDetailFields,
|
|
748
763
|
demoEnabled,
|
|
749
764
|
dropDemoPickupCode,
|
|
750
765
|
]);
|
|
@@ -1707,7 +1722,7 @@ const CallQueuePop = ({
|
|
|
1707
1722
|
{calls.map((call) => {
|
|
1708
1723
|
const pickupCode = pickupCodeFor(call);
|
|
1709
1724
|
const client = call.client;
|
|
1710
|
-
const details = clientDetails(client);
|
|
1725
|
+
const details = clientDetails(client, clientDetailFields);
|
|
1711
1726
|
const nextEvent = client?.next_event ?? null;
|
|
1712
1727
|
const nextEventDate = formatEventDate(nextEvent?.date);
|
|
1713
1728
|
const openTasks = Number(client?.open_tasks ?? 0);
|
|
@@ -210,32 +210,69 @@ export const defaultTaskUrl = (task) => `/tasks/detail/${task?.id}`;
|
|
|
210
210
|
/** Default detail route for a matched client. */
|
|
211
211
|
export const defaultClientUrl = (client) => `/clients/${client?.id}`;
|
|
212
212
|
|
|
213
|
-
/**
|
|
213
|
+
/**
|
|
214
|
+
* The client fields shown as label/value rows, in card order — the DEFAULT,
|
|
215
|
+
* inherited from the financial-planning CRM this component was ported from.
|
|
216
|
+
* Each entry's `demo` is the sample value the demo card shows for it.
|
|
217
|
+
*
|
|
218
|
+
* A host whose caller enrichment returns a different shape passes its own list
|
|
219
|
+
* as `clientDetailFields` rather than living with rows it can never fill.
|
|
220
|
+
*/
|
|
214
221
|
export const CLIENT_DETAIL_FIELDS = [
|
|
215
|
-
{ key: 'adviser', label: 'Adviser' },
|
|
216
|
-
{ key: 'coding', label: 'Coding' },
|
|
217
|
-
{ key: 'age', label: 'Age' },
|
|
218
|
-
{ key: 'city', label: 'City' },
|
|
222
|
+
{ key: 'adviser', label: 'Adviser', demo: 'Alina Bailey' },
|
|
223
|
+
{ key: 'coding', label: 'Coding', demo: 'Early Retirement' },
|
|
224
|
+
{ key: 'age', label: 'Age', demo: 66 },
|
|
225
|
+
{ key: 'city', label: 'City', demo: 'Fremantle' },
|
|
219
226
|
];
|
|
220
227
|
|
|
221
228
|
/**
|
|
222
229
|
* Detail rows for a client block, with the empty ones dropped — a card only
|
|
223
230
|
* ever shows what the CRM actually knows, never a row with a blank value.
|
|
231
|
+
*
|
|
232
|
+
* `fields` is the host's own list, so the card only ever names fields its CRM
|
|
233
|
+
* actually has. An entry with no string `key` is malformed and is ignored
|
|
234
|
+
* rather than drawn as a row with nothing behind it.
|
|
224
235
|
*/
|
|
225
|
-
export const clientDetails = (client) => {
|
|
236
|
+
export const clientDetails = (client, fields = CLIENT_DETAIL_FIELDS) => {
|
|
226
237
|
if (!client || typeof client !== 'object') {
|
|
227
238
|
return [];
|
|
228
239
|
}
|
|
229
240
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
241
|
+
const list = Array.isArray(fields) ? fields : CLIENT_DETAIL_FIELDS;
|
|
242
|
+
|
|
243
|
+
return list
|
|
244
|
+
.filter((field) => field && typeof field.key === 'string')
|
|
245
|
+
.map(({ key, label }) => ({
|
|
246
|
+
label,
|
|
247
|
+
value: client[key],
|
|
248
|
+
}))
|
|
249
|
+
.filter(
|
|
250
|
+
(row) =>
|
|
251
|
+
row.value !== null &&
|
|
252
|
+
row.value !== undefined &&
|
|
253
|
+
String(row.value).trim() !== ''
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The demo card's client block, built from the same list the real card draws
|
|
259
|
+
* from — so a host passing its own fields sees its own fields in the demo
|
|
260
|
+
* rather than another CRM's. An entry carrying no `demo` contributes nothing.
|
|
261
|
+
*/
|
|
262
|
+
export const demoClientDetails = (fields = CLIENT_DETAIL_FIELDS) => {
|
|
263
|
+
const list = Array.isArray(fields) ? fields : CLIENT_DETAIL_FIELDS;
|
|
264
|
+
|
|
265
|
+
return list.reduce((carry, field) => {
|
|
266
|
+
if (!field || typeof field.key !== 'string') {
|
|
267
|
+
return carry;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (field.demo === undefined) {
|
|
271
|
+
return carry;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return { ...carry, [field.key]: field.demo };
|
|
275
|
+
}, {});
|
|
239
276
|
};
|
|
240
277
|
|
|
241
278
|
/** Seconds since `startedAt` rendered as m:ss (never negative). */
|
package/src/index.js
CHANGED
|
@@ -141,6 +141,13 @@ import {
|
|
|
141
141
|
import CallQueuePop from './components/callQueue/CallQueuePop';
|
|
142
142
|
import CallQueueSettings from './components/callQueue/CallQueueSettings';
|
|
143
143
|
import CallQueueDiagnostics from './components/callQueue/CallQueueDiagnostics';
|
|
144
|
+
// The client-block field list and its two readers, so a host can build its own
|
|
145
|
+
// `clientDetailFields` beside the default rather than restating the shape.
|
|
146
|
+
import {
|
|
147
|
+
CLIENT_DETAIL_FIELDS,
|
|
148
|
+
clientDetails,
|
|
149
|
+
demoClientDetails,
|
|
150
|
+
} from './components/callQueue/callQueueHelpers';
|
|
144
151
|
import {
|
|
145
152
|
CALL_POP_STATUS_EVENT,
|
|
146
153
|
getCallPopStatus,
|
|
@@ -365,6 +372,9 @@ export {
|
|
|
365
372
|
CallQueueDiagnostics,
|
|
366
373
|
CallQueuePop,
|
|
367
374
|
CallQueueSettings,
|
|
375
|
+
CLIENT_DETAIL_FIELDS,
|
|
376
|
+
clientDetails,
|
|
377
|
+
demoClientDetails,
|
|
368
378
|
getCallPopStatus,
|
|
369
379
|
updateCallPopStatus,
|
|
370
380
|
cleanBase32,
|