'use client' import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { filamentService, colorService } from '@/src/services/api'; import { Filament } from '@/src/types/filament'; import { bambuLabColors, colorsByFinish, getColorHex } from '@/src/data/bambuLabColorsComplete'; import '@/src/styles/select.css'; interface FilamentWithId extends Filament { id: string; createdAt?: string; updatedAt?: string; bojaHex?: string; } export default function AdminDashboard() { const router = useRouter(); const [filaments, setFilaments] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [editingFilament, setEditingFilament] = useState(null); const [showAddForm, setShowAddForm] = useState(false); const [darkMode, setDarkMode] = useState(false); const [mounted, setMounted] = useState(false); const [sortField, setSortField] = useState(''); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); // Initialize dark mode - default to true for admin useEffect(() => { setMounted(true); const saved = localStorage.getItem('darkMode'); if (saved !== null) { setDarkMode(JSON.parse(saved)); } else { // Default to dark mode for admin setDarkMode(true); } }, []); useEffect(() => { if (!mounted) return; localStorage.setItem('darkMode', JSON.stringify(darkMode)); if (darkMode) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }, [darkMode, mounted]); // Check authentication useEffect(() => { const token = localStorage.getItem('authToken'); const expiry = localStorage.getItem('tokenExpiry'); if (!token || !expiry || Date.now() > parseInt(expiry)) { router.push('/upadaj'); } }, [router]); // Fetch filaments const fetchFilaments = async () => { try { setLoading(true); const filaments = await filamentService.getAll(); setFilaments(filaments); } catch (err) { setError('Greška pri učitavanju filamenata'); console.error('Fetch error:', err); } finally { setLoading(false); } }; useEffect(() => { fetchFilaments(); }, []); // Sorting logic const handleSort = (field: string) => { if (sortField === field) { setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortOrder('asc'); } }; const sortedFilaments = [...filaments].sort((a, b) => { if (!sortField) return 0; let aVal = a[sortField as keyof FilamentWithId]; let bVal = b[sortField as keyof FilamentWithId]; // Handle null/undefined values if (aVal === null || aVal === undefined) aVal = ''; if (bVal === null || bVal === undefined) bVal = ''; // Convert to strings for comparison aVal = String(aVal).toLowerCase(); bVal = String(bVal).toLowerCase(); if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1; if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1; return 0; }); const handleSave = async (filament: Partial) => { try { if (filament.id) { await filamentService.update(filament.id, filament); } else { await filamentService.create(filament); } setEditingFilament(null); setShowAddForm(false); fetchFilaments(); } catch (err) { setError('Greška pri čuvanju filamenata'); console.error('Save error:', err); } }; const handleDelete = async (id: string) => { if (!confirm('Da li ste sigurni da želite obrisati ovaj filament?')) { return; } try { await filamentService.delete(id); fetchFilaments(); } catch (err) { setError('Greška pri brisanju filamenata'); console.error('Delete error:', err); } }; const handleLogout = () => { localStorage.removeItem('authToken'); localStorage.removeItem('tokenExpiry'); router.push('/upadaj'); }; if (loading) { return
Učitavanje...
; } return (
{/* Main Content - Full Screen */}

Admin

{!showAddForm && !editingFilament && ( )} {mounted && ( )}
{error && (
{error}
)} {/* Add/Edit Form */} {(showAddForm || editingFilament) && (
{ setEditingFilament(null); setShowAddForm(false); }} />
)} {/* Filaments Table */}
{sortedFilaments.map((filament) => ( ))}
handleSort('tip')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"> Tip {sortField === 'tip' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('finish')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"> Finish {sortField === 'finish' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('boja')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"> Boja {sortField === 'boja' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('refill')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"> Refill {sortField === 'refill' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('vakum')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"> Vakuum {sortField === 'vakum' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('otvoreno')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"> Otvoreno {sortField === 'otvoreno' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('kolicina')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"> Količina {sortField === 'kolicina' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('cena')} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"> Cena {sortField === 'cena' && (sortOrder === 'asc' ? '↑' : '↓')} Akcije
{filament.tip} {filament.finish}
{filament.bojaHex && (
)} {filament.boja}
{(() => { const refillCount = parseInt(filament.refill) || 0; if (refillCount > 0) { return {refillCount}; } return 0; })()} {(() => { const match = filament.vakum?.match(/^(\d+)\s*vakuum/); const vakuumCount = match ? parseInt(match[1]) : 0; if (vakuumCount > 0) { return {vakuumCount}; } return 0; })()} {(() => { const match = filament.otvoreno?.match(/^(\d+)\s*otvorena/); const otvorenCount = match ? parseInt(match[1]) : 0; if (otvorenCount > 0) { return {otvorenCount}; } return 0; })()} {filament.kolicina} {filament.cena}
); } // Filament Form Component function FilamentForm({ filament, filaments, onSave, onCancel }: { filament: Partial, filaments: FilamentWithId[], onSave: (filament: Partial) => void, onCancel: () => void }) { const [formData, setFormData] = useState({ tip: filament.tip || '', finish: filament.finish || '', boja: filament.boja || '', bojaHex: filament.bojaHex || '', refill: filament.refill || '', vakum: filament.vakum || '', otvoreno: filament.otvoreno || '', kolicina: filament.kolicina || '', cena: filament.cena || (filament.id ? '' : '3999'), // Default price for new filaments }); const [availableColors, setAvailableColors] = useState>([]); // Load colors from API useEffect(() => { const loadColors = async () => { try { const colors = await colorService.getAll(); setAvailableColors(colors.sort((a: any, b: any) => a.name.localeCompare(b.name))); } catch (error) { console.error('Error loading colors:', error); // Fallback to colors from existing filaments const existingColors = [...new Set(filaments.map(f => f.boja).filter(Boolean))]; const colorObjects = existingColors.map((color, idx) => ({ id: `existing-${idx}`, name: color, hex: filaments.find(f => f.boja === color)?.bojaHex || '#000000' })); setAvailableColors(colorObjects.sort((a, b) => a.name.localeCompare(b.name))); } }; loadColors(); // Reload colors when window gets focus (in case user added colors in another tab) const handleFocus = () => loadColors(); window.addEventListener('focus', handleFocus); return () => window.removeEventListener('focus', handleFocus); }, [filaments]); // Update form when filament prop changes useEffect(() => { setFormData({ tip: filament.tip || '', finish: filament.finish || '', boja: filament.boja || '', bojaHex: filament.bojaHex || '', refill: filament.refill || '', vakum: filament.vakum || '', otvoreno: filament.otvoreno || '', kolicina: filament.kolicina || '', cena: filament.cena || (filament.id ? '' : '3999'), // Default price for new filaments }); }, [filament]); const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; if (name === 'refill') { // Auto-set price based on refill quantity const refillCount = parseInt(value) || 0; setFormData({ ...formData, [name]: value, // Auto-fill price based on refill status if this is a new filament ...(filament.id ? {} : { cena: refillCount > 0 ? '3499' : '3999' }) }); } else { setFormData({ ...formData, [name]: value }); } }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); onSave({ ...filament, ...formData }); }; return (

{filament.id ? 'Izmeni filament' : 'Dodaj novi filament'}

{formData.bojaHex && ( {formData.bojaHex} )}
{/* Quantity inputs for refill, vakuum, and otvoreno */}
{ if (!formData.refill) return 0; const num = parseInt(formData.refill); return isNaN(num) ? (formData.refill.toLowerCase() === 'da' ? 1 : 0) : num; })()} onChange={(e) => { const value = parseInt(e.target.value) || 0; handleChange({ target: { name: 'refill', value: value > 0 ? value.toString() : 'Ne' } } as any); }} min="0" step="1" placeholder="0" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500" />
{ if (!formData.vakum) return 0; const match = formData.vakum.match(/^(\d+)\s*vakuum/); if (match) return parseInt(match[1]); return formData.vakum.toLowerCase().includes('vakuum') ? 1 : 0; })()} onChange={(e) => { const value = parseInt(e.target.value) || 0; handleChange({ target: { name: 'vakum', value: value > 0 ? `${value} vakuum` : 'Ne' } } as any); }} min="0" step="1" placeholder="0" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500" />
{ if (!formData.otvoreno) return 0; const match = formData.otvoreno.match(/^(\d+)\s*otvorena/); if (match) return parseInt(match[1]); return formData.otvoreno.toLowerCase().includes('otvorena') ? 1 : 0; })()} onChange={(e) => { const value = parseInt(e.target.value) || 0; handleChange({ target: { name: 'otvoreno', value: value > 0 ? `${value} otvorena` : 'Ne' } } as any); }} min="0" step="1" placeholder="0" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500" />
); }