Add repairs domain with tickets, line items, batches, and service templates

Full-stack implementation of instrument repair tracking: DB schema with
repair_ticket, repair_line_item, repair_batch, and repair_service_template
tables. Backend services and routes with pagination/search/sort. 20 API
tests covering CRUD, status workflow, line items, and batch operations.
Admin frontend with ticket list, detail with status progression, line item
management, batch list/detail with approval workflow, and new ticket form
with searchable account picker and intake photo uploads.
This commit is contained in:
Ryan Moon
2026-03-29 09:12:40 -05:00
parent 1d48f0befa
commit f17bbff02c
20 changed files with 2791 additions and 1 deletions

View File

@@ -0,0 +1,103 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { RepairTicket, RepairLineItem, RepairBatch } from '@/types/repair'
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
// --- Repair Tickets ---
export const repairTicketKeys = {
all: ['repair-tickets'] as const,
list: (params: PaginationInput) => [...repairTicketKeys.all, 'list', params] as const,
detail: (id: string) => [...repairTicketKeys.all, 'detail', id] as const,
}
export function repairTicketListOptions(params: PaginationInput) {
return queryOptions({
queryKey: repairTicketKeys.list(params),
queryFn: () => api.get<PaginatedResponse<RepairTicket>>('/v1/repair-tickets', params),
})
}
export function repairTicketDetailOptions(id: string) {
return queryOptions({
queryKey: repairTicketKeys.detail(id),
queryFn: () => api.get<RepairTicket>(`/v1/repair-tickets/${id}`),
})
}
export const repairTicketMutations = {
create: (data: Record<string, unknown>) =>
api.post<RepairTicket>('/v1/repair-tickets', data),
update: (id: string, data: Record<string, unknown>) =>
api.patch<RepairTicket>(`/v1/repair-tickets/${id}`, data),
updateStatus: (id: string, status: string) =>
api.post<RepairTicket>(`/v1/repair-tickets/${id}/status`, { status }),
delete: (id: string) =>
api.del<RepairTicket>(`/v1/repair-tickets/${id}`),
}
// --- Repair Line Items ---
export const repairLineItemKeys = {
all: (ticketId: string) => ['repair-tickets', ticketId, 'line-items'] as const,
list: (ticketId: string, params: PaginationInput) => [...repairLineItemKeys.all(ticketId), params] as const,
}
export function repairLineItemListOptions(ticketId: string, params: PaginationInput) {
return queryOptions({
queryKey: repairLineItemKeys.list(ticketId, params),
queryFn: () => api.get<PaginatedResponse<RepairLineItem>>(`/v1/repair-tickets/${ticketId}/line-items`, params),
})
}
export const repairLineItemMutations = {
create: (ticketId: string, data: Record<string, unknown>) =>
api.post<RepairLineItem>(`/v1/repair-tickets/${ticketId}/line-items`, data),
update: (id: string, data: Record<string, unknown>) =>
api.patch<RepairLineItem>(`/v1/repair-line-items/${id}`, data),
delete: (id: string) =>
api.del<RepairLineItem>(`/v1/repair-line-items/${id}`),
}
// --- Repair Batches ---
export const repairBatchKeys = {
all: ['repair-batches'] as const,
list: (params: PaginationInput) => [...repairBatchKeys.all, 'list', params] as const,
detail: (id: string) => [...repairBatchKeys.all, 'detail', id] as const,
tickets: (batchId: string, params: PaginationInput) => [...repairBatchKeys.all, batchId, 'tickets', params] as const,
}
export function repairBatchListOptions(params: PaginationInput) {
return queryOptions({
queryKey: repairBatchKeys.list(params),
queryFn: () => api.get<PaginatedResponse<RepairBatch>>('/v1/repair-batches', params),
})
}
export function repairBatchDetailOptions(id: string) {
return queryOptions({
queryKey: repairBatchKeys.detail(id),
queryFn: () => api.get<RepairBatch>(`/v1/repair-batches/${id}`),
})
}
export function repairBatchTicketsOptions(batchId: string, params: PaginationInput) {
return queryOptions({
queryKey: repairBatchKeys.tickets(batchId, params),
queryFn: () => api.get<PaginatedResponse<RepairTicket>>(`/v1/repair-batches/${batchId}/tickets`, params),
})
}
export const repairBatchMutations = {
create: (data: Record<string, unknown>) =>
api.post<RepairBatch>('/v1/repair-batches', data),
update: (id: string, data: Record<string, unknown>) =>
api.patch<RepairBatch>(`/v1/repair-batches/${id}`, data),
updateStatus: (id: string, status: string) =>
api.post<RepairBatch>(`/v1/repair-batches/${id}/status`, { status }),
approve: (id: string) =>
api.post<RepairBatch>(`/v1/repair-batches/${id}/approve`, {}),
reject: (id: string) =>
api.post<RepairBatch>(`/v1/repair-batches/${id}/reject`, {}),
}