Skip to content

Commit

Permalink
Rework Transaction Form (#26)
Browse files Browse the repository at this point in the history
* Remove forwardRef wrappers as they broke typings (anyway not needed yet, just unnecessary complexity)

* Re-add ref capability to Button component using the innerRef pattern

* Fix types

* New mockup for Transaction page

* Rework transaction model
  • Loading branch information
felixmokross authored Jan 3, 2025
1 parent 8cab9f1 commit 5591e7e
Show file tree
Hide file tree
Showing 37 changed files with 1,536 additions and 4,368 deletions.
20 changes: 0 additions & 20 deletions app/balance-change-categories/builders.ts

This file was deleted.

25 changes: 0 additions & 25 deletions app/balance-change-categories/functions.server.ts

This file was deleted.

4 changes: 0 additions & 4 deletions app/balance-change-categories/types.ts

This file was deleted.

34 changes: 16 additions & 18 deletions app/common/base/buttons/button.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { forwardRef } from "react";
import type {
Ref,
ComponentPropsWithRef,
ComponentPropsWithoutRef,
ElementType,
PropsWithChildren,
Ref,
} from "react";
import { cn } from "../classnames";
import type { IconComponentType } from "../icons/types";
Expand Down Expand Up @@ -43,34 +42,32 @@ function buttonClassName(size: ButtonSize, variant: ButtonVariant) {
*
* For a link button use the `LinkButton` component instead.
*/
export const Button = forwardRef(function Button<T extends ElementType>(
{
as,
variant = "secondary",
size = "default",
children,
className,
icon,
...props
}: ButtonProps<T>,
ref: Ref<HTMLButtonElement>,
) {
export function Button<T extends ElementType = "button">({
as,
variant = "secondary",
size = "default",
children,
className,
icon,
innerRef,
...props
}: ButtonProps<T>) {
const Component = as || "button";
const Icon = icon;

const classNames = buttonClassName(size, variant);
return (
<Component
className={cn(classNames.button, className)}
ref={innerRef}
{...(Component === "button" ? { type: "button" } : {})}
{...props}
ref={ref}
>
{Icon && <Icon className={classNames.icon} />}
{children}
</Component>
);
});
}

export type ButtonProps<T extends ElementType> = PropsWithChildren<
{
Expand All @@ -88,7 +85,8 @@ export type ButtonProps<T extends ElementType> = PropsWithChildren<
icon?: IconComponentType;
/** The size of the button. */
size?: "default" | "compact";
} & Omit<ComponentPropsWithRef<T>, "as">
innerRef?: T extends "button" ? Ref<HTMLButtonElement> : never;
} & Omit<ComponentPropsWithoutRef<T>, "as">
>;

type ButtonVariant = "primary" | "secondary" | "negative";
Expand Down
4 changes: 2 additions & 2 deletions app/common/base/buttons/icon-button.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComponentPropsWithRef, ElementType } from "react";
import type { ComponentPropsWithoutRef, ElementType } from "react";
import { cn } from "../classnames";
import type { IconComponentType } from "../icons/types";
import { Link } from "@remix-run/react";
Expand All @@ -8,7 +8,7 @@ export type IconButtonProps<T extends ElementType> = {
altText: string;
icon: IconComponentType;
size?: "default" | "large";
} & Omit<ComponentPropsWithRef<T>, "as">;
} & Omit<ComponentPropsWithoutRef<T>, "as">;

