diff --git a/src/App.tsx b/src/App.tsx index d5cda4b..8a3b32a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,8 @@ -import React, { useState } from 'react'; -import { Menu, Wrench } from 'lucide-react'; +import React, { useState, useEffect } from 'react'; +import { Menu, Wrench, Loader2 } from 'lucide-react'; +import { AuthProvider, useAuth } from './context/AuthContext'; +import { ThemeProvider } from './context/ThemeContext'; +import { AuthView } from './views/AuthView'; import { Sidebar } from './components/Sidebar'; import { DashboardView } from './views/DashboardView'; import { CustomersView } from './views/CustomersView'; @@ -9,8 +12,11 @@ import { EquipmentView } from './views/EquipmentView'; import { TechniciansView } from './views/TechniciansView'; import { TimeTrackingView } from './views/TimeTrackingView'; import { SupabaseSyncView } from './views/SupabaseSyncView'; +import { CustomerPortalView } from './views/CustomerPortalView'; + +const MainLayout: React.FC = () => { + const { profile, isLoading } = useAuth(); -export const App: React.FC = () => { const [activeTab, setActiveTabState] = useState(() => { return localStorage.getItem('handwerksfreund_active_tab') || 'dashboard'; }); @@ -21,6 +27,43 @@ export const App: React.FC = () => { localStorage.setItem('handwerksfreund_active_tab', tab); }; + // Adjust active tab if user role restricts access + useEffect(() => { + if (!profile) return; + + if (profile.role === 'monteur' && ['dashboard', 'lager', 'monteure', 'supabase'].includes(activeTab)) { + setActiveTab('dispatching'); + } else if (profile.role === 'kunde' && !['portal', 'kunden', 'geraete'].includes(activeTab)) { + setActiveTab('portal'); + } + }, [profile, activeTab]); + + if (isLoading) { + return ( +
+ + + Handwerksfreund wird geladen... + +
+ ); + } + + // If unauthenticated, show AuthView + if (!profile) { + return ; + } + const renderView = () => { switch (activeTab) { case 'dashboard': @@ -37,10 +80,12 @@ export const App: React.FC = () => { return ; case 'zeiterfassung': return ; + case 'portal': + return ; case 'supabase': return ; default: - return ; + return ; } }; @@ -76,4 +121,14 @@ export const App: React.FC = () => { ); }; +export const App: React.FC = () => { + return ( + + + + + + ); +}; + export default App; diff --git a/src/components/CustomerModal.tsx b/src/components/CustomerModal.tsx index e104ea7..428b508 100644 --- a/src/components/CustomerModal.tsx +++ b/src/components/CustomerModal.tsx @@ -346,22 +346,10 @@ export const CustomerModal: React.FC = ({ diff --git a/src/components/InstalledPartModal.tsx b/src/components/InstalledPartModal.tsx index 63a4f70..922d596 100644 --- a/src/components/InstalledPartModal.tsx +++ b/src/components/InstalledPartModal.tsx @@ -332,22 +332,10 @@ export const InstalledPartModal: React.FC = ({ diff --git a/src/components/LiveOrdersTable.tsx b/src/components/LiveOrdersTable.tsx index 0675343..2159d16 100644 --- a/src/components/LiveOrdersTable.tsx +++ b/src/components/LiveOrdersTable.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { CalendarDays } from 'lucide-react'; import { ScheduleItem } from '../types'; interface LiveOrdersTableProps { @@ -7,35 +8,56 @@ interface LiveOrdersTableProps { export const LiveOrdersTable: React.FC = ({ orders }) => { return ( -
-

Heutige Live-Aufträge (Dispatching)

-
- +
+
+ +

Heutige Live-Aufträge (Dispatching)

