@@ -32,7 +32,7 @@ interface MeetingSchedule {
3232interface MeetingBasedAttendanceProps {
3333 teamMembers : SimpleMemberData [ ] ;
3434 teamLead : SimpleMemberData | null ;
35- readOnly ?: boolean ; // ✅ ADDED: Controls edit access
35+ readOnly ?: boolean ;
3636 projectId : string ;
3737 meetingSchedule : MeetingSchedule [ ] | null ;
3838 onHasChangesUpdate ?: ( hasChanges : boolean ) => void ;
@@ -146,15 +146,12 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
146146
147147 // Toggle attendance status
148148 const toggleAttendance = ( memberId : string , day : number ) => {
149- // Prevent editing in read-only mode
150149 if ( readOnly ) {
151150 toast . error ( "You don't have permission to edit attendance." ) ;
152151 return ;
153152 }
154153
155154 const scheduledMeeting = hasScheduledMeeting ( day ) ;
156-
157- // Only allow changes for scheduled meeting days
158155 if ( ! scheduledMeeting ) return ;
159156
160157 const dateKey = `${ memberId } -${ selectedYear } -${ String ( selectedMonth + 1 ) . padStart ( 2 , '0' ) } -${ String ( day ) . padStart ( 2 , '0' ) } ` ;
@@ -164,7 +161,7 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
164161 if ( currentStatus === "present" ) newStatus = "absent" ;
165162 else if ( currentStatus === "absent" ) newStatus = "excused" ;
166163 else if ( currentStatus === "excused" ) newStatus = "present" ;
167- else newStatus = "absent" ; // For not_scheduled, start with absent
164+ else newStatus = "absent" ;
168165
169166 setAttendanceData ( prev => ( {
170167 ...prev ,
@@ -175,7 +172,6 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
175172
176173 // Save all attendance changes
177174 const saveAllAttendance = useCallback ( async ( ) => {
178- // Prevent saving in read-only mode
179175 if ( readOnly ) {
180176 toast . error ( "You don't have permission to save attendance." ) ;
181177 return ;
@@ -188,14 +184,11 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
188184 allMembers . forEach ( member => {
189185 monthDays . forEach ( day => {
190186 const scheduledMeeting = hasScheduledMeeting ( day ) ;
191-
192- // Only save attendance for scheduled meeting days
193187 if ( ! scheduledMeeting ) return ;
194188
195189 const dateKey = `${ member . id } -${ selectedYear } -${ String ( selectedMonth + 1 ) . padStart ( 2 , '0' ) } -${ String ( day ) . padStart ( 2 , '0' ) } ` ;
196190 const status = attendanceData [ dateKey ] ;
197191
198- // Skip if not_scheduled
199192 if ( status === "not_scheduled" ) return ;
200193
201194 const dateStr = `${ selectedYear } -${ String ( selectedMonth + 1 ) . padStart ( 2 , '0' ) } -${ String ( day ) . padStart ( 2 , '0' ) } ` ;
@@ -224,7 +217,6 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
224217 toast . success ( "Meeting attendance saved successfully!" ) ;
225218 setHasUnsavedChanges ( false ) ;
226219
227- // Check for attendance warnings after saving
228220 const warningResult = await checkAttendanceWarnings ( projectId , selectedYear , selectedMonth ) ;
229221 if ( warningResult . success && warningResult . warnings && warningResult . warnings . length > 0 ) {
230222 const warningCount = warningResult . warnings . filter ( w => w . notificationSent ) . length ;
@@ -274,13 +266,12 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
274266 const countAbsences = ( memberId : string ) => {
275267 let count = 0 ;
276268 const today = new Date ( ) ;
277- today . setHours ( 0 , 0 , 0 , 0 ) ; // Reset to start of day for accurate comparison
269+ today . setHours ( 0 , 0 , 0 , 0 ) ;
278270
279271 monthDays . forEach ( day => {
280272 const scheduledMeeting = hasScheduledMeeting ( day ) ;
281273 if ( ! scheduledMeeting ) return ;
282274
283- // Skip future dates
284275 const currentDateToCheck = new Date ( selectedYear , selectedMonth , day ) ;
285276 if ( currentDateToCheck > today ) return ;
286277
@@ -407,7 +398,6 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
407398 < h3 className = "text-sm sm:text-base font-semibold text-gray-900 dark:text-white" >
408399 Meeting Attendance Tracker
409400 </ h3 >
410- { /* Read-only indicator badge */ }
411401 { readOnly && (
412402 < span className = "text-xs bg-amber-100 text-amber-700 dark:bg-amber-900/20 dark:text-amber-300 px-2 py-0.5 rounded-full" >
413403 View Only
@@ -467,145 +457,148 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
467457
468458 { /* Attendance Grid */ }
469459 < div className = "overflow-x-auto max-w-full" >
470- < TooltipProvider >
471- < table className = "w-full table-auto" >
472- < thead >
473- < tr className = "border-b border-gray-200 dark:border-gray-700" >
474- < th className = "sticky left-0 z-10 bg-gray-50 dark:bg-gray-900 text-left p-1 sm:p-2 text-[10px] sm:text-xs font-medium text-gray-700 dark:text-gray-300 w-28 sm:w-36" >
475- < span className = "hidden sm:inline" > Team Member</ span >
476- < span className = "sm:hidden" > Member</ span >
477- </ th >
478- { monthDays . map ( day => {
479- const scheduledMeeting = hasScheduledMeeting ( day ) ;
480- const dayOfWeek = getDayOfWeek ( selectedYear , selectedMonth , day ) ;
481- const dayName = getDayName ( dayOfWeek ) ;
460+ < TooltipProvider >
461+ < table className = "w-full table-auto" >
462+ < thead >
463+ < tr className = "border-b border-gray-200 dark:border-gray-700" >
464+ < th className = "sticky left-0 z-10 bg-gray-50 dark:bg-gray-900 text-left p-1 sm:p-2 text-[10px] sm:text-xs font-medium text-gray-700 dark:text-gray-300 w-28 sm:w-36" >
465+ < span className = "hidden sm:inline" > Team Member</ span >
466+ < span className = "sm:hidden" > Member</ span >
467+ </ th >
468+ { /* ✅ FIXED: Tooltip is now INSIDE <th>, not wrapping it */ }
469+ { monthDays . map ( day => {
470+ const scheduledMeeting = hasScheduledMeeting ( day ) ;
471+ const dayOfWeek = getDayOfWeek ( selectedYear , selectedMonth , day ) ;
472+ const dayName = getDayName ( dayOfWeek ) ;
473+
474+ return (
475+ < th
476+ key = { day }
477+ className = { `p-0 text-center text-[9px] font-medium w-7 sm:w-8 ${
478+ scheduledMeeting
479+ ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
480+ : 'bg-gray-100 dark:bg-gray-800 text-gray-400'
481+ } `}
482+ >
483+ < Tooltip >
484+ < TooltipTrigger asChild >
485+ < div >
486+ < div className = "font-medium text-[8px] sm:text-[10px]" > { day } </ div >
487+ { scheduledMeeting && (
488+ < div className = "text-[7px] text-blue-600 dark:text-blue-400" >
489+ { scheduledMeeting . time }
490+ </ div >
491+ ) }
492+ </ div >
493+ </ TooltipTrigger >
494+ < TooltipContent >
495+ < p className = "text-xs" >
496+ { dayName . charAt ( 0 ) . toUpperCase ( ) + dayName . slice ( 1 ) }
497+ { scheduledMeeting && ` - Meeting at ${ scheduledMeeting . time } ` }
498+ </ p >
499+ </ TooltipContent >
500+ </ Tooltip >
501+ </ th >
502+ ) ;
503+ } ) }
504+ < th className = "sticky right-0 z-10 bg-green-50 dark:bg-green-900/20 text-center p-1 text-[9px] font-medium text-gray-700 dark:text-gray-300 w-14 sm:w-16" >
505+ < div className = "text-[8px] sm:text-[10px]" > Attended</ div >
506+ < div className = "text-[8px] sm:text-[10px] font-normal text-gray-500" > Pts</ div >
507+ </ th >
508+ </ tr >
509+ </ thead >
510+ < tbody >
511+ { allMembers . map ( ( member , index ) => {
512+ const isLead = teamLead ?. id === member . id ;
513+ const meetingsAttended = countMeetingsAttended ( member . id ) ;
514+ const absences = countAbsences ( member . id ) ;
515+ const hasWarning = absences >= 3 ;
482516
483517 return (
484- < Tooltip key = { day } >
485- < TooltipTrigger asChild >
486- < th
487- className = { `p-0 text-center text-[9px] font-medium w-7 sm:w-8 ${
488- scheduledMeeting
489- ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
490- : 'bg-gray-100 dark:bg-gray-800 text-gray-400'
491- } `}
492- >
493- < div className = "font-medium text-[8px] sm:text-[10px]" > { day } </ div >
494- { scheduledMeeting && (
495- < div className = "text-[7px] text-blue-600 dark:text-blue-400" >
496- { scheduledMeeting . time }
497- </ div >
498- ) }
499- </ th >
500- </ TooltipTrigger >
501- < TooltipContent >
502- < p className = "text-xs" >
503- { dayName . charAt ( 0 ) . toUpperCase ( ) + dayName . slice ( 1 ) }
504- { scheduledMeeting && ` - Meeting at ${ scheduledMeeting . time } ` }
505- </ p >
506- </ TooltipContent >
507- </ Tooltip >
508- ) ;
509- } ) }
510- < th className = "sticky right-0 z-10 bg-green-50 dark:bg-green-900/20 text-center p-1 text-[9px] font-medium text-gray-700 dark:text-gray-300 w-14 sm:w-16" >
511- < div className = "text-[8px] sm:text-[10px]" > Attended</ div >
512- < div className = "text-[8px] sm:text-[10px] font-normal text-gray-500" > Pts</ div >
513- </ th >
514- </ tr >
515- </ thead >
516- < tbody >
517- { allMembers . map ( ( member , index ) => {
518- const isLead = teamLead ?. id === member . id ;
519- const meetingsAttended = countMeetingsAttended ( member . id ) ;
520- const absences = countAbsences ( member . id ) ;
521- const hasWarning = absences >= 3 ;
522-
523- return (
524- < tr
525- key = { member . id }
526- className = { `border-b border-gray-200 dark:border-gray-700 ${
527- index % 2 === 0 ? 'bg-white dark:bg-gray-900' : 'bg-gray-50 dark:bg-gray-800/50'
528- } `}
529- >
530- < td className = "sticky left-0 z-10 bg-inherit p-1 sm:p-2" >
531- < div className = "flex flex-col" >
532- < div className = "flex items-center gap-1" >
533- < span className = "font-medium text-[9px] sm:text-[11px] text-gray-900 dark:text-white truncate max-w-[50px] sm:max-w-[100px]" >
534- < span className = "sm:hidden" > { member . first_name . charAt ( 0 ) } . { member . last_name . charAt ( 0 ) } .</ span >
535- < span className = "hidden sm:inline" > { member . first_name } { member . last_name } </ span >
536- </ span >
537- { isLead && (
538- < span className = "rounded bg-blue-100 px-0.5 py-0 text-[7px] text-blue-800 dark:bg-blue-900/20 dark:text-blue-400" >
539- TL
518+ < tr
519+ key = { member . id }
520+ className = { `border-b border-gray-200 dark:border-gray-700 ${
521+ index % 2 === 0 ? 'bg-white dark:bg-gray-900' : 'bg-gray-50 dark:bg-gray-800/50'
522+ } `}
523+ >
524+ < td className = "sticky left-0 z-10 bg-inherit p-1 sm:p-2" >
525+ < div className = "flex flex-col" >
526+ < div className = "flex items-center gap-1" >
527+ < span className = "font-medium text-[9px] sm:text-[11px] text-gray-900 dark:text-white truncate max-w-[50px] sm:max-w-[100px]" >
528+ < span className = "sm:hidden" > { member . first_name . charAt ( 0 ) } . { member . last_name . charAt ( 0 ) } .</ span >
529+ < span className = "hidden sm:inline" > { member . first_name } { member . last_name } </ span >
540530 </ span >
541- ) }
542- { hasWarning && (
543- < Tooltip >
544- < TooltipTrigger >
545- < AlertTriangle className = "h-3 w-3 text-red-500 animate-pulse" />
546- </ TooltipTrigger >
547- < TooltipContent >
548- < p className = "text-xs" >
549- Warning: { absences } absences - Account at risk of deactivation
550- </ p >
551- </ TooltipContent >
552- </ Tooltip >
553- ) }
554- </ div >
555- </ div >
556- </ td >
557- { monthDays . map ( day => {
558- const scheduledMeeting = hasScheduledMeeting ( day ) ;
559- const dateKey = `${ member . id } -${ selectedYear } -${ String ( selectedMonth + 1 ) . padStart ( 2 , '0' ) } -${ String ( day ) . padStart ( 2 , '0' ) } ` ;
560- const status = attendanceData [ dateKey ] || "not_scheduled" ;
561-
562- return (
563- < td
564- key = { day }
565- className = { `p-0 sm:p-0.5 text-center w-7 sm:w-8 ${
566- scheduledMeeting ? 'bg-blue-50 dark:bg-blue-900/20' : 'bg-gray-100 dark:bg-gray-800'
567- } `}
568- >
569- < div className = "flex items-center justify-center p-1" >
570- { /* Disable interaction in read-only mode */ }
571- { scheduledMeeting && ! readOnly ? (
572- < button
573- onClick = { ( ) => toggleAttendance ( member . id , day ) }
574- className = "hover:scale-110 transition-transform p-1"
575- title = { `Toggle attendance for ${ scheduledMeeting . day } meeting at ${ scheduledMeeting . time } ` }
576- >
577- { getStatusIcon ( status , true ) }
578- </ button >
579- ) : (
580- < div className = { readOnly && scheduledMeeting ? "cursor-not-allowed opacity-70" : "cursor-not-allowed" } >
581- { getStatusIcon ( scheduledMeeting ? status : "not_scheduled" , ! ! scheduledMeeting ) }
582- </ div >
531+ { isLead && (
532+ < span className = "rounded bg-blue-100 px-0.5 py-0 text-[7px] text-blue-800 dark:bg-blue-900/20 dark:text-blue-400" >
533+ TL
534+ </ span >
535+ ) }
536+ { hasWarning && (
537+ < Tooltip >
538+ < TooltipTrigger >
539+ < AlertTriangle className = "h-3 w-3 text-red-500 animate-pulse" />
540+ </ TooltipTrigger >
541+ < TooltipContent >
542+ < p className = "text-xs" >
543+ Warning: { absences } absences - Account at risk of deactivation
544+ </ p >
545+ </ TooltipContent >
546+ </ Tooltip >
583547 ) }
584548 </ div >
585- </ td >
586- ) ;
587- } ) }
588- < td className = { `sticky right-0 z-10 text-center p-0.5 ${ hasWarning ? 'bg-red-50 dark:bg-red-900/20' : 'bg-green-50 dark:bg-green-900/20' } ` } >
589- < div className = "space-y-0" >
590- < div className = { `font-semibold text-[10px] sm:text-xs ${ hasWarning ? 'text-red-700 dark:text-red-400' : 'text-green-700 dark:text-green-400' } ` } >
591- { meetingsAttended } /{ totalScheduledMeetings }
592- </ div >
593- < div className = "text-[8px] sm:text-[10px] text-gray-600 dark:text-gray-400 leading-tight" >
594- +{ meetingsAttended * ATTENDANCE_POINTS_PER_MEETING }
595549 </ div >
596- { hasWarning && (
597- < div className = "text-[7px] text-red-600 dark:text-red-400 font-medium" >
598- { absences } absent
550+ </ td >
551+ { monthDays . map ( day => {
552+ const scheduledMeeting = hasScheduledMeeting ( day ) ;
553+ const dateKey = `${ member . id } -${ selectedYear } -${ String ( selectedMonth + 1 ) . padStart ( 2 , '0' ) } -${ String ( day ) . padStart ( 2 , '0' ) } ` ;
554+ const status = attendanceData [ dateKey ] || "not_scheduled" ;
555+
556+ return (
557+ < td
558+ key = { day }
559+ className = { `p-0 sm:p-0.5 text-center w-7 sm:w-8 ${
560+ scheduledMeeting ? 'bg-blue-50 dark:bg-blue-900/20' : 'bg-gray-100 dark:bg-gray-800'
561+ } `}
562+ >
563+ < div className = "flex items-center justify-center p-1" >
564+ { scheduledMeeting && ! readOnly ? (
565+ < button
566+ onClick = { ( ) => toggleAttendance ( member . id , day ) }
567+ className = "hover:scale-110 transition-transform p-1"
568+ title = { `Toggle attendance for ${ scheduledMeeting . day } meeting at ${ scheduledMeeting . time } ` }
569+ >
570+ { getStatusIcon ( status , true ) }
571+ </ button >
572+ ) : (
573+ < div className = { readOnly && scheduledMeeting ? "cursor-not-allowed opacity-70" : "cursor-not-allowed" } >
574+ { getStatusIcon ( scheduledMeeting ? status : "not_scheduled" , ! ! scheduledMeeting ) }
575+ </ div >
576+ ) }
577+ </ div >
578+ </ td >
579+ ) ;
580+ } ) }
581+ < td className = { `sticky right-0 z-10 text-center p-0.5 ${ hasWarning ? 'bg-red-50 dark:bg-red-900/20' : 'bg-green-50 dark:bg-green-900/20' } ` } >
582+ < div className = "space-y-0" >
583+ < div className = { `font-semibold text-[10px] sm:text-xs ${ hasWarning ? 'text-red-700 dark:text-red-400' : 'text-green-700 dark:text-green-400' } ` } >
584+ { meetingsAttended } /{ totalScheduledMeetings }
585+ </ div >
586+ < div className = "text-[8px] sm:text-[10px] text-gray-600 dark:text-gray-400 leading-tight" >
587+ +{ meetingsAttended * ATTENDANCE_POINTS_PER_MEETING }
599588 </ div >
600- ) }
601- </ div >
602- </ td >
603- </ tr >
604- ) ;
605- } ) }
606- </ tbody >
607- </ table >
608- </ TooltipProvider >
589+ { hasWarning && (
590+ < div className = "text-[7px] text-red-600 dark:text-red-400 font-medium" >
591+ { absences } absent
592+ </ div >
593+ ) }
594+ </ div >
595+ </ td >
596+ </ tr >
597+ ) ;
598+ } ) }
599+ </ tbody >
600+ </ table >
601+ </ TooltipProvider >
609602 </ div >
610603
611604 { /* Legend */ }
0 commit comments