diff --git a/docs/schema.md b/docs/schema.md index 364dbfd..e43cee0 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -65,6 +65,8 @@ Attendance tracking using a junction table pattern (one record per apprentice pe | Status | `fldew45fDGpgl1aRr` | singleSelect | Attendance status | | Date Time (from Event) | `fldokfSk68MhJGlm6` | multipleLookupValues | Event date/time lookup | | FAC Cohort (from Event) | `fldkc9zLJe7NZVAz1` | multipleLookupValues | Cohort lookup from Event | +| External Name | `fldIhZnMxfjh9ps78` | singleLineText | Name for non-registered attendees | +| External Email | `fldHREfpkx1bGv3K3` | email | Email for non-registered attendees | --- @@ -221,6 +223,8 @@ Scheduled events/sessions for attendance tracking. | Survey | `fld9XBHnCWBtZiZah` | url | Optional survey form URL | | Attendance | `fldcPf53fVfStFZsa` | multipleRecordLinks | Linked attendance records | | Name - Date | `fld7POykodV0LGsg1` | formula | Display name with date | +| Public | `fldatQzdAo8evWlNc` | checkbox | Visible on public check-in page | +| Check-in Code | `fldKMWSFmYONkvYMK` | number | 4-digit code for external attendees | --- diff --git a/scripts/schema-2025-12-29-13-05-09.md b/scripts/schema-2025-12-29-13-05-09.md new file mode 100644 index 0000000..c417235 --- /dev/null +++ b/scripts/schema-2025-12-29-13-05-09.md @@ -0,0 +1,37 @@ +# Airtable Schema + +## Learners / Attendace - Apprentice Pulse + +Table ID: `tblkDbhJcuT9TTwFc` + +| Field | ID | Type | +|-------|-----|------| +| Id | `fldGdpuw6SoHkQbOs` | autoNumber | +| Apprentice | `fldOyo3hlj9Ht0rfZ` | multipleRecordLinks | +| Cohort | `fldn53kWDE8GHg2Yy` | multipleLookupValues | +| Checkin Time | `fldvXHPmoLlEA8EuN` | dateTime | +| Status | `fldew45fDGpgl1aRr` | singleSelect | +| Event | `fldiHd75LYtopwyN9` | multipleRecordLinks | +| Date Time (from Event) | `fldokfSk68MhJGlm6` | multipleLookupValues | +| FAC Cohort (from FAC Cohort) (from Event) | `fldE783vnY3SLjmh7` | multipleLookupValues | +| FAC Cohort (from Event) | `fldkc9zLJe7NZVAz1` | multipleLookupValues | +| External Name | `fldIhZnMxfjh9ps78` | singleLineText | +| External Email | `fldHREfpkx1bGv3K3` | email | + +## Learners / Events - Apprentice Pulse + +Table ID: `tblkbskw4fuTq0E9p` + +| Field | ID | Type | +|-------|-----|------| +| Name | `fldMCZijN6TJeUdFR` | singleLineText | +| FAC Cohort | `fldcXDEDkeHvWTnxE` | multipleRecordLinks | +| FAC Cohort (from FAC Cohort) | `fldsLUl1MrhhsVBe7` | multipleLookupValues | +| Date Time | `fld8AkM3EanzZa5QX` | dateTime | +| Survey | `fld9XBHnCWBtZiZah` | url | +| Select | `fldo7fwAsFhkA1icC` | singleSelect | +| Attendace - Apprentice Pulse | `fldcPf53fVfStFZsa` | multipleRecordLinks | +| Name - Date | `fld7POykodV0LGsg1` | formula | +| Public | `fldatQzdAo8evWlNc` | checkbox | +| Number | `fldKMWSFmYONkvYMK` | number | + diff --git a/src/lib/airtable/config.ts b/src/lib/airtable/config.ts index 5e40b29..04f2b39 100644 --- a/src/lib/airtable/config.ts +++ b/src/lib/airtable/config.ts @@ -39,6 +39,8 @@ export const EVENT_FIELDS = { SURVEY: 'fld9XBHnCWBtZiZah', // url (optional survey form) ATTENDANCE: 'fldcPf53fVfStFZsa', // multipleRecordLinks to Attendance (reverse link) NAME_DATE: 'fld7POykodV0LGsg1', // formula (display name) + PUBLIC: 'fldatQzdAo8evWlNc', // checkbox (visible to all on check-in page) + CHECK_IN_CODE: 'fldKMWSFmYONkvYMK', // number (4-digit code for external attendees) } as const; // Fields - Attendance @@ -48,4 +50,6 @@ export const ATTENDANCE_FIELDS = { EVENT: 'fldiHd75LYtopwyN9', // multipleRecordLinks to Events CHECKIN_TIME: 'fldvXHPmoLlEA8EuN', // dateTime STATUS: 'fldew45fDGpgl1aRr', // singleSelect (Present/Absent/Late) + EXTERNAL_NAME: 'fldIhZnMxfjh9ps78', // singleLineText (for non-registered attendees) + EXTERNAL_EMAIL: 'fldHREfpkx1bGv3K3', // email (for non-registered attendees) } as const; diff --git a/src/lib/airtable/events.spec.ts b/src/lib/airtable/events.spec.ts index a46c1f9..393c19e 100644 --- a/src/lib/airtable/events.spec.ts +++ b/src/lib/airtable/events.spec.ts @@ -72,6 +72,8 @@ describe('events', () => { cohortId: 'recCohort1', eventType: 'Regular class', surveyUrl: 'https://survey.example.com', + isPublic: false, + checkInCode: undefined, }); }); }); @@ -103,6 +105,8 @@ describe('events', () => { cohortId: 'recCohort2', eventType: 'Workshop', surveyUrl: undefined, + isPublic: false, + checkInCode: undefined, }); }); @@ -133,6 +137,8 @@ describe('events', () => { expect(event).toEqual({ id: 'recNew123', ...input, + isPublic: false, + checkInCode: undefined, }); expect(mockTable.create).toHaveBeenCalled(); }); diff --git a/src/lib/airtable/events.ts b/src/lib/airtable/events.ts index 5fc643d..1107296 100644 --- a/src/lib/airtable/events.ts +++ b/src/lib/airtable/events.ts @@ -44,6 +44,8 @@ export function createEventsClient(apiKey: string, baseId: string) { cohortId: cohortLookup?.[0] ?? '', eventType: record.get(EVENT_FIELDS.EVENT_TYPE) as EventType, surveyUrl: record.get(EVENT_FIELDS.SURVEY) as string | undefined, + isPublic: (record.get(EVENT_FIELDS.PUBLIC) as boolean) ?? false, + checkInCode: record.get(EVENT_FIELDS.CHECK_IN_CODE) as number | undefined, }; }); } @@ -62,6 +64,8 @@ export function createEventsClient(apiKey: string, baseId: string) { cohortId: cohortLookup?.[0] ?? '', eventType: record.get(EVENT_FIELDS.EVENT_TYPE) as EventType, surveyUrl: record.get(EVENT_FIELDS.SURVEY) as string | undefined, + isPublic: (record.get(EVENT_FIELDS.PUBLIC) as boolean) ?? false, + checkInCode: record.get(EVENT_FIELDS.CHECK_IN_CODE) as number | undefined, }; } catch { @@ -73,21 +77,28 @@ export function createEventsClient(apiKey: string, baseId: string) { * Create a new event */ async function createEvent(data: CreateEventInput): Promise { - const record = await eventsTable.create({ + const fields: Airtable.FieldSet = { [EVENT_FIELDS.NAME]: data.name, [EVENT_FIELDS.DATE_TIME]: data.dateTime, - [EVENT_FIELDS.COHORT]: [data.cohortId], [EVENT_FIELDS.EVENT_TYPE]: data.eventType, - [EVENT_FIELDS.SURVEY]: data.surveyUrl, - }); + }; + + if (data.cohortId) fields[EVENT_FIELDS.COHORT] = [data.cohortId]; + if (data.surveyUrl) fields[EVENT_FIELDS.SURVEY] = data.surveyUrl; + if (data.isPublic !== undefined) fields[EVENT_FIELDS.PUBLIC] = data.isPublic; + if (data.checkInCode !== undefined) fields[EVENT_FIELDS.CHECK_IN_CODE] = data.checkInCode; + + const record = await eventsTable.create(fields); return { id: record.id, name: data.name, dateTime: data.dateTime, - cohortId: data.cohortId, + cohortId: data.cohortId ?? '', eventType: data.eventType, surveyUrl: data.surveyUrl, + isPublic: data.isPublic ?? false, + checkInCode: data.checkInCode, }; } @@ -102,6 +113,8 @@ export function createEventsClient(apiKey: string, baseId: string) { if (data.cohortId !== undefined) fields[EVENT_FIELDS.COHORT] = [data.cohortId]; if (data.eventType !== undefined) fields[EVENT_FIELDS.EVENT_TYPE] = data.eventType; if (data.surveyUrl !== undefined) fields[EVENT_FIELDS.SURVEY] = data.surveyUrl; + if (data.isPublic !== undefined) fields[EVENT_FIELDS.PUBLIC] = data.isPublic; + if (data.checkInCode !== undefined) fields[EVENT_FIELDS.CHECK_IN_CODE] = data.checkInCode; const record = await eventsTable.update(id, fields); const cohortLookup = record.get(EVENT_FIELDS.COHORT) as string[] | undefined; @@ -113,6 +126,8 @@ export function createEventsClient(apiKey: string, baseId: string) { cohortId: cohortLookup?.[0] ?? '', eventType: record.get(EVENT_FIELDS.EVENT_TYPE) as EventType, surveyUrl: record.get(EVENT_FIELDS.SURVEY) as string | undefined, + isPublic: (record.get(EVENT_FIELDS.PUBLIC) as boolean) ?? false, + checkInCode: record.get(EVENT_FIELDS.CHECK_IN_CODE) as number | undefined, }; } diff --git a/src/lib/types/event.ts b/src/lib/types/event.ts index bc9c6a4..6ecc58e 100644 --- a/src/lib/types/event.ts +++ b/src/lib/types/event.ts @@ -8,14 +8,18 @@ export interface Event { cohortName?: string; eventType: EventType; surveyUrl?: string; + isPublic: boolean; + checkInCode?: number; } export interface CreateEventInput { name: string; dateTime: string; - cohortId: string; + cohortId?: string; eventType: EventType; surveyUrl?: string; + isPublic?: boolean; + checkInCode?: number; } export interface UpdateEventInput { @@ -24,6 +28,8 @@ export interface UpdateEventInput { cohortId?: string; eventType?: EventType; surveyUrl?: string; + isPublic?: boolean; + checkInCode?: number; } export interface EventFilters { diff --git a/src/routes/admin/events/+page.svelte b/src/routes/admin/events/+page.svelte index 69c8323..f71e562 100644 --- a/src/routes/admin/events/+page.svelte +++ b/src/routes/admin/events/+page.svelte @@ -26,11 +26,13 @@ function handleCohortFilter(event: Event) { const select = event.target as HTMLSelectElement; const cohortId = select.value; + const baseUrl = resolve('/admin/events'); if (cohortId) { - goto(resolve(`/admin/events?cohort=${cohortId}`)); + // eslint-disable-next-line svelte/no-navigation-without-resolve -- query params require string concat + goto(baseUrl + '?cohort=' + cohortId); } else { - goto(resolve('/admin/events')); + goto(baseUrl); } }