482 lines
14 KiB
TypeScript
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
|
|
}; |