417 lines
15 KiB
TypeScript
417 lines
15 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import SearchInput from '@/components/SearchInput';
|
|
|
|
// Sample build data
|
|
const sampleMyBuilds = [
|
|
{
|
|
id: '1',
|
|
name: 'Budget AR-15 Build',
|
|
description: 'A cost-effective AR-15 build using quality budget components',
|
|
status: 'completed' as const,
|
|
totalCost: 847.50,
|
|
completedDate: '2024-01-15',
|
|
components: {
|
|
total: 18,
|
|
completed: 18,
|
|
categories: {
|
|
'Upper': 8,
|
|
'Lower': 7,
|
|
'Accessory': 3
|
|
}
|
|
},
|
|
tags: ['Budget', '5.56 NATO', '16" Barrel'],
|
|
image: 'https://picsum.photos/400/250?random=1'
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'Precision Long Range',
|
|
description: 'High-end precision build optimized for long-range accuracy',
|
|
status: 'in-progress' as const,
|
|
totalCost: 2847.99,
|
|
startedDate: '2024-02-01',
|
|
components: {
|
|
total: 18,
|
|
completed: 12,
|
|
categories: {
|
|
'Upper': 6,
|
|
'Lower': 4,
|
|
'Accessory': 2
|
|
}
|
|
},
|
|
tags: ['Precision', '6.5 Creedmoor', '20" Barrel'],
|
|
image: 'https://picsum.photos/400/250?random=2'
|
|
},
|
|
{
|
|
id: '3',
|
|
name: 'Home Defense Setup',
|
|
description: 'Compact AR-15 configured for home defense scenarios',
|
|
status: 'planning' as const,
|
|
totalCost: 0,
|
|
plannedDate: '2024-03-01',
|
|
components: {
|
|
total: 18,
|
|
completed: 0,
|
|
categories: {
|
|
'Upper': 0,
|
|
'Lower': 0,
|
|
'Accessory': 0
|
|
}
|
|
},
|
|
tags: ['Home Defense', '5.56 NATO', '10.5" Barrel'],
|
|
image: 'https://picsum.photos/400/250?random=3'
|
|
},
|
|
{
|
|
id: '4',
|
|
name: 'Competition Rifle',
|
|
description: 'Lightweight competition build for 3-gun matches',
|
|
status: 'completed' as const,
|
|
totalCost: 1650.75,
|
|
completedDate: '2023-12-10',
|
|
components: {
|
|
total: 18,
|
|
completed: 18,
|
|
categories: {
|
|
'Upper': 8,
|
|
'Lower': 7,
|
|
'Accessory': 3
|
|
}
|
|
},
|
|
tags: ['Competition', '5.56 NATO', '18" Barrel'],
|
|
image: 'https://picsum.photos/400/250?random=4'
|
|
},
|
|
{
|
|
id: '5',
|
|
name: 'Suppressed SBR',
|
|
description: 'Short-barreled rifle build with suppressor integration',
|
|
status: 'in-progress' as const,
|
|
totalCost: 1895.25,
|
|
startedDate: '2024-01-20',
|
|
components: {
|
|
total: 18,
|
|
completed: 8,
|
|
categories: {
|
|
'Upper': 4,
|
|
'Lower': 3,
|
|
'Accessory': 1
|
|
}
|
|
},
|
|
tags: ['SBR', 'Suppressed', '300 BLK'],
|
|
image: 'https://picsum.photos/400/250?random=5'
|
|
},
|
|
{
|
|
id: '6',
|
|
name: 'Retro M16A1 Clone',
|
|
description: 'Faithful reproduction of the classic M16A1 rifle',
|
|
status: 'planning' as const,
|
|
totalCost: 0,
|
|
plannedDate: '2024-04-01',
|
|
components: {
|
|
total: 18,
|
|
completed: 0,
|
|
categories: {
|
|
'Upper': 0,
|
|
'Lower': 0,
|
|
'Accessory': 0
|
|
}
|
|
},
|
|
tags: ['Retro', '5.56 NATO', '20" Barrel'],
|
|
image: 'https://picsum.photos/400/250?random=6'
|
|
}
|
|
];
|
|
|
|
type MyBuildStatus = 'completed' | 'in-progress' | 'planning';
|
|
type SortField = 'name' | 'status' | 'totalCost' | 'completedDate';
|
|
type SortDirection = 'asc' | 'desc';
|
|
|
|
export default function MyBuildsPage() {
|
|
const [sortField, setSortField] = useState<SortField>('completedDate');
|
|
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
|
|
const [selectedStatus, setSelectedStatus] = useState<MyBuildStatus | 'all'>('all');
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
|
|
// Filter builds
|
|
const filteredMyBuilds = sampleMyBuilds.filter(build => {
|
|
if (selectedStatus !== 'all' && build.status !== selectedStatus) {
|
|
return false;
|
|
}
|
|
|
|
if (searchTerm && !build.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
|
|
!build.description.toLowerCase().includes(searchTerm.toLowerCase())) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// Sort builds
|
|
const sortedMyBuilds = [...filteredMyBuilds].sort((a, b) => {
|
|
let aValue: any, bValue: any;
|
|
|
|
if (sortField === 'totalCost') {
|
|
aValue = a.totalCost;
|
|
bValue = b.totalCost;
|
|
} else if (sortField === 'status') {
|
|
aValue = a.status;
|
|
bValue = b.status;
|
|
} else if (sortField === 'completedDate') {
|
|
aValue = a.completedDate || a.startedDate || a.plannedDate || '';
|
|
bValue = b.completedDate || b.startedDate || b.plannedDate || '';
|
|
} else {
|
|
aValue = a.name.toLowerCase();
|
|
bValue = b.name.toLowerCase();
|
|
}
|
|
|
|
if (sortDirection === 'asc') {
|
|
return aValue > bValue ? 1 : -1;
|
|
} else {
|
|
return aValue < bValue ? 1 : -1;
|
|
}
|
|
});
|
|
|
|
const getStatusColor = (status: MyBuildStatus) => {
|
|
switch (status) {
|
|
case 'completed':
|
|
return 'bg-green-100 text-green-800';
|
|
case 'in-progress':
|
|
return 'bg-blue-100 text-blue-800';
|
|
case 'planning':
|
|
return 'bg-yellow-100 text-yellow-800';
|
|
default:
|
|
return 'bg-gray-100 text-gray-800';
|
|
}
|
|
};
|
|
|
|
const getStatusIcon = (status: MyBuildStatus) => {
|
|
switch (status) {
|
|
case 'completed':
|
|
return '✓';
|
|
case 'in-progress':
|
|
return '🔄';
|
|
case 'planning':
|
|
return '📋';
|
|
default:
|
|
return '❓';
|
|
}
|
|
};
|
|
|
|
const formatDate = (dateString: string) => {
|
|
return new Date(dateString).toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
});
|
|
};
|
|
|
|
const getProgressPercentage = (build: typeof sampleMyBuilds[0]) => {
|
|
return Math.round((build.components.completed / build.components.total) * 100);
|
|
};
|
|
|
|
const totalMyBuilds = sampleMyBuilds.length;
|
|
const completedMyBuilds = sampleMyBuilds.filter(build => build.status === 'completed').length;
|
|
const inProgressMyBuilds = sampleMyBuilds.filter(build => build.status === 'in-progress').length;
|
|
const totalValue = sampleMyBuilds.reduce((sum, build) => sum + build.totalCost, 0);
|
|
|
|
return (
|
|
<main className="min-h-screen bg-gray-50">
|
|
{/* Page Title */}
|
|
<div className="bg-white border-b">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
|
<h1 className="text-3xl font-bold text-gray-900">My Builds</h1>
|
|
<p className="text-gray-600 mt-2">Track and manage your firearm builds</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Build Summary */}
|
|
<div className="bg-white border-b">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-gray-900">{totalMyBuilds}</div>
|
|
<div className="text-sm text-gray-500">Total Builds</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-green-600">{completedMyBuilds}</div>
|
|
<div className="text-sm text-gray-500">Completed</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-blue-600">{inProgressMyBuilds}</div>
|
|
<div className="text-sm text-gray-500">In Progress</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-purple-600">${totalValue.toFixed(2)}</div>
|
|
<div className="text-sm text-gray-500">Total Value</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Search and Filters */}
|
|
<div className="bg-white border-b">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
|
{/* Filters Row */}
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
{/* Status Filter */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Status</label>
|
|
<select
|
|
value={selectedStatus}
|
|
onChange={(e) => setSelectedStatus(e.target.value as MyBuildStatus | 'all')}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
>
|
|
<option value="all">All Status</option>
|
|
<option value="completed">Completed</option>
|
|
<option value="in-progress">In Progress</option>
|
|
<option value="planning">Planning</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Sort by */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Sort By</label>
|
|
<select
|
|
value={sortField}
|
|
onChange={(e) => setSortField(e.target.value as SortField)}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
>
|
|
<option value="completedDate">Date</option>
|
|
<option value="name">Name</option>
|
|
<option value="totalCost">Cost</option>
|
|
<option value="status">Status</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Sort Direction */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Order</label>
|
|
<select
|
|
value={sortDirection}
|
|
onChange={(e) => setSortDirection(e.target.value as SortDirection)}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
>
|
|
<option value="desc">Newest First</option>
|
|
<option value="asc">Oldest First</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* New Build Button */}
|
|
<div className="flex items-end">
|
|
<button className="w-full bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors">
|
|
+ New Build
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Builds Grid */}
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
{sortedMyBuilds.length > 0 ? (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{sortedMyBuilds.map((build) => (
|
|
<div key={build.id} className="bg-white rounded-lg shadow-sm border hover:shadow-md transition-shadow overflow-hidden">
|
|
{/* Build Image */}
|
|
<div className="h-48 bg-gray-200 relative">
|
|
<img
|
|
src={build.image}
|
|
alt={build.name}
|
|
className="w-full h-full object-cover"
|
|
onError={(e) => {
|
|
const target = e.target as HTMLImageElement;
|
|
target.style.display = 'none';
|
|
target.nextElementSibling?.classList.remove('hidden');
|
|
}}
|
|
/>
|
|
<div className="hidden w-full h-full flex items-center justify-center bg-gradient-to-br from-gray-300 to-gray-400">
|
|
<div className="text-center text-gray-600">
|
|
<div className="text-4xl mb-2">🔫</div>
|
|
<div className="text-sm font-medium">{build.name}</div>
|
|
</div>
|
|
</div>
|
|
<div className="absolute top-3 right-3">
|
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusColor(build.status)}`}>
|
|
{getStatusIcon(build.status)} {build.status}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Build Content */}
|
|
<div className="p-6">
|
|
{/* Build Title and Date */}
|
|
<div className="mb-4">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-1">{build.name}</h3>
|
|
<p className="text-sm text-gray-600 mb-2">{build.description}</p>
|
|
<div className="text-xs text-gray-500">
|
|
{build.status === 'completed' && build.completedDate && `Completed ${formatDate(build.completedDate)}`}
|
|
{build.status === 'in-progress' && build.startedDate && `Started ${formatDate(build.startedDate)}`}
|
|
{build.status === 'planning' && build.plannedDate && `Planned for ${formatDate(build.plannedDate)}`}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress Bar */}
|
|
<div className="mb-4">
|
|
<div className="flex justify-between text-sm text-gray-600 mb-1">
|
|
<span>Progress</span>
|
|
<span>{build.components.completed}/{build.components.total} components</span>
|
|
</div>
|
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
|
<div
|
|
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
|
|
style={{ width: `${getProgressPercentage(build)}%` }}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Component Categories */}
|
|
<div className="mb-4">
|
|
<div className="flex space-x-2">
|
|
{Object.entries(build.components.categories).map(([category, count]) => (
|
|
<span key={category} className="inline-flex items-center px-2 py-1 rounded text-xs bg-gray-100 text-gray-700">
|
|
{category}: {count}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tags */}
|
|
<div className="mb-4">
|
|
<div className="flex flex-wrap gap-1">
|
|
{build.tags.map((tag) => (
|
|
<span key={tag} className="inline-flex items-center px-2 py-1 rounded text-xs bg-blue-100 text-blue-800">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Cost and Actions */}
|
|
<div className="flex justify-between items-center">
|
|
<div className="text-lg font-bold text-gray-900">
|
|
${build.totalCost.toFixed(2)}
|
|
</div>
|
|
<div className="flex space-x-2">
|
|
<button className="bg-blue-600 text-white px-3 py-1 rounded text-sm hover:bg-blue-700 transition-colors">
|
|
View Details
|
|
</button>
|
|
<button className="bg-gray-100 text-gray-700 px-2 py-1 rounded text-sm hover:bg-gray-200 transition-colors">
|
|
Edit
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="text-center py-12">
|
|
<div className="text-gray-500">
|
|
<div className="text-lg font-medium mb-2">No builds found</div>
|
|
<div className="text-sm">Try adjusting your filters or create a new build</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|