diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/containers/ContractorForm.tsx | 51 | ||||
-rw-r--r-- | src/containers/Contractors.tsx | 33 | ||||
-rw-r--r-- | src/containers/ProductForm.tsx | 48 | ||||
-rw-r--r-- | src/containers/Products.tsx | 32 | ||||
-rw-r--r-- | src/containers/Service/Service.tsx | 19 | ||||
-rw-r--r-- | src/containers/Service/ServiceContext.tsx | 20 | ||||
-rw-r--r-- | src/containers/Service/ServiceForm.tsx | 45 | ||||
-rw-r--r-- | src/containers/Service/ServiceList.tsx | 28 | ||||
-rw-r--r-- | src/hooks/useAPIClient.ts | 57 | ||||
-rw-r--r-- | src/index.tsx | 24 | ||||
-rw-r--r-- | src/services.js | 29 |
11 files changed, 193 insertions, 193 deletions
diff --git a/src/containers/ContractorForm.tsx b/src/containers/ContractorForm.tsx index 7f0d660..a67eabe 100644 --- a/src/containers/ContractorForm.tsx +++ b/src/containers/ContractorForm.tsx @@ -1,51 +1,16 @@ import React from 'react'; -import { useParams, useHistory } from 'react-router-dom'; -import { Formik, Form, Field } from 'formik'; -import Page, { Action } from './Page'; +import { Form, Field } from 'formik'; import Input from '../components/Input'; -import { useContractor } from '../hooks/useAPIClient'; -import { post, patch } from '../requests'; - -interface Params { - id: string; -} - -const actions: Action[] = [ - { name: 'Назад', variant: 'outlined', route: '..' }, - { name: 'Сохранить', type: 'submit', form: 'contractorForm' }, -]; const ContractorForm: React.FC = () => { - const history = useHistory(); - const { id } = useParams<Params>(); - const { data: contractor } = useContractor(id); - - const onSubmit = (values: any) => { - const promise = id - ? patch(`/contractors/${id}`, values) - : post('/contractors', values); - return promise.then(() => history.push('/contractors')); - }; - return ( - <Page title={id ? contractor?.name : 'Новый контрагент'} actions={actions}> - {(!id || contractor) && ( - <Formik - initialValues={contractor || { name: '', debt: '', vatId: '' }} - onSubmit={onSubmit} - > - {() => ( - <Form id="contractorForm"> - <div className="max-w-lg"> - <Field name="name" label="Название" as={Input} /> - <Field name="vatId" label="УНП" as={Input} /> - <Field name="debt" type="number" label="Долг ($)" as={Input} /> - </div> - </Form> - )} - </Formik> - )} - </Page> + <Form id="form"> + <div className="max-w-lg"> + <Field name="name" label="Название" as={Input} /> + <Field name="vatId" label="УНП" as={Input} /> + <Field name="debt" type="number" label="Долг ($)" as={Input} /> + </div> + </Form> ); }; diff --git a/src/containers/Contractors.tsx b/src/containers/Contractors.tsx deleted file mode 100644 index 5d589ea..0000000 --- a/src/containers/Contractors.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { useHistory } from 'react-router-dom'; -import Page from './Page'; -import ListTable from '../components/ListTable'; -import { useContractors } from '../hooks/useAPIClient'; - -const fields = [ - { key: 'vatId', label: 'УНП' }, - { key: 'name', label: 'Название' }, - { key: 'debt', label: 'Долг' }, -]; - -const actions = [ - { name: 'Добавить', route: 'contractors/add' }, -]; - -const Contractors: React.FC = () => { - const history = useHistory(); - const { data: contractors } = useContractors(); - - const handleRowClick = (index: number) => { - const contractor = contractors && contractors[index]; - history.push(`/contractors/edit/${contractor?._id}`); - }; - - return ( - <Page title="Контрагенты" actions={actions}> - <ListTable items={contractors} fields={fields} handleRowClick={handleRowClick} /> - </Page> - ); -}; - -export default Contractors; diff --git a/src/containers/ProductForm.tsx b/src/containers/ProductForm.tsx index 0d21df9..4d01881 100644 --- a/src/containers/ProductForm.tsx +++ b/src/containers/ProductForm.tsx @@ -1,50 +1,16 @@ import React from 'react'; -import { useParams, useHistory } from 'react-router-dom'; -import { Formik, Form, Field } from 'formik'; -import Page, { Action } from './Page'; +import { Form, Field } from 'formik'; import Input from '../components/Input'; -import { useProduct } from '../hooks/useAPIClient'; -import { post, patch } from '../requests'; -interface Params { - id: string; -} - -const actions: Action[] = [ - { name: 'Назад', variant: 'outlined', route: '..' }, - { name: 'Сохранить', type: 'submit', form: 'productForm' }, -]; const ProductForm: React.FC = () => { - const history = useHistory(); - const { id } = useParams<Params>(); - const { data: product } = useProduct(id); - - const onSubmit = (values: any) => { - const promise = id - ? patch(`/products/${id}`, values) - : post('/products', values); - return promise.then(() => history.push('/products')); - }; - return ( - <Page title={id ? product?.name : 'Новый товар'} actions={actions}> - {(!id || product) && ( - <Formik - initialValues={product || { name: '', price: '' }} - onSubmit={onSubmit} - > - {() => ( - <Form id="productForm"> - <div className="max-w-lg"> - <Field name="name" label="Название" as={Input} /> - <Field name="price" type="number" label="Цена ($)" as={Input} /> - </div> - </Form> - )} - </Formik> - )} - </Page> + <Form id="form"> + <div className="max-w-lg"> + <Field name="name" label="Название" as={Input} /> + <Field name="price" type="number" label="Цена ($)" as={Input} /> + </div> + </Form> ); }; diff --git a/src/containers/Products.tsx b/src/containers/Products.tsx deleted file mode 100644 index 0b6ea70..0000000 --- a/src/containers/Products.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { useHistory } from 'react-router-dom'; -import Page from './Page'; -import ListTable from '../components/ListTable'; -import { useProducts } from '../hooks/useAPIClient'; - -const fields = [ - { key: 'name', label: 'Название' }, - { key: 'price', label: 'Цена' }, -]; - -const actions = [ - { name: 'Добавить', route: 'products/add' }, -]; - -const Products: React.FC = () => { - const history = useHistory(); - const { data: products } = useProducts(); - - const handleRowClick = (index: number) => { - const product = products && products[index]; - history.push(`/products/edit/${product?._id}`); - }; - - return ( - <Page title="Товары" actions={actions}> - <ListTable items={products} fields={fields} handleRowClick={handleRowClick} /> - </Page> - ); -}; - -export default Products; diff --git a/src/containers/Service/Service.tsx b/src/containers/Service/Service.tsx new file mode 100644 index 0000000..3a7cbee --- /dev/null +++ b/src/containers/Service/Service.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Route, Switch, useRouteMatch } from 'react-router-dom'; +import ServiceList from './ServiceList'; +import ServiceForm from './ServiceForm'; + + +const Service: React.FC = () => { + const { path } = useRouteMatch(); + + return ( + <Switch> + <Route exact path={path} component={ServiceList} /> + <Route path={`${path}/add`} component={ServiceForm} /> + <Route path={`${path}/edit/:id`} component={ServiceForm} /> + </Switch> + ); +}; + +export default Service; diff --git a/src/containers/Service/ServiceContext.tsx b/src/containers/Service/ServiceContext.tsx new file mode 100644 index 0000000..2602936 --- /dev/null +++ b/src/containers/Service/ServiceContext.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +export interface ServiceParams { + route: string; + name: string; + nameSingular: string; + tableFields: any[]; + Form: React.FC<any>; +} + +const ServiceContext = React.createContext<ServiceParams>({ + route: '', + name: '', + nameSingular: '', + tableFields: [], + Form: () => null, +}); + +export const ServiceProvider = ServiceContext.Provider; +export default ServiceContext; diff --git a/src/containers/Service/ServiceForm.tsx b/src/containers/Service/ServiceForm.tsx new file mode 100644 index 0000000..273f5bd --- /dev/null +++ b/src/containers/Service/ServiceForm.tsx @@ -0,0 +1,45 @@ +import React, { useContext } from 'react'; +import { useParams, useHistory } from 'react-router-dom'; +import { Formik } from 'formik'; +import Page, { Action } from '../Page'; +import hooks from '../../hooks/useAPIClient'; +import { post, patch } from '../../requests'; +import ServiceContext from './ServiceContext'; + +interface Params { + id: string; +} + +const actions: Action[] = [ + { name: 'Назад', variant: 'outlined', route: '..' }, + { name: 'Сохранить', type: 'submit', form: 'form' }, +]; + +const ServiceForm: React.FC = () => { + const service = useContext(ServiceContext); + const history = useHistory(); + const { id } = useParams<Params>(); + const { data: item } = hooks[service.route].useItem(id); + + const onSubmit = (values: any) => { + const promise = id + ? patch(`/${service.route}/${id}`, values) + : post(`/${service.route}`, values); + return promise.then(() => history.push(`/${service.route}`)); + }; + + return ( + <Page title={id ? item?.name : `Новый ${service.nameSingular}`} actions={actions}> + {(!id || item) && ( + <Formik + initialValues={item || { name: '', debt: '', vatId: '' }} + onSubmit={onSubmit} + > + <service.Form /> + </Formik> + )} + </Page> + ); +}; + +export default ServiceForm; diff --git a/src/containers/Service/ServiceList.tsx b/src/containers/Service/ServiceList.tsx new file mode 100644 index 0000000..6af2d1b --- /dev/null +++ b/src/containers/Service/ServiceList.tsx @@ -0,0 +1,28 @@ +import React, { useContext } from 'react'; +import { useHistory } from 'react-router-dom'; +import Page from '../Page'; +import ListTable from '../../components/ListTable'; +import hooks from '../../hooks/useAPIClient'; +import ServiceContext from './ServiceContext'; + + +const ServiceList: React.FC = () => { + const service = useContext(ServiceContext); + const history = useHistory(); + const { data } = hooks[service.route].useList(); + + const actions = [{ name: 'Добавить', route: `/${service.route}/add` }]; + + const handleRowClick = (index: number) => { + const item = data && data[index]; + history.push(`/${service.route}/edit/${item?._id}`); + }; + + return ( + <Page title={service.name} actions={actions}> + <ListTable items={data} fields={service.tableFields} handleRowClick={handleRowClick} /> + </Page> + ); +}; + +export default ServiceList; diff --git a/src/hooks/useAPIClient.ts b/src/hooks/useAPIClient.ts index eb427f1..8f3a077 100644 --- a/src/hooks/useAPIClient.ts +++ b/src/hooks/useAPIClient.ts @@ -1,11 +1,32 @@ import useSWR, { responseInterface } from 'swr'; import _ from 'lodash'; import { get } from '../requests'; +import services from '../services'; type Response<T> = responseInterface<T, Error>; const fetcher = (endpoint: string) => get(endpoint).then(response => response.data); +const createServiceHooks = <Item = any>(service: string) => { + const useList = (options = {}): Response<Item[]> => { + return useSWR(`/${service}`, fetcher, options); + }; + + const useItem = (_id: string): Response<Item> => { + const { data: preloadedItems } = useList({ revalidateOnMount: false }); + const result = useSWR(_id && `/${service}/${_id}`, fetcher); + if (!result.data && result.isValidating) { + // If we are waiting for the first result, check if we can maybe + // get the data from already cached list for the time-being + const item = _.find(preloadedItems, { _id }); + return { ...result, data: item } as Response<Item>; + } + return result; + }; + + return { useItem, useList }; +}; + // Products export interface Product { _id: string; @@ -18,23 +39,6 @@ export interface Product { updatedAt: string; } -export const useProducts = (options = {}): Response<Product[]> => { - return useSWR('/products', fetcher, options); -}; - -export const useProduct = (_id: string): Response<Product> => { - const { data: preloadedProducts } = useProducts({ revalidateOnMount: false }); - const result = useSWR(_id && `/products/${_id}`, fetcher); - if (!result.data && result.isValidating) { - // If we are waiting for the first result, check if we can maybe - // get the data from already cached list for the time-being - const product = _.find(preloadedProducts, { _id }); - return { ...result, data: product }; - } - return result; -}; - - // Contractors export interface Contractor { _id: string; @@ -45,18 +49,9 @@ export interface Contractor { debt: number; } -export const useContractors = (options = {}): Response<Product[]> => { - return useSWR('/contractors', fetcher, options); -}; +const hooks = services.reduce((acc, { route }) => { + return _.set(acc, route, createServiceHooks(route)); +}, {}); -export const useContractor = (_id: string): Response<Product> => { - const { data: preloadedContractors } = useContractors({ revalidateOnMount: false }); - const result = useSWR(_id && `/contractors/${_id}`, fetcher); - if (!result.data && result.isValidating) { - // If we are waiting for the first result, check if we can maybe - // get the data from already cached list for the time-being - const contractor = _.find(preloadedContractors, { _id }); - return { ...result, data: contractor }; - } - return result; -}; + +export default hooks as any; diff --git a/src/index.tsx b/src/index.tsx index 4ec1db0..932fbf3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,16 +8,13 @@ import { } from 'react-router-dom'; import Header from './components/Header'; import Home from './containers/Home'; -import Products from './containers/Products'; -import ProductForm from './containers/ProductForm'; -import Contractors from './containers/Contractors'; -import ContractorForm from './containers/ContractorForm'; +import Service from './containers/Service/Service'; +import { ServiceProvider } from './containers/Service/ServiceContext'; +import services from './services'; const navigation = [ { name: 'Главная', route: '/' }, - { name: 'Товары', route: '/products' }, - { name: 'Контрагенты', route: '/contractors' }, - { name: 'Накладные', route: '/waybills' }, + ...services.map(({ name, route }) => ({ name, route: `/${route}` })), ]; const App: React.FC = () => ( @@ -25,12 +22,13 @@ const App: React.FC = () => ( <Header navigation={navigation} /> <Switch> <Route exact path="/" component={Home} /> - <Route exact path="/products" component={Products} /> - <Route exact path="/products/add" component={ProductForm} /> - <Route exact path="/products/edit/:id" component={ProductForm} /> - <Route exact path="/contractors" component={Contractors} /> - <Route exact path="/contractors/add" component={ContractorForm} /> - <Route exact path="/contractors/edit/:id" component={ContractorForm} /> + {services.map(service => ( + <Route path={`/${service.route}`}> + <ServiceProvider value={service}> + <Service /> + </ServiceProvider> + </Route> + ))} </Switch> </Router> ); diff --git a/src/services.js b/src/services.js new file mode 100644 index 0000000..83fc425 --- /dev/null +++ b/src/services.js @@ -0,0 +1,29 @@ +import ContractorForm from './containers/ContractorForm'; +import ProductForm from './containers/ProductForm'; +import { ServiceParams } from './containers/Service/ServiceContext'; + +const services: ServiceParams[] = [ + { + route: 'products', + name: 'Товары', + nameSingular: 'Товар', + tableFields: [ + { key: 'name', label: 'Название' }, + { key: 'price', label: 'Цена' }, + ], + Form: ProductForm, + }, + { + route: 'contractors', + name: 'Контрагенты', + nameSingular: 'Контрагент', + tableFields: [ + { key: 'vatId', label: 'УНП' }, + { key: 'name', label: 'Название' }, + { key: 'debt', label: 'Долг' }, + ], + Form: ContractorForm, + }, +]; + +export default services; |