chordia-ui 4.0.2 → 4.0.4
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/DataTable2.cjs.js +1 -1
- package/dist/DataTable2.cjs.js.map +1 -1
- package/dist/DataTable2.es.js +437 -398
- package/dist/DataTable2.es.js.map +1 -1
- package/dist/components/UpdatedInteractionDetails.cjs.js +4 -4
- package/dist/components/UpdatedInteractionDetails.cjs.js.map +1 -1
- package/dist/components/UpdatedInteractionDetails.es.js +1891 -1486
- package/dist/components/UpdatedInteractionDetails.es.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +50 -49
- package/package.json +1 -1
- package/src/components/UpdatedInteractionDetails/InteractionQualityOverview.jsx +508 -0
- package/src/components/UpdatedInteractionDetails/UpdatedCompassScore.jsx +42 -35
- package/src/components/UpdatedInteractionDetails/UpdatedInteractionDetails.jsx +68 -6
- package/src/components/UpdatedInteractionDetails/index.js +1 -0
- package/src/components/data/DataTable2.jsx +88 -12
- package/src/components/index.js +1 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import React, { useState, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
SquareChartGantt,
|
|
4
|
+
CalendarDays,
|
|
5
|
+
PhoneIncoming,
|
|
6
|
+
PhoneOutgoing,
|
|
7
|
+
Headset,
|
|
8
|
+
CircleUser,
|
|
9
|
+
ChevronRight,
|
|
10
|
+
ChevronDown,
|
|
11
|
+
Pencil,
|
|
12
|
+
Award,
|
|
13
|
+
Lightbulb,
|
|
14
|
+
Info,
|
|
15
|
+
} from 'lucide-react';
|
|
16
|
+
import { useMediaQuery } from '../../utils/useMediaQuery';
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Client-specific "Interaction Quality" overview.
|
|
20
|
+
*
|
|
21
|
+
* Figma nodes:
|
|
22
|
+
* 1910-11142 — section header bar
|
|
23
|
+
* 1910-10758 — 4-column body card
|
|
24
|
+
*
|
|
25
|
+
* Purely presentational: every value comes in through props, no business logic
|
|
26
|
+
* or data derivation lives here (the host builds the data bag). Rendered by
|
|
27
|
+
* UpdatedInteractionDetails only when its `oldInteractionDetailsView` flag is on.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// Averta — the app's display font. Use `--default-font-averta` (not
|
|
31
|
+
// `--font-sans`, which can resolve to a system fallback in some scopes) so the
|
|
32
|
+
// text matches the rest of the interaction detail view (e.g. the transcript).
|
|
33
|
+
const AVERTA = 'var(--default-font-averta)';
|
|
34
|
+
|
|
35
|
+
const COLORS = {
|
|
36
|
+
strong: 'var(--Grey-Strong, #2E3236)',
|
|
37
|
+
muted: 'var(--Grey-Muted, #808183)',
|
|
38
|
+
absent: 'var(--Grey-absent, #D9D9D9)',
|
|
39
|
+
white: 'var(--Grey-White, #FFF)',
|
|
40
|
+
accent: 'var(--rail-orange, #C98A5A)',
|
|
41
|
+
track: 'var(--rail-surface-2, #E3E1D7)',
|
|
42
|
+
surface: '#FAF9F5',
|
|
43
|
+
infoIcon: 'var(--color-input-border, #ACACAD)',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// 5-segment score bar — mirrors the private helper in UpdatedCompassScore.jsx.
|
|
47
|
+
const ScoreBar = ({ value = 0, maxValue = 5 }) => {
|
|
48
|
+
const numericValue = Number(value);
|
|
49
|
+
const clampedValue = Number.isFinite(numericValue)
|
|
50
|
+
? Math.min(Math.max(numericValue, 0), maxValue)
|
|
51
|
+
: 0;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div style={{ display: 'flex', gap: 2 }}>
|
|
55
|
+
{Array.from({ length: maxValue }, (_, i) => {
|
|
56
|
+
const segmentFill = Math.max(0, Math.min(1, clampedValue - i));
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
key={i}
|
|
60
|
+
style={{
|
|
61
|
+
width: 22,
|
|
62
|
+
height: 6,
|
|
63
|
+
borderRadius: 1,
|
|
64
|
+
background: COLORS.track,
|
|
65
|
+
overflow: 'hidden',
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
{segmentFill > 0 && (
|
|
69
|
+
<div
|
|
70
|
+
style={{
|
|
71
|
+
width: `${segmentFill * 100}%`,
|
|
72
|
+
height: '100%',
|
|
73
|
+
background: COLORS.strong,
|
|
74
|
+
}}
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
})}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Fixed-position tooltip that escapes overflow containers — mirrors the
|
|
85
|
+
// InfoTooltip pattern used across the UpdatedInteractionDetails folder.
|
|
86
|
+
const InfoTooltip = ({ text, children }) => {
|
|
87
|
+
const [show, setShow] = useState(false);
|
|
88
|
+
const ref = useRef(null);
|
|
89
|
+
const [pos, setPos] = useState({ top: 0, left: 0 });
|
|
90
|
+
|
|
91
|
+
const handleEnter = () => {
|
|
92
|
+
if (ref.current) {
|
|
93
|
+
const rect = ref.current.getBoundingClientRect();
|
|
94
|
+
setPos({ top: rect.top - 8, left: rect.left + rect.width / 2 });
|
|
95
|
+
}
|
|
96
|
+
setShow(true);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
ref={ref}
|
|
102
|
+
style={{ position: 'relative', display: 'inline-flex' }}
|
|
103
|
+
onMouseEnter={handleEnter}
|
|
104
|
+
onMouseLeave={() => setShow(false)}
|
|
105
|
+
>
|
|
106
|
+
{children}
|
|
107
|
+
{show && text && (
|
|
108
|
+
<div style={{
|
|
109
|
+
position: 'fixed',
|
|
110
|
+
top: pos.top,
|
|
111
|
+
left: pos.left,
|
|
112
|
+
transform: 'translate(-50%, -100%)',
|
|
113
|
+
display: 'flex',
|
|
114
|
+
width: 'max-content',
|
|
115
|
+
maxWidth: 300,
|
|
116
|
+
padding: 10,
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
borderRadius: 4,
|
|
119
|
+
border: `1px solid ${COLORS.absent}`,
|
|
120
|
+
background: COLORS.strong,
|
|
121
|
+
zIndex: 9999,
|
|
122
|
+
pointerEvents: 'none',
|
|
123
|
+
}}>
|
|
124
|
+
<span style={{ color: '#FFF', fontSize: 12, fontWeight: 400, lineHeight: '140%' }}>
|
|
125
|
+
{text}
|
|
126
|
+
</span>
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Normalise a strengths/improvements entry that may be a plain string or a
|
|
134
|
+
// { text, quote } object — same handling as UpdatedCoachingSynthesisCard.jsx.
|
|
135
|
+
const itemText = (item) => (typeof item === 'string' ? item : item?.text || '');
|
|
136
|
+
|
|
137
|
+
// "What was great?" / "What can be better?" card.
|
|
138
|
+
const FeedbackCard = ({ icon: Icon, title, items = [], loading = false, emptyText }) => (
|
|
139
|
+
<div style={{
|
|
140
|
+
display: 'flex',
|
|
141
|
+
flexDirection: 'column',
|
|
142
|
+
gap: 8,
|
|
143
|
+
padding: 16,
|
|
144
|
+
borderRadius: 8,
|
|
145
|
+
background: COLORS.surface,
|
|
146
|
+
flex: 1,
|
|
147
|
+
minWidth: 0,
|
|
148
|
+
alignSelf: 'stretch',
|
|
149
|
+
}}>
|
|
150
|
+
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
|
|
151
|
+
<Icon size={16} color={COLORS.strong} strokeWidth={1.75} />
|
|
152
|
+
<span style={{
|
|
153
|
+
flex: 1,
|
|
154
|
+
fontSize: 14,
|
|
155
|
+
fontWeight: 600,
|
|
156
|
+
color: COLORS.strong,
|
|
157
|
+
fontFamily: AVERTA,
|
|
158
|
+
lineHeight: '20px',
|
|
159
|
+
}}>
|
|
160
|
+
{title}
|
|
161
|
+
</span>
|
|
162
|
+
</div>
|
|
163
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
164
|
+
{loading ? (
|
|
165
|
+
<span style={{ fontSize: 14, color: COLORS.muted, fontFamily: AVERTA, lineHeight: '20px' }}>
|
|
166
|
+
Loading...
|
|
167
|
+
</span>
|
|
168
|
+
) : items.length > 0 ? (
|
|
169
|
+
items.map((item, i) => {
|
|
170
|
+
const text = itemText(item);
|
|
171
|
+
if (!text) return null;
|
|
172
|
+
return (
|
|
173
|
+
<p key={i} style={{
|
|
174
|
+
margin: 0,
|
|
175
|
+
fontSize: 14,
|
|
176
|
+
fontWeight: 400,
|
|
177
|
+
color: COLORS.strong,
|
|
178
|
+
fontFamily: AVERTA,
|
|
179
|
+
lineHeight: '20px',
|
|
180
|
+
wordBreak: 'break-word',
|
|
181
|
+
}}>
|
|
182
|
+
{text}
|
|
183
|
+
</p>
|
|
184
|
+
);
|
|
185
|
+
})
|
|
186
|
+
) : (
|
|
187
|
+
<span style={{ fontSize: 14, color: COLORS.muted, fontFamily: AVERTA, lineHeight: '20px' }}>
|
|
188
|
+
{emptyText}
|
|
189
|
+
</span>
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const InteractionQualityOverview = ({
|
|
196
|
+
// Header (Figma 1910-11142)
|
|
197
|
+
title = 'Interaction Quality',
|
|
198
|
+
dateStr = '',
|
|
199
|
+
direction = 'Inbound',
|
|
200
|
+
agentName = 'Agent',
|
|
201
|
+
agentFullName,
|
|
202
|
+
customerName = 'Customer',
|
|
203
|
+
customerFullName,
|
|
204
|
+
sessionCount = 0,
|
|
205
|
+
onViewAllSessions,
|
|
206
|
+
canEdit = false,
|
|
207
|
+
onEditInteraction,
|
|
208
|
+
moreDetailsContent = null,
|
|
209
|
+
// Body card (Figma 1910-10758)
|
|
210
|
+
scorePercent = null,
|
|
211
|
+
scoreOwnerName = '',
|
|
212
|
+
details = [],
|
|
213
|
+
summary = '',
|
|
214
|
+
csat = { value: 0, maxValue: 5 },
|
|
215
|
+
strengths = [],
|
|
216
|
+
improvements = [],
|
|
217
|
+
loading = false,
|
|
218
|
+
}) => {
|
|
219
|
+
const isPhone = useMediaQuery('(max-width: 640px)');
|
|
220
|
+
const [showMoreDetails, setShowMoreDetails] = useState(false);
|
|
221
|
+
|
|
222
|
+
const DirectionIcon = String(direction).toLowerCase() === 'outbound' ? PhoneOutgoing : PhoneIncoming;
|
|
223
|
+
const scoreLabel = scoreOwnerName
|
|
224
|
+
? `${scoreOwnerName}'s Compass Score`
|
|
225
|
+
: 'Compass Score';
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 24, alignSelf: 'stretch' }}>
|
|
229
|
+
{/* ── Header bar (Figma 1910-11142) ── */}
|
|
230
|
+
<div style={{
|
|
231
|
+
display: 'flex',
|
|
232
|
+
flexWrap: isPhone ? 'wrap' : 'nowrap',
|
|
233
|
+
alignItems: isPhone ? 'flex-start' : 'center',
|
|
234
|
+
justifyContent: 'space-between',
|
|
235
|
+
gap: isPhone ? '8px 16px' : 16,
|
|
236
|
+
paddingBottom: 12,
|
|
237
|
+
borderBottom: `1px solid ${COLORS.absent}`,
|
|
238
|
+
alignSelf: 'stretch',
|
|
239
|
+
}}>
|
|
240
|
+
{/* Title */}
|
|
241
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
|
|
242
|
+
<SquareChartGantt size={24} color={COLORS.accent} strokeWidth={2} />
|
|
243
|
+
<span style={{
|
|
244
|
+
fontSize: 16,
|
|
245
|
+
fontWeight: 600,
|
|
246
|
+
color: COLORS.strong,
|
|
247
|
+
fontFamily: AVERTA,
|
|
248
|
+
lineHeight: 1.2,
|
|
249
|
+
textTransform: 'uppercase',
|
|
250
|
+
whiteSpace: 'nowrap',
|
|
251
|
+
}}>
|
|
252
|
+
{title}
|
|
253
|
+
</span>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
{/* Metadata */}
|
|
257
|
+
<div style={{
|
|
258
|
+
display: 'flex',
|
|
259
|
+
flexWrap: isPhone ? 'wrap' : 'nowrap',
|
|
260
|
+
alignItems: 'center',
|
|
261
|
+
gap: isPhone ? '8px 12px' : 16,
|
|
262
|
+
...(isPhone ? { width: '100%' } : {}),
|
|
263
|
+
}}>
|
|
264
|
+
{/* Date */}
|
|
265
|
+
{dateStr && (
|
|
266
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 0' }}>
|
|
267
|
+
<CalendarDays size={16} color={COLORS.muted} strokeWidth={1.5} />
|
|
268
|
+
<span style={{ fontSize: 13, fontWeight: 400, color: COLORS.strong, fontFamily: AVERTA, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
|
|
269
|
+
{dateStr}
|
|
270
|
+
</span>
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
|
|
274
|
+
{/* Direction */}
|
|
275
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 0' }}>
|
|
276
|
+
<DirectionIcon size={16} color={COLORS.muted} strokeWidth={1.5} />
|
|
277
|
+
<span style={{ fontSize: 13, fontWeight: 400, color: COLORS.strong, fontFamily: AVERTA, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
|
|
278
|
+
{direction}
|
|
279
|
+
</span>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
{/* Agent */}
|
|
283
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 0' }} title={agentFullName || agentName}>
|
|
284
|
+
<Headset size={16} color={COLORS.muted} strokeWidth={1.5} />
|
|
285
|
+
<span style={{ fontSize: 13, fontWeight: 400, color: COLORS.strong, fontFamily: AVERTA, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
|
|
286
|
+
{agentName}
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
{/* More Details toggle — reveals the reused context grid below */}
|
|
291
|
+
{moreDetailsContent && (
|
|
292
|
+
<button
|
|
293
|
+
type="button"
|
|
294
|
+
onClick={() => setShowMoreDetails((v) => !v)}
|
|
295
|
+
style={{
|
|
296
|
+
display: 'flex',
|
|
297
|
+
alignItems: 'center',
|
|
298
|
+
gap: 6,
|
|
299
|
+
padding: '8px 0',
|
|
300
|
+
background: 'none',
|
|
301
|
+
border: 'none',
|
|
302
|
+
cursor: 'pointer',
|
|
303
|
+
}}
|
|
304
|
+
>
|
|
305
|
+
<span style={{ fontSize: 13, fontWeight: 500, color: COLORS.strong, fontFamily: AVERTA, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
|
|
306
|
+
More Details
|
|
307
|
+
</span>
|
|
308
|
+
<ChevronDown
|
|
309
|
+
size={16}
|
|
310
|
+
color={COLORS.strong}
|
|
311
|
+
strokeWidth={1.75}
|
|
312
|
+
style={{ transform: showMoreDetails ? 'rotate(180deg)' : 'none', transition: 'transform 0.15s' }}
|
|
313
|
+
/>
|
|
314
|
+
</button>
|
|
315
|
+
)}
|
|
316
|
+
|
|
317
|
+
{/* Edit */}
|
|
318
|
+
{canEdit && onEditInteraction && (
|
|
319
|
+
<button
|
|
320
|
+
type="button"
|
|
321
|
+
onClick={onEditInteraction}
|
|
322
|
+
style={{
|
|
323
|
+
display: 'flex',
|
|
324
|
+
alignItems: 'center',
|
|
325
|
+
justifyContent: 'center',
|
|
326
|
+
gap: 8,
|
|
327
|
+
height: 28,
|
|
328
|
+
padding: '8px 10px',
|
|
329
|
+
borderRadius: 10,
|
|
330
|
+
border: '1px solid var(--Neutral-250, #BFBFBF)',
|
|
331
|
+
background: 'none',
|
|
332
|
+
cursor: 'pointer',
|
|
333
|
+
}}
|
|
334
|
+
>
|
|
335
|
+
<Pencil size={16} color="var(--Neutral-800, #323232)" strokeWidth={1.75} />
|
|
336
|
+
<span style={{ fontSize: 14, fontWeight: 400, color: 'var(--Neutral-800, #323232)', fontFamily: AVERTA, lineHeight: '24px', whiteSpace: 'nowrap' }}>
|
|
337
|
+
Edit
|
|
338
|
+
</span>
|
|
339
|
+
</button>
|
|
340
|
+
)}
|
|
341
|
+
|
|
342
|
+
{/* Customer sessions pill */}
|
|
343
|
+
{sessionCount > 1 ? (
|
|
344
|
+
<button
|
|
345
|
+
type="button"
|
|
346
|
+
onClick={onViewAllSessions}
|
|
347
|
+
title={customerFullName || customerName}
|
|
348
|
+
style={{
|
|
349
|
+
display: 'flex',
|
|
350
|
+
alignItems: 'center',
|
|
351
|
+
gap: 16,
|
|
352
|
+
padding: '0 16px',
|
|
353
|
+
height: 34,
|
|
354
|
+
borderRadius: 8,
|
|
355
|
+
background: COLORS.track,
|
|
356
|
+
border: 'none',
|
|
357
|
+
cursor: onViewAllSessions ? 'pointer' : 'default',
|
|
358
|
+
}}
|
|
359
|
+
>
|
|
360
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 4, minWidth: 0 }}>
|
|
361
|
+
<CircleUser size={16} color={COLORS.strong} strokeWidth={1.5} />
|
|
362
|
+
<span style={{ fontSize: 13, fontWeight: 400, color: COLORS.strong, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
|
|
363
|
+
<span style={{ fontWeight: 600 }}>{customerName}</span>{' '}
|
|
364
|
+
<span>({sessionCount} Sessions)</span>
|
|
365
|
+
</span>
|
|
366
|
+
</div>
|
|
367
|
+
<ChevronRight size={16} color={COLORS.strong} strokeWidth={1.7} />
|
|
368
|
+
</button>
|
|
369
|
+
) : (
|
|
370
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '8px 0' }} title={customerFullName || customerName}>
|
|
371
|
+
<CircleUser size={16} color={COLORS.muted} strokeWidth={1.5} />
|
|
372
|
+
<span style={{ fontSize: 13, fontWeight: 400, color: COLORS.strong, lineHeight: 1.2, whiteSpace: 'nowrap' }}>
|
|
373
|
+
{customerName}
|
|
374
|
+
</span>
|
|
375
|
+
</div>
|
|
376
|
+
)}
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
{/* More Details expandable — reuses the existing context grid element */}
|
|
381
|
+
{moreDetailsContent && showMoreDetails && (
|
|
382
|
+
<div style={{ alignSelf: 'stretch' }}>
|
|
383
|
+
{moreDetailsContent}
|
|
384
|
+
</div>
|
|
385
|
+
)}
|
|
386
|
+
|
|
387
|
+
{/* ── Body: 4-column card (Figma 1910-10758) ── */}
|
|
388
|
+
<div style={{
|
|
389
|
+
display: 'flex',
|
|
390
|
+
flexDirection: isPhone ? 'column' : 'row',
|
|
391
|
+
alignItems: 'stretch',
|
|
392
|
+
gap: 24,
|
|
393
|
+
alignSelf: 'stretch',
|
|
394
|
+
}}>
|
|
395
|
+
{/* Col 1 — score + details. Top-aligned so the score lines up with the
|
|
396
|
+
"Summary" title and the feedback-card headers (Figma 1910-10759). */}
|
|
397
|
+
<div style={{
|
|
398
|
+
display: 'flex',
|
|
399
|
+
flexDirection: 'column',
|
|
400
|
+
gap: 24,
|
|
401
|
+
flex: 1,
|
|
402
|
+
minWidth: 0,
|
|
403
|
+
justifyContent: 'flex-start',
|
|
404
|
+
}}>
|
|
405
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, color: COLORS.strong }}>
|
|
406
|
+
<span style={{
|
|
407
|
+
fontSize: 80,
|
|
408
|
+
fontWeight: 400,
|
|
409
|
+
fontFamily: AVERTA,
|
|
410
|
+
lineHeight: 1,
|
|
411
|
+
whiteSpace: 'nowrap',
|
|
412
|
+
}}>
|
|
413
|
+
{scorePercent != null ? (
|
|
414
|
+
<>
|
|
415
|
+
{scorePercent}
|
|
416
|
+
{/* Percent sign as a refined unit suffix: about half the
|
|
417
|
+
digit size, same colour as the number, on its baseline. */}
|
|
418
|
+
<span style={{
|
|
419
|
+
fontSize: 36,
|
|
420
|
+
fontWeight: 400,
|
|
421
|
+
marginLeft: 4,
|
|
422
|
+
}}>
|
|
423
|
+
%
|
|
424
|
+
</span>
|
|
425
|
+
</>
|
|
426
|
+
) : '—'}
|
|
427
|
+
</span>
|
|
428
|
+
<span style={{ fontSize: 14, fontWeight: 600, fontFamily: AVERTA, lineHeight: '20px' }}>
|
|
429
|
+
{scoreLabel}
|
|
430
|
+
</span>
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
{details.length > 0 && (
|
|
434
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, fontSize: 13, lineHeight: 1.2 }}>
|
|
435
|
+
{details.map((row, i) => (
|
|
436
|
+
<div key={i} style={{
|
|
437
|
+
display: 'flex',
|
|
438
|
+
flexWrap: 'wrap',
|
|
439
|
+
alignItems: 'center',
|
|
440
|
+
justifyContent: 'space-between',
|
|
441
|
+
gap: '4px 24px',
|
|
442
|
+
paddingTop: 8,
|
|
443
|
+
paddingBottom: 12,
|
|
444
|
+
borderBottom: `1px solid ${COLORS.absent}`,
|
|
445
|
+
}}>
|
|
446
|
+
<span style={{ color: COLORS.strong, fontFamily: AVERTA }}>{row.label}</span>
|
|
447
|
+
<span style={{ color: COLORS.muted, fontFamily: AVERTA }}>{row.value}</span>
|
|
448
|
+
</div>
|
|
449
|
+
))}
|
|
450
|
+
</div>
|
|
451
|
+
)}
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
{/* Col 2 — summary + CSAT. Matches the feedback cards' 16px top inset so
|
|
455
|
+
the "Summary" title lines up with the card titles (Figma 1910-10759). */}
|
|
456
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, flex: 1, minWidth: 0, paddingTop: 16 }}>
|
|
457
|
+
<span style={{ fontSize: 14, fontWeight: 600, color: COLORS.strong, fontFamily: AVERTA, lineHeight: '20px' }}>
|
|
458
|
+
Summary
|
|
459
|
+
</span>
|
|
460
|
+
<p style={{
|
|
461
|
+
margin: 0,
|
|
462
|
+
fontSize: 14,
|
|
463
|
+
fontWeight: 400,
|
|
464
|
+
color: COLORS.strong,
|
|
465
|
+
fontFamily: AVERTA,
|
|
466
|
+
lineHeight: '20px',
|
|
467
|
+
wordBreak: 'break-word',
|
|
468
|
+
}}>
|
|
469
|
+
{loading && !summary ? 'Loading…' : summary || '—'}
|
|
470
|
+
</p>
|
|
471
|
+
{csat && (
|
|
472
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, padding: '8px 0' }}>
|
|
473
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
474
|
+
<span style={{ fontSize: 12, fontWeight: 400, color: COLORS.strong, fontFamily: AVERTA, lineHeight: 1.2 }}>
|
|
475
|
+
CSAT Score
|
|
476
|
+
</span>
|
|
477
|
+
<InfoTooltip text="Predicted Customer Satisfaction">
|
|
478
|
+
<Info size={14} color={COLORS.infoIcon} strokeWidth={1} style={{ cursor: 'pointer' }} />
|
|
479
|
+
</InfoTooltip>
|
|
480
|
+
</div>
|
|
481
|
+
<ScoreBar value={csat.value} maxValue={csat.maxValue || 5} />
|
|
482
|
+
</div>
|
|
483
|
+
)}
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
{/* Col 3 — What was great? */}
|
|
487
|
+
<FeedbackCard
|
|
488
|
+
icon={Award}
|
|
489
|
+
title="What was great?"
|
|
490
|
+
items={strengths}
|
|
491
|
+
loading={loading}
|
|
492
|
+
emptyText="No highlights available."
|
|
493
|
+
/>
|
|
494
|
+
|
|
495
|
+
{/* Col 4 — What can be better? */}
|
|
496
|
+
<FeedbackCard
|
|
497
|
+
icon={Lightbulb}
|
|
498
|
+
title="What can be better?"
|
|
499
|
+
items={improvements}
|
|
500
|
+
loading={loading}
|
|
501
|
+
emptyText="No suggestions available."
|
|
502
|
+
/>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
);
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
export default InteractionQualityOverview;
|
|
@@ -323,43 +323,50 @@ const UpdatedCompassScore = ({
|
|
|
323
323
|
alignItems: 'center',
|
|
324
324
|
display: 'flex',
|
|
325
325
|
}}>
|
|
326
|
-
|
|
326
|
+
{/* Gauge with the compass pin overlaid. The pin is absolutely
|
|
327
|
+
positioned (out of layout flow) so it doesn't push the score down
|
|
328
|
+
— the number then sits directly below the gauge (Figma 1898-18916,
|
|
329
|
+
"Label + Value" at top:128 just under the 120px gauge). */}
|
|
330
|
+
<div style={{ position: 'relative', width: '100%', display: 'flex', justifyContent: 'center' }}>
|
|
331
|
+
<GaugeMeter score={score} minScore={minScore} maxScore={maxScore} />
|
|
327
332
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
333
|
+
{/* Compass pin — needle rotates to point at the score. Centered in
|
|
334
|
+
the lower part of the gauge opening; `top` is a percentage so it
|
|
335
|
+
tracks the gauge as it scales down responsively. */}
|
|
336
|
+
{(() => {
|
|
337
|
+
// score=0 → needle points left (180°); score=max → points right (0°).
|
|
338
|
+
// Pin's default needle points top-right (~45°), so rotate by
|
|
339
|
+
// -(targetAngle - 45) to align it with the gauge position.
|
|
340
|
+
const pct = maxScore > minScore ? (clampedScore - minScore) / (maxScore - minScore) : 0;
|
|
341
|
+
const targetAngle = 180 - pct * 180;
|
|
342
|
+
const rotateDeg = -(targetAngle - 45);
|
|
343
|
+
return (
|
|
344
|
+
<svg
|
|
345
|
+
width="34" height="35" viewBox="0 0 34 35" fill="none"
|
|
346
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
347
|
+
style={{
|
|
348
|
+
position: 'absolute',
|
|
349
|
+
left: '50%',
|
|
350
|
+
top: '82%',
|
|
351
|
+
transform: `translate(-50%, -50%) rotate(${rotateDeg}deg)`,
|
|
352
|
+
transformOrigin: 'center center',
|
|
353
|
+
}}
|
|
354
|
+
>
|
|
355
|
+
<path d="M16 25.2169C19.958 25.2169 23.1667 22.0083 23.1667 18.0503C23.1667 14.0922 19.958 10.8836 16 10.8836C12.042 10.8836 8.83334 14.0922 8.83334 18.0503C8.83334 22.0083 12.042 25.2169 16 25.2169Z" fill="var(--rail-orange, #C98A5A)" />
|
|
356
|
+
<path fillRule="evenodd" clipRule="evenodd" d="M30.4791 11.2328L33.4351 0L21.6888 3.09113C19.9212 2.41855 18.0036 2.05025 16 2.05025C7.16344 2.05025 0 9.2137 0 18.0503C0 26.8868 7.16344 34.0503 16 34.0503C24.8366 34.0503 32 26.8868 32 18.0503C32 15.6119 31.4546 13.301 30.4791 11.2328ZM16 30.5503C22.9036 30.5503 28.5 24.9538 28.5 18.0503C28.5 11.1467 22.9036 5.55025 16 5.55025C9.09644 5.55025 3.5 11.1467 3.5 18.0503C3.5 24.9538 9.09644 30.5503 16 30.5503Z" fill="var(--rail-orange, #C98A5A)" />
|
|
357
|
+
</svg>
|
|
358
|
+
);
|
|
359
|
+
})()}
|
|
360
|
+
</div>
|
|
354
361
|
|
|
355
|
-
{/* Score number below gauge —
|
|
356
|
-
brackets next to it (Figma
|
|
362
|
+
{/* Score number just below the gauge — outcome value in round
|
|
363
|
+
brackets next to it (Figma 1898-18916), e.g. "4 (80%)". */}
|
|
357
364
|
<div style={{
|
|
358
365
|
fontSize: 28,
|
|
359
366
|
fontWeight: 400,
|
|
360
367
|
color: COLORS.strong,
|
|
361
368
|
fontFamily: 'var(--font-sans)',
|
|
362
|
-
lineHeight:
|
|
369
|
+
lineHeight: '28px',
|
|
363
370
|
textAlign: 'center',
|
|
364
371
|
marginTop: 8,
|
|
365
372
|
display: 'flex',
|
|
@@ -369,19 +376,19 @@ const UpdatedCompassScore = ({
|
|
|
369
376
|
}}>
|
|
370
377
|
<span>{scoreText}</span>
|
|
371
378
|
{outcomePercent != null && (
|
|
372
|
-
<span style={{ fontSize:
|
|
379
|
+
<span style={{ fontSize: 14, color: COLORS.muted, lineHeight: '28px' }}>
|
|
373
380
|
({outcomePercent}%)
|
|
374
381
|
</span>
|
|
375
382
|
)}
|
|
376
383
|
</div>
|
|
377
384
|
<div style={{
|
|
378
|
-
fontSize:
|
|
379
|
-
fontWeight:
|
|
385
|
+
fontSize: 14,
|
|
386
|
+
fontWeight: 400,
|
|
380
387
|
color: COLORS.strong,
|
|
381
388
|
fontFamily: 'var(--font-sans)',
|
|
382
|
-
lineHeight:
|
|
389
|
+
lineHeight: '20px',
|
|
383
390
|
textAlign: 'center',
|
|
384
|
-
marginTop:
|
|
391
|
+
marginTop: 2,
|
|
385
392
|
}}>
|
|
386
393
|
Compass Score
|
|
387
394
|
</div>
|