feat: layout hamburger, media browser filters+previews, billing fixes
- Layout: mobile hamburger menu + overlay backdrop + close button; content area always full-width - Media browser: filter chips (default still+turntable); advanced toggle for GLB/STL; thumbnail_url previews for non-image types; video hover-play for turntable - Backend: asset_types multi-filter, thumbnail_url in MediaAssetOut, download proxy endpoint for MinIO/local files - Admin: "Import Existing Media" button → POST /api/admin/import-media-assets - Billing: fix invoice create 500 (MissingGreenlet — use selectinload after commit); PDF download uses axios blob instead of bare <a href> (auth header missing); fix storage.upload() accepting str|Path - SSE task logs: task_logs.py core + router, LiveRenderLog component - CadPreview: fix infinite loop when no gltf_geometry assets; loading screen before ThreeDViewer render - render-worker: add trimesh layer to Dockerfile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { Outlet, NavLink, useNavigate, Link } from 'react-router-dom'
|
||||
import { LayoutDashboard, Package, Settings, LogOut, FlaskConical, Activity, Library, Plus, SlidersHorizontal, Building2, GitBranch, Image, BellRing, Receipt, Server, Upload } from 'lucide-react'
|
||||
import { LayoutDashboard, Package, Settings, LogOut, FlaskConical, Activity, Library, Plus, SlidersHorizontal, Building2, GitBranch, Image, BellRing, Receipt, Server, Upload, Menu, X } from 'lucide-react'
|
||||
import { useAuthStore } from '../../store/auth'
|
||||
import { clsx } from 'clsx'
|
||||
import { useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getWorkerActivity } from '../../api/worker'
|
||||
import { listOrders } from '../../api/orders'
|
||||
@@ -20,6 +21,7 @@ const nav = [
|
||||
export default function Layout() {
|
||||
const { user, logout } = useAuthStore()
|
||||
const navigate = useNavigate()
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||
|
||||
const { data: activity } = useQuery({
|
||||
queryKey: ['worker-activity'],
|
||||
@@ -43,8 +45,36 @@ export default function Layout() {
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-surface-alt">
|
||||
{/* Mobile top header bar */}
|
||||
<header className="fixed top-0 left-0 right-0 z-30 md:hidden bg-surface border-b border-border-default h-12 flex items-center px-4 gap-3">
|
||||
<button
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
className="text-content-secondary hover:text-content transition-colors"
|
||||
aria-label="Open navigation"
|
||||
>
|
||||
<Menu size={20} />
|
||||
</button>
|
||||
<span className="flex-1 text-sm font-semibold text-content">Schaeffler Automat</span>
|
||||
<NotificationCenter />
|
||||
</header>
|
||||
|
||||
{/* Overlay backdrop (mobile only) */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-30 md:hidden"
|
||||
style={{ backgroundColor: 'rgba(0,0,0,0.4)' }}
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside className="w-60 flex-shrink-0 bg-surface border-r border-border-default flex flex-col">
|
||||
<aside
|
||||
className={clsx(
|
||||
'fixed left-0 top-0 h-full z-40 w-60 bg-surface border-r border-border-default flex flex-col transform transition-transform duration-200',
|
||||
'md:relative md:translate-x-0 md:flex-shrink-0',
|
||||
sidebarOpen ? 'translate-x-0' : '-translate-x-full',
|
||||
)}
|
||||
>
|
||||
<div className="p-5 border-b border-border-default">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-accent rounded flex items-center justify-center">
|
||||
@@ -54,14 +84,26 @@ export default function Layout() {
|
||||
<p className="font-semibold text-content text-sm">Schaeffler</p>
|
||||
<p className="text-xs text-content-muted">Automat</p>
|
||||
</div>
|
||||
<NotificationCenter />
|
||||
{/* NotificationCenter in sidebar header (desktop); hidden on mobile (shown in top bar) */}
|
||||
<span className="hidden md:block">
|
||||
<NotificationCenter />
|
||||
</span>
|
||||
{/* Close button — mobile only */}
|
||||
<button
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className="md:hidden text-content-secondary hover:text-content transition-colors"
|
||||
aria-label="Close navigation"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 p-3 space-y-1">
|
||||
<nav className="flex-1 p-3 space-y-1 overflow-y-auto">
|
||||
{/* New Order — primary CTA at the top */}
|
||||
<Link
|
||||
to="/orders/new"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className="flex items-center gap-2 px-3 py-2.5 mb-3 rounded-md text-sm font-semibold bg-accent text-accent-text hover:bg-accent-hover transition-colors shadow-sm"
|
||||
>
|
||||
<Plus size={18} />
|
||||
@@ -79,6 +121,7 @@ export default function Layout() {
|
||||
key={to}
|
||||
to={to}
|
||||
end={end}
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
@@ -108,6 +151,7 @@ export default function Layout() {
|
||||
{(user?.role === 'admin' || user?.role === 'project_manager') && (
|
||||
<NavLink
|
||||
to="/admin"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
@@ -124,6 +168,7 @@ export default function Layout() {
|
||||
{(user?.role === 'admin' || user?.role === 'project_manager') && (
|
||||
<NavLink
|
||||
to="/billing"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
@@ -140,6 +185,7 @@ export default function Layout() {
|
||||
{(user?.role === 'admin' || user?.role === 'project_manager') && (
|
||||
<NavLink
|
||||
to="/media"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
@@ -156,6 +202,7 @@ export default function Layout() {
|
||||
{(user?.role === 'admin' || user?.role === 'project_manager') && (
|
||||
<NavLink
|
||||
to="/workers"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
@@ -172,6 +219,7 @@ export default function Layout() {
|
||||
{(user?.role === 'admin' || user?.role === 'project_manager') && (
|
||||
<NavLink
|
||||
to="/workflows"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
@@ -188,6 +236,7 @@ export default function Layout() {
|
||||
{(user?.role === 'admin' || user?.role === 'project_manager') && (
|
||||
<NavLink
|
||||
to="/asset-libraries"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
@@ -204,6 +253,7 @@ export default function Layout() {
|
||||
{user?.role === 'admin' && (
|
||||
<NavLink
|
||||
to="/notification-settings"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
@@ -220,6 +270,7 @@ export default function Layout() {
|
||||
{user?.role === 'admin' && (
|
||||
<NavLink
|
||||
to="/tenants"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
@@ -258,7 +309,7 @@ export default function Layout() {
|
||||
</aside>
|
||||
|
||||
{/* Main content */}
|
||||
<main className="flex-1 overflow-auto">
|
||||
<main className="flex-1 overflow-auto min-w-0 pt-12 md:pt-0">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user