UNPKG

@seplan/diti-ds

Version:

Reusable UI component library developed by DITI (Technology and Innovation Directorate of SEPLAN PI) based on Mantine and Tailwind CSS

1 lines 154 kB
{"version":3,"file":"async-filter-popover-BLNo67V4.cjs","sources":["../src/components/ui/axis-card.tsx","../src/components/ui/client-wrapper.tsx","../src/components/ui/breadcrumbs.tsx","../src/components/ui/card.tsx","../src/components/ui/counselor-status-badge.tsx","../src/components/ui/data-list.tsx","../src/components/ui/empty-state.tsx","../src/lib/dayjs.ts","../src/components/ui/form-drawer.tsx","../src/components/ui/navigation-tabs.tsx","../src/components/ui/search-input.tsx","../src/components/ui/seplan-apps-bar.tsx","../src/constants/pagination.ts","../src/components/ui/sortable-column-header/sort-icon.tsx","../src/components/ui/async-filter/selected-items-provider.tsx","../src/components/ui/async-filter/content.tsx","../src/components/ui/async-filter/index.tsx","../src/components/ui/filters/components/filters-button.tsx","../src/components/ui/filters/components/filters-modal.tsx","../src/components/ui/filters/components/filter-group-content.tsx","../src/components/ui/filters/components/filter-group.tsx","../src/components/ui/filters/filter-manager.tsx","../src/components/ui/filters/components/base-content.tsx","../src/components/ui/filters/components/filter-button.tsx","../src/components/ui/filters/components/async-filter-group.tsx","../src/components/ui/filters/components/async-filter-popover.tsx","../src/components/ui/back-button.tsx","../src/components/ui/clear-filters-button.tsx","../src/components/ui/pagination-footer/controlled-pagination-footer.tsx","../src/components/ui/date-range-filter.tsx","../src/components/ui/filters/filter-popover.tsx","../src/components/ui/image-placeholder.tsx","../src/components/ui/label.tsx","../src/components/ui/logo/logo.tsx","../src/components/ui/logo/logo-horizontal.tsx","../src/components/ui/optional-input-label.tsx","../src/components/ui/radio-card.tsx","../src/components/ui/sortable-column-header/index.tsx","../src/components/ui/filters/components/static-filter-group.tsx","../src/components/ui/filters/components/static-filter-popover.tsx","../src/components/ui/pagination-footer/url-pagination-footer.tsx","../src/components/ui/value-range-filter.tsx"],"sourcesContent":["import React from 'react'\r\n\r\ninterface AxisCardProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction AxisCard({ children, className = '' }: AxisCardProps) {\r\n return (\r\n <div className={`rounded-xl border bg-white p-6 shadow-sm ${className}`}>\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\ninterface CardHeaderProps {\r\n children: React.ReactNode\r\n className?: string\r\n linkUrl?: string\r\n onLinkClick?: (e: React.MouseEvent<HTMLButtonElement>) => void\r\n}\r\n\r\nfunction CardHeader({\r\n children,\r\n className = '',\r\n linkUrl = '',\r\n onLinkClick\r\n}: CardHeaderProps) {\r\n function handleClick(e: React.MouseEvent<HTMLButtonElement>) {\r\n if (onLinkClick) {\r\n onLinkClick(e)\r\n }\r\n\r\n if (linkUrl && !e.defaultPrevented) {\r\n window.open(linkUrl, '_blank')\r\n }\r\n }\r\n\r\n return (\r\n <header className={`mb-6 flex items-center gap-2 ${className}`}>\r\n {children}\r\n {(linkUrl || onLinkClick) && (\r\n <button\r\n onClick={handleClick}\r\n className=\"ml-auto rounded-full p-2 hover:bg-gray-100\"\r\n >\r\n <svg\r\n className=\"size-5\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n >\r\n <path d=\"M7 17L17 7M7 7h10v10\"></path>\r\n </svg>\r\n </button>\r\n )}\r\n </header>\r\n )\r\n}\r\nAxisCard.Header = CardHeader\r\n\r\ninterface CardTitleProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction CardTitle({ children, className = '' }: CardTitleProps) {\r\n return <h3 className={`text-md font-semibold ${className}`}>{children}</h3>\r\n}\r\nAxisCard.Title = CardTitle\r\n\r\ninterface CardBodyProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction CardBody({ children, className = '' }: CardBodyProps) {\r\n return (\r\n <div className={`flex justify-between space-x-2 ${className}`}>\r\n {children}\r\n </div>\r\n )\r\n}\r\nAxisCard.Body = CardBody\r\n\r\ninterface CardMetricProps {\r\n label: string\r\n value: string | number\r\n className?: string\r\n}\r\n\r\nfunction CardMetric({ label, value, className = '' }: CardMetricProps) {\r\n return (\r\n <div className={`flex flex-col ${className}`}>\r\n <span className=\"text-xs\">{label}</span>\r\n <span className=\"text-3xl font-semibold\">{value}</span>\r\n </div>\r\n )\r\n}\r\nAxisCard.Metric = CardMetric\r\n\r\ninterface CardBadgeGroupProps {\r\n label?: string\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction CardBadgeGroup({\r\n label,\r\n children,\r\n className = ''\r\n}: CardBadgeGroupProps) {\r\n return (\r\n <div className={`flex flex-col space-y-2 ${className}`}>\r\n {label && <span className=\"text-xs\">{label}</span>}\r\n {children}\r\n </div>\r\n )\r\n}\r\nAxisCard.BadgeGroup = CardBadgeGroup\r\n\r\nexport { AxisCard }\r\n","'use client'\r\n\r\nimport { useEffect, useState } from 'react'\r\n\r\ninterface ClientWrapperProps {\r\n children: React.ReactNode\r\n fallback?: React.ReactNode\r\n}\r\n\r\n/**\r\n * Wrapper para componentes que usam hooks React\r\n * Resolve problemas de SSR garantindo que o componente só renderize no cliente\r\n */\r\nexport function ClientWrapper({ children, fallback = null }: ClientWrapperProps) {\r\n const [isClient, setIsClient] = useState(false)\r\n\r\n useEffect(() => {\r\n setIsClient(true)\r\n }, [])\r\n\r\n if (!isClient) {\r\n return <>{fallback}</>\r\n }\r\n\r\n return <>{children}</>\r\n}\r\n\r\n/**\r\n * Hook para detectar se estamos no cliente\r\n */\r\nexport function useIsClient() {\r\n const [isClient, setIsClient] = useState(false)\r\n\r\n useEffect(() => {\r\n setIsClient(true)\r\n }, [])\r\n\r\n return isClient\r\n}\r\n","'use client'\r\n\r\nimport {\r\n ActionIcon,\r\n Anchor,\r\n Breadcrumbs as MantineBreadcrumbs,\r\n Menu\r\n} from '@mantine/core'\r\nimport { MoreHorizontal } from 'lucide-react'\r\nimport Link from 'next/link'\r\nimport type React from 'react'\r\nimport { memo, useMemo } from 'react'\r\nimport { ClientWrapper } from './client-wrapper'\r\n\r\nexport interface BreadcrumbItem {\r\n label: string\r\n href?: string\r\n}\r\n\r\ninterface BreadcrumbsProps {\r\n items: BreadcrumbItem[]\r\n separator?: React.ReactNode\r\n maxVisibleItems?: number\r\n maxItemWidth?: string\r\n}\r\n\r\ninterface BreadcrumbItemProps {\r\n item: BreadcrumbItem\r\n isLastItem: boolean\r\n maxWidth?: string\r\n}\r\n\r\nconst BreadcrumbItemComponent: React.FC<BreadcrumbItemProps> = memo(\r\n ({ item, isLastItem, maxWidth = '160px' }) => {\r\n const { href, label } = item\r\n\r\n const itemClasses = `\r\n inline-flex items-center text-xs transition-colors duration-200 \r\n ${isLastItem ? 'font-semibold text-content-emphasis' : 'text-content-muted hover:text-content cursor-pointer'}\r\n `\r\n\r\n const content = (\r\n <span className=\"truncate\" style={{ maxWidth }} title={label}>\r\n {label}\r\n </span>\r\n )\r\n\r\n return href ? (\r\n <Anchor\r\n unstyled\r\n component={Link}\r\n href={href}\r\n className={itemClasses}\r\n underline=\"never\"\r\n >\r\n {content}\r\n </Anchor>\r\n ) : (\r\n <span className={itemClasses}>{content}</span>\r\n )\r\n }\r\n)\r\n\r\nBreadcrumbItemComponent.displayName = 'BreadcrumbItem'\r\n\r\ninterface DropdownMenuProps {\r\n items: BreadcrumbItem[]\r\n maxItemWidth?: string\r\n}\r\n\r\nconst DropdownMenu: React.FC<DropdownMenuProps> = memo(\r\n ({ items, maxItemWidth }) => (\r\n <Menu shadow=\"md\" width={200} position=\"bottom-start\">\r\n <Menu.Target>\r\n <ActionIcon\r\n variant=\"white\"\r\n color=\"gray.4\"\r\n size=\"sm\"\r\n aria-label=\"Show more breadcrumb items\"\r\n className=\"mx-1\"\r\n >\r\n <MoreHorizontal className=\"size-4\" />\r\n </ActionIcon>\r\n </Menu.Target>\r\n\r\n <Menu.Dropdown>\r\n {items.map((item, index) => {\r\n const MenuItemContent = (\r\n <span\r\n className=\"truncate\"\r\n style={{ maxWidth: maxItemWidth }}\r\n title={item.label}\r\n >\r\n {item.label}\r\n </span>\r\n )\r\n\r\n return item.href ? (\r\n <Menu.Item key={index} className=\"text-xs\">\r\n <Link href={item.href} className=\"block w-full\">\r\n {MenuItemContent}\r\n </Link>\r\n </Menu.Item>\r\n ) : (\r\n <Menu.Item key={index} className=\"text-xs\">\r\n {MenuItemContent}\r\n </Menu.Item>\r\n )\r\n })}\r\n </Menu.Dropdown>\r\n </Menu>\r\n )\r\n)\r\n\r\nDropdownMenu.displayName = 'DropdownMenu'\r\n\r\nconst BreadcrumbsComponent: React.FC<BreadcrumbsProps> = memo(\r\n ({\r\n items,\r\n separator = <span className=\"text-content-muted\">/</span>,\r\n maxVisibleItems = 4,\r\n maxItemWidth = '180px'\r\n }) => {\r\n const { visibleItems, hiddenItems } = useMemo(() => {\r\n if (items.length <= maxVisibleItems) {\r\n return { visibleItems: items, hiddenItems: [] }\r\n }\r\n\r\n // Always show first item, last item, and some middle items\r\n const firstItem = items[0]\r\n const lastItem = items[items.length - 1]\r\n const middleItems = items.slice(1, -1)\r\n\r\n // Calculate how many middle items we can show\r\n const availableSlots = maxVisibleItems - 2 // Reserve slots for first and last\r\n const visibleMiddleCount = Math.max(0, availableSlots - 1) // Reserve 1 slot for dropdown\r\n\r\n const visibleMiddleItems = middleItems.slice(-visibleMiddleCount)\r\n const hiddenMiddleItems = middleItems.slice(0, -visibleMiddleCount)\r\n\r\n return {\r\n visibleItems: [firstItem, ...visibleMiddleItems, lastItem],\r\n hiddenItems: hiddenMiddleItems\r\n }\r\n }, [items, maxVisibleItems])\r\n\r\n const breadcrumbs = useMemo(() => {\r\n const elements: React.ReactNode[] = []\r\n\r\n visibleItems.forEach((item, index) => {\r\n const isLastItem = index === visibleItems.length - 1\r\n\r\n // Add dropdown before the last visible item if there are hidden items\r\n if (hiddenItems.length > 0 && index === visibleItems.length - 1) {\r\n elements.push(\r\n <DropdownMenu\r\n key=\"dropdown\"\r\n items={hiddenItems}\r\n maxItemWidth={maxItemWidth}\r\n />\r\n )\r\n }\r\n\r\n elements.push(\r\n <BreadcrumbItemComponent\r\n key={`item-${index}`}\r\n item={item}\r\n isLastItem={isLastItem}\r\n maxWidth={maxItemWidth}\r\n />\r\n )\r\n })\r\n\r\n return elements\r\n }, [visibleItems, hiddenItems, maxItemWidth])\r\n\r\n return (\r\n <MantineBreadcrumbs\r\n separator={separator}\r\n separatorMargin=\"xs\"\r\n className=\"flex items-center\"\r\n >\r\n {breadcrumbs}\r\n </MantineBreadcrumbs>\r\n )\r\n }\r\n)\r\n\r\nBreadcrumbsComponent.displayName = 'Breadcrumbs'\r\n\r\nexport const Breadcrumbs: React.FC<BreadcrumbsProps> = memo((props) => {\r\n return (\r\n <ClientWrapper\r\n fallback={\r\n <div className=\"flex items-center opacity-50\">\r\n {props.items.slice(0, 3).map((item, index) => (\r\n <span key={index} className=\"text-xs text-content-muted\">\r\n {item.label}\r\n {index < props.items.length - 1 && (\r\n <span className=\"mx-2 text-content-muted\">/</span>\r\n )}\r\n </span>\r\n ))}\r\n {props.items.length > 3 && (\r\n <span className=\"text-xs text-content-muted\">...</span>\r\n )}\r\n </div>\r\n }\r\n >\r\n <BreadcrumbsComponent {...props} />\r\n </ClientWrapper>\r\n )\r\n})\r\n","import React from 'react'\r\nimport { Badge } from '@mantine/core'\r\nimport { cn } from '../../utils/core/cn'\r\nimport { TrendingDown, TrendingUp } from 'lucide-react'\r\n\r\n// Base Card Root component\r\ninterface CardRootProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction CardRoot({\r\n children,\r\n className,\r\n ...props\r\n}: CardRootProps & React.HTMLAttributes<HTMLDivElement>) {\r\n const baseStyles =\r\n 'bg-white rounded-xl p-4 overflow-hidden border flex-1 shadow-1'\r\n\r\n return (\r\n <div className={cn(baseStyles, className)} {...props}>\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// Card Header\r\ninterface CardHeaderProps {\r\n children: React.ReactNode\r\n className?: string\r\n orientation?: 'horizontal' | 'vertical'\r\n}\r\n\r\nfunction CardHeader({\r\n children,\r\n className,\r\n orientation = 'horizontal'\r\n}: CardHeaderProps) {\r\n if (orientation === 'vertical') {\r\n return (\r\n <div className={cn('mb-2 flex flex-col gap-1', className)}>\r\n {children}\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div\r\n className={cn('mb-2 flex items-center justify-between gap-2', className)}\r\n >\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// Card Title\r\ninterface CardTitleProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction CardTitle({ children, className }: CardTitleProps) {\r\n return <h3 className={cn('text-xs font-semibold', className)}>{children}</h3>\r\n}\r\n\r\n// Card Value Display\r\ninterface CardValueProps {\r\n children: React.ReactNode\r\n /** Unit that appears before the value */\r\n prefix?: string\r\n /** Unit that appears after the value */\r\n suffix?: string\r\n className?: string\r\n}\r\n\r\nfunction CardValue({ children, prefix, suffix, className }: CardValueProps) {\r\n return (\r\n <div\r\n className={cn('text-2xl font-semibold text-content-emphasis', className)}\r\n >\r\n {prefix && <span className=\"text-content-muted\">{prefix}</span>}\r\n {children}\r\n {suffix && <span className=\"text-content-muted\">{suffix}</span>}\r\n </div>\r\n )\r\n}\r\n\r\n// Card Description\r\ninterface CardDescriptionProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction CardDescription({ children, className }: CardDescriptionProps) {\r\n return (\r\n <p className={cn('text-xs text-content-muted', className)}>{children}</p>\r\n )\r\n}\r\n\r\n// Card Subtitle\r\ninterface CardSubtitleProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction CardSubtitle({ children, className }: CardSubtitleProps) {\r\n return (\r\n <p className={cn('text-sm text-content-muted', className)}>{children}</p>\r\n )\r\n}\r\n\r\ninterface CardContentProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction CardContent({ children, className }: CardContentProps) {\r\n return (\r\n <div className={cn('flex flex-1 items-end gap-2', className)}>\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// Card Footer for actions, additional info\r\ninterface CardFooterProps {\r\n children: React.ReactNode\r\n className?: string\r\n orientation?: 'horizontal' | 'vertical'\r\n}\r\n\r\nfunction CardFooter({\r\n children,\r\n className,\r\n orientation = 'horizontal'\r\n}: CardFooterProps) {\r\n if (orientation === 'vertical') {\r\n return (\r\n <div className={cn('mt-2', className)}>\r\n <div className=\"flex flex-col gap-2\">{children}</div>\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div className={cn('mt-2', className)}>\r\n <div className=\"flex items-center gap-2\">{children}</div>\r\n </div>\r\n )\r\n}\r\n\r\n// Card Icon container\r\ninterface CardIconProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction CardIcon({ children, className }: CardIconProps) {\r\n return (\r\n <div\r\n className={cn(\r\n 'flex h-8 w-8 items-center justify-center rounded-full bg-gray-100',\r\n className\r\n )}\r\n >\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// Card Chart container\r\ninterface CardChartProps {\r\n children: React.ReactNode\r\n className?: string\r\n height?: number\r\n}\r\n\r\nfunction CardChart({ children, className, height = 200 }: CardChartProps) {\r\n return (\r\n <div className={cn('w-full overflow-hidden', className)} style={{ height }}>\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\n// Card Trend component that auto-determines styling\r\ninterface CardTrendProps {\r\n value: number\r\n}\r\n\r\nfunction CardTrend({ value }: CardTrendProps) {\r\n const isPositive = value >= 0\r\n const isNegative = value < 0\r\n\r\n return (\r\n <Badge variant=\"light\" color={isPositive ? 'green' : 'red'} radius=\"sm\">\r\n <span className=\"flex items-center gap-1\">\r\n {isPositive && <TrendingUp className=\"size-3\" />}\r\n {isNegative && <TrendingDown className=\"size-3\" />}\r\n {Math.abs(value)}\r\n </span>\r\n </Badge>\r\n )\r\n}\r\n\r\n// Card List container for organizing cards horizontally\r\ninterface CardListProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction CardList({ children, className }: CardListProps) {\r\n return <div className={cn('flex w-full gap-4', className)}>{children}</div>\r\n}\r\n\r\n// Main Card export with composition pattern\r\nexport const Card = {\r\n Root: CardRoot,\r\n Header: CardHeader,\r\n Title: CardTitle,\r\n Subtitle: CardSubtitle,\r\n Value: CardValue,\r\n Description: CardDescription,\r\n Content: CardContent,\r\n Footer: CardFooter,\r\n Icon: CardIcon,\r\n Chart: CardChart,\r\n Trend: CardTrend,\r\n List: CardList\r\n}\r\n\r\n// Type exports for easier usage\r\nexport type {\r\n CardRootProps,\r\n CardHeaderProps,\r\n CardTitleProps,\r\n CardSubtitleProps,\r\n CardValueProps,\r\n CardDescriptionProps,\r\n CardContentProps,\r\n CardFooterProps,\r\n CardIconProps,\r\n CardChartProps,\r\n CardTrendProps,\r\n CardListProps\r\n}\r\n","import { Badge } from '@mantine/core'\r\nimport { Check, Circle, X } from 'lucide-react'\r\n\r\ntype Status = 'PENDENTE' | 'APROVADO' | 'REJEITADO'\r\n\r\ninterface StatusBadgeProps {\r\n status: Status\r\n}\r\n\r\nconst statusConfig = {\r\n PENDENTE: {\r\n color: 'amber',\r\n icon: Circle,\r\n label: 'Pendente'\r\n },\r\n APROVADO: {\r\n color: 'govGreen',\r\n icon: Check,\r\n label: 'Aprovado'\r\n },\r\n REJEITADO: {\r\n color: 'red',\r\n icon: X,\r\n label: 'Rejeitado'\r\n }\r\n}\r\n\r\nexport function CounselorStatusBadge({ status }: StatusBadgeProps) {\r\n const config = statusConfig[status]\r\n const Icon = config.icon\r\n\r\n return (\r\n <Badge\r\n variant=\"light\"\r\n color={config.color}\r\n leftSection={<Icon size={12} strokeWidth={3} />}\r\n >\r\n {config.label}\r\n </Badge>\r\n )\r\n}\r\n","'use client'\r\n\r\nimport { cn } from '../../utils/core/cn'\r\n\r\nimport React, {\r\n ReactNode,\r\n HTMLAttributes,\r\n createContext,\r\n useContext\r\n} from 'react'\r\n\r\ninterface DataListContextType {\r\n orientation: 'vertical' | 'horizontal'\r\n minWidth?: string\r\n maxWidth?: string\r\n}\r\n\r\nconst DataListContext = createContext<DataListContextType | undefined>(\r\n undefined\r\n)\r\n\r\nconst spacingMap = {\r\n xs: 2, // 0.5rem\r\n sm: 3, // 0.75rem\r\n md: 4, // 1rem\r\n lg: 6, // 1.5rem\r\n xl: 8 // 2rem\r\n} as const\r\n\r\ntype SpacingSize = keyof typeof spacingMap\r\n\r\ninterface DataListRootProps extends HTMLAttributes<HTMLElement> {\r\n children: ReactNode\r\n orientation?: 'vertical' | 'horizontal'\r\n spacing?: SpacingSize\r\n minWidth?: string\r\n maxWidth?: string\r\n}\r\n\r\ninterface DataListItemProps extends HTMLAttributes<HTMLDivElement> {\r\n children: ReactNode\r\n}\r\n\r\ninterface DataListLabelProps extends HTMLAttributes<HTMLSpanElement> {\r\n children: ReactNode\r\n}\r\n\r\ninterface DataListValueProps extends HTMLAttributes<HTMLSpanElement> {\r\n children: ReactNode\r\n}\r\n\r\nexport function DataListRoot({\r\n children,\r\n className,\r\n orientation = 'horizontal',\r\n spacing = 'md',\r\n minWidth,\r\n maxWidth,\r\n ...props\r\n}: DataListRootProps) {\r\n const spacingValue = spacingMap[spacing]\r\n\r\n return (\r\n <DataListContext.Provider value={{ orientation, minWidth, maxWidth }}>\r\n <dl\r\n className={cn(\r\n `overflow-wrap-anywhere flex flex-col space-y-${spacingValue}`,\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n </dl>\r\n </DataListContext.Provider>\r\n )\r\n}\r\n\r\nexport function DataListItem({\r\n children,\r\n className,\r\n ...props\r\n}: DataListItemProps) {\r\n const context = useContext(DataListContext)\r\n const orientation = context?.orientation ?? 'horizontal'\r\n\r\n const gridTemplateColumns =\r\n context?.minWidth && context?.maxWidth\r\n ? `minmax(${context.minWidth}, ${context.maxWidth}) 2fr`\r\n : context?.minWidth\r\n ? `minmax(${context.minWidth}, 1fr) 2fr`\r\n : context?.maxWidth\r\n ? `minmax(0, ${context.maxWidth}) 2fr`\r\n : '1fr 2fr'\r\n\r\n const style =\r\n orientation === 'horizontal' ? { gridTemplateColumns } : undefined\r\n\r\n return (\r\n <div\r\n className={cn(\r\n orientation === 'vertical'\r\n ? 'flex flex-col gap-2'\r\n : 'grid items-baseline',\r\n\r\n className\r\n )}\r\n style={style}\r\n {...props}\r\n >\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\nexport function DataListLabel({\r\n children,\r\n className,\r\n ...props\r\n}: DataListLabelProps) {\r\n return (\r\n <span className={cn('text-sm text-content-emphasis', className)} {...props}>\r\n {children}\r\n </span>\r\n )\r\n}\r\n\r\nexport function DataListValue({\r\n children,\r\n className,\r\n ...props\r\n}: DataListValueProps) {\r\n return (\r\n <span className={cn('min-w-0 text-sm', className)} {...props}>\r\n {children}\r\n </span>\r\n )\r\n}\r\n","import React from 'react'\r\nimport Image from 'next/image'\r\nimport { cn } from '../../utils/core/cn'\r\n\r\ninterface EmptyStateProps {\r\n children: React.ReactNode\r\n className?: string\r\n size?: 'narrow' | 'wide'\r\n}\r\n\r\ninterface EmptyStateHeaderProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\ninterface EmptyStateDescriptionProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\ninterface EmptyStateIconProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\ninterface EmptyStateImageProps {\r\n src: string\r\n alt: string\r\n width: number\r\n height: number\r\n className?: string\r\n}\r\n\r\ninterface EmptyStateActionsProps {\r\n children: React.ReactNode\r\n className?: string\r\n}\r\n\r\nfunction EmptyState({ children, className, size = 'narrow' }: EmptyStateProps) {\r\n const sizeClasses = {\r\n narrow: 'max-w-lg',\r\n wide: 'max-w-3xl'\r\n }\r\n\r\n return (\r\n <div\r\n className={cn(\r\n 'mx-auto flex flex-col items-center justify-center p-12 text-center',\r\n sizeClasses[size],\r\n className\r\n )}\r\n >\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\nfunction EmptyStateHeader({ children, className }: EmptyStateHeaderProps) {\r\n return (\r\n <h3\r\n className={cn(\r\n 'mb-4 text-xl font-semibold text-content-emphasis',\r\n className\r\n )}\r\n >\r\n {children}\r\n </h3>\r\n )\r\n}\r\n\r\nfunction EmptyStateDescription({\r\n children,\r\n className\r\n}: EmptyStateDescriptionProps) {\r\n return <p className={cn('mb-4 text-sm', className)}>{children}</p>\r\n}\r\n\r\nfunction EmptyStateIcon({ children, className }: EmptyStateIconProps) {\r\n return (\r\n <div\r\n className={cn(\r\n 'mb-6 flex size-14 items-center justify-center rounded-lg border text-content-muted shadow-6',\r\n className\r\n )}\r\n >\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\nfunction EmptyStateImage({\r\n src,\r\n alt,\r\n width,\r\n height,\r\n className\r\n}: EmptyStateImageProps) {\r\n return (\r\n <Image\r\n src={src}\r\n alt={alt}\r\n width={width}\r\n height={height}\r\n className={cn('mb-6 max-w-xs opacity-50', className)}\r\n />\r\n )\r\n}\r\n\r\nfunction EmptyStateActions({ children, className }: EmptyStateActionsProps) {\r\n return (\r\n <div\r\n className={cn(\r\n 'flex flex-col justify-center gap-2 sm:flex-row',\r\n className\r\n )}\r\n >\r\n {children}\r\n </div>\r\n )\r\n}\r\n\r\nEmptyState.Header = EmptyStateHeader\r\nEmptyState.Description = EmptyStateDescription\r\nEmptyState.Icon = EmptyStateIcon\r\nEmptyState.Image = EmptyStateImage\r\nEmptyState.Actions = EmptyStateActions\r\n\r\nexport { EmptyState }\r\n","import dayjs from 'dayjs'\r\nimport 'dayjs/locale/pt-br'\r\nimport customParseFormat from 'dayjs/plugin/customParseFormat'\r\n\r\ndayjs.locale('pt-br')\r\ndayjs.extend(customParseFormat)\r\n\r\nexport { dayjs }\r\n","'use client'\r\n\r\nimport { Drawer, DrawerProps } from '@mantine/core'\r\nimport { PropsWithChildren, ReactNode } from 'react'\r\n\r\ninterface FormDrawerRootProps extends Omit<DrawerProps, 'children'> {\r\n children: ReactNode\r\n onClose: () => void\r\n}\r\n\r\nfunction FormDrawerRoot({ children, onClose, ...props }: FormDrawerRootProps) {\r\n return (\r\n <Drawer.Root onClose={onClose} position=\"right\" size=\"xl\" {...props}>\r\n <Drawer.Overlay />\r\n <Drawer.Content>\r\n <div className=\"flex h-full flex-col\">{children}</div>\r\n </Drawer.Content>\r\n </Drawer.Root>\r\n )\r\n}\r\n\r\nfunction FormDrawerHeader({ children }: PropsWithChildren) {\r\n return (\r\n <Drawer.Header className=\"p-6 pb-0\">\r\n <Drawer.Title className=\"text-content-emphasis text-lg font-semibold\">{children}</Drawer.Title>\r\n <Drawer.CloseButton />\r\n </Drawer.Header>\r\n )\r\n}\r\n\r\ninterface FormDrawerContentProps {\r\n children: ReactNode\r\n}\r\n\r\nfunction FormDrawerContent({ children }: FormDrawerContentProps) {\r\n return (\r\n <Drawer.Body className=\"flex flex-1 flex-col p-6\">{children}</Drawer.Body>\r\n )\r\n}\r\n\r\nfunction FormDrawerFooter({ children }: PropsWithChildren) {\r\n return (\r\n <footer className=\"sticky bottom-0 border-t bg-white p-6\">\r\n {children}\r\n </footer>\r\n )\r\n}\r\n\r\nexport const FormDrawer = {\r\n Root: FormDrawerRoot,\r\n Header: FormDrawerHeader,\r\n Content: FormDrawerContent,\r\n Footer: FormDrawerFooter\r\n}\r\n","'use client'\r\n\r\nimport React, { createContext, useContext, useState, useEffect } from 'react'\r\nimport { usePathname, useRouter } from 'next/navigation'\r\nimport { Tabs, TabsList, TabsTab } from '@mantine/core'\r\nimport { cn } from '../../utils/core/cn'\r\nimport { ClientWrapper } from './client-wrapper'\r\n\r\ninterface NavigationTabsContextValue {\r\n activeTab: string\r\n handleTabChange: (value: string | null) => void\r\n}\r\n\r\nconst NavigationTabsContext = createContext<\r\n NavigationTabsContextValue | undefined\r\n>(undefined)\r\n\r\nexport function useNavigationTabsContext() {\r\n const context = useContext(NavigationTabsContext)\r\n if (!context) {\r\n throw new Error(\r\n 'NavigationTabs components must be used within a NavigationTabsContainer provider'\r\n )\r\n }\r\n return context\r\n}\r\n\r\nfunction useOptionalNavigationTabsContext() {\r\n return useContext(NavigationTabsContext)\r\n}\r\n\r\nexport interface NavigationTabsContainerProps {\r\n children: React.ReactNode\r\n className?: string\r\n basePath: string\r\n}\r\n\r\n/**\r\n * Main container that provides navigation context and handles routing\r\n * Contains both NavigationTabs and NavigationTabsActions as siblings\r\n */\r\nfunction NavigationTabsContainerComponent({\r\n children,\r\n className,\r\n basePath\r\n}: NavigationTabsContainerProps) {\r\n const router = useRouter()\r\n const pathname = usePathname()\r\n\r\n function getActiveTabFromUrl(): string {\r\n // If current path starts with basePath, extract the tab value\r\n if (pathname.startsWith(basePath + '/')) {\r\n const tabPath = pathname.slice(basePath.length + 1)\r\n // Return the first segment after basePath as the tab value\r\n const tabValue = tabPath.split('/')[0]\r\n return tabValue\r\n }\r\n\r\n // If we're exactly at basePath, return empty string (no active tab)\r\n return ''\r\n }\r\n\r\n // Controlled state for Mantine tabs\r\n const [activeTab, setActiveTab] = useState<string | null>(() =>\r\n getActiveTabFromUrl()\r\n )\r\n\r\n // Sync with URL changes\r\n useEffect(() => {\r\n const urlTab = getActiveTabFromUrl()\r\n setActiveTab(urlTab || null)\r\n }, [pathname, basePath])\r\n\r\n function handleTabChange(value: string | null) {\r\n if (!value) return\r\n\r\n setActiveTab(value)\r\n router.push(`${basePath}/${value}`)\r\n }\r\n\r\n const contextValue: NavigationTabsContextValue = {\r\n activeTab: activeTab || '',\r\n handleTabChange\r\n }\r\n\r\n return (\r\n <NavigationTabsContext.Provider value={contextValue}>\r\n <div\r\n className={cn(\r\n 'flex min-h-8 w-full items-center justify-between gap-x-4 border-b',\r\n className\r\n )}\r\n >\r\n {children}\r\n </div>\r\n </NavigationTabsContext.Provider>\r\n )\r\n}\r\n\r\nexport function NavigationTabsContainer(props: NavigationTabsContainerProps) {\r\n return (\r\n <ClientWrapper\r\n fallback={\r\n <div className={cn(\r\n 'flex min-h-8 w-full items-center justify-between gap-x-4 border-b opacity-50',\r\n props.className\r\n )}>\r\n {props.children}\r\n </div>\r\n }\r\n >\r\n <NavigationTabsContainerComponent {...props} />\r\n </ClientWrapper>\r\n )\r\n}\r\n\r\nexport interface NavigationTabsProps {\r\n children?: React.ReactNode\r\n className?: string\r\n // For compatibility with external usage\r\n tabs?: Array<{ label: string; value: string; count?: number }>\r\n activeTab?: string\r\n onTabChange?: (tab: string) => void\r\n}\r\n\r\n/**\r\n * Navigation tabs component using Mantine Tabs\r\n * Gets state and handlers from context\r\n */\r\nexport function NavigationTabs({ \r\n children, \r\n className, \r\n tabs, \r\n activeTab: externalActiveTab, \r\n onTabChange \r\n}: NavigationTabsProps) {\r\n // Use external props if provided, otherwise use context\r\n const context = useOptionalNavigationTabsContext()\r\n const activeTab = externalActiveTab || context?.activeTab || ''\r\n const handleTabChange = (value: string | null) => {\r\n if (value) {\r\n onTabChange?.(value) || context?.handleTabChange(value)\r\n }\r\n }\r\n\r\n return (\r\n <Tabs\r\n value={activeTab}\r\n onChange={handleTabChange}\r\n unstyled\r\n className={className}\r\n classNames={{\r\n root: 'h-10',\r\n list: 'flex h-full',\r\n tab: 'flex space-x-2 items-center h-full font-normal px-3 text-sm text-gray-700 data-[active=true]:font-semibold data-[active=true]:text-primary-600 border-[transparent] border-b-2 data-[active=true]:border-primary-600',\r\n tabLabel: 'leading-[20px]'\r\n }}\r\n >\r\n <TabsList>\r\n {tabs ? (\r\n tabs.map((tab) => (\r\n <TabsTab key={tab.value} value={tab.value}>\r\n {tab.label}\r\n {tab.count !== undefined && (\r\n <span className=\"ml-2 text-xs text-gray-500\">({tab.count})</span>\r\n )}\r\n </TabsTab>\r\n ))\r\n ) : (\r\n children\r\n )}\r\n </TabsList>\r\n </Tabs>\r\n )\r\n}\r\n\r\nexport interface NavigationTabsItemProps {\r\n children: React.ReactNode\r\n value: string\r\n leftSection?: React.ReactNode\r\n rightSection?: React.ReactNode\r\n className?: string\r\n}\r\n\r\n/**\r\n * Individual navigation tab item\r\n * Uses Mantine TabsTab behind the hood with automatic active detection\r\n * Supports leftSection and rightSection for icons or other content\r\n */\r\nexport function NavigationTabsItem({\r\n children,\r\n value,\r\n leftSection,\r\n rightSection,\r\n className\r\n}: NavigationTabsItemProps) {\r\n return (\r\n <TabsTab\r\n value={value}\r\n className={className}\r\n leftSection={leftSection}\r\n rightSection={rightSection}\r\n >\r\n {children}\r\n </TabsTab>\r\n )\r\n}\r\n\r\nexport interface NavigationTabsActionsProps {\r\n children: React.ReactNode\r\n value: string\r\n className?: string\r\n}\r\n\r\n/**\r\n * Actions panel that renders only when its value matches the active tab\r\n * Sibling to NavigationTabs, not nested inside\r\n */\r\nexport function NavigationTabsActions({\r\n children,\r\n value,\r\n className\r\n}: NavigationTabsActionsProps) {\r\n const { activeTab } = useNavigationTabsContext()\r\n\r\n if (activeTab !== value) {\r\n return null\r\n }\r\n\r\n return (\r\n <div className={cn('flex shrink-0 items-center gap-x-2', className)}>\r\n {children}\r\n </div>\r\n )\r\n}\r\n","'use client'\r\n\r\nimport { CloseButton, Input } from '@mantine/core'\r\nimport { useDebouncedCallback } from '@mantine/hooks'\r\nimport { Search } from 'lucide-react'\r\nimport { ChangeEvent, useEffect, useRef, useState } from 'react'\r\nimport { cn } from '../../utils/core/cn'\r\nimport { ClientWrapper } from './client-wrapper'\r\n\r\nexport type SearchInputProps = {\r\n placeholder?: string\r\n className?: string\r\n inputClassName?: string\r\n onSearch?: (value: string) => void\r\n debounceMs?: number\r\n}\r\n\r\nfunction SearchInputComponent({\r\n placeholder,\r\n className,\r\n inputClassName,\r\n onSearch,\r\n debounceMs = 500\r\n}: SearchInputProps) {\r\n const inputRef = useRef<HTMLInputElement | null>(null)\r\n const [searchValue, setSearchValue] = useState('')\r\n\r\n const handleSearch = useDebouncedCallback(\r\n (value: string) => {\r\n onSearch?.(value)\r\n },\r\n debounceMs\r\n )\r\n\r\n const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {\r\n const value = event.target.value\r\n setSearchValue(value)\r\n handleSearch(value)\r\n }\r\n\r\n const clearSearch = () => {\r\n if (inputRef.current) {\r\n inputRef.current.value = ''\r\n setSearchValue('')\r\n onSearch?.('')\r\n }\r\n }\r\n\r\n return (\r\n <Input\r\n ref={inputRef}\r\n onChange={handleInputChange}\r\n className={cn('w-full max-w-[300px]', className)}\r\n classNames={{\r\n input: cn(inputClassName)\r\n }}\r\n placeholder={placeholder || 'Busca'}\r\n rightSectionPointerEvents=\"all\"\r\n rightSection={\r\n searchValue ? (\r\n <CloseButton aria-label=\"Limpar pesquisa\" onClick={clearSearch} />\r\n ) : (\r\n <Search className=\"size-4 text-primary-600\" />\r\n )\r\n }\r\n />\r\n )\r\n}\r\n\r\nexport function SearchInput(props: SearchInputProps) {\r\n return (\r\n <ClientWrapper\r\n fallback={\r\n <Input\r\n className={cn('w-full max-w-[300px]', props.className)}\r\n classNames={{\r\n input: cn(props.inputClassName)\r\n }}\r\n placeholder={props.placeholder || 'Busca'}\r\n rightSection={<Search className=\"size-4 text-primary-600\" />}\r\n disabled\r\n />\r\n }\r\n >\r\n <SearchInputComponent {...props} />\r\n </ClientWrapper>\r\n )\r\n}\r\n","'use client'\r\n\r\nimport { Anchor, Button, Menu } from '@mantine/core'\r\nimport { ChevronDown } from 'lucide-react'\r\nimport Image from 'next/image'\r\nimport Link from 'next/link'\r\nimport { cn } from '../../utils/core/cn'\r\n\r\ntype App = {\r\n key: string\r\n name: string\r\n link: string\r\n}\r\n\r\nconst apps: App[] = [\r\n { key: 'dialogos', name: 'Diálogos', link: 'https://dialogos.pi.gov.br/' },\r\n {\r\n key: 'opa',\r\n name: 'OPA',\r\n link: 'https://opa.seplan.pi.gov.br/'\r\n },\r\n { key: 'dadospi', name: 'dadosPI', link: 'https://dados.pi.gov.br/' },\r\n {\r\n key: 'pactos',\r\n name: 'Pactos pelo Piauí',\r\n link: 'https://pactospelopiaui.pi.gov.br/'\r\n },\r\n {\r\n key: 'retribuir',\r\n name: 'Retribuir',\r\n link: 'https://www.seplan.pi.gov.br/projetos/retribuir/'\r\n },\r\n { key: 'psi', name: 'PSI', link: 'https://psi.seplan.pi.gov.br/' },\r\n {\r\n key: 'pilares',\r\n name: 'Pilares',\r\n link: 'https://www.seplan.pi.gov.br/projetos/pilares-ii/'\r\n }\r\n]\r\n\r\ninterface SeplanAppsBarProps {\r\n currentApp?: string\r\n className?: string\r\n contentClassName?: string\r\n}\r\n\r\nexport function SeplanAppsBar({\r\n currentApp,\r\n className,\r\n contentClassName\r\n}: SeplanAppsBarProps) {\r\n return (\r\n <header\r\n className={cn(\r\n 'flex h-[64px] items-center justify-between bg-primary-700 px-8 sm:h-[84px]',\r\n className\r\n )}\r\n >\r\n <div\r\n className={cn(\r\n 'mx-auto flex w-full max-w-[1120px] items-center justify-between',\r\n contentClassName\r\n )}\r\n >\r\n <div className=\"flex items-center gap-5\">\r\n <Button\r\n variant=\"white\"\r\n component={Link}\r\n className=\"hidden sm:block\"\r\n href=\"https://www.seplan.pi.gov.br/\"\r\n >\r\n Site da SEPLAN\r\n </Button>\r\n\r\n {/* Mobile Menu */}\r\n <div className=\"sm:hidden\">\r\n <Menu shadow=\"md\" width={200} position=\"bottom-start\">\r\n <Menu.Target>\r\n <Button\r\n variant=\"transparent\"\r\n color=\"white\"\r\n size=\"xs\"\r\n className=\"-translate-x-3\"\r\n rightSection={<ChevronDown className=\"size-4\" />}\r\n >\r\n Programas e projetos\r\n </Button>\r\n </Menu.Target>\r\n\r\n <Menu.Dropdown>\r\n {apps\r\n .filter((app) => app.key !== currentApp)\r\n .map((app) => (\r\n <Menu.Item key={app.key} component=\"a\" href={app.link}>\r\n {app.name}\r\n </Menu.Item>\r\n ))}\r\n </Menu.Dropdown>\r\n </Menu>\r\n </div>\r\n\r\n <nav className=\"hidden gap-5 sm:flex\">\r\n {apps\r\n .filter((app) => app.key !== currentApp)\r\n .map((app) => (\r\n <Anchor\r\n key={app.key}\r\n href={app.link}\r\n className=\"text-sm font-medium text-white\"\r\n >\r\n {app.name}\r\n </Anchor>\r\n ))}\r\n </nav>\r\n </div>\r\n <Anchor href=\"https://www.seplan.pi.gov.br/\">\r\n <Image\r\n src=\"/logo-seplan-white.svg\"\r\n alt=\"SEPLAN\"\r\n width={161}\r\n height={60}\r\n priority\r\n className=\"h-[40px] w-auto object-cover sm:h-[60px]\"\r\n />\r\n </Anchor>\r\n </div>\r\n </header>\r\n )\r\n}\r\n","export const PAGINATION = {\r\n DEFAULT_PAGE_SIZE: 15,\r\n EVENTS_GROUPS_PAGE_SIZE: 10,\r\n OPTIONS: [10, 15, 25, 50, 100] as const\r\n} as const\r\n","import { ArrowDown, ArrowUp, ChevronsUpDown } from 'lucide-react'\r\n\r\ninterface SortIconProps {\r\n isSorted: boolean\r\n isDescending: boolean\r\n}\r\n\r\nexport function SortIcon({ isSorted, isDescending, ...props }: SortIconProps) {\r\n if (!isSorted)\r\n return (\r\n <span className=\"ml-2 flex size-5 items-center justify-center rounded-full\">\r\n <ChevronsUpDown\r\n className=\"size-3 text-content-muted transition-colors group-hover:text-content data-[sorted=true]:text-content\"\r\n {...props}\r\n />\r\n </span>\r\n )\r\n\r\n return (\r\n <span className=\"ml-2 flex size-5 items-center justify-center rounded-full bg-primary-600/[0.08]\">\r\n {isDescending ? (\r\n <ArrowDown className=\"size-3 text-primary-600\" {...props} />\r\n ) : (\r\n <ArrowUp className=\"size-3 text-primary-600\" {...props} />\r\n )}\r\n </span>\r\n )\r\n}\r\n","import {\r\n createContext,\r\n useContext,\r\n PropsWithChildren,\r\n useMemo,\r\n useCallback\r\n} from 'react'\r\nimport {\r\n parseAsInteger,\r\n parseAsString,\r\n parseAsArrayOf,\r\n useQueryStates\r\n} from 'nuqs'\r\n\r\ntype SelectedItemsContextType = {\r\n selectedItems: Set<string>\r\n toggle: (value: string) => void\r\n clear: () => void\r\n}\r\n\r\nconst SelectedItemsContext = createContext<\r\n SelectedItemsContextType | undefined\r\n>(undefined)\r\n\r\nexport function useSelectedItems() {\r\n const context = useContext(SelectedItemsContext)\r\n if (!context) {\r\n throw new Error(\r\n 'useSelectedItems must be used within SelectedItemsContext.Provider'\r\n )\r\n }\r\n return context\r\n}\r\n\r\nexport function SelectedItemsProvider({\r\n children,\r\n searchParamKey,\r\n onSelectionChange\r\n}: PropsWithChildren<{\r\n searchParamKey: string\r\n onSelectionChange?: (items: Set<string>) => void\r\n}>) {\r\n const [{ selectedParam }, setSelectedParam] = useQueryStates(\r\n {\r\n selectedParam: parseAsArrayOf(parseAsString).withDefault([]),\r\n page: parseAsInteger.withDefault(1)\r\n },\r\n {\r\n shallow: false,\r\n urlKeys: {\r\n selectedParam: searchParamKey\r\n }\r\n }\r\n )\r\n\r\n const selectedItems = useMemo(() => {\r\n return new Set(selectedParam)\r\n }, [selectedParam])\r\n\r\n const toggle = useCallback(\r\n (value: string) => {\r\n setSelectedParam((prev) => {\r\n const currentSet = new Set(prev.selectedParam)\r\n\r\n if (currentSet.has(value)) {\r\n currentSet.delete(value)\r\n } else {\r\n currentSet.add(value)\r\n }\r\n\r\n const newArray = Array.from(currentSet)\r\n onSelectionChange?.(currentSet)\r\n return { selectedParam: newArray.length ? newArray : null, page: 1 }\r\n })\r\n },\r\n [setSelectedParam, onSelectionChange]\r\n )\r\n\r\n const clear = useCallback(() => {\r\n setSelectedParam(null)\r\n onSelectionChange?.(new Set())\r\n }, [setSelectedParam, onSelectionChange])\r\n\r\n const contextValue = useMemo(\r\n () => ({\r\n selectedItems,\r\n toggle,\r\n clear\r\n }),\r\n [selectedItems, toggle, clear]\r\n )\r\n\r\n return (\r\n <SelectedItemsContext.Provider value={contextValue}>\r\n {children}\r\n </SelectedItemsContext.Provider>\r\n )\r\n}\r\n","import { useDebouncedState } from '@mantine/hooks'\r\nimport { useQuery } from '@tanstack/react-query'\r\nimport { Input, Checkbox, Divider, Button, Loader, Alert } from '@mantine/core'\r\nimport { Search } from 'lucide-react'\r\nimport { ContentProps } from './types'\r\nimport { useSelectedItems } from './selected-items-provider'\r\n\r\nexport function Content<T>({\r\n loadOptions,\r\n renderOption,\r\n getOptionValue,\r\n getOptionDisabled,\r\n filterId,\r\n filterFn,\r\n showSearch = true\r\n}: ContentProps<T>) {\r\n const [search, setSearch] = useDebouncedState('', 100)\r\n const { selectedItems, toggle, clear } = useSelectedItems()\r\n const { data, error, isFetching } = useQuery({\r\n queryKey: [`async-filter-${filterId}`],\r\n queryFn: () => loadOptions()\r\n })\r\n\r\n if (isFetching) {\r\n return (\r\n <div className=\"flex flex-col items-center space-y-4 p-6\">\r\n <Loader size=\"sm\" />\r\n <span className=\"text-sm font-semibold text-secondary-foreground\">\r\n Carregando...\r\n </span>\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return (\r\n <Alert color=\"red\" title=\"Erro ao carregar opções\" className=\"m-2\">\r\n {error.message}\r\n </Alert>\r\n )\r\n }\r\n\r\n const filteredOptions =\r\n data?.filter((option) => filterFn(option, search)) || []\r\n\r\n const selectedOptions = filteredOptions.filter((option) =>\r\n selectedItems.has(getOptionValue(option))\r\n )\r\n\r\n const renderCheckboxItem = (option: T) => (\r\n <li key={getOptionValue(option)} className=\"group\">\r\n <label\r\n className={`flex w-full cursor-pointer space-x-2 p-2 text-xs hover:bg-gray-100 ${\r\n getOptionDisabled(option)\r\n ? 'cursor-not-allowed text-muted-foreground'\r\n : ''\r\n }`}\r\n >\r\n <Checkbox\r\n className=\"mt-0.5\"\r\n size=\"xs\"\r\n checked={selectedItems.has(getOptionValue(option))}\r\n onChange={() =>\r\n !getOptionDisabled(option) && toggle(getOptionValue(option))\r\n }\r\n disabled={getOptionDisabled(option)}\r\n />\r\n {renderOption(option)}\r\n </label>\r\n </li>\r\n )\r\n\r\n return (\r\n <>\r\n {showSearch && (\r\n <header className=\"border-b p-1\">\r\n <Input\r\n classNames={{\r\n input: 'border-none'\r\n }}\r\n onChange={(event) => setSearch(event.target.value)}\r\n placeholder=\"Pesquisar\"\r\n rightSection={<Search className=\"size-4\" />}\r\n />\r\n </header>\r\n )}\r\n <ul className=\"max-h-96 w-full overflow-y-auto overflow-x-clip\">\r\n {selectedOptions.length > 0 && (\r\n <>\r\n <span className=\"block p-2 text-xs font-semibold text-secondary-foreground\">\r\n Selecinados\r\n </span>\r\n {selectedOptions.map(renderCheckboxItem)}\r\n <Divider className=\"my-2\" />\r\n </>\r\n )}\r\n {filteredOptions.map(\r\n (option) =>\r\n !selectedItems.has(getOptionValue(option)) &&\r\n renderCheckboxItem(option)\r\n )}\r\n </ul>\r\n {selectedOptions.length > 0 && (\r\n <footer className=\"border-t\">\r\n <Button variant=\"white\" size=\"sm\" onClick={() => clear()}>\r\n Limpar\r\n </Button>\r\n </footer>\r\n )}\r\n </>\r\n )\r\n}\r\n","'use client'\r\n\r\nimport { forwardRef, PropsWithChildren } from 'react'\r\nimport { Button, Popover, Badge } from '@mantine/core'\r\nimport { ChevronDown } from 'lucide-react'\r\nimport { Content } from './content'\r\nimport {\r\n SelectedItemsProvider,\r\n useSelectedItems\r\n} from './selected-items-provider'\r\nimport { DefaultOption, AsyncFilterProps } from './types'\r\n\r\nexport function AsyncFilter<T extends object = DefaultOption>({\r\n children,\r\n loadOptions,\r\n renderOption,\r\n getOptionValue,\r\n getOptionLabel,\r\n getOptionDisabled,\r\n filterId,\r\n searchParamKey = 'selectedItems',\r\n filterFn,\r\n showSearch = true\r\n}: AsyncFilterProps<T>) {\r\n const isDefaultOption = (option: T): option is T & DefaultOption =>\r\n 'id' in option && 'label' in option\r\n\r\n const handlers = {\r\n getValue: (option: T): string => {\r\n if (getOptionValue) return getOptionValue(option)\r\n if (isDefaultOption(option)) return option.id\r\n throw new Error('getOptionValue is required for non-DefaultOption types')\r\n },\r\n getLabel: (option: T): string => {\r\n if (getOptionLabel) return getOptionLabel(option)\r\n if (isDefaultOption(option)) return option.label\r\n throw new Error('getOptionLabel is required for non-DefaultOption types')\r\n }\r\n }\r\n\r\n const finalRenderOption =\r\n renderOption ||\r\n ((option: T) => (\r\n <span\r\n className={`flex-grow font-medium ${\r\n getOptionDisabled?.(option) ? 'text-gray-400' : ''\r\n }`}\r\n >\r\n {handlers.getLabel(option)}\r\n </span>\r\n ))\r\n\r\n const finalFilterFn =\r\n filterFn ||\r\n ((option: T, search: string) =>\r\n handlers.getLabel(option).toLowerCase().includes(search.toLowerCase()))\r\n\r\n const finalGetOptionDisabled = getOptionDisabled || (() => false)\r\n\r\n return (\r\n <SelectedItemsProvider searchParamKey={searchParamKey}>\r\n <Popover width={300} position=\"bottom-start\" shadow=\"md\">\r\n <Popover.Target>\r\n <FilterButton>{children}</FilterButton>\r\n </Popover.Target>\r\n <Popover.Dropdown className=\"p-0\">\r\n <Content\r\n {...{\r\n loadOptions,\r\n renderOption: finalRenderOption,\r\n getOptionValue: handlers.getValue,\r\n getOptionLabel: handlers.getLabel,\r\n getOptionDisabled: finalGetOptionDisabled,\r\n filterId,\r\n filterFn: finalFilterFn,\r\n showSearch // Pass the prop\r\n }}\r\n />\r\n </Popover.Dropdown>