shoc-frontend-new/src/domain/services/use-cases/use-services-registry-controller.ts

224 lines
6.7 KiB
TypeScript
Raw Normal View History

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 type { Service, ServiceInput, ServiceWorkOrderType } from "@/domain/services/types/service";
import {
useCreateService,
useCompletionDocTemplates,
useDeactivateService,
useServices,
useUpdateService,
} from "@/domain/services/use-cases/use-services";
export type ServiceStatus = "all" | "active" | "inactive";
export type ServiceForm = Omit<ServiceInput, "completionDocTemplateId"> & {
completionDocTemplateId: string | number | null;
};
export const EMPTY_SERVICE_FORM: ServiceForm = {
name: "",
trade: "",
iconKey: "",
requiresCompletionDocument: false,
completionDocTemplateId: null,
supportedWorkOrderTypes: ["PM", "Reactive", "Emergency"],
};
export const DEFAULT_ICON_BY_TRADE: Record<string, string> = {
HVAC: "droplets",
Plumbing: "droplets",
Electrical: "zap",
"General Building": "hammer",
};
export function buildServiceInput(form: ServiceForm): {
input: ServiceInput | null;
error: string;
} {
const name = form.name.trim();
if (!name || !form.trade || !form.iconKey) {
return { input: null, error: "Service Name, Trade, and Icon are required." };
}
if (!form.supportedWorkOrderTypes.length) {
return { input: null, error: "Select at least one Work Order Type." };
}
if (form.requiresCompletionDocument && form.completionDocTemplateId == null) {
return {
input: null,
error: "Select a completion document template when the document is required.",
};
}
const templateId = form.completionDocTemplateId;
return {
input: {
...form,
name,
completionDocTemplateId: templateId == null || templateId === "" ? null : Number(templateId),
},
error: "",
};
}
function emptyForm(): ServiceForm {
return {
...EMPTY_SERVICE_FORM,
supportedWorkOrderTypes: [...EMPTY_SERVICE_FORM.supportedWorkOrderTypes],
};
}
function formFromService(service: Service): ServiceForm {
return {
name: service.name,
trade: service.trade,
iconKey: service.iconKey,
requiresCompletionDocument: service.requiresCompletionDocument,
completionDocTemplateId: service.completionDocTemplate?.id ?? null,
supportedWorkOrderTypes: [...service.supportedWorkOrderTypes],
};
}
export function useServicesRegistryController() {
const { user } = useAuthContext();
const role = getPrimaryUserRole(user?.userRoles).toLowerCase();
const canManage = isAdminUser(user?.userRoles) || role === "scheduler";
const canDeactivate = isAdminUser(user?.userRoles);
const [status, setStatus] = useState<ServiceStatus>("active");
const [search, setSearch] = useState("");
const [selected, setSelected] = useState<Service | null>(null);
const [editorMode, setEditorMode] = useState<"add" | "edit" | null>(null);
const [form, setForm] = useState<ServiceForm>(emptyForm);
const [iconTouched, setIconTouched] = useState(false);
const [formError, setFormError] = useState("");
const [deactivateTarget, setDeactivateTarget] = useState<Service | null>(null);
const queryStatus = status === "active" ? true : status === "inactive" ? false : undefined;
const servicesQuery = useServices(queryStatus);
const { data: trades = [] } = useDropdownOptionsByCategory("Trade");
const { data: templates = [] } = useCompletionDocTemplates();
const createService = useCreateService();
const updateService = useUpdateService();
const deactivateService = useDeactivateService();
const filteredServices = useMemo(() => {
const needle = search.trim().toLowerCase();
if (!needle) return servicesQuery.data ?? [];
return (servicesQuery.data ?? []).filter(
(service) =>
service.name.toLowerCase().includes(needle) || service.trade.toLowerCase().includes(needle),
);
}, [search, servicesQuery.data]);
useEffect(() => {
if (!iconTouched && form.trade) {
setForm((current) => ({
...current,
iconKey: DEFAULT_ICON_BY_TRADE[current.trade] ?? "wrench",
}));
}
}, [form.trade, iconTouched]);
const openCreate = () => {
setSelected(null);
setForm(emptyForm());
setIconTouched(false);
setFormError("");
setEditorMode("add");
};
const openEdit = (service: Service) => {
setSelected(service);
setForm(formFromService(service));
setIconTouched(true);
setFormError("");
setEditorMode("edit");
};
const closeEditor = () => {
if (!createService.isPending && !updateService.isPending) setEditorMode(null);
};
const updateForm = <K extends keyof ServiceForm>(key: K, value: ServiceForm[K]) => {
if (key === "trade") setIconTouched(false);
setFormError("");
setForm((current) => ({ ...current, [key]: value }));
};
const toggleType = (type: ServiceWorkOrderType) => {
setForm((current) => {
const supported = current.supportedWorkOrderTypes.includes(type)
? current.supportedWorkOrderTypes.filter((item) => item !== type)
: [...current.supportedWorkOrderTypes, type];
return { ...current, supportedWorkOrderTypes: supported };
});
};
const save = () => {
const validation = buildServiceInput(form);
if (!validation.input) {
setFormError(validation.error);
return;
}
const input = validation.input;
if (editorMode === "add") {
createService.mutate(input, {
onSuccess: (created) => {
setEditorMode(null);
setSelected(created);
},
});
} else if (editorMode === "edit" && selected) {
updateService.mutate(
{ id: selected.id, input },
{
onSuccess: (updated) => {
setEditorMode(null);
setSelected(updated);
},
},
);
}
};
const confirmDeactivate = () => {
if (!deactivateTarget) return;
deactivateService.mutate(deactivateTarget.id, {
onSuccess: () => {
setDeactivateTarget(null);
setSelected(null);
},
});
};
return {
canManage,
canDeactivate,
status,
setStatus,
search,
setSearch,
selected,
setSelected,
editorMode,
form,
formError,
trades,
templates,
filteredServices,
services: servicesQuery.data ?? [],
isLoading: servicesQuery.isLoading,
error: servicesQuery.error,
openCreate,
openEdit,
closeEditor,
updateForm,
toggleType,
save,
deactivateTarget,
setDeactivateTarget,
confirmDeactivate,
isSaving: createService.isPending || updateService.isPending,
isDeactivating: deactivateService.isPending,
};
}