add filters

This commit is contained in:
Виталий Лавшонок
2025-12-10 00:04:20 +03:00
parent 14d2f5cbf1
commit 02de330034
23 changed files with 639 additions and 212 deletions

View File

@@ -3,7 +3,10 @@ import { SecondaryButton } from '../../../components/button/SecondaryButton';
import { cn } from '../../../lib/cn';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import ContestsBlock from './ContestsBlock';
import { setMenuActivePage } from '../../../redux/slices/store';
import {
setContestsNameFilter,
setMenuActivePage,
} from '../../../redux/slices/store';
import {
fetchContests,
fetchMyContests,
@@ -21,6 +24,10 @@ const Contests = () => {
(state) => state.contests.fetchContests,
);
const nameFilter = useAppSelector(
(state) => state.store.contests.filterName,
);
// При загрузке страницы — выставляем активную вкладку и подгружаем контесты
useEffect(() => {
dispatch(setMenuActivePage('contests'));
@@ -49,7 +56,11 @@ const Contests = () => {
/>
</div>
<Filters />
<Filters
onChangeName={(v: string) => {
dispatch(setContestsNameFilter(v));
}}
/>
{status == 'loading' && (
<div className="text-liquid-white p-4">
Загрузка контестов...
@@ -60,18 +71,30 @@ const Contests = () => {
<ContestsBlock
className="mb-[20px]"
title="Текущие"
contests={contests.filter(
(c) => c.scheduleType != 'AlwaysOpen',
)}
contests={contests
.filter((v) =>
v.name
.toLocaleLowerCase()
.includes(
nameFilter.toLocaleLowerCase(),
),
)
.filter((c) => c.scheduleType != 'AlwaysOpen')}
type="upcoming"
/>
<ContestsBlock
className="mb-[20px]"
title="Постоянные"
contests={contests.filter(
(c) => c.scheduleType == 'AlwaysOpen',
)}
contests={contests
.filter((v) =>
v.name
.toLocaleLowerCase()
.includes(
nameFilter.toLocaleLowerCase(),
),
)
.filter((c) => c.scheduleType == 'AlwaysOpen')}
type="past"
/>
</>

View File

@@ -1,49 +1,18 @@
import { FilterDropDown, FilterItem } from '../../../components/filters/Filter';
import { SorterDropDown } from '../../../components/filters/Sorter';
import { FC } from 'react';
import { SearchInput } from '../../../components/input/SearchInput';
const Filters = () => {
const items: FilterItem[] = [
{ text: 'React', value: 'react' },
{ text: 'Vue', value: 'vue' },
{ text: 'Angular', value: 'angular' },
{ text: 'Svelte', value: 'svelte' },
{ text: 'Next.js', value: 'next' },
{ text: 'Nuxt', value: 'nuxt' },
{ text: 'Solid', value: 'solid' },
{ text: 'Qwik', value: 'qwik' },
];
interface ContestFiltersProps {
onChangeName: (value: string) => void;
}
const Filters: FC<ContestFiltersProps> = ({ onChangeName }) => {
return (
<div className=" h-[50px] mb-[20px] flex gap-[20px] items-center">
<SearchInput onChange={() => {}} placeholder="Поиск задачи" />
<SorterDropDown
items={[
{
value: '1',
text: 'Сложность',
},
{
value: '2',
text: 'Дата создания',
},
{
value: '3',
text: 'ID',
},
]}
onChange={(v) => {
v;
}}
/>
<FilterDropDown
items={items}
defaultState={[]}
onChange={(values) => {
values;
<SearchInput
onChange={(value: string) => {
onChangeName(value);
}}
placeholder="Поиск контеста"
/>
</div>
);

View File

@@ -14,7 +14,7 @@ import { NumberInput } from '../../../components/input/NumberInput';
import {
DropDownList,
DropDownListItem,
} from '../../../components/filters/DropDownList';
} from '../../../components/input/DropDownList';
import DateInput from '../../../components/input/DateInput';
import { cn } from '../../../lib/cn';

View File

@@ -83,6 +83,61 @@ const PastContestItem: React.FC<PastContestItemProps> = ({
(state) => state.contests.fetchParticipating,
);
const nameFilter = useAppSelector(
(state) => state.store.contests.filterName,
);
const highlightZ = (name: string, filter: string) => {
if (!filter) return name;
const s = filter.toLowerCase();
const t = name.toLowerCase();
const n = t.length;
const m = s.length;
const mark = Array(n).fill(false);
// Проходимся с конца и ставим отметки
for (let i = n - 1; i >= 0; i--) {
if (i + m <= n && t.slice(i, i + m) === s) {
for (let j = i; j < i + m; j++) {
if (mark[j]) break;
mark[j] = true;
}
}
}
// === Формируем единые жёлтые блоки ===
const result: any[] = [];
let i = 0;
while (i < n) {
if (!mark[i]) {
// обычный символ
result.push(name[i]);
i++;
} else {
// начинаем жёлтый блок
let j = i;
while (j < n && mark[j]) j++;
const chunk = name.slice(i, j);
result.push(
<span
key={i}
className="bg-yellow-400 text-black rounded px-1"
>
{chunk}
</span>,
);
i = j;
}
}
return result;
};
useEffect(() => {
setRole(
(() => {
@@ -119,7 +174,9 @@ const PastContestItem: React.FC<PastContestItemProps> = ({
navigate(`/contest/${contestId}?${params}`);
}}
>
<div className="text-left font-bold text-[18px]">{name}</div>
<div className="text-left font-bold text-[18px]">
{highlightZ(name, nameFilter)}
</div>
<div className="text-center text-liquid-brightmain font-normal flex items-center justify-center">
{username}
</div>

View File

@@ -98,6 +98,61 @@ const UpcoingContestItem: React.FC<UpcoingContestItemProps> = ({
(state) => state.contests.fetchParticipating,
);
const nameFilter = useAppSelector(
(state) => state.store.contests.filterName,
);
const highlightZ = (name: string, filter: string) => {
if (!filter) return name;
const s = filter.toLowerCase();
const t = name.toLowerCase();
const n = t.length;
const m = s.length;
const mark = Array(n).fill(false);
// Проходимся с конца и ставим отметки
for (let i = n - 1; i >= 0; i--) {
if (i + m <= n && t.slice(i, i + m) === s) {
for (let j = i; j < i + m; j++) {
if (mark[j]) break;
mark[j] = true;
}
}
}
// === Формируем единые жёлтые блоки ===
const result: any[] = [];
let i = 0;
while (i < n) {
if (!mark[i]) {
// обычный символ
result.push(name[i]);
i++;
} else {
// начинаем жёлтый блок
let j = i;
while (j < n && mark[j]) j++;
const chunk = name.slice(i, j);
result.push(
<span
key={i}
className="bg-yellow-400 text-black rounded px-1"
>
{chunk}
</span>,
);
i = j;
}
}
return result;
};
const query = useQuery();
const username = query.get('username') ?? myname ?? '';
@@ -146,7 +201,9 @@ const UpcoingContestItem: React.FC<UpcoingContestItemProps> = ({
navigate(`/contest/${contestId}?${params}`);
}}
>
<div className="text-left font-bold text-[18px]">{name}</div>
<div className="text-left font-bold text-[18px]">
{highlightZ(name, nameFilter)}
</div>
<div className="text-center text-liquid-brightmain font-normal flex items-center justify-center">
{username}
</div>