Design: Geruest mit Textmenue, Schublade fuers Handy, Logo; Footer und Einklappen entfallen
Weisses Geruest (.shell, feste Seitenleiste, Dokument-Scroll), Menue als Textliste mit Zaehlern und Serverpuls-Punkt, Schublade mit Scrim und Esc auf dem Handy, Logo-Komponente und Favicon. Footer, Einklappen (lib/sidebar.js) und das alte Menue-CSS (.side*, .app-*) sind entfernt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c19e19968d
commit
5a0307c9d6
14 files changed
+102
-234
No files matched your search
@@ -1,9 +0,0 @@
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="footer">
|
||||
<p className="faint" style={{ fontSize: 13, margin: 0 }}>
|
||||
Webklar Ticket-Plattform · © {new Date().getFullYear()}
|
||||
</p>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// WEBklar-Zeichen (Ellipse mit Stern); Pfade aus dem Prototyp. Die Groesse setzen im Menue und auf der Anmeldung die Stile
|
||||
// (.brand svg, .login-box svg), size gilt, wo keine Regel greift.
|
||||
export default function Logo({ size = 24 }) {
|
||||
return (
|
||||
<svg viewBox="0 0 512 512" width={size} height={size} aria-hidden="true">
|
||||
<g transform="translate(256 256) scale(0.98) translate(-256 -256)">
|
||||
<ellipse cx="256" cy="256" rx="210" ry="150" fill="#111" transform="rotate(-28 256 256)" />
|
||||
<circle cx="356" cy="172" r="18" fill="#fff" />
|
||||
<path
|
||||
transform="translate(256 256) rotate(-28)"
|
||||
d="M 0,-170 C 22,-105 60,-72 120,-56 C 60,-40 22,-8 0,64 C -22,-8 -60,-40 -120,-56 C -60,-72 -22,-105 0,-170 Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
+33
-97
@@ -1,52 +1,16 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import {
|
||||
IconTicket,
|
||||
IconUsers,
|
||||
IconTargetArrow,
|
||||
IconReportMoney,
|
||||
IconScale,
|
||||
IconActivityHeartbeat,
|
||||
IconFolders,
|
||||
IconLayoutGrid,
|
||||
IconCalendar,
|
||||
IconSettings,
|
||||
IconLogout,
|
||||
IconLayoutSidebarLeftCollapse,
|
||||
IconLayoutSidebarLeftExpand,
|
||||
IconMessages,
|
||||
IconReceiptRefund,
|
||||
IconDiscount,
|
||||
IconMailCog,
|
||||
} from '@tabler/icons-react'
|
||||
import Logo from './Logo'
|
||||
import { serverpulsApi } from '../lib/serverpulsApi'
|
||||
import { startsCollapsed } from '../lib/sidebar'
|
||||
import { buildNavLinks } from '../lib/navLinks'
|
||||
import { useSidebarCounts } from '../hooks/useSidebarCounts'
|
||||
|
||||
const ICONS = {
|
||||
tickets: <IconTicket />,
|
||||
planboard: <IconCalendar />,
|
||||
projects: <IconFolders />,
|
||||
services: <IconLayoutGrid />,
|
||||
customers: <IconUsers />,
|
||||
chat: <IconMessages />,
|
||||
gutschriften: <IconReceiptRefund />,
|
||||
angebote: <IconDiscount />,
|
||||
speicher: <IconMailCog />,
|
||||
leads: <IconTargetArrow />,
|
||||
finance: <IconReportMoney />,
|
||||
legal: <IconScale />,
|
||||
serverpuls: <IconActivityHeartbeat />,
|
||||
admin: <IconSettings />,
|
||||
}
|
||||
|
||||
export default function Sidebar() {
|
||||
// Seitenmenue als reine Textliste. onNavigate schliesst die Schublade auf dem Handy (App.jsx).
|
||||
export default function Sidebar({ onNavigate }) {
|
||||
const { user, logout, isAdmin } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const [collapsed, setCollapsed] = useState(() => startsCollapsed())
|
||||
const [pulseLevel, setPulseLevel] = useState('ok')
|
||||
const counts = useSidebarCounts()
|
||||
|
||||
@@ -69,7 +33,7 @@ export default function Sidebar() {
|
||||
navigate('/login')
|
||||
}
|
||||
|
||||
// Eintraege und Unterpunkte (Kunden -> Chat) aus lib/navLinks.js, Icons ueber key
|
||||
// Eintraege und Unterpunkte (Kunden -> Chat) aus lib/navLinks.js
|
||||
const links = buildNavLinks({ isAdmin, pulseLevel, counts })
|
||||
|
||||
const isActive = (href) =>
|
||||
@@ -78,79 +42,51 @@ export default function Sidebar() {
|
||||
const isLinkActive = (link) => isActive(link.href) || (link.also ? isActive(link.also) : false)
|
||||
|
||||
return (
|
||||
<aside className={`side ${collapsed ? 'collapsed' : ''}`}>
|
||||
<div className="side-head">
|
||||
<Link to="/" className="side-logo" title="Webklar">W</Link>
|
||||
<div className="side-brand">
|
||||
Webklar
|
||||
<small>Ticket-Plattform</small>
|
||||
</div>
|
||||
</div>
|
||||
<aside className="sidebar" id="menue">
|
||||
<Link className="brand" to="/tickets" onClick={onNavigate}><Logo />WEBklar</Link>
|
||||
|
||||
<nav className="side-nav">
|
||||
<div className="side-group">
|
||||
{links.map((link) => link.paused ? (
|
||||
<span
|
||||
key={link.href}
|
||||
className="side-link side-link-paused"
|
||||
title={`${link.label} (pausiert)`}
|
||||
aria-disabled="true"
|
||||
>
|
||||
{ICONS[link.key]}
|
||||
<span className="side-link-label">{link.label}</span>
|
||||
<nav className="nav">
|
||||
{links.flatMap((link) => [
|
||||
link.paused ? (
|
||||
<span key={link.href} className="nav-item paused" aria-disabled="true">
|
||||
{link.label}
|
||||
<span className="nav-count">pausiert</span>
|
||||
</span>
|
||||
) : (
|
||||
<Link
|
||||
key={link.href}
|
||||
to={link.href}
|
||||
className={`side-link ${isLinkActive(link) ? 'active' : ''}`}
|
||||
title={link.label}
|
||||
className={`nav-item${isLinkActive(link) ? ' active' : ''}`}
|
||||
onClick={onNavigate}
|
||||
>
|
||||
{ICONS[link.key]}
|
||||
<span className="side-link-label">{link.label}</span>
|
||||
{link.label}
|
||||
{link.dot && link.dot !== 'ok' && (
|
||||
<span
|
||||
className={`side-dot side-dot-${link.dot}`}
|
||||
className={`nav-dot${link.dot === 'critical' ? ' critical' : ''}`}
|
||||
data-testid="sp-nav-dot"
|
||||
title={link.dot === 'critical' ? 'Critical-Warnung aktiv' : 'Warnung aktiv'}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
)).flatMap((item, i) => [
|
||||
item,
|
||||
...(links[i].children || []).map((child) => (
|
||||
<Link
|
||||
key={child.href}
|
||||
to={child.href}
|
||||
className={`side-link side-sublink ${isActive(child.href) ? 'active' : ''}`}
|
||||
title={child.count > 0 ? `${child.label} (${child.count} ${child.key === 'chat' ? 'ungelesen' : 'offen'})` : child.label}
|
||||
>
|
||||
{ICONS[child.key]}
|
||||
<span className="side-link-label">{child.label}</span>
|
||||
{child.count > 0 && <span className="side-count">{child.count}</span>}
|
||||
</Link>
|
||||
)),
|
||||
])}
|
||||
</div>
|
||||
),
|
||||
...(link.children || []).map((child) => (
|
||||
<Link
|
||||
key={child.href}
|
||||
to={child.href}
|
||||
className={`nav-item sub${isActive(child.href) ? ' active' : ''}`}
|
||||
title={child.count > 0 ? `${child.label} (${child.count} ${child.key === 'chat' ? 'ungelesen' : 'offen'})` : undefined}
|
||||
onClick={onNavigate}
|
||||
>
|
||||
{child.label}
|
||||
{child.count > 0 && <span className={`nav-count${child.hot ? ' hot' : ''}`}>{child.count}</span>}
|
||||
</Link>
|
||||
)),
|
||||
])}
|
||||
</nav>
|
||||
|
||||
<div className="side-foot">
|
||||
{user && (
|
||||
<div className="side-link" title={user.name || user.email} style={{ cursor: 'default' }}>
|
||||
<div className="side-logo" style={{ width: 28, height: 28, fontSize: 12, background: 'linear-gradient(135deg,#667eea,#764ba2)' }}>
|
||||
{(user.name || user.email || '?').charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<span className="side-link-label text-ellipsis">{user.name || user.email}</span>
|
||||
</div>
|
||||
)}
|
||||
<button className="side-toggle" onClick={handleLogout} title="Logout">
|
||||
<IconLogout size={20} />
|
||||
<span className="side-link-label">Logout</span>
|
||||
</button>
|
||||
<button className="side-toggle" onClick={() => setCollapsed((c) => !c)} title="Menue ein-/ausklappen">
|
||||
{collapsed ? <IconLayoutSidebarLeftExpand size={20} /> : <IconLayoutSidebarLeftCollapse size={20} />}
|
||||
<span className="side-link-label">Einklappen</span>
|
||||
</button>
|
||||
<div className="sidebar-foot">
|
||||
{user && <span className="who text-ellipsis">{user.name || user.email}</span>}
|
||||
<button type="button" className="link" onClick={handleLogout}>Abmelden</button>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user