Fix search functionality (#62)

* Improve README instructions for local setup

* Fix search functionality #61
- use 'includes' for expense filtering

* Ensure expense groups with no matching expenses are hidden after filtering

* Improve README instructions for local setup
This commit is contained in:
Vid Čufar
2024-01-26 16:27:34 +01:00
committed by GitHub
parent 58ee685e22
commit 2228415323
2 changed files with 74 additions and 80 deletions

View File

@@ -43,10 +43,10 @@ If you want to contribute financially and help us keep the application free and
## Run locally ## Run locally
1. Clone the repository (or fork it if you intend to contribute) 1. Clone the repository (or fork it if you intend to contribute)
2. `npm install` 2. Start a PostgreSQL server. You can run `./scripts/start-local-db.sh` if you dont have a server already.
3. Start a PostgreSQL server. You can run `./scripts/start-local-db.sh` if you dont have a server already. 3. Copy the file `.env.example` as `.env`
4. Copy the file `.env.example` as `.env` 4. Run `npm install` to install dependencies. This will also apply database migrations and update Prisma Client.
5. `npm run dev` 5. Run `npm run dev` to start the development server
## Run in a container ## Run in a container

View File

@@ -92,8 +92,15 @@ export function ExpenseList({
<> <>
<SearchBar onChange={(e) => setSearchText(e.target.value)} /> <SearchBar onChange={(e) => setSearchText(e.target.value)} />
{Object.values(EXPENSE_GROUPS).map((expenseGroup: string) => { {Object.values(EXPENSE_GROUPS).map((expenseGroup: string) => {
const groupExpenses = groupedExpensesByDate[expenseGroup] let groupExpenses = groupedExpensesByDate[expenseGroup]
if (!groupExpenses) return null if (!groupExpenses) return null
groupExpenses = groupExpenses.filter(({ title }) =>
title.toLowerCase().includes(searchText.toLowerCase()),
)
if (groupExpenses.length === 0) return null
return ( return (
<div key={expenseGroup}> <div key={expenseGroup}>
<div <div
@@ -103,83 +110,70 @@ export function ExpenseList({
> >
{expenseGroup} {expenseGroup}
</div> </div>
{groupExpenses {groupExpenses.map((expense: any) => (
.filter( <div
(exp) => key={expense.id}
exp.title.toLowerCase().match(searchText.toLowerCase()) !== className={cn(
null, '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',
.map((expense: any) => ( )}
<div onClick={() => {
key={expense.id} router.push(`/groups/${groupId}/expenses/${expense.id}/edit`)
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', <CategoryIcon
)} category={expense.category}
onClick={() => { className="w-4 h-4 mr-2 mt-0.5 text-muted-foreground"
router.push( />
`/groups/${groupId}/expenses/${expense.id}/edit`, <div className="flex-1">
) <div
}} className={cn('mb-1', expense.isReimbursement && 'italic')}
>
<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>{getParticipant(expense.paidById)?.name}</strong>{' '}
for{' '}
{expense.paidFor.map((paidFor: any, index: number) => (
<Fragment key={index}>
{index !== 0 && <>, </>}
<strong>
{
participants.find(
(p) => p.id === paidFor.participantId,
)?.name
}
</strong>
</Fragment>
))}
</div>
</div>
<div className="flex flex-col justify-between items-end">
<div
className={cn(
'tabular-nums whitespace-nowrap',
expense.isReimbursement ? 'italic' : 'font-bold',
)}
>
{currency} {(expense.amount / 100).toFixed(2)}
</div>
<div className="text-xs text-muted-foreground">
{formatDate(expense.expenseDate)}
</div>
</div>
<Button
size="icon"
variant="link"
className="self-center hidden sm:flex"
asChild
> >
<Link {expense.title}
href={`/groups/${groupId}/expenses/${expense.id}/edit`} </div>
> <div className="text-xs text-muted-foreground">
<ChevronRight className="w-4 h-4" /> Paid by{' '}
</Link> <strong>{getParticipant(expense.paidById)?.name}</strong>{' '}
</Button> for{' '}
{expense.paidFor.map((paidFor: any, index: number) => (
<Fragment key={index}>
{index !== 0 && <>, </>}
<strong>
{
participants.find(
(p) => p.id === paidFor.participantId,
)?.name
}
</strong>
</Fragment>
))}
</div>
</div> </div>
))} <div className="flex flex-col justify-between items-end">
<div
className={cn(
'tabular-nums whitespace-nowrap',
expense.isReimbursement ? 'italic' : 'font-bold',
)}
>
{currency} {(expense.amount / 100).toFixed(2)}
</div>
<div className="text-xs text-muted-foreground">
{formatDate(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>
))}
</div> </div>
) )
})} })}