Erster Wurf
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
google_sheet:
|
google_sheet:
|
||||||
sheet_id: "15_SuNjWruwHcX4_R5OZPYb8Zwa__DrQ56ZcAodPj7QQ"
|
sheet_id: "15_SuNjWruwHcX4_R5OZPYb8Zwa__DrQ56ZcAodPj7QQ"
|
||||||
gid_times: "0" # Meistens 0 für das erste Blatt
|
gid_times: "635683780"
|
||||||
gid_remarks: "92621051" # Beispiel-ID deines Bemerkungen-Blatts
|
gid_remarks: "1437056802"
|
||||||
date_column: "Datum"
|
date_column: "Datum"
|
||||||
|
|
||||||
processing:
|
processing:
|
||||||
|
|||||||
@@ -1,66 +1,83 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import requests
|
import requests
|
||||||
|
import markdown
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from .config_loader import config
|
from .config_loader import config
|
||||||
|
|
||||||
_cache = {"events": None, "remarks": None}
|
_cache = {"events": None, "remarks": None, "timestamp": None}
|
||||||
HEADERS = {"User-Agent": "Mozilla/5.0"}
|
HEADERS = {"User-Agent": "Mozilla/5.0"}
|
||||||
|
|
||||||
def invalidate_cache():
|
def invalidate_cache():
|
||||||
global _cache
|
global _cache
|
||||||
_cache = {"events": None, "remarks": None}
|
_cache = {"events": None, "remarks": None, "timestamp": None}
|
||||||
print("DEBUG: Cache wurde manuell geleert.")
|
|
||||||
return "Cache gelöscht"
|
return "Cache gelöscht"
|
||||||
|
|
||||||
def get_upcoming_events():
|
def _is_cache_valid():
|
||||||
if _cache["events"] is not None:
|
if _cache["timestamp"] is None:
|
||||||
return _cache["events"]
|
return False
|
||||||
|
return (datetime.now() - _cache["timestamp"]) < timedelta(hours=1)
|
||||||
|
|
||||||
|
def get_upcoming_events(days_to_show=None, limit=None):
|
||||||
|
# Sofort Standardwert aus Config setzen, falls None oder 0
|
||||||
|
if not days_to_show:
|
||||||
|
days_to_show = config['processing']['days_to_show']
|
||||||
|
|
||||||
|
# 1. Daten laden (entweder aus Cache oder von Google)
|
||||||
|
if not _is_cache_valid() or _cache["events"] is None:
|
||||||
url = config['links']['times_csv']
|
url = config['links']['times_csv']
|
||||||
print(f"DEBUG: Lade Zeiten von URL: {url}")
|
|
||||||
|
|
||||||
response = requests.get(url, headers=HEADERS)
|
response = requests.get(url, headers=HEADERS)
|
||||||
|
response.raise_for_status()
|
||||||
|
response.encoding = 'utf-8'
|
||||||
|
|
||||||
df = pd.read_csv(StringIO(response.text))
|
df = pd.read_csv(StringIO(response.text))
|
||||||
|
|
||||||
# Debug: Spaltennamen prüfen
|
# Typografie: Bindestrich durch En-Dash (–) ersetzen
|
||||||
print(f"DEBUG: Spalten in Zeiten-Sheet gefunden: {df.columns.tolist()}")
|
for col in ['Morgen', 'Nachmittag']:
|
||||||
|
df[col] = df[col].fillna('').astype(str).str.replace('-', '–', regex=False)
|
||||||
|
|
||||||
date_col = config['google_sheet']['date_column']
|
date_col = config['google_sheet']['date_column']
|
||||||
df = df.dropna(subset=[date_col])
|
df = df.dropna(subset=[date_col])
|
||||||
df[date_col] = pd.to_datetime(df[date_col], dayfirst=True, errors='coerce')
|
df[date_col] = pd.to_datetime(df[date_col], dayfirst=True, errors='coerce')
|
||||||
df = df.dropna(subset=[date_col]).fillna('')
|
|
||||||
|
|
||||||
heute = pd.Timestamp(datetime.now().date())
|
|
||||||
ende = heute + timedelta(days=config['processing']['days_to_show'])
|
|
||||||
|
|
||||||
mask = (df[date_col] >= heute) & (df[date_col] <= ende)
|
|
||||||
gefilterte = df.loc[mask].sort_values(by=date_col).copy()
|
|
||||||
|
|
||||||
wt_map = {0: 'Mo', 1: 'Di', 2: 'Mi', 3: 'Do', 4: 'Fr', 5: 'Sa', 6: 'So'}
|
wt_map = {0: 'Mo', 1: 'Di', 2: 'Mi', 3: 'Do', 4: 'Fr', 5: 'Sa', 6: 'So'}
|
||||||
gefilterte['Wochentag'] = gefilterte[date_col].dt.weekday.map(wt_map)
|
df['Wochentag'] = df[date_col].dt.weekday.map(wt_map)
|
||||||
|
|
||||||
_cache["events"] = gefilterte.to_dict(orient='records')
|
_cache["events"] = df.sort_values(by=date_col).to_dict(orient='records')
|
||||||
return _cache["events"]
|
_cache["timestamp"] = datetime.now()
|
||||||
|
|
||||||
|
heute = pd.Timestamp(datetime.now().date())
|
||||||
|
date_col = config['google_sheet']['date_column']
|
||||||
|
|
||||||
|
# PRIORITÄT 1: Zeilen-Limit (gewinnt immer)
|
||||||
|
if limit and limit > 0:
|
||||||
|
return [e for e in _cache["events"] if e[date_col] >= heute][:limit]
|
||||||
|
|
||||||
|
# PRIORITÄT 2: Tage-Logik
|
||||||
|
ende = heute + timedelta(days=int(days_to_show))
|
||||||
|
return [e for e in _cache["events"] if heute <= e[date_col] <= ende]
|
||||||
|
|
||||||
def get_remarks():
|
def get_remarks():
|
||||||
if _cache["remarks"] is not None:
|
if _is_cache_valid() and _cache["remarks"] is not None:
|
||||||
return _cache["remarks"]
|
return _cache["remarks"]
|
||||||
|
|
||||||
url = config['links']['remarks_csv']
|
url = config['links']['remarks_csv']
|
||||||
print(f"DEBUG: Lade Bemerkungen von URL: {url}")
|
|
||||||
|
|
||||||
response = requests.get(url, headers=HEADERS)
|
response = requests.get(url, headers=HEADERS)
|
||||||
|
response.raise_for_status()
|
||||||
# Debug: Die ersten 100 Zeichen der Antwort sehen
|
response.encoding = 'utf-8'
|
||||||
print(f"DEBUG: Raw Data Bemerkungen (Anfang): {response.text[:100]}...")
|
|
||||||
|
|
||||||
df = pd.read_csv(StringIO(response.text), skiprows=2, header=None)
|
df = pd.read_csv(StringIO(response.text), skiprows=2, header=None)
|
||||||
|
|
||||||
if not df.empty:
|
if not df.empty:
|
||||||
print(f"DEBUG: Bemerkungen-DF Head:\n{df.head(3)}")
|
raw_remarks = df[0].dropna().astype(str).tolist()
|
||||||
remarks = df[0].dropna().astype(str).tolist()
|
processed = []
|
||||||
_cache["remarks"] = [r.strip() for r in remarks if r.strip()]
|
for r in raw_remarks:
|
||||||
|
html = markdown.markdown(r.strip())
|
||||||
|
if html.startswith("<p>") and html.endswith("</p>"):
|
||||||
|
html = html[3:-4]
|
||||||
|
processed.append(html)
|
||||||
|
_cache["remarks"] = processed
|
||||||
|
_cache["timestamp"] = datetime.now()
|
||||||
else:
|
else:
|
||||||
_cache["remarks"] = []
|
_cache["remarks"] = []
|
||||||
|
|
||||||
|
|||||||
2
main.py
2
main.py
@@ -1,4 +1,5 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
from routers.web_routes import router as web_router
|
from routers.web_routes import router as web_router
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from core.config_loader import config
|
from core.config_loader import config
|
||||||
@@ -7,6 +8,7 @@ app = FastAPI()
|
|||||||
|
|
||||||
# Router einbinden
|
# Router einbinden
|
||||||
app.include_router(web_router)
|
app.include_router(web_router)
|
||||||
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run(app, host=config['server']['host'], port=config['server']['port'])
|
uvicorn.run(app, host=config['server']['host'], port=config['server']['port'])
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ fastapi
|
|||||||
uvicorn
|
uvicorn
|
||||||
jinja2
|
jinja2
|
||||||
python-multipart
|
python-multipart
|
||||||
|
markdown
|
||||||
@@ -2,31 +2,39 @@ from fastapi import APIRouter, Request
|
|||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from core.data_processor import get_upcoming_events, get_remarks, invalidate_cache
|
from core.data_processor import get_upcoming_events, get_remarks, invalidate_cache
|
||||||
|
from core.config_loader import config
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
BASE_DIR = Path(__file__).parent.parent
|
BASE_DIR = Path(__file__).parent.parent
|
||||||
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
||||||
|
|
||||||
# Admin auf Root
|
@router.get("/zeiten", response_class=HTMLResponse)
|
||||||
@router.get("/", response_class=HTMLResponse)
|
async def public_table(request: Request, days: int = None, lines: int = None, test: bool = False):
|
||||||
async def admin_page(request: Request):
|
# Wenn nichts angegeben wurde, greift der Standard aus der Config
|
||||||
from core.config_loader import config
|
if not days and not lines:
|
||||||
return templates.TemplateResponse("admin.html", {
|
days = config['processing']['days_to_show']
|
||||||
"request": request, "links": config['links']
|
|
||||||
|
events = get_upcoming_events(days_to_show=days, limit=lines)
|
||||||
|
remarks = get_remarks()
|
||||||
|
|
||||||
|
# Template 'zeiten.html' laden und 'test' Parameter durchreichen
|
||||||
|
return templates.TemplateResponse("zeiten.html", {
|
||||||
|
"request": request,
|
||||||
|
"events": events,
|
||||||
|
"remarks": remarks,
|
||||||
|
"test": test
|
||||||
})
|
})
|
||||||
|
|
||||||
# Tabelle unter /zeiten
|
@router.get("/", response_class=HTMLResponse)
|
||||||
@router.get("/zeiten", response_class=HTMLResponse)
|
async def admin_page(request: Request):
|
||||||
async def times_table(request: Request):
|
return templates.TemplateResponse("admin.html", {
|
||||||
events = get_upcoming_events()
|
"request": request,
|
||||||
remarks = get_remarks()
|
"links": config['links'],
|
||||||
return templates.TemplateResponse("index.html", {
|
"config_days": config['processing']['days_to_show']
|
||||||
"request": request, "events": events, "remarks": remarks
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@router.get("/cache-clear")
|
@router.get("/cache-clear")
|
||||||
async def clear_cache():
|
async def clear_cache():
|
||||||
invalidate_cache()
|
invalidate_cache()
|
||||||
# Relativer Redirect zurück zum Admin-Interface
|
return RedirectResponse(url="/")
|
||||||
return RedirectResponse(url="./")
|
|
||||||
BIN
static/BrittanySignature.ttf
Normal file
BIN
static/BrittanySignature.ttf
Normal file
Binary file not shown.
BIN
static/BrittanySignature.woff2
Normal file
BIN
static/BrittanySignature.woff2
Normal file
Binary file not shown.
@@ -1,11 +1,39 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Admin - Öffnungszeiten</title>
|
<title>Administration - Öffnungszeiten</title>
|
||||||
<style>
|
<style>
|
||||||
body { font-family: sans-serif; padding: 40px; line-height: 1.6; }
|
body { font-family: sans-serif; padding: 40px; line-height: 1.6; }
|
||||||
.btn { background: #ff4757; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; }
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
background: #ff4757;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
.card { border: 1px solid #ddd; padding: 20px; margin-bottom: 20px; border-radius: 8px; }
|
.card { border: 1px solid #ddd; padding: 20px; margin-bottom: 20px; border-radius: 8px; }
|
||||||
|
|
||||||
|
/* Flex-Container für die Linksbündigkeit (Standard) */
|
||||||
|
.input-row {
|
||||||
|
margin-top: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start; /* Alles nach links */
|
||||||
|
align-items: center;
|
||||||
|
gap: 25px; /* Abstand zwischen den Paaren (Tage / Zeilen) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gruppiert Label und Input eng zusammen */
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint { text-align: left; font-size: 13px; color: #666; margin-top: 10px; }
|
||||||
|
.action-row { text-align: left; margin-top: 20px; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -15,14 +43,58 @@
|
|||||||
<h3>Google Sheets Links</h3>
|
<h3>Google Sheets Links</h3>
|
||||||
<p><a href="{{ links.times_edit }}" target="_blank">➔ Tabelle: Öffnungszeiten bearbeiten</a></p>
|
<p><a href="{{ links.times_edit }}" target="_blank">➔ Tabelle: Öffnungszeiten bearbeiten</a></p>
|
||||||
<p><a href="{{ links.remarks_edit }}" target="_blank">➔ Tabelle: Bemerkungen bearbeiten</a></p>
|
<p><a href="{{ links.remarks_edit }}" target="_blank">➔ Tabelle: Bemerkungen bearbeiten</a></p>
|
||||||
|
|
||||||
|
<div class="input-row">
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="days_input" style="font-size: 14px;">Tage:</label>
|
||||||
|
<input type="number" id="days_input" value="0" min="0" style="width: 55px; padding: 4px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="lines_input" style="font-size: 14px;">Zeilen:</label>
|
||||||
|
<input type="number" id="lines_input" value="0" min="0" style="width: 55px; padding: 4px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="hint">
|
||||||
|
Zeilen hat Vorrang vor Tagen. Wenn beide Werte 0 sind, werden die Standard-Tage aus der Config genutzt (aktuell: {{ config_days }}).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="action-row">
|
||||||
|
<a id="preview_link" href="/zeiten?test=1" target="_blank" class="btn" style="background: #2ed573;">➔ Vorschau mit Test-Hintergrund</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>Cache Management</h3>
|
<h3>Cache Management</h3>
|
||||||
<p>Nach Änderungen in Google Sheets muss der Cache gelöscht werden, damit die Website sofort aktualisiert wird.</p>
|
<p>Der Cache wird automatisch alle 60 Minuten aktualisiert. Nach manuellen Änderungen in Google Sheets können Sie ihn hier sofort leeren.</p>
|
||||||
<a href="/admin/cache-clear" class="btn">Invalidate Cache (Cache löschen)</a>
|
<a href="/cache-clear" class="btn">Cache jetzt löschen</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p><a href="/">← Zurück zur Ansicht</a></p>
|
<p><a href="/zeiten">← Zurück zur Ansicht</a></p>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const daysInput = document.getElementById('days_input');
|
||||||
|
const linesInput = document.getElementById('lines_input');
|
||||||
|
const previewLink = document.getElementById('preview_link');
|
||||||
|
|
||||||
|
function updateUrl() {
|
||||||
|
const days = parseInt(daysInput.value) || 0;
|
||||||
|
const lines = parseInt(linesInput.value) || 0;
|
||||||
|
|
||||||
|
let url = "/zeiten?test=1";
|
||||||
|
|
||||||
|
if (lines > 0) {
|
||||||
|
url += `&lines=${lines}`;
|
||||||
|
} else if (days > 0) {
|
||||||
|
url += `&days=${days}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewLink.href = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
daysInput.addEventListener('input', updateUrl);
|
||||||
|
linesInput.addEventListener('input', updateUrl);
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<style>
|
|
||||||
/* Für Wix: background auf transparent stellen */
|
|
||||||
body { font-family: sans-serif; color: white; background: black; margin: 0; padding: 10px; }
|
|
||||||
table { width: 100%; border-collapse: collapse; max-width: 500px; table-layout: fixed; }
|
|
||||||
td { padding: 10px 5px; border-bottom: 1px solid #333; font-size: 14px; vertical-align: middle; }
|
|
||||||
|
|
||||||
/* Spalten-Definitionen */
|
|
||||||
.col-wt { text-align: left; width: 45px; font-weight: bold; }
|
|
||||||
.col-date { text-align: right; width: 55px; color: #aaa; }
|
|
||||||
.col-time { text-align: right; width: 100px; }
|
|
||||||
.col-closed { text-align: center; font-style: italic; opacity: 0.6; }
|
|
||||||
|
|
||||||
.remarks { margin-top: 30px; font-size: 13px; color: #ccc; border-top: 1px solid #444; padding-top: 15px; line-height: 1.6; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<table>
|
|
||||||
{% for event in events %}
|
|
||||||
<tr>
|
|
||||||
<td class="col-wt">{{ event.Wochentag }}.</td>
|
|
||||||
<td class="col-date">{{ event.Datum.strftime('%d.%m.') }}</td>
|
|
||||||
|
|
||||||
{% if not event.Morgen and not event.Nachmittag %}
|
|
||||||
<td colspan="2" class="col-closed">geschlossen</td>
|
|
||||||
{% else %}
|
|
||||||
<td class="col-time">{{ event.Morgen }}</td>
|
|
||||||
<td class="col-time">{{ event.Nachmittag }}</td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{% if remarks %}
|
|
||||||
<div class="remarks">
|
|
||||||
{% for r in remarks %}
|
|
||||||
<div style="margin-bottom: 8px;">• {{ r }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
108
templates/zeiten.html
Normal file
108
templates/zeiten.html
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; color: white; background: transparent; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'BrittanySignature';
|
||||||
|
src: url('/static/BrittanySignature.woff2') format('woff2');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap; /* Verhindert unsichtbaren Text beim Laden */
|
||||||
|
}
|
||||||
|
|
||||||
|
.monat {
|
||||||
|
font-size: 25px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 1.5ex 0.5em 0.5ex 0.5em;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
font-family: 'BrittanySignature', cursive, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed; /* Garantiert gleiches Alignment über mehrere Tabellen */
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0.5ex 0.5em;
|
||||||
|
font-size: 15px;
|
||||||
|
vertical-align: top;
|
||||||
|
white-space: nowrap;
|
||||||
|
/* border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2); */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tr:nth-of-type(odd) { background-color: rgba(255, 255, 255, 0.1); } */
|
||||||
|
tr { background-color: rgba(255, 255, 255, 0.1); }
|
||||||
|
tr.week-spacer { border: none !important; background: transparent !important; height: 15px; }
|
||||||
|
|
||||||
|
/* Feste Prozentwerte für identische Spalten in allen Tabellen */
|
||||||
|
.col-wt { text-align: left; width: 9%; padding-right: 0; }
|
||||||
|
.col-date { text-align: right; width:14%; }
|
||||||
|
.col-time { text-align: right; width: 37%; }
|
||||||
|
.col-closed { text-align: center; }
|
||||||
|
|
||||||
|
.remarks-section { margin-top: 1ex; }
|
||||||
|
.remark-item { margin-top: 0ex; margin-bottom: 0.5ex; text-align: center; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if test %}
|
||||||
|
<div style="width: 300px ; margin: 20px auto; padding: 10px; background: rgb(55, 55, 55); border-radius: 8px;">
|
||||||
|
{% endif %}
|
||||||
|
{% set monate = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'] %}
|
||||||
|
{% set ns = namespace(last_month=none, last_week=none) %}
|
||||||
|
|
||||||
|
{% for event in events %}
|
||||||
|
{% set current_month_name = monate[event.Datum.month - 1] %}
|
||||||
|
{% set current_week = event.Datum.isocalendar()[1] %}
|
||||||
|
|
||||||
|
{# Monatstitel und Tabellen-Start/Ende Logik #}
|
||||||
|
{% if current_month_name != ns.last_month %}
|
||||||
|
{% if ns.last_month is not none %} </table> {% endif %}
|
||||||
|
|
||||||
|
<div class="monat">{{ current_month_name }}</div>
|
||||||
|
<table>
|
||||||
|
{% set ns.last_week = none %} {# Reset Wochendistanz bei neuem Monat #}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Wochen-Abstand innerhalb eines Monats #}
|
||||||
|
{% if ns.last_week is not none and current_week != ns.last_week %}
|
||||||
|
<tr class="week-spacer"><td colspan="4"></td></tr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<tr class="data-row">
|
||||||
|
<td class="col-wt">{{ event.Wochentag }}.</td>
|
||||||
|
<td class="col-date">{{ event.Datum.strftime('%d.%m.') }}</td>
|
||||||
|
|
||||||
|
{% if not event.Morgen and not event.Nachmittag %}
|
||||||
|
<td colspan="2" class="col-closed">geschlossen</td>
|
||||||
|
{% else %}
|
||||||
|
<td class="col-time">{{ event.Morgen }}</td>
|
||||||
|
<td class="col-time">{{ event.Nachmittag }}</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% set ns.last_month = current_month_name %}
|
||||||
|
{% set ns.last_week = current_week %}
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% if remarks %}
|
||||||
|
<div class="remarks-section">
|
||||||
|
{% for r in remarks %}
|
||||||
|
<div class="remark-item">{{ r | safe }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if test %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user