mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
feat: Add folder sync selection and All Mail exclusion logic
This commit is contained in:
@@ -27,7 +27,7 @@ import {
|
|||||||
} from '@/components/ui/dialog'
|
} from '@/components/ui/dialog'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { Loader2 } from 'lucide-react'
|
import { Loader2, CheckSquare, Square } from 'lucide-react'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { AccountModel } from '../data/schema'
|
import { AccountModel } from '../data/schema'
|
||||||
import { toast } from '@/hooks/use-toast'
|
import { toast } from '@/hooks/use-toast'
|
||||||
@@ -50,6 +50,7 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
|||||||
const [selectedFolders, setSelectedFolders] = useState<string[]>(currentRow.sync_folders || []);
|
const [selectedFolders, setSelectedFolders] = useState<string[]>(currentRow.sync_folders || []);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { data: mailboxes, isLoading } = useQuery({
|
const { data: mailboxes, isLoading } = useQuery({
|
||||||
queryKey: ['account-mailboxes', currentRow.id],
|
queryKey: ['account-mailboxes', currentRow.id],
|
||||||
queryFn: () => list_mailboxes(currentRow.id, true),
|
queryFn: () => list_mailboxes(currentRow.id, true),
|
||||||
@@ -64,21 +65,56 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
|||||||
.map(mailbox => mailbox.id.toString());
|
.map(mailbox => mailbox.id.toString());
|
||||||
}, [mailboxes, selectedFolders]);
|
}, [mailboxes, selectedFolders]);
|
||||||
|
|
||||||
// Convert data to tree structure
|
|
||||||
const treeData = useMemo(() => {
|
const treeData = useMemo(() => {
|
||||||
if (!mailboxes) return [];
|
if (!mailboxes) return [];
|
||||||
return buildTree(mailboxes, undefined, true, true);
|
return buildTree(mailboxes, undefined, true, true);
|
||||||
}, [mailboxes]);
|
}, [mailboxes]);
|
||||||
|
|
||||||
const handleSelectItems = useCallback((selectedItems: TreeDataItem[]) => {
|
const handleSelectItems = useCallback((selectedItems: TreeDataItem[]) => {
|
||||||
|
const allMailboxes = mailboxes || [];
|
||||||
const selected = selectedItems
|
const selected = selectedItems
|
||||||
.map(item => mailboxes?.find(m => m.id === parseInt(item.id, 10))?.name)
|
.map(item => mailboxes?.find(m => m.id === parseInt(item.id, 10))?.name)
|
||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
const allMailSelected = selected.some(selectedName => {
|
||||||
|
const mailbox = allMailboxes.find(m => m.name === selectedName);
|
||||||
|
if (!mailbox) return false;
|
||||||
|
return mailbox.attributes.some(a => a.attr === 'All');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allMailSelected) {
|
||||||
|
toast({
|
||||||
|
title: 'Heads Up: "All Mail" Folder Selected',
|
||||||
|
description: 'Selecting folders with the "All Mail" attribute will likely lead to duplicating messages already synced from folders like Inbox and Sent. This may consume significantly more storage space.',
|
||||||
|
action: <ToastAction altText="I Understand">OK</ToastAction>,
|
||||||
|
});
|
||||||
|
}
|
||||||
setSelectedFolders(selected);
|
setSelectedFolders(selected);
|
||||||
}, [mailboxes]);
|
}, [mailboxes]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleSelectAll = useCallback(() => {
|
||||||
|
if (!mailboxes) return;
|
||||||
|
const validFolderNames = mailboxes
|
||||||
|
.filter(mailbox => {
|
||||||
|
const isAllMail = mailbox.attributes.some(a => a.attr === 'All');
|
||||||
|
if (isAllMail) return false;
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map(m => m.name);
|
||||||
|
setSelectedFolders(validFolderNames);
|
||||||
|
if (validFolderNames.length < mailboxes.length) {
|
||||||
|
toast({
|
||||||
|
description: "Selected standard folders. 'All Mail' was skipped to avoid duplicates.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [mailboxes]);
|
||||||
|
|
||||||
|
const handleDeselectAll = useCallback(() => {
|
||||||
|
setSelectedFolders([]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: (data: Record<string, any>) => update_account(currentRow?.id ?? '', data),
|
mutationFn: (data: Record<string, any>) => update_account(currentRow?.id ?? '', data),
|
||||||
onSuccess: handleSuccess,
|
onSuccess: handleSuccess,
|
||||||
@@ -96,6 +132,7 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
|||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleError(error: AxiosError) {
|
function handleError(error: AxiosError) {
|
||||||
const errorMessage = (error.response?.data as { message?: string })?.message ||
|
const errorMessage = (error.response?.data as { message?: string })?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
@@ -111,7 +148,6 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (selectedFolders.length === 0) {
|
if (selectedFolders.length === 0) {
|
||||||
toast({
|
toast({
|
||||||
@@ -138,7 +174,29 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-between pt-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleSelectAll}
|
||||||
|
disabled={isLoading || !mailboxes || mailboxes.length === 0}
|
||||||
|
className="h-8"
|
||||||
|
>
|
||||||
|
<CheckSquare className="w-4 h-4 mr-2" />
|
||||||
|
Select All
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleDeselectAll}
|
||||||
|
disabled={isLoading || selectedFolders.length === 0}
|
||||||
|
className="h-8"
|
||||||
|
>
|
||||||
|
<Square className="w-4 h-4 mr-2" />
|
||||||
|
Deselect All
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
{selectedFolders.length} folder(s) selected
|
{selectedFolders.length} folder(s) selected
|
||||||
</div>
|
</div>
|
||||||
@@ -160,6 +218,7 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
|||||||
)}
|
)}
|
||||||
{!isLoading && (
|
{!isLoading && (
|
||||||
<TreeView
|
<TreeView
|
||||||
|
key={selectedFolders.length > 0 ? `tree-${selectedFolders.length}-${selectedFolders[0]}` : 'tree-empty'}
|
||||||
data={treeData}
|
data={treeData}
|
||||||
multiple
|
multiple
|
||||||
expandAll
|
expandAll
|
||||||
|
|||||||
Reference in New Issue
Block a user