diff --git a/drizzle/0001_workout_sessions.sql b/drizzle/0001_workout_sessions.sql new file mode 100644 index 0000000..9ef32a5 --- /dev/null +++ b/drizzle/0001_workout_sessions.sql @@ -0,0 +1,35 @@ +CREATE TABLE `workout_sessions` ( + `id` text PRIMARY KEY NOT NULL, + `workout_id` text, + `name` text, + `pool_length` real DEFAULT 25 NOT NULL, + `started_at` text NOT NULL, + `finished_at` text, + `total_distance` integer, + `total_duration` real, + `calories` real, + `avg_heart_rate` integer, + `source` text DEFAULT 'manual' NOT NULL, + FOREIGN KEY (`workout_id`) REFERENCES `workouts`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO `workout_sessions` (`id`, `workout_id`, `name`, `pool_length`, `started_at`, `finished_at`, `total_distance`, `total_duration`, `calories`, `avg_heart_rate`, `source`) +SELECT `id`, `id`, `name`, `pool_length`, `started_at`, `finished_at`, `total_distance`, `total_duration`, `calories`, `avg_heart_rate`, `source` +FROM `workouts` +WHERE `finished_at` IS NOT NULL; +--> statement-breakpoint +ALTER TABLE `laps` RENAME COLUMN `workout_id` TO `session_id`; +--> statement-breakpoint +ALTER TABLE `workouts` DROP COLUMN `started_at`; +--> statement-breakpoint +ALTER TABLE `workouts` DROP COLUMN `finished_at`; +--> statement-breakpoint +ALTER TABLE `workouts` DROP COLUMN `total_distance`; +--> statement-breakpoint +ALTER TABLE `workouts` DROP COLUMN `total_duration`; +--> statement-breakpoint +ALTER TABLE `workouts` DROP COLUMN `calories`; +--> statement-breakpoint +ALTER TABLE `workouts` DROP COLUMN `avg_heart_rate`; +--> statement-breakpoint +ALTER TABLE `workouts` DROP COLUMN `source`; diff --git a/drizzle/0002_gifted_annihilus.sql b/drizzle/0002_gifted_annihilus.sql new file mode 100644 index 0000000..d02ed4a --- /dev/null +++ b/drizzle/0002_gifted_annihilus.sql @@ -0,0 +1,36 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_laps` ( + `id` text PRIMARY KEY NOT NULL, + `session_id` text NOT NULL, + `set_position` integer NOT NULL, + `rep_number` integer NOT NULL, + `distance` integer NOT NULL, + `duration` real NOT NULL, + `stroke_count` integer, + `heart_rate` integer, + `swolf` real, + FOREIGN KEY (`session_id`) REFERENCES `workout_sessions`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO `__new_laps`("id", "session_id", "set_position", "rep_number", "distance", "duration", "stroke_count", "heart_rate", "swolf") SELECT "id", "session_id", "set_position", "rep_number", "distance", "duration", "stroke_count", "heart_rate", "swolf" FROM `laps`;--> statement-breakpoint +DROP TABLE `laps`;--> statement-breakpoint +ALTER TABLE `__new_laps` RENAME TO `laps`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE TABLE `__new_workout_sessions` ( + `id` text PRIMARY KEY NOT NULL, + `workout_id` text, + `name` text, + `pool_length` real DEFAULT 25 NOT NULL, + `started_at` text NOT NULL, + `finished_at` text, + `total_distance` integer, + `total_duration` real, + `calories` real, + `avg_heart_rate` integer, + `source` text DEFAULT 'manual' NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_workout_sessions`("id", "workout_id", "name", "pool_length", "started_at", "finished_at", "total_distance", "total_duration", "calories", "avg_heart_rate", "source") SELECT "id", "workout_id", "name", "pool_length", "started_at", "finished_at", "total_distance", "total_duration", "calories", "avg_heart_rate", "source" FROM `workout_sessions`;--> statement-breakpoint +DROP TABLE `workout_sessions`;--> statement-breakpoint +ALTER TABLE `__new_workout_sessions` RENAME TO `workout_sessions`;--> statement-breakpoint +ALTER TABLE `workouts` ADD `created_at` text NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..a3a7947 --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,397 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "1a621e4a-ea8a-4479-a98c-0f02fc8e14e3", + "prevId": "a9688cc9-9314-4d59-9b68-b7e202f3e3ff", + "tables": { + "laps": { + "name": "laps", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "workout_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "set_position": { + "name": "set_position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rep_number": { + "name": "rep_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "distance": { + "name": "distance", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "stroke_count": { + "name": "stroke_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "heart_rate": { + "name": "heart_rate", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "swolf": { + "name": "swolf", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "laps_workout_id_workouts_id_fk": { + "name": "laps_session_id_workout_sessions_id_fk", + "tableFrom": "laps", + "tableTo": "workout_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sets": { + "name": "sets", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workout_id": { + "name": "workout_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "stroke": { + "name": "stroke", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "distance_per_rep": { + "name": "distance_per_rep", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rep_count": { + "name": "rep_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rest_type": { + "name": "rest_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fixed_rest'" + }, + "rest_duration": { + "name": "rest_duration", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "target_time": { + "name": "target_time", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "sets_workout_id_workouts_id_fk": { + "name": "sets_workout_id_workouts_id_fk", + "tableFrom": "sets", + "tableTo": "workouts", + "columnsFrom": [ + "workout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "templates": { + "name": "templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pool_length": { + "name": "pool_length", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 25 + }, + "json_data": { + "name": "json_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_favorite": { + "name": "is_favorite", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workouts": { + "name": "workouts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pool_length": { + "name": "pool_length", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 25 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workout_sessions": { + "name": "workout_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workout_id": { + "name": "workout_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pool_length": { + "name": "pool_length", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 25 + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_distance": { + "name": "total_distance", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_duration": { + "name": "total_duration", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "calories": { + "name": "calories", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avg_heart_rate": { + "name": "avg_heart_rate", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manual'" + } + }, + "indexes": {}, + "foreignKeys": { + "workout_sessions_workout_id_workouts_id_fk": { + "name": "workout_sessions_workout_id_workouts_id_fk", + "tableFrom": "workout_sessions", + "tableTo": "workouts", + "columnsFrom": [ + "workout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..6c263b0 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,390 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "3e3e9dde-1e8c-4a6a-94ac-b5a750829d2b", + "prevId": "1a621e4a-ea8a-4479-a98c-0f02fc8e14e3", + "tables": { + "laps": { + "name": "laps", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "set_position": { + "name": "set_position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rep_number": { + "name": "rep_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "distance": { + "name": "distance", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "stroke_count": { + "name": "stroke_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "heart_rate": { + "name": "heart_rate", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "swolf": { + "name": "swolf", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "laps_session_id_workout_sessions_id_fk": { + "name": "laps_session_id_workout_sessions_id_fk", + "tableFrom": "laps", + "tableTo": "workout_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sets": { + "name": "sets", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workout_id": { + "name": "workout_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "stroke": { + "name": "stroke", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "distance_per_rep": { + "name": "distance_per_rep", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rep_count": { + "name": "rep_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rest_type": { + "name": "rest_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fixed_rest'" + }, + "rest_duration": { + "name": "rest_duration", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "target_time": { + "name": "target_time", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "sets_workout_id_workouts_id_fk": { + "name": "sets_workout_id_workouts_id_fk", + "tableFrom": "sets", + "tableTo": "workouts", + "columnsFrom": [ + "workout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "templates": { + "name": "templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pool_length": { + "name": "pool_length", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 25 + }, + "json_data": { + "name": "json_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_favorite": { + "name": "is_favorite", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workout_sessions": { + "name": "workout_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workout_id": { + "name": "workout_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pool_length": { + "name": "pool_length", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 25 + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_distance": { + "name": "total_distance", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_duration": { + "name": "total_duration", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "calories": { + "name": "calories", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avg_heart_rate": { + "name": "avg_heart_rate", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manual'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workouts": { + "name": "workouts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pool_length": { + "name": "pool_length", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 25 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index ef960b2..0ad6a5b 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,20 @@ "when": 1785854381700, "tag": "0000_mushy_groot", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1785940781700, + "tag": "0001_workout_sessions", + "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1786525398056, + "tag": "0002_gifted_annihilus", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/migrations.js b/drizzle/migrations.js index 035ebea..514ba31 100644 --- a/drizzle/migrations.js +++ b/drizzle/migrations.js @@ -54,9 +54,88 @@ CREATE TABLE \`workouts\` ( ); `; +const m0001 = ` +CREATE TABLE \`workout_sessions\` ( + \`id\` text PRIMARY KEY NOT NULL, + \`workout_id\` text, + \`name\` text, + \`pool_length\` real DEFAULT 25 NOT NULL, + \`started_at\` text NOT NULL, + \`finished_at\` text, + \`total_distance\` integer, + \`total_duration\` real, + \`calories\` real, + \`avg_heart_rate\` integer, + \`source\` text DEFAULT 'manual' NOT NULL, + FOREIGN KEY (\`workout_id\`) REFERENCES \`workouts\`(\`id\`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO \`workout_sessions\` (\`id\`, \`workout_id\`, \`name\`, \`pool_length\`, \`started_at\`, \`finished_at\`, \`total_distance\`, \`total_duration\`, \`calories\`, \`avg_heart_rate\`, \`source\`) +SELECT \`id\`, \`id\`, \`name\`, \`pool_length\`, \`started_at\`, \`finished_at\`, \`total_distance\`, \`total_duration\`, \`calories\`, \`avg_heart_rate\`, \`source\` +FROM \`workouts\` +WHERE \`finished_at\` IS NOT NULL; +--> statement-breakpoint +ALTER TABLE \`laps\` RENAME COLUMN \`workout_id\` TO \`session_id\`; +--> statement-breakpoint +ALTER TABLE \`workouts\` DROP COLUMN \`started_at\`; +--> statement-breakpoint +ALTER TABLE \`workouts\` DROP COLUMN \`finished_at\`; +--> statement-breakpoint +ALTER TABLE \`workouts\` DROP COLUMN \`total_distance\`; +--> statement-breakpoint +ALTER TABLE \`workouts\` DROP COLUMN \`total_duration\`; +--> statement-breakpoint +ALTER TABLE \`workouts\` DROP COLUMN \`calories\`; +--> statement-breakpoint +ALTER TABLE \`workouts\` DROP COLUMN \`avg_heart_rate\`; +--> statement-breakpoint +ALTER TABLE \`workouts\` DROP COLUMN \`source\`; +`; + +const m0002 = ` +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE \`__new_laps\` ( + \`id\` text PRIMARY KEY NOT NULL, + \`session_id\` text NOT NULL, + \`set_position\` integer NOT NULL, + \`rep_number\` integer NOT NULL, + \`distance\` integer NOT NULL, + \`duration\` real NOT NULL, + \`stroke_count\` integer, + \`heart_rate\` integer, + \`swolf\` real, + FOREIGN KEY (\`session_id\`) REFERENCES \`workout_sessions\`(\`id\`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO \`__new_laps\`("id", "session_id", "set_position", "rep_number", "distance", "duration", "stroke_count", "heart_rate", "swolf") SELECT "id", "session_id", "set_position", "rep_number", "distance", "duration", "stroke_count", "heart_rate", "swolf" FROM \`laps\`;--> statement-breakpoint +DROP TABLE \`laps\`;--> statement-breakpoint +ALTER TABLE \`__new_laps\` RENAME TO \`laps\`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE TABLE \`__new_workout_sessions\` ( + \`id\` text PRIMARY KEY NOT NULL, + \`workout_id\` text, + \`name\` text, + \`pool_length\` real DEFAULT 25 NOT NULL, + \`started_at\` text NOT NULL, + \`finished_at\` text, + \`total_distance\` integer, + \`total_duration\` real, + \`calories\` real, + \`avg_heart_rate\` integer, + \`source\` text DEFAULT 'manual' NOT NULL +); +--> statement-breakpoint +INSERT INTO \`__new_workout_sessions\`("id", "workout_id", "name", "pool_length", "started_at", "finished_at", "total_distance", "total_duration", "calories", "avg_heart_rate", "source") SELECT "id", "workout_id", "name", "pool_length", "started_at", "finished_at", "total_distance", "total_duration", "calories", "avg_heart_rate", "source" FROM \`workout_sessions\`;--> statement-breakpoint +DROP TABLE \`workout_sessions\`;--> statement-breakpoint +ALTER TABLE \`__new_workout_sessions\` RENAME TO \`workout_sessions\`;--> statement-breakpoint +ALTER TABLE \`workouts\` ADD \`created_at\` text NOT NULL; +`; + export default { journal, migrations: { - m0000 + m0000, + m0001, + m0002 } -} +} \ No newline at end of file diff --git a/src/db/operations.ts b/src/db/operations.ts index 3605985..5da1c29 100644 --- a/src/db/operations.ts +++ b/src/db/operations.ts @@ -1,8 +1,9 @@ import { db } from "./client"; import { generateUUID } from "../utils/uuid"; -import { workouts, sets, laps, templates } from "./schema"; +import { workouts, workoutSessions, sets, laps, templates } from "./schema"; import { eq, desc, sql } from "drizzle-orm"; import type { WorkoutSet, LapData } from "@types/workout"; +import WatchConnectivity from "../modules/watch-connectivity"; // ─── Templates ─────────────────────────────────────────────── @@ -30,7 +31,7 @@ export async function toggleTemplateFavorite(id: string, isFavorite: boolean) { await db.update(templates).set({ isFavorite }).where(eq(templates.id, id)); } -// ─── Workouts ──────────────────────────────────────────────── +// ─── Workout Plans ─────────────────────────────────────────── export async function saveWorkout( name: string | null, @@ -39,16 +40,13 @@ export async function saveWorkout( templateId?: string ) { const workoutId = generateUUID(); - const totalDistance = setsData.reduce((acc, s) => acc + s.distancePerRep * s.repCount, 0); await db.insert(workouts).values({ id: workoutId, name, templateId: templateId ?? null, poolLength, - startedAt: new Date().toISOString(), - totalDistance, - source: "manual", + createdAt: new Date().toISOString(), }); for (let i = 0; i < setsData.length; i++) { @@ -69,74 +67,50 @@ export async function saveWorkout( return workoutId; } -export async function finishWorkout( - workoutId: string, - duration: number, - calories: number, - avgHeartRate: number -) { - await db - .update(workouts) - .set({ - finishedAt: new Date().toISOString(), - totalDuration: duration, - calories, - avgHeartRate, - }) - .where(eq(workouts.id, workoutId)); -} - export async function getWorkoutWithSets(workoutId: string) { const workout = await db.select().from(workouts).where(eq(workouts.id, workoutId)).get(); if (!workout) return null; const workoutSets = await db.select().from(sets).where(eq(sets.workoutId, workoutId)); - const workoutLaps = await db.select().from(laps).where(eq(laps.workoutId, workoutId)); - - return { ...workout, sets: workoutSets, laps: workoutLaps }; + return { ...workout, sets: workoutSets }; } export async function getAllWorkouts() { - return db.select().from(workouts).orderBy(desc(workouts.startedAt)); + return db.select().from(workouts).orderBy(desc(workouts.createdAt)); } -export async function deleteWorkout(id: string) { - await db.delete(laps).where(eq(laps.workoutId, id)); - await db.delete(sets).where(eq(sets.workoutId, id)); - await db.delete(workouts).where(eq(workouts.id, id)); -} +// ─── Workout Sessions ──────────────────────────────────────── -// ─── Laps ──────────────────────────────────────────────────── +export async function finishWorkout( + workoutId: string, + duration: number, + calories: number, + avgHeartRate: number, + totalDistance: number +) { + const sessionId = generateUUID(); + const workout = await db.select().from(workouts).where(eq(workouts.id, workoutId)).get(); -export async function saveLap(workoutId: string, lap: LapData) { - const swolf = - lap.strokeCount != null ? lap.strokeCount + Math.round(lap.duration) : null; - - await db.insert(laps).values({ - id: generateUUID(), + await db.insert(workoutSessions).values({ + id: sessionId, workoutId, - setPosition: lap.setPosition, - repNumber: lap.repNumber, - distance: lap.distance, - duration: lap.duration, - strokeCount: lap.strokeCount ?? null, - heartRate: lap.heartRate ?? null, - swolf, + name: workout?.name ?? null, + poolLength: workout?.poolLength ?? 25, + startedAt: new Date().toISOString(), + finishedAt: new Date().toISOString(), + totalDistance, + totalDuration: duration, + calories, + avgHeartRate, + source: "phone", }); -} -export async function saveLaps(workoutId: string, lapsData: LapData[]) { - for (const lap of lapsData) { - await saveLap(workoutId, lap); - } -} - -export async function getWorkoutLaps(workoutId: string) { - return db.select().from(laps).where(eq(laps.workoutId, workoutId)); + return sessionId; } export async function saveWatchCompletedWorkout(data: { workoutId: string; + sessionId?: string; duration: number; totalDistance: number; calories: number; @@ -154,36 +128,48 @@ export async function saveWatchCompletedWorkout(data: { ? Math.round(data.laps.reduce((sum, l) => sum + (l.heartRate || 0), 0) / data.laps.length) : 0; - const existing = await db - .select({ id: workouts.id }) + // Haal plan op als het bestaat + const plan = await db + .select() .from(workouts) .where(eq(workouts.id, data.workoutId)) + .get(); + + // Gebruik sessionId van de watch, of genereer een nieuwe + const sessionId = data.sessionId ?? generateUUID(); + + // Check of er al een sessie bestaat voor dit sessionId + const existingSession = await db + .select({ id: workoutSessions.id }) + .from(workoutSessions) + .where(eq(workoutSessions.id, sessionId)) .limit(1); - if (existing.length === 0) { - await db.insert(workouts).values({ - id: data.workoutId, - name: "Watch Workout", - poolLength: 25, + if (existingSession.length === 0) { + await db.insert(workoutSessions).values({ + id: sessionId, + workoutId: data.workoutId, + name: plan?.name ?? "Watch Workout", + poolLength: plan?.poolLength ?? 25, + startedAt: new Date().toISOString(), finishedAt: new Date().toISOString(), - totalDuration: data.duration, totalDistance: data.totalDistance || 0, + totalDuration: data.duration, calories: data.calories || 0, avgHeartRate, source: "watch", }); } else { await db - .update(workouts) + .update(workoutSessions) .set({ finishedAt: new Date().toISOString(), totalDuration: data.duration, totalDistance: data.totalDistance || 0, calories: data.calories || 0, avgHeartRate, - source: "watch", }) - .where(eq(workouts.id, data.workoutId)); + .where(eq(workoutSessions.id, sessionId)); } for (const lap of data.laps) { @@ -192,7 +178,7 @@ export async function saveWatchCompletedWorkout(data: { await db.insert(laps).values({ id: generateUUID(), - workoutId: data.workoutId, + sessionId, setPosition: lap.setPosition, repNumber: lap.repNumber, distance: lap.distance, @@ -204,6 +190,91 @@ export async function saveWatchCompletedWorkout(data: { } } +export async function getAllSessions() { + return db + .select() + .from(workoutSessions) + .orderBy(desc(workoutSessions.startedAt)); +} + +export async function getSessionWithDetails(sessionId: string) { + const session = await db + .select() + .from(workoutSessions) + .where(eq(workoutSessions.id, sessionId)) + .get(); + if (!session) return null; + + const sessionLaps = await db.select().from(laps).where(eq(laps.sessionId, sessionId)); + + let sessionSets: any[] = []; + if (session.workoutId) { + sessionSets = await db.select().from(sets).where(eq(sets.workoutId, session.workoutId)); + } + + return { ...session, laps: sessionLaps, sets: sessionSets }; +} + +// ─── Laps ──────────────────────────────────────────────────── + +export async function saveLap(sessionId: string, lap: LapData) { + const swolf = + lap.strokeCount != null ? lap.strokeCount + Math.round(lap.duration) : null; + + await db.insert(laps).values({ + id: generateUUID(), + sessionId, + setPosition: lap.setPosition, + repNumber: lap.repNumber, + distance: lap.distance, + duration: lap.duration, + strokeCount: lap.strokeCount ?? null, + heartRate: lap.heartRate ?? null, + swolf, + }); +} + +export async function saveLaps(sessionId: string, lapsData: LapData[]) { + for (const lap of lapsData) { + await saveLap(sessionId, lap); + } +} + +export async function getSessionLaps(sessionId: string) { + return db.select().from(laps).where(eq(laps.sessionId, sessionId)); +} + +// ─── Delete ────────────────────────────────────────────────── + +export async function deleteSession(sessionId: string) { + await db.delete(laps).where(eq(laps.sessionId, sessionId)); + await db.delete(workoutSessions).where(eq(workoutSessions.id, sessionId)); +} + +export async function deleteSessionAndSync(sessionId: string) { + await deleteSession(sessionId); + await WatchConnectivity.sendWorkoutDeleted(sessionId); +} + +export async function deleteWorkout(id: string) { + // Verwijder alle sessies die aan dit plan gelinkt zijn + const sessions = await db + .select({ id: workoutSessions.id }) + .from(workoutSessions) + .where(eq(workoutSessions.workoutId, id)); + for (const session of sessions) { + await db.delete(laps).where(eq(laps.sessionId, session.id)); + } + await db.delete(workoutSessions).where(eq(workoutSessions.workoutId, id)); + await db.delete(sets).where(eq(sets.workoutId, id)); + await db.delete(workouts).where(eq(workouts.id, id)); +} + +export async function deleteWorkoutAndSync(id: string) { + await deleteWorkout(id); + await WatchConnectivity.sendWorkoutDeleted(id); +} + // ─── Stats ─────────────────────────────────────────────────── export async function getWeeklyStats() { @@ -213,12 +284,12 @@ export async function getWeeklyStats() { return db .select({ count: sql`count(*)`, - totalDistance: sql`coalesce(sum(${workouts.totalDistance}), 0)`, - totalDuration: sql`coalesce(sum(${workouts.totalDuration}), 0)`, - avgCalories: sql`coalesce(avg(${workouts.calories}), 0)`, + totalDistance: sql`coalesce(sum(${workoutSessions.totalDistance}), 0)`, + totalDuration: sql`coalesce(sum(${workoutSessions.totalDuration}), 0)`, + avgCalories: sql`coalesce(avg(${workoutSessions.calories}), 0)`, }) - .from(workouts) - .where(sql`${workouts.startedAt} >= ${weekAgo.toISOString()}`) + .from(workoutSessions) + .where(sql`${workoutSessions.startedAt} >= ${weekAgo.toISOString()}`) .get(); } @@ -238,10 +309,10 @@ export async function getTotalStats() { return db .select({ totalWorkouts: sql`count(*)`, - totalDistance: sql`coalesce(sum(${workouts.totalDistance}), 0)`, - totalDuration: sql`coalesce(sum(${workouts.totalDuration}), 0)`, + totalDistance: sql`coalesce(sum(${workoutSessions.totalDistance}), 0)`, + totalDuration: sql`coalesce(sum(${workoutSessions.totalDuration}), 0)`, }) - .from(workouts) - .where(sql`${workouts.finishedAt} IS NOT NULL`) + .from(workoutSessions) + .where(sql`${workoutSessions.finishedAt} IS NOT NULL`) .get(); } diff --git a/src/db/relations.ts b/src/db/relations.ts index b193f66..2de7955 100644 --- a/src/db/relations.ts +++ b/src/db/relations.ts @@ -1,5 +1,5 @@ import { relations } from "drizzle-orm"; -import { workouts, sets, laps, templates } from "./schema"; +import { templates, workouts, workoutSessions, sets, laps } from "./schema"; export const templatesRelations = relations(templates, ({ many }) => ({ workouts: many(workouts), @@ -11,6 +11,14 @@ export const workoutsRelations = relations(workouts, ({ one, many }) => ({ references: [templates.id], }), sets: many(sets), + sessions: many(workoutSessions), +})); + +export const workoutSessionsRelations = relations(workoutSessions, ({ one, many }) => ({ + workout: one(workouts, { + fields: [workoutSessions.workoutId], + references: [workouts.id], + }), laps: many(laps), })); @@ -22,8 +30,8 @@ export const setsRelations = relations(sets, ({ one }) => ({ })); export const lapsRelations = relations(laps, ({ one }) => ({ - workout: one(workouts, { - fields: [laps.workoutId], - references: [workouts.id], + session: one(workoutSessions, { + fields: [laps.sessionId], + references: [workoutSessions.id], }), })); diff --git a/src/db/schema.ts b/src/db/schema.ts index 7ec8704..62c64e3 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -15,6 +15,14 @@ export const workouts = sqliteTable("workouts", { name: text("name"), templateId: text("template_id"), poolLength: real("pool_length").notNull().default(25), + createdAt: text("created_at").notNull(), +}); + +export const workoutSessions = sqliteTable("workout_sessions", { + id: text("id").primaryKey(), + workoutId: text("workout_id"), + name: text("name"), + poolLength: real("pool_length").notNull().default(25), startedAt: text("started_at").notNull(), finishedAt: text("finished_at"), totalDistance: integer("total_distance"), @@ -40,9 +48,9 @@ export const sets = sqliteTable("sets", { export const laps = sqliteTable("laps", { id: text("id").primaryKey(), - workoutId: text("workout_id") + sessionId: text("session_id") .notNull() - .references(() => workouts.id), + .references(() => workoutSessions.id), setPosition: integer("set_position").notNull(), repNumber: integer("rep_number").notNull(), distance: integer("distance").notNull(), diff --git a/src/features/active-workout/screens/PostSwimScreen.tsx b/src/features/active-workout/screens/PostSwimScreen.tsx index 3bd06c9..54cec91 100644 --- a/src/features/active-workout/screens/PostSwimScreen.tsx +++ b/src/features/active-workout/screens/PostSwimScreen.tsx @@ -5,7 +5,7 @@ import { useNavigation, useRoute } from "@react-navigation/native"; import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; import type { RouteProp } from "@react-navigation/native"; import type { RootStackParamList } from "@navigation/types"; -import { getWorkoutWithSets, getWorkoutLaps } from "@db/operations"; +import { getSessionWithDetails } from "@db/operations"; import { WorkoutSummary } from "../components/WorkoutSummary"; type Set = { @@ -38,6 +38,8 @@ export function PostSwimScreen() { sets: Set[]; laps: Lap[]; avgSwolf: number | null; + totalLengths: number | null; + avgPace: number | null; } | null>(null); useEffect(() => { @@ -45,29 +47,27 @@ export function PostSwimScreen() { }, []); const loadSummary = async () => { - const workout = await getWorkoutWithSets(route.params.workoutId); - const laps = await getWorkoutLaps(route.params.workoutId); + const session = await getSessionWithDetails(route.params.sessionId); + if (!session) return; - if (!workout) return; - - const totalDuration = workout.laps?.reduce((acc: number, l: any) => acc + l.duration, 0) ?? 0; + const totalDuration = session.totalDuration ?? 0; const avgSwolf = - laps.length > 0 - ? Math.round(laps.reduce((acc, l) => acc + (l.swolf ?? 0), 0) / laps.length) + session.laps.length > 0 + ? Math.round(session.laps.reduce((acc, l) => acc + (l.swolf ?? 0), 0) / session.laps.length) : null; const totalLengths = - workout.totalDistance && workout.poolLength - ? Math.round(workout.totalDistance / workout.poolLength) + session.totalDistance && session.poolLength + ? Math.round(session.totalDistance / session.poolLength) : null; const avgPace = - workout.totalDistance && totalDuration - ? workout.totalDistance / totalDuration + session.totalDistance && totalDuration + ? session.totalDistance / totalDuration : null; - const setsWithPace = (workout.sets ?? []).map((set: any) => { - const setLaps = laps.filter((l: any) => l.setPosition === set.position); + const setsWithPace = (session.sets ?? []).map((set: any) => { + const setLaps = session.laps.filter((l: any) => l.setPosition === set.position); const setDuration = setLaps.reduce((acc: number, l: any) => acc + l.duration, 0); const setDistance = setLaps.reduce((acc: number, l: any) => acc + l.distance, 0); const poolPace = setDistance > 0 ? setDuration / setDistance : null; @@ -75,14 +75,14 @@ export function PostSwimScreen() { }); setData({ - name: workout.name, - poolLength: workout.poolLength, - totalDistance: workout.totalDistance ?? 0, + name: session.name, + poolLength: session.poolLength, + totalDistance: session.totalDistance ?? 0, totalDuration, - calories: workout.calories, - avgHeartRate: workout.avgHeartRate, + calories: session.calories, + avgHeartRate: session.avgHeartRate, sets: setsWithPace, - laps, + laps: session.laps, avgSwolf, totalLengths, avgPace, diff --git a/src/features/history/screens/HistoryScreen.tsx b/src/features/history/screens/HistoryScreen.tsx index 95b6c29..ef7eb7d 100644 --- a/src/features/history/screens/HistoryScreen.tsx +++ b/src/features/history/screens/HistoryScreen.tsx @@ -5,12 +5,12 @@ import { useNavigation } from "@react-navigation/native"; import { useFocusEffect } from "@react-navigation/native"; import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; import type { HistoryStackParamList } from "@navigation/types"; -import { getAllWorkouts, deleteWorkout } from "@db/operations"; +import { getAllSessions, deleteSessionAndSync } from "@db/operations"; import { formatTime } from "@utils/formatTime"; import { formatDistance } from "@utils/formatDistance"; import { SwipeableRow } from "@components/SwipeableRow"; -type WorkoutRow = { +type SessionRow = { id: string; name: string | null; totalDistance: number | null; @@ -24,23 +24,23 @@ export function HistoryScreen() { const { t } = useTranslation("history"); const { t: tc } = useTranslation("common"); const navigation = useNavigation>(); - const [workouts, setWorkouts] = useState([]); + const [sessions, setSessions] = useState([]); const [refreshing, setRefreshing] = useState(false); - const loadWorkouts = async () => { - const data = await getAllWorkouts(); - setWorkouts((data as WorkoutRow[]).filter((w) => w.finishedAt)); + const loadSessions = async () => { + const data = await getAllSessions(); + setSessions(data as SessionRow[]); }; useFocusEffect( useCallback(() => { - loadWorkouts(); + loadSessions(); }, []) ); const onRefresh = async () => { setRefreshing(true); - await loadWorkouts(); + await loadSessions(); setRefreshing(false); }; @@ -51,17 +51,17 @@ export function HistoryScreen() { text: tc("delete"), style: "destructive", onPress: async () => { - await deleteWorkout(id); - await loadWorkouts(); + await deleteSessionAndSync(id); + await loadSessions(); }, }, ]); }; - const grouped = workouts.reduce>((acc, w) => { - const date = new Date(w.startedAt).toLocaleDateString(); + const grouped = sessions.reduce>((acc, s) => { + const date = new Date(s.startedAt).toLocaleDateString(); if (!acc[date]) acc[date] = []; - acc[date].push(w); + acc[date].push(s); return acc; }, {}); @@ -85,25 +85,25 @@ export function HistoryScreen() { {date} - {rows.map((w) => ( - onDelete(w.id)} deleteLabel={tc("delete")}> + {rows.map((s) => ( + onDelete(s.id)} deleteLabel={tc("delete")}> navigation.navigate("SessionDetail", { sessionId: w.id })} + onPress={() => navigation.navigate("SessionDetail", { sessionId: s.id })} className="p-4" > - {w.name || tc("swimSession")} + {s.name || tc("swimSession")} - {formatDistance(w.totalDistance ?? 0)} - {w.totalDuration ? ` · ${formatTime(w.totalDuration)}` : ""} - {w.calories ? ` · ${tc("kcal", { count: Math.round(w.calories) })}` : ""} + {formatDistance(s.totalDistance ?? 0)} + {s.totalDuration ? ` · ${formatTime(s.totalDuration)}` : ""} + {s.calories ? ` · ${tc("kcal", { count: Math.round(s.calories) })}` : ""} - {new Date(w.startedAt).toLocaleTimeString([], { + {new Date(s.startedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })} diff --git a/src/features/history/screens/SessionDetailScreen.tsx b/src/features/history/screens/SessionDetailScreen.tsx index 389a643..71be0b5 100644 --- a/src/features/history/screens/SessionDetailScreen.tsx +++ b/src/features/history/screens/SessionDetailScreen.tsx @@ -3,10 +3,10 @@ import { View, Text, Pressable, Alert } from "react-native"; import { useTranslation } from "react-i18next"; import { useNavigation } from "@react-navigation/native"; import type { HistoryStackProps } from "@navigation/types"; -import { getWorkoutWithSets, deleteWorkout } from "@db/operations"; +import { getSessionWithDetails, deleteSessionAndSync } from "@db/operations"; import { WorkoutSummary } from "../../active-workout/components/WorkoutSummary"; -type WorkoutDetail = { +type SessionDetail = { id: string; name: string | null; poolLength: number; @@ -16,6 +16,7 @@ type WorkoutDetail = { avgHeartRate: number | null; startedAt: string; finishedAt: string | null; + source: string; sets: { stroke: string; distancePerRep: number; @@ -35,15 +36,15 @@ type WorkoutDetail = { export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail">) { const { t: tc } = useTranslation("common"); const navigation = useNavigation(); - const [workout, setWorkout] = useState(null); + const [session, setSession] = useState(null); useEffect(() => { - loadWorkout(); + loadSession(); }, []); - const loadWorkout = async () => { - const data = await getWorkoutWithSets(route.params.sessionId); - setWorkout(data as WorkoutDetail | null); + const loadSession = async () => { + const data = await getSessionWithDetails(route.params.sessionId); + setSession(data as SessionDetail | null); }; const handleDelete = () => { @@ -53,7 +54,7 @@ export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail" text: tc("delete"), style: "destructive", onPress: async () => { - await deleteWorkout(route.params.sessionId); + await deleteSessionAndSync(route.params.sessionId); navigation.goBack(); }, }, @@ -61,7 +62,7 @@ export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail" }; useEffect(() => { - if (workout) { + if (session) { navigation.setOptions({ headerRight: () => ( @@ -70,9 +71,9 @@ export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail" ), }); } - }, [workout]); + }, [session]); - if (!workout) { + if (!session) { return ( {tc("loading")} @@ -81,24 +82,24 @@ export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail" } const avgSwolf = - workout.laps.length > 0 + session.laps.length > 0 ? Math.round( - workout.laps.reduce((acc, l) => acc + (l.swolf ?? 0), 0) / workout.laps.length + session.laps.reduce((acc, l) => acc + (l.swolf ?? 0), 0) / session.laps.length ) : null; const totalLengths = - workout.totalDistance && workout.poolLength - ? Math.round(workout.totalDistance / workout.poolLength) + session.totalDistance && session.poolLength + ? Math.round(session.totalDistance / session.poolLength) : null; const avgPace = - workout.totalDistance && workout.totalDuration - ? workout.totalDistance / workout.totalDuration + session.totalDistance && session.totalDuration + ? session.totalDistance / session.totalDuration : null; - const setsWithPace = workout.sets.map((set) => { - const setLaps = workout.laps.filter((l) => l.setPosition === set.position); + const setsWithPace = session.sets.map((set) => { + const setLaps = session.laps.filter((l) => l.setPosition === set.position); const setDuration = setLaps.reduce((acc, l) => acc + l.duration, 0); const setDistance = setLaps.reduce((acc, l) => acc + l.distance, 0); const poolPace = setDistance > 0 ? setDuration / setDistance : null; @@ -107,17 +108,17 @@ export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail" return ( ); diff --git a/src/features/watch-connectivity/hooks/useWatchWorkoutSync.ts b/src/features/watch-connectivity/hooks/useWatchWorkoutSync.ts index 53001af..2bb1fd0 100644 --- a/src/features/watch-connectivity/hooks/useWatchWorkoutSync.ts +++ b/src/features/watch-connectivity/hooks/useWatchWorkoutSync.ts @@ -2,7 +2,7 @@ import { useEffect, useRef } from "react"; import { Platform } from "react-native"; import WatchConnectivity from "../../../modules/watch-connectivity"; import WearConnectivity from "../../../modules/wear-connectivity"; -import { saveWatchCompletedWorkout } from "@db/operations"; +import { saveWatchCompletedWorkout, deleteSession } from "@db/operations"; export function useWatchWorkoutSync() { const pollingRef = useRef | null>(null); @@ -22,11 +22,14 @@ export function useWatchWorkoutSync() { if (event.type === "workoutCompleted") { await saveWatchCompletedWorkout({ workoutId: event.workoutId, + sessionId: event.sessionId, duration: event.duration ?? 0, totalDistance: event.totalDistance ?? 0, calories: event.calories ?? 0, laps: event.laps ?? [], }); + } else if (event.type === "workoutDeleted") { + await deleteSession(event.workoutId); } } } catch (error) { diff --git a/src/features/workout-builder/screens/WorkoutListScreen.tsx b/src/features/workout-builder/screens/WorkoutListScreen.tsx index 8f572d5..0171a18 100644 --- a/src/features/workout-builder/screens/WorkoutListScreen.tsx +++ b/src/features/workout-builder/screens/WorkoutListScreen.tsx @@ -5,18 +5,16 @@ import { useNavigation } from "@react-navigation/native"; import { useFocusEffect } from "@react-navigation/native"; import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; import type { WorkoutStackParamList } from "@navigation/types"; -import { getAllWorkouts, deleteWorkout } from "@db/operations"; -import { formatTime } from "@utils/formatTime"; +import { getAllWorkouts, getWorkoutWithSets, deleteWorkoutAndSync } from "@db/operations"; import { formatDistance } from "@utils/formatDistance"; import { SwipeableRow } from "@components/SwipeableRow"; type WorkoutRow = { id: string; name: string | null; - totalDistance: number | null; - totalDuration: number | null; - startedAt: string; - finishedAt: string | null; + poolLength: number; + createdAt: string; + totalDistance?: number; }; export function WorkoutListScreen() { @@ -28,7 +26,22 @@ export function WorkoutListScreen() { const loadWorkouts = async () => { const data = await getAllWorkouts(); - setWorkouts(data as WorkoutRow[]); + const rows = data as WorkoutRow[]; + + // Bereken totalDistance per workout op basis van sets + const withDistance = await Promise.all( + rows.map(async (w) => { + const detail = await getWorkoutWithSets(w.id); + if (!detail) return w; + const totalDistance = (detail.sets ?? []).reduce( + (acc: number, s: any) => acc + s.distancePerRep * s.repCount, + 0 + ); + return { ...w, totalDistance }; + }) + ); + + setWorkouts(withDistance); }; useFocusEffect( @@ -50,7 +63,7 @@ export function WorkoutListScreen() { text: tc("delete"), style: "destructive", onPress: async () => { - await deleteWorkout(id); + await deleteWorkoutAndSync(id); await loadWorkouts(); }, }, @@ -92,18 +105,13 @@ export function WorkoutListScreen() { {item.name || tc("untitledWorkout")} - {formatDistance(item.totalDistance ?? 0)} - {item.totalDuration ? ` · ${formatTime(item.totalDuration)}` : ""} + {item.totalDistance ? formatDistance(item.totalDistance) : ""} + {item.poolLength ? ` · ${tc("pool", { length: item.poolLength })}` : ""} - {new Date(item.startedAt).toLocaleDateString()} + {new Date(item.createdAt).toLocaleDateString()} - {item.finishedAt && ( - - {tc("done")} - - )} diff --git a/src/navigation/types.ts b/src/navigation/types.ts index 5bd97d9..5399ae8 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -4,7 +4,7 @@ export type RootStackParamList = { Main: undefined; WorkoutBuilder: { templateId?: string } | undefined; PreSwim: { workoutId: string }; - PostSwim: { workoutId: string }; + PostSwim: { sessionId: string }; SessionDetail: { sessionId: string }; }; diff --git a/targets/watch/WorkoutManager.swift b/targets/watch/WorkoutManager.swift index eee7232..595f23e 100644 --- a/targets/watch/WorkoutManager.swift +++ b/targets/watch/WorkoutManager.swift @@ -526,9 +526,11 @@ class WorkoutManager: NSObject, ObservableObject, HKLiveWorkoutBuilderDelegate { // MARK: - Sync to Phone private func syncCompletedWorkout() { + let sessionId = UUID().uuidString let workoutSummary: [String: Any] = [ "type": "workoutCompleted", "workoutId": selectedWorkout?.id ?? "", + "sessionId": sessionId, "name": selectedWorkout?.name ?? "Workout", "poolLength": selectedWorkout?.poolLength ?? 0, "laps": completedLaps.map { lap in