import { ExternalLink } from "lucide-react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
type CountByProperty = [string, string, string?][];
function calculateCountPercentages(countByProperty: CountByProperty) {
const totalCount = countByProperty.reduce(
(sum, row) => sum + parseInt(row[1]),
0,
);
return countByProperty.map((row) => {
const count = parseInt(row[1]);
const percentage = ((count / totalCount) * 100).toFixed(2);
return `${percentage}%`;
});
}
export default function TableCard({
countByProperty,
columnHeaders,
onClick,
labelFormatter,
}: {
countByProperty: CountByProperty;
columnHeaders: string[];
onClick?: (key: string) => void;
labelFormatter?: (label: string) => string;
}) {
const barChartPercentages = calculateCountPercentages(countByProperty);
const countFormatter = Intl.NumberFormat("en", { notation: "compact" });
const gridCols =
(columnHeaders || []).length === 3
? "grid-cols-[minmax(0,1fr),minmax(0,8ch),minmax(0,8ch)]"
: "grid-cols-[minmax(0,1fr),minmax(0,8ch)]";
return (
{(columnHeaders || []).map((header: string, index) => (
{header}
))}
{(countByProperty || []).map((item, index) => {
const desc = item[0];
// the description can be either a single string (that is both the key and the label),
// or a tuple of type [key, label]
const [key, label] = Array.isArray(desc)
? [desc[0], desc[1] || "(unknown)"]
: [desc, desc || "(unknown)"];
const formattedLabel =
labelFormatter && typeof label === "string"
? labelFormatter(label)
: label;
return (
{/^https?:\/\//.test(label) ? (
<>
{
// Fallback to external link icon if favicon fails to load
const target =
e.target as HTMLImageElement;
target.style.display = "none";
}}
/>
{onClick ? (
) : (
formattedLabel
)}
>
) : (
<>
{onClick ? (
) : (
formattedLabel
)}
>
)}
{countFormatter.format(parseInt(item[1], 10))}
{item.length > 2 && item[2] !== undefined && (
{countFormatter.format(
parseInt(item[2], 10),
)}
)}
);
})}
);
}