# v1.2.0 (Imprimante P-touch)

### Corrections
* Correction d'un bug qui empêchait la suppression des mélanges.
* Correction d'un bug qui empêche les boutons supprimer de fonctionner.
* Correction d'un bug qui permettait d'envoyer les formulaires demandant des mots de passe sans donner un mot de passe valide.
* Correction d'une désynchronisation entre le nom des mélanges et leur produit interne.
* Amélioration du style.
* Amélioration de la fluidité de la navigation.
* Transition complète des modèles vers Lombok.

### Ajouts
* Ajout du support pour l'imprimante P-touch de Brother.
  * L'extension b-Pac doit être installée sur le navigateur des clients.
    * [Firefox](https://cre.fyloz.dev/bpac.xpi)
    * [Chrome](https://chrome.google.com/webstore/detail/brother-b-pac-extension/ilpghlfadkjifilabejhhijpfphfcfhb)
  * Le logiciel b-Pac doit être installé sur l'ordinateur des clients.
    * [Windows](https://download.brother.com/welcome/dlfp100614/bcciw32031.msi)
* Ajout de la possibilité d'imprimer les mélanges avec P-Touch
* Ajout de la possibilité d'imprimer les étiquettes de kit de retouche avec P-Touch
* Ajout des boîtes de confirmation.
* Ajout d'un type de produit aux mélanges.
* Ajout du changement d'ordre des produits d'un mélange.
* Les produits dans l'inventaire sont maintenant ordonnés alphabétiquement.

### Dépendances
* Ajout de jQuery, début de la transition.
* Migration vers Java 11
This commit is contained in:
William Nolin 2020-01-29 20:48:25 -05:00
parent 4f7336e982
commit 88f7ec338e
91 changed files with 2936 additions and 1275 deletions

25
pom.xml
View File

@ -10,12 +10,12 @@
</parent>
<groupId>dev.fyloz.trial.colorrecipesexplorer</groupId>
<artifactId>ColorRecipesExplorer</artifactId>
<version>1.1.3</version>
<version>1.2.0</version>
<name>Color Recipes Explorer</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<java.version>11</java.version>
</properties>
<dependencies>
@ -41,11 +41,6 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.18</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
@ -56,16 +51,6 @@
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.10</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
@ -95,6 +80,12 @@
<artifactId>commonmark</artifactId>
<version>0.13.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<build>

View File

@ -35,7 +35,7 @@ public class InitialDataLoader implements ApplicationListener<ApplicationReadyEv
MaterialType defaultMaterialType = new MaterialType(name, "", false);
Optional<MaterialType> optionalSavedMaterialType = materialTypeService.save(defaultMaterialType);
if (!optionalSavedMaterialType.isPresent()) {
if (optionalSavedMaterialType.isEmpty()) {
ColorRecipesExplorerApplication.LOGGER.warn(String.format("Échec de la création du type de produit par défaut '%s'.", name));
}
}

View File

@ -28,6 +28,13 @@ public enum ResponseCode {
RECIPE_NOT_FOUND_NO_PARAMS(25, ResponseCodeType.ERROR, 0),
MATERIAL_NOT_FOUND_BY_NAME(26, ResponseCodeType.ERROR, 1),
SUCCESS_DELETING_COMPANY(27, ResponseCodeType.SUCCESS, 1),
SUCCESS_SAVING_MATERIAL(28, ResponseCodeType.SUCCESS, 1),
SUCCESS_SAVING_MATERIAL_TYPE(29, ResponseCodeType.SUCCESS, 1),
SUCCESS_SAVING_RECIPE(30, ResponseCodeType.SUCCESS, 1),
SUCCESS_DELETING_MATERIAL(31, ResponseCodeType.SUCCESS, 1),
SUCCESS_SAVING_COMPANY(32, ResponseCodeType.SUCCESS, 1),
SUCCESS_DELETING_RECIPE(33, ResponseCodeType.SUCCESS, 1),
SUCCESS_DELETING_MATERIAL_TYPE(34, ResponseCodeType.SUCCESS, 1),
// HTTP Errors
_500(100, ResponseCodeType.ERROR, 0),

View File

@ -11,9 +11,9 @@ import java.io.Serializable;
@Entity
@Table(name = "materials")
@Data
@EqualsAndHashCode(callSuper = false)
@RequiredArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class Material extends BeanModel implements Serializable {
@Id

View File

@ -20,7 +20,7 @@ public class MaterialType extends BeanModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer materialTypeID;
private Integer materialTypeID = 0;
@NonNull
@NotNull

View File

@ -57,27 +57,13 @@ public class Recipe extends BeanModel implements Serializable {
return recipeID;
}
public Material getBase() {
if (recipeMixes.isEmpty() || recipeMixes.stream().allMatch(m -> m.getMixQuantities().isEmpty())) return null;
Material base = recipeMixes
.stream()
.map(mix -> mix
.getMixQuantities()
.stream()
.filter(mq -> mq.getMaterial().getMaterialType().getMaterialTypeName().equals(MaterialType.BASE_MATERIAL_TYPE_NAME))
.findFirst().get()
)
.findFirst().orElse(recipeMixes
.stream()
.filter(m -> !m.getMixQuantities().isEmpty())
.findFirst()
.get()
.getMixQuantities()
.stream()
.findFirst()
.get()).getMaterial();
return base;
}
// public Material getBase() {
// for (Mix mix : recipeMixes) {
// for(MixQuantity mq : mix.getMixQuantities()) {
// if(mq.getMaterial().getMaterialType().getMaterialTypeName().equals(MaterialType.BASE_MATERIAL_TYPE_NAME)) return mq.getMaterial();
// }
// }
//
// return null;
// }
}

View File

@ -32,13 +32,13 @@ public class InventoryService {
return null;
}
public boolean useMix(Mix mix, Map<Material, Float> quantities) {
public boolean useMix(Map<Material, Float> quantities) {
for (Map.Entry<Material, Float> entry : quantities.entrySet()) {
Material material = entry.getKey();
if (!material.isMixType()) {
material.setInventoryQuantity(material.getInventoryQuantity() - entry.getValue());
if (!materialService.update(material).isPresent()) return false;
if (materialService.update(material).isEmpty()) return false;
}
}

View File

@ -72,9 +72,10 @@ public class MaterialService extends GenericService<Material, MaterialDao> {
if (material == null) return false;
Optional<Material> materialByCode = dao.findByMaterialCode(material.getMaterialCode());
return super.isValidForUpdate(material) && (!materialByCode.isPresent() || material.getMaterialID().equals(materialByCode.get().getMaterialID()));
return super.isValidForUpdate(material) && (materialByCode.isEmpty() || material.getMaterialID().equals(materialByCode.get().getMaterialID()));
}
@Deprecated(since = "1.2.0")
public List<Material> getAllBySearchString(String searchString) {
return dao.findAllByMaterialCodeContainingIgnoreCase(searchString).stream().filter(m -> !m.isMixType()).collect(Collectors.toList());
}

View File

@ -46,13 +46,13 @@ public class MaterialTypeService extends GenericService<MaterialType, MaterialTy
public boolean isValidForUpdateName(MaterialType materialType) {
Optional<MaterialType> materialTypeByName = dao.findByMaterialTypeName(materialType.getMaterialTypeName());
return !materialTypeByName.isPresent() || materialType.getMaterialTypeID().equals(materialTypeByName.get().getMaterialTypeID());
return materialTypeByName.isEmpty() || materialType.getMaterialTypeID().equals(materialTypeByName.get().getMaterialTypeID());
}
public boolean isValidForUpdatePrefix(MaterialType materialType) {
Optional<MaterialType> materialTypeByPrefix = dao.findByPrefix(materialType.getPrefix());
return !materialTypeByPrefix.isPresent() || materialType.getMaterialTypeID().equals(materialTypeByPrefix.get().getMaterialTypeID());
return materialTypeByPrefix.isEmpty() || materialType.getMaterialTypeID().equals(materialTypeByPrefix.get().getMaterialTypeID());
}
public Optional<MaterialType> getByName(String name) {

View File

@ -11,9 +11,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class MixService extends GenericService<Mix, MixDao> {
@ -32,6 +31,25 @@ public class MixService extends GenericService<Mix, MixDao> {
this.recipeService = recipeService;
}
public Collection<Material> getAvailableMaterialsForNewMix(Recipe recipe) {
Collection<MixType> recipeMixTypes = recipeService.getAssociatedMixesTypes(recipe);
return materialService
.getAll()
.stream()
.filter(m -> !m.isMixType() || recipeMixTypes.contains(mixTypeService.getByMaterial(m).get()))
.sorted(Comparator.comparing(Material::getMaterialCode))
.sorted(Comparator.comparing(m -> m.getMaterialType().getMaterialTypeName()))
.collect(Collectors.toList());
}
public Collection<Material> getAvailableMaterialsForMix(Recipe recipe, Mix mix) {
return getAvailableMaterialsForNewMix(recipe)
.stream()
.filter(m -> !m.equals(mix.getMixType().getMaterial()))
.collect(Collectors.toList());
}
@Transactional
public ModelResponseBuilder create(MixCreationFormDto formDto, @NotNull Recipe recipe) {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder();
@ -39,7 +57,7 @@ public class MixService extends GenericService<Mix, MixDao> {
List<Material> materials = new ArrayList<>();
for (String materialCode : formDto.getMaterials()) {
Optional<Material> found = materialService.getByMaterialCode(materialCode);
if (!found.isPresent()) {
if (found.isEmpty()) {
return modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND_BY_NAME, materialCode);
}
@ -47,7 +65,7 @@ public class MixService extends GenericService<Mix, MixDao> {
}
Optional<MixType> optionalMixType = mixTypeService.createByName(formDto.getMixTypeName(), formDto.getMaterialType());
if (!optionalMixType.isPresent()) return modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
if (optionalMixType.isEmpty()) return modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
MixType mixType = optionalMixType.get();
if (recipeService.hasMixType(recipe, mixType))
@ -80,7 +98,7 @@ public class MixService extends GenericService<Mix, MixDao> {
List<Material> materials = new ArrayList<>();
for (String materialCode : formDto.getMaterials()) {
Optional<Material> found = materialService.getByMaterialCode(materialCode);
if (!found.isPresent()) {
if (found.isEmpty()) {
return modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND_BY_NAME, materialCode);
}

View File

@ -68,7 +68,7 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
storedRecipe.setNote(newRecipe.getNote());
Optional<Recipe> optionalRecipe = convertAndCreateSteps(storedRecipe, form);
if (!optionalRecipe.isPresent()) {
if (optionalRecipe.isEmpty()) {
return Optional.empty();
}
@ -126,6 +126,7 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
.contains(mixType);
}
@Deprecated(since = "1.2.0")
public Map<Company, List<Recipe>> getRecipesForSearchString(String searchString) {
List<Recipe> recipes = dao.findAllByRecipeDescriptionContainsOrRecipeCodeContains(searchString.toUpperCase());

View File

@ -0,0 +1,32 @@
package dev.fyloz.trial.colorrecipesexplorer.core.services.model;
import dev.fyloz.trial.colorrecipesexplorer.core.utils.PdfBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
public class TouchUpKitService {
private static final String TOUCH_UP_FR = "KIT DE RETOUCHE";
private static final String TOUCH_UP_EN = "TOUCH UP KIT";
public static final int FONT_SIZE = 42;
private ResourceLoader resourceLoader;
@Autowired
public TouchUpKitService(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public byte[] getPdfForJobNumber(String jobNumber) throws IOException {
return new PdfBuilder(resourceLoader, true, FONT_SIZE)
.addLine(TOUCH_UP_FR, true, 0)
.addLine(TOUCH_UP_EN, true, 0)
.addLine(jobNumber, false, 10)
.build();
}
}

View File

@ -0,0 +1,101 @@
package dev.fyloz.trial.colorrecipesexplorer.core.utils;
import dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.springframework.core.io.ResourceLoader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
public class PdfBuilder {
private PDFont font;
private PDDocument document = new PDDocument();
private PDPage page = new PDPage();
private Collection<PdfLine> lines = new ArrayList<>();
private boolean duplicated;
private int fontSize;
private int fontSizeBold;
private int lineSpacing;
public PdfBuilder(ResourceLoader resourceLoader, boolean duplicated, int fontSize) throws IOException {
this.duplicated = duplicated;
this.fontSize = fontSize;
this.fontSizeBold = this.fontSize + 12;
this.lineSpacing = (int) (this.fontSize * 1.5f);
document.addPage(page);
font = PDType0Font.load(document, resourceLoader.getResource(WebsitePaths.FONT_ARIAL_BOLD).getFile());
}
public PdfBuilder addLine(String text, boolean bold, int marginTop) {
lines.add(new PdfLine(text, bold, marginTop));
return this;
}
public byte[] build() throws IOException {
writeContent();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
document.save(outputStream);
document.close();
return outputStream.toByteArray();
}
private void writeContent() throws IOException {
PDPageContentStream contentStream = new PDPageContentStream(document, page);
contentStream.beginText();
int marginTop = 30;
for (PdfLine line : lines) {
writeCenteredText(contentStream, line, marginTop);
marginTop += lineSpacing;
}
if (duplicated) {
marginTop = (int) page.getMediaBox().getHeight() / 2;
for (PdfLine line : lines) {
writeCenteredText(contentStream, line, marginTop);
marginTop += lineSpacing;
}
}
contentStream.endText();
contentStream.close();
}
private void writeCenteredText(PDPageContentStream contentStream, PdfLine line, int marginTop) throws IOException {
float textWidth = font.getStringWidth(line.getText()) / 1000 * (line.isBold() ? fontSizeBold : fontSize);
float textHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * (line.isBold() ? fontSizeBold : fontSize);
float textX = (page.getMediaBox().getWidth() - textWidth) / 2f;
float textY = (page.getMediaBox().getHeight() - (marginTop + line.getMarginTop()) - textHeight);
if (line.isBold()) contentStream.setFont(font, fontSizeBold);
else contentStream.setFont(font, fontSize);
contentStream.newLineAtOffset(textX, textY);
contentStream.showText(line.getText());
contentStream.newLineAtOffset(-textX, -textY); // Réinitialise la position pour la prochaine ligne
}
@Data
@AllArgsConstructor
public static class PdfLine {
private String text;
private boolean bold;
private int marginTop;
}
}

View File

@ -1,19 +1,19 @@
package dev.fyloz.trial.colorrecipesexplorer.web;
public class PagesPaths {
public class WebsitePaths {
// Autres
public static final String INDEX = "index";
public static final String SEARCH = "search";
public static final String SEARCH_INVENTORY = "inventory/search";
public static final String SIMDUT_FILES = "simdut/{materialID}";
public static final String PASSWORD_VALIDATION = "password/valid";
public static final String TOUCHUP = "touchup";
public static final String RECIPE_XLS = "recipe/xls/{recipeID}";
public static final String ALL_RECIPES_XLS = "recipe/xls";
public static final String ERROR = "error";
public static final String CLOSE_TAB = "closeTab";
public static final String UPDATES = "updates";
public static final String UPDATES_GET = "updates/get";
public static final String FONT_ARIAL_BOLD = "classpath:fonts/arialbd.ttf";
// Images
public static final String IMAGES_FILES = "images/{image}";
@ -21,6 +21,12 @@ public class PagesPaths {
public static final String ADD_IMAGE_SPECIFIC = "images/add/{recipeID}";
public static final String DELETE_IMAGE = "images/delete";
// Touch up kits
public static final String TOUCHUP = "touchup";
public static final String TOUCHUP_PDF = "touchup/pdf";
public static final String TOUCHUP_PTOUCH = "touchup/ptouch";
public static final String TOUCHUP_PTOUCH_PAGE = "touchupPtouch";
// Inventaire
public static final String INVENTORY = "inventory";
public static final String USE_INVENTORY = "inventory/use";

View File

@ -10,7 +10,7 @@ import org.springframework.web.servlet.ModelAndView;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.ERROR;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.ERROR;
@Controller
public class CREErrorController implements ErrorController {

View File

@ -6,7 +6,6 @@ import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Company;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.core.services.PasswordService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.CompanyService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
@ -19,17 +18,16 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.INDEX;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.PASSWORD_VALIDATION;
@Controller
public class IndexController {
private CompanyService companyService;
private RecipeService recipeService;
@Autowired
public IndexController(CompanyService companyService, RecipeService recipeService) {
this.companyService = companyService;
public IndexController(RecipeService recipeService) {
this.recipeService = recipeService;
}
@ -39,21 +37,14 @@ public class IndexController {
* @return La page à afficher.
*/
@GetMapping({INDEX, "/"})
public ModelAndView showPage() {
List<Company> companies = companyService.getAll();
Map<Company, List<Recipe>> recipes = new HashMap<>();
for (Company company : companies) {
recipes.put(company, recipeService.getByCompany(company));
}
public ModelAndView getPage() {
return new ModelResponseBuilder(INDEX)
.addResponseData(ResponseDataType.RECIPE_MAP, recipes)
.addResponseData(ResponseDataType.RECIPE_MAP, recipeService.getRecipesByCompany())
.build();
}
@GetMapping(value = SEARCH, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
// @GetMapping(value = SEARCH, produces = MediaType.APPLICATION_JSON_VALUE)
// @ResponseBody
public Map<String, Object> searchWord(@RequestParam String searchString) {
Map<Company, List<Recipe>> searchResult = recipeService.getRecipesForSearchString(searchString);
Map<Integer, List<Integer>> outputResult = new HashMap<>();

View File

@ -21,8 +21,9 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.*;
import java.util.stream.Collectors;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.StringBank.RESPONSE_REASON;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.INVENTORY;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.USE_INVENTORY;
@Controller
public class InventoryController {
@ -41,7 +42,7 @@ public class InventoryController {
}
@GetMapping(INVENTORY)
public ModelAndView getInventory(ModelAndView model) {
public ModelAndView getPage(ModelAndView model) {
return new ModelResponseBuilder(model)
.withView(INVENTORY)
.addResponseData(ResponseDataType.MATERIALS, materialService.getAllOrdered().stream().filter(m -> !m.isMixType()).collect(Collectors.toList()))
@ -77,7 +78,8 @@ public class InventoryController {
@ResponseBody
@Transactional
// TODO vers DTO
public Map<String, Object> consumeMaterials(@RequestBody Map<String, Object> form) {
// TODO + vers service
public Map<String, Object> consumeMaterials(@RequestBody Map<String, Map<String, Float>> form) {
JSONResponseBuilder responseBuilder = new JSONResponseBuilder();
List<Mix> mixes = new ArrayList<>();
@ -87,7 +89,7 @@ public class InventoryController {
int mixID = Integer.parseInt(mixIDStr);
Optional<Mix> optionalMix = mixService.getByID(mixID);
if (!optionalMix.isPresent()) {
if (optionalMix.isEmpty()) {
return responseBuilder
.addResponseCode(ResponseCode.MIX_NOT_FOUND, mixID)
.build();
@ -96,14 +98,15 @@ public class InventoryController {
Mix mix = optionalMix.get();
mixes.add(mix);
Map<String, String> formMaterials = (Map<String, String>) form.get(mixIDStr);
Map<String, Float> formMaterials = form.get(mixIDStr);
Map<Material, Float> mixQuantities = new HashMap<>();
for (Material material : mix.getMixQuantities().stream().map(MixQuantity::getMaterial).collect(Collectors.toList())) {
String materialIDAsString = String.valueOf(material.getMaterialID());
if (formMaterials.containsKey(materialIDAsString)) {
mixQuantities.put(material, Float.parseFloat(formMaterials.get(materialIDAsString)));
Float quantityAsString = formMaterials.get(materialIDAsString);
mixQuantities.put(material, quantityAsString);
}
}
@ -123,7 +126,7 @@ public class InventoryController {
}
for (Mix mix : mixes) {
if (!inventoryService.useMix(mix, quantities.get(mix))) {
if (!inventoryService.useMix(quantities.get(mix))) {
return responseBuilder
.addResponseCode(ResponseCode.ERROR_SAVING)
.build();
@ -135,8 +138,8 @@ public class InventoryController {
.build();
}
@GetMapping(value = SEARCH_INVENTORY, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
// @GetMapping(value = SEARCH_INVENTORY, produces = MediaType.APPLICATION_JSON_VALUE)
// @ResponseBody
public Map<String, Object> searchWordInventory(@RequestParam String searchString) {
List<Material> searchResult = materialService.getAllBySearchString(searchString);
List<Integer> outputResult = searchResult.stream().map(Material::getMaterialID).collect(Collectors.toList());

View File

@ -1,23 +1,19 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Mix;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MixType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MaterialService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MixService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MixTypeService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.core.utils.FileUtils;
import dev.fyloz.trial.colorrecipesexplorer.xlsx.XlsxExporter;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@ -27,19 +23,11 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.StringBank.MATERIALS;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Controller
public class OthersController {
@ -69,7 +57,7 @@ public class OthersController {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder(MATERIAL_SELECTOR_FRAGMENT);
Optional<Recipe> optionalRecipe = recipeService.getByID(recipeID);
if (!optionalRecipe.isPresent()) {
if (optionalRecipe.isEmpty()) {
return modelResponseBuilder
.withView("")
.build();
@ -78,77 +66,13 @@ public class OthersController {
Optional<Mix> optionalMix = mixService.getByID(mixID);
boolean mixExist = optionalMix.isPresent();
List<MixType> associatedMixTypes = recipeService.getAssociatedMixesTypes(optionalRecipe.get());
// Récupère seulement les produits qui ne sont pas des types de mélange OU que ce type existe dans la recette
List<Material> materials = materialService
.getAll()
.stream()
.filter(m -> !m.isMixType() || (!mixExist || !m.equals(optionalMix.get().getMixType().getMaterial())) && associatedMixTypes.contains(mixTypeService.getByMaterial(m).get()))
.sorted(Comparator.comparing(Material::getMaterialCode))
.sorted(Comparator.comparing(m -> m.getMaterialType().getMaterialTypeName()))
.collect(Collectors.toList());
Collection<Material> materials = mixExist ? mixService.getAvailableMaterialsForMix(optionalRecipe.get(), optionalMix.get()) : mixService.getAvailableMaterialsForNewMix(optionalRecipe.get());
return modelResponseBuilder
.addAttribute(MATERIALS, materials)
.build();
}
@GetMapping(TOUCHUP)
public ModelAndView getTouchUpPdf() {
return new ModelResponseBuilder().withRedirect("pdf/touchup.pdf").build();
}
@GetMapping(RECIPE_XLS)
public ResponseEntity<byte[]> getXlsForRecipe(HttpServletRequest request, @PathVariable int recipeID) {
HttpHeaders headers = new HttpHeaders();
Optional<Recipe> optionalRecipe = recipeService.getByID(recipeID);
if (!optionalRecipe.isPresent()) {
headers.add(HttpHeaders.LOCATION, request.getHeader("referer"));
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}
byte[] recipeXLS = new XlsxExporter().generate(optionalRecipe.get());
if (recipeXLS.length <= 0) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
return ResponseEntity.ok()
.headers(headers)
.contentLength(recipeXLS.length)
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
.body(recipeXLS);
}
@GetMapping(value = ALL_RECIPES_XLS, produces = "application/zip")
public ResponseEntity<byte[]> getAllXls() throws IOException {
HttpHeaders headers = new HttpHeaders();
ColorRecipesExplorerApplication.LOGGER.info("Exportation de toutes les couleurs en XLS");
ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
ZipOutputStream out = new ZipOutputStream(byteOS);
Collection<Recipe> recipes = recipeService.getAll();
for (Recipe recipe : recipes) {
byte[] recipeXLS = new XlsxExporter().generate(recipe);
if (recipeXLS.length <= 0) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
out.putNextEntry(new ZipEntry(String.format("%s_%s.xlsx", recipe.getCompany().getCompanyName(), recipe.getRecipeCode())));
out.write(recipeXLS, 0, recipeXLS.length);
out.closeEntry();
}
out.close();
byte[] zipContent = byteOS.toByteArray();
byteOS.close();
return ResponseEntity.ok()
.headers(headers)
.contentLength(zipContent.length)
.contentType(MediaType.parseMediaType("application/zip"))
.body(zipContent);
}
@GetMapping(value = UPDATES)
public ModelAndView showUpdates() {
return new ModelResponseBuilder(UPDATES).build();
@ -156,7 +80,7 @@ public class OthersController {
@GetMapping(value = UPDATES_GET, produces = MediaType.TEXT_HTML_VALUE)
@ResponseBody
public ResponseEntity<String> getUpdates() throws IOException {
public ResponseEntity<String> getUpdates() {
String fileContent = FileUtils.readClasspathFile(resourceLoader, "classpath:updates.md");
if (fileContent == null) return ResponseEntity.status(HttpStatus.NOT_FOUND).build();

View File

@ -16,7 +16,7 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Controller
public class RecipeExplorerController {
@ -44,11 +44,11 @@ public class RecipeExplorerController {
* @return La page à afficher.
*/
@GetMapping(EXPLORER_RECIPE_SPECIFIC)
public ModelAndView showRecipe(@PathVariable int recipeID) {
public ModelAndView getPage(@PathVariable int recipeID) {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder(EXPLORER_RECIPE);
Optional<Recipe> optionalRecipe = recipeService.getByID(recipeID);
if (!optionalRecipe.isPresent()) {
if (optionalRecipe.isEmpty()) {
return modelResponseBuilder
.withView(INDEX)
.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, recipeID)
@ -78,7 +78,7 @@ public class RecipeExplorerController {
String note = form.get("note").toString();
Optional<Recipe> optionalRecipe = recipeService.getByID(recipeID);
if (!optionalRecipe.isPresent()) {
if (optionalRecipe.isEmpty()) {
responseBuilder.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, recipeID);
} else {
Recipe recipe = optionalRecipe.get();
@ -91,7 +91,7 @@ public class RecipeExplorerController {
int mixID = Integer.parseInt(mixIDStr);
Optional<Mix> optionalMix = mixService.getByID(mixID);
if (!optionalMix.isPresent()) {
if (optionalMix.isEmpty()) {
responseBuilder.addResponseCode(ResponseCode.MIX_NOT_FOUND, mixID);
} else {
Mix mix = optionalMix.get();

View File

@ -16,8 +16,8 @@ import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.CREATOR_COMPANY;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.INDEX;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.CREATOR_COMPANY;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.INDEX;
@Controller
public class CompanyCreatorController {
@ -33,7 +33,7 @@ public class CompanyCreatorController {
public ModelAndView showCreationPage(ModelAndView model, Company company) {
return new ModelResponseBuilder(model)
.withView(CREATOR_COMPANY)
.addResponseData(ResponseDataType.COMPANY, company)
.addResponseData(ResponseDataType.COMPANY, company == null ? new Company() : company)
.build();
}
@ -62,9 +62,9 @@ public class CompanyCreatorController {
Optional<Company> savedCompany = companyService.save(company);
if (savedCompany.isPresent()) {
return modelResponseBuilder
.addResponseData(ResponseDataType.COMPANY_NAME, savedCompany.get().getCompanyName())
.build();
return showCreationPage(modelResponseBuilder
.addResponseCode(ResponseCode.SUCCESS_SAVING_COMPANY, savedCompany.get().getCompanyName())
.build(), null);
} else {
modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
}

View File

@ -17,7 +17,7 @@ import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.CREATOR_MATERIAL;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.CREATOR_MATERIAL;
@Controller
public class MaterialCreatorController {
@ -68,7 +68,7 @@ public class MaterialCreatorController {
if (savedMaterial.isPresent()) {
material = savedMaterial.get();
modelResponseBuilder.addResponseData(ResponseDataType.MATERIAL_CODE, material.getMaterialCode());
modelResponseBuilder.addResponseCode(ResponseCode.SUCCESS_SAVING_MATERIAL, material.getMaterialCode());
if (simdut.getSize() > 0 && !materialService.addSimdut(simdut, material)) {
modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING_SIMDUT);

View File

@ -13,9 +13,10 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.CREATOR_MATERIAL_TYPE;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.INDEX;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.CREATOR_MATERIAL_TYPE;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.INDEX;
@Controller
public class MaterialTypeCreatorController {
@ -28,7 +29,7 @@ public class MaterialTypeCreatorController {
}
@GetMapping(CREATOR_MATERIAL_TYPE)
public ModelAndView showPage(ModelAndView model, MaterialType materialType) {
public ModelAndView getPage(ModelAndView model, MaterialType materialType) {
return new ModelResponseBuilder(model)
.withView(CREATOR_MATERIAL_TYPE)
.addResponseData(ResponseDataType.MATERIAL_TYPE, materialType == null ? new MaterialType() : materialType)
@ -40,8 +41,11 @@ public class MaterialTypeCreatorController {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder(ControllerUtils.redirect(INDEX));
if (materialTypeService.isValidForCreation(materialType)) {
if (materialTypeService.save(materialType) != null) {
return modelResponseBuilder.build();
Optional<MaterialType> optionalMaterialType = materialTypeService.save(materialType);
if (optionalMaterialType.isPresent()) {
return getPage(modelResponseBuilder
.addResponseCode(ResponseCode.SUCCESS_SAVING_MATERIAL_TYPE, optionalMaterialType.get().getMaterialTypeName())
.build(), null);
} else {
modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
}
@ -49,6 +53,6 @@ public class MaterialTypeCreatorController {
modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_ALREADY_EXIST, materialType.getMaterialTypeName());
}
return showPage(modelResponseBuilder.build(), materialType);
return getPage(modelResponseBuilder.build(), materialType);
}
}

View File

@ -3,8 +3,6 @@ package dev.fyloz.trial.colorrecipesexplorer.web.controller.creators;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MixType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.MixCreationFormDto;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.*;
@ -19,12 +17,9 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Controller
public class MixCreatorController {
@ -58,7 +53,7 @@ public class MixCreatorController {
.withView(CREATOR_MIX);
Optional<Recipe> optionalRecipe = recipeService.getByID(recipeID);
if (!optionalRecipe.isPresent()) {
if (optionalRecipe.isEmpty()) {
return modelResponseBuilder
.withView(ControllerUtils.redirect(EDITOR_RECIPE))
.build();
@ -66,20 +61,10 @@ public class MixCreatorController {
Recipe recipe = optionalRecipe.get();
List<MixType> associatedMixTypes = recipeService.getAssociatedMixesTypes(recipe);
// Récupère seulement les produits qui ne sont pas des types de mélange OU que ce type existe dans la recette
List<Material> materials = materialService
.getAll()
.stream()
.filter(m -> !m.isMixType() || associatedMixTypes.contains(mixTypeService.getByMaterial(m).get()))
.sorted(Comparator.comparing(Material::getMaterialCode))
.sorted(Comparator.comparing(m -> m.getMaterialType().getMaterialTypeName()))
.collect(Collectors.toList());
ModelResponseBuilder responseBuilder = modelResponseBuilder
.addResponseData(ResponseDataType.RECIPE, recipe)
.addResponseData(ResponseDataType.MATERIAL_TYPES, materialTypeService.getAll())
.addAttribute("materialsJson", materialService.asJson(materials));
.addAttribute("materialsJson", materialService.asJson(mixService.getAvailableMaterialsForNewMix(recipe)));
if (materialService.getAll().isEmpty()) {
responseBuilder.addResponseCode(ResponseCode.NO_MATERIAL)

View File

@ -15,8 +15,8 @@ import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.CREATOR_RECIPE;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.CREATOR_RECIPE_SUCCESS;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.CREATOR_RECIPE;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.EDITOR_RECIPE_SPECIFIC;
@Controller
public class RecipeCreatorController {
@ -62,12 +62,12 @@ public class RecipeCreatorController {
*/
@PostMapping(value = CREATOR_RECIPE)
public ModelAndView createRecipe(@Valid Recipe recipe) {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder(CREATOR_RECIPE_SUCCESS);
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder();
Optional<Recipe> savedRecipe = recipeService.save(recipe);
if (savedRecipe.isPresent()) {
return modelResponseBuilder
.addResponseData(ResponseDataType.RECIPE, savedRecipe.get())
.withRedirect(EDITOR_RECIPE_SPECIFIC, savedRecipe.get().getRecipeID())
.build();
}
modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);

View File

@ -19,7 +19,7 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.Optional;
import java.util.stream.Collectors;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Controller
public class MaterialEditorController {
@ -58,7 +58,7 @@ public class MaterialEditorController {
if (material.getMaterialCode() == null) {
Optional<Material> optionalMaterial = materialService.getByID(materialID);
if (!optionalMaterial.isPresent()) {
if (optionalMaterial.isEmpty()) {
return listMaterials(
responseBuilder
.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND, materialID)
@ -110,7 +110,8 @@ public class MaterialEditorController {
} else {
Optional<Material> updatedMaterial = materialService.update(material);
if (updatedMaterial.isPresent()) {
responseBuilder.addResponseData(ResponseDataType.MATERIAL_CODE, updatedMaterial.get().getMaterialCode());
responseBuilder.addResponseCode(ResponseCode.SUCCESS_SAVING_MATERIAL, updatedMaterial.get().getMaterialCode());
// responseBuilder.addResponseData(ResponseDataType.MATERIAL_CODE, updatedMaterial.get().getMaterialCode());
} else {
return showEditPage(
responseBuilder
@ -159,7 +160,7 @@ public class MaterialEditorController {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder(ControllerUtils.redirect("material/editor/" + materialID));
Optional<Material> optionalMaterial = materialService.getByID(materialID);
if (!optionalMaterial.isPresent()) {
if (optionalMaterial.isEmpty()) {
return chooseSIMDUTFile(modelResponseBuilder
.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND, materialID)
.build(),

View File

@ -15,7 +15,7 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Controller
public class MaterialTypeEditorController {
@ -43,7 +43,7 @@ public class MaterialTypeEditorController {
if (materialType.getMaterialTypeName() == null) {
Optional<MaterialType> optionalMaterialType = materialTypeService.getByID(materialTypeID);
if (!optionalMaterialType.isPresent()) {
if (optionalMaterialType.isEmpty()) {
return listMaterialTypes(
responseBuilder
.addResponseCode(ResponseCode.MATERIAL_TYPE_NOT_FOUND, materialTypeID)
@ -62,32 +62,38 @@ public class MaterialTypeEditorController {
@PostMapping(value = EDITOR_MATERIAL_TYPE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ModelAndView saveEditedMaterialType(MaterialType materialType) {
ModelResponseBuilder responseBuilder = new ModelResponseBuilder("");
int materialTypeID = materialType.getMaterialTypeID();
if (!materialTypeService.getByID(materialTypeID).isPresent()) {
responseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_NOT_FOUND, materialTypeID);
} else if (!materialTypeService.isValidForUpdateName(materialType)) {
return showEditPage(
responseBuilder
.addResponseCode(ResponseCode.MATERIAL_TYPE_ALREADY_EXIST, materialType.getMaterialTypeName())
.build(),
materialTypeID, materialType);
} else if (!materialTypeService.isValidForUpdatePrefix(materialType)) {
return showEditPage(
responseBuilder
.addResponseCode(ResponseCode.MATERIAL_TYPE_ALREADY_EXIST_PREFIX, materialType.getPrefix())
.build(),
materialTypeID, materialType);
// L'ID est 0 lors de la désérialisation, il faut utiliser le nom unique
String materialTypeName = materialType.getMaterialTypeName();
Optional<MaterialType> optionalMaterialType = materialTypeService.getByName(materialTypeName);
if (optionalMaterialType.isEmpty()) {
responseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_NOT_FOUND, materialTypeName);
} else {
Optional<MaterialType> updatedMaterialType = materialTypeService.update(materialType);
if (updatedMaterialType.isPresent()) {
responseBuilder.addResponseData(ResponseDataType.MATERIAL_TYPE_NAME, updatedMaterialType.get().getMaterialTypeName());
} else {
materialType = optionalMaterialType.get();
if (!materialTypeService.isValidForUpdateName(materialType)) {
return showEditPage(
responseBuilder
.addResponseCode(ResponseCode.ERROR_SAVING)
.addResponseCode(ResponseCode.MATERIAL_TYPE_ALREADY_EXIST, materialType.getMaterialTypeName())
.build(),
materialTypeID, null);
materialType.getMaterialTypeID(), materialType);
} else if (!materialTypeService.isValidForUpdatePrefix(materialType)) {
return showEditPage(
responseBuilder
.addResponseCode(ResponseCode.MATERIAL_TYPE_ALREADY_EXIST_PREFIX, materialType.getPrefix())
.build(),
materialType.getMaterialTypeID(), materialType);
} else {
Optional<MaterialType> updatedMaterialType = materialTypeService.update(materialType);
if (updatedMaterialType.isPresent()) {
responseBuilder.addResponseCode(ResponseCode.SUCCESS_SAVING_MATERIAL_TYPE, updatedMaterialType.get().getMaterialTypeName());
} else {
return showEditPage(
responseBuilder
.addResponseCode(ResponseCode.ERROR_SAVING)
.build(),
materialType.getMaterialTypeID(), materialType);
}
}
}

View File

@ -3,9 +3,7 @@ package dev.fyloz.trial.colorrecipesexplorer.web.controller.editors;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Mix;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MixType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.MixCreationFormDto;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.*;
import dev.fyloz.trial.colorrecipesexplorer.core.utils.ControllerUtils;
@ -19,13 +17,10 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.StringBank.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Controller
public class MixEditorController {
@ -54,27 +49,18 @@ public class MixEditorController {
* @return La page à afficher.
*/
@GetMapping(EDITOR_MIX_SPECIFIC)
public ModelAndView showPage(ModelAndView model, @PathVariable int mixID) {
public ModelAndView getPage(ModelAndView model, @PathVariable int mixID) {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder(model)
.withView(EDITOR_MIX_SPECIFIC.replaceAll("/\\{" + MIX_ID + "}", ""));
Optional<Mix> optionalMix = mixService.getByID(mixID);
if (!optionalMix.isPresent()) {
if (optionalMix.isEmpty()) {
return modelResponseBuilder
.withView(ControllerUtils.redirect(EDITOR_RECIPE))
.build();
}
Mix mix = optionalMix.get();
List<MixType> associatedMixTypes = recipeService.getAssociatedMixesTypes(mix.getRecipe());
// Récupère seulement les produits qui ne sont pas des types de mélange OU que ce type existe dans la recette
List<Material> materials = materialService
.getAll()
.stream()
.filter(m -> !m.isMixType() || (!m.equals(mix.getMixType().getMaterial()) && associatedMixTypes.contains(mixTypeService.getByMaterial(m).get())))
.sorted(Comparator.comparing(Material::getMaterialCode))
.sorted(Comparator.comparing(m -> m.getMaterialType().getMaterialTypeName()))
.collect(Collectors.toList());
return modelResponseBuilder
.addResponseData(ResponseDataType.MIX, mix)
@ -82,7 +68,7 @@ public class MixEditorController {
.addAttribute(MATERIAL_TYPE, mix.getMixType().getMaterial().getMaterialType())
.addAttribute(MATERIAL_TYPES, materialTypeService.getAll())
.addAttribute("mixJson", materialService.asJson(mix))
.addAttribute("materialsJson", materialService.asJson(materials))
.addAttribute("materialsJson", mixService.asJson(mixService.getAvailableMaterialsForMix(mix.getRecipe(), mix)))
.build();
}
@ -108,10 +94,10 @@ public class MixEditorController {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder();
Optional<Mix> optionalMix = mixService.getByID(mixID);
if (!optionalMix.isPresent()) {
if (optionalMix.isEmpty()) {
modelResponseBuilder.addResponseCode(ResponseCode.MIX_NOT_FOUND, mixID);
return showPage(modelResponseBuilder.build(), mixID);
return getPage(modelResponseBuilder.build(), mixID);
}
Mix mix = optionalMix.get();
@ -119,7 +105,7 @@ public class MixEditorController {
ModelResponseBuilder editResult = mixService.edit(mix, formDto);
if (editResult != null) {
return showPage(
return getPage(
modelResponseBuilder
.addResponseCode(ResponseCode.ERROR_SAVING)
.build(),
@ -134,8 +120,8 @@ public class MixEditorController {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder("");
Optional<Mix> optionalMix = mixService.getByID(mixID);
if (!optionalMix.isPresent()) {
return showPage(modelResponseBuilder
if (optionalMix.isEmpty()) {
return getPage(modelResponseBuilder
.addResponseCode(ResponseCode.MIX_NOT_FOUND, mixID)
.build(),
mixID);
@ -147,7 +133,7 @@ public class MixEditorController {
if (!mixService.deleteMix(mix)) {
modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
return showPage(modelResponseBuilder.build(), mixID);
return getPage(modelResponseBuilder.build(), mixID);
}
return modelResponseBuilder.build();

View File

@ -20,7 +20,7 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.ArrayList;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Controller
public class RecipeEditorController {
@ -59,7 +59,7 @@ public class RecipeEditorController {
if (recipe.getRecipeCode() == null || recipe.getCompany() == null) {
Optional<Recipe> optionalRecipe = recipeService.getByID(recipeID);
if (!optionalRecipe.isPresent()) {
if (optionalRecipe.isEmpty()) {
return listRecipes(
modelResponseBuilder
.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, recipeID)
@ -104,7 +104,7 @@ public class RecipeEditorController {
int recipeID = recipe.getRecipeID();
Optional<Recipe> optionalStoredRecipe = recipeService.getByID(recipeID);
if (!optionalStoredRecipe.isPresent()) {
if (optionalStoredRecipe.isEmpty()) {
return listRecipes(
modelResponseBuilder
.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, recipeID)
@ -113,7 +113,7 @@ public class RecipeEditorController {
Optional<Recipe> optionalRecipe = recipeService.updateRecipe(recipe, optionalStoredRecipe.get(), form);
if (optionalRecipe.isPresent()) {
modelResponseBuilder.addResponseData(ResponseDataType.RECIPE_CODE, optionalRecipe.get().getRecipeCode());
modelResponseBuilder.addResponseCode(ResponseCode.SUCCESS_SAVING_RECIPE, optionalRecipe.get().getRecipeCode());
return listRecipes(modelResponseBuilder.build());
}

View File

@ -1,4 +1,4 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller;
package dev.fyloz.trial.colorrecipesexplorer.web.controller.files;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import dev.fyloz.trial.colorrecipesexplorer.core.io.file.ImageHandler;
@ -25,7 +25,7 @@ import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.*;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Controller
public class ImageFilesController {
@ -67,7 +67,7 @@ public class ImageFilesController {
* @return La page à afficher.
*/
@GetMapping(ADD_IMAGE_SPECIFIC)
public ModelAndView showPage(ModelAndView model, @PathVariable int recipeID) {
public ModelAndView getPage(ModelAndView model, @PathVariable int recipeID) {
return new ModelResponseBuilder(model)
.withView(ADD_IMAGE)
.addResponseData(ResponseDataType.RECIPE_ID, recipeID)
@ -100,14 +100,14 @@ public class ImageFilesController {
// Vérifie que le fichier est bien une image
if (ImageIO.read(image.getInputStream()) == null) {
return showPage(modelResponseBuilder
return getPage(modelResponseBuilder
.addResponseCode(ResponseCode.FILE_NOT_IMAGE)
.build(), recipeID);
}
Optional<Recipe> optionalRecipe = recipeService.getByID(recipeID);
if (!optionalRecipe.isPresent()) {
return showPage(modelResponseBuilder
if (optionalRecipe.isEmpty()) {
return getPage(modelResponseBuilder
.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, recipeID)
.build(), recipeID);
}
@ -115,7 +115,7 @@ public class ImageFilesController {
Recipe recipe = optionalRecipe.get();
ImageHandler imageHandler = new ImageHandler(recipe, recipeService);
if (!imageHandler.createFile()) {
return showPage(modelResponseBuilder
return getPage(modelResponseBuilder
.addResponseCode(ResponseCode.ERROR_SAVING_IMAGE)
.build(), recipeID);
}
@ -133,7 +133,7 @@ public class ImageFilesController {
ColorRecipesExplorerApplication.LOGGER.error("Erreur inconnue lors de la création d'une image", e);
modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING_IMAGE);
return showPage(modelResponseBuilder.build(), recipeID);
return getPage(modelResponseBuilder.build(), recipeID);
}
}

View File

@ -1,4 +1,4 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller;
package dev.fyloz.trial.colorrecipesexplorer.web.controller.files;
import dev.fyloz.trial.colorrecipesexplorer.core.io.file.FileHandler;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
@ -16,8 +16,8 @@ import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.CLOSE_TAB;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.SIMDUT_FILES;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.CLOSE_TAB;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.SIMDUT_FILES;
@Controller
public class SIMDUTFilesController {
@ -34,7 +34,7 @@ public class SIMDUTFilesController {
Optional<Material> optionalMaterial = materialService.getByID(materialID);
HttpHeaders headers = new HttpHeaders();
if (!optionalMaterial.isPresent()) {
if (optionalMaterial.isEmpty()) {
headers.add("Location", request.getHeader("referer"));
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}
@ -53,7 +53,7 @@ public class SIMDUTFilesController {
@PostMapping(SIMDUT_FILES)
public ResponseEntity<Void> getFile(@PathVariable int materialID) {
Optional<Material> optionalMaterial = materialService.getByID(materialID);
if (!optionalMaterial.isPresent()) return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
if (optionalMaterial.isEmpty()) return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
FileHandler fileHandler = new FileHandler(String.format("%s_%s", materialID, optionalMaterial.get().getMaterialCode()), FileHandler.FileContext.SIMDUT, FileHandler.FileExtension.PDF);
if (!fileHandler.isValid()) return ResponseEntity.status(HttpStatus.NOT_FOUND).build();

View File

@ -0,0 +1,53 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.files;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.TouchUpKitService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Controller
public class TouchUpKitController {
private TouchUpKitService touchUpKitService;
private RecipeService recipeService;
@Autowired
public TouchUpKitController(TouchUpKitService touchUpKitService, RecipeService recipeService) {
this.touchUpKitService = touchUpKitService;
this.recipeService = recipeService;
}
@GetMapping(TOUCHUP)
public ModelAndView getPage(ModelAndView model) {
return new ModelResponseBuilder(model)
.withView(TOUCHUP)
.addResponseData(ResponseDataType.RECIPE_MAP, recipeService.getRecipesByCompany())
.build();
}
@PostMapping(value = TOUCHUP_PDF, produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<byte[]> getTouchUpKitPdf(@RequestBody String jobNumber) throws IOException {
return new ResponseEntity<>(touchUpKitService.getPdfForJobNumber(jobNumber.replace("jobNumber=", "")), HttpStatus.FOUND);
}
@PostMapping(value = TOUCHUP_PTOUCH)
public ModelAndView getTouchUpKitPtouch(@RequestBody String jobNumber) {
return new ModelResponseBuilder(TOUCHUP_PTOUCH_PAGE)
.addAttribute("jobNumber", jobNumber.replace("jobNumber=", ""))
.build();
}
}

View File

@ -0,0 +1,89 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.files;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.xlsx.XlsxExporter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.ALL_RECIPES_XLS;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.RECIPE_XLS;
// TODO Une grande partie du code de ce controlleur devrait se trouver dans un service
@Controller
public class XlsExporterController {
private RecipeService recipeService;
@Autowired
public XlsExporterController(RecipeService recipeService) {
this.recipeService = recipeService;
}
@GetMapping(RECIPE_XLS)
public ResponseEntity<byte[]> getXlsForRecipe(HttpServletRequest request, @PathVariable int recipeID) {
HttpHeaders headers = new HttpHeaders();
Optional<Recipe> optionalRecipe = recipeService.getByID(recipeID);
if (optionalRecipe.isEmpty()) {
headers.add(HttpHeaders.LOCATION, request.getHeader("referer"));
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}
byte[] recipeXLS = new XlsxExporter().generate(optionalRecipe.get());
if (recipeXLS.length <= 0) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
return ResponseEntity.ok()
.headers(headers)
.contentLength(recipeXLS.length)
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
.body(recipeXLS);
}
@GetMapping(value = ALL_RECIPES_XLS, produces = "application/zip")
public ResponseEntity<byte[]> getAllXls() throws IOException {
HttpHeaders headers = new HttpHeaders();
ColorRecipesExplorerApplication.LOGGER.info("Exportation de toutes les couleurs en XLS");
ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
ZipOutputStream out = new ZipOutputStream(byteOS);
Collection<Recipe> recipes = recipeService.getAll();
for (Recipe recipe : recipes) {
byte[] recipeXLS = new XlsxExporter().generate(recipe);
if (recipeXLS.length <= 0) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
out.putNextEntry(new ZipEntry(String.format("%s_%s.xlsx", recipe.getCompany().getCompanyName(), recipe.getRecipeCode())));
out.write(recipeXLS, 0, recipeXLS.length);
out.closeEntry();
}
out.close();
byte[] zipContent = byteOS.toByteArray();
byteOS.close();
return ResponseEntity.ok()
.headers(headers)
.contentLength(zipContent.length)
.contentType(MediaType.parseMediaType("application/zip"))
.body(zipContent);
}
}

View File

@ -14,8 +14,8 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.REMOVER_COMPANY;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.REMOVER_COMPANY_SPECIFIC;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.REMOVER_COMPANY;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.REMOVER_COMPANY_SPECIFIC;
@Controller
public class CompanyRemoverController {
@ -37,7 +37,7 @@ public class CompanyRemoverController {
* @return La page à afficher
*/
@GetMapping(REMOVER_COMPANY)
public ModelAndView showPage(ModelAndView model) {
public ModelAndView getPage(ModelAndView model) {
return new ModelResponseBuilder(model)
.withView(REMOVER_COMPANY)
.addResponseData(ResponseDataType.COMPANIES, companyService.getAll())
@ -78,6 +78,6 @@ public class CompanyRemoverController {
modelResponseBuilder.addResponseCode(ResponseCode.COMPANY_NOT_FOUND, companyID);
}
return showPage(modelResponseBuilder.build());
return getPage(modelResponseBuilder.build());
}
}

View File

@ -15,8 +15,8 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.Optional;
import java.util.stream.Collectors;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.REMOVER_MATERIAL;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.REMOVER_MATERIAL_SPECIFIC;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.REMOVER_MATERIAL;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.REMOVER_MATERIAL_SPECIFIC;
@Controller
public class MaterialRemoverController {
@ -35,7 +35,7 @@ public class MaterialRemoverController {
* @return La page à afficher.
*/
@GetMapping(REMOVER_MATERIAL)
public ModelAndView showPage(ModelAndView model) {
public ModelAndView getPage(ModelAndView model) {
return new ModelResponseBuilder(model)
.withView(REMOVER_MATERIAL)
.addResponseData(ResponseDataType.MATERIALS, materialService.getAll().stream().filter(m -> !m.isMixType()).collect(Collectors.toList()))
@ -65,13 +65,13 @@ public class MaterialRemoverController {
ModelResponseBuilder responseBuilder = new ModelResponseBuilder("");
Optional<Material> optionalMaterial = materialService.getByID(materialID);
if (!optionalMaterial.isPresent()) {
if (optionalMaterial.isEmpty()) {
responseBuilder.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND, materialID);
} else {
Material material = optionalMaterial.get();
if (materialService.deleteIfNotLinked(material)) {
responseBuilder.addResponseData(ResponseDataType.MATERIAL_CODE, material.getMaterialCode());
responseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_MATERIAL, material.getMaterialCode());
if (!materialService.removeSimdut(material)) {
responseBuilder.addResponseCode(ResponseCode.ERROR_SAVING_SIMDUT);
@ -81,6 +81,6 @@ public class MaterialRemoverController {
}
}
return showPage(responseBuilder.build());
return getPage(responseBuilder.build());
}
}

View File

@ -14,8 +14,8 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.REMOVER_MATERIAL_TYPE;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.REMOVER_MATERIAL_TYPE_SPECIFIC;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.REMOVER_MATERIAL_TYPE;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.REMOVER_MATERIAL_TYPE_SPECIFIC;
@Controller
public class MaterialTypeRemoverController {
@ -29,7 +29,7 @@ public class MaterialTypeRemoverController {
}
@GetMapping(REMOVER_MATERIAL_TYPE)
public ModelAndView showPage(ModelAndView model) {
public ModelAndView getPage(ModelAndView model) {
return new ModelResponseBuilder(model)
.withView(REMOVER_MATERIAL_TYPE)
.addResponseData(ResponseDataType.MATERIAL_TYPES, materialTypeService.getAll())
@ -41,18 +41,18 @@ public class MaterialTypeRemoverController {
ModelResponseBuilder responseBuilder = new ModelResponseBuilder("");
Optional<MaterialType> optionalMaterialType = materialTypeService.getByID(materialTypeID);
if (!optionalMaterialType.isPresent()) {
if (optionalMaterialType.isEmpty()) {
responseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_NOT_FOUND, materialTypeID);
} else {
MaterialType materialType = optionalMaterialType.get();
if (materialTypeService.deleteIfNotLinked(materialType)) {
responseBuilder.addResponseData(ResponseDataType.MATERIAL_TYPE_NAME, materialType.getMaterialTypeName());
responseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_MATERIAL_TYPE, materialType.getMaterialTypeName());
} else {
responseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_LINKED, materialType.getMaterialTypeName());
}
}
return showPage(responseBuilder.build());
return getPage(responseBuilder.build());
}
}

View File

@ -13,8 +13,8 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.REMOVER_RECIPE;
import static dev.fyloz.trial.colorrecipesexplorer.web.PagesPaths.REMOVER_RECIPE_SPECIFIC;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.REMOVER_RECIPE;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.REMOVER_RECIPE_SPECIFIC;
@Controller
public class RecipeRemoverController {
@ -62,7 +62,7 @@ public class RecipeRemoverController {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder("");
Optional<Recipe> optionalRecipe = recipeService.getByID(recipeID);
if (!optionalRecipe.isPresent()) {
if (optionalRecipe.isEmpty()) {
return listRecipes(
modelResponseBuilder
.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, recipeID)
@ -77,7 +77,7 @@ public class RecipeRemoverController {
.build());
}
modelResponseBuilder.addResponseData(ResponseDataType.RECIPE_CODE, recipe.getRecipeCode());
modelResponseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_RECIPE, recipe.getRecipeCode());
return listRecipes(modelResponseBuilder.build());
}
}

View File

@ -24,6 +24,8 @@ spring:
web-allow-others: true
server:
port: 9090
http2:
enabled: true
error:
whitelabel:
enabled: false

Binary file not shown.

View File

@ -1,105 +1,118 @@
company.form.companyName=Banner name
menu.explore=Explore
menu.add=Add
menu.edit=Edit
menu.delete=Delete
menu.inventory=Inventory
menu.others=Others
recipe.color=Color
recipe.description=Description
recipe.sample=Sample
recipe.approbationDate=Approbation date
recipe.remark=Remark
recipe.notice=Notice
recipe.steps=Steps
keyword.company=Banner
keyword.material=Material
keyword.recipe=Recipe
keyword.touchUpKitPDF=Touch Up Kit PDF
keyword.quantity=Quantity
keyword.type=Type
keyword.units=Units
keyword.edit=Edit
keyword.use=Use
keyword.save=Save
keyword.back=Back
mix.location=Location
material.code=Code
material.inventoryQuantity=Inventory quantity
material.type=Material type
material.simdutFile=SIMDUT File
units.milliliters=Milliliters
units.liters=Liters
units.gallons=Gallons
inventory.lowQuantity=Low quantity
inventory.hideOthers=Hide other materials
inventory.showOnly=Show only
material.error.anyFound=No materials were found.
app.title=Color recipes explorer
footer.lang=Voir en français
company.add.title=Adding a banner
company.success.created=The banner {0} has been saved.
keyword.see=See
company.error.anyFound=No banners were found.
recipe.warning.notApproved=This recipe is not approved
company.add.title=Add a banner
company.delete.title=Delete banners
company.error.anyFound=No banners were found.
company.form.companyName=Banner name
company.success.created=The banner {0} has been saved.
company.success.deleted=The banner {0} has been deleted.
keyword.delete=Delete
material.add.title=Adding a material
material.success.created=The material {0} has been saved.
material.editing.title=Editing {0}
material.delete.title=Delete materials
material.success.deleted=The material {0} has been deleted.
material.success.saved=The material {0} has been saved.
material.edit.title=Edit materials
material.simdut.choose=Choose material's SIMDUT file
materialType.add.title=Add a material type
materialType.name=Name
materialType.prefix=Prefix
keyword.characters=characters
materialType.usePercents=Use percentages
image.add.title=Add an image
mix.add.title=Adding a banner
mix.add.subtitle=Adding a mix for the recipe {0}
mix.mixType=Mix type
keyword.remove=Remove
mix.edit.title=Editing a mix
mix.edit.subtitle=Editing a mix for the recipe {0} ({1})
password.ask=What is your password?
password.notValid=Your password is not valid
error.serverError=An error occurred while sending data to the server.
recipe.add.title=Add a recipe
recipe.sucess.saved=The recipe {0} has been saved. You can now add ingredients.
keyword.continue=Continue
recipe.editing.title=Editing {0}
recipe.edit.addMix=Add a mix
recipe.edit.addImage=Add an image
recipe.success.edit=The recipe {0} has been saved.
recipe.edit.title=Edit a recipe
recipe.explore.title={0} color
recipe.warning.notApproved.short=Not approved
recipe.image.corrupted=Image deleted or corrupted
recipe.delete.title=Delete recipes
recipe.success.deleted=The recipe {0} has been deleted.
keyword.search=Search
materialType.editor.title=Edit material types
materialType.editing.title=Editing {0}
keyword.id=ID
materialType.success.saved=The material type {0} has been saved.
materialType.delete.title=Delete material types
materialType.success.deleted=The material type {0} has been deleted.
materialType.error.anyFound=Any material type has been found.
keyword.calculation=Calculation
recipe.error.anyFound=No recipes were found.
recipe.exportAllXLS=Export all colors (XLSX)
recipe.xlsVersion=XLSX version
keyword.updates=Updates history
material.simdutFile.notFound=No SIMDUT file found
recipe.warning.changesNotSaved=Changes are not saved
recipe.warning.exportAll=Do you really want to export all the colors? This can take some times and slow down the application.
warning.noResult=Nothing corresponding the the research was found
footer.lang=Voir en français
image.add.title=Add an image
inventory.askUseMix=Do you really want to deduct this mix from the inventory?
inventory.askUseRecipe=Do you really want to deduct this recipe from the inventory?
inventory.hideOthers=Hide other materials
inventory.lowQuantity=Low quantity
inventory.showOnly=Show only
keyword.back=Back
keyword.calculation=Calculation
keyword.cancel=Cancel
keyword.characters=characters
keyword.company=Banner
keyword.continue=Continue
keyword.delete=Delete
keyword.edit=Edit
keyword.id=ID
keyword.images=Images
warning.askChangePage=Are you sure you want to continue? Unsaved changes will be lost.
keyword.material=Material
keyword.pdf=PDF
keyword.print=Print
keyword.ptouch=P-touch
keyword.ptouchTemplate=P-touch templates
keyword.quantity=Quantity
keyword.recipe=Recipe
keyword.remove=Remove
keyword.save=Save
keyword.search=Search
keyword.see=See
keyword.type=Type
keyword.units=Units
keyword.updates=Updates history
keyword.use=Use
material.add.title=Add a material
material.code=Code
material.delete.title=Delete materials
material.edit.title=Edit materials
material.editing.title=Editing {0}
material.error.anyFound=No materials were found.
material.inventoryQuantity=Inventory quantity
material.simdut.choose=Choose material's SIMDUT file
material.simdut.error.notFound=No SIMDUT file found
material.simdut=SIMDUT File
material.success.created=The material {0} has been saved.
material.success.deleted=The material {0} has been deleted.
material.success.saved=The material {0} has been saved.
material.type=Material type
materialType.add.title=Add a material type
materialType.delete.title=Delete material types
materialType.editing.title=Editing {0}
materialType.editor.title=Edit material types
materialType.error.anyFound=Any material type has been found.
materialType.name=Name
materialType.prefix=Prefix
materialType.success.deleted=The material type {0} has been deleted.
materialType.success.saved=The material type {0} has been saved.
materialType.usePercents=Use percentages
menu.add=Add
menu.delete=Delete
menu.edit=Edit
menu.explore=Explore
menu.inventory=Inventory
menu.others=Others
mix.add.subtitle=Add a mix for the recipe {0}
mix.add.title=Add a banner
mix.edit.subtitle=Editing a mix for the recipe {0} ({1})
mix.edit.title=Editing a mix
mix.location=Location
mix.mixType=Mix type
password.ask=What is your password?
password.notValid=Your password is not valid
recipe.add.title=Add a recipe
recipe.approbationDate=Approbation date
recipe.color=Color
recipe.delete.title=Delete recipes
recipe.description=Description
recipe.edit.addImage=Add an image
recipe.edit.addMix=Add a mix
recipe.edit.title=Edit a recipe
recipe.editing.title=Editing {0}
recipe.error.anyFound=No recipes were found.
recipe.explore.title={0} color
recipe.exportAllXLS=Export all colors (XLSX)
recipe.image.corrupted=Image deleted or corrupted
recipe.notice=Notice
recipe.print.confirm=Are you sure you want to print this mix?
recipe.print.error.bpacNotInstalled=BPac is not installed
recipe.print.error.noBase=There is no base in this mix
recipe.print.error=An error occurred while printing
recipe.print.printing=Printing in progress. This may take a few seconds.
recipe.print.status.error=The b-Pac extension is not installed
recipe.print.status.ok=The b-Pac extension is installed
recipe.remark=Remark
recipe.sample=Sample
recipe.steps=Steps
recipe.success.deleted=The recipe {0} has been deleted.
recipe.success.edit=The recipe {0} has been saved.
recipe.sucess.saved=The recipe {0} has been saved. You can now add ingredients.
recipe.touchup.title=Touch-up kit labels
recipe.touchup=Touch-up kit labels
recipe.warning.changesNotSaved=Changes are not saved
recipe.warning.exportAll=Do you really want to export all the colors? This can take some times and slow down the application.
recipe.warning.notApproved.short=Not approved
recipe.warning.notApproved=This recipe is not approved
recipe.xlsVersion=XLSX version
units.gallons=Gallons
units.liters=Liters
units.milliliters=Milliliters
warning.askChangePage=Are you sure you want to continue? Unsaved changes will be lost.
warning.noResult=Search returned no results
keyword.jobNumber=Job number

View File

@ -1,106 +1,118 @@
menu.explore=Explorer
menu.add=Ajouter
menu.edit=Modifier
menu.delete=Supprimer
menu.inventory=Inventaire
menu.others=Autres
recipe.color=Couleur
recipe.description=Description
recipe.sample=Échantillon
recipe.approbationDate=Date d'approbation
recipe.remark=Remarque
recipe.notice=Note
recipe.steps=Étapes
keyword.company=Bannière
keyword.material=Produit
keyword.recipe=Recette
keyword.touchUpKitPDF=PDF Kits de retouche
keyword.quantity=Quantité
keyword.type=Type
keyword.units=Unités
keyword.edit=Modifier
keyword.use=Utiliser
keyword.save=Enregistrer
keyword.back=Retour
keyword.delete=Supprimer
mix.location=Position
material.code=Code
material.inventoryQuantity=Quantité en inventaire
material.type=Type de produit
material.simdutFile=Fichier SIMDUT
units.milliliters=Millilitres
units.liters=Litres
units.gallons=Gallons
inventory.lowQuantity=Quantité faible
inventory.hideOthers=Cacher les autres produits
inventory.showOnly=Voir seulement
material.error.anyFound=Aucun produit n'a été trouvé.
app.title=Explorateur de recettes de couleur
footer.lang=See in english
company.add.title=Ajout d'une bannière
company.success.created=La bannière {0} à été enregistrée.
company.form.companyName=Nom de la bannière
keyword.see=Voir
company.error.anyFound=Aucune bannière n'a été trouvée.
recipe.warning.notApproved=Cette recette n'est pas approuvée
company.delete.title=Supprimer des bannières
company.error.anyFound=Aucune bannière n'a été trouvée.
company.form.companyName=Nom de la bannière
company.success.created=La bannière {0} à été enregistrée.
company.success.deleted=La bannière {0} a bien été supprimée.
material.add.title=Ajout d'un produit
material.success.created=Le produit {0} a été enregistré.
material.editing.title=Modification de {0}
material.delete.title=Supprimer des produits
material.success.deleted=Le produit {0} a bien été supprimée.
material.success.saved=Le produit {0} a bien été sauvegardé.
material.edit.title=Modifer des produits
material.simdut.choose=Choisissez le fichier SIMDUT du produit
materialType.add.title=Ajout d'un type de produit
materialType.name=Nom
materialType.prefix=Préfix
keyword.characters=caractères
materialType.usePercents=Utiliser les pourcentages
image.add.title=Ajout une image
mix.add.title=Ajout d'un mélange
mix.add.subtitle=Ajout d''un mélange pour la recette {0}
mix.mixType=Type de mélange
keyword.remove=Retirer
mix.edit.title=Modifier un mélange
mix.edit.subtitle=Modifier un mélange pour la recette {0} ({1})
password.ask=Quel est votre mot de passe?
password.notValid=Votre mot de passe n'est pas valide
error.serverError=Une erreur est survenue lors de l'envoie des informations vers le serveur.
recipe.add.title=Ajout d'une recette
recipe.sucess.saved=La recette {0} à été enregistrée. Vous pouvez maintenant ajouter les ingrédients.
keyword.continue=Continuer
recipe.editing.title=Modification de {0}
recipe.edit.addMix=Ajouter un mélange
recipe.edit.addImage=Ajouter une image
recipe.success.edit=La recette {0} a bien été sauvegardée.
recipe.edit.title=Modifier une recette
recipe.explore.title=Couleur {0}
recipe.warning.notApproved.short=Non approuvée
recipe.image.corrupted=Image supprimée ou corrompue
recipe.delete.title=Supprimer des recettes
recipe.success.deleted=La recette {0} a bien été supprimée.
keyword.search=Rechercher
materialType.editor.title=Modifier un type de produit
materialType.editing.title=Modification de {0}
keyword.id=Identifiant
materialType.success.saved=Le type de produit {0} a bien été sauvegardé.
materialType.delete.title=Supprimer des types de produit
materialType.success.deleted=Le type de produit {0} a bien été supprimé.
materialType.error.anyFound=Aucun type de produit n'a été trouvé.
keyword.calculation=Calcul
recipe.error.anyFound=Aucune recette n'a été trouvée.
recipe.exportAllXLS=Exporter toutes les couleurs (XLSX)
recipe.xlsVersion=Version XLSX
keyword.updates=Historique des mises à jour
material.simdutFile.notFound=Aucun fichier SIMDUT trouvé
recipe.warning.changesNotSaved=Des modifications ne sont pas sauvegardées
recipe.warning.exportAll=Voulez-vous vraiment exporter toutes les couleurs? Cela peut prendre un certain temps et ralentir l'application.
warning.noResult=Rien correspondant à la recherche n'a été trouvé
footer.lang=See in english
image.add.title=Ajout une image
inventory.askUseMix=Êtes-vous certain de vouloir déduire ce mélange de l'inventaire?
inventory.askUseRecipe=Êtes-vous certain de vouloir déduire cette recette de l'inventaire?
inventory.hideOthers=Cacher les autres produits
inventory.lowQuantity=Quantité faible
inventory.showOnly=Voir seulement
keyword.back=Retour
keyword.calculation=Calcul
keyword.cancel=Annuler
keyword.characters=caractères
keyword.company=Bannière
keyword.continue=Continuer
keyword.delete=Supprimer
keyword.edit=Modifier
keyword.id=Identifiant
keyword.images=Images
warning.askChangePage=Êtes-vous sûr de vouloir continuer? Les modifications non enregistrées seront perdues.
keyword.material=Produit
keyword.pdf=PDF
keyword.print=Imprimer
keyword.ptouch=P-touch
keyword.ptouchTemplate=Modèles P-touch
keyword.quantity=Quantité
keyword.recipe=Recette
keyword.remove=Retirer
keyword.save=Enregistrer
keyword.search=Rechercher
keyword.see=Voir
keyword.type=Type
keyword.units=Unités
keyword.updates=Historique des mises à jour
keyword.use=Utiliser
material.add.title=Ajout d'un produit
material.code=Code
material.delete.title=Supprimer des produits
material.edit.title=Modifer des produits
material.editing.title=Modification de {0}
material.error.anyFound=Aucun produit n'a été trouvé.
material.inventoryQuantity=Quantité en inventaire
material.simdut.choose=Choisissez le fichier SIMDUT du produit
material.simdut.error.notFound=Aucun fichier SIMDUT trouvé
material.simdut=Fichier SIMDUT
material.success.created=Le produit {0} a été enregistré.
material.success.deleted=Le produit {0} a bien été supprimée.
material.success.saved=Le produit {0} a bien été sauvegardé.
material.type=Type de produit
materialType.add.title=Ajout d'un type de produit
materialType.delete.title=Supprimer des types de produit
materialType.editing.title=Modification de {0}
materialType.editor.title=Modifier un type de produit
materialType.error.anyFound=Aucun type de produit n'a été trouvé.
materialType.name=Nom
materialType.prefix=Préfix
materialType.success.deleted=Le type de produit {0} a bien été supprimé.
materialType.success.saved=Le type de produit {0} a bien été sauvegardé.
materialType.usePercents=Utiliser les pourcentages
menu.add=Ajouter
menu.delete=Supprimer
menu.edit=Modifier
menu.explore=Explorer
menu.inventory=Inventaire
menu.others=Autres
mix.add.subtitle=Ajout d''un mélange pour la recette {0}
mix.add.title=Ajout d'un mélange
mix.edit.subtitle=Modifier un mélange pour la recette {0} ({1})
mix.edit.title=Modifier un mélange
mix.location=Position
mix.mixType=Type de mélange
password.ask=Quel est votre mot de passe?
password.notValid=Votre mot de passe n'est pas valide
recipe.add.title=Ajout d'une recette
recipe.approbationDate=Date d'approbation
recipe.color=Couleur
recipe.delete.title=Supprimer des recettes
recipe.description=Description
recipe.edit.addImage=Ajouter une image
recipe.edit.addMix=Ajouter un mélange
recipe.edit.title=Modifier une recette
recipe.editing.title=Modification de {0}
recipe.error.anyFound=Aucune recette n'a été trouvée.
recipe.explore.title=Couleur {0}
recipe.exportAllXLS=Exporter toutes les couleurs (XLSX)
recipe.image.corrupted=Image supprimée ou corrompue
recipe.notice=Note
recipe.print.confirm=Voulez-vous vraiment imprimer ce mélange?
recipe.print.error.bpacNotInstalled=BPac n'est pas installé
recipe.print.error.noBase=Il n'y a pas de base dans ce mélange
recipe.print.error=Une erreur est survenue pendant l'impression
recipe.print.printing=Impression en cours. Cette opération peut prendre quelques secondes.
recipe.print.status.error=L'extension b-Pac n'est pas installée
recipe.print.status.ok=L'extension b-Pac est installée
recipe.remark=Remarque
recipe.sample=Échantillon
recipe.steps=Étapes
recipe.success.deleted=La recette {0} a bien été supprimée.
recipe.success.edit=La recette {0} a bien été sauvegardée.
recipe.sucess.saved=La recette {0} à été enregistrée. Vous pouvez maintenant ajouter les ingrédients.
recipe.touchup.title=Étiquettes de kit de retouche
recipe.touchup=Étiquettes de kit de retouche
recipe.warning.changesNotSaved=Des modifications ne sont pas sauvegardées
recipe.warning.exportAll=Voulez-vous vraiment exporter toutes les couleurs? Cela peut prendre un certain temps et ralentir l'application.
recipe.warning.notApproved.short=Non approuvée
recipe.warning.notApproved=Cette recette n'est pas approuvée
recipe.xlsVersion=Version XLSX
units.gallons=Gallons
units.liters=Litres
units.milliliters=Millilitres
warning.askChangePage=Êtes-vous sûr de vouloir continuer? Les modifications non enregistrées seront perdues.
warning.noResult=La recherche n'a donné aucun résultat
keyword.jobNumber=Numéro de job

View File

@ -1,22 +1,22 @@
response.1=The quantities of each material used have been deducted from the inventory
response.1=The quantities of used materials have been deducted from the inventory
response.10=There is already a material with the code {0}
response.11=There is already a material type named {0}
response.12=Any banner with the ID {0} has been found
response.12=No banner with the ID {0} has been found
response.13=There is already a banner named {0}
response.14=The material {0} is linked to one or more recipes, edit them first
response.15=The banner {0} is linked to one or more recipes, delete them first
response.16=The mix with the ID {0} is not linked to the recipe with the ID {1}
response.17=There is not enough {0} in inventory for this recipe
response.18=This recipe already contains a mix of the type {0}
response.19=Any material type with the ID {0} has been found
response.19=No material type with the name {0} has been found
response.2=The recipe's informations have been saved
response.3=An error has occurred while saving
response.4=An error has occurred while saving the image
response.5=An error has occurred while saving the SIMDUT file
response.6=Your password is not valid
response.7=Any recipe with the ID {0} has been found
response.8=Any mix with the ID {0} has been found
response.9=Any material with the ID {0} has been found
response.7=No recipe with the ID {0} has been found
response.8=No mix with the ID {0} has been found
response.9=No material with the ID {0} has been found
response.20=There is already a material type with the prefix {0}
response.21=The material type {0} is linked to one or more materials, delete them first.
response.101=The requested page could not be found
@ -28,3 +28,10 @@ response.24=This file need to be an image
response.25=The recipe was not found
response.26=The material with the code {0} was not found
response.27=The banner {0} has been delete
response.28=The material {0} has been saved
response.29=The material type {0} has been saved
response.30=The recipe for the color {0} has been saved
response.31=The material {0} has been deleted
response.32=The banner {0} has been saved
response.33=The recipe {0} has been deleted
response.34=The material type {0} has been deleted

View File

@ -1,4 +1,4 @@
response.1=Les quantités de chaque produits utilisés ont été déduites de l'inventaire
response.1=Les quantités des produits utilisés ont été déduites de l'inventaire
response.10=Il y a déjà un produit ayant le code {0}
response.11=Il y a déjà un type de produit s''appellant {0}
response.12=Aucune bannière ayant l''identifiant {0} n''a été trouvée
@ -8,7 +8,7 @@ response.15=La bannière {0} est liée à une ou plusieurs recettes, veuillez le
response.16=Le mélange ayant l''identifiant {0} n''est pas associé à la recette ayant l''identifiant {1}
response.17=Il n''y a pas assez de {0} en inventaire pour cette recette
response.18=Cette recette contient déjà un mélange du type {0}
response.19=Aucun type de produit ayant l''identifiant {0} n''a été trouvée
response.19=Aucun type de produit ayant le nom {0} n''a été trouvée
response.2=Les informations de la recette ont été sauvegardées
response.3=Une erreur est survenue lors de l''enregistrement
response.4=Une erreur est survenue lors de l'enregistrement de l'image
@ -28,3 +28,10 @@ response.24=Ce fichier doit être une image
response.25=La recette n'a pas été trouvée
response.26=Le produit ayant le code {0} n''a pas été trouvé
response.27=La bannière {0} a bien été supprimée
response.28=Le produit {0} a bien été sauvegardé
response.29=Le type de produit {0} a bien été sauvegardé
response.30=La recette pour la couleur {0} a bien été sauvegardée
response.31=Le produit {0} a bien été supprimée
response.32=La bannière {0} a bien été sauvegardée
response.33=La recette {0} a bien été supprimée
response.34=Le type de produit {0} a bien été supprimé

Binary file not shown.

View File

@ -4,6 +4,10 @@ body {
overflow-x: hidden;
}
* {
transition: all .1s;
}
h1 {
text-decoration: underline;
}
@ -73,18 +77,6 @@ textarea {
padding: 10px;
}
/*.error {*/
/* color: red;*/
/*}*/
/*.success {*/
/* color: green;*/
/*}*/
/*.error, .success {*/
/* font-weight: bold;*/
/*}*/
table {
border-collapse: collapse;
}
@ -129,49 +121,82 @@ table:not(.noStyle) tr:nth-child(odd) {
width: 150px;
}
.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;
display: none;
position: fixed;
top: -20px;
left: 50%;
transform: translateX(-50%);
}
.messageBox .messageBoxContainer {
display: inline-block;
height: 24px;
padding: 16px;
text-align: left;
}
.messageBox .messageBoxInnerBox {
display: inline-block;
height: 24px;
.messageBox .messageBoxMessageContainer {
display: flex;
flex-direction: row;
position: relative;
vertical-align: center;
align-items: center;
text-align: center;
}
.messageBox img {
float: left;
width: 24px;
height: 24px;
}
.messageBox p {
display: inline-block;
margin: 0 0 0 16px;
line-height: 24px;
font-weight: bold;
margin: 0 0 0 20px;
width: 100%;
}
#errorBox {
background-color: #ef9a9a;
color: #e53935;
}
#successBox {
background-color: #a5d6a7;
color: #4caf50;
}
#warningBox {
background-color: #fff59d;
color: #fdd835;
}
#confirmBox, #promptBox {
background-color: #90caf9;
color: #1e88e5;
height: 75px;
z-index: 60;
}
#promptBox {
height: 110px;
}
.confirmButtonsContainer button {
margin: 0 60px;
}
.confirmButtonsContainer {
display: flex;
flex-direction: row;
margin: 5px 0;
justify-content: center;
}
.confirmInputContainer input {
width: 90%;
background-color: white;
padding: 2px 0;
margin: 10px 5% 2px;
text-align: left !important;
}
.subtitle {
@ -205,3 +230,14 @@ table:not(.noStyle) tr:nth-child(odd) {
.submitButton {
float: right;
}
#filter {
display: none;
background-color: black;
width: 100%;
height: 100%;
position: fixed;
top: 0;
opacity: 0;
z-index: 50;
}

View File

@ -4,24 +4,46 @@ header, footer {
text-align: center;
width: 100%;
color: white;
position: fixed;
z-index: 99;
}
header {
top: 0;
display: flex;
background-color: black;
}
header .spacer {
width: 100%;
}
header a:hover, .dropdown:hover .dropbtn {
background-color: #0d0d0d;
}
header .header-right {
display: flex;
flex-direction: row;
}
header #printStatusIcon {
margin-right: 20px;
}
nav {
display: flex;
flex-direction: row;
}
footer {
position: fixed;
height: 5%;
bottom: 0;
padding-top: 10px;
}
nav img {
margin-left: auto;
}
nav {
display: flex;
flex-wrap: wrap;
overflow: visible;
background-color: inherit;
section {
margin-top: 100px;
}
nav a {
@ -50,10 +72,6 @@ nav a {
margin: 0;
}
nav a:hover, .dropdown:hover .dropbtn {
background-color: #0d0d0d;
}
.dropdown-content {
display: none;
position: absolute;
@ -81,7 +99,7 @@ nav a:hover, .dropdown:hover .dropbtn {
}
@media only screen and (max-width: 702px) {
nav img {
header img {
display: none;
}
}

View File

@ -0,0 +1,7 @@
<?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="#1e88e5"
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: 395 B

View File

@ -2,5 +2,5 @@
<!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"/>
d="M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 485 B

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="#4caf50"
d="M18 7H6V3H18V7M6 21V17H2V11C2 9.34 3.34 8 5 8H19C20.66 8 22 9.34 22 11V13.81C21.12 13.3 20.1 13 19 13C17.77 13 16.64 13.37 15.69 14H8V19H13C13 19.7 13.13 20.37 13.35 21H6M18 11C18 11.55 18.45 12 19 12S20 11.55 20 11 19.55 10 19 10 18 10.45 18 11M23.5 17L22 15.5L18.5 19L16.5 17L15 18.5L18.5 22L23.5 17"/>
</svg>

After

Width:  |  Height:  |  Size: 586 B

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="M14,4V8H6V4H14M15,13A1,1 0 0,0 16,12A1,1 0 0,0 15,11A1,1 0 0,0 14,12A1,1 0 0,0 15,13M13,19V15H7V19H13M15,9A3,3 0 0,1 18,12V17H15V21H5V17H2V12A3,3 0 0,1 5,9H15M22,7V12H20V7H22M22,14V16H20V14H22Z"/>
</svg>

After

Width:  |  Height:  |  Size: 478 B

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,19 @@
const body = document.querySelector("body");
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");
const body = $("body");
const errorMsg = $("#errorBox");
const warningMsg = $("#warningBox");
const successMsg = $("#successBox");
const confirmMsg = $("#confirmBox");
const promptMsg = $("#promptBox");
const messageBoxesAnimationTime = 200;
const messageBoxesFunctionsDelay = 300;
$(() => {
$('.materialCode').each(function () {
const row = $(this);
const materialID = row.data("materialid");
// SIMDUT
axios.post(`/simdut/${materialID}`)
.catch(function (err) {
if (err.response.status === 404) {
@ -19,127 +22,203 @@ $(() => {
}
});
});
$(".messageBox").each(function () {
checkMessageBoxesDisplay($(this)[0])
});
});
window.addEventListener("load", () => {
document.querySelectorAll(".returnButton").forEach((e) => {
e.addEventListener("click", () => {
document.location.href = referer;
});
$(".toSave").on({
keyup: function () {
showMessage(warningMsg, changesNotSavedText, false);
}
});
$(".returnButton").on({click: () => window.location.href = referer});
$(".materialCode").on({
click: function () {
window.open(`/simdut/${$(this).data("materialid")}`, "_blank");
}
});
$("img").on({
click: function () {
if ($(this).attr("id") === "logo") return;
window.open($(this).attr("src"), "_blank");
}
});
$('input[type="text"], input[type="number"], input[type="date"], textarea').each(function () {
$(this).addClass("rawInput");
});
$(".rawInput:not(.noPlaceholder)").each(function () {
$(this).attr({placeholder: "N/A"});
});
document.querySelectorAll(".materialCode").forEach((e) => {
const materialID = e.getAttribute("data-materialID");
e.addEventListener("click", () => {
window.open("/simdut/" + materialID, "_blank");
});
// Formulaires protégés
$(".requireAuth").on({
submit: function () {
return checkPassword(this);
}
});
document.querySelectorAll("img").forEach((e) => {
if (e.id !== "logo") {
e.addEventListener("click", () => {
window.open(e.src, "_blank");
const removerForm = $(".requireAuth-remover");
removerForm.find(".remover").on({
click: function () {
console.log($(this));
removerForm.attr({
action: removerForm.attr("action") + $(this).data("entityid")
});
checkPassword(removerForm);
}
});
document.querySelectorAll(".requireAuth").forEach((e) => {
e.onsubmit = () => {
return checkPassword(e);
};
// Favicon
$("head").append($("<link/>").attr({
rel: "icon",
href: "/favicon.png"
}));
// Imprimante
let src = `${baseUrl}/icons/printerError.svg`;
let title = printErrorTitle;
if ($(".bpac-extension-installed").length) {
src = `${baseUrl}/icons/printer.svg`;
title = printOkTitle;
}
$("#printStatusIcon").attr({
src: src,
title: title
});
document.querySelectorAll(".requireAuth-remover").forEach((e) => {
e.querySelectorAll(".remover").forEach(elem => {
elem.addEventListener("click", () => {
e.action += elem.getAttribute("data-entityID");
checkPassword(e);
});
});
});
document.querySelectorAll(".companyTabTitle").forEach(e => {
e.addEventListener("click", () => {
const companyName = e.getAttribute("data-companyName");
const table = document.getElementById("recipes_" + companyName);
if (table.style.display === "none") {
table.style.display = "table";
} else {
table.style.display = "none";
}
});
});
document.querySelectorAll('input[type="text"], input[type="number"], input[type="date"], textarea').forEach(e => e.classList.add("rawInput"))
document.querySelectorAll(".rawInput").forEach(e => e.placeholder = "N/A");
window.addEventListener("keyup", e => {
if (e.target) {
if (e.target.classList.contains("toSave")) {
warningMsgBoxText.innerText = changesNotSavedText;
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(exportAllWarningText);
function confirmDatabaseExport() {
showConfirm(exportAllWarningText, false, () => {
window.location.href = "./recipe/xls";
});
}
function checkPassword(form, callback) {
hideElement(errorMsgBox);
function checkPassword(form, callback = () => {
}) {
let shouldContinue = false;
const password = prompt(askPasswordText);
if (!password) return false;
promptMsg.find("input").attr({type: "password"});
showConfirm(askPasswordText, true, async pwd => {
let data = {};
data.password = pwd;
let data = {};
data.password = password;
await axios.post("/password/valid", data)
.then(r => {
if (r.data === true) {
if (form != null) form.submit();
callback();
shouldContinue = true;
} else {
showMessage(errorMsg, invalidPasswordText);
}
})
.catch(e => {
showMessage(errorMsg, generalErrorText);
console.log(e);
});
axios.post("/password/valid", data)
.then(r => {
if (r.data === true) {
if (form != null) form.submit();
if (callback != null) callback();
return true;
} else {
errorMsgBoxText.innerText = invalidPasswordText;
showElement(errorMsgBox);
}
})
.catch(e => {
errorMsgBoxText.innerText = generalErrorText;
showElement(errorMsgBox);
console.log(e);
});
promptMsg.find("input").attr({type: "text"});
}, () => promptMsg.find("input").attr({type: "text"}));
return false;
return shouldContinue;
}
const messageBoxesY = -20;
const headerY = 70;
function checkMessageBoxesDisplay(element) {
if (!element.querySelector("p").textContent) hideElement(element);
else showElement(element);
const node = $(element);
if (!node.find("p").text()) hideMessage(element);
else showMessage(element);
}
function hideElement(element) {
element.style.display = "none";
function isMessageVisible(type) {
return $(type).is(":visible");
}
function showElement(element) {
element.style.display = "";
function showMessage(type, value, autoHide = true, hideTimeout = 2000) {
const node = $(type);
const message = node.find("p");
// Cache les autres boîtes de message sauf si une boîte de confirmation est active
if (!$("#filter").is(":visible")) {
$(`.messageBox:not(#${$(type).attr("id")}`).each(function () {
hideMessage(this);
});
} else {
return;
}
if (!isMessageVisible(type)) {
message.text(value);
node.show();
node.animate({top: `${headerY}px`}, messageBoxesAnimationTime);
if (autoHide) {
setTimeout(() => {
hideMessage(node);
}, hideTimeout);
}
} else {
if (message.text() !== value) hideMessage(type, () => showMessage(type, value, autoHide, hideTimeout));
}
}
function hideMessage(type, callback = () => {
}) {
const node = $(type);
if (isMessageVisible(type)) {
node.animate({top: `${messageBoxesY}px`}, messageBoxesAnimationTime, "swing", () => {
node.hide();
callback();
});
}
}
function showConfirm(value, prompt = false, continueCallback = () => {
}, cancelCallback = () => {
}) {
const filter = $("#filter");
const node = prompt ? $(promptMsg) : $(confirmMsg);
if (prompt) node.find("#confirmInput").val("");
showMessage(node, value, false);
filter.show();
filter.animate({opacity: 0.5}, messageBoxesAnimationTime);
node.find(".confirmContinue").one({
click: function () {
hideConfirm(node);
setTimeout(function () {
if (!prompt) continueCallback();
else continueCallback(node.find("#confirmInput").val());
}, messageBoxesFunctionsDelay);
}
});
node.find(".confirmCancel").one({
click: function () {
hideConfirm(node);
setTimeout(function () {
cancelCallback();
}, messageBoxesFunctionsDelay);
}
});
}
function hideConfirm(node) {
hideMessage(node);
const filter = $("#filter");
filter.animate({opacity: 0}, messageBoxesAnimationTime + 100, "swing", () => {
filter.hide();
// Retire les callbacks des anciennes confirmations
$(confirmMsg).find(".confirmContinue").unbind();
$(confirmMsg).find(".confirmCancel").unbind();
});
}
const lTomL = 1000;
@ -151,15 +230,14 @@ let currentUnit = "mL";
function changeUnits(unitSelect, quantitiesSelector, unitsDisplay) {
currentUnit = unitSelect.value;
document.querySelectorAll(unitsDisplay).forEach(e => {
e.innerText = currentUnit;
$(unitsDisplay).each(function () {
$(this).text(currentUnit);
// Modifie la quantitée
const quantityElem = e.parentElement.parentElement.querySelector(quantitiesSelector);
const quantityElem = $(this).parents(".materialRow").find(quantitiesSelector);
console.log(quantityElem);
if (quantityElem) {
const originalQuantity = parseInt(quantityElem.dataset.quantityml);
quantityElem.innerText = convertMlToUnit(originalQuantity);
const originalQuantity = parseInt(quantityElem.data("quantityml"));
quantityElem.text(convertMlToUnit(originalQuantity));
}
});
}
@ -187,3 +265,7 @@ function percentageOf(percentage, number) {
function searchIn(searchString, str) {
return str.toUpperCase().indexOf(searchString.toUpperCase()) > -1;
}
function formatMessage(message) {
return message.replace(/&#39;/g, "'");
}

View File

@ -8,19 +8,22 @@ let materialSelectorHtml;
let recipeID;
$(() => {
recipeID = document.querySelector("#recipeID").value;
recipeID = $("#recipeID").val();
axios.get(`/mix/selector/${recipeID}/-1`)
const mixIDInput = $("#mixID");
const mixID = mixIDInput ? mixIDInput.val() : -1;
axios.get(`/mix/selector/${recipeID}/${mixID}`)
.then(r => {
materialSelectorHtml = r.data;
init();
})
.catch(e => {
console.log(e.status);
errorMsgBoxText.innerText = generalErrorText;
showElement(errorMsgBox);
showMessage(errorMsg, generalErrorText);
console.log(e);
});
onRowPositionChanged();
});
$(document).on("click", function () {
@ -32,165 +35,204 @@ $(document).on("click", ".materialSelector", function (event) {
showMaterialList($(this)[0]);
});
// $(document).on("click", ".materialList p", function () {
// $(this).parents(".materialSelector").find("input").val($(this).data("materialcode"));
// });
$(document).on("click", ".removeMaterial", function () {
const id = $(this).data("removerow");
if ($(".materialListRow").length > 1) $(`#${id}`).remove();
});
$(document).on("click", ".upRow", function () {
const row = $(this).parent().parent();
const row = $(this).parents(".materialListRow");
row.insertBefore(row.prev());
onRowPositionChanged();
});
$(document).on("click", ".downRow", function () {
const row = $(this).parent().parent();
row.insertBefore(row.next());
const row = $(this).parents(".materialListRow");
row.insertAfter(row.next());
onRowPositionChanged()
});
document.querySelector(".mixMaterialListTitle button").addEventListener("click", () => {
addMaterial(null, null);
$(".mixMaterialListTitle button").on({
click: function () {
addMaterial(null, null);
}
});
function onRowPositionChanged() {
$(".materialListRow").each(function () {
const node = $(this);
const upRow = node.find(".upRow");
const downRow = node.find(".downRow");
let upRowHidden = false;
let downRowHidden = false;
if (node.is(":first-child")) {
upRowHidden = true;
} else if (node.is(":last-child")) {
downRowHidden = true;
}
if (upRowHidden) upRow.css({opacity: 0});
else upRow.css({opacity: 1});
if (downRowHidden) downRow.css({opacity: 0});
else downRow.css({opacity: 1});
});
}
function addMaterial(materialCode, quantity) {
const row = addRow(materialNbr);
const positionColumn = addColumn("positionButtons", row);
const materialColumn = addColumn("material", row);
const quantityColumn = addColumn("quantity", row);
const unitsColumn = addColumn("units", row);
const removeButtonColumn = addColumn("removeButton", row);
const positionColumn = $(addColumn("positionButtons", row));
const materialColumn = $(addColumn("material", row));
const quantityColumn = $(addColumn("quantity", row));
const unitsColumn = $(addColumn("units", row));
const removeButtonColumn = $(addColumn("removeButton", row));
addPositionButtons(positionColumn);
addQuantityInput(quantity, quantityColumn);
addUnits(unitsColumn);
addRemoveButton(materialNbr, removeButtonColumn);
materialColumn.innerHTML = materialSelectorHtml;
const materialSelector = materialColumn.querySelector("input");
materialColumn.html(materialSelectorHtml);
const materialSelector = materialColumn.find("input");
if (materialCode) {
const material = materialColumn.querySelector(`.materialList p[data-materialcode="${materialCode}"]`);
const material = materialColumn.find(`.materialList p[data-materialcode="${materialCode}"]`);
if (material) {
materialSelector.value = material.dataset.materialcode;
materialSelector.dataset.usepercentages = material.dataset.usepercentages;
materialSelector.val(material.data("materialcode"));
materialSelector.data({
usepercentages: material.data("usepercentages")
});
}
}
row.appendChild(materialColumn);
row.appendChild(quantityColumn);
row.appendChild(unitsColumn);
row.appendChild(removeButtonColumn);
document.querySelector(".mixMaterialList").appendChild(row);
$(".mixMaterialList").append(row);
checkUnits(materialSelector, row);
onRowPositionChanged();
materialNbr++;
}
function addRow(index) {
const row = document.createElement("div");
row.classList.add("materialListRow");
row.id = `material_${index}`;
return row;
return $("<div></div>")
.attr({
class: "materialListRow",
id: `material_${index}`
});
}
function addColumn(type, parent) {
const column = document.createElement("div");
column.classList.add(type);
const column = $("<div></div>")
.attr({class: type});
parent.appendChild(column);
$(parent).append(column);
return column;
}
function addUnits(parent) {
const units = document.createElement("p");
units.classList.add("quantityUnits");
units.textContent = "mL";
const units = $("<p></p>")
.attr({class: "quantityUnits"})
.text("mL");
parent.appendChild(units);
$(parent).append(units);
return units;
}
function addRemoveButton(index, parent) {
const button = document.createElement("button");
button.type = "button";
button.textContent = removeText;
button.dataset.removerow = `material_${index}`;
button.classList.add("removeMaterial");
const button = $("<button></button>")
.attr({
type: "button",
class: "removeMaterial"
})
.data({removerow: `material_${index}`})
.text(removeText);
parent.appendChild(button);
$(parent).append(button);
return button;
}
function addQuantityInput(quantity, parent) {
let input = document.createElement("input");
if (quantity === undefined || quantity === null) quantity = 1;
input.type = "number";
input.name = "quantities";
input.value = quantity;
input.step = 0.001;
input.min = 0.001;
input.required = true;
const input = $("<input/>")
.attr({
type: "number",
name: "quantities",
value: quantity,
step: 0.001,
min: 0.001,
required: true
});
parent.appendChild(input);
$(parent).append(input);
return input;
}
function addPositionButtons(parent) {
const up = document.createElement("button");
up.classList.add("upRow");
up.type = "button";
up.textContent = "↑";
parent.appendChild(up);
$(parent).append($("<button></button>")
.attr({
class: "upRow",
type: "button"
})
.text("↑"));
const down = document.createElement("button");
down.classList.add("downRow");
down.type = "button";
down.textContent = "↓";
parent.appendChild(down);
$(parent).append($("<button></button>")
.attr({
class: "downRow",
type: "button"
})
.text("↓"));
}
function showMaterialList(input) {
hideMaterialList();
input.parentElement.querySelector(".materialSelector .materialList").classList.add("show");
$(input).parent().find(".materialSelector .materialList").addClass("show");
}
function hideMaterialList() {
const list = document.querySelector(".materialSelector .materialList.show");
if (list != null) list.classList.remove("show");
const list = $(".materialSelector .materialList.show");
if (list) list.removeClass("show");
}
function selectMaterial(material) {
const materialName = material.dataset.materialcode;
const input = material.parentElement.parentElement.querySelector("input");
const materialName = $(material).data("materialcode");
const input = $(material).parents(".materialSelector").find("input");
input.value = materialName;
input.val(materialName);
hideMaterialList();
checkUnits(material, input.parentElement.parentElement.parentElement)
checkUnits(material, input.parents(".materialListRow"));
}
function searchMaterial(input) {
let filter, filterUpper, materials;
const filter = input.val();
const materials = input.parent().find(".materialList p");
filter = input.value;
materials = input.parentElement.querySelectorAll(".materialList p");
materials.forEach(e => {
if (searchIn(filter, e.textContent) || searchIn(filter, e.dataset.materialtype)) e.style.display = "";
else e.style.display = "none";
materials.each(function () {
const node = $(this);
if (searchIn(filter, node.text()) || searchIn(filter, node.data("materialtype"))) node.hide();
else node.show();
});
const found = input.parentElement.querySelector(`.materialList p[data-materialcode="${filter}"]`);
if (found) input.dataset.usepercentages = found.dataset.usepercentages;
const found = input.parent().find(`.materialList p[data-materialcode="${filter}"]`);
if (found) {
input.data({
usepercentages: found.data("usepercentages")
});
}
checkUnits(input, input.parentElement.parentElement.parentElement);
checkUnits(input, input.parents(".materialListRow"));
}
function checkUnits(materialSelector, row) {
const quantityUnits = row.querySelector(".quantityUnits");
if (materialSelector.dataset.usepercentages === "true") quantityUnits.innerText = "%";
else quantityUnits.innerText = "mL";
function checkUnits(materialSelector) {
const quantityUnits = $(materialSelector).parents(".materialListRow").find(".quantityUnits");
console.log($(quantityUnits));
if ($(materialSelector).data("usepercentages")) quantityUnits.text("%");
else quantityUnits.text("mL");
}

View File

@ -0,0 +1,47 @@
import * as bpac from "/js/bpac.js";
export class PtouchPrinter {
constructor(object) {
this.object = object;
this.pdocument = bpac.IDocument;
}
async print() {
if (!bpac.IsExtensionInstalled()) {
console.error("L'extension b-Pac n'est pas installée");
return -1;
}
try {
await this.openDoc();
await this.fillDoc();
this.printDoc();
this.pdocument.Close();
return 1;
} catch (e) {
console.log(e);
return 99;
}
};
async openDoc() {
const docUrl = `${baseUrl}/lbx/${this.object.template}.lbx`;
console.log("Ouverture du modèle: " + docUrl);
await this.pdocument.Open(docUrl);
}
async fillDoc() {
for (let i = 0; i < this.object.lines.length; i++) {
const line = this.object.lines[i];
const label = await this.pdocument.GetObject(line.name);
label.Text = line.value;
}
}
printDoc() {
this.pdocument.StartPrint("", 0);
this.pdocument.PrintOut(1, 0);
this.pdocument.EndPrint();
}
}

View File

@ -0,0 +1,8 @@
$(() => {
$(".companyTabTitle").on({
click: function () {
const companyID = $(this).parent().data("companyid");
$(`#recipes_${companyID}`).toggle();
}
});
});

View File

@ -33,8 +33,6 @@ function performSearch(searchString) {
companyRows.forEach(c => {
const recipeRows = c.querySelectorAll(".recipeRow");
hideElement(warningMsgBox);
if (!searchString || !searchString.replace(/\s/g, '').length) {
c.classList.add("researchResult");
@ -44,7 +42,7 @@ function performSearch(searchString) {
found = true;
emptySearch = true;
} else if (searchIn(searchString, c.querySelector("h2").dataset.companyname)) {
} else if (searchIn(searchString, c.querySelector("h2").textContent)) {
c.classList.add("researchResult");
recipeRows.forEach(r => r.classList.add("researchResult"));
found = true;
@ -59,14 +57,12 @@ function performSearch(searchString) {
});
});
}
if (!found) {
warningMsgBoxText.innerText = researchNotFound;
showElement(warningMsgBox);
}
if (emptySearch) closeTabs();
else openTabs();
}
);
if (!found) showMessage(warningMsg, researchNotFound, false);
else hideMessage(warningMsg);
if (emptySearch) closeTabs();
else openTabs();
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -6,7 +6,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>

View File

@ -20,7 +20,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>

View File

@ -5,7 +5,7 @@
</head>
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<section>
<div th:include="fragments.html :: messages"></div>

View File

@ -11,10 +11,10 @@
</head>
<body>
<div th:fragment="header">
<div th:fragment="header(showPrinterIcon)">
<nav>
<div class="dropdown">
<button class="dropbtn" onclick="document.location.href='/'" th:text="#{menu.explore}">
<button class="dropbtn" onclick="window.location.href='/'" th:text="#{menu.explore}">
</button>
</div>
<div class="dropdown">
@ -44,59 +44,116 @@
</div>
</div>
<div class="dropdown">
<button class="dropbtn" onclick="document.location.href='/inventory'" th:text="#{menu.inventory}"></button>
<button class="dropbtn" onclick="window.location.href='/inventory'" th:text="#{menu.inventory}"></button>
</div>
<div class="dropdown">
<button class="dropbtn" th:text="#{menu.others}"></button>
<div class="dropdown-content">
<a th:href="@{|${baseUrl}/touchup|}" th:text="#{keyword.touchUpKitPDF}"></a>
<a th:href="@{|${baseUrl}/recipe/xls|}" th:text="#{recipe.exportAllXLS}"
onclick="return askDatabaseExport()"></a>
<a th:href="@{|${baseUrl}/touchup|}" th:text="#{recipe.touchup}"></a>
<a href="#" th:text="#{recipe.exportAllXLS}"
onclick="return confirmDatabaseExport()"></a>
<a th:href="@{|${baseUrl}/lbx/Templates.zip|}" th:text="#{keyword.ptouchTemplate}"></a>
<a th:href="@{|${baseUrl}/updates|}" th:text="#{keyword.updates}"></a>
</div>
</div>
<img alt="logo" id="logo" th:src="@{|${baseUrl}/logo.png|}" onclick="location.href = '/'"/>
</nav>
<div class="spacer"></div>
<div class="header-right">
<img th:if="${showPrinterIcon}" alt="print status" id="printStatusIcon"
th:src="@{|${baseUrl}/icons/printerError.svg|}"/>
<img alt="logo" id="logo" th:src="@{|${baseUrl}/logo.png|}" onclick="location.href = '/'"/>
</div>
</div>
<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 th:include="fragments.html :: confirm"></div>
<div th:include="fragments.html :: prompt"></div>
<div th:include="fragments.html :: filter"></div>
</div>
<div th:fragment="error">
<div class="messageBox errorBox">
<div class="messageBox" id="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 class="messageBoxMessageContainer">
<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>
<div th:fragment="success">
<div class="messageBox successBox">
<div class="messageBox" id="successBox">
<div class="messageBoxContainer">
<img th:src="@{|${baseUrl}/icons/success.svg|}" alt="success icon"/>
<p th:if="${success != null && !success.isEmpty()}"
th:text="#{${success}(${responseArg1}, ${responseArg2})}"></p>
<p th:if="${success == null || success.isEmpty()}"></p>
<div class="messageBoxMessageContainer">
<img th:src="@{|${baseUrl}/icons/success.svg|}" alt="success icon"/>
<p th:if="${success != null && !success.isEmpty()}"
th:text="#{${success}(${responseArg1}, ${responseArg2})}"></p>
<p th:if="${success == null || success.isEmpty()}"></p>
</div>
</div>
</div>
</div>
<div th:fragment="warning">
<div class="messageBox warningBox">
<div class="messageBox" id="warningBox">
<div class="messageBoxContainer">
<img th:src="@{|${baseUrl}/icons/warning.svg|}" alt="warning icon"/>
<p th:if="${warning != null && !warning.isEmpty()}"
th:text="#{${warning}(${responseArg1}, ${responseArg2})}"></p>
<p th:if="${warning == null || warning.isEmpty()}"></p>
<div class="messageBoxMessageContainer">
<img th:src="@{|${baseUrl}/icons/warning.svg|}" alt="warning icon"/>
<p th:if="${warning != null && !warning.isEmpty()}"
th:text="#{${warning}(${responseArg1}, ${responseArg2})}"></p>
<p th:if="${warning == null || warning.isEmpty()}"></p>
</div>
</div>
</div>
</div>
<div th:fragment="confirm">
<div class="messageBox" id="confirmBox">
<div class="messageBoxContainer">
<div class="messageBoxMessageContainer">
<img th:src="@{|${baseUrl}/icons/confirm.svg|}" alt="confirm icon"/>
<p th:if="${confirm != null && !confirm.isEmpty()}"
th:text="#{${confirm}(${responseArg1}, ${responseArg2})}"></p>
<p th:if="${confirm == null || confirm.isEmpty()}"></p>
</div>
<div class="confirmButtonsContainer">
<button type="button" class="confirmCancel" th:text="#{keyword.cancel}"></button>
<button type="button" class="confirmContinue" th:text="#{keyword.continue}"></button>
</div>
</div>
</div>
</div>
<div th:fragment="prompt">
<div class="messageBox" id="promptBox">
<div class="messageBoxContainer">
<div class="messageBoxMessageContainer">
<img th:src="@{|${baseUrl}/icons/confirm.svg|}" alt="confirm icon"/>
<p th:if="${prompt != null && !prompt.isEmpty()}"
th:text="#{${prompt}(${responseArg1}, ${responseArg2})}"></p>
<p th:if="${prompt == null || prompt.isEmpty()}"></p>
</div>
<div class="confirmInputContainer">
<input type="text" class="noPlaceholder" id="confirmInput"/>
</div>
<div class="confirmButtonsContainer">
<button type="button" class="confirmCancel" th:text="#{keyword.cancel}"></button>
<button type="button" class="confirmContinue" th:text="#{keyword.continue}"></button>
</div>
</div>
</div>
</div>
<div th:fragment="filter">
<div id="filter"></div>
</div>
<div th:fragment="separator">
<td colspan="2">
<hr/>
@ -116,17 +173,20 @@
<script th:src="@{|${baseUrl}/js/main.js|}"></script>
<script>
/*<![CDATA[*/
const simdutNotFoundText = "[[#{material.simdutFile.notFound}]]".replace("&#39;", "'");
const changesNotSavedText = "[[#{recipe.warning.changesNotSaved}]]".replace("&#39;", "'");
const exportAllWarningText = "[[#{recipe.warning.exportAll}]]".replace("&#39;", "'");
const askPasswordText = "[[#{password.ask}]]".replace("&#39;", "'");
const invalidPasswordText = "[[#{password.notValid}]]".replace("&#39;", "'");
const generalErrorText = "[[#{error.serverError}]]".replace("&#39;", "'");
const researchNotFound = "[[#{warning.noResult}]]".replace("&#39;", "'");
const recipeNotApproved = "[[#{recipe.warning.notApproved}]]".replace("&#39;", "'");
const askChangePage = "[[#{warning.askChangePage}]]".replace("&#39;", "'");
const simdutNotFoundText = formatMessage("[[#{material.simdutFile.notFound}]]");
const changesNotSavedText = formatMessage("[[#{recipe.warning.changesNotSaved}]]");
const exportAllWarningText = formatMessage("[[#{recipe.warning.exportAll}]]");
const askPasswordText = formatMessage("[[#{password.ask}]]");
const invalidPasswordText = formatMessage("[[#{password.notValid}]]");
const generalErrorText = formatMessage("[[#{error.serverError}]]");
const researchNotFound = formatMessage("[[#{warning.noResult}]]");
const recipeNotApproved = formatMessage("[[#{recipe.warning.notApproved}]]");
const askChangePage = formatMessage("[[#{warning.askChangePage}]]");
const printOkTitle = formatMessage("[[#{recipe.print.status.ok}]]");
const printErrorTitle = formatMessage("[[#{recipe.print.status.error}]]");
const referer = "[[${referer}]]";
const baseUrl = "[[${baseUrl}]]";
/*]]>*/
</script>
</div>
@ -147,5 +207,13 @@
</div>
</div>
<script th:fragment="printStrings">
const noBaseError = formatMessage("[[#{recipe.print.error.noBase}]]");
const bpacNotInstalledError = formatMessage("[[#{recipe.print.error.bpacNotInstalled}]]");
const printError = formatMessage("[[#{recipe.print.error}]]");
const confirmPrintMessage = formatMessage("[[#{recipe.print.confirm}]]");
const printingMessage = formatMessage("[[#{recipe.print.printing}]]");
</script>
</body>
</html>

View File

@ -6,7 +6,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>

View File

@ -5,7 +5,7 @@
</head>
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div id="researchBoxContainer">
@ -20,10 +20,9 @@
<div class="content recipesContainer">
<div class="companyTab" th:if="${!recipeMap.empty}" th:each="company : ${recipeMap.keySet()}"
th:data-companyID="${company.companyID}">
<h2 class="companyTabTitle" th:data-companyName="${company.companyName}"
th:text="${company.companyName}"></h2>
<h2 class="companyTabTitle" th:text="${company.companyName}"></h2>
<table style="display:none" th:id="'recipes_' + ${company.companyName}" class="recipesList"
<table style="display:none" th:id="'recipes_' + ${company.companyID}" class="recipesList"
th:if="${!recipeMap.get(company).empty}">
<tr>
<th th:text="#{recipe.color}"></th>
@ -51,16 +50,15 @@
<footer th:include="fragments.html :: footer(null, true)"></footer>
<script th:src="@{|${baseUrl}/js/recipeResearch.js|}"></script>
<script th:src="@{|${baseUrl}/js/recipeList.js|}"></script>
<script>
(() => {
document.querySelectorAll(".gotoRecipe").forEach(e => {
e.addEventListener("click", () => {
const recipeID = e.getAttribute("data-recipeID");
document.location.href = "/recipe/explore/" + recipeID;
});
$(() => {
$(".gotoRecipe").on({
click: function () {
window.location.href = `/recipe/explore/${$(this).data("recipeid")}`;
}
});
})();
});
</script>
</body>
</html>

View File

@ -57,7 +57,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<th:block th:each="materialType : ${materialTypes}"
@ -156,7 +156,7 @@
// Rajoute un événement sur les boutons pour modifier les produits pour rediriger l'utilisateur vers la page de modification
document.querySelectorAll(".modifyMaterial").forEach(e => {
e.addEventListener("click", () => {
document.location.href = `/material/editor/${e.dataset.materialid}`;
window.location.href = `/material/editor/${e.dataset.materialid}`;
});
});
@ -213,8 +213,6 @@
const materialRows = document.querySelectorAll(".materialRow");
function performSearch(searchString) {
hideElement(warningMsgBox);
let found = false;
const recipesContainer = document.querySelector(".materialsContainer:not(.researchEnabled)");
@ -242,10 +240,8 @@
}
});
if (!found) {
warningMsgBoxText.innerText = researchNotFound;
showElement(warningMsgBox);
}
if (!found) showMessage(warningMsg, researchNotFound, false);
else hideMessage(warningMsg);
}
/*]]>*/

View File

@ -8,7 +8,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<p th:text="#{material.success.created(${materialCode})}"></p>

View File

@ -6,10 +6,9 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<p th:if="${materialCode != null}" th:text="#{material.success.created(${materialCode})}" class="success"></p>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{material.add.title}"></h1>
@ -29,7 +28,7 @@
<label th:for="${#ids.next('materialType')}" th:text="#{material.type} + ':'"></label>
</div>
<div>
<label for="simdut" th:text="#{material.simdutFile} + ':'"></label>
<label for="simdut" th:text="#{material.simdut} + ':'"></label>
</div>
</div>
<div class="formColumn">

View File

@ -9,7 +9,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
@ -34,6 +34,9 @@
<div>
<label th:for="${#ids.next('materialType')}">Type de produit: </label>
</div>
<div>
<label th:text="#{material.simdut} + ':'"></label>
</div>
</div>
<div class="formColumn">
<div>
@ -71,6 +74,10 @@
th:value="${mType.materialTypeID}"></option>
</select>
</div>
<div>
<button id="showSIMDUT" type="button" th:text="#{keyword.see}"></button>
<button id="editSIMDUT" type="button" th:text="#{keyword.edit}"></button>
</div>
</div>
</div>
<div th:include="fragments.html :: formEndButtons"></div>
@ -90,7 +97,7 @@
});
document.querySelector("#editSIMDUT").addEventListener("click", () => {
document.location.href = `/material/simdut/${materialID}`;
window.location.href = `/material/simdut/${materialID}`;
});
})();

View File

@ -17,13 +17,10 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
<p class="success" th:if="${materialCode != null}"
th:text="#{material.success.saved(${materialCode})}"></b>
</p>
<h1 th:text="#{material.edit.title}"></h1>
<table th:if="${!materials.empty}">
@ -55,7 +52,7 @@
e.addEventListener("click", () => {
const materialID = e.getAttribute("data-materialID");
document.location.href = "/material/editor/" + materialID;
window.location.href = "/material/editor/" + materialID;
});
});
})();

View File

@ -17,12 +17,10 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<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>
<form th:action="@{/material/remover/}" class="requireAuth-remover" method="POST">

View File

@ -6,7 +6,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>

View File

@ -8,7 +8,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>

View File

@ -7,7 +7,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
@ -19,7 +19,7 @@
<div class="formWrap">
<div class="formColumn">
<div>
<label th:for="${#ids.next('materialTypeID')}" th:text="#{material.type}"></label>
<label th:for="${#ids.next('materialTypeID')}" th:text="#{keyword.id} + ':'"></label>
</div>
<div>
<label th:for="${#ids.next('materialTypeName')}" th:text="#{materialType.name} + ':'"></label>

View File

@ -17,13 +17,10 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
<p class="success" th:if="${materialTypeName != null}"
th:text="#{materialType.success.saved(${materialTypeName})}"></b>
</p>
<h1 th:text="#{materialType.editor.title}"></h1>
<table th:if="${!materialTypes.empty}">
@ -53,7 +50,7 @@
e.addEventListener("click", () => {
const materialTypeID = e.getAttribute("data-materialTypeID");
document.location.href = "/materialType/editor/" + materialTypeID;
window.location.href = "/materialType/editor/" + materialTypeID;
});
});
})();

View File

@ -17,7 +17,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>

View File

@ -8,7 +8,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>

View File

@ -7,7 +7,7 @@
</head>
<body th:with="nbrMaterials = 0">
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
@ -85,9 +85,9 @@
removeText = "[[#{keyword.remove}]]";
document.querySelector("#removeMix").addEventListener("click", () => {
showElement(errorMsgBox);
showMessage(errorMsg);
checkPassword(null, () => document.location.href = `/mix/remover/[[${mix.mixID}]]`);
checkPassword(null, () => window.location.href = `/mix/remover/[[${mix.mixID}]]`);
});
})();

View File

@ -19,7 +19,8 @@
th:text="${material.materialType.materialTypeName == 'Aucun' ? '' : '[' + material.materialType.prefix + ']'} + ' ' + ${material.materialCode}"
th:data-materialcode="${material.materialCode}"
th:data-materialtype="${material.materialType.materialTypeName}"
th:data-usepercentages="${material.materialType.usePercentages}"></p>
th:data-usepercentages="${material.materialType.usePercentages}"
onclick="selectMaterial(this)"></p>
</div>
</div>

View File

@ -1,155 +0,0 @@
<div class="form">
<form th:action="@{/recipe/editor}" th:object="${recipe}" class="requireAuth" method="POST">
<input type="hidden" th:field="*{recipeID}"/>
<table class="mainTable">
<tr>
<td>
<table>
<tr>
<td><b th:text="#{keyword.id} + ':'"></b></td>
<td th:text="${recipe.recipeID}"></td>
</tr>
<tr th:include="fragments.html :: separator"></tr>
<tr>
<td><b><label th:for="${#ids.next('recipeCode')}"
th:text="#{recipe.color} + ':'"></label></b></td>
<td><input type="text" th:field="*{recipeCode}"></td>
</tr>
<tr th:include="fragments.html :: separator"></tr>
<tr>
<td><b><label th:for="${#ids.next('company')}"
th:text="#{keyword.company} + ':'"></label></b>
</td>
<td>
<select th:field="*{company}">
<option th:each="company : ${companies}"
th:selected="${recipe.company.equals(company)}"
th:text="${company.companyName}"
th:value="${company.companyID}"></option>
</select>
</td>
</tr>
<tr th:include="fragments.html :: separator"></tr>
<tr>
<td><b><label th:for="${#ids.next('recipeDescription')}"
th:text="#{recipe.description} + ':'"></label></b></td>
<td><input type="text" th:field="*{recipeDescription}"/></td>
</tr>
<tr th:include="fragments.html :: separator"></tr>
<tr>
<td><b><label th:for="${#ids.next('sample')}" th:text="#{recipe.sample} + ':'"></label></b>
</td>
<td><input type="number" th:field="*{sample}"/></td>
</tr>
<tr th:include="fragments.html :: separator"></tr>
<tr>
<td><b><label th:for="${#ids.next('approbationDate')}"
th:text="#{recipe.approbationDate} + ':'"></label></b></td>
<td><input type="date" th:field="*{approbationDate}"/></td>
</tr>
<tr th:include="fragments.html :: separator"></tr>
<tr>
<td><b><label th:for="${#ids.next('remark')}" th:text="#{recipe.remark} + ':'"></label></b>
</td>
<td><textarea cols="20" rows="2" th:field="*{remark}"></textarea>
</td>
</tr>
</table>
</td>
<td>
<table>
<tr>
<td colspan="2" style="height: 40px; text-align: right">
<button id="newMix" type="button" th:text="#{recipe.edit.addMix}"></button>
</td>
</tr>
<th:block th:each="mix : ${mixes}">
<tr>
<td class="mixNameColumn">
<b th:text="${mix.mixType.typeName} + ':'"></b>
<br/>
<th:block th:if="${mix != null}">
<button class="mixEditor" th:data-mixID="${mix.mixID}" type="button"
th:text="#{keyword.edit}"></button>
</th:block>
</td>
<td>
<div class="recipe">
<table style="margin-left: 50px">
<!-- Produits -->
<tr>
<th th:text="#{keyword.material}"></th>
<th th:text="#{keyword.type}"></th>
<th th:text="#{keyword.quantity}"></th>
</tr>
<tr th:each="mixQuantity : ${mix.mixQuantities}"
th:with="material = ${mixQuantity.material}">
<td th:classappend="${material.isMixType()} ? '' : materialCode"
th:data-materialID="${material.materialID}"
th:text="${material.materialCode}"></td>
<td>
<p th:text="${material.materialType.materialTypeName}"></p>
</td>
<td th:text="${mixQuantity.quantity} + ' ' + ${material.materialType.usePercentages ? '%' : 'mL'}"></td>
</tr>
</table>
</div>
</td>
</tr>
<tr th:include="fragments.html :: separator"></tr>
</th:block>
</table>
</td>
</tr>
<tr>
<td>
<!-- Images -->
<table>
<tr th:each="image : ${images}">
<td class="centerTD">
<img alt="Image supprimée ou corrompue" th:src="'/images/' + ${image}"
height="250px"/>
</td>
<td style="text-align: left; padding-left: 50px">
<button class="deleteImg" th:data-image="${image}" type="button"
style="height: 250px">
<span th:text="#{keyword.delete}"
style="display: inline-block; transform: rotate(90deg); font-weight: bolder"></span>
</button>
</td>
</tr>
</table>
</td>
<td>
<!-- Étapes -->
<table>
<tr>
<td style="text-align: center">
<h3 th:text="#{recipe.steps}"></h3>
</td>
<td style="text-align: left; vertical-align: middle; padding-left: 20px">
<button id="addStep" type="button">+</button>
</td>
<td style="width: 100%"></td>
</tr>
<tr>
<td colspan="3">
<table id="steps" style="width:100%">
<tbody></tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<button id="addImg" th:data-recipeID="${recipe.recipeID}" type="button"
th:text="#{recipe.edit.addImage}"></button>
</td>
</tr>
<tr th:include="fragments.html :: mainTableButtons"></tr>
</table>
</form>
</div>

View File

@ -6,14 +6,14 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{recipe.add.title}"></h1>
<p th:text="#{recipe.sucess.saved(${recipe.recipeCode})}"></p>
<button th:onclick="'document.location.href=\'/recipe/editor/' + ${recipe.recipeID} + '\''"
<button th:onclick="'window.location.href=\'/recipe/editor/' + ${recipe.recipeID} + '\''"
th:text="#{keyword.continue}"></button>
</section>
<!-- Fragment du pied de page -->

View File

@ -5,7 +5,7 @@
</head>
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>

View File

@ -6,29 +6,6 @@
<link href="/css/explore.css" rel="stylesheet"/>
<style>
/*p {*/
/* display: inline;*/
/*}*/
/*.recipe table {*/
/* background-color: #fafafa;*/
/* border: 1px solid #7a7a7a;*/
/* border-collapse: collapse !important;*/
/*}*/
/*.recipe td, .recipe th {*/
/* min-width: 100px;*/
/* text-align: center;*/
/*}*/
/*.recipe tr:nth-child(odd) {*/
/* background-color: #f5f5f5;*/
/*}*/
/*.mixNameColumn {*/
/* display: inline-block;*/
/*}*/
.content {
flex-direction: row;
}
@ -63,14 +40,14 @@
}
#steps label {
vertical-align: middle;
/*vertical-align: middle;*/
}
</style>
</head>
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
@ -171,16 +148,13 @@
</tr>
</table>
</div>
<div class="mixActionsContainer">
<button class="useMixSubmit" type="button" th:text="#{keyword.use}"></button>
</div>
</div>
</div>
<!-- Étapes -->
<div class="flexContent" id="steps" style="display: flex; flex-direction: column">
<div style="display: flex; flex-direction: row; justify-content: center">
<h3 th:text="#{recipe.steps}"></h3>
<button id="addStep" type="button" style="width: 20px; height: 20px; margin:20px">+</button>
<h3 th:text="#{recipe.steps}" style="margin: 0"></h3>
<button id="addStep" type="button" style="width: 20px; height: 20px; margin: 0 20px">+</button>
</div>
</div>
@ -224,50 +198,44 @@
e.addEventListener("click", () => {
const mixID = e.getAttribute("data-mixID");
document.location.href = "/mix/editor/" + mixID;
window.location.href = "/mix/editor/" + mixID;
});
});
document.querySelector("#newMix").addEventListener("click", () => {
const recipeID = "[[${recipe.recipeID}]]";
document.location.href = "/mix/creator/" + recipeID;
window.location.href = "/mix/creator/" + recipeID;
});
document.querySelectorAll(".deleteImg").forEach(e => {
e.addEventListener("click", async () => {
if (confirm(askChangePage)) {
showConfirm(askChangePage, false, () => {
checkPassword(null, () => {
let data = {};
data['image'] = e.getAttribute("data-image");
hideElement(errorMsgBox);
axios.post("/images/delete", data)
.then(r => {
const data = r.data;
if (data['error'] !== undefined) {
errorMsgBoxText.innerText = data['error'];
showElement(errorMsgBox);
showMessage(errorMsg, data['error']);
} else {
document.location.reload();
window.location.reload();
}
})
.catch(e => {
console.log(e);
errorMsgBoxText.innerText = "[[#{error.serverError}]]";
showElement(errorMsgBox);
showMessage(errorMsg, generalErrorText);
});
});
}
});
});
});
document.querySelector("#addImage").addEventListener("click", () => {
if (confirm(askChangePage)) {
document.location.href = "/images/add/[[${recipe.recipeID}]]";
}
showConfirm(askChangePage, false, () => window.location.href = "/images/add/[[${recipe.recipeID}]]");
});
const recipeText = "[[${recipeJSON}]]";
@ -288,7 +256,7 @@
let input = document.createElement("input");
input.type = "text";
input.id = `step_${stepNbr}`;
input.classList.add("step", "rawInput");
input.classList.add("step", "rawInput", "toSave");
input.name = "step";
input.autocomplete = "off";
if (value != null) input.value = value;

View File

@ -6,7 +6,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div id="researchBoxContainer">
@ -21,10 +21,8 @@
<div class="content recipesContainer">
<div class="companyTab" th:if="${!recipeMap.empty}" th:each="company : ${recipeMap.keySet()}"
th:data-companyID="${company.companyID}">
<h2 class="companyTabTitle" th:data-companyName="${company.companyName}"
th:text="${company.companyName}"></h2>
<table style="display:none" th:id="'recipes_' + ${company.companyName}" class="recipesList"
<h2 class="companyTabTitle" th:text="${company.companyName}"></h2>
<table style="display:none" th:id="'recipes_' + ${company.companyID}" class="recipesList"
th:if="${!recipeMap.get(company).empty}">
<tr>
<th th:text="#{recipe.color}"></th>
@ -52,16 +50,15 @@
<footer th:include="fragments.html :: footer(null, true)"></footer>
<script th:src="@{|${baseUrl}/js/recipeResearch.js|}"></script>
<script th:src="@{|${baseUrl}/js/recipeList.js|}"></script>
<script>
(() => {
document.querySelectorAll(".editRecipe").forEach((e) => {
e.addEventListener("click", () => {
const recipeID = e.getAttribute("data-recipeID");
document.location.href = "/recipe/editor/" + recipeID;
});
$(() => {
$(".editRecipe").on({
click: function () {
window.location.href = `/recipe/editor/${$(this).data("recipeid")}`;
}
});
})();
});
</script>
</body>
</html>

View File

@ -8,7 +8,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(true)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
@ -117,6 +117,8 @@
<!-- Produits -->
<tr th:each="mixQuantity : ${mix.mixQuantities}"
th:with="material = ${mixQuantity.material}"
class="materialRow"
th:data-materialtypename="${material.materialType.materialTypeName}"
th:id="'material-' + ${material.materialID}">
<td class="materialCodeColumn"
th:classappend="${material.isMixType()} ? '' : materialCode"
@ -136,6 +138,7 @@
th:data-mixID="${mix.mixID}"
th:data-quantityML="${mixQuantity.quantity}"
th:data-usePercentages="${material.materialType.usePercentages}"
th:data-defaultvalue="${mixQuantity.quantity}"
th:value="${mixQuantity.quantity}"
th:disabled="${material.materialType.usePercentages}"
type="number"/></td>
@ -170,7 +173,7 @@
</div>
<div class="mixActionsContainer">
<button class="useMixSubmit" type="button" th:text="#{keyword.use}"></button>
<button type="button" th:text="#{keyword.print}"></button>
<button type="button" class="printButton" th:text="#{keyword.print}"></button>
</div>
</div>
</th:block>
@ -207,210 +210,160 @@
<script>
/*<![CDATA[*/
const quantityKeyword = "[[#{keyword.quantity}]]";
(() => {
document.querySelector("#modifyRecipe").addEventListener("click", () => {
const recipeID = document.querySelector("#recipeID").value;
document.location.href = `/recipe/editor/${recipeID}`;
});
document.querySelectorAll(".quantityCustomizer").forEach(e => {
if (e.dataset.usepercentages === "true") {
const parentQuantity = e.parentElement.parentElement.parentElement.querySelectorAll(".quantityCustomizer")[0].dataset.quantityml;
e.dataset.quantityml = percentageOf(e.value, parentQuantity);
$(() => {
$("#modifyRecipe").on({
click: function () {
window.location.href = `/recipe/editor/${$("#recipeID").val()}`;
}
e.addEventListener("change", () => {
// Les 3 parentElement récupèrent la table dans laquelle le produit se trouve
const parentTable = e.parentElement.parentElement.parentElement;
const firstInput = parentTable.querySelectorAll(".quantityCustomizer")[0];
if (e.dataset.usepercentages === "false") {
const value = e.valueAsNumber;
const oldValue = e.defaultValue;
const currentUnit = document.querySelector("#unitsSelect").value;
// Modifie les quantités de tous les produits
parentTable.querySelectorAll(".quantityCustomizer").forEach((elem) => {
if (elem.dataset.usepercentages === "false") {
const defaultValue = elem.defaultValue;
const newValue = (defaultValue * value) / oldValue;
elem.value = round(newValue);
// Recalcule la quantité en millilitres de chaque produit
elem.dataset.quantityml = convertUnitToMl(newValue);
} else {
elem.dataset.quantityml = percentageOf(elem.value, firstInput.dataset.quantityml);
}
});
} else {
e.dataset.quantityml = percentageOf(e.value, firstInput.dataset.quantityml);
}
doCalculations(parentTable);
});
});
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}`);
$(".quantityCustomizer").each(function () {
if ($(this).data("usepercentages")) {
const parentQuantity = $(this).parents(".mixContainer").find(".quantityCustomizer").eq(0).data("quantityml");
$(this).data({quantityml: percentageOf($(this).val(), parentQuantity)});
}
});
mixTable.querySelectorAll(".quantityCustomizer").forEach(elem => {
if (elem.dataset.usepercentages === "false") {
const defaultValue = elem.defaultValue;
const newValue = (defaultValue * value) / oldValue;
$(".quantityCustomizer").on({
keyup: function () {
computeQuantities($(this));
}
});
console.log(value + "/" + oldValue + "/" + defaultValue + "/" + newValue);
$(".totalQuantityCustomizer").on({
keyup: function () {
const mix = $(this).parents(".mixContainer");
elem.value = round(newValue);
}
const value = $(this).val();
const defaultValue = $(this).data("defaultvalue");
const firstMaterial = mix.find(".quantityCustomizer").eq(0);
const firstDefaultValue = firstMaterial.data("defaultvalue");
const firstNewValue = (firstDefaultValue * value) / defaultValue;
firstMaterial.val(round(firstNewValue));
computeQuantities(firstMaterial, false);
}
});
$("#formSubmit").on({
click: function () {
let formData = {};
formData.recipeID = $("#recipeID").val();
formData.locations = {};
$(".recipeLocation").each(function () {
formData.locations[$(this).data("mixid")] = $(this).val();
});
doCalculations(mixTable);
})
formData.note = $("#note").val();
sendPost(formData, "/recipe/explore", () => showMessage(errorMsg, generalErrorText));
}
});
document.querySelector("#formSubmit").addEventListener("click", () => {
let formData = {};
// Identifiant de la recette
formData.recipeID = document.querySelector("#recipeID").value;
// Position
formData.locations = {};
document.querySelectorAll(".recipeLocation").forEach((e) => {
formData.locations[e.dataset.mixid] = e.value;
});
formData.note = document.querySelector("#note").value;
sendPost(formData, "/recipe/explore", () => showElement(warningMsgBox));
hideElement(warningMsgBox);
});
document.querySelector("#useSubmit").addEventListener("click", () => {
const shouldContinue = confirm("[[#{inventory.askUseRecipe}]]".replace("&#39;", "'"));
if (!shouldContinue) return;
let formData = {};
document.querySelectorAll(".quantityCustomizer").forEach(e => {
const materialID = e.dataset.materialid;
const mixID = e.dataset.mixid;
if (formData[mixID] === undefined) {
formData[mixID] = {};
}
formData[mixID][materialID] = e.dataset.quantityml;
});
clearNotEnoughClasses();
sendPost(formData, "/inventory/use", r => displayNotEnoughReason(r));
});
document.querySelectorAll(".useMixSubmit").forEach(e => {
e.addEventListener("click", () => {
const shouldContinue = confirm("[[#{inventory.askUseMix}]]".replace("&#39;", "'"));
if (!shouldContinue) return;
$("#useSubmit").on({
click: function () {
let formData = {};
e.parentElement.parentElement.querySelectorAll(".quantityCustomizer").forEach(elem => {
const materialID = elem.getAttribute("data-materialID");
const mixID = elem.getAttribute("data-mixID");
$(".quantityCustomizer").each(function () {
const materialID = $(this).data("materialid");
const mixID = $(this).data("mixid");
if (formData[mixID] === undefined) {
formData[mixID] = {};
}
formData[mixID][materialID] = elem.dataset.quantityml;
formData[mixID][materialID] = $(this).data("quantityml");
});
clearNotEnoughClasses();
sendPost(formData, "/inventory/use", r => displayNotEnoughReason(r));
});
}
});
window.addEventListener("load", () => {
document.querySelectorAll(".mix").forEach(e => doCalculations(e));
$(".useMixSubmit").on({
click: function () {
showConfirm(formatMessage("[[#{inventory.askUseMix}]]"), false, () => {
let formData = {};
$(this).parents(".mixContainer").find(".quantityCustomizer").each(function () {
const materialID = $(this).data("materialid");
const mixID = $(this).data("mixid");
if (formData[mixID] === undefined) formData[mixID] = {};
formData[mixID][materialID] = $(this).data("quantityml");
});
clearNotEnoughClasses();
sendPost(formData, "/inventory/use", r => displayNotEnoughReason(r));
});
}
});
})();
$(".mix").each(function () {
doCalculations(this)
});
});
function changeCustomizersUnits() {
document.querySelectorAll(".quantityCustomizer").forEach(e => {
if (e.dataset.usepercentages === "false") {
const originalValue = e.dataset.quantityml;
e.value = convertMlToUnit(originalValue);
}
$(".quantityCustomizer").each(function () {
const node = $(this);
if (node.data("usepercentages") === "false") $(this).val(convertMlToUnit($(this).data("quantityml")));
});
}
function clearNotEnoughClasses() {
document.querySelectorAll(".notEnough").forEach(elem => {
elem.classList.remove("notEnough");
});
$(".notEnough").removeClass("notEnough");
}
function displayNotEnoughReason(reason) {
const splitReason = reason.split("-");
const mixID = splitReason[0];
const materialID = splitReason[1];
document.querySelector(`#mix-${mixID}`).querySelector(`#material-${materialID}`).classList.add("notEnough");
$(`#mix-${splitReason[0]}`).find(`#material-${splitReason[1]}`).addClass("notEnough");
}
function hideQuantities(button) {
let hidden = button.dataset.hidden;
button = $(button);
if (hidden === "true") hidden = "false";
else hidden = "true";
// Récupère la valeur, la convertit en bool, puis l'inverse
let hiddenString = button.data("hidden");
let hidden = hiddenString || hiddenString === "true";
hidden = !hidden;
button.parents(".mix").find(".inventoryQuantity").each(function () {
const node = $(this);
let css;
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");
if (hidden) {
css = {minWidth: "auto"};
node.parent().css(css);
button.parent().css(css);
button.innerText = "->";
node.hide();
button.text("->");
} else {
e.style.display = "inline";
css = {minWidth: 75};
node.parent().css(css);
button.parent().css(css);
e.parentElement.setAttribute("style", "min-width: 75px !important");
button.parentElement.setAttribute("style", "min-width: 75px !important");
button.innerText = quantityKeyword;
node.show();
button.text("[[#{keyword.quantity}]]");
}
});
button.dataset.hidden = hidden;
button.data({hidden: hidden});
}
function sendPost(data, url, errorCallback) {
hideElement(successMsgBox);
hideElement(errorMsgBox);
axios.post(url, data)
.then(r => {
const data = r.data;
if (data.success !== undefined) {
successMsgBoxText.innerHTML = data.success.message;
showElement(successMsgBox);
showMessage(successMsg, data.success.message);
return true;
} else if (data.error !== undefined) {
errorMsgBoxText.innerText = data.error.message;
showElement(errorMsgBox);
showMessage(errorMsg, data.error.message);
if (typeof errorCallback !== 'undefined') {
errorCallback(data.reason);
@ -421,96 +374,146 @@
})
.catch(e => {
console.log(e);
errorMsgBoxText.innerText = "[[#{error.serverError}]]";
showElement(errorMsgBox);
showMessage(errorMsg, generalErrorText);
return false;
});
}
function doCalculations(parent) {
function computeQuantities(customizer, computeTotal) {
const mix = customizer.parents(".mixContainer");
const mixCustomizers = mix.find(".quantityCustomizer");
const firstInput = mixCustomizers.eq(0);
const value = customizer.val();
const defaultValue = customizer.data("defaultvalue");
customizer.data("blabla", "balbla");
mixCustomizers.each(function () {
if (!$(this).data("usepercentages")) {
const currentDefaultValue = $(this).data("defaultvalue");
const newValue = (currentDefaultValue * value) / defaultValue;
$(this).val(round(newValue));
$(this).data({
quantityml: convertUnitToMl(newValue)
});
} else {
$(this).data({
quantityml: percentageOf($(this).val(), firstInput.data("quantityml"))
})
}
});
doCalculations(mix, computeTotal);
}
function doCalculations(parent, computeTotal = true) {
let lastQuantity = 0;
parent = $(parent);
parent.querySelectorAll(".quantityCustomizer").forEach(e => {
const materialID = e.dataset.materialid;
const mixID = e.dataset.mixid;
const quantity = e.dataset.usepercentages === "true" ? round(convertMlToUnit(e.dataset.quantityml)) : round(e.value);
parent.find(".quantityCustomizer").each(function () {
const node = $(this);
const materialID = node.data("materialid");
const mixID = node.data("mixid");
const quantity = round(convertMlToUnit(node.data("quantityml")));
const p = parent.find(`.calculation[data-materialid='${materialID}'][data-mixid='${mixID}']`);
const totalQuantity = round(lastQuantity + parseFloat(quantity));
p.data({
quantity: quantity,
totalQuantity: totalQuantity
});
const p = parent.querySelector(`.calculation[data-materialid='${materialID}'][data-mixid='${mixID}']`);
let totalQuantity = round(lastQuantity + parseFloat(quantity));
p.dataset.quantity = quantity;
p.dataset.totalQuantity = totalQuantity;
p.innerHTML = `<span>+${quantity}</span> (${totalQuantity})`;
p.html(`<span>+${quantity}</span> (${totalQuantity})`);
lastQuantity = totalQuantity;
});
const totalQuantityCustomizer = parent.querySelector(".totalQuantityCustomizer");
totalQuantityCustomizer.value = lastQuantity;
totalQuantityCustomizer.defaultValue = lastQuantity;
if (computeTotal) {
const totalQuantityCustomizer = parent.find(".totalQuantityCustomizer");
totalQuantityCustomizer.val(lastQuantity);
totalQuantityCustomizer.data({
defaultvalue: lastQuantity
});
}
}
function changeCalculations() {
document.querySelectorAll(".calculation").forEach(e => {
e.dataset.quantity = convertMlToUnit(e.dataset.quantity);
e.dataset.totalQuantity = convertMlToUnit(e.dataset.quantity);
const node = $(".calculation");
const initialQuantity = node.data("quantity");
node.each(function () {
node.data({
quantity: convertMlToUnit(initialQuantity),
totalQuantity: convertMlToUnit(initialQuantity)
});
});
document.querySelectorAll(".mix").forEach(e => doCalculations(e));
$(".mix").each(e => doCalculations(e));
}
startHovering = () => document.querySelector(".imagesContainer").classList.add("hovering");
endHovering = () => document.querySelector(".imagesContainer").classList.remove("hovering");
startHovering = () => $(".imagesContainer").addClass("hovering");
endHovering = () => $(".imagesContainer").removeClass("hovering");
/*]]>*/
</script>
<script th:include="fragments.html :: printStrings"></script>
<script type="module">
import * as bpac from "/js/bpac.js";
/*<![CDATA[*/
const dataFolder = "/lbx";
const labelPath = dataFolder + "/Couleur.lbx";
console.log(labelPath);
import {PtouchPrinter} from "/js/ptouchPrint.js";
const recipeCode = "[[${recipe.recipeCode}]]";
const banner = "[[${recipe.company.companyName}]]";
const baseMaterial = "[[${recipe.getBase().materialCode}]]";
const description = "[[${recipe.recipeDescription}]]";
async function print(mix) {
if (!document.querySelector(".bpac-extension-installed")) {
console.log("No extension");
async function printMix(printButton) {
const mixContainer = $(printButton).parents(".mixContainer");
const allBases = $(mixContainer).find(".materialRow[data-materialtypename='[[${T(dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType).BASE_MATERIAL_TYPE_NAME}]]']");
console.log(allBases.length + " bases trouvées");
if (allBases.length <= 0) {
showMessage(errorMsg, noBaseError);
return;
}
const baseName = allBases.eq(0).find(".materialCodeColumn").text();
try {
const objDoc = bpac.IDocument;
await objDoc.Open("http://localhost:9090/lbx/Couleur.lbx");
const objColor = await objDoc.GetObject("color");
objColor.Text = recipeCode;
const objBanner = await objDoc.GetObject("banner");
objBanner.Text = banner;
const objBarcode = await objDoc.GetObject("color_barcode");
objBarcode.Text = recipeCode;
objDoc.StartPrint("", 0);
objDoc.PrintOut(1, 0);
objDoc.EndPrint();
objDoc.Close();
// } else {
// console.log("Can't open document");
// }
} catch (e) {
console.log(e);
const printer = new PtouchPrinter({
template: "Couleur",
lines: [
{name: "color", value: recipeCode},
{name: "banner", value: banner},
{name: "base", value: baseName},
{name: "description", value: description}
]
});
const errorCode = await printer.print();
switch (errorCode) {
case -1:
showMessage(errorMsg, bpacNotInstalledError);
break;
case 99:
showMessage(errorMsg, printError);
break;
case 1:
showMessage(successMsg, printingMessage);
break;
default:
showMessage(errorMsg, generalErrorText);
break;
}
}
window.addEventListener("load", () => {
//print();
$('.printButton').on({
click: function () {
const node = $(this);
showConfirm(confirmPrintMessage, false, () => {
setTimeout(() => {
printMix(node);
}, 300);
});
}
});
/*]]>*/
</script>
</body>
</html>

View File

@ -6,7 +6,7 @@
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div id="researchBoxContainer">
@ -22,10 +22,8 @@
<form th:action="@{/recipe/remover/}" class="requireAuth-remover" method="POST">
<div class="companyTab" th:if="${!recipeMap.empty}" th:each="company : ${recipeMap.keySet()}"
th:data-companyID="${company.companyID}">
<h2 class="companyTabTitle" th:data-companyName="${company.companyName}"
th:text="${company.companyName}"></h2>
<table style="display:none" th:id="'recipes_' + ${company.companyName}" class="recipesList"
<h2 class="companyTabTitle" th:text="${company.companyName}"></h2>
<table style="display:none" th:id="'recipes_' + ${company.companyID}" class="recipesList"
th:if="${!recipeMap.get(company).empty}">
<tr>
<th th:text="#{recipe.color}"></th>
@ -55,5 +53,6 @@
<footer th:include="fragments.html :: footer(null, true)"></footer>
<script th:src="@{|${baseUrl}/js/recipeResearch.js|}"></script>
<script th:src="@{|${baseUrl}/js/recipeList.js|}"></script>
</body>
</html>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="fr" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="fragments.html :: head(#{recipe.touchup.title}, 'form')"></th:block>
<style>
.content button {
margin-top: 20px;
}
</style>
</head>
<body th:with='error = ${recipeMap.empty ? "__#{recipe.error.anyFound}__" : error}'>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header(true)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
<h1 th:text="#{recipe.touchup.title}"></h1>
<form action="#" method="post">
<div class="content">
<div class="formWrap">
<div class="formColumn">
<label for="jobNumber" th:text="#{keyword.jobNumber} + ':'"></label>
<button type="button" class="printPtouch" th:text="#{keyword.ptouch}"></button>
</div>
<div class="formColumn">
<input type="text" name="jobNumber" id="jobNumber" required/>
<button type="button" class="printPdf" th:text="#{keyword.pdf}"></button>
</div>
</div>
</div>
</form>
</section>
<!-- Fragment du pied de page -->
<footer th:include="fragments.html :: footer(null, true)"></footer>
<script>
/*<![CDATA[*/
$(() => {
const form = $("form");
$(".printPtouch").on({
click: function () {
if (!$("#jobNumber").val()) return;
form.attr({
action: "[[@{|${baseUrl}/touchup/ptouch|}]]"
});
form.submit();
}
});
$(".printPdf").on({
click: function () {
if (!$("#jobNumber").val()) return;
form.attr({
action: "[[@{|${baseUrl}/touchup/pdf|}]]"
});
form.submit();
}
});
});
/*]]>*/
</script>
</body>
</html>

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="fr" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="fragments.html :: head(#{recipe.touchup.title}, null)"></th:block>
</head>
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header(true)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
</section>
<!-- Fragment du pied de page -->
<footer th:include="fragments.html :: footer(null, true)"></footer>
<script th:include="fragments.html :: printStrings"></script>
<script type="module">
/*<![CDATA[*/
import {PtouchPrinter} from "/js/ptouchPrint.js";
const jobNumber = "[[${jobNumber}]]";
$(async () => {
const printer = new PtouchPrinter({
template: "Touchup",
lines: [
{name: "job", value: jobNumber}
]
});
const errorCode = await printer.print();
switch (errorCode) {
case -1:
showMessage(errorMsg, bpacNotInstalledError, false);
break;
case 99:
showMessage(errorMsg, printError, false);
break;
case 1:
showMessage(successMsg, printingMessage, false);
break;
default:
showMessage(errorMsg, generalErrorText, false);
break;
}
setTimeout(() => {
window.location.href = referer;
}, 3000);
});
/*]]>*/
</script>
</body>
</html>

View File

@ -14,7 +14,7 @@
</head>
<body>
<!-- Fragment de l'entête -->
<header th:include="fragments.html :: header"></header>
<header th:include="fragments.html :: header(false)"></header>
<!-- Corps de la page -->
<section>
<div th:include="fragments.html :: messages"></div>
@ -34,8 +34,7 @@
document.querySelector("#markdown").innerHTML = r.data;
})
.catch(e => {
errorMsgBoxText.innerText = generalErrorText;
showElement(errorMsgBox);
showMessage(errorMsg, generalErrorText);
console.log(e);
});
});

View File

@ -3,16 +3,28 @@
* Correction d'un bug qui empêchait la suppression des mélanges.
* Correction d'un bug qui empêche les boutons supprimer de fonctionner.
* Correction d'un bug qui permettait d'envoyer les formulaires demandant des mots de passe sans donner un mot de passe valide.
* Correction d'une désynchronisation entre le nom des mélanges et leurs produits internes.
* Amélioration du style du site.
* Transition des modèles vers Lombok.
* Correction d'une désynchronisation entre le nom des mélanges et leur produit interne.
* Amélioration du style.
* Amélioration de la fluidité de la navigation.
* Transition complète des modèles vers Lombok.
### Ajouts
* +++ Ajout du support pour l'imprimante P-touch.
* Les produits dans l'inventaire sont maintenant ordonnés alphabétiquement.
* Changement de l'ordre d'un mélange.
* Ajout du support pour l'imprimante P-touch de Brother.
* L'extension b-Pac doit être installée sur le navigateur des clients.
* [Firefox](https://cre.fyloz.dev/bpac.xpi)
* [Chrome](https://chrome.google.com/webstore/detail/brother-b-pac-extension/ilpghlfadkjifilabejhhijpfphfcfhb)
* Le logiciel b-Pac doit être installé sur l'ordinateur des clients.
* [Windows](https://download.brother.com/welcome/dlfp100614/bcciw32031.msi)
* Ajout de la possibilité d'imprimer les mélanges avec P-Touch
* Ajout de la possibilité d'imprimer les étiquettes de kit de retouche avec P-Touch
* Ajout des boîtes de confirmation.
* Ajout d'un type de produit aux mélanges.
* Ajout du changement d'ordre des produits d'un mélange.
* Les produits dans l'inventaire sont maintenant ordonnés alphabétiquement.
### Dépendances
* Ajout de jQuery, début de la transition.
* Migration vers Java 11
# v1.1.3
### Corrections