@handled-ai/design-system 0.20.34 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/data-table.js +1 -15
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/linked-entity-cell.d.ts +16 -1
- package/dist/components/linked-entity-cell.js +33 -3
- package/dist/components/linked-entity-cell.js.map +1 -1
- package/dist/components/virtualized-data-table.js +7 -11
- package/dist/components/virtualized-data-table.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/entity-color.d.ts +3 -0
- package/dist/lib/entity-color.js +19 -0
- package/dist/lib/entity-color.js.map +1 -0
- package/package.json +1 -1
- package/src/components/__tests__/linked-entity-cell.test.tsx +143 -0
- package/src/components/__tests__/virtualized-data-table.test.tsx +124 -0
- package/src/components/data-table.tsx +1 -17
- package/src/components/linked-entity-cell.tsx +44 -2
- package/src/components/virtualized-data-table.tsx +15 -14
- package/src/index.ts +1 -0
- package/src/lib/entity-color.ts +22 -0
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
useReactTable
|
|
42
42
|
} from "@tanstack/react-table";
|
|
43
43
|
import { cn } from "../lib/utils.js";
|
|
44
|
+
import { getEntityColor } from "../lib/entity-color.js";
|
|
44
45
|
import { Badge } from "./badge.js";
|
|
45
46
|
import {
|
|
46
47
|
DataTableQuickViews
|
|
@@ -398,21 +399,6 @@ const DEFAULT_COLUMN_VISIBILITY = {
|
|
|
398
399
|
owner: false,
|
|
399
400
|
opportunityCount: false
|
|
400
401
|
};
|
|
401
|
-
function getEntityColor(name) {
|
|
402
|
-
const colors = [
|
|
403
|
-
"bg-muted text-muted-foreground",
|
|
404
|
-
"bg-gray-100 text-gray-600",
|
|
405
|
-
"bg-zinc-100 text-zinc-600",
|
|
406
|
-
"bg-blue-50 text-blue-600",
|
|
407
|
-
"bg-indigo-50 text-indigo-600",
|
|
408
|
-
"bg-violet-50 text-violet-600"
|
|
409
|
-
];
|
|
410
|
-
let hash = 0;
|
|
411
|
-
for (let i = 0; i < name.length; i += 1) {
|
|
412
|
-
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
413
|
-
}
|
|
414
|
-
return colors[Math.abs(hash) % colors.length];
|
|
415
|
-
}
|
|
416
402
|
function getIndustryColor(industry) {
|
|
417
403
|
var _a;
|
|
418
404
|
const colors = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/data-table.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n Briefcase,\n Calendar,\n DollarSign,\n History,\n Link as LinkIcon,\n SearchX,\n TrendingUp,\n User,\n Users,\n} from \"lucide-react\"\nimport {\n createColumnHelper,\n flexRender,\n getCoreRowModel,\n getSortedRowModel,\n useReactTable,\n type SortingState,\n type VisibilityState,\n} from \"@tanstack/react-table\"\n\nimport { cn } from \"../lib/utils\"\nimport { Badge } from \"./badge\"\nimport {\n DataTableQuickViews,\n type DataTableQuickViewValue,\n} from \"./data-table-quick-views\"\nimport { DataTableToolbar } from \"./data-table-toolbar\"\nimport { type DataTableFilterCategory } from \"./data-table-filter\"\nimport { ScoreAnalysisModal } from \"./score-analysis-modal\"\nimport type { ScoreFactor } from \"./score-breakdown\"\nimport { Citation, type SourceDef } from \"./detail-view\"\n\nexport type DataRow = {\n id: string\n name: string\n industry: string[]\n accountRisks: string[]\n riskScore: number\n expansionScore: number\n growthIndicators: string[]\n lastInteraction: string\n lastInteractionDays: number\n createdAt: string\n revenue: string\n headcount: string\n lastFunding: string\n owner: string\n ownerEmail?: string\n opportunityCount: number\n productAdoptionScore: number\n sourceSystem?: string\n sourceRef?: string\n}\n\nconst QUICK_VIEWS = [\n \"Balance Flight Detected\",\n \"Not Touched in 30+ Days\",\n \"Open Opportunity, Stalled\",\n \"Growth Signal Detected\",\n \"Low Product Adoption\",\n]\n\nconst MORE_QUICK_VIEWS = [\n \"Missed meeting this week\",\n \"High churn risk score\",\n \"Key contact departed\",\n \"Recent large inflow\",\n \"Dormant (no payments)\",\n \"Support tickets elevated\",\n]\n\nconst FILTER_CATEGORIES: DataTableFilterCategory[] = [\n {\n id: \"industry\",\n label: \"Industry\",\n icon: Briefcase,\n options: [\n \"Software\",\n \"E-commerce\",\n \"Financial Technology\",\n \"Workforce Management\",\n \"Artificial Intelligence\",\n \"Health Technology\",\n \"Design\",\n ],\n },\n {\n id: \"lastInteraction\",\n label: \"Last interaction\",\n icon: History,\n options: [\"1 day ago\", \"3 days ago\", \"1 week ago\", \"1 month ago\", \"2 months ago\"],\n },\n {\n id: \"createdAt\",\n label: \"Created at\",\n icon: Calendar,\n options: [\"Last 30 days\", \"Last 90 days\", \"This year\", \"Last year\"],\n },\n {\n id: \"revenue\",\n label: \"Revenue\",\n icon: DollarSign,\n options: [\"$0 - $1M\", \"$1M - $10M\", \"$10M - $50M\", \"$50M+\"],\n },\n {\n id: \"headcount\",\n label: \"Headcount\",\n icon: Users,\n options: [\"11-50\", \"51-200\", \"201-500\", \"500-1000\", \"1000+\"],\n },\n {\n id: \"lastFunding\",\n label: \"Last funding\",\n icon: TrendingUp,\n options: [\"Seed\", \"Series A\", \"Series B\", \"Series C+\", \"Undisclosed\"],\n },\n {\n id: \"owner\",\n label: \"Owner\",\n icon: User,\n options: [\"Sam Lee\", \"Alex Morgan\", \"Jordan Case\", \"Taylor Reed\"],\n },\n {\n id: \"opportunityCount\",\n label: \"Opportunity count\",\n icon: LinkIcon,\n options: [\"0\", \"1\", \"2\", \"3+\"],\n },\n]\n\nconst ROWS: DataRow[] = [\n {\n id: \"rappi\",\n name: \"Rappi\",\n industry: [\"E-commerce\", \"Food Delivery\", \"Financial Technology\"],\n accountRisks: [\"Flight Risk\", \"Low Engagement\"],\n riskScore: 65,\n expansionScore: 45,\n growthIndicators: [\"Job Openings\", \"Recent Funding\"],\n lastInteraction: \"1 week ago\",\n lastInteractionDays: 7,\n createdAt: \"This year\",\n revenue: \"$10M - $50M\",\n headcount: \"1000+\",\n lastFunding: \"Series C+\",\n owner: \"Sam Lee\",\n opportunityCount: 2,\n productAdoptionScore: 62,\n },\n {\n id: \"codeshot\",\n name: \"Codeshot\",\n industry: [\"Software\"],\n accountRisks: [\"Flight Risk\", \"Low Engagement\"],\n riskScore: 85,\n expansionScore: 20,\n growthIndicators: [],\n lastInteraction: \"2 months ago\",\n lastInteractionDays: 64,\n createdAt: \"Last year\",\n revenue: \"$1M - $10M\",\n headcount: \"201-500\",\n lastFunding: \"Series A\",\n owner: \"Alex Morgan\",\n opportunityCount: 1,\n productAdoptionScore: 31,\n },\n {\n id: \"lovi\",\n name: \"Lovi\",\n industry: [\"Artificial Intelligence\", \"Health Technology\"],\n accountRisks: [\"Low Engagement\", \"Key Contact Departure\"],\n riskScore: 55,\n expansionScore: 75,\n growthIndicators: [\"Recent Funding\", \"Headcount Expansion\"],\n lastInteraction: \"1 month ago\",\n lastInteractionDays: 36,\n createdAt: \"This year\",\n revenue: \"$10M - $50M\",\n headcount: \"500-1000\",\n lastFunding: \"Series B\",\n owner: \"Jordan Case\",\n opportunityCount: 3,\n productAdoptionScore: 38,\n },\n {\n id: \"anthropic\",\n name: \"Anthropic\",\n industry: [\"Software\"],\n accountRisks: [],\n riskScore: 25,\n expansionScore: 68,\n growthIndicators: [\"Recent Funding\", \"Headcount Expansion\"],\n lastInteraction: \"3 days ago\",\n lastInteractionDays: 3,\n createdAt: \"Last 90 days\",\n revenue: \"$50M+\",\n headcount: \"1000+\",\n lastFunding: \"Series C+\",\n owner: \"Sam Lee\",\n opportunityCount: 2,\n productAdoptionScore: 86,\n },\n {\n id: \"buildbear\",\n name: \"BuildBear\",\n industry: [\"Software\"],\n accountRisks: [],\n riskScore: 35,\n expansionScore: 92,\n growthIndicators: [\"Recent Funding\", \"Revenue Growth\"],\n lastInteraction: \"1 day ago\",\n lastInteractionDays: 1,\n createdAt: \"Last 30 days\",\n revenue: \"$1M - $10M\",\n headcount: \"51-200\",\n lastFunding: \"Seed\",\n owner: \"Taylor Reed\",\n opportunityCount: 2,\n productAdoptionScore: 91,\n },\n {\n id: \"content-mobbin\",\n name: \"Content-mobbin\",\n industry: [\"Workforce Management\"],\n accountRisks: [],\n riskScore: 28,\n expansionScore: 85,\n growthIndicators: [\"Recent Funding\", \"Headcount Expansion\"],\n lastInteraction: \"1 week ago\",\n lastInteractionDays: 9,\n createdAt: \"Last 30 days\",\n revenue: \"$0 - $1M\",\n headcount: \"11-50\",\n lastFunding: \"Seed\",\n owner: \"Taylor Reed\",\n opportunityCount: 2,\n productAdoptionScore: 77,\n },\n {\n id: \"figma\",\n name: \"Figma\",\n industry: [\"Design\", \"Software\"],\n accountRisks: [],\n riskScore: 15,\n expansionScore: 88,\n growthIndicators: [\"Headcount Expansion\", \"Job Openings\"],\n lastInteraction: \"3 days ago\",\n lastInteractionDays: 3,\n createdAt: \"This year\",\n revenue: \"$50M+\",\n headcount: \"1000+\",\n lastFunding: \"Series C+\",\n owner: \"Alex Morgan\",\n opportunityCount: 1,\n productAdoptionScore: 94,\n },\n {\n id: \"loom\",\n name: \"Loom\",\n industry: [\"Software\"],\n accountRisks: [\"Key Contact Departure\"],\n riskScore: 35,\n expansionScore: 68,\n growthIndicators: [\"Headcount Expansion\", \"Job Openings\"],\n lastInteraction: \"1 month ago\",\n lastInteractionDays: 33,\n createdAt: \"Last year\",\n revenue: \"$10M - $50M\",\n headcount: \"500-1000\",\n lastFunding: \"Series B\",\n owner: \"Jordan Case\",\n opportunityCount: 1,\n productAdoptionScore: 58,\n },\n {\n id: \"miro\",\n name: \"Miro\",\n industry: [\"Software\"],\n accountRisks: [],\n riskScore: 32,\n expansionScore: 55,\n growthIndicators: [],\n lastInteraction: \"1 week ago\",\n lastInteractionDays: 8,\n createdAt: \"This year\",\n revenue: \"$50M+\",\n headcount: \"1000+\",\n lastFunding: \"Series C+\",\n owner: \"Sam Lee\",\n opportunityCount: 0,\n productAdoptionScore: 64,\n },\n {\n id: \"webflow\",\n name: \"Webflow\",\n industry: [\"Software\"],\n accountRisks: [],\n riskScore: 25,\n expansionScore: 72,\n growthIndicators: [\"Recent Funding\", \"Headcount Expansion\"],\n lastInteraction: \"1 week ago\",\n lastInteractionDays: 10,\n createdAt: \"Last 90 days\",\n revenue: \"$10M - $50M\",\n headcount: \"500-1000\",\n lastFunding: \"Series B\",\n owner: \"Alex Morgan\",\n opportunityCount: 2,\n productAdoptionScore: 71,\n },\n]\n\ntype ScoreAnalysisData = {\n title: string\n description: string\n whyNow: string\n evidence: React.ReactNode[]\n factors: ScoreFactor[]\n}\n\nconst RISK_SOURCES: SourceDef[] = [\n { id: 1, summary: \"Weekly active users declined 12% over the past 30 days with no recovery trend.\", meta: \"Product telemetry \\u00b7 2h ago\" },\n { id: 2, summary: \"Critical support ticket #4821 has been unresolved for over 48 hours.\", meta: \"Zendesk \\u00b7 6h ago\" },\n { id: 3, summary: \"Competitor mentions detected in recent Slack conversations from the finance team.\", meta: \"Slack signal \\u00b7 1d ago\" },\n]\n\nconst EXPANSION_SOURCES: SourceDef[] = [\n { id: 1, summary: \"Treasury feature utilization is above the 85th percentile compared to peer accounts.\", meta: \"Product telemetry \\u00b7 3h ago\" },\n { id: 2, summary: \"Multiple feature requests submitted for advanced reporting and API access.\", meta: \"Zendesk \\u00b7 1d ago\" },\n { id: 3, summary: \"Finance department headcount grew from 8 to 14 in the last quarter.\", meta: \"LinkedIn signal \\u00b7 2d ago\" },\n]\n\nconst SCORE_ANALYSIS: Record<string, (row: DataRow) => ScoreAnalysisData> = {\n Risk: (row) => ({\n title: \"Risk Score Analysis\",\n description:\n \"Estimated probability of churn within the next 90 days based on activity and support signals\",\n whyNow:\n row.riskScore >= 60\n ? \"Critical risk factors detected requiring immediate intervention to prevent churn.\"\n : \"Account health is stable, but monitoring recent support interactions is recommended.\",\n evidence: [\n <>Recent decline in <span className=\"font-medium text-foreground\">weekly active users (-12%)</span> with no recovery trend<Citation number={1} source={RISK_SOURCES[0]} /></>,\n <>Unresolved <span className=\"font-medium text-foreground\">critical support ticket</span> open for over 48 hours<Citation number={2} source={RISK_SOURCES[1]} /></>,\n <>Competitor presence detected in recent conversations from finance team<Citation number={3} source={RISK_SOURCES[2]} /></>,\n ],\n factors: [\n { key: \"engagement\", label: \"Engagement drop\", score: Math.min(row.riskScore + 10, 100), why: \"Weekly active users declined 12% over past 30 days\" },\n { key: \"support\", label: \"Support load\", score: Math.min(row.riskScore + 5, 100), why: \"Unresolved critical ticket open for >48h\" },\n { key: \"competitive\", label: \"Competitive risk\", score: null, risk: row.riskScore >= 60 ? \"High\" as const : \"Low\" as const, why: \"Competitor mentions detected in recent conversations\" },\n { key: \"usage\", label: \"Product usage\", score: Math.max(100 - row.riskScore, 10), why: \"Core feature adoption remains consistent\" },\n ],\n }),\n Expansion: (row) => ({\n title: \"Expansion Score Analysis\",\n description:\n \"Likelihood of successful upsell and cross-sell opportunities based on usage and engagement\",\n whyNow:\n row.expansionScore >= 70\n ? \"Usage patterns and growth signals indicate readiness for additional product adoption.\"\n : \"Moderate expansion potential; consider targeted engagement to increase adoption.\",\n evidence: [\n <>Treasury utilization above <span className=\"font-medium text-foreground\">85th percentile</span> vs peer accounts<Citation number={1} source={EXPANSION_SOURCES[0]} /></>,\n <>Frequent <span className=\"font-medium text-foreground\">feature requests</span> for advanced reporting and API access<Citation number={2} source={EXPANSION_SOURCES[1]} /></>,\n <>Recent <span className=\"font-medium text-foreground\">team expansion</span> in finance department (8 → 14)<Citation number={3} source={EXPANSION_SOURCES[2]} /></>,\n ],\n factors: [\n { key: \"usage-depth\", label: \"Usage depth\", score: Math.min(row.expansionScore + 8, 100), why: \"Active usage across 4+ core product features\" },\n { key: \"growth\", label: \"Growth signals\", score: row.expansionScore, why: row.growthIndicators.length > 0 ? row.growthIndicators.join(\", \") + \" detected\" : \"No active growth signals\" },\n { key: \"fit\", label: \"Product fit\", score: Math.min(row.expansionScore + 12, 100), why: \"Company profile matches high-expansion ICP\" },\n { key: \"timing\", label: \"Timing\", score: null, risk: row.expansionScore >= 70 ? \"Low\" as const : \"Medium\" as const, why: \"Within typical evaluation window for upsell\" },\n ],\n }),\n}\n\nconst QUICK_VIEW_FILTERS: Record<string, (row: DataRow) => boolean> = {\n \"Balance Flight Detected\": (row) => row.riskScore >= 60,\n \"Not Touched in 30+ Days\": (row) => row.lastInteractionDays >= 30,\n \"Open Opportunity, Stalled\": (row) =>\n row.opportunityCount > 0 && row.lastInteractionDays >= 21,\n \"Growth Signal Detected\": (row) => row.expansionScore >= 70,\n \"Low Product Adoption\": (row) => row.productAdoptionScore <= 40,\n \"Missed meeting this week\": (row) => row.lastInteractionDays >= 6,\n \"High churn risk score\": (row) => row.riskScore >= 75,\n \"Key contact departed\": (row) =>\n row.accountRisks.some((risk) => risk.toLowerCase().includes(\"contact\")),\n \"Recent large inflow\": (row) => row.expansionScore >= 85,\n \"Dormant (no payments)\": (row) => row.opportunityCount === 0,\n \"Support tickets elevated\": (row) =>\n row.accountRisks.some((risk) => risk.toLowerCase().includes(\"engagement\")),\n}\n\nconst columnHelper = createColumnHelper<DataRow>()\n\nconst DEFAULT_COLUMN_VISIBILITY: VisibilityState = {\n industry: false,\n lastInteraction: false,\n revenue: false,\n headcount: false,\n lastFunding: false,\n owner: false,\n opportunityCount: false,\n}\n\nfunction getEntityColor(name: string) {\n const colors = [\n \"bg-muted text-muted-foreground\",\n \"bg-gray-100 text-gray-600\",\n \"bg-zinc-100 text-zinc-600\",\n \"bg-blue-50 text-blue-600\",\n \"bg-indigo-50 text-indigo-600\",\n \"bg-violet-50 text-violet-600\",\n ]\n\n let hash = 0\n for (let i = 0; i < name.length; i += 1) {\n hash = name.charCodeAt(i) + ((hash << 5) - hash)\n }\n return colors[Math.abs(hash) % colors.length]\n}\n\nfunction getIndustryColor(industry: string) {\n const colors: Record<string, string> = {\n \"E-commerce\": \"bg-emerald-50 text-emerald-700 border-emerald-100\",\n \"Food Delivery\": \"bg-blue-50 text-blue-700 border-blue-100\",\n \"Financial Technology\": \"bg-amber-50 text-amber-700 border-amber-100\",\n \"Workforce Management\": \"bg-violet-50 text-violet-700 border-violet-100\",\n \"Artificial Intelligence\": \"bg-rose-50 text-rose-700 border-rose-100\",\n \"Health Technology\": \"bg-orange-50 text-orange-700 border-orange-100\",\n Software: \"bg-muted text-muted-foreground border-border\",\n }\n\n return colors[industry] ?? \"bg-muted text-muted-foreground border-border\"\n}\n\nfunction toggleFilterValue(\n current: Record<string, string[]>,\n categoryId: string,\n option: string\n) {\n const currentValues = current[categoryId] ?? []\n const isActive = currentValues.includes(option)\n const nextValues = isActive\n ? currentValues.filter((value) => value !== option)\n : [...currentValues, option]\n\n return {\n ...current,\n [categoryId]: nextValues,\n }\n}\n\nfunction isRowMatchingCategoryFilter(\n row: DataRow,\n categoryId: string,\n options: string[]\n) {\n if (options.length === 0) {\n return true\n }\n\n switch (categoryId) {\n case \"industry\":\n return options.some((option) => row.industry.includes(option))\n case \"lastInteraction\":\n return options.includes(row.lastInteraction)\n case \"createdAt\":\n return options.includes(row.createdAt)\n case \"revenue\":\n return options.includes(row.revenue)\n case \"headcount\":\n return options.includes(row.headcount)\n case \"lastFunding\":\n return options.includes(row.lastFunding)\n case \"owner\":\n return options.includes(row.ownerEmail ?? row.owner)\n case \"opportunityCount\":\n return options.some((option) => {\n if (option === \"3+\") {\n return row.opportunityCount >= 3\n }\n return row.opportunityCount === Number(option)\n })\n default:\n return true\n }\n}\n\nexport interface DataTableProps {\n onRowClick?: (row: DataRow) => void\n rows?: DataRow[]\n filterCategories?: DataTableFilterCategory[]\n quickViews?: string[]\n moreQuickViews?: string[]\n quickViewFilters?: Record<string, (row: DataRow) => boolean>\n iconMap?: { salesforce?: string }\n entityUrlBuilder?: (row: DataRow) => string\n onScoreFactorFeedback?: (account: string, scoreType: string, factorKey: string, type: \"up\" | \"down\" | null, detail?: string) => void\n onScoreApproveFeedback?: (account: string, scoreType: string, reasons: string[], detail: string) => void\n onScoreDismissFeedback?: (account: string, scoreType: string, reasons: string[], detail: string) => void\n}\n\nexport function DataTable({\n onRowClick,\n rows: rowsProp,\n filterCategories: filterCategoriesProp,\n quickViews: quickViewsProp,\n moreQuickViews: moreQuickViewsProp,\n quickViewFilters: quickViewFiltersProp,\n iconMap,\n entityUrlBuilder,\n onScoreFactorFeedback,\n onScoreApproveFeedback,\n onScoreDismissFeedback,\n}: DataTableProps = {}) {\n const resolvedRows = rowsProp ?? ROWS\n const resolvedFilterCategories = filterCategoriesProp ?? FILTER_CATEGORIES\n const resolvedQuickViews = quickViewsProp ?? QUICK_VIEWS\n const resolvedMoreQuickViews = moreQuickViewsProp ?? MORE_QUICK_VIEWS\n const resolvedQuickViewFilters = quickViewFiltersProp ?? QUICK_VIEW_FILTERS\n\n const [sorting, setSorting] = React.useState<SortingState>([])\n const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(\n DEFAULT_COLUMN_VISIBILITY\n )\n const [selectedFilters, setSelectedFilters] = React.useState<Record<string, string[]>>(\n {}\n )\n const [activeQuickView, setActiveQuickView] =\n React.useState<DataTableQuickViewValue>(null)\n const [scoreModal, setScoreModal] = React.useState<{\n row: DataRow\n type: \"Risk\" | \"Expansion\"\n } | null>(null)\n\n React.useEffect(() => {\n if (!activeQuickView) {\n return\n }\n\n setColumnVisibility((previous) => {\n const next = { ...previous }\n if (activeQuickView.includes(\"Touched\")) {\n next.lastInteraction = true\n }\n if (activeQuickView.includes(\"Growth\")) {\n next.expansionScore = true\n }\n if (activeQuickView.includes(\"risk\") || activeQuickView.includes(\"Risk\")) {\n next.riskScore = true\n }\n return next\n })\n }, [activeQuickView])\n\n const filteredRows = React.useMemo(() => {\n return resolvedRows.filter((row) => {\n const quickViewMatches = activeQuickView\n ? (resolvedQuickViewFilters[activeQuickView]?.(row) ?? true)\n : true\n\n if (!quickViewMatches) {\n return false\n }\n\n return Object.entries(selectedFilters).every(([categoryId, options]) =>\n isRowMatchingCategoryFilter(row, categoryId, options)\n )\n })\n }, [activeQuickView, selectedFilters, resolvedRows, resolvedQuickViewFilters])\n\n const columns = React.useMemo(\n () => [\n columnHelper.accessor(\"name\", {\n header: \"Entity\",\n cell: (info) => {\n const row = info.row.original\n const sfUrl = entityUrlBuilder?.(row)\n return (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-6 w-6 shrink-0 items-center justify-center rounded text-[10px] font-bold\",\n getEntityColor(row.name)\n )}\n >\n {row.name.slice(0, 1)}\n </div>\n <span className=\"text-sm font-medium text-foreground\">\n {row.name}\n </span>\n {iconMap?.salesforce && (\n <a\n href={sfUrl ?? \"#\"}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className=\"shrink-0 text-muted-foreground hover:text-foreground transition-colors\"\n >\n <img src={iconMap.salesforce} alt=\"Salesforce\" className=\"w-4 h-4 object-contain\" />\n </a>\n )}\n </div>\n )\n },\n }),\n columnHelper.accessor(\"accountRisks\", {\n header: \"Risk Signals\",\n cell: (info) => {\n const risks = info.getValue()\n if (!risks.length) {\n return <span className=\"text-xs text-muted-foreground\">None</span>\n }\n\n return (\n <div className=\"flex items-center gap-1.5\">\n {risks.slice(0, 2).map((risk) => (\n <Badge\n key={risk}\n variant=\"outline\"\n className=\"rounded-md border-red-200 bg-red-50 px-2 py-0.5 text-xs font-normal text-red-700\"\n >\n {risk}\n </Badge>\n ))}\n </div>\n )\n },\n }),\n columnHelper.accessor(\"riskScore\", {\n id: \"riskScore\",\n header: \"Risk Score\",\n cell: (info) => (\n <div\n className=\"inline-flex cursor-pointer\"\n onClick={(e) => {\n e.stopPropagation()\n setScoreModal({ row: info.row.original, type: \"Risk\" })\n }}\n >\n <Badge\n variant=\"outline\"\n className={cn(\n \"px-2 py-0.5 text-xs font-medium hover:underline decoration-dotted underline-offset-2\",\n info.getValue() >= 60\n ? \"border-red-200 bg-red-50 text-red-700\"\n : \"border-border bg-muted/50 text-foreground\"\n )}\n >\n {info.getValue()}%\n </Badge>\n </div>\n ),\n }),\n columnHelper.accessor(\"expansionScore\", {\n id: \"expansionScore\",\n header: \"Expansion Score\",\n cell: (info) => (\n <div\n className=\"inline-flex cursor-pointer\"\n onClick={(e) => {\n e.stopPropagation()\n setScoreModal({ row: info.row.original, type: \"Expansion\" })\n }}\n >\n <Badge\n variant=\"outline\"\n className={cn(\n \"px-2 py-0.5 text-xs font-medium hover:underline decoration-dotted underline-offset-2\",\n info.getValue() >= 70\n ? \"border-blue-200 bg-blue-50 text-blue-700\"\n : \"border-border bg-muted/50 text-foreground\"\n )}\n >\n {info.getValue()}%\n </Badge>\n </div>\n ),\n }),\n columnHelper.accessor(\"growthIndicators\", {\n header: \"Growth Signals\",\n cell: (info) => {\n const indicators = info.getValue()\n if (!indicators.length) {\n return <span className=\"text-xs text-muted-foreground\">None</span>\n }\n return (\n <div className=\"flex items-center gap-1.5\">\n {indicators.slice(0, 2).map((indicator) => (\n <Badge\n key={indicator}\n variant=\"outline\"\n className=\"rounded-md border-blue-200 bg-blue-50 px-2 py-0.5 text-xs font-normal text-blue-700\"\n >\n {indicator}\n </Badge>\n ))}\n </div>\n )\n },\n }),\n columnHelper.accessor(\"industry\", {\n header: \"Industry\",\n cell: (info) => (\n <div className=\"flex items-center gap-1.5\">\n {info.getValue().slice(0, 2).map((industry) => (\n <Badge\n key={industry}\n variant=\"outline\"\n className={cn(\n \"rounded-md px-2 py-0.5 text-xs font-normal\",\n getIndustryColor(industry)\n )}\n >\n {industry}\n </Badge>\n ))}\n </div>\n ),\n }),\n columnHelper.accessor(\"lastInteraction\", {\n header: \"Last interaction\",\n cell: (info) => <span className=\"text-sm\">{info.getValue()}</span>,\n }),\n columnHelper.accessor(\"revenue\", {\n header: \"Revenue\",\n cell: (info) => (\n <span className=\"rounded-md bg-muted/50 px-2 py-0.5 text-xs font-medium\">\n {info.getValue()}\n </span>\n ),\n }),\n columnHelper.accessor(\"headcount\", {\n header: \"Headcount\",\n cell: (info) => (\n <span className=\"rounded-md bg-muted/50 px-2 py-0.5 text-xs font-medium\">\n {info.getValue()}\n </span>\n ),\n }),\n columnHelper.accessor(\"lastFunding\", {\n header: \"Last funding\",\n cell: (info) => (\n <span className=\"rounded-md bg-muted/50 px-2 py-0.5 text-xs font-medium\">\n {info.getValue()}\n </span>\n ),\n }),\n columnHelper.accessor(\"owner\", {\n header: \"Owner\",\n cell: (info) => <span className=\"text-sm\">{info.getValue()}</span>,\n }),\n columnHelper.accessor(\"opportunityCount\", {\n header: \"Opportunity count\",\n cell: (info) => (\n <span className=\"rounded-md bg-muted/50 px-2 py-0.5 text-xs font-medium\">\n {info.getValue()}\n </span>\n ),\n }),\n ],\n [iconMap, entityUrlBuilder]\n )\n\n const table = useReactTable({\n data: filteredRows,\n columns,\n state: {\n sorting,\n columnVisibility,\n },\n onSortingChange: setSorting,\n onColumnVisibilityChange: setColumnVisibility,\n getCoreRowModel: getCoreRowModel(),\n getSortedRowModel: getSortedRowModel(),\n })\n\n const displayColumns = table.getAllLeafColumns().map((column) => {\n const header = column.columnDef.header\n const label = typeof header === \"string\" ? header : column.id\n\n return {\n id: column.id,\n label,\n visible: column.getIsVisible(),\n canHide: column.getCanHide(),\n }\n })\n\n const toggleCategoryFilter = (categoryId: string, option: string) => {\n setSelectedFilters((previous) => toggleFilterValue(previous, categoryId, option))\n }\n\n return (\n <div className=\"relative flex h-full min-h-[560px] flex-col bg-background\">\n <DataTableToolbar\n categories={resolvedFilterCategories}\n selectedFilters={selectedFilters}\n onToggleFilter={toggleCategoryFilter}\n sorting={sorting}\n onSortingChange={setSorting}\n displayColumns={displayColumns}\n onToggleColumn={(columnId) => table.getColumn(columnId)?.toggleVisibility()}\n onResetDisplay={() => setColumnVisibility(DEFAULT_COLUMN_VISIBILITY)}\n />\n\n <DataTableQuickViews\n quickViews={resolvedQuickViews}\n moreViews={resolvedMoreQuickViews}\n activeView={activeQuickView}\n onViewChange={setActiveQuickView}\n />\n\n <div className=\"relative min-h-0 flex-1 overflow-auto border-t border-border\">\n <table className=\"w-max min-w-full border-collapse text-sm\">\n <thead className=\"sticky top-0 z-10 bg-background\">\n {table.getHeaderGroups().map((headerGroup) => (\n <tr key={headerGroup.id} className=\"border-b border-border\">\n {headerGroup.headers.map((header) => (\n <th\n key={header.id}\n className=\"h-10 border-r border-border px-4 text-left text-xs font-medium text-muted-foreground/80 last:border-r-0\"\n >\n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext()\n )}\n </th>\n ))}\n </tr>\n ))}\n </thead>\n <tbody>\n {table.getRowModel().rows.length > 0 ? (\n <>\n {table.getRowModel().rows.map((row) => (\n <tr\n key={row.id}\n onClick={() => onRowClick?.(row.original)}\n className={cn(\n \"group border-b border-border/50 transition-colors hover:bg-muted/30\",\n onRowClick && \"cursor-pointer\",\n )}\n >\n {row.getVisibleCells().map((cell) => (\n <td\n key={cell.id}\n className=\"border-r border-border/40 px-4 py-2.5 align-middle last:border-r-0\"\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </td>\n ))}\n </tr>\n ))}\n <tr>\n <td\n className=\"px-4 py-2 text-xs text-muted-foreground\"\n colSpan={columns.length}\n >\n {table.getRowModel().rows.length} rows\n </td>\n </tr>\n </>\n ) : (\n <tr>\n <td colSpan={columns.length} className=\"h-52 px-4 text-center\">\n <div className=\"flex flex-col items-center gap-1 text-muted-foreground\">\n <SearchX className=\"h-7 w-7 opacity-40\" />\n <p className=\"text-sm font-medium\">No rows found</p>\n <p className=\"text-xs\">Try adjusting your filters or quick views</p>\n </div>\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {scoreModal && (() => {\n const data = SCORE_ANALYSIS[scoreModal.type](scoreModal.row)\n return (\n <ScoreAnalysisModal\n open\n onOpenChange={(open) => { if (!open) setScoreModal(null) }}\n title={data.title}\n description={data.description}\n score={scoreModal.type === \"Risk\" ? scoreModal.row.riskScore : scoreModal.row.expansionScore}\n scoreIntent={scoreModal.type === \"Risk\" ? \"risk\" : \"positive\"}\n whyNow={data.whyNow}\n evidence={data.evidence}\n factors={data.factors}\n onFactorFeedback={onScoreFactorFeedback\n ? (key, type, detail) => onScoreFactorFeedback(scoreModal.row.name, scoreModal.type, key, type, detail)\n : (key, type, detail) => console.log(\"Factor feedback:\", { account: scoreModal.row.name, factor: key, type, detail })\n }\n companyName={scoreModal.row.name}\n opportunityUrl={`https://acme.lightning.force.com/lightning/r/Opportunity/006${scoreModal.row.id}/view`}\n onApprove={() => console.log(\"Approved signal — creating opportunity:\", { account: scoreModal.row.name, type: scoreModal.type })}\n onApproveFeedback={onScoreApproveFeedback\n ? (reasons, detail) => onScoreApproveFeedback(scoreModal.row.name, scoreModal.type, reasons, detail)\n : (reasons, detail) => console.log(\"Approval feedback:\", { account: scoreModal.row.name, reasons, detail })\n }\n onDismiss={onScoreDismissFeedback\n ? (reasons, detail) => onScoreDismissFeedback(scoreModal.row.name, scoreModal.type, reasons, detail)\n : (reasons, detail) => console.log(\"Dismissed signal:\", { account: scoreModal.row.name, reasons, detail })\n }\n />\n )\n })()}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n Briefcase,\n Calendar,\n DollarSign,\n History,\n Link as LinkIcon,\n SearchX,\n TrendingUp,\n User,\n Users,\n} from \"lucide-react\"\nimport {\n createColumnHelper,\n flexRender,\n getCoreRowModel,\n getSortedRowModel,\n useReactTable,\n type SortingState,\n type VisibilityState,\n} from \"@tanstack/react-table\"\n\nimport { cn } from \"../lib/utils\"\nimport { getEntityColor } from \"../lib/entity-color\"\nimport { Badge } from \"./badge\"\nimport {\n DataTableQuickViews,\n type DataTableQuickViewValue,\n} from \"./data-table-quick-views\"\nimport { DataTableToolbar } from \"./data-table-toolbar\"\nimport { type DataTableFilterCategory } from \"./data-table-filter\"\nimport { ScoreAnalysisModal } from \"./score-analysis-modal\"\nimport type { ScoreFactor } from \"./score-breakdown\"\nimport { Citation, type SourceDef } from \"./detail-view\"\n\nexport type DataRow = {\n id: string\n name: string\n industry: string[]\n accountRisks: string[]\n riskScore: number\n expansionScore: number\n growthIndicators: string[]\n lastInteraction: string\n lastInteractionDays: number\n createdAt: string\n revenue: string\n headcount: string\n lastFunding: string\n owner: string\n ownerEmail?: string\n opportunityCount: number\n productAdoptionScore: number\n sourceSystem?: string\n sourceRef?: string\n}\n\nconst QUICK_VIEWS = [\n \"Balance Flight Detected\",\n \"Not Touched in 30+ Days\",\n \"Open Opportunity, Stalled\",\n \"Growth Signal Detected\",\n \"Low Product Adoption\",\n]\n\nconst MORE_QUICK_VIEWS = [\n \"Missed meeting this week\",\n \"High churn risk score\",\n \"Key contact departed\",\n \"Recent large inflow\",\n \"Dormant (no payments)\",\n \"Support tickets elevated\",\n]\n\nconst FILTER_CATEGORIES: DataTableFilterCategory[] = [\n {\n id: \"industry\",\n label: \"Industry\",\n icon: Briefcase,\n options: [\n \"Software\",\n \"E-commerce\",\n \"Financial Technology\",\n \"Workforce Management\",\n \"Artificial Intelligence\",\n \"Health Technology\",\n \"Design\",\n ],\n },\n {\n id: \"lastInteraction\",\n label: \"Last interaction\",\n icon: History,\n options: [\"1 day ago\", \"3 days ago\", \"1 week ago\", \"1 month ago\", \"2 months ago\"],\n },\n {\n id: \"createdAt\",\n label: \"Created at\",\n icon: Calendar,\n options: [\"Last 30 days\", \"Last 90 days\", \"This year\", \"Last year\"],\n },\n {\n id: \"revenue\",\n label: \"Revenue\",\n icon: DollarSign,\n options: [\"$0 - $1M\", \"$1M - $10M\", \"$10M - $50M\", \"$50M+\"],\n },\n {\n id: \"headcount\",\n label: \"Headcount\",\n icon: Users,\n options: [\"11-50\", \"51-200\", \"201-500\", \"500-1000\", \"1000+\"],\n },\n {\n id: \"lastFunding\",\n label: \"Last funding\",\n icon: TrendingUp,\n options: [\"Seed\", \"Series A\", \"Series B\", \"Series C+\", \"Undisclosed\"],\n },\n {\n id: \"owner\",\n label: \"Owner\",\n icon: User,\n options: [\"Sam Lee\", \"Alex Morgan\", \"Jordan Case\", \"Taylor Reed\"],\n },\n {\n id: \"opportunityCount\",\n label: \"Opportunity count\",\n icon: LinkIcon,\n options: [\"0\", \"1\", \"2\", \"3+\"],\n },\n]\n\nconst ROWS: DataRow[] = [\n {\n id: \"rappi\",\n name: \"Rappi\",\n industry: [\"E-commerce\", \"Food Delivery\", \"Financial Technology\"],\n accountRisks: [\"Flight Risk\", \"Low Engagement\"],\n riskScore: 65,\n expansionScore: 45,\n growthIndicators: [\"Job Openings\", \"Recent Funding\"],\n lastInteraction: \"1 week ago\",\n lastInteractionDays: 7,\n createdAt: \"This year\",\n revenue: \"$10M - $50M\",\n headcount: \"1000+\",\n lastFunding: \"Series C+\",\n owner: \"Sam Lee\",\n opportunityCount: 2,\n productAdoptionScore: 62,\n },\n {\n id: \"codeshot\",\n name: \"Codeshot\",\n industry: [\"Software\"],\n accountRisks: [\"Flight Risk\", \"Low Engagement\"],\n riskScore: 85,\n expansionScore: 20,\n growthIndicators: [],\n lastInteraction: \"2 months ago\",\n lastInteractionDays: 64,\n createdAt: \"Last year\",\n revenue: \"$1M - $10M\",\n headcount: \"201-500\",\n lastFunding: \"Series A\",\n owner: \"Alex Morgan\",\n opportunityCount: 1,\n productAdoptionScore: 31,\n },\n {\n id: \"lovi\",\n name: \"Lovi\",\n industry: [\"Artificial Intelligence\", \"Health Technology\"],\n accountRisks: [\"Low Engagement\", \"Key Contact Departure\"],\n riskScore: 55,\n expansionScore: 75,\n growthIndicators: [\"Recent Funding\", \"Headcount Expansion\"],\n lastInteraction: \"1 month ago\",\n lastInteractionDays: 36,\n createdAt: \"This year\",\n revenue: \"$10M - $50M\",\n headcount: \"500-1000\",\n lastFunding: \"Series B\",\n owner: \"Jordan Case\",\n opportunityCount: 3,\n productAdoptionScore: 38,\n },\n {\n id: \"anthropic\",\n name: \"Anthropic\",\n industry: [\"Software\"],\n accountRisks: [],\n riskScore: 25,\n expansionScore: 68,\n growthIndicators: [\"Recent Funding\", \"Headcount Expansion\"],\n lastInteraction: \"3 days ago\",\n lastInteractionDays: 3,\n createdAt: \"Last 90 days\",\n revenue: \"$50M+\",\n headcount: \"1000+\",\n lastFunding: \"Series C+\",\n owner: \"Sam Lee\",\n opportunityCount: 2,\n productAdoptionScore: 86,\n },\n {\n id: \"buildbear\",\n name: \"BuildBear\",\n industry: [\"Software\"],\n accountRisks: [],\n riskScore: 35,\n expansionScore: 92,\n growthIndicators: [\"Recent Funding\", \"Revenue Growth\"],\n lastInteraction: \"1 day ago\",\n lastInteractionDays: 1,\n createdAt: \"Last 30 days\",\n revenue: \"$1M - $10M\",\n headcount: \"51-200\",\n lastFunding: \"Seed\",\n owner: \"Taylor Reed\",\n opportunityCount: 2,\n productAdoptionScore: 91,\n },\n {\n id: \"content-mobbin\",\n name: \"Content-mobbin\",\n industry: [\"Workforce Management\"],\n accountRisks: [],\n riskScore: 28,\n expansionScore: 85,\n growthIndicators: [\"Recent Funding\", \"Headcount Expansion\"],\n lastInteraction: \"1 week ago\",\n lastInteractionDays: 9,\n createdAt: \"Last 30 days\",\n revenue: \"$0 - $1M\",\n headcount: \"11-50\",\n lastFunding: \"Seed\",\n owner: \"Taylor Reed\",\n opportunityCount: 2,\n productAdoptionScore: 77,\n },\n {\n id: \"figma\",\n name: \"Figma\",\n industry: [\"Design\", \"Software\"],\n accountRisks: [],\n riskScore: 15,\n expansionScore: 88,\n growthIndicators: [\"Headcount Expansion\", \"Job Openings\"],\n lastInteraction: \"3 days ago\",\n lastInteractionDays: 3,\n createdAt: \"This year\",\n revenue: \"$50M+\",\n headcount: \"1000+\",\n lastFunding: \"Series C+\",\n owner: \"Alex Morgan\",\n opportunityCount: 1,\n productAdoptionScore: 94,\n },\n {\n id: \"loom\",\n name: \"Loom\",\n industry: [\"Software\"],\n accountRisks: [\"Key Contact Departure\"],\n riskScore: 35,\n expansionScore: 68,\n growthIndicators: [\"Headcount Expansion\", \"Job Openings\"],\n lastInteraction: \"1 month ago\",\n lastInteractionDays: 33,\n createdAt: \"Last year\",\n revenue: \"$10M - $50M\",\n headcount: \"500-1000\",\n lastFunding: \"Series B\",\n owner: \"Jordan Case\",\n opportunityCount: 1,\n productAdoptionScore: 58,\n },\n {\n id: \"miro\",\n name: \"Miro\",\n industry: [\"Software\"],\n accountRisks: [],\n riskScore: 32,\n expansionScore: 55,\n growthIndicators: [],\n lastInteraction: \"1 week ago\",\n lastInteractionDays: 8,\n createdAt: \"This year\",\n revenue: \"$50M+\",\n headcount: \"1000+\",\n lastFunding: \"Series C+\",\n owner: \"Sam Lee\",\n opportunityCount: 0,\n productAdoptionScore: 64,\n },\n {\n id: \"webflow\",\n name: \"Webflow\",\n industry: [\"Software\"],\n accountRisks: [],\n riskScore: 25,\n expansionScore: 72,\n growthIndicators: [\"Recent Funding\", \"Headcount Expansion\"],\n lastInteraction: \"1 week ago\",\n lastInteractionDays: 10,\n createdAt: \"Last 90 days\",\n revenue: \"$10M - $50M\",\n headcount: \"500-1000\",\n lastFunding: \"Series B\",\n owner: \"Alex Morgan\",\n opportunityCount: 2,\n productAdoptionScore: 71,\n },\n]\n\ntype ScoreAnalysisData = {\n title: string\n description: string\n whyNow: string\n evidence: React.ReactNode[]\n factors: ScoreFactor[]\n}\n\nconst RISK_SOURCES: SourceDef[] = [\n { id: 1, summary: \"Weekly active users declined 12% over the past 30 days with no recovery trend.\", meta: \"Product telemetry \\u00b7 2h ago\" },\n { id: 2, summary: \"Critical support ticket #4821 has been unresolved for over 48 hours.\", meta: \"Zendesk \\u00b7 6h ago\" },\n { id: 3, summary: \"Competitor mentions detected in recent Slack conversations from the finance team.\", meta: \"Slack signal \\u00b7 1d ago\" },\n]\n\nconst EXPANSION_SOURCES: SourceDef[] = [\n { id: 1, summary: \"Treasury feature utilization is above the 85th percentile compared to peer accounts.\", meta: \"Product telemetry \\u00b7 3h ago\" },\n { id: 2, summary: \"Multiple feature requests submitted for advanced reporting and API access.\", meta: \"Zendesk \\u00b7 1d ago\" },\n { id: 3, summary: \"Finance department headcount grew from 8 to 14 in the last quarter.\", meta: \"LinkedIn signal \\u00b7 2d ago\" },\n]\n\nconst SCORE_ANALYSIS: Record<string, (row: DataRow) => ScoreAnalysisData> = {\n Risk: (row) => ({\n title: \"Risk Score Analysis\",\n description:\n \"Estimated probability of churn within the next 90 days based on activity and support signals\",\n whyNow:\n row.riskScore >= 60\n ? \"Critical risk factors detected requiring immediate intervention to prevent churn.\"\n : \"Account health is stable, but monitoring recent support interactions is recommended.\",\n evidence: [\n <>Recent decline in <span className=\"font-medium text-foreground\">weekly active users (-12%)</span> with no recovery trend<Citation number={1} source={RISK_SOURCES[0]} /></>,\n <>Unresolved <span className=\"font-medium text-foreground\">critical support ticket</span> open for over 48 hours<Citation number={2} source={RISK_SOURCES[1]} /></>,\n <>Competitor presence detected in recent conversations from finance team<Citation number={3} source={RISK_SOURCES[2]} /></>,\n ],\n factors: [\n { key: \"engagement\", label: \"Engagement drop\", score: Math.min(row.riskScore + 10, 100), why: \"Weekly active users declined 12% over past 30 days\" },\n { key: \"support\", label: \"Support load\", score: Math.min(row.riskScore + 5, 100), why: \"Unresolved critical ticket open for >48h\" },\n { key: \"competitive\", label: \"Competitive risk\", score: null, risk: row.riskScore >= 60 ? \"High\" as const : \"Low\" as const, why: \"Competitor mentions detected in recent conversations\" },\n { key: \"usage\", label: \"Product usage\", score: Math.max(100 - row.riskScore, 10), why: \"Core feature adoption remains consistent\" },\n ],\n }),\n Expansion: (row) => ({\n title: \"Expansion Score Analysis\",\n description:\n \"Likelihood of successful upsell and cross-sell opportunities based on usage and engagement\",\n whyNow:\n row.expansionScore >= 70\n ? \"Usage patterns and growth signals indicate readiness for additional product adoption.\"\n : \"Moderate expansion potential; consider targeted engagement to increase adoption.\",\n evidence: [\n <>Treasury utilization above <span className=\"font-medium text-foreground\">85th percentile</span> vs peer accounts<Citation number={1} source={EXPANSION_SOURCES[0]} /></>,\n <>Frequent <span className=\"font-medium text-foreground\">feature requests</span> for advanced reporting and API access<Citation number={2} source={EXPANSION_SOURCES[1]} /></>,\n <>Recent <span className=\"font-medium text-foreground\">team expansion</span> in finance department (8 → 14)<Citation number={3} source={EXPANSION_SOURCES[2]} /></>,\n ],\n factors: [\n { key: \"usage-depth\", label: \"Usage depth\", score: Math.min(row.expansionScore + 8, 100), why: \"Active usage across 4+ core product features\" },\n { key: \"growth\", label: \"Growth signals\", score: row.expansionScore, why: row.growthIndicators.length > 0 ? row.growthIndicators.join(\", \") + \" detected\" : \"No active growth signals\" },\n { key: \"fit\", label: \"Product fit\", score: Math.min(row.expansionScore + 12, 100), why: \"Company profile matches high-expansion ICP\" },\n { key: \"timing\", label: \"Timing\", score: null, risk: row.expansionScore >= 70 ? \"Low\" as const : \"Medium\" as const, why: \"Within typical evaluation window for upsell\" },\n ],\n }),\n}\n\nconst QUICK_VIEW_FILTERS: Record<string, (row: DataRow) => boolean> = {\n \"Balance Flight Detected\": (row) => row.riskScore >= 60,\n \"Not Touched in 30+ Days\": (row) => row.lastInteractionDays >= 30,\n \"Open Opportunity, Stalled\": (row) =>\n row.opportunityCount > 0 && row.lastInteractionDays >= 21,\n \"Growth Signal Detected\": (row) => row.expansionScore >= 70,\n \"Low Product Adoption\": (row) => row.productAdoptionScore <= 40,\n \"Missed meeting this week\": (row) => row.lastInteractionDays >= 6,\n \"High churn risk score\": (row) => row.riskScore >= 75,\n \"Key contact departed\": (row) =>\n row.accountRisks.some((risk) => risk.toLowerCase().includes(\"contact\")),\n \"Recent large inflow\": (row) => row.expansionScore >= 85,\n \"Dormant (no payments)\": (row) => row.opportunityCount === 0,\n \"Support tickets elevated\": (row) =>\n row.accountRisks.some((risk) => risk.toLowerCase().includes(\"engagement\")),\n}\n\nconst columnHelper = createColumnHelper<DataRow>()\n\nconst DEFAULT_COLUMN_VISIBILITY: VisibilityState = {\n industry: false,\n lastInteraction: false,\n revenue: false,\n headcount: false,\n lastFunding: false,\n owner: false,\n opportunityCount: false,\n}\n\nfunction getIndustryColor(industry: string) {\n const colors: Record<string, string> = {\n \"E-commerce\": \"bg-emerald-50 text-emerald-700 border-emerald-100\",\n \"Food Delivery\": \"bg-blue-50 text-blue-700 border-blue-100\",\n \"Financial Technology\": \"bg-amber-50 text-amber-700 border-amber-100\",\n \"Workforce Management\": \"bg-violet-50 text-violet-700 border-violet-100\",\n \"Artificial Intelligence\": \"bg-rose-50 text-rose-700 border-rose-100\",\n \"Health Technology\": \"bg-orange-50 text-orange-700 border-orange-100\",\n Software: \"bg-muted text-muted-foreground border-border\",\n }\n\n return colors[industry] ?? \"bg-muted text-muted-foreground border-border\"\n}\n\nfunction toggleFilterValue(\n current: Record<string, string[]>,\n categoryId: string,\n option: string\n) {\n const currentValues = current[categoryId] ?? []\n const isActive = currentValues.includes(option)\n const nextValues = isActive\n ? currentValues.filter((value) => value !== option)\n : [...currentValues, option]\n\n return {\n ...current,\n [categoryId]: nextValues,\n }\n}\n\nfunction isRowMatchingCategoryFilter(\n row: DataRow,\n categoryId: string,\n options: string[]\n) {\n if (options.length === 0) {\n return true\n }\n\n switch (categoryId) {\n case \"industry\":\n return options.some((option) => row.industry.includes(option))\n case \"lastInteraction\":\n return options.includes(row.lastInteraction)\n case \"createdAt\":\n return options.includes(row.createdAt)\n case \"revenue\":\n return options.includes(row.revenue)\n case \"headcount\":\n return options.includes(row.headcount)\n case \"lastFunding\":\n return options.includes(row.lastFunding)\n case \"owner\":\n return options.includes(row.ownerEmail ?? row.owner)\n case \"opportunityCount\":\n return options.some((option) => {\n if (option === \"3+\") {\n return row.opportunityCount >= 3\n }\n return row.opportunityCount === Number(option)\n })\n default:\n return true\n }\n}\n\nexport interface DataTableProps {\n onRowClick?: (row: DataRow) => void\n rows?: DataRow[]\n filterCategories?: DataTableFilterCategory[]\n quickViews?: string[]\n moreQuickViews?: string[]\n quickViewFilters?: Record<string, (row: DataRow) => boolean>\n iconMap?: { salesforce?: string }\n entityUrlBuilder?: (row: DataRow) => string\n onScoreFactorFeedback?: (account: string, scoreType: string, factorKey: string, type: \"up\" | \"down\" | null, detail?: string) => void\n onScoreApproveFeedback?: (account: string, scoreType: string, reasons: string[], detail: string) => void\n onScoreDismissFeedback?: (account: string, scoreType: string, reasons: string[], detail: string) => void\n}\n\nexport function DataTable({\n onRowClick,\n rows: rowsProp,\n filterCategories: filterCategoriesProp,\n quickViews: quickViewsProp,\n moreQuickViews: moreQuickViewsProp,\n quickViewFilters: quickViewFiltersProp,\n iconMap,\n entityUrlBuilder,\n onScoreFactorFeedback,\n onScoreApproveFeedback,\n onScoreDismissFeedback,\n}: DataTableProps = {}) {\n const resolvedRows = rowsProp ?? ROWS\n const resolvedFilterCategories = filterCategoriesProp ?? FILTER_CATEGORIES\n const resolvedQuickViews = quickViewsProp ?? QUICK_VIEWS\n const resolvedMoreQuickViews = moreQuickViewsProp ?? MORE_QUICK_VIEWS\n const resolvedQuickViewFilters = quickViewFiltersProp ?? QUICK_VIEW_FILTERS\n\n const [sorting, setSorting] = React.useState<SortingState>([])\n const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(\n DEFAULT_COLUMN_VISIBILITY\n )\n const [selectedFilters, setSelectedFilters] = React.useState<Record<string, string[]>>(\n {}\n )\n const [activeQuickView, setActiveQuickView] =\n React.useState<DataTableQuickViewValue>(null)\n const [scoreModal, setScoreModal] = React.useState<{\n row: DataRow\n type: \"Risk\" | \"Expansion\"\n } | null>(null)\n\n React.useEffect(() => {\n if (!activeQuickView) {\n return\n }\n\n setColumnVisibility((previous) => {\n const next = { ...previous }\n if (activeQuickView.includes(\"Touched\")) {\n next.lastInteraction = true\n }\n if (activeQuickView.includes(\"Growth\")) {\n next.expansionScore = true\n }\n if (activeQuickView.includes(\"risk\") || activeQuickView.includes(\"Risk\")) {\n next.riskScore = true\n }\n return next\n })\n }, [activeQuickView])\n\n const filteredRows = React.useMemo(() => {\n return resolvedRows.filter((row) => {\n const quickViewMatches = activeQuickView\n ? (resolvedQuickViewFilters[activeQuickView]?.(row) ?? true)\n : true\n\n if (!quickViewMatches) {\n return false\n }\n\n return Object.entries(selectedFilters).every(([categoryId, options]) =>\n isRowMatchingCategoryFilter(row, categoryId, options)\n )\n })\n }, [activeQuickView, selectedFilters, resolvedRows, resolvedQuickViewFilters])\n\n const columns = React.useMemo(\n () => [\n columnHelper.accessor(\"name\", {\n header: \"Entity\",\n cell: (info) => {\n const row = info.row.original\n const sfUrl = entityUrlBuilder?.(row)\n return (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-6 w-6 shrink-0 items-center justify-center rounded text-[10px] font-bold\",\n getEntityColor(row.name)\n )}\n >\n {row.name.slice(0, 1)}\n </div>\n <span className=\"text-sm font-medium text-foreground\">\n {row.name}\n </span>\n {iconMap?.salesforce && (\n <a\n href={sfUrl ?? \"#\"}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className=\"shrink-0 text-muted-foreground hover:text-foreground transition-colors\"\n >\n <img src={iconMap.salesforce} alt=\"Salesforce\" className=\"w-4 h-4 object-contain\" />\n </a>\n )}\n </div>\n )\n },\n }),\n columnHelper.accessor(\"accountRisks\", {\n header: \"Risk Signals\",\n cell: (info) => {\n const risks = info.getValue()\n if (!risks.length) {\n return <span className=\"text-xs text-muted-foreground\">None</span>\n }\n\n return (\n <div className=\"flex items-center gap-1.5\">\n {risks.slice(0, 2).map((risk) => (\n <Badge\n key={risk}\n variant=\"outline\"\n className=\"rounded-md border-red-200 bg-red-50 px-2 py-0.5 text-xs font-normal text-red-700\"\n >\n {risk}\n </Badge>\n ))}\n </div>\n )\n },\n }),\n columnHelper.accessor(\"riskScore\", {\n id: \"riskScore\",\n header: \"Risk Score\",\n cell: (info) => (\n <div\n className=\"inline-flex cursor-pointer\"\n onClick={(e) => {\n e.stopPropagation()\n setScoreModal({ row: info.row.original, type: \"Risk\" })\n }}\n >\n <Badge\n variant=\"outline\"\n className={cn(\n \"px-2 py-0.5 text-xs font-medium hover:underline decoration-dotted underline-offset-2\",\n info.getValue() >= 60\n ? \"border-red-200 bg-red-50 text-red-700\"\n : \"border-border bg-muted/50 text-foreground\"\n )}\n >\n {info.getValue()}%\n </Badge>\n </div>\n ),\n }),\n columnHelper.accessor(\"expansionScore\", {\n id: \"expansionScore\",\n header: \"Expansion Score\",\n cell: (info) => (\n <div\n className=\"inline-flex cursor-pointer\"\n onClick={(e) => {\n e.stopPropagation()\n setScoreModal({ row: info.row.original, type: \"Expansion\" })\n }}\n >\n <Badge\n variant=\"outline\"\n className={cn(\n \"px-2 py-0.5 text-xs font-medium hover:underline decoration-dotted underline-offset-2\",\n info.getValue() >= 70\n ? \"border-blue-200 bg-blue-50 text-blue-700\"\n : \"border-border bg-muted/50 text-foreground\"\n )}\n >\n {info.getValue()}%\n </Badge>\n </div>\n ),\n }),\n columnHelper.accessor(\"growthIndicators\", {\n header: \"Growth Signals\",\n cell: (info) => {\n const indicators = info.getValue()\n if (!indicators.length) {\n return <span className=\"text-xs text-muted-foreground\">None</span>\n }\n return (\n <div className=\"flex items-center gap-1.5\">\n {indicators.slice(0, 2).map((indicator) => (\n <Badge\n key={indicator}\n variant=\"outline\"\n className=\"rounded-md border-blue-200 bg-blue-50 px-2 py-0.5 text-xs font-normal text-blue-700\"\n >\n {indicator}\n </Badge>\n ))}\n </div>\n )\n },\n }),\n columnHelper.accessor(\"industry\", {\n header: \"Industry\",\n cell: (info) => (\n <div className=\"flex items-center gap-1.5\">\n {info.getValue().slice(0, 2).map((industry) => (\n <Badge\n key={industry}\n variant=\"outline\"\n className={cn(\n \"rounded-md px-2 py-0.5 text-xs font-normal\",\n getIndustryColor(industry)\n )}\n >\n {industry}\n </Badge>\n ))}\n </div>\n ),\n }),\n columnHelper.accessor(\"lastInteraction\", {\n header: \"Last interaction\",\n cell: (info) => <span className=\"text-sm\">{info.getValue()}</span>,\n }),\n columnHelper.accessor(\"revenue\", {\n header: \"Revenue\",\n cell: (info) => (\n <span className=\"rounded-md bg-muted/50 px-2 py-0.5 text-xs font-medium\">\n {info.getValue()}\n </span>\n ),\n }),\n columnHelper.accessor(\"headcount\", {\n header: \"Headcount\",\n cell: (info) => (\n <span className=\"rounded-md bg-muted/50 px-2 py-0.5 text-xs font-medium\">\n {info.getValue()}\n </span>\n ),\n }),\n columnHelper.accessor(\"lastFunding\", {\n header: \"Last funding\",\n cell: (info) => (\n <span className=\"rounded-md bg-muted/50 px-2 py-0.5 text-xs font-medium\">\n {info.getValue()}\n </span>\n ),\n }),\n columnHelper.accessor(\"owner\", {\n header: \"Owner\",\n cell: (info) => <span className=\"text-sm\">{info.getValue()}</span>,\n }),\n columnHelper.accessor(\"opportunityCount\", {\n header: \"Opportunity count\",\n cell: (info) => (\n <span className=\"rounded-md bg-muted/50 px-2 py-0.5 text-xs font-medium\">\n {info.getValue()}\n </span>\n ),\n }),\n ],\n [iconMap, entityUrlBuilder]\n )\n\n const table = useReactTable({\n data: filteredRows,\n columns,\n state: {\n sorting,\n columnVisibility,\n },\n onSortingChange: setSorting,\n onColumnVisibilityChange: setColumnVisibility,\n getCoreRowModel: getCoreRowModel(),\n getSortedRowModel: getSortedRowModel(),\n })\n\n const displayColumns = table.getAllLeafColumns().map((column) => {\n const header = column.columnDef.header\n const label = typeof header === \"string\" ? header : column.id\n\n return {\n id: column.id,\n label,\n visible: column.getIsVisible(),\n canHide: column.getCanHide(),\n }\n })\n\n const toggleCategoryFilter = (categoryId: string, option: string) => {\n setSelectedFilters((previous) => toggleFilterValue(previous, categoryId, option))\n }\n\n return (\n <div className=\"relative flex h-full min-h-[560px] flex-col bg-background\">\n <DataTableToolbar\n categories={resolvedFilterCategories}\n selectedFilters={selectedFilters}\n onToggleFilter={toggleCategoryFilter}\n sorting={sorting}\n onSortingChange={setSorting}\n displayColumns={displayColumns}\n onToggleColumn={(columnId) => table.getColumn(columnId)?.toggleVisibility()}\n onResetDisplay={() => setColumnVisibility(DEFAULT_COLUMN_VISIBILITY)}\n />\n\n <DataTableQuickViews\n quickViews={resolvedQuickViews}\n moreViews={resolvedMoreQuickViews}\n activeView={activeQuickView}\n onViewChange={setActiveQuickView}\n />\n\n <div className=\"relative min-h-0 flex-1 overflow-auto border-t border-border\">\n <table className=\"w-max min-w-full border-collapse text-sm\">\n <thead className=\"sticky top-0 z-10 bg-background\">\n {table.getHeaderGroups().map((headerGroup) => (\n <tr key={headerGroup.id} className=\"border-b border-border\">\n {headerGroup.headers.map((header) => (\n <th\n key={header.id}\n className=\"h-10 border-r border-border px-4 text-left text-xs font-medium text-muted-foreground/80 last:border-r-0\"\n >\n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext()\n )}\n </th>\n ))}\n </tr>\n ))}\n </thead>\n <tbody>\n {table.getRowModel().rows.length > 0 ? (\n <>\n {table.getRowModel().rows.map((row) => (\n <tr\n key={row.id}\n onClick={() => onRowClick?.(row.original)}\n className={cn(\n \"group border-b border-border/50 transition-colors hover:bg-muted/30\",\n onRowClick && \"cursor-pointer\",\n )}\n >\n {row.getVisibleCells().map((cell) => (\n <td\n key={cell.id}\n className=\"border-r border-border/40 px-4 py-2.5 align-middle last:border-r-0\"\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </td>\n ))}\n </tr>\n ))}\n <tr>\n <td\n className=\"px-4 py-2 text-xs text-muted-foreground\"\n colSpan={columns.length}\n >\n {table.getRowModel().rows.length} rows\n </td>\n </tr>\n </>\n ) : (\n <tr>\n <td colSpan={columns.length} className=\"h-52 px-4 text-center\">\n <div className=\"flex flex-col items-center gap-1 text-muted-foreground\">\n <SearchX className=\"h-7 w-7 opacity-40\" />\n <p className=\"text-sm font-medium\">No rows found</p>\n <p className=\"text-xs\">Try adjusting your filters or quick views</p>\n </div>\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {scoreModal && (() => {\n const data = SCORE_ANALYSIS[scoreModal.type](scoreModal.row)\n return (\n <ScoreAnalysisModal\n open\n onOpenChange={(open) => { if (!open) setScoreModal(null) }}\n title={data.title}\n description={data.description}\n score={scoreModal.type === \"Risk\" ? scoreModal.row.riskScore : scoreModal.row.expansionScore}\n scoreIntent={scoreModal.type === \"Risk\" ? \"risk\" : \"positive\"}\n whyNow={data.whyNow}\n evidence={data.evidence}\n factors={data.factors}\n onFactorFeedback={onScoreFactorFeedback\n ? (key, type, detail) => onScoreFactorFeedback(scoreModal.row.name, scoreModal.type, key, type, detail)\n : (key, type, detail) => console.log(\"Factor feedback:\", { account: scoreModal.row.name, factor: key, type, detail })\n }\n companyName={scoreModal.row.name}\n opportunityUrl={`https://acme.lightning.force.com/lightning/r/Opportunity/006${scoreModal.row.id}/view`}\n onApprove={() => console.log(\"Approved signal — creating opportunity:\", { account: scoreModal.row.name, type: scoreModal.type })}\n onApproveFeedback={onScoreApproveFeedback\n ? (reasons, detail) => onScoreApproveFeedback(scoreModal.row.name, scoreModal.type, reasons, detail)\n : (reasons, detail) => console.log(\"Approval feedback:\", { account: scoreModal.row.name, reasons, detail })\n }\n onDismiss={onScoreDismissFeedback\n ? (reasons, detail) => onScoreDismissFeedback(scoreModal.row.name, scoreModal.type, reasons, detail)\n : (reasons, detail) => console.log(\"Dismissed signal:\", { account: scoreModal.row.name, reasons, detail })\n }\n />\n )\n })()}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -8,7 +8,22 @@ interface LinkedEntityCellProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
8
8
|
icon?: React.ReactNode;
|
|
9
9
|
external?: boolean;
|
|
10
10
|
onNavigate?: () => void;
|
|
11
|
+
/**
|
|
12
|
+
* When provided, renders a small colored rounded initial badge before the
|
|
13
|
+
* name. The full string is used as the color seed; the displayed letter is
|
|
14
|
+
* the first character. Omit to render no badge.
|
|
15
|
+
*/
|
|
16
|
+
avatarLabel?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional node rendered after the name (a trailing slot), e.g. a separate
|
|
19
|
+
* action link. Does not affect the name's own `href`.
|
|
20
|
+
*/
|
|
21
|
+
trailingAction?: React.ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* Escape-hatch class merged onto the name span/link. Defaults to `text-sm`.
|
|
24
|
+
*/
|
|
25
|
+
nameClassName?: string;
|
|
11
26
|
}
|
|
12
|
-
declare function LinkedEntityCell({ name, href, subtitle, meta, icon, external, onNavigate, className, ...props }: LinkedEntityCellProps): React.JSX.Element;
|
|
27
|
+
declare function LinkedEntityCell({ name, href, subtitle, meta, icon, external, onNavigate, avatarLabel, trailingAction, nameClassName, className, ...props }: LinkedEntityCellProps): React.JSX.Element;
|
|
13
28
|
|
|
14
29
|
export { LinkedEntityCell, type LinkedEntityCellProps };
|
|
@@ -35,6 +35,7 @@ var __objRest = (source, exclude) => {
|
|
|
35
35
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
36
36
|
import { ExternalLink } from "lucide-react";
|
|
37
37
|
import { cn } from "../lib/utils.js";
|
|
38
|
+
import { getEntityColor } from "../lib/entity-color.js";
|
|
38
39
|
function LinkedEntityCell(_a) {
|
|
39
40
|
var _b = _a, {
|
|
40
41
|
name,
|
|
@@ -44,6 +45,9 @@ function LinkedEntityCell(_a) {
|
|
|
44
45
|
icon,
|
|
45
46
|
external = false,
|
|
46
47
|
onNavigate,
|
|
48
|
+
avatarLabel,
|
|
49
|
+
trailingAction,
|
|
50
|
+
nameClassName,
|
|
47
51
|
className
|
|
48
52
|
} = _b, props = __objRest(_b, [
|
|
49
53
|
"name",
|
|
@@ -53,6 +57,9 @@ function LinkedEntityCell(_a) {
|
|
|
53
57
|
"icon",
|
|
54
58
|
"external",
|
|
55
59
|
"onNavigate",
|
|
60
|
+
"avatarLabel",
|
|
61
|
+
"trailingAction",
|
|
62
|
+
"nameClassName",
|
|
56
63
|
"className"
|
|
57
64
|
]);
|
|
58
65
|
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -66,6 +73,18 @@ function LinkedEntityCell(_a) {
|
|
|
66
73
|
className: cn("flex min-w-0 items-center gap-2", className)
|
|
67
74
|
}, props), {
|
|
68
75
|
children: [
|
|
76
|
+
avatarLabel ? /* @__PURE__ */ jsx(
|
|
77
|
+
"span",
|
|
78
|
+
{
|
|
79
|
+
"data-slot": "linked-entity-cell-avatar",
|
|
80
|
+
"aria-hidden": "true",
|
|
81
|
+
className: cn(
|
|
82
|
+
"flex h-6 w-6 shrink-0 items-center justify-center rounded text-[10px] font-bold",
|
|
83
|
+
getEntityColor(avatarLabel)
|
|
84
|
+
),
|
|
85
|
+
children: avatarLabel.slice(0, 1)
|
|
86
|
+
}
|
|
87
|
+
) : null,
|
|
69
88
|
icon ? /* @__PURE__ */ jsx("span", { "data-slot": "linked-entity-cell-icon", className: "shrink-0 text-muted-foreground", children: icon }) : null,
|
|
70
89
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
71
90
|
href ? /* @__PURE__ */ jsx(
|
|
@@ -76,16 +95,27 @@ function LinkedEntityCell(_a) {
|
|
|
76
95
|
target: external ? "_blank" : void 0,
|
|
77
96
|
rel: external ? "noreferrer" : void 0,
|
|
78
97
|
onClick: onNavigate,
|
|
79
|
-
className:
|
|
98
|
+
className: cn(
|
|
99
|
+
"inline-flex max-w-full items-center gap-1 truncate text-sm font-medium text-foreground underline-offset-4 hover:text-primary hover:underline",
|
|
100
|
+
nameClassName
|
|
101
|
+
),
|
|
80
102
|
children: content
|
|
81
103
|
}
|
|
82
|
-
) : /* @__PURE__ */ jsx(
|
|
104
|
+
) : /* @__PURE__ */ jsx(
|
|
105
|
+
"span",
|
|
106
|
+
{
|
|
107
|
+
"data-slot": "linked-entity-cell-name",
|
|
108
|
+
className: cn("block truncate text-sm font-medium text-foreground", nameClassName),
|
|
109
|
+
children: name
|
|
110
|
+
}
|
|
111
|
+
),
|
|
83
112
|
subtitle || meta ? /* @__PURE__ */ jsxs("div", { "data-slot": "linked-entity-cell-meta", className: "mt-0.5 truncate text-xs text-muted-foreground", children: [
|
|
84
113
|
subtitle,
|
|
85
114
|
subtitle && meta ? /* @__PURE__ */ jsx("span", { className: "px-1", children: "\xB7" }) : null,
|
|
86
115
|
meta
|
|
87
116
|
] }) : null
|
|
88
|
-
] })
|
|
117
|
+
] }),
|
|
118
|
+
trailingAction ? /* @__PURE__ */ jsx("span", { "data-slot": "linked-entity-cell-trailing", className: "shrink-0", children: trailingAction }) : null
|
|
89
119
|
]
|
|
90
120
|
})
|
|
91
121
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/linked-entity-cell.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ExternalLink } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\n\nexport interface LinkedEntityCellProps extends React.HTMLAttributes<HTMLDivElement> {\n name: React.ReactNode\n href?: string\n subtitle?: React.ReactNode\n meta?: React.ReactNode\n icon?: React.ReactNode\n external?: boolean\n onNavigate?: () => void\n}\n\nexport function LinkedEntityCell({\n name,\n href,\n subtitle,\n meta,\n icon,\n external = false,\n onNavigate,\n className,\n ...props\n}: LinkedEntityCellProps) {\n const content = (\n <>\n <span className=\"truncate\">{name}</span>\n {external ? <ExternalLink className=\"h-3 w-3 shrink-0 opacity-60\" aria-hidden=\"true\" /> : null}\n </>\n )\n\n return (\n <div\n data-slot=\"linked-entity-cell\"\n className={cn(\"flex min-w-0 items-center gap-2\", className)}\n {...props}\n >\n {icon ? (\n <span data-slot=\"linked-entity-cell-icon\" className=\"shrink-0 text-muted-foreground\">\n {icon}\n </span>\n ) : null}\n <div className=\"min-w-0 flex-1\">\n {href ? (\n <a\n data-slot=\"linked-entity-cell-link\"\n href={href}\n target={external ? \"_blank\" : undefined}\n rel={external ? \"noreferrer\" : undefined}\n onClick={onNavigate}\n className
|
|
1
|
+
{"version":3,"sources":["../../src/components/linked-entity-cell.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ExternalLink } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getEntityColor } from \"../lib/entity-color\"\n\nexport interface LinkedEntityCellProps extends React.HTMLAttributes<HTMLDivElement> {\n name: React.ReactNode\n href?: string\n subtitle?: React.ReactNode\n meta?: React.ReactNode\n icon?: React.ReactNode\n external?: boolean\n onNavigate?: () => void\n /**\n * When provided, renders a small colored rounded initial badge before the\n * name. The full string is used as the color seed; the displayed letter is\n * the first character. Omit to render no badge.\n */\n avatarLabel?: string\n /**\n * Optional node rendered after the name (a trailing slot), e.g. a separate\n * action link. Does not affect the name's own `href`.\n */\n trailingAction?: React.ReactNode\n /**\n * Escape-hatch class merged onto the name span/link. Defaults to `text-sm`.\n */\n nameClassName?: string\n}\n\nexport function LinkedEntityCell({\n name,\n href,\n subtitle,\n meta,\n icon,\n external = false,\n onNavigate,\n avatarLabel,\n trailingAction,\n nameClassName,\n className,\n ...props\n}: LinkedEntityCellProps) {\n const content = (\n <>\n <span className=\"truncate\">{name}</span>\n {external ? <ExternalLink className=\"h-3 w-3 shrink-0 opacity-60\" aria-hidden=\"true\" /> : null}\n </>\n )\n\n return (\n <div\n data-slot=\"linked-entity-cell\"\n className={cn(\"flex min-w-0 items-center gap-2\", className)}\n {...props}\n >\n {avatarLabel ? (\n <span\n data-slot=\"linked-entity-cell-avatar\"\n aria-hidden=\"true\"\n className={cn(\n \"flex h-6 w-6 shrink-0 items-center justify-center rounded text-[10px] font-bold\",\n getEntityColor(avatarLabel),\n )}\n >\n {avatarLabel.slice(0, 1)}\n </span>\n ) : null}\n {icon ? (\n <span data-slot=\"linked-entity-cell-icon\" className=\"shrink-0 text-muted-foreground\">\n {icon}\n </span>\n ) : null}\n <div className=\"min-w-0 flex-1\">\n {href ? (\n <a\n data-slot=\"linked-entity-cell-link\"\n href={href}\n target={external ? \"_blank\" : undefined}\n rel={external ? \"noreferrer\" : undefined}\n onClick={onNavigate}\n className={cn(\n \"inline-flex max-w-full items-center gap-1 truncate text-sm font-medium text-foreground underline-offset-4 hover:text-primary hover:underline\",\n nameClassName,\n )}\n >\n {content}\n </a>\n ) : (\n <span\n data-slot=\"linked-entity-cell-name\"\n className={cn(\"block truncate text-sm font-medium text-foreground\", nameClassName)}\n >\n {name}\n </span>\n )}\n {subtitle || meta ? (\n <div data-slot=\"linked-entity-cell-meta\" className=\"mt-0.5 truncate text-xs text-muted-foreground\">\n {subtitle}\n {subtitle && meta ? <span className=\"px-1\">·</span> : null}\n {meta}\n </div>\n ) : null}\n </div>\n {trailingAction ? (\n <span data-slot=\"linked-entity-cell-trailing\" className=\"shrink-0\">\n {trailingAction}\n </span>\n ) : null}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDI,mBACE,KADF;AA7CJ,SAAS,oBAAoB;AAE7B,SAAS,UAAU;AACnB,SAAS,sBAAsB;AA2BxB,SAAS,iBAAiB,IAaP;AAbO,eAC/B;AAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EA5CF,IAiCiC,IAY5B,kBAZ4B,IAY5B;AAAA,IAXH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAGA,QAAM,UACJ,iCACE;AAAA,wBAAC,UAAK,WAAU,YAAY,gBAAK;AAAA,IAChC,WAAW,oBAAC,gBAAa,WAAU,+BAA8B,eAAY,QAAO,IAAK;AAAA,KAC5F;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,mCAAmC,SAAS;AAAA,OACtD,QAHL;AAAA,MAKE;AAAA,sBACC;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,eAAY;AAAA,YACZ,WAAW;AAAA,cACT;AAAA,cACA,eAAe,WAAW;AAAA,YAC5B;AAAA,YAEC,sBAAY,MAAM,GAAG,CAAC;AAAA;AAAA,QACzB,IACE;AAAA,QACH,OACC,oBAAC,UAAK,aAAU,2BAA0B,WAAU,kCACjD,gBACH,IACE;AAAA,QACJ,qBAAC,SAAI,WAAU,kBACZ;AAAA,iBACC;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV;AAAA,cACA,QAAQ,WAAW,WAAW;AAAA,cAC9B,KAAK,WAAW,eAAe;AAAA,cAC/B,SAAS;AAAA,cACT,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cACF;AAAA,cAEC;AAAA;AAAA,UACH,IAEA;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,WAAW,GAAG,sDAAsD,aAAa;AAAA,cAEhF;AAAA;AAAA,UACH;AAAA,UAED,YAAY,OACX,qBAAC,SAAI,aAAU,2BAA0B,WAAU,iDAChD;AAAA;AAAA,YACA,YAAY,OAAO,oBAAC,UAAK,WAAU,QAAO,kBAAC,IAAU;AAAA,YACrD;AAAA,aACH,IACE;AAAA,WACN;AAAA,QACC,iBACC,oBAAC,UAAK,aAAU,+BAA8B,WAAU,YACrD,0BACH,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -178,6 +178,8 @@ function VirtualizedDataTable({
|
|
|
178
178
|
const newDir = activeSortColumn === sortKey ? activeSortDirection === "asc" ? "desc" : "asc" : "asc";
|
|
179
179
|
onColumnSort(sortKey, newDir);
|
|
180
180
|
} : void 0;
|
|
181
|
+
const headerDef = header.column.columnDef.header;
|
|
182
|
+
const headerTitle = typeof headerDef === "string" ? headerDef : void 0;
|
|
181
183
|
return /* @__PURE__ */ jsxs(
|
|
182
184
|
"div",
|
|
183
185
|
{
|
|
@@ -198,10 +200,10 @@ function VirtualizedDataTable({
|
|
|
198
200
|
"button",
|
|
199
201
|
{
|
|
200
202
|
type: "button",
|
|
201
|
-
className: "flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors",
|
|
203
|
+
className: "flex min-w-0 flex-1 items-center gap-1 text-xs leading-4 hover:text-foreground transition-colors",
|
|
202
204
|
onClick: handleHeaderClick,
|
|
203
205
|
children: [
|
|
204
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: flexRender(
|
|
206
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-xs leading-4", title: headerTitle, children: flexRender(headerDef, header.getContext()) }),
|
|
205
207
|
sortIcon
|
|
206
208
|
]
|
|
207
209
|
}
|
|
@@ -209,20 +211,14 @@ function VirtualizedDataTable({
|
|
|
209
211
|
"button",
|
|
210
212
|
{
|
|
211
213
|
type: "button",
|
|
212
|
-
className: "flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors",
|
|
214
|
+
className: "flex min-w-0 flex-1 items-center gap-1 text-xs leading-4 hover:text-foreground transition-colors",
|
|
213
215
|
onClick: header.column.getToggleSortingHandler(),
|
|
214
216
|
children: [
|
|
215
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: flexRender(
|
|
216
|
-
header.column.columnDef.header,
|
|
217
|
-
header.getContext()
|
|
218
|
-
) }),
|
|
217
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-xs leading-4", title: headerTitle, children: flexRender(headerDef, header.getContext()) }),
|
|
219
218
|
header.column.getIsSorted() === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "w-3 h-3 shrink-0" }) : header.column.getIsSorted() === "desc" ? /* @__PURE__ */ jsx(ArrowDown, { className: "w-3 h-3 shrink-0" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "w-3 h-3 shrink-0 opacity-40" })
|
|
220
219
|
]
|
|
221
220
|
}
|
|
222
|
-
) : /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: flexRender(
|
|
223
|
-
header.column.columnDef.header,
|
|
224
|
-
header.getContext()
|
|
225
|
-
) }),
|
|
221
|
+
) : /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs leading-4", title: headerTitle, children: flexRender(headerDef, header.getContext()) }),
|
|
226
222
|
(canServerSort || header.column.getCanSort() || header.column.getCanHide()) && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
227
223
|
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
228
224
|
"button",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/virtualized-data-table.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { useVirtualizer } from \"@tanstack/react-virtual\"\nimport {\n useReactTable,\n getCoreRowModel,\n flexRender,\n type ColumnDef,\n type SortingState,\n type ColumnFiltersState,\n type VisibilityState,\n type ColumnSizingState,\n type OnChangeFn,\n} from \"@tanstack/react-table\"\nimport type { RowData } from \"@tanstack/react-table\"\nimport { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown, EyeOff, Check, SearchX, Loader2 } from \"lucide-react\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\"\n\nimport { cn } from \"../lib/utils\"\n\ndeclare module \"@tanstack/react-table\" {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface ColumnMeta<TData extends RowData, TValue> {\n /** Server-side sort key for this column. Enables sort in the header menu when onColumnSort is also provided. */\n sortKey?: string\n }\n}\n\nexport interface VirtualizedDataTableProps<TData> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n columns: ColumnDef<TData, any>[]\n data: TData[]\n\n // Virtualization\n height?: number | string\n estimateRowHeight?: number\n overscan?: number\n\n // Row interaction\n onRowClick?: (row: TData) => void\n getRowId?: (original: TData, index: number) => string\n\n // Infinite scroll\n onReachBottom?: () => void\n reachBottomThreshold?: number\n hasMore?: boolean\n isFetchingMore?: boolean\n\n // Column resizing\n enableColumnResizing?: boolean\n columnResizeMode?: \"onChange\" | \"onEnd\"\n columnSizing?: ColumnSizingState\n onColumnSizingChange?: OnChangeFn<ColumnSizingState>\n\n // Server-driven state (controlled) — omit for internal state\n sorting?: SortingState\n onSortingChange?: OnChangeFn<SortingState>\n columnFilters?: ColumnFiltersState\n onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>\n columnVisibility?: VisibilityState\n onColumnVisibilityChange?: OnChangeFn<VisibilityState>\n\n // Loading / Empty state\n isLoading?: boolean\n emptyIcon?: React.ReactNode\n emptyMessage?: string\n emptyDescription?: string\n\n // Column header menu\n /** Called when user requests sorting from column header. columnId is the column's meta.sortKey. */\n onColumnSort?: (columnId: string, direction: \"asc\" | \"desc\") => void\n /** Called when user hides a column from the header menu. */\n onColumnHide?: (columnId: string) => void\n /** The currently active sort column ID — matches a column's meta.sortKey. Used for visual indicators and aria-sort. */\n activeSortColumn?: string | null\n /** The current sort direction. Used for visual indicators and aria-sort. */\n activeSortDirection?: \"asc\" | \"desc\"\n\n // Styling\n className?: string\n}\n\nexport function VirtualizedDataTable<TData>({\n columns,\n data,\n height = 600,\n estimateRowHeight = 48,\n overscan = 8,\n onRowClick,\n getRowId,\n onReachBottom,\n reachBottomThreshold = 5,\n hasMore = true,\n isFetchingMore,\n enableColumnResizing = false,\n columnResizeMode = \"onEnd\",\n columnSizing,\n onColumnSizingChange,\n sorting,\n onSortingChange,\n columnFilters,\n onColumnFiltersChange,\n columnVisibility,\n onColumnVisibilityChange,\n onColumnSort,\n onColumnHide,\n activeSortColumn,\n activeSortDirection,\n isLoading,\n emptyIcon,\n emptyMessage = \"No rows found\",\n emptyDescription = \"Try adjusting your filters\",\n className,\n}: VirtualizedDataTableProps<TData>) {\n // Controlled/uncontrolled state for sorting\n const [internalSorting, setInternalSorting] = React.useState<SortingState>([])\n const resolvedSorting = sorting ?? internalSorting\n const resolvedOnSortingChange = onSortingChange ?? setInternalSorting\n\n // Controlled/uncontrolled state for column filters\n const [internalColumnFilters, setInternalColumnFilters] =\n React.useState<ColumnFiltersState>([])\n const resolvedColumnFilters = columnFilters ?? internalColumnFilters\n const resolvedOnColumnFiltersChange =\n onColumnFiltersChange ?? setInternalColumnFilters\n\n // Controlled/uncontrolled state for column visibility\n const [internalColumnVisibility, setInternalColumnVisibility] =\n React.useState<VisibilityState>({})\n const resolvedColumnVisibility = columnVisibility ?? internalColumnVisibility\n const resolvedOnColumnVisibilityChange =\n onColumnVisibilityChange ?? setInternalColumnVisibility\n\n // Controlled/uncontrolled state for column sizing\n const [internalColumnSizing, setInternalColumnSizing] =\n React.useState<ColumnSizingState>({})\n const resolvedColumnSizing = columnSizing ?? internalColumnSizing\n const resolvedOnColumnSizingChange =\n onColumnSizingChange ?? setInternalColumnSizing\n\n // TanStack Table setup\n const table = useReactTable({\n data,\n columns,\n ...(getRowId ? { getRowId } : {}),\n state: {\n sorting: resolvedSorting,\n columnFilters: resolvedColumnFilters,\n columnVisibility: resolvedColumnVisibility,\n columnSizing: resolvedColumnSizing,\n },\n onSortingChange: resolvedOnSortingChange,\n onColumnFiltersChange: resolvedOnColumnFiltersChange,\n onColumnVisibilityChange: resolvedOnColumnVisibilityChange,\n onColumnSizingChange: resolvedOnColumnSizingChange,\n enableColumnResizing,\n columnResizeMode,\n manualSorting: true,\n manualFiltering: true,\n manualPagination: true,\n getCoreRowModel: getCoreRowModel(),\n })\n\n // Virtualizer setup\n const scrollContainerRef = React.useRef<HTMLDivElement>(null)\n const rows = table.getRowModel().rows\n\n const virtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollContainerRef.current,\n estimateSize: () => estimateRowHeight,\n overscan,\n measureElement: (element) => element.getBoundingClientRect().height,\n })\n\n // Infinite scroll detection\n const lastTriggeredDataLengthRef = React.useRef<number>(0)\n\n // Derive a stable primitive for the last visible virtual-item index so the\n // effect below doesn't re-run on every render (getVirtualItems() returns a\n // new array reference each call).\n const virtualItems = virtualizer.getVirtualItems()\n const lastVirtualItemIndex =\n virtualItems.length > 0\n ? virtualItems[virtualItems.length - 1].index\n : -1\n\n React.useEffect(() => {\n if (!onReachBottom || isFetchingMore || hasMore === false) return\n if (lastVirtualItemIndex < 0) return\n if (lastVirtualItemIndex < rows.length - reachBottomThreshold) return\n\n // Prevent re-firing until data.length changes (i.e. new page loaded).\n if (lastTriggeredDataLengthRef.current === data.length) return\n lastTriggeredDataLengthRef.current = data.length\n\n onReachBottom()\n }, [\n lastVirtualItemIndex,\n rows.length,\n data.length,\n onReachBottom,\n isFetchingMore,\n hasMore,\n reachBottomThreshold,\n ])\n\n return (\n <div className={cn(\n \"w-full\",\n typeof height === \"string\" && height.trim().endsWith(\"%\") && \"h-full\",\n className,\n )}>\n <div\n ref={scrollContainerRef}\n className=\"relative overflow-auto\"\n style={{\n height: typeof height === \"number\" ? `${height}px` : height,\n contain: \"strict\",\n }}\n role=\"table\"\n aria-rowcount={data.length}\n aria-colcount={table.getVisibleLeafColumns().length}\n >\n {/* Sticky header */}\n <div className=\"sticky top-0 z-10 bg-background\" role=\"rowgroup\">\n {table.getHeaderGroups().map((headerGroup) => (\n <div\n key={headerGroup.id}\n className=\"flex w-max min-w-full border-b border-border/50\"\n role=\"row\"\n >\n {headerGroup.headers.map((header, colIdx) => {\n const sortKey = header.column.columnDef.meta?.sortKey\n const canServerSort = Boolean(sortKey && onColumnSort)\n\n const resolvedAriaSort = (() => {\n if (activeSortColumn !== undefined) {\n // Server-driven\n if (!sortKey) return undefined\n if (activeSortColumn === sortKey) return activeSortDirection === \"asc\" ? \"ascending\" as const : \"descending\" as const\n return \"none\" as const\n }\n // Fallback to TanStack state\n const sorted = header.column.getIsSorted()\n if (sorted === \"asc\") return \"ascending\" as const\n if (sorted === \"desc\") return \"descending\" as const\n if (header.column.getCanSort()) return \"none\" as const\n return undefined\n })()\n\n const sortIcon = (() => {\n if (!canServerSort) return null\n if (activeSortColumn === sortKey && activeSortDirection === \"asc\") return <ArrowUp className=\"w-3 h-3 shrink-0\" />\n if (activeSortColumn === sortKey && activeSortDirection === \"desc\") return <ArrowDown className=\"w-3 h-3 shrink-0\" />\n return <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n })()\n\n const handleHeaderClick = canServerSort ? () => {\n const newDir = activeSortColumn === sortKey\n ? (activeSortDirection === \"asc\" ? \"desc\" : \"asc\")\n : \"asc\"\n onColumnSort!(sortKey!, newDir)\n } : undefined\n\n return (\n <div\n key={header.id}\n className={cn(\n \"group/header h-9 min-w-0 px-3 flex items-center text-xs font-medium text-muted-foreground whitespace-nowrap relative\",\n header.column.getCanResize() && \"pr-4\",\n )}\n style={{\n width: header.getSize(),\n minWidth: header.getSize(),\n }}\n role=\"columnheader\"\n aria-colindex={colIdx + 1}\n aria-sort={resolvedAriaSort}\n >\n {header.isPlaceholder ? null : (\n <>\n {canServerSort ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors\"\n onClick={handleHeaderClick}\n >\n <span className=\"min-w-0 truncate\">\n {flexRender(header.column.columnDef.header, header.getContext())}\n </span>\n {sortIcon}\n </button>\n ) : header.column.getCanSort() ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors\"\n onClick={header.column.getToggleSortingHandler()}\n >\n <span className=\"min-w-0 truncate\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </span>\n {header.column.getIsSorted() === \"asc\" ? (\n <ArrowUp className=\"w-3 h-3 shrink-0\" />\n ) : header.column.getIsSorted() === \"desc\" ? (\n <ArrowDown className=\"w-3 h-3 shrink-0\" />\n ) : (\n <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n )}\n </button>\n ) : (\n <span className=\"min-w-0 flex-1 truncate\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </span>\n )}\n {(canServerSort || header.column.getCanSort() || header.column.getCanHide()) && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"ml-1 inline-flex shrink-0 items-center hover:text-foreground transition-all opacity-0 group-hover/header:opacity-100\"\n aria-label=\"Column actions\"\n >\n <ChevronDown className=\"w-3 h-3\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"asc\")}\n >\n <ArrowUp className=\"w-3.5 h-3.5 mr-2\" />\n Sort ascending\n {activeSortColumn === sortKey && activeSortDirection === \"asc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"desc\")}\n >\n <ArrowDown className=\"w-3.5 h-3.5 mr-2\" />\n Sort descending\n {activeSortColumn === sortKey && activeSortDirection === \"desc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n {header.column.getCanHide() && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={() => onColumnHide ? onColumnHide(header.column.id) : header.column.toggleVisibility(false)}\n >\n <EyeOff className=\"w-3.5 h-3.5 mr-2\" />\n Hide column\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </>\n )}\n {header.column.getCanResize() && (\n <div\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n className={cn(\n \"absolute right-0 top-0 z-20 h-full w-4 -mr-2 cursor-col-resize select-none touch-none\",\n \"after:absolute after:right-2 after:top-1 after:h-[calc(100%-0.5rem)] after:w-px after:rounded-full\",\n \"after:bg-border/70 after:transition-colors hover:after:bg-primary/60\",\n header.column.getIsResizing() && \"after:bg-primary/70\",\n )}\n role=\"separator\"\n aria-orientation=\"vertical\"\n />\n )}\n </div>\n )\n })}\n </div>\n ))}\n </div>\n\n {/* Virtualized body or empty state */}\n {rows.length > 0 ? (\n <div\n role=\"rowgroup\"\n style={{\n height: virtualizer.getTotalSize(),\n width: \"100%\",\n position: \"relative\",\n }}\n >\n {virtualizer.getVirtualItems().map((virtualRow) => {\n const row = rows[virtualRow.index]\n return (\n <div\n key={row.id}\n data-index={virtualRow.index}\n ref={virtualizer.measureElement}\n className={cn(\n \"absolute left-0 w-max min-w-full flex group transition-colors\",\n onRowClick && \"cursor-pointer\",\n )}\n style={{\n transform: `translateY(${virtualRow.start}px)`,\n }}\n role=\"row\"\n aria-rowindex={virtualRow.index + 2}\n onClick={() => onRowClick?.(row.original)}\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault()\n onRowClick(row.original)\n }\n }\n : undefined\n }\n >\n {row.getVisibleCells().map((cell, colIdx) => (\n <div\n key={cell.id}\n className=\"px-3 py-3 flex items-center whitespace-nowrap group-hover:bg-muted/50\"\n style={{\n width: cell.column.getSize(),\n minWidth: cell.column.getSize(),\n }}\n role=\"cell\"\n aria-colindex={colIdx + 1}\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </div>\n ))}\n </div>\n )\n })}\n </div>\n ) : isLoading ? (\n <div className=\"flex flex-col items-center justify-center gap-2 text-muted-foreground py-20\">\n <Loader2 className=\"h-7 w-7 animate-spin opacity-60\" />\n <p className=\"text-sm font-medium\">Loading...</p>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center gap-1 text-muted-foreground py-20\">\n {emptyIcon ?? <SearchX className=\"h-7 w-7 opacity-40\" />}\n <p className=\"text-sm font-medium\">{emptyMessage}</p>\n <p className=\"text-xs\">{emptyDescription}</p>\n </div>\n )}\n\n {/* Loading indicator */}\n {isFetchingMore && (\n <div className=\"flex items-center justify-center py-4\">\n <Loader2 className=\"h-5 w-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/virtualized-data-table.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { useVirtualizer } from \"@tanstack/react-virtual\"\nimport {\n useReactTable,\n getCoreRowModel,\n flexRender,\n type ColumnDef,\n type SortingState,\n type ColumnFiltersState,\n type VisibilityState,\n type ColumnSizingState,\n type OnChangeFn,\n} from \"@tanstack/react-table\"\nimport type { RowData } from \"@tanstack/react-table\"\nimport { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown, EyeOff, Check, SearchX, Loader2 } from \"lucide-react\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\"\n\nimport { cn } from \"../lib/utils\"\n\ndeclare module \"@tanstack/react-table\" {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface ColumnMeta<TData extends RowData, TValue> {\n /** Server-side sort key for this column. Enables sort in the header menu when onColumnSort is also provided. */\n sortKey?: string\n }\n}\n\nexport interface VirtualizedDataTableProps<TData> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n columns: ColumnDef<TData, any>[]\n data: TData[]\n\n // Virtualization\n height?: number | string\n estimateRowHeight?: number\n overscan?: number\n\n // Row interaction\n onRowClick?: (row: TData) => void\n getRowId?: (original: TData, index: number) => string\n\n // Infinite scroll\n onReachBottom?: () => void\n reachBottomThreshold?: number\n hasMore?: boolean\n isFetchingMore?: boolean\n\n // Column resizing\n enableColumnResizing?: boolean\n columnResizeMode?: \"onChange\" | \"onEnd\"\n columnSizing?: ColumnSizingState\n onColumnSizingChange?: OnChangeFn<ColumnSizingState>\n\n // Server-driven state (controlled) — omit for internal state\n sorting?: SortingState\n onSortingChange?: OnChangeFn<SortingState>\n columnFilters?: ColumnFiltersState\n onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>\n columnVisibility?: VisibilityState\n onColumnVisibilityChange?: OnChangeFn<VisibilityState>\n\n // Loading / Empty state\n isLoading?: boolean\n emptyIcon?: React.ReactNode\n emptyMessage?: string\n emptyDescription?: string\n\n // Column header menu\n /** Called when user requests sorting from column header. columnId is the column's meta.sortKey. */\n onColumnSort?: (columnId: string, direction: \"asc\" | \"desc\") => void\n /** Called when user hides a column from the header menu. */\n onColumnHide?: (columnId: string) => void\n /** The currently active sort column ID — matches a column's meta.sortKey. Used for visual indicators and aria-sort. */\n activeSortColumn?: string | null\n /** The current sort direction. Used for visual indicators and aria-sort. */\n activeSortDirection?: \"asc\" | \"desc\"\n\n // Styling\n className?: string\n}\n\nexport function VirtualizedDataTable<TData>({\n columns,\n data,\n height = 600,\n estimateRowHeight = 48,\n overscan = 8,\n onRowClick,\n getRowId,\n onReachBottom,\n reachBottomThreshold = 5,\n hasMore = true,\n isFetchingMore,\n enableColumnResizing = false,\n columnResizeMode = \"onEnd\",\n columnSizing,\n onColumnSizingChange,\n sorting,\n onSortingChange,\n columnFilters,\n onColumnFiltersChange,\n columnVisibility,\n onColumnVisibilityChange,\n onColumnSort,\n onColumnHide,\n activeSortColumn,\n activeSortDirection,\n isLoading,\n emptyIcon,\n emptyMessage = \"No rows found\",\n emptyDescription = \"Try adjusting your filters\",\n className,\n}: VirtualizedDataTableProps<TData>) {\n // Controlled/uncontrolled state for sorting\n const [internalSorting, setInternalSorting] = React.useState<SortingState>([])\n const resolvedSorting = sorting ?? internalSorting\n const resolvedOnSortingChange = onSortingChange ?? setInternalSorting\n\n // Controlled/uncontrolled state for column filters\n const [internalColumnFilters, setInternalColumnFilters] =\n React.useState<ColumnFiltersState>([])\n const resolvedColumnFilters = columnFilters ?? internalColumnFilters\n const resolvedOnColumnFiltersChange =\n onColumnFiltersChange ?? setInternalColumnFilters\n\n // Controlled/uncontrolled state for column visibility\n const [internalColumnVisibility, setInternalColumnVisibility] =\n React.useState<VisibilityState>({})\n const resolvedColumnVisibility = columnVisibility ?? internalColumnVisibility\n const resolvedOnColumnVisibilityChange =\n onColumnVisibilityChange ?? setInternalColumnVisibility\n\n // Controlled/uncontrolled state for column sizing\n const [internalColumnSizing, setInternalColumnSizing] =\n React.useState<ColumnSizingState>({})\n const resolvedColumnSizing = columnSizing ?? internalColumnSizing\n const resolvedOnColumnSizingChange =\n onColumnSizingChange ?? setInternalColumnSizing\n\n // TanStack Table setup\n const table = useReactTable({\n data,\n columns,\n ...(getRowId ? { getRowId } : {}),\n state: {\n sorting: resolvedSorting,\n columnFilters: resolvedColumnFilters,\n columnVisibility: resolvedColumnVisibility,\n columnSizing: resolvedColumnSizing,\n },\n onSortingChange: resolvedOnSortingChange,\n onColumnFiltersChange: resolvedOnColumnFiltersChange,\n onColumnVisibilityChange: resolvedOnColumnVisibilityChange,\n onColumnSizingChange: resolvedOnColumnSizingChange,\n enableColumnResizing,\n columnResizeMode,\n manualSorting: true,\n manualFiltering: true,\n manualPagination: true,\n getCoreRowModel: getCoreRowModel(),\n })\n\n // Virtualizer setup\n const scrollContainerRef = React.useRef<HTMLDivElement>(null)\n const rows = table.getRowModel().rows\n\n const virtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollContainerRef.current,\n estimateSize: () => estimateRowHeight,\n overscan,\n measureElement: (element) => element.getBoundingClientRect().height,\n })\n\n // Infinite scroll detection\n const lastTriggeredDataLengthRef = React.useRef<number>(0)\n\n // Derive a stable primitive for the last visible virtual-item index so the\n // effect below doesn't re-run on every render (getVirtualItems() returns a\n // new array reference each call).\n const virtualItems = virtualizer.getVirtualItems()\n const lastVirtualItemIndex =\n virtualItems.length > 0\n ? virtualItems[virtualItems.length - 1].index\n : -1\n\n React.useEffect(() => {\n if (!onReachBottom || isFetchingMore || hasMore === false) return\n if (lastVirtualItemIndex < 0) return\n if (lastVirtualItemIndex < rows.length - reachBottomThreshold) return\n\n // Prevent re-firing until data.length changes (i.e. new page loaded).\n if (lastTriggeredDataLengthRef.current === data.length) return\n lastTriggeredDataLengthRef.current = data.length\n\n onReachBottom()\n }, [\n lastVirtualItemIndex,\n rows.length,\n data.length,\n onReachBottom,\n isFetchingMore,\n hasMore,\n reachBottomThreshold,\n ])\n\n return (\n <div className={cn(\n \"w-full\",\n typeof height === \"string\" && height.trim().endsWith(\"%\") && \"h-full\",\n className,\n )}>\n <div\n ref={scrollContainerRef}\n className=\"relative overflow-auto\"\n style={{\n height: typeof height === \"number\" ? `${height}px` : height,\n contain: \"strict\",\n }}\n role=\"table\"\n aria-rowcount={data.length}\n aria-colcount={table.getVisibleLeafColumns().length}\n >\n {/* Sticky header */}\n <div className=\"sticky top-0 z-10 bg-background\" role=\"rowgroup\">\n {table.getHeaderGroups().map((headerGroup) => (\n <div\n key={headerGroup.id}\n className=\"flex w-max min-w-full border-b border-border/50\"\n role=\"row\"\n >\n {headerGroup.headers.map((header, colIdx) => {\n const sortKey = header.column.columnDef.meta?.sortKey\n const canServerSort = Boolean(sortKey && onColumnSort)\n\n const resolvedAriaSort = (() => {\n if (activeSortColumn !== undefined) {\n // Server-driven\n if (!sortKey) return undefined\n if (activeSortColumn === sortKey) return activeSortDirection === \"asc\" ? \"ascending\" as const : \"descending\" as const\n return \"none\" as const\n }\n // Fallback to TanStack state\n const sorted = header.column.getIsSorted()\n if (sorted === \"asc\") return \"ascending\" as const\n if (sorted === \"desc\") return \"descending\" as const\n if (header.column.getCanSort()) return \"none\" as const\n return undefined\n })()\n\n const sortIcon = (() => {\n if (!canServerSort) return null\n if (activeSortColumn === sortKey && activeSortDirection === \"asc\") return <ArrowUp className=\"w-3 h-3 shrink-0\" />\n if (activeSortColumn === sortKey && activeSortDirection === \"desc\") return <ArrowDown className=\"w-3 h-3 shrink-0\" />\n return <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n })()\n\n const handleHeaderClick = canServerSort ? () => {\n const newDir = activeSortColumn === sortKey\n ? (activeSortDirection === \"asc\" ? \"desc\" : \"asc\")\n : \"asc\"\n onColumnSort!(sortKey!, newDir)\n } : undefined\n\n const headerDef = header.column.columnDef.header\n // When the header is a plain string, expose it as a native title\n // tooltip so truncated headers remain readable. Non-string\n // ReactNode headers render no title.\n const headerTitle =\n typeof headerDef === \"string\" ? headerDef : undefined\n\n return (\n <div\n key={header.id}\n className={cn(\n \"group/header h-9 min-w-0 px-3 flex items-center text-xs font-medium text-muted-foreground whitespace-nowrap relative\",\n header.column.getCanResize() && \"pr-4\",\n )}\n style={{\n width: header.getSize(),\n minWidth: header.getSize(),\n }}\n role=\"columnheader\"\n aria-colindex={colIdx + 1}\n aria-sort={resolvedAriaSort}\n >\n {header.isPlaceholder ? null : (\n <>\n {canServerSort ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 text-xs leading-4 hover:text-foreground transition-colors\"\n onClick={handleHeaderClick}\n >\n <span className=\"min-w-0 truncate text-xs leading-4\" title={headerTitle}>\n {flexRender(headerDef, header.getContext())}\n </span>\n {sortIcon}\n </button>\n ) : header.column.getCanSort() ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 text-xs leading-4 hover:text-foreground transition-colors\"\n onClick={header.column.getToggleSortingHandler()}\n >\n <span className=\"min-w-0 truncate text-xs leading-4\" title={headerTitle}>\n {flexRender(headerDef, header.getContext())}\n </span>\n {header.column.getIsSorted() === \"asc\" ? (\n <ArrowUp className=\"w-3 h-3 shrink-0\" />\n ) : header.column.getIsSorted() === \"desc\" ? (\n <ArrowDown className=\"w-3 h-3 shrink-0\" />\n ) : (\n <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n )}\n </button>\n ) : (\n <span className=\"min-w-0 flex-1 truncate text-xs leading-4\" title={headerTitle}>\n {flexRender(headerDef, header.getContext())}\n </span>\n )}\n {(canServerSort || header.column.getCanSort() || header.column.getCanHide()) && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"ml-1 inline-flex shrink-0 items-center hover:text-foreground transition-all opacity-0 group-hover/header:opacity-100\"\n aria-label=\"Column actions\"\n >\n <ChevronDown className=\"w-3 h-3\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"asc\")}\n >\n <ArrowUp className=\"w-3.5 h-3.5 mr-2\" />\n Sort ascending\n {activeSortColumn === sortKey && activeSortDirection === \"asc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"desc\")}\n >\n <ArrowDown className=\"w-3.5 h-3.5 mr-2\" />\n Sort descending\n {activeSortColumn === sortKey && activeSortDirection === \"desc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n {header.column.getCanHide() && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={() => onColumnHide ? onColumnHide(header.column.id) : header.column.toggleVisibility(false)}\n >\n <EyeOff className=\"w-3.5 h-3.5 mr-2\" />\n Hide column\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </>\n )}\n {header.column.getCanResize() && (\n <div\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n className={cn(\n \"absolute right-0 top-0 z-20 h-full w-4 -mr-2 cursor-col-resize select-none touch-none\",\n \"after:absolute after:right-2 after:top-1 after:h-[calc(100%-0.5rem)] after:w-px after:rounded-full\",\n \"after:bg-border/70 after:transition-colors hover:after:bg-primary/60\",\n header.column.getIsResizing() && \"after:bg-primary/70\",\n )}\n role=\"separator\"\n aria-orientation=\"vertical\"\n />\n )}\n </div>\n )\n })}\n </div>\n ))}\n </div>\n\n {/* Virtualized body or empty state */}\n {rows.length > 0 ? (\n <div\n role=\"rowgroup\"\n style={{\n height: virtualizer.getTotalSize(),\n width: \"100%\",\n position: \"relative\",\n }}\n >\n {virtualizer.getVirtualItems().map((virtualRow) => {\n const row = rows[virtualRow.index]\n return (\n <div\n key={row.id}\n data-index={virtualRow.index}\n ref={virtualizer.measureElement}\n className={cn(\n \"absolute left-0 w-max min-w-full flex group transition-colors\",\n onRowClick && \"cursor-pointer\",\n )}\n style={{\n transform: `translateY(${virtualRow.start}px)`,\n }}\n role=\"row\"\n aria-rowindex={virtualRow.index + 2}\n onClick={() => onRowClick?.(row.original)}\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault()\n onRowClick(row.original)\n }\n }\n : undefined\n }\n >\n {row.getVisibleCells().map((cell, colIdx) => (\n <div\n key={cell.id}\n className=\"px-3 py-3 flex items-center whitespace-nowrap group-hover:bg-muted/50\"\n style={{\n width: cell.column.getSize(),\n minWidth: cell.column.getSize(),\n }}\n role=\"cell\"\n aria-colindex={colIdx + 1}\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </div>\n ))}\n </div>\n )\n })}\n </div>\n ) : isLoading ? (\n <div className=\"flex flex-col items-center justify-center gap-2 text-muted-foreground py-20\">\n <Loader2 className=\"h-7 w-7 animate-spin opacity-60\" />\n <p className=\"text-sm font-medium\">Loading...</p>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center gap-1 text-muted-foreground py-20\">\n {emptyIcon ?? <SearchX className=\"h-7 w-7 opacity-40\" />}\n <p className=\"text-sm font-medium\">{emptyMessage}</p>\n <p className=\"text-xs\">{emptyDescription}</p>\n </div>\n )}\n\n {/* Loading indicator */}\n {isFetchingMore && (\n <div className=\"flex items-center justify-center py-4\">\n <Loader2 className=\"h-5 w-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { cn } from './lib/utils.js';
|
|
2
|
+
export { getEntityColor } from './lib/entity-color.js';
|
|
2
3
|
export { BRAND_GRAPHICS, BRAND_ICONS } from './lib/icons.js';
|
|
3
4
|
export { ProfileLike, displayName, getInitials, shortName } from './lib/user-display.js';
|
|
4
5
|
export { useIsMobile } from './hooks/use-mobile.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cn } from "./lib/utils.js";
|
|
2
|
+
import { getEntityColor } from "./lib/entity-color.js";
|
|
2
3
|
import { BRAND_ICONS, BRAND_GRAPHICS } from "./lib/icons.js";
|
|
3
4
|
import { displayName, getInitials, shortName } from "./lib/user-display.js";
|
|
4
5
|
import { useIsMobile } from "./hooks/use-mobile.js";
|
|
@@ -128,6 +129,7 @@ export {
|
|
|
128
129
|
SignalPriorityPopover,
|
|
129
130
|
cn,
|
|
130
131
|
displayName,
|
|
132
|
+
getEntityColor,
|
|
131
133
|
getInitials,
|
|
132
134
|
shortName,
|
|
133
135
|
useIsMobile
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @handled-ai/design-system\n * UI components and utilities (shadcn-style, New York)\n */\n\n// Utilities\nexport { cn } from \"./lib/utils\"\nexport { BRAND_ICONS, BRAND_GRAPHICS } from \"./lib/icons\"\nexport { displayName, getInitials, shortName, type ProfileLike } from \"./lib/user-display\"\n\n// Hooks\nexport { useIsMobile } from \"./hooks/use-mobile\"\n\n// Components (light — no recharts/nivo/three transitive deps)\nexport * from \"./components/activity-detail\"\nexport * from \"./components/activity-log\"\nexport * from \"./components/agent-popover\"\nexport * from \"./components/agent-widget\"\nexport * from \"./components/avatar\"\nexport * from \"./components/badge\"\nexport * from \"./components/button\"\nexport * from \"./components/card\"\nexport * from \"./components/case-panel-email-composer\"\nexport * from \"./components/email-body\"\nexport * from \"./components/email-composer-row\"\nexport * from \"./components/email-display-helpers\"\nexport * from \"./components/email-preview-card\"\nexport * from \"./components/email-recipient-field\"\nexport * from \"./components/email-send-bar\"\nexport * from \"./components/case-panel-activity-timeline\"\nexport * from \"./components/case-panel-detail\"\nexport * from \"./components/case-panel-why\"\nexport { CollapsibleSection, type CollapsibleSectionProps } from \"./components/collapsible-section\"\nexport * from \"./components/comment-composer\"\nexport * from \"./components/compliance-badge\"\nexport * from \"./components/contact-chip\"\nexport * from \"./components/contact-list\"\nexport * from \"./components/contextual-quick-action-launcher\"\nexport * from \"./components/conversation-panel\"\nexport * from \"./components/dashboard-cards\"\nexport * from \"./components/data-table\"\nexport * from \"./components/data-table-condition-filter\"\nexport * from \"./components/data-table-display\"\nexport * from \"./components/data-table-filter\"\nexport * from \"./components/data-table-quick-views\"\nexport * from \"./components/data-table-toolbar\"\nexport * from \"./components/detail-view\"\nexport * from \"./components/detail-drawer\"\nexport * from \"./components/dialog\"\nexport * from \"./components/dropdown-menu\"\nexport * from \"./components/empty-state\"\nexport * from \"./components/entity-panel\"\nexport * from \"./components/related-record-action-card\"\nexport { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions, InlineFeedbackControl } from \"./components/feedback-primitives\"\nexport type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData, InlineFeedbackControlProps } from \"./components/feedback-primitives\"\nexport { SignalPriorityPopover } from \"./components/signal-priority-popover\"\nexport type { SignalPriorityPopoverProps, SignalPriorityScoreDisplay, PriorityFactor } from \"./components/signal-priority-popover\"\nexport * from \"./components/filter-chip\"\nexport * from \"./components/inbox-row\"\nexport * from \"./components/inbox-toolbar\"\nexport * from \"./components/inline-banner\"\nexport * from \"./components/input\"\nexport * from \"./components/insights-filter-bar\"\nexport * from \"./components/days-open-cell\"\nexport * from \"./components/linked-entity-cell\"\nexport * from \"./components/item-list\"\nexport * from \"./components/item-list-display\"\nexport * from \"./components/item-list-filter\"\nexport * from \"./components/item-list-toolbar\"\nexport * from \"./components/kbd-hint\"\nexport * from \"./components/label\"\nexport * from \"./components/message\"\nexport * from \"./components/metric-card\"\nexport * from \"./components/owner-chips\"\nexport * from \"./components/performance-metrics-table\"\nexport * from \"./components/pill\"\nexport * from \"./components/preview-list\"\nexport * from \"./components/progress\"\nexport * from \"./components/quick-action-chat-area\"\nexport * from \"./components/quick-segment\"\nexport {\n QuickActionModal,\n type QuickActionPriority,\n type QuickActionTaskDraft,\n type QuickActionTemplate,\n} from \"./components/quick-action-modal\"\nexport * from \"./components/quick-action-sidebar-nav\"\nexport * from \"./components/recommended-actions-section\"\nexport * from \"./components/report-card\"\nexport * from \"./components/rich-text-toolbar\"\nexport * from \"./components/score-analysis-modal\"\nexport * from \"./components/score-breakdown\"\nexport * from \"./components/score-feedback\"\nexport * from \"./components/score-semantics\"\nexport * from \"./components/score-why-chips\"\nexport * from \"./components/score-ring\"\nexport * from \"./components/scroll-area\"\nexport * from \"./components/select\"\nexport * from \"./components/separator\"\nexport * from \"./components/sheet\"\nexport * from \"./components/sidebar\"\nexport * from \"./components/signal-feedback-inline\"\nexport * from \"./components/simple-data-table\"\nexport * from \"./components/skeleton\"\nexport * from \"./components/status-badge\"\nexport * from \"./components/step-timeline\"\nexport * from \"./components/sticky-action-bar\"\nexport * from \"./components/styled-bar-list\"\nexport { DraftFeedbackInline } from \"./components/draft-feedback-inline\"\nexport type { DraftFeedbackInlineProps } from \"./components/draft-feedback-inline\"\nexport { AccountContactsPopover, BrandIcon } from \"./components/account-contacts-popover\"\nexport type { AccountContactsPopoverProps } from \"./components/account-contacts-popover\"\nexport * from \"./components/suggested-actions\"\nexport * from \"./components/switch\"\nexport * from \"./components/table\"\nexport * from \"./components/tabs\"\nexport * from \"./components/textarea\"\nexport * from \"./components/timeline-activity\"\nexport * from \"./components/tooltip\"\nexport * from \"./components/user-display\"\nexport * from \"./components/variable-autocomplete\"\nexport * from \"./components/view-mode-toggle\"\nexport * from \"./components/virtualized-data-table\"\nexport type { ColumnSizingState } from \"@tanstack/react-table\"\n\n// Charts (re-exported for backward compatibility with root imports)\nexport * from \"./charts/index\"\n\n// Prototype template system (re-exported for backward compatibility)\nexport * from \"./prototype/prototype-config\"\nexport * from \"./prototype/prototype-shell\"\nexport * from \"./prototype/prototype-inbox-view\"\nexport * from \"./prototype/prototype-insights-view\"\nexport * from \"./prototype/prototype-accounts-view\"\nexport * from \"./prototype/prototype-admin-view\"\nexport * from \"./prototype/prototype-work-queue-view\"\n"],"mappings":"AAMA,SAAS,UAAU;AACnB,SAAS,aAAa,sBAAsB;AAC5C,SAAS,aAAa,aAAa,iBAAmC;AAGtE,SAAS,mBAAmB;AAG5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,0BAAwD;AACjE,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,gBAAgB,mBAAmB,eAAe,iBAAiB,6BAA6B;AAEzG,SAAS,6BAA6B;AAEtC,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,OAIK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,2BAA2B;AAEpC,SAAS,wBAAwB,iBAAiB;AAElD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @handled-ai/design-system\n * UI components and utilities (shadcn-style, New York)\n */\n\n// Utilities\nexport { cn } from \"./lib/utils\"\nexport { getEntityColor } from \"./lib/entity-color\"\nexport { BRAND_ICONS, BRAND_GRAPHICS } from \"./lib/icons\"\nexport { displayName, getInitials, shortName, type ProfileLike } from \"./lib/user-display\"\n\n// Hooks\nexport { useIsMobile } from \"./hooks/use-mobile\"\n\n// Components (light — no recharts/nivo/three transitive deps)\nexport * from \"./components/activity-detail\"\nexport * from \"./components/activity-log\"\nexport * from \"./components/agent-popover\"\nexport * from \"./components/agent-widget\"\nexport * from \"./components/avatar\"\nexport * from \"./components/badge\"\nexport * from \"./components/button\"\nexport * from \"./components/card\"\nexport * from \"./components/case-panel-email-composer\"\nexport * from \"./components/email-body\"\nexport * from \"./components/email-composer-row\"\nexport * from \"./components/email-display-helpers\"\nexport * from \"./components/email-preview-card\"\nexport * from \"./components/email-recipient-field\"\nexport * from \"./components/email-send-bar\"\nexport * from \"./components/case-panel-activity-timeline\"\nexport * from \"./components/case-panel-detail\"\nexport * from \"./components/case-panel-why\"\nexport { CollapsibleSection, type CollapsibleSectionProps } from \"./components/collapsible-section\"\nexport * from \"./components/comment-composer\"\nexport * from \"./components/compliance-badge\"\nexport * from \"./components/contact-chip\"\nexport * from \"./components/contact-list\"\nexport * from \"./components/contextual-quick-action-launcher\"\nexport * from \"./components/conversation-panel\"\nexport * from \"./components/dashboard-cards\"\nexport * from \"./components/data-table\"\nexport * from \"./components/data-table-condition-filter\"\nexport * from \"./components/data-table-display\"\nexport * from \"./components/data-table-filter\"\nexport * from \"./components/data-table-quick-views\"\nexport * from \"./components/data-table-toolbar\"\nexport * from \"./components/detail-view\"\nexport * from \"./components/detail-drawer\"\nexport * from \"./components/dialog\"\nexport * from \"./components/dropdown-menu\"\nexport * from \"./components/empty-state\"\nexport * from \"./components/entity-panel\"\nexport * from \"./components/related-record-action-card\"\nexport { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions, InlineFeedbackControl } from \"./components/feedback-primitives\"\nexport type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData, InlineFeedbackControlProps } from \"./components/feedback-primitives\"\nexport { SignalPriorityPopover } from \"./components/signal-priority-popover\"\nexport type { SignalPriorityPopoverProps, SignalPriorityScoreDisplay, PriorityFactor } from \"./components/signal-priority-popover\"\nexport * from \"./components/filter-chip\"\nexport * from \"./components/inbox-row\"\nexport * from \"./components/inbox-toolbar\"\nexport * from \"./components/inline-banner\"\nexport * from \"./components/input\"\nexport * from \"./components/insights-filter-bar\"\nexport * from \"./components/days-open-cell\"\nexport * from \"./components/linked-entity-cell\"\nexport * from \"./components/item-list\"\nexport * from \"./components/item-list-display\"\nexport * from \"./components/item-list-filter\"\nexport * from \"./components/item-list-toolbar\"\nexport * from \"./components/kbd-hint\"\nexport * from \"./components/label\"\nexport * from \"./components/message\"\nexport * from \"./components/metric-card\"\nexport * from \"./components/owner-chips\"\nexport * from \"./components/performance-metrics-table\"\nexport * from \"./components/pill\"\nexport * from \"./components/preview-list\"\nexport * from \"./components/progress\"\nexport * from \"./components/quick-action-chat-area\"\nexport * from \"./components/quick-segment\"\nexport {\n QuickActionModal,\n type QuickActionPriority,\n type QuickActionTaskDraft,\n type QuickActionTemplate,\n} from \"./components/quick-action-modal\"\nexport * from \"./components/quick-action-sidebar-nav\"\nexport * from \"./components/recommended-actions-section\"\nexport * from \"./components/report-card\"\nexport * from \"./components/rich-text-toolbar\"\nexport * from \"./components/score-analysis-modal\"\nexport * from \"./components/score-breakdown\"\nexport * from \"./components/score-feedback\"\nexport * from \"./components/score-semantics\"\nexport * from \"./components/score-why-chips\"\nexport * from \"./components/score-ring\"\nexport * from \"./components/scroll-area\"\nexport * from \"./components/select\"\nexport * from \"./components/separator\"\nexport * from \"./components/sheet\"\nexport * from \"./components/sidebar\"\nexport * from \"./components/signal-feedback-inline\"\nexport * from \"./components/simple-data-table\"\nexport * from \"./components/skeleton\"\nexport * from \"./components/status-badge\"\nexport * from \"./components/step-timeline\"\nexport * from \"./components/sticky-action-bar\"\nexport * from \"./components/styled-bar-list\"\nexport { DraftFeedbackInline } from \"./components/draft-feedback-inline\"\nexport type { DraftFeedbackInlineProps } from \"./components/draft-feedback-inline\"\nexport { AccountContactsPopover, BrandIcon } from \"./components/account-contacts-popover\"\nexport type { AccountContactsPopoverProps } from \"./components/account-contacts-popover\"\nexport * from \"./components/suggested-actions\"\nexport * from \"./components/switch\"\nexport * from \"./components/table\"\nexport * from \"./components/tabs\"\nexport * from \"./components/textarea\"\nexport * from \"./components/timeline-activity\"\nexport * from \"./components/tooltip\"\nexport * from \"./components/user-display\"\nexport * from \"./components/variable-autocomplete\"\nexport * from \"./components/view-mode-toggle\"\nexport * from \"./components/virtualized-data-table\"\nexport type { ColumnSizingState } from \"@tanstack/react-table\"\n\n// Charts (re-exported for backward compatibility with root imports)\nexport * from \"./charts/index\"\n\n// Prototype template system (re-exported for backward compatibility)\nexport * from \"./prototype/prototype-config\"\nexport * from \"./prototype/prototype-shell\"\nexport * from \"./prototype/prototype-inbox-view\"\nexport * from \"./prototype/prototype-insights-view\"\nexport * from \"./prototype/prototype-accounts-view\"\nexport * from \"./prototype/prototype-admin-view\"\nexport * from \"./prototype/prototype-work-queue-view\"\n"],"mappings":"AAMA,SAAS,UAAU;AACnB,SAAS,sBAAsB;AAC/B,SAAS,aAAa,sBAAsB;AAC5C,SAAS,aAAa,aAAa,iBAAmC;AAGtE,SAAS,mBAAmB;AAG5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,0BAAwD;AACjE,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,gBAAgB,mBAAmB,eAAe,iBAAiB,6BAA6B;AAEzG,SAAS,6BAA6B;AAEtC,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,OAIK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,2BAA2B;AAEpC,SAAS,wBAAwB,iBAAiB;AAElD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const COLORS = [
|
|
2
|
+
"bg-muted text-muted-foreground",
|
|
3
|
+
"bg-gray-100 text-gray-600",
|
|
4
|
+
"bg-zinc-100 text-zinc-600",
|
|
5
|
+
"bg-blue-50 text-blue-600",
|
|
6
|
+
"bg-indigo-50 text-indigo-600",
|
|
7
|
+
"bg-violet-50 text-violet-600"
|
|
8
|
+
];
|
|
9
|
+
function getEntityColor(name) {
|
|
10
|
+
let hash = 0;
|
|
11
|
+
for (let i = 0; i < name.length; i += 1) {
|
|
12
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
13
|
+
}
|
|
14
|
+
return COLORS[Math.abs(hash) % COLORS.length];
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
getEntityColor
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=entity-color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/entity-color.ts"],"sourcesContent":["/**\n * Deterministically maps an entity name to a muted Tailwind color class pair\n * (background + text) used for entity avatar/initial badges. The same input\n * always yields the same color so a given entity keeps a stable color across\n * the app.\n */\nconst COLORS = [\n \"bg-muted text-muted-foreground\",\n \"bg-gray-100 text-gray-600\",\n \"bg-zinc-100 text-zinc-600\",\n \"bg-blue-50 text-blue-600\",\n \"bg-indigo-50 text-indigo-600\",\n \"bg-violet-50 text-violet-600\",\n]\n\nexport function getEntityColor(name: string) {\n let hash = 0\n for (let i = 0; i < name.length; i += 1) {\n hash = name.charCodeAt(i) + ((hash << 5) - hash)\n }\n return COLORS[Math.abs(hash) % COLORS.length]\n}\n"],"mappings":"AAMA,MAAM,SAAS;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,eAAe,MAAc;AAC3C,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,WAAO,KAAK,WAAW,CAAC,MAAM,QAAQ,KAAK;AAAA,EAC7C;AACA,SAAO,OAAO,KAAK,IAAI,IAAI,IAAI,OAAO,MAAM;AAC9C;","names":[]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import { LinkedEntityCell } from "../linked-entity-cell";
|
|
5
|
+
import { getEntityColor } from "../../lib/entity-color";
|
|
6
|
+
|
|
7
|
+
describe("LinkedEntityCell — base font size", () => {
|
|
8
|
+
it("name span includes text-sm by default (no href)", () => {
|
|
9
|
+
const { container } = render(<LinkedEntityCell name="Acme Corp" />);
|
|
10
|
+
const nameSpan = container.querySelector(
|
|
11
|
+
'[data-slot="linked-entity-cell-name"]',
|
|
12
|
+
)!;
|
|
13
|
+
expect(nameSpan.className).toContain("text-sm");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("name link includes text-sm by default (with href)", () => {
|
|
17
|
+
const { container } = render(
|
|
18
|
+
<LinkedEntityCell name="Acme Corp" href="/accounts/1" />,
|
|
19
|
+
);
|
|
20
|
+
const link = container.querySelector(
|
|
21
|
+
'[data-slot="linked-entity-cell-link"]',
|
|
22
|
+
)!;
|
|
23
|
+
expect(link.className).toContain("text-sm");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("nameClassName overrides the default size on the name span (twMerge removes text-sm)", () => {
|
|
27
|
+
const { container } = render(
|
|
28
|
+
<LinkedEntityCell name="Acme Corp" nameClassName="text-base" />,
|
|
29
|
+
);
|
|
30
|
+
const nameSpan = container.querySelector(
|
|
31
|
+
'[data-slot="linked-entity-cell-name"]',
|
|
32
|
+
)!;
|
|
33
|
+
const classes = nameSpan.className.split(/\s+/);
|
|
34
|
+
expect(classes).toContain("text-base");
|
|
35
|
+
// twMerge drops the conflicting default size
|
|
36
|
+
expect(classes).not.toContain("text-sm");
|
|
37
|
+
// default font-medium retained
|
|
38
|
+
expect(classes).toContain("font-medium");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("nameClassName overrides the default size on the name link (twMerge removes text-sm)", () => {
|
|
42
|
+
const { container } = render(
|
|
43
|
+
<LinkedEntityCell
|
|
44
|
+
name="Acme Corp"
|
|
45
|
+
href="/accounts/1"
|
|
46
|
+
nameClassName="text-base"
|
|
47
|
+
/>,
|
|
48
|
+
);
|
|
49
|
+
const link = container.querySelector(
|
|
50
|
+
'[data-slot="linked-entity-cell-link"]',
|
|
51
|
+
)!;
|
|
52
|
+
const classes = link.className.split(/\s+/);
|
|
53
|
+
expect(classes).toContain("text-base");
|
|
54
|
+
expect(classes).not.toContain("text-sm");
|
|
55
|
+
expect(classes).toContain("font-medium");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("LinkedEntityCell — avatar badge", () => {
|
|
60
|
+
it("renders no avatar badge by default", () => {
|
|
61
|
+
const { container } = render(<LinkedEntityCell name="Acme Corp" />);
|
|
62
|
+
expect(
|
|
63
|
+
container.querySelector('[data-slot="linked-entity-cell-avatar"]'),
|
|
64
|
+
).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("renders an avatar badge only when avatarLabel is passed, showing the first letter and a getEntityColor color class", () => {
|
|
68
|
+
const { container } = render(
|
|
69
|
+
<LinkedEntityCell name="Acme Corp" avatarLabel="Acme Corp" />,
|
|
70
|
+
);
|
|
71
|
+
const avatar = container.querySelector(
|
|
72
|
+
'[data-slot="linked-entity-cell-avatar"]',
|
|
73
|
+
)!;
|
|
74
|
+
expect(avatar).not.toBeNull();
|
|
75
|
+
expect(avatar.textContent).toBe("A");
|
|
76
|
+
// The full label seeds the color
|
|
77
|
+
const expectedColor = getEntityColor("Acme Corp");
|
|
78
|
+
for (const cls of expectedColor.split(/\s+/)) {
|
|
79
|
+
expect(avatar.className).toContain(cls);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("LinkedEntityCell — trailing action", () => {
|
|
85
|
+
it("renders trailingAction after the name without affecting the name href", () => {
|
|
86
|
+
const { container } = render(
|
|
87
|
+
<LinkedEntityCell
|
|
88
|
+
name="Acme Corp"
|
|
89
|
+
href="/accounts/1"
|
|
90
|
+
trailingAction={<a href="/sf">SF</a>}
|
|
91
|
+
/>,
|
|
92
|
+
);
|
|
93
|
+
const link = container.querySelector(
|
|
94
|
+
'[data-slot="linked-entity-cell-link"]',
|
|
95
|
+
) as HTMLAnchorElement;
|
|
96
|
+
// Name keeps its own href
|
|
97
|
+
expect(link.getAttribute("href")).toBe("/accounts/1");
|
|
98
|
+
|
|
99
|
+
const trailing = container.querySelector(
|
|
100
|
+
'[data-slot="linked-entity-cell-trailing"]',
|
|
101
|
+
)!;
|
|
102
|
+
expect(trailing).not.toBeNull();
|
|
103
|
+
expect(trailing.textContent).toBe("SF");
|
|
104
|
+
|
|
105
|
+
// Trailing slot comes after the name block in DOM order
|
|
106
|
+
const root = container.querySelector(
|
|
107
|
+
'[data-slot="linked-entity-cell"]',
|
|
108
|
+
)!;
|
|
109
|
+
const children = Array.from(root.children);
|
|
110
|
+
const nameBlockIdx = children.findIndex((c) => c.contains(link));
|
|
111
|
+
const trailingIdx = children.findIndex((c) =>
|
|
112
|
+
c.matches('[data-slot="linked-entity-cell-trailing"]'),
|
|
113
|
+
);
|
|
114
|
+
expect(trailingIdx).toBeGreaterThan(nameBlockIdx);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const KNOWN_PAIRS = [
|
|
119
|
+
"bg-muted text-muted-foreground",
|
|
120
|
+
"bg-gray-100 text-gray-600",
|
|
121
|
+
"bg-zinc-100 text-zinc-600",
|
|
122
|
+
"bg-blue-50 text-blue-600",
|
|
123
|
+
"bg-indigo-50 text-indigo-600",
|
|
124
|
+
"bg-violet-50 text-violet-600",
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
describe("getEntityColor — stable palette", () => {
|
|
128
|
+
it("returns one of the six known color pairs", () => {
|
|
129
|
+
for (const name of ["Acme Corp", "Globex", "Initech", "Umbrella", "Stark Industries", "a"]) {
|
|
130
|
+
expect(KNOWN_PAIRS).toContain(getEntityColor(name));
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("returns the exact expected pair for representative names (guards against logic drift)", () => {
|
|
135
|
+
// Hardcoded snapshot of current output so accounts colors cannot shift.
|
|
136
|
+
expect(getEntityColor("Acme Corp")).toBe("bg-muted text-muted-foreground");
|
|
137
|
+
expect(getEntityColor("Globex")).toBe("bg-gray-100 text-gray-600");
|
|
138
|
+
expect(getEntityColor("Initech")).toBe("bg-muted text-muted-foreground");
|
|
139
|
+
expect(getEntityColor("Umbrella")).toBe("bg-muted text-muted-foreground");
|
|
140
|
+
expect(getEntityColor("Stark Industries")).toBe("bg-blue-50 text-blue-600");
|
|
141
|
+
expect(getEntityColor("a")).toBe("bg-gray-100 text-gray-600");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -465,6 +465,130 @@ describe("VirtualizedDataTable — consistent header styling", () => {
|
|
|
465
465
|
expect(triggers[0].className).toContain("group-hover/header:opacity-100");
|
|
466
466
|
});
|
|
467
467
|
|
|
468
|
+
it("sortable header label carries text-xs so it does not inherit a larger button font", () => {
|
|
469
|
+
const columns: ColumnDef<TestRow, unknown>[] = [
|
|
470
|
+
{
|
|
471
|
+
accessorKey: "name",
|
|
472
|
+
header: "Name",
|
|
473
|
+
size: 200,
|
|
474
|
+
meta: { sortKey: "name" },
|
|
475
|
+
},
|
|
476
|
+
];
|
|
477
|
+
const { container } = render(
|
|
478
|
+
<VirtualizedDataTable
|
|
479
|
+
columns={columns}
|
|
480
|
+
data={testData}
|
|
481
|
+
height={300}
|
|
482
|
+
onColumnSort={vi.fn()}
|
|
483
|
+
activeSortColumn="name"
|
|
484
|
+
activeSortDirection="asc"
|
|
485
|
+
/>,
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
const sortButton = Array.from(
|
|
489
|
+
container.querySelectorAll('[role="columnheader"] button'),
|
|
490
|
+
).find((b) => b.getAttribute("aria-label") !== "Column actions")!;
|
|
491
|
+
// The button itself and its inner label span should carry text-xs
|
|
492
|
+
expect(sortButton.className).toContain("text-xs");
|
|
493
|
+
const labelSpan = sortButton.querySelector("span")!;
|
|
494
|
+
expect(labelSpan.className).toContain("text-xs");
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("sortable and non-sortable header labels share the same size class", () => {
|
|
498
|
+
const columns: ColumnDef<TestRow, unknown>[] = [
|
|
499
|
+
{
|
|
500
|
+
accessorKey: "name",
|
|
501
|
+
header: "Name",
|
|
502
|
+
size: 200,
|
|
503
|
+
meta: { sortKey: "name" },
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
accessorKey: "value",
|
|
507
|
+
header: "Value",
|
|
508
|
+
size: 150,
|
|
509
|
+
// non-sortable, non-hideable → plain span
|
|
510
|
+
enableSorting: false,
|
|
511
|
+
enableHiding: false,
|
|
512
|
+
},
|
|
513
|
+
];
|
|
514
|
+
const { container } = render(
|
|
515
|
+
<VirtualizedDataTable
|
|
516
|
+
columns={columns}
|
|
517
|
+
data={testData}
|
|
518
|
+
height={300}
|
|
519
|
+
onColumnSort={vi.fn()}
|
|
520
|
+
activeSortColumn="name"
|
|
521
|
+
activeSortDirection="asc"
|
|
522
|
+
/>,
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
const headers = container.querySelectorAll('[role="columnheader"]');
|
|
526
|
+
// Sortable label span lives inside the sort button
|
|
527
|
+
const sortButton = Array.from(
|
|
528
|
+
headers[0].querySelectorAll("button"),
|
|
529
|
+
).find((b) => b.getAttribute("aria-label") !== "Column actions")!;
|
|
530
|
+
const sortableLabel = sortButton.querySelector("span")!;
|
|
531
|
+
// Non-sortable label is the direct span in the header cell
|
|
532
|
+
const nonSortableLabel = headers[1].querySelector("span")!;
|
|
533
|
+
|
|
534
|
+
expect(sortableLabel.className).toContain("text-xs");
|
|
535
|
+
expect(nonSortableLabel.className).toContain("text-xs");
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it("string header renders a native title equal to the header text", () => {
|
|
539
|
+
const columns: ColumnDef<TestRow, unknown>[] = [
|
|
540
|
+
{
|
|
541
|
+
accessorKey: "name",
|
|
542
|
+
header: "Account Name",
|
|
543
|
+
size: 200,
|
|
544
|
+
meta: { sortKey: "name" },
|
|
545
|
+
},
|
|
546
|
+
];
|
|
547
|
+
const { container } = render(
|
|
548
|
+
<VirtualizedDataTable
|
|
549
|
+
columns={columns}
|
|
550
|
+
data={testData}
|
|
551
|
+
height={300}
|
|
552
|
+
onColumnSort={vi.fn()}
|
|
553
|
+
activeSortColumn="name"
|
|
554
|
+
activeSortDirection="asc"
|
|
555
|
+
/>,
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
const sortButton = Array.from(
|
|
559
|
+
container.querySelectorAll('[role="columnheader"] button'),
|
|
560
|
+
).find((b) => b.getAttribute("aria-label") !== "Column actions")!;
|
|
561
|
+
const labelSpan = sortButton.querySelector("span")!;
|
|
562
|
+
expect(labelSpan.getAttribute("title")).toBe("Account Name");
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("non-string ReactNode header renders no title attribute", () => {
|
|
566
|
+
const columns: ColumnDef<TestRow, unknown>[] = [
|
|
567
|
+
{
|
|
568
|
+
accessorKey: "name",
|
|
569
|
+
header: () => <em>Custom</em>,
|
|
570
|
+
size: 200,
|
|
571
|
+
meta: { sortKey: "name" },
|
|
572
|
+
},
|
|
573
|
+
];
|
|
574
|
+
const { container } = render(
|
|
575
|
+
<VirtualizedDataTable
|
|
576
|
+
columns={columns}
|
|
577
|
+
data={testData}
|
|
578
|
+
height={300}
|
|
579
|
+
onColumnSort={vi.fn()}
|
|
580
|
+
activeSortColumn="name"
|
|
581
|
+
activeSortDirection="asc"
|
|
582
|
+
/>,
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
const sortButton = Array.from(
|
|
586
|
+
container.querySelectorAll('[role="columnheader"] button'),
|
|
587
|
+
).find((b) => b.getAttribute("aria-label") !== "Column actions")!;
|
|
588
|
+
const labelSpan = sortButton.querySelector("span")!;
|
|
589
|
+
expect(labelSpan.hasAttribute("title")).toBe(false);
|
|
590
|
+
});
|
|
591
|
+
|
|
468
592
|
it("header cell container uses same classes for all columns regardless of sort state", () => {
|
|
469
593
|
const columns: ColumnDef<TestRow, unknown>[] = [
|
|
470
594
|
{
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from "@tanstack/react-table"
|
|
24
24
|
|
|
25
25
|
import { cn } from "../lib/utils"
|
|
26
|
+
import { getEntityColor } from "../lib/entity-color"
|
|
26
27
|
import { Badge } from "./badge"
|
|
27
28
|
import {
|
|
28
29
|
DataTableQuickViews,
|
|
@@ -407,23 +408,6 @@ const DEFAULT_COLUMN_VISIBILITY: VisibilityState = {
|
|
|
407
408
|
opportunityCount: false,
|
|
408
409
|
}
|
|
409
410
|
|
|
410
|
-
function getEntityColor(name: string) {
|
|
411
|
-
const colors = [
|
|
412
|
-
"bg-muted text-muted-foreground",
|
|
413
|
-
"bg-gray-100 text-gray-600",
|
|
414
|
-
"bg-zinc-100 text-zinc-600",
|
|
415
|
-
"bg-blue-50 text-blue-600",
|
|
416
|
-
"bg-indigo-50 text-indigo-600",
|
|
417
|
-
"bg-violet-50 text-violet-600",
|
|
418
|
-
]
|
|
419
|
-
|
|
420
|
-
let hash = 0
|
|
421
|
-
for (let i = 0; i < name.length; i += 1) {
|
|
422
|
-
hash = name.charCodeAt(i) + ((hash << 5) - hash)
|
|
423
|
-
}
|
|
424
|
-
return colors[Math.abs(hash) % colors.length]
|
|
425
|
-
}
|
|
426
|
-
|
|
427
411
|
function getIndustryColor(industry: string) {
|
|
428
412
|
const colors: Record<string, string> = {
|
|
429
413
|
"E-commerce": "bg-emerald-50 text-emerald-700 border-emerald-100",
|
|
@@ -4,6 +4,7 @@ import * as React from "react"
|
|
|
4
4
|
import { ExternalLink } from "lucide-react"
|
|
5
5
|
|
|
6
6
|
import { cn } from "../lib/utils"
|
|
7
|
+
import { getEntityColor } from "../lib/entity-color"
|
|
7
8
|
|
|
8
9
|
export interface LinkedEntityCellProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
10
|
name: React.ReactNode
|
|
@@ -13,6 +14,21 @@ export interface LinkedEntityCellProps extends React.HTMLAttributes<HTMLDivEleme
|
|
|
13
14
|
icon?: React.ReactNode
|
|
14
15
|
external?: boolean
|
|
15
16
|
onNavigate?: () => void
|
|
17
|
+
/**
|
|
18
|
+
* When provided, renders a small colored rounded initial badge before the
|
|
19
|
+
* name. The full string is used as the color seed; the displayed letter is
|
|
20
|
+
* the first character. Omit to render no badge.
|
|
21
|
+
*/
|
|
22
|
+
avatarLabel?: string
|
|
23
|
+
/**
|
|
24
|
+
* Optional node rendered after the name (a trailing slot), e.g. a separate
|
|
25
|
+
* action link. Does not affect the name's own `href`.
|
|
26
|
+
*/
|
|
27
|
+
trailingAction?: React.ReactNode
|
|
28
|
+
/**
|
|
29
|
+
* Escape-hatch class merged onto the name span/link. Defaults to `text-sm`.
|
|
30
|
+
*/
|
|
31
|
+
nameClassName?: string
|
|
16
32
|
}
|
|
17
33
|
|
|
18
34
|
export function LinkedEntityCell({
|
|
@@ -23,6 +39,9 @@ export function LinkedEntityCell({
|
|
|
23
39
|
icon,
|
|
24
40
|
external = false,
|
|
25
41
|
onNavigate,
|
|
42
|
+
avatarLabel,
|
|
43
|
+
trailingAction,
|
|
44
|
+
nameClassName,
|
|
26
45
|
className,
|
|
27
46
|
...props
|
|
28
47
|
}: LinkedEntityCellProps) {
|
|
@@ -39,6 +58,18 @@ export function LinkedEntityCell({
|
|
|
39
58
|
className={cn("flex min-w-0 items-center gap-2", className)}
|
|
40
59
|
{...props}
|
|
41
60
|
>
|
|
61
|
+
{avatarLabel ? (
|
|
62
|
+
<span
|
|
63
|
+
data-slot="linked-entity-cell-avatar"
|
|
64
|
+
aria-hidden="true"
|
|
65
|
+
className={cn(
|
|
66
|
+
"flex h-6 w-6 shrink-0 items-center justify-center rounded text-[10px] font-bold",
|
|
67
|
+
getEntityColor(avatarLabel),
|
|
68
|
+
)}
|
|
69
|
+
>
|
|
70
|
+
{avatarLabel.slice(0, 1)}
|
|
71
|
+
</span>
|
|
72
|
+
) : null}
|
|
42
73
|
{icon ? (
|
|
43
74
|
<span data-slot="linked-entity-cell-icon" className="shrink-0 text-muted-foreground">
|
|
44
75
|
{icon}
|
|
@@ -52,12 +83,18 @@ export function LinkedEntityCell({
|
|
|
52
83
|
target={external ? "_blank" : undefined}
|
|
53
84
|
rel={external ? "noreferrer" : undefined}
|
|
54
85
|
onClick={onNavigate}
|
|
55
|
-
className=
|
|
86
|
+
className={cn(
|
|
87
|
+
"inline-flex max-w-full items-center gap-1 truncate text-sm font-medium text-foreground underline-offset-4 hover:text-primary hover:underline",
|
|
88
|
+
nameClassName,
|
|
89
|
+
)}
|
|
56
90
|
>
|
|
57
91
|
{content}
|
|
58
92
|
</a>
|
|
59
93
|
) : (
|
|
60
|
-
<span
|
|
94
|
+
<span
|
|
95
|
+
data-slot="linked-entity-cell-name"
|
|
96
|
+
className={cn("block truncate text-sm font-medium text-foreground", nameClassName)}
|
|
97
|
+
>
|
|
61
98
|
{name}
|
|
62
99
|
</span>
|
|
63
100
|
)}
|
|
@@ -69,6 +106,11 @@ export function LinkedEntityCell({
|
|
|
69
106
|
</div>
|
|
70
107
|
) : null}
|
|
71
108
|
</div>
|
|
109
|
+
{trailingAction ? (
|
|
110
|
+
<span data-slot="linked-entity-cell-trailing" className="shrink-0">
|
|
111
|
+
{trailingAction}
|
|
112
|
+
</span>
|
|
113
|
+
) : null}
|
|
72
114
|
</div>
|
|
73
115
|
)
|
|
74
116
|
}
|
|
@@ -270,6 +270,13 @@ export function VirtualizedDataTable<TData>({
|
|
|
270
270
|
onColumnSort!(sortKey!, newDir)
|
|
271
271
|
} : undefined
|
|
272
272
|
|
|
273
|
+
const headerDef = header.column.columnDef.header
|
|
274
|
+
// When the header is a plain string, expose it as a native title
|
|
275
|
+
// tooltip so truncated headers remain readable. Non-string
|
|
276
|
+
// ReactNode headers render no title.
|
|
277
|
+
const headerTitle =
|
|
278
|
+
typeof headerDef === "string" ? headerDef : undefined
|
|
279
|
+
|
|
273
280
|
return (
|
|
274
281
|
<div
|
|
275
282
|
key={header.id}
|
|
@@ -290,25 +297,22 @@ export function VirtualizedDataTable<TData>({
|
|
|
290
297
|
{canServerSort ? (
|
|
291
298
|
<button
|
|
292
299
|
type="button"
|
|
293
|
-
className="flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors"
|
|
300
|
+
className="flex min-w-0 flex-1 items-center gap-1 text-xs leading-4 hover:text-foreground transition-colors"
|
|
294
301
|
onClick={handleHeaderClick}
|
|
295
302
|
>
|
|
296
|
-
<span className="min-w-0 truncate">
|
|
297
|
-
{flexRender(
|
|
303
|
+
<span className="min-w-0 truncate text-xs leading-4" title={headerTitle}>
|
|
304
|
+
{flexRender(headerDef, header.getContext())}
|
|
298
305
|
</span>
|
|
299
306
|
{sortIcon}
|
|
300
307
|
</button>
|
|
301
308
|
) : header.column.getCanSort() ? (
|
|
302
309
|
<button
|
|
303
310
|
type="button"
|
|
304
|
-
className="flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors"
|
|
311
|
+
className="flex min-w-0 flex-1 items-center gap-1 text-xs leading-4 hover:text-foreground transition-colors"
|
|
305
312
|
onClick={header.column.getToggleSortingHandler()}
|
|
306
313
|
>
|
|
307
|
-
<span className="min-w-0 truncate">
|
|
308
|
-
{flexRender(
|
|
309
|
-
header.column.columnDef.header,
|
|
310
|
-
header.getContext(),
|
|
311
|
-
)}
|
|
314
|
+
<span className="min-w-0 truncate text-xs leading-4" title={headerTitle}>
|
|
315
|
+
{flexRender(headerDef, header.getContext())}
|
|
312
316
|
</span>
|
|
313
317
|
{header.column.getIsSorted() === "asc" ? (
|
|
314
318
|
<ArrowUp className="w-3 h-3 shrink-0" />
|
|
@@ -319,11 +323,8 @@ export function VirtualizedDataTable<TData>({
|
|
|
319
323
|
)}
|
|
320
324
|
</button>
|
|
321
325
|
) : (
|
|
322
|
-
<span className="min-w-0 flex-1 truncate">
|
|
323
|
-
{flexRender(
|
|
324
|
-
header.column.columnDef.header,
|
|
325
|
-
header.getContext(),
|
|
326
|
-
)}
|
|
326
|
+
<span className="min-w-0 flex-1 truncate text-xs leading-4" title={headerTitle}>
|
|
327
|
+
{flexRender(headerDef, header.getContext())}
|
|
327
328
|
</span>
|
|
328
329
|
)}
|
|
329
330
|
{(canServerSort || header.column.getCanSort() || header.column.getCanHide()) && (
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministically maps an entity name to a muted Tailwind color class pair
|
|
3
|
+
* (background + text) used for entity avatar/initial badges. The same input
|
|
4
|
+
* always yields the same color so a given entity keeps a stable color across
|
|
5
|
+
* the app.
|
|
6
|
+
*/
|
|
7
|
+
const COLORS = [
|
|
8
|
+
"bg-muted text-muted-foreground",
|
|
9
|
+
"bg-gray-100 text-gray-600",
|
|
10
|
+
"bg-zinc-100 text-zinc-600",
|
|
11
|
+
"bg-blue-50 text-blue-600",
|
|
12
|
+
"bg-indigo-50 text-indigo-600",
|
|
13
|
+
"bg-violet-50 text-violet-600",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
export function getEntityColor(name: string) {
|
|
17
|
+
let hash = 0
|
|
18
|
+
for (let i = 0; i < name.length; i += 1) {
|
|
19
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash)
|
|
20
|
+
}
|
|
21
|
+
return COLORS[Math.abs(hash) % COLORS.length]
|
|
22
|
+
}
|