Skip to content

Commit 81aa31e

Browse files
Zeff01claude
andcommitted
feat: Add survey management system and fix attendance notifications for new members
Survey Feature: - Create comprehensive survey management system in settings - Add surveys table with support for multiple types and target audiences - Implement CRUD operations for surveys (create, read, update, delete) - Add survey image upload functionality - Support external survey URLs (Google Forms, Typeform, etc.) - Restrict survey management to admin-only access - Add survey card to settings page Attendance Fix: - Fix attendance warning notifications for new project members - Update database trigger to only count absences from member join date - Update server actions to filter attendance by member join date - Prevent new members from getting notifications for dates before they joined - Add migration: fix_attendance_warning_for_new_members.sql 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1c166bd commit 81aa31e

13 files changed

Lines changed: 1368 additions & 29 deletions

File tree

‎apps/codebility/app/home/applicants/_components/applicantTechStack.tsx‎

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import Image from "next/image";
33

4+
import { getTechStackIconName } from "@/constants/techstack";
45
import { NewApplicantType } from "../_service/types";
56

67
export default function ApplicantTechStack({
@@ -22,21 +23,24 @@ export default function ApplicantTechStack({
2223
applicant.tech_stacks.length > 0 &&
2324
!applicant.tech_stacks.includes("none") ? (
2425
<>
25-
{displayStacks?.map((stack) => (
26-
<div
27-
key={stack}
28-
className="group relative"
29-
title={stack}
30-
>
31-
<Image
32-
src={`/assets/svgs/techstack/icon-${stack.toLowerCase()}.svg`}
33-
alt={stack}
34-
width={24}
35-
height={24}
36-
className="h-6 w-6 transition-transform group-hover:scale-110"
37-
/>
38-
</div>
39-
))}
26+
{displayStacks?.map((stack) => {
27+
const iconName = getTechStackIconName(stack);
28+
return (
29+
<div
30+
key={stack}
31+
className="group relative"
32+
title={stack}
33+
>
34+
<Image
35+
src={`/assets/svgs/techstack/icon-${iconName}.svg`}
36+
alt={stack}
37+
width={24}
38+
height={24}
39+
className="h-6 w-6 transition-transform group-hover:scale-110"
40+
/>
41+
</div>
42+
);
43+
})}
4044
{hasMoreStacks && (
4145
<button
4246
onClick={() => setShowAll(!showAll)}

‎apps/codebility/app/home/applicants/loading.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default function ApplicantsLoading() {
3232
key={index}
3333
className="mx-auto my-4 flex flex-col items-center gap-10"
3434
>
35-
<Skeleton className="h-10 w-full" />
35+
<Skeleton className="h-16 w-full" />
3636
</div>
3737
))}
3838
</Box>

‎apps/codebility/app/home/my-team/[projectId]/actions/attendance-warnings.ts‎

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ export async function checkAttendanceWarnings(
6464
.eq("id", projectId)
6565
.single();
6666

67-
// Get all members of the project
67+
// Get all members of the project with their join dates
6868
const { data: members } = await supabase
6969
.from("project_members")
70-
.select("codev_id, codev(first_name, last_name, email_address)")
70+
.select("codev_id, created_at, codev(first_name, last_name, email_address)")
7171
.eq("project_id", projectId);
7272

7373
if (!members) {
@@ -89,17 +89,29 @@ export async function checkAttendanceWarnings(
8989
return { success: false, error: "No attendance records found" };
9090
}
9191

92-
// Count absences per member (excluding future dates and non-scheduled days)
92+
// Count absences per member (excluding future dates, non-scheduled days, and dates before joining)
9393
const today = new Date();
9494
today.setHours(0, 0, 0, 0); // Reset to start of day
9595
const meetingSchedule = projectData?.meeting_schedule || null;
96-
96+
97+
// Create a map of member join dates for quick lookup
98+
const memberJoinDates = new Map<string, Date>();
99+
members.forEach(member => {
100+
const joinDate = new Date(member.created_at);
101+
joinDate.setHours(0, 0, 0, 0);
102+
memberJoinDates.set(member.codev_id, joinDate);
103+
});
104+
97105
const absenceCounts = new Map<string, number>();
98106
attendance.forEach(record => {
99107
// Skip future dates
100108
const recordDate = new Date(record.date);
101109
if (recordDate > today) return;
102-
110+
111+
// Skip dates before member joined
112+
const memberJoinDate = memberJoinDates.get(record.codev_id);
113+
if (memberJoinDate && recordDate < memberJoinDate) return;
114+
103115
// Only count absences on scheduled meeting days
104116
if (isScheduledMeetingDay(recordDate, meetingSchedule) && record.status === "absent") {
105117
const current = absenceCounts.get(record.codev_id) || 0;
@@ -205,10 +217,10 @@ export async function getAttendanceWarningStatus(
205217
.eq("id", projectId)
206218
.single();
207219

208-
// Get all members
220+
// Get all members with their join dates
209221
const { data: members } = await supabase
210222
.from("project_members")
211-
.select("codev_id, codev(first_name, last_name)")
223+
.select("codev_id, created_at, codev(first_name, last_name)")
212224
.eq("project_id", projectId);
213225

214226
if (!members) {
@@ -226,18 +238,23 @@ export async function getAttendanceWarningStatus(
226238
.gte("date", startDate)
227239
.lte("date", endDate);
228240

229-
// Count absences per member (excluding future dates and non-scheduled days)
241+
// Count absences per member (excluding future dates, non-scheduled days, and dates before joining)
230242
const today = new Date();
231243
today.setHours(0, 0, 0, 0); // Reset to start of day
232244
const meetingSchedule = projectData?.meeting_schedule || null;
233-
245+
234246
const memberStatus = members.map(member => {
247+
const memberJoinDate = new Date(member.created_at);
248+
memberJoinDate.setHours(0, 0, 0, 0);
249+
235250
const memberAttendance = attendance?.filter(a => a.codev_id === member.codev_id) || [];
236-
237-
// Filter out future dates and non-scheduled days
251+
252+
// Filter out future dates, non-scheduled days, and dates before member joined
238253
const scheduledMeetingAttendance = memberAttendance.filter(a => {
239254
const recordDate = new Date(a.date);
240-
return recordDate <= today && isScheduledMeetingDay(recordDate, meetingSchedule);
255+
return recordDate <= today &&
256+
recordDate >= memberJoinDate &&
257+
isScheduledMeetingDay(recordDate, meetingSchedule);
241258
});
242259

243260
const absences = scheduledMeetingAttendance.filter(a => a.status === "absent").length;

‎apps/codebility/app/home/settings/page.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type PermissionKey = keyof RolePermissions;
3232
// Hidden from: Codev (10), Intern (4), HR (2), and all other non-admin roles
3333
const ADMIN_ONLY_PATHS = [
3434
"/home/settings/news-banners",
35+
"/home/settings/surveys",
3536
"/home/settings/services",
3637
];
3738

0 commit comments

Comments
 (0)