feat: budget crud actions
This commit is contained in:
@@ -1,26 +1,39 @@
|
||||
import { use } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { use, useRef, useState } from "react";
|
||||
import { View, Text, TextInput } from "react-native";
|
||||
import { RouterContext } from ".";
|
||||
import { queries, type Mutators, type Schema } from "@money/shared";
|
||||
import {
|
||||
queries,
|
||||
type Category,
|
||||
type Mutators,
|
||||
type Schema,
|
||||
} from "@money/shared";
|
||||
import { useQuery, useZero } from "@rocicorp/zero/react";
|
||||
import * as Table from "../components/Table";
|
||||
import { Button } from "../components/Button";
|
||||
import * as Dialog from "../components/Dialog";
|
||||
|
||||
const COLUMNS: Table.Column[] = [{ name: "label", label: "Name" }];
|
||||
const COLUMNS: Table.Column[] = [
|
||||
{ name: "label", label: "Name" },
|
||||
{ name: "week", label: "Week" },
|
||||
{ name: "month", label: "Month" },
|
||||
{ name: "year", label: "Year" },
|
||||
{ name: "order", label: "Order" },
|
||||
];
|
||||
|
||||
export function Budget() {
|
||||
const { auth } = use(RouterContext);
|
||||
const [budgets] = useQuery(queries.getBudgets(auth));
|
||||
// const [items] = useQuery(queries.getBudgetCategories(auth));
|
||||
|
||||
const items: any[] = [];
|
||||
const [renaming, setRenaming] = useState<Category>();
|
||||
|
||||
const z = useZero<Schema, Mutators>();
|
||||
const refText = useRef("");
|
||||
|
||||
const newBudget = () => {
|
||||
const id = new Date().getTime().toString();
|
||||
const categoryId = new Date().getTime().toString();
|
||||
z.mutate.budget.create({
|
||||
id,
|
||||
categoryId,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -45,21 +58,89 @@ export function Budget() {
|
||||
|
||||
const budget = budgets[0]!;
|
||||
|
||||
const data = budget.categories.slice().map((category) => {
|
||||
const { amount } = category;
|
||||
const week = amount;
|
||||
const month = amount * 4;
|
||||
const year = amount * 12;
|
||||
|
||||
return {
|
||||
...category,
|
||||
...{
|
||||
week,
|
||||
month,
|
||||
year,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const newCategory = ({ index }: { index: number }) => {
|
||||
const id = new Date().getTime().toString();
|
||||
z.mutate.budget.createCategory({
|
||||
id,
|
||||
budgetId: budget.id,
|
||||
order: index - 1,
|
||||
});
|
||||
};
|
||||
|
||||
const deleteCategory = ({ selected }: { selected: { id: string }[] }) => {
|
||||
for (const { id } of selected) {
|
||||
z.mutate.budget.deleteCategory({ id });
|
||||
}
|
||||
};
|
||||
|
||||
const renameCategory = ({ selected }: { selected: Category[] }) => {
|
||||
for (const category of selected) {
|
||||
setRenaming(category);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<View>
|
||||
<Text style={{ fontFamily: "mono" }}>
|
||||
<Dialog.Provider
|
||||
visible={renaming != undefined}
|
||||
close={() => setRenaming(undefined)}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<Text style={{ fontFamily: "mono" }}>Edit Category</Text>
|
||||
|
||||
<TextInput
|
||||
style={{ fontFamily: "mono" }}
|
||||
autoFocus
|
||||
selectTextOnFocus
|
||||
defaultValue={renaming?.label}
|
||||
onChangeText={(t) => {
|
||||
refText.current = t;
|
||||
}}
|
||||
onKeyPress={(e) => {
|
||||
if (!renaming) return;
|
||||
if (e.nativeEvent.key == "Enter") {
|
||||
z.mutate.budget.updateCategory({
|
||||
id: renaming.id,
|
||||
label: refText.current,
|
||||
});
|
||||
setRenaming(undefined);
|
||||
} else if (e.nativeEvent.key == "Escape") {
|
||||
setRenaming(undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Dialog.Content>
|
||||
</Dialog.Provider>
|
||||
|
||||
<View style={{ alignItems: "flex-start" }}>
|
||||
<Text style={{ fontFamily: "mono", textAlign: "left" }}>
|
||||
Selected Budget: {budget.label}
|
||||
</Text>
|
||||
</View>
|
||||
<Table.Provider
|
||||
data={items}
|
||||
data={data}
|
||||
columns={COLUMNS}
|
||||
onKey={(event) => {
|
||||
if (event.name == "n" && event.shift) {
|
||||
newBudget();
|
||||
}
|
||||
}}
|
||||
shortcuts={[
|
||||
{ key: "i", handler: newCategory },
|
||||
{ key: "d", handler: deleteCategory },
|
||||
{ key: "r", handler: renameCategory },
|
||||
]}
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ flexShrink: 0 }}>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { createContext, use, useEffect, useRef } from "react";
|
||||
import { createContext, use, type ReactNode } from "react";
|
||||
import { Transactions } from "./transactions";
|
||||
import { View } from "react-native";
|
||||
import { Settings } from "./settings";
|
||||
import type { AuthData } from "@money/shared/auth";
|
||||
import { Budget } from "./budget";
|
||||
import { ShortcutProvider, ShortcutDebug, keysStore } from "../lib/shortcuts";
|
||||
import { useShortcut } from "../lib/shortcuts/hooks";
|
||||
import {
|
||||
ShortcutProvider,
|
||||
ShortcutDebug,
|
||||
useShortcut,
|
||||
type KeyName,
|
||||
} from "../lib/shortcuts";
|
||||
|
||||
const PAGES = {
|
||||
"/": {
|
||||
@@ -24,7 +28,10 @@ const PAGES = {
|
||||
"/family": {},
|
||||
},
|
||||
},
|
||||
};
|
||||
} satisfies Record<
|
||||
string,
|
||||
{ screen: ReactNode; key: KeyName; children?: Record<string, unknown> }
|
||||
>;
|
||||
|
||||
type Join<A extends string, B extends string> = `${A}${B}` extends `${infer X}`
|
||||
? X
|
||||
@@ -32,14 +39,14 @@ type Join<A extends string, B extends string> = `${A}${B}` extends `${infer X}`
|
||||
|
||||
type ChildRoutes<Parent extends string, Children> = {
|
||||
[K in keyof Children & string]: K extends `/${string}`
|
||||
? Join<Parent, K>
|
||||
: never;
|
||||
? Join<Parent, K>
|
||||
: never;
|
||||
}[keyof Children & string];
|
||||
|
||||
type Routes<T> = {
|
||||
[K in keyof T & string]:
|
||||
| K
|
||||
| (T[K] extends { children: infer C } ? ChildRoutes<K, C> : never);
|
||||
| K
|
||||
| (T[K] extends { children: infer C } ? ChildRoutes<K, C> : never);
|
||||
}[keyof T & string];
|
||||
|
||||
export type Route = Routes<typeof PAGES>;
|
||||
@@ -53,7 +60,7 @@ interface RouterContextType {
|
||||
export const RouterContext = createContext<RouterContextType>({
|
||||
auth: null,
|
||||
route: "/",
|
||||
setRoute: () => {},
|
||||
setRoute: () => { },
|
||||
});
|
||||
|
||||
type AppProps = {
|
||||
@@ -84,8 +91,8 @@ function Main() {
|
||||
route in PAGES
|
||||
? (route as keyof typeof PAGES)
|
||||
: (Object.keys(PAGES)
|
||||
.sort((a, b) => b.length - a.length)
|
||||
.find((p) => route.startsWith(p)) as keyof typeof PAGES);
|
||||
.sort((a, b) => b.length - a.length)
|
||||
.find((p) => route.startsWith(p)) as keyof typeof PAGES);
|
||||
|
||||
return (
|
||||
<View style={{ backgroundColor: "white", flex: 1 }}>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { RouterContext, type Route } from ".";
|
||||
import { General } from "./settings/general";
|
||||
import { Accounts } from "./settings/accounts";
|
||||
import { Family } from "./settings/family";
|
||||
import { useShortcut } from "../lib/shortcuts";
|
||||
|
||||
type SettingsRoute = Extract<Route, `/settings${string}`>;
|
||||
|
||||
@@ -30,27 +31,24 @@ type Tab = keyof typeof TABS;
|
||||
export function Settings() {
|
||||
const { route, setRoute } = use(RouterContext);
|
||||
|
||||
// useKeyboard(
|
||||
// (key) => {
|
||||
// if (key.name == "h") {
|
||||
// const currentIdx = Object.entries(TABS).findIndex(
|
||||
// ([tabRoute, _]) => tabRoute == route,
|
||||
// );
|
||||
// const routes = Object.keys(TABS) as SettingsRoute[];
|
||||
// const last = routes[currentIdx - 1];
|
||||
// if (!last) return;
|
||||
// setRoute(last);
|
||||
// } else if (key.name == "l") {
|
||||
// const currentIdx = Object.entries(TABS).findIndex(
|
||||
// ([tabRoute, _]) => tabRoute == route,
|
||||
// );
|
||||
// const routes = Object.keys(TABS) as SettingsRoute[];
|
||||
// const next = routes[currentIdx + 1];
|
||||
// if (!next) return;
|
||||
// setRoute(next);
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
useShortcut("h", () => {
|
||||
const currentIdx = Object.entries(TABS).findIndex(
|
||||
([tabRoute, _]) => tabRoute == route,
|
||||
);
|
||||
const routes = Object.keys(TABS) as SettingsRoute[];
|
||||
const last = routes[currentIdx - 1];
|
||||
if (!last) return;
|
||||
setRoute(last);
|
||||
});
|
||||
useShortcut("l", () => {
|
||||
const currentIdx = Object.entries(TABS).findIndex(
|
||||
([tabRoute, _]) => tabRoute == route,
|
||||
);
|
||||
const routes = Object.keys(TABS) as SettingsRoute[];
|
||||
const next = routes[currentIdx + 1];
|
||||
if (!next) return;
|
||||
setRoute(next);
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
|
||||
Reference in New Issue
Block a user