diff --git a/client/src/components/layout/AppShell.tsx b/client/src/components/layout/AppShell.tsx index f0e4ae0..b696b18 100644 --- a/client/src/components/layout/AppShell.tsx +++ b/client/src/components/layout/AppShell.tsx @@ -1,13 +1,15 @@ /** - * AppShell — 3-Spalten-Layout - * Bloomberg Terminal × Raumschiff-Brücke - * - * Layout: Sidebar (160px) | Content (flex-grow) | CoPilot (320px) + * AppShell — Responsive Layout + * Desktop: Sidebar (160px) | Content | CoPilot (320px) + * Mobile: Top-Bar | Content | Bottom-Nav + Slide-up CoPilot */ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Sidebar } from './Sidebar'; +import { BottomNav } from './BottomNav'; import { CoPilotPanel } from './CoPilotPanel'; +import { CoPilotSheet } from './CoPilotSheet'; +import { Sparkles, Menu } from 'lucide-react'; interface AppShellProps { children: React.ReactNode; @@ -15,113 +17,170 @@ interface AppShellProps { } export function AppShell({ children, currentPath }: AppShellProps) { - const [copilotOpen, setCopilotOpen] = useState(true); + const [copilotOpen, setCopilotOpen] = useState(false); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + const check = () => setIsMobile(window.innerWidth < 1024); + check(); + window.addEventListener('resize', check); + return () => window.removeEventListener('resize', check); + }, []); + + // Desktop: Co-Pilot default open + useEffect(() => { + if (!isMobile) setCopilotOpen(true); + else setCopilotOpen(false); + }, [isMobile]); return ( -
- {/* Left Sidebar Navigation */} - - - {/* Main Content Area */} -
- {/* Top Bar */} -
-
- + {/* Mobile: Hamburger */} + {isMobile && ( + + )} + + ATLAS OS + + + + Social + +
+ + {/* Right: Status + Clock + CoPilot Toggle */} +
+
+
+ + Postiz
- -
- {/* System Status */} -
-
- - Postiz - -
- -
+ +
+ + {/* Co-Pilot Toggle */} + +
+
+ + {/* ── MAIN BODY ── */} +
+ {/* Desktop Sidebar */} + {!isMobile && ( + + )} + + {/* Mobile Slide-out Sidebar */} + {isMobile && mobileMenuOpen && ( + <> +
setMobileMenuOpen(false)} /> - - {/* Live Clock */} - - -
- - {/* Co-Pilot Toggle */} - + setMobileMenuOpen(false)} /> +
+ + )} + + {/* Content */} +
+
+ {children}
-
- - {/* Page Content */} -
- {children} -
-
- - {/* Right Co-Pilot Panel */} - {copilotOpen && ( - setCopilotOpen(false)} /> + + + {/* Desktop Co-Pilot Panel */} + {!isMobile && copilotOpen && ( + setCopilotOpen(false)} /> + )} +
+ + {/* ── MOBILE BOTTOM NAV ── */} + {isMobile && ( + + )} + + {/* ── MOBILE CO-PILOT SHEET ── */} + {isMobile && ( + setCopilotOpen(false)} /> )} ); } -function LiveClock() { +function LiveClock({ className = '' }: { className?: string }) { const [time, setTime] = useState(new Date()); - - // Update every second - useState(() => { + + useEffect(() => { const interval = setInterval(() => setTime(new Date()), 1000); return () => clearInterval(interval); - }); - + }, []); + return ( - {time.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' })} diff --git a/client/src/components/layout/BottomNav.tsx b/client/src/components/layout/BottomNav.tsx new file mode 100644 index 0000000..7636e8e --- /dev/null +++ b/client/src/components/layout/BottomNav.tsx @@ -0,0 +1,84 @@ +/** + * BottomNav — Mobile Bottom Navigation + * Zeigt die 5 wichtigsten Seiten als Icons + * "Mehr" öffnet das volle Menü + */ + +import { Link } from 'wouter'; +import { + LayoutDashboard, + Zap, + FileEdit, + Calendar, + BarChart2, +} from 'lucide-react'; + +interface BottomNavProps { + currentPath: string; +} + +const primaryNav = [ + { label: 'War Room', path: '/', icon: LayoutDashboard }, + { label: 'Signale', path: '/signals', icon: Zap }, + { label: 'Drafts', path: '/drafts', icon: FileEdit }, + { label: 'Kalender', path: '/calendar', icon: Calendar }, + { label: 'Analytics', path: '/analytics', icon: BarChart2 }, +]; + +export function BottomNav({ currentPath }: BottomNavProps) { + return ( + + ); +} diff --git a/client/src/components/layout/CoPilotSheet.tsx b/client/src/components/layout/CoPilotSheet.tsx new file mode 100644 index 0000000..e94ce16 --- /dev/null +++ b/client/src/components/layout/CoPilotSheet.tsx @@ -0,0 +1,71 @@ +/** + * CoPilotSheet — Mobile Slide-up Sheet für den Co-Pilot + * Öffnet von unten als Overlay auf Mobile + */ + +import { useEffect, useRef } from 'react'; +import { CoPilotPanel } from './CoPilotPanel'; + +interface CoPilotSheetProps { + open: boolean; + onClose: () => void; +} + +export function CoPilotSheet({ open, onClose }: CoPilotSheetProps) { + const overlayRef = useRef(null); + + // Prevent body scroll when open + useEffect(() => { + if (open) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + return () => { document.body.style.overflow = ''; }; + }, [open]); + + if (!open) return null; + + return ( + <> + {/* Backdrop */} +
+ + {/* Sheet */} +
+ {/* Drag Handle */} +
+
+
+ + {/* Co-Pilot Panel — full height */} +
+ +
+
+ + ); +} diff --git a/client/src/components/layout/Sidebar.tsx b/client/src/components/layout/Sidebar.tsx index 2ee7be2..f35b66e 100644 --- a/client/src/components/layout/Sidebar.tsx +++ b/client/src/components/layout/Sidebar.tsx @@ -48,9 +48,10 @@ const systemNav: NavItem[] = [ interface SidebarProps { currentPath: string; + onNavigate?: () => void; } -export function Sidebar({ currentPath }: SidebarProps) { +export function Sidebar({ currentPath, onNavigate }: SidebarProps) { return (
@@ -99,7 +100,7 @@ export function Sidebar({ currentPath }: SidebarProps) {
Content
{contentNav.map(item => ( - + ))}
@@ -108,7 +109,7 @@ export function Sidebar({ currentPath }: SidebarProps) { {/* System Section */}
{systemNav.map(item => ( - + ))}
@@ -170,7 +171,7 @@ export function Sidebar({ currentPath }: SidebarProps) { ); } -function NavLink({ item, currentPath }: { item: NavItem; currentPath: string }) { +function NavLink({ item, currentPath, onNavigate }: { item: NavItem; currentPath: string; onNavigate?: () => void }) { const isActive = item.path === '/' ? currentPath === '/' : currentPath.startsWith(item.path); @@ -179,7 +180,7 @@ function NavLink({ item, currentPath }: { item: NavItem; currentPath: string }) return ( -
+
{item.label}
diff --git a/client/src/index.css b/client/src/index.css index 5f4a5ea..bbfa663 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -491,3 +491,155 @@ animation: none; } } + +/* ── Mobile Animations ── */ +@keyframes slideInLeft { + from { transform: translateX(-100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} +@keyframes slideInUp { + from { transform: translateY(100%); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +/* ── Mobile Responsive Overrides ── */ +@media (max-width: 1023px) { + /* KPI Cards — horizontal scroll on mobile */ + .atlas-kpi-grid { + display: flex !important; + overflow-x: auto !important; + gap: 0.75rem !important; + padding-bottom: 0.5rem !important; + scrollbar-width: none !important; + -ms-overflow-style: none !important; + } + .atlas-kpi-grid::-webkit-scrollbar { display: none; } + .atlas-kpi-grid > * { + flex-shrink: 0 !important; + min-width: 160px !important; + } + + /* Tables — horizontal scroll */ + .atlas-table-container { + overflow-x: auto !important; + -webkit-overflow-scrolling: touch !important; + } + + /* Page padding — tighter on mobile */ + .atlas-page { + padding: 1rem !important; + } + + /* Charts — full width */ + .atlas-chart-grid { + grid-template-columns: 1fr !important; + } + + /* Signal Cards — single column */ + .atlas-signal-grid { + grid-template-columns: 1fr !important; + } + + /* Campaign Cards — single column */ + .atlas-campaign-grid { + grid-template-columns: 1fr !important; + } + + /* Draft Studio — stacked layout */ + .atlas-draft-layout { + flex-direction: column !important; + } + .atlas-draft-editor { + width: 100% !important; + max-height: 50vh !important; + overflow-y: auto !important; + } + .atlas-draft-preview { + width: 100% !important; + } + + /* Calendar — compact */ + .atlas-calendar-grid { + font-size: 0.7rem !important; + } + .atlas-calendar-cell { + min-height: 60px !important; + padding: 0.25rem !important; + } + + /* Co-Pilot Panel — full width on mobile (via Sheet) */ + .atlas-copilot-desktop { + display: none !important; + } + + /* Top bar breadcrumb — compact */ + .atlas-topbar-breadcrumb { + display: none !important; + } + + /* Sidebar — hidden on mobile (use BottomNav) */ + .atlas-sidebar-desktop { + display: none !important; + } +} + +/* ── Touch Optimizations ── */ +@media (hover: none) and (pointer: coarse) { + /* Larger tap targets */ + .atlas-nav-item { + min-height: 44px !important; + padding: 0.625rem 0.75rem !important; + } + button { + min-height: 36px; + } + /* Remove hover states that don't apply on touch */ + .atlas-nav-item:hover { + background: transparent !important; + } + .atlas-nav-item.active { + background: oklch(0.82 0.18 210 / 0.1) !important; + } +} + +/* ── KPI horizontal scroll on mobile ── */ +.atlas-kpi-scroll { + scrollbar-width: none; + -ms-overflow-style: none; +} +.atlas-kpi-scroll::-webkit-scrollbar { display: none; } +.atlas-kpi-scroll > * { + min-width: 150px; +} + +/* ── Tables: horizontal scroll wrapper ── */ +.atlas-table-scroll { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} +.atlas-table-scroll table { + min-width: 600px; +} + +/* ── Page padding responsive ── */ +@media (max-width: 640px) { + .p-5 { padding: 0.875rem !important; } + .p-4 { padding: 0.75rem !important; } + .space-y-5 > * + * { margin-top: 0.875rem !important; } + .space-y-4 > * + * { margin-top: 0.75rem !important; } + + /* Header: hide subtitle on very small screens */ + .atlas-page-subtitle { + display: none; + } + + /* Compact header on mobile */ + .atlas-page-header { + flex-direction: column !important; + align-items: flex-start !important; + gap: 0.5rem !important; + } + .atlas-page-header > div:last-child { + flex-wrap: wrap; + } +} diff --git a/client/src/pages/Analytics.tsx b/client/src/pages/Analytics.tsx index 3dc7471..c3bc630 100644 --- a/client/src/pages/Analytics.tsx +++ b/client/src/pages/Analytics.tsx @@ -106,7 +106,7 @@ export default function Analytics() {
{/* KPI Bar */} -
+
{[ { label: 'Reichweite gesamt', value: '2,48 Mio.', trend: '↑ 28%', color: '#60a5fa', icon: '👥' }, { label: 'Engagement-Rate', value: '4,32 %', trend: '↑ 15%', color: '#e1306c', icon: '❤️' }, @@ -126,9 +126,9 @@ export default function Analytics() {
{/* Charts Row */} -
+
{/* Performance über Zeit */} -
+

Performance über Zeit

@@ -202,9 +202,9 @@ export default function Analytics() {
{/* Content Performance Table + Funnel */} -
+
{/* Table */} -
+

Content Performance

@@ -214,7 +214,7 @@ export default function Analytics() {
- +
{['Inhalt', 'Plattform', 'Impressionen', 'Engagement', 'Klicks', 'Umsatz'].map(h => ( diff --git a/client/src/pages/Approvals.tsx b/client/src/pages/Approvals.tsx index ab9a7b5..23e5d71 100644 --- a/client/src/pages/Approvals.tsx +++ b/client/src/pages/Approvals.tsx @@ -81,7 +81,7 @@ export default function Approvals() { {/* KPI Bar */} -
+
{[ { label: 'Offene Freigaben', value: '48', trend: '↑ 12% vs. gestern', color: '#a78bfa' }, { label: 'Dringend', value: '7', trend: '↑ 75% vs. gestern', color: '#ef4444' }, @@ -131,7 +131,7 @@ export default function Approvals() { Freigaben-Queue (48)
-
+
{['Priorität', 'Vorschau', 'Titel', 'Plattform', 'Produkt', 'Affiliate', 'Geplant für', 'Erstellt von', 'Status', 'Aktionen'].map(h => ( diff --git a/client/src/pages/Calendar.tsx b/client/src/pages/Calendar.tsx index e3d9477..9b17fee 100644 --- a/client/src/pages/Calendar.tsx +++ b/client/src/pages/Calendar.tsx @@ -125,7 +125,7 @@ export default function Calendar() { {/* KPI Bar */} -
+
{[ { label: 'Geplante Beiträge', value: '24', trend: '↑ 14% vs. letzte Woche', color: '#60a5fa' }, { label: 'Ausstehende Freigaben', value: '7', trend: '↑ 2 vs. letzte Woche', color: '#a78bfa' }, diff --git a/client/src/pages/Campaigns.tsx b/client/src/pages/Campaigns.tsx index dbdb2b5..37065df 100644 --- a/client/src/pages/Campaigns.tsx +++ b/client/src/pages/Campaigns.tsx @@ -82,7 +82,7 @@ export default function Campaigns() {
{/* KPI Bar */} -
+
{[ { label: 'Aktive Kampagnen', value: '12', trend: '↑ 20% vs. letzte Woche', color: '#60a5fa' }, { label: 'Geplante Assets', value: '86', trend: '↑ 16% vs. letzte Woche', color: '#a78bfa' }, @@ -103,7 +103,7 @@ export default function Campaigns() {

Kampagnen-Übersicht

-
+
{CAMPAIGNS.map(c => (
diff --git a/client/src/pages/Drafts.tsx b/client/src/pages/Drafts.tsx index 3ea851e..80bb0e0 100644 --- a/client/src/pages/Drafts.tsx +++ b/client/src/pages/Drafts.tsx @@ -187,7 +187,7 @@ export default function Drafts() {
-
+
Campaign
@@ -260,7 +260,7 @@ export default function Drafts() { {/* Freigabe Status */}
-
+
Status
Entwurf diff --git a/client/src/pages/Products.tsx b/client/src/pages/Products.tsx index bd7f790..9c6ed7e 100644 --- a/client/src/pages/Products.tsx +++ b/client/src/pages/Products.tsx @@ -74,7 +74,7 @@ export default function Products() {
{/* KPI Bar */} -
+
{[ { label: 'Produkte aktiv', value: '156', trend: '↑ 12% vs. letzte Woche', color: '#60a5fa' }, { label: 'Mit Affiliate-Link', value: '128', trend: '↑ 18% vs. letzte Woche', color: '#a78bfa' }, @@ -124,7 +124,7 @@ export default function Products() { {/* Product Table */}
-
+
{['Produkt', 'Kategorie', 'Preis', 'Händler', 'Verfügbarkeit', 'Content Score ⓘ', 'Affiliate', 'Aktionen'].map(h => ( diff --git a/client/src/pages/Settings.tsx b/client/src/pages/Settings.tsx index f18a78a..c583d67 100644 --- a/client/src/pages/Settings.tsx +++ b/client/src/pages/Settings.tsx @@ -68,7 +68,7 @@ export default function Settings() { {/* KPI Bar */} -
+
{[ { label: 'Verbundene Kanäle', value: '5 / 5', sub: 'Alle Systeme verbunden', color: '#10b981' }, { label: 'Integrationen aktiv', value: '6 / 8', sub: '75% aktiv', color: '#a78bfa' }, @@ -102,7 +102,7 @@ export default function Settings() {
{/* Content Grid */} -
+
{/* Verbundene Kanäle */}
@@ -194,7 +194,7 @@ export default function Settings() {

Branding & Assets

-
+
Logo (Standard)
@@ -290,7 +290,7 @@ export default function Settings() {

System-Health & Sync-Status

-
+
{HEALTH_ITEMS.map(item => (
diff --git a/client/src/pages/Signals.tsx b/client/src/pages/Signals.tsx index 44f4c6c..d0038ec 100644 --- a/client/src/pages/Signals.tsx +++ b/client/src/pages/Signals.tsx @@ -165,7 +165,7 @@ export default function Signals() {
{/* Signal Cards Grid */}
-
+
{filtered.map((signal, i) => (
{/* KPI Cards */} -
+
} iconBg="oklch(0.6 0.15 250 / 0.15)" @@ -290,7 +290,7 @@ export default function WarRoom() {
-
+
{SIGNAL_CARDS.map((card, i) => (
{/* Bottom Row: Pipeline + Kanal-Status */} -
+
{/* Content Pipeline */}