250 lines
11 KiB
TypeScript
250 lines
11 KiB
TypeScript
import { Card } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import {
|
|
X,
|
|
ShoppingBag,
|
|
Activity,
|
|
Tag,
|
|
Wrench,
|
|
AlertTriangle,
|
|
ClipboardList,
|
|
DollarSign,
|
|
Shield,
|
|
FileText,
|
|
FileCheck,
|
|
Download,
|
|
Plus,
|
|
} from 'lucide-react';
|
|
import type { GearItem } from '@/types';
|
|
import { getWarrantyStatus } from './gearUtils';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
export interface GearDetailModalProps {
|
|
item: GearItem;
|
|
onClose: () => void;
|
|
onSellOnMarketplace?: (item: GearItem) => void;
|
|
onLogMaintenance?: (item: GearItem) => void;
|
|
onContactSupport?: (item: GearItem) => void;
|
|
onUploadDocument?: (item: GearItem) => void;
|
|
className?: string;
|
|
}
|
|
|
|
export function GearDetailModal({
|
|
item,
|
|
onClose,
|
|
onSellOnMarketplace,
|
|
onLogMaintenance,
|
|
onContactSupport,
|
|
onUploadDocument,
|
|
className,
|
|
}: GearDetailModalProps) {
|
|
const warranty = getWarrantyStatus(item.warrantyExpire);
|
|
|
|
return (
|
|
<div className={cn('fixed inset-0 z-50 flex items-center justify-center p-4', className)}>
|
|
<div
|
|
className="absolute inset-0 bg-background/90 backdrop-blur-sm"
|
|
onClick={onClose}
|
|
aria-hidden
|
|
/>
|
|
<div className="relative w-full max-w-5xl bg-card border border-border rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-screen">
|
|
{/* Modal Header */}
|
|
<div className="p-8 border-b border-border bg-muted/30 flex justify-between items-start shrink-0">
|
|
<div className="flex gap-8">
|
|
<div className="w-32 h-32 bg-muted rounded-lg overflow-hidden border border-border shrink-0">
|
|
{item.image ? (
|
|
<img src={item.image} className="w-full h-full object-cover" alt="" />
|
|
) : (
|
|
<div className="w-full h-full flex items-center justify-center text-muted-foreground text-sm">No image</div>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<div className="flex items-center gap-4 mb-1">
|
|
<Badge label={item.category} variant="cyan" />
|
|
<span className="text-muted-foreground text-xs font-mono uppercase">{item.serialNumber ?? '—'}</span>
|
|
</div>
|
|
<h2 className="text-3xl font-display font-bold text-foreground">{item.name}</h2>
|
|
<h3 className="text-xl text-primary font-medium mb-4">
|
|
{item.brand} {item.model}
|
|
</h3>
|
|
<div className="flex gap-4">
|
|
<Button
|
|
variant="glass"
|
|
size="sm"
|
|
icon={<ShoppingBag className="w-4 h-4" />}
|
|
onClick={() => onSellOnMarketplace?.(item)}
|
|
>
|
|
SELL ON MARKETPLACE
|
|
</Button>
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
icon={<Activity className="w-4 h-4" />}
|
|
onClick={() => onLogMaintenance?.(item)}
|
|
>
|
|
LOG MAINTENANCE
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="text-muted-foreground hover:text-foreground p-1"
|
|
aria-label="Close"
|
|
>
|
|
<X className="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Modal Body */}
|
|
<div className="flex-1 overflow-y-auto p-8 min-h-0">
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
<div className="lg:col-span-2 space-y-8">
|
|
<Card variant="default">
|
|
<h4 className="flex items-center gap-2 font-bold text-foreground mb-4 border-b border-border pb-2">
|
|
<Tag className="w-4 h-4 text-muted-foreground" /> Specifications
|
|
</h4>
|
|
<div className="grid grid-cols-2 gap-y-4 gap-x-8">
|
|
{item.specs && Object.keys(item.specs).length > 0 ? (
|
|
Object.entries(item.specs).map(([key, val]) => (
|
|
<div key={key} className="flex justify-between border-b border-border pb-1">
|
|
<span className="text-muted-foreground text-sm">{key}</span>
|
|
<span className="text-foreground text-sm font-medium">{val}</span>
|
|
</div>
|
|
))
|
|
) : (
|
|
<p className="text-muted-foreground italic col-span-2">No specs available.</p>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
|
|
<Card variant="default">
|
|
<h4 className="flex items-center gap-2 font-bold text-foreground mb-4 border-b border-border pb-2">
|
|
<Wrench className="w-4 h-4 text-amber-500" /> Maintenance & Support (SAV)
|
|
</h4>
|
|
{item.status === 'Maintenance' && item.notes && (
|
|
<div className="bg-amber-500/10 border border-amber-500/30 p-4 rounded mb-4 flex items-center gap-4">
|
|
<AlertTriangle className="w-5 h-5 text-amber-500 shrink-0" />
|
|
<div>
|
|
<div className="text-sm font-bold text-amber-500">Currently in Repair</div>
|
|
<div className="text-xs text-muted-foreground">{item.notes}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="space-y-4">
|
|
{item.maintenanceHistory?.map((log) => (
|
|
<div
|
|
key={log.id}
|
|
className="flex items-start gap-4 p-4 bg-muted/30 rounded hover:bg-muted/50 transition-colors"
|
|
>
|
|
<div className="bg-muted p-2 rounded text-muted-foreground shrink-0">
|
|
<ClipboardList className="w-4 h-4" />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex justify-between gap-2">
|
|
<span className="font-bold text-foreground text-sm">{log.type}</span>
|
|
<span className="text-xs text-muted-foreground shrink-0">{log.date}</span>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-1">{log.notes}</p>
|
|
{log.cost != null && (
|
|
<div className="text-xs text-destructive mt-1 font-mono">Cost: ${log.cost}</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
{(!item.maintenanceHistory || item.maintenanceHistory.length === 0) && (
|
|
<p className="text-sm text-muted-foreground italic">No maintenance history recorded.</p>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="space-y-8">
|
|
<Card variant="glass">
|
|
<h4 className="flex items-center gap-2 font-bold text-foreground mb-4 border-b border-border pb-2">
|
|
<DollarSign className="w-4 h-4 text-primary" /> Purchase Info
|
|
</h4>
|
|
<div className="space-y-4 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Price Paid</span>
|
|
<span className="text-foreground font-mono">
|
|
{item.currency} {item.purchasePrice}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Date</span>
|
|
<span className="text-foreground">{item.purchaseDate}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Vendor</span>
|
|
<span className="text-foreground">{item.vendor ?? '—'}</span>
|
|
</div>
|
|
{item.orderNumber && (
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Order #</span>
|
|
<span className="text-muted-foreground font-mono">{item.orderNumber}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
|
|
<Card variant="default">
|
|
<h4 className="flex items-center gap-2 font-bold text-foreground mb-4 border-b border-border pb-2">
|
|
<Shield className="w-4 h-4 text-green-600" /> Warranty
|
|
</h4>
|
|
<div className={cn('p-4 rounded text-center mb-4', warranty.bg)}>
|
|
<div className={cn('text-lg font-bold', warranty.color)}>{warranty.label}</div>
|
|
<div className="text-xs text-muted-foreground">Expires: {item.warrantyExpire ?? 'N/A'}</div>
|
|
</div>
|
|
{item.supportContact && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="w-full text-xs"
|
|
onClick={() => onContactSupport?.(item)}
|
|
>
|
|
Contact Support
|
|
</Button>
|
|
)}
|
|
</Card>
|
|
|
|
<Card variant="default">
|
|
<h4 className="flex items-center gap-2 font-bold text-foreground mb-4 border-b border-border pb-2">
|
|
<FileText className="w-4 h-4 text-muted-foreground" /> Documentation
|
|
</h4>
|
|
<div className="space-y-2">
|
|
{item.documents?.map((doc, i) => (
|
|
<div
|
|
key={i}
|
|
className="flex items-center justify-between p-2 hover:bg-muted/50 rounded cursor-pointer group"
|
|
>
|
|
<div className="flex items-center gap-4 overflow-hidden min-w-0">
|
|
{doc.type === 'manual' ? (
|
|
<FileText className="w-4 h-4 text-muted-foreground shrink-0" />
|
|
) : (
|
|
<FileCheck className="w-4 h-4 text-primary shrink-0" />
|
|
)}
|
|
<span className="text-sm text-foreground truncate">{doc.name}</span>
|
|
</div>
|
|
<Download className="w-4 h-4 text-muted-foreground group-hover:text-foreground shrink-0" />
|
|
</div>
|
|
))}
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="w-full mt-2 text-xs border border-dashed border-border"
|
|
onClick={() => onUploadDocument?.(item)}
|
|
>
|
|
<Plus className="w-3 h-3 mr-1" /> Upload Document
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|