### Corrections
* Amélioration du style du site.
* Correction d'un bug qui permettait de supprimer des images sans mot de passe.

### Ajouts
* Il n'est plus possible de modifier la quantité des produits utilisant les pourcentages dans l'explorateur de recette.
* La quantité initiale d'un produit peut maintenant être cachée dans l'explorateur de recette.
* La quantité minimum d'un produit dans un mélange est maintenant de 0.001.
* La quantité de chaque produit d'un mélange peut maintenant être calculée depuis la quantitée totale du mélange.
* Ajout de boîtes de notification améliorées.
This commit is contained in:
FyloZ 2019-12-20 10:50:46 -05:00
parent b66777aa23
commit f21cfd94c1
34 changed files with 531 additions and 159 deletions

View File

@ -10,7 +10,7 @@
</parent>
<groupId>dev.fyloz.trial.colorrecipesexplorer</groupId>
<artifactId>ColorRecipesExplorer</artifactId>
<version>1.1.1</version>
<version>1.1.2</version>
<name>Color Recipes Explorer</name>
<properties>

View File

@ -66,11 +66,13 @@ public class RecipeExplorerController {
.build();
}
// TODO convertir form en DTO
@PostMapping(value = EXPLORER_RECIPE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Object> saveRecipeInformations(@RequestBody Map<String, Object> form) {
JSONResponseBuilder responseBuilder = new JSONResponseBuilder();
int recipeID = Integer.parseInt(form.get("recipeID").toString());
Map<String, String> location = (Map<String, String>) form.get("locations");
String note = form.get("note").toString();

View File

@ -7,6 +7,13 @@ td {
vertical-align: top;
}
th {
background-color: black;
color: white;
font-weight: normal;
font-size: large;
}
h1 {
text-decoration: underline;
}
@ -49,6 +56,71 @@ nav a {
text-decoration: none;
}
footer a {
color: white;
text-decoration: none;
}
button,
select {
background-color: #d7dedc;
border-style: solid;
border-color: #d7dedc;
border-width: 1px;
padding: 2px 4px;
margin: 2px;
}
select {
padding: 1px 0;
}
select:after {
background-color: red;
}
button:hover {
background-color: #bec4c3;
}
input {
background-color: rgba(0, 0, 0, 0);
border-style: solid;
border-width: 1px;
border-color: #7a7a7a;
}
input[type=file] {
border-style: none;
}
input[disabled] {
border-style: none;
color: black;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] {
-moz-appearance: textfield;
}
table {
border-collapse: collapse;
}
textarea {
font-family: 'Open Sans', sans-serif;
background-color: #fafafa;
border-style: solid;
border-color: #7a7a7a;
border-width: 1px;
}
.dropdown {
margin-top: 22px;
float: left;
@ -120,6 +192,10 @@ nav a:hover, .dropdown:hover .dropbtn {
background-color: #ffc299;
}
.nosimdut input:not(:disabled) {
background-color: #fafafa;
}
.unapproved {
background-color: #fff0b3;
}
@ -131,4 +207,71 @@ nav a:hover, .dropdown:hover .dropbtn {
#researchBox {
margin-top: 10px;
margin-right: 50px;
}
}
.errorBox {
background-color: #ef9a9a;
color: #e53935;
font-weight: bold;
}
.successBox {
background-color: #a5d6a7;
color: #4caf50;
font-weight: bold;
}
.warningBox {
background-color: #fff59d;
color: #fdd835;
font-weight: bold;
}
.messageBox {
display: inline-block;
margin: 20px auto auto;
}
.messageBox .messageBoxContainer {
display: inline-block;
height: 24px;
padding: 16px;
text-align: left;
}
.messageBox .messageBoxInnerBox {
display: inline-block;
height: 24px;
}
.messageBox img {
float: left;
}
.messageBox p {
display: inline-block;
/*padding: 3px 0;*/
margin: 0 0 0 16px;
line-height: 24px;
}
/*.errorBox div {*/
/* height: 24px;*/
/* !*padding: 20px;*!*/
/* margin: auto;*/
/* vertical-align: middle;*/
/*}*/
/*.errorBox img {*/
/* display: inline-block;*/
/* float: left;*/
/* height: 25px;*/
/*}*/
/*.errorBox p {*/
/* !*float: left;*!*/
/* display: inline-block;*/
/* font-size: 18px;*/
/* margin: auto;*/
/* padding: 2px 0;*/
/*}*/

View File

@ -34,4 +34,8 @@
.materialSelector p:hover {
background-color: #e6e6e6;
}
}
.unitsColumn {
vertical-align: middle;
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path fill="#e53935"
d="M13,13H11V7H13M13,17H11V15H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 390 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path fill="#4caf50" d="M9,20.42L2.79,14.21L5.62,11.38L9,14.77L18.88,4.88L21.71,7.71L9,20.42Z"/>
</svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path fill="#fdd835" d="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z"/>
</svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@ -1,6 +1,12 @@
const body = document.querySelector("body");
const errorP = document.querySelector(".error");
const errorMsgBox = document.querySelector(".errorBox");
const errorMsgBoxText = errorMsgBox.querySelector("p");
const warningMsgBox = document.querySelector(".warningBox");
const warningMsgBoxText = warningMsgBox.querySelector("p");
const successMsgBox = document.querySelector(".successBox");
const successMsgBoxText = successMsgBox.querySelector("p");
// Ne fonctionne pas dans window#load
(() => {
// Ajoute Axios
const axiosElement = document.createElement("script");
@ -21,6 +27,12 @@ const errorP = document.querySelector(".error");
});
};
// Placé ici pour un chargement plus rapide
document.querySelectorAll(".messageBox").forEach(e => checkMessageBoxesDisplay(e));
})();
window.addEventListener("load", () => {
document.querySelectorAll(".returnIndex").forEach((e) => {
e.addEventListener("click", () => {
document.location.href = "/";
@ -72,19 +84,29 @@ const errorP = document.querySelector(".error");
});
});
window.addEventListener("change", e => {
if (e.target) {
if (e.target.classList.contains("toSave")) {
// TODO traductions
warningMsgBoxText.innerText = "Des modifications ne sont pas été sauvegardées";
showElement(warningMsgBox);
}
}
});
// Ajoute le favicon
let faviconElement = document.createElement("link");
faviconElement.rel = "icon";
faviconElement.href = "/favicon.png";
document.querySelector("head").appendChild(faviconElement);
})();
});
function askDatabaseExport() {
return confirm("Voulez-vous vraiment exporter toutes les couleurs? Cela peut prendre un certain temps et ralentir l'application pendant un certain temps.");
}
function checkPassword(form) {
errorP.innerHTML = "";
function checkPassword(form, callback) {
hideElement(errorMsgBox);
const password = prompt("Quel est votre mot de passe?");
@ -94,18 +116,34 @@ function checkPassword(form) {
axios.post("/password/valid", data)
.then(r => {
if (r.data) {
form.submit();
if (form != null) form.submit();
if (callback != null) callback();
return true;
} else {
errorP.innerHTML = "Votre mot de passe n'est pas valide";
errorMsgBoxText.innerText = "Votre mot de passe n'est pas valide";
showElement(errorMsgBox);
return false;
}
})
.catch(e => {
errorP.innerHTML = "Une erreur est survenue lors de l'envoie des informations vers le serveur.";
errorMsgBoxText.innerText = "Une erreur est survenue lors de l'envoie des informations vers le serveur.";
showElement(errorMsgBox);
console.log(e);
return false;
});
}
return false;
function checkMessageBoxesDisplay(element) {
if (!element.querySelector("p").innerText) hideElement(element);
else showElement(element);
}
function hideElement(element) {
element.style.display = "none";
}
function showElement(element) {
element.style.display = "inline-block";
}
const lTomL = 1000;
@ -146,4 +184,4 @@ function round(x) {
function percentageOf(percentage, number) {
return (percentage / 100) * number;
}
}

View File

@ -16,7 +16,8 @@ window.addEventListener("load", () => {
init();
})
.catch(e => {
errorP.innerHTML = "Une erreur est survenue lors de la récupération des produits";
errorMsgBoxText.innerText = "Une erreur est survenue lors de la récupération des produits";
showElement(errorMsgBox);
console.log(e);
});
});
@ -44,13 +45,13 @@ document.querySelector("#materials button").addEventListener("click", () => {
function addMaterial(materialCode, quantity) {
let row = addRow();
let materialSelectionColumn = addColumn(row);
let quantityInputColumn = addColumn(row);
let removeButtonColumn = addColumn(row);
let materialSelectionColumn = addColumn(row, null);
let quantityInputColumn = addColumn(row, null);
let unitsColumn = addColumn(row, "unitsColumn");
let removeButtonColumn = addColumn(row, null);
addInput(quantityInputColumn, quantity);
addSpan(quantityInputColumn);
addSpan(unitsColumn);
addButton(removeButtonColumn);
materialSelectionColumn.innerHTML = materialSelectorHtml;
@ -73,12 +74,13 @@ function addMaterial(materialCode, quantity) {
function addInput(parent, quantity) {
let input = document.createElement("input");
if (quantity === null) quantity = 0;
if (quantity === null) quantity = 1;
input.type = "number";
input.name = "quantities";
input.value = quantity;
input.step = 0.001;
input.min = 0.001;
input.required = true;
parent.appendChild(input);
@ -114,8 +116,9 @@ function addRow() {
return row;
}
function addColumn(parent) {
function addColumn(parent, className) {
let column = document.createElement("td");
if (className != null) column.className = className;
parent.appendChild(column);
return column;
@ -165,4 +168,4 @@ function checkUnits(materialSelector, row) {
const quantityUnits = row.querySelector(".quantityUnit");
if (materialSelector.dataset.usepercentages === "true") quantityUnits.innerText = "%";
else quantityUnits.innerText = "mL";
}
}

View File

@ -12,7 +12,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{company.add.title}"></h1>
<div class="form">
@ -41,4 +41,4 @@
<script src="/js/main.js"></script>
</body>
</html>
</html>

View File

@ -25,7 +25,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<p class="success" th:if="${companyName != null}"
th:text="#{company.success.deleted(${companyName})}"></b>
</p>
@ -36,6 +36,7 @@
<table id="companiesList">
<tr>
<th th:text="#{company.form.companyName}"></th>
<th></th>
</tr>
<th:block th:each="company : ${companies}">
<tr>
@ -60,4 +61,4 @@
<script src="/js/main.js"></script>
</body>
</html>
</html>

View File

@ -55,10 +55,40 @@
</nav>
</div>
<div th:fragment="error()">
<p class="error" th:if="${error != null && !error.isEmpty()}"
th:text="#{${error}(${responseArg1}, ${responseArg2})}"></p>
<p class="error" th:if="${error == null || error.isEmpty()}"></p>
<div th:fragment="messages">
<div th:include="fragments.html :: error"></div>
<div th:include="fragments.html :: success"></div>
<div th:include="fragments.html :: warning"></div>
</div>
<div th:fragment="error">
<div class="messageBox errorBox">
<div class="messageBoxContainer">
<img th:src="@{|${baseUrl}/icons/error.svg|}" alt="error icon"/>
<p th:if="${error != null && !error.isEmpty()}" th:text="#{${error}(${responseArg1}, ${responseArg2})}"></p>
<p th:if="${error == null || error.isEmpty()}"></p>
</div>
</div>
</div>
<div th:fragment="success">
<div class="messageBox successBox">
<div class="messageBoxContainer">
<img th:src="@{|${baseUrl}/icons/success.svg|}" alt="success icon"/>
<p th:if="${success != null && !success.isEmpty()}" th:text="#{${success}}"></p>
<p th:if="${success == null || success.isEmpty()}"></p>
</div>
</div>
</div>
<div th:fragment="warning">
<div class="messageBox warningBox">
<div class="messageBoxContainer">
<img th:src="@{|${baseUrl}/icons/warning.svg|}" alt="warning icon"/>
<p th:if="${warning != null && !warning.isEmpty()}" th:text="#{${warning}}"></p>
<p th:if="${warning == null || warning.isEmpty()}"></p>
</div>
</div>
</div>
<div th:fragment="separator">
@ -77,4 +107,4 @@
</div>
</body>
</html>
</html>

View File

@ -9,14 +9,14 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<form th:action="@{/images/add}" class="requireAuth" enctype="multipart/form-data" method="POST">
<input name="recipeID" th:value="${recipeID}" type="hidden"/>
<input type="file" id="image" name="image" required/>
<br/>
<input type="submit" th:value="#{keyword.save}"/>
<button type="submit" th:text="#{keyword.save}"></button>
</form>
</section>
<!-- Fragment du pied de page -->
@ -25,4 +25,4 @@
<script src="/js/main.js"></script>
</body>
</html>
</html>

View File

@ -51,7 +51,7 @@
<input id="researchBox" type="text" th:placeholder="#{keyword.search}" onchange="performSearch(this.value)"/>
</div>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<div class="recipesContainer">
<div class="companyTab" th:if="${!recipeMap.empty}" th:each="company : ${recipeMap.keySet()}"
@ -65,6 +65,7 @@
<th th:text="#{recipe.color}"></th>
<th th:text="#{recipe.description}"></th>
<th th:text="#{recipe.sample}"></th>
<th></th>
</tr>
<tr th:each="recipe : ${recipeMap.get(company)}" class="recipeRow"
th:data-approbationDate="${recipe.approbationDate}" th:data-recipeID="${recipe.recipeID}">
@ -121,7 +122,7 @@
return;
}
errorP.innerText = "";
hideElement(errorMsgBox);
axios.get(`/search?searchString=${searchString}`)
.then(r => {
const result = r.data.result;
@ -149,7 +150,8 @@
.catch(e => {
console.log(e);
errorP.innerText = /*[[#{error.serverError}]]*/ "Erreur";
errorMsgBoxText.innerText = /*[[#{error.serverError}]]*/ "Erreur";
showElement(errorMsgBox);
});
}

View File

@ -61,7 +61,7 @@
<input type="text" id="researchBox" th:placeholder="#{keyword.search}" onchange="performSearch(this.value)"/>
</div>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<table class="materialsContainer" th:if="${!materials.empty}">
@ -71,13 +71,13 @@
<th th:text="#{keyword.quantity}"></th>
<!--Unités de la quantité-->
<td></td>
<th th:text="#{keyword.units}"></th>
<th th:text="#{keyword.type}"></th>
<td></td>
<th></th>
<!--Options-->
<td rowspan="5">
<td rowspan="5" style="padding-left : 20px">
<label for="lowQuantity" th:text="#{inventory.lowQuantity} + ':'"></label>
<input id="lowQuantity" min="0" onchange="checkLowQuantity(this.value)" step="0.01"
style="width: 65px" type="number"
@ -199,7 +199,7 @@
return;
}
errorP.innerText = "";
hideElement(errorMsgBox);
axios.get(`inventory/search?searchString=${searchString}`)
.then(r => {
const result = r.data.result;
@ -217,21 +217,13 @@
console.log(`.materialRow[data-materialID=\"${m}\"]`);
const materialRow = document.querySelector(`.materialRow[data-materialID=\"${m}\"]`);
materialRow.classList.add("researchResult");
// const companyTab = document.querySelector(`.companyTab[data-companyID=\"${c}\"]`);
// companyTab.classList.add("researchResult");
// companyTab.querySelector(".recipesList").style.display = "table";
//
// for (let r = 0; r < result[c].length; r++) {
// const recipeRow = companyTab.querySelector(`.recipeRow[data-recipeID=\"${result[c][r]}\"]`);
// recipeRow.classList.add("researchResult");
// }
});
})
.catch(e => {
console.log(e);
errorP.innerText = /*[[#{error.serverError}]]*/ "Erreur";
errorMsgBoxText.innerText = /*[[#{error.serverError}]]*/ "Erreur";
showElement(errorMsgBox);
});
}

View File

@ -13,7 +13,7 @@
<!-- Corps de la page -->
<section>
<p th:if="${materialCode != null}" th:text="#{material.success.created(${materialCode})}" class="success"></p>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{material.add.title}"></h1>
<div class="form">
@ -104,4 +104,4 @@
}
</script>
</body>
</html>
</html>

View File

@ -11,7 +11,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 class="materialCode" th:text="#{material.editing.title(${material.materialCode})}"></h1>
<div class="form">
@ -115,4 +115,4 @@
}
</script>
</body>
</html>
</html>

