import {Controller} from '@hotwired/stimulus'; import Choices from 'choices.js'; import {TabulatorFull as Tabulator} from 'tabulator-tables'; const TABULATOR_FR_LANG = { fr: { ajax: { loading: "Chargement...", error: "Erreur" }, pagination: { page_size: "Taille de page", page_title: "Afficher la page", first: "Premier", first_title: "Première page", last: "Dernier", last_title: "Dernière page", prev: "Précédent", prev_title: "Page précédente", next: "Suivant", next_title: "Page suivante", all: "Tout", counter: { showing: "Affiche", of: "de", rows: "lignes", pages: "pages" }, }, headerFilters: { default: "Filtrer la colonne...", columns: {} }, data: { loading: "Chargement des données...", error: "Erreur de chargement des données" }, groups: { item: "élément", items: "éléments" }, }, }; export default class extends Controller { static values = { rolesArray: Array, selectedRoleIds: Array, id: Number, aws: String, list: Boolean, listOrganization: Boolean, new: Boolean, admin: Boolean, listSmall: Boolean, statut: Boolean, orgId: Number } static targets = ["select"]; connect() { this.roleSelect(); if (this.listValue) { this.table(); } if (this.newValue) { this.tableSmall(); } if (this.adminValue) { this.tableSmallAdmin(); } if (this.listOrganizationValue) { this.tableOrganization() } } roleSelect() { if (this.hasSelectTarget) { const choicesData = this.rolesArrayValue.map(role => ({ value: role.id, label: role.name, selected: this.selectedRoleIdsValue.includes(role.id) })); new Choices(this.selectTarget, { choices: choicesData, removeItemButton: true, placeholder: true, placeholderValue: 'Ajouter un ou plusieurs rôles', }); } } // TODO: vérifier le style des header filter et vertAlign/hozalign table() { const columns = [ { title: "", field: "isConnected", width: 40, // small column hozAlign: "center", vertAlign: "middle", headerSort: false, tooltip: false, formatter: (cell) => { const online = !!cell.getValue(); const color = online ? "#80F20E" : "#E42E31"; // green/red return ``; }, // Optional: for accessibility formatterPrint: (cell) => (cell.getValue() ? "online" : "offline"), formatterClipboard: (cell) => (cell.getValue() ? "online" : "offline"), }, { title: "Profil", field: "pictureUrl", width: 80, hozAlign: "center", headerSort: false, formatter: (cell) => { const data = cell.getRow().getData(); const url = cell.getValue(); const first = (s) => (typeof s === "string" && s.length ? s.trim()[0].toUpperCase() : ""); const initials = `${first(data.name)}${first(data.prenom)}`; const wrapper = document.createElement("div"); wrapper.className = "avatar-wrapper"; // same size for both cases wrapper.style.width = "40px"; wrapper.style.height = "40px"; wrapper.style.display = "flex"; wrapper.style.alignItems = "center"; wrapper.style.justifyContent = "center"; wrapper.style.borderRadius = "50%"; wrapper.style.overflow = "hidden"; // ensure image clips to circle if (!url) { wrapper.style.background = "#6c757d"; // gray background const span = document.createElement("span"); span.className = "avatar-initials"; span.style.color = "#fff"; span.style.fontWeight = "600"; span.style.fontSize = "14px"; span.textContent = initials || "•"; wrapper.appendChild(span); return wrapper; } // Image case: make it fill the same wrapper const img = document.createElement("img"); img.src = `${this.awsValue || ""}${url}`; img.alt = initials || "avatar"; img.style.width = "100%"; img.style.height = "100%"; img.style.objectFit = "cover"; // keep aspect and cover circle wrapper.appendChild(img); // Optional: fallback if image fails img.addEventListener("error", () => { wrapper.innerHTML = ""; wrapper.style.background = "#6c757d"; const span = document.createElement("span"); span.className = "avatar-initials"; span.style.color = "#fff"; span.style.fontWeight = "600"; span.style.fontSize = "12px"; span.textContent = initials || "•"; wrapper.appendChild(span); }); return wrapper; }, }, {title: "Nom", field: "name", headerFilter: "input", widthGrow: 2, vertAlign: "middle"}, {title: "Prénom", field: "prenom", headerFilter: "input", widthGrow: 2, vertAlign: "middle"}, {title: "Email", field: "email", headerFilter: "input", widthGrow: 3, vertAlign: "middle"}, { title: "Actions", field: "showUrl", width: 130, vertAlign: "middle", headerSort: false, formatter: (cell) => { const url = cell.getValue(); if (!url) return ''; // You can get other row data like ID for delete endpoint if needed const rowData = cell.getRow().getData(); const deleteId = rowData.id; return `
`; }, cellClick: function (e, cell) { const target = e.target.closest('a'); if (target && target.classList.contains('delete-user')) { e.preventDefault(); const userId = target.getAttribute('data-id'); if (confirm('Voulez-vous vraiment supprimer cet utilisateur ?')) { // Example delete call (replace URL as needed) fetch(`/user/delete/${userId}`, { method: 'POST', }) .then(response => { if (response.ok) { alert('Utilisateur supprimé'); cell.getRow().delete(); // Remove row from table } else { alert('Erreur lors de la suppression'); } }) .catch(() => alert('Erreur lors de la suppression')); } } } }]; const tabulator = new Tabulator("#tabulator-userList", { langs: TABULATOR_FR_LANG, locale: "fr", //'en' for English, 'fr' for French (en is default, no need to include it) ajaxURL: "/user/data", ajaxConfig: "GET", pagination: true, paginationMode: "remote", paginationSize: 10, ajaxResponse: (url, params, response) => response, paginationDataSent: {page: "page", size: "size"}, paginationDataReceived: {last_page: "last_page"}, ajaxSorting: true, ajaxFiltering: true, rowHeight: 60, layout: "fitColumns", // activate French columns }); }; onSelectChange(row, newValue) { const data = row.getData(); console.log("Change select" + data); }; tableSmall() { const columns = [ { title: "Profil", field: "pictureUrl", width: 80, hozAlign: "center", headerSort: false, formatter: (cell) => { const data = cell.getRow().getData(); const url = cell.getValue(); const first = (s) => (typeof s === "string" && s.length ? s.trim()[0].toUpperCase() : ""); const initials = `${data.initials}`; const wrapper = document.createElement("div"); wrapper.className = "avatar-wrapper"; // same size for both cases wrapper.style.width = "40px"; wrapper.style.height = "40px"; wrapper.style.display = "flex"; wrapper.style.alignItems = "center"; wrapper.style.justifyContent = "center"; wrapper.style.borderRadius = "50%"; wrapper.style.overflow = "hidden"; // ensure image clips to circle if (!url) { wrapper.style.background = "#6c757d"; // gray background const span = document.createElement("span"); span.className = "avatar-initials"; span.style.color = "#fff"; span.style.fontWeight = "600"; span.style.fontSize = "14px"; span.textContent = initials || "•"; wrapper.appendChild(span); return wrapper; } // Image case: make it fill the same wrapper const img = document.createElement("img"); img.src = `${this.awsValue || ""}${url}`; img.alt = initials || "avatar"; img.style.width = "100%"; img.style.height = "100%"; img.style.objectFit = "cover"; // keep aspect and cover circle wrapper.appendChild(img); // Optional: fallback if image fails img.addEventListener("error", () => { wrapper.innerHTML = ""; wrapper.style.background = "#6c757d"; const span = document.createElement("span"); span.className = "avatar-initials"; span.style.color = "#fff"; span.style.fontWeight = "600"; span.style.fontSize = "12px"; span.textContent = initials || "•"; wrapper.appendChild(span); }); return wrapper; }, }, {title: "Email", field: "email", widthGrow: 3, vertAlign: "middle"}, { title: "Actions", field: "showUrl", hozAlign: "center", width: 100, vertAlign: "middle", headerSort: false, formatter: (cell) => { const url = cell.getValue(); if (url) { return ` `; } return ''; } } ]; const tabulator = new Tabulator("#tabulator-userListSmall", { locale: "fr", //'en' for English, 'fr' for French (en is default, no need to include it) ajaxURL: "/user/data/new", ajaxConfig: "GET", pagination: false, paginationMode: "remote", // paginationSize: 5, ajaxParams: {orgId: this.orgIdValue}, langs: TABULATOR_FR_LANG, ajaxResponse: (url, params, response) => response.data, // paginationDataSent: {page: "page", size: "size"}, // paginationDataReceived: {last_page: "last_page"}, // ajaxSorting: true, // ajaxFiltering: true, rowHeight: 60, layout: "fitColumns", // activate French columns }); } tableSmallAdmin() { const columns = [ { title: "Profil", field: "pictureUrl", width: 80, hozAlign: "center", headerSort: false, formatter: (cell) => { const data = cell.getRow().getData(); const url = cell.getValue(); const first = (s) => (typeof s === "string" && s.length ? s.trim()[0].toUpperCase() : ""); const initials = `${data.initials}`; const wrapper = document.createElement("div"); wrapper.className = "avatar-wrapper"; // same size for both cases wrapper.style.width = "40px"; wrapper.style.height = "40px"; wrapper.style.display = "flex"; wrapper.style.alignItems = "center"; wrapper.style.justifyContent = "center"; wrapper.style.borderRadius = "50%"; wrapper.style.overflow = "hidden"; // ensure image clips to circle if (!url) { wrapper.style.background = "#6c757d"; // gray background const span = document.createElement("span"); span.className = "avatar-initials"; span.style.color = "#fff"; span.style.fontWeight = "600"; span.style.fontSize = "14px"; span.textContent = initials || "•"; wrapper.appendChild(span); return wrapper; } // Image case: make it fill the same wrapper const img = document.createElement("img"); img.src = `${this.awsValue || ""}${url}`; img.alt = initials || "avatar"; img.style.width = "100%"; img.style.height = "100%"; img.style.objectFit = "cover"; // keep aspect and cover circle wrapper.appendChild(img); // Optional: fallback if image fails img.addEventListener("error", () => { wrapper.innerHTML = ""; wrapper.style.background = "#6c757d"; const span = document.createElement("span"); span.className = "avatar-initials"; span.style.color = "#fff"; span.style.fontWeight = "600"; span.style.fontSize = "12px"; span.textContent = initials || "•"; wrapper.appendChild(span); }); return wrapper; }, }, {title: "Email", field: "email", widthGrow: 3, vertAlign: "middle"}, { title: "Actions", field: "showUrl", hozAlign: "center", width: 100, vertAlign: "middle", headerSort: false, formatter: (cell) => { const url = cell.getValue(); if (url) { return ` `; } return ''; } } ]; const tabulator = new Tabulator("#tabulator-userListSmallAdmin", { locale: "fr", //'en' for English, 'fr' for French (en is default, no need to include it) ajaxURL: "/user/data/admin", ajaxConfig: "GET", pagination: false, paginationMode: "remote", // paginationSize: 5, ajaxParams: {orgId: this.orgIdValue}, langs: TABULATOR_FR_LANG, ajaxResponse: (url, params, response) => response.data, // paginationDataSent: {page: "page", size: "size"}, // paginationDataReceived: {last_page: "last_page"}, // ajaxSorting: true, // ajaxFiltering: true, rowHeight: 60, layout: "fitColumns", // activate French columns }); } tableOrganization() { const columns = [ { title: "", field: "isConnected", width: 40, // small column hozAlign: "center", vertAlign: "middle", headerSort: false, tooltip: false, formatter: (cell) => { const online = !!cell.getValue(); const color = online ? "#80F20E" : "#E42E31"; // green/red return ``; }, // Optional: for accessibility formatterPrint: (cell) => (cell.getValue() ? "online" : "offline"), formatterClipboard: (cell) => (cell.getValue() ? "online" : "offline"), }, { title: "Profil", field: "pictureUrl", width: 80, hozAlign: "center", headerSort: false, formatter: (cell) => { const data = cell.getRow().getData(); const url = cell.getValue(); const first = (s) => (typeof s === "string" && s.length ? s.trim()[0].toUpperCase() : ""); const initials = `${first(data.name)}${first(data.prenom)}`; const wrapper = document.createElement("div"); wrapper.className = "avatar-wrapper"; // same size for both cases wrapper.style.width = "40px"; wrapper.style.height = "40px"; wrapper.style.display = "flex"; wrapper.style.alignItems = "center"; wrapper.style.justifyContent = "center"; wrapper.style.borderRadius = "50%"; wrapper.style.overflow = "hidden"; // ensure image clips to circle if (!url) { wrapper.style.background = "#6c757d"; // gray background const span = document.createElement("span"); span.className = "avatar-initials"; span.style.color = "#fff"; span.style.fontWeight = "600"; span.style.fontSize = "14px"; span.textContent = initials || "•"; wrapper.appendChild(span); return wrapper; } // Image case: make it fill the same wrapper const img = document.createElement("img"); img.src = `${this.awsValue || ""}${url}`; img.alt = initials || "avatar"; img.style.width = "100%"; img.style.height = "100%"; img.style.objectFit = "cover"; // keep aspect and cover circle wrapper.appendChild(img); // Optional: fallback if image fails img.addEventListener("error", () => { wrapper.innerHTML = ""; wrapper.style.background = "#6c757d"; const span = document.createElement("span"); span.className = "avatar-initials"; span.style.color = "#fff"; span.style.fontWeight = "600"; span.style.fontSize = "12px"; span.textContent = initials || "•"; wrapper.appendChild(span); }); return wrapper; }, }, {title: "Nom", field: "name", headerFilter: "input", widthGrow: 2, vertAlign: "middle"}, {title: "Prénom", field: "prenom", headerFilter: "input", widthGrow: 2, vertAlign: "middle"}, {title: "Email", field: "email", headerFilter: "input", widthGrow: 3, vertAlign: "middle"}, { title: "Actions", field: "showUrl", width: 130, vertAlign: "middle", headerSort: false, formatter: (cell) => { const url = cell.getValue(); if (!url) return ''; // You can get other row data like ID for delete endpoint if needed const rowData = cell.getRow().getData(); const deleteId = rowData.id; const orgId = this.orgIdValue return ` `; }, cellClick: function (e, cell) { const target = e.target.closest('a'); if (target?.classList.contains('delete-user')) { e.preventDefault(); const userId = target.getAttribute('data-id'); if (confirm('Voulez-vous vraiment désactiver cet utilisateur ?')) { const formData = new FormData(); formData.append('organizationId', target.getAttribute('data-org-id')); fetch(`/user/organization/deactivate/${userId}`, { method: 'POST', body: formData, headers: {'X-Requested-With': 'XMLHttpRequest'} }) .then(async (response) => { if (response.ok) { cell.getRow().delete(); } else { const text = await response.text(); alert('Erreur lors de la suppression: ' + text); } }) .catch(() => alert('Erreur lors de la suppression')); } } } }]; // if (this.statutValue) { // columns.push( // { // title: "Statut", field: "role", // or any field you want // headerSort: false, // hozAlign: "center", // vertAlign: "middle", // formatter: (cell) => { // const row = cell.getRow(); // const current = cell.getValue() ?? ""; // // const select = document.createElement("select"); // select.className = "table-select-action"; // // Options // [ // {value: "", label: "Choisir..."}, // {value: "viewer", label: "Viewer"}, // {value: "editor", label: "Editor"}, // {value: "admin", label: "Admin"}, // ].forEach(opt => { // const o = document.createElement("option"); // o.value = opt.value; // o.textContent = opt.label; // if (opt.value === current) o.selected = true; // select.appendChild(o); // }); // // // Hook change // select.addEventListener("change", (e) => { // this.onSelectChange(row, e.target.value); // }); // // // Return a DOM node from a formatter → Tabulator will mount it // return select; // }, // // Optional: provide text for clipboard/print // formatterClipboard: cell => cell.getValue(), // formatterPrint: cell => cell.getValue(), // }, // ) // } const tabulator = new Tabulator("#tabulator-userListOrganization", { langs: TABULATOR_FR_LANG, locale: "fr", ajaxURL: "/user/data/organization", ajaxConfig: "GET", ajaxParams: {orgId: this.orgIdValue}, pagination: true, paginationMode: "remote", paginationSize: 10, ajaxResponse: (url, params, response) => response, paginationDataSent: {page: "page", size: "size"}, paginationDataReceived: {last_page: "last_page"}, ajaxSorting: true, ajaxFiltering: true, rowHeight: 60, layout: "fitColumns", // activate French columns }); }; }