feat: CRM-Dashboard, Content-Verwaltung und Wort-Autocomplete
- Home-Seite nach Login mit Begrüßung und 3 Kacheln (Content erstellen, Content verwalten, User verwalten) - AuthContext speichert User-Profil + Rolle; AdminRoute blockt Nicht-Admins - Content verwalten (admin-only): Status-Dashboard pro Collection, Liste/Kachel-View, generisches Edit-Formular - Nur aktive db_-Collections im Dashboard (alte pictures/objects/words/questions entfernt) - Wort-Autocomplete in DrawIt: ab dem ersten Buchstaben Vorschläge aus db_words, Tastatur-Navigation, Duplikat-Filter - Backend: /users/me Proxy, db-words/search Endpoint, generische Collection-Endpoints Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
123
frontend/src/pages/Home.tsx
Normal file
123
frontend/src/pages/Home.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import Topbar from '../components/Topbar'
|
||||
|
||||
const PenIcon = () => (
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M12 20h9" />
|
||||
<path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const FolderIcon = () => (
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z" />
|
||||
<line x1="8" y1="13" x2="16" y2="13" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const UsersIcon = () => (
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="9" cy="7" r="4" />
|
||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const LockIcon = () => (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
interface TileProps {
|
||||
icon: React.ReactNode
|
||||
title: string
|
||||
subtitle: string
|
||||
onClick?: () => void
|
||||
disabled?: boolean
|
||||
comingSoon?: boolean
|
||||
adminOnly?: boolean
|
||||
locked?: boolean
|
||||
}
|
||||
|
||||
function Tile({ icon, title, subtitle, onClick, disabled, comingSoon, adminOnly, locked }: TileProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`home-tile${disabled ? ' is-disabled' : ''}`}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<div className="home-tile-icon">{icon}</div>
|
||||
<div className="home-tile-body">
|
||||
<div className="home-tile-title">
|
||||
{title}
|
||||
{adminOnly && (
|
||||
<span className="home-tile-badge" title="Nur für Admins">
|
||||
<LockIcon /> Admin
|
||||
</span>
|
||||
)}
|
||||
{comingSoon && <span className="home-tile-badge home-tile-badge-soft">bald</span>}
|
||||
</div>
|
||||
<div className="home-tile-subtitle">{subtitle}</div>
|
||||
</div>
|
||||
{locked && <div className="home-tile-locked"><LockIcon /></div>}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const navigate = useNavigate()
|
||||
const { user, isAdmin } = useAuth()
|
||||
|
||||
const firstName = user?.first_name?.trim() || user?.email?.split('@')[0] || 'da'
|
||||
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<Topbar page="draw" />
|
||||
<div className="home-page">
|
||||
<div className="home-greeting">
|
||||
<h1>Super, {firstName} ist wieder da,</h1>
|
||||
<p>Was möchtest du heute machen?</p>
|
||||
</div>
|
||||
|
||||
<div className="home-tiles">
|
||||
<Tile
|
||||
icon={<PenIcon />}
|
||||
title="Content erstellen"
|
||||
subtitle="Bilder annotieren, Objekte zuschneiden, Fragen generieren."
|
||||
onClick={() => navigate('/draw')}
|
||||
/>
|
||||
|
||||
<Tile
|
||||
icon={<FolderIcon />}
|
||||
title="Content verwalten"
|
||||
subtitle={isAdmin
|
||||
? 'Übersicht aller Collections nach Status — bearbeiten als Liste oder Kacheln.'
|
||||
: 'Status-Dashboard für alle Collections. Nur für Admins verfügbar.'}
|
||||
adminOnly
|
||||
disabled={!isAdmin}
|
||||
locked={!isAdmin}
|
||||
onClick={() => navigate('/content')}
|
||||
/>
|
||||
|
||||
<Tile
|
||||
icon={<UsersIcon />}
|
||||
title="User verwalten"
|
||||
subtitle={isAdmin
|
||||
? 'Admins, API-Tokens und Endnutzer — inkl. aktive Sessions.'
|
||||
: 'User-Übersicht und aktive Sessions. Nur für Admins verfügbar.'}
|
||||
adminOnly
|
||||
comingSoon
|
||||
disabled
|
||||
locked={!isAdmin}
|
||||
onClick={() => { /* Schritt 3 */ }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user