feat: organize functions and add type to foods

This commit is contained in:
2025-09-07 16:33:56 -03:00
parent 3b09e33b2d
commit f8f2c98d49
8 changed files with 224 additions and 244 deletions
+6 -6
View File
@@ -221,27 +221,27 @@
<div class="container"> <div class="container">
<h1>⚙️ Settings</h1> <h1>⚙️ Settings</h1>
<div class="date-display">Define your macros goal</div> <div class="date-display">Define your daily macros goal</div>
<form id="settingsForm" class="settings-form"> <form id="settingsForm" class="settings-form">
<div class="form-group"> <div class="form-group">
<label for="caloriesGoal">Calories goal</label> <label for="caloriesGoal">Daily calorie goal</label>
<input type="number" id="caloriesGoal" placeholder="Calories" required> <input type="number" id="caloriesGoal" placeholder="Calories" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="proteinGoal">Protein goal</label> <label for="proteinGoal">Daily protein goal</label>
<input type="number" id="proteinGoal" placeholder="Proteins in grams" required> <input type="number" id="proteinGoal" placeholder="Proteins in grams" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="proteinGoal">Fat goal</label> <label for="fatGoal">Daily fat goal</label>
<input type="number" id="fatGoal" placeholder="Fat in grams" required> <input type="number" id="fatGoal" placeholder="Fat in grams" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="proteinGoal">Carbs goal</label> <label for="carboGoal">Daily carbs goal</label>
<input type="number" id="carboGoal" placeholder="Carbo in grams" required> <input type="number" id="carboGoal" placeholder="Carbo in grams" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="proteinGoal">Fiber goal</label> <label for="fiberGoal">Daily fiber goal</label>
<input type="number" id="fiberGoal" placeholder="Fiber in grams" required> <input type="number" id="fiberGoal" placeholder="Fiber in grams" required>
</div> </div>
<button type="submit" class="settings-btn">Save</button> <button type="submit" class="settings-btn">Save</button>
View File
+110
View File
@@ -0,0 +1,110 @@
import { getDivById } from "./DomUtils";
import { appState } from "./state";
export const SECOND = 1000;
export const QUICK_DELAY = 0.3 * SECOND;
export function showLoading() {
getDivById('loading-overlay').classList.remove('hidden');
}
export function hideLoading() {
getDivById('loading-overlay').classList.add('hidden');
}
export function scrollToCalendarView() {
const calendarSection = document.querySelector('.calendar-section') as HTMLElement;
if (calendarSection) {
calendarSection.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
export function handleMobileCalendarClick() {
closeMobileMenu();
scrollToCalendarView();
}
export function toggleMobileMenu() {
const mobileMenu = document.getElementById('mobileMenu');
if (mobileMenu) {
mobileMenu.classList.toggle('hidden');
}
}
export function closeMobileMenu() {
const mobileMenu = document.getElementById('mobileMenu');
if (mobileMenu) {
mobileMenu.classList.add('hidden');
}
}
export function getCleanName(foodName: string): string {
const newName = foodName
.replace(/á/g, 'a')
.replace(/ã/g, 'a')
.replace(/â/g, 'a')
.replace(/ê/g, 'e')
.replace(/é/g, 'e')
.replace(/ó/g, 'o')
.replace(/ú/g, 'u')
.replace(/ç/g, 'c');
return newName.toLowerCase();
}
export function navigateResultsKeyboard(direction: number) {
const items = getDivById('searchResults').querySelectorAll('.search-result-item');
if (items.length === 0) return;
// Remove current highlight
if (appState.currentHighlightIndex >= 0) {
items[appState.currentHighlightIndex].classList.remove('highlighted');
}
// Calculate new index
appState.currentHighlightIndex += direction;
if (appState.currentHighlightIndex < 0) appState.currentHighlightIndex = items.length - 1;
if (appState.currentHighlightIndex >= items.length) appState.currentHighlightIndex = 0;
// Add new highlight
items[appState.currentHighlightIndex].classList.add('highlighted');
items[appState.currentHighlightIndex].scrollIntoView({ block: 'nearest' });
}
export function hideSearchResults() {
getDivById('searchResults').classList.remove('show');
appState.currentHighlightIndex = -1;
}
export 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');
}
}
export function delay(seconds: number) {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
export function getIcon(category: string) {
const icons = {
'fats': '🥑 🍳 🍟',
'proteins': '🫘 🥩 🥚',
'carbs (high)': '🍞 🥔 🍠',
'leaves': '🥬 🥗 🌿',
'fruits': '🍊 🍇 🍎',
'carbs (low)': '🥦 🍅 🍓',
'dairy': '🧀 🧈 🥛'
};
return icons[category];
}
+18
View File
@@ -0,0 +1,18 @@
import { getDivById } from "./DomUtils";
export function showAuthForms() {
getDivById('auth-section').classList.remove('hidden');
getDivById('user-info').classList.add('hidden');
getDivById('app-content').classList.add('hidden');
}
export function toggleAuthForms() {
document.getElementById('login-form')?.classList.toggle('hidden');
document.getElementById('register-form')?.classList.toggle('hidden');
const logForm = document.getElementById('loginForm') as HTMLFormElement;
logForm.reset();
const resetForm = document.getElementById('registerForm') as HTMLFormElement;
resetForm.reset();
}
+2
View File
@@ -72,6 +72,8 @@ export const foodDatabase: FoodItem[] = [
{ 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: '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: '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 } }, { name: 'Paçoca, amendoim', info: { calories: 487, protein: 16, fat: 26.1, carbs: 52.4, fiber: 7.3, category: 'carbs (high)', alkaline: false } },
{ name: 'Tortelete de mousse chocolate zero Quiero Café', info: { calories: 250, protein: 2.3, fat: 8.9, carbs: 40, fiber: 5, category: 'carbs (high)', alkaline: false } },
{ name: 'Pão de beijo com chia e pequi Empório Veggie', info: { calories: 316, protein: 0.6, fat: 14, carbs: 47, fiber: 1, 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 } },
+72 -237
View File
@@ -1,34 +1,18 @@
import { foodDatabase } from './foodDatabase.js'; import { foodDatabase } from './foodDatabase.js';
import { getButtonById, getButtonListByClassName, getDivById, getInputById, showFoodPreview } from './HtmlUtil.ts'; import { getButtonById, getButtonListByClassName, getDivById, getInputById, showFoodPreview } from './DomUtils.ts';
import { DailyTotalCalories, FoodItem, FoodStorage } from './types.js'; import { DailyTotalCalories, 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'; import { closeMobileMenu, delay, getCleanName, getIcon, handleMobileCalendarClick, hideLoading, hideSearchResults, navigateResultsKeyboard, QUICK_DELAY, scrollToCalendarView, showLoading, toggleCardHandler, toggleMobileMenu } from './Utils.ts';
import { showAuthForms, toggleAuthForms } from './auth.ts';
import { appState } from "./state";
// App state // App state
let selectedDate = new Date(); let selectedDate = new Date();
let currentViewDate = new Date(); let currentViewDate = new Date();
let searchTimeout: number | null = null;
let selectedFood: FoodItem | null = null; // review here let selectedFood: FoodItem | null = null; // review here
let currentHighlightIndex: number = -1;
let currentResults: FoodItem[] = [];
let calendarMonthlyCalories: DailyTotalCalories[] = [];
// Authentication state
let currentUser: any | null = null; let currentUser: any | null = null;
// DOM Elements
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');
const searchInput = getInputById('foodSearchInput');
const searchLoading = document.getElementById('searchLoading');
const searchResults = getDivById('searchResults');
// Initialize when page loads // Initialize when page loads
document.addEventListener('DOMContentLoaded', async function() { document.addEventListener('DOMContentLoaded', async function() {
await initializeAuth(); await initializeAuth();
@@ -36,12 +20,10 @@ document.addEventListener('DOMContentLoaded', async function() {
setupEventListeners(); setupEventListeners();
}); });
// Initialize application
async function initializeAuth() { async function initializeAuth() {
showLoading(); showLoading();
try { try {
// Check if user is already logged in
const isLoggedIn = await AppwriteAuth.isLoggedIn(); const isLoggedIn = await AppwriteAuth.isLoggedIn();
if (isLoggedIn) { if (isLoggedIn) {
@@ -59,20 +41,8 @@ async function initializeAuth() {
} }
} }
// Toggle between login and register forms
function toggleAuthForms() {
loginForm?.classList.toggle('hidden');
registerForm?.classList.toggle('hidden');
// Clear form data
const logForm = document.getElementById('loginForm') as HTMLFormElement;
logForm.reset();
const resetForm = document.getElementById('registerForm') as HTMLFormElement;
resetForm.reset();
}
// Handle user registration
async function handleRegister(e: SubmitEvent) { async function handleRegister(e: SubmitEvent) {
e.preventDefault(); e.preventDefault();
showLoading(); showLoading();
@@ -100,7 +70,6 @@ async function handleRegister(e: SubmitEvent) {
} }
} }
// Handle user login
async function handleLogin(e: SubmitEvent) { async function handleLogin(e: SubmitEvent) {
e.preventDefault(); e.preventDefault();
showLoading(); showLoading();
@@ -141,12 +110,10 @@ async function fetchUserSettings(documentId: string): Promise<any> {
return AppwriteDB.deleteUserSettings(documentId); return AppwriteDB.deleteUserSettings(documentId);
} }
// Main function that processes the response
async function handleBulkDelete(idsToDelete: string[]): Promise<void> { async function handleBulkDelete(idsToDelete: string[]): Promise<void> {
try { try {
// This creates and executes all promises concurrently // This creates and executes all promises concurrently
const results = await processResponseItems(idsToDelete, fetchUserSettings); const results = await processResponseItems(idsToDelete, fetchUserSettings);
console.debug('All requests completed:', results); console.debug('All requests completed:', results);
} catch (error) { } catch (error) {
console.error('One or more requests failed:', error); console.error('One or more requests failed:', error);
@@ -175,7 +142,6 @@ async function handleSaveSettings(e: SubmitEvent) {
await handleBulkDelete(documentsToDelete); await handleBulkDelete(documentsToDelete);
} }
// Save to Appwrite
await AppwriteDB.saveUserSettings({ await AppwriteDB.saveUserSettings({
caloriesGoal: parseInt(caloriesGoal), caloriesGoal: parseInt(caloriesGoal),
proteinGoal: parseInt(proteinGoal), proteinGoal: parseInt(proteinGoal),
@@ -185,7 +151,7 @@ async function handleSaveSettings(e: SubmitEvent) {
}); });
hideLoading(); hideLoading();
handleSettings(); toggleSettingsView();
selectDate(selectedDate); selectDate(selectedDate);
} catch (error) { } catch (error) {
hideLoading(); hideLoading();
@@ -210,14 +176,13 @@ async function handleLogout() {
} }
} }
async function handleSettings() { async function toggleSettingsView() {
const showSettings = getButtonById('settingsBtn').textContent.includes('Settings'); const showSettings = getButtonById('settingsBtn').textContent.includes('Settings');
if (showSettings) { if (showSettings) {
showLoading(); showLoading();
try { try {
// Fetch goals from settings
const settings = await AppwriteDB.getUserSettings(); const settings = await AppwriteDB.getUserSettings();
const hasGoals = Array.isArray(settings) && settings.length > 0; const hasGoals = Array.isArray(settings) && settings.length > 0;
if (hasGoals) { if (hasGoals) {
@@ -238,8 +203,8 @@ async function handleSettings() {
getButtonById('settingsBtn').textContent = '🔙 Back'; getButtonById('settingsBtn').textContent = '🔙 Back';
getButtonById('settingsBtnMobile').textContent = '🔙 Back'; getButtonById('settingsBtnMobile').textContent = '🔙 Back';
appContent?.classList.add('hidden'); getDivById('app-content').classList.add('hidden');
settingsContent?.classList.remove('hidden'); getDivById('settings-content').classList.remove('hidden');
hideLoading(); hideLoading();
} catch (error) { } catch (error) {
hideLoading(); hideLoading();
@@ -248,73 +213,30 @@ async function handleSettings() {
} }
} }
else { else {
appContent?.classList.remove('hidden'); getDivById('app-content').classList.remove('hidden');
settingsContent?.classList.add('hidden'); getDivById('settings-content').classList.add('hidden');
getButtonById('settingsBtn').textContent = '⚙️ Settings'; getButtonById('settingsBtn').textContent = '⚙️ Settings';
getButtonById('settingsBtnMobile').textContent = '⚙️ Settings'; getButtonById('settingsBtnMobile').textContent = '⚙️ Settings';
} }
} }
// Handle settings for mobile (same logic but different button) async function handleMobileSettingsClick() {
async function handleSettingsMobile() {
closeMobileMenu(); closeMobileMenu();
await handleSettings(); await toggleSettingsView();
}
// Handle calendar button - scroll to calendar section
function handleCalendar() {
const calendarSection = document.querySelector('.calendar-section') as HTMLElement;
if (calendarSection) {
calendarSection.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
// Handle calendar for mobile
function handleCalendarMobile() {
closeMobileMenu();
handleCalendar();
}
// Toggle mobile menu
function toggleMobileMenu() {
const mobileMenu = document.getElementById('mobileMenu');
if (mobileMenu) {
mobileMenu.classList.toggle('hidden');
}
}
// Close mobile menu
function closeMobileMenu() {
const mobileMenu = document.getElementById('mobileMenu');
if (mobileMenu) {
mobileMenu.classList.add('hidden');
}
}
// Show authentication forms
function showAuthForms() {
authSection?.classList.remove('hidden');
userInfo?.classList.add('hidden');
appContent?.classList.add('hidden');
} }
async function showMainApp() { async function showMainApp() {
authSection?.classList.add('hidden'); getDivById('auth-section').classList.add('hidden');
userInfo?.classList.remove('hidden'); getDivById('user-info').classList.remove('hidden');
appContent?.classList.remove('hidden'); getDivById('app-content').classList.remove('hidden');
// Update user info display
if (currentUser) { if (currentUser) {
getDivById('userName').textContent = `Welcome, ${currentUser.name}!`; getDivById('userName').textContent = `Welcome, ${currentUser.name}!`;
} }
// Update date display
updateCurrentDate(); updateCurrentDate();
// Get entries with total // Get daily entries with total from Appwrite to display in calendar
const entries = await AppwriteDB.getMonthlyCalories(selectedDate); const entries = await AppwriteDB.getMonthlyCalories(selectedDate);
entries.forEach((entry) => { entries.forEach((entry) => {
const calories = parseInt(entry.totalCalories); const calories = parseInt(entry.totalCalories);
@@ -323,17 +245,8 @@ async function showMainApp() {
}); });
} }
// Show/hide loading overlay
function showLoading() {
loadingOverlay.classList.remove('hidden');
}
function hideLoading() {
loadingOverlay.classList.add('hidden');
}
function clearEditing() { function clearEditing() {
searchInput.value = ''; getInputById('foodSearchInput').value = '';
getInputById('gramAmount').value = '100'; getInputById('gramAmount').value = '100';
showFoodPreview(false); showFoodPreview(false);
getDivById('add-foot-title').innerHTML = 'Add Food Entry'; getDivById('add-foot-title').innerHTML = 'Add Food Entry';
@@ -456,13 +369,13 @@ const setupEventListeners = () => {
// Desktop header buttons // Desktop header buttons
document.getElementById('logoutBtn')?.addEventListener('click', handleLogout); document.getElementById('logoutBtn')?.addEventListener('click', handleLogout);
document.getElementById('settingsBtn')?.addEventListener('click', handleSettings); document.getElementById('settingsBtn')?.addEventListener('click', toggleSettingsView);
document.getElementById('calendarBtn')?.addEventListener('click', handleCalendar); document.getElementById('calendarBtn')?.addEventListener('click', scrollToCalendarView);
// Mobile header buttons // Mobile header buttons
document.getElementById('logoutBtnMobile')?.addEventListener('click', handleLogout); document.getElementById('logoutBtnMobile')?.addEventListener('click', handleLogout);
document.getElementById('settingsBtnMobile')?.addEventListener('click', handleSettingsMobile); document.getElementById('settingsBtnMobile')?.addEventListener('click', handleMobileSettingsClick);
document.getElementById('calendarBtnMobile')?.addEventListener('click', handleCalendarMobile); document.getElementById('calendarBtnMobile')?.addEventListener('click', handleMobileCalendarClick);
// Mobile menu toggle // Mobile menu toggle
document.getElementById('mobileMenuToggle')?.addEventListener('click', toggleMobileMenu); document.getElementById('mobileMenuToggle')?.addEventListener('click', toggleMobileMenu);
@@ -471,7 +384,7 @@ const setupEventListeners = () => {
document.getElementById('settingsForm')?.addEventListener('submit', handleSaveSettings); document.getElementById('settingsForm')?.addEventListener('submit', handleSaveSettings);
// Search functionality // Search functionality
searchInput.addEventListener('input', function(e: Event) { getInputById('foodSearchInput').addEventListener('input', function(e: Event) {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
const query = target.value.trim(); const query = target.value.trim();
@@ -481,36 +394,36 @@ const setupEventListeners = () => {
} }
// Show loading // Show loading
searchLoading?.classList.add('show'); getDivById('searchLoading').classList.add('show');
// Clear previous timeout // Clear previous timeout
if (searchTimeout) { if (appState.searchTimeout) {
clearTimeout(searchTimeout); clearTimeout(appState.searchTimeout);
} }
// Debounce search // Debounce search
searchTimeout = setTimeout(() => { appState.searchTimeout = setTimeout(() => {
performSearch(query); performSearch(query);
}, 300); }, 300);
}); });
// Keyboard navigation // Keyboard navigation
searchInput?.addEventListener('keydown', function(e) { getInputById('foodSearchInput').addEventListener('keydown', function(e) {
if (!searchResults?.classList.contains('show')) return; if (!getDivById('searchResults').classList.contains('show')) return;
switch(e.key) { switch(e.key) {
case 'ArrowDown': case 'ArrowDown':
e.preventDefault(); e.preventDefault();
navigateResults(1); navigateResultsKeyboard(1);
break; break;
case 'ArrowUp': case 'ArrowUp':
e.preventDefault(); e.preventDefault();
navigateResults(-1); navigateResultsKeyboard(-1);
break; break;
case 'Enter': case 'Enter':
e.preventDefault(); e.preventDefault();
if (currentHighlightIndex >= 0 && currentResults[currentHighlightIndex]) { if (appState.currentHighlightIndex >= 0 && appState.searchResults[appState.currentHighlightIndex]) {
selectFood(currentResults[currentHighlightIndex]); selectFood(appState.searchResults[appState.currentHighlightIndex]);
} }
break; break;
case 'Escape': case 'Escape':
@@ -522,7 +435,7 @@ const setupEventListeners = () => {
// Click outside to close search results // Click outside to close search results
document.addEventListener('click', function(e: Event) { document.addEventListener('click', function(e: Event) {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
if (!searchResults.contains(target) && !searchInput.contains(target)) { if (!getDivById('searchResults').contains(target) && !getInputById('foodSearchInput').contains(target)) {
hideSearchResults(); hideSearchResults();
} }
}); });
@@ -541,52 +454,26 @@ const setupEventListeners = () => {
}); });
} }
const getCleanName = (foodName: string): string => {
const newName = foodName
.replace(/á/g, 'a')
.replace(/ã/g, 'a')
.replace(/â/g, 'a')
.replace(/ê/g, 'e')
.replace(/é/g, 'e')
.replace(/ó/g, 'o')
.replace(/ú/g, 'u')
.replace(/ç/g, 'c');
return newName.toLowerCase();
}
// Perform search
function performSearch(query: string) { function performSearch(query: string) {
// Simulate API call - replace with your actual database search
const results: FoodItem[] = foodDatabase.filter(food => { const results: FoodItem[] = foodDatabase.filter(food => {
const cleanName = getCleanName(food.name); const cleanName = getCleanName(food.name);
const category = food.info.category.toLowerCase(); const category = food.info.category.toLowerCase();
return cleanName.includes(query.toLowerCase()) || category.includes(query.toLowerCase()) return cleanName.includes(query.toLowerCase()) || category.includes(query.toLowerCase())
}).slice(0, 5); // Limit to 5 results }).slice(0, 5); // Limit to 5 results
currentResults = results; appState.searchResults = results;
displaySearchResults(results); displaySearchResults(results);
searchLoading?.classList.remove('show'); getDivById('searchLoading').classList.remove('show');
} }
// Display search results
function displaySearchResults(results: FoodItem[]) { function displaySearchResults(results: FoodItem[]) {
if (results.length === 0) { if (results.length === 0) {
searchResults.innerHTML = '<div class="no-results">No foods found. Try a different search term.</div>'; getDivById('searchResults').innerHTML = '<div class="no-results">No foods found. Try a different search term.</div>';
} else { } else {
const icons = { getDivById('searchResults').innerHTML = results.map((food, index) => `
'fats': '🥑 🍳 🍟',
'proteins': '🫘 🥩 🥚',
'carbs (high)': '🍞 🥔 🍠',
'leaves': '🥬 🥗 🌿',
'fruits': '🍊 🍇 🍎',
'carbs (low)': '🥦 🍅 🍓',
'dairy': '🧀 🧈 🥛'
};
searchResults.innerHTML = results.map((food, index) => `
<div class="search-result-item" data-index="${index}" onclick="selectFood(${JSON.stringify(food).replace(/"/g, '&quot;')})"> <div class="search-result-item" data-index="${index}" onclick="selectFood(${JSON.stringify(food).replace(/"/g, '&quot;')})">
<div class="food-info"> <div class="food-info">
<div class="food-name">${icons[food.info.category]} ${food.name}</div> <div class="food-name">${getIcon(food.info.category)} ${food.name}</div>
<div class="food-details">${food.info.category} • 100 g</div> <div class="food-details">${food.info.category} • 100 g</div>
</div> </div>
<div class="food-calories">${food.info.calories} cal</div> <div class="food-calories">${food.info.calories} cal</div>
@@ -594,42 +481,16 @@ function displaySearchResults(results: FoodItem[]) {
`).join(''); `).join('');
} }
searchResults.classList.add('show'); getDivById('searchResults').classList.add('show');
currentHighlightIndex = -1; appState.currentHighlightIndex = -1;
} }
// Navigate results with keyboard
function navigateResults(direction: number) {
const items = searchResults.querySelectorAll('.search-result-item');
if (items.length === 0) return;
// Remove current highlight
if (currentHighlightIndex >= 0) {
items[currentHighlightIndex].classList.remove('highlighted');
}
// Calculate new index
currentHighlightIndex += direction;
if (currentHighlightIndex < 0) currentHighlightIndex = items.length - 1;
if (currentHighlightIndex >= items.length) currentHighlightIndex = 0;
// Add new highlight
items[currentHighlightIndex].classList.add('highlighted');
items[currentHighlightIndex].scrollIntoView({ block: 'nearest' });
}
// Select food
function selectFood(food: FoodItem) { function selectFood(food: FoodItem) {
selectedFood = food; selectedFood = food;
// Clear search and hide results getInputById('foodSearchInput').value = food.name;
searchInput.value = food.name; hideSearchResults();
hideSearchResults();
// Focus on quantity input
getInputById('gramAmount').focus(); getInputById('gramAmount').focus();
// Update calories
previewCalories(food); previewCalories(food);
} }
@@ -648,7 +509,7 @@ async function setFoodToEdit(foodId: string) {
// set the selected food name and quantity // set the selected food name and quantity
const foodData: FoodItem = getFoodItemByName(foodToEdit[0].name); const foodData: FoodItem = getFoodItemByName(foodToEdit[0].name);
selectedFood = foodData; selectedFood = foodData;
searchInput.value = foodToEdit[0].name; getInputById('foodSearchInput').value = foodToEdit[0].name;
getInputById('gramAmount').value = foodToEdit[0].grams; getInputById('gramAmount').value = foodToEdit[0].grams;
previewCalories(foodData) previewCalories(foodData)
@@ -676,25 +537,6 @@ async function setFoodToEdit(foodId: string) {
} }
} }
// 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) { function deleteCardHandler(e: Event) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -719,7 +561,7 @@ function editCardHandler(e: Event) {
} }
} }
const addDeleteEvents = () => { const setupEditAndDeleteEvents = () => {
// Setup toggle show cards events // Setup toggle show cards events
const cards = getButtonListByClassName('card-header-toggle'); const cards = getButtonListByClassName('card-header-toggle');
Array.from(cards).forEach((card: HTMLElement) => { Array.from(cards).forEach((card: HTMLElement) => {
@@ -742,7 +584,6 @@ const addDeleteEvents = () => {
}); });
} }
// Update current date display
const updateCurrentDate = () => { const updateCurrentDate = () => {
const date = new Date(selectedDate); const date = new Date(selectedDate);
getDivById('currentDate').textContent = date.toLocaleDateString('en-US', { getDivById('currentDate').textContent = date.toLocaleDateString('en-US', {
@@ -805,7 +646,7 @@ const updateFood = async () => {
const monthlyUpdated = await AppwriteDB.updateMonthlyCaloryForDay(monthlyDocumentId?.documentId, selectedDate, totalCalories); const monthlyUpdated = await AppwriteDB.updateMonthlyCaloryForDay(monthlyDocumentId?.documentId, selectedDate, totalCalories);
updateDocumentIdForToday(monthlyUpdated.$id, monthlyUpdated.totalCalories); updateDocumentIdForToday(monthlyUpdated.$id, monthlyUpdated.totalCalories);
} }
delay(1); delay(QUICK_DELAY);
clearEditing(); clearEditing();
selectDate(selectedDate); selectDate(selectedDate);
} catch (error) { } catch (error) {
@@ -818,7 +659,7 @@ const updateFood = async () => {
const getDocumentIdForToday = (): DailyTotalCalories | null => { const getDocumentIdForToday = (): DailyTotalCalories | null => {
const today = selectedDate.getDate(); const today = selectedDate.getDate();
const todayRecord = calendarMonthlyCalories.filter(x => x.day === today); const todayRecord = appState.calendarMonthlyCalories.filter(x => x.day === today);
if (todayRecord.length > 0) { if (todayRecord.length > 0) {
return todayRecord[0]; return todayRecord[0];
} }
@@ -826,7 +667,7 @@ const getDocumentIdForToday = (): DailyTotalCalories | null => {
} }
const getDocumentIdForDay = (day: number): number => { const getDocumentIdForDay = (day: number): number => {
const todayRecord = calendarMonthlyCalories.filter(x => x.day === day); const todayRecord = appState.calendarMonthlyCalories.filter(x => x.day === day);
return todayRecord.length > 0 ? todayRecord[0].totalCalories : 0; return todayRecord.length > 0 ? todayRecord[0].totalCalories : 0;
} }
@@ -836,15 +677,15 @@ const updateDocumentIdForToday = (id: string, totalCalories: number): void => {
} }
const updateDocumentIdForDay = (id: string, totalCalories: number, day: number): void => { const updateDocumentIdForDay = (id: string, totalCalories: number, day: number): void => {
const record = calendarMonthlyCalories.filter(x => x.day === day); const record = appState.calendarMonthlyCalories.filter(x => x.day === day);
if (record.length === 0) { if (record.length === 0) {
calendarMonthlyCalories.push({ appState.calendarMonthlyCalories.push({
documentId: id, documentId: id,
day: day, day: day,
totalCalories: totalCalories totalCalories: totalCalories
}); });
} else { } else {
calendarMonthlyCalories.forEach((record) => { appState.calendarMonthlyCalories.forEach((record) => {
if (record.day === day) { if (record.day === day) {
record.documentId = id; record.documentId = id;
record.totalCalories = totalCalories; record.totalCalories = totalCalories;
@@ -853,7 +694,6 @@ const updateDocumentIdForDay = (id: string, totalCalories: number, day: number):
} }
} }
// Add food to the log
const addFood = async () => { const addFood = async () => {
if (!currentUser) { if (!currentUser) {
swal('Hey!', 'Please log in to add food entries.', 'info'); swal('Hey!', 'Please log in to add food entries.', 'info');
@@ -911,11 +751,8 @@ const addFood = async () => {
updateDocumentIdForToday(monthlyCreated.$id, monthlyCreated.totalCalories); updateDocumentIdForToday(monthlyCreated.$id, monthlyCreated.totalCalories);
} }
// Add to HTML table with Appwrite document ID addFoodToView(entry, savedEntry.$id);
addFoodToTable(entry, savedEntry.$id); updateTotalCalories();
// Update totals
updateTotalCalories([savedEntry]);
// Update total macros // Update total macros
const proteinDiv: HTMLElement = getDivById('proteinValue'); const proteinDiv: HTMLElement = getDivById('proteinValue');
@@ -938,7 +775,7 @@ const addFood = async () => {
getInputById('foodSearchInput').value = ''; getInputById('foodSearchInput').value = '';
gramAmount.value = '100'; gramAmount.value = '100';
showFoodPreview(false); showFoodPreview(false);
await delay(1); await delay(QUICK_DELAY);
renderCalendar(); renderCalendar();
} catch (error) { } catch (error) {
console.error('Add food error:', error); console.error('Add food error:', error);
@@ -948,7 +785,7 @@ const addFood = async () => {
} }
} }
// Load food entries from Appwrite // Load food entries from Appwrite for a given date
async function loadFoodEntries(date: Date) { async function loadFoodEntries(date: Date) {
if (!currentUser) return; if (!currentUser) return;
@@ -957,7 +794,6 @@ async function loadFoodEntries(date: Date) {
try { try {
const entries = await AppwriteDB.getFoodEntries(date); const entries = await AppwriteDB.getFoodEntries(date);
// Clear current table
getDivById('foodCardsContainer').innerHTML = ''; getDivById('foodCardsContainer').innerHTML = '';
// Add each entry to table // Add each entry to table
@@ -975,8 +811,8 @@ async function loadFoodEntries(date: Date) {
date: entry.date, date: entry.date,
alkaline: entry.alkaline, alkaline: entry.alkaline,
}; };
addFoodToTable(foodData, entry.$id); addFoodToView(foodData, entry.$id);
}); });
if (entries.length === 0) { if (entries.length === 0) {
@@ -989,11 +825,8 @@ async function loadFoodEntries(date: Date) {
`; `;
} }
// Setup Delete events setupEditAndDeleteEvents();
addDeleteEvents(); updateTotalCalories();
// Update total calories
updateTotalCalories(entries);
// Update counters // Update counters
let totalCalories = 0; let totalCalories = 0;
@@ -1063,8 +896,6 @@ async function loadFoodEntries(date: Date) {
} }
} }
const delay = (seconds: number) => new Promise(resolve => setTimeout(resolve, seconds * 1000));
// Handle food deletion // Handle food deletion
async function handleDeleteFood(documentId: string) { async function handleDeleteFood(documentId: string) {
if (!documentId) return; if (!documentId) return;
@@ -1101,7 +932,7 @@ async function handleDeleteFood(documentId: string) {
const totalCalories = monthlyDocumentId?.totalCalories - existingToDelete; const totalCalories = monthlyDocumentId?.totalCalories - existingToDelete;
const monthlyCreated = await AppwriteDB.updateMonthlyCaloryForDay(monthlyDocumentId?.documentId, selectedDate, totalCalories); const monthlyCreated = await AppwriteDB.updateMonthlyCaloryForDay(monthlyDocumentId?.documentId, selectedDate, totalCalories);
updateDocumentIdForToday(monthlyCreated.$id, monthlyCreated.totalCalories); updateDocumentIdForToday(monthlyCreated.$id, monthlyCreated.totalCalories);
await delay(1); await delay(QUICK_DELAY);
} }
selectDate(selectedDate); selectDate(selectedDate);
@@ -1113,8 +944,7 @@ async function handleDeleteFood(documentId: string) {
} }
} }
// Add food to table (existing function - update to use document ID) function addFoodToView(foodData: FoodStorage, documentId: string) {
function addFoodToTable(foodData: FoodStorage, documentId: string) {
// New Food card // New Food card
const container = getDivById('foodCardsContainer'); const container = getDivById('foodCardsContainer');
const card = document.createElement('div'); const card = document.createElement('div');
@@ -1125,6 +955,8 @@ function addFoodToTable(foodData: FoodStorage, documentId: string) {
container.innerHTML = ''; container.innerHTML = '';
} }
const foodFromDatabase = getFoodItemByName(foodData.name);
card.innerHTML = ` card.innerHTML = `
<div class="card-header card-header-toggle"> <div class="card-header card-header-toggle">
<div class="card-main-info"> <div class="card-main-info">
@@ -1158,8 +990,11 @@ function addFoodToTable(foodData: FoodStorage, documentId: string) {
<div class="food-is-alkaline hidden">${foodData.alkaline ? 'alkaline' : ''}</div> <div class="food-is-alkaline hidden">${foodData.alkaline ? 'alkaline' : ''}</div>
</div> </div>
<div class="card-actions"> <div class="card-actions">
<button class="btn btn-edit" data-food-id="${documentId}">Edit</button> <span class="muted">${getIcon(foodFromDatabase.info.category)} ${foodFromDatabase.info.category}</span>
<button class="btn btn-delete" data-food-id="${documentId}">Delete</button> <div>
<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>
</div> </div>
</div> </div>
@@ -1167,12 +1002,12 @@ function addFoodToTable(foodData: FoodStorage, documentId: string) {
container?.appendChild(card); container?.appendChild(card);
// Setup Delete events setupEditAndDeleteEvents();
addDeleteEvents();
} }
// Update total calories // Update total calories and display it in the counter
function updateTotalCalories(entries: Models.DefaultDocument[]) { // Also updates alkaline level percentage
function updateTotalCalories() {
const caloriesDiv = document.querySelectorAll('.calories-display'); const caloriesDiv = document.querySelectorAll('.calories-display');
let total = 0; let total = 0;
+15
View File
@@ -0,0 +1,15 @@
import { DailyTotalCalories, FoodItem } from "./types";
interface AppState {
currentHighlightIndex: number;
searchTimeout: number | null;
searchResults: FoodItem[];
calendarMonthlyCalories: DailyTotalCalories[];
}
export const appState: AppState = {
currentHighlightIndex: -1,
searchTimeout: null,
searchResults: [],
calendarMonthlyCalories: []
};
+1 -1
View File
@@ -305,7 +305,7 @@ h1 {
.card-actions { .card-actions {
display: flex; display: flex;
justify-content: flex-end; justify-content: space-between;
gap: 8px; gap: 8px;
padding-top: 16px; padding-top: 16px;
border-top: 1px solid #e0e0e0; border-top: 1px solid #e0e0e0;