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

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 (46) hide show
  1. package/README.md +1 -1
  2. package/app/changelog/page.tsx +87 -19
  3. package/app/globals.css +10 -8
  4. package/app/guide/page.tsx +71 -34
  5. package/app/implementation/page.tsx +70 -60
  6. package/app/licenses/page.tsx +79 -47
  7. package/app/logs/page.tsx +103 -47
  8. package/app/page.tsx +104 -169
  9. package/app/reporting/page.tsx +1918 -1436
  10. package/app/settings/page.tsx +66 -44
  11. package/components/KronosysPayloadProvider.tsx +19 -5
  12. package/components/dashboard/AppShellHeaderKronoFocus.tsx +78 -0
  13. package/components/dashboard/AppShellHeaderToolbarLayout.tsx +36 -0
  14. package/components/dashboard/AppShellHeaderUtilityRibbon.tsx +19 -0
  15. package/components/dashboard/AppShellHeaderWallClock.tsx +23 -17
  16. package/components/dashboard/AppShellRouteNav.tsx +336 -209
  17. package/components/dashboard/AppShellToolbarCommandCenter.tsx +225 -0
  18. package/components/dashboard/AppShellToolbarRouteNav.tsx +204 -0
  19. package/components/dashboard/DashboardCommandCenter.tsx +119 -30
  20. package/components/dashboard/KronoFocusPanel.tsx +287 -260
  21. package/components/dashboard/LanguageMenu.tsx +23 -7
  22. package/components/dashboard/PageRefreshButton.tsx +42 -16
  23. package/components/dashboard/ReportingTour.tsx +20 -2
  24. package/components/dashboard/SessionListPanel.tsx +4 -4
  25. package/components/dashboard/ThemeToggle.tsx +4 -3
  26. package/components/dashboard/useAnchoredFloatingPortalStyle.ts +9 -2
  27. package/components/dashboard/useKronoFocusLiveSeconds.ts +4 -2
  28. package/lib/appShellHeaderClasses.ts +22 -3
  29. package/lib/appShellToolbarChrome.ts +112 -0
  30. package/lib/appShellToolbarDeferredIntents.ts +112 -0
  31. package/lib/appShellToolbarSessionSlices.ts +67 -0
  32. package/lib/dashboardCopy.ts +78 -29
  33. package/lib/dashboardQuickSearch.ts +37 -6
  34. package/lib/dashboardUrlSession.ts +36 -0
  35. package/lib/generatedUserChangelog.ts +14 -0
  36. package/lib/implementationNotes.ts +18 -14
  37. package/lib/reportingAggregate.ts +68 -9
  38. package/lib/reportingMetricHelp.ts +8 -8
  39. package/lib/reportingStrings.ts +118 -9
  40. package/lib/reportingTagWeekBreakdown.ts +55 -13
  41. package/lib/settingsCopy.ts +6 -7
  42. package/lib/userGuideCopy.ts +29 -26
  43. package/package.json +7 -5
  44. package/server/db.ts +6 -4
  45. package/server/dbSchema.ts +2 -2
  46. package/components/dashboard/AppShellCommandCenterPlaceholder.tsx +0 -17
@@ -12,23 +12,27 @@ import {
12
12
  Logs,
13
13
  Pause,
14
14
  Play,
15
+ ScrollText,
15
16
  Settings,
16
17
  } from "lucide-react";
17
18
  import { withDashboardSessionParam } from "@/lib/dashboardSessionNav";
18
19
  import { PLANNED_BOUNDARY_ATTENTION_EVENT } from "@/lib/plannedBoundaryAttention";
20
+ import type { AppShellRouteNavLabelBundle } from "@/lib/reportingStrings";
21
+ import {
22
+ appShellToolbarDashboardPulseChromeClass,
23
+ appShellToolbarGlobalPauseResumeHighlightClass,
24
+ appShellToolbarIconActiveClass,
25
+ appShellToolbarIconLinkClass,
26
+ appShellToolbarRibbonGroupClass,
27
+ } from "@/lib/appShellToolbarChrome";
19
28
 
