Files
dps-webshop/01-frontend/src/helper/adminpanel/ItemsInfo.tsx
2025-06-22 13:06:57 +02:00

249 lines
8.4 KiB
TypeScript

import DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit";
import {Box, Button, IconButton, Toolbar, useTheme} from "@mui/material";
import {Gauge, gaugeClasses} from "@mui/x-charts";
import {DataGrid, GridColDef, GridRowId, GridRowSelectionModel} from "@mui/x-data-grid";
import {useMutation, useQuery} from "@tanstack/react-query";
import {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import Item from "../../components/Item";
import {mapValueToColor} from "../../util/ColorUtil.tsx";
import {useAccount} from "../AccountProvider.tsx";
import {deleteItemQuery, fetchItems} from "../query/Queries.tsx";
import ItemImageDialog from "./ItemImageDialog.tsx";
import NewItemDialog from "./NewItemDialog.tsx";
export default function ItemsInfo() {
const theme = useTheme();
const {t} = useTranslation();
const [rows, setRows] = useState<Item[]>([]);
const [selectedRows, setSelectedRows] = useState<Set<GridRowId>>(new Set());
const [editImageDialog, setEditImageDialog] = useState(false);
const [newItemDialog, setNewItemDialog] = useState(false);
const [selectedItem, setSelectedItem] = useState<Item | null>(null);
const [isFarmStationImage, setIsFarmStationImage] = useState(false);
function handleImageEdit(item: Item) {
setIsFarmStationImage(false);
setSelectedItem(item);
setEditImageDialog(true);
console.log("IconEdit", item);
}
function handleFarmImageEdit(item: Item) {
setIsFarmStationImage(true);
setSelectedItem(item);
setEditImageDialog(true);
console.log("IconEdit", item);
}
function handleAddItem() {
setNewItemDialog(true);
}
const {user: loginData} = useAccount();
const {data} = useQuery({
queryKey: ["fetchItems", loginData],
queryFn: () => fetchItems(),
retry: 3,
retryDelay: 1000,
});
useEffect(() => {
if (data) {
setRows(data);
}
}, [data]);
const handleSelectionChange = (newSelection: GridRowSelectionModel) => {
setSelectedRows(newSelection.ids);
};
const deleteItem = useMutation({
mutationFn: (uuid: string) =>
deleteItemQuery(uuid),
});
const handleDeleteSelected = async () => {
await Promise.all(
Array.from(selectedRows).map(async (row) => {
await deleteItem.mutateAsync(rows.find(item => item.id === row)?.uuid || "");
})
);
setRows(rows.filter((row) => !selectedRows.has(row.id)));
};
const columns: GridColDef<(typeof rows)[number]>[] = [
{field: 'id', headerName: 'ID', width: 60},
{
field: 'uuid',
headerName: t('uuid'),
type: "string",
width: 120,
editable: true
},
{
field: 'name',
headerName: t('name'),
width: 200,
editable: true,
},
{
field: 'category',
headerName: t('category'),
width: 150,
editable: true,
valueFormatter: (val) => t(val),
},
{
field: 'description',
headerName: t('description'),
width: 150,
editable: true,
},
{
field: 'price100',
headerName: t('price100€'),
width: 100,
editable: true,
type: 'number',
valueFormatter: (val) => (val / 100).toFixed(2),
},
{
field: 'discount100',
headerName: t('discount100'),
width: 120,
editable: true,
type: 'number'
},
{
field: 'stock',
headerName: t('stock'),
width: 100,
editable: true,
type: 'number',
renderCell: params => <Gauge value={Math.min(params.row.stock, params.row.stockExpected)} valueMin={0}
valueMax={params.row.stockExpected} startAngle={-90} endAngle={90} sx={{
[`& .${gaugeClasses.valueArc}`]: {
fill: () => {
return mapValueToColor(0, params.row.stockExpected, params.row.stock)
},
},
}} text={() => `${params.row.stock}`}/>
},
{
field: 'rating',
headerName: t('rating'),
width: 100,
editable: false, //the rating is averaged from ratings
type: 'number',
renderCell: params => <Gauge value={Math.min(params.row.rating, 10)} valueMin={0} valueMax={10}
startAngle={-90} endAngle={90} sx={{
[`& .${gaugeClasses.valueArc}`]: {
fill: () => {
return mapValueToColor(0, 10, params.row.rating)
},
},
}} text={() => `${params.row.rating.toFixed(2)}`}/>
},
{
field: "actualPrice",
headerName: t('actualPrice'),
width: 90,
editable: false,
valueGetter: (_, row) => (row.price100 / 100 * ((100 - row.discount100) / 100)).toFixed(2)
},
{
field: 'images',
headerName: t('images'),
width: 90,
editable: false,
renderCell: params => <IconButton onClick={() => handleImageEdit(params.row)}> <EditIcon/> </IconButton>,
},
{
field: 'farmImage',
headerName: t('fsImage'),
width: 90,
editable: false,
renderCell: params => <IconButton onClick={() => handleFarmImageEdit(params.row)}> <EditIcon/>
</IconButton>,
}
];
return (
<Box
className="page-table"
sx={{
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.secondary
}}
>
<DataGrid
rows={rows}
columns={columns}
initialState={{}}
checkboxSelection
disableRowSelectionOnClick
onRowSelectionModelChange={handleSelectionChange}
slots={{
toolbar: () => (
<Toolbar>
<Button
variant="contained"
color="error"
startIcon={<DeleteIcon/>}
onClick={handleDeleteSelected}
disabled={selectedRows.size === 0}
sx={{
marginRight: 1
}}
>
{t('deleteProduct')}
</Button>
<Button
variant="contained"
color="primary"
startIcon={<DeleteIcon/>}
onClick={handleAddItem}
sx={{
marginRight: 1
}}
>
{t('addProduct')}
</Button>
</Toolbar>
)
}}
showToolbar
processRowUpdate={(updatedRow) => {
setRows(rows.map(row => row.id === updatedRow.id ? updatedRow : row));
//TODO: make REST callback
return updatedRow;
}}
/>
{selectedItem && (
<ItemImageDialog
open={editImageDialog}
onClose={() => setEditImageDialog(false)}
item={selectedItem}
onSuccess={() => {
// Refresh data or update UI
console.log('Image uploaded successfully');
}}
isFarmStationImage={isFarmStationImage}
/>
)}
<NewItemDialog
open={newItemDialog}
onClose={() => setNewItemDialog(false)}
/>
</Box>
);
}