@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.27.0",
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
- // Every field the server's client block can carry, so the
684
- // rich card can be reviewed without a real match. id 0
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
- adviser: 'Alina Bailey',
691
- coding: 'Early Retirement',
692
- age: 66,
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
- city: 'Fremantle',
708
+ ...demoClientDetails(clientDetailFields),
695
709
  next_event: {
696
- title: 'Client - Annual Review',
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 rollover paperwork',
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: 'Superannuation',
726
+ task_type: 'Ticket',
713
727
  },
714
728
  {
715
729
  id: 90002,
716
- label: 'Send updated risk profile questionnaire',
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: 'Insurance',
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
- /** The client fields shown as label/value rows, in card order. */
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
- return CLIENT_DETAIL_FIELDS.map(({ key, label }) => ({
231
- label,
232
- value: client[key],
233
- })).filter(
234
- (row) =>
235
- row.value !== null &&
236
- row.value !== undefined &&
237
- String(row.value).trim() !== ''
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,