559 lines
22 KiB
TypeScript
559 lines
22 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Card } from '../ui/card';
|
|
import { Button } from '../ui/button';
|
|
import { Badge } from '../ui/badge';
|
|
import { SearchInput } from '../ui/input';
|
|
import {
|
|
Plus,
|
|
Wrench,
|
|
FileText,
|
|
DollarSign,
|
|
Shield,
|
|
Tag,
|
|
Download,
|
|
Activity,
|
|
AlertTriangle,
|
|
ShoppingBag,
|
|
X,
|
|
FileCheck,
|
|
ClipboardList,
|
|
} from 'lucide-react';
|
|
import { GearItem } from '../../types';
|
|
import { useToast } from '../../components/feedback/ToastProvider';
|
|
|
|
// --- MOCK DATA ---
|
|
|
|
const INVENTORY: GearItem[] = [
|
|
{
|
|
id: '1',
|
|
name: 'Prophet-6',
|
|
category: 'Synth',
|
|
brand: 'Sequential',
|
|
model: 'Prophet-6 Desktop',
|
|
serialNumber: 'SQ-P6-99281',
|
|
purchaseDate: '2023-01-15',
|
|
purchasePrice: 2499,
|
|
currency: 'USD',
|
|
status: 'Active',
|
|
condition: 'Mint',
|
|
vendor: 'Sweetwater',
|
|
orderNumber: 'SW-8821002',
|
|
warrantyExpire: '2025-01-15',
|
|
warrantyType: 'Manufacturer',
|
|
supportContact: 'support@sequential.com',
|
|
image: 'https://picsum.photos/id/100/400/400',
|
|
specs: {
|
|
Polyphony: '6 Voices',
|
|
Oscillators: '2 Discrete VCOs',
|
|
Filter: 'Low-pass + High-pass',
|
|
Sequencer: '64-step',
|
|
},
|
|
documents: [
|
|
{ name: 'User Manual', type: 'manual', url: '#', size: '4.2 MB' },
|
|
{ name: 'Purchase Receipt', type: 'receipt', url: '#', size: '150 KB' },
|
|
],
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'Apollo Twin X',
|
|
category: 'Interface',
|
|
brand: 'Universal Audio',
|
|
model: 'Twin X Duo',
|
|
serialNumber: 'UA-TWX-2210',
|
|
purchaseDate: '2022-11-20',
|
|
purchasePrice: 999,
|
|
currency: 'USD',
|
|
status: 'Active',
|
|
condition: 'Good',
|
|
vendor: 'Thomann',
|
|
warrantyExpire: '2023-11-20',
|
|
warrantyType: 'Manufacturer',
|
|
image: 'https://picsum.photos/id/101/400/400',
|
|
specs: {
|
|
Inputs: '2 Mic/Line',
|
|
Outputs: '4 Line',
|
|
Connection: 'Thunderbolt 3',
|
|
DSP: 'Duo Core',
|
|
},
|
|
documents: [
|
|
{ name: 'Firmware v1.2', type: 'firmware', url: '#', size: '120 MB' },
|
|
],
|
|
maintenanceHistory: [
|
|
{
|
|
id: 'm1',
|
|
date: '2023-05-10',
|
|
type: 'Cleaning',
|
|
notes: 'Potentiometer de-oxidizing',
|
|
cost: 0,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: '3',
|
|
name: 'SM7B',
|
|
category: 'Microphone',
|
|
brand: 'Shure',
|
|
model: 'SM7B Dynamic',
|
|
serialNumber: 'SH-SM7-004',
|
|
purchaseDate: '2021-05-10',
|
|
purchasePrice: 399,
|
|
currency: 'USD',
|
|
status: 'Maintenance',
|
|
condition: 'Fair',
|
|
vendor: 'Guitar Center',
|
|
warrantyExpire: '2023-05-10',
|
|
warrantyType: 'None',
|
|
image: 'https://picsum.photos/id/102/400/400',
|
|
notes: 'XLR connector feels loose. Sent for repair.',
|
|
maintenanceHistory: [
|
|
{
|
|
id: 'm2',
|
|
date: '2024-02-15',
|
|
type: 'Repair',
|
|
notes: 'XLR Jack Replacement',
|
|
cost: 45,
|
|
provider: 'Local Shop',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
export const GearView: React.FC = () => {
|
|
const { addToast } = useToast();
|
|
const [selectedItem, setSelectedItem] = useState<GearItem | null>(null);
|
|
const [filter, setFilter] = useState('All');
|
|
const [search, setSearch] = useState('');
|
|
|
|
// Filtering Logic
|
|
const filteredInventory = INVENTORY.filter((item) => {
|
|
const matchesFilter =
|
|
filter === 'All' || item.category === filter || item.status === filter;
|
|
const matchesSearch =
|
|
item.name.toLowerCase().includes(search.toLowerCase()) ||
|
|
item.brand.toLowerCase().includes(search.toLowerCase());
|
|
return matchesFilter && matchesSearch;
|
|
});
|
|
|
|
const getWarrantyStatus = (dateStr?: string) => {
|
|
if (!dateStr)
|
|
return { label: 'Unknown', color: 'text-kodo-content-dim', bg: 'bg-kodo-steel/10' };
|
|
const expiry = new Date(dateStr);
|
|
const now = new Date();
|
|
const daysLeft = Math.ceil(
|
|
(expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
|
|
);
|
|
|
|
if (daysLeft < 0)
|
|
return { label: 'Expired', color: 'text-kodo-red', bg: 'bg-kodo-red/10' };
|
|
if (daysLeft < 90)
|
|
return {
|
|
label: `Expiring (${daysLeft}d)`,
|
|
color: 'text-kodo-gold',
|
|
bg: 'bg-kodo-gold/10',
|
|
};
|
|
return { label: 'Active', color: 'text-kodo-lime', bg: 'bg-kodo-lime/10' };
|
|
};
|
|
|
|
const handleListOnMarketplace = (item: GearItem) => {
|
|
addToast(`Draft listing created for ${item.brand} ${item.name}`, 'success');
|
|
setSelectedItem(null);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-8 animate-fadeIn relative">
|
|
{/* Header */}
|
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
|
|
<div>
|
|
<h2 className="text-3xl font-display font-bold text-white mb-2">
|
|
GEAR LOCKER
|
|
</h2>
|
|
<p className="text-kodo-content-dim font-mono text-sm">
|
|
Manage hardware assets, documentation, and warranties.
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<Button
|
|
variant="ghost"
|
|
icon={<Download className="w-4 h-4" />}
|
|
onClick={() => addToast('Exporting Inventory CSV...')}
|
|
>
|
|
EXPORT CSV
|
|
</Button>
|
|
<Button variant="glass" icon={<Plus className="w-4 h-4" />}>
|
|
REGISTER GEAR
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Filters */}
|
|
<div className="flex flex-col md:flex-row gap-4 items-center bg-kodo-ink/50 p-4 rounded-xl border border-kodo-steel/50">
|
|
<div className="w-full md:w-64">
|
|
<SearchInput
|
|
placeholder="Search brand, model, serial..."
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="flex gap-2 overflow-x-auto w-full md:w-auto pb-2 md:pb-0">
|
|
{[
|
|
'All',
|
|
'Synth',
|
|
'Interface',
|
|
'Microphone',
|
|
'Active',
|
|
'Maintenance',
|
|
'Sold',
|
|
].map((f) => (
|
|
<button
|
|
key={f}
|
|
onClick={() => setFilter(f)}
|
|
className={`px-4 py-1.5 rounded-lg text-xs font-bold uppercase tracking-wider transition-colors border ${filter === f ? 'bg-kodo-cyan text-black border-kodo-cyan' : 'bg-kodo-slate text-kodo-content-dim border-transparent hover:border-kodo-steel'}`}
|
|
>
|
|
{f}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
{filteredInventory.map((item) => {
|
|
const warranty = getWarrantyStatus(item.warrantyExpire);
|
|
|
|
return (
|
|
<Card
|
|
key={item.id}
|
|
variant="glass"
|
|
className="group cursor-pointer hover:border-kodo-steel/50 transition-colors"
|
|
onClick={() => setSelectedItem(item)}
|
|
>
|
|
<div className="flex items-start justify-between mb-4">
|
|
<Badge label={item.category} variant="terminal" />
|
|
<div
|
|
className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase ${item.status === 'Active' ? 'bg-kodo-lime/10 text-kodo-lime' : 'bg-kodo-orange/10 text-kodo-orange'}`}
|
|
>
|
|
{item.status}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-4 mb-4">
|
|
<div className="w-24 h-24 bg-kodo-graphite rounded-lg border border-kodo-steel overflow-hidden flex-shrink-0">
|
|
<img
|
|
src={item.image}
|
|
className="w-full h-full object-cover opacity-80 group-hover:opacity-100 transition-opacity"
|
|
/>
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<h3 className="text-lg font-bold text-white truncate">
|
|
{item.name}
|
|
</h3>
|
|
<p className="text-kodo-gold text-sm font-mono mb-1">
|
|
{item.brand}
|
|
</p>
|
|
<p className="text-xs text-kodo-content-dim truncate">
|
|
S/N: {item.serialNumber}
|
|
</p>
|
|
<div className="mt-2 flex items-center gap-2">
|
|
<span
|
|
className={`w-2 h-2 rounded-full ${warranty.color.replace('text', 'bg')}`}
|
|
></span>
|
|
<span className={`text-[10px] font-bold ${warranty.color}`}>
|
|
{warranty.label} Warranty
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-2 mt-4 pt-4 border-t border-kodo-steel">
|
|
<div className="text-center p-2 bg-white/5 rounded">
|
|
<div className="text-[10px] text-kodo-content-dim uppercase">
|
|
Purchased
|
|
</div>
|
|
<div className="text-sm font-bold text-white">
|
|
{item.purchaseDate}
|
|
</div>
|
|
</div>
|
|
<div className="text-center p-2 bg-white/5 rounded">
|
|
<div className="text-[10px] text-kodo-content-dim uppercase">
|
|
Condition
|
|
</div>
|
|
<div className="text-sm font-bold text-white">
|
|
{item.condition}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
})}
|
|
|
|
{/* Add New Placeholder */}
|
|
<div
|
|
className="border-2 border-dashed border-kodo-steel rounded-xl flex flex-col items-center justify-center p-8 hover:bg-kodo-slate/20 transition-colors cursor-pointer text-kodo-content-dim hover:text-white hover:border-kodo-steel/50 min-h-[280px]"
|
|
onClick={() => addToast('Opens Registration Form')}
|
|
>
|
|
<Plus className="w-12 h-12 mb-4 opacity-50" />
|
|
<span className="font-mono font-bold">REGISTER NEW HARDWARE</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* --- GEAR DETAIL MODAL --- */}
|
|
{selectedItem && (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
|
<div
|
|
className="absolute inset-0 bg-kodo-void/90 backdrop-blur-sm"
|
|
onClick={() => setSelectedItem(null)}
|
|
></div>
|
|
<div className="relative w-full max-w-5xl bg-kodo-graphite border border-kodo-steel rounded-2xl shadow-2xl animate-scaleIn overflow-hidden flex flex-col max-h-[90vh]">
|
|
{/* Modal Header */}
|
|
<div className="p-8 border-b border-kodo-steel bg-kodo-ink flex justify-between items-start">
|
|
<div className="flex gap-8">
|
|
<div className="w-32 h-32 bg-kodo-graphite rounded-lg overflow-hidden border border-kodo-steel shadow-lg">
|
|
<img
|
|
src={selectedItem.image}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<div className="flex items-center gap-4 mb-1">
|
|
<Badge label={selectedItem.category} variant="cyan" />
|
|
<span className="text-kodo-content-dim text-xs font-mono uppercase">
|
|
{selectedItem.serialNumber}
|
|
</span>
|
|
</div>
|
|
<h2 className="text-3xl font-display font-bold text-white">
|
|
{selectedItem.name}
|
|
</h2>
|
|
<h3 className="text-xl text-kodo-gold font-medium mb-4">
|
|
{selectedItem.brand} {selectedItem.model}
|
|
</h3>
|
|
|
|
<div className="flex gap-4">
|
|
<Button
|
|
variant="glass"
|
|
size="sm"
|
|
icon={<ShoppingBag className="w-4 h-4" />}
|
|
onClick={() => handleListOnMarketplace(selectedItem)}
|
|
>
|
|
SELL ON MARKETPLACE
|
|
</Button>
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
icon={<Activity className="w-4 h-4" />}
|
|
onClick={() => addToast('Maintenance Log Updated')}
|
|
>
|
|
LOG MAINTENANCE
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => setSelectedItem(null)}
|
|
className="text-kodo-content-dim hover:text-white"
|
|
>
|
|
<X className="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Modal Body */}
|
|
<div className="flex-1 overflow-y-auto p-8">
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
{/* Left: Specs & Info */}
|
|
<div className="lg:col-span-2 space-y-8">
|
|
{/* Specifications */}
|
|
<Card variant="default">
|
|
<h4 className="flex items-center gap-2 font-bold text-white mb-4 border-b border-kodo-steel pb-2">
|
|
<Tag className="w-4 h-4 text-kodo-steel" /> Specifications
|
|
</h4>
|
|
<div className="grid grid-cols-2 gap-y-4 gap-x-8">
|
|
{selectedItem.specs ? (
|
|
Object.entries(selectedItem.specs).map(([key, val]) => (
|
|
<div
|
|
key={key}
|
|
className="flex justify-between border-b border-kodo-steel pb-1"
|
|
>
|
|
<span className="text-kodo-content-dim text-sm">{key}</span>
|
|
<span className="text-white text-sm font-medium">
|
|
{val}
|
|
</span>
|
|
</div>
|
|
))
|
|
) : (
|
|
<p className="text-kodo-content-dim italic">
|
|
No specs available.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Maintenance Log */}
|
|
<Card variant="default">
|
|
<h4 className="flex items-center gap-2 font-bold text-white mb-4 border-b border-kodo-steel pb-2">
|
|
<Wrench className="w-4 h-4 text-kodo-orange" />{' '}
|
|
Maintenance & Support (SAV)
|
|
</h4>
|
|
{selectedItem.status === 'Maintenance' && (
|
|
<div className="bg-kodo-orange/10 border border-kodo-orange/30 p-4 rounded mb-4 flex items-center gap-4">
|
|
<AlertTriangle className="w-5 h-5 text-kodo-orange" />
|
|
<div>
|
|
<div className="text-sm font-bold text-kodo-orange">
|
|
Currently in Repair
|
|
</div>
|
|
<div className="text-xs text-kodo-content-dim">
|
|
{selectedItem.notes}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-4">
|
|
{selectedItem.maintenanceHistory?.map((log) => (
|
|
<div
|
|
key={log.id}
|
|
className="flex items-start gap-4 p-4 bg-white/5 rounded hover:bg-white/10 transition-colors"
|
|
>
|
|
<div className="bg-kodo-graphite p-2 rounded text-kodo-content-dim">
|
|
<ClipboardList className="w-4 h-4" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex justify-between">
|
|
<span className="font-bold text-white text-sm">
|
|
{log.type}
|
|
</span>
|
|
<span className="text-xs text-kodo-content-dim">
|
|
{log.date}
|
|
</span>
|
|
</div>
|
|
<p className="text-xs text-kodo-content-dim mt-1">
|
|
{log.notes}
|
|
</p>
|
|
{log.cost && (
|
|
<div className="text-xs text-kodo-red mt-1 font-mono">
|
|
Cost: ${log.cost}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
{(!selectedItem.maintenanceHistory ||
|
|
selectedItem.maintenanceHistory.length === 0) && (
|
|
<p className="text-sm text-kodo-content-dim italic">
|
|
No maintenance history recorded.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Right: Purchase & Docs */}
|
|
<div className="space-y-8">
|
|
{/* Purchase Info */}
|
|
<Card variant="glass">
|
|
<h4 className="flex items-center gap-2 font-bold text-white mb-4 border-b border-kodo-steel pb-2">
|
|
<DollarSign className="w-4 h-4 text-kodo-gold" /> Purchase
|
|
Info
|
|
</h4>
|
|
<div className="space-y-4 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-kodo-content-dim">Price Paid</span>
|
|
<span className="text-white font-mono">
|
|
{selectedItem.currency} {selectedItem.purchasePrice}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-kodo-content-dim">Date</span>
|
|
<span className="text-white">
|
|
{selectedItem.purchaseDate}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-kodo-content-dim">Vendor</span>
|
|
<span className="text-white">
|
|
{selectedItem.vendor}
|
|
</span>
|
|
</div>
|
|
{selectedItem.orderNumber && (
|
|
<div className="flex justify-between">
|
|
<span className="text-kodo-content-dim">Order #</span>
|
|
<span className="text-kodo-steel">
|
|
{selectedItem.orderNumber}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Warranty */}
|
|
<Card variant="default">
|
|
<h4 className="flex items-center gap-2 font-bold text-white mb-4 border-b border-kodo-steel pb-2">
|
|
<Shield className="w-4 h-4 text-kodo-lime" /> Warranty
|
|
</h4>
|
|
<div
|
|
className={`p-4 rounded text-center mb-4 ${getWarrantyStatus(selectedItem.warrantyExpire).bg}`}
|
|
>
|
|
<div
|
|
className={`text-lg font-bold ${getWarrantyStatus(selectedItem.warrantyExpire).color}`}
|
|
>
|
|
{getWarrantyStatus(selectedItem.warrantyExpire).label}
|
|
</div>
|
|
<div className="text-xs text-kodo-content-dim">
|
|
Expires: {selectedItem.warrantyExpire || 'N/A'}
|
|
</div>
|
|
</div>
|
|
{selectedItem.supportContact && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="w-full text-xs"
|
|
onClick={() =>
|
|
addToast(`Contacting ${selectedItem.supportContact}`)
|
|
}
|
|
>
|
|
Contact Support
|
|
</Button>
|
|
)}
|
|
</Card>
|
|
|
|
{/* Documents */}
|
|
<Card variant="default">
|
|
<h4 className="flex items-center gap-2 font-bold text-white mb-4 border-b border-kodo-steel pb-2">
|
|
<FileText className="w-4 h-4 text-kodo-content-dim" />{' '}
|
|
Documentation
|
|
</h4>
|
|
<div className="space-y-2">
|
|
{selectedItem.documents?.map((doc, i) => (
|
|
<div
|
|
key={i}
|
|
className="flex items-center justify-between p-2 hover:bg-white/5 rounded cursor-pointer group"
|
|
>
|
|
<div className="flex items-center gap-4 overflow-hidden">
|
|
{doc.type === 'manual' ? (
|
|
<FileText className="w-4 h-4 text-kodo-steel" />
|
|
) : (
|
|
<FileCheck className="w-4 h-4 text-kodo-gold" />
|
|
)}
|
|
<span className="text-sm text-kodo-text-main truncate">
|
|
{doc.name}
|
|
</span>
|
|
</div>
|
|
<Download className="w-4 h-4 text-kodo-content-dim group-hover:text-white" />
|
|
</div>
|
|
))}
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="w-full mt-2 text-xs border border-dashed border-kodo-steel"
|
|
>
|
|
<Plus className="w-3 h-3 mr-1" /> Upload Document
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|