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:
@@ -1,6 +1,7 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Combobox,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
@@ -161,6 +162,7 @@ export function TemplatesPage() {
|
||||
const [jsonResources, setJsonResources] = useState("[]");
|
||||
const [editorTab, setEditorTab] = useState<TemplateEditorTab>("parameters");
|
||||
const [serviceFilterId, setServiceFilterId] = useState("all");
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
const [expandedServiceGroups, setExpandedServiceGroups] = useState<Record<string, boolean>>({});
|
||||
const [expandedCategoryGroups, setExpandedCategoryGroups] = useState<Record<string, boolean>>({});
|
||||
const { data, error, isLoading } = useQuery({
|
||||
@@ -255,6 +257,15 @@ export function TemplatesPage() {
|
||||
await queryClient.invalidateQueries({ queryKey: ["templates"] });
|
||||
},
|
||||
});
|
||||
const deleteSelectedTemplates = useMutation({
|
||||
mutationFn: async (ids: string[]) => {
|
||||
await Promise.all(ids.map((id) => portalApi.deleteTemplate(id)));
|
||||
},
|
||||
onSuccess: async () => {
|
||||
setSelectedIds([]);
|
||||
await queryClient.invalidateQueries({ queryKey: ["templates"] });
|
||||
},
|
||||
});
|
||||
|
||||
const submitTemplate = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
@@ -326,6 +337,11 @@ export function TemplatesPage() {
|
||||
.sort((a, b) => a.categoryName.localeCompare(b.categoryName)),
|
||||
}))
|
||||
.filter((group) => group.categories.length > 0);
|
||||
const allSelected =
|
||||
filteredTemplates.length > 0 && filteredTemplates.every((template) => selectedIds.includes(template.id));
|
||||
const toggleSelected = (id: string, checked: boolean) => {
|
||||
setSelectedIds((current) => (checked ? [...new Set([...current, id])] : current.filter((entry) => entry !== id)));
|
||||
};
|
||||
|
||||
const toggleServiceGroup = (serviceId: string) => {
|
||||
setExpandedServiceGroups((current) => ({
|
||||
@@ -350,6 +366,18 @@ export function TemplatesPage() {
|
||||
<Button appearance="primary" icon={<AddRegular />} onClick={openAddDialog}>
|
||||
Template hinzufuegen
|
||||
</Button>
|
||||
<Button
|
||||
appearance="secondary"
|
||||
disabled={selectedIds.length === 0 || deleteSelectedTemplates.isPending}
|
||||
icon={<DeleteRegular />}
|
||||
onClick={() => {
|
||||
if (window.confirm(`${selectedIds.length} Templates wirklich loeschen?`)) {
|
||||
deleteSelectedTemplates.mutate(selectedIds);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Auswahl loeschen
|
||||
</Button>
|
||||
</div>
|
||||
<Field className={styles.filter} label="Service Filter">
|
||||
<Combobox
|
||||
@@ -487,12 +515,20 @@ export function TemplatesPage() {
|
||||
|
||||
<DataState
|
||||
isLoading={isLoading || templateCategoriesLoading || servicesLoading}
|
||||
error={error ?? templateCategoriesError ?? servicesError ?? deleteTemplate.error}
|
||||
error={error ?? templateCategoriesError ?? servicesError ?? deleteTemplate.error ?? deleteSelectedTemplates.error}
|
||||
/>
|
||||
{data && (
|
||||
<Table aria-label="Templates">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell>
|
||||
<Checkbox
|
||||
checked={allSelected}
|
||||
onChange={(_, checkboxData) =>
|
||||
setSelectedIds(checkboxData.checked ? filteredTemplates.map((template) => template.id) : [])
|
||||
}
|
||||
/>
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell>Name</TableHeaderCell>
|
||||
<TableHeaderCell>Version</TableHeaderCell>
|
||||
<TableHeaderCell>Template Category</TableHeaderCell>
|
||||
@@ -504,7 +540,7 @@ export function TemplatesPage() {
|
||||
{groupedTemplates.map((group) => (
|
||||
<Fragment key={`group-${group.serviceId}`}>
|
||||
<TableRow className={styles.groupRow} onClick={() => toggleServiceGroup(group.serviceId)}>
|
||||
<TableCell colSpan={5}>
|
||||
<TableCell colSpan={6}>
|
||||
<div className={styles.groupHeader}>
|
||||
{expandedServiceGroups[group.serviceId] !== false ? <ChevronDownRegular /> : <ChevronRightRegular />}
|
||||
<strong>{group.serviceName}</strong>
|
||||
@@ -517,7 +553,7 @@ export function TemplatesPage() {
|
||||
className={styles.groupRow}
|
||||
onClick={() => toggleCategoryGroup(group.serviceId, categoryGroup.categoryId)}
|
||||
>
|
||||
<TableCell className={styles.categoryHeaderCell} colSpan={5}>
|
||||
<TableCell className={styles.categoryHeaderCell} colSpan={6}>
|
||||
<div className={styles.groupHeader}>
|
||||
{expandedCategoryGroups[`${group.serviceId}:${categoryGroup.categoryId}`] !== false ? (
|
||||
<ChevronDownRegular />
|
||||
@@ -531,6 +567,12 @@ export function TemplatesPage() {
|
||||
{expandedCategoryGroups[`${group.serviceId}:${categoryGroup.categoryId}`] !== false &&
|
||||
categoryGroup.items.map((template) => (
|
||||
<TableRow key={template.id}>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
checked={selectedIds.includes(template.id)}
|
||||
onChange={(_, checkboxData) => toggleSelected(template.id, Boolean(checkboxData.checked))}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={styles.nestedLevel2Cell}>{template.name}</TableCell>
|
||||
<TableCell>{template.version}</TableCell>
|
||||
<TableCell>
|
||||
|
||||
Reference in New Issue
Block a user