diff --git a/apps/web/package.json b/apps/web/package.json
index 87d95a8..a8377ba 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -16,8 +16,8 @@
"@dnd-kit/utilities": "^3.2.2",
"@dropit/contract": "workspace:*",
"@dropit/i18n": "workspace:*",
- "@dropit/schemas": "workspace:*",
"@dropit/permissions": "workspace:*",
+ "@dropit/schemas": "workspace:*",
"@fullcalendar/core": "^6.1.17",
"@fullcalendar/daygrid": "^6.1.17",
"@fullcalendar/interaction": "^6.1.17",
@@ -52,6 +52,7 @@
"react": "19.0.0",
"react-dom": "19.0.0",
"react-hook-form": "^7.54.2",
+ "recharts": "^3.3.0",
"tailwind-merge": "^2.5.5",
"tailwindcss": "^3.0.0",
"tailwindcss-animate": "^1.0.7",
diff --git a/apps/web/src/assets/images/hero-pages/1993010.svg b/apps/web/src/assets/images/hero-pages/1993010.svg
new file mode 100644
index 0000000..c5ec022
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/1993010.svg
@@ -0,0 +1,75 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/1993011.svg b/apps/web/src/assets/images/hero-pages/1993011.svg
new file mode 100644
index 0000000..3120caf
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/1993011.svg
@@ -0,0 +1,60 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/1993012.svg b/apps/web/src/assets/images/hero-pages/1993012.svg
new file mode 100644
index 0000000..fdb76e6
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/1993012.svg
@@ -0,0 +1,63 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/1993013.svg b/apps/web/src/assets/images/hero-pages/1993013.svg
new file mode 100644
index 0000000..53f25e7
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/1993013.svg
@@ -0,0 +1,71 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/1993014.svg b/apps/web/src/assets/images/hero-pages/1993014.svg
new file mode 100644
index 0000000..44531bc
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/1993014.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/1993015.svg b/apps/web/src/assets/images/hero-pages/1993015.svg
new file mode 100644
index 0000000..dc81ac9
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/1993015.svg
@@ -0,0 +1,48 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/199306.svg b/apps/web/src/assets/images/hero-pages/199306.svg
new file mode 100644
index 0000000..f0103bd
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/199306.svg
@@ -0,0 +1,77 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/199307.svg b/apps/web/src/assets/images/hero-pages/199307.svg
new file mode 100644
index 0000000..0a4f510
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/199307.svg
@@ -0,0 +1,88 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/199308.svg b/apps/web/src/assets/images/hero-pages/199308.svg
new file mode 100644
index 0000000..14449dd
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/199308.svg
@@ -0,0 +1,103 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/199309.svg b/apps/web/src/assets/images/hero-pages/199309.svg
new file mode 100644
index 0000000..bb8e654
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/199309.svg
@@ -0,0 +1,81 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/19930a.svg b/apps/web/src/assets/images/hero-pages/19930a.svg
new file mode 100644
index 0000000..7c481e6
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/19930a.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/19930b.svg b/apps/web/src/assets/images/hero-pages/19930b.svg
new file mode 100644
index 0000000..3154b84
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/19930b.svg
@@ -0,0 +1,46 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/19930c.svg b/apps/web/src/assets/images/hero-pages/19930c.svg
new file mode 100644
index 0000000..7e0a86b
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/19930c.svg
@@ -0,0 +1,56 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/19930e.svg b/apps/web/src/assets/images/hero-pages/19930e.svg
new file mode 100644
index 0000000..636ab7e
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/19930e.svg
@@ -0,0 +1,51 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/login.svg b/apps/web/src/assets/images/hero-pages/login.svg
new file mode 100644
index 0000000..b6c6477
--- /dev/null
+++ b/apps/web/src/assets/images/hero-pages/login.svg
@@ -0,0 +1,147 @@
+
+
\ No newline at end of file
diff --git a/apps/web/src/assets/images/hero-pages/logo-dropit.png b/apps/web/src/assets/images/hero-pages/logo-dropit.png
new file mode 100644
index 0000000..5e7369e
Binary files /dev/null and b/apps/web/src/assets/images/hero-pages/logo-dropit.png differ
diff --git a/apps/web/src/features/athletes/athlete-detail.tsx b/apps/web/src/features/athletes/athlete-detail.tsx
index 6ece2ae..fbe46cd 100644
--- a/apps/web/src/features/athletes/athlete-detail.tsx
+++ b/apps/web/src/features/athletes/athlete-detail.tsx
@@ -1,8 +1,3 @@
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from '@/shared/components/ui/avatar';
import { Button } from '@/shared/components/ui/button';
import { Card, CardContent } from '@/shared/components/ui/card';
import {
@@ -39,7 +34,7 @@ import {
} from '@radix-ui/react-select';
import { format as formatDate } from 'date-fns';
import { fr } from 'date-fns/locale';
-import { Dumbbell, Pencil, Plus } from 'lucide-react';
+import { Dumbbell, Pencil } from 'lucide-react';
import { Form, useForm } from 'react-hook-form';
type AthleteDetailProps = {
@@ -61,11 +56,6 @@ type AthleteDetailProps = {
onCreateCompetitorStatus: (data: CreateCompetitorStatus) => void;
};
-// Helper function to generate initials from name
-function getInitials(firstName: string, lastName: string): string {
- return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
-}
-
export function AthleteDetail({
athlete,
personalRecords,
@@ -84,31 +74,9 @@ export function AthleteDetail({
return (
- {/* Profile Header with Avatar */}
-
-
-
-
- {getInitials(athlete.firstName, athlete.lastName)}
-
-
-
-
-
- {athlete.firstName} {athlete.lastName}
-
- {athlete.country && (
-
{athlete.country}
- )}
-
-
-
{/* Personal Information */}
-
+
{t('athletes:details.personal_info')}
@@ -148,7 +116,7 @@ export function AthleteDetail({
{/* Stats & Club */}
{/* Stats */}
-
+
{t('athletes:details.statistics')}
@@ -172,7 +140,7 @@ export function AthleteDetail({
{/* Competitor Status */}
-
+
@@ -182,7 +150,6 @@ export function AthleteDetail({
{athlete.competitorStatus && (
)}
-
@@ -434,7 +398,7 @@ export function AthleteDetail({
>
{t('athletes:details.cancel')}
-
+
{t('athletes:details.create_new_status')}
@@ -470,7 +434,7 @@ export function AthleteDetail({
{/* Personal Records Section */}
-
+
@@ -494,7 +458,7 @@ export function AthleteDetail({
{athlete.personalRecords ? (
<>
-
+
@@ -505,7 +469,7 @@ export function AthleteDetail({
-
+
@@ -516,7 +480,7 @@ export function AthleteDetail({
-
+
@@ -544,7 +508,7 @@ export function AthleteDetail({
{personalRecords ? (
personalRecords.map((record) => (
-
+
{record.exerciseName ||
@@ -578,7 +542,7 @@ export function AthleteDetail({
{/* History */}
-
+
{t('athletes:details.training_history')}
diff --git a/apps/web/src/features/athletes/columns.tsx b/apps/web/src/features/athletes/columns.tsx
index 716527d..c62592f 100644
--- a/apps/web/src/features/athletes/columns.tsx
+++ b/apps/web/src/features/athletes/columns.tsx
@@ -3,16 +3,19 @@ import {
AvatarFallback,
AvatarImage,
} from '@/shared/components/ui/avatar';
+import { Badge } from '@/shared/components/ui/badge';
import { Checkbox } from '@/shared/components/ui/checkbox';
import { useTranslation } from '@dropit/i18n';
import { AthleteDetailsDto } from '@dropit/schemas';
import { ColumnDef } from '@tanstack/react-table';
+import { Mars, Venus } from 'lucide-react';
+import { getLevelBadgeVariant } from '@/shared/utils';
export const columns: ColumnDef[] = [
{
id: 'select',
header: ({ table }) => (
-
+
[] = [
),
cell: ({ row }) => (
-
+
row.toggleSelected(!!value)}
@@ -65,10 +68,26 @@ export const columns: ColumnDef[] = [
id: 'sexCategory',
header: () => {
const { t } = useTranslation(['athletes']);
- return t('columns.sex_category');
+ return {t('columns.sex_category')}
;
},
cell: ({ row }) => {
- return row.original.competitorStatus?.sexCategory || '-';
+ const sexCategory = row.original.competitorStatus?.sexCategory;
+ if (!sexCategory) return -
;
+
+ const isMale = sexCategory.toLowerCase() === 'men' || sexCategory.toLowerCase() === 'male' || sexCategory.toLowerCase() === 'm';
+ const isFemale = sexCategory.toLowerCase() === 'women' || sexCategory.toLowerCase() === 'female' || sexCategory.toLowerCase() === 'f';
+
+ return (
+
+ {isMale ? (
+
+ ) : isFemale ? (
+
+ ) : (
+ sexCategory
+ )}
+
+ );
},
},
{
@@ -125,7 +144,16 @@ export const columns: ColumnDef[] = [
return t('columns.level');
},
cell: ({ row }) => {
- return row.original.competitorStatus?.level || '-';
+ const level = row.original.competitorStatus?.level;
+ if (!level) return '-';
+
+ const badgeVariant = getLevelBadgeVariant(level);
+
+ return (
+
+ {level}
+
+ );
},
},
{
diff --git a/apps/web/src/features/athletes/data-table.tsx b/apps/web/src/features/athletes/data-table.tsx
index 512a66b..9e8387f 100644
--- a/apps/web/src/features/athletes/data-table.tsx
+++ b/apps/web/src/features/athletes/data-table.tsx
@@ -6,6 +6,7 @@ import {
DropdownMenuTrigger,
} from '@/shared/components/ui/dropdown-menu';
import { Input } from '@/shared/components/ui/input';
+import { ScrollArea } from '@/shared/components/ui/scroll-area';
import {
Select,
SelectContent,
@@ -36,7 +37,7 @@ import {
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
-import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
+import { ChevronDown, ChevronLeft, ChevronRight, Search } from 'lucide-react';
import { useState } from 'react';
interface DataTableProps {
@@ -103,153 +104,163 @@ export function DataTable({
});
return (
-
- {/* Filters */}
-
-
-
-
- setGlobalFilter(event.target.value)}
- className="pl-8 bg-sidebar max-w-lg"
- />
-
-
-
-
-
- {t('common:table.columns')}
-
-
-
- {table
- .getAllColumns()
- .filter((column) => column.getCanHide())
- .map((column) => {
- return (
-
- column.toggleVisibility(!!value)
- }
- >
- {column.id}
-
- );
- })}
-
-
-
-
onDialogCreation(true)}>
- {t('athletes:filters.create_athlete')}
-
+
+ {/* Fixed Filters Section */}
+
+
+
+
+
+ setGlobalFilter(event.target.value)}
+ className="pl-8 bg-background max-w-lg"
+ />
+
+
+
+
+
+ {t('common:table.columns')}
+
+
+
+
+ {table
+ .getAllColumns()
+ .filter((column) => column.getCanHide())
+ .map((column) => {
+ return (
+
+ column.toggleVisibility(!!value)
+ }
+ >
+ {column.id}
+
+ );
+ })}
+
+
+
+ onDialogCreation(true)}>
+ {t('athletes:filters.create_athlete')}
+
+
- {/* Table */}
-
-
-
- {table.getHeaderGroups().map((headerGroup) => (
-
- {headerGroup.headers.map((header) => {
- return (
-
- {header.isPlaceholder
- ? null
- : flexRender(
- header.column.columnDef.header,
- header.getContext()
+
+ {/* Scrollable Table and Pagination Section */}
+
+
+ {/* Table */}
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+ onRowClick?.(row.original.id)}
+ >
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
)}
-
- );
- })}
-
- ))}
-
-
- {table.getRowModel().rows?.length ? (
- table.getRowModel().rows.map((row) => (
- onRowClick?.(row.original.id)}
- >
- {row.getVisibleCells().map((cell) => (
-
- {flexRender(
- cell.column.columnDef.cell,
- cell.getContext()
- )}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ {t('athletes:filters.no_results')}
- ))}
-
- ))
- ) : (
-
-
- {t('athletes:filters.no_results')}
-
-
- )}
-
-
-
- {/* Pagination */}
-
-
-
- {t('common:table.selected_rows', {
- count: table.getFilteredSelectedRowModel().rows.length,
- total: table.getFilteredRowModel().rows.length,
- })}
+
+ )}
+
+
-
-
{t('common:table.rows_per_page')}
-
+
+ {/* Pagination */}
+
+
+
+ {t('common:table.selected_rows', {
+ count: table.getFilteredSelectedRowModel().rows.length,
+ total: table.getFilteredRowModel().rows.length,
+ })}
+
+
+ {t('common:table.rows_per_page')}
+
+
+
+
+ table.previousPage()}
+ disabled={!table.getCanPreviousPage()}
+ >
+
+
+ table.nextPage()}
+ disabled={!table.getCanNextPage()}
+ >
+
+
+
-
- table.previousPage()}
- disabled={!table.getCanPreviousPage()}
- >
-
-
- table.nextPage()}
- disabled={!table.getCanNextPage()}
- >
-
-
-
-
+
);
}
diff --git a/apps/web/src/features/complex/complex-card.tsx b/apps/web/src/features/complex/complex-card.tsx
index 3183450..8762641 100644
--- a/apps/web/src/features/complex/complex-card.tsx
+++ b/apps/web/src/features/complex/complex-card.tsx
@@ -26,7 +26,7 @@ interface ComplexCardProps {
export function ComplexCard({ complex, onClick }: ComplexCardProps) {
return (
diff --git a/apps/web/src/features/complex/complex-detail.tsx b/apps/web/src/features/complex/complex-detail.tsx
index 02f0bc8..570f04f 100644
--- a/apps/web/src/features/complex/complex-detail.tsx
+++ b/apps/web/src/features/complex/complex-detail.tsx
@@ -1,7 +1,7 @@
import { api } from '@/lib/api';
import { Badge } from '@/shared/components/ui/badge';
import { Button } from '@/shared/components/ui/button';
-import { Card, CardContent, CardHeader } from '@/shared/components/ui/card';
+import { CardContent, CardHeader } from '@/shared/components/ui/card';
import {
Form,
FormControl,
@@ -10,7 +10,6 @@ import {
FormLabel,
FormMessage,
} from '@/shared/components/ui/form';
-import { Input } from '@/shared/components/ui/input';
import { Label } from '@/shared/components/ui/label';
import {
Select,
@@ -19,7 +18,6 @@ import {
SelectTrigger,
SelectValue,
} from '@/shared/components/ui/select';
-import { Separator } from '@/shared/components/ui/separator';
import { Textarea } from '@/shared/components/ui/textarea';
import { useToast } from '@/shared/hooks/use-toast';
import {
@@ -45,7 +43,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { format } from 'date-fns';
import { fr } from 'date-fns/locale';
-import { GripVertical, PlusCircle, Trash2 } from 'lucide-react';
+import { GripVertical, Trash2 } from 'lucide-react';
import { useState } from 'react';
import { UseFormReturn, useFieldArray, useForm } from 'react-hook-form';
import { z } from 'zod';
@@ -62,7 +60,6 @@ function SortableExerciseItem({
id,
index,
children,
- form,
remove,
}: {
id: string;
@@ -100,24 +97,6 @@ function SortableExerciseItem({
-
(
-
-
- field.onChange(parseInt(e.target.value))}
- />
-
-
- )}
- />
-
{children}
{index >= 2 && (
@@ -301,7 +280,6 @@ export function ComplexDetail({ complex }: ComplexDetailProps) {
className="space-y-6"
>
{/* Informations principales */}
-
-
{/* Catégorie dans une Card séparée */}
-
-
{/* Liste des exercices avec drag & drop */}
-
append({
exerciseId: '',
@@ -376,8 +349,8 @@ export function ComplexDetail({ complex }: ComplexDetailProps) {
})
}
disabled={!exercises?.length}
+ className="bg-secondary-foreground text-secondary hover:bg-secondary-foreground/80"
>
-
Ajouter un exercice
@@ -455,9 +428,6 @@ export function ComplexDetail({ complex }: ComplexDetailProps) {
-
-
-
+
{/* Informations principales */}
-
-
-
- {complex.description || 'Pas de description'}
-
+
+
{complex.description || '-'}
-
{/* Catégorie dans une Card séparée */}
-
-
-
-
+
+
{complex.complexCategory.name}
-
{/* Liste des exercices */}
-
-
+
{complex.exercises?.map((exercise) => (
@@ -546,17 +508,13 @@ export function ComplexDetail({ complex }: ComplexDetailProps) {
key={exercise.id}
className="flex items-center gap-4 p-2 rounded-md bg-muted"
>
-
- {exercise.reps}
-
{exercise.name}
))}
-
+
{/* Métadonnées */}
-
@@ -575,9 +533,6 @@ export function ComplexDetail({ complex }: ComplexDetailProps) {
-
-
-
setIsEditing(true)}>Modifier
diff --git a/apps/web/src/features/complex/complex-filters.tsx b/apps/web/src/features/complex/complex-filters.tsx
index f912589..1953564 100644
--- a/apps/web/src/features/complex/complex-filters.tsx
+++ b/apps/web/src/features/complex/complex-filters.tsx
@@ -39,7 +39,7 @@ export function ComplexFilters({
placeholder={t('complex.filters.search_placeholder')}
onChange={(e) => onFilterChange(e.target.value)}
disabled={disabled}
- className="pl-8 bg-sidebar max-w-lg"
+ className="pl-8 bg-background max-w-lg"
/>
{/* Filters */}
diff --git a/apps/web/src/features/exercises/columns.tsx b/apps/web/src/features/exercises/columns.tsx
index 3d760e0..01fc337 100644
--- a/apps/web/src/features/exercises/columns.tsx
+++ b/apps/web/src/features/exercises/columns.tsx
@@ -20,7 +20,7 @@ export const columns: ColumnDef[] = [
{
id: 'select',
header: ({ table }) => (
-
+
[] = [
),
cell: ({ row }) => (
-
+
row.toggleSelected(!!value)}
diff --git a/apps/web/src/features/exercises/data-table.tsx b/apps/web/src/features/exercises/data-table.tsx
index 304a119..fa19df2 100644
--- a/apps/web/src/features/exercises/data-table.tsx
+++ b/apps/web/src/features/exercises/data-table.tsx
@@ -6,6 +6,7 @@ import {
DropdownMenuTrigger,
} from '@/shared/components/ui/dropdown-menu';
import { Input } from '@/shared/components/ui/input';
+import { ScrollArea } from '@/shared/components/ui/scroll-area';
import { Separator } from '@/shared/components/ui/separator';
import {
Table,
@@ -28,7 +29,7 @@ import {
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
-import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
+import { ChevronDown, ChevronLeft, ChevronRight, Search } from 'lucide-react';
import { useState } from 'react';
interface DataTableProps {
@@ -69,135 +70,145 @@ export function DataTable({
});
return (
-
- {/* Filters */}
-
-
-
-
-
- table.getColumn('name')?.setFilterValue(event.target.value)
- }
- className="pl-8 bg-sidebar max-w-lg"
- />
-
-
-
-
-
- {t('exercise.table.columns')}
-
-
-
- {table
- .getAllColumns()
- .filter((column) => column.getCanHide())
- .map((column) => {
- return (
-
- column.toggleVisibility(!!value)
- }
- >
- {column.id}
-
- );
- })}
-
-
-
-
onDialogCreation(true)}>
- {t('exercise.filters.create_exercise')}
-
+
+ {/* Fixed Filters Section */}
+
+
+
+
+
+
+ table.getColumn('name')?.setFilterValue(event.target.value)
+ }
+ className="pl-8 bg-background max-w-lg"
+ />
+
+
+
+
+
+ {t('common:table.columns')}
+
+
+
+
+ {table
+ .getAllColumns()
+ .filter((column) => column.getCanHide())
+ .map((column) => {
+ return (
+
+ column.toggleVisibility(!!value)
+ }
+ >
+ {column.id}
+
+ );
+ })}
+
+
+
+ onDialogCreation(true)}>
+ {t('exercise.filters.create_exercise')}
+
+
- {/* Table */}
-
-
-
- {table.getHeaderGroups().map((headerGroup) => (
-
- {headerGroup.headers.map((header) => {
- return (
-
- {header.isPlaceholder
- ? null
- : flexRender(
- header.column.columnDef.header,
- header.getContext()
+
+ {/* Scrollable Table and Pagination Section */}
+
+
+ {/* Table */}
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+ onRowClick?.(row.original.id)}
+ >
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
)}
-
- );
- })}
-
- ))}
-
-
- {table.getRowModel().rows?.length ? (
- table.getRowModel().rows.map((row) => (
- onRowClick?.(row.original.id)}
- >
- {row.getVisibleCells().map((cell) => (
-
- {flexRender(
- cell.column.columnDef.cell,
- cell.getContext()
- )}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ {t('exercise.table.no_results')}
- ))}
-
- ))
- ) : (
-
-
- {t('exercise.table.no_results')}
-
-
- )}
-
-
-
- {/* Pagination */}
-
-
- {t('exercise.table.selected_rows', {
- count: table.getFilteredSelectedRowModel().rows.length,
- total: table.getFilteredRowModel().rows.length
- })}
-
-
- table.previousPage()}
- disabled={!table.getCanPreviousPage()}
- >
-
-
- table.nextPage()}
- disabled={!table.getCanNextPage()}
- >
-
-
+
+ )}
+
+
+
+
+ {/* Pagination */}
+
+
+ {t('exercise.table.selected_rows', {
+ count: table.getFilteredSelectedRowModel().rows.length,
+ total: table.getFilteredRowModel().rows.length
+ })}
+
+
+ table.previousPage()}
+ disabled={!table.getCanPreviousPage()}
+ >
+
+
+ table.nextPage()}
+ disabled={!table.getCanNextPage()}
+ >
+
+
+
+
-
+
);
}
diff --git a/apps/web/src/features/exercises/exercise-detail.tsx b/apps/web/src/features/exercises/exercise-detail.tsx
index a20d766..6b0c89c 100644
--- a/apps/web/src/features/exercises/exercise-detail.tsx
+++ b/apps/web/src/features/exercises/exercise-detail.tsx
@@ -18,7 +18,6 @@ import {
SelectTrigger,
SelectValue,
} from '@/shared/components/ui/select';
-import { Separator } from '@/shared/components/ui/separator';
import { Textarea } from '@/shared/components/ui/textarea';
import { toast } from '@/shared/hooks/use-toast';
import { UpdateExercise, updateExerciseSchema } from '@dropit/schemas';
@@ -260,8 +259,6 @@ export function ExerciseDetail({ exercise }: ExerciseDetailProps) {
-
-
-
-
setIsEditing(true)}>
Modifier
diff --git a/apps/web/src/features/planning/create-workout-modal.tsx b/apps/web/src/features/planning/create-workout-modal.tsx
index feb5f78..f3b22f0 100644
--- a/apps/web/src/features/planning/create-workout-modal.tsx
+++ b/apps/web/src/features/planning/create-workout-modal.tsx
@@ -4,11 +4,13 @@ import {
DialogHeader,
DialogTitle,
} from '@/shared/components/ui/dialog';
+import { Steps } from '@/shared/components/ui/steps';
import { useTranslation } from '@dropit/i18n';
import { CreateWorkout } from '@dropit/schemas';
import { format } from 'date-fns';
import { enGB, fr } from 'date-fns/locale';
-import { WorkoutCreationStepper } from '../workout/workout-creation-stepper';
+import { useState } from 'react';
+import { WorkoutCreationStepper, workoutCreationSteps } from '../workout/workout-creation-stepper';
interface CreateWorkoutModalProps {
isOpen: boolean;
@@ -23,6 +25,7 @@ export function CreateWorkoutModal({
}: CreateWorkoutModalProps) {
const { t, i18n } = useTranslation('planning');
const locale = i18n.language === 'fr' ? fr : enGB;
+ const [currentStep, setCurrentStep] = useState(0);
const handleSubmitSuccess = (data: CreateWorkout) => {
// Ici vous pourriez traiter les données du workout si nécessaire
@@ -39,8 +42,17 @@ export function CreateWorkoutModal({
{selectedDate && `- ${format(selectedDate, 'PPP', { locale })}`}
+
+
+
diff --git a/apps/web/src/features/planning/planning-calendar.tsx b/apps/web/src/features/planning/planning-calendar.tsx
index 08fb792..9542184 100644
--- a/apps/web/src/features/planning/planning-calendar.tsx
+++ b/apps/web/src/features/planning/planning-calendar.tsx
@@ -4,7 +4,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { useToast } from '@/shared/hooks/use-toast';
import { useTranslation } from '@dropit/i18n';
import { TrainingSessionDto } from '@dropit/schemas';
-import { Duration, EventApi, EventClickArg } from '@fullcalendar/core';
+import { Duration, EventApi, EventClickArg, EventContentArg } from '@fullcalendar/core';
import enLocale from '@fullcalendar/core/locales/en-gb';
import frLocale from '@fullcalendar/core/locales/fr';
import dayGridPlugin from '@fullcalendar/daygrid';
@@ -13,6 +13,7 @@ import multiMonthPlugin from '@fullcalendar/multimonth';
import FullCalendar from '@fullcalendar/react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { useRef, useState } from 'react';
+import { TrainingSessionWeekView } from './training-session-week-view';
interface EventDropInfo {
event: EventApi;
@@ -24,7 +25,7 @@ interface EventDropInfo {
interface PlanningCalendarProps {
className?: string;
initialEvents?: TrainingSessionDto[];
- onEventClick?: (eventInfo: EventClickArg) => void;
+ onEventClick?: (eventInfo: EventClickArg, currentView: string) => void;
onDateClick?: (dateInfo: DateClickArg) => void;
onEventDrop?: (eventDropInfo: EventDropInfo) => void;
}
@@ -45,7 +46,7 @@ export function PlanningCalendar({
const handleEventClick = (info: EventClickArg) => {
if (onEventClick) {
- onEventClick(info);
+ onEventClick(info, currentView);
}
};
@@ -122,6 +123,23 @@ export function PlanningCalendar({
}
};
+ const renderEventContent = (eventInfo: EventContentArg) => {
+ // Rendu personnalisé pour la vue semaine
+ if (eventInfo.view.type === 'dayGridWeek') {
+ const trainingSession = eventInfo.event.extendedProps.trainingSession as TrainingSessionDto;
+ if (trainingSession) {
+ return
;
+ }
+ }
+
+ // Rendu par défaut pour les autres vues
+ return (
+
+
{eventInfo.event.title}
+
+ );
+ };
+
return (
{/* Custom Toolbar */}
@@ -160,7 +178,7 @@ export function PlanningCalendar({
{/* View Selector */}
;
if (!data) return
No data
;
- // Calculer la durée totale estimée (si disponible)
- const totalDuration = data.workout.elements.reduce((total, element) => {
- if (element.duration) {
- return total + element.duration;
- }
- return total;
- }, 0);
-
return (
- {/* Header avec titre et actions */}
+ {/* Header avec date de la session et actions */}
-
- {data.workout.title}
-
{format(new Date(data.scheduledDate), 'PPP', { locale })}
- {totalDuration > 0 && ` · ${totalDuration} min`}
@@ -154,7 +142,7 @@ export function TrainingSessionDetail({
{/* Athlètes */}
{data.athletes && data.athletes.length > 0 && (
-
{t('athletes')}
+
{t('athletes')}
{data.athletes.slice(0, 3).map((athlete) => (
- {t('workoutElements')}
{data.workout.elements.map((element) => (
@@ -238,8 +220,8 @@ export function TrainingSessionDetail({
className={cn(
'text-xs',
element.type === WORKOUT_ELEMENT_TYPES.EXERCISE
- ? 'bg-blue-100'
- : 'bg-purple-100'
+ ? 'bg-tertiary text-tertiary-foreground hover:bg-tertiary'
+ : 'bg-secondary text-secondary-foreground hover:bg-secondary'
)}
>
{element.type === WORKOUT_ELEMENT_TYPES.EXERCISE
@@ -251,7 +233,7 @@ export function TrainingSessionDetail({
{/* Détails additionnels pour les complexes */}
{element.type === WORKOUT_ELEMENT_TYPES.COMPLEX &&
element.complex.exercises && (
-
+
{element.complex.exercises.map((ex, index) => (
+ {/* Titre */}
+
+ {trainingSession.workout.title}
+
+
+ {/* Athlètes */}
+ {trainingSession.athletes && trainingSession.athletes.length > 0 && (
+
+
+
+ {trainingSession.athletes.length} {t('athletes').toLowerCase()}
+
+
+ )}
+
+ {/* Éléments de l'entraînement - version compacte */}
+
+ {trainingSession.workout.elements.slice(0, 3).map((element) => (
+
+
+ {element.type === WORKOUT_ELEMENT_TYPES.EXERCISE
+ ? element.exercise.name
+ : element.complex.complexCategory?.name || 'Complex'}
+
+
+
+ {element.sets}x{element.reps}
+
+
+
+ ))}
+ {trainingSession.workout.elements.length > 3 && (
+
+ +{trainingSession.workout.elements.length - 3} {t('others')}
+
+ )}
+
+
+ );
+}
diff --git a/apps/web/src/features/workout/steps/workout-elements-step.tsx b/apps/web/src/features/workout/steps/workout-elements-step.tsx
index f81e7f4..def60ff 100644
--- a/apps/web/src/features/workout/steps/workout-elements-step.tsx
+++ b/apps/web/src/features/workout/steps/workout-elements-step.tsx
@@ -149,7 +149,7 @@ export function WorkoutElementsStep({
{/* Layout principal : 2 colonnes */}
-
+
{/* Colonne gauche : Liste des éléments disponibles */}
@@ -199,7 +199,7 @@ export function WorkoutElementsStep({
placeholder="Rechercher un exercice..."
value={exerciseSearch}
onChange={(e) => setExerciseSearch(e.target.value)}
- className="pl-10"
+ className="pl-10 bg-background"
/>
@@ -263,7 +263,7 @@ export function WorkoutElementsStep({
placeholder="Rechercher un complexe..."
value={complexSearch}
onChange={(e) => setComplexSearch(e.target.value)}
- className="pl-10"
+ className="pl-10 bg-background"
/>
diff --git a/apps/web/src/features/workout/steps/workout-info-step.tsx b/apps/web/src/features/workout/steps/workout-info-step.tsx
index 083038b..fcd59f9 100644
--- a/apps/web/src/features/workout/steps/workout-info-step.tsx
+++ b/apps/web/src/features/workout/steps/workout-info-step.tsx
@@ -91,7 +91,7 @@ export function WorkoutInfoStep({
return (
{/* Layout principal : 2 colonnes */}
-
+
{/* Colonne gauche : Formulaire d'informations */}
diff --git a/apps/web/src/features/workout/steps/workout-planning-step.tsx b/apps/web/src/features/workout/steps/workout-planning-step.tsx
index 5852add..a81786b 100644
--- a/apps/web/src/features/workout/steps/workout-planning-step.tsx
+++ b/apps/web/src/features/workout/steps/workout-planning-step.tsx
@@ -136,7 +136,7 @@ export function WorkoutPlanningStep({
return (
{/* Layout principal : une seule colonne centrée */}
-
+
@@ -199,7 +199,7 @@ export function WorkoutPlanningStep({
placeholder="Rechercher un athlète..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
- className="pl-10"
+ className="pl-10 bg-background"
disabled={!isScheduled}
/>
diff --git a/apps/web/src/features/workout/workout-card.tsx b/apps/web/src/features/workout/workout-card.tsx
index f8b81de..f791b8d 100644
--- a/apps/web/src/features/workout/workout-card.tsx
+++ b/apps/web/src/features/workout/workout-card.tsx
@@ -14,22 +14,10 @@ interface WorkoutCardProps {
onWorkoutClick: (id: string) => void;
}
-// Fonction pour obtenir la couleur pastel selon la catégorie (jaune ou rouge pâle)
-const getCategoryColor = (category: string) => {
- const colors: Record
= {
- Force: 'bg-red-50 text-red-700 border-red-200',
- Technique: 'bg-yellow-50 text-yellow-700 border-yellow-200',
- Endurance: 'bg-red-50 text-red-700 border-red-200',
- Mobilité: 'bg-yellow-50 text-yellow-700 border-yellow-200',
- Conditioning: 'bg-red-50 text-red-700 border-red-200',
- };
- return colors[category] || 'bg-yellow-50 text-yellow-700 border-yellow-200';
-};
-
export function WorkoutCard({ workout, trainingSessions, onWorkoutClick }: WorkoutCardProps) {
return (
@@ -39,7 +27,7 @@ export function WorkoutCard({ workout, trainingSessions, onWorkoutClick }: Worko
{workout.workoutCategory && (
{workout.workoutCategory}
@@ -55,11 +43,7 @@ export function WorkoutCard({ workout, trainingSessions, onWorkoutClick }: Worko
return (
{/* Header avec type et sets/reps */}
@@ -67,8 +51,8 @@ export function WorkoutCard({ workout, trainingSessions, onWorkoutClick }: Worko
variant="secondary"
className={`text-[10px] font-semibold uppercase ${
isExercise
- ? 'bg-yellow-100 text-yellow-700 hover:bg-yellow-100'
- : 'bg-red-100 text-red-700 hover:bg-red-100'
+ ? 'bg-tertiary text-tertiary-foreground hover:bg-tertiary'
+ : 'bg-secondary text-secondary-foreground hover:bg-secondary'
}`}
>
{isExercise ? 'exercise' : 'complex'}
diff --git a/apps/web/src/features/workout/workout-creation-stepper.tsx b/apps/web/src/features/workout/workout-creation-stepper.tsx
index 9c9ca30..8a8d9d6 100644
--- a/apps/web/src/features/workout/workout-creation-stepper.tsx
+++ b/apps/web/src/features/workout/workout-creation-stepper.tsx
@@ -1,8 +1,6 @@
import { Form } from '@/shared/components/ui/form';
-import { Steps } from '@/shared/components/ui/steps';
import { createWorkoutSchema } from '@dropit/schemas';
import { zodResolver } from '@hookform/resolvers/zod';
-import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { WorkoutElementsStep } from './steps/workout-elements-step';
@@ -21,7 +19,7 @@ const extendedWorkoutSchema = createWorkoutSchema.extend({
type ExtendedWorkoutSchema = z.infer
;
-const steps = [
+export const workoutCreationSteps = [
{
id: 'info',
name: 'Description',
@@ -37,15 +35,18 @@ const steps = [
];
interface WorkoutCreationStepperProps {
+ currentStep: number;
+ setCurrentStep: (step: number) => void;
onSuccess: (data: z.infer) => void;
onCancel: () => void;
}
export function WorkoutCreationStepper({
+ currentStep,
+ setCurrentStep,
onSuccess,
onCancel,
}: WorkoutCreationStepperProps) {
- const [currentStep, setCurrentStep] = useState(0);
const form = useForm({
resolver: zodResolver(extendedWorkoutSchema),
defaultValues: {
@@ -76,12 +77,6 @@ export function WorkoutCreationStepper({
return (