Таблицы, добавление, редактирование, удаление, удаление выделенного.

This commit is contained in:
dmitryisav 2022-12-20 11:28:44 +03:00
parent 1e6688e4cf
commit 123a6fde18
48 changed files with 4842 additions and 90 deletions

2
.gitignore vendored
View File

@ -4,6 +4,8 @@
/node_modules
/.pnp
.pnp.js
.idea
# testing
/coverage

View File

@ -24,5 +24,29 @@
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
</code_scheme>
</component>

159
.idea/workspace.xml generated
View File

@ -1,12 +1,169 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="3c1f5459-12ab-4427-89ae-be4c0e341eb6" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/src/Components/Common/Button/Button.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Common/Button/button.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Common/Checkbox/Checkbox.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Common/Checkbox/checkbox.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Common/Checkbox/selected.svg" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Common/Input/Input.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Common/Input/input.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Common/Loader/Loader.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Common/Loader/loader.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Common/Select/Select.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Common/Select/select.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Form/Form.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Form/form.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Modal/Modal.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Modal/modal.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Sidebar/Sidebar.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Sidebar/sidebar.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/Table.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/TableCell/TableCell.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/TableCell/tableCell.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/TableEmpty/TableEmpty.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/TableEmpty/tableEmpty.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/TableHead/TableHead.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/TableHead/tableHead.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/TableHeadItem/TableHeadItem.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/TableHeadItem/tableHeadItem.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/TableRow/TableRow.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/TableRow/tableRow.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Table/table.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Template/TableCompany/TableCompany.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Template/TableCompany/tableCompany.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Template/TableWorkers/TableWorkers.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Components/Template/TableWorkers/tableWorkers.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/helpers/array.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/mock/mockTable.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/store.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/tableCompanySlice.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/tableWorkersSlice.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/codeStyles/Project.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/codeStyles/Project.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/App.css" beforeDir="false" afterPath="$PROJECT_DIR$/src/App.scss" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/App.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/App.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/index.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/index.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/logo.svg" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="JavaScript File" />
<option value="SCSS File" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectId" id="2J35rc74r3VojkFeH4ngbLhp6ge" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showExcludedFiles" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/../IRI" />
<property name="list.type.of.created.stylesheet" value="SCSS" />
<property name="node.js.detected.package.eslint" value="true" />
<property name="node.js.detected.package.standard" value="true" />
<property name="node.js.path.for.package.eslint" value="project" />
<property name="node.js.path.for.package.standard" value="project" />
<property name="node.js.selected.package.eslint" value="(autodetect)" />
<property name="node.js.selected.package.standard" value="" />
<property name="nodejs_package_manager_path" value="npm" />
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src/Components/Table" />
</key>
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src/Components/Common" />
<recent name="$PROJECT_DIR$/src/Components/Table" />
<recent name="$PROJECT_DIR$/src" />
<recent name="$PROJECT_DIR$/src/Components/Common/Checkbox" />
<recent name="$PROJECT_DIR$/src/Components" />
</key>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="3c1f5459-12ab-4427-89ae-be4c0e341eb6" name="Default Changelist" comment="" />
<created>1671294125199</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1671294125199</updated>
<workItem from="1671294127387" duration="5598000" />
<workItem from="1671299757432" duration="6341000" />
<workItem from="1671365091146" duration="14875000" />
<workItem from="1671388144305" duration="15952000" />
<workItem from="1671448137631" duration="41117000" />
<workItem from="1671524387598" duration="275000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="WindowStateProjectService">
<state x="555" y="181" width="800" height="671" key="#ESLint" timestamp="1671294406686">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="555" y="181" width="800" height="671" key="#ESLint/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671294406686" />
<state x="627" y="325" width="655" height="382" key="#com.intellij.fileTypes.FileTypeChooser" timestamp="1671390741373">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="627" y="325" width="655" height="382" key="#com.intellij.fileTypes.FileTypeChooser/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671390741373" />
<state x="693" y="269" width="524" height="494" key="#com.intellij.refactoring.safeDelete.UnsafeUsagesDialog" timestamp="1671375945131">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="693" y="269" width="524" height="494" key="#com.intellij.refactoring.safeDelete.UnsafeUsagesDialog/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671375945131" />
<state x="561" y="118" width="788" height="796" key="CommitChangelistDialog2" timestamp="1671524816158">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="561" y="118" width="788" height="796" key="CommitChangelistDialog2/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671524816158" />
<state x="743" y="277" width="424" height="478" key="FileChooserDialogImpl" timestamp="1671486080118">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="743" y="277" width="424" height="478" key="FileChooserDialogImpl/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671486080118" />
<state width="1899" height="276" key="GridCell.Tab.0.bottom" timestamp="1671299466029">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1899" height="276" key="GridCell.Tab.0.bottom/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671299466029" />
<state width="1899" height="276" key="GridCell.Tab.0.center" timestamp="1671299466029">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1899" height="276" key="GridCell.Tab.0.center/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671299466029" />
<state width="1899" height="276" key="GridCell.Tab.0.left" timestamp="1671299466029">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1899" height="276" key="GridCell.Tab.0.left/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671299466029" />
<state width="1899" height="276" key="GridCell.Tab.0.right" timestamp="1671299466029">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1899" height="276" key="GridCell.Tab.0.right/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671299466029" />
<state x="624" y="238" key="run.anything.popup" timestamp="1671472733814">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="624" y="238" key="run.anything.popup/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671472733814" />
<state x="623" y="228" width="672" height="678" key="search.everywhere.popup" timestamp="1671389871175">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="623" y="228" width="672" height="678" key="search.everywhere.popup/0.0.1920.1040/0.0.1920.1040@0.0.1920.1040" timestamp="1671389871175" />
</component>
</project>

