Quellcode des ICON CRM (1)
This commit is contained in:
+149
@@ -0,0 +1,149 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Sidebar } from './components/Sidebar';
|
||||
import { Header } from './components/Header';
|
||||
import { Dashboard } from './components/Dashboard';
|
||||
import { KanbanBoard } from './components/KanbanBoard';
|
||||
import { Tenders } from './components/Tenders';
|
||||
import { Customers } from './components/Customers';
|
||||
import { Experts } from './components/Experts';
|
||||
import { Projects } from './components/Projects';
|
||||
import { OrgChart } from './components/OrgChart';
|
||||
import { Settings } from './components/Settings';
|
||||
import { ViewState, Tender, Expert, Customer, Project } from './types';
|
||||
import { supabase, TABLES } from './lib/supabase';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [currentView, setCurrentView] = useState<ViewState>('Dashboard');
|
||||
const [isDarkMode, setIsDarkMode] = useState<boolean>(true);
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
||||
|
||||
const [tenders, setTenders] = useState<Tender[]>([]);
|
||||
const [customers, setCustomers] = useState<Customer[]>([]);
|
||||
const [experts, setExperts] = useState<Expert[]>([]);
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [isInitialLoading, setIsInitialLoading] = useState(true);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
const fetchData = async (showRefreshIndicator = false) => {
|
||||
if (showRefreshIndicator) setIsRefreshing(true);
|
||||
try {
|
||||
const { data: customersData } = await supabase
|
||||
.from(TABLES.CUSTOMERS)
|
||||
.select('*, icon_contacts(*)');
|
||||
|
||||
const formattedCustomers = (customersData || []).map(c => ({
|
||||
...c,
|
||||
contacts: c.icon_contacts || []
|
||||
}));
|
||||
setCustomers(formattedCustomers);
|
||||
|
||||
const { data: expertsData } = await supabase.from(TABLES.EXPERTS).select('*');
|
||||
setExperts(expertsData || []);
|
||||
|
||||
const { data: projectsData } = await supabase.from(TABLES.PROJECTS).select('*');
|
||||
setProjects(projectsData || []);
|
||||
|
||||
const { data: tendersData } = await supabase
|
||||
.from(TABLES.TENDERS)
|
||||
.select(`
|
||||
*,
|
||||
selectedCustomer:icon_customers(*),
|
||||
selectedExperts:icon_tender_experts(icon_experts(*))
|
||||
`)
|
||||
.neq('status', 'Archiviert');
|
||||
|
||||
if (tendersData) {
|
||||
const formattedTenders: Tender[] = tendersData.map(t => ({
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
source: t.source,
|
||||
link: t.link,
|
||||
details: t.details,
|
||||
notes: t.notes,
|
||||
status: t.status,
|
||||
kanbanStatus: t.kanban_status,
|
||||
selectedCustomer: t.selectedCustomer,
|
||||
selectedExperts: t.selectedExperts?.map((se: any) => se.icon_experts) || [],
|
||||
applicantsCount: t.selectedExperts?.length || 0
|
||||
}));
|
||||
setTenders(formattedTenders);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
} finally {
|
||||
setIsInitialLoading(false);
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDarkMode) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
}, [isDarkMode]);
|
||||
|
||||
const toggleDarkMode = () => setIsDarkMode(!isDarkMode);
|
||||
|
||||
if (isInitialLoading) {
|
||||
return (
|
||||
<div className="h-screen w-screen flex items-center justify-center bg-slate-950">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="w-12 h-12 border-4 border-brand-500 border-t-transparent rounded-full animate-spin"></div>
|
||||
<p className="text-white font-bold tracking-widest animate-pulse">ICON DATABASE CONNECTING...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderContent = () => {
|
||||
switch (currentView) {
|
||||
case 'Dashboard': return <Dashboard />;
|
||||
case 'Live!': return <KanbanBoard tenders={tenders} setTenders={setTenders} refreshData={() => fetchData(true)} />;
|
||||
case 'Ausschreibungen': return <Tenders tenders={tenders} setTenders={setTenders} customers={customers} experts={experts} refreshData={() => fetchData(true)} />;
|
||||
case 'Kunden': return <Customers customers={customers} refreshData={() => fetchData(true)} />;
|
||||
case 'Experten': return <Experts experts={experts} setExperts={setExperts} refreshData={() => fetchData(true)} />;
|
||||
case 'Laufende Projekte': return <Projects projects={projects} setProjects={setProjects} customers={customers} experts={experts} refreshData={() => fetchData(true)} />;
|
||||
case 'Organigramm': return <OrgChart customers={customers} projects={projects} experts={experts} />;
|
||||
case 'Einstellungen': return <Settings isDarkMode={isDarkMode} onToggleDarkMode={toggleDarkMode} />;
|
||||
default: return <Dashboard />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-slate-50 dark:bg-slate-950 text-slate-900 dark:text-slate-100 font-sans">
|
||||
<Sidebar
|
||||
currentView={currentView}
|
||||
onViewChange={setCurrentView}
|
||||
isOpen={isSidebarOpen}
|
||||
onToggle={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
/>
|
||||
|
||||
<main className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
<Header
|
||||
title={currentView}
|
||||
isDarkMode={isDarkMode}
|
||||
onToggleDarkMode={toggleDarkMode}
|
||||
onToggleSidebar={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
/>
|
||||
<div className="flex-1 overflow-y-auto p-4 md:p-8 relative">
|
||||
{isRefreshing && (
|
||||
<div className="absolute top-4 right-8 z-50 flex items-center gap-2 bg-white/80 dark:bg-slate-900/80 px-4 py-2 rounded-full shadow-lg border border-slate-100 dark:border-slate-800 backdrop-blur-sm animate-in fade-in slide-in-from-top-2">
|
||||
<div className="w-4 h-4 border-2 border-brand-500 border-t-transparent rounded-full animate-spin"></div>
|
||||
<span className="text-[10px] font-bold uppercase tracking-widest">Synchronisiere...</span>
|
||||
</div>
|
||||
)}
|
||||
{renderContent()}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user