Files
RFID/components/InventoryPanel.tsx
2026-01-23 11:41:59 +09:00

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>
);
};