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
package/AGENTS.md ADDED
@@ -0,0 +1,5 @@
1
+ <!-- BEGIN:nextjs-agent-rules -->
2
+ # This is NOT the Next.js you know
3
+
4
+ This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
5
+ <!-- END:nextjs-agent-rules -->
package/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
+
5
+ ### [0.1.2](https://github.com/f3rnox/super-time-tracker-ui/compare/v0.1.1...v0.1.2) (2026-05-18)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * rm private from manifest and add author email ([ec4d2ac](https://github.com/f3rnox/super-time-tracker-ui/commit/ec4d2ac8b340d8ccb1339aa785f78beae87d68ad))
11
+
12
+ ### 0.1.1 (2026-05-18)
13
+
14
+
15
+ ### Features
16
+
17
+ * add move to sheet option to active entry ([ef1fe83](https://github.com/f3rnox/super-time-tracker-ui/commit/ef1fe8357b05a255e3e107ae67d6c75bd9845276))
18
+ * initial commit ([15a8998](https://github.com/f3rnox/super-time-tracker-ui/commit/15a8998c1ac09fa5392635a92b93d0a9d8412f53))
19
+ * minor updates, multi-select functionality ([69ccbd0](https://github.com/f3rnox/super-time-tracker-ui/commit/69ccbd01c8ac04dfebbdbad40bd648f95d5d55dc))
20
+ * show sheet entry list, minor changes ([8d918f7](https://github.com/f3rnox/super-time-tracker-ui/commit/8d918f71070bf537daa203e4483525109f90b949))
21
+ * update ([ed0d7e6](https://github.com/f3rnox/super-time-tracker-ui/commit/ed0d7e614ab7ee73ef1d5f7c8f5bf2ca8b7f66e0))
22
+ * update ([b272c68](https://github.com/f3rnox/super-time-tracker-ui/commit/b272c68b16a1ee0b70384c328cbcbcc42a2b290d))
23
+ * update ([c9a138d](https://github.com/f3rnox/super-time-tracker-ui/commit/c9a138d5019f6aa488acb74eb8fbf0eabe3ea8e8))
24
+ * update ([f49cc33](https://github.com/f3rnox/super-time-tracker-ui/commit/f49cc33227666f59b8db55517d98352515a88dd1))
25
+ * update ([4efa65f](https://github.com/f3rnox/super-time-tracker-ui/commit/4efa65f63cee5e0bf829b209170c8b8a383d749e))
26
+ * update ([913173d](https://github.com/f3rnox/super-time-tracker-ui/commit/913173dc8dfec47e181c5e839fa6d6bfa0e0cd96))
27
+ * update with many new settings and features ([198f093](https://github.com/f3rnox/super-time-tracker-ui/commit/198f093c1f515380a8885bdb5119efdc7aabb07f))
28
+ * update, add entry editing and --at support ([bc51980](https://github.com/f3rnox/super-time-tracker-ui/commit/bc51980436e12f907fe13e810586ea7b7ff59e08))
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
@@ -0,0 +1,39 @@
1
+ import { NextResponse } from 'next/server'
2
+
3
+ import { api_error_response } from '@/lib/api_error_response'
4
+ import { DB_FILE_NAME } from '@/lib/config'
5
+ import { read_db_backup_contents } from '@/lib/read_db_backup_contents'
6
+ import { restore_db_from_uploaded_json } from '@/lib/restore_db_from_uploaded_json'
7
+
8
+ /**
9
+ * Downloads the tracker database as JSON.
10
+ */
11
+ export async function GET(): Promise<NextResponse> {
12
+ try {
13
+ const contents = await read_db_backup_contents()
14
+
15
+ return new NextResponse(contents, {
16
+ headers: {
17
+ 'Content-Type': 'application/json; charset=utf-8',
18
+ 'Content-Disposition': `attachment; filename="${DB_FILE_NAME}"`,
19
+ },
20
+ })
21
+ } catch (error: unknown) {
22
+ return api_error_response(error, 500)
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Restores the tracker database from an uploaded backup.
28
+ */
29
+ export async function POST(request: Request): Promise<NextResponse> {
30
+ try {
31
+ const uploaded: unknown = await request.json()
32
+
33
+ await restore_db_from_uploaded_json(uploaded)
34
+
35
+ return NextResponse.json({ ok: true })
36
+ } catch (error: unknown) {
37
+ return api_error_response(error, 400)
38
+ }
39
+ }
@@ -0,0 +1,53 @@
1
+ import { NextResponse } from 'next/server'
2
+
3
+ import { api_error_response } from '@/lib/api_error_response'
4
+ import { delete_entries } from '@/lib/delete_entries'
5
+ import { get_tracker_state } from '@/lib/get_tracker_state'
6
+
7
+ interface DeleteEntryRefBody {
8
+ sheetName?: string
9
+ entryId?: number
10
+ }
11
+
12
+ interface DeleteEntriesBody {
13
+ entries?: DeleteEntryRefBody[]
14
+ }
15
+
16
+ /**
17
+ * Deletes multiple time sheet entries across sheets.
18
+ */
19
+ export async function POST(request: Request): Promise<NextResponse> {
20
+ try {
21
+ const body = (await request.json()) as DeleteEntriesBody
22
+ const raw_entries = body.entries ?? []
23
+
24
+ if (raw_entries.length === 0) {
25
+ return api_error_response(new Error('No entries selected'))
26
+ }
27
+
28
+ const entries = raw_entries.map((entry, index) => {
29
+ const sheet_name = entry.sheetName?.trim() ?? ''
30
+ const entry_id = entry.entryId
31
+
32
+ if (sheet_name.length === 0) {
33
+ throw new Error(`Entry ${index + 1} is missing a sheet name`)
34
+ }
35
+
36
+ if (entry_id === undefined || !Number.isFinite(entry_id)) {
37
+ throw new Error(`Entry ${index + 1} is missing an entry id`)
38
+ }
39
+
40
+ return {
41
+ sheet_name,
42
+ entry_id,
43
+ }
44
+ })
45
+
46
+ await delete_entries({ entries })
47
+
48
+ const state = await get_tracker_state()
49
+ return NextResponse.json(state)
50
+ } catch (error: unknown) {
51
+ return api_error_response(error)
52
+ }
53
+ }
@@ -0,0 +1,46 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { api_error_response } from "@/lib/api_error_response";
4
+ import { get_tracker_state } from "@/lib/get_tracker_state";
5
+ import { move_entry_to_sheet } from "@/lib/move_entry_to_sheet";
6
+
7
+ interface MoveEntryBody {
8
+ sheetName?: string;
9
+ entryId?: number;
10
+ targetSheetName?: string;
11
+ }
12
+
13
+ /**
14
+ * Moves a time sheet entry to another sheet.
15
+ */
16
+ export async function POST(request: Request): Promise<NextResponse> {
17
+ try {
18
+ const body = (await request.json()) as MoveEntryBody;
19
+ const sheet_name = body.sheetName?.trim() ?? "";
20
+ const entry_id = body.entryId;
21
+ const target_sheet_name = body.targetSheetName?.trim() ?? "";
22
+
23
+ if (sheet_name.length === 0) {
24
+ return api_error_response(new Error("Sheet name is required"));
25
+ }
26
+
27
+ if (entry_id === undefined || !Number.isFinite(entry_id)) {
28
+ return api_error_response(new Error("Entry id is required"));
29
+ }
30
+
31
+ if (target_sheet_name.length === 0) {
32
+ return api_error_response(new Error("Target sheet name is required"));
33
+ }
34
+
35
+ await move_entry_to_sheet({
36
+ sheet_name,
37
+ entry_id,
38
+ target_sheet_name,
39
+ });
40
+
41
+ const state = await get_tracker_state();
42
+ return NextResponse.json(state);
43
+ } catch (error: unknown) {
44
+ return api_error_response(error);
45
+ }
46
+ }
@@ -0,0 +1,62 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { api_error_response } from "@/lib/api_error_response";
4
+ import { get_tracker_state } from "@/lib/get_tracker_state";
5
+ import { move_entries_to_sheet } from "@/lib/move_entries_to_sheet";
6
+
7
+ interface MoveEntryRefBody {
8
+ sheetName?: string;
9
+ entryId?: number;
10
+ }
11
+
12
+ interface MoveEntriesBody {
13
+ entries?: MoveEntryRefBody[];
14
+ targetSheetName?: string;
15
+ }
16
+
17
+ /**
18
+ * Moves multiple time sheet entries to another sheet.
19
+ */
20
+ export async function POST(request: Request): Promise<NextResponse> {
21
+ try {
22
+ const body = (await request.json()) as MoveEntriesBody;
23
+ const target_sheet_name = body.targetSheetName?.trim() ?? "";
24
+ const raw_entries = body.entries ?? [];
25
+
26
+ if (target_sheet_name.length === 0) {
27
+ return api_error_response(new Error("Target sheet name is required"));
28
+ }
29
+
30
+ if (raw_entries.length === 0) {
31
+ return api_error_response(new Error("No entries selected"));
32
+ }
33
+
34
+ const entries = raw_entries.map((entry, index) => {
35
+ const sheet_name = entry.sheetName?.trim() ?? "";
36
+ const entry_id = entry.entryId;
37
+
38
+ if (sheet_name.length === 0) {
39
+ throw new Error(`Entry ${index + 1} is missing a sheet name`);
40
+ }
41
+
42
+ if (entry_id === undefined || !Number.isFinite(entry_id)) {
43
+ throw new Error(`Entry ${index + 1} is missing an entry id`);
44
+ }
45
+
46
+ return {
47
+ sheet_name,
48
+ entry_id,
49
+ };
50
+ });
51
+
52
+ await move_entries_to_sheet({
53
+ entries,
54
+ target_sheet_name,
55
+ });
56
+
57
+ const state = await get_tracker_state();
58
+ return NextResponse.json(state);
59
+ } catch (error: unknown) {
60
+ return api_error_response(error);
61
+ }
62
+ }
@@ -0,0 +1,75 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { api_error_response } from "@/lib/api_error_response";
4
+ import { delete_entry } from "@/lib/delete_entry";
5
+ import { edit_entry } from "@/lib/edit_entry";
6
+ import { get_tracker_state } from "@/lib/get_tracker_state";
7
+
8
+ interface EntryIdBody {
9
+ sheetName?: string;
10
+ entryId?: number;
11
+ }
12
+
13
+ interface EditEntryBody extends EntryIdBody {
14
+ start?: string;
15
+ end?: string;
16
+ description?: string;
17
+ }
18
+
19
+ /**
20
+ * Deletes a time sheet entry by id.
21
+ */
22
+ export async function POST(request: Request): Promise<NextResponse> {
23
+ try {
24
+ const body = (await request.json()) as EntryIdBody;
25
+ const sheet_name = body.sheetName?.trim() ?? "";
26
+ const entry_id = body.entryId;
27
+
28
+ if (sheet_name.length === 0) {
29
+ return api_error_response(new Error("Sheet name is required"));
30
+ }
31
+
32
+ if (entry_id === undefined || !Number.isFinite(entry_id)) {
33
+ return api_error_response(new Error("Entry id is required"));
34
+ }
35
+
36
+ await delete_entry({ sheet_name, entry_id });
37
+
38
+ const state = await get_tracker_state();
39
+ return NextResponse.json(state);
40
+ } catch (error: unknown) {
41
+ return api_error_response(error);
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Updates a time sheet entry's times or description.
47
+ */
48
+ export async function PATCH(request: Request): Promise<NextResponse> {
49
+ try {
50
+ const body = (await request.json()) as EditEntryBody;
51
+ const sheet_name = body.sheetName?.trim() ?? "";
52
+ const entry_id = body.entryId;
53
+
54
+ if (sheet_name.length === 0) {
55
+ return api_error_response(new Error("Sheet name is required"));
56
+ }
57
+
58
+ if (entry_id === undefined || !Number.isFinite(entry_id)) {
59
+ return api_error_response(new Error("Entry id is required"));
60
+ }
61
+
62
+ await edit_entry({
63
+ sheet_name,
64
+ entry_id,
65
+ start: body.start,
66
+ end: body.end,
67
+ description: body.description,
68
+ });
69
+
70
+ const state = await get_tracker_state();
71
+ return NextResponse.json(state);
72
+ } catch (error: unknown) {
73
+ return api_error_response(error);
74
+ }
75
+ }
@@ -0,0 +1,38 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { api_error_response } from "@/lib/api_error_response";
4
+ import { check_in_entry } from "@/lib/check_in_entry";
5
+ import { get_tracker_state } from "@/lib/get_tracker_state";
6
+
7
+ interface CheckInBody {
8
+ description?: string;
9
+ sheetName?: string;
10
+ note?: string;
11
+ at?: string;
12
+ }
13
+
14
+ /**
15
+ * Checks in to a time sheet entry.
16
+ */
17
+ export async function POST(request: Request): Promise<NextResponse> {
18
+ try {
19
+ const body = (await request.json()) as CheckInBody;
20
+ const description = body.description?.trim() ?? "";
21
+
22
+ if (description.length === 0) {
23
+ return api_error_response(new Error("Description is required"));
24
+ }
25
+
26
+ await check_in_entry({
27
+ description,
28
+ sheet_name: body.sheetName,
29
+ note: body.note,
30
+ at: body.at,
31
+ });
32
+
33
+ const state = await get_tracker_state();
34
+ return NextResponse.json(state);
35
+ } catch (error: unknown) {
36
+ return api_error_response(error);
37
+ }
38
+ }
@@ -0,0 +1,120 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { add_note_to_entry } from "@/lib/add_note_to_entry";
4
+ import { delete_note_on_entry } from "@/lib/delete_note_on_entry";
5
+ import { edit_note_on_entry } from "@/lib/edit_note_on_entry";
6
+ import { api_error_response } from "@/lib/api_error_response";
7
+ import { get_tracker_state } from "@/lib/get_tracker_state";
8
+
9
+ interface NoteBody {
10
+ text?: string;
11
+ at?: string;
12
+ sheetName?: string;
13
+ entryId?: number;
14
+ }
15
+
16
+ interface EditNoteBody extends NoteBody {
17
+ timestamp?: string;
18
+ }
19
+
20
+ /**
21
+ * Attaches a note to an entry.
22
+ */
23
+ export async function POST(request: Request): Promise<NextResponse> {
24
+ try {
25
+ const body = (await request.json()) as NoteBody;
26
+ const text = body.text?.trim() ?? "";
27
+
28
+ if (text.length === 0) {
29
+ return api_error_response(new Error("Note text is required"));
30
+ }
31
+
32
+ await add_note_to_entry({
33
+ text,
34
+ at: body.at,
35
+ sheet_name: body.sheetName,
36
+ entry_id: body.entryId,
37
+ });
38
+
39
+ const state = await get_tracker_state();
40
+ return NextResponse.json(state);
41
+ } catch (error: unknown) {
42
+ return api_error_response(error);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Updates an existing note on an entry.
48
+ */
49
+ export async function PATCH(request: Request): Promise<NextResponse> {
50
+ try {
51
+ const body = (await request.json()) as EditNoteBody;
52
+ const sheet_name = body.sheetName?.trim() ?? "";
53
+ const entry_id = body.entryId;
54
+ const timestamp = body.timestamp?.trim() ?? "";
55
+ const text = body.text?.trim() ?? "";
56
+
57
+ if (sheet_name.length === 0) {
58
+ return api_error_response(new Error("Sheet name is required"));
59
+ }
60
+
61
+ if (entry_id === undefined || !Number.isFinite(entry_id)) {
62
+ return api_error_response(new Error("Entry id is required"));
63
+ }
64
+
65
+ if (timestamp.length === 0) {
66
+ return api_error_response(new Error("Note timestamp is required"));
67
+ }
68
+
69
+ if (text.length === 0) {
70
+ return api_error_response(new Error("Note text is required"));
71
+ }
72
+
73
+ await edit_note_on_entry({
74
+ sheet_name,
75
+ entry_id,
76
+ note_timestamp: timestamp,
77
+ text,
78
+ });
79
+
80
+ const state = await get_tracker_state();
81
+ return NextResponse.json(state);
82
+ } catch (error: unknown) {
83
+ return api_error_response(error);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Removes a note from an entry.
89
+ */
90
+ export async function DELETE(request: Request): Promise<NextResponse> {
91
+ try {
92
+ const body = (await request.json()) as EditNoteBody;
93
+ const sheet_name = body.sheetName?.trim() ?? "";
94
+ const entry_id = body.entryId;
95
+ const timestamp = body.timestamp?.trim() ?? "";
96
+
97
+ if (sheet_name.length === 0) {
98
+ return api_error_response(new Error("Sheet name is required"));
99
+ }
100
+
101
+ if (entry_id === undefined || !Number.isFinite(entry_id)) {
102
+ return api_error_response(new Error("Entry id is required"));
103
+ }
104
+
105
+ if (timestamp.length === 0) {
106
+ return api_error_response(new Error("Note timestamp is required"));
107
+ }
108
+
109
+ await delete_note_on_entry({
110
+ sheet_name,
111
+ entry_id,
112
+ note_timestamp: timestamp,
113
+ });
114
+
115
+ const state = await get_tracker_state();
116
+ return NextResponse.json(state);
117
+ } catch (error: unknown) {
118
+ return api_error_response(error);
119
+ }
120
+ }
@@ -0,0 +1,31 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { api_error_response } from "@/lib/api_error_response";
4
+ import { check_out_entry } from "@/lib/check_out_entry";
5
+ import { get_tracker_state } from "@/lib/get_tracker_state";
6
+
7
+ interface CheckOutBody {
8
+ sheetName?: string;
9
+ note?: string;
10
+ at?: string;
11
+ }
12
+
13
+ /**
14
+ * Checks out of the active time sheet entry.
15
+ */
16
+ export async function POST(request: Request): Promise<NextResponse> {
17
+ try {
18
+ const body = (await request.json()) as CheckOutBody;
19
+
20
+ await check_out_entry({
21
+ sheet_name: body.sheetName,
22
+ note: body.note,
23
+ at: body.at,
24
+ });
25
+
26
+ const state = await get_tracker_state();
27
+ return NextResponse.json(state);
28
+ } catch (error: unknown) {
29
+ return api_error_response(error);
30
+ }
31
+ }
@@ -0,0 +1,68 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { api_error_response } from "@/lib/api_error_response";
4
+ import { delete_sheet } from "@/lib/delete_sheet";
5
+ import { get_tracker_state } from "@/lib/get_tracker_state";
6
+ import { rename_sheet } from "@/lib/rename_sheet";
7
+ import { set_active_sheet } from "@/lib/set_active_sheet";
8
+
9
+ interface SheetBody {
10
+ name?: string;
11
+ delete?: boolean;
12
+ }
13
+
14
+ interface RenameSheetBody {
15
+ name?: string;
16
+ newName?: string;
17
+ }
18
+
19
+ /**
20
+ * Switches the active sheet or deletes one by name.
21
+ */
22
+ export async function POST(request: Request): Promise<NextResponse> {
23
+ try {
24
+ const body = (await request.json()) as SheetBody;
25
+ const name = body.name?.trim() ?? "";
26
+
27
+ if (name.length === 0) {
28
+ return api_error_response(new Error("Sheet name is required"));
29
+ }
30
+
31
+ if (body.delete === true) {
32
+ await delete_sheet(name);
33
+ } else {
34
+ await set_active_sheet(name);
35
+ }
36
+
37
+ const state = await get_tracker_state(body.delete === true ? undefined : name);
38
+ return NextResponse.json(state);
39
+ } catch (error: unknown) {
40
+ return api_error_response(error);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Renames an existing sheet.
46
+ */
47
+ export async function PATCH(request: Request): Promise<NextResponse> {
48
+ try {
49
+ const body = (await request.json()) as RenameSheetBody;
50
+ const name = body.name?.trim() ?? "";
51
+ const new_name = body.newName?.trim() ?? "";
52
+
53
+ if (name.length === 0) {
54
+ return api_error_response(new Error("Sheet name is required"));
55
+ }
56
+
57
+ if (new_name.length === 0) {
58
+ return api_error_response(new Error("New sheet name is required"));
59
+ }
60
+
61
+ await rename_sheet({ sheet_name: name, new_name });
62
+
63
+ const state = await get_tracker_state();
64
+ return NextResponse.json(state);
65
+ } catch (error: unknown) {
66
+ return api_error_response(error);
67
+ }
68
+ }
@@ -0,0 +1,16 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { api_error_response } from "@/lib/api_error_response";
4
+ import { get_tracker_state } from "@/lib/get_tracker_state";
5
+
6
+ /**
7
+ * Returns the current tracker snapshot for the UI.
8
+ */
9
+ export async function GET(): Promise<NextResponse> {
10
+ try {
11
+ const state = await get_tracker_state();
12
+ return NextResponse.json(state);
13
+ } catch (error: unknown) {
14
+ return api_error_response(error, 500);
15
+ }
16
+ }