42 Commits

Author SHA1 Message Date
ad83355833 Update Node.js to v24
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-19 00:20:20 +00:00
d0c20aa68b Update dependency @vitejs/plugin-react to ^4.5.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-18 00:21:29 +00:00
b84305428c Update dependency @mui/x-date-pickers to ^8.5.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-17 00:21:04 +00:00
560e415cb1 Update dependency @eslint/js to ^9.29.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-16 00:21:26 +00:00
4863b1f4af Update Rust crate clap to 4.5.40
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-15 00:21:37 +00:00
6fa87a67d6 Update dependency @mui/x-data-grid to ^8.5.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-14 00:21:49 +00:00
e7219d2e44 Update dependency @mui/x-charts to ^8.5.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-13 00:21:50 +00:00
c9af4e4fc9 Update dependency @types/react to ^19.1.8
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-12 00:22:31 +00:00
6cb4bcf4d4 Update dependency react-router-dom to ^7.6.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-11 00:22:23 +00:00
447b6d89fb Update dependency @types/react to ^19.1.7
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-10 00:22:28 +00:00
a2a7d3d94a Update dependency @fontsource/roboto to ^5.2.6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-09 00:22:07 +00:00
05a5da7527 Update dependency @mui/x-data-grid to ^8.5.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-07 00:21:40 +00:00
43f62802dc Update dependency @mui/x-charts to ^8.5.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-06 00:21:09 +00:00
60f6b94b41 Update dependency @types/react-dom to ^19.1.6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-05 00:21:35 +00:00
5aaefafb48 Update dependency react-router to ^7.6.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-04 00:22:14 +00:00
63af408224 Update materialui to ^7.1.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-03 00:22:47 +00:00
a5966c8d53 Update dependency @mui/x-data-grid to ^8.5.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-02 00:20:49 +00:00
196cb0b5c9 Update dependency @mui/x-charts to ^8.5.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-06-01 00:20:41 +00:00
ba06cd04ff Update dependency @eslint/js to ^9.28.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-31 00:07:09 +00:00
dc4f41f31c Update Rust crate sha2 to 0.11.0-rc.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-30 00:07:40 +00:00
9ba928102c Update Rust crate clap to 4.5.39
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-29 00:07:57 +00:00
7d7ad0ce89 Update dependency @types/react to ^19.1.6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-28 00:08:22 +00:00
d0ff90701f Update dependency react-router-dom to ^7.6.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-27 00:07:18 +00:00
f38482757e Update dependency react-router to ^7.6.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-26 00:07:39 +00:00
7e976d903d Update Rust crate tokio to 1.45.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-25 00:07:32 +00:00
737d7f76d4 Update dependency @mui/x-charts to ^8.4.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-24 00:07:37 +00:00
cf0e42ff0e Update dependency @eslint/js to ^9.27.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-23 00:06:54 +00:00
d2fa9bf9ee Update dependency @types/react to ^19.1.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-22 00:07:20 +00:00
fb5a85311c Update Rust crate jwt-simple to 0.12.12
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-21 00:07:25 +00:00
288d334615 Update dependency ts-pattern to ^5.7.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-20 00:07:21 +00:00
87f017fc42 OIDC functional
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-19 21:08:51 +02:00
43fb8dcda6 Update 2025-05-19 20:58:50 +02:00
a3b9c7cdb1 Fix bad file name
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-19 19:35:26 +02:00
c42b8b1bda Merge branch 'main' of ssh://gitea.communiquons.org:52001/pierre/MoneyMgr
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-05-19 18:36:50 +02:00
bdcbe94c97 Add api_curl to release 2025-05-19 18:36:34 +02:00
caeff985c2 Update Rust crate diesel_migrations to 2.2.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-18 00:07:13 +00:00
bed538793d Update Rust crate actix-session to 0.10.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-17 00:16:01 +00:00
602f20ad18 Update dependency eslint-plugin-react-hooks to ^5.2.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-16 00:16:24 +00:00
457c96b37e Fix FinancesManager export
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-15 22:23:50 +02:00
a3f2b77548 Improve usability
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-15 22:18:36 +02:00
3c5c82371a Fix mui-grid issue after update
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-05-15 21:51:12 +02:00
fb46626cff Update project dependencies
Some checks failed
continuous-integration/drone/push Build is failing
2025-05-15 21:45:36 +02:00
17 changed files with 2290 additions and 1103 deletions

