@visns-studio/visns-components 6.25.0 → 6.26.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.
@@ -0,0 +1,398 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Call Pop Diagnostics (.cqdiag)
3
+ // ---------------------------------------------------------------------------
4
+ // The support panel behind Settings -> Call Queues -> Diagnostics
5
+ // (components/callQueue/CallQueueDiagnostics.jsx). Sibling of
6
+ // CallQueueSettings.module.scss and deliberately built from the same tokens,
7
+ // pills and panel frame — it hangs off the bottom of that page and should read
8
+ // as one more section of it, not a different product.
9
+ //
10
+ // Every brand value is a CSS custom property with a neutral fallback, because
11
+ // this library has no sass config layer of its own.
12
+ // ---------------------------------------------------------------------------
13
+
14
+ $cqdiag-mono: 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
15
+
16
+ .cqdiag {
17
+ --cq-primary: var(--primary-color, #00386c);
18
+ --cq-secondary: var(--secondary-color, #404040);
19
+ --cq-tertiary: var(--tertiary-color, #fff);
20
+ --cq-paragraph: var(--paragraph-color, #2b2b2b);
21
+ --cq-background: var(--background-color, #fff);
22
+ --cq-br: var(--br, 5px);
23
+ --cq-btn-br: var(--btn-br, 5px);
24
+
25
+ // The same three verdict colours the pop and the settings table use, so
26
+ // "good", "look at this" and "broken" mean one thing across the feature.
27
+ --cq-ok: rgb(43, 145, 85);
28
+ --cq-warn: rgb(191, 138, 41);
29
+ --cq-bad: rgb(214, 100, 100);
30
+
31
+ width: 100%;
32
+ padding: 0.5rem 0 1rem;
33
+ color: var(--cq-paragraph);
34
+ font-size: 0.8rem;
35
+
36
+ .spin {
37
+ animation: spin 1s linear infinite;
38
+ }
39
+
40
+ .cqdiagTitle {
41
+ margin: 0 0 0.75rem;
42
+ color: var(--cq-primary);
43
+ font-size: 0.95rem;
44
+ font-weight: 700;
45
+ }
46
+
47
+ // --- verdict tones -----------------------------------------------------
48
+ // Applied to a value, a badge, a chip or a dot; each picks up whichever of
49
+ // colour/background it needs from the rules below.
50
+ .isOk {
51
+ color: var(--cq-ok);
52
+ }
53
+
54
+ .isWarn {
55
+ color: color-mix(in srgb, var(--cq-warn) 88%, #000);
56
+ }
57
+
58
+ .isBad {
59
+ color: color-mix(in srgb, var(--cq-bad) 78%, #000);
60
+ }
61
+
62
+ .isMuted {
63
+ color: color-mix(in srgb, var(--cq-paragraph) 55%, transparent);
64
+ }
65
+
66
+ // --- the three top panels ----------------------------------------------
67
+ // Auto-fit rather than a fixed three-up: inside the Admin card the panel is
68
+ // narrow, and a squeezed third column is worse than a second row.
69
+ .cqdiagColumns {
70
+ display: grid;
71
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
72
+ gap: 0.75rem;
73
+ margin-bottom: 0.75rem;
74
+ }
75
+
76
+ .cqdiagSection {
77
+ padding: 0.75rem 0.85rem;
78
+ border: 1px solid color-mix(in srgb, var(--cq-primary) 14%, transparent);
79
+ border-radius: var(--cq-br);
80
+ background: var(--cq-background);
81
+ }
82
+
83
+ .cqdiagHeading {
84
+ display: flex;
85
+ gap: 0.4rem;
86
+ align-items: center;
87
+ margin: 0 0 0.55rem;
88
+ color: var(--cq-primary);
89
+ font-size: 0.72rem;
90
+ font-weight: 700;
91
+ letter-spacing: 0.06em;
92
+ text-transform: uppercase;
93
+
94
+ svg {
95
+ flex: none;
96
+ }
97
+ }
98
+
99
+ .cqdiagCopy {
100
+ margin: 0 0 0.6rem;
101
+ color: color-mix(in srgb, var(--cq-paragraph) 65%, transparent);
102
+ font-size: 0.74rem;
103
+ line-height: 1.45;
104
+ }
105
+
106
+ // --- label / value rows ------------------------------------------------
107
+ // Two columns so the values line up down the panel; the value wraps rather
108
+ // than pushing the panel wider, because an auth error can be a sentence.
109
+ .cqdiagRow {
110
+ display: flex;
111
+ gap: 0.5rem;
112
+ align-items: baseline;
113
+ justify-content: space-between;
114
+ padding: 0.22rem 0;
115
+ border-bottom: 1px solid
116
+ color-mix(in srgb, var(--cq-secondary) 8%, transparent);
117
+
118
+ &:last-of-type {
119
+ border-bottom: none;
120
+ }
121
+ }
122
+
123
+ .cqdiagLabel {
124
+ flex: none;
125
+ color: color-mix(in srgb, var(--cq-paragraph) 60%, transparent);
126
+ font-size: 0.72rem;
127
+ }
128
+
129
+ .cqdiagValue {
130
+ display: inline-flex;
131
+ gap: 0.35rem;
132
+ align-items: center;
133
+ min-width: 0;
134
+ font-size: 0.74rem;
135
+ font-weight: 600;
136
+ text-align: right;
137
+ overflow-wrap: anywhere;
138
+ }
139
+
140
+ .cqdiagNone {
141
+ color: color-mix(in srgb, var(--cq-paragraph) 35%, transparent);
142
+ font-weight: 400;
143
+ }
144
+
145
+ // The socket light. Colour comes from the tone class beside it, so the dot
146
+ // and the word it sits next to can never disagree.
147
+ .cqdiagDot {
148
+ display: inline-block;
149
+ flex: none;
150
+ width: 8px;
151
+ height: 8px;
152
+ border-radius: 50%;
153
+ background: currentColor;
154
+ }
155
+
156
+ // --- buttons -----------------------------------------------------------
157
+ .cqdiagActions {
158
+ display: flex;
159
+ flex-wrap: wrap;
160
+ gap: 0.4rem;
161
+ margin-top: 0.65rem;
162
+ }
163
+
164
+ .cqdiagButton {
165
+ display: inline-flex;
166
+ gap: 0.35rem;
167
+ align-items: center;
168
+ justify-content: center;
169
+ padding: 0.3rem 0.6rem;
170
+ border: 1px solid color-mix(in srgb, var(--cq-primary) 25%, transparent);
171
+ border-radius: var(--cq-btn-br);
172
+ background: color-mix(in srgb, var(--cq-primary) 6%, transparent);
173
+ color: var(--cq-primary);
174
+ font-family: inherit;
175
+ font-size: 0.72rem;
176
+ font-weight: 700;
177
+ line-height: 1.4;
178
+ cursor: pointer;
179
+ transition:
180
+ background 0.15s ease,
181
+ border-color 0.15s ease;
182
+
183
+ svg {
184
+ flex: none;
185
+ }
186
+
187
+ &:hover:not(:disabled) {
188
+ background: color-mix(in srgb, var(--cq-primary) 12%, transparent);
189
+ }
190
+
191
+ &:focus-visible {
192
+ outline: 2px solid
193
+ color-mix(in srgb, var(--cq-primary) 45%, transparent);
194
+ outline-offset: 1px;
195
+ }
196
+
197
+ &:disabled {
198
+ border-color: color-mix(
199
+ in srgb,
200
+ var(--cq-secondary) 20%,
201
+ transparent
202
+ );
203
+ background: color-mix(in srgb, var(--cq-secondary) 7%, transparent);
204
+ color: color-mix(in srgb, var(--cq-paragraph) 40%, transparent);
205
+ cursor: default;
206
+ }
207
+ }
208
+
209
+ .cqdiagResult {
210
+ margin: 0.55rem 0 0;
211
+ font-size: 0.74rem;
212
+ font-weight: 600;
213
+ line-height: 1.45;
214
+ }
215
+
216
+ .cqdiagWarning {
217
+ display: flex;
218
+ gap: 0.5rem;
219
+ align-items: flex-start;
220
+ margin: 0 0 0.6rem;
221
+ padding: 0.45rem 0.6rem;
222
+ border: 1px solid color-mix(in srgb, var(--cq-bad) 35%, transparent);
223
+ border-radius: var(--cq-br);
224
+ background: color-mix(in srgb, var(--cq-bad) 8%, transparent);
225
+ color: color-mix(in srgb, var(--cq-bad) 67%, #000);
226
+ font-size: 0.72rem;
227
+ line-height: 1.45;
228
+
229
+ svg {
230
+ flex: none;
231
+ margin-top: 0.12rem;
232
+ }
233
+ }
234
+
235
+ // --- ledger bar + summary ----------------------------------------------
236
+ .cqdiagBar {
237
+ display: flex;
238
+ flex-wrap: wrap;
239
+ gap: 0.5rem;
240
+ align-items: center;
241
+ justify-content: space-between;
242
+
243
+ .cqdiagHeading {
244
+ margin-bottom: 0;
245
+ }
246
+ }
247
+
248
+ .cqdiagBarActions {
249
+ display: flex;
250
+ flex-wrap: wrap;
251
+ gap: 0.5rem;
252
+ align-items: center;
253
+ }
254
+
255
+ .cqdiagCheck {
256
+ display: inline-flex;
257
+ gap: 0.3rem;
258
+ align-items: center;
259
+ color: color-mix(in srgb, var(--cq-paragraph) 65%, transparent);
260
+ font-size: 0.72rem;
261
+ cursor: pointer;
262
+ }
263
+
264
+ .cqdiagSummary {
265
+ display: grid;
266
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
267
+ gap: 0.25rem 1rem;
268
+ margin: 0.65rem 0;
269
+ }
270
+
271
+ .cqdiagSummaryRow {
272
+ display: flex;
273
+ gap: 0.5rem;
274
+ align-items: baseline;
275
+ justify-content: space-between;
276
+ padding: 0.2rem 0;
277
+ }
278
+
279
+ .cqdiagChips {
280
+ display: flex;
281
+ flex-wrap: wrap;
282
+ gap: 0.25rem;
283
+ justify-content: flex-end;
284
+ }
285
+
286
+ .cqdiagChip,
287
+ .cqdiagBadge {
288
+ display: inline-block;
289
+ padding: 0.1rem 0.38rem;
290
+ border: 1px solid currentColor;
291
+ border-radius: 999px;
292
+ background: color-mix(in srgb, currentColor 8%, transparent);
293
+ font-size: 0.66rem;
294
+ font-weight: 700;
295
+ letter-spacing: 0.02em;
296
+ white-space: nowrap;
297
+ }
298
+
299
+ // --- the ledger table --------------------------------------------------
300
+ // The panel is the scroll container, exactly as on the settings page: on a
301
+ // narrow viewport the table slides inside its frame rather than pushing the
302
+ // page body sideways.
303
+ .cqdiagPanel {
304
+ border: 1px solid color-mix(in srgb, var(--cq-primary) 14%, transparent);
305
+ border-radius: var(--cq-br);
306
+ background: var(--cq-background);
307
+ overflow-x: auto;
308
+ }
309
+
310
+ .cqdiagState {
311
+ display: flex;
312
+ gap: 0.5rem;
313
+ align-items: center;
314
+ padding: 1.1rem;
315
+ color: color-mix(in srgb, var(--cq-paragraph) 60%, transparent);
316
+ font-size: 0.78rem;
317
+ }
318
+
319
+ .cqdiagTable {
320
+ width: 100%;
321
+ min-width: 900px;
322
+ border-collapse: collapse;
323
+ font-size: 0.74rem;
324
+
325
+ th,
326
+ td {
327
+ padding: 0.4rem 0.7rem;
328
+ text-align: left;
329
+ vertical-align: middle;
330
+ white-space: nowrap;
331
+ }
332
+
333
+ thead th {
334
+ border-bottom: 1px solid
335
+ color-mix(in srgb, var(--cq-primary) 18%, transparent);
336
+ background: #f0f0f0;
337
+ color: var(--cq-primary);
338
+ font-size: 0.64rem;
339
+ font-weight: 700;
340
+ letter-spacing: 0.06em;
341
+ text-transform: uppercase;
342
+ }
343
+
344
+ tbody tr {
345
+ border-bottom: 1px solid
346
+ color-mix(in srgb, var(--cq-secondary) 10%, transparent);
347
+
348
+ &:last-child {
349
+ border-bottom: none;
350
+ }
351
+
352
+ &:hover {
353
+ background: color-mix(
354
+ in srgb,
355
+ var(--cq-primary) 3.5%,
356
+ transparent
357
+ );
358
+ }
359
+ }
360
+
361
+ // Ids, numbers and timings all read as columns of digits.
362
+ tbody td:first-child,
363
+ tbody td:nth-child(5),
364
+ tbody td:nth-child(6),
365
+ tbody td:nth-child(8) {
366
+ font-family: $cqdiag-mono;
367
+ font-size: 0.7rem;
368
+ font-variant-numeric: tabular-nums;
369
+ }
370
+ }
371
+
372
+ .cqdiagNote {
373
+ margin: 0.7rem 0 0;
374
+ padding: 0.55rem 0.75rem;
375
+ border: 1px solid color-mix(in srgb, var(--cq-primary) 18%, transparent);
376
+ border-radius: var(--cq-br);
377
+ background: color-mix(in srgb, var(--cq-primary) 5%, transparent);
378
+ color: color-mix(in srgb, var(--cq-paragraph) 80%, transparent);
379
+ font-size: 0.72rem;
380
+ line-height: 1.5;
381
+
382
+ strong {
383
+ color: var(--cq-primary);
384
+ }
385
+ }
386
+ }
387
+
388
+ // Same reason as the settings module: the CRM had `spin` globally in
389
+ // main.scss, this library ships it beside the class that references it.
390
+ @keyframes spin {
391
+ from {
392
+ transform: rotate(0deg);
393
+ }
394
+
395
+ to {
396
+ transform: rotate(360deg);
397
+ }
398
+ }
@@ -95,6 +95,12 @@
95
95
  border-left: 4px solid var(--cq-primary);
96
96
  }
97
97
 
98
+ // Direct calls carry their badge colour down the spine too, so a glance at the
99
+ // stack separates "the practice is ringing" from "Jane is ringing".
100
+ .cqpopCardDirect {
101
+ border-left-color: var(--cq-answer);
102
+ }
103
+
98
104
  .cqpopCardLeaving {
99
105
  animation: cqpop-out 0.22s cubic-bezier(0.33, 0, 0.68, 0) both;
100
106
  }
@@ -149,6 +155,13 @@
149
155
  white-space: nowrap;
150
156
  }
151
157
 
158
+ // A direct call rang a person, not a queue. It gets the answer green rather
159
+ // than the brand primary — the only two-colour distinction on the card, and it
160
+ // is the one that says "this is somebody's own phone ringing out".
161
+ .cqpopBadgeDirect {
162
+ background: var(--cq-answer);
163
+ }
164
+
152
165
  // Pulsing "still ringing" indicator inside the badge.
153
166
  .cqpopDot {
154
167
  width: 7px;
@@ -203,6 +216,22 @@
203
216
  color: var(--cq-paragraph);
204
217
  }
205
218
 
219
+ // "Ringing Jane Smith · transferred by Reception" — who a direct call is
220
+ // actually ringing. Quiet: the caller is still the headline.
221
+ .cqpopCallee {
222
+ font-size: 0.74rem;
223
+ font-weight: 600;
224
+ color: color-mix(in srgb, var(--cq-paragraph) 70%, transparent);
225
+ }
226
+
227
+ // One ringing leg was declined, and the card is in its grace period. Muted on
228
+ // purpose: the call may well still be ringing somewhere else.
229
+ .cqpopMissed {
230
+ font-size: 0.68rem;
231
+ font-style: italic;
232
+ color: color-mix(in srgb, var(--cq-paragraph) 55%, transparent);
233
+ }
234
+
206
235
  .cqpopClient {
207
236
  font-size: 0.78rem;
208
237
  color: var(--cq-paragraph);
@@ -255,6 +255,68 @@ $cqsettings-mono: 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
255
255
  color: color-mix(in srgb, var(--cq-paragraph) 30%, transparent);
256
256
  }
257
257
 
258
+ // --- direct calls -------------------------------------------------------
259
+ // Calls that ring one staff member's own extension are not a Zoom call
260
+ // queue, so their row sits under its own heading rather than being read as
261
+ // a queue nobody recognises.
262
+ .cqsettingsSection {
263
+ td {
264
+ padding: 1rem 0.75rem 0.55rem;
265
+ border-top: 2px solid
266
+ color-mix(in srgb, var(--cq-primary) 20%, transparent);
267
+ }
268
+ }
269
+
270
+ .cqsettingsSectionTitle {
271
+ display: block;
272
+ color: var(--cq-primary);
273
+ font-size: 0.82rem;
274
+ font-weight: 700;
275
+ }
276
+
277
+ .cqsettingsSectionText {
278
+ display: block;
279
+ margin-top: 0.15rem;
280
+ color: color-mix(in srgb, var(--cq-paragraph) 60%, transparent);
281
+ font-size: 0.72rem;
282
+ line-height: 1.45;
283
+ }
284
+
285
+ // Same callout language as the page-level info note, at row scale: the
286
+ // pickup code this row takes is a different kind of code from every other
287
+ // row's, and that has to be said where it is typed.
288
+ .cqsettingsSectionNote {
289
+ display: flex;
290
+ gap: 0.4rem;
291
+ align-items: flex-start;
292
+ // Wide enough to read, narrow enough not to stretch across a 940px
293
+ // table and lose the eye on the way back.
294
+ max-width: 78ch;
295
+ margin-top: 0.5rem;
296
+ padding: 0.45rem 0.6rem;
297
+ border: 1px solid color-mix(in srgb, var(--cq-primary) 18%, transparent);
298
+ border-radius: var(--cq-br);
299
+ background-color: color-mix(in srgb, var(--cq-primary) 5%, transparent);
300
+ color: color-mix(in srgb, var(--cq-paragraph) 80%, transparent);
301
+ font-size: 0.72rem;
302
+ font-weight: 400;
303
+ line-height: 1.45;
304
+
305
+ svg {
306
+ flex: none;
307
+ margin-top: 0.1rem;
308
+ color: var(--cq-primary);
309
+ }
310
+ }
311
+
312
+ // "No call queues found" as a table row, for the case where Zoom returned
313
+ // nothing but the direct-calls row still has to be editable.
314
+ .cqsettingsEmptyCell {
315
+ padding: 1rem 0.75rem;
316
+ color: color-mix(in srgb, var(--cq-paragraph) 60%, transparent);
317
+ font-size: 0.82rem;
318
+ }
319
+
258
320
  // --- pills -------------------------------------------------------------
259
321
  // One shape for both status columns: muted by default, tinted green only
260
322
  // when the answer is "yes, this is working".
@@ -448,6 +510,37 @@ $cqsettings-mono: 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
448
510
  cursor: default;
449
511
  }
450
512
  }
513
+
514
+ // --- diagnostics disclosure --------------------------------------------
515
+ // Folded away under the table: an admin who is not chasing a fault should
516
+ // see one quiet line, not a wall of socket state.
517
+ .cqsettingsDiagnostics {
518
+ margin-top: 1rem;
519
+ }
520
+
521
+ .cqsettingsDisclosure {
522
+ display: inline-flex;
523
+ gap: 0.3rem;
524
+ align-items: center;
525
+ padding: 0.3rem 0;
526
+ border: none;
527
+ background: none;
528
+ color: var(--cq-primary);
529
+ font-family: inherit;
530
+ font-size: 0.78rem;
531
+ font-weight: 700;
532
+ cursor: pointer;
533
+
534
+ svg {
535
+ flex: none;
536
+ }
537
+
538
+ &:focus-visible {
539
+ outline: 2px solid
540
+ color-mix(in srgb, var(--cq-primary) 45%, transparent);
541
+ outline-offset: 2px;
542
+ }
543
+ }
451
544
  }
452
545
 
453
546
  // The CRM had this globally in main.scss; the library ships it with the module
package/src/index.js CHANGED
@@ -140,6 +140,12 @@ import {
140
140
  /** Call Queue Components */
141
141
  import CallQueuePop from './components/callQueue/CallQueuePop';
142
142
  import CallQueueSettings from './components/callQueue/CallQueueSettings';
143
+ import CallQueueDiagnostics from './components/callQueue/CallQueueDiagnostics';
144
+ import {
145
+ CALL_POP_STATUS_EVENT,
146
+ getCallPopStatus,
147
+ updateCallPopStatus,
148
+ } from './components/callQueue/callPopStatus';
143
149
 
144
150
  // Vault — the staff password manager. VaultQuickSearch mounts through
145
151
  // Navigation's `renderers` slot; VaultManager is the page behind it.
@@ -355,8 +361,12 @@ export {
355
361
  buildEnv,
356
362
  BusinessCardOcr,
357
363
  Call,
364
+ CALL_POP_STATUS_EVENT,
365
+ CallQueueDiagnostics,
358
366
  CallQueuePop,
359
367
  CallQueueSettings,
368
+ getCallPopStatus,
369
+ updateCallPopStatus,
360
370
  cleanBase32,
361
371
  decodeFromBlob,
362
372
  decodeFromSource,