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
56 changes: 43 additions & 13 deletions .claude/hooks/plan-iterator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,34 @@ IFS=$'\t' read -r REMAINING COMPLETED NEXT_TASK LAST_DONE TO_COMPLETE < <(
}

# Main task: incomplete (strict [ ])
match($0, /^([0-9]+)\.[[:space:]]*\[ \][[:space:]]*(.*)$/, m) {
n = m[1]
/^[0-9]+\.[[:space:]]*\[ \][[:space:]]*/ {
n = $0
sub(/\..*$/, "", n)
text = $0
sub(/^[0-9]+\.[[:space:]]*\[ \][[:space:]]*/, "", text)
main_state[n] = "incomplete"
main_text[n] = trim(m[2])
main_text[n] = trim(text)
next
}

# Main task: complete (strict [x])
match($0, /^([0-9]+)\.[[:space:]]*\[x\][[:space:]]*(.*)$/, m) {
n = m[1]
/^[0-9]+\.[[:space:]]*\[x\][[:space:]]*/ {
n = $0
sub(/\..*$/, "", n)
text = $0
sub(/^[0-9]+\.[[:space:]]*\[x\][[:space:]]*/, "", text)
main_state[n] = "complete"
main_text[n] = trim(m[2])
main_text[n] = trim(text)
next
}

# Subtask: strict [ ] or [x], and requires a leading main number like 1.1, 2.3 etc
match($0, /^[[:space:]]*-[[:space:]]*\[([ x])\][[:space:]]*([0-9]+)\.[0-9]+[[:space:]]*(.*)$/, m) {
status = m[1]
n = m[2]
/^[[:space:]]*-[[:space:]]*\[[ x]\][[:space:]]*[0-9]+\.[0-9]+[[:space:]]*/ {
status = ($0 ~ /^[[:space:]]*-[[:space:]]*\[x\]/) ? "x" : " "
tmp = $0
sub(/^[[:space:]]*-[[:space:]]*\[[ x]\][[:space:]]*/, "", tmp)
n = tmp
sub(/\..*$/, "", n)
sub_any[n] = 1

if (status == "x") {
Expand Down Expand Up @@ -168,11 +177,32 @@ if [[ -n "${TO_COMPLETE:-}" ]]; then

BEGIN { remaining=0; completed=0; next_task=""; last_done="" }

match($0, /^([0-9]+)\.[[:space:]]*\[ \][[:space:]]*(.*)$/, m) { main_state[m[1]]="incomplete"; main_text[m[1]]=trim(m[2]); next }
match($0, /^([0-9]+)\.[[:space:]]*\[x\][[:space:]]*(.*)$/, m) { main_state[m[1]]="complete"; main_text[m[1]]=trim(m[2]); next }
/^[0-9]+\.[[:space:]]*\[ \][[:space:]]*/ {
n = $0
sub(/\..*$/, "", n)
text = $0
sub(/^[0-9]+\.[[:space:]]*\[ \][[:space:]]*/, "", text)
main_state[n]="incomplete"
main_text[n]=trim(text)
next
}
/^[0-9]+\.[[:space:]]*\[x\][[:space:]]*/ {
n = $0
sub(/\..*$/, "", n)
text = $0
sub(/^[0-9]+\.[[:space:]]*\[x\][[:space:]]*/, "", text)
main_state[n]="complete"
main_text[n]=trim(text)
next
}

match($0, /^[[:space:]]*-[[:space:]]*\[([ x])\][[:space:]]*([0-9]+)\.[0-9]+[[:space:]]*(.*)$/, m) {
status=m[1]; n=m[2]; sub_any[n]=1
/^[[:space:]]*-[[:space:]]*\[[ x]\][[:space:]]*[0-9]+\.[0-9]+[[:space:]]*/ {
status = ($0 ~ /^[[:space:]]*-[[:space:]]*\[x\]/) ? "x" : " "
tmp = $0
sub(/^[[:space:]]*-[[:space:]]*\[[ x]\][[:space:]]*/, "", tmp)
n = tmp
sub(/\..*$/, "", n)
sub_any[n]=1
if (status=="x") { completed++; last_done=after_checkbox($0) }
else { remaining++; if (next_task=="") next_task=after_checkbox($0) }
next
Expand Down
46 changes: 37 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ The app uses **magic link authentication** with a single login page for all user
|------|-------------------|--------------|
| Staff | Staff table (collaborator email) | `/admin` |
| Student | Apprentices table (learner email) | `/checkin` |
| External | Staff table (external access fields) | `/admin` |

### How It Works

1. User enters email at `/login`
2. Server checks Staff table first, then Apprentices table
2. Server checks Staff table first, then external access fields, then Apprentices table
3. JWT token generated (15-minute expiry) and emailed via Resend
4. User clicks link → token verified → session cookie set (90-day expiry)
5. User redirected based on type: staff → `/admin`, students → `/checkin`
5. User redirected based on type: staff/external → `/admin`, students → `/checkin`

### Route Protection

Expand Down Expand Up @@ -64,6 +65,39 @@ See [Staff Who Are Also Apprentices](#staff-who-are-also-apprentices) for setup
2. Add a record in the **Staff - Apprentice Pulse** table, selecting their collaborator profile
3. They can now log in at `/login` using their collaborator email

### Adding External Staff Access

External staff are people who need login access to view attendance data but are not regular staff members or apprentices. Examples include external trainers, consultants, or partner organization staff.

To grant external staff access:

1. **In the Staff - Apprentice Pulse table**, find any existing staff record (or create a dummy one)
2. **Add the external person's email** to the `Attendace access` field (this field can contain multiple emails, one per line)
3. **Add their name** to the `Name - Attendance access` field (this should match the order of emails in step 2)

**Example setup:**
- `Attendace access` field: `external.trainer@company.com`
- `Name - Attendance access` field: `External Trainer`

**How it works:**
- External staff can log in at `/login` using their email
- They receive the same magic link authentication as regular staff
- They have **full admin access** - same permissions as regular staff members
- No Airtable workspace collaboration required

**Multiple external users:**
You can add multiple external users to the same staff record by putting each email on a new line:
- `Attendace access` field:
```
trainer1@company.com
trainer2@company.com
```
- `Name - Attendance access` field:
```
External Trainer 1
External Trainer 2
```

### Staff Who Are Also Apprentices

Some staff members may also be apprentices (e.g., apprentice coaches). These users need to:
Expand Down Expand Up @@ -193,10 +227,4 @@ Event types are cached for performance (5-minute cache) and include automatic co

### Default Values

Default values used in forms are stored in `src/lib/airtable/config.ts` under `DEFAULTS`:

| Key | Description |
|-----|-------------|
| `SURVEY_URL` | Default survey URL pre-filled when creating events |

To change the default survey URL, update `DEFAULTS.SURVEY_URL` in the config file.
Default values for event creation are now managed dynamically through Airtable's "Event types - Apprentice Pulse" table. Each event type can have its own default survey URL, providing more flexibility than hardcoded values.
204 changes: 0 additions & 204 deletions docs/plan-search-apprentice.md

This file was deleted.

9 changes: 0 additions & 9 deletions docs/scratchpad.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
add externals by email as staff
are we sure we want to give access to airtable?





attendace send email

survey send email
Expand All @@ -16,8 +9,6 @@ Per week email to Jess. Absent, not survey, whoever marked "In need of support"





Show mii plaza

Integration with LUMA
Expand Down
15 changes: 15 additions & 0 deletions scripts/schema-2026-01-08-15-11-42.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Airtable Schema

## Learners / Staff - Apprentice pulse

Table ID: `tblJjn62ExE1LVjmx`

| Field | ID | Type |
|-------|-----|------|
| Id | `fldbTKP32s3Soev91` | autoNumber |
| Staff Member | `fldHEHhQInmSdipn8` | singleCollaborator |
| Apprentice Link | `fldAMwe9jOOdwIyBY` | multipleRecordLinks |
| Learner email (from Apprentice Link) | `fldPjDZTSySzbefXz` | multipleLookupValues |
| Attendace access | `fldsR6gvnOsSEhfh9` | email |
| Name - Attendance access | `fld5Z9dl265e22TPQ` | singleLineText |

Loading