improvement: rtl direction, solar calendar and persian formatting added for persian users.

This commit is contained in:
2026-07-14 01:27:11 +03:30
parent 27eae25f61
commit ec2b23f4b1
50 changed files with 1410 additions and 528 deletions

View File

@@ -24,6 +24,8 @@ Monorepo: `backend/` (NestJS + Prisma), `frontend/` (Next.js + next-intl), `infr
All user-visible strings: `frontend/messages/en.json`, `fa.json`, `nl.json` — add keys to **all three**.
Dates/times/numbers: `frontend/src/lib/i18n/format.ts` + `useLocale()`. **Form date fields:** `AppDateInput` only (no native `<input type="date">`); wire format `YYYY-MM-DD`. **Filter selects:** `FORM_SELECT_CLASS` from `components/shared/formSelectStyles.ts` (chevron via `globals.css`). **Tables:** `components/ui/shared/Table.tsx` — use `text-start`/`text-end`/`text-center`, never physical `text-left`/`text-right`. Appointments: `ScheduleDayPicker`. Skill: `.cursor/skills/i18n-formatting/SKILL.md`.
## Treatment / appointment colors
Treatment-type colors and labels: `components/shared/treatmentTypeDisplay.ts` + `catalog-type-colors.ts`. UI badges: `components/ui/treatment/TreatmentTypeBadge.tsx`.

View File

@@ -51,3 +51,14 @@ Reference: `app/.../treatment/page.tsx` + `components/ui/treatment/TreatmentWork
1. Check `components/ui/shared/` for an existing primitive.
2. Check the feature's `ui/{feature}/` folder for an existing pattern.
3. Add i18n keys to en, fa, and nl.
## Shared form & table primitives
| Need | Use |
|------|-----|
| Date filter / due date field | `AppDateInput` (`ui/shared/`) — not `<input type="date">` |
| Filter or inline `<select>` | `FORM_SELECT_CLASS` from `components/shared/formSelectStyles.ts` |
| Tiny select (sort dir, etc.) | `FORM_SELECT_COMPACT_CLASS` |
| Desktop data table | `Table` (`ui/shared/Table.tsx`) — logical alignment only |
Styles for `.form-select` chevrons live in `styles/globals.css`. Do not duplicate chevron icons on raw selects.

View File

@@ -0,0 +1,80 @@
# i18n formatting (dates, times, numbers)
Use when adding or changing user-visible dates/times/numbers, RTL layout, or locale-specific pickers.
## Display formatting
- Module: `frontend/src/lib/i18n/format.ts`
- Hook: `frontend/src/lib/hooks/useAppFormatters.ts` (`useLocale()` inside)
- **Never** use raw `toLocaleDateString` / `toLocaleTimeString` / bare `Intl.DateTimeFormat(undefined, …)` in UI.
| Helper | Use for |
|--------|---------|
| `formatAppDate` | Date-only labels |
| `formatAppTime` | Time-only labels |
| `formatAppDateTime` | Combined stamp |
| `formatAppTimeRange` | Appointment ranges |
| `formatAppTableDate` | Table cells |
| `formatAppNumber` | Counts, amounts (grouped) |
| `formatAppInteger` | Calendar year/day — no comma grouping |
| `formatAppPickerDateLabel` | Calendar trigger button label |
| `APP_DATE.*` | Shared preset option objects |
## Form controls (dates & selects)
| Piece | Location / use |
|-------|----------------|
| **`AppDateInput`** | All form/filter date fields — **every locale**; masked typing + calendar popup; wire value `YYYY-MM-DD` or empty |
| **`FORM_DATE_INPUT_CLASS`** | Visual shell for date input (same padding/inset as selects; no CSS chevron) |
| **`FORM_SELECT_CLASS`** | Native `<select>` filters/fields — `ps-3 pe-10`, chevron from `globals.css` |
| **`FORM_SELECT_COMPACT_CLASS`** | Tiny selects (e.g. sort direction `↓`/`↑`) — symmetric `px-2`, no chevron gutter |
| **`Dropdown`** | Labeled form select — only where already used; prefer `FORM_SELECT_CLASS` for new filters |
| **`CompactSelect`** | Year/month/day sub-selects inside calendar panels |
**`AppDateInput` behavior**
- `fa`: Jalali display `YYYY/MM/DD` (Persian digits), parse/mask in `persianCalendar.ts`
- `en` / `nl`: Gregorian display `YYYY-MM-DD`, parse/mask in `dateInputFormat.ts`
- Calendar icon at **`end-3`** (matches select chevron inset); text uses **`text-start`** (logical — right in RTL, left in LTR)
- Popup: **`CalendarDayPartsPanel`** (shared with schedule picker)
- **Do not** add native `<input type="date">` — one component for all locales
**Select chevron**
- Defined once in `frontend/src/styles/globals.css` on `.form-select:not(.form-select-no-chevron)`
- RTL: `background-position: left 0.75rem center`; LTR: `right 0.75rem center`
- `text-align: start` on selects
## Calendar / appointment pickers
| Component | Use for |
|-----------|---------|
| `ScheduleDayPicker` | Appointments strip — nav arrows + today toggle + expandable panel |
| `CalendarDaySelect` | Navigator wrapper (arrows + panel) |
| `CalendarDayPartsPanel` | Year / month / day row — used by schedule picker and `AppDateInput` |
Persian (`fa`): Jalali calendar + `arabext` digits via Intl (`usesPersianCalendar`). Internal model stays **`Date` at local midnight** (Gregorian) — APIs unchanged.
- Conversion: `frontend/src/lib/i18n/persianCalendar.ts`
- Gregorian typing: `frontend/src/lib/i18n/dateInputFormat.ts`
- Gregorian month labels: `schedule.monthJanuary` … message keys
## RTL
- `dir` / `lang` on `<html>` from `app/[locale]/layout.tsx`
- `isRtlLocale` in `frontend/src/i18n/routing.ts`
- Use **logical** CSS: `text-start`, `text-end`, `ps-*`, `pe-*`, `ms-*`, `me-*`
- **`Table`**: default `[&_th]:text-start [&_td]:text-start`; override with `text-center` or `text-end` on cells — **never** `text-left` / `text-right` on headers (causes header/body column drift in RTL)
- Minimal overrides in `globals.css` — avoid double-mirroring (no extra `row-reverse` on shells that already inherit `direction: rtl`)
## i18n strings
- User-facing copy: `frontend/messages/{en,fa,nl}.json` — all three locales.
## Verify
```bash
cd frontend && npx tsc --noEmit
```
Manual: switch to Persian — Cases date filters, Tasks filter row (single line + sort visible), Staff/Orgs table columns aligned; switch to English — date fields match adjacent dropdown alignment.