From a122523992f446c7a6ca2aaa41198380a73ee923 Mon Sep 17 00:00:00 2001 From: artem <89ufisum@gmail.com> Date: Fri, 22 May 2026 12:54:24 +0300 Subject: [PATCH 1/4] YH-1915: added task sorting by name, difficulty, createdAt with ASC/DESC order --- public/locales/ru/task.json | 13 ++++++ src/entities/task/model/types/filters.ts | 2 + src/entities/topic/index.ts | 2 + .../ui/QuestionsFilters/QuestionsFilters.tsx | 2 +- src/features/task/filterTasks/index.ts | 2 + .../task/filterTasks/model/constants/sort.ts | 12 ++++++ .../model/hooks/useTasksFilters.ts | 11 +++++ .../ui/SortTasksByField/SortTasksByField.tsx | 40 +++++++++++++++++++ .../ui/SortTasksByOrder/SortTasksByOrder.tsx | 40 +++++++++++++++++++ .../ui/TaskFilters/TasksFilters.tsx | 8 ++++ .../ui/TasksTablePage/TasksTablePage.tsx | 4 ++ .../tasks/tasks/ui/TasksPage/TasksPage.tsx | 6 +++ src/shared/config/i18n/i18nTranslations.ts | 7 ++++ 13 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 src/features/task/filterTasks/model/constants/sort.ts create mode 100644 src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx create mode 100644 src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx diff --git a/public/locales/ru/task.json b/public/locales/ru/task.json index fa3211743..4acee9545 100644 --- a/public/locales/ru/task.json +++ b/public/locales/ru/task.json @@ -93,6 +93,19 @@ "companies": { "title": "Компании" }, + "sort": { + "fields": { + "title": "Сортировать по полю", + "name": "Название", + "difficulty": "Сложность", + "createdAt": "Дата создания" + }, + "order": { + "title": "Сортировать по", + "asc": "Возрастанию", + "desc": "Убыванию" + } + }, "subscription.level": { "title": "Уровень подписки", "subtitle": "Укажите требуемый уровень подписки для допуска к задаче", diff --git a/src/entities/task/model/types/filters.ts b/src/entities/task/model/types/filters.ts index eb1b223cd..79628f230 100644 --- a/src/entities/task/model/types/filters.ts +++ b/src/entities/task/model/types/filters.ts @@ -7,4 +7,6 @@ export interface TasksFilterParams { category?: TaskCategoryCode; langIds?: number[]; companyId?: string; + sortBy?: string; + sortOrder?: string; } diff --git a/src/entities/topic/index.ts b/src/entities/topic/index.ts index 0f4e9a698..719ab9fb6 100644 --- a/src/entities/topic/index.ts +++ b/src/entities/topic/index.ts @@ -1,3 +1,5 @@ +export { TopicFilterField } from './ui/TopicFilterField/TopicFilterField'; + export { TopicForm } from './ui/TopicForm/TopicForm'; export type { Topic, CreateOrEditTopicFormValues } from './model/types/topic'; diff --git a/src/features/question/filterQuestions/ui/QuestionsFilters/QuestionsFilters.tsx b/src/features/question/filterQuestions/ui/QuestionsFilters/QuestionsFilters.tsx index 98c4fdd06..e9c6a4549 100644 --- a/src/features/question/filterQuestions/ui/QuestionsFilters/QuestionsFilters.tsx +++ b/src/features/question/filterQuestions/ui/QuestionsFilters/QuestionsFilters.tsx @@ -11,7 +11,7 @@ import { getSpecializationId } from '@/entities/profile'; import { ChooseQuestionComplexity, QuestionsFilterParams } from '@/entities/question'; import { SkillsListField } from '@/entities/skill'; import { SpecializationsListField } from '@/entities/specialization'; -import { TopicFilterField } from '@/entities/topic/ui/TopicFilterField/TopicFilterField'; +import { TopicFilterField } from '@/entities/topic'; import { QuestionRateFilter } from '../QuestionRateFilter/QuestionRateFilter'; import { QuestionSortByFieldFilter } from '../QuestionSortByFieldFilter/QuestionSortByFieldFilter'; diff --git a/src/features/task/filterTasks/index.ts b/src/features/task/filterTasks/index.ts index 276ec6926..2b0308efe 100644 --- a/src/features/task/filterTasks/index.ts +++ b/src/features/task/filterTasks/index.ts @@ -3,3 +3,5 @@ export { useTasksFilters } from './model/hooks/useTasksFilters'; export { TasksFiltersSkeleton } from './ui/TaskFilters/TasksFilters.skeleton'; export { TaskDifficultyFilterSkeleton } from './ui/TaskDifficultyFilter/TaskDifficultyFilter.skeleton'; export { TaskLanguagesFilterSkeleton } from './ui/TaskLanguagesFilter/TaskLanguagesFilter.skeleton'; +export { SortTasksByOrder } from './ui/SortTasksByOrder/SortTasksByOrder'; +export { SortTasksByField } from './ui/SortTasksByField/SortTasksByField'; diff --git a/src/features/task/filterTasks/model/constants/sort.ts b/src/features/task/filterTasks/model/constants/sort.ts new file mode 100644 index 000000000..59e1c2af6 --- /dev/null +++ b/src/features/task/filterTasks/model/constants/sort.ts @@ -0,0 +1,12 @@ +import { Tasks } from '@/shared/config'; + +export const SORT_ORDER_DATA = [ + { id: 1, label: Tasks.SORT_ASC, value: 'ASC' }, + { id: 2, label: Tasks.SORT_DESC, value: 'DESC' }, +] as const; + +export const SORT_FIELDS_DATA = [ + { id: 1, label: Tasks.SORT_FIELDS_NAME, value: 'name' }, + { id: 2, label: Tasks.SORT_DIFFICULTY, value: 'difficulty' }, + { id: 3, label: Tasks.SORT_CREATED_AT, value: 'createdAt' }, +] as const; diff --git a/src/features/task/filterTasks/model/hooks/useTasksFilters.ts b/src/features/task/filterTasks/model/hooks/useTasksFilters.ts index 5a171a98c..62941c13a 100644 --- a/src/features/task/filterTasks/model/hooks/useTasksFilters.ts +++ b/src/features/task/filterTasks/model/hooks/useTasksFilters.ts @@ -19,6 +19,7 @@ export const useTasksFilters = (initialParams: TasksFilterParams) => { Boolean(filters.category) || Boolean(filters.companyId) || (filters.langIds || []).length > 0; + Boolean(filters.sortBy) || Boolean(filters.sortOrder); const onChangePage = (page: TasksFilterParams['page']) => { onFilterChange({ page }); @@ -45,6 +46,14 @@ export const useTasksFilters = (initialParams: TasksFilterParams) => { onFilterChange({ langIds, page: 1 }); }; + const onChangeSortField = (sortBy?: TasksFilterParams['sortBy']) => { + onFilterChange({ sortBy, page: 1 }); + }; + + const onChangeSortOrder = (sortOrder?: TasksFilterParams['sortOrder']) => { + onFilterChange({ sortOrder, page: 1 }); + }; + return { filters, hasFilters, @@ -54,6 +63,8 @@ export const useTasksFilters = (initialParams: TasksFilterParams) => { onChangeLangIds, onChangeCategory, onChangeCompanyId, + onChangeSortField, + onChangeSortOrder, onResetFilters, }; }; diff --git a/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx b/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx new file mode 100644 index 000000000..7f6c945d3 --- /dev/null +++ b/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from 'react-i18next'; +import { useSearchParams } from 'react-router-dom'; + +import { i18Namespace, Tasks } from '@/shared/config'; +import { BaseFilterSection } from '@/shared/ui/BaseFilterSection'; + +import { SORT_FIELDS_DATA } from '../../model/constants/sort'; + +interface SortTasksFieldProps { + onChangeSortField: (field?: string) => void; +} + +export const SortTasksByField = ({ onChangeSortField }: SortTasksFieldProps) => { + const { t } = useTranslation(i18Namespace.task); + const [searchParams] = useSearchParams(); + const currentSort = searchParams?.get('sortBy'); + + const updatedFieldsData = SORT_FIELDS_DATA.map((field) => ({ + ...field, + title: t(field.label), + active: currentSort === field.value, + })); + + const onChooseField = (id: number) => { + const selectedField = SORT_FIELDS_DATA.find((field) => field.id === id); + + if (!selectedField) return; + onChangeSortField(selectedField?.value); + }; + + return ( + <> + + + ); +}; diff --git a/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx b/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx new file mode 100644 index 000000000..263023671 --- /dev/null +++ b/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from 'react-i18next'; +import { useSearchParams } from 'react-router-dom'; + +import { i18Namespace, Tasks } from '@/shared/config'; +import { BaseFilterSection } from '@/shared/ui/BaseFilterSection'; + +import { SORT_ORDER_DATA } from '../../model/constants/sort'; + +interface SortTasksByOrderProps { + onChangeSortOrder: (sortOrder?: string | undefined) => void; +} + +export const SortTasksByOrder = ({ onChangeSortOrder }: SortTasksByOrderProps) => { + const [searchParams] = useSearchParams(); + const currentSort = searchParams?.get('sortOrder'); + const { t } = useTranslation(i18Namespace.task); + + const updatedSortOrderData = SORT_ORDER_DATA.map((order) => ({ + ...order, + title: t(order.label), + active: currentSort === order.value, + })); + + const onChooseSortOrder = (id: number) => { + const order = SORT_ORDER_DATA.find((order) => order.id === id); + + if (!order) return; + onChangeSortOrder(order.value); + }; + + return ( + <> + + + ); +}; diff --git a/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx b/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx index 6d70b6a6f..ee8ecd1a5 100644 --- a/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx +++ b/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx @@ -7,6 +7,8 @@ import { SearchInput } from '@/shared/ui/SearchInput'; import { PublicCompanySelect } from '@/entities/company'; import { TaskCategoryFilterList, TasksFilterParams } from '@/entities/task'; +import { SortTasksByField } from '../SortTasksByField/SortTasksByField'; +import { SortTasksByOrder } from '../SortTasksByOrder/SortTasksByOrder'; import { TaskDifficultyFilter } from '../TaskDifficultyFilter/TaskDifficultyFilter'; import { TaskLanguagesFilter } from '../TaskLanguagesFilter/TaskLanguagesFilter'; @@ -17,6 +19,8 @@ interface TasksFiltersProps { onChangeLangIds: (langIds?: TasksFilterParams['langIds']) => void; onChangeCategory: (category?: TasksFilterParams['category']) => void; onChangeCompanyId: (companyId?: TasksFilterParams['companyId']) => void; + onChangeSortField: (sortBy?: TasksFilterParams['sortBy']) => void; + onChangeSortOrder: (sortOrder?: TasksFilterParams['sortOrder']) => void; } export const TasksFilters = ({ @@ -26,6 +30,8 @@ export const TasksFilters = ({ onChangeLangIds, onChangeCategory, onChangeCompanyId, + onChangeSortField, + onChangeSortOrder, }: TasksFiltersProps) => { const { title, difficulty, langIds, category, companyId } = filters; const { t } = useTranslation(i18Namespace.task); @@ -46,6 +52,8 @@ export const TasksFilters = ({ + + ); }; diff --git a/src/pages/admin/task/tasks/ui/TasksTablePage/TasksTablePage.tsx b/src/pages/admin/task/tasks/ui/TasksTablePage/TasksTablePage.tsx index 15f83dc78..23c947b0d 100644 --- a/src/pages/admin/task/tasks/ui/TasksTablePage/TasksTablePage.tsx +++ b/src/pages/admin/task/tasks/ui/TasksTablePage/TasksTablePage.tsx @@ -33,6 +33,8 @@ const TasksTablePage = () => { onChangeLangIds, onChangeCategory, onChangeCompanyId, + onChangeSortField, + onChangeSortOrder, } = useTasksFilters({ page: 1 }); const { @@ -109,6 +111,8 @@ const TasksTablePage = () => { onChangeLangIds={onChangeLangIds} onChangeCategory={onChangeCategory} onChangeCompanyId={onChangeCompanyId} + onChangeSortField={onChangeSortField} + onChangeSortOrder={onChangeSortOrder} /> )} /> diff --git a/src/pages/tasks/tasks/ui/TasksPage/TasksPage.tsx b/src/pages/tasks/tasks/ui/TasksPage/TasksPage.tsx index 67d0ff11d..a883cc645 100644 --- a/src/pages/tasks/tasks/ui/TasksPage/TasksPage.tsx +++ b/src/pages/tasks/tasks/ui/TasksPage/TasksPage.tsx @@ -30,6 +30,8 @@ const TasksPage = () => { onChangeLangIds, onChangeCategory, onChangeCompanyId, + onChangeSortField, + onChangeSortOrder, onResetFilters, } = useTasksFilters({ page: 1, @@ -55,12 +57,16 @@ const TasksPage = () => { onChangeLangIds={onChangeLangIds} onChangeCategory={onChangeCategory} onChangeCompanyId={onChangeCompanyId} + onChangeSortField={onChangeSortField} + onChangeSortOrder={onChangeSortOrder} filters={{ title: filters.title, difficulty: filters.difficulty, langIds: filters.langIds, category: filters.category, companyId: filters.companyId, + sortBy: filters.sortBy, + sortOrder: filters.sortOrder, }} /> ); diff --git a/src/shared/config/i18n/i18nTranslations.ts b/src/shared/config/i18n/i18nTranslations.ts index 6fdd50081..e444ced40 100644 --- a/src/shared/config/i18n/i18nTranslations.ts +++ b/src/shared/config/i18n/i18nTranslations.ts @@ -1674,6 +1674,13 @@ export enum Tasks { DESCRIPTION_TAB_TITLE = 'description.tab.title', DESCRIPTION_CONSTRAINTS_TITLE = 'description.constraints.title', DESCRIPTION_TAB_SUBTITLE = 'description.tab.subtitle', + SORT_FIELDS_TITLE = 'sort.fields.title', + SORT_FIELDS_NAME = 'sort.fields.name', + SORT_DIFFICULTY = 'sort.fields.difficulty', + SORT_CREATED_AT = 'sort.fields.createdAt', + SORT_ORDER_TITLE = 'sort.order.title', + SORT_ASC = 'sort.order.asc', + SORT_DESC = 'sort.order.desc', STATUS_ATTEMPTED = 'status.attempted', STATUS_NOT_STARTED = 'status.not.started', STATUS_SOLVED = 'status.solved', From 856d55ba3a11ce2ec2ab8c0e9bce8da52e0159fc Mon Sep 17 00:00:00 2001 From: artem <89ufisum@gmail.com> Date: Fri, 22 May 2026 13:33:16 +0300 Subject: [PATCH 2/4] YH-1915: added English translations for task sorting --- public/locales/en/task.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/public/locales/en/task.json b/public/locales/en/task.json index f10d2918b..cb254c626 100644 --- a/public/locales/en/task.json +++ b/public/locales/en/task.json @@ -78,6 +78,19 @@ "companies": { "title": "Companies" }, + "sort": { + "fields": { + "title": "Sort by field", + "name": "Name", + "difficulty": "Difficulty", + "createdAt": "Date created" + }, + "order": { + "title": "Sort by", + "asc": "Ascending", + "desc": "Descending" + } + }, "subscription.level": { "title": "Subscription level", "subtitle": "Specify the required subscription level for access to the task", From b0ec187bddd76517c3c10972d7118d0901657a3a Mon Sep 17 00:00:00 2001 From: artem <89ufisum@gmail.com> Date: Sat, 23 May 2026 15:24:08 +0300 Subject: [PATCH 3/4] YH-1915: integrate sort parameters with API and refactor sorting logic --- src/entities/task/model/types/filters.ts | 6 ++- src/entities/task/model/types/task.ts | 3 +- .../model/helpers/prepareSortFilterData.ts | 31 ++++++++++++++ .../model/hooks/useTasksFilters.ts | 5 ++- .../ui/SortTasksByField/SortTasksByField.tsx | 41 ++++++------------- .../ui/SortTasksByOrder/SortTasksByOrder.tsx | 41 ++++++------------- .../ui/TaskFilters/TasksFilters.tsx | 4 +- .../tasks/tasks/ui/TasksPage/TasksPage.tsx | 2 + src/shared/libs/app/types.ts | 2 +- .../SortFilterSection/SortFilterSection.tsx | 36 ++++++++++++++++ src/shared/ui/SortFilterSection/index.ts | 3 ++ 11 files changed, 110 insertions(+), 64 deletions(-) create mode 100644 src/features/task/filterTasks/model/helpers/prepareSortFilterData.ts create mode 100644 src/shared/ui/SortFilterSection/SortFilterSection.tsx create mode 100644 src/shared/ui/SortFilterSection/index.ts diff --git a/src/entities/task/model/types/filters.ts b/src/entities/task/model/types/filters.ts index 79628f230..d156a1d9e 100644 --- a/src/entities/task/model/types/filters.ts +++ b/src/entities/task/model/types/filters.ts @@ -1,4 +1,6 @@ +import { SortOrder } from '@/shared/libs'; import type { TaskCategoryCode, TaskDifficulty } from './task'; +import { SortBy } from '@/shared/libs/app/types'; export interface TasksFilterParams { page?: number; @@ -7,6 +9,6 @@ export interface TasksFilterParams { category?: TaskCategoryCode; langIds?: number[]; companyId?: string; - sortBy?: string; - sortOrder?: string; + sortBy?: SortBy; + sortOrder?: SortOrder; } diff --git a/src/entities/task/model/types/task.ts b/src/entities/task/model/types/task.ts index 2969a625d..de06003dd 100644 --- a/src/entities/task/model/types/task.ts +++ b/src/entities/task/model/types/task.ts @@ -2,6 +2,7 @@ import { Response, SortOrder } from '@/shared/libs'; import { Company } from '@/entities/company/@x/task'; import { ProgrammingLanguage } from '@/entities/programmingLanguage/@x/task'; +import { SortBy } from '@/shared/libs/app/types'; export type TaskStatus = 'solved' | 'attempted' | 'not_started'; export type TaskDifficulty = 1 | 2 | 3 | 4 | 5; @@ -105,7 +106,7 @@ export interface GetTasksListParams { langIds?: number[]; isActive?: boolean; search?: string; - sortBy?: 'name' | 'difficulty' | 'createdAt' | 'updatedAt'; + sortBy?: SortBy; sortOrder?: SortOrder; canSolve?: boolean; collectionId?: number; diff --git a/src/features/task/filterTasks/model/helpers/prepareSortFilterData.ts b/src/features/task/filterTasks/model/helpers/prepareSortFilterData.ts new file mode 100644 index 000000000..5b39a4556 --- /dev/null +++ b/src/features/task/filterTasks/model/helpers/prepareSortFilterData.ts @@ -0,0 +1,31 @@ +import { TFunction } from 'i18next'; + +interface SortFilterItem{ + label: string; + value: T; + id: number; +} + +interface FilterDataProps{ + t: TFunction; + value?: T; + data: readonly SortFilterItem[]; +} + +type PreparedSortFilterItem = { + id: number; + value: T; + title: string; + active: boolean; +}; + +export default function prepareSortFilterData({data, value, t}: FilterDataProps): PreparedSortFilterItem[] { + + const preparedData = data.map((item) => ({ + ...item, + title: t(item.label), + active: value === item.value, + })); + + return preparedData +} diff --git a/src/features/task/filterTasks/model/hooks/useTasksFilters.ts b/src/features/task/filterTasks/model/hooks/useTasksFilters.ts index 62941c13a..3fe5c421e 100644 --- a/src/features/task/filterTasks/model/hooks/useTasksFilters.ts +++ b/src/features/task/filterTasks/model/hooks/useTasksFilters.ts @@ -18,8 +18,9 @@ export const useTasksFilters = (initialParams: TasksFilterParams) => { Boolean(filters.difficulty) || Boolean(filters.category) || Boolean(filters.companyId) || - (filters.langIds || []).length > 0; - Boolean(filters.sortBy) || Boolean(filters.sortOrder); + (filters.langIds || []).length > 0 || + Boolean(filters.sortBy) || + Boolean(filters.sortOrder); const onChangePage = (page: TasksFilterParams['page']) => { onFilterChange({ page }); diff --git a/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx b/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx index 7f6c945d3..c05da73d0 100644 --- a/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx +++ b/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx @@ -1,40 +1,25 @@ import { useTranslation } from 'react-i18next'; -import { useSearchParams } from 'react-router-dom'; - import { i18Namespace, Tasks } from '@/shared/config'; -import { BaseFilterSection } from '@/shared/ui/BaseFilterSection'; - import { SORT_FIELDS_DATA } from '../../model/constants/sort'; +import { SortBy } from '@/shared/libs/app/types'; +import { SortFilterSection } from '@/shared/ui/SortFilterSection'; +import prepareSortFilterData from '../../model/helpers/prepareSortFilterData'; interface SortTasksFieldProps { - onChangeSortField: (field?: string) => void; + value?: SortBy; + onChangeSortField: (field?: SortBy) => void; } -export const SortTasksByField = ({ onChangeSortField }: SortTasksFieldProps) => { +export const SortTasksByField = ({ onChangeSortField, value }: SortTasksFieldProps) => { const { t } = useTranslation(i18Namespace.task); - const [searchParams] = useSearchParams(); - const currentSort = searchParams?.get('sortBy'); - - const updatedFieldsData = SORT_FIELDS_DATA.map((field) => ({ - ...field, - title: t(field.label), - active: currentSort === field.value, - })); - - const onChooseField = (id: number) => { - const selectedField = SORT_FIELDS_DATA.find((field) => field.id === id); - - if (!selectedField) return; - onChangeSortField(selectedField?.value); - }; + const preparedData = prepareSortFilterData({data: SORT_FIELDS_DATA, value, t}) return ( - <> - - + ); }; diff --git a/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx b/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx index 263023671..099c9ed04 100644 --- a/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx +++ b/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx @@ -1,40 +1,25 @@ import { useTranslation } from 'react-i18next'; -import { useSearchParams } from 'react-router-dom'; - import { i18Namespace, Tasks } from '@/shared/config'; -import { BaseFilterSection } from '@/shared/ui/BaseFilterSection'; - import { SORT_ORDER_DATA } from '../../model/constants/sort'; +import { SortOrder } from '@/shared/libs'; +import { SortFilterSection } from '@/shared/ui/SortFilterSection'; +import prepareSortFilterData from '../../model/helpers/prepareSortFilterData'; interface SortTasksByOrderProps { - onChangeSortOrder: (sortOrder?: string | undefined) => void; + value?: SortOrder; + onChangeSortOrder: (sortOrder?: SortOrder) => void; } -export const SortTasksByOrder = ({ onChangeSortOrder }: SortTasksByOrderProps) => { - const [searchParams] = useSearchParams(); - const currentSort = searchParams?.get('sortOrder'); +export const SortTasksByOrder = ({ onChangeSortOrder, value }: SortTasksByOrderProps) => { const { t } = useTranslation(i18Namespace.task); - - const updatedSortOrderData = SORT_ORDER_DATA.map((order) => ({ - ...order, - title: t(order.label), - active: currentSort === order.value, - })); - - const onChooseSortOrder = (id: number) => { - const order = SORT_ORDER_DATA.find((order) => order.id === id); - - if (!order) return; - onChangeSortOrder(order.value); - }; + const preparedData = prepareSortFilterData({data: SORT_ORDER_DATA, value, t}) return ( - <> - - + ); }; diff --git a/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx b/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx index ee8ecd1a5..617b0deba 100644 --- a/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx +++ b/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx @@ -52,8 +52,8 @@ export const TasksFilters = ({ - - + + ); }; diff --git a/src/pages/tasks/tasks/ui/TasksPage/TasksPage.tsx b/src/pages/tasks/tasks/ui/TasksPage/TasksPage.tsx index a883cc645..9a2dae0a5 100644 --- a/src/pages/tasks/tasks/ui/TasksPage/TasksPage.tsx +++ b/src/pages/tasks/tasks/ui/TasksPage/TasksPage.tsx @@ -46,6 +46,8 @@ const TasksPage = () => { category: filters.category, companyId: filters.companyId, canSolve: filters.title ? true : undefined, + sortBy: filters.sortBy, + sortOrder: filters.sortOrder }); const tasksList = data?.data || []; diff --git a/src/shared/libs/app/types.ts b/src/shared/libs/app/types.ts index 0c1c99b2f..913bf2e31 100644 --- a/src/shared/libs/app/types.ts +++ b/src/shared/libs/app/types.ts @@ -1,5 +1,5 @@ export type SortOrder = 'ASC' | 'DESC'; - +export type SortBy = 'name' | 'difficulty' | 'createdAt' | 'updatedAt'; export interface SelectedEntity { id: Id; title?: string; diff --git a/src/shared/ui/SortFilterSection/SortFilterSection.tsx b/src/shared/ui/SortFilterSection/SortFilterSection.tsx new file mode 100644 index 000000000..f9383e9c4 --- /dev/null +++ b/src/shared/ui/SortFilterSection/SortFilterSection.tsx @@ -0,0 +1,36 @@ +import { BaseFilterSection } from "../BaseFilterSection"; + +interface SortFilterItem{ + title: string; + value: T; + id: number; +} + +interface SortFilterSectionProps{ + data: SortFilterItem[]; + title: string; + onChange: (value?: T) => void; + value?: string; +} + +export const SortFilterSection = ({value, onChange, data, title }: SortFilterSectionProps) => { + + const onChoose = (id: number) => { + const item = data.find((order) => order.id === id); + + if (!item) return; + + if (item.value === undefined) return; + + onChange(value === item.value ? undefined : item.value); + + }; + + return ( + + ) +} diff --git a/src/shared/ui/SortFilterSection/index.ts b/src/shared/ui/SortFilterSection/index.ts new file mode 100644 index 000000000..1cfe62e0f --- /dev/null +++ b/src/shared/ui/SortFilterSection/index.ts @@ -0,0 +1,3 @@ +import SortFilterSection from "./SortFilterSection"; + +export {SortFilterSection} \ No newline at end of file From 657e0bb7fd87ea07f490b81b062245c9ef415f71 Mon Sep 17 00:00:00 2001 From: artem <89ufisum@gmail.com> Date: Mon, 25 May 2026 16:50:11 +0300 Subject: [PATCH 4/4] YH-1915: fixed review comments --- src/entities/task/index.ts | 1 + src/entities/task/model/types/filters.ts | 6 ++-- src/entities/task/model/types/task.ts | 5 +-- .../task/filterTasks/model/constants/sort.ts | 12 ------- .../model/helpers/prepareSortFilterData.ts | 31 ---------------- .../ui/SortTasksByField/SortTasksByField.tsx | 35 ++++++++++++------ .../ui/SortTasksByOrder/SortTasksByOrder.tsx | 29 ++++++++++----- .../ui/TaskFilters/TasksFilters.tsx | 20 ++++++++--- .../ui/TasksTablePage/TasksTablePage.tsx | 3 ++ .../tasks/tasks/ui/TasksPage/TasksPage.tsx | 8 ----- src/shared/libs/app/types.ts | 2 +- .../SortFilterSection/SortFilterSection.tsx | 36 ------------------- src/shared/ui/SortFilterSection/index.ts | 3 -- 13 files changed, 71 insertions(+), 120 deletions(-) delete mode 100644 src/features/task/filterTasks/model/constants/sort.ts delete mode 100644 src/features/task/filterTasks/model/helpers/prepareSortFilterData.ts delete mode 100644 src/shared/ui/SortFilterSection/SortFilterSection.tsx delete mode 100644 src/shared/ui/SortFilterSection/index.ts diff --git a/src/entities/task/index.ts b/src/entities/task/index.ts index 42e3108c6..fe2ae6a21 100644 --- a/src/entities/task/index.ts +++ b/src/entities/task/index.ts @@ -12,6 +12,7 @@ export type { TaskSubscriptionLevel, TaskTestCaseResult, TaskTestCaseResultTest, + TaskFilterOrderBy, } from './model/types/task'; export type { TasksFilterParams } from './model/types/filters'; export { taskApiUrls } from './model/constants/task'; diff --git a/src/entities/task/model/types/filters.ts b/src/entities/task/model/types/filters.ts index d156a1d9e..1e2a77577 100644 --- a/src/entities/task/model/types/filters.ts +++ b/src/entities/task/model/types/filters.ts @@ -1,6 +1,6 @@ import { SortOrder } from '@/shared/libs'; -import type { TaskCategoryCode, TaskDifficulty } from './task'; -import { SortBy } from '@/shared/libs/app/types'; + +import type { TaskCategoryCode, TaskDifficulty, TaskFilterOrderBy } from './task'; export interface TasksFilterParams { page?: number; @@ -9,6 +9,6 @@ export interface TasksFilterParams { category?: TaskCategoryCode; langIds?: number[]; companyId?: string; - sortBy?: SortBy; + sortBy?: TaskFilterOrderBy; sortOrder?: SortOrder; } diff --git a/src/entities/task/model/types/task.ts b/src/entities/task/model/types/task.ts index de06003dd..2b0595f89 100644 --- a/src/entities/task/model/types/task.ts +++ b/src/entities/task/model/types/task.ts @@ -2,7 +2,6 @@ import { Response, SortOrder } from '@/shared/libs'; import { Company } from '@/entities/company/@x/task'; import { ProgrammingLanguage } from '@/entities/programmingLanguage/@x/task'; -import { SortBy } from '@/shared/libs/app/types'; export type TaskStatus = 'solved' | 'attempted' | 'not_started'; export type TaskDifficulty = 1 | 2 | 3 | 4 | 5; @@ -95,6 +94,8 @@ export interface TaskCategory { export type GetTaskCategoriesResponse = TaskCategory[]; +export type TaskFilterOrderBy = 'name' | 'difficulty' | 'createdAt' | 'updatedAt'; + export interface GetTasksListParams { page?: number; limit?: number; @@ -106,7 +107,7 @@ export interface GetTasksListParams { langIds?: number[]; isActive?: boolean; search?: string; - sortBy?: SortBy; + sortBy?: TaskFilterOrderBy; sortOrder?: SortOrder; canSolve?: boolean; collectionId?: number; diff --git a/src/features/task/filterTasks/model/constants/sort.ts b/src/features/task/filterTasks/model/constants/sort.ts deleted file mode 100644 index 59e1c2af6..000000000 --- a/src/features/task/filterTasks/model/constants/sort.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Tasks } from '@/shared/config'; - -export const SORT_ORDER_DATA = [ - { id: 1, label: Tasks.SORT_ASC, value: 'ASC' }, - { id: 2, label: Tasks.SORT_DESC, value: 'DESC' }, -] as const; - -export const SORT_FIELDS_DATA = [ - { id: 1, label: Tasks.SORT_FIELDS_NAME, value: 'name' }, - { id: 2, label: Tasks.SORT_DIFFICULTY, value: 'difficulty' }, - { id: 3, label: Tasks.SORT_CREATED_AT, value: 'createdAt' }, -] as const; diff --git a/src/features/task/filterTasks/model/helpers/prepareSortFilterData.ts b/src/features/task/filterTasks/model/helpers/prepareSortFilterData.ts deleted file mode 100644 index 5b39a4556..000000000 --- a/src/features/task/filterTasks/model/helpers/prepareSortFilterData.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { TFunction } from 'i18next'; - -interface SortFilterItem{ - label: string; - value: T; - id: number; -} - -interface FilterDataProps{ - t: TFunction; - value?: T; - data: readonly SortFilterItem[]; -} - -type PreparedSortFilterItem = { - id: number; - value: T; - title: string; - active: boolean; -}; - -export default function prepareSortFilterData({data, value, t}: FilterDataProps): PreparedSortFilterItem[] { - - const preparedData = data.map((item) => ({ - ...item, - title: t(item.label), - active: value === item.value, - })); - - return preparedData -} diff --git a/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx b/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx index c05da73d0..4a397e5ba 100644 --- a/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx +++ b/src/features/task/filterTasks/ui/SortTasksByField/SortTasksByField.tsx @@ -1,25 +1,38 @@ import { useTranslation } from 'react-i18next'; + import { i18Namespace, Tasks } from '@/shared/config'; -import { SORT_FIELDS_DATA } from '../../model/constants/sort'; -import { SortBy } from '@/shared/libs/app/types'; -import { SortFilterSection } from '@/shared/ui/SortFilterSection'; -import prepareSortFilterData from '../../model/helpers/prepareSortFilterData'; +import { BaseFilterItem, BaseFilterSection } from '@/shared/ui/BaseFilterSection'; + +import { TaskFilterOrderBy } from '@/entities/task'; interface SortTasksFieldProps { - value?: SortBy; - onChangeSortField: (field?: SortBy) => void; + selectedOrderBy?: TaskFilterOrderBy; + onChangeSortField: (field?: TaskFilterOrderBy) => void; } -export const SortTasksByField = ({ onChangeSortField, value }: SortTasksFieldProps) => { +export const SortTasksByField = ({ onChangeSortField, selectedOrderBy }: SortTasksFieldProps) => { const { t } = useTranslation(i18Namespace.task); - const preparedData = prepareSortFilterData({data: SORT_FIELDS_DATA, value, t}) + + const field: BaseFilterItem[] = [ + { id: 'name', title: t(Tasks.SORT_FIELDS_NAME) }, + { id: 'difficulty', title: t(Tasks.SORT_DIFFICULTY) }, + { id: 'createdAt', title: t(Tasks.SORT_CREATED_AT) }, + ]; + + const preparedData = field.map((item) => ({ + ...item, + active: selectedOrderBy === item.id, + })); + + const onChangeOrder = (orderBy: TaskFilterOrderBy) => { + onChangeSortField(orderBy === selectedOrderBy ? undefined : orderBy); + }; return ( - ); }; diff --git a/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx b/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx index 099c9ed04..722505007 100644 --- a/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx +++ b/src/features/task/filterTasks/ui/SortTasksByOrder/SortTasksByOrder.tsx @@ -1,25 +1,36 @@ import { useTranslation } from 'react-i18next'; + import { i18Namespace, Tasks } from '@/shared/config'; -import { SORT_ORDER_DATA } from '../../model/constants/sort'; import { SortOrder } from '@/shared/libs'; -import { SortFilterSection } from '@/shared/ui/SortFilterSection'; -import prepareSortFilterData from '../../model/helpers/prepareSortFilterData'; +import { BaseFilterItem, BaseFilterSection } from '@/shared/ui/BaseFilterSection'; interface SortTasksByOrderProps { - value?: SortOrder; + selectedOrder?: SortOrder; onChangeSortOrder: (sortOrder?: SortOrder) => void; } -export const SortTasksByOrder = ({ onChangeSortOrder, value }: SortTasksByOrderProps) => { +export const SortTasksByOrder = ({ onChangeSortOrder, selectedOrder }: SortTasksByOrderProps) => { const { t } = useTranslation(i18Namespace.task); - const preparedData = prepareSortFilterData({data: SORT_ORDER_DATA, value, t}) + + const order: BaseFilterItem[] = [ + { id: 'ASC', title: t(Tasks.SORT_ASC) }, + { id: 'DESC', title: t(Tasks.SORT_DESC) }, + ]; + + const preparedData = order.map((item) => ({ + ...item, + active: selectedOrder === item.id, + })); + + const onChangeOrder = (orderBy: SortOrder) => { + onChangeSortOrder(orderBy === selectedOrder ? undefined : orderBy); + }; return ( - ); }; diff --git a/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx b/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx index 617b0deba..7e3cd6a33 100644 --- a/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx +++ b/src/features/task/filterTasks/ui/TaskFilters/TasksFilters.tsx @@ -14,17 +14,19 @@ import { TaskLanguagesFilter } from '../TaskLanguagesFilter/TaskLanguagesFilter' interface TasksFiltersProps { filters: TasksFilterParams; + withSorting?: boolean; onChangeTitle: (title: TasksFilterParams['title']) => void; onChangeDifficulty: (difficulty?: TasksFilterParams['difficulty']) => void; onChangeLangIds: (langIds?: TasksFilterParams['langIds']) => void; onChangeCategory: (category?: TasksFilterParams['category']) => void; onChangeCompanyId: (companyId?: TasksFilterParams['companyId']) => void; - onChangeSortField: (sortBy?: TasksFilterParams['sortBy']) => void; - onChangeSortOrder: (sortOrder?: TasksFilterParams['sortOrder']) => void; + onChangeSortField?: (sortBy?: TasksFilterParams['sortBy']) => void; + onChangeSortOrder?: (sortOrder?: TasksFilterParams['sortOrder']) => void; } export const TasksFilters = ({ filters, + withSorting, onChangeTitle, onChangeDifficulty, onChangeLangIds, @@ -52,8 +54,18 @@ export const TasksFilters = ({ - - + {withSorting && onChangeSortField && onChangeSortOrder && ( + <> + + + + )} ); }; diff --git a/src/pages/admin/task/tasks/ui/TasksTablePage/TasksTablePage.tsx b/src/pages/admin/task/tasks/ui/TasksTablePage/TasksTablePage.tsx index 23c947b0d..065084b09 100644 --- a/src/pages/admin/task/tasks/ui/TasksTablePage/TasksTablePage.tsx +++ b/src/pages/admin/task/tasks/ui/TasksTablePage/TasksTablePage.tsx @@ -49,6 +49,8 @@ const TasksTablePage = () => { langIds: filters.langIds, category: filters.category, companyId: filters.companyId, + sortBy: filters.sortBy, + sortOrder: filters.sortOrder, }); const onSelectTasks = (ids: SelectedAdminEntities) => { @@ -106,6 +108,7 @@ const TasksTablePage = () => { renderFilter={() => ( { onChangeLangIds, onChangeCategory, onChangeCompanyId, - onChangeSortField, - onChangeSortOrder, onResetFilters, } = useTasksFilters({ page: 1, @@ -46,8 +44,6 @@ const TasksPage = () => { category: filters.category, companyId: filters.companyId, canSolve: filters.title ? true : undefined, - sortBy: filters.sortBy, - sortOrder: filters.sortOrder }); const tasksList = data?.data || []; @@ -59,16 +55,12 @@ const TasksPage = () => { onChangeLangIds={onChangeLangIds} onChangeCategory={onChangeCategory} onChangeCompanyId={onChangeCompanyId} - onChangeSortField={onChangeSortField} - onChangeSortOrder={onChangeSortOrder} filters={{ title: filters.title, difficulty: filters.difficulty, langIds: filters.langIds, category: filters.category, companyId: filters.companyId, - sortBy: filters.sortBy, - sortOrder: filters.sortOrder, }} /> ); diff --git a/src/shared/libs/app/types.ts b/src/shared/libs/app/types.ts index 913bf2e31..0c1c99b2f 100644 --- a/src/shared/libs/app/types.ts +++ b/src/shared/libs/app/types.ts @@ -1,5 +1,5 @@ export type SortOrder = 'ASC' | 'DESC'; -export type SortBy = 'name' | 'difficulty' | 'createdAt' | 'updatedAt'; + export interface SelectedEntity { id: Id; title?: string; diff --git a/src/shared/ui/SortFilterSection/SortFilterSection.tsx b/src/shared/ui/SortFilterSection/SortFilterSection.tsx deleted file mode 100644 index f9383e9c4..000000000 --- a/src/shared/ui/SortFilterSection/SortFilterSection.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { BaseFilterSection } from "../BaseFilterSection"; - -interface SortFilterItem{ - title: string; - value: T; - id: number; -} - -interface SortFilterSectionProps{ - data: SortFilterItem[]; - title: string; - onChange: (value?: T) => void; - value?: string; -} - -export const SortFilterSection = ({value, onChange, data, title }: SortFilterSectionProps) => { - - const onChoose = (id: number) => { - const item = data.find((order) => order.id === id); - - if (!item) return; - - if (item.value === undefined) return; - - onChange(value === item.value ? undefined : item.value); - - }; - - return ( - - ) -} diff --git a/src/shared/ui/SortFilterSection/index.ts b/src/shared/ui/SortFilterSection/index.ts deleted file mode 100644 index 1cfe62e0f..000000000 --- a/src/shared/ui/SortFilterSection/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import SortFilterSection from "./SortFilterSection"; - -export {SortFilterSection} \ No newline at end of file