diff --git a/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx b/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx index 0def32932..c72c77f65 100644 --- a/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx +++ b/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx @@ -69,7 +69,7 @@ export function CommentPanel({ style={!isMobile && effectiveMaxHeight ? { maxHeight: effectiveMaxHeight } : undefined} > {hasThreads && ( -
+
{threads.map((thread) => ( )} -
+
+ + + + + + Comments + {commentCount > 0 && ( + + {commentCount} + + )} + + +
+ +
+
+ + ); + } + + // Use Sheet for medium screens (right side) return ( - {/* Drag handle indicator - only for bottom sheet */} - {isBottomSheet && ( -
-
-
- )} - + Comments diff --git a/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx index 2b57e5e6e..ebe537869 100644 --- a/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx @@ -21,6 +21,13 @@ import { createPortal } from "react-dom"; import { convertRenderedToDisplay } from "@/components/chat-comments/comment-item/comment-item"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; +import { + Drawer, + DrawerContent, + DrawerHandle, + DrawerHeader, + DrawerTitle, +} from "@/components/ui/drawer"; import { DropdownMenu, DropdownMenuContent, @@ -34,6 +41,7 @@ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { InboxItem } from "@/hooks/use-inbox"; +import { useMediaQuery } from "@/hooks/use-media-query"; import type { ConnectorIndexingMetadata } from "@/contracts/types/inbox.types"; import { cn } from "@/lib/utils"; @@ -56,6 +64,40 @@ function getInitials(name: string | null | undefined, email: string | null | und return "U"; } +/** + * Get display name for connector type + */ +function getConnectorTypeDisplayName(connectorType: string): string { + const displayNames: Record = { + GITHUB_CONNECTOR: "GitHub", + GOOGLE_CALENDAR_CONNECTOR: "Google Calendar", + GOOGLE_GMAIL_CONNECTOR: "Gmail", + GOOGLE_DRIVE_CONNECTOR: "Google Drive", + LINEAR_CONNECTOR: "Linear", + NOTION_CONNECTOR: "Notion", + SLACK_CONNECTOR: "Slack", + TEAMS_CONNECTOR: "Microsoft Teams", + DISCORD_CONNECTOR: "Discord", + JIRA_CONNECTOR: "Jira", + CONFLUENCE_CONNECTOR: "Confluence", + BOOKSTACK_CONNECTOR: "BookStack", + CLICKUP_CONNECTOR: "ClickUp", + AIRTABLE_CONNECTOR: "Airtable", + LUMA_CONNECTOR: "Luma", + ELASTICSEARCH_CONNECTOR: "Elasticsearch", + WEBCRAWLER_CONNECTOR: "Web Crawler", + YOUTUBE_CONNECTOR: "YouTube", + CIRCLEBACK_CONNECTOR: "Circleback", + MCP_CONNECTOR: "MCP", + TAVILY_API: "Tavily", + SEARXNG_API: "SearXNG", + LINKUP_API: "Linkup", + BAIDU_SEARCH_API: "Baidu", + }; + + return displayNames[connectorType] || connectorType.replace(/_/g, " ").replace(/CONNECTOR|API/gi, "").trim(); +} + type InboxTab = "mentions" | "status"; type InboxFilter = "all" | "unread"; @@ -82,14 +124,17 @@ export function InboxSidebar({ }: InboxSidebarProps) { const t = useTranslations("sidebar"); const router = useRouter(); + const isMobile = !useMediaQuery("(min-width: 640px)"); const [searchQuery, setSearchQuery] = useState(""); const [activeTab, setActiveTab] = useState("mentions"); const [activeFilter, setActiveFilter] = useState("all"); const [selectedConnector, setSelectedConnector] = useState(null); const [mounted, setMounted] = useState(false); - // Dropdown state for filter menu + // Dropdown state for filter menu (desktop only) const [openDropdown, setOpenDropdown] = useState<"filter" | null>(null); + // Drawer state for filter menu (mobile only) + const [filterDrawerOpen, setFilterDrawerOpen] = useState(false); const [markingAsReadId, setMarkingAsReadId] = useState(null); useEffect(() => { @@ -138,23 +183,23 @@ export function InboxSidebar({ [inboxItems] ); - // Get unique connectors from status items for filtering - const uniqueConnectors = useMemo(() => { - const connectorMap = new Map(); + // Get unique connector types from status items for filtering + const uniqueConnectorTypes = useMemo(() => { + const connectorTypes = new Set(); statusItems .filter((item) => item.type === "connector_indexing") .forEach((item) => { const metadata = item.metadata as ConnectorIndexingMetadata; - if (metadata?.connector_type && !connectorMap.has(metadata.connector_type)) { - connectorMap.set(metadata.connector_type, { - type: metadata.connector_type, - name: metadata.connector_name || metadata.connector_type, - }); + if (metadata?.connector_type) { + connectorTypes.add(metadata.connector_type); } }); - return Array.from(connectorMap.values()); + return Array.from(connectorTypes).map((type) => ({ + type, + displayName: getConnectorTypeDisplayName(type), + })); }, [statusItems]); // Get items for current tab @@ -358,53 +403,205 @@ export function InboxSidebar({

{t("inbox") || "Inbox"}

- setOpenDropdown(isOpen ? "filter" : null)} - > - - - + {/* Mobile: Button that opens bottom drawer */} + {isMobile ? ( + <> + + - - - - {t("filter") || "Filter"} - - - - - {t("filter") || "Filter"} - - setActiveFilter("all")} - className="flex items-center justify-between" - > - - - {t("all") || "All"} - - {activeFilter === "all" && } - - setActiveFilter("unread")} - className="flex items-center justify-between" - > - - - {t("unread") || "Unread"} - - {activeFilter === "unread" && } - - - + + + {t("filter") || "Filter"} + + + + + + + + + {t("filter") || "Filter"} + + +
+ {/* Filter section */} +
+

+ {t("filter") || "Filter"} +

+
+ + +
+
+ {/* Connectors section - only for status tab */} + {activeTab === "status" && uniqueConnectorTypes.length > 0 && ( +
+

+ {t("connectors") || "Connectors"} +

+
+ + {uniqueConnectorTypes.map((connector) => ( + + ))} +
+
+ )} +
+
+
+ + ) : ( + /* Desktop: Dropdown menu */ + setOpenDropdown(isOpen ? "filter" : null)} + > + + + + + + + + {t("filter") || "Filter"} + + + + + {t("filter") || "Filter"} + + setActiveFilter("all")} + className="flex items-center justify-between" + > + + + {t("all") || "All"} + + {activeFilter === "all" && } + + setActiveFilter("unread")} + className="flex items-center justify-between" + > + + + {t("unread") || "Unread"} + + {activeFilter === "unread" && } + + {activeTab === "status" && uniqueConnectorTypes.length > 0 && ( + <> + + {t("connectors") || "Connectors"} + + setSelectedConnector(null)} + className="flex items-center justify-between" + > + {t("all_connectors") || "All connectors"} + {selectedConnector === null && } + + {uniqueConnectorTypes.map((connector) => ( + setSelectedConnector(connector.type)} + className="flex items-center justify-between" + > + + {getConnectorIcon(connector.type, "h-4 w-4")} + {connector.displayName} + + {selectedConnector === connector.type && } + + ))} + + )} + + + )} - {uniqueConnectors.map((connector) => ( - - - - - - {connector.name} - - - ))} -
-
- )} -
{loading ? (
diff --git a/surfsense_web/components/ui/drawer.tsx b/surfsense_web/components/ui/drawer.tsx new file mode 100644 index 000000000..81733487d --- /dev/null +++ b/surfsense_web/components/ui/drawer.tsx @@ -0,0 +1,140 @@ +"use client"; + +import * as React from "react"; +import { Drawer as DrawerPrimitive } from "vaul"; + +import { cn } from "@/lib/utils"; + +function Drawer({ + shouldScaleBackground = true, + ...props +}: React.ComponentProps) { + return ( + + ); +} +Drawer.displayName = "Drawer"; + +const DrawerTrigger = DrawerPrimitive.Trigger; + +const DrawerPortal = DrawerPrimitive.Portal; + +const DrawerClose = DrawerPrimitive.Close; + +function DrawerOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName; + +function DrawerContent({ + className, + children, + overlayClassName, + ...props +}: React.ComponentProps & { + overlayClassName?: string; +}) { + return ( + + + + {children} + + + ); +} +DrawerContent.displayName = "DrawerContent"; + +function DrawerHeader({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ); +} +DrawerHeader.displayName = "DrawerHeader"; + +function DrawerFooter({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ); +} +DrawerFooter.displayName = "DrawerFooter"; + +function DrawerTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} +DrawerTitle.displayName = DrawerPrimitive.Title.displayName; + +function DrawerDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} +DrawerDescription.displayName = DrawerPrimitive.Description.displayName; + +function DrawerHandle({ className, ...props }: React.HTMLAttributes) { + return ( +
+ ); +} +DrawerHandle.displayName = "DrawerHandle"; + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, + DrawerHandle, +}; + diff --git a/surfsense_web/components/ui/sheet.tsx b/surfsense_web/components/ui/sheet.tsx index accd4f782..650e85403 100644 --- a/surfsense_web/components/ui/sheet.tsx +++ b/surfsense_web/components/ui/sheet.tsx @@ -42,13 +42,15 @@ function SheetContent({ className, children, side = "right", + overlayClassName, ...props }: React.ComponentProps & { side?: "top" | "right" | "bottom" | "left"; + overlayClassName?: string; }) { return ( - +