From 2aa855f459831949f72fe8ed11c65143d6866409 Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sun, 15 Feb 2026 20:51:52 +0100 Subject: [PATCH 01/13] intake and weight 'find_last' functions should sort by added date, not id --- src-tauri/src/service/intake.rs | 2 +- src-tauri/src/service/weight.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/service/intake.rs b/src-tauri/src/service/intake.rs index ef53d201..5ed5cd98 100644 --- a/src-tauri/src/service/intake.rs +++ b/src-tauri/src/service/intake.rs @@ -315,7 +315,7 @@ impl IntakeTarget { /// Find the last intake target pub fn find_last(conn: &mut SqliteConnection) -> QueryResult { intake_target::table - .order(intake_target::id.desc()) + .order(intake_target::added.desc()) .first::(conn) } } diff --git a/src-tauri/src/service/weight.rs b/src-tauri/src/service/weight.rs index a0e30aa3..7366c557 100644 --- a/src-tauri/src/service/weight.rs +++ b/src-tauri/src/service/weight.rs @@ -253,7 +253,7 @@ impl WeightTracker { /// Find most recent weight tracker entry pub fn get_latest(conn: &mut SqliteConnection) -> QueryResult { weight_tracker::table - .order(weight_tracker::id.desc()) + .order(weight_tracker::added.desc()) .first::(conn) } } From 11d173fa51a9eddaffedbd05d4a21ee8e233792b Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sun, 15 Feb 2026 21:53:31 +0100 Subject: [PATCH 02/13] add settings menu backdrop; add icons to settings menu --- src/lib/component/settings/Settings.svelte | 52 +++++++++++++--------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/lib/component/settings/Settings.svelte b/src/lib/component/settings/Settings.svelte index 18a898e1..cd4d2228 100644 --- a/src/lib/component/settings/Settings.svelte +++ b/src/lib/component/settings/Settings.svelte @@ -1,34 +1,42 @@ {#if open} - - + + - +
+ +
{/if} From 706ef0a3a9a0ac3d6312b7ce69e5e631182e9068 Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sun, 15 Feb 2026 22:01:18 +0100 Subject: [PATCH 03/13] redesign wizard, welcome and setup page --- .../component/journey/WeightGoalsCard.svelte | 21 ++- src/lib/component/wizard/Setup.svelte | 73 ++++------ src/lib/component/wizard/body/Report.svelte | 128 ++++++++---------- src/routes/setup/+page.svelte | 103 +++++++++++++- src/routes/welcome/+page.svelte | 2 +- 5 files changed, 199 insertions(+), 128 deletions(-) diff --git a/src/lib/component/journey/WeightGoalsCard.svelte b/src/lib/component/journey/WeightGoalsCard.svelte index 344c1918..61ca56ec 100644 --- a/src/lib/component/journey/WeightGoalsCard.svelte +++ b/src/lib/component/journey/WeightGoalsCard.svelte @@ -1,6 +1,4 @@ -
-
- - +
+
+ Current Weight + {currentWeight} + kg +
+
+ Target Weight + {targetWeight} + kg
diff --git a/src/lib/component/wizard/Setup.svelte b/src/lib/component/wizard/Setup.svelte index cdba32ea..45ba4939 100644 --- a/src/lib/component/wizard/Setup.svelte +++ b/src/lib/component/wizard/Setup.svelte @@ -39,24 +39,31 @@ interface Props { userData?: LibreUser; bodyData?: BodyData; + currentStep?: number; + recommendation?: string; } const CalculationGoal = CalculationGoalSchema.enum; const CalculationSex = CalculationSexSchema.enum; const WizardRecommendation = WizardRecommendationSchema.enum; - let props: Props = $props(); + let { + userData: userDataProp, + bodyData: bodyDataProp, + currentStep = $bindable(1), + recommendation = $bindable('') + }: Props = $props(); // Create reactive local state for userData with default let userData = $state( - props.userData || { + userDataProp || { id: 1, name: 'Arnie', avatar: '' } ); - let bodyData = props.bodyData || { + let bodyData = bodyDataProp || { id: 0, age: 30, sex: CalculationSex.MALE, @@ -64,8 +71,6 @@ height: 180 }; - let currentStep = $state(1); - let wizardInput: WizardInput = $state({ age: bodyData.age, sex: CalculationSexSchema.safeParse(bodyData.sex).data!, @@ -144,6 +149,7 @@ if (result) { wizardResult = result; + recommendation = result.recommendation; if (result.recommendation === WizardRecommendation.LOSE) { chosenOption.customDetails = result.targetWeightUpper; @@ -309,52 +315,20 @@ {#if !showCompletion} {#snippet step1()} -
-

Body Parameters

-

- Let's start with some basic information about you -

-
{/snippet} {#snippet step2()} -
-

Activity Level

-

- How active are you during your day? Choose what describes your daily activity level best. -

-
{/snippet} {#snippet step3()} -
-

Your Results

-

- Here's what your body composition and metabolism look like -

-
{#if wizardResult} {/if} {/snippet} {#snippet step4()} -
- {#if wizardResult?.recommendation === WizardRecommendation.HOLD} -

Select Your Target Weight

- {:else} -

Choose Your Pace

- {/if} -

- {#if wizardResult?.recommendation === WizardRecommendation.HOLD} - Choose a target weight within your healthy weight range - {:else} - Select a calorie deficit that fits your lifestyle and goals - {/if} -

-
{#if wizardTargetWeightResult} {@const rates = Object.keys(wizardTargetWeightResult.dateByRate).map((v) => +v)} {@const targetDates = wizardTargetWeightResult.dateByRate} @@ -370,12 +344,6 @@ {/snippet} {#snippet step5()} -
-

Your Personalized Plan

-

- Here's your customized fitness journey roadmap -

-
{/snippet}
@@ -391,3 +359,22 @@ onRetry={handleRetry} /> {/if} + + diff --git a/src/lib/component/wizard/body/Report.svelte b/src/lib/component/wizard/body/Report.svelte index e270841d..a36aba22 100644 --- a/src/lib/component/wizard/body/Report.svelte +++ b/src/lib/component/wizard/body/Report.svelte @@ -5,7 +5,7 @@ type WizardInput, type WizardResult } from '$lib/api/gen'; - import { AlertBox, AlertType, AlertVariant, StatCard } from '@thwbh/veilchen'; + import { AlertBox, AlertType, AlertVariant } from '@thwbh/veilchen'; import { getBmiCategoryDisplayValue } from '$lib/enum'; import { z } from 'zod'; @@ -20,7 +20,6 @@ let { wizardResult, wizardInput }: Props = $props(); // Check if user is in low-normal BMI range (18.5-19.9) - // This is when they're in healthy range but have GAIN recommendation let isLowNormalBmi = $derived( wizardResult.bmi >= 18.5 && wizardResult.bmi < 20 && @@ -35,71 +34,72 @@ const getClassificationStyle = (category: string) => { if (classificationLose.safeParse(category).success) { - return 'badge-error'; + return 'bg-error text-content-error'; } else if (category === BmiCategory.Underweight) { - return 'badge-warning'; + return 'bg-warning text-content-warning'; } else { - return 'badge-success'; + return 'bg-success text-content-success'; } }; -
+
-
- - +
+
+ BMI + {wizardResult.bmi} + + {getBmiCategoryDisplayValue(wizardResult.bmiCategory)} + +
+
+ Recommendation + {wizardResult.recommendation.toLowerCase()} + + {wizardInput.weight} kg + +
- -
-

Your Metabolic Profile

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Age{wizardInput.age} years
Height{wizardInput.height} cm
Current Weight{wizardInput.weight} kg
Basal Metabolic Rate{wizardResult.bmr} kcal
Total Daily Energy Expenditure{wizardResult.tdee} kcal
+ +
+
+

+ Metabolic Profile +

+
+
+
+ Age + {wizardInput.age} years +
+
+ Height + {wizardInput.height} cm +
+
+ Weight + {wizardInput.weight} kg +
+
+ Basal Metabolic Rate + {wizardResult.bmr} kcal +
+
+ Daily Energy Expenditure + {wizardResult.tdee} kcal +
+
-
-

Your Analysis

+
+

Analysis

-
+

Your basal metabolic rate is {wizardResult.bmr} kcal. To maintain your @@ -108,7 +108,7 @@

{#if wizardResult.targetBmi} -
+

At {wizardInput.height}cm and {wizardInput.weight}kg, your BMI is @@ -120,7 +120,6 @@

{#if isLowNormalBmi} - You are currently in the healthy weight range

@@ -137,24 +136,11 @@

{:else if wizardResult.targetBmiLower <= wizardResult.bmi && wizardResult.bmi <= wizardResult.targetBmiUpper} - -
- + You are currently in the healthy weight range! -
+ {:else} Outside healthy range diff --git a/src/routes/setup/+page.svelte b/src/routes/setup/+page.svelte index c835193f..33f79380 100644 --- a/src/routes/setup/+page.svelte +++ b/src/routes/setup/+page.svelte @@ -1,7 +1,106 @@ -
- +
+

Setup Wizard

+ + +
+
+
+ Step {currentStep} of {totalSteps} + {currentConfig.title} + {currentConfig.subtitle} +
+ + {#if currentConfig.icon} + {@const Icon = currentConfig.icon} + + + + {/if} +
+ + +
+
+
+
+
+
+ + +
+ +
+ + diff --git a/src/routes/welcome/+page.svelte b/src/routes/welcome/+page.svelte index 6e7c0fb8..09955307 100644 --- a/src/routes/welcome/+page.svelte +++ b/src/routes/welcome/+page.svelte @@ -22,7 +22,7 @@
-
+
{#if mounted}
From a6ebe880d001f60c53a02d463379eada2f4a997f Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sun, 15 Feb 2026 22:03:19 +0100 Subject: [PATCH 04/13] redesign settings routes --- src/routes/(app)/about/+page.svelte | 22 +--- src/routes/(app)/export/+page.svelte | 128 +++++++++++++--------- src/routes/(app)/import/+page.svelte | 152 +++++++++++++++----------- src/routes/(app)/profile/+page.svelte | 44 +++----- src/routes/(app)/wizard/+page.svelte | 120 ++++++++++++++++---- 5 files changed, 287 insertions(+), 179 deletions(-) diff --git a/src/routes/(app)/about/+page.svelte b/src/routes/(app)/about/+page.svelte index c43cc348..ae15ce0b 100644 --- a/src/routes/(app)/about/+page.svelte +++ b/src/routes/(app)/about/+page.svelte @@ -1,40 +1,22 @@ About LibreFit -
- - +
{#if mounted} diff --git a/src/routes/(app)/export/+page.svelte b/src/routes/(app)/export/+page.svelte index 9ec7ed1c..1aebf7fd 100644 --- a/src/routes/(app)/export/+page.svelte +++ b/src/routes/(app)/export/+page.svelte @@ -1,7 +1,6 @@ -
+

Data Export

- - -
-

Your Data, Your Control

-

- Export a complete backup of your LibreFit data. This includes all your tracked meals, weight - entries, targets, and personal settings. -

- -

What's included:

-
    -
  • All calorie tracking entries
  • -
  • Weight history and targets
  • -
  • Your profile and body data
  • -
  • Custom settings and preferences
  • -
-
-
- - {#snippet icon(option)} - {#if option.value === 'raw'} - - {:else if option.value === 'csv'} - - {:else if option.value === 'pdf'} - - {/if} - {/snippet} - + +
+
+
+ Export Data + Back up your LibreFit data +
+ + + +
- + +
+ + Your data, your control + + Export a complete backup of your LibreFit data. This includes all your tracked meals, weight + entries, targets, and personal settings. + + + +
+
+

What's included

+
+
+
+ + All calorie tracking entries +
+
+ + Weight history and targets +
+
+ + Your profile and body data +
+
+ + Custom settings and preferences +
+
+
+ +
+ + {#snippet icon(option)} + {#if option.value === 'raw'} + + {:else if option.value === 'csv'} + + {:else if option.value === 'pdf'} + + {/if} + {/snippet} + +
+ + +
@@ -313,3 +326,20 @@
{/snippet} + + diff --git a/src/routes/(app)/import/+page.svelte b/src/routes/(app)/import/+page.svelte index b630011d..434fc921 100644 --- a/src/routes/(app)/import/+page.svelte +++ b/src/routes/(app)/import/+page.svelte @@ -7,7 +7,6 @@ ImportTableSchema, type ImportResult } from '$lib/api/gen/types'; - import SettingsIcon from '$lib/component/navigation/SettingsIcon.svelte'; import { Channel } from '@tauri-apps/api/core'; import { open } from '@tauri-apps/plugin-dialog'; import { debug, error } from '@tauri-apps/plugin-log'; @@ -15,12 +14,9 @@ AlertBox, AlertType, AlertVariant, - Breadcrumbs, LoadingIndicator, ModalDialog, OptionCards, - TextSize, - type BreadcrumbItem, type OptionCardData } from '@thwbh/veilchen'; import { Check, ForkKnife, Scales, Upload, Warning } from 'phosphor-svelte'; @@ -45,26 +41,12 @@ { value: ImportTable.intake, header: 'Intake', - text: 'Import intake data.' + text: 'Restore your tracked meals and calorie entries from a CSV file.' }, { value: ImportTable.weightTracker, header: 'Weight', - text: 'Import weight tracking data.' - } - ]; - - const items: BreadcrumbItem[] = [ - { - id: '1', - icon: SettingsIcon - }, - { - id: '2', - href: '/import', - label: 'Import Data', - icon: Upload, - iconProps: { weight: 'bold' } + text: 'Restore your weight history and measurements from a CSV file.' } ]; @@ -163,55 +145,84 @@ } -
+

Data Import

- - -
-

Import Existing Data

-

- Import your LibreFit data from CSV files. Choose a valid CSV file and select the target table - to import into. -

- -

Available import targets:

-
    -
  • Calorie tracking entries (Intake)
  • -
  • Weight tracking history
  • -
-
-
- - {#snippet icon(option)} - {#if option.value === ImportTable.intake} - - {:else if option.value === ImportTable.weightTracker} - - {/if} - {/snippet} - + +
+
+
+ Import Data + Restore your data from CSV files +
+ + + +
-
- Pick a file -
- - + +
+ + Import existing data + + Import your LibreFit data from CSV files. Choose a valid CSV file and select the target + table to import into. + + + +
+
+

+ Available import targets +

+
+
+
+ + Meal data (Intake) +
+
+ + Weight tracking history +
+
+
+ +
+ + {#snippet icon(option)} + {#if option.value === ImportTable.intake} + + {:else if option.value === ImportTable.weightTracker} + + {/if} + {/snippet} +
-
- - Important: Importing the same file multiple times will create duplicate entries. - There is no automatic deduplication. - +
+ Pick a file +
+ + +
+
+ + + Important: Importing the same file multiple times will create duplicate entries. + There is no automatic deduplication. + - + +
@@ -290,3 +301,20 @@
{/snippet} + + diff --git a/src/routes/(app)/profile/+page.svelte b/src/routes/(app)/profile/+page.svelte index c648e8a6..55023d63 100644 --- a/src/routes/(app)/profile/+page.svelte +++ b/src/routes/(app)/profile/+page.svelte @@ -1,13 +1,5 @@ -
+

User Profile

- -
+ +
+
+
+ Profile + Manage your personal information +
+ + + +
+
+ + +
openEdit()}> {#snippet leftAction()} diff --git a/src/routes/(app)/wizard/+page.svelte b/src/routes/(app)/wizard/+page.svelte index 6ea1480e..b526592e 100644 --- a/src/routes/(app)/wizard/+page.svelte +++ b/src/routes/(app)/wizard/+page.svelte @@ -1,37 +1,115 @@ -
- +
+

Setup Wizard

+ + +
+
+
+ Step {currentStep} of {totalSteps} + {currentConfig.title} + {currentConfig.subtitle} +
+ + {#if currentConfig.icon} + {@const Icon = currentConfig.icon} + + + + {/if} +
- + +
+
+
+
+
+
+ + +
+ +
+ + From 288968b808b049e69e4480b5d33b025729e049a4 Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sun, 15 Feb 2026 22:03:52 +0100 Subject: [PATCH 05/13] mark settings icon as active in dock when settings subpages are hit --- src/routes/(app)/+layout.svelte | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 24176909..518be4f5 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -26,7 +26,6 @@ id: 'home', label: 'Home', href: '/', - /* icon: House,*/ icon: JournalIcon, iconProps: { size: '1.25em', weight: 'bold' } }, @@ -34,21 +33,18 @@ id: 'progress', label: 'Progress', href: '/progress', - icon: ProgressIcon, - iconProps: { size: '1.25em', weight: 'bold' } + icon: ProgressIcon }, { id: 'history', label: 'History', href: '/history', - icon: HistoryIcon, - iconProps: { size: '1.25em', weight: 'bold' } + icon: HistoryIcon }, { id: 'settings', label: 'Settings', icon: SettingsIcon, - iconProps: { size: '1.25em', weight: 'bold' }, onclick: () => { isSettingsOpen = !isSettingsOpen; } @@ -63,7 +59,7 @@ item.href && (page.url.pathname === item.href || (page.url.pathname.startsWith(item.href) && item.href !== '/')) - )?.id + )?.id ?? 'settings' ); From d796c198df6b6f0c6736ef9154bd1f0442bd3b68 Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sun, 15 Feb 2026 22:04:36 +0100 Subject: [PATCH 06/13] decrease FAB z-index to avoid settings menu overlap --- src/routes/(app)/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 1666e54b..483dd65f 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -309,7 +309,7 @@
From f4b0305d4226a47a5e4a976bdad9aaa98d59602a Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sun, 15 Feb 2026 22:39:07 +0100 Subject: [PATCH 09/13] tidy up WeightGoalsCard --- src-tauri/src/service/dashboard.rs | 2 +- .../component/journey/WeightGoalsCard.svelte | 45 +++++++++---- .../component/journey/WeightGoalsCard.test.ts | 63 ++++--------------- src/lib/component/wizard/Finish.svelte | 2 +- 4 files changed, 48 insertions(+), 64 deletions(-) diff --git a/src-tauri/src/service/dashboard.rs b/src-tauri/src/service/dashboard.rs index e9ab615f..f6065a2a 100644 --- a/src-tauri/src/service/dashboard.rs +++ b/src-tauri/src/service/dashboard.rs @@ -88,7 +88,7 @@ impl Dashboard { let day_count: i32 = end_date .signed_duration_since(intake_target_start_date) .num_days() as i32; - let current_day: i32 = day_count + 2; // Day count will be zero at the first day + let current_day: i32 = day_count + 1; // Day count will be zero at the first day let days_total: i32 = intake_target_end_date .signed_duration_since(intake_target_start_date) diff --git a/src/lib/component/journey/WeightGoalsCard.svelte b/src/lib/component/journey/WeightGoalsCard.svelte index 61ca56ec..d99e2465 100644 --- a/src/lib/component/journey/WeightGoalsCard.svelte +++ b/src/lib/component/journey/WeightGoalsCard.svelte @@ -1,21 +1,42 @@ -
-
- Current Weight - {currentWeight} - kg -
-
- Target Weight - {targetWeight} - kg +
+

Weight Goal

+
+
+ + kg + + Start +
+ +
+ {#if isGaining} + + {:else} + + {/if} + {Math.abs(weightDiff).toFixed(1)} kg +
+ +
+ + kg + + Target +
diff --git a/src/lib/component/journey/WeightGoalsCard.test.ts b/src/lib/component/journey/WeightGoalsCard.test.ts index d52eae72..b82d0249 100644 --- a/src/lib/component/journey/WeightGoalsCard.test.ts +++ b/src/lib/component/journey/WeightGoalsCard.test.ts @@ -7,76 +7,39 @@ import { setupVeilchenMock } from '../../../../tests/utils/mocks'; setupVeilchenMock(); describe('WeightGoalsCard Component', () => { - it('should render current weight', () => { + it('should render start and target labels', () => { render(WeightGoalsCard, { props: { - currentWeight: 75, - targetWeight: 70 + initialWeight: 85, + targetWeight: 80 } }); - expect(screen.getByText('Current Weight')).toBeInTheDocument(); + expect(screen.getByText('Start')).toBeInTheDocument(); + expect(screen.getByText('Target')).toBeInTheDocument(); }); - it('should render target weight', () => { - render(WeightGoalsCard, { - props: { - currentWeight: 80, - targetWeight: 75 - } - }); - - expect(screen.getByText('Target Weight')).toBeInTheDocument(); - }); - - it('should display weights as numbers', () => { + it('should render weight difference', () => { const { container } = render(WeightGoalsCard, { props: { - currentWeight: 85.5, - targetWeight: 80.0 + initialWeight: 85, + targetWeight: 80 } }); - // The component uses StatCard which renders the values + expect(screen.getByText('5.0 kg')).toBeInTheDocument(); expect(container).toBeDefined(); }); - it('should apply correct styling', () => { - const { container } = render(WeightGoalsCard, { - props: { - currentWeight: 90, - targetWeight: 85 - } - }); - - const wrapper = container.querySelector('.bg-base-100'); - expect(wrapper).toBeDefined(); - - const stats = container.querySelector('.stats'); - expect(stats).toBeDefined(); - }); - it('should handle weight gain scenario', () => { - render(WeightGoalsCard, { + const { container } = render(WeightGoalsCard, { props: { - currentWeight: 60, + initialWeight: 60, targetWeight: 70 } }); - expect(screen.getByText('Current Weight')).toBeInTheDocument(); - expect(screen.getByText('Target Weight')).toBeInTheDocument(); - }); - - it('should handle weight loss scenario', () => { - render(WeightGoalsCard, { - props: { - currentWeight: 100, - targetWeight: 85 - } - }); - - expect(screen.getByText('Current Weight')).toBeInTheDocument(); - expect(screen.getByText('Target Weight')).toBeInTheDocument(); + expect(screen.getByText('10.0 kg')).toBeInTheDocument(); + expect(container).toBeDefined(); }); }); diff --git a/src/lib/component/wizard/Finish.svelte b/src/lib/component/wizard/Finish.svelte index 99f211ff..274cc55e 100644 --- a/src/lib/component/wizard/Finish.svelte +++ b/src/lib/component/wizard/Finish.svelte @@ -23,7 +23,7 @@ /> From 701cf7177620d1dcdccfb7c43eff37bb45a52e65 Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sun, 15 Feb 2026 23:10:44 +0100 Subject: [PATCH 10/13] make date diff calculations zero-based and pad display value in frontend --- src-tauri/src/service/dashboard.rs | 25 ++-- src-tauri/src/service/progress.rs | 22 ++- src-tauri/tests/cmd/test_dashboard_cmd.rs | 9 +- src/routes/(app)/+page.svelte | 4 +- src/routes/(app)/progress/+page.svelte | 163 ++++++++++++---------- 5 files changed, 119 insertions(+), 104 deletions(-) diff --git a/src-tauri/src/service/dashboard.rs b/src-tauri/src/service/dashboard.rs index f6065a2a..73603794 100644 --- a/src-tauri/src/service/dashboard.rs +++ b/src-tauri/src/service/dashboard.rs @@ -2,7 +2,7 @@ use crate::db::connection::DbPool; use crate::service::intake::{FoodCategory, Intake, IntakeTarget}; use crate::service::user::LibreUser; use crate::service::weight::{WeightTarget, WeightTracker}; -use chrono::{Days, NaiveDate, TimeDelta}; +use chrono::{NaiveDate, TimeDelta}; use diesel::SqliteConnection; use serde::{Deserialize, Serialize}; use std::ops::Sub; @@ -52,14 +52,6 @@ impl Dashboard { let intake_target_end_date = NaiveDate::parse_from_str(&intake_target.end_date, "%Y-%m-%d") .map_err(|_| "Invalid date format".to_string())?; - // Calculate end_date for current_day calculation - let today_date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap(); - let end_date: NaiveDate = if today_date < intake_target_end_date { - today_date.checked_sub_days(Days::new(1)).unwrap() - } else { - intake_target_end_date - }; - // Calculate date ranges let week_start_str = get_date_range_begin(&date, chrono::Duration::days(7)); let month_start_str = get_date_range_begin(&date, chrono::Duration::weeks(4)); @@ -84,11 +76,16 @@ impl Dashboard { // Fetch food categories let food_categories = FoodCategory::all(conn).unwrap_or_else(|_| vec![]); - // Calculate current day - let day_count: i32 = end_date - .signed_duration_since(intake_target_start_date) - .num_days() as i32; - let current_day: i32 = day_count + 1; // Day count will be zero at the first day + let current_day: i32 = if date < intake_target_end_date { + // Elapsed days (0 on first day) + date.signed_duration_since(intake_target_start_date) + .num_days() as i32 + } else { + // Plan complete: cap at total + intake_target_end_date + .signed_duration_since(intake_target_start_date) + .num_days() as i32 + }; let days_total: i32 = intake_target_end_date .signed_duration_since(intake_target_start_date) diff --git a/src-tauri/src/service/progress.rs b/src-tauri/src/service/progress.rs index fec5a813..a2f31975 100644 --- a/src-tauri/src/service/progress.rs +++ b/src-tauri/src/service/progress.rs @@ -67,7 +67,8 @@ impl Progress { let today_date: NaiveDate = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") .map_err(|_| "Invalid date format".to_string())?; - let end_date: NaiveDate = if today_date < intake_target_end_date { + // Query up to yesterday to avoid incomplete data skewing charts + let query_end_date: NaiveDate = if today_date <= intake_target_end_date { today_date .checked_sub_days(Days::new(1)) .ok_or("Failed to subtract day".to_string())? @@ -75,22 +76,29 @@ impl Progress { intake_target_end_date }; - let end_date_str = end_date.format("%Y-%m-%d").to_string(); + let query_end_str = query_end_date.format("%Y-%m-%d").to_string(); let intake: Vec = - Intake::find_by_date_range(conn, &intake_target.start_date, &end_date_str) + Intake::find_by_date_range(conn, &intake_target.start_date, &query_end_str) .unwrap_or_default(); let weight_tracker: Vec = - WeightTracker::find_by_date_range(conn, &weight_target.start_date, &end_date_str) + WeightTracker::find_by_date_range(conn, &weight_target.start_date, &query_end_str) .unwrap_or_default(); let intake_chart_data = process_intake(conn, &intake)?; let weight_chart_data = process_weight(&weight_tracker); - let days_passed: i32 = end_date - .signed_duration_since(intake_target_start_date) - .num_days() as i32; + // Elapsed days (0-indexed: 0 on first day), capped at days_total after plan ends + let days_passed: i32 = if today_date < intake_target_end_date { + today_date + .signed_duration_since(intake_target_start_date) + .num_days() as i32 + } else { + intake_target_end_date + .signed_duration_since(intake_target_start_date) + .num_days() as i32 + }; let days_total: i32 = intake_target_end_date .signed_duration_since(intake_target_start_date) diff --git a/src-tauri/tests/cmd/test_dashboard_cmd.rs b/src-tauri/tests/cmd/test_dashboard_cmd.rs index c701227b..55c95351 100644 --- a/src-tauri/tests/cmd/test_dashboard_cmd.rs +++ b/src-tauri/tests/cmd/test_dashboard_cmd.rs @@ -95,8 +95,8 @@ fn test_daily_dashboard_last_day_of_target() { assert!(result.is_ok()); let dashboard = result.unwrap(); - // Last day should be day 31 (when querying on the last day, it shows 31) - assert_eq!(dashboard.current_day, 31); + // Last day: 0-indexed elapsed days capped at days_total (30 for a Jan 1–Jan 31 range) + assert_eq!(dashboard.current_day, dashboard.days_total); } #[test] @@ -124,9 +124,8 @@ fn test_daily_dashboard_date_beyond_target() { assert!(result.is_ok()); let dashboard = result.unwrap(); - // When querying beyond target, it should still return successfully - // The current_day should be based on the end date, not the query date - assert_eq!(dashboard.current_day, 31); + // When querying beyond target, current_day caps at days_total + assert_eq!(dashboard.current_day, dashboard.days_total); } #[test] diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 71f44c7d..3b263582 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -173,9 +173,7 @@
- {#if data.dashboardData.currentDay > 0} - Day {data.dashboardData.currentDay} - {/if} + Day {dashboard.currentDay + 1} {displayDate}
diff --git a/src/routes/(app)/progress/+page.svelte b/src/routes/(app)/progress/+page.svelte index 5bc03b89..5479e5eb 100644 --- a/src/routes/(app)/progress/+page.svelte +++ b/src/routes/(app)/progress/+page.svelte @@ -1,7 +1,7 @@
@@ -297,7 +314,8 @@