diff --git a/assets/controllers/organization_controller.js b/assets/controllers/organization_controller.js
index 225c662..a1fc967 100644
--- a/assets/controllers/organization_controller.js
+++ b/assets/controllers/organization_controller.js
@@ -18,7 +18,7 @@ export default class extends Controller {
this.loadActivities();
setInterval(() => {
this.loadActivities();
- }, 60000); // Refresh every 60 seconds
+ }, 300000); // Refresh every 5 minutes
}
if (this.tableValue && this.sadminValue) {
this.table();
diff --git a/assets/controllers/project_controller.js b/assets/controllers/project_controller.js
new file mode 100644
index 0000000..bcf2e12
--- /dev/null
+++ b/assets/controllers/project_controller.js
@@ -0,0 +1,235 @@
+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"];
+ connect(){
+ if(this.listProjectValue){
+ this.table();
+ }
+ this.modal = new Modal(this.modalTarget);
+ }
+
+ table(){
+ const columns = [
+ {title: "ID ", field: "id", visible: false},
+ {title: "Nom du projet ", 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 "Aucune";
+ }
+
+ // Wrap everything in a flex container to keep them on one line
+ const content = apps.map(app => `
+
+

+
+ `).join('');
+
+ return `${content}
`;
+ }
+ }
+ ];
+ // 2. Add the conditional column if admin value is true
+ if (this.adminValue) {
+ columns.push({
+ title: "Base de données",
+ field: "bddName",
+ hozAlign: "left",
+ },
+ {
+ title: "Actions",
+ field: "id",
+ width: 120,
+ hozAlign: "center",
+ headerSort: false,
+ formatter: (cell) => {
+ const id = cell.getValue();
+ // Return a button that Stimulus can listen to
+ return `
+
+
+
`;
+ }
+ });
+ }
+
+ 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 => `
+
+ `).join('');
+ } catch (error) {
+ this.appListTarget.innerHTML = 'Erreur de chargement.
';
+ }
+ }
+
+ async submitForm(event) {
+ event.preventDefault();
+ const formData = new FormData(event.target);
+
+ const payload = {
+ organizationId: this.orgIdValue,
+ applications: formData.getAll('applications[]')
+ };
+
+ // Only include name if it wasn't disabled (new projects)
+ if (!this.nameInputTarget.disabled) {
+ payload.name = formData.get('name');
+ }
+
+ const url = this.currentProjectId
+ ? `/project/edit/${this.currentProjectId}/ajax`
+ : `/project/new/ajax`;
+
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+
+ if (response.ok) {
+ this.modal.hide();
+ // Use Tabulator's setData() instead of reload() for better UX if possible
+ location.reload();
+ } else {
+ if (response.status === 409) {
+ alert("Un projet avec ce nom existe déjà. Veuillez choisir un nom différent.");
+ }
+ }
+ }
+
+ 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;
+
+ // 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.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/assets/js/global.js b/assets/js/global.js
index 784bef5..80d5c79 100644
--- a/assets/js/global.js
+++ b/assets/js/global.js
@@ -32,6 +32,25 @@ export function eyeIconLink(url) {
`;
}
+export function pencilIcon() {
+ return `
+
+
+
+ `
+}
+
+export function trashIcon(url) {
+ return `
+
+
+
+ `
+}
+
+
export function deactivateUserIcon() {
return `