+
+ +
+
- - - - + + + + + - {orders.map((order) => ( - - - - - - ))} + {orders.map((order) => { + const isInProgress = order.status === 'In Bearbeitung' || order.isInProgress; + const isCompleted = order.status === 'Abgeschlossen' || order.isCompleted; + + return ( + + + + + + + ); + })}
KundeMonteurStatus
KundeMonteurUhrzeitStatus
{order.customerName}{order.technicianName} - - {order.status === 'Geplant' ? `Geplant (${order.startTime})` : order.status} - -
+ {order.customerName || 'Kunde'} + + {order.technicianName || 'Obermonteur Max'} + + {order.startTime} - {order.endTime} + + {isInProgress ? ( + In Bearbeitung + ) : isCompleted ? ( + Abgeschlossen + ) : ( + Geplant + )} +
diff --git a/src/components/ScheduleItemModal.tsx b/src/components/ScheduleItemModal.tsx index bae9ed8..7221a58 100644 --- a/src/components/ScheduleItemModal.tsx +++ b/src/components/ScheduleItemModal.tsx @@ -406,22 +406,10 @@ export const ScheduleItemModal: React.FC = ({
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index d3ef488..033e56a 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Wrench, LayoutDashboard, @@ -8,8 +8,18 @@ import { Clock, Database, X, - Info + Info, + LogOut, + Settings, + ShieldCheck, + Sun, + Moon, + ChevronRight } from 'lucide-react'; +import { useAuth } from '../context/AuthContext'; +import { useTheme } from '../context/ThemeContext'; +import { UserProfileModal } from './UserProfileModal'; +import { VersionHistoryModal } from './VersionHistoryModal'; interface SidebarProps { activeTab: string; @@ -24,22 +34,43 @@ export const Sidebar: React.FC = ({ isOpen, onClose }) => { - const menuItems = [ - { id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard }, - { id: 'kunden', label: 'Kunden & Aufträge', icon: Users }, - { id: 'dispatching', label: 'Dispatching', icon: CalendarDays }, - { id: 'lager', label: 'Ersatzteillager', icon: Package }, - { id: 'geraete', label: 'Gerätekartei', icon: Wrench }, - { id: 'monteure', label: 'Monteure', icon: Users }, - { id: 'zeiterfassung', label: 'Zeiterfassung', icon: Clock }, - { id: 'supabase', label: 'Supabase Sync', icon: Database }, + const { profile, signOut } = useAuth(); + const { theme, setTheme } = useTheme(); + const [profileModalOpen, setProfileModalOpen] = useState(false); + const [versionModalOpen, setVersionModalOpen] = useState(false); + + const allMenuItems = [ + { id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard, roles: ['admin', 'disponent'] }, + { id: 'kunden', label: 'Kunden & Aufträge', icon: Users, roles: ['admin', 'disponent', 'monteur', 'kunde'] }, + { id: 'dispatching', label: 'Dispatching', icon: CalendarDays, roles: ['admin', 'disponent', 'monteur'] }, + { id: 'lager', label: 'Ersatzteillager', icon: Package, roles: ['admin', 'disponent'] }, + { id: 'geraete', label: 'Gerätekartei', icon: Wrench, roles: ['admin', 'disponent', 'monteur', 'kunde'] }, + { id: 'monteure', label: 'Monteure', icon: Users, roles: ['admin', 'disponent'] }, + { id: 'zeiterfassung', label: 'Zeiterfassung', icon: Clock, roles: ['admin', 'disponent', 'monteur'] }, + { id: 'portal', label: 'Kundenportal (Demo)', icon: ShieldCheck, roles: ['admin', 'disponent', 'kunde'] }, + { id: 'supabase', label: 'Supabase Sync', icon: Database, roles: ['admin', 'disponent'] }, ]; + const userRole = profile?.role || 'disponent'; + + // Filter items accessible by the user's role + const menuItems = allMenuItems.filter(item => item.roles.includes(userRole)); + const handleSelect = (tabId: string) => { setActiveTab(tabId); onClose(); // Close mobile sidebar on selection }; + const getRoleLabel = (role: string) => { + switch (role) { + case 'admin': return 'Admin'; + case 'disponent': return 'Disponent'; + case 'monteur': return 'Monteur'; + case 'kunde': return 'Kunde'; + default: return role; + } + }; + return ( <> {/* Mobile Backdrop */} @@ -68,6 +99,79 @@ export const Sidebar: React.FC = ({ + {/* User Profile Card */} + {profile && ( +
+
+ {profile.avatarUrl ? ( + {profile.fullName} + ) : ( +
+ {profile.fullName.charAt(0).toUpperCase()} +
+ )} +
+
+ {profile.fullName} +
+
+ + {getRoleLabel(profile.role)} +
+
+
+ + +
+ )} + - {/* Sidebar Footer with Version Badge */} + {/* Sidebar Footer with Theme Switcher & Logout & Clickable Version Badge */}
-
- - Version + {/* Theme Switcher Toggle */} +
+ + +
- - v0.1 - + {/* Abmelden Button */} + {profile && ( + + )} + + {/* Clickable Version History Trigger Button */} +
+ + {/* Profile Modal */} + setProfileModalOpen(false)} + /> + + {/* Version History Modal */} + setVersionModalOpen(false)} + /> ); }; diff --git a/src/components/StatCard.tsx b/src/components/StatCard.tsx index 0f3dc33..be67f90 100644 --- a/src/components/StatCard.tsx +++ b/src/components/StatCard.tsx @@ -9,8 +9,10 @@ interface StatCardProps { export const StatCard: React.FC = ({ label, value, isWarning }) => { return (
- {label} - {value} + {label} + + {value} +
); }; diff --git a/src/components/SystemStatusCard.tsx b/src/components/SystemStatusCard.tsx index 29caaad..56d694c 100644 --- a/src/components/SystemStatusCard.tsx +++ b/src/components/SystemStatusCard.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Database, CheckCircle2 } from 'lucide-react'; import { SystemStatus } from '../types'; interface SystemStatusCardProps { @@ -10,26 +11,63 @@ export const SystemStatusCard: React.FC = ({ status }) => const latency = status?.latencyMs ?? 18; return ( -
-

Supabase System-Status

-

- Datenbank-Verbindung und Realtime-Sync laufen stabil. +

+
+
+ +

Supabase System-Status

+
+ +
+ + Verbunden +
+
+ +

+ Die Verbindung zur PostgreSQL Datenbank und Realtime-Sync laufen stabil.

-
-
- DB Host: - {host} +
+
+
DB Host
+
{host}
-
- Latency: - {latency}ms +
+
Latenz
+
{latency}ms
-
- Letzter Sync: - Vor wenigen Sekunden +
+
Letzter Sync
+
Vor wenigen Sekunden
diff --git a/src/components/TechnicianModal.tsx b/src/components/TechnicianModal.tsx index e5c6cb3..e72401d 100644 --- a/src/components/TechnicianModal.tsx +++ b/src/components/TechnicianModal.tsx @@ -294,22 +294,10 @@ export const TechnicianModal: React.FC = ({
diff --git a/src/components/UserProfileModal.tsx b/src/components/UserProfileModal.tsx new file mode 100644 index 0000000..fe2b346 --- /dev/null +++ b/src/components/UserProfileModal.tsx @@ -0,0 +1,421 @@ +import React, { useState, useRef } from 'react'; +import { X, User, Mail, Phone, Shield, Check, Save, Camera, Upload, Trash2 } from 'lucide-react'; +import { useAuth } from '../context/AuthContext'; +import { SupabaseService } from '../services/supabaseService'; + +interface UserProfileModalProps { + isOpen: boolean; + onClose: () => void; +} + +export const UserProfileModal: React.FC = ({ isOpen, onClose }) => { + const { profile, updateProfile } = useAuth(); + + const [fullName, setFullName] = useState(profile?.fullName || ''); + const [phone, setPhone] = useState(profile?.phone || ''); + const [avatarUrl, setAvatarUrl] = useState(profile?.avatarUrl || ''); + const [isSaved, setIsSaved] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [isUploadingPhoto, setIsUploadingPhoto] = useState(false); + + const fileInputRef = useRef(null); + + if (!isOpen || !profile) return null; + + const handleFileChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + setIsUploadingPhoto(true); + + try { + // 1. Try uploading to Supabase Storage + const path = `avatars/${profile.id}_${Date.now()}.${file.name.split('.').pop() || 'jpg'}`; + const storageUrl = await SupabaseService.uploadFileToStorage('equipment-files', path, file); + + if (storageUrl) { + setAvatarUrl(storageUrl); + } else { + // Fallback: Read as Data URL for immediate local preview & storage + const reader = new FileReader(); + reader.onload = (event) => { + if (event.target?.result) { + setAvatarUrl(event.target.result as string); + } + }; + reader.readAsDataURL(file); + } + } catch (err) { + console.warn('Could not upload to storage bucket, using data URL fallback:', err); + const reader = new FileReader(); + reader.onload = (event) => { + if (event.target?.result) { + setAvatarUrl(event.target.result as string); + } + }; + reader.readAsDataURL(file); + } finally { + setIsUploadingPhoto(false); + } + }; + + const handleSave = async (e: React.FormEvent) => { + e.preventDefault(); + setIsSaving(true); + await updateProfile({ + fullName, + phone, + avatarUrl + }); + setIsSaving(false); + setIsSaved(true); + setTimeout(() => setIsSaved(false), 2500); + }; + + const getRoleBadgeStyle = (role: string) => { + switch (role) { + case 'admin': + case 'disponent': + return { bg: 'rgba(56, 189, 248, 0.15)', border: '#38bdf8', color: '#38bdf8', label: 'Admin / Disponent' }; + case 'monteur': + return { bg: 'rgba(74, 222, 128, 0.15)', border: '#4ade80', color: '#4ade80', label: 'Monteur / Außendienst' }; + case 'kunde': + return { bg: 'rgba(250, 204, 21, 0.15)', border: '#facc15', color: '#facc15', label: 'Kunde' }; + default: + return { bg: '#1e293b', border: '#64748b', color: '#94a3b8', label: role }; + } + }; + + const badge = getRoleBadgeStyle(profile.role); + + return ( +
+
+ {/* Header */} +
+
+
+ {avatarUrl ? ( + {fullName} + ) : ( +
+ {profile.fullName.charAt(0).toUpperCase()} +
+ )} +
+
+

Mein Profil

+

Profilbild & Benutzerkontodaten

+
+
+ + +
+ + {/* Content Form */} +
+ {/* Avatar Upload Section */} +
+ + +
fileInputRef.current?.click()}> + {avatarUrl ? ( + Profilbild + ) : ( +
+ {fullName.charAt(0).toUpperCase() || 'U'} +
+ )} +
+ +
+
+ +
+
+ Profilfoto +
+
+ JPG, PNG oder WebP Format +
+
+ + + {avatarUrl && ( + + )} +
+
+
+ + {/* Role Pill */} +
+
+ + Zugriffsrolle +
+ + {badge.label} + +
+ + {/* Full Name */} +
+ +
+ + setFullName(e.target.value)} + style={{ + width: '100%', + padding: '10px 14px 10px 42px', + backgroundColor: '#0f172a', + border: '1px solid #334155', + borderRadius: '8px', + color: '#f8fafc', + fontSize: '0.9rem', + boxSizing: 'border-box', + outline: 'none' + }} + /> +
+
+ + {/* Email (Readonly) */} +
+ +
+ + +
+
+ + {/* Phone */} +
+ +
+ + setPhone(e.target.value)} + style={{ + width: '100%', + padding: '10px 14px 10px 42px', + backgroundColor: '#0f172a', + border: '1px solid #334155', + borderRadius: '8px', + color: '#f8fafc', + fontSize: '0.9rem', + boxSizing: 'border-box', + outline: 'none' + }} + /> +
+
+ + {/* Footer Actions */} +
+ + +
+
+
+
+ ); +}; diff --git a/src/components/VersionHistoryModal.tsx b/src/components/VersionHistoryModal.tsx new file mode 100644 index 0000000..5a3df84 --- /dev/null +++ b/src/components/VersionHistoryModal.tsx @@ -0,0 +1,238 @@ +import React from 'react'; +import { X, Sparkles, CheckCircle2, History, GitCommit, ShieldCheck, Palette, Lock } from 'lucide-react'; + +interface VersionHistoryModalProps { + isOpen: boolean; + onClose: () => void; +} + +//Achtung, hier dürfen immer nur neue Versionsbeschreibungen eingefügt werden, aber keine alten gelöscht werden! + +export const VersionHistoryModal: React.FC = ({ isOpen, onClose }) => { + if (!isOpen) return null; + + const releases = [ + { + version: 'v0.4', + title: 'Kundenportal & Projekt-Transparenz', + date: '05. August 2026', + badge: 'Aktuell', + badgeColor: '#ea580c', + icon: ShieldCheck, + highlights: [ + 'Dediziertes Kundenportal mit Login via Projekt-Code (z.B. PRJ-8392) & PIN', + 'Transparenter Projekt-Gesundheit Status: 🟢 On Track vs. 🟠 Delay (Verzögerung) mit Begründung', + 'Visuelle Fortschrittsanzeige in % (Progress-Bar) & Vor-Ort-Fotos', + 'Online-Terminbuchungsmodul für Kunden direkt im Portal', + 'Disponenten-Funktion: 📧 Portal-Zugangs-Link & PIN direkt per Mail-Template senden' + ] + }, + { + version: 'v0.3', + title: 'Design System & Theme Engine', + date: '05. August 2026', + badge: 'Release', + badgeColor: '#38bdf8', + icon: Palette, + highlights: [ + 'Light Mode & Dark Mode mit automatischer System-Theme-Erkennung', + 'Refactoring UI Farbschema „Der moderne Profi“ (Craft Blue & Builder Orange CTAs)', + 'Neuer Theme-Switcher Toggle in der Navigation', + 'Kompensation von uneinheitlichen Stilen für alle Modals & Views' + ] + }, + { + version: 'v0.2', + title: 'User Accounts & Authentifizierung', + date: '05. August 2026', + badge: 'Release', + badgeColor: '#4ade80', + icon: Lock, + highlights: [ + 'Supabase Auth Integration (E-Mail/Passwort & Quick-Demo-Buttons)', + 'Rollen- & Berechtigungskonzept (RBAC: Admin/Disponent, Monteur, Kunde)', + 'Profil-Modal zur Bearbeitung von Name und Telefonnummer', + 'PostgreSQL Row Level Security (RLS) & `profiles`-Tabelle mit Auth-Trigger' + ] + }, + { + version: 'v0.1', + title: 'Basis-Prototyp & Live Dispatching', + date: '30. Juli 2026', + badge: 'Initial', + badgeColor: '#94a3b8', + icon: GitCommit, + highlights: [ + 'Live Betriebs-Cockpit mit Supabase Realtime-Datenbankverbindung', + 'Interaktiver Dispatcher mit Drag & Drop Wochen- und Tagesplaner', + 'Kundenverwaltung, Gerätekartei (Buderus/Bosch) & Ersatzteillager', + 'Zeiterfassung und Supabase Storage Integration für Auftrags-Baupläne' + ] + } + ]; + + return ( +
+
+ {/* Header */} +
+
+
+ +
+
+

Versionshistorie

+

Entwicklungsfortschritt von Handwerksfreund

+
+
+ + +
+ + {/* Content Body: Timeline */} +
+ {releases.map((rel, idx) => { + const Icon = rel.icon; + return ( +
+ {/* Bullet Node */} +
+ + {/* Release Header */} +
+
+ + {rel.title} + + + {rel.version} + +
+ + {rel.date} +
+ + {/* Highlights List */} +
    + {rel.highlights.map((item, i) => ( +
  • {item}
  • + ))} +
+
+ ); + })} +
+ + {/* Footer */} +
+ + +
+
+
+ ); +}; diff --git a/src/components/WorkOrderModal.tsx b/src/components/WorkOrderModal.tsx index 61cd8cc..66d8e39 100644 --- a/src/components/WorkOrderModal.tsx +++ b/src/components/WorkOrderModal.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { X, Save, ClipboardList, Calendar, User, FileText } from 'lucide-react'; -import { WorkOrder, Customer, OrderStatus } from '../types'; +import { X, Save, ClipboardList, Calendar, User, FileText, Send, Mail, Check, AlertTriangle, CheckCircle2, ShieldCheck } from 'lucide-react'; +import { WorkOrder, Customer, OrderStatus, ProjectHealthStatus } from '../types'; interface WorkOrderModalProps { isOpen: boolean; @@ -24,30 +24,53 @@ export const WorkOrderModal: React.FC = ({ title: '', description: '', scheduledDate: new Date().toISOString().split('T')[0], - status: 'offen' as OrderStatus + status: 'offen' as OrderStatus, + progressPercent: 65, + healthStatus: 'on_track' as ProjectHealthStatus, + delayReason: '', + accessCode: '', + accessPin: '' }); + const [isSaving, setIsSaving] = useState(false); const [errorMsg, setErrorMsg] = useState(''); + const [showMailModal, setShowMailModal] = useState(false); + const [mailCopied, setMailCopied] = useState(false); useEffect(() => { if (initialOrder) { + const code = initialOrder.accessCode || `PRJ-${initialOrder.id.substring(0, 4).toUpperCase()}`; + const pin = initialOrder.accessPin || '749201'; setFormData({ customerId: initialOrder.customerId || '', title: initialOrder.title || '', description: initialOrder.description || '', scheduledDate: initialOrder.scheduledDate || new Date().toISOString().split('T')[0], - status: initialOrder.status || 'offen' + status: initialOrder.status || 'offen', + progressPercent: initialOrder.progressPercent ?? 65, + healthStatus: initialOrder.healthStatus || 'on_track', + delayReason: initialOrder.delayReason || '', + accessCode: code, + accessPin: pin }); } else { + const code = `PRJ-${Math.floor(1000 + Math.random() * 9000)}`; + const pin = `${Math.floor(100000 + Math.random() * 900000)}`; setFormData({ customerId: defaultCustomerId || (customers.length > 0 ? customers[0].id : ''), title: '', description: '', scheduledDate: new Date().toISOString().split('T')[0], - status: 'offen' + status: 'offen', + progressPercent: 0, + healthStatus: 'on_track', + delayReason: '', + accessCode: code, + accessPin: pin }); } setErrorMsg(''); + setShowMailModal(false); }, [initialOrder, defaultCustomerId, customers, isOpen]); if (!isOpen) return null; @@ -79,231 +102,454 @@ export const WorkOrderModal: React.FC = ({ } }; + const selectedCustomer = customers.find(c => c.id === formData.customerId); + + const handleCopyMailLink = () => { + const text = `Hallo ${selectedCustomer?.name || 'Kunde'},\n\nhier ist dein persönlicher Zugang zu unserem Kundenportal für dein Projekt "${formData.title}":\n\nDirekt-Link: https://handwerksfreund.app/portal?code=${formData.accessCode}&pin=${formData.accessPin}\nProjekt-Code: ${formData.accessCode}\nSicherheits-PIN: ${formData.accessPin}\n\nHier kannst du jederzeit den Baufortschritt (On Track / Delays) einsehen und Termine buchen.\n\nViele Grüße,\nDein Handwerksfreund Team`; + navigator.clipboard.writeText(text); + setMailCopied(true); + setTimeout(() => setMailCopied(false), 3000); + }; + return ( -
+ <>
- {/* Header */}
-

- - {initialOrder ? 'Auftrag bearbeiten' : 'Neuen Auftrag erstellen'} -

- -
- - {/* Form Body */} -
- {errorMsg && ( -
- {errorMsg} + {/* Header */} +
+
+ +

+ {initialOrder ? 'Auftrag & Projekt-Status bearbeiten' : 'Neuen Auftrag erstellen'} +

- )} - - {/* Customer Selection */} -
- -
- - -
-
- - {/* Title */} -
- - setFormData({ ...formData, title: e.target.value })} - placeholder="z.B. Wartung Heizungsanlage" - required +
- {/* Date & Status */} -
+ + {errorMsg && ( +
+ {errorMsg} +
+ )} + + {/* Kunde wählen */}
-
+ + {/* Auftragstitel */} +
+ + setFormData({ ...formData, title: e.target.value })} + style={{ + width: '100%', + padding: '10px 12px', + borderRadius: '8px', + border: '1px solid #cbd5e1', + fontSize: '0.9rem', + boxSizing: 'border-box', + backgroundColor: 'var(--bg-app, #ffffff)', + color: 'var(--text-primary, #0f172a)' + }} + /> +
+ + {/* Status & Datum */} +
+
+ + +
+ +
+ + setFormData({ ...formData, scheduledDate: e.target.value })} style={{ width: '100%', - padding: '10px 14px 10px 38px', + padding: '10px 12px', borderRadius: '8px', border: '1px solid #cbd5e1', - fontSize: '0.95rem' + fontSize: '0.9rem', + boxSizing: 'border-box', + backgroundColor: 'var(--bg-app, #ffffff)', + color: 'var(--text-primary, #0f172a)' }} />
-
- - -
-
+ {/* --- KUNDENPORTAL & PROJEKTSTATUS STEUERUNG --- */} +
+
+
+ + Kundenportal & Projekt-Gesundheit +
- {/* Description */} -
- -
- -