128 lines
4.1 KiB
TypeScript
128 lines
4.1 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { useEffect } from 'react';
|
||
|
|
import { useParams } from 'next/navigation';
|
||
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||
|
|
import { toast } from 'sonner';
|
||
|
|
|
||
|
|
import { useMobileChrome } from '@/components/layout/mobile/mobile-layout-provider';
|
||
|
|
import { DataTable } from '@/components/shared/data-table';
|
||
|
|
import { FilterBar } from '@/components/shared/filter-bar';
|
||
|
|
import { ColumnPicker } from '@/components/shared/column-picker';
|
||
|
|
import { PageHeader } from '@/components/shared/page-header';
|
||
|
|
import { EmptyState } from '@/components/shared/empty-state';
|
||
|
|
import { TableSkeleton } from '@/components/shared/loading-skeleton';
|
||
|
|
import { usePaginatedQuery } from '@/hooks/use-paginated-query';
|
||
|
|
import { useTablePreferences } from '@/hooks/use-table-preferences';
|
||
|
|
import { apiFetch } from '@/lib/api/client';
|
||
|
|
import { toastError } from '@/lib/api/toast-error';
|
||
|
|
import { inquiryFilterDefinitions } from '@/components/inquiries/inquiry-filters';
|
||
|
|
import {
|
||
|
|
getInquiryColumns,
|
||
|
|
INQUIRY_COLUMN_OPTIONS,
|
||
|
|
INQUIRY_DEFAULT_HIDDEN,
|
||
|
|
type InquiryRow,
|
||
|
|
type InquiryTriageState,
|
||
|
|
} from '@/components/inquiries/inquiry-columns';
|
||
|
|
import { InquiryCard } from '@/components/inquiries/inquiry-card';
|
||
|
|
|
||
|
|
export function InquiryList() {
|
||
|
|
const params = useParams<{ portSlug: string }>();
|
||
|
|
const portSlug = params?.portSlug ?? '';
|
||
|
|
const queryClient = useQueryClient();
|
||
|
|
|
||
|
|
const { setChrome } = useMobileChrome();
|
||
|
|
useEffect(() => {
|
||
|
|
setChrome({ title: 'Inquiries', showBackButton: false });
|
||
|
|
return () => setChrome({ title: null, showBackButton: false });
|
||
|
|
}, [setChrome]);
|
||
|
|
|
||
|
|
const {
|
||
|
|
data,
|
||
|
|
pagination,
|
||
|
|
isLoading,
|
||
|
|
isFetching,
|
||
|
|
sort,
|
||
|
|
setSort,
|
||
|
|
setPage,
|
||
|
|
setPageSize,
|
||
|
|
filters,
|
||
|
|
setFilter,
|
||
|
|
clearFilters,
|
||
|
|
} = usePaginatedQuery<InquiryRow>({
|
||
|
|
queryKey: ['inquiries'],
|
||
|
|
endpoint: '/api/v1/inquiries',
|
||
|
|
initialSort: { field: 'receivedAt', direction: 'desc' },
|
||
|
|
filterDefinitions: inquiryFilterDefinitions,
|
||
|
|
});
|
||
|
|
|
||
|
|
const triageMutation = useMutation({
|
||
|
|
mutationFn: (args: { id: string; state: InquiryTriageState }) =>
|
||
|
|
apiFetch(`/api/v1/inquiries/${args.id}/triage`, {
|
||
|
|
method: 'PATCH',
|
||
|
|
body: { state: args.state },
|
||
|
|
}),
|
||
|
|
onSuccess: (_d, vars) => {
|
||
|
|
queryClient.invalidateQueries({ queryKey: ['inquiries'] });
|
||
|
|
toast.success(`Marked ${vars.state}.`);
|
||
|
|
},
|
||
|
|
onError: (err: unknown) => toastError(err, 'Update failed'),
|
||
|
|
});
|
||
|
|
|
||
|
|
const columns = getInquiryColumns({
|
||
|
|
portSlug,
|
||
|
|
onTriage: (row, state) => triageMutation.mutate({ id: row.id, state }),
|
||
|
|
});
|
||
|
|
|
||
|
|
const { hidden, setHidden } = useTablePreferences('inquiries', INQUIRY_DEFAULT_HIDDEN);
|
||
|
|
const columnVisibility = Object.fromEntries(hidden.map((id) => [id, false]));
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-4">
|
||
|
|
<PageHeader
|
||
|
|
title="Inquiries"
|
||
|
|
description="Submissions captured from the public marketing site (berth, residence, and contact forms)."
|
||
|
|
variant="gradient"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<div className="flex flex-wrap items-center gap-2">
|
||
|
|
<FilterBar
|
||
|
|
filters={inquiryFilterDefinitions}
|
||
|
|
values={filters}
|
||
|
|
onChange={setFilter}
|
||
|
|
onClear={clearFilters}
|
||
|
|
/>
|
||
|
|
<div className="ml-auto flex flex-wrap items-center gap-2">
|
||
|
|
<ColumnPicker columns={INQUIRY_COLUMN_OPTIONS} hidden={hidden} onChange={setHidden} />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{isLoading ? (
|
||
|
|
<TableSkeleton />
|
||
|
|
) : (
|
||
|
|
<DataTable
|
||
|
|
columns={columns}
|
||
|
|
columnVisibility={columnVisibility}
|
||
|
|
data={data}
|
||
|
|
pagination={pagination}
|
||
|
|
onPaginationChange={(p, ps) => {
|
||
|
|
setPage(p);
|
||
|
|
setPageSize(ps);
|
||
|
|
}}
|
||
|
|
sort={sort}
|
||
|
|
onSortChange={setSort}
|
||
|
|
isLoading={isFetching && !isLoading}
|
||
|
|
getRowId={(row) => row.id}
|
||
|
|
cardRender={(row) => <InquiryCard inquiry={row.original} portSlug={portSlug} />}
|
||
|
|
emptyState={
|
||
|
|
<EmptyState
|
||
|
|
title="No inquiries found"
|
||
|
|
description="Submissions from the marketing site will appear here."
|
||
|
|
/>
|
||
|
|
}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|