First Commit

This commit is contained in:
Maxime Boulay
2024-12-23 08:05:29 +01:00
commit ba64ceb471
171 changed files with 654157 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
import React from 'react'
import {FaArrowCircleLeft} from 'react-icons/fa'
import {Link} from 'react-router-dom'
import { useTranslation } from 'react-i18next';
const BackButton = ({url}) => {
const { t } = useTranslation(["Purchase"]);
return (
<Link className="btn btn-reverse btn-black" to={url}>
<FaArrowCircleLeft /> {t("back")}
</Link>
)
}
export default BackButton

View File

@@ -0,0 +1,61 @@
import React from 'react';
import {FaSignInAlt, FaSignOutAlt, FaUser} from 'react-icons/fa'
import {Link, useNavigate} from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux';
import {logout, reset} from '../features/auth/authSlice'
import LanguageSelector from './LanguageSelector';
import { useTranslation } from 'react-i18next';
function Header() {
const {user} = useSelector(state => state.auth)
const dispatch = useDispatch()
const navigate = useNavigate()
const { t } = useTranslation(["Header"]);
const onLogout = async() => {
await dispatch(logout())
dispatch(reset())
navigate('/login')
}
return (
<div className="header">
<div className="logo">
<Link to='/'>GiecChallenge</Link>
</div>
<ul>
{
!user ? (
<>
<li>
<Link to='/login'>
<FaSignInAlt /> {t("login")}
</Link>
</li>
<li>
<Link to='/register'>
<FaUser /> {t("register")}
</Link>
</li>
<li>
<LanguageSelector />
</li>
</>) : (
<>
<li>
<a onClick={onLogout} href='/'>
<FaSignOutAlt /> {t("logout")}
</a>
</li>
<li>
<LanguageSelector />
</li>
</>
)
}
</ul>
</div>
);
}
export default Header;

View File

