"use client"; import { Badge, Card, Collapse, Group, Stack, Title, UnstyledButton } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; import { IconChevronDown, IconChevronUp } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context"; import type { Board, CategorySection, DynamicSectionItem, EmptySection, SectionItem, } from "~/app/[locale]/boards/_types"; import { BoardItemContent } from "../items/item-content"; import { CategoryMenu } from "./category/category-menu"; import classes from "./item.module.css"; interface StaticBoardGridProps { board: Board; } export const StaticBoardGrid = ({ board }: StaticBoardGridProps) => { const currentLayoutId = useCurrentLayout(); const currentLayout = board.layouts.find((layout) => layout.id === currentLayoutId); const columnCount = currentLayout?.columnCount ?? 12; const fullWidthSortedSections = board.sections .filter( (section): section is CategorySection | EmptySection => section.kind === "empty" || section.kind === "category", ) .sort((sectionA, sectionB) => sectionA.yOffset - sectionB.yOffset); return ( {fullWidthSortedSections.map((section) => section.kind === "empty" ? ( ) : ( ), )} ); }; interface StaticSectionGridProps { sectionId: string; board: Board; layoutId: string; columnCount: number; isDynamic?: boolean; fixedRowCount?: number; } const StaticSectionGrid = ({ sectionId, board, layoutId, columnCount, isDynamic, fixedRowCount, }: StaticSectionGridProps) => { const items = getSectionItems(board, sectionId, layoutId); const innerSections = getDynamicSections(board, sectionId, layoutId); const allItems = [...items, ...innerSections].sort((itemA, itemB) => { if (itemA.yOffset === itemB.yOffset) return itemA.xOffset - itemB.xOffset; return itemA.yOffset - itemB.yOffset; }); if (allItems.length === 0) return null; const rowCount = fixedRowCount ?? Math.max(...allItems.map((item) => item.yOffset + item.height)); return (
{allItems.map((item) => ( ))}
); }; interface StaticGridItemProps { item: SectionItem | DynamicSectionItem; board: Board; layoutId: string; } const StaticGridItem = ({ item, board, layoutId }: StaticGridItemProps) => (
{item.type === "item" ? ( ) : ( )}
); interface StaticEmptySectionProps { section: EmptySection; board: Board; layoutId: string; columnCount: number; } const StaticEmptySection = ({ section, board, layoutId, columnCount }: StaticEmptySectionProps) => { const items = getSectionItems(board, section.id, layoutId); const innerSections = getDynamicSections(board, section.id, layoutId); if (items.length === 0 && innerSections.length === 0) return null; return ; }; interface StaticCategorySectionProps { section: CategorySection; board: Board; layoutId: string; columnCount: number; } const StaticCategorySection = ({ section, board, layoutId, columnCount }: StaticCategorySectionProps) => { const { mutate } = clientApi.section.changeCollapsed.useMutation(); const boardData = useRequiredBoard(); const [opened, { toggle }] = useDisclosure(section.collapsed); const handleToggle = () => { toggle(); mutate({ sectionId: section.id, collapsed: !opened }); }; return ( {opened ? : } {section.name} ); }; interface StaticDynamicSectionProps { section: DynamicSectionItem; board: Board; layoutId: string; } const StaticDynamicSection = ({ section, board, layoutId }: StaticDynamicSectionProps) => { const boardData = useRequiredBoard(); const options = section.options; return ( {options.title && ( {options.title} )} ); }; function getSectionItems(board: Board, sectionId: string, layoutId: string): SectionItem[] { return board.items .map(({ layouts, ...item }) => { const layout = layouts.find((candidate) => candidate.layoutId === layoutId); if (!layout) return null; return { ...layout, ...item, type: "item" as const }; }) .filter((item): item is SectionItem => item !== null && item.sectionId === sectionId); } function getDynamicSections(board: Board, sectionId: string, layoutId: string): DynamicSectionItem[] { return board.sections .filter((section) => section.kind === "dynamic") .map(({ layouts, ...section }) => { const layout = layouts.find((candidate) => candidate.layoutId === layoutId); if (!layout) return null; return { ...layout, ...section, type: "section" as const }; }) .filter((entry): entry is DynamicSectionItem => entry !== null && entry.parentSectionId === sectionId); }