@nightkatana/kronosys-app 1.0.0-beta.0
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 +81 -0
- package/app/api/action/route.ts +16 -0
- package/app/api/backup/route.ts +84 -0
- package/app/api/health/route.ts +22 -0
- package/app/api/state/route.ts +27 -0
- package/app/apple-icon.png +0 -0
- package/app/changelog/page.tsx +122 -0
- package/app/globals.css +210 -0
- package/app/guide/layout.tsx +11 -0
- package/app/guide/page.tsx +278 -0
- package/app/icon.png +0 -0
- package/app/layout.tsx +77 -0
- package/app/licenses/layout.tsx +11 -0
- package/app/licenses/page.tsx +246 -0
- package/app/manifest.ts +32 -0
- package/app/page.tsx +1610 -0
- package/app/reporting/page.tsx +2943 -0
- package/app/settings/layout.tsx +10 -0
- package/app/settings/page.tsx +3518 -0
- package/bin/kronosys.mjs +46 -0
- package/components/KronosysPackageVersionProvider.tsx +19 -0
- package/components/KronosysPayloadProvider.tsx +109 -0
- package/components/PwaRegister.tsx +25 -0
- package/components/SiteLegalFooter.tsx +21 -0
- package/components/ThemeProvider.tsx +78 -0
- package/components/dashboard/AppShellLiveSessionDrawer.tsx +394 -0
- package/components/dashboard/AppShellRouteNav.tsx +131 -0
- package/components/dashboard/AppVersionStamp.tsx +16 -0
- package/components/dashboard/DashboardCollapsibleSection.tsx +57 -0
- package/components/dashboard/DashboardColumnHintsBanner.tsx +159 -0
- package/components/dashboard/DashboardCommandCenter.tsx +470 -0
- package/components/dashboard/DashboardLangGateModal.tsx +118 -0
- package/components/dashboard/DashboardLoadingOverlay.tsx +42 -0
- package/components/dashboard/DashboardSimpleModal.tsx +337 -0
- package/components/dashboard/DashboardSuspenseFallback.tsx +52 -0
- package/components/dashboard/DashboardToastProvider.tsx +64 -0
- package/components/dashboard/DashboardTour.tsx +435 -0
- package/components/dashboard/DeferredDescriptionPopoverWrap.tsx +39 -0
- package/components/dashboard/DeleteSessionModal.tsx +130 -0
- package/components/dashboard/DescriptionTooltipPortaled.tsx +31 -0
- package/components/dashboard/GitIdentityQuickSetupModal.tsx +211 -0
- package/components/dashboard/HeaderIntegrationBadges.tsx +69 -0
- package/components/dashboard/InlineMetricHelpTrigger.tsx +102 -0
- package/components/dashboard/IssuePickerModal.tsx +168 -0
- package/components/dashboard/KronoFocusPanel.tsx +834 -0
- package/components/dashboard/KronosysDatetimePopoverField.tsx +357 -0
- package/components/dashboard/KronosysTimePopoverField.tsx +233 -0
- package/components/dashboard/LanguageMenu.tsx +123 -0
- package/components/dashboard/MongoMirrorSyncLine.tsx +57 -0
- package/components/dashboard/NewSessionScopeModal.tsx +410 -0
- package/components/dashboard/PageRefreshButton.tsx +130 -0
- package/components/dashboard/PlainHelpPopover.tsx +97 -0
- package/components/dashboard/ReportingPageToc.tsx +68 -0
- package/components/dashboard/ReportingTour.tsx +342 -0
- package/components/dashboard/SavedProjectPicker.tsx +92 -0
- package/components/dashboard/SavedTagPicker.tsx +115 -0
- package/components/dashboard/ScrollToTopFab.tsx +41 -0
- package/components/dashboard/SelectedSessionSidebarBlock.tsx +630 -0
- package/components/dashboard/SessionEndReasonEditor.tsx +114 -0
- package/components/dashboard/SessionListPanel.tsx +320 -0
- package/components/dashboard/SessionLocMetricsSection.tsx +128 -0
- package/components/dashboard/SettingsTagsProjectsSection.tsx +993 -0
- package/components/dashboard/SettingsTour.tsx +332 -0
- package/components/dashboard/TagPills.tsx +149 -0
- package/components/dashboard/TagsHelpTrigger.tsx +84 -0
- package/components/dashboard/TaskFocusPanel.tsx +1261 -0
- package/components/dashboard/TaskSessionLiveCard.tsx +832 -0
- package/components/dashboard/TaskSubtasksBlock.tsx +748 -0
- package/components/dashboard/ThemeToggle.test.tsx +26 -0
- package/components/dashboard/ThemeToggle.tsx +36 -0
- package/components/dashboard/UserGuideBodyText.tsx +62 -0
- package/components/dashboard/WorkspaceGitRepoCard.tsx +191 -0
- package/components/dashboard/taskFieldStyles.ts +139 -0
- package/components/dashboard/useAnchoredFloatingPortalStyle.ts +71 -0
- package/components/dashboard/useDescriptionPopoverAfterMs.ts +220 -0
- package/components/dashboard/useKronoFocusLiveSeconds.ts +36 -0
- package/components/dashboard/useSmoothStopwatchMs.ts +25 -0
- package/lib/appShellHeaderClasses.ts +12 -0
- package/lib/backupCsvExport.test.ts +149 -0
- package/lib/backupCsvExport.ts +392 -0
- package/lib/changelogCopy.ts +34 -0
- package/lib/concurrentTaskStartPreference.ts +29 -0
- package/lib/dashboardClockFormat.ts +13 -0
- package/lib/dashboardColumnChrome.ts +3 -0
- package/lib/dashboardColumnHintsStorage.ts +57 -0
- package/lib/dashboardCopy.ts +1831 -0
- package/lib/dashboardDetachedUrlHintStorage.ts +24 -0
- package/lib/dashboardGitIdentityBannerStorage.ts +36 -0
- package/lib/dashboardLangStorage.ts +72 -0
- package/lib/dashboardQuickSearch.ts +476 -0
- package/lib/dashboardQuickSearchQuery.test.ts +63 -0
- package/lib/dashboardQuickSearchQuery.ts +179 -0
- package/lib/dashboardSessionNav.ts +33 -0
- package/lib/dashboardShortcuts.ts +268 -0
- package/lib/dashboardTimeZone.ts +91 -0
- package/lib/dashboardTourStorage.ts +68 -0
- package/lib/dataDir.test.ts +87 -0
- package/lib/dataDir.ts +83 -0
- package/lib/devDataPreferenceFile.ts +55 -0
- package/lib/devDataRuntimeInfo.ts +34 -0
- package/lib/formatIsoShort.test.ts +46 -0
- package/lib/formatIsoShort.ts +29 -0
- package/lib/generatedUserChangelog.ts +34 -0
- package/lib/gitlabIssueSearch.ts +8 -0
- package/lib/kronoFocusDurationHistory.ts +71 -0
- package/lib/kronoFocusRhythm.test.ts +130 -0
- package/lib/kronoFocusRhythm.ts +46 -0
- package/lib/kronoFocusTimerUrgency.test.ts +74 -0
- package/lib/kronoFocusTimerUrgency.ts +24 -0
- package/lib/kronosysApi.ts +143 -0
- package/lib/legacyEditorPayloadKeys.ts +52 -0
- package/lib/legacyKronoFocusStorageKeys.test.ts +29 -0
- package/lib/legacyKronoFocusStorageKeys.ts +32 -0
- package/lib/licensesCopy.ts +128 -0
- package/lib/openPlainTextInNewTab.ts +49 -0
- package/lib/readKronosysPackageVersion.ts +10 -0
- package/lib/reportingAggregate.test.ts +325 -0
- package/lib/reportingAggregate.ts +819 -0
- package/lib/reportingDatePresets.ts +41 -0
- package/lib/reportingMetricHelp.ts +430 -0
- package/lib/reportingNonFinalIndicators.test.ts +157 -0
- package/lib/reportingNonFinalIndicators.ts +102 -0
- package/lib/reportingStrings.ts +491 -0
- package/lib/reportingTagWeekBreakdown.test.ts +141 -0
- package/lib/reportingTagWeekBreakdown.ts +181 -0
- package/lib/reportingWeekLayout.test.ts +239 -0
- package/lib/reportingWeekLayout.ts +313 -0
- package/lib/sessionAssiduity.test.ts +25 -0
- package/lib/sessionAssiduity.ts +33 -0
- package/lib/sessionEndReason.ts +55 -0
- package/lib/sessionEndWarnings.test.ts +200 -0
- package/lib/sessionEndWarnings.ts +125 -0
- package/lib/sessionListMerge.test.ts +101 -0
- package/lib/sessionListMerge.ts +70 -0
- package/lib/sessionTaskSidebarStats.test.ts +24 -0
- package/lib/sessionTaskSidebarStats.ts +54 -0
- package/lib/settingsCopy.ts +1276 -0
- package/lib/taskParsing.test.ts +153 -0
- package/lib/taskParsing.ts +737 -0
- package/lib/theme.ts +15 -0
- package/lib/translucentButtonClasses.ts +34 -0
- package/lib/usageProfile.test.ts +84 -0
- package/lib/usageProfile.ts +52 -0
- package/lib/userGuideCopy.ts +464 -0
- package/lib/workspaceLocDefaults.ts +21 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +15 -0
- package/package.json +87 -0
- package/postcss.config.mjs +12 -0
- package/public/apple-icon.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/icon.png +0 -0
- package/public/next.svg +1 -0
- package/public/sw.js +13 -0
- package/public/traceback.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/server/actionDispatch.test.ts +723 -0
- package/server/actionDispatch.ts +1476 -0
- package/server/actionTaskSession.test.ts +713 -0
- package/server/actionTaskSession.ts +717 -0
- package/server/db.ts +42 -0
- package/server/defaultCfg.ts +87 -0
- package/server/gitlabTokenStore.ts +34 -0
- package/server/kronoFocusHydrate.test.ts +142 -0
- package/server/kronoFocusHydrate.ts +69 -0
- package/server/kronoFocusMigrate.test.ts +53 -0
- package/server/kronoFocusMigrate.ts +78 -0
- package/server/mainTimerHydrate.test.ts +65 -0
- package/server/mainTimerHydrate.ts +53 -0
- package/server/payloadStore.test.ts +78 -0
- package/server/payloadStore.ts +83 -0
- package/server/sessionWallHydrate.test.ts +46 -0
- package/server/sessionWallHydrate.ts +88 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Suspense, useMemo, useState, type ReactNode } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { useSearchParams } from "next/navigation";
|
|
6
|
+
import { BookOpen, Search, X } from "lucide-react";
|
|
7
|
+
import { useKronosysPayload } from "@/components/KronosysPayloadProvider";
|
|
8
|
+
import { postKronosysAction } from "@/lib/kronosysApi";
|
|
9
|
+
import { AppVersionStamp } from "@/components/dashboard/AppVersionStamp";
|
|
10
|
+
import { appShellHeaderClassName, appShellHeaderToolRowClassName } from "@/lib/appShellHeaderClasses";
|
|
11
|
+
import { AppShellRouteNav } from "@/components/dashboard/AppShellRouteNav";
|
|
12
|
+
import { UserGuideBodyText } from "@/components/dashboard/UserGuideBodyText";
|
|
13
|
+
import { ThemeToggle } from "@/components/dashboard/ThemeToggle";
|
|
14
|
+
import { PageRefreshButton } from "@/components/dashboard/PageRefreshButton";
|
|
15
|
+
import { LanguageMenu } from "@/components/dashboard/LanguageMenu";
|
|
16
|
+
import { ScrollToTopFab } from "@/components/dashboard/ScrollToTopFab";
|
|
17
|
+
import { dashboardStrings, type Lang } from "@/lib/dashboardCopy";
|
|
18
|
+
import { withDashboardSessionParam } from "@/lib/dashboardSessionNav";
|
|
19
|
+
import { reportingNav, reportingStrings } from "@/lib/reportingStrings";
|
|
20
|
+
import { sectionSearchHaystack, userGuideBundle } from "@/lib/userGuideCopy";
|
|
21
|
+
|
|
22
|
+
type LiveShape = { language?: string };
|
|
23
|
+
|
|
24
|
+
function filterSections(query: string, sections: ReturnType<typeof userGuideBundle>["sections"]): number[] {
|
|
25
|
+
const q = query.trim().toLowerCase();
|
|
26
|
+
if (q.length === 0) {
|
|
27
|
+
return sections.map((_, i) => i);
|
|
28
|
+
}
|
|
29
|
+
return sections
|
|
30
|
+
.map((s, i) => ({ i, h: sectionSearchHaystack(s) }))
|
|
31
|
+
.filter((x) => x.h.includes(q))
|
|
32
|
+
.map((x) => x.i);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function GuideContent() {
|
|
36
|
+
const searchParams = useSearchParams();
|
|
37
|
+
const dashboardSessionNavId = searchParams.get("session");
|
|
38
|
+
const { payload, refresh } = useKronosysPayload();
|
|
39
|
+
const [q, setQ] = useState("");
|
|
40
|
+
|
|
41
|
+
const live = payload?.current as LiveShape | undefined;
|
|
42
|
+
const lang: Lang = live?.language === "fr" ? "fr" : "en";
|
|
43
|
+
const t = reportingStrings(lang);
|
|
44
|
+
const nav = useMemo(() => reportingNav(lang), [lang]);
|
|
45
|
+
const dt = dashboardStrings(lang);
|
|
46
|
+
const bundle = useMemo(() => userGuideBundle(lang), [lang]);
|
|
47
|
+
const visible = useMemo(() => filterSections(q, bundle.sections), [q, bundle.sections]);
|
|
48
|
+
|
|
49
|
+
const postLang = async (next: Lang) => {
|
|
50
|
+
await postKronosysAction({ type: "setLanguage", lang: next });
|
|
51
|
+
await refresh();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const bulletList = (lines: string[], idPrefix: string, listType: "ul" | "ol" = "ul"): ReactNode => {
|
|
55
|
+
const listClass =
|
|
56
|
+
listType === "ol"
|
|
57
|
+
? "mt-2 list-inside list-decimal space-y-1.5 pl-0.5 text-sm leading-relaxed text-zinc-600 dark:text-zinc-300"
|
|
58
|
+
: "mt-2 list-inside list-disc space-y-1.5 pl-0.5 text-sm leading-relaxed text-zinc-600 dark:text-zinc-300";
|
|
59
|
+
const Tag = listType === "ol" ? "ol" : "ul";
|
|
60
|
+
return (
|
|
61
|
+
<Tag className={listClass}>
|
|
62
|
+
{lines.map((line, bIdx) => (
|
|
63
|
+
<li key={`${idPrefix}-b${bIdx}`}>
|
|
64
|
+
<UserGuideBodyText line={line} sessionId={dashboardSessionNavId} />
|
|
65
|
+
</li>
|
|
66
|
+
))}
|
|
67
|
+
</Tag>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="min-h-screen bg-zinc-100 text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100">
|
|
73
|
+
<header className={appShellHeaderClassName}>
|
|
74
|
+
<div className={appShellHeaderToolRowClassName}>
|
|
75
|
+
<div className="flex min-w-0 flex-col gap-1">
|
|
76
|
+
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
|
77
|
+
<Link
|
|
78
|
+
href={withDashboardSessionParam("/", dashboardSessionNavId)}
|
|
79
|
+
className="text-xl font-semibold tracking-tight text-zinc-900 hover:text-violet-700 dark:text-zinc-100 dark:hover:text-violet-300"
|
|
80
|
+
>
|
|
81
|
+
Kronosys
|
|
82
|
+
</Link>
|
|
83
|
+
<span className="text-zinc-400 dark:text-zinc-600" aria-hidden>
|
|
84
|
+
/
|
|
85
|
+
</span>
|
|
86
|
+
<span className="text-lg font-medium text-zinc-700 dark:text-zinc-300">{bundle.pageTitle}</span>
|
|
87
|
+
<span className="inline-flex items-center text-violet-500 dark:text-violet-400" aria-hidden>
|
|
88
|
+
<BookOpen className="size-5" strokeWidth={2} />
|
|
89
|
+
</span>
|
|
90
|
+
</div>
|
|
91
|
+
<p className="flex flex-wrap items-center gap-x-2 text-xs font-medium leading-snug text-zinc-500 dark:text-zinc-400">
|
|
92
|
+
<span>{dt.brandTagline}</span>
|
|
93
|
+
<span className="text-zinc-400/70 dark:text-zinc-600" aria-hidden>
|
|
94
|
+
·
|
|
95
|
+
</span>
|
|
96
|
+
<AppVersionStamp ariaLabelTemplate={dt.appVersionAriaLabel} />
|
|
97
|
+
</p>
|
|
98
|
+
</div>
|
|
99
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
100
|
+
<AppShellRouteNav
|
|
101
|
+
current="guide"
|
|
102
|
+
labels={nav}
|
|
103
|
+
navAriaLabel={dt.appShellRouteNavAria}
|
|
104
|
+
dashboardSessionId={dashboardSessionNavId}
|
|
105
|
+
/>
|
|
106
|
+
<ThemeToggle lang={lang} />
|
|
107
|
+
<PageRefreshButton
|
|
108
|
+
title={dt.pageRefreshTitle}
|
|
109
|
+
ariaLabel={dt.pageRefreshAriaLabel}
|
|
110
|
+
inlineMessages={{
|
|
111
|
+
loading: dt.pageRefreshProgressLabel,
|
|
112
|
+
success: dt.pageRefreshDoneToast,
|
|
113
|
+
error: dt.pageRefreshFailedToast,
|
|
114
|
+
}}
|
|
115
|
+
onRefresh={async () => {
|
|
116
|
+
return await refresh({ routerInvalidate: true });
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
<LanguageMenu
|
|
120
|
+
lang={lang}
|
|
121
|
+
labelEn="English"
|
|
122
|
+
labelFr="Français"
|
|
123
|
+
menuHeading={lang === "fr" ? "Langue" : "Language"}
|
|
124
|
+
triggerAriaLabel={lang === "fr" ? "Langue de l’interface" : "Interface language"}
|
|
125
|
+
onSelect={(next) => void postLang(next)}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</header>
|
|
130
|
+
|
|
131
|
+
<main
|
|
132
|
+
className="mx-auto w-full max-w-[100rem] px-4 pb-16 pt-5 sm:px-8 sm:pt-6 lg:px-12"
|
|
133
|
+
data-user-guide-scrolled=""
|
|
134
|
+
>
|
|
135
|
+
<p className="max-w-3xl text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
|
|
136
|
+
<UserGuideBodyText line={bundle.pageSubtitle} sessionId={dashboardSessionNavId} />
|
|
137
|
+
</p>
|
|
138
|
+
<p className="mt-2 text-xs text-zinc-500 dark:text-zinc-500">{bundle.lastUpdated}</p>
|
|
139
|
+
|
|
140
|
+
<form
|
|
141
|
+
onSubmit={(e) => e.preventDefault()}
|
|
142
|
+
className="sticky top-0 z-20 mt-6 -mx-4 border-b border-zinc-200/80 bg-zinc-100/95 px-4 py-3 backdrop-blur-sm sm:-mx-8 sm:px-8 dark:border-zinc-700/80 dark:bg-zinc-900/95"
|
|
143
|
+
role="search"
|
|
144
|
+
>
|
|
145
|
+
<label className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
|
146
|
+
<span className="sr-only">{bundle.searchAriaLabel}</span>
|
|
147
|
+
<div className="relative min-w-0 flex-1">
|
|
148
|
+
<Search
|
|
149
|
+
className="pointer-events-none absolute top-1/2 left-3 z-[1] size-4 -translate-y-1/2 text-violet-600/80 dark:text-violet-300/80"
|
|
150
|
+
aria-hidden
|
|
151
|
+
strokeWidth={2}
|
|
152
|
+
/>
|
|
153
|
+
<input
|
|
154
|
+
type="search"
|
|
155
|
+
className="w-full rounded-lg border border-zinc-300 bg-white py-2.5 pr-9 pl-10 text-sm text-zinc-900 shadow-sm transition placeholder:text-zinc-400 focus:border-violet-500/70 focus:ring-2 focus:ring-violet-500/25 focus:outline-none dark:border-zinc-600 dark:bg-zinc-800/90 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-violet-500/60"
|
|
156
|
+
placeholder={bundle.searchPlaceholder}
|
|
157
|
+
value={q}
|
|
158
|
+
onChange={(e) => setQ(e.target.value)}
|
|
159
|
+
autoComplete="off"
|
|
160
|
+
id="ug-search"
|
|
161
|
+
aria-label={bundle.searchAriaLabel}
|
|
162
|
+
/>
|
|
163
|
+
{q ? (
|
|
164
|
+
<button
|
|
165
|
+
type="button"
|
|
166
|
+
onClick={() => setQ("")}
|
|
167
|
+
className="absolute top-1/2 right-2 flex size-7 -translate-y-1/2 items-center justify-center rounded-md border border-zinc-300/80 bg-white text-zinc-600 transition hover:border-violet-500/50 hover:text-violet-800 dark:border-zinc-600 dark:bg-zinc-800/90 dark:text-zinc-300 dark:hover:border-violet-500/40 dark:hover:text-violet-100"
|
|
168
|
+
title={t.resetAllFilters}
|
|
169
|
+
aria-label={t.resetAllFilters}
|
|
170
|
+
>
|
|
171
|
+
<X className="size-4" strokeWidth={2} />
|
|
172
|
+
</button>
|
|
173
|
+
) : null}
|
|
174
|
+
</div>
|
|
175
|
+
<p className="shrink-0 text-xs text-zinc-500 dark:text-zinc-400">
|
|
176
|
+
{q.trim() ? `${visible.length} / ${bundle.sections.length}` : "—"}
|
|
177
|
+
</p>
|
|
178
|
+
</label>
|
|
179
|
+
</form>
|
|
180
|
+
|
|
181
|
+
{visible.length === 0 && q.trim() ? (
|
|
182
|
+
<output className="mt-8 block text-sm text-zinc-500 dark:text-zinc-400" aria-live="polite">
|
|
183
|
+
{bundle.searchNoResults}
|
|
184
|
+
</output>
|
|
185
|
+
) : null}
|
|
186
|
+
|
|
187
|
+
<div className="mt-6 flex flex-col gap-10 lg:flex-row lg:items-start lg:gap-12">
|
|
188
|
+
{visible.length > 0 && (
|
|
189
|
+
<nav
|
|
190
|
+
className="w-full max-w-sm shrink-0 scroll-mt-6 rounded-xl border border-zinc-200 bg-white/90 p-4 text-sm text-zinc-800 shadow-sm dark:border-zinc-600/80 dark:bg-zinc-800/50 dark:text-zinc-200 lg:sticky lg:top-36"
|
|
191
|
+
aria-label={bundle.tocNavAria}
|
|
192
|
+
>
|
|
193
|
+
<p className="text-[0.65rem] font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
|
|
194
|
+
{bundle.tocHeading}
|
|
195
|
+
</p>
|
|
196
|
+
<ul className="mt-2 space-y-0.5">
|
|
197
|
+
{visible.map((idx) => {
|
|
198
|
+
const s = bundle.sections[idx];
|
|
199
|
+
return (
|
|
200
|
+
<li key={s.id}>
|
|
201
|
+
<a
|
|
202
|
+
href={`#${s.id}`}
|
|
203
|
+
onClick={() => {
|
|
204
|
+
if (q.trim()) {
|
|
205
|
+
setQ("");
|
|
206
|
+
}
|
|
207
|
+
}}
|
|
208
|
+
className="block rounded-md border border-transparent py-1.5 pl-0 pr-0 text-violet-800 no-underline transition hover:border-violet-300/80 hover:bg-violet-100/30 hover:underline dark:text-violet-200/95 dark:hover:border-violet-600/40 dark:hover:bg-violet-950/20"
|
|
209
|
+
>
|
|
210
|
+
{s.title}
|
|
211
|
+
</a>
|
|
212
|
+
</li>
|
|
213
|
+
);
|
|
214
|
+
})}
|
|
215
|
+
</ul>
|
|
216
|
+
</nav>
|
|
217
|
+
)}
|
|
218
|
+
|
|
219
|
+
<div className="min-w-0 flex-1 space-y-10">
|
|
220
|
+
{visible.map((idx) => {
|
|
221
|
+
const s = bundle.sections[idx]!;
|
|
222
|
+
return (
|
|
223
|
+
<article
|
|
224
|
+
key={s.id}
|
|
225
|
+
id={s.id}
|
|
226
|
+
className="scroll-mt-40 rounded-xl border border-zinc-200/90 bg-white/80 p-5 text-zinc-800 shadow-sm dark:border-zinc-600/50 dark:bg-zinc-800/30 dark:text-zinc-200 sm:p-6"
|
|
227
|
+
>
|
|
228
|
+
<h2 className="text-lg font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">
|
|
229
|
+
{s.title}
|
|
230
|
+
</h2>
|
|
231
|
+
<div className="mt-3 space-y-3 text-sm leading-relaxed text-zinc-600 dark:text-zinc-300">
|
|
232
|
+
{s.paragraphs.map((p, pIdx) => (
|
|
233
|
+
<p key={`${s.id}-p${pIdx}`} className="[&:not(:first-of-type)]:pt-0.5">
|
|
234
|
+
<UserGuideBodyText line={p} sessionId={dashboardSessionNavId} />
|
|
235
|
+
</p>
|
|
236
|
+
))}
|
|
237
|
+
{s.steps && s.steps.length > 0 ? (
|
|
238
|
+
<div>
|
|
239
|
+
<h3 className="pt-1 text-sm font-semibold text-zinc-800 dark:text-zinc-200">
|
|
240
|
+
{s.stepsTitle ?? bundle.defaultStepBlockTitle}
|
|
241
|
+
</h3>
|
|
242
|
+
{bulletList(s.steps, `${s.id}-st`, "ol")}
|
|
243
|
+
</div>
|
|
244
|
+
) : null}
|
|
245
|
+
{s.options && s.options.length > 0 ? (
|
|
246
|
+
<div>
|
|
247
|
+
<h3 className="pt-1 text-sm font-semibold text-zinc-800 dark:text-zinc-200">
|
|
248
|
+
{s.optionsTitle ?? bundle.defaultOptionsBlockTitle}
|
|
249
|
+
</h3>
|
|
250
|
+
{bulletList(s.options, `${s.id}-op`, "ul")}
|
|
251
|
+
</div>
|
|
252
|
+
) : null}
|
|
253
|
+
</div>
|
|
254
|
+
{s.bullets && s.bullets.length > 0 ? bulletList(s.bullets, s.id) : null}
|
|
255
|
+
</article>
|
|
256
|
+
);
|
|
257
|
+
})}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
</main>
|
|
261
|
+
<ScrollToTopFab ariaLabel={t.scrollToTopAria} />
|
|
262
|
+
</div>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export default function GuidePage() {
|
|
267
|
+
return (
|
|
268
|
+
<Suspense
|
|
269
|
+
fallback={
|
|
270
|
+
<div className="min-h-screen bg-zinc-100 px-6 py-10 text-sm text-zinc-500 dark:bg-zinc-900">
|
|
271
|
+
Kronosys…
|
|
272
|
+
</div>
|
|
273
|
+
}
|
|
274
|
+
>
|
|
275
|
+
<GuideContent />
|
|
276
|
+
</Suspense>
|
|
277
|
+
);
|
|
278
|
+
}
|
package/app/icon.png
ADDED
|
Binary file
|
package/app/layout.tsx
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Metadata, Viewport } from "next";
|
|
2
|
+
import { Geist_Mono, Rubik } from "next/font/google";
|
|
3
|
+
import { PwaRegister } from "@/components/PwaRegister";
|
|
4
|
+
import { SiteLegalFooter } from "@/components/SiteLegalFooter";
|
|
5
|
+
import { ThemeProvider } from "@/components/ThemeProvider";
|
|
6
|
+
import { KronosysPackageVersionProvider } from "@/components/KronosysPackageVersionProvider";
|
|
7
|
+
import { DashboardToastProvider } from "@/components/dashboard/DashboardToastProvider";
|
|
8
|
+
import { KronosysPayloadProvider } from "@/components/KronosysPayloadProvider";
|
|
9
|
+
import { AppShellLiveSessionDrawer } from "@/components/dashboard/AppShellLiveSessionDrawer";
|
|
10
|
+
import { readKronosysPackageVersion } from "@/lib/readKronosysPackageVersion";
|
|
11
|
+
import { getThemeBootstrapScript } from "@/lib/theme";
|
|
12
|
+
import "./globals.css";
|
|
13
|
+
|
|
14
|
+
const rubik = Rubik({
|
|
15
|
+
variable: "--font-rubik",
|
|
16
|
+
subsets: ["latin", "latin-ext"],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const geistMono = Geist_Mono({
|
|
20
|
+
variable: "--font-geist-mono",
|
|
21
|
+
subsets: ["latin"],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const metadata: Metadata = {
|
|
25
|
+
title: "Kronosys — Tableau de bord",
|
|
26
|
+
description:
|
|
27
|
+
"Kronosys : sessions, tâches, KronoFocus — application web locale (Next.js + SQLite).",
|
|
28
|
+
applicationName: "Kronosys Dashboard",
|
|
29
|
+
icons: {
|
|
30
|
+
icon: [{ url: "/icon-192.png", sizes: "192x192", type: "image/png" }, { url: "/icon-512.png", sizes: "512x512", type: "image/png" }],
|
|
31
|
+
apple: [{ url: "/apple-icon.png", sizes: "180x180", type: "image/png" }],
|
|
32
|
+
},
|
|
33
|
+
appleWebApp: {
|
|
34
|
+
capable: true,
|
|
35
|
+
title: "Kronosys",
|
|
36
|
+
statusBarStyle: "black-translucent",
|
|
37
|
+
},
|
|
38
|
+
formatDetection: {
|
|
39
|
+
telephone: false,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const viewport: Viewport = {
|
|
44
|
+
themeColor: "#18181b",
|
|
45
|
+
colorScheme: "dark light",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default function RootLayout({
|
|
49
|
+
children,
|
|
50
|
+
}: Readonly<{
|
|
51
|
+
children: React.ReactNode;
|
|
52
|
+
}>) {
|
|
53
|
+
const kronosysPackageVersion = readKronosysPackageVersion();
|
|
54
|
+
return (
|
|
55
|
+
<html
|
|
56
|
+
lang="fr"
|
|
57
|
+
className={`${rubik.variable} ${geistMono.variable} h-full scroll-smooth antialiased`}
|
|
58
|
+
suppressHydrationWarning
|
|
59
|
+
>
|
|
60
|
+
<body className="flex min-h-full flex-col font-sans">
|
|
61
|
+
<script dangerouslySetInnerHTML={{ __html: getThemeBootstrapScript() }} />
|
|
62
|
+
<ThemeProvider>
|
|
63
|
+
<KronosysPackageVersionProvider version={kronosysPackageVersion}>
|
|
64
|
+
<DashboardToastProvider>
|
|
65
|
+
<KronosysPayloadProvider>
|
|
66
|
+
<PwaRegister />
|
|
67
|
+
<div className="flex min-h-full flex-1 flex-col">{children}</div>
|
|
68
|
+
<AppShellLiveSessionDrawer />
|
|
69
|
+
<SiteLegalFooter />
|
|
70
|
+
</KronosysPayloadProvider>
|
|
71
|
+
</DashboardToastProvider>
|
|
72
|
+
</KronosysPackageVersionProvider>
|
|
73
|
+
</ThemeProvider>
|
|
74
|
+
</body>
|
|
75
|
+
</html>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
|
|
3
|
+
export const metadata: Metadata = {
|
|
4
|
+
title: "Licences — Kronosys",
|
|
5
|
+
description:
|
|
6
|
+
"Licence MIT de Kronosys, logiciels tiers du tableau de bord (Next.js, React, etc.) et mentions de polices (OFL).",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function LicensesLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
10
|
+
return children;
|
|
11
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Suspense, useMemo } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
6
|
+
import { AppVersionStamp } from "@/components/dashboard/AppVersionStamp";
|
|
7
|
+
import { ScrollToTopFab } from "@/components/dashboard/ScrollToTopFab";
|
|
8
|
+
import { ThemeToggle } from "@/components/dashboard/ThemeToggle";
|
|
9
|
+
import { PageRefreshButton } from "@/components/dashboard/PageRefreshButton";
|
|
10
|
+
import { AppShellRouteNav } from "@/components/dashboard/AppShellRouteNav";
|
|
11
|
+
import { dashboardStrings, type Lang } from "@/lib/dashboardCopy";
|
|
12
|
+
import { appShellHeaderClassName, appShellHeaderToolRowClassName } from "@/lib/appShellHeaderClasses";
|
|
13
|
+
import { reportingNav } from "@/lib/reportingStrings";
|
|
14
|
+
import {
|
|
15
|
+
DASHBOARD_DEV_THIRD_PARTY,
|
|
16
|
+
DASHBOARD_THIRD_PARTY,
|
|
17
|
+
KRONOSYS_MIT_LICENSE_BODY,
|
|
18
|
+
licensesCopy,
|
|
19
|
+
type LicensesLang,
|
|
20
|
+
} from "@/lib/licensesCopy";
|
|
21
|
+
import { withDashboardSessionParam } from "@/lib/dashboardSessionNav";
|
|
22
|
+
|
|
23
|
+
function LicensesBody() {
|
|
24
|
+
const router = useRouter();
|
|
25
|
+
const searchParams = useSearchParams();
|
|
26
|
+
const dashboardSessionNavId = searchParams.get("session");
|
|
27
|
+
const lang: LicensesLang = searchParams.get("lang") === "en" ? "en" : "fr";
|
|
28
|
+
const c = useMemo(() => licensesCopy(lang), [lang]);
|
|
29
|
+
const nav = reportingNav(lang as Lang);
|
|
30
|
+
const dt = dashboardStrings(lang as Lang);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="min-h-screen bg-zinc-100 text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100">
|
|
34
|
+
<header className={appShellHeaderClassName}>
|
|
35
|
+
<div className={appShellHeaderToolRowClassName}>
|
|
36
|
+
<div className="flex min-w-0 flex-col gap-1">
|
|
37
|
+
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
|
38
|
+
<Link
|
|
39
|
+
href={withDashboardSessionParam("/", dashboardSessionNavId)}
|
|
40
|
+
className="text-xl font-semibold tracking-tight text-zinc-900 hover:text-violet-700 dark:text-zinc-100 dark:hover:text-violet-300"
|
|
41
|
+
>
|
|
42
|
+
Kronosys
|
|
43
|
+
</Link>
|
|
44
|
+
<span className="text-zinc-400 dark:text-zinc-600">/</span>
|
|
45
|
+
<span className="text-lg font-medium text-zinc-700 dark:text-zinc-300">{c.title}</span>
|
|
46
|
+
</div>
|
|
47
|
+
<p className="flex flex-wrap items-center gap-x-2 text-xs font-medium leading-snug text-zinc-500 dark:text-zinc-400">
|
|
48
|
+
<span>{dt.brandTagline}</span>
|
|
49
|
+
<span className="text-zinc-400/70 dark:text-zinc-600" aria-hidden>
|
|
50
|
+
·
|
|
51
|
+
</span>
|
|
52
|
+
<AppVersionStamp ariaLabelTemplate={dt.appVersionAriaLabel} />
|
|
53
|
+
</p>
|
|
54
|
+
</div>
|
|
55
|
+
<div className="flex flex-wrap items-center gap-1.5 text-sm">
|
|
56
|
+
<AppShellRouteNav
|
|
57
|
+
current="licenses"
|
|
58
|
+
labels={{ ...nav, licenses: c.title }}
|
|
59
|
+
navAriaLabel={dt.appShellRouteNavAria}
|
|
60
|
+
dashboardSessionId={dashboardSessionNavId}
|
|
61
|
+
/>
|
|
62
|
+
<ThemeToggle lang={lang} />
|
|
63
|
+
<PageRefreshButton
|
|
64
|
+
title={dt.pageRefreshTitle}
|
|
65
|
+
ariaLabel={dt.pageRefreshAriaLabel}
|
|
66
|
+
inlineMessages={{
|
|
67
|
+
loading: dt.pageRefreshProgressLabel,
|
|
68
|
+
success: dt.pageRefreshDoneToast,
|
|
69
|
+
error: dt.pageRefreshFailedToast,
|
|
70
|
+
}}
|
|
71
|
+
onRefresh={() => {
|
|
72
|
+
router.refresh();
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
<span className="hidden h-4 w-px bg-zinc-300 dark:bg-zinc-700 sm:inline-block" aria-hidden />
|
|
76
|
+
<Link
|
|
77
|
+
href={withDashboardSessionParam("/licenses", dashboardSessionNavId)}
|
|
78
|
+
className={`rounded-md px-3 py-1.5 ${lang === "fr" ? "border border-violet-600/60 bg-violet-950/40 text-violet-100" : "border border-zinc-700 bg-zinc-900 text-zinc-200 hover:border-zinc-500"}`}
|
|
79
|
+
hrefLang="fr"
|
|
80
|
+
>
|
|
81
|
+
Français
|
|
82
|
+
</Link>
|
|
83
|
+
<Link
|
|
84
|
+
href={withDashboardSessionParam("/licenses?lang=en", dashboardSessionNavId)}
|
|
85
|
+
className={`rounded-md px-3 py-1.5 ${lang === "en" ? "border border-violet-600/60 bg-violet-950/40 text-violet-100" : "border border-zinc-700 bg-zinc-900 text-zinc-200 hover:border-zinc-500"}`}
|
|
86
|
+
hrefLang="en"
|
|
87
|
+
>
|
|
88
|
+
English
|
|
89
|
+
</Link>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</header>
|
|
93
|
+
|
|
94
|
+
<main className="mx-auto max-w-3xl px-5 py-8 pb-16 sm:px-8 lg:px-10">
|
|
95
|
+
<p className="text-sm leading-relaxed text-zinc-400">{c.intro}</p>
|
|
96
|
+
|
|
97
|
+
<section className="mt-10" aria-labelledby="lic-kronosys-product">
|
|
98
|
+
<h2 id="lic-kronosys-product" className="text-base font-semibold text-zinc-200">
|
|
99
|
+
{c.kronosysSectionTitle}
|
|
100
|
+
</h2>
|
|
101
|
+
<p className="mt-2 text-sm leading-relaxed text-zinc-400">{c.kronosysProduct}</p>
|
|
102
|
+
<p className="mt-2 text-sm font-medium text-zinc-300">{c.copyrightLine}</p>
|
|
103
|
+
|
|
104
|
+
<h3 className="mt-6 text-sm font-semibold uppercase tracking-wide text-zinc-500">{c.mitHeading}</h3>
|
|
105
|
+
<pre className="mt-3 overflow-x-auto rounded-lg border border-zinc-800 bg-zinc-900/60 p-4 text-xs leading-relaxed whitespace-pre-wrap text-zinc-300">
|
|
106
|
+
{KRONOSYS_MIT_LICENSE_BODY}
|
|
107
|
+
</pre>
|
|
108
|
+
</section>
|
|
109
|
+
|
|
110
|
+
<section className="mt-10" aria-labelledby="lic-third">
|
|
111
|
+
<h2 id="lic-third" className="text-base font-semibold text-zinc-200">
|
|
112
|
+
{c.thirdPartySectionTitle}
|
|
113
|
+
</h2>
|
|
114
|
+
<div className="mt-4 overflow-x-auto rounded-lg border border-zinc-800">
|
|
115
|
+
<table className="w-full min-w-[20rem] text-left text-sm">
|
|
116
|
+
<thead className="border-b border-zinc-800 bg-zinc-900/50 text-xs uppercase text-zinc-500">
|
|
117
|
+
<tr>
|
|
118
|
+
<th className="px-3 py-2 font-medium">{c.thirdPartyColName}</th>
|
|
119
|
+
<th className="px-3 py-2 font-medium">{c.thirdPartyColLicense}</th>
|
|
120
|
+
<th className="px-3 py-2 font-medium">{c.thirdPartyColLink}</th>
|
|
121
|
+
</tr>
|
|
122
|
+
</thead>
|
|
123
|
+
<tbody>
|
|
124
|
+
{DASHBOARD_THIRD_PARTY.map((row) => (
|
|
125
|
+
<tr key={row.name} className="border-b border-zinc-800/80 last:border-0">
|
|
126
|
+
<td className="px-3 py-2.5 text-zinc-200">{row.name}</td>
|
|
127
|
+
<td className="px-3 py-2.5 font-mono text-xs text-zinc-400">{row.license}</td>
|
|
128
|
+
<td className="px-3 py-2.5">
|
|
129
|
+
<a
|
|
130
|
+
href={row.url}
|
|
131
|
+
target="_blank"
|
|
132
|
+
rel="noopener noreferrer"
|
|
133
|
+
className="text-sky-400/90 underline-offset-2 hover:underline"
|
|
134
|
+
>
|
|
135
|
+
{row.url.replace(/^https:\/\//, "")}
|
|
136
|
+
</a>
|
|
137
|
+
</td>
|
|
138
|
+
</tr>
|
|
139
|
+
))}
|
|
140
|
+
</tbody>
|
|
141
|
+
</table>
|
|
142
|
+
</div>
|
|
143
|
+
</section>
|
|
144
|
+
|
|
145
|
+
<section className="mt-10" aria-labelledby="lic-dev">
|
|
146
|
+
<h2 id="lic-dev" className="text-base font-semibold text-zinc-200">
|
|
147
|
+
{c.devThirdPartySectionTitle}
|
|
148
|
+
</h2>
|
|
149
|
+
<div className="mt-4 overflow-x-auto rounded-lg border border-zinc-800">
|
|
150
|
+
<table className="w-full min-w-[20rem] text-left text-sm">
|
|
151
|
+
<thead className="border-b border-zinc-800 bg-zinc-900/50 text-xs uppercase text-zinc-500">
|
|
152
|
+
<tr>
|
|
153
|
+
<th className="px-3 py-2 font-medium">{c.thirdPartyColName}</th>
|
|
154
|
+
<th className="px-3 py-2 font-medium">{c.thirdPartyColLicense}</th>
|
|
155
|
+
<th className="px-3 py-2 font-medium">{c.thirdPartyColLink}</th>
|
|
156
|
+
</tr>
|
|
157
|
+
</thead>
|
|
158
|
+
<tbody>
|
|
159
|
+
{DASHBOARD_DEV_THIRD_PARTY.map((row) => (
|
|
160
|
+
<tr key={row.name} className="border-b border-zinc-800/80 last:border-0">
|
|
161
|
+
<td className="px-3 py-2.5 text-zinc-200">{row.name}</td>
|
|
162
|
+
<td className="px-3 py-2.5 font-mono text-xs text-zinc-400">{row.license}</td>
|
|
163
|
+
<td className="px-3 py-2.5">
|
|
164
|
+
<a
|
|
165
|
+
href={row.url}
|
|
166
|
+
target="_blank"
|
|
167
|
+
rel="noopener noreferrer"
|
|
168
|
+
className="text-sky-400/90 underline-offset-2 hover:underline"
|
|
169
|
+
>
|
|
170
|
+
{row.url.replace(/^https:\/\//, "")}
|
|
171
|
+
</a>
|
|
172
|
+
</td>
|
|
173
|
+
</tr>
|
|
174
|
+
))}
|
|
175
|
+
</tbody>
|
|
176
|
+
</table>
|
|
177
|
+
</div>
|
|
178
|
+
</section>
|
|
179
|
+
|
|
180
|
+
<section className="mt-10" aria-labelledby="lic-fonts">
|
|
181
|
+
<h2 id="lic-fonts" className="text-base font-semibold text-zinc-200">
|
|
182
|
+
{c.fontsSectionTitle}
|
|
183
|
+
</h2>
|
|
184
|
+
<p className="mt-2 text-sm leading-relaxed text-zinc-400">{c.fontsBody}</p>
|
|
185
|
+
<ul className="mt-3 list-inside list-disc text-sm text-zinc-400">
|
|
186
|
+
<li>
|
|
187
|
+
<a
|
|
188
|
+
href="https://scripts.sil.org/OFL"
|
|
189
|
+
target="_blank"
|
|
190
|
+
rel="noopener noreferrer"
|
|
191
|
+
className="text-sky-400/90 underline-offset-2 hover:underline"
|
|
192
|
+
>
|
|
193
|
+
SIL Open Font License 1.1
|
|
194
|
+
</a>
|
|
195
|
+
</li>
|
|
196
|
+
<li>
|
|
197
|
+
<a
|
|
198
|
+
href="https://fonts.google.com/specimen/Rubik"
|
|
199
|
+
target="_blank"
|
|
200
|
+
rel="noopener noreferrer"
|
|
201
|
+
className="text-sky-400/90 underline-offset-2 hover:underline"
|
|
202
|
+
>
|
|
203
|
+
Rubik (Google Fonts)
|
|
204
|
+
</a>
|
|
205
|
+
</li>
|
|
206
|
+
<li>
|
|
207
|
+
<a
|
|
208
|
+
href="https://vercel.com/font"
|
|
209
|
+
target="_blank"
|
|
210
|
+
rel="noopener noreferrer"
|
|
211
|
+
className="text-sky-400/90 underline-offset-2 hover:underline"
|
|
212
|
+
>
|
|
213
|
+
Geist (Vercel)
|
|
214
|
+
</a>
|
|
215
|
+
</li>
|
|
216
|
+
</ul>
|
|
217
|
+
</section>
|
|
218
|
+
|
|
219
|
+
<section className="mt-10" aria-labelledby="lic-ext">
|
|
220
|
+
<h2 id="lic-ext" className="text-base font-semibold text-zinc-200">
|
|
221
|
+
{c.extensionSectionTitle}
|
|
222
|
+
</h2>
|
|
223
|
+
<p className="mt-2 text-sm leading-relaxed text-zinc-400">{c.extensionNote}</p>
|
|
224
|
+
</section>
|
|
225
|
+
|
|
226
|
+
<p className="mt-8 text-xs text-zinc-600">{c.disclaimer}</p>
|
|
227
|
+
</main>
|
|
228
|
+
|
|
229
|
+
<ScrollToTopFab ariaLabel={lang === "fr" ? "Retour en haut de la page" : "Back to top"} />
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export default function LicensesPage() {
|
|
235
|
+
return (
|
|
236
|
+
<Suspense
|
|
237
|
+
fallback={
|
|
238
|
+
<div className="min-h-screen bg-zinc-100 px-6 py-10 text-sm text-zinc-500 dark:bg-zinc-900">
|
|
239
|
+
Kronosys…
|
|
240
|
+
</div>
|
|
241
|
+
}
|
|
242
|
+
>
|
|
243
|
+
<LicensesBody />
|
|
244
|
+
</Suspense>
|
|
245
|
+
);
|
|
246
|
+
}
|
package/app/manifest.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next";
|
|
2
|
+
|
|
3
|
+
/** PWA : installation « Application » dans Chrome, Edge, Brave, etc. */
|
|
4
|
+
export default function manifest(): MetadataRoute.Manifest {
|
|
5
|
+
return {
|
|
6
|
+
name: "Kronosys — Tableau de bord",
|
|
7
|
+
short_name: "Kronosys",
|
|
8
|
+
description:
|
|
9
|
+
"Tableau de bord local Kronosys — sessions, tâches et rapports via l’application web Next.js.",
|
|
10
|
+
start_url: "/",
|
|
11
|
+
scope: "/",
|
|
12
|
+
display: "standalone",
|
|
13
|
+
orientation: "any",
|
|
14
|
+
background_color: "#18181b",
|
|
15
|
+
theme_color: "#18181b",
|
|
16
|
+
categories: ["productivity", "developer"],
|
|
17
|
+
icons: [
|
|
18
|
+
{
|
|
19
|
+
src: "/icon-192.png",
|
|
20
|
+
sizes: "192x192",
|
|
21
|
+
type: "image/png",
|
|
22
|
+
purpose: "any",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
src: "/icon-512.png",
|
|
26
|
+
sizes: "512x512",
|
|
27
|
+
type: "image/png",
|
|
28
|
+
purpose: "any",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
}
|