Skip to content

Commit e1d0b9c

Browse files
author
Cohen, Yohay
committed
Enhance Telegram settings with a new test message feature, allowing users to send test messages directly from the UI. Update localization for new strings and improve analytics by excluding loan categories from spending charts. Update package-lock.json for Rollup dependencies and add error handling in the Telegram routes for better feedback.
1 parent a729174 commit e1d0b9c

17 files changed

Lines changed: 1176 additions & 349 deletions

client/dev-dist/sw.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ define(['./workbox-46f6dd99'], (function (workbox) { 'use strict';
8282
"revision": "3ca0b8505b4bec776b69afdba2768812"
8383
}, {
8484
"url": "index.html",
85-
"revision": "0.nostcd9l0mg"
85+
"revision": "0.lnhr78ntask"
8686
}], {});
8787
workbox.cleanupOutdatedCaches();
8888
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

client/src/components/TelegramSettings.tsx

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,28 @@ export function TelegramSettings({ isOpen, onClose, isInline }: TelegramSettings
155155
},
156156
});
157157

158+
const { mutate: sendTestMessage, isPending: isSendingTest } = useMutation({
159+
mutationFn: async () => {
160+
const res = await fetch(`${API_BASE}/telegram/send-test-message`, {
161+
method: 'POST',
162+
headers: { 'Content-Type': 'application/json' },
163+
body: JSON.stringify({}),
164+
});
165+
const data = await res.json();
166+
if (!res.ok) throw new Error(data.error || 'Failed to send test message');
167+
return data;
168+
},
169+
onSuccess: (data) => {
170+
const msg = data?.sent > 0
171+
? (data.sent === 1 ? t('telegram.test_success') : `Test message sent to ${data.sent} chats.`)
172+
: (data?.errors?.[0] || t('telegram.test_success'));
173+
showNotification('success', msg);
174+
},
175+
onError: (err: any) => {
176+
showNotification('error', err.message || 'Failed to send test message');
177+
},
178+
});
179+
158180
const { mutate: testConnection, isPending: isTesting } = useMutation({
159181
mutationFn: async () => {
160182
if (!botToken || !chatId) {
@@ -298,19 +320,38 @@ export function TelegramSettings({ isOpen, onClose, isInline }: TelegramSettings
298320
{isStarting ? t('telegram.starting') : t('telegram.start')}
299321
</button>
300322
) : (
301-
<button
302-
onClick={() => stopBot()}
303-
disabled={isStopping}
304-
className="flex items-center gap-2 bg-red-600 text-white px-4 py-2.5 rounded-2xl hover:bg-red-700 disabled:bg-gray-400"
305-
>
306-
<Square className="w-4 h-4" />
307-
{isStopping ? t('telegram.stopping') : t('telegram.stop')}
308-
</button>
323+
<>
324+
<button
325+
onClick={() => sendTestMessage()}
326+
disabled={isSendingTest}
327+
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2.5 rounded-2xl hover:bg-blue-700 disabled:bg-gray-400"
328+
>
329+
{isSendingTest ? t('telegram.testing') : t('telegram.send_test_message')}
330+
</button>
331+
<button
332+
onClick={() => stopBot()}
333+
disabled={isStopping}
334+
className="flex items-center gap-2 bg-red-600 text-white px-4 py-2.5 rounded-2xl hover:bg-red-700 disabled:bg-gray-400"
335+
>
336+
<Square className="w-4 h-4" />
337+
{isStopping ? t('telegram.stopping') : t('telegram.stop')}
338+
</button>
339+
</>
309340
)}
310341
</div>
311342
</div>
312343
</div>
313344

345+
{!status?.isActive && status?.lastStartError && (
346+
<div className="mb-6 p-4 rounded-2xl bg-red-50 border border-red-200 flex items-start gap-3">
347+
<AlertCircle className="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5" />
348+
<div>
349+
<p className="font-semibold text-red-900">{t('telegram.why_bot_not_started')}</p>
350+
<p className="text-sm text-red-800 mt-1">{status.lastStartError}</p>
351+
</div>
352+
</div>
353+
)}
354+
314355
{!status?.usersConfigured && (
315356
<div className="mb-6 p-4 rounded-2xl bg-amber-100 border border-amber-300 flex items-start gap-3">
316357
<AlertCircle className="w-5 h-5 text-amber-700 flex-shrink-0 mt-0.5" />

client/src/hooks/useAnalytics.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useMemo } from 'react';
22
import { Transaction } from '@app/shared';
3-
import { isInternalTransfer } from '../utils/transactionUtils';
3+
import { isInternalTransfer, isLoanCategory } from '../utils/transactionUtils';
44

55
interface AnalyticsData {
66
totalIncome: number;
@@ -73,6 +73,12 @@ function parseTransactionDate(dateValue: string): Date {
7373
return new Date(dateValue);
7474
}
7575

76+
/** Exclude mortgage/loan category from analytics spending charts and related totals */
77+
function skipLoanExpense(t: Transaction): boolean {
78+
const amount = t.chargedAmount || t.amount || 0;
79+
return amount < 0 && isLoanCategory(t.category);
80+
}
81+
7682
export function useAnalytics(transactions: Transaction[], customCCKeywords: string[] = []): AnalyticsData {
7783
return useMemo(() => {
7884
if (!transactions || transactions.length === 0) {
@@ -94,6 +100,7 @@ export function useAnalytics(transactions: Transaction[], customCCKeywords: stri
94100

95101
transactions.forEach(t => {
96102
if (isInternalTransfer(t, customCCKeywords)) return;
103+
if (skipLoanExpense(t)) return;
97104
const amount = t.chargedAmount || t.amount || 0;
98105
if (amount > 0) {
99106
totalIncome += amount;
@@ -106,6 +113,7 @@ export function useAnalytics(transactions: Transaction[], customCCKeywords: stri
106113
const categoryMap = new Map<string, number>();
107114
transactions.forEach(t => {
108115
if (isInternalTransfer(t, customCCKeywords)) return;
116+
if (skipLoanExpense(t)) return;
109117
const category = t.category || 'אחר';
110118
const amount = Math.abs(t.chargedAmount || t.amount || 0);
111119

@@ -138,7 +146,7 @@ export function useAnalytics(transactions: Transaction[], customCCKeywords: stri
138146
const entry = monthMap.get(monthKey)!;
139147
if (amount > 0) {
140148
entry.income += amount;
141-
} else {
149+
} else if (!skipLoanExpense(t)) {
142150
entry.expenses += Math.abs(amount);
143151
}
144152
});
@@ -155,6 +163,7 @@ export function useAnalytics(transactions: Transaction[], customCCKeywords: stri
155163
const weekdayMap = new Map<number, number>();
156164
transactions.forEach(t => {
157165
if (isInternalTransfer(t, customCCKeywords)) return;
166+
if (skipLoanExpense(t)) return;
158167
const amount = t.chargedAmount || t.amount || 0;
159168
if (amount >= 0) return;
160169

@@ -172,6 +181,7 @@ export function useAnalytics(transactions: Transaction[], customCCKeywords: stri
172181
const monthDayMap = new Map<number, number>();
173182
transactions.forEach(t => {
174183
if (isInternalTransfer(t, customCCKeywords)) return;
184+
if (skipLoanExpense(t)) return;
175185
const amount = t.chargedAmount || t.amount || 0;
176186
if (amount >= 0) return;
177187

@@ -191,6 +201,7 @@ export function useAnalytics(transactions: Transaction[], customCCKeywords: stri
191201
const merchantMap = new Map<string, { count: number; total: number }>();
192202
transactions.forEach(t => {
193203
if (isInternalTransfer(t, customCCKeywords)) return;
204+
if (skipLoanExpense(t)) return;
194205
const desc = t.description;
195206
if (!merchantMap.has(desc)) {
196207
merchantMap.set(desc, { count: 0, total: 0 });

client/src/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,8 @@
656656
"test": "Test",
657657
"testing": "Testing...",
658658
"test_success": "Test message sent successfully",
659+
"send_test_message": "Send test message",
660+
"why_bot_not_started": "Why the bot did not start",
659661
"loading_status": "Loading status...",
660662
"user_added": "User added to allowed list",
661663
"user_removed": "User removed from allowed list",

client/src/locales/he.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,8 @@
656656
"test": "בדוק",
657657
"testing": "בודק...",
658658
"test_success": "הודעת בדיקה נשלחה בהצלחה",
659+
"send_test_message": "שלח הודעת בדיקה",
660+
"why_bot_not_started": "מדוע הבוט לא הופעל",
659661
"chat_id_placeholder": "הכנס זהות צ'אט או זהות קבוצה",
660662
"chat_id_help": "בדרך כלל מתחיל ב- עבור קבוצות או מספר לצ'אטים פרטיים",
661663
"config_saved": "התצורה נשמרה",

client/src/utils/transactionUtils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,12 @@ export function isInternalTransfer(txn: Transaction, customCCKeywords: string[]
6464

6565
return true;
6666
}
67+
68+
/** Mortgage / loan payment category — excluded from daily spend pace vs historical baseline. */
69+
export function isLoanCategory(category: string | undefined): boolean {
70+
if (!category) return false;
71+
const c = category.trim();
72+
if (c === 'משכנתא והלוואות') return true;
73+
const lower = c.toLowerCase();
74+
return lower === 'mortgage & loans' || lower === 'mortgage and loans';
75+
}

0 commit comments

Comments
 (0)