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