+9
-24
@@ -149,6 +149,10 @@
|
|||||||
<div class="search-results" id="searchResults">
|
<div class="search-results" id="searchResults">
|
||||||
<!-- Results will be populated here -->
|
<!-- Results will be populated here -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hidden inputs for state -->
|
||||||
|
<input type="hidden" id="foodIdToUpdate">
|
||||||
|
<input type="hidden" id="foodTimeToUpdate">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -156,6 +160,7 @@
|
|||||||
<input type="number" id="gramAmount" min="1" step="1" value="100">
|
<input type="number" id="gramAmount" min="1" step="1" value="100">
|
||||||
</div>
|
</div>
|
||||||
<button class="add-btn" id="add-food-btn">Add Food</button>
|
<button class="add-btn" id="add-food-btn">Add Food</button>
|
||||||
|
<button class="cancel-btn" id="cancel-food-btn">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="display-none" id="food-preview">
|
<div class="display-none" id="food-preview">
|
||||||
<div class="nutrition-summary">
|
<div class="nutrition-summary">
|
||||||
@@ -183,30 +188,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="food-table-section">
|
<div class="section-title">Food Log</div>
|
||||||
<div class="table-title">Food Log</div>
|
|
||||||
<div class="table-wrapper">
|
<div class="food-cards-container" id="foodCardsContainer">
|
||||||
<table class="food-table">
|
<!-- Food cards will be added here -->
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="hidden-column">ID</th>
|
|
||||||
<th>Time</th>
|
|
||||||
<th>Food</th>
|
|
||||||
<th>Grams</th>
|
|
||||||
<th>Calories</th>
|
|
||||||
<th>Protein (g)</th>
|
|
||||||
<th>Fat (g)</th>
|
|
||||||
<th>Carbs (g)</th>
|
|
||||||
<th>Fiber (g)</th>
|
|
||||||
<th>Action</th>
|
|
||||||
<th class="hidden-column">Alkaline</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="foodTableBody">
|
|
||||||
<!-- Food entries will be added here -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="calendar-section">
|
<div class="calendar-section">
|
||||||
|
|||||||
Generated
+8
-8
@@ -9,12 +9,12 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"appwrite": "^18.2.0",
|
"appwrite": "^19.0.0",
|
||||||
"sweetalert": "^2.1.2"
|
"sweetalert": "^2.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"vite": "^7.1.3"
|
"vite": "^7.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
@@ -747,9 +747,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/appwrite": {
|
"node_modules/appwrite": {
|
||||||
"version": "18.2.0",
|
"version": "19.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/appwrite/-/appwrite-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/appwrite/-/appwrite-19.0.0.tgz",
|
||||||
"integrity": "sha512-g7pQpsxqR7+amEIaQLXMN4XzdQKenTHnGdA4s7UUJdZufhlHdJby8895h8z893+S0XipeHZhi0wpxYA2An95Rg==",
|
"integrity": "sha512-guamzST6Fvn/lvuBMLJGzJGL+N+o313j53qyfKM1ZHfcDd1ADnSONgm9DZihRFYBaWtU7s1bwkphkBJWSUwurQ==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/es6-object-assign": {
|
"node_modules/es6-object-assign": {
|
||||||
@@ -999,9 +999,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.4.tgz",
|
||||||
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
|
"integrity": "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+2
-2
@@ -13,10 +13,10 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"vite": "^7.1.3"
|
"vite": "^7.1.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"appwrite": "^18.2.0",
|
"appwrite": "^19.0.0",
|
||||||
"sweetalert": "^2.1.2"
|
"sweetalert": "^2.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
VITE_APPWRITE_ENDPOINT=here
|
||||||
|
VITE_APPWRITE_DBID=here
|
||||||
|
VITE_APPWRITE_FOODENTRIESID=here
|
||||||
|
VITE_APPWRITE_USERSETTINGSID=here
|
||||||
@@ -98,6 +98,26 @@ export class AppwriteDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upadte food entry
|
||||||
|
static async updateFoodEntry(documentId: string, entryData: Partial<FoodStorage>) {
|
||||||
|
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
|
// Save food entry
|
||||||
static async saveUserSettings(userSettings: UserSettings) {
|
static async saveUserSettings(userSettings: UserSettings) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+42
-12
@@ -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 } },
|
{ 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
|
// 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: '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, de galinha, 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, 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: '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: '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: '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: '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: '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, 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: '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
|
// 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 } },
|
{ 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, 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: '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: '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: '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, 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: '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: '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 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: '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: '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 c/óleo', info: { calories: 448, protein: 9.9, fat: 15.9, carbs: 70.3, fiber: 14.3, 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 s/óleo', info: { calories: 178, protein: 3.3, fat: 7.2, carbs: 25, fiber: 4.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: '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: '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 } },
|
{ 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: '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: '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: '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
|
// 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 } },
|
{ 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: '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: '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: '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
|
// 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 } },
|
{ 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: '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: '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: '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 } },
|
||||||
];
|
];
|
||||||
|
|||||||
+270
-54
@@ -3,6 +3,7 @@ import { getButtonById, getButtonListByClassName, getDivById, getInputById, show
|
|||||||
import { FoodItem, FoodStorage } from './types.js';
|
import { FoodItem, FoodStorage } from './types.js';
|
||||||
import { AppwriteAuth, AppwriteDB } from './appwrite.js';
|
import { AppwriteAuth, AppwriteDB } from './appwrite.js';
|
||||||
import swal from 'sweetalert';
|
import swal from 'sweetalert';
|
||||||
|
import { Models } from 'appwrite';
|
||||||
|
|
||||||
// App state
|
// App state
|
||||||
let selectedDate = new Date();
|
let selectedDate = new Date();
|
||||||
@@ -11,6 +12,9 @@ let searchTimeout: number | null = null;
|
|||||||
let selectedFood: FoodItem | null = null; // review here
|
let selectedFood: FoodItem | null = null; // review here
|
||||||
let currentHighlightIndex: number = -1;
|
let currentHighlightIndex: number = -1;
|
||||||
let currentResults: FoodItem[] = [];
|
let currentResults: FoodItem[] = [];
|
||||||
|
let totalCaloriesToday: number = 0;
|
||||||
|
let totalGramsToday: number = 0;
|
||||||
|
let totalAlkalineToday: number = 0;
|
||||||
|
|
||||||
// Authentication state
|
// Authentication state
|
||||||
let currentUser: any | null = null;
|
let currentUser: any | null = null;
|
||||||
@@ -281,13 +285,31 @@ function hideLoading() {
|
|||||||
loadingOverlay.classList.add('hidden');
|
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
|
// Clear application data
|
||||||
function clearAppData() {
|
function clearAppData() {
|
||||||
getDivById('foodTableBody').innerHTML = '';
|
|
||||||
getDivById('caloriesCounter').textContent = '0';
|
getDivById('caloriesCounter').textContent = '0';
|
||||||
getInputById('foodSearchInput').value = '';
|
getInputById('foodSearchInput').value = '';
|
||||||
getInputById('gramAmount').value = '100';
|
getInputById('gramAmount').value = '100';
|
||||||
|
getDivById('foodCardsContainer').innerHTML = `
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="empty-state-icon">🍽️</div>
|
||||||
|
<div>No food items logged yet.</div>
|
||||||
|
<div style="margin-top: 8px; font-size: 14px; opacity: 0.7;">Add your first item to get started!</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
selectedFood = null;
|
selectedFood = null;
|
||||||
|
totalCaloriesToday = 0;
|
||||||
|
totalGramsToday = 0;
|
||||||
|
totalAlkalineToday = 0;
|
||||||
|
getButtonById('cancel-food-btn').style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFoodData = (grams: number, foodData: FoodItem): FoodItem => {
|
const getFoodData = (grams: number, foodData: FoodItem): FoodItem => {
|
||||||
@@ -309,7 +331,7 @@ const getFoodData = (grams: number, foodData: FoodItem): FoodItem => {
|
|||||||
const getFoodItemByName = (foodName: string): FoodItem => {
|
const getFoodItemByName = (foodName: string): FoodItem => {
|
||||||
const foodDataSearch: FoodItem[] = foodDatabase.filter(f => f.name === foodName);
|
const foodDataSearch: FoodItem[] = foodDatabase.filter(f => f.name === foodName);
|
||||||
if (foodDataSearch.length === 0) {
|
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];
|
return foodDataSearch[0];
|
||||||
}
|
}
|
||||||
@@ -353,7 +375,11 @@ const setupEventListeners = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
getButtonById('add-food-btn').addEventListener('click', () => {
|
getButtonById('add-food-btn').addEventListener('click', () => {
|
||||||
addFood();
|
if (getButtonById('add-food-btn').innerHTML === 'Add Food') {
|
||||||
|
addFood();
|
||||||
|
} else {
|
||||||
|
updateFood();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
getButtonById('next-month-btn').addEventListener('click', () => {
|
getButtonById('next-month-btn').addEventListener('click', () => {
|
||||||
@@ -375,6 +401,10 @@ const setupEventListeners = () => {
|
|||||||
toggleAuthForms();
|
toggleAuthForms();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getButtonById('cancel-food-btn').addEventListener('click', () => {
|
||||||
|
clearEditing();
|
||||||
|
});
|
||||||
|
|
||||||
// Auth forms
|
// Auth forms
|
||||||
document.getElementById('loginForm')?.addEventListener('submit', handleLogin);
|
document.getElementById('loginForm')?.addEventListener('submit', handleLogin);
|
||||||
document.getElementById('registerForm')?.addEventListener('submit', handleRegister);
|
document.getElementById('registerForm')?.addEventListener('submit', handleRegister);
|
||||||
@@ -473,12 +503,13 @@ function displaySearchResults(results: FoodItem[]) {
|
|||||||
searchResults.innerHTML = '<div class="no-results">No foods found. Try a different search term.</div>';
|
searchResults.innerHTML = '<div class="no-results">No foods found. Try a different search term.</div>';
|
||||||
} else {
|
} else {
|
||||||
const icons = {
|
const icons = {
|
||||||
'fats': '🥑',
|
'fats': '🥑 🍳 🍟',
|
||||||
'proteins': '🫘',
|
'proteins': '🫘 🥩 🥚',
|
||||||
'carbs (high)': '🍞',
|
'carbs (high)': '🍞 🥔 🍠',
|
||||||
'leaves': '🥬',
|
'leaves': '🥬 🥗 🌿',
|
||||||
'fruits': '🍊',
|
'fruits': '🍊 🍇 🍎',
|
||||||
'carbs (low)': '🍍'
|
'carbs (low)': '🥦 🍅 🍓',
|
||||||
|
'dairy': '🧀 🧈 🥛'
|
||||||
};
|
};
|
||||||
|
|
||||||
searchResults.innerHTML = results.map((food, index) => `
|
searchResults.innerHTML = results.map((food, index) => `
|
||||||
@@ -531,23 +562,101 @@ function selectFood(food: FoodItem) {
|
|||||||
previewCalories(food);
|
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
|
// Hide search results
|
||||||
function hideSearchResults() {
|
function hideSearchResults() {
|
||||||
searchResults.classList.remove('show');
|
searchResults.classList.remove('show');
|
||||||
currentHighlightIndex = -1;
|
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 addDeleteEvents = () => {
|
||||||
const elements = getButtonListByClassName('delete-food-entry');
|
// Setup toggle show cards events
|
||||||
Array.from(elements).forEach((el: HTMLButtonElement) => {
|
const cards = getButtonListByClassName('card-header-toggle');
|
||||||
el.addEventListener('click', (e) => {
|
Array.from(cards).forEach((card: HTMLElement) => {
|
||||||
const target = e.target as HTMLElement;
|
card.removeEventListener('click', toggleCardHandler);
|
||||||
const row = target.closest('tr');
|
card.addEventListener('click', toggleCardHandler);
|
||||||
const entryId = row?.querySelector('.hidden-column')?.textContent;
|
});
|
||||||
if (entryId && row) {
|
|
||||||
handleDeleteFood(entryId, row);
|
// 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
|
// Add food to the log
|
||||||
const addFood = async () => {
|
const addFood = async () => {
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
@@ -572,7 +731,7 @@ const addFood = async () => {
|
|||||||
const gramAmount = getInputById('gramAmount');
|
const gramAmount = getInputById('gramAmount');
|
||||||
|
|
||||||
if (!selectedFood || !gramAmount.value) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,7 +770,7 @@ const addFood = async () => {
|
|||||||
addFoodToTable(entry, savedEntry.$id);
|
addFoodToTable(entry, savedEntry.$id);
|
||||||
|
|
||||||
// Update totals
|
// Update totals
|
||||||
updateTotalCalories();
|
updateTotalCalories([savedEntry]);
|
||||||
|
|
||||||
// Update total macros
|
// Update total macros
|
||||||
const proteinDiv: HTMLElement = getDivById('proteinValue');
|
const proteinDiv: HTMLElement = getDivById('proteinValue');
|
||||||
@@ -655,7 +814,7 @@ async function loadFoodEntries(date: Date) {
|
|||||||
const entries = await AppwriteDB.getFoodEntries(date);
|
const entries = await AppwriteDB.getFoodEntries(date);
|
||||||
|
|
||||||
// Clear current table
|
// Clear current table
|
||||||
getDivById('foodTableBody').innerHTML = '';
|
getDivById('foodCardsContainer').innerHTML = '';
|
||||||
|
|
||||||
// Add each entry to table
|
// Add each entry to table
|
||||||
entries.forEach(entry => {
|
entries.forEach(entry => {
|
||||||
@@ -676,11 +835,21 @@ async function loadFoodEntries(date: Date) {
|
|||||||
addFoodToTable(foodData, entry.$id);
|
addFoodToTable(foodData, entry.$id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (entries.length === 0) {
|
||||||
|
getDivById('foodCardsContainer').innerHTML = `
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="empty-state-icon">🍽️</div>
|
||||||
|
<div>No food items logged yet.</div>
|
||||||
|
<div style="margin-top: 8px; font-size: 14px; opacity: 0.7;">Add your first item to get started!</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
// Setup Delete events
|
// Setup Delete events
|
||||||
addDeleteEvents();
|
addDeleteEvents();
|
||||||
|
|
||||||
// Update total calories
|
// Update total calories
|
||||||
updateTotalCalories();
|
updateTotalCalories(entries);
|
||||||
|
|
||||||
// Update counters
|
// Update counters
|
||||||
let totalCalories = 0;
|
let totalCalories = 0;
|
||||||
@@ -751,7 +920,7 @@ async function loadFoodEntries(date: Date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle food deletion
|
// Handle food deletion
|
||||||
async function handleDeleteFood(documentId: string, row: HTMLTableRowElement) {
|
async function handleDeleteFood(documentId: string) {
|
||||||
if (!documentId) return;
|
if (!documentId) return;
|
||||||
|
|
||||||
const willDelete = await swal({
|
const willDelete = await swal({
|
||||||
@@ -772,7 +941,6 @@ async function handleDeleteFood(documentId: string, row: HTMLTableRowElement) {
|
|||||||
await AppwriteDB.deleteFoodEntry(documentId);
|
await AppwriteDB.deleteFoodEntry(documentId);
|
||||||
|
|
||||||
selectDate(selectedDate);
|
selectDate(selectedDate);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Delete food error:', error);
|
console.error('Delete food error:', error);
|
||||||
swal('Oh no!', 'Failed to delete food entry: ' + error.message, '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)
|
// Add food to table (existing function - update to use document ID)
|
||||||
function addFoodToTable(foodData: FoodStorage, documentId: string) {
|
function addFoodToTable(foodData: FoodStorage, documentId: string) {
|
||||||
const tableBody = document.getElementById('foodTableBody');
|
// New Food card
|
||||||
const row = document.createElement('tr');
|
const container = getDivById('foodCardsContainer');
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'food-card';
|
||||||
|
card.setAttribute('data-id', documentId);
|
||||||
|
|
||||||
row.innerHTML = `
|
if (container.innerHTML.includes('empty-state')) {
|
||||||
<td class="hidden-column">${documentId}</td>
|
container.innerHTML = '';
|
||||||
<td>${foodData.time || new Date().toLocaleTimeString()}</td>
|
}
|
||||||
<td>${foodData.name}</td>
|
|
||||||
<td>${foodData.grams}</td>
|
card.innerHTML = `
|
||||||
<td>${foodData.calories}</td>
|
<div class="card-header card-header-toggle">
|
||||||
<td>${foodData.protein}</td>
|
<div class="card-main-info">
|
||||||
<td>${foodData.fat}</td>
|
<div class="food-name-time">
|
||||||
<td>${foodData.carbs}</td>
|
<div class="food-name">${foodData.name}</div>
|
||||||
<td>${foodData.fiber}</td>
|
<div class="food-time">${foodData.time || new Date().toLocaleTimeString()} • ${foodData.grams}g</div>
|
||||||
<td><button class="delete-btn delete-food-entry">Delete</button></td>
|
</div>
|
||||||
<td class="hidden-column">${foodData.alkaline}</td>
|
<div class="calories-display">${foodData.calories} cal</div>
|
||||||
|
</div>
|
||||||
|
<div class="expand-icon">▼</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-details">
|
||||||
|
<div class="details-content">
|
||||||
|
<div class="nutrition-grid">
|
||||||
|
<div class="nutrition-item">
|
||||||
|
<div class="nutrition-label">Protein</div>
|
||||||
|
<div class="nutrition-value">${foodData.protein}g</div>
|
||||||
|
</div>
|
||||||
|
<div class="nutrition-item">
|
||||||
|
<div class="nutrition-label">Fat</div>
|
||||||
|
<div class="nutrition-value">${foodData.fat}g</div>
|
||||||
|
</div>
|
||||||
|
<div class="nutrition-item">
|
||||||
|
<div class="nutrition-label">Carbs</div>
|
||||||
|
<div class="nutrition-value">${foodData.carbs}g</div>
|
||||||
|
</div>
|
||||||
|
<div class="nutrition-item">
|
||||||
|
<div class="nutrition-label">Fiber</div>
|
||||||
|
<div class="nutrition-value">${foodData.fiber}g</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<button class="btn btn-edit" data-food-id="${documentId}">Edit</button>
|
||||||
|
<button class="btn btn-delete" data-food-id="${documentId}">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
tableBody?.appendChild(row);
|
container?.appendChild(card);
|
||||||
|
|
||||||
// Setup Delete events
|
// Setup Delete events
|
||||||
addDeleteEvents();
|
addDeleteEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total calories
|
// Update total calories
|
||||||
function updateTotalCalories() {
|
function updateTotalCalories(entries: Models.DefaultDocument[]) {
|
||||||
const caloriesCells = document.querySelectorAll('#foodTableBody td:nth-child(5)'); // 5th column is calories
|
let total = totalCaloriesToday;
|
||||||
let total = 0;
|
|
||||||
|
|
||||||
caloriesCells.forEach((cell: Element) => {
|
entries.forEach((entry: Models.DefaultDocument) => {
|
||||||
total += parseInt(cell.innerHTML) || 0;
|
total += entry.calories || 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
getDivById('caloriesCounter').textContent = total.toString();
|
getDivById('caloriesCounter').textContent = total.toString();
|
||||||
|
totalCaloriesToday = total;
|
||||||
|
|
||||||
|
// FIX ME: alkaline count
|
||||||
// Update alkaline level
|
// Update alkaline level
|
||||||
const gramsCell = document.querySelectorAll('#foodTableBody td:nth-child(4)'); // 4th is grams
|
let totalGrams = totalGramsToday;
|
||||||
let totalGrams = 0;
|
|
||||||
let indexMap: {index: number, grams: number}[] = [];
|
let indexMap: {index: number, grams: number}[] = [];
|
||||||
gramsCell.forEach((cell: Element, key: number) => {
|
entries.forEach((entry: Models.DefaultDocument, key: number) => {
|
||||||
totalGrams += parseInt(cell.innerHTML) || 0;
|
totalGrams += entry.grams || 0;
|
||||||
indexMap.push({
|
indexMap.push({
|
||||||
index: key,
|
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 = totalAlkalineToday;
|
||||||
let totalAlkaline = 0;
|
entries.forEach((entry: Models.DefaultDocument, key: number) => {
|
||||||
alkalineCell.forEach((cell: Element, key: number) => {
|
if (entry.alkaline) {
|
||||||
if (cell.innerHTML === 'true') {
|
|
||||||
for (let ob of indexMap) {
|
for (let ob of indexMap) {
|
||||||
if (ob.index === key) {
|
if (ob.index === key) {
|
||||||
totalAlkaline += ob.grams;
|
totalAlkaline += ob.grams;
|
||||||
@@ -858,6 +1058,9 @@ const nextMonth = () => {
|
|||||||
|
|
||||||
const selectDate = async (date: Date) => {
|
const selectDate = async (date: Date) => {
|
||||||
selectedDate = date;
|
selectedDate = date;
|
||||||
|
totalCaloriesToday = 0;
|
||||||
|
totalGramsToday = 0;
|
||||||
|
totalAlkalineToday = 0;
|
||||||
updateCurrentDate();
|
updateCurrentDate();
|
||||||
await loadFoodEntries(date);
|
await loadFoodEntries(date);
|
||||||
renderCalendar();
|
renderCalendar();
|
||||||
@@ -970,4 +1173,17 @@ function createDayElement(day: number, isOtherMonth: boolean, year: number, mont
|
|||||||
return dayElement;
|
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;
|
(window as any).selectFood = selectFood;
|
||||||
|
|||||||
+1
-1
@@ -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 = {
|
export type FoodItem = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -167,11 +167,204 @@ h1 {
|
|||||||
background-color: #f8f9ff;
|
background-color: #f8f9ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.food-log-section {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.food-name {
|
.section-title {
|
||||||
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
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 {
|
.food-details {
|
||||||
@@ -209,7 +402,7 @@ h1 {
|
|||||||
|
|
||||||
.add-food-form {
|
.add-food-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2fr 1fr auto;
|
grid-template-columns: 2fr 1fr 1fr auto;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
}
|
}
|
||||||
@@ -274,6 +467,24 @@ label {
|
|||||||
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3);
|
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 {
|
.food-table-section {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -341,6 +552,7 @@ label {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-header {
|
.calendar-header {
|
||||||
@@ -522,4 +734,25 @@ label {
|
|||||||
h1 {
|
h1 {
|
||||||
font-size: 2rem;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user