mirror of https://gitee.com/answerdev/answer.git
fix: change admin questions and answers operate
This commit is contained in:
parent
a4adf7f40d
commit
0269c30516
|
@ -1097,12 +1097,15 @@ ui:
|
||||||
Are you sure you wish to delete?
|
Are you sure you wish to delete?
|
||||||
other: Are you sure you wish to delete?
|
other: Are you sure you wish to delete?
|
||||||
tip_answer_deleted: This answer has been deleted
|
tip_answer_deleted: This answer has been deleted
|
||||||
|
undelete_title: Undelete this post
|
||||||
|
undelete_desc: Are you sure you wish to undelete?
|
||||||
btns:
|
btns:
|
||||||
confirm: Confirm
|
confirm: Confirm
|
||||||
cancel: Cancel
|
cancel: Cancel
|
||||||
edit: Edit
|
edit: Edit
|
||||||
save: Save
|
save: Save
|
||||||
delete: Delete
|
delete: Delete
|
||||||
|
undelete: Undelete
|
||||||
login: Log in
|
login: Log in
|
||||||
signup: Sign up
|
signup: Sign up
|
||||||
logout: Log out
|
logout: Log out
|
||||||
|
@ -1123,6 +1126,8 @@ ui:
|
||||||
active: Active
|
active: Active
|
||||||
suspend: Suspend
|
suspend: Suspend
|
||||||
unsuspend: Unsuspend
|
unsuspend: Unsuspend
|
||||||
|
close: Close
|
||||||
|
reopen: Reopen
|
||||||
search:
|
search:
|
||||||
title: Search Results
|
title: Search Results
|
||||||
keywords: Keywords
|
keywords: Keywords
|
||||||
|
@ -1411,34 +1416,6 @@ ui:
|
||||||
created: Created
|
created: Created
|
||||||
action: Action
|
action: Action
|
||||||
review: Review
|
review: Review
|
||||||
change_modal:
|
|
||||||
title: Change user status to...
|
|
||||||
btn_cancel: Cancel
|
|
||||||
btn_submit: Submit
|
|
||||||
normal_name: normal
|
|
||||||
normal_desc: A normal user can ask and answer questions.
|
|
||||||
suspended_name: suspended
|
|
||||||
suspended_desc: A suspended user can't log in.
|
|
||||||
deleted_name: deleted
|
|
||||||
deleted_desc: "Delete profile, authentication associations."
|
|
||||||
inactive_name: inactive
|
|
||||||
inactive_desc: An inactive user must re-validate their email.
|
|
||||||
confirm_title: Delete this user
|
|
||||||
confirm_content: Are you sure you want to delete this user? This is permanent!
|
|
||||||
confirm_btn: Delete
|
|
||||||
msg:
|
|
||||||
empty: Please select a reason.
|
|
||||||
status_modal:
|
|
||||||
title: "Change {{ type }} status to..."
|
|
||||||
normal_name: normal
|
|
||||||
normal_desc: A normal post available to everyone.
|
|
||||||
closed_name: closed
|
|
||||||
closed_desc: "A closed question can't answer, but still can edit, vote and comment."
|
|
||||||
deleted_name: deleted
|
|
||||||
deleted_desc: This post will be deleted.
|
|
||||||
btn_cancel: Cancel
|
|
||||||
btn_submit: Submit
|
|
||||||
btn_next: Next
|
|
||||||
user_role_modal:
|
user_role_modal:
|
||||||
title: Change user role to...
|
title: Change user role to...
|
||||||
btn_cancel: Cancel
|
btn_cancel: Cancel
|
||||||
|
|
|
@ -2,8 +2,6 @@ import useTagModal from './useTagModal';
|
||||||
import useToast from './useToast';
|
import useToast from './useToast';
|
||||||
import useReportModal from './useReportModal';
|
import useReportModal from './useReportModal';
|
||||||
import usePageUsers from './usePageUsers';
|
import usePageUsers from './usePageUsers';
|
||||||
import useChangeModal from './useChangeModal';
|
|
||||||
import useEditStatusModal from './useEditStatusModal';
|
|
||||||
import useChangeUserRoleModal from './useChangeUserRoleModal';
|
import useChangeUserRoleModal from './useChangeUserRoleModal';
|
||||||
import useUserModal from './useUserModal';
|
import useUserModal from './useUserModal';
|
||||||
import useChangePasswordModal from './useChangePasswordModal';
|
import useChangePasswordModal from './useChangePasswordModal';
|
||||||
|
@ -18,8 +16,6 @@ export {
|
||||||
useToast,
|
useToast,
|
||||||
useReportModal,
|
useReportModal,
|
||||||
usePageUsers,
|
usePageUsers,
|
||||||
useChangeModal,
|
|
||||||
useEditStatusModal,
|
|
||||||
useChangeUserRoleModal,
|
useChangeUserRoleModal,
|
||||||
useUserModal,
|
useUserModal,
|
||||||
useChangePasswordModal,
|
useChangePasswordModal,
|
||||||
|
|
|
@ -1,215 +0,0 @@
|
||||||
import { useLayoutEffect, useState } from 'react';
|
|
||||||
import { Modal, Form, Button, FormCheck } from 'react-bootstrap';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
|
|
||||||
import { Modal as AnswerModal } from '@/components';
|
|
||||||
import { changeUserStatus } from '@/services';
|
|
||||||
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const root = ReactDOM.createRoot(div);
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
callback?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useChangeModal = ({ callback }: Props) => {
|
|
||||||
const { t } = useTranslation('translation', {
|
|
||||||
keyPrefix: 'admin.change_modal',
|
|
||||||
});
|
|
||||||
const [id, setId] = useState('');
|
|
||||||
const [defaultType, setDefaultType] = useState('');
|
|
||||||
const [isInvalid, setInvalidState] = useState(false);
|
|
||||||
const [changeType, setChangeType] = useState({
|
|
||||||
type: '',
|
|
||||||
haveContent: false,
|
|
||||||
});
|
|
||||||
const [content, setContent] = useState({
|
|
||||||
value: '',
|
|
||||||
isInvalid: false,
|
|
||||||
errorMsg: '',
|
|
||||||
});
|
|
||||||
const [show, setShow] = useState(false);
|
|
||||||
const [list] = useState<any[]>([
|
|
||||||
{
|
|
||||||
type: 'normal',
|
|
||||||
name: t('normal_name'),
|
|
||||||
description: t('normal_desc'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'suspended',
|
|
||||||
name: t('suspended_name'),
|
|
||||||
description: t('suspended_desc'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'deleted',
|
|
||||||
name: t('deleted_name'),
|
|
||||||
description: t('deleted_desc'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'inactive',
|
|
||||||
name: t('inactive_name'),
|
|
||||||
description: t('inactive_desc'),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleRadio = (val) => {
|
|
||||||
setInvalidState(false);
|
|
||||||
setContent({
|
|
||||||
value: '',
|
|
||||||
isInvalid: false,
|
|
||||||
errorMsg: '',
|
|
||||||
});
|
|
||||||
setChangeType({
|
|
||||||
type: val.type,
|
|
||||||
haveContent: val.have_content,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClose = () => {
|
|
||||||
setChangeType({
|
|
||||||
type: '',
|
|
||||||
haveContent: false,
|
|
||||||
});
|
|
||||||
setContent({
|
|
||||||
value: '',
|
|
||||||
isInvalid: false,
|
|
||||||
errorMsg: '',
|
|
||||||
});
|
|
||||||
setContent({
|
|
||||||
value: '',
|
|
||||||
isInvalid: false,
|
|
||||||
errorMsg: '',
|
|
||||||
});
|
|
||||||
setShow(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
if (changeType.type === '') {
|
|
||||||
setInvalidState(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (changeType.haveContent && !content.value) {
|
|
||||||
setContent({
|
|
||||||
value: content.value,
|
|
||||||
isInvalid: true,
|
|
||||||
errorMsg: t('remark.empty'),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (defaultType === changeType.type) {
|
|
||||||
onClose();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (changeType.type === 'deleted') {
|
|
||||||
onClose();
|
|
||||||
|
|
||||||
AnswerModal.confirm({
|
|
||||||
title: t('confirm_title'),
|
|
||||||
content: t('confirm_content'),
|
|
||||||
confirmText: t('confirm_btn'),
|
|
||||||
confirmBtnVariant: 'danger',
|
|
||||||
onConfirm: () => {
|
|
||||||
changeUserStatus({
|
|
||||||
user_id: id,
|
|
||||||
status: changeType.type,
|
|
||||||
}).then(() => {
|
|
||||||
callback?.();
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
changeUserStatus({
|
|
||||||
user_id: id,
|
|
||||||
status: changeType.type,
|
|
||||||
}).then(() => {
|
|
||||||
callback?.();
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onShow = (params) => {
|
|
||||||
setId(params.id);
|
|
||||||
setChangeType({
|
|
||||||
...changeType,
|
|
||||||
type: params.type,
|
|
||||||
});
|
|
||||||
setDefaultType(params.type);
|
|
||||||
setShow(true);
|
|
||||||
};
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
root.render(
|
|
||||||
<Modal show={show} onHide={onClose}>
|
|
||||||
<Modal.Header closeButton>
|
|
||||||
<Modal.Title as="h5">{t('title')}</Modal.Title>
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Body>
|
|
||||||
<Form>
|
|
||||||
{list.map((item) => {
|
|
||||||
if (
|
|
||||||
defaultType === 'inactive' &&
|
|
||||||
(item.type === 'suspended' || item.type === 'deleted')
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultType === 'suspended' && item.type === 'inactive') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div key={item?.type}>
|
|
||||||
<Form.Group
|
|
||||||
controlId={item.type}
|
|
||||||
className={`${
|
|
||||||
item.have_content && changeType === item.type
|
|
||||||
? 'mb-2'
|
|
||||||
: 'mb-3'
|
|
||||||
}`}>
|
|
||||||
<FormCheck>
|
|
||||||
<FormCheck.Input
|
|
||||||
id={item.type}
|
|
||||||
type="radio"
|
|
||||||
checked={changeType.type === item.type}
|
|
||||||
onChange={() => handleRadio(item)}
|
|
||||||
isInvalid={isInvalid}
|
|
||||||
/>
|
|
||||||
<FormCheck.Label htmlFor={item.type}>
|
|
||||||
<span className="fw-bold">{item?.name}</span>
|
|
||||||
<br />
|
|
||||||
<span className="text-secondary">
|
|
||||||
{item?.description}
|
|
||||||
</span>
|
|
||||||
</FormCheck.Label>
|
|
||||||
<Form.Control.Feedback type="invalid">
|
|
||||||
{t('msg.empty')}
|
|
||||||
</Form.Control.Feedback>
|
|
||||||
</FormCheck>
|
|
||||||
</Form.Group>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Form>
|
|
||||||
</Modal.Body>
|
|
||||||
<Modal.Footer>
|
|
||||||
<Button variant="link" onClick={() => onClose()}>
|
|
||||||
{t('btn_cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button variant="primary" onClick={handleSubmit}>
|
|
||||||
{t('btn_submit')}
|
|
||||||
</Button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
onClose,
|
|
||||||
onShow,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useChangeModal;
|
|
|
@ -1,134 +0,0 @@
|
||||||
import { useLayoutEffect, useState } from 'react';
|
|
||||||
import { Modal, Form, Button, FormCheck } from 'react-bootstrap';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const root = ReactDOM.createRoot(div);
|
|
||||||
|
|
||||||
const useEditStatusModal = ({
|
|
||||||
editType = '',
|
|
||||||
callback,
|
|
||||||
}: {
|
|
||||||
editType: string;
|
|
||||||
callback: (id, type) => void;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation('translation', {
|
|
||||||
keyPrefix: 'admin.status_modal',
|
|
||||||
});
|
|
||||||
const [id, setId] = useState('');
|
|
||||||
const [defaultType, setDefaultType] = useState('');
|
|
||||||
const [isInvalid, setInvalidState] = useState(false);
|
|
||||||
const [changeType, setChangeType] = useState('normal');
|
|
||||||
|
|
||||||
const [show, setShow] = useState(false);
|
|
||||||
const [list] = useState<any[]>([
|
|
||||||
{
|
|
||||||
type: 'normal',
|
|
||||||
name: t('normal_name'),
|
|
||||||
description: t('normal_desc'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'closed',
|
|
||||||
name: t('closed_name'),
|
|
||||||
description: t('closed_desc'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'deleted',
|
|
||||||
name: t('deleted_name'),
|
|
||||||
description: t('deleted_desc'),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleRadio = (val) => {
|
|
||||||
setInvalidState(false);
|
|
||||||
setChangeType(val.type);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClose = () => {
|
|
||||||
setChangeType('');
|
|
||||||
setShow(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
if (changeType === '') {
|
|
||||||
setInvalidState(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultType === changeType) {
|
|
||||||
onClose();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose();
|
|
||||||
callback?.(id, changeType);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onShow = (params) => {
|
|
||||||
setId(params.id);
|
|
||||||
setChangeType(params.type);
|
|
||||||
setDefaultType(params.type);
|
|
||||||
setShow(true);
|
|
||||||
};
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
root.render(
|
|
||||||
<Modal show={show} onHide={onClose}>
|
|
||||||
<Modal.Header closeButton>
|
|
||||||
<Modal.Title as="h5">{t('title', { type: editType })}</Modal.Title>
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Body>
|
|
||||||
<Form>
|
|
||||||
{list.map((item) => {
|
|
||||||
if (editType === 'answer' && item.type === 'closed') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div key={item?.type}>
|
|
||||||
<Form.Group controlId={item.type} className="mb-3">
|
|
||||||
<FormCheck>
|
|
||||||
<FormCheck.Input
|
|
||||||
id={item.type}
|
|
||||||
type="radio"
|
|
||||||
checked={changeType === item.type}
|
|
||||||
onChange={() => handleRadio(item)}
|
|
||||||
isInvalid={isInvalid}
|
|
||||||
/>
|
|
||||||
<FormCheck.Label htmlFor={item.type}>
|
|
||||||
<span className="fw-bold">{item.name}</span>
|
|
||||||
<br />
|
|
||||||
<span className="small text-secondary">
|
|
||||||
{item.description}
|
|
||||||
</span>
|
|
||||||
</FormCheck.Label>
|
|
||||||
<Form.Control.Feedback type="invalid">
|
|
||||||
{t('msg.empty')}
|
|
||||||
</Form.Control.Feedback>
|
|
||||||
</FormCheck>
|
|
||||||
</Form.Group>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Form>
|
|
||||||
</Modal.Body>
|
|
||||||
<Modal.Footer>
|
|
||||||
<Button variant="link" onClick={() => onClose()}>
|
|
||||||
{t('btn_cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button variant="primary" onClick={handleSubmit}>
|
|
||||||
{changeType !== 'normal' ? t('btn_next') : t('btn_submit')}
|
|
||||||
</Button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
onClose,
|
|
||||||
onShow,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useEditStatusModal;
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { Dropdown } from 'react-bootstrap';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { Icon, Modal } from '@/components';
|
||||||
|
import { changeAnswerStatus } from '@/services';
|
||||||
|
|
||||||
|
const AnswerActions = ({ itemData, curFilter, refreshList }) => {
|
||||||
|
const { t } = useTranslation('translation', { keyPrefix: 'delete' });
|
||||||
|
|
||||||
|
const handleAction = (type) => {
|
||||||
|
console.log(type);
|
||||||
|
if (type === 'delete') {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('title'),
|
||||||
|
content: itemData.accepted === 2 ? t('answer_accepted') : t('other'),
|
||||||
|
cancelBtnVariant: 'link',
|
||||||
|
confirmBtnVariant: 'danger',
|
||||||
|
confirmText: t('delete', { keyPrefix: 'btns' }),
|
||||||
|
onConfirm: () => {
|
||||||
|
changeAnswerStatus(itemData.id, 'deleted').then(() => {
|
||||||
|
refreshList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'undelete') {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('undelete_title'),
|
||||||
|
content: t('undelete_desc'),
|
||||||
|
cancelBtnVariant: 'link',
|
||||||
|
confirmBtnVariant: 'danger',
|
||||||
|
confirmText: t('undelete', { keyPrefix: 'btns' }),
|
||||||
|
onConfirm: () => {
|
||||||
|
changeAnswerStatus(itemData.id, 'available').then(() => {
|
||||||
|
refreshList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown>
|
||||||
|
<Dropdown.Toggle variant="link" className="no-toggle p-0">
|
||||||
|
<Icon
|
||||||
|
name="three-dots-vertical"
|
||||||
|
title={t('action', { keyPrefix: 'admin.answers' })}
|
||||||
|
/>
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
<Dropdown.Menu>
|
||||||
|
{curFilter === 'deleted' ? (
|
||||||
|
<Dropdown.Item onClick={() => handleAction('undelete')}>
|
||||||
|
{t('undelete', { keyPrefix: 'btns' })}
|
||||||
|
</Dropdown.Item>
|
||||||
|
) : (
|
||||||
|
<Dropdown.Item onClick={() => handleAction('delete')}>
|
||||||
|
{t('delete', { keyPrefix: 'btns' })}
|
||||||
|
</Dropdown.Item>
|
||||||
|
)}
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnswerActions;
|
|
@ -1,5 +1,5 @@
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Button, Form, Table, Stack } from 'react-bootstrap';
|
import { Form, Table, Stack } from 'react-bootstrap';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -9,18 +9,18 @@ import {
|
||||||
FormatTime,
|
FormatTime,
|
||||||
Icon,
|
Icon,
|
||||||
Pagination,
|
Pagination,
|
||||||
Modal,
|
|
||||||
BaseUserCard,
|
BaseUserCard,
|
||||||
Empty,
|
Empty,
|
||||||
QueryGroup,
|
QueryGroup,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { ADMIN_LIST_STATUS } from '@/common/constants';
|
import { ADMIN_LIST_STATUS } from '@/common/constants';
|
||||||
import { useEditStatusModal } from '@/hooks';
|
|
||||||
import * as Type from '@/common/interface';
|
import * as Type from '@/common/interface';
|
||||||
import { useAnswerSearch, changeAnswerStatus } from '@/services';
|
import { useAnswerSearch } from '@/services';
|
||||||
import { escapeRemove } from '@/utils';
|
import { escapeRemove } from '@/utils';
|
||||||
import { pathFactory } from '@/router/pathFactory';
|
import { pathFactory } from '@/router/pathFactory';
|
||||||
|
|
||||||
|
import AnswerAction from './components/Action';
|
||||||
|
|
||||||
const answerFilterItems: Type.AdminContentsFilterBy[] = ['normal', 'deleted'];
|
const answerFilterItems: Type.AdminContentsFilterBy[] = ['normal', 'deleted'];
|
||||||
|
|
||||||
const Answers: FC = () => {
|
const Answers: FC = () => {
|
||||||
|
@ -45,44 +45,6 @@ const Answers: FC = () => {
|
||||||
});
|
});
|
||||||
const count = listData?.count || 0;
|
const count = listData?.count || 0;
|
||||||
|
|
||||||
const handleCallback = (id, type) => {
|
|
||||||
if (type === 'normal') {
|
|
||||||
changeAnswerStatus(id, 'available').then(() => {
|
|
||||||
refreshList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === 'deleted') {
|
|
||||||
const item = listData?.list?.filter((v) => v.id === id)?.[0];
|
|
||||||
Modal.confirm({
|
|
||||||
title: t('title', { keyPrefix: 'delete' }),
|
|
||||||
content:
|
|
||||||
item.accepted === 2
|
|
||||||
? t('answer_accepted', { keyPrefix: 'delete' })
|
|
||||||
: t('other', { keyPrefix: 'delete' }),
|
|
||||||
cancelBtnVariant: 'link',
|
|
||||||
confirmBtnVariant: 'danger',
|
|
||||||
confirmText: t('delete', { keyPrefix: 'btns' }),
|
|
||||||
onConfirm: () => {
|
|
||||||
changeAnswerStatus(id, 'deleted').then(() => {
|
|
||||||
refreshList();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeModal = useEditStatusModal({
|
|
||||||
editType: 'answer',
|
|
||||||
callback: handleCallback,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleChange = (itemId) => {
|
|
||||||
changeModal.onShow({
|
|
||||||
id: itemId,
|
|
||||||
type: curFilter,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFilter = (e) => {
|
const handleFilter = (e) => {
|
||||||
urlSearchParams.set('query', e.target.value);
|
urlSearchParams.set('query', e.target.value);
|
||||||
urlSearchParams.delete('page');
|
urlSearchParams.delete('page');
|
||||||
|
@ -115,9 +77,9 @@ const Answers: FC = () => {
|
||||||
<th style={{ width: '11%' }}>{t('votes')}</th>
|
<th style={{ width: '11%' }}>{t('votes')}</th>
|
||||||
<th style={{ width: '14%' }}>{t('created')}</th>
|
<th style={{ width: '14%' }}>{t('created')}</th>
|
||||||
<th style={{ width: '11%' }}>{t('status')}</th>
|
<th style={{ width: '11%' }}>{t('status')}</th>
|
||||||
{curFilter !== 'deleted' && (
|
<th style={{ width: '11%' }} className="text-end">
|
||||||
<th style={{ width: '11%' }}>{t('action')}</th>
|
{t('action')}
|
||||||
)}
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="align-middle">
|
<tbody className="align-middle">
|
||||||
|
@ -172,16 +134,13 @@ const Answers: FC = () => {
|
||||||
{t(ADMIN_LIST_STATUS[curFilter]?.name)}
|
{t(ADMIN_LIST_STATUS[curFilter]?.name)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
{curFilter !== 'deleted' && (
|
<td className="text-end">
|
||||||
<td>
|
<AnswerAction
|
||||||
<Button
|
itemData={{ id: li.id, accepted: li.accepted }}
|
||||||
variant="link"
|
curFilter={curFilter}
|
||||||
className="p-0 btn-no-border"
|
refreshList={refreshList}
|
||||||
onClick={() => handleChange(li.id)}>
|
/>
|
||||||
{t('change')}
|
|
||||||
</Button>
|
|
||||||
</td>
|
</td>
|
||||||
)}
|
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { Dropdown } from 'react-bootstrap';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { Icon, Modal } from '@/components';
|
||||||
|
import { changeQuestionStatus, reopenQuestion } from '@/services';
|
||||||
|
import { useReportModal, useToast } from '@/hooks';
|
||||||
|
|
||||||
|
const AnswerActions = ({ itemData, refreshList, curFilter }) => {
|
||||||
|
const { t } = useTranslation('translation', { keyPrefix: 'delete' });
|
||||||
|
const closeModal = useReportModal(refreshList);
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const handleAction = (type) => {
|
||||||
|
console.log(type);
|
||||||
|
if (type === 'delete') {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('title', { keyPrefix: 'delete' }),
|
||||||
|
content:
|
||||||
|
itemData.answer_count > 0
|
||||||
|
? t('question', { keyPrefix: 'delete' })
|
||||||
|
: t('other', { keyPrefix: 'delete' }),
|
||||||
|
cancelBtnVariant: 'link',
|
||||||
|
confirmBtnVariant: 'danger',
|
||||||
|
confirmText: t('delete', { keyPrefix: 'btns' }),
|
||||||
|
onConfirm: () => {
|
||||||
|
changeQuestionStatus(itemData.id, 'deleted').then(() => {
|
||||||
|
refreshList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'undelete') {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('undelete_title'),
|
||||||
|
content: t('undelete_desc'),
|
||||||
|
cancelBtnVariant: 'link',
|
||||||
|
confirmBtnVariant: 'danger',
|
||||||
|
confirmText: t('undelete', { keyPrefix: 'btns' }),
|
||||||
|
onConfirm: () => {
|
||||||
|
changeQuestionStatus(itemData.id, 'available').then(() => {
|
||||||
|
refreshList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'close') {
|
||||||
|
closeModal.onShow({
|
||||||
|
type: 'question',
|
||||||
|
id: itemData.id,
|
||||||
|
action: 'close',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'reopen') {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('title', { keyPrefix: 'question_detail.reopen' }),
|
||||||
|
content: t('content', { keyPrefix: 'question_detail.reopen' }),
|
||||||
|
cancelBtnVariant: 'link',
|
||||||
|
confirmText: t('confirm_btn', { keyPrefix: 'question_detail.reopen' }),
|
||||||
|
onConfirm: () => {
|
||||||
|
reopenQuestion({
|
||||||
|
question_id: itemData.id,
|
||||||
|
}).then(() => {
|
||||||
|
toast.onShow({
|
||||||
|
msg: t('post_reopen', { keyPrefix: 'messages' }),
|
||||||
|
variant: 'success',
|
||||||
|
});
|
||||||
|
refreshList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown>
|
||||||
|
<Dropdown.Toggle variant="link" className="no-toggle p-0">
|
||||||
|
<Icon
|
||||||
|
name="three-dots-vertical"
|
||||||
|
title={t('action', { keyPrefix: 'admin.answers' })}
|
||||||
|
/>
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
<Dropdown.Menu>
|
||||||
|
{curFilter === 'normal' && (
|
||||||
|
<Dropdown.Item onClick={() => handleAction('close')}>
|
||||||
|
{t('close', { keyPrefix: 'btns' })}
|
||||||
|
</Dropdown.Item>
|
||||||
|
)}
|
||||||
|
{curFilter === 'closed' && (
|
||||||
|
<Dropdown.Item onClick={() => handleAction('reopen')}>
|
||||||
|
{t('reopen', { keyPrefix: 'btns' })}
|
||||||
|
</Dropdown.Item>
|
||||||
|
)}
|
||||||
|
{curFilter !== 'deleted' ? (
|
||||||
|
<Dropdown.Item onClick={() => handleAction('delete')}>
|
||||||
|
{t('delete', { keyPrefix: 'btns' })}
|
||||||
|
</Dropdown.Item>
|
||||||
|
) : (
|
||||||
|
<Dropdown.Item onClick={() => handleAction('undelete')}>
|
||||||
|
{t('undelete', { keyPrefix: 'btns' })}
|
||||||
|
</Dropdown.Item>
|
||||||
|
)}
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnswerActions;
|
|
@ -1,5 +1,5 @@
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Button, Form, Table, Stack } from 'react-bootstrap';
|
import { Form, Table, Stack } from 'react-bootstrap';
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -9,17 +9,17 @@ import {
|
||||||
FormatTime,
|
FormatTime,
|
||||||
Icon,
|
Icon,
|
||||||
Pagination,
|
Pagination,
|
||||||
Modal,
|
|
||||||
BaseUserCard,
|
BaseUserCard,
|
||||||
Empty,
|
Empty,
|
||||||
QueryGroup,
|
QueryGroup,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { ADMIN_LIST_STATUS } from '@/common/constants';
|
import { ADMIN_LIST_STATUS } from '@/common/constants';
|
||||||
import { useEditStatusModal, useReportModal } from '@/hooks';
|
|
||||||
import * as Type from '@/common/interface';
|
import * as Type from '@/common/interface';
|
||||||
import { useQuestionSearch, changeQuestionStatus } from '@/services';
|
import { useQuestionSearch } from '@/services';
|
||||||
import { pathFactory } from '@/router/pathFactory';
|
import { pathFactory } from '@/router/pathFactory';
|
||||||
|
|
||||||
|
import Action from './components/Action';
|
||||||
|
|
||||||
const questionFilterItems: Type.AdminContentsFilterBy[] = [
|
const questionFilterItems: Type.AdminContentsFilterBy[] = [
|
||||||
'normal',
|
'normal',
|
||||||
'closed',
|
'closed',
|
||||||
|
@ -46,53 +46,6 @@ const Questions: FC = () => {
|
||||||
});
|
});
|
||||||
const count = listData?.count || 0;
|
const count = listData?.count || 0;
|
||||||
|
|
||||||
const closeModal = useReportModal(refreshList);
|
|
||||||
|
|
||||||
const handleCallback = (id, type) => {
|
|
||||||
if (type === 'normal') {
|
|
||||||
changeQuestionStatus(id, 'available').then(() => {
|
|
||||||
refreshList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === 'closed') {
|
|
||||||
closeModal.onShow({
|
|
||||||
type: 'question',
|
|
||||||
id,
|
|
||||||
action: 'close',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === 'deleted') {
|
|
||||||
const item = listData?.list?.filter((v) => v.id === id)?.[0];
|
|
||||||
Modal.confirm({
|
|
||||||
title: t('title', { keyPrefix: 'delete' }),
|
|
||||||
content:
|
|
||||||
item.answer_count > 0
|
|
||||||
? t('question', { keyPrefix: 'delete' })
|
|
||||||
: t('other', { keyPrefix: 'delete' }),
|
|
||||||
cancelBtnVariant: 'link',
|
|
||||||
confirmBtnVariant: 'danger',
|
|
||||||
confirmText: t('delete', { keyPrefix: 'btns' }),
|
|
||||||
onConfirm: () => {
|
|
||||||
changeQuestionStatus(id, 'deleted').then(() => {
|
|
||||||
refreshList();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeModal = useEditStatusModal({
|
|
||||||
editType: 'question',
|
|
||||||
callback: handleCallback,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleChange = (itemId) => {
|
|
||||||
changeModal.onShow({
|
|
||||||
id: itemId,
|
|
||||||
type: curFilter,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFilter = (e) => {
|
const handleFilter = (e) => {
|
||||||
urlSearchParams.set('query', e.target.value);
|
urlSearchParams.set('query', e.target.value);
|
||||||
urlSearchParams.delete('page');
|
urlSearchParams.delete('page');
|
||||||
|
@ -126,9 +79,9 @@ const Questions: FC = () => {
|
||||||
<th style={{ width: '8%' }}>{t('answers')}</th>
|
<th style={{ width: '8%' }}>{t('answers')}</th>
|
||||||
<th style={{ width: '20%' }}>{t('created')}</th>
|
<th style={{ width: '20%' }}>{t('created')}</th>
|
||||||
<th style={{ width: '9%' }}>{t('status')}</th>
|
<th style={{ width: '9%' }}>{t('status')}</th>
|
||||||
{curFilter !== 'deleted' && (
|
<th style={{ width: '10%' }} className="text-end">
|
||||||
<th style={{ width: '10%' }}>{t('action')}</th>
|
{t('action')}
|
||||||
)}
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="align-middle">
|
<tbody className="align-middle">
|
||||||
|
@ -176,16 +129,14 @@ const Questions: FC = () => {
|
||||||
{t(ADMIN_LIST_STATUS[curFilter]?.name)}
|
{t(ADMIN_LIST_STATUS[curFilter]?.name)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
{curFilter !== 'deleted' && (
|
|
||||||
<td>
|
<td className="text-end">
|
||||||
<Button
|
<Action
|
||||||
variant="link"
|
itemData={{ id: li.id, answer_count: li.answer_count }}
|
||||||
className="p-0 btn-no-border"
|
refreshList={refreshList}
|
||||||
onClick={() => handleChange(li.id)}>
|
curFilter={curFilter}
|
||||||
{t('change')}
|
/>
|
||||||
</Button>
|
|
||||||
</td>
|
</td>
|
||||||
)}
|
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -138,7 +138,7 @@ const UserOperation = ({
|
||||||
return (
|
return (
|
||||||
<td className="text-end">
|
<td className="text-end">
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<Dropdown.Toggle variant="link" className="no-toggle">
|
<Dropdown.Toggle variant="link" className="no-toggle p-0">
|
||||||
<Icon name="three-dots-vertical" title={t('action')} />
|
<Icon name="three-dots-vertical" title={t('action')} />
|
||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
|
@ -25,7 +25,7 @@ import { loggedUserInfoStore, userCenterStore } from '@/stores';
|
||||||
import { formatCount } from '@/utils';
|
import { formatCount } from '@/utils';
|
||||||
|
|
||||||
import DeleteUserModal from './components/DeleteUserModal';
|
import DeleteUserModal from './components/DeleteUserModal';
|
||||||
import UserOperate from './components/UserOperate';
|
import Action from './components/Action';
|
||||||
|
|
||||||
const UserFilterKeys: Type.UserFilterBy[] = [
|
const UserFilterKeys: Type.UserFilterBy[] = [
|
||||||
'all',
|
'all',
|
||||||
|
@ -243,7 +243,7 @@ const Users: FC = () => {
|
||||||
)}
|
)}
|
||||||
{curFilter !== 'deleted' &&
|
{curFilter !== 'deleted' &&
|
||||||
(showAction || user.status === 'inactive') ? (
|
(showAction || user.status === 'inactive') ? (
|
||||||
<UserOperate
|
<Action
|
||||||
userData={user}
|
userData={user}
|
||||||
showActionPassword={showActionPassword}
|
showActionPassword={showActionPassword}
|
||||||
showActionRole={showActionRole}
|
showActionRole={showActionRole}
|
||||||
|
|
Loading…
Reference in New Issue