Skip to content

Commit 9fb7e29

Browse files
Merge pull request #20 from foundersandcoders/feature/ap-30-external-staff-access-management-via-email-and-name
External staff access management via email and name (AP-30)
2 parents 487bf5a + 54882eb commit 9fb7e29

23 files changed

+1475
-403
lines changed

.claude/hooks/plan-iterator.sh

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,34 @@ IFS=$'\t' read -r REMAINING COMPLETED NEXT_TASK LAST_DONE TO_COMPLETE < <(
8282
}
8383
8484
# Main task: incomplete (strict [ ])
85-
match($0, /^([0-9]+)\.[[:space:]]*\[ \][[:space:]]*(.*)$/, m) {
86-
n = m[1]
85+
/^[0-9]+\.[[:space:]]*\[ \][[:space:]]*/ {
86+
n = $0
87+
sub(/\..*$/, "", n)
88+
text = $0
89+
sub(/^[0-9]+\.[[:space:]]*\[ \][[:space:]]*/, "", text)
8790
main_state[n] = "incomplete"
88-
main_text[n] = trim(m[2])
91+
main_text[n] = trim(text)
8992
next
9093
}
9194
9295
# Main task: complete (strict [x])
93-
match($0, /^([0-9]+)\.[[:space:]]*\[x\][[:space:]]*(.*)$/, m) {
94-
n = m[1]
96+
/^[0-9]+\.[[:space:]]*\[x\][[:space:]]*/ {
97+
n = $0
98+
sub(/\..*$/, "", n)
99+
text = $0
100+
sub(/^[0-9]+\.[[:space:]]*\[x\][[:space:]]*/, "", text)
95101
main_state[n] = "complete"
96-
main_text[n] = trim(m[2])
102+
main_text[n] = trim(text)
97103
next
98104
}
99105
100106
# Subtask: strict [ ] or [x], and requires a leading main number like 1.1, 2.3 etc
101-
match($0, /^[[:space:]]*-[[:space:]]*\[([ x])\][[:space:]]*([0-9]+)\.[0-9]+[[:space:]]*(.*)$/, m) {
102-
status = m[1]
103-
n = m[2]
107+
/^[[:space:]]*-[[:space:]]*\[[ x]\][[:space:]]*[0-9]+\.[0-9]+[[:space:]]*/ {
108+
status = ($0 ~ /^[[:space:]]*-[[:space:]]*\[x\]/) ? "x" : " "
109+
tmp = $0
110+
sub(/^[[:space:]]*-[[:space:]]*\[[ x]\][[:space:]]*/, "", tmp)
111+
n = tmp
112+
sub(/\..*$/, "", n)
104113
sub_any[n] = 1
105114
106115
if (status == "x") {
@@ -168,11 +177,32 @@ if [[ -n "${TO_COMPLETE:-}" ]]; then
168177
169178
BEGIN { remaining=0; completed=0; next_task=""; last_done="" }
170179
171-
match($0, /^([0-9]+)\.[[:space:]]*\[ \][[:space:]]*(.*)$/, m) { main_state[m[1]]="incomplete"; main_text[m[1]]=trim(m[2]); next }
172-
match($0, /^([0-9]+)\.[[:space:]]*\[x\][[:space:]]*(.*)$/, m) { main_state[m[1]]="complete"; main_text[m[1]]=trim(m[2]); next }
180+
/^[0-9]+\.[[:space:]]*\[ \][[:space:]]*/ {
181+
n = $0
182+
sub(/\..*$/, "", n)
183+
text = $0
184+
sub(/^[0-9]+\.[[:space:]]*\[ \][[:space:]]*/, "", text)
185+
main_state[n]="incomplete"
186+
main_text[n]=trim(text)
187+
next
188+
}
189+
/^[0-9]+\.[[:space:]]*\[x\][[:space:]]*/ {
190+
n = $0
191+
sub(/\..*$/, "", n)
192+
text = $0
193+
sub(/^[0-9]+\.[[:space:]]*\[x\][[:space:]]*/, "", text)
194+
main_state[n]="complete"
195+
main_text[n]=trim(text)
196+
next
197+
}
173198
174-
match($0, /^[[:space:]]*-[[:space:]]*\[([ x])\][[:space:]]*([0-9]+)\.[0-9]+[[:space:]]*(.*)$/, m) {
175-
status=m[1]; n=m[2]; sub_any[n]=1
199+
/^[[:space:]]*-[[:space:]]*\[[ x]\][[:space:]]*[0-9]+\.[0-9]+[[:space:]]*/ {
200+
status = ($0 ~ /^[[:space:]]*-[[:space:]]*\[x\]/) ? "x" : " "
201+
tmp = $0
202+
sub(/^[[:space:]]*-[[:space:]]*\[[ x]\][[:space:]]*/, "", tmp)
203+
n = tmp
204+
sub(/\..*$/, "", n)
205+
sub_any[n]=1
176206
if (status=="x") { completed++; last_done=after_checkbox($0) }
177207
else { remaining++; if (next_task=="") next_task=after_checkbox($0) }
178208
next

README.md

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ The app uses **magic link authentication** with a single login page for all user
2828
|------|-------------------|--------------|
2929
| Staff | Staff table (collaborator email) | `/admin` |
3030
| Student | Apprentices table (learner email) | `/checkin` |
31+
| External | Staff table (external access fields) | `/admin` |
3132

3233
### How It Works
3334

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

4041
### Route Protection
4142

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

68+
### Adding External Staff Access
69+
70+
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.
71+
72+
To grant external staff access:
73+
74+
1. **In the Staff - Apprentice Pulse table**, find any existing staff record (or create a dummy one)
75+
2. **Add the external person's email** to the `Attendace access` field (this field can contain multiple emails, one per line)
76+
3. **Add their name** to the `Name - Attendance access` field (this should match the order of emails in step 2)
77+
78+
**Example setup:**
79+
- `Attendace access` field: `[email protected]`
80+
- `Name - Attendance access` field: `External Trainer`
81+
82+
**How it works:**
83+
- External staff can log in at `/login` using their email
84+
- They receive the same magic link authentication as regular staff
85+
- They have **full admin access** - same permissions as regular staff members
86+
- No Airtable workspace collaboration required
87+
88+
**Multiple external users:**
89+
You can add multiple external users to the same staff record by putting each email on a new line:
90+
- `Attendace access` field:
91+
```
92+
93+
94+
```
95+
- `Name - Attendance access` field:
96+
```
97+
External Trainer 1
98+
External Trainer 2
99+
```
100+
67101
### Staff Who Are Also Apprentices
68102

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

194228
### Default Values
195229

196-
Default values used in forms are stored in `src/lib/airtable/config.ts` under `DEFAULTS`:
197-
198-
| Key | Description |
199-
|-----|-------------|
200-
| `SURVEY_URL` | Default survey URL pre-filled when creating events |
201-
202-
To change the default survey URL, update `DEFAULTS.SURVEY_URL` in the config file.
230+
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.

docs/plan-search-apprentice.md

Lines changed: 0 additions & 204 deletions
This file was deleted.

docs/scratchpad.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
add externals by email as staff
2-
are we sure we want to give access to airtable?
3-
4-
5-
6-
7-
81
attendace send email
92

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

1710

1811

19-
20-
2112
Show mii plaza
2213

2314
Integration with LUMA
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Airtable Schema
2+
3+
## Learners / Staff - Apprentice pulse
4+
5+
Table ID: `tblJjn62ExE1LVjmx`
6+
7+
| Field | ID | Type |
8+
|-------|-----|------|
9+
| Id | `fldbTKP32s3Soev91` | autoNumber |
10+
| Staff Member | `fldHEHhQInmSdipn8` | singleCollaborator |
11+
| Apprentice Link | `fldAMwe9jOOdwIyBY` | multipleRecordLinks |
12+
| Learner email (from Apprentice Link) | `fldPjDZTSySzbefXz` | multipleLookupValues |
13+
| Attendace access | `fldsR6gvnOsSEhfh9` | email |
14+
| Name - Attendance access | `fld5Z9dl265e22TPQ` | singleLineText |
15+

0 commit comments

Comments
 (0)