frontend, added user active flag, localization

This commit is contained in:
2024-03-30 11:22:59 +01:00
parent 9fc0a19f59
commit 0125c83bea
20 changed files with 220 additions and 92 deletions

View File

@@ -56,7 +56,7 @@ export default function EditGroupView(props) {
<div className={"col-sm-6"}>
<ol className={"breadcrumb float-sm-right"}>
<li className={"breadcrumb-item"}><Link to={"/admin/dashboard"}>Home</Link></li>
<li className="breadcrumb-item active"><Link to={"/admin/groups"}>Group</Link></li>
<li className="breadcrumb-item active"><Link to={"/admin/groups"}>{L("account.group")}</Link></li>
<li className="breadcrumb-item active">{ isNewGroup ? L("general.new") : groupId }</li>
</ol>
</div>
@@ -68,13 +68,15 @@ export default function EditGroupView(props) {
<div className={"col-6 pl-5 pr-5"}>
<form role={"form"} onSubmit={(e) => this.submitForm(e)}>
<div className={"form-group"}>
<label htmlFor={"name"}>Group Name</label>
<label htmlFor={"name"}>{L("account.group_name")}</label>
<input type={"text"} className={"form-control"} placeholder={"Name"}
name={"name"} id={"name"} maxLength={32} value={group.name}/>
</div>
<div className={"form-group"}>
<label htmlFor={"color"}>Color</label>
<label htmlFor={"color"}>
{L("account.color")}
</label>
<div>
<ColorPicker
value={group.color}
@@ -88,13 +90,15 @@ export default function EditGroupView(props) {
</div>
<Link to={"/admin/groups"} className={"btn btn-info mt-2 mr-2"}>
&nbsp;Back
&nbsp;{L("general.go_back")}
</Link>
<button type={"submit"} className={"btn btn-primary mt-2"}>Submit</button>
<button type={"submit"} className={"btn btn-primary mt-2"}>
{L("general.submit")}
</button>
</form>
</div>
<div className={"col-6"}>
<h3>Members</h3>
<h3>{L("account.members")}</h3>
</div>
</div>
</div>

View File

@@ -52,8 +52,8 @@ export default function GroupListView(props) {
const columnDefinitions = [
new NumericColumn(L("general.id"), "id"),
new StringColumn(L("group.name"), "name"),
new NumericColumn(L("group.member_count"), "memberCount"),
new StringColumn(L("account.name"), "name"),
new NumericColumn(L("account.member_count"), "memberCount"),
actionColumn,
];
@@ -66,7 +66,7 @@ export default function GroupListView(props) {
<div className={"col-sm-6"}>
<ol className={"breadcrumb float-sm-right"}>
<li className={"breadcrumb-item"}><Link to={"/admin/dashboard"}>Home</Link></li>
<li className="breadcrumb-item active">Groups</li>
<li className="breadcrumb-item active">{L("account.groups")}</li>
</ol>
</div>
</div>

View File

@@ -9,7 +9,8 @@ import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import {API_DATETIME_FORMAT} from "shared/constants";
import {format, toDate} from "date-fns";
import {FormControl, FormGroup, FormLabel, MenuItem, Select} from "@material-ui/core";
import {Box, FormControl, FormGroup, FormLabel, IconButton, MenuItem, Select} from "@material-ui/core";
import {ExpandLess, ExpandMore} from "@material-ui/icons";
export default function LogView(props) {
@@ -59,6 +60,16 @@ export default function LogView(props) {
});
}, [api, showDialog, logLevel, timestamp, query]);
const onToggleDetails = useCallback(entry => {
let newLogEntries = [...logEntries];
for (const logEntry of newLogEntries) {
if (logEntry.id === entry.id) {
logEntry.showDetails = !logEntry.showDetails;
}
}
setLogEntries(newLogEntries);
}, [logEntries]);
useEffect(() => {
// TODO: wait for user to finish typing before force reloading
setForceReload(forceReload + 1);
@@ -68,7 +79,19 @@ export default function LogView(props) {
let column = new DataColumn(L("logs.message"), "message");
column.sortable = false;
column.renderData = (L, entry) => {
return <pre>{entry.message}</pre>
return <Box display={"grid"} gridTemplateColumns={"40px auto"}>
<Box alignSelf={"top"} textAlign={"center"}>
<IconButton size={"small"} onClick={() => onToggleDetails(entry)}
title={L(entry.showDetails ? "logs.hide_details" : "logs.show_details")}>
{entry.showDetails ? <ExpandLess /> : <ExpandMore />}
</IconButton>
</Box>
<Box alignSelf={"center"}>
<pre>
{entry.showDetails ? entry.message : entry.message.split("\n")[0]}
</pre>
</Box>
</Box>
}
return column;
})();

View File

@@ -1,13 +1,45 @@
import * as React from "react";
import {Link} from "react-router-dom";
import {format, getDaysInMonth} from "date-fns";
import {CircularProgress, Icon} from "@material-ui/core";
import {useCallback, useEffect, useState} from "react";
import {CircularProgress} from "@material-ui/core";
import {useCallback, useContext, useEffect, useState} from "react";
import {LocaleContext} from "shared/locale";
import {LibraryBooks, People} from "@material-ui/icons";
import {ArrowCircleRight, Groups} from "@mui/icons-material";
const StatBox = (props) => <div className={"col-lg-3 col-6"}>
<div className={"small-box bg-" + props.color}>
<div className={"inner"}>
{props.count ?
<>
<h3>{props.count}</h3>
<p>{props.text}</p>
</> : <CircularProgress variant={"determinate"} />
}
</div>
<div className={"icon"}>
{props.icon}
</div>
<Link to={props.link} className={"small-box-footer text-right p-1"}>
More info <ArrowCircleRight />
</Link>
</div>
</div>
export default function Overview(props) {
const [fetchStats, setFetchStats] = useState(true);
const [stats, setStats] = useState(null);
const {translate: L, currentLocale, requestModules} = useContext(LocaleContext);
useEffect(() => {
requestModules(props.api, ["general", "admin"], currentLocale).then(data => {
if (!data.success) {
props.showDialog("Error fetching translations: " + data.msg);
}
});
}, [currentLocale]);
const onFetchStats = useCallback((force = false) => {
if (force || fetchStats) {
@@ -16,7 +48,7 @@ export default function Overview(props) {
if (res.success) {
setStats(res.data);
} else {
props.showDialog("Error fetching stats: " + res.msg, "Error fetching stats");
props.showDialog(res.msg, L("admin.fetch_stats_error"));
}
});
}
@@ -73,12 +105,12 @@ export default function Overview(props) {
<div className={"container-fluid"}>
<div className={"row mb-2"}>
<div className={"col-sm-6"}>
<h1 className={"m-0 text-dark"}>Dashboard</h1>
<h1 className={"m-0 text-dark"}>{L("admin.dashboard")}</h1>
</div>
<div className={"col-sm-6"}>
<ol className={"breadcrumb float-sm-right"}>
<li className={"breadcrumb-item"}><Link to={"/admin/dashboard"}>Home</Link></li>
<li className="breadcrumb-item active">Dashboard</li>
<li className="breadcrumb-item active">{L("admin.dashboard")}</li>
</ol>
</div>
</div>
@@ -87,24 +119,18 @@ export default function Overview(props) {
<section className={"content"}>
<div className={"container-fluid"}>
<div className={"row"}>
<div className={"col-lg-3 col-6"}>
<div className="small-box bg-info">
<div className={"inner"}>
{stats ?
<>
<h3>{stats.userCount}</h3>
<p>Users registered</p>
</> : <CircularProgress variant={"determinate"} />
}
</div>
<div className="icon">
<Icon icon={"users"} />
</div>
<Link to={"/admin/users"} className="small-box-footer">
More info <Icon icon={"arrow-circle-right"}/>
</Link>
</div>
</div>
<StatBox color={"info"} count={stats?.userCount}
text={L("admin.users_registered")}
icon={<People />}
link={"/admin/users"} />
<StatBox color={"success"} count={stats?.groupCount}
text={L("admin.available_groups")}
icon={<Groups />}
link={"/admin/users"} />
<StatBox color={"warning"} count={stats?.pageCount}
text={L("admin.routes_defined")}
icon={<LibraryBooks />}
link={"/admin/routes"} />
</div>
</div>
</section>

View File

@@ -5,7 +5,7 @@ import {
BoolColumn,
ControlsColumn,
DataColumn,
DataTable,
DataTable, DateTimeColumn,
NumericColumn,
StringColumn
} from "shared/elements/data-table";
@@ -48,7 +48,11 @@ export default function UserListView(props) {
const groupColumn = (() => {
let column = new DataColumn(L("account.groups"), "groups");
column.renderData = (L, entry) => {
return Object.values(entry.groups).map(group => <Chip key={"group-" + group.id} label={group.name}/>)
return Object.values(entry.groups).map(group => <Chip
key={"group-" + group.id}
style={{ backgroundColor: group.color }}
label={group.name} />
)
}
return column;
})();
@@ -59,6 +63,8 @@ export default function UserListView(props) {
new StringColumn(L("account.full_name"), "fullName"),
new StringColumn(L("account.email"), "email"),
groupColumn,
new DateTimeColumn(L("account.registered_at"), "registeredAt"),
new BoolColumn(L("account.active"), "active", { align: "center" }),
new BoolColumn(L("account.confirmed"), "confirmed", { align: "center" }),
new ControlsColumn(L("general.controls"), [
{ label: L("general.edit"), element: EditIcon, onClick: (entry) => navigate(`/admin/user/${entry.id}`) }

View File

@@ -83,7 +83,11 @@ export function DataTable(props) {
title={L("general.sort_by") + ": " + column.label}
onClick={() => onChangeSort(index, column)}
align={column.align}>
{sortColumn === index ? (sortAscending ? <ArrowUpwardIcon /> : <ArrowDownwardIcon />): <></>}{column.renderHead(index)}
{sortColumn === index ?
(sortAscending ? <ArrowUpwardIcon /> : <ArrowDownwardIcon />) :
<></>
}
{column.renderHead(index)}
</TableCell>);
} else {
headerRow.push(<TableCell key={"col-" + index} align={column.align}>