import React, { useState, useEffect } from 'react'; import { useParams, useNavigate, useSearchParams } from 'react-router-dom'; import { useAssetDetails, useAssetMutations } from '../hooks/useAsset'; import { useDocTypeMeta } from '../hooks/useDocTypeMeta'; import { FaArrowLeft, FaSave, FaEdit, FaQrcode, FaCheck } from 'react-icons/fa'; import type { CreateAssetData,AssetFinanceBookRow } from '../services/assetService'; import LinkField from '../components/LinkField'; import apiService from '../services/apiService'; const AssetDetail: React.FC = () => { const { assetName } = useParams<{ assetName: string }>(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const duplicateFromAsset = searchParams.get('duplicate'); const isNewAsset = assetName === 'new'; const isDuplicating = isNewAsset && !!duplicateFromAsset; // Fetch DocType metadata to check allow_on_submit fields const { isAllowedOnSubmit } = useDocTypeMeta('Asset'); const { asset, loading, error, refetch: refetchAsset } = useAssetDetails( isDuplicating ? duplicateFromAsset : (isNewAsset ? null : assetName || null) ); const { createAsset, updateAsset, submitAsset, loading: saving } = useAssetMutations(); const [isEditing, setIsEditing] = useState(isNewAsset); // Check document status (matching Frappe behavior) const docstatus = asset?.docstatus ?? 0; // Default to 0 (Draft) if not set const isSubmitted = docstatus === 1; const isCancelled = docstatus === 2; const isDraft = docstatus === 0; // Debug logging (development only) useEffect(() => { if (import.meta.env.DEV && asset) { console.log(`[AssetDetail] Document Status:`, { docstatus, isDraft, isSubmitted, isCancelled, isEditing }); } }, [asset, docstatus, isDraft, isSubmitted, isCancelled, isEditing]); // Helper function to determine if a field should be disabled // This matches Frappe's behavior: // - Draft (0): All fields editable when in edit mode // - Submitted (1): Only fields with allow_on_submit can be edited // - Cancelled (2): No fields can be edited const isFieldDisabled = (fieldname: string): boolean => { // Always disabled when not in edit mode if (!isEditing) return true; // Cancelled documents cannot be edited at all if (isCancelled) return true; // Submitted documents: only allow editing fields marked as allow_on_submit if (isSubmitted) { return !isAllowedOnSubmit(fieldname); } // Draft documents: all fields are editable when in edit mode if (isDraft) { return false; } // Default: disable if status is unknown return true; }; const [userSiteName, setUserSiteName] = useState(''); const [departmentFilters, setDepartmentFilters] = useState>({}); const [formData, setFormData] = useState({ asset_name: '', company: '', custom_serial_number: '', location: '', custom_manufacturer: '', department: '', custom_asset_type: '', custom_manufacturing_year: '', custom_model: '', custom_class: '', custom_device_status: '', custom_down_time: 0, asset_owner_company: '', custom_up_time: 0, custom_modality: '', custom_attach_image: '', custom_site_contractor: '', custom_total_amount: 0, calculate_depreciation: false, available_for_use_date: isNewAsset ? new Date().toISOString().split('T')[0] : undefined, finance_books:[] }); // Load user details on mount useEffect(() => { async function loadUserDetails() { try { const user = await apiService.getUserDetails(); setUserSiteName(user.custom_site_name || ''); } catch (err) { console.error('Error loading user details', err); } } loadUserDetails(); }, []); const addFinanceRow = () => { // Get today's date in YYYY-MM-DD format for date input const today = new Date().toISOString().split('T')[0]; const newRow: AssetFinanceBookRow = { finance_book: 'Depreciation Entries', depreciation_method: 'Straight Line', total_number_of_depreciations: 10, frequency_of_depreciation: 12, depreciation_start_date: today }; setFormData(prev => ({ ...prev, finance_books: [ ...(prev.finance_books || []), newRow ] })); }; const removeFinanceRow = (index: number) => { setFormData(prev => { const rows = [...(prev.finance_books || [])]; rows.splice(index, 1); return { ...prev, finance_books: rows }; }); }; const updateFinanceRow = (index: number, patch: Partial) => { setFormData(prev => { const rows = [...(prev.finance_books || [])]; rows[index] = { ...(rows[index] || {}), ...patch }; return { ...prev, finance_books: rows }; }); }; // Update department filters when company or userSiteName changes useEffect(() => { const filters: Record = {}; if (formData.company) { filters['company'] = formData.company; } const isMobileSite = (userSiteName && userSiteName.startsWith('Mobile')) || (formData.company && formData.company.startsWith('Mobile')); if (isMobileSite) { filters['department_name'] = ['not like', 'Non Bio%']; } else if (userSiteName || formData.company) { filters['department_name'] = ['not like', 'Bio%']; } setDepartmentFilters(filters); }, [formData.company, userSiteName]); // Load asset data for editing or duplicating useEffect(() => { if (asset) { setFormData({ asset_name: isDuplicating ? `${asset.asset_name} (Copy)` : (asset.asset_name || ''), company: asset.company || '', custom_serial_number: isDuplicating ? '' : (asset.custom_serial_number || ''), location: asset.location || '', custom_manufacturer: asset.custom_manufacturer || '', department: asset.department || '', custom_asset_type: asset.custom_asset_type || '', custom_manufacturing_year: asset.custom_manufacturing_year || '', custom_model: asset.custom_model || '', custom_class: asset.custom_class || '', custom_device_status: asset.custom_device_status || '', custom_down_time: asset.custom_down_time || 0, asset_owner_company: asset.asset_owner_company || '', custom_up_time: asset.custom_up_time || 0, custom_modality: asset.custom_modality || '', custom_attach_image: asset.custom_attach_image || '', custom_site_contractor: asset.custom_site_contractor || '', custom_total_amount: asset.custom_total_amount || 0, gross_purchase_amount: asset.gross_purchase_amount || 0, available_for_use_date: asset.available_for_use_date || '', calculate_depreciation: asset.calculate_depreciation || false, finance_books: asset.finance_books || [] }); } }, [asset, isDuplicating]); const [qrCodeUrl, setQrCodeUrl] = useState(null); useEffect(() => { if (!assetName || assetName === "new") return; const fetchQRCode = async () => { try { const directUrl = `/files/${assetName}-qr.png`; const response = await fetch(directUrl, { method: "HEAD" }); if (response.ok) { setQrCodeUrl(directUrl); return; } const fileRes = await apiService.apiCall( `/api/resource/File?filters=[["File","attached_to_name","=","${assetName}"]]` ); if (fileRes?.data?.length > 0) { setQrCodeUrl(fileRes.data[0].file_url); } } catch (error) { console.error("Error loading QR code:", error); } }; fetchQRCode(); }, [assetName, asset]); const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!formData.asset_name) { alert('Please enter an Asset Name'); return; } if (!formData.custom_asset_type) { alert('Please select a Category'); return; } console.log('Submitting asset data:', formData); try { if (isNewAsset || isDuplicating) { const newAsset = await createAsset(formData); if (newAsset.name) { const qrUrl = `/files/${newAsset.name}-qr.png`; setQrCodeUrl(qrUrl); } const successMessage = isDuplicating ? 'Asset duplicated successfully!' : 'Asset created successfully!'; alert(successMessage); navigate(`/assets/${newAsset.name}`); } else if (assetName) { await updateAsset(assetName, formData); alert('Asset updated successfully!'); setIsEditing(false); // Refresh asset data to get updated docstatus refetchAsset(); } } catch (err) { console.error('Asset save error:', err); const errorMessage = err instanceof Error ? err.message : 'Unknown error'; if (errorMessage.includes('404') || errorMessage.includes('not found') || errorMessage.includes('has no attribute') || errorMessage.includes('417')) { alert( '⚠️ Asset API Not Deployed\n\n' + 'The Asset API endpoint (asset_api.py) is not deployed on your Frappe server yet.\n\n' + 'To fix this:\n' + '1. SSH into your Frappe server\n' + '2. Navigate to: frappe-bench/apps/asset_lite/asset_lite/api/\n' + '3. Create the file: asset_api.py\n' + '4. Copy the content from frappe_asset_api.py in this project\n' + '5. Restart Frappe: bench restart\n\n' + 'Error: ' + errorMessage ); } else { alert('Failed to save asset:\n\n' + errorMessage); } } }; const handleSubmitDocument = async () => { if (!assetName || isNewAsset) { alert('Cannot submit: Asset not saved yet'); return; } if (!window.confirm('Are you sure you want to submit this asset? Once submitted, only fields marked as "Allow on Submit" can be edited.')) { return; } try { await submitAsset(assetName); alert('Asset submitted successfully!'); // Refresh asset data to get updated docstatus refetchAsset(); setIsEditing(false); } catch (err) { console.error('Asset submit error:', err); const errorMessage = err instanceof Error ? err.message : 'Unknown error'; alert('Failed to submit asset:\n\n' + errorMessage); } }; if (loading) { return (

Loading asset details...

); } if (error && !isNewAsset && !isDuplicating) { return (

Error: {error}

); } if (error && isDuplicating) { return (

Source Asset Not Found

The asset you're trying to duplicate could not be found.

); } const handlePrintQR = () => { if (!qrCodeUrl || !asset) return; const printWindow = window.open('', '_blank'); if (!printWindow) return; printWindow.document.write(` Print QR Code - ${asset.name}

Asset QR Code

Asset ID: ${asset.name}
Asset Name: ${asset.asset_name || 'N/A'}
QR Code
`); printWindow.document.close(); }; const handlePrintStayPlugged = () => { const printWindow = window.open('', '_blank'); if (!printWindow) return; printWindow.document.write(` Stay Plugged
Stay Plugged
`); printWindow.document.close(); }; return (
{/* Header */}
{/* Document Status Badge */} {asset && ( {docstatus === 0 ? 'Draft' : docstatus === 1 ? 'Submitted' : 'Cancelled'} )}
{!isNewAsset && !isEditing && !isCancelled && ( <> {/* Print QR Button - Only show if QR code exists */} {qrCodeUrl && ( )} {/* Stay Plugged Button */} {/* Submit button - only show for Draft documents */} {isDraft && ( )} )} {isCancelled && ( Cancelled documents cannot be edited )} {isEditing && ( <> )}
{/* 4-Column Grid Layout */}
{/* COLUMN 1: Asset Information */}

Asset Information

setFormData({ ...formData, custom_asset_type: val })} disabled={isFieldDisabled('custom_asset_type')} /> setFormData({ ...formData, custom_modality: val })} disabled={isFieldDisabled('custom_modality')} />
{isDuplicating && (

💡 Duplicating from: {duplicateFromAsset}

)}
{/* COLUMN 2: Technical Specs */}

Technical Specs

setFormData({ ...formData, custom_manufacturer: val })} disabled={isFieldDisabled('custom_manufacturer')} />
{/* COLUMN 3: Location */}

Location

{ setFormData({ ...formData, company: val, department: '' }); }} disabled={isFieldDisabled('company')} filters={{ domain: 'Healthcare' }} /> setFormData({ ...formData, department: val })} disabled={isFieldDisabled('department')} filters={departmentFilters} /> setFormData({ ...formData, location: val })} disabled={isFieldDisabled('location')} />
{/* COLUMN 4: More Details */}

More Details

{/*
*/} {/* QR Code */}
{qrCodeUrl ? ( <> {`QR { const target = e.target as HTMLImageElement; target.style.display = 'none'; const fallback = target.nextElementSibling as HTMLElement; if (fallback) { fallback.style.display = 'flex'; } }} />
) : (
)}
{asset?.name && (

Asset ID: {asset.name}

)}