180 lines
7.4 KiB
TypeScript
180 lines
7.4 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|
import { TagData } from '../types';
|
|
import { Play, Pause, Trash2, Download, Fingerprint, ScanBarcode, CircuitBoard } from 'lucide-react';
|
|
|
|
interface Props {
|
|
tags: TagData[];
|
|
isScanning: boolean;
|
|
onToggleScan: () => void;
|
|
onClear: () => void;
|
|
onFetchTids: () => void;
|
|
}
|
|
|
|
export const InventoryPanel: React.FC<Props> = ({
|
|
tags,
|
|
isScanning,
|
|
onToggleScan,
|
|
onClear,
|
|
onFetchTids
|
|
}) => {
|
|
const [viewMode, setViewMode] = useState<'epc' | 'tid'>('epc');
|
|
|
|
const exportCsv = () => {
|
|
const csvContent = "data:text/csv;charset=utf-8,"
|
|
+ "EPC,TID,Count,Timestamp\n"
|
|
+ tags.map(t => `${t.epc},${t.tid || ''},${t.count},${new Date(t.timestamp).toISOString()}`).join("\n");
|
|
const encodedUri = encodeURI(csvContent);
|
|
const link = document.createElement("a");
|
|
link.setAttribute("href", encodedUri);
|
|
link.setAttribute("download", "rfid_inventory.csv");
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full gap-4">
|
|
|
|
{/* Main Inventory Table Card */}
|
|
<div className="flex flex-col flex-1 bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
{/* Header / Controls */}
|
|
<div className="p-4 border-b border-slate-100 flex flex-col xl:flex-row items-start xl:items-center justify-between bg-slate-50/50 gap-4">
|
|
<div className="flex items-center gap-4">
|
|
<div className="flex flex-col">
|
|
<h1 className="text-xl font-bold text-slate-800">EPC C1G2 Inventory</h1>
|
|
<span className="text-xs text-slate-500">{tags.length} Unique Tags Found</span>
|
|
</div>
|
|
|
|
{/* View Mode Switcher */}
|
|
<div className="bg-slate-200 p-1 rounded-lg flex items-center gap-1 ml-4">
|
|
<button
|
|
onClick={() => setViewMode('epc')}
|
|
className={`px-3 py-1.5 rounded-md text-xs font-bold flex items-center gap-2 transition-all ${viewMode === 'epc' ? 'bg-white text-blue-600 shadow-sm' : 'text-slate-500 hover:text-slate-700'}`}
|
|
>
|
|
<ScanBarcode className="w-3.5 h-3.5" /> EPC
|
|
</button>
|
|
<button
|
|
onClick={() => setViewMode('tid')}
|
|
className={`px-3 py-1.5 rounded-md text-xs font-bold flex items-center gap-2 transition-all ${viewMode === 'tid' ? 'bg-white text-indigo-600 shadow-sm' : 'text-slate-500 hover:text-slate-700'}`}
|
|
>
|
|
<Fingerprint className="w-3.5 h-3.5" /> TID
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
{viewMode === 'tid' && (
|
|
<button
|
|
onClick={onFetchTids}
|
|
disabled={isScanning || tags.length === 0}
|
|
className="flex items-center gap-2 px-3 py-2 bg-indigo-50 text-indigo-700 border border-indigo-200 rounded-lg text-sm font-medium hover:bg-indigo-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors mr-2"
|
|
title="Read TID memory for all listed tags"
|
|
>
|
|
<CircuitBoard className="w-4 h-4" />
|
|
Read TIDs
|
|
</button>
|
|
)}
|
|
|
|
<button
|
|
onClick={onToggleScan}
|
|
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all ${
|
|
isScanning
|
|
? 'bg-amber-100 text-amber-700 hover:bg-amber-200 border border-amber-200'
|
|
: 'bg-emerald-600 text-white hover:bg-emerald-700 shadow-md shadow-emerald-500/20'
|
|
}`}
|
|
>
|
|
{isScanning ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
|
|
{isScanning ? 'Stop Scan' : 'Start Scan'}
|
|
</button>
|
|
|
|
<div className="h-6 w-px bg-slate-300 mx-1"></div>
|
|
|
|
<button
|
|
onClick={onClear}
|
|
className="p-2 text-slate-600 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
|
title="Clear List"
|
|
>
|
|
<Trash2 className="w-5 h-5" />
|
|
</button>
|
|
|
|
<button
|
|
onClick={exportCsv}
|
|
disabled={tags.length === 0}
|
|
className="p-2 text-slate-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors disabled:opacity-50"
|
|
title="Export CSV"
|
|
>
|
|
<Download className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Table Area */}
|
|
<div className="flex-1 overflow-auto">
|
|
<table className="w-full text-sm text-left text-slate-600">
|
|
<thead className="text-xs text-slate-700 uppercase bg-slate-50 sticky top-0 z-10 shadow-sm">
|
|
<tr>
|
|
<th scope="col" className="px-6 py-3 w-16">#</th>
|
|
<th scope="col" className="px-6 py-3">
|
|
{viewMode === 'epc' ? 'EPC ID' : 'TID (Tag Identifier)'}
|
|
</th>
|
|
{viewMode === 'tid' && <th scope="col" className="px-6 py-3 text-slate-400">EPC Reference</th>}
|
|
<th scope="col" className="px-6 py-3 w-32">Length (Bits)</th>
|
|
<th scope="col" className="px-6 py-3 w-32 text-right">Count</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{tags.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={viewMode === 'tid' ? 5 : 4} className="px-6 py-12 text-center text-slate-400 italic">
|
|
No tags scanned yet. Press "Start Scan" to begin.
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
tags.map((tag, index) => (
|
|
<tr key={tag.epc} className="bg-white border-b border-slate-100 hover:bg-slate-50 transition-colors">
|
|
<td className="px-6 py-4 font-mono text-slate-400">{index + 1}</td>
|
|
|
|
{/* Main ID Column */}
|
|
<td className="px-6 py-4 font-mono font-medium text-slate-900">
|
|
{viewMode === 'epc' ? (
|
|
tag.epc
|
|
) : (
|
|
tag.tid ? (
|
|
<span className="text-indigo-700">{tag.tid}</span>
|
|
) : (
|
|
<span className="text-slate-300 italic text-xs">Not Read (Click "Read TIDs")</span>
|
|
)
|
|
)}
|
|
</td>
|
|
|
|
{/* EPC Reference Column (Only in TID view) */}
|
|
{viewMode === 'tid' && (
|
|
<td className="px-6 py-4 font-mono text-xs text-slate-400">
|
|
{tag.epc}
|
|
</td>
|
|
)}
|
|
|
|
{/* Length Column */}
|
|
<td className="px-6 py-4">
|
|
{viewMode === 'epc'
|
|
? tag.epc.replace(/\s/g, '').length * 4
|
|
: (tag.tid ? tag.tid.replace(/\s/g, '').length * 4 : '-')
|
|
}
|
|
</td>
|
|
|
|
<td className="px-6 py-4 text-right">
|
|
<span className="bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded-full">
|
|
{tag.count}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|