From 0bc92cdb8ba9bac879b869efe5af531289365bd6 Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 15 Oct 2026 04:42:30 -0700 Subject: [PATCH] frontend 1 --- README.md | 1 + frontend/app.js | 98 +++++++++++++++++++++++++++++++++++++++------ frontend/index.html | 75 +++++++++++++++++++++++++--------- main.py | 7 +++- 4 files changed, 148 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 8c7f965..1e1c58f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ This app is intended to use with tracking personal fitness providers and visits to them. This will allow for tracking the providers, clients, and visits, and is intended to be used by the client for record-keeping. Providers will have photos and hourly rates. Visits will have duration and provider info. The client using the app should have a dashboard that shows a summary of all visits, and areas to view past visits, add new providers, and add visits. This will require a web front-end and a database back-end. + diff --git a/frontend/app.js b/frontend/app.js index d106170..9084963 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -1,14 +1,88 @@ -// Simple fetch to populate visit list -const visitList = document.getElementById('visit-list'); -async function loadVisits() { - const resp = await fetch('http://localhost:8000/visits/'); - const data = await resp.json(); - visitList.innerHTML = ''; - data.forEach(v => { - const li = document.createElement('li'); - li.textContent = `${new Date(v.start_time).toLocaleString()} - ${v.duration_minutes} min`; - visitList.appendChild(li); +// --------------------------------------------------------------------- +// Helper: call the API relative to the current host (no hard‑coded host) +const api = async (method, endpoint, data) => { + const res = await fetch(`/${endpoint}`, { + method, + headers: { 'Content-Type': 'application/json' }, + body: data ? JSON.stringify(data) : null, }); + if (!res.ok) throw await res.json(); + return res.json(); +}; + +// --------------------------------------------------------------------- +// Load Providers +const providersList = document.getElementById('providers-list'); +const providerForm = document.getElementById('provider-form'); +const providerSelect = document.querySelector('#service-form select[name="provider_id"]'); +const visitProviderSelect = document.querySelector('#visit-form select[name="provider_id"]'); +async function loadProviders() { + const providers = await api('GET', 'providers/'); + providersList.innerHTML = ``; + const opts = providers.map(p => ``).join(''); + providerSelect.innerHTML = opts; + visitProviderSelect.innerHTML = opts; } -loadVisits(); -// Could add intervals or form handling later \ No newline at end of file +providerForm.addEventListener('submit', async e => { + e.preventDefault(); + const data = Object.fromEntries(new FormData(e.target).entries()); + if (!data.hourly_rate) delete data.hourly_rate; + await api('POST', 'providers/', data); + await loadProviders(); +}); + +// --------------------------------------------------------------------- +// Load Clients +const clientsList = document.getElementById('clients-list'); +const clientForm = document.getElementById('client-form'); +const clientSelect = document.querySelector('#visit-form select[name="client_id"]'); +async function loadClients() { + const clients = await api('GET', 'clients/'); + clientsList.innerHTML = ``; + clientSelect.innerHTML = clients.map(c => ``).join(''); +} +clientForm.addEventListener('submit', async e => { + e.preventDefault(); + const data = Object.fromEntries(new FormData(e.target).entries()); + await api('POST', 'clients/', data); + await loadClients(); +}); + +// --------------------------------------------------------------------- +// Load Services +const servicesList = document.getElementById('services-list'); +const serviceForm = document.getElementById('service-form'); +async function loadServices() { + const services = await api('GET', 'services/'); + servicesList.innerHTML = ``; +} +serviceForm.addEventListener('submit', async e => { + e.preventDefault(); + const data = Object.fromEntries(new FormData(e.target).entries()); + if (!data.duration_minutes) delete data.duration_minutes; + await api('POST', 'services/', data); + await loadServices(); +}); + +// --------------------------------------------------------------------- +// Load Visits +const visitsList = document.getElementById('visits-list'); +const visitForm = document.getElementById('visit-form'); +async function loadVisits() { + const visits = await api('GET', 'visits/'); + visitsList.innerHTML = ``; +} +visitForm.addEventListener('submit', async e => { + e.preventDefault(); + const data = Object.fromEntries(new FormData(e.target).entries()); + if (data.start_time) data.start_time = new Date(data.start_time).toISOString(); + await api('POST', 'visits/', data); + await loadVisits(); +}); + +// --------------------------------------------------------------------- +// Initial load +async function init() { + await Promise.all([loadProviders(), loadClients(), loadServices(), loadVisits()]); +} +init(); \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index a15cdc7..b178d51 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,20 +1,55 @@ - - - - - Fitness Tracker Dashboard - - - -

Fitness Tracker Dashboard

-
-

Visits

- -
- - - \ No newline at end of file +@@ +-

Fitness Tracker Dashboard

+-
+-

Visits

+- +-
+- ++

Fitness Tracker Dashboard

++ ++
++

Providers

++
++
++
++
++ ++
++
++
++ ++
++

Clients

++
++
++ ++
++
++
++ ++
++

Services

++
++
++
++
++
++ ++
++
++
++ ++
++

Visits

++
++
++
++
++
++ ++
++
++
++ ++ +*** End Patch diff --git a/main.py b/main.py index da46db7..b9e8fc1 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles +from fastapi.responses import RedirectResponse import models, database from routers import providers, clients, visits, services @@ -26,6 +27,10 @@ app.include_router(services.router, prefix="/services", tags=["services"]) @app.on_event("startup") async def startup(): database.Base.metadata.create_all(bind=database.engine) - # Mount frontend static files + # Mount frontend static files at /frontend app.mount("/frontend", StaticFiles(directory="frontend"), name="frontend") + # Simple redirect from root to /frontend for convenience + @app.get("/") + def root(): + return RedirectResponse(url="/frontend/")