@nualang/nualang-ui-components 0.1.1433 → 0.1.1434

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,658 @@
1
+ import { useId, useState } from "react";
2
+ import { Box, Button, Card, Chip, CircularProgress, Collapse, IconButton, Tooltip, Typography } from "@mui/material";
3
+ import { alpha } from "@mui/material/styles";
4
+ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
5
+ import ExpandLessIcon from "@mui/icons-material/ExpandLess";
6
+ import HelpOutlinedIcon from "@mui/icons-material/HelpOutlined";
7
+ import SignalCellularAltIcon from "@mui/icons-material/SignalCellularAlt";
8
+ import StarIcon from "@mui/icons-material/Star";
9
+ import TrendingUpIcon from "@mui/icons-material/TrendingUp";
10
+ import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome";
11
+ import SpellcheckIcon from "@mui/icons-material/Spellcheck";
12
+ import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
13
+ import PDFViewer from "../../Dialogs/PDFViewer/PDFViewer";
14
+ import level1Rubric from "../../Dialogs/PDFViewer/rubrics/level1Rubric.pdf";
15
+ import level2Rubric from "../../Dialogs/PDFViewer/rubrics/level2Rubric.pdf";
16
+ import level3Rubric from "../../Dialogs/PDFViewer/rubrics/level3Rubric.pdf";
17
+ import level4Rubric from "../../Dialogs/PDFViewer/rubrics/level4Rubric.pdf";
18
+ import { SESSION_REPORT_RATINGS, RATING_LABEL_KEYS, domainLabelKey, getRatedDomains } from "./sessionReportDomains";
19
+ import { CorrectionList } from "./SessionReportFocusAreas";
20
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
21
+ const RUBRIC_PDFS = {
22
+ 1: level1Rubric,
23
+ 2: level2Rubric,
24
+ 3: level3Rubric,
25
+ 4: level4Rubric
26
+ };
27
+ const readStoredCollapsed = (storageKey, fallback) => {
28
+ if (!storageKey) return fallback;
29
+ try {
30
+ const stored = window.localStorage.getItem(storageKey);
31
+ return stored === null ? fallback : stored === "true";
32
+ } catch {
33
+ return fallback;
34
+ }
35
+ };
36
+ const writeStoredCollapsed = (storageKey, collapsed) => {
37
+ if (!storageKey) return;
38
+ try {
39
+ window.localStorage.setItem(storageKey, String(collapsed));
40
+ } catch {
41
+ // Private mode or blocked storage: the card still works, it just won't remember.
42
+ }
43
+ };
44
+
45
+ // "New" chip
46
+ const readSeen = newStorageKey => {
47
+ if (!newStorageKey) return true;
48
+ try {
49
+ return window.localStorage.getItem(newStorageKey) === "true";
50
+ } catch {
51
+ return false;
52
+ }
53
+ };
54
+ const writeSeen = newStorageKey => {
55
+ if (!newStorageKey) return;
56
+ try {
57
+ window.localStorage.setItem(newStorageKey, "true");
58
+ } catch {
59
+ // Blocked storage: the chip is hidden for the rest of this visit only.
60
+ }
61
+ };
62
+
63
+ // Read by screen readers, not shown. (MUI's visuallyHidden is in @mui/utils, only a
64
+ // transitive dependency here.)
65
+ const visuallyHidden = {
66
+ position: "absolute",
67
+ width: "1px",
68
+ height: "1px",
69
+ overflow: "hidden",
70
+ clipPath: "inset(50%)",
71
+ whiteSpace: "nowrap"
72
+ };
73
+ const describeDomain = ({
74
+ evidence = "",
75
+ grammarInsights = []
76
+ }) => [evidence, ...grammarInsights].filter(Boolean).join(" ");
77
+ function Meter({
78
+ rating,
79
+ size
80
+ }) {
81
+ const filled = SESSION_REPORT_RATINGS.indexOf(rating) + 1;
82
+ const small = size === "small";
83
+ return /*#__PURE__*/_jsx(Box, {
84
+ sx: {
85
+ display: "flex",
86
+ gap: small ? "2px" : "3px",
87
+ flex: "none"
88
+ },
89
+ "aria-hidden": "true",
90
+ children: SESSION_REPORT_RATINGS.map((step, index) => /*#__PURE__*/_jsx(Box, {
91
+ "data-filled": index < filled,
92
+ sx: {
93
+ width: small ? 14 : 20,
94
+ height: small ? 4 : 5,
95
+ borderRadius: small ? "2px" : "3px",
96
+ bgcolor: index < filled ? "primary.main" : "action.disabledBackground"
97
+ }
98
+ }, step))
99
+ });
100
+ }
101
+ function RubricLink({
102
+ rubricLevel,
103
+ t
104
+ }) {
105
+ const [open, setOpen] = useState(false);
106
+ const [pageNumber, setPageNumber] = useState(1);
107
+ const [scale, setScale] = useState(1);
108
+ const pdfUrl = RUBRIC_PDFS[rubricLevel];
109
+ if (!pdfUrl) return null;
110
+ return /*#__PURE__*/_jsxs(_Fragment, {
111
+ children: [/*#__PURE__*/_jsx(Tooltip, {
112
+ title: t("rubric_info_desc"),
113
+ children: /*#__PURE__*/_jsx(Button, {
114
+ size: "small",
115
+ color: "primary",
116
+ startIcon: /*#__PURE__*/_jsx(HelpOutlinedIcon, {
117
+ sx: {
118
+ fontSize: 18
119
+ }
120
+ }),
121
+ onClick: () => setOpen(true),
122
+ "data-cy": "session-report-rubric",
123
+ "data-rubric-level": rubricLevel,
124
+ sx: {
125
+ flex: "none",
126
+ fontSize: 12,
127
+ fontWeight: 700,
128
+ letterSpacing: ".04em",
129
+ textTransform: "uppercase"
130
+ },
131
+ children: t("rubric_info")
132
+ })
133
+ }), /*#__PURE__*/_jsx(PDFViewer, {
134
+ open: open,
135
+ handleClose: () => setOpen(false),
136
+ pdfUrl: pdfUrl,
137
+ pageNumber: pageNumber,
138
+ setPageNumber: setPageNumber,
139
+ scale: scale,
140
+ setScale: setScale,
141
+ t: t
142
+ })]
143
+ });
144
+ }
145
+ function AiGeneratedLabel({
146
+ showText,
147
+ t
148
+ }) {
149
+ return /*#__PURE__*/_jsxs(Box, {
150
+ "data-cy": "session-report-ai-label",
151
+ sx: {
152
+ display: "flex",
153
+ alignItems: "center",
154
+ gap: 0.5,
155
+ flex: "none",
156
+ color: "text.secondary"
157
+ },
158
+ children: [/*#__PURE__*/_jsx(AutoAwesomeIcon, {
159
+ sx: {
160
+ fontSize: 16,
161
+ color: "primary.main"
162
+ }
163
+ }), /*#__PURE__*/_jsx(Typography, {
164
+ component: "span",
165
+ "data-cy": "session-report-ai-label-text",
166
+ sx: {
167
+ fontSize: 12,
168
+ whiteSpace: "nowrap",
169
+ display: showText ? {
170
+ xs: "none",
171
+ sm: "inline"
172
+ } : "none"
173
+ },
174
+ children: t("ai_generated_report")
175
+ }), /*#__PURE__*/_jsx(Tooltip, {
176
+ title: t("ai_generated_report_tooltip"),
177
+ children: /*#__PURE__*/_jsx(IconButton, {
178
+ size: "small",
179
+ "aria-label": t("ai_generated_report_tooltip"),
180
+ "data-cy": "session-report-ai-info",
181
+ sx: {
182
+ p: 0.25,
183
+ color: "text.secondary"
184
+ },
185
+ children: /*#__PURE__*/_jsx(InfoOutlinedIcon, {
186
+ sx: {
187
+ fontSize: 16
188
+ }
189
+ })
190
+ })
191
+ })]
192
+ });
193
+ }
194
+ function CollapsedSummary({
195
+ suggestedProficiency,
196
+ ratedDomains,
197
+ t
198
+ }) {
199
+ return /*#__PURE__*/_jsxs(_Fragment, {
200
+ children: [suggestedProficiency && /*#__PURE__*/_jsxs(Box, {
201
+ "data-cy": "session-report-level-chip",
202
+ sx: theme => ({
203
+ display: "flex",
204
+ alignItems: "center",
205
+ gap: 0.75,
206
+ flex: "none",
207
+ px: 1.25,
208
+ py: "3px",
209
+ borderRadius: 1,
210
+ border: `1px dashed ${alpha(theme.palette.primary.main, 0.5)}`
211
+ }),
212
+ children: [/*#__PURE__*/_jsx(Typography, {
213
+ component: "span",
214
+ sx: {
215
+ fontSize: 11,
216
+ color: "text.secondary",
217
+ whiteSpace: "nowrap"
218
+ },
219
+ children: t("suggested_level")
220
+ }), /*#__PURE__*/_jsx(Typography, {
221
+ component: "span",
222
+ sx: {
223
+ fontSize: 13,
224
+ fontWeight: 700,
225
+ whiteSpace: "nowrap"
226
+ },
227
+ children: suggestedProficiency
228
+ })]
229
+ }), /*#__PURE__*/_jsx(Box, {
230
+ "data-cy": "session-report-mini-meters",
231
+ sx: {
232
+ display: {
233
+ xs: "none",
234
+ lg: "flex"
235
+ },
236
+ flex: "1 1 auto",
237
+ gap: 2,
238
+ ml: 0.75,
239
+ minWidth: 0,
240
+ overflow: "hidden"
241
+ },
242
+ children: ratedDomains.map(({
243
+ key,
244
+ shortKey,
245
+ rating
246
+ }) => /*#__PURE__*/_jsxs(Box, {
247
+ sx: {
248
+ display: "flex",
249
+ flexDirection: "column",
250
+ gap: "5px",
251
+ flex: "0 1 auto",
252
+ minWidth: 62
253
+ },
254
+ children: [/*#__PURE__*/_jsx(Typography, {
255
+ component: "span",
256
+ title: t(shortKey),
257
+ sx: {
258
+ fontSize: 11,
259
+ color: "text.secondary",
260
+ whiteSpace: "nowrap",
261
+ overflow: "hidden",
262
+ textOverflow: "ellipsis"
263
+ },
264
+ children: t(shortKey)
265
+ }), /*#__PURE__*/_jsx(Meter, {
266
+ rating: rating,
267
+ size: "small"
268
+ }), /*#__PURE__*/_jsx(Box, {
269
+ component: "span",
270
+ sx: visuallyHidden,
271
+ children: t(RATING_LABEL_KEYS[rating])
272
+ })]
273
+ }, key))
274
+ })]
275
+ });
276
+ }
277
+ function Tile({
278
+ icon: Icon,
279
+ title,
280
+ children,
281
+ dataCy
282
+ }) {
283
+ return /*#__PURE__*/_jsxs(Box, {
284
+ "data-cy": dataCy,
285
+ sx: {
286
+ display: "flex",
287
+ gap: 1.25,
288
+ p: "10px 12px",
289
+ borderRadius: 1.5,
290
+ bgcolor: "action.hover"
291
+ },
292
+ children: [/*#__PURE__*/_jsx(Icon, {
293
+ sx: {
294
+ fontSize: 18,
295
+ color: "primary.main",
296
+ flex: "none"
297
+ }
298
+ }), /*#__PURE__*/_jsxs(Typography, {
299
+ component: "div",
300
+ sx: {
301
+ fontSize: 13,
302
+ lineHeight: 1.5,
303
+ color: "text.secondary",
304
+ minWidth: 0
305
+ },
306
+ children: [/*#__PURE__*/_jsx(Typography, {
307
+ component: "b",
308
+ sx: {
309
+ display: "block",
310
+ fontSize: 13,
311
+ fontWeight: 700,
312
+ color: "text.primary"
313
+ },
314
+ children: title
315
+ }), children]
316
+ })]
317
+ });
318
+ }
319
+ function FocusTile({
320
+ icon,
321
+ title,
322
+ domain,
323
+ text,
324
+ t,
325
+ dataCy
326
+ }) {
327
+ return /*#__PURE__*/_jsx(Tile, {
328
+ icon: icon,
329
+ title: `${title}: ${t(domainLabelKey(domain))}`,
330
+ dataCy: dataCy,
331
+ children: text
332
+ });
333
+ }
334
+ function CriterionRow({
335
+ labelKey,
336
+ level,
337
+ rating,
338
+ description,
339
+ t,
340
+ dataCy
341
+ }) {
342
+ return /*#__PURE__*/_jsxs(Box, {
343
+ "data-cy": dataCy,
344
+ sx: {
345
+ py: 1.25,
346
+ display: "flex",
347
+ flexDirection: "column",
348
+ gap: 0.75,
349
+ borderBottom: 1,
350
+ borderColor: "divider"
351
+ },
352
+ children: [/*#__PURE__*/_jsxs(Box, {
353
+ sx: {
354
+ display: "flex",
355
+ alignItems: "center",
356
+ gap: 1.25
357
+ },
358
+ children: [/*#__PURE__*/_jsx(Typography, {
359
+ sx: {
360
+ fontSize: 14,
361
+ flex: 1
362
+ },
363
+ children: t(labelKey)
364
+ }), /*#__PURE__*/_jsx(Meter, {
365
+ rating: rating
366
+ }), /*#__PURE__*/_jsx(Typography, {
367
+ sx: {
368
+ fontSize: 12.5,
369
+ color: "primary.main",
370
+ width: 80,
371
+ textAlign: "right",
372
+ whiteSpace: "nowrap"
373
+ },
374
+ children: t(RATING_LABEL_KEYS[rating])
375
+ })]
376
+ }), /*#__PURE__*/_jsxs(Typography, {
377
+ sx: {
378
+ fontSize: 12.5,
379
+ lineHeight: 1.45,
380
+ color: "text.secondary"
381
+ },
382
+ children: [/*#__PURE__*/_jsxs(Box, {
383
+ component: "span",
384
+ sx: {
385
+ color: "text.primary"
386
+ },
387
+ children: [level, " \xB7 "]
388
+ }), description]
389
+ })]
390
+ });
391
+ }
392
+ function ExpandedBody({
393
+ report,
394
+ ratedDomains,
395
+ corrections,
396
+ t
397
+ }) {
398
+ const {
399
+ summary,
400
+ suggestedProficiency,
401
+ areaOfStrength,
402
+ areaForGrowth
403
+ } = report;
404
+ const hasCriteria = ratedDomains.length > 0;
405
+ return /*#__PURE__*/_jsxs(Box, {
406
+ sx: {
407
+ display: "grid",
408
+ gridTemplateColumns: {
409
+ xs: "1fr",
410
+ md: hasCriteria ? "minmax(0, 0.9fr) minmax(0, 1.1fr)" : "1fr"
411
+ }
412
+ },
413
+ children: [/*#__PURE__*/_jsxs(Box, {
414
+ sx: {
415
+ p: "16px 20px",
416
+ display: "flex",
417
+ flexDirection: "column",
418
+ gap: 1.75,
419
+ borderStyle: "solid",
420
+ borderColor: "divider",
421
+ borderWidth: 0,
422
+ borderRightWidth: {
423
+ xs: 0,
424
+ md: hasCriteria ? 1 : 0
425
+ },
426
+ borderBottomWidth: {
427
+ xs: hasCriteria ? 1 : 0,
428
+ md: 0
429
+ }
430
+ },
431
+ children: [suggestedProficiency && /*#__PURE__*/_jsxs(Box, {
432
+ sx: {
433
+ display: "flex",
434
+ alignItems: "center",
435
+ gap: 1.25
436
+ },
437
+ "data-cy": "session-report-suggested-level",
438
+ children: [/*#__PURE__*/_jsx(SignalCellularAltIcon, {
439
+ sx: {
440
+ fontSize: 22,
441
+ color: "primary.main"
442
+ }
443
+ }), /*#__PURE__*/_jsxs(Box, {
444
+ children: [/*#__PURE__*/_jsx(Typography, {
445
+ sx: {
446
+ fontSize: 11,
447
+ color: "text.secondary"
448
+ },
449
+ children: t("suggested_level")
450
+ }), /*#__PURE__*/_jsx(Typography, {
451
+ sx: {
452
+ fontSize: 17,
453
+ fontWeight: 700
454
+ },
455
+ children: suggestedProficiency
456
+ })]
457
+ })]
458
+ }), /*#__PURE__*/_jsx(Typography, {
459
+ sx: {
460
+ m: 0,
461
+ fontSize: 13.5,
462
+ lineHeight: 1.55,
463
+ textWrap: "pretty"
464
+ },
465
+ "data-cy": "session-report-text",
466
+ children: summary
467
+ }), areaOfStrength && /*#__PURE__*/_jsx(FocusTile, {
468
+ icon: StarIcon,
469
+ title: t("session_report_strength"),
470
+ domain: areaOfStrength.domain,
471
+ text: areaOfStrength.explanation,
472
+ t: t,
473
+ dataCy: "session-report-strength"
474
+ }), areaForGrowth && /*#__PURE__*/_jsx(FocusTile, {
475
+ icon: TrendingUpIcon,
476
+ title: t("session_report_growth"),
477
+ domain: areaForGrowth.domain,
478
+ text: areaForGrowth.suggestion,
479
+ t: t,
480
+ dataCy: "session-report-growth"
481
+ }), corrections.length > 0 && /*#__PURE__*/_jsx(Tile, {
482
+ icon: SpellcheckIcon,
483
+ title: t("language_corrections_to_review"),
484
+ dataCy: "session-report-corrections",
485
+ children: /*#__PURE__*/_jsx(CorrectionList, {
486
+ corrections: corrections,
487
+ fontSize: 13
488
+ })
489
+ })]
490
+ }), hasCriteria && /*#__PURE__*/_jsx(Box, {
491
+ sx: {
492
+ p: "6px 20px 10px"
493
+ },
494
+ "data-cy": "session-report-criteria",
495
+ children: ratedDomains.map(domain => /*#__PURE__*/_jsx(CriterionRow, {
496
+ labelKey: domain.labelKey,
497
+ level: domain.level,
498
+ rating: domain.rating,
499
+ description: describeDomain(domain),
500
+ t: t,
501
+ dataCy: `session-report-domain-${domain.key}`
502
+ }, domain.key))
503
+ })]
504
+ });
505
+ }
506
+ function SessionReportCard({
507
+ sessionReport,
508
+ defaultCollapsed = true,
509
+ storageKey,
510
+ newStorageKey,
511
+ corrections = [],
512
+ t = text => text
513
+ }) {
514
+ const bodyId = useId();
515
+ const [collapsed, setCollapsed] = useState(() => readStoredCollapsed(storageKey, defaultCollapsed));
516
+ const [seen, setSeen] = useState(() => readSeen(newStorageKey));
517
+ if (!sessionReport?.status) return null;
518
+ const {
519
+ status,
520
+ report
521
+ } = sessionReport;
522
+ const isReady = status === "ready" && report;
523
+ const ratedDomains = isReady ? getRatedDomains(report.domains) : [];
524
+ const toggle = () => {
525
+ if (!seen) {
526
+ writeSeen(newStorageKey);
527
+ setSeen(true);
528
+ }
529
+ setCollapsed(current => {
530
+ writeStoredCollapsed(storageKey, !current);
531
+ return !current;
532
+ });
533
+ };
534
+ return /*#__PURE__*/_jsxs(Card, {
535
+ "data-cy": "session-report-card",
536
+ "data-collapsed": isReady ? collapsed : undefined,
537
+ sx: {
538
+ borderRadius: 1
539
+ },
540
+ children: [/*#__PURE__*/_jsxs(Box, {
541
+ sx: {
542
+ display: "flex",
543
+ alignItems: "center",
544
+ gap: 1.75,
545
+ px: 2.5,
546
+ py: 1.5,
547
+ borderBottom: isReady && !collapsed ? 1 : 0,
548
+ borderColor: "divider"
549
+ },
550
+ children: [isReady && collapsed && !seen && /*#__PURE__*/_jsx(Chip, {
551
+ label: t("session_report_new"),
552
+ color: "primary",
553
+ size: "small",
554
+ "data-cy": "session-report-new",
555
+ sx: {
556
+ flex: "none",
557
+ height: 20,
558
+ fontSize: 11,
559
+ fontWeight: 700
560
+ }
561
+ }), /*#__PURE__*/_jsx(Typography, {
562
+ component: "h2",
563
+ onClick: isReady ? toggle : undefined,
564
+ "data-cy": "session-report-title",
565
+ sx: {
566
+ fontSize: 20,
567
+ whiteSpace: "nowrap",
568
+ cursor: isReady ? "pointer" : "default"
569
+ },
570
+ children: t("session_report")
571
+ }), /*#__PURE__*/_jsx(AiGeneratedLabel, {
572
+ showText: !(isReady && collapsed),
573
+ t: t
574
+ }), isReady ? /*#__PURE__*/_jsx(Box, {
575
+ onClick: toggle,
576
+ "data-cy": "session-report-summary-area",
577
+ sx: {
578
+ flex: 1,
579
+ minWidth: 0,
580
+ overflow: "hidden",
581
+ alignSelf: "stretch",
582
+ display: "flex",
583
+ alignItems: "center",
584
+ gap: 1.75,
585
+ cursor: "pointer"
586
+ },
587
+ children: collapsed && /*#__PURE__*/_jsx(CollapsedSummary, {
588
+ suggestedProficiency: report.suggestedProficiency,
589
+ ratedDomains: ratedDomains,
590
+ t: t
591
+ })
592
+ }) : /*#__PURE__*/_jsxs(Box, {
593
+ sx: {
594
+ flex: 1,
595
+ minWidth: 0,
596
+ display: "flex",
597
+ alignItems: "center",
598
+ gap: 1.75
599
+ },
600
+ children: [status === "pending" && /*#__PURE__*/_jsxs(Box, {
601
+ sx: {
602
+ display: "flex",
603
+ alignItems: "center",
604
+ gap: 1
605
+ },
606
+ "data-cy": "session-report-pending",
607
+ children: [/*#__PURE__*/_jsx(CircularProgress, {
608
+ size: 16
609
+ }), /*#__PURE__*/_jsx(Typography, {
610
+ variant: "body2",
611
+ children: t("session_feedback_preparing")
612
+ })]
613
+ }), status === "failed" && /*#__PURE__*/_jsx(Typography, {
614
+ variant: "body2",
615
+ sx: {
616
+ color: "text.secondary"
617
+ },
618
+ "data-cy": "session-report-unavailable",
619
+ children: t("session_feedback_unavailable")
620
+ })]
621
+ }), isReady && !collapsed && /*#__PURE__*/_jsx(RubricLink, {
622
+ rubricLevel: report.rubricLevel,
623
+ t: t
624
+ }), isReady && /*#__PURE__*/_jsx(IconButton, {
625
+ onClick: toggle,
626
+ "aria-expanded": !collapsed,
627
+ "aria-controls": bodyId,
628
+ "aria-label": t("session_report"),
629
+ "data-cy": "session-report-toggle",
630
+ size: "small",
631
+ sx: {
632
+ flex: "none",
633
+ color: "text.secondary"
634
+ },
635
+ children: collapsed ? /*#__PURE__*/_jsx(ExpandMoreIcon, {
636
+ sx: {
637
+ fontSize: 22
638
+ }
639
+ }) : /*#__PURE__*/_jsx(ExpandLessIcon, {
640
+ sx: {
641
+ fontSize: 22
642
+ }
643
+ })
644
+ })]
645
+ }), isReady && /*#__PURE__*/_jsx(Collapse, {
646
+ in: !collapsed,
647
+ id: bodyId,
648
+ unmountOnExit: true,
649
+ children: /*#__PURE__*/_jsx(ExpandedBody, {
650
+ report: report,
651
+ ratedDomains: ratedDomains,
652
+ corrections: corrections,
653
+ t: t
654
+ })
655
+ })]
656
+ });
657
+ }
658
+ export default SessionReportCard;