diff --git a/README.md b/README.md index 971b75585554652a8938b5d54ced63663a3c17cb..2f13fe2d9a25648d82d804b3e0a3082b5a23cf27 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,57 @@ -# Template repository for React (TypeScript) with Tailwind +Projet ERP RMS <img src="./public/logo.png" width="60" height="90" /> +============== [](https://drone.princelle.org/erp-sil/rms) -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +Ce projet est une interface, mise a disposition pour l'administration de l'[AIUS](https://fr-fr.facebook.com/aius.strasbourg/). Elle materialise une caisse enregistreuse dématerialisée permetant la gestion des achat et du stock. -Tailwind was installed with the method provided in the [documentation on the official website](https://tailwindcss.com/docs/guides/create-react-app). +### Table des matières +1. [Installation du module](#module-installation) +2. [Dépendences](#dependencies) +6. [Configuration](#configuration) +7. [Fonctionnalité](#module-operating) -## Available Scripts +### Installation du module <a name="module-installation"></a> -In the project directory, you can run: +Dans le dossier \RMS, vous pouvez lancer la commande : +#### `docker-compose up` -### `yarn start` -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. +### Dépendences <a name="dependencies"></a> -The page will reload if you make edits.\ -You will also see any lint errors in the console. +Ce projet est crée avec [Create React App](https://fr.reactjs.org/) et [Tailwind](https://tailwindcss.com/). -### `yarn test` +### Confirguration <a name="configuration"></a> -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. +Rajoutez un fichier `.env` à la racine du dossier \RMS, suivant ce model: -### `yarn build` +``` +REACT_APP_BACK_URL="" +REACT_APP_MONEY_URL="" +REACT_APP_STUDENT_URL="" +REACT_APP_BACK_API_KEY="" +APP_PORT= +``` -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. +### Fonctionnalité <a name="module-operating"></a> -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! +Cette application se décompose en trois espaces: -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. +#### Vente -### `yarn eject` +L'espace vente affiche la liste des produits disponibles. +Il permet la mise en pannier de produits et de les facturer. -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** +<img src="./public/vente.png" width="750" height="300" /> -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. +#### Gestion des stocks -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. +L'espace de Gestion des stocks permet de redéfinir la quantitité disponible de chaque produit. -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. +<img src="./public/stock.png" width="750" height="300" /> -## Learn More +#### Utilisateurs -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). +L'espace Utilisateur affiche la liste des utilisateurs en affichant leurs caractéristiques notament s'ils sont adhérant. -To learn React, check out the [React documentation](https://reactjs.org/). +<img src="./public/utilisateurs.png" width="750" height="200" /> \ No newline at end of file diff --git a/public/stock.png b/public/stock.png new file mode 100644 index 0000000000000000000000000000000000000000..4492f3d43b1a134c69b36208ab06c094368e056a Binary files /dev/null and b/public/stock.png differ diff --git a/public/utilisateurs.png b/public/utilisateurs.png new file mode 100644 index 0000000000000000000000000000000000000000..4fc9b4b94b7591c51372a605896531b0e5a51290 Binary files /dev/null and b/public/utilisateurs.png differ diff --git a/public/vente.png b/public/vente.png new file mode 100644 index 0000000000000000000000000000000000000000..954343b7a0b81ac0e52d44bbac250433dafac28a Binary files /dev/null and b/public/vente.png differ diff --git a/src/apis/back.ts b/src/apis/back.ts index d53e6a9d7f69eed59f91dc55d0e1e32602fb377c..96688eb191b0067956b56b25e117c3108b7789cf 100644 --- a/src/apis/back.ts +++ b/src/apis/back.ts @@ -1,4 +1,5 @@ import axios from 'axios'; + import { Product } from '../cart/CartTypes'; import { mockedStocks } from './mocks'; @@ -21,6 +22,7 @@ export async function getItems(): Promise<Product[]> { // Format products data const products = stocks.map(stock => new Product({ + id: stock.item.id, name: stock.item.name, category: stock.item.category.name, price: stock.item.price, @@ -28,7 +30,27 @@ export async function getItems(): Promise<Product[]> { stock: stock.quantity })); + // console.table(products); + return products; +} + +export async function updateStock(itemId: number, stock: number): Promise<boolean> { + const response = await axios.put(`${BASE_URL}/stock`, { + item_id: itemId, + quantity: stock + }, { + headers: { + 'Content-Type': 'application/json', + 'apikey': APIKEY, + } + }).then(({ status }) => status === 200) + .catch(error => { + console.error(error); + return false; + }); + + return response; }; export async function getCategories(): Promise<any[]> { @@ -83,4 +105,4 @@ export async function addItem(name: string, price: number, priceAdherant: number } else { console.error("Error while creating item"); } -} \ No newline at end of file +} diff --git a/src/apis/mocks.ts b/src/apis/mocks.ts index 0783b8ba1c6a16f7c6d406992721b6efbda879fc..566880dfaf94bb6f3cc22a3bbf43e92be91c7289 100644 --- a/src/apis/mocks.ts +++ b/src/apis/mocks.ts @@ -6,7 +6,7 @@ export const mockedStocks: any[] = [ price: 0.5, subscriber_price: 0.5, category: { - name: "Boisson" + name: "Boissons" } } }, @@ -17,7 +17,7 @@ export const mockedStocks: any[] = [ price: 0.5, subscriber_price: 0.5, category: { - name: "Boisson" + name: "Boissons" } } }, @@ -28,7 +28,7 @@ export const mockedStocks: any[] = [ price: 0.5, subscriber_price: 0.5, category: { - name: "Boisson" + name: "Boissons" } } }, @@ -39,7 +39,7 @@ export const mockedStocks: any[] = [ price: 0.6, subscriber_price: 0.6, category: { - name: "Snack" + name: "Snacks" } } }, @@ -50,7 +50,7 @@ export const mockedStocks: any[] = [ price: 0.3, subscriber_price: 0.3, category: { - name: "Snack" + name: "Snacks" } } }, @@ -61,7 +61,7 @@ export const mockedStocks: any[] = [ price: 0.5, subscriber_price: 0.5, category: { - name: "Snack" + name: "Snacks" } } }, @@ -77,3 +77,26 @@ export const mockedStocks: any[] = [ } }, ]; + +export const mockedUsers: any[] = [ + { + "student_number": 12345678, + "username": "12345678", + "lastname": "Princelle", + "firstname": "Maxime", + "email": "princelle@etu.unistra.fr", + "date_subscription": null, + "picture": "https://cdn.pixabay.com/photo/2018/09/06/18/26/person-3658927_960_720.png", + "date_birth": "1999-06-05", + }, + { + "student_number": 87654321, + "username": "Test User", + "lastname": "Test", + "firstname": "User", + "email": "test@test.test", + "date_subscription": "2021-01-01", + "picture": "https://cdn.pixabay.com/photo/2018/09/06/18/26/person-3658927_960_720.png", + "date_birth": "2000-01-01", + }, +]; diff --git a/src/apis/money.ts b/src/apis/money.ts index d8f0e0a290d1ed7ae41a8519159ecd619c8d33a0..fae3ef252a0c92a60e12da99882ccad8a0b48723 100644 --- a/src/apis/money.ts +++ b/src/apis/money.ts @@ -5,7 +5,7 @@ const BASE_URL = "https://" + process.env.REACT_APP_MONEY_URL; // Fetch transactions from API export async function getTransactions(studentNumber: string): Promise<any> { // Fetch transactions data - const transactionsData: any[] = await axios.get(`${BASE_URL}/api/etudiants`, { + const transactionsData: any[] = await axios.get(`${BASE_URL}/transactions/get/${studentNumber}`, { headers: { 'Content-Type': 'application/json', } @@ -19,4 +19,26 @@ export async function getTransactions(studentNumber: string): Promise<any> { // TODO: Format transaction data return transactionsData; -} \ No newline at end of file +} + +export async function sendTransaction(studentNumber: string, userName: string, price: number): Promise<boolean> { + return await axios.post(`${BASE_URL}/payments/add/mastercard`, { + student_number: parseInt(studentNumber), + user_name: userName, + payment_method: "TODO", + payment_name: "mastercard", + identifier: "123454678", + amount: price, + }, { + headers: { + 'Content-Type': 'application/json', + } + }).then((res) => { + console.log(res); + return res.status === 200; + }) + .catch(error => { + console.error(error); + return false; + }); +} diff --git a/src/apis/student.ts b/src/apis/student.ts index 27e1a7e30ef3b06914cec36c6c079bc6ca816576..8a48163b782f2be9486c228db221d2424de0c0ff 100644 --- a/src/apis/student.ts +++ b/src/apis/student.ts @@ -1,5 +1,7 @@ import axios from 'axios'; +import { mockedUsers } from './mocks'; + export class User { nom: string; prenom: string; @@ -30,7 +32,7 @@ export async function getUsers(): Promise<User[]> { }).then(({ data }) => data) .catch(error => { console.error(error); - return []; + return mockedUsers; }); // Format users data @@ -46,6 +48,23 @@ export async function getUsers(): Promise<User[]> { return users; }; +export async function setAdhesion(studentNumber: string) : Promise<boolean> { + // Set adhesion for given user + const success = await axios.post(`${BASE_URL}/api/pay_adhesion`, { + 'student_number': studentNumber ?? '12345678' + }, { + headers: { + 'Content-Type': 'application/json', + } + }).then(({ status }) => status === 200) + .catch(error => { + console.error(error); + return false; + }); + + return success; +} + function validateSubscriptionDate(dateStr: string): boolean { const YEAR_IN_MS = 31536000000; const date = Date.parse(dateStr) ?? Date.parse('01 Jan 1970 00:00:00 GMT'); diff --git a/src/cart/CartTypes.ts b/src/cart/CartTypes.ts index 17bbb867b7b97c05c0b05f48da89a2bcc7549912..c154c65393facc6cf9435554274e0fff00becf12 100644 --- a/src/cart/CartTypes.ts +++ b/src/cart/CartTypes.ts @@ -1,6 +1,7 @@ import { Dispatch } from "react"; export class Product { + public id: number; public name: string; public category: string; public price: number; diff --git a/src/components/sell/cart.tsx b/src/components/sell/cart.tsx index 8fb3c1a2a78e830df5f359e7d8e74927940e3f07..94189ecd752d727f719b962589865ff504584c26 100644 --- a/src/components/sell/cart.tsx +++ b/src/components/sell/cart.tsx @@ -1,13 +1,17 @@ import {Fragment, useContext, useEffect, useState} from 'react' import { Dialog, Transition } from '@headlessui/react' -import { ArrowLeftIcon, EmojiSadIcon } from '@heroicons/react/outline'; -import { XIcon } from '@heroicons/react/outline' +import { ArrowLeftIcon, EmojiSadIcon, XIcon } from '@heroicons/react/outline'; +import { XCircleIcon } from '@heroicons/react/solid'; import { cartContext } from '../../cart/CartStore'; import { updateProductQuantity } from '../../cart/CartActions'; +import { setAdhesion } from '../../apis/student'; +import { sendTransaction } from '../../apis/money'; +import { updateStock } from '../../apis/back'; const Cart = () => { + let [cartError, setCartError] = useState(""); const { cartState, dispatch } = useContext(cartContext); function renderPrice(price: number): string { @@ -15,8 +19,41 @@ const Cart = () => { return `${price.toFixed(2).replace(".", ",")} €`; } - let [totalPrice, setTotalPrice] = useState(0); + async function doValidate() { + const studentNumber: string = "12345678"; + const userName: string = "UserName"; + + // Validate transaction with Money + const successTransaction = await sendTransaction(studentNumber, userName, cartState.totalPrice); + if (!successTransaction) { + console.error("Error during transaction with Money."); + setCartError("Error during transaction with payement server."); + return; + } + + // Set adhesion in Student + const adhesionInCart = cartState.cart.find(p => p.name === "Adhésion"); + if (adhesionInCart !== undefined){ + const adhesionSuccess = await setAdhesion(studentNumber); + if (!adhesionSuccess) { + console.error("Error during adhesion with Student."); + setCartError("Error during adhesion with student database."); + } + } + + // Update stock in Back + cartState.cart.forEach(product => { + updateStock(product.id, -(product?.quantity ?? 1)); + }); + + // Reset cart error message + setCartError(""); + + // Reset cart + dispatch({ type: 'SET_CART', payload: [] }); + } + let [totalPrice, setTotalPrice] = useState(0); useEffect(() => { // Update cart totalPrice let totalPrice = 0; @@ -164,14 +201,27 @@ const Cart = () => { </div> <div className="mt-10"> <button - type="submit" - className="w-full bg-indigo-600 border border-transparent rounded-md shadow-sm py-3 px-4 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-50 focus:ring-indigo-500" + onClick={() => doValidate()} + type="button" + className="w-full bg-indigo-600 border border-transparent rounded-md shadow-sm py-3 px-4 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-50 focus:ring-indigo-500" > Valider </button> </div> </div> } + {cartError && + <div className="rounded-md bg-red-50 p-4 mt-4"> + <div className="flex"> + <div className="flex-shrink-0"> + <XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" /> + </div> + <div className="ml-3"> + <h3 className="text-sm font-medium text-red-800">{cartError}</h3> + </div> + </div> + </div> + } </form> </div> </div>