feat: Dark Mode mit Toggle, localStorage-Persistenz und System-Preference
- Sun/Moon Toggle im Top-Bar - Alle Seiten und Komponenten mit dark: Varianten - Tailwind darkMode: 'class' Strategie - Respektiert prefers-color-scheme als Default
This commit is contained in:
parent
9e3b431e7c
commit
3bad762329
@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Workflow Portal</title>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900 antialiased">
|
||||
<body class="bg-gray-50 text-gray-900 antialiased dark:bg-gray-900 dark:text-gray-100">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
@ -29,7 +29,7 @@ export default function ConfirmDialog({
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div
|
||||
className={`flex h-12 w-12 items-center justify-center rounded-full ${
|
||||
variant === 'danger' ? 'bg-red-100' : 'bg-amber-100'
|
||||
variant === 'danger' ? 'bg-red-100 dark:bg-red-900/40' : 'bg-amber-100 dark:bg-amber-900/40'
|
||||
}`}
|
||||
>
|
||||
<AlertTriangle
|
||||
@ -37,8 +37,8 @@ export default function ConfirmDialog({
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="mt-3 text-lg font-semibold text-gray-900">{title}</h3>
|
||||
<p className="mt-2 text-sm text-gray-500">{message}</p>
|
||||
<h3 className="mt-3 text-lg font-semibold text-gray-900 dark:text-white">{title}</h3>
|
||||
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">{message}</p>
|
||||
<div className="mt-6 flex w-full gap-3">
|
||||
<button
|
||||
onClick={onClose}
|
||||
|
||||
@ -15,12 +15,12 @@ export default function EmptyState({
|
||||
}: EmptyStateProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-16 text-center">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-gray-100">
|
||||
<Icon className="text-gray-400" size={32} />
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-gray-100 dark:bg-gray-700">
|
||||
<Icon className="text-gray-400 dark:text-gray-500" size={32} />
|
||||
</div>
|
||||
<h3 className="mt-4 text-base font-semibold text-gray-900">{title}</h3>
|
||||
<h3 className="mt-4 text-base font-semibold text-gray-900 dark:text-white">{title}</h3>
|
||||
{description && (
|
||||
<p className="mt-1 text-sm text-gray-500">{description}</p>
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">{description}</p>
|
||||
)}
|
||||
{action && <div className="mt-6">{action}</div>}
|
||||
</div>
|
||||
|
||||
@ -6,13 +6,16 @@ import {
|
||||
LayoutDashboard,
|
||||
LogOut,
|
||||
Menu,
|
||||
Moon,
|
||||
Settings,
|
||||
Sun,
|
||||
User,
|
||||
Users,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { Menu as HeadlessMenu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react';
|
||||
import { useAuth } from '../hooks/useAuth';
|
||||
import { useDarkMode } from '../hooks/useDarkMode';
|
||||
|
||||
const navigation = [
|
||||
{ name: 'Dashboard', href: '/', icon: LayoutDashboard },
|
||||
@ -29,6 +32,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const { user, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const { isDark, toggle: toggleDark } = useDarkMode();
|
||||
|
||||
const isAdmin =
|
||||
user?.role === 'SYSTEM_ADMIN' || user?.role === 'TECHNICIAN';
|
||||
@ -41,22 +45,22 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const navLinkClass = ({ isActive }: { isActive: boolean }) =>
|
||||
`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors ${
|
||||
isActive
|
||||
? 'bg-primary-50 text-primary-700'
|
||||
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'
|
||||
? 'bg-primary-50 text-primary-700 dark:bg-primary-900/30 dark:text-primary-400'
|
||||
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'
|
||||
}`;
|
||||
|
||||
const sidebarContent = (
|
||||
<>
|
||||
<div className="flex h-16 items-center gap-3 border-b border-gray-200 px-6">
|
||||
<div className="flex h-16 items-center gap-3 border-b border-gray-200 px-6 dark:border-gray-700">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary-600 text-sm font-bold text-white">
|
||||
WP
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-sm font-semibold text-gray-900">
|
||||
<h1 className="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
Workflow Portal
|
||||
</h1>
|
||||
{user?.tenant && (
|
||||
<p className="text-xs text-gray-500">{user.tenant.name}</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{user.tenant.name}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@ -77,8 +81,8 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
{isAdmin && (
|
||||
<>
|
||||
<div className="my-4 border-t border-gray-200" />
|
||||
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-gray-400">
|
||||
<div className="my-4 border-t border-gray-200 dark:border-gray-700" />
|
||||
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
||||
Administration
|
||||
</p>
|
||||
{adminNavigation.map((item) => (
|
||||
@ -96,17 +100,17 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<div className="border-t border-gray-200 p-4">
|
||||
<div className="border-t border-gray-200 p-4 dark:border-gray-700">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary-100 text-sm font-semibold text-primary-700">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary-100 text-sm font-semibold text-primary-700 dark:bg-primary-900/40 dark:text-primary-400">
|
||||
{user?.firstName?.[0]}
|
||||
{user?.lastName?.[0]}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium text-gray-900">
|
||||
<p className="truncate text-sm font-medium text-gray-900 dark:text-white">
|
||||
{user?.firstName} {user?.lastName}
|
||||
</p>
|
||||
<p className="truncate text-xs text-gray-500">{user?.email}</p>
|
||||
<p className="truncate text-xs text-gray-500 dark:text-gray-400">{user?.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -114,7 +118,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-50">
|
||||
<div className="flex h-screen overflow-hidden bg-gray-50 dark:bg-gray-900">
|
||||
{/* Mobile sidebar overlay */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
@ -125,13 +129,13 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
{/* Mobile sidebar */}
|
||||
<aside
|
||||
className={`fixed inset-y-0 left-0 z-50 flex w-64 flex-col bg-white shadow-xl transition-transform duration-200 lg:hidden ${
|
||||
className={`fixed inset-y-0 left-0 z-50 flex w-64 flex-col bg-white shadow-xl transition-transform duration-200 lg:hidden dark:bg-gray-900 ${
|
||||
sidebarOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className="absolute right-3 top-4 rounded-lg p-1 text-gray-400 hover:bg-gray-100"
|
||||
className="absolute right-3 top-4 rounded-lg p-1 text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
@ -139,25 +143,34 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
</aside>
|
||||
|
||||
{/* Desktop sidebar */}
|
||||
<aside className="hidden w-64 flex-col border-r border-gray-200 bg-white lg:flex">
|
||||
<aside className="hidden w-64 flex-col border-r border-gray-200 bg-white lg:flex dark:border-gray-700 dark:bg-gray-900">
|
||||
{sidebarContent}
|
||||
</aside>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
{/* Top bar */}
|
||||
<header className="flex h-16 items-center justify-between border-b border-gray-200 bg-white px-4 lg:px-8">
|
||||
<header className="flex h-16 items-center justify-between border-b border-gray-200 bg-white px-4 lg:px-8 dark:border-gray-700 dark:bg-gray-800">
|
||||
<button
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
className="rounded-lg p-2 text-gray-500 hover:bg-gray-100 lg:hidden"
|
||||
className="rounded-lg p-2 text-gray-500 hover:bg-gray-100 lg:hidden dark:text-gray-400 dark:hover:bg-gray-700"
|
||||
>
|
||||
<Menu size={22} />
|
||||
</button>
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Dark mode toggle */}
|
||||
<button
|
||||
onClick={toggleDark}
|
||||
className="mr-2 rounded-lg p-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
||||
title={isDark ? 'Helles Design' : 'Dunkles Design'}
|
||||
>
|
||||
{isDark ? <Sun size={20} /> : <Moon size={20} />}
|
||||
</button>
|
||||
|
||||
<HeadlessMenu as="div" className="relative">
|
||||
<MenuButton className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||
<MenuButton className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">
|
||||
<User size={18} />
|
||||
<span className="hidden sm:inline">
|
||||
{user?.firstName} {user?.lastName}
|
||||
@ -172,13 +185,13 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<MenuItems className="absolute right-0 mt-2 w-48 origin-top-right rounded-lg border border-gray-200 bg-white py-1 shadow-lg focus:outline-none">
|
||||
<MenuItems className="absolute right-0 mt-2 w-48 origin-top-right rounded-lg border border-gray-200 bg-white py-1 shadow-lg focus:outline-none dark:border-gray-700 dark:bg-gray-800">
|
||||
<MenuItem>
|
||||
{({ focus }) => (
|
||||
<Link
|
||||
to="/"
|
||||
className={`flex items-center gap-2 px-4 py-2 text-sm ${
|
||||
focus ? 'bg-gray-50 text-gray-900' : 'text-gray-700'
|
||||
focus ? 'bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white' : 'text-gray-700 dark:text-gray-200'
|
||||
}`}
|
||||
>
|
||||
<User size={16} />
|
||||
@ -191,7 +204,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className={`flex w-full items-center gap-2 px-4 py-2 text-sm ${
|
||||
focus ? 'bg-gray-50 text-gray-900' : 'text-gray-700'
|
||||
focus ? 'bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white' : 'text-gray-700 dark:text-gray-200'
|
||||
}`}
|
||||
>
|
||||
<LogOut size={16} />
|
||||
|
||||
@ -51,17 +51,17 @@ export default function Modal({
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<DialogPanel
|
||||
className={`w-full ${sizeClasses[size]} transform rounded-xl bg-white p-6 shadow-xl transition-all`}
|
||||
className={`w-full ${sizeClasses[size]} transform rounded-xl bg-white p-6 shadow-xl transition-all dark:bg-gray-800`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{title && (
|
||||
<DialogTitle className="text-lg font-semibold text-gray-900">
|
||||
<DialogTitle className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{title}
|
||||
</DialogTitle>
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="ml-auto rounded-lg p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
|
||||
className="ml-auto rounded-lg p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
|
||||
@ -27,13 +27,13 @@ export default function Pagination({
|
||||
<button
|
||||
onClick={() => onPageChange(page - 1)}
|
||||
disabled={page <= 1}
|
||||
className="rounded-lg p-2 text-gray-500 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-40"
|
||||
className="rounded-lg p-2 text-gray-500 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-40 dark:text-gray-400 dark:hover:bg-gray-700"
|
||||
>
|
||||
<ChevronLeft size={18} />
|
||||
</button>
|
||||
{pages.map((p, idx) =>
|
||||
p === '...' ? (
|
||||
<span key={`dots-${idx}`} className="px-2 text-gray-400">
|
||||
<span key={`dots-${idx}`} className="px-2 text-gray-400 dark:text-gray-500">
|
||||
...
|
||||
</span>
|
||||
) : (
|
||||
@ -43,7 +43,7 @@ export default function Pagination({
|
||||
className={`min-w-[36px] rounded-lg px-3 py-1.5 text-sm font-medium ${
|
||||
p === page
|
||||
? 'bg-primary-600 text-white'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
: 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
{p}
|
||||
@ -53,7 +53,7 @@ export default function Pagination({
|
||||
<button
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
disabled={page >= totalPages}
|
||||
className="rounded-lg p-2 text-gray-500 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-40"
|
||||
className="rounded-lg p-2 text-gray-500 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-40 dark:text-gray-400 dark:hover:bg-gray-700"
|
||||
>
|
||||
<ChevronRight size={18} />
|
||||
</button>
|
||||
|
||||
@ -35,7 +35,7 @@ export default function SearchBar({
|
||||
return (
|
||||
<div className="relative">
|
||||
<Search
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500"
|
||||
size={18}
|
||||
/>
|
||||
<input
|
||||
@ -48,7 +48,7 @@ export default function SearchBar({
|
||||
{internal && (
|
||||
<button
|
||||
onClick={() => handleChange('')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
|
||||
@ -6,19 +6,19 @@ const statusConfig: Record<
|
||||
> = {
|
||||
DRAFT: {
|
||||
label: 'Entwurf',
|
||||
className: 'bg-gray-100 text-gray-700 ring-gray-300',
|
||||
className: 'bg-gray-100 text-gray-700 ring-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:ring-gray-600',
|
||||
},
|
||||
REVIEW: {
|
||||
label: 'In Prüfung',
|
||||
className: 'bg-yellow-50 text-yellow-700 ring-yellow-300',
|
||||
className: 'bg-yellow-50 text-yellow-700 ring-yellow-300 dark:bg-yellow-900/30 dark:text-yellow-400 dark:ring-yellow-700',
|
||||
},
|
||||
PUBLISHED: {
|
||||
label: 'Veröffentlicht',
|
||||
className: 'bg-green-50 text-green-700 ring-green-300',
|
||||
className: 'bg-green-50 text-green-700 ring-green-300 dark:bg-green-900/30 dark:text-green-400 dark:ring-green-700',
|
||||
},
|
||||
ARCHIVED: {
|
||||
label: 'Archiviert',
|
||||
className: 'bg-red-50 text-red-700 ring-red-300',
|
||||
className: 'bg-red-50 text-red-700 ring-red-300 dark:bg-red-900/30 dark:text-red-400 dark:ring-red-700',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
38
frontend/src/hooks/useDarkMode.ts
Normal file
38
frontend/src/hooks/useDarkMode.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
function getInitialTheme(): boolean {
|
||||
const stored = localStorage.getItem('theme');
|
||||
if (stored === 'dark') return true;
|
||||
if (stored === 'light') return false;
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
export function useDarkMode() {
|
||||
const [isDark, setIsDark] = useState(getInitialTheme);
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
if (isDark) {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
}, [isDark]);
|
||||
|
||||
// Listen for system preference changes when no explicit choice stored
|
||||
useEffect(() => {
|
||||
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const handler = (e: MediaQueryListEvent) => {
|
||||
if (!localStorage.getItem('theme')) {
|
||||
setIsDark(e.matches);
|
||||
}
|
||||
};
|
||||
mq.addEventListener('change', handler);
|
||||
return () => mq.removeEventListener('change', handler);
|
||||
}, []);
|
||||
|
||||
const toggle = () => setIsDark((prev) => !prev);
|
||||
|
||||
return { isDark, toggle };
|
||||
}
|
||||
@ -7,24 +7,24 @@
|
||||
@apply scroll-smooth;
|
||||
}
|
||||
body {
|
||||
@apply min-h-screen bg-gray-50 text-gray-900;
|
||||
@apply min-h-screen bg-gray-50 text-gray-900 dark:bg-gray-900 dark:text-gray-100;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.btn-primary {
|
||||
@apply inline-flex items-center justify-center rounded-lg bg-primary-600 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50;
|
||||
@apply inline-flex items-center justify-center rounded-lg bg-primary-600 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:focus:ring-offset-gray-900;
|
||||
}
|
||||
.btn-secondary {
|
||||
@apply inline-flex items-center justify-center rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50;
|
||||
@apply inline-flex items-center justify-center rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 dark:focus:ring-offset-gray-900;
|
||||
}
|
||||
.btn-danger {
|
||||
@apply inline-flex items-center justify-center rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50;
|
||||
@apply inline-flex items-center justify-center rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:focus:ring-offset-gray-900;
|
||||
}
|
||||
.input-field {
|
||||
@apply block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-gray-400 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500;
|
||||
@apply block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-gray-400 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-400;
|
||||
}
|
||||
.card {
|
||||
@apply rounded-xl border border-gray-200 bg-white shadow-sm;
|
||||
@apply rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800;
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,25 +77,25 @@ export default function Dashboard() {
|
||||
label: 'Gesamt',
|
||||
value: stats.total,
|
||||
icon: FileText,
|
||||
color: 'bg-primary-50 text-primary-600',
|
||||
color: 'bg-primary-50 text-primary-600 dark:bg-primary-900/30 dark:text-primary-400',
|
||||
},
|
||||
{
|
||||
label: 'Veröffentlicht',
|
||||
value: stats.published,
|
||||
icon: FileCheck,
|
||||
color: 'bg-green-50 text-green-600',
|
||||
color: 'bg-green-50 text-green-600 dark:bg-green-900/30 dark:text-green-400',
|
||||
},
|
||||
{
|
||||
label: 'In Prüfung',
|
||||
value: stats.review,
|
||||
icon: Clock,
|
||||
color: 'bg-yellow-50 text-yellow-600',
|
||||
color: 'bg-yellow-50 text-yellow-600 dark:bg-yellow-900/30 dark:text-yellow-400',
|
||||
},
|
||||
{
|
||||
label: 'Entwurf',
|
||||
value: stats.draft,
|
||||
icon: Archive,
|
||||
color: 'bg-gray-100 text-gray-600',
|
||||
color: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400',
|
||||
},
|
||||
];
|
||||
|
||||
@ -104,10 +104,10 @@ export default function Dashboard() {
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
Willkommen, {user?.firstName}!
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Hier ist eine Übersicht Ihrer Workflows.
|
||||
</p>
|
||||
</div>
|
||||
@ -139,10 +139,10 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl font-bold text-gray-900">
|
||||
<p className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{loading ? '-' : card.value}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">{card.label}</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">{card.label}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@ -154,12 +154,12 @@ export default function Dashboard() {
|
||||
to="/workflows/new"
|
||||
className="card flex items-center gap-4 p-5 transition-shadow hover:shadow-md"
|
||||
>
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary-50">
|
||||
<Plus className="text-primary-600" size={24} />
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary-50 dark:bg-primary-900/30">
|
||||
<Plus className="text-primary-600 dark:text-primary-400" size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">Neuer Workflow</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">Neuer Workflow</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Erstellen Sie einen neuen Workflow
|
||||
</p>
|
||||
</div>
|
||||
@ -168,12 +168,12 @@ export default function Dashboard() {
|
||||
to="/workflows?template=true"
|
||||
className="card flex items-center gap-4 p-5 transition-shadow hover:shadow-md"
|
||||
>
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-purple-50">
|
||||
<Layers className="text-purple-600" size={24} />
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-purple-50 dark:bg-purple-900/30">
|
||||
<Layers className="text-purple-600 dark:text-purple-400" size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">Vorlagen durchsuchen</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">Vorlagen durchsuchen</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Starten Sie mit einer bestehenden Vorlage
|
||||
</p>
|
||||
</div>
|
||||
@ -182,13 +182,13 @@ export default function Dashboard() {
|
||||
|
||||
{/* Recent workflows */}
|
||||
<div className="card">
|
||||
<div className="flex items-center justify-between border-b border-gray-200 px-6 py-4">
|
||||
<h2 className="text-base font-semibold text-gray-900">
|
||||
<div className="flex items-center justify-between border-b border-gray-200 px-6 py-4 dark:border-gray-700">
|
||||
<h2 className="text-base font-semibold text-gray-900 dark:text-white">
|
||||
Aktuelle Workflows
|
||||
</h2>
|
||||
<Link
|
||||
to="/workflows"
|
||||
className="text-sm font-medium text-primary-600 hover:text-primary-700"
|
||||
className="text-sm font-medium text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300"
|
||||
>
|
||||
Alle anzeigen
|
||||
</Link>
|
||||
@ -198,34 +198,34 @@ export default function Dashboard() {
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary-600 border-t-transparent" />
|
||||
</div>
|
||||
) : recent.length === 0 ? (
|
||||
<div className="py-12 text-center text-sm text-gray-500">
|
||||
<div className="py-12 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
Noch keine Workflows vorhanden.
|
||||
</div>
|
||||
) : (
|
||||
<ul className="divide-y divide-gray-100">
|
||||
<ul className="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{recent.map((wf) => (
|
||||
<li key={wf.id}>
|
||||
<Link
|
||||
to={`/workflows/${wf.id}`}
|
||||
className="flex items-center gap-4 px-6 py-4 transition-colors hover:bg-gray-50"
|
||||
className="flex items-center gap-4 px-6 py-4 transition-colors hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
>
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100">
|
||||
<FilePlus className="text-gray-500" size={20} />
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700">
|
||||
<FilePlus className="text-gray-500 dark:text-gray-400" size={20} />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate font-medium text-gray-900">
|
||||
<p className="truncate font-medium text-gray-900 dark:text-white">
|
||||
{wf.title}
|
||||
</p>
|
||||
<p className="truncate text-sm text-gray-500">
|
||||
<p className="truncate text-sm text-gray-500 dark:text-gray-400">
|
||||
{wf.tenant && (
|
||||
<span className="font-medium text-primary-600">{wf.tenant.name}</span>
|
||||
<span className="font-medium text-primary-600 dark:text-primary-400">{wf.tenant.name}</span>
|
||||
)}
|
||||
{wf.tenant && <span> · </span>}
|
||||
{wf._count?.steps ?? wf.steps?.length ?? 0} Schritte · v{wf.version}
|
||||
</p>
|
||||
</div>
|
||||
<StatusBadge status={wf.status} />
|
||||
<span className="hidden text-xs text-gray-400 sm:inline">
|
||||
<span className="hidden text-xs text-gray-400 dark:text-gray-500 sm:inline">
|
||||
{new Date(wf.updatedAt).toLocaleDateString('de-DE')}
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
@ -67,24 +67,24 @@ export default function LoginPage() {
|
||||
</div>
|
||||
|
||||
{/* Right login form */}
|
||||
<div className="flex flex-1 items-center justify-center px-6 py-12">
|
||||
<div className="flex flex-1 items-center justify-center bg-white px-6 py-12 dark:bg-gray-900">
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="mb-8 text-center lg:hidden">
|
||||
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-xl bg-primary-600 text-xl font-bold text-white">
|
||||
WP
|
||||
</div>
|
||||
<h1 className="mt-3 text-xl font-bold text-gray-900">
|
||||
<h1 className="mt-3 text-xl font-bold text-gray-900 dark:text-white">
|
||||
Workflow Portal
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold text-gray-900">Anmelden</h2>
|
||||
<p className="mt-2 text-sm text-gray-500">
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">Anmelden</h2>
|
||||
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Melden Sie sich mit Ihren Zugangsdaten an.
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div className="mt-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
<div className="mt-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-800 dark:bg-red-900/30 dark:text-red-400">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@ -93,7 +93,7 @@ export default function LoginPage() {
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="mb-1.5 block text-sm font-medium text-gray-700"
|
||||
className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
E-Mail-Adresse
|
||||
</label>
|
||||
@ -112,7 +112,7 @@ export default function LoginPage() {
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="mb-1.5 block text-sm font-medium text-gray-700"
|
||||
className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Passwort
|
||||
</label>
|
||||
@ -130,7 +130,7 @@ export default function LoginPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
>
|
||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
|
||||
@ -129,8 +129,8 @@ export default function TenantList() {
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Mandanten</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Mandanten</h1>
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Verwalten Sie die Mandanten des Portals.
|
||||
</p>
|
||||
</div>
|
||||
@ -172,47 +172,47 @@ export default function TenantList() {
|
||||
) : (
|
||||
<div className="card overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-700/50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
Name
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
Slug
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
Status
|
||||
</th>
|
||||
<th className="px-6 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||||
<th className="px-6 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-100 bg-white">
|
||||
<tbody className="divide-y divide-gray-100 bg-white dark:divide-gray-700 dark:bg-gray-800">
|
||||
{tenants.map((tenant) => (
|
||||
<tr key={tenant.id} className="hover:bg-gray-50">
|
||||
<tr key={tenant.id} className="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td className="whitespace-nowrap px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary-50 text-sm font-bold text-primary-700">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary-50 text-sm font-bold text-primary-700 dark:bg-primary-900/30 dark:text-primary-400">
|
||||
{tenant.name[0]}
|
||||
</div>
|
||||
<span className="font-medium text-gray-900">
|
||||
<span className="font-medium text-gray-900 dark:text-white">
|
||||
{tenant.name}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
||||
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
{tenant.slug}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-6 py-4">
|
||||
{tenant.active ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-green-50 px-2.5 py-0.5 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-300">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-green-50 px-2.5 py-0.5 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-300 dark:bg-green-900/30 dark:text-green-400 dark:ring-green-700">
|
||||
<Check size={12} />
|
||||
Aktiv
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-300">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-300 dark:bg-gray-700 dark:text-gray-400 dark:ring-gray-600">
|
||||
<X size={12} />
|
||||
Inaktiv
|
||||
</span>
|
||||
@ -222,14 +222,14 @@ export default function TenantList() {
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => openEdit(tenant)}
|
||||
className="rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
|
||||
className="rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300"
|
||||
title="Bearbeiten"
|
||||
>
|
||||
<Edit size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeactivateTarget(tenant)}
|
||||
className="rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
|
||||
className="rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300"
|
||||
title={tenant.active ? 'Deaktivieren' : 'Aktivieren'}
|
||||
>
|
||||
<Power size={16} />
|
||||
@ -256,7 +256,7 @@ export default function TenantList() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Name *
|
||||
</label>
|
||||
<input
|
||||
@ -278,7 +278,7 @@ export default function TenantList() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Slug *
|
||||
</label>
|
||||
<input
|
||||
@ -294,9 +294,9 @@ export default function TenantList() {
|
||||
type="checkbox"
|
||||
checked={formActive}
|
||||
onChange={(e) => setFormActive(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">Aktiv</span>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Aktiv</span>
|
||||
</label>
|
||||
<div className="flex justify-end gap-3 pt-2">
|
||||
<button
|
||||
|
||||
@ -161,8 +161,8 @@ export default function UserList() {
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Benutzer</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Benutzer</h1>
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Verwalten Sie die Benutzer und deren Rollen.
|
||||
</p>
|
||||
</div>
|
||||
@ -236,63 +236,63 @@ export default function UserList() {
|
||||
) : (
|
||||
<div className="card overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-700/50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
Name
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
E-Mail
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
Rolle
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
Mandant
|
||||
</th>
|
||||
<th className="px-6 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||||
<th className="px-6 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-100 bg-white">
|
||||
<tbody className="divide-y divide-gray-100 bg-white dark:divide-gray-700 dark:bg-gray-800">
|
||||
{users.map((u) => (
|
||||
<tr key={u.id} className="hover:bg-gray-50">
|
||||
<tr key={u.id} className="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td className="whitespace-nowrap px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary-100 text-sm font-semibold text-primary-700">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary-100 text-sm font-semibold text-primary-700 dark:bg-primary-900/40 dark:text-primary-400">
|
||||
{u.firstName[0]}
|
||||
{u.lastName[0]}
|
||||
</div>
|
||||
<span className="font-medium text-gray-900">
|
||||
<span className="font-medium text-gray-900 dark:text-white">
|
||||
{u.firstName} {u.lastName}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
||||
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
{u.email}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-6 py-4">
|
||||
<span className="inline-flex rounded-full bg-primary-50 px-2.5 py-0.5 text-xs font-medium text-primary-700">
|
||||
<span className="inline-flex rounded-full bg-primary-50 px-2.5 py-0.5 text-xs font-medium text-primary-700 dark:bg-primary-900/30 dark:text-primary-400">
|
||||
{roleLabels[u.role]}
|
||||
</span>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
||||
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
{u.tenant?.name || '-'}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-6 py-4 text-right">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => openEdit(u)}
|
||||
className="rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
|
||||
className="rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-300"
|
||||
title="Bearbeiten"
|
||||
>
|
||||
<Edit size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeactivateTarget(u)}
|
||||
className="rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-red-500"
|
||||
className="rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-red-500 dark:hover:bg-gray-700"
|
||||
title="Deaktivieren"
|
||||
>
|
||||
<Power size={16} />
|
||||
@ -321,7 +321,7 @@ export default function UserList() {
|
||||
<div className="space-y-4">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Vorname *
|
||||
</label>
|
||||
<input
|
||||
@ -333,7 +333,7 @@ export default function UserList() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Nachname *
|
||||
</label>
|
||||
<input
|
||||
@ -346,7 +346,7 @@ export default function UserList() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
E-Mail *
|
||||
</label>
|
||||
<input
|
||||
@ -358,7 +358,7 @@ export default function UserList() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Passwort {editingUser ? '(leer lassen um beizubehalten)' : '*'}
|
||||
</label>
|
||||
<input
|
||||
@ -371,7 +371,7 @@ export default function UserList() {
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Rolle *
|
||||
</label>
|
||||
<select
|
||||
@ -387,7 +387,7 @@ export default function UserList() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Mandant
|
||||
</label>
|
||||
<select
|
||||
|
||||
@ -280,24 +280,24 @@ export default function WorkflowEditor() {
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
className="inline-flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-700"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
>
|
||||
<ArrowLeft size={16} />
|
||||
Zurück
|
||||
</button>
|
||||
{autoSaved && (
|
||||
<span className="text-xs text-green-600">Automatisch gespeichert</span>
|
||||
<span className="text-xs text-green-600 dark:text-green-400">Automatisch gespeichert</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{isNew ? 'Neuer Workflow' : 'Workflow bearbeiten'}
|
||||
</h1>
|
||||
|
||||
{/* Metadata */}
|
||||
<div className="card space-y-5 p-6">
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Titel *
|
||||
</label>
|
||||
<input
|
||||
@ -310,7 +310,7 @@ export default function WorkflowEditor() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Beschreibung
|
||||
</label>
|
||||
<textarea
|
||||
@ -324,7 +324,7 @@ export default function WorkflowEditor() {
|
||||
|
||||
{isAdmin && isNew && (
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Mandant *
|
||||
</label>
|
||||
<select
|
||||
@ -344,7 +344,7 @@ export default function WorkflowEditor() {
|
||||
|
||||
<div className="grid gap-5 sm:grid-cols-2">
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Kategorie
|
||||
</label>
|
||||
<select
|
||||
@ -362,7 +362,7 @@ export default function WorkflowEditor() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Tags (kommagetrennt)
|
||||
</label>
|
||||
<input
|
||||
@ -380,16 +380,16 @@ export default function WorkflowEditor() {
|
||||
type="checkbox"
|
||||
checked={isTemplate}
|
||||
onChange={(e) => setIsTemplate(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">Als Vorlage speichern</span>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Als Vorlage speichern</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Steps */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Schritte</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">Schritte</h2>
|
||||
<button onClick={addStep} className="btn-secondary gap-1.5 text-sm">
|
||||
<Plus size={16} />
|
||||
Schritt hinzufügen
|
||||
@ -418,21 +418,21 @@ export default function WorkflowEditor() {
|
||||
snapshot.isDragging ? 'shadow-lg ring-2 ring-primary-300' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 border-b border-gray-100 bg-gray-50 px-4 py-2">
|
||||
<div className="flex items-center gap-2 border-b border-gray-100 bg-gray-50 px-4 py-2 dark:border-gray-700 dark:bg-gray-700/50">
|
||||
<div
|
||||
{...provided.dragHandleProps}
|
||||
className="cursor-grab text-gray-400 hover:text-gray-600"
|
||||
className="cursor-grab text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
>
|
||||
<GripVertical size={18} />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-500">
|
||||
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
Schritt {index + 1}
|
||||
</span>
|
||||
<StepTypeIcon type={step.type} />
|
||||
<div className="flex-1" />
|
||||
<button
|
||||
onClick={() => removeStep(index)}
|
||||
className="rounded p-1 text-gray-400 hover:bg-red-50 hover:text-red-500"
|
||||
className="rounded p-1 text-gray-400 hover:bg-red-50 hover:text-red-500 dark:hover:bg-red-900/20"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
@ -440,7 +440,7 @@ export default function WorkflowEditor() {
|
||||
<div className="space-y-4 p-4">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-gray-500">
|
||||
<label className="mb-1 block text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
Titel
|
||||
</label>
|
||||
<input
|
||||
@ -455,7 +455,7 @@ export default function WorkflowEditor() {
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1">
|
||||
<label className="mb-1 block text-xs font-medium text-gray-500">
|
||||
<label className="mb-1 block text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
Typ
|
||||
</label>
|
||||
<select
|
||||
@ -488,9 +488,9 @@ export default function WorkflowEditor() {
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700"
|
||||
/>
|
||||
<span className="text-xs text-gray-600">
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||
Pflicht
|
||||
</span>
|
||||
</label>
|
||||
@ -498,7 +498,7 @@ export default function WorkflowEditor() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-gray-500">
|
||||
<label className="mb-1 block text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
Inhalt
|
||||
</label>
|
||||
<textarea
|
||||
@ -532,7 +532,7 @@ export default function WorkflowEditor() {
|
||||
|
||||
{steps.length === 0 && (
|
||||
<div className="card py-12 text-center">
|
||||
<p className="text-sm text-gray-500">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Noch keine Schritte vorhanden.
|
||||
</p>
|
||||
<button onClick={addStep} className="btn-primary mt-4 gap-1.5">
|
||||
|
||||
@ -83,10 +83,10 @@ export default function WorkflowList() {
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{isTemplate ? 'Vorlagen' : 'Workflows'}
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{isTemplate
|
||||
? 'Wiederverwendbare Workflow-Vorlagen'
|
||||
: 'Alle Workflows Ihres Mandanten'}
|
||||
@ -157,13 +157,13 @@ export default function WorkflowList() {
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
<div className="flex rounded-lg border border-gray-300 bg-white">
|
||||
<div className="flex rounded-lg border border-gray-300 bg-white dark:border-gray-600 dark:bg-gray-800">
|
||||
<button
|
||||
onClick={() => setViewMode('grid')}
|
||||
className={`rounded-l-lg p-2 ${
|
||||
viewMode === 'grid'
|
||||
? 'bg-primary-50 text-primary-600'
|
||||
: 'text-gray-400 hover:text-gray-600'
|
||||
? 'bg-primary-50 text-primary-600 dark:bg-primary-900/30 dark:text-primary-400'
|
||||
: 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<Grid3X3 size={18} />
|
||||
@ -172,8 +172,8 @@ export default function WorkflowList() {
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`rounded-r-lg p-2 ${
|
||||
viewMode === 'list'
|
||||
? 'bg-primary-50 text-primary-600'
|
||||
: 'text-gray-400 hover:text-gray-600'
|
||||
? 'bg-primary-50 text-primary-600 dark:bg-primary-900/30 dark:text-primary-400'
|
||||
: 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<List size={18} />
|
||||
@ -212,23 +212,23 @@ export default function WorkflowList() {
|
||||
className="card p-5 transition-shadow hover:shadow-md"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h3 className="line-clamp-1 font-semibold text-gray-900">
|
||||
<h3 className="line-clamp-1 font-semibold text-gray-900 dark:text-white">
|
||||
{wf.title}
|
||||
</h3>
|
||||
<StatusBadge status={wf.status} />
|
||||
</div>
|
||||
{wf.tenant && (
|
||||
<div className="mt-2 flex items-center gap-1.5 text-xs font-medium text-primary-600">
|
||||
<div className="mt-2 flex items-center gap-1.5 text-xs font-medium text-primary-600 dark:text-primary-400">
|
||||
<Building2 size={12} />
|
||||
{wf.tenant.name}
|
||||
</div>
|
||||
)}
|
||||
{wf.description && (
|
||||
<p className="mt-1 line-clamp-2 text-sm text-gray-500">
|
||||
<p className="mt-1 line-clamp-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
{wf.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 flex items-center justify-between text-xs text-gray-400">
|
||||
<div className="mt-4 flex items-center justify-between text-xs text-gray-400 dark:text-gray-500">
|
||||
<span>
|
||||
{wf._count?.steps ?? wf.steps?.length ?? 0} Schritte · v{wf.version}
|
||||
</span>
|
||||
@ -237,7 +237,7 @@ export default function WorkflowList() {
|
||||
</span>
|
||||
</div>
|
||||
{wf.category && (
|
||||
<div className="mt-3 flex items-center gap-1.5 text-xs text-gray-500">
|
||||
<div className="mt-3 flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
<Tag size={12} />
|
||||
{wf.category.name}
|
||||
</div>
|
||||
@ -247,13 +247,13 @@ export default function WorkflowList() {
|
||||
{wf.tags.slice(0, 3).map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600"
|
||||
className="rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600 dark:bg-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{wf.tags.length > 3 && (
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-xs text-gray-400 dark:text-gray-500">
|
||||
+{wf.tags.length - 3}
|
||||
</span>
|
||||
)}
|
||||
@ -263,36 +263,36 @@ export default function WorkflowList() {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="card divide-y divide-gray-100">
|
||||
<div className="card divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{workflows.map((wf) => (
|
||||
<Link
|
||||
key={wf.id}
|
||||
to={`/workflows/${wf.id}`}
|
||||
className="flex items-center gap-4 px-6 py-4 transition-colors hover:bg-gray-50"
|
||||
className="flex items-center gap-4 px-6 py-4 transition-colors hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
>
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100">
|
||||
<FileText className="text-gray-500" size={20} />
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700">
|
||||
<FileText className="text-gray-500 dark:text-gray-400" size={20} />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate font-medium text-gray-900">{wf.title}</p>
|
||||
<p className="truncate text-sm text-gray-500">
|
||||
<p className="truncate font-medium text-gray-900 dark:text-white">{wf.title}</p>
|
||||
<p className="truncate text-sm text-gray-500 dark:text-gray-400">
|
||||
{wf.tenant && (
|
||||
<span className="font-medium text-primary-600">{wf.tenant.name}</span>
|
||||
<span className="font-medium text-primary-600 dark:text-primary-400">{wf.tenant.name}</span>
|
||||
)}
|
||||
{wf.tenant && wf.description && <span> · </span>}
|
||||
{wf.description}
|
||||
</p>
|
||||
</div>
|
||||
{wf.category && (
|
||||
<span className="hidden rounded bg-gray-100 px-2 py-1 text-xs text-gray-600 md:inline">
|
||||
<span className="hidden rounded bg-gray-100 px-2 py-1 text-xs text-gray-600 md:inline dark:bg-gray-700 dark:text-gray-300">
|
||||
{wf.category.name}
|
||||
</span>
|
||||
)}
|
||||
<span className="hidden text-sm text-gray-400 sm:inline">
|
||||
<span className="hidden text-sm text-gray-400 dark:text-gray-500 sm:inline">
|
||||
{wf._count?.steps ?? wf.steps?.length ?? 0} Schritte
|
||||
</span>
|
||||
<StatusBadge status={wf.status} />
|
||||
<span className="hidden text-xs text-gray-400 lg:inline">
|
||||
<span className="hidden text-xs text-gray-400 dark:text-gray-500 lg:inline">
|
||||
{new Date(wf.updatedAt).toLocaleDateString('de-DE')}
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
@ -23,15 +23,15 @@ import ConfirmDialog from '../components/ConfirmDialog';
|
||||
function getStepBgClass(type: WorkflowStep['type']): string {
|
||||
switch (type) {
|
||||
case 'WARNING':
|
||||
return 'border-l-4 border-l-amber-400 bg-amber-50';
|
||||
return 'border-l-4 border-l-amber-400 bg-amber-50 dark:bg-amber-900/20';
|
||||
case 'COMMAND':
|
||||
return 'border-l-4 border-l-purple-400 bg-gray-900';
|
||||
return 'border-l-4 border-l-purple-400 bg-gray-900 dark:bg-gray-950';
|
||||
case 'INFO':
|
||||
return 'border-l-4 border-l-cyan-400 bg-cyan-50';
|
||||
return 'border-l-4 border-l-cyan-400 bg-cyan-50 dark:bg-cyan-900/20';
|
||||
case 'CHECKLIST':
|
||||
return 'border-l-4 border-l-green-400 bg-green-50';
|
||||
return 'border-l-4 border-l-green-400 bg-green-50 dark:bg-green-900/20';
|
||||
default:
|
||||
return 'border-l-4 border-l-primary-400 bg-white';
|
||||
return 'border-l-4 border-l-primary-400 bg-white dark:bg-gray-800';
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ export default function WorkflowView() {
|
||||
{/* Back */}
|
||||
<Link
|
||||
to="/workflows"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-700"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
>
|
||||
<ArrowLeft size={16} />
|
||||
Zurück zu Workflows
|
||||
@ -126,15 +126,15 @@ export default function WorkflowView() {
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{workflow.title}
|
||||
</h1>
|
||||
<StatusBadge status={workflow.status} />
|
||||
</div>
|
||||
{workflow.description && (
|
||||
<p className="mt-2 text-gray-600">{workflow.description}</p>
|
||||
<p className="mt-2 text-gray-600 dark:text-gray-300">{workflow.description}</p>
|
||||
)}
|
||||
<div className="mt-4 flex flex-wrap items-center gap-4 text-sm text-gray-500">
|
||||
<div className="mt-4 flex flex-wrap items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span>Version {workflow.version}</span>
|
||||
<span>{sortedSteps.length} Schritte</span>
|
||||
{workflow.category && <span>{workflow.category.name}</span>}
|
||||
@ -152,7 +152,7 @@ export default function WorkflowView() {
|
||||
{workflow.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="rounded-full bg-primary-50 px-3 py-0.5 text-xs font-medium text-primary-700"
|
||||
className="rounded-full bg-primary-50 px-3 py-0.5 text-xs font-medium text-primary-700 dark:bg-primary-900/30 dark:text-primary-400"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
@ -181,7 +181,7 @@ export default function WorkflowView() {
|
||||
{workflow.status !== 'ARCHIVED' && (
|
||||
<button
|
||||
onClick={() => setArchiveOpen(true)}
|
||||
className="btn-secondary gap-1.5 text-red-600 hover:bg-red-50"
|
||||
className="btn-secondary gap-1.5 text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
|
||||
>
|
||||
<Archive size={16} />
|
||||
Archivieren
|
||||
@ -193,9 +193,9 @@ export default function WorkflowView() {
|
||||
|
||||
{/* Steps */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Schritte</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">Schritte</h2>
|
||||
{sortedSteps.length === 0 ? (
|
||||
<div className="card py-12 text-center text-sm text-gray-500">
|
||||
<div className="card py-12 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
Keine Schritte vorhanden.
|
||||
</div>
|
||||
) : (
|
||||
@ -208,21 +208,21 @@ export default function WorkflowView() {
|
||||
<div className="p-5">
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Step number */}
|
||||
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-white text-sm font-bold text-gray-700 shadow-sm ring-1 ring-gray-200">
|
||||
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-white text-sm font-bold text-gray-700 shadow-sm ring-1 ring-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:ring-gray-600">
|
||||
{idx + 1}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<StepTypeIcon type={step.type} showLabel />
|
||||
{step.isRequired && (
|
||||
<span className="rounded bg-red-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-red-600">
|
||||
<span className="rounded bg-red-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-red-600 dark:bg-red-900/40 dark:text-red-400">
|
||||
Pflicht
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h3
|
||||
className={`mt-1 font-semibold ${
|
||||
step.type === 'COMMAND' ? 'text-gray-100' : 'text-gray-900'
|
||||
step.type === 'COMMAND' ? 'text-gray-100' : 'text-gray-900 dark:text-white'
|
||||
}`}
|
||||
>
|
||||
{step.title}
|
||||
@ -235,10 +235,10 @@ export default function WorkflowView() {
|
||||
<div
|
||||
className={`mt-2 whitespace-pre-wrap text-sm ${
|
||||
step.type === 'WARNING'
|
||||
? 'text-amber-800'
|
||||
? 'text-amber-800 dark:text-amber-300'
|
||||
: step.type === 'INFO'
|
||||
? 'text-cyan-800'
|
||||
: 'text-gray-600'
|
||||
? 'text-cyan-800 dark:text-cyan-300'
|
||||
: 'text-gray-600 dark:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
{step.content}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user