Skip to content

Commit cc50d2b

Browse files
committed
feat(dashboard): implement bulk selection and deletion for admins and cores
- Added bulk selection functionality in the admins and cores tables. - Introduced bulk delete actions with confirmation dialogs for selected admins and cores. - Updated localization files to remove "Active Users" label and adjust translations accordingly. - Enhanced user interface components to support selection and bulk actions.
1 parent 3e714ae commit cc50d2b

33 files changed

+1573
-663
lines changed

dashboard/public/statics/locales/en.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,6 @@
553553
"saving": "Saving...",
554554
"general": "General",
555555
"core": "Core Configs",
556-
"activeUsers": "Active Users",
557556
"apply": "Apply",
558557
"cancel": "Cancel",
559558
"created": "Created",

dashboard/public/statics/locales/fa.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,6 @@
441441
}
442442
},
443443
"core": "هسته‌ها",
444-
"activeUsers": "کاربران فعال",
445444
"apply": "تایید",
446445
"cancel": "لغو",
447446
"created": "ایجاد شده",
@@ -1972,7 +1971,7 @@
19721971
"reviewAndApplyDesc": "تنظیمات خود را بررسی کنید و عملیات گروهی را اعمال کنید",
19731972
"operationSummary": "خلاصه عملیات",
19741973
"settings": "تنظیمات",
1975-
"targets": "اهداف",
1974+
"targets": "هدف",
19761975
"allTargets": "همه کاربران، مدیران و گروه‌ها",
19771976
"warning": "هشدار",
19781977
"applyToAllWarning": "این عملیات به همه کاربران، مدیران و گروه‌های سیستم اعمال خواهد شد.",

dashboard/public/statics/locales/ru.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,6 @@
539539
"saving": "Сохранение...",
540540
"general": "Общие",
541541
"core": "Ядра",
542-
"activeUsers": "Пользователи",
543542
"apply": "Применить",
544543
"cancel": "Отмена",
545544
"created": "Создано",

dashboard/public/statics/locales/zh.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,6 @@
552552
"saving": "保存中...",
553553
"general": "常规",
554554
"core": "核心",
555-
"activeUsers": "活跃用户",
556555
"apply": "应用",
557556
"cancel": "取消",
558557
"created": "已创建",

dashboard/src/components/admins/admins-table.tsx

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useTranslation } from 'react-i18next'
22
import type { AdminDetails } from '@/service/api'
3-
import { useActivateAllDisabledUsers, useDisableAllActiveUsers, useGetAdmins, useRemoveAllUsers } from '@/service/api'
3+
import { useActivateAllDisabledUsers, useBulkDeleteAdmins, useDisableAllActiveUsers, useGetAdmins, useRemoveAllUsers } from '@/service/api'
44
import { DataTable } from './data-table'
55
import { setupColumns } from './columns'
66
import { Filters } from './filters'
@@ -14,6 +14,8 @@ import { toast } from 'sonner'
1414
import { useAdmin } from '@/hooks/use-admin'
1515
import { patchAdminInAdminsCache } from '@/utils/adminsCache'
1616
import { useQueryClient } from '@tanstack/react-query'
17+
import { BulkActionsBar } from '@/components/users/bulk-actions-bar'
18+
import { BulkActionAlertDialog } from '@/components/users/bulk-action-alert-dialog'
1719

