Compare commits
	
		
			8 Commits
		
	
	
		
			1.0.0
			...
			c2ce55af1c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c2ce55af1c | |||
| caeff985c2 | |||
| bed538793d | |||
| 602f20ad18 | |||
| 457c96b37e | |||
| a3f2b77548 | |||
| 3c5c82371a | |||
| fb46626cff | 
							
								
								
									
										661
									
								
								moneymgr_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										661
									
								
								moneymgr_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -6,33 +6,34 @@ edition = "2024" | |||||||
| [dependencies] | [dependencies] | ||||||
| env_logger = "0.11.8" | env_logger = "0.11.8" | ||||||
| log = "0.4.27" | log = "0.4.27" | ||||||
| diesel = { version = "2.2.0", features = ["postgres", "r2d2"] } | diesel = { version = "2.2.10", features = ["postgres", "r2d2"] } | ||||||
| diesel_migrations = "2.1.0" | diesel_migrations = "2.2.0" | ||||||
| clap = { version = "4.5.35", features = ["env", "derive"] } | clap = { version = "4.5.38", features = ["env", "derive"] } | ||||||
| actix-web = "4" | actix-web = "4.11.0" | ||||||
| actix-cors = "0.7.0" | actix-cors = "0.7.1" | ||||||
| actix-multipart = "0.7.0" | actix-multipart = "0.7.2" | ||||||
| actix-remote-ip = "0.1.0" | 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" | actix-files = "0.6.6" | ||||||
| lazy_static = "1.5.0" | lazy_static = "1.5.0" | ||||||
| anyhow = "1.0.97" | anyhow = "1.0.98" | ||||||
| serde = { version = "1.0.219", features = ["derive"] } | serde = { version = "1.0.219", features = ["derive"] } | ||||||
| rust-s3 = "0.36.0-beta.2" | rust-s3 = "0.36.0-beta.2" | ||||||
| thiserror = "2.0.12" | thiserror = "2.0.12" | ||||||
| tokio = "1.44.1" | tokio = "1.45.0" | ||||||
| futures-util = "0.3.31" | futures-util = "0.3.31" | ||||||
| serde_json = "1.0.140" | serde_json = "1.0.140" | ||||||
| light-openid = "1.0.4" | light-openid = "1.0.4" | ||||||
| rand = "0.9.0" | rand = "0.9.1" | ||||||
| ipnet = { version = "2.11.0", features = ["serde"] } | ipnet = { version = "2.11.0", features = ["serde"] } | ||||||
| lazy-regex = "3.4.1" | lazy-regex = "3.4.1" | ||||||
| jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] } | jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] } | ||||||
| mime_guess = "2.0.5" | mime_guess = "2.0.5" | ||||||
| rust-embed = { version = "8.6.0" } | rust-embed = { version = "8.7.2" } | ||||||
| sha2 = "0.10.8" | sha2 = "0.11.0-pre.5" | ||||||
|  | base16ct = "0.2.0" | ||||||
| httpdate = "1.0.3" | httpdate = "1.0.3" | ||||||
| chrono = "0.4.41" | chrono = "0.4.41" | ||||||
| tempfile = "3.19.1" | tempfile = "3.20.0" | ||||||
| zip = "2.6.1" | zip = "3.0.0" | ||||||
| rust_xlsxwriter = "0.86.1" | rust_xlsxwriter = "0.87.0" | ||||||
| @@ -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 | /// Export data to a [FinancesManager](https://gitlab.com/pierre42100/cpp-financesmanager) file | ||||||
| pub async fn finances_manager_export(auth: AuthExtractor) -> HttpResult { | 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![] }; |     let mut out = FinancesManagerFile { accounts: vec![] }; | ||||||
|  |  | ||||||
|     for account in accounts { |     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 { |         let mut file_account = FinancesManagerAccount { | ||||||
|             name: account.name, |             name: account.name, | ||||||
|             movements: Vec::with_capacity(movements.len()), |             movements: Vec::with_capacity(movements.len()), | ||||||
|   | |||||||
| @@ -5,5 +5,5 @@ pub fn sha512(bytes: &[u8]) -> String { | |||||||
|     let mut hasher = Sha512::new(); |     let mut hasher = Sha512::new(); | ||||||
|     hasher.update(bytes); |     hasher.update(bytes); | ||||||
|     let h = hasher.finalize(); |     let h = hasher.finalize(); | ||||||
|     format!("{:x}", h) |     base16ct::lower::encode_string(h.as_slice()) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										2385
									
								
								moneymgr_web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2385
									
								
								moneymgr_web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -16,34 +16,34 @@ | |||||||
|     "@jsonjoy.com/base64": "^1.1.2", |     "@jsonjoy.com/base64": "^1.1.2", | ||||||
|     "@mdi/js": "^7.4.47", |     "@mdi/js": "^7.4.47", | ||||||
|     "@mdi/react": "^1.6.1", |     "@mdi/react": "^1.6.1", | ||||||
|     "@mui/icons-material": "^7.0.1", |     "@mui/icons-material": "^7.1.0", | ||||||
|     "@mui/material": "^7.0.1", |     "@mui/material": "^7.1.0", | ||||||
|     "@mui/x-charts": "^8.2.0", |     "@mui/x-charts": "^8.3.1", | ||||||
|     "@mui/x-data-grid": "^7.28.3", |     "@mui/x-data-grid": "^8.3.1", | ||||||
|     "@mui/x-date-pickers": "^8.0.0-beta.3", |     "@mui/x-date-pickers": "^8.3.1", | ||||||
|     "date-and-time": "^3.6.0", |     "date-and-time": "^3.6.0", | ||||||
|     "dayjs": "^1.11.13", |     "dayjs": "^1.11.13", | ||||||
|     "filesize": "^10.1.6", |     "filesize": "^10.1.6", | ||||||
|     "qrcode.react": "^4.2.0", |     "qrcode.react": "^4.2.0", | ||||||
|     "react": "^19.1.0", |     "react": "^19.1.0", | ||||||
|     "react-dom": "^19.1.0", |     "react-dom": "^19.1.0", | ||||||
|     "react-router": "^7.4.1", |     "react-router": "^7.6.0", | ||||||
|     "react-router-dom": "^7.4.1", |     "react-router-dom": "^7.6.0", | ||||||
|     "ts-pattern": "^5.7.0" |     "ts-pattern": "^5.7.1" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@eslint/js": "^9.23.0", |     "@eslint/js": "^9.26.0", | ||||||
|     "@types/react": "^19.1.0", |     "@types/react": "^19.1.4", | ||||||
|     "@types/react-dom": "^19.1.1", |     "@types/react-dom": "^19.1.5", | ||||||
|     "@vitejs/plugin-react": "^4.3.4", |     "@vitejs/plugin-react": "^4.4.1", | ||||||
|     "eslint": "^9.23.0", |     "eslint": "^9.26.0", | ||||||
|     "eslint-plugin-react-dom": "^1.40.2", |     "eslint-plugin-react-dom": "^1.49.0", | ||||||
|     "eslint-plugin-react-hooks": "^5.1.0", |     "eslint-plugin-react-hooks": "^5.2.0", | ||||||
|     "eslint-plugin-react-refresh": "^0.4.19", |     "eslint-plugin-react-refresh": "^00.4.20", | ||||||
|     "eslint-plugin-react-x": "^1.40.2", |     "eslint-plugin-react-x": "^1.49.0", | ||||||
|     "globals": "^16.0.0", |     "globals": "^16.1.0", | ||||||
|     "typescript": "~5.8.2", |     "typescript": "~5.8.3", | ||||||
|     "typescript-eslint": "^8.29.0", |     "typescript-eslint": "^8.32.1", | ||||||
|     "vite": "^6.2.5" |     "vite": "^6.3.5" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -121,16 +121,18 @@ function MovementsTable(p: { | |||||||
|  |  | ||||||
|   const chooseAccount = useSelectAccount(); |   const chooseAccount = useSelectAccount(); | ||||||
|  |  | ||||||
|   const [labelFilter, setLabelFilter] = React.useState(""); |   const [filter, setFilter] = React.useState(""); | ||||||
|  |  | ||||||
|   const filteredList = React.useMemo(() => { |   const filteredList = React.useMemo(() => { | ||||||
|     return p.movements.filter((m) => |     return p.movements.filter( | ||||||
|       m.label.toLowerCase().includes(labelFilter.toLowerCase()) |       (m) => | ||||||
|  |         m.label.toLowerCase().includes(filter.toLowerCase()) || | ||||||
|  |         m.amount.toString().includes(filter) | ||||||
|     ); |     ); | ||||||
|   }, [p.movements, labelFilter]); |   }, [p.movements, filter]); | ||||||
|  |  | ||||||
|   const [rowSelectionModel, setRowSelectionModel] = |   const [rowSelectionModel, setRowSelectionModel] = | ||||||
|     React.useState<GridRowSelectionModel>([]); |     React.useState<GridRowSelectionModel>({ type: "include", ids: new Set() }); | ||||||
|  |  | ||||||
|   // Set uploaded file |   // Set uploaded file | ||||||
|   const setUploadedFile = async ( |   const setUploadedFile = async ( | ||||||
| @@ -216,7 +218,7 @@ function MovementsTable(p: { | |||||||
|   const moveMultiple = async () => { |   const moveMultiple = async () => { | ||||||
|     try { |     try { | ||||||
|       const movements = p.movements.filter((m) => |       const movements = p.movements.filter((m) => | ||||||
|         rowSelectionModel.includes(m.id) |         rowSelectionModel.ids.has(m.id) | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|       const targetAccount = await chooseAccount( |       const targetAccount = await chooseAccount( | ||||||
| @@ -260,7 +262,7 @@ function MovementsTable(p: { | |||||||
|   const deleteMultiple = async () => { |   const deleteMultiple = async () => { | ||||||
|     try { |     try { | ||||||
|       const movements = p.movements.filter((m) => |       const movements = p.movements.filter((m) => | ||||||
|         rowSelectionModel.includes(m.id) |         rowSelectionModel.ids.has(m.id) | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|       if ( |       if ( | ||||||
| @@ -382,26 +384,30 @@ function MovementsTable(p: { | |||||||
|     <> |     <> | ||||||
|       <div style={{ display: "flex" }}> |       <div style={{ display: "flex" }}> | ||||||
|         <TextField |         <TextField | ||||||
|           placeholder="Filter by label" |           placeholder="Filter by label or amount" | ||||||
|           variant="standard" |           variant="standard" | ||||||
|           size="small" |           size="small" | ||||||
|           value={labelFilter} |           value={filter} | ||||||
|           onChange={(e) => { |           onChange={(e) => { | ||||||
|             setLabelFilter(e.target.value); |             setFilter(e.target.value); | ||||||
|           }} |           }} | ||||||
|           style={{ padding: "0px", flex: 1 }} |           style={{ padding: "0px", flex: 1 }} | ||||||
|         /> |         /> | ||||||
|         <span style={{ flex: 1 }}></span> |         <span style={{ flex: 1 }}></span> | ||||||
|         <Tooltip title="Refresh table"> |         <Tooltip title="Refresh table"> | ||||||
|           <IconButton onClick={() => { p.needReload(false); }}> |           <IconButton | ||||||
|  |             onClick={() => { | ||||||
|  |               p.needReload(false); | ||||||
|  |             }} | ||||||
|  |           > | ||||||
|             <RefreshIcon /> |             <RefreshIcon /> | ||||||
|           </IconButton> |           </IconButton> | ||||||
|         </Tooltip> |         </Tooltip> | ||||||
|         <Tooltip title="Move all the selected entries to another account"> |         <Tooltip title="Move all the selected entries to another account"> | ||||||
|           <IconButton |           <IconButton | ||||||
|             disabled={ |             disabled={ | ||||||
|               rowSelectionModel.length === 0 || |               rowSelectionModel.ids.size === 0 || | ||||||
|               rowSelectionModel.length === p.movements.length |               rowSelectionModel.ids.size === p.movements.length | ||||||
|             } |             } | ||||||
|             onClick={moveMultiple} |             onClick={moveMultiple} | ||||||
|           > |           > | ||||||
| @@ -411,8 +417,8 @@ function MovementsTable(p: { | |||||||
|         <Tooltip title="Delete all the selected entries"> |         <Tooltip title="Delete all the selected entries"> | ||||||
|           <IconButton |           <IconButton | ||||||
|             disabled={ |             disabled={ | ||||||
|               rowSelectionModel.length === 0 || |               rowSelectionModel.ids.size === 0 || | ||||||
|               rowSelectionModel.length === p.movements.length |               rowSelectionModel.ids.size === p.movements.length | ||||||
|             } |             } | ||||||
|             onClick={deleteMultiple} |             onClick={deleteMultiple} | ||||||
|           > |           > | ||||||
|   | |||||||
| @@ -158,7 +158,7 @@ function ImportExportModal(p: { | |||||||
|         </CardContent>{" "} |         </CardContent>{" "} | ||||||
|         <CardActions> |         <CardActions> | ||||||
|           <span style={{ flex: 1 }}> |           <span style={{ flex: 1 }}> | ||||||
|             <RouterLink to={p.exportURL}> |             <RouterLink to={p.exportURL} target="_blank"> | ||||||
|               <Button |               <Button | ||||||
|                 startIcon={<DownloadIcon />} |                 startIcon={<DownloadIcon />} | ||||||
|                 variant="outlined" |                 variant="outlined" | ||||||
|   | |||||||
| @@ -133,7 +133,7 @@ function InboxTable(p: { | |||||||
|   }, [p.entries, labelFilter]); |   }, [p.entries, labelFilter]); | ||||||
|  |  | ||||||
|   const [rowSelectionModel, setRowSelectionModel] = |   const [rowSelectionModel, setRowSelectionModel] = | ||||||
|     React.useState<GridRowSelectionModel>([]); |     React.useState<GridRowSelectionModel>({ type: "include", ids: new Set() }); | ||||||
|  |  | ||||||
|   const [attaching, setAttaching] = React.useState<InboxEntry | undefined>(); |   const [attaching, setAttaching] = React.useState<InboxEntry | undefined>(); | ||||||
|  |  | ||||||
| @@ -244,7 +244,7 @@ function InboxTable(p: { | |||||||
|  |  | ||||||
|       // Find the entry to map |       // Find the entry to map | ||||||
|       const entries = p.entries.filter( |       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[][] = []; |       const movements: Movement[][] = []; | ||||||
|  |  | ||||||
| @@ -324,7 +324,7 @@ function InboxTable(p: { | |||||||
|   const deleteMultiple = async () => { |   const deleteMultiple = async () => { | ||||||
|     try { |     try { | ||||||
|       const deletedEntries = p.entries.filter((m) => |       const deletedEntries = p.entries.filter((m) => | ||||||
|         rowSelectionModel.includes(m.id) |         rowSelectionModel.ids.has(m.id) | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|       if ( |       if ( | ||||||
| @@ -437,7 +437,9 @@ function InboxTable(p: { | |||||||
|               icon={<SearchIcon />} |               icon={<SearchIcon />} | ||||||
|               label="Attach entry to movement" |               label="Attach entry to movement" | ||||||
|               color="inherit" |               color="inherit" | ||||||
|               onClick={() => { handleAttachClick(params.row); }} |               onClick={() => { | ||||||
|  |                 handleAttachClick(params.row); | ||||||
|  |               }} | ||||||
|               disabled={!!params.row.movement_id} |               disabled={!!params.row.movement_id} | ||||||
|             /> |             /> | ||||||
|           </Tooltip>, |           </Tooltip>, | ||||||
| @@ -496,7 +498,7 @@ function InboxTable(p: { | |||||||
|         </Tooltip> |         </Tooltip> | ||||||
|         <Tooltip title="Attach all the selected inbox entries to movements"> |         <Tooltip title="Attach all the selected inbox entries to movements"> | ||||||
|           <IconButton |           <IconButton | ||||||
|             disabled={rowSelectionModel.length === 0} |             disabled={rowSelectionModel.ids.size === 0} | ||||||
|             onClick={attachMultiple} |             onClick={attachMultiple} | ||||||
|           > |           > | ||||||
|             <SearchIcon /> |             <SearchIcon /> | ||||||
| @@ -505,8 +507,8 @@ function InboxTable(p: { | |||||||
|         <Tooltip title="Delete all the selected inbox entries"> |         <Tooltip title="Delete all the selected inbox entries"> | ||||||
|           <IconButton |           <IconButton | ||||||
|             disabled={ |             disabled={ | ||||||
|               rowSelectionModel.length === 0 || |               rowSelectionModel.ids.size === 0 || | ||||||
|               rowSelectionModel.length === p.entries.length |               rowSelectionModel.ids.size === p.entries.length | ||||||
|             } |             } | ||||||
|             onClick={deleteMultiple} |             onClick={deleteMultiple} | ||||||
|           > |           > | ||||||
|   | |||||||
| @@ -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 { dateToTime, timeToDate } from "../../utils/DateUtils"; | ||||||
| import { TextFieldVariants } from "@mui/material"; | import { TextFieldVariants } from "@mui/material"; | ||||||
|  |  | ||||||
| @@ -13,13 +13,16 @@ export function DateInput(p: { | |||||||
|   variant?: TextFieldVariants; |   variant?: TextFieldVariants; | ||||||
| }): React.ReactElement { | }): React.ReactElement { | ||||||
|   return ( |   return ( | ||||||
|     <DatePicker |     <DateField | ||||||
|       autoFocus={p.autoFocus} |       autoFocus={p.autoFocus} | ||||||
|       readOnly={p.editable === false} |       readOnly={p.editable === false} | ||||||
|       label={p.label} |       label={p.label} | ||||||
|       slotProps={{ |       slotProps={{ | ||||||
|         field: { ref: p.ref }, |         textField: { | ||||||
|         textField: { variant: p.variant ?? "standard", style: p.style }, |           ref: p.ref, | ||||||
|  |           variant: p.variant ?? "standard", | ||||||
|  |           style: p.style, | ||||||
|  |         }, | ||||||
|       }} |       }} | ||||||
|       value={timeToDate(p.value)} |       value={timeToDate(p.value)} | ||||||
|       onChange={(v) => { |       onChange={(v) => { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user