feat: Mandanten-Zuordnung sichtbar auf Workflow-Karten + Mandanten-Filter

- Mandantenname auf allen Workflow-Karten (Grid + Liste)
- Mandantenname im Dashboard bei aktuellen Workflows
- Mandanten-Filter-Dropdown in der Workflow-Liste (für Admins/Techniker)
- Tenant-Info im Workflow-Typ ergänzt
This commit is contained in:
root 2026-03-20 23:32:55 +00:00
parent 7f9e302e5e
commit b0561ce714
3 changed files with 50 additions and 10 deletions

View File

@ -217,6 +217,10 @@ export default function Dashboard() {
{wf.title}
</p>
<p className="truncate text-sm text-gray-500">
{wf.tenant && (
<span className="font-medium text-primary-600">{wf.tenant.name}</span>
)}
{wf.tenant && <span> &middot; </span>}
{wf._count?.steps ?? wf.steps?.length ?? 0} Schritte &middot; v{wf.version}
</p>
</div>

View File

@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import {
Building2,
FileText,
Grid3X3,
List,
@ -8,8 +9,9 @@ import {
Tag,
} from 'lucide-react';
import toast from 'react-hot-toast';
import { workflowsApi, categoriesApi } from '../services/api';
import type { Category, Workflow } from '../types';
import { workflowsApi, categoriesApi, tenantsApi } from '../services/api';
import { useAuth } from '../hooks/useAuth';
import type { Category, Tenant, Workflow } from '../types';
import SearchBar from '../components/SearchBar';
import StatusBadge from '../components/StatusBadge';
import Pagination from '../components/Pagination';
@ -28,12 +30,16 @@ const statusOptions: { value: string; label: string }[] = [
export default function WorkflowList() {
const [searchParams, setSearchParams] = useSearchParams();
const isTemplate = searchParams.get('template') === 'true';
const { user } = useAuth();
const isAdmin = user?.role === 'SYSTEM_ADMIN' || user?.role === 'TECHNICIAN';
const [workflows, setWorkflows] = useState<Workflow[]>([]);
const [categories, setCategories] = useState<Category[]>([]);
const [tenants, setTenants] = useState<Tenant[]>([]);
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState('');
const [categoryFilter, setCategoryFilter] = useState('');
const [tenantFilter, setTenantFilter] = useState('');
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [loading, setLoading] = useState(true);
@ -41,11 +47,14 @@ export default function WorkflowList() {
useEffect(() => {
categoriesApi.list().then(setCategories).catch(() => {});
}, []);
if (isAdmin) {
tenantsApi.list({ pageSize: 100 }).then((r) => setTenants(r.data)).catch(() => {});
}
}, [isAdmin]);
useEffect(() => {
loadWorkflows();
}, [search, statusFilter, categoryFilter, page, isTemplate]);
}, [search, statusFilter, categoryFilter, tenantFilter, page, isTemplate]);
const loadWorkflows = async () => {
setLoading(true);
@ -57,6 +66,7 @@ export default function WorkflowList() {
status: statusFilter || undefined,
categoryId: categoryFilter || undefined,
isTemplate: isTemplate || undefined,
tenantId: tenantFilter || undefined,
};
const res = await workflowsApi.list(params);
setWorkflows(res.data);
@ -130,6 +140,23 @@ export default function WorkflowList() {
</option>
))}
</select>
{isAdmin && tenants.length > 0 && (
<select
value={tenantFilter}
onChange={(e) => {
setTenantFilter(e.target.value);
setPage(1);
}}
className="input-field w-auto"
>
<option value="">Alle Mandanten</option>
{tenants.map((t) => (
<option key={t.id} value={t.id}>
{t.name}
</option>
))}
</select>
)}
<div className="flex rounded-lg border border-gray-300 bg-white">
<button
onClick={() => setViewMode('grid')}
@ -190,8 +217,14 @@ export default function WorkflowList() {
</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">
<Building2 size={12} />
{wf.tenant.name}
</div>
)}
{wf.description && (
<p className="mt-2 line-clamp-2 text-sm text-gray-500">
<p className="mt-1 line-clamp-2 text-sm text-gray-500">
{wf.description}
</p>
)}
@ -242,11 +275,13 @@ export default function WorkflowList() {
</div>
<div className="min-w-0 flex-1">
<p className="truncate font-medium text-gray-900">{wf.title}</p>
{wf.description && (
<p className="truncate text-sm text-gray-500">
{wf.tenant && (
<span className="font-medium text-primary-600">{wf.tenant.name}</span>
)}
{wf.tenant && wf.description && <span> &middot; </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">

View File

@ -29,6 +29,7 @@ export interface Workflow {
tags: string[];
steps?: WorkflowStep[];
_count?: { steps: number };
tenant?: Tenant;
createdBy: User;
createdAt: string;
updatedAt: string;