mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
update
This commit is contained in:
@@ -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, Search } from 'lucide-react';
|
import { Mail, HardDrive, Database, Users, Inbox, Info } 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';
|
||||||
@@ -408,14 +408,22 @@ export default function MailArchiveDashboard() {
|
|||||||
{stats!.top_senders.map((s) => (
|
{stats!.top_senders.map((s) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={s.key}
|
key={s.key}
|
||||||
className="cursor-pointer hover:bg-accent/50 group"
|
className="cursor-pointer hover:bg-accent/50 group relative"
|
||||||
onClick={() => handleQuickSearch({ from: s.key })}
|
onClick={() => handleQuickSearch({ from: s.key })}
|
||||||
>
|
>
|
||||||
<TableCell className="font-medium max-w-[380px] truncate flex items-center gap-2">
|
<TableCell className="font-medium max-w-[380px] truncate relative px-4 h-9">
|
||||||
<Search size={12} className="opacity-0 group-hover:opacity-100 text-primary transition-opacity" />
|
<div className="absolute left-0 top-2 bottom-2 w-0.5 bg-primary opacity-0 group-hover:opacity-100 transition-opacity duration-150" />
|
||||||
<span title={s.key}>{s.key}</span>
|
<div className="absolute inset-x-4 bottom-1 h-[1px] bg-primary scale-x-0 group-hover:scale-x-100 transition-transform duration-200 origin-left" />
|
||||||
|
<span
|
||||||
|
title={s.key}
|
||||||
|
className="group-hover:text-primary transition-colors duration-200"
|
||||||
|
>
|
||||||
|
{s.key}
|
||||||
|
</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatNumber(s.count)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">{formatNumber(s.count)}</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
@@ -443,13 +451,21 @@ export default function MailArchiveDashboard() {
|
|||||||
{stats!.top_largest_emails.map((m, index) => (
|
{stats!.top_largest_emails.map((m, index) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={index}
|
key={index}
|
||||||
className="cursor-pointer hover:bg-accent/50 group"
|
className="cursor-pointer hover:bg-accent/50 group relative"
|
||||||
onClick={() => handleQuickSearch({ text: m.subject })}
|
onClick={() => handleQuickSearch({ text: m.subject })}
|
||||||
>
|
>
|
||||||
<TableCell className="max-w-[350px] truncate font-medium flex items-center gap-2" title={m.subject}>
|
<TableCell
|
||||||
<Search size={12} className="opacity-0 group-hover:opacity-100 text-primary transition-opacity" />
|
className="max-w-[350px] truncate font-medium relative px-4"
|
||||||
<span>{m.subject || t('dashboard.noSubject')}</span>
|
title={m.subject}
|
||||||
|
>
|
||||||
|
<div className="absolute left-0 top-2 bottom-2 w-0.5 bg-primary opacity-0 group-hover:opacity-100 transition-opacity duration-150" />
|
||||||
|
<div className="absolute inset-x-4 bottom-1 h-[1px] bg-primary scale-x-0 group-hover:scale-x-100 transition-transform duration-200 origin-left" />
|
||||||
|
|
||||||
|
<span className="group-hover:text-primary transition-colors duration-200">
|
||||||
|
{m.subject || t('dashboard.noSubject')}
|
||||||
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="text-right font-mono text-orange-600">
|
<TableCell className="text-right font-mono text-orange-600">
|
||||||
{formatBytes(m.size_bytes)}
|
{formatBytes(m.size_bytes)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -482,7 +498,7 @@ export default function MailArchiveDashboard() {
|
|||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={acc.key}
|
key={acc.key}
|
||||||
className="group cursor-pointer hover:bg-accent/50 transition-colors"
|
className="group cursor-pointer hover:bg-accent/50 transition-colors relative"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (accountId) {
|
if (accountId) {
|
||||||
handleQuickSearch({ account_ids: [accountId] });
|
handleQuickSearch({ account_ids: [accountId] });
|
||||||
@@ -491,11 +507,19 @@ export default function MailArchiveDashboard() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableCell className="font-medium max-w-[300px] truncate flex items-center gap-2">
|
<TableCell className="font-medium max-w-[300px] truncate relative px-4 h-9">
|
||||||
<Search size={12} className="opacity-0 group-hover:opacity-100 text-primary transition-opacity" />
|
<div className="absolute left-0 top-2 bottom-2 w-0.5 bg-primary opacity-0 group-hover:opacity-100 transition-opacity duration-150" />
|
||||||
<span title={acc.key}>{acc.key}</span>
|
<div className="absolute inset-x-4 bottom-1 h-[1px] bg-primary scale-x-0 group-hover:scale-x-100 transition-transform duration-200 origin-left" />
|
||||||
|
<span
|
||||||
|
title={acc.key}
|
||||||
|
className="group-hover:text-primary transition-colors duration-200"
|
||||||
|
>
|
||||||
|
{acc.key}
|
||||||
|
</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatNumber(acc.count)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">{formatNumber(acc.count)}</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -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, Search } from "lucide-react"
|
import { MessageSquareText, Paperclip } 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"
|
||||||
@@ -93,27 +93,26 @@ export function MailListTable({
|
|||||||
const { account_email, account_id } = row.original;
|
const { account_email, account_id } = row.original;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative flex items-center w-full min-w-0 h-full">
|
<div
|
||||||
<button
|
className="group relative flex items-center w-full h-full px-2 cursor-pointer"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setFilter((prev: any) => ({
|
setFilter((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
account_ids: [account_id],
|
account_ids: [account_id],
|
||||||
mailbox_ids: undefined
|
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"
|
>
|
||||||
>
|
<div className="absolute left-0 top-1 bottom-1 w-0.5 bg-primary opacity-0 group-hover:opacity-100 transition-opacity duration-150" />
|
||||||
<Search size={12} className="text-primary" />
|
<div className="absolute inset-x-2 bottom-0.5 h-[1px] bg-primary scale-x-0 group-hover:scale-x-100 transition-transform duration-200 origin-left" />
|
||||||
</button>
|
<span className="truncate flex-1 min-w-0 group-hover:text-primary transition-colors">
|
||||||
<span className="text-[11px] truncate group-hover:pl-5 transition-all duration-200">
|
<LongText className='text-xs'>{account_email}</LongText>
|
||||||
{account_email}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
meta: { className: 'w-[150px] text-xs' },
|
meta: { className: 'w-[150px]' },
|
||||||
minSize: 150, maxSize: 150,
|
minSize: 150, maxSize: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -128,22 +127,22 @@ export function MailListTable({
|
|||||||
const visibleTags = safeTags.slice(0, 2);
|
const visibleTags = safeTags.slice(0, 2);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative flex items-center w-full min-w-0 h-full">
|
<div
|
||||||
<button
|
className="group relative flex items-center w-full min-w-0 h-full px-2 cursor-pointer"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setFilter((prev: any) => ({
|
setFilter((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
account_ids: [account_id],
|
account_ids: [account_id],
|
||||||
mailbox_ids: [mailbox_id]
|
mailbox_ids: [mailbox_id]
|
||||||
}));
|
}))
|
||||||
}}
|
}}
|
||||||
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"
|
>
|
||||||
>
|
<div className="absolute left-0 top-1 bottom-1 w-0.5 bg-primary opacity-0 group-hover:opacity-100 transition-opacity duration-150" />
|
||||||
<Search size={11} className="text-primary" />
|
<div className="absolute inset-x-2 bottom-0.5 h-[1px] bg-primary scale-x-0 group-hover:scale-x-100 transition-transform duration-200 origin-left" />
|
||||||
</button>
|
|
||||||
<div className="flex flex-col min-w-0 transition-all duration-200 group-hover:pl-5">
|
<div className="flex flex-col min-w-0 flex-1">
|
||||||
<span className="text-[11px] truncate font-medium leading-none">
|
<span className="text-[11px] truncate font-medium leading-none group-hover:text-primary transition-colors">
|
||||||
{mailbox_name}
|
{mailbox_name}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex flex-wrap gap-1 mt-1">
|
<div className="flex flex-wrap gap-1 mt-1">
|
||||||
@@ -173,20 +172,16 @@ export function MailListTable({
|
|||||||
const { setFilter } = useSearchMessages();
|
const { setFilter } = useSearchMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative flex items-center w-full min-w-0">
|
<div
|
||||||
<button
|
className="group relative flex items-center w-full min-w-0 h-full px-2 cursor-pointer"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setFilter((prev: Record<string, any>) => ({ ...prev, from: fromEmail }));
|
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}`}
|
<div className="absolute left-0 top-1 bottom-1 w-0.5 bg-primary opacity-0 group-hover:opacity-100 transition-opacity duration-150" />
|
||||||
>
|
<div className="absolute inset-x-2 bottom-0.5 h-[1px] bg-primary scale-x-0 group-hover:scale-x-100 transition-transform duration-200 origin-left" />
|
||||||
<Search size={12} className="text-primary" />
|
<LongText className="text-xs flex-1 truncate group-hover:text-primary transition-colors">
|
||||||
</button>
|
|
||||||
<LongText
|
|
||||||
className='text-xs pl-0 group-hover:pl-6 transition-all duration-200 ease-in-out'
|
|
||||||
>
|
|
||||||
{fromEmail}
|
{fromEmail}
|
||||||
</LongText>
|
</LongText>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,20 +199,10 @@ export function MailListTable({
|
|||||||
const { setFilter } = useSearchMessages();
|
const { setFilter } = useSearchMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative flex items-center w-full min-w-0">
|
<div className="group relative flex items-center w-full min-w-0 h-full px-2 overflow-hidden">
|
||||||
{recipients.length > 0 && (
|
<div className="absolute left-0 top-0 bottom-0 w-[2px] bg-primary opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
<div className="text-xs flex flex-wrap gap-x-1 min-w-0 flex-1">
|
||||||
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) => (
|
{recipients.map((email, index) => (
|
||||||
<span key={index} className="flex items-center">
|
<span key={index} className="flex items-center">
|
||||||
<button
|
<button
|
||||||
@@ -227,7 +212,6 @@ export function MailListTable({
|
|||||||
setFilter((prev: Record<string, any>) => ({ ...prev, to: email }));
|
setFilter((prev: Record<string, any>) => ({ ...prev, to: email }));
|
||||||
}}
|
}}
|
||||||
className="hover:text-primary hover:underline transition-colors truncate max-w-[150px]"
|
className="hover:text-primary hover:underline transition-colors truncate max-w-[150px]"
|
||||||
title={`${t('search.filter_by')} ${email}`}
|
|
||||||
>
|
>
|
||||||
{email}
|
{email}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user