feat: standalone buttons for all plugin pages + window system for modals
Standalone buttons: - Add ExternalLink button to Dms, Calendar, Mail, ContactsList toolbars - Create standalone pages for each (no sidebar, full screen) - Add standalone routes outside ProtectedRoute Window system for modals: - AppointmentModal -> AppointmentEditForm + openWindow() - ComposeModal -> MailComposeForm + openWindow() - FilePreviewModal -> FilePreviewContent + openWindow() - Calendar, Mail, Dms use openWindow() instead of modal state
This commit is contained in:
+117
-59
@@ -15,9 +15,10 @@ import { ResizablePanel } from '@/components/ui/ResizablePanel';
|
||||
import { MailFolderTree } from '@/components/mail/MailFolderTree';
|
||||
import { MailList } from '@/components/mail/MailList';
|
||||
import { MailDetail } from '@/components/mail/MailDetail';
|
||||
import { ComposeModal, type ComposeMode } from '@/components/mail/ComposeModal';
|
||||
import { MailComposeForm, type ComposeMode } from '@/components/mail/MailComposeForm';
|
||||
import { useWindowStore } from '@/store/windowStore';
|
||||
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
|
||||
import { ArrowRight, Check, ChevronLeft, Loader2, Plus, Redo2, Trash2, TrendingUp, Undo2 } from 'lucide-react';
|
||||
import { ArrowRight, Check, ChevronLeft, ExternalLink, Loader2, Plus, Redo2, Trash2, TrendingUp, Undo2 } from 'lucide-react';
|
||||
import {
|
||||
fetchAccounts,
|
||||
fetchFolders,
|
||||
@@ -69,11 +70,11 @@ export function MailPage() {
|
||||
const [loadingMails, setLoadingMails] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [signatures, setSignatures] = useState<MailSignature[]>([]);
|
||||
const [composeOpen, setComposeOpen] = useState(false);
|
||||
const [composeMode, setComposeMode] = useState<ComposeMode>('new');
|
||||
const [replyToMail, setReplyToMail] = useState<Mail | null>(null);
|
||||
const [forwardMailState, setForwardMailState] = useState<Mail | null>(null);
|
||||
const [draftMailState, setDraftMailState] = useState<Mail | null>(null);
|
||||
const openWindow = useWindowStore((s) => s.openWindow);
|
||||
const [downloadingAttachmentId, setDownloadingAttachmentId] = useState<string | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeView, setActiveView] = useState<'folders' | 'list' | 'detail'>('folders');
|
||||
@@ -233,14 +234,66 @@ export function MailPage() {
|
||||
[toast],
|
||||
);
|
||||
|
||||
// Handle send (compose)
|
||||
const handleSend = useCallback(
|
||||
async (payload: SendMailPayload | ReplyPayload | ForwardPayload, mode: ComposeMode) => {
|
||||
try {
|
||||
if (mode === 'reply' && replyToMail) {
|
||||
await replyMail(replyToMail.id, payload as ReplyPayload);
|
||||
} else if (mode === 'forward' && forwardMailState) {
|
||||
await forwardMail(forwardMailState.id, payload as ForwardPayload);
|
||||
} else {
|
||||
await sendMail(payload as SendMailPayload);
|
||||
}
|
||||
toast.success(t('mail.sent'));
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[replyToMail, forwardMailState, toast, t],
|
||||
);
|
||||
|
||||
// Handle save draft (new or existing)
|
||||
const handleSaveDraft = useCallback(
|
||||
async (payload: MailDraftPayload) => {
|
||||
try {
|
||||
if (composeMode === 'draft' && draftMailState) {
|
||||
await updateDraft(draftMailState.id, payload);
|
||||
} else {
|
||||
await saveDraft(payload);
|
||||
}
|
||||
toast.success(t('mail.draftSaved'));
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[composeMode, draftMailState, toast, t],
|
||||
);
|
||||
|
||||
// Handle compose
|
||||
const handleCompose = useCallback(() => {
|
||||
setComposeMode('new');
|
||||
setReplyToMail(null);
|
||||
setForwardMailState(null);
|
||||
setDraftMailState(null);
|
||||
setComposeOpen(true);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: t('mail.compose'),
|
||||
type: 'mail-compose',
|
||||
component: MailComposeForm,
|
||||
componentProps: {
|
||||
mode: 'new',
|
||||
accountId: selectedAccountId,
|
||||
replyToMail: null,
|
||||
forwardMail: null,
|
||||
draftMail: null,
|
||||
signatures,
|
||||
onSend: handleSend,
|
||||
onSaveDraft: handleSaveDraft,
|
||||
},
|
||||
});
|
||||
}, [openWindow, t, selectedAccountId, signatures, handleSend, handleSaveDraft]);
|
||||
|
||||
// Handle reply
|
||||
const handleReply = useCallback((mail: Mail) => {
|
||||
@@ -248,8 +301,22 @@ export function MailPage() {
|
||||
setReplyToMail(mail);
|
||||
setForwardMailState(null);
|
||||
setDraftMailState(null);
|
||||
setComposeOpen(true);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: t('mail.reply'),
|
||||
type: 'mail-compose',
|
||||
component: MailComposeForm,
|
||||
componentProps: {
|
||||
mode: 'reply',
|
||||
accountId: selectedAccountId,
|
||||
replyToMail: mail,
|
||||
forwardMail: null,
|
||||
draftMail: null,
|
||||
signatures,
|
||||
onSend: handleSend,
|
||||
onSaveDraft: handleSaveDraft,
|
||||
},
|
||||
});
|
||||
}, [openWindow, t, selectedAccountId, signatures, handleSend, handleSaveDraft]);
|
||||
|
||||
// Handle forward
|
||||
const handleForward = useCallback((mail: Mail) => {
|
||||
@@ -257,8 +324,22 @@ export function MailPage() {
|
||||
setForwardMailState(mail);
|
||||
setReplyToMail(null);
|
||||
setDraftMailState(null);
|
||||
setComposeOpen(true);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: t('mail.forward'),
|
||||
type: 'mail-compose',
|
||||
component: MailComposeForm,
|
||||
componentProps: {
|
||||
mode: 'forward',
|
||||
accountId: selectedAccountId,
|
||||
replyToMail: null,
|
||||
forwardMail: mail,
|
||||
draftMail: null,
|
||||
signatures,
|
||||
onSend: handleSend,
|
||||
onSaveDraft: handleSaveDraft,
|
||||
},
|
||||
});
|
||||
}, [openWindow, t, selectedAccountId, signatures, handleSend, handleSaveDraft]);
|
||||
|
||||
// Handle delete single mail
|
||||
const handleDeleteMail = useCallback(
|
||||
@@ -324,23 +405,6 @@ export function MailPage() {
|
||||
[selectedMailIds, toast, t],
|
||||
);
|
||||
|
||||
// Handle save draft (new or existing)
|
||||
const handleSaveDraft = useCallback(
|
||||
async (payload: MailDraftPayload) => {
|
||||
try {
|
||||
if (composeMode === 'draft' && draftMailState) {
|
||||
await updateDraft(draftMailState.id, payload);
|
||||
} else {
|
||||
await saveDraft(payload);
|
||||
}
|
||||
toast.success(t('mail.draftSaved'));
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[composeMode, draftMailState, toast, t],
|
||||
);
|
||||
|
||||
// Handle edit draft
|
||||
const handleEditDraft = useCallback((mail: Mail) => {
|
||||
@@ -348,8 +412,22 @@ export function MailPage() {
|
||||
setDraftMailState(mail);
|
||||
setReplyToMail(null);
|
||||
setForwardMailState(null);
|
||||
setComposeOpen(true);
|
||||
}, []);
|
||||
openWindow({
|
||||
title: t('mail.editDraft'),
|
||||
type: 'mail-compose',
|
||||
component: MailComposeForm,
|
||||
componentProps: {
|
||||
mode: 'draft',
|
||||
accountId: selectedAccountId,
|
||||
replyToMail: null,
|
||||
forwardMail: null,
|
||||
draftMail: mail,
|
||||
signatures,
|
||||
onSend: handleSend,
|
||||
onSaveDraft: handleSaveDraft,
|
||||
},
|
||||
});
|
||||
}, [openWindow, t, selectedAccountId, signatures, handleSend, handleSaveDraft]);
|
||||
|
||||
// Handle toggle flag
|
||||
const handleToggleFlag = useCallback(
|
||||
@@ -486,25 +564,6 @@ export function MailPage() {
|
||||
[toast],
|
||||
);
|
||||
|
||||
// Handle send (compose)
|
||||
const handleSend = useCallback(
|
||||
async (payload: SendMailPayload | ReplyPayload | ForwardPayload, mode: ComposeMode) => {
|
||||
try {
|
||||
if (mode === 'reply' && replyToMail) {
|
||||
await replyMail(replyToMail.id, payload as ReplyPayload);
|
||||
} else if (mode === 'forward' && forwardMailState) {
|
||||
await forwardMail(forwardMailState.id, payload as ForwardPayload);
|
||||
} else {
|
||||
await sendMail(payload as SendMailPayload);
|
||||
}
|
||||
toast.success(t('mail.sent'));
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[replyToMail, forwardMailState, toast, t],
|
||||
);
|
||||
|
||||
// Handle search
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
@@ -632,6 +691,17 @@ export function MailPage() {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'open-standalone',
|
||||
plugin: 'mail',
|
||||
label: 'In neuem Fenster',
|
||||
type: 'button' as const,
|
||||
group: 'tools',
|
||||
icon: (
|
||||
<ExternalLink className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
),
|
||||
onClick: () => window.open('/mail-standalone', '_blank', 'width=1200,height=800'),
|
||||
},
|
||||
];
|
||||
if (hasBulkSelection) {
|
||||
items.push(
|
||||
@@ -935,18 +1005,6 @@ export function MailPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ComposeModal
|
||||
open={composeOpen}
|
||||
mode={composeMode}
|
||||
accountId={selectedAccountId}
|
||||
replyToMail={replyToMail}
|
||||
forwardMail={forwardMailState}
|
||||
draftMail={draftMailState}
|
||||
signatures={signatures}
|
||||
onSend={handleSend}
|
||||
onSaveDraft={handleSaveDraft}
|
||||
onClose={() => setComposeOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user