Create Tree Type
-
-
\ No newline at end of file
+
diff --git a/app/src/app/Views/Admin/TreeType/Edit.php b/app/src/app/Views/Admin/TreeType/Edit.php
index 5fd08736..668cb7f6 100644
--- a/app/src/app/Views/Admin/TreeType/Edit.php
+++ b/app/src/app/Views/Admin/TreeType/Edit.php
@@ -11,7 +11,8 @@ class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg
\ No newline at end of file
+
diff --git a/app/src/public/assets/js/app.js b/app/src/public/assets/js/app.js
index 5b924882..1757403c 100644
--- a/app/src/public/assets/js/app.js
+++ b/app/src/public/assets/js/app.js
@@ -1,58 +1,265 @@
-function validateFormTreeType(event) {
- // Contenidor per a tots els errors
- const errorMessagesDiv = document.getElementById("errorMessages");
-
- // Netejar errors anteriors
- errorMessagesDiv.innerHTML = ""; // Eliminar contingut d'errors
- errorMessagesDiv.classList.add("hidden"); // Amagar inicialment
-
- let hasError = false; // Controlar si hi ha errors
- let errorMessage = ""; // Acumular missatges d'error
-
- // Expressió regular per validar noms (només lletres i espais)
- const namePattern = /^[a-zA-Z\s]+$/;
-
- // Validació de cada camp
- const family = document.getElementById("family").value.trim();
- const genus = document.getElementById("genus").value.trim();
- const species = document.getElementById("species").value.trim();
-
- if (!family) {
- errorMessage += "- La família és obligatòria.
";
- hasError = true;
- } else if (!namePattern.test(family)) {
- errorMessage +=
- "- La família només pot contenir lletres i espais.
";
- hasError = true;
- }
+const errorMessagesDiv = document.getElementById("errorMessages");
- if (!genus) {
- errorMessage += "- El gènere és obligatori.
";
- hasError = true;
- } else if (!namePattern.test(genus)) {
- errorMessage +=
- "- El gènere només pot contenir lletres i espais.
";
- hasError = true;
- }
+const regexPatterns = [
+ {
+ name: "lettersAndSpaces",
+ regex: /^[a-zA-Z\s]+$/,
+ explanation: "El camp només pot contenir lletres i espais.",
+ },
+ {
+ name: "numbersOnly",
+ regex: /^\d+$/,
+ explanation: "Només s'accepten números.",
+ },
+];
+
+function createErrorElement(message) {
+ const errorMsg = document.createElement("p");
+ errorMsg.textContent = message;
+ errorMessagesDiv.appendChild(errorMsg);
+ return false;
+}
+
+/**
+ * Validate if a field is empty and return an error message if it is
+ */
+function validateEmptyField(value, fieldName) {
+ return !value
+ ? createErrorElement(`${fieldName}: El camp no pot estar buit.`)
+ : true;
+}
- if (!species) {
- errorMessage += "- L'espècie és obligatòria.
";
- hasError = true;
- } else if (!namePattern.test(species)) {
- errorMessage +=
- "- L'espècie només pot contenir lletres i espais.
";
- hasError = true;
+/**
+ * Validate a field based on a regex pattern name and return an error message if invalid
+ */
+function validateField(value, regexName, fieldName) {
+ const pattern = regexPatterns.find((pattern) => pattern.name === regexName);
+ const explanation = pattern ? pattern.explanation : "El valor no és vàlid.";
+ return pattern && !pattern.regex.test(value)
+ ? createErrorElement(`${fieldName}: ${explanation}`)
+ : true;
+}
+
+/**
+ * Validate that one date is not after another date
+ */
+function dateCannotBeAfter(startDate, endDate, fieldName) {
+ return new Date(startDate) > new Date(endDate)
+ ? createErrorElement(
+ `${fieldName}: La data de finalització no pot ser anterior a la data d'inici.`
+ )
+ : true;
+}
+
+/**
+ * Validate that a field contains a positive integer
+ */
+function validatePositiveInteger(value, fieldName) {
+ const pattern = /^[+]?\d+([eE][+]?\d+)?$/;
+ return pattern.test(value) && parseFloat(value) > 0
+ ? true
+ : createErrorElement(
+ `${fieldName}: El valor ha de ser un número positiu.`
+ );
+}
+
+/**
+ * Validate that a field does not exceed the maximum value
+ */
+function validateMaxValue(value, max, fieldName) {
+ return parseFloat(value) <= max
+ ? true
+ : createErrorElement(
+ `${fieldName}: El valor no pot ser superior a ${max}.`
+ );
+}
+
+function getFieldName(fieldId) {
+ const label = document.querySelector(`label[for="${fieldId}"]`);
+ return label ? label.textContent.trim() : fieldId;
+}
+
+function validateForm(event, fields) {
+ errorMessagesDiv.innerHTML = "";
+ errorMessagesDiv.classList.add("hidden");
+
+ let isValid = true;
+ fields.forEach((field) => {
+ const element = document.getElementById(field.id);
+ const value = element ? element.value.trim() : null;
+ const fieldName = getFieldName(field.id);
+ const max = element ? parseFloat(element.max) : null;
+
+ for (const check of field.checks) {
+ let validation;
+ switch (check.type) {
+ case "empty":
+ validation = validateEmptyField(value, fieldName);
+ break;
+ case "regex":
+ validation = validateField(
+ value,
+ check.regexName,
+ fieldName
+ );
+ break;
+ case "daterange":
+ const startDate = document
+ .getElementById(check.startDateId)
+ .value.trim();
+ const endDate = document
+ .getElementById(check.endDateId)
+ .value.trim();
+ validation = dateCannotBeAfter(
+ startDate,
+ endDate,
+ fieldName
+ );
+ break;
+ case "positiveInteger":
+ validation = validatePositiveInteger(value, fieldName);
+ break;
+ case "maxValue":
+ validation = validateMaxValue(value, max, fieldName);
+ break;
+ default:
+ validation = true;
+ }
+ if (!validation) {
+ isValid = false;
+ break;
+ }
+ }
+ });
+
+ if (!isValid) {
+ event.preventDefault();
+ errorMessagesDiv.classList.remove("hidden");
}
+}
- // Si hi ha errors, mostrar el contenidor i prevenir l'enviament del formulari
- if (hasError) {
- errorMessagesDiv.innerHTML = errorMessage; // Inserir errors al quadre
- errorMessagesDiv.classList.remove("hidden"); // Mostrar el contenidor
- event.preventDefault(); // Evitar l'enviament del formulari
+function addFormValidation(formId, fields) {
+ const form = document.getElementById(formId);
+ if (form) {
+ form.addEventListener("submit", (event) => {
+ validateForm(event, fields);
+ });
}
}
-// Assignar l'esdeveniment submit al formulari
-document
- .getElementById("treeForm")
- .addEventListener("submit", validateFormTreeType);
+/**
+ * Event listeners for form validation
+ */
+addFormValidation("contractForm", [
+ {
+ id: "name",
+ checks: [
+ {
+ type: "empty",
+ },
+ {
+ type: "regex",
+ regexName: "lettersAndSpaces",
+ },
+ ],
+ },
+ {
+ id: "start_date",
+ checks: [
+ {
+ type: "empty",
+ },
+ ],
+ },
+ {
+ id: "end_date",
+ checks: [
+ {
+ type: "daterange",
+ startDateId: "start_date",
+ endDateId: "end_date",
+ },
+ ],
+ },
+ {
+ id: "invoice_proposed",
+ checks: [
+ {
+ type: "empty",
+ },
+ {
+ type: "positiveInteger",
+ },
+ {
+ type: "maxValue",
+ },
+ ],
+ },
+ {
+ id: "invoice_agreed",
+ checks: [
+ {
+ type: "empty",
+ },
+ {
+ type: "positiveInteger",
+ },
+ {
+ type: "maxValue",
+ },
+ ],
+ },
+ {
+ id: "invoice_paid",
+ checks: [
+ {
+ type: "empty",
+ },
+ {
+ type: "positiveInteger",
+ },
+ {
+ type: "maxValue",
+ },
+ ],
+ },
+]);
+
+addFormValidation("treeTypeForm", [
+ {
+ id: "family",
+ checks: [
+ {
+ type: "empty",
+ },
+ {
+ type: "regex",
+ regexName: "lettersAndSpaces",
+ },
+ ],
+ },
+ {
+ id: "genus",
+ checks: [
+ {
+ type: "empty",
+ },
+ {
+ type: "regex",
+ regexName: "lettersAndSpaces",
+ },
+ ],
+ },
+ {
+ id: "species",
+ checks: [
+ {
+ type: "empty",
+ },
+ {
+ type: "regex",
+ regexName: "lettersAndSpaces",
+ },
+ ],
+ },
+]);
diff --git a/database/start-scripts/0-init.sql b/database/start-scripts/0-init.sql
index 36848995..3052b8eb 100644
--- a/database/start-scripts/0-init.sql
+++ b/database/start-scripts/0-init.sql
@@ -29,8 +29,8 @@ create table users (
create table contracts (
id int auto_increment primary key,
name varchar(255) not null,
- start_date timestamp not null,
- end_date timestamp,
+ start_date date not null,
+ end_date date,
invoice_proposed float,
invoice_agreed float,
invoice_paid float,