import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { useParams, useNavigate, useSearchParams, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useItemDetails, useItemMutations } from '../hooks/useItem'; import { FaArrowLeft, FaSave, FaEdit, FaCheck, FaTrashAlt, FaSync } from 'react-icons/fa'; import type { CreateItemData } from '../services/itemService'; import LinkField from '../components/LinkField'; import API_CONFIG from '../config/api'; import CommentSection from '../components/CommentSection'; import ActivityLog from '../components/ActivityLog'; import DeleteRequestButton from '../components/DeleteRequestButton'; import type { DeleteStatus } from '../services/deleteRequestService'; import apiService from '../services/apiService'; const ItemDetail: React.FC = () => { const { t } = useTranslation(); // const { itemName } = useParams<{ itemName: string }>(); // const navigate = useNavigate(); // const [searchParams] = useSearchParams(); // const duplicateFromItem = searchParams.get('duplicate'); // const isNewItem = itemName === 'new'; // const isDuplicating = isNewItem && !!duplicateFromItem; const { itemName: rawItemName } = useParams<{ itemName: string }>(); const navigate = useNavigate(); const location = useLocation(); const [searchParams] = useSearchParams(); const duplicateFromItem = searchParams.get('duplicate'); // Extract item name from pathname directly to preserve # characters // which browsers strip from useParams as URL fragments // const itemName = useMemo(() => { // const prefix = '/inventory/'; // const idx = location.pathname.indexOf(prefix); // if (idx !== -1) { // const encoded = location.pathname.slice(idx + prefix.length); // const decoded = decodeURIComponent(encoded); // return decoded; // } // return rawItemName || ''; // }, [location.pathname, rawItemName]); const itemName = useMemo(() => { if (rawItemName === 'new') return 'new'; // Use the raw encoded pathname and decode it ourselves // to avoid React Router's automatic decoding losing # info const prefix = '/inventory/'; const fullPath = window.location.pathname; // e.g. /asm_app/inventory/DELUGE%20VALVE%20%20NO%23%201 const idx = fullPath.indexOf(prefix); if (idx !== -1) { const encoded = fullPath.slice(idx + prefix.length); try { return decodeURIComponent(encoded); } catch { return encoded; } } return rawItemName || ''; }, [rawItemName, location.pathname]); const isNewItem = itemName === 'new'; const isDuplicating = isNewItem && !!duplicateFromItem; // Balance Qty state (fetched from Bin doctype) const [balanceQty, setBalanceQty] = useState(0); const [balanceQtyLoading, setBalanceQtyLoading] = useState(false); const [userRoles, setUserRoles] = useState([]); const [isSystemManager, setIsSystemManager] = useState(false); const [rolesLoaded, setRolesLoaded] = useState(false); useEffect(() => { const fetchRoles = async () => { try { const response = await apiService.apiCall( '/api/method/asset_lite.api.user_roles.get_user_roles' ); const roles = Array.isArray(response) ? response : (response?.message || []); setUserRoles(roles); setIsSystemManager(roles.includes('System Manager')); } catch (err) { console.error('Error fetching roles:', err); } finally { setRolesLoaded(true); } }; fetchRoles(); }, []); // Form data state const [formData, setFormData] = useState({ item_code: '', item_name: '', item_group: '', custom_technical_department: '', custom_hospital_name: '', custom_part_description: '', stock_uom: 'Nos', custom_item_cost_per_unit: 0, disabled: 0, is_stock_item: 1, is_fixed_asset: 0, opening_stock: 0, valuation_rate: 0, standard_rate: 0, custom_last_calibration_date: '', custom_next_due_calibration_date: '', description: '', brand: '', custom_warranty_in_months: '', valuation_method: '', has_batch_no: 0, has_serial_no: 0, custom_serial_no: '', custom_date_in: '', custom_code: '', custom_type: '', custom_volts: undefined as number | undefined, custom_w: undefined as number | undefined, is_purchase_item: 1, is_sales_item: 1, country_of_origin: 'Saudi Arabia', }); const { item, loading, error, refetch: refetchItem } = useItemDetails( isDuplicating ? duplicateFromItem : (isNewItem ? null : itemName || null) ); const { createItem, updateItem, submitItem, loading: saving } = useItemMutations(); const [isEditing, setIsEditing] = useState(isNewItem); // Check document status const docstatus = item?.docstatus ?? 0; const isSubmitted = docstatus === 1; const isCancelled = docstatus === 2; const isDraft = docstatus === 0; const hasDeleteRequest = !!(item?.custom_delete_status); // Check if Calibration Information should be shown const showCalibrationInfo = formData.item_group === 'Tools'; // Fetch Balance Qty from Bin doctype const fetchBalanceQty = useCallback(async (itemCode: string) => { if (!itemCode) return; setBalanceQtyLoading(true); try { // Get CSRF token let csrfToken: string | null = null; if (typeof window !== 'undefined' && (window as any).csrf_token) { csrfToken = (window as any).csrf_token; } // Build filters and fields for Frappe API const filters = JSON.stringify([['item_code', '=', itemCode]]); const fields = JSON.stringify(['actual_qty', 'warehouse']); const url = `${API_CONFIG.BASE_URL}/api/resource/Bin?filters=${encodeURIComponent(filters)}&fields=${encodeURIComponent(fields)}&limit_page_length=0`; const headers: Record = { 'Accept': 'application/json', 'Content-Type': 'application/json', }; if (csrfToken) { headers['X-Frappe-CSRF-Token'] = csrfToken; } const response = await fetch(url, { method: 'GET', headers, credentials: 'include', // Include cookies for session auth }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); // Sum up actual_qty from all warehouses const totalQty = result.data?.reduce((sum: number, bin: any) => { return sum + (bin.actual_qty || 0); }, 0) || 0; setBalanceQty(totalQty); } catch (err) { console.error('Failed to fetch balance qty:', err); setBalanceQty(0); } finally { setBalanceQtyLoading(false); } }, []); // Fetch balance qty when item is loaded (for existing items) useEffect(() => { if (!isNewItem && item?.item_code) { fetchBalanceQty(item.item_code); } }, [isNewItem, item?.item_code, fetchBalanceQty]); // Load item data when item is fetched useEffect(() => { if (item && !isDuplicating) { setFormData({ item_code: item.item_code || '', item_name: item.item_name || '', item_group: item.item_group || '', custom_technical_department: item.custom_technical_department || '', custom_hospital_name: item.custom_hospital_name || '', custom_part_description: item.custom_part_description || '', stock_uom: item.stock_uom || 'Nos', custom_item_cost_per_unit: item.custom_item_cost_per_unit || 0, disabled: item.disabled || 0, is_stock_item: item.is_stock_item ?? 1, is_fixed_asset: item.is_fixed_asset ?? 0, opening_stock: item.opening_stock || 0, valuation_rate: item.valuation_rate ?? 0, standard_rate: item.standard_rate || 0, custom_last_calibration_date: item.custom_last_calibration_date || '', custom_next_due_calibration_date: item.custom_next_due_calibration_date || '', description: item.description || '', brand: item.brand || '', custom_warranty_in_months: item.custom_warranty_in_months || '', valuation_method: item.valuation_method || '', has_batch_no: item.has_batch_no || 0, has_serial_no: item.has_serial_no || 0, is_purchase_item: item.is_purchase_item ?? 1, is_sales_item: item.is_sales_item ?? 1, country_of_origin: item.country_of_origin || 'Saudi Arabia', uoms: item.uoms || [], item_defaults: item.item_defaults || [], custom_serial_no: item.custom_serial_no || '', custom_date_in: item.custom_date_in || '', custom_code: item.custom_code || '', custom_type: item.custom_type || '', custom_volts: item.custom_volts, custom_w: item.custom_w, }); setIsEditing(false); } else if (isDuplicating && item) { // When duplicating, copy data but clear name/code setFormData({ item_code: '', item_name: item.item_name || '', item_group: item.item_group || '', custom_technical_department: item.custom_technical_department || '', custom_hospital_name: item.custom_hospital_name || '', custom_part_description: item.custom_part_description || '', stock_uom: item.stock_uom || 'Nos', custom_item_cost_per_unit: item.custom_item_cost_per_unit || 0, disabled: 0, is_stock_item: item.is_stock_item ?? 1, is_fixed_asset: item.is_fixed_asset ?? 0, opening_stock: item.opening_stock || 0, valuation_rate: item.valuation_rate ?? 0, standard_rate: item.standard_rate || 0, custom_last_calibration_date: item.custom_last_calibration_date || '', custom_next_due_calibration_date: item.custom_next_due_calibration_date || '', description: item.description || '', brand: item.brand || '', custom_warranty_in_months: item.custom_warranty_in_months || '', valuation_method: item.valuation_method || '', has_batch_no: item.has_batch_no || 0, has_serial_no: item.has_serial_no || 0, is_purchase_item: item.is_purchase_item ?? 1, is_sales_item: item.is_sales_item ?? 1, country_of_origin: item.country_of_origin || 'Saudi Arabia', uoms: item.uoms || [], item_defaults: item.item_defaults || [], custom_serial_no: item.custom_serial_no || '', custom_date_in: item.custom_date_in || '', custom_code: item.custom_code || '', custom_type: item.custom_type || '', custom_volts: item.custom_volts, custom_w: item.custom_w, }); } }, [item, isDuplicating]); const handleSave = async () => { try { if (isNewItem) { const newItem = await createItem(formData); navigate(`/inventory/${newItem.name}`); } else { await updateItem(itemName!, formData); await refetchItem(); // Refresh balance qty after update if (formData.item_code) { fetchBalanceQty(formData.item_code); } setIsEditing(false); alert(t('items.itemUpdatedSuccessfully')); } } catch (err) { alert(`${t('items.failedToSave')}: ${err instanceof Error ? err.message : 'Unknown error'}`); } }; const handleSubmit = async () => { if (!itemName || isNewItem) { alert(t('items.pleaseSaveFirst')); return; } try { await submitItem(itemName); await refetchItem(); setIsEditing(false); alert(t('items.submittedSuccessfully')); } catch (err) { alert(`${t('items.failedToSubmit')}: ${err instanceof Error ? err.message : 'Unknown error'}`); } }; const isFieldDisabled = useCallback((fieldname: string): boolean => { if (!isEditing) return true; if (isCancelled) return true; if (hasDeleteRequest) return true; if (isSubmitted) { // For submitted items, most fields are read-only // Only allow editing certain fields if needed return true; } return false; }, [isEditing, isCancelled, isSubmitted, hasDeleteRequest]); if (loading) { return (

{t('items.loadingItem')}

); } if (error && !isNewItem) { return (

{t('items.errorLoadingItem')}

{error}

); } const inputClassName = "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"; const labelClassName = "block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1"; const sectionHeaderClassName = "text-base font-semibold text-gray-800 dark:text-white mb-4 pb-2 border-b border-gray-200 dark:border-gray-700"; const cardClassName = "bg-white dark:bg-gray-800 rounded-xl shadow-md p-6 border border-gray-200 dark:border-gray-700"; return (
{/* Header */}
{!isNewItem && ( {item?.item_code || itemName} )}
{!isNewItem && !isEditing && isDraft && !hasDeleteRequest && ( )} {/* {!isNewItem && !isEditing && rolesLoaded && ( refetchItem()} /> )} */} {isEditing && ( <> {/* {!isNewItem && isDraft && ( )} */} )}
{/* Form - Grid Layout matching AssetDetail */}
{/* COLUMN 1: Basic Information */}

{t('items.basicInformation')}

setFormData({ ...formData, item_code: e.target.value })} disabled={isFieldDisabled('item_code') || !isNewItem} className={inputClassName} required />
setFormData({ ...formData, custom_hospital_name: value })} disabled={isFieldDisabled('custom_hospital_name')} placeholder={t('items.selectHospital')} filters={{ domain: 'Healthcare' }} /> setFormData({ ...formData, item_group: value })} disabled={isFieldDisabled('item_group')} placeholder={t('items.selectItemGroup')} /> setFormData({ ...formData, custom_technical_department: value })} disabled={isFieldDisabled('custom_technical_department')} placeholder={t('items.selectTechnicalDepartment')} />
setFormData({ ...formData, stock_uom: e.target.value })} disabled={isFieldDisabled('stock_uom')} className={inputClassName} />
setFormData({ ...formData, custom_part_description: e.target.value })} disabled={isFieldDisabled('custom_part_description')} className={inputClassName} />
{/* COLUMN 2: Inventory Details */}

