Add search functionality to accounts, receipts, and transactions pages with dynamic filtering. Update UI elements for better responsiveness and user experience.
This commit is contained in:
@@ -1,9 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
import { Card, CardContent } from "$lib/components/ui/card";
|
import { Card, CardContent } from "$lib/components/ui/card";
|
||||||
|
import { Input } from "$lib/components/ui/input";
|
||||||
|
|
||||||
let { data }: { data: { accounts: { id: string }[] } } = $props();
|
let { data }: { data: { accounts: { id: string }[] } } = $props();
|
||||||
|
let searchQuery = $state("");
|
||||||
|
|
||||||
const accounts = $derived(data?.accounts ?? []);
|
const accounts = $derived(data?.accounts ?? []);
|
||||||
|
const filteredAccounts = $derived(
|
||||||
|
(() => {
|
||||||
|
const q = searchQuery.trim().toLowerCase();
|
||||||
|
if (!q) return accounts;
|
||||||
|
return accounts.filter((a) => a.id.toLowerCase().includes(q));
|
||||||
|
})()
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -12,20 +22,37 @@
|
|||||||
|
|
||||||
<main class="container mx-auto flex flex-col gap-6 px-4 py-6 sm:px-6 sm:py-8">
|
<main class="container mx-auto flex flex-col gap-6 px-4 py-6 sm:px-6 sm:py-8">
|
||||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
<a href="/" class="text-muted-foreground hover:text-foreground shrink-0 text-sm" title="Back to main">Back</a>
|
<a href="/" class="text-muted-foreground hover:text-foreground shrink-0 text-sm" title="Back to main">Back</a>
|
||||||
<h1 class="text-xl font-semibold sm:text-2xl">Accounts</h1>
|
<h1 class="text-xl font-semibold truncate sm:text-2xl">Accounts</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:gap-2">
|
||||||
|
<div class="w-full sm:w-64 sm:min-w-0">
|
||||||
|
<Input
|
||||||
|
type="search"
|
||||||
|
placeholder="Search accounts…"
|
||||||
|
bind:value={searchQuery}
|
||||||
|
class="h-11 w-full sm:h-9"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted-foreground text-sm shrink-0">
|
||||||
|
{filteredAccounts.length}{filteredAccounts.length !== accounts.length ? ` of ${accounts.length}` : ""} accounts
|
||||||
|
</span>
|
||||||
|
<a href="/accounts/new" class="block shrink-0">
|
||||||
|
<Button class="w-full sm:w-auto min-h-11">New account</Button>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a href="/accounts/new" class="block shrink-0">
|
|
||||||
<Button class="w-full sm:w-auto">New account</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if accounts.length === 0}
|
{#if filteredAccounts.length === 0}
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent class="flex flex-col items-center justify-center gap-4 py-12">
|
<CardContent class="flex flex-col items-center justify-center gap-4 py-12">
|
||||||
<p class="text-muted-foreground text-center text-sm">No accounts yet.</p>
|
<p class="text-muted-foreground text-center text-sm">
|
||||||
<p class="text-muted-foreground text-center text-xs">Create an account to get started.</p>
|
{accounts.length === 0 ? "No accounts yet." : "No accounts match your search."}
|
||||||
|
</p>
|
||||||
|
<p class="text-muted-foreground text-center text-xs">
|
||||||
|
{accounts.length === 0 ? "Create an account to get started." : "Try a different search."}
|
||||||
|
</p>
|
||||||
<a href="/accounts/new">
|
<a href="/accounts/new">
|
||||||
<Button variant="outline">New account</Button>
|
<Button variant="outline">New account</Button>
|
||||||
</a>
|
</a>
|
||||||
@@ -36,7 +63,7 @@
|
|||||||
</Card>
|
</Card>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
{#each accounts as account (account.id)}
|
{#each filteredAccounts as account (account.id)}
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent class="p-4">
|
<CardContent class="p-4">
|
||||||
<a href="/accounts/{account.id}" class="font-medium hover:underline">Account {account.id}</a>
|
<a href="/accounts/{account.id}" class="font-medium hover:underline">Account {account.id}</a>
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
import { Card, CardContent } from "$lib/components/ui/card";
|
import { Card, CardContent } from "$lib/components/ui/card";
|
||||||
|
import { Input } from "$lib/components/ui/input";
|
||||||
|
|
||||||
let { data }: { data: { receipts: { id: string }[] } } = $props();
|
let { data }: { data: { receipts: { id: string }[] } } = $props();
|
||||||
|
let searchQuery = $state("");
|
||||||
|
|
||||||
const receipts = $derived(data?.receipts ?? []);
|
const receipts = $derived(data?.receipts ?? []);
|
||||||
|
const filteredReceipts = $derived(
|
||||||
|
(() => {
|
||||||
|
const q = searchQuery.trim().toLowerCase();
|
||||||
|
if (!q) return receipts;
|
||||||
|
return receipts.filter((r) => r.id.toLowerCase().includes(q));
|
||||||
|
})()
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -12,20 +22,37 @@
|
|||||||
|
|
||||||
<main class="container mx-auto flex flex-col gap-6 px-4 py-6 sm:px-6 sm:py-8">
|
<main class="container mx-auto flex flex-col gap-6 px-4 py-6 sm:px-6 sm:py-8">
|
||||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
<a href="/" class="text-muted-foreground hover:text-foreground shrink-0 text-sm" title="Back to main">Back</a>
|
<a href="/" class="text-muted-foreground hover:text-foreground shrink-0 text-sm" title="Back to main">Back</a>
|
||||||
<h1 class="text-xl font-semibold sm:text-2xl">Receipts</h1>
|
<h1 class="text-xl font-semibold truncate sm:text-2xl">Receipts</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:gap-2">
|
||||||
|
<div class="w-full sm:w-64 sm:min-w-0">
|
||||||
|
<Input
|
||||||
|
type="search"
|
||||||
|
placeholder="Search receipts…"
|
||||||
|
bind:value={searchQuery}
|
||||||
|
class="h-11 w-full sm:h-9"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted-foreground text-sm shrink-0">
|
||||||
|
{filteredReceipts.length}{filteredReceipts.length !== receipts.length ? ` of ${receipts.length}` : ""} receipts
|
||||||
|
</span>
|
||||||
|
<a href="/receipts/new" class="block shrink-0">
|
||||||
|
<Button class="w-full sm:w-auto min-h-11">New receipt</Button>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a href="/receipts/new" class="block shrink-0">
|
|
||||||
<Button class="w-full sm:w-auto">New receipt</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if receipts.length === 0}
|
{#if filteredReceipts.length === 0}
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent class="flex flex-col items-center justify-center gap-4 py-12">
|
<CardContent class="flex flex-col items-center justify-center gap-4 py-12">
|
||||||
<p class="text-muted-foreground text-center text-sm">No receipts yet.</p>
|
<p class="text-muted-foreground text-center text-sm">
|
||||||
<p class="text-muted-foreground text-center text-xs">Create a receipt to get started.</p>
|
{receipts.length === 0 ? "No receipts yet." : "No receipts match your search."}
|
||||||
|
</p>
|
||||||
|
<p class="text-muted-foreground text-center text-xs">
|
||||||
|
{receipts.length === 0 ? "Create a receipt to get started." : "Try a different search."}
|
||||||
|
</p>
|
||||||
<a href="/receipts/new">
|
<a href="/receipts/new">
|
||||||
<Button variant="outline">New receipt</Button>
|
<Button variant="outline">New receipt</Button>
|
||||||
</a>
|
</a>
|
||||||
@@ -36,7 +63,7 @@
|
|||||||
</Card>
|
</Card>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
{#each receipts as receipt (receipt.id)}
|
{#each filteredReceipts as receipt (receipt.id)}
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent class="p-4">
|
<CardContent class="p-4">
|
||||||
<a href="/receipts/{receipt.id}" class="font-medium hover:underline">Receipt {receipt.id}</a>
|
<a href="/receipts/{receipt.id}" class="font-medium hover:underline">Receipt {receipt.id}</a>
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
import { Card, CardContent } from "$lib/components/ui/card";
|
import { Card, CardContent } from "$lib/components/ui/card";
|
||||||
|
import { Input } from "$lib/components/ui/input";
|
||||||
|
|
||||||
let { data }: { data: { transactions: { id: string }[] } } = $props();
|
let { data }: { data: { transactions: { id: string }[] } } = $props();
|
||||||
|
let searchQuery = $state("");
|
||||||
|
|
||||||
const transactions = $derived(data?.transactions ?? []);
|
const transactions = $derived(data?.transactions ?? []);
|
||||||
|
const filteredTransactions = $derived(
|
||||||
|
(() => {
|
||||||
|
const q = searchQuery.trim().toLowerCase();
|
||||||
|
if (!q) return transactions;
|
||||||
|
return transactions.filter((t) => t.id.toLowerCase().includes(q));
|
||||||
|
})()
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -12,20 +22,37 @@
|
|||||||
|
|
||||||
<main class="container mx-auto flex flex-col gap-6 px-4 py-6 sm:px-6 sm:py-8">
|
<main class="container mx-auto flex flex-col gap-6 px-4 py-6 sm:px-6 sm:py-8">
|
||||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
<a href="/" class="text-muted-foreground hover:text-foreground shrink-0 text-sm" title="Back to main">Back</a>
|
<a href="/" class="text-muted-foreground hover:text-foreground shrink-0 text-sm" title="Back to main">Back</a>
|
||||||
<h1 class="text-xl font-semibold sm:text-2xl">Transactions</h1>
|
<h1 class="text-xl font-semibold truncate sm:text-2xl">Transactions</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:gap-2">
|
||||||
|
<div class="w-full sm:w-64 sm:min-w-0">
|
||||||
|
<Input
|
||||||
|
type="search"
|
||||||
|
placeholder="Search transactions…"
|
||||||
|
bind:value={searchQuery}
|
||||||
|
class="h-11 w-full sm:h-9"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted-foreground text-sm shrink-0">
|
||||||
|
{filteredTransactions.length}{filteredTransactions.length !== transactions.length ? ` of ${transactions.length}` : ""} transactions
|
||||||
|
</span>
|
||||||
|
<a href="/transactions/new" class="block shrink-0">
|
||||||
|
<Button class="w-full sm:w-auto min-h-11">New transaction</Button>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a href="/transactions/new" class="block shrink-0">
|
|
||||||
<Button class="w-full sm:w-auto">New transaction</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if transactions.length === 0}
|
{#if filteredTransactions.length === 0}
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent class="flex flex-col items-center justify-center gap-4 py-12">
|
<CardContent class="flex flex-col items-center justify-center gap-4 py-12">
|
||||||
<p class="text-muted-foreground text-center text-sm">No transactions yet.</p>
|
<p class="text-muted-foreground text-center text-sm">
|
||||||
<p class="text-muted-foreground text-center text-xs">Create a transaction to get started.</p>
|
{transactions.length === 0 ? "No transactions yet." : "No transactions match your search."}
|
||||||
|
</p>
|
||||||
|
<p class="text-muted-foreground text-center text-xs">
|
||||||
|
{transactions.length === 0 ? "Create a transaction to get started." : "Try a different search."}
|
||||||
|
</p>
|
||||||
<a href="/transactions/new">
|
<a href="/transactions/new">
|
||||||
<Button variant="outline">New transaction</Button>
|
<Button variant="outline">New transaction</Button>
|
||||||
</a>
|
</a>
|
||||||
@@ -36,7 +63,7 @@
|
|||||||
</Card>
|
</Card>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
{#each transactions as tx (tx.id)}
|
{#each filteredTransactions as tx (tx.id)}
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent class="p-4">
|
<CardContent class="p-4">
|
||||||
<a href="/transactions/{tx.id}" class="font-medium hover:underline">Transaction {tx.id}</a>
|
<a href="/transactions/{tx.id}" class="font-medium hover:underline">Transaction {tx.id}</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user