super-time-tracker-ui 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +28 -0
  3. package/CLAUDE.md +1 -0
  4. package/README.md +36 -0
  5. package/app/api/backup/route.ts +39 -0
  6. package/app/api/entry/delete-bulk/route.ts +53 -0
  7. package/app/api/entry/move/route.ts +46 -0
  8. package/app/api/entry/move-bulk/route.ts +62 -0
  9. package/app/api/entry/route.ts +75 -0
  10. package/app/api/in/route.ts +38 -0
  11. package/app/api/note/route.ts +120 -0
  12. package/app/api/out/route.ts +31 -0
  13. package/app/api/sheet/route.ts +68 -0
  14. package/app/api/state/route.ts +16 -0
  15. package/app/api/tags/route.ts +75 -0
  16. package/app/color-palettes.css +260 -0
  17. package/app/favicon.ico +0 -0
  18. package/app/globals.css +140 -0
  19. package/app/layout.tsx +54 -0
  20. package/app/page.tsx +24 -0
  21. package/app/reporting/page.tsx +11 -0
  22. package/app/settings/data/page.tsx +9 -0
  23. package/app/settings/display/page.tsx +8 -0
  24. package/app/settings/page.tsx +12 -0
  25. package/app/settings/tags/page.tsx +13 -0
  26. package/bin/stt-ui.js +63 -0
  27. package/components/active-entry-panel.tsx +199 -0
  28. package/components/backup-restore-setting.tsx +168 -0
  29. package/components/check-in-form-collapsed-setting.tsx +44 -0
  30. package/components/check-in-form-collapsible.tsx +52 -0
  31. package/components/check-in-form.tsx +89 -0
  32. package/components/checkbox.tsx +75 -0
  33. package/components/checkout-button-group.tsx +73 -0
  34. package/components/chevron-icon.tsx +25 -0
  35. package/components/clear-tag-filters-on-sheet-change-setting.tsx +45 -0
  36. package/components/color-palette-setting.tsx +75 -0
  37. package/components/compact-lists-setting.tsx +42 -0
  38. package/components/confirm-before-checkout-setting.tsx +42 -0
  39. package/components/confirm-destructive-actions-setting.tsx +46 -0
  40. package/components/confirm-dialog-provider.tsx +71 -0
  41. package/components/confirm-dialog.tsx +90 -0
  42. package/components/data-settings-view.tsx +47 -0
  43. package/components/default-reporting-range-setting.tsx +56 -0
  44. package/components/default-reporting-sort-setting.tsx +45 -0
  45. package/components/default-sheet-session-setting.tsx +118 -0
  46. package/components/display-settings-view.tsx +75 -0
  47. package/components/duration-format-setting.tsx +40 -0
  48. package/components/entry-actions-menu.tsx +207 -0
  49. package/components/entry-edit-form.tsx +113 -0
  50. package/components/entry-list-bulk-bar.tsx +128 -0
  51. package/components/entry-list-sort-setting.tsx +41 -0
  52. package/components/entry-list.tsx +336 -0
  53. package/components/entry-notes-list.tsx +211 -0
  54. package/components/entry-tag-filter.tsx +99 -0
  55. package/components/format_datetime_hint.ts +8 -0
  56. package/components/format_time.ts +10 -0
  57. package/components/general-settings-view.tsx +40 -0
  58. package/components/hamburger-icon.tsx +21 -0
  59. package/components/note-edit-form.tsx +77 -0
  60. package/components/note-form.tsx +109 -0
  61. package/components/pencil-icon.tsx +21 -0
  62. package/components/reporting-date-range-picker.tsx +121 -0
  63. package/components/reporting-sort-controls.tsx +53 -0
  64. package/components/reporting-view.tsx +340 -0
  65. package/components/setting-radio-group.tsx +79 -0
  66. package/components/settings-nav.tsx +66 -0
  67. package/components/settings-page-layout.tsx +53 -0
  68. package/components/settings-saved-toast.tsx +57 -0
  69. package/components/sheet-actions-menu.tsx +108 -0
  70. package/components/sheet-sidebar.tsx +196 -0
  71. package/components/tag-autocomplete-input.tsx +183 -0
  72. package/components/tag-filter-mode-setting.tsx +47 -0
  73. package/components/tag-management-view.tsx +290 -0
  74. package/components/theme-mode-setting.tsx +44 -0
  75. package/components/theme-mode-system-listener.tsx +43 -0
  76. package/components/theme_switcher.tsx +38 -0
  77. package/components/time-format-setting.tsx +39 -0
  78. package/components/timer-in-title-setting.tsx +38 -0
  79. package/components/timer-show-seconds-setting.tsx +41 -0
  80. package/components/tracker-active-bar.tsx +76 -0
  81. package/components/tracker-app.tsx +338 -0
  82. package/components/tracker-breadcrumb.tsx +56 -0
  83. package/components/tracker-document-title.tsx +67 -0
  84. package/components/tracker-topbar.tsx +63 -0
  85. package/components/trash-icon.tsx +24 -0
  86. package/components/week-starts-on-setting.tsx +39 -0
  87. package/eslint.config.mjs +18 -0
  88. package/lib/add_note_to_entry.ts +65 -0
  89. package/lib/api_error_response.ts +10 -0
  90. package/lib/apply_accent_color.ts +12 -0
  91. package/lib/apply_color_palette.ts +12 -0
  92. package/lib/apply_compact_lists.ts +9 -0
  93. package/lib/apply_tag_autocomplete_selection.ts +26 -0
  94. package/lib/apply_theme.ts +8 -0
  95. package/lib/build_reporting_stats.ts +55 -0
  96. package/lib/build_resume_description.ts +15 -0
  97. package/lib/check_in_entry.ts +81 -0
  98. package/lib/check_out_entry.ts +75 -0
  99. package/lib/collect_known_tags.ts +22 -0
  100. package/lib/collect_tag_stats.ts +27 -0
  101. package/lib/collect_tags_from_entries.ts +35 -0
  102. package/lib/config.ts +9 -0
  103. package/lib/convert_json_db.ts +49 -0
  104. package/lib/delete_entries.ts +62 -0
  105. package/lib/delete_entry.ts +29 -0
  106. package/lib/delete_note_on_entry.ts +42 -0
  107. package/lib/delete_sheet.ts +30 -0
  108. package/lib/delete_tracker_action.ts +22 -0
  109. package/lib/edit_entry.ts +56 -0
  110. package/lib/edit_note_on_entry.ts +49 -0
  111. package/lib/ensure_dir_exists.ts +22 -0
  112. package/lib/entry_matches_tag_filter.ts +26 -0
  113. package/lib/fetch_tracker_state.ts +15 -0
  114. package/lib/filter_entries_by_tags.ts +20 -0
  115. package/lib/filter_known_tags.ts +20 -0
  116. package/lib/find_all_serialized_active_entries.ts +28 -0
  117. package/lib/find_serialized_active_entry.ts +12 -0
  118. package/lib/find_serialized_active_entry_for_sheet.ts +31 -0
  119. package/lib/find_sheet_with_active_entry.ts +16 -0
  120. package/lib/format_display_tag.ts +6 -0
  121. package/lib/format_duration.ts +45 -0
  122. package/lib/gen_db.ts +43 -0
  123. package/lib/get_active_panel_class_name.ts +20 -0
  124. package/lib/get_average_entry_ms.ts +13 -0
  125. package/lib/get_button_class_name.ts +24 -0
  126. package/lib/get_check_out_confirm_dialog.ts +19 -0
  127. package/lib/get_clipped_entry_duration_ms.ts +18 -0
  128. package/lib/get_compact_lists_snapshot.ts +15 -0
  129. package/lib/get_date_range_ms_from_inputs.ts +31 -0
  130. package/lib/get_delete_entries_confirm_dialog.ts +21 -0
  131. package/lib/get_delete_entry_confirm_dialog.ts +19 -0
  132. package/lib/get_delete_note_confirm_dialog.ts +21 -0
  133. package/lib/get_delete_sheet_confirm_dialog.ts +25 -0
  134. package/lib/get_entry_duration_ms.ts +14 -0
  135. package/lib/get_entry_row_key.ts +8 -0
  136. package/lib/get_initial_preferred_sheet_name.ts +34 -0
  137. package/lib/get_initial_reporting_range_inputs.ts +31 -0
  138. package/lib/get_input_class_name.ts +15 -0
  139. package/lib/get_merge_tags_confirm_dialog.ts +25 -0
  140. package/lib/get_period_range_ms.ts +43 -0
  141. package/lib/get_reporting_date_range_shortcut_inputs.ts +84 -0
  142. package/lib/get_reporting_period_totals.ts +39 -0
  143. package/lib/get_reporting_stats.ts +25 -0
  144. package/lib/get_restore_db_confirm_dialog.ts +14 -0
  145. package/lib/get_running_entry_key.ts +8 -0
  146. package/lib/get_serialized_entries_total_ms.ts +10 -0
  147. package/lib/get_sheet.ts +14 -0
  148. package/lib/get_sheet_report_stats.ts +22 -0
  149. package/lib/get_sheet_report_stats_for_range.ts +46 -0
  150. package/lib/get_sheet_tag_filter_snapshot.ts +22 -0
  151. package/lib/get_sheets_duration_in_range.ts +27 -0
  152. package/lib/get_tag_autocomplete_context.ts +32 -0
  153. package/lib/get_theme_snapshot.ts +16 -0
  154. package/lib/get_tracker_state.ts +67 -0
  155. package/lib/has_string_value.ts +6 -0
  156. package/lib/is_entry_in_day.ts +15 -0
  157. package/lib/is_idle_sheet_report.ts +8 -0
  158. package/lib/is_json_time_tracker_db.ts +14 -0
  159. package/lib/merge_tags_across_db.ts +79 -0
  160. package/lib/migrate_json_db.ts +56 -0
  161. package/lib/migrate_json_db_to_version_three.ts +51 -0
  162. package/lib/migrate_json_db_to_version_two.ts +50 -0
  163. package/lib/move_entries_to_sheet.ts +152 -0
  164. package/lib/move_entry_to_sheet.ts +82 -0
  165. package/lib/normalize_stored_tag.ts +16 -0
  166. package/lib/notify_settings_saved.ts +47 -0
  167. package/lib/parse_default_sheet_session_mode.ts +21 -0
  168. package/lib/parse_entry_from_input.ts +23 -0
  169. package/lib/parse_natural_language_date.ts +23 -0
  170. package/lib/parse_reporting_source_sheets.ts +22 -0
  171. package/lib/partition_sheet_report_stats.ts +30 -0
  172. package/lib/patch_tracker_action.ts +22 -0
  173. package/lib/persist_ui_preference.ts +18 -0
  174. package/lib/post_tracker_action.ts +22 -0
  175. package/lib/preferences/accent_color_preference.ts +21 -0
  176. package/lib/preferences/check_in_form_collapsed_preference.ts +20 -0
  177. package/lib/preferences/clear_tag_filters_on_sheet_change_preference.ts +20 -0
  178. package/lib/preferences/color_palette_preference.ts +21 -0
  179. package/lib/preferences/confirm_before_checkout_preference.ts +20 -0
  180. package/lib/preferences/confirm_destructive_actions_preference.ts +20 -0
  181. package/lib/preferences/default_reporting_range_preference.ts +21 -0
  182. package/lib/preferences/default_reporting_sort_preference.ts +24 -0
  183. package/lib/preferences/duration_format_preference.ts +19 -0
  184. package/lib/preferences/entry_list_sort_preference.ts +21 -0
  185. package/lib/preferences/tag_filter_mode_preference.ts +18 -0
  186. package/lib/preferences/theme_mode_preference.ts +18 -0
  187. package/lib/preferences/time_format_preference.ts +18 -0
  188. package/lib/preferences/timer_in_title_preference.ts +18 -0
  189. package/lib/preferences/timer_show_seconds_preference.ts +19 -0
  190. package/lib/preferences/week_starts_on_preference.ts +19 -0
  191. package/lib/prompt_check_out_at.ts +17 -0
  192. package/lib/prompt_entry_note.ts +14 -0
  193. package/lib/prune_sheet_tag_filter.ts +27 -0
  194. package/lib/read_db.ts +49 -0
  195. package/lib/read_db_backup_contents.ts +22 -0
  196. package/lib/read_document_compact_lists.ts +12 -0
  197. package/lib/read_document_theme.ts +14 -0
  198. package/lib/read_sheet_tag_filter.ts +26 -0
  199. package/lib/read_stored_active_sheet.ts +14 -0
  200. package/lib/read_stored_compact_lists.ts +24 -0
  201. package/lib/read_stored_default_sheet_fixed_name.ts +16 -0
  202. package/lib/read_stored_default_sheet_session_mode.ts +18 -0
  203. package/lib/read_stored_sheet_tag_filters.ts +28 -0
  204. package/lib/read_stored_theme.ts +18 -0
  205. package/lib/rename_sheet.ts +39 -0
  206. package/lib/rename_tag_across_db.ts +19 -0
  207. package/lib/resolve_active_sheet_name.ts +36 -0
  208. package/lib/resolve_session_preferred_sheet.ts +37 -0
  209. package/lib/resolve_theme.ts +18 -0
  210. package/lib/resolve_theme_mode_to_theme.ts +19 -0
  211. package/lib/restore_db_from_uploaded_json.ts +24 -0
  212. package/lib/serialize_entry.ts +27 -0
  213. package/lib/serialize_reporting_source_sheets.ts +19 -0
  214. package/lib/serialize_sheet_entries.ts +18 -0
  215. package/lib/set_accent_color.ts +12 -0
  216. package/lib/set_active_sheet.ts +18 -0
  217. package/lib/set_color_palette.ts +12 -0
  218. package/lib/set_compact_lists.ts +12 -0
  219. package/lib/set_default_sheet_fixed_name.ts +8 -0
  220. package/lib/set_default_sheet_session_mode.ts +11 -0
  221. package/lib/set_sheet_tag_filter.ts +13 -0
  222. package/lib/set_theme_mode.ts +19 -0
  223. package/lib/sheet_tag_filter_snapshots.ts +48 -0
  224. package/lib/sort_serialized_entries.ts +35 -0
  225. package/lib/sort_sheet_report_stats.ts +43 -0
  226. package/lib/subscribe_compact_lists.ts +25 -0
  227. package/lib/subscribe_sheet_tag_filters.ts +28 -0
  228. package/lib/subscribe_theme.ts +23 -0
  229. package/lib/sync_active_sheet_preference.ts +19 -0
  230. package/lib/tags_are_equal.ts +12 -0
  231. package/lib/theme_init_script.ts +11 -0
  232. package/lib/toggle_sheet_tag_filter.ts +28 -0
  233. package/lib/toggle_theme.ts +20 -0
  234. package/lib/types/confirm_dialog.ts +9 -0
  235. package/lib/types/data.ts +16 -0
  236. package/lib/types/generic_data.ts +25 -0
  237. package/lib/types/index.ts +2 -0
  238. package/lib/types/reporting.ts +59 -0
  239. package/lib/types/tag_management.ts +7 -0
  240. package/lib/types/theme.ts +3 -0
  241. package/lib/types/tracker_state.ts +39 -0
  242. package/lib/types/ui_preferences.ts +104 -0
  243. package/lib/types/ui_settings.ts +17 -0
  244. package/lib/ui_preference_store.ts +80 -0
  245. package/lib/ui_settings_init_script.ts +33 -0
  246. package/lib/use_check_in_form_collapsed.ts +18 -0
  247. package/lib/use_clear_tag_filters_on_sheet_change.ts +18 -0
  248. package/lib/use_confirm_before_checkout.ts +18 -0
  249. package/lib/use_confirm_destructive_actions.ts +18 -0
  250. package/lib/use_duration_format.ts +17 -0
  251. package/lib/use_entry_list_sort.ts +17 -0
  252. package/lib/use_tag_filter_mode.ts +17 -0
  253. package/lib/use_time_format.ts +17 -0
  254. package/lib/use_timer_in_title.ts +18 -0
  255. package/lib/use_timer_show_seconds.ts +18 -0
  256. package/lib/use_week_starts_on.ts +17 -0
  257. package/lib/validate_entry_times.ts +12 -0
  258. package/lib/week_starts_on_to_index.ts +8 -0
  259. package/lib/write_active_sheet_preference.ts +28 -0
  260. package/lib/write_db.ts +20 -0
  261. package/lib/write_sheet_tag_filter.ts +20 -0
  262. package/lib/write_stored_compact_lists.ts +15 -0
  263. package/lib/write_stored_default_sheet_fixed_name.ts +28 -0
  264. package/lib/write_stored_default_sheet_session_mode.ts +24 -0
  265. package/lib/write_stored_sheet_tag_filters.ts +18 -0
  266. package/lib/write_stored_theme.ts +12 -0
  267. package/next.config.ts +7 -0
  268. package/package.json +96 -0
  269. package/pnpm-workspace.yaml +7 -0
  270. package/postcss.config.mjs +7 -0
  271. package/public/file.svg +1 -0
  272. package/public/globe.svg +1 -0
  273. package/public/next.svg +1 -0
  274. package/public/vercel.svg +1 -0
  275. package/public/window.svg +1 -0
  276. package/tsconfig.json +34 -0
