尚未收集仓库信息。
尚未检测到更新。GitHub 发布版本后会显示在这里。
powerlift tracker
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Gym-tracking webapp met twee kanten:
/app, mobile-first) voor sporters: training van vandaag volgens het schema, trainingen loggen met doelen, vorige prestatie, voorgesteld gewicht, schijvencalculator, PR-meldingen, progressiegrafieken, maxima per blok, lichaamsgewicht, wekelijkse check-in, historiek met trainersfeedback en import uit Google Sheets./trainer, desktop-first) voor coaches: overzicht van alle cliënten (met markering bij 7+ dagen inactief of pijn "BAD"), cliëntdetail, schema's bouwen en toewijzen, oefeningenbibliotheek, feedback op trainingen en import voor een cliënt.De naam "Liftlog" is een voorlopige naam. Je past hem op één plek aan: src/lib/config.ts.
| Onderdeel | Keuze |
|---|---|
| Framework | Next.js 16 (App Router, Server Components, Server Actions) + React 19 + TypeScript |
| Styling | Tailwind CSS v4, met design tokens (ink, chalk, lime) in src/app/globals.css |
| Database en auth | Supabase (Postgres, Supabase Auth, Row Level Security) via @supabase/ssr |
| Grafieken | Recharts |
| Excel inlezen | SheetJS 0.20.3, geïnstalleerd van cdn.sheetjs.com (niet de verouderde npm-versie) |
| Tests | Vitest |
| Hosting | Vercel (Hobby) + Supabase (Free) |
supabase/schema.sql en klik Run. Dit maakt alle tabellen, RLS-policies, triggers en functies aan, plus een standaardbibliotheek van 29 oefeningen die elke gebruiker ziet (ook een nieuwe trainer).supabase/seed.sql uit voor demo-accounts en voorbeeldtrainingen.Online (productie) voer je enkel
schema.sqluit. De demo-accounts inseed.sqlhebben een publiek gekend wachtwoord: iedereen die je repo ziet, zou ermee kunnen inloggen. Toch per ongeluk uitgevoerd? Draai dansupabase/remove-demo.sql. Dat verwijdert enkel de drie demo-accounts en hun data.
schema.sqlbegint met het opruimen van bestaande tabellen. Opnieuw uitvoeren wist dus alle app-data. Handig tijdens development, niet in productie.
Demo-accounts uit seed.sql (wachtwoord voor alle drie: liftlog-demo, enkel voor lokaal testen):
| Rol | Wat zie je | |
|---|---|---|
[email protected] | trainer | uitnodigingscode DEMO42, twee cliënten, een schema |
[email protected] | member | 4 weken trainingen, progressie, check-ins, feedback |
[email protected] | member | 10 dagen niet getraind en pijn "BAD": gemarkeerd bij de trainer |
In Authentication:
http://localhost:3000 (later je Vercel-URL).http://localhost:3000/** toe (en later https://jouw-app.vercel.app/**).{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email
Zo werkt de bevestigingslink ook als je hem in een andere browser opent. De standaardlink werkt ook: src/app/auth/confirm/route.ts ondersteunt beide.cp .env.example .env.local
Vul in .env.local in (je vindt beide in Project Settings → API Keys):
NEXT_PUBLIC_SUPABASE_URL: de project-URL, bv. https://abcdefgh.supabase.coNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: de publishable key (sb_publishable_…). Een oudere anon key werkt ook.Deze key mag publiek zijn: de beveiliging zit in Row Level Security, niet in het geheimhouden van de key. Gebruik in deze app nooit de secret of service_role key.
npm install
npm run dev
Open http://localhost:3000 en log in met een demo-account, of registreer een nieuw account (kies sporter of trainer).
NEXT_PUBLIC_SUPABASE_URL en NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY toe.https://jouw-app.vercel.app/** toe aan de Redirect URLs.Een gratis Supabase-project pauzeert na een week zonder activiteit. Je zet het weer aan via het dashboard.
Waarom niet via FTP (FileZilla) op gewone webhosting? Liftlog draait code op de server (Server Components, Server Actions,
proxy.ts). Daarvoor moet er een Node.js-proces blijven draaien. Gewone webhosting (bv. Combell) serveert enkel bestanden en PHP, dus losse bestanden uploaden geeft geen werkende app.
Wil je liftlog.jouwdomein.be in plaats van ….vercel.app? De app blijft op Vercel draaien, je domein wijst er enkel naar.
liftlog.jouwdomein.be in. Vercel toont welk DNS-record nodig is (een CNAME, meestal naar cname.vercel-dns.com).CNAME, hostnaam liftlog, waarde zoals Vercel aangeeft.https://liftlog.jouwdomein.be en voeg https://liftlog.jouwdomein.be/** toe aan de Redirect URLs. Zet in Vercel ook NEXT_PUBLIC_SITE_URL op dat adres en klik Redeploy.schema.sql begint met alles op te ruimen, dus die voer je online maar één keer uit. Latere wijzigingen staan in supabase/updates/, op datum. Zo'n bestand wist niets en mag je veilig opnieuw draaien: plak het in de SQL Editor en klik Run, vóór je de nieuwe code naar main pusht.
| Bestand | Wat |
|---|---|
2026-10-30-geplande-trainingen-afwerken.sql | Geplande sessies uit je sheet afwerken in de app |
2026-10-30-meldingen.sql | Meldingen (afgewerkte training, nieuw schema, feedback) met Realtime |
2026-10-31-blokopbouw.sql | Blokopbouw (RPE per week omhoog) en een geïmporteerde maand opschuiven |
2026-10-31-blok-bij-afwerken.sql | Een afgewerkte sessie blijft bij haar blok (planned_tab) |
2026-10-31-concepten.sql | Training automatisch opslaan op de server tijdens het loggen |
| Commando | Wat het doet |
|---|---|
npm run dev | Development-server op poort 3000 |
npm run build | Productiebuild (controleert ook TypeScript) |
npm start | Productiebuild draaien |
npm run lint | ESLint |
npm test | Unit tests (Vitest) |
tests/import-parser.test.ts test de import op de echte voorbeeldsheet fixtures/gymSheet.xlsx en op een kleine synthetische sheet voor varianten die niet in de fixture zitten.tests/import-integration.test.ts importeert de fixture écht in Supabase (als [email protected]), twee keer, en controleert dat er geen dubbels ontstaan. Hij wordt overgeslagen tenzij je een database opgeeft:SUPABASE_TEST_URL=http://127.0.0.1:54321 SUPABASE_TEST_KEY=sb_publishable_... npm test
Gebruik daarvoor een lokale Supabase (npx supabase start, met Docker) of een testproject, nooit productie: de test maakt en verwijdert data.
src/
proxy.ts Next 16-naam voor "middleware": sessie verversen + routes afschermen
app/
(auth)/login, register Inloggen en registreren (+ Server Actions in actions.ts)
auth/confirm/route.ts Bevestigingslink uit de e-mail
app/ URL /app: gebruikersapp (layout controleert rol "member")
log/ Training loggen: logger, Server Actions
schema/ progress/ checkin/ history/ import/ profile/
trainer/ URL /trainer: dashboard (layout controleert rol "trainer")
clients/[id]/ Cliëntdetail, trainingen + feedback, import
programs/ exercises/ Schema-editor en oefeningenbibliotheek
components/ Gedeelde UI (ui/, charts/, progress/, import/)
lib/
supabase/ Supabase-clients (server en proxy)
auth.ts getCurrentProfile() en requireRole()
data.ts Leesfuncties voor de database
import/ Parser, datums, naamkoppeling, payload, Server Actions
supabase/schema.sql Structuur + RLS + standaardbibliotheek (ook online)
supabase/seed.sql Demo-accounts en -trainingen (enkel lokaal)
supabase/remove-demo.sql Demo-accounts weer verwijderen
fixtures/gymSheet.xlsx
tests/
Server of Client Component? Standaard is alles een Server Component: data wordt op de server opgehaald en er gaat enkel HTML naar de browser. Een bestand begint pas met "use client" als het state, effects of browser-API's nodig heeft (formulieren met directe feedback, de logger, de RPE-calculator, grafieken). Elk bestand legt bovenaan uit waarom het het een of het ander is.
Alles staat in src/lib/rpe.ts (pure functies, getest in tests/powerlifting.test.ts).
RPE van de coaching-sheet. In de logger kan je de tabel openklappen, met de betekenis van elke RPE in "reps in reserve"./app/rpe voor sporters, /trainer/rpe voor trainers), met drie standen:
src/lib/supabase/proxy.ts): niet ingelogd → /login; een trainer op /app of een member op /trainer → eigen startpagina.src/lib/auth.ts): elke sectie-layout, pagina en Server Action roept requireRole() aan. Een Server Action is een publiek endpoint, ook als de knop verborgen is.supabase/schema.sql): de database zelf bepaalt welke rijen je ziet. Een member ziet enkel eigen data; een trainer ziet de data van zijn gekoppelde cliënten, mag voor hen importeren en beheert eigen schema's, oefeningen en feedback.Extra:
authenticated mag in profiles enkel full_name wijzigen.join_trainer) en een uitnodigingscode vernieuwen (regenerate_invite_code) gaan via databasefuncties die zelf controleren wie wat mag.Invoer: plak de deellink (de sheet moet gedeeld zijn met "Iedereen met de link"), of upload een .xlsx (Bestand → Downloaden → Microsoft Excel). De server haalt de sheet op als .xlsx via https://docs.google.com/spreadsheets/d/{ID}/export?format=xlsx.
Flow: bron kiezen → preview (tabs, weken, sessies, oefeningen, waarschuwingen; startdatum per maandtab en oefeningkoppelingen aanpasbaar) → bevestigen. De server parset het bestand bij het bevestigen opnieuw en vertrouwt enkel de keuzes uit de browser. Alles gaat in één transactie naar de databasefunctie import_sheet. Een herimport van dezelfde tab vervangt de vorige import (geen dubbels).
Hoe de parser de sheet leest (src/lib/import/parser.ts):
MMMYYYY (JUL2026, ook OKT/OCT). RPE en Externals worden overgeslagen. Van Info neemt hij enkel de startdatum, de lijst "Beschikbare oefening" en de maxima per blok.WEEK n-koppen, of via de SESSIE 1-koppen als die er niet zijn. Kolommen zoekt hij op kopteksten (SETS, REPS, …), met de standaardvolgorde enkel als terugvaloptie.reps_estimated), bij een RPE-bereik het gemiddelde. De originele celwaarden staan altijd in het raw-veld.GOOD wordt AMAZING (positief op een schaal van drie). Andere waarden worden leeg, met een waarschuwing.Geplande sessies afwerken: een sessie die in de sheet nog niet gedaan is, staat bij Historiek → Nog af te werken. Met Afwerken open je ze in de logger, met de doelen uit de sheet. Bij opslaan wordt die rij een afgewerkte training (dezelfde id, los van de import). Importeer je die maand later opnieuw, dan herkent de import de sessie aan de oorspronkelijke datum en titel en zet hij de planning er niet nog eens bij.
Exporteren: Historiek → Exporteren (.xlsx) (en de trainer via de cliëntpagina) downloadt een Excel-bestand in hetzelfde template als de coachingsheet: een tab Info met de maxima per blok, per maand een tab (AUG2026) met de weken naast elkaar en de sessies onder elkaar, en een tab Lichaamsgewicht. Een week hoort bij de maand van haar maandag, zodat week 1 op de eerste maandag start, zoals de import aanneemt: je kan de export dus meteen opnieuw importeren. Een test leest de echte sheet in, exporteert en importeert opnieuw, en vergelijkt elke set.
Valkuil in de echte sheet: Google Sheets zet tekst als 10-12 (reps) of 6-7 (RPE) automatisch om naar een datum. In de cel staat dan intern een getal (46366), op het scherm "10-12". De parser leest bij zulke datumcellen daarom de getoonde tekst.
cacheComponents: false in next.config.ts). Elke pagina hangt af van de ingelogde gebruiker, dus er valt niets statisch te prerenderen. Het klassieke dynamische model is hier eenvoudiger te volgen.tailwind.config.js: de configuratie (en dus de kleurtokens) staat in CSS onder @theme. Het standaardpalet is gewist, zodat er geen andere kleuren kunnen insluipen.chalk, #ECECE4) voor tekst en 1px-lijnen. Lime (#D7FF3A) is nu enkel nog een accent: primaire knoppen, de actieve tab, markeringen ("aandacht nodig", pijn "BAD"), afgevinkte sets, persoonlijke records, focus en de lijnen in grafieken. Variatie gebeurt nog altijd via opacity, nooit via extra tinten.proxy.ts in plaats van middleware.ts: zo heet het bestand sinds Next.js 16.workouts.status (gepland/uitgevoerd), workout_exercises.performed, en in weekly_checkins ook overall_feeling, sessions_done en raw.schema.sql, niet in seed.sql. Zo krijgt ook een online installatie zonder demodata meteen een bruikbare bibliotheek.log_workout (een training loggen) en import_sheet (de import), zodat er nooit een halve training in de database belandt.