20
- const iconLinkClass =
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";
22
-
23
- const iconActiveClass =
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";
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). */
29
+ const iconLinkClass = appShellToolbarIconLinkClass;
30
+ const iconActiveClass = appShellToolbarIconActiveClass;
31
+ const dashboardPulseChromeClass = appShellToolbarDashboardPulseChromeClass;
32
+ const ribbonGroupClass = appShellToolbarRibbonGroupClass;
30
33
  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)]";
34
+ appShellToolbarGlobalPauseResumeHighlightClass;
35
+ const iconButtonDisabledNativeClass = `${iconLinkClass} disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-45`;
32
36
 
33
37
  type GlobalPauseControlProps = Readonly<{
34
38
  active: boolean;
@@ -85,19 +89,9 @@ function AppShellGlobalPauseButton({
85
89
  );
86
90
  }
87
91
 
88
- export type AppShellRouteNavLabels = {
89
- dashboard: string;
90
- reporting: string;
91
- settings: string;
92
- logs?: string;
93
- /** Guide d’utilisation in-app. */
94
- guide: string;
95
- /** Détails d’implémentation (documentation technique maintenue). */
96
- implementation?: string;
92
+ export type AppShellRouteNavLabels = AppShellRouteNavLabelBundle & {
97
93
  /** Libellé infobulle / `aria-label` pour l’icône « licences » (page licences uniquement). */
98
94
  licenses?: string;
99
- /** Infobulle lorsque l’icône tableau de bord pulse (rappel conflit minuteurs). */
100
- dashboardAttentionHint?: string;
101
95
  };
102
96
 
103
97
  export type AppShellRouteNavCurrent =
@@ -107,7 +101,8 @@ export type AppShellRouteNavCurrent =
107
101
  | "logs"
108
102
  | "licenses"
109
103
  | "guide"
110
- | "implementation";
104
+ | "implementation"
105
+ | "changelog";
111
106
 
112
107
  type Props = Readonly<{
113
108
  current: AppShellRouteNavCurrent;
@@ -119,8 +114,6 @@ type Props = Readonly<{
119
114
  dashboardSessionId?: string | null;
120
115
  globalPauseControl?: GlobalPauseControlProps;
121
116
  ganttControl?: { label: string; onPress: () => void };
122
- /** Réserve l’emplacement du bouton pause globale (tableau de bord) pour éviter les sauts. */
123
- reserveGlobalPauseSlot?: boolean;
124
117
  }>;
125
118
 
126
119
  export function AppShellRouteNav({
@@ -131,9 +124,9 @@ export function AppShellRouteNav({
131
124
  dashboardSessionId,
132
125
  globalPauseControl,
133
126
  ganttControl,
134
- reserveGlobalPauseSlot,
135
127
  }: Props) {
136
- const wrapClass = className ?? "flex flex-wrap items-center gap-1.5";
128
+ const wrapClass =
129
+ className ?? "flex shrink-0 flex-nowrap items-center gap-2.5";
137
130
  const dash = (path: string) =>
138
131
  withDashboardSessionParam(path, dashboardSessionId);
139
132
 
@@ -192,156 +185,138 @@ export function AppShellRouteNav({
192
185
  ? `${dashboardBaseNavClass} ${dashboardPulseChromeClass}`
193
186
  : dashboardBaseNavClass;
194
187
 
188
+ const implLabel = labels.implementation?.trim() ?? "";
189
+
195
190
  return (
196
191
  <nav className={wrapClass} aria-label={navAriaLabel}>
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
- )}
226
-
227
- {current === "reporting" ? (
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
- />
240
- </span>
241
- ) : (
242
- <Link
243
- href={dash("/reporting")}
244
- className={iconLinkClass}
245
- title={labels.reporting}
246
- aria-label={labels.reporting}
247
- >
248
- <BarChart3
249
- size={20}
250
- strokeWidth={2}
251
- className="shrink-0"
252
- aria-hidden
253
- />
254
- </Link>
255
- )}
192
+ <div
193
+ role="group"
194
+ aria-label={labels.navGroupAppAria}
195
+ className={ribbonGroupClass}
196
+ >
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
+ )}
256
226
 
257
- {current === "settings" ? (
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
- />
270
- </span>
271
- ) : (
272
- <Link
273
- href={dash("/settings")}
274
- className={iconLinkClass}
275
- title={labels.settings}
276
- aria-label={labels.settings}
277
- >
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 />
303
- </Link>
304
- )}
227
+ {current === "reporting" ? (
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
+ />
240
+ </span>
241
+ ) : (
242
+ <Link
243
+ href={dash("/reporting")}
244
+ className={iconLinkClass}
245
+ title={labels.reporting}
246
+ aria-label={labels.reporting}
247
+ >
248
+ <BarChart3
249
+ size={20}
250
+ strokeWidth={2}
251
+ className="shrink-0"
252
+ aria-hidden
253
+ />
254
+ </Link>
255
+ )}
305
256
 
306
- {current === "guide" ? (
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
- />
319
- </span>
320
- ) : (
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
- />
333
- </Link>
334
- )}
257
+ {current === "settings" ? (
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
+ />
270
+ </span>
271
+ ) : (
272
+ <Link
273
+ href={dash("/settings")}
274
+ className={iconLinkClass}
275
+ title={labels.settings}
276
+ aria-label={labels.settings}
277
+ >
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 />
303
+ </Link>
304
+ )}
305
+ </div>
335
306
 
336
- {labels.implementation ? (
337
- current === "implementation" ? (
307
+ <div
308
+ role="group"
309
+ aria-label={labels.navGroupDocsAria}
310
+ className={ribbonGroupClass}
311
+ >
312
+ {current === "guide" ? (
338
313
  <span
339
314
  className={iconActiveClass}
340
- title={labels.implementation}
341
- aria-label={labels.implementation}
315
+ title={labels.guide}
316
+ aria-label={labels.guide}
342
317
  aria-current="page"
343
318
  >
344
- <FileCode2
319
+ <BookOpen
345
320
  size={20}
346
321
  strokeWidth={2}
347
322
  className="shrink-0"
@@ -350,57 +325,209 @@ export function AppShellRouteNav({
350
325
  </span>
351
326
  ) : (
352
327
  <Link
353
- href={dash("/implementation")}
328
+ href={dash("/guide")}
354
329
  className={iconLinkClass}
355
- title={labels.implementation}
356
- aria-label={labels.implementation}
330
+ title={labels.guide}
331
+ aria-label={labels.guide}
357
332
  >
358
- <FileCode2
333
+ <BookOpen
359
334
  size={20}
360
335
  strokeWidth={2}
361
336
  className="shrink-0"
362
337
  aria-hidden
363
338
  />
364
339
  </Link>
365
- )
366
- ) : null}
340
+ )}
367
341
 
368
- {current === "licenses" ? (
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
- />
381
- </span>
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
342
+ {(() => {
343
+ const changelogLabel = labels.changelog?.trim() ?? "";
344
+ const changelogDisabled = changelogLabel.length === 0;
345
+ if (changelogDisabled) {
346
+ return (
347
+ <button
348
+ type="button"
349
+ disabled
350
+ className={iconButtonDisabledNativeClass}
351
+ title={labels.navChangelogUnavailableTooltip}
352
+ aria-label={labels.navChangelogUnavailableTooltip}
353
+ >
354
+ <ScrollText
355
+ size={20}
356
+ strokeWidth={2}
357
+ className="shrink-0"
358
+ aria-hidden
359
+ />
360
+ </button>
361
+ );
362
+ }
363
+ return current === "changelog" ? (
364
+ <span
365
+ className={iconActiveClass}
366
+ title={changelogLabel}
367
+ aria-label={changelogLabel}
368
+ aria-current="page"
369
+ >
370
+ <ScrollText
371
+ size={20}
372
+ strokeWidth={2}
373
+ className="shrink-0"
374
+ aria-hidden
375
+ />
376
+ </span>
377
+ ) : (
378
+ <Link
379
+ href={dash("/changelog")}
380
+ className={iconLinkClass}
381
+ title={changelogLabel}
382
+ aria-label={changelogLabel}
383
+ >
384
+ <ScrollText
385
+ size={20}
386
+ strokeWidth={2}
387
+ className="shrink-0"
388
+ aria-hidden
389
+ />
390
+ </Link>
391
+ );
392
+ })()}
393
+
394
+ {implLabel ? (
395
+ current === "implementation" ? (
396
+ <span
397
+ className={iconActiveClass}
398
+ title={implLabel}
399
+ aria-label={implLabel}
400
+ aria-current="page"
401
+ >
402
+ <FileCode2
403
+ size={20}
404
+ strokeWidth={2}
405
+ className="shrink-0"
406
+ aria-hidden
407
+ />
408
+ </span>
409
+ ) : (
410
+ <Link
411
+ href={dash("/implementation")}
412
+ className={iconLinkClass}
413
+ title={implLabel}
414
+ aria-label={implLabel}
415
+ >
416
+ <FileCode2
417
+ size={20}
418
+ strokeWidth={2}
419
+ className="shrink-0"
420
+ aria-hidden
421
+ />
422
+ </Link>
423
+ )
424
+ ) : (
425
+ <button
426
+ type="button"
427
+ disabled
428
+ className={iconButtonDisabledNativeClass}
429
+ title={labels.navImplementationUnavailableTooltip}
430
+ aria-label={labels.navImplementationUnavailableTooltip}
431
+ >
432
+ <FileCode2
433
+ size={20}
434
+ strokeWidth={2}
435
+ className="shrink-0"
436
+ aria-hidden
437
+ />
438
+ </button>
439
+ )}
440
+ </div>
441
+
442
+ <div
443
+ role="group"
444
+ aria-label={labels.navGroupToolsAria}
445
+ className={ribbonGroupClass}
446
+ >
447
+ {ganttControl ? (
448
+ <button
449
+ type="button"
450
+ onClick={ganttControl.onPress}
451
+ className={iconLinkClass}
452
+ title={ganttControl.label}
453
+ aria-label={ganttControl.label}
454
+ >
455
+ <LayoutGrid
456
+ size={20}
457
+ strokeWidth={2}
458
+ className="shrink-0"
459
+ aria-hidden
460
+ />
461
+ </button>
462
+ ) : (
463
+ <button
464
+ type="button"
465
+ disabled
466
+ className={iconButtonDisabledNativeClass}
467
+ title={labels.navGanttDashboardOnlyTooltip}
468
+ aria-label={`${labels.navGanttButtonLabel}. ${labels.navGanttDashboardOnlyTooltip}`}
469
+ >
470
+ <LayoutGrid
471
+ size={20}
472
+ strokeWidth={2}
473
+ className="shrink-0"
474
+ aria-hidden
475
+ />
476
+ </button>
477
+ )}
478
+ {globalPauseControl ? (
479
+ <AppShellGlobalPauseButton control={globalPauseControl} />
480
+ ) : (
481
+ <AppShellGlobalPauseButton
482
+ control={{
483
+ active: false,
484
+ label: labels.navGlobalPauseButtonLabel,
485
+ disabled: true,
486
+ disabledTooltip:
487
+ current === "dashboard"
488
+ ? labels.navGlobalPauseNoSessionContextTooltip
489
+ : labels.navGlobalPauseDashboardOnlyTooltip,
490
+ onPress: () => {},
491
+ }}
396
492
  />
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}
493
+ )}
494
+ </div>
495
+
496
+ <div
497
+ role="group"
498
+ aria-label={labels.navGroupLegalAria}
499
+ className={ribbonGroupClass}
500
+ >
501
+ {current === "licenses" ? (
502
+ <span
503
+ className={iconActiveClass}
504
+ title={licensesTitle}
505
+ aria-label={licensesTitle}
506
+ aria-current="page"
507
+ >
508
+ <FileText
509
+ size={20}
510
+ strokeWidth={2}
511
+ className="shrink-0"
512
+ aria-hidden
513
+ />
514
+ </span>
515
+ ) : (
516
+ <Link
517
+ href={dash("/licenses")}
518
+ className={iconLinkClass}
519
+ title={licensesTitle}
520
+ aria-label={licensesTitle}
521
+ >
522
+ <FileText
523
+ size={20}
524
+ strokeWidth={2}
525
+ className="shrink-0"
526
+ aria-hidden
527
+ />
528
+ </Link>
529
+ )}
530
+ </div>
404
531
  </nav>
405
532
  );
406
533
  }