@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.
- package/README.md +28 -1
- package/app/api/action/route.ts +39 -3
- package/app/api/action-logs/route.ts +24 -0
- package/app/api/backup/route.ts +1 -1
- package/app/api/restore/route.ts +145 -0
- package/app/changelog/page.tsx +71 -4
- package/app/globals.css +127 -0
- package/app/guide/page.tsx +61 -15
- package/app/implementation/page.tsx +700 -0
- package/app/layout.tsx +14 -3
- package/app/licenses/page.tsx +99 -37
- package/app/logs/page.tsx +258 -0
- package/app/manifest.ts +5 -5
- package/app/page.tsx +784 -229
- package/app/reporting/page.tsx +1266 -474
- package/app/settings/page.tsx +252 -18
- package/bin/kronosys.mjs +140 -15
- package/components/KronosysPayloadProvider.tsx +2 -0
- package/components/RouteTransition.tsx +18 -0
- package/components/dashboard/AppShellCommandCenterPlaceholder.tsx +17 -0
- package/components/dashboard/AppShellHeaderSessionMeta.tsx +210 -0
- package/components/dashboard/AppShellHeaderWallClock.tsx +54 -0
- package/components/dashboard/AppShellLiveSessionDrawer.tsx +154 -38
- package/components/dashboard/AppShellRouteNav.tsx +323 -48
- package/components/dashboard/DashboardPauseBackdrop.tsx +50 -0
- package/components/dashboard/DashboardSimpleModal.tsx +168 -25
- package/components/dashboard/DashboardTour.tsx +115 -29
- package/components/dashboard/GlobalPauseConfirmModal.tsx +183 -0
- package/components/dashboard/KronosysDatetimePopoverField.tsx +167 -122
- package/components/dashboard/KronosysTimePopoverField.tsx +54 -12
- package/components/dashboard/NewSessionScopeModal.tsx +211 -20
- package/components/dashboard/PlannedTaskBoundaryConflictWatcher.tsx +275 -0
- package/components/dashboard/ReportingTour.tsx +87 -21
- package/components/dashboard/SavedProjectPicker.tsx +16 -3
- package/components/dashboard/SelectedSessionSidebarBlock.tsx +512 -142
- package/components/dashboard/SessionListPanel.tsx +327 -44
- package/components/dashboard/SettingsTagsProjectsSection.tsx +1073 -264
- package/components/dashboard/SettingsTaskTemplatesSection.tsx +316 -0
- package/components/dashboard/SettingsTour.tsx +86 -21
- package/components/dashboard/TagPills.tsx +14 -1
- package/components/dashboard/TaskFocusPanel.tsx +1081 -478
- package/components/dashboard/TaskSessionLiveCard.tsx +650 -135
- package/components/dashboard/TaskTimelineGanttModal.tsx +601 -0
- package/components/dashboard/taskFieldStyles.ts +20 -4
- package/components/dashboard/useReportingInteractionState.ts +80 -0
- package/lib/appShellHeaderClasses.ts +13 -0
- package/lib/businessRulesMatrix.ts +210 -0
- package/lib/copyToClipboard.ts +43 -0
- package/lib/dashboardCopy.ts +494 -84
- package/lib/dashboardQuickSearch.ts +54 -2
- package/lib/dashboardTimeZone.ts +109 -0
- package/lib/formatAppShellWallClock.ts +66 -0
- package/lib/formatSessionNameTemplate.ts +141 -0
- package/lib/generatedUserChangelog.ts +177 -6
- package/lib/globalPausePreview.ts +292 -0
- package/lib/implementationNotes.ts +1188 -0
- package/lib/kronosysApi.ts +6 -0
- package/lib/kronosysDashboardModalGates.ts +24 -0
- package/lib/plannedBoundaryAttention.ts +9 -0
- package/lib/plannedBoundaryConflict.ts +23 -0
- package/lib/reportingAggregate.ts +517 -75
- package/lib/reportingMetricHelp.ts +8 -0
- package/lib/reportingStrings.ts +37 -3
- package/lib/sessionListMerge.ts +4 -0
- package/lib/sessionTaskSidebarStats.ts +182 -21
- package/lib/settingsCopy.ts +178 -4
- package/lib/taskParsing.ts +360 -103
- package/lib/taskTemplateDraft.ts +135 -0
- package/lib/taskTimelineGantt.ts +265 -0
- package/lib/temporalDisplayPlanned.ts +71 -0
- package/lib/userGuideCopy.ts +121 -47
- package/next.config.ts +7 -0
- package/package.json +12 -24
- package/server/actionDispatch.ts +1000 -77
- package/server/actionTaskSession.ts +337 -24
- package/server/db.ts +7 -15
- package/server/dbSchema.ts +24 -0
- package/server/defaultCfg.ts +5 -0
- package/server/gitlabTokenStore.ts +0 -12
- package/server/liveHistorySync.ts +53 -0
- package/server/mainTimerHydrate.ts +38 -2
- package/server/payloadStore.ts +33 -11
- package/server/sessionWallHydrate.ts +66 -3
- package/server/userActionLog.ts +126 -0
- package/sonar-project.properties +11 -0
- package/tsconfig.json +2 -1
- package/components/dashboard/IssuePickerModal.tsx +0 -168
- package/components/dashboard/ThemeToggle.test.tsx +0 -26
- package/lib/backupCsvExport.test.ts +0 -149
- package/lib/dashboardQuickSearchQuery.test.ts +0 -63
- package/lib/dataDir.test.ts +0 -87
- package/lib/formatIsoShort.test.ts +0 -46
- package/lib/kronoFocusRhythm.test.ts +0 -130
- package/lib/kronoFocusTimerUrgency.test.ts +0 -74
- package/lib/legacyKronoFocusStorageKeys.test.ts +0 -29
- package/lib/reportingAggregate.test.ts +0 -325
- package/lib/reportingNonFinalIndicators.test.ts +0 -157
- package/lib/reportingTagWeekBreakdown.test.ts +0 -141
- package/lib/reportingWeekLayout.test.ts +0 -239
- package/lib/sessionAssiduity.test.ts +0 -25
- package/lib/sessionEndWarnings.test.ts +0 -200
- package/lib/sessionListMerge.test.ts +0 -101
- package/lib/sessionTaskSidebarStats.test.ts +0 -24
- package/lib/taskParsing.test.ts +0 -153
- package/lib/usageProfile.test.ts +0 -84
- package/server/actionDispatch.test.ts +0 -723
- package/server/actionTaskSession.test.ts +0 -713
- package/server/kronoFocusHydrate.test.ts +0 -142
- package/server/kronoFocusMigrate.test.ts +0 -53
- package/server/mainTimerHydrate.test.ts +0 -65
- package/server/payloadStore.test.ts +0 -78
- package/server/sessionWallHydrate.test.ts +0 -46
|
@@ -2,17 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
import type { RefObject } from "react";
|
|
4
4
|
import { useEffect, useMemo, useState } from "react";
|
|
5
|
-
import { CheckCircle2, Pencil } from "lucide-react";
|
|
5
|
+
import { CheckCircle2, ChevronDown, LayoutGrid, Pencil } from "lucide-react";
|
|
6
6
|
import { InlineMetricHelpTrigger } from "@/components/dashboard/InlineMetricHelpTrigger";
|
|
7
7
|
import { SessionLocMetricsSection } from "@/components/dashboard/SessionLocMetricsSection";
|
|
8
8
|
import { WorkspaceGitRepoCard } from "@/components/dashboard/WorkspaceGitRepoCard";
|
|
9
9
|
import { formatDuration, formatWallDurationMs } from "@/lib/taskParsing";
|
|
10
|
-
import { tbEmeraldIcon } from "@/lib/translucentButtonClasses";
|
|
10
|
+
import { tbEmeraldIcon, tbVioletIcon } from "@/lib/translucentButtonClasses";
|
|
11
11
|
import type { DashboardStrings, Lang } from "@/lib/dashboardCopy";
|
|
12
12
|
import type { GitRepoStatisticsPayload } from "@/lib/kronosysApi";
|
|
13
13
|
import { formatSessionEndReasonLine } from "@/lib/sessionEndReason";
|
|
14
14
|
import { SessionEndReasonEditor } from "@/components/dashboard/SessionEndReasonEditor";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
countSessionTasksForSidebar,
|
|
17
|
+
getSessionTaskTimerBreakdown,
|
|
18
|
+
} from "@/lib/sessionTaskSidebarStats";
|
|
16
19
|
import { useSmoothStopwatchDisplayMs } from "@/components/dashboard/useSmoothStopwatchMs";
|
|
17
20
|
import {
|
|
18
21
|
KronosysDatetimePopoverField,
|
|
@@ -27,31 +30,62 @@ type SessionMetricsShape = {
|
|
|
27
30
|
archived?: boolean;
|
|
28
31
|
/** Session en pause (collecte) : la durée murale ne progresse pas tant que la session est en pause. */
|
|
29
32
|
isPaused?: boolean;
|
|
33
|
+
/** Contexte de pause globale sur la session live (présent tant que la reprise globale n’a pas eu lieu). */
|
|
34
|
+
globalPauseContext?: unknown;
|
|
30
35
|
/** Horodatage immuable de création de la session ; repli : `startAt` pour les anciennes données. */
|
|
31
36
|
createdAt?: string | null;
|
|
32
37
|
/** Début officiel de la session (ISO 8601), aligné sur l’historique / la liste. */
|
|
33
38
|
startAt?: string | null;
|
|
34
39
|
endAt?: string | null;
|
|
40
|
+
scheduledEndAt?: string | null;
|
|
35
41
|
sessionEndReasonKind?: string;
|
|
36
42
|
sessionEndReasonNote?: string;
|
|
43
|
+
sessionNote?: string;
|
|
37
44
|
sessionDurationMinutes?: number;
|
|
38
45
|
codingMinutesSession?: number;
|
|
39
46
|
activeMinutes?: number;
|
|
40
47
|
totalEvents?: number;
|
|
41
48
|
tasks?: Array<{
|
|
42
49
|
id?: string;
|
|
50
|
+
name?: string;
|
|
51
|
+
startTime?: string;
|
|
52
|
+
endTime?: string;
|
|
53
|
+
durationMs?: number;
|
|
54
|
+
project?: string | null;
|
|
43
55
|
isDone?: boolean;
|
|
44
56
|
manualTaskTimerPaused?: boolean;
|
|
57
|
+
subtasks?: Array<{ id?: string; durationMs?: number }>;
|
|
58
|
+
activeSubtaskTimerId?: string | null;
|
|
59
|
+
subtaskTimerStartedAt?: string | number;
|
|
60
|
+
mainTimerSegmentStartedAt?: string | null;
|
|
45
61
|
}>;
|
|
46
62
|
activeTasks?: Array<{
|
|
47
63
|
id?: string;
|
|
64
|
+
name?: string;
|
|
65
|
+
startTime?: string;
|
|
66
|
+
endTime?: string;
|
|
67
|
+
durationMs?: number;
|
|
68
|
+
project?: string | null;
|
|
48
69
|
isDone?: boolean;
|
|
49
70
|
manualTaskTimerPaused?: boolean;
|
|
71
|
+
subtasks?: Array<{ id?: string; durationMs?: number }>;
|
|
72
|
+
activeSubtaskTimerId?: string | null;
|
|
73
|
+
subtaskTimerStartedAt?: string | number;
|
|
74
|
+
mainTimerSegmentStartedAt?: string | null;
|
|
50
75
|
}>;
|
|
51
76
|
activeTask?: {
|
|
52
77
|
id?: string;
|
|
78
|
+
name?: string;
|
|
79
|
+
startTime?: string;
|
|
80
|
+
endTime?: string;
|
|
81
|
+
durationMs?: number;
|
|
82
|
+
project?: string | null;
|
|
53
83
|
isDone?: boolean;
|
|
54
84
|
manualTaskTimerPaused?: boolean;
|
|
85
|
+
subtasks?: Array<{ id?: string; durationMs?: number }>;
|
|
86
|
+
activeSubtaskTimerId?: string | null;
|
|
87
|
+
subtaskTimerStartedAt?: string | number;
|
|
88
|
+
mainTimerSegmentStartedAt?: string | null;
|
|
55
89
|
} | null;
|
|
56
90
|
linesWrittenTotal?: number;
|
|
57
91
|
linesWrittenHuman?: number;
|
|
@@ -89,6 +123,9 @@ export function SelectedSessionSidebarBlock({
|
|
|
89
123
|
sessionDurationAlertThresholdMinutes,
|
|
90
124
|
allowSessionStartTimeEdit = true,
|
|
91
125
|
allowSessionEndTimeEdit = true,
|
|
126
|
+
onOpenTaskGantt,
|
|
127
|
+
/** Cadre plein largeur (défaut) ou contenu plat pour modale élargie (moins de hauteur, grille horizontale). */
|
|
128
|
+
surface = "card",
|
|
92
129
|
}: {
|
|
93
130
|
lang: Lang;
|
|
94
131
|
t: DashboardStrings;
|
|
@@ -126,6 +163,9 @@ export function SelectedSessionSidebarBlock({
|
|
|
126
163
|
allowSessionStartTimeEdit?: boolean;
|
|
127
164
|
/** Option `dashboardAllowSessionEndTimeEdit` : correction de la fin de session (terminée). */
|
|
128
165
|
allowSessionEndTimeEdit?: boolean;
|
|
166
|
+
/** Ouvre la modale Gantt pour la session affichée dans cette carte. */
|
|
167
|
+
onOpenTaskGantt: () => void;
|
|
168
|
+
surface?: "card" | "modalEmbed";
|
|
129
169
|
}) {
|
|
130
170
|
const liveSid =
|
|
131
171
|
typeof sessionCurrent?.sessionId === "string"
|
|
@@ -146,13 +186,17 @@ export function SelectedSessionSidebarBlock({
|
|
|
146
186
|
number | null
|
|
147
187
|
>(null);
|
|
148
188
|
const [sessionEndDraft, setSessionEndDraft] = useState("");
|
|
189
|
+
const [sessionNoteDraft, setSessionNoteDraft] = useState("");
|
|
190
|
+
const [sessionClosureExpanded, setSessionClosureExpanded] = useState(false);
|
|
191
|
+
const [blinkNowMs, setBlinkNowMs] = useState(() => Date.now());
|
|
192
|
+
const [statsNowMs, setStatsNowMs] = useState(() => Date.now());
|
|
149
193
|
|
|
150
194
|
const sessionWallMinutes =
|
|
151
195
|
optimisticSessionWallMinutes !== null
|
|
152
196
|
? optimisticSessionWallMinutes
|
|
153
197
|
: derivedSessionWallMinutes !== null
|
|
154
|
-
|
|
155
|
-
|
|
198
|
+
? derivedSessionWallMinutes
|
|
199
|
+
: sessionCurrent?.sessionDurationMinutes ?? 0;
|
|
156
200
|
const sessionEnded =
|
|
157
201
|
typeof sessionCurrent?.endAt === "string" &&
|
|
158
202
|
sessionCurrent.endAt.trim() !== "";
|
|
@@ -175,7 +219,11 @@ export function SelectedSessionSidebarBlock({
|
|
|
175
219
|
const sessionWallDisplayMinutes = sessionWallDisplayMs / 60_000;
|
|
176
220
|
const threshold = Math.max(1, sessionDurationAlertThresholdMinutes);
|
|
177
221
|
const sessionDurationOverThreshold = sessionWallDisplayMinutes >= threshold;
|
|
178
|
-
const taskCounts = countSessionTasksForSidebar(sessionCurrent);
|
|
222
|
+
const taskCounts = countSessionTasksForSidebar(sessionCurrent, statsNowMs);
|
|
223
|
+
const taskTimerBreakdown = getSessionTaskTimerBreakdown(
|
|
224
|
+
sessionCurrent,
|
|
225
|
+
statsNowMs,
|
|
226
|
+
);
|
|
179
227
|
const endReasonLine = sessionCurrent
|
|
180
228
|
? formatSessionEndReasonLine(
|
|
181
229
|
t,
|
|
@@ -203,6 +251,10 @@ export function SelectedSessionSidebarBlock({
|
|
|
203
251
|
targetSessionEndReasonId !== "" &&
|
|
204
252
|
!inspectingLiveRunning &&
|
|
205
253
|
(sessionEnded || inspectingArchive);
|
|
254
|
+
const hasSessionClosureBox =
|
|
255
|
+
hasSessionContext &&
|
|
256
|
+
(canEditSessionEndReason ||
|
|
257
|
+
(showSessionEndReason && Boolean(endReasonLine)));
|
|
206
258
|
const canEditSessionStartTime =
|
|
207
259
|
allowSessionStartTimeEdit &&
|
|
208
260
|
hasSessionContext &&
|
|
@@ -211,11 +263,8 @@ export function SelectedSessionSidebarBlock({
|
|
|
211
263
|
const canEditSessionEndTime =
|
|
212
264
|
allowSessionEndTimeEdit &&
|
|
213
265
|
hasSessionContext &&
|
|
214
|
-
sessionEnded &&
|
|
215
266
|
typeof sessionCurrent?.startAt === "string" &&
|
|
216
|
-
sessionCurrent.startAt.trim() !== ""
|
|
217
|
-
typeof sessionCurrent?.endAt === "string" &&
|
|
218
|
-
sessionCurrent.endAt.trim() !== "";
|
|
267
|
+
sessionCurrent.startAt.trim() !== "";
|
|
219
268
|
const [sessionStartDraft, setSessionStartDraft] = useState("");
|
|
220
269
|
|
|
221
270
|
useEffect(() => {
|
|
@@ -235,6 +284,8 @@ export function SelectedSessionSidebarBlock({
|
|
|
235
284
|
const raw =
|
|
236
285
|
typeof sessionCurrent?.endAt === "string"
|
|
237
286
|
? sessionCurrent.endAt.trim()
|
|
287
|
+
: typeof sessionCurrent?.scheduledEndAt === "string"
|
|
288
|
+
? sessionCurrent.scheduledEndAt.trim()
|
|
238
289
|
: "";
|
|
239
290
|
const parsed = raw ? new Date(raw) : null;
|
|
240
291
|
if (!parsed || Number.isNaN(parsed.getTime())) {
|
|
@@ -242,7 +293,19 @@ export function SelectedSessionSidebarBlock({
|
|
|
242
293
|
return;
|
|
243
294
|
}
|
|
244
295
|
setSessionEndDraft(formatDatetimeLocalValue(parsed));
|
|
245
|
-
}, [
|
|
296
|
+
}, [
|
|
297
|
+
sessionCurrent?.sessionId,
|
|
298
|
+
sessionCurrent?.endAt,
|
|
299
|
+
sessionCurrent?.scheduledEndAt,
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
useEffect(() => {
|
|
303
|
+
setSessionNoteDraft(
|
|
304
|
+
typeof sessionCurrent?.sessionNote === "string"
|
|
305
|
+
? sessionCurrent.sessionNote
|
|
306
|
+
: "",
|
|
307
|
+
);
|
|
308
|
+
}, [sessionCurrent?.sessionId, sessionCurrent?.sessionNote]);
|
|
246
309
|
|
|
247
310
|
useEffect(() => {
|
|
248
311
|
setOptimisticSessionWallMinutes(null);
|
|
@@ -252,6 +315,11 @@ export function SelectedSessionSidebarBlock({
|
|
|
252
315
|
sessionCurrent?.endAt,
|
|
253
316
|
]);
|
|
254
317
|
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
// Par défaut: bloc de clôture replié à chaque changement de session.
|
|
320
|
+
setSessionClosureExpanded(false);
|
|
321
|
+
}, [sessionCurrent?.sessionId, columnArchiveId]);
|
|
322
|
+
|
|
255
323
|
useEffect(() => {
|
|
256
324
|
const startMs =
|
|
257
325
|
typeof sessionCurrent?.startAt === "string"
|
|
@@ -276,6 +344,24 @@ export function SelectedSessionSidebarBlock({
|
|
|
276
344
|
setDerivedSessionWallMinutes(null);
|
|
277
345
|
}, [sessionCurrent?.startAt, sessionCurrent?.endAt, smoothSessionWall]);
|
|
278
346
|
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
const hasScheduledEnd =
|
|
349
|
+
typeof sessionCurrent?.scheduledEndAt === "string" &&
|
|
350
|
+
sessionCurrent.scheduledEndAt.trim() !== "";
|
|
351
|
+
if (!hasScheduledEnd || sessionEnded) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const interval = globalThis.setInterval(() => {
|
|
355
|
+
setBlinkNowMs(Date.now());
|
|
356
|
+
}, 1000);
|
|
357
|
+
return () => globalThis.clearInterval(interval);
|
|
358
|
+
}, [sessionCurrent?.scheduledEndAt, sessionEnded, sessionCurrent?.sessionId]);
|
|
359
|
+
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
const id = globalThis.setInterval(() => setStatsNowMs(Date.now()), 1000);
|
|
362
|
+
return () => globalThis.clearInterval(id);
|
|
363
|
+
}, []);
|
|
364
|
+
|
|
279
365
|
const applySessionStartTimeEdit = () => {
|
|
280
366
|
if (!canEditSessionStartTime) {
|
|
281
367
|
return;
|
|
@@ -288,6 +374,13 @@ export function SelectedSessionSidebarBlock({
|
|
|
288
374
|
if (startAt === sessionCurrent?.startAt) {
|
|
289
375
|
return;
|
|
290
376
|
}
|
|
377
|
+
const endWallMs =
|
|
378
|
+
typeof sessionCurrent?.endAt === "string"
|
|
379
|
+
? Date.parse(sessionCurrent.endAt)
|
|
380
|
+
: Number.NaN;
|
|
381
|
+
if (Number.isFinite(endWallMs) && startMs > endWallMs) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
291
384
|
if (smoothSessionWall) {
|
|
292
385
|
setOptimisticSessionWallMinutes(
|
|
293
386
|
Math.max(0, (Date.now() - startMs) / 60000),
|
|
@@ -313,7 +406,7 @@ export function SelectedSessionSidebarBlock({
|
|
|
313
406
|
return;
|
|
314
407
|
}
|
|
315
408
|
const endAt = new Date(endMs).toISOString();
|
|
316
|
-
if (endAt === sessionCurrent.endAt) {
|
|
409
|
+
if (endAt === (sessionCurrent.endAt ?? sessionCurrent.scheduledEndAt)) {
|
|
317
410
|
return;
|
|
318
411
|
}
|
|
319
412
|
setOptimisticSessionWallMinutes(Math.max(0, (endMs - startMs) / 60000));
|
|
@@ -325,42 +418,165 @@ export function SelectedSessionSidebarBlock({
|
|
|
325
418
|
};
|
|
326
419
|
|
|
327
420
|
const sessionStartFormatted = useMemo(() => {
|
|
328
|
-
const raw =
|
|
421
|
+
const raw =
|
|
422
|
+
typeof sessionCurrent?.startAt === "string"
|
|
423
|
+
? sessionCurrent.startAt.trim()
|
|
424
|
+
: "";
|
|
329
425
|
if (!raw) {
|
|
330
426
|
return null;
|
|
331
427
|
}
|
|
332
428
|
return formatIsoInstantShort(raw, lang, displayTimeZone, use24HourClock);
|
|
333
429
|
}, [sessionCurrent?.startAt, displayTimeZone, lang, use24HourClock]);
|
|
430
|
+
const sessionEndFormatted = useMemo(() => {
|
|
431
|
+
const raw =
|
|
432
|
+
typeof sessionCurrent?.endAt === "string"
|
|
433
|
+
? sessionCurrent.endAt.trim()
|
|
434
|
+
: typeof sessionCurrent?.scheduledEndAt === "string"
|
|
435
|
+
? sessionCurrent.scheduledEndAt.trim()
|
|
436
|
+
: "";
|
|
437
|
+
if (!raw) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
return formatIsoInstantShort(raw, lang, displayTimeZone, use24HourClock);
|
|
441
|
+
}, [
|
|
442
|
+
sessionCurrent?.endAt,
|
|
443
|
+
sessionCurrent?.scheduledEndAt,
|
|
444
|
+
displayTimeZone,
|
|
445
|
+
lang,
|
|
446
|
+
use24HourClock,
|
|
447
|
+
]);
|
|
334
448
|
const sessionCreatedFormatted = useMemo(() => {
|
|
335
449
|
const raw =
|
|
336
|
-
typeof sessionCurrent?.createdAt === "string" &&
|
|
450
|
+
typeof sessionCurrent?.createdAt === "string" &&
|
|
451
|
+
sessionCurrent.createdAt.trim() !== ""
|
|
337
452
|
? sessionCurrent.createdAt.trim()
|
|
338
453
|
: typeof sessionCurrent?.startAt === "string"
|
|
339
|
-
|
|
340
|
-
|
|
454
|
+
? sessionCurrent.startAt.trim()
|
|
455
|
+
: "";
|
|
341
456
|
if (!raw) {
|
|
342
457
|
return null;
|
|
343
458
|
}
|
|
344
459
|
return formatIsoInstantShort(raw, lang, displayTimeZone, use24HourClock);
|
|
345
|
-
}, [
|
|
460
|
+
}, [
|
|
461
|
+
sessionCurrent?.createdAt,
|
|
462
|
+
sessionCurrent?.startAt,
|
|
463
|
+
displayTimeZone,
|
|
464
|
+
lang,
|
|
465
|
+
use24HourClock,
|
|
466
|
+
]);
|
|
346
467
|
const sessionNameFallback =
|
|
347
468
|
liveSid !== ""
|
|
348
469
|
? sessionCreatedFormatted
|
|
349
470
|
? `${liveSid.slice(0, 8)} · ${sessionCreatedFormatted}`
|
|
350
471
|
: liveSid.slice(0, 8)
|
|
351
472
|
: lang === "fr"
|
|
352
|
-
|
|
353
|
-
|
|
473
|
+
? "Nom de la session"
|
|
474
|
+
: "Session name";
|
|
475
|
+
const scheduledSessionEndMs =
|
|
476
|
+
typeof sessionCurrent?.scheduledEndAt === "string" &&
|
|
477
|
+
sessionCurrent.scheduledEndAt.trim() !== ""
|
|
478
|
+
? Date.parse(sessionCurrent.scheduledEndAt)
|
|
479
|
+
: Number.NaN;
|
|
480
|
+
const sessionRemainingMs = Number.isFinite(scheduledSessionEndMs)
|
|
481
|
+
? scheduledSessionEndMs - blinkNowMs
|
|
482
|
+
: Number.NaN;
|
|
483
|
+
let sessionEndAlertClassName = "";
|
|
484
|
+
if (
|
|
485
|
+
!sessionEnded &&
|
|
486
|
+
Number.isFinite(sessionRemainingMs) &&
|
|
487
|
+
sessionRemainingMs > 0
|
|
488
|
+
) {
|
|
489
|
+
if (sessionRemainingMs <= 5 * 60_000) {
|
|
490
|
+
sessionEndAlertClassName = "kronosys-end-time-alert-fast";
|
|
491
|
+
} else if (sessionRemainingMs <= 15 * 60_000) {
|
|
492
|
+
sessionEndAlertClassName = "kronosys-end-time-alert-slow";
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** Session live au premier plan : pas d’archive / pas d’onglet détaché sur une autre session. */
|
|
497
|
+
const isLiveSessionSidebar = hasSessionContext && columnArchiveId === null;
|
|
498
|
+
|
|
499
|
+
const globalPauseActive =
|
|
500
|
+
isLiveSessionSidebar &&
|
|
501
|
+
sessionCurrent &&
|
|
502
|
+
typeof sessionCurrent === "object" &&
|
|
503
|
+
"globalPauseContext" in sessionCurrent &&
|
|
504
|
+
(sessionCurrent as Record<string, unknown>).globalPauseContext != null;
|
|
505
|
+
const sessionWallPausedLive =
|
|
506
|
+
isLiveSessionSidebar &&
|
|
507
|
+
sessionCurrent?.archived !== true &&
|
|
508
|
+
sessionCurrent?.isPaused === true;
|
|
509
|
+
|
|
510
|
+
let sessionSidebarHeading = t.selectedSessionSidebarTitleIdle;
|
|
511
|
+
if (hasSessionContext) {
|
|
512
|
+
sessionSidebarHeading = isLiveSessionSidebar
|
|
513
|
+
? t.selectedSessionSidebarTitleLive
|
|
514
|
+
: t.selectedSessionSidebarTitle;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const isModalEmbed = surface === "modalEmbed";
|
|
518
|
+
const timingStartLabel = lang === "fr" ? "Début" : "Start";
|
|
519
|
+
const timingDurationLabel = lang === "fr" ? "Durée" : "Duration";
|
|
520
|
+
const timingTrackedTotalLabel =
|
|
521
|
+
lang === "fr" ? "Total minuté (tâches)" : "Tracked total (tasks)";
|
|
522
|
+
const timingEndLabel = lang === "fr" ? "Fin" : "End";
|
|
523
|
+
const metricCellClass = isModalEmbed
|
|
524
|
+
? "w-full min-w-0"
|
|
525
|
+
: "w-full max-w-[11rem] sm:max-w-none";
|
|
526
|
+
const tasksCellClass = isModalEmbed
|
|
527
|
+
? "w-full min-w-0"
|
|
528
|
+
: "w-full max-w-[13rem] sm:max-w-none";
|
|
529
|
+
const metricsSectionClass = [
|
|
530
|
+
"grid rounded-lg border border-zinc-200 bg-zinc-50/80 p-3 dark:border-zinc-700/80 dark:bg-zinc-900/35",
|
|
531
|
+
isModalEmbed
|
|
532
|
+
? showIdeCodeTimingMetrics
|
|
533
|
+
? "grid-cols-2 gap-2 text-left md:grid-cols-3 md:gap-3"
|
|
534
|
+
: "grid-cols-2 gap-2 text-left"
|
|
535
|
+
: showIdeCodeTimingMetrics
|
|
536
|
+
? "justify-items-center gap-3 text-center sm:grid-cols-3"
|
|
537
|
+
: "justify-items-center gap-3 text-center sm:grid-cols-2",
|
|
538
|
+
].join(" ");
|
|
539
|
+
|
|
540
|
+
const rootSurfaceClass = isModalEmbed
|
|
541
|
+
? "flex min-w-0 flex-col gap-3 rounded-none border-0 bg-transparent p-0 text-left shadow-none sm:gap-4 dark:bg-transparent"
|
|
542
|
+
: "flex min-w-0 flex-col gap-5 rounded-xl border border-zinc-200 bg-white/90 p-5 text-center shadow-sm sm:p-6 dark:border-zinc-800 dark:bg-zinc-800/40 dark:shadow-none";
|
|
354
543
|
|
|
355
544
|
return (
|
|
356
|
-
<div className=
|
|
545
|
+
<div className={rootSurfaceClass}>
|
|
357
546
|
<div>
|
|
358
|
-
<h2
|
|
359
|
-
{
|
|
547
|
+
<h2
|
|
548
|
+
className={`${
|
|
549
|
+
isLiveSessionSidebar
|
|
550
|
+
? "text-xs font-semibold uppercase tracking-wide text-emerald-600 dark:text-emerald-400"
|
|
551
|
+
: "text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400"
|
|
552
|
+
} ${
|
|
553
|
+
isModalEmbed ? "text-left" : ""
|
|
554
|
+
} flex flex-wrap items-center gap-x-2 gap-y-1`}
|
|
555
|
+
>
|
|
556
|
+
<span>{sessionSidebarHeading}</span>
|
|
557
|
+
{globalPauseActive ? (
|
|
558
|
+
<span className="rounded-md border border-amber-600/55 bg-amber-950/55 px-2 py-0.5 text-[0.65rem] font-semibold uppercase tracking-wide text-amber-100 shadow-sm dark:border-amber-500/45 dark:bg-amber-950/65 dark:text-amber-50">
|
|
559
|
+
{t.selectedSessionSidebarPausedBadgeGlobal}
|
|
560
|
+
</span>
|
|
561
|
+
) : sessionWallPausedLive ? (
|
|
562
|
+
<span className="rounded-md border border-amber-600/55 bg-amber-950/55 px-2 py-0.5 text-[0.65rem] font-semibold uppercase tracking-wide text-amber-100 shadow-sm dark:border-amber-500/45 dark:bg-amber-950/65 dark:text-amber-50">
|
|
563
|
+
{t.selectedSessionSidebarPausedBadgeSession}
|
|
564
|
+
</span>
|
|
565
|
+
) : null}
|
|
360
566
|
</h2>
|
|
361
567
|
{hasSessionContext ? (
|
|
362
|
-
<div
|
|
363
|
-
|
|
568
|
+
<div
|
|
569
|
+
className={`mt-2 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 ${
|
|
570
|
+
isModalEmbed ? "justify-start" : "justify-center"
|
|
571
|
+
}`}
|
|
572
|
+
>
|
|
573
|
+
<div
|
|
574
|
+
className={`flex min-w-0 max-w-full flex-1 basis-48 gap-1.5 sm:max-w-md ${
|
|
575
|
+
isModalEmbed
|
|
576
|
+
? "items-center justify-start"
|
|
577
|
+
: "items-center justify-center"
|
|
578
|
+
}`}
|
|
579
|
+
>
|
|
364
580
|
<input
|
|
365
581
|
ref={sessionNameInputRef}
|
|
366
582
|
id="kronosys-session-name-sidebar"
|
|
@@ -425,51 +641,161 @@ export function SelectedSessionSidebarBlock({
|
|
|
425
641
|
{t.selectedSessionIdleHint}
|
|
426
642
|
</p>
|
|
427
643
|
)}
|
|
428
|
-
{hasSessionContext
|
|
429
|
-
<
|
|
430
|
-
<
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
644
|
+
{hasSessionContext ? (
|
|
645
|
+
<section className="mt-3 space-y-2 rounded-lg border border-zinc-200 bg-zinc-50/90 p-3 text-left text-zinc-700 dark:border-zinc-700/80 dark:bg-zinc-900/40 dark:text-zinc-300">
|
|
646
|
+
<div className="flex min-w-0 items-center gap-2 text-sm">
|
|
647
|
+
<span className="w-20 shrink-0 font-medium text-zinc-500 dark:text-zinc-400">
|
|
648
|
+
{timingStartLabel}
|
|
649
|
+
</span>
|
|
650
|
+
{canEditSessionStartTime ? (
|
|
651
|
+
<div className="min-w-0 flex-1">
|
|
652
|
+
<KronosysDatetimePopoverField
|
|
653
|
+
value={sessionStartDraft}
|
|
654
|
+
onChange={setSessionStartDraft}
|
|
655
|
+
onBlur={applySessionStartTimeEdit}
|
|
656
|
+
aria-label={timingStartLabel}
|
|
657
|
+
lang={lang}
|
|
658
|
+
t={t}
|
|
659
|
+
/>
|
|
660
|
+
</div>
|
|
661
|
+
) : (
|
|
662
|
+
<span className="min-w-0 flex-1 font-mono font-medium text-zinc-800 dark:text-zinc-200">
|
|
663
|
+
{sessionStartFormatted ?? "—"}
|
|
664
|
+
</span>
|
|
665
|
+
)}
|
|
666
|
+
</div>
|
|
667
|
+
<div className="flex min-w-0 items-center gap-2 text-sm">
|
|
668
|
+
<span className="w-20 shrink-0 font-medium text-zinc-500 dark:text-zinc-400">
|
|
669
|
+
{timingEndLabel}
|
|
670
|
+
</span>
|
|
671
|
+
{canEditSessionEndTime ? (
|
|
672
|
+
<div className={`min-w-0 flex-1 ${sessionEndAlertClassName}`}>
|
|
673
|
+
<KronosysDatetimePopoverField
|
|
674
|
+
value={sessionEndDraft}
|
|
675
|
+
onChange={setSessionEndDraft}
|
|
676
|
+
onBlur={applySessionEndTimeEdit}
|
|
677
|
+
aria-label={timingEndLabel}
|
|
678
|
+
lang={lang}
|
|
679
|
+
defaultTimeMode="next-half-hour"
|
|
680
|
+
t={t}
|
|
681
|
+
/>
|
|
682
|
+
</div>
|
|
683
|
+
) : (
|
|
684
|
+
<span
|
|
685
|
+
className={`min-w-0 flex-1 font-mono font-medium text-zinc-800 dark:text-zinc-200 ${sessionEndAlertClassName}`}
|
|
686
|
+
>
|
|
687
|
+
{sessionEndFormatted ?? "—"}
|
|
688
|
+
</span>
|
|
689
|
+
)}
|
|
690
|
+
</div>
|
|
691
|
+
<div className="flex min-w-0 items-center gap-2 text-sm">
|
|
692
|
+
<span className="w-20 shrink-0 font-medium text-zinc-500 dark:text-zinc-400">
|
|
693
|
+
{timingDurationLabel}
|
|
694
|
+
</span>
|
|
695
|
+
<span
|
|
696
|
+
className={`min-w-0 flex-1 font-semibold tabular-nums ${
|
|
697
|
+
sessionDurationOverThreshold
|
|
698
|
+
? "kronosys-session-duration-alert"
|
|
699
|
+
: "text-zinc-900 dark:text-zinc-100"
|
|
700
|
+
}`}
|
|
701
|
+
title={
|
|
702
|
+
sessionDurationOverThreshold
|
|
703
|
+
? lang === "fr"
|
|
704
|
+
? `Durée murale au seuil d’alerte ou au-delà (${Math.round(
|
|
705
|
+
threshold / 60,
|
|
706
|
+
)} h — Paramètres)`
|
|
707
|
+
: `Wall-clock duration at or past alert threshold (${Math.round(
|
|
708
|
+
threshold / 60,
|
|
709
|
+
)} h — Settings)`
|
|
710
|
+
: undefined
|
|
711
|
+
}
|
|
712
|
+
>
|
|
713
|
+
{smoothSessionWall
|
|
714
|
+
? formatWallDurationMs(sessionWallDisplayMs)
|
|
715
|
+
: formatDuration(sessionWallDisplayMinutes)}
|
|
444
716
|
</span>
|
|
445
717
|
<InlineMetricHelpTrigger
|
|
446
718
|
ariaLabel={t.statsMetricSessionDurationHelpAria}
|
|
447
719
|
body={t.statsMetricSessionDurationHelpBody}
|
|
448
720
|
/>
|
|
449
721
|
</div>
|
|
450
|
-
<div
|
|
451
|
-
className=
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
722
|
+
<div className="flex min-w-0 items-start gap-2 text-sm">
|
|
723
|
+
<span className="w-20 shrink-0 font-medium text-zinc-500 dark:text-zinc-400">
|
|
724
|
+
{timingTrackedTotalLabel}
|
|
725
|
+
</span>
|
|
726
|
+
<div className="min-w-0 flex-1 space-y-0.5 font-medium tabular-nums text-zinc-800 dark:text-zinc-200">
|
|
727
|
+
<div>
|
|
728
|
+
{lang === "fr"
|
|
729
|
+
? `${taskTimerBreakdown.taskCount} tâche${
|
|
730
|
+
taskTimerBreakdown.taskCount > 1 ? "s" : ""
|
|
731
|
+
} (${formatWallDurationMs(
|
|
732
|
+
taskTimerBreakdown.taskTimersTotalMs,
|
|
733
|
+
)})`
|
|
734
|
+
: `${taskTimerBreakdown.taskCount} task${
|
|
735
|
+
taskTimerBreakdown.taskCount > 1 ? "s" : ""
|
|
736
|
+
} (${formatWallDurationMs(
|
|
737
|
+
taskTimerBreakdown.taskTimersTotalMs,
|
|
738
|
+
)})`}
|
|
739
|
+
</div>
|
|
740
|
+
<div>
|
|
741
|
+
{lang === "fr"
|
|
742
|
+
? `${taskTimerBreakdown.subtaskCount} sous-tâche${
|
|
743
|
+
taskTimerBreakdown.subtaskCount > 1 ? "s" : ""
|
|
744
|
+
} (${formatWallDurationMs(
|
|
745
|
+
taskTimerBreakdown.subtaskTimersTotalMs,
|
|
746
|
+
)})`
|
|
747
|
+
: `${taskTimerBreakdown.subtaskCount} subtask${
|
|
748
|
+
taskTimerBreakdown.subtaskCount > 1 ? "s" : ""
|
|
749
|
+
} (${formatWallDurationMs(
|
|
750
|
+
taskTimerBreakdown.subtaskTimersTotalMs,
|
|
751
|
+
)})`}
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
467
754
|
</div>
|
|
468
|
-
|
|
755
|
+
<div className="space-y-1 text-sm">
|
|
756
|
+
<label
|
|
757
|
+
htmlFor="kronosys-session-note"
|
|
758
|
+
className="text-[0.65rem] font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-500"
|
|
759
|
+
>
|
|
760
|
+
{t.sessionNoteLabel}
|
|
761
|
+
</label>
|
|
762
|
+
<textarea
|
|
763
|
+
id="kronosys-session-note"
|
|
764
|
+
className="w-full rounded-lg border border-zinc-300 bg-white/90 px-3 py-2 text-sm text-zinc-800 outline-none transition focus:border-violet-500 dark:border-zinc-700 dark:bg-zinc-900/70 dark:text-zinc-100"
|
|
765
|
+
placeholder={t.sessionNotePlaceholder}
|
|
766
|
+
value={sessionNoteDraft}
|
|
767
|
+
onChange={(e) => setSessionNoteDraft(e.target.value)}
|
|
768
|
+
onBlur={() => {
|
|
769
|
+
if (
|
|
770
|
+
sessionNoteDraft === (sessionCurrent?.sessionNote ?? "")
|
|
771
|
+
) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
void post({
|
|
775
|
+
type: "setSessionNote",
|
|
776
|
+
sessionId: targetSessionEndReasonId,
|
|
777
|
+
note: sessionNoteDraft,
|
|
778
|
+
});
|
|
779
|
+
}}
|
|
780
|
+
rows={3}
|
|
781
|
+
/>
|
|
782
|
+
</div>
|
|
783
|
+
</section>
|
|
784
|
+
) : null}
|
|
785
|
+
</div>
|
|
786
|
+
|
|
787
|
+
{hasSessionContext ? (
|
|
788
|
+
<section className={metricsSectionClass}>
|
|
469
789
|
{showIdeCodeTimingMetrics ? (
|
|
470
790
|
<>
|
|
471
|
-
<div className=
|
|
472
|
-
<div
|
|
791
|
+
<div className={metricCellClass}>
|
|
792
|
+
<div
|
|
793
|
+
className={`flex min-h-5 gap-0.5 ${
|
|
794
|
+
isModalEmbed
|
|
795
|
+
? "items-center justify-start"
|
|
796
|
+
: "items-center justify-center"
|
|
797
|
+
}`}
|
|
798
|
+
>
|
|
473
799
|
<span className="text-[0.65rem] uppercase text-zinc-500 dark:text-zinc-500">
|
|
474
800
|
{headerCoding}
|
|
475
801
|
</span>
|
|
@@ -478,12 +804,22 @@ export function SelectedSessionSidebarBlock({
|
|
|
478
804
|
body={t.statsMetricCodingTimeHelpBody}
|
|
479
805
|
/>
|
|
480
806
|
</div>
|
|
481
|
-
<div
|
|
807
|
+
<div
|
|
808
|
+
className={`text-xl font-semibold tabular-nums text-zinc-900 dark:text-zinc-100 ${
|
|
809
|
+
isModalEmbed ? "text-left" : ""
|
|
810
|
+
}`}
|
|
811
|
+
>
|
|
482
812
|
{formatDuration(sessionCurrent?.codingMinutesSession ?? 0)}
|
|
483
813
|
</div>
|
|
484
814
|
</div>
|
|
485
|
-
<div className=
|
|
486
|
-
<div
|
|
815
|
+
<div className={metricCellClass}>
|
|
816
|
+
<div
|
|
817
|
+
className={`flex min-h-5 gap-0.5 ${
|
|
818
|
+
isModalEmbed
|
|
819
|
+
? "items-center justify-start"
|
|
820
|
+
: "items-center justify-center"
|
|
821
|
+
}`}
|
|
822
|
+
>
|
|
487
823
|
<span className="text-[0.65rem] uppercase text-zinc-500 dark:text-zinc-500">
|
|
488
824
|
{headerActive}
|
|
489
825
|
</span>
|
|
@@ -492,14 +828,24 @@ export function SelectedSessionSidebarBlock({
|
|
|
492
828
|
body={t.statsMetricActiveTimeHelpBody}
|
|
493
829
|
/>
|
|
494
830
|
</div>
|
|
495
|
-
<div
|
|
831
|
+
<div
|
|
832
|
+
className={`text-xl font-semibold tabular-nums text-zinc-900 dark:text-zinc-100 ${
|
|
833
|
+
isModalEmbed ? "text-left" : ""
|
|
834
|
+
}`}
|
|
835
|
+
>
|
|
496
836
|
{formatDuration(sessionCurrent?.activeMinutes ?? 0)}
|
|
497
837
|
</div>
|
|
498
838
|
</div>
|
|
499
839
|
</>
|
|
500
840
|
) : null}
|
|
501
|
-
<div className=
|
|
502
|
-
<div
|
|
841
|
+
<div className={tasksCellClass}>
|
|
842
|
+
<div
|
|
843
|
+
className={`flex min-h-5 gap-0.5 ${
|
|
844
|
+
isModalEmbed
|
|
845
|
+
? "items-center justify-start"
|
|
846
|
+
: "items-center justify-center"
|
|
847
|
+
}`}
|
|
848
|
+
>
|
|
503
849
|
<span className="text-[0.65rem] uppercase text-zinc-500 dark:text-zinc-500">
|
|
504
850
|
{headerTasks}
|
|
505
851
|
</span>
|
|
@@ -509,7 +855,13 @@ export function SelectedSessionSidebarBlock({
|
|
|
509
855
|
align="end"
|
|
510
856
|
/>
|
|
511
857
|
</div>
|
|
512
|
-
<div
|
|
858
|
+
<div
|
|
859
|
+
className={`mt-1 w-full space-y-1 text-[0.7rem] leading-tight text-zinc-800 dark:text-zinc-200 ${
|
|
860
|
+
isModalEmbed
|
|
861
|
+
? "max-w-none text-left"
|
|
862
|
+
: "mx-auto max-w-[12.5rem] text-left"
|
|
863
|
+
}`}
|
|
864
|
+
>
|
|
513
865
|
<div className="flex items-baseline justify-between gap-2">
|
|
514
866
|
<span className="min-w-0 shrink text-zinc-500 dark:text-zinc-400">
|
|
515
867
|
{t.statsTasksRowRunning}
|
|
@@ -518,6 +870,14 @@ export function SelectedSessionSidebarBlock({
|
|
|
518
870
|
{taskCounts.running}
|
|
519
871
|
</span>
|
|
520
872
|
</div>
|
|
873
|
+
<div className="flex items-baseline justify-between gap-2">
|
|
874
|
+
<span className="min-w-0 shrink text-zinc-500 dark:text-zinc-400">
|
|
875
|
+
{t.statsTasksRowPlanned}
|
|
876
|
+
</span>
|
|
877
|
+
<span className="shrink-0 tabular-nums font-semibold text-zinc-900 dark:text-zinc-100">
|
|
878
|
+
{taskCounts.planned}
|
|
879
|
+
</span>
|
|
880
|
+
</div>
|
|
521
881
|
<div className="flex items-baseline justify-between gap-2">
|
|
522
882
|
<span className="min-w-0 shrink text-zinc-500 dark:text-zinc-400">
|
|
523
883
|
{t.statsTasksRowPausedList}
|
|
@@ -539,90 +899,100 @@ export function SelectedSessionSidebarBlock({
|
|
|
539
899
|
</section>
|
|
540
900
|
) : null}
|
|
541
901
|
|
|
542
|
-
{
|
|
902
|
+
{hasSessionClosureBox ? (
|
|
543
903
|
<div className="rounded-lg border border-zinc-200 bg-zinc-50/90 px-3 py-2.5 text-left text-zinc-700 dark:border-zinc-700/80 dark:bg-zinc-900/40 dark:text-zinc-300">
|
|
544
|
-
<
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
{canEditSessionEndTime ? (
|
|
561
|
-
<div className="rounded-lg border border-zinc-200 bg-zinc-50/90 px-3 py-2.5 text-left text-zinc-700 dark:border-zinc-700/80 dark:bg-zinc-900/40 dark:text-zinc-300">
|
|
562
|
-
<div className="text-[0.65rem] font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-500">
|
|
563
|
-
{t.sessionEndTimeEditSectionTitle}
|
|
564
|
-
</div>
|
|
565
|
-
<div className="mt-2 flex min-w-0 flex-wrap items-center gap-2">
|
|
566
|
-
<KronosysDatetimePopoverField
|
|
567
|
-
value={sessionEndDraft}
|
|
568
|
-
onChange={setSessionEndDraft}
|
|
569
|
-
onBlur={applySessionEndTimeEdit}
|
|
570
|
-
aria-label={t.sessionEndTimeEditSectionTitle}
|
|
571
|
-
lang={lang}
|
|
572
|
-
t={t}
|
|
904
|
+
<button
|
|
905
|
+
type="button"
|
|
906
|
+
className="flex w-full items-center justify-between gap-2 rounded-md text-left hover:text-zinc-900 dark:hover:text-zinc-100"
|
|
907
|
+
aria-expanded={sessionClosureExpanded}
|
|
908
|
+
onClick={() => setSessionClosureExpanded((v) => !v)}
|
|
909
|
+
>
|
|
910
|
+
<div className="text-[0.65rem] font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-500">
|
|
911
|
+
{t.selectedSessionEndReasonTitle}
|
|
912
|
+
</div>
|
|
913
|
+
<ChevronDown
|
|
914
|
+
size={16}
|
|
915
|
+
className={`shrink-0 text-zinc-500 transition-transform dark:text-zinc-400 ${
|
|
916
|
+
sessionClosureExpanded ? "rotate-180" : ""
|
|
917
|
+
}`}
|
|
918
|
+
aria-hidden
|
|
573
919
|
/>
|
|
574
|
-
</
|
|
920
|
+
</button>
|
|
921
|
+
{sessionClosureExpanded ? (
|
|
922
|
+
canEditSessionEndReason ? (
|
|
923
|
+
<div className="mt-2">
|
|
924
|
+
<SessionEndReasonEditor
|
|
925
|
+
t={t}
|
|
926
|
+
radioGroupName="kronosys-session-end-reason-edit-sidebar"
|
|
927
|
+
sessionId={targetSessionEndReasonId}
|
|
928
|
+
initialKind={sessionCurrent?.sessionEndReasonKind}
|
|
929
|
+
initialNote={sessionCurrent?.sessionEndReasonNote}
|
|
930
|
+
post={post}
|
|
931
|
+
/>
|
|
932
|
+
</div>
|
|
933
|
+
) : showSessionEndReason && endReasonLine ? (
|
|
934
|
+
<p className="mt-2 whitespace-pre-wrap text-[0.75rem] leading-snug">
|
|
935
|
+
{endReasonLine}
|
|
936
|
+
</p>
|
|
937
|
+
) : null
|
|
938
|
+
) : null}
|
|
575
939
|
</div>
|
|
576
940
|
) : null}
|
|
577
941
|
|
|
578
|
-
{
|
|
579
|
-
<div className="
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
sessionId={targetSessionEndReasonId}
|
|
588
|
-
initialKind={sessionCurrent?.sessionEndReasonKind}
|
|
589
|
-
initialNote={sessionCurrent?.sessionEndReasonNote}
|
|
590
|
-
post={post}
|
|
591
|
-
/>
|
|
592
|
-
</div>
|
|
593
|
-
</div>
|
|
594
|
-
) : hasSessionContext && showSessionEndReason && endReasonLine ? (
|
|
595
|
-
<div className="rounded-lg border border-zinc-200 bg-zinc-50/90 px-3 py-2.5 text-left text-[0.75rem] leading-snug text-zinc-700 dark:border-zinc-700/80 dark:bg-zinc-900/40 dark:text-zinc-300">
|
|
596
|
-
<div className="text-[0.65rem] font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-500">
|
|
597
|
-
{t.selectedSessionEndReasonTitle}
|
|
942
|
+
{isModalEmbed && trackCodeMetrics ? (
|
|
943
|
+
<div className="grid gap-3 md:grid-cols-2 md:items-start">
|
|
944
|
+
{hasSessionContext ? (
|
|
945
|
+
<div className="min-w-0 text-left">
|
|
946
|
+
<SessionLocMetricsSection session={sessionCurrent ?? {}} t={t} />
|
|
947
|
+
</div>
|
|
948
|
+
) : null}
|
|
949
|
+
<div className="min-w-0 text-left">
|
|
950
|
+
<WorkspaceGitRepoCard git={gitStats} t={t} lang={lang} />
|
|
598
951
|
</div>
|
|
599
|
-
<p className="mt-1 whitespace-pre-wrap">{endReasonLine}</p>
|
|
600
|
-
</div>
|
|
601
|
-
) : null}
|
|
602
|
-
|
|
603
|
-
{hasSessionContext && trackCodeMetrics ? (
|
|
604
|
-
<div className="text-left">
|
|
605
|
-
<SessionLocMetricsSection session={sessionCurrent ?? {}} t={t} />
|
|
606
|
-
</div>
|
|
607
|
-
) : null}
|
|
608
|
-
|
|
609
|
-
{trackCodeMetrics ? (
|
|
610
|
-
<div className="text-left">
|
|
611
|
-
<WorkspaceGitRepoCard git={gitStats} t={t} lang={lang} />
|
|
612
952
|
</div>
|
|
953
|
+
) : !isModalEmbed ? (
|
|
954
|
+
<>
|
|
955
|
+
{hasSessionContext && trackCodeMetrics ? (
|
|
956
|
+
<div className="text-left">
|
|
957
|
+
<SessionLocMetricsSection session={sessionCurrent ?? {}} t={t} />
|
|
958
|
+
</div>
|
|
959
|
+
) : null}
|
|
960
|
+
{trackCodeMetrics ? (
|
|
961
|
+
<div className="text-left">
|
|
962
|
+
<WorkspaceGitRepoCard git={gitStats} t={t} lang={lang} />
|
|
963
|
+
</div>
|
|
964
|
+
) : null}
|
|
965
|
+
</>
|
|
613
966
|
) : null}
|
|
614
967
|
|
|
615
|
-
{showEndLiveSession ? (
|
|
616
|
-
<div
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
968
|
+
{hasSessionContext || showEndLiveSession ? (
|
|
969
|
+
<div
|
|
970
|
+
className={`flex gap-2 ${
|
|
971
|
+
isModalEmbed ? "justify-start" : "justify-end"
|
|
972
|
+
}`}
|
|
973
|
+
>
|
|
974
|
+
{hasSessionContext ? (
|
|
975
|
+
<button
|
|
976
|
+
type="button"
|
|
977
|
+
className={tbVioletIcon}
|
|
978
|
+
aria-label={t.selectedSessionSidebarTimelineIconAria}
|
|
979
|
+
title={t.selectedSessionSidebarTimelineIconTooltip}
|
|
980
|
+
onClick={() => onOpenTaskGantt()}
|
|
981
|
+
>
|
|
982
|
+
<LayoutGrid size={20} aria-hidden />
|
|
983
|
+
</button>
|
|
984
|
+
) : null}
|
|
985
|
+
{showEndLiveSession ? (
|
|
986
|
+
<button
|
|
987
|
+
type="button"
|
|
988
|
+
className={tbEmeraldIcon}
|
|
989
|
+
aria-label={t.sessionEndLiveAria}
|
|
990
|
+
title={`${t.sessionEndLiveSidebarBtn} — ${t.sessionEndLiveTitle}`}
|
|
991
|
+
onClick={() => onEndLiveSession?.()}
|
|
992
|
+
>
|
|
993
|
+
<CheckCircle2 size={20} aria-hidden />
|
|
994
|
+
</button>
|
|
995
|
+
) : null}
|
|
626
996
|
</div>
|
|
627
997
|
) : null}
|
|
628
998
|
</div>
|