mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
feat(ui): sync search filters with URL and add dashboard navigation
This commit is contained in:
@@ -30,6 +30,7 @@ export interface EmailEnvelope {
|
|||||||
id: string;
|
id: string;
|
||||||
message_id: string;
|
message_id: string;
|
||||||
account_id: number;
|
account_id: number;
|
||||||
|
mailbox_id: number;
|
||||||
account_email?: string;
|
account_email?: string;
|
||||||
mailbox_name?: string;
|
mailbox_name?: string;
|
||||||
uid: number;
|
uid: number;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, BarChart, Bar } from 'recharts';
|
import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, BarChart, Bar } from 'recharts';
|
||||||
import { Mail, HardDrive, Database, Users, Inbox, Info } from 'lucide-react';
|
import { Mail, HardDrive, Database, Users, Inbox, Info, Search } from 'lucide-react';
|
||||||
import { formatBytes, formatNumber } from '@/lib/utils';
|
import { formatBytes, formatNumber } from '@/lib/utils';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { get_dashboard_stats, INITIAL_DASHBOARD_STATS, TimeBucket } from '@/api/system/api';
|
import { get_dashboard_stats, INITIAL_DASHBOARD_STATS, TimeBucket } from '@/api/system/api';
|
||||||
@@ -30,6 +30,8 @@ import { Main } from '@/components/layout/main';
|
|||||||
import { FixedHeader } from '@/components/layout/fixed-header';
|
import { FixedHeader } from '@/components/layout/fixed-header';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { getToken } from '@/stores/authStore';
|
import { getToken } from '@/stores/authStore';
|
||||||
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
|
import useMinimalAccountList from '@/hooks/use-minimal-account-list';
|
||||||
|
|
||||||
interface DailyActivity {
|
interface DailyActivity {
|
||||||
date: string;
|
date: string;
|
||||||
@@ -111,6 +113,8 @@ export default function MailArchiveDashboard() {
|
|||||||
placeholderData: INITIAL_DASHBOARD_STATS,
|
placeholderData: INITIAL_DASHBOARD_STATS,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
const currentLocale = i18n.resolvedLanguage || i18n.language || navigator.language;
|
const currentLocale = i18n.resolvedLanguage || i18n.language || navigator.language;
|
||||||
@@ -123,6 +127,28 @@ export default function MailArchiveDashboard() {
|
|||||||
const hasTopEmails = stats?.top_largest_emails && stats.top_largest_emails.length > 0;
|
const hasTopEmails = stats?.top_largest_emails && stats.top_largest_emails.length > 0;
|
||||||
const hasTopAccounts = stats?.top_accounts && stats.top_accounts.length > 0;
|
const hasTopAccounts = stats?.top_accounts && stats.top_accounts.length > 0;
|
||||||
|
|
||||||
|
const { minimalList } = useMinimalAccountList();
|
||||||
|
const getAccountIdByEmail = (email: string): number | null => {
|
||||||
|
if (!minimalList) return null;
|
||||||
|
const account = minimalList.find(a => a.email === email);
|
||||||
|
return account ? account.id : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleQuickSearch = (filter: Record<string, any>) => {
|
||||||
|
navigate({
|
||||||
|
to: '/search',
|
||||||
|
search: (prev: any) => ({
|
||||||
|
page: 1,
|
||||||
|
pageSize: prev.pageSize ?? 50,
|
||||||
|
sortBy: prev.sortBy ?? "DATE",
|
||||||
|
sortOrder: prev.sortOrder ?? "desc",
|
||||||
|
q: JSON.stringify(filter),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const attachmentData = totalAttachments > 0
|
const attachmentData = totalAttachments > 0
|
||||||
? [
|
? [
|
||||||
{ name: 'With Attachments', value: attachmentRatio, fill: COLORS[1] },
|
{ name: 'With Attachments', value: attachmentRatio, fill: COLORS[1] },
|
||||||
@@ -380,9 +406,14 @@ export default function MailArchiveDashboard() {
|
|||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{stats!.top_senders.map((s) => (
|
{stats!.top_senders.map((s) => (
|
||||||
<TableRow key={s.key}>
|
<TableRow
|
||||||
<TableCell className="font-medium max-w-[180px] truncate" title={s.key}>
|
key={s.key}
|
||||||
{s.key}
|
className="cursor-pointer hover:bg-accent/50 group"
|
||||||
|
onClick={() => handleQuickSearch({ from: s.key })}
|
||||||
|
>
|
||||||
|
<TableCell className="font-medium max-w-[380px] truncate flex items-center gap-2">
|
||||||
|
<Search size={12} className="opacity-0 group-hover:opacity-100 text-primary transition-opacity" />
|
||||||
|
<span title={s.key}>{s.key}</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">{formatNumber(s.count)}</TableCell>
|
<TableCell className="text-right">{formatNumber(s.count)}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -410,14 +441,18 @@ export default function MailArchiveDashboard() {
|
|||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{stats!.top_largest_emails.map((m, index) => (
|
{stats!.top_largest_emails.map((m, index) => (
|
||||||
<TableRow key={index}>
|
<TableRow
|
||||||
<TableCell
|
key={index}
|
||||||
className="max-w-[180px] truncate font-medium"
|
className="cursor-pointer hover:bg-accent/50 group"
|
||||||
title={m.subject}
|
onClick={() => handleQuickSearch({ text: m.subject })}
|
||||||
>
|
>
|
||||||
{m.subject || t('dashboard.noSubject')}
|
<TableCell className="max-w-[350px] truncate font-medium flex items-center gap-2" title={m.subject}>
|
||||||
|
<Search size={12} className="opacity-0 group-hover:opacity-100 text-primary transition-opacity" />
|
||||||
|
<span>{m.subject || t('dashboard.noSubject')}</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right font-mono text-orange-600">
|
||||||
|
{formatBytes(m.size_bytes)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">{formatBytes(m.size_bytes)}</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
@@ -442,14 +477,28 @@ export default function MailArchiveDashboard() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{stats!.top_accounts.map((acc) => (
|
{stats!.top_accounts.map((acc) => {
|
||||||
<TableRow key={acc.key}>
|
const accountId = getAccountIdByEmail(acc.key);
|
||||||
<TableCell className="font-medium max-w-[160px] truncate" title={acc.key}>
|
return (
|
||||||
{acc.key}
|
<TableRow
|
||||||
</TableCell>
|
key={acc.key}
|
||||||
<TableCell className="text-right">{formatNumber(acc.count)}</TableCell>
|
className="group cursor-pointer hover:bg-accent/50 transition-colors"
|
||||||
</TableRow>
|
onClick={() => {
|
||||||
))}
|
if (accountId) {
|
||||||
|
handleQuickSearch({ account_ids: [accountId] });
|
||||||
|
} else {
|
||||||
|
handleQuickSearch({ to: acc.key });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell className="font-medium max-w-[300px] truncate flex items-center gap-2">
|
||||||
|
<Search size={12} className="opacity-0 group-hover:opacity-100 text-primary transition-opacity" />
|
||||||
|
<span title={acc.key}>{acc.key}</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">{formatNumber(acc.count)}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
import { dateFnsLocaleMap, formatBytes } from "@/lib/utils"
|
import { dateFnsLocaleMap, formatBytes } from "@/lib/utils"
|
||||||
import { format, formatDistanceToNow } from "date-fns"
|
import { format, formatDistanceToNow } from "date-fns"
|
||||||
import { MessageSquareText, Paperclip } from "lucide-react"
|
import { MessageSquareText, Paperclip, Search } from "lucide-react"
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import { EmailEnvelope } from "@/api"
|
import { EmailEnvelope } from "@/api"
|
||||||
@@ -32,9 +32,10 @@ import LongText from "@/components/long-text"
|
|||||||
import { DataTableColumnHeader } from "./table/data-table-column-header"
|
import { DataTableColumnHeader } from "./table/data-table-column-header"
|
||||||
import { SearchTable } from "./table/table"
|
import { SearchTable } from "./table/table"
|
||||||
import { DataTableRowActions } from "./table/data-table-row-actions"
|
import { DataTableRowActions } from "./table/data-table-row-actions"
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import { DataTableToolbar } from "./table/toolbar"
|
import { DataTableToolbar } from "./table/toolbar"
|
||||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
|
||||||
|
import { useSearchMessages } from "@/hooks/use-search-messages"
|
||||||
|
|
||||||
interface MailListProps {
|
interface MailListProps {
|
||||||
items: EmailEnvelope[]
|
items: EmailEnvelope[]
|
||||||
@@ -87,87 +88,161 @@ export function MailListTable({
|
|||||||
{
|
{
|
||||||
accessorKey: "account_email",
|
accessorKey: "account_email",
|
||||||
header: t('search.account'),
|
header: t('search.account'),
|
||||||
cell: ({ row }) => <LongText className='text-xs'>{row.original.account_email}</LongText>,
|
cell: ({ row }) => {
|
||||||
meta: { className: 'text-left text-xs' },
|
const { setFilter } = useSearchMessages();
|
||||||
minSize: 150,
|
const { account_email, account_id } = row.original;
|
||||||
maxSize: 156,
|
|
||||||
|
return (
|
||||||
|
<div className="group relative flex items-center w-full min-w-0 h-full">
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setFilter((prev: any) => ({
|
||||||
|
...prev,
|
||||||
|
account_ids: [account_id],
|
||||||
|
mailbox_ids: undefined
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
className="absolute left-0 z-10 opacity-0 group-hover:opacity-100 p-0.5 bg-background border rounded shadow-sm hover:bg-accent"
|
||||||
|
>
|
||||||
|
<Search size={12} className="text-primary" />
|
||||||
|
</button>
|
||||||
|
<span className="text-[11px] truncate group-hover:pl-5 transition-all duration-200">
|
||||||
|
{account_email}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
meta: { className: 'w-[150px]' },
|
||||||
|
minSize: 150, maxSize: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "mailbox_name",
|
accessorKey: "mailbox_name",
|
||||||
header: t('search.mailbox'),
|
header: t('search.mailbox'),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const mailbox = row.original.mailbox_name
|
const { setFilter } = useSearchMessages();
|
||||||
const tags = row.original.tags ?? []
|
const { mailbox_name, mailbox_id, account_id, tags } = row.original;
|
||||||
|
|
||||||
if (!mailbox) return null
|
if (!mailbox_name) return null;
|
||||||
|
const safeTags = tags ?? [];
|
||||||
const visible = tags.slice(0, 3)
|
const visibleTags = safeTags.slice(0, 2);
|
||||||
const rest = tags.length - visible.length
|
|
||||||
|
|
||||||
const fullTags = tags.join(' · ')
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={200}>
|
<div className="group relative flex items-center w-full min-w-0 h-full">
|
||||||
<Tooltip>
|
<button
|
||||||
<TooltipTrigger asChild>
|
onClick={(e) => {
|
||||||
<div className="flex flex-col leading-tight max-w-[130px] cursor-default">
|
e.stopPropagation();
|
||||||
<span className="text-xs truncate">
|
setFilter((prev: any) => ({
|
||||||
{mailbox}
|
...prev,
|
||||||
</span>
|
account_ids: [account_id],
|
||||||
|
mailbox_ids: [mailbox_id]
|
||||||
{visible.length > 0 && (
|
}));
|
||||||
<span className="text-[10px] text-primary/80 truncate">
|
}}
|
||||||
{visible.join(' · ')}
|
className="absolute left-0 z-10 opacity-0 group-hover:opacity-100 p-0.5 bg-background border rounded shadow-sm hover:bg-accent transition-all"
|
||||||
{rest > 0 && ` · +${rest}`}
|
>
|
||||||
</span>
|
<Search size={11} className="text-primary" />
|
||||||
)}
|
</button>
|
||||||
</div>
|
<div className="flex flex-col min-w-0 transition-all duration-200 group-hover:pl-5">
|
||||||
</TooltipTrigger>
|
<span className="text-[11px] truncate font-medium leading-none">
|
||||||
|
{mailbox_name}
|
||||||
<TooltipContent
|
</span>
|
||||||
side="right"
|
{safeTags.length > 0 && (
|
||||||
align="start"
|
<span className="text-[9px] text-primary/70 truncate leading-none mt-0.5">
|
||||||
className="max-w-xs"
|
{visibleTags.join(' · ')}
|
||||||
>
|
{safeTags.length > 2 && ` · +${safeTags.length - 2}`}
|
||||||
<div className="text-xs font-medium mb-1">
|
</span>
|
||||||
{mailbox}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="text-[11px] text-muted-foreground break-words">
|
);
|
||||||
{fullTags}
|
|
||||||
</div>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
meta: { className: 'text-left text-xs' },
|
maxSize: 120,
|
||||||
minSize: 116,
|
|
||||||
maxSize: 116,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "from",
|
accessorKey: "from",
|
||||||
header: t('search.from'),
|
header: t('search.from'),
|
||||||
cell: ({ row }) => <LongText className='text-xs'>{row.original.from}</LongText>,
|
cell: ({ row }) => {
|
||||||
|
const fromEmail = row.original.from;
|
||||||
|
const { setFilter } = useSearchMessages();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="group relative flex items-center w-full min-w-0">
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setFilter((prev: Record<string, any>) => ({ ...prev, from: fromEmail }));
|
||||||
|
}}
|
||||||
|
className="absolute left-0 z-10 opacity-0 group-hover:opacity-100 p-1 bg-background/90 hover:bg-accent rounded shadow-sm transition-all cursor-pointer"
|
||||||
|
title={`Filter by ${fromEmail}`}
|
||||||
|
>
|
||||||
|
<Search size={12} className="text-primary" />
|
||||||
|
</button>
|
||||||
|
<LongText
|
||||||
|
className='text-xs pl-0 group-hover:pl-6 transition-all duration-200 ease-in-out'
|
||||||
|
>
|
||||||
|
{fromEmail}
|
||||||
|
</LongText>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
meta: { className: 'text-left text-xs' },
|
meta: { className: 'text-left text-xs' },
|
||||||
minSize: 150,
|
minSize: 150,
|
||||||
maxSize: 156,
|
maxSize: 300,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "to",
|
accessorKey: "to",
|
||||||
header: t('search.to'),
|
header: t('search.to'),
|
||||||
cell: ({ row }) => <LongText className='text-xs'>{row.original.to.join(", ")}</LongText>,
|
cell: ({ row }) => {
|
||||||
|
const recipients = row.original.to || [];
|
||||||
|
const { setFilter } = useSearchMessages();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="group relative flex items-center w-full min-w-0">
|
||||||
|
{recipients.length > 0 && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setFilter((prev: Record<string, any>) => ({ ...prev, to: recipients[0] }));
|
||||||
|
}}
|
||||||
|
className="absolute left-0 z-10 opacity-0 group-hover:opacity-100 p-1 bg-background/90 hover:bg-accent rounded shadow-sm transition-all cursor-pointer"
|
||||||
|
title={`${t('search.filter_by')} ${recipients[0]}`}
|
||||||
|
>
|
||||||
|
<Search size={12} className="text-primary" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<div className="text-xs transition-all duration-200 ease-in-out group-hover:pl-6 flex flex-wrap gap-x-1 min-w-0 overflow-hidden">
|
||||||
|
{recipients.map((email, index) => (
|
||||||
|
<span key={index} className="flex items-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setFilter((prev: Record<string, any>) => ({ ...prev, to: email }));
|
||||||
|
}}
|
||||||
|
className="hover:text-primary hover:underline transition-colors truncate max-w-[150px]"
|
||||||
|
title={`${t('search.filter_by')} ${email}`}
|
||||||
|
>
|
||||||
|
{email}
|
||||||
|
</button>
|
||||||
|
{index < recipients.length - 1 && (
|
||||||
|
<span className="text-muted-foreground ml-0.5">,</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
meta: { className: 'text-left text-xs' },
|
meta: { className: 'text-left text-xs' },
|
||||||
minSize: 150,
|
minSize: 150,
|
||||||
maxSize: 156,
|
maxSize: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "subject",
|
accessorKey: "subject",
|
||||||
header: t('search.subject'),
|
header: t('search.subject'),
|
||||||
cell: ({ row }) => <LongText className='text-xs'>{row.original.subject}</LongText>,
|
cell: ({ row }) => <LongText className='text-xs'>{row.original.subject}</LongText>,
|
||||||
meta: { className: 'text-left text-xs' },
|
meta: { className: 'text-left text-xs' },
|
||||||
minSize: 450,
|
maxSize: 600,
|
||||||
maxSize: 456,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "text_preview",
|
id: "text_preview",
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ export function MailboxPopover() {
|
|||||||
const { data: activeMailboxes = [], isLoading: activeIsLoading } = useQuery({
|
const { data: activeMailboxes = [], isLoading: activeIsLoading } = useQuery({
|
||||||
queryKey: ['search-mailboxes', activeAccountId],
|
queryKey: ['search-mailboxes', activeAccountId],
|
||||||
queryFn: () => list_mailboxes(activeAccountId!, false),
|
queryFn: () => list_mailboxes(activeAccountId!, false),
|
||||||
enabled: !!activeAccountId,
|
enabled: !!activeAccountId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const treeData = React.useMemo(() => {
|
const treeData = React.useMemo(() => {
|
||||||
@@ -184,7 +184,6 @@ export function MailboxPopover() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteClick = (id: string) => {
|
const handleDeleteClick = (id: string) => {
|
||||||
console.log("delete=", id);
|
|
||||||
setDeleteMailboxId(id);
|
setDeleteMailboxId(id);
|
||||||
setSelectedAccountId(activeAccountId);
|
setSelectedAccountId(activeAccountId);
|
||||||
setOpen('delete-mailbox');
|
setOpen('delete-mailbox');
|
||||||
|
|||||||
@@ -20,29 +20,73 @@
|
|||||||
import { EmailEnvelope, PaginatedResponse } from '@/api';
|
import { EmailEnvelope, PaginatedResponse } from '@/api';
|
||||||
import { search_messages } from '@/api/search/api';
|
import { search_messages } from '@/api/search/api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getRouteApi } from '@tanstack/react-router';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
|
|
||||||
|
const routeApi = getRouteApi('/_authenticated/search/')
|
||||||
|
|
||||||
export function useSearchMessages() {
|
export function useSearchMessages() {
|
||||||
// const queryClient = useQueryClient();
|
// const queryClient = useQueryClient();
|
||||||
const [filter, _setFilter] = useState<Record<string, any>>({});
|
const search = routeApi.useSearch()
|
||||||
const [page, setPage] = useState(1);
|
const navigate = routeApi.useNavigate()
|
||||||
const [pageSize, setPageSize] = useState(Number(localStorage.getItem('bichon_search_page_size')) || 30);
|
|
||||||
const [sortBy, setSortBy] = useState<"DATE" | "SIZE">("DATE");
|
|
||||||
const [sortOrder, setSortOrder] = useState<"desc" | "asc">("desc");
|
|
||||||
|
|
||||||
const setFilter = React.useCallback((val: any) => {
|
const page = search.page;
|
||||||
_setFilter(val);
|
const pageSize = search.pageSize;
|
||||||
setPage(1);
|
const sortBy = search.sortBy;
|
||||||
}, []);
|
const sortOrder = search.sortOrder;
|
||||||
|
|
||||||
|
const filter = React.useMemo(() => {
|
||||||
|
if (!search.q) return {};
|
||||||
|
try {
|
||||||
|
return JSON.parse(search.q);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("URL 'q' parameter parse error:", e);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}, [search.q]);
|
||||||
|
|
||||||
|
|
||||||
const setSearchPageSize = (value: number) => {
|
|
||||||
localStorage.setItem('bichon_search_page_size', value.toString());
|
const updateParams = React.useCallback((newParams: Partial<typeof search>) => {
|
||||||
setPageSize(value);
|
navigate({
|
||||||
}
|
search: (prev) => ({
|
||||||
|
...prev,
|
||||||
|
...newParams,
|
||||||
|
}),
|
||||||
|
replace: false,
|
||||||
|
});
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
|
||||||
|
const setFilter = React.useCallback((val: any | ((prev: any) => any)) => {
|
||||||
|
navigate({
|
||||||
|
search: (prev) => {
|
||||||
|
let currentFilter = {};
|
||||||
|
try {
|
||||||
|
currentFilter = prev.q ? JSON.parse(prev.q) : {};
|
||||||
|
} catch (e) {
|
||||||
|
currentFilter = {};
|
||||||
|
}
|
||||||
|
const nextFilter = typeof val === 'function' ? val(currentFilter) : val;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
page: 1,
|
||||||
|
q: Object.keys(nextFilter).length > 0 ? JSON.stringify(nextFilter) : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
|
||||||
|
const setPage = (p: number) => updateParams({ page: p });
|
||||||
|
|
||||||
|
const setSearchPageSize = (size: number) => {
|
||||||
|
localStorage.setItem('bichon_search_page_size', size.toString());
|
||||||
|
updateParams({ pageSize: size, page: 1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSortBy = (val: "DATE" | "SIZE") => updateParams({ sortBy: val });
|
||||||
|
const setSortOrder = (val: "desc" | "asc") => updateParams({ sortOrder: val });
|
||||||
|
|
||||||
const onSubmit = (cleaned: Record<string, any>) => {
|
const onSubmit = (cleaned: Record<string, any>) => {
|
||||||
if ('has_attachment' in cleaned && cleaned.has_attachment === false) {
|
if ('has_attachment' in cleaned && cleaned.has_attachment === false) {
|
||||||
@@ -55,7 +99,6 @@ export function useSearchMessages() {
|
|||||||
...(cleaned.before && { before: cleaned.before.getTime() }),
|
...(cleaned.before && { before: cleaned.before.getTime() }),
|
||||||
};
|
};
|
||||||
setFilter(payload);
|
setFilter(payload);
|
||||||
setPage(1);
|
|
||||||
} else {
|
} else {
|
||||||
setFilter({});
|
setFilter({});
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-33
@@ -17,6 +17,7 @@ import { Route as AuthenticatedRouteImport } from './routes/_authenticated/route
|
|||||||
import { Route as AuthenticatedIndexImport } from './routes/_authenticated/index'
|
import { Route as AuthenticatedIndexImport } from './routes/_authenticated/index'
|
||||||
import { Route as authSignInImport } from './routes/(auth)/sign-in'
|
import { Route as authSignInImport } from './routes/(auth)/sign-in'
|
||||||
import { Route as auth500Import } from './routes/(auth)/500'
|
import { Route as auth500Import } from './routes/(auth)/500'
|
||||||
|
import { Route as AuthenticatedSearchIndexImport } from './routes/_authenticated/search/index'
|
||||||
|
|
||||||
// Create Virtual Routes
|
// Create Virtual Routes
|
||||||
|
|
||||||
@@ -37,9 +38,6 @@ const AuthenticatedUsersIndexLazyImport = createFileRoute(
|
|||||||
const AuthenticatedSettingsIndexLazyImport = createFileRoute(
|
const AuthenticatedSettingsIndexLazyImport = createFileRoute(
|
||||||
'/_authenticated/settings/',
|
'/_authenticated/settings/',
|
||||||
)()
|
)()
|
||||||
const AuthenticatedSearchIndexLazyImport = createFileRoute(
|
|
||||||
'/_authenticated/search/',
|
|
||||||
)()
|
|
||||||
const AuthenticatedOauth2IndexLazyImport = createFileRoute(
|
const AuthenticatedOauth2IndexLazyImport = createFileRoute(
|
||||||
'/_authenticated/oauth2/',
|
'/_authenticated/oauth2/',
|
||||||
)()
|
)()
|
||||||
@@ -175,15 +173,6 @@ const AuthenticatedSettingsIndexLazyRoute =
|
|||||||
import('./routes/_authenticated/settings/index.lazy').then((d) => d.Route),
|
import('./routes/_authenticated/settings/index.lazy').then((d) => d.Route),
|
||||||
)
|
)
|
||||||
|
|
||||||
const AuthenticatedSearchIndexLazyRoute =
|
|
||||||
AuthenticatedSearchIndexLazyImport.update({
|
|
||||||
id: '/search/',
|
|
||||||
path: '/search/',
|
|
||||||
getParentRoute: () => AuthenticatedRouteRoute,
|
|
||||||
} as any).lazy(() =>
|
|
||||||
import('./routes/_authenticated/search/index.lazy').then((d) => d.Route),
|
|
||||||
)
|
|
||||||
|
|
||||||
const AuthenticatedOauth2IndexLazyRoute =
|
const AuthenticatedOauth2IndexLazyRoute =
|
||||||
AuthenticatedOauth2IndexLazyImport.update({
|
AuthenticatedOauth2IndexLazyImport.update({
|
||||||
id: '/oauth2/',
|
id: '/oauth2/',
|
||||||
@@ -222,6 +211,12 @@ const AuthenticatedAccountsIndexLazyRoute =
|
|||||||
import('./routes/_authenticated/accounts/index.lazy').then((d) => d.Route),
|
import('./routes/_authenticated/accounts/index.lazy').then((d) => d.Route),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const AuthenticatedSearchIndexRoute = AuthenticatedSearchIndexImport.update({
|
||||||
|
id: '/search/',
|
||||||
|
path: '/search/',
|
||||||
|
getParentRoute: () => AuthenticatedRouteRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const AuthenticatedUsersRolesLazyRoute =
|
const AuthenticatedUsersRolesLazyRoute =
|
||||||
AuthenticatedUsersRolesLazyImport.update({
|
AuthenticatedUsersRolesLazyImport.update({
|
||||||
id: '/roles',
|
id: '/roles',
|
||||||
@@ -425,6 +420,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticatedUsersRolesLazyImport
|
preLoaderRoute: typeof AuthenticatedUsersRolesLazyImport
|
||||||
parentRoute: typeof AuthenticatedUsersRouteLazyImport
|
parentRoute: typeof AuthenticatedUsersRouteLazyImport
|
||||||
}
|
}
|
||||||
|
'/_authenticated/search/': {
|
||||||
|
id: '/_authenticated/search/'
|
||||||
|
path: '/search'
|
||||||
|
fullPath: '/search'
|
||||||
|
preLoaderRoute: typeof AuthenticatedSearchIndexImport
|
||||||
|
parentRoute: typeof AuthenticatedRouteImport
|
||||||
|
}
|
||||||
'/_authenticated/accounts/': {
|
'/_authenticated/accounts/': {
|
||||||
id: '/_authenticated/accounts/'
|
id: '/_authenticated/accounts/'
|
||||||
path: '/accounts'
|
path: '/accounts'
|
||||||
@@ -453,13 +455,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticatedOauth2IndexLazyImport
|
preLoaderRoute: typeof AuthenticatedOauth2IndexLazyImport
|
||||||
parentRoute: typeof AuthenticatedRouteImport
|
parentRoute: typeof AuthenticatedRouteImport
|
||||||
}
|
}
|
||||||
'/_authenticated/search/': {
|
|
||||||
id: '/_authenticated/search/'
|
|
||||||
path: '/search'
|
|
||||||
fullPath: '/search'
|
|
||||||
preLoaderRoute: typeof AuthenticatedSearchIndexLazyImport
|
|
||||||
parentRoute: typeof AuthenticatedRouteImport
|
|
||||||
}
|
|
||||||
'/_authenticated/settings/': {
|
'/_authenticated/settings/': {
|
||||||
id: '/_authenticated/settings/'
|
id: '/_authenticated/settings/'
|
||||||
path: '/'
|
path: '/'
|
||||||
@@ -529,11 +524,11 @@ interface AuthenticatedRouteRouteChildren {
|
|||||||
AuthenticatedSettingsRouteLazyRoute: typeof AuthenticatedSettingsRouteLazyRouteWithChildren
|
AuthenticatedSettingsRouteLazyRoute: typeof AuthenticatedSettingsRouteLazyRouteWithChildren
|
||||||
AuthenticatedUsersRouteLazyRoute: typeof AuthenticatedUsersRouteLazyRouteWithChildren
|
AuthenticatedUsersRouteLazyRoute: typeof AuthenticatedUsersRouteLazyRouteWithChildren
|
||||||
AuthenticatedIndexRoute: typeof AuthenticatedIndexRoute
|
AuthenticatedIndexRoute: typeof AuthenticatedIndexRoute
|
||||||
|
AuthenticatedSearchIndexRoute: typeof AuthenticatedSearchIndexRoute
|
||||||
AuthenticatedAccountsIndexLazyRoute: typeof AuthenticatedAccountsIndexLazyRoute
|
AuthenticatedAccountsIndexLazyRoute: typeof AuthenticatedAccountsIndexLazyRoute
|
||||||
AuthenticatedApiDocsIndexLazyRoute: typeof AuthenticatedApiDocsIndexLazyRoute
|
AuthenticatedApiDocsIndexLazyRoute: typeof AuthenticatedApiDocsIndexLazyRoute
|
||||||
AuthenticatedOauth2ResultIndexLazyRoute: typeof AuthenticatedOauth2ResultIndexLazyRoute
|
AuthenticatedOauth2ResultIndexLazyRoute: typeof AuthenticatedOauth2ResultIndexLazyRoute
|
||||||
AuthenticatedOauth2IndexLazyRoute: typeof AuthenticatedOauth2IndexLazyRoute
|
AuthenticatedOauth2IndexLazyRoute: typeof AuthenticatedOauth2IndexLazyRoute
|
||||||
AuthenticatedSearchIndexLazyRoute: typeof AuthenticatedSearchIndexLazyRoute
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = {
|
const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = {
|
||||||
@@ -542,12 +537,12 @@ const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = {
|
|||||||
AuthenticatedUsersRouteLazyRoute:
|
AuthenticatedUsersRouteLazyRoute:
|
||||||
AuthenticatedUsersRouteLazyRouteWithChildren,
|
AuthenticatedUsersRouteLazyRouteWithChildren,
|
||||||
AuthenticatedIndexRoute: AuthenticatedIndexRoute,
|
AuthenticatedIndexRoute: AuthenticatedIndexRoute,
|
||||||
|
AuthenticatedSearchIndexRoute: AuthenticatedSearchIndexRoute,
|
||||||
AuthenticatedAccountsIndexLazyRoute: AuthenticatedAccountsIndexLazyRoute,
|
AuthenticatedAccountsIndexLazyRoute: AuthenticatedAccountsIndexLazyRoute,
|
||||||
AuthenticatedApiDocsIndexLazyRoute: AuthenticatedApiDocsIndexLazyRoute,
|
AuthenticatedApiDocsIndexLazyRoute: AuthenticatedApiDocsIndexLazyRoute,
|
||||||
AuthenticatedOauth2ResultIndexLazyRoute:
|
AuthenticatedOauth2ResultIndexLazyRoute:
|
||||||
AuthenticatedOauth2ResultIndexLazyRoute,
|
AuthenticatedOauth2ResultIndexLazyRoute,
|
||||||
AuthenticatedOauth2IndexLazyRoute: AuthenticatedOauth2IndexLazyRoute,
|
AuthenticatedOauth2IndexLazyRoute: AuthenticatedOauth2IndexLazyRoute,
|
||||||
AuthenticatedSearchIndexLazyRoute: AuthenticatedSearchIndexLazyRoute,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticatedRouteRouteWithChildren =
|
const AuthenticatedRouteRouteWithChildren =
|
||||||
@@ -571,11 +566,11 @@ export interface FileRoutesByFullPath {
|
|||||||
'/settings/proxy': typeof AuthenticatedSettingsProxyLazyRoute
|
'/settings/proxy': typeof AuthenticatedSettingsProxyLazyRoute
|
||||||
'/users/api-tokens': typeof AuthenticatedUsersApiTokensLazyRoute
|
'/users/api-tokens': typeof AuthenticatedUsersApiTokensLazyRoute
|
||||||
'/users/roles': typeof AuthenticatedUsersRolesLazyRoute
|
'/users/roles': typeof AuthenticatedUsersRolesLazyRoute
|
||||||
|
'/search': typeof AuthenticatedSearchIndexRoute
|
||||||
'/accounts': typeof AuthenticatedAccountsIndexLazyRoute
|
'/accounts': typeof AuthenticatedAccountsIndexLazyRoute
|
||||||
'/api-docs': typeof AuthenticatedApiDocsIndexLazyRoute
|
'/api-docs': typeof AuthenticatedApiDocsIndexLazyRoute
|
||||||
'/oauth2-result': typeof AuthenticatedOauth2ResultIndexLazyRoute
|
'/oauth2-result': typeof AuthenticatedOauth2ResultIndexLazyRoute
|
||||||
'/oauth2': typeof AuthenticatedOauth2IndexLazyRoute
|
'/oauth2': typeof AuthenticatedOauth2IndexLazyRoute
|
||||||
'/search': typeof AuthenticatedSearchIndexLazyRoute
|
|
||||||
'/settings/': typeof AuthenticatedSettingsIndexLazyRoute
|
'/settings/': typeof AuthenticatedSettingsIndexLazyRoute
|
||||||
'/users/': typeof AuthenticatedUsersIndexLazyRoute
|
'/users/': typeof AuthenticatedUsersIndexLazyRoute
|
||||||
}
|
}
|
||||||
@@ -595,11 +590,11 @@ export interface FileRoutesByTo {
|
|||||||
'/settings/proxy': typeof AuthenticatedSettingsProxyLazyRoute
|
'/settings/proxy': typeof AuthenticatedSettingsProxyLazyRoute
|
||||||
'/users/api-tokens': typeof AuthenticatedUsersApiTokensLazyRoute
|
'/users/api-tokens': typeof AuthenticatedUsersApiTokensLazyRoute
|
||||||
'/users/roles': typeof AuthenticatedUsersRolesLazyRoute
|
'/users/roles': typeof AuthenticatedUsersRolesLazyRoute
|
||||||
|
'/search': typeof AuthenticatedSearchIndexRoute
|
||||||
'/accounts': typeof AuthenticatedAccountsIndexLazyRoute
|
'/accounts': typeof AuthenticatedAccountsIndexLazyRoute
|
||||||
'/api-docs': typeof AuthenticatedApiDocsIndexLazyRoute
|
'/api-docs': typeof AuthenticatedApiDocsIndexLazyRoute
|
||||||
'/oauth2-result': typeof AuthenticatedOauth2ResultIndexLazyRoute
|
'/oauth2-result': typeof AuthenticatedOauth2ResultIndexLazyRoute
|
||||||
'/oauth2': typeof AuthenticatedOauth2IndexLazyRoute
|
'/oauth2': typeof AuthenticatedOauth2IndexLazyRoute
|
||||||
'/search': typeof AuthenticatedSearchIndexLazyRoute
|
|
||||||
'/settings': typeof AuthenticatedSettingsIndexLazyRoute
|
'/settings': typeof AuthenticatedSettingsIndexLazyRoute
|
||||||
'/users': typeof AuthenticatedUsersIndexLazyRoute
|
'/users': typeof AuthenticatedUsersIndexLazyRoute
|
||||||
}
|
}
|
||||||
@@ -624,11 +619,11 @@ export interface FileRoutesById {
|
|||||||
'/_authenticated/settings/proxy': typeof AuthenticatedSettingsProxyLazyRoute
|
'/_authenticated/settings/proxy': typeof AuthenticatedSettingsProxyLazyRoute
|
||||||
'/_authenticated/users/api-tokens': typeof AuthenticatedUsersApiTokensLazyRoute
|
'/_authenticated/users/api-tokens': typeof AuthenticatedUsersApiTokensLazyRoute
|
||||||
'/_authenticated/users/roles': typeof AuthenticatedUsersRolesLazyRoute
|
'/_authenticated/users/roles': typeof AuthenticatedUsersRolesLazyRoute
|
||||||
|
'/_authenticated/search/': typeof AuthenticatedSearchIndexRoute
|
||||||
'/_authenticated/accounts/': typeof AuthenticatedAccountsIndexLazyRoute
|
'/_authenticated/accounts/': typeof AuthenticatedAccountsIndexLazyRoute
|
||||||
'/_authenticated/api-docs/': typeof AuthenticatedApiDocsIndexLazyRoute
|
'/_authenticated/api-docs/': typeof AuthenticatedApiDocsIndexLazyRoute
|
||||||
'/_authenticated/oauth2-result/': typeof AuthenticatedOauth2ResultIndexLazyRoute
|
'/_authenticated/oauth2-result/': typeof AuthenticatedOauth2ResultIndexLazyRoute
|
||||||
'/_authenticated/oauth2/': typeof AuthenticatedOauth2IndexLazyRoute
|
'/_authenticated/oauth2/': typeof AuthenticatedOauth2IndexLazyRoute
|
||||||
'/_authenticated/search/': typeof AuthenticatedSearchIndexLazyRoute
|
|
||||||
'/_authenticated/settings/': typeof AuthenticatedSettingsIndexLazyRoute
|
'/_authenticated/settings/': typeof AuthenticatedSettingsIndexLazyRoute
|
||||||
'/_authenticated/users/': typeof AuthenticatedUsersIndexLazyRoute
|
'/_authenticated/users/': typeof AuthenticatedUsersIndexLazyRoute
|
||||||
}
|
}
|
||||||
@@ -653,11 +648,11 @@ export interface FileRouteTypes {
|
|||||||
| '/settings/proxy'
|
| '/settings/proxy'
|
||||||
| '/users/api-tokens'
|
| '/users/api-tokens'
|
||||||
| '/users/roles'
|
| '/users/roles'
|
||||||
|
| '/search'
|
||||||
| '/accounts'
|
| '/accounts'
|
||||||
| '/api-docs'
|
| '/api-docs'
|
||||||
| '/oauth2-result'
|
| '/oauth2-result'
|
||||||
| '/oauth2'
|
| '/oauth2'
|
||||||
| '/search'
|
|
||||||
| '/settings/'
|
| '/settings/'
|
||||||
| '/users/'
|
| '/users/'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
@@ -676,11 +671,11 @@ export interface FileRouteTypes {
|
|||||||
| '/settings/proxy'
|
| '/settings/proxy'
|
||||||
| '/users/api-tokens'
|
| '/users/api-tokens'
|
||||||
| '/users/roles'
|
| '/users/roles'
|
||||||
|
| '/search'
|
||||||
| '/accounts'
|
| '/accounts'
|
||||||
| '/api-docs'
|
| '/api-docs'
|
||||||
| '/oauth2-result'
|
| '/oauth2-result'
|
||||||
| '/oauth2'
|
| '/oauth2'
|
||||||
| '/search'
|
|
||||||
| '/settings'
|
| '/settings'
|
||||||
| '/users'
|
| '/users'
|
||||||
id:
|
id:
|
||||||
@@ -703,11 +698,11 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticated/settings/proxy'
|
| '/_authenticated/settings/proxy'
|
||||||
| '/_authenticated/users/api-tokens'
|
| '/_authenticated/users/api-tokens'
|
||||||
| '/_authenticated/users/roles'
|
| '/_authenticated/users/roles'
|
||||||
|
| '/_authenticated/search/'
|
||||||
| '/_authenticated/accounts/'
|
| '/_authenticated/accounts/'
|
||||||
| '/_authenticated/api-docs/'
|
| '/_authenticated/api-docs/'
|
||||||
| '/_authenticated/oauth2-result/'
|
| '/_authenticated/oauth2-result/'
|
||||||
| '/_authenticated/oauth2/'
|
| '/_authenticated/oauth2/'
|
||||||
| '/_authenticated/search/'
|
|
||||||
| '/_authenticated/settings/'
|
| '/_authenticated/settings/'
|
||||||
| '/_authenticated/users/'
|
| '/_authenticated/users/'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
@@ -761,11 +756,11 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticated/settings",
|
"/_authenticated/settings",
|
||||||
"/_authenticated/users",
|
"/_authenticated/users",
|
||||||
"/_authenticated/",
|
"/_authenticated/",
|
||||||
|
"/_authenticated/search/",
|
||||||
"/_authenticated/accounts/",
|
"/_authenticated/accounts/",
|
||||||
"/_authenticated/api-docs/",
|
"/_authenticated/api-docs/",
|
||||||
"/_authenticated/oauth2-result/",
|
"/_authenticated/oauth2-result/",
|
||||||
"/_authenticated/oauth2/",
|
"/_authenticated/oauth2/"
|
||||||
"/_authenticated/search/"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/(auth)/500": {
|
"/(auth)/500": {
|
||||||
@@ -842,6 +837,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "_authenticated/users/roles.lazy.tsx",
|
"filePath": "_authenticated/users/roles.lazy.tsx",
|
||||||
"parent": "/_authenticated/users"
|
"parent": "/_authenticated/users"
|
||||||
},
|
},
|
||||||
|
"/_authenticated/search/": {
|
||||||
|
"filePath": "_authenticated/search/index.tsx",
|
||||||
|
"parent": "/_authenticated"
|
||||||
|
},
|
||||||
"/_authenticated/accounts/": {
|
"/_authenticated/accounts/": {
|
||||||
"filePath": "_authenticated/accounts/index.lazy.tsx",
|
"filePath": "_authenticated/accounts/index.lazy.tsx",
|
||||||
"parent": "/_authenticated"
|
"parent": "/_authenticated"
|
||||||
@@ -858,10 +857,6 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "_authenticated/oauth2/index.lazy.tsx",
|
"filePath": "_authenticated/oauth2/index.lazy.tsx",
|
||||||
"parent": "/_authenticated"
|
"parent": "/_authenticated"
|
||||||
},
|
},
|
||||||
"/_authenticated/search/": {
|
|
||||||
"filePath": "_authenticated/search/index.lazy.tsx",
|
|
||||||
"parent": "/_authenticated"
|
|
||||||
},
|
|
||||||
"/_authenticated/settings/": {
|
"/_authenticated/settings/": {
|
||||||
"filePath": "_authenticated/settings/index.lazy.tsx",
|
"filePath": "_authenticated/settings/index.lazy.tsx",
|
||||||
"parent": "/_authenticated/settings"
|
"parent": "/_authenticated/settings"
|
||||||
|
|||||||
+19
-2
@@ -17,9 +17,26 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
import { createLazyFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import Search from '@/features/search'
|
import Search from '@/features/search'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
export const Route = createLazyFileRoute('/_authenticated/search/')({
|
const searchSchema = z.object({
|
||||||
|
page: z.number().catch(1),
|
||||||
|
pageSize: z.number().optional(),
|
||||||
|
sortBy: z.enum(['DATE', 'SIZE']).catch('DATE'),
|
||||||
|
sortOrder: z.enum(['asc', 'desc']).catch('desc'),
|
||||||
|
q: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_authenticated/search/')({
|
||||||
component: Search,
|
component: Search,
|
||||||
|
validateSearch: (search) => {
|
||||||
|
const result = searchSchema.parse(search);
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
page: result.page ?? 1,
|
||||||
|
pageSize: result.pageSize ?? (Number(localStorage.getItem('bichon_search_page_size')) || 30),
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user