diff --git a/CLAUDE.md b/CLAUDE.md index d5f4e7a..98aba74 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,36 +12,42 @@ ## Domain-Modell -**TaskSchema** — Vorlage/Template für Aufgaben -- Felder: name, status, taskType, category, deadline, startDate, endDate, weekdays, monthDays, yearDays -- TaskSchemaStatus: Active (`aktiv`), Completed (`erledigt`), Inactive (`inaktiv`) -- TaskSchemaType: Single (`einzel`), Daily (`taeglich`), Multi (`multi`), Weekly (`woechentlich`), Monthly (`monatlich`), Yearly (`jaehrlich`) +**Task** — Einzelne Aufgabe +- Felder: id, name, status, date, schema (nullable FK), category (nullable FK), createdAt +- TaskStatus: Active (`active`), Done (`done`) +- Tasks mit schema=null sind standalone (z.B. aus Single-Schemas erstellt) +- Tasks mit schema!=null gehören zu einem wiederkehrenden Schema -**Task** — Einzelne Aufgabe (Instanz eines Schemas) -- Felder: schema (FK), name (Override), category (Override), categoryOverridden, date, status, createdAt -- TaskStatus: Active (`aktiv`), Completed (`erledigt`) -- Kann Name und Kategorie pro Instanz überschreiben (`getEffectiveName()`, `getEffectiveCategory()`) -- Unique Constraint: (schema_id, date) +**TaskSchema** — Template für wiederkehrende Aufgaben +- Felder: id, name, status, taskType, category, startDate, endDate, days, createdAt +- TaskSchemaStatus: Active (`active`), Disabled (`disabled`) +- TaskSchemaType: Single (`single`), Daily (`daily`), Custom (`custom`) +- `days`: JSON-Feld mit optionalen Keys: `week` (1-7), `month` (1-31), `year` ([{month, day}]) +- Single: Erstellt Tasks via YearPicker, Tasks werden detached (schema=null), Schema löscht sich automatisch +- Daily: Erstellt Tasks für jeden Tag im Zeitraum start–end +- Custom: Erstellt Tasks basierend auf days-Konfiguration im Zeitraum start–end +- Disabled: Generiert keine neuen Tasks, bestehende bleiben **Category** — Farbkodierte Kategorie - Felder: id, name, color (Hex #RRGGBB) -Enum-Case-Namen sind Englisch, String-Werte bleiben Deutsch (in DB gespeichert). +Enum-Case-Namen und DB-Werte sind Englisch. ## Architektur (Backend) ``` Controller → Service (Manager) → Repository → Entity ↓ - DTO (Request/Response) + DTO (Request) ``` **Prinzipien:** - Controller: nur Routing + Response, keine Geschäftslogik - Manager-Services: Geschäftslogik (CRUD, Validierung, Toggle) -- DTOs: typisierter Input (Request) und Output (Response) +- DTOs: typisierter Input (Request) - Validierung: nur auf Request-DTOs (`#[Assert\...]`), nicht auf Entities -- Entities: Doctrine-Mapping + Getter/Setter + berechnete Felder via Serializer-Groups +- Entities: Doctrine-Mapping + Getter/Setter, Serializer-Groups für API-Output +- Task-Generierung: Lazy beim Aufruf von GET /api/tasks (nicht per Cronjob) ## Verzeichnisstruktur (Backend) @@ -49,13 +55,12 @@ Controller → Service (Manager) → Repository → Entity backend/src/ Controller/Api/ CategoryController.php — Category CRUD - TaskController.php — Task show/update/delete - TaskSchemaController.php — Schema CRUD + week view + toggle + TaskController.php — Task CRUD + index + toggle + TaskSchemaController.php — Schema CRUD DTO/ - Request/ — CreateSchemaRequest, UpdateSchemaRequest, UpdateTaskRequest, - ToggleRequest, CreateCategoryRequest, UpdateCategoryRequest, + Request/ — CreateSchemaRequest, UpdateSchemaRequest, CreateTaskRequest, + UpdateTaskRequest, CreateCategoryRequest, UpdateCategoryRequest, SchemaValidationTrait - Response/ — WeekViewResponse, DayResponse, ToggleResponse Entity/ Category.php, Task.php, TaskSchema.php Enum/ @@ -64,16 +69,34 @@ backend/src/ CategoryRepository.php, TaskRepository.php, TaskSchemaRepository.php Service/ CategoryManager.php — Category CRUD-Logik - TaskManager.php — Task Update/Delete/Toggle - TaskSchemaManager.php — Schema CRUD + Sync-Auslösung - TaskGenerator.php — Erzeugt Task-Instanzen für Zeiträume + TaskManager.php — Task Create/Update/Delete/Toggle + TaskSchemaManager.php — Schema CRUD + Single-Flow + Sync-Auslösung + TaskGenerator.php — Erzeugt Task-Instanzen für Zeiträume (lazy) TaskSynchronizer.php — Sync nach Schema-Update (löschen/erstellen/reset) - DeadlineCalculator.php — Berechnet Fälligkeitsdaten für ein Schema - TaskViewBuilder.php — Baut Wochenansicht + Alle-Aufgaben-View + DeadlineCalculator.php — Berechnet Fälligkeitsdaten für Daily/Custom-Schemas ``` ## API-Routen +### Tasks (`/api/tasks`) +``` +GET / — Alle Tasks (triggert Task-Generierung) +GET /{id} — Task-Details +POST / — Task direkt erstellen (schema=null) +PUT /{id} — Task aktualisieren +DELETE /{id} — Task löschen +PATCH /{id}/toggle — Status Active↔Done umschalten +``` + +### Schemas (`/api/schemas`) +``` +GET / — Alle Schemas +GET /{id} — Einzelnes Schema +POST / — Erstellen (Single: erstellt Tasks + löscht Schema) +PUT /{id} — Aktualisieren (synchronisiert zukünftige Tasks) +DELETE /{id} — Löschen (?deleteTasks=1 für Task-Löschung) +``` + ### Categories (`/api/categories`) ``` GET / — Alle Kategorien @@ -83,34 +106,14 @@ PUT /{id} — Aktualisieren DELETE /{id} — Löschen ``` -### Schemas (`/api/schemas`) -``` -GET / — Alle Schemas -GET /week?start=DATE — Wochenansicht (7 Tage) -GET /all — Alle Schemas sortiert -GET /all-tasks — Alle Tasks über alle Schemas -GET /{id} — Einzelnes Schema -POST / — Erstellen -PUT /{id} — Aktualisieren (synchronisiert Tasks) -DELETE /{id} — Löschen -PATCH /{id}/toggle — Task-Status umschalten -``` - -### Tasks (`/api/tasks`) -``` -GET /{id} — Task-Details -PUT /{id} — Task aktualisieren -DELETE /{id} — Task löschen -``` - ## Verzeichnisstruktur (Frontend) ``` frontend/src/ views/ - HomeView.vue — Startseite, Wochenansicht + HomeView.vue — Startseite, Wochenansicht (client-seitig berechnet) AllTasksView.vue — Übersicht aller Schemas/Tasks - SchemaView.vue — Schema erstellen/bearbeiten + SchemaView.vue — Schema erstellen/bearbeiten (3 Typen) TaskDetailView.vue — Einzelne Aufgabe bearbeiten CategoriesView.vue — Kategorien verwalten components/ @@ -144,11 +147,12 @@ frontend/src/ - **Sprache Code**: Englisch (Klassen, Methoden, Variablen, CSS-Klassen) - **Sprache UI**: Deutsch (Labels, Fehlermeldungen, Platzhalter) -- **Enum-Werte**: Deutsch in DB (`aktiv`, `erledigt`, `einzel` etc.), Englisch als PHP-Case-Namen (`Active`, `Completed`, `Single`) -- **API-Serialisierung**: Symfony Serializer-Groups auf allen Entities (`schema:read`, `task:read`, `category:read`) +- **Enum-Werte**: Englisch in DB und Code (`active`, `done`, `single`, `daily`, `custom`, `disabled`) +- **API-Serialisierung**: Symfony Serializer-Groups auf Entities (`schema:read`, `task:read`, `category:read`) - **Validierung**: Auf Request-DTOs, nicht auf Entities - **Error-Handling**: `HttpException` → Symfony built-in - **Frontend**: Vue 3 Composition API mit ` diff --git a/frontend/src/views/SchemaView.vue b/frontend/src/views/SchemaView.vue index 3b7edce..34771e8 100644 --- a/frontend/src/views/SchemaView.vue +++ b/frontend/src/views/SchemaView.vue @@ -17,18 +17,20 @@ const isEdit = computed(() => !!route.params.id) const loading = ref(false) const saving = ref(false) const error = ref(null) +const deleteTasksOnRemove = ref(false) const form = ref({ name: '', categoryId: null, - status: 'aktiv', - taskType: 'einzel', - deadline: null, + status: 'active', + type: 'single', startDate: null, endDate: null, - weekdays: [], - monthDays: [], - yearDays: [], + days: { + week: [], + month: [], + year: [], + }, }) function formatDateForInput(dateStr) { @@ -44,13 +46,14 @@ async function loadTask() { name: task.name, categoryId: task.category?.id ?? null, status: task.status, - taskType: task.taskType, - deadline: formatDateForInput(task.deadline), + type: task.type, startDate: formatDateForInput(task.startDate), endDate: formatDateForInput(task.endDate), - weekdays: task.weekdays || [], - monthDays: task.monthDays || [], - yearDays: task.yearDays || [], + days: { + week: task.days?.week || [], + month: task.days?.month || [], + year: task.days?.year || [], + }, } setDynamicLabel(task.name) } finally { @@ -64,23 +67,25 @@ function buildPayload() { name: f.name, categoryId: f.categoryId, status: f.status, - taskType: f.taskType, - deadline: null, + type: f.type, startDate: null, endDate: null, - weekdays: null, - monthDays: null, - yearDays: null, + days: null, } - if (f.taskType === 'einzel') { - payload.deadline = f.deadline - } else { + if (f.type === 'single') { + payload.days = { year: f.days.year } + } else if (f.type === 'daily') { payload.startDate = f.startDate payload.endDate = f.endDate - if (f.taskType === 'woechentlich') payload.weekdays = f.weekdays - if (f.taskType === 'monatlich') payload.monthDays = f.monthDays - if (f.taskType === 'multi' || f.taskType === 'jaehrlich') payload.yearDays = f.yearDays + } else if (f.type === 'custom') { + payload.startDate = f.startDate + payload.endDate = f.endDate + const days = {} + if (f.days.week.length) days.week = f.days.week + if (f.days.month.length) days.month = f.days.month + if (f.days.year.length) days.year = f.days.year + payload.days = Object.keys(days).length ? days : null } return payload @@ -105,8 +110,8 @@ async function save() { } async function remove() { - if (!confirm('Aufgabe wirklich löschen?')) return - await deleteSchema(route.params.id) + if (!confirm('Schema wirklich löschen?')) return + await deleteSchema(route.params.id, deleteTasksOnRemove.value) router.back() } @@ -151,9 +156,8 @@ onMounted(() => {
{{ error }}