feat: add multi-select functionality across various pages

- Implemented checkbox selection for multiple items in DeploymentRulesPage, DomainsPage, EnvironmentsPage, ServicesPage, TargetsPage, TemplateCategoriesPage, and TemplatesPage.
- Added delete functionality for selected items with confirmation prompts.
- Updated state management to handle selected IDs for batch operations.
- Enhanced UI with a toolbar for actions related to selected items.
- Adjusted API calls to accommodate changes in data structure, including deployment requests and job details.
This commit is contained in:
Torsten Brendgen
2026-07-09 23:49:10 +02:00
parent 3083e81452
commit 1894f740b0
13 changed files with 911 additions and 118 deletions

View File

@@ -1,6 +1,7 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
Button,
Checkbox,
Combobox,
Dialog,
DialogActions,
@@ -66,6 +67,7 @@ export function TargetsPage() {
const [providerType, setProviderType] = useState("OnPrem");
const [externalId, setExternalId] = useState("");
const [metadataJson, setMetadataJson] = useState("");
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const { data, error, isLoading } = useQuery({
queryKey: ["targets"],
@@ -132,6 +134,15 @@ export function TargetsPage() {
await queryClient.invalidateQueries({ queryKey: ["targets"] });
},
});
const deleteSelectedTargets = useMutation({
mutationFn: async (ids: string[]) => {
await Promise.all(ids.map((id) => portalApi.deleteTarget(id)));
},
onSuccess: async () => {
setSelectedIds([]);
await queryClient.invalidateQueries({ queryKey: ["targets"] });
},
});
const linkTargetToDomain = useMutation({
mutationFn: ({ targetId, targetDomainId }: { targetId: string; targetDomainId: string }) =>
@@ -172,6 +183,10 @@ export function TargetsPage() {
const domainNameById = new Map((domains ?? []).map((domain: Domain) => [domain.id, domain.name]));
const linkedTargets = (data ?? []).filter((target) => Boolean(target.domainID));
const unlinkedTargets = (data ?? []).filter((target) => !target.domainID);
const allSelected = Boolean(data?.length) && data!.every((target) => selectedIds.includes(target.id));
const toggleSelected = (id: string, checked: boolean) => {
setSelectedIds((current) => (checked ? [...new Set([...current, id])] : current.filter((entry) => entry !== id)));
};
return (
<>
@@ -203,6 +218,19 @@ export function TargetsPage() {
>
Unlink Domain
</Button>
<Button
appearance="secondary"
disabled={selectedIds.length === 0 || deleteSelectedTargets.isPending}
icon={<DeleteRegular />}
onClick={() => {
if (window.confirm(`${selectedIds.length} Targets wirklich loeschen?`)) {
deleteSelectedTargets.mutate(selectedIds);
}
}}
style={{ marginLeft: "10px" }}
>
Auswahl loeschen
</Button>
</div>
<Dialog open={dialogMode !== null} onOpenChange={(_, dialogData) => !dialogData.open && closeDialog()}>
@@ -373,12 +401,18 @@ export function TargetsPage() {
<DataState
isLoading={isLoading || domainsLoading}
error={error ?? domainsError ?? deleteTarget.error ?? linkTargetToDomain.error ?? unlinkTargetFromDomain.error}
error={error ?? domainsError ?? deleteTarget.error ?? deleteSelectedTargets.error ?? linkTargetToDomain.error ?? unlinkTargetFromDomain.error}
/>
{data && (
<Table aria-label="Targets">
<TableHeader>
<TableRow>
<TableHeaderCell>
<Checkbox
checked={allSelected}
onChange={(_, checkboxData) => setSelectedIds(checkboxData.checked ? data.map((target) => target.id) : [])}
/>
</TableHeaderCell>
<TableHeaderCell>Name</TableHeaderCell>
<TableHeaderCell>Type</TableHeaderCell>
<TableHeaderCell>Provider</TableHeaderCell>
@@ -391,6 +425,12 @@ export function TargetsPage() {
<TableBody>
{data.map((target) => (
<TableRow key={target.id}>
<TableCell>
<Checkbox
checked={selectedIds.includes(target.id)}
onChange={(_, checkboxData) => toggleSelected(target.id, Boolean(checkboxData.checked))}
/>
</TableCell>
<TableCell>{target.name}</TableCell>
<TableCell>{target.targetType}</TableCell>
<TableCell>{target.providerType}</TableCell>