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/.env.example b/src/.env.example
new file mode 100644
index 0000000..0493e4a
--- /dev/null
+++ b/src/.env.example
@@ -0,0 +1,4 @@
+VITE_APPWRITE_ENDPOINT=here
+VITE_APPWRITE_DBID=here
+VITE_APPWRITE_FOODENTRIESID=here
+VITE_APPWRITE_USERSETTINGSID=here
\ No newline at end of file
diff --git a/src/appwrite.ts b/src/appwrite.ts
index d3a53b2..3dee252 100644
--- a/src/appwrite.ts
+++ b/src/appwrite.ts
@@ -98,6 +98,26 @@ export class AppwriteDB {
}
}
+ // Upadte food entry
+ static async updateFoodEntry(documentId: string, entryData: Partial
) {
+ try {
+ const response = await databases.updateDocument(
+ DATABASE_ID,
+ FOOD_ENTRIES_COLLECTION_ID,
+ documentId,
+ {
+ ...entryData,
+ userId: (await account.get()).$id,
+ }
+ );
+ console.debug('Food entry updated:', response);
+ return response;
+ } catch (error) {
+ console.error('Update food entry error:', error);
+ throw error;
+ }
+ }
+
// Save food entry
static async saveUserSettings(userSettings: UserSettings) {
try {
diff --git a/src/foodDatabase.ts b/src/foodDatabase.ts
index 9652aba..999acae 100644
--- a/src/foodDatabase.ts
+++ b/src/foodDatabase.ts
@@ -15,19 +15,29 @@ export const foodDatabase: FoodItem[] = [
{ name: 'Semente de Abóbora', info: { calories: 446, protein: 18.55, fat: 19.4, carbs: 53.75, fiber: 6, category: 'fats', alkaline: true } },
// Proteínas - Proteins
- { name: 'Atum Sólido, natural', info: { calories: 135, protein: 28.3, fat: 2.7, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Atum, conserva em óleo', info: { calories: 166, protein: 26.2, fat: 6, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
{ name: 'Filé de Tilápia, cozido', info: { calories: 128, protein: 26.1, fat: 2.6, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
- { name: 'Ovo inteiro, cozido', info: { calories: 146, protein: 13.3, fat: 9.5, carbs: 0.6, fiber: 0, category: 'proteins', alkaline: false } },
- { name: 'Ovo clara, cozido', info: { calories: 59, protein: 13.4, fat: 0.1, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Ovo, de galinha, inteiro, cozido', info: { calories: 146, protein: 13.3, fat: 9.5, carbs: 0.6, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Ovo, de galinha, gema, cozida', info: { calories: 353, protein: 15.9, fat: 30.8, carbs: 1.6, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Ovo, de galinha, clara, cozida', info: { calories: 59, protein: 13.4, fat: 0.1, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
{ name: 'PTS, crua', info: { calories: 288, protein: 52, fat: 0, carbs: 20, fiber: 14, category: 'proteins', alkaline: false } },
- { name: 'Patinho moído, grelhado', info: { calories: 219, protein: 35.9, fat: 7.3, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Carne, bovina, patinho, sem gordura, grelhado', info: { calories: 219, protein: 35.9, fat: 7.3, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
{ name: 'Soja em Pó Growth', info: { calories: 400, protein: 86.7, fat: 5, carbs: 3.3, fiber: 0, category: 'proteins', alkaline: false } },
{ name: 'Sobrecoxa s/pele, cozida', info: { calories: 245, protein: 24.9, fat: 15.4, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
- { name: 'Tofu', info: { calories: 65, protein: 6.5, fat: 4, carbs: 2, fiber: 0.75, category: 'proteins', alkaline: false } },
+ { name: 'Soja, queijo (tofu)', info: { calories: 64, protein: 6.6, fat: 4, carbs: 2.1, fiber: 0.8, category: 'proteins', alkaline: false } },
{ name: 'Feijão preto, cozido', info: { calories: 77, protein: 4.5, fat: 0.5, carbs: 14, fiber: 8.4, category: 'proteins', alkaline: false } },
{ name: 'Lentilha, cozida', info: { calories: 93, protein: 6.3, fat: 0.5, carbs: 16.3, fiber: 7.9, category: 'proteins', alkaline: false } },
{ name: 'Frango, peito, sem pele, cozido', info: { calories: 163, protein: 31.5, fat: 3.2, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
{ name: 'Frango, peito, sem pele, grelhado', info: { calories: 159, protein: 32, fat: 2.5, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Carne, bovina, acém, moído, cozido', info: { calories: 212, protein: 26.7, fat: 10.9, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Carne, bovina, contra-filé, sem gordura, grelhado', info: { calories: 194, protein: 35.9, fat: 4.5, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Carne, bovina, costela, assada', info: { calories: 373, protein: 28.8, fat: 27.7, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Carne, bovina, cupim, assado', info: { calories: 330, protein: 28.6, fat: 23, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Carne, bovina, fígado, grelhado', info: { calories: 225, protein: 29.9, fat: 9, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Carne, bovina, picanha, sem gordura, grelhada', info: { calories: 238, protein: 31.9, fat: 11.3, carbs: 0, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Apresuntado', info: { calories: 129, protein: 13.5, fat: 6.7, carbs: 2.9, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Presunto, com capa de gordura', info: { calories: 128, protein: 14.4, fat: 6.8, carbs: 1.4, fiber: 0, category: 'proteins', alkaline: false } },
+ { name: 'Presunto, sem capa de gordura', info: { calories: 94, protein: 14.3, fat: 2.7, carbs: 2.1, fiber: 0, category: 'proteins', alkaline: false } },
// Carboidratos, amiláceos - Carbs
{ name: 'Grão de Bico, cozido', info: { calories: 180, protein: 9.5, fat: 3, carbs: 30, fiber: 8.6, category: 'carbs (high)', alkaline: false } },
@@ -41,16 +51,16 @@ export const foodDatabase: FoodItem[] = [
{ name: 'Batata inglesa, cozida', info: { calories: 52, protein: 1.2, fat: 0, carbs: 11.9, fiber: 1.3, category: 'carbs (high)', alkaline: true } },
{ name: 'Batata inglesa, crua', info: { calories: 64, protein: 1.8, fat: 0, carbs: 14.7, fiber: 1.2, category: 'carbs (high)', alkaline: true } },
{ name: 'Mandioca, cozida', info: { calories: 125, protein: 0.6, fat: 0.3, carbs: 30.1, fiber: 1.6, category: 'carbs (high)', alkaline: false } },
- { name: 'Pão Francês', info: { calories: 300, protein: 8, fat: 3.1, carbs: 58.6, fiber: 2.3, category: 'carbs (high)', alkaline: false } },
+ { name: 'Pão, trigo, francês', info: { calories: 300, protein: 8, fat: 3.1, carbs: 58.6, fiber: 2.3, category: 'carbs (high)', alkaline: false } },
{ name: 'Polenta, cozida', info: { calories: 192, protein: 4.8, fat: 1.2, carbs: 40, fiber: 3.8, category: 'carbs (high)', alkaline: false } },
- { name: 'Arroz branco, cozido', info: { calories: 128, protein: 2.5, fat: 0.2, carbs: 28.1, fiber: 1.6, category: 'carbs (high)', alkaline: false } },
- { name: 'Arroz integral, cozido', info: { calories: 124, protein: 2.6, fat: 1.0, carbs: 25.8, fiber: 2.7, category: 'carbs (high)', alkaline: false } },
+ { name: 'Arroz, tipo 1 (branco), cozido', info: { calories: 128, protein: 2.5, fat: 0.2, carbs: 28.1, fiber: 1.6, category: 'carbs (high)', alkaline: false } },
+ { name: 'Arroz, integral, cozido', info: { calories: 124, protein: 2.6, fat: 1.0, carbs: 25.8, fiber: 2.7, category: 'carbs (high)', alkaline: false } },
{ name: 'Aveia', info: { calories: 394, protein: 13.9, fat: 8.5, carbs: 66.6, fiber: 9.1, category: 'carbs (high)', alkaline: false } },
{ name: 'Ervilha seca, cozida', info: { calories: 88, protein: 7.5, fat: 0.5, carbs: 14.2, fiber: 9.7, category: 'carbs (high)', alkaline: true } },
{ name: 'Ervilha congelada, cozida', info: { calories: 57, protein: 5, fat: 1, carbs: 7, fiber: 8, category: 'carbs (high)', alkaline: true } },
- { name: 'Pão integral de forma', info: { calories: 253, protein: 9.4, fat: 3.7, carbs: 49.9, fiber: 6.9, category: 'carbs (high)', alkaline: false } },
- { name: 'Pipoca c/óleo', info: { calories: 448, protein: 9.9, fat: 15.9, carbs: 70.3, fiber: 14.3, category: 'carbs (high)', alkaline: false } },
- { name: 'Pipoca s/óleo', info: { calories: 178, protein: 3.3, fat: 7.2, carbs: 25, fiber: 4.3, category: 'carbs (high)', alkaline: false } },
+ { name: 'Pão, trigo, forma, integral', info: { calories: 253, protein: 9.4, fat: 3.7, carbs: 49.9, fiber: 6.9, category: 'carbs (high)', alkaline: false } },
+ { name: 'Pipoca, com óleo de soja, sem sal', info: { calories: 448, protein: 9.9, fat: 15.9, carbs: 70.3, fiber: 14.3, category: 'carbs (high)', alkaline: false } },
+ { name: 'Pipoca, sem óleo, sem sal', info: { calories: 178, protein: 3.3, fat: 7.2, carbs: 25, fiber: 4.3, category: 'carbs (high)', alkaline: false } },
{ name: 'Quinoa, cozida', info: { calories: 158, protein: 5.55, fat: 2.5, carbs: 29, fiber: 2.5, category: 'carbs (high)', alkaline: true } },
{ name: '70% Nibs Garoto', info: { calories: 544, protein: 8.4, fat: 40, carbs: 34, fiber: 0, category: 'carbs (high)', alkaline: false } },
{ name: 'Barra de Proteína Vegana Growth', info: { calories: 400, protein: 25, fat: 22, carbs: 25, fiber: 8, category: 'carbs (high)', alkaline: false } },
@@ -59,6 +69,9 @@ export const foodDatabase: FoodItem[] = [
{ name: 'Mel Silvestre', info: { calories: 230, protein: 0, fat: 0, carbs: 65, fiber: 0, category: 'carbs (high)', alkaline: false } },
{ name: 'Melado', info: { calories: 297, protein: 0, fat: 0, carbs: 76.6, fiber: 0, category: 'carbs (high)', alkaline: false } },
{ name: 'Tâmara desidratada', info: { calories: 230, protein: 0.3, fat: 33, carbs: 63.3, fiber: 7, category: 'carbs (high)', alkaline: false } },
+ { name: 'Pão, de queijo, assado', info: { calories: 363, protein: 5.1, fat: 24.6, carbs: 34.2, fiber: 0.6, category: 'carbs (high)', alkaline: false } },
+ { name: 'Cuscuz, de milho , cozido com sal', info: { calories: 113, protein: 2.2, fat: 0.7, carbs: 25.3, fiber: 2.1, category: 'carbs (high)', alkaline: false } },
+ { name: 'Paçoca, amendoim', info: { calories: 487, protein: 16, fat: 26.1, carbs: 52.4, fiber: 7.3, category: 'carbs (high)', alkaline: false } },
// Folhas e verduras - Leaves
{ name: 'Agrião', info: { calories: 17, protein: 2.7, fat: 0.2, carbs: 2.3, fiber: 2.1, category: 'leaves', alkaline: true } },
@@ -75,6 +88,22 @@ export const foodDatabase: FoodItem[] = [
{ name: 'Melão', info: { calories: 29, protein: 0.7, fat: 0, carbs: 7.5, fiber: 0.3, category: 'fruits', alkaline: true } },
{ name: 'Uva', info: { calories: 53, protein: 0.7, fat: 0.2, carbs: 13.6, fiber: 0.9, category: 'fruits', alkaline: true } },
{ name: 'Goiaba, branca, com casca, crua', info: { calories: 52, protein: 0.9, fat: 0.5, carbs: 12.4, fiber: 6.3, category: 'fruits', alkaline: true } },
+ { name: 'Morango, cru', info: { calories: 30, protein: 0.9, fat: 0.3, carbs: 6.8, fiber: 1.7, category: 'fruits', alkaline: true } },
+
+ // Dairy
+ { name: 'Iogurte, natural, desnatado', info: { calories: 41, protein: 3.8, fat: 0.3, carbs: 5.8, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Queijo, mozarela, mussarela, muçarela', info: { calories: 330, protein: 22.6, fat: 25.2, carbs: 3, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Queijo, parmesão', info: { calories: 453, protein: 35.6, fat: 33.5, carbs: 1.7, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Queijo, prato', info: { calories: 360, protein: 22.7, fat: 29.1, carbs: 1.9, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Queijo, requeijão, cremoso', info: { calories: 257, protein: 9.6, fat: 23.4, carbs: 2.4, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Requeijão light (Tirol)', info: { calories: 177, protein: 11, fat: 13.3, carbs: 3.3, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Queijo, ricota', info: { calories: 140, protein: 12.6, fat: 8.1, carbs: 3.8, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Creme de leite', info: { calories: 221, protein: 1.5, fat: 22.5, carbs: 4.6, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Leite, condensado', info: { calories: 313, protein: 7.7, fat: 6.7, carbs: 57, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Leite, de vaca, desnatado, pó', info: { calories: 362, protein: 34.7, fat: 0.9, carbs: 53, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Leite, de vaca, desnatado, UTH (Tirol)', info: { calories: 30.5, protein: 3.15, fat: 0, carbs: 4.5, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Leite, de vaca, integral, UTH (Tirol)', info: { calories: 57.5, protein: 3.15, fat: 3, carbs: 4.5, fiber: 0, category: 'dairy', alkaline: true } },
+ { name: 'Leite, de vaca, integral, pó', info: { calories: 496, protein: 26, fat: 26.4, carbs: 38.4, fiber: 0, category: 'dairy', alkaline: true } },
// Low carb
{ name: 'Abóbrinha, italinaa, cozida', info: { calories: 15, protein: 1.1, fat: 0.2, carbs: 3.0, fiber: 1.6, category: 'carbs (low)', alkaline: true } },
@@ -89,5 +118,6 @@ export const foodDatabase: FoodItem[] = [
{ name: 'Couve flor, cozida', info: { calories: 19, protein: 1.2, fat: 0.3, carbs: 3.9, fiber: 2.1, category: 'carbs (low)', alkaline: true } },
{ name: 'Pepino', info: { calories: 10, protein: 0.9, fat: 0, carbs: 2, fiber: 1.1, category: 'carbs (low)', alkaline: true } },
{ name: 'Chuchu, cozido', info: { calories: 19, protein: 0.4, fat: 0, carbs: 4.9, fiber: 1.0, category: 'carbs (low)', alkaline: true } },
- { name: 'Cogumelo Paris, in natura', info: { calories: 17, protein: 4.9, fat: 0, carbs: 1, fiber: 1.7, category: 'proteins', alkaline: true } },
+ { name: 'Cogumelo Paris, in natura', info: { calories: 17, protein: 4.9, fat: 0, carbs: 1, fiber: 1.7, category: 'carbs (low)', alkaline: true } },
+ { name: 'Milho, verde, enlatado, drenado', info: { calories: 98, protein: 3.2, fat: 2.4, carbs: 17.1, fiber: 4.6, category: 'carbs (low)', alkaline: true } },
];
diff --git a/src/index.ts b/src/index.ts
index 9a1efc6..749caf5 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;
@@ -281,13 +285,31 @@ function hideLoading() {
loadingOverlay.classList.add('hidden');
}
+function clearEditing() {
+ searchInput.value = '';
+ getInputById('gramAmount').value = '100';
+ showFoodPreview(false);
+ getButtonById('add-food-btn').innerHTML = 'Add Food';
+ selectedFood = null;
+}
+
// 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;
+ getButtonById('cancel-food-btn').style.display = 'none';
}
const getFoodData = (grams: number, foodData: FoodItem): FoodItem => {
@@ -309,7 +331,7 @@ const getFoodData = (grams: number, foodData: FoodItem): FoodItem => {
const getFoodItemByName = (foodName: string): FoodItem => {
const foodDataSearch: FoodItem[] = foodDatabase.filter(f => f.name === foodName);
if (foodDataSearch.length === 0) {
- throw new Error(`Food not found for name ${name}`);
+ throw new Error(`Food not found for name ${foodName}`);
}
return foodDataSearch[0];
}
@@ -353,7 +375,11 @@ const setupEventListeners = () => {
});
getButtonById('add-food-btn').addEventListener('click', () => {
- addFood();
+ if (getButtonById('add-food-btn').innerHTML === 'Add Food') {
+ addFood();
+ } else {
+ updateFood();
+ }
});
getButtonById('next-month-btn').addEventListener('click', () => {
@@ -375,6 +401,10 @@ const setupEventListeners = () => {
toggleAuthForms();
});
+ getButtonById('cancel-food-btn').addEventListener('click', () => {
+ clearEditing();
+ });
+
// Auth forms
document.getElementById('loginForm')?.addEventListener('submit', handleLogin);
document.getElementById('registerForm')?.addEventListener('submit', handleRegister);
@@ -473,12 +503,13 @@ function displaySearchResults(results: FoodItem[]) {
searchResults.innerHTML = 'No foods found. Try a different search term.
';
} else {
const icons = {
- 'fats': '🥑',
- 'proteins': '🫘',
- 'carbs (high)': '🍞',
- 'leaves': '🥬',
- 'fruits': '🍊',
- 'carbs (low)': '🍍'
+ 'fats': '🥑 🍳 🍟',
+ 'proteins': '🫘 🥩 🥚',
+ 'carbs (high)': '🍞 🥔 🍠',
+ 'leaves': '🥬 🥗 🌿',
+ 'fruits': '🍊 🍇 🍎',
+ 'carbs (low)': '🥦 🍅 🍓',
+ 'dairy': '🧀 🧈 🥛'
};
searchResults.innerHTML = results.map((food, index) => `
@@ -531,23 +562,101 @@ function selectFood(food: FoodItem) {
previewCalories(food);
}
+async function setFoodToEdit(foodId: string) {
+ showLoading();
+
+ try {
+ // get food item
+ const entries = await AppwriteDB.getFoodEntries(selectedDate);
+ const foodToEdit = entries.filter(entry => entry.$id === foodId);
+ if (foodToEdit.length === 0) {
+ swal('Hey!', 'Unable to get food entry from server', 'error');
+ return;
+ }
+
+ // set the selected food name and quantity
+ const foodData: FoodItem = getFoodItemByName(foodToEdit[0].name);
+ selectedFood = foodData;
+ searchInput.value = foodToEdit[0].name;
+ getInputById('gramAmount').value = foodToEdit[0].grams;
+ previewCalories(foodData)
+
+ getButtonById('add-food-btn').innerHTML = 'Save Food';
+ getInputById('foodIdToUpdate').value = foodId;
+ getInputById('foodTimeToUpdate').value = foodToEdit[0].time;
+
+ hideLoading();
+ } catch (error) {
+ console.error('Set food to edit error:', error);
+ swal('Oh no!', 'Failed to load food entry: ' + error.message, 'error');
+ } finally {
+ hideLoading();
+ }
+}
+
// Hide search results
function hideSearchResults() {
searchResults.classList.remove('show');
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) {
+ setFoodToEdit(foodId);
+ }
+}
+
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, row);
- }
- });
+ // 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);
});
}
@@ -562,6 +671,56 @@ const updateCurrentDate = () => {
});
}
+const updateFood = async () => {
+ if (!currentUser) {
+ swal('Hey!', 'Please log in to add food entries.', 'info');
+ return;
+ }
+
+ const gramAmount = getInputById('gramAmount');
+
+ if (!selectedFood || !gramAmount.value) {
+ swal('Hey!', 'Please select a food item and enter the amount', 'error');
+ return;
+ }
+
+ showLoading();
+
+ try {
+ const foodId = getInputById('foodIdToUpdate').value;
+ const grams: number = parseFloat(gramAmount.value);
+ const foodData: FoodItem = getFoodItemByName(selectedFood.name);
+
+ // Current date, with timezone
+ const date = new Date(selectedDate.getTime() - (selectedDate.getTimezoneOffset() * 60000)).toISOString();
+
+ // Calculate nutrition for the amount
+ const proportion: FoodItem = getFoodData(grams, foodData);
+ const entry: FoodStorage = {
+ name: selectedFood.name,
+ grams: grams,
+ calories: proportion.info.calories,
+ protein: proportion.info.protein,
+ fat: proportion.info.fat,
+ carbs: proportion.info.carbs,
+ fiber: proportion.info.fiber,
+ date: date.split('T')[0],
+ alkaline: selectedFood.info.alkaline,
+ time: getInputById('foodTimeToUpdate').value,
+ };
+
+ // Save to Appwrite
+ await AppwriteDB.updateFoodEntry(foodId, entry);
+ clearEditing();
+ selectDate(selectedDate);
+ } catch (error) {
+ console.error('Update food error:', error);
+ swal('Oh no!', 'Failed to update food entry: ' + error.message, 'error');
+ } finally {
+ hideLoading();
+ }
+}
+
// Add food to the log
const addFood = async () => {
if (!currentUser) {
@@ -572,7 +731,7 @@ const addFood = async () => {
const gramAmount = getInputById('gramAmount');
if (!selectedFood || !gramAmount.value) {
- swal('Hey!', 'Please select a food item and enter amount', 'error');
+ swal('Hey!', 'Please select a food item and enter the amount', 'error');
return;
}
@@ -611,7 +770,7 @@ const addFood = async () => {
addFoodToTable(entry, savedEntry.$id);
// Update totals
- updateTotalCalories();
+ updateTotalCalories([savedEntry]);
// Update total macros
const proteinDiv: HTMLElement = getDivById('proteinValue');
@@ -655,7 +814,7 @@ 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
entries.forEach(entry => {
@@ -676,11 +835,21 @@ async function loadFoodEntries(date: Date) {
addFoodToTable(foodData, entry.$id);
});
+ if (entries.length === 0) {
+ getDivById('foodCardsContainer').innerHTML = `
+
+
🍽️
+
No food items logged yet.
+
Add your first item to get started!
+
+ `;
+ }
+
// Setup Delete events
addDeleteEvents();
// Update total calories
- updateTotalCalories();
+ updateTotalCalories(entries);
// Update counters
let totalCalories = 0;
@@ -751,7 +920,7 @@ async function loadFoodEntries(date: Date) {
}
// Handle food deletion
-async function handleDeleteFood(documentId: string, row: HTMLTableRowElement) {
+async function handleDeleteFood(documentId: string) {
if (!documentId) return;
const willDelete = await swal({
@@ -772,7 +941,6 @@ async function handleDeleteFood(documentId: string, row: HTMLTableRowElement) {
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');
@@ -783,55 +951,87 @@ async function handleDeleteFood(documentId: string, row: HTMLTableRowElement) {
// 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} |
+ // New Food card
+ 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 = `
+
+
+
+
+
+
Protein
+
${foodData.protein}g
+
+
+
Fat
+
${foodData.fat}g
+
+
+
Carbs
+
${foodData.carbs}g
+
+
+
Fiber
+
${foodData.fiber}g
+
+
+
+
+
+
+
+
`;
-
- tableBody?.appendChild(row);
+
+ 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;
@@ -858,6 +1058,9 @@ const nextMonth = () => {
const selectDate = async (date: Date) => {
selectedDate = date;
+ totalCaloriesToday = 0;
+ totalGramsToday = 0;
+ totalAlkalineToday = 0;
updateCurrentDate();
await loadFoodEntries(date);
renderCalendar();
@@ -970,4 +1173,17 @@ function createDayElement(day: number, isOtherMonth: boolean, year: number, mont
return dayElement;
}
+function toggleCard(id: string) {
+ const card = document.querySelector(`[data-id="${id}"]`);
+ card?.classList.toggle('expanded');
+}
+
+function deleteFood(id: string) {
+ handleDeleteFood(id);
+}
+
+function editFood(id: string) {
+ alert(`Edit functionality for food item ${id} would be implemented here`);
+}
+
(window as any).selectFood = selectFood;
diff --git a/src/types.ts b/src/types.ts
index 0722cf0..1fe51b6 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,4 +1,4 @@
-type FoodCategory = 'fats' | 'proteins' | 'carbs (high)' | 'leaves' | 'fruits' | 'carbs (low)';
+type FoodCategory = 'fats' | 'proteins' | 'carbs (high)' | 'leaves' | 'fruits' | 'carbs (low)' | 'dairy';
export type FoodItem = {
name: string;
diff --git a/style.css b/style.css
index 0c69910..bb2345c 100644
--- a/style.css
+++ b/style.css
@@ -167,11 +167,204 @@ h1 {
background-color: #f8f9ff;
}
+.food-log-section {
+ max-width: 800px;
+ margin: 0 auto;
+}
-.food-name {
+.section-title {
+ font-size: 24px;
font-weight: 600;
color: #333;
- margin-bottom: 2px;
+ margin-bottom: 20px;
+ text-align: center;
+}
+
+.food-cards-container {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.food-card {
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ transition: all 0.3s ease;
+}
+
+.food-card:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.card-header {
+ padding: 16px;
+ cursor: pointer;
+ display: flex;
+ justify-content: between;
+ align-items: center;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ position: relative;
+}
+
+.card-header:hover {
+ background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
+}
+
+.card-main-info {
+ flex: 1;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16px;
+}
+
+.food-name-time {
+ flex: 1;
+}
+
+.food-name {
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+ margin-bottom: 4px;
+}
+
+.card-header .food-name {
+ color: #fff;
+}
+
+.food-time {
+ font-size: 13px;
+ opacity: 0.9;
+}
+
+.calories-display {
+ font-size: 18px;
+ font-weight: 700;
+ color: #fff;
+ text-align: right;
+}
+
+.expand-icon {
+ margin-left: 12px;
+ font-size: 14px;
+ transition: transform 0.3s ease;
+ opacity: 0.8;
+}
+
+.card-details {
+ display: none;
+}
+
+.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 {
+ padding: 20px;
+}
+
+.nutrition-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+ gap: 16px;
+ margin-bottom: 20px;
+}
+
+.nutrition-item {
+ text-align: center;
+ padding: 12px;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.nutrition-label {
+ font-size: 12px;
+ color: #666;
+ letter-spacing: 0.5px;
+ margin-bottom: 4px;
+}
+
+.nutrition-value {
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+}
+
+.card-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ padding-top: 16px;
+ border-top: 1px solid #e0e0e0;
+}
+
+.btn {
+ padding: 8px 16px;
+ border: none;
+ border-radius: 6px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.btn-delete {
+ background: #ff4757;
+ color: white;
+}
+
+.btn-delete:hover {
+ background: #ff3838;
+}
+
+.btn-edit {
+ background: #5352ed;
+ color: white;
+}
+
+.btn-edit:hover {
+ background: #4834d4;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 60px 20px;
+ color: #666;
+}
+
+.empty-state-icon {
+ font-size: 48px;
+ margin-bottom: 16px;
+ opacity: 0.5;
+}
+
+.add-food-btn {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-size: 16px;
+ cursor: pointer;
+ margin-bottom: 20px;
+ transition: all 0.2s ease;
+}
+
+.add-food-btn:hover {
+ background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
+ transform: translateY(-1px);
}
.food-details {
@@ -209,7 +402,7 @@ h1 {
.add-food-form {
display: grid;
- grid-template-columns: 2fr 1fr auto;
+ grid-template-columns: 2fr 1fr 1fr auto;
gap: 15px;
align-items: end;
}
@@ -274,6 +467,24 @@ label {
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3);
}
+.cancel-btn {
+ padding: 17px 25px;
+ background: linear-gradient(135deg, #757575, #616161);
+ color: white;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 600;
+ transition: all 0.3s ease;
+ height: fit-content;
+}
+
+.cancel-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3);
+}
+
.food-table-section {
background: white;
border-radius: 6px;
@@ -341,6 +552,7 @@ label {
border-radius: 6px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
overflow: hidden;
+ margin-top: 30px;
}
.calendar-header {
@@ -522,4 +734,25 @@ label {
h1 {
font-size: 2rem;
}
+
+ .card-header {
+ padding: 14px;
+ }
+
+ .food-name {
+ font-size: 15px;
+ }
+
+ .calories-display {
+ font-size: 16px;
+ }
+
+ .nutrition-grid {
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px;
+ }
+
+ .details-content {
+ padding: 16px;
+ }
}