From 521bf211a20f0b23e83968d31e200bfe2450470d Mon Sep 17 00:00:00 2001 From: Hugo Haldi <20846785+HaldiH@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:43:44 +0200 Subject: [PATCH] feat: add user activation control - Add is_active field to user model - Create SealCheckbox component with clickable container - Add activation control to user modal - Display user status in users table - Add localization strings for activation features --- src/components/seal-form/seal-checkbox.tsx | 74 ++++++++++++++++++++++ src/locales/en-US/users.ts | 5 ++ src/locales/ja-JP/users.ts | 6 ++ src/locales/ru-RU/users.ts | 6 ++ src/locales/zh-CN/users.ts | 5 ++ src/pages/users/components/add-modal.tsx | 63 ++++++++++++------ src/pages/users/config/types.ts | 2 + src/pages/users/index.tsx | 17 +++++ 8 files changed, 158 insertions(+), 20 deletions(-) create mode 100644 src/components/seal-form/seal-checkbox.tsx diff --git a/src/components/seal-form/seal-checkbox.tsx b/src/components/seal-form/seal-checkbox.tsx new file mode 100644 index 00000000..b00ab0d9 --- /dev/null +++ b/src/components/seal-form/seal-checkbox.tsx @@ -0,0 +1,74 @@ +import { Checkbox, Form, type CheckboxProps } from 'antd'; +import React from 'react'; +import styled from 'styled-components'; +import LabelInfo from './components/label-info'; +import { SealFormItemProps } from './types'; +import Wrapper from './wrapper'; + +const Inner = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding-inline: 14px; + cursor: pointer; + .ant-checkbox-wrapper { + margin-top: 0 !important; + } +`; + +interface SealCheckboxProps extends CheckboxProps, SealFormItemProps {} + +const SealCheckbox: React.FC = (props) => { + const { + label, + checked, + required, + description, + isInFormItems = true, + defaultChecked, + checkStatus, + ...rest + } = props; + + let status = ''; + if (isInFormItems) { + const statusData = Form?.Item?.useStatus?.(); + status = statusData?.status || ''; + } + + const handleChange = (e: any) => { + props.onChange?.(e); + }; + + const handleContainerClick = () => { + // Create a synthetic event to toggle the checkbox + const syntheticEvent = { + target: { + checked: !checked + } + }; + handleChange(syntheticEvent); + }; + + return ( + + + + + + + ); +}; + +export default SealCheckbox; diff --git a/src/locales/en-US/users.ts b/src/locales/en-US/users.ts index 6e94c401..b750cc02 100644 --- a/src/locales/en-US/users.ts +++ b/src/locales/en-US/users.ts @@ -5,6 +5,11 @@ export default { 'users.form.create': 'New User', 'users.table.username': 'User Name', 'users.table.role': 'Role', + 'users.table.status': 'Status', + 'users.status.active': 'Active', + 'users.status.inactive': 'Inactive', + 'users.form.active': 'Active account', + 'users.form.active.description': 'Enable or disable this user account', 'users.form.fullname': 'Full Name', 'users.form.source': 'Source', 'users.table.user': 'users', diff --git a/src/locales/ja-JP/users.ts b/src/locales/ja-JP/users.ts index 0a3fc691..2f5e1e40 100644 --- a/src/locales/ja-JP/users.ts +++ b/src/locales/ja-JP/users.ts @@ -5,6 +5,12 @@ export default { 'users.form.create': 'ユーザーを追加', 'users.table.username': 'ユーザー名', 'users.table.role': '役割', + 'users.table.status': 'ステータス', + 'users.status.active': 'アクティブ', + 'users.status.inactive': '非アクティブ', + 'users.form.active': 'アクティブアカウント', + 'users.form.active.description': + 'このユーザーアカウントを有効または無効にする', 'users.form.fullname': 'フルネーム', 'users.form.source': 'ソース', 'users.table.user': 'ユーザー', diff --git a/src/locales/ru-RU/users.ts b/src/locales/ru-RU/users.ts index e580fc62..1cbef673 100644 --- a/src/locales/ru-RU/users.ts +++ b/src/locales/ru-RU/users.ts @@ -5,6 +5,12 @@ export default { 'users.form.create': 'Новый пользователь', 'users.table.username': 'Имя пользователя', 'users.table.role': 'Роль', + 'users.table.status': 'Статус', + 'users.status.active': 'Активен', + 'users.status.inactive': 'Неактивен', + 'users.form.active': 'Активная учетная запись', + 'users.form.active.description': + 'Включить или отключить эту учетную запись пользователя', 'users.form.fullname': 'Полное имя', 'users.form.source': 'Источник', 'users.table.user': 'пользователи', diff --git a/src/locales/zh-CN/users.ts b/src/locales/zh-CN/users.ts index be0ac20b..072bc787 100644 --- a/src/locales/zh-CN/users.ts +++ b/src/locales/zh-CN/users.ts @@ -5,6 +5,11 @@ export default { 'users.form.create': '新建用户', 'users.table.username': '用户名', 'users.table.role': '角色', + 'users.table.status': '状态', + 'users.status.active': '激活', + 'users.status.inactive': '禁用', + 'users.form.active': '激活账户', + 'users.form.active.description': '启用或禁用此用户账户', 'users.form.fullname': '全名', 'users.form.source': '来源', 'users.table.user': '用户', diff --git a/src/pages/users/components/add-modal.tsx b/src/pages/users/components/add-modal.tsx index 279301f1..fc1df8ce 100644 --- a/src/pages/users/components/add-modal.tsx +++ b/src/pages/users/components/add-modal.tsx @@ -1,5 +1,6 @@ import ModalFooter from '@/components/modal-footer'; import ScrollerModal from '@/components/scroller-modal'; +import SealCheckbox from '@/components/seal-form/seal-checkbox'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { PageAction, PasswordReg } from '@/config'; @@ -34,11 +35,13 @@ const AddModal: React.FC = ({ if (action === PageAction.EDIT && open) { form.setFieldsValue({ ...data, - is_admin: data?.is_admin ? UserRoles.ADMIN : UserRoles.USER + is_admin: data?.is_admin ? UserRoles.ADMIN : UserRoles.USER, + is_active: data?.is_active !== undefined ? data.is_active : true }); } else if (action === PageAction.CREATE && open) { form.setFieldsValue({ - is_admin: UserRoles.USER + is_admin: UserRoles.USER, + is_active: true }); } }; @@ -93,24 +96,44 @@ const AddModal: React.FC = ({ label={intl.formatMessage({ id: 'users.form.fullname' })} > - name="is_admin" rules={[{ required: false }]}> - - {UserRolesOptions.map((item) => { - return ( - - {item.value === UserRoles.ADMIN ? ( - - ) : ( - - )} - - {intl.formatMessage({ id: item.label })} - - - ); - })} - - +
+
+ name="is_admin" rules={[{ required: false }]}> + + {UserRolesOptions.map((item) => { + return ( + + {item.value === UserRoles.ADMIN ? ( + + ) : ( + + )} + + {intl.formatMessage({ id: item.label })} + + + ); + })} + + +
+
+ + name="is_active" + rules={[{ required: false }]} + valuePropName="checked" + > + + +
+
name="password" diff --git a/src/pages/users/config/types.ts b/src/pages/users/config/types.ts index 136a55e1..14d362ef 100644 --- a/src/pages/users/config/types.ts +++ b/src/pages/users/config/types.ts @@ -8,6 +8,7 @@ export interface ListItem { username: string; created_at: string; updated_at: string; + is_active?: boolean; } export interface FormData { @@ -16,4 +17,5 @@ export interface FormData { is_admin: boolean | string; full_name: string; password: string; + is_active?: boolean; } diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx index 1e6d9c04..6a541824 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -295,6 +295,23 @@ const Users: React.FC = () => { ); }} /> + { + return ( + + {record.is_active + ? intl.formatMessage({ id: 'users.status.active' }) + : intl.formatMessage({ id: 'users.status.inactive' })} + + ); + }} + />