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,10 @@
1
+ import { format } from 'date-fns'
2
+
3
+ import { type TimeFormat } from '@/lib/types/ui_preferences'
4
+
5
+ /**
6
+ * Formats an ISO timestamp for display in entry lists.
7
+ */
8
+ export function format_time(iso: string, time_format: TimeFormat = '12h'): string {
9
+ return format(new Date(iso), time_format === '24h' ? 'HH:mm' : 'h:mm a')
10
+ }
@@ -0,0 +1,40 @@
1
+ import { ClearTagFiltersOnSheetChangeSetting } from '@/components/clear-tag-filters-on-sheet-change-setting'
2
+ import { ConfirmBeforeCheckoutSetting } from '@/components/confirm-before-checkout-setting'
3
+ import { ConfirmDestructiveActionsSetting } from '@/components/confirm-destructive-actions-setting'
4
+ import { DefaultSheetSessionSetting } from '@/components/default-sheet-session-setting'
5
+ import { SettingsPageLayout } from '@/components/settings-page-layout'
6
+
7
+ interface GeneralSettingsViewProps {
8
+ sheet_names: string[]
9
+ }
10
+
11
+ /**
12
+ * Settings page: general tracker behavior.
13
+ */
14
+ export function GeneralSettingsView({ sheet_names }: GeneralSettingsViewProps) {
15
+ return (
16
+ <SettingsPageLayout
17
+ breadcrumb={{ current: 'General', parent: { label: 'Settings' } }}
18
+ title="General"
19
+ description="Tracker startup, confirmations, and session behavior."
20
+ >
21
+ <ul
22
+ className="m-0 flex w-full list-none flex-col gap-2 p-0"
23
+ aria-label="General settings"
24
+ >
25
+ <li className="rounded-md border border-panel-border bg-panel p-3.5 shadow-sm">
26
+ <DefaultSheetSessionSetting sheet_names={sheet_names} />
27
+ </li>
28
+ <li className="rounded-md border border-panel-border bg-panel p-3.5 shadow-sm">
29
+ <ConfirmBeforeCheckoutSetting />
30
+ </li>
31
+ <li className="rounded-md border border-panel-border bg-panel p-3.5 shadow-sm">
32
+ <ConfirmDestructiveActionsSetting />
33
+ </li>
34
+ <li className="rounded-md border border-panel-border bg-panel p-3.5 shadow-sm">
35
+ <ClearTagFiltersOnSheetChangeSetting />
36
+ </li>
37
+ </ul>
38
+ </SettingsPageLayout>
39
+ )
40
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Renders a three-line menu icon without a filled background.
3
+ */
4
+ export function HamburgerIcon() {
5
+ return (
6
+ <svg
7
+ className="block h-4 w-4 text-muted"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ viewBox="0 0 24 24"
10
+ fill="none"
11
+ stroke="currentColor"
12
+ strokeWidth="2"
13
+ strokeLinecap="round"
14
+ aria-hidden="true"
15
+ >
16
+ <path d="M4 7h16" />
17
+ <path d="M4 12h16" />
18
+ <path d="M4 17h16" />
19
+ </svg>
20
+ )
21
+ }
@@ -0,0 +1,77 @@
1
+ 'use client'
2
+
3
+ import { type FormEvent, useState } from 'react'
4
+
5
+ import { get_button_class_name } from '@/lib/get_button_class_name'
6
+ import { get_input_class_name } from '@/lib/get_input_class_name'
7
+
8
+ interface NoteEditFormProps {
9
+ initial_text: string
10
+ is_pending: boolean
11
+ inline?: boolean
12
+ on_save: (text: string) => void
13
+ on_cancel: () => void
14
+ }
15
+
16
+ /**
17
+ * Inline form for editing an existing entry note.
18
+ */
19
+ export function NoteEditForm({
20
+ initial_text,
21
+ is_pending,
22
+ inline = false,
23
+ on_save,
24
+ on_cancel,
25
+ }: NoteEditFormProps) {
26
+ const [text, set_text] = useState(initial_text)
27
+ const line_count = initial_text.split('\n').length
28
+ const rows = Math.min(6, Math.max(2, line_count))
29
+
30
+ const handle_submit = (event: FormEvent<HTMLFormElement>): void => {
31
+ event.preventDefault()
32
+ const trimmed = text.trim()
33
+
34
+ if (trimmed.length === 0) {
35
+ return
36
+ }
37
+
38
+ on_save(trimmed)
39
+ }
40
+
41
+ const textarea_class = inline
42
+ ? `${get_input_class_name()} min-h-10 w-full resize-y text-xs leading-snug`
43
+ : `${get_input_class_name()} min-h-10 w-full resize-y text-[0.85rem] leading-snug`
44
+
45
+ return (
46
+ <form
47
+ className="flex w-full flex-col gap-1.5"
48
+ onSubmit={handle_submit}
49
+ onClick={(event) => event.stopPropagation()}
50
+ >
51
+ <textarea
52
+ className={textarea_class}
53
+ value={text}
54
+ rows={rows}
55
+ disabled={is_pending}
56
+ onChange={(event) => set_text(event.target.value)}
57
+ />
58
+ <div className="flex justify-end gap-1.5">
59
+ <button
60
+ type="button"
61
+ className={get_button_class_name('ghost')}
62
+ disabled={is_pending}
63
+ onClick={on_cancel}
64
+ >
65
+ Cancel
66
+ </button>
67
+ <button
68
+ type="submit"
69
+ className={get_button_class_name('ghost')}
70
+ disabled={is_pending || text.trim().length === 0}
71
+ >
72
+ Save
73
+ </button>
74
+ </div>
75
+ </form>
76
+ )
77
+ }
@@ -0,0 +1,109 @@
1
+ 'use client'
2
+
3
+ import { type FormEvent, useState } from 'react'
4
+
5
+ import { get_button_class_name } from '@/lib/get_button_class_name'
6
+ import { get_input_class_name } from '@/lib/get_input_class_name'
7
+
8
+ interface NoteFormProps {
9
+ on_submit: (text: string, at?: string) => void
10
+ on_cancel?: () => void
11
+ is_pending: boolean
12
+ in_active_panel?: boolean
13
+ in_bar?: boolean
14
+ allow_at?: boolean
15
+ }
16
+
17
+ /**
18
+ * Adds a note to the active entry.
19
+ */
20
+ export function NoteForm({
21
+ on_submit,
22
+ on_cancel,
23
+ is_pending,
24
+ in_active_panel = false,
25
+ in_bar = false,
26
+ allow_at = false,
27
+ }: NoteFormProps) {
28
+ const [text, set_text] = useState('')
29
+ const [at, set_at] = useState('')
30
+
31
+ const handle_submit = (event: FormEvent<HTMLFormElement>): void => {
32
+ event.preventDefault()
33
+ const trimmed = text.trim()
34
+
35
+ if (trimmed.length === 0) {
36
+ return
37
+ }
38
+
39
+ const trimmed_at = at.trim()
40
+
41
+ on_submit(trimmed, trimmed_at.length > 0 ? trimmed_at : undefined)
42
+ set_text('')
43
+ set_at('')
44
+ }
45
+
46
+ const border_class =
47
+ in_active_panel && !in_bar
48
+ ? 'border-t border-accent-border pt-4'
49
+ : in_bar
50
+ ? 'border-t border-[color-mix(in_srgb,var(--accent-border)_65%,var(--panel-border))] pt-3.5'
51
+ : ''
52
+
53
+ return (
54
+ <form
55
+ className={`flex flex-col gap-2 ${border_class}`}
56
+ onSubmit={handle_submit}
57
+ >
58
+ <label className="text-[0.85rem] text-muted" htmlFor="note-text">
59
+ Add note
60
+ </label>
61
+ <div className="grid grid-cols-[minmax(0,1fr)_auto] gap-2 max-[860px]:grid-cols-1">
62
+ <input
63
+ id="note-text"
64
+ className={get_input_class_name()}
65
+ value={text}
66
+ onChange={(event) => set_text(event.target.value)}
67
+ placeholder="Pair with alice on the widget"
68
+ disabled={is_pending}
69
+ autoFocus
70
+ />
71
+ <div className="flex flex-wrap items-center gap-2 max-[860px]:w-full">
72
+ <button
73
+ type="submit"
74
+ className={get_button_class_name('ghost')}
75
+ disabled={is_pending || text.trim().length === 0}
76
+ >
77
+ Save note
78
+ </button>
79
+ {on_cancel !== undefined ? (
80
+ <button
81
+ type="button"
82
+ className={get_button_class_name('ghost')}
83
+ disabled={is_pending}
84
+ onClick={on_cancel}
85
+ >
86
+ Cancel
87
+ </button>
88
+ ) : null}
89
+ </div>
90
+ </div>
91
+ {allow_at ? (
92
+ <>
93
+ <label className="text-[0.85rem] text-muted" htmlFor="note-at">
94
+ Note time{' '}
95
+ <span className="font-normal opacity-85">(optional, natural language)</span>
96
+ </label>
97
+ <input
98
+ id="note-at"
99
+ className={get_input_class_name()}
100
+ value={at}
101
+ onChange={(event) => set_at(event.target.value)}
102
+ placeholder="e.g. 30 minutes ago"
103
+ disabled={is_pending}
104
+ />
105
+ </>
106
+ ) : null}
107
+ </form>
108
+ )
109
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Renders a small pencil icon for edit actions.
3
+ */
4
+ export function PencilIcon() {
5
+ return (
6
+ <svg
7
+ className="h-[0.85rem] w-[0.85rem]"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ viewBox="0 0 24 24"
10
+ fill="none"
11
+ stroke="currentColor"
12
+ strokeWidth="2"
13
+ strokeLinecap="round"
14
+ strokeLinejoin="round"
15
+ aria-hidden="true"
16
+ >
17
+ <path d="M12 20h9" />
18
+ <path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" />
19
+ </svg>
20
+ )
21
+ }
@@ -0,0 +1,121 @@
1
+ 'use client'
2
+
3
+ import { get_button_class_name } from '@/lib/get_button_class_name'
4
+ import { get_input_class_name } from '@/lib/get_input_class_name'
5
+ import { get_reporting_date_range_shortcut_inputs } from '@/lib/get_reporting_date_range_shortcut_inputs'
6
+ import { use_week_starts_on } from '@/lib/use_week_starts_on'
7
+ import { week_starts_on_to_index } from '@/lib/week_starts_on_to_index'
8
+ import {
9
+ type ReportingDateRangeInputs,
10
+ type ReportingDateRangeShortcut,
11
+ } from '@/lib/types/reporting'
12
+
13
+ export type { ReportingDateRangeInputs } from '@/lib/types/reporting'
14
+
15
+ const shortcut_options: {
16
+ value: ReportingDateRangeShortcut
17
+ label: string
18
+ }[] = [
19
+ { value: 'today', label: 'Today' },
20
+ { value: 'yesterday', label: 'Yesterday' },
21
+ { value: 'week', label: 'This week' },
22
+ { value: 'month', label: 'This month' },
23
+ { value: 'last_month', label: 'Last month' },
24
+ { value: 'year', label: 'This year' },
25
+ { value: 'last_year', label: 'Last year' },
26
+ ]
27
+
28
+ interface ReportingDateRangePickerProps {
29
+ range: ReportingDateRangeInputs
30
+ is_invalid: boolean
31
+ on_range_change: (range: ReportingDateRangeInputs) => void
32
+ on_clear: () => void
33
+ }
34
+
35
+ /**
36
+ * Date range filter controls for the reporting view.
37
+ */
38
+ export function ReportingDateRangePicker({
39
+ range,
40
+ is_invalid,
41
+ on_range_change,
42
+ on_clear,
43
+ }: ReportingDateRangePickerProps) {
44
+ const week_starts_on = use_week_starts_on()
45
+ const week_starts_on_index = week_starts_on_to_index(week_starts_on)
46
+ const has_filter =
47
+ range.from_date.length > 0 || range.to_date.length > 0
48
+
49
+ return (
50
+ <fieldset className="m-0 flex w-full max-w-2xl flex-col gap-2 rounded-md border border-panel-border bg-panel p-3.5 shadow-sm">
51
+ <legend className="mb-2 text-[0.72rem] font-semibold uppercase tracking-[0.06em] text-muted">
52
+ Date range
53
+ </legend>
54
+ <div className="flex flex-wrap gap-1.5" role="group" aria-label="Date range shortcuts">
55
+ {shortcut_options.map((option) => (
56
+ <button
57
+ key={option.value}
58
+ type="button"
59
+ className={get_button_class_name('ghost', 'small')}
60
+ onClick={() =>
61
+ on_range_change(
62
+ get_reporting_date_range_shortcut_inputs(
63
+ option.value,
64
+ new Date(),
65
+ week_starts_on_index,
66
+ ),
67
+ )
68
+ }
69
+ >
70
+ {option.label}
71
+ </button>
72
+ ))}
73
+ </div>
74
+ <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
75
+ <label className="flex flex-col gap-1 text-[0.82rem] text-muted">
76
+ From
77
+ <input
78
+ type="date"
79
+ className={get_input_class_name('compact')}
80
+ value={range.from_date}
81
+ onChange={(event) =>
82
+ on_range_change({
83
+ ...range,
84
+ from_date: event.target.value,
85
+ })
86
+ }
87
+ />
88
+ </label>
89
+ <label className="flex flex-col gap-1 text-[0.82rem] text-muted">
90
+ To
91
+ <input
92
+ type="date"
93
+ className={get_input_class_name('compact')}
94
+ value={range.to_date}
95
+ onChange={(event) =>
96
+ on_range_change({
97
+ ...range,
98
+ to_date: event.target.value,
99
+ })
100
+ }
101
+ />
102
+ </label>
103
+ </div>
104
+ <div className="flex flex-wrap items-center gap-2">
105
+ <button
106
+ type="button"
107
+ className={get_button_class_name('ghost', 'small')}
108
+ disabled={!has_filter}
109
+ onClick={on_clear}
110
+ >
111
+ Clear
112
+ </button>
113
+ {is_invalid ? (
114
+ <span className="text-[0.82rem] text-danger">
115
+ Enter a valid from and to date.
116
+ </span>
117
+ ) : null}
118
+ </div>
119
+ </fieldset>
120
+ )
121
+ }
@@ -0,0 +1,53 @@
1
+ 'use client'
2
+
3
+ import { get_button_class_name } from '@/lib/get_button_class_name'
4
+ import { type SheetReportSort } from '@/lib/types/reporting'
5
+
6
+ interface ReportingSortControlsProps {
7
+ sort: SheetReportSort
8
+ on_sort_change: (sort: SheetReportSort) => void
9
+ }
10
+
11
+ const sort_options: { value: SheetReportSort; label: string }[] = [
12
+ { value: 'duration', label: 'Duration' },
13
+ { value: 'name', label: 'Name' },
14
+ { value: 'entry_count', label: 'Entries' },
15
+ { value: 'active_first', label: 'Active first' },
16
+ ]
17
+
18
+ /**
19
+ * Sort mode toggles for the reporting sheet lists.
20
+ */
21
+ export function ReportingSortControls({
22
+ sort,
23
+ on_sort_change,
24
+ }: ReportingSortControlsProps) {
25
+ return (
26
+ <fieldset className="m-0 flex w-full max-w-2xl flex-col gap-2 border-0 p-0">
27
+ <legend className="mb-2 text-[0.72rem] font-semibold uppercase tracking-[0.06em] text-muted">
28
+ Sort by
29
+ </legend>
30
+ <div className="flex flex-wrap gap-1.5" role="group" aria-label="Sort sheets by">
31
+ {sort_options.map((option) => {
32
+ const is_selected = sort === option.value
33
+
34
+ return (
35
+ <button
36
+ key={option.value}
37
+ type="button"
38
+ className={`${get_button_class_name('ghost', 'small')} ${
39
+ is_selected
40
+ ? 'border-accent-border bg-accent-soft text-foreground'
41
+ : ''
42
+ }`}
43
+ aria-pressed={is_selected}
44
+ onClick={() => on_sort_change(option.value)}
45
+ >
46
+ {option.label}
47
+ </button>
48
+ )
49
+ })}
50
+ </div>
51
+ </fieldset>
52
+ )
53
+ }