From 71564b1c8072788d3e0f9236d7c4f919a3c18bcb Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Wed, 3 Sep 2025 21:03:46 -0300 Subject: [PATCH] feat: replace the food table by a better ui --- index.html | 31 ++------- package-lock.json | 16 ++--- package.json | 4 +- src/index.ts | 169 +++++++++++++++++++++++++++++----------------- style.css | 27 +++++--- 5 files changed, 138 insertions(+), 109 deletions(-) diff --git a/index.html b/index.html index 5dafd76..197923d 100644 --- a/index.html +++ b/index.html @@ -183,33 +183,10 @@ -
-
Food Log
-
- - - - - - - - - - - - - - - - - - - -
IDTimeFoodGramsCaloriesProtein (g)Fat (g)Carbs (g)Fiber (g)ActionAlkaline
-
-
- -
+
Food Log
+ +
+
diff --git a/package-lock.json b/package-lock.json index abf198f..b3d4055 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "appwrite": "^18.2.0", + "appwrite": "^19.0.0", "sweetalert": "^2.1.2" }, "devDependencies": { "typescript": "^5.9.2", - "vite": "^7.1.3" + "vite": "^7.1.4" } }, "node_modules/@esbuild/aix-ppc64": { @@ -747,9 +747,9 @@ "license": "MIT" }, "node_modules/appwrite": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-18.2.0.tgz", - "integrity": "sha512-g7pQpsxqR7+amEIaQLXMN4XzdQKenTHnGdA4s7UUJdZufhlHdJby8895h8z893+S0XipeHZhi0wpxYA2An95Rg==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-19.0.0.tgz", + "integrity": "sha512-guamzST6Fvn/lvuBMLJGzJGL+N+o313j53qyfKM1ZHfcDd1ADnSONgm9DZihRFYBaWtU7s1bwkphkBJWSUwurQ==", "license": "BSD-3-Clause" }, "node_modules/es6-object-assign": { @@ -999,9 +999,9 @@ } }, "node_modules/vite": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", - "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.4.tgz", + "integrity": "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index a870824..4503707 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,10 @@ "description": "", "devDependencies": { "typescript": "^5.9.2", - "vite": "^7.1.3" + "vite": "^7.1.4" }, "dependencies": { - "appwrite": "^18.2.0", + "appwrite": "^19.0.0", "sweetalert": "^2.1.2" } } diff --git a/src/index.ts b/src/index.ts index a677558..af5cdaa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import { getButtonById, getButtonListByClassName, getDivById, getInputById, show import { FoodItem, FoodStorage } from './types.js'; import { AppwriteAuth, AppwriteDB } from './appwrite.js'; import swal from 'sweetalert'; +import { Models } from 'appwrite'; // App state let selectedDate = new Date(); @@ -11,6 +12,9 @@ let searchTimeout: number | null = null; let selectedFood: FoodItem | null = null; // review here let currentHighlightIndex: number = -1; let currentResults: FoodItem[] = []; +let totalCaloriesToday: number = 0; +let totalGramsToday: number = 0; +let totalAlkalineToday: number = 0; // Authentication state let currentUser: any | null = null; @@ -283,11 +287,20 @@ function hideLoading() { // Clear application data function clearAppData() { - getDivById('foodTableBody').innerHTML = ''; getDivById('caloriesCounter').textContent = '0'; getInputById('foodSearchInput').value = ''; getInputById('gramAmount').value = '100'; + getDivById('foodCardsContainer').innerHTML = ` +
+
🍽️
+
No food items logged yet.
+
Add your first item to get started!
+
+ `; selectedFood = null; + totalCaloriesToday = 0; + totalGramsToday = 0; + totalAlkalineToday = 0; } const getFoodData = (grams: number, foodData: FoodItem): FoodItem => { @@ -537,17 +550,65 @@ function hideSearchResults() { currentHighlightIndex = -1; } +function toggleCardHandler(e: Event) { + e.preventDefault(); + e.stopPropagation(); + + const card = e.currentTarget as HTMLElement; + card.classList.toggle('expanded'); + + const cardDetails = card.nextElementSibling as HTMLElement; + if (cardDetails && cardDetails.classList.contains('card-details')) { + cardDetails.classList.toggle('expanded'); + } +} + +function deleteCardHandler(e: Event) { + e.preventDefault(); + e.stopPropagation(); + + const card = e.currentTarget as HTMLElement; + const foodId = card.getAttribute('data-food-id'); + + if (foodId) { + handleDeleteFood(foodId); + } +} + +function editCardHandler(e: Event) { + e.preventDefault(); + e.stopPropagation(); + + const card = e.currentTarget as HTMLElement; + const foodId = card.getAttribute('data-food-id'); + + if (foodId) { + // FIXME: Implement edit functionality + // handleEditFood(foodId); + window.alert('Edit functionality is not implemented yet.'); + } +} + const addDeleteEvents = () => { - const elements = getButtonListByClassName('delete-food-entry'); - Array.from(elements).forEach((el: HTMLButtonElement) => { - el.addEventListener('click', (e) => { - const target = e.target as HTMLElement; - const row = target.closest('tr'); - const entryId = row?.querySelector('.hidden-column')?.textContent; - if (entryId && row) { - handleDeleteFood(entryId); - } - }); + // Setup toggle show cards events + const cards = getButtonListByClassName('card-header-toggle'); + Array.from(cards).forEach((card: HTMLElement) => { + card.removeEventListener('click', toggleCardHandler); + card.addEventListener('click', toggleCardHandler); + }); + + // Setup delete card events + const deleteCards = getButtonListByClassName('btn-delete'); + Array.from(deleteCards).forEach((card: HTMLElement) => { + card.removeEventListener('click', deleteCardHandler); + card.addEventListener('click', deleteCardHandler); + }); + + // Setup edit card events + const editCards = getButtonListByClassName('btn-edit'); + Array.from(editCards).forEach((card: HTMLElement) => { + card.removeEventListener('click', editCardHandler); + card.addEventListener('click', editCardHandler); }); } @@ -611,7 +672,7 @@ const addFood = async () => { addFoodToTable(entry, savedEntry.$id); // Update totals - updateTotalCalories(); + updateTotalCalories([savedEntry]); // Update total macros const proteinDiv: HTMLElement = getDivById('proteinValue'); @@ -655,7 +716,6 @@ async function loadFoodEntries(date: Date) { const entries = await AppwriteDB.getFoodEntries(date); // Clear current table - getDivById('foodTableBody').innerHTML = ''; getDivById('foodCardsContainer').innerHTML = ''; // Add each entry to table @@ -691,7 +751,7 @@ async function loadFoodEntries(date: Date) { addDeleteEvents(); // Update total calories - updateTotalCalories(); + updateTotalCalories(entries); // Update counters let totalCalories = 0; @@ -783,7 +843,6 @@ async function handleDeleteFood(documentId: string) { await AppwriteDB.deleteFoodEntry(documentId); selectDate(selectedDate); - } catch (error) { console.error('Delete food error:', error); swal('Oh no!', 'Failed to delete food entry: ' + error.message, 'error'); @@ -794,36 +853,18 @@ async function handleDeleteFood(documentId: string) { // Add food to table (existing function - update to use document ID) function addFoodToTable(foodData: FoodStorage, documentId: string) { - const tableBody = document.getElementById('foodTableBody'); - const row = document.createElement('tr'); - - row.innerHTML = ` - ${documentId} - ${foodData.time || new Date().toLocaleTimeString()} - ${foodData.name} - ${foodData.grams} - ${foodData.calories} - ${foodData.protein} - ${foodData.fat} - ${foodData.carbs} - ${foodData.fiber} - - ${foodData.alkaline} - `; - - tableBody?.appendChild(row); - - // Setup Delete events - addDeleteEvents(); - // New Food card - const container = document.getElementById('foodCardsContainer'); + const container = getDivById('foodCardsContainer'); const card = document.createElement('div'); card.className = 'food-card'; card.setAttribute('data-id', documentId); + if (container.innerHTML.includes('empty-state')) { + container.innerHTML = ''; + } + card.innerHTML = ` -
+
${foodData.name}
@@ -854,42 +895,45 @@ function addFoodToTable(foodData: FoodStorage, documentId: string) {
- - + +
`; container?.appendChild(card); + + // Setup Delete events + addDeleteEvents(); } // Update total calories -function updateTotalCalories() { - const caloriesCells = document.querySelectorAll('#foodTableBody td:nth-child(5)'); // 5th column is calories - let total = 0; - - caloriesCells.forEach((cell: Element) => { - total += parseInt(cell.innerHTML) || 0; +function updateTotalCalories(entries: Models.DefaultDocument[]) { + let total = totalCaloriesToday; + + entries.forEach((entry: Models.DefaultDocument) => { + total += entry.calories || 0; }); - + getDivById('caloriesCounter').textContent = total.toString(); + totalCaloriesToday = total; + + // FIX ME: alkaline count // Update alkaline level - const gramsCell = document.querySelectorAll('#foodTableBody td:nth-child(4)'); // 4th is grams - let totalGrams = 0; + let totalGrams = totalGramsToday; let indexMap: {index: number, grams: number}[] = []; - gramsCell.forEach((cell: Element, key: number) => { - totalGrams += parseInt(cell.innerHTML) || 0; + entries.forEach((entry: Models.DefaultDocument, key: number) => { + totalGrams += entry.grams || 0; indexMap.push({ index: key, - grams: parseInt(cell.innerHTML) || 0 + grams: entry.grams || 0 }); }); - const alkalineCell = document.querySelectorAll('#foodTableBody td:nth-child(11)'); // 11th is alkaline - let totalAlkaline = 0; - alkalineCell.forEach((cell: Element, key: number) => { - if (cell.innerHTML === 'true') { + let totalAlkaline = totalAlkalineToday; + entries.forEach((entry: Models.DefaultDocument, key: number) => { + if (entry.alkaline) { for (let ob of indexMap) { if (ob.index === key) { totalAlkaline += ob.grams; @@ -916,6 +960,9 @@ const nextMonth = () => { const selectDate = async (date: Date) => { selectedDate = date; + totalCaloriesToday = 0; + totalGramsToday = 0; + totalAlkalineToday = 0; updateCurrentDate(); await loadFoodEntries(date); renderCalendar(); @@ -1028,16 +1075,16 @@ function createDayElement(day: number, isOtherMonth: boolean, year: number, mont return dayElement; } -function toggleCard(id) { +function toggleCard(id: string) { const card = document.querySelector(`[data-id="${id}"]`); - card.classList.toggle('expanded'); + card?.classList.toggle('expanded'); } -function deleteFood(id) { +function deleteFood(id: string) { handleDeleteFood(id); } -function editFood(id) { +function editFood(id: string) { alert(`Edit functionality for food item ${id} would be implemented here`); } diff --git a/style.css b/style.css index 19af16c..ccbe813 100644 --- a/style.css +++ b/style.css @@ -232,6 +232,10 @@ h1 { margin-bottom: 4px; } +.card-header .food-name { + color: #fff; +} + .food-time { font-size: 13px; opacity: 0.9; @@ -251,19 +255,20 @@ h1 { opacity: 0.8; } -.food-card.expanded .expand-icon { - transform: rotate(180deg); -} - .card-details { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease; - background: #fafafa; + display: none; } -.food-card.expanded .card-details { - max-height: 300px; +.card-details.expanded { + display: block; +} + +.card-header-toggle .expand-icon { + transition: transform 0.3s ease; +} + +.card-header-toggle.expanded .expand-icon { + transform: rotate(180deg); } .details-content { @@ -288,7 +293,6 @@ h1 { .nutrition-label { font-size: 12px; color: #666; - text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; } @@ -530,6 +534,7 @@ label { border-radius: 6px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1); overflow: hidden; + margin-top: 30px; } .calendar-header {