View File

@ -6,7 +6,7 @@ name: default
steps:
# Frontend
- name: web_build
image: node:23
image: node:24
volumes:
- name: web_app
path: /tmp/web_build
@ -38,6 +38,7 @@ steps:
- cd moneymgr_backend
- rustup component add clippy
- cargo clippy -- -D warnings
- cargo clippy --example api_curl -- -D warnings
- name: backend_test
image: rust
@ -51,7 +52,7 @@ steps:
- cargo test
- name: backend_compile
- name: backend_build
image: rust
volumes:
- name: rust_registry
@ -67,15 +68,16 @@ steps:
- cd moneymgr_backend
- mv /tmp/web_build/dist static
- cargo build --release
- ls -lah target/release/moneymgr_backend
- cp target/release/moneymgr_backend /tmp/release
- cargo build --release --example api_curl
- ls -lah target/release/moneymgr_backend target/release/examples/api_curl
- cp target/release/moneymgr_backend target/release/examples/api_curl /tmp/release
# Release
- name: gitea_release
image: plugins/gitea-release
depends_on:
- backend_compile
- backend_build
when:
event:
- tag

View File

@ -3,10 +3,46 @@
Open Source web-based personal expenses tool.
**Note :** This project does not handle authentication itself. Instead, it relies on OpenID to achieve users authentication.
## Setup prod env
1. Install prerequisites:
1. docker
2. docker compose
3. git
2. Clone this git repository:
```bash
git clone https://gitea.communiquons.org/pierre/MoneyMgr
cd MoneyMgr/docker_prod
```
3. Copy and adapt env values
```bash
cp .env.sample .env
nano .env
```
4. Create required directories:
```bash
mkdir -p storage/{db,redis-data,redis-conf,minio}
```
5. Start containers
```bash
docker compose up
```
6. Checkout http://localhost:8000/
> The default credentials are `admin` / `admin`
## Setup dev env
1. Install prerequisites:
1. docker
2. docker-compose
2. docker compose
3. rust
4. node

10
docker_prod/.env.sample Normal file
View File

@ -0,0 +1,10 @@
MINIO_ROOT_USER=rootuser
MINIO_ROOT_PASSWORD=rootpassword
DB_USER=db_user
DB_PASSWORD=db_password
REDIS_PASS=redis_password
WEBSITE_ORIGIN=http://localhost:8000
APP_SECRET=secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret
AUTH_SECRET_KEY=secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret
OIDC_CLIENT_ID=bar
OIDC_CLIENT_SECRET=foo

3
docker_prod/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.env
storage
auth/users.json

View File

@ -0,0 +1,5 @@
- id: ${OIDC_CLIENT_ID}
name: MoneyMgr
description: Money management tool
secret: ${OIDC_CLIENT_SECRET}
redirect_uri: ${APP_ORIGIN}/oidc_cb

View File

@ -0,0 +1,79 @@
services:
minio:
image: minio/minio
user: "1000"
environment:
- MINIO_ROOT_USER=$MINIO_ROOT_USER
- MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD
volumes:
- ./storage/minio:/data
command: [ "minio", "server", "/data", "--console-address", ":9090" ]
ports:
- 9000:9000
- 9090:9090
expose:
- 9000
db:
image: postgres
user: "1000"
ports:
- "5432:5432"
expose:
- 5432
environment:
- POSTGRES_USER=$DB_USER
- POSTGRES_PASSWORD=$DB_PASSWORD
- POSTGRES_DB=moneymgr
volumes:
- ./storage/db:/var/lib/postgresql/data
oidc:
image: pierre42100/basic_oidc
user: "1000"
environment:
- LISTEN_ADDRESS=0.0.0.0:9001
- STORAGE_PATH=/storage
- TOKEN_KEY=$AUTH_SECRET_KEY
- WEBSITE_ORIGIN=http://localhost:9001
- OIDC_CLIENT_ID=$OIDC_CLIENT_ID
- OIDC_CLIENT_SECRET=$OIDC_CLIENT_SECRET
- APP_ORIGIN=$WEBSITE_ORIGIN
expose:
- 9001
ports:
- 9001:9001
volumes:
- ./auth:/storage
redis:
image: redis:alpine
user: "1000"
command: redis-server --requirepass ${REDIS_PASS:-secretredis}
expose:
- 6379
volumes:
- ./storage/redis-data:/data
- ./storage/redis-conf:/usr/local/etc/redis/redis.conf
moneymgr:
image: pierre42100/moneymgr_backend
user: "1000"
ports:
- 8000:8000
environment:
- WEBSITE_ORIGIN=${WEBSITE_ORIGIN}
- SECRET=${APP_SECRET}
- DB_HOST=db
- DB_USERNAME=$DB_USER
- DB_PASSWORD=$DB_PASSWORD
- DB_NAME=moneymgr
- OIDC_CONFIGURATION_URL=http://oidc:9001/.well-known/openid-configuration
- OIDC_PROVIDER_NAME=OIDC
- OIDC_CLIENT_ID=$OIDC_CLIENT_ID
- OIDC_CLIENT_SECRET=$OIDC_CLIENT_SECRET
- S3_ENDPOINT=http://minio:9000
- S3_ACCESS_KEY=$MINIO_ROOT_USER
- S3_SECRET_KEY=$MINIO_ROOT_PASSWORD
- REDIS_HOSTNAME=redis
- REDIS_PASSWORD=${REDIS_PASS:-secretredis}

