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
+27
View File
@@ -11,6 +11,7 @@ import { useAuthStore, redirectToLogin } from "@/stores/auth-store";
import { useAccountStore } from "@/stores/account-store";
import { useEmailStore } from "@/stores/email-store";
import { useFileStore } from "@/stores/file-store";
import { useProTabStore } from "@/stores/pro-tab-store";
import { toast } from "@/stores/toast-store";
import { cn, formatFileSize } from "@/lib/utils";
import { NavigationRail } from "@/components/layout/navigation-rail";
@@ -411,6 +412,31 @@ export default function FilesPage() {
await shareResource(id, principalId, rights);
}, [shareResource]);
const handleSendAsAttachment = useCallback((names: string[]) => {
const store = useFileStore.getState();
const fileAtts = names
.map((name) => {
const r = store.resources.find((res) => res.name === name);
if (!r || r.isDirectory || !r.blobId) return null;
return {
blobId: r.blobId,
name: r.name,
type: r.contentType || "application/octet-stream",
size: r.contentLength,
};
})
.filter(Boolean) as Array<{ blobId: string; name: string; type: string; size: number }>;
if (fileAtts.length === 0) return;
useProTabStore.getState().openComposeTab({
sessionId: Date.now(),
mode: "compose",
replyTo: { attachments: fileAtts },
title: fileAtts.length === 1 ? fileAtts[0].name : `${fileAtts.length} attachments`,
});
}, []);
// Pro shell only: all connected accounts are equal top-level entries at
// the root. The root path "/" itself is a cross-account picker - no
// account's files are shown until the user enters one.
@@ -554,6 +580,7 @@ export default function FilesPage() {
ownAccountId={filesAccountId}
sharingEnabled={sharingEnabled}
onShare={handleShare}
onSendAsAttachment={handleSendAsAttachment}
/>
</div>
)}