feat: update costs
This commit is contained in:
@@ -24,7 +24,7 @@ import { Configuration, CountryCode, PlaidApi, PlaidEnvironments, Products } fro
|
|||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { balance, plaidAccessTokens, plaidLink, transaction } from "@money/shared/db";
|
import { balance, plaidAccessTokens, plaidLink, transaction } from "@money/shared/db";
|
||||||
import { asc, desc, eq, sql, type InferInsertModel } from "drizzle-orm";
|
import { asc, desc, eq, inArray, sql, type InferInsertModel } from "drizzle-orm";
|
||||||
|
|
||||||
|
|
||||||
const configuration = new Configuration({
|
const configuration = new Configuration({
|
||||||
@@ -123,6 +123,7 @@ const createMutators = (authData: AuthData | null) => {
|
|||||||
id: randomUUID(),
|
id: randomUUID(),
|
||||||
user_id: authData.user.id,
|
user_id: authData.user.id,
|
||||||
plaid_id: tx.transaction_id,
|
plaid_id: tx.transaction_id,
|
||||||
|
account_id: tx.account_id,
|
||||||
name: tx.name,
|
name: tx.name,
|
||||||
amount: tx.amount as any,
|
amount: tx.amount as any,
|
||||||
datetime: tx.datetime ? new Date(tx.datetime) : new Date(tx.date),
|
datetime: tx.datetime ? new Date(tx.datetime) : new Date(tx.date),
|
||||||
@@ -132,7 +133,15 @@ const createMutators = (authData: AuthData | null) => {
|
|||||||
|
|
||||||
await db.insert(transaction).values(transactions).onConflictDoNothing({
|
await db.insert(transaction).values(transactions).onConflictDoNothing({
|
||||||
target: transaction.plaid_id,
|
target: transaction.plaid_id,
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const txReplacingPendingIds = data.transactions
|
||||||
|
.filter(t => t.pending_transaction_id)
|
||||||
|
.map(t => t.pending_transaction_id!);
|
||||||
|
|
||||||
|
await db.delete(transaction)
|
||||||
|
.where(inArray(transaction.plaid_id, txReplacingPendingIds));
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,47 @@
|
|||||||
import { authClient } from '@/lib/auth-client';
|
import { authClient } from '@/lib/auth-client';
|
||||||
import { Button, Linking, Pressable, ScrollView, Text, View } from 'react-native';
|
import { Pressable, ScrollView, Text, View } from 'react-native';
|
||||||
import { useQuery, useZero } from "@rocicorp/zero/react";
|
import { useQuery, useZero } from "@rocicorp/zero/react";
|
||||||
import { queries, type Mutators, type Schema } from '@money/shared';
|
import { queries, type Mutators, type Schema } from '@money/shared';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { transaction } from '@/shared/src/db';
|
|
||||||
|
|
||||||
export default function HomeScreen() {
|
export default function HomeScreen() {
|
||||||
const { data: session } = authClient.useSession();
|
const { data: session } = authClient.useSession();
|
||||||
|
|
||||||
const z = useZero<Schema, Mutators>();
|
const z = useZero<Schema, Mutators>();
|
||||||
const [plaidLink] = useQuery(queries.getPlaidLink(session));
|
|
||||||
const [transactions] = useQuery(queries.allTransactions(session));
|
const [transactions] = useQuery(queries.allTransactions(session));
|
||||||
const [balances] = useQuery(queries.getBalances(session));
|
const [balances] = useQuery(queries.getBalances(session));
|
||||||
|
|
||||||
const [idx, setIdx] = useState(0);
|
const [idx, setIdx] = useState(0);
|
||||||
|
const [accountIdx, setAccountIdx] = useState(0);
|
||||||
|
|
||||||
|
const account = balances.at(accountIdx)!;
|
||||||
|
|
||||||
|
const filteredTransactions = transactions
|
||||||
|
.filter(t => t.account_id == account.plaid_id)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === "j") {
|
if (event.key === "j") {
|
||||||
setIdx((prevIdx) => {
|
setIdx((prevIdx) => {
|
||||||
if (prevIdx + 1 == transactions.length) return prevIdx;
|
if (prevIdx + 1 == filteredTransactions.length) return prevIdx;
|
||||||
return prevIdx + 1
|
return prevIdx + 1
|
||||||
});
|
});
|
||||||
} else if (event.key === "k") {
|
} else if (event.key === "k") {
|
||||||
setIdx((prevIdx) => prevIdx == 0 ? 0 : prevIdx - 1);
|
setIdx((prevIdx) => prevIdx == 0 ? 0 : prevIdx - 1);
|
||||||
|
} else if (event.key == 'g') {
|
||||||
|
setIdx(0);
|
||||||
|
} else if (event.key == "G") {
|
||||||
|
setIdx(transactions.length - 1);
|
||||||
|
} else if (event.key == 'R') {
|
||||||
|
z.mutate.link.updateTransactions();
|
||||||
|
z.mutate.link.updateBalences();
|
||||||
|
} else if (event.key == 'h') {
|
||||||
|
setAccountIdx((prevIdx) => prevIdx == 0 ? 0 : prevIdx - 1);
|
||||||
|
} else if (event.key == 'l') {
|
||||||
|
setAccountIdx((prevIdx) => {
|
||||||
|
if (prevIdx + 1 == balances.length) return prevIdx;
|
||||||
|
return prevIdx + 1
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,33 +50,32 @@ export default function HomeScreen() {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("keydown", handleKeyDown);
|
window.removeEventListener("keydown", handleKeyDown);
|
||||||
};
|
};
|
||||||
}, [transactions]);
|
}, [filteredTransactions, balances]);
|
||||||
|
|
||||||
|
function lpad(n: number): string {
|
||||||
|
const LEN = 9;
|
||||||
|
const nstr = n.toFixed(2).toLocaleString();
|
||||||
|
return Array.from({ length: LEN - nstr.length }).join(" ") + nstr;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
{plaidLink && <Button onPress={() => {
|
|
||||||
z.mutate.link.updateTransactions();
|
|
||||||
}} title="Update transactions" />}
|
|
||||||
{plaidLink && <Button onPress={() => {
|
|
||||||
z.mutate.link.updateBalences();
|
|
||||||
}} title="Update bal" />}
|
|
||||||
|
|
||||||
<View style={{ flexDirection: "row" }}>
|
<View style={{ flexDirection: "row" }}>
|
||||||
<View style={{ backgroundColor: '' }}>
|
<View style={{ backgroundColor: '' }}>
|
||||||
{balances.map(bal => <View key={bal.id}>
|
{balances.map((bal, i) => <View key={bal.id} style={{ backgroundColor: i == accountIdx ? 'black' : undefined}}>
|
||||||
<Text style={{ fontFamily: 'mono', }}>{bal.name}: {bal.current} ({bal.avaliable})</Text>
|
<Text style={{ fontFamily: 'mono', color: i == accountIdx ? 'white' : undefined }}>{bal.name}: {bal.current} ({bal.avaliable})</Text>
|
||||||
</View>)}
|
</View>)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View>
|
<View>
|
||||||
{transactions.map((t, i) => <Pressable onHoverIn={() => {
|
{filteredTransactions.map((t, i) => <Pressable onHoverIn={() => {
|
||||||
setIdx(i);
|
setIdx(i);
|
||||||
}} style={{ backgroundColor: i == idx ? 'black' : undefined, cursor: 'default' as 'auto' }} key={t.id}>
|
}} style={{ backgroundColor: i == idx ? 'black' : undefined, cursor: 'default' as 'auto' }} key={t.id}>
|
||||||
<Text style={{ fontFamily: 'mono', color: i == idx ? 'white' : undefined }}>{new Date(t.datetime!).toDateString()} {t.name.substring(0, 50)} {t.amount}</Text>
|
<Text style={{ fontFamily: 'mono', color: i == idx ? 'white' : undefined }}>{new Date(t.datetime!).toDateString()} <Text style={{ color: t.amount > 0 ? 'red' : 'green' }}>{lpad(t.amount)}</Text> {t.name.substring(0, 50)}</Text>
|
||||||
</Pressable>)}
|
</Pressable>)}
|
||||||
</View>
|
</View>
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<Text style={{ fontFamily: 'mono' }}>{JSON.stringify(JSON.parse(transactions.at(idx)?.json || "null"), null, 4)}</Text>
|
<Text style={{ fontFamily: 'mono' }}>{JSON.stringify(JSON.parse(filteredTransactions.at(idx)?.json || "null"), null, 4)}</Text>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const transaction = pgTable("transaction", {
|
|||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
user_id: text("user_id").notNull(),
|
user_id: text("user_id").notNull(),
|
||||||
plaid_id: text("plaid_id").notNull().unique(),
|
plaid_id: text("plaid_id").notNull().unique(),
|
||||||
|
account_id: text("account_id").notNull(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
amount: decimal("amount").notNull(),
|
amount: decimal("amount").notNull(),
|
||||||
datetime: timestamp("datetime"),
|
datetime: timestamp("datetime"),
|
||||||
|
|||||||
@@ -185,6 +185,15 @@ export const schema = {
|
|||||||
"plaid_id"
|
"plaid_id"
|
||||||
>,
|
>,
|
||||||
},
|
},
|
||||||
|
account_id: {
|
||||||
|
type: "string",
|
||||||
|
optional: false,
|
||||||
|
customType: null as unknown as ZeroCustomType<
|
||||||
|
ZeroSchema,
|
||||||
|
"transaction",
|
||||||
|
"account_id"
|
||||||
|
>,
|
||||||
|
},
|
||||||
name: {
|
name: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user