-
-
Notifications
You must be signed in to change notification settings - Fork 193
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement "infinite scroll" for expenses (#95)
* Extract ExpenseCard vom ExpenseList * Implement simple pagination of expenses (see #30) - display only this year's entries by default - a "Show more" button reveals all expenses * Turn getPrisma() into constant "prisma" - getPrisma() is not async and doesn't need to be awaited - turn getPrisma() into exported constant "prisma" * Select fields to be returned by getGroupExpenses() - make JSON more concise and less redundant - some properties were removed (i.e.instead of "expense.paidById" use "expense.paidBy.id") * Remove "participants" from ExpenseCard - no need to search for participant by id to get it's name - name property is already present in expense * Add option to fetch a slice of group expenses - specify offset and length to get expenses for [offset, offset+length[ - add function to get total number of group expenses * Add api route for client to fetch group expenses * Remove "Show more" button from expense list * Implement infinite scroll - in server component Page - only load first 200 expenses max - pass preloaded expenses and total count - in client component ExpenseList, if there are more expenses to show - test if there are more expenses - append preloading "skeletons" to end of list - fetch more expenses when last item in list comes into view - after each fetch increase fetch-length by factor 1.5 - rationale: db fetch usually is not the issue here, the longer the list gets, the longer react needs to redraw * Use server action instead of api endpoint * Fixes --------- Co-authored-by: Sebastien Castiel <sebastien@castiel.me>
- Loading branch information
Showing
12 changed files
with
266 additions
and
138 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
'use client' | ||
import { ActiveUserBalance } from '@/app/groups/[groupId]/expenses/active-user-balance' | ||
import { CategoryIcon } from '@/app/groups/[groupId]/expenses/category-icon' | ||
import { Button } from '@/components/ui/button' | ||
import { getGroupExpenses } from '@/lib/api' | ||
import { cn, formatCurrency, formatExpenseDate } from '@/lib/utils' | ||
import { ChevronRight } from 'lucide-react' | ||
import Link from 'next/link' | ||
import { useRouter } from 'next/navigation' | ||
import { Fragment } from 'react' | ||
|
||
type Props = { | ||
expense: Awaited<ReturnType<typeof getGroupExpenses>>[number] | ||
currency: string | ||
groupId: string | ||
} | ||
|
||
export function ExpenseCard({ expense, currency, groupId }: Props) { | ||
const router = useRouter() | ||
|
||
return ( | ||
<div | ||
key={expense.id} | ||
className={cn( | ||
'flex justify-between sm:mx-6 px-4 sm:rounded-lg sm:pr-2 sm:pl-4 py-4 text-sm cursor-pointer hover:bg-accent gap-1 items-stretch', | ||
expense.isReimbursement && 'italic', | ||
)} | ||
onClick={() => { | ||
router.push(`/groups/${groupId}/expenses/${expense.id}/edit`) | ||
}} | ||
> | ||
<CategoryIcon | ||
category={expense.category} | ||
className="w-4 h-4 mr-2 mt-0.5 text-muted-foreground" | ||
/> | ||
<div className="flex-1"> | ||
<div className={cn('mb-1', expense.isReimbursement && 'italic')}> | ||
{expense.title} | ||
</div> | ||
<div className="text-xs text-muted-foreground"> | ||
Paid by <strong>{expense.paidBy.name}</strong> for{' '} | ||
{expense.paidFor.map((paidFor, index) => ( | ||
<Fragment key={index}> | ||
{index !== 0 && <>, </>} | ||
<strong>{paidFor.participant.name}</strong> | ||
</Fragment> | ||
))} | ||
</div> | ||
<div className="text-xs text-muted-foreground"> | ||
<ActiveUserBalance {...{ groupId, currency, expense }} /> | ||
</div> | ||
</div> | ||
<div className="flex flex-col justify-between items-end"> | ||
<div | ||
className={cn( | ||
'tabular-nums whitespace-nowrap', | ||
expense.isReimbursement ? 'italic' : 'font-bold', | ||
)} | ||
> | ||
{formatCurrency(currency, expense.amount)} | ||
</div> | ||
<div className="text-xs text-muted-foreground"> | ||
{formatExpenseDate(expense.expenseDate)} | ||
</div> | ||
</div> | ||
<Button | ||
size="icon" | ||
variant="link" | ||
className="self-center hidden sm:flex" | ||
asChild | ||
> | ||
<Link href={`/groups/${groupId}/expenses/${expense.id}/edit`}> | ||
<ChevronRight className="w-4 h-4" /> | ||
</Link> | ||
</Button> | ||
</div> | ||
) | ||
} |
16 changes: 16 additions & 0 deletions
16
src/app/groups/[groupId]/expenses/expense-list-fetch-action.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
'use server' | ||
|
||
import { getGroupExpenses } from '@/lib/api' | ||
|
||
export async function getGroupExpensesAction( | ||
groupId: string, | ||
options?: { offset: number; length: number }, | ||
) { | ||
'use server' | ||
|
||
try { | ||
return getGroupExpenses(groupId, options) | ||
} catch { | ||
return null | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.