From d4ec396fb3d6aaa898a052d698730324d3b3c3f5 Mon Sep 17 00:00:00 2001
From: niladic
Date: Mon, 17 Jul 2023 14:48:35 +0200
Subject: [PATCH] Nouvelle page utilisateurs pour tout le monde + bump
tabulator 5.5.0 (#1762)
---
app/controllers/Operators.scala | 10 --
app/controllers/UserController.scala | 95 +----------------
app/models/EventType.scala | 2 -
app/models/User.scala | 13 ++-
app/serializers/ApiModel.scala | 29 ++++--
app/services/UserService.scala | 2 +-
app/views/allUsersByGroup.scala.html | 5 -
app/views/users.scala | 37 ++++++-
conf/routes | 1 -
package.json | 4 +-
typescript/src/admin.ts | 12 +--
typescript/src/applicationsAdmin.ts | 12 +--
typescript/src/franceServices.ts | 38 +++----
typescript/src/users.ts | 146 ++++++++++++++++++++++-----
14 files changed, 223 insertions(+), 183 deletions(-)
diff --git a/app/controllers/Operators.scala b/app/controllers/Operators.scala
index 401fc8856..058891496 100644
--- a/app/controllers/Operators.scala
+++ b/app/controllers/Operators.scala
@@ -124,16 +124,6 @@ object Operators {
payload()
}
- def asAdminWhoSeesUsersOfArea(areaId: UUID)(errorEventType: EventType, errorMessage: => String)(
- payload: () => Future[Result]
- )(implicit request: RequestWithUserData[_], ec: ExecutionContext): Future[Result] =
- if (not(request.currentUser.admin) || not(request.currentUser.canSeeUsersInArea(areaId))) {
- eventService.log(errorEventType, errorMessage)
- Future(Unauthorized("Vous n'avez pas le droit de faire ça"))
- } else {
- payload()
- }
-
def asUserWhoSeesUsersOfArea(areaId: UUID)(errorEventType: EventType, errorMessage: => String)(
payload: () => Future[Result]
)(implicit request: RequestWithUserData[_], ec: ExecutionContext): Future[Result] =
diff --git a/app/controllers/UserController.scala b/app/controllers/UserController.scala
index e9e7866f3..6d8ec54ca 100644
--- a/app/controllers/UserController.scala
+++ b/app/controllers/UserController.scala
@@ -13,8 +13,6 @@ import helper.{Time, UUIDHelper}
import javax.inject.{Inject, Singleton}
import models.EventType.{
AddUserError,
- AllUserCSVUnauthorized,
- AllUserCsvShowed,
AllUserIncorrectSetup,
AllUserUnauthorized,
CGUShowed,
@@ -64,7 +62,7 @@ import play.filters.csrf.CSRF
import play.filters.csrf.CSRF.Token
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
-import serializers.{Keys, UserAndGroupCsvSerializer}
+import serializers.Keys
import serializers.ApiModel.{SearchResult, UserGroupInfos, UserInfos}
import services._
@@ -234,7 +232,7 @@ case class UserController @Inject() (
val applications = applicationService.allByArea(selectedArea.id, anonymous = true)
eventService.log(UsersShowed, "Visualise la vue des utilisateurs")
val result = request.getQueryString(Keys.QueryParam.vue).getOrElse("nouvelle") match {
- case "nouvelle" if request.currentUser.admin =>
+ case "nouvelle" =>
views.users.page(request.currentUser, request.rights, selectedArea)
case _ =>
views.html.allUsersByGroup(request.currentUser, request.rights)(
@@ -249,99 +247,12 @@ case class UserController @Inject() (
}
}
- def allCSV(areaId: UUID): Action[AnyContent] =
- loginAction.async { implicit request: RequestWithUserData[AnyContent] =>
- asAdminWhoSeesUsersOfArea(areaId)(
- AllUserCSVUnauthorized,
- "Accès non autorisé à l'export utilisateur"
- ) { () =>
- val area = Area.fromId(areaId).get
- val usersFuture: Future[List[User]] = if (areaId === Area.allArea.id) {
- if (Authorization.isAdmin(request.rights)) {
- // Includes users without any group for debug purpose
- userService.all
- } else {
- groupService.byAreas(request.currentUser.areas).map { groupsOfArea =>
- userService.byGroupIds(groupsOfArea.map(_.id), includeDisabled = true)
- }
- }
- } else {
- groupService.byArea(areaId).map { groupsOfArea =>
- userService.byGroupIds(groupsOfArea.map(_.id), includeDisabled = true)
- }
- }
- val groupsFuture: Future[List[UserGroup]] =
- groupService.byAreas(request.currentUser.areas)
- eventService.log(AllUserCsvShowed, "Visualise le CSV de tous les zones de l'utilisateur")
-
- usersFuture.zip(groupsFuture).map { case (users, groups) =>
- def userToCSV(user: User): String = {
- val userGroups = user.groupIds.flatMap(id => groups.find(_.id === id))
- List[String](
- user.id.toString,
- user.firstName.orEmpty,
- user.lastName.orEmpty,
- user.email,
- Time.formatPatternFr(user.creationDate, "dd-MM-YYYY-HHhmm"),
- if (user.sharedAccount) "Compte Partagé" else " ",
- if (user.sharedAccount) user.name else " ",
- user.helperRoleName.getOrElse(""),
- if (user.instructor) "Instructeur" else " ",
- if (user.groupAdmin) "Responsable" else " ",
- if (user.expert) "Expert" else " ",
- if (user.admin) "Admin" else " ",
- if (user.disabled) "Désactivé" else " ",
- user.communeCode,
- user.areas.flatMap(Area.fromId).map(_.name).mkString(", "),
- userGroups.map(_.name).mkString(", "),
- userGroups
- .flatMap(_.organisation)
- .map(_.shortName)
- .mkString(", "),
- if (user.cguAcceptationDate.nonEmpty) "CGU Acceptées" else "",
- if (user.newsletterAcceptationDate.nonEmpty) "Newsletter Acceptée" else ""
- ).mkString(";")
- }
-
- val headers = List[String](
- "Id",
- UserAndGroupCsvSerializer.USER_FIRST_NAME.prefixes.head,
- UserAndGroupCsvSerializer.USER_LAST_NAME.prefixes.head,
- UserAndGroupCsvSerializer.USER_EMAIL.prefixes.head,
- "Création",
- UserAndGroupCsvSerializer.USER_ACCOUNT_IS_SHARED.prefixes.head,
- UserAndGroupCsvSerializer.SHARED_ACCOUNT_NAME.prefixes.head,
- "Aidant",
- UserAndGroupCsvSerializer.USER_INSTRUCTOR.prefixes.head,
- UserAndGroupCsvSerializer.USER_GROUP_MANAGER.prefixes.head,
- "Expert",
- "Admin",
- "Actif",
- "Commune INSEE",
- UserAndGroupCsvSerializer.GROUP_AREAS_IDS.prefixes.head,
- UserAndGroupCsvSerializer.GROUP_NAME.prefixes.head,
- UserAndGroupCsvSerializer.GROUP_ORGANISATION.prefixes.head,
- "CGU",
- "Newsletter"
- ).mkString(";")
-
- val csvContent = (List(headers) ++ users.map(userToCSV)).mkString("\n")
- val date = Time.formatPatternFr(Time.nowParis(), "dd-MMM-YYY-HH'h'mm")
- val filename = "aplus-" + date + "-users-" + area.name.replace(" ", "-") + ".csv"
-
- Ok(csvContent)
- .withHeaders("Content-Disposition" -> s"""attachment; filename="$filename"""")
- .as("text/csv")
- }
- }
- }
-
def search: Action[AnyContent] =
loginAction.async { implicit request =>
def toUserInfos(usersAndGroups: (List[User], List[UserGroup])): List[UserInfos] = {
val (users, groups) = usersAndGroups
val idToGroup = groups.map(group => (group.id, group)).toMap
- users.map(user => UserInfos.fromUser(user, idToGroup))
+ users.map(user => UserInfos.fromUser(user, request.rights, idToGroup))
}
val area = request
.getQueryString(Keys.QueryParam.searchAreaId)
diff --git a/app/models/EventType.scala b/app/models/EventType.scala
index d24099263..8c2799a38 100644
--- a/app/models/EventType.scala
+++ b/app/models/EventType.scala
@@ -47,8 +47,6 @@ object EventType {
object AllAsShowed extends Info
object AllAsUnauthorized extends Warn
object AllCSVShowed extends Info
- object AllUserCSVUnauthorized extends Warn
- object AllUserCsvShowed extends Info
object AllUserIncorrectSetup extends Info
object AllUserUnauthorized extends Warn
object ApplicationCreated extends Info
diff --git a/app/models/User.scala b/app/models/User.scala
index 2898edf21..608dbe860 100644
--- a/app/models/User.scala
+++ b/app/models/User.scala
@@ -7,7 +7,12 @@ import cats.syntax.all._
import constants.Constants
import helper.{Hash, Pseudonymizer, Time, UUIDHelper}
import helper.Time.zonedDateTimeInstance
-import helper.StringHelper.{notLetterNorNumberRegex, withQuotes}
+import helper.StringHelper.{
+ capitalizeName,
+ commonStringInputNormalization,
+ notLetterNorNumberRegex,
+ withQuotes
+}
case class User(
id: UUID,
@@ -236,4 +241,10 @@ object User {
internalSupportComment = None
)
+ def standardName(firstName: String, lastName: String): String = {
+ val normalizedFirstName = commonStringInputNormalization(firstName)
+ val normalizedLastName = commonStringInputNormalization(lastName)
+ s"${normalizedLastName.toUpperCase} ${capitalizeName(normalizedFirstName)}"
+ }
+
}
diff --git a/app/serializers/ApiModel.scala b/app/serializers/ApiModel.scala
index 05696b665..29d020e60 100644
--- a/app/serializers/ApiModel.scala
+++ b/app/serializers/ApiModel.scala
@@ -150,18 +150,25 @@ object ApiModel {
}
object UserInfos {
- case class Group(id: UUID, name: String)
+ case class Group(id: UUID, name: String, currentUserCanEditGroup: Boolean)
implicit val userInfosGroupFormat: Format[UserInfos.Group] = Json.format[UserInfos.Group]
implicit val userInfosFormat: Format[UserInfos] = Json.format[UserInfos]
- def fromUser(user: User, idToGroup: Map[UUID, UserGroup]): UserInfos = {
- val completeName = {
- val firstName = user.firstName.getOrElse("")
- val lastName = user.lastName.getOrElse("")
- if (firstName.nonEmpty || lastName.nonEmpty) s"${user.name} ($lastName $firstName)"
- else user.name
- }
+ def fromUser(
+ user: User,
+ rights: Authorization.UserRights,
+ idToGroup: Map[UUID, UserGroup]
+ ): UserInfos = {
+ val completeName =
+ if (user.sharedAccount)
+ user.name
+ else {
+ val firstName = user.firstName.getOrElse("")
+ val lastName = user.lastName.getOrElse("")
+ if (firstName.nonEmpty || lastName.nonEmpty) User.standardName(firstName, lastName)
+ else user.name
+ }
UserInfos(
id = user.id,
firstName = user.firstName,
@@ -173,11 +180,13 @@ object ApiModel {
phoneNumber = user.phoneNumber,
helper = user.helperRoleName.nonEmpty,
instructor = user.instructorRoleName.nonEmpty,
- areas = user.areas.flatMap(Area.fromId).map(_.toString),
+ areas = user.areas.flatMap(Area.fromId).map(_.toString).sorted,
groupNames = user.groupIds.flatMap(idToGroup.get).map(_.name),
groups = user.groupIds
.flatMap(idToGroup.get)
- .map(group => UserInfos.Group(group.id, group.name)),
+ .map(group =>
+ UserInfos.Group(group.id, group.name, Authorization.canEditGroup(group)(rights))
+ ),
groupEmails = user.groupIds.flatMap(idToGroup.get).flatMap(_.email),
groupAdmin = user.groupAdminRoleName.nonEmpty,
admin = user.adminRoleName.nonEmpty,
diff --git a/app/services/UserService.scala b/app/services/UserService.scala
index 5e7ee4e99..ec50a25b4 100644
--- a/app/services/UserService.scala
+++ b/app/services/UserService.scala
@@ -342,7 +342,7 @@ class UserService @Inject() (
val normalizedFirstName = firstName.normalized
val normalizedLastName = lastName.normalized
val normalizedQualite = qualite.normalized
- val name = s"${normalizedLastName.toUpperCase} ${normalizedFirstName.capitalizeWords}"
+ val name = User.standardName(firstName, lastName)
SQL"""
UPDATE "user" SET
name = $name,
diff --git a/app/views/allUsersByGroup.scala.html b/app/views/allUsersByGroup.scala.html
index c3088d69b..fdf420bc7 100644
--- a/app/views/allUsersByGroup.scala.html
+++ b/app/views/allUsersByGroup.scala.html
@@ -83,11 +83,6 @@
}
- }
- @if(currentUser.admin) {
-
}
@for(userGroup <- userGroups.sortBy(_.name)) {
@defining(allUsers.filter(_.groupIds.contains(userGroup.id))){ groupUsers =>
diff --git a/app/views/users.scala b/app/views/users.scala
index f1bc103db..593781a37 100644
--- a/app/views/users.scala
+++ b/app/views/users.scala
@@ -22,7 +22,7 @@ object users {
mainInfos: MainInfos
): Html =
views.html.main(currentUser, currentUserRights, maxWidth = false)(
- s"Gestion des groupes utilisateurs - ${selectedArea.name}"
+ s"Gestion des utilisateurs - ${selectedArea.name}"
)(
views.helpers.head.publicCss("stylesheets/newForm.css")
)(
@@ -75,14 +75,43 @@ object users {
)
),
div(id := "current-area-value", data("area-id") := selectedArea.id.toString),
+ div(
+ id := "user-role",
+ data("is-admin") := currentUser.admin.toString,
+ data("can-see-edit-user-page") := Authorization
+ .canSeeEditUserPage(currentUserRights)
+ .toString,
+ ),
currentUser.admin.some.filter(identity).map(_ => searchForm()),
div(cls := "mdl-cell mdl-cell--12-col", id := "tabulator-users-table"),
- div(cls := "mdl-cell mdl-cell--12-col", id := "tabulator-groups-table"),
currentUser.admin.some
.filter(identity)
- .map(_ => views.addGroup.innerForm(currentUser, selectedArea))
+ .map(_ => div(cls := "mdl-cell mdl-cell--12-col", id := "tabulator-groups-table")),
+ currentUser.admin.some
+ .filter(identity)
+ .map(_ => views.addGroup.innerForm(currentUser, selectedArea)),
+ div(
+ cls := "mdl-cell",
+ a(
+ id := "users-download-btn-csv",
+ href := "#",
+ i(cls := "fas fa-download"),
+ " Téléchargement au format CSV"
+ )
+ ),
+ div(
+ cls := "mdl-cell",
+ a(
+ id := "users-download-btn-xlsx",
+ href := "#",
+ i(cls := "fas fa-download"),
+ " Téléchargement au format XLSX"
+ )
+ ),
)
- )(Nil)
+ )(
+ views.helpers.head.publicScript("generated-js/xlsx.full.min.js")
+ )
def searchForm(): Tag =
div(
diff --git a/conf/routes b/conf/routes
index 59cdaad60..62603c464 100644
--- a/conf/routes
+++ b/conf/routes
@@ -106,7 +106,6 @@ GET /territoires/deploiement
GET /territoires/deploiement/france-service controllers.AreaController.franceServiceDeploymentDashboard
GET /territoires/deploiement/france-services controllers.AreaController.franceServices
GET /territoires/:areaId/utilisateurs controllers.UserController.all(areaId: java.util.UUID)
-GET /territoires/:areaId/utilisateurs.csv controllers.UserController.allCSV(areaId: java.util.UUID)
# Check
GET /status controllers.HomeController.status
diff --git a/package.json b/package.json
index 2ff78961a..1c5737254 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"dialog-polyfill": "0.5.6",
"proxy-polyfill": "0.3.2",
"slim-select": "1.27.1",
- "tabulator-tables": "5.2.7",
+ "tabulator-tables": "5.5.0",
"ts-polyfill": "3.8.2",
"unfetch": "4.2.0",
"unorm": "1.6.0",
@@ -22,7 +22,7 @@
"devDependencies": {
"@babel/core": "7.21.8",
"@babel/preset-env": "7.21.5",
- "@types/tabulator-tables": "5.2.0",
+ "@types/tabulator-tables": "5.4.8",
"babel-loader": "9.1.2",
"copy-webpack-plugin": "11.0.0",
"css-loader": "6.7.4",
diff --git a/typescript/src/admin.ts b/typescript/src/admin.ts
index abc941275..da70fbbaa 100644
--- a/typescript/src/admin.ts
+++ b/typescript/src/admin.ts
@@ -1,4 +1,4 @@
-import { Tabulator, TabulatorFull } from 'tabulator-tables';
+import { ColumnDefinition, Formatter, TabulatorFull } from 'tabulator-tables';
import "tabulator-tables/dist/css/tabulator.css";
@@ -19,7 +19,7 @@ const franceServiceDeploymentDownloadBtnXlsxId = "aplus-admin-deployment-france-
// TODO: macro that will generate field names from the scala case class (Scala => TS)
-const franceServiceDeploymentColumns: Array = [
+const franceServiceDeploymentColumns: Array = [
{ title: "Nom", field: "nomFranceService", sorter: "string", width: 200 },
{
title: "Département",
@@ -175,7 +175,7 @@ if (window.document.getElementById(deploymentTableTagId)) {
}
fetch(url).then((response) => response.json()).then((deploymentData: DeploymentData) => {
- const organisationFormatter: Tabulator.Formatter = (cell) => {
+ const organisationFormatter: Formatter = (cell) => {
const value = cell.getValue();
const areaName = cell.getRow().getData().areaName;
@@ -208,7 +208,7 @@ if (window.document.getElementById(deploymentTableTagId)) {
const title = orgSet.organisations.map((organisation) => organisation.shortName).join(" / ");
const field = orgSet.id;
- const column: Tabulator.ColumnDefinition = {
+ const column: ColumnDefinition = {
title,
field,
sorter: "number",
@@ -218,12 +218,12 @@ if (window.document.getElementById(deploymentTableTagId)) {
return column;
});
- const areaColumn: Tabulator.ColumnDefinition =
+ const areaColumn: ColumnDefinition =
{
title: "Département", field: "areaName", sorter: "string", width: 150,
frozen: true
};
- const totalColumn: Tabulator.ColumnDefinition =
+ const totalColumn: ColumnDefinition =
{
title: "Couverture",
field: "total",
diff --git a/typescript/src/applicationsAdmin.ts b/typescript/src/applicationsAdmin.ts
index ef271df2a..2e71de390 100644
--- a/typescript/src/applicationsAdmin.ts
+++ b/typescript/src/applicationsAdmin.ts
@@ -1,5 +1,5 @@
/* global jsRoutes */
-import { Tabulator, TabulatorFull } from 'tabulator-tables';
+import { ColumnDefinition, Formatter, Options, Tabulator, TabulatorFull } from 'tabulator-tables';
import "tabulator-tables/dist/css/tabulator.css";
const applicationsTableId = "tabulator-applications-table";
@@ -100,7 +100,7 @@ if (window.document.getElementById(applicationsTableId)) {
// Setup Tabulator
- const linkFormatter: Tabulator.Formatter = (cell) => {
+ const linkFormatter: Formatter = (cell) => {
let uuid = cell.getRow().getData().id;
let authorized = cell.getRow().getData().currentUserCanSeeAnonymousApplication;
let url = jsRoutes.controllers.ApplicationController.show(uuid).url;
@@ -111,7 +111,7 @@ if (window.document.getElementById(applicationsTableId)) {
}
};
- const usefulnessFormatter: Tabulator.Formatter = (cell) => {
+ const usefulnessFormatter: Formatter = (cell) => {
let value = cell.getValue();
if (value) {
if (value === "Oui") {
@@ -123,7 +123,7 @@ if (window.document.getElementById(applicationsTableId)) {
return value;
};
- const pertinenceFormatter: Tabulator.Formatter = (cell) => {
+ const pertinenceFormatter: Formatter = (cell) => {
let value = cell.getValue();
if (value && value === "Non") {
cell.getElement().classList.add("mdl-color--red");
@@ -131,7 +131,7 @@ if (window.document.getElementById(applicationsTableId)) {
return value;
};
- const columns: Array = [
+ const columns: Array = [
{
title: "",
field: "id",
@@ -288,7 +288,7 @@ if (window.document.getElementById(applicationsTableId)) {
},
];
- const options: Tabulator.Options = {
+ const options: Options = {
height: "75vh",
langs: {
"fr-fr": {
diff --git a/typescript/src/franceServices.ts b/typescript/src/franceServices.ts
index 31a30a046..c5aeb67f2 100644
--- a/typescript/src/franceServices.ts
+++ b/typescript/src/franceServices.ts
@@ -1,5 +1,5 @@
/* global jsRoutes */
-import { Tabulator, TabulatorFull } from 'tabulator-tables';
+import { CellEditEventCallback, CellEventCallback, ColumnDefinition, Formatter, Options, Tabulator, TabulatorFull } from 'tabulator-tables';
import 'tabulator-tables/dist/css/tabulator.css';
const tableId = 'tabulator-france-services-table';
@@ -456,7 +456,7 @@ if (window.document.getElementById(tableId)) {
// Tables Config
//
- const deleteColBase: Tabulator.ColumnDefinition = {
+ const deleteColBase: ColumnDefinition = {
title: '',
field: '',
hozAlign: 'center',
@@ -465,18 +465,18 @@ if (window.document.getElementById(tableId)) {
download: false,
};
- const addTableDeleteColCellFormatter: Tabulator.Formatter =
+ const addTableDeleteColCellFormatter: Formatter =
(cell) => {
cell.getElement().classList.add('mdl-color--red-200');
return '';
};
- const addTableDeleteColCellClick: Tabulator.CellEventCallback =
+ const addTableDeleteColCellClick: CellEventCallback =
(_e, cell) => {
cell.getRow().delete();
};
- const matriculeColBase: Tabulator.ColumnDefinition = {
+ const matriculeColBase: ColumnDefinition = {
title: 'Matricule',
field: 'matricule',
hozAlign: 'right',
@@ -492,7 +492,7 @@ if (window.document.getElementById(tableId)) {
},
};
- const matriculeColEdited: Tabulator.CellEditEventCallback =
+ const matriculeColEdited: CellEditEventCallback =
(cell) => {
const oldMatricule = cell.getOldValue();
const matricule = cell.getValue();
@@ -536,7 +536,7 @@ if (window.document.getElementById(tableId)) {
return options;
});
- const addTableGroupColEdited: Tabulator.CellEditEventCallback =
+ const addTableGroupColEdited: CellEditEventCallback =
(cell) => {
const groupId = cell.getValue();
const group = groupList.find((g) => g.id === groupId);
@@ -545,7 +545,7 @@ if (window.document.getElementById(tableId)) {
}
};
- const groupColBase: Tabulator.ColumnDefinition = {
+ const groupColBase: ColumnDefinition = {
title: 'Groupe',
field: 'groupId',
headerFilter: 'input',
@@ -565,36 +565,36 @@ if (window.document.getElementById(tableId)) {
}
};
- const groupCol: Tabulator.ColumnDefinition = Object.assign(groupColEditorParams, groupColBase);
+ const groupCol: ColumnDefinition = Object.assign(groupColEditorParams, groupColBase);
- const nameCol: Tabulator.ColumnDefinition = {
+ const nameCol: ColumnDefinition = {
title: 'Nom',
field: 'name',
headerFilter: 'input',
width: 300,
};
- const descriptionCol: Tabulator.ColumnDefinition = {
+ const descriptionCol: ColumnDefinition = {
title: 'Description',
field: 'description',
headerFilter: 'input',
maxWidth: 300,
};
- const areasCol: Tabulator.ColumnDefinition = {
+ const areasCol: ColumnDefinition = {
title: 'Départements',
field: 'areas',
headerFilter: 'input',
maxWidth: 300,
};
- const organisationCol: Tabulator.ColumnDefinition = {
+ const organisationCol: ColumnDefinition = {
title: 'Organisme',
field: 'organisation',
headerFilter: 'input',
};
- const emailCol: Tabulator.ColumnDefinition = {
+ const emailCol: ColumnDefinition = {
title: 'BAL',
field: 'email',
headerFilter: 'input',
@@ -602,21 +602,21 @@ if (window.document.getElementById(tableId)) {
maxWidth: 300,
};
- const publicNoteCol: Tabulator.ColumnDefinition = {
+ const publicNoteCol: ColumnDefinition = {
title: 'Description détaillée',
field: 'publicNote',
headerFilter: 'input',
maxWidth: 300,
};
- const addTableAreaCodeCol: Tabulator.ColumnDefinition = {
+ const addTableAreaCodeCol: ColumnDefinition = {
title: 'Code INSEE',
field: 'areaCode',
headerFilter: 'input',
editor: 'input',
};
- const addTableInternalCommentCol: Tabulator.ColumnDefinition = {
+ const addTableInternalCommentCol: ColumnDefinition = {
title: 'Commentaire interne',
field: 'internalSupportComment',
headerFilter: 'input',
@@ -624,7 +624,7 @@ if (window.document.getElementById(tableId)) {
maxWidth: 300,
};
- const options: Tabulator.Options = {
+ const options: Options = {
height: '50vh',
langs: {
'fr-fr': {
@@ -655,7 +655,7 @@ if (window.document.getElementById(tableId)) {
};
table = new TabulatorFull('#' + tableId, options);
- const addOptions: Tabulator.Options = {
+ const addOptions: Options = {
langs: {
'fr-fr': {
'data': {
diff --git a/typescript/src/users.ts b/typescript/src/users.ts
index 75b334dd8..cc117d7f0 100644
--- a/typescript/src/users.ts
+++ b/typescript/src/users.ts
@@ -1,4 +1,5 @@
-import { Tabulator, TabulatorFull } from 'tabulator-tables';
+/* global jsRoutes */
+import { ColumnDefinition, CustomAccessor, Formatter, Options, RowComponent, Tabulator, TabulatorFull } from 'tabulator-tables';
import "tabulator-tables/dist/css/tabulator.css";
import { debounceAsync } from './helpers';
@@ -15,6 +16,7 @@ let groupsTable: Tabulator | null = null;
interface UserInfosGroup {
id: string;
name: string;
+ currentUserCanEditGroup: boolean;
}
interface UserInfos {
@@ -70,14 +72,14 @@ async function callSearch(searchString: string): Promise {
if (window.document.getElementById(usersTableId)) {
const verticalHeader = false;
- const editIcon: Tabulator.Formatter = function(cell) {
+ const editIcon: Formatter = function(cell) {
//plain text value
let uuid = cell.getRow().getData().id;
let url = jsRoutes.controllers.UserController.editUser(uuid).url;
return "";
};
- const groupsFormatter: Tabulator.Formatter = function(cell) {
+ const groupsFormatter: Formatter = function(cell) {
const groups = >cell.getRow().getData().groups;
let links = "";
let isNotFirst = false;
@@ -87,20 +89,32 @@ if (window.document.getElementById(usersTableId)) {
if (isNotFirst) {
links += ", ";
}
- links += "" + groupName + "";
+ if (group.currentUserCanEditGroup) {
+ links += "" + groupName + "";
+ } else {
+ links += groupName;
+ }
isNotFirst = true;
});
return links;
};
- const groupNameFormatter: Tabulator.Formatter = function(cell) {
+ const joinWithCommaDownload: CustomAccessor = function(value) {
+ if (value != null) {
+ return value.join(", ");
+ } else {
+ return value;
+ }
+ };
+
+ const groupNameFormatter: Formatter = function(cell) {
const group = cell.getRow().getData();
const groupUrl = jsRoutes.controllers.GroupController.editGroup(group.id).url;
const html = "" + group.name + "";
return html;
};
- const rowFormatter = function(row: Tabulator.RowComponent) {
+ const rowFormatter = function(row: RowComponent) {
let element = row.getElement(),
data = row.getData();
if (data.disabled) {
@@ -108,8 +122,15 @@ if (window.document.getElementById(usersTableId)) {
}
};
- const usersColumns: Array = [
- { title: "", formatter: editIcon, width: 40, frozen: true },
+ const adminColumns: Array = [
+ {
+ title: "",
+ field: "id",
+ formatter: editIcon,
+ hozAlign: "center",
+ width: 40,
+ frozen: true,
+ },
{
title: "Email",
field: "email",
@@ -120,8 +141,10 @@ if (window.document.getElementById(usersTableId)) {
title: "Groupes",
field: "groupNames",
formatter: groupsFormatter,
+ sorter: "string",
headerFilter: "input",
width: 400,
+ accessorDownload: joinWithCommaDownload,
},
{
title: "Nom Complet",
@@ -133,8 +156,10 @@ if (window.document.getElementById(usersTableId)) {
{
title: "BALs",
field: "groupEmails",
+ sorter: "string",
headerFilter: "input",
width: 200,
+ accessorDownload: joinWithCommaDownload,
},
{
title: "Qualité",
@@ -231,8 +256,10 @@ if (window.document.getElementById(usersTableId)) {
{
title: "Départements",
field: "areas",
+ sorter: "string",
headerFilter: "input",
width: 200,
+ accessorDownload: joinWithCommaDownload,
},
{
title: "Nom et Prénom",
@@ -257,8 +284,7 @@ if (window.document.getElementById(usersTableId)) {
},
];
-
- const groupsColumns: Array = [
+ const groupsColumns: Array = [
{
title: "Nom",
field: "name",
@@ -308,7 +334,19 @@ if (window.document.getElementById(usersTableId)) {
}
}
- const usersOptions: Tabulator.Options = {
+ let isAdmin = false;
+ let canSeeEditUserPage = false;
+ const roleDataField = document.getElementById("user-role");
+ if (roleDataField != null) {
+ if (roleDataField.dataset["isAdmin"] === "true") {
+ isAdmin = true;
+ }
+ if (roleDataField.dataset["canSeeEditUserPage"] === "true") {
+ canSeeEditUserPage = true;
+ }
+ }
+
+ const usersOptionsForAdmins: Options = {
height: "48vh",
rowFormatter,
langs: {
@@ -318,32 +356,68 @@ if (window.document.getElementById(usersTableId)) {
}
}
},
- columns: usersColumns,
+ columns: adminColumns,
};
- usersTable = new TabulatorFull("#" + usersTableId, usersOptions);
- usersTable.on("tableBuilt", function() {
- usersTable?.setLocale("fr-fr");
- usersTable?.setSort("name", "asc");
- });
- const groupsOptions: Tabulator.Options = {
- height: "25vh",
+ const excludedFieldsForNonAdmins = ["name", "lastName", "firstName", "helper", "expert", "admin"];
+ if (!canSeeEditUserPage) {
+ excludedFieldsForNonAdmins.push("id");
+ }
+ const usersOptionsForNonAdmins: Options = {
+ height: "75vh",
rowFormatter,
langs: {
"fr-fr": {
+ "data": {
+ "loading": "Chargement",
+ "error": "Erreur",
+ },
headerFilters: {
"default": "filtrer..."
}
}
},
- columns: groupsColumns,
+ columns: adminColumns
+ .filter((item) => {
+ if (item.field) {
+ return !excludedFieldsForNonAdmins.includes(item.field);
+ } else {
+ return true;
+ }
+ }),
+ initialSort: [{ column: "areas", dir: "asc" }],
+ ajaxURL: jsRoutes.controllers.UserController.search().url,
+ ajaxResponse(_url, _params, response) {
+ return response.users;
+ }
};
- groupsTable = new TabulatorFull("#" + groupsTableId, groupsOptions);
- groupsTable.on("tableBuilt", function() {
- groupsTable?.setLocale("fr-fr");
- groupsTable?.setSort("name", "asc");
+
+ const usersOptions: Options = isAdmin ? usersOptionsForAdmins : usersOptionsForNonAdmins;
+ usersTable = new TabulatorFull("#" + usersTableId, usersOptions);
+ usersTable.on("tableBuilt", function() {
+ usersTable?.setLocale("fr-fr");
});
+ if (document.getElementById(groupsTableId) != null) {
+ const groupsOptions: Options = {
+ height: "25vh",
+ rowFormatter,
+ langs: {
+ "fr-fr": {
+ headerFilters: {
+ "default": "filtrer..."
+ }
+ }
+ },
+ columns: groupsColumns,
+ };
+ groupsTable = new TabulatorFull("#" + groupsTableId, groupsOptions);
+ groupsTable.on("tableBuilt", function() {
+ groupsTable?.setLocale("fr-fr");
+ groupsTable?.setSort("name", "asc");
+ });
+ }
+
const searchBox = document.getElementById(searchBoxId);
@@ -360,4 +434,28 @@ if (window.document.getElementById(usersTableId)) {
fillData();
}
+ const csvDownloadBtn = window.document.getElementById("users-download-btn-csv");
+ if (csvDownloadBtn) {
+ csvDownloadBtn.onclick = () => {
+ const date = new Date().toLocaleDateString(
+ 'fr-fr',
+ { year: 'numeric', month: 'numeric', day: 'numeric' }
+ );
+ const filename = 'Utilisateurs - ' + date;
+ usersTable?.download('csv', filename + '.csv');
+ };
+ }
+
+ const xlsxDownloadBtn = window.document.getElementById("users-download-btn-xlsx");
+ if (xlsxDownloadBtn) {
+ xlsxDownloadBtn.onclick = () => {
+ const date = new Date().toLocaleDateString(
+ 'fr-fr',
+ { year: 'numeric', month: 'numeric', day: 'numeric' }
+ );
+ const filename = 'Utilisateurs - ' + date;
+ usersTable?.download('xlsx', filename + '.xlsx', { sheetName: 'Utilisateurs' });
+ };
+ }
+
}