feat: projects redux storage and select
This commit is contained in:
42
src/app/deals/components/Header/Header.tsx
Normal file
42
src/app/deals/components/Header/Header.tsx
Normal file
@ -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 (
|
||||
<Group
|
||||
justify={"flex-end"}
|
||||
w={"100%"}>
|
||||
<ProjectSelect
|
||||
data={projectsState.projects}
|
||||
value={projectsState.selectedProject}
|
||||
onChange={value => value && dispatch(selectProject(value))}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@ -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 (
|
||||
<PageContainer>
|
||||
<PageBlock>
|
||||
<Header />
|
||||
<Boards />
|
||||
</PageBlock>
|
||||
</PageContainer>
|
||||
|
||||
110
src/components/selects/ObjectSelect/ObjectSelect.tsx
Normal file
110
src/components/selects/ObjectSelect/ObjectSelect.tsx
Normal file
@ -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> = T;
|
||||
|
||||
type ControlledValueProps<T> = {
|
||||
value: SelectObjectType<T>;
|
||||
onChange: (value: SelectObjectType<T>) => void;
|
||||
};
|
||||
type CustomLabelAndKeyProps<T> = {
|
||||
getLabelFn: (item: SelectObjectType<T>) => string;
|
||||
getValueFn: (item: SelectObjectType<T>) => string;
|
||||
};
|
||||
|
||||
type RestProps<T> = {
|
||||
defaultValue?: SelectObjectType<T>;
|
||||
onChange: (value: SelectObjectType<T>) => void;
|
||||
data: SelectObjectType<T>[];
|
||||
groupBy?: (item: SelectObjectType<T>) => string;
|
||||
filterBy?: (item: SelectObjectType<T>) => boolean;
|
||||
};
|
||||
const defaultGetLabelFn = <T extends { name: string }>(item: T): string => {
|
||||
return item.name;
|
||||
};
|
||||
|
||||
const defaultGetValueFn = <T extends { id: number }>(item: T): string => {
|
||||
if (!item) return item;
|
||||
return item.id.toString();
|
||||
};
|
||||
export type ObjectSelectProps<T> = (RestProps<T> &
|
||||
Partial<ControlledValueProps<T>>) &
|
||||
Omit<SelectProps, "value" | "onChange" | "data"> &
|
||||
(T extends ObjectWithIdAndName
|
||||
? Partial<CustomLabelAndKeyProps<T>>
|
||||
: CustomLabelAndKeyProps<T>);
|
||||
|
||||
const ObjectSelect = <T,>(props: ObjectSelectProps<T>) => {
|
||||
const isControlled = "value" in props;
|
||||
const haveGetValueFn = "getValueFn" in props;
|
||||
const haveGetLabelFn = "getLabelFn" in props;
|
||||
const [internalValue, setInternalValue] = useState<
|
||||
SelectObjectType<T> | 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 (
|
||||
<Select
|
||||
{...restProps}
|
||||
value={value && getValueFn(value)}
|
||||
onChange={handleOnChange}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ObjectSelect;
|
||||
26
src/components/selects/ProjectSelect/ProjectSelect.tsx
Normal file
26
src/components/selects/ProjectSelect/ProjectSelect.tsx
Normal file
@ -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<ProjectSchema | null>,
|
||||
"getLabelFn" | "getValueFn"
|
||||
>;
|
||||
|
||||
const ProjectSelect: FC<Props> = ({ data, ...props }) => {
|
||||
const onClear = () => props.onChange(null);
|
||||
|
||||
return (
|
||||
<ObjectSelect
|
||||
data={data}
|
||||
searchable
|
||||
placeholder={"Выберите проект"}
|
||||
onClear={onClear}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectSelect;
|
||||
29
src/lib/features/projects/projectsSlice.ts
Normal file
29
src/lib/features/projects/projectsSlice.ts
Normal file
@ -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<ProjectSchema[]>) => {
|
||||
state.projects = action.payload;
|
||||
},
|
||||
selectProject: (state, action: PayloadAction<ProjectSchema>) => {
|
||||
state.selectedProject = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setProjects, selectProject } = projectsSlice.actions;
|
||||
|
||||
export default projectsSlice.reducer;
|
||||
@ -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;
|
||||
|
||||
@ -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: {
|
||||
|
||||
4
src/types/ProjectSchema.ts
Normal file
4
src/types/ProjectSchema.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type ProjectSchema = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
Reference in New Issue
Block a user