@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
package/app/page.tsx
CHANGED
|
@@ -10,16 +10,20 @@ import {
|
|
|
10
10
|
useState,
|
|
11
11
|
} from "react";
|
|
12
12
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { Plus, X } from "lucide-react";
|
|
14
|
+
import { AppShellHeaderSessionMeta } from "@/components/dashboard/AppShellHeaderSessionMeta";
|
|
15
|
+
import { AppShellHeaderWallClock } from "@/components/dashboard/AppShellHeaderWallClock";
|
|
15
16
|
import {
|
|
16
17
|
postKronosysAction,
|
|
17
18
|
type GitRepoStatisticsPayload,
|
|
18
19
|
type KronosysUpdatePayload,
|
|
19
|
-
type WorkspaceCodeSnapshotPayload,
|
|
20
20
|
} from "@/lib/kronosysApi";
|
|
21
21
|
import { useKronosysPayload } from "@/components/KronosysPayloadProvider";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
appShellHeaderClassName,
|
|
24
|
+
appShellHeaderTitleMetaRowClassName,
|
|
25
|
+
appShellHeaderToolbarClassName,
|
|
26
|
+
} from "@/lib/appShellHeaderClasses";
|
|
23
27
|
import { dashboardColumnTitleRowClassName } from "@/lib/dashboardColumnChrome";
|
|
24
28
|
import { AppVersionStamp } from "@/components/dashboard/AppVersionStamp";
|
|
25
29
|
import { dashboardStrings, type Lang } from "@/lib/dashboardCopy";
|
|
@@ -63,6 +67,7 @@ import { SettingsTagsProjectsSection } from "@/components/dashboard/SettingsTags
|
|
|
63
67
|
import { InlineMetricHelpTrigger } from "@/components/dashboard/InlineMetricHelpTrigger";
|
|
64
68
|
import { LanguageMenu } from "@/components/dashboard/LanguageMenu";
|
|
65
69
|
import { SelectedSessionSidebarBlock } from "@/components/dashboard/SelectedSessionSidebarBlock";
|
|
70
|
+
import { DashboardPauseBackdrop } from "@/components/dashboard/DashboardPauseBackdrop";
|
|
66
71
|
import { ThemeToggle } from "@/components/dashboard/ThemeToggle";
|
|
67
72
|
import { PageRefreshButton } from "@/components/dashboard/PageRefreshButton";
|
|
68
73
|
import { AppShellRouteNav } from "@/components/dashboard/AppShellRouteNav";
|
|
@@ -77,25 +82,51 @@ import { DashboardLangGateModal } from "@/components/dashboard/DashboardLangGate
|
|
|
77
82
|
import { GitIdentityQuickSetupModal } from "@/components/dashboard/GitIdentityQuickSetupModal";
|
|
78
83
|
import { DashboardTour } from "@/components/dashboard/DashboardTour";
|
|
79
84
|
import { isDashboardTourCompleted } from "@/lib/dashboardTourStorage";
|
|
80
|
-
import { workspaceFolderPathStrings } from "@/lib/legacyEditorPayloadKeys";
|
|
81
85
|
import { mergeLiveSessionIntoHistory } from "@/lib/sessionListMerge";
|
|
86
|
+
import {
|
|
87
|
+
buildTaskTimelineGanttRows,
|
|
88
|
+
mergeSessionTasksForTimeline,
|
|
89
|
+
parseSessionBoundsMs,
|
|
90
|
+
} from "@/lib/taskTimelineGantt";
|
|
82
91
|
import {
|
|
83
92
|
showIdeLinkedCodeTimingMetrics,
|
|
84
|
-
showWorkspaceFoldersEmptyMessage,
|
|
85
93
|
trackCodeMetricsFromCfg,
|
|
86
94
|
} from "@/lib/usageProfile";
|
|
87
95
|
import { buildDashboardQuickSearchItems } from "@/lib/dashboardQuickSearch";
|
|
96
|
+
import { parseTaskTemplatesFromPayload } from "@/lib/taskTemplateDraft";
|
|
88
97
|
import { readDashboardUse24HourClockFromCfg } from "@/lib/dashboardClockFormat";
|
|
89
|
-
import {
|
|
98
|
+
import {
|
|
99
|
+
calendarDateKeyInTimeZone,
|
|
100
|
+
readDashboardTimeZoneFromCfg,
|
|
101
|
+
} from "@/lib/dashboardTimeZone";
|
|
90
102
|
import { NewSessionScopeModal } from "@/components/dashboard/NewSessionScopeModal";
|
|
103
|
+
import { GlobalPauseConfirmModal } from "@/components/dashboard/GlobalPauseConfirmModal";
|
|
104
|
+
import { TaskTimelineGanttModal } from "@/components/dashboard/TaskTimelineGanttModal";
|
|
91
105
|
import { useKronosysPackageVersion } from "@/components/KronosysPackageVersionProvider";
|
|
106
|
+
import {
|
|
107
|
+
buildGlobalPauseActivationPreview,
|
|
108
|
+
isGlobalPauseActivationNoOp,
|
|
109
|
+
} from "@/lib/globalPausePreview";
|
|
92
110
|
|
|
93
111
|
type LiveTaskShape = {
|
|
94
112
|
id: string;
|
|
95
113
|
name?: string;
|
|
114
|
+
startTime?: string;
|
|
115
|
+
endTime?: string;
|
|
116
|
+
durationMs?: number;
|
|
117
|
+
taskTimerLaps?: Array<{
|
|
118
|
+
startTime?: string;
|
|
119
|
+
endTime?: string;
|
|
120
|
+
durationMs?: number;
|
|
121
|
+
}>;
|
|
122
|
+
project?: string | null;
|
|
96
123
|
isDone?: boolean;
|
|
97
124
|
manualTaskTimerPaused?: boolean;
|
|
98
|
-
subtasks?: Array<{ done?: boolean }>;
|
|
125
|
+
subtasks?: Array<{ id?: string; durationMs?: number; done?: boolean }>;
|
|
126
|
+
activeSubtaskTimerId?: string | null;
|
|
127
|
+
subtaskTimerStartedAt?: string | number;
|
|
128
|
+
mainTimerSegmentStartedAt?: string | null;
|
|
129
|
+
taskCurrentLapStartedAt?: string | number | null;
|
|
99
130
|
};
|
|
100
131
|
|
|
101
132
|
type LiveShape = {
|
|
@@ -193,13 +224,35 @@ function DashboardHome() {
|
|
|
193
224
|
const [archiveConfirmSessionId, setArchiveConfirmSessionId] = useState<
|
|
194
225
|
string | null
|
|
195
226
|
>(null);
|
|
227
|
+
/** Instantané pour animer la disparition dans la liste tant que le serveur n’a plus cette session dans l’historique. */
|
|
228
|
+
const [archiveListExitSession, setArchiveListExitSession] =
|
|
229
|
+
useState<SessionListEntry | null>(null);
|
|
230
|
+
/** Instantané de la session live pour animer la sortie une fois l’état live effacé côté serveur. */
|
|
231
|
+
const [endLiveListExitSession, setEndLiveListExitSession] =
|
|
232
|
+
useState<SessionListEntry | null>(null);
|
|
196
233
|
const [archiveDismissChecked, setArchiveDismissChecked] = useState(false);
|
|
197
234
|
const [endLiveSessionConfirmFlags, setEndLiveSessionConfirmFlags] =
|
|
198
235
|
useState<EndLiveSessionWarningFlags | null>(null);
|
|
199
236
|
const [endSessionReasonKind, setEndSessionReasonKind] = useState<string>("");
|
|
200
237
|
const [endSessionReasonNote, setEndSessionReasonNote] = useState("");
|
|
238
|
+
const [endSessionTaskHandling, setEndSessionTaskHandling] = useState<
|
|
239
|
+
"keep" | "finish" | "moveToPausedSession"
|
|
240
|
+
>("keep");
|
|
201
241
|
const [tourOpen, setTourOpen] = useState(false);
|
|
202
242
|
const [newSessionModalOpen, setNewSessionModalOpen] = useState(false);
|
|
243
|
+
const pendingNewSessionFocusRef = useRef(false);
|
|
244
|
+
const pendingNewSessionAfterEndRef = useRef<{
|
|
245
|
+
sessionScope: unknown;
|
|
246
|
+
sessionStartAtIso: string | null;
|
|
247
|
+
sessionEndAtIso: string | null;
|
|
248
|
+
} | null>(null);
|
|
249
|
+
const createNewSessionAndFocusRef = useRef<
|
|
250
|
+
(args: {
|
|
251
|
+
sessionScope: unknown;
|
|
252
|
+
sessionStartAtIso: string | null;
|
|
253
|
+
sessionEndAtIso: string | null;
|
|
254
|
+
}) => Promise<void>
|
|
255
|
+
>(async () => {});
|
|
203
256
|
const [gitBannerDismissed, setGitBannerDismissed] = useState(false);
|
|
204
257
|
const [gitIdentitySetupModalOpen, setGitIdentitySetupModalOpen] =
|
|
205
258
|
useState(false);
|
|
@@ -207,6 +260,12 @@ function DashboardHome() {
|
|
|
207
260
|
useState(false);
|
|
208
261
|
const [updateChangelogModalOpen, setUpdateChangelogModalOpen] =
|
|
209
262
|
useState(false);
|
|
263
|
+
const [globalPauseConfirmOpen, setGlobalPauseConfirmOpen] = useState(false);
|
|
264
|
+
const [ganttForSessionId, setGanttForSessionId] = useState<string | null>(
|
|
265
|
+
null,
|
|
266
|
+
);
|
|
267
|
+
const [ganttModalOpen, setGanttModalOpen] = useState(false);
|
|
268
|
+
const [todayGanttModalOpen, setTodayGanttModalOpen] = useState(false);
|
|
210
269
|
const packageVersion = useKronosysPackageVersion();
|
|
211
270
|
|
|
212
271
|
useEffect(() => {
|
|
@@ -252,6 +311,70 @@ function DashboardHome() {
|
|
|
252
311
|
const raw = (payload.history || []) as SessionListEntry[];
|
|
253
312
|
return mergeLiveSessionIntoHistory(raw, live);
|
|
254
313
|
}, [payload, live]);
|
|
314
|
+
|
|
315
|
+
const liveEndedForExitAnimation =
|
|
316
|
+
typeof endLiveListExitSession?.sessionId === "string" &&
|
|
317
|
+
endLiveListExitSession.sessionId.trim() !== "" &&
|
|
318
|
+
(typeof live?.sessionId !== "string" ||
|
|
319
|
+
live.sessionId.trim() !== endLiveListExitSession.sessionId);
|
|
320
|
+
|
|
321
|
+
const sessionListPanelSessions = useMemo(() => {
|
|
322
|
+
if (archiveListExitSession) {
|
|
323
|
+
const aid = archiveListExitSession.sessionId;
|
|
324
|
+
if (!history.some((s) => s.sessionId === aid)) {
|
|
325
|
+
return [...history, archiveListExitSession];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (
|
|
330
|
+
endLiveListExitSession &&
|
|
331
|
+
liveEndedForExitAnimation &&
|
|
332
|
+
history.some((s) => s.sessionId === endLiveListExitSession.sessionId)
|
|
333
|
+
) {
|
|
334
|
+
const eid = endLiveListExitSession.sessionId;
|
|
335
|
+
return [
|
|
336
|
+
endLiveListExitSession,
|
|
337
|
+
...history.filter((s) => s.sessionId !== eid),
|
|
338
|
+
];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return history;
|
|
342
|
+
}, [
|
|
343
|
+
history,
|
|
344
|
+
archiveListExitSession,
|
|
345
|
+
endLiveListExitSession,
|
|
346
|
+
liveEndedForExitAnimation,
|
|
347
|
+
]);
|
|
348
|
+
|
|
349
|
+
const archiveListExitAnimateId = useMemo(() => {
|
|
350
|
+
if (!archiveListExitSession) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const id = archiveListExitSession.sessionId;
|
|
354
|
+
if (history.some((s) => s.sessionId === id)) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
return id;
|
|
358
|
+
}, [history, archiveListExitSession]);
|
|
359
|
+
|
|
360
|
+
const endLiveListExitAnimateId = useMemo(() => {
|
|
361
|
+
if (!endLiveListExitSession || !liveEndedForExitAnimation) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
const id = endLiveListExitSession.sessionId;
|
|
365
|
+
if (!history.some((s) => s.sessionId === id)) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
return id;
|
|
369
|
+
}, [history, endLiveListExitSession, liveEndedForExitAnimation]);
|
|
370
|
+
|
|
371
|
+
const sessionRowExitAnimateId =
|
|
372
|
+
archiveListExitAnimateId ?? endLiveListExitAnimateId ?? null;
|
|
373
|
+
|
|
374
|
+
const clearSessionRowExitStates = useCallback(() => {
|
|
375
|
+
setArchiveListExitSession(null);
|
|
376
|
+
setEndLiveListExitSession(null);
|
|
377
|
+
}, []);
|
|
255
378
|
const urlResolution = resolveUrlSession(
|
|
256
379
|
urlParam,
|
|
257
380
|
payload,
|
|
@@ -279,7 +402,7 @@ function DashboardHome() {
|
|
|
279
402
|
const columnArchiveId =
|
|
280
403
|
isDetachedUrlTab && urlResolution.id !== live?.sessionId
|
|
281
404
|
? urlResolution.id
|
|
282
|
-
:
|
|
405
|
+
: inspectingId ?? null;
|
|
283
406
|
const viewingSession = columnArchiveId
|
|
284
407
|
? ((history.find((s) => s.sessionId === columnArchiveId) ||
|
|
285
408
|
historyArchived.find((s) => s.sessionId === columnArchiveId)) as
|
|
@@ -288,6 +411,15 @@ function DashboardHome() {
|
|
|
288
411
|
: undefined;
|
|
289
412
|
const sessionCurrent = (viewingSession ?? live) as LiveShape | undefined;
|
|
290
413
|
const isInspecting = Boolean(columnArchiveId);
|
|
414
|
+
const archiveSessionInMainHistoryList = useMemo(
|
|
415
|
+
() =>
|
|
416
|
+
typeof columnArchiveId === "string" &&
|
|
417
|
+
columnArchiveId.length > 0 &&
|
|
418
|
+
sessionListPanelSessions.some((s) => s.sessionId === columnArchiveId),
|
|
419
|
+
[columnArchiveId, sessionListPanelSessions],
|
|
420
|
+
);
|
|
421
|
+
const showSessionSidebarAboveSessionList =
|
|
422
|
+
!columnArchiveId || !archiveSessionInMainHistoryList;
|
|
291
423
|
|
|
292
424
|
useEffect(() => {
|
|
293
425
|
if (!payload) {
|
|
@@ -463,6 +595,19 @@ function DashboardHome() {
|
|
|
463
595
|
const post = useCallback(
|
|
464
596
|
async (body: Record<string, unknown>) => {
|
|
465
597
|
const res = await postKronosysAction(body);
|
|
598
|
+
if (!res.ok) {
|
|
599
|
+
const raw =
|
|
600
|
+
typeof res.result?.newSessionError === "string"
|
|
601
|
+
? res.result.newSessionError.trim()
|
|
602
|
+
: "";
|
|
603
|
+
setHomeDialogAlert(
|
|
604
|
+
raw ||
|
|
605
|
+
(lang === "fr"
|
|
606
|
+
? "L’action n’a pas pu être appliquée."
|
|
607
|
+
: "The action could not be applied."),
|
|
608
|
+
);
|
|
609
|
+
return res;
|
|
610
|
+
}
|
|
466
611
|
await refresh();
|
|
467
612
|
const op = res.result?.sessionOp;
|
|
468
613
|
if (op && !op.ok) {
|
|
@@ -475,8 +620,57 @@ function DashboardHome() {
|
|
|
475
620
|
}
|
|
476
621
|
return res;
|
|
477
622
|
},
|
|
478
|
-
[refresh],
|
|
623
|
+
[lang, refresh],
|
|
624
|
+
);
|
|
625
|
+
const globalPauseContextActive = Boolean(
|
|
626
|
+
live &&
|
|
627
|
+
typeof live === "object" &&
|
|
628
|
+
"globalPauseContext" in live &&
|
|
629
|
+
(live as { globalPauseContext?: unknown }).globalPauseContext,
|
|
630
|
+
);
|
|
631
|
+
const globalPauseActivationPreview = useMemo(
|
|
632
|
+
() =>
|
|
633
|
+
live && typeof live === "object"
|
|
634
|
+
? buildGlobalPauseActivationPreview(live as Record<string, unknown>)
|
|
635
|
+
: null,
|
|
636
|
+
[live],
|
|
479
637
|
);
|
|
638
|
+
const globalPauseNavDisabled = useMemo(
|
|
639
|
+
() =>
|
|
640
|
+
!globalPauseContextActive &&
|
|
641
|
+
globalPauseActivationPreview !== null &&
|
|
642
|
+
isGlobalPauseActivationNoOp(globalPauseActivationPreview),
|
|
643
|
+
[globalPauseContextActive, globalPauseActivationPreview],
|
|
644
|
+
);
|
|
645
|
+
const onGlobalPauseNavPress = useCallback(() => {
|
|
646
|
+
if (globalPauseContextActive) {
|
|
647
|
+
void post({ type: "toggleGlobalPauseContext" });
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (globalPauseNavDisabled) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
setGlobalPauseConfirmOpen(true);
|
|
654
|
+
}, [globalPauseContextActive, globalPauseNavDisabled, post]);
|
|
655
|
+
const confirmGlobalPauseActivation = useCallback(() => {
|
|
656
|
+
setGlobalPauseConfirmOpen(false);
|
|
657
|
+
void post({ type: "toggleGlobalPauseContext" });
|
|
658
|
+
}, [post]);
|
|
659
|
+
const cancelGlobalPauseConfirm = useCallback(() => {
|
|
660
|
+
setGlobalPauseConfirmOpen(false);
|
|
661
|
+
}, []);
|
|
662
|
+
|
|
663
|
+
useEffect(() => {
|
|
664
|
+
if (globalPauseContextActive) {
|
|
665
|
+
setGlobalPauseConfirmOpen(false);
|
|
666
|
+
}
|
|
667
|
+
}, [globalPauseContextActive]);
|
|
668
|
+
|
|
669
|
+
useEffect(() => {
|
|
670
|
+
if (!live) {
|
|
671
|
+
setGlobalPauseConfirmOpen(false);
|
|
672
|
+
}
|
|
673
|
+
}, [live]);
|
|
480
674
|
|
|
481
675
|
const completeLangGate = useCallback(
|
|
482
676
|
(next: Lang) => {
|
|
@@ -504,10 +698,10 @@ function DashboardHome() {
|
|
|
504
698
|
p.error === "disabled"
|
|
505
699
|
? dt.sessionMongoPushFailedDisabled
|
|
506
700
|
: p.error === "not_found"
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
701
|
+
? dt.sessionMongoPushFailedNotFound
|
|
702
|
+
: p.error === "uri_incomplete"
|
|
703
|
+
? dt.sessionMongoPushFailedUri
|
|
704
|
+
: dt.sessionMongoPushFailedMongo;
|
|
511
705
|
setHomeDialogAlert(msg);
|
|
512
706
|
}
|
|
513
707
|
} catch {
|
|
@@ -532,12 +726,16 @@ function DashboardHome() {
|
|
|
532
726
|
const confirmArchiveSession = useCallback(
|
|
533
727
|
(sessionId: string) => {
|
|
534
728
|
if (payload?.dismissArchiveSessionConfirm === true) {
|
|
729
|
+
const snap = history.find((s) => s.sessionId === sessionId);
|
|
730
|
+
if (snap) {
|
|
731
|
+
setArchiveListExitSession(snap);
|
|
732
|
+
}
|
|
535
733
|
void post({ type: "archiveSession", sessionId, archived: true });
|
|
536
734
|
return;
|
|
537
735
|
}
|
|
538
736
|
setArchiveConfirmSessionId(sessionId);
|
|
539
737
|
},
|
|
540
|
-
[payload?.dismissArchiveSessionConfirm, post],
|
|
738
|
+
[history, payload?.dismissArchiveSessionConfirm, post],
|
|
541
739
|
);
|
|
542
740
|
|
|
543
741
|
const handleDeleteConfirm = useCallback(
|
|
@@ -566,6 +764,7 @@ function DashboardHome() {
|
|
|
566
764
|
const flags = getEndLiveSessionWarningFlags(live);
|
|
567
765
|
setEndSessionReasonKind("");
|
|
568
766
|
setEndSessionReasonNote("");
|
|
767
|
+
setEndSessionTaskHandling("keep");
|
|
569
768
|
setEndLiveSessionConfirmFlags(flags);
|
|
570
769
|
}, [live]);
|
|
571
770
|
|
|
@@ -631,15 +830,28 @@ function DashboardHome() {
|
|
|
631
830
|
setEndLiveSessionConfirmFlags(null);
|
|
632
831
|
setEndSessionReasonKind("");
|
|
633
832
|
setEndSessionReasonNote("");
|
|
833
|
+
setEndSessionTaskHandling("keep");
|
|
834
|
+
pendingNewSessionAfterEndRef.current = null;
|
|
634
835
|
}, []);
|
|
635
836
|
|
|
636
|
-
const confirmEndLiveSession = useCallback(() => {
|
|
837
|
+
const confirmEndLiveSession = useCallback(async () => {
|
|
637
838
|
const kind = endSessionReasonKind;
|
|
638
839
|
const note = endSessionReasonNote.trim();
|
|
840
|
+
const handling = endSessionTaskHandling;
|
|
841
|
+
const liveSidRaw =
|
|
842
|
+
typeof live?.sessionId === "string" ? live.sessionId.trim() : "";
|
|
843
|
+
const snap =
|
|
844
|
+
liveSidRaw !== ""
|
|
845
|
+
? history.find((s) => s.sessionId === liveSidRaw)
|
|
846
|
+
: undefined;
|
|
639
847
|
setEndLiveSessionConfirmFlags(null);
|
|
640
848
|
setEndSessionReasonKind("");
|
|
641
849
|
setEndSessionReasonNote("");
|
|
642
|
-
|
|
850
|
+
setEndSessionTaskHandling("keep");
|
|
851
|
+
if (snap) {
|
|
852
|
+
setEndLiveListExitSession(snap);
|
|
853
|
+
}
|
|
854
|
+
await post({
|
|
643
855
|
type: "endLiveSession",
|
|
644
856
|
...(kind === "planned" ||
|
|
645
857
|
kind === "early" ||
|
|
@@ -648,8 +860,21 @@ function DashboardHome() {
|
|
|
648
860
|
? { sessionEndReasonKind: kind }
|
|
649
861
|
: {}),
|
|
650
862
|
...(note.length > 0 ? { sessionEndReasonNote: note } : {}),
|
|
863
|
+
...(handling !== "keep" ? { taskHandling: handling } : {}),
|
|
651
864
|
});
|
|
652
|
-
|
|
865
|
+
const pend = pendingNewSessionAfterEndRef.current;
|
|
866
|
+
pendingNewSessionAfterEndRef.current = null;
|
|
867
|
+
if (pend) {
|
|
868
|
+
await createNewSessionAndFocusRef.current(pend);
|
|
869
|
+
}
|
|
870
|
+
}, [
|
|
871
|
+
history,
|
|
872
|
+
live?.sessionId,
|
|
873
|
+
post,
|
|
874
|
+
endSessionReasonKind,
|
|
875
|
+
endSessionReasonNote,
|
|
876
|
+
endSessionTaskHandling,
|
|
877
|
+
]);
|
|
653
878
|
|
|
654
879
|
const handleSelectSession = useCallback(
|
|
655
880
|
async (sessionId: string) => {
|
|
@@ -709,6 +934,32 @@ function DashboardHome() {
|
|
|
709
934
|
});
|
|
710
935
|
}, []);
|
|
711
936
|
|
|
937
|
+
const focusTaskLauncherInput = useCallback(() => {
|
|
938
|
+
document.getElementById("dashboard-col-tasks")?.scrollIntoView({
|
|
939
|
+
behavior: "smooth",
|
|
940
|
+
block: "start",
|
|
941
|
+
});
|
|
942
|
+
const tryFocus = () => {
|
|
943
|
+
const input = document.getElementById(
|
|
944
|
+
"kronosys-task-launcher-input",
|
|
945
|
+
) as HTMLInputElement | null;
|
|
946
|
+
if (!input) {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
input.focus();
|
|
950
|
+
input.select();
|
|
951
|
+
return true;
|
|
952
|
+
};
|
|
953
|
+
globalThis.requestAnimationFrame(() => {
|
|
954
|
+
if (tryFocus()) {
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
globalThis.setTimeout(() => {
|
|
958
|
+
void tryFocus();
|
|
959
|
+
}, 140);
|
|
960
|
+
});
|
|
961
|
+
}, []);
|
|
962
|
+
|
|
712
963
|
const scrollToTaskInPanel = useCallback((taskId: string) => {
|
|
713
964
|
const el =
|
|
714
965
|
document.getElementById(`kronosys-active-task-${taskId}`) ??
|
|
@@ -716,6 +967,18 @@ function DashboardHome() {
|
|
|
716
967
|
el?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
717
968
|
}, []);
|
|
718
969
|
|
|
970
|
+
const taskLauncherApplyDraftRef = useRef<((text: string) => void) | null>(
|
|
971
|
+
null,
|
|
972
|
+
);
|
|
973
|
+
|
|
974
|
+
const taskTemplatesForSearch = useMemo(
|
|
975
|
+
() =>
|
|
976
|
+
parseTaskTemplatesFromPayload(
|
|
977
|
+
(payload as Record<string, unknown> | undefined)?.taskTemplates,
|
|
978
|
+
),
|
|
979
|
+
[payload],
|
|
980
|
+
);
|
|
981
|
+
|
|
719
982
|
const sessionDurationAlertThresholdMinutes = useMemo(() => {
|
|
720
983
|
const raw = (payload?.cfg as Record<string, unknown> | undefined)
|
|
721
984
|
?.dashboardSessionDurationAlertHours;
|
|
@@ -761,14 +1024,195 @@ function DashboardHome() {
|
|
|
761
1024
|
}, [payload?.cfg]);
|
|
762
1025
|
|
|
763
1026
|
const dashboardDisplayTimeZone = useMemo(
|
|
764
|
-
() =>
|
|
765
|
-
|
|
1027
|
+
() =>
|
|
1028
|
+
readDashboardTimeZoneFromCfg(
|
|
1029
|
+
payload?.cfg as Record<string, unknown> | undefined,
|
|
1030
|
+
),
|
|
1031
|
+
[payload?.cfg],
|
|
766
1032
|
);
|
|
767
1033
|
const dashboardUse24HourClock = useMemo(
|
|
768
|
-
() =>
|
|
769
|
-
|
|
1034
|
+
() =>
|
|
1035
|
+
readDashboardUse24HourClockFromCfg(
|
|
1036
|
+
payload?.cfg as Record<string, unknown> | undefined,
|
|
1037
|
+
),
|
|
1038
|
+
[payload?.cfg],
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
const openGanttForSessionId = useCallback((sessionId: string) => {
|
|
1042
|
+
const id = sessionId.trim();
|
|
1043
|
+
if (!id) {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
setTodayGanttModalOpen(false);
|
|
1047
|
+
setGanttForSessionId(id);
|
|
1048
|
+
setGanttModalOpen(true);
|
|
1049
|
+
}, []);
|
|
1050
|
+
|
|
1051
|
+
const closeGanttModal = useCallback(() => {
|
|
1052
|
+
setGanttModalOpen(false);
|
|
1053
|
+
setGanttForSessionId(null);
|
|
1054
|
+
}, []);
|
|
1055
|
+
|
|
1056
|
+
const openTodayGanttModal = useCallback(() => {
|
|
1057
|
+
setGanttModalOpen(false);
|
|
1058
|
+
setGanttForSessionId(null);
|
|
1059
|
+
setTodayGanttModalOpen(true);
|
|
1060
|
+
}, []);
|
|
1061
|
+
|
|
1062
|
+
const closeTodayGanttModal = useCallback(() => {
|
|
1063
|
+
setTodayGanttModalOpen(false);
|
|
1064
|
+
}, []);
|
|
1065
|
+
|
|
1066
|
+
const ganttResolvedSession = useMemo(():
|
|
1067
|
+
| SessionListEntry
|
|
1068
|
+
| LiveShape
|
|
1069
|
+
| null => {
|
|
1070
|
+
const focus = ganttForSessionId?.trim();
|
|
1071
|
+
if (!focus) {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
const liveSid =
|
|
1075
|
+
typeof live?.sessionId === "string" ? live.sessionId.trim() : "";
|
|
1076
|
+
if (liveSid !== "" && liveSid === focus) {
|
|
1077
|
+
return live as LiveShape;
|
|
1078
|
+
}
|
|
1079
|
+
return (
|
|
1080
|
+
history.find((s) => s.sessionId === focus) ??
|
|
1081
|
+
historyArchived.find((s) => s.sessionId === focus) ??
|
|
1082
|
+
null
|
|
1083
|
+
);
|
|
1084
|
+
}, [ganttForSessionId, live, history, historyArchived]);
|
|
1085
|
+
|
|
1086
|
+
const ganttIsInspecting = useMemo(() => {
|
|
1087
|
+
const focus = ganttForSessionId?.trim() ?? "";
|
|
1088
|
+
const liveSid =
|
|
1089
|
+
typeof live?.sessionId === "string" ? live.sessionId.trim() : "";
|
|
1090
|
+
return liveSid === "" || focus !== liveSid;
|
|
1091
|
+
}, [ganttForSessionId, live?.sessionId]);
|
|
1092
|
+
|
|
1093
|
+
const ganttTimelineRows = useMemo(() => {
|
|
1094
|
+
if (!ganttResolvedSession) {
|
|
1095
|
+
return [];
|
|
1096
|
+
}
|
|
1097
|
+
return buildTaskTimelineGanttRows(
|
|
1098
|
+
mergeSessionTasksForTimeline(ganttResolvedSession),
|
|
1099
|
+
{
|
|
1100
|
+
isInspecting: ganttIsInspecting,
|
|
1101
|
+
lang,
|
|
1102
|
+
displayTimeZone: dashboardDisplayTimeZone,
|
|
1103
|
+
use24HourClock: dashboardUse24HourClock,
|
|
1104
|
+
},
|
|
1105
|
+
);
|
|
1106
|
+
}, [
|
|
1107
|
+
ganttResolvedSession,
|
|
1108
|
+
ganttIsInspecting,
|
|
1109
|
+
lang,
|
|
1110
|
+
dashboardDisplayTimeZone,
|
|
1111
|
+
dashboardUse24HourClock,
|
|
1112
|
+
]);
|
|
1113
|
+
|
|
1114
|
+
const {
|
|
1115
|
+
sessionStartMs: ganttSessionStartMs,
|
|
1116
|
+
sessionEndMs: ganttSessionEndMs,
|
|
1117
|
+
} = useMemo(
|
|
1118
|
+
() => parseSessionBoundsMs(ganttResolvedSession ?? undefined),
|
|
1119
|
+
[ganttResolvedSession],
|
|
770
1120
|
);
|
|
771
1121
|
|
|
1122
|
+
const todayGanttTimelineRows = useMemo(() => {
|
|
1123
|
+
const dayKey = calendarDateKeyInTimeZone(
|
|
1124
|
+
new Date().toISOString(),
|
|
1125
|
+
dashboardDisplayTimeZone,
|
|
1126
|
+
);
|
|
1127
|
+
if (!dayKey) {
|
|
1128
|
+
return [];
|
|
1129
|
+
}
|
|
1130
|
+
const sessionMap = new Map<string, SessionListEntry | LiveShape>();
|
|
1131
|
+
const liveSid =
|
|
1132
|
+
typeof live?.sessionId === "string" ? live.sessionId.trim() : "";
|
|
1133
|
+
if (liveSid !== "") {
|
|
1134
|
+
sessionMap.set(liveSid, live as LiveShape);
|
|
1135
|
+
}
|
|
1136
|
+
for (const session of history) {
|
|
1137
|
+
const sid = session.sessionId?.trim();
|
|
1138
|
+
if (sid) {
|
|
1139
|
+
sessionMap.set(sid, session);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
for (const session of historyArchived) {
|
|
1143
|
+
const sid = session.sessionId?.trim();
|
|
1144
|
+
if (sid && !sessionMap.has(sid)) {
|
|
1145
|
+
sessionMap.set(sid, session);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const mergedTasks: Array<{
|
|
1150
|
+
id: string;
|
|
1151
|
+
name?: string;
|
|
1152
|
+
startTime?: string;
|
|
1153
|
+
endTime?: string;
|
|
1154
|
+
durationMs?: number;
|
|
1155
|
+
project?: string | null;
|
|
1156
|
+
}> = [];
|
|
1157
|
+
|
|
1158
|
+
for (const [sid, session] of sessionMap.entries()) {
|
|
1159
|
+
const tasks = mergeSessionTasksForTimeline(session);
|
|
1160
|
+
for (const task of tasks) {
|
|
1161
|
+
const startIso =
|
|
1162
|
+
typeof task.startTime === "string" ? task.startTime : null;
|
|
1163
|
+
const endIso = typeof task.endTime === "string" ? task.endTime : null;
|
|
1164
|
+
const startDay = calendarDateKeyInTimeZone(
|
|
1165
|
+
startIso,
|
|
1166
|
+
dashboardDisplayTimeZone,
|
|
1167
|
+
);
|
|
1168
|
+
const endDay = calendarDateKeyInTimeZone(
|
|
1169
|
+
endIso,
|
|
1170
|
+
dashboardDisplayTimeZone,
|
|
1171
|
+
);
|
|
1172
|
+
const includeToday =
|
|
1173
|
+
startDay === dayKey ||
|
|
1174
|
+
endDay === dayKey ||
|
|
1175
|
+
(endIso === null &&
|
|
1176
|
+
startDay !== null &&
|
|
1177
|
+
startDay <= dayKey &&
|
|
1178
|
+
liveSid === sid);
|
|
1179
|
+
if (!includeToday) {
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
const taskId =
|
|
1183
|
+
typeof task.id === "string" && task.id.trim() !== ""
|
|
1184
|
+
? task.id.trim()
|
|
1185
|
+
: `task-${mergedTasks.length + 1}`;
|
|
1186
|
+
mergedTasks.push({
|
|
1187
|
+
id: `${sid}:${taskId}`,
|
|
1188
|
+
name: task.name,
|
|
1189
|
+
startTime: startIso ?? undefined,
|
|
1190
|
+
endTime: endIso ?? undefined,
|
|
1191
|
+
durationMs:
|
|
1192
|
+
typeof task.durationMs === "number" ? task.durationMs : undefined,
|
|
1193
|
+
project:
|
|
1194
|
+
typeof task.project === "string" || task.project === null
|
|
1195
|
+
? task.project
|
|
1196
|
+
: null,
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
return buildTaskTimelineGanttRows(mergedTasks, {
|
|
1202
|
+
isInspecting: false,
|
|
1203
|
+
lang,
|
|
1204
|
+
displayTimeZone: dashboardDisplayTimeZone,
|
|
1205
|
+
use24HourClock: dashboardUse24HourClock,
|
|
1206
|
+
});
|
|
1207
|
+
}, [
|
|
1208
|
+
dashboardDisplayTimeZone,
|
|
1209
|
+
dashboardUse24HourClock,
|
|
1210
|
+
history,
|
|
1211
|
+
historyArchived,
|
|
1212
|
+
lang,
|
|
1213
|
+
live,
|
|
1214
|
+
]);
|
|
1215
|
+
|
|
772
1216
|
const quickSearchItems = useMemo(
|
|
773
1217
|
() =>
|
|
774
1218
|
buildDashboardQuickSearchItems({
|
|
@@ -794,6 +1238,13 @@ function DashboardHome() {
|
|
|
794
1238
|
scrollToSession: scrollToSessionInList,
|
|
795
1239
|
focusTasksColumn: focusTasksColumnForSearch,
|
|
796
1240
|
scrollToTask: scrollToTaskInPanel,
|
|
1241
|
+
taskTemplates: taskTemplatesForSearch,
|
|
1242
|
+
onSelectTaskTemplate: (draft) => {
|
|
1243
|
+
focusTasksColumnForSearch();
|
|
1244
|
+
globalThis.requestAnimationFrame(() => {
|
|
1245
|
+
taskLauncherApplyDraftRef.current?.(draft);
|
|
1246
|
+
});
|
|
1247
|
+
},
|
|
797
1248
|
}),
|
|
798
1249
|
[
|
|
799
1250
|
dt,
|
|
@@ -805,6 +1256,7 @@ function DashboardHome() {
|
|
|
805
1256
|
scrollToSessionInList,
|
|
806
1257
|
scrollToTaskInPanel,
|
|
807
1258
|
sessionCurrent,
|
|
1259
|
+
taskTemplatesForSearch,
|
|
808
1260
|
],
|
|
809
1261
|
);
|
|
810
1262
|
|
|
@@ -823,9 +1275,79 @@ function DashboardHome() {
|
|
|
823
1275
|
await refresh();
|
|
824
1276
|
}, [pathname, refresh, router, searchParams, sessionQueryMode]);
|
|
825
1277
|
|
|
1278
|
+
useEffect(() => {
|
|
1279
|
+
if (!pendingNewSessionFocusRef.current) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
const liveSid =
|
|
1283
|
+
typeof live?.sessionId === "string" ? live.sessionId.trim() : "";
|
|
1284
|
+
if (!liveSid) {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
pendingNewSessionFocusRef.current = false;
|
|
1288
|
+
focusTaskLauncherInput();
|
|
1289
|
+
}, [focusTaskLauncherInput, live?.sessionId]);
|
|
1290
|
+
|
|
1291
|
+
const createNewSessionAndFocus = useCallback(
|
|
1292
|
+
async (args: {
|
|
1293
|
+
sessionScope: unknown;
|
|
1294
|
+
sessionStartAtIso: string | null;
|
|
1295
|
+
sessionEndAtIso: string | null;
|
|
1296
|
+
}) => {
|
|
1297
|
+
setNewSessionModalOpen(false);
|
|
1298
|
+
const res = await post({
|
|
1299
|
+
type: "newSession",
|
|
1300
|
+
sessionScope: args.sessionScope,
|
|
1301
|
+
...(args.sessionStartAtIso
|
|
1302
|
+
? { sessionStartAt: args.sessionStartAtIso }
|
|
1303
|
+
: {}),
|
|
1304
|
+
...(args.sessionEndAtIso ? { sessionEndAt: args.sessionEndAtIso } : {}),
|
|
1305
|
+
});
|
|
1306
|
+
if (!res.ok) {
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
const hid =
|
|
1310
|
+
typeof res.result?.newHistorySessionId === "string"
|
|
1311
|
+
? res.result.newHistorySessionId.trim()
|
|
1312
|
+
: "";
|
|
1313
|
+
if (hid !== "") {
|
|
1314
|
+
pendingNewSessionFocusRef.current = false;
|
|
1315
|
+
await handleSelectSession(hid);
|
|
1316
|
+
scrollToSessionInList(hid);
|
|
1317
|
+
focusTaskLauncherInput();
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
pendingNewSessionFocusRef.current = true;
|
|
1321
|
+
if (sessionQueryMode) {
|
|
1322
|
+
router.replace(
|
|
1323
|
+
pathnameWithUpdatedSessionQuery(
|
|
1324
|
+
pathname,
|
|
1325
|
+
searchParams.toString(),
|
|
1326
|
+
null,
|
|
1327
|
+
),
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
focusTaskLauncherInput();
|
|
1331
|
+
},
|
|
1332
|
+
[
|
|
1333
|
+
focusTaskLauncherInput,
|
|
1334
|
+
handleSelectSession,
|
|
1335
|
+
pathname,
|
|
1336
|
+
post,
|
|
1337
|
+
router,
|
|
1338
|
+
scrollToSessionInList,
|
|
1339
|
+
searchParams,
|
|
1340
|
+
sessionQueryMode,
|
|
1341
|
+
],
|
|
1342
|
+
);
|
|
1343
|
+
|
|
1344
|
+
createNewSessionAndFocusRef.current = createNewSessionAndFocus;
|
|
1345
|
+
|
|
826
1346
|
const openSessionInNewTab = useCallback(
|
|
827
1347
|
(sessionId: string) => {
|
|
828
|
-
const url = `${
|
|
1348
|
+
const url = `${
|
|
1349
|
+
globalThis.location.origin
|
|
1350
|
+
}${pathname}?session=${encodeURIComponent(sessionId)}`;
|
|
829
1351
|
globalThis.open(url, "_blank", "noopener,noreferrer");
|
|
830
1352
|
},
|
|
831
1353
|
[pathname],
|
|
@@ -835,10 +1357,10 @@ function DashboardHome() {
|
|
|
835
1357
|
urlResolution.mode === "ok"
|
|
836
1358
|
? urlResolution.id
|
|
837
1359
|
: urlResolution.mode === "loading" && urlParam
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1360
|
+
? urlParam
|
|
1361
|
+
: urlResolution.mode === "invalid"
|
|
1362
|
+
? live?.sessionId
|
|
1363
|
+
: inspectingId ?? live?.sessionId;
|
|
842
1364
|
|
|
843
1365
|
const trimmedSelectedSessionId =
|
|
844
1366
|
typeof selectedSessionId === "string" ? selectedSessionId.trim() : "";
|
|
@@ -858,13 +1380,6 @@ function DashboardHome() {
|
|
|
858
1380
|
typeof gitIdentity?.gitAccountLogin === "string"
|
|
859
1381
|
? gitIdentity.gitAccountLogin.trim()
|
|
860
1382
|
: "";
|
|
861
|
-
const gitContextLine = [
|
|
862
|
-
gitUserName || null,
|
|
863
|
-
gitUserEmail || null,
|
|
864
|
-
gitAccountLogin || null,
|
|
865
|
-
]
|
|
866
|
-
.filter(Boolean)
|
|
867
|
-
.join(" · ");
|
|
868
1383
|
const hasGitIdentityConfigured = Boolean(
|
|
869
1384
|
gitUserName || gitUserEmail || gitAccountLogin,
|
|
870
1385
|
);
|
|
@@ -925,56 +1440,58 @@ function DashboardHome() {
|
|
|
925
1440
|
: "sqlite";
|
|
926
1441
|
const mongoPushEnabled = mongoConnected;
|
|
927
1442
|
|
|
928
|
-
/** Chemins workspace : payload, puis cfg, puis instantané LOC. */
|
|
929
|
-
const resolvedWorkspaceRoots = useMemo(() => {
|
|
930
|
-
const top = workspaceFolderPathStrings(payload);
|
|
931
|
-
if (top.length > 0) {
|
|
932
|
-
return top;
|
|
933
|
-
}
|
|
934
|
-
const cfgObj = payload?.cfg as Record<string, unknown> | undefined;
|
|
935
|
-
const fromCfg = workspaceFolderPathStrings(cfgObj);
|
|
936
|
-
if (fromCfg.length > 0) {
|
|
937
|
-
return fromCfg;
|
|
938
|
-
}
|
|
939
|
-
const snap = payload?.workspaceCodeSnapshot as
|
|
940
|
-
| WorkspaceCodeSnapshotPayload
|
|
941
|
-
| undefined;
|
|
942
|
-
if (snap?.ok === true) {
|
|
943
|
-
const w = snap.workspaceFolder?.trim();
|
|
944
|
-
if (w) {
|
|
945
|
-
return [w];
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
return [];
|
|
949
|
-
}, [payload]);
|
|
950
|
-
|
|
951
1443
|
const gitStats = payload?.gitStats as GitRepoStatisticsPayload | undefined;
|
|
952
1444
|
|
|
953
|
-
const showWorkspaceFoldersEmpty = showWorkspaceFoldersEmptyMessage(
|
|
954
|
-
payload,
|
|
955
|
-
resolvedWorkspaceRoots.length,
|
|
956
|
-
);
|
|
957
1445
|
const showIdeCodeTimingMetrics = showIdeLinkedCodeTimingMetrics(payload, cfg);
|
|
958
1446
|
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1447
|
+
const renderSelectedSessionSidebarCard = () => (
|
|
1448
|
+
<SelectedSessionSidebarBlock
|
|
1449
|
+
lang={lang}
|
|
1450
|
+
t={dt}
|
|
1451
|
+
sessionCurrent={sessionCurrent}
|
|
1452
|
+
columnArchiveId={columnArchiveId}
|
|
1453
|
+
sessionName={sessionName}
|
|
1454
|
+
setSessionName={setSessionName}
|
|
1455
|
+
sessionNameInputRef={sessionNameInputRef}
|
|
1456
|
+
sessionNameFieldActiveRef={sessionNameFieldActiveRef}
|
|
1457
|
+
sessionNameRowFocused={sessionNameRowFocused}
|
|
1458
|
+
setSessionNameRowFocused={setSessionNameRowFocused}
|
|
1459
|
+
post={post}
|
|
1460
|
+
headerSessionLabel={header.session}
|
|
1461
|
+
headerSessionDuration={header.sessionDuration}
|
|
1462
|
+
headerSessionStart={header.sessionStart}
|
|
1463
|
+
displayTimeZone={dashboardDisplayTimeZone}
|
|
1464
|
+
use24HourClock={dashboardUse24HourClock}
|
|
1465
|
+
headerCoding={header.coding}
|
|
1466
|
+
headerActive={header.active}
|
|
1467
|
+
headerTasks={header.tasks}
|
|
1468
|
+
archivedBadge={dt.archivedBadge}
|
|
1469
|
+
trackCodeMetrics={trackCodeMetrics}
|
|
1470
|
+
showIdeCodeTimingMetrics={showIdeCodeTimingMetrics}
|
|
1471
|
+
gitStats={gitStats}
|
|
1472
|
+
liveSessionId={live?.sessionId}
|
|
1473
|
+
onEndLiveSession={() => void handleRequestEndLiveSession()}
|
|
1474
|
+
sessionDurationAlertThresholdMinutes={
|
|
1475
|
+
sessionDurationAlertThresholdMinutes
|
|
1476
|
+
}
|
|
1477
|
+
allowSessionStartTimeEdit={dashboardAllowSessionStartTimeEdit}
|
|
1478
|
+
allowSessionEndTimeEdit={dashboardAllowSessionEndTimeEdit}
|
|
1479
|
+
onOpenTaskGantt={() => {
|
|
1480
|
+
const sid =
|
|
1481
|
+
typeof sessionCurrent?.sessionId === "string"
|
|
1482
|
+
? sessionCurrent.sessionId.trim()
|
|
1483
|
+
: "";
|
|
1484
|
+
if (sid) {
|
|
1485
|
+
openGanttForSessionId(sid);
|
|
1486
|
+
}
|
|
1487
|
+
}}
|
|
1488
|
+
/>
|
|
1489
|
+
);
|
|
973
1490
|
|
|
974
1491
|
return (
|
|
975
1492
|
<div className="min-h-screen bg-zinc-100 text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100">
|
|
976
1493
|
<header className={appShellHeaderClassName}>
|
|
977
|
-
<div className=
|
|
1494
|
+
<div className={appShellHeaderTitleMetaRowClassName}>
|
|
978
1495
|
<div id="dashboard-tour-anchor-intro" className="min-w-0 shrink-0">
|
|
979
1496
|
<div className="flex min-w-0 flex-col gap-1">
|
|
980
1497
|
<h1 className="text-xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">
|
|
@@ -992,79 +1509,11 @@ function DashboardHome() {
|
|
|
992
1509
|
</p>
|
|
993
1510
|
</div>
|
|
994
1511
|
</div>
|
|
995
|
-
<
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
<div className="flex min-w-0 flex-col items-stretch gap-1.5 text-xs text-zinc-600 sm:items-end sm:text-right dark:text-zinc-400">
|
|
1001
|
-
{gitContextLine ? (
|
|
1002
|
-
<p
|
|
1003
|
-
className="flex max-w-full items-center gap-x-2 sm:justify-end"
|
|
1004
|
-
title={gitContextLine}
|
|
1005
|
-
>
|
|
1006
|
-
<User
|
|
1007
|
-
className="shrink-0 text-zinc-500 dark:text-zinc-500"
|
|
1008
|
-
size={14}
|
|
1009
|
-
aria-hidden
|
|
1010
|
-
/>
|
|
1011
|
-
<span className="min-w-0 max-w-[min(100%,48rem)] truncate font-medium text-zinc-800 dark:text-zinc-300">
|
|
1012
|
-
{gitContextLine}
|
|
1013
|
-
</span>
|
|
1014
|
-
</p>
|
|
1015
|
-
) : null}
|
|
1016
|
-
<p className="flex max-w-full flex-wrap items-center gap-x-1.5 sm:justify-end" title={headerDisplayPrefsTitle}>
|
|
1017
|
-
<Globe className="shrink-0 text-zinc-500 dark:text-zinc-500" size={14} aria-hidden />
|
|
1018
|
-
<span className="min-w-0 max-w-[min(100%,48rem)] break-all font-mono text-[0.7rem] font-medium text-zinc-800 dark:text-zinc-300">
|
|
1019
|
-
{dashboardDisplayTimeZone}
|
|
1020
|
-
</span>
|
|
1021
|
-
<span className="text-zinc-400/70 dark:text-zinc-600" aria-hidden>
|
|
1022
|
-
·
|
|
1023
|
-
</span>
|
|
1024
|
-
<Clock className="shrink-0 text-zinc-500 dark:text-zinc-500" size={14} aria-hidden />
|
|
1025
|
-
<span className="shrink-0 font-medium text-zinc-800 dark:text-zinc-300">{headerClockShort}</span>
|
|
1026
|
-
</p>
|
|
1027
|
-
{showWorkspaceFoldersEmpty ? (
|
|
1028
|
-
<p className="flex max-w-full items-start gap-x-2 sm:justify-end">
|
|
1029
|
-
<FolderOpen
|
|
1030
|
-
className="mt-0.5 shrink-0 text-zinc-500 dark:text-zinc-500"
|
|
1031
|
-
size={14}
|
|
1032
|
-
aria-hidden
|
|
1033
|
-
/>
|
|
1034
|
-
<span className="min-w-0 max-w-[min(100%,48rem)] break-words font-medium text-zinc-800 sm:text-right dark:text-zinc-300">
|
|
1035
|
-
{dt.workspaceFoldersEmpty ?? "—"}
|
|
1036
|
-
</span>
|
|
1037
|
-
</p>
|
|
1038
|
-
) : resolvedWorkspaceRoots.length > 0 ? (
|
|
1039
|
-
resolvedWorkspaceRoots.map((p) => (
|
|
1040
|
-
<p
|
|
1041
|
-
key={p}
|
|
1042
|
-
className="flex max-w-full items-start gap-x-2 sm:justify-end"
|
|
1043
|
-
>
|
|
1044
|
-
<FolderOpen
|
|
1045
|
-
className="mt-0.5 shrink-0 text-zinc-500 dark:text-zinc-500"
|
|
1046
|
-
size={14}
|
|
1047
|
-
aria-hidden
|
|
1048
|
-
/>
|
|
1049
|
-
<span className="min-w-0 max-w-[min(100%,48rem)] break-all font-medium text-zinc-800 sm:text-right dark:text-zinc-300">
|
|
1050
|
-
{p}
|
|
1051
|
-
</span>
|
|
1052
|
-
</p>
|
|
1053
|
-
))
|
|
1054
|
-
) : null}
|
|
1055
|
-
{cfg ? (
|
|
1056
|
-
<div className="flex w-full min-w-0 sm:justify-end">
|
|
1057
|
-
<HeaderIntegrationBadges
|
|
1058
|
-
t={dt}
|
|
1059
|
-
localPersistenceDriver={localPersistenceDriver}
|
|
1060
|
-
mongoConnected={mongoConnected}
|
|
1061
|
-
mongoEnabled={mongoEnabled}
|
|
1062
|
-
/>
|
|
1063
|
-
</div>
|
|
1064
|
-
) : null}
|
|
1065
|
-
</div>
|
|
1066
|
-
) : null}
|
|
1067
|
-
</div>
|
|
1512
|
+
<AppShellHeaderSessionMeta
|
|
1513
|
+
domId="dashboard-tour-anchor-user-storage"
|
|
1514
|
+
payload={payload}
|
|
1515
|
+
dt={dt}
|
|
1516
|
+
/>
|
|
1068
1517
|
</div>
|
|
1069
1518
|
<div className="grid w-full grid-cols-1 gap-3 xl:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] xl:items-center xl:gap-x-6 xl:gap-y-0">
|
|
1070
1519
|
<div className="flex flex-wrap items-center justify-between gap-4 xl:contents">
|
|
@@ -1074,8 +1523,9 @@ function DashboardHome() {
|
|
|
1074
1523
|
/>
|
|
1075
1524
|
<div
|
|
1076
1525
|
id="dashboard-tour-anchor-app-toolbar"
|
|
1077
|
-
className=
|
|
1526
|
+
className={`${appShellHeaderToolbarClassName} xl:col-start-3 xl:row-start-1 xl:justify-self-end`}
|
|
1078
1527
|
>
|
|
1528
|
+
<AppShellHeaderWallClock lang={lang} dt={dt} />
|
|
1079
1529
|
<DashboardCommandCenter
|
|
1080
1530
|
dt={dt}
|
|
1081
1531
|
handlers={commandHandlers}
|
|
@@ -1087,6 +1537,25 @@ function DashboardHome() {
|
|
|
1087
1537
|
labels={nav}
|
|
1088
1538
|
navAriaLabel={dt.appShellRouteNavAria}
|
|
1089
1539
|
dashboardSessionId={selectedSessionId}
|
|
1540
|
+
ganttControl={{
|
|
1541
|
+
label: dt.appShellRouteNavTodayGantt,
|
|
1542
|
+
onPress: openTodayGanttModal,
|
|
1543
|
+
}}
|
|
1544
|
+
reserveGlobalPauseSlot={!(live && live.archived !== true)}
|
|
1545
|
+
globalPauseControl={
|
|
1546
|
+
live && live.archived !== true
|
|
1547
|
+
? {
|
|
1548
|
+
active: globalPauseContextActive,
|
|
1549
|
+
label: globalPauseContextActive
|
|
1550
|
+
? dt.appShellRouteNavGlobalResume
|
|
1551
|
+
: dt.appShellRouteNavGlobalPause,
|
|
1552
|
+
disabled: globalPauseNavDisabled,
|
|
1553
|
+
disabledTooltip:
|
|
1554
|
+
dt.appShellRouteNavGlobalPauseDisabledTooltip,
|
|
1555
|
+
onPress: onGlobalPauseNavPress,
|
|
1556
|
+
}
|
|
1557
|
+
: undefined
|
|
1558
|
+
}
|
|
1090
1559
|
/>
|
|
1091
1560
|
<ThemeToggle lang={lang} />
|
|
1092
1561
|
<PageRefreshButton
|
|
@@ -1133,8 +1602,8 @@ function DashboardHome() {
|
|
|
1133
1602
|
typeof id === "string" && id.length > 0,
|
|
1134
1603
|
)
|
|
1135
1604
|
: live.activeTask?.id
|
|
1136
|
-
|
|
1137
|
-
|
|
1605
|
+
? [live.activeTask.id]
|
|
1606
|
+
: undefined
|
|
1138
1607
|
}
|
|
1139
1608
|
t={dt}
|
|
1140
1609
|
post={postVoid}
|
|
@@ -1145,6 +1614,18 @@ function DashboardHome() {
|
|
|
1145
1614
|
</div>
|
|
1146
1615
|
</header>
|
|
1147
1616
|
|
|
1617
|
+
{payload &&
|
|
1618
|
+
live &&
|
|
1619
|
+
live.archived !== true &&
|
|
1620
|
+
typeof live.sessionId === "string" &&
|
|
1621
|
+
live.sessionId.trim() !== "" &&
|
|
1622
|
+
(live.isPaused === true || globalPauseContextActive) ? (
|
|
1623
|
+
<DashboardPauseBackdrop
|
|
1624
|
+
variant={globalPauseContextActive ? "global" : "session"}
|
|
1625
|
+
dt={dt}
|
|
1626
|
+
/>
|
|
1627
|
+
) : null}
|
|
1628
|
+
|
|
1148
1629
|
<div className="mx-auto w-full max-w-[1920px] px-5 pt-5 pb-8 sm:px-8 sm:pt-6 sm:pb-10 lg:px-12 lg:pt-7 lg:pb-11 xl:px-14 xl:pt-7 xl:pb-12">
|
|
1149
1630
|
{error && (
|
|
1150
1631
|
<div
|
|
@@ -1242,10 +1723,10 @@ function DashboardHome() {
|
|
|
1242
1723
|
<div className="grid w-full grid-cols-1 gap-8 xl:grid-cols-[minmax(0,1fr)_minmax(0,2.25fr)_minmax(0,1fr)] xl:items-start xl:gap-x-6 2xl:gap-x-10">
|
|
1243
1724
|
<div
|
|
1244
1725
|
id="dashboard-col-sessions"
|
|
1245
|
-
className="flex min-w-0 flex-col xl:
|
|
1726
|
+
className="flex min-w-0 flex-col xl:min-h-0 xl:overflow-visible xl:pb-6 xl:pr-3 2xl:pr-4"
|
|
1246
1727
|
>
|
|
1247
|
-
<div className="w-full min-w-0">
|
|
1248
|
-
<div className="mx-auto flex w-full max-w-md flex-col gap-8 sm:max-w-lg xl:mx-0 xl:max-w-none">
|
|
1728
|
+
<div className="w-full min-w-0 xl:flex xl:min-h-0 xl:flex-1 xl:flex-col">
|
|
1729
|
+
<div className="mx-auto flex w-full max-w-md flex-col gap-8 sm:max-w-lg xl:mx-0 xl:max-w-none xl:min-h-0 xl:flex-1 xl:flex-col">
|
|
1249
1730
|
<div
|
|
1250
1731
|
className={`min-w-0 ${dashboardColumnTitleRowClassName}`}
|
|
1251
1732
|
>
|
|
@@ -1273,76 +1754,63 @@ function DashboardHome() {
|
|
|
1273
1754
|
</button>
|
|
1274
1755
|
</div>
|
|
1275
1756
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
),
|
|
1334
|
-
)
|
|
1335
|
-
}
|
|
1336
|
-
archivedCount={historyArchived.length}
|
|
1337
|
-
mongoPushEnabled={mongoPushEnabled}
|
|
1338
|
-
onPushSessionToMongo={(id) =>
|
|
1339
|
-
void handlePushSessionToMongo(id)
|
|
1340
|
-
}
|
|
1341
|
-
pushingSessionId={mongoPushBusyId}
|
|
1342
|
-
sessionDurationAlertThresholdMinutes={
|
|
1343
|
-
sessionDurationAlertThresholdMinutes
|
|
1344
|
-
}
|
|
1345
|
-
/>
|
|
1757
|
+
{showSessionSidebarAboveSessionList
|
|
1758
|
+
? renderSelectedSessionSidebarCard()
|
|
1759
|
+
: null}
|
|
1760
|
+
|
|
1761
|
+
<div className="min-h-[14rem] xl:h-full xl:min-h-0 xl:flex-1">
|
|
1762
|
+
<SessionListPanel
|
|
1763
|
+
sessions={sessionListPanelSessions}
|
|
1764
|
+
lang={lang}
|
|
1765
|
+
displayTimeZone={dashboardDisplayTimeZone}
|
|
1766
|
+
use24HourClock={dashboardUse24HourClock}
|
|
1767
|
+
liveSessionId={live?.sessionId}
|
|
1768
|
+
selectedSessionId={selectedSessionId}
|
|
1769
|
+
t={dt}
|
|
1770
|
+
onSelectSession={(id) => void handleSelectSession(id)}
|
|
1771
|
+
onOpenSessionInNewTab={openSessionInNewTab}
|
|
1772
|
+
onEndLiveSession={() =>
|
|
1773
|
+
void handleRequestEndLiveSession()
|
|
1774
|
+
}
|
|
1775
|
+
onArchiveSession={confirmArchiveSession}
|
|
1776
|
+
onDeleteSession={(id) => setDeleteSessionId(id)}
|
|
1777
|
+
onOpenArchives={() =>
|
|
1778
|
+
void router.push(
|
|
1779
|
+
withDashboardSessionParam(
|
|
1780
|
+
"/settings#settings-archived-sessions",
|
|
1781
|
+
selectedSessionId,
|
|
1782
|
+
),
|
|
1783
|
+
)
|
|
1784
|
+
}
|
|
1785
|
+
archivedCount={historyArchived.length}
|
|
1786
|
+
mongoPushEnabled={mongoPushEnabled}
|
|
1787
|
+
onPushSessionToMongo={(id) =>
|
|
1788
|
+
void handlePushSessionToMongo(id)
|
|
1789
|
+
}
|
|
1790
|
+
pushingSessionId={mongoPushBusyId}
|
|
1791
|
+
sessionDurationAlertThresholdMinutes={
|
|
1792
|
+
sessionDurationAlertThresholdMinutes
|
|
1793
|
+
}
|
|
1794
|
+
onOpenSessionGantt={openGanttForSessionId}
|
|
1795
|
+
sessionRowExitAnimateId={sessionRowExitAnimateId}
|
|
1796
|
+
onSessionRowExitAnimationDone={
|
|
1797
|
+
clearSessionRowExitStates
|
|
1798
|
+
}
|
|
1799
|
+
liveChromeExitSessionId={endLiveListExitAnimateId}
|
|
1800
|
+
sortPinSessionId={endLiveListExitAnimateId}
|
|
1801
|
+
sessionDetailInline={
|
|
1802
|
+
archiveSessionInMainHistoryList &&
|
|
1803
|
+
typeof columnArchiveId === "string" &&
|
|
1804
|
+
columnArchiveId.length > 0
|
|
1805
|
+
? {
|
|
1806
|
+
sessionId: columnArchiveId,
|
|
1807
|
+
content: renderSelectedSessionSidebarCard(),
|
|
1808
|
+
}
|
|
1809
|
+
: null
|
|
1810
|
+
}
|
|
1811
|
+
forcePageScroll
|
|
1812
|
+
/>
|
|
1813
|
+
</div>
|
|
1346
1814
|
</div>
|
|
1347
1815
|
</div>
|
|
1348
1816
|
</div>
|
|
@@ -1376,12 +1844,13 @@ function DashboardHome() {
|
|
|
1376
1844
|
showKronoFocusInTaskOps={dashboardShowKronoFocusInTaskOps}
|
|
1377
1845
|
allowTaskStartTimeEdit={dashboardAllowTaskStartTimeEdit}
|
|
1378
1846
|
allowTaskEndTimeEdit={dashboardAllowTaskEndTimeEdit}
|
|
1847
|
+
taskLauncherApplyDraftRef={taskLauncherApplyDraftRef}
|
|
1379
1848
|
/>
|
|
1380
1849
|
</div>
|
|
1381
1850
|
|
|
1382
1851
|
<aside
|
|
1383
1852
|
id="dashboard-col-tags"
|
|
1384
|
-
className="flex min-w-0 flex-col xl:sticky xl:top-
|
|
1853
|
+
className="flex min-w-0 flex-col xl:sticky xl:top-44 xl:max-h-[calc(100vh-12rem)] xl:overflow-y-auto xl:pb-6 xl:pl-3 [scrollbar-gutter:stable] 2xl:pl-4"
|
|
1385
1854
|
aria-labelledby="dashboard-tags-projects-heading"
|
|
1386
1855
|
>
|
|
1387
1856
|
<div className={`min-w-0 ${dashboardColumnTitleRowClassName}`}>
|
|
@@ -1425,11 +1894,61 @@ function DashboardHome() {
|
|
|
1425
1894
|
lang={lang}
|
|
1426
1895
|
t={dt}
|
|
1427
1896
|
onCancel={() => setNewSessionModalOpen(false)}
|
|
1428
|
-
onConfirm={(
|
|
1429
|
-
|
|
1430
|
-
|
|
1897
|
+
onConfirm={(payload) => {
|
|
1898
|
+
const liveSid =
|
|
1899
|
+
typeof live?.sessionId === "string" ? live.sessionId.trim() : "";
|
|
1900
|
+
const hasActiveLive = liveSid !== "" && live?.archived !== true;
|
|
1901
|
+
if (!payload.sessionStartAtIso && hasActiveLive) {
|
|
1902
|
+
pendingNewSessionAfterEndRef.current = {
|
|
1903
|
+
sessionScope: payload.scope,
|
|
1904
|
+
sessionStartAtIso: null,
|
|
1905
|
+
sessionEndAtIso: null,
|
|
1906
|
+
};
|
|
1907
|
+
setNewSessionModalOpen(false);
|
|
1908
|
+
handleRequestEndLiveSession();
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
void createNewSessionAndFocus({
|
|
1912
|
+
sessionScope: payload.scope,
|
|
1913
|
+
sessionStartAtIso: payload.sessionStartAtIso,
|
|
1914
|
+
sessionEndAtIso: payload.sessionEndAtIso,
|
|
1915
|
+
});
|
|
1431
1916
|
}}
|
|
1432
1917
|
/>
|
|
1918
|
+
<GlobalPauseConfirmModal
|
|
1919
|
+
open={globalPauseConfirmOpen}
|
|
1920
|
+
preview={globalPauseActivationPreview}
|
|
1921
|
+
lang={lang}
|
|
1922
|
+
displayTimeZone={dashboardDisplayTimeZone}
|
|
1923
|
+
use24HourClock={dashboardUse24HourClock}
|
|
1924
|
+
t={dt}
|
|
1925
|
+
onCancel={cancelGlobalPauseConfirm}
|
|
1926
|
+
onConfirm={confirmGlobalPauseActivation}
|
|
1927
|
+
/>
|
|
1928
|
+
<TaskTimelineGanttModal
|
|
1929
|
+
open={ganttModalOpen}
|
|
1930
|
+
onClose={closeGanttModal}
|
|
1931
|
+
lang={lang}
|
|
1932
|
+
displayTimeZone={dashboardDisplayTimeZone}
|
|
1933
|
+
use24HourClock={dashboardUse24HourClock}
|
|
1934
|
+
t={dt}
|
|
1935
|
+
rows={ganttTimelineRows}
|
|
1936
|
+
sessionStartMs={ganttSessionStartMs}
|
|
1937
|
+
sessionEndMs={ganttSessionEndMs}
|
|
1938
|
+
/>
|
|
1939
|
+
<TaskTimelineGanttModal
|
|
1940
|
+
open={todayGanttModalOpen}
|
|
1941
|
+
onClose={closeTodayGanttModal}
|
|
1942
|
+
lang={lang}
|
|
1943
|
+
displayTimeZone={dashboardDisplayTimeZone}
|
|
1944
|
+
use24HourClock={dashboardUse24HourClock}
|
|
1945
|
+
t={dt}
|
|
1946
|
+
title={dt.tasksTimelineGanttTodayTitle}
|
|
1947
|
+
description={dt.tasksTimelineGanttTodayDescription}
|
|
1948
|
+
rows={todayGanttTimelineRows}
|
|
1949
|
+
sessionStartMs={null}
|
|
1950
|
+
sessionEndMs={null}
|
|
1951
|
+
/>
|
|
1433
1952
|
<DashboardAlertModal
|
|
1434
1953
|
open={homeDialogAlert !== null}
|
|
1435
1954
|
message={homeDialogAlert ?? ""}
|
|
@@ -1509,6 +2028,38 @@ function DashboardHome() {
|
|
|
1509
2028
|
</label>
|
|
1510
2029
|
))}
|
|
1511
2030
|
</fieldset>
|
|
2031
|
+
{endLiveSessionConfirmFlags.hasPausedOrPendingTasks ||
|
|
2032
|
+
endLiveSessionConfirmFlags.hasActiveTracking ? (
|
|
2033
|
+
<fieldset className="space-y-2 border-0 p-0">
|
|
2034
|
+
<legend className="text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-500">
|
|
2035
|
+
{dt.sessionEndLiveTasksHandlingLegend}
|
|
2036
|
+
</legend>
|
|
2037
|
+
{(
|
|
2038
|
+
[
|
|
2039
|
+
["keep", dt.sessionEndLiveTasksHandlingKeep],
|
|
2040
|
+
["finish", dt.sessionEndLiveTasksHandlingFinish],
|
|
2041
|
+
[
|
|
2042
|
+
"moveToPausedSession",
|
|
2043
|
+
dt.sessionEndLiveTasksHandlingMoveToPaused,
|
|
2044
|
+
],
|
|
2045
|
+
] as const
|
|
2046
|
+
).map(([value, label]) => (
|
|
2047
|
+
<label
|
|
2048
|
+
key={value}
|
|
2049
|
+
className="flex cursor-pointer items-start gap-2 rounded-md py-0.5 pr-1 hover:bg-zinc-100/80 dark:hover:bg-zinc-800/50"
|
|
2050
|
+
>
|
|
2051
|
+
<input
|
|
2052
|
+
type="radio"
|
|
2053
|
+
name="kronosys-session-end-task-handling"
|
|
2054
|
+
className="mt-1 size-4 shrink-0 border-zinc-300 text-violet-600 focus:ring-violet-500/50 dark:border-zinc-600"
|
|
2055
|
+
checked={endSessionTaskHandling === value}
|
|
2056
|
+
onChange={() => setEndSessionTaskHandling(value)}
|
|
2057
|
+
/>
|
|
2058
|
+
<span className="leading-snug">{label}</span>
|
|
2059
|
+
</label>
|
|
2060
|
+
))}
|
|
2061
|
+
</fieldset>
|
|
2062
|
+
) : null}
|
|
1512
2063
|
<label className="block">
|
|
1513
2064
|
<span className="sr-only">{dt.sessionEndReasonNoteAria}</span>
|
|
1514
2065
|
<textarea
|
|
@@ -1545,6 +2096,10 @@ function DashboardHome() {
|
|
|
1545
2096
|
setArchiveConfirmSessionId(null);
|
|
1546
2097
|
setArchiveDismissChecked(false);
|
|
1547
2098
|
if (id) {
|
|
2099
|
+
const snap = history.find((s) => s.sessionId === id);
|
|
2100
|
+
if (snap) {
|
|
2101
|
+
setArchiveListExitSession(snap);
|
|
2102
|
+
}
|
|
1548
2103
|
await post({
|
|
1549
2104
|
type: "archiveSession",
|
|
1550
2105
|
sessionId: id,
|