View File

@ -20,7 +20,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<p class="success" th:if="${materialCode != null}"
th:text="#{material.success.saved(${materialCode})}"></b>
</p>
@ -30,6 +30,7 @@
<tr>
<th th:text="#{keyword.material}"></th>
<th th:text="#{keyword.type}"></th>
<th></th>
</tr>
<tr th:each="material : ${materials}">
<td class="materialCode" th:data-materialID="${material.materialID}"
@ -62,4 +63,4 @@
/*]]>*/
</script>
</body>
</html>
</html>

View File

@ -20,7 +20,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<p class="success" th:if="${materialCode != null}"
th:text="#{material.success.deleted(${materialCode})}"></p>
<h1 th:text="#{material.delete.title}"></h1>
@ -30,6 +30,7 @@
<tr>
<th th:text="#{keyword.material}"></th>
<th th:text="#{material.type}"></th>
<th></th>
</tr>
<tr th:each="material : ${materials}">
<td class="materialCode" th:data-materialID="${material.materialID}"
@ -53,4 +54,4 @@
<script src="/js/main.js"></script>
</body>
</html>
</html>

View File

@ -9,7 +9,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{material.add.title}"></h1>
<div class="form">
@ -32,4 +32,4 @@
<script src="/js/main.js"></script>
</body>
</html>
</html>

