From 68e265de839b81dcf3bf7d7d315c1ade2db957db Mon Sep 17 00:00:00 2001 From: David Amunga Date: Thu, 25 Sep 2025 09:53:34 +0300 Subject: [PATCH 1/3] feat: add optional Charges/Fees sheet to Excel exports --- .changeset/charges-fees-sheet.md | 11 +++ package.json | 1 + pnpm-lock.yaml | 55 ++++++++++++++ src-tauri/Cargo.lock | 2 +- src-tauri/tauri.conf.json | 2 +- src/App.tsx | 76 +++++++++++++++++-- src/components/password-prompt.tsx | 28 +++---- src/components/ui/checkbox.tsx | 30 ++++++++ src/components/ui/input.tsx | 21 ++++++ src/components/ui/password-input.tsx | 54 ++++++++++++++ src/components/ui/select.tsx | 2 +- src/index.css | 22 ++++++ src/services/exportService.ts | 12 +-- src/services/xlsxService.ts | 107 ++++++++++++++++++++++++++- src/types/index.ts | 4 + tsconfig.json | 4 +- 16 files changed, 396 insertions(+), 35 deletions(-) create mode 100644 .changeset/charges-fees-sheet.md create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/password-input.tsx diff --git a/.changeset/charges-fees-sheet.md b/.changeset/charges-fees-sheet.md new file mode 100644 index 0000000..61a803f --- /dev/null +++ b/.changeset/charges-fees-sheet.md @@ -0,0 +1,11 @@ +--- +"mpesa2csv": minor +--- + +Add optional Charges/Fees sheet to Excel exports + +- Add new export option to include a separate "Charges & Fees" sheet when exporting to Excel +- Filter and categorize all transactions containing "charge" in the details +- Display charges with date, amount, and balance information +- Include summary totals for total charges and number of charge transactions + diff --git a/package.json b/package.json index f843a92..4cf8863 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "sync-versions": "node scripts/sync-versions.js" }, "dependencies": { + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/vite": "^4.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47f5302..487a73a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@radix-ui/react-checkbox': + specifier: ^1.3.3 + version: 1.3.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-select': specifier: ^2.2.6 version: 2.2.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -548,6 +551,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.7': resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: @@ -658,6 +674,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.1.3': resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} peerDependencies: @@ -2532,6 +2561,22 @@ snapshots: '@types/react': 19.1.13 '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.13)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) @@ -2627,6 +2672,16 @@ snapshots: '@types/react': 19.1.13 '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.1) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 52638dd..cd886d6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2122,7 +2122,7 @@ dependencies = [ [[package]] name = "mpesa2csv" -version = "0.0.2" +version = "0.0.3" dependencies = [ "serde", "serde_json", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index ecb6537..cc59461 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,7 +14,7 @@ { "title": "mpesa2csv - Convert M-PESA Statements to CSV", "width": 600, - "height": 450, + "height": 500, "minWidth": 500, "minHeight": 400, "resizable": true diff --git a/src/App.tsx b/src/App.tsx index 94522e2..c37f112 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,10 @@ import { useState, useEffect } from "react"; -import { MPesaStatement, FileStatus, ExportFormat } from "./types"; +import { + MPesaStatement, + FileStatus, + ExportFormat, + ExportOptions, +} from "./types"; import { PdfService } from "./services/pdfService"; import { ExportService } from "./services/exportService"; import FileUploader from "./components/file-uploader"; @@ -15,6 +20,7 @@ import { SelectValue, } from "./components/ui/select"; import { Button } from "./components/ui/button"; +import { Checkbox } from "./components/ui/checkbox"; function App() { const [files, setFiles] = useState([]); @@ -26,6 +32,9 @@ function App() { const [exportFormat, setExportFormat] = useState( ExportFormat.XLSX ); + const [exportOptions, setExportOptions] = useState({ + includeChargesSheet: false, + }); const [currentFileIndex, setCurrentFileIndex] = useState(0); const [isDownloading, setIsDownloading] = useState(false); @@ -119,7 +128,11 @@ function App() { ); // Generate download link asynchronously - ExportService.createDownloadLink(combinedStatement, exportFormat) + ExportService.createDownloadLink( + combinedStatement, + exportFormat, + exportOptions + ) .then(setExportLink) .catch(() => setExportLink("")); setExportFileName(fileName); @@ -183,7 +196,11 @@ function App() { formatDateForFilename() ); - ExportService.createDownloadLink(combinedStatement, exportFormat) + ExportService.createDownloadLink( + combinedStatement, + exportFormat, + exportOptions + ) .then(setExportLink) .catch(() => setExportLink("")); setExportFileName(fileName); @@ -221,7 +238,8 @@ function App() { const arrayBuffer = await ExportService.getFileBuffer( combinedStatement, - exportFormat + exportFormat, + exportOptions ); const content = new Uint8Array(arrayBuffer); @@ -324,7 +342,11 @@ function App() { formatDateForFilename() ); - ExportService.createDownloadLink(combinedStatement, value) + ExportService.createDownloadLink( + combinedStatement, + value, + exportOptions + ) .then(setExportLink) .catch(() => setExportLink("")); setExportFileName(fileName); @@ -345,6 +367,46 @@ function App() { + {exportFormat === ExportFormat.XLSX && ( +
+ +
+ +

+ Creates a separate sheet with all transaction charges + and fees +

+
+
+ )} +
-