# Kontext: spawntree Timetracker ## Wer ich bin Flo Eisenmenger, Geschäftsführer der spawntree GmbH (kleine Webagentur, Hamburg). DDEV-Entwicklungsumgebung, Symfony 7.x, PHP 8.2, MariaDB 10.11. --- ## Was wir bauen Ein internes Timetracking-Tool – zunächst nur für mich, später ggf. als SaaS. Technisch ähnlich wie **mite.de** – wir orientieren uns stark am UI/UX von mite. --- ## Tech Stack - **Backend**: Symfony 7, PHP 8.2, Doctrine ORM, MariaDB - **Frontend**: Twig, SCSS (Webpack Encore), Vanilla JS (keine jQuery, kein Framework) - **SCSS-Struktur**: Atoms → Components → Sections (BEM-ähnlich) - **Dev**: DDEV (Port 8456 HTTPS), PHPMyAdmin installiert - **Kein** Symfony Forms mehr – eigene HTML-Formulare mit fetch()-API --- ## Datenbankstruktur (Entities) ### `User` - `id`, `email` (unique), `firstName`, `lastName`, `password` (nullable), `note` - Standard-User: `f.eisenmenger@spawntree.de` / Flo Eisenmenger ### `Client` (Kunde) - `id`, `name`, `hourlyRate` (decimal, nullable), `note` - Hat viele `Project`s ### `Project` - `id`, `name`, `client` (ManyToOne → Client), `note` ### `Service` (Leistung) - `id`, `name`, `billable` (bool, default true), `note` ### `TimeEntry` - `id`, `date` (DATE_IMMUTABLE), `duration` (int, **Minuten**), `user`, `project`, `service` (nullable), `note`, `createdAt`, `updatedAt` - `toArray()` Methode für JSON-Responses --- ## Routing-Übersicht ### Timetracking (Hauptseite) - `GET /` → `timetracking_week` - `GET /week/{date}` → `timetracking_week_date` - `GET /api/entries?date=Y-m-d` → Einträge für einen Tag (JSON) - `POST /api/entries` → Eintrag erstellen - `PATCH /api/entries/{id}` → Eintrag bearbeiten - `DELETE /api/entries/{id}` → Eintrag löschen ### CRUD-Seiten - `GET /clients` → `client_index` - `POST /api/clients`, `PATCH /api/clients/{id}`, `DELETE /api/clients/{id}` - `GET /projects` → `project_index` - `POST /api/projects`, `PATCH /api/projects/{id}`, `DELETE /api/projects/{id}` - `GET /services` → `service_index` - `POST /api/services`, `PATCH /api/services/{id}`, `DELETE /api/services/{id}` --- ## Template-Struktur ``` templates/ ├── base.html.twig ← Basistemplate mit Nav-Include ├── _nav.html.twig ← Dunkle Top-Navigation ├── timetracking/ │ ├── week.html.twig ← Hauptseite (Zeiterfassung) │ └── _entry_row.html.twig ← Partial: einzelne Zeiteintrag-Zeile ├── client/ │ └── index.html.twig ← Kundenliste mit Inline-Edit ├── project/ │ └── index.html.twig ← Projektliste mit Inline-Edit └── service/ └── index.html.twig ← Leistungsliste mit Inline-Edit ``` --- ## SCSS-Struktur ``` assets/styles/ ├── main.scss ← Entry Point, importiert alles ├── atoms/ │ ├── _variables.scss ← Farben, Spacing, etc. │ ├── _typography.scss │ ├── _buttons.scss ← .btn, .btn-primary, .btn-secondary │ └── _inputs.scss ← .input, .select, .textarea └── components/ ├── _week-nav.scss ← Wochennavigation im Header ├── _month-calendar.scss ← Monatskalender (Popup) ├── _entry-form.scss ← Zeiterfassungs-Formular ├── _entry-list.scss ← Eintrags-Liste inkl. Inline-Edit ├── _duration-help.scss ← "?"-Tooltip beim Dauerfeld ├── _main-nav.scss ← Dunkle Top-Nav ├── _greeting.scss ← Begrüßungszeile └── _crud.scss ← CRUD-Seiten (Kunden/Projekte/Leistungen) sections/ └── _timetracking.scss ← .tt-page, .tt-header, .tt-content ``` --- ## JS-Struktur ``` assets/ ├── app.js ← Webpack Entry für Hauptseite │ importiert: main.scss, calendar.js, entries.js ├── scripts/ │ ├── calendar.js ← WeekCalendar Klasse │ │ - Wochennavigation mit Slide-Animation │ │ - Monatsansicht (Popup) │ │ - Ruft window.entryManager.loadEntriesForDate() auf │ ├── entries.js ← EntryManager Klasse │ │ - Event Delegation auf #entry-list │ │ - CRUD via fetch() │ │ - localStorage für letztes Projekt/Leistung │ │ - Importiert aus duration.js │ ├── duration.js ← Hilfsfunktionen (Export) │ │ - parseDuration() (1:30, 8 12, 1,75) │ │ - roundToQuarter() (konfigurierbar) │ │ - DURATION_CONFIG.roundToQuarter = true │ │ - initDurationBlurHandler() │ └── crud.js ← Webpack Entry für CRUD-Seiten │ Generic für Kunden/Projekte/Leistungen ``` **Wichtig**: `entries.js` nutzt ES Module `import/export` – funktioniert mit Webpack Encore. --- ## Webpack Encore (webpack.config.js) Zwei Entries: - `app` → `./assets/app.js` (Hauptseite) - `crud` → `./assets/scripts/crud.js` (CRUD-Seiten) CRUD-Seiten laden `crud.js` via `{{ encore_entry_script_tags('crud') }}` im Twig-Block. --- ## Wichtige Konventionen ### Durations - Gespeichert als **Integer (Minuten)** in der DB - Eingabe: `1:30`, `8 12` (von-bis), `1,75` (Dezimal), `0:00` (Reset) - Automatisch auf **15-Minuten-Schritt aufgerundet** (konfigurierbar) - Anzeige: `formatMinutes()` → `"1:30"` ### Translations - Alle UI-Strings in `translations/messages.de.yaml` - Datums-Arrays (Monate, Wochentage) in `AppExtension.php` als Twig-Functions: `deMonths()`, `deMonthsShort()`, `deWeekdays()`, `deWeekdaysShort()` - JS bekommt alle Strings via `window.TT.i18n` (aus Twig gesetzt) - JS-Zugriff: `function t(key) { return window.TT?.i18n?.[key] ?? key; }` ### API-Pattern - Alle API-Routen unter `/api/...` - JSON Request/Response - Kein CSRF (reine JSON-API) - Fehler: `{ error: "..." }` mit passendem HTTP-Status ### Aktiver User Aktuell hardcoded auf `f.eisenmenger@spawntree.de`. Auth (Login/Session) ist noch **nicht gebaut** – kommt später. --- ## Was noch fehlt / TODO - [ ] Login / Authentifizierung (Symfony Security) - [ ] Reports-Seite - [ ] Wochenübersicht mit Summen - [ ] Export (CSV / PDF) - [ ] Multi-User / Mandantenfähigkeit - [ ] Timer-Funktion (Live-Zeiterfassung) - [ ] Passwort-Hashing für User-Entity --- ## Seed-Daten (reset-and-seed.sh) ```bash bash reset-and-seed.sh # → DB droppen, Migrations ausführen, app:seed aufrufen ``` `app:seed` legt an: 1 User, 5 Leistungen (4 verrechenbar, 1 intern), 10 Kunden mit je 1-3 Projekten. --- ## DDEV-Konfiguration - Projekt: `testtimetracking` - URL: `https://testtimetracking.ddev.site:8456` - PHPMyAdmin: `https://testtimetracking.ddev.site:8037` (nach `ddev get ddev/ddev-phpmyadmin`) - MariaDB: User `db`, Passwort `db`, DB `db` - `.env`: `DATABASE_URL="mysql://db:db@db:3306/db?serverVersion=10.11.2-MariaDB&charset=utf8mb4"`