front: partial

This commit is contained in:
Владислав Чикалкин 2020-08-26 14:56:23 +03:00
parent b8bbdf3799
commit 4745d0cfd6
31 changed files with 567 additions and 95 deletions

26
.vscode/settings.json vendored
View File

@ -1,11 +1,17 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/node_modules": true
},
"explorerExclude.backup": null
}
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/node_modules": true
},
"search.exclude": {
"**/.git": true,
"**/node_modules": true,
"**/bower_components": true,
"**/tmp": true
},
"explorerExclude.backup": null
}

View File

@ -8,7 +8,7 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"axios": "^0.19.2",
"axios": "^0.20.0",
"body-parser": "^1.19.0",
"class-validator": "^0.12.2",
"compression": "^1.7.4",
@ -19,6 +19,8 @@
"helmet": "^4.1.0",
"http-errors": "^1.8.0",
"lodash": "^4.17.20",
"mobx": "^5.15.6",
"mobx-react": "^6.2.5",
"morgan": "^1.10.0",
"mssql": "^6.2.1",
"node-sspi": "^0.2.8",
@ -30,6 +32,7 @@
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.3",
"recompose": "^0.30.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^6.6.2",
"styled-components": "^5.1.1",
@ -45,6 +48,7 @@
"@types/cookie-parser": "^1.4.2",
"@types/cors": "^2.8.7",
"@types/express": "^4.17.7",
"@types/lodash": "^4.14.159",
"@types/morgan": "^1.9.1",
"@types/node": "^14.6.0",
"@types/react-router-dom": "^5.1.5",
@ -57,6 +61,7 @@
"test": "react-scripts test",
"eject": "react-scripts eject",
"dev": "cross-env NODE_ENV=development concurrently -p name -c \"yellow,magenta,blue\" -n \"webpack-server,nodemon-server,CRA\" \"yarn dev:server:webpack\" \"yarn dev:server:nodemon\" \"yarn dev:client\"",
"dev:client:only": "react-scripts start",
"dev:client": "wait-on -l tcp:3001 && react-scripts start",
"dev:server": "concurrently -p name -c \"yellow,magenta\" -n \"webpack-server,nodemon-server\" \"yarn dev:server:webpack\" \"yarn dev:server:nodemon\"",
"dev:server:webpack": "webpack --config webpack.config.server.js --watch",

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,15 +1,20 @@
import { StoreProvider } from "client/contexts/storeContext";
import theme from "client/UIKit/theme";
import React from "react";
import { ThemeProvider } from "styled-components";
import { BrowserRouter } from "react-router-dom";
import theme from "UIKit/theme";
import { ThemeProvider } from "styled-components";
import "./App.css";
import Layout from "./Layout";
function App() {
return (
<div className="App">
<ThemeProvider theme={theme}>
<BrowserRouter>
<Layout />
</BrowserRouter>
<StoreProvider>
<BrowserRouter>
<Layout />
</BrowserRouter>
</StoreProvider>
</ThemeProvider>
</div>
);

View File

@ -0,0 +1,74 @@
import Background from "client/Elements/Background";
import { useStores } from "client/hooks/useStores";
import Elements from "core/config/elements";
import { useObserver } from "mobx-react";
import React from "react";
function buildElement(
elementName,
Element,
title,
sourceValueName,
getValue,
action,
actionAsync,
valuesStore
) {
if (sourceValueName) {
return (
<Element
title={title}
value={valuesStore.getValue(sourceValueName)}
onChange={(e) => {
valuesStore.setValue(sourceValueName, e.target.value);
}}
/>
);
}
if (getValue) {
return (
<Element
title={title}
value={getValue(valuesStore)}
onChange={(e) => {
valuesStore.setValue(sourceValueName, e.target.value);
}}
/>
);
}
}
const Calculation = () => {
const {
calculationStore: { valuesStore, statusStore },
} = useStores();
return useObserver(() => (
<Background>
{Object.keys(Elements).map((elementName) => {
const { Element, title, sourceValueName, getValue } = Elements[
elementName
];
return buildElement(
elementName,
Element,
title,
sourceValueName,
getValue,
undefined,
undefined,
valuesStore
);
})}
</Background>
));
};
const styles = {
root: {},
contentWrapper: { flex: 2 },
resultsWrapper: { flex: 1 },
};
// export default observer(Calculation);
export default Calculation;

View File

@ -1,5 +0,0 @@
import React from "react";
const Main = () => <div>Main</div>;
export default Main;

View File

@ -0,0 +1,12 @@
import colors from "client/UIKit/colors";
import { Box, Flex } from "client/UIKit/grid";
import styled from "styled-components";
const Background = styled(Box)`
background: ${colors.white[0]};
margin: 10px 10px;
padding: 10px 18px;
box-shadow: 4px 5px 17px -11px rgba(0, 0, 0, 0.75);
`;
export default Background;

View File

@ -0,0 +1,7 @@
import styled from "styled-components";
export const MenuButton = styled.button`
padding: 15px;
border: 0;
`;

View File

@ -0,0 +1,21 @@
import React from "react";
import styled from "styled-components";
import { Flex } from "client/UIKit/grid";
export const TextInput = styled.input`
width: 200px;
`;
const Title = styled.div`
margin: 5px 0;
`;
export const TitledInput = (props) => {
const { title } = props;
return (
<Flex flexDirection="column" mx="10px" my="5px">
{title && <Title>{title}</Title>}
<TextInput {...props} />
</Flex>
);
};

View File

@ -1,3 +0,0 @@
import React from "react";
const Background = () => <div></div>;

View File

@ -0,0 +1,10 @@
import styled from "styled-components";
import { Flex } from "client/UIKit/grid";
export const LogoText = styled(Flex)`
color: white;
font-size: 1.125rem;
font-weight: bold;
flex-direction: column;
justify-content: center;
`;

View File

@ -0,0 +1,59 @@
import paths from "client/common/paths";
import { MenuButton } from "client/Elements/Buttons";
import colors from "client/UIKit/colors";
import { Box, Flex } from "client/UIKit/grid";
import React from "react";
import { Link, Route, Switch } from "react-router-dom";
import { LogoText } from "client/Elements/Text";
const Header = () => (
<Flex style={styles.header}>
{/* {paths.map(
(path, i) =>
path.route && (
<Link to={path.route}>
<MenuButton key={i}>{path.name}</MenuButton>
</Link>
)
)} */}
<LogoText>Evo Calculator</LogoText>
</Flex>
);
const Content = () => (
<Box style={styles.root}>
<Switch>
{paths.map((path, i) => (
<Route
key={i}
path={path.route}
exact={path.exact}
component={path.content}
/>
))}
</Switch>
</Box>
);
const Layout = () => (
<Flex flexDirection="column" style={styles.flex}>
<Header />
<Content />
</Flex>
);
const styles = {
root: {
height: "100%",
},
flex: { width: "100%" },
header: {
backgroundColor: colors.violet.shades[700],
height: "50px",
padding: "10px 12px",
paddingLeft: "20px",
// borderRadius: "0 0 10px 10px",
},
};
export default Layout;

View File

@ -0,0 +1,97 @@
export default {
black: {
0: "#000000",
100: "#121212",
200: "#242424",
300: "#373737",
400: "#494949",
500: "#5B5B5B",
},
white: {
0: "#FFFFFF",
100: "#EDEDED",
200: "#DBDBDB",
300: "#C8C8C8",
400: "#B6B6B6",
500: "#A4A4A4",
},
gray: {
0: "#A1A1A1",
100: "#959595",
200: "#8A8A8A",
300: "#7E7E7E",
400: "#737373",
500: "#676767",
600: "#5C5C5C",
700: "#505050",
800: "#454545",
900: "#393939",
1000: "#2E2E2E",
1100: "#222222",
},
violet: {
shades: {
0: "#A200FF",
100: "#9600ED",
200: "#8A00DB",
300: "#7F00C8",
400: "#7300B6",
500: "#6800A4",
600: "#5C0092",
700: "#510080",
800: "#45006D",
900: "#3A005B",
1000: "#2E0049",
1100: "#230037",
1200: "#170024",
},
tints: {
0: "#A200FF",
100: "#A812FF",
200: "#AF24FF",
300: "#B637FF",
400: "#BC49FF",
500: "#C35BFF",
600: "#CA6DFF",
700: "#D080FF",
800: "#D792FF",
900: "#DEA4FF",
1000: "#E4B6FF",
1100: "#EBC8FF",
1200: "#F2DBFF",
},
},
blue: {
shades: {
0: "#2900BF",
100: "#2600AF",
200: "#2300A2",
300: "#200094",
400: "#1D0087",
500: "#1A0079",
600: "#17006C",
700: "#14005E",
800: "#120051",
900: "#0F0043",
1000: "#0C0036",
1100: "#090028",
1200: "#06001B",
},
tints: {
0: "#2900BF",
100: "#2E00D4",
200: "#3300EB",
300: "#3903FF",
400: "#4B1AFF",
500: "#5D30FF",
600: "#6F47FF",
700: "#815EFF",
800: "#9375FF",
900: "#A58CFF",
1000: "#B7A3FF",
1100: "#C9BAFF",
1200: "#DBD1FF",
},
},
};

View File

@ -0,0 +1,20 @@
import { NotFound } from "client/Components/Result";
import Calculation from "client/Containers/Calculation";
const paths = [
{
id: "Calculation",
name: "Расчет",
route: "/",
content: Calculation,
exact: true,
},
{
name: 404,
route: undefined,
content: NotFound,
exact: true,
},
];
export default paths;

View File

@ -1,21 +0,0 @@
import { NotFound } from "Components/Result";
import Main from "Containers/Main";
const paths = [
{
id: "Main",
name: "Главная",
route: "/",
content: Main,
exact: true,
},
{
name: 404,
route: undefined,
content: NotFound,
exact: true,
},
];
export default paths;

View File

@ -0,0 +1,10 @@
import React, { createContext } from "react";
import RootStore from "client/stores";
export const StoreContext = createContext();
export const StoreProvider = ({ children }) => (
<StoreContext.Provider value={new RootStore()}>
{children}
</StoreContext.Provider>
);

View File

@ -0,0 +1,24 @@
import React, { ComponentType } from "react";
// import hoistNonReactStatics from "hoist-non-react-statics";
import { useStores } from "../hooks/useStores";
export type TWithStoreHOC = <P extends unknown>(
Component: ComponentType<P>
) => (props: P) => JSX.Element;
export const withStore: TWithStoreHOC = (WrappedComponent) => (props) => {
const ComponentWithStore = () => {
const store = useStores();
return <WrappedComponent {...props} store={store} />;
};
ComponentWithStore.defaultProps = { ...WrappedComponent.defaultProps };
ComponentWithStore.displayName = `WithStores(${
WrappedComponent.name || WrappedComponent.displayName
})`;
// hoistNonReactStatics(ComponentWithStore, WrappedComponent);
return <ComponentWithStore />;
};

View File

@ -0,0 +1,5 @@
import { useContext } from "react";
import { StoreContext } from "../contexts/storeContext";
import RootStore from "client/stores";
export const useStores = (): RootStore => useContext(StoreContext);

View File

@ -0,0 +1,13 @@
import { action, observable } from "mobx";
import { Status } from "core/types/elements";
import initialStatuses from "core/config/initialStatuses";
class StatusStore {
statuses = observable(initialStatuses);
setStatus = action((elementName: string, status: Status) => {
this.statuses[elementName] = status;
});
}
export default StatusStore;

View File

@ -0,0 +1,17 @@
import initialValues from "core/config/initialValues";
import { SourceValuesNames } from "core/types/values";
import { action, observable } from "mobx";
class ValuesStore {
values = observable(initialValues);
setValue = action((sourceValueName: SourceValuesNames, newValue: any) => {
//@ts-ignore
this.values[sourceValueName] = newValue;
});
getValue = (sourceValueName: SourceValuesNames) =>
this.values[sourceValueName];
}
export default ValuesStore;

View File

@ -0,0 +1,16 @@
import Elements from "core/config/elements";
import { action, observable } from "mobx";
import StatusStore from "./StatusStore";
import ValuesStore from "./ValuesStore";
class CalculationStore {
statusStore: StatusStore;
valuesStore: ValuesStore;
constructor() {
this.statusStore = new StatusStore();
this.valuesStore = new ValuesStore();
}
}
export default CalculationStore;

View File

@ -0,0 +1 @@
export default {};

View File

@ -0,0 +1,11 @@
import CalculationStore from "./CalculationStore";
class RootStore {
calculationStore: CalculationStore;
constructor() {
this.calculationStore = new CalculationStore();
}
}
export default RootStore;

View File

@ -0,0 +1,35 @@
import { IElement } from "core/types/elements";
import React from "react";
import { TitledInput } from "client/Elements/Input";
const elements: {
[element: string]: IElement;
} = {
tbx1: {
// title: "Такса",
Element: (props: any) => <TitledInput {...props} />,
sourceValueName: "tax",
},
tbx2: {
Element: (props: any) => <TitledInput {...props} />,
sourceValueName: "price",
},
tbx3: {
Element: (props: any) => <TitledInput {...props} />,
getValue: (valuesStore) => {
const { tax, price } = valuesStore.values;
if (tax && price) {
{
if (typeof tax === "string" && typeof price === "string") {
return parseInt(tax) + parseInt(price);
}
}
}
},
// getStatus:(elementName,statusesStore)=>{
// }
},
};
export default elements;

View File

@ -0,0 +1,9 @@
import { Status } from "../types/elements";
interface IInitialStatuses {
[elementName: string]: Status;
}
const initialStatuses: IInitialStatuses = {};
export default initialStatuses;

View File

@ -0,0 +1,9 @@
import { ValuesMap } from "core/types/values";
const initialValues: ValuesMap = {
price: 10000,
tax: 151515,
test: 999999,
};
export default initialValues;

View File

@ -0,0 +1,52 @@
import { ReactNode } from "react";
import ValuesStore from "client/stores/CalculationStore/ValuesStore";
import StatusStore from "client/stores/CalculationStore/StatusStore";
import { Value, SourceValuesNames } from "./values";
type Action = { (): void };
type Trigger = { (): Action[] };
export enum Status {
Disabled,
Hidden,
Fetching,
}
export interface IBaseElement {
title?: string;
Element: React.Component | ReactNode | JSX.IntrinsicElements | JSX.Element;
getInitialValue?: () => Value;
getInitialStatus?: () => Status | undefined;
getStatus?: (
elementName: string,
statusStore: StatusStore
) => Status | undefined;
}
export interface IBindedElement extends IBaseElement {
sourceValueName?: SourceValuesNames;
}
export interface IComputedElement extends IBaseElement {
getValue?: (valuesStore: ValuesStore) => Value;
getValueAsync?: (
elementName: string,
valuesStore: ValuesStore
) => Promise<Value>;
}
export interface ITriggerElement extends IComputedElement, IBindedElement {
action?: (
e: React.ChangeEvent,
valuesStore: ValuesStore,
statusStore: StatusStore
) => void;
actionAsync?: (
e: React.ChangeEvent,
valuesStore: ValuesStore,
statusStore: StatusStore
) => Promise<void>;
}
export type IElement = IBindedElement & IComputedElement & ITriggerElement;

6
src/core/types/values.ts Normal file
View File

@ -0,0 +1,6 @@
export type SourceValuesNames = "price" | "tax" | "test";
export type Value = string | number | boolean | undefined;
export type ValuesMap = {
[sourceValueName in SourceValuesNames]?: Value;
};

View File

@ -1,6 +1,14 @@
@import "~normalize.css";
html,
body,
#root,
.App {
height: 100%;
}
body {
background-color: rgb(230, 230, 230);
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",

View File

@ -1,10 +1,12 @@
{
"rootDir": "src",
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
@ -14,18 +16,8 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"baseUrl": "src",
"paths": {
"common/*": ["./client/common/*"],
"Components/*": ["./client/Components/*"],
"Containers/*": ["./client/Containers/*"],
"Elements/*": ["./client/Elements/*"],
"hooks/*": ["./client/hooks/*"],
"services/*": ["./client/services/*"],
"tools/*": ["./client/tools/*"],
"UIKit/*": ["./client/UIKit/*"]
}
"baseUrl": "src"
},
"include": ["./src/**/*"],
"include": ["src", "src/client"],
"exclude": ["src/server", "./node_modules/**/*"]
}

15
tsconfig.paths.json.old Normal file
View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"common/*": ["./client/common/*"],
"Components/*": ["./client/Components/*"],
"Containers/*": ["./client/Containers/*"],
"Elements/*": ["./client/Elements/*"],
"hooks/*": ["./client/hooks/*"],
"services/*": ["./client/services/*"],
"tools/*": ["./client/tools/*"],
"UIKit/*": ["./client/UIKit/*"]
}
}
}