Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 111 additions & 15 deletions apps/web/src/features/workout/sortable-workout-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
FormMessage,
} from '@/shared/components/ui/form';
import { Input } from '@/shared/components/ui/input';
import { Textarea } from '@/shared/components/ui/textarea';
import {
Select,
SelectContent,
Expand Down Expand Up @@ -64,6 +65,10 @@ export function SortableWorkoutElement({
const [editingWeight, setEditingWeight] = useState(false);
const [editingRest, setEditingRest] = useState(false);
const [editingElement, setEditingElement] = useState(false);
const [editingDescription, setEditingDescription] = useState(false);
const [editingExerciseDescription, setEditingExerciseDescription] = useState(false);
const [localDescription, setLocalDescription] = useState<string>('');
const [localExerciseDescription, setLocalExerciseDescription] = useState<string>('');
const selectRef = useRef<HTMLButtonElement>(null);

const {
Expand All @@ -87,19 +92,38 @@ export function SortableWorkoutElement({

// Trouver le complex sélectionné
const selectedComplex = complexes.find((c) => c.id === selectedComplexId);

// Trouver l'exercice sélectionné
const selectedExercise = exercises.find((e) => e.id === control._formValues.elements[index].id);

// Mettre à jour selectedComplexId quand l'ID change dans le form
// Mettre à jour selectedComplexId et exercice quand l'ID change dans le form
useEffect(() => {
const currentId = control._formValues.elements[index].id;
if (
currentId &&
control._formValues.elements[index].type === WORKOUT_ELEMENT_TYPES.COMPLEX
) {
const currentType = control._formValues.elements[index].type;

if (currentId && currentType === WORKOUT_ELEMENT_TYPES.COMPLEX) {
setSelectedComplexId(currentId);
// Initialiser la description locale avec celle du complexe
const complex = complexes.find(c => c.id === currentId);
if (complex?.description && !localDescription) {
setLocalDescription(complex.description);
}
}

if (currentId && currentType === WORKOUT_ELEMENT_TYPES.EXERCISE) {
// Initialiser la description locale avec celle de l'exercice
const exercise = exercises.find(e => e.id === currentId);
if (exercise?.description && !localExerciseDescription) {
setLocalExerciseDescription(exercise.description);
}
}
}, [
control._formValues.elements[index].id,
control._formValues.elements[index].type,
complexes,
exercises,
localDescription,
localExerciseDescription,
]);

const renderEditableBadge = (
Expand Down Expand Up @@ -256,7 +280,14 @@ export function SortableWorkoutElement({
))
: complexes.map((complex) => (
<SelectItem key={complex.id} value={complex.id}>
{complex.complexCategory?.name || 'Complex'}
<div className="flex flex-col">
<span className="font-medium">
{complex.exercises.map(ex => ex.name).join(', ')}
</span>
<span className="text-xs text-muted-foreground">
{complex.complexCategory?.name}
</span>
</div>
</SelectItem>
))}
</SelectContent>
Expand All @@ -275,7 +306,7 @@ export function SortableWorkoutElement({
>
{type === WORKOUT_ELEMENT_TYPES.EXERCISE
? exercises.find((e) => e.id === field.value)?.name
: complexes.find((c) => c.id === field.value)?.complexCategory?.name || 'Complex'}
: complexes.find((c) => c.id === field.value)?.exercises.map(ex => ex.name).join(', ') || 'Complex'}
</button>
)}
<FormMessage />
Expand Down Expand Up @@ -320,6 +351,42 @@ export function SortableWorkoutElement({
)}
{renderElementSelect(WORKOUT_ELEMENT_TYPES.EXERCISE)}
</div>
<div className="text-sm text-muted-foreground ml-2">
{editingExerciseDescription ? (
<Textarea
value={localExerciseDescription || selectedExercise?.description || ''}
onChange={(e) => setLocalExerciseDescription(e.target.value)}
onBlur={() => setEditingExerciseDescription(false)}
onKeyDown={(e) => {
if (e.key === 'Escape') {
setEditingExerciseDescription(false);
setLocalExerciseDescription(selectedExercise?.description || '');
}
if (e.key === 'Enter' && e.ctrlKey) {
setEditingExerciseDescription(false);
}
}}
autoFocus
className="text-sm resize-none min-h-[60px]"
placeholder="Description personnalisée pour cet exercice..."
/>
) : (
<div
className="cursor-pointer hover:bg-muted/30 p-2 rounded min-h-[60px] border-2 border-dashed border-transparent hover:border-muted"
onClick={() => setEditingExerciseDescription(true)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
setEditingExerciseDescription(true);
}
}}
tabIndex={0}
role="button"
aria-label="Ajouter une description personnalisée pour cet exercice"
>
{localExerciseDescription || selectedExercise?.description || 'Cliquez pour ajouter une description personnalisée...'}
</div>
)}
</div>
<div className="text-sm text-muted-foreground ml-2 flex items-center gap-1">
{renderEditableText(
control._formValues.elements[index].sets,
Expand Down Expand Up @@ -358,15 +425,44 @@ export function SortableWorkoutElement({
<div className="flex items-center gap-4 rounded-md p-3">
<div className="flex-1 ml-2 flex flex-col gap-2">
<div className="flex flex-1 gap-2">
{renderEditableBadge(
control._formValues.elements[index].reps,
editingReps,
setEditingReps,
'reps',
1
)}
{renderElementSelect(WORKOUT_ELEMENT_TYPES.COMPLEX)}
</div>
<div className="text-sm text-muted-foreground ml-2">
{editingDescription ? (
<Textarea
value={localDescription || selectedComplex?.description || ''}
onChange={(e) => setLocalDescription(e.target.value)}
onBlur={() => setEditingDescription(false)}
onKeyDown={(e) => {
if (e.key === 'Escape') {
setEditingDescription(false);
setLocalDescription(selectedComplex?.description || '');
}
if (e.key === 'Enter' && e.ctrlKey) {
setEditingDescription(false);
}
}}
autoFocus
className="text-sm resize-none min-h-[60px]"
placeholder="Description personnalisée pour ce workout..."
/>
) : (
<div
className="cursor-pointer hover:bg-muted/30 p-2 rounded min-h-[60px] border-2 border-dashed border-transparent hover:border-muted"
onClick={() => setEditingDescription(true)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
setEditingDescription(true);
}
}}
tabIndex={0}
role="button"
aria-label="Ajouter une description personnalisée pour ce complex"
>
{localDescription || selectedComplex?.description || 'Cliquez pour ajouter une description personnalisée...'}
</div>
)}
</div>
<div className="text-sm text-muted-foreground ml-2 flex items-center gap-1">
{renderEditableText(
control._formValues.elements[index].sets,
Expand Down Expand Up @@ -412,7 +508,7 @@ export function SortableWorkoutElement({
<div className="flex-1 ml-2 flex flex-col gap-2">
<div className="flex flex-1 gap-2">
<Badge variant="outline">{exercise.reps}</Badge>
<div className="font-sm">{exercise.name}</div>
<div className="font-medium">{exercise.name}</div>
</div>
</div>
</div>
Expand Down
99 changes: 66 additions & 33 deletions apps/web/src/features/workout/steps/workout-elements-step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { api } from '@/lib/api';
import { Button } from '@/shared/components/ui/button';
import { Card, CardContent } from '@/shared/components/ui/card';
import { Input } from '@/shared/components/ui/input';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/shared/components/ui/tooltip';
import {
DndContext,
DragEndEvent,
Expand Down Expand Up @@ -132,12 +133,20 @@ export function WorkoutElementsStep({
exercise.name.toLowerCase().includes(exerciseSearch.toLowerCase())
) || [];

const filteredComplexes = complexes?.filter(complex =>
complex.complexCategory?.name?.toLowerCase().includes(complexSearch.toLowerCase())
) || [];
const filteredComplexes = complexes?.filter(complex => {
const searchTerm = complexSearch.toLowerCase();
// Recherche dans le nom de la catégorie
const categoryMatch = complex.complexCategory?.name?.toLowerCase().includes(searchTerm);
// Recherche dans les noms des exercices du complexe
const exerciseMatch = complex.exercises.some(exercise =>
exercise.name.toLowerCase().includes(searchTerm)
);
return categoryMatch || exerciseMatch;
}) || [];

return (
<div className="h-full flex flex-col">
<TooltipProvider>
<div className="h-full flex flex-col">

{/* Layout principal : 2 colonnes */}
<div className="flex-1 grid grid-cols-4 gap-6 min-h-0 mt-6">
Expand Down Expand Up @@ -204,7 +213,7 @@ export function WorkoutElementsStep({
e.stopPropagation();
setCreateExerciseModalOpen(true);
}}
className="w-full bg-orange-100"
className="w-full bg-primary/5 hover:bg-primary/10"
>
<Plus className="h-4 w-4 mr-2" />
Créer un nouvel exercice
Expand All @@ -215,23 +224,30 @@ export function WorkoutElementsStep({
{filteredExercises.map((exercise) => (
<div
key={exercise.id}
className="border rounded-lg p-3 cursor-pointer hover:bg-muted/50 transition-colors bg-background"
className="border rounded-lg p-3 cursor-pointer hover:bg-muted/50 transition-colors bg-muted/30"
>
<div className="flex items-center justify-between">
<div>
<h5 className="font-medium text-sm">{exercise.name}</h5>
<div className="flex-1 mr-2">
<h5 className="font-medium text-sm mb-2">{exercise.name}</h5>
<p className="text-xs text-muted-foreground">
{exercise.exerciseCategory.name}
</p>
</div>
<Button
type="button"
size="sm"
variant="ghost"
onClick={() => handleAddElement('exercise', exercise.id)}
>
<Plus className="h-4 w-4" />
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
size="sm"
variant="outline"
onClick={() => handleAddElement('exercise', exercise.id)}
>
<Plus className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Ajouter cet exercice à l'entraînement</p>
</TooltipContent>
</Tooltip>
</div>
</div>
))}
Expand All @@ -241,7 +257,7 @@ export function WorkoutElementsStep({

{activeTab === 'complex' && (
<div className="flex-1 flex flex-col min-h-0">
<div className="relative mb-3 flex-shrink-0 bg-sidebar">
<div className="relative mb-3 flex-shrink-0">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Rechercher un complexe..."
Expand All @@ -261,7 +277,7 @@ export function WorkoutElementsStep({
e.stopPropagation();
setCreateComplexModalOpen(true);
}}
className="w-full bg-orange-100"
className="w-full bg-secondary hover:bg-secondary/80"
>
<Plus className="h-4 w-4 mr-2" />
Créer un nouveau complexe
Expand All @@ -272,23 +288,39 @@ export function WorkoutElementsStep({
{filteredComplexes.map((complex) => (
<div
key={complex.id}
className="border rounded-lg p-3 cursor-pointer hover:bg-muted/50 transition-colors bg-background"
className="border rounded-lg p-3 cursor-pointer hover:bg-muted/50 transition-colors bg-muted/30"
>
<div className="flex items-center justify-between">
<div>
<h5 className="font-medium text-sm">{complex.complexCategory?.name || 'Complex'}</h5>
<p className="text-xs text-muted-foreground">
{complex.exercises.length} exercices
</p>
<div className="flex-1 mr-2">
<h5 className="font-medium text-sm mb-2">
{complex.exercises.map((exercise, index) => (
<span key={exercise.id}>
{exercise.name}
{index < complex.exercises.length - 1 ? ', ' : ''}
</span>
))}
</h5>
{complex.complexCategory?.name && (
<p className="text-xs text-muted-foreground">
{complex.complexCategory.name}
</p>
)}
</div>
<Button
type="button"
size="sm"
variant="ghost"
onClick={() => handleAddElement('complex', complex.id)}
>
<Plus className="h-4 w-4" />
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
size="sm"
variant="outline"
onClick={() => handleAddElement('complex', complex.id)}
>
<Plus className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Ajouter ce complexe à l'entraînement</p>
</TooltipContent>
</Tooltip>
</div>
</div>
))}
Expand Down Expand Up @@ -396,6 +428,7 @@ export function WorkoutElementsStep({
onCancel={() => setCreateComplexModalOpen(false)}
/>
</DialogCreation>
</div>
</div>
</TooltipProvider>
);
}
5 changes: 5 additions & 0 deletions apps/web/src/routes/__home.workouts.create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ function CreateWorkoutPage() {

const handleCreationSuccess = (data: CreateWorkout) => {
createWorkoutMutation(data);
if (data.trainingSession?.scheduledDate) {
navigate({ to: '/planning', replace: true, search: { date: data.trainingSession.scheduledDate } });
} else {
navigate({ to: '/programs/workouts', replace: true });
}
};

const handleCancel = () => {
Expand Down