Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/legal-peas-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"mpesa2csv": patch
---

fix: refine statement processing
5 changes: 5 additions & 0 deletions .changeset/sour-weeks-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"mpesa2csv": minor
---

feat: added financial summary export
11 changes: 11 additions & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
tauri-plugin-dialog = "2"
tauri-plugin-fs = "2"
tauri-plugin-process = "2"
tauri-plugin-updater = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
6 changes: 5 additions & 1 deletion src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"core:webview:allow-webview-position",
"core:webview:allow-webview-size",
"fs:allow-read-file",
"fs:default"
"fs:default",
"updater:default",
"updater:allow-check",
"process:default",
"process:allow-restart"
]
}
8 changes: 7 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ async fn save_csv_file(
}
}

#[tauri::command]
fn get_app_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}

#[tauri::command]
async fn save_file(
app: tauri::AppHandle,
Expand Down Expand Up @@ -79,8 +84,9 @@ pub fn run() {
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.invoke_handler(tauri::generate_handler![greet, save_csv_file, save_file])
.invoke_handler(tauri::generate_handler![greet, save_csv_file, save_file, get_app_version])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
4 changes: 2 additions & 2 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
{
"title": "mpesa2csv - Convert M-PESA Statements to CSV/Excel",
"width": 600,
"height": 500,
"height": 550,
"minWidth": 500,
"minHeight": 400,
"resizable": true
Expand Down Expand Up @@ -64,7 +64,7 @@
},
"windowSize": {
"width": 660,
"height": 500
"height": 550
}
}
},
Expand Down
146 changes: 103 additions & 43 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { PdfService } from "./services/pdfService";
import { ExportService } from "./services/exportService";
import FileUploader from "./components/file-uploader";
import PasswordPrompt from "./components/password-prompt";
import { UpdateChecker } from "./components/update-checker";
import { Download, RotateCcw } from "lucide-react";
import { invoke } from "@tauri-apps/api/core";
import dayjs from "dayjs";
Expand All @@ -34,14 +35,30 @@ function App() {
);
const [exportOptions, setExportOptions] = useState<ExportOptions>({
includeChargesSheet: false,
includeSummarySheet: false,
});
const [currentFileIndex, setCurrentFileIndex] = useState<number>(0);
const [isDownloading, setIsDownloading] = useState<boolean>(false);
const [appVersion, setAppVersion] = useState<string>("");

const formatDateForFilename = (): string => {
return dayjs().format("YYYY-MM-DD_HH-mm-ss");
};

// Get app version on component mount
useEffect(() => {
const getVersion = async () => {
try {
const version = await invoke<string>("get_app_version");
setAppVersion(version);
} catch (error) {
console.error("Failed to get app version:", error);
setAppVersion("unknown");
}
};
getVersion();
}, []);

const handleFilesSelected = async (selectedFiles: File[]) => {
setFiles(selectedFiles);
setStatus(FileStatus.LOADING);
Expand Down Expand Up @@ -274,6 +291,8 @@ function App() {

return (
<div className="min-h-screen max-h-screen flex flex-col overflow-hidden">
{/* Auto-check for updates on app start */}
<UpdateChecker autoCheck={true} />
<div className="flex-1 mx-auto px-4 py-4 flex flex-col max-w-4xl w-full">
<main className="flex-1 flex items-center justify-center">
<div className="w-full max-w-2xl transition-all duration-300 ease-in-out">
Expand Down Expand Up @@ -305,7 +324,7 @@ function App() {
) : status === FileStatus.PROCESSING ? (
<div className="rounded-lg shadow-sm p-6 text-center flex flex-col items-center justify-center transition-all duration-300 min-h-[300px]">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
<p className="text-foreground">
<p className="text-center">
Processing file {currentFileIndex + 1} of {files.length}...
</p>
{files[currentFileIndex] && (
Expand Down Expand Up @@ -372,37 +391,70 @@ function App() {
<label className="block text-sm font-medium mb-2">
Additional Sheets
</label>
<div className="space-y-2">
<label className="flex items-center space-x-2">
<Checkbox
checked={exportOptions.includeChargesSheet}
onCheckedChange={(value) => {
const newOptions = {
...exportOptions,
includeChargesSheet: Boolean(value),
};
setExportOptions(newOptions);

// Regenerate download link with new options
const combinedStatement = statements[0];
ExportService.createDownloadLink(
combinedStatement,
exportFormat,
newOptions
)
.then(setExportLink)
.catch(() => setExportLink(""));
}}
className="rounded border-gray-300 text-primary focus:ring-primary"
/>
<span className="text-sm">
Include Charges/Fees Sheet
</span>
</label>
<p className="text-xs text-muted-foreground ml-6">
Creates a separate sheet with all transaction charges
and fees
</p>
<div className="space-y-3">
<div>
<label className="flex items-center space-x-2">
<Checkbox
checked={exportOptions.includeChargesSheet}
onCheckedChange={(value) => {
const newOptions = {
...exportOptions,
includeChargesSheet: Boolean(value),
};
setExportOptions(newOptions);

const combinedStatement = statements[0];
ExportService.createDownloadLink(
combinedStatement,
exportFormat,
newOptions
)
.then(setExportLink)
.catch(() => setExportLink(""));
}}
className="rounded border-gray-300 text-primary focus:ring-primary"
/>
<span className="text-sm">
Include Charges/Fees Sheet
</span>
</label>
<p className="text-xs text-muted-foreground ml-6">
Creates a separate sheet with all transaction charges
and fees
</p>
</div>

<div>
<label className="flex items-center space-x-2">
<Checkbox
checked={exportOptions.includeSummarySheet}
onCheckedChange={(value) => {
const newOptions = {
...exportOptions,
includeSummarySheet: Boolean(value),
};
setExportOptions(newOptions);

const combinedStatement = statements[0];
ExportService.createDownloadLink(
combinedStatement,
exportFormat,
newOptions
)
.then(setExportLink)
.catch(() => setExportLink(""));
}}
className="rounded border-gray-300 text-primary focus:ring-primary"
/>
<span className="text-sm">
Include Financial Summary Sheet
</span>
</label>
<p className="text-xs text-muted-foreground ml-6">
Creates a comprehensive financial analysis with cash
flow, spending patterns, and insights
</p>
</div>
</div>
</div>
)}
Expand Down Expand Up @@ -450,17 +502,25 @@ function App() {
</main>

<footer className="flex-shrink-0 text-center text-xs border-t py-3 mt-0">
<p>
Built by{" "}
<a
href="https://twitter.com/davidamunga_"
className="text-green-500 hover:text-green-500/80 font-medium transition-colors"
target="_blank"
rel="noopener noreferrer"
>
@davidamunga
</a>
</p>
<div className="flex flex-col sm:flex-row items-center justify-between gap-2">
<p>
Built by{" "}
<a
href="https://twitter.com/davidamunga_"
className="text-green-500 hover:text-green-500/80 font-medium transition-colors"
target="_blank"
rel="noopener noreferrer"
>
@davidamunga
</a>
</p>
<div className="flex items-center gap-3">
{appVersion && (
<span className="">v{appVersion}</span>
)}
<UpdateChecker showButton={true} />
</div>
</div>
</footer>
</div>
</div>
Expand Down
Loading