@@ -39,18 +39,86 @@ import { cn } from "@/lib/utils";
39
39
import { models , types } from "@/data/models" ;
40
40
import { current , future , past } from "@/data/presets" ;
41
41
import { useAppStateStore } from "@/lib/providers/app-state-store-provider" ;
42
- //import { useAuthStore } from "@/lib/providers/auth-store-provider";
42
+ import { useEffect , useState } from "react" ;
43
+ import {
44
+ createNote ,
45
+ fetchNotesByCoachingSessionId ,
46
+ updateNote ,
47
+ } from "@/lib/api/notes" ;
48
+ import { Note , noteToString } from "@/types/note" ;
49
+ import { useAuthStore } from "@/lib/providers/auth-store-provider" ;
50
+ import { Id } from "@/types/general" ;
43
51
44
52
// export const metadata: Metadata = {
45
53
// title: "Coaching Session",
46
54
// description: "Coaching session main page, where the good stuff happens.",
47
55
// };
48
56
49
57
export default function CoachingSessionsPage ( ) {
50
- const [ isOpen , setIsOpen ] = React . useState ( false ) ;
51
- //const { isLoggedIn, userId } = useAuthStore((state) => state);
52
- const { organizationId, relationshipId, coachingSessionId } =
53
- useAppStateStore ( ( state ) => state ) ;
58
+ const [ isOpen , setIsOpen ] = useState ( false ) ;
59
+ const [ noteId , setNoteId ] = useState < Id > ( "" ) ;
60
+ const [ note , setNote ] = useState < string > ( "" ) ;
61
+ const [ syncStatus , setSyncStatus ] = useState < string > ( "" ) ;
62
+ const { userId } = useAuthStore ( ( state ) => state ) ;
63
+ const { coachingSessionId } = useAppStateStore ( ( state ) => state ) ;
64
+
65
+ useEffect ( ( ) => {
66
+ async function fetchNote ( ) {
67
+ if ( ! coachingSessionId ) return ;
68
+
69
+ await fetchNotesByCoachingSessionId ( coachingSessionId )
70
+ . then ( ( notes ) => {
71
+ // Apparently it's normal for this to be triggered twice in modern
72
+ // React versions in strict + development modes
73
+ // https://stackoverflow.com/questions/60618844/react-hooks-useeffect-is-called-twice-even-if-an-empty-array-is-used-as-an-ar
74
+ const note = notes [ 0 ] ;
75
+ console . trace ( "note: " + noteToString ( note ) ) ;
76
+ setNoteId ( note . id ) ;
77
+ setNote ( note . body ) ;
78
+ } )
79
+ . catch ( ( err ) => {
80
+ console . error (
81
+ "Failed to fetch Note for current coaching session: " + err
82
+ ) ;
83
+
84
+ createNote ( coachingSessionId , userId , "" )
85
+ . then ( ( note ) => {
86
+ // Apparently it's normal for this to be triggered twice in modern
87
+ // React versions in strict + development modes
88
+ // https://stackoverflow.com/questions/60618844/react-hooks-useeffect-is-called-twice-even-if-an-empty-array-is-used-as-an-ar
89
+ console . trace ( "New empty note: " + noteToString ( note ) ) ;
90
+ setNoteId ( note . id ) ;
91
+ } )
92
+ . catch ( ( err ) => {
93
+ console . error ( "Failed to create new empty Note: " + err ) ;
94
+ } ) ;
95
+ } ) ;
96
+ }
97
+ fetchNote ( ) ;
98
+ } , [ coachingSessionId , ! note ] ) ;
99
+
100
+ const handleInputChange = ( value : string ) => {
101
+ setNote ( value ) ;
102
+
103
+ if ( noteId && coachingSessionId && userId ) {
104
+ updateNote ( noteId , coachingSessionId , userId , value )
105
+ . then ( ( note ) => {
106
+ // Apparently it's normal for this to be triggered twice in modern
107
+ // React versions in strict + development modes
108
+ // https://stackoverflow.com/questions/60618844/react-hooks-useeffect-is-called-twice-even-if-an-empty-array-is-used-as-an-ar
109
+ console . trace ( "Updated Note: " + noteToString ( note ) ) ;
110
+ setSyncStatus ( "All changes saved" ) ;
111
+ } )
112
+ . catch ( ( err ) => {
113
+ setSyncStatus ( "Failed to save changes" ) ;
114
+ console . error ( "Failed to update Note: " + err ) ;
115
+ } ) ;
116
+ }
117
+ } ;
118
+
119
+ const handleKeyDown = ( ) => {
120
+ setSyncStatus ( "" ) ;
121
+ } ;
54
122
55
123
return (
56
124
< >
@@ -156,10 +224,14 @@ export default function CoachingSessionsPage() {
156
224
</ TabsList >
157
225
< TabsContent value = "notes" >
158
226
< div className = "flex h-full flex-col space-y-4" >
159
- < Textarea
160
- placeholder = "Session notes"
161
- className = "p-4 min-h-[400px] md:min-h-[630px] lg:min-h-[630px]"
162
- />
227
+ < CoachingNotes
228
+ value = { note }
229
+ onChange = { handleInputChange }
230
+ onKeyDown = { handleKeyDown }
231
+ > </ CoachingNotes >
232
+ < p className = "text-sm text-muted-foreground" >
233
+ { syncStatus }
234
+ </ p >
163
235
</ div >
164
236
</ TabsContent >
165
237
< TabsContent value = "program" >
@@ -194,3 +266,60 @@ export default function CoachingSessionsPage() {
194
266
</ >
195
267
) ;
196
268
}
269
+
270
+ // A debounced input CoachingNotes textarea component
271
+ // TODO: move this into the components dir
272
+ const CoachingNotes : React . FC < {
273
+ value : string ;
274
+ onChange : ( value : string ) => void ;
275
+ onKeyDown : ( ) => void ;
276
+ } > = ( { value, onChange, onKeyDown } ) => {
277
+ const WAIT_INTERVAL = 1000 ;
278
+ const [ timer , setTimer ] = useState < number | undefined > ( undefined ) ;
279
+ const [ note , setNote ] = useState < string > ( value ) ;
280
+
281
+ // Make sure the internal value prop updates when the component interface's
282
+ // value prop changes.
283
+ useEffect ( ( ) => {
284
+ setNote ( value ) ;
285
+ } , [ value ] ) ;
286
+
287
+ const handleSessionNoteChange = (
288
+ e : React . ChangeEvent < HTMLTextAreaElement >
289
+ ) => {
290
+ const newValue = e . target . value ;
291
+ setNote ( newValue ) ;
292
+
293
+ if ( timer ) {
294
+ clearTimeout ( timer ) ;
295
+ }
296
+
297
+ const newTimer = window . setTimeout ( ( ) => {
298
+ onChange ( newValue ) ;
299
+ } , WAIT_INTERVAL ) ;
300
+
301
+ setTimer ( newTimer ) ;
302
+ } ;
303
+
304
+ const handleOnKeyDown = ( e : React . KeyboardEvent < HTMLTextAreaElement > ) => {
305
+ onKeyDown ( ) ;
306
+ } ;
307
+
308
+ useEffect ( ( ) => {
309
+ return ( ) => {
310
+ if ( timer ) {
311
+ clearTimeout ( timer ) ;
312
+ }
313
+ } ;
314
+ } , [ timer ] ) ;
315
+
316
+ return (
317
+ < Textarea
318
+ placeholder = "Session notes"
319
+ value = { note }
320
+ className = "p-4 min-h-[400px] md:min-h-[630px] lg:min-h-[630px]"
321
+ onChange = { handleSessionNoteChange }
322
+ onKeyDown = { handleOnKeyDown }
323
+ />
324
+ ) ;
325
+ } ;
0 commit comments