Display the list of inbox entries

This commit is contained in:
Pierre HUBERT 2025-05-12 19:13:14 +02:00
parent e55b99b264
commit 76f9e37ded
2 changed files with 118 additions and 6 deletions

View File

@ -54,4 +54,19 @@ export class InboxApi {
}) })
).data; ).data;
} }
/**
* Update inbox entry
*/
static async Update(
inbox: InboxEntry & InboxEntryUpdate
): Promise<InboxEntry> {
return (
await APIClient.exec({
uri: `/inbox/${inbox.id}`,
method: "PUT",
jsonData: inbox,
})
).data;
}
} }

View File

@ -1,13 +1,17 @@
import RefreshIcon from "@mui/icons-material/Refresh"; import RefreshIcon from "@mui/icons-material/Refresh";
import { Checkbox, FormControlLabel, IconButton, Tooltip } from "@mui/material"; import { Checkbox, FormControlLabel, IconButton, Tooltip } from "@mui/material";
import { DataGrid, GridColDef, GridRowSelectionModel } from "@mui/x-data-grid";
import React from "react"; import React from "react";
import { InboxApi, InboxEntry } from "../api/InboxApi"; import { InboxApi, InboxEntry } from "../api/InboxApi";
import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider"; import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider";
import { useUnmatchedInboxEntriesCount } from "../hooks/UnmatchedInboxEntriesCountProvider";
import { AmountWidget } from "../widgets/AmountWidget";
import { AsyncWidget } from "../widgets/AsyncWidget"; import { AsyncWidget } from "../widgets/AsyncWidget";
import { DateWidget } from "../widgets/DateWidget";
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer"; import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
import { NewMovementWidget } from "../widgets/NewMovementWidget"; import { NewMovementWidget } from "../widgets/NewMovementWidget";
import { useUnmatchedInboxEntriesCount } from "../hooks/UnmatchedInboxEntriesCountProvider"; import { UploadedFileWidget } from "../widgets/UploadedFileWidget";
export function InboxRoute(): React.ReactElement { export function InboxRoute(): React.ReactElement {
const loadingMessage = useLoadingMessage(); const loadingMessage = useLoadingMessage();
@ -46,9 +50,8 @@ export function InboxRoute(): React.ReactElement {
checked={includeAttached} checked={includeAttached}
control={ control={
<Checkbox <Checkbox
onChange={(e) => { onChange={(_e, value) => {
setIncludeAttached(e.target.checked); setIncludeAttached(value);
reload(false);
}} }}
/> />
} }
@ -65,10 +68,10 @@ export function InboxRoute(): React.ReactElement {
<div style={{ display: "flex", flexDirection: "column", flex: 1 }}> <div style={{ display: "flex", flexDirection: "column", flex: 1 }}>
<div style={{ flex: 1, display: "flex", flexDirection: "column" }}> <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
<AsyncWidget <AsyncWidget
loadKey={loadKey.current} loadKey={loadKey.current + String(includeAttached)}
load={load} load={load}
errMsg="Failed to load the content of inbox!" errMsg="Failed to load the content of inbox!"
build={() => <>todo table</>} build={() => <InboxTable entries={entries!} onReload={reload} />}
/> />
<NewMovementWidget isInbox onCreated={() => reload(false)} /> <NewMovementWidget isInbox onCreated={() => reload(false)} />
</div> </div>
@ -76,3 +79,97 @@ export function InboxRoute(): React.ReactElement {
</MoneyMgrWebRouteContainer> </MoneyMgrWebRouteContainer>
); );
} }
function InboxTable(p: {
entries: InboxEntry[];
onReload: (skipEntries: boolean) => void;
}): React.ReactElement {
const [rowSelectionModel, setRowSelectionModel] =
React.useState<GridRowSelectionModel>([]);
const columns: GridColDef<(typeof p.entries)[number]>[] = [
{
field: "time",
headerName: "Date",
width: 98 + 80,
editable: true,
type: "dateTime",
valueGetter(_, m) {
return new Date(m.time * 1000);
},
valueSetter(v, row) {
row.time = Math.floor(v.getTime() / 1000);
return row;
},
renderCell: (params) => {
return <DateWidget time={params.row.time} />;
},
},
{
field: "label",
headerName: "Label",
flex: 3,
editable: true,
type: "string",
},
{
field: "amount",
headerName: "Amount",
width: 110,
editable: true,
type: "number",
align: "left",
headerAlign: "left",
renderCell: (params) => {
if (params.row.amount)
return <AmountWidget amount={params.row.amount} />;
else return <i>Unspecified</i>;
},
},
{
field: "file",
headerName: "File",
editable: false,
flex: 3,
renderCell: (params) => {
return <UploadedFileWidget file_id={params.row.file_id} />;
},
},
];
return (
<div style={{ flex: 1 }}>
<DataGrid<InboxEntry>
columns={columns}
rows={p.entries}
autoPageSize
checkboxSelection
initialState={{
sorting: {
sortModel: [{ field: "time", sort: "desc" }],
},
columns: {
columnVisibilityModel: {
checked: false,
},
},
}}
onRowSelectionModelChange={(newRowSelectionModel) => {
setRowSelectionModel(newRowSelectionModel);
}}
rowSelectionModel={rowSelectionModel}
processRowUpdate={async (n) => {
try {
return await InboxApi.Update(n);
} catch (e) {
console.error("Failed to update movement information!", e);
alert(`Failed to update row! ${e}`);
throw e;
} finally {
p.onReload(true);
}
}}
/>
</div>
);
}