- 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
73 lines
2.4 KiB
TypeScript
73 lines
2.4 KiB
TypeScript
import { createFileRoute, Outlet, Link, useParams } from '@tanstack/react-router'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { accountDetailOptions } from '@/api/accounts'
|
|
import { Skeleton } from '@/components/ui/skeleton'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
export const Route = createFileRoute('/_authenticated/accounts/$accountId')({
|
|
component: AccountDetailLayout,
|
|
})
|
|
|
|
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' },
|
|
] as const
|
|
|
|
function AccountDetailLayout() {
|
|
const { accountId } = useParams({ from: '/_authenticated/accounts/$accountId' })
|
|
const { data: account, isLoading } = useQuery(accountDetailOptions(accountId))
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="space-y-4">
|
|
<Skeleton className="h-8 w-64" />
|
|
<Skeleton className="h-4 w-96" />
|
|
<Skeleton className="h-10 w-full" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!account) {
|
|
return <p className="text-muted-foreground">Account not found</p>
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<div className="flex items-center gap-3">
|
|
<h1 className="text-2xl font-bold">{account.name}</h1>
|
|
<Badge variant="secondary">{account.billingMode}</Badge>
|
|
</div>
|
|
{account.email && <p className="text-muted-foreground">{account.email}</p>}
|
|
</div>
|
|
|
|
<nav className="flex gap-1 border-b">
|
|
{tabs.map((tab) => (
|
|
<Link
|
|
key={tab.to}
|
|
to={tab.to}
|
|
params={{ accountId }}
|
|
activeOptions={{ exact: tab.to === '/accounts/$accountId' }}
|
|
className={cn(
|
|
'px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors',
|
|
'text-muted-foreground border-transparent hover:text-foreground hover:border-border',
|
|
)}
|
|
activeProps={{
|
|
className: 'text-foreground border-primary',
|
|
}}
|
|
>
|
|
{tab.label}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
|
|
<Outlet />
|
|
</div>
|
|
)
|
|
}
|