From 65ae493d54dc13e95201607c0e2a9d81a51b1d4b Mon Sep 17 00:00:00 2001 From: DaX Date: Thu, 5 Mar 2026 01:04:06 +0100 Subject: [PATCH] Align catalog with Bambu Lab product line, add conditional filters and admin sidebar - 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 --- __tests__/data/bambuLabColorsComplete.test.ts | 18 +- __tests__/ui-features.test.ts | 19 +- app/dashboard/page.tsx | 159 ++----- app/upadaj/analytics/page.tsx | 16 +- app/upadaj/colors/page.tsx | 16 +- app/upadaj/customers/page.tsx | 46 +- app/upadaj/page.tsx | 9 + app/upadaj/requests/page.tsx | 13 +- app/upadaj/sales/page.tsx | 46 +- src/components/AdminSidebar.tsx | 42 ++ src/components/EnhancedFilters.tsx | 136 +++--- src/components/FilamentTableV2.tsx | 3 +- src/data/bambuLabCatalog.ts | 407 ++++++++++++++++++ src/data/bambuLabColors.ts | 22 +- src/data/bambuLabColorsComplete.ts | 332 ++++++++------ 15 files changed, 817 insertions(+), 467 deletions(-) create mode 100644 src/components/AdminSidebar.tsx create mode 100644 src/data/bambuLabCatalog.ts diff --git a/__tests__/data/bambuLabColorsComplete.test.ts b/__tests__/data/bambuLabColorsComplete.test.ts index 65fcc17..8c323c4 100644 --- a/__tests__/data/bambuLabColorsComplete.test.ts +++ b/__tests__/data/bambuLabColorsComplete.test.ts @@ -15,15 +15,15 @@ describe('Bambu Lab Colors Complete Data', () => { }); it('should have matte colors', () => { - expect(bambuLabColors).toHaveProperty('Matte Black'); - expect(bambuLabColors).toHaveProperty('Matte White'); - expect(bambuLabColors).toHaveProperty('Matte Red'); + expect(bambuLabColors).toHaveProperty('Matte Ivory White'); + expect(bambuLabColors).toHaveProperty('Matte Charcoal'); + expect(bambuLabColors).toHaveProperty('Matte Scarlet Red'); }); it('should have silk colors', () => { - expect(bambuLabColors).toHaveProperty('Silk White'); - expect(bambuLabColors).toHaveProperty('Silk Black'); - expect(bambuLabColors).toHaveProperty('Silk Gold'); + expect(bambuLabColors).toHaveProperty('Silk Aurora Purple'); + expect(bambuLabColors).toHaveProperty('Silk Phantom Blue'); + expect(bambuLabColors).toHaveProperty('Silk Mystic Magenta'); }); it('should have valid hex colors', () => { @@ -40,12 +40,12 @@ describe('Bambu Lab Colors Complete Data', () => { it('should have finish categories', () => { expect(colorsByFinish).toHaveProperty('Basic'); expect(colorsByFinish).toHaveProperty('Matte'); - expect(colorsByFinish).toHaveProperty('Silk'); + expect(colorsByFinish).toHaveProperty('Silk+'); expect(colorsByFinish).toHaveProperty('Metal'); expect(colorsByFinish).toHaveProperty('Sparkle'); expect(colorsByFinish).toHaveProperty('Glow'); - expect(colorsByFinish).toHaveProperty('Transparent'); - expect(colorsByFinish).toHaveProperty('Support'); + expect(colorsByFinish).toHaveProperty('Translucent'); + expect(colorsByFinish).toHaveProperty('CF'); }); it('should return valid hex for getColorHex', () => { diff --git a/__tests__/ui-features.test.ts b/__tests__/ui-features.test.ts index b1c5da5..15ce4d9 100644 --- a/__tests__/ui-features.test.ts +++ b/__tests__/ui-features.test.ts @@ -46,21 +46,18 @@ describe('UI Features Tests', () => { it('should have predefined material options', () => { const adminDashboardPath = join(process.cwd(), 'app', 'dashboard', 'page.tsx'); const adminContent = readFileSync(adminDashboardPath, 'utf-8'); - - // Check for material select dropdown - expect(adminContent).toContain(''); - expect(adminContent).toContain(''); - expect(adminContent).toContain(''); + + // Check for material select dropdown (now generated from catalog) + expect(adminContent).toContain('getMaterialOptions()'); + expect(adminContent).toContain('Izaberi tip'); }); - it('should have admin header with navigation', () => { + it('should have admin sidebar and header', () => { const adminDashboardPath = join(process.cwd(), 'app', 'dashboard', 'page.tsx'); - const dashboardContent = readFileSync(adminDashboardPath, 'utf-8'); - - // Check for admin header - expect(dashboardContent).toContain('Admin'); - expect(dashboardContent).toContain('Nazad na sajt'); + + // Check for admin sidebar and header + expect(dashboardContent).toContain('AdminSidebar'); expect(dashboardContent).toContain('Odjava'); }); diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 715fb83..d0836df 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -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 = { - '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([]); @@ -360,8 +314,10 @@ export default function AdminDashboard() { return (
- {/* Main Content - Full Screen */} -
+
+ + {/* Main Content */} +
@@ -401,24 +357,6 @@ export default function AdminDashboard() { filaments={filaments} onUpdate={fetchFilaments} /> - - - {mounted && (
+
); @@ -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" > - - - - - - - - - - - + {getMaterialOptions().map(mat => ( + + ))}
@@ -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" > - {(FINISH_OPTIONS_BY_TYPE[formData.tip] || []).map(finish => ( + {getFinishesForMaterial(formData.tip).map(finish => ( ))} @@ -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({
@@ -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`} diff --git a/app/upadaj/analytics/page.tsx b/app/upadaj/analytics/page.tsx index 0703c18..ad778f7 100644 --- a/app/upadaj/analytics/page.tsx +++ b/app/upadaj/analytics/page.tsx @@ -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 (
- {/* Sidebar */} -
-
-

Admin Panel

- -
-
+ {/* Main Content */}
diff --git a/app/upadaj/colors/page.tsx b/app/upadaj/colors/page.tsx index 28c9bad..d129211 100644 --- a/app/upadaj/colors/page.tsx +++ b/app/upadaj/colors/page.tsx @@ -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 (
- {/* Sidebar */} -
-
-

Admin Panel

- -
-
+ {/* Main Content */}
diff --git a/app/upadaj/customers/page.tsx b/app/upadaj/customers/page.tsx index 22533b8..7e34341 100644 --- a/app/upadaj/customers/page.tsx +++ b/app/upadaj/customers/page.tsx @@ -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 (
- {/* Sidebar */} - + {/* Main Content */}
diff --git a/app/upadaj/page.tsx b/app/upadaj/page.tsx index 610601a..35065c0 100644 --- a/app/upadaj/page.tsx +++ b/app/upadaj/page.tsx @@ -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'); diff --git a/app/upadaj/requests/page.tsx b/app/upadaj/requests/page.tsx index b933c00..f8032ec 100644 --- a/app/upadaj/requests/page.tsx +++ b/app/upadaj/requests/page.tsx @@ -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 (
-
+
+ +

Zahtevi za Boje

-
- Inventar - Boje - Prodaja - Kupci - Analitika -
{error && ( @@ -347,6 +343,7 @@ export default function ColorRequestsAdmin() {
+
); diff --git a/app/upadaj/sales/page.tsx b/app/upadaj/sales/page.tsx index 76a127d..afedb7e 100644 --- a/app/upadaj/sales/page.tsx +++ b/app/upadaj/sales/page.tsx @@ -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 (
- {/* Sidebar */} - + {/* Main Content */}
diff --git a/src/components/AdminSidebar.tsx b/src/components/AdminSidebar.tsx new file mode 100644 index 0000000..fe423ab --- /dev/null +++ b/src/components/AdminSidebar.tsx @@ -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 ( +
+
+

Admin Panel

+ +
+
+ ) +} diff --git a/src/components/EnhancedFilters.tsx b/src/components/EnhancedFilters.tsx index e427171..308c00e 100644 --- a/src/components/EnhancedFilters.tsx +++ b/src/components/EnhancedFilters.tsx @@ -1,6 +1,8 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import '@/src/styles/select.css'; import { trackEvent } from './MatomoAnalytics'; +import { getFinishesForMaterial, getAllFinishes, getMaterialOptions } from '@/src/data/bambuLabCatalog'; +import { Filament } from '@/src/types/filament'; interface EnhancedFiltersProps { filters: { @@ -8,22 +10,74 @@ interface EnhancedFiltersProps { finish: string; color: string; }; - onFilterChange: (filters: any) => void; + onFilterChange: (filters: { material: string; finish: string; color: string }) => void; uniqueValues: { materials: string[]; finishes: string[]; colors: string[]; }; + inventoryFilaments?: Filament[]; } -export const EnhancedFilters: React.FC = ({ - filters, - onFilterChange, - uniqueValues +export const EnhancedFilters: React.FC = ({ + filters, + onFilterChange, + uniqueValues, + inventoryFilaments = [] }) => { - // Check if any filters are active 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 (
{/* Filters Grid */} @@ -35,26 +89,15 @@ export const EnhancedFilters: React.FC = ({
@@ -65,35 +108,15 @@ export const EnhancedFilters: React.FC = ({
@@ -104,16 +127,13 @@ export const EnhancedFilters: React.FC = ({ @@ -141,4 +161,4 @@ export const EnhancedFilters: React.FC = ({ )}
); -}; \ No newline at end of file +}; diff --git a/src/components/FilamentTableV2.tsx b/src/components/FilamentTableV2.tsx index 5c0303f..4bad8f2 100644 --- a/src/components/FilamentTableV2.tsx +++ b/src/components/FilamentTableV2.tsx @@ -141,10 +141,11 @@ const FilamentTableV2: React.FC = ({ filaments }) => {
{/* Enhanced Filters */} - diff --git a/src/data/bambuLabCatalog.ts b/src/data/bambuLabCatalog.ts new file mode 100644 index 0000000..e563463 --- /dev/null +++ b/src/data/bambuLabCatalog.ts @@ -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>; + +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(); + 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(); + 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); +} diff --git a/src/data/bambuLabColors.ts b/src/data/bambuLabColors.ts index 51d00be..55b96aa 100644 --- a/src/data/bambuLabColors.ts +++ b/src/data/bambuLabColors.ts @@ -147,11 +147,8 @@ export const bambuLabColors: Record = { 'Yellow': { hex: '#F4EE2A' }, // ABS Colors - // ABS GF Colors 'ABS GF Yellow': { hex: '#FDD835' }, 'ABS GF Orange': { hex: '#F48438' }, - - // ABS Colors 'ABS Azure': { hex: '#489FDF' }, 'ABS Olive': { hex: '#748C45' }, 'ABS Blue': { hex: '#0A2989' }, @@ -164,7 +161,7 @@ export const bambuLabColors: Record = { 'ABS Black': { hex: '#000000' }, 'ABS Silver': { hex: '#A6A9AA' }, - // Translucent Colors + // PETG Translucent Colors 'Translucent Gray': { hex: '#B8B8B8' }, 'Translucent Brown': { hex: '#C89A74' }, 'Translucent Purple': { hex: '#C5A8D8' }, @@ -174,7 +171,15 @@ export const bambuLabColors: Record = { 'Translucent Light Blue': { hex: '#A8D8F0' }, '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 Bone White': { hex: '#C8C5B6' }, 'Matte Caramel': { hex: '#A4845C' }, @@ -185,7 +190,6 @@ export const bambuLabColors: Record = { 'Matte Dark Red': { hex: '#BB3D43' }, 'Matte Grass Green': { hex: '#7CB342' }, 'Matte Ice Blue': { hex: '#A3D8E1' }, - 'Matte Ivory': { hex: '#FFFFF0' }, 'Matte Lemon Yellow': { hex: '#F7D959' }, 'Matte Lilac Purple': { hex: '#AE96D4' }, 'Matte Plum': { hex: '#851A52' }, @@ -199,7 +203,11 @@ export const bambuLabColors: Record = { 'Silk Phantom Blue': { hex: ['#00629B', '#000000'], 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' }, // Default fallback diff --git a/src/data/bambuLabColorsComplete.ts b/src/data/bambuLabColorsComplete.ts index d1e41ca..b992aaa 100644 --- a/src/data/bambuLabColorsComplete.ts +++ b/src/data/bambuLabColorsComplete.ts @@ -1,6 +1,12 @@ // Complete Bambu Lab color database with hex codes -export const bambuLabColors = { - // Basic Colors +// Re-exports catalog-aligned color groupings + +import { BAMBU_LAB_CATALOG } from './bambuLabCatalog'; + +// Flat hex lookup (for backwards compatibility) +export const bambuLabColors: Record = { + // PLA Basic + "Jade White": "#FFFFFF", "Black": "#000000", "White": "#FFFFFF", "Red": "#E53935", @@ -8,39 +14,64 @@ export const bambuLabColors = { "Green": "#43A047", "Yellow": "#FDD835", "Orange": "#FB8C00", - "Purple": "#8E24AA", - "Pink": "#EC407A", - "Grey": "#757575", - "Brown": "#6D4C41", - "Light Blue": "#64B5F6", - "Light Green": "#81C784", - "Mint Green": "#4DB6AC", - "Lime Green": "#C0CA33", - "Sky Blue": "#81D4FA", - "Navy Blue": "#283593", - "Magenta": "#E91E63", - "Violet": "#7B1FA2", - "Beige": "#F5DEB3", - "Ivory": "#FFFFF0", - - // Matte Colors - "Matte Black": "#212121", - "Matte White": "#FAFAFA", - "Matte Red": "#C62828", - "Matte Blue": "#1565C0", - "Matte Green": "#2E7D32", - "Matte Yellow": "#F9A825", - "Matte Orange": "#EF6C00", - "Matte Purple": "#6A1B9A", - "Matte Pink": "#D81B60", - "Matte Grey": "#616161", - "Matte Brown": "#4E342E", - "Matte Mint": "#26A69A", - "Matte Lime": "#9E9D24", - "Matte Navy": "#1A237E", - "Matte Coral": "#FF5252", + "Purple": "#5E43B7", + "Pink": "#F55A74", + "Gray": "#8E9089", + "Brown": "#9D432C", + "Light Blue": "#61B0FF", + "Light Gray": "#D0D2D4", + "Sky Blue": "#73B2E5", + "Navy Blue": "#0C2340", + "Magenta": "#EC008C", + "Beige": "#F7E6DE", + "Bambu Green": "#00AE42", + "Scarlet Red": "#DE4343", + "Lemon Yellow": "#F7D959", + "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 - New 2025 + // PLA Basic Gradient + "Neon City": "#0047BB", + "Midnight Blaze": "#0047BB", + "South Beach": "#468791", + "Arctic Whisper": "#ECF7F8", + "Cotton Candy Cloud": "#E9E2EC", + "Ocean to Meadow": "#A1E4CA", + "Solar Breeze": "#F3D9D5", + "Velvet Eclipse": "#000000", + "Dawn Radiance": "#C472A1", + "Dusk Glare": "#F6B790", + "Blueberry Bubblegum": "#BADCF4", + "Blue Hawaii": "#739FE6", + "Gilded Rose": "#ED982C", + "Pink Citrus": "#F8C4BC", + "Mint Lime": "#BAF382", + + // 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 Bone White": "#C8C5B6", "Matte Caramel": "#A4845C", @@ -49,7 +80,7 @@ export const bambuLabColors = { "Matte Dark Chocolate": "#4A3729", "Matte Dark Green": "#68724D", "Matte Dark Red": "#BB3D43", - "Matte Grass Green": "#61C680", + "Matte Grass Green": "#7CB342", "Matte Ice Blue": "#A3D8E1", "Matte Lemon Yellow": "#F7D959", "Matte Lilac Purple": "#AE96D4", @@ -58,132 +89,147 @@ export const bambuLabColors = { "Matte Sky Blue": "#73B2E5", "Matte Latte Brown": "#D3B7A7", "Matte Terracotta": "#A25A37", - - // Silk Colors - "Silk White": "#FEFEFE", - "Silk Black": "#0A0A0A", - "Silk Red": "#F44336", - "Silk Blue": "#2196F3", - "Silk Green": "#4CAF50", - "Silk Gold": "#FFD54F", - "Silk Silver": "#CFD8DC", - "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+ + "Candy Green": "#408619", + "Candy Red": "#BB3A2E", + "Mint": "#A5DAB7", + "Titan Gray": "#606367", + "Rose Gold": "#B29593", + "Champagne": "#EBD0B1", + "Baby Blue": "#AEC3ED", + + // PLA Silk Multi-Color "Silk Aurora Purple": "#7F3696", "Silk Phantom Blue": "#00629B", "Silk Mystic Magenta": "#720062", - - // Metal Colors - "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) + // PLA Metal "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 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", - // Wood Colors - "Ochre Yellow": "#BC8B39", - "White Oak": "#D2CCA2", - "Clay Brown": "#8E621A" + // PC + "Clear Black": "#5A5161", + "Transparent": "#FFFFFF", }; -// Colors grouped by finish type for easier selection -export const colorsByFinish = { - "Basic": [ - "Black", "White", "Red", "Blue", "Green", "Yellow", "Orange", - "Purple", "Pink", "Grey", "Brown", "Light Blue", "Light Green", - "Mint Green", "Lime Green", "Sky Blue", "Navy Blue", "Magenta", - "Violet", "Beige", "Ivory" - ], - "Matte": [ - "Matte Black", "Matte White", "Matte Red", "Matte Blue", "Matte Green", - "Matte Yellow", "Matte Orange", "Matte Purple", "Matte Pink", "Matte Grey", - "Matte Brown", "Matte Mint", "Matte Lime", "Matte Navy", "Matte Coral", - "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": [ - "Silk White", "Silk Black", "Silk Red", "Silk Blue", "Silk Green", - "Silk Gold", "Silk Silver", "Silk Purple", "Silk Pink", "Silk Orange", - "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" - ] -}; +// Colors grouped by finish type — derived from catalog +export const colorsByFinish: Record = {}; + +// Build colorsByFinish from catalog +for (const [, finishes] of Object.entries(BAMBU_LAB_CATALOG)) { + for (const [finishName, finishData] of Object.entries(finishes)) { + if (!colorsByFinish[finishName]) { + colorsByFinish[finishName] = []; + } + for (const color of finishData.colors) { + if (!colorsByFinish[finishName].includes(color.name)) { + colorsByFinish[finishName].push(color.name); + } + } + } +} + +// Sort each finish's colors +for (const finish of Object.keys(colorsByFinish)) { + colorsByFinish[finish].sort(); +} // Function to get hex code for a color 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 export function getColorsForFinish(finish: string): string[] { - return colorsByFinish[finish as keyof typeof colorsByFinish] || []; -} \ No newline at end of file + return colorsByFinish[finish] || []; +}