veza/apps/web/src/components/views/GearView.tsx

560 lines
22 KiB
TypeScript
Raw Normal View History

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 '../../context/ToastContext';
// --- 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-6 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-3">
<Button
variant="ghost"
icon={<Download className="w-4 h-4" />}
onClick={() => addToast('Exporting Inventory CSV...')}
>
EXPORT CSV
</Button>
<Button variant="gaming" 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-3 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-6">
{filteredInventory.map((item) => {
const warranty = getWarrantyStatus(item.warrantyExpire);
return (
<Card
key={item.id}
variant="gaming"
className="group cursor-pointer hover:border-kodo-cyan/50 transition-all hover:-translate-y-1"
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-kodo-cyan hover:border-kodo-cyan 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-6 border-b border-kodo-steel bg-kodo-ink flex justify-between items-start">
<div className="flex gap-6">
<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-3 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-3">
<Button
variant="gaming"
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-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left: Specs & Info */}
<div className="lg:col-span-2 space-y-6">
{/* 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-cyan" /> 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-3 rounded mb-4 flex items-center gap-3">
<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-3">
{selectedItem.maintenanceHistory?.map((log) => (
<div
key={log.id}
className="flex items-start gap-4 p-3 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-6">
{/* 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-3 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-cyan">
{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-3 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-3 overflow-hidden">
{doc.type === 'manual' ? (
<FileText className="w-4 h-4 text-kodo-cyan" />
) : (
<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>
);
};