@nightkatana/kronosys-app 1.0.0-beta.2 → 1.0.0-beta.21

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.
Files changed (112) hide show
  1. package/README.md +28 -1
  2. package/app/api/action/route.ts +39 -3
  3. package/app/api/action-logs/route.ts +24 -0
  4. package/app/api/backup/route.ts +1 -1
  5. package/app/api/restore/route.ts +145 -0
  6. package/app/changelog/page.tsx +71 -4
  7. package/app/globals.css +127 -0
  8. package/app/guide/page.tsx +61 -15
  9. package/app/implementation/page.tsx +700 -0
  10. package/app/layout.tsx +14 -3
  11. package/app/licenses/page.tsx +99 -37
  12. package/app/logs/page.tsx +258 -0
  13. package/app/manifest.ts +5 -5
  14. package/app/page.tsx +784 -229
  15. package/app/reporting/page.tsx +1266 -474
  16. package/app/settings/page.tsx +252 -18
  17. package/bin/kronosys.mjs +140 -15
  18. package/components/KronosysPayloadProvider.tsx +2 -0
  19. package/components/RouteTransition.tsx +18 -0
  20. package/components/dashboard/AppShellCommandCenterPlaceholder.tsx +17 -0
  21. package/components/dashboard/AppShellHeaderSessionMeta.tsx +210 -0
  22. package/components/dashboard/AppShellHeaderWallClock.tsx +54 -0
  23. package/components/dashboard/AppShellLiveSessionDrawer.tsx +154 -38
  24. package/components/dashboard/AppShellRouteNav.tsx +323 -48
  25. package/components/dashboard/DashboardPauseBackdrop.tsx +50 -0
  26. package/components/dashboard/DashboardSimpleModal.tsx +168 -25
  27. package/components/dashboard/DashboardTour.tsx +115 -29
  28. package/components/dashboard/GlobalPauseConfirmModal.tsx +183 -0
  29. package/components/dashboard/KronosysDatetimePopoverField.tsx +167 -122
  30. package/components/dashboard/KronosysTimePopoverField.tsx +54 -12
  31. package/components/dashboard/NewSessionScopeModal.tsx +211 -20
  32. package/components/dashboard/PlannedTaskBoundaryConflictWatcher.tsx +275 -0
  33. package/components/dashboard/ReportingTour.tsx +87 -21
  34. package/components/dashboard/SavedProjectPicker.tsx +16 -3
  35. package/components/dashboard/SelectedSessionSidebarBlock.tsx +512 -142
  36. package/components/dashboard/SessionListPanel.tsx +327 -44
  37. package/components/dashboard/SettingsTagsProjectsSection.tsx +1073 -264
  38. package/components/dashboard/SettingsTaskTemplatesSection.tsx +316 -0
  39. package/components/dashboard/SettingsTour.tsx +86 -21
  40. package/components/dashboard/TagPills.tsx +14 -1
  41. package/components/dashboard/TaskFocusPanel.tsx +1081 -478
  42. package/components/dashboard/TaskSessionLiveCard.tsx +650 -135
  43. package/components/dashboard/TaskTimelineGanttModal.tsx +601 -0
  44. package/components/dashboard/taskFieldStyles.ts +20 -4
  45. package/components/dashboard/useReportingInteractionState.ts +80 -0
  46. package/lib/appShellHeaderClasses.ts +13 -0
  47. package/lib/businessRulesMatrix.ts +210 -0
  48. package/lib/copyToClipboard.ts +43 -0
  49. package/lib/dashboardCopy.ts +494 -84
  50. package/lib/dashboardQuickSearch.ts +54 -2
  51. package/lib/dashboardTimeZone.ts +109 -0
  52. package/lib/formatAppShellWallClock.ts +66 -0
  53. package/lib/formatSessionNameTemplate.ts +141 -0
  54. package/lib/generatedUserChangelog.ts +177 -6
  55. package/lib/globalPausePreview.ts +292 -0
  56. package/lib/implementationNotes.ts +1188 -0
  57. package/lib/kronosysApi.ts +6 -0
  58. package/lib/kronosysDashboardModalGates.ts +24 -0
  59. package/lib/plannedBoundaryAttention.ts +9 -0
  60. package/lib/plannedBoundaryConflict.ts +23 -0
  61. package/lib/reportingAggregate.ts +517 -75
  62. package/lib/reportingMetricHelp.ts +8 -0
  63. package/lib/reportingStrings.ts +37 -3
  64. package/lib/sessionListMerge.ts +4 -0
  65. package/lib/sessionTaskSidebarStats.ts +182 -21
  66. package/lib/settingsCopy.ts +178 -4
  67. package/lib/taskParsing.ts +360 -103
  68. package/lib/taskTemplateDraft.ts +135 -0
  69. package/lib/taskTimelineGantt.ts +265 -0
  70. package/lib/temporalDisplayPlanned.ts +71 -0
  71. package/lib/userGuideCopy.ts +121 -47
  72. package/next.config.ts +7 -0
  73. package/package.json +12 -24
  74. package/server/actionDispatch.ts +1000 -77
  75. package/server/actionTaskSession.ts +337 -24
  76. package/server/db.ts +7 -15
  77. package/server/dbSchema.ts +24 -0
  78. package/server/defaultCfg.ts +5 -0
  79. package/server/gitlabTokenStore.ts +0 -12
  80. package/server/liveHistorySync.ts +53 -0
  81. package/server/mainTimerHydrate.ts +38 -2
  82. package/server/payloadStore.ts +33 -11
  83. package/server/sessionWallHydrate.ts +66 -3
  84. package/server/userActionLog.ts +126 -0
  85. package/sonar-project.properties +11 -0
  86. package/tsconfig.json +2 -1
  87. package/components/dashboard/IssuePickerModal.tsx +0 -168
  88. package/components/dashboard/ThemeToggle.test.tsx +0 -26
  89. package/lib/backupCsvExport.test.ts +0 -149
  90. package/lib/dashboardQuickSearchQuery.test.ts +0 -63
  91. package/lib/dataDir.test.ts +0 -87
  92. package/lib/formatIsoShort.test.ts +0 -46
  93. package/lib/kronoFocusRhythm.test.ts +0 -130
  94. package/lib/kronoFocusTimerUrgency.test.ts +0 -74
  95. package/lib/legacyKronoFocusStorageKeys.test.ts +0 -29
  96. package/lib/reportingAggregate.test.ts +0 -325
  97. package/lib/reportingNonFinalIndicators.test.ts +0 -157
  98. package/lib/reportingTagWeekBreakdown.test.ts +0 -141
  99. package/lib/reportingWeekLayout.test.ts +0 -239
  100. package/lib/sessionAssiduity.test.ts +0 -25
  101. package/lib/sessionEndWarnings.test.ts +0 -200
  102. package/lib/sessionListMerge.test.ts +0 -101
  103. package/lib/sessionTaskSidebarStats.test.ts +0 -24
  104. package/lib/taskParsing.test.ts +0 -153
  105. package/lib/usageProfile.test.ts +0 -84
  106. package/server/actionDispatch.test.ts +0 -723
  107. package/server/actionTaskSession.test.ts +0 -713
  108. package/server/kronoFocusHydrate.test.ts +0 -142
  109. package/server/kronoFocusMigrate.test.ts +0 -53
  110. package/server/mainTimerHydrate.test.ts +0 -65
  111. package/server/payloadStore.test.ts +0 -78
  112. package/server/sessionWallHydrate.test.ts +0 -46
