Listings are the backbone of data-driven features in the platform. They provide a unified pattern for displaying, searching, filtering, creating, editing, and deleting records—all with a consistent UX and optimistic updates.
Every listing follows the same structure, making it predictable for developers and consistent for users. Whether you're working with Students, Teachers, Classes, Events, or any other entity—the pattern remains the same.
Add, update, and remove items instantly with automatic rollback on errors.
Table/Grid view toggle persists via URL query params using nuqs.
Load-more pagination for seamless data browsing without page reloads.
Type-safe query builders with role-based authorization per module.
Every listing follows the mirror pattern — the URL route produces two directories: one in app/ for routing, one in components/ for feature logic. Replace abc with your feature name.
prisma/— Database layermodels/abc.prisma— Model with schoolId scopeseeds/abc.ts— Test seed datasrc/— Source codeapp/[lang]/s/[subdomain]/(school-dashboard)/(listings)/— Route layerlayout.tsx— Shared listings layoutabc/— Feature routepage.tsx— Imports AbcContentlayout.tsx— Feature layoutloading.tsx— Loading skeletonerror.tsx— Error boundary[id]/page.tsx— Detail viewcomponents/— Component layeratom/— Reusable UI atomspage-title.tsx— Page title displaypage-nav.tsx— Sub-navigation tabstoolbar.tsx— Action toolbarsearch-input.tsx— Debounced searchview-toggle.tsx— Grid/Table togglegrid-container.tsx— Responsive gridempty-state.tsx— Empty state displaymodal/— Modal systemcontext.tsx— Modal state (useModal)modal.tsx— Modal wrappermodal-form-layout.tsx— Two-column layoutmodal-footer.tsx— Progress + navigationtable/— Reusable table componentsdata-table.tsx— Main DataTabledata-table-toolbar.tsx— Table toolbardata-table-column-header.tsx— Sortable headeruse-data-table.ts— Table state hookschool-dashboard/listings/— Listings componentscard.tsx— Generic listing cardform.tsx— Generic form baseindex.ts— Shared exportsabc/— Feature logic (mirrors route)content.tsx— Server: data fetchingtable.tsx— Client: interactive tablecolumns.tsx— Column definitionsform.tsx— Extends generic formcard.tsx— Extends generic cardactions.ts— Server actionsqueries.ts— Query buildersauthorization.ts— RBAC checksvalidation.ts— Zod schemastypes.ts— TypeScript typeshooks/— Shared hooksuse-school-dashboard-data.ts— Optimistic updates + infinite scrolluse-school-dashboard-view.ts— View mode (URL-persisted)Each listing directory follows these naming conventions:
┌─────────────────────────────────────────────────────────────────┐
│ PageTitle src/components/atom/page-title.tsx│
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Abc Management ││
│ │ Manage all abc records in your school ││
│ └─────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────┤
│ PageNav src/components/atom/page-nav.tsx│
│ ┌──────────┬──────────┬──────────┬──────────┐ │
│ │ All │ Active │ Archive │ Settings │ │
│ └──────────┴──────────┴──────────┴──────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Toolbar src/components/atom/toolbar.tsx │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ SearchInput Filters ViewToggle Export Create ││
│ │ [🔍 Search...] [Status▾][Type▾] [≡/⊞] [↓] [+] ││
│ └─────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────┤
│ DataTable src/components/table/data-table.tsx │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ ☐ │ Name │ Status │ Created │ Actions ││
│ │───┼─────────────┼──────────┼────────────┼──────────────────││
│ │ ☐ │ Item 1 │ Active │ 2025-01-20 │ [···] ││
│ │ ☐ │ Item 2 │ Draft │ 2025-01-19 │ [···] ││
│ └─────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────┤
│ OR GridContainer src/components/atom/grid-container.tsx│
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Card 1 │ │ Card 2 │ │ Card 3 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Modal (when open) src/components/atom/modal/modal │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ ModalFormLayout src/components/atom/modal/modal-form-layout ││
│ │ ┌──────────────┐ ┌───────────────────────────────────────┐││
│ │ │ Create Abc │ │ Form (listings/abc/form.tsx) │││
│ │ │ Add new item │ │ - Field 1 │││
│ │ └──────────────┘ └───────────────────────────────────────┘││
│ │ ModalFooter src/components/atom/modal/modal-footer ││
│ │ [━━━━━━━━━━━━━━━━━━━━━━━━━━━━] ││
│ │ Step 1 of 2: Basic Info [Cancel] [Next] ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
Route (page.tsx)
│
▼
Content (content.tsx) ──── Server Component
│ • getTenantContext()
│ • Parse URL params
│ • Fetch data via queries.ts
│
▼
Table (table.tsx) ──────── Client Component
│ • useModal()
│ • usePlatformView()
│ • useDataTable()
│
├──► Toolbar
│ • SearchInput
│ • ViewToggle
│ • ExportButton
│ • CreateButton
│
├──► DataTable / GridContainer
│ • Columns (columns.tsx)
│ • Row actions
│ • Pagination
│
└──► Modal
• Form (form.tsx)
• ModalFormLayout
• ModalFooter
The Grades module serves as the reference implementation with the complete pattern:
✅ = Complete | 🔄 = Using pattern, needs queries.ts/authorization.ts
schoolId in every database queryconst { schoolId } = session.useruseMemogetColumns(dictionary, lang, { onDelete, onEdit, onView })optimisticRemove(id) before server requestrefresh() on error to rollbackoptimisticUpdate(id, updater) for in-place updates"use server" directiverevalidatePath() or redirect() after mutationsActionResponse<T> resultsModalFormLayout for two-column header/form layoutModalFooter for progress bar and navigationonSuccess() callback after successful mutationsThe Listings pattern ensures consistency across all data-driven features. By following this pattern, you get auto-refresh, optimistic updates, view toggles, and export functionality—all with type-safe queries and role-based authorization.