diff --git a/src/api/api-paths.ts b/src/api/api-paths.ts index dcc144bc..72240ef3 100644 --- a/src/api/api-paths.ts +++ b/src/api/api-paths.ts @@ -31,6 +31,7 @@ export const API_PATHS = { boardSearch: "workorders/board/search", boardPatch: (id: string | number) => `workorders/${id}/board`, boardDetail: (id: string | number) => `workorders/${id}/detail`, + completionTemplates: "workorders/completion-templates", comments: (id: string | number) => `workorders/${id}/comments`, commentItem: (workOrderId: string | number, commentId: string | number) => `workorders/${workOrderId}/comments/${commentId}`, diff --git a/src/app/(protected)/settings/_components/services-registry-components.tsx b/src/app/(protected)/settings/_components/services-registry-components.tsx index 81fff5cb..e52d5087 100644 --- a/src/app/(protected)/settings/_components/services-registry-components.tsx +++ b/src/app/(protected)/settings/_components/services-registry-components.tsx @@ -24,9 +24,9 @@ import { } from "@mui/material"; import { Droplets, Hammer, Settings2, Wrench, Zap } from "lucide-react"; import type { DropdownOption } from "@/domain/settings/dropdown-options/types/dropdown-option"; -import type { TaskTemplate } from "@/domain/settings/task-templates/types/task-template"; import { SERVICE_WORK_ORDER_TYPES, + type CompletionDocTemplateOption, type Service, type ServiceWorkOrderType, } from "@/domain/services/types/service"; @@ -142,7 +142,7 @@ type EditorProps = { form: ServiceForm; formError: string; trades: DropdownOption[]; - templates: TaskTemplate[]; + templates: CompletionDocTemplateOption[]; isSaving: boolean; onClose: () => void; onSave: () => void; diff --git a/src/domain/services/api/services-api.ts b/src/domain/services/api/services-api.ts index 8c5aa96d..3c1e6c82 100644 --- a/src/domain/services/api/services-api.ts +++ b/src/domain/services/api/services-api.ts @@ -1,8 +1,16 @@ import { apiGet, apiPost, apiPut } from "@/api/api"; import { API_PATHS } from "@/api/api-paths"; import { handleApiResponse } from "@/api/handle-api-response"; -import { mapService, mapServices } from "@/domain/services/mappers/service-mapper"; -import type { Service, ServiceInput } from "@/domain/services/types/service"; +import { + mapCompletionDocTemplates, + mapService, + mapServices, +} from "@/domain/services/mappers/service-mapper"; +import type { + CompletionDocTemplateOption, + Service, + ServiceInput, +} from "@/domain/services/types/service"; const SERVICE_WORK_ORDER_TYPE_API = { PM: 2, @@ -27,6 +35,11 @@ export const servicesApi = { return mapServices(handleApiResponse(data)); }, + getCompletionTemplates: async (): Promise => { + const data = await apiGet(API_PATHS.workOrder.completionTemplates); + return mapCompletionDocTemplates(handleApiResponse(data)); + }, + create: async (input: ServiceInput): Promise => { const data = await apiPost(API_PATHS.services.list, toApiInput(input)); return mapService(handleApiResponse(data)); diff --git a/src/domain/services/mappers/service-mapper.ts b/src/domain/services/mappers/service-mapper.ts index c8230077..d7b37603 100644 --- a/src/domain/services/mappers/service-mapper.ts +++ b/src/domain/services/mappers/service-mapper.ts @@ -1,4 +1,5 @@ import type { + CompletionDocTemplateOption, LinkedServiceTemplate, Service, ServiceWorkOrderType, @@ -87,3 +88,26 @@ export function mapServices(raw: unknown): Service[] { ? payload.map(mapService).filter((service) => service.id !== "") : []; } + +export function mapCompletionDocTemplates(raw: unknown): CompletionDocTemplateOption[] { + const payload = Array.isArray(raw) + ? raw + : (() => { + const record = asRecord(raw); + return record.items ?? record.Items ?? record.data ?? record.Data; + })(); + return Array.isArray(payload) + ? payload.flatMap((item) => { + const record = asRecord(item); + const id = readId(record); + return id === "" + ? [] + : [ + { + id, + name: readString(record, "name", "Name"), + } satisfies CompletionDocTemplateOption, + ]; + }) + : []; +} diff --git a/src/domain/services/types/service.ts b/src/domain/services/types/service.ts index bb3650e9..6d198466 100644 --- a/src/domain/services/types/service.ts +++ b/src/domain/services/types/service.ts @@ -7,6 +7,8 @@ export interface LinkedServiceTemplate { name: string; } +export type CompletionDocTemplateOption = LinkedServiceTemplate; + export interface Service { id: string | number; name: string; diff --git a/src/domain/services/use-cases/use-services-registry-controller.ts b/src/domain/services/use-cases/use-services-registry-controller.ts index 51dc19ae..3b187633 100644 --- a/src/domain/services/use-cases/use-services-registry-controller.ts +++ b/src/domain/services/use-cases/use-services-registry-controller.ts @@ -2,10 +2,10 @@ import { useEffect, useMemo, useState } from "react"; import { useAuthContext } from "@/providers/auth-context"; import { getPrimaryUserRole, isAdminUser } from "@/lib/auth/user-utils"; import { useDropdownOptionsByCategory } from "@/domain/settings/dropdown-options/use-cases/use-dropdown-options-by-category"; -import { useTaskTemplates } from "@/domain/settings/task-templates/use-cases/use-task-templates"; import type { Service, ServiceInput, ServiceWorkOrderType } from "@/domain/services/types/service"; import { useCreateService, + useCompletionDocTemplates, useDeactivateService, useServices, useUpdateService, @@ -95,7 +95,7 @@ export function useServicesRegistryController() { const queryStatus = status === "active" ? true : status === "inactive" ? false : undefined; const servicesQuery = useServices(queryStatus); const { data: trades = [] } = useDropdownOptionsByCategory("Trade"); - const { data: templates = [] } = useTaskTemplates(); + const { data: templates = [] } = useCompletionDocTemplates(); const createService = useCreateService(); const updateService = useUpdateService(); const deactivateService = useDeactivateService(); diff --git a/src/domain/services/use-cases/use-services.ts b/src/domain/services/use-cases/use-services.ts index 2a20156c..a82b702c 100644 --- a/src/domain/services/use-cases/use-services.ts +++ b/src/domain/services/use-cases/use-services.ts @@ -1,7 +1,11 @@ import { useMutation, useQuery, useQueryClient, type UseQueryResult } from "@tanstack/react-query"; import { toast } from "react-toastify"; import { servicesApi } from "@/domain/services/api/services-api"; -import type { Service, ServiceInput } from "@/domain/services/types/service"; +import type { + CompletionDocTemplateOption, + Service, + ServiceInput, +} from "@/domain/services/types/service"; import { queryKeys } from "@/infra/query-key/query-key"; export function useServices(isActive?: boolean): UseQueryResult { @@ -12,6 +16,14 @@ export function useServices(isActive?: boolean): UseQueryResult { + return useQuery({ + queryKey: queryKeys.services.completionTemplates(), + queryFn: servicesApi.getCompletionTemplates, + staleTime: 5 * 60 * 1000, + }); +} + export function useCreateService() { const queryClient = useQueryClient(); return useMutation({ diff --git a/src/test/domain/services/api/services-api.test.ts b/src/test/domain/services/api/services-api.test.ts index 5f17b9d7..74f37875 100644 --- a/src/test/domain/services/api/services-api.test.ts +++ b/src/test/domain/services/api/services-api.test.ts @@ -77,4 +77,13 @@ describe("servicesApi", () => { supportedWorkOrderTypes: [2], }); }); + + it("loads active Completion Document Template options from the completion route", async () => { + apiGet.mockResolvedValue([{ Id: 4, Name: "HVAC checklist", IsActive: true }]); + + const templates = await servicesApi.getCompletionTemplates(); + + expect(apiGet).toHaveBeenCalledWith(API_PATHS.workOrder.completionTemplates); + expect(templates).toEqual([{ id: 4, name: "HVAC checklist" }]); + }); });