From a5329af7b304cff54647bc2147aeab17368fcaa7 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Fri, 13 Feb 2026 14:02:41 -0300 Subject: [PATCH] feat: manage current date with datetime api. Closes #6 --- index.html | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ settings.css | 29 ++++++++++++++++++++++++ src/dateUtils.ts | 36 +++++++++++++++++++++++++++++ src/index.ts | 31 ++++++++++++++++++++----- src/state.ts | 6 ++++- src/types.ts | 1 + 6 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 src/dateUtils.ts diff --git a/index.html b/index.html index 0636f48..f93ef53 100644 --- a/index.html +++ b/index.html @@ -393,6 +393,65 @@ +
+ + +
diff --git a/settings.css b/settings.css index ad8bf25..a49f769 100644 --- a/settings.css +++ b/settings.css @@ -32,6 +32,35 @@ border-color: #6B8DD6; } +.settings-form select { + width: 100%; + padding: 12px; + padding-right: 36px; + border-radius: 6px; + font-size: 1rem; + box-sizing: border-box; + border: 1px solid #404040; + background-color: #2a2a2a; + color: #e0e0e0; + appearance: none; + -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23a0a0a0' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + cursor: pointer; +} + +.settings-form select:focus { + outline: none; + border-color: #6B8DD6; +} + +.settings-form select option, +.settings-form select optgroup { + background-color: #2a2a2a; + color: #e0e0e0; +} + .settings-btn { width: 100%; padding: 12px; diff --git a/src/dateUtils.ts b/src/dateUtils.ts new file mode 100644 index 0000000..9ccfb06 --- /dev/null +++ b/src/dateUtils.ts @@ -0,0 +1,36 @@ +export interface CurrentDateResult { + date: string; // "2026-02-13" + dateTime: Date; // new Date(year, month-1, day) — local-midnight Date + timeString: string; // "HH:MM" +} + +export async function getCurrentDate(timezone: string): Promise { + if (timezone) { + try { + const response = await fetch( + `https://timeapi.io/api/v1/time/current/zone?timezone=${encodeURIComponent(timezone)}` + ); + if (response.ok) { + const data = await response.json(); + // data.date is "YYYY-MM-DD", data.time is "HH:MM:SS.mmm" or "HH:MM" + const [year, month, day] = data.date.split('-').map(Number); + const dateTime = new Date(year, month - 1, day); + const timeParts = data.time.split(':'); + const timeString = `${timeParts[0].padStart(2, '0')}:${timeParts[1].padStart(2, '0')}`; + return { date: data.date, dateTime, timeString }; + } + } catch (e) { + console.warn('timeapi.io request failed, falling back to browser time:', e); + } + } + + // Fallback: browser local time + const now = new Date(); + const year = now.getFullYear(); + const month = now.getMonth() + 1; + const day = now.getDate(); + const date = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + const dateTime = new Date(year, now.getMonth(), day); + const timeString = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; + return { date, dateTime, timeString }; +} diff --git a/src/index.ts b/src/index.ts index f047b52..c8da14f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import { closeMobileMenu, delay, getCleanName, getIcon, handleMobileCalendarClic import { showAuthForms, toggleAuthForms, showRegisterForm, showLoginForm, hideAuthForms, closeAuthModal } from './auth'; import { appState } from "./state"; import { getNutritionInfo, NutritionInfo, clearNutritionCache, getCacheStats } from './claudeService'; +import { getCurrentDate } from './dateUtils'; // PWA Service Worker Registration import { registerSW } from 'virtual:pwa-register'; @@ -42,6 +43,20 @@ document.addEventListener('DOMContentLoaded', async function() { setupEventListeners(); }); +async function initTimezone() { + try { + const allDocs = await AppwriteDB.getUserSettings(); + const globalSettings = allDocs.find((d: any) => !d.goalName); + appState.userTimezone = globalSettings?.timezone ?? ''; + } catch { + appState.userTimezone = ''; + } + const result = await getCurrentDate(appState.userTimezone); + selectedDate = result.dateTime; + currentViewDate = result.dateTime; + appState.todayDateString = result.date; +} + async function initializeAuth() { // Check if this is a shared view first const urlParams = new URLSearchParams(window.location.search); @@ -60,6 +75,7 @@ async function initializeAuth() { if (isLoggedIn) { currentUser = await AppwriteAuth.getCurrentUser(); await showMainApp(); + await initTimezone(); selectDate(selectedDate); } else { showAuthForms(); @@ -91,6 +107,7 @@ async function handleRegister(e: SubmitEvent) { closeAuthModal(); await showMainApp(); + await initTimezone(); selectDate(selectedDate); swal('Registration successful! Welcome to Food Tracker.'); @@ -117,6 +134,7 @@ async function handleLogin(e: SubmitEvent) { closeAuthModal(); await showMainApp(); + await initTimezone(); selectDate(selectedDate); } catch (error) { @@ -192,12 +210,14 @@ async function handleSaveSettings(e: SubmitEvent) { const height = getInputById('height').value; const bmi = getInputById('bmi').value; const bmiResult = getInputById('bmiResult'); + const timezone = getInputById('timezone').value; const metricsData = { bodyWeight: bodyWeight ? parseFloat(bodyWeight) : undefined, height: height ? parseFloat(height) : undefined, bmi: bmi ? parseFloat(bmi) : undefined, bmiResult: bmiResult ? bmiResult.value : undefined, + timezone: timezone || undefined, }; // Find existing global settings doc (no goalName) and update it, or create new one @@ -210,6 +230,7 @@ async function handleSaveSettings(e: SubmitEvent) { await AppwriteDB.saveUserSettings(metricsData); } + appState.userTimezone = timezone; hideLoading(); toggleSettingsView(); selectDate(selectedDate); @@ -308,11 +329,13 @@ async function toggleSettingsView() { getInputById('height').value = globalSettings.height ?? ''; getInputById('bmi').value = globalSettings.bmi ?? ''; getInputById('bmiResult').value = globalSettings.bmiResult ?? ''; + (document.getElementById('timezone') as HTMLSelectElement).value = globalSettings.timezone ?? ''; } else { getInputById('bodyWeight').value = ''; getInputById('height').value = ''; getInputById('bmi').value = ''; getInputById('bmiResult').value = ''; + (document.getElementById('timezone') as HTMLSelectElement).value = ''; } const goals: UserSettings[] = allDocs @@ -1331,11 +1354,7 @@ const addFood = async () => { fiber: proportion.info.fiber, date: date.split('T')[0], alkaline: selectedFood.info.alkaline, - time: new Date().toLocaleTimeString('en-US', { - hour12: false, - hour: '2-digit', - minute: '2-digit' - }) + time: (await getCurrentDate(appState.userTimezone)).timeString }; // Save to Appwrite @@ -1961,7 +1980,7 @@ function createDayElement(day: number, isOtherMonth: boolean, year: number, mont } // Check if this is today - const today = new Date().toISOString().split('T')[0]; + const today = appState.todayDateString || new Date().toISOString().split('T')[0]; if (!isOtherMonth && dateString === today) { dayElement.classList.add('today'); } diff --git a/src/state.ts b/src/state.ts index 10c422d..c3feafa 100644 --- a/src/state.ts +++ b/src/state.ts @@ -7,6 +7,8 @@ interface AppState { calendarMonthlyCalories: DailyTotalCalories[]; isSharedView: boolean; sharedData: SharedDay | null; + userTimezone: string; + todayDateString: string; } export const appState: AppState = { @@ -15,5 +17,7 @@ export const appState: AppState = { searchResults: [], calendarMonthlyCalories: [], isSharedView: false, - sharedData: null + sharedData: null, + userTimezone: '', + todayDateString: '' }; diff --git a/src/types.ts b/src/types.ts index 7e720cf..240b10a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -33,6 +33,7 @@ export type UserSettings = { bmiResult?: string; goalName?: string; isActive?: boolean; + timezone?: string; }; export type DailyTotalCalories = {