Skip to content

Commit d6d7650

Browse files
committed
Add income toggle feature and update AGENTS.md with date command instructions
1 parent 3116d55 commit d6d7650

File tree

7 files changed

+257
-17
lines changed

7 files changed

+257
-17
lines changed

AGENTS.md

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,78 @@ This document explains how to use the tools and utilities available in the Actua
44

55
## Table of Contents
66

7+
- [CHANGELOG Maintenance](#changelog-maintenance)
78
- [File API Service](#file-api-service)
89
- [React Hooks](#react-hooks)
910
- [Data Transformation Utilities](#data-transformation-utilities)
1011
- [Type Definitions](#type-definitions)
1112

13+
## CHANGELOG Maintenance
14+
15+
**IMPORTANT**: Agents MUST update `CHANGELOG.md` after making any code changes to the project.
16+
17+
### Update Requirements
18+
19+
1. **When to Update**: Update `CHANGELOG.md` after making any changes to:
20+
- Source code files (`.ts`, `.tsx`, `.js`, `.jsx`)
21+
- Configuration files (e.g., `package.json`, `vite.config.ts`, `tsconfig.json`)
22+
- Documentation files (e.g., `README.md`, `AGENTS.md`)
23+
- Test files that represent new functionality
24+
- Build or deployment configurations
25+
26+
2. **Format**: Use date-based format with entries grouped by date (YYYY-MM-DD)
27+
- Most recent entries appear at the top
28+
- Each date section should be formatted as: `## YYYY-MM-DD`
29+
- **Getting Today's Date**: Use the CLI command to get the current date in the correct format:
30+
```bash
31+
date +%Y-%m-%d
32+
```
33+
This will output the date in YYYY-MM-DD format (e.g., `2026-01-08`)
34+
35+
3. **Categories**: Use appropriate categories under each date:
36+
- **Added**: New features, components, utilities, or functionality
37+
- **Changed**: Changes to existing functionality or behavior
38+
- **Fixed**: Bug fixes or corrections
39+
- **Removed**: Removed features, code, or functionality
40+
- **Security**: Security-related updates
41+
42+
4. **Entry Format**:
43+
- Add entries under the current date (YYYY-MM-DD)
44+
- If a date section doesn't exist for today, create it at the top of the file
45+
- Each entry should be concise but descriptive
46+
- Use bullet points for multiple entries under the same category
47+
- Example:
48+
49+
```
50+
## 2026-01-04
51+
52+
### Added
53+
- New feature description
54+
55+
### Fixed
56+
- Bug fix description
57+
```
58+
59+
5. **Placement**: Always add new entries to the top of the file, under the current date. If today's date section already exists, add your entry to the appropriate category within that section.
60+
61+
### Example
62+
63+
```markdown
64+
## 2026-01-04
65+
66+
### Added
67+
- New calendar heatmap visualization component
68+
- Support for currency override in settings
69+
70+
### Changed
71+
- Updated data transformation logic to handle transfers more efficiently
72+
73+
### Fixed
74+
- Resolved issue with off-budget transaction filtering
75+
```
76+
77+
Remember: The CHANGELOG serves as a historical record of all project changes. Keep entries clear and meaningful for future reference.
78+
1279
## File API Service
1380
1481
The File API service (`src/services/fileApi.ts`) provides functions to load and query an Actual Budget SQLite database from a zip file in the browser.
@@ -357,7 +424,12 @@ console.log(wrappedData.monthlyData);
357424
- Multiple transfers to the same account are grouped together in both categories and payees
358425
- **Off-Budget Filtering**: Excludes off-budget transactions by default. Set `includeOffBudget = true` to include them
359426
- Excludes starting balance transactions (transactions where payee name is "Starting Balance")
360-
- Handles deleted categories/payees (marks with "deleted: " prefix)
427+
- **Category Merges**: When a category is deleted and merged into another category, transactions are automatically resolved through the `category_mapping` table:
428+
- Transactions from merged categories appear under the merged (target) category name
429+
- Handles transitive merges (Category A → B → C resolves to C)
430+
- Budget data is also resolved through merge chains and combined for merged categories
431+
- Only non-merged deleted categories show the "deleted: " prefix
432+
- Handles deleted categories/payees (marks with "deleted: " prefix for non-merged deleted categories)
361433
- Converts amounts from cents to dollars
362434
- Calculates all derived metrics automatically
363435

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to date-based versioning with entries grouped by date.
66

7+
## 2026-01-08
8+
9+
### Added
10+
11+
- Add toggle to include/exclude income transactions from calculations (default: expenses only)
12+
- Add CLI command instructions to AGENTS.md for getting today's date when updating CHANGELOG
13+
714
## 2026-01-05
815

916
### Added

src/App.tsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useState } from 'react';
33
import styles from './App.module.css';
44
import { ConnectionForm } from './components/ConnectionForm';
55
import { CurrencySelector } from './components/CurrencySelector';
6+
import { IncludeIncomeToggle } from './components/IncludeIncomeToggle';
67
import { Navigation } from './components/Navigation';
78
import { OffBudgetToggle } from './components/OffBudgetToggle';
89
import { AllTransfersToggle } from './components/OffBudgetTransfersToggle';
@@ -40,6 +41,7 @@ const PAGES = [
4041

4142
function App() {
4243
const [currentPage, setCurrentPage] = useState(0);
44+
const [includeIncome, setIncludeIncome] = useLocalStorage('includeIncome', false);
4345
const [includeOffBudget, setIncludeOffBudget] = useLocalStorage('includeOffBudget', false);
4446
const [includeOnBudgetTransfers, setIncludeOnBudgetTransfers] = useLocalStorage(
4547
'includeOnBudgetTransfers',
@@ -62,6 +64,18 @@ function App() {
6264
includeOnBudgetTransfers,
6365
includeAllTransfers,
6466
overrideCurrency || undefined,
67+
includeIncome,
68+
);
69+
};
70+
71+
const handleIncludeIncomeToggle = (value: boolean) => {
72+
setIncludeIncome(value);
73+
retransformData(
74+
includeOffBudget,
75+
includeOnBudgetTransfers,
76+
includeAllTransfers,
77+
overrideCurrency || undefined,
78+
value,
6579
);
6680
};
6781

@@ -72,12 +86,19 @@ function App() {
7286
includeOnBudgetTransfers,
7387
includeAllTransfers,
7488
overrideCurrency || undefined,
89+
includeIncome,
7590
);
7691
};
7792

7893
const handleOnBudgetTransfersToggle = (value: boolean) => {
7994
setIncludeOnBudgetTransfers(value);
80-
retransformData(includeOffBudget, value, includeAllTransfers, overrideCurrency || undefined);
95+
retransformData(
96+
includeOffBudget,
97+
value,
98+
includeAllTransfers,
99+
overrideCurrency || undefined,
100+
includeIncome,
101+
);
81102
};
82103

83104
const handleAllTransfersToggle = (value: boolean) => {
@@ -92,6 +113,7 @@ function App() {
92113
effectiveIncludeOnBudgetTransfers, // If includeAllTransfers is true, also enable on-budget transfers
93114
value,
94115
overrideCurrency || undefined,
116+
includeIncome,
95117
);
96118
};
97119

@@ -100,14 +122,21 @@ function App() {
100122
const defaultCurrency = data?.currencySymbol || '$';
101123
if (currencySymbol === defaultCurrency) {
102124
setOverrideCurrency(null);
103-
retransformData(includeOffBudget, includeOnBudgetTransfers, includeAllTransfers, undefined);
125+
retransformData(
126+
includeOffBudget,
127+
includeOnBudgetTransfers,
128+
includeAllTransfers,
129+
undefined,
130+
includeIncome,
131+
);
104132
} else {
105133
setOverrideCurrency(currencySymbol);
106134
retransformData(
107135
includeOffBudget,
108136
includeOnBudgetTransfers,
109137
includeAllTransfers,
110138
currencySymbol,
139+
includeIncome,
111140
);
112141
}
113142
};
@@ -164,6 +193,7 @@ function App() {
164193
return (
165194
<div className={styles.app}>
166195
<SettingsMenu>
196+
<IncludeIncomeToggle includeIncome={includeIncome} onToggle={handleIncludeIncomeToggle} />
167197
<OffBudgetToggle includeOffBudget={includeOffBudget} onToggle={handleOffBudgetToggle} />
168198
<OnBudgetTransfersToggle
169199
includeOnBudgetTransfers={includeAllTransfers || includeOnBudgetTransfers}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
.toggle {
2+
position: fixed;
3+
top: 2rem;
4+
right: 2rem;
5+
z-index: 1001;
6+
background: rgba(0, 0, 0, 0.8);
7+
backdrop-filter: blur(10px);
8+
padding: 0.75rem 1.25rem;
9+
border-radius: 12px;
10+
border: 1px solid rgba(255, 255, 255, 0.1);
11+
display: flex;
12+
align-items: center;
13+
gap: 0.75rem;
14+
color: white;
15+
font-size: 0.9rem;
16+
font-weight: 500;
17+
}
18+
19+
.toggleLabel {
20+
user-select: none;
21+
cursor: pointer;
22+
display: flex;
23+
align-items: center;
24+
justify-content: space-between;
25+
width: 100%;
26+
gap: 0.75rem;
27+
}
28+
29+
.toggleSwitch {
30+
position: relative;
31+
width: 44px;
32+
height: 24px;
33+
background: rgba(255, 255, 255, 0.2);
34+
border-radius: 12px;
35+
cursor: pointer;
36+
transition: background-color 0.3s;
37+
flex-shrink: 0;
38+
margin-left: auto;
39+
}
40+
41+
.toggleSwitch.active {
42+
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
43+
}
44+
45+
.toggleSlider {
46+
position: absolute;
47+
top: 2px;
48+
left: 2px;
49+
width: 20px;
50+
height: 20px;
51+
background: white;
52+
border-radius: 50%;
53+
transition: transform 0.3s;
54+
}
55+
56+
.toggleSwitch.active .toggleSlider {
57+
transform: translateX(20px);
58+
}
59+
60+
.toggleText {
61+
font-size: 0.85rem;
62+
opacity: 0.9;
63+
text-align: left;
64+
flex: 1;
65+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { motion } from 'framer-motion';
2+
3+
import styles from './IncludeIncomeToggle.module.css';
4+
5+
interface IncludeIncomeToggleProps {
6+
includeIncome: boolean;
7+
onToggle: (value: boolean) => void;
8+
}
9+
10+
export function IncludeIncomeToggle({ includeIncome, onToggle }: IncludeIncomeToggleProps) {
11+
return (
12+
<motion.div
13+
className={styles.toggle}
14+
initial={{ opacity: 0, y: -20 }}
15+
animate={{ opacity: 1, y: 0 }}
16+
transition={{ duration: 0.3, delay: 0.1 }}
17+
>
18+
<div className={styles.toggleLabel}>
19+
<span className={styles.toggleText}>Include Income</span>
20+
<div
21+
className={`${styles.toggleSwitch} ${includeIncome ? styles.active : ''}`}
22+
onClick={() => onToggle(!includeIncome)}
23+
role="switch"
24+
aria-checked={includeIncome}
25+
aria-label="Include income transactions"
26+
tabIndex={0}
27+
onKeyDown={e => {
28+
if (e.key === 'Enter' || e.code === 'Space') {
29+
e.preventDefault();
30+
onToggle(!includeIncome);
31+
}
32+
}}
33+
>
34+
<div className={styles.toggleSlider} />
35+
</div>
36+
</div>
37+
</motion.div>
38+
);
39+
}

src/hooks/useActualData.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export function useActualData() {
5050
budgetData?: Array<{ categoryId: string; month: string; budgetedAmount: number }>,
5151
groupSortOrders: Map<string, number> = new Map(),
5252
groupTombstones: Map<string, boolean> = new Map(),
53+
includeIncome: boolean = false,
5354
) => {
5455
const wrappedData = transformToWrappedData(
5556
raw.transactions,
@@ -64,6 +65,7 @@ export function useActualData() {
6465
budgetData,
6566
groupSortOrders,
6667
groupTombstones,
68+
includeIncome,
6769
);
6870
setData(wrappedData);
6971
},
@@ -77,6 +79,7 @@ export function useActualData() {
7779
includeOnBudgetTransfers: boolean = true, // Default to true (on by default)
7880
includeAllTransfers: boolean = false,
7981
overrideCurrencySymbol?: string,
82+
includeIncome: boolean = false,
8083
) => {
8184
setLoading(true);
8285
setError(null);
@@ -151,6 +154,7 @@ export function useActualData() {
151154
fetchedBudgetData.length > 0 ? fetchedBudgetData : undefined,
152155
fetchedGroupSortOrders,
153156
fetchedGroupTombstones,
157+
includeIncome,
154158
);
155159

156160
setProgress(100);
@@ -176,6 +180,7 @@ export function useActualData() {
176180
includeOnBudgetTransfers: boolean = true, // Default to true (on by default)
177181
includeAllTransfers: boolean = false,
178182
overrideCurrencySymbol?: string,
183+
includeIncome: boolean = false,
179184
) => {
180185
if (!file) {
181186
throw new Error('No file available');
@@ -186,6 +191,7 @@ export function useActualData() {
186191
includeOnBudgetTransfers,
187192
includeAllTransfers,
188193
overrideCurrencySymbol,
194+
includeIncome,
189195
);
190196
},
191197
[file, fetchData],
@@ -197,6 +203,7 @@ export function useActualData() {
197203
includeOnBudgetTransfers: boolean,
198204
includeAllTransfers: boolean,
199205
overrideCurrencySymbol?: string,
206+
includeIncome: boolean = false,
200207
) => {
201208
if (rawData) {
202209
const effectiveCurrency = overrideCurrencySymbol || currencySymbol;
@@ -209,6 +216,7 @@ export function useActualData() {
209216
budgetData,
210217
groupSortOrders,
211218
groupTombstones,
219+
includeIncome,
212220
);
213221
}
214222
},

0 commit comments

Comments
 (0)