diff --git a/index.html b/index.html
index c4f5700..89b3cf4 100644
--- a/index.html
+++ b/index.html
@@ -164,6 +164,7 @@
+
diff --git a/src/.env.example b/src/.env.example
index 0493e4a..ed5e221 100644
--- a/src/.env.example
+++ b/src/.env.example
@@ -1,4 +1,5 @@
VITE_APPWRITE_ENDPOINT=here
VITE_APPWRITE_DBID=here
VITE_APPWRITE_FOODENTRIESID=here
-VITE_APPWRITE_USERSETTINGSID=here
\ No newline at end of file
+VITE_APPWRITE_USERSETTINGSID=here
+VITE_APPWRITE_MONTLYCALORIESID=here
\ No newline at end of file
diff --git a/src/appwrite.ts b/src/appwrite.ts
index 3dee252..707b38c 100644
--- a/src/appwrite.ts
+++ b/src/appwrite.ts
@@ -1,5 +1,5 @@
import { Client, Account, Databases, Query } from 'appwrite';
-import { FoodStorage, UserSettings } from './types';
+import { DailyTotalCalories, FoodStorage, UserSettings } from './types';
// Initialize Appwrite client
const client = new Client();
@@ -16,6 +16,7 @@ export const databases = new Databases(client);
export const DATABASE_ID = import.meta.env.VITE_APPWRITE_DBID
export const FOOD_ENTRIES_COLLECTION_ID = import.meta.env.VITE_APPWRITE_FOODENTRIESID
export const USER_SETTINGS_COLLECTION_ID = import.meta.env.VITE_APPWRITE_USERSETTINGSID
+export const MONTHLY_CALORIES_COLLECTION_ID = import.meta.env.VITE_APPWRITE_MONTLYCALORIESID
// Auth helper functions
export class AppwriteAuth {
@@ -160,6 +161,72 @@ export class AppwriteDB {
}
}
+ // Get all calories for a particular date
+ static async getMonthlyCalories(date: Date) {
+ try {
+ const user = await account.get();
+ const localDateTime = new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString();
+
+ const response = await databases.listDocuments(
+ DATABASE_ID,
+ MONTHLY_CALORIES_COLLECTION_ID,
+ [
+ Query.equal('userId', user.$id),
+ Query.startsWith('date', localDateTime.substring(0, 7)),
+ ]
+ );
+ console.debug('Food entries retrieved:', response);
+ return response.documents;
+ } catch (error) {
+ console.error('Get all calories for month error:', error);
+ throw error;
+ }
+ }
+
+ static async createMonthlyCaloryForDay(date: Date, totalCalories: number) {
+ try {
+ const localDateTime = new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString();
+
+ const response = await databases.createDocument(
+ DATABASE_ID,
+ MONTHLY_CALORIES_COLLECTION_ID ,
+ 'unique()',
+ {
+ userId: (await account.get()).$id,
+ date: localDateTime.substring(0, 10),
+ totalCalories: totalCalories
+ }
+ );
+ console.debug('User settings saved:', response);
+ return response;
+ } catch (error) {
+ console.error('Save user settings error:', error);
+ throw error;
+ }
+ }
+
+ static async updateMonthlyCaloryForDay(id: string, date: Date, totalCalories: number) {
+ try {
+ const localDateTime = new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString();
+
+ const response = await databases.updateDocument(
+ DATABASE_ID,
+ MONTHLY_CALORIES_COLLECTION_ID ,
+ id,
+ {
+ userId: (await account.get()).$id,
+ date: localDateTime.substring(0, 10),
+ totalCalories: totalCalories
+ }
+ );
+ console.debug('User settings saved:', response);
+ return response;
+ } catch (error) {
+ console.error('Save user settings error:', error);
+ throw error;
+ }
+ }
+
// get user's settings
static async getUserSettings() {
try {
diff --git a/src/index.ts b/src/index.ts
index cd94e58..4802743 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,6 @@
import { foodDatabase } from './foodDatabase.js';
import { getButtonById, getButtonListByClassName, getDivById, getInputById, showFoodPreview } from './HtmlUtil.ts';
-import { FoodItem, FoodStorage } from './types.js';
+import { DailyTotalCalories, FoodItem, FoodStorage } from './types.js';
import { AppwriteAuth, AppwriteDB } from './appwrite.js';
import swal from 'sweetalert';
import { Models } from 'appwrite';
@@ -12,6 +12,7 @@ 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;
@@ -45,7 +46,7 @@ async function initializeAuth() {
if (isLoggedIn) {
currentUser = await AppwriteAuth.getCurrentUser();
- showMainApp();
+ await showMainApp();
selectDate(selectedDate);
} else {
showAuthForms();
@@ -87,7 +88,7 @@ async function handleRegister(e: SubmitEvent) {
await AppwriteAuth.login(email, password);
currentUser = await AppwriteAuth.getCurrentUser();
- showMainApp();
+ await showMainApp();
selectDate(selectedDate);
swal('Registration successful! Welcome to Food Tracker.');
@@ -111,7 +112,7 @@ async function handleLogin(e: SubmitEvent) {
await AppwriteAuth.login(email, password);
currentUser = await AppwriteAuth.getCurrentUser();
- showMainApp();
+ await showMainApp();
selectDate(selectedDate);
} catch (error) {
@@ -300,7 +301,7 @@ function showAuthForms() {
appContent?.classList.add('hidden');
}
-function showMainApp() {
+async function showMainApp() {
authSection?.classList.add('hidden');
userInfo?.classList.remove('hidden');
appContent?.classList.remove('hidden');
@@ -312,6 +313,14 @@ function showMainApp() {
// Update date display
updateCurrentDate();
+
+ // Get entries with total
+ const entries = await AppwriteDB.getMonthlyCalories(selectedDate);
+ entries.forEach((entry) => {
+ const calories = parseInt(entry.totalCalories);
+ const day = parseInt(entry.date.substring(8));
+ updateDocumentIdForDay(entry.$id, calories, day);
+ });
}
// Show/hide loading overlay
@@ -645,6 +654,7 @@ async function setFoodToEdit(foodId: string) {
getButtonById('add-food-btn').innerHTML = 'Save Food';
getInputById('foodIdToUpdate').value = foodId;
getInputById('foodTimeToUpdate').value = foodToEdit[0].time;
+ getInputById('foodCaloriesToUpdate').value = foodToEdit[0].calories;
hideLoading();
} catch (error) {
@@ -772,6 +782,19 @@ const updateFood = async () => {
// Save to Appwrite
await AppwriteDB.updateFoodEntry(foodId, entry);
+
+ // Save or update monthly total calories
+ const monthlyDocumentId = getDocumentIdForToday();
+ if (!monthlyDocumentId?.documentId) {
+ const monthlyCreated = await AppwriteDB.createMonthlyCaloryForDay(selectedDate, entry.calories);
+ updateDocumentIdForToday(monthlyCreated.$id, monthlyCreated.totalCalories);
+ } else {
+ const gramsToRemove = parseInt(getInputById('foodCaloriesToUpdate').value);
+ const totalCalories = monthlyDocumentId?.totalCalories + entry.calories - gramsToRemove;
+ const monthlyUpdated = await AppwriteDB.updateMonthlyCaloryForDay(monthlyDocumentId?.documentId, selectedDate, totalCalories);
+ updateDocumentIdForToday(monthlyUpdated.$id, monthlyUpdated.totalCalories);
+ }
+ delay(1);
clearEditing();
selectDate(selectedDate);
} catch (error) {
@@ -782,6 +805,43 @@ const updateFood = async () => {
}
}
+const getDocumentIdForToday = (): DailyTotalCalories | null => {
+ const today = selectedDate.getDate();
+ const todayRecord = calendarMonthlyCalories.filter(x => x.day === today);
+ if (todayRecord.length > 0) {
+ return todayRecord[0];
+ }
+ return null;
+}
+
+const getDocumentIdForDay = (day: number): number => {
+ const todayRecord = calendarMonthlyCalories.filter(x => x.day === day);
+ return todayRecord.length > 0 ? todayRecord[0].totalCalories : 0;
+}
+
+const updateDocumentIdForToday = (id: string, totalCalories: number): void => {
+ const today = selectedDate.getDate();
+ updateDocumentIdForDay(id, totalCalories, today);
+}
+
+const updateDocumentIdForDay = (id: string, totalCalories: number, day: number): void => {
+ const record = calendarMonthlyCalories.filter(x => x.day === day);
+ if (record.length === 0) {
+ calendarMonthlyCalories.push({
+ documentId: id,
+ day: day,
+ totalCalories: totalCalories
+ });
+ } else {
+ calendarMonthlyCalories.forEach((record) => {
+ if (record.day === day) {
+ record.documentId = id;
+ record.totalCalories = totalCalories;
+ }
+ });
+ }
+}
+
// Add food to the log
const addFood = async () => {
if (!currentUser) {
@@ -827,6 +887,19 @@ const addFood = async () => {
// Save to Appwrite
const savedEntry = await AppwriteDB.saveFoodEntry(entry);
+ // Save or update monthly total calories
+ const monthlyDocumentId = getDocumentIdForToday();
+ if (!monthlyDocumentId?.documentId) {
+ // create
+ const monthlyCreated = await AppwriteDB.createMonthlyCaloryForDay(selectedDate, entry.calories);
+ updateDocumentIdForToday(monthlyCreated.$id, monthlyCreated.totalCalories);
+ } else {
+ // update
+ const totalCalories = monthlyDocumentId?.totalCalories + entry.calories;
+ const monthlyCreated = await AppwriteDB.updateMonthlyCaloryForDay(monthlyDocumentId?.documentId, selectedDate, totalCalories);
+ updateDocumentIdForToday(monthlyCreated.$id, monthlyCreated.totalCalories);
+ }
+
// Add to HTML table with Appwrite document ID
addFoodToTable(entry, savedEntry.$id);
@@ -849,14 +922,13 @@ const addFood = async () => {
carboDiv.textContent = (Math.round(totalCarbs * 10) / 10).toString();
fiberDiv.textContent = (Math.round(totalFiber * 10) / 10).toString();
- // Update alkaline level
- const div = getDivById('alkaline-level');
-
// Reset form
selectedFood = null;
getInputById('foodSearchInput').value = '';
gramAmount.value = '100';
showFoodPreview(false);
+ await delay(1);
+ renderCalendar();
} catch (error) {
console.error('Add food error:', error);
swal('Oh no!', 'Failed to add food entry: ' + error.message, 'error');
@@ -980,6 +1052,8 @@ 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;
@@ -1000,6 +1074,24 @@ async function handleDeleteFood(documentId: string) {
try {
// Delete from Appwrite
await AppwriteDB.deleteFoodEntry(documentId);
+
+ // Delete from monthly total
+ const monthlyDocumentId = getDocumentIdForToday();
+ if (monthlyDocumentId?.documentId) {
+ // find calories from document
+ const targetDiv = document.querySelector(`.food-card[data-id="${documentId}"]`);
+ let existingToDelete = 0;
+ if (targetDiv) {
+ const divCalories = targetDiv.querySelectorAll('.calories-display');
+ if (divCalories && divCalories.length > 0) {
+ existingToDelete = parseInt(divCalories[0].innerHTML.replace(' cal', ''));
+ }
+ }
+ const totalCalories = monthlyDocumentId?.totalCalories - existingToDelete;
+ const monthlyCreated = await AppwriteDB.updateMonthlyCaloryForDay(monthlyDocumentId?.documentId, selectedDate, totalCalories);
+ updateDocumentIdForToday(monthlyCreated.$id, monthlyCreated.totalCalories);
+ await delay(1);
+ }
selectDate(selectedDate);
} catch (error) {
@@ -1172,13 +1264,14 @@ const renderCalendar = async () => {
for (let i = startingDayOfWeek - 1; i >= 0; i--) {
const day = daysInPrevMonth - i;
- const dayElement = createDayElement(day, true, year, month - 1);
+ const dayElement = createDayElement(day, true, year, month - 1, 0);
grid.appendChild(dayElement);
}
// Add days of current month
for (let day = 1; day <= daysInMonth; day++) {
- const dayElement = createDayElement(day, false, year, month);
+ const dayCalories = getDocumentIdForDay(day);
+ const dayElement = createDayElement(day, false, year, month, dayCalories);
grid.appendChild(dayElement);
}
@@ -1187,12 +1280,12 @@ const renderCalendar = async () => {
const remainingCells = 42 - totalCells;
for (let day = 1; day <= remainingCells; day++) {
- const dayElement = createDayElement(day, true, year, month + 1);
+ const dayElement = createDayElement(day, true, year, month + 1, 0);
grid.appendChild(dayElement);
}
}
-function createDayElement(day: number, isOtherMonth: boolean, year: number, month: number) {
+function createDayElement(day: number, isOtherMonth: boolean, year: number, month: number, calories: number) {
const dayElement = document.createElement('div') as HTMLElement;
dayElement.className = 'calendar-day';
@@ -1217,14 +1310,10 @@ function createDayElement(day: number, isOtherMonth: boolean, year: number, mont
}
// Check if this day has data
- const dayData = []; // FIXME get from AppWrite
- if (dayData.length > 0) {
- dayElement.classList.add('has-data');
- const totalCalories = 0; // dayData.reduce((sum, entry) => sum + entry.calories, 0);
-
+ if (calories > 0) {
dayElement.innerHTML = `
-
${day}
-
${totalCalories} cal
+ ${day}
+
${calories}
`;
} else {
dayElement.textContent = day.toString();
diff --git a/src/types.ts b/src/types.ts
index 1fe51b6..36b6d28 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -27,3 +27,9 @@ export type UserSettings = {
carboGoal: number;
fiberGoal: number;
};
+
+export type DailyTotalCalories = {
+ documentId: string;
+ day: number;
+ totalCalories: number;
+};
diff --git a/style.css b/style.css
index e530d25..8f5f69d 100644
--- a/style.css
+++ b/style.css
@@ -685,6 +685,10 @@ label {
display: none;
}
+.muted {
+ color: #ccc;
+}
+
@media (max-width: 768px) {
.container {
margin: 0px;
diff --git a/todo.md b/todo.md
index 1a06ad3..40d2c56 100644
--- a/todo.md
+++ b/todo.md
@@ -1,9 +1,9 @@
# TODO
- [x] Allow search without special characters
-- [ ] Get the calories from the last 7 days and display in the calendar
+- [x] Get the calories from the last 7 days and display in the calendar
- [x] Add food type: Alkaline or Acid
- [x] Display the percentage of Alkaline daily
-- [ ] Remove the table and create a list-like component
+- [x] Remove the table and create a list-like component
- [x] Add logo
- [ ] Group food added by time and consider a meal