awesome ui changes and expense split restructured
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"use client";
|
||||
import { create } from "zustand";
|
||||
import { calculateSplits, type Payment } from "@/lib/utils/expense";
|
||||
import type { Session } from "next-auth";
|
||||
import type { PublicUser } from "next-auth";
|
||||
|
||||
export enum SplitTypeEnum {
|
||||
Fixed = "fixed",
|
||||
Percentage = "percentage",
|
||||
Equal = "equal",
|
||||
}
|
||||
export type SplitType = keyof typeof SplitTypeEnum;
|
||||
export const splitTypeKeys = Object.keys(SplitTypeEnum);
|
||||
|
||||
interface ExpenseStore {
|
||||
amount: number;
|
||||
session: Session | null;
|
||||
|
||||
participants: PublicUser[];
|
||||
friendTarget?: string;
|
||||
splitType: SplitType;
|
||||
payments: Payment[];
|
||||
splits: Record<string, string>;
|
||||
|
||||
setAmount: (amount: number) => void;
|
||||
setSession: (session: Session) => void;
|
||||
|
||||
addParticipant: (user: PublicUser) => void;
|
||||
removeParticipant: (userId: string) => void;
|
||||
|
||||
setFriendTarget: (id?: string) => void;
|
||||
setSplitType: (type: SplitType) => void;
|
||||
setPayments: (payments: Array<Payment>) => void;
|
||||
recalculateSplits: () => void;
|
||||
resetExpenseStore: () => void;
|
||||
}
|
||||
|
||||
const defaultValues: Pick<
|
||||
ExpenseStore,
|
||||
| "amount"
|
||||
| "participants"
|
||||
| "friendTarget"
|
||||
| "splitType"
|
||||
| "payments"
|
||||
| "splits"
|
||||
> = {
|
||||
amount: 0,
|
||||
participants: [],
|
||||
friendTarget: undefined,
|
||||
splitType: "Equal",
|
||||
payments: [],
|
||||
splits: {},
|
||||
};
|
||||
|
||||
export const useExpenseStore = create<ExpenseStore>((set, get) => ({
|
||||
...defaultValues,
|
||||
session: null,
|
||||
setAmount: (amount) => set({ amount }),
|
||||
setSession: (session) =>
|
||||
set(
|
||||
{
|
||||
session,
|
||||
participants: [session.user],
|
||||
payments: [{ amount: get().amount, userId: session.user.id }],
|
||||
},
|
||||
false
|
||||
),
|
||||
|
||||
addParticipant: (newUser) => {
|
||||
const { participants } = get();
|
||||
if (!participants.find((user) => user.id === newUser.id)) {
|
||||
set({ participants: [...participants, newUser] });
|
||||
}
|
||||
},
|
||||
|
||||
removeParticipant: (userId) => {
|
||||
const filtered = get().participants.filter((user) => user.id !== userId);
|
||||
set({ participants: filtered });
|
||||
},
|
||||
|
||||
setFriendTarget: (id) => set({ friendTarget: id }),
|
||||
setSplitType: (type) => set({ splitType: type }),
|
||||
setPayments: (payments) => set({ payments }),
|
||||
|
||||
recalculateSplits: () => {
|
||||
const { amount, participants, splitType } = get();
|
||||
const splits = calculateSplits({
|
||||
amount,
|
||||
users: participants.map((u) => u.id!),
|
||||
splitType,
|
||||
});
|
||||
set({ splits });
|
||||
},
|
||||
|
||||
resetExpenseStore: () => set(defaultValues),
|
||||
}));
|
||||
+14
-11
@@ -1,10 +1,13 @@
|
||||
import type { SplitType } from "@/hooks/use-expense-split";
|
||||
import type { SplitType } from "@/lib/store/expense-store";
|
||||
import type { z } from "zod";
|
||||
import { expenseSplitSchema } from "../validations/expense";
|
||||
|
||||
export type Debt = {
|
||||
from: string;
|
||||
to: string;
|
||||
amount: number;
|
||||
};
|
||||
const debtSchema = expenseSplitSchema.pick({
|
||||
owedFromId: true,
|
||||
owedToId: true,
|
||||
amount: true,
|
||||
});
|
||||
export type Debt = z.infer<typeof debtSchema>;
|
||||
|
||||
export type Payment = {
|
||||
userId: string;
|
||||
@@ -18,8 +21,6 @@ export function calculateSplits(args: {
|
||||
}): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
const { splitType = "Equal", amount, users, payments } = args;
|
||||
// If splits are provided, validate them
|
||||
// const amount = Object.values(payments).reduce((sum, value) => sum + value, 0);
|
||||
|
||||
switch (splitType) {
|
||||
case "Equal":
|
||||
@@ -69,7 +70,7 @@ export function calculateDepts(
|
||||
total: number,
|
||||
users: string[],
|
||||
payments: Array<Payment>
|
||||
): Debt[] {
|
||||
): Array<Debt> {
|
||||
console.log("Payments: ", payments);
|
||||
|
||||
const share = total / users.length;
|
||||
@@ -90,6 +91,8 @@ export function calculateDepts(
|
||||
const debtors = Object.entries(balances)
|
||||
.filter(([_, balance]) => balance < 0)
|
||||
.map(([userId, balance]) => ({ userId, balance: -balance }));
|
||||
console.log("debtors: ", debtors);
|
||||
console.log("creditors: ", creditors);
|
||||
|
||||
const debts: Debt[] = [];
|
||||
|
||||
@@ -103,8 +106,8 @@ export function calculateDepts(
|
||||
const payment = Math.min(amountToPay, creditor.balance);
|
||||
|
||||
debts.push({
|
||||
from: debtor.userId,
|
||||
to: creditor.userId,
|
||||
owedToId: debtor.userId,
|
||||
owedFromId: creditor.userId,
|
||||
amount: parseFloat(payment.toFixed(2)), // for rounding safety
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { z } from "zod";
|
||||
const amount = z.number().min(0.01);
|
||||
|
||||
export const expenseSchema = z.object({
|
||||
amount: z.number().min(0.01),
|
||||
description: z.string().optional(),
|
||||
amount,
|
||||
description: z.string().nonempty({ message: "Description is required" }),
|
||||
});
|
||||
|
||||
export const expenseSplitSchema = z.object({
|
||||
expenseId: z.string(),
|
||||
paidById: z.string().min(1),
|
||||
owedById: z.string().min(1),
|
||||
amount: z.number().min(1),
|
||||
owedToId: z.string().min(1),
|
||||
owedFromId: z.string().min(1),
|
||||
amount,
|
||||
status: z.string(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user