Добавление страницы information (#41)
This commit is contained in:
@ -95,7 +95,7 @@
|
||||
"@typescript-eslint/no-shadow": "warn",
|
||||
"@typescript-eslint/no-namespace": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error"],
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"array-bracket-spacing": ["warn", "never"],
|
||||
"block-spacing": ["warn", "never"],
|
||||
|
||||
@ -89,8 +89,8 @@ module.exports = {
|
||||
'^_types(.*)$': '<rootDir>/src/core/types$1',
|
||||
'^_utils(.*)$': '<rootDir>/src/core/utils$1',
|
||||
'^_enums(.*)$': '<rootDir>/src/core/enums$1',
|
||||
'^_referers(.*)$': '<rootDir>/src/core/referers$1',
|
||||
'^_pages(.*)$': '<rootDir>/src/pages$1',
|
||||
'^_referers(.*)$': '<rootDir>/src/referers$1',
|
||||
},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
import {AppBar, createStyles, Fab, IconButton, makeStyles, Theme, Toolbar} from '@material-ui/core';
|
||||
import React, {memo} from 'react';
|
||||
import Slide from '@material-ui/core/Slide';
|
||||
import MoreIcon from '@material-ui/icons/MoreVert';
|
||||
import AddIcon from '@material-ui/icons/Add';
|
||||
import MoveToInboxIcon from '@material-ui/icons/MoveToInbox';
|
||||
import CalendarTodayIcon from '@material-ui/icons/CalendarToday';
|
||||
import ListAltIcon from '@material-ui/icons/ListAlt';
|
||||
|
||||
type Props = {
|
||||
trigger: boolean;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
iconRight: {
|
||||
@ -29,51 +34,52 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
}),
|
||||
);
|
||||
|
||||
const BothMenu: React.FC = () => {
|
||||
const BothMenu: React.FC<Props> = ({trigger}) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
position="fixed"
|
||||
color="primary"
|
||||
className={classes.appBar}
|
||||
>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
className={classes.iconRight}
|
||||
edge="start"
|
||||
color="inherit"
|
||||
>
|
||||
<MoveToInboxIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
edge="end"
|
||||
color="inherit"
|
||||
>
|
||||
<ListAltIcon />
|
||||
</IconButton>
|
||||
<Fab
|
||||
color="secondary"
|
||||
className={classes.fabButton}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
<div className={classes.grow} />
|
||||
<IconButton
|
||||
className={classes.iconRight}
|
||||
edge="start"
|
||||
color="inherit"
|
||||
>
|
||||
<CalendarTodayIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
edge="end"
|
||||
color="inherit"
|
||||
>
|
||||
<MoreIcon />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Slide appear={false} direction="up" in={!trigger}>
|
||||
<AppBar
|
||||
color="primary"
|
||||
className={classes.appBar}
|
||||
>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
className={classes.iconRight}
|
||||
edge="start"
|
||||
color="inherit"
|
||||
>
|
||||
<MoveToInboxIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
edge="end"
|
||||
color="inherit"
|
||||
>
|
||||
<ListAltIcon />
|
||||
</IconButton>
|
||||
<Fab
|
||||
color="secondary"
|
||||
className={classes.fabButton}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
<div className={classes.grow} />
|
||||
<IconButton
|
||||
className={classes.iconRight}
|
||||
edge="start"
|
||||
color="inherit"
|
||||
>
|
||||
<CalendarTodayIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
edge="end"
|
||||
color="inherit"
|
||||
>
|
||||
<MoreIcon />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</Slide>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React, {Fragment, memo} from 'react';
|
||||
import {Route, Switch} from 'react-router-dom';
|
||||
import {Container, createStyles, makeStyles, useScrollTrigger} from '@material-ui/core';
|
||||
|
||||
import mainPageRouter from '_pages/main/routing';
|
||||
import chaosBoxPageRouter from '_pages/chaos-box/routing';
|
||||
import calendarPageRouter from '_pages/calendar/routing';
|
||||
@ -13,26 +15,40 @@ import TopMenu from '../top-menu';
|
||||
import './Page.scss';
|
||||
import BothMenu from '../both-menu';
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
container: {
|
||||
height: '100hv',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
const trigger = useScrollTrigger();
|
||||
return (
|
||||
<Fragment>
|
||||
<TopMenu />
|
||||
<div>
|
||||
<Switch>
|
||||
{mainPageRouter}
|
||||
{chaosBoxPageRouter}
|
||||
{calendarPageRouter}
|
||||
{informationPageRouter}
|
||||
{projectsPageRouter}
|
||||
{settingsPageRouter}
|
||||
{signInPageRouter}
|
||||
{tagsPageRouter}
|
||||
<Route>
|
||||
<NotFoundPage />
|
||||
</Route>
|
||||
</Switch>
|
||||
<div className={classes.container}>
|
||||
<TopMenu trigger={trigger} />
|
||||
<Container>
|
||||
<Switch>
|
||||
{mainPageRouter}
|
||||
{chaosBoxPageRouter}
|
||||
{calendarPageRouter}
|
||||
{informationPageRouter}
|
||||
{projectsPageRouter}
|
||||
{settingsPageRouter}
|
||||
{signInPageRouter}
|
||||
{tagsPageRouter}
|
||||
<Route>
|
||||
<NotFoundPage />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Container>
|
||||
<BothMenu trigger={trigger} />
|
||||
</div>
|
||||
<BothMenu />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import {useHistory} from 'react-router-dom';
|
||||
import {createStyles, makeStyles} from '@material-ui/core/styles';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import Slide from '@material-ui/core/Slide';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';
|
||||
@ -13,13 +14,14 @@ import {PAGE_TITLE} from '_consts/common';
|
||||
import {usePageType} from '_hooks/usePageType';
|
||||
import {buildPath} from '_utils/buildPath';
|
||||
|
||||
type Props = {
|
||||
trigger: boolean;
|
||||
}
|
||||
|
||||
const NO_NAME_AVATAR = 'https://d.newsweek.com/en/full/425257/02-10-putin-economy.jpg';
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
title: {
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
@ -28,7 +30,7 @@ const useStyles = makeStyles(() =>
|
||||
}),
|
||||
);
|
||||
|
||||
const TopMenu: React.FC = () => {
|
||||
const TopMenu: React.FC<Props> = ({trigger}) => {
|
||||
const classes = useStyles();
|
||||
const pageType = usePageType();
|
||||
const history = useHistory();
|
||||
@ -39,8 +41,8 @@ const TopMenu: React.FC = () => {
|
||||
|
||||
const title = PAGE_TITLE[pageType];
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AppBar position="static">
|
||||
<Slide appear={false} direction="down" in={!trigger}>
|
||||
<AppBar position="sticky">
|
||||
<Toolbar>
|
||||
{pageType === PageType.Main && (
|
||||
<IconButton
|
||||
@ -70,7 +72,7 @@ const TopMenu: React.FC = () => {
|
||||
<Avatar src={NO_NAME_AVATAR} />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</div>
|
||||
</Slide>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -4,10 +4,14 @@ import {HashRouter} from 'react-router-dom';
|
||||
import App from './components/page';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<HashRouter >
|
||||
<App />
|
||||
</HashRouter>
|
||||
</React.StrictMode>,
|
||||
/*
|
||||
* Выключаем стрикт мод, пока не починят
|
||||
* https://github.com/mui-org/material-ui/issues/13394
|
||||
*/
|
||||
// <React.StrictMode>
|
||||
<HashRouter >
|
||||
<App />
|
||||
</HashRouter>,
|
||||
// </React.StrictMode>
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {makeApi} from '_utils/makeApi';
|
||||
import {http} from '../infrastructure/Http';
|
||||
import {makeApi} from '../utils/makeApi';
|
||||
|
||||
type User = {
|
||||
id: number;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {PageType} from '../enums/common';
|
||||
import {PageType} from '_enums/common';
|
||||
|
||||
export const ROUTES = {
|
||||
MAIN: '/',
|
||||
|
||||
@ -19,3 +19,9 @@ export enum PageType {
|
||||
Settings = 'settings',
|
||||
SigIn = 'sign-in',
|
||||
}
|
||||
|
||||
export enum Icon {
|
||||
AcUnit = 'acUnit',
|
||||
Apple = 'apple',
|
||||
Apartment = 'apartment',
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {useMemo} from 'react';
|
||||
import {useLocation} from 'react-router-dom';
|
||||
import {getPageType} from '../utils/common';
|
||||
import {getPageType} from '_utils/common';
|
||||
|
||||
export const usePageType = () => {
|
||||
const location = useLocation();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {useMemo} from 'react';
|
||||
import {useParams as useReactParams} from 'react-router-dom';
|
||||
import {getParamsFromUrl} from '../utils/getParamFromUrl';
|
||||
import {QueryParsers} from '../utils/getQueryFromUrl';
|
||||
import {getParamsFromUrl} from '_utils/getParamFromUrl';
|
||||
import {QueryParsers} from '_utils/getQueryFromUrl';
|
||||
|
||||
export function useParams<T extends Record<string, unknown>>(paramParsers: QueryParsers<T>) {
|
||||
const params = useReactParams<Record<keyof T, string>>();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {useMemo} from 'react';
|
||||
import {useLocation} from 'react-router-dom';
|
||||
import {getQueryFromUrl, QueryParsers} from '../utils/getQueryFromUrl';
|
||||
import {getQueryFromUrl, QueryParsers} from '_utils/getQueryFromUrl';
|
||||
|
||||
export function useQuery<T extends Record<string, unknown>>(queryParsers: QueryParsers<T>): T {
|
||||
const {search} = useLocation();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {v4} from 'uuid';
|
||||
import {TaskStatus} from '../enums/common';
|
||||
import {Task} from '../types/common';
|
||||
import {createService} from '../utils/createService';
|
||||
import {TaskStatus} from '_enums/common';
|
||||
import {Task} from '_types/common';
|
||||
import {createService} from '_utils/createService';
|
||||
import {makeLocalStorageService} from './LocalStorageService';
|
||||
|
||||
const TASK_STORAGE_NAME = 'FYB_TASK_STORAGE';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {FolderType, TaskStatus} from '../enums/common';
|
||||
import {FolderType, Icon, TaskStatus} from '_enums/common';
|
||||
|
||||
export type Task = {
|
||||
/**
|
||||
@ -10,6 +10,7 @@ export type Task = {
|
||||
created_at: string;
|
||||
start_at?: string;
|
||||
end_at?: string;
|
||||
icon?: Icon;
|
||||
/**
|
||||
* Контекст выполнения, теги
|
||||
*/
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {PageType} from '../../enums/common';
|
||||
import {PageType} from '_enums/common';
|
||||
import {buildPath, BuildPathOptions} from '../buildPath';
|
||||
|
||||
describe('buildPath', () => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {PageType} from '../../enums/common';
|
||||
import {PageType} from '_enums/common';
|
||||
import {getParamsFromUrl} from '../getParamFromUrl';
|
||||
import {QueryParsers} from '../getQueryFromUrl';
|
||||
import {booleanParser, numberParser, stringParser} from '../queryParsers';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {PageType} from '../../enums/common';
|
||||
import {PageType} from '_enums/common';
|
||||
import {getQueryFromUrl, QueryParsers} from '../getQueryFromUrl';
|
||||
import {arrayParser, booleanParser, numberParser, stringParser} from '../queryParsers';
|
||||
|
||||
|
||||
49
src/core/utils/__test__/makeTreeList.test.ts
Normal file
49
src/core/utils/__test__/makeTreeList.test.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {Folder, Task} from '_types/common';
|
||||
import {FolderType, TaskStatus} from '_enums/common';
|
||||
import {makeMap, makeTreeList, ROOT_TREE} from '../makeTreeList';
|
||||
|
||||
const Folder_1 = {id: '1', name: '1', type: FolderType.Information};
|
||||
const Folder_2 = {id: '2', name: '2', type: FolderType.Information, folder: '999'};
|
||||
const Folder_3 = {id: '3', name: '3', type: FolderType.Information};
|
||||
const Folder_4 = {id: '4', name: '4', type: FolderType.Information, folder: '1'};
|
||||
|
||||
const Task_1 = {id: '11', created_at: '', status: TaskStatus.Progress};
|
||||
const Task_2 = {id: '22', created_at: '', status: TaskStatus.Progress, folder: '1'};
|
||||
const Task_3 = {id: '33', created_at: '', status: TaskStatus.Progress, folder: '4'};
|
||||
const Task_4 = {id: '44', created_at: '', status: TaskStatus.Progress, folder: '999'};
|
||||
|
||||
const folders: Folder[] = [Folder_1, Folder_2, Folder_3, Folder_4];
|
||||
const tasks: Task[] = [Task_1, Task_2, Task_3, Task_4];
|
||||
|
||||
describe('makeTreeList', () => {
|
||||
it('Создает хеш-таблицу', () => {
|
||||
expect(makeMap(folders, tasks)).toEqual({
|
||||
[ROOT_TREE]: [Folder_1, Folder_2, Folder_3, Task_1, Task_4],
|
||||
'1': [Folder_4, Task_2],
|
||||
'2': [],
|
||||
'3': [],
|
||||
'4': [Task_3],
|
||||
});
|
||||
});
|
||||
|
||||
it('Создает дерево', () => {
|
||||
expect(makeTreeList(folders, tasks)).toEqual([
|
||||
{
|
||||
data: Folder_1,
|
||||
children: [
|
||||
{
|
||||
data: Folder_4,
|
||||
children: [
|
||||
{data: Task_3},
|
||||
],
|
||||
},
|
||||
{data: Task_2},
|
||||
],
|
||||
},
|
||||
{data: Folder_2},
|
||||
{data: Folder_3},
|
||||
{data: Task_1},
|
||||
{data: Task_4},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
import {decode, ParsedUrlQueryInput, stringify} from 'querystring';
|
||||
import {PageType} from '../enums/common';
|
||||
import {PageType} from '_enums/common';
|
||||
|
||||
export type BuildPathOptions = {
|
||||
pageType: PageType;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {PageType} from '../enums/common';
|
||||
import {isPageType} from '../referers/common';
|
||||
import {PageType} from '_enums/common';
|
||||
import {isPageType} from '_referers/common';
|
||||
|
||||
export const numberToString = (num: number): string => num.toString();
|
||||
|
||||
|
||||
78
src/core/utils/makeTreeList.ts
Normal file
78
src/core/utils/makeTreeList.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import {sortBy} from 'lodash';
|
||||
import {isNotEmpty} from '_referers/common';
|
||||
import {Folder, Task} from '_types/common';
|
||||
|
||||
export type FolderItem = {
|
||||
data: Folder;
|
||||
children?: TreeList;
|
||||
};
|
||||
|
||||
export type TaskItem = {
|
||||
data: Task;
|
||||
};
|
||||
|
||||
export type TreeList = Array<FolderItem | TaskItem>;
|
||||
|
||||
export type FolderTaskMap = Record<string, Array<(Folder | Task)> | undefined>;
|
||||
|
||||
export const isFolderItem = (item: FolderItem | TaskItem): item is FolderItem => (
|
||||
'type' in item.data
|
||||
);
|
||||
export const isTaskItem = (item: FolderItem | TaskItem): item is TaskItem => (
|
||||
'status' in item.data
|
||||
);
|
||||
|
||||
export const ROOT_TREE = '__root__';
|
||||
|
||||
export const makeMap = (folders: Folder[], tasks: Task[]) => {
|
||||
const INIT_MAP: FolderTaskMap = {
|
||||
[ROOT_TREE]: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Собираем хеш-таблицу существующих папок с предзаполненным root элементом
|
||||
*/
|
||||
const map = folders.reduce<FolderTaskMap>((acc, folder) => ({
|
||||
...acc,
|
||||
[folder.id]: [],
|
||||
}), INIT_MAP);
|
||||
|
||||
/**
|
||||
* Наполняем хеш-таблицу мутабельно для скорости. Если у папки или таски folder.id,
|
||||
* которого уже не существует, то кладем их в root
|
||||
*/
|
||||
folders.forEach(folder => {
|
||||
const parent = folder.folder ?? ROOT_TREE;
|
||||
(map[parent] ?? map[ROOT_TREE])?.push(folder);
|
||||
});
|
||||
|
||||
tasks.forEach(task => {
|
||||
const parent = task.folder ?? ROOT_TREE;
|
||||
(map[parent] ?? map[ROOT_TREE])?.push(task);
|
||||
});
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
const makeTree = (parentId: string, map: FolderTaskMap): TreeList | undefined => {
|
||||
const list = map[parentId];
|
||||
|
||||
if (isNotEmpty(list)) {
|
||||
return sortBy(list, ['type']).map(item => {
|
||||
if ('type' in item) {
|
||||
const children = makeTree(item.id, map);
|
||||
return {
|
||||
...(children ? {children} : {}),
|
||||
data: item,
|
||||
};
|
||||
}
|
||||
return {
|
||||
data: item,
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const makeTreeList = (folders: Folder[], tasks: Task[]) => {
|
||||
return makeTree(ROOT_TREE, makeMap(folders, tasks));
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
import ru from 'convert-layout/ru';
|
||||
import {isEmpty, isNotEmpty} from '../referers/common';
|
||||
import {isEmpty, isNotEmpty} from '_referers/common';
|
||||
|
||||
export function performTextSearch<T, K extends keyof T>(items: T[], searchText: string, searchProperties: K[]) {
|
||||
if (isEmpty(items) || isEmpty(searchText)) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {isNotEmpty} from '../referers/common';
|
||||
import {isNotEmpty} from '_referers/common';
|
||||
|
||||
export function toRequestParamValue<T>(val: T): T;
|
||||
export function toRequestParamValue<T>(val?: T): Undefinable<T>;
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
import {Collapse, ListItemIcon, ListItem, ListItemText} from '@material-ui/core';
|
||||
import ExpandLess from '@material-ui/icons/ExpandLess';
|
||||
import ExpandMore from '@material-ui/icons/ExpandMore';
|
||||
import FolderIcon from '@material-ui/icons/Folder';
|
||||
import React, {FC, Fragment, memo, useCallback, useMemo} from 'react';
|
||||
import {isNotEmpty} from '_referers/common';
|
||||
import {FolderItem} from '_utils/makeTreeList';
|
||||
import InfoList from './InfoList';
|
||||
|
||||
type Props = {
|
||||
item: FolderItem;
|
||||
className: string;
|
||||
space: number;
|
||||
openFolderIds: string[];
|
||||
onFolderClick: (folderId: string) => void;
|
||||
};
|
||||
|
||||
const FolderListItem: FC<Props> = ({item, className, space, openFolderIds, onFolderClick}) => {
|
||||
const isOpen = useMemo(() => {
|
||||
return openFolderIds.some(id => id === item.data.id);
|
||||
}, [openFolderIds, item.data.id]);
|
||||
|
||||
const isNotEmptyChildren = useMemo(() => isNotEmpty(item.children), [item.children]);
|
||||
|
||||
const handleItemClick = useCallback(() => {
|
||||
onFolderClick(item.data.id);
|
||||
}, [onFolderClick, item.data.id]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ListItem
|
||||
className={className}
|
||||
button
|
||||
onClick={handleItemClick}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<FolderIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={item.data.name} />
|
||||
{isNotEmptyChildren && (
|
||||
isOpen ? <ExpandLess /> : <ExpandMore />
|
||||
)}
|
||||
</ListItem>
|
||||
{isNotEmptyChildren && (
|
||||
<Collapse in={isOpen} timeout="auto" unmountOnExit>
|
||||
<InfoList list={item.children} space={space + 1} />
|
||||
</Collapse>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(FolderListItem);
|
||||
60
src/pages/information/components/info-list/InfoList.tsx
Normal file
60
src/pages/information/components/info-list/InfoList.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import {createStyles, makeStyles, Theme, List} from '@material-ui/core';
|
||||
import {xor} from 'lodash';
|
||||
import React, {Fragment, memo, useCallback, useState} from 'react';
|
||||
import {isFolderItem, isTaskItem, TreeList} from '_utils/makeTreeList';
|
||||
import FolderListItem from './FolderListItem';
|
||||
import TaskListItem from './TaskListItem';
|
||||
|
||||
type Props = {
|
||||
list: Undefinable<TreeList>;
|
||||
space: number;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles<Theme, {space: number}>((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
width: '100%',
|
||||
maxWidth: 360,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
nested: ({space}) => ({
|
||||
paddingLeft: theme.spacing(4 * space),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const InfoList: React.FC<Props> = ({list, space}) => {
|
||||
const [openFolderIds, setOpenFolder] = useState<string[]>([]);
|
||||
|
||||
const handleSetOpenFolder = useCallback((id: string) => {
|
||||
setOpenFolder(xor(openFolderIds, [id]));
|
||||
}, [setOpenFolder, openFolderIds]);
|
||||
|
||||
const classes = useStyles({space});
|
||||
|
||||
return (
|
||||
<List component="div" className={classes.root}>
|
||||
{list?.map(item => (
|
||||
<Fragment key={item.data.id}>
|
||||
{isFolderItem(item) && (
|
||||
<FolderListItem
|
||||
item={item}
|
||||
className={classes.nested}
|
||||
space={space}
|
||||
openFolderIds={openFolderIds}
|
||||
onFolderClick={handleSetOpenFolder}
|
||||
/>
|
||||
)}
|
||||
{isTaskItem(item) && (
|
||||
<TaskListItem
|
||||
item={item}
|
||||
className={classes.nested}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(InfoList);
|
||||
22
src/pages/information/components/info-list/TaskListItem.tsx
Normal file
22
src/pages/information/components/info-list/TaskListItem.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import {ListItemIcon, ListItem, ListItemText} from '@material-ui/core';
|
||||
import AssignmentLateIcon from '@material-ui/icons/AssignmentLate';
|
||||
import React, {FC, memo} from 'react';
|
||||
import {TaskItem} from '_utils/makeTreeList';
|
||||
|
||||
type Props = {
|
||||
item: TaskItem;
|
||||
className: string;
|
||||
};
|
||||
|
||||
const TaskListItem: FC<Props> = ({item, className}) => {
|
||||
return (
|
||||
<ListItem button className={className}>
|
||||
<ListItemIcon>
|
||||
<AssignmentLateIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={item.data.title} />
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(TaskListItem);
|
||||
1
src/pages/information/components/info-list/index.ts
Normal file
1
src/pages/information/components/info-list/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {default} from './InfoList';
|
||||
@ -1,8 +1,18 @@
|
||||
import React, {memo} from 'react';
|
||||
import React, {Fragment, memo} from 'react';
|
||||
import {isNotEmpty} from '_referers/common';
|
||||
import {makeTreeList} from '_utils/makeTreeList';
|
||||
import {FolderList, TaskList} from '../../consts';
|
||||
import InfoList from '../info-list';
|
||||
|
||||
const tree = makeTreeList(FolderList, TaskList);
|
||||
|
||||
const Page: React.FC = () => {
|
||||
return (
|
||||
<div>information</div>
|
||||
<Fragment>
|
||||
{isNotEmpty(tree) && (
|
||||
<InfoList list={tree} space={1} />
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
32
src/pages/information/consts.ts
Normal file
32
src/pages/information/consts.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import {v4} from 'uuid';
|
||||
import {FolderType, Icon, TaskStatus} from '_enums/common';
|
||||
import {Folder, Tag, Task} from '_types/common';
|
||||
|
||||
// Псевдоданные
|
||||
export const TagList: Tag[] = [
|
||||
{id: '33', name: 'Tag', color: '#2fc036'},
|
||||
{id: '66', name: 'Tag', color: '#2fc036'},
|
||||
{id: '77', name: 'Tag', color: '#2fc036'},
|
||||
{id: '22', name: 'Tag', color: '#2fc036'},
|
||||
];
|
||||
|
||||
export const FolderList: Folder[] = [
|
||||
{id: '4', name: 'Folder 1', type: FolderType.Information},
|
||||
{id: '6', name: 'Folder 34', type: FolderType.Information},
|
||||
{id: '7', name: 'Folder ffd', type: FolderType.Information},
|
||||
{id: '73', name: 'Folder ffd', type: FolderType.Information, folder: '7'},
|
||||
];
|
||||
|
||||
export const TaskList: Task[] = [
|
||||
{id: v4(), title: 'Title number 1', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress, folder: '4'},
|
||||
{id: v4(), title: 'Title number 2', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress, folder: '4'},
|
||||
{id: v4(), title: 'Title number 3', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress, folder: '6'},
|
||||
{id: v4(), title: 'Title number 4', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress, tags: ['33', '77']},
|
||||
{id: v4(), title: 'Title number 5', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress},
|
||||
{id: v4(), title: 'Title number 6', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress},
|
||||
{id: v4(), title: 'Title number 7', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress, folder: '4'},
|
||||
{id: v4(), title: 'Title number 8', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress, folder: '4'},
|
||||
{id: v4(), title: 'Title number 9', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress, folder: '7'},
|
||||
{id: v4(), title: 'Title number 10', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress, folder: '7'},
|
||||
{id: v4(), title: 'Title number 11', body: 'Description', created_at: '2019-01-01T13:00', icon: Icon.Apple, status: TaskStatus.Progress, folder: '73'},
|
||||
];
|
||||
@ -29,8 +29,8 @@
|
||||
"_types/*": ["./src/core/types/*"],
|
||||
"_utils/*": ["./src/core/utils/*"],
|
||||
"_enums/*": ["./src/core/enums/*"],
|
||||
"_referers/*": ["./src/core/referers/*"],
|
||||
"_pages/*": ["./src/pages/*"],
|
||||
"_referers/*": ["./src/referers/*"],
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
|
||||
@ -38,8 +38,8 @@ module.exports = {
|
||||
_services: path.resolve(__dirname, 'src/core/services/'),
|
||||
_utils: path.resolve(__dirname, 'src/core/utils/'),
|
||||
_enums: path.resolve(__dirname, 'src/core/enums/'),
|
||||
_referers: path.resolve(__dirname, 'src/core/referers/'),
|
||||
_pages: path.resolve(__dirname, 'src/pages/'),
|
||||
_referers: path.resolve(__dirname, 'src/referers/'),
|
||||
}
|
||||
},
|
||||
optimization: {
|
||||
|
||||
Reference in New Issue
Block a user