@@ -11,10 +11,10 @@ import { listEpics, linkTaskToEpic, unlinkTaskFromEpic, type Epic } from '@/enti
1111
1212const STATUS_COLOR : Record < TaskStatus , string > = Object . fromEntries ( COLUMNS . map ( c => [ c . status , c . color ] ) ) as Record < TaskStatus , string > ;
1313const PRIORITY_OPTIONS : { value : TaskPriority ; label : string } [ ] = [
14- { value : 'critical' , label : 'Critical ' } ,
15- { value : 'high' , label : 'High ' } ,
16- { value : 'medium' , label : 'Medium ' } ,
17- { value : 'low' , label : 'Low ' } ,
14+ { value : 'critical' , label : 'CRITICAL ' } ,
15+ { value : 'high' , label : 'HIGH ' } ,
16+ { value : 'medium' , label : 'MEDIUM ' } ,
17+ { value : 'low' , label : 'LOW ' } ,
1818] ;
1919import { listTeam , type TeamMember } from '@/entities/project/api.ts' ;
2020
@@ -32,9 +32,10 @@ interface TaskFormProps {
3232 onSubmit : ( data : { title : string ; description : string ; status : TaskStatus ; priority : TaskPriority ; tags : string [ ] ; dueDate ?: number | null ; estimate ?: number | null ; assignee ?: string | null } ) => Promise < void > ;
3333 onCancel : ( ) => void ;
3434 submitLabel ?: string ;
35+ extraMain ?: React . ReactNode ;
3536}
3637
37- export function TaskForm ( { task, defaults, onSubmit, onCancel, submitLabel = 'Save' } : TaskFormProps ) {
38+ export function TaskForm ( { task, defaults, onSubmit, onCancel, submitLabel = 'Save' , extraMain } : TaskFormProps ) {
3839 const { projectId } = useParams < { projectId : string } > ( ) ;
3940 const [ title , setTitle ] = useState ( '' ) ;
4041 const [ description , setDescription ] = useState ( '' ) ;
@@ -46,8 +47,8 @@ export function TaskForm({ task, defaults, onSubmit, onCancel, submitLabel = 'Sa
4647 const [ assignee , setAssignee ] = useState < string > ( '' ) ;
4748 const [ team , setTeam ] = useState < TeamMember [ ] > ( [ ] ) ;
4849 const [ epics , setEpics ] = useState < Epic [ ] > ( [ ] ) ;
49- const [ selectedEpicIds , setSelectedEpicIds ] = useState < string [ ] > ( [ ] ) ;
50- const [ initialEpicIds , setInitialEpicIds ] = useState < string [ ] > ( [ ] ) ;
50+ const [ selectedEpicId , setSelectedEpicId ] = useState < string > ( '' ) ;
51+ const [ initialEpicId , setInitialEpicId ] = useState < string > ( '' ) ;
5152 const [ saving , setSaving ] = useState ( false ) ;
5253 const [ titleError , setTitleError ] = useState ( false ) ;
5354
@@ -79,9 +80,9 @@ export function TaskForm({ task, defaults, onSubmit, onCancel, submitLabel = 'Sa
7980 useEffect ( ( ) => {
8081 if ( ! projectId || ! task ) return ;
8182 listTaskRelations ( projectId , task . id ) . then ( rels => {
82- const epicIds = rels . filter ( r => r . kind === 'belongs_to' ) . map ( r => r . toId ) ;
83- setSelectedEpicIds ( epicIds ) ;
84- setInitialEpicIds ( epicIds ) ;
83+ const epicId = rels . find ( r => r . kind === 'belongs_to' ) ?. toId ?? '' ;
84+ setSelectedEpicId ( epicId ) ;
85+ setInitialEpicId ( epicId ) ;
8586 } ) . catch ( ( ) => { } ) ;
8687 } , [ projectId , task ] ) ;
8788
@@ -103,16 +104,12 @@ export function TaskForm({ task, defaults, onSubmit, onCancel, submitLabel = 'Sa
103104 assignee : assignee || null ,
104105 } ) ;
105106
106- // Sync epic links after save
107- if ( projectId ) {
107+ // Sync epic link after save
108+ if ( projectId && selectedEpicId !== initialEpicId ) {
108109 const taskId = task ?. id ?? ( result as any ) ?. id ;
109110 if ( taskId ) {
110- const toLink = selectedEpicIds . filter ( id => ! initialEpicIds . includes ( id ) ) ;
111- const toUnlink = initialEpicIds . filter ( id => ! selectedEpicIds . includes ( id ) ) ;
112- await Promise . all ( [
113- ...toLink . map ( epicId => linkTaskToEpic ( projectId , epicId , taskId ) ) ,
114- ...toUnlink . map ( epicId => unlinkTaskFromEpic ( projectId , epicId , taskId ) ) ,
115- ] ) ;
111+ if ( initialEpicId ) await unlinkTaskFromEpic ( projectId , initialEpicId , taskId ) ;
112+ if ( selectedEpicId ) await linkTaskToEpic ( projectId , selectedEpicId , taskId ) ;
116113 }
117114 }
118115 } finally {
@@ -124,6 +121,7 @@ export function TaskForm({ task, defaults, onSubmit, onCancel, submitLabel = 'Sa
124121 < Box component = "form" id = "task-form" onSubmit = { e => { e . preventDefault ( ) ; handleSubmit ( ) ; } } sx = { { display : 'flex' , flexDirection : 'column' , gap : 3 } } >
125122 < DetailLayout
126123 main = {
124+ < >
127125 < Section title = "Details" >
128126 < Box sx = { { display : 'flex' , flexDirection : 'column' , gap : 2 } } >
129127 < AppTextField
@@ -142,6 +140,8 @@ export function TaskForm({ task, defaults, onSubmit, onCancel, submitLabel = 'Sa
142140 </ Box >
143141 </ Box >
144142 </ Section >
143+ { extraMain }
144+ </ >
145145 }
146146 sidebar = {
147147 < Section title = "Properties" >
@@ -202,17 +202,19 @@ export function TaskForm({ task, defaults, onSubmit, onCancel, submitLabel = 'Sa
202202 </ Box >
203203 { epics . length > 0 && (
204204 < Box >
205- < FieldLabel > Epics </ FieldLabel >
205+ < FieldLabel > Epic </ FieldLabel >
206206 < Select
207- fullWidth multiple value = { selectedEpicIds }
208- onChange = { e => setSelectedEpicIds ( e . target . value as string [ ] ) }
207+ fullWidth value = { selectedEpicId }
208+ onChange = { e => setSelectedEpicId ( e . target . value as string ) }
209209 displayEmpty
210- renderValue = { selected => {
211- if ( ( selected as string [ ] ) . length === 0 ) return 'No epic' ;
212- return ( selected as string [ ] ) . map ( id => epics . find ( e => e . id === id ) ?. title ?? id ) . join ( ', ' ) ;
210+ renderValue = { v => {
211+ if ( ! v ) return 'No epic' ;
212+ const ep = epics . find ( e => e . id === v ) ;
213+ return ep ?. title ?? v ;
213214 } }
214215 >
215- { epics . filter ( e => e . status === 'open' || e . status === 'in_progress' || selectedEpicIds . includes ( e . id ) ) . map ( e => (
216+ < MenuItem value = "" > No epic</ MenuItem >
217+ { epics . filter ( e => e . status === 'open' || e . status === 'in_progress' || e . id === selectedEpicId ) . map ( e => (
216218 < MenuItem key = { e . id } value = { e . id } >
217219 < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 1 } } >
218220 < FlagIcon sx = { { fontSize : 14 , color : e . status === 'open' ? '#1976d2' : e . status === 'in_progress' ? '#f57c00' : '#388e3c' } } />
0 commit comments