frontend 1
This commit is contained in:
1 parent
168c817324
commit
0bc92cdb8b
4 files changed
+148
-33
No files matched your search
@@ -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.
|
||||
|
||||
|
||||
+86
-12
@@ -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 = `<ul>${providers.map(p => `<li>${p.name} (${p.hourly_rate || 'n/a'})</li>`).join('')}</ul>`;
|
||||
const opts = providers.map(p => `<option value="${p.id}">${p.name}</option>`).join('');
|
||||
providerSelect.innerHTML = opts;
|
||||
visitProviderSelect.innerHTML = opts;
|
||||
}
|
||||
loadVisits();
|
||||
// Could add intervals or form handling later
|
||||
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 = `<ul>${clients.map(c => `<li>${c.name}</li>`).join('')}</ul>`;
|
||||
clientSelect.innerHTML = clients.map(c => `<option value="${c.id}">${c.name}</option>`).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 = `<ul>${services.map(s => `<li>${s.name} @ $${s.price} (${s.duration_minutes || 'n/a'}min)</li>`).join('')}</ul>`;
|
||||
}
|
||||
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 = `<ul>${visits.map(v => `<li>${new Date(v.start_time).toLocaleString()} – ${v.duration_minutes}min (Provider #${v.provider_id} • Client #${v.client_id})</li>`).join('')}</ul>`;
|
||||
}
|
||||
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();
|
||||
+55
-20
@@ -1,20 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Fitness Tracker Dashboard</title>
|
||||
<style>
|
||||
body {font-family: Arial, sans-serif; margin: 20px;}
|
||||
.card {border: 1px solid #ddd; padding: 10px; margin-bottom: 10px;}
|
||||
h2 {margin-top: 0;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Fitness Tracker Dashboard</h1>
|
||||
<div id="visits" class="card">
|
||||
<h2>Visits</h2>
|
||||
<ul id="visit-list"></ul>
|
||||
</div>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@
|
||||
- <h1>Fitness Tracker Dashboard</h1>
|
||||
- <div id="visits" class="card">
|
||||
- <h2>Visits</h2>
|
||||
- <ul id="visit-list"></ul>
|
||||
- </div>
|
||||
- <script src="app.js"></script>
|
||||
+ <h1>Fitness Tracker Dashboard</h1>
|
||||
+
|
||||
+ <div class="card">
|
||||
+ <h2>Providers</h2>
|
||||
+ <form id="provider-form">
|
||||
+ <div><label>Name: <input name="name" required></label></div>
|
||||
+ <div><label>Photo URL: <input name="photo_url"></label></div>
|
||||
+ <div><label>Hourly Rate: <input name="hourly_rate" type="number" step="0.01"></label></div>
|
||||
+ <button type="submit">Add Provider</button>
|
||||
+ </form>
|
||||
+ <div class="list" id="providers-list"></div>
|
||||
+ </div>
|
||||
+
|
||||
+ <div class="card">
|
||||
+ <h2>Clients</h2>
|
||||
+ <form id="client-form">
|
||||
+ <div><label>Name: <input name="name" required></label></div>
|
||||
+ <button type="submit">Add Client</button>
|
||||
+ </form>
|
||||
+ <div class="list" id="clients-list"></div>
|
||||
+ </div>
|
||||
+
|
||||
+ <div class="card">
|
||||
+ <h2>Services</h2>
|
||||
+ <form id="service-form">
|
||||
+ <div><label>Provider: <select name="provider_id" required></select></label></div>
|
||||
+ <div><label>Name: <input name="name" required></label></div>
|
||||
+ <div><label>Price: <input name="price" type="number" step="0.01" required></label></div>
|
||||
+ <div><label>Duration (min): <input name="duration_minutes" type="number"></label></div>
|
||||
+ <button type="submit">Add Service</button>
|
||||
+ </form>
|
||||
+ <div class="list" id="services-list"></div>
|
||||
+ </div>
|
||||
+
|
||||
+ <div class="card">
|
||||
+ <h2>Visits</h2>
|
||||
+ <form id="visit-form">
|
||||
+ <div><label>Provider: <select name="provider_id" required></select></label></div>
|
||||
+ <div><label>Client: <select name="client_id" required></select></label></div>
|
||||
+ <div><label>Start Time: <input name="start_time" type="datetime-local"></label></div>
|
||||
+ <div><label>Duration (min): <input name="duration_minutes" type="number" required></label></div>
|
||||
+ <button type="submit">Add Visit</button>
|
||||
+ </form>
|
||||
+ <div class="list" id="visits-list"></div>
|
||||
+ </div>
|
||||
+
|
||||
+ <script src="app.js"></script>
|
||||
*** End Patch
|
||||
@@ -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/")
|
||||
|
||||
Reference in new issue
Block a user