3020
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,12 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"node-sass": "^7.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"redux-devtools-extension": "^2.13.9",
"uuid": "^9.0.0",
"web-vitals": "^2.1.4"
},
"scripts": {
@ -34,5 +37,9 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@reduxjs/toolkit": "^1.9.1",
"react-redux": "^8.0.5"
}
}

View File

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -1,23 +1,76 @@
import logo from './logo.svg';
import './App.css';
import React, {useEffect, useState} from "react";
import {useDispatch, useSelector} from 'react-redux'
import TableWorkers from "./Components/Template/TableWorkers/TableWorkers";
import TableCompany from "./Components/Template/TableCompany/TableCompany";
import {companyRecords} from "./mock/mockTable";
import {companyWorkers} from "./mock/mockTable";
import {tableCompanySlice} from "./store/tableCompanySlice";
import {tableWorkersSlice} from "./store/tableWorkersSlice";
import './App.scss'
function App() {
const [isLoading, setIsLoading] = useState(true);
const dispatch = useDispatch();
const tableCompanyData = useSelector((state) => state.table);
const tableWorkersData = useSelector((state) => state.tableWorkers);
const {
setInitialCompany, calculateWorkersInCompany
} = tableCompanySlice.actions;
const {setInitialWorkers, showCompanyWorkers, setColorActiveWorker,} = tableWorkersSlice.actions;
useEffect(() => {
dispatch(setInitialCompany(companyRecords));
setIsLoading(false);
dispatch(setInitialWorkers(companyWorkers))
}, []);
useEffect(() => {
if (tableCompanyData.data.data) {
dispatch(showCompanyWorkers(tableCompanyData.activeCompanyId))
}
}, [tableCompanyData.activeCompanyId]);
useEffect(() => {
dispatch(setColorActiveWorker(tableWorkersData.activeWorkersId));
}, [tableWorkersData.activeWorkersId]);
useEffect(() => {
console.log(tableWorkersData.workers)
dispatch(calculateWorkersInCompany(tableWorkersData.workers))
}, [tableWorkersData.workers]);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<TableCompany
tableData={tableCompanyData}
isLoading={isLoading}
/>
<TableWorkers
companyData={tableCompanyData.data.data}
tableData={tableWorkersData}
/>
</div>
);
}

10
src/App.scss Normal file
View File

@ -0,0 +1,10 @@
.App {
text-align: center;
display: flex;
background: #131826;
}
body {
background: #131826;
height: 100vh;
}

View File

@ -0,0 +1,13 @@
import React from 'react';
import './button.scss';
export const Button = ({text, onClick, styles = {}, containerStyles = {}, children, disabled = false}) => {
return (
<div className='button' style={containerStyles}>
<button onClick={onClick} style={styles} disabled={!!disabled}>
{text || children}
</button>
</div>
)
};

View File

@ -0,0 +1,61 @@
.button {
display: flex;
justify-content: center;
button {
cursor: pointer;
height: 100%;
width: 100%;
padding: 10px 15px;
display: flex;
justify-content: center;
align-items: center;
background: #219FE6;
border-radius: 4px;
font-style: normal;
font-weight: normal;
font-size: 13px;
line-height: 18px;
color: #FFFFFF;
outline: none;
border: none;
transition: all ease 0.3s;
position: relative;
&:hover {
background: #0F8ACF;
box-shadow: 0 0 33px #276B92;
}
&:disabled {
background: #3F3F3F;
color: #ACACAC;
box-shadow: none;
cursor: alias;
}
&:focus {
box-shadow: 0 0 33px #276B92, inset 0 0 10px rgba(14, 89, 131, 0.43);
}
&:active {
background-color: #0F8ACF;
&:before {
background: none;
border: 1px solid #0F8ACF;
content: "";
display: block;
border-radius: 6px;
position: absolute;
top: -4px;
left: -4px;
right: -4px;
bottom: -4px;
pointer-events: none;
}
}
}
}

View File

@ -0,0 +1,18 @@
import React from "react";
import selectedIcon from './selected.svg'
import './checkbox.scss'
export const Checkbox = ({ isActive, onChange = () => {} }) => {
return (
<div onClick={onChange} className='checkbox__input'>
{isActive && <img src={selectedIcon} alt="iconChecked"/>}
</div>
)
};

View File

