Iman_AMS/asm_app/src/pages/AssetMaintenanceDetail.tsx

1034 lines
47 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useCallback } from 'react';
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useMaintenanceLogDetails, useMaintenanceMutations } from '../hooks/useAssetMaintenance';
import { FaArrowLeft, FaSave, FaEdit, FaClock, FaList, FaPlus, FaTrash, FaCheck, FaTimes, FaExclamationTriangle } from 'react-icons/fa';
import apiService from '../services/apiService';
import WorkflowActions from '../components/WorkflowActions';
import useDefaultHospital from '../hooks/useDefaultHospital';
// Cast apiService to any to bypass strict typing
const api = apiService as any;
// PPM Table Row Interface
interface PPMTableRow {
name?: string;
idx?: number;
maintenance_name: string;
working: number | boolean;
defect_found: number | boolean;
not_working: number | boolean;
}
// Updated Interface with all fields from JSON
interface MaintenanceLogData {
name?: string;
owner?: string;
creation?: string;
modified?: string;
modified_by?: string;
docstatus?: number;
idx?: number;
workflow_state?: string;
asset_maintenance?: string;
naming_series?: string;
asset_name: string;
custom_asset_type?: string;
item_code?: string;
item_name?: string;
custom_asset_names?: string;
custom_hospital_name?: string;
task?: string;
task_name?: string;
maintenance_type: string;
periodicity?: string;
has_certificate?: number | boolean;
custom_early_completion?: string;
maintenance_status: string;
custom_accepted_by_moh?: number | boolean;
assign_to_name?: string;
due_date?: string;
completion_date?: string;
custom_early_completion_reason?: string;
custom_accepted_by_moh_?: number | boolean;
custom_template?: string;
custom_table?: PPMTableRow[];
description?: string;
}
// Toast notification component
const Toast: React.FC<{ message: string; type: 'warning' | 'success' | 'error'; onClose: () => void }> = ({ message, type, onClose }) => {
useEffect(() => {
const timer = setTimeout(onClose, 10000);
return () => clearTimeout(timer);
}, [onClose]);
const bgColor = type === 'warning' ? 'bg-yellow-500' : type === 'success' ? 'bg-green-500' : 'bg-red-500';
return (
<div className={`fixed top-4 right-4 ${bgColor} text-white px-6 py-4 rounded-lg shadow-lg z-50 max-w-md`}>
<div className="flex items-start gap-3">
<FaExclamationTriangle className="mt-0.5 flex-shrink-0" />
<p className="text-sm">{message}</p>
<button onClick={onClose} className="ml-2 text-white hover:text-gray-200">
<FaTimes />
</button>
</div>
</div>
);
};
const AssetMaintenanceDetail: React.FC = () => {
const { t } = useTranslation();
const { logName } = useParams<{ logName: string }>();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const duplicateFromLog = searchParams.get('duplicate');
const isNewLog = logName === 'new';
const isDuplicating = isNewLog && !!duplicateFromLog;
const { log, loading, error } = useMaintenanceLogDetails(
isDuplicating ? duplicateFromLog : (isNewLog ? null : logName || null)
);
const { createLog, updateLog, loading: saving } = useMaintenanceMutations();
// Check if document is approved (not editable)
const isApproved = log?.workflow_state === 'Approved';
const [isEditing, setIsEditing] = useState(isNewLog);
const [ppmTableRows, setPpmTableRows] = useState<PPMTableRow[]>([]);
const [toast, setToast] = useState<{ message: string; type: 'warning' | 'success' | 'error' } | null>(null);
const [templateLoading, setTemplateLoading] = useState(false);
const [formData, setFormData] = useState<MaintenanceLogData>({
asset_name: '',
custom_asset_type: '',
item_code: '',
item_name: '',
custom_asset_names: '',
custom_hospital_name: '',
task: '',
task_name: '',
maintenance_type: 'Preventive Maintenance',
periodicity: '',
has_certificate: 0,
custom_early_completion: '',
maintenance_status: 'Planned',
custom_accepted_by_moh: 0,
assign_to_name: '',
due_date: '',
completion_date: '',
custom_early_completion_reason: '',
custom_accepted_by_moh_: 0,
custom_template: '',
custom_table: [],
description: '',
});
useDefaultHospital(setFormData, {
enabled: isNewLog && !isDuplicating,
fields: ['custom_hospital_name'],
});
// Check early completion logic
const checkEarlyCompletion = useCallback((dueDate: string, completionDate: string, maintenanceStatus: string) => {
if (maintenanceStatus !== 'Completed') {
setFormData(prev => ({ ...prev, custom_early_completion: '' }));
return;
}
if (!dueDate || !completionDate) {
return;
}
const dueDateObj = new Date(dueDate);
const completionDateObj = new Date(completionDate);
const dueMonthStart = new Date(dueDateObj.getFullYear(), dueDateObj.getMonth(), 1);
if (completionDateObj < dueMonthStart) {
setFormData(prev => ({ ...prev, custom_early_completion: 'Yes' }));
const formattedCompletionDate = completionDateObj.toLocaleDateString();
const formattedDueDate = dueDateObj.toLocaleDateString();
setToast({
message: `⚠️ Warning: Completion Date (${formattedCompletionDate}) is before the Due Date Month (${formattedDueDate}). Please verify.`,
type: 'warning'
});
}
}, []);
// Load PPM Template
const loadPPMTemplate = useCallback(async (templateName: string) => {
if (!templateName) return;
setTemplateLoading(true);
try {
const response = await api.apiCall(
`/api/resource/PPM Templates/${encodeURIComponent(templateName)}`,
'GET'
);
if (response?.data?.ppm_template_table && Array.isArray(response.data.ppm_template_table)) {
const templateRows: PPMTableRow[] = response.data.ppm_template_table.map((row: any, index: number) => ({
idx: index + 1,
maintenance_name: row.maintenance_name || '',
working: 0,
defect_found: 0,
not_working: 0,
}));
setPpmTableRows(templateRows);
}
} catch (err) {
console.error('Error loading PPM template:', err);
setToast({
message: 'Failed to load PPM template. Please add items manually.',
type: 'error'
});
} finally {
setTemplateLoading(false);
}
}, []);
// Load data when log is loaded
useEffect(() => {
if (log) {
// If document is approved, ensure editing is disabled
if (log.workflow_state === 'Approved') {
setIsEditing(false);
}
setFormData({
asset_name: log.asset_name || '',
custom_asset_type: log.custom_asset_type || '',
item_code: log.item_code || '',
item_name: log.item_name || '',
custom_asset_names: log.custom_asset_names || '',
custom_hospital_name: log.custom_hospital_name || '',
task: log.task || '',
task_name: log.task_name || '',
maintenance_type: log.maintenance_type || 'Preventive Maintenance',
periodicity: log.periodicity || '',
has_certificate: log.has_certificate || 0,
custom_early_completion: isDuplicating ? '' : (log.custom_early_completion || ''),
maintenance_status: isDuplicating ? 'Planned' : (log.maintenance_status || 'Planned'),
custom_accepted_by_moh: log.custom_accepted_by_moh || 0,
assign_to_name: log.assign_to_name || '',
due_date: log.due_date || '',
completion_date: isDuplicating ? '' : (log.completion_date || ''),
custom_early_completion_reason: isDuplicating ? '' : (log.custom_early_completion_reason || ''),
custom_accepted_by_moh_: log.custom_accepted_by_moh_ || 0,
custom_template: log.custom_template || '',
custom_table: log.custom_table || [],
description: log.description || '',
});
if (log.custom_table && Array.isArray(log.custom_table) && log.custom_table.length > 0) {
setPpmTableRows(log.custom_table.map((row: any, index: number) => ({
name: row.name,
idx: row.idx || index + 1,
maintenance_name: row.maintenance_name || '',
working: row.working || 0,
defect_found: row.defect_found || 0,
not_working: row.not_working || 0,
})));
} else if (log.custom_template && (!log.custom_table || log.custom_table.length === 0)) {
loadPPMTemplate(log.custom_template);
}
}
}, [log, isDuplicating, loadPPMTemplate]);
// Handle completion_date change - trigger early completion check
useEffect(() => {
if (formData.completion_date && formData.due_date) {
checkEarlyCompletion(formData.due_date, formData.completion_date, formData.maintenance_status);
}
}, [formData.completion_date, formData.due_date, formData.maintenance_status, checkEarlyCompletion]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
const { name, value, type } = e.target;
const checked = (e.target as HTMLInputElement).checked;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? (checked ? 1 : 0) : value
}));
};
const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const newStatus = e.target.value;
setFormData(prev => ({
...prev,
maintenance_status: newStatus
}));
if (newStatus === 'Completed') {
checkEarlyCompletion(formData.due_date || '', formData.completion_date || '', newStatus);
} else {
setFormData(prev => ({ ...prev, custom_early_completion: '' }));
}
};
const handleCompletionDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newDate = e.target.value;
setFormData(prev => ({
...prev,
completion_date: newDate
}));
if (formData.maintenance_status === 'Completed') {
checkEarlyCompletion(formData.due_date || '', newDate, formData.maintenance_status);
}
};
const handleTemplateChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const newTemplate = e.target.value;
setFormData(prev => ({ ...prev, custom_template: newTemplate }));
if (newTemplate && ppmTableRows.length === 0) {
loadPPMTemplate(newTemplate);
} else if (newTemplate && ppmTableRows.length > 0) {
if (window.confirm('Loading a template will replace existing checklist items. Continue?')) {
loadPPMTemplate(newTemplate);
}
}
};
// PPM Table handlers
const addPpmRow = () => {
const newRow: PPMTableRow = {
idx: ppmTableRows.length + 1,
maintenance_name: '',
working: 0,
defect_found: 0,
not_working: 0,
};
setPpmTableRows([...ppmTableRows, newRow]);
};
const removePpmRow = (index: number) => {
const updated = ppmTableRows.filter((_, i) => i !== index).map((row, i) => ({
...row,
idx: i + 1
}));
setPpmTableRows(updated);
};
const updatePpmRow = (index: number, field: keyof PPMTableRow, value: any) => {
const updated = [...ppmTableRows];
updated[index] = { ...updated[index], [field]: value };
setPpmTableRows(updated);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!formData.asset_name) {
alert('Please enter Asset Name');
return;
}
if (!formData.maintenance_type) {
alert('Please select Maintenance Type');
return;
}
// Clean PPM table rows - ensure name is either a valid string or removed
const cleanedPpmRows = ppmTableRows.map((row, index) => {
const cleanedRow: any = {
idx: row.idx || index + 1,
maintenance_name: row.maintenance_name || '',
working: row.working ? 1 : 0,
defect_found: row.defect_found ? 1 : 0,
not_working: row.not_working ? 1 : 0,
};
// Only include name if it's a valid non-empty string (existing row from DB)
if (row.name && typeof row.name === 'string' && row.name.trim() !== '') {
cleanedRow.name = row.name;
}
return cleanedRow;
});
const submitData = {
...formData,
custom_table: cleanedPpmRows,
};
console.log('Submitting maintenance log data:', submitData);
try {
if (isNewLog || isDuplicating) {
const newLog = await createLog(submitData as any);
const successMessage = isDuplicating
? 'Maintenance log duplicated successfully!'
: 'Maintenance log created successfully!';
alert(successMessage);
navigate(`/maintenance/${newLog.name}`);
} else if (logName) {
await updateLog(logName, submitData as any);
alert('Maintenance log updated successfully!');
setIsEditing(false);
}
} catch (err) {
console.error('Maintenance log save error:', err);
alert('Failed to save: ' + (err instanceof Error ? err.message : 'Unknown error'));
}
};
const getWorkflowStateBadge = (state: string) => {
const stateColors: Record<string, string> = {
'Draft': 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300',
'Applied': 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
'Pending': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
'Approved': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
'Rejected': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
'Completed': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
};
return stateColors[state] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300';
};
const getStatusBadge = (status: string) => {
const statusColors: Record<string, string> = {
'Planned': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
'Completed': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
'Overdue': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
'Cancelled': 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300',
};
return statusColors[status] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300';
};
if (loading) {
return (
<div className="flex items-center justify-center h-screen bg-gray-50 dark:bg-gray-900">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto"></div>
<p className="mt-4 text-gray-600 dark:text-gray-400">Loading maintenance log...</p>
</div>
</div>
);
}
if (error && !isNewLog && !isDuplicating) {
return (
<div className="p-4 sm:p-6 bg-gray-50 dark:bg-gray-900 min-h-screen min-w-0 overflow-x-hidden">
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
<p className="text-red-600 dark:text-red-400">Error: {error}</p>
<button
onClick={() => navigate('/maintenance')}
className="mt-2 text-red-700 dark:text-red-400 underline hover:text-red-800 dark:hover:text-red-300"
>
Back to maintenance logs
</button>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-4 sm:p-6 min-w-0 overflow-x-hidden">
{/* Toast Notification */}
{toast && (
<Toast
message={toast.message}
type={toast.type}
onClose={() => setToast(null)}
/>
)}
{/* Header */}
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between lg:items-center flex-wrap">
<div className="flex items-center gap-4">
<button
onClick={() => navigate('/maintenance')}
className="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 flex items-center gap-2"
>
<FaArrowLeft />
<span className="text-gray-900 dark:text-white font-semibold">
{isDuplicating ? 'Duplicate Maintenance Log' : (isNewLog ? 'New Maintenance Log' : 'Maintenance Log Details')}
</span>
</button>
{!isNewLog && log?.workflow_state && (
<span className={`px-3 py-1 rounded-full text-sm font-medium ${getWorkflowStateBadge(log.workflow_state)}`}>
{log.workflow_state}
</span>
)}
{/* {isApproved && (
<span className="px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 flex items-center gap-1">
🔒 Locked
</span>
)} */}
</div>
<div className="flex items-center gap-3">
{!isNewLog && !isEditing && !isApproved && (
<button
onClick={() => setIsEditing(true)}
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg flex items-center gap-2"
>
<FaEdit />
Edit
</button>
)}
{isEditing && (
<>
<button
onClick={() => {
if (isNewLog) {
navigate('/maintenance');
} else {
setIsEditing(false);
}
}}
className="bg-gray-300 hover:bg-gray-400 text-gray-700 px-6 py-2 rounded-lg"
disabled={saving}
>
Cancel
</button>
<button
onClick={handleSubmit}
disabled={saving}
className="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg flex items-center gap-2 disabled:opacity-50"
>
<FaSave />
{saving ? 'Saving...' : 'Save Changes'}
</button>
</>
)}
</div>
</div>
{/* Standard View Form */}
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Column - Main Content */}
<div className="lg:col-span-2 space-y-6">
{/* Basic Information */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-md p-6 border border-gray-200 dark:border-gray-700">
<h2 className="text-base font-semibold text-gray-800 dark:text-white mb-4 pb-2 border-b border-gray-200 dark:border-gray-700">
Basic Information
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Log ID</label>
<input
type="text"
value={isNewLog || isDuplicating ? 'Auto-generated' : log?.name || ''}
disabled
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white"
/>
{isDuplicating && (
<p className="mt-1 text-xs text-blue-600 dark:text-blue-400">
💡 Duplicating from: {duplicateFromLog}
</p>
)}
</div>
{/* <div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Workflow State</label>
<input
type="text"
value={log?.workflow_state || 'Draft'}
disabled
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div> */}
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Hospital</label>
<input
type="text"
name="custom_hospital_name"
value={formData.custom_hospital_name || ''}
onChange={handleChange}
disabled={!isEditing}
placeholder="Hospital name"
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
Asset Name <span className="text-red-500">*</span>
</label>
<input
type="text"
name="asset_name"
value={formData.asset_name}
onChange={handleChange}
required
disabled={!isEditing}
placeholder="Asset ID"
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Asset Display Name</label>
<input
type="text"
name="custom_asset_names"
value={formData.custom_asset_names || ''}
onChange={handleChange}
disabled={!isEditing}
placeholder="Display name"
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Asset Type</label>
<input
type="text"
name="custom_asset_type"
value={formData.custom_asset_type || ''}
onChange={handleChange}
disabled={!isEditing}
placeholder="e.g., Bio Medical"
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
</div>
</div>
{/* Maintenance Details */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-md p-6 border border-gray-200 dark:border-gray-700">
<h2 className="text-base font-semibold text-gray-800 dark:text-white mb-4 pb-2 border-b border-gray-200 dark:border-gray-700">
Maintenance Details
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
Maintenance Type <span className="text-red-500">*</span>
</label>
<select
name="maintenance_type"
value={formData.maintenance_type}
onChange={handleChange}
required
disabled={!isEditing}
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="Preventive Maintenance">Preventive Maintenance</option>
<option value="Corrective Maintenance">Corrective Maintenance</option>
<option value="Calibration">Calibration</option>
<option value="Inspection">Inspection</option>
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Periodicity</label>
<select
name="periodicity"
value={formData.periodicity || ''}
onChange={handleChange}
disabled={!isEditing}
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="">Select periodicity</option>
<option value="Daily">Daily</option>
<option value="Weekly">Weekly</option>
<option value="Monthly">Monthly</option>
<option value="Quarterly">Quarterly</option>
<option value="Half Yearly">Half Yearly</option>
<option value="Yearly">Yearly</option>
<option value="2 Yearly">2 Yearly</option>
<option value="3 Yearly">3 Yearly</option>
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
Template
{templateLoading && <span className="ml-2 text-blue-500">(Loading...)</span>}
</label>
<input
type="text"
name="custom_template"
value={formData.custom_template || ''}
onChange={handleTemplateChange}
disabled={!isEditing}
placeholder="PPM Template name"
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
<p className="mt-1 text-xs text-gray-500">Enter template name to auto-load checklist items</p>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Status</label>
<select
name="maintenance_status"
value={formData.maintenance_status}
onChange={handleStatusChange}
disabled={!isEditing}
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="Planned">Planned</option>
<option value="Completed">Completed</option>
<option value="Overdue">Overdue</option>
<option value="Cancelled">Cancelled</option>
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Assigned To</label>
<input
type="text"
name="assign_to_name"
value={formData.assign_to_name || ''}
onChange={handleChange}
disabled={!isEditing}
placeholder="Technician name"
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Due Date</label>
<input
type="date"
name="due_date"
value={formData.due_date || ''}
onChange={handleChange}
disabled={!isEditing}
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">Completion Date</label>
<input
type="date"
name="completion_date"
value={formData.completion_date || ''}
onChange={handleCompletionDateChange}
disabled={!isEditing}
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
</div>
</div>
{/* Early Completion & MOH Section */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-md p-6 border border-gray-200 dark:border-gray-700">
<h2 className="text-base font-semibold text-gray-800 dark:text-white mb-4 pb-2 border-b border-gray-200 dark:border-gray-700">
Completion & Approval
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
Early Completion
{formData.custom_early_completion === 'Yes' && (
<span className="ml-2 text-yellow-500"> Auto-detected</span>
)}
</label>
<input
type="text"
value={formData.custom_early_completion || 'No'}
disabled
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white"
/>
<p className="mt-1 text-xs text-gray-500">Auto-set based on completion date vs due date</p>
</div>
{formData.custom_early_completion === 'Yes' && (
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
Early Completion Reason <span className="text-red-500">*</span>
</label>
<input
type="text"
name="custom_early_completion_reason"
value={formData.custom_early_completion_reason || ''}
onChange={handleChange}
disabled={!isEditing}
required
placeholder="Required when early completion"
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
)}
<div className="flex items-center gap-2">
<input
type="checkbox"
name="custom_accepted_by_moh"
checked={!!formData.custom_accepted_by_moh}
onChange={handleChange}
disabled={!isEditing}
className="w-4 h-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
/>
<label className="text-xs font-medium text-gray-700 dark:text-gray-300">Accepted by MOH</label>
</div>
</div>
</div>
{/* PPM Table (Checklist) */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-md p-6 border border-gray-200 dark:border-gray-700">
<div className="flex justify-between items-center mb-4 pb-2 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-base font-semibold text-gray-800 dark:text-white flex items-center gap-2">
<FaList />
PPM Checklist Table
{templateLoading && <span className="text-sm text-blue-500 font-normal">(Loading template...)</span>}
</h2>
{isEditing && (
<button
type="button"
onClick={addPpmRow}
className="flex items-center gap-1 px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded-md"
>
<FaPlus className="text-xs" />
Add Row
</button>
)}
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="bg-gray-50 dark:bg-gray-700">
<th className="text-left py-3 px-3 text-xs font-medium text-gray-700 dark:text-gray-300 w-12">#</th>
<th className="text-left py-3 px-3 text-xs font-medium text-gray-700 dark:text-gray-300">Maintenance Task</th>
<th className="text-center py-3 px-3 text-xs font-medium text-green-600 dark:text-green-400 w-24">
<div className="flex items-center justify-center gap-1">
<FaCheck /> Working
</div>
</th>
<th className="text-center py-3 px-3 text-xs font-medium text-yellow-600 dark:text-yellow-400 w-28">
<div className="flex items-center justify-center gap-1">
<FaExclamationTriangle /> Defect
</div>
</th>
<th className="text-center py-3 px-3 text-xs font-medium text-red-600 dark:text-red-400 w-28">
<div className="flex items-center justify-center gap-1">
<FaTimes /> Not Working
</div>
</th>
{isEditing && <th className="text-center py-3 px-3 text-xs font-medium text-gray-700 dark:text-gray-300 w-16">Action</th>}
</tr>
</thead>
<tbody>
{ppmTableRows.length === 0 ? (
<tr>
<td colSpan={isEditing ? 6 : 5} className="text-center py-8 text-gray-500 dark:text-gray-400">
No checklist items. {isEditing && 'Enter a template name above or click "Add Row" to add items.'}
</td>
</tr>
) : (
ppmTableRows.map((row, index) => (
<tr key={row.name || index} className="border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/50">
<td className="py-3 px-3 text-sm text-gray-600 dark:text-gray-400">{row.idx || index + 1}</td>
<td className="py-3 px-3">
{isEditing ? (
<input
type="text"
value={row.maintenance_name}
onChange={(e) => updatePpmRow(index, 'maintenance_name', e.target.value)}
placeholder="Enter maintenance task"
className="w-full px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-1 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
) : (
<span className="text-sm text-gray-900 dark:text-white">{row.maintenance_name}</span>
)}
</td>
<td className="py-3 px-3 text-center">
<input
type="checkbox"
checked={!!row.working}
onChange={(e) => updatePpmRow(index, 'working', e.target.checked ? 1 : 0)}
disabled={!isEditing}
className="w-5 h-5 text-green-600 rounded focus:ring-2 focus:ring-green-500 cursor-pointer disabled:cursor-default"
/>
</td>
<td className="py-3 px-3 text-center">
<input
type="checkbox"
checked={!!row.defect_found}
onChange={(e) => updatePpmRow(index, 'defect_found', e.target.checked ? 1 : 0)}
disabled={!isEditing}
className="w-5 h-5 text-yellow-600 rounded focus:ring-2 focus:ring-yellow-500 cursor-pointer disabled:cursor-default"
/>
</td>
<td className="py-3 px-3 text-center">
<input
type="checkbox"
checked={!!row.not_working}
onChange={(e) => updatePpmRow(index, 'not_working', e.target.checked ? 1 : 0)}
disabled={!isEditing}
className="w-5 h-5 text-red-600 rounded focus:ring-2 focus:ring-red-500 cursor-pointer disabled:cursor-default"
/>
</td>
{isEditing && (
<td className="py-3 px-3 text-center">
<button
type="button"
onClick={() => removePpmRow(index)}
className="text-red-500 hover:text-red-700 p-1"
title="Remove row"
>
<FaTrash />
</button>
</td>
)}
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
{/* Right Column - Status Summary */}
<div className="space-y-6">
{/* Workflow Actions */}
{!isNewLog && log && (
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-md p-6 border border-gray-200 dark:border-gray-700">
<h2 className="text-base font-semibold text-gray-800 dark:text-white mb-4 pb-2 border-b border-gray-200 dark:border-gray-700">
Workflow Actions
</h2>
<WorkflowActions
doctype="Asset Maintenance Log"
docname={log.name || null}
workflowState={log.workflow_state}
showStateInfo={false}
onActionComplete={(action, success) => {
if (success) {
console.log(`Action "${action}" completed successfully`);
}
}}
onStateChange={() => {
// Reload page to reflect new state
window.location.reload();
}}
/>
</div>
)}
{/* Status Card */}
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-md p-6 border border-gray-200 dark:border-gray-700">
<h2 className="text-base font-semibold text-gray-800 dark:text-white mb-4 pb-2 border-b border-gray-200 dark:border-gray-700">
Status Summary
</h2>
{!isNewLog && log ? (
<div className="space-y-4">
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Current Status</p>
<span className={`inline-block px-3 py-1 rounded-full text-sm font-medium ${getStatusBadge(formData.maintenance_status)}`}>
{formData.maintenance_status || 'Planned'}
</span>
</div>
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Workflow State</p>
<span className={`inline-block px-3 py-1 rounded-full text-sm font-medium ${getWorkflowStateBadge(log.workflow_state || 'Draft')}`}>
{log.workflow_state || 'Draft'}
</span>
</div>
{formData.custom_early_completion === 'Yes' && (
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">
<p className="text-xs text-yellow-600 dark:text-yellow-400 mb-1"> Early Completion</p>
<p className="text-sm text-yellow-700 dark:text-yellow-300 font-medium">
Completion date is before the due month
</p>
</div>
)}
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Due Date</p>
<p className="text-sm text-gray-900 dark:text-white">
{formData.due_date ? new Date(formData.due_date).toLocaleDateString() : 'Not set'}
</p>
{formData.due_date && new Date(formData.due_date) < new Date() && formData.maintenance_status !== 'Completed' && (
<p className="text-xs text-red-600 dark:text-red-400 font-semibold mt-1">
Overdue
</p>
)}
</div>
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Completion Date</p>
<p className="text-sm text-gray-900 dark:text-white">
{formData.completion_date ? new Date(formData.completion_date).toLocaleDateString() : 'Not completed'}
</p>
</div>
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Assigned To</p>
<p className="text-sm text-gray-900 dark:text-white">
{formData.assign_to_name || 'Unassigned'}
</p>
</div>
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">PPM Checklist</p>
<p className="text-sm text-gray-900 dark:text-white">
{ppmTableRows.length} item(s)
</p>
{ppmTableRows.length > 0 && (
<div className="mt-2 text-xs space-y-1">
<p className="text-green-600 dark:text-green-400">
Working: {ppmTableRows.filter(r => r.working).length}
</p>
<p className="text-yellow-600 dark:text-yellow-400">
Defects: {ppmTableRows.filter(r => r.defect_found).length}
</p>
<p className="text-red-600 dark:text-red-400">
Not Working: {ppmTableRows.filter(r => r.not_working).length}
</p>
</div>
)}
</div>
</div>
) : (
<div className="text-center py-8">
<FaClock className="text-4xl text-gray-400 dark:text-gray-500 mx-auto mb-2" />
<p className="text-sm text-gray-500 dark:text-gray-400">
Status information will appear after creation
</p>
</div>
)}
</div>
{/* Audit Information */}
{!isNewLog && log && (
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-md p-6 border border-gray-200 dark:border-gray-700">
<h2 className="text-base font-semibold text-gray-800 dark:text-white mb-4 pb-2 border-b border-gray-200 dark:border-gray-700">
Audit Information
</h2>
<div className="space-y-3 text-sm">
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Created By</p>
<p className="text-gray-900 dark:text-white">{log.owner || '-'}</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Created On</p>
<p className="text-gray-900 dark:text-white">
{log.creation ? new Date(log.creation).toLocaleString() : '-'}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Modified By</p>
<p className="text-gray-900 dark:text-white">{log.modified_by || '-'}</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Modified On</p>
<p className="text-gray-900 dark:text-white">
{log.modified ? new Date(log.modified).toLocaleString() : '-'}
</p>
</div>
</div>
</div>
)}
</div>
</div>
</form>
</div>
);
};
export default AssetMaintenanceDetail;