- Introduction
- Pitch
- Hogwarts
- Live Demo
- MVP
- Roadmap
- Launch Sprint
- PRD
- Get Started
- Localhost
- Architecture
- Structure
- Pattern
- Page
- Layout
- Content
- Types
- Config
- Actions
- Queries
- Authorization
- Validation
- Form
- Table
- Detail
- Card
- Util
- Hooks
- List Params
- Views
- README.md
- ISSUE.md
- Technology Stack
- Database
- File
- CDN Assets
- Entry Points
- Dashboard
- Authentication
- Credentials
- OAuth
- Flow Diagrams
- Multi-Tenancy
- Offline
- Onboarding
- Onboarding Videos
- Add Values
- Admission
- Application
- Attendance
- Compliance
- Profile
- Exams
- Exam Wizard
- Timetable
- Classrooms
- Notifications
- Conference
- LMS (Lumos)
- Finance
- Fee Management
- Invoice
- Wallet
- Salary
- Payroll
- Timesheet
- Expenses
- Budget
- Receipt
- Accounts
- Banking
- Reports
- Dashboard
- Permissions
- Messages
- Integration Flow
- Provision
- AI Document Processing
- Document Intelligence
- Internationalization
- Translation
- Translation Guide
- Icons
- Docs Factory
- Inspiration
- Listings
- Teachers
- Students
- Catalog
- Library
- Contributing
- Code of conduct
- GitHub Workflow
- Database Seeds
- Database Safety
- Test Accounts
- Playwright
- Prettier
- Block Rebound
Teachers enter the system through five entry points, all unified under a single validation schema and wizard infrastructure. Incomplete records are clearly marked for completion.
Entry points
1. Admin Wizard → 7 steps, 35+ fields, draft flow
2. CSV Bulk Import → ~9 fields, marked incomplete
3. Internal Onboarding → ~15 fields, marked incomplete
4. Profile Self-Edit → Contact, qualifications, experience
5. Admin Detail Edit → Link to wizard at any step
Wizard steps
7-step wizard with draft flow. "Skip & Create" appears once required fields (name + email) are filled.
| # | Step | Route | Fields | Required |
|---|---|---|---|---|
| 1 | Photo | /photo | profilePhotoUrl | None |
| 2 | Information | /information | firstName, lastName, gender, birthDate | firstName, lastName |
| 3 | Contact | /contact | emailAddress, phoneNumbers[] | emailAddress |
| 4 | Employment | /employment | employeeId, joiningDate, employmentStatus, employmentType, contractStartDate, contractEndDate | None (defaults) |
| 5 | Qualifications | /qualifications | qualifications[] (type, name, institution, major, dates, license) | None |
| 6 | Experience | /experience | experiences[] (institution, position, dates, isCurrent) | None |
| 7 | Expertise | /expertise | subjectExpertise[] (subjectId, expertiseLevel) | None |
Routes
src/app/[lang]/s/[subdomain]/(school-dashboard)/(listings)/teachers/
add/
page.tsx # Creates draft, redirects to step 1
[id]/
photo/page.tsx
information/page.tsx
contact/page.tsx
employment/page.tsx
qualifications/page.tsx
experience/page.tsx
expertise/page.tsx
Draft lifecycle
1. Admin clicks "Add Teacher"
2. page.tsx creates draft: { firstName: "", emailAddress: "draft-xxx@draft.internal", wizardStep: "photo" }
3. Each step saves fields + advances wizardStep
4. Two completion paths:
a. FAST: "Skip & Create" on contact step (or later) → saves + completes + redirects
b. FULL: All 7 steps → final completes wizard
5. Both set wizardStep: null
6. Abandoned drafts deletable via deleteDraftTeacher()
Component structure
src/components/school-dashboard/listings/teachers/
├── content.tsx # Server — list page
├── table.tsx # Client — DataTable
├── columns.tsx # Client — incomplete badge
├── actions.ts # createTeacher, updateTeacher, deleteTeacher
├── queries.ts # buildTeacherWhere (supports includeDrafts)
├── validation.ts # Composed from wizard step schemas
├── config.ts # Enum constants
├── authorization.ts # RBAC
├── types.ts # TeacherDTO, TeacherRow, form step types
├── detail/content.tsx # Profile view with edit link
└── wizard/
├── actions.ts # createDraftTeacher, completeTeacherWizard, updateTeacherWizardStep
├── config.ts # Step config
├── photo/ # Step 1
├── information/ # Step 2
├── contact/ # Step 3
├── employment/ # Step 4
├── qualifications/ # Step 5
├── experience/ # Step 6
└── expertise/ # Step 7
Validation
validation.ts composes from wizard step schemas:
import { contactSchema } from "./wizard/contact/validation"
import { experienceItemSchema } from "./wizard/experience/validation"
import { expertiseItemSchema } from "./wizard/expertise/validation"
import { informationSchema } from "./wizard/information/validation"
import { qualificationSchema } from "./wizard/qualifications/validation"
export const teacherCreateSchema = informationSchema
.merge(contactSchema)
.extend({
// Employment fields (can't merge refined schemas)
employeeId: z.string().optional(),
joiningDate: z.coerce.date().optional(),
employmentStatus: z
.enum(["ACTIVE", "ON_LEAVE", "TERMINATED", "RETIRED"])
.default("ACTIVE"),
employmentType: z
.enum(["FULL_TIME", "PART_TIME", "CONTRACT", "SUBSTITUTE"])
.default("FULL_TIME"),
contractStartDate: z.coerce.date().optional(),
contractEndDate: z.coerce.date().optional(),
qualifications: z.array(qualificationSchema).optional().default([]),
experiences: z.array(experienceItemSchema).optional().default([]),
subjectExpertise: z.array(expertiseItemSchema).optional().default([]),
})
.refine(/* contract dates + birth/joining cross-validation */)Re-exported schemas for consumers: phoneNumberSchema (CSV / onboarding), qualificationSchema (CSV / onboarding), experienceSchema (onboarding), subjectExpertiseSchema (onboarding).
Entry point details
CSV bulk import
await tx.teacher.createMany({
data: chunk.map((r) => ({
schoolId,
firstName: parts[0],
lastName: parts.slice(1).join(" "),
emailAddress: r.validated.email,
wizardStep: "employment", // CSV covers name + contact only
})),
})CSV-imported teachers appear in the "Incomplete" tab. Click to open wizard at the employment step.
Internal onboarding
When a teacher joins through the school's join page, onboarding creates:
- Teacher with personal + contact +
wizardStep: "employment" - TeacherPhoneNumber (if phone provided)
- TeacherQualification (if qualification provided)
- TeacherSubjectExpertise (for selected subjects)
Profile self-edit
Teachers can edit contact, qualifications, experience. Cannot edit personal info, employment, subject expertise (admin only).
Admin detail edit
Detail view's "Edit" links to /{lang}/teachers/add/{teacherId}/information — wizard pre-populated.
Incomplete records
| Source | wizardStep | Fields covered |
|---|---|---|
| Wizard (in progress) | Current step | All steps up to current |
| CSV import | "employment" | Name, email, phone, department |
| Internal onboarding | "employment" | Name, email, phone, qualification, subjects |
Main list filters wizardStep: null by default. Toggle "Show Incomplete" or use status filter to see drafts. Incomplete records show amber "Incomplete" badge. Action dropdown shows "Complete Profile" linking to wizard at the incomplete step.
buildTeacherWhere(schoolId, { includeDrafts: true })Config constants
| Constant | Values |
|---|---|
GENDER_OPTIONS | Male, Female |
EMPLOYMENT_STATUS_OPTIONS | Active, On Leave, Terminated, Retired |
EMPLOYMENT_TYPE_OPTIONS | Full-Time, Part-Time, Contract, Substitute |
QUALIFICATION_TYPE_OPTIONS | Degree, Certification, License |
EXPERTISE_LEVEL_OPTIONS | Primary, Secondary, Certified |
CLASS_TEACHER_ROLE_OPTIONS | Primary Teacher, Co-Teacher, Assistant |
Key files
| File | Purpose |
|---|---|
teachers/add/page.tsx | Creates draft + redirects to wizard |
teachers/wizard/actions.ts | createDraftTeacher, completeTeacherWizard, deleteDraftTeacher |
teachers/wizard/config.ts | Step config with skipToComplete |
teachers/validation.ts | Composed schema |
teachers/queries.ts | buildTeacherWhere with includeDrafts |
teachers/columns.tsx | Incomplete badge + "Complete Profile" action |
internal-onboarding/actions.ts | Onboarding teacher creation with subjects + wizardStep |
file/import/csv-import.ts | Bulk import |
profile/edit-role-data.tsx | Self-edit dialog |
profile/edit-role-actions.ts | Permission-checked self-edit server actions |