5.5 KiB
UI Improvements Batch Design
Goal: Five improvements — locale-aware formatting (worldwide), custom datetime picker, modal margin fix, default hourly rate bug fix, and custom number input component.
1. Locale-Aware Formatting (Worldwide)
New Settings (General tab)
Locale — searchable dropdown with ALL locales the browser supports. Use Intl.supportedValuesOf('collation') pattern to enumerate, or provide a hardcoded comprehensive list of ~100+ BCP 47 locale tags with display names. Group by region. Include a "System Default" option that reads navigator.language. Stored as locale setting.
Currency — searchable dropdown with ALL ISO 4217 currency codes. Use Intl.supportedValuesOf('currency') to get the full list (~160 currencies). Display as "USD — US Dollar", "EUR — Euro", etc. using Intl.DisplayNames for native names. Stored as currency setting. Default: detect from locale or fall back to USD.
Searchable AppSelect
The existing AppSelect dropdown can't handle 160+ items. Add a search/filter input at the top of the dropdown panel. When the user types, filter options by label match. This is a modification to AppSelect.vue — add an optional searchable prop.
Locale Helpers
Create src/utils/locale.ts with formatting functions that read from the settings store:
// Uses Intl APIs with the user's chosen locale + currency
formatDate(dateString: string): string // "Feb 17, 2026" or "17 Feb 2026" etc.
formatDateTime(dateString: string): string // includes time
formatCurrency(amount: number): string // "$50.00" or "50,00 €" etc.
formatNumber(amount: number, decimals?): string // "1,234.56" or "1.234,56"
Files to Update (replace hardcoded en-US and $)
- Entries.vue —
formatDate()function (line ~219) - Invoices.vue —
formatDate()function, all${{ }}currency displays (~8 occurrences) - Reports.vue —
${{ }}currency in summary stats and breakdown (~4 occurrences) - Timer.vue —
formatDate()function - Projects.vue —
${{ project.hourly_rate.toFixed(2) }}/hrdisplay - Dashboard.vue — any date/currency displays
- AppDatePicker.vue — month/day labels use
en-US, should use locale
2. Custom DateTime Picker for Edit Entry
Replace <input type="datetime-local"> in Entries.vue edit dialog with a composite layout:
[AppDatePicker for date] [HH] : [MM]
- Reuse existing
AppDatePickerfor the date portion - Two small styled
<input>fields for hour (0-23) and minute (0-59), separated by a:label - Parse
editForm.start_timeinto separate date and time parts on dialog open - Reconstruct ISO string on submit
No new component needed — just inline the date picker + two inputs in Entries.vue.
3. Modal Viewport Margin
Problem: Clients.vue dialog has max-h-[85vh] but no guaranteed margin from viewport edges when window is very small.
Fix: On ALL modal dialogs across the app, ensure the overlay container uses p-4 (16px padding on all sides) so the modal can never touch the viewport edge. Change from flex items-center justify-center to flex items-center justify-center p-4 on the overlay div. The modal itself keeps max-h-[calc(100vh-2rem)] with overflow-y-auto.
Files: Clients.vue, Projects.vue, Entries.vue (2 dialogs), Invoices.vue (2 dialogs), Settings.vue (1 dialog) — every fixed inset-0 modal overlay.
4. Default Hourly Rate Bug Fix
Problem: Projects.vue openCreateDialog() sets formData.hourly_rate = 0 instead of reading the default from settings.
Fix: Import useSettingsStore, read parseFloat(settingsStore.settings.hourly_rate) || 0 in openCreateDialog(). Also need to ensure settings are fetched on mount (add to the Promise.all).
5. AppNumberInput Component
Design
A reusable src/components/AppNumberInput.vue:
[ - ] [ value display ] [ + ]
Minus/Plusbuttons styled like the UI Scale buttons in Settings (bordered, rounded-lg, icon)- Center shows formatted value with optional prefix/suffix
- Direct text editing: clicking the value makes it editable (input field)
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue |
number |
required | v-model binding |
min |
number |
0 |
Minimum value |
max |
number |
Infinity |
Maximum value |
step |
number |
1 |
Increment/decrement amount |
precision |
number |
0 |
Decimal places for display |
prefix |
string |
'' |
Shown before value (e.g. "$") |
suffix |
string |
'' |
Shown after value (e.g. "%", "min") |
Press-and-Hold Behavior
On mousedown of +/- button:
- Immediately fire one increment/decrement
- After 400ms delay, start repeating every 80ms
- On
mouseupormouseleave, clear all timers - Also handle
touchstart/touchendfor touch devices
Locations to Replace
| View | Field | Props |
|---|---|---|
| Settings.vue | Default Hourly Rate | prefix="$" step=1 min=0 precision=2 |
| Projects.vue | Hourly Rate (create/edit) | prefix="$" step=1 min=0 precision=2 |
| Entries.vue | Duration (edit dialog) | suffix="min" step=1 min=1 |
| Invoices.vue | Tax Rate (create form) | suffix="%" step=0.5 min=0 max=100 precision=2 |
| Invoices.vue | Discount (create form) | prefix="$" step=1 min=0 precision=2 |
NOT replaced: Reminder Interval in Settings (stays as plain input per user request).