froodwork
Stundenerfassung, die auf deinem eigenen Server läuft.
Von der gearbeiteten Minute bis zur Rechnung: erfassen, zuordnen, bewerten, abrechnen. PHP und SQLite, kein Build-Schritt, keine externe Datenbank, kein Framework. Entpacken, hochladen, aufrufen.
data/ und uploads/. Der Rest ist ersetzbarer Code.
Dazu pdo_sqlite. Mehr braucht es nicht — gd und zip sind optional.
Kein CDN, kein Tracking, kein npm. Schriften und Symbole liegen im Ordner.
Die App
Aufnahmen aus der laufenden App · Klick zeigt grossFünf Sätze, an denen sich alles ausrichtet
- Die Daten gehören dem Betreiber. Kein externer Dienst, keine Laufzeitabhängigkeit. Was die App braucht, liegt in ihrem eigenen Ordner.
- Nichts wird stillschweigend entschieden. Vergessener Timer, Überschneidung, fremde Währung: die App fragt oder weist aus, statt zu raten.
- Erfassen darf nicht unterbrechen. Der Weg von „ich fange jetzt an“ bis zum laufenden Timer ist der kürzeste Weg der App.
- Das Schema ist Vertrag. Jede Fläche funktioniert in jedem Nutzerschema, hell wie dunkel. Gestaltung arbeitet mit den Variablen, nicht gegen sie.
- Einfach genug, um sie in zehn Jahren noch zu warten. Lesbarer Code ohne Build, ehrliche Dokumentation, keine Abkürzung, die später jemanden einsperrt.
Erfassen
Live-Timer, serverseitig laufend — er zählt weiter, wenn du den Tab schließt oder das Gerät wechselst. Oder manuell nachtragen.
Projekte & Kunden
Stundensätze gelten hierarchisch: Position → Projekt → Kunde → Standardsatz. Der erste gefundene Wert zählt.
Anrufe
Ein eigener Bereich. Gespräche hängen am Kunden, nicht am Projekt — und pausieren einen laufenden Projekttimer.
Vorlagen
Wiederkehrende Arbeiten als Baustein über den Kennzahlen. Ein Klick startet den Timer damit; häufig Genutztes wandert nach vorn.
Rechnungsläufe
Aus einer Auswahl wird eine Rechnung mit Nummer, Datum und Notiz — nach eigenem Nummernmuster, jederzeit rücknehmbar.
Kundenlink
Eine reine Leseansicht der offenen Stunden, 128-Bit-Schlüssel im Link, widerrufbar, ohne Anmeldung und ohne Formular.
Schwebefenster
Auf dem Desktop schrumpft die App auf ein kleines Fenster mit Zeit, Pause und Stopp — in Chrome und Edge über allen anderen.
PWA
Auf iOS und Android installierbar, mit eigenem Icon aus deinem Logo. Handy und Desktop sind gleichwertig.
Export
CSV mit allen aktiven Filtern, vollständiger JSON-Export, Backup-ZIP. Deine Daten bleiben deine Daten.
Der Unterschied
Zu SaaS-Zeiterfassung: Die Daten bleiben bei dir, es gibt keine laufenden Kosten und keine Abhängigkeit vom Fortbestand eines Anbieters. Zu selbstgehosteten Alternativen: kein Docker, kein Composer, keine MySQL-Instanz. Signierte Selbst-Updates aus deinem eigenen Verzeichnis halten das aufrecht, ohne dass du jedes Mal per FTP arbeiten musst.
Erfassen
Der kürzeste Weg der App
Ein Klick auf Start, und die Zeit läuft — auf dem Server, nicht im Browser. Tab zu, Rechner aus, Handy raus: sie läuft weiter.
Timer
bereitBeim Stoppen rundet die App auf die angefangene Einheit auf — aus 47 Minuten werden bei Viertelstunden 60. Das Raster steht in den Einstellungen und lässt sich je Kunde überschreiben.
Dauer eintippen
Nachtragen soll nicht heißen, im Kopf zu rechnen. Das Feld versteht vier Schreibweisen. Probier eine aus.
Die Regel dahinter: Eine ganze Zahl sind Minuten, eine Kommazahl sind Stunden.
Wer es ausdrücklich sagen will, hängt h oder min an.
Aufrunden
Auf die angefangene Einheit. Wie in der App: global eingestellt, je Kunde überschreibbar.
Der Betrag rechnet mit der gerundeten Zeit — was auf der Rechnung steht, steht auch in der Position.
Anrufe
Ein Gespräch über eine Rechnung gehört zu keinem Projekt, kostet aber trotzdem Zeit. Anrufe hängen deshalb am Kunden. Auf der Erfassen-Seite genügt eine Zeile: Kunde wählen, Richtung, starten. Beim Beenden trägst du das Thema ein.
Läuft gerade ein Projekttimer, hält der Anruf ihn an und lässt ihn danach weiterlaufen — die Gesprächszeit steckt dann nicht zusätzlich in der Projektzeit. Einen von Hand angehaltenen Timer weckt der Anruf nicht auf.
Vorlagen
Projekt, Beschreibung und Abrechenbarkeit in einem Baustein. Ein Klick startet den Timer damit. Häufig Genutztes wandert von selbst nach vorn.
Ein Klick setzt hier oben den Timer-Text — in der App startet er die Zeit gleich mit.
Wenn der Timer vergessen wird
Läuft er länger als die eingestellte Grenze — Vorgabe zehn Stunden —, fragt die App beim nächsten Öffnen nach, bis wann tatsächlich gearbeitet wurde, statt stillschweigend eine 14-Stunden-Position anzulegen. Kollidieren zwei Positionen am selben Tag zeitlich, tragen beide in der Liste einen Hinweis Überschneidung und beim Speichern erscheint eine Meldung — gespeichert wird trotzdem, denn manchmal ist eine Doppelbuchung gewollt.
Auswerten
Sehen, was fehlt
Die Positionsliste zeigt, was da ist. Das Wochenraster zeigt, was nicht da ist — dafür ist es vor allem gebaut.
Wochenraster
Klick auf eine leere ZelleZeilen sind Projekte, Spalten die sieben Tage. Ein Klick auf eine leere Zelle legt direkt eine Position für diesen Tag und dieses Projekt an.
Positionen
- Startseite umgebaut Mo, 24.08. · 10:00–12:15 · Website-Relaunch2,25 hoffen
- Bilder freigestellt Di, 25.08. · 14:00–15:00 · Website-Relaunch1,00 hR-2026-041
- Backup eingerichtet Di, 25.08. · 16:30–17:00 · Wartung0,50 hnicht abrechenbar
- Abstimmung Layout Mi, 26.08. · 09:00–10:30 · Broschüre1,50 hÜberschneidung
Jede Position ist abrechenbar oder nicht und offen oder abgerechnet. Abgerechnete Zeilen tragen ihre Rechnungsnummer.
Budget
Projekte können ein Stundenkontingent bekommen. Ab 80 Prozent warnt der Balken, über 100 Prozent wird er rot. Zieh den Regler.
Alles im Rahmen.
Redezeit je Kunde
Der Reiter Anrufe zeigt, wie viel Redezeit auf welchen Kunden entfällt und wie viel davon als Position übernommen wurde. Ein langer Balken mit wenig Hellem heißt: viel telefoniert, wenig berechnet.
Eigene Bedienelemente
Auswahl-, Datums- und Zeitfelder sind eigene Bausteine im Design der App statt der Browser-Standards — mit Gruppierung nach Kunde, Farbpunkt je Projekt, Suchfeld ab neun Optionen und Tastaturbedienung: Pfeiltasten, Enter, Escape. Der Kalender startet die Woche am Montag.
Das native Feld bleibt im Hintergrund erhalten und ist weiterhin die Datenquelle. Ohne JavaScript greifen also die normalen Browser-Elemente — die App bleibt bedienbar.
Abrechnen
Am Monatsende steht die Aufstellung
Mehrere Positionen auswählen, Rechnungslauf anlegen, fertig. Mit eigener Nummer, eingefrorenem Steuersatz und — falls nötig — rücknehmbar.
Nummernmuster
Die App schlägt die nächste Nummer nach deinem Muster vor.
Platzhalter: {JJJJ} Jahr, {JJ} zweistellig, {MM} Monat,
{TT} Tag, {NNN} fortlaufender Zähler.
Überschreiben geht immer — nötig, wenn die Rechnungen aus einem anderen Programm kommen. Eine getippte Fremdnummer verschiebt die eigene Reihe nicht.
Rechnungsläufe
3 Läufe| Nummer | Kunde | Datum | Stunden | Betrag |
|---|---|---|---|---|
| R-2026-041 | Meiers Möbel | 31.07.2026 | 18,25 | 1.551,25 € |
| R-2026-040 | Nordwind GmbH | 30.06.2026 | 7,50 | 637,50 € |
| R-2026-039 | Studio Vik CHF | 30.06.2026 | 12,00 | 1.140,00 CHF |
Eine Rechnung gehört zu genau einem Kunden, und eine Position steht auf höchstens einer Rechnung. Zurücknehmen macht die Positionen wieder offen.
Stundensätze
Hierarchisch: Position → Projekt → Kunde → Standardsatz aus den Einstellungen. Der erste gefundene Wert zählt. Ein Sondersatz für eine einzelne Position hebelt nichts anderes aus.
Umsatzsteuer
Standardsatz global, je Kunde überschreibbar. Eine 0 heißt ausdrücklich kein Ausweis, etwa im Ausland. Leer genügt für Kleinunternehmer. Der beim Abrechnen gültige Satz wird auf der Rechnung festgehalten — eine alte Rechnung ändert sich nicht mehr.
Währungen
Jeder Kunde kann seine eigene haben. Summen stehen deshalb je Währung getrennt und werden niemals zusammengerechnet — ein Umrechnungskurs wäre geraten und in der Buchhaltung wertlos. Der CSV-Export hat dafür eine eigene Spalte und je Währung eine Summenzeile.
Der Kundenlink
share.phpJeder Kunde kann einen eigenen Link bekommen und sieht darüber jederzeit seine noch nicht abgerechneten Stunden — mit Datum, Projekt, Tätigkeit, Dauer und Betrag.
- Führt ausschließlich Leseabfragen aus. Keine Formulare, keine Anmeldung.
- Der Link enthält einen zufälligen 128-Bit-Schlüssel, ist also nicht erratbar — und jederzeit widerrufbar.
- Suchmaschinen werden per
noindexausgesperrt. - Sichtbar sind nur die Daten dieses einen Kunden. Keine anderen Kunden, keine als nicht abrechenbar markierten Stunden.
- Dein Name für Kunden und eine Mailadresse für Rückfragen stehen unter der Aufstellung — sonst sieht er Beträge, ohne zu wissen, von wem sie kommen.
Erscheinungsbild
Das Schema ist Vertrag
Neun Farben und die Ecken-Rundung sind frei einstellbar. Jede Fläche der App muss in jedem davon tragen — auch diese Seite hier. Probier es aus: die Regler wirken sofort.
Schemata
sechs mitgelieferte, eigene speicherbarÄnderungen sind sofort sichtbar und werden automatisch gespeichert. Eine eingestellte Kombination lässt sich unter eigenem Namen ablegen — praktisch, um zwischen heller Tagvariante und dunkler Abendvariante zu wechseln, ohne die eigene Einstellung zu verlieren. Bei mehreren Benutzern hat jeder sein eigenes Erscheinungsbild.
Rundung
10 pxEin Wert, dem alles folgt: Knöpfe, Karten, Felder, Marken. Kleine Bedienflächen leiten ihre Rundung daraus ab, Logos und Farbfelder rechnen in Prozent — deshalb stimmt es bei jeder Kastengröße von selbst.
Der Tagesbogen
10:00 UhrDer Grund hinter allem sagt eine einzige Zahl: wo im Tag du stehst. Das Licht wandert über den Schirm wie ein Zeiger über ein Zifferblatt — 0:00 links unter der Kante, 12:00 oben in der Mitte, 24:00 rechts wieder darunter.
Es ist ausdrücklich keine Sonne: die hätte einen Ort und ein Datum, und beides kennt die App nicht. Was hier steigt und fällt, ist die Uhrzeit selbst. Nachts bleibt die Fläche vollständig leer — Licht hebt, kein Licht hebt nicht.
Und in keinem Fall gelb: Der Akzent ist das Statuslicht des laufenden Timers. Ein Himmel in derselben Farbe hätte ihm die einzige Aufgabe genommen, die er hat.
Zwei Schriften, zwei Rollen
Delight beschriftet — die Marke und die gesperrten Versalien, also das, was einer Sache einen Namen gibt. Inter misst und bedient: Fließtext, Knöpfe, Felder, Listen und jede Zahl.
Delight · Versalien
Inter · 4:37:12 · 1.062,50 €
Beide werden lokal ausgeliefert, mit metrisch angepasstem Vertreter für den ersten Aufschlag. Kein Google-Font, kein CDN.
Logo und App-Icon
Ein eigenes Logo lässt sich als SVG, PNG, JPG, GIF oder WebP hochladen; es erscheint in der
Kopfzeile, auf der Anmeldeseite und im Info-Fenster. SVG bleibt die beste Wahl — es bleibt in jeder Größe
scharf und übernimmt mit currentColor sogar die Schemafarbe.
Aus dem Logo erzeugt die App auf Knopfdruck Favicon, iOS-Symbol, Browser-Icon und das maskierbare Android-Icon. Das rechnet der Browser auf einer Leinwand, nicht der Server: PHP kann von sich aus kein SVG rastern.
Betrieb
Hochladen, aufrufen, fertig
Shared Hosting genügt. Es gibt nichts zu bauen, nichts zu installieren und nichts zu abonnieren.
Installation
- Hochladen Das Release-ZIP entpacken und den Inhalt per FTP auf den Server legen.
data/,uploads/und die.htaccesslegt die App beim ersten Start selbst an. - Schreibrechte Der Zielordner muss für PHP beschreibbar sein. Meist stimmt das schon; falls nicht, auf
755setzen. - Aufrufen Die Adresse im Browser öffnen. Die Einrichtung startet automatisch und fragt nach einem Passwort.
- Fertig Nach dem Speichern bist du direkt angemeldet.
HTTPS verwenden. Das Passwort geht im Klartext über die Leitung,
wenn die Seite über http:// läuft. Praktisch jeder Hoster bietet ein kostenloses
Let’s-Encrypt-Zertifikat an.
Voraussetzungen
| PHP 7.4 oder neuer | 8.x wird empfohlen |
pdo_sqlite | fast überall Standard, wird beim Setup geprüft |
gd | optional, für erzeugte App-Icons |
zip | optional, für Backup-Downloads |
Keine externe Datenbank, kein Composer, kein Docker, kein npm. Alles, was ausgeliefert wird, liegt als lesbare Datei im Ordner.
Auf dem Handy: iOS über Safari → Teilen → Zum Home-Bildschirm; Android über Chrome → Menü → App installieren. Danach startet sie im Vollbild mit eigenem Icon.
Sicherheit
- Das Passwort wird als bcrypt-Hash gespeichert, nie im Klartext.
- Nach fünf Fehlversuchen greift eine Wartezeit, die sich bei weiteren Versuchen verdoppelt.
- Alle schreibenden Aufrufe brauchen einen CSRF-Token.
- Alle Datenbankzugriffe laufen über vorbereitete Statements.
- Hochgeladene SVGs werden serverseitig von
<script>, Event-Handlern und externen Referenzen befreit. data/undlib/sind gegen direkten Webzugriff gesperrt, inuploads/wird kein PHP ausgeführt.
Updates
Die App kann sich selbst aktualisieren — aus einem Verzeichnis auf deiner eigenen Domain. Sie prüft höchstens alle zwölf Stunden, meldet Neues mit einem Punkt am Info-Symbol und installiert auf Knopfdruck: mit Signaturprüfung, Sicherung des bisherigen Codes und Rückrollen, falls etwas schiefgeht.
Die Adresse von CSS und JavaScript ändert sich bei jedem Update automatisch mit. Deshalb muss niemand mehr den Browser-Verlauf leeren, damit Änderungen ankommen.
Wer nicht will, lässt es: Ohne eingetragene Update-Adresse läuft
die App normal weiter und meldet nie ein Update. Dateien per FTP ersetzen geht genauso —
alles außer data/ und uploads/.
Was wo liegt
| Ordner | Inhalt |
|---|---|
data/ | Deine Daten. tracker.sqlite und config.php. Nie überschreiben. |
uploads/ | Deine Bilder. Kundenlogos und App-Icon. |
lib/ | Start, Datenbank, Update, Schema-Variablen. |
assets/ | app.css, app.js, Schriften. Lesbar, ohne Build. |
| Wurzel | index.php, api.php, login.php, share.php und die kleinen Helfer. |
Alles Wertvolle liegt in data/ und uploads/.
Der Rest ist ersetzbarer Code. Ein Backup ist damit ein Kopiervorgang, keine Migration —
und die App bringt Export als CSV, JSON und ZIP gleich mit.
Passwort vergessen?
Es gibt bewusst keine Zurücksetzen-Funktion per E-Mail. Stattdessen: data/config.php
per FTP löschen — nicht data/tracker.sqlite, dort liegen die Daten —, die App
aufrufen, neues Passwort vergeben. Alle Positionen, Projekte und Kunden bleiben erhalten.