Major frontend and admin improvements
Frontend changes: - Removed brand filter and column from table - Removed inventory summary grid - Removed stanje (state) and težina (weight) columns - Reorganized filters: Material → Finish → Color - Updated EnhancedFilters component with new filter structure - Removed legend section for storage conditions Admin dashboard changes: - Removed sidebar navigation (Boje option) - Made dashboard full screen - Removed brand column from table - Removed brand field from add/edit form - Updated all boolean columns to use checkmark/X icons - Made color tiles 40% bigger - Added sortable columns functionality - Auto-fill price: 3499 for refill, 3999 for regular Other improvements: - Added BackToTop button component on all pages - Fixed Next.js dialog backdrop CSS error - Updated tests to match new UI structure - Removed obsolete FilamentTable.tsx component - Ensured proper synchronization between admin and frontend 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -18,11 +18,11 @@ export const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments, loa
|
||||
const [sortField, setSortField] = useState<string>('color.name');
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
|
||||
const [filters, setFilters] = useState({
|
||||
brand: '',
|
||||
material: '',
|
||||
finish: '',
|
||||
color: '',
|
||||
storageCondition: '',
|
||||
isRefill: null as boolean | null,
|
||||
color: ''
|
||||
isRefill: null as boolean | null
|
||||
});
|
||||
|
||||
// Convert legacy filaments to V2 format for display
|
||||
@@ -78,8 +78,8 @@ export const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments, loa
|
||||
|
||||
// Get unique values for filters
|
||||
const uniqueValues = useMemo(() => ({
|
||||
brands: [...new Set(normalizedFilaments.map(f => f.brand))].sort(),
|
||||
materials: [...new Set(normalizedFilaments.map(f => f.material.base))].sort(),
|
||||
finishes: [...new Set(normalizedFilaments.map(f => f.material.modifier).filter(Boolean))].sort() as string[],
|
||||
colors: [...new Set(normalizedFilaments.map(f => f.color.name))].sort()
|
||||
}), [normalizedFilaments]);
|
||||
|
||||
@@ -98,15 +98,13 @@ export const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments, loa
|
||||
false; // SKU removed
|
||||
|
||||
// Other filters
|
||||
const matchesBrand = !filters.brand || filament.brand === filters.brand;
|
||||
const matchesMaterial = !filters.material ||
|
||||
filament.material.base === filters.material ||
|
||||
`${filament.material.base}-${filament.material.modifier}` === filters.material;
|
||||
const matchesMaterial = !filters.material || filament.material.base === filters.material;
|
||||
const matchesFinish = !filters.finish || filament.material.modifier === filters.finish;
|
||||
const matchesStorage = !filters.storageCondition || filament.condition.storageCondition === filters.storageCondition;
|
||||
const matchesRefill = filters.isRefill === null || filament.condition.isRefill === filters.isRefill;
|
||||
const matchesColor = !filters.color || filament.color.name === filters.color;
|
||||
|
||||
return matchesSearch && matchesBrand && matchesMaterial && matchesStorage && matchesRefill && matchesColor;
|
||||
return matchesSearch && matchesMaterial && matchesFinish && matchesStorage && matchesRefill && matchesColor;
|
||||
});
|
||||
|
||||
// Sort
|
||||
@@ -138,29 +136,6 @@ export const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments, loa
|
||||
}
|
||||
};
|
||||
|
||||
// Inventory summary
|
||||
const inventorySummary = useMemo(() => {
|
||||
const summary = {
|
||||
totalSpools: 0,
|
||||
availableSpools: 0,
|
||||
vacuumCount: 0,
|
||||
openedCount: 0,
|
||||
refillCount: 0
|
||||
};
|
||||
|
||||
normalizedFilaments.forEach(f => {
|
||||
summary.totalSpools += f.inventory.total;
|
||||
summary.availableSpools += f.inventory.available;
|
||||
summary.vacuumCount += f.inventory.locations.vacuum;
|
||||
summary.openedCount += f.inventory.locations.opened;
|
||||
|
||||
if (f.condition.isRefill) {
|
||||
summary.refillCount += f.inventory.total;
|
||||
}
|
||||
});
|
||||
|
||||
return summary;
|
||||
}, [normalizedFilaments]);
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-center py-8">Učitavanje...</div>;
|
||||
@@ -172,29 +147,6 @@ export const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments, loa
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Inventory Summary */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">Ukupno filamenta</div>
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">{inventorySummary.totalSpools}</div>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">Dostupno</div>
|
||||
<div className="text-2xl font-bold text-green-600 dark:text-green-400">{inventorySummary.availableSpools}</div>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">Vakum</div>
|
||||
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">{inventorySummary.vacuumCount}</div>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">Otvoreno</div>
|
||||
<div className="text-2xl font-bold text-orange-600 dark:text-orange-400">{inventorySummary.openedCount}</div>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">Refill</div>
|
||||
<div className="text-2xl font-bold text-purple-600 dark:text-purple-400">{inventorySummary.refillCount}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<div className="relative">
|
||||
@@ -217,45 +169,18 @@ export const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments, loa
|
||||
uniqueValues={uniqueValues}
|
||||
/>
|
||||
|
||||
{/* Icon Legend */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
||||
<h3 className="text-base font-medium text-gray-700 dark:text-gray-300 mb-3 text-center">Legenda stanja:</h3>
|
||||
<div className="flex justify-center gap-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="transform scale-125">
|
||||
<InventoryBadge type="vacuum" count={1} />
|
||||
</div>
|
||||
<span className="text-gray-600 dark:text-gray-400 text-[15px]">Vakuum pakovanje</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="transform scale-125">
|
||||
<InventoryBadge type="opened" count={1} />
|
||||
</div>
|
||||
<span className="text-gray-600 dark:text-gray-400 text-[15px]">Otvoreno</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto bg-white dark:bg-gray-800 rounded-lg shadow">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-900">
|
||||
<tr>
|
||||
<th onClick={() => handleSort('brand')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Brend
|
||||
</th>
|
||||
<th onClick={() => handleSort('material.base')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Materijal
|
||||
</th>
|
||||
<th onClick={() => handleSort('color.name')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Boja
|
||||
</th>
|
||||
<th onClick={() => handleSort('inventory.total')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Stanje
|
||||
</th>
|
||||
<th onClick={() => handleSort('weight.value')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Težina
|
||||
</th>
|
||||
<th onClick={() => handleSort('pricing.purchasePrice')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
Cena
|
||||
</th>
|
||||
@@ -267,31 +192,12 @@ export const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments, loa
|
||||
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{filteredAndSortedFilaments.map(filament => (
|
||||
<tr key={filament.id} className="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
||||
{filament.brand}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<MaterialBadge base={filament.material.base} modifier={filament.material.modifier} />
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<ColorSwatch name={filament.color.name} hex={filament.color.hex} size="sm" />
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex gap-2">
|
||||
{filament.inventory.locations.vacuum > 0 && (
|
||||
<InventoryBadge type="vacuum" count={filament.inventory.locations.vacuum} />
|
||||
)}
|
||||
{filament.inventory.locations.opened > 0 && (
|
||||
<InventoryBadge type="opened" count={filament.inventory.locations.opened} />
|
||||
)}
|
||||
{filament.inventory.locations.printer > 0 && (
|
||||
<InventoryBadge type="printer" count={filament.inventory.locations.printer} />
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{filament.weight.value}{filament.weight.unit}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
||||
{(() => {
|
||||
// PLA Basic and Matte pricing logic
|
||||
|
||||
Reference in New Issue
Block a user