@ -0,0 +1,10 @@
.checkbox__input {
width: 16px;
height: 16px;
border: 1px solid #898A98;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.66659 0.333328C2.78253 0.333328 1.93468 0.684518 1.30956 1.30964C0.684441 1.93476 0.333252 2.78261 0.333252 3.66666V10.3333C0.333252 11.2174 0.684441 12.0652 1.30956 12.6904C1.93468 13.3155 2.78253 13.6667 3.66659 13.6667H10.3333C11.2173 13.6667 12.0652 13.3155 12.6903 12.6904C13.3154 12.0652 13.6666 11.2174 13.6666 10.3333V3.66666C13.6666 2.78261 13.3154 1.93476 12.6903 1.30964C12.0652 0.684518 11.2173 0.333328 10.3333 0.333328H3.66659ZM9.48659 6.12266C9.54647 6.05875 9.59318 5.98367 9.62404 5.90171C9.65491 5.81975 9.66933 5.73251 9.66648 5.64498C9.66364 5.55744 9.64358 5.47133 9.60745 5.39154C9.57132 5.31176 9.51983 5.23988 9.45592 5.18C9.39201 5.12011 9.31693 5.0734 9.23497 5.04254C9.15301 5.01167 9.06577 4.99725 8.97823 5.0001C8.8907 5.00294 8.80458 5.023 8.7248 5.05913C8.64502 5.09526 8.57314 5.14675 8.51325 5.21066L6.45792 7.404L5.44258 6.502C5.30956 6.39141 5.13875 6.33686 4.96625 6.34987C4.79375 6.36289 4.63306 6.44245 4.51813 6.57174C4.4032 6.70104 4.34303 6.86994 4.35033 7.04278C4.35763 7.21562 4.43183 7.37885 4.55725 7.49799L6.05725 8.83133C6.18704 8.94662 6.35669 9.00674 6.53011 8.99889C6.70354 8.99105 6.86706 8.91587 6.98592 8.78933L9.48592 6.12266H9.48659Z" fill="#219FE6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,39 @@
import React, { useEffect, useState } from 'react';
import './input.scss';
export const Input = ({ required, name, type = 'text', value, placeholder, isNotChangeable=false, onChange=()=>{}, onChangeValue=(value)=>value, fieldData={},}) => {
const [inputValue, setInputValue] = useState('');
useEffect( ()=>{
setInputValue(value)
}, [value]);
return (
<div className={`customInput ${required && !inputValue ? ' customInput--required':''}`}>
<div className='customInput__field'>
<input
name={name}
className='customInput__input'
type={type}
placeholder={placeholder}
disabled={isNotChangeable}
value={inputValue}
{...fieldData}
onChange={e => {
const res = onChangeValue(e.target.value);
setInputValue(res);
fieldData.onChange && fieldData.onChange({...e, target: {...e.target, value: res}})
}}
onBlur={(e)=>{
onChange(inputValue);
fieldData.onBlur && fieldData.onBlur(e)
}}
/>
</div>
</div>
)
};

View File

@ -0,0 +1,50 @@
.customInput {
width: 100%;
position: relative;
&--required {
border: 1px solid #b21;
border-radius: 4px;
}
&__field {
display: flex;
align-items: center;
background: #2D313D;
border-radius: 4px;
}
&__input {
width: 100%;
height: 40px;
background: #2D313D;
border-radius: 4px;
padding: 0 10px;
font-size: 16px;
line-height: 22px;
border: 1px solid #2D313D;
color: #898A98;
transition: all ease 0.3s;
&:hover {
background: #3A3F4F;
}
&:focus {
color: #fff;
text-decoration: none;
outline: none;
border: 1px solid #219FE6
}
&:disabled {
background: #3F3F3F;
}
}
}

View File

@ -0,0 +1,9 @@
import React from 'react';
import './loader.scss';
export const Loader = () => {
return (
<div className='loader'/>
)
};

View File

@ -0,0 +1,49 @@
.loader {
--clock-color: #007affbb;
--white-color: #fff;
--clock-width: 2.5rem;
--clock-radius: calc(var(--clock-width) / 2);
--clock-minute-length: calc(var(--clock-width) * 0.4);
--clock-hour-length: calc(var(--clock-width) * 0.2);
--clock-thickness: 0.2rem;
margin-left: 12px;
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: var(--clock-width);
height: var(--clock-width);
border: 2px solid var(--clock-color);
border-radius: 50%;
&::before,
&::after {
position: absolute;
content: "";
top: calc(var(--clock-radius) * 0.25);
width: var(--clock-thickness);
background: var(--clock-color);
border-radius: 10px;
transform-origin: center calc(100% - calc(var(--clock-thickness) / 2));
animation: spin infinite linear;
}
&::before {
height: var(--clock-minute-length);
animation-duration: 2s;
}
&::after {
top: calc(var(--clock-radius) * 0.25 + var(--clock-hour-length));
height: var(--clock-hour-length);
animation-duration: 15s;
}
}
@keyframes spin {
to {
transform: rotate(1turn);
}
}

View File

@ -0,0 +1,20 @@
import React from "react";
import './select.scss'
export const Select = ({options, onChange}) => {
return (
<select className='select' defaultValue={'DEFAULT'} onChange={(e) => {
onChange(e.target.value)
}}>
<option disabled value='DEFAULT'>Выберите компанию</option>
{
options.map((item, key) => {
return <option key={key} value={item[Object.keys(item)[0]]}>{item[Object.keys(item)[1]]}</option>
})
}
</select>
)
};

View File

@ -0,0 +1,10 @@
.select {
background: #2D313D;
border: none;
width: 100%;
height: 42px;
font-size: 14px;
margin-bottom: 10px;
color: white;
outline: none;
}

View File

@ -0,0 +1,68 @@
import React, {useState} from "react";
import {Input} from "../Common/Input/Input";
import {Button} from "../Common/Button/Button";
import './form.scss'
import {Select} from "../Common/Select/Select";
export const Form = ({
data, config, onClick = () => {
}
}) => {
const [formData, setFormData] = useState(data);
const inputs = config.filter((item) => item.editable === true);
const inputsIdRender = inputs.map((item) => item.columnId);
return (
<form className='form'>
{
Object.entries(formData || {}).map((item, key) => {
const rowName = inputs.find((i) =>
i.columnId === item[0]
);
if (inputsIdRender.indexOf(item[0]) + 1) {
if (Array.isArray(item[1])) {
return <div key={key} className='form__row'>
<label>{rowName.columnName}</label>
<Select options={item[1]}
onChange={(e) =>
setFormData({
...formData,
[item[0]]: [...item[1]].map((i) => {
if (i.companyId === parseInt(e)) {
return {...i, active: true}
} else {
return {...i, active: false}
}
})
})}/>
</div>
}
return (
<div className='form__row' key={key}>
<label>{rowName.columnName}</label>
<Input name={item[0]}
onChange={(e) => setFormData({...formData, [item[0]]: e})} type="text"
isNotChangeable={!rowName.editable}
value={item[1]}/>
</div>
)
}
}
)}
<div className='form__buttons'>
<Button onClick={(e) => {
e.preventDefault();
onClick(formData);
}}>Сохранить</Button>
</div>
</form>
)
};

View File

@ -0,0 +1,18 @@
.form {
background: #1c2a41;
&__row {
label {
color: white;
}
margin-bottom: 10px;
}
&__buttons {
width: 100%;
position: relative;
}
}

View File

@ -0,0 +1,18 @@
import React from "react";
import ReactDom from 'react-dom'
import './modal.scss'
export default React.memo(function Modal({isOpen, children, onClose = ()=> {}}) {
const body = document.querySelector('body');
return ReactDom.createPortal(
<div className={`modal ${isOpen ? ' active' : ''}`} onClick={() => onClose()}>
<div className='modal__content' onClick={(e) => e.stopPropagation()}>
<button className='modal__close' onClick={() => onClose()}>Х</button>
{children}
</div>
</div>, body)
});

View File

@ -0,0 +1,29 @@
.modal {
width: 100vw;
height: 100vh;
position: fixed;
background: rgba(0, 0, 0, 0.4);
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
transform: scale(0);
&__content {
background-color: #1c2a41;
padding: 20px;
border-radius: 5px;
}
&__close {
position: absolute;
top: 10px;
right: 10px;
}
}
.modal.active {
transform: scale(1)
}

View File

@ -0,0 +1,15 @@
import React from "react";
import './sidebar.scss'
export const Sidebar = () => {
return (
<div>
</div>
)
};

View File

View File

@ -0,0 +1,57 @@
import React from "react";
import {TableRow} from "./TableRow/TableRow";
import {TableHead} from "./TableHead/TableHead";
import {TableEmpty} from "./TableEmpty/TableEmpty";
import {Button} from "../Common/Button/Button";
import {Loader} from "../Common/Loader/Loader";
import './table.scss'
export const Table = ({groups, tableConfig, data, emptyTable, fetchMore, isFetching, hasMore, isLoading, onChange}) => {
if(isLoading) {
return (
<div className='table'>
<TableEmpty {...emptyTable} text='Загрузка' loader={Loader} />
</div>
)
}
return (
<>
<div className='table'>
{
data && data.length > 0 ?
<div className='table__data'>
<TableHead tableConfig={tableConfig} groups={groups}/>
<div className='table__rowList'>
{
data.map((row, key) => {
return (
<TableRow row={row} onChange={onChange} key={`TableRow${key}`} tableConfig={tableConfig}/>
)
})
}
</div>
{ hasMore && <div className='table__load'>
{
isFetching
? <Button><Loader /></Button>
: <Button onClick={()=>fetchMore()}>Load more</Button>
}
</div> }
</div>
: <TableEmpty {...emptyTable}/>
}
</div>
</>
)
};

View File

@ -0,0 +1,14 @@
import React from 'react';
import './tableCell.scss';
export const TableCell = ({ row, tableConfig, onChange, item, columnWidth, renderRowItem = (item => item), isGroupEnd, onClick = ()=> {}}) => {
return (
<div onClick={onClick} className={`tableCell${isGroupEnd ? " tableCell--last" : ""}`}
style={{minWidth: columnWidth}}>
{
renderRowItem(item, row, tableConfig, onChange)
}
</div>
)
};

View File

@ -0,0 +1,36 @@
.tableCell {
flex: 1;
position: relative;
width: 100%;
font-style: normal;
font-weight: normal;
font-size: 13px;
line-height: 18px;
display: flex;
justify-content: center;
align-items: center;
color: #FFFFFF;
height: 100%;
&--last {
border-right: 1px solid #282D3A;
}
&>p {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 100%;
}
&:after {
content: '';
position: absolute;
width: 100%;
bottom: 0;
left: 0;
border-bottom: 1px solid #282D3A;
}
}

View File

@ -0,0 +1,26 @@
import React from 'react';
import './tableEmpty.scss';
export const TableEmpty = ({icon, text, button, link, loader: Loader}) => {
return (
<div className='tableEmpty'>
{ icon && <div className='tableEmpty__icon'>
<img src={icon} alt={'icon'}/>
</div> }
<div className='tableEmpty__text'>
<p>{text}</p>
{ Loader && <Loader /> }
</div>
{/*{button && <div className='tableEmpty__button'>*/}
{/* {*/}
{/* button.link*/}
{/* ? <Link to={button.link}>{button.text}</Link>*/}
{/* : <Button {...button} />*/}
{/* }*/}
{/*</div>}*/}
</div>
)
};

View File

@ -0,0 +1,64 @@
.tableEmpty {
width: 100%;
height: max-content;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: 25px 0;
background: #131826;
@media (max-width: 480px) {
background-color: transparent;
}
&__icon {
margin-top: 4px;
@media (max-width: 480px) {
margin-top: 130px;
}
}
&__text {
display: flex;
align-items: center;
width: 446px;
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 22px;
text-align: center;
justify-content: center;
color: #FFFFFF;
@media (max-width: 480px) {
margin-top: 27px;
width: 258px;
}
}
&__button {
margin-top: 30px;
a {
display: flex;
justify-content: center;
align-items: center;
padding: 10px 15px;
width: 135px;
height: 42px;
background: #219FE6;
color: #FFFFFF;
text-decoration: none;
border-radius: 4px;
}
@media (max-width: 480px) {
margin-top: 20px;
}
}
}

View File

@ -0,0 +1,37 @@
import React from 'react';
import {TableHeadItem} from "../TableHeadItem/TableHeadItem";
import './tableHead.scss';
export const TableHead = ({groups, tableConfig, info}) => {
return (
<div className='tableHead'>
{
groups
? groups.map((groupItem, key) => {
return (
<div key={key} className='tableHead__group'
style={{minWidth: groupItem.width, flex: groupItem.columnCount}}>
<TableHeadItem item={{columnName: groupItem.id}} info={groupItem.info} isGroup={true}/>
<div className='tableHead__groupItems'>
{
tableConfig.filter(headItem => headItem.groupId === groupItem.id).map((headItem, key) => {
return (
<TableHeadItem item={headItem} key={key} info={info && info[headItem.columnId]}/>
)
})
}
</div>
</div>
)
})
: tableConfig.map((headItem, key) => {
return (
<TableHeadItem key={`tableHeadItem${key}`} item={headItem}/>
)
})
}
</div>
)
};

View File

@ -0,0 +1,44 @@
.tableHead {
display: flex;
background-color: #131826;
position: relative;
&:after {
content: '';
position: absolute;
width: 100%;
bottom: 0;
left: 0;
border-bottom: 1px solid #282D3A;
}
// &__item {
// height: 38px;
// position: relative;
// flex: 1;
// &:after {
// content: '';
// position: absolute;
// width: 100%;
// bottom: 0;
// left: 0;
// border-bottom: 1px solid #282D3A;
// }
// }
&__groupItems {
display: flex;
flex-direction: row;
}
&__group {
display: flex;
flex-direction: column;
border-right: 1px solid #282D3A;
&:last-child {
border-right: none;
}
}
}

View File

@ -0,0 +1,29 @@
import React from 'react';
import './tableHeadItem.scss';
export const TableHeadItem = ({item, isGroup}) => {
return (
<div className={`tableHeadItem${isGroup ? ' tableHeadItem--group' : ''}`}
style={{minWidth: item.columnWidth, width: '100%'}}>
<div className='tableHeadItem__text'>
{
typeof item.columnName === "function" ?
item.columnName() : item.columnName
}
</div>
{
item.info
&& (
<div className='tableHeadItem__info'>
<div className='tableHeadItem__message'>
{item.info.message}
</div>
{item.info.message ? 'i' : ''}
</div>
)
}
</div>
)
};

View File

@ -0,0 +1,116 @@
.tableHeadItem {
position: relative;
display: flex;
justify-content: center;
align-items: center;
height: 38px;
flex: 1;
//padding: 10px 0;
&:after {
content: '';
position: absolute;
width: 100%;
bottom: 0;
left: 0;
border-bottom: 1px solid #282D3A;
}
&--group {
height: 50px;
padding: 18px 27px;
display: flex;
align-items: center;
background: rgba(0, 0, 0, 0.2);
//border-left: 1px solid #282D3A;
.tableHeadItem__text {
font-size: 15px;
line-height: 20px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.tableHeadItem__info {
width: 14px;
height: 14px;
margin-left: 8px;
display: flex;
justify-content: center;
align-items: center;
font-size: 8px;
&--highlighted {
background-color: #FFFFFF;
box-shadow: 0 0 20px #FFFFFF;
}
}
}
&__text {
font-style: normal;
font-weight: normal;
font-size: 13px;
display: flex;
justify-content: center;
align-items: center;
line-height: 18px;
display: flex;
justify-content: center;
align-items: center;
color: #898A98;
}
&__info {
margin-left: 5px;
position: relative;
width: 14px;
height: 14px;
background-color: #898A98;
border-radius: 50%;
font-size: 8px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
&:hover {
background-color: #FFFFFF;
box-shadow: 0 0 20px #FFFFFF;
.tableHeadItem__message {
display: initial;
}
}
}
&__message {
position: absolute;
display: none;
width: max-content;
max-width: 420px;
padding: 10px;
background-color: #131826;
color: #FFFFFF;
font-size: 12px;
z-index: 2;
top: initial;
bottom: 16px;
left: initial;
right: -50%;
transform: translate(20%, 0);
}
}

View File

@ -0,0 +1,28 @@
import React from 'react';
import {TableCell} from "../TableCell/TableCell";
import './tableRow.scss';
export const TableRow = ({tableConfig, row, onChange}) => {
return (
<div className={`tableRow ${row.active ? "tableRow--active" : ""}`}>
{
tableConfig.map(({columnId, columnWidth, renderRowItem, isGroupEnd}, key) => {
return (
<TableCell
key={`cell${row.id}${key}`}
row={row}
onChange={onChange}
item={row[columnId]}
columnWidth={columnWidth}
renderRowItem={renderRowItem}
isGroupEnd={isGroupEnd}
tableConfig={tableConfig}
/>
)
})
}
</div>
)
};

View File

@ -0,0 +1,28 @@
.tableRow {
height: 60px;
width: 100%;
background-color: #131826;
display: flex;
box-sizing: border-box;
position: relative;
&:after {
content: '';
position: absolute;
width: 100%;
bottom: 0;
left: 0;
border-bottom: 1px solid #282D3A;
}
// &__cell {
// height: 100%;
// border-bottom: 1px solid #282D3A;
// flex: 1;
// }
}
.tableRow--active {
background-color: #23324f;
}

View File

@ -0,0 +1,22 @@
.table {
border-radius: 4px;
width: 100%;
&__data {
background-color: #131826;
overflow-x: auto;
overflow-y: auto;
max-height: 100vh;
}
&__head {
border-bottom: 1px solid #282D3A;
}
&__row {
border-bottom: 1px solid #282D3A;
}
&__load {
margin-top: 15px;
}
}

View File

@ -0,0 +1,169 @@
import React, {useState} from "react";
import {useDispatch} from "react-redux";
import {v4 as uuidv4} from "uuid";
import {Checkbox} from "../../Common/Checkbox/Checkbox";
import {Button} from "../../Common/Button/Button";
import {Table} from "../../Table/Table";
import {Form} from "../../Form/Form";
import Modal from "../../Modal/Modal";
import {tableCompanySlice} from "../../../store/tableCompanySlice";
import {tableWorkersSlice} from "../../../store/tableWorkersSlice";
import './tableCompany.scss'
export default React.memo(function TableCompany({isLoading, tableData}) {
const [modalData, setModalData] = useState({});
const [modalOpen, setModalOpen] = useState(false);
const [modalCreateOpen, setModalCreateOpen] = useState(false);
const dispatch = useDispatch();
const {
setActiveCompany, setActiveAllCompany, removeSelectedCompany,
setUnActiveWorkers, removeCompany, editCompanyData, createCompany
} = tableCompanySlice.actions;
const {removeWorkersByIdCompany} = tableWorkersSlice.actions;
const companyTableConfig = [
{
columnId: 'check',
columnName: () =>
<Checkbox
isActive={tableData.data.data.length === tableData.activeCompanyId.length}
onChange={() => {
dispatch(setUnActiveWorkers());
dispatch(setActiveAllCompany());
}}
/>,
columnWidth: '40px',
renderRowItem: (item, row) =>
<Checkbox
id={row.id}
isActive={tableData.activeCompanyId.includes(row.id)}
onChange={() => {
dispatch(setUnActiveWorkers());
dispatch(setActiveCompany(row.id))
}}
/>,
},
{
columnId: 'company_name',
columnName: 'Название компании',
columnWidth: '110px',
editable: true,
},
{
columnId: 'number_of_employees',
columnName: 'Количество сотрудников',
columnWidth: '90px',
editable: false,
},
{
columnId: 'address',
columnName: 'Адрес',
columnWidth: '110px',
editable: true,
},
{
columnId: "handlers",
columnName: () => <Button
styles={{padding: '1px', background: '#881818', maxWidth: '98px'}}
onClick={() => {
dispatch(removeSelectedCompany(tableData.activeCompanyId))
}}
>Удалить выбранные</Button>,
renderRowItem: (item, row) =>
<div>
<Button styles={{padding: '3px', marginBottom: '3px'}} onClick={() => {
dispatch(removeCompany(row.id));
dispatch(removeWorkersByIdCompany(row.id))
}
}> Удалить
</Button>
<Button styles={{padding: '3px'}} onClick={
() => {
setModalData({...row});
setModalOpen(true);
}
}>Редактировать
</Button>
</div>
}
];
const createCompanyConfig = [
{
columnId: 'id',
columnName: 'Фамилия',
},
{
columnId: 'company_name',
columnName: 'Название компании',
editable: true,
},
{
columnId: 'address',
columnName: 'Адрес',
editable: true,
},
];
return (
<div className='tableWrapper'>
<div className='tableWrapper__title'>
<Button styles={{background: "#3dda0c87", maxWidth: '200px'}} onClick={() => {
setModalCreateOpen(true)
}}>
Добавить компанию
</Button>
</div>
<Table
// hasMore={tableData.has_more_pages}
// fetchMore={() => {
// setCursor(tableData.next_page_cursor_param);
// setPushTableData(true)
// }}
isLoading={isLoading}
tableConfig={companyTableConfig}
emptyTable={{
text: "Пусто...",
}}
data={tableData.data.data}
/>
{modalOpen &&
<Modal isOpen={modalOpen} onClose={() => setModalOpen(false)}>
<Form data={modalData} onClick={(formData) => {
dispatch(editCompanyData(formData));
setModalOpen(false)
}} config={companyTableConfig}/>
</Modal>}
{modalCreateOpen &&
<Modal isOpen={modalCreateOpen} onClose={() => {
setModalCreateOpen(false)
}}>
<Form data={{
company_name: '',
address: "",
}} onClick={(formData) => {
const newFormData = {
...formData,
id: Math.floor(Math.random() * 10000),
};
dispatch(createCompany(newFormData));
setModalCreateOpen(false)
}} config={createCompanyConfig}/>
</Modal>
}
</div>
)
})

View File

@ -0,0 +1,173 @@
import React, {useState} from "react";
import {useDispatch} from "react-redux";
import {v4 as uuidv4} from 'uuid';
import {Table} from "../../Table/Table";
import {Button} from "../../Common/Button/Button";
import {Checkbox} from "../../Common/Checkbox/Checkbox";
import {Form} from "../../Form/Form";
import Modal from "../../Modal/Modal";
import {tableWorkersSlice} from "../../../store/tableWorkersSlice";
import './tableWorkers.scss'
export default React.memo(function TableWorkers({tableData, companyData}) {
const [modalData, setModalData] = useState({});
const [modalEditOpen, setModalEditOpen] = useState(false);
const [modalCreateOpen, setModalCreateOpen] = useState(false);
const dispatch = useDispatch();
const {removeWorker, editWorkersData, removeSelectedWorkers, setActiveWorker, setActiveAllWorkers, createWorker} = tableWorkersSlice.actions;
const arrCompany = companyData?.map((item) => {
return {companyId: item.id, company_name: item.company_name}
});
const companyWorkersTableConfig = [
{
columnId: 'checkWorkers',
columnName: () =>
<Checkbox
isActive={tableData.visibleWorkers.length === tableData.activeWorkersId.length}
onChange={() => {
dispatch(setActiveAllWorkers());
}}
/>,
columnWidth: '40px',
renderRowItem: (item, row) =>
<Checkbox
id={row.id}
isActive={tableData.activeWorkersId.includes(row.id)}
onChange={() => {
dispatch(setActiveWorker(row.id))
}}
/>,
},
{
columnId: 'last_name',
columnName: 'Фамилия',
columnWidth: '100px',
editable: true,
},
{
columnId: 'first_name',
columnName: 'Имя',
columnWidth: '100px',
editable: true,
},
{
columnId: 'position',
columnName: 'Должность',
columnWidth: '110px',
editable: true,
},
{
columnId: "handlers",
columnName: () => <Button
onClick={() => {
dispatch(removeSelectedWorkers(tableData.activeWorkersId))
}}
styles={{padding: '1px', background: '#881818', maxWidth: '98px'}}
>Удалить выбранные</Button>,
renderRowItem: (item, row) =>
<div>
<Button styles={{padding: '3px', marginBottom: '3px'}} onClick={() =>
dispatch(removeWorker(row.id))
}> Удалить
</Button>
<Button styles={{padding: '3px'}} onClick={
() => {
setModalData({...row});
setModalEditOpen(true);
}
}>Редактировать
</Button>
</div>
},
];
const createWorkerConfig = [
{
columnId: 'last_name',
columnName: 'Фамилия',
editable: true,
},
{
columnId: 'first_name',
columnName: 'Имя',
editable: true,
},
{
columnId: 'position',
columnName: 'Должность',
editable: true,
},
{
columnId: 'companyId',
columnName: 'Компания',
editable: true,
}
];
return (
<div className='tableWrapper'>
<div className='tableWrapper__title'>
<Button styles={{background: "#3dda0c87", maxWidth: '200px'}} onClick={() => {
setModalCreateOpen(true)
}}>
Добавить работника
</Button>
</div>
<Table
// hasMore={tableData.has_more_pages}
// fetchMore={() => {
// setCursor(tableData.next_page_cursor_param);
// setPushTableData(true)
// }}
tableConfig={companyWorkersTableConfig}
emptyTable={{
text: "Пусто...",
}}
data={tableData.visibleWorkers}
onChange={editWorkersData}
/>
{modalEditOpen &&
<Modal isOpen={modalEditOpen} onClose={() => {
setModalEditOpen(false)
}}>
<Form data={modalData} onClick={(formData) => {
dispatch(editWorkersData(formData));
setModalEditOpen(false)
}} config={companyWorkersTableConfig}/>
</Modal>
}
{modalCreateOpen &&
<Modal isOpen={modalCreateOpen} onClose={() => {
setModalCreateOpen(false)
}}>
<Form data={{
last_name: '',
first_name: "",
position: "",
companyId: arrCompany
}} onClick={(formData) => {
const newFormData = {
...formData,
id: uuidv4(),
companyId: formData.companyId.find((item) => item.active).companyId
};
dispatch(createWorker(newFormData));
setModalCreateOpen(false)
}} config={createWorkerConfig}/>
</Modal>
}
</div>
)
})

View File

@ -0,0 +1,9 @@
.tableWorkers {
width: 100%;
display: flex;
flex-direction: column;
&__title {
width: 100%;
}
}

8
src/helpers/array.js Normal file
View File

@ -0,0 +1,8 @@
export function flat(arr) {
const newArr = [];
arr.forEach(i =>
Array.isArray(i) ? newArr.push(...flat(i)) : newArr.push(i)
);
return newArr;
}

View File

@ -1,17 +1,22 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import {Provider} from "react-redux";
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from "./store/store";
import './index.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App/>
</Provider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

73
src/mock/mockTable.js Normal file
View File

@ -0,0 +1,73 @@
import {v4 as uuidv4} from 'uuid';
const tableData = [
{
id: 1111,
company_name: "Sber Bank",
address: 'asfd asd',
},
{
id: 2222,
company_name: "alfa Bank",
address: 'asfdasd',
},
{
id: 3333,
company_name: "Tinkoff Bank",
address: 'asfd ',
},
];
const workers= [
{
id: uuidv4(),
companyId: 1111,
last_name: "Karpenko",
first_name: "sergey",
position: "Front End developer",
},
{
id: uuidv4(),
companyId: 1111,
last_name: "Ne karp",
first_name: "Ne",
position: "Ne Front End developer",
},
{
id: uuidv4(),
companyId: 1111,
last_name: "Sasuke",
first_name: "Kakashi",
position: "Коллектор",
},
{
id: uuidv4(),
companyId: 2222,
last_name: "Кактов",
first_name: "ктото",
position: "буздельник",
},
{
id: uuidv4(),
companyId: 3333,
last_name: "Savenko",
first_name: "Dima",
position: "Front End developer",
},
{
id: uuidv4(),
companyId: 3333,
last_name: "Ne Savenko",
first_name: "Ne Dima",
position: "Ne Front End developer",
},
];
export const companyRecords = {
data: tableData,
totalRecords: tableData.length,
pages: (tableData.length + 1) / 10
};
export const companyWorkers = workers

18
src/store/store.js Normal file
View File

@ -0,0 +1,18 @@
import {configureStore, combineReducers} from "@reduxjs/toolkit";
import tableCompanySlice from './tableCompanySlice'
import tableWorkersSlice from "./tableWorkersSlice";
import {composeWithDevTools} from "redux-devtools-extension";
const rootReducer = combineReducers({
table: tableCompanySlice,
tableWorkers: tableWorkersSlice,
});
const store = configureStore({
reducer: rootReducer,
}, composeWithDevTools());
export default store

View File

@ -0,0 +1,89 @@
import {createSlice} from "@reduxjs/toolkit";
export const tableCompanySlice = createSlice({
name: "tableCompany",
initialState: {
data: [],
activeCompanyId: [],
},
reducers: {
setInitialCompany(state, {payload}) {
state.data = payload
},
calculateWorkersInCompany(state, {payload}) {
const countWorkersInCompany = payload.reduce((acc, el) => {
acc[el.companyId] = (acc[el.companyId] || 0) + 1;
return acc;
}, {});
if (Array.isArray(state.data.data)) {
state.data.data = state.data?.data.map((i) => {
return {
...i,
number_of_employees: countWorkersInCompany[i.id]
}
}
)
}
},
setActiveCompany(state, {payload}) {
const index = state.activeCompanyId.indexOf(payload);
if (index > -1) {
state.activeCompanyId.splice(index, 1)
} else {
state.activeCompanyId.push(payload)
}
},
setActiveAllCompany(state) {
if (state.activeCompanyId.length && state.activeCompanyId.length === state.data.data.length) {
state.activeCompanyId = []
} else {
state.activeCompanyId = state.data.data.map(item => item.id)
}
},
setUnActiveWorkers(state) {
state.activeWorkersId = []
},
removeCompany(state, {payload}) {
state.data.data = state.data.data.filter((item) => item.id !== payload)
},
removeSelectedCompany(state, {payload}) {
state.data.data = state.data.data.filter((item) => !payload.includes(item.id))
},
editCompanyData(state, action) {
state.data.data = state.data.data.map((i) => {
if (i.id === action.payload.id) {
return action.payload
}
return i
})
},
createCompany(state, {payload}) {
state.data.data = [...state.data.data, payload]
}
}
}
);
export default tableCompanySlice.reducer
export const {
setInitialCompany, setActiveCompany, setActiveAllCompany, createCompany,
setUnActiveWorkers, removeCompany, editCompanyData, calculateWorkersInCompany
} = tableCompanySlice.actions;

View File

@ -0,0 +1,93 @@
import {createSlice} from "@reduxjs/toolkit";
export const tableWorkersSlice = createSlice({
name: "tableWorkers",
initialState: {
workers: [],
activeWorkersId: [],
visibleWorkers: []
},
reducers: {
setInitialWorkers(state, {payload}) {
state.workers = payload
},
showCompanyWorkers(state, {payload}) {
state.visibleWorkers = state.workers.filter((item) => payload.includes(item.companyId))
},
setColorActiveWorker(state) {
state.visibleWorkers = state.visibleWorkers.map((worker) => {
if (state.activeWorkersId.includes(worker.id)) {
return {...worker, active: true}
} else {
return {...worker, active: false}
}
})
},
setActiveWorker(state, action) {
const index = state.activeWorkersId.indexOf(action.payload);
if (index > -1) {
state.activeWorkersId.splice(index, 1)
} else {
state.activeWorkersId.push(action.payload)
}
},
setActiveAllWorkers(state) {
if (state.activeWorkersId.length > 0 && state.activeWorkersId.length === state.visibleWorkers.length) {
state.activeWorkersId = []
} else {
state.activeWorkersId = state.visibleWorkers.map(item => item.id)
}
},
removeWorkersByIdCompany(state, {payload}) {
state.workers = state.workers.filter((i) => i.companyId !== payload);
state.visibleWorkers = state.visibleWorkers.filter((i) => i.companyId !== payload)
},
removeWorker(state, action) {
state.workers = state.workers.filter((item) => item.id !== action.payload);
state.visibleWorkers = state.visibleWorkers.filter((item) => item.id !== action.payload)
},
removeSelectedWorkers(state, {payload}) {
state.workers = state.workers.filter((item) => !payload.includes(item.id));
state.visibleWorkers = state.visibleWorkers.filter((item) => !payload.includes(item.id))
},
editWorkersData(state, {payload}) {
state.workers = state.workers.map((i) => {
if (i.id === payload.id) {
return payload
}
return i
});
state.visibleWorkers = state.visibleWorkers.map((i) => {
if (i.id === payload.id) {
return payload
}
return i
});
},
createWorker(state, {payload}) {
state.workers = [...state.workers, payload]
}
}
}
);
export default tableWorkersSlice.reducer
export const {
setInitialWorkers, showCompanyWorkers, setColorActiveWorker, removeWorkersByIdCompany, removeWorker,
editWorkersData, removeSelectedWorkers, setActiveWorker, createWorker
} = tableWorkersSlice.actions;