diff --git a/src/App.tsx b/src/App.tsx
index d91cade..4a83a64 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
+import { Menu, Wrench } from 'lucide-react';
import { Sidebar } from './components/Sidebar';
import { DashboardView } from './views/DashboardView';
import { DispatchingView } from './views/DispatchingView';
@@ -9,6 +10,7 @@ import { SupabaseSyncView } from './views/SupabaseSyncView';
export const App: React.FC = () => {
const [activeTab, setActiveTab] = useState('dashboard');
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const renderView = () => {
switch (activeTab) {
@@ -31,7 +33,29 @@ export const App: React.FC = () => {
return (
-
+ {/* Mobile Header Bar */}
+
+
+
+
+
+ Handwerksfreund
+
+
+
+
setMobileMenuOpen(false)}
+ />
+
{renderView()}
diff --git a/src/components/LiveOrdersTable.tsx b/src/components/LiveOrdersTable.tsx
index 88c8d87..0675343 100644
--- a/src/components/LiveOrdersTable.tsx
+++ b/src/components/LiveOrdersTable.tsx
@@ -9,34 +9,36 @@ export const LiveOrdersTable: React.FC = ({ orders }) => {
return (
Heutige Live-Aufträge (Dispatching)
-
-
-
- | Kunde |
- Monteur |
- Status |
-
-
-
- {orders.map((order) => (
-
- | {order.customerName} |
- {order.technicianName} |
-
-
- {order.status === 'Geplant' ? `Geplant (${order.startTime})` : order.status}
-
- |
+
+
+
+
+ | Kunde |
+ Monteur |
+ Status |
- ))}
-
-
+
+
+ {orders.map((order) => (
+
+ | {order.customerName} |
+ {order.technicianName} |
+
+
+ {order.status === 'Geplant' ? `Geplant (${order.startTime})` : order.status}
+
+ |
+
+ ))}
+
+
+
);
};
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index e810515..aba3ab1 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -6,15 +6,23 @@ import {
Package,
Users,
Clock,
- Database
+ Database,
+ X
} from 'lucide-react';
interface SidebarProps {
activeTab: string;
setActiveTab: (tab: string) => void;
+ isOpen: boolean;
+ onClose: () => void;
}
-export const Sidebar: React.FC = ({ activeTab, setActiveTab }) => {
+export const Sidebar: React.FC = ({
+ activeTab,
+ setActiveTab,
+ isOpen,
+ onClose
+}) => {
const menuItems = [
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard },
{ id: 'dispatching', label: 'Dispatching', icon: CalendarDays },
@@ -24,33 +32,58 @@ export const Sidebar: React.FC = ({ activeTab, setActiveTab }) =>
{ id: 'supabase', label: 'Supabase Sync', icon: Database },
];
- return (
-
+ return (
+ <>
+ {/* Mobile Backdrop */}
+ {isOpen && (
+
+ )}
+
+
+ >
);
};
diff --git a/src/index.css b/src/index.css
index f81edfa..9061bf4 100644
--- a/src/index.css
+++ b/src/index.css
@@ -31,12 +31,62 @@ body {
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
min-height: 100vh;
+ overflow-x: hidden;
}
.app-container {
display: flex;
min-height: 100vh;
width: 100vw;
+ position: relative;
+}
+
+/* Mobile Top Navigation Bar (Hidden on Desktop) */
+.mobile-top-bar {
+ display: none;
+ align-items: center;
+ justify-content: space-between;
+ background-color: var(--bg-sidebar);
+ color: #ffffff;
+ padding: 14px 20px;
+ position: sticky;
+ top: 0;
+ z-index: 40;
+ width: 100%;
+}
+
+.mobile-hamburger-btn {
+ background: none;
+ border: none;
+ color: #ffffff;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 6px;
+ border-radius: 8px;
+}
+
+.mobile-hamburger-btn:hover {
+ background-color: var(--bg-sidebar-hover);
+}
+
+.mobile-brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-weight: 700;
+ font-size: 1.1rem;
+}
+
+/* Sidebar Backdrop for Mobile */
+.sidebar-backdrop {
+ display: none;
+ position: fixed;
+ inset: 0;
+ background-color: rgba(15, 23, 42, 0.6);
+ backdrop-filter: blur(4px);
+ z-index: 49;
}
/* Sidebar Styles */
@@ -48,12 +98,14 @@ body {
flex-direction: column;
padding: 24px 16px;
flex-shrink: 0;
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ z-index: 50;
}
.brand {
display: flex;
align-items: center;
- gap: 12px;
+ justify-content: space-between;
color: #ffffff;
font-size: 1.25rem;
font-weight: 700;
@@ -64,6 +116,21 @@ body {
color: #38bdf8;
}
+.mobile-close-btn {
+ display: none;
+ background: none;
+ border: none;
+ color: #94a3b8;
+ cursor: pointer;
+ padding: 4px;
+ border-radius: 6px;
+}
+
+.mobile-close-btn:hover {
+ color: #ffffff;
+ background-color: var(--bg-sidebar-hover);
+}
+
.nav-list {
list-style: none;
display: flex;
@@ -83,6 +150,10 @@ body {
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
+ width: 100%;
+ background: none;
+ border: none;
+ text-align: left;
}
.nav-item:hover {
@@ -109,9 +180,10 @@ body {
display: flex;
flex-direction: column;
gap: 32px;
+ max-width: 100%;
}
-/* Top Header */
+/* Top Header Bar */
.header-bar {
display: flex;
justify-content: space-between;
@@ -121,6 +193,8 @@ body {
border-radius: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
border: 1px solid var(--border-color);
+ flex-wrap: wrap;
+ gap: 16px;
}
.header-title {
@@ -137,6 +211,10 @@ body {
font-size: 0.9rem;
font-weight: 600;
color: #10b981;
+ background-color: #f0fdf4;
+ padding: 6px 14px;
+ border-radius: 20px;
+ border: 1px solid #bbf7d0;
}
.status-dot {
@@ -197,6 +275,7 @@ body {
border-radius: 16px;
padding: 28px;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
+ overflow: hidden;
}
.card-heading {
@@ -206,11 +285,19 @@ body {
color: var(--text-primary);
}
+/* Table Container for Mobile Horizontal Scroll */
+.table-responsive {
+ width: 100%;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
/* Table Styles */
.orders-table {
width: 100%;
border-collapse: separate;
border-spacing: 0 12px;
+ min-width: 500px; /* Ensures table readability on small screens */
}
.orders-table th {
@@ -219,6 +306,7 @@ body {
font-weight: 600;
color: var(--text-secondary);
padding: 0 16px 8px 16px;
+ white-space: nowrap;
}
.orders-table td {
@@ -228,6 +316,7 @@ body {
background-color: #ffffff;
border-top: 1px solid #f1f5f9;
border-bottom: 1px solid #f1f5f9;
+ white-space: nowrap;
}
.orders-table tr td:first-child {
@@ -278,6 +367,7 @@ body {
display: flex;
flex-direction: column;
gap: 14px;
+ word-break: break-word;
}
.status-item {
@@ -298,11 +388,90 @@ body {
color: #ffffff;
}
+/* ==========================================================================
+ Responsive Breakpoints
+ ========================================================================== */
+
+/* Tablet & Mobile Layout (< 1024px) */
@media (max-width: 1024px) {
.kpi-grid {
grid-template-columns: repeat(2, 1fr);
}
+
.dashboard-grid {
grid-template-columns: 1fr;
}
}
+
+/* Mobile Phone Layout (< 768px) */
+@media (max-width: 768px) {
+ .app-container {
+ flex-direction: column;
+ }
+
+ .mobile-top-bar {
+ display: flex;
+ }
+
+ .sidebar-backdrop {
+ display: block;
+ }
+
+ .sidebar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ height: 100vh;
+ transform: translateX(-100%);
+ box-shadow: 4px 0 24px rgba(0, 0, 0, 0.3);
+ }
+
+ .sidebar.open {
+ transform: translateX(0);
+ }
+
+ .mobile-close-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .main-content {
+ padding: 20px 16px;
+ gap: 20px;
+ }
+
+ .header-bar {
+ padding: 16px 20px;
+ border-radius: 12px;
+ }
+
+ .header-title {
+ font-size: 1.35rem;
+ }
+
+ .kpi-grid {
+ grid-template-columns: 1fr;
+ gap: 12px;
+ }
+
+ .kpi-card {
+ padding: 16px 18px;
+ border-radius: 12px;
+ }
+
+ .kpi-value {
+ font-size: 1.45rem;
+ }
+
+ .content-card {
+ padding: 20px 16px;
+ border-radius: 12px;
+ }
+
+ .card-heading {
+ font-size: 1.1rem;
+ margin-bottom: 14px;
+ }
+}
diff --git a/src/views/DispatchingView.tsx b/src/views/DispatchingView.tsx
index 5cbd605..0ebd3aa 100644
--- a/src/views/DispatchingView.tsx
+++ b/src/views/DispatchingView.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { CalendarDays, Plus, UserCheck } from 'lucide-react';
+import { Plus, UserCheck } from 'lucide-react';
export const DispatchingView: React.FC = () => {
const scheduleData = [
@@ -30,42 +30,44 @@ export const DispatchingView: React.FC = () => {
-
+
Tagesdisposition (Heute)
10 Monteure im System
-
-
-
- | Uhrzeit |
- Monteur |
- Kunde |
- Aufgabe / Tätigkeit |
- Status |
-
-
-
- {scheduleData.map((item, idx) => (
-
- | {item.time} |
-
-
-
- {item.technician}
-
- |
- {item.customer} |
- {item.task} |
-
-
- {item.status}
-
- |
+
+
+
+
+ | Uhrzeit |
+ Monteur |
+ Kunde |
+ Aufgabe / Tätigkeit |
+ Status |
- ))}
-
-
+
+
+ {scheduleData.map((item, idx) => (
+
+ | {item.time} |
+
+
+
+ {item.technician}
+
+ |
+ {item.customer} |
+ {item.task} |
+
+
+ {item.status}
+
+ |
+
+ ))}
+
+
+
>
);
diff --git a/src/views/InventoryView.tsx b/src/views/InventoryView.tsx
index 28c6bf1..1269977 100644
--- a/src/views/InventoryView.tsx
+++ b/src/views/InventoryView.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Package, AlertTriangle } from 'lucide-react';
+import { AlertTriangle } from 'lucide-react';
export const InventoryView: React.FC = () => {
const parts = [
@@ -21,41 +21,43 @@ export const InventoryView: React.FC = () => {
Ersatzteilbestand & Bestellwarnungen
-
-
-
- | Artikelbezeichnung |
- Kategorie |
- Lagerbestand |
- Mindestbestand |
- Status |
-
-
-
- {parts.map((p, idx) => (
-
- | {p.name} |
- {p.category} |
-
- {p.stock} Stk.
- |
- {p.minStock} Stk. |
-
-
- {p.status}
-
- |
+
+
+
+
+ | Artikelbezeichnung |
+ Kategorie |
+ Lagerbestand |
+ Mindestbestand |
+ Status |
- ))}
-
-
+
+
+ {parts.map((p, idx) => (
+
+ | {p.name} |
+ {p.category} |
+
+ {p.stock} Stk.
+ |
+ {p.minStock} Stk. |
+
+
+ {p.status}
+
+ |
+
+ ))}
+
+
+
>
);
diff --git a/src/views/TimeTrackingView.tsx b/src/views/TimeTrackingView.tsx
index faf7277..af8bd9b 100644
--- a/src/views/TimeTrackingView.tsx
+++ b/src/views/TimeTrackingView.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { Clock, CheckCircle } from 'lucide-react';
export const TimeTrackingView: React.FC = () => {
const timeEntries = [
@@ -18,36 +17,38 @@ export const TimeTrackingView: React.FC = () => {
Erfasste Arbeitszeiten der Monteure
-
-
-
- | Datum |
- Monteur |
- Kunde / Projekt |
- Tätigkeit |
- Dauer |
- Status |
-
-
-
- {timeEntries.map((e, idx) => (
-
- | {e.date} |
- {e.technician} |
- {e.customer} |
- {e.activity} |
- {e.hours} |
-
-
- {e.status}
-
- |
+
+
+
+
+ | Datum |
+ Monteur |
+ Kunde / Projekt |
+ Tätigkeit |
+ Dauer |
+ Status |
- ))}
-
-
+
+
+ {timeEntries.map((e, idx) => (
+
+ | {e.date} |
+ {e.technician} |
+ {e.customer} |
+ {e.activity} |
+ {e.hours} |
+
+
+ {e.status}
+
+ |
+
+ ))}
+
+
+
>
);