mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
added website navigation structur
This commit is contained in:
@@ -1,79 +1,90 @@
|
||||
// frontend/src/App.tsx
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
||||
import Layout from './components/Layout/Layout';
|
||||
import Login from './pages/Auth/Login';
|
||||
import Dashboard from './pages/Dashboard/Dashboard';
|
||||
import ShiftTemplateList from './pages/ShiftTemplates/ShiftTemplateList';
|
||||
import ShiftTemplateEditor from './pages/ShiftTemplates/ShiftTemplateEditor';
|
||||
import ShiftPlanList from './pages/ShiftPlans/ShiftPlanList';
|
||||
import ShiftPlanCreate from './pages/ShiftPlans/ShiftPlanCreate';
|
||||
import EmployeeManagement from './pages/Employees/EmployeeManagement';
|
||||
import Settings from './pages/Settings/Settings';
|
||||
import Help from './pages/Help/Help';
|
||||
|
||||
// Protected Route Component
|
||||
const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { user, loading } = useAuth();
|
||||
const ProtectedRoute: React.FC<{ children: React.ReactNode; roles?: string[] }> = ({
|
||||
children,
|
||||
roles = ['admin', 'instandhalter', 'user']
|
||||
}) => {
|
||||
const { user, loading, hasRole } = useAuth();
|
||||
|
||||
if (loading) {
|
||||
return <div style={{ padding: '20px' }}>Lade...</div>;
|
||||
return (
|
||||
<Layout>
|
||||
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||
<div>⏳ Lade Anwendung...</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
return user ? <>{children}</> : <Navigate to="/login" replace />;
|
||||
};
|
||||
|
||||
const PublicRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { user, loading } = useAuth();
|
||||
|
||||
if (loading) {
|
||||
return <div style={{ padding: '20px' }}>Lade...</div>;
|
||||
if (!user || !hasRole(roles)) {
|
||||
return <Login />;
|
||||
}
|
||||
|
||||
return !user ? <>{children}</> : <Navigate to="/" replace />;
|
||||
return <Layout>{children}</Layout>;
|
||||
};
|
||||
|
||||
function AppRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
{/* Public Route - nur für nicht eingeloggte User */}
|
||||
<Route path="/login" element={
|
||||
<PublicRoute>
|
||||
<Login />
|
||||
</PublicRoute>
|
||||
} />
|
||||
|
||||
{/* Protected Routes - nur für eingeloggte User */}
|
||||
<Route path="/" element={
|
||||
<ProtectedRoute>
|
||||
<Dashboard />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
<Route path="/shift-templates" element={
|
||||
<ProtectedRoute>
|
||||
<ShiftTemplateList />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
<Route path="/shift-templates/new" element={
|
||||
<ProtectedRoute>
|
||||
<ShiftTemplateEditor />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
<Route path="/shift-templates/:id" element={
|
||||
<ProtectedRoute>
|
||||
<ShiftTemplateEditor />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
{/* Fallback Route */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<AppRoutes />
|
||||
<Routes>
|
||||
{/* Public Route */}
|
||||
<Route path="/login" element={<Login />} />
|
||||
|
||||
{/* Protected Routes with Layout */}
|
||||
<Route path="/" element={
|
||||
<ProtectedRoute>
|
||||
<Dashboard />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
<Route path="/shift-plans" element={
|
||||
<ProtectedRoute>
|
||||
<ShiftPlanList />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
<Route path="/shift-plans/new" element={
|
||||
<ProtectedRoute roles={['admin', 'instandhalter']}>
|
||||
<ShiftPlanCreate />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
<Route path="/employees" element={
|
||||
<ProtectedRoute roles={['admin', 'instandhalter']}>
|
||||
<EmployeeManagement />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
<Route path="/settings" element={
|
||||
<ProtectedRoute roles={['admin']}>
|
||||
<Settings />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
<Route path="/help" element={
|
||||
<ProtectedRoute>
|
||||
<Help />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
{/* Legal Pages (ohne Layout für einfacheren Zugang) */}
|
||||
<Route path="/impressum" element={<div>Impressum Seite</div>} />
|
||||
<Route path="/datenschutz" element={<div>Datenschutz Seite</div>} />
|
||||
<Route path="/agb" element={<div>AGB Seite</div>} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
98
frontend/src/components/Layout/Footer.tsx
Normal file
98
frontend/src/components/Layout/Footer.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
// frontend/src/components/Layout/Footer.tsx
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
return (
|
||||
<footer style={{
|
||||
backgroundColor: '#34495e',
|
||||
color: 'white',
|
||||
padding: '30px 20px',
|
||||
marginTop: 'auto'
|
||||
}}>
|
||||
<div style={{
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
||||
gap: '30px'
|
||||
}}>
|
||||
{/* App Info */}
|
||||
<div>
|
||||
<h4 style={{ marginBottom: '15px' }}>🗓️ SchichtPlaner</h4>
|
||||
<p style={{ fontSize: '14px', lineHeight: '1.5' }}>
|
||||
Einfache Schichtplanung für Ihr Team.
|
||||
Optimierte Arbeitszeiten, transparente Planung.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div>
|
||||
<h4 style={{ marginBottom: '15px' }}>Schnellzugriff</h4>
|
||||
<ul style={{ listStyle: 'none', padding: 0, fontSize: '14px' }}>
|
||||
<li style={{ marginBottom: '8px' }}>
|
||||
<Link to="/help" style={{ color: '#bdc3c7', textDecoration: 'none' }}>
|
||||
📖 Anleitung
|
||||
</Link>
|
||||
</li>
|
||||
<li style={{ marginBottom: '8px' }}>
|
||||
<Link to="/help/faq" style={{ color: '#bdc3c7', textDecoration: 'none' }}>
|
||||
❓ Häufige Fragen
|
||||
</Link>
|
||||
</li>
|
||||
<li style={{ marginBottom: '8px' }}>
|
||||
<Link to="/help/support" style={{ color: '#bdc3c7', textDecoration: 'none' }}>
|
||||
💬 Support
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Legal Links */}
|
||||
<div>
|
||||
<h4 style={{ marginBottom: '15px' }}>Rechtliches</h4>
|
||||
<ul style={{ listStyle: 'none', padding: 0, fontSize: '14px' }}>
|
||||
<li style={{ marginBottom: '8px' }}>
|
||||
<Link to="/impressum" style={{ color: '#bdc3c7', textDecoration: 'none' }}>
|
||||
📄 Impressum
|
||||
</Link>
|
||||
</li>
|
||||
<li style={{ marginBottom: '8px' }}>
|
||||
<Link to="/datenschutz" style={{ color: '#bdc3c7', textDecoration: 'none' }}>
|
||||
🔒 Datenschutz
|
||||
</Link>
|
||||
</li>
|
||||
<li style={{ marginBottom: '8px' }}>
|
||||
<Link to="/agb" style={{ color: '#bdc3c7', textDecoration: 'none' }}>
|
||||
📝 AGB
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Contact */}
|
||||
<div>
|
||||
<h4 style={{ marginBottom: '15px' }}>Kontakt</h4>
|
||||
<div style={{ fontSize: '14px', color: '#bdc3c7' }}>
|
||||
<p>📧 support@schichtplaner.de</p>
|
||||
<p>📞 +49 123 456 789</p>
|
||||
<p>🕘 Mo-Fr: 9:00-17:00</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
borderTop: '1px solid #2c3e50',
|
||||
marginTop: '30px',
|
||||
paddingTop: '20px',
|
||||
textAlign: 'center',
|
||||
fontSize: '12px',
|
||||
color: '#95a5a6'
|
||||
}}>
|
||||
<p>© 2024 SchichtPlaner. Alle Rechte vorbehalten.</p>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
34
frontend/src/components/Layout/Layout.tsx
Normal file
34
frontend/src/components/Layout/Layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
// frontend/src/components/Layout/Layout.tsx
|
||||
import React from 'react';
|
||||
import Navigation from './Navigation';
|
||||
import Footer from './Footer';
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
return (
|
||||
<div style={{
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}>
|
||||
<Navigation />
|
||||
|
||||
<main style={{
|
||||
flex: 1,
|
||||
padding: '20px',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
width: '100%'
|
||||
}}>
|
||||
{children}
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
189
frontend/src/components/Layout/Navigation.tsx
Normal file
189
frontend/src/components/Layout/Navigation.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
// frontend/src/components/Layout/Navigation.tsx
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
|
||||
const Navigation: React.FC = () => {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const { user, logout, hasRole } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
const isActive = (path: string) => location.pathname === path;
|
||||
|
||||
const navigationItems = [
|
||||
{ path: '/', label: '🏠 Dashboard', icon: '🏠', roles: ['admin', 'instandhalter', 'user'] },
|
||||
{ path: '/shift-plans', label: '📅 Schichtpläne', icon: '📅', roles: ['admin', 'instandhalter', 'user'] },
|
||||
{ path: '/employees', label: '👥 Mitarbeiter', icon: '👥', roles: ['admin', 'instandhalter'] },
|
||||
{ path: '/settings', label: '⚙️ Einstellungen', icon: '⚙️', roles: ['admin'] },
|
||||
{ path: '/help', label: '❓ Hilfe', icon: '❓', roles: ['admin', 'instandhalter', 'user'] },
|
||||
];
|
||||
|
||||
const filteredNavigation = navigationItems.filter(item =>
|
||||
hasRole(item.roles)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Desktop Navigation */}
|
||||
<nav style={{
|
||||
backgroundColor: '#2c3e50',
|
||||
padding: '0 20px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto'
|
||||
}}>
|
||||
{/* Logo/Brand */}
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Link
|
||||
to="/"
|
||||
style={{
|
||||
color: 'white',
|
||||
textDecoration: 'none',
|
||||
fontSize: '20px',
|
||||
fontWeight: 'bold',
|
||||
padding: '15px 0'
|
||||
}}
|
||||
>
|
||||
🗓️ SchichtPlaner
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Desktop Menu */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px'
|
||||
}}>
|
||||
{filteredNavigation.map(item => (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
style={{
|
||||
color: 'white',
|
||||
textDecoration: 'none',
|
||||
padding: '15px 20px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: isActive(item.path) ? '#3498db' : 'transparent',
|
||||
transition: 'background-color 0.2s',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isActive(item.path)) {
|
||||
e.currentTarget.style.backgroundColor = '#34495e';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isActive(item.path)) {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span>{item.icon}</span>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* User Menu */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '15px' }}>
|
||||
<span style={{ color: 'white', fontSize: '14px' }}>
|
||||
{user?.name} ({user?.role})
|
||||
</span>
|
||||
<button
|
||||
onClick={logout}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: '1px solid #e74c3c',
|
||||
color: '#e74c3c',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#e74c3c';
|
||||
e.currentTarget.style.color = 'white';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
e.currentTarget.style.color = '#e74c3c';
|
||||
}}
|
||||
>
|
||||
Abmelden
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
style={{
|
||||
display: 'block',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: 'white',
|
||||
fontSize: '24px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
☰
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{mobileMenuOpen && (
|
||||
<div style={{
|
||||
display: 'block',
|
||||
backgroundColor: '#34495e',
|
||||
padding: '10px 0'
|
||||
}}>
|
||||
{filteredNavigation.map(item => (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
style={{
|
||||
display: 'block',
|
||||
color: 'white',
|
||||
textDecoration: 'none',
|
||||
padding: '12px 20px',
|
||||
borderBottom: '1px solid #2c3e50'
|
||||
}}
|
||||
>
|
||||
<span style={{ marginRight: '10px' }}>{item.icon}</span>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
{/* Breadcrumbs */}
|
||||
<div style={{
|
||||
backgroundColor: '#ecf0f1',
|
||||
padding: '10px 20px',
|
||||
borderBottom: '1px solid #bdc3c7',
|
||||
fontSize: '14px'
|
||||
}}>
|
||||
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
||||
{/* Breadcrumb wird dynamisch basierend auf der Route gefüllt */}
|
||||
<span style={{ color: '#7f8c8d' }}>
|
||||
🏠 Dashboard {location.pathname !== '/' && '>'}
|
||||
{location.pathname === '/shift-plans' && ' 📅 Schichtpläne'}
|
||||
{location.pathname === '/employees' && ' 👥 Mitarbeiter'}
|
||||
{location.pathname === '/settings' && ' ⚙️ Einstellungen'}
|
||||
{location.pathname === '/help' && ' ❓ Hilfe'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navigation;
|
||||
@@ -110,7 +110,7 @@ const Login: React.FC = () => {
|
||||
transition: 'background-color 0.2s'
|
||||
}}
|
||||
>
|
||||
{loading ? '⏳ Anmeldung...' : '🔐 Anmelden'}
|
||||
{loading ? '⏳ Anmeldung...' : 'Anmelden'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -1,159 +1,392 @@
|
||||
// frontend/src/pages/Dashboard/Dashboard.tsx
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
|
||||
// Mock Data für die Demo
|
||||
const mockData = {
|
||||
currentShiftPlan: {
|
||||
id: '1',
|
||||
name: 'November Schichtplan 2024',
|
||||
period: '01.11.2024 - 30.11.2024',
|
||||
status: 'Aktiv',
|
||||
shiftsCovered: 85,
|
||||
totalShifts: 120
|
||||
},
|
||||
upcomingShifts: [
|
||||
{ id: '1', date: 'Heute', time: '08:00 - 16:00', type: 'Frühschicht', assigned: true },
|
||||
{ id: '2', date: 'Morgen', time: '14:00 - 22:00', type: 'Spätschicht', assigned: true },
|
||||
{ id: '3', date: '15.11.2024', time: '08:00 - 16:00', type: 'Frühschicht', assigned: false }
|
||||
],
|
||||
teamStats: {
|
||||
totalEmployees: 24,
|
||||
availableToday: 18,
|
||||
onVacation: 3,
|
||||
sickLeave: 2
|
||||
},
|
||||
recentActivities: [
|
||||
{ id: '1', action: 'Schichtplan veröffentlicht', user: 'Max Mustermann', time: 'vor 2 Stunden' },
|
||||
{ id: '2', action: 'Mitarbeiter hinzugefügt', user: 'Sarah Admin', time: 'vor 4 Stunden' },
|
||||
{ id: '3', action: 'Verfügbarkeit geändert', user: 'Tom Bauer', time: 'vor 1 Tag' }
|
||||
]
|
||||
};
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
const { user, logout, hasRole } = useAuth();
|
||||
const { user, hasRole } = useAuth();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Simuliere Daten laden
|
||||
setTimeout(() => setLoading(false), 1000);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||
<div>⏳ Lade Dashboard...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '30px' }}>
|
||||
<h1>Schichtplan Dashboard</h1>
|
||||
<div>
|
||||
<span style={{ marginRight: '15px' }}>Eingeloggt als: <strong>{user?.name}</strong> ({user?.role})</span>
|
||||
<button
|
||||
onClick={logout}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#dc3545',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px'
|
||||
}}
|
||||
>
|
||||
Abmelden
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
{/* Willkommens-Bereich */}
|
||||
<div style={{
|
||||
backgroundColor: '#e8f4fd',
|
||||
padding: '25px',
|
||||
borderRadius: '8px',
|
||||
marginBottom: '30px',
|
||||
border: '1px solid #b6d7e8'
|
||||
}}>
|
||||
<h1 style={{ margin: '0 0 10px 0', color: '#2c3e50' }}>
|
||||
Willkommen zurück, {user?.name}! 👋
|
||||
</h1>
|
||||
<p style={{ margin: 0, color: '#546e7a', fontSize: '16px' }}>
|
||||
{new Date().toLocaleDateString('de-DE', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Admin/Instandhalter Funktionen */}
|
||||
{/* Quick Actions - Nur für Admins/Instandhalter */}
|
||||
{hasRole(['admin', 'instandhalter']) && (
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
|
||||
gap: '20px',
|
||||
marginBottom: '40px'
|
||||
}}>
|
||||
<div style={{ marginBottom: '30px' }}>
|
||||
<h2 style={{ marginBottom: '15px', color: '#2c3e50' }}>Schnellaktionen</h2>
|
||||
<div style={{
|
||||
border: '1px solid #007bff',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: '#f8f9fa'
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
||||
gap: '15px'
|
||||
}}>
|
||||
<h3>Schichtplan erstellen</h3>
|
||||
<p>Neuen Schichtplan erstellen und verwalten</p>
|
||||
<Link to="/shift-plans/new">
|
||||
<button style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#007bff',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px'
|
||||
<Link to="/shift-plans/new" style={{ textDecoration: 'none' }}>
|
||||
<div style={{
|
||||
backgroundColor: '#3498db',
|
||||
color: 'white',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
textAlign: 'center',
|
||||
transition: 'transform 0.2s',
|
||||
cursor: 'pointer'
|
||||
}} onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
}} onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
}}>
|
||||
Erstellen
|
||||
</button>
|
||||
<div style={{ fontSize: '24px', marginBottom: '8px' }}>📅</div>
|
||||
<div style={{ fontWeight: 'bold' }}>Neuen Schichtplan</div>
|
||||
<div style={{ fontSize: '14px', opacity: 0.9 }}>Erstellen</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link to="/employees" style={{ textDecoration: 'none' }}>
|
||||
<div style={{
|
||||
backgroundColor: '#2ecc71',
|
||||
color: 'white',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
textAlign: 'center',
|
||||
transition: 'transform 0.2s',
|
||||
cursor: 'pointer'
|
||||
}} onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
}} onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
}}>
|
||||
<div style={{ fontSize: '24px', marginBottom: '8px' }}>👥</div>
|
||||
<div style={{ fontWeight: 'bold' }}>Mitarbeiter</div>
|
||||
<div style={{ fontSize: '14px', opacity: 0.9 }}>Verwalten</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link to="/shift-plans" style={{ textDecoration: 'none' }}>
|
||||
<div style={{
|
||||
backgroundColor: '#9b59b6',
|
||||
color: 'white',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
textAlign: 'center',
|
||||
transition: 'transform 0.2s',
|
||||
cursor: 'pointer'
|
||||
}} onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
}} onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
}}>
|
||||
<div style={{ fontSize: '24px', marginBottom: '8px' }}>📋</div>
|
||||
<div style={{ fontWeight: 'bold' }}>Alle Pläne</div>
|
||||
<div style={{ fontSize: '14px', opacity: 0.9 }}>Anzeigen</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
border: '1px solid #28a745',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: '#f8f9fa'
|
||||
}}>
|
||||
<h3>Vorlagen verwalten</h3>
|
||||
<p>Schichtplan Vorlagen erstellen und bearbeiten</p>
|
||||
<Link to="/shift-templates">
|
||||
<button style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#28a745',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
Verwalten
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{hasRole(['admin']) && (
|
||||
<div style={{
|
||||
border: '1px solid #6f42c1',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: '#f8f9fa'
|
||||
}}>
|
||||
<h3>Benutzer verwalten</h3>
|
||||
<p>Benutzerkonten erstellen und verwalten</p>
|
||||
<Link to="/user-management">
|
||||
<button style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#6f42c1',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
Verwalten
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Aktuelle Schichtpläne */}
|
||||
{/* Haupt-Grid mit Informationen */}
|
||||
<div style={{
|
||||
border: '1px solid #ddd',
|
||||
padding: '20px',
|
||||
borderRadius: '8px'
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
|
||||
gap: '25px',
|
||||
marginBottom: '30px'
|
||||
}}>
|
||||
<h2>Aktuelle Schichtpläne</h2>
|
||||
<div style={{ padding: '20px', textAlign: 'center', color: '#666' }}>
|
||||
<p>Noch keine Schichtpläne vorhanden.</p>
|
||||
{hasRole(['admin', 'instandhalter']) && (
|
||||
<Link to="/shift-plans/new">
|
||||
<button style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#007bff',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
Ersten Schichtplan erstellen
|
||||
</button>
|
||||
</Link>
|
||||
)}
|
||||
{/* Aktueller Schichtplan */}
|
||||
<div style={{
|
||||
backgroundColor: 'white',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #e0e0e0',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<h3 style={{ margin: '0 0 15px 0', color: '#2c3e50' }}>📊 Aktueller Schichtplan</h3>
|
||||
<div style={{ marginBottom: '15px' }}>
|
||||
<div style={{ fontWeight: 'bold', fontSize: '18px' }}>
|
||||
{mockData.currentShiftPlan.name}
|
||||
</div>
|
||||
<div style={{ color: '#666', fontSize: '14px' }}>
|
||||
{mockData.currentShiftPlan.period}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '15px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '5px' }}>
|
||||
<span>Fortschritt:</span>
|
||||
<span>{mockData.currentShiftPlan.shiftsCovered}/{mockData.currentShiftPlan.totalShifts} Schichten</span>
|
||||
</div>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
backgroundColor: '#ecf0f1',
|
||||
borderRadius: '10px',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<div style={{
|
||||
width: `${(mockData.currentShiftPlan.shiftsCovered / mockData.currentShiftPlan.totalShifts) * 100}%`,
|
||||
backgroundColor: '#3498db',
|
||||
height: '8px',
|
||||
borderRadius: '10px'
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'inline-block',
|
||||
backgroundColor: mockData.currentShiftPlan.status === 'Aktiv' ? '#2ecc71' : '#f39c12',
|
||||
color: 'white',
|
||||
padding: '4px 12px',
|
||||
borderRadius: '20px',
|
||||
fontSize: '12px',
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
{mockData.currentShiftPlan.status}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Team-Statistiken */}
|
||||
<div style={{
|
||||
backgroundColor: 'white',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #e0e0e0',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<h3 style={{ margin: '0 0 15px 0', color: '#2c3e50' }}>👥 Team-Übersicht</h3>
|
||||
<div style={{ display: 'grid', gap: '12px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>Gesamt Mitarbeiter:</span>
|
||||
<span style={{ fontWeight: 'bold', fontSize: '18px' }}>
|
||||
{mockData.teamStats.totalEmployees}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>Verfügbar heute:</span>
|
||||
<span style={{ fontWeight: 'bold', color: '#2ecc71' }}>
|
||||
{mockData.teamStats.availableToday}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>Im Urlaub:</span>
|
||||
<span style={{ fontWeight: 'bold', color: '#f39c12' }}>
|
||||
{mockData.teamStats.onVacation}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>Krankgeschrieben:</span>
|
||||
<span style={{ fontWeight: 'bold', color: '#e74c3c' }}>
|
||||
{mockData.teamStats.sickLeave}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Schnellzugriff für alle User */}
|
||||
<div style={{ marginTop: '30px' }}>
|
||||
<h3>Schnellzugriff</h3>
|
||||
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
||||
<Link to="/shift-templates">
|
||||
<button style={{
|
||||
padding: '8px 16px',
|
||||
border: '1px solid #007bff',
|
||||
backgroundColor: 'white',
|
||||
color: '#007bff',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
Vorlagen ansehen
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
{hasRole(['user']) && (
|
||||
<button style={{
|
||||
padding: '8px 16px',
|
||||
border: '1px solid #28a745',
|
||||
backgroundColor: 'white',
|
||||
color: '#28a745',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
Meine Schichten
|
||||
</button>
|
||||
)}
|
||||
{/* Unteres Grid */}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))',
|
||||
gap: '25px'
|
||||
}}>
|
||||
{/* Meine nächsten Schichten (für normale User) */}
|
||||
{hasRole(['user']) && (
|
||||
<div style={{
|
||||
backgroundColor: 'white',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #e0e0e0',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<h3 style={{ margin: '0 0 15px 0', color: '#2c3e50' }}>⏰ Meine nächsten Schichten</h3>
|
||||
<div style={{ display: 'grid', gap: '10px' }}>
|
||||
{mockData.upcomingShifts.map(shift => (
|
||||
<div key={shift.id} style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '12px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '6px',
|
||||
border: shift.assigned ? '1px solid #d4edda' : '1px solid #fff3cd'
|
||||
}}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 'bold' }}>{shift.date}</div>
|
||||
<div style={{ fontSize: '14px', color: '#666' }}>{shift.time}</div>
|
||||
<div style={{ fontSize: '12px', color: '#999' }}>{shift.type}</div>
|
||||
</div>
|
||||
<div style={{
|
||||
padding: '4px 8px',
|
||||
backgroundColor: shift.assigned ? '#d4edda' : '#fff3cd',
|
||||
color: shift.assigned ? '#155724' : '#856404',
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px',
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
{shift.assigned ? 'Zugewiesen' : 'Noch offen'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Letzte Aktivitäten (für Admins/Instandhalter) */}
|
||||
{hasRole(['admin', 'instandhalter']) && (
|
||||
<div style={{
|
||||
backgroundColor: 'white',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #e0e0e0',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<h3 style={{ margin: '0 0 15px 0', color: '#2c3e50' }}>📝 Letzte Aktivitäten</h3>
|
||||
<div style={{ display: 'grid', gap: '12px' }}>
|
||||
{mockData.recentActivities.map(activity => (
|
||||
<div key={activity.id} style={{
|
||||
padding: '12px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '6px',
|
||||
borderLeft: '4px solid #3498db'
|
||||
}}>
|
||||
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
|
||||
{activity.action}
|
||||
</div>
|
||||
<div style={{ fontSize: '14px', color: '#666' }}>
|
||||
von {activity.user}
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#999' }}>
|
||||
{activity.time}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Schnelllinks */}
|
||||
<div style={{
|
||||
backgroundColor: 'white',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #e0e0e0',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<h3 style={{ margin: '0 0 15px 0', color: '#2c3e50' }}>🔗 Schnellzugriff</h3>
|
||||
<div style={{ display: 'grid', gap: '10px' }}>
|
||||
<Link to="/shift-plans" style={{ textDecoration: 'none' }}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '12px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '6px',
|
||||
transition: 'background-color 0.2s',
|
||||
cursor: 'pointer'
|
||||
}} onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#e9ecef';
|
||||
}} onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f8f9fa';
|
||||
}}>
|
||||
<span style={{ marginRight: '10px', fontSize: '18px' }}>📅</span>
|
||||
<span>Alle Schichtpläne anzeigen</span>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link to="/help" style={{ textDecoration: 'none' }}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '12px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '6px',
|
||||
transition: 'background-color 0.2s',
|
||||
cursor: 'pointer'
|
||||
}} onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#e9ecef';
|
||||
}} onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f8f9fa';
|
||||
}}>
|
||||
<span style={{ marginRight: '10px', fontSize: '18px' }}>❓</span>
|
||||
<span>Hilfe & Anleitung</span>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{hasRole(['user']) && (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '12px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '6px',
|
||||
transition: 'background-color 0.2s',
|
||||
cursor: 'pointer'
|
||||
}} onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#e9ecef';
|
||||
}} onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f8f9fa';
|
||||
}}>
|
||||
<span style={{ marginRight: '10px', fontSize: '18px' }}>📝</span>
|
||||
<span>Meine Verfügbarkeit bearbeiten</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
28
frontend/src/pages/Employees/EmployeeManagement.tsx
Normal file
28
frontend/src/pages/Employees/EmployeeManagement.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
// frontend/src/pages/Employees/EmployeeManagement.tsx
|
||||
import React from 'react';
|
||||
|
||||
const EmployeeManagement: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>👥 Mitarbeiter Verwaltung</h1>
|
||||
|
||||
<div style={{
|
||||
padding: '40px',
|
||||
textAlign: 'center',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '8px',
|
||||
border: '2px dashed #dee2e6',
|
||||
marginTop: '20px'
|
||||
}}>
|
||||
<div style={{ fontSize: '48px', marginBottom: '20px' }}>👥</div>
|
||||
<h3>Mitarbeiter Übersicht</h3>
|
||||
<p>Hier können Sie Mitarbeiter verwalten und deren Verfügbarkeiten einsehen.</p>
|
||||
<p style={{ fontSize: '14px', color: '#6c757d' }}>
|
||||
Diese Seite wird demnächst mit Funktionen gefüllt.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmployeeManagement;
|
||||
28
frontend/src/pages/Help/Help.tsx
Normal file
28
frontend/src/pages/Help/Help.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
// frontend/src/pages/Help/Help.tsx
|
||||
import React from 'react';
|
||||
|
||||
const Help: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>❓ Hilfe & Support</h1>
|
||||
|
||||
<div style={{
|
||||
padding: '40px',
|
||||
textAlign: 'center',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '8px',
|
||||
border: '2px dashed #dee2e6',
|
||||
marginTop: '20px'
|
||||
}}>
|
||||
<div style={{ fontSize: '48px', marginBottom: '20px' }}>📖</div>
|
||||
<h3>Hilfe Center</h3>
|
||||
<p>Hier finden Sie Anleitungen und Support für die Schichtplan-App.</p>
|
||||
<p style={{ fontSize: '14px', color: '#6c757d' }}>
|
||||
Diese Seite wird demnächst mit Funktionen gefüllt.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Help;
|
||||
28
frontend/src/pages/Settings/Settings.tsx
Normal file
28
frontend/src/pages/Settings/Settings.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
// frontend/src/pages/Settings/Settings.tsx
|
||||
import React from 'react';
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>⚙️ Einstellungen</h1>
|
||||
|
||||
<div style={{
|
||||
padding: '40px',
|
||||
textAlign: 'center',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '8px',
|
||||
border: '2px dashed #dee2e6',
|
||||
marginTop: '20px'
|
||||
}}>
|
||||
<div style={{ fontSize: '48px', marginBottom: '20px' }}>⚙️</div>
|
||||
<h3>System Einstellungen</h3>
|
||||
<p>Hier können Sie Systemweite Einstellungen vornehmen.</p>
|
||||
<p style={{ fontSize: '14px', color: '#6c757d' }}>
|
||||
Diese Seite wird demnächst mit Funktionen gefüllt.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
46
frontend/src/pages/ShiftPlans/ShiftPlanList.tsx
Normal file
46
frontend/src/pages/ShiftPlans/ShiftPlanList.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
// frontend/src/pages/ShiftPlans/ShiftPlanList.tsx
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
|
||||
const ShiftPlanList: React.FC = () => {
|
||||
const { hasRole } = useAuth();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '30px' }}>
|
||||
<h1>📅 Schichtpläne</h1>
|
||||
{hasRole(['admin', 'instandhalter']) && (
|
||||
<Link to="/shift-plans/new">
|
||||
<button style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#3498db',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
+ Neuen Plan
|
||||
</button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
padding: '40px',
|
||||
textAlign: 'center',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '8px',
|
||||
border: '2px dashed #dee2e6'
|
||||
}}>
|
||||
<div style={{ fontSize: '48px', marginBottom: '20px' }}>📋</div>
|
||||
<h3>Schichtpläne Übersicht</h3>
|
||||
<p>Hier werden alle Schichtpläne angezeigt.</p>
|
||||
<p style={{ fontSize: '14px', color: '#6c757d' }}>
|
||||
Diese Seite wird demnächst mit Funktionen gefüllt.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShiftPlanList;
|
||||
Reference in New Issue
Block a user