1+ import { useState } from "react" ;
2+
3+ import { TimeInput } from "@/components/time_input" ;
4+ import { TopicInput } from "@/components/topic_input" ;
5+
16interface Time {
27 id : number ;
38 day : number ;
@@ -24,58 +29,200 @@ interface Item {
2429type ItemProps = {
2530 item : Item ;
2631 onToggle ?: ( id : number ) => void ;
32+ onUpdate : ( item : Item ) => void ;
33+ availableTopics : Topic [ ] ;
2734} ;
2835
29- export function TaskItem ( { item, onToggle } : ItemProps ) {
36+ type TopicSelection =
37+ | { type : "existing" ; id : number }
38+ | { type : "new" ; name : string ; color_hex : number } ;
39+
40+ export function TaskItem ( {
41+ item,
42+ onToggle,
43+ onUpdate,
44+ availableTopics,
45+ } : ItemProps ) {
46+ {
47+ /* Editing Functionality */
48+ }
49+ const [ isEditing , setIsEditing ] = useState ( false ) ;
50+ const [ draftName , setDraftName ] = useState ( item . name ) ;
51+ const [ draftDescription , setDraftDescription ] = useState ( item . description ) ;
52+ const [ draftTopics , setDraftTopics ] = useState < TopicSelection [ ] > ( [ ] ) ;
53+ const [ draftTimes , setDraftTimes ] = useState < Time [ ] > ( [ ] ) ;
54+ const [ saving , setSaving ] = useState ( false ) ;
55+
56+ function startEdit ( ) {
57+ setDraftName ( item . name ) ;
58+ setDraftDescription ( item . description ) ;
59+ setDraftTopics (
60+ item . topics . map ( ( t ) => ( {
61+ type : "existing" ,
62+ id : t . id ,
63+ } ) ) ,
64+ ) ;
65+ setDraftTimes ( item . times . map ( ( t ) => ( { ...t } ) ) ) ;
66+ setIsEditing ( true ) ;
67+ }
68+
69+ function cancelEdit ( ) {
70+ setDraftName ( item . name ) ;
71+ setDraftDescription ( item . description ) ;
72+ setDraftTopics ( item . topics . map ( ( t ) => ( { type : "existing" , id : t . id } ) ) ) ;
73+ setDraftTimes ( item . times . map ( ( t ) => ( { ...t } ) ) ) ;
74+ setIsEditing ( false ) ;
75+ }
76+
77+ async function saveEdit ( ) {
78+ setSaving ( true ) ;
79+ try {
80+ const existing_topic_ids = draftTopics
81+ . filter ( ( t ) => t . type === "existing" )
82+ . map ( ( t ) => t . id ) ;
83+
84+ const new_topics = draftTopics
85+ . filter ( ( t ) => t . type === "new" )
86+ . map ( ( t ) => ( {
87+ name : t . name ,
88+ color_hex : t . color_hex ,
89+ } ) ) ;
90+
91+ const response = await fetch (
92+ `http://localhost:8000/api/planner/tasks/${ item . id } /` ,
93+ {
94+ method : "PUT" ,
95+ headers : { "Content-Type" : "application/json" } ,
96+ body : JSON . stringify ( {
97+ name : draftName ,
98+ description : draftDescription ,
99+ completed : item . completed ,
100+ existing_topic_ids : existing_topic_ids ,
101+ new_topics : new_topics ,
102+ times : draftTimes ,
103+ } ) ,
104+ } ,
105+ ) ;
106+ if ( ! response . ok ) {
107+ throw new Error ( "Failed to update task" ) ;
108+ }
109+ const updatedItem = await response . json ( ) ;
110+ onUpdate ( updatedItem ) ;
111+ setIsEditing ( false ) ;
112+ } catch ( error ) {
113+ console . error ( "Error saving task:" , error ) ;
114+ } finally {
115+ setSaving ( false ) ;
116+ }
117+ }
118+
119+ {
120+ /* Item Display */
121+ }
30122 return (
31- < label className = "m-1 flex w-96 flex-col rounded-md border-2 border-zinc-200 bg-zinc-700 p-2" >
123+ < div className = "m-1 flex w-96 flex-col rounded-md border-2 border-zinc-200 bg-zinc-700 p-2" >
32124 < div className = "flex items-center justify-between" >
125+ { /* Task Name and Completion */ }
33126 < div className = "flex items-center space-x-2" >
34127 < input
35128 type = "checkbox"
36129 checked = { item . completed }
37130 onChange = { ( ) => onToggle ?.( item . id ) }
38131 className = "w-xl h-xl accent-zinc-600"
39132 />
40- < span
41- className = {
42- item . completed ? "text-zinc-400 line-through" : "text-zinc-300"
43- }
44- >
45- { item . name }
46- </ span >
133+ { isEditing ? (
134+ < input
135+ value = { draftName }
136+ onChange = { ( e ) => setDraftName ( e . target . value ) }
137+ className = "rounded border-2 bg-zinc-600 p-1 text-zinc-200"
138+ />
139+ ) : (
140+ < span
141+ className = {
142+ item . completed ? "text-zinc-400 line-through" : "text-zinc-300"
143+ }
144+ >
145+ { item . name }
146+ </ span >
147+ ) }
47148 </ div >
149+
150+ { /* Task Times */ }
48151 < div className = "flex flex-col items-end text-sm text-zinc-300" >
49- { item . times ?. length > 0
50- ? item . times . map ( ( t ) => (
51- < span key = { t . id } >
52- { formatDay ( t . day ) } { t . start_time . slice ( 0 , 5 ) } -{ " " }
53- { t . end_time . slice ( 0 , 5 ) }
54- </ span >
55- ) )
56- : "No time set" }
152+ { isEditing ? (
153+ < TimeInput times = { draftTimes } setTimes = { setDraftTimes } />
154+ ) : item . times ?. length > 0 ? (
155+ item . times . map ( ( t ) => (
156+ < span key = { t . id } >
157+ { formatDay ( t . day ) } { t . start_time . slice ( 0 , 5 ) } -{ " " }
158+ { t . end_time . slice ( 0 , 5 ) }
159+ </ span >
160+ ) )
161+ ) : (
162+ "No time set"
163+ ) }
57164 </ div >
58165 </ div >
59166
167+ { /* Task Topics */ }
60168 < div className = "flex flex-wrap gap-1 text-sm text-zinc-300" >
61- { item . topics ?. length > 0
62- ? item . topics . map ( ( topic ) => (
169+ { isEditing ? (
170+ < TopicInput
171+ availableTopics = { availableTopics }
172+ topics = { draftTopics }
173+ setTopics = { setDraftTopics }
174+ />
175+ ) : item . topics . length > 0 ? (
176+ item . topics . map ( ( topic ) => (
177+ < span
178+ key = { topic . id }
179+ className = "flex items-center gap-1 rounded-lg border-2 border-zinc-500 px-2 py-0.5 text-zinc-100"
180+ >
63181 < span
64- key = { topic . id }
65- className = "flex items-center gap-1 rounded-lg border-2 border-zinc-500 px-2 py-0.5 text-zinc-100"
66- >
67- < span
68- className = "h-2 w-2 rounded-full"
69- style = { {
70- backgroundColor : `#${ topic . color_hex . toString ( 16 ) . padStart ( 6 , "0" ) } ` ,
71- } }
72- />
73- { topic . name }
74- </ span >
75- ) )
76- : "No topics" }
182+ className = "h-2 w-2 rounded-full"
183+ style = { {
184+ backgroundColor : `#${ topic . color_hex
185+ . toString ( 16 )
186+ . padStart ( 6 , "0" ) } `,
187+ } }
188+ />
189+ { topic . name }
190+ </ span >
191+ ) )
192+ ) : (
193+ "No topics"
194+ ) }
195+ </ div >
196+
197+ { /* Task Description */ }
198+ < div >
199+ { isEditing ? (
200+ < textarea
201+ value = { draftDescription }
202+ onChange = { ( e ) => setDraftDescription ( e . target . value ) }
203+ className = "mt-2 w-full rounded border-2 bg-zinc-600 p-1 text-zinc-200"
204+ />
205+ ) : item . description ? (
206+ < span className = "mt-2 block text-zinc-300" > { item . description } </ span >
207+ ) : (
208+ < span className = "mt-2 block italic text-zinc-500" >
209+ No description.
210+ </ span >
211+ ) }
212+ </ div >
213+ < div className = "mt-2 flex gap-2" >
214+ { isEditing ? (
215+ < >
216+ < button onClick = { saveEdit } disabled = { saving } >
217+ Save
218+ </ button >
219+ < button onClick = { cancelEdit } > Cancel</ button >
220+ </ >
221+ ) : (
222+ < button onClick = { startEdit } > Edit</ button >
223+ ) }
77224 </ div >
78- </ label >
225+ </ div >
79226 ) ;
80227}
81228
0 commit comments