Skip to content

feat: CSV shift import with preview and confirmation, weekly CSV export - #7

Merged
kris7011 merged 3 commits into
mainfrom
feature/csv-import-export
Nov 1, 2026
Merged

kris7011 merged 3 commits into
mainfrom
feature/csv-import-export

Conversation

@kris7011

@kris7011 kris7011 commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Purpose

Let a planner import a week of shifts from a spreadsheet safely: preview every row before anything is saved, see row-level problems, confirm explicitly, and export a week back to CSV. Previously listed under "Future Improvements" as "CSV import/export demo".

Implementation

API

  • POST /api/shift-imports/preview (raw CSV body): parses, validates every row, stores a pending import, creates no shifts. Unreadable files → 400 with fileErrors.
  • POST /api/shift-imports/{id}/confirm: re-validates the stored file against the current schedule; if still valid, inserts all shifts and marks the import confirmed in one transaction. 409 if already confirmed or no longer valid (with a fresh preview), 404 if unknown.
  • GET /api/schedule/export?weekStart=: the Monday–Sunday week in the import format (round-trips).

Validation (ShiftImportValidator)

  • Field checks (date, shift type, skill), formula-injection guard, employee matching by name (unknown/ambiguous/lacks skill).
  • Reuses the registered ISchedulingRules (max assignments, same day, night→day, high load), with the saved schedule plus earlier file rows as planned shifts, just as schedule generation does.
  • Duplicates: same assignment twice in the file → error pointing to the first row; already in schedule → error; repeated open shifts → warning (that is how several employees are requested); same file content already imported → blocked.

Consistency

  • One-time confirmation: conditional UPDATE … WHERE Status = 'Pending' inside the insert transaction.
  • One import per file content: filtered unique index on ContentHash where confirmed.
  • AddShiftImports migration is additive. Demo reset also clears import history.

Frontend: new "Import og eksport" page (Danish), preview table with errors and warnings, confirm button enabled only when valid, export link; rows stack as cards at phone width.

Existing scheduling rules, endpoints and tests are unchanged. The only refactors: ShiftEntity.FromDomain (shared mapping) and a shared test helper for the rule list.

Testing

  • dotnet test: 101 unit (38 new) and 46 integration (17 new) passed.
  • Frontend: npm test 13 (5 new), npm run lint clean, npm run build OK.
  • Mutation checks: removing the Pending condition, skipping re-validation, or removing the file-input reset each makes a test fail.
  • Real local demo (Edge via Playwright, API :5026 with a separate SQLite file, Vite :5173):
    • Error sample: 4/11 valid with seven kinds of row errors shown, confirm disabled, shifts unchanged at 14.
    • Valid sample: preview left 14 shifts; confirm → 25.
    • Same file again: blocked.
    • Export shows the 11 shifts; a real browser download produced shift-schedule-2026-05-11.csv.
    • 390 px: no overflow on the import page; no console errors.
    • Screenshots in docs/images/import-*.png.
  • The demo found two UI issues, fixed here: the file input kept showing the old file after confirming, and the preview table widened the page on phones.

Notes

  • The "Ugeplan" page already overflowed horizontally at 390 px before this change; it is left unchanged.
  • The README test counts were stale (59/20); they are now updated to the real numbers.
  • Design, trade-offs and interview notes: docs/PROJECT_WALKTHROUGH.md.

🤖 Generated with Claude Code

kris7011 and others added 3 commits November 1, 2026 09:53
Add a two-step shift import and a weekly CSV export:

- POST /api/shift-imports/preview parses the CSV body, validates every
  row and stores a pending import; no shift is created.
- POST /api/shift-imports/{id}/confirm validates the stored file again
  against the current schedule and, if every row is still valid,
  creates all shifts in one transaction. An import is confirmed once
  (conditional status update), and a file with the same content can be
  confirmed once (filtered unique index on the content hash).
- GET /api/schedule/export returns the Monday-Sunday week in the import
  format, so a week can be edited and imported again.

Row validation reports date, shift type, skill, employee and duplicate
problems and runs the ISchedulingRule implementations registered for
schedule generation, so imported assignments follow the same rules.
Values that spreadsheets run as formulas are refused on import and
neutralised on export. Demo reset now also clears import history.

Migration AddShiftImports only adds a table. 38 unit and 17 integration
tests cover parsing, validation, duplicates, confirmation, concurrency
guards and the export round trip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add an "Import og eksport" dashboard page: choose a CSV file, review the
preview with every row's errors and warnings in Danish, and save only by
confirming. A schedule that changed after the preview shows the new
result and saves nothing; an already imported file is refused. The page
links to the CSV export of a chosen week and stacks the preview rows as
cards on narrow screens.

The fake API used by the tests now passes non-JSON request bodies
through as text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document the import format, validation, confirmation and export in the
README, correct the test counts, add sample files with and without
errors, screenshots from a real local run, and a project walkthrough.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kris7011
kris7011 merged commit 641576c into main Nov 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant