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:
@@ -15,15 +15,15 @@ describe('Bambu Lab Colors Complete Data', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have matte colors', () => {
|
it('should have matte colors', () => {
|
||||||
expect(bambuLabColors).toHaveProperty('Matte Black');
|
expect(bambuLabColors).toHaveProperty('Matte Ivory White');
|
||||||
expect(bambuLabColors).toHaveProperty('Matte White');
|
expect(bambuLabColors).toHaveProperty('Matte Charcoal');
|
||||||
expect(bambuLabColors).toHaveProperty('Matte Red');
|
expect(bambuLabColors).toHaveProperty('Matte Scarlet Red');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have silk colors', () => {
|
it('should have silk colors', () => {
|
||||||
expect(bambuLabColors).toHaveProperty('Silk White');
|
expect(bambuLabColors).toHaveProperty('Silk Aurora Purple');
|
||||||
expect(bambuLabColors).toHaveProperty('Silk Black');
|
expect(bambuLabColors).toHaveProperty('Silk Phantom Blue');
|
||||||
expect(bambuLabColors).toHaveProperty('Silk Gold');
|
expect(bambuLabColors).toHaveProperty('Silk Mystic Magenta');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have valid hex colors', () => {
|
it('should have valid hex colors', () => {
|
||||||
@@ -40,12 +40,12 @@ describe('Bambu Lab Colors Complete Data', () => {
|
|||||||
it('should have finish categories', () => {
|
it('should have finish categories', () => {
|
||||||
expect(colorsByFinish).toHaveProperty('Basic');
|
expect(colorsByFinish).toHaveProperty('Basic');
|
||||||
expect(colorsByFinish).toHaveProperty('Matte');
|
expect(colorsByFinish).toHaveProperty('Matte');
|
||||||
expect(colorsByFinish).toHaveProperty('Silk');
|
expect(colorsByFinish).toHaveProperty('Silk+');
|
||||||
expect(colorsByFinish).toHaveProperty('Metal');
|
expect(colorsByFinish).toHaveProperty('Metal');
|
||||||
expect(colorsByFinish).toHaveProperty('Sparkle');
|
expect(colorsByFinish).toHaveProperty('Sparkle');
|
||||||
expect(colorsByFinish).toHaveProperty('Glow');
|
expect(colorsByFinish).toHaveProperty('Glow');
|
||||||
expect(colorsByFinish).toHaveProperty('Transparent');
|
expect(colorsByFinish).toHaveProperty('Translucent');
|
||||||
expect(colorsByFinish).toHaveProperty('Support');
|
expect(colorsByFinish).toHaveProperty('CF');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return valid hex for getColorHex', () => {
|
it('should return valid hex for getColorHex', () => {
|
||||||
|
|||||||
@@ -47,20 +47,17 @@ describe('UI Features Tests', () => {
|
|||||||
const adminDashboardPath = join(process.cwd(), 'app', 'dashboard', 'page.tsx');
|
const adminDashboardPath = join(process.cwd(), 'app', 'dashboard', 'page.tsx');
|
||||||
const adminContent = readFileSync(adminDashboardPath, 'utf-8');
|
const adminContent = readFileSync(adminDashboardPath, 'utf-8');
|
||||||
|
|
||||||
// Check for material select dropdown
|
// Check for material select dropdown (now generated from catalog)
|
||||||
expect(adminContent).toContain('<option value="PLA">PLA</option>');
|
expect(adminContent).toContain('getMaterialOptions()');
|
||||||
expect(adminContent).toContain('<option value="PETG">PETG</option>');
|
expect(adminContent).toContain('Izaberi tip');
|
||||||
expect(adminContent).toContain('<option value="ABS">ABS</option>');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have admin header with navigation', () => {
|
it('should have admin sidebar and header', () => {
|
||||||
const adminDashboardPath = join(process.cwd(), 'app', 'dashboard', 'page.tsx');
|
const adminDashboardPath = join(process.cwd(), 'app', 'dashboard', 'page.tsx');
|
||||||
|
|
||||||
const dashboardContent = readFileSync(adminDashboardPath, 'utf-8');
|
const dashboardContent = readFileSync(adminDashboardPath, 'utf-8');
|
||||||
|
|
||||||
// Check for admin header
|
// Check for admin sidebar and header
|
||||||
expect(dashboardContent).toContain('Admin');
|
expect(dashboardContent).toContain('AdminSidebar');
|
||||||
expect(dashboardContent).toContain('Nazad na sajt');
|
|
||||||
expect(dashboardContent).toContain('Odjava');
|
expect(dashboardContent).toContain('Odjava');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,70 +7,40 @@ import { Filament } from '@/src/types/filament';
|
|||||||
import { trackEvent } from '@/src/components/MatomoAnalytics';
|
import { trackEvent } from '@/src/components/MatomoAnalytics';
|
||||||
import { SaleManager } from '@/src/components/SaleManager';
|
import { SaleManager } from '@/src/components/SaleManager';
|
||||||
import { BulkFilamentPriceEditor } from '@/src/components/BulkFilamentPriceEditor';
|
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';
|
import '@/src/styles/select.css';
|
||||||
|
|
||||||
// Colors that only come as refills (no spools)
|
// Catalog-driven helpers
|
||||||
const REFILL_ONLY_COLORS = [
|
const isSpoolOnly = (finish?: string, type?: string, color?: string): boolean => {
|
||||||
'Beige',
|
if (!type || !finish) return false;
|
||||||
'Light Gray',
|
if (color) return catalogIsSpoolOnly(type, finish, color);
|
||||||
'Yellow',
|
// If no specific color, check if ALL colors in this finish are spool-only
|
||||||
'Orange',
|
const colors = getColorsForMaterialFinish(type, finish);
|
||||||
'Gold',
|
return colors.length > 0 && colors.every(c => c.spool && !c.refill);
|
||||||
'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';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to check if a filament should be refill-only
|
|
||||||
const isRefillOnly = (color: string, finish?: string, type?: string): boolean => {
|
const isRefillOnly = (color: string, finish?: string, type?: string): boolean => {
|
||||||
// If the finish/type combination is spool-only, then it's not refill-only
|
if (!type || !finish || !color) return false;
|
||||||
if (isSpoolOnly(finish, type)) {
|
return catalogIsRefillOnly(type, finish, color);
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to filter colors based on material and finish
|
const getFilteredColors = (
|
||||||
const getFilteredColors = (colors: Array<{id: string, name: string, hex: string, cena_refill?: number, cena_spulna?: number}>, type?: string, finish?: string) => {
|
colors: Array<{id: string, name: string, hex: string, cena_refill?: number, cena_spulna?: number}>,
|
||||||
// PPA CF only has black color
|
type?: string,
|
||||||
if (type === 'PPA' && finish === 'CF') {
|
finish?: string
|
||||||
return colors.filter(color => color.name.toLowerCase() === 'black');
|
) => {
|
||||||
}
|
if (!type || !finish) return colors;
|
||||||
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 {
|
interface FilamentWithId extends Filament {
|
||||||
@@ -80,22 +50,6 @@ interface FilamentWithId extends Filament {
|
|||||||
boja_hex?: string;
|
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() {
|
export default function AdminDashboard() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [filaments, setFilaments] = useState<FilamentWithId[]>([]);
|
const [filaments, setFilaments] = useState<FilamentWithId[]>([]);
|
||||||
@@ -360,8 +314,10 @@ export default function AdminDashboard() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
||||||
{/* Main Content - Full Screen */}
|
<div className="flex">
|
||||||
<div className="w-full">
|
<AdminSidebar />
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="flex-1">
|
||||||
<header className="bg-white dark:bg-gray-800 shadow transition-colors">
|
<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="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">
|
<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}
|
filaments={filaments}
|
||||||
onUpdate={fetchFilaments}
|
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 && (
|
{mounted && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setDarkMode(!darkMode)}
|
onClick={() => setDarkMode(!darkMode)}
|
||||||
@@ -688,6 +626,7 @@ export default function AdminDashboard() {
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,7 +649,7 @@ function FilamentForm({
|
|||||||
finish: filament.finish || (filament.id ? '' : 'Basic'), // Default to Basic for new filaments
|
finish: filament.finish || (filament.id ? '' : 'Basic'), // Default to Basic for new filaments
|
||||||
boja: filament.boja || '',
|
boja: filament.boja || '',
|
||||||
boja_hex: filament.boja_hex || '',
|
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
|
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
|
kolicina: filament.kolicina || 0, // Default to 0, stored as number
|
||||||
cena: '', // Price is now determined by color selection
|
cena: '', // Price is now determined by color selection
|
||||||
@@ -787,9 +726,9 @@ function FilamentForm({
|
|||||||
});
|
});
|
||||||
} else if (name === 'tip') {
|
} else if (name === 'tip') {
|
||||||
// If changing filament type, reset finish if it's not compatible
|
// 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 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
|
// 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';
|
const needsColorReset = value === 'PPA' && formData.finish === 'CF' && formData.boja.toLowerCase() !== 'black';
|
||||||
setFormData({
|
setFormData({
|
||||||
@@ -810,7 +749,7 @@ function FilamentForm({
|
|||||||
} else if (name === 'finish') {
|
} else if (name === 'finish') {
|
||||||
// If changing to Translucent finish, enable spool option and disable refill
|
// If changing to Translucent finish, enable spool option and disable refill
|
||||||
const refillOnly = isRefillOnly(formData.boja, value, formData.tip);
|
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
|
// If changing to PPA CF and current color is not black, reset color
|
||||||
const needsColorReset = formData.tip === 'PPA' && value === 'CF' && formData.boja.toLowerCase() !== 'black';
|
const needsColorReset = formData.tip === 'PPA' && value === 'CF' && formData.boja.toLowerCase() !== 'black';
|
||||||
setFormData({
|
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"
|
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="">Izaberi tip</option>
|
||||||
<option value="ABS">ABS</option>
|
{getMaterialOptions().map(mat => (
|
||||||
<option value="ASA">ASA</option>
|
<option key={mat} value={mat}>{mat}</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>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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"
|
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>
|
<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>
|
<option key={finish} value={finish}>{finish}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -991,9 +922,9 @@ function FilamentForm({
|
|||||||
min="0"
|
min="0"
|
||||||
step="1"
|
step="1"
|
||||||
placeholder="3499"
|
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 ${
|
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-gray-100 dark:bg-gray-600 cursor-not-allowed'
|
||||||
: 'bg-white dark:bg-gray-700'
|
: '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`}
|
} 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>
|
<div>
|
||||||
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">
|
||||||
Refil
|
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>
|
<span className="text-xs text-gray-500 dark:text-gray-400 ml-2">(samo špulna postoji)</span>
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
@@ -1037,9 +968,9 @@ function FilamentForm({
|
|||||||
min="0"
|
min="0"
|
||||||
step="1"
|
step="1"
|
||||||
placeholder="0"
|
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 ${
|
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-gray-100 dark:bg-gray-600 cursor-not-allowed'
|
||||||
: 'bg-white dark:bg-gray-700'
|
: 'bg-white dark:bg-gray-700'
|
||||||
} text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500`}
|
} 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 { analyticsService } from '@/src/services/api';
|
||||||
import type { AnalyticsOverview, TopSeller, RevenueDataPoint, InventoryAlert, TypeBreakdown } from '@/src/types/sales';
|
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 { 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';
|
type Period = '7d' | '30d' | '90d' | '6m' | '1y' | 'all';
|
||||||
|
|
||||||
@@ -127,20 +128,7 @@ export default function AnalyticsDashboard() {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{/* Sidebar */}
|
<AdminSidebar />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { colorService } from '@/src/services/api';
|
import { colorService } from '@/src/services/api';
|
||||||
import { bambuLabColors, getColorHex } from '@/src/data/bambuLabColorsComplete';
|
import { bambuLabColors, getColorHex } from '@/src/data/bambuLabColorsComplete';
|
||||||
import { BulkPriceEditor } from '@/src/components/BulkPriceEditor';
|
import { BulkPriceEditor } from '@/src/components/BulkPriceEditor';
|
||||||
|
import { AdminSidebar } from '@/src/components/AdminSidebar';
|
||||||
import '@/src/styles/select.css';
|
import '@/src/styles/select.css';
|
||||||
|
|
||||||
interface Color {
|
interface Color {
|
||||||
@@ -188,20 +189,7 @@ export default function ColorsManagement() {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{/* Sidebar */}
|
<AdminSidebar />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from 'react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { customerService } from '@/src/services/api';
|
import { customerService } from '@/src/services/api';
|
||||||
import { Customer, Sale } from '@/src/types/sales';
|
import { Customer, Sale } from '@/src/types/sales';
|
||||||
|
import { AdminSidebar } from '@/src/components/AdminSidebar';
|
||||||
|
|
||||||
interface CustomerWithSales extends Customer {
|
interface CustomerWithSales extends Customer {
|
||||||
sales?: Sale[];
|
sales?: Sale[];
|
||||||
@@ -172,50 +173,7 @@ export default function CustomersManagement() {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{/* Sidebar */}
|
<AdminSidebar />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ export default function AdminLogin() {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
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
|
// Set dark mode by default
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.documentElement.classList.add('dark');
|
document.documentElement.classList.add('dark');
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { colorRequestService } from '@/src/services/api';
|
import { colorRequestService } from '@/src/services/api';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { AdminSidebar } from '@/src/components/AdminSidebar';
|
||||||
|
|
||||||
interface ColorRequest {
|
interface ColorRequest {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -120,16 +121,11 @@ export default function ColorRequestsAdmin() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
<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">
|
<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>
|
<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>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
@@ -349,5 +345,6 @@ export default function ColorRequestsAdmin() {
|
|||||||
</div>
|
</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 { saleService, customerService, filamentService, colorService } from '@/src/services/api';
|
||||||
import { Customer, Sale, SaleItem, CreateSaleRequest } from '@/src/types/sales';
|
import { Customer, Sale, SaleItem, CreateSaleRequest } from '@/src/types/sales';
|
||||||
import { Filament } from '@/src/types/filament';
|
import { Filament } from '@/src/types/filament';
|
||||||
|
import { AdminSidebar } from '@/src/components/AdminSidebar';
|
||||||
|
|
||||||
interface LineItem {
|
interface LineItem {
|
||||||
filament_id: string;
|
filament_id: string;
|
||||||
@@ -120,50 +121,7 @@ export default function SalesPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex">
|
||||||
{/* Sidebar */}
|
<AdminSidebar />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1 p-8">
|
<div className="flex-1 p-8">
|
||||||
|
|||||||
42
src/components/AdminSidebar.tsx
Normal file
42
src/components/AdminSidebar.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ href: '/dashboard', label: 'Filamenti' },
|
||||||
|
{ href: '/upadaj/colors', label: 'Boje' },
|
||||||
|
{ href: '/upadaj/requests', label: 'Zahtevi za boje' },
|
||||||
|
{ href: '/upadaj/sales', label: 'Prodaja' },
|
||||||
|
{ href: '/upadaj/customers', label: 'Kupci' },
|
||||||
|
{ href: '/upadaj/analytics', label: 'Analitika' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function AdminSidebar() {
|
||||||
|
const pathname = usePathname()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-64 bg-white dark:bg-gray-800 shadow-lg h-screen sticky top-0 flex-shrink-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">
|
||||||
|
{navItems.map((item) => {
|
||||||
|
const isActive = pathname === item.href
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className={`block px-4 py-2 rounded ${
|
||||||
|
isActive
|
||||||
|
? 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300'
|
||||||
|
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import '@/src/styles/select.css';
|
import '@/src/styles/select.css';
|
||||||
import { trackEvent } from './MatomoAnalytics';
|
import { trackEvent } from './MatomoAnalytics';
|
||||||
|
import { getFinishesForMaterial, getAllFinishes, getMaterialOptions } from '@/src/data/bambuLabCatalog';
|
||||||
|
import { Filament } from '@/src/types/filament';
|
||||||
|
|
||||||
interface EnhancedFiltersProps {
|
interface EnhancedFiltersProps {
|
||||||
filters: {
|
filters: {
|
||||||
@@ -8,22 +10,74 @@ interface EnhancedFiltersProps {
|
|||||||
finish: string;
|
finish: string;
|
||||||
color: string;
|
color: string;
|
||||||
};
|
};
|
||||||
onFilterChange: (filters: any) => void;
|
onFilterChange: (filters: { material: string; finish: string; color: string }) => void;
|
||||||
uniqueValues: {
|
uniqueValues: {
|
||||||
materials: string[];
|
materials: string[];
|
||||||
finishes: string[];
|
finishes: string[];
|
||||||
colors: string[];
|
colors: string[];
|
||||||
};
|
};
|
||||||
|
inventoryFilaments?: Filament[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EnhancedFilters: React.FC<EnhancedFiltersProps> = ({
|
export const EnhancedFilters: React.FC<EnhancedFiltersProps> = ({
|
||||||
filters,
|
filters,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
uniqueValues
|
uniqueValues,
|
||||||
|
inventoryFilaments = []
|
||||||
}) => {
|
}) => {
|
||||||
// Check if any filters are active
|
|
||||||
const hasActiveFilters = filters.material || filters.finish || filters.color;
|
const hasActiveFilters = filters.material || filters.finish || filters.color;
|
||||||
|
|
||||||
|
// Catalog-aware material list (static from catalog)
|
||||||
|
const materialOptions = useMemo(() => getMaterialOptions(), []);
|
||||||
|
|
||||||
|
// Finish options: conditional on selected material
|
||||||
|
const finishOptions = useMemo(() => {
|
||||||
|
const inStock = inventoryFilaments.filter(f => f.kolicina > 0);
|
||||||
|
if (filters.material) {
|
||||||
|
// Show finishes from catalog for this material, but only if they exist in inventory
|
||||||
|
const catalogFinishes = getFinishesForMaterial(filters.material);
|
||||||
|
const inventoryFinishes = new Set(
|
||||||
|
inStock.filter(f => f.tip === filters.material).map(f => f.finish)
|
||||||
|
);
|
||||||
|
return catalogFinishes.filter(f => inventoryFinishes.has(f));
|
||||||
|
}
|
||||||
|
// No material selected: show all finishes from inventory
|
||||||
|
if (inStock.length > 0) {
|
||||||
|
return [...new Set(inStock.map(f => f.finish))].sort();
|
||||||
|
}
|
||||||
|
return getAllFinishes();
|
||||||
|
}, [filters.material, inventoryFilaments]);
|
||||||
|
|
||||||
|
// Color options: conditional on selected material + finish
|
||||||
|
const colorOptions = useMemo(() => {
|
||||||
|
const inStock = inventoryFilaments.filter(f => f.kolicina > 0);
|
||||||
|
let filtered = inStock;
|
||||||
|
if (filters.material) {
|
||||||
|
filtered = filtered.filter(f => f.tip === filters.material);
|
||||||
|
}
|
||||||
|
if (filters.finish) {
|
||||||
|
filtered = filtered.filter(f => f.finish === filters.finish);
|
||||||
|
}
|
||||||
|
return [...new Set(filtered.map(f => f.boja))].sort();
|
||||||
|
}, [filters.material, filters.finish, inventoryFilaments]);
|
||||||
|
|
||||||
|
const handleMaterialChange = (value: string) => {
|
||||||
|
// Reset finish and color when material changes
|
||||||
|
onFilterChange({ material: value, finish: '', color: '' });
|
||||||
|
trackEvent('Filter', 'Material', value || 'All');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFinishChange = (value: string) => {
|
||||||
|
// Reset color when finish changes
|
||||||
|
onFilterChange({ ...filters, finish: value, color: '' });
|
||||||
|
trackEvent('Filter', 'Finish', value || 'All');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleColorChange = (value: string) => {
|
||||||
|
onFilterChange({ ...filters, color: value });
|
||||||
|
trackEvent('Filter', 'Color', value || 'All');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||||
{/* Filters Grid */}
|
{/* Filters Grid */}
|
||||||
@@ -35,26 +89,15 @@ export const EnhancedFilters: React.FC<EnhancedFiltersProps> = ({
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={filters.material}
|
value={filters.material}
|
||||||
onChange={(e) => {
|
onChange={(e) => handleMaterialChange(e.target.value)}
|
||||||
onFilterChange({ ...filters, material: e.target.value });
|
|
||||||
trackEvent('Filter', 'Material', e.target.value || 'All');
|
|
||||||
}}
|
|
||||||
className="custom-select w-full px-3 py-2 border border-gray-300 dark:border-gray-600
|
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
|
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"
|
rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
>
|
>
|
||||||
<option value="">Svi materijali</option>
|
<option value="">Svi materijali</option>
|
||||||
<option value="ABS">ABS</option>
|
{materialOptions.map(mat => (
|
||||||
<option value="ASA">ASA</option>
|
<option key={mat} value={mat}>{mat}</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>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -65,35 +108,15 @@ export const EnhancedFilters: React.FC<EnhancedFiltersProps> = ({
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={filters.finish}
|
value={filters.finish}
|
||||||
onChange={(e) => {
|
onChange={(e) => handleFinishChange(e.target.value)}
|
||||||
onFilterChange({ ...filters, finish: e.target.value });
|
|
||||||
trackEvent('Filter', 'Finish', e.target.value || 'All');
|
|
||||||
}}
|
|
||||||
className="custom-select w-full px-3 py-2 border border-gray-300 dark:border-gray-600
|
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
|
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"
|
rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
>
|
>
|
||||||
<option value="">Svi finish tipovi</option>
|
<option value="">Svi finish tipovi</option>
|
||||||
<option value="85A">85A</option>
|
{finishOptions.map(finish => (
|
||||||
<option value="90A">90A</option>
|
<option key={finish} value={finish}>{finish}</option>
|
||||||
<option value="95A HF">95A HF</option>
|
))}
|
||||||
<option value="Aero">Aero</option>
|
|
||||||
<option value="Basic">Basic</option>
|
|
||||||
<option value="Basic Gradient">Basic Gradient</option>
|
|
||||||
<option value="CF">CF</option>
|
|
||||||
<option value="FR">FR</option>
|
|
||||||
<option value="Galaxy">Galaxy</option>
|
|
||||||
<option value="GF">GF</option>
|
|
||||||
<option value="Glow">Glow</option>
|
|
||||||
<option value="HF">HF</option>
|
|
||||||
<option value="Marble">Marble</option>
|
|
||||||
<option value="Matte">Matte</option>
|
|
||||||
<option value="Metal">Metal</option>
|
|
||||||
<option value="Silk Multi-Color">Silk Multi-Color</option>
|
|
||||||
<option value="Silk+">Silk+</option>
|
|
||||||
<option value="Sparkle">Sparkle</option>
|
|
||||||
<option value="Translucent">Translucent</option>
|
|
||||||
<option value="Wood">Wood</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -104,16 +127,13 @@ export const EnhancedFilters: React.FC<EnhancedFiltersProps> = ({
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={filters.color}
|
value={filters.color}
|
||||||
onChange={(e) => {
|
onChange={(e) => handleColorChange(e.target.value)}
|
||||||
onFilterChange({ ...filters, color: e.target.value });
|
|
||||||
trackEvent('Filter', 'Color', e.target.value || 'All');
|
|
||||||
}}
|
|
||||||
className="custom-select w-full px-3 py-2 border border-gray-300 dark:border-gray-600
|
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
|
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"
|
rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
>
|
>
|
||||||
<option value="">Sve boje</option>
|
<option value="">Sve boje</option>
|
||||||
{uniqueValues.colors.map(color => (
|
{colorOptions.map(color => (
|
||||||
<option key={color} value={color}>{color}</option>
|
<option key={color} value={color}>{color}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments }) => {
|
|||||||
filters={filters}
|
filters={filters}
|
||||||
onFilterChange={setFilters}
|
onFilterChange={setFilters}
|
||||||
uniqueValues={uniqueValues}
|
uniqueValues={uniqueValues}
|
||||||
|
inventoryFilaments={filaments}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
407
src/data/bambuLabCatalog.ts
Normal file
407
src/data/bambuLabCatalog.ts
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
// Master Bambu Lab product catalog — single source of truth
|
||||||
|
// Material → Finish → Color[] with refill/spool availability
|
||||||
|
|
||||||
|
export interface CatalogColorEntry {
|
||||||
|
name: string;
|
||||||
|
refill: boolean;
|
||||||
|
spool: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CatalogFinish {
|
||||||
|
colors: CatalogColorEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BambuLabCatalog = Record<string, Record<string, CatalogFinish>>;
|
||||||
|
|
||||||
|
export const BAMBU_LAB_CATALOG: BambuLabCatalog = {
|
||||||
|
PLA: {
|
||||||
|
Basic: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Jade White', refill: true, spool: true },
|
||||||
|
{ name: 'Black', refill: true, spool: true },
|
||||||
|
{ name: 'Red', refill: true, spool: true },
|
||||||
|
{ name: 'Bambu Green', refill: true, spool: true },
|
||||||
|
{ name: 'Blue', refill: true, spool: true },
|
||||||
|
{ name: 'Scarlet Red', refill: true, spool: true },
|
||||||
|
{ name: 'Lemon Yellow', refill: true, spool: true },
|
||||||
|
{ name: 'Cyan', refill: true, spool: true },
|
||||||
|
{ name: 'Sakura Pink', refill: true, spool: true },
|
||||||
|
{ name: 'Cobalt Blue', refill: true, spool: true },
|
||||||
|
{ name: 'Mistletoe Green', refill: true, spool: true },
|
||||||
|
{ name: 'Dark Red', refill: true, spool: true },
|
||||||
|
{ name: 'Hot Pink', refill: true, spool: true },
|
||||||
|
{ name: 'Lavender', refill: true, spool: true },
|
||||||
|
{ name: 'Light Blue', refill: true, spool: true },
|
||||||
|
{ name: 'Sky Blue', refill: true, spool: true },
|
||||||
|
{ name: 'Sunflower Yellow', refill: true, spool: true },
|
||||||
|
{ name: 'Pumpkin Orange', refill: true, spool: true },
|
||||||
|
{ name: 'Lime', refill: true, spool: true },
|
||||||
|
{ name: 'Blue Grey', refill: true, spool: false },
|
||||||
|
{ name: 'Beige', refill: true, spool: false },
|
||||||
|
{ name: 'Light Gray', refill: true, spool: false },
|
||||||
|
{ name: 'Yellow', refill: true, spool: false },
|
||||||
|
{ name: 'Orange', refill: true, spool: false },
|
||||||
|
{ name: 'Gold', refill: true, spool: false },
|
||||||
|
{ name: 'Bright Green', refill: true, spool: false },
|
||||||
|
{ name: 'Pink', refill: true, spool: false },
|
||||||
|
{ name: 'Magenta', refill: true, spool: false },
|
||||||
|
{ name: 'Maroon Red', refill: true, spool: false },
|
||||||
|
{ name: 'Purple', refill: true, spool: false },
|
||||||
|
{ name: 'Turquoise', refill: true, spool: false },
|
||||||
|
{ name: 'Brown', refill: true, spool: false },
|
||||||
|
{ name: 'Bronze', refill: true, spool: false },
|
||||||
|
{ name: 'Silver', refill: true, spool: false },
|
||||||
|
{ name: 'Dark Gray', refill: true, spool: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'Basic Gradient': {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Neon City', refill: false, spool: true },
|
||||||
|
{ name: 'Midnight Blaze', refill: false, spool: true },
|
||||||
|
{ name: 'South Beach', refill: false, spool: true },
|
||||||
|
{ name: 'Arctic Whisper', refill: false, spool: true },
|
||||||
|
{ name: 'Cotton Candy Cloud', refill: false, spool: true },
|
||||||
|
{ name: 'Ocean to Meadow', refill: false, spool: true },
|
||||||
|
{ name: 'Solar Breeze', refill: false, spool: true },
|
||||||
|
{ name: 'Velvet Eclipse', refill: false, spool: true },
|
||||||
|
{ name: 'Dawn Radiance', refill: false, spool: true },
|
||||||
|
{ name: 'Dusk Glare', refill: false, spool: true },
|
||||||
|
{ name: 'Blueberry Bubblegum', refill: false, spool: true },
|
||||||
|
{ name: 'Blue Hawaii', refill: false, spool: true },
|
||||||
|
{ name: 'Gilded Rose', refill: false, spool: true },
|
||||||
|
{ name: 'Pink Citrus', refill: false, spool: true },
|
||||||
|
{ name: 'Mint Lime', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Matte: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Matte Ivory White', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Charcoal', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Scarlet Red', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Marine Blue', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Mandarin Orange', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Ash Gray', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Desert Tan', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Nardo Gray', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Apple Green', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Bone White', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Caramel', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Dark Blue', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Dark Brown', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Dark Chocolate', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Dark Green', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Dark Red', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Grass Green', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Ice Blue', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Lemon Yellow', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Lilac Purple', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Plum', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Sakura Pink', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Sky Blue', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Latte Brown', refill: true, spool: false },
|
||||||
|
{ name: 'Matte Terracotta', refill: true, spool: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'Silk+': {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Candy Green', refill: false, spool: true },
|
||||||
|
{ name: 'Candy Red', refill: false, spool: true },
|
||||||
|
{ name: 'Mint', refill: false, spool: true },
|
||||||
|
{ name: 'Titan Gray', refill: false, spool: true },
|
||||||
|
{ name: 'Rose Gold', refill: false, spool: true },
|
||||||
|
{ name: 'Champagne', refill: false, spool: true },
|
||||||
|
{ name: 'Baby Blue', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'Silk Multi-Color': {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Silk Aurora Purple', refill: false, spool: true },
|
||||||
|
{ name: 'Silk Phantom Blue', refill: false, spool: true },
|
||||||
|
{ name: 'Silk Mystic Magenta', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Metal: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Iron Gray Metallic', refill: false, spool: true },
|
||||||
|
{ name: 'Iridium Gold Metallic', refill: false, spool: true },
|
||||||
|
{ name: 'Cobalt Blue Metallic', refill: false, spool: true },
|
||||||
|
{ name: 'Copper Brown Metallic', refill: false, spool: true },
|
||||||
|
{ name: 'Oxide Green Metallic', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Sparkle: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Onyx Black Sparkle', refill: false, spool: true },
|
||||||
|
{ name: 'Classic Gold Sparkle', refill: false, spool: true },
|
||||||
|
{ name: 'Crimson Red Sparkle', refill: false, spool: true },
|
||||||
|
{ name: 'Royal Purple Sparkle', refill: false, spool: true },
|
||||||
|
{ name: 'Slate Gray Sparkle', refill: false, spool: true },
|
||||||
|
{ name: 'Alpine Green Sparkle', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Galaxy: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Nebulae', refill: true, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Marble: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'White Marble', refill: false, spool: true },
|
||||||
|
{ name: 'Red Granite', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Glow: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Glow Blue', refill: false, spool: true },
|
||||||
|
{ name: 'Glow Green', refill: false, spool: true },
|
||||||
|
{ name: 'Glow Orange', refill: false, spool: true },
|
||||||
|
{ name: 'Glow Pink', refill: false, spool: true },
|
||||||
|
{ name: 'Glow Yellow', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Wood: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Ochre Yellow', refill: false, spool: true },
|
||||||
|
{ name: 'White Oak', refill: false, spool: true },
|
||||||
|
{ name: 'Clay Brown', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'Tough+': {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: true, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
CF: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
{ name: 'Burgundy Red', refill: false, spool: true },
|
||||||
|
{ name: 'Jeans Blue', refill: false, spool: true },
|
||||||
|
{ name: 'Lava Gray', refill: false, spool: true },
|
||||||
|
{ name: 'Matcha Green', refill: false, spool: true },
|
||||||
|
{ name: 'Royal Blue', refill: false, spool: true },
|
||||||
|
{ name: 'Iris Purple', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PETG: {
|
||||||
|
HF: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Jade White', refill: true, spool: true },
|
||||||
|
{ name: 'Black', refill: true, spool: true },
|
||||||
|
{ name: 'Red', refill: true, spool: true },
|
||||||
|
{ name: 'Green', refill: true, spool: true },
|
||||||
|
{ name: 'Blue', refill: true, spool: true },
|
||||||
|
{ name: 'Gray', refill: true, spool: true },
|
||||||
|
{ name: 'Orange', refill: true, spool: true },
|
||||||
|
{ name: 'Cream', refill: true, spool: true },
|
||||||
|
{ name: 'Forest Green', refill: true, spool: true },
|
||||||
|
{ name: 'Lake Blue', refill: true, spool: true },
|
||||||
|
{ name: 'Lime Green', refill: true, spool: true },
|
||||||
|
{ name: 'Peanut Brown', refill: true, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Translucent: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Clear', refill: false, spool: true },
|
||||||
|
{ name: 'Translucent Gray', refill: false, spool: true },
|
||||||
|
{ name: 'Translucent Brown', refill: false, spool: true },
|
||||||
|
{ name: 'Translucent Purple', refill: false, spool: true },
|
||||||
|
{ name: 'Translucent Orange', refill: false, spool: true },
|
||||||
|
{ name: 'Translucent Olive', refill: false, spool: true },
|
||||||
|
{ name: 'Translucent Pink', refill: false, spool: true },
|
||||||
|
{ name: 'Translucent Light Blue', refill: false, spool: true },
|
||||||
|
{ name: 'Translucent Tea', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
CF: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
{ name: 'Brick Red', refill: false, spool: true },
|
||||||
|
{ name: 'Indigo Blue', refill: false, spool: true },
|
||||||
|
{ name: 'Malachite Green', refill: false, spool: true },
|
||||||
|
{ name: 'Titan Gray', refill: false, spool: true },
|
||||||
|
{ name: 'Violet Purple', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ABS: {
|
||||||
|
Basic: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'ABS Azure', refill: true, spool: true },
|
||||||
|
{ name: 'ABS Black', refill: true, spool: true },
|
||||||
|
{ name: 'ABS Blue', refill: true, spool: true },
|
||||||
|
{ name: 'ABS Olive', refill: true, spool: true },
|
||||||
|
{ name: 'ABS Tangerine Yellow', refill: true, spool: true },
|
||||||
|
{ name: 'ABS Navy Blue', refill: true, spool: true },
|
||||||
|
{ name: 'ABS Orange', refill: true, spool: true },
|
||||||
|
{ name: 'ABS Bambu Green', refill: true, spool: true },
|
||||||
|
{ name: 'ABS Red', refill: true, spool: true },
|
||||||
|
{ name: 'ABS White', refill: true, spool: true },
|
||||||
|
{ name: 'ABS Silver', refill: true, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
GF: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'ABS GF Yellow', refill: true, spool: false },
|
||||||
|
{ name: 'ABS GF Orange', refill: true, spool: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TPU: {
|
||||||
|
'85A': {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
{ name: 'White', refill: false, spool: true },
|
||||||
|
{ name: 'Flesh', refill: false, spool: true },
|
||||||
|
{ name: 'Light Cyan', refill: false, spool: true },
|
||||||
|
{ name: 'Neon Orange', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'90A': {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
{ name: 'White', refill: false, spool: true },
|
||||||
|
{ name: 'Red', refill: false, spool: true },
|
||||||
|
{ name: 'Blaze', refill: false, spool: true },
|
||||||
|
{ name: 'Frozen', refill: false, spool: true },
|
||||||
|
{ name: 'Grape Jelly', refill: false, spool: true },
|
||||||
|
{ name: 'Crystal Blue', refill: false, spool: true },
|
||||||
|
{ name: 'Quicksilver', refill: false, spool: true },
|
||||||
|
{ name: 'Cocoa Brown', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'95A HF': {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: true, spool: false },
|
||||||
|
{ name: 'White', refill: true, spool: false },
|
||||||
|
{ name: 'TPU 95A HF Yellow', refill: true, spool: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ASA: {
|
||||||
|
Basic: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: true, spool: true },
|
||||||
|
{ name: 'Blue', refill: true, spool: true },
|
||||||
|
{ name: 'Gray', refill: true, spool: true },
|
||||||
|
{ name: 'Green', refill: true, spool: true },
|
||||||
|
{ name: 'Red', refill: true, spool: true },
|
||||||
|
{ name: 'White', refill: true, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
CF: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Aero: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'White', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PA6: {
|
||||||
|
GF: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PAHT: {
|
||||||
|
CF: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PC: {
|
||||||
|
Basic: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Clear Black', refill: false, spool: true },
|
||||||
|
{ name: 'Transparent', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
FR: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PET: {
|
||||||
|
CF: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PPA: {
|
||||||
|
CF: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PPS: {
|
||||||
|
CF: {
|
||||||
|
colors: [
|
||||||
|
{ name: 'Black', refill: false, spool: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
|
||||||
|
export function getMaterialOptions(): string[] {
|
||||||
|
return Object.keys(BAMBU_LAB_CATALOG).sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFinishesForMaterial(material: string): string[] {
|
||||||
|
const materialData = BAMBU_LAB_CATALOG[material];
|
||||||
|
if (!materialData) return [];
|
||||||
|
return Object.keys(materialData).sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllFinishes(): string[] {
|
||||||
|
const finishes = new Set<string>();
|
||||||
|
for (const material of Object.values(BAMBU_LAB_CATALOG)) {
|
||||||
|
for (const finish of Object.keys(material)) {
|
||||||
|
finishes.add(finish);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...finishes].sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getColorsForMaterialFinish(material: string, finish: string): CatalogColorEntry[] {
|
||||||
|
return BAMBU_LAB_CATALOG[material]?.[finish]?.colors ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getColorsForMaterial(material: string): CatalogColorEntry[] {
|
||||||
|
const materialData = BAMBU_LAB_CATALOG[material];
|
||||||
|
if (!materialData) return [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const result: CatalogColorEntry[] = [];
|
||||||
|
for (const finish of Object.values(materialData)) {
|
||||||
|
for (const color of finish.colors) {
|
||||||
|
if (!seen.has(color.name)) {
|
||||||
|
seen.add(color.name);
|
||||||
|
result.push(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function catalogIsSpoolOnly(material: string, finish: string, color: string): boolean {
|
||||||
|
const entry = BAMBU_LAB_CATALOG[material]?.[finish]?.colors.find(c => c.name === color);
|
||||||
|
return entry ? (entry.spool && !entry.refill) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function catalogIsRefillOnly(material: string, finish: string, color: string): boolean {
|
||||||
|
const entry = BAMBU_LAB_CATALOG[material]?.[finish]?.colors.find(c => c.name === color);
|
||||||
|
return entry ? (entry.refill && !entry.spool) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFinishOptionsForType(type: string): string[] {
|
||||||
|
return getFinishesForMaterial(type);
|
||||||
|
}
|
||||||
@@ -147,11 +147,8 @@ export const bambuLabColors: Record<string, ColorMapping> = {
|
|||||||
'Yellow': { hex: '#F4EE2A' },
|
'Yellow': { hex: '#F4EE2A' },
|
||||||
|
|
||||||
// ABS Colors
|
// ABS Colors
|
||||||
// ABS GF Colors
|
|
||||||
'ABS GF Yellow': { hex: '#FDD835' },
|
'ABS GF Yellow': { hex: '#FDD835' },
|
||||||
'ABS GF Orange': { hex: '#F48438' },
|
'ABS GF Orange': { hex: '#F48438' },
|
||||||
|
|
||||||
// ABS Colors
|
|
||||||
'ABS Azure': { hex: '#489FDF' },
|
'ABS Azure': { hex: '#489FDF' },
|
||||||
'ABS Olive': { hex: '#748C45' },
|
'ABS Olive': { hex: '#748C45' },
|
||||||
'ABS Blue': { hex: '#0A2989' },
|
'ABS Blue': { hex: '#0A2989' },
|
||||||
@@ -164,7 +161,7 @@ export const bambuLabColors: Record<string, ColorMapping> = {
|
|||||||
'ABS Black': { hex: '#000000' },
|
'ABS Black': { hex: '#000000' },
|
||||||
'ABS Silver': { hex: '#A6A9AA' },
|
'ABS Silver': { hex: '#A6A9AA' },
|
||||||
|
|
||||||
// Translucent Colors
|
// PETG Translucent Colors
|
||||||
'Translucent Gray': { hex: '#B8B8B8' },
|
'Translucent Gray': { hex: '#B8B8B8' },
|
||||||
'Translucent Brown': { hex: '#C89A74' },
|
'Translucent Brown': { hex: '#C89A74' },
|
||||||
'Translucent Purple': { hex: '#C5A8D8' },
|
'Translucent Purple': { hex: '#C5A8D8' },
|
||||||
@@ -174,7 +171,15 @@ export const bambuLabColors: Record<string, ColorMapping> = {
|
|||||||
'Translucent Light Blue': { hex: '#A8D8F0' },
|
'Translucent Light Blue': { hex: '#A8D8F0' },
|
||||||
'Translucent Tea': { hex: '#D9C7A8' },
|
'Translucent Tea': { hex: '#D9C7A8' },
|
||||||
|
|
||||||
// PLA Matte - New Colors (2025)
|
// PLA Matte Colors
|
||||||
|
'Matte Ivory White': { hex: '#FFFFF0' },
|
||||||
|
'Matte Charcoal': { hex: '#333333' },
|
||||||
|
'Matte Scarlet Red': { hex: '#DE4343' },
|
||||||
|
'Matte Marine Blue': { hex: '#0078BF' },
|
||||||
|
'Matte Mandarin Orange': { hex: '#F99963' },
|
||||||
|
'Matte Ash Gray': { hex: '#9B9EA0' },
|
||||||
|
'Matte Desert Tan': { hex: '#E8DBB7' },
|
||||||
|
'Matte Nardo Gray': { hex: '#747474' },
|
||||||
'Matte Apple Green': { hex: '#C6E188' },
|
'Matte Apple Green': { hex: '#C6E188' },
|
||||||
'Matte Bone White': { hex: '#C8C5B6' },
|
'Matte Bone White': { hex: '#C8C5B6' },
|
||||||
'Matte Caramel': { hex: '#A4845C' },
|
'Matte Caramel': { hex: '#A4845C' },
|
||||||
@@ -185,7 +190,6 @@ export const bambuLabColors: Record<string, ColorMapping> = {
|
|||||||
'Matte Dark Red': { hex: '#BB3D43' },
|
'Matte Dark Red': { hex: '#BB3D43' },
|
||||||
'Matte Grass Green': { hex: '#7CB342' },
|
'Matte Grass Green': { hex: '#7CB342' },
|
||||||
'Matte Ice Blue': { hex: '#A3D8E1' },
|
'Matte Ice Blue': { hex: '#A3D8E1' },
|
||||||
'Matte Ivory': { hex: '#FFFFF0' },
|
|
||||||
'Matte Lemon Yellow': { hex: '#F7D959' },
|
'Matte Lemon Yellow': { hex: '#F7D959' },
|
||||||
'Matte Lilac Purple': { hex: '#AE96D4' },
|
'Matte Lilac Purple': { hex: '#AE96D4' },
|
||||||
'Matte Plum': { hex: '#851A52' },
|
'Matte Plum': { hex: '#851A52' },
|
||||||
@@ -199,7 +203,11 @@ export const bambuLabColors: Record<string, ColorMapping> = {
|
|||||||
'Silk Phantom Blue': { hex: ['#00629B', '#000000'], isGradient: true },
|
'Silk Phantom Blue': { hex: ['#00629B', '#000000'], isGradient: true },
|
||||||
'Silk Mystic Magenta': { hex: ['#720062', '#3A913F'], isGradient: true },
|
'Silk Mystic Magenta': { hex: ['#720062', '#3A913F'], isGradient: true },
|
||||||
|
|
||||||
// TPU 95A HF Colors
|
// TPU Colors
|
||||||
|
'Flesh': { hex: '#E8C4A2' },
|
||||||
|
'Grape Jelly': { hex: '#6B2D75' },
|
||||||
|
'Crystal Blue': { hex: '#5BC0EB' },
|
||||||
|
'Quicksilver': { hex: '#A6A9AA' },
|
||||||
'TPU 95A HF Yellow': { hex: '#F3E600' },
|
'TPU 95A HF Yellow': { hex: '#F3E600' },
|
||||||
|
|
||||||
// Default fallback
|
// Default fallback
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
// Complete Bambu Lab color database with hex codes
|
// Complete Bambu Lab color database with hex codes
|
||||||
export const bambuLabColors = {
|
// Re-exports catalog-aligned color groupings
|
||||||
// Basic Colors
|
|
||||||
|
import { BAMBU_LAB_CATALOG } from './bambuLabCatalog';
|
||||||
|
|
||||||
|
// Flat hex lookup (for backwards compatibility)
|
||||||
|
export const bambuLabColors: Record<string, string> = {
|
||||||
|
// PLA Basic
|
||||||
|
"Jade White": "#FFFFFF",
|
||||||
"Black": "#000000",
|
"Black": "#000000",
|
||||||
"White": "#FFFFFF",
|
"White": "#FFFFFF",
|
||||||
"Red": "#E53935",
|
"Red": "#E53935",
|
||||||
@@ -8,39 +14,64 @@ export const bambuLabColors = {
|
|||||||
"Green": "#43A047",
|
"Green": "#43A047",
|
||||||
"Yellow": "#FDD835",
|
"Yellow": "#FDD835",
|
||||||
"Orange": "#FB8C00",
|
"Orange": "#FB8C00",
|
||||||
"Purple": "#8E24AA",
|
"Purple": "#5E43B7",
|
||||||
"Pink": "#EC407A",
|
"Pink": "#F55A74",
|
||||||
"Grey": "#757575",
|
"Gray": "#8E9089",
|
||||||
"Brown": "#6D4C41",
|
"Brown": "#9D432C",
|
||||||
"Light Blue": "#64B5F6",
|
"Light Blue": "#61B0FF",
|
||||||
"Light Green": "#81C784",
|
"Light Gray": "#D0D2D4",
|
||||||
"Mint Green": "#4DB6AC",
|
"Sky Blue": "#73B2E5",
|
||||||
"Lime Green": "#C0CA33",
|
"Navy Blue": "#0C2340",
|
||||||
"Sky Blue": "#81D4FA",
|
"Magenta": "#EC008C",
|
||||||
"Navy Blue": "#283593",
|
"Beige": "#F7E6DE",
|
||||||
"Magenta": "#E91E63",
|
"Bambu Green": "#00AE42",
|
||||||
"Violet": "#7B1FA2",
|
"Scarlet Red": "#DE4343",
|
||||||
"Beige": "#F5DEB3",
|
"Lemon Yellow": "#F7D959",
|
||||||
"Ivory": "#FFFFF0",
|
"Cyan": "#0086D6",
|
||||||
|
"Sakura Pink": "#E8AFCF",
|
||||||
|
"Cobalt Blue": "#0055B8",
|
||||||
|
"Mistletoe Green": "#3F8E43",
|
||||||
|
"Dark Red": "#BB3D43",
|
||||||
|
"Hot Pink": "#F5547D",
|
||||||
|
"Lavender": "#B5AAD5",
|
||||||
|
"Sunflower Yellow": "#FEC601",
|
||||||
|
"Pumpkin Orange": "#FF8E16",
|
||||||
|
"Lime": "#C5ED48",
|
||||||
|
"Blue Grey": "#5B6579",
|
||||||
|
"Gold": "#E4BD68",
|
||||||
|
"Bright Green": "#BDCF00",
|
||||||
|
"Maroon Red": "#0A2989",
|
||||||
|
"Turquoise": "#00B1B7",
|
||||||
|
"Bronze": "#847D48",
|
||||||
|
"Silver": "#A6A9AA",
|
||||||
|
"Dark Gray": "#555555",
|
||||||
|
|
||||||
// Matte Colors
|
// PLA Basic Gradient
|
||||||
"Matte Black": "#212121",
|
"Neon City": "#0047BB",
|
||||||
"Matte White": "#FAFAFA",
|
"Midnight Blaze": "#0047BB",
|
||||||
"Matte Red": "#C62828",
|
"South Beach": "#468791",
|
||||||
"Matte Blue": "#1565C0",
|
"Arctic Whisper": "#ECF7F8",
|
||||||
"Matte Green": "#2E7D32",
|
"Cotton Candy Cloud": "#E9E2EC",
|
||||||
"Matte Yellow": "#F9A825",
|
"Ocean to Meadow": "#A1E4CA",
|
||||||
"Matte Orange": "#EF6C00",
|
"Solar Breeze": "#F3D9D5",
|
||||||
"Matte Purple": "#6A1B9A",
|
"Velvet Eclipse": "#000000",
|
||||||
"Matte Pink": "#D81B60",
|
"Dawn Radiance": "#C472A1",
|
||||||
"Matte Grey": "#616161",
|
"Dusk Glare": "#F6B790",
|
||||||
"Matte Brown": "#4E342E",
|
"Blueberry Bubblegum": "#BADCF4",
|
||||||
"Matte Mint": "#26A69A",
|
"Blue Hawaii": "#739FE6",
|
||||||
"Matte Lime": "#9E9D24",
|
"Gilded Rose": "#ED982C",
|
||||||
"Matte Navy": "#1A237E",
|
"Pink Citrus": "#F8C4BC",
|
||||||
"Matte Coral": "#FF5252",
|
"Mint Lime": "#BAF382",
|
||||||
|
|
||||||
// Matte Colors - New 2025
|
// PLA Matte
|
||||||
|
"Matte Ivory White": "#FFFFF0",
|
||||||
|
"Matte Charcoal": "#333333",
|
||||||
|
"Matte Scarlet Red": "#DE4343",
|
||||||
|
"Matte Marine Blue": "#0078BF",
|
||||||
|
"Matte Mandarin Orange": "#F99963",
|
||||||
|
"Matte Ash Gray": "#9B9EA0",
|
||||||
|
"Matte Desert Tan": "#E8DBB7",
|
||||||
|
"Matte Nardo Gray": "#747474",
|
||||||
"Matte Apple Green": "#C6E188",
|
"Matte Apple Green": "#C6E188",
|
||||||
"Matte Bone White": "#C8C5B6",
|
"Matte Bone White": "#C8C5B6",
|
||||||
"Matte Caramel": "#A4845C",
|
"Matte Caramel": "#A4845C",
|
||||||
@@ -49,7 +80,7 @@ export const bambuLabColors = {
|
|||||||
"Matte Dark Chocolate": "#4A3729",
|
"Matte Dark Chocolate": "#4A3729",
|
||||||
"Matte Dark Green": "#68724D",
|
"Matte Dark Green": "#68724D",
|
||||||
"Matte Dark Red": "#BB3D43",
|
"Matte Dark Red": "#BB3D43",
|
||||||
"Matte Grass Green": "#61C680",
|
"Matte Grass Green": "#7CB342",
|
||||||
"Matte Ice Blue": "#A3D8E1",
|
"Matte Ice Blue": "#A3D8E1",
|
||||||
"Matte Lemon Yellow": "#F7D959",
|
"Matte Lemon Yellow": "#F7D959",
|
||||||
"Matte Lilac Purple": "#AE96D4",
|
"Matte Lilac Purple": "#AE96D4",
|
||||||
@@ -59,131 +90,146 @@ export const bambuLabColors = {
|
|||||||
"Matte Latte Brown": "#D3B7A7",
|
"Matte Latte Brown": "#D3B7A7",
|
||||||
"Matte Terracotta": "#A25A37",
|
"Matte Terracotta": "#A25A37",
|
||||||
|
|
||||||
// Silk Colors
|
// PLA Silk+
|
||||||
"Silk White": "#FEFEFE",
|
"Candy Green": "#408619",
|
||||||
"Silk Black": "#0A0A0A",
|
"Candy Red": "#BB3A2E",
|
||||||
"Silk Red": "#F44336",
|
"Mint": "#A5DAB7",
|
||||||
"Silk Blue": "#2196F3",
|
"Titan Gray": "#606367",
|
||||||
"Silk Green": "#4CAF50",
|
"Rose Gold": "#B29593",
|
||||||
"Silk Gold": "#FFD54F",
|
"Champagne": "#EBD0B1",
|
||||||
"Silk Silver": "#CFD8DC",
|
"Baby Blue": "#AEC3ED",
|
||||||
"Silk Purple": "#9C27B0",
|
|
||||||
"Silk Pink": "#F06292",
|
|
||||||
"Silk Orange": "#FF9800",
|
|
||||||
"Silk Bronze": "#A1887F",
|
|
||||||
"Silk Copper": "#BF6F3F",
|
|
||||||
"Silk Jade": "#00897B",
|
|
||||||
"Silk Rose Gold": "#E8A09A",
|
|
||||||
"Silk Pearl": "#F8F8FF",
|
|
||||||
"Silk Ruby": "#E91E63",
|
|
||||||
"Silk Sapphire": "#1976D2",
|
|
||||||
"Silk Emerald": "#00695C",
|
|
||||||
|
|
||||||
// Silk Multi-Color
|
// PLA Silk Multi-Color
|
||||||
"Silk Aurora Purple": "#7F3696",
|
"Silk Aurora Purple": "#7F3696",
|
||||||
"Silk Phantom Blue": "#00629B",
|
"Silk Phantom Blue": "#00629B",
|
||||||
"Silk Mystic Magenta": "#720062",
|
"Silk Mystic Magenta": "#720062",
|
||||||
|
|
||||||
// Metal Colors
|
// PLA Metal
|
||||||
"Metal Grey": "#9E9E9E",
|
|
||||||
"Metal Silver": "#B0BEC5",
|
|
||||||
"Metal Gold": "#D4AF37",
|
|
||||||
"Metal Copper": "#B87333",
|
|
||||||
"Metal Bronze": "#CD7F32",
|
|
||||||
|
|
||||||
// Sparkle Colors
|
|
||||||
"Sparkle Red": "#EF5350",
|
|
||||||
"Sparkle Blue": "#42A5F5",
|
|
||||||
"Sparkle Green": "#66BB6A",
|
|
||||||
"Sparkle Purple": "#AB47BC",
|
|
||||||
"Sparkle Gold": "#FFCA28",
|
|
||||||
"Sparkle Silver": "#E0E0E0",
|
|
||||||
|
|
||||||
// Glow Colors
|
|
||||||
"Glow in the Dark Green": "#C8E6C9",
|
|
||||||
"Glow in the Dark Blue": "#BBDEFB",
|
|
||||||
|
|
||||||
// Transparent Colors
|
|
||||||
"Clear": "#FFFFFF",
|
|
||||||
"Transparent Red": "#EF5350",
|
|
||||||
"Transparent Blue": "#42A5F5",
|
|
||||||
"Transparent Green": "#66BB6A",
|
|
||||||
"Transparent Yellow": "#FFEE58",
|
|
||||||
"Transparent Orange": "#FFA726",
|
|
||||||
"Transparent Purple": "#AB47BC",
|
|
||||||
|
|
||||||
// Support Materials
|
|
||||||
"Natural": "#F5F5DC",
|
|
||||||
"Support White": "#F5F5F5",
|
|
||||||
"Support G": "#90CAF9",
|
|
||||||
|
|
||||||
// Metal Colors (PLA)
|
|
||||||
"Iron Gray Metallic": "#6B6C6F",
|
"Iron Gray Metallic": "#6B6C6F",
|
||||||
|
"Iridium Gold Metallic": "#B39B84",
|
||||||
|
"Cobalt Blue Metallic": "#39699E",
|
||||||
|
"Copper Brown Metallic": "#AA6443",
|
||||||
|
"Oxide Green Metallic": "#1D7C6A",
|
||||||
|
|
||||||
// ABS GF Colors
|
// PLA Sparkle
|
||||||
|
"Onyx Black Sparkle": "#2D2B28",
|
||||||
|
"Classic Gold Sparkle": "#E4BD68",
|
||||||
|
"Crimson Red Sparkle": "#792B36",
|
||||||
|
"Royal Purple Sparkle": "#483D8B",
|
||||||
|
"Slate Gray Sparkle": "#8E9089",
|
||||||
|
"Alpine Green Sparkle": "#3F5443",
|
||||||
|
|
||||||
|
// PLA Galaxy
|
||||||
|
"Nebulae": "#424379",
|
||||||
|
|
||||||
|
// PLA Marble
|
||||||
|
"White Marble": "#F7F3F0",
|
||||||
|
"Red Granite": "#AD4E38",
|
||||||
|
|
||||||
|
// PLA Glow
|
||||||
|
"Glow Blue": "#7AC0E9",
|
||||||
|
"Glow Green": "#A1FFAC",
|
||||||
|
"Glow Orange": "#FF9D5B",
|
||||||
|
"Glow Pink": "#F17B8F",
|
||||||
|
"Glow Yellow": "#F8FF80",
|
||||||
|
|
||||||
|
// PLA Wood
|
||||||
|
"Ochre Yellow": "#BC8B39",
|
||||||
|
"White Oak": "#D2CCA2",
|
||||||
|
"Clay Brown": "#8E621A",
|
||||||
|
|
||||||
|
// PLA CF
|
||||||
|
"Burgundy Red": "#951E23",
|
||||||
|
"Jeans Blue": "#6E88BC",
|
||||||
|
"Lava Gray": "#4D5054",
|
||||||
|
"Matcha Green": "#5C9748",
|
||||||
|
"Royal Blue": "#2842AD",
|
||||||
|
"Iris Purple": "#69398E",
|
||||||
|
|
||||||
|
// PETG HF
|
||||||
|
"Cream": "#F3E0B8",
|
||||||
|
"Forest Green": "#415520",
|
||||||
|
"Lake Blue": "#4672E4",
|
||||||
|
"Lime Green": "#8EE43D",
|
||||||
|
"Peanut Brown": "#7E5A1F",
|
||||||
|
|
||||||
|
// PETG Translucent
|
||||||
|
"Clear": "#FAFAFA",
|
||||||
|
"Translucent Gray": "#B8B8B8",
|
||||||
|
"Translucent Brown": "#C89A74",
|
||||||
|
"Translucent Purple": "#C5A8D8",
|
||||||
|
"Translucent Orange": "#FFB380",
|
||||||
|
"Translucent Olive": "#A4B885",
|
||||||
|
"Translucent Pink": "#F9B8D0",
|
||||||
|
"Translucent Light Blue": "#A8D8F0",
|
||||||
|
"Translucent Tea": "#D9C7A8",
|
||||||
|
|
||||||
|
// PETG CF
|
||||||
|
"Brick Red": "#9F332A",
|
||||||
|
"Indigo Blue": "#324585",
|
||||||
|
"Malachite Green": "#16B08E",
|
||||||
|
"Violet Purple": "#583061",
|
||||||
|
|
||||||
|
// ABS
|
||||||
|
"ABS Azure": "#489FDF",
|
||||||
|
"ABS Black": "#000000",
|
||||||
|
"ABS Blue": "#0A2989",
|
||||||
|
"ABS Olive": "#748C45",
|
||||||
|
"ABS Tangerine Yellow": "#FFC72C",
|
||||||
|
"ABS Navy Blue": "#0C2340",
|
||||||
|
"ABS Orange": "#FF6A13",
|
||||||
|
"ABS Bambu Green": "#00AE42",
|
||||||
|
"ABS Red": "#C12E1F",
|
||||||
|
"ABS White": "#FFFFFF",
|
||||||
|
"ABS Silver": "#A6A9AA",
|
||||||
"ABS GF Yellow": "#FDD835",
|
"ABS GF Yellow": "#FDD835",
|
||||||
"ABS GF Orange": "#F48438",
|
"ABS GF Orange": "#F48438",
|
||||||
|
|
||||||
// TPU 95A HF Colors
|
// TPU
|
||||||
|
"Flesh": "#E8C4A2",
|
||||||
|
"Light Cyan": "#B9E3DF",
|
||||||
|
"Neon Orange": "#F68A1B",
|
||||||
|
"Blaze": "#E78390",
|
||||||
|
"Frozen": "#A6DEF3",
|
||||||
|
"Grape Jelly": "#6B2D75",
|
||||||
|
"Crystal Blue": "#5BC0EB",
|
||||||
|
"Quicksilver": "#A6A9AA",
|
||||||
|
"Cocoa Brown": "#6F5034",
|
||||||
"TPU 95A HF Yellow": "#F3E600",
|
"TPU 95A HF Yellow": "#F3E600",
|
||||||
|
|
||||||
// Wood Colors
|
// PC
|
||||||
"Ochre Yellow": "#BC8B39",
|
"Clear Black": "#5A5161",
|
||||||
"White Oak": "#D2CCA2",
|
"Transparent": "#FFFFFF",
|
||||||
"Clay Brown": "#8E621A"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Colors grouped by finish type for easier selection
|
// Colors grouped by finish type — derived from catalog
|
||||||
export const colorsByFinish = {
|
export const colorsByFinish: Record<string, string[]> = {};
|
||||||
"Basic": [
|
|
||||||
"Black", "White", "Red", "Blue", "Green", "Yellow", "Orange",
|
// Build colorsByFinish from catalog
|
||||||
"Purple", "Pink", "Grey", "Brown", "Light Blue", "Light Green",
|
for (const [, finishes] of Object.entries(BAMBU_LAB_CATALOG)) {
|
||||||
"Mint Green", "Lime Green", "Sky Blue", "Navy Blue", "Magenta",
|
for (const [finishName, finishData] of Object.entries(finishes)) {
|
||||||
"Violet", "Beige", "Ivory"
|
if (!colorsByFinish[finishName]) {
|
||||||
],
|
colorsByFinish[finishName] = [];
|
||||||
"Matte": [
|
}
|
||||||
"Matte Black", "Matte White", "Matte Red", "Matte Blue", "Matte Green",
|
for (const color of finishData.colors) {
|
||||||
"Matte Yellow", "Matte Orange", "Matte Purple", "Matte Pink", "Matte Grey",
|
if (!colorsByFinish[finishName].includes(color.name)) {
|
||||||
"Matte Brown", "Matte Mint", "Matte Lime", "Matte Navy", "Matte Coral",
|
colorsByFinish[finishName].push(color.name);
|
||||||
"Matte Apple Green", "Matte Bone White", "Matte Caramel", "Matte Dark Blue",
|
}
|
||||||
"Matte Dark Brown", "Matte Dark Chocolate", "Matte Dark Green", "Matte Dark Red",
|
}
|
||||||
"Matte Grass Green", "Matte Ice Blue", "Matte Lemon Yellow", "Matte Lilac Purple",
|
}
|
||||||
"Matte Latte Brown", "Matte Plum", "Matte Sakura Pink", "Matte Sky Blue", "Matte Terracotta"
|
}
|
||||||
],
|
|
||||||
"Silk": [
|
// Sort each finish's colors
|
||||||
"Silk White", "Silk Black", "Silk Red", "Silk Blue", "Silk Green",
|
for (const finish of Object.keys(colorsByFinish)) {
|
||||||
"Silk Gold", "Silk Silver", "Silk Purple", "Silk Pink", "Silk Orange",
|
colorsByFinish[finish].sort();
|
||||||
"Silk Bronze", "Silk Copper", "Silk Jade", "Silk Rose Gold", "Silk Pearl",
|
}
|
||||||
"Silk Ruby", "Silk Sapphire", "Silk Emerald"
|
|
||||||
],
|
|
||||||
"Metal": [
|
|
||||||
"Metal Grey", "Metal Silver", "Metal Gold", "Metal Copper", "Metal Bronze"
|
|
||||||
],
|
|
||||||
"Sparkle": [
|
|
||||||
"Sparkle Red", "Sparkle Blue", "Sparkle Green", "Sparkle Purple",
|
|
||||||
"Sparkle Gold", "Sparkle Silver"
|
|
||||||
],
|
|
||||||
"Glow": [
|
|
||||||
"Glow in the Dark Green", "Glow in the Dark Blue"
|
|
||||||
],
|
|
||||||
"Transparent": [
|
|
||||||
"Clear", "Transparent Red", "Transparent Blue", "Transparent Green",
|
|
||||||
"Transparent Yellow", "Transparent Orange", "Transparent Purple"
|
|
||||||
],
|
|
||||||
"Support": [
|
|
||||||
"Natural", "Support White", "Support G"
|
|
||||||
],
|
|
||||||
"Wood": [
|
|
||||||
"Ochre Yellow", "White Oak", "Clay Brown"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to get hex code for a color
|
// Function to get hex code for a color
|
||||||
export function getColorHex(colorName: string): string {
|
export function getColorHex(colorName: string): string {
|
||||||
return bambuLabColors[colorName as keyof typeof bambuLabColors] || "#000000";
|
return bambuLabColors[colorName] || "#000000";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get colors for a specific finish
|
// Function to get colors for a specific finish
|
||||||
export function getColorsForFinish(finish: string): string[] {
|
export function getColorsForFinish(finish: string): string[] {
|
||||||
return colorsByFinish[finish as keyof typeof colorsByFinish] || [];
|
return colorsByFinish[finish] || [];
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user