Fix production environment variables
- Remove old Confluence variables - Add NEXT_PUBLIC_API_URL for API access 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useState, useMemo } from 'react';
|
||||
import { Filament } from '../types/filament';
|
||||
import { ColorCell } from './ColorCell';
|
||||
import { getFilamentColor, getColorStyle, getContrastColor } from '../data/bambuLabColors';
|
||||
import '../styles/select.css';
|
||||
|
||||
interface FilamentTableProps {
|
||||
filaments: Filament[];
|
||||
@@ -13,13 +14,48 @@ export const FilamentTable: React.FC<FilamentTableProps> = ({ filaments, loading
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [sortField, setSortField] = useState<keyof Filament>('boja');
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
|
||||
|
||||
// Filter states
|
||||
const [filterBrand, setFilterBrand] = useState('');
|
||||
const [filterTip, setFilterTip] = useState('');
|
||||
const [filterFinish, setFilterFinish] = useState('');
|
||||
const [filterStatus, setFilterStatus] = useState('');
|
||||
|
||||
// Get unique values for filters
|
||||
const uniqueBrands = useMemo(() => [...new Set(filaments.map(f => f.brand))].sort(), [filaments]);
|
||||
const uniqueTips = useMemo(() => [...new Set(filaments.map(f => f.tip))].sort(), [filaments]);
|
||||
const uniqueFinishes = useMemo(() => [...new Set(filaments.map(f => f.finish))].sort(), [filaments]);
|
||||
|
||||
const filteredAndSortedFilaments = useMemo(() => {
|
||||
let filtered = filaments.filter(filament =>
|
||||
Object.values(filament).some(value =>
|
||||
let filtered = filaments.filter(filament => {
|
||||
// Search filter
|
||||
const matchesSearch = Object.values(filament).some(value =>
|
||||
value.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
// Brand filter
|
||||
const matchesBrand = !filterBrand || filament.brand === filterBrand;
|
||||
|
||||
// Type filter
|
||||
const matchesTip = !filterTip || filament.tip === filterTip;
|
||||
|
||||
// Finish filter
|
||||
const matchesFinish = !filterFinish || filament.finish === filterFinish;
|
||||
|
||||
// Status filter
|
||||
let matchesStatus = true;
|
||||
if (filterStatus) {
|
||||
if (filterStatus === 'new') {
|
||||
matchesStatus = filament.vakum.toLowerCase().includes('vakuum') && !filament.otvoreno;
|
||||
} else if (filterStatus === 'opened') {
|
||||
matchesStatus = filament.otvoreno.toLowerCase().includes('otvorena');
|
||||
} else if (filterStatus === 'refill') {
|
||||
matchesStatus = filament.refill.toLowerCase() === 'da';
|
||||
}
|
||||
}
|
||||
|
||||
return matchesSearch && matchesBrand && matchesTip && matchesFinish && matchesStatus;
|
||||
});
|
||||
|
||||
filtered.sort((a, b) => {
|
||||
const aValue = a[sortField];
|
||||
@@ -33,7 +69,7 @@ export const FilamentTable: React.FC<FilamentTableProps> = ({ filaments, loading
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}, [filaments, searchTerm, sortField, sortOrder]);
|
||||
}, [filaments, searchTerm, sortField, sortOrder, filterBrand, filterTip, filterFinish, filterStatus]);
|
||||
|
||||
const handleSort = (field: keyof Filament) => {
|
||||
if (sortField === field) {
|
||||
@@ -62,14 +98,103 @@ export const FilamentTable: React.FC<FilamentTableProps> = ({ filaments, loading
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Pretraži filamente..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<div className="space-y-4 mb-4">
|
||||
{/* Search Bar */}
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Pretraži filamente..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Filter Row */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{/* Brand Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Brend
|
||||
</label>
|
||||
<select
|
||||
value={filterBrand}
|
||||
onChange={(e) => setFilterBrand(e.target.value)}
|
||||
className="custom-select w-full px-3 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Svi brendovi</option>
|
||||
{uniqueBrands.map(brand => (
|
||||
<option key={brand} value={brand}>{brand}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Type Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Tip
|
||||
</label>
|
||||
<select
|
||||
value={filterTip}
|
||||
onChange={(e) => setFilterTip(e.target.value)}
|
||||
className="custom-select w-full px-3 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Svi tipovi</option>
|
||||
{uniqueTips.map(tip => (
|
||||
<option key={tip} value={tip}>{tip}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Finish Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Završna obrada
|
||||
</label>
|
||||
<select
|
||||
value={filterFinish}
|
||||
onChange={(e) => setFilterFinish(e.target.value)}
|
||||
className="custom-select w-full px-3 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Sve obrade</option>
|
||||
{uniqueFinishes.map(finish => (
|
||||
<option key={finish} value={finish}>{finish}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Status Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Status
|
||||
</label>
|
||||
<select
|
||||
value={filterStatus}
|
||||
onChange={(e) => setFilterStatus(e.target.value)}
|
||||
className="custom-select w-full px-3 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Svi statusi</option>
|
||||
<option value="new">Novi (vakuum)</option>
|
||||
<option value="opened">Otvoreni</option>
|
||||
<option value="refill">Refill</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Clear Filters Button */}
|
||||
{(filterBrand || filterTip || filterFinish || filterStatus) && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setFilterBrand('');
|
||||
setFilterTip('');
|
||||
setFilterFinish('');
|
||||
setFilterStatus('');
|
||||
}}
|
||||
className="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300"
|
||||
>
|
||||
Obriši filtere
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
|
||||
24
src/styles/select.css
Normal file
24
src/styles/select.css
Normal file
@@ -0,0 +1,24 @@
|
||||
/* Custom select styling for cross-browser consistency */
|
||||
.custom-select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0.5rem center;
|
||||
background-size: 1.5em 1.5em;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
.dark .custom-select {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||
}
|
||||
|
||||
/* Safari-specific fixes */
|
||||
@media not all and (min-resolution:.001dpcm) {
|
||||
@supports (-webkit-appearance:none) {
|
||||
.custom-select {
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user