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:
Agent Zero
2026-07-25 00:21:37 +02:00
parent 2f6c3175a3
commit 868bb274ef
12 changed files with 1220 additions and 124 deletions
+79 -50
View File
@@ -20,12 +20,13 @@ import { DayView } from '@/components/calendar/DayView';
import { RangeView } from '@/components/calendar/RangeView';
import { formatDateInput } from '@/utils/date';
import { CalendarDetail } from '@/components/calendar/CalendarDetail';
import { AppointmentModal } from '@/components/calendar/AppointmentModal';
import { AppointmentEditForm } from '@/components/calendar/AppointmentEditForm';
import { useWindowStore } from '@/store/windowStore';
import { IcsControls } from '@/components/calendar/IcsControls';
import { SharingSettings } from '@/components/calendar/SharingSettings';
import { useCalendarStore, type CalendarViewMode } from '@/store/calendarStore';
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
import { ChevronLeft, ChevronRight, Info, Plus } from 'lucide-react';
import { ChevronLeft, ChevronRight, ExternalLink, Info, Plus } from 'lucide-react';
import {
fetchCalendars,
createCalendar,
@@ -72,9 +73,7 @@ export function CalendarPage() {
const [loadingCalendars, setLoadingCalendars] = useState(true);
const [error, setError] = useState<string | null>(null);
const [modalOpen, setModalOpen] = useState(false);
const [modalEntry, setModalEntry] = useState<CalendarEntry | null>(null);
const [prefillDate, setPrefillDate] = useState<Date | null>(null);
const openWindow = useWindowStore((s) => s.openWindow);
const [showSharing, setShowSharing] = useState(false);
const [showIcs, setShowIcs] = useState(false);
@@ -176,11 +175,44 @@ export function CalendarPage() {
// ??? Handlers ???????????????????????????????????????????????????????????
const handleSaved = useCallback(
(saved: CalendarEntry) => {
setEntries((cur) => {
const exists = cur.some((e) => e.id === saved.id);
return exists ? cur.map((e) => (e.id === saved.id ? saved : e)) : [...cur, saved];
});
if (selectedEntry?.id === saved.id) {
setSelectedEntry(saved);
}
},
[selectedEntry, setSelectedEntry],
);
const handleDeleted = useCallback(
(entryId: string) => {
setEntries((cur) => cur.filter((e) => e.id !== entryId));
if (selectedEntry?.id === entryId) {
setSelectedEntry(null);
}
},
[selectedEntry, setSelectedEntry],
);
const handleCreateAt = useCallback((date: Date) => {
setModalEntry(null);
setPrefillDate(date);
setModalOpen(true);
}, []);
openWindow({
title: t('calendar.newAppointment'),
type: 'appointment-create',
component: AppointmentEditForm,
componentProps: {
entry: null,
prefillDate: date,
calendars,
defaultCalendarId: activeCalendarId,
onSaved: handleSaved,
onDeleted: handleDeleted,
},
});
}, [openWindow, t, calendars, activeCalendarId, handleSaved, handleDeleted]);
const handleEditEntry = useCallback(
(entry: CalendarEntry) => {
@@ -194,10 +226,20 @@ export function CalendarPage() {
const handleOpenEditModal = useCallback(() => {
if (!selectedEntry) return;
setModalEntry(selectedEntry);
setPrefillDate(null);
setModalOpen(true);
}, [selectedEntry]);
openWindow({
title: t('calendar.editAppointment'),
type: 'appointment-edit',
component: AppointmentEditForm,
componentProps: {
entry: selectedEntry,
prefillDate: null,
calendars,
defaultCalendarId: activeCalendarId,
onSaved: handleSaved,
onDeleted: handleDeleted,
},
});
}, [selectedEntry, openWindow, t, calendars, activeCalendarId, handleSaved, handleDeleted]);
const handleMoveEntry = useCallback(
async (entry: CalendarEntry, newStart: Date) => {
@@ -223,29 +265,6 @@ export function CalendarPage() {
[t, loadEntries, selectedEntry, setSelectedEntry],
);
const handleSaved = useCallback(
(saved: CalendarEntry) => {
setEntries((cur) => {
const exists = cur.some((e) => e.id === saved.id);
return exists ? cur.map((e) => (e.id === saved.id ? saved : e)) : [...cur, saved];
});
if (selectedEntry?.id === saved.id) {
setSelectedEntry(saved);
}
},
[selectedEntry, setSelectedEntry],
);
const handleDeleted = useCallback(
(entryId: string) => {
setEntries((cur) => cur.filter((e) => e.id !== entryId));
if (selectedEntry?.id === entryId) {
setSelectedEntry(null);
}
},
[selectedEntry, setSelectedEntry],
);
const handleConfirmDelete = useCallback(async () => {
if (!deleteTarget) return;
try {
@@ -367,9 +386,19 @@ export function CalendarPage() {
label: t('calendar.newAppointment'),
group: 'actions',
onClick: () => {
setModalEntry(null);
setPrefillDate(null);
setModalOpen(true);
openWindow({
title: t('calendar.newAppointment'),
type: 'appointment-create',
component: AppointmentEditForm,
componentProps: {
entry: null,
prefillDate: null,
calendars,
defaultCalendarId: activeCalendarId,
onSaved: handleSaved,
onDeleted: handleDeleted,
},
});
},
icon: (
<Plus className="w-3.5 h-3.5" strokeWidth={2} />
@@ -401,6 +430,17 @@ export function CalendarPage() {
<Info className="w-3.5 h-3.5" strokeWidth={2} />
),
},
{
id: 'open-standalone',
plugin: 'calendar',
label: 'In neuem Fenster',
type: 'button' as const,
group: 'actions',
icon: (
<ExternalLink className="w-3.5 h-3.5" strokeWidth={2} />
),
onClick: () => window.open('/calendar-standalone', '_blank', 'width=1200,height=800'),
},
];
registerItems('calendar', items);
@@ -685,17 +725,6 @@ export function CalendarPage() {
onCancel={() => setDeleteTarget(null)}
/>
{/* Appointment modal */}
<AppointmentModal
open={modalOpen}
onClose={() => setModalOpen(false)}
entry={modalEntry}
prefillDate={prefillDate}
calendars={calendars}
defaultCalendarId={activeCalendarId}
onSaved={handleSaved}
onDeleted={handleDeleted}
/>
</div>
);
}