Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 

7.2 KiB

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 Projects

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 /clientsclient_index
  • POST /api/clients, PATCH /api/clients/{id}, DELETE /api/clients/{id}
  • GET /projectsproject_index
  • POST /api/projects, PATCH /api/projects/{id}, DELETE /api/projects/{id}
  • GET /servicesservice_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


Seed-Daten (reset-and-seed.sh)

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"