diff --git a/package-lock.json b/package-lock.json index 3af5358..859fa1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,7 @@ "version": "0.0.1", "dependencies": { "@types/pg": "^8.15.5", - "pg": "^8.16.3", - "zod": "^4.1.5" + "pg": "^8.16.3" }, "devDependencies": { "@sveltejs/adapter-node": "^5.3.1", @@ -2733,15 +2732,6 @@ "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", "dev": true, "license": "MIT" - }, - "node_modules/zod": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.5.tgz", - "integrity": "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index 27f3c55..7e3f16b 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ }, "dependencies": { "@types/pg": "^8.15.5", - "pg": "^8.16.3", - "zod": "^4.1.5" + "pg": "^8.16.3" } } diff --git a/src/hooks.client.ts b/src/hooks.client.ts deleted file mode 100644 index b3af44e..0000000 --- a/src/hooks.client.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { HandleClientError } from '@sveltejs/kit'; -import { dev } from '$app/environment'; - -// Handle client-side errors -export const handleError: HandleClientError = ({ error, event }) => { - console.error('Client error:', error); - - // Log additional context in development - if (dev) { - console.error('Event details:', { - url: event.url, - route: event.route?.id - }); - } - - // Return user-friendly error message - return { - message: dev ? String(error) : 'Something went wrong', - code: 'CLIENT_ERROR' - }; -}; diff --git a/src/hooks.server.ts b/src/hooks.server.ts index e91472a..57782e3 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -3,7 +3,7 @@ import { dev } from '$app/environment'; import { db } from '$lib/db'; export const init: ServerInit = async () => { - db.createTables(); + await db.createTables(); console.log('Tables created'); }; diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg index cc5dc66..fbcfd7c 100644 --- a/src/lib/assets/favicon.svg +++ b/src/lib/assets/favicon.svg @@ -1 +1,26 @@ -svelte-logo \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lib/components/DbConnection.svelte b/src/lib/components/DbConnection.svelte deleted file mode 100644 index e23db3c..0000000 --- a/src/lib/components/DbConnection.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - -

Here be connection: {testConnection().current}

