@@ -27,6 +27,12 @@ interface Income {
2727 is_active : number ;
2828 average_amount : number | null ;
2929 history_count : number ;
30+ target_account_id : string ;
31+ }
32+
33+ interface AccountOption {
34+ id : string ;
35+ name : string ;
3036}
3137
3238export default function IncomePage ( ) {
@@ -41,6 +47,8 @@ export default function IncomePage() {
4147 const [ newPattern , setNewPattern ] = useState ( "" ) ;
4248 const [ patternMinAmount , setPatternMinAmount ] = useState ( "" ) ;
4349 const [ patternMaxAmount , setPatternMaxAmount ] = useState ( "" ) ;
50+ const [ accounts , setAccounts ] = useState < AccountOption [ ] > ( [ ] ) ;
51+ const [ editTargetAccount , setEditTargetAccount ] = useState < string > ( "" ) ;
4452 const addFormRef = useRef < HTMLFormElement > ( null ) ;
4553 const editFormRef = useRef < HTMLFormElement > ( null ) ;
4654
@@ -49,9 +57,13 @@ export default function IncomePage() {
4957 Promise . all ( [
5058 fetch ( "/api/income" ) . then ( ( r ) => r . json ( ) ) ,
5159 fetch ( "/api/matches" ) . then ( ( r ) => r . json ( ) ) ,
60+ fetch ( "/api/ynab/accounts" ) . then ( ( r ) => r . json ( ) ) ,
5261 ] )
53- . then ( ( [ incomeData , matchData ] ) => {
62+ . then ( ( [ incomeData , matchData , accountsData ] ) => {
5463 if ( incomeData . incomes ) setIncomes ( incomeData . incomes ) ;
64+ if ( accountsData . accounts ) {
65+ setAccounts ( accountsData . accounts . filter ( ( a : { type : string ; closed : number } ) => ( a . type === "checking" || a . type === "savings" ) && ! a . closed ) . map ( ( a : { id : string ; name : string } ) => ( { id : a . id , name : a . name } ) ) ) ;
66+ }
5567 if ( matchData . patterns ) {
5668 const grouped : Record < number , { id : number ; payee_pattern : string } [ ] > = { } ;
5769 for ( const p of matchData . patterns ) {
@@ -90,7 +102,7 @@ export default function IncomePage() {
90102 const res = await fetch ( "/api/income" , { method : "POST" , headers : { "Content-Type" : "application/json" } , body : JSON . stringify ( body ) } ) ;
91103 const data = await res . json ( ) ;
92104 if ( data . id ) {
93- setIncomes ( ( prev ) => [ ...prev , { id : data . id , ...body , is_recurring : 1 , is_active : 1 , average_amount : null , history_count : 0 } ] ) ;
105+ setIncomes ( ( prev ) => [ ...prev , { id : data . id , ...body , is_recurring : 1 , is_active : 1 , average_amount : null , history_count : 0 , target_account_id : "" } ] ) ;
94106 setAddOpen ( false ) ;
95107 form . reset ( ) ;
96108 }
@@ -106,11 +118,12 @@ export default function IncomePage() {
106118 name : fd . get ( "name" ) as string ,
107119 amount : parseFloat ( ( fd . get ( "amount" ) as string ) . replace ( "," , "." ) ) ,
108120 expected_day : parseInt ( fd . get ( "expected_day" ) as string , 10 ) ,
121+ target_account_id : editTargetAccount ,
109122 } ;
110123 console . info ( "[income] Editing:" , body . id ) ;
111124 try {
112125 await fetch ( "/api/income" , { method : "PUT" , headers : { "Content-Type" : "application/json" } , body : JSON . stringify ( body ) } ) ;
113- setIncomes ( ( prev ) => prev . map ( ( i ) => i . id === body . id ? { ...i , name : body . name , amount : body . amount , expected_day : body . expected_day } : i ) ) ;
126+ setIncomes ( ( prev ) => prev . map ( ( i ) => i . id === body . id ? { ...i , name : body . name , amount : body . amount , expected_day : body . expected_day , target_account_id : body . target_account_id } : i ) ) ;
114127 setEditOpen ( false ) ;
115128 setEditTarget ( null ) ;
116129 } catch ( err ) { console . error ( "[income] Edit error:" , err ) ; }
@@ -242,7 +255,7 @@ export default function IncomePage() {
242255 < div
243256 key = { income . id }
244257 className = "list-item"
245- onClick = { ( ) => { setEditTarget ( income ) ; setEditOpen ( true ) ; } }
258+ onClick = { ( ) => { setEditTarget ( income ) ; setEditTargetAccount ( income . target_account_id || "" ) ; setEditOpen ( true ) ; } }
246259 >
247260 < div className = "list-item-body" >
248261 < div className = "list-item-name-row" >
@@ -293,6 +306,24 @@ export default function IncomePage() {
293306 < Input name = "expected_day" type = "number" min = "0" max = "31" defaultValue = { editTarget . expected_day } required />
294307 </ div >
295308 </ div >
309+ < div className = "form-field" >
310+ < Label > { locale === "fi" ? "Tulotili" : "Target account" } </ Label >
311+ < select
312+ className = "input"
313+ value = { editTargetAccount }
314+ onChange = { ( e ) => setEditTargetAccount ( e . target . value ) }
315+ >
316+ < option value = "" > { locale === "fi" ? "Käytä Synci-mappausta" : "Use Synci mapping" } </ option >
317+ { accounts . map ( ( a ) => (
318+ < option key = { a . id } value = { a . id } > { a . name } </ option >
319+ ) ) }
320+ </ select >
321+ < p className = "settings-help" >
322+ { locale === "fi"
323+ ? "Valitse tili, jolle tämä tulo kirjataan YNABissa. Käytä jos pankki maksaa eri tilille kuin haluat seurata."
324+ : "Pick the YNAB account this income should be posted to. Use when the deposit lands on a different account than you want to track." }
325+ </ p >
326+ </ div >
296327 < div className = "form-field" >
297328 < Label > { locale === "fi" ? "Yhdistä maksajaan" : "Match payee" } </ Label >
298329 < div className = "match-pattern-row" >
0 commit comments