Repository: IgnisDa/ryot (language: rust)

Issue:
General improvements
- [ ] ~if next exercise starts a superset, then open details for all of them~
- [x] add a "advanced settings" section while editing a notification platform
- [x] If i delete a set (before the current) while a rest timer is ticking down, the position of the timer gets messed


Discussion / hints:

Fix patch:
diff --git a/apps/frontend/app/lib/state/fitness.ts b/apps/frontend/app/lib/state/fitness.ts
index cebd1d22d1..1b888eea2a 100644
--- a/apps/frontend/app/lib/state/fitness.ts
+++ b/apps/frontend/app/lib/state/fitness.ts
@@ -38,6 +38,7 @@ import type { useCoreDetails } from "../hooks";
 
 export type ExerciseSet = {
 	lot: SetLot;
+	identifier: string;
 	rpe?: number | null;
 	restTimerStartedAt?: string;
 	statistic: WorkoutSetStatistic;
@@ -187,6 +188,7 @@ export const convertHistorySetToCurrentSet = (
 		lot: set.lot,
 		rpe: set.rpe,
 		note: set.note,
+		identifier: randomUUID(),
 		statistic: set.statistic,
 		confirmedAt: confirmedAt ?? null,
 		restTimer: set.restTime ? { duration: set.restTime } : null,
@@ -262,7 +264,7 @@ export type CurrentWorkoutTimer = {
 	willEndAt: string;
 	totalTime: number;
 	wasPausedAt?: string;
-	triggeredBy?: { exerciseIdentifier: string; setIdx: number };
+	triggeredBy?: { exerciseIdentifier: string; setIdentifier: string };
 };
 
 const currentWorkoutTimerAtom = atomWithStorage<CurrentWorkoutTimer | null>(
@@ -398,6 +400,7 @@ export const addExerciseToCurrentWorkout = async (
 				lot: setLot,
 				statistic: {},
 				confirmedAt: null,
+				identifier: randomUUID(),
 				restTimer: restTimer ? { duration: restTimer } : undefined,
 			},
 		];
diff --git a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx
index bf0afeac20..44c60cd84a 100644
--- a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx
+++ b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx
@@ -354,12 +354,15 @@ export default function Page() {
 					const exerciseIdx = currentWorkout?.exercises.findIndex(
 						(c) => c.identifier === triggeredBy.exerciseIdentifier,
 					);
+					const setIdx = currentWorkout?.exercises[exerciseIdx]?.sets.findIndex(
+						(s) => s.identifier === triggeredBy.setIdentifier,
+					);
 					if (
 						exerciseIdx !== -1 &&
 						exerciseIdx !== undefined &&
 						userPreferences.fitness.logging.promptForRestTimer
 					) {
-						performTasksAfterSetConfirmed(triggeredBy.setIdx, exerciseIdx);
+						performTasksAfterSetConfirmed(setIdx, exerciseIdx);
 					}
 				}
 				playCompleteTimerSound();
@@ -382,7 +385,7 @@ export default function Page() {
 	};
 	const startTimer = (
 		duration: number,
-		triggeredBy?: { exerciseIdentifier: string; setIdx: number },
+		triggeredBy?: { exerciseIdentifier: string; setIdentifier: string },
 	) => {
 		setCurrentTimer({
 			triggeredBy,
@@ -411,8 +414,13 @@ export default function Page() {
 					const exercise = draft.exercises.find(
 						(e) => e.identifier === triggeredBy.exerciseIdentifier,
 					);
-					const restTimer = exercise?.sets[triggeredBy.setIdx].restTimer;
-					if (exercise && restTimer) restTimer.hasElapsed = true;
+					if (exercise) {
+						const setIdx = exercise.sets.findIndex(
+							(s) => s.identifier === triggeredBy.setIdentifier,
+						);
+						const restTimer = exercise.sets[setIdx].restTimer;
+						if (restTimer) restTimer.hasElapsed = true;
+					}
 				}),
 			);
 		}
@@ -1245,7 +1253,7 @@ const EditSupersetExerciseButton = (props: {
 
 type FuncStartTimer = (
 	duration: number,
-	triggeredBy: { exerciseIdentifier: string; setIdx: number },
+	triggeredBy: { exerciseIdentifier: string; setIdentifier: string },
 ) => void;
 
 const focusOnExercise = (idx: number) => {
@@ -1869,6 +1877,7 @@ const ExerciseDisplay = (props: {
 											draft.exercises[props.exerciseIdx].sets.push({
 												lot: setLot,
 												confirmedAt: null,
+												identifier: randomUUID(),
 												statistic: currentSet?.statistic ?? {},
 												restTimer: restTimer
 													? { duration: restTimer }
@@ -1975,7 +1984,7 @@ const SetDisplay = (props: {
 
 	const didCurrentSetActivateTimer =
 		currentTimer?.triggeredBy?.exerciseIdentifier === exercise.identifier &&
-		currentTimer?.triggeredBy?.setIdx === props.setIdx;
+		currentTimer?.triggeredBy?.setIdentifier === set.identifier;
 	const hasRestTimerOfThisSetElapsed = set.restTimer?.hasElapsed;
 	const promptForRestTimer = userPreferences.fitness.logging.promptForRestTimer;
 
@@ -2272,7 +2281,7 @@ const SetDisplay = (props: {
 										onClick={() => {
 											invariant(set.restTimer);
 											props.startTimer(set.restTimer.duration, {
-												setIdx: props.setIdx,
+												setIdentifier: set.identifier,
 												exerciseIdentifier: exercise.identifier,
 											});
 											setCurrentWorkout(
@@ -2336,12 +2345,13 @@ const SetDisplay = (props: {
 												!newConfirmed &&
 												currentTimer?.triggeredBy?.exerciseIdentifier ===
 													exercise.identifier &&
-												currentTimer?.triggeredBy?.setIdx === props.setIdx
+												currentTimer?.triggeredBy?.setIdentifier ===
+													set.identifier
 											)
 												props.stopTimer();
 											if (set.restTimer && newConfirmed && !promptForRestTimer)
 												props.startTimer(set.restTimer.duration, {
-													setIdx: props.setIdx,
+													setIdentifier: set.identifier,
 													exerciseIdentifier: exercise.identifier,
 												});
 											setCurrentWorkout(
diff --git a/apps/frontend/app/routes/_dashboard.settings.notifications.tsx b/apps/frontend/app/routes/_dashboard.settings.notifications.tsx
index 4553c6bb12..0127034a48 100644
--- a/apps/frontend/app/routes/_dashboard.settings.notifications.tsx
+++ b/apps/frontend/app/routes/_dashboard.settings.notifications.tsx
@@ -3,8 +3,8 @@ import {
 	Anchor,
 	Box,
 	Button,
+	Collapse,
 	Container,
-	Divider,
 	Flex,
 	Group,
 	Modal,
@@ -311,6 +311,8 @@ const DisplayNotification = (props: {
 	const submit = useConfirmSubmit();
 	const [editModalOpened, { open: openEditModal, close: closeEditModal }] =
 		useDisclosure(false);
+	const [isAdvancedSettingsOpen, { toggle: toggleAdvancedSettings }] =
+		useDisclosure(false);
 	const [configuredEvents, configuredEventsHandler] =
 		useListState<UserNotificationContent>(props.notification.configuredEvents);
 
@@ -339,89 +341,98 @@ const DisplayNotification = (props: {
 							label="Disable notification"
 							defaultChecked={props.notification.isDisabled ?? false}
 						/>
-						<Divider />
-						<Stack gap="xs">
-							{Object.values(UserNotificationContent).map((name) => (
-								<Switch
-									size="xs"
-									key={name}
-									defaultChecked={props.notification.configuredEvents.includes(
-										name,
-									)}
-									onChange={(value) => {
-										const checked = value.target.checked;
-										if (checked) configuredEventsHandler.append(name);
-										else
-											configuredEventsHandler.filter((event) => event !== name);
-									}}
-									label={match(name)
-										.with(
-											UserNotificationContent.OutdatedSeenEntries,
-											() => "Media has been in progress/on hold for too long",
-										)
-										.with(
-											UserNotificationContent.MetadataEpisodeNameChanged,
-											() => "Name of an episode changes",
-										)
-										.with(
-											UserNotificationContent.MetadataEpisodeImagesChanged,
-											() => "Images for an episode changes",
-										)
-										.with(
-											UserNotificationContent.MetadataEpisodeReleased,
-											() => "Number of episodes changes",
-										)
-										.with(
-											UserNotificationContent.MetadataPublished,
+						<Flex justify="end">
+							<Anchor c="blue" size="xs" onClick={toggleAdvancedSettings}>
+								{isAdvancedSettingsOpen ? "Hide" : "Show"} advanced settings
+							</Anchor>
+						</Flex>
+						<Collapse in={isAdvancedSettingsOpen}>
+							<Stack gap="xs">
+								{Object.values(UserNotificationContent).map((name) => (
+									<Switch
+										size="xs"
+										key={name}
+										defaultChecked={props.notification.configuredEvents.includes(
+											name,
+										)}
+										onChange={(value) => {
+											const checked = value.target.checked;
+											if (checked) configuredEventsHandler.append(name);
+											else
+												configuredEventsHandler.filter(
+													(event) => event !== name,
+												);
+										}}
+										label={match(name)
+											.with(
+												UserNotificationContent.OutdatedSeenEntries,
+												() => "Media has been in progress/on hold for too long",
+											)
+											.with(
+												UserNotificationContent.MetadataEpisodeNameChanged,
+												() => "Name of an episode changes",
+											)
+											.with(
+												UserNotificationContent.MetadataEpisodeImagesChanged,
+												() => "Images for an episode changes",
+											)
+											.with(
+												UserNotificationContent.MetadataEpisodeReleased,
+												() => "Number of episodes changes",
+											)
+											.with(
+												UserNotificationContent.MetadataPublished,
 
-											() => "A media is published",
-										)
-										.with(
-											UserNotificationContent.MetadataStatusChanged,
-											() => "Status changes",
-										)
-										.with(
-											UserNotificationContent.MetadataReleaseDateChanged,
-											() => "Release date changes",
-										)
-										.with(
-											UserNotificationContent.MetadataNumberOfSeasonsChanged,
-											() => "Number of seasons changes",
-										)
-										.with(
-											UserNotificationContent.MetadataChaptersOrEpisodesChanged,
-											() =>
-												"Number of chapters/episodes changes for manga/anime",
-										)
-										.with(
-											UserNotificationContent.ReviewPosted,
-											() =>
-												"A new public review is posted for media/people you monitor",
-										)
-										.with(
-											UserNotificationContent.PersonMetadataAssociated,
-											() => "New media is associated with a person",
-										)
-										.with(
-											UserNotificationContent.PersonMetadataGroupAssociated,
-											() => "New media group is associated with a person",
-										)
-										.with(
-											UserNotificationContent.NotificationFromReminderCollection,
-											() => "When an item is added to the reminder collection",
-										)
-										.with(
-											UserNotificationContent.NewWorkoutCreated,
-											() => "A new workout is created",
-										)
-										.with(
-											UserNotificationContent.IntegrationDisabledDueToTooManyErrors,
-											() => "Integration disabled due to too many errors",
-										)
-										.exhaustive()}
-								/>
-							))}
-						</Stack>
+												() => "A media is published",
+											)
+											.with(
+												UserNotificationContent.MetadataStatusChanged,
+												() => "Status changes",
+											)
+											.with(
+												UserNotificationContent.MetadataReleaseDateChanged,
+												() => "Release date changes",
+											)
+											.with(
+												UserNotificationContent.MetadataNumberOfSeasonsChanged,
+												() => "Number of seasons changes",
+											)
+											.with(
+												UserNotificationContent.MetadataChaptersOrEpisodesChanged,
+												() =>
+													"Number of chapters/episodes changes for manga/anime",
+											)
+											.with(
+												UserNotificationContent.ReviewPosted,
+												() =>
+													"A new public review is posted for media/people you monitor",
+											)
+											.with(
+												UserNotificationContent.PersonMetadataAssociated,
+												() => "New media is associated with a person",
+											)
+											.with(
+												UserNotificationContent.PersonMetadataGroupAssociated,
+												() => "New media group is associated with a person",
+											)
+											.with(
+												UserNotificationContent.NotificationFromReminderCollection,
+												() =>
+													"When an item is added to the reminder collection",
+											)
+											.with(
+												UserNotificationContent.NewWorkoutCreated,
+												() => "A new workout is created",
+											)
+											.with(
+												UserNotificationContent.IntegrationDisabledDueToTooManyErrors,
+												() => "Integration disabled due to too many errors",
+											)
+											.exhaustive()}
+									/>
+								))}
+							</Stack>
+						</Collapse>
 						<Button type="submit" onClick={closeEditModal}>
 							Save
 						</Button>
@@ -453,7 +464,15 @@ const DisplayNotification = (props: {
 							<IconPencil />
 						</ActionIcon>
 						<Tooltip label="Delete">
-							<Form method="POST" action={withQuery(".", { intent: "delete" })}>
+							<Form
+								method="POST"
+								action={withQuery(".", { intent: "delete" })}
+								style={{
+									display: "flex",
+									alignItems: "center",
+									justifyContent: "center",
+								}}
+							>
 								<input
 									hidden
 									name="notificationId"


Test patch: