workout-builder.store.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import { create } from "zustand";
  2. import { ExerciseAttributeValueEnum, WorkoutSessionExercise } from "@prisma/client";
  3. import { WorkoutBuilderStep } from "../types";
  4. import { shuffleExerciseAction } from "../actions/shuffle-exercise.action";
  5. import { getExercisesAction } from "./get-exercises.action";
  6. interface WorkoutBuilderState {
  7. currentStep: WorkoutBuilderStep;
  8. selectedEquipment: ExerciseAttributeValueEnum[];
  9. selectedMuscles: ExerciseAttributeValueEnum[];
  10. exercisesByMuscle: any[]; //TODO: type this
  11. isLoadingExercises: boolean;
  12. exercisesError: any; //TODO: type this
  13. exercisesOrder: string[];
  14. isShuffling: boolean;
  15. // Actions
  16. setStep: (step: WorkoutBuilderStep) => void;
  17. nextStep: () => void;
  18. prevStep: () => void;
  19. toggleEquipment: (equipment: ExerciseAttributeValueEnum) => void;
  20. clearEquipment: () => void;
  21. toggleMuscle: (muscle: ExerciseAttributeValueEnum) => void;
  22. clearMuscles: () => void;
  23. fetchExercises: () => Promise<void>;
  24. setExercisesOrder: (order: string[]) => void;
  25. shuffleExercise: (exerciseId: string, muscle: ExerciseAttributeValueEnum) => Promise<void>;
  26. loadFromSession: (params: {
  27. equipment: ExerciseAttributeValueEnum[];
  28. muscles: ExerciseAttributeValueEnum[];
  29. exercisesByMuscle: {
  30. muscle: ExerciseAttributeValueEnum;
  31. exercises: WorkoutSessionExercise[];
  32. }[];
  33. exercisesOrder: string[];
  34. }) => void;
  35. }
  36. export const useWorkoutBuilderStore = create<WorkoutBuilderState>((set, get) => ({
  37. currentStep: 1 as WorkoutBuilderStep,
  38. selectedEquipment: [],
  39. selectedMuscles: [],
  40. exercisesByMuscle: [],
  41. isLoadingExercises: false,
  42. exercisesError: null,
  43. exercisesOrder: [],
  44. isShuffling: false,
  45. setStep: (step) => set({ currentStep: step }),
  46. nextStep: () => set((state) => ({ currentStep: Math.min(state.currentStep + 1, 3) as WorkoutBuilderStep })),
  47. prevStep: () => set((state) => ({ currentStep: Math.max(state.currentStep - 1, 1) as WorkoutBuilderStep })),
  48. toggleEquipment: (equipment) =>
  49. set((state) => ({
  50. selectedEquipment: state.selectedEquipment.includes(equipment)
  51. ? state.selectedEquipment.filter((e) => e !== equipment)
  52. : [...state.selectedEquipment, equipment],
  53. })),
  54. clearEquipment: () => set({ selectedEquipment: [] }),
  55. toggleMuscle: (muscle) =>
  56. set((state) => ({
  57. selectedMuscles: state.selectedMuscles.includes(muscle)
  58. ? state.selectedMuscles.filter((m) => m !== muscle)
  59. : [...state.selectedMuscles, muscle],
  60. })),
  61. clearMuscles: () => set({ selectedMuscles: [] }),
  62. fetchExercises: async () => {
  63. set({ isLoadingExercises: true, exercisesError: null });
  64. try {
  65. const { selectedEquipment, selectedMuscles } = get();
  66. const result = await getExercisesAction({
  67. equipment: selectedEquipment,
  68. muscles: selectedMuscles,
  69. limit: 3,
  70. });
  71. if (result?.serverError) {
  72. throw new Error(result.serverError);
  73. }
  74. set({ exercisesByMuscle: result?.data || [], isLoadingExercises: false });
  75. } catch (error) {
  76. set({ exercisesError: error, isLoadingExercises: false });
  77. }
  78. },
  79. setExercisesOrder: (order) => set({ exercisesOrder: order }),
  80. shuffleExercise: async (exerciseId, muscle) => {
  81. set({ isShuffling: true });
  82. try {
  83. const { selectedEquipment, exercisesByMuscle } = get();
  84. const allExerciseIds = exercisesByMuscle.flatMap((group) => group.exercises.map((ex: any) => ex.id));
  85. const result = await shuffleExerciseAction({
  86. muscle: muscle,
  87. equipment: selectedEquipment,
  88. excludeExerciseIds: allExerciseIds,
  89. });
  90. if (result?.serverError) {
  91. throw new Error(result.serverError);
  92. }
  93. if (result?.data?.exercise) {
  94. const newExercise = result.data.exercise;
  95. set((state) => ({
  96. exercisesByMuscle: state.exercisesByMuscle.map((group) => {
  97. if (group.muscle === muscle) {
  98. return {
  99. ...group,
  100. exercises: group.exercises.map((ex: any) => (ex.id === exerciseId ? { ...newExercise, order: ex.order } : ex)),
  101. };
  102. }
  103. return group;
  104. }),
  105. exercisesOrder: state.exercisesOrder.map((id) => (id === exerciseId ? newExercise.id : id)),
  106. }));
  107. }
  108. } catch (error) {
  109. console.error("Error shuffling exercise:", error);
  110. throw error;
  111. } finally {
  112. set({ isShuffling: false });
  113. }
  114. },
  115. loadFromSession: ({ equipment, muscles, exercisesByMuscle, exercisesOrder }) => {
  116. set({
  117. selectedEquipment: equipment,
  118. selectedMuscles: muscles,
  119. exercisesByMuscle,
  120. exercisesOrder,
  121. currentStep: 3,
  122. isLoadingExercises: false,
  123. exercisesError: null,
  124. });
  125. },
  126. }));