@@ -1,8 +1,21 @@
1
1
  "use client";
2
2
 
3
3
  import Link from "next/link";
4
- import { BarChart3, BookOpen, FileText, LayoutDashboard, Settings } from "lucide-react";
4
+ import { useEffect, useState } from "react";
5
+ import {
6
+ BarChart3,
7
+ BookOpen,
8
+ FileCode2,
9
+ FileText,
10
+ LayoutDashboard,
11
+ LayoutGrid,
12
+ Logs,
13
+ Pause,
14
+ Play,
15
+ Settings,
16
+ } from "lucide-react";
5
17
  import { withDashboardSessionParam } from "@/lib/dashboardSessionNav";
18
+ import { PLANNED_BOUNDARY_ATTENTION_EVENT } from "@/lib/plannedBoundaryAttention";
6
19
 
7
20
  const iconLinkClass =
8
21
  "inline-flex size-10 items-center justify-center rounded-lg border border-zinc-300 bg-white text-zinc-700 transition hover:border-zinc-400 hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-800/80 dark:text-zinc-200 dark:hover:border-zinc-500 dark:hover:bg-zinc-800";
@@ -10,17 +23,91 @@ const iconLinkClass =
10
23
  const iconActiveClass =
11
24
  "inline-flex size-10 items-center justify-center rounded-lg border border-violet-400/70 bg-violet-100/90 text-violet-950 dark:border-violet-600/60 dark:bg-violet-950/40 dark:text-violet-100";
12
25
 
26
+ const dashboardPulseChromeClass =
27
+ "ring-2 ring-amber-400/85 motion-safe:animate-pulse shadow-[0_0_14px_rgba(251,191,36,0.35)] dark:ring-amber-300/80";
28
+
29
+ /** Anneau autour du bouton pause globale lorsque la reprise est disponible (pause active). */
30
+ const globalPauseResumeHighlightClass =
31
+ "ring-2 ring-amber-500/90 ring-offset-2 ring-offset-zinc-100 shadow-[0_0_18px_rgba(245,158,11,0.42)] dark:ring-amber-400/85 dark:ring-offset-zinc-900 dark:shadow-[0_0_20px_rgba(251,191,36,0.28)]";
32
+
33
+ type GlobalPauseControlProps = Readonly<{
34
+ active: boolean;
35
+ label: string;
36
+ disabled?: boolean;
37
+ disabledTooltip?: string;
38
+ onPress: () => void;
39
+ }>;
40
+
41
+ function AppShellGlobalPauseButton({
42
+ control,
43
+ }: Readonly<{ control: GlobalPauseControlProps }>) {
44
+ const gp = control;
45
+ const idlePause =
46
+ !gp.active &&
47
+ gp.disabled === true &&
48
+ typeof gp.disabledTooltip === "string" &&
49
+ gp.disabledTooltip.trim() !== "";
50
+ const aria =
51
+ idlePause && gp.disabledTooltip
52
+ ? `${gp.label}. ${gp.disabledTooltip}`
53
+ : gp.label;
54
+ return (
55
+ <button
56
+ type="button"
57
+ aria-disabled={idlePause}
58
+ tabIndex={idlePause ? -1 : 0}
59
+ onClick={() => {
60
+ if (idlePause) {
61
+ return;
62
+ }
63
+ gp.onPress();
64
+ }}
65
+ onKeyDown={(e) => {
66
+ if (
67
+ idlePause &&
68
+ (e.key === "Enter" || e.key === " " || e.code === "Space")
69
+ ) {
70
+ e.preventDefault();
71
+ }
72
+ }}
73
+ className={`${gp.active ? iconActiveClass : iconLinkClass}${
74
+ gp.active ? ` ${globalPauseResumeHighlightClass}` : ""
75
+ }${idlePause ? " cursor-not-allowed opacity-45" : ""}`}
76
+ title={idlePause ? gp.disabledTooltip : gp.label}
77
+ aria-label={aria}
78
+ >
79
+ {gp.active ? (
80
+ <Play size={20} strokeWidth={2} className="shrink-0" aria-hidden />
81
+ ) : (
82
+ <Pause size={20} strokeWidth={2} className="shrink-0" aria-hidden />
83
+ )}
84
+ </button>
85
+ );
86
+ }
87
+
13
88
  export type AppShellRouteNavLabels = {
14
89
  dashboard: string;
15
90
  reporting: string;
16
91
  settings: string;
92
+ logs?: string;
17
93
  /** Guide d’utilisation in-app. */
18
94
  guide: string;
95
+ /** Détails d’implémentation (documentation technique maintenue). */
96
+ implementation?: string;
19
97
  /** Libellé infobulle / `aria-label` pour l’icône « licences » (page licences uniquement). */
20
98
  licenses?: string;
99
+ /** Infobulle lorsque l’icône tableau de bord pulse (rappel conflit minuteurs). */
100
+ dashboardAttentionHint?: string;
21
101
  };
