123 lines
5.5 KiB
Markdown
123 lines
5.5 KiB
Markdown
# 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:
|
|
|
|
```typescript
|
|
// 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) }}/hr` display
|
|
- **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 `AppDatePicker` for the date portion
|
|
- Two small styled `<input>` fields for hour (0-23) and minute (0-59), separated by a `:` label
|
|
- Parse `editForm.start_time` into 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` / `Plus` buttons 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:
|
|
1. Immediately fire one increment/decrement
|
|
2. After 400ms delay, start repeating every 80ms
|
|
3. On `mouseup` or `mouseleave`, clear all timers
|
|
4. Also handle `touchstart`/`touchend` for 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).
|