"use client"; import { useMemo, useState } from "react"; import { Badge, Center, Group, Input, ScrollArea, SimpleGrid, Stack, Text } from "@mantine/core"; import { IconAd, IconApps, IconBinaryTree, IconBookmark, IconBrandDocker, IconBrandMinecraft, IconBrowser, IconBuildingBank, IconBusStop, IconCalendar, IconChartBar, IconClock, IconClockPlay, IconCloud, IconDeviceCctv, IconDeviceGamepad, IconDownload, IconGraphFilled, IconHeartRateMonitor, IconHourglass, IconMessage, IconMovie, IconNotes, IconPuzzle, IconReportSearch, IconRobot, IconRocket, IconRss, IconSearch, IconServer2, IconTicket, IconTopologyFull, IconTransform, IconVideo, IconWall, IconWind, IconZoomQuestion, } from "@tabler/icons-react"; import { getWidgetName } from "@homarr/definitions"; import type { IntegrationKind, WidgetKind } from "@homarr/definitions"; import { useI18n } from "@homarr/translation/client"; import { IntegrationAvatar } from "../components/integration-avatar"; import { SelectableCard } from "../components/selectable-card"; import { selectGridCols } from "../select-grid-layout"; import { buildSortedIntegrations, filterIntegrations } from "./integration-grid-shared"; import type { IntegrationGridItem } from "./integration-grid-shared"; export interface IntegrationSelectGridProps { onSelect: (kind: IntegrationKind) => void; enableMockIntegration?: boolean; allowedKinds?: readonly IntegrationKind[]; integrationData?: { kind: IntegrationKind; name?: string }[]; } export const IntegrationSelectGrid = ({ onSelect, enableMockIntegration = false, allowedKinds, integrationData, }: IntegrationSelectGridProps) => { const [search, setSearch] = useState(""); const t = useI18n(); const integrations = useMemo( () => buildSortedIntegrations({ enableMockIntegration, allowedKinds }), [allowedKinds, enableMockIntegration], ); const filtered = useMemo(() => filterIntegrations(integrations, search), [integrations, search]); return ( {/* Top Search Input */} setSearch(event.currentTarget.value)} leftSection={} placeholder={`${t("integration.page.list.search")}...`} aria-label={t("integration.page.list.search")} data-autofocus onKeyDown={(event) => { if (event.key === "Enter" && filtered.length === 1 && filtered[0]) { onSelect(filtered[0].kind); } }} /> {/* Scrollable Container with Integration Cards */} {filtered.map((integration) => ( i.kind === integration.kind).length} /> ))} {filtered.length === 0 && (
{t("common.noResults")}
)}
); }; // ========================================================================= // IntegrationCard: Shared Modular SelectableCard with Tied Widgets // ========================================================================= const IntegrationCard = ({ integration, onSelect, connectedCount = 0, }: { integration: IntegrationGridItem; onSelect: (kind: IntegrationKind) => void; connectedCount?: number; }) => { const t = useI18n(); return ( onSelect(integration.kind)} aria-label={integration.name} icon={} title={ {integration.name} } footerLeft={ connectedCount > 0 ? ( {t("integration.grid.connected", { count: connectedCount })} ) : null } > {t("integration.grid.tiedWidgets")} ); }; const widgetIconsMap: Partial> = { clock: IconClock, weather: IconCloud, airQuality: IconWind, countdown: IconHourglass, timer: IconClockPlay, app: IconApps, assistant: IconRobot, iframe: IconBrowser, video: IconDeviceCctv, notebook: IconNotes, anchorNote: IconNotes, dnsHoleSummary: IconAd, dnsHoleControls: IconDeviceGamepad, "smartHome-entityState": IconBinaryTree, "smartHome-executeAutomation": IconBinaryTree, stockPrice: IconBuildingBank, mediaServer: IconVideo, calendar: IconCalendar, downloads: IconDownload, "mediaRequests-requestList": IconZoomQuestion, "mediaRequests-requestStats": IconChartBar, mediaTranscoding: IconTransform, mediaMissing: IconMovie, minecraftServerStatus: IconBrandMinecraft, networkControllerSummary: IconTopologyFull, networkControllerStatus: IconTopologyFull, rssFeed: IconRss, bookmarks: IconBookmark, indexerManager: IconReportSearch, healthMonitoring: IconHeartRateMonitor, releases: IconRocket, mediaReleases: IconTicket, dockerContainers: IconBrandDocker, firewall: IconWall, notifications: IconMessage, systemResources: IconGraphFilled, coolify: IconCloud, systemDisks: IconServer2, timetable: IconBusStop, }; const IntegrationTiedWidgets = ({ widgets, limit }: { widgets: WidgetKind[]; limit?: number }) => { const t = useI18n(); if (widgets.length === 0) { return ( {t("integration.grid.noWidgets")} ); } const items = limit ? widgets.slice(0, limit) : widgets; const moreCount = limit ? widgets.length - limit : 0; return ( {items.map((widgetKind) => { const WidgetIcon = widgetIconsMap[widgetKind] ?? IconPuzzle; return ( } style={{ fontWeight: 500 }} > {getWidgetName(widgetKind, t)} ); })} {moreCount > 0 && ( {t("integration.grid.more", { count: moreCount })} )} ); };