export function IconButton<T extends ElementType>({
as,
Expand Down
14 changes: 5 additions & 9 deletions app/common/base/buttons/link-button.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import { Link } from "@remix-run/react";
import type { ButtonProps } from "./button";
import { Button } from "./button";
import { forwardRef } from "react";
import type { Ref, ComponentPropsWithRef } from "react";
import type { ComponentPropsWithoutRef } from "react";

export type LinkButtonProps = {
/** The link target. See the Remix `Link` component for details. */
to: ComponentPropsWithRef<typeof Link>["to"];
to: ComponentPropsWithoutRef<typeof Link>["to"];
} & Omit<ButtonProps<typeof Link>, "as" | "to">;

/**
* Renders a Remix `Link` component styled as a button. Any additional props are
* forwarded to the `Link` component. Use the `to` prop to specify the link
* target.
*/
export const LinkButton = forwardRef(function LinkButton(
props: LinkButtonProps,
ref: Ref<HTMLButtonElement>,
) {
return <Button {...props} as={Link} ref={ref} />;
});
export const LinkButton = function LinkButton(props: LinkButtonProps) {
return <Button {...props} as={Link} />;
};
14 changes: 8 additions & 6 deletions app/common/base/forms/radio-group.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ export const ManyOptions: Story = {
name: "my-radio-group",
defaultValue: "1",
options: [
{ value: "1", label: "Transfer" },
{ value: "2", label: "Balance Change" },
{ value: "3", label: "Value Change" },
{ value: "1", label: "Charge" },
{ value: "2", label: "Deposit" },
{ value: "3", label: "Income" },
{ value: "4", label: "Expense" },
],
},
};
Expand All @@ -63,9 +64,10 @@ export const Compact: Story = {
name: "my-radio-group",
defaultValue: "1",
options: [
{ value: "1", label: "Transfer" },
{ value: "2", label: "Balance Change" },
{ value: "3", label: "Value Change" },
{ value: "1", label: "Charge" },
{ value: "2", label: "Deposit" },
{ value: "3", label: "Income" },
{ value: "4", label: "Expense" },
],
},
};
Expand Down
2 changes: 1 addition & 1 deletion app/common/base/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export default function Modal({
variant="secondary"
type="button"
onClick={() => onDismiss()}
ref={cancelButtonRef}
innerRef={cancelButtonRef}
disabled={isDisabled}
>
Cancel
Expand Down
15 changes: 5 additions & 10 deletions app/common/main-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Disclosure } from "@headlessui/react";
import type { PropsWithChildren, Ref } from "react";
import { forwardRef } from "react";
import type { PropsWithChildren } from "react";
import type { NavLinkProps as RemixNavLinkProps } from "@remix-run/react";
import { NavLink as RemixNavLink } from "@remix-run/react";
import { cn } from "./base/classnames";
Expand All @@ -22,8 +21,8 @@ export function MainMenu({ user }: MainMenuProps) {
<MainMenuNavLink to=".">Home</MainMenuNavLink>
<MainMenuNavLink to="accounts">Accounts</MainMenuNavLink>
<MainMenuNavLink to="asset-classes">Asset Classes</MainMenuNavLink>
<MainMenuNavLink to="balance-change-categories">
Balance Change Categories
<MainMenuNavLink to="income-categories">
Income Categories
</MainMenuNavLink>
</div>
<div className="border-t border-gray-200 pb-3 pt-4">
Expand Down Expand Up @@ -74,10 +73,7 @@ function MainMenuNavLink({ to, children }: MainMenuNavLinkProps) {

type NavLinkProps = Omit<RemixNavLinkProps, "className">;

const NavLink = forwardRef(function NavLink(
props: NavLinkProps,
ref: Ref<HTMLAnchorElement>,
) {
function NavLink(props: NavLinkProps) {
return (
<RemixNavLink
{...props}
Expand All @@ -89,7 +85,6 @@ const NavLink = forwardRef(function NavLink(
"block border-l-4 py-2 pl-3 pr-4 text-base font-medium",
)
}
ref={ref}
/>
);
});
}
18 changes: 18 additions & 0 deletions app/income-categories/builders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createId } from "@paralleldrive/cuid2";
import type { IncomeCategoryDto } from "./types";

export function buildIncomeCategoryDto(
values: Partial<IncomeCategoryDto> = {},
) {
return {
id: createId(),
name: "Salary",

createdAt: new Date(2021, 3, 7).toJSON(),
updatedAt: new Date(2021, 3, 7).toJSON(),

userId: createId(),

...values,
} as IncomeCategoryDto;
}
22 changes: 22 additions & 0 deletions app/income-categories/functions.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { IncomeCategory } from "@prisma/client";
import { prisma } from "~/common/prisma.server";

export async function getIncomeCategories(userId: IncomeCategory["userId"]) {
return await prisma.incomeCategory.findMany({
where: { userId },
});
}

export async function createIncomeCategory(
userId: IncomeCategory["userId"],
{ name }: Pick<IncomeCategory, "name">,
) {
name = name.trim();

return await prisma.incomeCategory.create({
data: {
name,
userId,
},
});
}
4 changes: 4 additions & 0 deletions app/income-categories/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { IncomeCategory } from "@prisma/client";
import type { SerializeFrom } from "@remix-run/node";

export type IncomeCategoryDto = SerializeFrom<IncomeCategory>;
2 changes: 1 addition & 1 deletion app/ledgers-lines/builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function buildLedgerDateGroupDto(
export function buildLedgerLineDto(values: Partial<LedgerLineDto> = {}) {
return {
id: createId(),
type: BookingType.ACCOUNT_CHANGE,
type: BookingType.CHARGE,
transaction: buildLedgerLineTransactionDto(),
amount: "1000",
amountFormatted: "1,000.00",
Expand Down
16 changes: 7 additions & 9 deletions app/ledgers-lines/functions.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ export async function getReverseLedgerDateGroups({
const groupByDate = new Map<number, LedgerDateGroup>();

for (const line of pagedLedgerLines) {
const dateKey = line.transaction.date.valueOf();
const dateKey = line.date.valueOf();

if (!groupByDate.has(dateKey)) {
groupByDate.set(dateKey, {
date: line.transaction.date,
date: line.date,
lines: [line],
balance: line.balance,
});
Expand Down Expand Up @@ -133,24 +133,25 @@ async function getBookings({
}) {
return await prisma.booking.findMany({
where: {
type: BookingType.ACCOUNT_CHANGE,
type: { in: [BookingType.DEPOSIT, BookingType.CHARGE] },
accountId,
userId,
},
select: {
id: true,
date: true,
type: true,
transaction: {
select: {
id: true,
date: true,
note: true,
bookings: {
select: {
id: true,
type: true,
account: { select: { id: true, name: true, slug: true } },
balanceChangeCategory: { select: { id: true, name: true } },
incomeCategory: { select: { id: true, name: true } },
expenseCategory: { select: { id: true, name: true } },
note: true,
},
},
Expand All @@ -159,9 +160,6 @@ async function getBookings({
note: true,
amount: true,
},
orderBy: [
{ transaction: { date: "asc" } },
{ transaction: { createdAt: "asc" } },
],
orderBy: [{ date: "asc" }, { transaction: { createdAt: "asc" } }],
});
}
Loading

0 comments on commit 5591e7e

Please # to comment.