diff --git a/apps/api/src/modules/exercise/exercise.controller.ts b/apps/api/src/modules/exercise/exercise.controller.ts index 424598e..40f6662 100644 --- a/apps/api/src/modules/exercise/exercise.controller.ts +++ b/apps/api/src/modules/exercise/exercise.controller.ts @@ -1,5 +1,4 @@ import { exerciseContract } from '@dropit/contract'; -import { UpdateExercise } from '@dropit/schemas'; import { BadRequestException, Controller, diff --git a/apps/web/package.json b/apps/web/package.json index 459a01e..f035c03 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -9,12 +9,19 @@ "preview": "vite preview" }, "dependencies": { + "@dropit/schemas": "workspace:*", + "@dropit/contract": "workspace:*", "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-select": "^2.1.4", + "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@tanstack/react-query": "^5.62.7", "@tanstack/react-router": "^1.89.0", "@tanstack/react-table": "^8.20.6", + "@ts-rest/core": "^3.51.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.469.0", diff --git a/apps/web/src/components/exercises/data-table.tsx b/apps/web/src/components/exercises/data-table.tsx index 665c4ee..680f8de 100644 --- a/apps/web/src/components/exercises/data-table.tsx +++ b/apps/web/src/components/exercises/data-table.tsx @@ -10,6 +10,7 @@ import { getSortedRowModel, useReactTable, } from '@tanstack/react-table'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; import { useState } from 'react'; import { Button } from '../ui/button'; import { @@ -19,6 +20,7 @@ import { DropdownMenuTrigger, } from '../ui/dropdown-menu'; import { Input } from '../ui/input'; +import { Separator } from '../ui/separator'; import { Table, TableBody, @@ -31,11 +33,13 @@ import { interface DataTableProps { columns: ColumnDef[]; data: TData[]; + onDialogCreation: (open: boolean) => void; } export function DataTable({ columns, data, + onDialogCreation, }: DataTableProps) { const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); @@ -73,32 +77,38 @@ export function DataTable({ className="max-w-sm" /> - - - - - - {table - .getAllColumns() - .filter((column) => column.getCanHide()) - .map((column) => { - return ( - - column.toggleVisibility(!!value) - } - > - {column.id} - - ); - })} - - +
+ + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + + + +
@@ -152,25 +162,25 @@ export function DataTable({
- {table.getFilteredSelectedRowModel().rows.length} de{' '} - {table.getFilteredRowModel().rows.length} colonne(s) sectectionné. + {table.getFilteredSelectedRowModel().rows.length} sur{' '} + {table.getFilteredRowModel().rows.length} colonne(s) sectectionnée.
diff --git a/apps/web/src/components/exercises/dialog-creation.tsx b/apps/web/src/components/exercises/dialog-creation.tsx new file mode 100644 index 0000000..8bd3c7c --- /dev/null +++ b/apps/web/src/components/exercises/dialog-creation.tsx @@ -0,0 +1,36 @@ +import { ReactNode } from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '../ui/dialog'; + +interface DialogCreationProps { + title: string; + description?: string; + children: ReactNode; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function DialogCreation({ + title, + description, + children, + open, + onOpenChange, +}: DialogCreationProps) { + return ( + + + + {title} + {description && {description}} + + {children} + + + ); +} diff --git a/apps/web/src/components/exercises/exercise-creation-form.tsx b/apps/web/src/components/exercises/exercise-creation-form.tsx new file mode 100644 index 0000000..f85fa2e --- /dev/null +++ b/apps/web/src/components/exercises/exercise-creation-form.tsx @@ -0,0 +1,139 @@ +import { api } from '@/lib/api'; +import { CreateExercise } from '@dropit/schemas'; +import { useState } from 'react'; +import { Button } from '../ui/button'; +import { FileUpload } from '../ui/file-upload'; +import { Input } from '../ui/input'; +import { Label } from '../ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '../ui/select'; +import { Textarea } from '../ui/textarea'; + +interface ExerciseCreationFormProps { + onSuccess?: () => void; + onCancel?: () => void; +} + +export function ExerciseCreationForm({ + onSuccess, + onCancel, +}: ExerciseCreationFormProps) { + const [isLoading, setIsLoading] = useState(false); + const [formData, setFormData] = useState>({}); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + + try { + const response = await api.createExercise({ + body: { + // biome-ignore lint/style/noNonNullAssertion: + name: formData.name!, + // biome-ignore lint/style/noNonNullAssertion: + exerciseCategory: formData.exerciseCategory!, + description: formData.description, + englishName: formData.englishName, + shortName: formData.shortName, + // La gestion de la vidéo nécessitera probablement une route séparée pour l'upload + }, + }); + + if (response.status === 201) { + onSuccess?.(); + } + } catch (error) { + console.error('Erreur lors de la création:', error); + } finally { + setIsLoading(false); + } + }; + + const handleChange = (field: keyof CreateExercise, value: string) => { + setFormData((prev) => ({ ...prev, [field]: value })); + }; + + return ( +
+
+ + handleChange('name', e.target.value)} + required + /> +
+
+ + +
+
+ +