feat: add search and filter functionality to MyApplicationsPage

This commit is contained in:
estifanos
2026-08-12 07:15:21 +00:00
parent 444c3111ab
commit 829dfed999

View File

@@ -1,3 +1,4 @@
import { useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Badge,
@@ -5,13 +6,15 @@ import {
Card,
Container,
Group,
SimpleGrid,
Paper,
Select,
Stack,
Text,
TextInput,
Title,
} from '@mantine/core';
import { IconDownload } from '@tabler/icons-react';
import { AdvancedTable, useServerTable, type AdvancedColumn } from '@ema-platform/ui';
import { IconDownload, IconSearch, IconX } from '@tabler/icons-react';
import { AdvancedTable, AmharicDatePicker, useServerTable, type AdvancedColumn } from '@ema-platform/ui';
import { LicenseCatalogue } from '../components/LicenseCatalogue';
import { useApplicationPayment } from '../../payments/hooks/useApplicationPayment';
import { notifications } from '@mantine/notifications';
@@ -26,9 +29,12 @@ import {
useGetMyLicensesQuery,
useGetPaymentCapabilitiesQuery,
type LicenseApplication,
type LicenseStatus,
type IssuedLicense,
} from '@ema-platform/api';
const ALL_APPLICATION_STATUSES = Object.keys(STATUS_LABELS) as LicenseStatus[];
/**
* The applicant's landing page: which licences they can apply for, and the
* state of anything already filed.
@@ -101,9 +107,32 @@ export function MyApplicationsPage() {
}
}
const items = data?.items ?? [];
const allItems = data?.items ?? [];
const licenceItems = licences?.items ?? [];
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState<LicenseStatus | null>(null);
const [dateFrom, setDateFrom] = useState('');
const [dateTo, setDateTo] = useState('');
const hasFilters = Boolean(search || statusFilter || dateFrom || dateTo);
const items = useMemo(() => {
const q = search.trim().toLowerCase();
return allItems.filter((app) => {
if (q) {
const haystack = `${app.applicationNumber} ${app.companyName ?? ''}`.toLowerCase();
if (!haystack.includes(q)) return false;
}
if (statusFilter && app.status !== statusFilter) return false;
// Drafts have no submittedAt, so date filtering falls back to createdAt
// rather than silently excluding every draft from a date-ranged search.
const at = app.submittedAt ?? app.createdAt;
if (dateFrom && (!at || at < dateFrom)) return false;
if (dateTo && (!at || at > `${dateTo}T23:59:59.999Z`)) return false;
return true;
});
}, [allItems, search, statusFilter, dateFrom, dateTo]);
const {
setPageIndex: setLicencePage,
pageSize: licencePageSize,
@@ -310,12 +339,68 @@ export function MyApplicationsPage() {
</Card>
)}
{allItems.length > 0 && (
<Paper withBorder p="sm" mb="sm">
<Group gap="sm" align="flex-end" wrap="wrap">
<TextInput
label="Search"
placeholder="Number or company"
leftSection={<IconSearch size={14} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
w={220}
/>
<Select
label="Status"
placeholder="Any"
data={ALL_APPLICATION_STATUSES.map((s) => ({ value: s, label: STATUS_LABELS[s] }))}
value={statusFilter}
onChange={(v) => setStatusFilter(v as LicenseStatus | null)}
clearable
w={200}
/>
<AmharicDatePicker
label="From"
value={dateFrom}
onChange={setDateFrom}
dateFormat="date"
w={160}
/>
<AmharicDatePicker
label="To"
value={dateTo}
onChange={setDateTo}
dateFormat="date"
w={160}
/>
{hasFilters && (
<Button
variant="subtle"
leftSection={<IconX size={14} />}
onClick={() => {
setSearch('');
setStatusFilter(null);
setDateFrom('');
setDateTo('');
}}
>
Clear
</Button>
)}
</Group>
</Paper>
)}
{items.length === 0 ? (
<Card withBorder padding="xl">
<Stack align="center" gap="xs">
<Text c="dimmed">You have not filed any applications yet.</Text>
<Text c="dimmed">
{hasFilters
? 'No applications match these filters.'
: 'You have not filed any applications yet.'}
</Text>
<Text size="sm" c="dimmed">
Pick a licence below to get started.
{hasFilters ? 'Try widening or clearing the filters.' : 'Pick a licence below to get started.'}
</Text>
</Stack>
</Card>