feat: uwuFinances app with themes, JWT auth and NoCodeBackend sync

This commit is contained in:
streaper2
2026-09-27 17:59:36 +02:00
commit 88f23caf3b
55 changed files with 20928 additions and 0 deletions

View File

@@ -0,0 +1,212 @@
import React from 'react';
import {
LayoutDashboard,
ShoppingCart,
Layers,
Clock,
BarChart3,
Settings as SettingsIcon,
Sparkles,
PackagePlus,
Coins,
Cpu,
Database,
RefreshCw,
} from 'lucide-react';
import { useApp } from '../../context/AppContext';
import { formatCurrency } from '../../utils/calculations';
export const TopHeader: React.FC = () => {
const {
currentTab,
inventory,
projects,
dbSyncStatus,
setShowNewProjectModal,
setShowNewInventoryModal,
authSession,
setShowAuthModal,
} = useApp();
// Quick topbar calculations
const totalStockValuation = inventory.reduce(
(acc, item) =>
acc +
item.quantityInStock *
(item.unitPurchasePrice + (item.shippingCostShare || 0) + (item.taxImportShare || 0)),
0
);
const activeProjectsCount = projects.filter(
(p) => p.status === 'in_progress' || p.status === 'qc_testing'
).length;
const getPageInfo = () => {
switch (currentTab) {
case 'dashboard':
return {
title: 'Tableau de Bord & Finances',
subtitle: 'Vue d\'ensemble des performances, graphiques et pipeline d\'atelier',
icon: LayoutDashboard,
iconColor: 'text-pink-400',
};
case 'purchases':
return {
title: 'Achats & Gestion des Stocks',
subtitle: 'Consoles donneuses, écrans IPS, coques, composants et alertes de stock',
icon: ShoppingCart,
iconColor: 'text-cyan-400',
};
case 'projects':
return {
title: 'Centre de Dépenses Projets',
subtitle: 'Nomenclature pièces (BOM), temps d\'atelier, coût de revient et certificats',
icon: Layers,
iconColor: 'text-purple-400',
};
case 'timetracker':
return {
title: 'Chronomètre d\'Atelier & Main d\'Œuvre',
subtitle: 'Chronométrage des sessions de modding et valorisation horaire en direct',
icon: Clock,
iconColor: 'text-amber-400',
};
case 'analytics':
return {
title: 'Rentabilité & Simulateur de Prix',
subtitle: 'Calculateur de marge nette, point mort (break-even) et analyse par console',
icon: BarChart3,
iconColor: 'text-emerald-400',
};
case 'settings':
return {
title: 'Paramètres & Sauvegardes',
subtitle: 'Identité de l\'atelier, taux horaire par défaut, thèmes et connexion NoCodeBackend',
icon: SettingsIcon,
iconColor: 'text-slate-300',
};
default:
return {
title: 'uwuFinances',
subtitle: 'Console Modding Studio Hub',
icon: LayoutDashboard,
iconColor: 'text-pink-400',
};
}
};
const pageInfo = getPageInfo();
const Icon = pageInfo.icon;
return (
<header className="sticky top-0 z-30 w-full theme-header backdrop-blur-xl border-b border-slate-800/80 px-4 sm:px-8 py-3.5 flex items-center justify-between no-print">
{/* Page Title & Breadcrumb */}
<div className="flex items-center space-x-3.5">
<div className="p-2 rounded-xl bg-slate-900 border border-slate-800 hidden sm:flex items-center justify-center shadow-inner">
<Icon className={`w-5 h-5 ${pageInfo.iconColor}`} />
</div>
<div>
<h2 className="text-base sm:text-lg font-bold text-white tracking-tight flex items-center space-x-2">
<span>{pageInfo.title}</span>
</h2>
<p className="text-xs text-slate-400 hidden md:block">
{pageInfo.subtitle}
</p>
</div>
</div>
{/* Right Stats & Quick Actions */}
<div className="flex items-center space-x-2 sm:space-x-3">
{/* User Profile & JWT Button */}
<button
onClick={() => setShowAuthModal(true)}
className={`flex items-center space-x-2 px-2.5 py-1.5 rounded-xl text-xs border transition-all cursor-pointer ${
authSession.isAuthenticated
? 'bg-slate-900 border-pink-500/30 hover:border-pink-500/60 text-slate-200'
: 'bg-slate-900/60 border-slate-800 hover:border-slate-700 text-slate-400'
}`}
title="Gérer la session utilisateur, le jeton JWT et la liaison NCB"
>
<div className="relative">
<img
src={authSession.user?.avatarUrl || 'https://api.dicebear.com/7.x/bottts/svg?seed=uwu'}
alt={authSession.user?.name}
className="w-5 h-5 rounded-full bg-slate-800 object-cover border border-slate-700"
/>
{authSession.isAuthenticated && (
<span className="absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
)}
</div>
<span className="font-semibold text-xs text-slate-200 hidden md:inline truncate max-w-[120px]">
{authSession.user?.name?.split(' ')[0] || 'Utilisateur'}
</span>
<span className="px-1.5 py-0.2 rounded text-[9px] font-mono font-bold uppercase bg-pink-500/15 text-pink-300 hidden lg:inline border border-pink-500/30">
JWT
</span>
</button>
{/* Cloud Database Status indicator */}
<button
onClick={() => setShowAuthModal(true)}
className={`hidden md:flex items-center space-x-1.5 px-2.5 py-1.5 rounded-xl text-xs border transition-all cursor-pointer ${
dbSyncStatus === 'connected'
? 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 hover:bg-emerald-500/20'
: dbSyncStatus === 'syncing'
? 'bg-amber-500/10 text-amber-400 border-amber-500/30'
: 'bg-rose-500/10 text-rose-400 border-rose-500/30 hover:bg-rose-500/20'
}`}
title="Statut Cloud Database NoCodeBackend (54770_uwu_finances) - Cliquez pour gérer"
>
{dbSyncStatus === 'syncing' ? (
<RefreshCw className="w-3 h-3 animate-spin text-amber-400" />
) : (
<span className={`w-2 h-2 rounded-full ${
dbSyncStatus === 'connected' ? 'bg-emerald-400 animate-pulse' : 'bg-rose-400'
}`} />
)}
<Database className="w-3 h-3 text-slate-400" />
<span className="font-mono text-[11px] font-semibold">NCB EU</span>
</button>
{/* Quick Inventory Stock Valuation badge */}
<div className="hidden xl:flex items-center space-x-2 px-3 py-1.5 rounded-xl bg-slate-900/80 border border-slate-800 text-xs">
<Coins className="w-3.5 h-3.5 text-cyan-400" />
<span className="text-slate-400">Stock valorisé:</span>
<span className="font-mono font-bold text-cyan-300">
{formatCurrency(totalStockValuation)}
</span>
</div>
{/* Active Projects badge */}
<div className="hidden xl:flex items-center space-x-2 px-3 py-1.5 rounded-xl bg-slate-900/80 border border-slate-800 text-xs">
<Cpu className="w-3.5 h-3.5 text-purple-400" />
<span className="text-slate-400">En cours:</span>
<span className="font-mono font-bold text-purple-300">
{activeProjectsCount} projet{activeProjectsCount > 1 ? 's' : ''}
</span>
</div>
{/* Quick Add Part */}
<button
onClick={() => setShowNewInventoryModal(true)}
className="flex items-center space-x-1.5 px-3 py-1.5 sm:px-3.5 sm:py-2 rounded-xl text-xs sm:text-sm font-semibold bg-slate-900 text-cyan-300 border border-cyan-500/30 hover:bg-cyan-500/10 hover:border-cyan-500/60 shadow-sm transition-all duration-200"
title="Ajouter un achat de pièces au stock"
>
<PackagePlus className="w-4 h-4 text-cyan-400" />
<span className="hidden sm:inline">Achat Pièce</span>
</button>
{/* Quick New Project */}
<button
onClick={() => setShowNewProjectModal(true)}
className="flex items-center space-x-1.5 px-3 py-1.5 sm:px-3.5 sm:py-2 rounded-xl text-xs sm:text-sm font-bold bg-gradient-to-r from-pink-500 via-purple-600 to-pink-600 text-white shadow-lg shadow-pink-500/20 hover:shadow-pink-500/35 hover:scale-[1.02] active:scale-[0.98] transition-all duration-200"
title="Créer une nouvelle console custom"
>
<Sparkles className="w-4 h-4 text-pink-200" />
<span>Nouveau Projet</span>
</button>
</div>
</header>
);
};