@@ -0,0 +1,77 @@
import React, { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import Spinner from '../../components/Spinner'
import ProgressBar from 'react-bootstrap/ProgressBar';
import { reset, getpurchasesbydate, deletepurchase, getCO2bydate} from '../../features/purchases/purchaseSlice'
import {LinePurchase} from './LinePurchase'
import { useTranslation } from 'react-i18next';
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
export const HomePurchase = () => {
const { isLoading, purchases, CO2Emissions } = useSelector((state) => state.purchase)
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(new Date());
const [purchasesUser, setPurchasesUser] = useState([]);
const [CO2EmissionsPurcentage, setCO2EmissionsPurcentage] = useState(0.0);
const { t } = useTranslation(["Home"]);
const dispatch = useDispatch()
useEffect(() => {
setPurchasesUser(purchases);
setCO2EmissionsPurcentage(CO2Emissions / (2500 / 365 * (endDate.getDate() - startDate.getDate() + 1)) * 100);
}, [purchases, CO2Emissions])
useEffect(() => {
changeDate();
}, [startDate, endDate])
function changeDate() {
dispatch(reset())
dispatch(getpurchasesbydate({
startDate: startDate.toLocaleDateString("es-CL"),
endDate: endDate.toLocaleDateString("es-CL")
}));
dispatch(getCO2bydate({
startDate: startDate.toLocaleDateString("es-CL"),
endDate: endDate.toLocaleDateString("es-CL")
}));
}
const deletePurchase = (p) => {
dispatch(reset());
dispatch(deletepurchase(p)).then(
changeDate()
);
}
if (isLoading)
return <Spinner />
else
return (
<>
<div className='mbottom-10'>
<ProgressBar now={CO2EmissionsPurcentage} label={`${CO2EmissionsPurcentage}%`} />
</div>
<div style={{display: "flex"}} className="form-group">
<DatePicker dateFormat="dd/MM/yyyy" selected={startDate} className="form-control" onChange={(date) => {setStartDate(date);}} />
<DatePicker dateFormat="dd/MM/yyyy" selected={endDate} className="form-control" onChange={(date) => {setEndDate(date);}} />
</div>
<div className='flex'>
<div className='form-group width-20'>{t("date")}</div>
<div className='form-group width-20'>{t("CO2Cost")}</div>
<div className='form-group width-20'>{t("WaterCost")}</div>
</div>
{
purchasesUser.map((purchaseUser, index) => {
return (
<LinePurchase key={index} purchaseUser={purchaseUser} onChange={(p) => deletePurchase(p)} />
)
})
}
</>
);
}
export default HomePurchase

View File

@@ -0,0 +1,22 @@
import React from 'react';
import {Link} from 'react-router-dom'
import { FaTrash } from 'react-icons/fa';
export const LinePurchase = ({ purchaseUser, onChange }) => {
return (
<>
<div className='flex'>
<div className='form-group width-20 inline-flex'>
<Link to={`/purchase/${purchaseUser.id}`}>{new Date(Date.parse(purchaseUser.datePurchase)).toLocaleString("fr-FR", {month: '2-digit',day: '2-digit',year: 'numeric'})}</Link>
</div>
<div className='form-group width-20 inline-flex'>{parseFloat(purchaseUser.cO2Cost).toFixed(2)}</div>
<div className='form-group width-20 inline-flex'>{parseFloat(purchaseUser.waterCost).toFixed(2)}</div>
<div>
<a onClick={() => onChange(purchaseUser.id)} href="#/" className='btn btn-reverse btn-block'>
<FaTrash />
</a>
</div>
</div>
</>
);
};

View File

@@ -0,0 +1,22 @@
import React from "react";
import { useTranslation } from "react-i18next";
const LanguageSelector = () => {
const { i18n } = useTranslation();
const changeLanguage = (event) => {
i18n.changeLanguage(event.target.value);
};
return (
<div onChange={changeLanguage}>
<select name="language">
<option value="FR">Français</option>
<option value="EN">English</option>
</select>
</div>
);
};
export default LanguageSelector;

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
export const LineGroup = ({ nameSelect, languageOptions, onChange }) => {
const [group, setGroup] = useState('')
const [language, setLanguage] = useState('')
const { t } = useTranslation(["NewProduct"]);
useEffect(() => {
onChange({
group,
language,
nameSelect
});
}, [onChange, group, language]);
return (
<>
<div className="form-group">
<label htmlFor="command">{t("group")}</label>
<input type="language" name="group" id="group" className='form-control width-100' value={group} placeholder={t("group")} onChange={(e) => setGroup(e.target.value)} />
<label htmlFor="language">{t("language")}</label>
<select name="language" id="language" value={language} className="form-control width-100" onChange={(e) => setLanguage(e.target.value)}>
{languageOptions}
</select>
</div>
</>
);
};

View File

@@ -0,0 +1,92 @@
import React from 'react';
import { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom';
import {reset, create} from '../../../features/groups/groupSlice'
import {FaShoppingCart} from 'react-icons/fa'
import { LineGroup } from './LineGroup'
import { toast } from 'react-toastify'
import Spinner from '../../../components/Spinner'
import { useTranslation } from 'react-i18next';
export const NewGroup = ({ languagesOption }) => {
const {isLoading, isError, isSuccess, message } = useSelector((state) => state.group)
const [groups, setGroups] = useState([{key: 0, name: "group0" }])
const { t } = useTranslation(["NewProduct"]);
const [lineGroupsData, setLineGroupsData] = useState([])
const dispatch = useDispatch()
const navigate = useNavigate()
useEffect(() => {
if (isError)
toast.error(message)
if (isSuccess) {
dispatch(reset())
setGroups([{key: 0, name: "group0" }])
setLineGroupsData([])
toast.success(t("groupSuccess"))
}
}, [isError, isSuccess, message, navigate, dispatch])
const onSubmit = (e) => {
e.preventDefault()
const newGroup = {
names: lineGroupsData.map((groupLine) => (
{
name: groupLine.group,
language: groupLine.language
}
))
}
dispatch(create(newGroup))
}
const addOrModifyLineLanguage = (lineLanguage) => {
if (languagesOption[0] !== undefined && lineLanguage.language === '')
lineLanguage.language = languagesOption[0].key;
let existingLine = lineGroupsData.findIndex(lpd => lpd.nameSelect === lineLanguage.nameSelect);
if (existingLine === -1)
setLineGroupsData(lineGroupsData.concat(lineLanguage))
else {
lineGroupsData[existingLine] = lineLanguage
setLineGroupsData(lineGroupsData)
}
}
const addNewGroup = () => {
setGroups(groups.concat([
{key: groups.length, name: "group" + groups.length }
]));
}
if (isLoading)
return <Spinner />
return (
<fieldset>
<legend>{t("newGroup")}</legend>
<form onSubmit={onSubmit} className="form-group">
{
groups.map((item) => (
<LineGroup key={item.key} languageOptions={languagesOption} nameSelect={item.key} onChange={(e) => addOrModifyLineLanguage(e)}/>
))
}
<div>
<a onClick={() => addNewGroup()} href="#/" className='btn btn-reverse btn-block'>
<FaShoppingCart /> {t("addNewTranslation")}
</a>
</div>
<div className="form-group">
<button className="btn btn-block">
{t("submit")}
</button>
</div>
</form>
</fieldset>
);
}
export default NewGroup

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
export const LineProductItem = ({ nameSelected, languageOptions, onChange }) => {
const [productName, setProductName] = useState('')
const [language, setLanguage] = useState('')
const { t } = useTranslation(["NewProduct"]);
useEffect(() => {
onChange({
nameSelected,
productName,
language
});
}, [onChange, productName, language]);
return (
<>
<div className="form-group">
<label htmlFor="product">{t("product")}</label>
<input type="text" name="productName" id="productName" className='form-control width-100' value={productName} placeholder={t("product")} onChange={(e) => setProductName(e.target.value)} />
<label htmlFor="language">{t("language")}</label>
<select name="language" id="language" value={language} className="form-control width-100" onChange={(e) => setLanguage(e.target.value)}>
{languageOptions}
</select>
</div>
</>
);
};

View File

@@ -0,0 +1,108 @@
import React from 'react';
import { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom';
import {reset, create} from '../../../features/products/productSlice'
import {FaShoppingCart} from 'react-icons/fa'
import { LineProductItem } from './LineProductItem'
import { toast } from 'react-toastify'
import Spinner from '../../../components/Spinner'
import { useTranslation } from 'react-i18next';
import {SubGroupSearchBox} from '../../SearchBox/SubGroupSearchBox'
export const NewProductItem = ({ languagesOption }) => {
const {isLoading, isError, isSuccess, message } = useSelector((state) => state.product)
const [selectedValue, setSelectedValue] = useState([]);
const [lineProductsData, setLineProductsData] = useState([{ nameSelected: "product0",productName: "", language: "" }])
const [CO2Emissions, setCO2Emission] = useState('0')
const [waterEmissions, setWaterEmission] = useState('0')
const [amortization, setAmortization] = useState('0')
const { t } = useTranslation(["NewProduct"]);
const dispatch = useDispatch()
const navigate = useNavigate()
useEffect(() => {
if (isError)
toast.error(message)
if (isSuccess) {
dispatch(reset())
setLineProductsData([{nameSelected: "product0", productName: "", language: "" }])
toast.success(t("productSuccess"))
}
}, [isError, isSuccess, message, navigate, dispatch])
const onSubmit = (e) => {
e.preventDefault()
const newProduct = {
group : selectedValue,
CO2: CO2Emissions,
water: waterEmissions,
amortization: amortization,
names: lineProductsData.map((productLine) => (
{
name: productLine.productName,
language: productLine.language
}
))
}
dispatch(create(newProduct))
}
const addOrModifyLineProduct = (lineProduct) => {
if (languagesOption[0] !== undefined && lineProduct.language === '')
lineProduct.language = languagesOption[0].key;
let existingLine = lineProductsData.findIndex(lpd => lpd.nameSelected === lineProduct.nameSelected);
if (existingLine === -1) {
setLineProductsData(lineProductsData.concat(lineProduct))
}
else {
lineProductsData[existingLine] = lineProduct
setLineProductsData(lineProductsData)
}
}
const addNewProduct = () => {
addOrModifyLineProduct(
{ nameSelected: "product" + lineProductsData.length, productName: "", language: "" }
);
}
if (isLoading)
return <Spinner />
return (
<fieldset>
<legend>{t("newProduct")}</legend>
<form onSubmit={onSubmit} className="form-group">
<label htmlFor="product">{t("product")}</label>
<SubGroupSearchBox key="subGroupSelected" className='mbottom-10 width-100' nameSelect="subGroupSelected" onChange={(e) => setSelectedValue(e)} />
<label htmlFor="CO2Emissions">{t("CO2Emissions")}</label>
<input type="text" key="CO2Emissions" className='mbottom-10 width-100' onChange={(e) => setCO2Emission(e.target.value)} />
<label htmlFor="WaterEmissions">{t("waterEmissions")}</label>
<input type="text" key="waterEmissions" className='mbottom-10 width-100'onChange={(e) => setWaterEmission(e.target.value)} />
<label htmlFor="amortization">{t("amortization")}</label>
<input type="text" key="amortization" className='mbottom-10 width-100'onChange={(e) => setAmortization(e.target.value)} />
{
lineProductsData.map((item) => (
<LineProductItem key={item.nameSelected} languageOptions={languagesOption} nameSelected={item.nameSelected} onChange={(e) => addOrModifyLineProduct(e)}/>
))
}
<div>
<a onClick={() => addNewProduct()} href="#/" className='btn btn-reverse btn-block'>
<FaShoppingCart /> {t("addNewTranslation")}
</a>
</div>
<div className="form-group">
<button className="btn btn-block">
{t("submit")}
</button>
</div>
</form>
</fieldset>
);
}
export default NewProductItem

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
export const LineSubGroup = ({ nameSelect, languageOptions, onChange }) => {
const [subGroup, setSubGroup] = useState('')
const [language, setLanguage] = useState('')
const { t } = useTranslation(["NewProduct"]);
useEffect(() => {
onChange({
subGroup,
language,
nameSelect
});
}, [onChange, subGroup, language]);
return (
<>
<div className="form-group">
<label htmlFor="command">{t("subGroup")}</label>
<input type="text" name="subgroup" id="subgroup" className='form-control width-100' value={subGroup} placeholder={t("subGroup")} onChange={(e) => setSubGroup(e.target.value)} />
<label htmlFor="language">{t("language")}</label>
<select name="language" id="language" value={language} className="form-control width-100" onChange={(e) => setLanguage(e.target.value)}>
{languageOptions}
</select>
</div>
</>
);
};

View File

@@ -0,0 +1,97 @@
import React from 'react';
import { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom';
import {reset, create} from '../../../features/subgroups/subGroupSlice'
import {FaShoppingCart} from 'react-icons/fa'
import { LineSubGroup } from './LineSubGroup'
import { toast } from 'react-toastify'
import Spinner from '../../../components/Spinner'
import { useTranslation } from 'react-i18next';
import {GroupSearchBox} from '../../SearchBox/GroupSearchBox'
export const NewSubGroup = ({ languagesOption }) => {
const {isLoading, isError, isSuccess, message } = useSelector((state) => state.group)
const [subgroups, setSubGroups] = useState([{key: 0, name: "subgroup0" }])
const [selectedValue, setSelectedValue] = useState([]);
const { t } = useTranslation(["NewProduct"]);
const [lineSubGroupsData, setLineSubGroupsData] = useState([])
const dispatch = useDispatch()
const navigate = useNavigate()
useEffect(() => {
if (isError)
toast.error(message)
if (isSuccess) {
dispatch(reset())
setSubGroups([{key: 0, name: "subgroup0" }])
setLineSubGroupsData([])
toast.success(t("subGroupSuccess"))
}
}, [isError, isSuccess, message, navigate, dispatch])
const onSubmit = (e) => {
e.preventDefault()
const newSubGroup = {
group : selectedValue,
names: lineSubGroupsData.map((subGroupLine) => (
{
name: subGroupLine.subGroup,
language: subGroupLine.language
}
))
}
dispatch(create(newSubGroup))
}
const addOrModifyLineLanguage = (lineLanguage) => {
if (languagesOption[0] !== undefined && lineLanguage.language === '')
lineLanguage.language = languagesOption[0].key;
let existingLine = lineSubGroupsData.findIndex(lpd => lpd.nameSelect === lineLanguage.nameSelect);
if (existingLine === -1)
setLineSubGroupsData(lineSubGroupsData.concat(lineLanguage))
else {
lineSubGroupsData[existingLine] = lineLanguage
setLineSubGroupsData(lineSubGroupsData)
}
}
const addNewGroup = () => {
setSubGroups(subgroups.concat([
{key: subgroups.length, name: "subgroup" + subgroups.length }
]));
}
if (isLoading)
return <Spinner />
return (
<fieldset>
<legend>{t("newSubGroup")}</legend>
<form onSubmit={onSubmit} className="form-group">
<label htmlFor="group">{t("group")}</label>
<GroupSearchBox key="groupSelected" className='mbottom-10' nameSelect="groupSelected" onChange={(e) => setSelectedValue(e)} />
{
subgroups.map((item) => (
<LineSubGroup key={item.key} languageOptions={languagesOption} nameSelect={item.key} onChange={(e) => addOrModifyLineLanguage(e)}/>
))
}
<div>
<a onClick={() => addNewGroup()} href="#/" className='btn btn-reverse btn-block'>
<FaShoppingCart /> {t("addNewTranslation")}
</a>
</div>
<div className="form-group">
<button className="btn btn-block">
{t("submit")}
</button>
</div>
</form>
</fieldset>
);
}
export default NewSubGroup

View File

@@ -0,0 +1,64 @@
import React from 'react';
import { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom';
import {createLaRuche, reset, resetIsSuccess} from '../../features/purchases/purchaseSlice'
import { toast } from 'react-toastify'
import Spinner from '../../components/Spinner'
import { useTranslation } from 'react-i18next';
export const NewPurchaseLaRuche = ({ datePurchase, purchaseSubmittedForLaRuche }) => {
const {isLoading, isError, isSuccess, message, purchasesToRename} = useSelector((state) => state.purchase)
const [command, setCommand] = useState('')
const { t } = useTranslation(["NewPurchase"]);
const dispatch = useDispatch()
const navigate = useNavigate()
useEffect(() => {
if (isError)
toast.error(message)
if (isSuccess && !isLoading) {
if (purchasesToRename.length === 0) {
dispatch(reset())
navigate('/')
}
else {
dispatch(resetIsSuccess())
purchaseSubmittedForLaRuche(purchasesToRename)
}
}
}, [isError, isSuccess, message, navigate, dispatch])
const onSubmit = (e) => {
e.preventDefault()
const purchase = {
datePurchase: datePurchase,
command: command
}
dispatch(createLaRuche(purchase))
}
if (isLoading)
return <Spinner />
return (
<>
<form onSubmit={onSubmit} className="form-group">
<div className="form-group">
<label htmlFor="command">{t("command")}</label>
<textarea name="command" id="command" className='form-control width-100' value={command} placeholder={t("command")} onChange={(e) => setCommand(e.target.value)}></textarea>
</div>
<div className="form-group">
<button className="btn btn-block">
{t("submit")}
</button>
</div>
</form>
</>
);
}
export default NewPurchaseLaRuche

View File

@@ -0,0 +1,83 @@
import React from 'react';
import { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom';
import { reset, updatepurchase } from '../../features/purchases/purchaseSlice'
import { toast } from 'react-toastify'
import Spinner from '../Spinner'
import { useTranslation } from 'react-i18next';
import { LineProductUnknownPreFill } from '../SearchBox/LineProductUnknownPreFill';
import "react-datepicker/dist/react-datepicker.css";
export const NewPurchaseLaRucheUnknownProduct = ({ listUnknowProduct }) => {
const {isLoading, isError, isSuccess, message, purchase } = useSelector((state) => state.purchase)
const [lineProductsData, setLineProductsData] = useState([]);
const { t } = useTranslation(["NewPurchase"]);
const dispatch = useDispatch()
const navigate = useNavigate()
useEffect(() => {
if (isError)
toast.error(message)
if (isSuccess) {
dispatch(reset())
navigate('/')
}
}, [isError, isSuccess, message, navigate, dispatch])
const onSubmit = (e) => {
e.preventDefault()
const purchaseToUpdate = {
id: purchase.id,
products: lineProductsData.flatMap((productLine) =>
!Array.isArray(productLine.selectedValue) ?
{
product: productLine.selectedValue,
quantity: productLine.quantity,
price: productLine.price,
currencyIsoCode: productLine.currencyIsoCode,
translation: productLine.translation
}
: [])
}
dispatch(updatepurchase(purchaseToUpdate))
}
const addOrModifyLineProduct = (lineProduct) => {
let existingLine = lineProductsData.findIndex(lpd => lpd.id === lineProduct.id);
if (existingLine === -1)
setLineProductsData(lineProductsData.concat(lineProduct))
else {
lineProductsData[existingLine] = lineProduct
setLineProductsData(lineProductsData)
}
}
if (isLoading)
return <Spinner />
return (
<>
<form onSubmit={onSubmit} className="form-group">
<div className="form-group" id="formProducts">
<label>{t("product")}</label>
{
listUnknowProduct.map((item) => (
<LineProductUnknownPreFill key={item.id} unknowProduct={item} nameSelect={item.id} onChange={(e) => addOrModifyLineProduct(e)}/>
))
}
</div>
<div className="form-group">
<button className="btn btn-block">
{t("submit")}
</button>
</div>
</form>
</>
);
}
export default NewPurchaseLaRucheUnknownProduct

View File

@@ -0,0 +1,96 @@
import React from 'react';
import { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom';
import {create, reset} from '../../features/purchases/purchaseSlice'
import { toast } from 'react-toastify'
import Spinner from '../../components/Spinner'
import { useTranslation } from 'react-i18next';
import { LineProduct } from '../../components/SearchBox/LineProduct';
import {FaShoppingCart} from 'react-icons/fa'
import "react-datepicker/dist/react-datepicker.css";
export const NewPurchaseManual = ({ datePurchase, currency }) => {
const {isLoading, isError, isSuccess, message } = useSelector((state) => state.purchase)
const [products, setProducts] = useState([{key: "0", name: "product0"}]);
const [lineProductsData, setLineProductsData] = useState([]);
const [nbProduct, setNbProduct] = useState(1);
const { t } = useTranslation(["NewPurchase"]);
const dispatch = useDispatch()
const navigate = useNavigate()
useEffect(() => {
if (isError)
toast.error(message)
if (isSuccess) {
dispatch(reset())
navigate('/')
}
}, [isError, isSuccess, message, products, navigate, dispatch])
const onSubmit = (e) => {
e.preventDefault()
const purchase = {
datePurchase: datePurchase,
products: lineProductsData.map((productLine) => (
{
product: productLine.selectedValue,
quantity: productLine.quantity,
price: productLine.price,
currencyIsoCode: currency
}
))
}
dispatch(create(purchase))
}
const addOrModifyLineProduct = (lineProduct) => {
let existingLine = lineProductsData.findIndex(lpd => lpd.nameSelect === lineProduct.nameSelect);
if (existingLine === -1)
setLineProductsData(lineProductsData.concat(lineProduct))
else {
lineProductsData[existingLine] = lineProduct
setLineProductsData(lineProductsData)
}
}
const addNewProduct = () => {
setProducts(products.concat([
{key: nbProduct, name: "product" + nbProduct }
]));
setNbProduct(nbProduct + 1)
}
if (isLoading)
return <Spinner />
return (
<>
<form onSubmit={onSubmit} className="form-group">
<div className="form-group" id="formProducts">
<label>{t("product")}</label>
{
products.map((item) => (
<LineProduct key={item.key} onChange={(e) => addOrModifyLineProduct(e)} nameSelect={item.name}/>
))
}
</div>
<div>
<a onClick={() => addNewProduct()} href="#/" className='btn btn-reverse btn-block'>
<FaShoppingCart /> {t("addNewProduct")}
</a>
</div>
<div className="form-group">
<button className="btn btn-block">
{t("submit")}
</button>
</div>
</form>
</>
);
}
export default NewPurchaseManual

View File

@@ -0,0 +1,19 @@
import React from "react";
import { useSelector } from "react-redux";
function NoteItem({id, note}) {
const {user} = useSelector((state) => state.auth)
return (
<div className="note" style={{
backgroundColor: note.isStaff ? 'rgba(0,0,0,0.7)' : '#fff',
color: note.isStaff ? '#fff' : '#000',
}} id={id}>
<h4>Note from {note.isStaff ? <span>Staff</span> : <span>{user.name}</span>}</h4>
<p>{note.text}</p>
<div className="note-date">{new Date(note.createdAt).toLocaleString('fr-FR')}</div>
</div>
);
}
export default NoteItem;

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { Navigate, Outlet } from "react-router-dom";
import { useAuthStatus } from "../hooks/useAuthStatus";
import Spinner from './Spinner'
export const PrivateRoute = () => {
const {loggedIn, checkingStatus} = useAuthStatus()
if (checkingStatus)
return <Spinner />
return loggedIn ? <Outlet /> : <Navigate to='/login' />;
};
export default PrivateRoute

View File

@@ -0,0 +1,60 @@
import React from 'react';
import Select from 'react-select';
import { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import { useTranslation } from 'react-i18next';
import { reset, getgroupsbyname } from '../../features/groups/groupSlice';
export const GroupSearchBox = ({ nameSelect, onChange }) => {
const { groups } = useSelector((state) => state.group)
const [options, setOptions] = useState([]);
const [inputValueChanged, setInputValueChanged] = useState('');
const [selectedValue, setSelectedValue] = useState([]);
const dispatch = useDispatch()
const { t } = useTranslation(["NewProduct"]);
useEffect(() => {
if (inputValueChanged !== null && inputValueChanged.length > 2) {
dispatch(getgroupsbyname(inputValueChanged));
}
}, [dispatch, inputValueChanged]);
useEffect(() => {
onChange(selectedValue)
dispatch(reset())
}, [selectedValue]);
useEffect(() => {
setOptions(groups.slice(0,10).map((p) => {
return {
value: p.id,
label: p.names.find(function(name) { return name.language === localStorage.getItem('i18nextLng') }).name
};
}));
}, [groups]);
const styles = {
container: base => ({
...base,
flex: 1
})
};
return (
<>
<Select
className="width-100"
styles={styles}
options={options}
name={nameSelect}
placeholder={t("group")}
closeMenuOnSelect={true}
onChange={(e) => {
setSelectedValue(e.value);
}}
onInputChange={(e) => {
setInputValueChanged(e)
}}
/>
</>
);
};

View File

@@ -0,0 +1,35 @@
import React from 'react';
import { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux'
import { useTranslation } from 'react-i18next';
import { ProductSearchBox } from './ProductSearchBox';
export const LineProduct = ({ nameSelect, onChange }) => {
const [selectedValue, setSelectedValue] = useState([]);
const [quantity, setQuantity] = useState('');
const [price, setPrice] = useState('');
const dispatch = useDispatch()
const { t } = useTranslation(["LineProduct"]);
useEffect(() => {
onChange({
nameSelect,
quantity,
price,
selectedValue
});
}, [dispatch, onChange, quantity, selectedValue, price, nameSelect]);
return (
<>
<fieldset className='mtop-10'>
<legend>{t("product")}</legend>
<div>
<input type="text" name={`${nameSelect}quantity`} className="width-40 inlineflex form-control" id={`${nameSelect}quantity`} value={quantity} placeholder={t("quantity")} onChange={(e) => { setQuantity(e.target.value); }} />
<input type="text" name={`${nameSelect}price`} className="width-40 inlineflex form-control" id={`${nameSelect}price`} value={price} placeholder={t("price")} onChange={(e) => { setPrice(e.target.value); }} />
</div>
<ProductSearchBox key={nameSelect} className='mbottom-10' nameSelect={nameSelect} onChange={(e) => setSelectedValue(e)}/>
</fieldset>
</>
);
};

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux'
import { useTranslation } from 'react-i18next';
import { ProductSearchBoxPreFill } from './ProductSearchBoxPreFill';
import { FaTrash } from 'react-icons/fa';
export const LineProductPreFill = ({ product, nameSelect, onChange, toDelete }) => {
const [selectedValue, setSelectedValue] = useState(product.productId);
const [quantity, setQuantity] = useState(product.quantity);
const [price, setPrice] = useState(product.price);
const dispatch = useDispatch()
const { t } = useTranslation(["LineProduct"]);
useEffect(() => {
onChange({
id: nameSelect,
quantity,
price,
selectedValue,
translation: product.product,
currencyIsoCode: product.currencyIsoCode
});
}, [dispatch, onChange, quantity, selectedValue, price, product.currencyIsoCode, product.product, nameSelect]);
return (
<>
<div className='flex'>
<div>
<fieldset key={`${nameSelect}fieldset`} className='mtop-10'>
<legend>{t("product")}</legend>
<div>
<input type="text" name={`${nameSelect}quantity`} className="width-40 inlineflex form-control" id={`${nameSelect}quantity`} value={quantity} placeholder={t("quantity")} onChange={(e) => { setQuantity(e.target.value); }} />
<input type="text" name={`${nameSelect}price`} className="width-40 inlineflex form-control" id={`${nameSelect}price`} value={price} placeholder={t("price")} onChange={(e) => { setPrice(e.target.value); }} />
</div>
<input type="hidden" key={`${nameSelect}currency`} name={`${nameSelect}currency`} className="width-100 inlineflex form-control" id={`${nameSelect}currency`} onChange={() => {}} value={product.currencyIsoCode} />
<ProductSearchBoxPreFill key={`${nameSelect}searchbox`} className='mbottom-10' preSelectedValue={product.productId} preSelectedInputValue={product.translation} nameSelect={nameSelect} onChange={(e) => setSelectedValue(e)}/>
</fieldset>
</div>
<div>
<a onClick={() => toDelete(product.id)} href="#/" className='btn btn-reverse btn-block'>
<FaTrash />
</a>
</div>
</div>
</>
);
};

View File

@@ -0,0 +1,39 @@
import React from 'react';
import { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux'
import { useTranslation } from 'react-i18next';
import { ProductSearchBox } from './ProductSearchBox';
export const LineProductUnknownPreFill = ({ unknowProduct, nameSelect, onChange }) => {
const [selectedValue, setSelectedValue] = useState([]);
const [quantity, setQuantity] = useState(unknowProduct.quantity);
const [price, setPrice] = useState(unknowProduct.price);
const dispatch = useDispatch()
const { t } = useTranslation(["LineProduct"]);
useEffect(() => {
onChange({
id: nameSelect,
quantity,
price,
selectedValue,
translation: unknowProduct.product,
currencyIsoCode: unknowProduct.currencyIsoCode
});
}, [dispatch, onChange, quantity, selectedValue, price, unknowProduct.currencyIsoCode, unknowProduct.product, nameSelect]);
return (
<>
<fieldset key={`${nameSelect}fieldset`} className='mtop-10'>
<legend>{t("product")}</legend>
<div>
<input type="text" name={`${nameSelect}quantity`} className="width-40 inlineflex form-control" id={`${nameSelect}quantity`} value={quantity} placeholder={t("quantity")} onChange={(e) => { setQuantity(e.target.value); }} />
<input type="text" name={`${nameSelect}price`} className="width-40 inlineflex form-control" id={`${nameSelect}price`} value={price} placeholder={t("price")} onChange={(e) => { setPrice(e.target.value); }} />
</div>
<input type="text" key={`${nameSelect}nameAlreadyPreFill`} name={`${nameSelect}nameAlreadyPreFill`} className="width-100 inlineflex form-control" id={`${nameSelect}nameAlreadyPreFill`} onChange={() => {}} value={unknowProduct.product} />
<input type="hidden" key={`${nameSelect}currency`} name={`${nameSelect}currency`} className="width-100 inlineflex form-control" id={`${nameSelect}currency`} onChange={() => {}} value={unknowProduct.currencyIsoCode} />
<ProductSearchBox key={`${nameSelect}searchbox`} className='mbottom-10' nameSelect={nameSelect} onChange={(e) => setSelectedValue(e)}/>
</fieldset>
</>
);
};

View File

@@ -0,0 +1,61 @@
import React from 'react';
import Select from 'react-select';
import {getproductbyname} from '../../features/products/productSlice'
import { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import { useTranslation } from 'react-i18next';
import { reset } from '../../features/products/productSlice';
export const ProductSearchBox = ({ nameSelect, onChange }) => {
const { products } = useSelector((state) => state.product)
const [options, setOptions] = useState([]);
const [inputValueChanged, setInputValueChanged] = useState('');
const [selectedValue, setSelectedValue] = useState([]);
const dispatch = useDispatch()
const { t } = useTranslation(["LineProduct"]);
useEffect(() => {
if (inputValueChanged !== null && inputValueChanged.length > 2) {
dispatch(getproductbyname(inputValueChanged));
}
}, [dispatch, inputValueChanged]);
useEffect(() => {
onChange(selectedValue)
dispatch(reset())
}, [selectedValue]);
useEffect(() => {
setOptions(products.slice(0,10).map((p) => {
return {
value: p.id,
label: p.names.find(function(name) { return name.language === localStorage.getItem('i18nextLng') }).name + ' - ' + p.group
};
}));
}, [products]);
const styles = {
container: base => ({
...base,
flex: 1
})
};
return (
<>
<Select
className="width-100"
styles={styles}
options={options}
name={nameSelect}
placeholder={t("product")}
closeMenuOnSelect={true}
onChange={(e) => {
setSelectedValue(e.value);
}}
onInputChange={(e) => {
setInputValueChanged(e)
}}
/>
</>
);
};

View File

@@ -0,0 +1,67 @@
import React from 'react';
import Select from 'react-select';
import {getproductbyname} from '../../features/products/productSlice'
import { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import { useTranslation } from 'react-i18next';
import { reset } from '../../features/products/productSlice';
export const ProductSearchBoxPreFill = ({ preSelectedValue, preSelectedInputValue, nameSelect, onChange }) => {
const { products } = useSelector((state) => state.product)
const [options, setOptions] = useState([]);
const [inputValueChanged, setInputValueChanged] = useState(preSelectedInputValue);
const [selectedValue, setSelectedValue] = useState(preSelectedValue);
const dispatch = useDispatch()
const { t } = useTranslation(["LineProduct"]);
useEffect(() => {
if (inputValueChanged !== null && inputValueChanged.length > 2) {
dispatch(getproductbyname(inputValueChanged));
}
}, [dispatch, inputValueChanged]);
useEffect(() => {
dispatch(getproductbyname(inputValueChanged));
}, []);
useEffect(() => {
onChange(selectedValue)
dispatch(reset())
}, [selectedValue]);
useEffect(() => {
setOptions(products.slice(0,10).map((p) => {
return {
value: p.id,
label: p.names.find(function(name) { return name.language === localStorage.getItem('i18nextLng') }).name + ' - ' + p.group
};
}));
}, [products]);
const styles = {
container: base => ({
...base,
flex: 1
})
};
return (
<>
<Select
className="width-100"
styles={styles}
options={options}
name={nameSelect}
placeholder={t("product")}
closeMenuOnSelect={true}
defaultInputValue={preSelectedInputValue}
defaultValue={preSelectedValue}
onChange={(e) => {
setSelectedValue(e.value);
}}
onInputChange={(e) => {
setInputValueChanged(e)
}}
/>
</>
);
};

View File

@@ -0,0 +1,60 @@
import React from 'react';
import Select from 'react-select';
import { useEffect, useState } from 'react';
import {useSelector, useDispatch } from 'react-redux'
import { useTranslation } from 'react-i18next';
import {reset, getsubgroupsbyname} from '../../features/subgroups/subGroupSlice'
export const SubGroupSearchBox = ({ nameSelect, onChange }) => {
const { subgroups } = useSelector((state) => state.subgroup)
const [options, setOptions] = useState([]);
const [inputValueChanged, setInputValueChanged] = useState('');
const [selectedValue, setSelectedValue] = useState([]);
const dispatch = useDispatch()
const { t } = useTranslation(["NewProduct"]);
useEffect(() => {
if (inputValueChanged !== null && inputValueChanged.length > 2) {
dispatch(getsubgroupsbyname(inputValueChanged));
}
}, [dispatch, inputValueChanged]);
useEffect(() => {
onChange(selectedValue)
dispatch(reset())
}, [selectedValue]);
useEffect(() => {
setOptions(subgroups.slice(0,10).map((p) => {
return {
value: p.id,
label: p.names.find(function(name) { return name.language === localStorage.getItem('i18nextLng') }).name + ' - ' + p.group
};
}));
}, [subgroups]);
const styles = {
container: base => ({
...base,
flex: 1
})
};
return (
<>
<Select
className="width-100"
styles={styles}
options={options}
name={nameSelect}
placeholder={t("subGroup")}
closeMenuOnSelect={true}
onChange={(e) => {
setSelectedValue(e.value);
}}
onInputChange={(e) => {
setInputValueChanged(e)
}}
/>
</>
);
};

View File

@@ -0,0 +1,11 @@
import React from 'react';
function Spinner() {
return (
<div className='loadingSpinnerContainer'>
<div className='loadingSpinner'></div>
</div>
)
}
export default Spinner

View File

@@ -0,0 +1,17 @@
import React from "react";
import {Link} from 'react-router-dom'
function TicketItem({id, ticket}) {
return (
<div className="ticket" id={id}>
<div>{new Date(ticket.createdAt).toLocaleString('fr-FR')}</div>
<div>{ticket.product}</div>
<div className={`status status-${ticket.status}`}>
{ticket.status}
</div>
<Link to={`/ticket/${ticket._id}`} className="btn btn-reverse btn-sm">View</Link>
</div>
);
}
export default TicketItem;