Add printable report cards, progress reports and invoices
Three documents, all print-styled rather than generated server-side: no PDF dependency is added, and "Save as PDF" in the browser produces the file. @media print in styles.css strips the app chrome (.no-print) and breaks each student onto its own sheet (.print-page). - Report card: weighted category breakdown, overall percent and letter from the existing gradebook config, plus an attendance summary. - Academic progress report: the same, plus assignment-level detail — a mid-term report is only actionable with the underlying work listed. - 504 / IEP progress report: each goal's baseline, target, status and progress entries within the period, for the quarterly report IDEA requires. RLS keeps it to the case manager, admins and parents. - Invoice, reachable per student from the tuition ledger: opening balance carried forward, itemised activity, and balance due. Grade maths is reused from lib/grades and the class grading config from useEffectiveConfig, so report cards cannot drift from the gradebook. The "Email to parents" button opens a prefilled mailto: draft — the same mechanism the intake-link share already uses. It gathers addresses from intake guardians and linked parent accounts. mailto cannot attach a file, so the itemisation goes in the body as text; sending a real attachment would need a transactional email provider and an API key. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import { Route as AuthenticatedRouteRouteImport } from './routes/_authenticated/
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as IntakeTokenRouteImport } from './routes/intake.$token'
|
||||
import { Route as AuthenticatedStudentsRouteImport } from './routes/_authenticated/students'
|
||||
import { Route as AuthenticatedReportsRouteImport } from './routes/_authenticated/reports'
|
||||
import { Route as AuthenticatedPlansRouteImport } from './routes/_authenticated/plans'
|
||||
import { Route as AuthenticatedMessagesRouteImport } from './routes/_authenticated/messages'
|
||||
import { Route as AuthenticatedLedgerRouteImport } from './routes/_authenticated/ledger'
|
||||
@@ -27,6 +28,7 @@ import { Route as AuthenticatedStudentsIndexRouteImport } from './routes/_authen
|
||||
import { Route as AuthenticatedClassesIndexRouteImport } from './routes/_authenticated/classes.index'
|
||||
import { Route as AuthenticatedStudentsNewRouteImport } from './routes/_authenticated/students.new'
|
||||
import { Route as AuthenticatedStudentsIdRouteImport } from './routes/_authenticated/students.$id'
|
||||
import { Route as AuthenticatedInvoiceIdRouteImport } from './routes/_authenticated/invoice.$id'
|
||||
import { Route as AuthenticatedClassesIdRouteImport } from './routes/_authenticated/classes.$id'
|
||||
|
||||
const AuthRoute = AuthRouteImport.update({
|
||||
@@ -53,6 +55,11 @@ const AuthenticatedStudentsRoute = AuthenticatedStudentsRouteImport.update({
|
||||
path: '/students',
|
||||
getParentRoute: () => AuthenticatedRouteRoute,
|
||||
} as any)
|
||||
const AuthenticatedReportsRoute = AuthenticatedReportsRouteImport.update({
|
||||
id: '/reports',
|
||||
path: '/reports',
|
||||
getParentRoute: () => AuthenticatedRouteRoute,
|
||||
} as any)
|
||||
const AuthenticatedPlansRoute = AuthenticatedPlansRouteImport.update({
|
||||
id: '/plans',
|
||||
path: '/plans',
|
||||
@@ -121,6 +128,11 @@ const AuthenticatedStudentsIdRoute = AuthenticatedStudentsIdRouteImport.update({
|
||||
path: '/$id',
|
||||
getParentRoute: () => AuthenticatedStudentsRoute,
|
||||
} as any)
|
||||
const AuthenticatedInvoiceIdRoute = AuthenticatedInvoiceIdRouteImport.update({
|
||||
id: '/invoice/$id',
|
||||
path: '/invoice/$id',
|
||||
getParentRoute: () => AuthenticatedRouteRoute,
|
||||
} as any)
|
||||
const AuthenticatedClassesIdRoute = AuthenticatedClassesIdRouteImport.update({
|
||||
id: '/$id',
|
||||
path: '/$id',
|
||||
@@ -139,9 +151,11 @@ export interface FileRoutesByFullPath {
|
||||
'/ledger': typeof AuthenticatedLedgerRoute
|
||||
'/messages': typeof AuthenticatedMessagesRoute
|
||||
'/plans': typeof AuthenticatedPlansRoute
|
||||
'/reports': typeof AuthenticatedReportsRoute
|
||||
'/students': typeof AuthenticatedStudentsRouteWithChildren
|
||||
'/intake/$token': typeof IntakeTokenRoute
|
||||
'/classes/$id': typeof AuthenticatedClassesIdRoute
|
||||
'/invoice/$id': typeof AuthenticatedInvoiceIdRoute
|
||||
'/students/$id': typeof AuthenticatedStudentsIdRoute
|
||||
'/students/new': typeof AuthenticatedStudentsNewRoute
|
||||
'/classes/': typeof AuthenticatedClassesIndexRoute
|
||||
@@ -158,8 +172,10 @@ export interface FileRoutesByTo {
|
||||
'/ledger': typeof AuthenticatedLedgerRoute
|
||||
'/messages': typeof AuthenticatedMessagesRoute
|
||||
'/plans': typeof AuthenticatedPlansRoute
|
||||
'/reports': typeof AuthenticatedReportsRoute
|
||||
'/intake/$token': typeof IntakeTokenRoute
|
||||
'/classes/$id': typeof AuthenticatedClassesIdRoute
|
||||
'/invoice/$id': typeof AuthenticatedInvoiceIdRoute
|
||||
'/students/$id': typeof AuthenticatedStudentsIdRoute
|
||||
'/students/new': typeof AuthenticatedStudentsNewRoute
|
||||
'/classes': typeof AuthenticatedClassesIndexRoute
|
||||
@@ -179,9 +195,11 @@ export interface FileRoutesById {
|
||||
'/_authenticated/ledger': typeof AuthenticatedLedgerRoute
|
||||
'/_authenticated/messages': typeof AuthenticatedMessagesRoute
|
||||
'/_authenticated/plans': typeof AuthenticatedPlansRoute
|
||||
'/_authenticated/reports': typeof AuthenticatedReportsRoute
|
||||
'/_authenticated/students': typeof AuthenticatedStudentsRouteWithChildren
|
||||
'/intake/$token': typeof IntakeTokenRoute
|
||||
'/_authenticated/classes/$id': typeof AuthenticatedClassesIdRoute
|
||||
'/_authenticated/invoice/$id': typeof AuthenticatedInvoiceIdRoute
|
||||
'/_authenticated/students/$id': typeof AuthenticatedStudentsIdRoute
|
||||
'/_authenticated/students/new': typeof AuthenticatedStudentsNewRoute
|
||||
'/_authenticated/classes/': typeof AuthenticatedClassesIndexRoute
|
||||
@@ -201,9 +219,11 @@ export interface FileRouteTypes {
|
||||
| '/ledger'
|
||||
| '/messages'
|
||||
| '/plans'
|
||||
| '/reports'
|
||||
| '/students'
|
||||
| '/intake/$token'
|
||||
| '/classes/$id'
|
||||
| '/invoice/$id'
|
||||
| '/students/$id'
|
||||
| '/students/new'
|
||||
| '/classes/'
|
||||
@@ -220,8 +240,10 @@ export interface FileRouteTypes {
|
||||
| '/ledger'
|
||||
| '/messages'
|
||||
| '/plans'
|
||||
| '/reports'
|
||||
| '/intake/$token'
|
||||
| '/classes/$id'
|
||||
| '/invoice/$id'
|
||||
| '/students/$id'
|
||||
| '/students/new'
|
||||
| '/classes'
|
||||
@@ -240,9 +262,11 @@ export interface FileRouteTypes {
|
||||
| '/_authenticated/ledger'
|
||||
| '/_authenticated/messages'
|
||||
| '/_authenticated/plans'
|
||||
| '/_authenticated/reports'
|
||||
| '/_authenticated/students'
|
||||
| '/intake/$token'
|
||||
| '/_authenticated/classes/$id'
|
||||
| '/_authenticated/invoice/$id'
|
||||
| '/_authenticated/students/$id'
|
||||
| '/_authenticated/students/new'
|
||||
| '/_authenticated/classes/'
|
||||
@@ -293,6 +317,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthenticatedStudentsRouteImport
|
||||
parentRoute: typeof AuthenticatedRouteRoute
|
||||
}
|
||||
'/_authenticated/reports': {
|
||||
id: '/_authenticated/reports'
|
||||
path: '/reports'
|
||||
fullPath: '/reports'
|
||||
preLoaderRoute: typeof AuthenticatedReportsRouteImport
|
||||
parentRoute: typeof AuthenticatedRouteRoute
|
||||
}
|
||||
'/_authenticated/plans': {
|
||||
id: '/_authenticated/plans'
|
||||
path: '/plans'
|
||||
@@ -384,6 +415,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthenticatedStudentsIdRouteImport
|
||||
parentRoute: typeof AuthenticatedStudentsRoute
|
||||
}
|
||||
'/_authenticated/invoice/$id': {
|
||||
id: '/_authenticated/invoice/$id'
|
||||
path: '/invoice/$id'
|
||||
fullPath: '/invoice/$id'
|
||||
preLoaderRoute: typeof AuthenticatedInvoiceIdRouteImport
|
||||
parentRoute: typeof AuthenticatedRouteRoute
|
||||
}
|
||||
'/_authenticated/classes/$id': {
|
||||
id: '/_authenticated/classes/$id'
|
||||
path: '/$id'
|
||||
@@ -434,7 +472,9 @@ interface AuthenticatedRouteRouteChildren {
|
||||
AuthenticatedLedgerRoute: typeof AuthenticatedLedgerRoute
|
||||
AuthenticatedMessagesRoute: typeof AuthenticatedMessagesRoute
|
||||
AuthenticatedPlansRoute: typeof AuthenticatedPlansRoute
|
||||
AuthenticatedReportsRoute: typeof AuthenticatedReportsRoute
|
||||
AuthenticatedStudentsRoute: typeof AuthenticatedStudentsRouteWithChildren
|
||||
AuthenticatedInvoiceIdRoute: typeof AuthenticatedInvoiceIdRoute
|
||||
}
|
||||
|
||||
const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = {
|
||||
@@ -447,7 +487,9 @@ const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = {
|
||||
AuthenticatedLedgerRoute: AuthenticatedLedgerRoute,
|
||||
AuthenticatedMessagesRoute: AuthenticatedMessagesRoute,
|
||||
AuthenticatedPlansRoute: AuthenticatedPlansRoute,
|
||||
AuthenticatedReportsRoute: AuthenticatedReportsRoute,
|
||||
AuthenticatedStudentsRoute: AuthenticatedStudentsRouteWithChildren,
|
||||
AuthenticatedInvoiceIdRoute: AuthenticatedInvoiceIdRoute,
|
||||
}
|
||||
|
||||
const AuthenticatedRouteRouteWithChildren =
|
||||
|
||||
Reference in New Issue
Block a user