Skip to content

Commit 0f114e5

Browse files
committed
resolve conflict
2 parents 37600a4 + 8975f15 commit 0f114e5

4 files changed

Lines changed: 225 additions & 84 deletions

File tree

‎apps/codebility/app/home/my-team/[projectId]/_components/AttendanceGrid.tsx‎

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useState, useEffect, useCallback, forwardRef, useImperativeHandle } from "react";
4-
import { ChevronLeft, ChevronRight, Circle, Save, Trophy, AlertTriangle } from "lucide-react";
4+
import { ChevronLeft, ChevronRight, Circle, Save, Trophy, AlertTriangle, Calendar } from "lucide-react";
55
import { Button } from "@/components/ui/button";
66
import { SimpleMemberData } from "@/app/home/projects/actions";
77
import {
@@ -28,11 +28,19 @@ interface AttendanceGridProps {
2828
teamMembers: SimpleMemberData[];
2929
teamLead: SimpleMemberData | null;
3030
projectId: string;
31+
readOnly?: boolean;
3132
onHasChangesUpdate?: (hasChanges: boolean) => void;
3233
allowWeekendMeetings?: boolean;
3334
}
3435

35-
const AttendanceGrid = forwardRef<any, AttendanceGridProps>(({ teamMembers, teamLead, projectId, onHasChangesUpdate, allowWeekendMeetings = false }, ref) => {
36+
const AttendanceGrid = forwardRef<any, AttendanceGridProps>(({
37+
teamMembers,
38+
teamLead,
39+
projectId,
40+
readOnly = false,
41+
onHasChangesUpdate,
42+
allowWeekendMeetings = false
43+
}, ref) => {
3644
const currentDate = new Date();
3745
const [selectedMonth, setSelectedMonth] = useState(currentDate.getMonth());
3846
const [selectedYear, setSelectedYear] = useState(currentDate.getFullYear());
@@ -126,6 +134,11 @@ const AttendanceGrid = forwardRef<any, AttendanceGridProps>(({ teamMembers, team
126134

127135
// Toggle attendance status
128136
const toggleAttendance = (memberId: string, day: number) => {
137+
if (readOnly) {
138+
toast.error("You don't have permission to edit attendance. Only team leaders can make changes.");
139+
return;
140+
}
141+
129142
const dateKey = `${memberId}-${selectedYear}-${String(selectedMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
130143
const currentStatus = attendanceData[dateKey] || "absent";
131144
const dayOfWeek = getDayOfWeek(selectedYear, selectedMonth, day);
@@ -149,6 +162,11 @@ const AttendanceGrid = forwardRef<any, AttendanceGridProps>(({ teamMembers, team
149162

150163
// Save all attendance changes
151164
const saveAllAttendance = useCallback(async () => {
165+
if (readOnly) {
166+
toast.error("You don't have permission to save attendance.");
167+
return;
168+
}
169+
152170
setIsSaving(true);
153171
try {
154172
const records: any[] = [];
@@ -170,8 +188,8 @@ const AttendanceGrid = forwardRef<any, AttendanceGridProps>(({ teamMembers, team
170188
project_id: projectId,
171189
date: dateStr,
172190
status: status as any,
173-
check_in: status === "present" || status === "late" ? "09:00" : undefined,
174-
check_out: status === "present" || status === "late" ? "18:00" : undefined
191+
check_in: status === "present" ? "09:00" : undefined,
192+
check_out: status === "present" ? "18:00" : undefined
175193
});
176194
});
177195
});
@@ -187,7 +205,10 @@ const AttendanceGrid = forwardRef<any, AttendanceGridProps>(({ teamMembers, team
187205
if (warningResult.success && warningResult.warnings && warningResult.warnings.length > 0) {
188206
const warningCount = warningResult.warnings.filter(w => w.notificationSent).length;
189207
if (warningCount > 0) {
190-
toast.warning(`${warningCount} member${warningCount > 1 ? 's' : ''} received attendance warnings`);
208+
toast.error(`${warningCount} member${warningCount > 1 ? 's' : ''} received attendance warnings`, {
209+
icon: '⚠️',
210+
duration: 5000,
211+
});
191212
}
192213
}
193214
} else {
@@ -199,7 +220,7 @@ const AttendanceGrid = forwardRef<any, AttendanceGridProps>(({ teamMembers, team
199220
} finally {
200221
setIsSaving(false);
201222
}
202-
}, [allMembers, monthDays, selectedYear, selectedMonth, attendanceData, projectId, allowWeekendMeetings]);
223+
}, [readOnly, allMembers, monthDays, selectedYear, selectedMonth, attendanceData, projectId, allowWeekendMeetings]);
203224

204225
// Expose save function to parent via ref
205226
useImperativeHandle(ref, () => ({
@@ -227,7 +248,7 @@ const AttendanceGrid = forwardRef<any, AttendanceGridProps>(({ teamMembers, team
227248
const countAbsentDays = (memberId: string) => {
228249
let count = 0;
229250
const today = new Date();
230-
today.setHours(0, 0, 0, 0); // Reset to start of day for accurate comparison
251+
today.setHours(0, 0, 0, 0);
231252

232253
monthDays.forEach(day => {
233254
// Skip future dates
@@ -312,6 +333,11 @@ const AttendanceGrid = forwardRef<any, AttendanceGridProps>(({ teamMembers, team
312333
Weekend meetings enabled
313334
</span>
314335
)}
336+
{readOnly && (
337+
<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">
338+
View Only
339+
</span>
340+
)}
315341
</div>
316342
<div className="flex items-center gap-1 sm:gap-2">
317343
<Button
@@ -453,14 +479,15 @@ const AttendanceGrid = forwardRef<any, AttendanceGridProps>(({ teamMembers, team
453479
}`}
454480
>
455481
<div className="flex items-center justify-center p-1">
456-
{isWeekend && !allowWeekendMeetings ? (
457-
<div className="cursor-not-allowed">
482+
{(isWeekend && !allowWeekendMeetings) || readOnly ? (
483+
<div className={readOnly ? "cursor-not-allowed opacity-70" : "cursor-not-allowed"}>
458484
{getStatusIcon(status)}
459485
</div>
460486
) : (
461487
<button
462488
onClick={() => toggleAttendance(member.id, day)}
463489
className="hover:scale-110 transition-transform p-1"
490+
disabled={readOnly}
464491
>
465492
{getStatusIcon(status)}
466493
</button>

‎apps/codebility/app/home/my-team/[projectId]/_components/MeetingBasedAttendance.tsx‎

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ interface MeetingSchedule {
3232
interface MeetingBasedAttendanceProps {
3333
teamMembers: SimpleMemberData[];
3434
teamLead: SimpleMemberData | null;
35+
readOnly?: boolean; // ✅ ADDED: Controls edit access
3536
projectId: string;
3637
meetingSchedule: MeetingSchedule[] | null;
3738
onHasChangesUpdate?: (hasChanges: boolean) => void;
@@ -41,6 +42,7 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
4142
teamMembers,
4243
teamLead,
4344
projectId,
45+
readOnly = false,
4446
meetingSchedule,
4547
onHasChangesUpdate
4648
}, ref) => {
@@ -72,7 +74,7 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
7274
// Get day name from day of week
7375
const getDayName = (dayOfWeek: number): string => {
7476
const days = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
75-
return days[dayOfWeek];
77+
return days[dayOfWeek]!;
7678
};
7779

7880
// Month names
@@ -144,6 +146,12 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
144146

145147
// Toggle attendance status
146148
const toggleAttendance = (memberId: string, day: number) => {
149+
// Prevent editing in read-only mode
150+
if (readOnly) {
151+
toast.error("You don't have permission to edit attendance.");
152+
return;
153+
}
154+
147155
const scheduledMeeting = hasScheduledMeeting(day);
148156

149157
// Only allow changes for scheduled meeting days
@@ -167,6 +175,12 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
167175

168176
// Save all attendance changes
169177
const saveAllAttendance = useCallback(async () => {
178+
// Prevent saving in read-only mode
179+
if (readOnly) {
180+
toast.error("You don't have permission to save attendance.");
181+
return;
182+
}
183+
170184
setIsSaving(true);
171185
try {
172186
const records: any[] = [];
@@ -186,14 +200,20 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
186200

187201
const dateStr = `${selectedYear}-${String(selectedMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
188202

203+
const checkOutTime = (status === "present" || status === "excused") && scheduledMeeting.time
204+
? (() => {
205+
const [hours, minutes] = scheduledMeeting.time.split(':');
206+
// ...
207+
})()
208+
: undefined;
209+
189210
records.push({
190211
codev_id: member.id,
191212
project_id: projectId,
192213
date: dateStr,
193214
status: status as any,
194215
check_in: status === "present" || status === "excused" ? scheduledMeeting.time : undefined,
195-
check_out: status === "present" || status === "excused" ?
196-
`${parseInt(scheduledMeeting.time.split(':')[0]) + 1}:${scheduledMeeting.time.split(':')[1]}` : undefined
216+
check_out: checkOutTime
197217
});
198218
});
199219
});
@@ -209,7 +229,7 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
209229
if (warningResult.success && warningResult.warnings && warningResult.warnings.length > 0) {
210230
const warningCount = warningResult.warnings.filter(w => w.notificationSent).length;
211231
if (warningCount > 0) {
212-
toast.warning(`${warningCount} member${warningCount > 1 ? 's' : ''} received attendance warnings`);
232+
toast.error(`⚠️ ${warningCount} member${warningCount > 1 ? 's' : ''} received attendance warnings`);
213233
}
214234
}
215235
} else {
@@ -221,7 +241,7 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
221241
} finally {
222242
setIsSaving(false);
223243
}
224-
}, [allMembers, monthDays, selectedYear, selectedMonth, attendanceData, projectId, meetingSchedule]);
244+
}, [readOnly, allMembers, monthDays, selectedYear, selectedMonth, attendanceData, projectId, meetingSchedule]);
225245

226246
// Expose save function to parent via ref
227247
useImperativeHandle(ref, () => ({
@@ -383,9 +403,17 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
383403
{/* Header */}
384404
<div className="bg-gray-50 dark:bg-gray-900 p-2 sm:p-3 border-b border-gray-200 dark:border-gray-700">
385405
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2">
386-
<h3 className="text-sm sm:text-base font-semibold text-gray-900 dark:text-white">
387-
Meeting Attendance Tracker
388-
</h3>
406+
<div className="flex items-center gap-3">
407+
<h3 className="text-sm sm:text-base font-semibold text-gray-900 dark:text-white">
408+
Meeting Attendance Tracker
409+
</h3>
410+
{/* Read-only indicator badge */}
411+
{readOnly && (
412+
<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">
413+
View Only
414+
</span>
415+
)}
416+
</div>
389417
<div className="flex items-center gap-1 sm:gap-2">
390418
<Button
391419
variant="ghost"
@@ -439,6 +467,7 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
439467

440468
{/* Attendance Grid */}
441469
<div className="overflow-x-auto max-w-full">
470+
<TooltipProvider>
442471
<table className="w-full table-auto">
443472
<thead>
444473
<tr className="border-b border-gray-200 dark:border-gray-700">
@@ -452,8 +481,7 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
452481
const dayName = getDayName(dayOfWeek);
453482

454483
return (
455-
<TooltipProvider key={day}>
456-
<Tooltip>
484+
<Tooltip key={day}>
457485
<TooltipTrigger asChild>
458486
<th
459487
className={`p-0 text-center text-[9px] font-medium w-7 sm:w-8 ${
@@ -477,7 +505,6 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
477505
</p>
478506
</TooltipContent>
479507
</Tooltip>
480-
</TooltipProvider>
481508
);
482509
})}
483510
<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">
@@ -540,7 +567,8 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
540567
}`}
541568
>
542569
<div className="flex items-center justify-center p-1">
543-
{scheduledMeeting ? (
570+
{/* Disable interaction in read-only mode */}
571+
{scheduledMeeting && !readOnly ? (
544572
<button
545573
onClick={() => toggleAttendance(member.id, day)}
546574
className="hover:scale-110 transition-transform p-1"
@@ -549,8 +577,8 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
549577
{getStatusIcon(status, true)}
550578
</button>
551579
) : (
552-
<div className="cursor-not-allowed">
553-
{getStatusIcon("not_scheduled", false)}
580+
<div className={readOnly && scheduledMeeting ? "cursor-not-allowed opacity-70" : "cursor-not-allowed"}>
581+
{getStatusIcon(scheduledMeeting ? status : "not_scheduled", !!scheduledMeeting)}
554582
</div>
555583
)}
556584
</div>
@@ -577,6 +605,7 @@ const MeetingBasedAttendance = forwardRef<any, MeetingBasedAttendanceProps>(({
577605
})}
578606
</tbody>
579607
</table>
608+
</TooltipProvider>
580609
</div>
581610

582611
{/* Legend */}

0 commit comments

Comments
 (0)