From 07e5e8498e4ccfce679713cc93555b5504843038 Mon Sep 17 00:00:00 2001 From: Nikan Zeyaei <72458440+NikanZeyaei@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:39:58 +0330 Subject: [PATCH] feat(ui): add select all / clear all shortcuts for inbound multi-select (#5175) * feat(ui): add select all / clear all shortcuts for inbound multi-select Adds 'Select all' and 'Clear all' buttons above the inbound multi-select in: - ClientFormModal (add/edit client) - BulkAttachInboundsModal (bulk attach clients to inbounds) - BulkDetachInboundsModal (bulk detach clients from inbounds) - ClientBulkAddModal (add bulk clients) Extracts the repeated button logic into a reusable SelectAllClearButtons component. Includes i18n keys for all 13 supported languages with proper translations. Closes #5144 * refactor(form): decouple SelectAllClearButtons labels and harden select-all Accept optional selectAllLabel/clearLabel props so the generic form component is not tied to the client-inbound i18n keys (defaults unchanged). Compute the all-selected state by checking every option is present and union the current value on select-all, so it stays correct if value holds ids outside options. --------- Co-authored-by: Sanaei --- .../components/form/SelectAllClearButtons.tsx | 51 +++++++++++++++++++ frontend/src/components/form/index.ts | 1 + .../pages/clients/BulkAttachInboundsModal.tsx | 28 ++++++---- .../pages/clients/BulkDetachInboundsModal.tsx | 28 ++++++---- .../src/pages/clients/ClientBulkAddModal.tsx | 7 ++- .../src/pages/clients/ClientFormModal.tsx | 8 ++- internal/web/translation/ar-EG.json | 2 + internal/web/translation/en-US.json | 2 + internal/web/translation/es-ES.json | 2 + internal/web/translation/fa-IR.json | 2 + internal/web/translation/id-ID.json | 2 + internal/web/translation/ja-JP.json | 2 + internal/web/translation/pt-BR.json | 2 + internal/web/translation/ru-RU.json | 2 + internal/web/translation/tr-TR.json | 3 +- internal/web/translation/uk-UA.json | 2 + internal/web/translation/vi-VN.json | 2 + internal/web/translation/zh-CN.json | 2 + internal/web/translation/zh-TW.json | 2 + 19 files changed, 126 insertions(+), 24 deletions(-) create mode 100644 frontend/src/components/form/SelectAllClearButtons.tsx diff --git a/frontend/src/components/form/SelectAllClearButtons.tsx b/frontend/src/components/form/SelectAllClearButtons.tsx new file mode 100644 index 000000000..2c3c71c04 --- /dev/null +++ b/frontend/src/components/form/SelectAllClearButtons.tsx @@ -0,0 +1,51 @@ +import { useTranslation } from 'react-i18next'; +import { Button } from 'antd'; + +interface Option { + value: number; +} + +interface SelectAllClearButtonsProps { + options: Option[]; + value: number[]; + onChange: (value: number[]) => void; + /** Override the default "Select all" label (defaults to the inbound copy). */ + selectAllLabel?: string; + /** Override the default "Clear all" label (defaults to the inbound copy). */ + clearLabel?: string; +} + +export default function SelectAllClearButtons({ + options, + value, + onChange, + selectAllLabel, + clearLabel, +}: SelectAllClearButtonsProps) { + const { t } = useTranslation(); + + const optionValues = options.map((o) => o.value); + // Treat as "all selected" when every option is chosen, rather than comparing + // lengths — this stays correct even if `value` holds ids outside `options`. + const allSelected = options.length > 0 && optionValues.every((v) => value.includes(v)); + + return ( +
+ + +
+ ); +} diff --git a/frontend/src/components/form/index.ts b/frontend/src/components/form/index.ts index 9f3e37137..0a5785541 100644 --- a/frontend/src/components/form/index.ts +++ b/frontend/src/components/form/index.ts @@ -1,3 +1,4 @@ export { default as DateTimePicker } from './DateTimePicker'; export { default as JsonEditor } from './JsonEditor'; export { default as HeaderMapEditor } from './HeaderMapEditor'; +export { default as SelectAllClearButtons } from './SelectAllClearButtons'; diff --git a/frontend/src/pages/clients/BulkAttachInboundsModal.tsx b/frontend/src/pages/clients/BulkAttachInboundsModal.tsx index 800e15747..1fd397a3e 100644 --- a/frontend/src/pages/clients/BulkAttachInboundsModal.tsx +++ b/frontend/src/pages/clients/BulkAttachInboundsModal.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Alert, Modal, Select, Typography, message } from 'antd'; +import { SelectAllClearButtons } from '@/components/form'; import type { InboundOption } from '@/hooks/useClients'; import { formatInboundLabel } from '@/lib/inbounds/label'; import type { BulkAttachResult } from '@/schemas/client'; @@ -82,16 +83,23 @@ export default function BulkAttachInboundsModal({ {targetOptions.length === 0 ? ( ) : ( - + )} diff --git a/frontend/src/pages/clients/BulkDetachInboundsModal.tsx b/frontend/src/pages/clients/BulkDetachInboundsModal.tsx index 2c120d47d..d3851f573 100644 --- a/frontend/src/pages/clients/BulkDetachInboundsModal.tsx +++ b/frontend/src/pages/clients/BulkDetachInboundsModal.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Alert, Modal, Select, Typography, message } from 'antd'; +import { SelectAllClearButtons } from '@/components/form'; import type { InboundOption } from '@/hooks/useClients'; import { formatInboundLabel } from '@/lib/inbounds/label'; import type { BulkDetachResult } from '@/schemas/client'; @@ -82,16 +83,23 @@ export default function BulkDetachInboundsModal({ {targetOptions.length === 0 ? ( ) : ( - + )} diff --git a/frontend/src/pages/clients/ClientBulkAddModal.tsx b/frontend/src/pages/clients/ClientBulkAddModal.tsx index 20dedc792..8d25d850c 100644 --- a/frontend/src/pages/clients/ClientBulkAddModal.tsx +++ b/frontend/src/pages/clients/ClientBulkAddModal.tsx @@ -8,7 +8,7 @@ import type { Dayjs } from 'dayjs'; import { RandomUtil, SizeFormatter } from '@/utils'; import { formatInboundLabel } from '@/lib/inbounds/label'; import { TLS_FLOW_CONTROL } from '@/schemas/primitives'; -import { DateTimePicker } from '@/components/form'; +import { DateTimePicker, SelectAllClearButtons } from '@/components/form'; import { useClients, type InboundOption } from '@/hooks/useClients'; import { ClientBulkAddFormSchema, type ClientBulkAddFormValues } from '@/schemas/client'; @@ -213,6 +213,11 @@ export default function ClientBulkAddModal({ >
+ update('inboundIds', v)} + />