fix: resolve all frontend lint errors and warnings
All checks were successful
CI / ci (pull_request) Successful in 21s
CI / e2e (pull_request) Successful in 59s

Replace all `any` types with proper types across 36 files:
- TanStack Router search params: `{} as Record<string, unknown>`
- API response pagination: proper typed interface
- DataTable column casts: remove unnecessary `as any`
- Function params and event handlers: use specific types
- Remove unused imports and variables in POS components

Frontend lint now passes with 0 errors and 0 warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ryan
2026-04-04 20:12:17 +00:00
parent 1673e18fe8
commit a0be16d848
36 changed files with 122 additions and 108 deletions

View File

@@ -119,7 +119,7 @@ export function currentDrawerOptions(locationId: string | null) {
export function productSearchOptions(search: string) {
return queryOptions({
queryKey: posKeys.products(search),
queryFn: () => api.get<{ data: Product[]; pagination: any }>('/v1/products', { q: search, limit: 24, isActive: true }),
queryFn: () => api.get<{ data: Product[]; pagination: { page: number; limit: number; total: number; totalPages: number } }>('/v1/products', { q: search, limit: 24, isActive: true }),
enabled: search.length >= 1,
})
}

View File

@@ -14,7 +14,7 @@ interface POSItemPanelProps {
transaction: Transaction | null
}
export function POSItemPanel({ transaction }: POSItemPanelProps) {
export function POSItemPanel({ transaction: _transaction }: POSItemPanelProps) {
const queryClient = useQueryClient()
const { currentTransactionId, setTransaction, locationId } = usePOSStore()
const [search, setSearch] = useState('')

View File

@@ -1,5 +1,5 @@
import { useEffect } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import { usePOSStore } from '@/stores/pos.store'
@@ -21,8 +21,7 @@ function locationsOptions() {
}
export function POSRegister() {
const { locationId, setLocation, currentTransactionId, drawerSessionId, setDrawerSession } = usePOSStore()
const queryClient = useQueryClient()
const { locationId, setLocation, currentTransactionId, setDrawerSession } = usePOSStore()
// Fetch locations
const { data: locationsData } = useQuery(locationsOptions())

View File

@@ -1,6 +1,6 @@
import { Link, useRouter } from '@tanstack/react-router'
import { useAuthStore } from '@/stores/auth.store'
import { usePOSStore } from '@/stores/pos.store'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'

View File

@@ -23,12 +23,12 @@ export function usePagination() {
function setParams(updates: Partial<PaginationSearch>) {
navigate({
search: ((prev: PaginationSearch) => ({
search: ((prev: Record<string, unknown>) => ({
...prev,
...updates,
// Reset to page 1 when search or sort changes
page: updates.q !== undefined || updates.sort !== undefined ? 1 : (updates.page ?? prev.page),
})) as any,
page: updates.q !== undefined || updates.sort !== undefined ? 1 : (updates.page ?? (prev as PaginationSearch).page),
})) as (prev: Record<string, unknown>) => Record<string, unknown>,
replace: true,
})
}

View File

@@ -67,7 +67,7 @@ function NavLink({ to, icon, label, collapsed }: { to: string; icon: React.React
return (
<Link
to={to as '/accounts'}
search={{} as any}
search={{} as Record<string, unknown>}
className="flex items-center gap-2 px-3 py-2 rounded-md text-sm text-sidebar-foreground hover:bg-sidebar-accent"
activeProps={{ className: 'flex items-center gap-2 px-3 py-2 rounded-md text-sm bg-sidebar-accent text-sidebar-accent-foreground' }}
title={collapsed ? label : undefined}

View File

@@ -20,7 +20,7 @@ function statusBadge(status: string) {
}
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: 'member_name', header: 'Member', sortable: true, render: (e) => <span className="font-medium">{e.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>}</> },
@@ -41,7 +41,7 @@ function AccountEnrollmentsTab() {
<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 })}>
<Button size="sm" onClick={() => navigate({ to: '/lessons/enrollments/new', search: {} as Record<string, unknown> })}>
<Plus className="h-4 w-4 mr-1" />Enroll a Member
</Button>
)}
@@ -55,7 +55,7 @@ function AccountEnrollmentsTab() {
total={data?.data?.length ?? 0}
onPageChange={() => {}}
onSort={() => {}}
onRowClick={(e) => navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId: e.id }, search: {} as any })}
onRowClick={(e) => navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId: e.id }, search: {} as Record<string, unknown> })}
/>
</div>
)

View File

@@ -281,7 +281,7 @@ function MembersTab() {
<DropdownMenuItem onClick={() => navigate({
to: '/members/$memberId',
params: { memberId: m.id },
search: {} as any,
search: {} as Record<string, unknown>,
})}>
<Pencil className="mr-2 h-4 w-4" />
Edit

View File

@@ -92,7 +92,7 @@ function FileManagerPage() {
})
if (!res.ok) {
const err = await res.json().catch(() => ({}))
toast.error(`Upload failed: ${(err as any).error?.message ?? file.name}`)
toast.error(`Upload failed: ${(err as { error?: { message?: string } }).error?.message ?? file.name}`)
}
} catch {
toast.error(`Upload failed: ${file.name}`)

View File

@@ -2,6 +2,6 @@ import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated/')({
beforeLoad: () => {
throw redirect({ to: '/accounts', search: {} as any })
throw redirect({ to: '/accounts', search: {} as Record<string, unknown> })
},
})

