feat: vollständige Mobile-Optimierung — Bottom-Nav, responsive Grids, CoPilot Sheet
This commit is contained in:
parent
e9fa87f5a9
commit
63fed9fb9c
14 changed files with 485 additions and 118 deletions
|
|
@ -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,72 +17,82 @@ 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 (
|
||||
<div
|
||||
className="h-screen flex overflow-hidden"
|
||||
className="h-screen flex flex-col overflow-hidden"
|
||||
style={{ background: 'var(--atlas-navy)' }}
|
||||
>
|
||||
{/* Left Sidebar Navigation */}
|
||||
<Sidebar currentPath={currentPath} />
|
||||
|
||||
{/* Main Content Area */}
|
||||
<main
|
||||
className="flex-1 flex flex-col overflow-hidden"
|
||||
style={{ borderLeft: '1px solid var(--atlas-border)' }}
|
||||
>
|
||||
{/* Top Bar */}
|
||||
{/* ── TOP BAR (immer sichtbar) ── */}
|
||||
<div
|
||||
className="flex items-center justify-between px-4 py-2 flex-shrink-0"
|
||||
className="flex items-center justify-between px-3 flex-shrink-0"
|
||||
style={{
|
||||
background: 'var(--atlas-surface)',
|
||||
borderBottom: '1px solid var(--atlas-border)',
|
||||
minHeight: '44px'
|
||||
minHeight: '44px',
|
||||
zIndex: 40,
|
||||
}}
|
||||
>
|
||||
{/* Left: Logo + Breadcrumb */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Mobile: Hamburger */}
|
||||
{isMobile && (
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="p-1 rounded mr-1"
|
||||
style={{ color: 'var(--muted-foreground)' }}
|
||||
>
|
||||
<Menu size={18} />
|
||||
</button>
|
||||
)}
|
||||
<span
|
||||
className="text-xs font-mono"
|
||||
className="text-xs font-mono hidden sm:inline"
|
||||
style={{ color: 'var(--muted-foreground)' }}
|
||||
>
|
||||
ATLAS OS
|
||||
</span>
|
||||
<span style={{ color: 'var(--atlas-border)' }}>›</span>
|
||||
<span className="hidden sm:inline" style={{ color: 'var(--atlas-border)' }}>›</span>
|
||||
<span
|
||||
className="text-xs font-mono"
|
||||
style={{ color: 'var(--atlas-cyan)' }}
|
||||
>
|
||||
Social Command Center
|
||||
Social
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{/* System Status */}
|
||||
{/* Right: Status + Clock + CoPilot Toggle */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="atlas-status-dot online" />
|
||||
<span className="text-xs" style={{ color: 'var(--muted-foreground)' }}>
|
||||
<span className="text-xs hidden sm:inline" style={{ color: 'var(--muted-foreground)' }}>
|
||||
Postiz
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="h-3 w-px"
|
||||
style={{ background: 'var(--atlas-border)' }}
|
||||
/>
|
||||
|
||||
{/* Live Clock */}
|
||||
<LiveClock />
|
||||
|
||||
<div
|
||||
className="h-3 w-px"
|
||||
style={{ background: 'var(--atlas-border)' }}
|
||||
/>
|
||||
<div className="h-3 w-px hidden sm:block" style={{ background: 'var(--atlas-border)' }} />
|
||||
<LiveClock className="hidden sm:inline" />
|
||||
<div className="h-3 w-px hidden sm:block" style={{ background: 'var(--atlas-border)' }} />
|
||||
|
||||
{/* Co-Pilot Toggle */}
|
||||
<button
|
||||
onClick={() => setCopilotOpen(!copilotOpen)}
|
||||
className="text-xs px-2 py-1 rounded transition-colors"
|
||||
className="flex items-center gap-1.5 text-xs px-2 py-1 rounded transition-colors"
|
||||
style={{
|
||||
color: copilotOpen ? 'var(--atlas-cyan)' : 'var(--muted-foreground)',
|
||||
background: copilotOpen ? 'oklch(0.82 0.18 210 / 0.08)' : 'transparent',
|
||||
|
|
@ -88,40 +100,87 @@ export function AppShell({ children, currentPath }: AppShellProps) {
|
|||
fontFamily: 'var(--font-mono)',
|
||||
}}
|
||||
>
|
||||
Co-Pilot {copilotOpen ? '▶' : '◀'}
|
||||
<Sparkles size={11} />
|
||||
<span className="hidden sm:inline">Co-Pilot</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Page Content */}
|
||||
<div className="flex-1 overflow-auto atlas-scrollbar">
|
||||
{/* ── MAIN BODY ── */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Desktop Sidebar */}
|
||||
{!isMobile && (
|
||||
<Sidebar currentPath={currentPath} />
|
||||
)}
|
||||
|
||||
{/* Mobile Slide-out Sidebar */}
|
||||
{isMobile && mobileMenuOpen && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
style={{ background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(2px)' }}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
/>
|
||||
<div
|
||||
className="fixed left-0 top-0 bottom-0 z-50 flex flex-col"
|
||||
style={{
|
||||
width: '220px',
|
||||
background: 'var(--atlas-navy)',
|
||||
borderRight: '1px solid var(--atlas-border)',
|
||||
animation: 'slideInLeft 0.2s cubic-bezier(0.23,1,0.32,1)',
|
||||
}}
|
||||
>
|
||||
<Sidebar currentPath={currentPath} onNavigate={() => setMobileMenuOpen(false)} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
<main
|
||||
className="flex-1 flex flex-col overflow-hidden"
|
||||
style={{ borderLeft: isMobile ? 'none' : '1px solid var(--atlas-border)' }}
|
||||
>
|
||||
<div
|
||||
className="flex-1 overflow-auto atlas-scrollbar"
|
||||
style={{ paddingBottom: isMobile ? '64px' : '0' }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Right Co-Pilot Panel */}
|
||||
{copilotOpen && (
|
||||
{/* Desktop Co-Pilot Panel */}
|
||||
{!isMobile && copilotOpen && (
|
||||
<CoPilotPanel onClose={() => setCopilotOpen(false)} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ── MOBILE BOTTOM NAV ── */}
|
||||
{isMobile && (
|
||||
<BottomNav currentPath={currentPath} />
|
||||
)}
|
||||
|
||||
{/* ── MOBILE CO-PILOT SHEET ── */}
|
||||
{isMobile && (
|
||||
<CoPilotSheet open={copilotOpen} onClose={() => setCopilotOpen(false)} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<span
|
||||
className="text-xs"
|
||||
className={`text-xs ${className}`}
|
||||
style={{
|
||||
fontFamily: 'var(--font-mono)',
|
||||
color: 'var(--muted-foreground)'
|
||||
color: 'var(--muted-foreground)',
|
||||
}}
|
||||
>
|
||||
{time.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
|
||||
|
|
|
|||
84
client/src/components/layout/BottomNav.tsx
Normal file
84
client/src/components/layout/BottomNav.tsx
Normal file
|
|
@ -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 (
|
||||
<nav
|
||||
className="fixed bottom-0 left-0 right-0 flex items-center justify-around"
|
||||
style={{
|
||||
background: 'var(--atlas-surface)',
|
||||
borderTop: '1px solid var(--atlas-border)',
|
||||
height: '60px',
|
||||
zIndex: 50,
|
||||
paddingBottom: 'env(safe-area-inset-bottom)',
|
||||
}}
|
||||
>
|
||||
{primaryNav.map(item => {
|
||||
const isActive = item.path === '/'
|
||||
? currentPath === '/'
|
||||
: currentPath.startsWith(item.path);
|
||||
const Icon = item.icon;
|
||||
|
||||
return (
|
||||
<Link key={item.path} href={item.path}>
|
||||
<div
|
||||
className="flex flex-col items-center justify-center gap-0.5 px-3 py-1 rounded-lg transition-all"
|
||||
style={{
|
||||
minWidth: '56px',
|
||||
color: isActive ? 'var(--atlas-cyan)' : 'var(--muted-foreground)',
|
||||
background: isActive ? 'oklch(0.82 0.18 210 / 0.08)' : 'transparent',
|
||||
}}
|
||||
>
|
||||
<Icon size={20} />
|
||||
<span
|
||||
style={{
|
||||
fontSize: '0.6rem',
|
||||
fontFamily: 'var(--font-body)',
|
||||
letterSpacing: '0.02em',
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
{isActive && (
|
||||
<div
|
||||
className="absolute bottom-0 rounded-full"
|
||||
style={{
|
||||
width: '4px',
|
||||
height: '4px',
|
||||
background: 'var(--atlas-cyan)',
|
||||
boxShadow: '0 0 6px var(--atlas-cyan)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
71
client/src/components/layout/CoPilotSheet.tsx
Normal file
71
client/src/components/layout/CoPilotSheet.tsx
Normal file
|
|
@ -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<HTMLDivElement>(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 */}
|
||||
<div
|
||||
ref={overlayRef}
|
||||
className="fixed inset-0 z-50"
|
||||
style={{ background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(2px)' }}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Sheet */}
|
||||
<div
|
||||
className="fixed left-0 right-0 bottom-0 z-50 flex flex-col"
|
||||
style={{
|
||||
height: '85vh',
|
||||
borderRadius: '16px 16px 0 0',
|
||||
overflow: 'hidden',
|
||||
animation: 'slideInUp 0.28s cubic-bezier(0.23,1,0.32,1)',
|
||||
}}
|
||||
>
|
||||
{/* Drag Handle */}
|
||||
<div
|
||||
className="flex justify-center pt-2 pb-1 flex-shrink-0"
|
||||
style={{ background: 'var(--atlas-surface)' }}
|
||||
>
|
||||
<div
|
||||
className="rounded-full"
|
||||
style={{
|
||||
width: '36px',
|
||||
height: '4px',
|
||||
background: 'var(--atlas-border)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Co-Pilot Panel — full height */}
|
||||
<div className="flex-1 overflow-hidden" style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<CoPilotPanel onClose={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<aside
|
||||
className="flex flex-col flex-shrink-0 overflow-hidden"
|
||||
|
|
@ -89,7 +90,7 @@ export function Sidebar({ currentPath }: SidebarProps) {
|
|||
<div className="mb-2">
|
||||
<div className="atlas-section-header">Mission</div>
|
||||
{missionNav.map(item => (
|
||||
<NavLink key={item.path} item={item} currentPath={currentPath} />
|
||||
<NavLink key={item.path} item={item} currentPath={currentPath} onNavigate={onNavigate} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
|
@ -99,7 +100,7 @@ export function Sidebar({ currentPath }: SidebarProps) {
|
|||
<div className="my-2">
|
||||
<div className="atlas-section-header">Content</div>
|
||||
{contentNav.map(item => (
|
||||
<NavLink key={item.path} item={item} currentPath={currentPath} />
|
||||
<NavLink key={item.path} item={item} currentPath={currentPath} onNavigate={onNavigate} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
|
@ -108,7 +109,7 @@ export function Sidebar({ currentPath }: SidebarProps) {
|
|||
{/* System Section */}
|
||||
<div className="mt-2">
|
||||
{systemNav.map(item => (
|
||||
<NavLink key={item.path} item={item} currentPath={currentPath} />
|
||||
<NavLink key={item.path} item={item} currentPath={currentPath} onNavigate={onNavigate} />
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
|
|
@ -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 (
|
||||
<Link href={item.path}>
|
||||
<div className={`atlas-nav-item mx-1 ${isActive ? 'active' : ''}`}>
|
||||
<div className={`atlas-nav-item mx-1 ${isActive ? 'active' : ''}`} onClick={onNavigate}>
|
||||
<Icon size={14} />
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ export default function Analytics() {
|
|||
</div>
|
||||
|
||||
{/* KPI Bar */}
|
||||
<div className="grid grid-cols-5 gap-3">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-5 gap-3">
|
||||
{[
|
||||
{ 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() {
|
|||
</div>
|
||||
|
||||
{/* Charts Row */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
{/* Performance über Zeit */}
|
||||
<div className="col-span-2 atlas-card p-4">
|
||||
<div className="col-span-1 lg:col-span-2 atlas-card p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h2 className="text-sm font-semibold" style={{ fontFamily: 'var(--font-display)', color: 'var(--foreground)' }}>Performance über Zeit</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -202,9 +202,9 @@ export default function Analytics() {
|
|||
</div>
|
||||
|
||||
{/* Content Performance Table + Funnel */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
{/* Table */}
|
||||
<div className="col-span-2 atlas-card overflow-hidden">
|
||||
<div className="col-span-1 lg:col-span-2 atlas-card overflow-hidden">
|
||||
<div className="p-3 flex items-center justify-between" style={{ borderBottom: '1px solid var(--atlas-border)' }}>
|
||||
<h2 className="text-sm font-semibold" style={{ fontFamily: 'var(--font-display)', color: 'var(--foreground)' }}>Content Performance</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -214,7 +214,7 @@ export default function Analytics() {
|
|||
<button className="atlas-btn-ghost text-xs gap-1"><Download size={11} /> Exportieren</button>
|
||||
</div>
|
||||
</div>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<div className="atlas-table-scroll"><table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '1px solid var(--atlas-border)' }}>
|
||||
{['Inhalt', 'Plattform', 'Impressionen', 'Engagement', 'Klicks', 'Umsatz'].map(h => (
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export default function Approvals() {
|
|||
</div>
|
||||
|
||||
{/* KPI Bar */}
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ 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)
|
||||
</span>
|
||||
</div>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<div className="atlas-table-scroll"><table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '1px solid var(--atlas-border)' }}>
|
||||
{['Priorität', 'Vorschau', 'Titel', 'Plattform', 'Produkt', 'Affiliate', 'Geplant für', 'Erstellt von', 'Status', 'Aktionen'].map(h => (
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ export default function Calendar() {
|
|||
</div>
|
||||
|
||||
{/* KPI Bar */}
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ 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' },
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export default function Campaigns() {
|
|||
</div>
|
||||
|
||||
{/* KPI Bar */}
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ 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() {
|
|||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-sm font-semibold" style={{ fontFamily: 'var(--font-display)', color: 'var(--foreground)' }}>Kampagnen-Übersicht</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{CAMPAIGNS.map(c => (
|
||||
<div key={c.id} className="atlas-card atlas-card-hover overflow-hidden cursor-pointer">
|
||||
<div className="relative" style={{ height: '100px' }}>
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ export default function Drafts() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="atlas-card p-3 space-y-2">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
<div>
|
||||
<div className="text-xs mb-1" style={{ color: 'var(--muted-foreground)', fontSize: '0.6rem' }}>Campaign</div>
|
||||
<input className="atlas-input text-xs" value="shelly-plug-s-gen3-launch" readOnly />
|
||||
|
|
@ -260,7 +260,7 @@ export default function Drafts() {
|
|||
|
||||
{/* Freigabe Status */}
|
||||
<div className="atlas-card p-3">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div className="text-xs mb-1" style={{ color: 'var(--muted-foreground)', fontSize: '0.6rem' }}>Status</div>
|
||||
<span className="atlas-badge-cyan">Entwurf</span>
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export default function Products() {
|
|||
</div>
|
||||
|
||||
{/* KPI Bar */}
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ 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 */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="atlas-card overflow-hidden">
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<div className="atlas-table-scroll"><table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '1px solid var(--atlas-border)' }}>
|
||||
{['Produkt', 'Kategorie', 'Preis', 'Händler', 'Verfügbarkeit', 'Content Score ⓘ', 'Affiliate', 'Aktionen'].map(h => (
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export default function Settings() {
|
|||
</div>
|
||||
|
||||
{/* KPI Bar */}
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ 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() {
|
|||
</div>
|
||||
|
||||
{/* Content Grid */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
{/* Verbundene Kanäle */}
|
||||
<div className="atlas-card p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
|
|
@ -194,7 +194,7 @@ export default function Settings() {
|
|||
<h2 className="text-sm font-semibold" style={{ fontFamily: 'var(--font-display)', color: 'var(--foreground)' }}>Branding & Assets</h2>
|
||||
<button className="text-xs" style={{ color: 'var(--atlas-cyan)' }}>Bearbeiten</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-3 mb-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-3">
|
||||
<div>
|
||||
<div className="text-xs mb-1" style={{ color: 'var(--muted-foreground)', fontSize: '0.6rem' }}>Logo (Standard)</div>
|
||||
<div className="flex items-center justify-center rounded p-2" style={{ background: 'var(--atlas-navy)', border: '1px solid var(--atlas-border)', height: '60px' }}>
|
||||
|
|
@ -290,7 +290,7 @@ export default function Settings() {
|
|||
<h2 className="text-sm font-semibold" style={{ fontFamily: 'var(--font-display)', color: 'var(--foreground)' }}>System-Health & Sync-Status</h2>
|
||||
<button className="atlas-btn-primary text-xs">System-Logs · Alle Logs anzeigen</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-3">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-5 gap-3">
|
||||
{HEALTH_ITEMS.map(item => (
|
||||
<div key={item.label} className="p-3 rounded" style={{ background: 'var(--atlas-navy)', border: '1px solid var(--atlas-border)' }}>
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export default function Signals() {
|
|||
<div className="flex gap-4">
|
||||
{/* Signal Cards Grid */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{filtered.map((signal, i) => (
|
||||
<div
|
||||
key={signal.id}
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ export default function WarRoom() {
|
|||
</div>
|
||||
|
||||
{/* KPI Cards */}
|
||||
<div className="flex gap-3">
|
||||
<div className="flex gap-3 overflow-x-auto pb-1 atlas-kpi-scroll">
|
||||
<KpiCard
|
||||
icon={<Calendar size={16} style={{ color: '#60a5fa' }} />}
|
||||
iconBg="oklch(0.6 0.15 250 / 0.15)"
|
||||
|
|
@ -290,7 +290,7 @@ export default function WarRoom() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{SIGNAL_CARDS.map((card, i) => (
|
||||
<div
|
||||
key={i}
|
||||
|
|
@ -355,7 +355,7 @@ export default function WarRoom() {
|
|||
</div>
|
||||
|
||||
{/* Bottom Row: Pipeline + Kanal-Status */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{/* Content Pipeline */}
|
||||
<div className="atlas-card p-4">
|
||||
<h2 className="text-sm font-semibold mb-3" style={{ fontFamily: 'var(--font-display)', color: 'var(--foreground)' }}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue