11import { useState , useEffect } from 'react' ;
22import { toast } from 'sonner' ;
33import { useNavigate } from 'react-router-dom' ;
4+ import { userStorySchema } from '../schemas/userstory.schema' ;
45import {
56 DndContext ,
67 DragOverlay ,
78 PointerSensor ,
89 useSensor ,
910 useSensors ,
10- closestCorners
11+ closestCorners ,
12+ useDroppable
1113} from '@dnd-kit/core' ;
1214import {
1315 SortableContext ,
@@ -29,6 +31,8 @@ const COLUMNS = [
2931 { id : 'DONE' , label : 'Done' , color : 'text-green-400' } ,
3032] ;
3133
34+ const PRIORITY_ORDER = { High : 0 , Medium : 1 , Low : 2 } ;
35+
3236const PRIORITY_COLORS = {
3337 High : 'text-red-400 bg-red-500/10' ,
3438 Medium : 'text-yellow-400 bg-yellow-500/10' ,
@@ -71,14 +75,15 @@ function StoryCard({ story, onEdit, onDelete, isDragging }) {
7175}
7276
7377function KanbanColumn ( { column, stories, onEdit, onDelete, activeId } ) {
78+ const { setNodeRef } = useDroppable ( { id : column . id } ) ;
7479 return (
7580 < div className = "flex flex-col" >
7681 < div className = "glass-card p-3 mb-3" >
7782 < h2 className = { `text-sm font-bold ${ column . color } ` } > { column . label } </ h2 >
7883 < span className = "text-xs text-white/40" > { stories . length } </ span >
7984 </ div >
8085 < SortableContext items = { stories . map ( s => s . id ) } strategy = { verticalListSortingStrategy } >
81- < div className = "space-y-3 min-h-[60px]" >
86+ < div ref = { setNodeRef } className = "space-y-3 min-h-[60px]" >
8287 { stories . map ( story => (
8388 < StoryCard
8489 key = { story . id }
@@ -99,6 +104,7 @@ export default function Dashboard() {
99104 const [ loading , setLoading ] = useState ( true ) ;
100105 const [ showModal , setShowModal ] = useState ( false ) ;
101106 const [ formData , setFormData ] = useState ( EMPTY_FORM ) ;
107+ const [ formErrors , setFormErrors ] = useState ( { } ) ;
102108 const [ editId , setEditId ] = useState ( null ) ;
103109 const [ activeId , setActiveId ] = useState ( null ) ;
104110
@@ -133,10 +139,21 @@ export default function Dashboard() {
133139 setShowModal ( false ) ;
134140 setEditId ( null ) ;
135141 setFormData ( EMPTY_FORM ) ;
142+ setFormErrors ( { } ) ;
136143 } ;
137144
138145 const handleSubmit = async ( e ) => {
139146 e . preventDefault ( ) ;
147+ setFormErrors ( { } ) ;
148+
149+ const result = userStorySchema . safeParse ( formData ) ;
150+ if ( ! result . success ) {
151+ const errs = { } ;
152+ result . error . issues . forEach ( err => { if ( err . path [ 0 ] ) errs [ err . path [ 0 ] ] = err . message ; } ) ;
153+ setFormErrors ( errs ) ;
154+ return ;
155+ }
156+
140157 const payload = {
141158 title : `En tant que ${ formData . asA } , je veux ${ formData . iWant } ` ,
142159 description : `Afin de ${ formData . soThat } ` ,
@@ -194,19 +211,21 @@ export default function Dashboard() {
194211
195212 // Trouver la colonne cible (over peut être une story ou une colonne)
196213 const targetStory = stories . find ( s => s . id === over . id ) ;
197- const targetStatus = targetStory ? targetStory . status : over . id ;
214+ const isColumn = COLUMNS . find ( c => c . id === over . id ) ;
215+ const targetStatus = isColumn ? over . id : ( targetStory ? targetStory . status : over . id ) ;
198216
199217 if ( ! COLUMNS . find ( c => c . id === targetStatus ) ) return ;
200218 if ( draggedStory . status === targetStatus ) return ;
201219
202220 // Update optimiste
221+ const previousStories = stories ;
203222 setStories ( prev => prev . map ( s => s . id === active . id ? { ...s , status : targetStatus } : s ) ) ;
204223
205224 try {
206225 await api . updateStoryStatus ( token , active . id , targetStatus , 0 ) ;
207226 } catch ( err ) {
227+ setStories ( previousStories ) ;
208228 toast . error ( 'Erreur lors du déplacement' ) ;
209- loadStories ( ) ;
210229 }
211230 } ;
212231
@@ -244,7 +263,10 @@ export default function Dashboard() {
244263 < KanbanColumn
245264 key = { col . id }
246265 column = { col }
247- stories = { stories . filter ( s => s . status === col . id ) . sort ( ( a , b ) => a . position - b . position ) }
266+ stories = { stories . filter ( s => s . status === col . id ) . sort ( ( a , b ) => {
267+ const pd = PRIORITY_ORDER [ a . priority ] - PRIORITY_ORDER [ b . priority ] ;
268+ return pd !== 0 ? pd : b . id - a . id ;
269+ } ) }
248270 onEdit = { handleEdit }
249271 onDelete = { handleDelete }
250272 activeId = { activeId }
@@ -275,14 +297,17 @@ export default function Dashboard() {
275297 < div >
276298 < label className = "block text-sm font-medium mb-2" > En tant que</ label >
277299 < input type = "text" value = { formData . asA } onChange = { ( e ) => setFormData ( { ...formData , asA : e . target . value } ) } className = "glass-input w-full" placeholder = "utilisateur, admin..." required />
300+ { formErrors . asA && < p className = "text-red-400 text-xs mt-1" > { formErrors . asA } </ p > }
278301 </ div >
279302 < div >
280303 < label className = "block text-sm font-medium mb-2" > Je veux</ label >
281304 < textarea value = { formData . iWant } onChange = { ( e ) => setFormData ( { ...formData , iWant : e . target . value } ) } className = "glass-input w-full min-h-[80px]" rows = "3" required />
305+ { formErrors . iWant && < p className = "text-red-400 text-xs mt-1" > { formErrors . iWant } </ p > }
282306 </ div >
283307 < div >
284308 < label className = "block text-sm font-medium mb-2" > Afin de</ label >
285309 < textarea value = { formData . soThat } onChange = { ( e ) => setFormData ( { ...formData , soThat : e . target . value } ) } className = "glass-input w-full min-h-[80px]" rows = "3" required />
310+ { formErrors . soThat && < p className = "text-red-400 text-xs mt-1" > { formErrors . soThat } </ p > }
286311 </ div >
287312
288313 < div className = "grid grid-cols-2 gap-4" >
0 commit comments