249 lines
8.4 KiB
TypeScript
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>
|
|
);
|
|
} |