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:
parent
7f9e302e5e
commit
b0561ce714
@ -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> · </span>}
|
||||
{wf._count?.steps ?? wf.steps?.length ?? 0} Schritte · v{wf.version}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -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> · </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">
|
||||
|
||||
@ -29,6 +29,7 @@ export interface Workflow {
|
||||
tags: string[];
|
||||
steps?: WorkflowStep[];
|
||||
_count?: { steps: number };
|
||||
tenant?: Tenant;
|
||||
createdBy: User;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user