Polish mobile layout and add collapsible sidebar
This commit is contained in:
@@ -7,9 +7,11 @@ import {
|
||||
Chip,
|
||||
FormControlLabel,
|
||||
Paper,
|
||||
Stack,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||
|
||||
import { api, getApiErrorMessage } from "../api";
|
||||
@@ -26,6 +28,7 @@ type UserDto = {
|
||||
};
|
||||
|
||||
export default function AdminUsersPage() {
|
||||
const isMobile = useMediaQuery("(max-width:767.95px)");
|
||||
const { toast } = useToast();
|
||||
const { confirmAction } = useDialogActions();
|
||||
const { t } = useI18n();
|
||||
@@ -149,23 +152,24 @@ export default function AdminUsersPage() {
|
||||
], [remove, sendReset, setAdminRole, t]);
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Paper sx={{ p: { xs: 1.5, sm: 2 } }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 950, mb: 0.5 }}>
|
||||
{t("adminUsersTitle")}
|
||||
</Typography>
|
||||
<Typography sx={{ color: "text.secondary", mb: 2 }}>{t("adminUsersSubtitle")}</Typography>
|
||||
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Paper sx={{ p: { xs: 1.5, sm: 2 }, mb: 2 }}>
|
||||
<Typography sx={{ fontWeight: 900, mb: 1 }}>{t("adminUsersCreateUser")}</Typography>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 1.5 }}>
|
||||
<TextField label={t("profileEmail")} value={newEmail} onChange={(e) => setNewEmail(e.target.value)} />
|
||||
<TextField label={t("profileNewPassword")} type="password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} />
|
||||
<TextField label={t("profileEmail")} value={newEmail} onChange={(e) => setNewEmail(e.target.value)} fullWidth />
|
||||
<TextField label={t("profileNewPassword")} type="password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} fullWidth />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, mt: 1.5, flexWrap: "wrap" }}>
|
||||
<Box sx={{ display: "flex", alignItems: { xs: "stretch", sm: "center" }, justifyContent: "space-between", gap: 2, mt: 1.5, flexWrap: "wrap" }}>
|
||||
<FormControlLabel control={<Checkbox checked={newIsAdmin} onChange={(e) => setNewIsAdmin(e.target.checked)} />} label={t("adminUsersAdmin")} />
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!canCreate || loading}
|
||||
sx={{ width: { xs: "100%", sm: "auto" } }}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await api.post("/users", { email: newEmail, password: newPassword, roles: newIsAdmin ? ["Admin"] : [] });
|
||||
@@ -185,33 +189,78 @@ export default function AdminUsersPage() {
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<Paper sx={{ borderRadius: 3, border: "1px solid", borderColor: "divider", overflow: "hidden" }}>
|
||||
<DataGrid
|
||||
autoHeight
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
disableRowSelectionOnClick
|
||||
loading={loading}
|
||||
pageSizeOptions={[5, 10, 25]}
|
||||
initialState={{
|
||||
pagination: { paginationModel: { pageSize: 10, page: 0 } },
|
||||
sorting: { sortModel: [{ field: "email", sort: "asc" }] },
|
||||
}}
|
||||
sx={{
|
||||
border: 0,
|
||||
'& .MuiDataGrid-columnHeaders': {
|
||||
backgroundColor: 'action.hover',
|
||||
fontWeight: 800,
|
||||
},
|
||||
'& .MuiDataGrid-cell': {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{!loading && rows.length === 0 ? (
|
||||
<Typography sx={{ color: "text.secondary", py: 2, textAlign: "center" }}>{t("adminUsersNoUsers")}</Typography>
|
||||
) : null}
|
||||
</Paper>
|
||||
{isMobile ? (
|
||||
<Stack spacing={1.5}>
|
||||
{!loading && rows.length === 0 ? (
|
||||
<Typography sx={{ color: "text.secondary", py: 2, textAlign: "center" }}>{t("adminUsersNoUsers")}</Typography>
|
||||
) : null}
|
||||
{rows.map((row) => {
|
||||
const user = row.raw as UserDto;
|
||||
const isAdmin = user.roles.includes("Admin");
|
||||
return (
|
||||
<Paper key={row.id} sx={{ p: 1.5, borderRadius: 3 }}>
|
||||
<Stack spacing={1.25}>
|
||||
<Box>
|
||||
<Typography sx={{ fontWeight: 900, overflowWrap: "anywhere" }}>
|
||||
{row.userName || row.email || row.id}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: "text.secondary", overflowWrap: "anywhere" }}>
|
||||
{row.email || "—"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", gap: 0.75, flexWrap: "wrap" }}>
|
||||
{(row.roles as string[]).length ? (row.roles as string[]).map((role) => (
|
||||
<Chip key={role} size="small" label={role} variant="outlined" />
|
||||
)) : <Chip size="small" label="—" variant="outlined" />}
|
||||
<Chip size="small" label={row.emailConfirmed ? t("yes") : t("noWord")} color={row.emailConfirmed ? "success" : "default"} variant={row.emailConfirmed ? "filled" : "outlined"} />
|
||||
</Box>
|
||||
|
||||
<Stack spacing={1}>
|
||||
<Button variant={isAdmin ? "contained" : "outlined"} onClick={() => void setAdminRole(user, !isAdmin)} fullWidth>
|
||||
{t("adminUsersAdmin")}
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={() => void sendReset(user)} fullWidth>
|
||||
{t("adminUsersSendReset")}
|
||||
</Button>
|
||||
<Button color="error" variant="outlined" onClick={() => void remove(user)} fullWidth>
|
||||
{t("adminUsersDelete")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
) : (
|
||||
<Paper sx={{ borderRadius: 3, border: "1px solid", borderColor: "divider", overflow: "hidden" }}>
|
||||
<DataGrid
|
||||
autoHeight
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
disableRowSelectionOnClick
|
||||
loading={loading}
|
||||
pageSizeOptions={[5, 10, 25]}
|
||||
initialState={{
|
||||
pagination: { paginationModel: { pageSize: 10, page: 0 } },
|
||||
sorting: { sortModel: [{ field: "email", sort: "asc" }] },
|
||||
}}
|
||||
sx={{
|
||||
border: 0,
|
||||
'& .MuiDataGrid-columnHeaders': {
|
||||
backgroundColor: 'action.hover',
|
||||
fontWeight: 800,
|
||||
},
|
||||
'& .MuiDataGrid-cell': {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{!loading && rows.length === 0 ? (
|
||||
<Typography sx={{ color: "text.secondary", py: 2, textAlign: "center" }}>{t("adminUsersNoUsers")}</Typography>
|
||||
) : null}
|
||||
</Paper>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user