- - {#snippet pending()} -

loading...

- {/snippet} -
diff --git a/src/lib/components/ExampleNewExercise.svelte b/src/lib/components/ExampleNewExercise.svelte deleted file mode 100644 index dcabe6b..0000000 --- a/src/lib/components/ExampleNewExercise.svelte +++ /dev/null @@ -1,107 +0,0 @@ - - -
-

Example: Adding New Exercises

- - -
- - - -
- - -
- - - -
- - -
-

How to add new exercises:

-
    -
  1. Define exercise config with name, icon, color, and quick-add options
  2. -
  3. - Use ExerciseField component for input forms -
  4. -
  5. - Use WorkoutStatCard component for display -
  6. -
  7. Add to workout-utils.ts for consistency
  8. -
-
-
diff --git a/src/lib/components/QuickAddButton.svelte b/src/lib/components/QuickAddButton.svelte deleted file mode 100644 index 2fdd40f..0000000 --- a/src/lib/components/QuickAddButton.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/src/lib/components/db.remote.js b/src/lib/components/db.remote.js deleted file mode 100644 index 9fb7616..0000000 --- a/src/lib/components/db.remote.js +++ /dev/null @@ -1,6 +0,0 @@ -import { query } from '$app/server'; -import { db } from '$lib/db'; - -export const testConnection = query(async () => { - return db.testConnection(); -}); diff --git a/src/lib/db.ts b/src/lib/db.ts index 4128794..fc538d7 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,15 +1,17 @@ import { Pool } from 'pg'; +import { env } from '$env/dynamic/private'; // Get DATABASE_URL with fallback -const getDatabaseUrl = - 'postgres://postgres:voHMlrqwwzlZcqWW3oCYvkmWnmOAFntc4nYJmTRVsr7bm8CiOoxqAKv1zVn5Opsq@192.168.0.133:5432/postgres'; +const getDatabaseUrl = () => { + return env.DATABASE_URL || ''; +}; // Lazy pool creation - only create when actually needed let pool: Pool | null = null; const getPool = () => { if (!pool) { pool = new Pool({ - connectionString: getDatabaseUrl, + connectionString: getDatabaseUrl(), ssl: false, options: '-c timezone=Europe/Oslo' }); diff --git a/src/routes/workoutData.ts b/src/lib/workout/exercises.ts similarity index 53% rename from src/routes/workoutData.ts rename to src/lib/workout/exercises.ts index 796dee3..0b52199 100644 --- a/src/routes/workoutData.ts +++ b/src/lib/workout/exercises.ts @@ -1,17 +1,6 @@ -export type ColorExercise = 'blue' | 'green' | 'orange' | 'red' | 'purple'; +import type { WorkoutData, ExerciseConfig } from './types'; -export interface ExerciseConfig { - name: string; - icon: string; - color: ColorExercise; - quickAddOptions: Array<{ label: string; value: number }>; - min?: number; - max?: number; - step?: number; - unit?: string; -} - -export const exerciseConfigs: Record = { +export const exerciseConfigs: Record = { pushups: { name: 'Push-ups', icon: '💪', @@ -35,7 +24,7 @@ export const exerciseConfigs: Record = { step: 1 }, plankSeconds: { - name: 'Plank', + name: 'Planke', icon: '🧘', color: 'orange', quickAddOptions: [ @@ -44,19 +33,8 @@ export const exerciseConfigs: Record = { ], max: 9999, step: 1, - unit: 'seconds' - }, - runKm: { - name: 'Running', - icon: '🏃', - color: 'red', - quickAddOptions: [ - { label: '+1km', value: 1 }, - { label: '+2.5km', value: 2.5 } - ], - max: 999.99, - step: 0.01, - unit: 'km' + unit: 'sekunder', + formatter: (number) => formatTime(number) }, hangups: { name: 'Hang-ups', @@ -68,5 +46,35 @@ export const exerciseConfigs: Record = { ], max: 9999, step: 1 + }, + runKm: { + name: 'Running', + icon: '🏃', + color: 'red', + quickAddOptions: [ + { label: '+1km', value: 1 }, + { label: '+2.5km', value: 2.5 } + ], + max: 999.99, + step: 0.01, + unit: 'km', + formatter: (value) => `${value.toFixed(2)} km` } }; + +function formatTime(seconds: number): string { + if (seconds === 0) return '0 seconds'; + if (seconds < 60) return `${seconds} seconds`; + + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + + if (remainingSeconds === 0) return `${minutes} minutes`; + return `${minutes}m ${remainingSeconds}s`; +} + +// Get strongly typed entries directly from exerciseConfigs +export const exercises = Object.entries(exerciseConfigs).map(([key, config]) => ({ + key: key as keyof WorkoutData, + config +})); diff --git a/src/lib/workout/index.ts b/src/lib/workout/index.ts new file mode 100644 index 0000000..1ecf9bd --- /dev/null +++ b/src/lib/workout/index.ts @@ -0,0 +1,4 @@ +// Barrel exports for workout module +export * from './types'; +export * from './exercises'; +export * from './utils'; diff --git a/src/lib/workout/types.ts b/src/lib/workout/types.ts new file mode 100644 index 0000000..fe6693b --- /dev/null +++ b/src/lib/workout/types.ts @@ -0,0 +1,32 @@ +export type ColorExercise = 'blue' | 'green' | 'orange' | 'red' | 'purple'; + +export type WorkoutData = { + pushups: number; + situps: number; + plankSeconds: number; + hangups: number; + runKm: number; +}; + +export interface ExerciseConfig { + name: string; + icon: string; + color: ColorExercise; + quickAddOptions: Array<{ label: string; value: number }>; + min?: number; + max?: number; + step?: number; + unit?: string; + formatter?: (value: number) => string; +} + +export interface QuickAddOption { + label: string; + value: number; +} + +export interface WorkoutApiResponse { + success: boolean; + data?: T; + message?: string; +} diff --git a/src/lib/workout/utils.ts b/src/lib/workout/utils.ts new file mode 100644 index 0000000..cee3592 --- /dev/null +++ b/src/lib/workout/utils.ts @@ -0,0 +1,13 @@ +import type { WorkoutData } from './types'; + +export function getTodayDateString(locale: string = 'nb-NO'): string { + return new Date().toLocaleDateString(locale); +} + +export const exampleWorkout: WorkoutData = { + pushups: 100, + situps: 50, + plankSeconds: 0, + hangups: 0, + runKm: 4.0 +}; diff --git a/src/routes/ExerciseField.svelte b/src/routes/ExerciseField.svelte index 92b0489..d45fba5 100644 --- a/src/routes/ExerciseField.svelte +++ b/src/routes/ExerciseField.svelte @@ -1,38 +1,41 @@
@@ -48,8 +51,21 @@ required={true} class="flex-1 rounded-md border border-gray-300 px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:outline-none" /> + {#each quickAddOptions as option} - quickAdd(option.value)} {color} /> + {@render quickAddButton(option.label, () => quickAdd(option.value), color)} {/each}
+ +{#snippet quickAddButton(label: string, onclick: () => void, color: ColorExercise)} + +{/snippet} diff --git a/src/routes/WorkoutDisplay.svelte b/src/routes/WorkoutDisplay.svelte index d840031..547f1e0 100644 --- a/src/routes/WorkoutDisplay.svelte +++ b/src/routes/WorkoutDisplay.svelte @@ -1,11 +1,31 @@
@@ -26,74 +46,26 @@ {:else}
- - - - - - - - - - - - - - + {#each exercises as { key, config }} + {@render workoutStatCard( + config.icon, + workoutData[key], + config.name, + config.color, + config.formatter + )} + {/each}
{@const allWorkouData = (await getWorkoutHistory()).data} +

📊 Summary for last 7 days

-
- Total pushups: - {allWorkouData.pushups} -
-
- Total situps: - {workoutData.situps} -
-
- Plank time: - {formatTime(workoutData.plankSeconds)} -
-
- Hang ups: - {workoutData.hangups} -
-
- Distance: - {formatDistance(workoutData.runKm)} -
+ {#each exercises as { key, config }} + {@render summaryStatRow(config.name, allWorkouData[key], config.formatter)} + {/each}
{/if} @@ -118,3 +90,24 @@ {/snippet}
+ +{#snippet workoutStatCard( + icon: string, + value: number, + label: string, + color: ColorExercise = 'blue', + formatter?: (value: number) => string +)} +
+
{icon}
+
{formatter ? formatter(value) : value}
+
{label}
+
+{/snippet} + +{#snippet summaryStatRow(label: string, value: number, formatter?: (value: number) => string)} +
+ {label} + {formatter ? formatter(value) : value} +
+{/snippet} diff --git a/src/routes/WorkoutLogger.svelte b/src/routes/WorkoutLogger.svelte index 6d137ee..60cedbe 100644 --- a/src/routes/WorkoutLogger.svelte +++ b/src/routes/WorkoutLogger.svelte @@ -1,14 +1,13 @@
@@ -39,74 +34,10 @@
- - handleFieldChange('pushups', value)} - quickAddOptions={exerciseConfigs.pushups.quickAddOptions} - color={exerciseConfigs.pushups.color} - max={exerciseConfigs.pushups.max} - step={exerciseConfigs.pushups.step} - /> - - - handleFieldChange('situps', value)} - defaultValue={form.situps} - quickAddOptions={exerciseConfigs.situps.quickAddOptions} - color={exerciseConfigs.situps.color} - max={exerciseConfigs.situps.max} - step={exerciseConfigs.situps.step} - /> - - - handleFieldChange('plankSeconds', value)} - defaultValue={form.plankSeconds} - quickAddOptions={exerciseConfigs.plankSeconds.quickAddOptions} - color={exerciseConfigs.plankSeconds.color} - max={exerciseConfigs.plankSeconds.max} - step={exerciseConfigs.plankSeconds.step} - /> - - - handleFieldChange('hangups', value)} - defaultValue={form.hangups} - quickAddOptions={exerciseConfigs.hangups.quickAddOptions} - color={exerciseConfigs.hangups.color} - max={exerciseConfigs.hangups.max} - step={exerciseConfigs.hangups.step} - /> - - handleFieldChange('runKm', value)} - defaultValue={form.runKm} - quickAddOptions={exerciseConfigs.runKm.quickAddOptions} - color={exerciseConfigs.runKm.color} - max={exerciseConfigs.runKm.max} - step={exerciseConfigs.runKm.step} - /> + + {#each exercises as { config, key }} + + {/each}
@@ -114,7 +45,11 @@ type="submit" class="flex-1 rounded-md bg-blue-600 px-4 py-2 font-medium text-white hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50" > - 💾 Save Workout + {#if saveWorkout.pending !== 0} + Loading... + {:else} + 💾 Save Workout + {/if}
@@ -129,8 +64,6 @@
{/if} - {#snippet pending()}{/snippet} -

Quick Example:

diff --git a/src/routes/WorkoutStatCard.svelte b/src/routes/WorkoutStatCard.svelte deleted file mode 100644 index 1695a19..0000000 --- a/src/routes/WorkoutStatCard.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - -
-
{icon}
-
{displayValue}
-
{label}
-
diff --git a/src/routes/workout.remote.ts b/src/routes/workout.remote.ts index 2c7c960..aff37e3 100644 --- a/src/routes/workout.remote.ts +++ b/src/routes/workout.remote.ts @@ -1,14 +1,7 @@ import { command, form, query } from '$app/server'; import { db } from '$lib/db'; import { error } from '@sveltejs/kit'; - -export interface WorkOutData { - pushups: number; - situps: number; - plankSeconds: number; - hangups: number; - runKm: number; -} +import type { WorkoutData } from '../lib/workout/types'; export const saveWorkout = form(async (data) => { let pushups = Number(data.get('pushups')); @@ -81,7 +74,7 @@ export const getTodaysWorkout = query(async () => { // Take all rows and add them up into each category const row = result.rows[0]; - const workoutData: WorkOutData = { + const workoutData: WorkoutData = { pushups: Number(row.pushups), situps: Number(row.situps), plankSeconds: Number(row.plank_time_seconds), @@ -113,7 +106,7 @@ export const getWorkoutHistory = query(async (days: number = 7) => { // Take all rows and add them up into each category const rows = result.rows; - const workoutData: WorkOutData = { + const workoutData: WorkoutData = { pushups: rows.reduce((sum, row) => sum + Number(row.pushups), 0), situps: rows.reduce((sum, row) => sum + Number(row.situps), 0), plankSeconds: rows.reduce((sum, row) => sum + Number(row.plank_time_seconds), 0), diff --git a/src/routes/workoutUtils.ts b/src/routes/workoutUtils.ts deleted file mode 100644 index a2c1826..0000000 --- a/src/routes/workoutUtils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { WorkOutData } from './workout.remote'; - -export function formatTime(seconds: number): string { - if (seconds === 0) return '0 seconds'; - if (seconds < 60) return `${seconds} seconds`; - - const minutes = Math.floor(seconds / 60); - const remainingSeconds = seconds % 60; - - if (remainingSeconds === 0) return `${minutes} minutes`; - return `${minutes}m ${remainingSeconds}s`; -} - -export function formatDistance(km: number): string { - return `${km} km`; -} - -export function getTodayDateString(locale: string = 'nb-NO'): string { - return new Date().toLocaleDateString(locale); -} - -export const exampleWorkout: WorkOutData = { - pushups: 100, - situps: 50, - plankSeconds: 0, - hangups: 0, - runKm: 4.0 -};