diff --git a/package.json b/package.json
index 1650784..97ceb77 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"@testing-library/user-event": "^14.6.1",
"@types/eslint-plugin-jsx-a11y": "^6",
"@types/jest": "^29.5.14",
+ "@types/lodash": "^4.17.20",
"@types/node": "^22.13.11",
"@types/react": "19.1.8",
"@types/react-redux": "^7.1.34",
diff --git a/src/app/deals/components/Header/Header.tsx b/src/app/deals/components/Header/Header.tsx
new file mode 100644
index 0000000..16219b2
--- /dev/null
+++ b/src/app/deals/components/Header/Header.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import { useEffect } from "react";
+import { useSelector } from "react-redux";
+import { Group } from "@mantine/core";
+import ProjectSelect from "@/components/selects/ProjectSelect/ProjectSelect";
+import {
+ selectProject,
+ setProjects,
+} from "@/lib/features/projects/projectsSlice";
+import { RootState, useAppDispatch } from "@/lib/store";
+
+const Header = () => {
+ const projectsState = useSelector(
+ (state: RootState) => state.projectsState
+ );
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ const mockProjects = [
+ { id: 1, name: "Проект 1" },
+ { id: 2, name: "Проект 2" },
+ { id: 3, name: "Проект 3" },
+ ];
+ dispatch(setProjects(mockProjects));
+ dispatch(selectProject(mockProjects[0]));
+ }, []);
+
+ return (
+
+ value && dispatch(selectProject(value))}
+ />
+
+ );
+};
+
+export default Header;
diff --git a/src/app/deals/page.tsx b/src/app/deals/page.tsx
index bb4fe41..fedcf06 100644
--- a/src/app/deals/page.tsx
+++ b/src/app/deals/page.tsx
@@ -1,4 +1,5 @@
import Boards from "@/app/deals/components/Boards/Boards";
+import Header from "@/app/deals/components/Header/Header";
import PageBlock from "@/components/PageBlock/PageBlock";
import PageContainer from "@/components/PageContainer/PageContainer";
@@ -6,6 +7,7 @@ export default function DealsPage() {
return (
+
diff --git a/src/components/selects/ObjectSelect/ObjectSelect.tsx b/src/components/selects/ObjectSelect/ObjectSelect.tsx
new file mode 100644
index 0000000..e05bb92
--- /dev/null
+++ b/src/components/selects/ObjectSelect/ObjectSelect.tsx
@@ -0,0 +1,110 @@
+import { useEffect, useMemo, useState } from "react";
+import { groupBy, omit } from "lodash";
+import { Select, SelectProps } from "@mantine/core";
+
+interface ObjectWithIdAndName {
+ id: number;
+ name: string;
+}
+
+export type SelectObjectType = T;
+
+type ControlledValueProps = {
+ value: SelectObjectType;
+ onChange: (value: SelectObjectType) => void;
+};
+type CustomLabelAndKeyProps = {
+ getLabelFn: (item: SelectObjectType) => string;
+ getValueFn: (item: SelectObjectType) => string;
+};
+
+type RestProps = {
+ defaultValue?: SelectObjectType;
+ onChange: (value: SelectObjectType) => void;
+ data: SelectObjectType[];
+ groupBy?: (item: SelectObjectType) => string;
+ filterBy?: (item: SelectObjectType) => boolean;
+};
+const defaultGetLabelFn = (item: T): string => {
+ return item.name;
+};
+
+const defaultGetValueFn = (item: T): string => {
+ if (!item) return item;
+ return item.id.toString();
+};
+export type ObjectSelectProps = (RestProps &
+ Partial>) &
+ Omit &
+ (T extends ObjectWithIdAndName
+ ? Partial>
+ : CustomLabelAndKeyProps);
+
+const ObjectSelect = (props: ObjectSelectProps) => {
+ const isControlled = "value" in props;
+ const haveGetValueFn = "getValueFn" in props;
+ const haveGetLabelFn = "getLabelFn" in props;
+ const [internalValue, setInternalValue] = useState<
+ SelectObjectType | undefined
+ >(props.defaultValue);
+
+ const value = isControlled ? props.value : internalValue;
+
+ const getValueFn =
+ (haveGetValueFn && props.getValueFn) || defaultGetValueFn;
+ const getLabelFn =
+ (haveGetLabelFn && props.getLabelFn) || defaultGetLabelFn;
+
+ const data = useMemo(() => {
+ const propsData = props.filterBy
+ ? props.data.filter(props.filterBy)
+ : props.data;
+ if (props.groupBy) {
+ const groupedData = groupBy(propsData, props.groupBy);
+ return Object.entries(groupedData).map(([group, items]) => ({
+ group,
+ items: items.map(item => ({
+ label: getLabelFn(item),
+ value: getValueFn(item),
+ })),
+ }));
+ }
+ return propsData.map(item => ({
+ label: getLabelFn(item),
+ value: getValueFn(item),
+ }));
+ }, [props.data, props.groupBy]);
+
+ const handleOnChange = (event: string | null) => {
+ if (!event) return;
+ const object = props.data.find(item => event === getValueFn(item));
+ if (!object) return;
+ if (isControlled) {
+ props.onChange(object);
+ return;
+ }
+ setInternalValue(object);
+ };
+
+ useEffect(() => {
+ if (isControlled || !internalValue) return;
+ props.onChange(internalValue);
+ }, [internalValue]);
+
+ const restProps = omit(props, [
+ "filterBy",
+ "groupBy",
+ "getValueFn",
+ "getLabelFn",
+ ]);
+ return (
+
+ );
+};
+
+export default ObjectSelect;
diff --git a/src/components/selects/ProjectSelect/ProjectSelect.tsx b/src/components/selects/ProjectSelect/ProjectSelect.tsx
new file mode 100644
index 0000000..f5034e5
--- /dev/null
+++ b/src/components/selects/ProjectSelect/ProjectSelect.tsx
@@ -0,0 +1,26 @@
+"use client";
+
+import { FC } from "react";
+import { ProjectSchema } from "@/types/ProjectSchema";
+import ObjectSelect, { ObjectSelectProps } from "@/components/selects/ObjectSelect/ObjectSelect";
+
+type Props = Omit<
+ ObjectSelectProps,
+ "getLabelFn" | "getValueFn"
+>;
+
+const ProjectSelect: FC = ({ data, ...props }) => {
+ const onClear = () => props.onChange(null);
+
+ return (
+
+ );
+};
+
+export default ProjectSelect;
diff --git a/src/lib/features/projects/projectsSlice.ts b/src/lib/features/projects/projectsSlice.ts
new file mode 100644
index 0000000..c1fd8dd
--- /dev/null
+++ b/src/lib/features/projects/projectsSlice.ts
@@ -0,0 +1,29 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+import { ProjectSchema } from "@/types/ProjectSchema";
+
+interface ProjectsState {
+ projects: ProjectSchema[];
+ selectedProject: ProjectSchema | null;
+}
+
+const initialState: ProjectsState = {
+ projects: [],
+ selectedProject: null,
+};
+
+export const projectsSlice = createSlice({
+ name: "projects",
+ initialState,
+ reducers: {
+ setProjects: (state, action: PayloadAction) => {
+ state.projects = action.payload;
+ },
+ selectProject: (state, action: PayloadAction) => {
+ state.selectedProject = action.payload;
+ },
+ },
+});
+
+export const { setProjects, selectProject } = projectsSlice.actions;
+
+export default projectsSlice.reducer;
diff --git a/src/lib/features/rootReducer.ts b/src/lib/features/rootReducer.ts
index 5f0140b..5266bf1 100644
--- a/src/lib/features/rootReducer.ts
+++ b/src/lib/features/rootReducer.ts
@@ -1,8 +1,10 @@
import { combineReducers } from "@reduxjs/toolkit";
import authReducer from "@/lib/features/auth/authSlice";
+import projectsReducer from "@/lib/features/projects/projectsSlice";
const rootReducer = combineReducers({
auth: authReducer,
+ projectsState: projectsReducer,
});
export default rootReducer;
diff --git a/src/theme.ts b/src/theme.ts
index 2c047ce..0abd2de 100644
--- a/src/theme.ts
+++ b/src/theme.ts
@@ -13,8 +13,8 @@ export const myColor: MantineColorsTuple = [
"#00718c",
];
-const radius = "lg";
-const size = "lg";
+const radius = "md";
+const size = "md";
export const theme = createTheme({
colors: {
diff --git a/src/types/ProjectSchema.ts b/src/types/ProjectSchema.ts
new file mode 100644
index 0000000..369177f
--- /dev/null
+++ b/src/types/ProjectSchema.ts
@@ -0,0 +1,4 @@
+export type ProjectSchema = {
+ id: number;
+ name: string;
+};
diff --git a/yarn.lock b/yarn.lock
index 06a9c83..4155115 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3904,6 +3904,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/lodash@npm:^4.17.20":
+ version: 4.17.20
+ resolution: "@types/lodash@npm:4.17.20"
+ checksum: 10c0/98cdd0faae22cbb8079a01a3bb65aa8f8c41143367486c1cbf5adc83f16c9272a2a5d2c1f541f61d0d73da543c16ee1d21cf2ef86cb93cd0cc0ac3bced6dd88f
+ languageName: node
+ linkType: hard
+
"@types/node@npm:*":
version: 24.1.0
resolution: "@types/node@npm:24.1.0"
@@ -5919,6 +5926,7 @@ __metadata:
"@testing-library/user-event": "npm:^14.6.1"
"@types/eslint-plugin-jsx-a11y": "npm:^6"
"@types/jest": "npm:^29.5.14"
+ "@types/lodash": "npm:^4.17.20"
"@types/node": "npm:^22.13.11"
"@types/react": "npm:19.1.8"
"@types/react-redux": "npm:^7.1.34"