1- import { FC , useMemo , useState } from 'react' ;
1+ import { FC , useCallback , useMemo , useState } from 'react' ;
22import useTranslation from 'next-translate/useTranslation' ;
33import { ColumnDef } from '@tanstack/react-table' ;
44import { ManagedCourse_Course_by_pk } from '../../../../queries/__generated__/ManagedCourse' ;
@@ -8,6 +8,11 @@ import { DegreeParticipantsWithDegreeEnrollments_Course_by_pk_CourseEnrollments
88import { DEGREE_PARTICIPANTS_WITH_DEGREE_ENROLLMENTS } from '../../../../queries/courseDegree' ;
99import { CertificateDownload } from '../../../common/CertificateDownload' ;
1010import { useTableGrid } from '../../../common/TableGrid/hooks' ;
11+ import { useRoleMutation } from '../../../../hooks/authedMutation' ;
12+ import { CREATE_CERTIFICATES } from '../../../../queries/actions' ;
13+ import { REMOVE_ACHIEVEMENT_CERTIFICATES } from '../../../../queries/courseEnrollment' ;
14+ import { BulkAction } from '../../../common/TableGrid/types' ;
15+ import NotificationSnackbar from '../../../common/dialogs/NotificationSnackbar' ;
1116
1217interface DegreeParticipationsTabIProps {
1318 course : ManagedCourse_Course_by_pk ;
@@ -25,12 +30,17 @@ export const DegreeParticipationsTab: FC<DegreeParticipationsTabIProps> = ({ cou
2530 const { t, lang } = useTranslation ( 'manageCourse' ) ;
2631
2732 const [ pageSize , setPageSize ] = useState ( 20 ) ;
33+ const [ snackbarOpen , setSnackbarOpen ] = useState ( false ) ;
34+ const [ snackbarMessage , setSnackbarMessage ] = useState ( '' ) ;
2835
2936 const handlePageSizeChange = ( newPageSize : number ) => {
3037 setPageSize ( newPageSize ) ;
3138 setPageIndex ( 0 ) ;
3239 } ;
3340
41+ const [ createCertificates ] = useRoleMutation ( CREATE_CERTIFICATES ) ;
42+ const [ removeAchievementCertificates ] = useRoleMutation ( REMOVE_ACHIEVEMENT_CERTIFICATES ) ;
43+
3444 const { data, loading, error, pageIndex, setPageIndex, searchFilter, setSearchFilter } = useTableGrid ( {
3545 queryHook : useRoleQuery ,
3646 query : DEGREE_PARTICIPANTS_WITH_DEGREE_ENROLLMENTS ,
@@ -155,67 +165,141 @@ export const DegreeParticipationsTab: FC<DegreeParticipationsTabIProps> = ({ cou
155165 } ;
156166 } ) ;
157167
168+ // Bulk actions for certificate management
169+ const bulkActions : BulkAction [ ] = useMemo (
170+ ( ) => [
171+ {
172+ value : 'generate-achievement-certificates' ,
173+ label : t ( 'generate_achievement_certificates' ) ,
174+ } ,
175+ {
176+ value : 'delete-achievement-certificates' ,
177+ label : t ( 'delete_achievement_certificates' ) ,
178+ } ,
179+ ] ,
180+ [ t ]
181+ ) ;
182+
183+ // Handle bulk actions
184+ const handleBulkAction = useCallback (
185+ async ( action : string , selectedRows : ExtendedDegreeParticipantsEnrollment [ ] ) => {
186+ if ( action === 'generate-achievement-certificates' ) {
187+ try {
188+ const userIds = selectedRows . map ( ( row ) => row . User . id ) ;
189+
190+ const response = await createCertificates ( {
191+ variables : {
192+ courseId : course . id ,
193+ userIds,
194+ certificateType : 'achievement' ,
195+ } ,
196+ } ) ;
197+
198+ const result = response . data . createCertificates ;
199+
200+ if ( ! result . success ) {
201+ throw new Error ( result . error || t ( `errors:${ result . messageKey } ` ) ) ;
202+ }
203+
204+ const certCount = result . count ;
205+ const successTranslationKey =
206+ certCount <= 1
207+ ? `course-page:${ certCount === 0 ? 'no-' : '1-' } certificate-generated`
208+ : 'course-page:certificates-generated' ;
209+
210+ setSnackbarMessage ( t ( successTranslationKey , { number : certCount } ) ) ;
211+ setSnackbarOpen ( true ) ;
212+
213+ // Refetch data to update the table
214+ setPageIndex ( pageIndex ) ; // This triggers a refetch via useTableGrid
215+ } catch ( err ) {
216+ console . error ( 'Certificate generation error:' , err ) ;
217+ setSnackbarMessage ( err . message || t ( 'errors:certificate_generation_failed' ) ) ;
218+ setSnackbarOpen ( true ) ;
219+ }
220+ } else if ( action === 'delete-achievement-certificates' ) {
221+ try {
222+ const enrollmentIds = selectedRows . map ( ( row ) => row . id ) ;
223+
224+ const response = await removeAchievementCertificates ( {
225+ variables : {
226+ enrollmentIds,
227+ } ,
228+ } ) ;
229+
230+ const affectedRows = response . data ?. update_CourseEnrollment ?. affected_rows || 0 ;
231+
232+ const successTranslationKey =
233+ affectedRows <= 1
234+ ? affectedRows === 0
235+ ? 'manageCourse:no_certificates_deleted'
236+ : 'manageCourse:certificate_deleted_singular'
237+ : 'manageCourse:certificates_deleted_plural' ;
238+
239+ setSnackbarMessage ( t ( successTranslationKey , { count : affectedRows } ) ) ;
240+ setSnackbarOpen ( true ) ;
241+
242+ // Refetch data to update the table
243+ setPageIndex ( pageIndex ) ; // This triggers a refetch via useTableGrid
244+ } catch ( err ) {
245+ console . error ( 'Certificate deletion error:' , err ) ;
246+ setSnackbarMessage ( err . message || t ( 'common:error_handling.certificate_deletion_failed' ) ) ;
247+ setSnackbarOpen ( true ) ;
248+ }
249+ }
250+ } ,
251+ [ course . id , createCertificates , removeAchievementCertificates , t , setPageIndex , pageIndex ]
252+ ) ;
253+
158254 const columns = useMemo < ColumnDef < ExtendedDegreeParticipantsEnrollment > [ ] > (
159255 ( ) => [
160256 {
161257 header : t ( 'name' ) ,
162258 accessorKey : 'name' ,
163259 enableSorting : true ,
164- className : '' ,
165- meta : {
166- width : 3 ,
167- } ,
260+ size : 200 ,
261+ minSize : 150 ,
168262 cell : ( { getValue } ) => < div className = "uppercase" > { getValue < string > ( ) } </ div > ,
169263 } ,
170264 {
171265 header : t ( 'participations' ) ,
172- accessorKey : 'participations' , // Use the flattened summary string
173- meta : {
174- width : 4 ,
175- } ,
176- cell : ( { getValue } ) => < div style = { { whiteSpace : 'pre-line' } } > { getValue < string > ( ) } </ div > , // Display the summary string with multiline support
266+ accessorKey : 'participations' ,
267+ size : 400 ,
268+ minSize : 300 ,
269+ maxSize : 600 ,
270+ cell : ( { getValue } ) => < div style = { { whiteSpace : 'pre-line' } } > { getValue < string > ( ) } </ div > ,
177271 } ,
178272 {
179273 header : t ( 'lastApplication' ) ,
180274 accessorKey : 'lastApplication' ,
181- meta : {
182- className : 'text-center' ,
183- width : 1 ,
184- } ,
275+ size : 150 ,
276+ minSize : 120 ,
185277 } ,
186278 {
187279 header : t ( 'status' ) ,
188280 accessorKey : 'status' ,
189- meta : {
190- className : 'text-center' ,
191- width : 1 ,
192- } ,
281+ size : 120 ,
282+ minSize : 100 ,
193283 } ,
194284 {
195285 header : t ( 'ectsTotal' ) ,
196286 accessorKey : 'ectsTotal' ,
197- meta : {
198- className : 'text-center' ,
199- width : 1 ,
200- } ,
287+ size : 120 ,
288+ minSize : 100 ,
201289 enableSorting : true ,
202290 } ,
203291 {
204292 header : t ( 'attendedEvents' ) ,
205293 accessorKey : 'attendedEvents' ,
206- meta : {
207- className : 'text-center' ,
208- width : 1 ,
209- } ,
294+ size : 150 ,
295+ minSize : 120 ,
210296 } ,
211297 {
212298 header : t ( 'certificate' ) ,
213299 accessorKey : 'certificate' ,
214300 accessorFn : ( row ) => row ,
215- meta : {
216- className : 'text-center' ,
217- width : 1 ,
218- } ,
301+ size : 150 ,
302+ minSize : 120 ,
219303 cell : ( { getValue } ) => (
220304 < div >
221305 < CertificateDownload courseEnrollment = { getValue < ExtendedDegreeParticipantsEnrollment > ( ) } manageView />
@@ -240,9 +324,16 @@ export const DegreeParticipationsTab: FC<DegreeParticipationsTabIProps> = ({ cou
240324 onSearchFilterChange = { setSearchFilter }
241325 error = { error }
242326 loading = { loading }
243- showCheckbox = { false }
327+ showCheckbox = { true }
328+ bulkActions = { bulkActions }
329+ onBulkAction = { handleBulkAction }
244330 refetchQueries = { [ 'DegreeParticipantsWithDegreeEnrollments' ] }
245331 />
332+ < NotificationSnackbar
333+ open = { snackbarOpen }
334+ onClose = { ( ) => setSnackbarOpen ( false ) }
335+ message = { snackbarMessage }
336+ />
246337 </ >
247338 ) ;
248339} ;
0 commit comments