File diff suppressed because it is too large Load Diff

View File

@ -6,33 +6,34 @@ edition = "2024"
[dependencies]
env_logger = "0.11.8"
log = "0.4.27"
diesel = { version = "2.2.0", features = ["postgres", "r2d2"] }
diesel_migrations = "2.1.0"
clap = { version = "4.5.35", features = ["env", "derive"] }
actix-web = "4"
actix-cors = "0.7.0"
actix-multipart = "0.7.0"
diesel = { version = "2.2.10", features = ["postgres", "r2d2"] }
diesel_migrations = "2.2.0"
clap = { version = "4.5.40", features = ["env", "derive"] }
actix-web = "4.11.0"
actix-cors = "0.7.1"
actix-multipart = "0.7.2"
actix-remote-ip = "0.1.0"
actix-session = { version = "0.10.0", features = ["redis-session"] }
actix-session = { version = "0.10.1", features = ["redis-session"] }
actix-files = "0.6.6"
lazy_static = "1.5.0"
anyhow = "1.0.97"
anyhow = "1.0.98"
serde = { version = "1.0.219", features = ["derive"] }
rust-s3 = "0.36.0-beta.2"
thiserror = "2.0.12"
tokio = "1.44.1"
tokio = "1.45.1"
futures-util = "0.3.31"
serde_json = "1.0.140"
light-openid = "1.0.4"
rand = "0.9.0"
rand = "0.9.1"
ipnet = { version = "2.11.0", features = ["serde"] }
lazy-regex = "3.4.1"
jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] }
jwt-simple = { version = "0.12.12", default-features = false, features = ["pure-rust"] }
mime_guess = "2.0.5"
rust-embed = { version = "8.6.0" }
sha2 = "0.10.8"
rust-embed = { version = "8.7.2" }
sha2 = "0.11.0-rc.0"
base16ct = "0.2.0"
httpdate = "1.0.3"
chrono = "0.4.41"
tempfile = "3.19.1"
zip = "2.6.1"
rust_xlsxwriter = "0.86.1"
tempfile = "3.20.0"
zip = "3.0.0"
rust_xlsxwriter = "0.87.0"

View File

