Easy_solution/assets/controllers/project_controller.js

247 lines
9.3 KiB
JavaScript

import {Controller} from '@hotwired/stimulus';
import { Modal } from "bootstrap";
import {TabulatorFull as Tabulator} from 'tabulator-tables';
import {eyeIconLink, pencilIcon, TABULATOR_FR_LANG, trashIcon} from "../js/global.js";
export default class extends Controller {
static values = {
listProject : Boolean,
orgId: Number,
admin: Boolean
}
static targets = ["modal", "appList", "nameInput", "formTitle", "timestampSelect", "deletionSelect"];
connect(){
if(this.listProjectValue){
this.table();
}
this.modal = new Modal(this.modalTarget);
}
table(){
const columns = [
{title: "<b>ID</b> ", field: "id", visible: false},
{title: "<b>Nom du projet</b> ", field: "name", headerFilter: "input", widthGrow: 2, vertAlign: "middle"},
{
title: "Applications",
field: "applications",
headerSort: false,
hozAlign: "left",
formatter: (cell) => {
const apps = cell.getValue();
if (!apps || apps.length === 0) {
return "<span class='text-muted' style='font-size: 0.8rem;'>Aucune</span>";
}
// Wrap everything in a flex container to keep them on one line
const content = apps.map(app => `
<div class="me-1" title="${app.name}">
<img src="${app.logoMiniUrl}"
alt="${app.name}"
style="height: 35px; width: 35px; object-fit: contain; border-radius: 4px;">
</div>
`).join('');
return `<div class="d-flex flex-wrap align-items-center">${content}</div>`;
}
}
];
// 2. Add the conditional column if admin value is true
if (this.adminValue) {
columns.push({
title: "<b>Base de données</b>",
field: "bddName",
hozAlign: "left",
},
{
title: "<b>Actions</b>",
field: "id",
width: 120,
hozAlign: "center",
headerSort: false,
formatter: (cell) => {
const id = cell.getValue();
// Return a button that Stimulus can listen to
return `<div class="d-flex gap-2 align-content-center">
<button class="btn btn-link p-0 border-0" data-action="click->project#openEditModal"
data-id="${id}">
${pencilIcon()}</button>
<button class="btn btn-link p-0 border-0" data-action="click->project#deleteProject"
data-id="${id}"> ${trashIcon()} </button>
</div>`;
}
});
}
const tabulator = new Tabulator("#tabulator-projectListOrganization", {
langs: TABULATOR_FR_LANG,
locale: "fr",
ajaxURL: `/project/organization/data`,
ajaxConfig: "GET",
pagination: true,
paginationMode: "remote",
paginationSize: 15,
ajaxParams: {orgId: this.orgIdValue},
ajaxResponse: (url, params, response) => response,
paginationDataSent: {page: "page", size: "size"},
paginationDataReceived: {last_page: "last_page"},
ajaxSorting: true,
ajaxFiltering: true,
filterMode: "remote",
ajaxURLGenerator: function(url, config, params) {
let queryParams = new URLSearchParams();
queryParams.append('orgId', params.orgId);
queryParams.append('page', params.page || 1);
queryParams.append('size', params.size || 15);
// Add filters
if (params.filter) {
params.filter.forEach(filter => {
queryParams.append(`filter[${filter.field}]`, filter.value);
});
}
return `${url}?${queryParams.toString()}`;
},
rowHeight: 60,
layout: "fitColumns", // activate French
columns
})
}
async loadApplications() {
try {
const response = await fetch('/application/data/all');
const apps = await response.json();
this.appListTarget.innerHTML = apps.map(app => `
<div class="col-md-4 mb-3">
<div class="form-check border p-2 rounded d-flex align-items-center gap-2">
<input class="form-check-input ms-1" type="checkbox" name="applications[]" value="${app.id}" id="app_${app.id}">
<label class="form-check-label d-flex align-items-center gap-2" for="app_${app.id}">
<img src="${app.logoMiniUrl}" alt="${app.name}" style="height: 20px; width: 20px; object-fit: contain;">
${app.name}
</label>
</div>
</div>
`).join('');
} catch (error) {
this.appListTarget.innerHTML = '<div class="text-danger">Erreur de chargement.</div>';
}
}
async submitForm(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form); // This automatically picks up the 'logo' file
// 1. Validate File Format
const logoFile = formData.get('logo');
if (logoFile && logoFile.size > 0) {
const allowedTypes = ['image/jpeg', 'image/png', 'image/jpg'];
if (!allowedTypes.includes(logoFile.type)) {
alert("Format invalide. Veuillez utiliser uniquement des fichiers PNG ou JPG.");
return; // Stop submission
}
}
// 2. Prepare for Multipart sending
// Since we are using FormData, we don't need JSON.stringify or 'Content-Type': 'application/json'
// We add the extra fields to the formData object
formData.append('organizationId', this.orgIdValue);
const url = this.currentProjectId
? `/project/edit/${this.currentProjectId}/ajax`
: `/project/new/ajax`;
const response = await fetch(url, {
method: 'POST',
// IMPORTANT: Do NOT set Content-Type header when sending FormData with files
// The browser will set 'multipart/form-data' and the boundary automatically
body: formData
});
if (response.ok) {
this.modal.hide();
location.reload();
} else {
const result = await response.json();
if (response.status === 409) {
alert("Un projet avec ce nom existe déjà.");
} else {
alert(result.error || "Une erreur est survenue.");
}
}
}
async openEditModal(event) {
const projectId = event.currentTarget.dataset.id;
this.currentProjectId = projectId;
this.modal.show();
this.nameInputTarget.disabled = true;
this.formTitleTarget.textContent = "Modifier le projet";
try {
// 1. Ensure checkboxes are loaded first
await this.loadApplications();
// 2. Fetch the project data
const response = await fetch(`/project/data/${projectId}`);
const project = await response.json();
// 3. Set the name
this.nameInputTarget.value = project.name;
console.log(project);
this.timestampSelectTarget.value = project.timestampPrecision;
this.deletionSelectTarget.value = project.deletionAllowed;
// 4. Check the boxes
// We look for all checkboxes inside our appList target
const checkboxes = this.appListTarget.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(cb => {
cb.checked = project.applications.includes(cb.value);
});
} catch (error) {
console.error("Error loading project data", error);
alert("Erreur lors de la récupération des données du projet.");
}
}
// Update your openCreateModal to reset the state
openCreateModal() {
this.currentProjectId = null;
this.modal.show();
this.nameInputTarget.disabled = false;
this.nameInputTarget.value = "";
this.formTitleTarget.textContent = "Nouveau Projet";
this.loadApplications();
}
async deleteProject(event) {
const projectId = event.currentTarget.dataset.id;
if (!confirm("Êtes-vous sûr de vouloir supprimer ce projet ?")) {
return;
}
try {
const response = await fetch(`/project/delete/${projectId}/ajax`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
if (response.ok) {
location.reload();
}
}catch (error) {
console.error("Error deleting project", error);
alert("Erreur lors de la suppression du projet.");
}
}
}