first commit

This commit is contained in:
2025-06-29 07:12:20 -04:00
parent 6612f40d9b
commit cfcc4c480e
16 changed files with 3156 additions and 767 deletions
+875 -98
View File
@@ -1,103 +1,880 @@
import Image from "next/image";
'use client';
import { useState, useEffect } from 'react';
import { useSearchParams } from 'next/navigation';
// Sample firearm parts data
const parts = [
{
id: '1',
name: 'Faxon 16" Gunner Barrel - 5.56 NATO',
description: 'Lightweight, high-performance AR-15 barrel with 1:8 twist rate',
image_url: 'https://picsum.photos/300/200?random=1',
brand: {
id: 'b1',
name: 'Faxon Firearms',
},
category: {
id: 'c1',
name: 'Barrel',
},
offers: [
{
price: 189.99,
url: 'https://primaryarms.com/faxon-16-gunner-barrel',
vendor: {
name: 'Primary Arms',
},
},
],
},
{
id: '2',
name: 'BCM M4 Upper Receiver',
description: 'Forged upper receiver with M4 feed ramps and T-markings',
image_url: 'https://picsum.photos/300/200?random=2',
brand: {
id: 'b2',
name: 'BCM',
},
category: {
id: 'c2',
name: 'Upper Receiver',
},
offers: [
{
price: 129.99,
url: 'https://rainierarms.com/bcm-m4-upper',
vendor: {
name: 'Rainier Arms',
},
},
],
},
{
id: '3',
name: 'Aero Precision Lower Receiver',
description: 'Mil-spec forged lower receiver with trigger guard and threaded bolt catch',
image_url: 'https://picsum.photos/300/200?random=3',
brand: {
id: 'b3',
name: 'Aero Precision',
},
category: {
id: 'c3',
name: 'Lower Receiver',
},
offers: [
{
price: 89.99,
url: 'https://aeroprecisionusa.com/lower',
vendor: {
name: 'Aero Precision',
},
},
],
},
{
id: '4',
name: 'Toolcraft BCG - Nitride',
description: 'Mil-spec bolt carrier group with Carpenter 158 bolt and nitride finish',
image_url: 'https://picsum.photos/300/200?random=4',
brand: {
id: 'b4',
name: 'Toolcraft',
},
category: {
id: 'c4',
name: 'BCG',
},
offers: [
{
price: 149.99,
url: 'https://wctoolcraft.com/bcg-nitride',
vendor: {
name: 'WC Toolcraft',
},
},
],
},
{
id: '5',
name: 'Geissele SSA-E Trigger',
description: 'Two-stage trigger with 3.5lb total pull weight and enhanced reliability',
image_url: 'https://picsum.photos/300/200?random=5',
brand: {
id: 'b5',
name: 'Geissele',
},
category: {
id: 'c5',
name: 'Trigger',
},
offers: [
{
price: 249.99,
url: 'https://geissele.com/ssa-e',
vendor: {
name: 'Geissele',
},
},
],
},
{
id: '6',
name: 'Magpul CTR Stock',
description: 'Collapsible stock with friction lock system and QD sling mount',
image_url: 'https://picsum.photos/300/200?random=6',
brand: {
id: 'b6',
name: 'Magpul',
},
category: {
id: 'c6',
name: 'Stock',
},
offers: [
{
price: 69.99,
url: 'https://magpul.com/ctr-stock',
vendor: {
name: 'Magpul',
},
},
],
},
{
id: '7',
name: 'Radian Raptor Charging Handle',
description: 'Ambidextrous charging handle with oversized latches and smooth operation',
image_url: 'https://picsum.photos/300/200?random=7',
brand: {
id: 'b7',
name: 'Radian Weapons',
},
category: {
id: 'c7',
name: 'Charging Handle',
},
offers: [
{
price: 89.99,
url: 'https://radianweapons.com/raptor',
vendor: {
name: 'Radian Weapons',
},
},
],
},
{
id: '8',
name: 'BCM Gunfighter Handguard - 15" M-LOK',
description: 'Free-float handguard with M-LOK slots and integrated QD mounts',
image_url: 'https://picsum.photos/300/200?random=8',
brand: {
id: 'b2',
name: 'BCM',
},
category: {
id: 'c8',
name: 'Handguard',
},
offers: [
{
price: 199.99,
url: 'https://bravocompanyusa.com/handguard',
vendor: {
name: 'BCM',
},
},
],
},
{
id: '9',
name: 'SureFire WarComp Flash Hider',
description: 'Compensator/flash hider hybrid with suppressor mount capability',
image_url: 'https://picsum.photos/300/200?random=9',
brand: {
id: 'b8',
name: 'SureFire',
},
category: {
id: 'c9',
name: 'Muzzle Device',
},
offers: [
{
price: 159.99,
url: 'https://surefire.com/warcomp',
vendor: {
name: 'SureFire',
},
},
],
},
{
id: '10',
name: 'Aero Precision Gas Block - Low Profile',
description: 'Low-profile adjustable gas block for free-float handguards',
image_url: 'https://picsum.photos/300/200?random=10',
brand: {
id: 'b3',
name: 'Aero Precision',
},
category: {
id: 'c10',
name: 'Gas Block',
},
offers: [
{
price: 49.99,
url: 'https://aeroprecisionusa.com/gas-block',
vendor: {
name: 'Aero Precision',
},
},
],
},
{
id: '11',
name: 'BCM Gas Tube - Mid Length',
description: 'Stainless steel gas tube for mid-length gas systems',
image_url: 'https://picsum.photos/300/200?random=11',
brand: {
id: 'b2',
name: 'BCM',
},
category: {
id: 'c11',
name: 'Gas Tube',
},
offers: [
{
price: 19.99,
url: 'https://bravocompanyusa.com/gas-tube',
vendor: {
name: 'BCM',
},
},
],
},
{
id: '12',
name: 'Magpul MOE Pistol Grip',
description: 'Ergonomic pistol grip with storage compartment and enhanced texture',
image_url: 'https://picsum.photos/300/200?random=12',
brand: {
id: 'b6',
name: 'Magpul',
},
category: {
id: 'c12',
name: 'Pistol Grip',
},
offers: [
{
price: 24.99,
url: 'https://magpul.com/moe-grip',
vendor: {
name: 'Magpul',
},
},
],
},
{
id: '13',
name: 'BCM Buffer Tube - Mil-Spec',
description: 'Mil-spec buffer tube with proper castle nut and end plate',
image_url: 'https://picsum.photos/300/200?random=13',
brand: {
id: 'b2',
name: 'BCM',
},
category: {
id: 'c13',
name: 'Buffer Tube',
},
offers: [
{
price: 44.99,
url: 'https://bravocompanyusa.com/buffer-tube',
vendor: {
name: 'BCM',
},
},
],
},
{
id: '14',
name: 'H2 Buffer Weight',
description: 'Heavy buffer weight for improved recoil management',
image_url: 'https://picsum.photos/300/200?random=14',
brand: {
id: 'b9',
name: 'Spikes Tactical',
},
category: {
id: 'c14',
name: 'Buffer',
},
offers: [
{
price: 29.99,
url: 'https://spikestactical.com/h2-buffer',
vendor: {
name: 'Spikes Tactical',
},
},
],
},
{
id: '15',
name: 'Sprinco Blue Buffer Spring',
description: 'Enhanced buffer spring for improved reliability and reduced wear',
image_url: 'https://picsum.photos/300/200?random=15',
brand: {
id: 'b10',
name: 'Sprinco',
},
category: {
id: 'c15',
name: 'Buffer Spring',
},
offers: [
{
price: 19.99,
url: 'https://sprinco.com/blue-spring',
vendor: {
name: 'Sprinco',
},
},
],
},
{
id: '16',
name: 'Magpul PMAG 30-Round Magazine',
description: '30-round polymer magazine with anti-tilt follower and dust cover',
image_url: 'https://picsum.photos/300/200?random=16',
brand: {
id: 'b6',
name: 'Magpul',
},
category: {
id: 'c16',
name: 'Magazine',
},
offers: [
{
price: 14.99,
url: 'https://magpul.com/pmag',
vendor: {
name: 'Magpul',
},
},
],
},
{
id: '17',
name: 'Troy Industries BUIS - Folding',
description: 'Folding backup iron sights with micro-adjustable elevation',
image_url: 'https://picsum.photos/300/200?random=17',
brand: {
id: 'b11',
name: 'Troy Industries',
},
category: {
id: 'c17',
name: 'Sights',
},
offers: [
{
price: 129.99,
url: 'https://troyind.com/buis',
vendor: {
name: 'Troy Industries',
},
},
],
},
{
id: '18',
name: 'Aero Precision Trigger Guard',
description: 'Enhanced trigger guard with oversized opening for gloved hands',
image_url: 'https://picsum.photos/300/200?random=18',
brand: {
id: 'b3',
name: 'Aero Precision',
},
category: {
id: 'c18',
name: 'Trigger Guard',
},
offers: [
{
price: 12.99,
url: 'https://aeroprecisionusa.com/trigger-guard',
vendor: {
name: 'Aero Precision',
},
},
],
},
{
id: '19',
name: 'Larue MBT-2S Trigger',
description: 'Two-stage trigger with 4.5lb total pull weight and excellent value',
image_url: 'https://picsum.photos/300/200?random=19',
brand: {
id: 'b12',
name: 'LaRue Tactical',
},
category: {
id: 'c5',
name: 'Trigger',
},
offers: [
{
price: 89.99,
url: 'https://laruetactical.com/mbt-2s',
vendor: {
name: 'LaRue Tactical',
},
},
],
},
{
id: '20',
name: 'Daniel Defense 18" Barrel - 5.56 NATO',
description: 'Cold hammer forged barrel with 1:7 twist rate for precision shooting',
image_url: 'https://picsum.photos/300/200?random=20',
brand: {
id: 'b13',
name: 'Daniel Defense',
},
category: {
id: 'c1',
name: 'Barrel',
},
offers: [
{
price: 399.99,
url: 'https://danieldefense.com/18-barrel',
vendor: {
name: 'Daniel Defense',
},
},
],
},
{
id: '21',
name: 'BCM Complete Lower Receiver',
description: 'Complete lower receiver with BCM trigger, grip, and stock',
image_url: 'https://picsum.photos/300/200?random=21',
brand: {
id: 'b2',
name: 'BCM',
},
category: {
id: 'c3',
name: 'Lower Receiver',
},
offers: [
{
price: 449.99,
url: 'https://bravocompanyusa.com/complete-lower',
vendor: {
name: 'BCM',
},
},
],
},
{
id: '22',
name: 'Aimpoint PRO Red Dot Sight',
description: '2 MOA red dot sight with 2x magnification and 40mm objective',
image_url: 'https://picsum.photos/300/200?random=22',
brand: {
id: 'b14',
name: 'Aimpoint',
},
category: {
id: 'c17',
name: 'Sights',
},
offers: [
{
price: 449.99,
url: 'https://aimpoint.com/pro',
vendor: {
name: 'Aimpoint',
},
},
],
}
];
// Extract unique values for dropdowns
const categories = ['All', ...Array.from(new Set(parts.map(part => part.category.name)))];
const brands = ['All', ...Array.from(new Set(parts.map(part => part.brand.name)))];
const vendors = ['All', ...Array.from(new Set(parts.flatMap(part => part.offers.map(offer => offer.vendor.name))))];
type SortField = 'name' | 'category' | 'price';
type SortDirection = 'asc' | 'desc';
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
src/app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
const searchParams = useSearchParams();
const [selectedCategory, setSelectedCategory] = useState('All');
const [selectedBrand, setSelectedBrand] = useState('All');
const [selectedVendor, setSelectedVendor] = useState('All');
const [sortField, setSortField] = useState<SortField>('name');
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
const [searchTerm, setSearchTerm] = useState('');
const [minPrice, setMinPrice] = useState('');
const [maxPrice, setMaxPrice] = useState('');
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
// Read category from URL parameter on page load
useEffect(() => {
const categoryParam = searchParams.get('category');
if (categoryParam && categories.includes(categoryParam)) {
setSelectedCategory(categoryParam);
}
}, [searchParams]);
// Filter parts by all criteria
const filteredParts = parts.filter(part => {
// Category filter
if (selectedCategory !== 'All' && part.category.name !== selectedCategory) {
return false;
}
// Brand filter
if (selectedBrand !== 'All' && part.brand.name !== selectedBrand) {
return false;
}
// Vendor filter
if (selectedVendor !== 'All' && !part.offers.some(offer => offer.vendor.name === selectedVendor)) {
return false;
}
// Search filter
if (searchTerm && !part.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
!part.description.toLowerCase().includes(searchTerm.toLowerCase())) {
return false;
}
// Price range filter
const minPriceNum = minPrice ? parseFloat(minPrice) : 0;
const maxPriceNum = maxPrice ? parseFloat(maxPrice) : Infinity;
const partPrice = Math.min(...part.offers.map(offer => offer.price));
if (partPrice < minPriceNum || partPrice > maxPriceNum) {
return false;
}
return true;
});
// Sort parts
const sortedParts = [...filteredParts].sort((a, b) => {
let aValue: any, bValue: any;
if (sortField === 'price') {
aValue = Math.min(...a.offers.map(offer => offer.price));
bValue = Math.min(...b.offers.map(offer => offer.price));
} else if (sortField === 'category') {
aValue = a.category.name.toLowerCase();
bValue = b.category.name.toLowerCase();
} else {
aValue = a.name.toLowerCase();
bValue = b.name.toLowerCase();
}
if (sortDirection === 'asc') {
return aValue > bValue ? 1 : -1;
} else {
return aValue < bValue ? 1 : -1;
}
});
const handleSort = (field: SortField) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
const getSortIcon = (field: SortField) => {
if (sortField !== field) {
return '↕️';
}
return sortDirection === 'asc' ? '↑' : '↓';
};
const clearFilters = () => {
setSelectedCategory('All');
setSelectedBrand('All');
setSelectedVendor('All');
setSearchTerm('');
setMinPrice('');
setMaxPrice('');
};
const hasActiveFilters = selectedCategory !== 'All' || selectedBrand !== 'All' || selectedVendor !== 'All' || searchTerm || minPrice || maxPrice;
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">
Parts Catalog
{selectedCategory !== 'All' && (
<span className="text-blue-600 ml-2 text-2xl">
- {selectedCategory}
</span>
)}
</h1>
<p className="text-gray-600 mt-2">
{selectedCategory !== 'All'
? `Showing ${selectedCategory} parts for your build`
: 'Browse and filter firearm parts for your build'
}
</p>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</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">
{/* Search Row */}
<div className="mb-4 flex justify-end">
<div className="w-1/2">
<label className="block text-sm font-medium text-gray-700 mb-1">Search</label>
<input
type="text"
placeholder="Search parts..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
{/* Filters Row */}
<div className="grid grid-cols-1 md:grid-cols-6 gap-4">
{/* Category Dropdown */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Category</label>
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
{categories.map((category) => (
<option key={category} value={category}>
{category}
</option>
))}
</select>
</div>
{/* Brand Dropdown */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Brand</label>
<select
value={selectedBrand}
onChange={(e) => setSelectedBrand(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
{brands.map((brand) => (
<option key={brand} value={brand}>
{brand}
</option>
))}
</select>
</div>
{/* Vendor Dropdown */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Vendor</label>
<select
value={selectedVendor}
onChange={(e) => setSelectedVendor(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
{vendors.map((vendor) => (
<option key={vendor} value={vendor}>
{vendor}
</option>
))}
</select>
</div>
{/* Min Price */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Min Price</label>
<input
type="number"
placeholder="0"
value={minPrice}
onChange={(e) => setMinPrice(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
{/* Max Price */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Max Price</label>
<input
type="number"
placeholder="1000"
value={maxPrice}
onChange={(e) => setMaxPrice(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
{/* Clear Filters */}
<div className="flex items-end">
<button
onClick={clearFilters}
disabled={!hasActiveFilters}
className={`w-full px-4 py-2 rounded-lg transition-colors ${
hasActiveFilters
? 'bg-red-600 text-white hover:bg-red-700'
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
}`}
>
Clear All
</button>
</div>
</div>
</div>
</div>
{/* Parts Table */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Category
</th>
<th
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100"
onClick={() => handleSort('name')}
>
<div className="flex items-center space-x-1">
<span>Name</span>
<span className="text-sm">{getSortIcon('name')}</span>
</div>
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Brand
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Description
</th>
<th
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100"
onClick={() => handleSort('price')}
>
<div className="flex items-center space-x-1">
<span>Price</span>
<span className="text-sm">{getSortIcon('price')}</span>
</div>
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Vendor
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{sortedParts.length > 0 ? (
sortedParts.map((part) => (
<tr key={part.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{part.category.name}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900">
{part.name}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">
{part.brand.name}
</div>
</td>
<td className="px-6 py-4">
<div className="text-sm text-gray-500 max-w-xs truncate">
{part.description}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-semibold text-gray-900">
${Math.min(...part.offers.map(offer => offer.price))}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-500">
{part.offers[0]?.vendor.name}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div className="flex space-x-2">
<button className="bg-blue-600 text-white py-1 px-3 rounded text-xs hover:bg-blue-700 transition-colors">
Add to Build
</button>
<button className="bg-gray-100 text-gray-700 py-1 px-2 rounded text-xs hover:bg-gray-200 transition-colors">
</button>
</div>
</td>
</tr>
))
) : (
<tr>
<td colSpan={7} className="px-6 py-12 text-center">
<div className="text-gray-500">
<div className="text-lg font-medium mb-2">No parts found</div>
<div className="text-sm">Try adjusting your filters or search terms</div>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Table Footer */}
<div className="bg-gray-50 px-6 py-3 border-t border-gray-200">
<div className="flex items-center justify-between">
<div className="text-sm text-gray-700">
Showing {sortedParts.length} of {parts.length} parts
{hasActiveFilters && (
<span className="ml-2 text-blue-600">
(filtered)
</span>
)}
</div>
{/* <div className="text-sm text-gray-500">
Total Value: ${sortedParts.reduce((sum, part) => sum + Math.min(...part.offers.map(offer => offer.price)), 0).toFixed(2)}
</div> */}
</div>
</div>
</div>
</div>
</main>
);
}
}