Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Middagsplanlegger</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @keyframes shake { | |
| 0%, 100% { transform: translateX(0); } | |
| 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); } | |
| 20%, 40%, 60%, 80% { transform: translateX(5px); } | |
| } | |
| .shake { | |
| animation: shake 0.5s; | |
| } | |
| .star-rating { | |
| display: inline-flex; | |
| direction: rtl; | |
| } | |
| .star-rating input { | |
| display: none; | |
| } | |
| .star-rating label { | |
| color: #d1d5db; | |
| font-size: 1.5rem; | |
| padding: 0 0.1rem; | |
| cursor: pointer; | |
| } | |
| .star-rating input:checked ~ label, | |
| .star-rating label:hover, | |
| .star-rating label:hover ~ label { | |
| color: #f59e0b; | |
| } | |
| .star-display { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .star-display .stars { | |
| color: #d1d5db; | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .star-display .stars-filled { | |
| color: #f59e0b; | |
| position: absolute; | |
| display: inline-block; | |
| top: 0; | |
| left: 0; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| width: 0; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <!-- Login Page (shown by default) --> | |
| <div id="loginPage" class="flex items-center justify-center min-h-screen"> | |
| <div class="bg-white rounded-xl shadow-lg p-8 w-full max-w-md"> | |
| <div class="text-center mb-8"> | |
| <i class="fas fa-utensils text-5xl text-green-500 mb-4"></i> | |
| <h1 class="text-3xl font-bold text-gray-800">Middagsplanlegger</h1> | |
| <p class="text-gray-600 mt-2">Planlegg ukens middager for din familie</p> | |
| </div> | |
| <form id="loginForm" class="space-y-6"> | |
| <div> | |
| <label for="familyCode" class="block text-sm font-medium text-gray-700 mb-1">Familiekode</label> | |
| <div class="relative"> | |
| <input | |
| type="password" | |
| id="familyCode" | |
| name="familyCode" | |
| required | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition" | |
| placeholder="Skriv inn din familie-kode" | |
| > | |
| <button | |
| type="button" | |
| id="togglePassword" | |
| class="absolute right-3 top-3 text-gray-400 hover:text-gray-600" | |
| > | |
| <i class="fas fa-eye"></i> | |
| </button> | |
| </div> | |
| <p id="errorMessage" class="text-red-500 text-sm mt-2 hidden"> | |
| Ukjent familie-kode, prøv igjen | |
| </p> | |
| </div> | |
| <div> | |
| <label for="userName" class="block text-sm font-medium text-gray-700 mb-1">Ditt navn (valgfri)</label> | |
| <input | |
| type="text" | |
| id="userName" | |
| name="userName" | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition" | |
| placeholder="Hva heter du?" | |
| > | |
| </div> | |
| <button | |
| type="submit" | |
| class="w-full bg-green-500 hover:bg-green-600 text-white font-medium py-3 px-4 rounded-lg transition duration-200 flex items-center justify-center" | |
| > | |
| <i class="fas fa-sign-in-alt mr-2"></i> Logg inn | |
| </button> | |
| <div class="text-center text-sm text-gray-500"> | |
| <p>Familieadministrator kan opprette ny familie-kode</p> | |
| <button | |
| type="button" | |
| id="createFamilyBtn" | |
| class="mt-2 text-green-600 hover:text-green-800 font-medium" | |
| > | |
| Opprett ny familie | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Create Family Page (hidden by default) --> | |
| <div id="createFamilyPage" class="hidden flex items-center justify-center min-h-screen"> | |
| <div class="bg-white rounded-xl shadow-lg p-8 w-full max-w-md"> | |
| <div class="text-center mb-8"> | |
| <i class="fas fa-users text-5xl text-blue-500 mb-4"></i> | |
| <h1 class="text-3xl font-bold text-gray-800">Opprett ny familie</h1> | |
| <p class="text-gray-600 mt-2">Opprett en unik kode for din familie</p> | |
| </div> | |
| <form id="createFamilyForm" class="space-y-6"> | |
| <div> | |
| <label for="newFamilyName" class="block text-sm font-medium text-gray-700 mb-1">Familienavn</label> | |
| <input | |
| type="text" | |
| id="newFamilyName" | |
| name="newFamilyName" | |
| required | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" | |
| placeholder="F.eks. Andersen-familien" | |
| > | |
| </div> | |
| <div> | |
| <label for="newFamilyCode" class="block text-sm font-medium text-gray-700 mb-1">Velg en familie-kode</label> | |
| <input | |
| type="password" | |
| id="newFamilyCode" | |
| name="newFamilyCode" | |
| required | |
| minlength="4" | |
| maxlength="8" | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" | |
| placeholder="Velg en 4-8 siffer kode" | |
| > | |
| <p class="text-xs text-gray-500 mt-1">Dette er koden alle familiemedlemmer bruker for å logge inn</p> | |
| </div> | |
| <div> | |
| <label for="adminName" class="block text-sm font-medium text-gray-700 mb-1">Ditt navn (administrator)</label> | |
| <input | |
| type="text" | |
| id="adminName" | |
| name="adminName" | |
| required | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" | |
| placeholder="Ditt navn" | |
| > | |
| </div> | |
| <div class="flex gap-4"> | |
| <button | |
| type="button" | |
| id="cancelCreateFamily" | |
| class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-3 px-4 rounded-lg transition duration-200" | |
| > | |
| Avbryt | |
| </button> | |
| <button | |
| type="submit" | |
| class="flex-1 bg-blue-500 hover:bg-blue-600 text-white font-medium py-3 px-4 rounded-lg transition duration-200 flex items-center justify-center" | |
| > | |
| <i class="fas fa-users mr-2"></i> Opprett familie | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Middagsplanlegger App (hidden by default) --> | |
| <div id="appPage" class="hidden p-6 max-w-4xl mx-auto"> | |
| <header class="mb-8 text-center"> | |
| <h1 class="text-3xl font-bold text-gray-800 flex items-center justify-center"> | |
| <i class="fas fa-utensils text-green-500 mr-3"></i> Middagsplanlegger | |
| </h1> | |
| <p class="text-gray-600 mt-2">Planlegg ukens middager og vurder rettene</p> | |
| <p id="currentWeek" class="text-sm text-gray-500 mt-1"></p> | |
| <p id="familyNameDisplay" class="text-lg font-medium text-blue-600 mt-2"></p> | |
| <p id="currentUserDisplay" class="text-sm text-gray-500"></p> | |
| </header> | |
| <div class="bg-white rounded-xl shadow-lg p-6 mb-8"> | |
| <h2 class="text-xl font-semibold text-gray-700 mb-4">Ukens middager</h2> | |
| <div class="space-y-4"> | |
| <!-- Monday --> | |
| <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> | |
| <label class="w-24 font-medium text-gray-700">Mandag:</label> | |
| <input type="text" id="monday" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition" placeholder="Hva skal vi spise?"> | |
| </div> | |
| <!-- Tuesday --> | |
| <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> | |
| <label class="w-24 font-medium text-gray-700">Tirsdag:</label> | |
| <input type="text" id="tuesday" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition" placeholder="Hva skal vi spise?"> | |
| </div> | |
| <!-- Wednesday --> | |
| <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> | |
| <label class="w-24 font-medium text-gray-700">Onsdag:</label> | |
| <input type="text" id="wednesday" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition" placeholder="Hva skal vi spise?"> | |
| </div> | |
| <!-- Thursday --> | |
| <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> | |
| <label class="w-24 font-medium text-gray-700">Torsdag:</label> | |
| <input type="text" id="thursday" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition" placeholder="Hva skal vi spise?"> | |
| </div> | |
| <!-- Friday --> | |
| <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> | |
| <label class="w-24 font-medium text-gray-700">Fredag:</label> | |
| <input type="text" id="friday" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition" placeholder="Hva skal vi spise?"> | |
| </div> | |
| <!-- Saturday --> | |
| <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> | |
| <label class="w-24 font-medium text-gray-700">Lørdag:</label> | |
| <input type="text" id="saturday" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition" placeholder="Hva skal vi spise?"> | |
| </div> | |
| <!-- Sunday --> | |
| <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> | |
| <label class="w-24 font-medium text-gray-700">Søndag:</label> | |
| <input type="text" id="sunday" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition" placeholder="Hva skal vi spise?"> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-end"> | |
| <button id="saveBtn" class="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-6 rounded-lg transition duration-200 flex items-center"> | |
| <i class="fas fa-save mr-2"></i> Lagre plan | |
| </button> | |
| </div> | |
| </div> | |
| <div class="bg-white rounded-xl shadow-lg p-6 mb-8"> | |
| <h2 class="text-xl font-semibold text-gray-700 mb-4">Denne ukens middager</h2> | |
| <div id="dinnersList" class="space-y-6"> | |
| <!-- This will be populated with JavaScript --> | |
| <p class="text-gray-500 italic">Ingen middager lagt til ennå</p> | |
| </div> | |
| <div id="ratingSummary" class="mt-4 pt-4 border-t border-gray-200 hidden"> | |
| <h3 class="font-medium text-gray-700 mb-2">Gjennomsnittlig vurdering:</h3> | |
| <div id="averageRating" class="star-display"> | |
| <div class="stars">★★★★★</div> | |
| <div class="stars-filled">★★★★★</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white rounded-xl shadow-lg p-6"> | |
| <h2 class="text-xl font-semibold text-gray-700 mb-4">Inspirasion</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <a href="https://vegetarmat.org//" target="_blank" class="bg-green-50 hover:bg-green-100 p-4 rounded-lg transition flex items-center"> | |
| <i class="fas fa-leaf text-green-500 text-2xl mr-3"></i> | |
| <div> | |
| <h3 class="font-medium text-gray-800">Vegetarmat.no</h3> | |
| <p class="text-sm text-gray-600">Vegetariske oppskrifter</p> | |
| </div> | |
| </a> | |
| <a href="https://www.godfisk.no/oppskrifter/" target="_blank" class="bg-blue-50 hover:bg-blue-100 p-4 rounded-lg transition flex items-center"> | |
| <i class="fas fa-fish text-blue-500 text-2xl mr-3"></i> | |
| <div> | |
| <h3 class="font-medium text-gray-800">Godfisk</h3> | |
| <p class="text-sm text-gray-600">Pescetariske oppskrifter</p> | |
| </div> | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM elements | |
| const loginForm = document.getElementById('loginForm'); | |
| const createFamilyForm = document.getElementById('createFamilyForm'); | |
| const familyCodeInput = document.getElementById('familyCode'); | |
| const togglePassword = document.getElementById('togglePassword'); | |
| const errorMessage = document.getElementById('errorMessage'); | |
| const loginPage = document.getElementById('loginPage'); | |
| const createFamilyPage = document.getElementById('createFamilyPage'); | |
| const appPage = document.getElementById('appPage'); | |
| const currentWeekDisplay = document.getElementById('currentWeek'); | |
| const familyNameDisplay = document.getElementById('familyNameDisplay'); | |
| const currentUserDisplay = document.getElementById('currentUserDisplay'); | |
| const createFamilyBtn = document.getElementById('createFamilyBtn'); | |
| const cancelCreateFamily = document.getElementById('cancelCreateFamily'); | |
| // Current family data | |
| let currentFamily = null; | |
| let currentUser = null; | |
| // Get current week number and year | |
| function getWeekNumber(date) { | |
| const d = new Date(date); | |
| d.setHours(0, 0, 0, 0); | |
| d.setDate(d.getDate() + 4 - (d.getDay() || 7)); | |
| const yearStart = new Date(d.getFullYear(), 0, 1); | |
| const weekNo = Math.ceil((((d - yearStart) / 86400000) + 1) / 7); | |
| return { week: weekNo, year: d.getFullYear() }; | |
| } | |
| // Get storage key for current week and family | |
| function getCurrentWeekKey(familyCode) { | |
| const { week, year } = getWeekNumber(new Date()); | |
| return `middagsplanlegger_${familyCode}_${year}_${week}`; | |
| } | |
| // Display current week | |
| function displayCurrentWeek() { | |
| const { week, year } = getWeekNumber(new Date()); | |
| currentWeekDisplay.textContent = `Uke ${week}, ${year}`; | |
| } | |
| // Toggle password visibility | |
| togglePassword.addEventListener('click', function() { | |
| const type = familyCodeInput.getAttribute('type') === 'password' ? 'text' : 'password'; | |
| familyCodeInput.setAttribute('type', type); | |
| this.innerHTML = type === 'password' ? '<i class="fas fa-eye"></i>' : '<i class="fas fa-eye-slash"></i>'; | |
| }); | |
| // Form submission - Login | |
| loginForm.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const enteredCode = familyCodeInput.value.trim(); | |
| const userName = document.getElementById('userName').value.trim() || 'Familiemedlem'; | |
| // Check if family exists | |
| const families = JSON.parse(localStorage.getItem('middagsplanlegger_families') || '[]'); | |
| const family = families.find(f => f.code === enteredCode); | |
| if (family) { | |
| // Successful login | |
| errorMessage.classList.add('hidden'); | |
| familyCodeInput.classList.remove('border-red-500'); | |
| // Set current family and user | |
| currentFamily = family; | |
| currentUser = userName; | |
| // Hide login and show app | |
| loginPage.classList.add('hidden'); | |
| appPage.classList.remove('hidden'); | |
| // Display family info | |
| familyNameDisplay.textContent = family.name; | |
| currentUserDisplay.textContent = `Logget inn som: ${userName}`; | |
| // Display current week | |
| displayCurrentWeek(); | |
| // Load any saved data | |
| loadSavedData(); | |
| } else { | |
| // Failed login | |
| errorMessage.classList.remove('hidden'); | |
| familyCodeInput.classList.add('border-red-500'); | |
| loginForm.classList.add('shake'); | |
| // Remove shake class after animation completes | |
| setTimeout(() => { | |
| loginForm.classList.remove('shake'); | |
| }, 500); | |
| // Focus and select the password field | |
| familyCodeInput.focus(); | |
| familyCodeInput.select(); | |
| } | |
| }); | |
| // Create new family button | |
| createFamilyBtn.addEventListener('click', function() { | |
| loginPage.classList.add('hidden'); | |
| createFamilyPage.classList.remove('hidden'); | |
| }); | |
| // Cancel create family | |
| cancelCreateFamily.addEventListener('click', function() { | |
| createFamilyPage.classList.add('hidden'); | |
| loginPage.classList.remove('hidden'); | |
| }); | |
| // Form submission - Create new family | |
| createFamilyForm.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const familyName = document.getElementById('newFamilyName').value.trim(); | |
| const familyCode = document.getElementById('newFamilyCode').value.trim(); | |
| const adminName = document.getElementById('adminName').value.trim(); | |
| // Validate code length | |
| if (familyCode.length < 4 || familyCode.length > 8) { | |
| alert('Familie-koden må være mellom 4 og 8 tegn'); | |
| return; | |
| } | |
| // Check if code is already taken | |
| const families = JSON.parse(localStorage.getItem('middagsplanlegger_families') || '[]'); | |
| const codeExists = families.some(f => f.code === familyCode); | |
| if (codeExists) { | |
| alert('Denne koden er allerede i bruk. Vennligst velg en annen.'); | |
| return; | |
| } | |
| // Create new family | |
| const newFamily = { | |
| name: familyName, | |
| code: familyCode, | |
| created: new Date().toISOString(), | |
| admin: adminName | |
| }; | |
| // Save to localStorage | |
| families.push(newFamily); | |
| localStorage.setItem('middagsplanlegger_families', JSON.stringify(families)); | |
| // Set as current family | |
| currentFamily = newFamily; | |
| currentUser = adminName; | |
| // Show the app | |
| createFamilyPage.classList.add('hidden'); | |
| appPage.classList.remove('hidden'); | |
| // Display family info | |
| familyNameDisplay.textContent = familyName; | |
| currentUserDisplay.textContent = `Logget inn som: ${adminName} (Administrator)`; | |
| // Display current week | |
| displayCurrentWeek(); | |
| // Clear form | |
| this.reset(); | |
| }); | |
| // Clear error when typing | |
| familyCodeInput.addEventListener('input', function() { | |
| errorMessage.classList.add('hidden'); | |
| this.classList.remove('border-red-500'); | |
| }); | |
| // Save button functionality | |
| document.getElementById('saveBtn').addEventListener('click', function() { | |
| saveData(); | |
| updateDinnersList(); | |
| }); | |
| // Function to save data to localStorage | |
| function saveData() { | |
| if (!currentFamily) return; | |
| const weekKey = getCurrentWeekKey(currentFamily.code); | |
| const weekData = { | |
| monday: { | |
| meal: document.getElementById('monday').value, | |
| ratings: [] // Array to store all user ratings | |
| }, | |
| tuesday: { | |
| meal: document.getElementById('tuesday').value, | |
| ratings: [] | |
| }, | |
| wednesday: { | |
| meal: document.getElementById('wednesday').value, | |
| ratings: [] | |
| }, | |
| thursday: { | |
| meal: document.getElementById('thursday').value, | |
| ratings: [] | |
| }, | |
| friday: { | |
| meal: document.getElementById('friday').value, | |
| ratings: [] | |
| }, | |
| saturday: { | |
| meal: document.getElementById('saturday').value, | |
| ratings: [] | |
| }, | |
| sunday: { | |
| meal: document.getElementById('sunday').value, | |
| ratings: [] | |
| } | |
| }; | |
| // Check if we already have data for this week | |
| const existingData = localStorage.getItem(weekKey); | |
| if (existingData) { | |
| const existingWeekData = JSON.parse(existingData); | |
| // Preserve any existing ratings when updating meals | |
| for (const day in weekData) { | |
| if (existingWeekData[day] && existingWeekData[day].ratings) { | |
| weekData[day].ratings = existingWeekData[day].ratings; | |
| } | |
| } | |
| } | |
| localStorage.setItem(weekKey, JSON.stringify(weekData)); | |
| // Show a temporary success message | |
| const btn = this; | |
| const originalText = btn.innerHTML; | |
| btn.innerHTML = '<i class="fas fa-check mr-2"></i> Lagret!'; | |
| btn.classList.remove('bg-green-500'); | |
| btn.classList.add('bg-green-600'); | |
| setTimeout(() => { | |
| btn.innerHTML = originalText; | |
| btn.classList.remove('bg-green-600'); | |
| btn.classList.add('bg-green-500'); | |
| }, 2000); | |
| } | |
| // Function to load saved data from localStorage | |
| function loadSavedData() { | |
| if (!currentFamily) return; | |
| const weekKey = getCurrentWeekKey(currentFamily.code); | |
| const savedData = localStorage.getItem(weekKey); | |
| if (savedData) { | |
| const weekData = JSON.parse(savedData); | |
| // Populate the form fields | |
| document.getElementById('monday').value = weekData.monday.meal || ''; | |
| document.getElementById('tuesday').value = weekData.tuesday.meal || ''; | |
| document.getElementById('wednesday').value = weekData.wednesday.meal || ''; | |
| document.getElementById('thursday').value = weekData.thursday.meal || ''; | |
| document.getElementById('friday').value = weekData.friday.meal || ''; | |
| document.getElementById('saturday').value = weekData.saturday.meal || ''; | |
| document.getElementById('sunday').value = weekData.sunday.meal || ''; | |
| // Update the dinners list | |
| updateDinnersList(); | |
| } | |
| } | |
| // Function to update the dinners list display | |
| function updateDinnersList() { | |
| if (!currentFamily) return; | |
| const dinnersList = document.getElementById('dinnersList'); | |
| const ratingSummary = document.getElementById('ratingSummary'); | |
| const averageRating = document.getElementById('averageRating'); | |
| const weekKey = getCurrentWeekKey(currentFamily.code); | |
| const savedData = localStorage.getItem(weekKey); | |
| if (!savedData) { | |
| dinnersList.innerHTML = '<p class="text-gray-500 italic">Ingen middager lagt til ennå</p>'; | |
| ratingSummary.classList.add('hidden'); | |
| return; | |
| } | |
| const weekData = JSON.parse(savedData); | |
| let hasMeals = false; | |
| let html = ''; | |
| let totalRatings = 0; | |
| let ratedMeals = 0; | |
| const days = [ | |
| { name: 'Mandag', key: 'monday' }, | |
| { name: 'Tirsdag', key: 'tuesday' }, | |
| { name: 'Onsdag', key: 'wednesday' }, | |
| { name: 'Torsdag', key: 'thursday' }, | |
| { name: 'Fredag', key: 'friday' }, | |
| { name: 'Lørdag', key: 'saturday' }, | |
| { name: 'Søndag', key: 'sunday' } | |
| ]; | |
| days.forEach(day => { | |
| const mealData = weekData[day.key]; | |
| if (mealData.meal) { | |
| hasMeals = true; | |
| // Calculate average rating for this meal | |
| let avgRating = 0; | |
| if (mealData.ratings && mealData.ratings.length > 0) { | |
| const sum = mealData.ratings.reduce((a, b) => a + b, 0); | |
| avgRating = sum / mealData.ratings.length; | |
| } | |
| // Create rating HTML for each meal | |
| let ratingHtml = ''; | |
| if (avgRating > 0) { | |
| const percentage = (avgRating / 5) * 100; | |
| ratingHtml = ` | |
| <div class="mt-2 flex items-center"> | |
| <div class="star-display mr-2"> | |
| <div class="stars">★★★★★</div> | |
| <div class="stars-filled" style="width: ${percentage}%">★★★★★</div> | |
| </div> | |
| <span class="text-sm text-gray-500">(${avgRating.toFixed(1)} av 5, ${mealData.ratings.length} stemmer)</span> | |
| </div> | |
| `; | |
| totalRatings += avgRating; | |
| ratedMeals++; | |
| } | |
| html += ` | |
| <div class="border-b border-gray-100 pb-4 last:border-0"> | |
| <div class="flex justify-between items-start"> | |
| <div> | |
| <p class="font-medium">${day.name}: ${mealData.meal}</p> | |
| ${ratingHtml} | |
| </div> | |
| <div class="flex items-center gap-2"> | |
| <div class="star-rating"> | |
| <input type="radio" id="${day.key}-5" name="${day.key}-rating" value="5"> | |
| <label for="${day.key}-5">★</label> | |
| <input type="radio" id="${day.key}-4" name="${day.key}-rating" value="4"> | |
| <label for="${day.key}-4">★</label> | |
| <input type="radio" id="${day.key}-3" name="${day.key}-rating" value="3"> | |
| <label for="${day.key}-3">★</label> | |
| <input type="radio" id="${day.key}-2" name="${day.key}-rating" value="2"> | |
| <label for="${day.key}-2">★</label> | |
| <input type="radio" id="${day.key}-1" name="${day.key}-rating" value="1"> | |
| <label for="${day.key}-1">★</label> | |
| </div> | |
| <button | |
| onclick="rateMeal('${day.key}')" | |
| class="bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium py-1 px-3 rounded transition duration-200" | |
| > | |
| Rate | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| }); | |
| if (!hasMeals) { | |
| html = '<p class="text-gray-500 italic">Ingen middager lagt til ennå</p>'; | |
| ratingSummary.classList.add('hidden'); | |
| } else { | |
| // Calculate and show average rating if there are any rated meals | |
| if (ratedMeals > 0) { | |
| const avgRating = totalRatings / ratedMeals; | |
| const percentage = (avgRating / 5) * 100; | |
| // Update the average rating display | |
| averageRating.innerHTML = ` | |
| <div class="stars">★★★★★</div> | |
| <div class="stars-filled" style="width: ${percentage}%">★★★★★</div> | |
| <span class="text-sm text-gray-500 ml-2">(${avgRating.toFixed(1)} av 5)</span> | |
| `; | |
| ratingSummary.classList.remove('hidden'); | |
| } else { | |
| ratingSummary.classList.add('hidden'); | |
| } | |
| } | |
| dinnersList.innerHTML = html; | |
| } | |
| // Add rateMeal function to the window object so it can be called from inline onclick | |
| window.rateMeal = function(dayKey) { | |
| if (!currentFamily) return; | |
| const selectedRating = document.querySelector(`input[name="${dayKey}-rating"]:checked`); | |
| if (!selectedRating) { | |
| alert('Velg en vurdering først!'); | |
| return; | |
| } | |
| const rating = parseInt(selectedRating.value); | |
| const weekKey = getCurrentWeekKey(currentFamily.code); | |
| const savedData = localStorage.getItem(weekKey); | |
| if (savedData) { | |
| const weekData = JSON.parse(savedData); | |
| // Initialize ratings array if it doesn't exist | |
| if (!weekData[dayKey].ratings) { | |
| weekData[dayKey].ratings = []; | |
| } | |
| // Add the new rating | |
| weekData[dayKey].ratings.push({ | |
| value: rating, | |
| user: currentUser || 'Anonym', | |
| timestamp: new Date().toISOString() | |
| }); | |
| // Save back to localStorage | |
| localStorage.setItem(weekKey, JSON.stringify(weekData)); | |
| // Update the display | |
| updateDinnersList(); | |
| // Show a temporary success message | |
| const rateBtn = selectedRating.closest('div').querySelector('button'); | |
| const originalText = rateBtn.innerHTML; | |
| rateBtn.innerHTML = '<i class="fas fa-check mr-1"></i> Lagret'; | |
| rateBtn.classList.remove('bg-blue-500'); | |
| rateBtn.classList.add('bg-green-500'); | |
| setTimeout(() => { | |
| rateBtn.innerHTML = 'Rate'; | |
| rateBtn.classList.remove('bg-green-500'); | |
| rateBtn.classList.add('bg-blue-500'); | |
| }, 2000); | |
| } | |
| }; | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=oyvsar/middagsplanlegger" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |