From 2f525f1de94a387280433dcfc1f3b73f5b2a56c2 Mon Sep 17 00:00:00 2001 From: Guilhem CARRON <gcarron@grandlyon.com> Date: Wed, 2 Mar 2022 14:29:11 +0000 Subject: [PATCH] feat(price): Add price managment --- dbinit/dbinit.sql | 4 + dbinit/fluidprices.CSV | 84 ++++++++++ dbinit/init.md | 25 +++ docker-compose.local.yml | 6 +- docker-compose.yml | 2 + package.json | 1 + src/assets/icons/down-arrow.png | Bin 0 -> 6724 bytes src/assets/icons/editing.png | Bin 0 -> 19180 bytes src/components/Prices/PriceRow.tsx | 55 +++++++ src/components/Prices/PriceSection.tsx | 215 +++++++++++++++++++++++++ src/components/Prices/Prices.tsx | 28 ++++ src/components/Prices/prices.scss | 119 ++++++++++++++ src/components/Routes/Routes.tsx | 2 + src/constants/routes.json | 4 + src/enum/fluidTypes.ts | 5 + src/enum/frequency.enum.ts | 5 + src/models/price.model.ts | 6 + src/services/prices.service.ts | 36 +++++ src/styles/config/_typography.scss | 4 + yarn.lock | 5 + 20 files changed, 604 insertions(+), 2 deletions(-) create mode 100644 dbinit/dbinit.sql create mode 100644 dbinit/fluidprices.CSV create mode 100644 dbinit/init.md create mode 100644 src/assets/icons/down-arrow.png create mode 100644 src/assets/icons/editing.png create mode 100644 src/components/Prices/PriceRow.tsx create mode 100644 src/components/Prices/PriceSection.tsx create mode 100644 src/components/Prices/Prices.tsx create mode 100644 src/components/Prices/prices.scss create mode 100644 src/enum/fluidTypes.ts create mode 100644 src/enum/frequency.enum.ts create mode 100644 src/models/price.model.ts create mode 100644 src/services/prices.service.ts diff --git a/dbinit/dbinit.sql b/dbinit/dbinit.sql new file mode 100644 index 00000000..1070e073 --- /dev/null +++ b/dbinit/dbinit.sql @@ -0,0 +1,4 @@ +LOAD DATA LOCAL INFILE '/dbinit/fluidprices.CSV' INTO TABLE prices +FIELDS TERMINATED BY ',' +LINES TERMINATED BY '\n' +IGNORE 1 ROWS; \ No newline at end of file diff --git a/dbinit/fluidprices.CSV b/dbinit/fluidprices.CSV new file mode 100644 index 00000000..21670990 --- /dev/null +++ b/dbinit/fluidprices.CSV @@ -0,0 +1,84 @@ +fluid_type,price,start_date,end_date,id,created_at,updated_at +0,0.1256,2012-07-23T00:00:00Z,2013-07-31T23:59:59Z,1,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1329,2013-08-01T00:00:00Z,2014-10-31T23:59:59Z,2,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1401,2014-01-11T00:00:00Z,2015-07-31T23:59:59Z,3,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1437,2015-08-01T00:00:00Z,2016-07-31T23:59:59Z,4,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1503,2016-08-01T00:00:00Z,2017-07-31T23:59:59Z,5,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1546,2017-08-01T00:00:00Z,2018-01-31T23:59:59Z,6,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1555,2018-02-01T00:00:00Z,2018-07-31T23:59:59Z,7,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.145,2018-08-01T00:00:00Z,2019-05-31T23:59:59Z,8,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1531,2019-06-01T00:00:00Z,2019-07-31T23:59:59Z,9,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1524,2019-08-01T00:00:00Z,2020-01-31T23:59:59Z,10,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1546,2020-02-01T00:00:00Z,2020-07-31T23:59:59Z,11,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1557,2020-08-01T00:00:00Z,2021-01-31T23:59:59Z,12,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1582,2021-02-01T00:00:00Z,2021-07-31T23:59:59Z,13,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.1558,2021-08-01T00:00:00Z,2022-01-31T23:59:59Z,14,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +0,0.174,2022-02-01T00:00:00Z,,15,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +1,0.0030735,2012-01-01T00:00:00Z,2012-12-31T23:59:59Z,16,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +1,0.0031483,2013-01-01T00:00:00Z,2013-12-31T23:59:59Z,17,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +1,0.0031381,2014-01-01T00:00:00Z,2014-12-31T23:59:59Z,18,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +1,0.00307,2015-01-01T00:00:00Z,2015-12-31T23:59:59Z,19,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +1,0.0031,2016-01-01T00:00:00Z,2016-12-31T23:59:59Z,20,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +1,0.00311,2017-01-01T00:00:00Z,2017-12-31T23:59:59Z,21,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +1,0.00313,2018-01-01T00:00:00Z,2018-12-31T23:59:59Z,22,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +1,0.00313,2019-01-01T00:00:00Z,2019-12-31T23:59:59Z,23,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +1,0.00315,2020-01-01T00:00:00Z,2020-12-31T23:59:59Z,24,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +1,0.00319,2021-01-01T00:00:00Z,,25,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0919,2017-01-01T00:00:00Z,2017-01-31T23:59:59Z,26,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0915,2017-02-01T00:00:00Z,2017-02-28T23:59:59Z,27,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0932,2017-03-01T00:00:00Z,2017-03-31T23:59:59Z,28,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0927,2017-04-01T00:00:00Z,2017-04-30T23:59:59Z,29,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0906,2017-05-01T00:00:00Z,2017-05-31T23:59:59Z,30,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0906,2017-06-01T00:00:00Z,2017-06-30T23:59:59Z,31,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0788,2017-07-01T00:00:00Z,2017-07-31T23:59:59Z,32,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0783,2017-08-01T00:00:00Z,2017-08-31T23:59:59Z,33,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0783,2017-09-01T00:00:00Z,2017-09-30T23:59:59Z,34,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0791,2017-10-01T00:00:00Z,2017-10-31T23:59:59Z,35,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0806,2017-11-01T00:00:00Z,2017-11-30T23:59:59Z,36,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0812,2017-12-01T00:00:00Z,2017-12-31T23:59:59Z,37,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0857,2018-01-01T00:00:00Z,2018-01-31T23:59:59Z,38,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0866,2018-02-01T00:00:00Z,2018-02-28T23:59:59Z,39,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0847,2018-03-01T00:00:00Z,2018-03-31T23:59:59Z,40,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0839,2018-04-01T00:00:00Z,2018-04-30T23:59:59Z,41,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0842,2018-05-01T00:00:00Z,2018-05-31T23:59:59Z,42,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0855,2018-06-01T00:00:00Z,2018-06-30T23:59:59Z,43,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0959,2018-07-01T00:00:00Z,2018-07-31T23:59:59Z,44,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0961,2018-08-01T00:00:00Z,2018-08-31T23:59:59Z,45,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0967,2018-09-01T00:00:00Z,2018-09-30T23:59:59Z,46,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0989,2018-10-01T00:00:00Z,2018-10-31T23:59:59Z,47,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.1031,2018-11-01T00:00:00Z,2018-11-30T23:59:59Z,48,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.1013,2018-12-01T00:00:00Z,2018-12-31T23:59:59Z,49,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0999,2019-01-01T00:00:00Z,2019-01-31T23:59:59Z,50,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0993,2019-02-01T00:00:00Z,2019-02-28T23:59:59Z,51,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0993,2019-03-01T00:00:00Z,2019-03-31T23:59:59Z,52,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0977,2019-04-01T00:00:00Z,2019-04-30T23:59:59Z,53,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0973,2019-05-01T00:00:00Z,2019-05-31T23:59:59Z,54,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0969,2019-06-01T00:00:00Z,2019-06-30T23:59:59Z,55,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0795,2019-07-01T00:00:00Z,2019-07-31T23:59:59Z,56,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0791,2019-08-01T00:00:00Z,2019-08-31T23:59:59Z,57,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0785,2019-09-01T00:00:00Z,2019-09-30T23:59:59Z,58,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.077,2019-10-01T00:00:00Z,2019-10-31T23:59:59Z,59,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0789,2019-11-01T00:00:00Z,2019-11-30T23:59:59Z,60,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0793,2019-12-01T00:00:00Z,2019-12-31T23:59:59Z,61,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0787,2020-01-01T00:00:00Z,2020-01-31T23:59:59Z,62,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0765,2020-02-01T00:00:00Z,2020-02-29T23:59:59Z,70,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0736,2020-03-01T00:00:00Z,2020-03-31T23:59:59Z,71,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.071,2020-04-01T00:00:00Z,2020-04-30T23:59:59Z,72,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0703,2020-05-01T00:00:00Z,2020-05-31T23:59:59Z,73,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0687,2020-06-01T00:00:00Z,2020-06-30T23:59:59Z,74,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0698,2020-07-01T00:00:00Z,2020-07-31T23:59:59Z,75,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0705,2020-08-01T00:00:00Z,2020-08-31T23:59:59Z,76,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0709,2020-09-01T00:00:00Z,2020-09-30T23:59:59Z,77,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0735,2020-10-01T00:00:00Z,2020-10-31T23:59:59Z,78,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0745,2020-11-01T00:00:00Z,2020-11-30T23:59:59Z,79,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0759,2020-12-01T00:00:00Z,2020-12-31T23:59:59Z,80,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.076,2021-01-01T00:00:00Z,2021-01-31T23:59:59Z,81,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0782,2021-02-01T00:00:00Z,2021-02-28T23:59:59Z,82,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0818,2021-03-01T00:00:00Z,2021-03-31T23:59:59Z,83,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.079,2021-04-01T00:00:00Z,2021-04-30T23:59:59Z,84,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0797,2021-05-01T00:00:00Z,2021-05-31T23:59:59Z,85,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0826,2021-06-01T00:00:00Z,2021-06-30T23:59:59Z,86,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0895,2021-07-01T00:00:00Z,2021-07-31T23:59:59Z,87,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.0934,2021-08-01T00:00:00Z,2021-08-31T23:59:59Z,88,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.1002,2021-09-01T00:00:00Z,2021-09-30T23:59:59Z,89,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z +2,0.1121,2021-10-01T00:00:00Z,,90,2000-01-01T00:00:00Z,2000-01-01T00:00:00Z \ No newline at end of file diff --git a/dbinit/init.md b/dbinit/init.md new file mode 100644 index 00000000..21120af5 --- /dev/null +++ b/dbinit/init.md @@ -0,0 +1,25 @@ +# Init first prices data + +## Local + +If the script is not working (problem with secure_file_priv variable), use the import in phpMyAdmin: + +- Ignore the request for the first line +- Select format "CVS using LOAD DATA" +- Choose the right column separator "," +- Then launch the execution + +## Rec/Prod + +- Connect to mysql docker container + +``` +docker exec -it <container-id> bash +``` + +- Open mysql shell + +``` +mysql --local-infile=1 -uroot -p backoffice < /dbinit/dbinit.sql + +``` diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 14115d60..76db778a 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -12,8 +12,8 @@ services: depends_on: - backend # For linux users - # extra_hosts: - # - 'host.docker.internal:host-gateway' + extra_hosts: + - 'host.docker.internal:host-gateway' database-agent: image: mysql:5 @@ -27,6 +27,8 @@ services: interval: 5s timeout: 10s retries: 60 + volumes: + - ./dbinit:/dbinit backend: image: registry.forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server:dev diff --git a/docker-compose.yml b/docker-compose.yml index 0ef136a4..ca641669 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,8 @@ services: interval: 5s timeout: 10s retries: 60 + volumes: + - ./dbinit:/dbinit backend: image: registry.forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server:dev diff --git a/package.json b/package.json index 5dc3567c..e20fdf70 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@types/html-to-draftjs": "^1.4.0", "@types/react-draft-wysiwyg": "^1.13.3", "axios": "^0.21.1", + "dayjs": "^1.10.7", "draft-js": "^0.11.7", "draft-js-export-html": "^1.4.1", "html-to-draftjs": "^1.5.0", diff --git a/src/assets/icons/down-arrow.png b/src/assets/icons/down-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..9bb76b55bb71faa4986fca57ca4974b39a76e836 GIT binary patch literal 6724 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 literal 0 HcmV?d00001 diff --git a/src/assets/icons/editing.png b/src/assets/icons/editing.png new file mode 100644 index 0000000000000000000000000000000000000000..5866258a45d4ca656122356afc42bcf6b35c4ae0 GIT binary patch literal 19180 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 literal 0 HcmV?d00001 diff --git a/src/components/Prices/PriceRow.tsx b/src/components/Prices/PriceRow.tsx new file mode 100644 index 00000000..d3e5b0d0 --- /dev/null +++ b/src/components/Prices/PriceRow.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import { IPrice } from '../../models/price.model' +import editing from '../../assets/icons/editing.png' + +interface PriceSectionProps { + getDate: (date: string) => string + setPriceToSave: React.Dispatch<React.SetStateAction<IPrice>> + priceToSave: IPrice + price: IPrice + prices: IPrice[] + index: number + isNextPrice?: boolean +} + +const PriceRow: React.FC<PriceSectionProps> = ({ + getDate, + setPriceToSave, + priceToSave, + price, + prices, + index, + isNextPrice, +}: PriceSectionProps) => { + const editableLimit: number = 3 + + return ( + <> + <li + className={ + priceToSave.startDate === price.startDate + ? 'flex-bloc price-selected' + : 'flex-bloc' + } + > + <div className="prix"> + {price.price === '' ? '----' : price.price} € + </div> + <p> + à partir de :{' '} + <span className="capital">{getDate(price.startDate)}</span> + </p> + {index < editableLimit - 1 && ( + <img + src={editing} + onClick={() => setPriceToSave(isNextPrice ? price : prices[index])} + alt="edit-icon" + /> + )} + </li> + <hr></hr> + </> + ) +} + +export default PriceRow diff --git a/src/components/Prices/PriceSection.tsx b/src/components/Prices/PriceSection.tsx new file mode 100644 index 00000000..20e70cb9 --- /dev/null +++ b/src/components/Prices/PriceSection.tsx @@ -0,0 +1,215 @@ +import React, { useCallback, useContext, useEffect, useState } from 'react' +import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' +import { FluidType } from '../../enum/fluidTypes' +import { FrequencyInMonth } from '../../enum/frequency.enum' +import { UserContext, UserContextProps } from '../../hooks/userContext' +import { IPrice } from '../../models/price.model' +import { PricesService } from '../../services/prices.service' +import arrowDown from '../../assets/icons/down-arrow.png' + +import Loader from '../Loader/Loader' +import './prices.scss' +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +import timezone from 'dayjs/plugin/timezone' +import PriceRow from './PriceRow' +dayjs.extend(utc) +dayjs.extend(timezone) + +interface PriceSectionProps { + fluid: FluidType + frequency: FrequencyInMonth +} + +const PriceSection: React.FC<PriceSectionProps> = ({ + fluid, + frequency, +}: PriceSectionProps) => { + const [prices, setPrices] = useState<IPrice[]>([]) + const [nextPrice, setNextPrice] = useState<IPrice>() + const [isLoading, setIsLoading] = useState<boolean>(false) + const [refreshData, setRefreshData] = useState<boolean>(false) + const [showHistory, setShowHistory] = useState<boolean>(false) + const [showFullList, setShowFullList] = useState<boolean>(false) + const [priceToSave, setPriceToSave] = useState<IPrice>({ + fluidType: fluid, + price: '', + startDate: '', + endDate: null, + }) + const { user }: Partial<UserContextProps> = useContext(UserContext) + const maxPerList: number = 8 + + const handlePriceSelection = useCallback((val: string) => { + if (val === '') val = '0' + val = val.replace(/,/g, '.') + val = val.replace(/([^0-9.]+)/, '') + setPriceToSave((prev) => { + return { ...prev, price: val } + }) + }, []) + + const savePrice = useCallback(async () => { + if ( + priceToSave && + user && + priceToSave.price !== '0' && + priceToSave.price !== '' + ) { + const priceService = new PricesService() + const formattedPrice = { + ...priceToSave, + price: parseFloat(priceToSave.price as string), + } + await priceService.savePrice(formattedPrice, user.xsrftoken) + setRefreshData(true) + } + }, [priceToSave, user]) + + const toggleHistory = useCallback(() => { + setShowHistory((prev) => !prev) + }, []) + + const getDate = useCallback((isoString: string): string => { + const date = new Date(isoString) + const month = date.toLocaleString('fr', { month: 'long' }) + const year = date.toLocaleString('fr', { year: 'numeric' }) + return `${month} ${year}` + }, []) + + const toggleFullList = useCallback(() => { + setShowFullList((prev) => !prev) + }, []) + + useEffect(() => { + let subscribed = true + setIsLoading(true) + async function getPrices() { + const priceService = new PricesService() + const pricesByFluid = await priceService.getPricesByFluid(fluid) + if (pricesByFluid.length) { + const nextPriceToCreate: IPrice = { + fluidType: fluid, + price: '', + startDate: '', + endDate: null, + } + // Set the correct for the next price to create + const date: string = dayjs(pricesByFluid[0].startDate) + .utc(true) + .tz('Europe/Paris') + .add(frequency, 'month') + .startOf('day') + .format('YYYY-MM-DDTHH:mm:ss[Z]') + + nextPriceToCreate.startDate = date + if (subscribed) { + setPrices(pricesByFluid) + setPriceToSave(nextPriceToCreate) + setNextPrice(nextPriceToCreate) + } + setIsLoading(false) + } + } + getPrices() + + return () => { + subscribed = false + setRefreshData(false) + } + }, [refreshData, frequency, fluid]) + + if (isLoading) return <Loader></Loader> + if (!prices.length) return <section> Aucun prix trouvé</section> + return ( + <section> + <h2> + {fluid === FluidType.ELECTRICITY && 'Electricité'} + {fluid === FluidType.WATER && 'Eau'} + {fluid === FluidType.GAS && 'Gaz'} + </h2> + <hr className="price-separator" /> + <div className="flex-bloc"> + <p>Nouveau prix : </p> + <input + className="input-dark price-select" + type="text" + value={priceToSave.price.toString()} + onChange={(e) => handlePriceSelection(e.target.value)} + placeholder={priceToSave.price === '' ? 'Saisir le nouveau prix' : ''} + /> + <span className="euro">€</span> + + <div className="flex-bloc startDate"> + <p>A partir de : </p> + <p className="date"> + <span className="capital">{getDate(priceToSave.startDate)}</span> + </p> + </div> + </div> + <button + className="btnValid" + onClick={savePrice} + disabled={priceToSave.price === '0' || priceToSave.price === ''} + > + Sauvegarder + </button> + <div className="history"> + <button onClick={toggleHistory} className={showHistory ? 'active' : ''}> + <span>Voir l'historique</span> + <img + src={arrowDown} + className={showHistory ? 'icon-active' : ''} + alt="arrow-icon" + /> + </button> + {showHistory && ( + <ul className={showHistory ? 'active' : ''}> + {nextPrice && ( + <PriceRow + getDate={getDate} + priceToSave={priceToSave} + price={nextPrice} + prices={prices} + setPriceToSave={setPriceToSave} + index={0} + isNextPrice={true} + /> + )} + {prices.map((price, i) => { + return ( + <div + key={i} + className={ + i > maxPerList && !showFullList ? 'price-hidden' : '' + } + > + <PriceRow + getDate={getDate} + priceToSave={priceToSave} + price={price} + prices={prices} + setPriceToSave={setPriceToSave} + index={i} + /> + {i === maxPerList && !showFullList && ( + <button onClick={toggleFullList} className="showButton"> + En voir plus + </button> + )} + </div> + ) + })} + {showFullList && ( + <button onClick={toggleFullList} className="showButton"> + En voir moins + </button> + )} + </ul> + )} + </div> + </section> + ) +} + +export default PriceSection diff --git a/src/components/Prices/Prices.tsx b/src/components/Prices/Prices.tsx new file mode 100644 index 00000000..beb1286e --- /dev/null +++ b/src/components/Prices/Prices.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' +import { FluidType } from '../../enum/fluidTypes' +import { FrequencyInMonth } from '../../enum/frequency.enum' +import './prices.scss' +import PriceSection from './PriceSection' +const Prices: React.FC = () => { + return ( + <> + <div className="header"> + <p className="title pagetitle">Prix des fluides</p> + </div> + <div className="prices"> + <PriceSection + fluid={FluidType.ELECTRICITY} + frequency={FrequencyInMonth.ELECTRICITY} + /> + <PriceSection + fluid={FluidType.WATER} + frequency={FrequencyInMonth.WATER} + /> + <PriceSection fluid={FluidType.GAS} frequency={FrequencyInMonth.GAS} /> + </div> + </> + ) +} + +export default Prices diff --git a/src/components/Prices/prices.scss b/src/components/Prices/prices.scss new file mode 100644 index 00000000..0f0d367b --- /dev/null +++ b/src/components/Prices/prices.scss @@ -0,0 +1,119 @@ +@import '../../styles/config/typography.scss'; +@import '../../styles/config/layout.scss'; +@import '../../styles/config/breakpoints'; +@import '../../styles/config/colors'; + +.prices { + margin-top: $navigator-height; + padding: 1rem; + .title { + margin: 1rem 0; + } + .capital { + text-transform: capitalize; + } + h2 { + margin-bottom: 1rem; + color: $gold; + } + .price-separator { + margin: 1rem 0; + background: white; + } + .flex-bloc { + display: flex; + align-items: center; + .price-select { + position: relative; + } + .euro { + display: block; + margin-left: 0.5rem; + font-weight: bold; + } + .startDate { + margin-left: 1rem; + .date { + margin-left: 0.5rem; + color: $gold; + font-weight: bold; + } + } + } + section { + margin-top: 1rem; + margin-bottom: 2rem; + .history { + button { + @include baseButton(); + background: $grey-dark; + border: solid 1px $gold; + display: flex; + align-items: center; + &:hover { + background: $dark-background; + opacity: 0.8; + } + &.active { + border-radius: 5px 5px 0 0; + } + img { + width: 20px; + margin-left: 0.5rem; + } + span { + color: $gold; + } + } + ul { + transition: all 300ms ease; + background: $grey-dark; + border: solid 1px $gold; + padding: 1rem; + .price-hidden { + display: none; + } + li { + transition: all 300ms ease; + margin: 0.5rem 0; + padding: 0.5rem; + .prix { + transition: all 300ms ease; + font-weight: bold; + min-width: 120px; + } + p, + p span { + transition: all 300ms ease; + margin-left: 1rem; + } + img { + cursor: pointer; + width: 22px; + margin-left: auto; + transition: all 300ms ease; + } + } + .price-selected { + background: $gold; + .prix, + p, + p span { + color: black; + } + } + hr { + margin: 0; + background: white; + } + .showButton { + text-align: center; + color: $gold; + } + } + .icon-active { + transform: rotate(180deg); + } + } + } +} diff --git a/src/components/Routes/Routes.tsx b/src/components/Routes/Routes.tsx index a5c1b43a..5a56426c 100644 --- a/src/components/Routes/Routes.tsx +++ b/src/components/Routes/Routes.tsx @@ -3,6 +3,7 @@ import { Redirect, Route, Switch } from 'react-router-dom' import { UserContext } from '../../hooks/userContext' import Editing from '../Editing/Editing' import Login from '../Login/Login' +import Prices from '../Prices/Prices' import Settings from '../Settings/Settings' import PrivateRoute from './PrivateRoute' @@ -14,6 +15,7 @@ const Routes: React.FC = () => { {user && <Redirect path="/login" to="/editing" />} <Route path="/login" component={Login} /> <PrivateRoute path="/editing" component={Editing} exact /> + <PrivateRoute path="/prices" component={Prices} exact /> <PrivateRoute path="/settings" component={Settings} exact /> <Redirect path="*" to="/editing" /> </Switch> diff --git a/src/constants/routes.json b/src/constants/routes.json index 21414496..c0f67b83 100644 --- a/src/constants/routes.json +++ b/src/constants/routes.json @@ -3,6 +3,10 @@ "label": "Edition", "path": "/editing" }, + { + "label": "Prix", + "path": "/prices" + }, { "label": "Paramètres", "path": "/settings" diff --git a/src/enum/fluidTypes.ts b/src/enum/fluidTypes.ts new file mode 100644 index 00000000..62e46bca --- /dev/null +++ b/src/enum/fluidTypes.ts @@ -0,0 +1,5 @@ +export enum FluidType { + ELECTRICITY = 0, + WATER = 1, + GAS = 2, +} diff --git a/src/enum/frequency.enum.ts b/src/enum/frequency.enum.ts new file mode 100644 index 00000000..211ae180 --- /dev/null +++ b/src/enum/frequency.enum.ts @@ -0,0 +1,5 @@ +export enum FrequencyInMonth { + ELECTRICITY = 6, + WATER = 12, + GAS = 1, +} diff --git a/src/models/price.model.ts b/src/models/price.model.ts new file mode 100644 index 00000000..fb1e52d1 --- /dev/null +++ b/src/models/price.model.ts @@ -0,0 +1,6 @@ +export interface IPrice { + fluidType: number + price: number | string + startDate: string + endDate: string | null +} diff --git a/src/services/prices.service.ts b/src/services/prices.service.ts new file mode 100644 index 00000000..723b1360 --- /dev/null +++ b/src/services/prices.service.ts @@ -0,0 +1,36 @@ +import axios from 'axios' +import { toast } from 'react-toastify' +import { IPrice } from '../models/price.model' +export class PricesService { + /** + * Save the partnersInfo + * @param price + * @param token + */ + public savePrice = async (price: IPrice, token: string): Promise<void> => { + try { + await axios.put(`/api/admin/prices`, price, { + headers: { + 'XSRF-TOKEN': token, + }, + }) + toast.success('Price succesfully saved !') + } catch (e) { + toast.error('Failed to save price') + console.error(e) + } + } + + /** + * Gets the prices by fluid + */ + public getPricesByFluid = async (fluidType: number): Promise<IPrice[]> => { + try { + const { data } = await axios.get(`/api/common/prices/${fluidType}`) + return data + } catch (e) { + console.error('error', e) + return [] + } + } +} diff --git a/src/styles/config/_typography.scss b/src/styles/config/_typography.scss index 380fea21..55178241 100644 --- a/src/styles/config/_typography.scss +++ b/src/styles/config/_typography.scss @@ -46,6 +46,10 @@ $main-spacing: 4px; &:hover { background-color: #b89318; } + &:disabled { + opacity: 0.8; + cursor: not-allowed; + } } .btnValid { @include baseButton(); diff --git a/yarn.lock b/yarn.lock index 921c761b..b713717b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3716,6 +3716,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +dayjs@^1.10.7: + version "1.10.7" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468" + integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" -- GitLab