// // Copyright (c) 2025 rustmailer.com (https://rustmailer.com) // // This file is part of the Bichon Email Archiving Project // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import * as React from "react" import { cn } from "@/lib/utils" import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable" import { Separator } from "@/components/ui/separator" import { TooltipProvider } from "@/components/ui/tooltip" import { AccountSwitcher } from "./account-switcher" import { ScrollArea } from "@/components/ui/scroll-area" import { list_mailboxes, MailboxData } from "@/api/mailbox/api" import { useQuery } from "@tanstack/react-query" import { Skeleton } from "@/components/ui/skeleton" import MailboxProvider, { MailboxDialogType } from "../context" import useDialogState from "@/hooks/use-dialog-state" import { MailboxDialog } from "./mailbox-detail" import { MailList } from "./mail-list" import { list_messages } from "@/api/mailbox/envelope/api" import { MailDisplayDrawer } from "./mail-display-drawer" import { toast } from "@/hooks/use-toast" import { EnvelopeDeleteDialog } from "./delete-dialog" import Logo from '@/assets/logo.svg' import { EmailEnvelope } from "@/api" import { EnvelopeListPagination } from "@/components/pagination" import { RichTreeView, TreeItemCheckbox, TreeItemContent, TreeItemDragAndDropOverlay, TreeItemIcon, TreeItemIconContainer, TreeItemLabel, TreeItemProvider, TreeItemRoot, useTreeItem, useTreeItemModel, UseTreeItemParameters } from "@mui/x-tree-view" import { buildTree, ExtendedTreeItemProps } from "@/lib/build-tree" import { useTheme } from "@/context/theme-context" import { styled } from "@mui/material/styles" import { animated, useSpring } from "@react-spring/web" import { TransitionProps } from "@mui/material/transitions" import Collapse from "@mui/material/Collapse" import { FolderIcon, MoreVertical, Trash2 } from "lucide-react" import { RestoreMessageDialog } from "./restore-message-dialog" import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { useTranslation } from "react-i18next" import { MailBoxDeleteDialog } from "./delete-mailbox-dialog" interface MailProps { defaultLayout: number[] | undefined defaultCollapsed?: boolean navCollapsedSize: number, lastSelectedAccountId?: number | undefined } interface ListMessagesOptions { accountId: number | undefined; mailboxId: number | undefined; page: number; page_size: number; } const useListMessages = ({ accountId, mailboxId, page, page_size }: ListMessagesOptions) => { return useQuery({ queryKey: ['mailbox-list-messages', `${accountId}`, mailboxId, page, page_size], queryFn: () => { return list_messages(accountId!, mailboxId!, page, page_size); }, enabled: !!accountId && !!mailboxId, retry: 0, staleTime: 1000, }); }; interface CustomLabelProps { exists?: number; attributes?: { attr: string; extension: string | null }[], children: React.ReactNode; id: string; icon?: React.ElementType; expandable?: boolean; onDelete: (id: string) => void; } function CustomLabel({ expandable, exists, attributes, children, id, onDelete, ...other }: CustomLabelProps) { const { t } = useTranslation() return ( {children}
{ e.stopPropagation(); }} onSelect={(e) => { e.preventDefault(); onDelete(id); }} > {t('common.delete')}
); } const CustomCollapse = styled(Collapse)({ padding: 0, }); const AnimatedCollapse = animated(CustomCollapse); function TransitionComponent(props: TransitionProps) { const style = useSpring({ to: { opacity: props.in ? 1 : 0, transform: `translate3d(0,${props.in ? 0 : 20}px,0)`, }, }); return ; } interface CustomTreeItemProps extends Omit, Omit, 'onFocus'> { } export function Mail({ defaultLayout = [20, 80], defaultCollapsed = false, navCollapsedSize, lastSelectedAccountId, }: MailProps) { const [open, setOpen] = useDialogState(null) const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed) const [selectedMailbox, setSelectedMailbox] = React.useState(undefined); const [selectedAccountId, setSelectedAccountId] = React.useState(lastSelectedAccountId); const [selectedEvelope, setSelectedEvelope] = React.useState(undefined); const [page, setPage] = React.useState(0); const [pageSize, setPageSize] = React.useState(30); const [deleteIds, setDeleteIds] = React.useState>(() => new Set()); const [selected, setSelected] = React.useState>(() => new Set()); const [deleteMailboxId, setDeleteMailboxId] = React.useState(undefined); const { theme } = useTheme() const { data: mailboxes, isLoading: isMailboxesLoading } = useQuery({ queryKey: ['account-mailboxes', `${selectedAccountId}`], queryFn: () => list_mailboxes(selectedAccountId!, false), enabled: !!selectedAccountId, }) const tree = buildTree(mailboxes ?? []); const { data: envelopes, isLoading: isMessagesLoading, isError, error } = useListMessages({ accountId: selectedAccountId, mailboxId: selectedMailbox?.id, page: page + 1, page_size: pageSize }); const hasNextPage = () => { return page + 1 < envelopes?.total_pages!; } const handlePageChange = (newPage: number) => { setPage(newPage); } const handlePageSizeChange = (newSize: number) => { setPage(0); setPageSize(newSize); } React.useEffect(() => { if (isError && error) { toast({ variant: "destructive", title: "Failed to load messages", description: error.message || "An unknown error occurred. Please try again.", }); } }, [isError, error]); // const handleItemSelectionToggle = ( // _event: React.SyntheticEvent | null, // itemId: string, // isSelected: boolean, // ) => { // if (isSelected) { // setSelectedMailbox(mailboxes?.find(m => String(m.id) === itemId)) // setPage(0); // } // }; const handleItemClick = ( _event: React.SyntheticEvent | null, itemId: string ) => { //console.log(itemId) setSelectedMailbox(mailboxes?.find(m => String(m.id) === itemId)) setPage(0); }; const handleDeleteClick = (id: string) => { setDeleteMailboxId(id); setOpen('delete'); }; const CustomTreeItem = React.useMemo(() => { return React.forwardRef(function CustomTreeItem( props: CustomTreeItemProps, ref: React.Ref, ) { const { id, itemId, label, disabled, children, ...other } = props; const { getContextProviderProps, getRootProps, getContentProps, getIconContainerProps, getCheckboxProps, getLabelProps, getGroupTransitionProps, getDragAndDropOverlayProps, status, } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const item = useTreeItemModel(itemId)!; return ( {children && } ); }); }, [theme]); return ( { localStorage.setItem('react-resizable-panels:layout:mail', JSON.stringify(sizes)); }} className="items-stretch" > { setIsCollapsed(true); localStorage.setItem('react-resizable-panels:collapsed', JSON.stringify(true)); }} onResize={() => { setIsCollapsed(false); localStorage.setItem('react-resizable-panels:collapsed', JSON.stringify(false)); }} className={cn( isCollapsed && "min-w-[50px] transition-all duration-300 ease-in-out" )} >
{ localStorage.setItem('mailbox:selectedAccountId', `${accountId}`); setSelectedAccountId(accountId); setSelectedMailbox(undefined); }} defaultAccountId={lastSelectedAccountId} />
{isMailboxesLoading ? (
{Array.from({ length: 5 }).map((_, index) => (
{Array.from({ length: 3 }).map((_, subIndex) => (
))}
))}
) : ( )}
{selectedMailbox &&

setOpen("mailbox")}> {selectedMailbox?.name}

{ const dateA = a.date; const dateB = b.date; return dateB - dateA; })} /> {selectedMailbox &&
}
} {!selectedMailbox &&
Bichon Logo
}
setOpen('mailbox')} /> setOpen('display')} /> setOpen('move-to-trash')} /> setOpen('restore')} /> setOpen('delete')} />
) }