Added filters and buttons for print QR
This commit is contained in:
parent
9cf1f3201b
commit
d4478bdacc
@ -9,6 +9,7 @@ interface LinkFieldProps {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
filters?: Record<string, any>;
|
filters?: Record<string, any>;
|
||||||
|
compact?: boolean; // ✅ Add this prop
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinkField: React.FC<LinkFieldProps> = ({
|
const LinkField: React.FC<LinkFieldProps> = ({
|
||||||
@ -19,6 +20,7 @@ const LinkField: React.FC<LinkFieldProps> = ({
|
|||||||
placeholder,
|
placeholder,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
filters = {},
|
filters = {},
|
||||||
|
compact = false, // ✅ Default to false
|
||||||
}) => {
|
}) => {
|
||||||
const [searchResults, setSearchResults] = useState<{ value: string; description?: string }[]>([]);
|
const [searchResults, setSearchResults] = useState<{ value: string; description?: string }[]>([]);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
@ -35,7 +37,6 @@ const LinkField: React.FC<LinkFieldProps> = ({
|
|||||||
|
|
||||||
// Add filters if provided
|
// Add filters if provided
|
||||||
if (filters && Object.keys(filters).length > 0) {
|
if (filters && Object.keys(filters).length > 0) {
|
||||||
// Convert filters to JSON string for Frappe API
|
|
||||||
params.append('filters', JSON.stringify(filters));
|
params.append('filters', JSON.stringify(filters));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,15 +55,13 @@ const LinkField: React.FC<LinkFieldProps> = ({
|
|||||||
if (isDropdownOpen) {
|
if (isDropdownOpen) {
|
||||||
searchLink(searchText || '');
|
searchLink(searchText || '');
|
||||||
}
|
}
|
||||||
}, [isDropdownOpen, filters]); // Re-fetch when filters change
|
}, [isDropdownOpen, filters]);
|
||||||
|
|
||||||
// Close dropdown when clicking outside
|
// Close dropdown when clicking outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
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('');
|
setSearchText('');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -85,8 +84,8 @@ const LinkField: React.FC<LinkFieldProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="relative w-full mb-4">
|
<div ref={containerRef} className={`relative w-full ${compact ? 'mb-2' : 'mb-4'}`}>
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label className={`block font-medium text-gray-700 dark:text-gray-300 ${compact ? 'text-[10px] mb-0.5' : 'text-sm mb-1'}`}>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@ -96,9 +95,14 @@ const LinkField: React.FC<LinkFieldProps> = ({
|
|||||||
value={isDropdownOpen ? searchText : value}
|
value={isDropdownOpen ? searchText : value}
|
||||||
placeholder={placeholder || `Select ${label}`}
|
placeholder={placeholder || `Select ${label}`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md
|
className={`w-full 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
|
focus:outline-none disabled:bg-gray-100 dark:disabled:bg-gray-700
|
||||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-white ${value ? 'pr-8' : ''}`}
|
bg-white dark:bg-gray-700 text-gray-900 dark:text-white
|
||||||
|
${compact
|
||||||
|
? 'px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 rounded'
|
||||||
|
: 'px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500'
|
||||||
|
}
|
||||||
|
${value ? (compact ? 'pr-5' : 'pr-8') : ''}`}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
setDropdownOpen(true);
|
setDropdownOpen(true);
|
||||||
@ -112,12 +116,13 @@ const LinkField: React.FC<LinkFieldProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Clear button - only show when there's a selected value and not disabled */}
|
{/* Clear button */}
|
||||||
{value && !disabled && !isDropdownOpen && (
|
{value && !disabled && !isDropdownOpen && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleClear}
|
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"
|
className={`absolute top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300
|
||||||
|
${compact ? 'right-1 text-xs' : 'right-2 text-sm'}`}
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
@ -125,24 +130,22 @@ const LinkField: React.FC<LinkFieldProps> = ({
|
|||||||
</div>
|
</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
|
||||||
rounded-md mt-1 max-h-48 overflow-auto w-full shadow-lg">
|
rounded-md overflow-auto w-full shadow-lg
|
||||||
|
${compact ? 'mt-0.5 max-h-36' : 'mt-1 max-h-48'}`}>
|
||||||
{searchResults.map((item, idx) => (
|
{searchResults.map((item, idx) => (
|
||||||
<li
|
<li
|
||||||
key={idx}
|
key={idx}
|
||||||
// onClick={() => {
|
|
||||||
// onChange(item.value);
|
|
||||||
// setDropdownOpen(false);
|
|
||||||
// }}
|
|
||||||
onClick={() => handleSelect(item.value)}
|
onClick={() => handleSelect(item.value)}
|
||||||
className={`px-3 py-2 cursor-pointer
|
className={`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
|
||||||
|
${compact ? 'px-2 py-1 text-xs' : 'px-3 py-2 text-sm'}
|
||||||
${value === item.value ? 'bg-blue-50 dark:bg-blue-700 font-semibold' : ''}`}
|
${value === item.value ? 'bg-blue-50 dark:bg-blue-700 font-semibold' : ''}`}
|
||||||
>
|
>
|
||||||
{item.value}
|
{item.value}
|
||||||
{item.description && (
|
{item.description && (
|
||||||
<span className="text-gray-600 dark:text-gray-300 text-xs ml-2">
|
<span className={`text-gray-600 dark:text-gray-300 ml-2
|
||||||
|
${compact ? 'text-[9px] ml-1' : 'text-xs ml-2'}`}>
|
||||||
{item.description}
|
{item.description}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -153,8 +156,9 @@ const LinkField: React.FC<LinkFieldProps> = ({
|
|||||||
|
|
||||||
{/* Show message when no results found */}
|
{/* Show message when no results found */}
|
||||||
{isDropdownOpen && searchResults.length === 0 && !disabled && (
|
{isDropdownOpen && searchResults.length === 0 && !disabled && (
|
||||||
<div className="absolute z-50 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600
|
<div className={`absolute z-50 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600
|
||||||
rounded-md mt-1 w-full shadow-lg p-3 text-center text-gray-500 dark:text-gray-400 text-sm">
|
rounded-md w-full shadow-lg text-center text-gray-500 dark:text-gray-400
|
||||||
|
${compact ? 'mt-0.5 p-1.5 text-[10px]' : 'mt-1 p-3 text-sm'}`}>
|
||||||
No results found
|
No results found
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -377,6 +377,129 @@ const AssetDetail: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handlePrintQR = () => {
|
||||||
|
if (!qrCodeUrl || !asset) return;
|
||||||
|
|
||||||
|
const printWindow = window.open('', '_blank');
|
||||||
|
if (!printWindow) return;
|
||||||
|
|
||||||
|
printWindow.document.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Print QR Code - ${asset.name}</title>
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
margin: 0.5in;
|
||||||
|
size: auto;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.qr-container {
|
||||||
|
text-align: center;
|
||||||
|
border: 2px solid #333;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: white;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
.qr-image {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
margin: 15px 0;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.asset-info {
|
||||||
|
margin: 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="qr-container">
|
||||||
|
<h1>Asset QR Code</h1>
|
||||||
|
<div class="asset-info">
|
||||||
|
<strong>Asset ID:</strong> ${asset.name}<br/>
|
||||||
|
<strong>Asset Name:</strong> ${asset.asset_name || 'N/A'}
|
||||||
|
</div>
|
||||||
|
<img src="${qrCodeUrl}" alt="QR Code" class="qr-image" onload="window.print();" />
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
|
||||||
|
printWindow.document.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrintStayPlugged = () => {
|
||||||
|
const printWindow = window.open('', '_blank');
|
||||||
|
if (!printWindow) return;
|
||||||
|
|
||||||
|
printWindow.document.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Stay Plugged</title>
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
margin: 0.5in;
|
||||||
|
size: auto;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.image-container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.stay-plugged-image {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="image-container">
|
||||||
|
<img src="/files/Stay Plugged.jpg" alt="Stay Plugged" class="stay-plugged-image" onload="window.print();" />
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
|
||||||
|
printWindow.document.close();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@ -406,6 +529,24 @@ const AssetDetail: React.FC = () => {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{!isNewAsset && !isEditing && !isCancelled && (
|
{!isNewAsset && !isEditing && !isCancelled && (
|
||||||
<>
|
<>
|
||||||
|
{/* Print QR Button - Only show if QR code exists */}
|
||||||
|
{qrCodeUrl && (
|
||||||
|
<button
|
||||||
|
onClick={handlePrintQR}
|
||||||
|
className="bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded-lg flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<FaQrcode />
|
||||||
|
Print QR
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{/* Stay Plugged Button */}
|
||||||
|
<button
|
||||||
|
onClick={handlePrintStayPlugged}
|
||||||
|
className="bg-purple-500 hover:bg-purple-700 text-white px-6 py-2 rounded-lg flex items-center gap-2"
|
||||||
|
>
|
||||||
|
{/* <FaQrcode /> */}
|
||||||
|
Stay Plugged
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsEditing(true)}
|
onClick={() => setIsEditing(true)}
|
||||||
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg flex items-center gap-2"
|
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg flex items-center gap-2"
|
||||||
|
|||||||
@ -143,7 +143,18 @@ const AssetList: React.FC = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Helper function to format date
|
||||||
|
const formatDate = (dateString?: string) => {
|
||||||
|
if (!dateString) return '-';
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Close dropdown when clicking outside
|
// Close dropdown when clicking outside
|
||||||
@ -364,22 +375,22 @@ const AssetList: React.FC = () => {
|
|||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
{/* Filter Section */}
|
{/* Filter Section */}
|
||||||
<div className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
<div className="mb-3 bg-white dark:bg-gray-800 rounded-lg shadow p-2">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-1.5">
|
||||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">Filters</h3>
|
<h3 className="text-[11px] font-semibold text-gray-700 dark:text-gray-300">Filters</h3>
|
||||||
{hasActiveFilters && (
|
{hasActiveFilters && (
|
||||||
<button
|
<button
|
||||||
onClick={handleClearFilters}
|
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"
|
className="text-[10px] text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 flex items-center gap-0.5"
|
||||||
>
|
>
|
||||||
<FaTimes />
|
<FaTimes className="text-[10px]" />
|
||||||
Clear All Filters
|
Clear All Filters
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* First Row - 5 filters */}
|
{/* First Row - 5 filters */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-2 mb-2">
|
||||||
{/* Asset ID Filter */}
|
{/* Asset ID Filter */}
|
||||||
<div>
|
<div>
|
||||||
<LinkField
|
<LinkField
|
||||||
@ -389,6 +400,7 @@ const AssetList: React.FC = () => {
|
|||||||
onChange={(val) => setFilterAssetId(val)}
|
onChange={(val) => setFilterAssetId(val)}
|
||||||
placeholder="Select Asset ID"
|
placeholder="Select Asset ID"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
|
compact={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -401,9 +413,59 @@ const AssetList: React.FC = () => {
|
|||||||
onChange={(val) => setFilterCompany(val)}
|
onChange={(val) => setFilterCompany(val)}
|
||||||
placeholder="Select Hospital"
|
placeholder="Select Hospital"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
|
compact={true}
|
||||||
filters={{ domain: 'Healthcare' }}
|
filters={{ domain: 'Healthcare' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Asset Name Filter - Text Input */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-[10px] font-medium text-gray-700 dark:text-gray-300 mb-0.5">
|
||||||
|
Asset Name
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={tempAssetName}
|
||||||
|
onChange={(e) => handleAssetNameChange(e.target.value)}
|
||||||
|
onKeyDown={(e) => handleKeyPress(e, 'assetName')}
|
||||||
|
placeholder="Type to search..."
|
||||||
|
className="w-full px-2 py-1 text-xs 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"
|
||||||
|
/>
|
||||||
|
{tempAssetName && tempAssetName !== filterAssetName && (
|
||||||
|
<span className="absolute right-1.5 top-1 text-[9px] text-gray-400">
|
||||||
|
typing...
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-[9px] text-gray-500 dark:text-gray-400 mt-0.5">
|
||||||
|
Press Enter or wait
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Serial Number Filter - Text Input */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-[10px] font-medium text-gray-700 dark:text-gray-300 mb-0.5">
|
||||||
|
Serial Number
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={tempSerialNumber}
|
||||||
|
onChange={(e) => handleSerialNumberChange(e.target.value)}
|
||||||
|
onKeyDown={(e) => handleKeyPress(e, 'serialNumber')}
|
||||||
|
placeholder="Type to search..."
|
||||||
|
className="w-full px-2 py-1 text-xs 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"
|
||||||
|
/>
|
||||||
|
{tempSerialNumber && tempSerialNumber !== filterSerialNumber && (
|
||||||
|
<span className="absolute right-1.5 top-1 text-[9px] text-gray-400">
|
||||||
|
typing...
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-[9px] text-gray-500 dark:text-gray-400 mt-0.5">
|
||||||
|
Press Enter or wait
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Location Filter */}
|
{/* Location Filter */}
|
||||||
<div>
|
<div>
|
||||||
@ -414,36 +476,13 @@ const AssetList: React.FC = () => {
|
|||||||
onChange={(val) => setFilterLocation(val)}
|
onChange={(val) => setFilterLocation(val)}
|
||||||
placeholder="Select Location"
|
placeholder="Select Location"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
/>
|
compact={true}
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Second Row - 5 filters */}
|
{/* Second Row - 5 filters */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-2">
|
||||||
{/* Manufacturer Filter */}
|
{/* Manufacturer Filter */}
|
||||||
<div>
|
<div>
|
||||||
<LinkField
|
<LinkField
|
||||||
@ -453,6 +492,7 @@ const AssetList: React.FC = () => {
|
|||||||
onChange={(val) => setFilterManufacturer(val)}
|
onChange={(val) => setFilterManufacturer(val)}
|
||||||
placeholder="Select Manufacturer"
|
placeholder="Select Manufacturer"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
|
compact={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -465,18 +505,45 @@ const AssetList: React.FC = () => {
|
|||||||
onChange={(val) => setFilterSupplier(val)}
|
onChange={(val) => setFilterSupplier(val)}
|
||||||
placeholder="Select Supplier"
|
placeholder="Select Supplier"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
|
compact={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Department Filter */}
|
||||||
|
<div>
|
||||||
|
<LinkField
|
||||||
|
label="Department"
|
||||||
|
doctype="Department"
|
||||||
|
value={filterDepartment}
|
||||||
|
onChange={(val) => setFilterDepartment(val)}
|
||||||
|
placeholder="Select Department"
|
||||||
|
disabled={false}
|
||||||
|
compact={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modality Filter */}
|
||||||
|
<div>
|
||||||
|
<LinkField
|
||||||
|
label="Modality"
|
||||||
|
doctype="Modality"
|
||||||
|
value={filterModality}
|
||||||
|
onChange={(val) => setFilterModality(val)}
|
||||||
|
placeholder="Select Modality"
|
||||||
|
disabled={false}
|
||||||
|
compact={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Device Status Filter - Dropdown */}
|
{/* Device Status Filter - Dropdown */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label className="block text-[10px] font-medium text-gray-700 dark:text-gray-300 mb-0.5">
|
||||||
Device Status
|
Device Status
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={filterDeviceStatus}
|
value={filterDeviceStatus}
|
||||||
onChange={(e) => setFilterDeviceStatus(e.target.value)}
|
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"
|
className="w-full px-2 py-1 text-xs 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"
|
||||||
>
|
>
|
||||||
<option value="">All Status</option>
|
<option value="">All Status</option>
|
||||||
<option value="Up">Up</option>
|
<option value="Up">Up</option>
|
||||||
@ -484,196 +551,135 @@ const AssetList: React.FC = () => {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Active Filters Display */}
|
{/* Active Filters Display */}
|
||||||
{hasActiveFilters && (
|
{hasActiveFilters && (
|
||||||
<div className="mt-4 flex flex-wrap gap-2">
|
<div className="mt-2 flex flex-wrap gap-1">
|
||||||
{filterAssetId && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full text-[10px]">
|
||||||
Asset ID: {filterAssetId}
|
Asset ID: {filterAssetId}
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterAssetId('')}
|
onClick={() => setFilterAssetId('')}
|
||||||
className="hover:text-blue-600 dark:hover:text-blue-400"
|
className="hover:text-blue-600 dark:hover:text-blue-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{filterCompany && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-full text-[10px]">
|
||||||
Hospital: {filterCompany}
|
Hospital: {filterCompany}
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterCompany('')}
|
onClick={() => setFilterCompany('')}
|
||||||
className="hover:text-green-600 dark:hover:text-green-400"
|
className="hover:text-green-600 dark:hover:text-green-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{filterLocation && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full text-[10px]">
|
||||||
Location: {filterLocation}
|
Location: {filterLocation}
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterLocation('')}
|
onClick={() => setFilterLocation('')}
|
||||||
className="hover:text-purple-600 dark:hover:text-purple-400"
|
className="hover:text-purple-600 dark:hover:text-purple-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{filterDepartment && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 rounded-full text-[10px]">
|
||||||
Department: {filterDepartment}
|
Department: {filterDepartment}
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterDepartment('')}
|
onClick={() => setFilterDepartment('')}
|
||||||
className="hover:text-yellow-600 dark:hover:text-yellow-400"
|
className="hover:text-yellow-600 dark:hover:text-yellow-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{filterModality && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-pink-100 dark:bg-pink-900 text-pink-800 dark:text-pink-200 rounded-full text-[10px]">
|
||||||
Modality: {filterModality}
|
Modality: {filterModality}
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterModality('')}
|
onClick={() => setFilterModality('')}
|
||||||
className="hover:text-pink-600 dark:hover:text-pink-400"
|
className="hover:text-pink-600 dark:hover:text-pink-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{filterManufacturer && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200 rounded-full text-[10px]">
|
||||||
Manufacturer: {filterManufacturer}
|
Manufacturer: {filterManufacturer}
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterManufacturer('')}
|
onClick={() => setFilterManufacturer('')}
|
||||||
className="hover:text-indigo-600 dark:hover:text-indigo-400"
|
className="hover:text-indigo-600 dark:hover:text-indigo-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{filterSupplier && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-teal-100 dark:bg-teal-900 text-teal-800 dark:text-teal-200 rounded-full text-[10px]">
|
||||||
Supplier: {filterSupplier}
|
Supplier: {filterSupplier}
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterSupplier('')}
|
onClick={() => setFilterSupplier('')}
|
||||||
className="hover:text-teal-600 dark:hover:text-teal-400"
|
className="hover:text-teal-600 dark:hover:text-teal-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{filterDeviceStatus && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200 rounded-full text-[10px]">
|
||||||
Status: {filterDeviceStatus}
|
Status: {filterDeviceStatus}
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterDeviceStatus('')}
|
onClick={() => setFilterDeviceStatus('')}
|
||||||
className="hover:text-orange-600 dark:hover:text-orange-400"
|
className="hover:text-orange-600 dark:hover:text-orange-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{filterAssetName && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-cyan-100 dark:bg-cyan-900 text-cyan-800 dark:text-cyan-200 rounded-full text-[10px]">
|
||||||
Asset Name: "{filterAssetName}"
|
Asset Name: "{filterAssetName}"
|
||||||
<button
|
<button
|
||||||
// onClick={() => setFilterAssetName('')}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFilterAssetName('');
|
setFilterAssetName('');
|
||||||
setTempAssetName('');
|
setTempAssetName('');
|
||||||
}}
|
}}
|
||||||
className="hover:text-cyan-600 dark:hover:text-cyan-400"
|
className="hover:text-cyan-600 dark:hover:text-cyan-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{filterSerialNumber && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-lime-100 dark:bg-lime-900 text-lime-800 dark:text-lime-200 rounded-full text-[10px]">
|
||||||
Serial: "{filterSerialNumber}"
|
Serial: "{filterSerialNumber}"
|
||||||
<button
|
<button
|
||||||
// onClick={() => setFilterSerialNumber('')}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFilterSerialNumber('');
|
setFilterSerialNumber('');
|
||||||
setTempSerialNumber('');
|
setTempSerialNumber('');
|
||||||
}}
|
}}
|
||||||
className="hover:text-lime-600 dark:hover:text-lime-400"
|
className="hover:text-lime-600 dark:hover:text-lime-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{searchTerm && (
|
{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">
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded-full text-[10px]">
|
||||||
Search: "{searchTerm}"
|
Search: "{searchTerm}"
|
||||||
<button
|
<button
|
||||||
onClick={() => setSearchTerm('')}
|
onClick={() => setSearchTerm('')}
|
||||||
className="hover:text-purple-600 dark:hover:text-purple-400"
|
className="hover:text-purple-600 dark:hover:text-purple-400"
|
||||||
>
|
>
|
||||||
<FaTimes className="text-xs" />
|
<FaTimes className="text-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -691,7 +697,7 @@ const AssetList: React.FC = () => {
|
|||||||
Asset Name
|
Asset Name
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
Serial Number
|
Serial No
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
Company
|
Company
|
||||||
@ -702,6 +708,9 @@ const AssetList: React.FC = () => {
|
|||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
|
Updated on
|
||||||
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
Actions
|
Actions
|
||||||
</th>
|
</th>
|
||||||
@ -710,7 +719,7 @@ const AssetList: React.FC = () => {
|
|||||||
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
{assets.length === 0 ? (
|
{assets.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="px-6 py-12 text-center text-gray-500 dark:text-gray-400">
|
<td colSpan={7} className="px-6 py-12 text-center text-gray-500 dark:text-gray-400">
|
||||||
<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>
|
||||||
@ -772,6 +781,9 @@ const AssetList: React.FC = () => {
|
|||||||
{asset.custom_device_status || 'Unknown'}
|
{asset.custom_device_status || 'Unknown'}
|
||||||
</span> */}
|
</span> */}
|
||||||
</td>
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
{formatDate(asset.modified)}
|
||||||
|
</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()}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user