Updated presets and added a 'sort by' feature.

This commit is contained in:
ktdd
2026-01-06 12:33:09 +02:00
parent 0c46432150
commit 7edd7c2e35
23 changed files with 103 additions and 47 deletions
+19 -2
View File
@@ -621,6 +621,7 @@ impl EnvelopeIndexManager {
page: u64, page: u64,
page_size: u64, page_size: u64,
desc: bool, desc: bool,
sort_by: String
) -> BichonResult<DataPage<Envelope>> { ) -> BichonResult<DataPage<Envelope>> {
assert!(page > 0, "Page number must be greater than 0"); assert!(page > 0, "Page number must be greater than 0");
assert!(page_size > 0, "Page size must be greater than 0"); assert!(page_size > 0, "Page size must be greater than 0");
@@ -653,7 +654,20 @@ impl EnvelopeIndexManager {
} }
let order = if desc { Order::Desc } else { Order::Asc }; let order = if desc { Order::Desc } else { Order::Asc };
let mailbox_docs: Vec<(i64, DocAddress)> = searcher let mailbox_docs: Vec<DocAddress>;
if sort_by == "size" {
let size_docs: Vec<(u64, DocAddress)> = searcher
.search(
&query,
&TopDocs::with_limit(page_size as usize)
.and_offset(offset as usize)
.order_by_fast_field(F_SIZE, order),
)
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
mailbox_docs = size_docs.into_iter().map(|(_, addr)| addr).collect();
} else {
let date_docs: Vec<(i64, DocAddress)> = searcher
.search( .search(
&query, &query,
&TopDocs::with_limit(page_size as usize) &TopDocs::with_limit(page_size as usize)
@@ -661,9 +675,12 @@ impl EnvelopeIndexManager {
.order_by_fast_field(F_DATE, order), .order_by_fast_field(F_DATE, order),
) )
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
mailbox_docs = date_docs.into_iter().map(|(_, addr)| addr).collect();
}
let mut result = Vec::new(); let mut result = Vec::new();
for (_, doc_address) in mailbox_docs { for doc_address in mailbox_docs {
let doc: TantivyDocument = searcher let doc: TantivyDocument = searcher
.doc_async(doc_address) .doc_async(doc_address)
.await .await
+2
View File
@@ -54,6 +54,7 @@ pub struct SearchRequest {
filter: SearchFilter, filter: SearchFilter,
page: u64, page: u64,
page_size: u64, page_size: u64,
sort_by: String
} }
impl SearchRequest { impl SearchRequest {
pub fn validate(&self) -> BichonResult<()> { pub fn validate(&self) -> BichonResult<()> {
@@ -85,6 +86,7 @@ pub async fn search_messages_impl(
request.page, request.page,
request.page_size, request.page_size,
true, true,
request.sort_by
) )
.await .await
} }
+31 -4
View File
@@ -39,6 +39,14 @@ import { EditTagsDialog } from './add-tag-dialog';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Logo from '@/assets/logo.svg' import Logo from '@/assets/logo.svg'
import { RestoreMessageDialog } from './restore-message-dialog'; import { RestoreMessageDialog } from './restore-message-dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Label } from '@/components/ui/label';
export default function Search() { export default function Search() {
const { t } = useTranslation() const { t } = useTranslation()
@@ -56,8 +64,10 @@ export default function Search() {
isFetching, isFetching,
page, page,
pageSize, pageSize,
sortBy,
setPage, setPage,
setPageSize, setPageSize,
setSortBy,
onSubmit, onSubmit,
reset, reset,
filter filter
@@ -121,10 +131,27 @@ export default function Search() {
</div> </div>
</aside> </aside>
<div className="flex-1 min-w-0 space-y-4"> <div className="flex-1 min-w-0 space-y-4">
<Button size="sm" onClick={() => setOpen("search-form")}> <div className="flex flex-row gap-4 items-end">
<SearchIcon className="mr-2 h-4 w-4" /> <Button size="sm" onClick={() => setOpen("search-form")}>
{t('common.search')} <SearchIcon className="mr-2 h-4 w-4" />
</Button> {t('common.search')}
</Button>
<Label className="">
{t('search.sortBy')}
<Select
value={sortBy}
onValueChange={(value: "date" | "size") => setSortBy(value)}
>
<SelectTrigger className='h-8 mt-2'>
<SelectValue placeholder="Placeholder" />
</SelectTrigger>
<SelectContent side='top'>
<SelectItem value="date">{t('search.date')}</SelectItem>
<SelectItem value="size">{t('search.size')}</SelectItem>
</SelectContent>
</Select>
</Label>
</div>
{isLoading && ( {isLoading && (
<Card> <Card>
<CardContent className="py-12"> <CardContent className="py-12">
+4 -2
View File
@@ -67,7 +67,7 @@ const getSearchFilterSchema = (t: (key: string) => string) => z.object({
before: z.date().optional(), before: z.date().optional(),
account_id: z.number().optional().or(z.literal("")), account_id: z.number().optional().or(z.literal("")),
mailbox_id: z.number().optional().or(z.literal("")), mailbox_id: z.number().optional().or(z.literal("")),
size_preset: z.enum(['any', 'tiny', 'small', 'medium', 'large']).optional(), size_preset: z.enum(['any', 'tiny', 'small', 'medium', 'large', 'huge']).optional(),
message_id: z.string().optional().or(z.literal("")), message_id: z.string().optional().or(z.literal("")),
}); });
@@ -107,8 +107,10 @@ function withSizePreset(values: Record<string, any>) {
case 'small': case 'small':
return { ...rest, max_size: 2 * 1024 * 1024 }; return { ...rest, max_size: 2 * 1024 * 1024 };
case 'medium': case 'medium':
return { ...rest, max_size: 20 * 1024 * 1024 }; return { ...rest, min_size: 2 * 1024 * 1024, max_size: 10 * 1024 * 1024 };
case 'large': case 'large':
return { ...rest, min_size: 10 * 1024 * 1024, max_size: 20 * 1024 * 1024 };
case 'huge':
return { ...rest, min_size: 20 * 1024 * 1024 }; return { ...rest, min_size: 20 * 1024 * 1024 };
default: default:
return rest; return rest;
+5 -1
View File
@@ -29,6 +29,7 @@ export function useSearchMessages() {
const [filter, setFilter] = useState<Record<string, any>>({}); const [filter, setFilter] = useState<Record<string, any>>({});
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(30); const [pageSize, setPageSize] = useState(30);
const [sortBy, setSortBy] = useState<"date" | "size">("date");
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) {
@@ -59,12 +60,13 @@ export function useSearchMessages() {
error, error,
isFetching, isFetching,
} = useQuery<PaginatedResponse<EmailEnvelope>>({ } = useQuery<PaginatedResponse<EmailEnvelope>>({
queryKey: ['search-messages', filter, page, pageSize], queryKey: ['search-messages', filter, page, pageSize, sortBy],
queryFn: () => queryFn: () =>
search_messages({ search_messages({
filter: filter, filter: filter,
page, page,
page_size: pageSize, page_size: pageSize,
sort_by: sortBy
}), }),
staleTime: 1000, staleTime: 1000,
retry: false, retry: false,
@@ -76,6 +78,8 @@ export function useSearchMessages() {
totalPages: data?.total_pages ?? 1, totalPages: data?.total_pages ?? 1,
pageSize: data?.page_size ?? pageSize, pageSize: data?.page_size ?? pageSize,
setPageSize, setPageSize,
sortBy,
setSortBy,
isLoading, isLoading,
isError, isError,
error: error as Error | null, error: error as Error | null,
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "الكل", "any": "الكل",
"tiny": "صغير جدًا (<15 كيلوبايت)", "tiny": "صغير جدًا (<15 كيلوبايت)",
"small": "صغير (<2 ميغابايت)", "small": "صغير (<2 ميغابايت)",
"medium": "متوسط (<20 ميغابايت)", "medium": "متوسط (2 - 10 ميغابايت)",
"large": "كبير (20 ميغابايت)", "large": "كبير (10 - 20 ميغابايت)",
"sizeDescription": "يشير الحجم إلى الحجم الإجمالي للبريد الإلكتروني، بما في ذلك المرفقات.", "sizeDescription": "يشير الحجم إلى الحجم الإجمالي للبريد الإلكتروني، بما في ذلك المرفقات.",
"title": "بحث", "title": "بحث",
"searching": "جارٍ البحث، يرجى الانتظار...", "searching": "جارٍ البحث، يرجى الانتظار...",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Alle", "any": "Alle",
"tiny": "Meget lille (<15 KB)", "tiny": "Meget lille (<15 KB)",
"small": "Lille (<2 MB)", "small": "Lille (<2 MB)",
"medium": "Mellem (<20 MB)", "medium": "Mellem (2 - 10 MB)",
"large": "Stor (20 MB)", "large": "Stor (10 - 20 MB)",
"sizeDescription": "Størrelsen henviser til den samlede e-mailstørrelse, inklusive vedhæftede filer.", "sizeDescription": "Størrelsen henviser til den samlede e-mailstørrelse, inklusive vedhæftede filer.",
"title": "Søg", "title": "Søg",
"searching": "Søger, vent venligst...", "searching": "Søger, vent venligst...",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Beliebig", "any": "Beliebig",
"tiny": "Sehr klein (<15 KB)", "tiny": "Sehr klein (<15 KB)",
"small": "Klein (<2 MB)", "small": "Klein (<2 MB)",
"medium": "Mittel (<20 MB)", "medium": "Mittel (2 - 10 MB)",
"large": "Groß (20 MB)", "large": "Groß (10 - 20 MB)",
"sizeDescription": "Die Größe bezieht sich auf die gesamte E-Mail inklusive Anhängen.", "sizeDescription": "Die Größe bezieht sich auf die gesamte E-Mail inklusive Anhängen.",
"title": "Suchen", "title": "Suchen",
"searching": "Wird gesucht, bitte warten Sie...", "searching": "Wird gesucht, bitte warten Sie...",
+4 -2
View File
@@ -412,8 +412,10 @@
"any": "Any", "any": "Any",
"tiny": "Tiny (<15 KB)", "tiny": "Tiny (<15 KB)",
"small": "Small (<2 MB)", "small": "Small (<2 MB)",
"medium": "Medium (<20 MB)", "medium": "Medium (2 - 10 MB)",
"large": "Large (20 MB)", "large": "Large (10 - 20 MB)",
"huge": "Huge (≥20 MB)",
"sortBy": "Sort by",
"sizeDescription": "The size refers to the total email size, including attachments.", "sizeDescription": "The size refers to the total email size, including attachments.",
"title": "Search", "title": "Search",
"searching": "Searching, please wait…", "searching": "Searching, please wait…",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Cualquiera", "any": "Cualquiera",
"tiny": "Muy pequeño (<15 KB)", "tiny": "Muy pequeño (<15 KB)",
"small": "Pequeño (<2 MB)", "small": "Pequeño (<2 MB)",
"medium": "Mediano (<20 MB)", "medium": "Mediano (2 - 10 MB)",
"large": "Grande (20 MB)", "large": "Grande (10 - 20 MB)",
"sizeDescription": "El tamaño se refiere al tamaño total del correo electrónico, incluidos los archivos adjuntos.", "sizeDescription": "El tamaño se refiere al tamaño total del correo electrónico, incluidos los archivos adjuntos.",
"title": "Buscar", "title": "Buscar",
"searching": "Buscando, por favor espera...", "searching": "Buscando, por favor espera...",
+4 -2
View File
@@ -412,8 +412,10 @@
"any": "Kaikki", "any": "Kaikki",
"tiny": "Erittäin pieni (<15 KB)", "tiny": "Erittäin pieni (<15 KB)",
"small": "Pieni (<2 MB)", "small": "Pieni (<2 MB)",
"medium": "Keskikokoinen (<20 MB)", "medium": "Keskikokoinen (2 - 10 MB)",
"large": "Suuri (20 MB)", "large": "Suuri (10 - 20 MB)",
"huge": "Valtava (≥20 MB)",
"sortBy": "Lajittele",
"sizeDescription": "Koko tarkoittaa sähköpostin kokonaiskokoa, liitteet mukaan lukien.", "sizeDescription": "Koko tarkoittaa sähköpostin kokonaiskokoa, liitteet mukaan lukien.",
"title": "Hae", "title": "Hae",
"searching": "Haetaan, odota hetki...", "searching": "Haetaan, odota hetki...",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Toutes", "any": "Toutes",
"tiny": "Très petite (<15 KB)", "tiny": "Très petite (<15 KB)",
"small": "Petite (<2 MB)", "small": "Petite (<2 MB)",
"medium": "Moyenne (<20 MB)", "medium": "Moyenne (2 - 10 MB)",
"large": "Grande (20 MB)", "large": "Grande (10 - 20 MB)",
"sizeDescription": "La taille correspond à la taille totale de le-mail, pièces jointes incluses.", "sizeDescription": "La taille correspond à la taille totale de le-mail, pièces jointes incluses.",
"title": "Recherche", "title": "Recherche",
"searching": "Recherche en cours, veuillez patienter...", "searching": "Recherche en cours, veuillez patienter...",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Qualsiasi", "any": "Qualsiasi",
"tiny": "Molto piccolo (<15 KB)", "tiny": "Molto piccolo (<15 KB)",
"small": "Piccolo (<2 MB)", "small": "Piccolo (<2 MB)",
"medium": "Medio (<20 MB)", "medium": "Medio (2 - 10 MB)",
"large": "Grande (20 MB)", "large": "Grande (10 - 20 MB)",
"sizeDescription": "La dimensione indica la dimensione totale dellemail, inclusi gli allegati.", "sizeDescription": "La dimensione indica la dimensione totale dellemail, inclusi gli allegati.",
"title": "Cerca", "title": "Cerca",
"searching": "Ricerca in corso, attendere prego...", "searching": "Ricerca in corso, attendere prego...",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "指定なし", "any": "指定なし",
"tiny": "極小(15 KB 未満)", "tiny": "極小(15 KB 未満)",
"small": "小(2 MB 未満)", "small": "小(2 MB 未満)",
"medium": "中(20 MB 未満", "medium": "中(2 - 10 MB",
"large": "大(20 MB 以上", "large": "大(10 - 20 MB",
"sizeDescription": "サイズは添付ファイルを含むメール全体の容量を指します。", "sizeDescription": "サイズは添付ファイルを含むメール全体の容量を指します。",
"title": "検索", "title": "検索",
"searching": "検索中です。お待ちください…", "searching": "検索中です。お待ちください…",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "전체", "any": "전체",
"tiny": "아주 작음 (<15 KB)", "tiny": "아주 작음 (<15 KB)",
"small": "작음 (<2 MB)", "small": "작음 (<2 MB)",
"medium": "중간 (<20 MB)", "medium": "중간 (2 - 10 MB)",
"large": "큼 (20 MB)", "large": "큼 (10 - 20 MB)",
"sizeDescription": "크기는 첨부 파일을 포함한 이메일 전체 크기를 의미합니다.", "sizeDescription": "크기는 첨부 파일을 포함한 이메일 전체 크기를 의미합니다.",
"title": "검색", "title": "검색",
"searching": "검색 중입니다. 잠시 기다려 주십시오...", "searching": "검색 중입니다. 잠시 기다려 주십시오...",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Alle", "any": "Alle",
"tiny": "Zeer klein (<15 KB)", "tiny": "Zeer klein (<15 KB)",
"small": "Klein (<2 MB)", "small": "Klein (<2 MB)",
"medium": "Middelgroot (<20 MB)", "medium": "Middelgroot (2 - 10 MB)",
"large": "Groot (20 MB)", "large": "Groot (10 - 20 MB)",
"sizeDescription": "De grootte verwijst naar de totale e-mailgrootte, inclusief bijlagen.", "sizeDescription": "De grootte verwijst naar de totale e-mailgrootte, inclusief bijlagen.",
"title": "Zoeken", "title": "Zoeken",
"searching": "Bezig met zoeken, even geduld alstublieft…", "searching": "Bezig met zoeken, even geduld alstublieft…",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Alle", "any": "Alle",
"tiny": "Svært liten (<15 KB)", "tiny": "Svært liten (<15 KB)",
"small": "Liten (<2 MB)", "small": "Liten (<2 MB)",
"medium": "Middels (<20 MB)", "medium": "Middels (2 - 10 MB)",
"large": "Stor (20 MB)", "large": "Stor (10 - 20 MB)",
"sizeDescription": "Størrelsen viser til e-postens totale størrelse, inkludert vedlegg.", "sizeDescription": "Størrelsen viser til e-postens totale størrelse, inkludert vedlegg.",
"title": "Søk", "title": "Søk",
"searching": "Søker, vennligst vent…", "searching": "Søker, vennligst vent…",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Dowolny", "any": "Dowolny",
"tiny": "Bardzo mały (<15 KB)", "tiny": "Bardzo mały (<15 KB)",
"small": "Mały (<2 MB)", "small": "Mały (<2 MB)",
"medium": "Średni (<20 MB)", "medium": "Średni (2 - 10 MB)",
"large": "Duży (20 MB)", "large": "Duży (10 - 20 MB)",
"sizeDescription": "Rozmiar odnosi się do całkowitego rozmiaru wiadomości e-mail, łącznie z załącznikami.", "sizeDescription": "Rozmiar odnosi się do całkowitego rozmiaru wiadomości e-mail, łącznie z załącznikami.",
"title": "Szukaj", "title": "Szukaj",
"searching": "Wyszukiwanie, proszę czekać...", "searching": "Wyszukiwanie, proszę czekać...",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Qualquer", "any": "Qualquer",
"tiny": "Muito pequeno (<15 KB)", "tiny": "Muito pequeno (<15 KB)",
"small": "Pequeno (<2 MB)", "small": "Pequeno (<2 MB)",
"medium": "Médio (<20 MB)", "medium": "Médio (2 - 10 MB)",
"large": "Grande (20 MB)", "large": "Grande (10 - 20 MB)",
"sizeDescription": "O tamanho refere-se ao tamanho total do e-mail, incluindo anexos.", "sizeDescription": "O tamanho refere-se ao tamanho total do e-mail, incluindo anexos.",
"title": "Pesquisa", "title": "Pesquisa",
"searching": "Pesquisando, por favor, aguarde...", "searching": "Pesquisando, por favor, aguarde...",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Любой", "any": "Любой",
"tiny": "Очень маленький (<15 КБ)", "tiny": "Очень маленький (<15 КБ)",
"small": "Маленький (<2 МБ)", "small": "Маленький (<2 МБ)",
"medium": "Средний (<20 МБ)", "medium": "Средний (2 - 10 МБ)",
"large": "Большой (20 МБ)", "large": "Большой (10 - 20 МБ)",
"sizeDescription": "Размер означает общий размер электронного письма, включая вложения.", "sizeDescription": "Размер означает общий размер электронного письма, включая вложения.",
"title": "Поиск", "title": "Поиск",
"searching": "Поиск, пожалуйста, подождите...", "searching": "Поиск, пожалуйста, подождите...",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "Alla", "any": "Alla",
"tiny": "Mycket liten (<15 KB)", "tiny": "Mycket liten (<15 KB)",
"small": "Liten (<2 MB)", "small": "Liten (<2 MB)",
"medium": "Medelstor (<20 MB)", "medium": "Medelstor (2 - 10 MB)",
"large": "Stor (20 MB)", "large": "Stor (10 - 20 MB)",
"sizeDescription": "Storleken avser e-postens totala storlek, inklusive bilagor.", "sizeDescription": "Storleken avser e-postens totala storlek, inklusive bilagor.",
"title": "Sök", "title": "Sök",
"searching": "Söker, vänligen vänta…", "searching": "Söker, vänligen vänta…",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "不限", "any": "不限",
"tiny": "很小(<15 KB", "tiny": "很小(<15 KB",
"small": "小(<2 MB", "small": "小(<2 MB",
"medium": "中(<20 MB", "medium": "中(2 - 10 MB",
"large": "大(20 MB", "large": "大(10 - 20 MB",
"sizeDescription": "大小指的是整封郵件的大小,包含附件。", "sizeDescription": "大小指的是整封郵件的大小,包含附件。",
"title": "搜尋", "title": "搜尋",
"searching": "正在搜尋,請稍候...", "searching": "正在搜尋,請稍候...",
+2 -2
View File
@@ -412,8 +412,8 @@
"any": "不限", "any": "不限",
"tiny": "很小(<15 KB", "tiny": "很小(<15 KB",
"small": "小(<2 MB", "small": "小(<2 MB",
"medium": "中(<20 MB", "medium": "中(2 - 10 MB",
"large": "大(20 MB", "large": "大(10 - 20 MB",
"sizeDescription": "大小指的是邮件整体大小,包含附件。", "sizeDescription": "大小指的是邮件整体大小,包含附件。",
"title": "搜索", "title": "搜索",
"searching": "搜索中,请稍候…", "searching": "搜索中,请稍候…",