{t('items.inventoryDetails')}

setFormData({ ...formData, custom_serial_no: e.target.value })} disabled={isFieldDisabled('custom_serial_no')} className={inputClassName} />
setFormData({ ...formData, custom_date_in: e.target.value })} disabled={isFieldDisabled('custom_date_in')} className={inputClassName} />
{ const v = parseFloat(e.target.value); setFormData({ ...formData, custom_w: e.target.value === '' || isNaN(v) ? undefined : v }); }} disabled={isFieldDisabled('custom_w')} className={inputClassName} />
{ const v = parseFloat(e.target.value); setFormData({ ...formData, custom_volts: e.target.value === '' || isNaN(v) ? undefined : v }); }} disabled={isFieldDisabled('custom_volts')} className={inputClassName} />
setFormData({ ...formData, custom_type: e.target.value })} disabled={isFieldDisabled('custom_type')} className={inputClassName} />
setFormData({ ...formData, custom_code: e.target.value })} disabled={isFieldDisabled('custom_code')} className={inputClassName} />
{/* COLUMN 3: Stock & Additional Information */}

{t('items.stockInformation')}

setFormData({ ...formData, is_stock_item: e.target.checked ? 1 : 0 })} disabled={isFieldDisabled('is_stock_item')} className="w-4 h-4" />
setFormData({ ...formData, is_fixed_asset: e.target.checked ? 1 : 0 })} disabled={isFieldDisabled('is_fixed_asset')} className="w-4 h-4" />
{isNewItem && formData.is_stock_item === 1 && (
setFormData({ ...formData, opening_stock: parseFloat(e.target.value) || 0 })} disabled={isFieldDisabled('opening_stock')} className={inputClassName} />
)} {formData.is_stock_item === 1 && (
setFormData({ ...formData, valuation_rate: parseFloat(e.target.value) || 0 })} disabled={isFieldDisabled('valuation_rate')} className={inputClassName} />
)} {!isNewItem && formData.is_stock_item === 1 && (
)}
{/* Calibration - when Item Group is Tools */} {showCalibrationInfo && ( <>

{t('items.calibrationInformation')}

setFormData({ ...formData, custom_last_calibration_date: e.target.value })} disabled={isFieldDisabled('custom_last_calibration_date')} className={inputClassName} />
setFormData({ ...formData, custom_next_due_calibration_date: e.target.value })} disabled={isFieldDisabled('custom_next_due_calibration_date')} className={inputClassName} />
)}

{t('items.additionalInformation')}