From 91aa1c63c151c7a676bb857bc2f5926aebd1d763 Mon Sep 17 00:00:00 2001 From: acuteengle Date: Tue, 16 Jan 2024 23:57:37 +0700 Subject: [PATCH 1/7] Group expenses my date --- .../[groupId]/expenses/expense-list.tsx | 144 +++++++++++------- 1 file changed, 86 insertions(+), 58 deletions(-) diff --git a/src/app/groups/[groupId]/expenses/expense-list.tsx b/src/app/groups/[groupId]/expenses/expense-list.tsx index 92171236..344b3285 100644 --- a/src/app/groups/[groupId]/expenses/expense-list.tsx +++ b/src/app/groups/[groupId]/expenses/expense-list.tsx @@ -16,6 +16,21 @@ type Props = { groupId: string } +function getGroupedExpensesByDate(expenses) { + return expenses.reduce((result, expense) => { + const dateString = formatDate(expense.expenseDate) + result[dateString] = result[dateString] ?? [] + result[dateString].push(expense) + return result + }, {}) +} + +function getOrderedDates(dates) { + return dates.sort(function (a, b) { + return new Date(b) - new Date(a); + }); +} + export function ExpenseList({ expenses, currency, @@ -44,68 +59,81 @@ export function ExpenseList({ const getParticipant = (id: string) => participants.find((p) => p.id === id) const router = useRouter() + const groupedExpensesByDate = getGroupedExpensesByDate(expenses) + return expenses.length > 0 ? ( - expenses.map((expense) => ( -
{ - router.push(`/groups/${groupId}/expenses/${expense.id}/edit`) - }} - > - -
-
- {expense.title} -
-
- Paid by {getParticipant(expense.paidById)?.name}{' '} - for{' '} - {expense.paidFor.map((paidFor, index) => ( - - {index !== 0 && <>, } - - { - participants.find((p) => p.id === paidFor.participantId) - ?.name - } - - - ))} -
-
-
+ getOrderedDates(Object.keys(groupedExpensesByDate)).map(dateString => { + const dateExpenses = groupedExpensesByDate[dateString]; + return ( +
- {currency} {(expense.amount / 100).toFixed(2)} -
-
- {formatDate(expense.expenseDate)} + {dateString}
-
- -
- )) - ) : ( + {dateExpenses.map((expense) => ( +
{ + router.push(`/groups/${groupId}/expenses/${expense.id}/edit`) + }} + > + +
+
+ {expense.title} +
+
+ Paid by {getParticipant(expense.paidById)?.name}{' '} + for{' '} + {expense.paidFor.map((paidFor, index) => ( + + {index !== 0 && <>, } + + { + participants.find((p) => p.id === paidFor.participantId) + ?.name + } + + + ))} +
+
+
+
+ {currency} {(expense.amount / 100).toFixed(2)} +
+
+ {formatDate(expense.expenseDate)} +
+
+ +
+ ))} + + ) + })) : (

Your group doesn’t contain any expense yet.{' '} - - )) - ) : ( + {dateExpenses.map((expense) => ( +

{ + router.push(`/groups/${groupId}/expenses/${expense.id}/edit`) + }} + > + +
+
+ {expense.title} +
+
+ Paid by {getParticipant(expense.paidById)?.name}{' '} + for{' '} + {expense.paidFor.map((paidFor, index) => ( + + {index !== 0 && <>, } + + { + participants.find((p) => p.id === paidFor.participantId) + ?.name + } + + + ))} +
+
+
+
+ {currency} {(expense.amount / 100).toFixed(2)} +
+
+ {formatDate(expense.expenseDate)} +
+
+ +
+ ))} + + ) + })) : (

Your group doesn’t contain any expense yet.{' '} - - - ))} - - ) - })) : ( + ))} + + ) + }, + ) + ) : (

Your group doesn’t contain any expense yet.{' '} + {currency} {(expense.amount / 100).toFixed(2)} + +

+ {formatDate(expense.expenseDate)} +
- ))} - - ) - }, - ) + + + ))} + + ) + }) ) : (

Your group doesn’t contain any expense yet.{' '} diff --git a/src/components/expense-form.tsx b/src/components/expense-form.tsx index cfc05db7..cfc749a4 100644 --- a/src/components/expense-form.tsx +++ b/src/components/expense-form.tsx @@ -88,32 +88,32 @@ export function ExpenseForm({ isReimbursement: expense.isReimbursement, } : searchParams.get('reimbursement') - ? { - title: 'Reimbursement', - expenseDate: new Date(), - amount: String( - (Number(searchParams.get('amount')) || 0) / 100, - ) as unknown as number, // hack - category: 1, // category with Id 1 is Payment - paidBy: searchParams.get('from') ?? undefined, - paidFor: [ - searchParams.get('to') - ? { participant: searchParams.get('to')!, shares: 1 } - : undefined, - ], - isReimbursement: true, - splitMode: 'EVENLY', - } - : { - title: '', - expenseDate: new Date(), - amount: 0, - category: 0, // category with Id 0 is General - paidFor: [], - paidBy: getSelectedPayer(), - isReimbursement: false, - splitMode: 'EVENLY', - }, + ? { + title: 'Reimbursement', + expenseDate: new Date(), + amount: String( + (Number(searchParams.get('amount')) || 0) / 100, + ) as unknown as number, // hack + category: 1, // category with Id 1 is Payment + paidBy: searchParams.get('from') ?? undefined, + paidFor: [ + searchParams.get('to') + ? { participant: searchParams.get('to')!, shares: 1 } + : undefined, + ], + isReimbursement: true, + splitMode: 'EVENLY', + } + : { + title: '', + expenseDate: new Date(), + amount: 0, + category: 0, // category with Id 0 is General + paidFor: [], + paidBy: getSelectedPayer(), + isReimbursement: false, + splitMode: 'EVENLY', + }, }) const categoriesByGroup = categories.reduce>( diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts index c3ed500e..be904815 100644 --- a/src/lib/schemas.ts +++ b/src/lib/schemas.ts @@ -100,10 +100,9 @@ export const expenseFormSchema = z } }), splitMode: z - .enum< - SplitMode, - [SplitMode, ...SplitMode[]] - >(Object.values(SplitMode) as any) + .enum( + Object.values(SplitMode) as any, + ) .default('EVENLY'), isReimbursement: z.boolean(), }) From 4431f727cf597489b9b1611c77f223ebd073153e Mon Sep 17 00:00:00 2001 From: acuteengle Date: Wed, 17 Jan 2024 10:14:41 +0700 Subject: [PATCH 6/7] update logic to use dayjs --- package-lock.json | 6 ++++++ package.json | 1 + .../[groupId]/expenses/expense-list.tsx | 21 +++++++++++-------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf150ffb..fa7d4c99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@tailwindcss/typography": "^0.5.10", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", + "dayjs": "^1.11.10", "lucide-react": "^0.290.0", "nanoid": "^5.0.4", "next": "^14.0.4", @@ -2583,6 +2584,11 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 26c8c791..29bb41d6 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@tailwindcss/typography": "^0.5.10", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", + "dayjs": "^1.11.10", "lucide-react": "^0.290.0", "nanoid": "^5.0.4", "next": "^14.0.4", diff --git a/src/app/groups/[groupId]/expenses/expense-list.tsx b/src/app/groups/[groupId]/expenses/expense-list.tsx index 45427940..98a7ab5e 100644 --- a/src/app/groups/[groupId]/expenses/expense-list.tsx +++ b/src/app/groups/[groupId]/expenses/expense-list.tsx @@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button' import { getGroupExpenses } from '@/lib/api' import { cn } from '@/lib/utils' import { Expense, Participant } from '@prisma/client' +import dayjs, { type Dayjs } from 'dayjs' import { ChevronRight } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/navigation' @@ -20,8 +21,9 @@ const EXPENSE_GROUPS = { THIS_WEEK: 'This week', EARLIER_THIS_MONTH: 'Earlier this month', LAST_MONTH: 'Last month', + EARLIER_THIS_YEAR: 'Earlier this year', LAST_YEAR: 'Last year', - OLDER: 'Older,', + OLDER: 'Older', } function getDifferenceInDays(earlierDate: Date, laterDate: Date) { @@ -29,14 +31,16 @@ function getDifferenceInDays(earlierDate: Date, laterDate: Date) { return Math.round(differenceInTime / (1000 * 3600 * 24)) // convert milliseconds to days } -function getExpenseGroup(days: number) { - if (days <= 7) { +function getExpenseGroup(date: Dayjs, today: Dayjs) { + if (today.isSame(date, 'week')) { return EXPENSE_GROUPS.THIS_WEEK - } else if (days <= 31) { + } else if (today.isSame(date, 'month')) { return EXPENSE_GROUPS.EARLIER_THIS_MONTH - } else if (days <= 62) { + } else if (today.subtract(1, 'month').isSame(date, 'month')) { return EXPENSE_GROUPS.LAST_MONTH - } else if (days <= 365) { + } else if (today.isSame(date, 'year')) { + return EXPENSE_GROUPS.EARLIER_THIS_YEAR + } else if (today.subtract(1, 'year').isSame(date, 'year')) { return EXPENSE_GROUPS.LAST_YEAR } else { return EXPENSE_GROUPS.OLDER @@ -46,11 +50,10 @@ function getExpenseGroup(days: number) { function getGroupedExpensesByDate( expenses: Awaited>, ) { - const today = new Date() + const today = dayjs() return expenses.reduce( (result: { [key: string]: Expense[] }, expense: Expense) => { - const differenceInDays = getDifferenceInDays(today, expense.expenseDate) - const expenseGroup = getExpenseGroup(differenceInDays) + const expenseGroup = getExpenseGroup(dayjs(expense.expenseDate), today) result[expenseGroup] = result[expenseGroup] ?? [] result[expenseGroup].push(expense) return result From 1b26828706122c5f33ab66f9766508e962656383 Mon Sep 17 00:00:00 2001 From: acuteengle Date: Wed, 17 Jan 2024 10:20:21 +0700 Subject: [PATCH 7/7] clean up --- src/app/groups/[groupId]/expenses/expense-list.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/app/groups/[groupId]/expenses/expense-list.tsx b/src/app/groups/[groupId]/expenses/expense-list.tsx index 98a7ab5e..1a361791 100644 --- a/src/app/groups/[groupId]/expenses/expense-list.tsx +++ b/src/app/groups/[groupId]/expenses/expense-list.tsx @@ -26,11 +26,6 @@ const EXPENSE_GROUPS = { OLDER: 'Older', } -function getDifferenceInDays(earlierDate: Date, laterDate: Date) { - const differenceInTime = laterDate.getTime() - earlierDate.getTime() - return Math.round(differenceInTime / (1000 * 3600 * 24)) // convert milliseconds to days -} - function getExpenseGroup(date: Dayjs, today: Dayjs) { if (today.isSame(date, 'week')) { return EXPENSE_GROUPS.THIS_WEEK