Corp/frontend/src/hooks/useEquipmentPage.ts

881 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useCallback, useDeferredValue, useEffect, useMemo, useState } from "react";
import { apiClient } from "@/lib/api";
import { canManageEquipment } from "@/lib/permissions";
import { displayUserName as sharedDisplayUserName, extractNextPage, formatDate, formatMoney, loadAllPages } from "@/lib/shared";
import type {
Department,
Equipment,
EquipmentCategory,
EquipmentComment,
EquipmentCreateOptions,
EquipmentTransferHistoryEntry,
MaintenanceRecord,
User,
} from "@/types/api";
type EquipmentFormState = {
name: string;
notes: string;
category: number | null;
department: number | null;
responsible_person: number | null;
quantity: number;
serial_number: string;
purchase_date: string;
purchase_cost: string;
location: string;
};
type EquipmentListMode = "all" | "mine" | "warranty";
type OperationModal = "transfer" | "writeoff" | "maintenance" | null;
type TransferFormState = {
to_department: number | null;
to_person: number | null;
reason: string;
};
type MaintenanceFormState = {
type: string;
description: string;
cost: string;
date: string;
};
type PaginatedLike<T> = {
results?: T[];
next?: string | null;
};
const emptyForm: EquipmentFormState = {
name: "",
notes: "",
category: null,
department: null,
responsible_person: null,
quantity: 1,
serial_number: "",
purchase_date: "",
purchase_cost: "",
location: "",
};
const createInitialMaintenanceForm = (): MaintenanceFormState => ({
type: "repair",
description: "",
cost: "",
date: new Date().toISOString().slice(0, 10),
});
const getPaginatedResults = <T,>(response: unknown): T[] => {
if (Array.isArray(response)) {
return response as T[];
}
if (response && typeof response === "object") {
const results = (response as PaginatedLike<T>).results;
if (Array.isArray(results)) {
return results;
}
}
return [];
};
const getPaginatedNext = (response: unknown): number | null => {
if (!response || typeof response !== "object" || Array.isArray(response)) {
return null;
}
return extractNextPage((response as PaginatedLike<unknown>).next ?? null);
};
const getReadableError = (error: unknown, fallback: string): string => {
const raw = String((error as Error)?.message || fallback);
try {
const parsed = JSON.parse(raw) as Record<string, unknown>;
return Object.entries(parsed)
.map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`)
.join(". ");
} catch {
return raw;
}
};
export function useEquipmentPage(user: User | null) {
const canManage = canManageEquipment(user);
const auth = user?.auth;
const [items, setItems] = useState<Equipment[]>([]);
const [detailsMap, setDetailsMap] = useState<Record<number, Equipment>>({});
const [transferHistoryMap, setTransferHistoryMap] = useState<Record<number, EquipmentTransferHistoryEntry[]>>({});
const [maintenanceMap, setMaintenanceMap] = useState<Record<number, MaintenanceRecord[]>>({});
const [employees, setEmployees] = useState<User[]>([]);
const [departments, setDepartments] = useState<Department[]>([]);
const [categories, setCategories] = useState<EquipmentCategory[]>([]);
const [createOptions, setCreateOptions] = useState<EquipmentCreateOptions | null>(null);
const [previewInventoryNumber, setPreviewInventoryNumber] = useState("");
const [commentsMap, setCommentsMap] = useState<Record<number, EquipmentComment[]>>({});
const [expandedRows, setExpandedRows] = useState<Record<number, boolean>>({});
const [expandedComments, setExpandedComments] = useState<Record<number, boolean>>({});
const [commentDrafts, setCommentDrafts] = useState<Record<number, string>>({});
const [loadingRowDetails, setLoadingRowDetails] = useState<Record<number, boolean>>({});
const [createOpen, setCreateOpen] = useState(false);
const [editingId, setEditingId] = useState<number | null>(null);
const [form, setForm] = useState<EquipmentFormState>(emptyForm);
const [listMode, setListMode] = useState<EquipmentListMode>("all");
const [ordering, setOrdering] = useState("-created_at");
const [operationModal, setOperationModal] = useState<OperationModal>(null);
const [selectedEquipment, setSelectedEquipment] = useState<Equipment | null>(null);
const [writeOffReason, setWriteOffReason] = useState("");
const [transferForm, setTransferForm] = useState<TransferFormState>({ to_department: null, to_person: null, reason: "" });
const [maintenanceForm, setMaintenanceForm] = useState<MaintenanceFormState>(createInitialMaintenanceForm);
const [searchQuery, setSearchQuery] = useState("");
const deferredSearchQuery = useDeferredValue(searchQuery);
const [statusFilter, setStatusFilter] = useState("");
const [categoryFilter, setCategoryFilter] = useState("");
const [departmentFilter, setDepartmentFilter] = useState("");
const [responsibleFilter, setResponsibleFilter] = useState("");
const [dateFromFilter, setDateFromFilter] = useState("");
const [dateToFilter, setDateToFilter] = useState("");
const [filtersOpen, setFiltersOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [error, setError] = useState<string | null>(null);
const [actionError, setActionError] = useState<string | null>(null);
const [actionSuccess, setActionSuccess] = useState<string | null>(null);
const [busyKey, setBusyKey] = useState<string | null>(null);
const [nextPage, setNextPage] = useState<number | null>(null);
const isCreateMode = createOpen && editingId === null;
const displayUserName = useCallback((person?: User | null) => sharedDisplayUserName(person), []);
const buildParams = useCallback(
(page: number): Record<string, string | number> => {
const params: Record<string, string | number> = { page };
if (deferredSearchQuery.trim()) params.search = deferredSearchQuery.trim();
if (ordering) params.ordering = ordering;
if (statusFilter) params.status = statusFilter;
if (categoryFilter) params.category = categoryFilter;
if (departmentFilter) params.department = departmentFilter;
if (responsibleFilter) params.responsible_person = responsibleFilter;
if (dateFromFilter) params.purchase_date__gte = dateFromFilter;
if (dateToFilter) params.purchase_date__lte = dateToFilter;
return params;
},
[categoryFilter, dateFromFilter, dateToFilter, deferredSearchQuery, departmentFilter, ordering, responsibleFilter, statusFilter],
);
const fetchEquipmentPage = useCallback(
async (page: number): Promise<unknown> => {
if (listMode === "mine") {
return apiClient.getMyEquipment({ page });
}
if (listMode === "warranty") {
if (page > 1) {
return { results: [], next: null };
}
return apiClient.getWarrantyExpiringEquipment();
}
return apiClient.getEquipment(buildParams(page));
},
[buildParams, listMode],
);
const reloadEquipmentList = useCallback(async () => {
const response = await fetchEquipmentPage(1);
setItems(getPaginatedResults<Equipment>(response));
setNextPage(getPaginatedNext(response));
}, [fetchEquipmentPage]);
const getCategoryName = useCallback(
(equipment: Equipment): string => {
if (equipment.category_name) return equipment.category_name;
const category = categories.find((item) => item.id === Number(equipment.category));
return category?.name || "—";
},
[categories],
);
const getDepartmentName = useCallback(
(equipment: Equipment): string => {
if (equipment.department_name) return equipment.department_name;
const department = departments.find((item) => item.id === Number(equipment.department));
return department?.name || "—";
},
[departments],
);
const getResponsibleName = useCallback(
(equipment: Equipment): string => {
if (equipment.responsible_name) return equipment.responsible_name;
if (equipment.responsible_person && typeof equipment.responsible_person === "number") {
const employee = employees.find((item) => item.id === equipment.responsible_person);
return employee ? displayUserName(employee) : "—";
}
return "—";
},
[displayUserName, employees],
);
const getResponsibleLink = useCallback(
(equipment: Equipment): string => {
if (!equipment.responsible_person || typeof equipment.responsible_person !== "number") return "";
if (equipment.responsible_person === user?.id) return "/profile";
return `/users/${equipment.responsible_person}`;
},
[user?.id],
);
const getEquipmentMeta = useCallback(
(equipment: Equipment) => ([
{ label: "Категория", value: getCategoryName(equipment) },
{ label: "Отдел", value: getDepartmentName(equipment) },
{ label: "Ответственный", value: getResponsibleName(equipment) },
{ label: "Серийный номер", value: equipment.serial_number || "—" },
{ label: "Расположение", value: equipment.location || "—" },
{ label: "Гарантия до", value: formatDate(equipment.warranty_until) || "—" },
{ label: "Обслуживаний", value: String(equipment.maintenance_count ?? "—") },
{ label: "Стоимость", value: formatMoney(equipment.purchase_cost) },
{ label: "Добавлено", value: formatDate(equipment.created_at) || "—" },
]),
[getCategoryName, getDepartmentName, getResponsibleName],
);
const filteredDepartmentsForForm = useMemo(() => {
if (!createOptions || !isCreateMode || createOptions.allowed_departments.length === 0) {
return departments;
}
const allowedIds = new Set(createOptions.allowed_departments.map((department) => department.id));
return departments.filter((department) => allowedIds.has(department.id));
}, [createOptions, departments, isCreateMode]);
const filteredEmployeesForForm = useMemo(() => {
if (!isCreateMode || !createOptions || createOptions.can_choose_responsible) {
return employees;
}
if (!createOptions.default_responsible) {
return [];
}
return employees.filter((employee) => employee.id === createOptions.default_responsible?.id);
}, [createOptions, employees, isCreateMode]);
const applyCreateDefaults = useCallback((options: EquipmentCreateOptions | null) => {
if (!options) return;
setForm((previous) => {
const nextDepartment = options.can_choose_department
? previous.department
: options.allowed_departments[0]?.id ?? previous.department;
const nextResponsible = options.can_choose_responsible
? previous.responsible_person
: options.default_responsible?.id ?? previous.responsible_person;
return {
...previous,
department: nextDepartment,
responsible_person: nextResponsible,
};
});
}, []);
const loadRowDetails = useCallback(async (equipmentId: number) => {
if (loadingRowDetails[equipmentId]) return;
try {
setLoadingRowDetails((previous) => ({ ...previous, [equipmentId]: true }));
const results = await Promise.allSettled([
detailsMap[equipmentId] ? Promise.resolve(detailsMap[equipmentId]) : apiClient.getEquipmentDetail(equipmentId),
transferHistoryMap[equipmentId] ? Promise.resolve(transferHistoryMap[equipmentId]) : apiClient.getEquipmentTransferHistory(equipmentId),
maintenanceMap[equipmentId] ? Promise.resolve(maintenanceMap[equipmentId]) : apiClient.getMaintenanceRecords({ equipment: equipmentId }),
]);
const [detailResult, transferResult, maintenanceResult] = results;
if (detailResult.status === "fulfilled") {
setDetailsMap((previous) => ({ ...previous, [equipmentId]: detailResult.value as Equipment }));
}
if (transferResult.status === "fulfilled") {
setTransferHistoryMap((previous) => ({
...previous,
[equipmentId]: getPaginatedResults<EquipmentTransferHistoryEntry>(transferResult.value),
}));
}
if (maintenanceResult.status === "fulfilled") {
setMaintenanceMap((previous) => ({
...previous,
[equipmentId]: getPaginatedResults<MaintenanceRecord>(maintenanceResult.value),
}));
}
} catch (loadError) {
console.error("Ошибка загрузки деталей оборудования:", loadError);
} finally {
setLoadingRowDetails((previous) => ({ ...previous, [equipmentId]: false }));
}
}, [detailsMap, loadingRowDetails, maintenanceMap, transferHistoryMap]);
const resetForm = useCallback(() => {
setForm(emptyForm);
}, []);
const openCreateModal = useCallback(async () => {
setEditingId(null);
resetForm();
setActionError(null);
setActionSuccess(null);
setCreateOpen(true);
try {
const [options, inventoryPreview] = await Promise.all([
apiClient.getEquipmentCreateOptions(),
apiClient.generateEquipmentInventoryNumber(),
]);
setCreateOptions(options);
setPreviewInventoryNumber(inventoryPreview.inventory_number || "");
applyCreateDefaults(options);
} catch (loadError) {
console.error("Ошибка загрузки опций создания оборудования:", loadError);
}
}, [applyCreateDefaults, resetForm]);
const openEdit = useCallback((equipment: Equipment) => {
setEditingId(equipment.id);
setCreateOpen(false);
setActionError(null);
setActionSuccess(null);
setPreviewInventoryNumber(equipment.inventory_number || "");
setForm({
name: equipment.name || "",
notes: equipment.notes || "",
category: equipment.category ?? null,
department: equipment.department ?? null,
responsible_person: typeof equipment.responsible_person === "number" ? equipment.responsible_person : null,
quantity: 1,
serial_number: equipment.serial_number || "",
purchase_date: equipment.purchase_date || "",
purchase_cost: String(equipment.purchase_cost || ""),
location: equipment.location || "",
});
}, []);
const openOperationModal = useCallback((type: Exclude<OperationModal, null>, equipment: Equipment) => {
setSelectedEquipment(equipment);
setOperationModal(type);
setActionError(null);
if (type === "transfer") {
setTransferForm({
to_department: equipment.department ?? null,
to_person: typeof equipment.responsible_person === "number" ? equipment.responsible_person : null,
reason: "",
});
}
if (type === "writeoff") {
setWriteOffReason("");
}
if (type === "maintenance") {
setMaintenanceForm(createInitialMaintenanceForm());
}
}, []);
const closeOperationModal = useCallback(() => {
setOperationModal(null);
setSelectedEquipment(null);
setWriteOffReason("");
setTransferForm({ to_department: null, to_person: null, reason: "" });
setMaintenanceForm(createInitialMaintenanceForm());
}, []);
useEffect(() => {
let cancelled = false;
void (async () => {
try {
setLoading(true);
setError(null);
const response = await fetchEquipmentPage(1);
if (cancelled) return;
setItems(getPaginatedResults<Equipment>(response));
setNextPage(getPaginatedNext(response));
} catch (loadError) {
if (cancelled) return;
console.error("Ошибка загрузки оборудования:", loadError);
setError("Не удалось загрузить оборудование");
} finally {
if (!cancelled) {
setLoading(false);
}
}
})();
return () => {
cancelled = true;
};
}, [fetchEquipmentPage]);
useEffect(() => {
let cancelled = false;
void (async () => {
try {
const [allEmployees, allDepartments, allCategories] = await Promise.all([
loadAllPages<User>((page) => apiClient.getEmployees(page)),
loadAllPages<Department>((page) => apiClient.getDepartments(page)),
loadAllPages<EquipmentCategory>((page) => apiClient.getEquipmentCategories(page)),
]);
if (cancelled) return;
setEmployees(allEmployees);
setDepartments(allDepartments);
setCategories(allCategories);
} catch (loadError) {
if (!cancelled) {
console.error("Ошибка загрузки справочников:", loadError);
}
}
})();
return () => {
cancelled = true;
};
}, []);
useEffect(() => {
let cancelled = false;
void (async () => {
try {
const options = await apiClient.getEquipmentCreateOptions();
if (!cancelled) {
setCreateOptions(options);
}
} catch (loadError) {
if (!cancelled) {
console.error("Ошибка загрузки create options:", loadError);
}
}
})();
return () => {
cancelled = true;
};
}, []);
const sortItemsLocally = useCallback((source: Equipment[]) => {
const sorted = [...source];
sorted.sort((left, right) => {
switch (ordering) {
case "name":
return (left.name || "").localeCompare(right.name || "", "ru");
case "purchase_date":
return new Date(left.purchase_date || 0).getTime() - new Date(right.purchase_date || 0).getTime();
case "-purchase_date":
return new Date(right.purchase_date || 0).getTime() - new Date(left.purchase_date || 0).getTime();
case "created_at":
return new Date(left.created_at || 0).getTime() - new Date(right.created_at || 0).getTime();
case "-created_at":
default:
return new Date(right.created_at || 0).getTime() - new Date(left.created_at || 0).getTime();
}
});
return sorted;
}, [ordering]);
const filteredItems = useMemo(() => {
const query = searchQuery.trim().toLowerCase();
let scopedItems = [...items];
if (listMode !== "all") {
scopedItems = scopedItems.filter((item) => {
const itemDate = item.purchase_date || "";
if (statusFilter && item.status !== statusFilter) return false;
if (categoryFilter && Number(item.category) !== Number(categoryFilter)) return false;
if (departmentFilter && Number(item.department) !== Number(departmentFilter)) return false;
if (responsibleFilter && Number(item.responsible_person) !== Number(responsibleFilter)) return false;
if (dateFromFilter && itemDate && itemDate < dateFromFilter) return false;
if (dateToFilter && itemDate && itemDate > dateToFilter) return false;
return true;
});
}
const sorted = listMode === "all" ? items : sortItemsLocally(scopedItems);
if (!query) return sorted;
return sorted.filter((item) => {
const name = (item.name || "").toLowerCase();
const notes = (item.notes || "").toLowerCase();
const serialNumber = (item.serial_number || "").toLowerCase();
const inventoryNumber = (item.inventory_number || "").toLowerCase();
const responsible = getResponsibleName(item).toLowerCase();
const category = getCategoryName(item).toLowerCase();
return (
name.includes(query)
|| notes.includes(query)
|| serialNumber.includes(query)
|| inventoryNumber.includes(query)
|| responsible.includes(query)
|| category.includes(query)
);
});
}, [
categoryFilter,
dateFromFilter,
dateToFilter,
departmentFilter,
getCategoryName,
getResponsibleName,
items,
listMode,
responsibleFilter,
searchQuery,
sortItemsLocally,
statusFilter,
]);
const handleSave = useCallback(async (mode: "create" | "edit") => {
try {
setBusyKey(`${mode}-save`);
setActionError(null);
setActionSuccess(null);
if (!form.name.trim()) {
setActionError("Укажите название оборудования.");
return;
}
if (!form.category) {
setActionError("Выберите категорию.");
return;
}
if (!form.department) {
setActionError("Выберите отдел.");
return;
}
if (!form.purchase_date) {
setActionError("Укажите дату покупки.");
return;
}
if (!form.purchase_cost) {
setActionError("Укажите стоимость.");
return;
}
const payload: Record<string, unknown> = {
name: form.name,
notes: form.notes,
category: form.category,
department: form.department,
purchase_date: form.purchase_date,
purchase_cost: form.purchase_cost,
};
if (form.responsible_person) payload.responsible_person = form.responsible_person;
if (mode === "create" && form.quantity > 1) payload.quantity = form.quantity;
if (form.serial_number) payload.serial_number = form.serial_number;
if (form.location) payload.location = form.location;
if (mode === "create") {
await apiClient.createEquipment(payload);
setActionSuccess("Оборудование добавлено.");
setCreateOpen(false);
} else if (editingId) {
await apiClient.updateEquipment(editingId, payload);
setActionSuccess("Оборудование обновлено.");
setEditingId(null);
}
resetForm();
await reloadEquipmentList();
} catch (saveError) {
setActionError(getReadableError(saveError, "Не удалось сохранить"));
} finally {
setBusyKey(null);
}
}, [editingId, form, reloadEquipmentList, resetForm]);
const handleLoadMore = useCallback(async () => {
if (!nextPage || loadingMore) return;
try {
setLoadingMore(true);
const response = await fetchEquipmentPage(nextPage);
const chunk = getPaginatedResults<Equipment>(response);
setItems((previous) => {
const known = new Set(previous.map((item) => item.id));
return [...previous, ...chunk.filter((item) => !known.has(item.id))];
});
setNextPage(getPaginatedNext(response));
} catch {
setError("Не удалось загрузить ещё");
} finally {
setLoadingMore(false);
}
}, [fetchEquipmentPage, loadingMore, nextPage]);
const handleDelete = useCallback(async (id: number) => {
if (!window.confirm("Удалить это оборудование?")) return;
try {
setBusyKey(`delete-${id}`);
await apiClient.deleteEquipment(id);
setItems((previous) => previous.filter((item) => item.id !== id));
} catch {
setActionError("Не удалось удалить");
} finally {
setBusyKey(null);
}
}, []);
const toggleRow = useCallback((equipmentId: number) => {
const nextOpen = !expandedRows[equipmentId];
setExpandedRows((previous) => ({ ...previous, [equipmentId]: nextOpen }));
if (nextOpen) {
void loadRowDetails(equipmentId);
}
}, [expandedRows, loadRowDetails]);
const handleTransfer = useCallback(async () => {
if (!selectedEquipment) return;
try {
setBusyKey(`transfer-${selectedEquipment.id}`);
await apiClient.transferEquipment(selectedEquipment.id, {
to_department: transferForm.to_department,
to_person: transferForm.to_person,
reason: transferForm.reason,
});
setActionSuccess("Оборудование переведено.");
closeOperationModal();
await reloadEquipmentList();
await loadRowDetails(selectedEquipment.id);
} catch (transferError) {
setActionError(String((transferError as Error)?.message || "Не удалось выполнить перевод"));
} finally {
setBusyKey(null);
}
}, [closeOperationModal, loadRowDetails, reloadEquipmentList, selectedEquipment, transferForm]);
const handleWriteOff = useCallback(async () => {
if (!selectedEquipment) return;
try {
setBusyKey(`writeoff-${selectedEquipment.id}`);
await apiClient.writeOffEquipment(selectedEquipment.id, writeOffReason);
setActionSuccess("Оборудование списано.");
closeOperationModal();
await reloadEquipmentList();
await loadRowDetails(selectedEquipment.id);
} catch (writeOffError) {
setActionError(String((writeOffError as Error)?.message || "Не удалось списать оборудование"));
} finally {
setBusyKey(null);
}
}, [closeOperationModal, loadRowDetails, reloadEquipmentList, selectedEquipment, writeOffReason]);
const handleMaintenance = useCallback(async () => {
if (!selectedEquipment) return;
try {
setBusyKey(`maintenance-${selectedEquipment.id}`);
await apiClient.addEquipmentMaintenance(selectedEquipment.id, {
type: maintenanceForm.type,
description: maintenanceForm.description,
cost: maintenanceForm.cost || undefined,
date: maintenanceForm.date,
});
setActionSuccess("Запись обслуживания добавлена.");
closeOperationModal();
await loadRowDetails(selectedEquipment.id);
} catch (maintenanceError) {
setActionError(String((maintenanceError as Error)?.message || "Не удалось добавить обслуживание"));
} finally {
setBusyKey(null);
}
}, [closeOperationModal, loadRowDetails, maintenanceForm, selectedEquipment]);
const openEquipmentById = useCallback(async (equipmentId: number, options?: { expand?: boolean }) => {
if (options?.expand !== false) {
setExpandedRows((previous) => ({ ...previous, [equipmentId]: true }));
}
const existing = detailsMap[equipmentId] || items.find((item) => item.id === equipmentId);
if (!existing) {
try {
const detail = (await apiClient.getEquipmentDetail(equipmentId)) as Equipment;
setItems((previous) =>
previous.some((item) => item.id === equipmentId)
? previous
: [detail, ...previous]
);
setDetailsMap((previous) => ({ ...previous, [equipmentId]: detail }));
} catch (loadError) {
setActionError(String((loadError as Error)?.message || "Не удалось открыть оборудование"));
return;
}
}
await loadRowDetails(equipmentId);
}, [detailsMap, items, loadRowDetails]);
const toggleComments = useCallback(async (equipmentId: number) => {
const isOpen = Boolean(expandedComments[equipmentId]);
setExpandedComments((previous) => ({ ...previous, [equipmentId]: !isOpen }));
if (!isOpen && !commentsMap[equipmentId]) {
try {
const response = await apiClient.getEquipmentComments(equipmentId);
setCommentsMap((previous) => ({
...previous,
[equipmentId]: getPaginatedResults<EquipmentComment>(response),
}));
} catch {
setCommentsMap((previous) => ({ ...previous, [equipmentId]: [] }));
}
}
}, [commentsMap, expandedComments]);
const handleAddComment = useCallback(async (equipmentId: number) => {
const text = (commentDrafts[equipmentId] || "").trim();
if (!text) return;
try {
setBusyKey(`comment-${equipmentId}`);
const saved = await apiClient.addEquipmentComment(equipmentId, text);
setCommentsMap((previous) => ({ ...previous, [equipmentId]: [...(previous[equipmentId] || []), saved as EquipmentComment] }));
setCommentDrafts((previous) => ({ ...previous, [equipmentId]: "" }));
} catch {
setActionError("Не удалось добавить комментарий");
} finally {
setBusyKey(null);
}
}, [commentDrafts]);
const handleDeleteComment = useCallback(async (equipmentId: number, commentId: number) => {
try {
setBusyKey(`comment-delete-${commentId}`);
await apiClient.deleteEquipmentComment(equipmentId, commentId);
setCommentsMap((previous) => ({
...previous,
[equipmentId]: (previous[equipmentId] || []).filter((comment) => comment.id !== commentId),
}));
} catch {
setActionError("Не удалось удалить комментарий");
} finally {
setBusyKey(null);
}
}, []);
const modalMode: "create" | "edit" = editingId ? "edit" : "create";
const isModalOpen = createOpen || editingId !== null;
const closeModal = useCallback(() => {
setCreateOpen(false);
setEditingId(null);
resetForm();
setActionError(null);
}, [resetForm]);
const activeFilterCount = [statusFilter, categoryFilter, departmentFilter, responsibleFilter, dateFromFilter, dateToFilter].filter(Boolean).length;
return {
auth,
actionError,
actionSuccess,
activeFilterCount,
busyKey,
canManage,
categories,
categoryFilter,
closeModal,
closeOperationModal,
commentDrafts,
commentsMap,
createOptions,
dateFromFilter,
dateToFilter,
departmentFilter,
departments,
detailsMap,
displayUserName,
editingId,
employees,
error,
expandedComments,
expandedRows,
filteredDepartmentsForForm,
filteredEmployeesForForm,
filteredItems,
filtersOpen,
form,
getEquipmentMeta,
getResponsibleLink,
getResponsibleName,
handleAddComment,
handleDelete,
handleDeleteComment,
handleLoadMore,
handleMaintenance,
handleSave,
handleTransfer,
handleWriteOff,
openEquipmentById,
isCreateMode,
isModalOpen,
listMode,
loading,
loadingMore,
loadingRowDetails,
maintenanceForm,
maintenanceMap,
modalMode,
nextPage,
openCreateModal,
openEdit,
openOperationModal,
operationModal,
ordering,
previewInventoryNumber,
responsibleFilter,
searchQuery,
selectedEquipment,
setCategoryFilter,
setCommentDrafts,
setDateFromFilter,
setDateToFilter,
setDepartmentFilter,
setFiltersOpen,
setForm,
setListMode,
setMaintenanceForm,
setOrdering,
setResponsibleFilter,
setSearchQuery,
setStatusFilter,
setTransferForm,
setWriteOffReason,
statusFilter,
transferForm,
transferHistoryMap,
toggleComments,
toggleRow,
writeOffReason,
};
}