-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
626ee2c
commit 6aff9be
Showing
5 changed files
with
265 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import Keycloak from "keycloak-js" | ||
import { ApiInvoker } from "../api/ApiInvoker" | ||
|
||
export class AuthenticationService { | ||
keycloak: Keycloak | ||
|
||
constructor(keycloak: Keycloak) { | ||
this.keycloak = keycloak | ||
} | ||
|
||
isAuthenticated(): boolean { | ||
return (!!process.env.REACT_APP_MOCK_API) || (!!this.keycloak.authenticated) | ||
} | ||
|
||
getCurrentUserName(): string | undefined { | ||
if(process.env.REACT_APP_MOCK_API) { | ||
return "Utilisateur TEST" | ||
} | ||
if (this.isAuthenticated()) { | ||
return this.keycloak.tokenParsed?.name | ||
} | ||
return undefined | ||
}} |
29 changes: 29 additions & 0 deletions
29
frontend/app/src/domains/production/components/PackageLotDescription.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
.package-lot { | ||
margin: 1rem; | ||
display: grid; | ||
grid-template-columns: 1fr 3fr; | ||
} | ||
|
||
.package-lot__label { | ||
font-size: large; | ||
grid-column: 1/-1; | ||
} | ||
|
||
.package-lot__description { | ||
font-size: smaller; | ||
grid-column: 1/-1; | ||
} | ||
|
||
.package-lot__icon { | ||
grid-column: 1; | ||
} | ||
|
||
.package-lot__total_price, | ||
.package-lot__unit_price, | ||
.package-lot__weight { | ||
grid-column: 2/-1; | ||
} | ||
|
||
.package-lot__unit_price { | ||
font-size: smaller; | ||
} |
24 changes: 24 additions & 0 deletions
24
frontend/app/src/domains/production/components/PackageLotDescription.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React from 'react' | ||
|
||
import './PackageLotDescription.css' | ||
|
||
export default function PackageLotDescription({ lot: lot }) { | ||
return <div className='package-lot'> | ||
<div className='package-lot__label'>{lot.label}</div> | ||
<div className='package-lot__description'>{lot.description}</div> | ||
<span className='icon euro-icon package-lot__icon'></span> | ||
<div className='package-lot__total_price'>{lot.unitPrice * lot.netWeight} €<sup>TTC</sup></div> | ||
<div className='package-lot__unit_price'>({lot.unitPrice} €<sup>TTC</sup>/kg)</div> | ||
<span className='icon weight-icon package-lot__icon'></span> | ||
<div className='package-lot__weight'>~{lot.netWeight} kg</div> | ||
{getImage()} | ||
</div> | ||
|
||
function getImage() { | ||
if (lot.photo) { | ||
return <img src={'data:image/png;base64,' + lot.photo} ></img> | ||
} else { | ||
return <></> | ||
} | ||
} | ||
} |
155 changes: 155 additions & 0 deletions
155
frontend/app/src/domains/sale/views/CustomerOrderForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import React from 'react' | ||
import { useState, useEffect } from 'react' | ||
|
||
import { Button, Checkbox, Stepper, Step, StepLabel, StepContent, Typography, Box, Toolbar, ButtonGroup } from "@mui/material" | ||
import dayjs from 'dayjs' | ||
|
||
import { useKeycloak } from '@react-keycloak/web' | ||
import { ApiInvoker } from '../../../api/ApiInvoker.ts' | ||
|
||
import OrderItem from "viandeendirect_eu/dist/model/OrderItem" | ||
import Order from "viandeendirect_eu/dist/model/Order" | ||
import Production from "viandeendirect_eu/dist/model/Production" | ||
import PackageLot from "viandeendirect_eu/dist/model/PackageLot" | ||
|
||
import PackageSelector from '../components/PackageSelector.tsx' | ||
import { AuthenticationService } from '../../../authentication/AuthenticationService.ts' | ||
|
||
export default function CustomerOrderForm({ sale: sale, returnCallback: returnCallback }) { | ||
window.scroll(0,0) | ||
|
||
const SET_ITEMS_STEP = 1 | ||
const SET_CUSTOMER_STEP = 2 | ||
const CONFIRMATION_STEP = 3 | ||
const PAYMENT_STEP = 4 | ||
|
||
const { keycloak, initialized } = useKeycloak() | ||
const apiInvoker = new ApiInvoker() | ||
const authenticationService = new AuthenticationService(keycloak) | ||
|
||
const [productions, setProductions] = useState<Array<Production>>([]) | ||
const [order, setOrder] = useState<Order>({sale: sale}) | ||
const [items, setItems] = useState<Array<OrderItem>>([]) | ||
const [activeStep, setActiveStep] = useState(SET_ITEMS_STEP) | ||
const [conditionApproved, setConditionApproved] = useState<boolean>(false) | ||
|
||
useEffect(() => { | ||
apiInvoker.callApiAnonymously(api => api.getSaleProductions, sale.id, setProductions) | ||
}, []) | ||
|
||
return <Box component="main" sx={{ flexGrow: 1, p: 3 }}> | ||
<Toolbar/> | ||
<Typography variant='h6'>Votre commande pour la vente du {dayjs(sale.deliveryStart).format('DD/MM/YYYY')} - {sale.deliveryAddressName}</Typography> | ||
|
||
<Stepper activeStep={activeStep} orientation="vertical"> | ||
<Step active={activeStep === SET_ITEMS_STEP}> | ||
<StepLabel>Sélectionnez les articles commandés</StepLabel> | ||
<StepContent> | ||
<div className='packages-list'> | ||
{packageLots()} | ||
</div> | ||
<div> | ||
{itemsValidationButton()} | ||
</div> | ||
</StepContent> | ||
</Step> | ||
<Step active={activeStep === SET_CUSTOMER_STEP}> | ||
<StepLabel>{getAuthenticationStepLabel()}</StepLabel> | ||
<StepContent> | ||
{getAuthenticationStepContent()} | ||
</StepContent> | ||
</Step> | ||
<Step active={activeStep === CONFIRMATION_STEP}> | ||
<StepLabel>Acceptez les conditions</StepLabel> | ||
<StepContent> | ||
<div> | ||
<div> | ||
En validant ma commande je m'engage à : | ||
<ol> | ||
<li>venir chercher la marchandise au rendez-vous fixé : | ||
<div>le {dayjs(sale.deliveryStart).format('DD/MM/YYYY')} entre {dayjs(sale.deliveryStart).format('HH:mm')} et {dayjs(sale.deliveryStop).format('HH:mm')}</div> | ||
<div>à l'adresse suivante :</div> | ||
<div>{sale.deliveryAddressLine1}</div> | ||
<div>{sale.deliveryAddressLine2}</div> | ||
<div>{sale.deliveryZipCode}</div> | ||
<div>{sale.deliveryCity}</div> | ||
</li> | ||
<li>respecter la chaîne du froid dès la réception de la marchandise</li> | ||
</ol> | ||
</div> | ||
<div> | ||
<Checkbox onChange={toggleConditionApproved}/>J'accepte les conditions | ||
</div> | ||
</div> | ||
<Button disabled={!conditionApproved} variant="contained" size="small" onClick={() => setActiveStep(PAYMENT_STEP)}>Valider la commande</Button> | ||
</StepContent> | ||
</Step> | ||
<Step active={activeStep === PAYMENT_STEP}> | ||
<StepLabel>Payez votre commande</StepLabel> | ||
</Step> | ||
</Stepper> | ||
{getFinalButton()} | ||
</Box> | ||
|
||
function packageLots() { | ||
return productions.flatMap(production => production.lots).map(lot => packageSelector(lot)) | ||
} | ||
|
||
function itemsValidationButton(){ | ||
const totalAmount = items.map(item => item.packageLot.unitPrice * item.packageLot.netWeight * item.quantity) | ||
.reduce((total, amount) => total + amount, 0) | ||
const buttonLabel = totalAmount > 0 ? `Valider la commande pour ${totalAmount} €TTC` : 'Valider la commande' | ||
const disabled = totalAmount === 0 | ||
return <Button type='submit' variant="contained" size="small" onClick={validateItems} disabled={disabled}>{buttonLabel}</Button> | ||
} | ||
|
||
function packageSelector(lot: PackageLot) { | ||
return <PackageSelector lot={lot} orderItems={items} updateItemsCallback={setItems}></PackageSelector> | ||
} | ||
|
||
function validateItems() { | ||
setActiveStep(SET_CUSTOMER_STEP) | ||
setOrder({...order, items: items}) | ||
} | ||
|
||
function getAuthenticationStepLabel(): string { | ||
if (authenticationService.isAuthenticated()) { | ||
return "Votre commande est au nom de " + authenticationService.getCurrentUserName() | ||
} | ||
return "Identifiez-vous" | ||
} | ||
|
||
function getAuthenticationStepContent() { | ||
if (authenticationService.isAuthenticated()) { | ||
return <ButtonGroup size='small'> | ||
<Button variant='contained' onClick={validateOrder}>Continuer avec ce compte</Button> | ||
<Button>Se déconnecter et changer de compte</Button> | ||
</ButtonGroup> | ||
} return <Button>Se connecter</Button> | ||
} | ||
|
||
function validateOrder() { | ||
createOrder(order) | ||
setActiveStep(CONFIRMATION_STEP) | ||
} | ||
|
||
function createOrder(order: Order) { | ||
apiInvoker.callApiAuthenticatedly(api => api.createOrder, order, () => {}, keycloak) | ||
} | ||
|
||
function toggleConditionApproved() { | ||
setConditionApproved(!conditionApproved) | ||
} | ||
|
||
function getFinalButton() { | ||
if (conditionApproved && activeStep === PAYMENT_STEP) { | ||
return <Button size="small" variant='contained' onClick={payOrder}>Payer ma commande</Button> | ||
} | ||
return <Button size="small" onClick={() => returnCallback(sale)}>Abandonner</Button> | ||
} | ||
|
||
function payOrder() { | ||
//TODO : faire le truc qui paye | ||
returnCallback(sale) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import React from 'react' | ||
import { useEffect, useState } from 'react' | ||
|
||
import { AppBar, Box, CssBaseline, Toolbar, Typography } from '@mui/material' | ||
|
||
import Sale from 'viandeendirect_eu/dist/model/Sale.js' | ||
|
||
import { ApiInvoker } from '../../api/ApiInvoker.ts' | ||
import SaleCustomerCard from '../../domains/sale/components/SaleCustomerCard.tsx' | ||
|
||
|
||
export default function Welcome({createOrderCallback: createOrderCallback}) { | ||
|
||
const [sales, setSales] = useState([]) | ||
const apiInvoker = new ApiInvoker() | ||
|
||
useEffect(() => { | ||
apiInvoker.callApiAnonymously(api => api.getSales, null, setSales) | ||
}, []) | ||
|
||
|
||
return <Box | ||
component="main" | ||
sx={{ flexGrow: 1, p: 3 }}> | ||
<Toolbar /> | ||
<Typography>Bienvenu dans l'espace client de ViandeEnDirect.eu</Typography> | ||
{sales.map(getSaleCard)} | ||
</Box> | ||
|
||
function getSaleCard(sale: Sale) { | ||
return <SaleCustomerCard sale={sale} createOrderCallback={() => createOrderCallback(sale)}></SaleCustomerCard> | ||
} | ||
|
||
} |