dist files for v1.4.0 built

This commit is contained in:
Kantine Wrapper
2026-02-22 21:40:41 +01:00
parent 4c55e34bc1
commit f19827ae91
11 changed files with 374 additions and 35 deletions

View File

@@ -26,16 +26,15 @@ trigger: always_on
- **Interaction**: Be proactive, concise, and helpful. Focus on code value. - **Interaction**: Be proactive, concise, and helpful. Focus on code value.
## 4. Development Standards ## 4. Development Standards
**Tech Stack:**
- **Container**: Docker-based application.
- **Config**: Configurable port.
**Coding Style:** **Coding Style:**
- **Typing**: Strict typing where applicable. - **Typing**: Strict typing where applicable.
- **Comments**: Concise, English. - **Comments**: Concise, English.
- **Frontend/UX**: - **Frontend/UX**:
- Priority on Usability. - Priority on Usability.
- **MANDATORY**: Tooltips/Help texts for all interactions. - **MANDATORY**: Tooltips/Help texts for all interactions.
- **Versioning**:
- version.txt has to be increased for any implemented features or fixed bugs)
- a change summary has to be documented in changelog.md
## 5. Agentic Workflow & Artifacts ## 5. Agentic Workflow & Artifacts
**Core Philosophy**: Plan first, act second. **Core Philosophy**: Plan first, act second.

View File

@@ -38,8 +38,9 @@ Das System umfasst die Darstellung von Menüplänen in einer Wochenübersicht, d
| FR-031 | Authentifizierte Benutzer müssen eine bestehende Bestellung direkt aus der Übersicht stornieren können. | Hoch | v1.0.1 | | FR-031 | Authentifizierte Benutzer müssen eine bestehende Bestellung direkt aus der Übersicht stornieren können. | Hoch | v1.0.1 |
| FR-032 | Nach Bestellschluss (Cutoff-Zeit) dürfen keine neuen Bestellungen oder Stornierungen für diesen Tag möglich sein. | Hoch | v1.0.1 | | FR-032 | Nach Bestellschluss (Cutoff-Zeit) dürfen keine neuen Bestellungen oder Stornierungen für diesen Tag möglich sein. | Hoch | v1.0.1 |
| FR-033 | Es muss möglich sein, dasselbe Menü mehrfach zu bestellen. Bei Mehrfachbestellungen muss die Anzahl angezeigt werden. | Niedrig | v1.0.1 | | FR-033 | Es muss möglich sein, dasselbe Menü mehrfach zu bestellen. Bei Mehrfachbestellungen muss die Anzahl angezeigt werden. | Niedrig | v1.0.1 |
| **Kostentransparenz** | | | | | **Kostentransparenz & Bestellhistorie** | | | |
| FR-040 | Das System muss die Gesamtkosten aller Bestellungen einer Woche automatisch berechnen und anzeigen. | Mittel | v1.1.0 | | FR-040 | Das System muss die Gesamtkosten aller Bestellungen einer Woche automatisch berechnen und anzeigen. | Mittel | v1.1.0 |
| FR-041 | Das System muss dem Benutzer eine paginierte oder vollständige Bestellhistorie (gruppiert nach Monat und KW) mit Fortschrittsanzeige auf Abruf in einem Modal bereitstellen. | Mittel | v1.4.0 |
| **Bestell-Countdown** | | | | | **Bestell-Countdown** | | | |
| FR-050 | Das System muss vor Bestellschluss einen visuell hervorgehobenen Countdown anzeigen. | Mittel | v1.1.0 | | FR-050 | Das System muss vor Bestellschluss einen visuell hervorgehobenen Countdown anzeigen. | Mittel | v1.1.0 |
| **Menü-Flagging & Benachrichtigungen** | | | | | **Menü-Flagging & Benachrichtigungen** | | | |

View File

@@ -1,3 +1,6 @@
## v1.4.0 (2026-02-22)
- **Feature**: Bestellhistorie per Knopfdruck abrufbar. Übersichtliche Darstellung, gruppiert nach Monaten und Kalenderwochen, inklusive Stornos. 📜✨
## v1.3.2 (2026-02-19) ## v1.3.2 (2026-02-19)
- **Fix**: Falsche Anzahl an Highlight-Menüs im "Nächste Woche"-Badge korrigiert (zählte alle Menüs statt nur Highlights). 🐛 - **Fix**: Falsche Anzahl an Highlight-Menüs im "Nächste Woche"-Badge korrigiert (zählte alle Menüs statt nur Highlights). 🐛

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16
dist/install.html vendored

File diff suppressed because one or more lines are too long

View File

@@ -448,7 +448,6 @@ body {
.modal-content { .modal-content {
background: var(--bg-card); background: var(--bg-card);
/* Changed from --surface */
width: 90%; width: 90%;
max-width: 400px; max-width: 400px;
border-radius: 16px; border-radius: 16px;
@@ -457,6 +456,114 @@ body {
animation: modalSlide 0.3s ease-out; animation: modalSlide 0.3s ease-out;
} }
/* History Modal specific */
.history-modal-content {
max-width: 600px;
max-height: 85vh;
display: flex;
flex-direction: column;
}
.history-modal-content .modal-body {
overflow-y: auto;
padding: 0; /* Padding is handled by inner elements */
}
/* History Styles */
.history-month-group {
margin-bottom: 24px;
}
.history-month-header {
position: sticky;
top: 0;
background: var(--bg-card);
padding: 12px 20px;
margin: 0;
font-size: 1.1rem;
font-weight: 700;
color: var(--text-primary);
border-bottom: 1px solid var(--border-color);
border-top: 1px solid var(--border-color);
z-index: 10;
}
.history-month-group:first-child .history-month-header {
border-top: none;
}
.history-week-group {
padding: 16px 20px 8px;
}
.history-week-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.95rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 12px;
}
.history-week-summary {
font-size: 0.85rem;
font-weight: 500;
background: rgba(100, 116, 139, 0.1);
padding: 4px 10px;
border-radius: 12px;
}
.history-items {
display: flex;
flex-direction: column;
gap: 8px;
}
.history-item {
display: grid;
grid-template-columns: 50px 1fr auto;
align-items: center;
gap: 12px;
padding: 10px 12px;
background: var(--bg-body);
border-radius: 8px;
border: 1px solid var(--border-color);
}
.history-item-date {
font-size: 0.85rem;
color: var(--text-secondary);
font-weight: 500;
}
.history-item-details {
display: flex;
flex-direction: column;
gap: 4px;
}
.history-item-name {
font-size: 0.95rem;
font-weight: 500;
color: var(--text-primary);
}
.history-item-price {
font-weight: 600;
color: var(--text-primary);
}
.history-item-name-cancelled {
text-decoration: line-through;
color: var(--text-secondary);
}
.history-item-price-cancelled {
text-decoration: line-through;
color: var(--text-secondary);
}
@keyframes modalSlide { @keyframes modalSlide {
from { from {
transform: translateY(20px); transform: translateY(20px);
@@ -1679,8 +1786,31 @@ body {
// Orders endpoint // Orders endpoint
if (urlStr.includes('/user/orders/') && (!options || options.method === 'GET' || !options.method)) { if (urlStr.includes('/user/orders/') && (!options || options.method === 'GET' || !options.method)) {
console.log('[MOCK] Returning mock orders'); console.log('[MOCK] Returning mock orders');
// Formatter for history mapping
const mappedOrders = mockOrders.map(o => ({
id: o.id,
date: `${o.date}T10:00:00Z`,
order_state: o.status === 'cancelled' ? 9 : 5,
total: o.price || '6.50',
items: [{
article: o.article,
name: o.article_name,
price: o.price || '6.50',
amount: 1
}]
}));
// Handle lazy load / pagination if requesting full history
if (urlStr.includes('limit=50')) {
return Promise.resolve(new Response(JSON.stringify({
count: mappedOrders.length,
next: null,
results: mappedOrders
}), { status: 200, headers: { 'Content-Type': 'application/json' } }));
}
return Promise.resolve(new Response(JSON.stringify({ return Promise.resolve(new Response(JSON.stringify({
results: mockOrders results: mappedOrders
}), { status: 200, headers: { 'Content-Type': 'application/json' } })); }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
} }
@@ -1807,7 +1937,7 @@ body {
<div class="brand"> <div class="brand">
<span class="material-icons-round logo-icon">restaurant_menu</span> <span class="material-icons-round logo-icon">restaurant_menu</span>
<div class="header-left"> <div class="header-left">
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.3.2</small></h1> <h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.4.0</small></h1>
<div id="last-updated-subtitle" class="subtitle"></div> <div id="last-updated-subtitle" class="subtitle"></div>
</div> </div>
</div> </div>
@@ -1819,6 +1949,9 @@ body {
<button id="btn-refresh" class="icon-btn" aria-label="Menüdaten aktualisieren" title="Menüdaten neu laden"> <button id="btn-refresh" class="icon-btn" aria-label="Menüdaten aktualisieren" title="Menüdaten neu laden">
<span class="material-icons-round">refresh</span> <span class="material-icons-round">refresh</span>
</button> </button>
<button id="btn-history" class="icon-btn" aria-label="Bestellhistorie" title="Bestellhistorie">
<span class="material-icons-round">receipt_long</span>
</button>
<button id="btn-highlights" class="icon-btn" aria-label="Persönliche Highlights verwalten" title="Persönliche Highlights verwalten"> <button id="btn-highlights" class="icon-btn" aria-label="Persönliche Highlights verwalten" title="Persönliche Highlights verwalten">
<span class="material-icons-round">label</span> <span class="material-icons-round">label</span>
</button> </button>
@@ -1909,6 +2042,30 @@ body {
</div> </div>
</div> </div>
<div id="history-modal" class="modal hidden">
<div class="modal-content history-modal-content">
<div class="modal-header">
<h2>Bestellhistorie</h2>
<button id="btn-history-close" class="icon-btn" aria-label="Close">
<span class="material-icons-round">close</span>
</button>
</div>
<div class="modal-body">
<div id="history-loading" class="hidden">
<p id="history-progress-text" style="text-align: center; margin-bottom: 1rem; color: var(--text-secondary);">Lade Historie...</p>
<div class="progress-container">
<div class="progress-bar">
<div id="history-progress-fill" class="progress-fill"></div>
</div>
</div>
</div>
<div id="history-content">
<!-- Dynamically populated -->
</div>
</div>
</div>
</div>
<div id="version-modal" class="modal hidden"> <div id="version-modal" class="modal hidden">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@@ -1919,7 +2076,7 @@ body {
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div style="margin-bottom: 1rem;"> <div style="margin-bottom: 1rem;">
<strong>Aktuell:</strong> <span id="version-current">v1.3.2</span> <strong>Aktuell:</strong> <span id="version-current">v1.4.0</span>
</div> </div>
<div class="dev-toggle"> <div class="dev-toggle">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;"> <label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
@@ -1971,17 +2128,26 @@ body {
const btnAddTag = document.getElementById('btn-add-tag'); const btnAddTag = document.getElementById('btn-add-tag');
const tagInput = document.getElementById('tag-input'); const tagInput = document.getElementById('tag-input');
btnHighlights.addEventListener('click', () => { // History Modal
highlightsModal.classList.remove('hidden'); const btnHistory = document.getElementById('btn-history');
renderTagsList(); const historyModal = document.getElementById('history-modal');
tagInput.focus(); const btnHistoryClose = document.getElementById('btn-history-close');
btnHistory.addEventListener('click', () => {
if (!authToken) {
loginModal.classList.remove('hidden');
return;
}
historyModal.classList.remove('hidden');
fetchFullOrderHistory();
}); });
btnHighlightsClose.addEventListener('click', () => { btnHistoryClose.addEventListener('click', () => {
highlightsModal.classList.add('hidden'); historyModal.classList.add('hidden');
}); });
window.addEventListener('click', (e) => { window.addEventListener('click', (e) => {
if (e.target === historyModal) historyModal.classList.add('hidden');
if (e.target === highlightsModal) highlightsModal.classList.add('hidden'); if (e.target === highlightsModal) highlightsModal.classList.add('hidden');
}); });
@@ -3294,7 +3460,7 @@ body {
// Periodic update check (runs on init + every hour) // Periodic update check (runs on init + every hour)
async function checkForUpdates() { async function checkForUpdates() {
const currentVersion = 'v1.3.2'; const currentVersion = 'v1.4.0';
const devMode = localStorage.getItem('kantine_dev_mode') === 'true'; const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
try { try {
@@ -3335,7 +3501,7 @@ body {
const modal = document.getElementById('version-modal'); const modal = document.getElementById('version-modal');
const container = document.getElementById('version-list-container'); const container = document.getElementById('version-list-container');
const devToggle = document.getElementById('dev-mode-toggle'); const devToggle = document.getElementById('dev-mode-toggle');
const currentVersion = 'v1.3.2'; const currentVersion = 'v1.4.0';
if (!modal) return; if (!modal) return;
modal.classList.remove('hidden'); modal.classList.remove('hidden');

View File

@@ -83,6 +83,9 @@
<button id="btn-refresh" class="icon-btn" aria-label="Menüdaten aktualisieren" title="Menüdaten neu laden"> <button id="btn-refresh" class="icon-btn" aria-label="Menüdaten aktualisieren" title="Menüdaten neu laden">
<span class="material-icons-round">refresh</span> <span class="material-icons-round">refresh</span>
</button> </button>
<button id="btn-history" class="icon-btn" aria-label="Bestellhistorie" title="Bestellhistorie">
<span class="material-icons-round">receipt_long</span>
</button>
<button id="btn-highlights" class="icon-btn" aria-label="Persönliche Highlights verwalten" title="Persönliche Highlights verwalten"> <button id="btn-highlights" class="icon-btn" aria-label="Persönliche Highlights verwalten" title="Persönliche Highlights verwalten">
<span class="material-icons-round">label</span> <span class="material-icons-round">label</span>
</button> </button>
@@ -173,6 +176,30 @@
</div> </div>
</div> </div>
<div id="history-modal" class="modal hidden">
<div class="modal-content history-modal-content">
<div class="modal-header">
<h2>Bestellhistorie</h2>
<button id="btn-history-close" class="icon-btn" aria-label="Close">
<span class="material-icons-round">close</span>
</button>
</div>
<div class="modal-body">
<div id="history-loading" class="hidden">
<p id="history-progress-text" style="text-align: center; margin-bottom: 1rem; color: var(--text-secondary);">Lade Historie...</p>
<div class="progress-container">
<div class="progress-bar">
<div id="history-progress-fill" class="progress-fill"></div>
</div>
</div>
</div>
<div id="history-content">
<!-- Dynamically populated -->
</div>
</div>
</div>
</div>
<div id="version-modal" class="modal hidden"> <div id="version-modal" class="modal hidden">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@@ -235,17 +262,26 @@
const btnAddTag = document.getElementById('btn-add-tag'); const btnAddTag = document.getElementById('btn-add-tag');
const tagInput = document.getElementById('tag-input'); const tagInput = document.getElementById('tag-input');
btnHighlights.addEventListener('click', () => { // History Modal
highlightsModal.classList.remove('hidden'); const btnHistory = document.getElementById('btn-history');
renderTagsList(); const historyModal = document.getElementById('history-modal');
tagInput.focus(); const btnHistoryClose = document.getElementById('btn-history-close');
btnHistory.addEventListener('click', () => {
if (!authToken) {
loginModal.classList.remove('hidden');
return;
}
historyModal.classList.remove('hidden');
fetchFullOrderHistory();
}); });
btnHighlightsClose.addEventListener('click', () => { btnHistoryClose.addEventListener('click', () => {
highlightsModal.classList.add('hidden'); historyModal.classList.add('hidden');
}); });
window.addEventListener('click', (e) => { window.addEventListener('click', (e) => {
if (e.target === historyModal) historyModal.classList.add('hidden');
if (e.target === highlightsModal) highlightsModal.classList.add('hidden'); if (e.target === highlightsModal) highlightsModal.classList.add('hidden');
}); });

View File

@@ -128,8 +128,31 @@
// Orders endpoint // Orders endpoint
if (urlStr.includes('/user/orders/') && (!options || options.method === 'GET' || !options.method)) { if (urlStr.includes('/user/orders/') && (!options || options.method === 'GET' || !options.method)) {
console.log('[MOCK] Returning mock orders'); console.log('[MOCK] Returning mock orders');
// Formatter for history mapping
const mappedOrders = mockOrders.map(o => ({
id: o.id,
date: `${o.date}T10:00:00Z`,
order_state: o.status === 'cancelled' ? 9 : 5,
total: o.price || '6.50',
items: [{
article: o.article,
name: o.article_name,
price: o.price || '6.50',
amount: 1
}]
}));
// Handle lazy load / pagination if requesting full history
if (urlStr.includes('limit=50')) {
return Promise.resolve(new Response(JSON.stringify({
count: mappedOrders.length,
next: null,
results: mappedOrders
}), { status: 200, headers: { 'Content-Type': 'application/json' } }));
}
return Promise.resolve(new Response(JSON.stringify({ return Promise.resolve(new Response(JSON.stringify({
results: mockOrders results: mappedOrders
}), { status: 200, headers: { 'Content-Type': 'application/json' } })); }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
} }

109
style.css
View File

@@ -437,7 +437,6 @@ body {
.modal-content { .modal-content {
background: var(--bg-card); background: var(--bg-card);
/* Changed from --surface */
width: 90%; width: 90%;
max-width: 400px; max-width: 400px;
border-radius: 16px; border-radius: 16px;
@@ -446,6 +445,114 @@ body {
animation: modalSlide 0.3s ease-out; animation: modalSlide 0.3s ease-out;
} }
/* History Modal specific */
.history-modal-content {
max-width: 600px;
max-height: 85vh;
display: flex;
flex-direction: column;
}
.history-modal-content .modal-body {
overflow-y: auto;
padding: 0; /* Padding is handled by inner elements */
}
/* History Styles */
.history-month-group {
margin-bottom: 24px;
}
.history-month-header {
position: sticky;
top: 0;
background: var(--bg-card);
padding: 12px 20px;
margin: 0;
font-size: 1.1rem;
font-weight: 700;
color: var(--text-primary);
border-bottom: 1px solid var(--border-color);
border-top: 1px solid var(--border-color);
z-index: 10;
}
.history-month-group:first-child .history-month-header {
border-top: none;
}
.history-week-group {
padding: 16px 20px 8px;
}
.history-week-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.95rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 12px;
}
.history-week-summary {
font-size: 0.85rem;
font-weight: 500;
background: rgba(100, 116, 139, 0.1);
padding: 4px 10px;
border-radius: 12px;
}
.history-items {
display: flex;
flex-direction: column;
gap: 8px;
}
.history-item {
display: grid;
grid-template-columns: 50px 1fr auto;
align-items: center;
gap: 12px;
padding: 10px 12px;
background: var(--bg-body);
border-radius: 8px;
border: 1px solid var(--border-color);
}
.history-item-date {
font-size: 0.85rem;
color: var(--text-secondary);
font-weight: 500;
}
.history-item-details {
display: flex;
flex-direction: column;
gap: 4px;
}
.history-item-name {
font-size: 0.95rem;
font-weight: 500;
color: var(--text-primary);
}
.history-item-price {
font-weight: 600;
color: var(--text-primary);
}
.history-item-name-cancelled {
text-decoration: line-through;
color: var(--text-secondary);
}
.history-item-price-cancelled {
text-decoration: line-through;
color: var(--text-secondary);
}
@keyframes modalSlide { @keyframes modalSlide {
from { from {
transform: translateY(20px); transform: translateY(20px);

View File

@@ -1 +1 @@
v1.3.2 v1.4.0