View File

@ -11,7 +11,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{materialType.add.title}"></h1>
<div class="form">
@ -52,4 +52,4 @@
<script src="/js/main.js"></script>
</body>
</html>
</html>

View File

@ -12,7 +12,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 class="materialCode" th:text="#{materialType.editing.title(${materialType.materialTypeName})}"></h1>
<div class="form">
@ -57,4 +57,4 @@
<script src="/js/main.js"></script>
</body>
</html>
</html>

View File

@ -20,7 +20,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<p class="success" th:if="${materialTypeName != null}"
th:text="#{materialType.success.saved(${materialTypeName})}"></b>
</p>
@ -29,6 +29,7 @@
<table th:if="${!materialTypes.empty}">
<tr>
<th th:text="#{material.type}"></th>
<th></th>
</tr>
<tr th:each="materialType : ${materialTypes}">
<td class="materialType" th:data-materialTypeID="${materialType.materialTypeID}"
@ -60,4 +61,4 @@
/*]]>*/
</script>
</body>
</html>
</html>

View File

@ -20,7 +20,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<p class="success" th:if="${materialTypeName != null}"
th:text="#{materialType.success.deleted(${materialTypeName})}"></p>
<h1 th:text="#{materialType.delete.title}"></h1>
@ -29,6 +29,7 @@
<table th:if="${!materialTypes.empty}" id="materialTypesList">
<tr>
<th th:text="#{material.type}"></th>
<th></th>
</tr>
<tr th:each="materialType : ${materialTypes}">
<td class="materialTypeName" th:data-materialTypeID="${materialType.materialTypeID}"
@ -50,4 +51,4 @@
<script src="/js/main.js"></script>
</body>
</html>
</html>

