feat: P2.2 Create Appointment from Email + P2.4 Calendar Dashlet + P2.12 Action Wheel + P2.14 Share Files

- P2.2: 'Create Appointment' button in email viewer → pre-fills event modal
  with subject, body, participants, date. calendar-store newEventPrefill state.
- P2.4: MiniCalendarDashlet in sidebar bottom — month grid with event dots,
  day click navigates to calendar. Collapsible, respect firstDayOfWeek.
- P2.12: Custom radial menu (components/ui/radial-menu.tsx) — circular SVG
  menu with keyboard nav, animations. Wired into email-list, contact-list,
  file-browser, calendar-month-view right-click handlers.
- P2.14: 'Send as Attachment' button in file browser — opens compose tab
  with selected files pre-attached via Pro tab store.
This commit is contained in:
Bernd Rodler
2026-08-07 13:15:04 +02:00
parent 67f61f18d0
commit 83e29b3ef1
14 changed files with 933 additions and 42 deletions
+28
View File
@@ -4,6 +4,7 @@ import { useState, useEffect, useMemo, ReactNode } from "react";
import { useTranslations } from "next-intl";
import { useRouter } from "@/i18n/navigation";
import { PluginSlot } from "@/components/plugins/plugin-slot";
import { MiniCalendarDashlet } from "@/components/calendar/mini-calendar-dashlet";
import { Button } from "@/components/ui/button";
import {
Inbox,
@@ -62,6 +63,7 @@ import { useUIStore } from "@/stores/ui-store";
import { useAuthStore } from "@/stores/auth-store";
import { useVacationStore } from "@/stores/vacation-store";
import { useSettingsStore, getKeywordVisibility } from "@/stores/settings-store";
import { usePolicyStore } from "@/stores/policy-store";
import { useEmailStore } from "@/stores/email-store";
import { toast } from "@/stores/toast-store";
import { debug } from "@/lib/debug";
@@ -808,6 +810,12 @@ export function Sidebar({
return stored !== null ? JSON.parse(stored) : true;
} catch { return true; }
});
const [calendarDashletExpanded, setCalendarDashletExpanded] = useState(() => {
try {
const stored = localStorage.getItem('sidebarCalendarDashletExpanded');
return stored !== null ? JSON.parse(stored) : true;
} catch { return true; }
});
const [unifiedExpanded, setUnifiedExpanded] = useState(() => {
try {
const stored = localStorage.getItem('sidebarUnifiedExpanded');
@@ -840,6 +848,7 @@ export function Sidebar({
const emailKeywords = useSettingsStore(s => s.emailKeywords);
const nestedTags = useSettingsStore(s => s.nestedTags);
const isEmbedded = useIsEmbedded();
const calendarEnabled = usePolicyStore((s) => s.isFeatureEnabled('calendarEnabled'));
// The Pro shell owns the global chrome (rail + tab bar), so the sidebar's
// own AccountSwitcher would be a redundant second account UI in the same
// pane.
@@ -1054,6 +1063,13 @@ export function Sidebar({
return next;
});
};
const toggleCalendarDashlet = () => {
setCalendarDashletExpanded((prev: boolean) => {
const next = !prev;
try { localStorage.setItem('sidebarCalendarDashletExpanded', JSON.stringify(next)); } catch { /* */ }
return next;
});
};
const toggleShared = () => {
setSharedExpanded((prev: boolean) => {
const next = !prev;
@@ -1405,6 +1421,18 @@ export function Sidebar({
</div>
)}
{!isCollapsed && calendarEnabled && (
<div>
<SidebarSectionHeader
label={t("calendar")}
expanded={calendarDashletExpanded}
onToggle={toggleCalendarDashlet}
isCollapsed={isCollapsed}
/>
{calendarDashletExpanded && <MiniCalendarDashlet />}
</div>
)}
{!isCollapsed && <PluginSlot name="sidebar-widget" className="border-t border-border" />}
</div>