Added the Asset Filters in List view

This commit is contained in:
Duradundi Hadimani 2025-11-20 20:11:20 +05:30
parent c3a42d67b5
commit 9cf1f3201b
3 changed files with 775 additions and 36 deletions

View File

@ -61,34 +61,68 @@ const LinkField: React.FC<LinkFieldProps> = ({
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) { if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setDropdownOpen(false); setDropdownOpen(false);
// Reset search text to current value when closing
setSearchText('');
} }
}; };
document.addEventListener('mousedown', handleClickOutside); document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside);
}, []); }, []);
// Handle selecting an item from dropdown
const handleSelect = (selectedValue: string) => {
onChange(selectedValue);
setSearchText('');
setDropdownOpen(false);
};
// Handle clearing the field
const handleClear = () => {
onChange('');
setSearchText('');
setDropdownOpen(false);
};
return ( return (
<div ref={containerRef} className="relative w-full mb-4"> <div ref={containerRef} className="relative w-full mb-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{label} {label}
</label> </label>
<input <div className="relative">
type="text" <input
value={value} type="text"
placeholder={placeholder || `Select ${label}`} value={isDropdownOpen ? searchText : value}
disabled={disabled} placeholder={placeholder || `Select ${label}`}
className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md disabled={disabled}
focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md
bg-white dark:bg-gray-700 text-gray-900 dark:text-white`} focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700
onFocus={() => !disabled && setDropdownOpen(true)} bg-white dark:bg-gray-700 text-gray-900 dark:text-white ${value ? 'pr-8' : ''}`}
onChange={(e) => { onFocus={() => {
const text = e.target.value; if (!disabled) {
setSearchText(text); setDropdownOpen(true);
searchLink(text); setSearchText('');
onChange(text); }
}} }}
/> onChange={(e) => {
const text = e.target.value;
setSearchText(text);
searchLink(text);
}}
/>
{/* Clear button - only show when there's a selected value and not disabled */}
{value && !disabled && !isDropdownOpen && (
<button
type="button"
onClick={handleClear}
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
</button>
)}
</div>
{isDropdownOpen && searchResults.length > 0 && !disabled && ( {isDropdownOpen && searchResults.length > 0 && !disabled && (
<ul className="absolute z-50 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 <ul className="absolute z-50 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600
@ -96,10 +130,11 @@ const LinkField: React.FC<LinkFieldProps> = ({
{searchResults.map((item, idx) => ( {searchResults.map((item, idx) => (
<li <li
key={idx} key={idx}
onClick={() => { // onClick={() => {
onChange(item.value); // onChange(item.value);
setDropdownOpen(false); // setDropdownOpen(false);
}} // }}
onClick={() => handleSelect(item.value)}
className={`px-3 py-2 cursor-pointer className={`px-3 py-2 cursor-pointer
text-gray-900 dark:text-gray-100 text-gray-900 dark:text-gray-100
hover:bg-blue-500 dark:hover:bg-blue-600 hover:text-white hover:bg-blue-500 dark:hover:bg-blue-600 hover:text-white

View File

@ -3,7 +3,7 @@ import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
import { useAssetDetails, useAssetMutations } from '../hooks/useAsset'; import { useAssetDetails, useAssetMutations } from '../hooks/useAsset';
import { useDocTypeMeta } from '../hooks/useDocTypeMeta'; import { useDocTypeMeta } from '../hooks/useDocTypeMeta';
import { FaArrowLeft, FaSave, FaEdit, FaQrcode, FaCheck } from 'react-icons/fa'; import { FaArrowLeft, FaSave, FaEdit, FaQrcode, FaCheck } from 'react-icons/fa';
import type { CreateAssetData } from '../services/assetService'; import type { CreateAssetData,AssetFinanceBookRow } from '../services/assetService';
import LinkField from '../components/LinkField'; import LinkField from '../components/LinkField';
import apiService from '../services/apiService'; import apiService from '../services/apiService';
@ -535,6 +535,22 @@ const AssetDetail: React.FC = () => {
</p> </p>
)} )}
</div> </div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Device Status <span className="text-red-500">*</span>
</label>
<select
name="custom_device_status"
value={formData.custom_device_status}
onChange={handleChange}
disabled={!isEditing}
className="w-full px-3 py-2 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 class</option> */}
<option value="Up">Up</option>
<option value="Down">Down</option>
</select>
</div>
</div> </div>
</div> </div>
@ -710,7 +726,7 @@ const AssetDetail: React.FC = () => {
More Details More Details
</h2> </h2>
<div className="space-y-4"> <div className="space-y-4">
<div> {/* <div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1"> <label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
Condition Condition
</label> </label>
@ -726,7 +742,7 @@ const AssetDetail: React.FC = () => {
<option value="Under Maintenance">Under Maintenance</option> <option value="Under Maintenance">Under Maintenance</option>
<option value="Decommissioned">Decommissioned</option> <option value="Decommissioned">Decommissioned</option>
</select> </select>
</div> </div> */}
{/* QR Code */} {/* QR Code */}
<div className="flex flex-col items-center my-4"> <div className="flex flex-col items-center my-4">
@ -1018,14 +1034,206 @@ const AssetDetail: React.FC = () => {
</div> </div>
</div> </div>
{/* Financial Details - Full Width */}
{/* Updated Financial Details */}
<div className="mt-6"> <div className="mt-6">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<h2 className="text-lg font-semibold text-gray-800 dark:text-white mb-4">Financial Details</h2>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
The depreciation method is an accounting method used to allocate the cost of a tangible asset over its useful life.
</p>
<div className="flex items-center mb-6">
<input
id="calculate_depreciation"
type="checkbox"
checked={formData.calculate_depreciation}
onChange={(e) =>
setFormData({
...formData,
calculate_depreciation: e.target.checked,
})
}
disabled={!isEditing}
className="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
/>
<label
htmlFor="calculate_depreciation"
className="ml-2 text-sm font-medium text-gray-700 dark:text-gray-300"
>
Calculate Depreciation
</label>
</div>
{/* Asset Finance Book child table — shown only when checkbox checked */}
{formData.calculate_depreciation && (
<div className="border-t pt-4">
{/* Header with Add Row button */}
<div className="flex justify-between items-center mb-4">
<h3 className="text-md font-semibold text-gray-800 dark:text-white">
Asset Finance Books
</h3>
{isEditing && (
<button
type="button"
onClick={addFinanceRow}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center gap-2"
>
<span>+</span> Add Row
</button>
)}
</div>
{/* Show message if no rows */}
{(!formData.finance_books || formData.finance_books.length === 0) && (
<div className="text-center py-8 text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-900 rounded-lg">
No finance books added yet. Click "Add Row" to add one.
</div>
)}
{/* TABLE - Full width desktop view with overflow fix */}
{formData.finance_books && formData.finance_books.length > 0 && (
<div className="overflow-visible">
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr className="bg-gray-100 dark:bg-gray-700 border-b border-gray-300 dark:border-gray-600">
<th className="text-left px-4 py-3 text-sm font-semibold text-gray-700 dark:text-gray-300 min-w-[200px]">
Finance Book
</th>
<th className="text-left px-4 py-3 text-sm font-semibold text-gray-700 dark:text-gray-300 min-w-[200px]">
Depreciation Method*
</th>
<th className="text-left px-4 py-3 text-sm font-semibold text-gray-700 dark:text-gray-300 min-w-[180px]">
Total Depreciations*
</th>
<th className="text-left px-4 py-3 text-sm font-semibold text-gray-700 dark:text-gray-300 min-w-[180px]">
Frequency (Months)*
</th>
<th className="text-left px-4 py-3 text-sm font-semibold text-gray-700 dark:text-gray-300 min-w-[200px]">
Depreciation Posting Date*
</th>
{isEditing && (
<th className="text-center px-4 py-3 text-sm font-semibold text-gray-700 dark:text-gray-300 min-w-[120px]">
Action
</th>
)}
</tr>
</thead>
<tbody>
{formData.finance_books.map((row: AssetFinanceBookRow, idx: number) => (
<tr
key={idx}
className="border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-750"
>
{/* Finance Book - with overflow visible */}
<td className="px-4 py-3 relative" style={{ overflow: 'visible' }}>
<div className="relative z-20">
<LinkField
label=""
doctype="Finance Book"
value={row.finance_book || ''}
onChange={(val) => updateFinanceRow(idx, { finance_book: val })}
disabled={!isEditing}
/>
</div>
</td>
{/* Depreciation Method */}
<td className="px-4 py-3">
<select
value={row.depreciation_method || ''}
onChange={(e) => updateFinanceRow(idx, { depreciation_method: e.target.value })}
disabled={!isEditing}
className="w-full px-3 py-2 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 Method</option>
<option value="Straight Line">Straight Line</option>
<option value="Double Declining Balance">Double Declining Balance</option>
<option value="Written Down Value">Written Down Value</option>
<option value="Manual">Manual</option>
</select>
</td>
{/* Total Depreciations */}
<td className="px-4 py-3">
<input
type="number"
value={row.total_number_of_depreciations ?? ''}
onChange={(e) =>
updateFinanceRow(idx, {
total_number_of_depreciations: Number(e.target.value),
})
}
disabled={!isEditing}
placeholder="0"
className="w-full px-3 py-2 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"
/>
</td>
{/* Frequency */}
<td className="px-4 py-3">
<input
type="number"
value={row.frequency_of_depreciation ?? ''}
onChange={(e) =>
updateFinanceRow(idx, {
frequency_of_depreciation: Number(e.target.value),
})
}
disabled={!isEditing}
placeholder="0"
className="w-full px-3 py-2 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"
/>
</td>
{/* Start Date */}
<td className="px-4 py-3">
<input
type="date"
value={row.depreciation_start_date || ''}
onChange={(e) =>
updateFinanceRow(idx, { depreciation_start_date: e.target.value })
}
disabled={!isEditing}
className="w-full px-3 py-2 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"
/>
</td>
{/* REMOVE BUTTON */}
{isEditing && (
<td className="px-4 py-3 text-center">
<button
type="button"
onClick={() => removeFinanceRow(idx)}
className="px-3 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors"
>
Remove
</button>
</td>
)}
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
)}
</div>
</div>
{/* Financial Details - Full Width */}
{/* <div className="mt-6">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6"> <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<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"> <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">
Financial Details Financial Details
</h2> </h2>
{/* Financial Input Fields */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div> <div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1"> <label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
@ -1081,7 +1289,7 @@ const AssetDetail: React.FC = () => {
</div> </div>
</div> </div>
{/* Calculate Depreciation Checkbox */}
<div className="flex items-center mb-6"> <div className="flex items-center mb-6">
<input <input
id="calculate_depreciation" id="calculate_depreciation"
@ -1104,7 +1312,6 @@ const AssetDetail: React.FC = () => {
</label> </label>
</div> </div>
{/* Depreciation Schedule Table */}
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full border-collapse border border-gray-300 dark:border-gray-600"> <table className="w-full border-collapse border border-gray-300 dark:border-gray-600">
<thead> <thead>
@ -1127,7 +1334,7 @@ const AssetDetail: React.FC = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{/* Sample rows - Replace with actual data from API */}
<tr className="hover:bg-gray-50 dark:hover:bg-gray-700/50"> <tr className="hover:bg-gray-50 dark:hover:bg-gray-700/50">
<td className="border border-gray-300 dark:border-gray-600 px-4 py-2 text-xs text-gray-700 dark:text-gray-300"> <td className="border border-gray-300 dark:border-gray-600 px-4 py-2 text-xs text-gray-700 dark:text-gray-300">
Year 1 Year 1
@ -1188,7 +1395,7 @@ const AssetDetail: React.FC = () => {
)} )}
</div> </div>
</div> </div>
</div> </div> */}
</form> </form>
</div> </div>
); );

View File

@ -1,7 +1,8 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useAssets, useAssetMutations } from '../hooks/useAsset'; import { useAssets, useAssetMutations } from '../hooks/useAsset';
import { FaPlus, FaSearch, FaEdit, FaEye, FaTrash, FaCopy, FaEllipsisV, FaDownload, FaPrint, FaFileExport } from 'react-icons/fa'; import { FaPlus, FaSearch, FaEdit, FaEye, FaTrash, FaCopy, FaEllipsisV, FaDownload, FaPrint, FaFileExport, FaTimes } from 'react-icons/fa';
import LinkField from '../components/LinkField';
const AssetList: React.FC = () => { const AssetList: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -12,8 +13,65 @@ const AssetList: React.FC = () => {
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
const limit = 20; const limit = 20;
// Filter states
const [filterAssetId, setFilterAssetId] = useState('');
const [filterCompany, setFilterCompany] = useState('');
const [filterManufacturer, setFilterManufacturer] = useState('');
const [filterSupplier, setFilterSupplier] = useState('');
const [filterLocation, setFilterLocation] = useState('');
const [filterDepartment, setFilterDepartment] = useState('');
const [filterModality, setFilterModality] = useState('');
const [filterDeviceStatus, setFilterDeviceStatus] = useState('');
const [filterAssetName, setFilterAssetName] = useState('');
const [filterSerialNumber, setFilterSerialNumber] = useState('');
// Temporary states for text inputs (not applied until user stops typing or presses Enter)
const [tempAssetName, setTempAssetName] = useState('');
const [tempSerialNumber, setTempSerialNumber] = useState('');
// Debounce timer refs
const assetNameDebounceRef = useRef<number | null>(null);
const serialNumberDebounceRef = useRef<number | null>(null);
// Build filters object
const filters: Record<string, any> = {};
if (filterAssetId) {
filters['name'] = filterAssetId;
}
if (filterCompany) {
filters['company'] = filterCompany;
}
if (filterManufacturer) {
filters['custom_manufacturer'] = filterManufacturer;
}
if (filterSupplier) {
filters['supplier'] = filterSupplier;
}
if (filterLocation) {
filters['location'] = filterLocation;
}
if (filterDepartment) {
filters['department'] = filterDepartment;
}
if (filterModality) {
filters['custom_modality'] = filterModality;
}
if (filterDeviceStatus) {
filters['custom_device_status'] = filterDeviceStatus;
}
if (filterAssetName) {
filters['asset_name'] = ['like', `%${filterAssetName}%`];
}
if (filterSerialNumber) {
filters['custom_serial_number'] = ['like', `%${filterSerialNumber}%`];
}
if (searchTerm) {
// Search across multiple fields
filters['asset_name'] = ['like', `%${searchTerm}%`];
}
const { assets, totalCount, hasMore, loading, error, refetch } = useAssets( const { assets, totalCount, hasMore, loading, error, refetch } = useAssets(
{}, filters,
limit, limit,
page * limit, page * limit,
'creation desc' 'creation desc'
@ -21,6 +79,73 @@ const AssetList: React.FC = () => {
const { deleteAsset, loading: mutationLoading } = useAssetMutations(); const { deleteAsset, loading: mutationLoading } = useAssetMutations();
// Reset page when filters change
useEffect(() => {
setPage(0);
}, [filterAssetId, filterCompany, filterManufacturer, filterSupplier, filterLocation, filterDepartment, filterModality,
filterDeviceStatus, filterAssetName, filterSerialNumber, searchTerm]);
// Debounce function for text inputs
const handleAssetNameChange = (value: string) => {
setTempAssetName(value);
// Clear existing timeout
if (assetNameDebounceRef.current) {
clearTimeout(assetNameDebounceRef.current);
}
// Set new timeout - apply filter after 800ms of no typing
assetNameDebounceRef.current = setTimeout(() => {
setFilterAssetName(value);
}, 800);
};
const handleSerialNumberChange = (value: string) => {
setTempSerialNumber(value);
// Clear existing timeout
if (serialNumberDebounceRef.current) {
clearTimeout(serialNumberDebounceRef.current);
}
// Set new timeout - apply filter after 800ms of no typing
serialNumberDebounceRef.current = setTimeout(() => {
setFilterSerialNumber(value);
}, 800);
};
// Handle Enter key press for immediate filter application
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>, type: 'assetName' | 'serialNumber') => {
if (e.key === 'Enter') {
if (type === 'assetName') {
if (assetNameDebounceRef.current) {
clearTimeout(assetNameDebounceRef.current);
}
setFilterAssetName(tempAssetName);
} else if (type === 'serialNumber') {
if (serialNumberDebounceRef.current) {
clearTimeout(serialNumberDebounceRef.current);
}
setFilterSerialNumber(tempSerialNumber);
}
}
};
// Cleanup timeouts on unmount
useEffect(() => {
return () => {
if (assetNameDebounceRef.current) {
clearTimeout(assetNameDebounceRef.current);
}
if (serialNumberDebounceRef.current) {
clearTimeout(serialNumberDebounceRef.current);
}
};
}, []);
// Close dropdown when clicking outside // Close dropdown when clicking outside
useEffect(() => { useEffect(() => {
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
@ -107,6 +232,34 @@ const AssetList: React.FC = () => {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}; };
const handleClearFilters = () => {
setFilterAssetId('');
setFilterCompany('');
setFilterManufacturer('');
setFilterSupplier('');
setFilterLocation('');
setFilterDepartment('');
setFilterModality('');
setFilterDeviceStatus('');
setFilterAssetName('');
setFilterSerialNumber('');
setTempAssetName('');
setTempSerialNumber('');
setSearchTerm('');
// Clear any pending debounce timers
if (assetNameDebounceRef.current) {
clearTimeout(assetNameDebounceRef.current);
}
if (serialNumberDebounceRef.current) {
clearTimeout(serialNumberDebounceRef.current);
}
};
const hasActiveFilters = filterAssetId || filterCompany || filterManufacturer || filterSupplier ||
filterLocation || filterDepartment || filterModality || filterDeviceStatus ||
filterAssetName || filterSerialNumber || searchTerm;
if (loading && page === 0) { if (loading && page === 0) {
return ( return (
<div className="flex items-center justify-center h-screen bg-gray-50 dark:bg-gray-900"> <div className="flex items-center justify-center h-screen bg-gray-50 dark:bg-gray-900">
@ -189,7 +342,7 @@ const AssetList: React.FC = () => {
</div> </div>
{/* Search Bar */} {/* Search Bar */}
<div className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow p-4"> {/* <div className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow p-4">
<div className="flex items-center gap-2 border border-gray-300 dark:border-gray-600 rounded-lg px-4 py-2 bg-white dark:bg-gray-700"> <div className="flex items-center gap-2 border border-gray-300 dark:border-gray-600 rounded-lg px-4 py-2 bg-white dark:bg-gray-700">
<FaSearch className="text-gray-400 dark:text-gray-500" /> <FaSearch className="text-gray-400 dark:text-gray-500" />
<input <input
@ -199,7 +352,333 @@ const AssetList: React.FC = () => {
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="flex-1 outline-none text-gray-700 dark:text-gray-200 bg-transparent" className="flex-1 outline-none text-gray-700 dark:text-gray-200 bg-transparent"
/> />
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<FaTimes />
</button>
)}
</div> </div>
</div> */}
{/* Filter Section */}
<div className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">Filters</h3>
{hasActiveFilters && (
<button
onClick={handleClearFilters}
className="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 flex items-center gap-1"
>
<FaTimes />
Clear All Filters
</button>
)}
</div>
{/* First Row - 5 filters */}
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-4">
{/* Asset ID Filter */}
<div>
<LinkField
label="Asset ID"
doctype="Asset"
value={filterAssetId}
onChange={(val) => setFilterAssetId(val)}
placeholder="Select Asset ID"
disabled={false}
/>
</div>
{/* Company Filter */}
<div>
<LinkField
label="Hospital"
doctype="Company"
value={filterCompany}
onChange={(val) => setFilterCompany(val)}
placeholder="Select Hospital"
disabled={false}
filters={{ domain: 'Healthcare' }}
/>
</div>
{/* Location Filter */}
<div>
<LinkField
label="Location"
doctype="Location"
value={filterLocation}
onChange={(val) => setFilterLocation(val)}
placeholder="Select Location"
disabled={false}
/>
</div>
{/* Department Filter */}
<div>
<LinkField
label="Department"
doctype="Department"
value={filterDepartment}
onChange={(val) => setFilterDepartment(val)}
placeholder="Select Department"
disabled={false}
/>
</div>
{/* Modality Filter */}
<div>
<LinkField
label="Modality"
doctype="Modality"
value={filterModality}
onChange={(val) => setFilterModality(val)}
placeholder="Select Modality"
disabled={false}
/>
</div>
</div>
{/* Second Row - 5 filters */}
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4">
{/* Manufacturer Filter */}
<div>
<LinkField
label="Manufacturer"
doctype="Manufacturer"
value={filterManufacturer}
onChange={(val) => setFilterManufacturer(val)}
placeholder="Select Manufacturer"
disabled={false}
/>
</div>
{/* Supplier Filter */}
<div>
<LinkField
label="Supplier"
doctype="Supplier"
value={filterSupplier}
onChange={(val) => setFilterSupplier(val)}
placeholder="Select Supplier"
disabled={false}
/>
</div>
{/* Device Status Filter - Dropdown */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Device Status
</label>
<select
value={filterDeviceStatus}
onChange={(e) => setFilterDeviceStatus(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="">All Status</option>
<option value="Up">Up</option>
<option value="Down">Down</option>
</select>
</div>
{/* Asset Name Filter - Text Input */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Asset Name
</label>
<div className="relative">
<input
type="text"
value={tempAssetName}
onChange={(e) => handleAssetNameChange(e.target.value)}
// onKeyPress={(e) => handleKeyPress(e, 'assetName')}
onKeyDown={(e) => handleKeyPress(e, 'assetName')}
placeholder="Type to search..."
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
{tempAssetName && tempAssetName !== filterAssetName && (
<span className="absolute right-2 top-2 text-xs text-gray-400">
typing...
</span>
)}
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Press Enter or wait to apply
</p>
</div>
{/* Serial Number Filter - Text Input */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Serial Number
</label>
{/* <input
type="text"
value={filterSerialNumber}
onChange={(e) => setFilterSerialNumber(e.target.value)}
placeholder="Enter serial number"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/> */}
<div className="relative">
<input
type="text"
value={tempSerialNumber}
onChange={(e) => handleSerialNumberChange(e.target.value)}
// onKeyPress={(e) => handleKeyPress(e, 'serialNumber')}
onKeyDown={(e) => handleKeyPress(e, 'serialNumber')}
placeholder="Type to search..."
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
{tempSerialNumber && tempSerialNumber !== filterSerialNumber && (
<span className="absolute right-2 top-2 text-xs text-gray-400">
typing...
</span>
)}
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Press Enter or wait to apply
</p>
</div>
</div>
{/* Active Filters Display */}
{hasActiveFilters && (
<div className="mt-4 flex flex-wrap gap-2">
{filterAssetId && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full text-sm">
Asset ID: {filterAssetId}
<button
onClick={() => setFilterAssetId('')}
className="hover:text-blue-600 dark:hover:text-blue-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
{filterCompany && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-full text-sm">
Hospital: {filterCompany}
<button
onClick={() => setFilterCompany('')}
className="hover:text-green-600 dark:hover:text-green-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
{filterLocation && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full text-sm">
Location: {filterLocation}
<button
onClick={() => setFilterLocation('')}
className="hover:text-purple-600 dark:hover:text-purple-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
{filterDepartment && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 rounded-full text-sm">
Department: {filterDepartment}
<button
onClick={() => setFilterDepartment('')}
className="hover:text-yellow-600 dark:hover:text-yellow-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
{filterModality && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-pink-100 dark:bg-pink-900 text-pink-800 dark:text-pink-200 rounded-full text-sm">
Modality: {filterModality}
<button
onClick={() => setFilterModality('')}
className="hover:text-pink-600 dark:hover:text-pink-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
{filterManufacturer && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200 rounded-full text-sm">
Manufacturer: {filterManufacturer}
<button
onClick={() => setFilterManufacturer('')}
className="hover:text-indigo-600 dark:hover:text-indigo-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
{filterSupplier && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-teal-100 dark:bg-teal-900 text-teal-800 dark:text-teal-200 rounded-full text-sm">
Supplier: {filterSupplier}
<button
onClick={() => setFilterSupplier('')}
className="hover:text-teal-600 dark:hover:text-teal-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
{filterDeviceStatus && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200 rounded-full text-sm">
Status: {filterDeviceStatus}
<button
onClick={() => setFilterDeviceStatus('')}
className="hover:text-orange-600 dark:hover:text-orange-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
{filterAssetName && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-cyan-100 dark:bg-cyan-900 text-cyan-800 dark:text-cyan-200 rounded-full text-sm">
Asset Name: "{filterAssetName}"
<button
// onClick={() => setFilterAssetName('')}
onClick={() => {
setFilterAssetName('');
setTempAssetName('');
}}
className="hover:text-cyan-600 dark:hover:text-cyan-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
{filterSerialNumber && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-lime-100 dark:bg-lime-900 text-lime-800 dark:text-lime-200 rounded-full text-sm">
Serial: "{filterSerialNumber}"
<button
// onClick={() => setFilterSerialNumber('')}
onClick={() => {
setFilterSerialNumber('');
setTempSerialNumber('');
}}
className="hover:text-lime-600 dark:hover:text-lime-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
{searchTerm && (
<span className="inline-flex items-center gap-1 px-3 py-1 bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full text-sm">
Search: "{searchTerm}"
<button
onClick={() => setSearchTerm('')}
className="hover:text-purple-600 dark:hover:text-purple-400"
>
<FaTimes className="text-xs" />
</button>
</span>
)}
</div>
)}
</div> </div>
{/* Assets Table */} {/* Assets Table */}
@ -235,12 +714,29 @@ const AssetList: React.FC = () => {
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<FaSearch className="text-4xl text-gray-300 dark:text-gray-600 mb-2" /> <FaSearch className="text-4xl text-gray-300 dark:text-gray-600 mb-2" />
<p>No assets found</p> <p>No assets found</p>
<button {/* <button
onClick={handleCreateNew} onClick={handleCreateNew}
className="mt-4 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 underline" className="mt-4 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 underline"
> >
Create your first asset Create your first asset
</button> </button> */}
{hasActiveFilters ? (
<button
onClick={handleClearFilters}
className="mt-4 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 underline"
>
Clear filters
</button>
) : (
<button
onClick={handleCreateNew}
className="mt-4 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 underline"
>
Create your first asset
</button>
)}
</div> </div>
</td> </td>
</tr> </tr>
@ -265,7 +761,8 @@ const AssetList: React.FC = () => {
{asset.location || '-'} {asset.location || '-'}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<span className={` {asset.custom_device_status || '-'}
{/* <span className={`
px-3 py-1 inline-flex text-xs leading-5 font-semibold rounded-full px-3 py-1 inline-flex text-xs leading-5 font-semibold rounded-full
${asset.custom_device_status === 'Operational' ? 'bg-green-100 text-green-800' : ''} ${asset.custom_device_status === 'Operational' ? 'bg-green-100 text-green-800' : ''}
${asset.custom_device_status === 'Under Maintenance' ? 'bg-yellow-100 text-yellow-800' : ''} ${asset.custom_device_status === 'Under Maintenance' ? 'bg-yellow-100 text-yellow-800' : ''}
@ -273,7 +770,7 @@ const AssetList: React.FC = () => {
${!asset.custom_device_status ? 'bg-gray-100 text-gray-800' : ''} ${!asset.custom_device_status ? 'bg-gray-100 text-gray-800' : ''}
`}> `}>
{asset.custom_device_status || 'Unknown'} {asset.custom_device_status || 'Unknown'}
</span> </span> */}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium"> <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}> <div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>