tooltips, two-column timer, font selector, tray behavior, icons, readme
- Custom tooltip directive (WCAG AAA) on every button in the app - Two-column timer layout with sticky hero and recent entries sidebar - Timer font selector with 16 monospace Google Fonts and live preview - UI font selector with 15+ Google Fonts - Close-to-tray and minimize-to-tray settings - New app icons (no-glow variants), platform icon set - Mini timer pop-out window - Favorites strip with drag-reorder and inline actions - README with feature documentation - Remove tracked files that belong in gitignore
This commit is contained in:
+496
-182
@@ -32,85 +32,93 @@
|
||||
|
||||
<!-- Content pane -->
|
||||
<div class="flex-1 p-6 overflow-y-auto">
|
||||
<div class="max-w-2xl">
|
||||
<Transition name="fade" mode="out-in" :duration="{ enter: 200, leave: 150 }">
|
||||
<div :key="activeTab">
|
||||
<!-- General -->
|
||||
<div v-if="activeTab === 'general'" id="tabpanel-general" role="tabpanel" aria-labelledby="tab-general" tabindex="0">
|
||||
<h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">General</h2>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">UI Scale</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Adjust the interface zoom level</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
@click="decreaseZoom"
|
||||
aria-label="Decrease zoom"
|
||||
class="w-8 h-8 flex items-center justify-center border border-border-visible rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
:disabled="zoomLevel <= 80"
|
||||
>
|
||||
<Minus class="w-3.5 h-3.5" aria-hidden="true" />
|
||||
</button>
|
||||
<span class="w-12 text-center text-sm font-mono text-text-primary" role="status" aria-live="polite">{{ zoomLevel }}%</span>
|
||||
<button
|
||||
@click="increaseZoom"
|
||||
aria-label="Increase zoom"
|
||||
class="w-8 h-8 flex items-center justify-center border border-border-visible rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
:disabled="zoomLevel >= 150"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-5">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Locale</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Date and number formatting</p>
|
||||
</div>
|
||||
<div class="w-56">
|
||||
<AppSelect
|
||||
v-model="locale"
|
||||
:options="LOCALES"
|
||||
label-key="name"
|
||||
value-key="code"
|
||||
placeholder="System Default"
|
||||
:placeholder-value="'system'"
|
||||
:searchable="true"
|
||||
@update:model-value="saveLocaleSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-5">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Currency</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Currency symbol and formatting</p>
|
||||
</div>
|
||||
<div class="w-56">
|
||||
<AppSelect
|
||||
v-model="currency"
|
||||
:options="currencyOptions"
|
||||
label-key="name"
|
||||
value-key="code"
|
||||
placeholder="US Dollar"
|
||||
:placeholder-value="'USD'"
|
||||
:searchable="true"
|
||||
@update:model-value="saveLocaleSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle mt-5 pt-5">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Theme</p>
|
||||
<p class="text-[0.8125rem] text-text-primary">UI Scale</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Adjust the interface zoom level</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
v-tooltip="'Decrease zoom'"
|
||||
@click="decreaseZoom"
|
||||
aria-label="Decrease zoom"
|
||||
class="w-8 h-8 flex items-center justify-center border border-border-visible rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
:disabled="zoomLevel <= 80"
|
||||
>
|
||||
<Minus class="w-3.5 h-3.5" aria-hidden="true" />
|
||||
</button>
|
||||
<span class="w-12 text-center text-sm font-mono text-text-primary" role="status" aria-live="polite">{{ zoomLevel }}%</span>
|
||||
<button
|
||||
v-tooltip="'Increase zoom'"
|
||||
@click="increaseZoom"
|
||||
aria-label="Increase zoom"
|
||||
class="w-8 h-8 flex items-center justify-center border border-border-visible rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
:disabled="zoomLevel >= 150"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p id="label-locale" class="text-[0.8125rem] text-text-primary">Locale</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Date and number formatting</p>
|
||||
</div>
|
||||
<div class="w-56">
|
||||
<AppSelect
|
||||
v-model="locale"
|
||||
aria-labelledby="label-locale"
|
||||
:options="LOCALES"
|
||||
label-key="name"
|
||||
value-key="code"
|
||||
placeholder="System Default"
|
||||
:placeholder-value="'system'"
|
||||
:searchable="true"
|
||||
@update:model-value="saveLocaleSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p id="label-currency" class="text-[0.8125rem] text-text-primary">Currency</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Currency symbol and formatting</p>
|
||||
</div>
|
||||
<div class="w-56">
|
||||
<AppSelect
|
||||
v-model="currency"
|
||||
aria-labelledby="label-currency"
|
||||
:options="currencyOptions"
|
||||
label-key="name"
|
||||
value-key="code"
|
||||
placeholder="US Dollar"
|
||||
:placeholder-value="'USD'"
|
||||
:searchable="true"
|
||||
@update:model-value="saveLocaleSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p id="label-theme" class="text-[0.8125rem] text-text-primary">Theme</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Light or dark appearance</p>
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<div class="w-48">
|
||||
<AppSelect
|
||||
v-model="themeMode"
|
||||
aria-labelledby="label-theme"
|
||||
:options="themeModes"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
@@ -121,7 +129,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Accent Color</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Primary interface color</p>
|
||||
@@ -139,17 +147,18 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle mt-5 pt-5">
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Reduce motion</p>
|
||||
<p id="label-reduce-motion" class="text-[0.8125rem] text-text-primary">Reduce motion</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Control animation behavior</p>
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<div class="w-48">
|
||||
<AppSelect
|
||||
v-model="reduceMotion"
|
||||
aria-labelledby="label-reduce-motion"
|
||||
:options="reduceMotionOptions"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
@@ -159,9 +168,9 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle mt-5 pt-5">
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Dyslexia-friendly mode</p>
|
||||
@@ -186,14 +195,15 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">UI Font</p>
|
||||
<p id="label-ui-font" class="text-[0.8125rem] text-text-primary">UI Font</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Choose a font for the interface</p>
|
||||
</div>
|
||||
<div class="w-56">
|
||||
<AppSelect
|
||||
v-model="uiFont"
|
||||
aria-labelledby="label-ui-font"
|
||||
:options="UI_FONTS"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
@@ -215,9 +225,9 @@
|
||||
</AppSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle mt-5 pt-5">
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Sound effects</p>
|
||||
@@ -242,15 +252,16 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="soundEnabled" class="space-y-5 mt-5">
|
||||
<div v-if="soundEnabled" class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Sound mode</p>
|
||||
<p id="label-sound-mode" class="text-[0.8125rem] text-text-primary">Sound mode</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">How sounds are generated</p>
|
||||
</div>
|
||||
<div class="w-48">
|
||||
<AppSelect
|
||||
v-model="soundMode"
|
||||
aria-labelledby="label-sound-mode"
|
||||
:options="soundModes"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
@@ -310,9 +321,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle mt-5 pt-5">
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Getting Started checklist</p>
|
||||
@@ -346,9 +357,61 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle mt-5 pt-5">
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Close to tray</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Hide in system tray instead of quitting when closing the window</p>
|
||||
</div>
|
||||
<button
|
||||
@click="toggleCloseToTray"
|
||||
role="switch"
|
||||
:aria-checked="closeToTray"
|
||||
aria-label="Close to tray"
|
||||
class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
|
||||
:class="[
|
||||
'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
|
||||
closeToTray ? 'bg-status-running' : 'bg-bg-elevated'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
|
||||
closeToTray ? 'translate-x-[18px]' : 'translate-x-[3px]'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Minimize to tray</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Hide in system tray instead of the taskbar when minimizing</p>
|
||||
</div>
|
||||
<button
|
||||
@click="toggleMinimizeToTray"
|
||||
role="switch"
|
||||
:aria-checked="minimizeToTray"
|
||||
aria-label="Minimize to tray"
|
||||
class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
|
||||
:class="[
|
||||
'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
|
||||
minimizeToTray ? 'bg-status-running' : 'bg-bg-elevated'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
|
||||
minimizeToTray ? 'translate-x-[18px]' : 'translate-x-[3px]'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Persistent notifications</p>
|
||||
@@ -380,7 +443,7 @@
|
||||
<div v-if="activeTab === 'timer'" id="tabpanel-timer" role="tabpanel" aria-labelledby="tab-timer" tabindex="0">
|
||||
<h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">Timer</h2>
|
||||
|
||||
<div class="space-y-5">
|
||||
<div class="space-y-4">
|
||||
<!-- Idle Detection toggle -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -407,7 +470,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Idle sub-settings - progressive disclosure -->
|
||||
<div v-if="idleDetection" class="space-y-5 pl-4 border-l-2 border-border-subtle ml-1">
|
||||
<div v-if="idleDetection" class="space-y-4 pl-4 border-l-2 border-border-subtle ml-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Idle Timeout</p>
|
||||
@@ -446,12 +509,13 @@
|
||||
<!-- App Tracking Mode -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">App Tracking Mode</p>
|
||||
<p id="label-app-tracking" class="text-[0.8125rem] text-text-primary">App Tracking Mode</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">What happens when a tracked app leaves focus</p>
|
||||
</div>
|
||||
<div class="w-48">
|
||||
<AppSelect
|
||||
v-model="appTrackingMode"
|
||||
aria-labelledby="label-app-tracking"
|
||||
:options="appTrackingModes"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
@@ -513,12 +577,13 @@
|
||||
<!-- Timer Font -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Timer Font</p>
|
||||
<p id="label-timer-font" class="text-[0.8125rem] text-text-primary">Timer Font</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Monospace font for timer display</p>
|
||||
</div>
|
||||
<div class="w-56">
|
||||
<AppSelect
|
||||
v-model="timerFont"
|
||||
aria-labelledby="label-timer-font"
|
||||
:options="timerFontOptions"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
@@ -623,6 +688,75 @@
|
||||
<!-- Divider -->
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<!-- Reminders -->
|
||||
<h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Reminders</h3>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">End-of-day reminder</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Get a notification to log your time at the end of the day</p>
|
||||
</div>
|
||||
<button
|
||||
@click="toggleEodReminder"
|
||||
role="switch"
|
||||
:aria-checked="eodReminderEnabled"
|
||||
aria-label="End-of-day reminder"
|
||||
class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
|
||||
:class="[
|
||||
'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
|
||||
eodReminderEnabled ? 'bg-status-running' : 'bg-bg-elevated'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
|
||||
eodReminderEnabled ? 'translate-x-[18px]' : 'translate-x-[3px]'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="eodReminderEnabled" class="flex items-center justify-between pl-4 border-l-2 border-border-subtle ml-1">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Reminder time</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">When to show the reminder</p>
|
||||
</div>
|
||||
<input
|
||||
v-model="eodReminderTime"
|
||||
type="time"
|
||||
class="px-3 py-1.5 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
|
||||
@change="saveReminderSettings"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Weekly summary</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Show a summary of last week's hours on Monday morning</p>
|
||||
</div>
|
||||
<button
|
||||
@click="toggleWeeklySummary"
|
||||
role="switch"
|
||||
:aria-checked="weeklySummaryEnabled"
|
||||
aria-label="Weekly summary"
|
||||
class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
|
||||
:class="[
|
||||
'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
|
||||
weeklySummaryEnabled ? 'bg-status-running' : 'bg-bg-elevated'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
|
||||
weeklySummaryEnabled ? 'translate-x-[18px]' : 'translate-x-[3px]'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<!-- Recurring Entries -->
|
||||
<h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Recurring Entries</h3>
|
||||
|
||||
@@ -737,10 +871,9 @@
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Time of Day</label>
|
||||
<input
|
||||
v-model="recTimeOfDay"
|
||||
type="time"
|
||||
class="w-full px-3 py-2 bg-bg-base border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
|
||||
<AppTimePicker
|
||||
v-model:hour="recTimeHour"
|
||||
v-model:minute="recTimeMinute"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -847,89 +980,88 @@
|
||||
<div v-if="activeTab === 'billing'" id="tabpanel-billing" role="tabpanel" aria-labelledby="tab-billing" tabindex="0">
|
||||
<h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">Billing</h2>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Default Hourly Rate</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Applied to new projects</p>
|
||||
</div>
|
||||
<AppNumberInput
|
||||
v-model="hourlyRate"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:precision="2"
|
||||
:prefix="getCurrencySymbol()"
|
||||
@update:model-value="saveSettings"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="border-t border-border-subtle mt-5 pt-5" />
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Time Rounding</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Round time entries for billing</p>
|
||||
</div>
|
||||
<button
|
||||
@click="toggleRounding"
|
||||
role="switch"
|
||||
:aria-checked="roundingEnabled"
|
||||
aria-label="Time rounding"
|
||||
:class="[
|
||||
'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
|
||||
roundingEnabled ? 'bg-status-running' : 'bg-bg-elevated'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
|
||||
roundingEnabled ? 'translate-x-[18px]' : 'translate-x-[3px]'
|
||||
]"
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Default Hourly Rate</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Applied to new projects</p>
|
||||
</div>
|
||||
<AppNumberInput
|
||||
v-model="hourlyRate"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:precision="2"
|
||||
:prefix="getCurrencySymbol()"
|
||||
@update:model-value="saveSettings"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<div v-if="roundingEnabled" class="space-y-5 pl-4 border-l-2 border-border-subtle ml-1 mt-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Increment</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Round to nearest increment</p>
|
||||
<p class="text-[0.8125rem] text-text-primary">Time Rounding</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Round time entries for billing</p>
|
||||
</div>
|
||||
<div class="w-36">
|
||||
<AppSelect
|
||||
v-model="roundingIncrement"
|
||||
:options="roundingIncrements"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
@update:model-value="saveRoundingSettings"
|
||||
<button
|
||||
@click="toggleRounding"
|
||||
role="switch"
|
||||
:aria-checked="roundingEnabled"
|
||||
aria-label="Time rounding"
|
||||
:class="[
|
||||
'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
|
||||
roundingEnabled ? 'bg-status-running' : 'bg-bg-elevated'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
|
||||
roundingEnabled ? 'translate-x-[18px]' : 'translate-x-[3px]'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="roundingEnabled" class="space-y-4 pl-4 border-l-2 border-border-subtle ml-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Increment</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Round to nearest increment</p>
|
||||
</div>
|
||||
<div class="w-48">
|
||||
<AppSelect
|
||||
v-model="roundingIncrement"
|
||||
:options="roundingIncrements"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
@update:model-value="saveRoundingSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Method</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Rounding direction</p>
|
||||
</div>
|
||||
<div class="w-48">
|
||||
<AppSelect
|
||||
v-model="roundingMethod"
|
||||
:options="roundingMethods"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
@update:model-value="saveRoundingSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Method</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Rounding direction</p>
|
||||
</div>
|
||||
<div class="w-36">
|
||||
<AppSelect
|
||||
v-model="roundingMethod"
|
||||
:options="roundingMethods"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
@update:model-value="saveRoundingSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="border-t border-border-subtle mt-5 pt-5" />
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<!-- Business Identity -->
|
||||
<h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium mb-4">Business Identity</h3>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mb-4">This information appears on your invoices.</p>
|
||||
<!-- Business Identity -->
|
||||
<h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Business Identity</h3>
|
||||
<p class="text-[0.6875rem] text-text-tertiary">This information appears on your invoices.</p>
|
||||
|
||||
<div class="space-y-4 max-w-md">
|
||||
<div class="space-y-4 max-w-md">
|
||||
<div>
|
||||
<label for="biz-name" class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Business Name</label>
|
||||
<input
|
||||
@@ -995,7 +1127,7 @@
|
||||
<button
|
||||
v-if="businessLogo"
|
||||
@click="removeLogo"
|
||||
class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-status-error rounded-lg hover:bg-status-error/10 transition-colors"
|
||||
class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-status-error-text rounded-lg hover:bg-status-error/10 transition-colors"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
@@ -1003,6 +1135,7 @@
|
||||
</div>
|
||||
<p class="text-[0.625rem] text-text-tertiary mt-1">PNG or JPG, max 200x80px. Appears on invoice header.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1183,7 +1316,7 @@
|
||||
<div v-if="activeTab === 'data'" id="tabpanel-data" role="tabpanel" aria-labelledby="tab-data" tabindex="0">
|
||||
<h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">Data</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="space-y-4">
|
||||
<!-- Export -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -1235,11 +1368,44 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Backup frequency and retention -->
|
||||
<div v-if="autoBackupEnabled" class="pl-4 border-l-2 border-border-subtle ml-1 space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Frequency</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">How often to run backups</p>
|
||||
</div>
|
||||
<div class="w-36">
|
||||
<AppSelect
|
||||
v-model="backupFrequency"
|
||||
:options="[{id:'daily',name:'Daily'},{id:'weekly',name:'Weekly'}]"
|
||||
label-key="name"
|
||||
value-key="id"
|
||||
@update:model-value="saveBackupFrequency"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Retention</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Keep last N backups</p>
|
||||
</div>
|
||||
<div class="w-28">
|
||||
<AppNumberInput v-model="backupRetention" :min="1" :max="30" @update:model-value="saveBackupRetention" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<!-- Import Data -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-[0.8125rem] font-medium text-text-primary mb-4">Import Data</h3>
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-[0.8125rem] font-medium text-text-primary">Import Data</h3>
|
||||
<button @click="showJsonImportWizard = true" class="px-3 py-1.5 text-[0.6875rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors">
|
||||
Restore from Backup
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-end gap-3 mb-4">
|
||||
<div class="w-48">
|
||||
@@ -1288,31 +1454,47 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="importStatus" class="text-[0.75rem] mt-2" :class="importStatus.startsWith('Error') ? 'text-status-error' : 'text-status-running'">
|
||||
<p v-if="importStatus" class="text-[0.75rem] mt-2" :class="importStatus.startsWith('Error') ? 'text-status-error-text' : 'text-status-running'">
|
||||
{{ importStatus }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Danger Zone -->
|
||||
<div class="mt-8 rounded-xl border border-status-error/20 p-5">
|
||||
<h3 class="text-xs text-status-error uppercase tracking-[0.08em] font-medium mb-4">Danger Zone</h3>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Clear All Data</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Permanently delete all entries, projects, clients, and invoices</p>
|
||||
<div class="rounded-xl border border-status-error/20 p-5">
|
||||
<h3 class="text-xs text-status-error-text uppercase tracking-[0.08em] font-medium mb-4">Danger Zone</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Load Sample Data</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Populate with demo data for screenshots (clears existing data first)</p>
|
||||
</div>
|
||||
<button
|
||||
@click="showSeedDialog = true"
|
||||
:disabled="isSeedingData"
|
||||
class="px-4 py-1.5 border border-status-warning text-status-warning text-[0.8125rem] font-medium rounded-lg hover:bg-status-warning/10 transition-colors duration-150 disabled:opacity-40"
|
||||
>
|
||||
{{ isSeedingData ? 'Loading...' : 'Load Demo' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Clear All Data</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Permanently delete all entries, projects, clients, and invoices</p>
|
||||
</div>
|
||||
<button
|
||||
@click="showClearDataDialog = true"
|
||||
class="px-4 py-1.5 border border-status-error text-status-error-text text-[0.8125rem] font-medium rounded-lg hover:bg-status-error/10 transition-colors duration-150"
|
||||
>
|
||||
Clear Data
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@click="showClearDataDialog = true"
|
||||
class="px-4 py-1.5 border border-status-error text-status-error text-[0.8125rem] font-medium rounded-lg hover:bg-status-error/10 transition-colors duration-150"
|
||||
>
|
||||
Clear Data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Clear Data Confirmation Dialog -->
|
||||
@@ -1327,7 +1509,7 @@
|
||||
<p id="clear-data-desc" class="text-[0.75rem] text-text-secondary mb-4">
|
||||
Are you sure? This action cannot be undone.
|
||||
</p>
|
||||
<p class="text-[0.6875rem] text-status-error mb-6">
|
||||
<p class="text-[0.6875rem] text-status-error-text mb-6">
|
||||
All time entries, projects, clients, and invoices will be permanently deleted.
|
||||
</p>
|
||||
<div class="flex justify-end gap-3">
|
||||
@@ -1347,6 +1529,45 @@
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Seed Data Confirmation Dialog -->
|
||||
<Transition name="modal">
|
||||
<div
|
||||
v-if="showSeedDialog"
|
||||
class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
|
||||
@click.self="showSeedDialog = false"
|
||||
>
|
||||
<div role="alertdialog" aria-modal="true" aria-labelledby="seed-data-title" aria-describedby="seed-data-desc" class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-sm p-6">
|
||||
<h2 id="seed-data-title" class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-2">Load Sample Data</h2>
|
||||
<p id="seed-data-desc" class="text-[0.75rem] text-text-secondary mb-4">
|
||||
This will clear all existing data and replace it with demo content.
|
||||
</p>
|
||||
<p class="text-[0.6875rem] text-status-warning mb-6">
|
||||
All current entries, projects, clients, and invoices will be replaced.
|
||||
</p>
|
||||
<div class="flex justify-end gap-3">
|
||||
<button
|
||||
@click="showSeedDialog = false"
|
||||
class="px-4 py-2 border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="seedSampleData"
|
||||
class="px-4 py-2 bg-status-warning text-white font-medium rounded-lg hover:bg-status-warning/80 transition-colors duration-150"
|
||||
>
|
||||
Load Demo Data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<JsonImportWizard
|
||||
:show="showJsonImportWizard"
|
||||
@close="showJsonImportWizard = false"
|
||||
@imported="showJsonImportWizard = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1363,13 +1584,16 @@ import { useEntryTemplatesStore } from '../stores/entryTemplates'
|
||||
import AppNumberInput from '../components/AppNumberInput.vue'
|
||||
import AppSelect from '../components/AppSelect.vue'
|
||||
import AppShortcutRecorder from '../components/AppShortcutRecorder.vue'
|
||||
import AppTimePicker from '../components/AppTimePicker.vue'
|
||||
import { LOCALES, getCurrencies, getCurrencySymbol } from '../utils/locale'
|
||||
import { parseCSV, mapTogglCSV, mapGenericCSV, type ImportEntry } from '../utils/import'
|
||||
import { TIMER_FONTS, loadGoogleFont, loadAndApplyTimerFont } from '../utils/fonts'
|
||||
import { UI_FONTS, loadUIFont } from '../utils/uiFonts'
|
||||
import { useFocusTrap } from '../utils/focusTrap'
|
||||
import JsonImportWizard from '../components/JsonImportWizard.vue'
|
||||
import { audioEngine, SOUND_EVENTS, DEFAULT_EVENTS } from '../utils/audio'
|
||||
import type { SoundEvent } from '../utils/audio'
|
||||
import { resetPositionCache } from '../utils/dropdown'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const toastStore = useToastStore()
|
||||
@@ -1389,6 +1613,8 @@ const recPattern = ref('daily')
|
||||
const recWeeklyDays = ref<string[]>([])
|
||||
const recMonthlyDay = ref(1)
|
||||
const recTimeOfDay = ref('09:00')
|
||||
const recTimeHour = ref(9)
|
||||
const recTimeMinute = ref(0)
|
||||
const recMode = ref('prompt')
|
||||
const recProjects = ref<Array<{ id: number; name: string; color: string }>>([])
|
||||
const recTasks = ref<Array<{ id: number; name: string }>>([])
|
||||
@@ -1473,6 +1699,10 @@ const reduceMotion = ref('system')
|
||||
const dyslexiaMode = ref(false)
|
||||
const uiFont = ref('Inter')
|
||||
|
||||
// Tray behavior settings
|
||||
const closeToTray = ref(false)
|
||||
const minimizeToTray = ref(false)
|
||||
|
||||
// Notification settings
|
||||
const persistentNotifications = ref(false)
|
||||
|
||||
@@ -1493,6 +1723,27 @@ const soundModes = [
|
||||
const dailyGoalHours = ref(8)
|
||||
const weeklyGoalHours = ref(40)
|
||||
|
||||
// Reminder settings
|
||||
const eodReminderEnabled = ref(false)
|
||||
const eodReminderTime = ref('17:00')
|
||||
const weeklySummaryEnabled = ref(false)
|
||||
|
||||
async function toggleEodReminder() {
|
||||
eodReminderEnabled.value = !eodReminderEnabled.value
|
||||
await saveReminderSettings()
|
||||
}
|
||||
|
||||
async function toggleWeeklySummary() {
|
||||
weeklySummaryEnabled.value = !weeklySummaryEnabled.value
|
||||
await saveReminderSettings()
|
||||
}
|
||||
|
||||
async function saveReminderSettings() {
|
||||
await settingsStore.updateSetting('eod_reminder_enabled', eodReminderEnabled.value ? 'true' : 'false')
|
||||
await settingsStore.updateSetting('eod_reminder_time', eodReminderTime.value)
|
||||
await settingsStore.updateSetting('weekly_summary_enabled', weeklySummaryEnabled.value ? 'true' : 'false')
|
||||
}
|
||||
|
||||
// Rounding settings
|
||||
const roundingEnabled = ref(false)
|
||||
const roundingIncrement = ref(15)
|
||||
@@ -1714,6 +1965,9 @@ const showClearDataDialog = ref(false)
|
||||
const clearDialogRef = ref<HTMLElement | null>(null)
|
||||
const { activate: activateClearTrap, deactivate: deactivateClearTrap } = useFocusTrap()
|
||||
|
||||
const showSeedDialog = ref(false)
|
||||
const isSeedingData = ref(false)
|
||||
|
||||
watch(showClearDataDialog, (val) => {
|
||||
if (val) {
|
||||
setTimeout(() => {
|
||||
@@ -1724,10 +1978,23 @@ watch(showClearDataDialog, (val) => {
|
||||
}
|
||||
})
|
||||
|
||||
// JSON import wizard
|
||||
const showJsonImportWizard = ref(false)
|
||||
|
||||
// Auto-backup state
|
||||
const autoBackupEnabled = ref(false)
|
||||
const backupPath = ref('')
|
||||
const lastExported = ref('')
|
||||
const backupFrequency = ref('daily')
|
||||
const backupRetention = ref(7)
|
||||
|
||||
async function saveBackupFrequency(val: string) {
|
||||
await settingsStore.updateSetting('auto_backup_frequency', val)
|
||||
}
|
||||
|
||||
async function saveBackupRetention(val: number) {
|
||||
await settingsStore.updateSetting('auto_backup_retention', String(val))
|
||||
}
|
||||
|
||||
const lastExportedFormatted = computed(() => {
|
||||
if (!lastExported.value) return ''
|
||||
@@ -1797,6 +2064,7 @@ function applyZoom() {
|
||||
if (app) {
|
||||
(app.style as any).zoom = `${zoomLevel.value}%`
|
||||
}
|
||||
resetPositionCache()
|
||||
settingsStore.updateSetting('ui_zoom', zoomLevel.value.toString())
|
||||
}
|
||||
|
||||
@@ -1881,6 +2149,18 @@ async function togglePersistentNotifications() {
|
||||
await settingsStore.updateSetting('persistent_notifications', persistentNotifications.value ? 'true' : 'false')
|
||||
}
|
||||
|
||||
// Toggle close to tray
|
||||
async function toggleCloseToTray() {
|
||||
closeToTray.value = !closeToTray.value
|
||||
await settingsStore.updateSetting('close_to_tray', closeToTray.value ? 'true' : 'false')
|
||||
}
|
||||
|
||||
// Toggle minimize to tray
|
||||
async function toggleMinimizeToTray() {
|
||||
minimizeToTray.value = !minimizeToTray.value
|
||||
await settingsStore.updateSetting('minimize_to_tray', minimizeToTray.value ? 'true' : 'false')
|
||||
}
|
||||
|
||||
// Save sound settings
|
||||
async function saveSoundSettings() {
|
||||
await settingsStore.updateSetting('sound_mode', soundMode.value)
|
||||
@@ -1904,6 +2184,11 @@ async function saveGoalSettings() {
|
||||
await settingsStore.updateSetting('weekly_goal_hours', weeklyGoalHours.value.toString())
|
||||
}
|
||||
|
||||
// Sync time picker hour/minute with recTimeOfDay string
|
||||
watch([recTimeHour, recTimeMinute], ([h, m]) => {
|
||||
recTimeOfDay.value = String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0')
|
||||
})
|
||||
|
||||
// Watch recurring project to fetch tasks
|
||||
watch(recProjectId, async (pid) => {
|
||||
if (pid) {
|
||||
@@ -1993,6 +2278,8 @@ function resetRecurringForm() {
|
||||
recWeeklyDays.value = []
|
||||
recMonthlyDay.value = 1
|
||||
recTimeOfDay.value = '09:00'
|
||||
recTimeHour.value = 9
|
||||
recTimeMinute.value = 0
|
||||
recMode.value = 'prompt'
|
||||
}
|
||||
|
||||
@@ -2003,6 +2290,9 @@ function editRecurringEntry(rec: RecurringEntry) {
|
||||
recDescription.value = rec.description || ''
|
||||
recDurationMinutes.value = Math.round(rec.duration / 60)
|
||||
recTimeOfDay.value = rec.time_of_day
|
||||
const [h, m] = rec.time_of_day.split(':').map(Number)
|
||||
recTimeHour.value = h || 0
|
||||
recTimeMinute.value = m || 0
|
||||
recMode.value = rec.mode
|
||||
|
||||
// Parse recurrence rule
|
||||
@@ -2192,6 +2482,21 @@ async function clearAllData() {
|
||||
}
|
||||
}
|
||||
|
||||
async function seedSampleData() {
|
||||
isSeedingData.value = true
|
||||
showSeedDialog.value = false
|
||||
try {
|
||||
await invoke('seed_sample_data')
|
||||
toastStore.success('Sample data loaded successfully')
|
||||
setTimeout(() => { window.location.reload() }, 500)
|
||||
} catch (error) {
|
||||
console.error('Failed to load sample data:', error)
|
||||
toastStore.error('Failed to load sample data')
|
||||
} finally {
|
||||
isSeedingData.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Load settings on mount
|
||||
onMounted(async () => {
|
||||
await settingsStore.fetchSettings()
|
||||
@@ -2212,6 +2517,9 @@ onMounted(async () => {
|
||||
shortcutQuickEntry.value = settingsStore.settings.shortcut_quick_entry || 'CmdOrCtrl+Shift+N'
|
||||
dailyGoalHours.value = parseFloat(settingsStore.settings.daily_goal_hours) || 8
|
||||
weeklyGoalHours.value = parseFloat(settingsStore.settings.weekly_goal_hours) || 40
|
||||
eodReminderEnabled.value = settingsStore.settings.eod_reminder_enabled === 'true'
|
||||
eodReminderTime.value = settingsStore.settings.eod_reminder_time || '17:00'
|
||||
weeklySummaryEnabled.value = settingsStore.settings.weekly_summary_enabled === 'true'
|
||||
roundingEnabled.value = settingsStore.settings.rounding_enabled === 'true'
|
||||
roundingIncrement.value = parseInt(settingsStore.settings.rounding_increment) || 15
|
||||
roundingMethod.value = settingsStore.settings.rounding_method || 'nearest'
|
||||
@@ -2226,6 +2534,10 @@ onMounted(async () => {
|
||||
dyslexiaMode.value = settingsStore.settings.dyslexia_mode === 'true'
|
||||
uiFont.value = settingsStore.settings.ui_font || 'Inter'
|
||||
|
||||
// Tray behavior settings
|
||||
closeToTray.value = settingsStore.settings.close_to_tray === 'true'
|
||||
minimizeToTray.value = settingsStore.settings.minimize_to_tray === 'true'
|
||||
|
||||
// Notification settings
|
||||
persistentNotifications.value = settingsStore.settings.persistent_notifications === 'true'
|
||||
|
||||
@@ -2233,6 +2545,8 @@ onMounted(async () => {
|
||||
autoBackupEnabled.value = settingsStore.settings.auto_backup === 'true'
|
||||
backupPath.value = settingsStore.settings.backup_path || ''
|
||||
lastExported.value = settingsStore.settings.last_exported || ''
|
||||
backupFrequency.value = settingsStore.settings.auto_backup_frequency || 'daily'
|
||||
backupRetention.value = parseInt(settingsStore.settings.auto_backup_retention || '7')
|
||||
|
||||
// Sound settings
|
||||
soundEnabled.value = settingsStore.settings.sound_enabled === 'true'
|
||||
|
||||
Reference in New Issue
Block a user