diff --git a/auth.css b/auth.css index 4513f3a..04db75a 100644 --- a/auth.css +++ b/auth.css @@ -191,7 +191,8 @@ } /* Update main content spacing */ -#app-content { +#app-content, +#settings-content { padding-top: 20px; } @@ -214,7 +215,8 @@ justify-content: center; } - .auth-form { + .auth-form, + .settings-form { min-width: 280px; padding: 20px; } diff --git a/index.html b/index.html index fbdf1da..d8f18b2 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,7 @@ Daily Calorie Tracker + @@ -61,6 +62,7 @@ @@ -88,18 +90,22 @@
0
+
Protein (g)
0
+
Fat (g)
0
+
Carbs (g)
0
+
Fiber (g)
@@ -183,6 +189,31 @@ + + + diff --git a/settings.css b/settings.css new file mode 100644 index 0000000..e1e6e81 --- /dev/null +++ b/settings.css @@ -0,0 +1,51 @@ +.settings-form { + backdrop-filter: blur(10px); + border-radius: 10px; + padding: 25px; + min-width: 300px; + /* background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); */ +} + +.settings-form .form-group { + margin-bottom: 15px; +} + +.settings-form input { + width: 100%; + padding: 12px; + border-radius: 6px; + font-size: 1rem; + backdrop-filter: blur(10px); + box-sizing: border-box; + border: 1px solid #ccc; +} + +.settings-form input::placeholder { + color: rgba(0, 0, 0, 0.7); +} + +.settings-form input:focus { + outline: none; + border-color: #667eea; +} + +.settings-btn { + width: 100%; + padding: 12px; + color: white; + background: #667eea; + border: 1px solid #667eea; + border-radius: 6px; + cursor: pointer; + font-size: 1rem; + font-weight: 600; + transition: all 0.3s ease; + backdrop-filter: blur(10px); +} + +.settings-btn:hover { + background: #8d9de7; + border: 1px solid #8d9de7; + transform: translateY(-1px); +} diff --git a/src/appwrite.ts b/src/appwrite.ts index d636882..75361ff 100644 --- a/src/appwrite.ts +++ b/src/appwrite.ts @@ -1,5 +1,5 @@ import { Client, Account, Databases, Query } from 'appwrite'; -import { FoodStorage } from './types'; +import { FoodStorage, UserSettings } from './types'; // Initialize Appwrite client const client = new Client(); @@ -15,6 +15,7 @@ export const databases = new Databases(client); // Configuration constants export const DATABASE_ID = '684b38db000fac8c781c'; // Replace with your database ID export const FOOD_ENTRIES_COLLECTION_ID = '684b38e300336fc605dc'; // Replace with your collection ID +export const USER_SETTINGS_COLLECTION_ID = '684c75140026689fe603'; // Replace with your collection ID // Auth helper functions export class AppwriteAuth { @@ -98,6 +99,26 @@ export class AppwriteDB { } } + // Save food entry + static async saveUserSettings(userSettings: UserSettings) { + try { + const response = await databases.createDocument( + DATABASE_ID, + USER_SETTINGS_COLLECTION_ID, + 'unique()', + { + ...userSettings, + userId: (await account.get()).$id, + } + ); + console.debug('User settings saved:', response); + return response; + } catch (error) { + console.error('Save user settings error:', error); + throw error; + } + } + // Get user's food entries for a specific date static async getFoodEntries(date: Date) { try { @@ -119,6 +140,40 @@ export class AppwriteDB { } } + // get user's settings + static async getUserSettings() { + try { + const user = await account.get(); + + const response = await databases.listDocuments( + DATABASE_ID, + USER_SETTINGS_COLLECTION_ID, + [ + Query.equal('userId', user.$id), + ] + ); + console.debug('User settings retrieved:', response); + return response.documents; + } catch (error) { + console.error('Get user settings error:', error); + throw error; + } + } + + static async deleteUserSettings(documentId: string) { + try { + await databases.deleteDocument( + DATABASE_ID, + USER_SETTINGS_COLLECTION_ID, + documentId + ); + console.debug('User settings deleted:', documentId); + } catch (error) { + console.error('Delete user settings error:', error); + throw error; + } + } + // Delete food entry static async deleteFoodEntry(documentId: string) { try { diff --git a/src/foodDatabase.ts b/src/foodDatabase.ts index 0037812..2455703 100644 --- a/src/foodDatabase.ts +++ b/src/foodDatabase.ts @@ -12,19 +12,19 @@ export const foodDatabase: FoodItem[] = [ { name: 'Frutas - Melão', info: { calories: 29, protein: 0.7, fat: 0, carbs: 7.5, fiber: 0.3 } }, { name: 'Frutas - Uva', info: { calories: 53, protein: 0.7, fat: 0.2, carbs: 13.6, fiber: 0.9 } }, - { name: 'Vegetais - Agrião', info: { calories: 17, protein: 2.7, fat: 0.2, carbs: 2.3, fiber: 2.1 } }, - { name: 'Vegetais - Brócolis (cozido)', info: { calories: 25, protein: 2.1, fat: 0.5, carbs: 4.4, fiber: 3.4 } }, - { name: 'Vegetais - Cenoura (cozida)', info: { calories: 30, protein: 0.8, fat: 0.2, carbs: 6.7, fiber: 2.6 } }, - { name: 'Vegetais - Couve flor (cozida)', info: { calories: 19, protein: 1.2, fat: 0.3, carbs: 3.9, fiber: 2.1 } }, - { name: 'Vegetais - Ervilha (cozida)', info: { calories: 88, protein: 7.5, fat: 0.5, carbs: 14.2, fiber: 9.7 } }, - { name: 'Vegetais - Espinafre', info: { calories: 16, protein: 2, fat: 0.2, carbs: 2.6, fiber: 2.1 } }, - { name: 'Vegetais - Feijão preto (cozido)', info: { calories: 77, protein: 4.5, fat: 0.5, carbs: 14, fiber: 8.4 } }, - { name: 'Vegetais - Lentilha (cozida)', info: { calories: 93, protein: 6.3, fat: 0.5, carbs: 16.3, fiber: 7.9 } }, - { name: 'Vegetais - Mandioca (cozida)', info: { calories: 125, protein: 0.6, fat: 0.3, carbs: 30.1, fiber: 1.6 } }, - { name: 'Vegetais - Pepino', info: { calories: 10, protein: 0.9, fat: 0, carbs: 2, fiber: 1.1 } }, - { name: 'Vegetais - Rúcula', info: { calories: 13, protein: 1.8, fat: 0.1, carbs: 2.2, fiber: 1.7 } }, - { name: 'Vegetais - Spirulina', info: { calories: 290, protein: 57.5, fat: 23.9, carbs: 7.7, fiber: 0 } }, - { name: 'Vegetais - Tomate', info: { calories: 15, protein: 1.1, fat: 0.2, carbs: 3.1, fiber: 1.2 } }, + { name: 'Verduras - Agrião', info: { calories: 17, protein: 2.7, fat: 0.2, carbs: 2.3, fiber: 2.1 } }, + { name: 'Verduras - Espinafre', info: { calories: 16, protein: 2, fat: 0.2, carbs: 2.6, fiber: 2.1 } }, + { name: 'Verduras - Rúcula', info: { calories: 13, protein: 1.8, fat: 0.1, carbs: 2.2, fiber: 1.7 } }, + + { name: 'Legumes - Abóbora Cabotian (crua)', info: { calories: 39, protein: 1.7, fat: 0.5, carbs: 8.4, fiber: 2.2 } }, + { name: 'Legumes - Abóbora Cabotian (cozida)', info: { calories: 48, protein: 1.4, fat: 0.7, carbs: 10.8, fiber: 2.5 } }, + { name: 'Legumes - Brócolis (cozido)', info: { calories: 25, protein: 2.1, fat: 0.5, carbs: 4.4, fiber: 3.4 } }, + { name: 'Legumes - Cenoura (cozida)', info: { calories: 30, protein: 0.8, fat: 0.2, carbs: 6.7, fiber: 2.6 } }, + { name: 'Legumes - Couve flor (cozida)', info: { calories: 19, protein: 1.2, fat: 0.3, carbs: 3.9, fiber: 2.1 } }, + { name: 'Legumes - Pepino', info: { calories: 10, protein: 0.9, fat: 0, carbs: 2, fiber: 1.1 } }, + { name: 'Legumes - Tomate', info: { calories: 15, protein: 1.1, fat: 0.2, carbs: 3.1, fiber: 1.2 } }, + + { name: 'Algas - Spirulina', info: { calories: 290, protein: 57.5, fat: 23.9, carbs: 7.7, fiber: 0 } }, { name: 'Carboidratos - Batata doce (assada)', info: { calories: 90, protein: 2.0, fat: 0.1, carbs: 20.7, fiber: 3.3 } }, { name: 'Carboidratos - Batata doce (cozida)', info: { calories: 77, protein: 0.6, fat: 0.1, carbs: 18.4, fiber: 2.2 } }, @@ -32,6 +32,7 @@ export const foodDatabase: FoodItem[] = [ { name: 'Carboidratos - Batata inglesa (assada)', info: { calories: 94, protein: 2.1, fat: 0.1, carbs: 21.8, fiber: 2.1 } }, { name: 'Carboidratos - Batata inglesa (cozida)', info: { calories: 52, protein: 1.2, fat: 0, carbs: 11.9, fiber: 1.3 } }, { name: 'Carboidratos - Batata inglesa (crua)', info: { calories: 64, protein: 1.8, fat: 0, carbs: 14.7, fiber: 1.2 } }, + { name: 'Carboidratos - Mandioca (cozida)', info: { calories: 125, protein: 0.6, fat: 0.3, carbs: 30.1, fiber: 1.6 } }, { name: 'Carboidratos - Pão Francês', info: { calories: 300, protein: 8, fat: 3.1, carbs: 58.6, fiber: 2.3 } }, { name: 'Carboidrados - Polenta (cozida)', info: { calories: 192, protein: 4.8, fat: 1.2, carbs: 40, fiber: 3.8 } }, @@ -44,9 +45,12 @@ export const foodDatabase: FoodItem[] = [ { name: 'Proteínas - Soja em Pó Growth', info: { calories: 400, protein: 86.7, fat: 5, carbs: 3.3, fiber: 0 } }, { name: 'Proteínas - Sobrecoxa s/pele (cozida)', info: { calories: 245, protein: 24.9, fat: 15.4, carbs: 0, fiber: 0 } }, { name: 'Proteínas - Tofu', info: { calories: 65, protein: 6.5, fat: 4, carbs: 2, fiber: 0.75 } }, - + { name: 'Grãos - Arroz integral (cozido)', info: { calories: 130, protein: 2.7, fat: 0.3, carbs: 28, fiber: 0.4 } }, { name: 'Grãos - Aveia', info: { calories: 394, protein: 13.9, fat: 8.5, carbs: 66.6, fiber: 9.1 } }, + { name: 'Grãos - Ervilha (cozida)', info: { calories: 88, protein: 7.5, fat: 0.5, carbs: 14.2, fiber: 9.7 } }, + { name: 'Grãos - Feijão preto (cozido)', info: { calories: 77, protein: 4.5, fat: 0.5, carbs: 14, fiber: 8.4 } }, + { name: 'Grãos - Lentilha (cozida)', info: { calories: 93, protein: 6.3, fat: 0.5, carbs: 16.3, fiber: 7.9 } }, { name: 'Grãos - Grão de Bico (cozido)', info: { calories: 180, protein: 9.5, fat: 3, carbs: 30, fiber: 8.6 } }, { name: 'Grãos - Pão integral de forma', info: { calories: 253, protein: 9.4, fat: 3.7, carbs: 49.9, fiber: 6.9 } }, { name: 'Grãos - Pipoca c/óleo', info: { calories: 448, protein: 9.9, fat: 15.9, carbs: 70.3, fiber: 14.3 } }, @@ -57,6 +61,7 @@ export const foodDatabase: FoodItem[] = [ { name: 'Castanhas - Amendoin natural', info: { calories: 611, protein: 26, fat: 49, carbs: 16, fiber: 8.5 } }, { name: 'Castanhas - Castanha de Caju', info: { calories: 570, protein: 18.5, fat: 46.3, carbs: 29.1, fiber: 3.7 } }, { name: 'Castanhas - Castanha do Pará', info: { calories: 643, protein: 14.5, fat: 63.5, carbs: 15.1, fiber: 7.9 } }, + { name: 'Sementes - Semente de Chia', info: { calories: 490, protein: 15.6, fat: 30.7, carbs: 43.8, fiber: 37.7 } }, { name: 'Sementes - Semente de Linhaça', info: { calories: 495, protein: 14.1, fat: 32.3, carbs: 43.3, fiber: 33.5 } }, { name: 'Sementes - Semente de Gergelim', info: { calories: 584, protein: 21.2, fat: 50.4, carbs: 21.6, fiber: 11.9 } }, @@ -67,5 +72,7 @@ export const foodDatabase: FoodItem[] = [ { name: 'Lanches - Melado', info: { calories: 297, protein: 0, fat: 0, carbs: 76.6, fiber: 0 } }, { name: 'Lanches - Pasta de Amendoim Growth', info: { calories: 544, protein: 27, fat: 33, carbs: 20, fiber: 8 } }, { name: 'Lanches - Goma de Tapioca', info: { calories: 226, protein: 0, fat: 0, carbs: 57, fiber: 0 } }, - { name: 'Lanches - 70% Nibs Garoto', info: { calories: 544, protein: 8.4, fat: 40, carbs: 34, fiber: 0 } } + { name: 'Lanches - 70% Nibs Garoto', info: { calories: 544, protein: 8.4, fat: 40, carbs: 34, fiber: 0 } }, + + { name: 'Óleos - Azeite de Oliva EV', info: { calories: 844, protein: 0, fat: 100, carbs: 0, fiber: 0 } } ]; diff --git a/src/index.ts b/src/index.ts index 7690f7a..766dfee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ let currentUser: any | null = null; const authSection = document.getElementById('auth-section'); const userInfo = document.getElementById('user-info'); const appContent = document.getElementById('app-content'); +const settingsContent = document.getElementById('settings-content'); const loadingOverlay = getDivById('loading-overlay'); const loginForm = document.getElementById('login-form'); const registerForm = document.getElementById('register-form'); @@ -114,6 +115,77 @@ async function handleLogin(e: SubmitEvent) { } } +async function processResponseItems( + response: T[], + processItem: (item: T) => Promise +): Promise { + + // Create promises for each item (but don't execute yet) + const promises = response.map(item => processItem(item)); + + // Execute all promises concurrently and wait for completion + const results = await Promise.all(promises); + + return results; +} + +// Example usage with API calls +interface ResponseItem { + id: string; + data: string; +} +async function fetchItemDetails(item: ResponseItem): Promise { + const response = await fetch(`/api/items/${item.id}`); + return response.json(); +} + +// Main function that processes the response +async function handleResponse(response: ResponseItem[]): Promise { + try { + // This creates and executes all promises concurrently + const results = await processResponseItems(response, fetchItemDetails); + + console.log('All requests completed:', results); + } catch (error) { + console.error('One or more requests failed:', error); + } +} + +async function handleSaveSettings(e: SubmitEvent) { + e.preventDefault(); + showLoading(); + + try { + const proteinGoal = getInputById('proteinGoal').value; + const fatGoal = getInputById('fatGoal').value; + const carboGoal = getInputById('carboGoal').value; + const fiberGoal = getInputById('fiberGoal').value; + + // Delete existing settings - keep only the new one + const documents = await AppwriteDB.getUserSettings(); + const promises = []; + for (let i=0; i 0; + if (hasGoals) { + const index = settings.length - 1; + getInputById('proteinGoal').value = settings[index].proteinGoal; + getInputById('fatGoal').value = settings[index].fatGoal; + getInputById('carboGoal').value = settings[index].carboGoal; + getInputById('fiberGoal').value = settings[index].fiberGoal; + } else { + getInputById('proteinGoal').value = ''; + getInputById('fatGoal').value = ''; + getInputById('carboGoal').value = ''; + getInputById('fiberGoal').value = ''; + } + + getButtonById('settingsBtn').textContent = 'Back to App'; + + appContent?.classList.add('hidden'); + settingsContent?.classList.remove('hidden'); + hideLoading(); + } catch (error) { + hideLoading(); + console.error('Fetching settings failed:', error); + swal('Oh no!', 'Fetching settings failed: ' + error.message, 'error'); + } + } + else { + appContent?.classList.remove('hidden'); + settingsContent?.classList.add('hidden'); + getButtonById('settingsBtn').textContent = 'Settings'; } } @@ -252,6 +365,8 @@ const setupEventListeners = () => { document.getElementById('loginForm')?.addEventListener('submit', handleLogin); document.getElementById('registerForm')?.addEventListener('submit', handleRegister); document.getElementById('logoutBtn')?.addEventListener('click', handleLogout); + document.getElementById('settingsBtn')?.addEventListener('click', handleSettings); + document.getElementById('settingsForm')?.addEventListener('submit', handleSaveSettings); } const addDeleteEvents = () => { @@ -341,6 +456,22 @@ const addFood = async () => { // Update totals updateTotalCalories(); + // Update total macros + const proteinDiv: HTMLElement = getDivById('proteinValue'); + const fatDiv: HTMLElement = getDivById('fatValue'); + const carboDiv: HTMLElement = getDivById('carboValue'); + const fiberDiv: HTMLElement = getDivById('fiberValue'); + + const totalProtein: number = parseInt(proteinDiv.textContent ?? '0') + proportion.info.protein; + const totalFat: number = parseInt(fatDiv.textContent ?? '0') + proportion.info.fat; + const totalCarbs: number = parseInt(carboDiv.textContent ?? '0') + proportion.info.carbs; + const totalFiber: number = parseInt(fiberDiv.textContent ?? '0') + proportion.info.fiber; + + proteinDiv.textContent = (Math.round(totalProtein * 10) / 10).toString(); + fatDiv.textContent = (Math.round(totalFat * 10) / 10).toString(); + carboDiv.textContent = (Math.round(totalCarbs * 10) / 10).toString(); + fiberDiv.textContent = (Math.round(totalFiber * 10) / 10).toString(); + // Reset form foodSelect.value = ''; gramAmount.value = '100'; @@ -407,7 +538,42 @@ async function loadFoodEntries(date: Date) { getDivById('proteinValue').textContent = (Math.round(totalProtein * 10) / 10).toString(); getDivById('fatValue').textContent = (Math.round(totalFat * 10) / 10).toString(); getDivById('carboValue').textContent = (Math.round(totalCarbs * 10) / 10).toString(); - getDivById('fiberValue').textContent = (Math.round(totalFiber * 10) / 10).toString(); + getDivById('fiberValue').textContent = (Math.round(totalFiber * 10) / 10).toString(); + + // Update goals + const settings = await AppwriteDB.getUserSettings(); + const hasGoals = Array.isArray(settings) && settings.length > 0; + if (hasGoals) { + const index = settings.length - 1; + getDivById('proteinGoalText').classList.add('hidden'); + if (parseInt(settings[index].proteinGoal) > 0) { + getDivById('proteinGoalText').textContent = `of ${settings[index].proteinGoal}`; + getDivById('proteinGoalText').classList.remove('hidden'); + } + + getDivById('fatGoalText').classList.add('hidden'); + if (parseInt(settings[index].fatGoal) > 0) { + getDivById('fatGoalText').textContent = `of ${settings[index].fatGoal}`; + getDivById('fatGoalText').classList.remove('hidden'); + } + + getDivById('carboGoalText').classList.add('hidden'); + if (parseInt(settings[index].carboGoal) > 0) { + getDivById('carboGoalText').textContent = `of ${settings[index].carboGoal}`; + getDivById('carboGoalText').classList.remove('hidden'); + } + + getDivById('fiberGoalText').classList.add('hidden'); + if (parseInt(settings[index].fiberGoal) > 0) { + getDivById('fiberGoalText').textContent = `of ${settings[index].fiberGoal}`; + getDivById('fiberGoalText').classList.remove('hidden'); + } + } else { + getDivById('proteinGoalText').classList.add('hidden'); + getDivById('fatGoalText').classList.add('hidden'); + getDivById('carboGoalText').classList.add('hidden'); + getDivById('fiberGoalText').classList.add('hidden'); + } } catch (error) { console.error('Load food entries error:', error); swal('Oh no!', 'Failed to load food entries: ' + error.message, 'error'); @@ -437,11 +603,7 @@ async function handleDeleteFood(documentId: string, row: HTMLTableRowElement) { // Delete from Appwrite await AppwriteDB.deleteFoodEntry(documentId); - // Remove from table - row.remove(); - - // Update totals - updateTotalCalories(); + selectDate(selectedDate); } catch (error) { console.error('Delete food error:', error); diff --git a/src/types.ts b/src/types.ts index 56cbdd9..780b63c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,3 +15,11 @@ export type FoodStorage = { time: string; date: string; }; + +export type UserSettings = { + id?: string; + proteinGoal: number; + fatGoal: number; + carboGoal: number; + fiberGoal: number; +};