First Commit
This commit is contained in:
16
frontend/src/components/BackButton.jsx
Normal file
16
frontend/src/components/BackButton.jsx
Normal 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
|
||||
61
frontend/src/components/Header.jsx
Normal file
61
frontend/src/components/Header.jsx
Normal 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;
|
||||
77
frontend/src/components/Home/HomePurchase.jsx
Normal file
77
frontend/src/components/Home/HomePurchase.jsx
Normal 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
|
||||
22
frontend/src/components/Home/LinePurchase.jsx
Normal file
22
frontend/src/components/Home/LinePurchase.jsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
22
frontend/src/components/LanguageSelector.jsx
Normal file
22
frontend/src/components/LanguageSelector.jsx
Normal 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;
|
||||
30
frontend/src/components/NewProduct/NewGroup/LineGroup.jsx
Normal file
30
frontend/src/components/NewProduct/NewGroup/LineGroup.jsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
92
frontend/src/components/NewProduct/NewGroup/NewGroup.jsx
Normal file
92
frontend/src/components/NewProduct/NewGroup/NewGroup.jsx
Normal 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
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
19
frontend/src/components/NoteItem.jsx
Normal file
19
frontend/src/components/NoteItem.jsx
Normal 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;
|
||||
15
frontend/src/components/PrivateRoute.jsx
Normal file
15
frontend/src/components/PrivateRoute.jsx
Normal 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
|
||||
60
frontend/src/components/SearchBox/GroupSearchBox.jsx
Normal file
60
frontend/src/components/SearchBox/GroupSearchBox.jsx
Normal 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)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
35
frontend/src/components/SearchBox/LineProduct.jsx
Normal file
35
frontend/src/components/SearchBox/LineProduct.jsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
48
frontend/src/components/SearchBox/LineProductPreFill.jsx
Normal file
48
frontend/src/components/SearchBox/LineProductPreFill.jsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
61
frontend/src/components/SearchBox/ProductSearchBox.jsx
Normal file
61
frontend/src/components/SearchBox/ProductSearchBox.jsx
Normal 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)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
60
frontend/src/components/SearchBox/SubGroupSearchBox.jsx
Normal file
60
frontend/src/components/SearchBox/SubGroupSearchBox.jsx
Normal 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)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
11
frontend/src/components/Spinner.jsx
Normal file
11
frontend/src/components/Spinner.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function Spinner() {
|
||||
return (
|
||||
<div className='loadingSpinnerContainer'>
|
||||
<div className='loadingSpinner'></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Spinner
|
||||
17
frontend/src/components/TicketItem.jsx
Normal file
17
frontend/src/components/TicketItem.jsx
Normal 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;
|
||||
Reference in New Issue
Block a user