Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/lib/airtable/airtable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface ApprenticeRecord {
id: string;
name: string;
email: string;
cohortId: string | null; // Record ID of cohort
cohortIds: string[]; // Record IDs of cohorts (apprentice may belong to multiple)
}

export interface Cohort {
Expand Down Expand Up @@ -199,15 +199,15 @@ export function createAirtableClient(apiKey: string, baseId: string) {
}

/**
* Check if email exists in Apprentices table
* Check if email exists in Apprentices table (case-insensitive)
* Note: filterByFormula requires field NAME "Learner email" - this would break if renamed in Airtable
*/
async function findApprenticeByEmail(email: string): Promise<boolean> {
const apprenticesTable = base(TABLES.APPRENTICES);

const apprenticeRecords = await apprenticesTable
.select({
filterByFormula: `{Learner email} = "${email}"`,
filterByFormula: `LOWER({Learner email}) = LOWER("${email}")`,
maxRecords: 1,
returnFieldsByFieldId: true,
})
Expand All @@ -224,7 +224,7 @@ export function createAirtableClient(apiKey: string, baseId: string) {

const records = await apprenticesTable
.select({
filterByFormula: `{Learner email} = "${email}"`,
filterByFormula: `LOWER({Learner email}) = LOWER("${email}")`,
maxRecords: 1,
returnFieldsByFieldId: true,
})
Expand All @@ -242,7 +242,7 @@ export function createAirtableClient(apiKey: string, baseId: string) {
id: record.id,
name: record.get(APPRENTICE_FIELDS.NAME) as string,
email: emailLookup?.[0] ?? email,
cohortId: cohortLink?.[0] ?? null,
cohortIds: cohortLink ?? [],
};
}

Expand Down Expand Up @@ -332,7 +332,7 @@ export function createAirtableClient(apiKey: string, baseId: string) {
id: record.id,
name: record.get(APPRENTICE_FIELDS.NAME) as string,
email: emailLookup?.[0] ?? '',
cohortId: cohortLink?.[0] ?? null,
cohortIds: cohortLink ?? [],
};
});
}
Expand Down Expand Up @@ -362,7 +362,7 @@ export function createAirtableClient(apiKey: string, baseId: string) {
id: record.id,
name: record.get(APPRENTICE_FIELDS.NAME) as string,
email: emailLookup?.[0] ?? '',
cohortId: cohortLink?.[0] ?? null,
cohortIds: cohortLink ?? [],
};
});
}
Expand Down
34 changes: 18 additions & 16 deletions src/lib/airtable/attendance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,20 +397,21 @@ export function createAttendanceClient(apiKey: string, baseId: string) {
// ============================================

/**
* Get events for a specific cohort, optionally filtered by date range
* Get events for cohorts, optionally filtered by date range
* This is THE source of truth for which events count toward an apprentice's stats
* Accepts multiple cohort IDs - returns events that match ANY of the cohorts
*/
function getEventsForCohort(
function getEventsForCohorts(
allEvents: EventForStats[],
cohortId: string | null,
cohortIds: string[],
options?: { startDate?: Date; endDate?: Date },
): EventForStats[] {
// Filter by cohort (if no cohort, return empty - apprentice must belong to a cohort)
if (!cohortId) {
// Filter by cohorts (if no cohorts, return empty - apprentice must belong to at least one cohort)
if (cohortIds.length <= 0) {
return [];
}

let events = allEvents.filter(e => e.cohortIds.includes(cohortId));
let events = allEvents.filter(e => e.cohortIds.some(c => cohortIds.includes(c)));

// Filter by date range if provided
if (options?.startDate && options?.endDate) {
Expand Down Expand Up @@ -475,14 +476,15 @@ export function createAttendanceClient(apiKey: string, baseId: string) {
const apprentice = apprenticeRecords[0];
const apprenticeName = apprentice.get(APPRENTICE_FIELDS.NAME) as string;
const cohortLink = apprentice.get(APPRENTICE_FIELDS.COHORT) as string[] | undefined;
const cohortId = cohortLink?.[0] ?? null;
const cohortIds = cohortLink ?? [];
const primaryCohortId = cohortIds[0] ?? null; // For display purposes

// Get cohort name if available
// Get primary cohort name if available (for display)
let cohortName: string | null = null;
if (cohortId) {
if (primaryCohortId) {
const cohortRecords = await cohortsTable
.select({
filterByFormula: `RECORD_ID() = "${cohortId}"`,
filterByFormula: `RECORD_ID() = "${primaryCohortId}"`,
maxRecords: 1,
returnFieldsByFieldId: true,
})
Expand All @@ -492,9 +494,9 @@ export function createAttendanceClient(apiKey: string, baseId: string) {
}
}

// Get events for this cohort (with optional date filter)
// Get events for all of the apprentice's cohorts (with optional date filter)
const allEvents = await getAllEvents();
const relevantEvents = getEventsForCohort(allEvents, cohortId, options);
const relevantEvents = getEventsForCohorts(allEvents, cohortIds, options);
const relevantEventIds = new Set(relevantEvents.map(e => e.id));

// Get attendance for this apprentice, filtered to cohort events only
Expand Down Expand Up @@ -542,7 +544,7 @@ export function createAttendanceClient(apiKey: string, baseId: string) {
...stats,
apprenticeId,
apprenticeName,
cohortId,
cohortId: primaryCohortId,
cohortName,
trend: calculateTrend(recentRate, previousRate),
};
Expand Down Expand Up @@ -827,11 +829,11 @@ export function createAttendanceClient(apiKey: string, baseId: string) {

const apprentice = apprenticeRecords[0];
const cohortLink = apprentice.get(APPRENTICE_FIELDS.COHORT) as string[] | undefined;
const cohortId = cohortLink?.[0] ?? null;
const cohortIds = cohortLink ?? [];

// Get events for this cohort (with optional date filter)
// Get events for all of the apprentice's cohorts (with optional date filter)
const allEvents = await getAllEvents();
const relevantEvents = getEventsForCohort(allEvents, cohortId, options);
const relevantEvents = getEventsForCohorts(allEvents, cohortIds, options);
const relevantEventIds = new Set(relevantEvents.map(e => e.id));

// Get attendance for this apprentice, filtered to cohort events only
Expand Down
4 changes: 2 additions & 2 deletions src/routes/api/checkin/events/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export const GET: RequestHandler = async ({ cookies }) => {
const allEvents = await listEvents({ startDate: today.toISOString() });

if (apprentice) {
// User has apprentice record: show cohort + public events
// User has apprentice record: show events from any of their cohorts + public events
const relevantEvents = allEvents.filter(
event => event.isPublic || (apprentice.cohortId && event.cohortIds.includes(apprentice.cohortId)),
event => event.isPublic || event.cohortIds.some(c => apprentice.cohortIds.includes(c)),
);

// Check attendance status for each event
Expand Down
6 changes: 3 additions & 3 deletions src/routes/checkin/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ export const load: PageServerLoad = async ({ locals }) => {

// Filter events based on user type
let availableEvents;
if (apprentice?.cohortId) {
// User with apprentice record: show cohort events + public events
if (apprentice?.cohortIds.length) {
// User with apprentice record: show events from any of their cohorts + public events
availableEvents = allEvents.filter(
event => event.cohortIds.includes(apprentice.cohortId!) || event.isPublic,
event => event.cohortIds.some(c => apprentice.cohortIds.includes(c)) || event.isPublic,
);
}
else {
Expand Down