Add repair list filters, template management page, and backend filter support

Repairs list now has a filter panel with status (defaults to active only),
condition, batch/individual toggle, and date range filters for intake and
promised dates. Added Batch column to the repairs table. Backend list
endpoint accepts filter query params for status, condition, dates, and
batch membership. Template management page (admin only) with CRUD for
common repair services (rehair, string change, etc.) with instrument
type, size, and default pricing. Sidebar updated with Repair Templates
link gated on repairs.admin permission.
This commit is contained in:
Ryan Moon
2026-03-29 10:13:38 -05:00
parent 7d55fbe7ef
commit 01cff80f2b
7 changed files with 527 additions and 17 deletions

View File

@@ -27,8 +27,23 @@ export const repairRoutes: FastifyPluginAsync = async (app) => {
})
app.get('/repair-tickets', { preHandler: [app.authenticate, app.requirePermission('repairs.view')] }, async (request, reply) => {
const params = PaginationSchema.parse(request.query)
const result = await RepairTicketService.list(app.db, request.companyId, params)
const query = request.query as Record<string, string | undefined>
const params = PaginationSchema.parse(query)
const filters = {
status: query.status?.split(',').filter(Boolean),
conditionIn: query.conditionIn?.split(',').filter(Boolean),
isBatch: query.isBatch === 'true' ? true : query.isBatch === 'false' ? false : undefined,
batchNumber: query.batchNumber,
intakeDateFrom: query.intakeDateFrom,
intakeDateTo: query.intakeDateTo,
promisedDateFrom: query.promisedDateFrom,
promisedDateTo: query.promisedDateTo,
completedDateFrom: query.completedDateFrom,
completedDateTo: query.completedDateTo,
}
const result = await RepairTicketService.list(app.db, request.companyId, params, filters)
return reply.send(result)
})

View File

@@ -1,4 +1,4 @@
import { eq, and, count, type Column } from 'drizzle-orm'
import { eq, and, count, inArray, isNull, isNotNull, gte, lte, type Column, type SQL } from 'drizzle-orm'
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import {
repairTickets,
@@ -83,18 +83,50 @@ export const RepairTicketService = {
return ticket ?? null
},
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput) {
const baseWhere = eq(repairTickets.companyId, companyId)
const searchCondition = params.q
? buildSearchCondition(params.q, [
repairTickets.ticketNumber,
repairTickets.customerName,
repairTickets.customerPhone,
repairTickets.instrumentDescription,
repairTickets.serialNumber,
])
: undefined
const where = searchCondition ? and(baseWhere, searchCondition) : baseWhere
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput, filters?: {
status?: string[]
conditionIn?: string[]
isBatch?: boolean
batchNumber?: string
intakeDateFrom?: string
intakeDateTo?: string
promisedDateFrom?: string
promisedDateTo?: string
completedDateFrom?: string
completedDateTo?: string
}) {
const conditions: SQL[] = [eq(repairTickets.companyId, companyId)]
if (params.q) {
const search = buildSearchCondition(params.q, [
repairTickets.ticketNumber,
repairTickets.customerName,
repairTickets.customerPhone,
repairTickets.instrumentDescription,
repairTickets.serialNumber,
])
if (search) conditions.push(search)
}
if (filters?.status?.length) {
conditions.push(inArray(repairTickets.status, filters.status as any))
}
if (filters?.conditionIn?.length) {
conditions.push(inArray(repairTickets.conditionIn, filters.conditionIn as any))
}
if (filters?.isBatch === true) {
conditions.push(isNotNull(repairTickets.repairBatchId))
} else if (filters?.isBatch === false) {
conditions.push(isNull(repairTickets.repairBatchId))
}
if (filters?.intakeDateFrom) conditions.push(gte(repairTickets.intakeDate, new Date(filters.intakeDateFrom)))
if (filters?.intakeDateTo) conditions.push(lte(repairTickets.intakeDate, new Date(filters.intakeDateTo)))
if (filters?.promisedDateFrom) conditions.push(gte(repairTickets.promisedDate, new Date(filters.promisedDateFrom)))
if (filters?.promisedDateTo) conditions.push(lte(repairTickets.promisedDate, new Date(filters.promisedDateTo)))
if (filters?.completedDateFrom) conditions.push(gte(repairTickets.completedDate, new Date(filters.completedDateFrom)))
if (filters?.completedDateTo) conditions.push(lte(repairTickets.completedDate, new Date(filters.completedDateTo)))
const where = and(...conditions)
const sortableColumns: Record<string, Column> = {
ticket_number: repairTickets.ticketNumber,