0%
balqalam Logo
balqalam
FeaturesCommunityPricingDocumentation
Login
  • 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
  • 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
Sales & GTM
  • Marketing Brief
  • Sales
  • Go-to-market
  • Marketing
  • Admission — Feature Spotlight
  • Pilot Program
  • Leads
  • Proposal
  • Outreach Templates
  • Case Study
  • Competitors
  • Landing-page teardown
  • Competitor FAQ
  • Business model
  • Shared economy
  • Traction
Fundraising & Ecosystem
  • Get Support
  • Investor Deck
  • Data Room
  • Investors
  • Accelerators
  • Incubators
  • Grants
  • Sponsors
  • Partners
  • Competitions & Hackathons
  • Universities & Training Centers

Classrooms

PreviousNext

Physical room management — types, capacity, grade affinity, section generation, room constraints, timetable integration, and cross-module map.

The classrooms module manages physical rooms across a school campus. Each classroom has a type, capacity, optional grade affinity, and integrates with sections for homeroom grouping. DEVELOPER and ADMIN have full CRUD; TEACHER and STAFF read-only.

Schema, seed data, CRUD, room detail, section generation, capacity stats, and timetable integration are production. The area is two tabs — Rooms (list + add/edit + Sync defaults, with capacity summary stats folded into the header) and Configure (sets sections-per-grade + capacity and generates each section's one main classroom). A class's room is assigned in the Classes add/edit form and per-period in the Timetable; the per-room weekly schedule lives on the room detail page (/classrooms/[id]).

Rationalized 2026-06-17 / 2026-06-18. Three redundant tabs were removed to keep the area clean: Subjects (a bulk Class.classroomId editor — the same field is required in the Classes form and overridden per-period by the Timetable), Schedule (a read-only section view duplicating the Timetable feature; the real per-room view is on the room detail page), and Capacity (its summary cards moved into the Rooms page). The Configure tab was then trimmed to the lean domain — sections-per-grade + capacity + each section's main classroom; its Generate Classes from Catalog and Enroll Students in Classes blocks were removed (subjects bind to grades at catalog-bridge time; students are placed into grades/sections elsewhere). Provisioning logic (Sync defaults ↔ Configure) shares one room-name helper (catalog/room-naming.ts).

Integration

Classrooms is a lean, upstream block: it owns only grades → sections → one main classroom each. It deliberately does not know students or teachers — every other block couples to it.

Couples withLinkDirectionWhat it means
Academic structure / GradeAcademicGrade ← Section.gradeId, Classroom.gradeIdupstreamA section belongs to a grade; rooms carry an optional grade affinity. The grade is the hinge — it implies the subjects (via the catalog bridge).
Catalog / Subjectsgrade ⇒ subjects at bridge timeupstreamSchool picks country/type/level → we bridge to the right curriculum. Knowing the grade means knowing the subjects, so classrooms never generates classes from catalog.
TimetableTimetable.classroomId (required)downstreamEvery slot books a room. The section's main classroom is the homeroom the generator prefers; labs/halls/fields are reached per-period here, not coupled to the section. Three unique constraints prevent double-booking.
StudentsStudent.sectionId → Section → main classroomdownstreamStudents are placed into a grade + section elsewhere; their homeroom is the section's main classroom. Classrooms doesn't own students.
AttendanceSection.students, period-aware via timetabledownstreamThe section (with its homeroom) is the roster unit; attendance is taken per period/room through timetable slots.
Classes (gradebook axis)Class.classroomId (required)downstreamClass is the de-emphasized per-subject gradebook/course axis; its room is set in the Classes form, not here.
TeachersSection.homeroomTeacherId; teachers ↔ subjects elsewheredownstreamA section may have a homeroom teacher; subject-teaching assignments live in their own flow. Classrooms doesn't own teachers.
ConferenceSection.conferenceRecordingOptOutdownstreamPer-section live-class recording preference.

Master classroom rule. Each section couples to exactly one main classroom — Section.classroomId is a single FK, so a section can never have more than one. Room type governs: the main room is the classroom-type homeroom; labs, halls, and fields are shared and reached through the timetable, never owned by the section. Provisioning mints a unique A0x/B0x room per section (defaultRoomName), keeping the pairing 1:1.

The flow (full chain in Integration flow):

School: country + type + level
  → bridge to catalog / curriculum        (grade ⇒ subjects)
  → sections-per-grade + capacity
  → each section ↔ one main classroom     ← classrooms stops here
  → timetable (section × subject × period × room, teacher-less)
  → students into sections · teachers onto subjects
  → attendance · exams · grades · reports

Status

FeatureStatusNotes
Classroom schemaProductionFull model with type, capacity, grade affinity, lang
Section modelProductionHomeroom groups within grades, linked to classrooms
Classroom typesProduction8 types: classroom, lab, library, art, music, hall, sports, admin
Seed dataProduction28 rooms across 5 buildings (A-E)
Configure tabProductionBulk section + room generation per grade
Room detailProductionDetail page with per-room timetable grid, class list, utilization
Capacity statsProductionSummary cards folded into the Rooms page header
Tab navigationProduction2 tabs: Rooms, Configure
AuthorizationProductionRBAC — DEVELOPER / ADMIN write, TEACHER / STAFF read
Timetable integrationProductionSlot assignment with 3 unique constraints
Room constraintsProductionScheduling rules, accessibility, maintenance blocks
Dropdown APIProductionGET /api/classrooms
CRUD UIProductionDataTable with create / edit / delete
Room ↔ classProductionSet in the Classes add/edit form (Class.classroomId) + Timetable

Models

Classroom

model Classroom {
  id       String  @id @default(cuid())
  schoolId String
  typeId   String
  gradeId  String?
  roomName String
  capacity Int
  lang     String  @default("ar")
 
  school        School         @relation(fields: [schoolId], references: [id], onDelete: Cascade)
  classroomType ClassroomType  @relation(fields: [typeId], references: [id])
  grade         AcademicGrade? @relation(fields: [gradeId], references: [id], onDelete: SetNull)
  classes       Class[]
  sections      Section[]
  timetables    Timetable[]
  constraints   RoomConstraint[]
 
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
 
  @@unique([schoolId, roomName])
  @@index([schoolId, gradeId])
  @@map("classrooms")
}
FieldTypeDescription
schoolIdStringTenant isolation
typeIdStringFK to ClassroomType
gradeIdString?Optional FK to AcademicGrade (grade affinity)
roomNameString"A101", "Physics Lab"
capacityIntMaximum student count
langStringStorage language for translation (default "ar")

Section

Homeroom group within a grade — represents "Grade 7-A", links students to a classroom and homeroom teacher.

model Section {
  id       String @id @default(cuid())
  schoolId String
  gradeId  String
  name     String   // "Grade 7-A"
  letter   String   // "A"
  lang     String   @default("ar")
 
  homeroomTeacherId String?
  classroomId       String?
  maxCapacity       Int     @default(30)
 
  school          School         @relation(fields: [schoolId], references: [id], onDelete: Cascade)
  grade           AcademicGrade  @relation(fields: [gradeId], references: [id], onDelete: Cascade)
  homeroomTeacher Teacher?       @relation("HomeroomTeacher", fields: [homeroomTeacherId], references: [id], onDelete: SetNull)
  classroom       Classroom?     @relation(fields: [classroomId], references: [id], onDelete: SetNull)
  students        Student[]
 
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
 
  @@unique([schoolId, gradeId, letter])
  @@unique([schoolId, name])
  @@index([schoolId])
  @@index([schoolId, gradeId])
  @@map("sections")
}

Relationship chain: AcademicGrade → Section → Student, Section → Classroom, Section → Teacher (homeroom).

ClassroomType

model ClassroomType {
  id       String @id @default(cuid())
  schoolId String
  name     String
  lang     String @default("ar")
 
  school     School      @relation(fields: [schoolId], references: [id], onDelete: Cascade)
  classrooms Classroom[]
 
  @@unique([schoolId, name])
  @@map("classroom_types")
}

8 seeded types: classroom, lab, library, art, music, hall, sports, admin.

RoomConstraint

Defined in prisma/models/timetable.prisma. Scheduling and accessibility metadata.

model RoomConstraint {
  id          String  @id @default(cuid())
  schoolId    String
  classroomId String
  termId      String?
 
  allowedSubjectTypes  String[] @default([])
  strictCapacityLimit  Boolean  @default(true)
  capacityBuffer       Int      @default(0)
 
  wheelchairAccessible Boolean @default(false)
  hasElevatorAccess    Boolean @default(false)
  floorLevel           Int?
 
  reservedPeriods   Json @default("{}")
  maintenanceBlocks Json @default("[]")
 
  @@unique([schoolId, classroomId, termId])
  @@map("room_constraints")
}

Seed uses upsert — safe to run multiple times.

Integration details

Per-module specifics behind the Integration map above.

Timetable

Every timetable slot assigns a room. Three unique constraints prevent double-booking:

@@unique([schoolId, termId, dayOfWeek, periodId, classId, weekOffset])
@@unique([schoolId, termId, dayOfWeek, periodId, teacherId, weekOffset])
@@unique([schoolId, termId, dayOfWeek, periodId, classroomId, weekOffset])

The third constraint ensures no room is in two classes in the same period.

  • Room detail timetable (/classrooms/[id]) — weekly grid with class, subject, teacher, grade. Utilization = usedSlots / (teachingPeriods * workingDays).
  • Timetable room view (by-room/content.tsx) — full weekly schedule + utilization widget.
  • Slot editor (slot-editor-dialog.tsx) — filters rooms by occupancy so only available rooms appear.
  • Auto-generation (algorithm.ts) — requiresLab heuristic routes science subjects to lab-type rooms. allowedSubjectTypes is loaded but not consumed by isRoomAvailable().

Section

AcademicGrade → Section → Classroom (via classroomId). Student → Section → Classroom (via sectionId → classroomId). Sections are created via the Configure tab's generateSections() action, which creates Section + Classroom atomically.

Default naming convention. New schools (autoProvisionSections) and the Configure wizard generate 2 grade-assigned main classrooms per grade, named {section letter}{2-digit grade number} — Grade 1 → A01 (section A) / B01 (section B), Grade 12 → A12 / B12, etc. These rooms carry a gradeId (homeroom classrooms, shown with their grade rather than the Shared badge). The section keeps its own label (Grade 1-A). Rooms without a gradeId (labs, halls, gym, seeded demo rooms) render as Shared.

List display (locale-correct without the translation API). The rooms table resolves two columns client-side so they read correctly in any language regardless of how the data was stored:

  • Grade — derived as "{Grade} {gradeNumber}" from the structured grade number (e.g. Grade 1 / الصف 1), not the stored grade name. The column sorts by grade number and defaults to ascending; Shared (no grade) rows sort last.
  • Type — mapped through the school.classrooms.roomTypes dictionary (keyed by the lowercased type slug: classroom, lab, hall, admin, art, music, library, sports). Unknown/custom type names fall back to their raw stored value. This is deterministic (config-like), so it does not depend on the on-demand translation cache.

The Add/Edit room form opens as a compact shadcn Dialog (not the legacy full-screen modal), laid out as two rows — room name + grade, then type + capacity. It is fed synchronously: the type/grade option lists are fetched once on the server (in content.tsx) and the edited row's current values come from the already-loaded table data (React Hook Form values), so opening an existing room does not trigger an on-open getClassroom fetch. This keeps the dialog stable on open — no blank→filled flash and no height re-center.

Class

Each Class has a required classroomId:

model Class {
  classroomId String
  classroom   Classroom @relation(fields: [classroomId], references: [id])
}

Students

  • Course path: Student → StudentClass → Class → Classroom
  • Homeroom path: Student → Section → Classroom (schema only, no UI)

Teachers

  • Home room: Teacher → Class → Classroom (assigned classes' default rooms)
  • Per-slot: Teacher → Timetable → Classroom (actual room per period)
  • Homeroom: Teacher → Section → Classroom (via homeroomTeacherId)

Parent portal

Guardian → Student → Timetable → Classroom (via getChildTimetable()).

Exams

No direct classroomId on Exam. Venue resolved via Exam → Class → Classroom. Conflict detection accepts optional classroomId and checks timetable + exam-to-exam conflicts.

Dashboard / facility (gaps)

UpcomingClassCard shows hardcoded mock data ("Room 101", "Hall A"). Facility management page is 100% static (42 facilities). Neither queries Classroom.

Cross-reference table

ModuleDirect FKPathStatus
TimetableclassroomIdDirectProduction
ClassclassroomIdDirectProduction
SectionclassroomIdDirectProduction
RoomConstraintclassroomIdDirectProduction
TeacherNoTeacher → Class / Timetable / Section → ClassroomIndirect
StudentNoStudent → StudentClass → Class → ClassroomIndirect
Student homeroomNoStudent → Section → ClassroomSchema only
Parent portalNoGuardian → Student → Timetable → ClassroomIndirect
ExamNoExam → Class → ClassroomIndirect
School configNodb.classroom.count()Read-only
CatalogSubjectNoNo pathGap
ApplicationNoNo connectionGap
DashboardNoHardcoded mockGap
Facility pageNoHardcoded staticGap
OnboardingNoSaves count to maxClasses onlyGap

API

GET /api/classrooms — returns rooms for dropdown selection.

AspectDetail
Locationsrc/app/api/classrooms/route.ts
Multi-tenantScoped by schoolId from tenant context (empty if missing)
Rate limitAPI tier limits applied
AuthNo auth() check — only rate limit + tenant context
Response{ classrooms: [{ id, roomName }] }

Key files

prisma/models/classrooms.prisma     # Classroom + Section + ClassroomType
prisma/models/timetable.prisma      # RoomConstraint
prisma/seeds/classrooms.ts          # Seed logic (types + rooms)
prisma/seeds/constants.ts           # 28 classroom definitions
src/app/api/classrooms/route.ts     # Dropdown API

See also

  • Timetable — slot assignment, room view
  • Multi-tenancy — schoolId scoping
  • Listings — DataTable conventions
TimetableNotifications

On This Page

IntegrationStatusModelsClassroomSectionClassroomTypeRoomConstraintIntegration detailsTimetableSectionClassStudentsTeachersParent portalExamsDashboard / facility (gaps)Cross-reference tableAPIKey filesSee also

Built by Databayt ·

Welcome to balqalam.

A great journey is about to begin.