feat: gekleurde iconen in sidebar + dashboard (legacy icon_colors)

This commit is contained in:
mo
2026-07-21 22:00:39 +02:00
parent 3c49df64ae
commit 345bc84640
10 changed files with 558 additions and 489 deletions
+26 -24
View File
@@ -7,56 +7,58 @@ import {
Search, Bell, LogOut, Menu, X, ExternalLink, ShieldCheck, PenTool, FileSearch
} from 'lucide-react';
import { useAuth } from '../auth';
import { useSettings, iconColor, brandName } from '../settings';
import { api } from '../api';
const NAV = [
{
label: 'CRM',
items: [
{ to: '/', icon: LayoutDashboard, label: 'Dashboard', end: true },
{ to: '/clients', icon: Users, label: 'Cliënten' },
{ to: '/engagements', icon: Briefcase, label: 'Engagements' },
{ to: '/tasks', icon: CheckCircle2, label: 'Taken' },
{ to: '/calendar', icon: Calendar, label: 'Agenda' },
{ to: '/email', icon: Mail, label: 'Email' }
{ to: '/', icon: LayoutDashboard, iconName: 'layout-dashboard', label: 'Dashboard', end: true },
{ to: '/clients', icon: Users, iconName: 'users', label: 'Cliënten' },
{ to: '/engagements', icon: Briefcase, iconName: 'briefcase', label: 'Engagements' },
{ to: '/tasks', icon: CheckCircle2, iconName: 'check-circle', label: 'Taken' },
{ to: '/calendar', icon: Calendar, iconName: 'calendar', label: 'Agenda' },
{ to: '/email', icon: Mail, iconName: 'mail', label: 'Email' }
]
},
{
label: 'Delivery',
items: [
{ to: '/projects', icon: FolderKanban, label: 'Projecten' },
{ to: '/time', icon: Clock, label: 'Uren' },
{ to: '/invoices', icon: Receipt, label: 'Facturen' },
{ to: '/finance', icon: Wallet, label: 'Finance' },
{ to: '/reports', icon: BarChart3, label: 'Rapporten' }
{ to: '/projects', icon: FolderKanban, iconName: 'folder-kanban', label: 'Projecten' },
{ to: '/time', icon: Clock, iconName: 'clock', label: 'Uren' },
{ to: '/invoices', icon: Receipt, iconName: 'receipt', label: 'Facturen' },
{ to: '/finance', icon: Wallet, iconName: 'wallet', label: 'Finance' },
{ to: '/reports', icon: BarChart3, iconName: 'bar-chart-3', label: 'Rapporten' }
]
},
{
label: 'Data & Infra',
items: [
{ to: '/racks', icon: Server, label: 'Racks' },
{ to: '/networking', icon: Share2, label: 'Netwerk' },
{ to: '/diagrams', icon: Network, label: 'Diagrammen' },
{ to: '/market', icon: Radar, label: 'Market Intel' },
{ to: '/ai', icon: Sparkles, label: 'AI Studio' }
{ to: '/racks', icon: Server, iconName: 'server', label: 'Racks' },
{ to: '/networking', icon: Share2, iconName: 'share-2', label: 'Netwerk' },
{ to: '/diagrams', icon: Network, iconName: 'network', label: 'Diagrammen' },
{ to: '/market', icon: Radar, iconName: 'radar', label: 'Market Intel' },
{ to: '/ai', icon: Sparkles, iconName: 'sparkles', label: 'AI Studio' }
]
},
{
label: 'Platform',
items: [
{ to: '/settings', icon: Settings, label: 'Instellingen' }
{ to: '/settings', icon: Settings, iconName: 'settings', label: 'Instellingen' }
]
}
];
const LEGACY_LINKS = [
{ href: '/quality', icon: ShieldCheck, label: 'Data Quality' },
{ href: '/architecture/designer', icon: PenTool, label: 'Pro Designer' },
{ href: '/assess', icon: FileSearch, label: 'Assessments' }
{ href: '/quality', icon: ShieldCheck, iconName: 'shield-check', label: 'Data Quality' },
{ href: '/architecture/designer', icon: PenTool, iconName: 'pen-tool', label: 'Pro Designer' },
{ href: '/assess', icon: FileSearch, iconName: 'file-search', label: 'Assessments' }
];
export default function Layout({ children }) {
const { user, logout } = useAuth();
const { settings } = useSettings();
const navigate = useNavigate();
const [mobileOpen, setMobileOpen] = useState(false);
const [unread, setUnread] = useState(0);
@@ -79,10 +81,10 @@ export default function Layout({ children }) {
<div className="flex flex-col h-full">
<div className="px-5 py-5 flex items-center gap-3 border-b border-border-soft">
<div className="w-9 h-9 rounded-lg bg-accent-soft flex items-center justify-center glow-accent">
<Network className="w-5 h-5 text-accent" />
<Network style={{ color: iconColor('network', settings) || '#00d4ff' }} className="w-5 h-5" />
</div>
<div>
<div className="font-bold text-[15px] tracking-tight">Mek-Tech</div>
<div className="font-bold text-[15px] tracking-tight">{brandName(settings)}</div>
<div className="text-[11px] text-muted">Consultancy Platform</div>
</div>
</div>
@@ -108,7 +110,7 @@ export default function Layout({ children }) {
}`
}
>
<item.icon className="w-4 h-4 shrink-0" />
<item.icon className="w-4 h-4 shrink-0" style={{ color: iconColor(item.iconName, settings) || 'inherit' }} />
{item.label}
</NavLink>
))}
@@ -127,7 +129,7 @@ export default function Layout({ children }) {
href={item.href}
className="flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-[13px] text-muted hover:text-text hover:bg-card transition-colors"
>
<item.icon className="w-4 h-4 shrink-0" />
<item.icon className="w-4 h-4 shrink-0" style={{ color: iconColor(item.iconName, settings) || 'inherit' }} />
{item.label}
<ExternalLink className="w-3 h-3 ml-auto opacity-50" />
</a>
+2 -2
View File
@@ -57,7 +57,7 @@ export function Card({ title, subtitle, actions, children, className = '', paddi
);
}
export function KpiCard({ icon: Icon, label, value, sub, color = 'accent', to }) {
export function KpiCard({ icon: Icon, label, value, sub, color = 'accent', to, iconColor: iconCol }) {
const colors = {
accent: 'text-accent bg-accent-soft',
green: 'text-green bg-green/15',
@@ -76,7 +76,7 @@ export function KpiCard({ icon: Icon, label, value, sub, color = 'accent', to })
</div>
{Icon && (
<div className={`w-9 h-9 rounded-lg flex items-center justify-center ${colors[color] || colors.accent}`}>
<Icon className="w-4.5 h-4.5 w-[18px] h-[18px]" />
<Icon className="w-[18px] h-[18px]" style={{ color: iconCol || 'currentColor' }} />
</div>
)}
</div>
+6 -3
View File
@@ -3,14 +3,17 @@ import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import { AuthProvider } from './auth';
import { SettingsProvider } from './settings';
import './index.css';
createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter basename="/app">
<AuthProvider>
<App />
</AuthProvider>
<SettingsProvider>
<AuthProvider>
<App />
</AuthProvider>
</SettingsProvider>
</BrowserRouter>
</React.StrictMode>
);
+8 -6
View File
@@ -4,6 +4,7 @@ import {
Users, Briefcase, CheckCircle2, Clock, Receipt, Wallet,
AlertTriangle, Lightbulb, Activity, ArrowRight
} from 'lucide-react';
import { useSettings, iconColor } from '../settings';
import {
ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid,
PieChart, Pie, Cell, Legend
@@ -29,6 +30,7 @@ const tooltipStyle = {
};
export default function Dashboard() {
const { settings } = useSettings();
const [data, setData] = useState(null);
const [error, setError] = useState(null);
@@ -62,12 +64,12 @@ export default function Dashboard() {
{/* KPI's */}
<div className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-3">
<KpiCard icon={Users} label="Cliënten" value={stats.totalClients} sub={`${stats.activeClients} actief · ${stats.leads} leads`} color="accent" />
<KpiCard icon={Briefcase} label="Lopende engagements" value={stats.activeEngagements} color="blue" />
<KpiCard icon={CheckCircle2} label="Open taken" value={stats.totalTasks - stats.completedTasks} sub={stats.overdueTasks > 0 ? `${stats.overdueTasks} te laat` : undefined} color={stats.overdueTasks > 0 ? 'red' : 'green'} />
<KpiCard icon={Clock} label="Uren totaal" value={fmt.hours(stats.totalHours)} sub={`${fmt.hours(stats.billableHours)} facturabel`} color="purple" />
<KpiCard icon={Receipt} label="Openstaand" value={fmt.euro(stats.totalOutstanding)} color="yellow" />
<KpiCard icon={Wallet} label="Betaald" value={fmt.euro(stats.totalPaid)} color="green" />
<KpiCard icon={Users} label="Cliënten" value={stats.totalClients} sub={`${stats.activeClients} actief · ${stats.leads} leads`} color="accent" iconColor={iconColor('users', settings)} />
<KpiCard icon={Briefcase} label="Lopende engagements" value={stats.activeEngagements} color="blue" iconColor={iconColor('briefcase', settings)} />
<KpiCard icon={CheckCircle2} label="Open taken" value={stats.totalTasks - stats.completedTasks} sub={stats.overdueTasks > 0 ? `${stats.overdueTasks} te laat` : undefined} color={stats.overdueTasks > 0 ? 'red' : 'green'} iconColor={iconColor('check-circle', settings)} />
<KpiCard icon={Clock} label="Uren totaal" value={fmt.hours(stats.totalHours)} sub={`${fmt.hours(stats.billableHours)} facturabel`} color="purple" iconColor={iconColor('clock', settings)} />
<KpiCard icon={Receipt} label="Openstaand" value={fmt.euro(stats.totalOutstanding)} color="yellow" iconColor={iconColor('receipt', settings)} />
<KpiCard icon={Wallet} label="Betaald" value={fmt.euro(stats.totalPaid)} color="green" iconColor={iconColor('wallet', settings)} />
</div>
{/* Grafieken */}
+62
View File
@@ -0,0 +1,62 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { api } from './api';
// Legacy standaard-icoonkleuren (fallback als gebruiker ze niet zelf heeft ingesteld)
const DEFAULT_ICON_COLORS = {
'layout-dashboard': '#58a6ff', 'users': '#3fb950', 'briefcase': '#d29922',
'server': '#bc8cff', 'check-circle': '#3fb950', 'lightbulb': '#d29922',
'clock': '#79c0ff', 'eye': '#f778ba', 'phone': '#3fb950', 'file-text': '#f778ba',
'edit-3': '#79c0ff', 'folder': '#d29922', 'clipboard-list': '#bc8cff',
'mail': '#58a6ff', 'calendar': '#79c0ff', 'receipt': '#d29922', 'wallet': '#3fb950',
'bar-chart-3': '#bc8cff', 'share-2': '#58a6ff', 'network': '#00d4ff', 'radar': '#d29922',
'sparkles': '#bc8cff', 'settings': '#8b9bb8', 'shield-check': '#3fb950',
'pen-tool': '#bc8cff', 'file-search': '#d29922', 'folder-kanban': '#d29922', 'cpu': '#58a6ff',
'building-2': '#bc8cff', 'activity': '#58a6ff', 'search': '#8b9bb8', 'bell': '#d29922',
'plus': '#00d4ff', 'trash-2': '#f85149', 'pencil': '#79c0ff', 'star': '#d29922',
'map-pin': '#58a6ff', 'globe': '#58a6ff', 'send': '#00d4ff', 'download': '#79c0ff',
'upload': '#79c0ff', 'save': '#3fb950', 'refresh-cw': '#58a6ff', 'filter': '#8b9bb8',
'more-vertical': '#8b9bb8', 'x': '#8b9bb8', 'menu': '#8b9bb8', 'external-link': '#8b9bb8',
'log-out': '#8b9bb8', 'trending-up': '#3fb950', 'pie-chart': '#bc8cff', 'target': '#d29922',
'shield': '#3fb950', 'zap': '#bc8cff', 'database': '#58a6ff', 'cloud': '#79c0ff',
'hard-drive': '#bc8cff', 'monitor': '#58a6ff', 'git-branch': '#f778ba', 'package': '#d29922',
'terminal': '#79c0ff', 'code': '#58a6ff', 'layers': '#bc8cff', 'link': '#58a6ff',
'unlink': '#8b9bb8', 'copy': '#79c0ff', 'check': '#3fb950', 'alert-triangle': '#d29922',
'info': '#58a6ff', 'help-circle': '#8b9bb8', 'type': '#79c0ff', 'columns': '#bc8cff',
'table': '#58a6ff', 'list': '#79c0ff', 'grid': '#bc8cff', 'tag': '#d29922',
'calendar-days': '#79c0ff', 'mail-open': '#58a6ff', 'inbox': '#58a6ff', 'send-horizontal': '#00d4ff',
'paperclip': '#8b9bb8', 'users-round': '#3fb950', 'projector': '#bc8cff', 'kanban-square': '#d29922',
'bar-chart': '#bc8cff', 'line-chart': '#58a6ff', 'activity-square': '#3fb950',
'server': '#bc8cff', 'wifi': '#58a6ff', 'ethernet-port': '#58a6ff', 'router': '#bc8cff',
'switch': '#3fb950', 'firewall': '#f85149', 'storage': '#bc8cff', 'ups': '#d29922',
'pdu': '#79c0ff', 'cloud-upload': '#58a6ff', 'cloud-download': '#58a6ff',
'brain-circuit': '#bc8cff', 'message-square': '#58a6ff', 'bot': '#bc8cff',
'key': '#f778ba', 'shield-check-big': '#3fb950', 'lock': '#79c0ff', 'unlock': '#8b9bb8'
};
const SettingsContext = createContext({ settings: null, loading: true });
export function SettingsProvider({ children }) {
const [settings, setSettings] = useState(null);
useEffect(() => {
api.get('/settings').then(setSettings).catch(() => setSettings(null));
}, []);
return (
<SettingsContext.Provider value={{ settings, loading: !settings }}>
{children}
</SettingsContext.Provider>
);
}
export function useSettings() {
return useContext(SettingsContext);
}
// Geef de kleur voor een icoon terug (user-instelling of legacy default)
export function iconColor(name, settings) {
if (settings?.icon_colors && settings.icon_colors[name]) return settings.icon_colors[name];
return DEFAULT_ICON_COLORS[name] || null;
}
export function brandName(settings) {
return settings?.brand_name || 'Mek-Tech';
}