Compare commits
5 Commits
847cac4bba
...
1926382021
| Author | SHA1 | Date | |
|---|---|---|---|
| 1926382021 | |||
| 65b2abdad6 | |||
| 81daf2aa0c | |||
| 3812dd5d47 | |||
| e1130fa493 |
@@ -1,7 +1,7 @@
|
|||||||
import locale
|
import locale
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from typing import Annotated, Union
|
from typing import Annotated
|
||||||
|
|
||||||
import starlette.status as status
|
import starlette.status as status
|
||||||
from fastapi import Depends, FastAPI, Request
|
from fastapi import Depends, FastAPI, Request
|
||||||
@@ -10,7 +10,8 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from sqlmodel import Session, SQLModel, create_engine, select
|
from sqlmodel import Session, SQLModel, create_engine, select
|
||||||
|
|
||||||
from models import Event, Household, Registration, TeamRegistration
|
from models import (Event, Household, Registration, Subscription,
|
||||||
|
TeamRegistration)
|
||||||
|
|
||||||
sqlite_file_name = "database.db"
|
sqlite_file_name = "database.db"
|
||||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||||
@@ -45,16 +46,109 @@ SessionDep = Annotated[Session, Depends(get_session)]
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def read_root(request: Request, session: SessionDep):
|
async def index(request: Request, session: SessionDep):
|
||||||
statement = select(Event).order_by(Event.event_time)
|
"""Displays coming events and a button to register new ones"""
|
||||||
|
now = datetime.now()
|
||||||
|
# TODO: Once we refactored to use SQLAlchemy directly, we can probably do a nicer filtering on the date alone
|
||||||
|
statement = (
|
||||||
|
select(Event)
|
||||||
|
.order_by(Event.event_time)
|
||||||
|
.where(Event.event_time >= now - timedelta(days=1))
|
||||||
|
)
|
||||||
events = session.exec(statement).all()
|
events = session.exec(statement).all()
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
name="index.html",
|
name="index.html",
|
||||||
context={"events": events, "current_page": "home", "now": datetime.now()},
|
context={"events": events, "current_page": "home", "now": now},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/past_events")
|
||||||
|
async def past_events(request: Request, session: SessionDep):
|
||||||
|
now = datetime.now()
|
||||||
|
# TODO: Once we refactored to use SQLAlchemy directly, we can probably do a nicer filtering on the date alone
|
||||||
|
statement = (
|
||||||
|
select(Event)
|
||||||
|
.order_by(Event.event_time)
|
||||||
|
.where(Event.event_time < now - timedelta(days=1))
|
||||||
|
)
|
||||||
|
events = session.exec(statement).all()
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request=request,
|
||||||
|
name="index.html",
|
||||||
|
context={"events": events, "current_page": "past", "now": now},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/subscribe")
|
||||||
|
async def subscribe(request: Request, session: SessionDep):
|
||||||
|
statement = select(Household)
|
||||||
|
households = session.exec(statement).all()
|
||||||
|
|
||||||
|
# filter out households with existing registrations
|
||||||
|
# households = [
|
||||||
|
# h
|
||||||
|
# for h in households
|
||||||
|
# if h.id not in [reg.household_id for reg in event.registrations]
|
||||||
|
# ]
|
||||||
|
|
||||||
|
subscriptions = session.exec(select(Subscription)).all()
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request=request,
|
||||||
|
name="subscribe.html",
|
||||||
|
context={"households": households, "subscriptions": subscriptions},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/subscribe")
|
||||||
|
async def add_subscribe(request: Request, session: SessionDep):
|
||||||
|
form_data = await request.form()
|
||||||
|
# TODO: Make this return a nicer error message
|
||||||
|
try:
|
||||||
|
num_adult_meals = int(form_data["numAdults"]) if form_data["numAdults"] else 0
|
||||||
|
num_children_meals = int(form_data["numKids"]) if form_data["numKids"] else 0
|
||||||
|
num_small_children_meals = (
|
||||||
|
int(form_data["numSmallKids"]) if form_data["numSmallKids"] else 0
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError("All number fields must be integers")
|
||||||
|
|
||||||
|
subscription = Subscription(
|
||||||
|
household_id=form_data["household"],
|
||||||
|
num_adult_meals=num_adult_meals,
|
||||||
|
num_children_meals=num_children_meals,
|
||||||
|
num_small_children_meals=num_small_children_meals,
|
||||||
|
)
|
||||||
|
|
||||||
|
selected_days = form_data.getlist("days")
|
||||||
|
if selected_days:
|
||||||
|
subscription.monday = "1" in selected_days
|
||||||
|
subscription.tuesday = "2" in selected_days
|
||||||
|
subscription.wednesday = "3" in selected_days
|
||||||
|
subscription.thursday = "4" in selected_days
|
||||||
|
subscription.friday = "5" in selected_days
|
||||||
|
subscription.saturday = "6" in selected_days
|
||||||
|
subscription.sunday = "7" in selected_days
|
||||||
|
|
||||||
|
session.add(subscription)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return RedirectResponse(url="/subscribe", status_code=status.HTTP_302_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/subscribe/{household_id}/delete")
|
||||||
|
async def delete_subscription(request: Request, session: SessionDep, household_id: int):
|
||||||
|
|
||||||
|
statement = select(Subscription).where(Subscription.household_id == household_id)
|
||||||
|
sub = session.exec(statement).one()
|
||||||
|
|
||||||
|
session.delete(sub)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return RedirectResponse(url="/subscribe", status_code=status.HTTP_302_FOUND)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/event/add")
|
@app.get("/event/add")
|
||||||
async def add_event_form(request: Request, session: SessionDep):
|
async def add_event_form(request: Request, session: SessionDep):
|
||||||
return templates.TemplateResponse(request=request, name="add_event.html")
|
return templates.TemplateResponse(request=request, name="add_event.html")
|
||||||
@@ -107,7 +201,7 @@ async def read_event(request: Request, event_id: int, session: SessionDep):
|
|||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
name="event.html",
|
name="event.html",
|
||||||
context={"event": event, "households": households},
|
context={"event": event, "households": households, "now": datetime.now()},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import typing
|
import typing
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from xmlrpc.client import DateTime
|
||||||
|
|
||||||
from sqlmodel import Field, Relationship, SQLModel, String
|
from sqlmodel import Field, Relationship, SQLModel, String
|
||||||
|
|
||||||
@@ -79,3 +80,51 @@ class Registration(SQLModel, table=True):
|
|||||||
comment: str | None
|
comment: str | None
|
||||||
|
|
||||||
household: Household = Relationship()
|
household: Household = Relationship()
|
||||||
|
|
||||||
|
|
||||||
|
class Subscription(SQLModel, table=True):
|
||||||
|
household_id: int | None = Field(
|
||||||
|
default=None, foreign_key="household.id", primary_key=True
|
||||||
|
)
|
||||||
|
num_adult_meals: int
|
||||||
|
num_children_meals: int
|
||||||
|
num_small_children_meals: int
|
||||||
|
comment: str | None
|
||||||
|
|
||||||
|
last_modified: datetime = Field(default_factory=datetime.now, nullable=False)
|
||||||
|
|
||||||
|
monday: bool = True
|
||||||
|
tuesday: bool = True
|
||||||
|
wednesday: bool = True
|
||||||
|
thursday: bool = True
|
||||||
|
friday: bool = True
|
||||||
|
saturday: bool = True
|
||||||
|
sunday: bool = True
|
||||||
|
|
||||||
|
household: Household = Relationship()
|
||||||
|
|
||||||
|
def day_string_de(self) -> str:
|
||||||
|
"""
|
||||||
|
Generates a string representation of selected days in German short form.
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
|
||||||
|
if self.monday:
|
||||||
|
result.append("Mo")
|
||||||
|
if self.tuesday:
|
||||||
|
result.append("Di")
|
||||||
|
if self.wednesday:
|
||||||
|
result.append("Mi")
|
||||||
|
if self.thursday:
|
||||||
|
result.append("Do")
|
||||||
|
if self.friday:
|
||||||
|
result.append("Fr")
|
||||||
|
if self.saturday:
|
||||||
|
result.append("Sa")
|
||||||
|
if self.sunday:
|
||||||
|
result.append("So")
|
||||||
|
|
||||||
|
if len(result) < 7:
|
||||||
|
return ", ".join(result)
|
||||||
|
else:
|
||||||
|
return "Alle"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ requires-python = ">=3.13"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi[standard]>=0.116.0",
|
"fastapi[standard]>=0.116.0",
|
||||||
"sqlmodel>=0.0.24",
|
"sqlmodel>=0.0.24",
|
||||||
|
"uvicorn[standard]>=0.35.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
|||||||
29
new-registration-app/static/css/allmende.css
Normal file
29
new-registration-app/static/css/allmende.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/* theme.css */
|
||||||
|
/* Green Bootstrap Theme */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bs-primary: #198754;
|
||||||
|
--bs-primary-rgb: 25, 135, 84;
|
||||||
|
--bs-success: #198754;
|
||||||
|
--bs-success-rgb: 25, 135, 84;
|
||||||
|
--bs-link-color: var(--bs-primary);
|
||||||
|
--bs-link-hover-color: #146c43;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Explicit fallback overrides for older versions (<=5.2) */
|
||||||
|
.btn-primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #198754;
|
||||||
|
border-color: #198754;
|
||||||
|
}
|
||||||
|
.btn-primary:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #157347;
|
||||||
|
border-color: #146c43;
|
||||||
|
}
|
||||||
|
.btn-primary:focus,
|
||||||
|
.btn-primary:active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #146c43;
|
||||||
|
border-color: #125c39;
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
<title>Allmende Essen</title>
|
<title>Allmende Essen</title>
|
||||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="/static/icons/bootstrap-icons.min.css" rel="stylesheet">
|
<link href="/static/icons/bootstrap-icons.min.css" rel="stylesheet">
|
||||||
|
<link href="/static/css/allmende.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body class="p-3 m-0 border-0 bd-example">
|
<body class="p-3 m-0 border-0 bd-example">
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
@@ -21,14 +22,14 @@
|
|||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {% if current_page == 'home' %}active{% endif %}" {% if current_page == 'home' %}aria-current="page"{% endif %} href="/">Home</a>
|
<a class="nav-link {% if current_page == 'home' %}active{% endif %}" {% if current_page == 'home' %}aria-current="page"{% endif %} href="/">Kommende</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {% if current_page == 'vergangene' %}active{% endif %}" {% if current_page == 'vergangene' %}aria-current="page"{% endif %} href="/vergangene">Vergangene</a>
|
<a class="nav-link {% if current_page == 'past' %}active{% endif %}" {% if current_page == 'past' %}aria-current="page"{% endif %} href="/past_events">Vergangene</a>
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link {% if current_page == 'preise' %}active{% endif %}" {% if current_page == 'preise' %}aria-current="page"{% endif %} href="/preise">Preise</a>
|
|
||||||
</li>
|
</li>
|
||||||
|
<!-- <li class="nav-item">-->
|
||||||
|
<!-- <a class="nav-link {% if current_page == 'preise' %}active{% endif %}" {% if current_page == 'preise' %}aria-current="page"{% endif %} href="/preise">Preise</a>-->
|
||||||
|
<!-- </li>-->
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,11 +20,12 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row m-2">
|
<div class="row m-2">
|
||||||
<div class="col-md-4 d-flex justify-content-center align-items-center flex-column">
|
<div class="col-md-4 d-flex justify-content-center align-items-center flex-column">
|
||||||
|
<p class="text-muted w-100 mb-2">Anmeldung schließt {{ event.registration_deadline.strftime('%A, %d.%m.%Y, %H:%M Uhr') }}</p>
|
||||||
<!-- Button trigger modal -->
|
<!-- Button trigger modal -->
|
||||||
<button type="button" class="btn btn-primary mb-2 w-100" data-bs-toggle="modal" data-bs-target="#registration">
|
<button type="button" class="btn btn-primary mb-2 w-100" {% if event.registration_deadline < now %}disabled{% endif%} data-bs-toggle="modal" data-bs-target="#registration">
|
||||||
Anmeldung hinzufügen
|
Anmeldung hinzufügen
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-primary mb-2 w-100" {% if event.all_teams_max() %}disabled{% endif %} data-bs-toggle="modal" data-bs-target="#teamRegistration">
|
<button type="button" class="btn btn-primary mb-2 w-100" {% if event.all_teams_max() or event.registration_deadline < now %}disabled{% endif %} data-bs-toggle="modal" data-bs-target="#teamRegistration">
|
||||||
Dienst übernehmen
|
Dienst übernehmen
|
||||||
</button>
|
</button>
|
||||||
{% if event.recipe_link %}
|
{% if event.recipe_link %}
|
||||||
@@ -162,15 +163,14 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>Wenn dein Haushalt nicht auswählbar ist, existiert schon eine Anmeldung. Wenn du die Anmeldung ändern willst, lösche die bestehende Anmeldung und lege eine neue an.</p>
|
<p>Wenn dein Haushalt nicht auswählbar ist, existiert schon eine Anmeldung. Wenn du die Anmeldung ändern willst, lösche die bestehende Anmeldung und lege eine neue an.</p>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<select name="household" class="form-select" aria-label="Multiple select example">
|
<select name="household" class="form-select" aria-label="Multiple select example" required>
|
||||||
<option selected>Wer?</option>
|
<option value="" disabled selected hidden>Wer?</option>
|
||||||
{% for household in households %}
|
{% for household in households %}
|
||||||
<option value="{{household.id}}">{{household.name}}</option>
|
<option value="{{household.id}}">{{household.name}}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<label for="InputAdults" class="form-label">Anzahl Erwachsene</label>
|
<label for="InputAdults" class="form-label">Anzahl Erwachsene</label>
|
||||||
|
|||||||
@@ -2,13 +2,29 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4 mb-3">
|
<div class="row mt-4 mb-3">
|
||||||
<div class="col d-flex justify-content-between align-items-center">
|
<div class="col d-flex justify-content-between align-items-center">
|
||||||
<h2>Kommende Events</h2>
|
<h2>{% if current_page == "home" %}Kommende{% else %}Vergangene{% endif %} Kochabende</h2>
|
||||||
<a href="/event/add" class="btn btn-primary">
|
<a href="/event/add" class="btn btn-primary">
|
||||||
<i class="bi bi-plus-circle"></i> Neues Event erstellen
|
<i class="bi bi-plus-circle"></i> Neues Event erstellen
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="card bg-success-subtle text-success-emphasis border-0 shadow-sm text-center">
|
||||||
|
<div class="card-body py-4">
|
||||||
|
<i class="bi bi-calendar-heart fs-3 mb-2"></i>
|
||||||
|
<h5 class="card-title mb-2">Nie wieder die Anmeldung vergessen</h5>
|
||||||
|
<p class="card-text small mb-3">
|
||||||
|
Die Dauerhafte Anmeldung gilt für alle kommenden Kochabende.
|
||||||
|
</p>
|
||||||
|
<a href="/subscribe" class="btn btn-light btn-sm fw-semibold px-3">
|
||||||
|
Jetzt dauerhaft Anmelden
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||||
|
|
||||||
|
|
||||||
{% for event in events %}
|
{% for event in events %}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="card h-100 shadow-sm">
|
||||||
|
|||||||
134
new-registration-app/templates/subscribe.html
Normal file
134
new-registration-app/templates/subscribe.html
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mt-4">
|
||||||
|
<!-- Left column: subscription form -->
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card shadow-sm mt-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h1 class="fs-5 mb-0">Dauerhafte Anmeldung zu allen Kochabenden</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="/subscribe" method="POST">
|
||||||
|
<div class="card-body">
|
||||||
|
<p>
|
||||||
|
Mit einer dauerhaften Anmeldung kannst du dich/euch für alle zukünftigen Kochabende
|
||||||
|
anmelden. Es ist möglich
|
||||||
|
diese Anmeldung auf bestimmte Wochentage zu beschränken.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Dauerhafte Anmeldungen werden eine Woche vor einem Kochabend als Anmeldungen für diesen
|
||||||
|
Abend eingetragen. Danach
|
||||||
|
können sie auch noch gelöscht bzw. bearbeitet werden.
|
||||||
|
</p>
|
||||||
|
<!-- Household selection -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<select name="household" class="form-select" required>
|
||||||
|
<option value="" disabled selected hidden>Wer?</option>
|
||||||
|
{% for household in households %}
|
||||||
|
<option value="{{household.id}}">{{household.name}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">
|
||||||
|
Wenn dein Haushalt hier nicht auswählbar ist, besteht bereits eine dauerhafte Anmeldung.
|
||||||
|
Um Änderungen vorzunehmen, lösche die bestehende Anmeldung und lege eine neue an.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Person counts -->
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<label for="InputAdults" class="form-label">Anzahl Erwachsene</label>
|
||||||
|
<input name="numAdults" id="InputAdults" type="number" class="form-control"
|
||||||
|
aria-label="Anzahl Erwachsene" min="0" step="1" inputmode="numeric">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label for="InputKids" class="form-label">Anzahl Kinder >7</label>
|
||||||
|
<input name="numKids" id="InputKids" type="number" class="form-control"
|
||||||
|
aria-label="Anzahl Kinder >7" min="0" step="1" inputmode="numeric">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label for="InputSmallKids" class="form-label">Anzahl Kinder <7</label>
|
||||||
|
<input name="numSmallKids" id="InputSmallKids" type="number" class="form-control"
|
||||||
|
aria-label="Anzahl Kinder <7" min="0" step="1" inputmode="numeric">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Days of the week -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Wochentage auswählen (optional)</label>
|
||||||
|
<p class="text-muted small mb-2">
|
||||||
|
Wenn du nur an bestimmten Tagen teilnehmen möchtest, wähle sie hier aus.
|
||||||
|
</p>
|
||||||
|
<div class="d-flex flex-wrap gap-3">
|
||||||
|
{% set days = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag",
|
||||||
|
"Sonntag"] %}
|
||||||
|
{% for day in days %}
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="days" value="{{loop.index}}"
|
||||||
|
id="day-{{loop.index}}">
|
||||||
|
<label class="form-check-label" for="day-{{loop.index}}">
|
||||||
|
{{day}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="card-footer d-flex justify-content-end gap-2">
|
||||||
|
<button type="submit" class="btn btn-primary">Dauerhaft anmelden</button>
|
||||||
|
<a href="/" class="btn btn-secondary">Abbrechen</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right column: existing registrations -->
|
||||||
|
<div class="col-12 col-lg-6 mt-4 mt-lg-0">
|
||||||
|
<p class="h4 m-2">Bestehende dauerhafte Anmeldungen</p>
|
||||||
|
{% if subscriptions | length == 0 %}
|
||||||
|
<p class="m-2">Es gibt noch keine dauerhaften Anmeldungen</p>
|
||||||
|
{% else %}
|
||||||
|
{% for sub in subscriptions %}
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-body py-2 px-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
|
<h6 class="mb-0">{{ sub.household.name }}</h6>
|
||||||
|
<a href="/subscribe/{{sub.household.id}}/delete" class="text-danger">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<div class="text-muted" style="font-size: 0.7rem;">Erwachsene</div>
|
||||||
|
<div class="fw-bold small">{{ sub.num_adult_meals }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<div class="text-muted" style="font-size: 0.7rem;">Kinder</div>
|
||||||
|
<div class="fw-bold small">{{ sub.num_children_meals }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<div class="text-muted" style="font-size: 0.7rem;">Kleinkinder</div>
|
||||||
|
<div class="fw-bold small">{{ sub.num_small_children_meals }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<div class="text-muted" style="font-size: 0.7rem;">Tage</div>
|
||||||
|
<div class="fw-bold small">{{ sub.day_string_de() }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
2
new-registration-app/uv.lock
generated
2
new-registration-app/uv.lock
generated
@@ -329,6 +329,7 @@ source = { virtual = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "fastapi", extra = ["standard"] },
|
{ name = "fastapi", extra = ["standard"] },
|
||||||
{ name = "sqlmodel" },
|
{ name = "sqlmodel" },
|
||||||
|
{ name = "uvicorn", extra = ["standard"] },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
@@ -341,6 +342,7 @@ dev = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.116.0" },
|
{ name = "fastapi", extras = ["standard"], specifier = ">=0.116.0" },
|
||||||
{ name = "sqlmodel", specifier = ">=0.0.24" },
|
{ name = "sqlmodel", specifier = ">=0.0.24" },
|
||||||
|
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.35.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
|
|||||||
Reference in New Issue
Block a user