fix: add QueryGroup component

This commit is contained in:
shuai 2022-10-09 18:20:19 +08:00 committed by mingcheng
parent 01bfa95962
commit 13a7bffc74
14 changed files with 178 additions and 290 deletions

View File

@ -0,0 +1,62 @@
import { FC, memo } from 'react';
import { ButtonGroup, Button } from 'react-bootstrap';
import { useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
interface Props {
data: string[] | Array<{ name: string; sort: string }>;
i18nkeyPrefix: string;
currentSort: string;
sortKey?: string;
className?: string;
}
const Index: FC<Props> = ({
data,
currentSort = '',
sortKey = 'order',
i18nkeyPrefix = '',
className = '',
}) => {
const [searchParams, setUrlSearchParams] = useSearchParams();
const { t } = useTranslation('translation', {
keyPrefix: i18nkeyPrefix,
});
const handleParams = (order): string => {
searchParams.delete('page');
searchParams.set(sortKey, order);
const searchStr = searchParams.toString();
return `?${searchStr}`;
};
const handleClick = (e, type) => {
e.preventDefault();
const str = handleParams(type);
setUrlSearchParams(str);
};
return (
<ButtonGroup size="sm">
{data.map((btn) => {
const key = typeof btn === 'string' ? btn : btn.sort;
const name = typeof btn === 'string' ? btn : btn.name;
return (
<Button
as="a"
key={key}
variant="outline-secondary"
active={currentSort === name}
className={`text-capitalize ${className}`}
href={handleParams(key)}
onClick={(evt) => handleClick(evt, key)}>
{t(name)}
</Button>
);
})}
</ButtonGroup>
);
};
export default memo(Index);

View File

@ -1,5 +1,5 @@
import { FC } from 'react';
import { Row, Col, ButtonGroup, Button, ListGroup } from 'react-bootstrap';
import { Row, Col, ListGroup } from 'react-bootstrap';
import { NavLink, useParams, useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@ -12,6 +12,7 @@ import {
FormatTime,
Empty,
BaseUserCard,
QueryGroup,
} from '@answer/components';
const QuestionOrderKeys: Type.QuestionOrderBy[] = [
@ -83,7 +84,7 @@ const QuestionLastUpdate = ({ q }) => {
const QuestionList: FC<Props> = ({ source }) => {
const { t } = useTranslation('translation', { keyPrefix: 'question' });
const { tagName = '' } = useParams();
const [urlSearchParams, setUrlSearchParams] = useSearchParams();
const [urlSearchParams] = useSearchParams();
const curOrder = urlSearchParams.get('order') || QuestionOrderKeys[0];
const curPage = Number(urlSearchParams.get('page')) || 1;
const pageSize = 20;
@ -99,15 +100,15 @@ const QuestionList: FC<Props> = ({ source }) => {
}
const { data: listData, isLoading } = useQuestionList(reqParams);
const count = listData?.count || 0;
const onOrderChange = (evt, order) => {
evt.preventDefault();
if (order === curOrder) {
return;
}
urlSearchParams.set('page', '1');
urlSearchParams.set('order', order);
setUrlSearchParams(urlSearchParams);
};
// const onOrderChange = (evt, order) => {
// evt.preventDefault();
// if (order === curOrder) {
// return;
// }
// urlSearchParams.set('page', '1');
// urlSearchParams.set('order', order);
// setUrlSearchParams(urlSearchParams);
// };
return (
<div>
@ -120,21 +121,11 @@ const QuestionList: FC<Props> = ({ source }) => {
</h5>
</Col>
<Col>
<ButtonGroup size="sm">
{QuestionOrderKeys.map((k) => {
return (
<Button
as="a"
key={k}
className="text-capitalize"
href={`?page=1&order=${k}`}
onClick={(evt) => onOrderChange(evt, k)}
variant={curOrder === k ? 'secondary' : 'outline-secondary'}>
{t(k)}
</Button>
);
})}
</ButtonGroup>
<QueryGroup
data={QuestionOrderKeys}
currentSort={curOrder}
i18nkeyPrefix="question"
/>
</Col>
</Row>
<ListGroup variant="flush" className="border-top border-bottom-0">

View File

@ -24,6 +24,7 @@ import PageTitle from './PageTitle';
import Empty from './Empty';
import BaseUserCard from './BaseUserCard';
import FollowingTags from './FollowingTags';
import QueryGroup from './QueryGroup';
export {
Avatar,
@ -53,5 +54,6 @@ export {
BaseUserCard,
FollowingTags,
htmlRender,
QueryGroup,
};
export type { EditorRef };

View File

@ -1,12 +1,5 @@
import { FC } from 'react';
import {
ButtonGroup,
Button,
Form,
Table,
Stack,
Badge,
} from 'react-bootstrap';
import { Button, Form, Table, Stack, Badge } from 'react-bootstrap';
import { useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@ -17,6 +10,7 @@ import {
Modal,
BaseUserCard,
Empty,
QueryGroup,
} from '@answer/components';
import { ADMIN_LIST_STATUS } from '@answer/common/constants';
import { useEditStatusModal } from '@answer/hooks';
@ -28,7 +22,7 @@ import '../index.scss';
const answerFilterItems: Type.AdminContentsFilterBy[] = ['normal', 'deleted'];
const Answers: FC = () => {
const [urlSearchParams, setUrlSearchParams] = useSearchParams();
const [urlSearchParams] = useSearchParams();
const curFilter = urlSearchParams.get('status') || answerFilterItems[0];
const PAGE_SIZE = 20;
const curPage = Number(urlSearchParams.get('page')) || 1;
@ -44,14 +38,6 @@ const Answers: FC = () => {
status: curFilter as Type.AdminContentsFilterBy,
});
const count = listData?.count || 0;
const onFilterChange = (filter) => {
if (filter === curFilter) {
return;
}
urlSearchParams.set('page', '1');
urlSearchParams.set('status', filter);
setUrlSearchParams(urlSearchParams);
};
const handleCallback = (id, type) => {
if (type === 'normal') {
@ -95,20 +81,13 @@ const Answers: FC = () => {
<>
<h3 className="mb-4">{t('page_title')}</h3>
<div className="d-flex justify-content-between align-items-center mb-3">
<ButtonGroup size="sm">
{answerFilterItems.map((li) => {
return (
<Button
key={li}
size="sm"
className="text-capitalize"
onClick={() => onFilterChange(li)}
variant={curFilter === li ? 'secondary' : 'outline-secondary'}>
{t(li)}
</Button>
);
})}
</ButtonGroup>
<QueryGroup
data={answerFilterItems}
currentSort={curFilter}
sortKey="status"
i18nkeyPrefix="admin.answers"
/>
<Form.Control
size="sm"
type="input"

View File

@ -1,5 +1,5 @@
import React, { FC } from 'react';
import { ButtonGroup, Button, Form, Table, Stack } from 'react-bootstrap';
import { Button, Form, Table, Stack } from 'react-bootstrap';
import { useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@ -8,6 +8,7 @@ import {
BaseUserCard,
Empty,
Pagination,
QueryGroup,
} from '@answer/components';
import { useReportModal } from '@answer/hooks';
import * as Type from '@answer/common/interface';
@ -38,14 +39,7 @@ const Flags: FC = () => {
const reportModal = useReportModal(refreshList);
const count = listData?.count || 0;
const onFilterChange = (filter) => {
if (filter === curFilter) {
return;
}
urlSearchParams.set('page', '1');
urlSearchParams.set('status', filter);
setUrlSearchParams(urlSearchParams);
};
const onTypeChange = (evt) => {
urlSearchParams.set('type', evt.target.value);
setUrlSearchParams(urlSearchParams);
@ -64,20 +58,13 @@ const Flags: FC = () => {
<>
<h3 className="mb-4">{t('title')}</h3>
<div className="d-flex justify-content-between align-items-center mb-3">
<ButtonGroup size="sm">
{flagFilterKeys.map((k) => {
return (
<Button
key={k}
size="sm"
className="text-capitalize"
onClick={() => onFilterChange(k)}
variant={curFilter === k ? 'secondary' : 'outline-secondary'}>
{t(k)}
</Button>
);
})}
</ButtonGroup>
<QueryGroup
data={flagFilterKeys}
currentSort={curFilter}
sortKey="status"
i18nkeyPrefix="admin.flags"
/>
<Form.Select
value={curType}
onChange={onTypeChange}

View File

@ -1,12 +1,5 @@
import { FC } from 'react';
import {
ButtonGroup,
Button,
Form,
Table,
Stack,
Badge,
} from 'react-bootstrap';
import { Button, Form, Table, Stack, Badge } from 'react-bootstrap';
import { useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@ -17,6 +10,7 @@ import {
Modal,
BaseUserCard,
Empty,
QueryGroup,
} from '@answer/components';
import { ADMIN_LIST_STATUS } from '@answer/common/constants';
import { useEditStatusModal, useReportModal } from '@answer/hooks';
@ -37,7 +31,7 @@ const questionFilterItems: Type.AdminContentsFilterBy[] = [
const PAGE_SIZE = 20;
const Questions: FC = () => {
const [urlSearchParams, setUrlSearchParams] = useSearchParams();
const [urlSearchParams] = useSearchParams();
const curFilter = urlSearchParams.get('status') || questionFilterItems[0];
const curPage = Number(urlSearchParams.get('page')) || 1;
const { t } = useTranslation('translation', { keyPrefix: 'admin.questions' });
@ -55,15 +49,6 @@ const Questions: FC = () => {
const closeModal = useReportModal(refreshList);
const onFilterChange = (filter) => {
if (filter === curFilter) {
return;
}
urlSearchParams.set('page', '1');
urlSearchParams.set('status', filter);
setUrlSearchParams(urlSearchParams);
};
const handleCallback = (id, type) => {
if (type === 'normal') {
changeQuestionStatus(id, 'available').then(() => {
@ -115,20 +100,13 @@ const Questions: FC = () => {
<>
<h3 className="mb-4">{t('page_title')}</h3>
<div className="d-flex justify-content-between align-items-center mb-3">
<ButtonGroup size="sm">
{questionFilterItems.map((li) => {
return (
<Button
key={li}
size="sm"
className="text-capitalize"
onClick={() => onFilterChange(li)}
variant={curFilter === li ? 'secondary' : 'outline-secondary'}>
{t(li)}
</Button>
);
})}
</ButtonGroup>
<QueryGroup
data={questionFilterItems}
currentSort={curFilter}
sortKey="status"
i18nkeyPrefix="admin.questions"
/>
<Form.Control
size="sm"
type="input"

View File

@ -1,5 +1,5 @@
import React, { FC, useState } from 'react';
import { ButtonGroup, Button, Form, Table, Badge } from 'react-bootstrap';
import { FC, useState } from 'react';
import { Button, Form, Table, Badge } from 'react-bootstrap';
import { useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@ -9,6 +9,7 @@ import {
FormatTime,
BaseUserCard,
Empty,
QueryGroup,
} from '@answer/components';
import * as Type from '@answer/common/interface';
import { useChangeModal } from '@answer/hooks';
@ -34,7 +35,7 @@ const Users: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'admin.users' });
const [userName, setUserName] = useState('');
const [urlSearchParams, setUrlSearchParams] = useSearchParams();
const [urlSearchParams] = useSearchParams();
const curFilter = urlSearchParams.get('filter') || UserFilterKeys[0];
const curPage = Number(urlSearchParams.get('page') || '1');
const {
@ -51,15 +52,6 @@ const Users: FC = () => {
callback: refreshUsers,
});
const onFilterChange = (filter) => {
if (filter === urlSearchParams.get('filter')) {
return;
}
urlSearchParams.set('page', '1');
urlSearchParams.set('filter', filter);
setUrlSearchParams(urlSearchParams);
};
const handleClick = ({ user_id, status }) => {
changeModal.onShow({
id: user_id,
@ -71,20 +63,13 @@ const Users: FC = () => {
<>
<h3 className="mb-4">{t('title')}</h3>
<div className="d-flex justify-content-between align-items-center mb-3">
<ButtonGroup size="sm">
{UserFilterKeys.map((k) => {
return (
<Button
key={k}
size="sm"
className="text-capitalize"
onClick={() => onFilterChange(k)}
variant={curFilter === k ? 'secondary' : 'outline-secondary'}>
{t(k)}
</Button>
);
})}
</ButtonGroup>
<QueryGroup
data={UserFilterKeys}
currentSort={curFilter}
sortKey="filter"
i18nkeyPrefix="admin.users"
/>
<Form.Control
className="d-none"
size="sm"

View File

@ -1,17 +1,29 @@
import { memo, FC } from 'react';
import { ButtonGroup } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Link, useLocation } from 'react-router-dom';
import { QueryGroup } from '@answer/components';
interface Props {
count: number;
order: string;
}
const sortBtns = [
{
name: 'score',
sort: 'default',
},
{
name: 'newest',
sort: 'updated',
},
];
const Index: FC<Props> = ({ count = 0, order = 'default' }) => {
const { t } = useTranslation('translation', {
keyPrefix: 'question_detail.answers',
});
const location = useLocation();
return (
<div
className="d-flex align-items-center justify-content-between mt-5 mb-3"
@ -19,22 +31,11 @@ const Index: FC<Props> = ({ count = 0, order = 'default' }) => {
<h5 className="mb-0">
{count} {t('title')}
</h5>
<ButtonGroup size="sm">
<Link
to={`${location.pathname}?order=default`}
className={`btn btn-outline-secondary ${
order !== 'updated' ? 'active' : ''
}`}>
{t('score')}
</Link>
<Link
to={`${location.pathname}?order=updated`}
className={`btn btn-outline-secondary ${
order === 'updated' ? 'active' : ''
}`}>
{t('newest')}
</Link>
</ButtonGroup>
<QueryGroup
data={sortBtns}
currentSort={order === 'updated' ? 'newest' : 'score'}
i18nkeyPrefix="question_detail.answers"
/>
</div>
);
};

View File

@ -1,65 +1,29 @@
import { FC, memo } from 'react';
import { ListGroupItem, ButtonGroup, Button } from 'react-bootstrap';
import { useSearchParams, useNavigate, useLocation } from 'react-router-dom';
import { ListGroupItem } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
const sortBtns = [
{
name: 'newest',
},
{
name: 'active',
},
{
name: 'score',
},
];
import { QueryGroup } from '@answer/components';
const sortBtns = ['newest', 'active', 'score'];
interface Props {
count: number;
sort: string;
}
const Index: FC<Props> = ({ sort, count = 0 }) => {
const [searchParams] = useSearchParams();
const location = useLocation();
const navigate = useNavigate();
const { t } = useTranslation('translation', {
keyPrefix: 'search.sort_btns',
});
const handleParams = (order): string => {
const basePath = location.pathname;
searchParams.delete('page');
searchParams.set('order', order);
const searchStr = searchParams.toString();
return `${basePath}?${searchStr}`;
};
const handleClick = (e, type) => {
e.preventDefault();
const str = handleParams(type);
navigate(str);
};
return (
<ListGroupItem className="d-flex flex-wrap align-items-center justify-content-between divide-line pb-3 border-bottom px-0">
<h5 className="mb-0">{t('counts', { count, keyPrefix: 'search' })}</h5>
<ButtonGroup size="sm">
{sortBtns.map((item) => {
return (
<Button
as="a"
variant="outline-secondary"
active={sort === item.name}
href={handleParams(item.name)}
key={item.name}
onClick={(e) => handleClick(e, item.name)}>
{t(item.name)}
</Button>
);
})}
</ButtonGroup>
<QueryGroup
data={sortBtns}
currentSort={sort}
sortKey="order"
i18nkeyPrefix="search.sort_btns"
/>
</ListGroupItem>
);
};

View File

@ -60,10 +60,9 @@ const Index: FC<Props> = ({ data }) => {
</div>
{data.object?.excerpt && (
<p
className="fs-14 text-truncate-2 mb-2 last-p text-break"
dangerouslySetInnerHTML={{ __html: data.object.excerpt }}
/>
<p className="fs-14 text-truncate-2 mb-2 last-p text-break">
{data.object.excerpt}
</p>
)}
{data.object?.tags?.map((item) => {

View File

@ -1,25 +1,18 @@
import { useState } from 'react';
import {
Container,
Row,
Col,
Card,
ButtonGroup,
Button,
Form,
} from 'react-bootstrap';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Form } from 'react-bootstrap';
import { useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useQueryTags, following } from '@answer/api';
import { Tag, Pagination, PageTitle } from '@answer/components';
import { Tag, Pagination, PageTitle, QueryGroup } from '@answer/components';
import { formatCount } from '@answer/utils';
const sortBtns = ['popular', 'name', 'newest'];
const Tags = () => {
const [urlSearch] = useSearchParams();
const { t } = useTranslation('translation', { keyPrefix: 'tags' });
const [searchTag, setSearchTag] = useState('');
const navigate = useNavigate();
const page = Number(urlSearch.get('page')) || 1;
const sort = urlSearch.get('sort');
@ -36,10 +29,6 @@ const Tags = () => {
setSearchTag(e.target.value);
};
const handleSort = (param) => {
navigate(`/tags?sort=${param}`);
};
const handleFollow = (tag) => {
following({
object_id: tag.tag_id,
@ -67,30 +56,12 @@ const Tags = () => {
/>
</Form.Group>
</Form>
<ButtonGroup size="sm">
<Button
variant={
!sort || sort === 'popular'
? 'secondary'
: 'outline-secondary'
}
onClick={() => handleSort('popular')}>
{t('sort_buttons.popular')}
</Button>
<Button
variant={sort === 'name' ? 'secondary' : 'outline-secondary'}
onClick={() => handleSort('name')}>
{t('sort_buttons.name')}
</Button>
<Button
className="text-capitalize"
variant={
sort === 'newest' ? 'secondary' : 'outline-secondary'
}
onClick={() => handleSort('newest')}>
{t('sort_buttons.newest')}
</Button>
</ButtonGroup>
<QueryGroup
data={sortBtns}
currentSort={sort || 'popular'}
sortKey="sort"
i18nkeyPrefix="tags.sort_buttons"
/>
</div>
</Col>

View File

@ -19,7 +19,9 @@ const Index: FC<Props> = ({ visible, tabName, data }) => {
<ListGroup variant="flush">
{data.map((item) => {
return (
<ListGroupItem className="py-3 px-0" key={item.question_id}>
<ListGroupItem
className="py-3 px-0"
key={tabName === 'questions' ? item.question_id : item.id}>
<h6 className="mb-2">
<a
className="text-break"

View File

@ -1,16 +1,9 @@
import { FC, memo } from 'react';
import { ButtonGroup, Button } from 'react-bootstrap';
import { useSearchParams, useNavigate, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
const sortBtns = [
{
name: 'newest',
},
{
name: 'score',
},
];
import { QueryGroup } from '@answer/components';
const sortBtns = ['newest', 'score'];
interface Props {
tabName: string;
@ -24,25 +17,8 @@ const Index: FC<Props> = ({
sort,
count = 0,
}) => {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const location = useLocation();
const { t } = useTranslation('translation', { keyPrefix: 'personal' });
const handleParams = (order): string => {
const basePath = location.pathname;
searchParams.delete('page');
searchParams.set('order', order);
const searchStr = searchParams.toString();
return `${basePath}?${searchStr}`;
};
const handleClick = (e, type) => {
e.preventDefault();
const str = handleParams(type);
navigate(str);
};
if (!visible) {
return null;
}
@ -53,20 +29,11 @@ const Index: FC<Props> = ({
{count} {t(tabName)}
</h5>
{(tabName === 'answers' || tabName === 'questions') && (
<ButtonGroup size="sm">
{sortBtns.map((item) => {
return (
<Button
variant="outline-secondary"
active={sort === item.name}
href={handleParams(item.name)}
key={item.name}
onClick={(e) => handleClick(e, item.name)}>
{t(item.name)}
</Button>
);
})}
</ButtonGroup>
<QueryGroup
data={sortBtns}
currentSort={sort}
i18nkeyPrefix="personal"
/>
)}
</div>
);

View File

@ -18,7 +18,7 @@ const Index: FC<Props> = ({ visible, data }) => {
<ListGroup variant="flush">
{data.map((item) => {
return (
<ListGroupItem className="d-flex py-3 px-0" key={item}>
<ListGroupItem className="d-flex py-3 px-0" key={item.object_id}>
<div
className={`me-3 text-end ${
item.reputation > 0 ? 'text-success' : 'text-danger'