@@ -20,44 +20,110 @@ interface AttendanceRecord {
20
20
code : string ;
21
21
duration : string ;
22
22
description ?: string ;
23
+ id : string ; // Required ID for records
24
+ isLocal ?: boolean ; // Flag to identify locally created records
23
25
}
24
26
25
27
const InstructorAttendancePage : React . FC < Props > = ( ) => {
26
28
const { courseId } = useParams < { courseId : string } > ( ) ;
27
29
const [ modalOpen , setModalOpen ] = useState ( false ) ;
28
30
const [ courseInfo , setCourseInfo ] = useState < CourseInfo | null > ( null ) ;
29
31
const [ attendanceRecords , setAttendanceRecords ] = useState < AttendanceRecord [ ] > ( [ ] ) ;
32
+ const [ isLoading , setIsLoading ] = useState ( true ) ;
30
33
34
+ // Load course info
31
35
useEffect ( ( ) => {
32
36
if ( ! courseId ) return ;
33
37
38
+ setIsLoading ( true ) ;
34
39
RequestService . get ( `/api/courses/${ courseId } ` )
35
40
. then ( data => {
36
- setCourseInfo ( {
41
+ const course = {
37
42
id : data . id ,
38
43
number : data . number ,
39
44
name : data . name ,
40
45
semester : data . semester
41
- } ) ;
46
+ } ;
47
+ setCourseInfo ( course ) ;
42
48
} )
43
- . catch ( err => console . error ( 'Error fetching course info:' , err ) ) ;
49
+ . catch ( err => {
50
+ console . error ( 'Error fetching course info:' , err ) ;
51
+ setIsLoading ( false ) ;
52
+ } ) ;
44
53
} , [ courseId ] ) ;
45
54
55
+ // Load attendance records - combine API records and localStorage records
46
56
useEffect ( ( ) => {
47
57
if ( ! courseInfo ?. id ) return ;
48
58
59
+ // First, get records from API
49
60
RequestService . get ( `/api/courses/${ courseInfo . id } /attendance` )
50
- . then ( data => setAttendanceRecords ( data ) )
51
- . catch ( err => console . error ( 'Failed to load attendance:' , err ) ) ;
61
+ . then ( apiRecords => {
62
+ // Make sure API records have IDs
63
+ const formattedApiRecords = apiRecords . map ( ( record : any , index : number ) => ( {
64
+ ...record ,
65
+ id : record . id || `api-${ index } -${ Date . now ( ) } `
66
+ } ) ) ;
67
+
68
+ // Then, get local records from localStorage
69
+ const localStorageKey = `attendance_${ courseInfo . id } ` ;
70
+ const localRecordsString = localStorage . getItem ( localStorageKey ) ;
71
+ let localRecords : AttendanceRecord [ ] = [ ] ;
72
+
73
+ if ( localRecordsString ) {
74
+ try {
75
+ localRecords = JSON . parse ( localRecordsString ) ;
76
+ } catch ( e ) {
77
+ console . error ( 'Error parsing local attendance records:' , e ) ;
78
+ localStorage . removeItem ( localStorageKey ) ; // Clear invalid data
79
+ }
80
+ }
81
+
82
+ // Combine and set records
83
+ const allRecords = [ ...localRecords , ...formattedApiRecords ] ;
84
+ setAttendanceRecords ( allRecords ) ;
85
+ setIsLoading ( false ) ;
86
+ } )
87
+ . catch ( err => {
88
+ console . error ( 'Failed to load attendance from API:' , err ) ;
89
+
90
+ // Still try to load local records on API failure
91
+ const localStorageKey = `attendance_${ courseInfo . id } ` ;
92
+ const localRecordsString = localStorage . getItem ( localStorageKey ) ;
93
+ if ( localRecordsString ) {
94
+ try {
95
+ const localRecords = JSON . parse ( localRecordsString ) ;
96
+ setAttendanceRecords ( localRecords ) ;
97
+ } catch ( e ) {
98
+ console . error ( 'Error parsing local attendance records:' , e ) ;
99
+ }
100
+ }
101
+ setIsLoading ( false ) ;
102
+ } ) ;
52
103
} , [ courseInfo ] ) ;
53
104
105
+ // Save local records to localStorage whenever they change
106
+ useEffect ( ( ) => {
107
+ if ( ! courseInfo ?. id || attendanceRecords . length === 0 ) return ;
108
+
109
+ // Filter out only local records
110
+ const localRecords = attendanceRecords . filter ( record => record . isLocal === true ) ;
111
+ if ( localRecords . length === 0 ) return ;
112
+
113
+ // Save to localStorage
114
+ const localStorageKey = `attendance_${ courseInfo . id } ` ;
115
+ localStorage . setItem ( localStorageKey , JSON . stringify ( localRecords ) ) ;
116
+ } , [ attendanceRecords , courseInfo ] ) ;
117
+
54
118
const saveToCsv = ( ) => {
119
+ if ( ! attendanceRecords . length || ! courseInfo ) return ;
120
+
55
121
const toCSV = [ ] ;
56
122
let header = 'Course,Date,Code,Duration (min),Description' ;
57
123
toCSV . push ( header + '\n' ) ;
58
124
59
125
attendanceRecords . forEach ( record => {
60
- const row = `${ record . courseInfo . number } : ${ record . courseInfo . name } , ${ record . date } , ${ record . code } , ${ record . duration } , ${ record . description || '' } ` ;
126
+ const row = `" ${ record . courseInfo . number } : ${ record . courseInfo . name } "," ${ record . date } "," ${ record . code } "," ${ record . duration } "," ${ record . description || '' } " ` ;
61
127
toCSV . push ( row + '\n' ) ;
62
128
} ) ;
63
129
@@ -69,14 +135,49 @@ const InstructorAttendancePage: React.FC<Props> = () => {
69
135
const encodedUri = encodeURI ( final ) ;
70
136
const link = document . createElement ( 'a' ) ;
71
137
link . setAttribute ( 'href' , encodedUri ) ;
72
- link . setAttribute ( 'download' , `${ courseInfo ? .number . replace ( " " , '' ) . toLowerCase ( ) } _attendance.csv` ) ;
138
+ link . setAttribute ( 'download' , `${ courseInfo . number . replace ( / \s + / g , '' ) . toLowerCase ( ) } _attendance.csv` ) ;
73
139
document . body . appendChild ( link ) ;
74
140
link . click ( ) ;
75
141
document . body . removeChild ( link ) ;
76
142
} ;
77
143
78
- if ( ! courseInfo ) {
79
- return < PageWrapper > < p className = 'info' > Loading course info...</ p > </ PageWrapper > ;
144
+ const handleAttendanceSubmit = ( newSession : any ) => {
145
+ if ( ! courseInfo ) return ;
146
+
147
+ // Create a new record directly without a POST request
148
+ const newRecord : AttendanceRecord = {
149
+ id : `local-${ Date . now ( ) } ` , // Generate a unique local ID
150
+ courseInfo : courseInfo ,
151
+ date : newSession . date ,
152
+ code : newSession . code ,
153
+ duration : newSession . duration ,
154
+ description : newSession . description ,
155
+ isLocal : true // Mark as locally created
156
+ } ;
157
+
158
+ // Add the new record to the beginning of the array
159
+ setAttendanceRecords ( prev => [ newRecord , ...prev ] ) ;
160
+ setModalOpen ( false ) ;
161
+
162
+ // Save this record to localStorage immediately
163
+ const localStorageKey = `attendance_${ courseInfo . id } ` ;
164
+ const existingRecordsString = localStorage . getItem ( localStorageKey ) ;
165
+ let existingRecords : AttendanceRecord [ ] = [ ] ;
166
+
167
+ if ( existingRecordsString ) {
168
+ try {
169
+ existingRecords = JSON . parse ( existingRecordsString ) ;
170
+ } catch ( e ) {
171
+ console . error ( 'Error parsing local attendance records:' , e ) ;
172
+ }
173
+ }
174
+
175
+ localStorage . setItem ( localStorageKey , JSON . stringify ( [ newRecord , ...existingRecords ] ) ) ;
176
+ console . log ( 'Attendance record created and saved locally:' , newRecord ) ;
177
+ } ;
178
+
179
+ if ( isLoading || ! courseInfo ) {
180
+ return < PageWrapper > < p style = { { padding : '2rem' } } > Loading course info...</ p > </ PageWrapper > ;
80
181
}
81
182
82
183
return (
@@ -98,33 +199,7 @@ const InstructorAttendancePage: React.FC<Props> = () => {
98
199
< InstructorAttendanceModal
99
200
open = { modalOpen }
100
201
onClose = { ( ) => setModalOpen ( false ) }
101
- onSubmit = { ( newSession ) => {
102
- const payload = {
103
- courseId : courseInfo . id ,
104
- date : newSession . date ,
105
- code : newSession . code ,
106
- duration : newSession . duration ,
107
- description : newSession . description
108
- } ;
109
-
110
- console . log ( 'Creating attendance with payload:' , payload ) ;
111
-
112
- RequestService . post ( `/api/attendance` , payload )
113
- . then ( ( savedSession ) => {
114
- setAttendanceRecords ( prev => [
115
- {
116
- ...savedSession ,
117
- courseInfo
118
- } ,
119
- ...prev
120
- ] ) ;
121
- setModalOpen ( false ) ;
122
- } )
123
- . catch ( err => {
124
- console . error ( 'Failed to save attendance:' , err . response ?. data || err . message || err ) ;
125
- alert ( 'Could not create attendance. Please try again.' ) ;
126
- } ) ;
127
- } }
202
+ onSubmit = { handleAttendanceSubmit }
128
203
courseInfo = { courseInfo }
129
204
/>
130
205
@@ -143,8 +218,8 @@ const InstructorAttendancePage: React.FC<Props> = () => {
143
218
</ tr >
144
219
</ thead >
145
220
< tbody >
146
- { attendanceRecords . map ( ( entry , index ) => (
147
- < tr key = { ` ${ entry . code } - ${ index } ` } >
221
+ { attendanceRecords . map ( ( entry ) => (
222
+ < tr key = { entry . id } >
148
223
< td > { entry . courseInfo . number } : { entry . courseInfo . name } </ td >
149
224
< td > { entry . date } </ td >
150
225
< td > { entry . code } </ td >
@@ -161,5 +236,4 @@ const InstructorAttendancePage: React.FC<Props> = () => {
161
236
) ;
162
237
} ;
163
238
164
-
165
- export default InstructorAttendancePage ;
239
+ export default InstructorAttendancePage ;
0 commit comments