KFH_ASM/asm_app/src/hooks/usePMSchedule.ts

482 lines
14 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import apiService from '../services/apiService';
// Types for PM Schedule Generator
export interface PMEntryLine {
name?: string;
asset: string;
asset_name: string;
start_date: string;
end_date: string;
manufacturer?: string;
model?: string;
idx?: number;
}
export interface PMSchedule {
name: string;
owner?: string;
creation?: string;
modified?: string;
modified_by?: string;
docstatus?: number;
hospital?: string;
modality?: string;
device_status?: string;
start_date?: string;
end_date?: string;
maintenance_team?: string;
maintenance_manager?: string;
periodicity?: string;
assign_to?: string;
due_date?: string;
pm_for?: string; // PM Name field
maintenance_entries?: PMEntryLine[];
doctype?: string;
[key: string]: any; // Allow additional fields
}
export interface CreatePMScheduleData {
hospital: string;
modality?: string;
device_status?: string;
start_date: string;
end_date: string;
maintenance_team?: string;
maintenance_manager?: string;
periodicity: string;
assign_to?: string;
due_date?: string;
maintenance_entries?: PMEntryLine[];
}
// Hook for fetching PM Schedules list
export function usePMSchedules(
filters: Record<string, any> = {},
limit: number = 20,
offset: number = 0,
orderBy: string = 'creation desc',
permissionFilters: Record<string, any> = {}
) {
const [pmSchedules, setPMSchedules] = useState<PMSchedule[]>([]);
const [totalCount, setTotalCount] = useState(0);
const [hasMore, setHasMore] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [refetchTrigger, setRefetchTrigger] = useState(0);
// Stringify filters to prevent object reference changes from causing re-renders
const filtersJson = JSON.stringify(filters);
const permissionFiltersJson = JSON.stringify(permissionFilters);
useEffect(() => {
let isCancelled = false;
// Capture values at effect execution time
const currentFiltersJson = filtersJson;
const currentPermissionFiltersJson = permissionFiltersJson;
const currentLimit = limit;
const currentOffset = offset;
const currentOrderBy = orderBy;
const fetchPMSchedules = async () => {
try {
setLoading(true);
setError(null);
// Parse filters from JSON strings to avoid closure issues
let currentFilters: Record<string, any> = {};
let currentPermissionFilters: Record<string, any> = {};
try {
currentFilters = currentFiltersJson ? JSON.parse(currentFiltersJson) : {};
} catch (e) {
currentFilters = {};
}
try {
currentPermissionFilters = currentPermissionFiltersJson ? JSON.parse(currentPermissionFiltersJson) : {};
} catch (e) {
currentPermissionFilters = {};
}
// Merge filters with permission filters
const combinedFilters = { ...currentFilters, ...currentPermissionFilters };
const response = await apiService.apiCall<any>(
'/api/method/asset_lite.api.ppm_generator_api.get_pm_schedules',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filters: JSON.stringify(combinedFilters),
limit: currentLimit,
offset: currentOffset,
order_by: currentOrderBy,
include_child_tables: true,
fields: JSON.stringify(['name', 'pm_for', 'hospital', 'modality', 'periodicity', 'start_date', 'end_date', 'due_date']) // Explicitly request pm_for
})
}
);
if (!isCancelled) {
// Handle both response formats: {message: {...}} or direct {...}
const data = response?.message || response;
if (data && data.pm_schedules) {
const schedules = data.pm_schedules || [];
console.log('[usePMSchedules] Loaded', schedules.length, 'PM Schedules');
// Debug: Log first schedule to see available fields - ALWAYS log in dev
if (schedules.length > 0) {
const firstSchedule = schedules[0];
console.log('[usePMSchedules] 🔍 FIRST SCHEDULE FIELDS:', {
name: firstSchedule.name,
pm_for: firstSchedule.pm_for,
'pm_for (bracket)': firstSchedule['pm_for'],
allKeys: Object.keys(firstSchedule),
allKeysList: Object.keys(firstSchedule).join(', '),
fullObject: firstSchedule
});
}
setPMSchedules(schedules);
setTotalCount(data.total_count || 0);
setHasMore(data.has_more || false);
} else {
console.warn('[usePMSchedules] No pm_schedules in response:', response);
setPMSchedules([]);
setTotalCount(0);
setHasMore(false);
}
}
} catch (err) {
if (!isCancelled) {
console.error('Error fetching PM Schedules:', err);
setError(err instanceof Error ? err.message : 'Failed to fetch PM Schedules');
setPMSchedules([]);
setTotalCount(0);
}
} finally {
if (!isCancelled) {
setLoading(false);
}
}
};
fetchPMSchedules();
return () => {
isCancelled = true;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filtersJson, permissionFiltersJson, limit, offset, orderBy, refetchTrigger]);
const refetch = useCallback(() => {
setRefetchTrigger(prev => prev + 1);
}, []);
return {
pmSchedules,
totalCount,
hasMore,
loading,
error,
refetch
};
}
// Hook for fetching single PM Schedule details
export function usePMScheduleDetails(pmScheduleName: string | null) {
const [pmSchedule, setPMSchedule] = useState<PMSchedule | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchPMSchedule = useCallback(async () => {
if (!pmScheduleName) {
setPMSchedule(null);
setLoading(false);
return;
}
setLoading(true);
setError(null);
try {
const response = await apiService.apiCall<any>(
'/api/method/asset_lite.api.ppm_generator_api.get_pm_schedule_details',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ pm_schedule_name: pmScheduleName })
}
);
console.log('[usePMScheduleDetails] API Response:', response);
// apiService.apiCall already unwraps the 'message' property
// So response is directly the PM Schedule data OR an error object
if (response && response.name && !response.error) {
console.log('[usePMScheduleDetails] Setting PM Schedule:', response);
setPMSchedule(response);
} else {
const errorMsg = response?.error || 'PM Schedule not found';
console.warn('[usePMScheduleDetails] Error or not found:', errorMsg);
setError(errorMsg);
setPMSchedule(null);
}
} catch (err) {
console.error('Error fetching PM Schedule details:', err);
setError(err instanceof Error ? err.message : 'Failed to fetch PM Schedule');
setPMSchedule(null);
} finally {
setLoading(false);
}
}, [pmScheduleName]);
useEffect(() => {
fetchPMSchedule();
}, [fetchPMSchedule]);
return {
pmSchedule,
loading,
error,
refetch: fetchPMSchedule
};
}
// Hook for PM Schedule mutations (create, update, delete, submit, cancel)
export function usePMScheduleMutations() {
const [loading, setLoading] = useState(false);
const createPMSchedule = async (data: CreatePMScheduleData): Promise<PMSchedule> => {
setLoading(true);
try {
const response = await apiService.apiCall<any>(
'/api/method/asset_lite.api.ppm_generator_api.create_pm_schedule',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ pm_schedule_data: JSON.stringify(data) })
}
);
// apiService.apiCall already unwraps the 'message' property
if (response?.success) {
return response.pm_schedule;
} else {
throw new Error(response?.error || 'Failed to create PM Schedule');
}
} finally {
setLoading(false);
}
};
const updatePMSchedule = async (name: string, data: Partial<CreatePMScheduleData>): Promise<PMSchedule> => {
setLoading(true);
try {
const response = await apiService.apiCall<any>(
'/api/method/asset_lite.api.ppm_generator_api.update_pm_schedule',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pm_schedule_name: name,
pm_schedule_data: JSON.stringify(data)
})
}
);
if (response?.success) {
return response.pm_schedule;
} else {
throw new Error(response?.error || 'Failed to update PM Schedule');
}
} finally {
setLoading(false);
}
};
const deletePMSchedule = async (name: string): Promise<void> => {
setLoading(true);
try {
const response = await apiService.apiCall<any>(
'/api/method/asset_lite.api.ppm_generator_api.delete_pm_schedule',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ pm_schedule_name: name })
}
);
if (!response?.success) {
throw new Error(response?.error || 'Failed to delete PM Schedule');
}
} finally {
setLoading(false);
}
};
const submitPMSchedule = async (name: string): Promise<PMSchedule> => {
setLoading(true);
try {
const response = await apiService.apiCall<any>(
'/api/method/asset_lite.api.ppm_generator_api.submit_pm_schedule',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ pm_schedule_name: name })
}
);
if (response?.success) {
return response.pm_schedule;
} else {
throw new Error(response?.error || 'Failed to submit PM Schedule');
}
} finally {
setLoading(false);
}
};
const cancelPMSchedule = async (name: string): Promise<PMSchedule> => {
setLoading(true);
try {
const response = await apiService.apiCall<any>(
'/api/method/asset_lite.api.ppm_generator_api.cancel_pm_schedule',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ pm_schedule_name: name })
}
);
if (response?.success) {
return response.pm_schedule;
} else {
throw new Error(response?.error || 'Failed to cancel PM Schedule');
}
} finally {
setLoading(false);
}
};
const addMaintenanceEntry = async (pmScheduleName: string, entryData: Partial<PMEntryLine>): Promise<PMEntryLine[]> => {
setLoading(true);
try {
const response = await apiService.apiCall<any>(
'/api/method/asset_lite.api.ppm_generator_api.add_maintenance_entry',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pm_schedule_name: pmScheduleName,
entry_data: JSON.stringify(entryData)
})
}
);
if (response?.success) {
return response.maintenance_entries;
} else {
throw new Error(response?.error || 'Failed to add maintenance entry');
}
} finally {
setLoading(false);
}
};
const removeMaintenanceEntry = async (pmScheduleName: string, entryName: string): Promise<PMEntryLine[]> => {
setLoading(true);
try {
const response = await apiService.apiCall<any>(
'/api/method/asset_lite.api.ppm_generator_api.remove_maintenance_entry',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pm_schedule_name: pmScheduleName,
entry_name: entryName
})
}
);
if (response?.success) {
return response.maintenance_entries;
} else {
throw new Error(response?.error || 'Failed to remove maintenance entry');
}
} finally {
setLoading(false);
}
};
const updateMaintenanceEntry = async (
pmScheduleName: string,
entryName: string,
entryData: Partial<PMEntryLine>
): Promise<PMEntryLine[]> => {
setLoading(true);
try {
const response = await apiService.apiCall<any>(
'/api/method/asset_lite.api.ppm_generator_api.update_maintenance_entry',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pm_schedule_name: pmScheduleName,
entry_name: entryName,
entry_data: JSON.stringify(entryData)
})
}
);
if (response?.success) {
return response.maintenance_entries;
} else {
throw new Error(response?.error || 'Failed to update maintenance entry');
}
} finally {
setLoading(false);
}
};
return {
createPMSchedule,
updatePMSchedule,
deletePMSchedule,
submitPMSchedule,
cancelPMSchedule,
addMaintenanceEntry,
removeMaintenanceEntry,
updateMaintenanceEntry,
loading
};
}
export default {
usePMSchedules,
usePMScheduleDetails,
usePMScheduleMutations
};