feat: add typescript

This commit is contained in:
2025-06-12 10:39:19 -03:00
parent f6f3c77c61
commit 15fec548d5
9 changed files with 533 additions and 426 deletions
+38
View File
@@ -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<HTMLButtonElement> => {
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<HTMLButtonElement>;
}
export const showFoodPreview = (show: boolean) => {
if (!show) {
getDivById('food-preview').innerHTML = '';
}
getDivById('food-preview').classList = `add-food-form-item ${show ? 'display-block' : 'display-none'}`;
}
-74
View File
@@ -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 }
};
+69
View File
@@ -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 } }
];
+2 -3
View File
@@ -51,8 +51,7 @@
</div>
<button class="add-btn" id="add-food-btn">Add Food</button>
</div>
<div id="food-preview" class="add-food-form-item display-none">
<p>Calories: 0 - Protein: 0 - Fat: 0 - Carbohydrates: 0</p>
<div class="add-food-form-item display-none" id="food-preview">
</div>
</div>
@@ -93,6 +92,6 @@
</div>
</div>
<script type="module" src="index.js"></script>
<script type="module" src="index.ts"></script>
</body>
</html>
-349
View File
@@ -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 = '<option value="">Select a food item...</option>';
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 = `
<td class="hidden-column">${entry.id}</td>
<td>${entry.time}</td>
<td>${entry.name}</td>
<td>${entry.grams}</td>
<td>${entry.calories}</td>
<td>${entry.protein}</td>
<td>${entry.fat}</td>
<td>${entry.carbs}</td>
<td>${entry.fiber}</td>
<td><button class="delete-btn delete-food-entry">Delete</button></td>
`;
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 = `
<div>${day}</div>
<div class="day-calories">${totalCalories} cal</div>
`;
} else {
dayElement.textContent = day;
}
return dayElement;
}
// Initialize when page loads
document.addEventListener('DOMContentLoaded', init);
+380
View File
@@ -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 = '<option value="">Select a food item...</option>';
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 = `
<td class="hidden-column">${entry.id}</td>
<td>${entry.time}</td>
<td>${entry.name}</td>
<td>${entry.grams}</td>
<td>${entry.calories}</td>
<td>${entry.protein}</td>
<td>${entry.fat}</td>
<td>${entry.carbs}</td>
<td>${entry.fiber}</td>
<td><button class="delete-btn delete-food-entry">Delete</button></td>
`;
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 = `
<div>${day}</div>
<div class="day-calories">${totalCalories} cal</div>
`;
} else {
dayElement.textContent = day.toString();
}
return dayElement;
}
// Initialize when page loads
document.addEventListener('DOMContentLoaded', init);
+24
View File
@@ -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",
+4
View File
@@ -12,6 +12,10 @@
"license": "ISC",
"description": "",
"devDependencies": {
"typescript": "^5.8.3",
"vite": "^6.3.5"
},
"dependencies": {
"appwrite": "^18.1.1"
}
}
+16
View File
@@ -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;
};