import m from "mithril" import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2" import {Select, iSelect, iSelectOption} from "./widgets/select" import {ModalView, ModalType, iModalView} from "./widgets/Modal" import {gridMountpoints} from "./mainlayout" import {iProjektUser, fetchProjektUsers, addProjektUserREST, removeProjektUserREST} from "../models/projekt_user" import {iProjekt, fetchProjektById} from "../models/projekt" import {iUser} from "./auth/models/users" import {showServerError} from "./auth/authmessages" /** * Liste der einem Projekt zugeordneten Nutzer (Sichtbarkeit). * * Der Liste wird die projekt_id über die Route-Params übergeben. Sie zeigt * alle Nutzer, die das Projekt sehen dürfen. Der AppAdmin (und SysAdmin) * kann Nutzer über ein Modal mit Select-Widget hinzufügen und über einen * Entfernen-Button pro Zeile wieder wegnehmen. */ export const projektuserliste: m.ClosureComponent = () => { let treenodes: iTreenode[] = [] let selectedNode: iTreenode | null = null let projektId: number = -1 let projektName: string = "" let projektUsers: iProjektUser[] = [] // Alle Nutzer aus der Auth-DB, für das Hinzufügen-Modal. let allUsers: iUser[] = [] const handleRowMarked = (node: iTreenode) => { selectedNode = node } /** * Lädt die zugeordneten Nutzer vom Server und aktualisiert die Treenodes. */ const loadProjektUsers = () => { fetchProjektUsers(projektId).then((result) => { projektUsers = result treenodes = result.map((pu: iProjektUser) => new Treenode(pu, true, [])) }).catch((e) => showServerError(e)) } /** * Lädt alle Nutzer aus der Auth-DB (für das Hinzufügen-Modal). */ const loadAllUsers = () => { m.request({ method: "GET", url: "/auth/getusers" }).then((result) => { allUsers = result as iUser[] }).catch((e) => showServerError(e)) } /** * Entfernt einen Nutzer aus dem Projekt. Wird beim Klick auf den * Entfernen-Button in der Zeile aufgerufen. */ const removeUser = (projektUser: iProjektUser, e: Event) => { e.stopPropagation() removeProjektUserREST(projektId, projektUser.user_id).then(() => { loadProjektUsers() }).catch((e) => showServerError(e)) } const columnmapper: (node: iTreenode) => any[] = (node: iTreenode) => { let pu: iProjektUser = node.nodevalue return [ m("div", m("strong", pu.userid)), m("span", pu.username), m("span", pu.email), m("div", {style: {display: 'flex', gap: '0', 'white-space': 'nowrap'}}, m("button.w3-button.w3-small.w3-red", { title: "Nutzer entfernen", style: {padding: '0 4px'}, onclick: (e: Event) => removeUser(pu, e) }, m("i.material-icons", "person_remove")) ) ] } const filterFunction = (node: iTreenode, searchvalue: string): boolean => { let pu: iProjektUser = node.nodevalue let searchLower = searchvalue.toLowerCase() return (pu.userid || "").toLowerCase().includes(searchLower) || (pu.username || "").toLowerCase().includes(searchLower) || (pu.email || "").toLowerCase().includes(searchLower) } /** * Öffnet das Modal zum Hinzufügen eines Nutzers. Die verfügbaren * Nutzer werden clientseitig gegen die bereits zugeordneten gefiltert. */ const openAddModal = () => { // Alle Nutzer laden, falls noch nicht geschehen. if (allUsers.length === 0) { loadAllUsers() } m.mount(gridMountpoints.modalhook, addUserModal) } /** * Modal-Komponente zur Auswahl eines Nutzers, der dem Projekt * zugeordnet werden soll. Verwendet das Select-Widget für die * Suche/Auswahl. Bereits zugeordnete Nutzer werden ausgeblendet. */ const addUserModal: m.ClosureComponent = () => { let selectedUserId: number = -1 // Verfügbare Nutzer: alle, die noch nicht zugeordnet sind. const getAvailableOptions = (): iSelectOption[] => { const assignedIds = new Set(projektUsers.map((pu) => pu.user_id)) return allUsers .filter((u: iUser) => !assignedIds.has(u.id)) .map((u: iUser) => ({ value: u.id, label: `${u.userid} (${u.username})` } as iSelectOption)) } const closeModal = () => { m.mount(gridMountpoints.modalhook, null) } const confirmAdd = () => { if (selectedUserId > 0) { addProjektUserREST(projektId, selectedUserId).then(() => { closeModal() loadProjektUsers() }).catch((e) => { showServerError(e) closeModal() }) } } return { view: (vnode) => { const options = getAvailableOptions() return m(ModalView, { title: "Nutzer zuordnen", type: ModalType.MESSAGE, width: '500px' } as iModalView, m("div.w3-padding-16", m("p", "Wählen Sie einen Nutzer aus, der dem Projekt \"" + projektName + "\" zugeordnet werden soll:"), m("label", "Nutzer"), m(Select, { name: "user_id", value: selectedUserId, options: options, required: true, onchanged: (value: any) => { selectedUserId = value } } as iSelect), m("div.w3-bar.w3-padding-16", m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, onclick: confirmAdd}, "Zuordnen"), m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, onclick: closeModal}, "Abbrechen") ) ) ) } } } return { oninit: (vnode) => { const routeParams = m.route.param() projektId = routeParams && routeParams.projekt_id ? parseInt(routeParams.projekt_id) : -1 projektName = routeParams && routeParams.projekt_name ? routeParams.projekt_name : "" // Projektname asynchron auflösen, falls nicht in Route-Params. if (projektId !== -1 && projektName === "") { fetchProjektById(projektId).then((projekt: iProjekt) => { projektName = projekt.projektname }).catch((e) => showServerError(e)) } // Alle Nutzer für das Hinzufügen-Modal vorab laden. loadAllUsers() // Zugeordnete Nutzer laden. loadProjektUsers() }, view: (vnode) => { return m("div.w3-container.w3-auto", { style: { display: 'flex', 'flex-direction': 'column', height: 'inherit', 'overflow-y': 'inherit' } }, m("h1", projektName ? "Nutzerberechtigungen für Projekt: " + projektName : "Nutzerberechtigungen"), m("div", {style: {flex: '1', 'overflow-y': 'inherit'}}, m(Treetable, { treenodes: treenodes, columnmapper: columnmapper, filter: filterFunction, onRowMarked: handleRowMarked, isSelected: (node: iTreenode) => node === selectedNode } as iTreetable, m(TopbarButton, { onclick: openAddModal, tooltip: "Nutzer hinzufügen" } as iTopbarButton, m("i.material-icons", "person_add") ) ) ) ) } } }