Align catalog with Bambu Lab product line, add conditional filters and admin sidebar
All checks were successful
Deploy / deploy (push) Successful in 2m24s
All checks were successful
Deploy / deploy (push) Successful in 2m24s
- Add master catalog (bambuLabCatalog.ts) as single source of truth for materials, finishes, colors, and refill/spool availability - Fix incorrect finish-per-material mappings (remove PLA: 85A/90A/95A HF/FR/GF/HF, add ASA: Basic/CF/Aero, fix PETG/PC) - Implement cascading filters on public site: material restricts finish, finish restricts color - Add AdminSidebar component across all admin pages - Redirect /upadaj to /dashboard when already authenticated - Update color hex mappings and tests to match official Bambu Lab names
This commit is contained in:
@@ -7,70 +7,40 @@ import { Filament } from '@/src/types/filament';
|
||||
import { trackEvent } from '@/src/components/MatomoAnalytics';
|
||||
import { SaleManager } from '@/src/components/SaleManager';
|
||||
import { BulkFilamentPriceEditor } from '@/src/components/BulkFilamentPriceEditor';
|
||||
// Removed unused imports for Bambu Lab color categorization
|
||||
import { AdminSidebar } from '@/src/components/AdminSidebar';
|
||||
import {
|
||||
getFinishesForMaterial,
|
||||
getColorsForMaterialFinish,
|
||||
catalogIsSpoolOnly,
|
||||
catalogIsRefillOnly,
|
||||
getMaterialOptions,
|
||||
} from '@/src/data/bambuLabCatalog';
|
||||
import '@/src/styles/select.css';
|
||||
|
||||
// Colors that only come as refills (no spools)
|
||||
const REFILL_ONLY_COLORS = [
|
||||
'Beige',
|
||||
'Light Gray',
|
||||
'Yellow',
|
||||
'Orange',
|
||||
'Gold',
|
||||
'Bright Green',
|
||||
'Pink',
|
||||
'Magenta',
|
||||
'Maroon Red',
|
||||
'Purple',
|
||||
'Turquoise',
|
||||
'Cobalt Blue',
|
||||
'Brown',
|
||||
'Bronze',
|
||||
'Silver',
|
||||
'Blue Grey',
|
||||
'Dark Gray'
|
||||
];
|
||||
|
||||
// Helper function to check if a filament is spool-only
|
||||
const isSpoolOnly = (finish?: string, type?: string): boolean => {
|
||||
return finish === 'Translucent' || finish === 'Metal' || finish === 'Silk+' || finish === 'Wood' || (type === 'PPA' && finish === 'CF') || type === 'PA6' || type === 'PC';
|
||||
// Catalog-driven helpers
|
||||
const isSpoolOnly = (finish?: string, type?: string, color?: string): boolean => {
|
||||
if (!type || !finish) return false;
|
||||
if (color) return catalogIsSpoolOnly(type, finish, color);
|
||||
// If no specific color, check if ALL colors in this finish are spool-only
|
||||
const colors = getColorsForMaterialFinish(type, finish);
|
||||
return colors.length > 0 && colors.every(c => c.spool && !c.refill);
|
||||
};
|
||||
|
||||
// Helper function to check if a filament should be refill-only
|
||||
const isRefillOnly = (color: string, finish?: string, type?: string): boolean => {
|
||||
// If the finish/type combination is spool-only, then it's not refill-only
|
||||
if (isSpoolOnly(finish, type)) {
|
||||
return false;
|
||||
}
|
||||
// Translucent finish always has spool option
|
||||
if (finish === 'Translucent') {
|
||||
return false;
|
||||
}
|
||||
// Specific type/finish/color combinations that are refill-only
|
||||
if (type === 'ABS' && finish === 'GF' && (color === 'Yellow' || color === 'Orange')) {
|
||||
return true;
|
||||
}
|
||||
if (type === 'TPU' && finish === '95A HF') {
|
||||
return true;
|
||||
}
|
||||
// All colors starting with "Matte " prefix are refill-only
|
||||
if (color.startsWith('Matte ')) {
|
||||
return true;
|
||||
}
|
||||
// Galaxy and Basic colors have spools available (not refill-only)
|
||||
if (finish === 'Galaxy' || finish === 'Basic') {
|
||||
return false;
|
||||
}
|
||||
return REFILL_ONLY_COLORS.includes(color);
|
||||
if (!type || !finish || !color) return false;
|
||||
return catalogIsRefillOnly(type, finish, color);
|
||||
};
|
||||
|
||||
// Helper function to filter colors based on material and finish
|
||||
const getFilteredColors = (colors: Array<{id: string, name: string, hex: string, cena_refill?: number, cena_spulna?: number}>, type?: string, finish?: string) => {
|
||||
// PPA CF only has black color
|
||||
if (type === 'PPA' && finish === 'CF') {
|
||||
return colors.filter(color => color.name.toLowerCase() === 'black');
|
||||
}
|
||||
return colors;
|
||||
const getFilteredColors = (
|
||||
colors: Array<{id: string, name: string, hex: string, cena_refill?: number, cena_spulna?: number}>,
|
||||
type?: string,
|
||||
finish?: string
|
||||
) => {
|
||||
if (!type || !finish) return colors;
|
||||
const catalogColors = getColorsForMaterialFinish(type, finish);
|
||||
if (catalogColors.length === 0) return colors;
|
||||
const catalogNames = new Set(catalogColors.map(c => c.name.toLowerCase()));
|
||||
return colors.filter(c => catalogNames.has(c.name.toLowerCase()));
|
||||
};
|
||||
|
||||
interface FilamentWithId extends Filament {
|
||||
@@ -80,22 +50,6 @@ interface FilamentWithId extends Filament {
|
||||
boja_hex?: string;
|
||||
}
|
||||
|
||||
// Finish options by filament type
|
||||
const FINISH_OPTIONS_BY_TYPE: Record<string, string[]> = {
|
||||
'ABS': ['GF', 'Bez Finisha'],
|
||||
'PLA': ['85A', '90A', '95A HF', 'Aero', 'Basic', 'Basic Gradient', 'CF', 'FR', 'Galaxy', 'GF', 'Glow', 'HF', 'Marble', 'Matte', 'Metal', 'Silk Multi-Color', 'Silk+', 'Sparkle', 'Tough+', 'Translucent', 'Wood'],
|
||||
'TPU': ['85A', '90A', '95A HF'],
|
||||
'PETG': ['Basic', 'CF', 'FR', 'HF', 'Translucent'],
|
||||
'PC': ['CF', 'FR', 'Bez Finisha'],
|
||||
'ASA': ['Bez Finisha'],
|
||||
'PA': ['CF', 'GF', 'Bez Finisha'],
|
||||
'PA6': ['CF', 'GF'],
|
||||
'PAHT': ['CF', 'Bez Finisha'],
|
||||
'PPA': ['CF'],
|
||||
'PVA': ['Bez Finisha'],
|
||||
'HIPS': ['Bez Finisha']
|
||||
};
|
||||
|
||||
export default function AdminDashboard() {
|
||||
const router = useRouter();
|
||||
const [filaments, setFilaments] = useState<FilamentWithId[]>([]);
|
||||
@@ -360,8 +314,10 @@ export default function AdminDashboard() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
||||
{/* Main Content - Full Screen */}
|
||||
<div className="w-full">
|
||||
<div className="flex">
|
||||
<AdminSidebar />
|
||||
{/* Main Content */}
|
||||
<div className="flex-1">
|
||||
<header className="bg-white dark:bg-gray-800 shadow transition-colors">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 lg:py-6">
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
@@ -401,24 +357,6 @@ export default function AdminDashboard() {
|
||||
filaments={filaments}
|
||||
onUpdate={fetchFilaments}
|
||||
/>
|
||||
<button
|
||||
onClick={() => router.push('/upadaj/colors')}
|
||||
className="flex-1 sm:flex-initial px-3 sm:px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600 text-sm sm:text-base"
|
||||
>
|
||||
Boje
|
||||
</button>
|
||||
<button
|
||||
onClick={() => router.push('/upadaj/requests')}
|
||||
className="flex-1 sm:flex-initial px-3 sm:px-4 py-2 bg-indigo-500 text-white rounded hover:bg-indigo-600 text-sm sm:text-base"
|
||||
>
|
||||
Zahtevi
|
||||
</button>
|
||||
<button
|
||||
onClick={() => router.push('/')}
|
||||
className="flex-1 sm:flex-initial px-3 sm:px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 text-sm sm:text-base"
|
||||
>
|
||||
Nazad na sajt
|
||||
</button>
|
||||
{mounted && (
|
||||
<button
|
||||
onClick={() => setDarkMode(!darkMode)}
|
||||
@@ -686,6 +624,7 @@ export default function AdminDashboard() {
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -710,7 +649,7 @@ function FilamentForm({
|
||||
finish: filament.finish || (filament.id ? '' : 'Basic'), // Default to Basic for new filaments
|
||||
boja: filament.boja || '',
|
||||
boja_hex: filament.boja_hex || '',
|
||||
refill: isSpoolOnly(filament.finish, filament.tip) ? 0 : (filament.refill || 0), // Store as number
|
||||
refill: isSpoolOnly(filament.finish, filament.tip, filament.boja) ? 0 : (filament.refill || 0), // Store as number
|
||||
spulna: isRefillOnly(filament.boja || '', filament.finish, filament.tip) ? 0 : (filament.spulna || 0), // Store as number
|
||||
kolicina: filament.kolicina || 0, // Default to 0, stored as number
|
||||
cena: '', // Price is now determined by color selection
|
||||
@@ -787,9 +726,9 @@ function FilamentForm({
|
||||
});
|
||||
} else if (name === 'tip') {
|
||||
// If changing filament type, reset finish if it's not compatible
|
||||
const newTypeFinishes = FINISH_OPTIONS_BY_TYPE[value] || [];
|
||||
const newTypeFinishes = getFinishesForMaterial(value);
|
||||
const resetFinish = !newTypeFinishes.includes(formData.finish);
|
||||
const spoolOnly = isSpoolOnly(formData.finish, value);
|
||||
const spoolOnly = isSpoolOnly(formData.finish, value, formData.boja);
|
||||
// If changing to PPA with CF finish and current color is not black, reset color
|
||||
const needsColorReset = value === 'PPA' && formData.finish === 'CF' && formData.boja.toLowerCase() !== 'black';
|
||||
setFormData({
|
||||
@@ -810,7 +749,7 @@ function FilamentForm({
|
||||
} else if (name === 'finish') {
|
||||
// If changing to Translucent finish, enable spool option and disable refill
|
||||
const refillOnly = isRefillOnly(formData.boja, value, formData.tip);
|
||||
const spoolOnly = isSpoolOnly(value, formData.tip);
|
||||
const spoolOnly = isSpoolOnly(value, formData.tip, formData.boja);
|
||||
// If changing to PPA CF and current color is not black, reset color
|
||||
const needsColorReset = formData.tip === 'PPA' && value === 'CF' && formData.boja.toLowerCase() !== 'black';
|
||||
setFormData({
|
||||
@@ -887,17 +826,9 @@ function FilamentForm({
|
||||
className="custom-select 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"
|
||||
>
|
||||
<option value="">Izaberi tip</option>
|
||||
<option value="ABS">ABS</option>
|
||||
<option value="ASA">ASA</option>
|
||||
<option value="PA6">PA6</option>
|
||||
<option value="PAHT">PAHT</option>
|
||||
<option value="PC">PC</option>
|
||||
<option value="PET">PET</option>
|
||||
<option value="PETG">PETG</option>
|
||||
<option value="PLA">PLA</option>
|
||||
<option value="PPA">PPA</option>
|
||||
<option value="PPS">PPS</option>
|
||||
<option value="TPU">TPU</option>
|
||||
{getMaterialOptions().map(mat => (
|
||||
<option key={mat} value={mat}>{mat}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -911,7 +842,7 @@ function FilamentForm({
|
||||
className="custom-select 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"
|
||||
>
|
||||
<option value="">Izaberi finiš</option>
|
||||
{(FINISH_OPTIONS_BY_TYPE[formData.tip] || []).map(finish => (
|
||||
{getFinishesForMaterial(formData.tip).map(finish => (
|
||||
<option key={finish} value={finish}>{finish}</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -991,9 +922,9 @@ function FilamentForm({
|
||||
min="0"
|
||||
step="1"
|
||||
placeholder="3499"
|
||||
disabled={isSpoolOnly(formData.finish, formData.tip)}
|
||||
disabled={isSpoolOnly(formData.finish, formData.tip, formData.boja)}
|
||||
className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md ${
|
||||
isSpoolOnly(formData.finish, formData.tip)
|
||||
isSpoolOnly(formData.finish, formData.tip, formData.boja)
|
||||
? 'bg-gray-100 dark:bg-gray-600 cursor-not-allowed'
|
||||
: 'bg-white dark:bg-gray-700'
|
||||
} text-green-600 dark:text-green-400 font-bold focus:outline-none focus:ring-2 focus:ring-green-500`}
|
||||
@@ -1025,7 +956,7 @@ function FilamentForm({
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">
|
||||
Refil
|
||||
{isSpoolOnly(formData.finish, formData.tip) && (
|
||||
{isSpoolOnly(formData.finish, formData.tip, formData.boja) && (
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 ml-2">(samo špulna postoji)</span>
|
||||
)}
|
||||
</label>
|
||||
@@ -1037,9 +968,9 @@ function FilamentForm({
|
||||
min="0"
|
||||
step="1"
|
||||
placeholder="0"
|
||||
disabled={isSpoolOnly(formData.finish, formData.tip)}
|
||||
disabled={isSpoolOnly(formData.finish, formData.tip, formData.boja)}
|
||||
className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md ${
|
||||
isSpoolOnly(formData.finish, formData.tip)
|
||||
isSpoolOnly(formData.finish, formData.tip, formData.boja)
|
||||
? 'bg-gray-100 dark:bg-gray-600 cursor-not-allowed'
|
||||
: 'bg-white dark:bg-gray-700'
|
||||
} text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500`}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
|
||||
import { analyticsService } from '@/src/services/api';
|
||||
import type { AnalyticsOverview, TopSeller, RevenueDataPoint, InventoryAlert, TypeBreakdown } from '@/src/types/sales';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, PieChart, Pie, Cell, Legend } from 'recharts';
|
||||
import { AdminSidebar } from '@/src/components/AdminSidebar';
|
||||
|
||||
type Period = '7d' | '30d' | '90d' | '6m' | '1y' | 'all';
|
||||
|
||||
@@ -127,20 +128,7 @@ export default function AnalyticsDashboard() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
||||
<div className="flex">
|
||||
{/* Sidebar */}
|
||||
<div className="w-64 bg-white dark:bg-gray-800 shadow-lg h-screen sticky top-0">
|
||||
<div className="p-6">
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-6">Admin Panel</h2>
|
||||
<nav className="space-y-2">
|
||||
<a href="/dashboard" className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Filamenti</a>
|
||||
<a href="/upadaj/colors" className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Boje</a>
|
||||
<a href="/upadaj/requests" className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Zahtevi za boje</a>
|
||||
<a href="/upadaj/sales" className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Prodaja</a>
|
||||
<a href="/upadaj/customers" className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Kupci</a>
|
||||
<a href="/upadaj/analytics" className="block px-4 py-2 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">Analitika</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<AdminSidebar />
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
|
||||
import { colorService } from '@/src/services/api';
|
||||
import { bambuLabColors, getColorHex } from '@/src/data/bambuLabColorsComplete';
|
||||
import { BulkPriceEditor } from '@/src/components/BulkPriceEditor';
|
||||
import { AdminSidebar } from '@/src/components/AdminSidebar';
|
||||
import '@/src/styles/select.css';
|
||||
|
||||
interface Color {
|
||||
@@ -188,20 +189,7 @@ export default function ColorsManagement() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
||||
<div className="flex">
|
||||
{/* Sidebar */}
|
||||
<div className="w-64 bg-white dark:bg-gray-800 shadow-lg h-screen">
|
||||
<div className="p-6">
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-6">Admin Panel</h2>
|
||||
<nav className="space-y-2">
|
||||
<a href="/dashboard" className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Filamenti</a>
|
||||
<a href="/upadaj/colors" className="block px-4 py-2 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">Boje</a>
|
||||
<a href="/upadaj/requests" className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Zahtevi za boje</a>
|
||||
<a href="/upadaj/sales" className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Prodaja</a>
|
||||
<a href="/upadaj/customers" className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Kupci</a>
|
||||
<a href="/upadaj/analytics" className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Analitika</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<AdminSidebar />
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { customerService } from '@/src/services/api';
|
||||
import { Customer, Sale } from '@/src/types/sales';
|
||||
import { AdminSidebar } from '@/src/components/AdminSidebar';
|
||||
|
||||
interface CustomerWithSales extends Customer {
|
||||
sales?: Sale[];
|
||||
@@ -172,50 +173,7 @@ export default function CustomersManagement() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
||||
<div className="flex">
|
||||
{/* Sidebar */}
|
||||
<div className="w-64 bg-white dark:bg-gray-800 shadow-lg h-screen sticky top-0">
|
||||
<div className="p-6">
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-6">Admin Panel</h2>
|
||||
<nav className="space-y-2">
|
||||
<a
|
||||
href="/dashboard"
|
||||
className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
Filamenti
|
||||
</a>
|
||||
<a
|
||||
href="/upadaj/colors"
|
||||
className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
Boje
|
||||
</a>
|
||||
<a
|
||||
href="/upadaj/requests"
|
||||
className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
Zahtevi za boje
|
||||
</a>
|
||||
<a
|
||||
href="/upadaj/sales"
|
||||
className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
Prodaja
|
||||
</a>
|
||||
<a
|
||||
href="/upadaj/customers"
|
||||
className="block px-4 py-2 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded"
|
||||
>
|
||||
Kupci
|
||||
</a>
|
||||
<a
|
||||
href="/upadaj/analytics"
|
||||
className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
Analitika
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<AdminSidebar />
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1">
|
||||
|
||||
@@ -12,6 +12,15 @@ export default function AdminLogin() {
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Redirect to dashboard if already authenticated
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const expiry = localStorage.getItem('tokenExpiry');
|
||||
if (token && expiry && Date.now() < parseInt(expiry)) {
|
||||
window.location.href = '/dashboard';
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Set dark mode by default
|
||||
useEffect(() => {
|
||||
document.documentElement.classList.add('dark');
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { colorRequestService } from '@/src/services/api';
|
||||
import Link from 'next/link';
|
||||
import { AdminSidebar } from '@/src/components/AdminSidebar';
|
||||
|
||||
interface ColorRequest {
|
||||
id: string;
|
||||
@@ -120,16 +121,11 @@ export default function ColorRequestsAdmin() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="flex">
|
||||
<AdminSidebar />
|
||||
<div className="flex-1 p-8">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-3xl font-bold text-gray-800 dark:text-gray-100">Zahtevi za Boje</h1>
|
||||
<div className="space-x-4 flex flex-wrap gap-2">
|
||||
<Link href="/dashboard" className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">Inventar</Link>
|
||||
<Link href="/upadaj/colors" className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">Boje</Link>
|
||||
<Link href="/upadaj/sales" className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">Prodaja</Link>
|
||||
<Link href="/upadaj/customers" className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">Kupci</Link>
|
||||
<Link href="/upadaj/analytics" className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700">Analitika</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
@@ -347,6 +343,7 @@ export default function ColorRequestsAdmin() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { saleService, customerService, filamentService, colorService } from '@/src/services/api';
|
||||
import { Customer, Sale, SaleItem, CreateSaleRequest } from '@/src/types/sales';
|
||||
import { Filament } from '@/src/types/filament';
|
||||
import { AdminSidebar } from '@/src/components/AdminSidebar';
|
||||
|
||||
interface LineItem {
|
||||
filament_id: string;
|
||||
@@ -120,50 +121,7 @@ export default function SalesPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex">
|
||||
{/* Sidebar */}
|
||||
<div className="w-64 bg-white dark:bg-gray-800 shadow-lg h-screen sticky top-0">
|
||||
<div className="p-6">
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-6">Admin Panel</h2>
|
||||
<nav className="space-y-2">
|
||||
<a
|
||||
href="/dashboard"
|
||||
className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
Filamenti
|
||||
</a>
|
||||
<a
|
||||
href="/upadaj/colors"
|
||||
className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
Boje
|
||||
</a>
|
||||
<a
|
||||
href="/upadaj/requests"
|
||||
className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
Zahtevi za boje
|
||||
</a>
|
||||
<a
|
||||
href="/upadaj/sales"
|
||||
className="block px-4 py-2 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded"
|
||||
>
|
||||
Prodaja
|
||||
</a>
|
||||
<a
|
||||
href="/upadaj/customers"
|
||||
className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
Kupci
|
||||
</a>
|
||||
<a
|
||||
href="/upadaj/analytics"
|
||||
className="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
Analitika
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<AdminSidebar />
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 p-8">
|
||||
|
||||
Reference in New Issue
Block a user