Watcha admin re-design dashboard (#32)

* feat: extract Login from App component

* refactor: remove obsolete package-lock.json since using yarn

* feat: use matrix-js-sdk for login

* feat: use URL() rather than string concatenation

* fix: remove useless t()

* feat: upgrade i18n engine

* feat: add an error boundary

* refactor: extensive but very safe cleaning of the code base

No purpose changes  :
- format with Prettier
- convert arrow methods with only an instruction into oneline
- convert the setStates having only one argument into oneline
- merge consecutive setStates
- reorganize imports
- improve react-bootstrap imports
- convert small components into stateless functional components
- isEmail() now returns only a boolean
- convert React life cycles (componentDidMount...) to standard methods, not to class properties
- remove useless constructors
- cleaning of line breaks...

* fix: replace obsolete prop for Alert components

* feat: use classnames() to improve BooleanRow component

* fix: upgrade dependencies

* fix: restore homepage in package.json and update i18n config

* feat: add context for matrix client

* feat: replace deactivateAccount() with client.deactivateSynapseUser()

* fix: add namespace array in i18n config

* fix: turn off debug for i18n

* fix: refresh the user table by validating user creation alert

* feat: use context wherever token and baseUrl are needed

* feat: add SuspenseFallback component

* feat: delay fallback suspense display

* cmd: yarn add react-table

* refactor: replace object[xxx] with object.xxx

* refactor: replace legacy functional components with arrow functions

* cmd: yarn add restful-react

* refactor: convert StatsTab into functional component

* refactor: convert CardStats into functional component

* feat: use restful-react for StatsTab component

* feat: replace custom loading animation with a spinner

* feat: replace App.css content with user.css one and improve centered layout

* refactor: convert App into functional component

* feat: add DelayedSpinner component

* refactor: convert AdminHome into functional component

* feat: improve variable names involved in changing tabs

* fix: typo

* feat: update i18n for tab titles

* feat: update i18n for Login component

* feat: improve pattern to avoid double submission

* fix: pass the right prop to CardStat component for tab change

* refactor: rename StatsTab component

* refactor: convert Login into functional component

* feat: add useMatrixClientContext()

* feat: add TabReducerContext

* feat: hide user list for empty rooms

* fix: replace setState callback() with callback for deactivateSynapseUser()

* feat: add ChangeTabContext

* feat: add UsersTab & RoomsTab components

* feat: add DispatchContext, UserIdContext and RoomIdContext

* feat: use react-table for users tab

* fix: improve layout

* feat: add new user button

* refactor: improve i18n hierarchy

* feat: add the old CreateUserRightPanel to the new UsersTab

* feat: add the old UserRightPanel to the new UsersTab

* fix: limit the scope of css classes for user account status

* fix: improve spinner layout

* fix: restore missing i18n

* fix: remove table outline

* Watcha op313 modify user page on admin dashboard (#11)

* feat: add email next to displayname on for each admin line.

* style: deletion of underline on each admin line.

* feat: deletion of useless console.log()

* style: add new line at the end of AdminCardStats.js file.

* style: format new code with prettier.

* style: formatting with prettier (tabsize = 4).

* feat: simplification of syntax code.

* feat: calculate displayname before passing props.

* Style: reorder props

Co-authored-by: c-cal <57274151+c-cal@users.noreply.github.com>

* Watcha op305 inconsistent values on admin stats (#10)

* feat: assignement of rooms statistic values returned by synapse on dashboard.

* style: translate new terms on room page on dashboard.

* style: regroup oldstyle i18n

* feat: fix of Warning: Encountered two children with the same key.

Co-authored-by: c-cal <github-2c7c@zebrina.fr>

* feat: add logged users informations on dashboard.

* feat: add number of users with pending invitation on dashboard.

* style: change 'member' terms by 'collaborator'.

* feat: modification of item parameter in resolve function (with synapse modification of OP350).

* feat: change watcha_room_list endpoint name (with synapse modification from OP320).

* feat: add pendingInvitationUsers translation on dashboardTab.json.

* feat: add admin translation on dashboardTab.json.

* style: add usersPannel spacename.

* style: add roomsPannel spacename.

* Watcha admin release sprint 22 (#24)

* feat: deletion of CardStats component.

* feat: add some userPannel translation.

* style: css for UserDashboardPanel component.

* feat: creation of UsersDashboardPanel component.

* feat: add UsersDashboardPanel on DashboardTab component.

* feat: add some translation for roomsPannel.

* feat: creation of RoomsDashboardPanel component.

* feat: addition of RoomsDashboardPanel on dashboardTab.

* feat: deletion of CardStats.js

* feat: deletion of AdminCardStats.js.

* feat: add noRoomsMessage on RoomsDashboardPanel.

* fix: fix label value of pendingInvitationUsers.

* style: add css for noRoomsMessage.

* feat: modification of some translation on dashboardTab.

* feat: addition of translation for serverStatePannel.

* feat: creation of ServerStateDashboardPanel component.

* feat: addition of serverStateDashboardPanel component on dashboardTab.

* style: change syntax code.

* feat: display empty string if no admin email value.

* feat: addition of translation for tooltip messages.

* feat: deletion of useless i18next on dashboardPanel component.

* feat: addition of tooltip on usersPanelDasboard component.

* style: modification of transaltion on dahsboardTab.

* feat: deletion of useless react-18next import.

* feat: deletion of useless Card component.

* feat: replacement of card by fieldset and include list of admin in fieldset.

* style: add some css modification.

* style: spelling correction.

* feat: addition of a key for Accordion component.

* feat: addition of resfreshButton on dashboardTab (OP358).

* feat: deletion of string formatting for date in serverPanel.

* fix : change API endpoint name and add JSON content

* fix: add RefreshButton component

* fix: change API endpoint name for rooms list

* fix: add ItemId parameter to resolve function.

* fix: change 'member' role to 'collaborator'

Co-authored-by: c-cal <github-2c7c@zebrina.fr>
Co-authored-by: c-cal <57274151+c-cal@users.noreply.github.com>
develop
KevICO 5 years ago committed by GitHub
parent 3ebdbdddf2
commit 3180ee1650
  1. 38
      public/locales/en/dashboardTab.json
  2. 7
      public/locales/fr/common.json
  3. 36
      public/locales/fr/dashboardTab.json
  4. 10
      src/AdminCardStats.js
  5. 106
      src/App.css
  6. 87
      src/CardStats.js
  7. 135
      src/DashboardTab.js
  8. 10
      src/RefreshButton.js
  9. 67
      src/RoomsDashboardPanel.js
  10. 2
      src/RoomsTab.js
  11. 87
      src/ServerStateDashboardPanel.js
  12. 5
      src/UserRightPanel.js
  13. 216
      src/UsersDashboardPanel.js
  14. 17
      src/UsersTab.js

@ -1,10 +1,36 @@
{
"title": "Dashboard",
"usersPannel": {
"footerLink": "Administrate users"
"usersPanel": {
"collaborators": "Collaborators",
"partners": "Partners",
"administrators": "Administrators",
"loggedUsers": "At least once",
"monthlyUsers": "During last month",
"weeklyUsers": "Duringlast week",
"pendingInvitationUsers": "Pending invitations",
"administrateButton": "Administrate users",
"usersPerRole": "Roles of users",
"connectedUsers": "Connected users",
"otherStatistics": "Other statistics",
"administratorTooltip": "Users with access to the administration console of this instance.",
"collaboratorTooltip": "Internal users with the right to create rooms and invite users into them.",
"partnerTooltip": "External users who have been invited to one or more rooms by an internal user and have limited rights.",
"pendingInvitationTooltip": "Users who have been invited, but have not yet set their password or logged in."
},
"roomsPannel": {
"footerLink": "Administrate rooms",
"noRoomsMessage": "This Watcha instance does not yet have some rooms. Return to Watcha to create a first room (button \"+\")."
"roomsPanel": {
"title": "Rooms per type",
"nonDirectRoomsCount": "Multiple conversations of which active",
"directRoomsCount": "Personnals conversations of which active",
"administrateButton": "Administrate rooms",
"noRoomsMessageOne": "This Watcha instance does not yet have some rooms.",
"noRoomsMessageTwo": "Return to Watcha to create a first room (button \"+\")."
},
"serverStatePannel": {
"title": "Server state",
"generaleInformation": "General information",
"version": "Version",
"installDate": "Installed on",
"upgradeDate": "Upgrade on",
"diskUsage": "Disk space used"
}
}
}

@ -19,13 +19,6 @@
"Password reset failed": "L'opération a échoué",
"Password reset": "Mot de passe réinitialisé",
"Activate account": "Activer le compte",
"Members": "Utilisateurs internes ",
"Partners": "Partenaires ",
"Number of rooms": "Nombre de salons",
"Number of personnals conversations": "Nombre de conversations privées",
"Of which active rooms": "Dont salons actifs",
"Of which active personnals conversations": "Dont conversations privées actives",
"Administrators": "Administrateurs ",
"login": {
"title": "Interface d'administration",

@ -1,10 +1,36 @@
{
"title": "Tableau de bord",
"usersPannel": {
"footerLink": "Administrer les utilisateurs"
"usersPanel": {
"collaborators": "Collaborateurs",
"partners": "Partenaires",
"administrators": "Administrateurs",
"loggedUsers": "Au moins une fois",
"monthlyUsers": "Sur le dernier mois",
"weeklyUsers": "Sur la dernière semaine",
"pendingInvitationUsers": "Invitations en attente",
"administrateButton": "Administrer les utilisateurs",
"usersPerRole": "Rôles des utilisateurs",
"connectedUsers": "Utilisateurs connectés",
"otherStatistics": "Autres statistiques",
"administratorTooltip": "Utilisateurs ayant accès à la console d'administration de l'instance.",
"collaboratorTooltip": "Utilisateurs internes ayant le droit de créer des salons et d'inviter des utilisateurs dans ces derniers.",
"partnerTooltip": "Utilisateurs externes ayant été invités dans un ou plusieurs salons, par un utilisateur interne et ayant des droits limités.",
"pendingInvitationTooltip": "Utilisateurs ayant été invités, mais n'ayant pas encore défini leur mot de passe, ni s'étant encore connecté."
},
"roomsPannel": {
"footerLink": "Administrer les salons",
"noRoomsMessage": "Cette installation de Watcha n'a encore aucun salon. Retournez sur Watcha pour creer un premier Salon (bouton \"+\")"
"roomsPanel": {
"title": "Salons par type",
"nonDirectRoomsCount": "Conversations a plusieurs actives",
"directRoomsCount": "Conversations privées actives",
"administrateButton": "Administrer les salons",
"noRoomsMessageOne": "Cette installation de Watcha n'a encore aucun salon.",
"noRoomsMessageTwo": "Retournez sur Watcha pour creer un premier Salon (bouton \"+\")"
},
"serverStatePannel": {
"title": "État du serveur",
"generaleInformation": "Informations générales",
"version": "Version",
"installDate": "Installé le",
"upgradeDate": "Mis à jour le",
"diskUsage": "Espace disque utilisé"
}
}

@ -1,10 +0,0 @@
import React from "react";
export default ({ onUserClicked, adminUserId, displayName, email }) => {
const onClick = () => onUserClicked(adminUserId);
return (
<div className="AdminName" {...{ onClick }}>
{`${displayName} (${email})`}
</div>
);
};

@ -195,30 +195,114 @@ td .inactive {
justify-content: space-between;
}
.statsPanelContent {
display: flex;
justify-content: space-between;
}
/* DashboardTab : */
.statsPanelsContainer {
.dashboardPanelsContainer {
display: flex;
font-size: 18px;
font-family: "Open Sans", Arial, Helvetica, sans-serif;
padding: 1.25rem;
align-items: space-between;
}
.dashboardPanel {
display: flex;
width: 100%;
flex: 1;
margin: 1.25rem;
background-color: white;
}
#tabs-tabpane-dashboard .dashboardPanel{
margin-left: unset;
}
.dashboardPanel .card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.dashboardPanelSection {
padding: 1rem;
font-size: 1rem;
}
.watcha-legend {
margin: 0.5rem;
padding: 1rem;
font-size: 1rem;
font-weight: bold;
width: auto;
}
.watcha-fieldset {
border: 2px solid;
border-width: 80%;
width: 100%;
}
.dashboardPanelSection tr:first-child td {
border: none;
margin-left: 1rem;
}
.statsPanel {
flex-grow: 1;
margin: 2vw;
background-color: #337ab7;
.watcha-fieldset tr:first-child td {
border: none;
margin-left: 1rem;
}
.sectionPanelData {
text-align: right;
font-weight: bold;
}
.sectionPanelAdminListLabel .card-header {
padding: unset;
background-color: unset;
border: unset;
display: inline-block;
}
.sectionPanelAdminListLabel .card-header:hover {
background-color: darkturquoise;
cursor: pointer;
color: white;
}
.statsPanelFooter:hover {
.sectionPanelAdminListLabel .card {
margin: unset;
border: unset;
}
.sectionPanelAdminListLabel .adminUserRow:hover {
background-color: darkturquoise;
cursor: pointer;
color: white;
}
.dashboardAdministrateButton:hover {
cursor: pointer;
background-color: darkturquoise;
text-decoration: underline;
border-color: darkturquoise;
}
.noRoomsMessage {
font-weight: bold;
}
.tooltipMessage {
font-style: italic;
}
#tabs-tabpane-dashboard .refreshButton {
margin-top: 10px;
margin-bottom: 10px;
margin-left: 20px;
}
/* end of DashboardTab : */
.dismissRight {
cursor: pointer;
}

@ -1,87 +0,0 @@
import React from "react";
import { withTranslation } from "react-i18next";
import Card from "react-bootstrap/Card";
import { useDispatchContext } from "./contexts";
import AdminCardStats from "./AdminCardStats";
export default withTranslation()(({ title, tab, lines, t, footer }) => {
const dispatch = useDispatchContext();
const onCardClicked = () => dispatch({ tab });
const onUserClicked = userId => dispatch({ tab: "users", userId });
const getPanelContent = lines => {
const panelContent = [];
if(lines.length !== 0){
for (const LINE in lines) {
if ({}.hasOwnProperty.call(lines, LINE)) {
if (lines[LINE].label === t("Admin")) {
const admins = [];
const profileInfosOfAdmins = lines[LINE].data;
for (const profileInfo of profileInfosOfAdmins) {
const displayName = setAdminName(
profileInfo["displayname"],
profileInfo["user_id"]
);
admins.push(
<div key={profileInfo["user_id"]}>
<AdminCardStats
email={profileInfo["email"]}
adminUserId={profileInfo["user_id"]}
{...{ onUserClicked, displayName }}
/>
</div>
);
}
panelContent.push(
<div key={lines[LINE].label}>
{" "}
{t("Administrators")}: {admins}{" "}
</div>
);
} else {
panelContent.push(
<div key={lines[LINE].label}>
{lines[LINE].label + ": " + lines[LINE].data}
</div>
);
}
}
}
}else{
panelContent.push(
<div>{t("dashboardTab:roomsPannel.noRoomsMessage")}</div>
);
}
return panelContent;
};
const setAdminName = (displayName, userId) => {
return displayName || userId.replace("@", "").split(":")[0];
};
const PANEL_CONTENT = getPanelContent(lines);
return (
<Card className="statsPanel">
<Card.Header>
<span>
{title}
</span>
</Card.Header>
<Card.Body>
<div className="statsPanelContent">
<div>{PANEL_CONTENT}</div>
</div>
</Card.Body>
<Card.Footer>
<span className="statsPanelFooter" onClick={onCardClicked}>
{footer}
</span>
</Card.Footer>
</Card>
);
});

@ -1,70 +1,79 @@
import React from "react";
import { useGet } from "restful-react";
import { useTranslation } from "react-i18next";
import CardStats from "./CardStats";
import React, { Component } from "react";
import Col from "react-bootstrap/Col";
import Row from "react-bootstrap/Col";
import UsersDashboardPanel from "./UsersDashboardPanel";
import RoomsDashboardPanel from "./RoomsDashboardPanel";
import ServerStateDashboardPanel from "./ServerStateDashboardPanel";
import CardDeck from "react-bootstrap/CardDeck";
import DelayedSpinner from "./DelayedSpinner";
import RefreshButton from "./RefreshButton";
import { MatrixClientContext } from "./contexts";
class DashboardTab extends Component {
constructor(props) {
super(props);
this.state = {
apiAdress: "_matrix/client/r0/watcha_admin_stats",
datas: undefined,
loading: true,
};
}
static contextType = MatrixClientContext;
export default () => {
const { t } = useTranslation();
getDatas = async () => {
const client = this.context;
let datas;
try {
const adminStatsRequest = await fetch(
new URL(this.state.apiAdress, client.baseUrl),
{
method: "GET",
headers: {
Authorization: "Bearer " + client.getAccessToken(),
},
}
);
datas = JSON.parse(await adminStatsRequest.text());
} catch (e) {
console.log("error: " + e);
return;
}
const { data, loading, error } = useGet({ path: "watcha_admin_stats" });
this.setState({ datas, loading: false });
};
if (error) {
console.error(error);
componentDidMount() {
this.getDatas();
}
return loading || !data ? (
<DelayedSpinner />
) : (
<div>
<div className="statsPanelsContainer">
<CardStats
lines={[
{ label: t("Members"), data: data.users.local },
{ label: t("Partners"), data: data.users.partners },
{ label: t("Admin"), data: data.admins },
]}
title={t("usersTab:title")}
footer={t("dashboardTab:usersPannel.footerLink")}
tab="users"
/>
<CardStats
lines={
data.rooms.non_direct_rooms_count === 0 &&
data.rooms.direct_rooms_count === 0
? []
: [
{
label: t("Number of rooms"),
data: data.rooms.non_direct_rooms_count,
},
{
label: t("Of which active rooms"),
data:
data.rooms
.non_direct_active_rooms_count,
},
{
label: t(
"Number of personnals conversations"
),
data: data.rooms.direct_rooms_count,
},
{
label: t(
"Of which active personnals conversations"
),
data:
data.rooms.direct_active_rooms_count,
},
]
}
title={t("roomsTab:title")}
footer={t("dashboardTab:roomsPannel.footerLink")}
tab="rooms"
/>
render() {
return this.state.loading || !this.state.datas ? (
<DelayedSpinner />
) : (
<div>
<RefreshButton onClick={this.getDatas} variant="primary" />
<CardDeck className="dashboardPanelsContainer">
<Col>
<RoomsDashboardPanel
datas={this.state.datas.rooms}
tab="rooms"
/>
<ServerStateDashboardPanel
datas={this.state.datas.server}
/>
</Col>
<Col>
<UsersDashboardPanel
datas={this.state.datas.users}
tab="users"
/>
</Col>
</CardDeck>
</div>
</div>
);
};
);
}
}
export default DashboardTab;

@ -0,0 +1,10 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSyncAlt } from "@fortawesome/free-solid-svg-icons";
import Button from "react-bootstrap/Button";
export default ({ variant, onClick }) => (
<Button className="refreshButton" {...{ variant, onClick }}>
<FontAwesomeIcon icon={faSyncAlt} />
</Button>
);

@ -0,0 +1,67 @@
import React from "react";
import { withTranslation } from "react-i18next";
import Card from "react-bootstrap/Card";
import Table from "react-bootstrap/Table";
import Row from "react-bootstrap/Row";
import Container from "react-bootstrap/Container";
import Button from "react-bootstrap/Button";
import { useDispatchContext } from "./contexts";
export default withTranslation()(({ t, datas, tab }) => {
const dispatch = useDispatchContext();
const onAdministrateLinkClick = () => dispatch({ tab });
const contentPanel =
datas.non_direct_rooms_count === 0 && datas.direct_rooms_count === 0 ? (
<div className="noRoomsMessage">
{t("dashboardTab:roomsPanel.noRoomsMessageOne")} <br></br>
{t("dashboardTab:roomsPanel.noRoomsMessageTwo")}
</div>
) : (
<Table>
<tbody>
<tr key={t("dashboardTab:roomsPanel.nonDirectRoomsCount")}>
<td className="sectionPanelLabel">
{t("dashboardTab:roomsPanel.nonDirectRoomsCount")}
</td>
<td className="sectionPanelData">
{`${datas.non_direct_active_rooms_count} / ${datas.non_direct_rooms_count}`}
</td>
</tr>
<tr key={t("dashboardTab:roomsPanel.directRoomsCount")}>
<td className="sectionPanelLabel">
{t("dashboardTab:roomsPanel.directRoomsCount")}
</td>
<td className="sectionPanelData">
{`${datas.direct_active_rooms_count} / ${datas.direct_rooms_count}`}
</td>
</tr>
</tbody>
</Table>
);
return (
<Card className="dashboardPanel">
<Card.Header>
<span>{t("dashboardTab:roomsPanel.title")}</span>
<Button
className="dashboardAdministrateButton"
onClick={onAdministrateLinkClick}
>
{t("dashboardTab:roomsPanel.administrateButton")}
</Button>
</Card.Header>
<Card.Body>
<Container fluid>
<Row
className="dashboardPanelSection"
key={t("dashboardTab:roomsPanel.roomsPerType")}
>
{contentPanel}
</Row>
</Container>
</Card.Body>
</Card>
);
});

@ -31,7 +31,7 @@ export default () => {
};
const requestParams = {
path: "watcha_extend_room_list",
path: "watcha_room_list",
lazy: true,
resolve,
};

@ -0,0 +1,87 @@
import React from "react";
import { withTranslation } from "react-i18next";
import Card from "react-bootstrap/Card";
import Table from "react-bootstrap/Table";
import Row from "react-bootstrap/Row";
import Container from "react-bootstrap/Container";
export default withTranslation()(({ t, datas }) => {
return (
<Card className="dashboardPanel">
<Card.Header>
<span>{t("dashboardTab:serverStatePannel:title")}</span>
</Card.Header>
<Card.Body>
<Container fluid>
<Row
className="dashboardPanelSection"
key={t("dashboardTab:serverStatePannel:title")}
>
<Table>
<tbody>
<tr
key={t(
"dashboardTab:serverStatePannel.version"
)}
>
<td className="sectionPanelLabel">
{t(
"dashboardTab:serverStatePannel.version"
)}
</td>
<td className="sectionPanelData">
{datas.watcha_release}
</td>
</tr>
<tr
key={t(
"dashboardTab:serverStatePannel.installDate"
)}
>
<td className="sectionPanelLabel">
{t(
"dashboardTab:serverStatePannel.installDate"
)}
</td>
<td className="sectionPanelData">
{datas.install_date}
</td>
</tr>
<tr
key={t(
"dashboardTab:serverStatePannel.upgradeDate"
)}
>
<td className="sectionPanelLabel">
{t(
"dashboardTab:serverStatePannel.upgradeDate"
)}
</td>
<td className="sectionPanelData">
{datas.upgrade_date}
</td>
</tr>
<tr
key={t(
"dashboardTab:serverStatePannel.diskUsage"
)}
>
<td className="sectionPanelLabel">
{t(
"dashboardTab:serverStatePannel.diskUsage"
)}
</td>
<td className="sectionPanelData">
{`${Math.round(
datas.disk.used / 1000000000
)} Go (${datas.disk.percent}%)`}
</td>
</tr>
</tbody>
</Table>
</Row>
</Container>
</Card.Body>
</Card>
);
});

@ -335,7 +335,7 @@ class UserRightPanel extends Component {
const userId = encodeURIComponent(this.props.user.userId);
const SERVER_REQUEST = await fetch(
new URL(
`_matrix/client/r0/watcha_update_partner_to_member/${userId}`,
`_matrix/client/r0/watcha_update_user_role/${userId}`,
client.baseUrl
),
{
@ -345,6 +345,9 @@ class UserRightPanel extends Component {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
role: "collaborator",
}),
}
);
const RESPONSE = JSON.parse(await SERVER_REQUEST.text());

@ -0,0 +1,216 @@
import React from "react";
import { withTranslation } from "react-i18next";
import Card from "react-bootstrap/Card";
import Table from "react-bootstrap/Table";
import Row from "react-bootstrap/Row";
import Container from "react-bootstrap/Container";
import Accordion from "react-bootstrap/Accordion";
import Button from "react-bootstrap/Button";
import OverlayTrigger from "react-bootstrap/OverlayTrigger";
import Popover from "react-bootstrap/Popover";
import { useDispatchContext } from "./contexts";
import infoCircle from "./images/info-circle.svg";
export default withTranslation()(({ t, datas, tab }) => {
const standardSections = [
{
title: t("dashboardTab:usersPanel.usersPerRole"),
labels: [
t("dashboardTab:usersPanel.administrators"),
t("dashboardTab:usersPanel.collaborators"),
t("dashboardTab:usersPanel.partners"),
],
datas: [
datas.users_per_role.administrators,
datas.users_per_role.collaborators,
datas.users_per_role.partners,
],
},
{
title: t("dashboardTab:usersPanel.connectedUsers"),
labels: [
t("dashboardTab:usersPanel.loggedUsers"),
t("dashboardTab:usersPanel.monthlyUsers"),
t("dashboardTab:usersPanel.weeklyUsers"),
],
datas: [
datas.connected_users.number_of_users_logged_at_least_once,
datas.connected_users.number_of_last_month_logged_users,
datas.connected_users.number_of_last_week_logged_users,
],
},
{
title: t("dashboardTab:usersPanel.otherStatistics"),
labels: [t("dashboardTab:usersPanel.pendingInvitationUsers")],
datas: [
datas.other_statistics.number_of_users_with_pending_invitation,
],
},
];
const getInfoCircleTag = label => {
let tooltipMessage = "";
switch (label) {
case t("dashboardTab:usersPanel.administrators"):
tooltipMessage = t(
"dashboardTab:usersPanel.administratorTooltip"
);
break;
case t("dashboardTab:usersPanel.collaborators"):
tooltipMessage = t(
"dashboardTab:usersPanel.collaboratorTooltip"
);
break;
case t("dashboardTab:usersPanel.partners"):
tooltipMessage = t("dashboardTab:usersPanel.partnerTooltip");
break;
case t("dashboardTab:usersPanel.pendingInvitationUsers"):
tooltipMessage = t(
"dashboardTab:usersPanel.pendingInvitationTooltip"
);
break;
default:
return;
}
const infoCircleTag = (
<OverlayTrigger
overlay={
<Popover className="tooltipMessage">
<Popover.Content>{tooltipMessage}</Popover.Content>
</Popover>
}
placement="right"
>
<img src={infoCircle}></img>
</OverlayTrigger>
);
return infoCircleTag;
};
const dispatch = useDispatchContext();
const onAdministrateLinkClick = () => dispatch({ tab });
const onAdminUserClick = userId => dispatch({ tab: "users", userId });
const getStandardPanelContent = sections => {
const panelContent = [];
for (const index in sections) {
const section = sections[index];
if (section.datas) {
const sectionContent = [];
for (const index in section.labels) {
const label = section.labels[index];
const labelCell =
label ===
t("dashboardTab:usersPanel.administrators") ? (
<td className="sectionPanelAdminListLabel">
{getAdminPanelContent(
datas.administrators_users
)}
</td>
) : (
<td className="sectionPanelLabel">
{label} {getInfoCircleTag(label)}
</td>
);
sectionContent.push(
<tr key={label}>
{labelCell}
<td className="sectionPanelData">
{section.datas[index]}
</td>
</tr>
);
}
if (sectionContent) {
panelContent.push(
<Row
className="dashboardPanelSection"
key={section.title}
>
<fieldset className="watcha-fieldset">
<legend className="watcha-legend">
{section.title}
</legend>
<Table>
<tbody>{sectionContent}</tbody>
</Table>
</fieldset>
</Row>
);
}
}
}
return panelContent;
};
const getAdminPanelContent = adminList => {
const adminPanelContent = [];
const sectionContent = [];
for (const index in adminList) {
const admin = adminList[index];
sectionContent.push(
<tr key={admin.user_id}>
<td
className="adminUserRow"
onClick={() => onAdminUserClick(admin.user_id)}
>
{`${setAdminName(admin.displayname, admin.user_id)} ${
admin.email ? `(${admin.email})` : ""
}`}
</td>
</tr>
);
}
if (sectionContent) {
adminPanelContent.push(
<Accordion key={t("dashboardTab:usersPanel.administrators")} defaultActiveKey="0">
<Card>
<Accordion.Toggle as={Card.Header} eventKey="1">
{t("dashboardTab:usersPanel.administrators")}{" "}
{getInfoCircleTag(
t("dashboardTab:usersPanel.administrators")
)}
</Accordion.Toggle>
<Accordion.Collapse eventKey="1">
<Card.Body>
{" "}
<Table>
<tbody>{sectionContent}</tbody>
</Table>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
);
}
return adminPanelContent;
};
const setAdminName = (displayname, adminUserID) => {
return displayname || adminUserID.replace("@", "").split(":")[0];
};
const standardPanelContent = getStandardPanelContent(standardSections);
return (
<Card className="dashboardPanel">
<Card.Header>
<span>{t("usersTab:title")}</span>
<Button
className="dashboardAdministrateButton"
onClick={onAdministrateLinkClick}
>
{t("dashboardTab:usersPanel.administrateButton")}
</Button>
</Card.Header>
<Card.Body>
<Container fluid>{standardPanelContent}</Container>
</Card.Body>
</Card>
);
});

@ -112,17 +112,12 @@ export default ({ userId }) => {
const resolve = data =>
data.map(item => ({
userId: item.name,
itemId: item.name,
displayName: item.displayname || "",
emailAddress: item.email || "",
userId: item.user_id,
itemId: item.user_id,
displayName: item.display_name || "",
emailAddress: item.email_address || "",
lastSeen: item.last_seen || null,
role:
item.admin === 1
? "administrator"
: item.is_partner === 1
? "partner"
: "collaborator",
status: item.is_active === 1 ? "active" : "inactive",
role: item.role,
status: item.status,
creationTs: item.creation_ts * 1000,
}));

Loading…
Cancel
Save