View File

@@ -159,7 +159,7 @@ function ProductDetailPage() {
})
function setTab(t: string) {
navigate({ to: '/inventory/$productId', params: { productId }, search: { tab: t } as any })
navigate({ to: '/inventory/$productId', params: { productId }, search: { tab: t } as Record<string, unknown> })
}
function handleQtySave() {
@@ -192,7 +192,7 @@ function ProductDetailPage() {
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/inventory', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/inventory', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="min-w-0">
@@ -507,12 +507,12 @@ function SuppliersTab({
setAddOpen: (v: boolean) => void
editTarget: ProductSupplier | null
setEditTarget: (v: ProductSupplier | null) => void
addMutation: any
updateMutation: any
removeMutation: any
addMutation: { mutate: (data: Record<string, unknown>) => void; isPending: boolean }
updateMutation: { mutate: (args: { id: string; data: Record<string, unknown> }) => void; isPending: boolean }
removeMutation: { mutate: (id: string) => void; isPending: boolean }
canEdit: boolean
}) {
const { data: allSuppliersData } = useQuery(supplierListOptions({ page: 1, limit: 500, order: 'asc', sort: 'name' } as any))
const { data: allSuppliersData } = useQuery(supplierListOptions({ page: 1, limit: 500, order: 'asc', sort: 'name' }))
const allSuppliers = allSuppliersData?.data ?? []
const linkedIds = new Set(linkedSuppliers.map((s) => s.supplierId))
const availableSuppliers = allSuppliers.filter((s) => !linkedIds.has(s.id))

View File

@@ -71,7 +71,7 @@ function InventoryPage() {
queryClient.invalidateQueries({ queryKey: productKeys.all })
toast.success('Product created')
setCreateOpen(false)
navigate({ to: '/inventory/$productId', params: { productId: product.id }, search: {} as any })
navigate({ to: '/inventory/$productId', params: { productId: product.id }, search: {} as Record<string, unknown> })
},
onError: (err) => toast.error(err.message),
})
@@ -83,23 +83,23 @@ function InventoryPage() {
function handleCategoryChange(v: string) {
setCategoryFilter(v === 'all' ? '' : v)
navigate({ to: '/inventory', search: { ...search, categoryId: v === 'all' ? undefined : v, page: 1 } as any })
navigate({ to: '/inventory', search: { ...search, categoryId: v === 'all' ? undefined : v, page: 1 } as Record<string, unknown> })
}
function handleActiveChange(v: string) {
setActiveFilter(v === 'all' ? '' : v)
navigate({ to: '/inventory', search: { ...search, isActive: v === 'all' ? undefined : v, page: 1 } as any })
navigate({ to: '/inventory', search: { ...search, isActive: v === 'all' ? undefined : v, page: 1 } as Record<string, unknown> })
}
function handleTypeChange(v: string) {
setTypeFilter(v === 'all' ? '' : v)
navigate({ to: '/inventory', search: { ...search, type: v === 'all' ? undefined : v, page: 1 } as any })
navigate({ to: '/inventory', search: { ...search, type: v === 'all' ? undefined : v, page: 1 } as Record<string, unknown> })
}
function handleLowStockChange(v: string) {
const on = v === 'true'
setLowStockFilter(on)
navigate({ to: '/inventory', search: { ...search, lowStock: on ? 'true' : undefined, page: 1 } as any })
navigate({ to: '/inventory', search: { ...search, lowStock: on ? 'true' : undefined, page: 1 } as Record<string, unknown> })
}
const columns: Column<Product>[] = [
@@ -246,7 +246,7 @@ function InventoryPage() {
order={params.order}
onPageChange={setPage}
onSort={setSort}
onRowClick={(p) => navigate({ to: '/inventory/$productId', params: { productId: p.id }, search: {} as any })}
onRowClick={(p) => navigate({ to: '/inventory/$productId', params: { productId: p.id }, search: {} as Record<string, unknown> })}
/>
</div>
)

View File

@@ -130,7 +130,7 @@ function SuppliersPage() {
<DialogContent>
<DialogHeader><DialogTitle>New Supplier</DialogTitle></DialogHeader>
<SupplierForm
onSubmit={supplierMutations.create.bind(null) as any}
onSubmit={(data) => { createMutation.mutate(data) }}
loading={createMutation.isPending}
/>
</DialogContent>

View File

@@ -21,7 +21,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
import { ArrowLeft, RefreshCw } from 'lucide-react'
import { toast } from 'sonner'
import { useAuthStore } from '@/stores/auth.store'
import type { LessonSession, LessonPlan, LessonPlanTemplate } from '@/types/lesson'
import type { Enrollment, LessonSession, LessonPlan, LessonPlanTemplate } from '@/types/lesson'
export const Route = createFileRoute('/_authenticated/lessons/enrollments/$enrollmentId')({
validateSearch: (search: Record<string, unknown>) => ({
@@ -81,7 +81,7 @@ function EnrollmentDetailPage() {
const tab = search.tab
function setTab(t: string) {
navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId }, search: { tab: t } as any })
navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId }, search: { tab: t } as Record<string, unknown> })
}
const { data: enrollment, isLoading } = useQuery(enrollmentDetailOptions(enrollmentId))
@@ -131,7 +131,7 @@ function EnrollmentDetailPage() {
return (
<div className="space-y-6">
<div className="flex items-center gap-4">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/enrollments', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/enrollments', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4 mr-1" />Back
</Button>
<div className="flex-1">
@@ -193,7 +193,17 @@ const BILLING_UNITS = [
function DetailsTab({
enrollment, slotLabel, lessonTypeName, instructorName,
canEdit, onSave, saving, onStatusChange, statusChanging,
}: any) {
}: {
enrollment: Enrollment
slotLabel: string
lessonTypeName: string | undefined
instructorName: string | undefined
canEdit: boolean
onSave: (data: Record<string, unknown>) => void
saving: boolean
onStatusChange: (status: string) => void
statusChanging: boolean
}) {
const [rate, setRate] = useState(enrollment.rate ?? '')
const [billingInterval, setBillingInterval] = useState(String(enrollment.billingInterval ?? 1))
const [billingUnit, setBillingUnit] = useState(enrollment.billingUnit ?? 'month')
@@ -334,7 +344,7 @@ function SessionsTab({ enrollmentId, onGenerate, generating }: { enrollmentId: s
total={data?.data?.length ?? 0}
onPageChange={() => {}}
onSort={() => {}}
onRowClick={(s) => navigate({ to: '/lessons/sessions/$sessionId', params: { sessionId: s.id }, search: {} as any })}
onRowClick={(s) => navigate({ to: '/lessons/sessions/$sessionId', params: { sessionId: s.id }, search: {} as Record<string, unknown> })}
/>
</div>
)
@@ -373,7 +383,7 @@ function LessonPlanTab({ enrollmentId, memberId, canEdit }: { enrollmentId: stri
queryClient.invalidateQueries({ queryKey: lessonPlanKeys.all })
toast.success('Plan created from template')
setTemplatePickerOpen(false)
navigate({ to: '/lessons/plans/$planId', params: { planId: plan.id }, search: {} as any })
navigate({ to: '/lessons/plans/$planId', params: { planId: plan.id }, search: {} as Record<string, unknown> })
},
onError: (err) => toast.error(err.message),
})
@@ -391,7 +401,7 @@ function LessonPlanTab({ enrollmentId, memberId, canEdit }: { enrollmentId: stri
{Math.round(activePlan.progress)}% complete
</p>
</div>
<Button variant="outline" size="sm" onClick={() => navigate({ to: '/lessons/plans/$planId', params: { planId: activePlan.id }, search: {} as any })}>
<Button variant="outline" size="sm" onClick={() => navigate({ to: '/lessons/plans/$planId', params: { planId: activePlan.id }, search: {} as Record<string, unknown> })}>
View Plan
</Button>
</div>

View File

@@ -43,9 +43,9 @@ function statusBadge(status: string) {
}
const columns: Column<Enrollment & { memberName?: string; instructorName?: string; slotInfo?: string; lessonTypeName?: string }>[] = [
{ key: 'member_name', header: 'Member', sortable: true, render: (e) => <span className="font-medium">{(e as any).memberName ?? e.memberId}</span> },
{ key: 'instructor_name', header: 'Instructor', render: (e) => <>{(e as any).instructorName ?? e.instructorId}</> },
{ key: 'slot_info', header: 'Day / Time', render: (e) => <>{(e as any).slotInfo ?? '—'}</> },
{ key: 'member_name', header: 'Member', sortable: true, render: (e) => <span className="font-medium">{e.memberName ?? e.memberId}</span> },
{ key: 'instructor_name', header: 'Instructor', render: (e) => <>{e.instructorName ?? e.instructorId}</> },
{ key: 'slot_info', header: 'Day / Time', render: (e) => <>{e.slotInfo ?? '—'}</> },
{ 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>}</> },
@@ -72,7 +72,7 @@ function EnrollmentsListPage() {
function handleStatusChange(v: string) {
const s = v === 'all' ? '' : v
setStatusFilter(s)
navigate({ to: '/lessons/enrollments', search: { ...search, status: s || undefined, page: 1 } as any })
navigate({ to: '/lessons/enrollments', search: { ...search, status: s || undefined, page: 1 } as Record<string, unknown> })
}
return (
@@ -80,7 +80,7 @@ function EnrollmentsListPage() {
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">Enrollments</h1>
{hasPermission('lessons.edit') && (
<Button onClick={() => navigate({ to: '/lessons/enrollments/new', search: {} as any })}>
<Button onClick={() => navigate({ to: '/lessons/enrollments/new', search: {} as Record<string, unknown> })}>
<Plus className="mr-2 h-4 w-4" />New Enrollment
</Button>
)}
@@ -125,7 +125,7 @@ function EnrollmentsListPage() {
order={params.order}
onPageChange={setPage}
onSort={setSort}
onRowClick={(e) => navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId: e.id }, search: {} as any })}
onRowClick={(e) => navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId: e.id }, search: {} as Record<string, unknown> })}
/>
</div>
)

View File

@@ -108,7 +108,7 @@ function NewEnrollmentPage() {
},
onSuccess: (enrollment) => {
toast.success('Enrollment created')
navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId: enrollment.id }, search: {} as any })
navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId: enrollment.id }, search: {} as Record<string, unknown> })
},
onError: (err) => toast.error(err.message),
})
@@ -141,7 +141,7 @@ function NewEnrollmentPage() {
return (
<div className="space-y-6 max-w-2xl">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/enrollments', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/enrollments', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<h1 className="text-2xl font-bold">New Enrollment</h1>
@@ -282,7 +282,7 @@ function NewEnrollmentPage() {
<Button type="submit" disabled={mutation.isPending || !selectedMember || !selectedSlotId || !startDate} size="lg">
{mutation.isPending ? 'Creating...' : 'Create Enrollment'}
</Button>
<Button variant="secondary" type="button" size="lg" onClick={() => navigate({ to: '/lessons/enrollments', search: {} as any })}>
<Button variant="secondary" type="button" size="lg" onClick={() => navigate({ to: '/lessons/enrollments', search: {} as Record<string, unknown> })}>
Cancel
</Button>
</div>

View File

@@ -93,7 +93,7 @@ function LessonPlanDetailPage() {
return (
<div className="space-y-6 max-w-3xl">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/plans', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/plans', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex-1">

View File

@@ -84,7 +84,7 @@ function LessonPlansPage() {
order={params.order}
onPageChange={setPage}
onSort={setSort}
onRowClick={(p) => navigate({ to: '/lessons/plans/$planId', params: { planId: p.id }, search: {} as any })}
onRowClick={(p) => navigate({ to: '/lessons/plans/$planId', params: { planId: p.id }, search: {} as Record<string, unknown> })}
/>
</div>
)

View File

@@ -49,7 +49,7 @@ function ScheduleHubPage() {
const canAdmin = hasPermission('lessons.admin')
function setTab(t: string) {
navigate({ to: '/lessons/schedule', search: { ...search, tab: t, page: 1 } as any })
navigate({ to: '/lessons/schedule', search: { ...search, tab: t, page: 1 } as Record<string, unknown> })
}
return (
@@ -90,7 +90,7 @@ const instructorColumns: Column<Instructor>[] = [
},
]
function InstructorsTab({ canAdmin, search: _search }: { canAdmin: boolean; search: any }) {
function InstructorsTab({ canAdmin, search: _search }: { canAdmin: boolean; search: Record<string, unknown> }) {
const navigate = useNavigate()
const queryClient = useQueryClient()
const { params, setPage, setSearch, setSort } = usePagination()
@@ -152,7 +152,7 @@ function InstructorsTab({ canAdmin, search: _search }: { canAdmin: boolean; sear
order={params.order}
onPageChange={setPage}
onSort={setSort}
onRowClick={(i) => navigate({ to: '/lessons/schedule/instructors/$instructorId', params: { instructorId: i.id }, search: {} as any })}
onRowClick={(i) => navigate({ to: '/lessons/schedule/instructors/$instructorId', params: { instructorId: i.id }, search: {} as Record<string, unknown> })}
/>
</div>
)
@@ -169,7 +169,7 @@ const lessonTypeColumns: Column<LessonType>[] = [
{ key: 'is_active', header: 'Status', render: (lt) => <Badge variant={lt.isActive ? 'default' : 'secondary'}>{lt.isActive ? 'Active' : 'Inactive'}</Badge> },
]
function LessonTypesTab({ canAdmin, search: _search }: { canAdmin: boolean; search: any }) {
function LessonTypesTab({ canAdmin, search: _search }: { canAdmin: boolean; search: Record<string, unknown> }) {
const queryClient = useQueryClient()
const { params, setPage, setSearch, setSort } = usePagination()
const [searchInput, setSearchInput] = useState(params.q ?? '')
@@ -215,8 +215,8 @@ function LessonTypesTab({ canAdmin, search: _search }: { canAdmin: boolean; sear
const columnsWithActions: Column<LessonType>[] = [
...lessonTypeColumns,
...(canAdmin ? [{
key: 'actions' as any,
header: '' as any,
key: 'actions',
header: '',
render: (lt: LessonType) => (
<div className="flex gap-1 justify-end">
<Button variant="ghost" size="sm" onClick={(e) => { e.stopPropagation(); setEditTarget(lt) }}>Edit</Button>
@@ -298,7 +298,7 @@ const gradingScaleColumns: Column<GradingScale>[] = [
{ key: 'is_active', header: 'Status', render: (gs) => <Badge variant={gs.isActive ? 'default' : 'secondary'}>{gs.isActive ? 'Active' : 'Inactive'}</Badge> },
]
function GradingScalesTab({ canAdmin, search: _search }: { canAdmin: boolean; search: any }) {
function GradingScalesTab({ canAdmin, search: _search }: { canAdmin: boolean; search: Record<string, unknown> }) {
const queryClient = useQueryClient()
const { params, setPage, setSort } = usePagination()
const [createOpen, setCreateOpen] = useState(false)
@@ -327,8 +327,8 @@ function GradingScalesTab({ canAdmin, search: _search }: { canAdmin: boolean; se
const columnsWithActions: Column<GradingScale>[] = [
...gradingScaleColumns,
...(canAdmin ? [{
key: 'actions' as any,
header: '' as any,
key: 'actions',
header: '',
render: (gs: GradingScale) => (
<Button variant="ghost" size="sm" onClick={(e) => { e.stopPropagation(); deleteMutation.mutate(gs.id) }}>
<Trash2 className="h-4 w-4 text-destructive" />

View File

@@ -42,7 +42,7 @@ function InstructorDetailPage() {
const tab = search.tab
function setTab(t: string) {
navigate({ to: '/lessons/schedule/instructors/$instructorId', params: { instructorId }, search: { tab: t } as any })
navigate({ to: '/lessons/schedule/instructors/$instructorId', params: { instructorId }, search: { tab: t } as Record<string, unknown> })
}
const { data: instructor, isLoading } = useQuery(instructorDetailOptions(instructorId))
@@ -62,7 +62,7 @@ function InstructorDetailPage() {
return (
<div className="space-y-6">
<div className="flex items-center gap-4">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/schedule', search: { tab: 'instructors' } as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/schedule', search: { tab: 'instructors' } as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4 mr-1" />Back
</Button>
<div className="flex-1">

View File

@@ -18,7 +18,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { ArrowLeft, CheckSquare, Square } from 'lucide-react'
import { toast } from 'sonner'
import { useAuthStore } from '@/stores/auth.store'
import type { LessonPlan, LessonPlanSection } from '@/types/lesson'
import type { LessonPlan, LessonPlanSection, LessonSession } from '@/types/lesson'
export const Route = createFileRoute('/_authenticated/lessons/sessions/$sessionId')({
component: SessionDetailPage,
@@ -126,7 +126,7 @@ function SessionDetailPage() {
return (
<div className="space-y-6 max-w-3xl">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/sessions', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/sessions', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex-1">
@@ -137,7 +137,7 @@ function SessionDetailPage() {
<Link
to="/lessons/enrollments/$enrollmentId"
params={{ enrollmentId: enrollment.id }}
search={{} as any}
search={{} as Record<string, unknown>}
className="text-sm text-primary hover:underline"
>
View Enrollment
@@ -209,7 +209,12 @@ function SessionDetailPage() {
// ─── Notes Card ───────────────────────────────────────────────────────────────
function NotesCard({ session, canEdit, onSave, saving }: any) {
function NotesCard({ session, canEdit, onSave, saving }: {
session: LessonSession
canEdit: boolean
onSave: (data: Record<string, unknown>) => void
saving: boolean
}) {
const [instructorNotes, setInstructorNotes] = useState(session.instructorNotes ?? '')
const [memberNotes, setMemberNotes] = useState(session.memberNotes ?? '')
const [homeworkAssigned, setHomeworkAssigned] = useState(session.homeworkAssigned ?? '')

View File

@@ -92,13 +92,13 @@ function SessionsPage() {
const weekEnd = endOfWeek(weekStart, { weekStartsOn: 0 })
function setView(v: 'list' | 'week') {
navigate({ to: '/lessons/sessions', search: { ...search, view: v, page: 1 } as any })
navigate({ to: '/lessons/sessions', search: { ...search, view: v, page: 1 } as Record<string, unknown> })
}
function handleStatusChange(v: string) {
const s = v === 'all' ? '' : v
setStatusFilter(s)
navigate({ to: '/lessons/sessions', search: { ...search, status: s || undefined, page: 1 } as any })
navigate({ to: '/lessons/sessions', search: { ...search, status: s || undefined, page: 1 } as Record<string, unknown> })
}
// List query
@@ -189,7 +189,7 @@ function SessionsPage() {
order={params.order}
onPageChange={setPage}
onSort={setSort}
onRowClick={(s) => navigate({ to: '/lessons/sessions/$sessionId', params: { sessionId: s.id }, search: {} as any })}
onRowClick={(s) => navigate({ to: '/lessons/sessions/$sessionId', params: { sessionId: s.id }, search: {} as Record<string, unknown> })}
/>
</>
)}
@@ -249,7 +249,7 @@ function SessionsPage() {
{daySessions.map((s) => (
<button
key={s.id}
onClick={() => navigate({ to: '/lessons/sessions/$sessionId', params: { sessionId: s.id }, search: {} as any })}
onClick={() => navigate({ to: '/lessons/sessions/$sessionId', params: { sessionId: s.id }, search: {} as Record<string, unknown> })}
className={`w-full text-left rounded border px-1.5 py-1 text-xs hover:opacity-80 transition-opacity ${STATUS_COLORS[s.status] ?? STATUS_COLORS.scheduled}`}
>
<p className="font-semibold">{formatTime(s.scheduledTime)}</p>

View File

@@ -18,7 +18,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
import { ArrowLeft, Search, X, Zap } from 'lucide-react'
import { toast } from 'sonner'
import { useAuthStore } from '@/stores/auth.store'
import type { LessonPlanTemplate } from '@/types/lesson'
import type { Enrollment, LessonPlanTemplate } from '@/types/lesson'
import type { MemberWithAccount } from '@/api/members'
export const Route = createFileRoute('/_authenticated/lessons/templates/$templateId')({
@@ -42,7 +42,7 @@ function TemplateDetailPage() {
return (
<div className="space-y-6 max-w-3xl">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/templates', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/templates', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex-1">
@@ -93,7 +93,7 @@ function TemplateDetailPage() {
// ─── Edit Form ────────────────────────────────────────────────────────────────
function EditTemplateForm({ template, templateId, queryClient }: { template: LessonPlanTemplate; templateId: string; queryClient: any }) {
function EditTemplateForm({ template, templateId, queryClient }: { template: LessonPlanTemplate; templateId: string; queryClient: ReturnType<typeof useQueryClient> }) {
const [name, setName] = useState(template.name)
const [description, setDescription] = useState(template.description ?? '')
const [instrument, setInstrument] = useState(template.instrument ?? '')
@@ -218,7 +218,7 @@ function InstantiateDialog({ template, templateId, open, onClose }: {
}),
onSuccess: (plan) => {
toast.success('Plan created from template')
navigate({ to: '/lessons/plans/$planId', params: { planId: plan.id }, search: {} as any })
navigate({ to: '/lessons/plans/$planId', params: { planId: plan.id }, search: {} as Record<string, unknown> })
},
onError: (err) => toast.error(err.message),
})
@@ -293,7 +293,7 @@ function InstantiateDialog({ template, templateId, open, onClose }: {
<SelectTrigger><SelectValue placeholder="Not linked to enrollment" /></SelectTrigger>
<SelectContent>
<SelectItem value="none">Not linked to enrollment</SelectItem>
{enrollments.map((e: any) => (
{enrollments.map((e: Enrollment) => (
<SelectItem key={e.id} value={e.id}>Enrollment {e.id.slice(-6)}</SelectItem>
))}
</SelectContent>

View File

@@ -81,8 +81,8 @@ function TemplatesListPage() {
const columnsWithActions: Column<LessonPlanTemplate>[] = [
...columns,
...(canAdmin ? [{
key: 'actions' as any,
header: '' as any,
key: 'actions',
header: '',
render: (t: LessonPlanTemplate) => (
<Button variant="ghost" size="sm" onClick={(e) => { e.stopPropagation(); deleteMutation.mutate(t.id) }}>
<Trash2 className="h-4 w-4 text-destructive" />
@@ -96,7 +96,7 @@ function TemplatesListPage() {
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">Lesson Plan Templates</h1>
{canAdmin && (
<Button onClick={() => navigate({ to: '/lessons/templates/new', search: {} as any })}>
<Button onClick={() => navigate({ to: '/lessons/templates/new', search: {} as Record<string, unknown> })}>
<Plus className="mr-2 h-4 w-4" />New Template
</Button>
)}
@@ -126,7 +126,7 @@ function TemplatesListPage() {
order={params.order}
onPageChange={setPage}
onSort={setSort}
onRowClick={(t) => navigate({ to: '/lessons/templates/$templateId', params: { templateId: t.id }, search: {} as any })}
onRowClick={(t) => navigate({ to: '/lessons/templates/$templateId', params: { templateId: t.id }, search: {} as Record<string, unknown> })}
/>
</div>
)

View File

@@ -45,7 +45,7 @@ function NewTemplatePage() {
}),
onSuccess: (template) => {
toast.success('Template created')
navigate({ to: '/lessons/templates/$templateId', params: { templateId: template.id }, search: {} as any })
navigate({ to: '/lessons/templates/$templateId', params: { templateId: template.id }, search: {} as Record<string, unknown> })
},
onError: (err) => toast.error(err.message),
})
@@ -63,7 +63,7 @@ function NewTemplatePage() {
return (
<form onSubmit={handleSubmit} className="space-y-6 max-w-3xl">
<div className="flex items-center gap-3">
<Button type="button" variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/templates', search: {} as any })}>
<Button type="button" variant="ghost" size="sm" onClick={() => navigate({ to: '/lessons/templates', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<h1 className="text-2xl font-bold">New Template</h1>
@@ -112,7 +112,7 @@ function NewTemplatePage() {
<Button type="submit" disabled={mutation.isPending || !name.trim() || !allSectionsValid} size="lg">
{mutation.isPending ? 'Creating...' : 'Create Template'}
</Button>
<Button type="button" variant="secondary" size="lg" onClick={() => navigate({ to: '/lessons/templates', search: {} as any })}>
<Button type="button" variant="secondary" size="lg" onClick={() => navigate({ to: '/lessons/templates', search: {} as Record<string, unknown> })}>
Cancel
</Button>
</div>

View File

@@ -70,11 +70,11 @@ function statusBadge(status: string) {
return <Badge variant={variants[status] ?? 'outline'}>{status}</Badge>
}
const enrollmentColumns: Column<Enrollment>[] = [
const enrollmentColumns: Column<Enrollment & { instructorName?: string; slotInfo?: string; lessonTypeName?: string }>[] = [
{ key: 'status', header: 'Status', sortable: true, render: (e) => statusBadge(e.status) },
{ key: 'instructor_name', header: 'Instructor', render: (e) => <>{(e as any).instructorName ?? e.instructorId}</> },
{ key: 'slot_info', header: 'Day / Time', render: (e) => <>{(e as any).slotInfo ?? '—'}</> },
{ key: 'lesson_type', header: 'Lesson', render: (e) => <>{(e as any).lessonTypeName ?? '—'}</> },
{ key: 'instructor_name', header: 'Instructor', render: (e) => <>{e.instructorName ?? e.instructorId}</> },
{ key: 'slot_info', header: 'Day / Time', render: (e) => <>{e.slotInfo ?? '—'}</> },
{ key: 'lesson_type', header: 'Lesson', render: (e) => <>{e.lessonTypeName ?? '—'}</> },
{ 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.billingUnit}` : <span className="text-muted-foreground"></span>}</> },
]
@@ -161,7 +161,7 @@ function MemberDetailPage() {
})
function setTab(t: string) {
navigate({ to: '/members/$memberId', params: { memberId }, search: { tab: t } as any })
navigate({ to: '/members/$memberId', params: { memberId }, search: { tab: t } as Record<string, unknown> })
}
if (isLoading) {
@@ -188,7 +188,7 @@ function MemberDetailPage() {
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/accounts/$accountId/members', params: { accountId: member.accountId }, search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/accounts/$accountId/members', params: { accountId: member.accountId }, search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
@@ -293,7 +293,7 @@ function MemberDetailPage() {
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">{enrollmentsData?.pagination.total ?? 0} enrollment(s)</p>
{hasPermission('lessons.edit') && (
<Button size="sm" onClick={() => navigate({ to: '/lessons/enrollments/new', search: { memberId } as any })}>
<Button size="sm" onClick={() => navigate({ to: '/lessons/enrollments/new', search: { memberId } as Record<string, unknown> })}>
<Plus className="h-4 w-4 mr-1" />Enroll
</Button>
)}
@@ -307,7 +307,7 @@ function MemberDetailPage() {
total={enrollmentsData?.data?.length ?? 0}
onPageChange={() => {}}
onSort={() => {}}
onRowClick={(e) => navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId: e.id }, search: {} as any })}
onRowClick={(e) => navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId: e.id }, search: {} as Record<string, unknown> })}
/>
</div>
)}

View File

@@ -84,7 +84,7 @@ function MembersListPage() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => navigate({ to: '/members/$memberId', params: { memberId: row.id }, search: {} as any })}>
<DropdownMenuItem onClick={() => navigate({ to: '/members/$memberId', params: { memberId: row.id }, search: {} as Record<string, unknown> })}>
<Pencil className="mr-2 h-4 w-4" />
Edit
</DropdownMenuItem>
@@ -134,7 +134,7 @@ function MembersListPage() {
order={params.order}
onPageChange={setPage}
onSort={setSort}
onRowClick={(member) => navigate({ to: '/members/$memberId', params: { memberId: member.id }, search: {} as any })}
onRowClick={(member) => navigate({ to: '/members/$memberId', params: { memberId: member.id }, search: {} as Record<string, unknown> })}
/>
</div>
)

View File

@@ -96,12 +96,12 @@ function RepairBatchDetailPage() {
const totalActual = tickets.reduce((sum, t) => sum + (t.actualCost ? parseFloat(t.actualCost) : 0), 0)
function handleTicketClick(ticket: RepairTicket) {
navigate({ to: '/repairs/$ticketId', params: { ticketId: ticket.id }, search: {} as any })
navigate({ to: '/repairs/$ticketId', params: { ticketId: ticket.id }, search: {} as Record<string, unknown> })
}
function handleAddRepair() {
// Navigate to new repair with batch and account pre-linked
navigate({ to: '/repairs/new', search: { batchId, batchName: batch!.batchNumber ?? '', accountId: batch!.accountId, contactName: batch!.contactName ?? '' } as any })
navigate({ to: '/repairs/new', search: { batchId, batchName: batch!.batchNumber ?? '', accountId: batch!.accountId, contactName: batch!.contactName ?? '' } as Record<string, unknown> })
}
async function generateBatchPdf() {
@@ -233,7 +233,7 @@ function RepairBatchDetailPage() {
return (
<div className="space-y-6 max-w-5xl">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/repair-batches', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/repair-batches', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex-1">

View File

@@ -75,7 +75,7 @@ function RepairBatchesListPage() {
}
function handleRowClick(batch: RepairBatch) {
navigate({ to: '/repair-batches/$batchId', params: { batchId: batch.id }, search: {} as any })
navigate({ to: '/repair-batches/$batchId', params: { batchId: batch.id }, search: {} as Record<string, unknown> })
}
return (

View File

@@ -50,7 +50,7 @@ function NewRepairBatchPage() {
mutationFn: repairBatchMutations.create,
onSuccess: (batch) => {
toast.success('Repair batch created')
navigate({ to: '/repair-batches/$batchId', params: { batchId: batch.id }, search: {} as any })
navigate({ to: '/repair-batches/$batchId', params: { batchId: batch.id }, search: {} as Record<string, unknown> })
},
onError: (err) => toast.error(err.message),
})
@@ -78,7 +78,7 @@ function NewRepairBatchPage() {
return (
<div className="space-y-6 max-w-3xl">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/repair-batches', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/repair-batches', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<h1 className="text-2xl font-bold">New Repair Batch</h1>
@@ -176,7 +176,7 @@ function NewRepairBatchPage() {
<Button type="submit" disabled={mutation.isPending} size="lg">
{mutation.isPending ? 'Creating...' : 'Create Batch'}
</Button>
<Button variant="secondary" type="button" size="lg" onClick={() => navigate({ to: '/repair-batches', search: {} as any })}>
<Button variant="secondary" type="button" size="lg" onClick={() => navigate({ to: '/repair-batches', search: {} as Record<string, unknown> })}>
Cancel
</Button>
</div>

View File

@@ -175,7 +175,7 @@ function RepairTicketDetailPage() {
<div className="space-y-4 max-w-5xl">
{/* Header */}
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/repairs', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/repairs', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex-1">

View File

@@ -129,7 +129,7 @@ function RepairsListPage() {
}
function handleRowClick(ticket: RepairTicket) {
navigate({ to: '/repairs/$ticketId', params: { ticketId: ticket.id }, search: {} as any })
navigate({ to: '/repairs/$ticketId', params: { ticketId: ticket.id }, search: {} as Record<string, unknown> })
}
return (
@@ -137,7 +137,7 @@ function RepairsListPage() {
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">Repairs</h1>
{hasPermission('repairs.edit') && (
<Button onClick={() => navigate({ to: '/repairs/new', search: {} as any })}>
<Button onClick={() => navigate({ to: '/repairs/new', search: {} as Record<string, unknown> })}>
<Plus className="mr-2 h-4 w-4" />
New Repair
</Button>

View File

@@ -136,7 +136,7 @@ function NewRepairPage() {
},
onSuccess: (ticket) => {
toast.success('Repair ticket created')
navigate({ to: '/repairs/$ticketId', params: { ticketId: ticket.id }, search: {} as any })
navigate({ to: '/repairs/$ticketId', params: { ticketId: ticket.id }, search: {} as Record<string, unknown> })
},
onError: (err) => toast.error(err.message),
})
@@ -210,7 +210,7 @@ function NewRepairPage() {
return (
<div className="space-y-6 max-w-4xl">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/repairs', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/repairs', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<h1 className="text-2xl font-bold">New Repair Ticket</h1>
@@ -314,7 +314,7 @@ function NewRepairPage() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Condition at Intake</Label>
<Select onValueChange={(v) => setValue('conditionIn', v as any)}>
<Select onValueChange={(v) => setValue('conditionIn', v as 'excellent' | 'good' | 'fair' | 'poor')}>
<SelectTrigger><SelectValue placeholder="Select condition" /></SelectTrigger>
<SelectContent>
<SelectItem value="excellent">Excellent</SelectItem>
@@ -486,7 +486,7 @@ function NewRepairPage() {
<Button type="submit" disabled={mutation.isPending} size="lg">
{mutation.isPending ? 'Creating...' : 'Create Ticket'}
</Button>
<Button variant="secondary" type="button" size="lg" onClick={() => navigate({ to: '/repairs', search: {} as any })}>
<Button variant="secondary" type="button" size="lg" onClick={() => navigate({ to: '/repairs', search: {} as Record<string, unknown> })}>
Cancel
</Button>
</div>

View File

@@ -100,7 +100,7 @@ function RoleDetailPage() {
return (
<div className="space-y-6 max-w-2xl">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/roles', search: {} as any })}>
<Button variant="ghost" size="sm" onClick={() => navigate({ to: '/roles', search: {} as Record<string, unknown> })}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
@@ -177,7 +177,7 @@ function RoleDetailPage() {
<Button onClick={handleSave} disabled={updateMutation.isPending}>
{updateMutation.isPending ? 'Saving...' : 'Save Changes'}
</Button>
<Button variant="secondary" onClick={() => navigate({ to: '/roles', search: {} as any })}>
<Button variant="secondary" onClick={() => navigate({ to: '/roles', search: {} as Record<string, unknown> })}>
Cancel
</Button>
</div>

View File

@@ -29,7 +29,7 @@ function NewRolePage() {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: rbacKeys.roles })
toast.success('Role created')
navigate({ to: '/roles', search: {} as any })
navigate({ to: '/roles', search: {} as Record<string, unknown> })
},
onError: (err) => toast.error(err.message),
})
@@ -153,7 +153,7 @@ function NewRolePage() {
<Button onClick={handleSubmit} disabled={mutation.isPending}>
{mutation.isPending ? 'Creating...' : 'Create Role'}
</Button>
<Button variant="secondary" onClick={() => navigate({ to: '/roles', search: {} as any })}>
<Button variant="secondary" onClick={() => navigate({ to: '/roles', search: {} as Record<string, unknown> })}>
Cancel
</Button>
</div>

View File

@@ -7,7 +7,7 @@ export const Route = createFileRoute('/login')({
beforeLoad: () => {
const { token } = useAuthStore.getState()
if (token) {
throw redirect({ to: '/accounts', search: {} as any })
throw redirect({ to: '/accounts', search: {} as Record<string, unknown> })
}
},
component: LoginPage,
@@ -30,7 +30,7 @@ function LoginPage() {
const res = await login(email, password)
setAuth(res.token, res.user)
await router.invalidate()
await router.navigate({ to: '/accounts', search: {} as any, replace: true })
await router.navigate({ to: '/accounts', search: {} as Record<string, unknown>, replace: true })
} catch (err) {
setError(err instanceof Error ? err.message : 'Login failed')
} finally {