22
102
 
23
- export type AppShellRouteNavCurrent = "dashboard" | "reporting" | "settings" | "licenses" | "guide";
103
+ export type AppShellRouteNavCurrent =
104
+ | "dashboard"
105
+ | "reporting"
106
+ | "settings"
107
+ | "logs"
108
+ | "licenses"
109
+ | "guide"
110
+ | "implementation";
24
111
 
25
112
  type Props = Readonly<{
26
113
  current: AppShellRouteNavCurrent;
@@ -30,6 +117,10 @@ type Props = Readonly<{
30
117
  className?: string;
31
118
  /** Identifiant de session du tableau de bord (`?session=`) à conserver sur les liens internes. */
32
119
  dashboardSessionId?: string | null;
120
+ globalPauseControl?: GlobalPauseControlProps;
121
+ ganttControl?: { label: string; onPress: () => void };
122
+ /** Réserve l’emplacement du bouton pause globale (tableau de bord) pour éviter les sauts. */
123
+ reserveGlobalPauseSlot?: boolean;
33
124
  }>;
34
125
 
35
126
  export function AppShellRouteNav({
@@ -38,52 +129,114 @@ export function AppShellRouteNav({
38
129
  navAriaLabel,
39
130
  className,
40
131
  dashboardSessionId,
132
+ globalPauseControl,
133
+ ganttControl,
134
+ reserveGlobalPauseSlot,
41
135
  }: Props) {
42
136
  const wrapClass = className ?? "flex flex-wrap items-center gap-1.5";
43
- const dash = (path: string) => withDashboardSessionParam(path, dashboardSessionId);
137
+ const dash = (path: string) =>
138
+ withDashboardSessionParam(path, dashboardSessionId);
44
139
 
45
- if (current === "dashboard") {
46
- return (
47
- <nav className={wrapClass} aria-label={navAriaLabel}>
48
- <Link
49
- href={dash("/guide")}
50
- className={iconLinkClass}
51
- title={labels.guide}
52
- aria-label={labels.guide}
53
- >
54
- <BookOpen size={20} strokeWidth={2} className="shrink-0" aria-hidden />
55
- </Link>
56
- <Link
57
- href={dash("/reporting")}
58
- className={iconLinkClass}
59
- title={labels.reporting}
60
- aria-label={labels.reporting}
61
- >
62
- <BarChart3 size={20} strokeWidth={2} className="shrink-0" aria-hidden />
63
- </Link>
64
- <Link
65
- href={dash("/settings")}
66
- className={iconLinkClass}
67
- title={labels.settings}
68
- aria-label={labels.settings}
69
- >
70
- <Settings size={20} strokeWidth={2} className="shrink-0" aria-hidden />
71
- </Link>
72
- </nav>
140
+ const licensesTitle = labels.licenses ?? "Licenses";
141
+ const logsTitle = labels.logs ?? "Action logs";
142
+
143
+ const [docHidden, setDocHidden] = useState(false);
144
+ const [dashboardPulse, setDashboardPulse] = useState(false);
145
+
146
+ useEffect(() => {
147
+ const syncHidden = () =>
148
+ setDocHidden(
149
+ typeof document !== "undefined" &&
150
+ document.visibilityState === "hidden",
151
+ );
152
+ syncHidden();
153
+ document.addEventListener("visibilitychange", syncHidden);
154
+ return () => document.removeEventListener("visibilitychange", syncHidden);
155
+ }, []);
156
+
157
+ useEffect(() => {
158
+ const onAttention = () => setDashboardPulse(true);
159
+ globalThis.window.addEventListener(
160
+ PLANNED_BOUNDARY_ATTENTION_EVENT,
161
+ onAttention,
73
162
  );
74
- }
163
+ return () =>
164
+ globalThis.window.removeEventListener(
165
+ PLANNED_BOUNDARY_ATTENTION_EVENT,
166
+ onAttention,
167
+ );
168
+ }, []);
75
169
 
76
- const licensesTitle = labels.licenses ?? "Licenses";
170
+ useEffect(() => {
171
+ if (!dashboardPulse) {
172
+ return;
173
+ }
174
+ const t = globalThis.setTimeout(() => setDashboardPulse(false), 14000);
175
+ return () => globalThis.clearTimeout(t);
176
+ }, [dashboardPulse]);
177
+
178
+ const showDashboardPulse =
179
+ dashboardPulse &&
180
+ (current !== "dashboard" ||
181
+ (current === "dashboard" && docHidden === true));
182
+
183
+ const dashboardAttentionHint = labels.dashboardAttentionHint?.trim();
184
+ const dashboardPulseTitle =
185
+ showDashboardPulse && dashboardAttentionHint
186
+ ? `${labels.dashboard} — ${dashboardAttentionHint}`
187
+ : labels.dashboard;
188
+
189
+ const dashboardBaseNavClass =
190
+ current === "dashboard" ? iconActiveClass : iconLinkClass;
191
+ const dashboardNavClass = showDashboardPulse
192
+ ? `${dashboardBaseNavClass} ${dashboardPulseChromeClass}`
193
+ : dashboardBaseNavClass;
77
194
 
78
195
  return (
79
196
  <nav className={wrapClass} aria-label={navAriaLabel}>
80
- <Link href={dash("/")} className={iconLinkClass} title={labels.dashboard} aria-label={labels.dashboard}>
81
- <LayoutDashboard size={20} strokeWidth={2} className="shrink-0" aria-hidden />
82
- </Link>
197
+ {current === "dashboard" ? (
198
+ <span
199
+ className={dashboardNavClass}
200
+ title={dashboardPulseTitle}
201
+ aria-label={dashboardPulseTitle}
202
+ aria-current="page"
203
+ >
204
+ <LayoutDashboard
205
+ size={20}
206
+ strokeWidth={2}
207
+ className="shrink-0"
208
+ aria-hidden
209
+ />
210
+ </span>
211
+ ) : (
212
+ <Link
213
+ href={dash("/")}
214
+ className={dashboardNavClass}
215
+ title={dashboardPulseTitle}
216
+ aria-label={dashboardPulseTitle}
217
+ >
218
+ <LayoutDashboard
219
+ size={20}
220
+ strokeWidth={2}
221
+ className="shrink-0"
222
+ aria-hidden
223
+ />
224
+ </Link>
225
+ )}
83
226
 
84
227
  {current === "reporting" ? (
85
- <span className={iconActiveClass} title={labels.reporting} aria-label={labels.reporting} aria-current="page">
86
- <BarChart3 size={20} strokeWidth={2} className="shrink-0" aria-hidden />
228
+ <span
229
+ className={iconActiveClass}
230
+ title={labels.reporting}
231
+ aria-label={labels.reporting}
232
+ aria-current="page"
233
+ >
234
+ <BarChart3
235
+ size={20}
236
+ strokeWidth={2}
237
+ className="shrink-0"
238
+ aria-hidden
239
+ />
87
240
  </span>
88
241
  ) : (
89
242
  <Link
@@ -92,13 +245,28 @@ export function AppShellRouteNav({
92
245
  title={labels.reporting}
93
246
  aria-label={labels.reporting}
94
247
  >
95
- <BarChart3 size={20} strokeWidth={2} className="shrink-0" aria-hidden />
248
+ <BarChart3
249
+ size={20}
250
+ strokeWidth={2}
251
+ className="shrink-0"
252
+ aria-hidden
253
+ />
96
254
  </Link>
97
255
  )}
98
256
 
99
257
  {current === "settings" ? (
100
- <span className={iconActiveClass} title={labels.settings} aria-label={labels.settings} aria-current="page">
101
- <Settings size={20} strokeWidth={2} className="shrink-0" aria-hidden />
258
+ <span
259
+ className={iconActiveClass}
260
+ title={labels.settings}
261
+ aria-label={labels.settings}
262
+ aria-current="page"
263
+ >
264
+ <Settings
265
+ size={20}
266
+ strokeWidth={2}
267
+ className="shrink-0"
268
+ aria-hidden
269
+ />
102
270
  </span>
103
271
  ) : (
104
272
  <Link
@@ -107,25 +275,132 @@ export function AppShellRouteNav({
107
275
  title={labels.settings}
108
276
  aria-label={labels.settings}
109
277
  >
110
- <Settings size={20} strokeWidth={2} className="shrink-0" aria-hidden />
278
+ <Settings
279
+ size={20}
280
+ strokeWidth={2}
281
+ className="shrink-0"
282
+ aria-hidden
283
+ />
284
+ </Link>
285
+ )}
286
+ {current === "logs" ? (
287
+ <span
288
+ className={iconActiveClass}
289
+ title={logsTitle}
290
+ aria-label={logsTitle}
291
+ aria-current="page"
292
+ >
293
+ <Logs size={20} strokeWidth={2} className="shrink-0" aria-hidden />
294
+ </span>
295
+ ) : (
296
+ <Link
297
+ href={dash("/logs")}
298
+ className={iconLinkClass}
299
+ title={logsTitle}
300
+ aria-label={logsTitle}
301
+ >
302
+ <Logs size={20} strokeWidth={2} className="shrink-0" aria-hidden />
111
303
  </Link>
112
304
  )}
113
305
 
114
306
  {current === "guide" ? (
115
- <span className={iconActiveClass} title={labels.guide} aria-label={labels.guide} aria-current="page">
116
- <BookOpen size={20} strokeWidth={2} className="shrink-0" aria-hidden />
307
+ <span
308
+ className={iconActiveClass}
309
+ title={labels.guide}
310
+ aria-label={labels.guide}
311
+ aria-current="page"
312
+ >
313
+ <BookOpen
314
+ size={20}
315
+ strokeWidth={2}
316
+ className="shrink-0"
317
+ aria-hidden
318
+ />
117
319
  </span>
118
320
  ) : (
119
- <Link href={dash("/guide")} className={iconLinkClass} title={labels.guide} aria-label={labels.guide}>
120
- <BookOpen size={20} strokeWidth={2} className="shrink-0" aria-hidden />
321
+ <Link
322
+ href={dash("/guide")}
323
+ className={iconLinkClass}
324
+ title={labels.guide}
325
+ aria-label={labels.guide}
326
+ >
327
+ <BookOpen
328
+ size={20}
329
+ strokeWidth={2}
330
+ className="shrink-0"
331
+ aria-hidden
332
+ />
121
333
  </Link>
122
334
  )}
123
335
 
336
+ {labels.implementation ? (
337
+ current === "implementation" ? (
338
+ <span
339
+ className={iconActiveClass}
340
+ title={labels.implementation}
341
+ aria-label={labels.implementation}
342
+ aria-current="page"
343
+ >
344
+ <FileCode2
345
+ size={20}
346
+ strokeWidth={2}
347
+ className="shrink-0"
348
+ aria-hidden
349
+ />
350
+ </span>
351
+ ) : (
352
+ <Link
353
+ href={dash("/implementation")}
354
+ className={iconLinkClass}
355
+ title={labels.implementation}
356
+ aria-label={labels.implementation}
357
+ >
358
+ <FileCode2
359
+ size={20}
360
+ strokeWidth={2}
361
+ className="shrink-0"
362
+ aria-hidden
363
+ />
364
+ </Link>
365
+ )
366
+ ) : null}
367
+
124
368
  {current === "licenses" ? (
125
- <span className={iconActiveClass} title={licensesTitle} aria-label={licensesTitle} aria-current="page">
126
- <FileText size={20} strokeWidth={2} className="shrink-0" aria-hidden />
369
+ <span
370
+ className={iconActiveClass}
371
+ title={licensesTitle}
372
+ aria-label={licensesTitle}
373
+ aria-current="page"
374
+ >
375
+ <FileText
376
+ size={20}
377
+ strokeWidth={2}
378
+ className="shrink-0"
379
+ aria-hidden
380
+ />
127
381
  </span>
128
382
  ) : null}
383
+ {ganttControl ? (
384
+ <button
385
+ type="button"
386
+ onClick={ganttControl.onPress}
387
+ className={iconLinkClass}
388
+ title={ganttControl.label}
389
+ aria-label={ganttControl.label}
390
+ >
391
+ <LayoutGrid
392
+ size={20}
393
+ strokeWidth={2}
394
+ className="shrink-0"
395
+ aria-hidden
396
+ />
397
+ </button>
398
+ ) : null}
399
+ {globalPauseControl ? (
400
+ <AppShellGlobalPauseButton control={globalPauseControl} />
401
+ ) : reserveGlobalPauseSlot ? (
402
+ <span className="inline-flex size-10 shrink-0" aria-hidden />
403
+ ) : null}
129
404
  </nav>
130
405
  );
131
406
  }
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import type { DashboardStrings } from "@/lib/dashboardCopy";
4
+
5
+ /**
6
+ * Voile plein écran (sous l’en-tête sticky du tableau de bord) lorsque la session live est en pause.
7
+ * L’en-tête reste au-dessus (z-50) pour permettre la reprise via la pause globale ou la navigation.
8
+ */
9
+ export function DashboardPauseBackdrop({
10
+ variant,
11
+ dt,
12
+ }: Readonly<{
13
+ variant: "global" | "session";
14
+ dt: DashboardStrings;
15
+ }>) {
16
+ const title =
17
+ variant === "global"
18
+ ? dt.dashboardPauseBackdropTitleGlobal
19
+ : dt.dashboardPauseBackdropTitleSession;
20
+ const detail =
21
+ variant === "global"
22
+ ? dt.dashboardPauseBackdropDetailGlobal
23
+ : dt.dashboardPauseBackdropDetailSession;
24
+ const aria =
25
+ variant === "global"
26
+ ? dt.dashboardPauseBackdropAriaGlobal
27
+ : dt.dashboardPauseBackdropAriaSession;
28
+
29
+ return (
30
+ <div
31
+ className="fixed inset-0 z-40 flex items-center justify-center p-5 pt-20 sm:p-8 sm:pt-24"
32
+ role="status"
33
+ aria-live="polite"
34
+ aria-label={aria}
35
+ >
36
+ <div
37
+ className="absolute inset-0 bg-zinc-950/40 dark:bg-black/50"
38
+ aria-hidden
39
+ />
40
+ <div className="relative z-10 max-w-md rounded-2xl border border-amber-500/45 bg-[#120d0a]/95 px-5 py-5 text-center shadow-2xl backdrop-blur-[2px] dark:border-amber-600/40 sm:px-7 sm:py-6">
41
+ <p className="text-xs font-semibold uppercase tracking-wide text-amber-200/95">
42
+ {title}
43
+ </p>
44
+ <p className="mt-2.5 text-sm leading-snug text-amber-100/92">
45
+ {detail}
46
+ </p>
47
+ </div>
48
+ </div>
49
+ );
50
+ }