feat: organize functions and add type to foods
This commit is contained in:
+6
-6
@@ -221,27 +221,27 @@
|
||||
<div class="container">
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<button type="submit" class="settings-btn">Save</button>
|
||||
|
||||
+110
@@ -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
@@ -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();
|
||||
}
|
||||
@@ -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: '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: '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
|
||||
{ name: 'Agrião', info: { calories: 17, protein: 2.7, fat: 0.2, carbs: 2.3, fiber: 2.1, category: 'leaves', alkaline: true } },
|
||||
|
||||
+72
-237
@@ -1,34 +1,18 @@
|
||||
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 { AppwriteAuth, AppwriteDB } from './appwrite.js';
|
||||
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
|
||||
let selectedDate = new Date();
|
||||
let currentViewDate = new Date();
|
||||
let searchTimeout: number | null = null;
|
||||
let selectedFood: FoodItem | null = null; // review here
|
||||
let currentHighlightIndex: number = -1;
|
||||
let currentResults: FoodItem[] = [];
|
||||
let calendarMonthlyCalories: DailyTotalCalories[] = [];
|
||||
|
||||
// Authentication state
|
||||
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
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
await initializeAuth();
|
||||
@@ -36,12 +20,10 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
setupEventListeners();
|
||||
});
|
||||
|
||||
// Initialize application
|
||||
async function initializeAuth() {
|
||||
showLoading();
|
||||
|
||||
try {
|
||||
// Check if user is already logged in
|
||||
const isLoggedIn = await AppwriteAuth.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) {
|
||||
e.preventDefault();
|
||||
showLoading();
|
||||
@@ -100,7 +70,6 @@ async function handleRegister(e: SubmitEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle user login
|
||||
async function handleLogin(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
showLoading();
|
||||
@@ -141,12 +110,10 @@ async function fetchUserSettings(documentId: string): Promise<any> {
|
||||
return AppwriteDB.deleteUserSettings(documentId);
|
||||
}
|
||||
|
||||
// Main function that processes the response
|
||||
async function handleBulkDelete(idsToDelete: string[]): Promise<void> {
|
||||
try {
|
||||
// This creates and executes all promises concurrently
|
||||
const results = await processResponseItems(idsToDelete, fetchUserSettings);
|
||||
|
||||
console.debug('All requests completed:', results);
|
||||
} catch (error) {
|
||||
console.error('One or more requests failed:', error);
|
||||
@@ -175,7 +142,6 @@ async function handleSaveSettings(e: SubmitEvent) {
|
||||
await handleBulkDelete(documentsToDelete);
|
||||
}
|
||||
|
||||
// Save to Appwrite
|
||||
await AppwriteDB.saveUserSettings({
|
||||
caloriesGoal: parseInt(caloriesGoal),
|
||||
proteinGoal: parseInt(proteinGoal),
|
||||
@@ -185,7 +151,7 @@ async function handleSaveSettings(e: SubmitEvent) {
|
||||
});
|
||||
|
||||
hideLoading();
|
||||
handleSettings();
|
||||
toggleSettingsView();
|
||||
selectDate(selectedDate);
|
||||
} catch (error) {
|
||||
hideLoading();
|
||||
@@ -210,14 +176,13 @@ async function handleLogout() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSettings() {
|
||||
async function toggleSettingsView() {
|
||||
const showSettings = getButtonById('settingsBtn').textContent.includes('Settings');
|
||||
|
||||
if (showSettings) {
|
||||
showLoading();
|
||||
|
||||
try {
|
||||
// Fetch goals from settings
|
||||
const settings = await AppwriteDB.getUserSettings();
|
||||
const hasGoals = Array.isArray(settings) && settings.length > 0;
|
||||
if (hasGoals) {
|
||||
@@ -238,8 +203,8 @@ async function handleSettings() {
|
||||
getButtonById('settingsBtn').textContent = '🔙 Back';
|
||||
getButtonById('settingsBtnMobile').textContent = '🔙 Back';
|
||||
|
||||
appContent?.classList.add('hidden');
|
||||
settingsContent?.classList.remove('hidden');
|
||||
getDivById('app-content').classList.add('hidden');
|
||||
getDivById('settings-content').classList.remove('hidden');
|
||||
hideLoading();
|
||||
} catch (error) {
|
||||
hideLoading();
|
||||
@@ -248,73 +213,30 @@ async function handleSettings() {
|
||||
}
|
||||
}
|
||||
else {
|
||||
appContent?.classList.remove('hidden');
|
||||
settingsContent?.classList.add('hidden');
|
||||
getDivById('app-content').classList.remove('hidden');
|
||||
getDivById('settings-content').classList.add('hidden');
|
||||
getButtonById('settingsBtn').textContent = '⚙️ Settings';
|
||||
getButtonById('settingsBtnMobile').textContent = '⚙️ Settings';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle settings for mobile (same logic but different button)
|
||||
async function handleSettingsMobile() {
|
||||
async function handleMobileSettingsClick() {
|
||||
closeMobileMenu();
|
||||
await handleSettings();
|
||||
}
|
||||
|
||||
// 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');
|
||||
await toggleSettingsView();
|
||||
}
|
||||
|
||||
async function showMainApp() {
|
||||
authSection?.classList.add('hidden');
|
||||
userInfo?.classList.remove('hidden');
|
||||
appContent?.classList.remove('hidden');
|
||||
getDivById('auth-section').classList.add('hidden');
|
||||
getDivById('user-info').classList.remove('hidden');
|
||||
getDivById('app-content').classList.remove('hidden');
|
||||
|
||||
// Update user info display
|
||||
if (currentUser) {
|
||||
getDivById('userName').textContent = `Welcome, ${currentUser.name}!`;
|
||||
}
|
||||
|
||||
// Update date display
|
||||
updateCurrentDate();
|
||||
|
||||
// Get entries with total
|
||||
// Get daily entries with total from Appwrite to display in calendar
|
||||
const entries = await AppwriteDB.getMonthlyCalories(selectedDate);
|
||||
entries.forEach((entry) => {
|
||||
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() {
|
||||
searchInput.value = '';
|
||||
getInputById('foodSearchInput').value = '';
|
||||
getInputById('gramAmount').value = '100';
|
||||
showFoodPreview(false);
|
||||
getDivById('add-foot-title').innerHTML = 'Add Food Entry';
|
||||
@@ -456,13 +369,13 @@ const setupEventListeners = () => {
|
||||
|
||||
// Desktop header buttons
|
||||
document.getElementById('logoutBtn')?.addEventListener('click', handleLogout);
|
||||
document.getElementById('settingsBtn')?.addEventListener('click', handleSettings);
|
||||
document.getElementById('calendarBtn')?.addEventListener('click', handleCalendar);
|
||||
document.getElementById('settingsBtn')?.addEventListener('click', toggleSettingsView);
|
||||
document.getElementById('calendarBtn')?.addEventListener('click', scrollToCalendarView);
|
||||
|
||||
// Mobile header buttons
|
||||
document.getElementById('logoutBtnMobile')?.addEventListener('click', handleLogout);
|
||||
document.getElementById('settingsBtnMobile')?.addEventListener('click', handleSettingsMobile);
|
||||
document.getElementById('calendarBtnMobile')?.addEventListener('click', handleCalendarMobile);
|
||||
document.getElementById('settingsBtnMobile')?.addEventListener('click', handleMobileSettingsClick);
|
||||
document.getElementById('calendarBtnMobile')?.addEventListener('click', handleMobileCalendarClick);
|
||||
|
||||
// Mobile menu toggle
|
||||
document.getElementById('mobileMenuToggle')?.addEventListener('click', toggleMobileMenu);
|
||||
@@ -471,7 +384,7 @@ const setupEventListeners = () => {
|
||||
document.getElementById('settingsForm')?.addEventListener('submit', handleSaveSettings);
|
||||
|
||||
// Search functionality
|
||||
searchInput.addEventListener('input', function(e: Event) {
|
||||
getInputById('foodSearchInput').addEventListener('input', function(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const query = target.value.trim();
|
||||
|
||||
@@ -481,36 +394,36 @@ const setupEventListeners = () => {
|
||||
}
|
||||
|
||||
// Show loading
|
||||
searchLoading?.classList.add('show');
|
||||
getDivById('searchLoading').classList.add('show');
|
||||
|
||||
// Clear previous timeout
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
if (appState.searchTimeout) {
|
||||
clearTimeout(appState.searchTimeout);
|
||||
}
|
||||
|
||||
// Debounce search
|
||||
searchTimeout = setTimeout(() => {
|
||||
appState.searchTimeout = setTimeout(() => {
|
||||
performSearch(query);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Keyboard navigation
|
||||
searchInput?.addEventListener('keydown', function(e) {
|
||||
if (!searchResults?.classList.contains('show')) return;
|
||||
getInputById('foodSearchInput').addEventListener('keydown', function(e) {
|
||||
if (!getDivById('searchResults').classList.contains('show')) return;
|
||||
|
||||
switch(e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
navigateResults(1);
|
||||
navigateResultsKeyboard(1);
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
navigateResults(-1);
|
||||
navigateResultsKeyboard(-1);
|
||||
break;
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
if (currentHighlightIndex >= 0 && currentResults[currentHighlightIndex]) {
|
||||
selectFood(currentResults[currentHighlightIndex]);
|
||||
if (appState.currentHighlightIndex >= 0 && appState.searchResults[appState.currentHighlightIndex]) {
|
||||
selectFood(appState.searchResults[appState.currentHighlightIndex]);
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
@@ -522,7 +435,7 @@ const setupEventListeners = () => {
|
||||
// Click outside to close search results
|
||||
document.addEventListener('click', function(e: Event) {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!searchResults.contains(target) && !searchInput.contains(target)) {
|
||||
if (!getDivById('searchResults').contains(target) && !getInputById('foodSearchInput').contains(target)) {
|
||||
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) {
|
||||
// Simulate API call - replace with your actual database search
|
||||
const results: FoodItem[] = foodDatabase.filter(food => {
|
||||
const cleanName = getCleanName(food.name);
|
||||
const category = food.info.category.toLowerCase();
|
||||
return cleanName.includes(query.toLowerCase()) || category.includes(query.toLowerCase())
|
||||
}).slice(0, 5); // Limit to 5 results
|
||||
|
||||
currentResults = results;
|
||||
appState.searchResults = results;
|
||||
displaySearchResults(results);
|
||||
searchLoading?.classList.remove('show');
|
||||
getDivById('searchLoading').classList.remove('show');
|
||||
}
|
||||
|
||||
// Display search results
|
||||
function displaySearchResults(results: FoodItem[]) {
|
||||
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 {
|
||||
const icons = {
|
||||
'fats': '🥑 🍳 🍟',
|
||||
'proteins': '🫘 🥩 🥚',
|
||||
'carbs (high)': '🍞 🥔 🍠',
|
||||
'leaves': '🥬 🥗 🌿',
|
||||
'fruits': '🍊 🍇 🍎',
|
||||
'carbs (low)': '🥦 🍅 🍓',
|
||||
'dairy': '🧀 🧈 🥛'
|
||||
};
|
||||
|
||||
searchResults.innerHTML = results.map((food, index) => `
|
||||
getDivById('searchResults').innerHTML = results.map((food, index) => `
|
||||
<div class="search-result-item" data-index="${index}" onclick="selectFood(${JSON.stringify(food).replace(/"/g, '"')})">
|
||||
<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>
|
||||
<div class="food-calories">${food.info.calories} cal</div>
|
||||
@@ -594,42 +481,16 @@ function displaySearchResults(results: FoodItem[]) {
|
||||
`).join('');
|
||||
}
|
||||
|
||||
searchResults.classList.add('show');
|
||||
currentHighlightIndex = -1;
|
||||
getDivById('searchResults').classList.add('show');
|
||||
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) {
|
||||
selectedFood = food;
|
||||
|
||||
// Clear search and hide results
|
||||
searchInput.value = food.name;
|
||||
hideSearchResults();
|
||||
|
||||
// Focus on quantity input
|
||||
getInputById('foodSearchInput').value = food.name;
|
||||
hideSearchResults();
|
||||
getInputById('gramAmount').focus();
|
||||
|
||||
// Update calories
|
||||
previewCalories(food);
|
||||
}
|
||||
|
||||
@@ -648,7 +509,7 @@ async function setFoodToEdit(foodId: string) {
|
||||
// set the selected food name and quantity
|
||||
const foodData: FoodItem = getFoodItemByName(foodToEdit[0].name);
|
||||
selectedFood = foodData;
|
||||
searchInput.value = foodToEdit[0].name;
|
||||
getInputById('foodSearchInput').value = foodToEdit[0].name;
|
||||
getInputById('gramAmount').value = foodToEdit[0].grams;
|
||||
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) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -719,7 +561,7 @@ function editCardHandler(e: Event) {
|
||||
}
|
||||
}
|
||||
|
||||
const addDeleteEvents = () => {
|
||||
const setupEditAndDeleteEvents = () => {
|
||||
// Setup toggle show cards events
|
||||
const cards = getButtonListByClassName('card-header-toggle');
|
||||
Array.from(cards).forEach((card: HTMLElement) => {
|
||||
@@ -742,7 +584,6 @@ const addDeleteEvents = () => {
|
||||
});
|
||||
}
|
||||
|
||||
// Update current date display
|
||||
const updateCurrentDate = () => {
|
||||
const date = new Date(selectedDate);
|
||||
getDivById('currentDate').textContent = date.toLocaleDateString('en-US', {
|
||||
@@ -805,7 +646,7 @@ const updateFood = async () => {
|
||||
const monthlyUpdated = await AppwriteDB.updateMonthlyCaloryForDay(monthlyDocumentId?.documentId, selectedDate, totalCalories);
|
||||
updateDocumentIdForToday(monthlyUpdated.$id, monthlyUpdated.totalCalories);
|
||||
}
|
||||
delay(1);
|
||||
delay(QUICK_DELAY);
|
||||
clearEditing();
|
||||
selectDate(selectedDate);
|
||||
} catch (error) {
|
||||
@@ -818,7 +659,7 @@ const updateFood = async () => {
|
||||
|
||||
const getDocumentIdForToday = (): DailyTotalCalories | null => {
|
||||
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) {
|
||||
return todayRecord[0];
|
||||
}
|
||||
@@ -826,7 +667,7 @@ const getDocumentIdForToday = (): DailyTotalCalories | null => {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -836,15 +677,15 @@ const updateDocumentIdForToday = (id: string, totalCalories: 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) {
|
||||
calendarMonthlyCalories.push({
|
||||
appState.calendarMonthlyCalories.push({
|
||||
documentId: id,
|
||||
day: day,
|
||||
totalCalories: totalCalories
|
||||
});
|
||||
} else {
|
||||
calendarMonthlyCalories.forEach((record) => {
|
||||
} else {
|
||||
appState.calendarMonthlyCalories.forEach((record) => {
|
||||
if (record.day === day) {
|
||||
record.documentId = id;
|
||||
record.totalCalories = totalCalories;
|
||||
@@ -853,7 +694,6 @@ const updateDocumentIdForDay = (id: string, totalCalories: number, day: number):
|
||||
}
|
||||
}
|
||||
|
||||
// Add food to the log
|
||||
const addFood = async () => {
|
||||
if (!currentUser) {
|
||||
swal('Hey!', 'Please log in to add food entries.', 'info');
|
||||
@@ -911,11 +751,8 @@ const addFood = async () => {
|
||||
updateDocumentIdForToday(monthlyCreated.$id, monthlyCreated.totalCalories);
|
||||
}
|
||||
|
||||
// Add to HTML table with Appwrite document ID
|
||||
addFoodToTable(entry, savedEntry.$id);
|
||||
|
||||
// Update totals
|
||||
updateTotalCalories([savedEntry]);
|
||||
addFoodToView(entry, savedEntry.$id);
|
||||
updateTotalCalories();
|
||||
|
||||
// Update total macros
|
||||
const proteinDiv: HTMLElement = getDivById('proteinValue');
|
||||
@@ -938,7 +775,7 @@ const addFood = async () => {
|
||||
getInputById('foodSearchInput').value = '';
|
||||
gramAmount.value = '100';
|
||||
showFoodPreview(false);
|
||||
await delay(1);
|
||||
await delay(QUICK_DELAY);
|
||||
renderCalendar();
|
||||
} catch (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) {
|
||||
if (!currentUser) return;
|
||||
|
||||
@@ -957,7 +794,6 @@ async function loadFoodEntries(date: Date) {
|
||||
try {
|
||||
const entries = await AppwriteDB.getFoodEntries(date);
|
||||
|
||||
// Clear current table
|
||||
getDivById('foodCardsContainer').innerHTML = '';
|
||||
|
||||
// Add each entry to table
|
||||
@@ -975,8 +811,8 @@ async function loadFoodEntries(date: Date) {
|
||||
date: entry.date,
|
||||
alkaline: entry.alkaline,
|
||||
};
|
||||
|
||||
addFoodToTable(foodData, entry.$id);
|
||||
|
||||
addFoodToView(foodData, entry.$id);
|
||||
});
|
||||
|
||||
if (entries.length === 0) {
|
||||
@@ -989,11 +825,8 @@ async function loadFoodEntries(date: Date) {
|
||||
`;
|
||||
}
|
||||
|
||||
// Setup Delete events
|
||||
addDeleteEvents();
|
||||
|
||||
// Update total calories
|
||||
updateTotalCalories(entries);
|
||||
setupEditAndDeleteEvents();
|
||||
updateTotalCalories();
|
||||
|
||||
// Update counters
|
||||
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
|
||||
async function handleDeleteFood(documentId: string) {
|
||||
if (!documentId) return;
|
||||
@@ -1101,7 +932,7 @@ async function handleDeleteFood(documentId: string) {
|
||||
const totalCalories = monthlyDocumentId?.totalCalories - existingToDelete;
|
||||
const monthlyCreated = await AppwriteDB.updateMonthlyCaloryForDay(monthlyDocumentId?.documentId, selectedDate, totalCalories);
|
||||
updateDocumentIdForToday(monthlyCreated.$id, monthlyCreated.totalCalories);
|
||||
await delay(1);
|
||||
await delay(QUICK_DELAY);
|
||||
}
|
||||
|
||||
selectDate(selectedDate);
|
||||
@@ -1113,8 +944,7 @@ async function handleDeleteFood(documentId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add food to table (existing function - update to use document ID)
|
||||
function addFoodToTable(foodData: FoodStorage, documentId: string) {
|
||||
function addFoodToView(foodData: FoodStorage, documentId: string) {
|
||||
// New Food card
|
||||
const container = getDivById('foodCardsContainer');
|
||||
const card = document.createElement('div');
|
||||
@@ -1125,6 +955,8 @@ function addFoodToTable(foodData: FoodStorage, documentId: string) {
|
||||
container.innerHTML = '';
|
||||
}
|
||||
|
||||
const foodFromDatabase = getFoodItemByName(foodData.name);
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="card-header card-header-toggle">
|
||||
<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>
|
||||
<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>
|
||||
<span class="muted">${getIcon(foodFromDatabase.info.category)} ${foodFromDatabase.info.category}</span>
|
||||
<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>
|
||||
@@ -1167,12 +1002,12 @@ function addFoodToTable(foodData: FoodStorage, documentId: string) {
|
||||
|
||||
container?.appendChild(card);
|
||||
|
||||
// Setup Delete events
|
||||
addDeleteEvents();
|
||||
setupEditAndDeleteEvents();
|
||||
}
|
||||
|
||||
// Update total calories
|
||||
function updateTotalCalories(entries: Models.DefaultDocument[]) {
|
||||
// Update total calories and display it in the counter
|
||||
// Also updates alkaline level percentage
|
||||
function updateTotalCalories() {
|
||||
const caloriesDiv = document.querySelectorAll('.calories-display');
|
||||
let total = 0;
|
||||
|
||||
|
||||
@@ -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: []
|
||||
};
|
||||
Reference in New Issue
Block a user