1820
interface AdminFilters {
1921
sort?: string
@@ -187,11 +189,15 @@ export default function AdminsTable({ onEdit, onDelete, onToggleStatus, onResetU
187189
const [resetUsersUsageDialogOpen, setResetUsersUsageDialogOpen] = useState(false)
188190
const [bulkUsersStatusDialogOpen, setBulkUsersStatusDialogOpen] = useState(false)
189191
const [removeAllUsersDialogOpen, setRemoveAllUsersDialogOpen] = useState(false)
192+
const [selectedAdminUsernames, setSelectedAdminUsernames] = useState<string[]>([])
193+
const [resetSelectionKey, setResetSelectionKey] = useState(0)
194+
const [bulkAction, setBulkAction] = useState<'delete' | null>(null)
190195
const [adminToDelete, setAdminToDelete] = useState<AdminDetails | null>(null)
191196
const [adminToToggleStatus, setAdminToToggleStatus] = useState<AdminDetails | null>(null)
192197
const [adminToReset, setAdminToReset] = useState<string | null>(null)
193198
const [bulkUsersStatusAction, setBulkUsersStatusAction] = useState<{ username: string; actionType: BulkUsersActionType } | null>(null)
194199
const [adminToRemoveAllUsers, setAdminToRemoveAllUsers] = useState<string | null>(null)
200+
const bulkDeleteAdminsMutation = useBulkDeleteAdmins()
195201

196202
const {
197203
data: adminsResponse,
@@ -277,6 +283,11 @@ export default function AdminsTable({ onEdit, onDelete, onToggleStatus, onResetU
277283
setDeleteDialogOpen(true)
278284
}
279285

286+
const clearSelection = () => {
287+
setResetSelectionKey(prev => prev + 1)
288+
setSelectedAdminUsernames([])
289+
}
290+
280291
const handleStatusToggleClick = (admin: AdminDetails) => {
281292
setAdminToToggleStatus(admin)
282293
setStatusToggleDialogOpen(true)
@@ -329,7 +340,8 @@ export default function AdminsTable({ onEdit, onDelete, onToggleStatus, onResetU
329340
toast.success(t('success', { defaultValue: 'Success' }), {
330341
description: t(actionType === 'disable' ? 'admins.disableAllActiveUsersSuccess' : 'admins.activateAllDisabledUsersSuccess', {
331342
name: username,
332-
defaultValue: actionType === 'disable' ? `All active users under admin "${username}" have been disabled successfully` : `All disabled users under admin "${username}" have been activated successfully`,
343+
defaultValue:
344+
actionType === 'disable' ? `All active users under admin "${username}" have been disabled successfully` : `All disabled users under admin "${username}" have been activated successfully`,
333345
}),
334346
})
335347
closeBulkUsersStatusDialog()
@@ -431,6 +443,36 @@ export default function AdminsTable({ onEdit, onDelete, onToggleStatus, onResetU
431443
}
432444
}
433445

446+
const handleBulkDelete = async () => {
447+
if (!selectedAdminUsernames.length) return
448+
449+
try {
450+
const response = await bulkDeleteAdminsMutation.mutateAsync({
451+
data: {
452+
usernames: selectedAdminUsernames,
453+
},
454+
})
455+
toast.success(t('success', { defaultValue: 'Success' }), {
456+
description: t('admins.bulkDeleteSuccess', {
457+
count: response.count,
458+
defaultValue: '{{count}} admins deleted successfully.',
459+
}),
460+
})
461+
clearSelection()
462+
setBulkAction(null)
463+
queryClient.invalidateQueries({ queryKey: ['/api/admins'] })
464+
} catch (error: any) {
465+
toast.error(t('error', { defaultValue: 'Error' }), {
466+
description:
467+
error?.data?.detail ||
468+
error?.message ||
469+
t('admins.bulkDeleteFailed', {
470+
defaultValue: 'Failed to delete selected admins.',
471+
}),
472+
})
473+
}
474+
}
475+
434476
const columns = setupColumns({
435477
t,
436478
handleSort,
@@ -451,6 +493,7 @@ export default function AdminsTable({ onEdit, onDelete, onToggleStatus, onResetU
451493
return (
452494
<div>
453495
<Filters filters={filters} onFilterChange={handleFilterChange} handleSort={handleSort} refetch={handleManualRefresh} />
496+
<BulkActionsBar selectedCount={selectedAdminUsernames.length} onClear={clearSelection} onDelete={selectedAdminUsernames.length > 0 ? () => setBulkAction('delete') : undefined} />
454497
<DataTable
455498
columns={columns}
456499
data={adminsData || []}
@@ -461,6 +504,8 @@ export default function AdminsTable({ onEdit, onDelete, onToggleStatus, onResetU
461504
onDisableAllActiveUsers={handleDisableAllActiveUsersClick}
462505
onActivateAllDisabledUsers={handleActivateAllDisabledUsersClick}
463506
onRemoveAllUsers={handleRemoveAllUsersClick}
507+
onSelectionChange={setSelectedAdminUsernames}
508+
resetSelectionKey={resetSelectionKey}
464509
currentAdminUsername={currentAdmin?.username}
465510
setStatusToggleDialogOpen={setStatusToggleDialogOpen}
466511
isLoading={isCurrentlyLoading && isFirstLoadRef.current}
@@ -504,6 +549,19 @@ export default function AdminsTable({ onEdit, onDelete, onToggleStatus, onResetU
504549
onClose={() => setRemoveAllUsersDialogOpen(false)}
505550
/>
506551
)}
552+
<BulkActionAlertDialog
553+
open={bulkAction === 'delete'}
554+
onOpenChange={open => setBulkAction(open ? 'delete' : null)}
555+
title={t('admins.bulkDeleteTitle', { defaultValue: 'Delete Selected Admins' })}
556+
description={t('admins.bulkDeletePrompt', {
557+
count: selectedAdminUsernames.length,
558+
defaultValue: 'Are you sure you want to delete {{count}} selected admins? This action cannot be undone.',
559+
})}
560+
actionLabel={t('delete')}
561+
onConfirm={handleBulkDelete}
562+
isPending={bulkDeleteAdminsMutation.isPending}
563+
destructive
564+
/>
507565
</div>
508566
)
509567
}

dashboard/src/components/admins/columns.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { AdminDetails } from '@/service/api'
2-
import { ColumnDef } from '@tanstack/react-table'
2+
import { ColumnDef, Row, Table } from '@tanstack/react-table'
33
import { ChartPie, ChevronDown, MoreVertical, Pen, Power, PowerOff, RefreshCw, Trash2, User, UserCheck, UserMinus, UserX } from 'lucide-react'
44
import { Button } from '@/components/ui/button'
55
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
66
import { formatBytes } from '@/utils/formatByte.ts'
77
import { AdminStatusBadge } from './admin-status-badge'
8+
import { Checkbox } from '@/components/ui/checkbox'
89

910
interface ColumnSetupProps {
1011
t: (key: string) => string
@@ -36,7 +37,7 @@ const createSortButton = (
3637
}
3738

3839
return (
39-
<button onClick={handleClick} className="flex w-full items-center gap-1">
40+
<button type="button" onClick={handleClick} className="flex w-full items-center gap-1">
4041
<div className="text-xs">{t(label)}</div>
4142
{filters.sort && (filters.sort === column || filters.sort === '-' + column) && (
4243
<ChevronDown size={16} className={`transition-transform duration-300 ${filters.sort === column ? 'rotate-180' : ''} ${filters.sort === '-' + column ? 'rotate-0' : ''} `} />
@@ -58,6 +59,42 @@ export const setupColumns = ({
5859
onActivateAllDisabledUsers,
5960
onRemoveAllUsers,
6061
}: ColumnSetupProps): ColumnDef<AdminDetails>[] => [
62+
{
63+
id: 'select',
64+
header: ({ table }: { table: Table<AdminDetails> }) => (
65+
<div className="flex h-5 items-center justify-center">
66+
<Checkbox
67+
aria-label={t('selectAll')}
68+
className="h-3.5 w-3.5 rounded-[3px] border-muted-foreground/40 data-[state=checked]:border-primary"
69+
checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate')}
70+
onCheckedChange={value => table.toggleAllPageRowsSelected(!!value)}
71+
onClick={event => event.stopPropagation()}
72+
onPointerDown={event => event.stopPropagation()}
73+
onKeyDown={event => event.stopPropagation()}
74+
/>
75+
</div>
76+
),
77+
cell: ({ row }: { row: Row<AdminDetails> }) => (
78+
<div className="flex h-5 items-center justify-center">
79+
{row.getCanSelect() ? (
80+
<Checkbox
81+
aria-label={t('select')}
82+
className="h-3.5 w-3.5 rounded-[3px] border-muted-foreground/40 bg-background data-[state=checked]:border-primary data-[state=indeterminate]:border-primary data-[state=checked]:bg-primary data-[state=indeterminate]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:text-primary-foreground"
83+
checked={row.getIsSelected()}
84+
onCheckedChange={value => row.toggleSelected(!!value)}
85+
onClick={event => event.stopPropagation()}
86+
onPointerDown={event => event.stopPropagation()}
87+
onKeyDown={event => event.stopPropagation()}
88+
/>
89+
) : (
90+
<div className="h-3.5 w-3.5" />
91+
)}
92+
</div>
93+
),
94+
enableSorting: false,
95+
enableHiding: false,
96+
size: 40,
97+
},
6198
{
6299
accessorKey: 'username',
63100
header: () => createSortButton('username', 'username', t, handleSort, filters),

0 commit comments

Comments
 (0)