Add lessons module, rate cycles, EC2 deploy scripts, and help content
- Lessons module: lesson types, instructors, schedule slots, enrollments, sessions (list + week grid view), lesson plans, grading scales, templates - Rate cycles: replace monthly_rate with billing_interval + billing_unit on enrollments; add weekly/monthly/quarterly rate presets to lesson types and schedule slots with auto-fill on enrollment form - Member detail page: tabbed layout for details, identity documents, enrollments - Sessions week view: custom 7-column grid replacing react-big-calendar - Music store seed: instructors, lesson types, slots, enrollments, sessions, grading scale, lesson plan template - Scrollbar styling: themed to match sidebar/app palette - deploy/: EC2 setup and redeploy scripts, nginx config, systemd service - Help: add Lessons category (overview, types, instructors, slots, enrollments, sessions, plans/grading); collapsible sidebar with independent scroll; remove POS/accounting references from docs
This commit is contained in:
@@ -12,6 +12,7 @@ export const Route = createFileRoute('/_authenticated/accounts/$accountId')({
|
||||
const tabs = [
|
||||
{ label: 'Overview', to: '/accounts/$accountId' },
|
||||
{ label: 'Members', to: '/accounts/$accountId/members' },
|
||||
{ label: 'Enrollments', to: '/accounts/$accountId/enrollments' },
|
||||
{ label: 'Payment Methods', to: '/accounts/$accountId/payment-methods' },
|
||||
{ label: 'Tax Exemptions', to: '/accounts/$accountId/tax-exemptions' },
|
||||
{ label: 'Processor Links', to: '/accounts/$accountId/processor-links' },
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { enrollmentListOptions } from '@/api/lessons'
|
||||
import { memberListOptions } from '@/api/members'
|
||||
import { DataTable, type Column } from '@/components/shared/data-table'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { useAuthStore } from '@/stores/auth.store'
|
||||
import type { Enrollment } from '@/types/lesson'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/accounts/$accountId/enrollments')({
|
||||
component: AccountEnrollmentsTab,
|
||||
})
|
||||
|
||||
function statusBadge(status: string) {
|
||||
const variants: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {
|
||||
active: 'default', paused: 'secondary', cancelled: 'destructive', completed: 'outline',
|
||||
}
|
||||
return <Badge variant={variants[status] ?? 'outline'}>{status}</Badge>
|
||||
}
|
||||
|
||||
const columns: Column<Enrollment & { memberName?: string }>[] = [
|
||||
{ key: 'member_name', header: 'Member', sortable: true, render: (e) => <span className="font-medium">{(e as any).memberName ?? e.memberId}</span> },
|
||||
{ key: 'status', header: 'Status', sortable: true, render: (e) => statusBadge(e.status) },
|
||||
{ key: 'start_date', header: 'Start', sortable: true, render: (e) => <>{new Date(e.startDate + 'T00:00:00').toLocaleDateString()}</> },
|
||||
{ key: 'rate', header: 'Rate', render: (e) => <>{e.rate ? `$${e.rate}${e.billingInterval ? ` / ${e.billingInterval} ${e.billingUnit}` : ''}` : <span className="text-muted-foreground">—</span>}</> },
|
||||
]
|
||||
|
||||
function AccountEnrollmentsTab() {
|
||||
const { accountId } = Route.useParams()
|
||||
const navigate = useNavigate()
|
||||
const hasPermission = useAuthStore((s) => s.hasPermission)
|
||||
|
||||
// Get member IDs for this account so we can filter enrollments
|
||||
const { data: membersData } = useQuery(memberListOptions(accountId, { page: 1, limit: 100, order: 'asc' }))
|
||||
const memberIds = (membersData?.data ?? []).map((m) => m.id)
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
...enrollmentListOptions({ accountId, page: 1, limit: 100 }),
|
||||
enabled: !!accountId,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-muted-foreground">{data?.pagination.total ?? 0} enrollment(s)</p>
|
||||
{hasPermission('lessons.edit') && (
|
||||
<Button size="sm" onClick={() => navigate({ to: '/lessons/enrollments/new', search: {} as any })}>
|
||||
<Plus className="h-4 w-4 mr-1" />Enroll a Member
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data?.data ?? []}
|
||||
loading={isLoading}
|
||||
page={1}
|
||||
totalPages={1}
|
||||
total={data?.data?.length ?? 0}
|
||||
onPageChange={() => {}}
|
||||
onSort={() => {}}
|
||||
onRowClick={(e) => navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId: e.id }, search: {} as any })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -281,6 +281,7 @@ function MembersTab() {
|
||||
<DropdownMenuItem onClick={() => navigate({
|
||||
to: '/members/$memberId',
|
||||
params: { memberId: m.id },
|
||||
search: {} as any,
|
||||
})}>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
Edit
|
||||
|
||||
Reference in New Issue
Block a user