From 15fec548d5209b7c3a35d5c77239cb2fc173e634 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Thu, 12 Jun 2025 10:39:19 -0300 Subject: [PATCH] feat: add typescript --- HtmlUtil.ts | 38 +++++ foodDatabase.js | 74 --------- foodDatabase.ts | 69 +++++++++ index.html | 5 +- index.js | 349 ------------------------------------------ index.ts | 380 ++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 24 +++ package.json | 4 + types.ts | 16 ++ 9 files changed, 533 insertions(+), 426 deletions(-) create mode 100644 HtmlUtil.ts delete mode 100644 foodDatabase.js create mode 100644 foodDatabase.ts delete mode 100644 index.js create mode 100644 index.ts create mode 100644 types.ts diff --git a/HtmlUtil.ts b/HtmlUtil.ts new file mode 100644 index 0000000..74f1ab1 --- /dev/null +++ b/HtmlUtil.ts @@ -0,0 +1,38 @@ +export const getInputById = (elementId: string): HTMLInputElement => { + const el = document.getElementById(elementId) as HTMLInputElement; + if (!el) { + throw new Error(`Input Element id ${elementId} not found!`); + } + return el; +}; + +export const getDivById = (elementId: string): HTMLElement => { + const el = document.getElementById(elementId) as HTMLElement; + if (!el) { + throw new Error(`HTML Element id ${elementId} not found!`); + } + return el; +} + +export const getButtonById = (elementId: string): HTMLButtonElement => { + const el = document.getElementById(elementId) as HTMLButtonElement; + if (!el) { + throw new Error(`HTML Button Element id ${elementId} not found!`); + } + return el; +} + +export const getButtonListByClassName = (name: string): Array => { + const el = document.getElementsByClassName(name); + if (!el) { + throw new Error(`HTML Button List Elements not found for class ${name}!`); + } + return Array.from(el) as Array; +} + +export const showFoodPreview = (show: boolean) => { + if (!show) { + getDivById('food-preview').innerHTML = ''; + } + getDivById('food-preview').classList = `add-food-form-item ${show ? 'display-block' : 'display-none'}`; +} diff --git a/foodDatabase.js b/foodDatabase.js deleted file mode 100644 index c15f517..0000000 --- a/foodDatabase.js +++ /dev/null @@ -1,74 +0,0 @@ -export const foodDatabase = { - // Fruits - 'Frutas - Abacate': { calories: 96, protein: 1.2, fat: 8.4, carbs: 6, fiber: 6.3 }, - 'Frutas - Abacaxi': { calories: 48, protein: 0.9, fat: 0.1, carbs: 12.3, fiber: 1 }, - 'Frutas - Banana Nanica': { calories: 92, protein: 1.4, fat: 0.1, carbs: 23.8, fiber: 1.9 }, - 'Frutas - Laranja': { calories: 37, protein: 1, fat: 0.1, carbs: 8.9, fiber: 0.8 }, - 'Frutas - Maçã': { calories: 56, protein: 0.3, fat: 0, carbs: 15.2, fiber: 1.3 }, - 'Frutas - Manga Palmer': { calories: 72, protein: 0.4, fat: 0.2, carbs: 19.4, fiber: 1.6 }, - 'Frutas - Mamão': { calories: 40, protein: 0.5, fat: 0.1, carbs: 10.4, fiber: 1 }, - 'Frutas - Melancia': { calories: 33, protein: 0.9, fat: 0, carbs: 8.1, fiber: 0.1 }, - 'Frutas - Melão': { calories: 29, protein: 0.7, fat: 0, carbs: 7.5, fiber: 0.3 }, - 'Frutas - Uva': { calories: 53, protein: 0.7, fat: 0.2, carbs: 13.6, fiber: 0.9 }, - - // Vegetables - 'Vegetais - Agrião': { calories: 17, protein: 2.7, fat: 0.2, carbs: 2.3, fiber: 2.1 }, - 'Vegetais - Brócolis (cozido)': { calories: 25, protein: 2.1, fat: 0.5, carbs: 4.4, fiber: 3.4 }, - 'Vegetais - Cenoura (cozida)': { calories: 30, protein: 0.8, fat: 0.2, carbs: 6.7, fiber: 2.6 }, - 'Vegetais - Couve flor (cozida)': { calories: 19, protein: 1.2, fat: 0.3, carbs: 3.9, fiber: 2.1 }, - 'Vegetais - Ervilha (cozida)': { calories: 88, protein: 7.5, fat: 0.5, carbs: 14.2, fiber: 9.7 }, - 'Vegetais - Espinafre': { calories: 16, protein: 2, fat: 0.2, carbs: 2.6, fiber: 2.1 }, - 'Vegetais - Feijão preto (cozido)': { calories: 77, protein: 4.5, fat: 0.5, carbs: 14, fiber: 8.4 }, - 'Vegetais - Lentilha (cozida)': { calories: 93, protein: 6.3, fat: 0.5, carbs: 16.3, fiber: 7.9 }, - 'Vegetais - Mandioca (cozida)': { calories: 125, protein: 0.6, fat: 0.3, carbs: 30.1, fiber: 1.6 }, - 'Vegetais - Pepino': { calories: 10, protein: 0.9, fat: 0, carbs: 2, fiber: 1.1 }, - 'Vegetais - Rúcula': { calories: 13, protein: 1.8, fat: 0.1, carbs: 2.2, fiber: 1.7 }, - 'Vegetais - Spirulina': { calories: 290, protein: 57.5, fat: 23.9, carbs: 7.7, fiber: 0 }, - 'Vegetais - Tomate': { calories: 15, protein: 1.1, fat: 0.2, carbs: 3.1, fiber: 1.2 }, - - // Carbohydrates - 'Carboidratos - Batata doce (cozida)': { calories: 77, protein: 0.6, fat: 0.1, carbs: 18.4, fiber: 2.2 }, - 'Carboidratos - Batata doce (crua)': { calories: 118, protein: 1.3, fat: 0.1, carbs: 28.2, fiber: 2.6 }, - 'Carboidratos - Batata inglesa (cozida)': { calories: 52, protein: 1.2, fat: 0, carbs: 11.9, fiber: 1.3 }, - 'Carboidratos - Batata inglesa (crua)': { calories: 64, protein: 1.8, fat: 0, carbs: 14.7, fiber: 1.2 }, - 'Carboidratos - Pão Francês': { calories: 300, protein: 8, fat: 3.1, carbs: 58.6, fiber: 2.3 }, - 'Carboidrados - Polenta (cozida)': { calories: 192, protein: 4.8, fat: 1.2, carbs: 40, fiber: 3.8 }, - - // Proteins - 'Proteínas - Atum Sólido (natural)': { calories: 135, protein: 28.3, fat: 2.7, carbs: 0, fiber: 0 }, - 'Proteínas - Filé de Tilápia (cozido)': { calories: 128, protein: 26.1, fat: 2.6, carbs: 0, fiber: 0 }, - 'Proteínas - Ovo inteiro (cozido)': { calories: 146, protein: 13.3, fat: 9.5, carbs: 0.6, fiber: 0 }, - 'Proteínas - Ovo clara (cozido)': { calories: 59, protein: 13.4, fat: 0.1, carbs: 0, fiber: 0 }, - 'Proteínas - PTS (crua)': { calories: 288, protein: 52, fat: 0, carbs: 20, fiber: 14 }, - 'Proteínas - Patinho moído (grelhado)': { calories: 219, protein: 35.9, fat: 7.3, carbs: 0, fiber: 0 }, - 'Proteínas - Soja em Pó Growth': { calories: 400, protein: 86.7, fat: 5, carbs: 3.3, fiber: 0 }, - 'Proteínas - Sobrecoxa s/pele (cozida)': { calories: 245, protein: 24.9, fat: 15.4, carbs: 0, fiber: 0 }, - 'Proteínas - Tofu': { calories: 65, protein: 6.5, fat: 4, carbs: 2, fiber: 0.75 }, - - // Grains - 'Grãos - Arroz integral (cozido)': { calories: 130, protein: 2.7, fat: 0.3, carbs: 28, fiber: 0.4 }, - 'Grãos - Aveia': { calories: 394, protein: 13.9, fat: 8.5, carbs: 66.6, fiber: 9.1 }, - 'Grãos - Grão de Bico (cozido)': { calories: 180, protein: 9.5, fat: 3, carbs: 30, fiber: 8.6 }, - 'Grãos - Pão integral de forma': { calories: 253, protein: 9.4, fat: 3.7, carbs: 49.9, fiber: 6.9 }, - 'Grãos - Pipoca c/óleo': { calories: 448, protein: 9.9, fat: 15.9, carbs: 70.3, fiber: 14.3 }, - 'Grãos - Pipoca s/óleo': { calories: 178, protein: 3.3, fat: 7.2, carbs: 25, fiber: 4.3 }, - 'Grãos - Quinoa (cozida)': { calories: 158, protein: 5.55, fat: 2.5, carbs: 29, fiber: 2.5 }, - - // Nuts & Seeds - 'Castanhas - Amêndoas': { calories: 581, protein: 18.6, fat: 47.3, carbs: 29.5, fiber: 11.6 }, - 'Castanhas - Amendoin natural': { calories: 611, protein: 26, fat: 49, carbs: 16, fiber: 8.5 }, - 'Castanhas - Castanha de Caju': { calories: 570, protein: 18.5, fat: 46.3, carbs: 29.1, fiber: 3.7 }, - 'Castanhas - Castanha do Pará': { calories: 643, protein: 14.5, fat: 63.5, carbs: 15.1, fiber: 7.9 }, - 'Sementes - Semente de Chia': { calories: 490, protein: 15.6, fat: 30.7, carbs: 43.8, fiber: 37.7 }, - 'Sementes - Semente de Linhaça': { calories: 495, protein: 14.1, fat: 32.3, carbs: 43.3, fiber: 33.5 }, - 'Sementes - Semente de Gergelim': { calories: 584, protein: 21.2, fat: 50.4, carbs: 21.6, fiber: 11.9 }, - - // Snacks - 'Lanches - Barra de Proteína Vegana Growth': { calories: 400, protein: 25, fat: 22, carbs: 25, fiber: 8 }, - 'Lanches - Goiabada': { calories: 320, protein: 0, fat: 0, carbs: 80, fiber: 4.5 }, - 'Lanches - Mel Silvestre': { calories: 230, protein: 0, fat: 0, carbs: 65, fiber: 0 }, - 'Lanches - Melado': { calories: 297, protein: 0, fat: 0, carbs: 76.6, fiber: 0 }, - 'Lanches - Pasta de Amendoim Growth': { calories: 544, protein: 27, fat: 33, carbs: 20, fiber: 8 }, - 'Lanches - Goma de Tapioca': { calories: 226, protein: 0, fat: 0, carbs: 57, fiber: 0 }, - 'Lanches - 70% Nibs Garoto': { calories: 544, protein: 8.4, fat: 40, carbs: 34, fiber: 0 } -}; diff --git a/foodDatabase.ts b/foodDatabase.ts new file mode 100644 index 0000000..6db744b --- /dev/null +++ b/foodDatabase.ts @@ -0,0 +1,69 @@ +import { FoodItem } from "./types"; + +export const foodDatabase: FoodItem[] = [ + { name: 'Frutas - Abacate', info: { calories: 96, protein: 1.2, fat: 8.4, carbs: 6, fiber: 6.3 } }, + { name: 'Frutas - Abacaxi', info: { calories: 48, protein: 0.9, fat: 0.1, carbs: 12.3, fiber: 1 } }, + { name: 'Frutas - Banana Nanica', info: { calories: 92, protein: 1.4, fat: 0.1, carbs: 23.8, fiber: 1.9 } }, + { name: 'Frutas - Laranja', info: { calories: 37, protein: 1, fat: 0.1, carbs: 8.9, fiber: 0.8 } }, + { name: 'Frutas - Maçã', info: { calories: 56, protein: 0.3, fat: 0, carbs: 15.2, fiber: 1.3 } }, + { name: 'Frutas - Manga Palmer', info: { calories: 72, protein: 0.4, fat: 0.2, carbs: 19.4, fiber: 1.6 } }, + { name: 'Frutas - Mamão', info: { calories: 40, protein: 0.5, fat: 0.1, carbs: 10.4, fiber: 1 } }, + { name: 'Frutas - Melancia', info: { calories: 33, protein: 0.9, fat: 0, carbs: 8.1, fiber: 0.1 } }, + { 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: 'Carboidratos - Batata doce (cozida)', info: { calories: 77, protein: 0.6, fat: 0.1, carbs: 18.4, fiber: 2.2 } }, + { name: 'Carboidratos - Batata doce (crua)', info: { calories: 118, protein: 1.3, fat: 0.1, carbs: 28.2, fiber: 2.6 } }, + { 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 - 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 } }, + + { name: 'Proteínas - Atum Sólido (natural)', info: { calories: 135, protein: 28.3, fat: 2.7, carbs: 0, fiber: 0 } }, + { name: 'Proteínas - Filé de Tilápia (cozido)', info: { calories: 128, protein: 26.1, fat: 2.6, carbs: 0, fiber: 0 } }, + { name: 'Proteínas - Ovo inteiro (cozido)', info: { calories: 146, protein: 13.3, fat: 9.5, carbs: 0.6, fiber: 0 } }, + { name: 'Proteínas - Ovo clara (cozido)', info: { calories: 59, protein: 13.4, fat: 0.1, carbs: 0, fiber: 0 } }, + { name: 'Proteínas - PTS (crua)', info: { calories: 288, protein: 52, fat: 0, carbs: 20, fiber: 14 } }, + { name: 'Proteínas - Patinho moído (grelhado)', info: { calories: 219, protein: 35.9, fat: 7.3, carbs: 0, fiber: 0 } }, + { 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 - 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 } }, + { name: 'Grãos - Pipoca s/óleo', info: { calories: 178, protein: 3.3, fat: 7.2, carbs: 25, fiber: 4.3 } }, + { name: 'Grãos - Quinoa (cozida)', info: { calories: 158, protein: 5.55, fat: 2.5, carbs: 29, fiber: 2.5 } }, + + { name: 'Castanhas - Amêndoas', info: { calories: 581, protein: 18.6, fat: 47.3, carbs: 29.5, fiber: 11.6 } }, + { 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 } }, + + { name: 'Lanches - Barra de Proteína Vegana Growth', info: { calories: 400, protein: 25, fat: 22, carbs: 25, fiber: 8 } }, + { name: 'Lanches - Goiabada', info: { calories: 320, protein: 0, fat: 0, carbs: 80, fiber: 4.5 } }, + { name: 'Lanches - Mel Silvestre', info: { calories: 230, protein: 0, fat: 0, carbs: 65, fiber: 0 } }, + { 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 } } +]; diff --git a/index.html b/index.html index 2c19241..9238570 100644 --- a/index.html +++ b/index.html @@ -51,8 +51,7 @@ - @@ -93,6 +92,6 @@ - + diff --git a/index.js b/index.js deleted file mode 100644 index addd93c..0000000 --- a/index.js +++ /dev/null @@ -1,349 +0,0 @@ -import { foodDatabase } from './foodDatabase.js'; - -// App state -let selectedDate = new Date().toISOString().split('T')[0]; -let currentViewDate = new Date(); - -// Initialize the app -function init() { - populateFoodSelect(); - updateCurrentDate(); - loadDayData(selectedDate); - renderCalendar(); - addEvents(); -} - -function getFoodProportion(grams, foodData) { - const multiplier = grams / 100; // Database values are per 100g - return { - calories: Math.round(foodData.calories * multiplier), - protein: Math.round(foodData.protein * multiplier * 10) / 10, - fat: Math.round(foodData.fat * multiplier * 10) / 10, - carbs: Math.round(foodData.carbs * multiplier * 10) / 10, - fiber: Math.round(foodData.fiber * multiplier * 10) / 10 - }; -} - -function previewCalories() { - const foodSelect = document.getElementById('foodSelect'); - const gramAmount = document.getElementById('gramAmount'); - - if (!foodSelect.value) { - document.getElementById('food-preview').innerHTML = ''; - document.getElementById('food-preview').classList = 'add-food-form-item display-none'; - return; - } - - let grams = 100; - if (gramAmount && gramAmount.value && parseFloat(gramAmount.value) > 0) { - grams = parseFloat(gramAmount.value); - } - - const foodName = foodSelect.value; - const foodData = foodDatabase[foodName]; - const proportion = getFoodProportion(grams, foodData); - - const textArray = []; - textArray.push(`Calories in ${grams}g: ${proportion.calories}; `); - textArray.push(`Protein: ${proportion.protein}g; `); - textArray.push(`Fat: ${proportion.fat}g; `); - textArray.push(`Carbohydrates: ${proportion.carbs}g; `); - textArray.push(`Fiber: ${proportion.fiber}g;`); - - document.getElementById('food-preview').innerHTML = textArray.join(''); - document.getElementById('food-preview').classList = 'add-food-form-item display-block'; -} - -function addEvents() { - document.getElementById('foodSelect').addEventListener('change', () => { - previewCalories(); - }); - - document.getElementById('gramAmount').addEventListener('change', () => { - previewCalories(); - }); - - document.getElementById('add-food-btn').addEventListener('click', () => { - addFood(); - }); - - document.getElementById('next-month-btn').addEventListener('click', () => { - nextMonth(); - }); - - document.getElementById('previous-month-btn').addEventListener('click', () => { - previousMonth(); - }); -} - -function addDeleteEvents() { - const elements = document.getElementsByClassName('delete-food-entry'); - Array.from(elements).forEach(el => { - el.addEventListener('click', (e) => { - const row = e.target.closest('tr'); - const entryId = row.querySelector('.hidden-column').textContent; - deleteEntry(selectedDate, entryId); - }); - }); -} - -// Populate food select dropdown -function populateFoodSelect() { - const select = document.getElementById('foodSelect'); - select.innerHTML = ''; - - const sortedFoods = Object.keys(foodDatabase).sort(); - sortedFoods.forEach(food => { - const option = document.createElement('option'); - option.value = food; - option.textContent = food.charAt(0).toUpperCase() + food.slice(1); - select.appendChild(option); - }); -} - -// Update current date display -function updateCurrentDate() { - const date = new Date(selectedDate); - const options = { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric' - }; - document.getElementById('currentDate').textContent = date.toLocaleDateString('en-US', options); -} - -// Add food to the log -function addFood() { - const foodSelect = document.getElementById('foodSelect'); - const gramAmount = document.getElementById('gramAmount'); - - if (!foodSelect.value || !gramAmount.value) { - alert('Please select a food item and enter amount'); - return; - } - - const foodName = foodSelect.value; - const grams = parseFloat(gramAmount.value); - const foodData = foodDatabase[foodName]; - - // Calculate nutrition for the amount - const proportion = getFoodProportion(grams, foodData); - const entry = { - id: Date.now(), - name: foodName, - grams: grams, - calories: proportion.calories, - protein: proportion.protein, - fat: proportion.fat, - carbs: proportion.carbs, - fiber: proportion.fiber, - time: new Date().toLocaleTimeString('en-US', { - hour12: false, - hour: '2-digit', - minute: '2-digit' - }) - }; - - // Save to localStorage - const dayData = getDayData(selectedDate); - dayData.push(entry); - saveDayData(selectedDate, dayData); - - // Update display - loadDayData(selectedDate); - renderCalendar(); - - // Reset form - foodSelect.value = ''; - gramAmount.value = '100'; - document.getElementById('food-preview').innerHTML = ''; - document.getElementById('food-preview').classList = 'add-food-form-item display-none'; -} - -// Get day data from localStorage -function getDayData(date) { - const key = `calories_${date}`; - return JSON.parse(localStorage.getItem(key)) || []; -} - -// Save day data to localStorage -function saveDayData(date, data) { - const key = `calories_${date}`; - localStorage.setItem(key, JSON.stringify(data)); -} - -// Load and display day data -function loadDayData(date) { - const dayData = getDayData(date); - - // Update counters - let totalCalories = 0; - let totalProtein = 0; - let totalFat = 0; - let totalCarbs = 0; - let totalFiber = 0; - - dayData.forEach(entry => { - totalCalories += entry.calories; - totalProtein += entry.protein; - totalFat += entry.fat; - totalCarbs += entry.carbs; - totalFiber += entry.fiber; - }); - - document.getElementById('caloriesCounter').textContent = totalCalories; - document.getElementById('proteinValue').textContent = Math.round(totalProtein * 10) / 10; - document.getElementById('fatValue').textContent = Math.round(totalFat * 10) / 10; - document.getElementById('carboValue').textContent = Math.round(totalCarbs * 10) / 10; - document.getElementById('fiberValue').textContent = Math.round(totalFiber * 10) / 10; - - // Update table - const tbody = document.getElementById('foodTableBody'); - tbody.innerHTML = ''; - - dayData.forEach(entry => { - const row = document.createElement('tr'); - row.innerHTML = ` - ${entry.id} - ${entry.time} - ${entry.name} - ${entry.grams} - ${entry.calories} - ${entry.protein} - ${entry.fat} - ${entry.carbs} - ${entry.fiber} - - `; - tbody.appendChild(row); - }); - addDeleteEvents(); -} - -// Delete an entry -function deleteEntry(date, entryId) { - if (confirm('Are you sure you want to delete this entry?')) { - let dayData = getDayData(date); - dayData = dayData.filter(entry => entry.id !== parseInt(entryId)); - saveDayData(date, dayData); - loadDayData(selectedDate); - renderCalendar(); - } -} - -// Calendar functions -function previousMonth() { - currentViewDate.setMonth(currentViewDate.getMonth() - 1); - renderCalendar(); -} - -function nextMonth() { - currentViewDate.setMonth(currentViewDate.getMonth() + 1); - renderCalendar(); -} - -function selectDate(date) { - selectedDate = date; - updateCurrentDate(); - loadDayData(selectedDate); - renderCalendar(); -} - -function renderCalendar() { - const year = currentViewDate.getFullYear(); - const month = currentViewDate.getMonth(); - - // Update calendar title - const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December']; - document.getElementById('calendarTitle').textContent = `${monthNames[month]} ${year}`; - - // Clear calendar - const grid = document.getElementById('calendarGrid'); - grid.innerHTML = ''; - - // Add day headers - const dayHeaders = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - dayHeaders.forEach(day => { - const header = document.createElement('div'); - header.className = 'calendar-day-header'; - header.textContent = day; - grid.appendChild(header); - }); - - // Get first day of month and number of days - const firstDay = new Date(year, month, 1); - const lastDay = new Date(year, month + 1, 0); - const daysInMonth = lastDay.getDate(); - const startingDayOfWeek = firstDay.getDay(); - - // Add empty cells for previous month - const prevMonth = new Date(year, month, 0); - const daysInPrevMonth = prevMonth.getDate(); - - for (let i = startingDayOfWeek - 1; i >= 0; i--) { - const day = daysInPrevMonth - i; - const dayElement = createDayElement(day, true, year, month - 1); - grid.appendChild(dayElement); - } - - // Add days of current month - for (let day = 1; day <= daysInMonth; day++) { - const dayElement = createDayElement(day, false, year, month); - grid.appendChild(dayElement); - } - - // Add days from next month to fill the grid - const totalCells = grid.children.length - 7; - const remainingCells = 42 - totalCells; - - for (let day = 1; day <= remainingCells; day++) { - const dayElement = createDayElement(day, true, year, month + 1); - grid.appendChild(dayElement); - } -} - -function createDayElement(day, isOtherMonth, year, month) { - const dayElement = document.createElement('div'); - dayElement.className = 'calendar-day'; - - const dayDate = new Date(year, month, day); - const dateString = dayDate.toISOString().split('T')[0]; - - if (isOtherMonth) { - dayElement.classList.add('other-month'); - } else { - dayElement.onclick = () => selectDate(dateString); - } - - // Check if this is today - const today = new Date().toISOString().split('T')[0]; - if (!isOtherMonth && dateString === today) { - dayElement.classList.add('today'); - } - - // Check if this is selected date - if (!isOtherMonth && dateString === selectedDate) { - dayElement.classList.add('selected'); - } - - // Check if this day has data - const dayData = getDayData(dateString); - if (dayData.length > 0) { - dayElement.classList.add('has-data'); - const totalCalories = dayData.reduce((sum, entry) => sum + entry.calories, 0); - - dayElement.innerHTML = ` -
${day}
-
${totalCalories} cal
- `; - } else { - dayElement.textContent = day; - } - - return dayElement; -} - -// Initialize when page loads -document.addEventListener('DOMContentLoaded', init); diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..94aca95 --- /dev/null +++ b/index.ts @@ -0,0 +1,380 @@ +import { foodDatabase } from './foodDatabase.js'; +import { getButtonById, getButtonListByClassName, getDivById, getInputById, showFoodPreview } from './HtmlUtil.js'; +import { FoodItem, FoodStorage } from './types.js'; + +// App state +let selectedDate = new Date().toISOString().split('T')[0]; +let currentViewDate = new Date(); + +// Initialize the app +const init = () => { + populateFoodSelect(); + updateCurrentDate(); + loadDayData(selectedDate); + renderCalendar(); + addEvents(); +} + +const getFoodProportion = (grams: number, foodData: FoodItem): FoodItem => { + const multiplier = grams / 100; // Database values are per 100g + return { + ...foodData, + info: { + calories: Math.round(foodData.info.calories * multiplier), + protein: Math.round(foodData.info.protein * multiplier * 10) / 10, + fat: Math.round(foodData.info.fat * multiplier * 10) / 10, + carbs: Math.round(foodData.info.carbs * multiplier * 10) / 10, + fiber: Math.round(foodData.info.fiber * multiplier * 10) / 10 + } + }; +} + +const getFoodItemByName = (foodName: string): FoodItem => { + const foodDataSearch: FoodItem[] = foodDatabase.filter(f => f.name === foodName); + if (foodDataSearch.length === 0) { + throw new Error(`Food not found for name ${name}`); + } + return foodDataSearch[0]; +} + +const previewCalories = () => { + const foodSelect = getInputById('foodSelect'); + const gramAmount = getInputById('gramAmount'); + + if (!foodSelect.value) { + showFoodPreview(false); + return; + } + + let grams = 100; + if (gramAmount && gramAmount.value && parseFloat(gramAmount.value) > 0) { + grams = parseFloat(gramAmount.value); + } + + const foodData: FoodItem = getFoodItemByName(foodSelect.value); + const proportion = getFoodProportion(grams, foodData); + + const textArray: string[] = []; + textArray.push(`Calories in ${grams}g: ${proportion.info.calories}; `); + textArray.push(`Protein: ${proportion.info.protein}g; `); + textArray.push(`Fat: ${proportion.info.fat}g; `); + textArray.push(`Carbohydrates: ${proportion.info.carbs}g; `); + textArray.push(`Fiber: ${proportion.info.fiber}g;`); + + getDivById('food-preview').innerHTML = textArray.join(''); + showFoodPreview(true); +} + +const addEvents = () => { + getInputById('foodSelect').addEventListener('change', () => { + previewCalories(); + }); + + getInputById('gramAmount')?.addEventListener('change', () => { + previewCalories(); + }); + + getButtonById('add-food-btn').addEventListener('click', () => { + addFood(); + }); + + getButtonById('next-month-btn').addEventListener('click', () => { + nextMonth(); + }); + + getButtonById('previous-month-btn').addEventListener('click', () => { + previousMonth(); + }); +} + +const addDeleteEvents = () => { + const elements = getButtonListByClassName('delete-food-entry'); + Array.from(elements).forEach((el: HTMLButtonElement) => { + el.addEventListener('click', (e) => { + const target = e.target as HTMLElement; + const row = target.closest('tr'); + const entryId = row?.querySelector('.hidden-column')?.textContent; + const entryIdNumber = entryId ? parseInt(entryId) : 0; + if (entryIdNumber) { + deleteEntry(selectedDate, entryIdNumber); + } + }); + }); +} + +// Populate food select dropdown +const populateFoodSelect = () => { + const select = getInputById('foodSelect'); + select.innerHTML = ''; + + const sortedFoods = foodDatabase.sort((f1, f2) => f1.name.localeCompare(f2.name)); + sortedFoods.forEach((food: FoodItem) => { + const option = document.createElement('option') as HTMLOptionElement; + option.value = food.name; + option.textContent = food.name; + select.appendChild(option); + }); +} + +// Update current date display +const updateCurrentDate = () => { + const date = new Date(selectedDate); + getDivById('currentDate').textContent = date.toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + }); +} + +// Add food to the log +const addFood = () => { + const foodSelect = getInputById('foodSelect'); + const gramAmount = getInputById('gramAmount'); + + if (!foodSelect.value || !gramAmount.value) { + alert('Please select a food item and enter amount'); + return; + } + + const grams: number = parseFloat(gramAmount.value); + const foodData: FoodItem = getFoodItemByName(foodSelect.value); + + // Calculate nutrition for the amount + const proportion: FoodItem = getFoodProportion(grams, foodData); + const entry: FoodStorage = { + id: Date.now(), + name: foodSelect.value, + grams: grams, + calories: proportion.info.calories, + protein: proportion.info.protein, + fat: proportion.info.fat, + carbs: proportion.info.carbs, + fiber: proportion.info.fiber, + time: new Date().toLocaleTimeString('en-US', { + hour12: false, + hour: '2-digit', + minute: '2-digit' + }) + }; + + // Save to localStorage + const dayData = getDayData(selectedDate); + dayData.push(entry); + saveDayData(selectedDate, dayData); + + // Update display + loadDayData(selectedDate); + renderCalendar(); + + // Reset form + foodSelect.value = ''; + gramAmount.value = '100'; + showFoodPreview(false); +} + +// Get day data from localStorage +const getDayData = (date: string): FoodStorage[] => { + const key = `calories_${date}`; + const saved = localStorage.getItem(key); + if (!saved) { + return []; + } + return JSON.parse(saved); +} + +// Save day data to localStorage +const saveDayData = (date: string, data: FoodStorage[]) => { + const key = `calories_${date}`; + localStorage.setItem(key, JSON.stringify(data)); +} + +// Load and display day data +const loadDayData = (date: string) => { + const dayData = getDayData(date); + + // Update counters + let totalCalories = 0; + let totalProtein = 0; + let totalFat = 0; + let totalCarbs = 0; + let totalFiber = 0; + + dayData.forEach(entry => { + totalCalories += entry.calories; + totalProtein += entry.protein; + totalFat += entry.fat; + totalCarbs += entry.carbs; + totalFiber += entry.fiber; + }); + + getDivById('caloriesCounter').textContent = totalCalories.toString(); + 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 (); + + // Update table + const tbody = document.getElementById('foodTableBody'); + if (!tbody) { + throw Error('Unable to create row tables!'); + } + tbody.innerHTML = ''; + + dayData.forEach(entry => { + const row = document.createElement('tr'); + row.innerHTML = ` + ${entry.id} + ${entry.time} + ${entry.name} + ${entry.grams} + ${entry.calories} + ${entry.protein} + ${entry.fat} + ${entry.carbs} + ${entry.fiber} + + `; + tbody.appendChild(row); + }); + addDeleteEvents(); +} + +// Delete an entry +const deleteEntry = (date: string, entryId: number) => { + if (confirm('Are you sure you want to delete this entry?')) { + let dayData: FoodStorage[] = getDayData(date); + dayData = dayData.filter(entry => entry.id !== entryId); + saveDayData(date, dayData); + loadDayData(selectedDate); + renderCalendar(); + } +} + +// Calendar functions +const previousMonth = () => { + currentViewDate.setMonth(currentViewDate.getMonth() - 1); + renderCalendar(); +} + +const nextMonth = () => { + currentViewDate.setMonth(currentViewDate.getMonth() + 1); + renderCalendar(); +} + +const selectDate = (date: string) => { + selectedDate = date; + updateCurrentDate(); + loadDayData(selectedDate); + renderCalendar(); +} + +const renderCalendar = () => { + const year = currentViewDate.getFullYear(); + const month = currentViewDate.getMonth(); + + // Update calendar title + const monthNames = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + getDivById('calendarTitle').textContent = `${monthNames[month]} ${year}`; + + // Clear calendar + const grid = getDivById('calendarGrid'); + grid.innerHTML = ''; + + // Add day headers + const dayHeaders = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + dayHeaders.forEach(day => { + const header = document.createElement('div'); + header.className = 'calendar-day-header'; + header.textContent = day; + grid.appendChild(header); + }); + + // Get first day of month and number of days + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + const daysInMonth = lastDay.getDate(); + const startingDayOfWeek = firstDay.getDay(); + + // Add empty cells for previous month + const prevMonth = new Date(year, month, 0); + const daysInPrevMonth = prevMonth.getDate(); + + for (let i = startingDayOfWeek - 1; i >= 0; i--) { + const day = daysInPrevMonth - i; + const dayElement = createDayElement(day, true, year, month - 1); + grid.appendChild(dayElement); + } + + // Add days of current month + for (let day = 1; day <= daysInMonth; day++) { + const dayElement = createDayElement(day, false, year, month); + grid.appendChild(dayElement); + } + + // Add days from next month to fill the grid + const totalCells = grid.children.length - 7; + const remainingCells = 42 - totalCells; + + for (let day = 1; day <= remainingCells; day++) { + const dayElement = createDayElement(day, true, year, month + 1); + grid.appendChild(dayElement); + } +} + +function createDayElement(day: number, isOtherMonth: boolean, year: number, month: number) { + const dayElement = document.createElement('div') as HTMLElement; + dayElement.className = 'calendar-day'; + + const dayDate = new Date(year, month, day); + const dateString = dayDate.toISOString().split('T')[0]; + + if (isOtherMonth) { + dayElement.classList.add('other-month'); + } else { + dayElement.onclick = () => selectDate(dateString); + } + + // Check if this is today + const today = new Date().toISOString().split('T')[0]; + if (!isOtherMonth && dateString === today) { + dayElement.classList.add('today'); + } + + // Check if this is selected date + if (!isOtherMonth && dateString === selectedDate) { + dayElement.classList.add('selected'); + } + + // Check if this day has data + const dayData = getDayData(dateString); + if (dayData.length > 0) { + dayElement.classList.add('has-data'); + const totalCalories = dayData.reduce((sum, entry) => sum + entry.calories, 0); + + dayElement.innerHTML = ` +
${day}
+
${totalCalories} cal
+ `; + } else { + dayElement.textContent = day.toString(); + } + + return dayElement; +} + +// Initialize when page loads +document.addEventListener('DOMContentLoaded', init); diff --git a/package-lock.json b/package-lock.json index 3def26f..38a3af2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,11 @@ "name": "calories-tracker", "version": "1.0.0", "license": "ISC", + "dependencies": { + "appwrite": "^18.1.1" + }, "devDependencies": { + "typescript": "^5.8.3", "vite": "^6.3.5" } }, @@ -724,6 +728,12 @@ "dev": true, "license": "MIT" }, + "node_modules/appwrite": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-18.1.1.tgz", + "integrity": "sha512-krwHjuwJcF+9Ig2+nqOEKMA/5kPIFhwwZsaLc7Gb8y2oP6EnG4ZMRPeHTFscdevOtVQj2Ax92cYYWAEvzlrc7A==", + "license": "BSD-3-Clause" + }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -930,6 +940,20 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", diff --git a/package.json b/package.json index 2190a29..cc0cd32 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,10 @@ "license": "ISC", "description": "", "devDependencies": { + "typescript": "^5.8.3", "vite": "^6.3.5" + }, + "dependencies": { + "appwrite": "^18.1.1" } } diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..a5b91e0 --- /dev/null +++ b/types.ts @@ -0,0 +1,16 @@ +export type FoodItem = { + name: string; + info: { calories: number, protein: number, fat: number, carbs: number, fiber: number } +} + +export type FoodStorage = { + id: number; + name: string; + grams: number; + calories: number; + protein: number; + fat: number; + carbs: number; + fiber: number; + time: string; +};