View File

@ -18,7 +18,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{mix.add.subtitle(${recipe.recipeCode})}"></h1>
<div class="form">
@ -78,4 +78,4 @@
/*]]*/
</script>
</body>
</html>
</html>

View File

@ -17,7 +17,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{mix.edit.subtitle(${recipeCode}, ${mix.mixType.typeName})}"></h1>
<button id="removeMix" type="button" th:text="#{keyword.delete}"></button>
<br/>
@ -72,26 +72,9 @@
removeText = "[[#{keyword.remove}]]";
document.querySelector("#removeMix").addEventListener("click", () => {
let errorP = document.querySelector(".error");
errorP.innerHTML = "";
showElement(errorMsgBox);
const password = prompt("[[#{password.ask}]]");
let data = {};
data.password = password;
axios.post("/password/valid", data)
.then(r => {
if (r.data) {
document.location.href = `/mix/remover/${mix.mixID}`;
} else {
errorP.innerHTML = "[[#{password.notValid}]]";
}
})
.catch(e => {
errorP.innerHTML = "[[#{error.serverError}]]";
console.log(e);
});
checkPassword(null, () => document.location.href = `/mix/remover/${mix.mixID}`);
});
})();
@ -108,4 +91,4 @@
/*]]*/
</script>
</body>
</html>
</html>

