From 7877dc0631695a2227fa278fcb8dd307b5491ae7 Mon Sep 17 00:00:00 2001 From: Achim Rohn Date: Sat, 16 May 2026 11:57:46 +0200 Subject: [PATCH] docs: update README with features and design notes; add NEXT_STEPS README now covers features list, run instructions, design rationale (integer money/duration, read-time cost resolution, soft budget enforcement, Flyway-only schema, API shape). NEXT_STEPS.md catalogs deferred work: user CRUD UI, rate snapshots, notes field, CSV export, auth, pagination, Playwright e2e, i18n, dark mode, bookmarkable URLs, full backend test pyramid. Co-Authored-By: Claude Opus 4.7 --- NEXT_STEPS.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 37 ++++++++++++++++++-- 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 NEXT_STEPS.md diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md new file mode 100644 index 0000000..f5ccc30 --- /dev/null +++ b/NEXT_STEPS.md @@ -0,0 +1,95 @@ +# Next Steps + +Items that were out of scope for the initial 6-hour build, with notes on how to approach each. + +## Features + +### User management UI +Currently, users are seeded via Flyway (`V2__seed_demo_data.sql`). A `POST /api/users` endpoint +with a "New User" form in the frontend would complete the CRUD cycle. Straightforward to add +following the same pattern as project creation. + +### Edit existing time entry (project-create form was shipped; entry edit is partially done) +The backend `PUT /api/time-entries/{id}` endpoint exists. The frontend `TimeEntryForm` handles +editing when `editEntry` is passed. Wire up the Edit button per entry row in `WeeklyView` — +already scaffolded, just needs end-to-end smoke-testing. + +### Notes / description field on time entries +Add a `TEXT` column to `time_entries` (new Flyway migration), expose it in the DTOs, and add a +textarea to `TimeEntryForm`. Straightforward schema change. + +### Rate history — audit-stable costs +Currently, cost is computed at read time from the current project/user hourly rate. If a rate +changes, historical costs change retroactively. To fix: add `hourly_rate_cents_snapshot INTEGER NOT NULL` +to `time_entries`, resolve and snapshot the rate in `TimeEntryService.create()`. Historical queries +then use the snapshot instead of joining to current rates. + +### CSV export of reports +Add a `GET /api/reports/weekly.csv?userId=&week=` and `/monthly.csv` endpoint that streams +`text/csv`. Frontend adds a "Download CSV" button that opens the URL in a new tab. + +## Technical quality + +### Full backend test pyramid +The current happy-path tests cover the repository date-range query, service cost/budget logic, and +one end-to-end MockMvc flow. Gaps to fill: +- `@WebMvcTest` controller-slice tests for validation error responses (400 on invalid fields). +- More `@DataJpaTest` cases (zero entries, multiple users, rate fallback). +- `@SpringBootTest` with Testcontainers so tests do not depend on an externally-running PostgreSQL. + +### Frontend end-to-end tests (Playwright) +A Playwright suite covering the golden paths: +1. Log a time entry → verify it appears in the weekly view. +2. Create a project → verify it appears in the project list. +3. Exceed a project budget → verify the budget banner appears. + +### Pagination of time entries +`GET /api/time-entries` returns all entries in a date range. For large datasets, add Spring Data +pagination (`Pageable` parameter) and a "Load more" button or cursor in the frontend. + +## UX improvements + +### Delete confirmation modal +Currently `confirm()` is used for delete. Replace with a small Tailwind dialog / Headless UI +`Dialog` for a consistent UX. + +### Bookmarkable week/month URLs +Weekly and monthly views use component state for the selected period. Moving the period to the +URL (e.g., `/weekly/2026-W20`, `/monthly/2026-05`) enables bookmarking and browser back/forward. +React Router v7 `useParams` + `useNavigate` is the implementation path. + +### Validation error UI polish +Form errors are shown as a banner above the form. Inline per-field errors +(below each input) would be more discoverable. + +## Architecture + +### Authentication & per-user data isolation +No auth in the current build — user is selected via a dropdown. For production, add Spring +Security with JWT or session-based auth. The `user_id` on `time_entries` already supports +per-user isolation; the API just needs to enforce it from the authenticated principal. + +### Per-project currency +Add `currency CHAR(3) NOT NULL DEFAULT 'EUR'` to `projects`. The aggregation queries then need +currency-aware math; cross-currency totals on the monthly report would require a conversion rate +or report per-currency totals separately. + +### Time zone awareness +`time_entries.entry_date` is a `LocalDate` (no zone). If users span time zones, "today" must +be resolved from the user's local zone rather than the server's. Pass a `timeZone` parameter or +derive it from the authenticated user's profile. + +### Dark mode +Tailwind v4 supports `prefers-color-scheme` media queries via the `dark:` variant. Add +`@media (prefers-color-scheme: dark)` styles or a toggle stored in `localStorage`. + +### i18n (de/en) +Hardcoded strings are in English; currency formatting uses German locale (`de-DE`). Introduce +`react-intl` or a similar library and extract string constants. Language preference lives in user +settings or `Accept-Language`. + +### Performance +- N+1 in `ProjectService.findAll()`: one aggregate query for all projects avoids it (already done + via `aggregateAllByProject`). No further action needed at small scale. +- Large datasets: consider materialized views or a dedicated reporting aggregate table updated + via a database trigger or scheduled job. diff --git a/README.md b/README.md index 1288a3c..26a2f35 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ -# App +# Time Tracker -React 19 + Spring Boot 3.4 web app backed by PostgreSQL 17. +A browser-based time-tracking app: log daily work against projects, review totals in weekly and +monthly views, and track project budgets with automatic cost calculation and soft breach warnings. + +**Stack:** React 19 + Vite 6 (TypeScript strict) — Spring Boot 3.4 (Java 21) — PostgreSQL 17 via +Docker Compose — Flyway for schema migrations — Tailwind CSS v4 — Biome 1.9 — Vitest. + +## Features + +- **Daily time entries** — date, project, duration (entered as h:mm), automatic cost from hourly rate +- **Weekly view** — navigate by ISO week; see total hours per day and a running week total +- **Monthly view** — per-project summary with time and cost progress bars; red banner when a budget is exceeded +- **Project management** — create projects with time and cost budgets; set a project-level hourly rate or fall back to the user's default +- **Multi-user** — pick any seeded user from the dropdown; cost is calculated using that user's rate when no project rate is set +- **Soft budget enforcement** — entries always save; over-budget projects are highlighted with a warning banner and colored bars ## Quick start @@ -43,6 +56,26 @@ mvn -f backend/pom.xml test -q cd frontend && npx vitest run ``` +## Design notes + +**Money and duration** are stored as integers (cents and minutes respectively) to avoid floating-point +drift. Cost for a time entry is computed at read time: `duration_minutes × effective_rate_cents / 60`, +where the effective rate is the project's `hourly_rate_cents` if set, otherwise the user's +`default_hourly_rate_cents`. This means changing a rate retroactively updates all historical cost +totals — a deliberate trade-off for simplicity (see `NEXT_STEPS.md` for a snapshot-based alternative). + +**Budget warnings** are soft: any entry saves regardless of budget status. Breach is detected in the +reporting layer by comparing aggregated totals to the project's `time_budget_minutes` and +`cost_budget_cents`. The monthly view renders a red warning banner and saturated progress bars for +any over-budget project. + +**Schema migrations** are managed exclusively by Flyway (`V1__init.sql` for schema, `V2__seed_demo_data.sql` +for demo users and projects). Hibernate is set to `validate` — it never modifies the schema. + +**API shape** — aggregation is done server-side via two dedicated endpoints: +`GET /api/reports/weekly?userId=&week=YYYY-Www` and `GET /api/reports/monthly?userId=&month=YYYY-MM`. +All other CRUD endpoints follow standard REST conventions under `/api/`. + ## Environment `.env.example` documents all env vars. `mise run setup` copies it to `.env` automatically on first run.