From 952dde5302d08321be761966692e694aea450ebb Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Thu, 12 Feb 2026 13:07:22 +0100 Subject: [PATCH 01/18] update veilchen to 0.2.3 --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 827685ed..a5ea9c8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "@testing-library/jest-dom": "^6.8.0", "@testing-library/svelte": "^5.1.0", "@testing-library/user-event": "^14.6.1", - "@thwbh/veilchen": "^0.2.2", + "@thwbh/veilchen": "^0.2.3", "@tsconfig/svelte": "^5.0.4", "@types/date-fns": "^2.6.0", "@types/node": "^22.10.1", @@ -65,7 +65,7 @@ }, "../veilchen": { "name": "@thwbh/veilchen", - "version": "0.2.2", + "version": "0.2.3", "extraneous": true, "license": "MIT", "devDependencies": { @@ -2074,9 +2074,9 @@ } }, "node_modules/@thwbh/veilchen": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@thwbh/veilchen/-/veilchen-0.2.2.tgz", - "integrity": "sha512-FabUTDYeyObacJso33V42/v3/LJ/qRHqJ2O77t/W1jb9rfYj/0+DslY8fwf8DxrutHxiq0YAZie9dBXwEI9VTg==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@thwbh/veilchen/-/veilchen-0.2.3.tgz", + "integrity": "sha512-T3sSbK3SJRnVMFtfo5KqLcot1lv1tNmRCpxmGYnyotrMXFocK021HEpXP4BmPmgDfMdfTNVQ9YB9P2S/41r4JQ==", "dev": true, "license": "MIT", "peerDependencies": { diff --git a/package.json b/package.json index 38ece7fd..b689cb89 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@testing-library/jest-dom": "^6.8.0", "@testing-library/svelte": "^5.1.0", "@testing-library/user-event": "^14.6.1", - "@thwbh/veilchen": "^0.2.2", + "@thwbh/veilchen": "^0.2.3", "@tsconfig/svelte": "^5.0.4", "@types/date-fns": "^2.6.0", "@types/node": "^22.10.1", From 307cad3c5bd8fa81ed2d02955a7e7458c6895443 Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Thu, 12 Feb 2026 13:08:31 +0100 Subject: [PATCH 02/18] update activity level and rate colors --- src/lib/activity.ts | 16 ++++++------ src/lib/component/wizard/targets/Rate.svelte | 26 +++++++++----------- tests/lib/activity.test.ts | 10 ++++---- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/lib/activity.ts b/src/lib/activity.ts index 576d0ebe..01eedb1a 100644 --- a/src/lib/activity.ts +++ b/src/lib/activity.ts @@ -1,14 +1,12 @@ import { Barbell, OfficeChair, PersonSimpleRun, PersonSimpleTaiChi, Trophy } from 'phosphor-svelte'; import type { Component } from 'svelte'; +import { BadgeColor, type OptionCardBadge } from '@thwbh/veilchen'; export interface ActivityLevelInfo { level: number; label: string; icon: Component; - badge: { - text: string; - color: 'success' | 'error' | 'warning' | 'info' | 'primary' | 'secondary' | 'accent'; - }; + badge: OptionCardBadge; description: string; } @@ -17,7 +15,7 @@ export const activityLevels: ActivityLevelInfo[] = [ level: 1, label: 'Mostly Sedentary', icon: OfficeChair, - badge: { text: 'Level 1', color: 'secondary' }, + badge: { text: 'Level 1', color: BadgeColor.Secondary }, description: 'You likely have an office job and try your best reaching your daily step goal. Apart from that, you do not work out regularly and spend most of your day stationary.' }, @@ -25,7 +23,7 @@ export const activityLevels: ActivityLevelInfo[] = [ level: 1.25, label: 'Light Activity', icon: PersonSimpleTaiChi, - badge: { text: 'Level 2', color: 'secondary' }, + badge: { text: 'Level 2', color: BadgeColor.Secondary }, description: 'You either have a job that requires you to move around frequently or you hit the gym 2x - 3x times a week. In either way, you are regularly lifting weight and training your cardiovascular system.' }, @@ -33,7 +31,7 @@ export const activityLevels: ActivityLevelInfo[] = [ level: 1.5, label: 'Moderate Activity', icon: PersonSimpleRun, - badge: { text: 'Level 3', color: 'secondary' }, + badge: { text: 'Level 3', color: BadgeColor.Secondary }, description: 'You consistently train your body 3x - 4x times a week. Your training plan became more sophisticated over the years and include cardiovascular HIIT sessions. You realized how important nutrition is and want to improve your sportive results.' }, @@ -41,7 +39,7 @@ export const activityLevels: ActivityLevelInfo[] = [ level: 1.75, label: 'Highly Active', icon: Barbell, - badge: { text: 'Level 4', color: 'accent' }, + badge: { text: 'Level 4', color: BadgeColor.Warning }, description: 'Fitness is your top priority in life. You dedicate large parts of your week to train your body, maybe even regularly visit sportive events. You work out almost every day and certainly know what you are doing.' }, @@ -49,7 +47,7 @@ export const activityLevels: ActivityLevelInfo[] = [ level: 2, label: 'Athlete', icon: Trophy, - badge: { text: 'Level 5', color: 'warning' }, + badge: { text: 'Level 5', color: BadgeColor.Accent }, description: "Your fitness level reaches into the (semi-) professional realm. Calculators like this won't fulfill your needs and you are curious how far off the results will be." } diff --git a/src/lib/component/wizard/targets/Rate.svelte b/src/lib/component/wizard/targets/Rate.svelte index 97cbad92..bc0965ad 100644 --- a/src/lib/component/wizard/targets/Rate.svelte +++ b/src/lib/component/wizard/targets/Rate.svelte @@ -6,8 +6,9 @@ AlertVariant, OptionCards, RangeInput, - StatCard, - type OptionCardData + BadgeColor, + type OptionCardData, + type OptionCardBadge } from '@thwbh/veilchen'; import { getWizardContext } from '$lib/context'; import TargetWeight from './TargetWeight.svelte'; @@ -56,19 +57,16 @@ const difficultyConfig: Record< number, { - badge: { - text: string; - color: 'success' | 'info' | 'warning' | 'error' | 'secondary' | 'accent'; - }; + badge: OptionCardBadge; } > = { - 100: { badge: { text: 'Very Easy', color: 'secondary' } }, - 200: { badge: { text: 'Easy', color: 'secondary' } }, - 300: { badge: { text: 'Moderate', color: 'info' } }, - 400: { badge: { text: 'Moderate', color: 'info' } }, - 500: { badge: { text: 'Challenging', color: 'accent' } }, - 600: { badge: { text: 'Hard', color: 'accent' } }, - 700: { badge: { text: 'Very Hard', color: 'warning' } } + 100: { badge: { text: 'Very Easy', color: BadgeColor.Primary } }, + 200: { badge: { text: 'Easy', color: BadgeColor.Secondary } }, + 300: { badge: { text: 'Moderate', color: BadgeColor.Info } }, + 400: { badge: { text: 'Moderate', color: BadgeColor.Info } }, + 500: { badge: { text: 'Challenging', color: BadgeColor.Warning } }, + 600: { badge: { text: 'Hard', color: BadgeColor.Warning } }, + 700: { badge: { text: 'Very Hard', color: BadgeColor.Accent } } }; const data: Array = $derived.by(() => { @@ -82,7 +80,7 @@ value: rate, header: `${rate} kcal/day`, badge: difficultyConfig[rate].badge, - highlight: isRecommended ? { text: 'Recommended', color: 'primary' } : undefined, + highlight: isRecommended ? { text: 'Recommended', color: BadgeColor.Primary } : undefined, metrics: [ { label: progressLabel, diff --git a/tests/lib/activity.test.ts b/tests/lib/activity.test.ts index 722db137..a550d709 100644 --- a/tests/lib/activity.test.ts +++ b/tests/lib/activity.test.ts @@ -52,11 +52,11 @@ describe('activity utilities', () => { }); it('should have appropriate badge colors', () => { - expect(activityLevels[0].badge.color).toBe('secondary'); - expect(activityLevels[1].badge.color).toBe('secondary'); - expect(activityLevels[2].badge.color).toBe('secondary'); - expect(activityLevels[3].badge.color).toBe('accent'); - expect(activityLevels[4].badge.color).toBe('warning'); + expect(activityLevels[0].badge.color).toBe('badge-secondary'); + expect(activityLevels[1].badge.color).toBe('badge-secondary'); + expect(activityLevels[2].badge.color).toBe('badge-secondary'); + expect(activityLevels[3].badge.color).toBe('badge-warning'); + expect(activityLevels[4].badge.color).toBe('badge-accent'); }); }); From e6c6e3cc2bc230c98d72e4f458450397545a5051 Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Fri, 13 Feb 2026 16:16:09 +0100 Subject: [PATCH 03/18] redesign category picker --- src/lib/component/intake/IntakeMask.svelte | 78 ++++++++++++---------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/lib/component/intake/IntakeMask.svelte b/src/lib/component/intake/IntakeMask.svelte index 5eae0921..6126e243 100644 --- a/src/lib/component/intake/IntakeMask.svelte +++ b/src/lib/component/intake/IntakeMask.svelte @@ -3,6 +3,7 @@ import { NumberStepper } from '@thwbh/veilchen'; import { getCategoriesContext } from '$lib/context'; import type { Intake, NewIntake } from '$lib/api'; + import { Coffee, BowlFood, ForkKnife, Cookie, IceCream, PintGlass } from 'phosphor-svelte'; interface Props { entry: Intake | NewIntake; @@ -21,6 +22,15 @@ // Get categories from context instead of props const categories = getCategoriesContext(); + const categoryIcons: Record = { + b: Coffee, + l: BowlFood, + d: ForkKnife, + s: Cookie, + t: IceCream, + u: PintGlass + }; + const select = (categoryShortvalue: string) => { entry.category = categoryShortvalue; }; @@ -49,54 +59,54 @@ {:else} -
- Category - -
-
- {#each categories as category} - - {/each} -
+
+ {getFoodCategoryLongvalue(categories, entry.category)} +
+ {#each categories as category (category.shortvalue)} + {@const Icon = categoryIcons[category.shortvalue]} + + {/each}
-
- + +
{entry.description?.length || 0} / 500
-
+ {/if} diff --git a/src/routes/(app)/history/+page.svelte b/src/routes/(app)/history/+page.svelte index e43f77e8..f7443522 100644 --- a/src/routes/(app)/history/+page.svelte +++ b/src/routes/(app)/history/+page.svelte @@ -309,7 +309,7 @@ {calories.amount} kcal - {getFoodCategoryLongvalue(foodCategories, calories.category)} Date: Fri, 13 Feb 2026 21:32:54 +0100 Subject: [PATCH 06/18] move journey review to dashboard section --- src/routes/(app)/+page.svelte | 144 ++++++++++++++++++++++++++- src/routes/(app)/review/+page.svelte | 97 ------------------ src/routes/(app)/review/+page.ts | 17 ---- 3 files changed, 140 insertions(+), 118 deletions(-) delete mode 100644 src/routes/(app)/review/+page.svelte delete mode 100644 src/routes/(app)/review/+page.ts diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index bd3522f9..c091df1a 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -19,8 +19,8 @@ import { getUserContext } from '$lib/context'; import { debug } from '@tauri-apps/plugin-log'; import { Avatar, ModalDialog, NumberStepper, useRefresh } from '@thwbh/veilchen'; - import { goto, invalidate } from '$app/navigation'; - import { Plus, Trash } from 'phosphor-svelte'; + import { invalidate } from '$app/navigation'; + import { Plus, Trash, CaretDown, Lightning, TrendDown, TrendUp } from 'phosphor-svelte'; import { useEntryModal } from '$lib/composition/useEntryModal.svelte'; import { convertDateStrToDisplayDateStr, @@ -32,6 +32,10 @@ import IntakeMask from '$lib/component/intake/IntakeMask.svelte'; import { getAvatarFromUser } from '$lib/avatar'; import { differenceInDays } from 'date-fns'; + import { slide, fly } from 'svelte/transition'; + import NumberFlow from '@number-flow/svelte'; + import CaloriePlanCard from '$lib/component/journey/CaloriePlanCard.svelte'; + import EncouragementMessage from '$lib/component/journey/EncouragementMessage.svelte'; let { data } = $props(); @@ -54,6 +58,46 @@ const progress = totalDays === 0 ? 0 : Math.round(((totalDays - dayDiff) / totalDays) * 100); + // Review plan accordion + let showPlan = $state(false); + + const currentWeight = $derived(lastWeightTracker?.amount ?? weightTarget.initialWeight); + + // Average daily intake from this week's entries + const averageIntake = $derived.by(() => { + const weekList = dashboard.intakeWeekList; + if (weekList.length === 0) return 0; + const dailyTotals = new Map(); + for (const entry of weekList) { + dailyTotals.set(entry.added, (dailyTotals.get(entry.added) ?? 0) + entry.amount); + } + const total = [...dailyTotals.values()].reduce((a, b) => a + b, 0); + return Math.round(total / dailyTotals.size); + }); + const weightTrackedToday = $derived(lastWeightTracker?.added === getDateAsStr(new Date())); + const weightLabel = $derived( + !lastWeightTracker + ? 'No data' + : weightTrackedToday + ? 'Today' + : convertDateStrToDisplayDateStr(lastWeightTracker.added) + ); + const daysElapsed = $derived( + Math.max(0, differenceInDays(new Date(), parseStringAsDate(weightTarget.startDate))) + ); + const weightChange = $derived(weightTarget.initialWeight - currentWeight); + const isGaining = $derived(weightTarget.targetWeight > weightTarget.initialWeight); + const isOnTrack = $derived( + isGaining + ? currentWeight >= weightTarget.initialWeight + : currentWeight <= weightTarget.initialWeight + ); + const goalReached = $derived( + isGaining + ? currentWeight >= weightTarget.targetWeight + : currentWeight <= weightTarget.targetWeight + ); + // Avatar const avatarSrc = $derived( userContext.user ? getAvatarFromUser(userContext.user.name, userContext.user.avatar) : '' @@ -124,6 +168,16 @@ debug(`user profile=${JSON.stringify(userContext.user)}`); useRefresh(() => invalidate('data:dashboardData')); + + /** Moves an element to document.body so `position: fixed` works inside transformed ancestors. */ + function portal(node: HTMLElement) { + document.body.appendChild(node); + return { + destroy() { + node.remove(); + } + }; + }
@@ -150,15 +204,96 @@ {dayDiff} days left
+ + + {#if showPlan} +
+
+
+ + kg + + {convertDateStrToDisplayDateStr(weightTarget.startDate)} +
+
+ + kg + + {convertDateStrToDisplayDateStr(weightTarget.endDate)} +
+
+ + +
+
+ + {weightLabel} +
+
+ + kg + + {#if weightChange === 0} + No change yet + {:else if isOnTrack} + {#if isGaining} + + {:else} + + {/if} + {Math.abs(weightChange).toFixed(1)} kg + {/if} +
+
+
+ {/if} + + + {#if showPlan} +
+
+ weightTarget.initialWeight + ? 'GAIN' + : 'LOSE'} + targetCalories={intakeTarget.targetCalories} + maximumCalories={intakeTarget.maximumCalories} + {averageIntake} + /> +
+ +
+ +
+
+ {/if} @@ -178,8 +313,9 @@ diff --git a/src/routes/(app)/review/+page.svelte b/src/routes/(app)/review/+page.svelte deleted file mode 100644 index 7a8826e7..00000000 --- a/src/routes/(app)/review/+page.svelte +++ /dev/null @@ -1,97 +0,0 @@ - - -
-
-

Your Plan

- - -

- Track your progress and stay motivated on your fitness journey -

-
- - {#if !weightTarget} -
- No active weight plan found. Complete the setup wizard to create one. -
- {:else if mounted} -
- -
- -
- -
- -
- weightTarget.initialWeight ? 'GAIN' : 'LOSE'} - targetCalories={intakeTarget.targetCalories} - maximumCalories={intakeTarget.maximumCalories} - /> -
- -
- -
- {/if} -
diff --git a/src/routes/(app)/review/+page.ts b/src/routes/(app)/review/+page.ts deleted file mode 100644 index 54432dd9..00000000 --- a/src/routes/(app)/review/+page.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - getBodyData, - getLastWeightTarget, - getLastWeightTracker, - getLastIntakeTarget -} from '$lib/api/gen/commands'; - -export const ssr = false; - -export async function load() { - return { - weightTarget: await getLastWeightTarget(), - lastWeightTracker: await getLastWeightTracker(), - intakeTarget: await getLastIntakeTarget(), - bodyData: await getBodyData() - }; -} From f9e68728789aa34f33a358e5ad1e1744743208aa Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Fri, 13 Feb 2026 21:33:21 +0100 Subject: [PATCH 07/18] restructure journey review components --- src/lib/component/intake/IntakeMask.test.ts | 41 +++-- .../component/journey/CaloriePlanCard.svelte | 127 ++++++++++---- .../component/journey/CaloriePlanCard.test.ts | 152 +++++++++++++--- .../journey/EncouragementMessage.svelte | 37 +++- .../journey/EncouragementMessage.test.ts | 67 ++++--- .../component/journey/JourneyTimeline.svelte | 165 ++++++++---------- .../component/journey/JourneyTimeline.test.ts | 87 ++------- .../component/journey/UserProfileCard.svelte | 2 +- src/lib/store.ts | 7 + 9 files changed, 415 insertions(+), 270 deletions(-) create mode 100644 src/lib/store.ts diff --git a/src/lib/component/intake/IntakeMask.test.ts b/src/lib/component/intake/IntakeMask.test.ts index 0da7458a..df4393aa 100644 --- a/src/lib/component/intake/IntakeMask.test.ts +++ b/src/lib/component/intake/IntakeMask.test.ts @@ -61,8 +61,9 @@ describe('IntakeMask', () => { it('should not show category selector in readonly mode', () => { const { container } = renderWithContext(mockEntry, { readonly: true }); - const categoryButtons = container.querySelectorAll('.badge-outline-neutral'); - expect(categoryButtons.length).toBe(0); + // In readonly mode, there should be no join group (icon buttons) + const joinGroup = container.querySelector('.join'); + expect(joinGroup).toBeNull(); }); }); @@ -70,17 +71,15 @@ describe('IntakeMask', () => { it('should show category selector when isEditing is true', () => { const { container } = renderWithContext(mockEntry, { isEditing: true }); - // Should show all category options - mockCategories.forEach((cat) => { - expect(container.textContent).toContain(cat.longvalue); - }); + // Should show selected category longvalue as heading + expect(container.textContent).toContain('Lunch'); }); it('should highlight selected category', () => { const { container } = renderWithContext(mockEntry, { isEditing: true }); const lunchButton = screen.getByLabelText(/Select Lunch/i); - expect(lunchButton.className).toContain('badge-primary'); + expect(lunchButton.className).toContain('btn-accent'); }); it('should allow category selection', async () => { @@ -92,7 +91,7 @@ describe('IntakeMask', () => { await user.click(dinnerButton); // After clicking, the button should be highlighted - expect(dinnerButton.className).toContain('badge-primary'); + expect(dinnerButton.className).toContain('btn-accent'); }); it('should show all categories as selectable', () => { @@ -120,17 +119,17 @@ describe('IntakeMask', () => { }); describe('Default Mode (Non-editing)', () => { - it('should show category badge', () => { + it('should show category name', () => { renderWithContext(mockEntry, { isEditing: false }); expect(screen.getByText('Lunch')).toBeTruthy(); }); - it('should show category selector as badges in default mode', () => { + it('should show category selector as icon buttons in default mode', () => { const { container } = renderWithContext(mockEntry, { isEditing: false }); - // Category selector shows all categories as badges - const categorySelector = container.querySelector('.snap-x'); + // Category selector shows all categories as buttons in a join group + const categorySelector = container.querySelector('.join'); expect(categorySelector).toBeTruthy(); }); }); @@ -268,23 +267,23 @@ describe('IntakeMask', () => { await user.click(snackButton); // After clicking, snack should be highlighted - expect(snackButton.className).toContain('badge-primary'); + expect(snackButton.className).toContain('btn-accent'); }); }); - describe('Custom className', () => { - it('should use default className', () => { + describe('Layout', () => { + it('should render category icon buttons in a join group', () => { const { container } = renderWithContext(mockEntry); - const fieldset = container.querySelector('.fieldset.rounded-box'); - expect(fieldset).toBeTruthy(); + const joinGroup = container.querySelector('.join'); + expect(joinGroup).toBeTruthy(); }); - it('should accept custom class prop', () => { - const { container } = renderWithContext(mockEntry, { class: 'custom-class' }); + it('should render textarea for description', () => { + renderWithContext(mockEntry); - const fieldset = container.querySelector('.custom-class'); - expect(fieldset).toBeTruthy(); + const textarea = document.querySelector('textarea'); + expect(textarea).toBeTruthy(); }); }); diff --git a/src/lib/component/journey/CaloriePlanCard.svelte b/src/lib/component/journey/CaloriePlanCard.svelte index ac18cdf1..539f2a0c 100644 --- a/src/lib/component/journey/CaloriePlanCard.svelte +++ b/src/lib/component/journey/CaloriePlanCard.svelte @@ -1,57 +1,118 @@ -
-

Calorie Plan

-
- {#if !isHolding || dailyRate !== 0} -
- {rateLabel} - {dailyRate} kcal +
+
+

Calorie Plan

+ {goalLabel} +
+ + +
+ + + + kcal / day +
+ + +
+ +
+
+ Target + {targetCalories} kcal +
+
+
+
+
+ + +
+
+ Your average + {#if averageIntake} + + {averageIntake} kcal + + {:else} + No data yet + {/if} +
+
+
- {/if} -
- {targetLabel} - {targetCalories} kcal
-
- {maximumLabel} - {maximumCalories} kcal + +
+ 0 + {maximumCalories} kcal max
- {#if isHolding} -
-

- Your calorie target is set to maintain your current weight. Stay within this range to keep - your weight stable. -

+ + + {#if !isHolding || dailyRate !== 0} +
+ Daily {rateLabel} + {dailyRate} kcal
{/if} +
+ Maximum + {maximumCalories} kcal +
+ + {#if isHolding} +

+ Your target is set to maintain current weight. Stay within this range. +

+ {/if}
diff --git a/src/lib/component/journey/CaloriePlanCard.test.ts b/src/lib/component/journey/CaloriePlanCard.test.ts index 5dc64b36..28ce66ca 100644 --- a/src/lib/component/journey/CaloriePlanCard.test.ts +++ b/src/lib/component/journey/CaloriePlanCard.test.ts @@ -1,7 +1,36 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi } from 'vitest'; import { render, screen } from '@testing-library/svelte'; import CaloriePlanCard from './CaloriePlanCard.svelte'; +// Mock NumberFlow +vi.mock('@number-flow/svelte', () => { + const NumberFlowMock = function (anchor: any, props: any) { + const value = props?.value ?? 0; + const textNode = document.createTextNode(String(value)); + + if (anchor && anchor.parentNode) { + anchor.parentNode.insertBefore(textNode, anchor); + } + + return { + p: (newProps: any) => { + if (newProps.value !== undefined) { + textNode.textContent = String(newProps.value); + } + }, + d: () => { + if (textNode.parentNode) { + textNode.parentNode.removeChild(textNode); + } + } + }; + }; + + return { + default: NumberFlowMock + }; +}); + describe('CaloriePlanCard Component', () => { it('should render calorie plan title', () => { render(CaloriePlanCard, { @@ -16,8 +45,22 @@ describe('CaloriePlanCard Component', () => { expect(screen.getByText('Calorie Plan')).toBeInTheDocument(); }); + it('should display hero target calories number', () => { + const { container } = render(CaloriePlanCard, { + props: { + recommendation: 'LOSE', + dailyRate: 500, + targetCalories: 1800, + maximumCalories: 2000 + } + }); + + expect(container.textContent).toContain('1800'); + expect(container.textContent).toContain('kcal / day'); + }); + it('should display daily deficit for weight loss', () => { - render(CaloriePlanCard, { + const { container } = render(CaloriePlanCard, { props: { recommendation: 'LOSE', dailyRate: 500, @@ -26,12 +69,12 @@ describe('CaloriePlanCard Component', () => { } }); - expect(screen.getByText('Daily Deficit')).toBeInTheDocument(); - expect(screen.getByText('500 kcal')).toBeInTheDocument(); + expect(container.textContent).toContain('Daily deficit'); + expect(container.textContent).toContain('500 kcal'); }); it('should display daily surplus for weight gain', () => { - render(CaloriePlanCard, { + const { container } = render(CaloriePlanCard, { props: { recommendation: 'GAIN', dailyRate: 300, @@ -40,12 +83,12 @@ describe('CaloriePlanCard Component', () => { } }); - expect(screen.getByText('Daily Surplus')).toBeInTheDocument(); - expect(screen.getByText('300 kcal')).toBeInTheDocument(); + expect(container.textContent).toContain('Daily surplus'); + expect(container.textContent).toContain('300 kcal'); }); - it('should display target intake with correct label for weight loss', () => { - render(CaloriePlanCard, { + it('should display goal label for weight loss', () => { + const { container } = render(CaloriePlanCard, { props: { recommendation: 'LOSE', dailyRate: 500, @@ -54,12 +97,11 @@ describe('CaloriePlanCard Component', () => { } }); - expect(screen.getByText('Target Intake (Loss)')).toBeInTheDocument(); - expect(screen.getByText('1800 kcal')).toBeInTheDocument(); + expect(container.textContent).toContain('Lose Weight'); }); - it('should display target intake with correct label for weight gain', () => { - render(CaloriePlanCard, { + it('should display goal label for weight gain', () => { + const { container } = render(CaloriePlanCard, { props: { recommendation: 'GAIN', dailyRate: 300, @@ -68,12 +110,11 @@ describe('CaloriePlanCard Component', () => { } }); - expect(screen.getByText('Target Intake (Gain)')).toBeInTheDocument(); - expect(screen.getByText('2500 kcal')).toBeInTheDocument(); + expect(container.textContent).toContain('Gain Weight'); }); it('should display maximum calories', () => { - render(CaloriePlanCard, { + const { container } = render(CaloriePlanCard, { props: { recommendation: 'LOSE', dailyRate: 500, @@ -82,12 +123,12 @@ describe('CaloriePlanCard Component', () => { } }); - expect(screen.getByText('Maximum Limit')).toBeInTheDocument(); - expect(screen.getByText('2000 kcal')).toBeInTheDocument(); + expect(container.textContent).toContain('Maximum'); + expect(container.textContent).toContain('2000 kcal'); }); it('should handle HOLD recommendation', () => { - render(CaloriePlanCard, { + const { container } = render(CaloriePlanCard, { props: { recommendation: 'HOLD', dailyRate: 0, @@ -96,12 +137,12 @@ describe('CaloriePlanCard Component', () => { } }); - expect(screen.getByText('Target Intake (Maintain)')).toBeInTheDocument(); - expect(screen.getByText('2200 kcal')).toBeInTheDocument(); + expect(container.textContent).toContain('Maintain Weight'); + expect(container.textContent).toContain('2200'); }); it('should show maintenance message for HOLD', () => { - render(CaloriePlanCard, { + const { container } = render(CaloriePlanCard, { props: { recommendation: 'HOLD', dailyRate: 0, @@ -110,9 +151,7 @@ describe('CaloriePlanCard Component', () => { } }); - expect( - screen.getByText(/Your calorie target is set to maintain your current weight/i) - ).toBeInTheDocument(); + expect(container.textContent).toMatch(/maintain current weight/i); }); it('should not show daily rate when HOLD with zero rate', () => { @@ -125,8 +164,67 @@ describe('CaloriePlanCard Component', () => { } }); - // Should not show "Daily Adjustment" when rate is 0 - expect(screen.queryByText('Daily Adjustment')).not.toBeInTheDocument(); + expect(container.textContent).not.toContain('Daily adjustment'); + }); + + it('should render target bar', () => { + const { container } = render(CaloriePlanCard, { + props: { + recommendation: 'LOSE', + dailyRate: 500, + targetCalories: 1800, + maximumCalories: 2000 + } + }); + + const bar = container.querySelector('.bg-primary.rounded-full'); + expect(bar).toBeTruthy(); + }); + + it('should render average intake bar when provided', () => { + const { container } = render(CaloriePlanCard, { + props: { + recommendation: 'LOSE', + dailyRate: 500, + targetCalories: 1800, + maximumCalories: 2000, + averageIntake: 1600 + } + }); + + expect(container.textContent).toContain('1600 kcal'); + expect(container.textContent).toContain('Your average'); + const bar = container.querySelector('.bg-success.rounded-full'); + expect(bar).toBeTruthy(); + }); + + it('should show warning bar when average exceeds target', () => { + const { container } = render(CaloriePlanCard, { + props: { + recommendation: 'LOSE', + dailyRate: 500, + targetCalories: 1800, + maximumCalories: 2000, + averageIntake: 1900 + } + }); + + const bar = container.querySelector('.bg-warning.rounded-full'); + expect(bar).toBeTruthy(); + }); + + it('should show no data message when average intake is zero', () => { + const { container } = render(CaloriePlanCard, { + props: { + recommendation: 'LOSE', + dailyRate: 500, + targetCalories: 1800, + maximumCalories: 2000, + averageIntake: 0 + } + }); + + expect(container.textContent).toContain('No data yet'); }); it('should apply correct styling', () => { diff --git a/src/lib/component/journey/EncouragementMessage.svelte b/src/lib/component/journey/EncouragementMessage.svelte index f515507e..1700379a 100644 --- a/src/lib/component/journey/EncouragementMessage.svelte +++ b/src/lib/component/journey/EncouragementMessage.svelte @@ -1,8 +1,39 @@ - - Remember: -

Consistency is key. Small daily actions lead to big results!

+ +

{message.text}

diff --git a/src/lib/component/journey/EncouragementMessage.test.ts b/src/lib/component/journey/EncouragementMessage.test.ts index 0f02da55..3d5cf6a7 100644 --- a/src/lib/component/journey/EncouragementMessage.test.ts +++ b/src/lib/component/journey/EncouragementMessage.test.ts @@ -7,38 +7,65 @@ import { setupVeilchenMock } from '../../../../tests/utils/mocks'; setupVeilchenMock(); describe('EncouragementMessage Component', () => { - it('should render the encouragement message', () => { - render(EncouragementMessage); + const defaultProps = { + daysElapsed: 10, + daysLeft: 100, + averageIntake: 0, + targetCalories: 1800, + goalReached: false + }; - expect(screen.getByText('Remember:')).toBeInTheDocument(); + it('should render a contextual message', () => { + const { container } = render(EncouragementMessage, { props: defaultProps }); + + expect(container.querySelector('.text-sm')).toBeDefined(); + }); + + it('should show goal reached message', () => { + const { container } = render(EncouragementMessage, { + props: { ...defaultProps, goalReached: true } + }); + + expect(container.textContent).toContain('You did it!'); + }); + + it('should show near-end message when close to finish', () => { + const { container } = render(EncouragementMessage, { + props: { ...defaultProps, daysLeft: 10, daysElapsed: 100, averageIntake: 1500 } + }); + + expect(container.textContent).toContain('finish line'); }); - it('should display the motivational text', () => { - render(EncouragementMessage); + it('should show early start message for new journeys', () => { + const { container } = render(EncouragementMessage, { + props: { ...defaultProps, daysElapsed: 1, daysLeft: 120 } + }); - expect( - screen.getByText('Consistency is key. Small daily actions lead to big results!') - ).toBeInTheDocument(); + expect(container.textContent).toContain('Great start'); }); - it('should render as an info alert', () => { - const { container } = render(EncouragementMessage); + it('should show no-data message when average intake is zero', () => { + const { container } = render(EncouragementMessage, { + props: { ...defaultProps, averageIntake: 0 } + }); - // The component uses AlertBox with AlertType.Info - expect(container.querySelector('.alert')).toBeDefined(); + expect(container.textContent).toContain('Consistency'); }); - it('should apply correct text styling', () => { - const { container } = render(EncouragementMessage); + it('should show on-target message when average is within target', () => { + const { container } = render(EncouragementMessage, { + props: { ...defaultProps, averageIntake: 1700, targetCalories: 1800 } + }); - const textElement = container.querySelector('.text-sm'); - expect(textElement).toBeDefined(); + expect(container.textContent).toContain('within your daily target'); }); - it('should render without props', () => { - const { container } = render(EncouragementMessage); + it('should show above-target message when averaging over', () => { + const { container } = render(EncouragementMessage, { + props: { ...defaultProps, averageIntake: 2100, targetCalories: 1800 } + }); - expect(container).toBeDefined(); - expect(screen.getByText('Remember:')).toBeInTheDocument(); + expect(container.textContent).toContain('above target'); }); }); diff --git a/src/lib/component/journey/JourneyTimeline.svelte b/src/lib/component/journey/JourneyTimeline.svelte index a3c199a2..a7cee8f9 100644 --- a/src/lib/component/journey/JourneyTimeline.svelte +++ b/src/lib/component/journey/JourneyTimeline.svelte @@ -1,7 +1,7 @@ -
-
-

Journey Timeline

+
+
+

Journey Timeline

+ {daysLeft} days left +
-
    - -
  • -
    -
    -
    - - - {convertDateStrToDisplayDateStr(startDate)} - -
    -
    - kg -
    - Starting Weight -
    -
    -
    -
    -
    -
    -
  • + +
    +
    +
    - -
  • -
    -
    -
    -
    -
    -
    -
    - - Today -
    -
    - kg -
    -
    - {#if weightChange === 0} - No change yet - {:else if isOnTrack} - {#if isGaining} - - +{Math.abs(weightChange).toFixed(1)} kg gained - {:else} - - {Math.abs(weightChange).toFixed(1)} kg lost - {/if} - {/if} -
    -
    -
    -
    -
  • + +
    +
    + + kg + + {convertDateStrToDisplayDateStr(startDate)} +
    +
    + + kg + + {convertDateStrToDisplayDateStr(endDate)} +
    +
    - -
  • -
    -
    -
    -
    - - - {convertDateStrToDisplayDateStr(endDate)} - -
    -
    - kg -
    - Target Weight -
    -
    -
    -
    -
    -
  • -
+ +
+
+ + Today +
+
+ + kg + + {#if weightChange === 0} + No change yet + {:else if isOnTrack} + {#if isGaining} + + {:else} + + {/if} + {Math.abs(weightChange).toFixed(1)} kg + {/if} +
+ + diff --git a/src/lib/component/journey/JourneyTimeline.test.ts b/src/lib/component/journey/JourneyTimeline.test.ts index 1d10ce29..5064b44a 100644 --- a/src/lib/component/journey/JourneyTimeline.test.ts +++ b/src/lib/component/journey/JourneyTimeline.test.ts @@ -34,43 +34,22 @@ describe('JourneyTimeline Component', () => { expect(screen.getByText('Journey Timeline')).toBeInTheDocument(); }); - it('should display starting weight label', () => { - render(JourneyTimeline, { props: mockProps }); - - expect(screen.getByText('Starting Weight')).toBeInTheDocument(); - }); - it('should display today label', () => { render(JourneyTimeline, { props: mockProps }); expect(screen.getByText('Today')).toBeInTheDocument(); }); - it('should display target weight label', () => { - render(JourneyTimeline, { props: mockProps }); + it('should display days left', () => { + const { container } = render(JourneyTimeline, { props: mockProps }); - expect(screen.getByText('Target Weight')).toBeInTheDocument(); + expect(container.textContent).toMatch(/days left/); }); it('should show weight loss progress', () => { - render(JourneyTimeline, { props: mockProps }); - - // Should show "kg lost" for weight loss - expect(screen.getByText(/kg lost/i)).toBeInTheDocument(); - }); - - it('should show weight gain progress', () => { - render(JourneyTimeline, { - props: { - ...mockProps, - initialWeight: 60, - targetWeight: 70, - currentWeight: 65 - } - }); + const { container } = render(JourneyTimeline, { props: mockProps }); - // Should show "kg gained" for weight gain - expect(screen.getByText(/kg gained/i)).toBeInTheDocument(); + expect(container.textContent).toMatch(/5\.0 kg/); }); it('should show no change message when weight unchanged', () => { @@ -84,28 +63,25 @@ describe('JourneyTimeline Component', () => { expect(screen.getByText('No change yet')).toBeInTheDocument(); }); - it('should render timeline structure', () => { + it('should render progress bar', () => { const { container } = render(JourneyTimeline, { props: mockProps }); - const timeline = container.querySelector('.timeline.timeline-vertical'); - expect(timeline).toBeDefined(); - - // Should have 3 timeline items (start, current, target) - const timelineItems = container.querySelectorAll('.timeline'); - expect(timelineItems.length).toBeGreaterThan(0); + const track = container.querySelector('.journey-bar-track'); + const fill = container.querySelector('.journey-bar-fill'); + expect(track).toBeTruthy(); + expect(fill).toBeTruthy(); }); - it('should apply correct card styling', () => { + it('should apply primary card styling', () => { const { container } = render(JourneyTimeline, { props: mockProps }); - const wrapper = container.querySelector('.bg-base-100.rounded-box.p-6.shadow'); - expect(wrapper).toBeDefined(); + const wrapper = container.querySelector('.bg-primary'); + expect(wrapper).toBeTruthy(); }); it('should display formatted dates', () => { render(JourneyTimeline, { props: mockProps }); - // Check that dates are rendered (formatted by mock) expect(screen.getByText(/Jan.*1.*2025/i)).toBeInTheDocument(); expect(screen.getByText(/Jun.*1.*2025/i)).toBeInTheDocument(); }); @@ -121,45 +97,14 @@ describe('JourneyTimeline Component', () => { } }); - // Should show timeline even when maintaining weight expect(screen.getByText('Journey Timeline')).toBeInTheDocument(); expect(screen.getByText('Today')).toBeInTheDocument(); }); - it('should show calendar icon for start date', () => { + it('should render today callout with inverted colors', () => { const { container } = render(JourneyTimeline, { props: mockProps }); - // Check for timeline structure with calendar icon - expect(container.querySelector('.timeline-start')).toBeDefined(); - }); - - it('should show lightning icon for current date', () => { - const { container } = render(JourneyTimeline, { props: mockProps }); - - // Check for current date with accent styling - const currentBox = container.querySelector('.bg-secondary.text-secondary-content'); - expect(currentBox).toBeDefined(); - }); - - it('should show target icon for end date', () => { - const { container } = render(JourneyTimeline, { props: mockProps }); - - // Check for target date timeline item - expect(container.querySelector('.timeline-start')).toBeDefined(); - }); - - it('should calculate weight change correctly', () => { - render(JourneyTimeline, { - props: { - startDate: '2025-01-01', - endDate: '2025-06-01', - initialWeight: 80, - targetWeight: 70, - currentWeight: 75 - } - }); - - // 80 - 75 = 5 kg lost - expect(screen.getByText('5.0 kg lost')).toBeInTheDocument(); + const todayCallout = container.querySelector('.bg-primary-content.text-primary'); + expect(todayCallout).toBeTruthy(); }); }); diff --git a/src/lib/component/journey/UserProfileCard.svelte b/src/lib/component/journey/UserProfileCard.svelte index f12993d7..c43f1bfb 100644 --- a/src/lib/component/journey/UserProfileCard.svelte +++ b/src/lib/component/journey/UserProfileCard.svelte @@ -16,7 +16,7 @@ let ActivityIcon = $derived(activityInfo.icon); -
+
diff --git a/src/lib/store.ts b/src/lib/store.ts new file mode 100644 index 00000000..6da0214b --- /dev/null +++ b/src/lib/store.ts @@ -0,0 +1,7 @@ +import { Store } from '@tauri-apps/plugin-store'; + +const store = new Store('settings.json'); + +export const showHint = async (feat: string) => { + return false; +}; From cb4e18e36166de12ec8839160d255b881e706869 Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sat, 14 Feb 2026 11:26:54 +0100 Subject: [PATCH 08/18] display target and actual average as progress bars --- .../component/journey/CaloriePlanCard.svelte | 76 +++++++++++-------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/src/lib/component/journey/CaloriePlanCard.svelte b/src/lib/component/journey/CaloriePlanCard.svelte index 539f2a0c..d735fae9 100644 --- a/src/lib/component/journey/CaloriePlanCard.svelte +++ b/src/lib/component/journey/CaloriePlanCard.svelte @@ -48,51 +48,54 @@ kcal / day
- -
- -
-
- Target - {targetCalories} kcal -
-
+ +
+ +
+ Target + {targetCalories} kcal +
+ + +
+ +
-
- -
-
+ +
Your average {#if averageIntake} - - {averageIntake} kcal - + {averageIntake} kcal {:else} No data yet {/if}
-
+ + +
-
-
- 0 - {maximumCalories} kcal max + + {#if averageIntake} +
+ +
+ +
+
+ {/if}
@@ -100,14 +103,12 @@ {#if !isHolding || dailyRate !== 0}
Daily {rateLabel} - {dailyRate} kcal + {dailyRate} kcal
{/if}
Maximum - {maximumCalories} kcal + {maximumCalories} kcal
{#if isHolding} @@ -116,3 +117,14 @@

{/if}
+ + From 4de7f010f6f0dd0ed4f573a894e28775952911cd Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sat, 14 Feb 2026 11:46:50 +0100 Subject: [PATCH 09/18] clean up calorie plan --- .../component/journey/CaloriePlanCard.svelte | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/lib/component/journey/CaloriePlanCard.svelte b/src/lib/component/journey/CaloriePlanCard.svelte index d735fae9..c5116cfe 100644 --- a/src/lib/component/journey/CaloriePlanCard.svelte +++ b/src/lib/component/journey/CaloriePlanCard.svelte @@ -32,6 +32,10 @@ ); const averageOverTarget = $derived(averageIntake != null && averageIntake > targetCalories); + + const actualDeficit = $derived( + averageIntake != null ? maximumCalories - averageIntake : undefined + );
@@ -102,14 +106,16 @@ {#if !isHolding || dailyRate !== 0}
- Daily {rateLabel} - {dailyRate} kcal + Planned {rateLabel} + {dailyRate} kcal +
+ {/if} + {#if actualDeficit != null} +
+ Actual {rateLabel} + {Math.abs(actualDeficit)} kcal
{/if} -
- Maximum - {maximumCalories} kcal -
{#if isHolding}

From ec29955793a5df29eac632682373c48e3ec4469e Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sat, 14 Feb 2026 15:36:42 +0100 Subject: [PATCH 10/18] return category shortvalue for chart data instead of longvalue --- src-tauri/src/service/progress.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/src/service/progress.rs b/src-tauri/src/service/progress.rs index 3a3f389f..fec5a813 100644 --- a/src-tauri/src/service/progress.rs +++ b/src-tauri/src/service/progress.rs @@ -169,7 +169,7 @@ fn process_intake( .into_iter() .map(|(category, (sum, count))| { FoodCategory::find_by_key(conn, category) - .map(|cat| (cat.longvalue, math_f32::floor_f32(sum / count as f32, 0))) + .map(|cat| (cat.shortvalue, math_f32::floor_f32(sum / count as f32, 0))) .map_err(|e| format!("Failed to get food category: {}", e)) }) .collect::, String>>()?; From 1270d63b748e57f913b68f342f583a5653b6c30b Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sat, 14 Feb 2026 15:37:42 +0100 Subject: [PATCH 11/18] restructure progress page --- src/routes/(app)/progress/+page.svelte | 340 ++++++++++++++++++------- 1 file changed, 251 insertions(+), 89 deletions(-) diff --git a/src/routes/(app)/progress/+page.svelte b/src/routes/(app)/progress/+page.svelte index 7ff8e028..ae39030b 100644 --- a/src/routes/(app)/progress/+page.svelte +++ b/src/routes/(app)/progress/+page.svelte @@ -1,154 +1,316 @@ -

+

Progress

- Your progress - + +
+
+ Your Progress + Day {daysPassed} of {daysTotal} +
-
-
-
Starting weight
-
- {data.trackerProgress.weightChartData.values[0]}kg + +
+
+
-
-
Current weight
-
- {data.trackerProgress.weightChartData.values[ - data.trackerProgress.weightChartData.values.length - 1 - ]} - kg + + +
+
+ + kg + + Start +
+ +
+ {#if weightDiff === 0} + No change + {:else} + {#if weightDiff > 0} + + {:else} + + {/if} + {Math.abs(weightDiff).toFixed(1)} kg + {/if} +
+ +
+ + kg + + Current
- - -
-
-
Average per day
-
- {data.trackerProgress.intakeChartData.avg} - kcal + +
+ +
+
+

Weight

+
+ + Actual + + + Target + +
-
Target: {data.trackerProgress.intakeTarget.targetCalories} kcal
+
-
-
∅ Deficit
-
- {data.trackerProgress.intakeTarget.maximumCalories - - data.trackerProgress.intakeChartData.dailyAverage} - kcal + + +
+
+

Calorie Intake

+
+ + Actual + + + + Target + +
-
- Target: {data.trackerProgress.intakeTarget.maximumCalories - - data.trackerProgress.intakeTarget.targetCalories} + + +
+
+ Average per day +
+ + kcal +
+ Target: {intakeTarget.targetCalories} kcal +
+
+ Average {rateLabel} +
+ + kcal +
+ Target: {targetDeficit} +
+ + + {#if categories.length > 0} +
+

By Category

+
+ {#each categories as [code, avg] (code)} + {@const Icon = getFoodCategoryIcon(code)} + {@const percent = Math.round((avg / maxCategoryAvg) * 100)} +
+
+ {#if Icon} + + {/if} +
+
+
+ {getFoodCategoryLongvalue(foodCategories, code) ?? code} + {Math.round(avg)} kcal +
+
+
+
+
+
+ {/each} +
+
+ {/if}
+ + From 43498815ecd907dce9f58c6377d10f6969a8e8b6 Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sat, 14 Feb 2026 15:38:15 +0100 Subject: [PATCH 12/18] update vitest tests --- .../component/journey/CaloriePlanCard.test.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lib/component/journey/CaloriePlanCard.test.ts b/src/lib/component/journey/CaloriePlanCard.test.ts index 28ce66ca..182976dc 100644 --- a/src/lib/component/journey/CaloriePlanCard.test.ts +++ b/src/lib/component/journey/CaloriePlanCard.test.ts @@ -59,7 +59,7 @@ describe('CaloriePlanCard Component', () => { expect(container.textContent).toContain('kcal / day'); }); - it('should display daily deficit for weight loss', () => { + it('should display planned deficit for weight loss', () => { const { container } = render(CaloriePlanCard, { props: { recommendation: 'LOSE', @@ -69,11 +69,11 @@ describe('CaloriePlanCard Component', () => { } }); - expect(container.textContent).toContain('Daily deficit'); + expect(container.textContent).toContain('Planned deficit'); expect(container.textContent).toContain('500 kcal'); }); - it('should display daily surplus for weight gain', () => { + it('should display planned surplus for weight gain', () => { const { container } = render(CaloriePlanCard, { props: { recommendation: 'GAIN', @@ -83,7 +83,7 @@ describe('CaloriePlanCard Component', () => { } }); - expect(container.textContent).toContain('Daily surplus'); + expect(container.textContent).toContain('Planned surplus'); expect(container.textContent).toContain('300 kcal'); }); @@ -113,7 +113,7 @@ describe('CaloriePlanCard Component', () => { expect(container.textContent).toContain('Gain Weight'); }); - it('should display maximum calories', () => { + it('should display target calories in bar label', () => { const { container } = render(CaloriePlanCard, { props: { recommendation: 'LOSE', @@ -123,8 +123,8 @@ describe('CaloriePlanCard Component', () => { } }); - expect(container.textContent).toContain('Maximum'); - expect(container.textContent).toContain('2000 kcal'); + expect(container.textContent).toContain('Target'); + expect(container.textContent).toContain('1800 kcal'); }); it('should handle HOLD recommendation', () => { @@ -164,7 +164,7 @@ describe('CaloriePlanCard Component', () => { } }); - expect(container.textContent).not.toContain('Daily adjustment'); + expect(container.textContent).not.toContain('Planned adjustment'); }); it('should render target bar', () => { @@ -194,11 +194,11 @@ describe('CaloriePlanCard Component', () => { expect(container.textContent).toContain('1600 kcal'); expect(container.textContent).toContain('Your average'); - const bar = container.querySelector('.bg-success.rounded-full'); + const bar = container.querySelector('.bg-accent.rounded-full'); expect(bar).toBeTruthy(); }); - it('should show warning bar when average exceeds target', () => { + it('should show accent bar when average exceeds target', () => { const { container } = render(CaloriePlanCard, { props: { recommendation: 'LOSE', @@ -209,7 +209,7 @@ describe('CaloriePlanCard Component', () => { } }); - const bar = container.querySelector('.bg-warning.rounded-full'); + const bar = container.querySelector('.bg-accent.rounded-full'); expect(bar).toBeTruthy(); }); From 587c77f5ccb5661133b9c798e369df7e74a5cfdb Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sat, 14 Feb 2026 15:38:55 +0100 Subject: [PATCH 13/18] update history colors; allow adding/editing weight tracker entries in history --- src/routes/(app)/history/+page.svelte | 309 +++++++++++++++++++------- 1 file changed, 233 insertions(+), 76 deletions(-) diff --git a/src/routes/(app)/history/+page.svelte b/src/routes/(app)/history/+page.svelte index f7443522..f831c30b 100644 --- a/src/routes/(app)/history/+page.svelte +++ b/src/routes/(app)/history/+page.svelte @@ -3,6 +3,7 @@ type Intake, type IntakeTarget, type NewIntake, + type NewWeightTracker, type TrackerHistory, type WeightTracker } from '$lib/api'; @@ -10,8 +11,8 @@ import NumberFlow from '@number-flow/svelte'; import { addDays, compareAsc, subDays } from 'date-fns'; import { getFoodCategoryLongvalue } from '$lib/api/category'; - import { CaretLeft, CaretRight, Pencil, Trash } from 'phosphor-svelte'; - import { ModalDialog, SwipeableListItem } from '@thwbh/veilchen'; + import { CaretLeft, CaretRight, HandTap, Pencil, Trash } from 'phosphor-svelte'; + import { ModalDialog, NumberStepper, SwipeableListItem } from '@thwbh/veilchen'; import { longpress } from '$lib/gesture/long-press'; import { vibrate } from '@tauri-apps/plugin-haptics'; import { getCategoriesContext } from '$lib/context'; @@ -21,9 +22,11 @@ import { cubicOut } from 'svelte/easing'; import { createIntake, + createWeightTrackerEntry, deleteIntake, getTrackerHistory, - updateIntake + updateIntake, + updateWeightTrackerEntry } from '$lib/api/gen/commands.js'; import { debug } from '@tauri-apps/plugin-log'; import IntakeScore from '$lib/component/intake/IntakeScore.svelte'; @@ -112,6 +115,42 @@ } }); + const modalWeight = useEntryModal({ + onCreate: (entry) => createWeightTrackerEntry({ newEntry: entry }), + onUpdate: (id, entry) => updateWeightTrackerEntry({ trackerId: id, updatedEntry: entry }), + onDelete: (_) => { + throw new Error('Delete not supported for weight entries'); + }, + getBlankEntry: () => ({ + added: selectedDateStr, + amount: 0 + }), + onCreateSuccess: (newEntry) => { + trackerHistory = { + ...trackerHistory, + weightHistory: { + ...trackerHistory.weightHistory, + [selectedDateStr]: [...trackerHistory.weightHistory[selectedDateStr], newEntry] + } + }; + }, + onUpdateSuccess: (updatedEntry) => { + const entries = [...trackerHistory.weightHistory[selectedDateStr]]; + + const idx = entries.findIndex((e) => e.id === updatedEntry.id); + if (idx !== -1) { + entries[idx] = updatedEntry; + trackerHistory = { + ...trackerHistory, + weightHistory: { + ...trackerHistory.weightHistory, + [selectedDateStr]: entries + } + }; + } + } + }); + const selectHistory = (dateStr: string) => { debug(`selectHistory dateStr={${dateStr}}`); swipeDirection = null; // Reset direction when clicking a date @@ -154,7 +193,9 @@ }; const getActiveClass = (dateStr: string) => - dateStr === selectedDateStr ? 'bg-primary text-primary-content' : ''; + dateStr === selectedDateStr + ? 'bg-primary-content text-primary' + : 'text-primary-content/70 hover:bg-primary-content/10'; const edit = async (calories: Intake) => { await vibrate(2); @@ -166,6 +207,16 @@ modal.openDelete(calories); }; + const createWeight = async () => { + await vibrate(2); + modalWeight.openCreate(); + }; + + const editWeight = async (weight: WeightTracker) => { + await vibrate(2); + modalWeight.openEdit(weight); + }; + // Animation parameters based on swipe direction // When swiping left, new content comes from right (positive x) // When swiping right, new content comes from left (negative x) @@ -217,25 +268,32 @@ }; -
+

History

-
+ +
{#if selectedDateStr} {@const selectedDate = parseStringAsDate(selectedDateStr)} -
- - {getDateAsStr(selectedDate, 'MMMM yyyy')} - +
+ {getDateAsStr(selectedDate, 'MMMM yyyy')} +
+ + + + avg kcal/day +
{/if} + +
({ timeframe: 300, minSwipeDistance: 60, touchAction: 'pan-y' })} onswipe={handleWeekSwipe} > - {#each dates as dateStr} @@ -243,20 +301,18 @@ {@const dayName = getDateAsStr(parseStringAsDate(dateStr), 'EE')} {/each} {#if showRightCaret} - {:else}
@@ -264,20 +320,19 @@
-
+ +
{#key selectedDateStr} -
+
+
({ timeframe: 300, minSwipeDistance: 60, touchAction: 'pan-y' })} onswipe={handleDaySwipe} > -
-
-
Average calories
-
-
-
- c.amount)} @@ -285,66 +340,88 @@ />
-
- {#each intakeHistory as calories} - edit(calories)} onright={() => remove(calories)}> + + {#if intakeHistory.length > 0} +
+ {#each intakeHistory as calories, i} + edit(calories)} onright={() => remove(calories)}> + {#snippet leftAction()} + + {/snippet} + + {#snippet rightAction()} + + {/snippet} + +
edit(calories)} + > +
+ + {calories.description} + + + {calories.amount} kcal + +
+ {getFoodCategoryLongvalue(foodCategories, calories.category)} +
+
+ {/each} +
+ {/if} + + + + +
+ {#if weightHistory.length > 0} + editWeight(weightHistory[0])}> {#snippet leftAction()} {/snippet} - - {#snippet rightAction()} - - {/snippet} - -
edit(calories)} - > -
- - {calories.description} - - - {calories.amount} kcal - +
+ Weight +
+ + kg
- {getFoodCategoryLongvalue(foodCategories, calories.category)} -
- {/each} - - -
-
({ timeframe: 300, minSwipeDistance: 60, touchAction: 'pan-y' })} - onswipe={handleDaySwipe} - > -
-
Weight
+
+ + {:else} +
+
+ Weight - {#if weightHistory.length > 0} -
- {weightHistory[0].amount} kg +
No weight tracked.
- {:else} -
No weight tracked.
- {/if} -
+ + +
+ {/if}
{/key}
+ {#snippet title()} - Add Intake + Add Intake Date: {convertDateStrToDisplayDateStr(selectedDateStr)} @@ -357,10 +434,11 @@ {/snippet} + {#snippet title()} {#if modal.currentEntry} - Edit Intake + Edit Intake Added: {convertDateStrToDisplayDateStr((modal.currentEntry as Intake).added)} @@ -390,6 +468,7 @@ {/snippet} + Cancel {/snippet} + + + + {#snippet title()} + Set Weight + {#if modalWeight.currentEntry} + + {convertDateStrToDisplayDateStr((modalWeight.currentEntry as WeightTracker).added)} + + {/if} + {/snippet} + {#snippet content()} +
+ {#if modalWeight.errorMessage} +
+ {modalWeight.errorMessage} +
+ {/if} + {#if modalWeight.currentEntry} + + {/if} +
+ {/snippet} +
+ + + + {#snippet title()} + Set Weight + {#if modalWeight.currentEntry} + + {convertDateStrToDisplayDateStr((modalWeight.currentEntry as WeightTracker).added)} + + {/if} + {/snippet} + {#snippet content()} +
+ {#if modalWeight.errorMessage} +
+ {modalWeight.errorMessage} +
+ {/if} + {#if modalWeight.currentEntry} + + {/if} +
+ {/snippet} +
From 0e97c3f907f5bfae24e690178d1bf61137eb6234 Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sat, 14 Feb 2026 17:47:29 +0100 Subject: [PATCH 14/18] display tracked category overview and empty section in history page --- src/routes/(app)/history/+page.svelte | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/routes/(app)/history/+page.svelte b/src/routes/(app)/history/+page.svelte index f831c30b..408b058a 100644 --- a/src/routes/(app)/history/+page.svelte +++ b/src/routes/(app)/history/+page.svelte @@ -10,8 +10,8 @@ import { convertDateStrToDisplayDateStr, getDateAsStr, parseStringAsDate } from '$lib/date.js'; import NumberFlow from '@number-flow/svelte'; import { addDays, compareAsc, subDays } from 'date-fns'; - import { getFoodCategoryLongvalue } from '$lib/api/category'; - import { CaretLeft, CaretRight, HandTap, Pencil, Trash } from 'phosphor-svelte'; + import { getFoodCategoryIcon, getFoodCategoryLongvalue } from '$lib/api/category'; + import { CaretLeft, CaretRight, ForkKnife, HandTap, Pencil, Trash } from 'phosphor-svelte'; import { ModalDialog, NumberStepper, SwipeableListItem } from '@thwbh/veilchen'; import { longpress } from '$lib/gesture/long-press'; import { vibrate } from '@tauri-apps/plugin-haptics'; @@ -340,6 +340,18 @@ />
+ +
+ {#each foodCategories as cat (cat.shortvalue)} + {@const Icon = getFoodCategoryIcon(cat.shortvalue)} + {@const isTracked = intakeHistory.some((e) => e.category === cat.shortvalue)} + + + {/each} +
+ {#if intakeHistory.length > 0}
@@ -375,6 +387,16 @@ {/each}
+ {:else} +
+ +
+

No meals logged

+

Tap below to start tracking

+
+
{/if} From 4996ba0e6e366a5b567c817a1e4edc1265ab819c Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sun, 15 Feb 2026 00:22:27 +0100 Subject: [PATCH 15/18] remove unused assets; add deconstructed logo icons --- src-tauri/src/service/dashboard.rs | 6 ++ src/lib/assets/icons/alert-circle-filled.svg | 1 - src/lib/assets/icons/arrow-right.svg | 1 - src/lib/assets/icons/book-bookmark.svg | 51 ++++++++++ src/lib/assets/icons/bookmark.svg | 50 ++++++++++ src/lib/assets/icons/chart-line.svg | 1 - src/lib/assets/icons/chart-pie-4.svg | 1 - src/lib/assets/icons/check.svg | 1 - src/lib/assets/icons/circle-check.svg | 1 - src/lib/assets/icons/circle-x.svg | 1 - src/lib/assets/icons/dashboard.svg | 1 - src/lib/assets/icons/file-type-csv.svg | 1 - src/lib/assets/icons/file-upload.svg | 1 - src/lib/assets/icons/fit.svg | 73 ++++++++++++++ src/lib/assets/icons/food-off.svg | 1 - src/lib/assets/icons/food.svg | 1 - src/lib/assets/icons/github.svg | 1 - src/lib/assets/icons/hamburger-plus.svg | 1 - src/lib/assets/icons/history.svg | 1 - src/lib/assets/icons/journal.svg | 67 +++++++++++++ src/lib/assets/icons/list-bullets.png | Bin 0 -> 238 bytes src/lib/assets/icons/list-bullets.svg | 1 + src/lib/assets/icons/login.svg | 1 - src/lib/assets/icons/new-section.svg | 1 - src/lib/assets/icons/notebook.svg | 1 - src/lib/assets/icons/overflow-1.svg | 1 - src/lib/assets/icons/overflow-2.svg | 1 - src/lib/assets/icons/pencil-off.svg | 1 - src/lib/assets/icons/pencil.svg | 1 - src/lib/assets/icons/plus.svg | 1 - src/lib/assets/icons/progress.svg | 98 +++++++++++++++++++ src/lib/assets/icons/scale-outline-off.svg | 1 - src/lib/assets/icons/scale-outline.svg | 1 - src/lib/assets/icons/target-arrow.svg | 1 - src/lib/assets/icons/target-off.svg | 1 - src/lib/assets/icons/trash-off.svg | 1 - src/lib/assets/icons/trash.svg | 1 - src/lib/assets/icons/user-off.svg | 1 - src/lib/assets/icons/user-square-rounded.svg | 1 - src/lib/assets/icons/user.svg | 1 - src/lib/assets/icons/wand.svg | 1 - 41 files changed, 346 insertions(+), 33 deletions(-) delete mode 100644 src/lib/assets/icons/alert-circle-filled.svg delete mode 100644 src/lib/assets/icons/arrow-right.svg create mode 100644 src/lib/assets/icons/book-bookmark.svg create mode 100644 src/lib/assets/icons/bookmark.svg delete mode 100644 src/lib/assets/icons/chart-line.svg delete mode 100644 src/lib/assets/icons/chart-pie-4.svg delete mode 100644 src/lib/assets/icons/check.svg delete mode 100644 src/lib/assets/icons/circle-check.svg delete mode 100644 src/lib/assets/icons/circle-x.svg delete mode 100644 src/lib/assets/icons/dashboard.svg delete mode 100644 src/lib/assets/icons/file-type-csv.svg delete mode 100644 src/lib/assets/icons/file-upload.svg create mode 100644 src/lib/assets/icons/fit.svg delete mode 100644 src/lib/assets/icons/food-off.svg delete mode 100644 src/lib/assets/icons/food.svg delete mode 100644 src/lib/assets/icons/github.svg delete mode 100644 src/lib/assets/icons/hamburger-plus.svg delete mode 100644 src/lib/assets/icons/history.svg create mode 100644 src/lib/assets/icons/journal.svg create mode 100644 src/lib/assets/icons/list-bullets.png create mode 100644 src/lib/assets/icons/list-bullets.svg delete mode 100644 src/lib/assets/icons/login.svg delete mode 100644 src/lib/assets/icons/new-section.svg delete mode 100644 src/lib/assets/icons/notebook.svg delete mode 100644 src/lib/assets/icons/overflow-1.svg delete mode 100644 src/lib/assets/icons/overflow-2.svg delete mode 100644 src/lib/assets/icons/pencil-off.svg delete mode 100644 src/lib/assets/icons/pencil.svg delete mode 100644 src/lib/assets/icons/plus.svg create mode 100644 src/lib/assets/icons/progress.svg delete mode 100644 src/lib/assets/icons/scale-outline-off.svg delete mode 100644 src/lib/assets/icons/scale-outline.svg delete mode 100644 src/lib/assets/icons/target-arrow.svg delete mode 100644 src/lib/assets/icons/target-off.svg delete mode 100644 src/lib/assets/icons/trash-off.svg delete mode 100644 src/lib/assets/icons/trash.svg delete mode 100644 src/lib/assets/icons/user-off.svg delete mode 100644 src/lib/assets/icons/user-square-rounded.svg delete mode 100644 src/lib/assets/icons/user.svg delete mode 100644 src/lib/assets/icons/wand.svg diff --git a/src-tauri/src/service/dashboard.rs b/src-tauri/src/service/dashboard.rs index 2591fdf9..19ef1cc9 100644 --- a/src-tauri/src/service/dashboard.rs +++ b/src-tauri/src/service/dashboard.rs @@ -24,6 +24,7 @@ pub struct Dashboard { pub weight_month_list: Vec, pub food_categories: Vec, pub current_day: i32, + pub days_total: i32, } // ============================================================================ @@ -88,6 +89,10 @@ impl Dashboard { .num_days() as i32; 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) + .num_days() as i32; + Ok(Self { user_data, intake_target, @@ -98,6 +103,7 @@ impl Dashboard { weight_month_list, food_categories, current_day, + days_total, }) } } diff --git a/src/lib/assets/icons/alert-circle-filled.svg b/src/lib/assets/icons/alert-circle-filled.svg deleted file mode 100644 index 56726308..00000000 --- a/src/lib/assets/icons/alert-circle-filled.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/lib/assets/icons/arrow-right.svg b/src/lib/assets/icons/arrow-right.svg deleted file mode 100644 index 8248e0b3..00000000 --- a/src/lib/assets/icons/arrow-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/book-bookmark.svg b/src/lib/assets/icons/book-bookmark.svg new file mode 100644 index 00000000..bab00906 --- /dev/null +++ b/src/lib/assets/icons/book-bookmark.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + diff --git a/src/lib/assets/icons/bookmark.svg b/src/lib/assets/icons/bookmark.svg new file mode 100644 index 00000000..cc23ec88 --- /dev/null +++ b/src/lib/assets/icons/bookmark.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/src/lib/assets/icons/chart-line.svg b/src/lib/assets/icons/chart-line.svg deleted file mode 100644 index 0a07ae6d..00000000 --- a/src/lib/assets/icons/chart-line.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/chart-pie-4.svg b/src/lib/assets/icons/chart-pie-4.svg deleted file mode 100644 index 4a4d67cf..00000000 --- a/src/lib/assets/icons/chart-pie-4.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/check.svg b/src/lib/assets/icons/check.svg deleted file mode 100644 index ee6bdf61..00000000 --- a/src/lib/assets/icons/check.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/circle-check.svg b/src/lib/assets/icons/circle-check.svg deleted file mode 100644 index 04d9fec4..00000000 --- a/src/lib/assets/icons/circle-check.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/circle-x.svg b/src/lib/assets/icons/circle-x.svg deleted file mode 100644 index ffe1b41b..00000000 --- a/src/lib/assets/icons/circle-x.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/dashboard.svg b/src/lib/assets/icons/dashboard.svg deleted file mode 100644 index 48b1d496..00000000 --- a/src/lib/assets/icons/dashboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/file-type-csv.svg b/src/lib/assets/icons/file-type-csv.svg deleted file mode 100644 index 078ce99c..00000000 --- a/src/lib/assets/icons/file-type-csv.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/file-upload.svg b/src/lib/assets/icons/file-upload.svg deleted file mode 100644 index f23afc37..00000000 --- a/src/lib/assets/icons/file-upload.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/fit.svg b/src/lib/assets/icons/fit.svg new file mode 100644 index 00000000..56d47611 --- /dev/null +++ b/src/lib/assets/icons/fit.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/lib/assets/icons/food-off.svg b/src/lib/assets/icons/food-off.svg deleted file mode 100644 index b68595c7..00000000 --- a/src/lib/assets/icons/food-off.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/food.svg b/src/lib/assets/icons/food.svg deleted file mode 100644 index 3141e998..00000000 --- a/src/lib/assets/icons/food.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/github.svg b/src/lib/assets/icons/github.svg deleted file mode 100644 index 14c8bc4c..00000000 --- a/src/lib/assets/icons/github.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/hamburger-plus.svg b/src/lib/assets/icons/hamburger-plus.svg deleted file mode 100644 index 9e4d710e..00000000 --- a/src/lib/assets/icons/hamburger-plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/history.svg b/src/lib/assets/icons/history.svg deleted file mode 100644 index 6450c8ef..00000000 --- a/src/lib/assets/icons/history.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/journal.svg b/src/lib/assets/icons/journal.svg new file mode 100644 index 00000000..4a535bf4 --- /dev/null +++ b/src/lib/assets/icons/journal.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + diff --git a/src/lib/assets/icons/list-bullets.png b/src/lib/assets/icons/list-bullets.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d4bf4410612c577602d1dc5e48fa4f3f3991a2 GIT binary patch literal 238 zcmeAS@N?(olHy`uVBq!ia0vp^5!3HF|Hk`EvQk(@Ik;M!Qe1}1p@p%4<6riAF ziEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0x3o-U3d7QJsTFXVM{6mY$0 z{G{BOyM-&`K{b~`1P`l%LCBZr1#C@C0S6Prnj2F#J8m$qOgpkivXHqtr{L6?xlS%? z48o5~zVo$fy*L<}%zrH+r0#<4 gkM^c_2i2FUx3X3>ztsx)26P64r>mdKI;Vst0GjbvkN^Mx literal 0 HcmV?d00001 diff --git a/src/lib/assets/icons/list-bullets.svg b/src/lib/assets/icons/list-bullets.svg new file mode 100644 index 00000000..30a46689 --- /dev/null +++ b/src/lib/assets/icons/list-bullets.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/lib/assets/icons/login.svg b/src/lib/assets/icons/login.svg deleted file mode 100644 index 1d9e9b86..00000000 --- a/src/lib/assets/icons/login.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/new-section.svg b/src/lib/assets/icons/new-section.svg deleted file mode 100644 index 3e0714d8..00000000 --- a/src/lib/assets/icons/new-section.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/notebook.svg b/src/lib/assets/icons/notebook.svg deleted file mode 100644 index 77591edf..00000000 --- a/src/lib/assets/icons/notebook.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/overflow-1.svg b/src/lib/assets/icons/overflow-1.svg deleted file mode 100644 index ec40c8e7..00000000 --- a/src/lib/assets/icons/overflow-1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/overflow-2.svg b/src/lib/assets/icons/overflow-2.svg deleted file mode 100644 index 3eb238b0..00000000 --- a/src/lib/assets/icons/overflow-2.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/pencil-off.svg b/src/lib/assets/icons/pencil-off.svg deleted file mode 100644 index 20fadef8..00000000 --- a/src/lib/assets/icons/pencil-off.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/pencil.svg b/src/lib/assets/icons/pencil.svg deleted file mode 100644 index dfe2334c..00000000 --- a/src/lib/assets/icons/pencil.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/plus.svg b/src/lib/assets/icons/plus.svg deleted file mode 100644 index d7e5b7a8..00000000 --- a/src/lib/assets/icons/plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/progress.svg b/src/lib/assets/icons/progress.svg new file mode 100644 index 00000000..63132305 --- /dev/null +++ b/src/lib/assets/icons/progress.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lib/assets/icons/scale-outline-off.svg b/src/lib/assets/icons/scale-outline-off.svg deleted file mode 100644 index bfbce6fe..00000000 --- a/src/lib/assets/icons/scale-outline-off.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/scale-outline.svg b/src/lib/assets/icons/scale-outline.svg deleted file mode 100644 index 22aae270..00000000 --- a/src/lib/assets/icons/scale-outline.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/target-arrow.svg b/src/lib/assets/icons/target-arrow.svg deleted file mode 100644 index 54b58efa..00000000 --- a/src/lib/assets/icons/target-arrow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/target-off.svg b/src/lib/assets/icons/target-off.svg deleted file mode 100644 index 61f4190f..00000000 --- a/src/lib/assets/icons/target-off.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/trash-off.svg b/src/lib/assets/icons/trash-off.svg deleted file mode 100644 index 9942ab4e..00000000 --- a/src/lib/assets/icons/trash-off.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/trash.svg b/src/lib/assets/icons/trash.svg deleted file mode 100644 index d872bd7a..00000000 --- a/src/lib/assets/icons/trash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/user-off.svg b/src/lib/assets/icons/user-off.svg deleted file mode 100644 index aec4e0ce..00000000 --- a/src/lib/assets/icons/user-off.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/user-square-rounded.svg b/src/lib/assets/icons/user-square-rounded.svg deleted file mode 100644 index 3807b207..00000000 --- a/src/lib/assets/icons/user-square-rounded.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/user.svg b/src/lib/assets/icons/user.svg deleted file mode 100644 index 1e828549..00000000 --- a/src/lib/assets/icons/user.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/assets/icons/wand.svg b/src/lib/assets/icons/wand.svg deleted file mode 100644 index e86b6762..00000000 --- a/src/lib/assets/icons/wand.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 88297885710750c821bcefeff5707d542632d24c Mon Sep 17 00:00:00 2001 From: Stefan Poindl Date: Sun, 15 Feb 2026 00:25:06 +0100 Subject: [PATCH 16/18] replace settings icon --- .../component/navigation/HistoryIcon.svelte | 13 +++++ .../component/navigation/JournalIcon.svelte | 43 ++++++++++++++ .../component/navigation/ProgressIcon.svelte | 56 +++++++++++++++++++ .../component/navigation/SettingsIcon.svelte | 42 ++++++++++++++ src/routes/(app)/+layout.svelte | 28 ++++++++-- src/routes/(app)/+page.svelte | 8 +-- src/routes/(app)/about/+page.svelte | 6 +- src/routes/(app)/export/+page.svelte | 5 +- src/routes/(app)/import/+page.svelte | 8 +-- src/routes/(app)/profile/+page.svelte | 6 +- src/routes/(app)/progress/+page.svelte | 11 +--- src/routes/(app)/wizard/+page.svelte | 7 +-- 12 files changed, 195 insertions(+), 38 deletions(-) create mode 100644 src/lib/component/navigation/HistoryIcon.svelte create mode 100644 src/lib/component/navigation/JournalIcon.svelte create mode 100644 src/lib/component/navigation/ProgressIcon.svelte create mode 100644 src/lib/component/navigation/SettingsIcon.svelte diff --git a/src/lib/component/navigation/HistoryIcon.svelte b/src/lib/component/navigation/HistoryIcon.svelte new file mode 100644 index 00000000..186adfb2 --- /dev/null +++ b/src/lib/component/navigation/HistoryIcon.svelte @@ -0,0 +1,13 @@ + + + + + + diff --git a/src/lib/component/navigation/JournalIcon.svelte b/src/lib/component/navigation/JournalIcon.svelte new file mode 100644 index 00000000..ae58b3de --- /dev/null +++ b/src/lib/component/navigation/JournalIcon.svelte @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + diff --git a/src/lib/component/navigation/ProgressIcon.svelte b/src/lib/component/navigation/ProgressIcon.svelte new file mode 100644 index 00000000..936abbd5 --- /dev/null +++ b/src/lib/component/navigation/ProgressIcon.svelte @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + diff --git a/src/lib/component/navigation/SettingsIcon.svelte b/src/lib/component/navigation/SettingsIcon.svelte new file mode 100644 index 00000000..9a575713 --- /dev/null +++ b/src/lib/component/navigation/SettingsIcon.svelte @@ -0,0 +1,42 @@ + + + + + + + + + + + + + diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index adb40b56..24176909 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -2,11 +2,14 @@ import { page } from '$app/state'; import { AppShell, ToastContainer, createRefreshContext } from '@thwbh/veilchen'; import type { BottomNavItem } from '@thwbh/veilchen'; - import { ChartLine, DotsThreeVertical, House, ListBullets } from 'phosphor-svelte'; import Settings from '$lib/component/settings/Settings.svelte'; import { fade } from 'svelte/transition'; import { cubicOut } from 'svelte/easing'; import { afterNavigate } from '$app/navigation'; + import JournalIcon from '$lib/component/navigation/JournalIcon.svelte'; + import ProgressIcon from '$lib/component/navigation/ProgressIcon.svelte'; + import SettingsIcon from '$lib/component/navigation/SettingsIcon.svelte'; + import HistoryIcon from '$lib/component/navigation/HistoryIcon.svelte'; let { children } = $props(); @@ -23,27 +26,28 @@ id: 'home', label: 'Home', href: '/', - icon: House, + /* icon: House,*/ + icon: JournalIcon, iconProps: { size: '1.25em', weight: 'bold' } }, { id: 'progress', label: 'Progress', href: '/progress', - icon: ChartLine, + icon: ProgressIcon, iconProps: { size: '1.25em', weight: 'bold' } }, { id: 'history', label: 'History', href: '/history', - icon: ListBullets, + icon: HistoryIcon, iconProps: { size: '1.25em', weight: 'bold' } }, { id: 'settings', label: 'Settings', - icon: DotsThreeVertical, + icon: SettingsIcon, iconProps: { size: '1.25em', weight: 'bold' }, onclick: () => { isSettingsOpen = !isSettingsOpen; @@ -77,3 +81,17 @@ + + diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index c091df1a..1117ecff 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -49,12 +49,8 @@ const weightTarget: WeightTarget = dashboard.weightTarget; const intakeTarget: IntakeTarget = dashboard.intakeTarget; - // Progress bar calculations (moved from WeightScore) - const dayDiff = differenceInDays(parseStringAsDate(weightTarget.endDate), new Date()); - const totalDays = differenceInDays( - parseStringAsDate(weightTarget.endDate), - parseStringAsDate(weightTarget.startDate) - ); + const totalDays = dashboard.daysTotal; + const dayDiff = totalDays - dashboard.currentDay; const progress = totalDays === 0 ? 0 : Math.round(((totalDays - dayDiff) / totalDays) * 100); diff --git a/src/routes/(app)/about/+page.svelte b/src/routes/(app)/about/+page.svelte index 932754dd..c43cc348 100644 --- a/src/routes/(app)/about/+page.svelte +++ b/src/routes/(app)/about/+page.svelte @@ -1,11 +1,12 @@