table.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import { ScrollView } from 'app/design/view';
  2. import { Text } from 'app/design/typography';
  3. import Time from 'app/ui/atoms/time';
  4. import Link from 'app/ui/atoms/link';
  5. import { BlockWrapper } from 'app/components/block-wrapper';
  6. import {
  7. Table,
  8. TableHeader,
  9. TableBody,
  10. TableRow,
  11. TableHead,
  12. TableCell,
  13. TableHeaderText,
  14. TableCellText,
  15. } from 'app/ui/molecules/page/table';
  16. const UNIX_TS_MIN = 1000000000;
  17. function isHttpUrl(value) {
  18. if (typeof value !== 'string') return false;
  19. const trimmed = value.trim();
  20. if (!/^https?:\/\//i.test(trimmed)) return false;
  21. try {
  22. const url = new URL(trimmed);
  23. return url.protocol === 'http:' || url.protocol === 'https:';
  24. } catch {
  25. return false;
  26. }
  27. }
  28. function isHeaderRow(row) {
  29. if (!row || typeof row !== 'object' || Array.isArray(row)) return false;
  30. const values = Object.values(row);
  31. if (!values.length) return false;
  32. return values.every((value) => typeof value === 'string' && !isHttpUrl(value));
  33. }
  34. function toUnixTs(value) {
  35. if (typeof value === 'number' && Number.isInteger(value) && value > UNIX_TS_MIN) {
  36. return value;
  37. }
  38. if (typeof value === 'string' && /^\d+$/.test(value.trim())) {
  39. const n = Number(value);
  40. if (Number.isInteger(n) && n > UNIX_TS_MIN) return n;
  41. }
  42. return null;
  43. }
  44. function cellKind(value) {
  45. if (value == null || value === '') return 'empty';
  46. if (toUnixTs(value) != null) return 'time';
  47. if (isHttpUrl(String(value))) return 'url';
  48. if (typeof value === 'number') return 'number';
  49. return 'text';
  50. }
  51. function columnClass(key, rows) {
  52. const wide = rows.some((row) => {
  53. const kind = cellKind(row?.[key]);
  54. return kind === 'url' || kind === 'text';
  55. });
  56. return wide ? 'flex-2 min-w-0' : 'flex-1 min-w-0';
  57. }
  58. function CellValue({ value }) {
  59. const kind = cellKind(value);
  60. if (kind === 'time') {
  61. const ts = toUnixTs(value);
  62. if (!ts) return null;
  63. return <Time ts={ts} format="datetime" stylesName="" />;
  64. }
  65. if (kind === 'url') {
  66. return (
  67. <Link href={value}>
  68. <Text className="text-primary" numberOfLines={1}>
  69. {value}
  70. </Text>
  71. </Link>
  72. );
  73. }
  74. if (kind === 'empty') return null;
  75. return <TableCellText numberOfLines={1}>{String(value)}</TableCellText>;
  76. }
  77. export default function ElementTable({ data, blockWrapperProps }) {
  78. const rows = Array.isArray(data) ? data : [];
  79. if (!rows.length) return null;
  80. const header = isHeaderRow(rows[0]) ? rows[0] : null;
  81. const body = header ? rows.slice(1) : rows;
  82. const keys = Object.keys(header || body[0] || {});
  83. return (
  84. <BlockWrapper {...blockWrapperProps}>
  85. <ScrollView
  86. horizontal
  87. className="w-full"
  88. contentContainerClassName="w-full min-w-full"
  89. showsHorizontalScrollIndicator
  90. >
  91. <Table className="w-full ">
  92. <TableHeader>
  93. <TableRow>
  94. {keys.map((key) => (
  95. <TableHead key={key} className={columnClass(key, body)}>
  96. <TableHeaderText numberOfLines={1}>
  97. {header ? header[key] : key}
  98. </TableHeaderText>
  99. </TableHead>
  100. ))}
  101. </TableRow>
  102. </TableHeader>
  103. <TableBody>
  104. {body.map((row, index) => (
  105. <TableRow key={index}>
  106. {keys.map((key) => (
  107. <TableCell key={key} className={columnClass(key, body)}>
  108. <CellValue value={row?.[key]} />
  109. </TableCell>
  110. ))}
  111. </TableRow>
  112. ))}
  113. </TableBody>
  114. </Table>
  115. </ScrollView>
  116. </BlockWrapper>
  117. );
  118. }
  119. ElementTable.checkEmpty = (item) => Array.isArray(item?.data) && item.data.length > 0;