@ -14,7 +14,7 @@ use std::process::Command;
struct Args {
/// URL to Money manager API
#[arg(short('U'), long, env, default_value = "http://localhost:8000/api")]
matrix_gw_url: String,
moneymgr_url: String,
/// Token ID
#[arg(short('i'), long, env)]
@ -39,7 +39,8 @@ struct Args {
fn main() {
let args: Args = Args::parse();
let full_url = format!("{}{}", args.matrix_gw_url, args.uri);
let full_url = format!("{}{}", args.moneymgr_url, args.uri);
log::debug!("Full URL: {full_url}");
let key = HS256Key::from_bytes(args.token_secret.as_bytes());

View File

@ -78,12 +78,15 @@ pub async fn finances_manager_import(auth: AuthExtractor, file: FileExtractor) -
/// Export data to a [FinancesManager](https://gitlab.com/pierre42100/cpp-financesmanager) file
pub async fn finances_manager_export(auth: AuthExtractor) -> HttpResult {
let accounts = accounts_service::get_list_user(auth.user_id()).await?;
let mut accounts = accounts_service::get_list_user(auth.user_id()).await?;
accounts.sort_by_key(|a| a.id());
let mut out = FinancesManagerFile { accounts: vec![] };
for account in accounts {
let movements = movements_service::get_list_account(account.id()).await?;
let mut movements = movements_service::get_list_account(account.id()).await?;
movements.sort_by(|a, b| b.time.cmp(&a.time));
let mut file_account = FinancesManagerAccount {
name: account.name,
movements: Vec::with_capacity(movements.len()),

View File

@ -5,5 +5,5 @@ pub fn sha512(bytes: &[u8]) -> String {
let mut hasher = Sha512::new();
hasher.update(bytes);
let h = hasher.finalize();
format!("{:x}", h)
base16ct::lower::encode_string(h.as_slice())
}

File diff suppressed because it is too large Load Diff

View File

@ -12,38 +12,38 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@fontsource/roboto": "^5.2.5",
"@fontsource/roboto": "^5.2.6",
"@jsonjoy.com/base64": "^1.1.2",
"@mdi/js": "^7.4.47",
"@mdi/react": "^1.6.1",
"@mui/icons-material": "^7.0.1",
"@mui/material": "^7.0.1",
"@mui/x-charts": "^8.2.0",
"@mui/x-data-grid": "^7.28.3",
"@mui/x-date-pickers": "^8.0.0-beta.3",
"@mui/icons-material": "^7.1.1",
"@mui/material": "^7.1.1",
"@mui/x-charts": "^8.5.2",
"@mui/x-data-grid": "^8.5.2",
"@mui/x-date-pickers": "^8.5.2",
"date-and-time": "^3.6.0",
"dayjs": "^1.11.13",
"filesize": "^10.1.6",
"qrcode.react": "^4.2.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router": "^7.4.1",
"react-router-dom": "^7.4.1",
"ts-pattern": "^5.7.0"
"react-router": "^7.6.2",
"react-router-dom": "^7.6.2",
"ts-pattern": "^5.7.1"
},
"devDependencies": {
"@eslint/js": "^9.23.0",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.1",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.23.0",
"eslint-plugin-react-dom": "^1.40.2",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"eslint-plugin-react-x": "^1.40.2",
"globals": "^16.0.0",
"typescript": "~5.8.2",
"typescript-eslint": "^8.29.0",
"vite": "^6.2.5"
"@eslint/js": "^9.29.0",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.5.2",
"eslint": "^9.26.0",
"eslint-plugin-react-dom": "^1.49.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^00.4.20",
"eslint-plugin-react-x": "^1.49.0",
"globals": "^16.1.0",
"typescript": "~5.8.3",
"typescript-eslint": "^8.32.1",
"vite": "^6.3.5"
}
}

View File

@ -121,16 +121,18 @@ function MovementsTable(p: {
const chooseAccount = useSelectAccount();
const [labelFilter, setLabelFilter] = React.useState("");
const [filter, setFilter] = React.useState("");
const filteredList = React.useMemo(() => {
return p.movements.filter((m) =>
m.label.toLowerCase().includes(labelFilter.toLowerCase())
return p.movements.filter(
(m) =>
m.label.toLowerCase().includes(filter.toLowerCase()) ||
m.amount.toString().includes(filter)
);
}, [p.movements, labelFilter]);
}, [p.movements, filter]);
const [rowSelectionModel, setRowSelectionModel] =
React.useState<GridRowSelectionModel>([]);
React.useState<GridRowSelectionModel>({ type: "include", ids: new Set() });
// Set uploaded file
const setUploadedFile = async (
@ -216,7 +218,7 @@ function MovementsTable(p: {
const moveMultiple = async () => {
try {
const movements = p.movements.filter((m) =>
rowSelectionModel.includes(m.id)
rowSelectionModel.ids.has(m.id)
);
const targetAccount = await chooseAccount(
@ -260,7 +262,7 @@ function MovementsTable(p: {
const deleteMultiple = async () => {
try {
const movements = p.movements.filter((m) =>
rowSelectionModel.includes(m.id)
rowSelectionModel.ids.has(m.id)
);
if (
@ -382,26 +384,30 @@ function MovementsTable(p: {
<>
<div style={{ display: "flex" }}>
<TextField
placeholder="Filter by label"
placeholder="Filter by label or amount"
variant="standard"
size="small"
value={labelFilter}
value={filter}
onChange={(e) => {
setLabelFilter(e.target.value);
setFilter(e.target.value);
}}
style={{ padding: "0px", flex: 1 }}
/>
<span style={{ flex: 1 }}></span>
<Tooltip title="Refresh table">
<IconButton onClick={() => { p.needReload(false); }}>
<IconButton
onClick={() => {
p.needReload(false);
}}
>
<RefreshIcon />
</IconButton>
</Tooltip>
<Tooltip title="Move all the selected entries to another account">
<IconButton
disabled={
rowSelectionModel.length === 0 ||
rowSelectionModel.length === p.movements.length
rowSelectionModel.ids.size === 0 ||
rowSelectionModel.ids.size === p.movements.length
}
onClick={moveMultiple}
>
@ -411,8 +417,8 @@ function MovementsTable(p: {
<Tooltip title="Delete all the selected entries">
<IconButton
disabled={
rowSelectionModel.length === 0 ||
rowSelectionModel.length === p.movements.length
rowSelectionModel.ids.size === 0 ||
rowSelectionModel.ids.size === p.movements.length
}
onClick={deleteMultiple}
>

View File

@ -158,7 +158,7 @@ function ImportExportModal(p: {
</CardContent>{" "}
<CardActions>
<span style={{ flex: 1 }}>
<RouterLink to={p.exportURL}>
<RouterLink to={p.exportURL} target="_blank">
<Button
startIcon={<DownloadIcon />}
variant="outlined"

View File

@ -133,7 +133,7 @@ function InboxTable(p: {
}, [p.entries, labelFilter]);
const [rowSelectionModel, setRowSelectionModel] =
React.useState<GridRowSelectionModel>([]);
React.useState<GridRowSelectionModel>({ type: "include", ids: new Set() });
const [attaching, setAttaching] = React.useState<InboxEntry | undefined>();
@ -244,7 +244,7 @@ function InboxTable(p: {
// Find the entry to map
const entries = p.entries.filter(
(m) => rowSelectionModel.includes(m.id) && !m.movement_id
(m) => rowSelectionModel.ids.has(m.id) && !m.movement_id
);
const movements: Movement[][] = [];
@ -324,7 +324,7 @@ function InboxTable(p: {
const deleteMultiple = async () => {
try {
const deletedEntries = p.entries.filter((m) =>
rowSelectionModel.includes(m.id)
rowSelectionModel.ids.has(m.id)
);
if (
@ -437,7 +437,9 @@ function InboxTable(p: {
icon={<SearchIcon />}
label="Attach entry to movement"
color="inherit"
onClick={() => { handleAttachClick(params.row); }}
onClick={() => {
handleAttachClick(params.row);
}}
disabled={!!params.row.movement_id}
/>
</Tooltip>,
@ -496,7 +498,7 @@ function InboxTable(p: {
</Tooltip>
<Tooltip title="Attach all the selected inbox entries to movements">
<IconButton
disabled={rowSelectionModel.length === 0}
disabled={rowSelectionModel.ids.size === 0}
onClick={attachMultiple}
>
<SearchIcon />
@ -505,8 +507,8 @@ function InboxTable(p: {
<Tooltip title="Delete all the selected inbox entries">
<IconButton
disabled={
rowSelectionModel.length === 0 ||
rowSelectionModel.length === p.entries.length
rowSelectionModel.ids.size === 0 ||
rowSelectionModel.ids.size === p.entries.length
}
onClick={deleteMultiple}
>

View File

@ -1,4 +1,4 @@
import { DatePicker } from "@mui/x-date-pickers";
import { DateField } from "@mui/x-date-pickers";
import { dateToTime, timeToDate } from "../../utils/DateUtils";
import { TextFieldVariants } from "@mui/material";
@ -13,13 +13,16 @@ export function DateInput(p: {
variant?: TextFieldVariants;
}): React.ReactElement {
return (
<DatePicker
<DateField
autoFocus={p.autoFocus}
readOnly={p.editable === false}
label={p.label}
slotProps={{
field: { ref: p.ref },
textField: { variant: p.variant ?? "standard", style: p.style },
textField: {
ref: p.ref,
variant: p.variant ?? "standard",
style: p.style,
},
}}
value={timeToDate(p.value)}
onChange={(v) => {