add contests
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
import { SearchInput } from '../../../components/input/SearchInput';
|
||||
import { useAppDispatch } from '../../../redux/hooks';
|
||||
import { setGroupFilter } from '../../../redux/slices/store';
|
||||
|
||||
const Filters = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
return (
|
||||
<div className=" h-[50px] mb-[20px] flex gap-[20px] items-center">
|
||||
<SearchInput onChange={() => {}} placeholder="Поиск группы" />
|
||||
<SearchInput
|
||||
onChange={(v: string) => {
|
||||
dispatch(setGroupFilter(v));
|
||||
}}
|
||||
placeholder="Поиск группы"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { cn } from '../../../lib/cn';
|
||||
import {
|
||||
Book,
|
||||
UserAdd,
|
||||
Edit,
|
||||
EyeClosed,
|
||||
EyeOpen,
|
||||
} from '../../../assets/icons/groups';
|
||||
import { Book, UserAdd, Edit } from '../../../assets/icons/groups';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { GroupInvite, GroupUpdate } from './Groups';
|
||||
import { useAppSelector } from '../../../redux/hooks';
|
||||
|
||||
export interface GroupItemProps {
|
||||
id: number;
|
||||
role: 'menager' | 'member' | 'owner' | 'viewer';
|
||||
visible: boolean;
|
||||
name: string;
|
||||
description: string;
|
||||
setUpdateActive: (value: any) => void;
|
||||
@@ -43,7 +37,6 @@ const IconComponent: React.FC<IconComponentProps> = ({ src, onClick }) => {
|
||||
const GroupItem: React.FC<GroupItemProps> = ({
|
||||
id,
|
||||
name,
|
||||
visible,
|
||||
description,
|
||||
setUpdateGroup,
|
||||
setUpdateActive,
|
||||
@@ -53,6 +46,61 @@ const GroupItem: React.FC<GroupItemProps> = ({
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const filter = useAppSelector(
|
||||
(state) => state.store.group.groupFilter,
|
||||
).toLowerCase();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -66,7 +114,10 @@ const GroupItem: React.FC<GroupItemProps> = ({
|
||||
className="bg-liquid-brightmain rounded-[10px]"
|
||||
/>
|
||||
<div className="grid grid-flow-row grid-rows-[1fr,24px]">
|
||||
<div className="text-[18px] font-bold">{name}</div>
|
||||
<div className="text-[18px] font-bold">
|
||||
{highlightZ(name, filter)}
|
||||
</div>
|
||||
|
||||
<div className=" flex gap-[10px]">
|
||||
{type == 'manage' && (
|
||||
<IconComponent
|
||||
@@ -86,8 +137,6 @@ const GroupItem: React.FC<GroupItemProps> = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{visible == false && <IconComponent src={EyeOpen} />}
|
||||
{visible == true && <IconComponent src={EyeClosed} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { cn } from '../../../lib/cn';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import GroupsBlock from './GroupsBlock';
|
||||
import { setMenuActivePage } from '../../../redux/slices/store';
|
||||
import { fetchMyGroups } from '../../../redux/slices/groups';
|
||||
import { fetchMyGroups, Group } from '../../../redux/slices/groups';
|
||||
import ModalCreate from './ModalCreate';
|
||||
import ModalUpdate from './ModalUpdate';
|
||||
import Filters from './Filter';
|
||||
@@ -45,6 +45,7 @@ const Groups = () => {
|
||||
const groupsError = useAppSelector(
|
||||
(store) => store.groups.fetchMyGroups.error,
|
||||
);
|
||||
const filter = useAppSelector((state) => state.store.group.groupFilter);
|
||||
|
||||
// Берём текущего пользователя
|
||||
const currentUserName = useAppSelector((store) => store.auth.username);
|
||||
@@ -54,17 +55,21 @@ const Groups = () => {
|
||||
dispatch(fetchMyGroups());
|
||||
}, [dispatch]);
|
||||
|
||||
// Разделяем группы
|
||||
const { managedGroups, currentGroups, hiddenGroups } = useMemo(() => {
|
||||
const applyFilter = (groups: Group[], filter: string) => {
|
||||
if (!filter || filter.trim() === '') return groups;
|
||||
const normalized = filter.toLowerCase();
|
||||
|
||||
return groups.filter((g) => g.name.toLowerCase().includes(normalized));
|
||||
};
|
||||
const { managedGroups, currentGroups } = useMemo(() => {
|
||||
if (!groups || !currentUserName) {
|
||||
return { managedGroups: [], currentGroups: [], hiddenGroups: [] };
|
||||
return { managedGroups: [], currentGroups: [] };
|
||||
}
|
||||
|
||||
const managed: typeof groups = [];
|
||||
const current: typeof groups = [];
|
||||
const hidden: typeof groups = []; // пока пустые, без логики
|
||||
|
||||
groups.forEach((group) => {
|
||||
applyFilter(groups, filter).forEach((group) => {
|
||||
const me = group.members.find(
|
||||
(m) => m.username === currentUserName,
|
||||
);
|
||||
@@ -80,9 +85,8 @@ const Groups = () => {
|
||||
return {
|
||||
managedGroups: managed,
|
||||
currentGroups: current,
|
||||
hiddenGroups: hidden,
|
||||
};
|
||||
}, [groups, currentUserName]);
|
||||
}, [groups, currentUserName, filter]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-[calc(100%+250px)] box-border p-[20px] pt-[20px]">
|
||||
@@ -137,16 +141,6 @@ const Groups = () => {
|
||||
setInviteGroup={setInviteGroup}
|
||||
type="member"
|
||||
/>
|
||||
<GroupsBlock
|
||||
className="mb-[20px]"
|
||||
title="Скрытые"
|
||||
groups={hiddenGroups} // пока пусто
|
||||
setUpdateActive={setModalUpdateActive}
|
||||
setUpdateGroup={setUpdateGroup}
|
||||
setInviteActive={setModalInviteActive}
|
||||
setInviteGroup={setInviteGroup}
|
||||
type="member"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,6 @@ const GroupsBlock: FC<GroupsBlockProps> = ({
|
||||
<GroupItem
|
||||
key={i}
|
||||
id={v.id}
|
||||
visible={true}
|
||||
description={v.description}
|
||||
setUpdateActive={setUpdateActive}
|
||||
setUpdateGroup={setUpdateGroup}
|
||||
|
||||
Reference in New Issue
Block a user