analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
1 lines • 10.6 kB
Source Map (JSON)
{"version":3,"sources":["../src/components/PerformanceRanking/PerformanceRanking.tsx"],"sourcesContent":["import { type HTMLAttributes } from 'react';\nimport {\n TrendUpIcon,\n TrendDownIcon,\n MedalIcon,\n SealWarningIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\nimport {\n type RankingVariant,\n BADGE_BACKGROUND_CLASSES,\n PERCENTAGE_BADGE_CLASSES,\n getPositionBackgroundClass,\n BaseRankingCard,\n RankingLayout,\n} from '../shared/RankingShared';\n\n/**\n * Single item in the performance ranking\n */\nexport interface PerformanceRankingItem {\n position: number;\n name: string;\n count: number;\n percentage: number;\n trend: 'up' | 'down' | null;\n /** Only present when groupedBy = 'class' */\n shift?: string;\n /** Only present when groupedBy = 'class' */\n grade?: string;\n}\n\n/**\n * How the ranking data is grouped\n */\nexport type GroupedBy = 'state' | 'municipality' | 'class';\n\n/**\n * Full ranking data from the API\n */\nexport interface PerformanceRankingData {\n /** Informational only — the component does not branch on this value.\n * Class-specific rendering (shift/grade) is derived from field presence. */\n groupedBy: GroupedBy;\n highlighted: (PerformanceRankingItem | null)[];\n needsAttention: (PerformanceRankingItem | null)[];\n}\n\n/**\n * Props for the PerformanceRanking component\n */\nexport interface PerformanceRankingProps extends HTMLAttributes<HTMLDivElement> {\n /** Ranking data from the API */\n data: PerformanceRankingData;\n /** Title for the highlight card */\n highlightTitle?: string;\n /** Title for the attention card */\n attentionTitle?: string;\n /** Label displayed next to the count value (e.g. \"estudantes\") */\n countLabel?: string;\n}\n\n/**\n * Position badge text color per variant\n */\nconst POSITION_TEXT_CLASSES = {\n highlight: 'text-text-950',\n attention: 'text-text',\n} as const;\n\n/**\n * Count badge classes — neutral color to distinguish from percentage\n */\nconst COUNT_BADGE_CLASSES = {\n highlight: 'bg-success-background text-success-800',\n attention: 'bg-error-background text-error-800',\n} as const;\n\n/**\n * Render the trend icon based on the item's trend value\n */\nfunction TrendIcon({\n trend,\n variant,\n}: Readonly<{\n trend: 'up' | 'down' | null;\n variant: RankingVariant;\n}>) {\n if (trend === 'up') {\n return <TrendUpIcon size={16} weight=\"bold\" aria-hidden=\"true\" />;\n }\n if (trend === 'down') {\n return <TrendDownIcon size={16} weight=\"bold\" aria-hidden=\"true\" />;\n }\n // Intentional UX decision: when the API returns null (trend unavailable),\n // we show the variant's default direction (up for highlight, down for attention)\n // so the badge layout stays uniform across all rows — confirmed in Figma.\n const DefaultIcon = variant === 'highlight' ? TrendUpIcon : TrendDownIcon;\n return <DefaultIcon size={16} weight=\"bold\" aria-hidden=\"true\" />;\n}\n\n/**\n * Build the display name including shift/grade inline when present.\n * e.g. \"Turma A (Manhã) (3° ano)\"\n */\nfunction buildDisplayName(item: PerformanceRankingItem): string {\n const parts = [item.name];\n if (item.shift) parts.push(`(${item.shift})`);\n if (item.grade) parts.push(`(${item.grade})`);\n return parts.join(' ');\n}\n\n/**\n * Individual performance ranking item card\n */\nfunction PerformanceItemCard({\n item,\n variant,\n countLabel,\n}: Readonly<{\n item: PerformanceRankingItem;\n variant: RankingVariant;\n countLabel?: string;\n}>) {\n const backgroundClass = getPositionBackgroundClass(variant, item.position);\n const displayName = buildDisplayName(item);\n const countText = countLabel\n ? `${item.count} ${countLabel}`\n : String(item.count);\n\n return (\n <div\n className={cn(\n 'flex flex-row items-center w-full p-4 gap-2 rounded-xl',\n backgroundClass\n )}\n >\n {/* Position badge */}\n <Text\n size=\"xs\"\n weight=\"bold\"\n aria-label={`Position ${item.position}`}\n className={cn(\n 'w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0',\n POSITION_TEXT_CLASSES[variant],\n BADGE_BACKGROUND_CLASSES[variant]\n )}\n >\n {item.position}\n </Text>\n\n {/* Name (with shift/grade inline) */}\n <Text\n size=\"sm\"\n weight=\"bold\"\n className=\"flex-1 min-w-0 text-text-950 tracking-[0.2px] truncate\"\n >\n {displayName}\n </Text>\n\n {/* Count badge */}\n <Text\n size=\"xs\"\n aria-label={`Count ${item.count}`}\n className={cn(\n 'flex-shrink-0 h-[22px] px-2 flex items-center rounded',\n COUNT_BADGE_CLASSES[variant]\n )}\n >\n {countText}\n </Text>\n\n {/* Percentage badge with trend */}\n <Text\n size=\"xs\"\n weight=\"bold\"\n aria-label={`Performance ${item.percentage}%`}\n className={cn(\n 'flex flex-row items-center flex-shrink-0 h-[22px] px-2 gap-1 rounded text-text',\n PERCENTAGE_BADGE_CLASSES[variant]\n )}\n >\n <TrendIcon trend={item.trend} variant={variant} />\n {item.percentage}%\n </Text>\n </div>\n );\n}\n\n/**\n * Render a single ranking item (handles null items)\n */\nfunction createRenderPerformanceItem(countLabel?: string) {\n return function renderPerformanceItem(\n item: PerformanceRankingItem | null,\n variant: RankingVariant,\n index: number\n ) {\n if (!item) {\n return (\n <div\n key={`${variant}-empty-${index}`}\n className=\"flex items-center justify-center w-full p-4 rounded-xl bg-background-50 border border-dashed border-border-100\"\n >\n <Text size=\"xs\" className=\"text-text-400\">\n Sem dados\n </Text>\n </div>\n );\n }\n\n return (\n <PerformanceItemCard\n key={`${variant}-${index}-${item.position}`}\n item={item}\n variant={variant}\n countLabel={countLabel}\n />\n );\n };\n}\n\n/**\n * PerformanceRanking component — displays two cards side by side showing\n * highlighted regions/municipalities/classes and those needing attention.\n *\n * Supports nullable items, per-item trend indicators, count badges,\n * and optional shift/grade info for class grouping.\n *\n * @example\n * ```tsx\n * <PerformanceRanking\n * data={{\n * groupedBy: 'state',\n * highlighted: [\n * { position: 1, name: 'SP', count: 150, percentage: 85, trend: 'up' },\n * { position: 2, name: 'RJ', count: 120, percentage: 78, trend: 'down' },\n * { position: 3, name: 'MG', count: 100, percentage: 72, trend: null },\n * ],\n * needsAttention: [\n * { position: 1, name: 'BA', count: 30, percentage: 25, trend: 'down' },\n * { position: 2, name: 'PE', count: 35, percentage: 28, trend: null },\n * { position: 3, name: 'CE', count: 40, percentage: 32, trend: 'up' },\n * ],\n * }}\n * />\n * ```\n */\nexport const PerformanceRanking = ({\n data,\n highlightTitle = 'Em destaque',\n attentionTitle = 'Precisando de atenção',\n countLabel,\n className,\n ...props\n}: PerformanceRankingProps) => {\n const renderItem = createRenderPerformanceItem(countLabel);\n\n return (\n <RankingLayout className={className} {...props}>\n <BaseRankingCard\n title={highlightTitle}\n variant=\"highlight\"\n items={data.highlighted}\n renderItem={renderItem}\n headerIcon={<MedalIcon size={14} className=\"text-text-950\" />}\n />\n <BaseRankingCard\n title={attentionTitle}\n variant=\"attention\"\n items={data.needsAttention}\n renderItem={renderItem}\n headerIcon={<SealWarningIcon size={14} className=\"text-text\" />}\n />\n </RankingLayout>\n );\n};\n\nexport default PerformanceRanking;\n"],"mappings":"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","names":[]}