From 2c106d1d3f6d59b10ad946e01e8bb3d0df587e40 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 14 Mar 2021 10:10:16 +0300 Subject: refactor: create Service abstraction --- src/containers/ContractorForm.tsx | 51 +++++---------------------- src/containers/Contractors.tsx | 33 ------------------ src/containers/ProductForm.tsx | 48 ++++---------------------- src/containers/Products.tsx | 32 ----------------- src/containers/Service/Service.tsx | 19 +++++++++++ src/containers/Service/ServiceContext.tsx | 20 +++++++++++ src/containers/Service/ServiceForm.tsx | 45 ++++++++++++++++++++++++ src/containers/Service/ServiceList.tsx | 28 +++++++++++++++ src/hooks/useAPIClient.ts | 57 ++++++++++++++----------------- src/index.tsx | 24 ++++++------- src/services.js | 29 ++++++++++++++++ 11 files changed, 193 insertions(+), 193 deletions(-) delete mode 100644 src/containers/Contractors.tsx delete mode 100644 src/containers/Products.tsx create mode 100644 src/containers/Service/Service.tsx create mode 100644 src/containers/Service/ServiceContext.tsx create mode 100644 src/containers/Service/ServiceForm.tsx create mode 100644 src/containers/Service/ServiceList.tsx create mode 100644 src/services.js 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(); - 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 ( - - {(!id || contractor) && ( - - {() => ( -
-
- - - -
-
- )} -
- )} -
+
+
+ + + +
+
); }; 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 ( - - - - ); -}; - -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(); - 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 ( - - {(!id || product) && ( - - {() => ( -
-
- - -
-
- )} -
- )} -
+
+
+ + +
+
); }; 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 ( - - - - ); -}; - -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 ( + + + + + + ); +}; + +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; +} + +const ServiceContext = React.createContext({ + 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(); + 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 ( + + {(!id || item) && ( + + + + )} + + ); +}; + +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 ( + + + + ); +}; + +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 = responseInterface; const fetcher = (endpoint: string) => get(endpoint).then(response => response.data); +const createServiceHooks = (service: string) => { + const useList = (options = {}): Response => { + return useSWR(`/${service}`, fetcher, options); + }; + + const useItem = (_id: string): Response => { + 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; + } + 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 => { - return useSWR('/products', fetcher, options); -}; - -export const useProduct = (_id: string): Response => { - 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 => { - return useSWR('/contractors', fetcher, options); -}; +const hooks = services.reduce((acc, { route }) => { + return _.set(acc, route, createServiceHooks(route)); +}, {}); -export const useContractor = (_id: string): Response => { - 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 = () => (
- - - - - - + {services.map(service => ( + + + + + + ))} ); 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; -- cgit v1.2.3