View File

@ -9,7 +9,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{recipe.add.title}"></h1>
<p th:text="#{recipe.sucess.saved(${recipe.recipeCode})}"></p>
@ -22,4 +22,4 @@
<script src="/js/main.js"></script>
</body>
</html>
</html>

View File

@ -11,7 +11,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{recipe.add.title}"></h1>
<div class="form">
@ -64,4 +64,4 @@
<script src="/js/main.js"></script>
</body>
</html>
</html>

View File

@ -10,10 +10,14 @@
display: inline;
}
table {
border-collapse: separate !important;
}
.recipe table {
background-color: #fafafa;
border: 1px solid #7a7a7a;
border-collapse: collapse;
border-collapse: collapse !important;
}
.recipe td, .recipe th {
@ -42,7 +46,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{recipe.editing.title(${recipe.recipeCode})}"></h1>
<button id="gotoRecipe" type="button" th:text="#{keyword.see}"></button>
@ -225,8 +229,6 @@
let stepNbr = 0;
(() => {
const errorP = document.querySelector(".error");
document.querySelector("#gotoRecipe").addEventListener("click", () => {
window.open("/recipe/explore/" + document.querySelector("#recipeID").value, "_blank");
});
@ -246,28 +248,30 @@
});
document.querySelectorAll(".deleteImg").forEach(e => {
e.addEventListener("click", () => {
let data = {};
e.addEventListener("click", async () => {
checkPassword(null, () => {
let data = {};
data['image'] = e.getAttribute("data-image");
data['password'] = prompt("[[#{password.ask}]]");
data['image'] = e.getAttribute("data-image");
hideElement(errorMsgBox);
errorP.innerHTML = "";
axios.post("/images/delete", data)
.then(r => {
const data = r.data;
axios.post("/images/delete", data)
.then(r => {
const data = r.data;
if (data['error'] !== undefined) {
errorP.innerHTML = data['error'];
} else {
document.location.reload();
}
})
.catch(e => {
console.log(e);
errorP.innerHTML = "[[#{error.serverError}]]";
});
if (data['error'] !== undefined) {
errorMsgBoxText.innerText = data['error'];
showElement(errorMsgBox);
} else {
document.location.reload();
}
})
.catch(e => {
console.log(e);
errorMsgBoxText.innerText = "[[#{error.serverError}]]";
showElement(errorMsgBox);
});
});
});
});
@ -325,4 +329,4 @@
/*]]*/
</script>
</body>
</html>
</html>

View File

@ -45,7 +45,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<p class="success" th:if="${recipeCode != null}"
th:text="#{recipe.success.edit(${recipeCode})}"></p>
@ -60,6 +60,7 @@
<th th:text="#{recipe.color}"></th>
<th th:text="#{recipe.description}"></th>
<th th:text="#{recipe.sample}"></th>
<th></th>
</tr>
<tr class="recipeRow" th:each="recipe : ${recipeMap.get(company)}"
th:data-approbationDate="${recipe.approbationDate}">

View File

@ -1,6 +1,8 @@
<!DOCTYPE html>
<html lang="fr" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="fragments.html :: head(#{recipe.explore.title(${recipe.recipeCode})})"></th:block>
<style>
td, th {
width: 200px;
@ -9,6 +11,11 @@
table {
margin: auto;
text-align: left;
border-collapse: separate;
}
textarea {
padding: 10px;
}
section {
@ -24,6 +31,7 @@
border: 1px solid #7a7a7a;
border-collapse: collapse;
margin: 0;
min-width: 600px;
}
.mixes tr:nth-child(odd) {
@ -49,16 +57,49 @@
}
.recipeLocation {
width: 30px;
width: 55px;
border-color: white;
color: #7a7a7a;
}
.recipeLocation:hover {
border-color: #e6e6e6;
}
.recipeLocation:focus {
border-color: #7a7a7a;
color: black;
}
.notEnough td {
background-color: #ffb3b3;
}
.quantityCustomizer {
max-width: 90px;
background-color: lightgray;
.quantityColumn {
max-width: 100px;
}
.totalQuantityLabel {
padding-right: 10px;
text-align: right;
vertical-align: middle;
}
.inventoryQuantity {
display: none;
}
.inventoryQuantityColumn {
min-width: auto !important;
width: auto !important;
}
.unitsColumn {
max-width: 25px;
}
.calculationColumn {
min-width: 125px !important;
}
.calculation {
@ -69,8 +110,6 @@
color: darkgreen;
}
</style>
<th:block th:include="fragments.html :: head(#{recipe.explore.title(${recipe.recipeCode})})"></th:block>
</head>
<body>
@ -78,7 +117,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<p class="success" th:text="${success}"></p>
<h1 th:text="#{recipe.explore.title(${recipe.recipeCode})}"></h1>
@ -137,7 +176,8 @@
<tr>
<td><b th:text="#{recipe.notice} + ':'"></b></td>
<td>
<textarea cols="30" id="note" name="note" rows="10" th:text="${recipe.note}"></textarea>
<textarea class="toSave" cols="30" id="note" name="note" rows="10"
th:text="${recipe.note}"></textarea>
</td>
</tr>
<tr>
@ -154,22 +194,37 @@
<th:block th:each="mix : ${mixes}">
<tr>
<td><b th:text="${mix.mixType.typeName} + ':'"></b><br/><br/>
<label for="location" th:text="' ' + #{mix.location} + ': '"></label><input
class="recipeLocation" id="location"
<label
th:for="'location' + ${mix.mixID}"
th:text="' ' + #{mix.location} + ': '">
</label>
<input
class="recipeLocation toSave"
th:id="'location' + ${mix.mixID}"
name="location"
th:data-mixID="${mix.mixID}"
th:value="${mix.location}"
type="text"/></td>
placeholder="N/A"
type="text"/>
</td>
<td>
<table class="mixes" th:id="'recipe-' + ${mix.mixID}">
<table class="mixes" th:id="'mix-' + ${mix.mixID}">
<tr>
<th th:text="#{keyword.material}"></th>
<th th:text="#{keyword.type}"></th>
<th th:text="#{keyword.quantity}"></th>
<th style="min-width: auto !important;">
<button
type="button"
data-hidden="true"
onclick="hideQuantities(this)">
->
</button>
</th>
<!-- Changement des quantités -->
<th></th>
<th th:text="#{keyword.calculation}"></th>
<th th:text="#{keyword.units}"></th>
<th th:text="#{keyword.calculation}"></th>
</tr>
<!-- Produits -->
<tr th:each="mixQuantity : ${mix.mixQuantities}"
@ -181,29 +236,47 @@
<td>
<p th:text="${material.materialType.materialTypeName}"></p>
</td>
<td>
<td class="inventoryQuantityColumn">
<p class="inventoryQuantity"
th:data-quantityML="${mixQuantity.quantity}"
th:text="${mixQuantity.quantity}"></p>
</td>
<td style="min-width: auto; max-width: 100px">
<td class="quantityColumn">
<input th:if="${!material.isMixType()}" class="quantityCustomizer"
min="0" step="0.001"
min="0.001" step="0.001"
th:data-materialID="${material.materialID}"
th:data-mixID="${mix.mixID}"
th:data-quantityML="${mixQuantity.quantity}"
th:data-usePercentages="${material.materialType.usePercentages}"
th:value="${mixQuantity.quantity}"
th:disabled="${material.materialType.usePercentages}"
type="number"/></td>
<td style="min-width: 125px">
<p class="calculation" th:data-mixID="${mix.mixID}"
th:data-materialID="${material.materialID}"></p>
</td>
<td style="min-width: auto; max-width: 25px">
<td class="unitsColumn">
<p class="inventoryQuantityUnits"
th:unless="${material.materialType.usePercentages}">mL</p>
<p th:if="${material.materialType.usePercentages}">%</p>
</td>
<td class="calculationColumn">
<p class="calculation" th:data-mixID="${mix.mixID}"
th:data-materialID="${material.materialID}"></p>
</td>
</tr>
<tr>
<td class="totalQuantityLabel" colspan="3">
Total:
</td>
<td>
<input
class="totalQuantityCustomizer"
type="number"
min="0.001"
step="0.001"
th:data-mixID="${mix.mixID}"
/>
</td>
<td>
<p class="inventoryQuantityUnits">mL</p>
</td>
</tr>
</table>
</td>
@ -248,6 +321,8 @@
/*<![CDATA[*/
const quantityKeyword = "[[#{keyword.quantity}]]";
(() => {
document.querySelector("#modifyRecipe").addEventListener("click", () => {
const recipeID = document.querySelector("#recipeID").value;
@ -293,6 +368,29 @@
});
});
document.querySelectorAll(".totalQuantityCustomizer").forEach(e => {
e.addEventListener("change", () => {
console.log(e);
const value = e.valueAsNumber;
const oldValue = e.defaultValue;
const mixID = e.dataset.mixid;
const mixTable = document.querySelector(`#mix-${mixID}`);
mixTable.querySelectorAll(".quantityCustomizer").forEach(elem => {
if (elem.dataset.usepercentages === "false") {
const defaultValue = elem.defaultValue;
const newValue = (defaultValue * value) / oldValue;
console.log(value + "/" + oldValue + "/" + defaultValue + "/" + newValue);
elem.value = round(newValue);
}
});
doCalculations(mixTable);
})
});
document.querySelector("#formSubmit").addEventListener("click", () => {
let formData = {};
@ -307,7 +405,8 @@
formData.note = document.querySelector("#note").value;
sendPost(formData, "/recipe/explore");
sendPost(formData, "/recipe/explore", () => showElement(warningMsgBox));
hideElement(warningMsgBox);
});
document.querySelector("#useSubmit").addEventListener("click", () => {
@ -376,34 +475,64 @@
const mixID = splitReason[0];
const materialID = splitReason[1];
document.querySelector(`#recipe-${mixID}`).querySelector(`#material-${materialID}`).classList.add("notEnough");
document.querySelector(`#mix-${mixID}`).querySelector(`#material-${materialID}`).classList.add("notEnough");
}
function hideQuantities(button) {
let hidden = button.dataset.hidden;
if (hidden === "true") hidden = "false";
else hidden = "true";
button.parentElement.parentElement.parentElement.querySelectorAll(".inventoryQuantity").forEach(e => {
if (hidden === "true") {
e.style.display = "none";
e.parentElement.setAttribute("style", "min-width: auto !important");
button.parentElement.setAttribute("style", "min-width: auto !important");
button.innerText = "->";
} else {
e.style.display = "inline";
e.parentElement.setAttribute("style", "min-width: 100px !important");
button.parentElement.setAttribute("style", "min-width: 100px !important");
button.innerText = quantityKeyword;
}
});
button.dataset.hidden = hidden;
}
function sendPost(data, url, errorCallback) {
const successP = document.querySelector(".success");
const errorP = document.querySelector(".error");
successP.innerHTML = "";
errorP.innerHTML = "";
hideElement(successMsgBox);
hideElement(errorMsgBox);
axios.post(url, data)
.then(r => {
console.log(r);
const data = r.data;
if (data.success !== undefined) {
successP.innerHTML = data.success.message;
successMsgBoxText.innerHTML = data.success.message;
showElement(successMsgBox);
return true;
} else if (data.error !== undefined) {
errorP.innerHTML = data.error.message;
errorMsgBoxText.innerText = data.error.message;
showElement(errorMsgBox);
if (typeof errorCallback !== 'undefined') {
errorCallback(data.reason);
}
return false;
}
})
.catch(e => {
console.log(e);
errorP.innerHTML = "[[#{error.serverError}]]";
errorMsgBoxText.innerText = "[[#{error.serverError}]]";
showElement(errorMsgBox);
return false;
});
}
@ -418,7 +547,6 @@
const p = parent.querySelector(`.calculation[data-materialid='${materialID}'][data-mixid='${mixID}']`);
let totalQuantity = round(lastQuantity + parseFloat(quantity));
// p.innerText = `+${quantity} (${totalQuantity})`;
p.dataset.quantity = quantity;
p.dataset.totalQuantity = totalQuantity;
@ -426,6 +554,10 @@
lastQuantity = totalQuantity;
});
const totalQuantityCustomizer = parent.querySelector(".totalQuantityCustomizer");
totalQuantityCustomizer.value = lastQuantity;
totalQuantityCustomizer.defaultValue = lastQuantity;
}
function changeCalculations() {

View File

@ -44,7 +44,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<p class="success" th:if="${recipeCode != null}"
th:text="#{recipe.success.deleted(${recipeCode})}"></p>
@ -60,6 +60,7 @@
<th th:text="#{recipe.color}"></th>
<th th:text="#{recipe.description}"></th>
<th th:text="#{recipe.sample}"></th>
<th></th>
</tr>
<tr th:each="recipe : ${recipeMap.get(company)}" class="recipeRow"
th:data-approbationDate="${recipe.approbationDate}">

View File

@ -17,7 +17,7 @@
<header th:include="fragments.html :: header"></header>
<!-- Corps de la page -->
<section>
<p th:include="fragments.html :: error"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{keyword.updates}"></h1>
<div id="markdown"></div>
@ -34,7 +34,8 @@
document.querySelector("#markdown").innerHTML = r.data;
})
.catch(e => {
errorP.innerText = "Une erreur est survenue lors de la récupération des mises à jour";
errorMsgBoxText.innerText = "Une erreur est survenue lors de la récupération des mises à jour";
showElement(errorMsgBox);
console.log(e);
});
});

View File

@ -1,3 +1,18 @@
# v1.1.3
# v1.1.2
### Corrections
* Amélioration du style du site.
* Correction d'un bug qui permettait de supprimer des images sans mot de passe.
### Ajouts
* Il n'est plus possible de modifier la quantité des produits utilisant les pourcentages dans l'explorateur de recette.
* La quantité initiale d'un produit peut maintenant être cachée dans l'explorateur de recette.
* La quantité minimum d'un produit dans un mélange est maintenant de 0.001.
* La quantité de chaque produit d'un mélange peut maintenant être calculée depuis la quantitée totale du mélange.
* Ajout de boîtes de notification améliorées.
# v1.1.1
### Corrections
* Désactivation de l'autocomplétion dans les étapes des recettes (permet d'éviter un bug qui affiche les suggestion par dessus toutes les étapes sur Edge)
@ -14,4 +29,4 @@
* Ajout de la journalisation.
### Dépendances
* Ajout de Lombok
* Ajout de Lombok