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>
+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">
<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';
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mek-Tech — Consultancy Platform</title>
<script type="module" crossorigin src="/app/assets/index-CQEBt6sl.js"></script>
<link rel="stylesheet" crossorigin href="/app/assets/index-A3FsIOhP.css">
<script type="module" crossorigin src="/app/assets/index-BeV6U3Lw.js"></script>
<link rel="stylesheet" crossorigin href="/app/assets/index-Bc_GiMLN.css">
</head>
<body>
<div id="root"></div>