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.
- package/AGENTS.md +5 -0
- package/CHANGELOG.md +28 -0
- package/CLAUDE.md +1 -0
- package/README.md +36 -0
- package/app/api/backup/route.ts +39 -0
- package/app/api/entry/delete-bulk/route.ts +53 -0
- package/app/api/entry/move/route.ts +46 -0
- package/app/api/entry/move-bulk/route.ts +62 -0
- package/app/api/entry/route.ts +75 -0
- package/app/api/in/route.ts +38 -0
- package/app/api/note/route.ts +120 -0
- package/app/api/out/route.ts +31 -0
- package/app/api/sheet/route.ts +68 -0
- package/app/api/state/route.ts +16 -0
- package/app/api/tags/route.ts +75 -0
- package/app/color-palettes.css +260 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +140 -0
- package/app/layout.tsx +54 -0
- package/app/page.tsx +24 -0
- package/app/reporting/page.tsx +11 -0
- package/app/settings/data/page.tsx +9 -0
- package/app/settings/display/page.tsx +8 -0
- package/app/settings/page.tsx +12 -0
- package/app/settings/tags/page.tsx +13 -0
- package/bin/stt-ui.js +63 -0
- package/components/active-entry-panel.tsx +199 -0
- package/components/backup-restore-setting.tsx +168 -0
- package/components/check-in-form-collapsed-setting.tsx +44 -0
- package/components/check-in-form-collapsible.tsx +52 -0
- package/components/check-in-form.tsx +89 -0
- package/components/checkbox.tsx +75 -0
- package/components/checkout-button-group.tsx +73 -0
- package/components/chevron-icon.tsx +25 -0
- package/components/clear-tag-filters-on-sheet-change-setting.tsx +45 -0
- package/components/color-palette-setting.tsx +75 -0
- package/components/compact-lists-setting.tsx +42 -0
- package/components/confirm-before-checkout-setting.tsx +42 -0
- package/components/confirm-destructive-actions-setting.tsx +46 -0
- package/components/confirm-dialog-provider.tsx +71 -0
- package/components/confirm-dialog.tsx +90 -0
- package/components/data-settings-view.tsx +47 -0
- package/components/default-reporting-range-setting.tsx +56 -0
- package/components/default-reporting-sort-setting.tsx +45 -0
- package/components/default-sheet-session-setting.tsx +118 -0
- package/components/display-settings-view.tsx +75 -0
- package/components/duration-format-setting.tsx +40 -0
- package/components/entry-actions-menu.tsx +207 -0
- package/components/entry-edit-form.tsx +113 -0
- package/components/entry-list-bulk-bar.tsx +128 -0
- package/components/entry-list-sort-setting.tsx +41 -0
- package/components/entry-list.tsx +336 -0
- package/components/entry-notes-list.tsx +211 -0
- package/components/entry-tag-filter.tsx +99 -0
- package/components/format_datetime_hint.ts +8 -0
- package/components/format_time.ts +10 -0
- package/components/general-settings-view.tsx +40 -0
- package/components/hamburger-icon.tsx +21 -0
- package/components/note-edit-form.tsx +77 -0
- package/components/note-form.tsx +109 -0
- package/components/pencil-icon.tsx +21 -0
- package/components/reporting-date-range-picker.tsx +121 -0
- package/components/reporting-sort-controls.tsx +53 -0
- package/components/reporting-view.tsx +340 -0
- package/components/setting-radio-group.tsx +79 -0
- package/components/settings-nav.tsx +66 -0
- package/components/settings-page-layout.tsx +53 -0
- package/components/settings-saved-toast.tsx +57 -0
- package/components/sheet-actions-menu.tsx +108 -0
- package/components/sheet-sidebar.tsx +196 -0
- package/components/tag-autocomplete-input.tsx +183 -0
- package/components/tag-filter-mode-setting.tsx +47 -0
- package/components/tag-management-view.tsx +290 -0
- package/components/theme-mode-setting.tsx +44 -0
- package/components/theme-mode-system-listener.tsx +43 -0
- package/components/theme_switcher.tsx +38 -0
- package/components/time-format-setting.tsx +39 -0
- package/components/timer-in-title-setting.tsx +38 -0
- package/components/timer-show-seconds-setting.tsx +41 -0
- package/components/tracker-active-bar.tsx +76 -0
- package/components/tracker-app.tsx +338 -0
- package/components/tracker-breadcrumb.tsx +56 -0
- package/components/tracker-document-title.tsx +67 -0
- package/components/tracker-topbar.tsx +63 -0
- package/components/trash-icon.tsx +24 -0
- package/components/week-starts-on-setting.tsx +39 -0
- package/eslint.config.mjs +18 -0
- package/lib/add_note_to_entry.ts +65 -0
- package/lib/api_error_response.ts +10 -0
- package/lib/apply_accent_color.ts +12 -0
- package/lib/apply_color_palette.ts +12 -0
- package/lib/apply_compact_lists.ts +9 -0
- package/lib/apply_tag_autocomplete_selection.ts +26 -0
- package/lib/apply_theme.ts +8 -0
- package/lib/build_reporting_stats.ts +55 -0
- package/lib/build_resume_description.ts +15 -0
- package/lib/check_in_entry.ts +81 -0
- package/lib/check_out_entry.ts +75 -0
- package/lib/collect_known_tags.ts +22 -0
- package/lib/collect_tag_stats.ts +27 -0
- package/lib/collect_tags_from_entries.ts +35 -0
- package/lib/config.ts +9 -0
- package/lib/convert_json_db.ts +49 -0
- package/lib/delete_entries.ts +62 -0
- package/lib/delete_entry.ts +29 -0
- package/lib/delete_note_on_entry.ts +42 -0
- package/lib/delete_sheet.ts +30 -0
- package/lib/delete_tracker_action.ts +22 -0
- package/lib/edit_entry.ts +56 -0
- package/lib/edit_note_on_entry.ts +49 -0
- package/lib/ensure_dir_exists.ts +22 -0
- package/lib/entry_matches_tag_filter.ts +26 -0
- package/lib/fetch_tracker_state.ts +15 -0
- package/lib/filter_entries_by_tags.ts +20 -0
- package/lib/filter_known_tags.ts +20 -0
- package/lib/find_all_serialized_active_entries.ts +28 -0
- package/lib/find_serialized_active_entry.ts +12 -0
- package/lib/find_serialized_active_entry_for_sheet.ts +31 -0
- package/lib/find_sheet_with_active_entry.ts +16 -0
- package/lib/format_display_tag.ts +6 -0
- package/lib/format_duration.ts +45 -0
- package/lib/gen_db.ts +43 -0
- package/lib/get_active_panel_class_name.ts +20 -0
- package/lib/get_average_entry_ms.ts +13 -0
- package/lib/get_button_class_name.ts +24 -0
- package/lib/get_check_out_confirm_dialog.ts +19 -0
- package/lib/get_clipped_entry_duration_ms.ts +18 -0
- package/lib/get_compact_lists_snapshot.ts +15 -0
- package/lib/get_date_range_ms_from_inputs.ts +31 -0
- package/lib/get_delete_entries_confirm_dialog.ts +21 -0
- package/lib/get_delete_entry_confirm_dialog.ts +19 -0
- package/lib/get_delete_note_confirm_dialog.ts +21 -0
- package/lib/get_delete_sheet_confirm_dialog.ts +25 -0
- package/lib/get_entry_duration_ms.ts +14 -0
- package/lib/get_entry_row_key.ts +8 -0
- package/lib/get_initial_preferred_sheet_name.ts +34 -0
- package/lib/get_initial_reporting_range_inputs.ts +31 -0
- package/lib/get_input_class_name.ts +15 -0
- package/lib/get_merge_tags_confirm_dialog.ts +25 -0
- package/lib/get_period_range_ms.ts +43 -0
- package/lib/get_reporting_date_range_shortcut_inputs.ts +84 -0
- package/lib/get_reporting_period_totals.ts +39 -0
- package/lib/get_reporting_stats.ts +25 -0
- package/lib/get_restore_db_confirm_dialog.ts +14 -0
- package/lib/get_running_entry_key.ts +8 -0
- package/lib/get_serialized_entries_total_ms.ts +10 -0
- package/lib/get_sheet.ts +14 -0
- package/lib/get_sheet_report_stats.ts +22 -0
- package/lib/get_sheet_report_stats_for_range.ts +46 -0
- package/lib/get_sheet_tag_filter_snapshot.ts +22 -0
- package/lib/get_sheets_duration_in_range.ts +27 -0
- package/lib/get_tag_autocomplete_context.ts +32 -0
- package/lib/get_theme_snapshot.ts +16 -0
- package/lib/get_tracker_state.ts +67 -0
- package/lib/has_string_value.ts +6 -0
- package/lib/is_entry_in_day.ts +15 -0
- package/lib/is_idle_sheet_report.ts +8 -0
- package/lib/is_json_time_tracker_db.ts +14 -0
- package/lib/merge_tags_across_db.ts +79 -0
- package/lib/migrate_json_db.ts +56 -0
- package/lib/migrate_json_db_to_version_three.ts +51 -0
- package/lib/migrate_json_db_to_version_two.ts +50 -0
- package/lib/move_entries_to_sheet.ts +152 -0
- package/lib/move_entry_to_sheet.ts +82 -0
- package/lib/normalize_stored_tag.ts +16 -0
- package/lib/notify_settings_saved.ts +47 -0
- package/lib/parse_default_sheet_session_mode.ts +21 -0
- package/lib/parse_entry_from_input.ts +23 -0
- package/lib/parse_natural_language_date.ts +23 -0
- package/lib/parse_reporting_source_sheets.ts +22 -0
- package/lib/partition_sheet_report_stats.ts +30 -0
- package/lib/patch_tracker_action.ts +22 -0
- package/lib/persist_ui_preference.ts +18 -0
- package/lib/post_tracker_action.ts +22 -0
- package/lib/preferences/accent_color_preference.ts +21 -0
- package/lib/preferences/check_in_form_collapsed_preference.ts +20 -0
- package/lib/preferences/clear_tag_filters_on_sheet_change_preference.ts +20 -0
- package/lib/preferences/color_palette_preference.ts +21 -0
- package/lib/preferences/confirm_before_checkout_preference.ts +20 -0
- package/lib/preferences/confirm_destructive_actions_preference.ts +20 -0
- package/lib/preferences/default_reporting_range_preference.ts +21 -0
- package/lib/preferences/default_reporting_sort_preference.ts +24 -0
- package/lib/preferences/duration_format_preference.ts +19 -0
- package/lib/preferences/entry_list_sort_preference.ts +21 -0
- package/lib/preferences/tag_filter_mode_preference.ts +18 -0
- package/lib/preferences/theme_mode_preference.ts +18 -0
- package/lib/preferences/time_format_preference.ts +18 -0
- package/lib/preferences/timer_in_title_preference.ts +18 -0
- package/lib/preferences/timer_show_seconds_preference.ts +19 -0
- package/lib/preferences/week_starts_on_preference.ts +19 -0
- package/lib/prompt_check_out_at.ts +17 -0
- package/lib/prompt_entry_note.ts +14 -0
- package/lib/prune_sheet_tag_filter.ts +27 -0
- package/lib/read_db.ts +49 -0
- package/lib/read_db_backup_contents.ts +22 -0
- package/lib/read_document_compact_lists.ts +12 -0
- package/lib/read_document_theme.ts +14 -0
- package/lib/read_sheet_tag_filter.ts +26 -0
- package/lib/read_stored_active_sheet.ts +14 -0
- package/lib/read_stored_compact_lists.ts +24 -0
- package/lib/read_stored_default_sheet_fixed_name.ts +16 -0
- package/lib/read_stored_default_sheet_session_mode.ts +18 -0
- package/lib/read_stored_sheet_tag_filters.ts +28 -0
- package/lib/read_stored_theme.ts +18 -0
- package/lib/rename_sheet.ts +39 -0
- package/lib/rename_tag_across_db.ts +19 -0
- package/lib/resolve_active_sheet_name.ts +36 -0
- package/lib/resolve_session_preferred_sheet.ts +37 -0
- package/lib/resolve_theme.ts +18 -0
- package/lib/resolve_theme_mode_to_theme.ts +19 -0
- package/lib/restore_db_from_uploaded_json.ts +24 -0
- package/lib/serialize_entry.ts +27 -0
- package/lib/serialize_reporting_source_sheets.ts +19 -0
- package/lib/serialize_sheet_entries.ts +18 -0
- package/lib/set_accent_color.ts +12 -0
- package/lib/set_active_sheet.ts +18 -0
- package/lib/set_color_palette.ts +12 -0
- package/lib/set_compact_lists.ts +12 -0
- package/lib/set_default_sheet_fixed_name.ts +8 -0
- package/lib/set_default_sheet_session_mode.ts +11 -0
- package/lib/set_sheet_tag_filter.ts +13 -0
- package/lib/set_theme_mode.ts +19 -0
- package/lib/sheet_tag_filter_snapshots.ts +48 -0
- package/lib/sort_serialized_entries.ts +35 -0
- package/lib/sort_sheet_report_stats.ts +43 -0
- package/lib/subscribe_compact_lists.ts +25 -0
- package/lib/subscribe_sheet_tag_filters.ts +28 -0
- package/lib/subscribe_theme.ts +23 -0
- package/lib/sync_active_sheet_preference.ts +19 -0
- package/lib/tags_are_equal.ts +12 -0
- package/lib/theme_init_script.ts +11 -0
- package/lib/toggle_sheet_tag_filter.ts +28 -0
- package/lib/toggle_theme.ts +20 -0
- package/lib/types/confirm_dialog.ts +9 -0
- package/lib/types/data.ts +16 -0
- package/lib/types/generic_data.ts +25 -0
- package/lib/types/index.ts +2 -0
- package/lib/types/reporting.ts +59 -0
- package/lib/types/tag_management.ts +7 -0
- package/lib/types/theme.ts +3 -0
- package/lib/types/tracker_state.ts +39 -0
- package/lib/types/ui_preferences.ts +104 -0
- package/lib/types/ui_settings.ts +17 -0
- package/lib/ui_preference_store.ts +80 -0
- package/lib/ui_settings_init_script.ts +33 -0
- package/lib/use_check_in_form_collapsed.ts +18 -0
- package/lib/use_clear_tag_filters_on_sheet_change.ts +18 -0
- package/lib/use_confirm_before_checkout.ts +18 -0
- package/lib/use_confirm_destructive_actions.ts +18 -0
- package/lib/use_duration_format.ts +17 -0
- package/lib/use_entry_list_sort.ts +17 -0
- package/lib/use_tag_filter_mode.ts +17 -0
- package/lib/use_time_format.ts +17 -0
- package/lib/use_timer_in_title.ts +18 -0
- package/lib/use_timer_show_seconds.ts +18 -0
- package/lib/use_week_starts_on.ts +17 -0
- package/lib/validate_entry_times.ts +12 -0
- package/lib/week_starts_on_to_index.ts +8 -0
- package/lib/write_active_sheet_preference.ts +28 -0
- package/lib/write_db.ts +20 -0
- package/lib/write_sheet_tag_filter.ts +20 -0
- package/lib/write_stored_compact_lists.ts +15 -0
- package/lib/write_stored_default_sheet_fixed_name.ts +28 -0
- package/lib/write_stored_default_sheet_session_mode.ts +24 -0
- package/lib/write_stored_sheet_tag_filters.ts +18 -0
- package/lib/write_stored_theme.ts +12 -0
- package/next.config.ts +7 -0
- package/package.json +96 -0
- package/pnpm-workspace.yaml +7 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- 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
|
+
}
|