Kundenportal: Toggle "Deaktivierte Verträge anzeigen"
Der Admin-/Mitarbeiter-Blick (CustomerDetail) hatte den Toggle schon; jetzt auch in der Kundenportal-Vertragsübersicht (ContractList, Portal-Zweig). Button nur bei isCustomerPortal, reicht includeDeactivated an die getTreeForCustomer-Queries durch (showDeactivated im Query-Key → frischer Fetch beim Umschalten). Security: derselbe Endpoint hinter canAccessCustomer (R120) – der Portal-Kunde bekommt nur eigene/vertretene Bäume, das Flag weitet nur den Status-Filter innerhalb der erlaubten Daten. Keine neue Exposition. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,17 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
|||||||
|
|
||||||
## ✅ Erledigt
|
## ✅ Erledigt
|
||||||
|
|
||||||
|
- [x] **👁 Kundenportal: Toggle „Deaktivierte Verträge anzeigen"**
|
||||||
|
- Der Admin-/Mitarbeiter-Blick (CustomerDetail) hatte den Toggle schon;
|
||||||
|
jetzt auch in der Kundenportal-Vertragsübersicht (`ContractList`,
|
||||||
|
Portal-Zweig). Button nur bei `isCustomerPortal`, reicht
|
||||||
|
`includeDeactivated` an die `getTreeForCustomer`-Queries durch
|
||||||
|
(`showDeactivated` im Query-Key → frischer Fetch beim Umschalten).
|
||||||
|
- Security: nutzt denselben Endpoint hinter `canAccessCustomer`
|
||||||
|
(R120) – Portal-Kunde sieht nur eigene/vertretene Bäume, das Flag
|
||||||
|
weitet nur den Status-Filter innerhalb der erlaubten Daten. Keine
|
||||||
|
neue Exposition.
|
||||||
|
|
||||||
- [x] **🔒 Mobilfunknetz: server-seitige Whitelist (Pentest-INFO)**
|
- [x] **🔒 Mobilfunknetz: server-seitige Whitelist (Pentest-INFO)**
|
||||||
- `mobileNetwork` akzeptierte serverseitig jeden (stripHtml-bereinigten)
|
- `mobileNetwork` akzeptierte serverseitig jeden (stripHtml-bereinigten)
|
||||||
String – das Frontend-Dropdown beschränkte nur clientseitig. Jetzt
|
String – das Frontend-Dropdown beschränkte nur clientseitig. Jetzt
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Input from '../../components/ui/Input';
|
|||||||
import Select from '../../components/ui/Select';
|
import Select from '../../components/ui/Select';
|
||||||
import Badge from '../../components/ui/Badge';
|
import Badge from '../../components/ui/Badge';
|
||||||
import CopyButton from '../../components/ui/CopyButton';
|
import CopyButton from '../../components/ui/CopyButton';
|
||||||
import { Plus, Search, Eye, Edit, Trash2, User, Users, ChevronDown, ChevronRight, Info, X, ShieldAlert } from 'lucide-react';
|
import { Plus, Search, Eye, EyeOff, Edit, Trash2, User, Users, ChevronDown, ChevronRight, Info, X, ShieldAlert } from 'lucide-react';
|
||||||
import { gdprApi } from '../../services/api';
|
import { gdprApi } from '../../services/api';
|
||||||
import { formatDate } from '../../utils/dateFormat';
|
import { formatDate } from '../../utils/dateFormat';
|
||||||
import { getContractTypeInfo } from '../../utils/contractInfo';
|
import { getContractTypeInfo } from '../../utils/contractInfo';
|
||||||
@@ -100,6 +100,9 @@ export default function ContractList() {
|
|||||||
// Status-Info Modal
|
// Status-Info Modal
|
||||||
const [showStatusInfo, setShowStatusInfo] = useState(false);
|
const [showStatusInfo, setShowStatusInfo] = useState(false);
|
||||||
|
|
||||||
|
// Toggle: deaktivierte Verträge im Kundenportal-Baum mit anzeigen
|
||||||
|
const [showDeactivated, setShowDeactivated] = useState(false);
|
||||||
|
|
||||||
const { hasPermission, isCustomer, isCustomerPortal, user } = useAuth();
|
const { hasPermission, isCustomer, isCustomerPortal, user } = useAuth();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
@@ -167,8 +170,8 @@ export default function ContractList() {
|
|||||||
// Baumstruktur für alle Kunden laden (Kundenportal)
|
// Baumstruktur für alle Kunden laden (Kundenportal)
|
||||||
const treeQueries = useQueries({
|
const treeQueries = useQueries({
|
||||||
queries: allCustomerIds.map(customerId => ({
|
queries: allCustomerIds.map(customerId => ({
|
||||||
queryKey: ['contract-tree', customerId],
|
queryKey: ['contract-tree', customerId, showDeactivated],
|
||||||
queryFn: () => contractApi.getTreeForCustomer(customerId),
|
queryFn: () => contractApi.getTreeForCustomer(customerId, showDeactivated),
|
||||||
enabled: isCustomerPortal,
|
enabled: isCustomerPortal,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
@@ -341,14 +344,27 @@ export default function ContractList() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h1 className="text-2xl font-bold">Verträge</h1>
|
<h1 className="text-2xl font-bold">Verträge</h1>
|
||||||
{hasPermission('contracts:create') && !isCustomer && (
|
<div className="flex items-center gap-3">
|
||||||
<Link to="/contracts/new">
|
{isCustomerPortal && (
|
||||||
<Button>
|
<Button
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
size="sm"
|
||||||
Neuer Vertrag
|
variant={showDeactivated ? 'secondary' : 'ghost'}
|
||||||
|
onClick={() => setShowDeactivated((v) => !v)}
|
||||||
|
title="Deaktivierte Verträge ein-/ausblenden"
|
||||||
|
>
|
||||||
|
{showDeactivated ? <EyeOff className="w-4 h-4 mr-2" /> : <Eye className="w-4 h-4 mr-2" />}
|
||||||
|
{showDeactivated ? 'Deaktivierte ausblenden' : 'Deaktivierte anzeigen'}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
)}
|
||||||
)}
|
{hasPermission('contracts:create') && !isCustomer && (
|
||||||
|
<Link to="/contracts/new">
|
||||||
|
<Button>
|
||||||
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
|
Neuer Vertrag
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="mb-6">
|
<Card className="mb-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user