feat: budget crud actions

This commit is contained in:
Max Koon
2025-12-06 23:02:28 -05:00
parent 76f2a43bd0
commit 27f6e627d4
20 changed files with 445 additions and 113 deletions

View File

@@ -1,5 +1,6 @@
import { useEffect, type ReactNode } from "react";
import { Text, Pressable } from "react-native";
import { useShortcut, type Key } from "../lib/shortcuts";
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
@@ -7,7 +8,7 @@ export interface ButtonProps {
children: ReactNode;
onPress?: () => void;
variant?: "default" | "secondary" | "destructive";
shortcut?: string;
shortcut?: Key;
}
const STYLES: Record<
@@ -22,16 +23,9 @@ const STYLES: Record<
export function Button({ children, variant, onPress, shortcut }: ButtonProps) {
const { backgroundColor, color } = STYLES[variant || "default"];
// if (shortcut) {
// useKeys((key) => {
// if (
// typeof shortcut == "object"
// ? key.name == shortcut.name
// : key.name == shortcut
// )
// return onPress;
// });
// }
if (shortcut && onPress) {
useShortcut(shortcut, onPress);
}
return (
<Pressable onPress={onPress} style={{ backgroundColor }}>

View File

@@ -1,11 +1,12 @@
import { createContext, type ReactNode } from "react";
import { createContext, use, type ReactNode } from "react";
import { Modal, View, Text } from "react-native";
import { useShortcut } from "../lib/shortcuts";
export interface DialogState {
close?: () => void;
}
export const Context = createContext<DialogState>({
close: () => {},
close: () => { },
});
interface ProviderProps {
@@ -37,6 +38,9 @@ interface ContentProps {
children: ReactNode;
}
export function Content({ children }: ContentProps) {
const { close } = use(Context);
useShortcut("escape", () => close?.());
return (
<View
style={{ backgroundColor: "white", padding: 12, alignItems: "center" }}

View File

@@ -9,6 +9,7 @@ import {
import { View, Text } from "react-native";
import type { KeyEvent } from "@opentui/core";
import { useShortcut } from "../lib/shortcuts/hooks";
import type { Key } from "../lib/shortcuts";
const HEADER_COLOR = "#7158e2";
const TABLE_COLORS = ["#ddd", "#eee"];
@@ -50,17 +51,22 @@ function renderCell(row: ValidRecord, column: Column): string {
return cell.toString();
}
interface TableShortcut<T> {
key: Key;
handler: (params: { selected: T[]; index: number }) => void;
}
export interface ProviderProps<T> {
data: T[];
columns: Column[];
children: ReactNode;
onKey?: (event: KeyEvent, selected: T[]) => void;
shortcuts?: TableShortcut<T>[];
}
export function Provider<T extends ValidRecord>({
data,
columns,
children,
onKey,
shortcuts,
}: ProviderProps<T>) {
const [idx, setIdx] = useState(0);
const [selectedFrom, setSelectedFrom] = useState<number>();
@@ -68,9 +74,30 @@ export function Provider<T extends ValidRecord>({
useShortcut("j", () => {
setIdx((prev) => Math.min(prev + 1, data.length - 1));
});
useShortcut("down", () => {
setIdx((prev) => Math.min(prev + 1, data.length - 1));
});
useShortcut("k", () => {
setIdx((prev) => Math.max(prev - 1, 0));
});
useShortcut("up", () => {
setIdx((prev) => Math.max(prev - 1, 0));
});
useEffect(() => {
setIdx((prev) => Math.max(Math.min(prev, data.length - 1), 0));
}, [data]);
if (shortcuts) {
for (const shortcut of shortcuts) {
useShortcut(shortcut.key, () => {
const from = selectedFrom ? Math.min(idx, selectedFrom) : idx;
const to = selectedFrom ? Math.max(idx, selectedFrom) : idx;
const selected = data.slice(from, to + 1);
shortcut.handler({ selected, index: idx });
});
}
}
const columnMap = new Map(
columns.map((col) => {