AccountInfo
This commit is contained in:
60
01-frontend/package-lock.json
generated
60
01-frontend/package-lock.json
generated
@@ -16,6 +16,7 @@
|
||||
"@mui/icons-material": "^7.0.2",
|
||||
"@mui/material": "^7.0.2",
|
||||
"@mui/x-charts": "^8.5.1",
|
||||
"@mui/x-data-grid": "^8.5.2",
|
||||
"@tanstack/react-query": "^5.79.2",
|
||||
"chart.js": "^4.4.9",
|
||||
"i18next": "^25.2.0",
|
||||
@@ -1550,6 +1551,65 @@
|
||||
"robust-predicates": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "8.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.5.2.tgz",
|
||||
"integrity": "sha512-4KzawLZqRKp3KcGKsTDVz7zkEjACllQD5Zb8ds1QKlA6C3/oIoSU7PsemFLj+RL3rT5aORsLMBl97/egQ5tUhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"@mui/utils": "^7.1.1",
|
||||
"@mui/x-internals": "8.5.2",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"use-sync-external-store": "^1.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid/node_modules/@mui/x-internals": {
|
||||
"version": "8.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.5.2.tgz",
|
||||
"integrity": "sha512-5YhB2AekK7G8d0YrAjg3WNf0uy3V73JD98WNxJhbIlCraQgl8QOQzr2zNO7MAf/X7mZQtjpjuAsiG3+gI2NVyg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"@mui/utils": "^7.1.1",
|
||||
"reselect": "^5.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-internals": {
|
||||
"version": "8.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.5.1.tgz",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@mui/icons-material": "^7.0.2",
|
||||
"@mui/material": "^7.0.2",
|
||||
"@mui/x-charts": "^8.5.1",
|
||||
"@mui/x-data-grid": "^8.5.2",
|
||||
"@tanstack/react-query": "^5.79.2",
|
||||
"chart.js": "^4.4.9",
|
||||
"i18next": "^25.2.0",
|
||||
|
||||
@@ -99,5 +99,9 @@
|
||||
"admin": "Admin",
|
||||
"fsmodel": "Indoor-Farming-Station",
|
||||
"zip": "Postleitzahl",
|
||||
"surname": "Nachname"
|
||||
"surname": "Nachname",
|
||||
"language": "Sprache",
|
||||
"password": "Passwort",
|
||||
"adminize": "Admin ernennen",
|
||||
"unadminize": "Admin abernennen",
|
||||
}
|
||||
|
||||
@@ -99,5 +99,9 @@
|
||||
"admin": "Admin",
|
||||
"fsmodel": "Indoor farming station",
|
||||
"zip": "Zip code",
|
||||
"surname": "Surname"
|
||||
"surname": "Surname",
|
||||
"language": "Language",
|
||||
"password": "Password",
|
||||
"adminize": "Make Admin",
|
||||
"unadminize": "Revoke Admin"
|
||||
}
|
||||
@@ -1,187 +1,224 @@
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import {Box, Button, IconButton, Toolbar, useTheme} from "@mui/material";
|
||||
import AccountType from "../../components/Account";
|
||||
|
||||
const mockAccounts: AccountType[] = [
|
||||
{ id: "1", name: "John Doe", email: "john.doe@example.com", status: "active" },
|
||||
{ id: "2", name: "Jane Smith", email: "jane.smith@example.com", status: "inactive" },
|
||||
{ id: "3", name: "Alice Johnson", email: "alice.johnson@example.com", status: "active" },
|
||||
];
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {DataGrid, GridColDef, GridRowId, GridRowSelectionModel} from "@mui/x-data-grid";
|
||||
import {useState} from "react";
|
||||
|
||||
export default function AccountsInfo() {
|
||||
const theme = useTheme();
|
||||
const {t} = useTranslation();
|
||||
|
||||
const [accounts, setAccounts] = useState<AccountType[]>(mockAccounts);
|
||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||
const [editMode, setEditMode] = useState<{ [key: string]: boolean }>({});
|
||||
|
||||
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchTerm(event.target.value.toLowerCase());
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
setAccounts((prev) => prev.filter((account) => account.id !== id));
|
||||
};
|
||||
|
||||
const handleToggleStatus = (id: string) => {
|
||||
setAccounts((prev) =>
|
||||
prev.map((account) =>
|
||||
account.id === id
|
||||
? {
|
||||
...account,
|
||||
status: account.status === "active" ? "inactive" : "active"
|
||||
function handleCustomerEdit(account: AccountType) {
|
||||
//TODO: implement
|
||||
console.log("CustomerEdit", account);
|
||||
}
|
||||
: account
|
||||
)
|
||||
);
|
||||
|
||||
function handleAccountPasswordEdit(account: AccountType) {
|
||||
//TODO: implement
|
||||
console.log("PasswordEdit", account);
|
||||
}
|
||||
|
||||
const _rows: AccountType[] = [
|
||||
{
|
||||
id: 1,
|
||||
customer: {id: 0, name: 'Big', surname: 'Smoke', address: "Grove Str", country: "USA", zip: "66424"},
|
||||
langI18n: "en",
|
||||
admin: false,
|
||||
email: "smoke@trainwatchers.com",
|
||||
password: "$1$s11alt$h111ash"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
customer: {id: 0, name: 'C', surname: 'J', address: "Grove Str", country: "USA", zip: "66424"},
|
||||
langI18n: "en",
|
||||
admin: true,
|
||||
email: "cj@grv.gov",
|
||||
password: "$1$salt$hash"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
customer: {
|
||||
id: 0,
|
||||
name: 'Flo',
|
||||
surname: 'Rian',
|
||||
address: "Weihbachstreet 555",
|
||||
country: "Germany",
|
||||
zip: "66424"
|
||||
},
|
||||
langI18n: "de",
|
||||
admin: false,
|
||||
email: "flo@ryan",
|
||||
password: "$1$salt$hash"
|
||||
},
|
||||
{
|
||||
id: 41,
|
||||
customer: {
|
||||
id: 0,
|
||||
name: 'Tim',
|
||||
surname: 'Wand',
|
||||
address: "Wandstraße 12",
|
||||
country: "Deutschland",
|
||||
zip: "66420"
|
||||
},
|
||||
langI18n: "de",
|
||||
admin: true,
|
||||
email: "management@shitposting.eu",
|
||||
password: "$5$sa23123lt$has21212h"
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
customer: {
|
||||
id: 0,
|
||||
name: 'Lia',
|
||||
surname: 'Shaprius',
|
||||
address: "Malstatter Str 18",
|
||||
country: "Saarbrooklyn",
|
||||
zip: "66117"
|
||||
},
|
||||
langI18n: "de",
|
||||
admin: false,
|
||||
email: "lia.prius@outlook.de",
|
||||
password: "$1$salt$hash"
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
customer: {
|
||||
id: 0,
|
||||
name: 'Mathu-san',
|
||||
surname: 'Saravanpavan',
|
||||
address: "Solarbro-Alee",
|
||||
country: "Deutschland",
|
||||
zip: "66000"
|
||||
},
|
||||
langI18n: "de",
|
||||
admin: false,
|
||||
email: "mathusan@bro.de",
|
||||
password: "$12$salt12$hash123"
|
||||
},
|
||||
{
|
||||
id: 81,
|
||||
customer: {id: 0, name: 'C', surname: 'J', address: "Grove Str", country: "USA", zip: "66424"},
|
||||
langI18n: "en",
|
||||
admin: false,
|
||||
email: "flo@ryan",
|
||||
password: "$1$salt$hash"
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
customer: {id: 0, name: 'C', surname: 'J', address: "Grove Str", country: "USA", zip: "66424"},
|
||||
langI18n: "en",
|
||||
admin: false,
|
||||
email: "flo@ryan",
|
||||
password: "$1$salt$hash"
|
||||
},
|
||||
]
|
||||
/*
|
||||
for testing purposes
|
||||
for (let i = 0; i < 100; i++) {
|
||||
_rows.push(_rows[5]);
|
||||
}
|
||||
* */
|
||||
|
||||
//TODO: get per REST
|
||||
const [rows, setRows] = useState<AccountType[]>(_rows);
|
||||
const [selectedRows, setSelectedRows] = useState<Set<GridRowId>>(new Set());
|
||||
|
||||
const handleSelectionChange = (newSelection: GridRowSelectionModel) => {
|
||||
setSelectedRows(newSelection.ids);
|
||||
};
|
||||
|
||||
const handleEdit = (id: string, field: keyof AccountType, value: string) => {
|
||||
setAccounts((prev) =>
|
||||
prev.map((account) =>
|
||||
account.id === id ? { ...account, [field]: value } : account
|
||||
)
|
||||
);
|
||||
const handleDeleteSelected = async () => {
|
||||
selectedRows.forEach((row) => {
|
||||
//TODO: send delete command, or send deleteall
|
||||
console.log(row);
|
||||
})
|
||||
|
||||
setRows(rows.filter((row) => !selectedRows.has(row.id)));
|
||||
};
|
||||
|
||||
const toggleEditMode = (id: string) => {
|
||||
setEditMode((prev) => ({
|
||||
...prev,
|
||||
[id]: !prev[id]
|
||||
}));
|
||||
};
|
||||
|
||||
const filteredAccounts = accounts.filter(
|
||||
(account) =>
|
||||
account.name.toLowerCase().includes(searchTerm) ||
|
||||
account.email.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
const columns: GridColDef<(typeof rows)[number]>[] = [
|
||||
{field: 'id', headerName: 'ID', width: 60},
|
||||
{
|
||||
field: 'admin',
|
||||
headerName: t('admin'),
|
||||
type: "boolean",
|
||||
width: 90,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
headerName: t('email'),
|
||||
width: 150,
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
field: 'password',
|
||||
headerName: t('password'),
|
||||
width: 90,
|
||||
editable: true,
|
||||
sortable: false,
|
||||
disableReorder: true,
|
||||
disableExport: true,
|
||||
renderCell: params => <IconButton onClick={() => handleAccountPasswordEdit(params.row)}> <EditIcon/>
|
||||
</IconButton>,
|
||||
},
|
||||
{
|
||||
field: 'langI18n',
|
||||
headerName: t('language'),
|
||||
width: 150,
|
||||
editable: true,
|
||||
},
|
||||
{ //edit billing information button
|
||||
field: "customer",
|
||||
headerName: t('address'),
|
||||
width: 90,
|
||||
sortable: false,
|
||||
disableReorder: true,
|
||||
disableColumnMenu: true,
|
||||
renderCell: params => <IconButton onClick={() => handleCustomerEdit(params.row)}> <EditIcon/> </IconButton>,
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Box sx={{ color: theme.palette.text.primary }}>
|
||||
<Typography variant="h4" gutterBottom align="center">
|
||||
Accounts Management
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
label="Search Accounts"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
<Box
|
||||
className="page-table"
|
||||
sx={{
|
||||
mb: 2,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
'& input': {
|
||||
color: theme.palette.text.primary
|
||||
},
|
||||
'& label': {
|
||||
color: theme.palette.text.secondary
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
borderColor: theme.palette.divider
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
borderColor: theme.palette.text.primary
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main
|
||||
}
|
||||
}
|
||||
}}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary
|
||||
}}
|
||||
>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{["ID", "Name", "Email", "Status", "Actions"].map((col) => (
|
||||
<TableCell
|
||||
key={col}
|
||||
sx={{ color: theme.palette.text.primary, fontWeight: "bold" }}
|
||||
>
|
||||
{col}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{filteredAccounts.map((account) => (
|
||||
<TableRow key={account.id}>
|
||||
<TableCell>{account.id}</TableCell>
|
||||
<TableCell>
|
||||
{editMode[account.id] ? (
|
||||
<TextField
|
||||
value={account.name}
|
||||
onChange={(e) => handleEdit(account.id, "name", e.target.value)}
|
||||
variant="standard"
|
||||
InputProps={{
|
||||
sx: { color: theme.palette.text.primary }
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
account.name
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{editMode[account.id] ? (
|
||||
<TextField
|
||||
value={account.email}
|
||||
onChange={(e) => handleEdit(account.id, "email", e.target.value)}
|
||||
variant="standard"
|
||||
InputProps={{
|
||||
sx: { color: theme.palette.text.primary }
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
account.email
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{account.status}</TableCell>
|
||||
<TableCell align="center">
|
||||
<IconButton color="error" onClick={() => handleDelete(account.id)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
initialState={{}}
|
||||
checkboxSelection
|
||||
disableRowSelectionOnClick
|
||||
onRowSelectionModelChange={handleSelectionChange}
|
||||
slots={{ toolbar: () => (
|
||||
<Toolbar>
|
||||
<Button
|
||||
variant="contained"
|
||||
color={account.status === "active" ? "warning" : "success"}
|
||||
onClick={() => handleToggleStatus(account.id)}
|
||||
sx={{ mx: 1 }}
|
||||
color="error"
|
||||
startIcon={<DeleteIcon/>}
|
||||
onClick={handleDeleteSelected}
|
||||
disabled={selectedRows.size === 0}
|
||||
sx={{
|
||||
marginRight: 1
|
||||
}}
|
||||
>
|
||||
{account.status === "active" ? "Set Inactive" : "Set Active"}
|
||||
{t('deleteAccount')}
|
||||
</Button>
|
||||
<IconButton color="primary" onClick={() => toggleEditMode(account.id)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Toolbar>
|
||||
)}}
|
||||
showToolbar
|
||||
processRowUpdate={(updatedRow, originalRow) => {
|
||||
setRows(rows.map(row => row.id === updatedRow.id ? updatedRow : row));
|
||||
//TODO: make REST callback
|
||||
return updatedRow;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export default function AdminPanel() {
|
||||
</Box>
|
||||
|
||||
{/* Content */}
|
||||
<Box className="page-background" pt={4}>{renderContent()}</Box>
|
||||
<Box className="page-background">{renderContent()}</Box>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -58,11 +58,13 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
padding: 20px 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.toppad {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.page-background-center {
|
||||
align-items: center;
|
||||
@@ -73,6 +75,14 @@
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
.page-table {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
height: var(--page-height);
|
||||
}
|
||||
|
||||
.impressum-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user