import type { LoaderFunction } from "remix"; import { useLoaderData, Link, useCatch, redirect } from "remix"; import type { Expense, User } from "@prisma/client"; import { db } from "~/utils/db.server"; import { getUser } from "~/utils/session.server"; import Group from "~/icons/Group"; type LoaderData = { lastExpenses: (Expense & { user: User })[]; user: User; teamCounts: { id: string; username: string; icon: string; count: number; spent: number; dueAmount: number; avgIncome: number; }[]; totalExpenses: { count: number; amount: number; }; }; export const loader: LoaderFunction = async ({ request }) => { const user = await getUser(request); if (!user) { return redirect("/login"); } const lastExpenses = await db.expense.findMany({ include: { user: true, }, take: 6, orderBy: { createdAt: "desc" }, where: { teamId: user.teamId }, }); let teamExpenses = await db.expense.groupBy({ by: ["userId"], _count: { _all: true, }, _sum: { amount: true, }, where: { user: { teamId: user.teamId } }, }); let expensesByUser = user.team.members.map((m) => ({ id: m.id, username: m.username, icon: m.icon, count: teamExpenses.find((e) => e.userId === m.id)?._count?._all ?? 0, spent: teamExpenses.find((e) => e.userId === m.id)?._sum?.amount ?? 0, avgIncome: m.avgIncome ?? 0, dueAmount: 0, })); let totalExpenses = expensesByUser.reduce( (acc, { count, spent, avgIncome }) => ({ count: acc.count + count, amount: acc.amount + spent, incomes: acc.incomes + avgIncome, }), { count: 0, amount: 0, incomes: 0 } ); const avgPerUser = totalExpenses.amount / user.team.members.length; const quotaPerUser = expensesByUser.reduce( (acc: { [key: string]: number }, userData) => { acc[userData.id] = (userData.avgIncome / totalExpenses.incomes) * totalExpenses.amount; return acc; }, {} ); let teamCounts = expensesByUser.map((userData) => ({ ...userData, dueAmount: user.team.balanceByIncome ? quotaPerUser[userData.id] - userData.spent : avgPerUser - userData.spent, })); const data: LoaderData = { lastExpenses, user, totalExpenses, teamCounts, }; return data; }; export default function ExpensesIndexRoute() { const data = useLoaderData(); return (

Last expenses

    {data.lastExpenses?.map((exp) => (
  • {exp.user.icon ?? exp.user.username[0]}
    0 ? "text-error" : "text-success" }`} > {-exp.amount} €
    {new Intl.DateTimeFormat("it", { dateStyle: "short", timeStyle: "short", }).format(new Date(exp.createdAt))} {exp.description}
  • ))}
See all

Team balance

{data.teamCounts?.map((user) => (
{user.icon ?? user.username[0]}
0 ? "text-error" : "text-success" }`} > {Math.abs(user.dueAmount)} €
{user.username}
{user.dueAmount > 0 ? `${user.id === data.user.id ? "You owe" : "Owes"} others` : `Others owe ${user.id === data.user.id ? "you" : ""}`}
))}
New
Trasfer
); } export function CatchBoundary() { const caught = useCatch(); if (caught.status === 404) { return (
There are no expenses to display.
); } throw new Error(`Unexpected caught response with status: ${caught.status}`); } export function ErrorBoundary() { return
I did a whoopsies.
; }