|
|
@@ -65,7 +65,8 @@ per-cell audit trail.
|
|
|
├── src/
|
|
|
│ ├── Auth/ LocalAdmin, OidcClient, SessionGuard
|
|
|
│ ├── Controllers/ AuthController, WorkerController, SprintController,
|
|
|
-│ │ TaskController, AuditController, UserController
|
|
|
+│ │ TaskController, AuditController, UserController,
|
|
|
+│ │ SettingsController
|
|
|
│ ├── Db/ Connection, Migrator
|
|
|
│ ├── Domain/ User, Worker, Sprint, SprintWeek, SprintWorker,
|
|
|
│ │ SprintWorkerDay, Task, TaskAssignment
|
|
|
@@ -73,24 +74,27 @@ per-cell audit trail.
|
|
|
│ ├── Repositories/ UserRepository, WorkerRepository, SprintRepository,
|
|
|
│ │ SprintWeekRepository, SprintWorkerRepository,
|
|
|
│ │ SprintWorkerDayRepository, TaskRepository,
|
|
|
-│ │ TaskAssignmentRepository, AuditRepository
|
|
|
+│ │ TaskAssignmentRepository, AuditRepository,
|
|
|
+│ │ AppSettingsRepository
|
|
|
│ └── Services/ AuditLogger, CapacityCalculator
|
|
|
├── migrations/ 001_init.sql (full schema per spec §3)
|
|
|
│ 002_sprint_week_active_days.sql (Phase 12 — mask column)
|
|
|
+│ 003_task_status_and_app_settings.sql (Phase 18 — task-cell status + KV)
|
|
|
├── views/ layout.php, home.php, auth/local.php,
|
|
|
│ workers/index.php, users/index.php,
|
|
|
│ sprints/{new,show,settings,present}.php,
|
|
|
-│ audit/index.php
|
|
|
+│ settings/index.php, audit/index.php
|
|
|
├── tests/ TestCase + Services/ + Repositories/ + Controllers/ + Cascade/
|
|
|
│ + Domain/ + Db/
|
|
|
└── data/ SQLite + sessions directory (volume-mounted, gitignored)
|
|
|
```
|
|
|
|
|
|
-## 4. Schema (migrations/001_init.sql + 002_sprint_week_active_days.sql)
|
|
|
+## 4. Schema (migrations/001..003)
|
|
|
|
|
|
Tables (already applied): `users`, `workers`, `sprints`, `sprint_weeks`,
|
|
|
`sprint_workers`, `sprint_worker_days`, `tasks`, `task_assignments`,
|
|
|
-`audit_log`, plus the `schema_version` tracking table.
|
|
|
+`audit_log`, `app_settings` (Phase 18 — KV store for global flags),
|
|
|
+plus the `schema_version` tracking table.
|
|
|
|
|
|
`sprint_weeks.active_days_mask INTEGER NOT NULL DEFAULT 31` (Phase 12) is
|
|
|
a 5-bit mask — bit0=Mo, bit1=Di, bit2=Mi, bit3=Do, bit4=Fr — and is the
|
|
|
@@ -108,6 +112,8 @@ Value constraints enforced in PHP (not SQL):
|
|
|
- `sprint_weeks.active_days_mask` ∈ 0..31 (bits Mo..Fr).
|
|
|
- `sprint_worker_days.days` ∈ {0, 0.5, …, 5}.
|
|
|
- `task_assignments.days` ≥ 0, no hard upper bound.
|
|
|
+- `task_assignments.status` ∈ {`zugewiesen`, `gestartet`, `abgeschlossen`,
|
|
|
+ `abgebrochen`} (Phase 18). DB CHECK constraint enforces this.
|
|
|
- `reserve_fraction`, `rtb` ∈ [0, 1].
|
|
|
|
|
|
FK cascades (every cascade path now snapshot-audits before the parent delete
|
|
|
@@ -161,6 +167,8 @@ Pages (HTML):
|
|
|
| GET | `/sprints/{id}/present` | signed-in |
|
|
|
| GET | `/sprints/{id}/settings` | admin |
|
|
|
| GET | `/audit` | admin |
|
|
|
+| GET | `/settings` | admin |
|
|
|
+| POST | `/settings` | admin (form CSRF via `_csrf`) |
|
|
|
|
|
|
JSON (admin-only, CSRF via `X-CSRF-Token` header; envelope per spec §7):
|
|
|
| Method | Path | What |
|
|
|
@@ -177,7 +185,8 @@ JSON (admin-only, CSRF via `X-CSRF-Token` header; envelope per spec §7):
|
|
|
| POST | `/sprints/{id}/tasks/reorder` | reorder tasks |
|
|
|
| PATCH | `/tasks/{id}` | title/owner/priority |
|
|
|
| DELETE | `/tasks/{id}` | delete task (audits cascaded assignments) |
|
|
|
-| PATCH | `/tasks/{id}/assignments` | batch assignment cells |
|
|
|
+| PATCH | `/tasks/{id}/assignments` | batch assignment cells (days only) |
|
|
|
+| PATCH | `/tasks/{id}/assignments/status` | batch cell status — **any signed-in user**; gated by `app_settings.task_status_enabled` (403 when off) |
|
|
|
|
|
|
Response envelope:
|
|
|
- Success: `{"ok": true, "data": …}`
|
|
|
@@ -602,6 +611,63 @@ with a `BOOTSTRAP_ADMIN` audit row.
|
|
|
ArrowUp/ArrowDown still steps via browser default. No
|
|
|
schema / route / PHP changes; tests untouched at 88 / 208.
|
|
|
|
|
|
+- [x] **Phase 18 — Per-cell task-status colours + filter +
|
|
|
+ global toggle** (`9cb7669`). Each task-assignment cell on
|
|
|
+ both `/sprints/{id}` and `/sprints/{id}/present` now carries
|
|
|
+ a workflow status — `zugewiesen` (transparent, default),
|
|
|
+ `gestartet` (yellow), `abgeschlossen` (green), `abgebrochen`
|
|
|
+ (red) — picked from a chevron-only `<select
|
|
|
+ data-assign-status>` next to the day input/span. The cell is
|
|
|
+ wrapped in `<span class="assign-cell assign-status-{state}"
|
|
|
+ data-assign-cell data-status="{state}">`; `sprint-planner.js`
|
|
|
+ mirrors the chosen value into the wrapper class + `data-
|
|
|
+ status` and queues a save through a new
|
|
|
+ `pendingStatus`/`flushStatus` debounced pipeline that hits
|
|
|
+ `PATCH /tasks/{id}/assignments/status` (400 ms, same
|
|
|
+ semantics as the days pipeline). New "Status" multi-select
|
|
|
+ filter sits between Owners and Focus in the toolbar; a row
|
|
|
+ passes when at least one cell is in the picked set, with a
|
|
|
+ special-case rule that the default `zugewiesen` only matches
|
|
|
+ when `days > 0` (so picking it doesn't include every task).
|
|
|
+ State persists in `localStorage`
|
|
|
+ (`sp:{sprintId}:statusFilter`, `:beamer`-namespaced for the
|
|
|
+ present view) and is wiped by the existing Reset button.
|
|
|
+ Schema: `migrations/003_task_status_and_app_settings.sql`
|
|
|
+ adds `task_assignments.status TEXT NOT NULL DEFAULT
|
|
|
+ 'zugewiesen'` with a CHECK constraint, and creates a new
|
|
|
+ `app_settings(key TEXT PK, value TEXT NOT NULL, updated_at
|
|
|
+ TEXT NOT NULL)` KV table seeded with
|
|
|
+ `('task_status_enabled', '0')` so the feature is opt-in.
|
|
|
+ `App\Repositories\AppSettingsRepository` (get/getBool/set)
|
|
|
+ reads it; `SprintController::loadSprintPage()` passes
|
|
|
+ `taskStatusEnabled` + `statusGrid` into both views, which
|
|
|
+ conditionally render the per-cell selectors and the toolbar
|
|
|
+ Status filter. New admin-only `/settings` page (linked from
|
|
|
+ the hamburger menu, `App\Controllers\SettingsController`)
|
|
|
+ flips the toggle via a native form POST with `_csrf`;
|
|
|
+ audit row `entity_type='app_setting'`. New
|
|
|
+ `PATCH /tasks/{id}/assignments/status` is the **first non-
|
|
|
+ admin write surface** in the app — gated by
|
|
|
+ `SessionGuard::requireAuthJson` (auth + CSRF, no admin) plus
|
|
|
+ `app_settings.task_status_enabled` (403 when off). Existing
|
|
|
+ `PATCH /tasks/{id}/assignments` stays admin-only and days-
|
|
|
+ only; `TaskAssignmentRepository::upsert` preserves status,
|
|
|
+ `::upsertStatus` preserves days and inserts a `days=0` row
|
|
|
+ when the cell didn't exist (so a state can be tracked before
|
|
|
+ any work is assigned). Per-cell audit semantics unchanged
|
|
|
+ — one row per changed cell. The four `.assign-status-*`
|
|
|
+ class names are interpolated server-side, so
|
|
|
+ `tailwind.config.js` gains a `safelist` keeping them in the
|
|
|
+ build (Tailwind was silently dropping them otherwise — the
|
|
|
+ `:not()` reference happened to keep `zugewiesen` in but the
|
|
|
+ other three vanished). Strict CSP unchanged. Tests:
|
|
|
+ 105 / 265 (was 88 / 208) — `+TaskAssignmentTest` (status
|
|
|
+ enum + audit-snapshot shape), `+AppSettingsRepositoryTest`
|
|
|
+ (seeded flag, get/set roundtrip, no-op equality, default
|
|
|
+ fallback), `+TaskAssignmentRepositoryTest` (upsertStatus's
|
|
|
+ four cases, days writes preserving status,
|
|
|
+ `InvalidArgumentException` guard, `statusGridForSprint`).
|
|
|
+
|
|
|
### Upcoming
|
|
|
|
|
|
Nothing scheduled.
|
|
|
@@ -662,7 +728,7 @@ for f in $(git ls-files '*.php'); do php -l "$f" | tail -1 | sed "s|^|$f: |"; do
|
|
|
Run the test suite:
|
|
|
```bash
|
|
|
vendor/bin/phpunit
|
|
|
-# → OK (88 tests, 208 assertions)
|
|
|
+# → OK (105 tests, 265 assertions)
|
|
|
```
|
|
|
|
|
|
## 12. How to resume in a fresh Claude session
|
|
|
@@ -670,10 +736,12 @@ vendor/bin/phpunit
|
|
|
Tell Claude:
|
|
|
|
|
|
> Working on `/Users/achiappa/Development/claude_code_private/sprint_planer_web`.
|
|
|
-> Read `SPEC.md`, the git log, and `ACCEPTANCE.md`. Phases 1–17
|
|
|
+> Read `SPEC.md`, the git log, and `ACCEPTANCE.md`. Phases 1–18
|
|
|
> are shipped (see §9; the Phase-17 slider popover was later
|
|
|
> removed — typed entry is now the only edit path on number
|
|
|
-> inputs) — nothing is currently scheduled. Outstanding
|
|
|
+> inputs; Phase 18 added per-cell task-status colours + filter
|
|
|
+> + a new /settings page, all gated by a global flag that's off
|
|
|
+> by default). Nothing is currently scheduled. Outstanding
|
|
|
> items are in §10 (mostly a human-run acceptance walkthrough in the
|
|
|
> running container, plus the jQuery UI dark-mode cosmetic gap noted
|
|
|
> there). If I ask you to plan or work a new phase, follow the
|
|
|
@@ -686,6 +754,8 @@ before acting — nothing here is load-bearing once it grows stale.
|
|
|
## 13. Git history (as of this writing)
|
|
|
|
|
|
```
|
|
|
+9cb7669 Phase 18: per-cell task-status colours + filter + global toggle
|
|
|
+da726bd SPEC.md: note number-stepper popover removal
|
|
|
e551705 Remove number-stepper slider popover
|
|
|
c5eef6a Docs: rename HANDOFF.md to SPEC.md, add admin manual, refresh README
|
|
|
fd2f0df changed docker compose port
|