/**
* Mock data for standalone HTML testing.
* Intercepts fetch() calls to api.bessa.app and returns realistic dummy data.
* Injected BEFORE kantine.js in standalone builds only.
*/
(function () {
'use strict';
// Generate dates for this week and next week (Mon-Fri)
function getWeekDates(weekOffset) {
const dates = [];
const now = new Date();
const dayOfWeek = now.getDay(); // 0=Sun, 1=Mon
const monday = new Date(now);
monday.setDate(now.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1) + (weekOffset * 7));
for (let i = 0; i < 5; i++) {
const d = new Date(monday);
d.setDate(monday.getDate() + i);
dates.push(d.toISOString().split('T')[0]);
}
return dates;
}
const thisWeekDates = getWeekDates(0);
const nextWeekDates = getWeekDates(1);
const allDates = [...thisWeekDates, ...nextWeekDates];
// Realistic German canteen menu items per day
const menuPool = [
[
{ id: 101, name: 'Wiener Schnitzel mit Kartoffelsalat', description: 'Paniertes Schweineschnitzel mit hausgemachtem Kartoffelsalat', price: '6.90', available_amount: '15', amount_tracking: true },
{ id: 102, name: 'Gemüse-Curry mit Basmatireis', description: 'Veganes Curry mit saisonalem Gemüse und Kokosmilch', price: '5.50', available_amount: '0', amount_tracking: true },
{ id: 103, name: 'Rindergulasch mit Spätzle', description: 'Geschmortes Rindfleisch in Paprikasauce mit Eierspätzle', price: '7.20', available_amount: '8', amount_tracking: true },
{ id: 104, name: 'Tagessuppe: Tomatencremesuppe', description: 'Cremige Tomatensuppe mit Croutons', price: '3.20', available_amount: '0', amount_tracking: false },
],
[
{ id: 201, name: 'Hähnchenbrust mit Pilzrahmsauce', description: 'Gebratene Hähnchenbrust mit Champignon-Rahmsauce und Reis', price: '6.50', available_amount: '12', amount_tracking: true },
{ id: 202, name: 'Vegetarische Lasagne', description: 'Lasagne mit Spinat, Ricotta und Tomatensauce', price: '5.80', available_amount: '10', amount_tracking: true },
{ id: 203, name: 'Bratwurst mit Sauerkraut', description: 'Thüringer Bratwurst mit Sauerkraut und Kartoffelpüree', price: '5.90', available_amount: '0', amount_tracking: true },
{ id: 204, name: 'Caesar Salad mit Hähnchen', description: 'Römersalat mit gegrilltem Hähnchen, Parmesan und Croutons', price: '6.10', available_amount: '0', amount_tracking: false },
],
[
{ id: 301, name: 'Spaghetti Bolognese', description: 'Klassische Bolognese mit frischen Spaghetti', price: '5.20', available_amount: '20', amount_tracking: true },
{ id: 302, name: 'Gebratener Lachs mit Dillsauce', description: 'Lachsfilet auf Blattspinat mit Senf-Dill-Sauce', price: '8.50', available_amount: '5', amount_tracking: true },
{ id: 303, name: 'Kartoffelgratin mit Salat', description: 'Überbackene Kartoffeln mit Sahne und Käse, dazu gemischter Salat', price: '5.00', available_amount: '0', amount_tracking: false },
{ id: 304, name: 'Chili con Carne', description: 'Pikantes Chili mit Hackfleisch, Bohnen und Reis', price: '5.80', available_amount: '9', amount_tracking: true },
],
[
{ id: 401, name: 'Schweinebraten mit Knödel', description: 'Bayerischer Schweinebraten mit Semmelknödel und Bratensauce', price: '7.00', available_amount: '7', amount_tracking: true },
{ id: 402, name: 'Falafel-Bowl mit Hummus', description: 'Knusprige Falafel mit Hummus, Tabouleh und Fladenbrot', price: '5.90', available_amount: '0', amount_tracking: false },
{ id: 403, name: 'Putengeschnetzeltes mit Nudeln', description: 'Putenstreifen in Champignon-Sahnesauce mit Bandnudeln', price: '6.30', available_amount: '11', amount_tracking: true },
{ id: 404, name: 'Tagessuppe: Erbsensuppe', description: 'Deftige Erbsensuppe mit Wiener Würstchen', price: '3.50', available_amount: '0', amount_tracking: false },
],
[
{ id: 501, name: 'Backfisch mit Remoulade', description: 'Paniertes Seelachsfilet mit Remouladensauce und Bratkartoffeln', price: '6.80', available_amount: '6', amount_tracking: true },
{ id: 502, name: 'Käsespätzle mit Röstzwiebeln', description: 'Allgäuer Käsespätzle mit karamellisierten Zwiebeln und Salat', price: '5.50', available_amount: '14', amount_tracking: true },
{ id: 503, name: 'Schnitzel Wiener Art mit Pommes', description: 'Paniertes Hähnchenschnitzel mit knusprigen Pommes Frites', price: '6.20', available_amount: '0', amount_tracking: true },
{ id: 504, name: 'Griechischer Bauernsalat', description: 'Frischer Salat mit Feta, Oliven, Gurke und Tomaten', price: '5.30', available_amount: '0', amount_tracking: false },
],
];
// Build mock responses for each date
const dateResponses = {};
allDates.forEach((date, i) => {
const menuIndex = i % menuPool.length;
dateResponses[date] = {
results: [{
id: 1,
name: 'Mittagsmenü',
items: menuPool[menuIndex].map(item => ({
...item,
// Ensure unique IDs per date
id: item.id + (i * 1000)
}))
}]
};
});
// Mock some orders for today (to show "Bestellt" badges)
const todayStr = new Date().toISOString().split('T')[0];
const todayMenu = dateResponses[todayStr];
const mockOrders = [];
let nextOrderId = 9001;
if (todayMenu) {
const firstItem = todayMenu.results[0].items[0];
mockOrders.push({
id: nextOrderId++,
article: firstItem.id,
article_name: firstItem.name,
date: todayStr,
venue: 591,
status: 'confirmed',
created: new Date().toISOString()
});
}
// Pre-seed a mock auth session so flag/order buttons render
sessionStorage.setItem('kantine_authToken', 'mock-token-for-testing');
sessionStorage.setItem('kantine_currentUser', '12345');
sessionStorage.setItem('kantine_firstName', 'Test');
sessionStorage.setItem('kantine_lastName', 'User');
// Intercept fetch
const originalFetch = window.fetch;
window.fetch = function (url, options) {
const urlStr = typeof url === 'string' ? url : url.toString();
// Menu dates endpoint
if (urlStr.includes('/menu/dates/')) {
console.log('[MOCK] Returning mock dates data');
return Promise.resolve(new Response(JSON.stringify({
results: allDates.map(date => ({ date, orders: [] }))
}), { status: 200, headers: { 'Content-Type': 'application/json' } }));
}
// Menu detail for a specific date
const dateMatch = urlStr.match(/\/menu\/\d+\/(\d{4}-\d{2}-\d{2})\//);
if (dateMatch) {
const date = dateMatch[1];
const data = dateResponses[date] || { results: [] };
console.log(`[MOCK] Returning mock menu for ${date}`);
return Promise.resolve(new Response(JSON.stringify(data), {
status: 200, headers: { 'Content-Type': 'application/json' }
}));
}
// Orders endpoint
if (urlStr.includes('/user/orders/') && (!options || options.method === 'GET' || !options.method)) {
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({
results: mappedOrders
}), { status: 200, headers: { 'Content-Type': 'application/json' } }));
}
// Auth user endpoint
if (urlStr.includes('/auth/user/')) {
console.log('[MOCK] Returning mock user');
return Promise.resolve(new Response(JSON.stringify({
pk: 12345,
username: 'testuser',
email: 'test@example.com',
first_name: 'Test',
last_name: 'User'
}), { status: 200, headers: { 'Content-Type': 'application/json' } }));
}
// Order create (POST to /user/orders/)
if (urlStr.includes('/user/orders/') && options && options.method === 'POST') {
const body = JSON.parse(options.body || '{}');
const newOrder = {
id: nextOrderId++,
article: body.article,
article_name: 'Mock Order',
date: body.date,
venue: 591,
status: 'confirmed',
created: new Date().toISOString()
};
mockOrders.push(newOrder);
console.log('[MOCK] Created order:', newOrder);
return Promise.resolve(new Response(JSON.stringify(newOrder), {
status: 201, headers: { 'Content-Type': 'application/json' }
}));
}
// Order cancel (POST to /user/orders/{id}/cancel/)
const cancelMatch = urlStr.match(/\/user\/orders\/(\d+)\/cancel\//);
if (cancelMatch) {
const orderId = parseInt(cancelMatch[1]);
const idx = mockOrders.findIndex(o => o.id === orderId);
if (idx >= 0) mockOrders.splice(idx, 1);
console.log('[MOCK] Cancelled order:', orderId);
return Promise.resolve(new Response('{}', {
status: 200, headers: { 'Content-Type': 'application/json' }
}));
}
// Fallback to real fetch for other URLs (fonts, etc.)
return originalFetch.apply(this, arguments);
};
console.log('[MOCK] 🧪 Mock data active – using dummy canteen menus for UI testing');
})();