@@ -0,0 +1,25 @@
1
+ interface ChevronIconProps {
2
+ rotated?: boolean
3
+ className?: string
4
+ }
5
+
6
+ /**
7
+ * Renders a chevron for expand/collapse controls.
8
+ */
9
+ export function ChevronIcon({ rotated = false, className = '' }: ChevronIconProps) {
10
+ return (
11
+ <svg
12
+ className={`h-3 w-3 shrink-0 transition-transform duration-150 ${rotated ? 'rotate-90' : ''} ${className}`.trim()}
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ viewBox="0 0 24 24"
15
+ fill="none"
16
+ stroke="currentColor"
17
+ strokeWidth="2"
18
+ strokeLinecap="round"
19
+ strokeLinejoin="round"
20
+ aria-hidden="true"
21
+ >
22
+ <path d="m9 18 6-6-6-6" />
23
+ </svg>
24
+ )
25
+ }
@@ -0,0 +1,45 @@
1
+ 'use client'
2
+
3
+ import { useSyncExternalStore } from 'react'
4
+
5
+ import { clear_tag_filters_on_sheet_change_preference } from '@/lib/preferences/clear_tag_filters_on_sheet_change_preference'
6
+ import { persist_ui_preference } from '@/lib/persist_ui_preference'
7
+
8
+ const set_clear_tag_filters_on_sheet_change = (enabled: boolean): void => {
9
+ persist_ui_preference(
10
+ clear_tag_filters_on_sheet_change_preference,
11
+ enabled ? 'true' : 'false',
12
+ )
13
+ }
14
+
15
+ /**
16
+ * Setting: reset tag filters when switching sheets.
17
+ */
18
+ export function ClearTagFiltersOnSheetChangeSetting() {
19
+ const value = useSyncExternalStore(
20
+ clear_tag_filters_on_sheet_change_preference.subscribe,
21
+ clear_tag_filters_on_sheet_change_preference.get_snapshot,
22
+ clear_tag_filters_on_sheet_change_preference.get_server_snapshot,
23
+ )
24
+
25
+ return (
26
+ <label className="flex w-full cursor-pointer items-center gap-2.5">
27
+ <input
28
+ type="checkbox"
29
+ className="shrink-0"
30
+ checked={value === 'true'}
31
+ onChange={(event) =>
32
+ set_clear_tag_filters_on_sheet_change(event.target.checked)
33
+ }
34
+ />
35
+ <span className="flex flex-col gap-0.5">
36
+ <span className="text-[0.95rem] font-semibold">
37
+ Clear tag filters on sheet change
38
+ </span>
39
+ <span className="text-[0.8rem] leading-snug text-muted">
40
+ Remove tag filters when you switch to another sheet.
41
+ </span>
42
+ </span>
43
+ </label>
44
+ )
45
+ }
@@ -0,0 +1,75 @@
1
+ 'use client'
2
+
3
+ import { useSyncExternalStore } from 'react'
4
+
5
+ import { SettingRadioGroup } from '@/components/setting-radio-group'
6
+ import { notify_settings_saved } from '@/lib/notify_settings_saved'
7
+ import { color_palette_preference } from '@/lib/preferences/color_palette_preference'
8
+ import { set_color_palette } from '@/lib/set_color_palette'
9
+ import { type ColorPalette } from '@/lib/types/ui_preferences'
10
+
11
+ const options: {
12
+ value: ColorPalette
13
+ label: string
14
+ description: string
15
+ }[] = [
16
+ {
17
+ value: 'default',
18
+ label: 'Default',
19
+ description: 'Balanced blue-gray surfaces for light and dark.',
20
+ },
21
+ {
22
+ value: 'midnight',
23
+ label: 'Midnight',
24
+ description: 'Cooler, deeper backgrounds with crisp panels.',
25
+ },
26
+ {
27
+ value: 'warm',
28
+ label: 'Warm',
29
+ description: 'Stone and sepia tones, easy on the eyes.',
30
+ },
31
+ {
32
+ value: 'ocean',
33
+ label: 'Ocean',
34
+ description: 'Soft blue-tinted surfaces and shadows.',
35
+ },
36
+ {
37
+ value: 'forest',
38
+ label: 'Forest',
39
+ description: 'Muted green-gray backgrounds.',
40
+ },
41
+ {
42
+ value: 'contrast',
43
+ label: 'High contrast',
44
+ description: 'Stronger text and border contrast for readability.',
45
+ },
46
+ ]
47
+
48
+ /**
49
+ * Setting: base color palette for light and dark themes.
50
+ */
51
+ export function ColorPaletteSetting() {
52
+ const value = useSyncExternalStore(
53
+ color_palette_preference.subscribe,
54
+ color_palette_preference.get_snapshot,
55
+ color_palette_preference.get_server_snapshot,
56
+ )
57
+
58
+ return (
59
+ <SettingRadioGroup<ColorPalette>
60
+ name="color-palette"
61
+ legend="Color palette"
62
+ description="Backgrounds, surfaces, and highlight colors for light and dark mode."
63
+ value={value}
64
+ options={options}
65
+ on_change={(palette) => {
66
+ if (palette === value) {
67
+ return
68
+ }
69
+
70
+ set_color_palette(palette)
71
+ notify_settings_saved()
72
+ }}
73
+ />
74
+ )
75
+ }
@@ -0,0 +1,42 @@
1
+ 'use client'
2
+
3
+ import { useSyncExternalStore } from 'react'
4
+
5
+ import {
6
+ get_compact_lists_server_snapshot,
7
+ get_compact_lists_snapshot,
8
+ } from '@/lib/get_compact_lists_snapshot'
9
+ import { notify_settings_saved } from '@/lib/notify_settings_saved'
10
+ import { set_compact_lists } from '@/lib/set_compact_lists'
11
+ import { subscribe_compact_lists } from '@/lib/subscribe_compact_lists'
12
+
13
+ /**
14
+ * Toggles denser entry list rows without rounded corners.
15
+ */
16
+ export function CompactListsSetting() {
17
+ const compact_lists = useSyncExternalStore(
18
+ subscribe_compact_lists,
19
+ get_compact_lists_snapshot,
20
+ get_compact_lists_server_snapshot,
21
+ )
22
+
23
+ return (
24
+ <label className="flex w-full cursor-pointer items-center gap-2.5">
25
+ <input
26
+ type="checkbox"
27
+ className="shrink-0"
28
+ checked={compact_lists}
29
+ onChange={(event) => {
30
+ set_compact_lists(event.target.checked)
31
+ notify_settings_saved()
32
+ }}
33
+ />
34
+ <span className="flex flex-col gap-0.5">
35
+ <span className="text-[0.95rem] font-semibold">Compact lists</span>
36
+ <span className="text-[0.8rem] leading-snug text-muted">
37
+ Flatter, tighter rows in the sheet entry list
38
+ </span>
39
+ </span>
40
+ </label>
41
+ )
42
+ }
@@ -0,0 +1,42 @@
1
+ 'use client'
2
+
3
+ import { useSyncExternalStore } from 'react'
4
+
5
+ import { confirm_before_checkout_preference } from '@/lib/preferences/confirm_before_checkout_preference'
6
+ import { persist_ui_preference } from '@/lib/persist_ui_preference'
7
+
8
+ const set_confirm_before_checkout = (enabled: boolean): void => {
9
+ persist_ui_preference(
10
+ confirm_before_checkout_preference,
11
+ enabled ? 'true' : 'false',
12
+ )
13
+ }
14
+
15
+ /**
16
+ * Setting: ask for confirmation before checking out of an active timer.
17
+ */
18
+ export function ConfirmBeforeCheckoutSetting() {
19
+ const value = useSyncExternalStore(
20
+ confirm_before_checkout_preference.subscribe,
21
+ confirm_before_checkout_preference.get_snapshot,
22
+ confirm_before_checkout_preference.get_server_snapshot,
23
+ )
24
+ const is_enabled = value === 'true'
25
+
26
+ return (
27
+ <label className="flex w-full cursor-pointer items-center gap-2.5">
28
+ <input
29
+ type="checkbox"
30
+ className="shrink-0"
31
+ checked={is_enabled}
32
+ onChange={(event) => set_confirm_before_checkout(event.target.checked)}
33
+ />
34
+ <span className="flex flex-col gap-0.5">
35
+ <span className="text-[0.95rem] font-semibold">Confirm before checkout</span>
36
+ <span className="text-[0.8rem] leading-snug text-muted">
37
+ Show a confirmation dialog when you stop the active timer.
38
+ </span>
39
+ </span>
40
+ </label>
41
+ )
42
+ }
@@ -0,0 +1,46 @@
1
+ 'use client'
2
+
3
+ import { useSyncExternalStore } from 'react'
4
+
5
+ import { confirm_destructive_actions_preference } from '@/lib/preferences/confirm_destructive_actions_preference'
6
+ import { persist_ui_preference } from '@/lib/persist_ui_preference'
7
+
8
+ const set_confirm_destructive_actions = (enabled: boolean): void => {
9
+ persist_ui_preference(
10
+ confirm_destructive_actions_preference,
11
+ enabled ? 'true' : 'false',
12
+ )
13
+ }
14
+
15
+ /**
16
+ * Setting: ask for confirmation before deleting entries or sheets.
17
+ */
18
+ export function ConfirmDestructiveActionsSetting() {
19
+ const value = useSyncExternalStore(
20
+ confirm_destructive_actions_preference.subscribe,
21
+ confirm_destructive_actions_preference.get_snapshot,
22
+ confirm_destructive_actions_preference.get_server_snapshot,
23
+ )
24
+ const is_enabled = value === 'true'
25
+
26
+ return (
27
+ <label className="flex w-full cursor-pointer items-center gap-2.5">
28
+ <input
29
+ type="checkbox"
30
+ className="shrink-0"
31
+ checked={is_enabled}
32
+ onChange={(event) =>
33
+ set_confirm_destructive_actions(event.target.checked)
34
+ }
35
+ />
36
+ <span className="flex flex-col gap-0.5">
37
+ <span className="text-[0.95rem] font-semibold">
38
+ Confirm destructive actions
39
+ </span>
40
+ <span className="text-[0.8rem] leading-snug text-muted">
41
+ Ask before deleting entries or sheets. Turn off for fast mode.
42
+ </span>
43
+ </span>
44
+ </label>
45
+ )
46
+ }
@@ -0,0 +1,71 @@
1
+ 'use client'
2
+
3
+ import {
4
+ createContext,
5
+ type ReactNode,
6
+ useCallback,
7
+ useContext,
8
+ useState,
9
+ } from 'react'
10
+
11
+ import { ConfirmDialog } from '@/components/confirm-dialog'
12
+ import { type ConfirmDialogOptions } from '@/lib/types/confirm_dialog'
13
+
14
+ interface ConfirmDialogRequest {
15
+ options: ConfirmDialogOptions
16
+ resolve: (confirmed: boolean) => void
17
+ }
18
+
19
+ interface ConfirmDialogContextValue {
20
+ confirm: (options: ConfirmDialogOptions) => Promise<boolean>
21
+ }
22
+
23
+ const ConfirmDialogContext = createContext<ConfirmDialogContextValue | null>(null)
24
+
25
+ /**
26
+ * Returns the promise-based confirm dialog API from context.
27
+ */
28
+ export function use_confirm_dialog(): ConfirmDialogContextValue {
29
+ const context = useContext(ConfirmDialogContext)
30
+
31
+ if (context === null) {
32
+ throw new Error('use_confirm_dialog must be used within ConfirmDialogProvider')
33
+ }
34
+
35
+ return context
36
+ }
37
+
38
+ interface ConfirmDialogProviderProps {
39
+ children: ReactNode
40
+ }
41
+
42
+ /**
43
+ * Provides a themed confirm dialog for the application tree.
44
+ */
45
+ export function ConfirmDialogProvider({ children }: ConfirmDialogProviderProps) {
46
+ const [request, set_request] = useState<ConfirmDialogRequest | null>(null)
47
+
48
+ const confirm = useCallback((options: ConfirmDialogOptions): Promise<boolean> => {
49
+ return new Promise<boolean>((resolve) => {
50
+ set_request({ options, resolve })
51
+ })
52
+ }, [])
53
+
54
+ const close = (confirmed: boolean): void => {
55
+ request?.resolve(confirmed)
56
+ set_request(null)
57
+ }
58
+
59
+ return (
60
+ <ConfirmDialogContext.Provider value={{ confirm }}>
61
+ {children}
62
+ {request !== null ? (
63
+ <ConfirmDialog
64
+ options={request.options}
65
+ on_confirm={() => close(true)}
66
+ on_cancel={() => close(false)}
67
+ />
68
+ ) : null}
69
+ </ConfirmDialogContext.Provider>
70
+ )
71
+ }
@@ -0,0 +1,90 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useId, useRef } from 'react'
4
+
5
+ import { get_button_class_name } from '@/lib/get_button_class_name'
6
+ import { type ConfirmDialogOptions } from '@/lib/types/confirm_dialog'
7
+
8
+ interface ConfirmDialogProps {
9
+ options: ConfirmDialogOptions
10
+ on_confirm: () => void
11
+ on_cancel: () => void
12
+ }
13
+
14
+ /**
15
+ * Themed modal dialog for destructive or important confirmations.
16
+ */
17
+ export function ConfirmDialog({
18
+ options,
19
+ on_confirm,
20
+ on_cancel,
21
+ }: ConfirmDialogProps) {
22
+ const title_id = useId()
23
+ const confirm_ref = useRef<HTMLButtonElement>(null)
24
+ const {
25
+ cancelLabel = 'Cancel',
26
+ confirmLabel = 'Confirm',
27
+ message,
28
+ title,
29
+ variant = 'default',
30
+ } = options
31
+ const confirm_variant = variant === 'danger' ? 'danger' : 'primary'
32
+
33
+ useEffect(() => {
34
+ confirm_ref.current?.focus()
35
+
36
+ const handle_key_down = (event: KeyboardEvent): void => {
37
+ if (event.key === 'Escape') {
38
+ on_cancel()
39
+ }
40
+ }
41
+
42
+ document.addEventListener('keydown', handle_key_down)
43
+
44
+ return () => {
45
+ document.removeEventListener('keydown', handle_key_down)
46
+ }
47
+ }, [on_cancel])
48
+
49
+ return (
50
+ <div
51
+ className="fixed inset-0 z-100 flex items-center justify-center p-5"
52
+ role="presentation"
53
+ >
54
+ <button
55
+ type="button"
56
+ className="absolute inset-0 cursor-default border-0 bg-overlay p-0"
57
+ aria-label="Dismiss dialog"
58
+ onClick={on_cancel}
59
+ />
60
+ <div
61
+ role="dialog"
62
+ aria-modal="true"
63
+ aria-labelledby={title_id}
64
+ className="relative z-1 w-full max-w-md rounded-lg border border-panel-border bg-panel p-5 shadow-md"
65
+ >
66
+ <h2 id={title_id} className="m-0 text-[1.1rem] font-[650] tracking-tight">
67
+ {title}
68
+ </h2>
69
+ <p className="m-0 mt-2 text-[0.9rem] leading-relaxed text-muted">{message}</p>
70
+ <div className="mt-5 flex flex-wrap justify-end gap-2">
71
+ <button
72
+ type="button"
73
+ className={get_button_class_name('ghost')}
74
+ onClick={on_cancel}
75
+ >
76
+ {cancelLabel}
77
+ </button>
78
+ <button
79
+ ref={confirm_ref}
80
+ type="button"
81
+ className={get_button_class_name(confirm_variant)}
82
+ onClick={on_confirm}
83
+ >
84
+ {confirmLabel}
85
+ </button>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ )
90
+ }
@@ -0,0 +1,47 @@
1
+ import Link from 'next/link'
2
+
3
+ import { BackupRestoreSetting } from '@/components/backup-restore-setting'
4
+ import { SettingsPageLayout } from '@/components/settings-page-layout'
5
+
6
+ interface DataSettingsViewProps {
7
+ db_path: string
8
+ }
9
+
10
+ /**
11
+ * Settings page: backup, restore, and tag management entry point.
12
+ */
13
+ export function DataSettingsView({ db_path }: DataSettingsViewProps) {
14
+ return (
15
+ <SettingsPageLayout
16
+ breadcrumb={{
17
+ current: 'Data & backup',
18
+ parent: { label: 'Settings', href: '/settings' },
19
+ }}
20
+ title="Data & backup"
21
+ description="Database backups and bulk operations on stored entries."
22
+ >
23
+ <ul
24
+ className="m-0 flex w-full list-none flex-col gap-2 p-0"
25
+ aria-label="Data settings"
26
+ >
27
+ <li className="rounded-md border border-panel-border bg-panel p-3.5 shadow-sm">
28
+ <BackupRestoreSetting db_path={db_path} />
29
+ </li>
30
+ <li className="rounded-md border border-panel-border bg-panel p-3.5 shadow-sm">
31
+ <div className="flex flex-col gap-0.5">
32
+ <span className="text-[0.95rem] font-semibold">Tag management</span>
33
+ <span className="text-[0.8rem] leading-snug text-muted">
34
+ Rename or merge @tags used across all entries.
35
+ </span>
36
+ <Link
37
+ className="mt-2 self-start text-[0.85rem] font-semibold text-accent no-underline hover:underline"
38
+ href="/settings/tags"
39
+ >
40
+ Manage tags
41
+ </Link>
42
+ </div>
43
+ </li>
44
+ </ul>
45
+ </SettingsPageLayout>
46
+ )
47
+ }
@@ -0,0 +1,56 @@
1
+ 'use client'
2
+
3
+ import { useSyncExternalStore } from 'react'
4
+
5
+ import { SettingRadioGroup } from '@/components/setting-radio-group'
6
+ import { default_reporting_range_preference } from '@/lib/preferences/default_reporting_range_preference'
7
+ import { persist_ui_preference } from '@/lib/persist_ui_preference'
8
+ import { type DefaultReportingRange } from '@/lib/types/ui_preferences'
9
+
10
+ const options: {
11
+ value: DefaultReportingRange
12
+ label: string
13
+ description: string
14
+ }[] = [
15
+ {
16
+ value: 'none',
17
+ label: 'All time',
18
+ description: 'Open with no date filter applied.',
19
+ },
20
+ {
21
+ value: 'today',
22
+ label: 'Today',
23
+ description: 'Pre-select today’s date range.',
24
+ },
25
+ {
26
+ value: 'week',
27
+ label: 'This week',
28
+ description: 'Pre-select the current week (respects week starts on).',
29
+ },
30
+ ]
31
+
32
+ const set_default_reporting_range = (value: DefaultReportingRange): void => {
33
+ persist_ui_preference(default_reporting_range_preference, value)
34
+ }
35
+
36
+ /**
37
+ * Setting: initial date range when opening reporting.
38
+ */
39
+ export function DefaultReportingRangeSetting() {
40
+ const value = useSyncExternalStore(
41
+ default_reporting_range_preference.subscribe,
42
+ default_reporting_range_preference.get_snapshot,
43
+ default_reporting_range_preference.get_server_snapshot,
44
+ )
45
+
46
+ return (
47
+ <SettingRadioGroup<DefaultReportingRange>
48
+ name="default-reporting-range"
49
+ legend="Default reporting range"
50
+ description="Date filter applied when you open the Reporting page."
51
+ value={value}
52
+ options={options}
53
+ on_change={set_default_reporting_range}
54
+ />
55
+ )
56
+ }
@@ -0,0 +1,45 @@
1
+ 'use client'
2
+
3
+ import { useSyncExternalStore } from 'react'
4
+
5
+ import { SettingRadioGroup } from '@/components/setting-radio-group'
6
+ import { default_reporting_sort_preference } from '@/lib/preferences/default_reporting_sort_preference'
7
+ import { persist_ui_preference } from '@/lib/persist_ui_preference'
8
+ import { type DefaultReportingSort } from '@/lib/types/ui_preferences'
9
+
10
+ const options: {
11
+ value: DefaultReportingSort
12
+ label: string
13
+ description?: string
14
+ }[] = [
15
+ { value: 'duration', label: 'Duration' },
16
+ { value: 'name', label: 'Name' },
17
+ { value: 'entry_count', label: 'Entries' },
18
+ { value: 'active_first', label: 'Active first' },
19
+ ]
20
+
21
+ const set_default_reporting_sort = (value: DefaultReportingSort): void => {
22
+ persist_ui_preference(default_reporting_sort_preference, value)
23
+ }
24
+
25
+ /**
26
+ * Setting: default sort order for the reporting view.
27
+ */
28
+ export function DefaultReportingSortSetting() {
29
+ const value = useSyncExternalStore(
30
+ default_reporting_sort_preference.subscribe,
31
+ default_reporting_sort_preference.get_snapshot,
32
+ default_reporting_sort_preference.get_server_snapshot,
33
+ )
34
+
35
+ return (
36
+ <SettingRadioGroup<DefaultReportingSort>
37
+ name="default-reporting-sort"
38
+ legend="Default reporting sort"
39
+ description="The Reporting view opens with this sort selected."
40
+ value={value}
41
+ options={options}
42
+ on_change={set_default_reporting_sort}
43
+ />
44
+ )
45
+ }