UNPKG

@shridey/intelligentable

Version:

Intelligentable is a highly customizable, fully-types, performant, and feature-rich React component library built on top of handpicked industry-level production-grade UI Components for modern web applications.

760 lines (759 loc) 22.3 kB
import { jsx as f, jsxs as k } from "react/jsx-runtime"; import { useState as V, useMemo as E } from "react"; import { Dropdown as W, Button as P, Input as z, Table as N, ConfigProvider as U } from "antd"; import q from "write-excel-file"; import { jsPDF as G } from "jspdf"; import J from "jspdf-autotable"; import { saveAs as D } from "file-saver"; import { DownloadOutlined as K, FilePdfOutlined as X, FileExcelOutlined as H, FileTextOutlined as O, SearchOutlined as Y } from "@ant-design/icons"; const Z = (a) => { const e = /* @__PURE__ */ new Map(), s = (n) => "min" in n || "max" in n, t = (n) => "str" in n || "regEx" in n, r = (n) => { Array.isArray(n.colorConfig) && n.colorConfig.forEach((o) => { const l = o.color; let d = "", c = ""; if (s(o)) { const { min: i, max: u, inclusiveMin: b = !0, inclusiveMax: p = !1 } = o; d = `threshold-${i ?? "-∞"}-${u ?? "∞"}-${b}-${p}-${l}`; const m = b ? "≥" : ">", y = p ? "≤" : "<"; i != null && u != null && i === u ? c = `= ${i}` : i != null && u != null ? c = `${m} ${i} & ${y} ${u}` : i != null ? c = `${m} ${i}` : u != null ? c = `${y} ${u}` : c = "All values", e.set(d, { label: c, color: l }); } t(o) && (o.str ? (d = `str-${o.str}-${o.exactMatch}-${l}`, c = o.exactMatch ? `= "${o.str}"` : `~ "${o.str}"`) : o.regEx && (d = `regex-${o.regEx}-${l}`, c = `= RegEx /${o.regEx}/`), d && e.set(d, { label: c, color: l })); }), Array.isArray(n.children) && n.children.forEach(r); }; return a?.forEach(r), Array.from(e.values()); }, _ = ({ columns: a = [], legendStyle: e = {} }) => { const s = Z(a); return s.length > 0 && /* @__PURE__ */ f( "div", { style: { display: "flex", gap: 18, padding: 6, border: e?.border || "1px solid #d9d9d9", borderRadius: 6, backgroundColor: e?.backgroundColor }, children: s.map((t, r) => /* @__PURE__ */ k( "div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [ /* @__PURE__ */ f( "svg", { width: e?.circle?.radius * 2 || 12, height: e?.circle?.radius * 2 || 12, children: /* @__PURE__ */ f( "circle", { cx: e?.circle?.radius || 6, cy: e?.circle?.radius || 6, r: e?.circle?.radius || 6, fill: t.color } ) } ), /* @__PURE__ */ f( "span", { style: { fontSize: e?.label?.fontSize || 14, fontWeight: e?.label?.fontWeight, fontFamily: e?.label?.fontFamily, color: e?.label?.color }, children: t.label } ) ] }, r )) } ); }, F = (a, e) => { const s = /* @__PURE__ */ new Date(), t = (o) => o.toString().padStart(2, "0"), r = `${s.getFullYear()}-${t(s.getMonth() + 1)}-${t( s.getDate() )}`, n = `${t(s.getHours())}-${t(s.getMinutes())}`; return `${a}-${r}_${n}.${e}`; }, j = (a, e, s) => { const t = e.filter((l) => l.title), r = t.map((l) => l.title), n = t.map((l) => l.dataIndex), o = a.map( (l) => n.map((d) => { const c = l[d]; if (c == null) return ""; const i = String(c); return typeof c == "string" && (i.includes(s) || i.includes(` `)) ? `"${i.replace(/"/g, '""')}"` : i; }).join(s) ); return [r.join(s), ...o].join(` `); }, Q = async (a, e = {}) => { const { fontUrl: s, fontFileName: t = "", fontName: r = "", fontStyles: n = [], fallbackFont: o = "helvetica" } = e; if (!s) { a.setFont(o); return; } try { const d = await (await fetch(s)).blob(), c = await new Promise((i) => { const u = new FileReader(); u.onload = () => i(u.result), u.readAsDataURL(d); }).then((i) => i.split(",")[1]); a.addFileToVFS(t, c), n.forEach((i) => { a.addFont(t, r, i); }), a.setFont(r); } catch (l) { console.error(`Failed to load custom font (${t}):`, l), a.setFont(o); } }, ee = async (a, e, s, t, r) => { const n = e.filter((d) => d.title), o = n.map((d) => d.title), l = n.map((d) => d.dataIndex); switch (s) { case "csv": { const d = j(a, e, ","), c = new Blob(["\uFEFF" + d], { type: "text/csv;charset=utf-8;" }); D(c, F(t || "table-export", "csv")); break; } case "tsv": { const d = j(a, e, " "), c = new Blob(["\uFEFF" + d], { type: "text/tab-separated-values;charset=utf-8;" }); D(c, F(t || "table-export", "tsv")); break; } case "xlsx": { const d = a.map((i) => { const u = {}; return l.forEach((b, p) => { u[o[p]] = i[b] ?? ""; }), u; }), c = o.map((i) => ({ column: i, type: String, // Force all values to strings value: (u) => String(u[i] ?? "") })); await q(d, { schema: c, fileName: F(t || "table-export", "xlsx") }); break; } case "json": { const d = a.map((i) => { const u = {}; return l.forEach((b, p) => { u[o[p]] = i[b]; }), u; }), c = new Blob([JSON.stringify(d, null, 2)], { type: "application/json" }); D(c, F(t || "table-export", "json")); break; } case "pdf": { const d = e.filter((p) => p.title), c = d.map((p) => p.title), i = d.map((p) => p.dataIndex), u = a.map( (p) => i.map((m) => { const y = p[m]; return y != null ? String(y) : ""; }) ), b = new G({ orientation: "landscape" }); await Q(b, r), J(b, { head: [c], body: u, styles: { font: r?.fontName, fontSize: 8, cellPadding: 2, overflow: "linebreak" }, headStyles: { fillColor: [70, 130, 180], textColor: 255, fontStyle: "bold" }, margin: { top: 10 } }), b.save(F(t || "table-export", "pdf")); break; } } }, te = ({ data: a = [], columns: e = [], exportFileName: s = "", pdfFontOptions: t = { fontUrl: "", fontFileName: "", fontName: "", fontStyles: [], fallbackFont: "helvetica" } }) => { const r = (o) => { ee( a, e, o, s, t ); }, n = [ { key: "pdf", label: "PDF", icon: /* @__PURE__ */ f(X, {}), onClick: () => r("pdf") }, { key: "xlsx", label: "Excel (XLSX)", icon: /* @__PURE__ */ f(H, {}), onClick: () => r("xlsx") }, { key: "json", label: "JSON", icon: /* @__PURE__ */ f(O, {}), onClick: () => r("json") }, { key: "tsv", label: "TSV", icon: /* @__PURE__ */ f(O, {}), onClick: () => r("tsv") }, { key: "csv", label: "CSV", icon: /* @__PURE__ */ f(O, {}), onClick: () => r("csv") } ].filter(Boolean); return /* @__PURE__ */ f(W, { menu: { items: n }, trigger: ["hover"], children: /* @__PURE__ */ f(P, { size: "middle", icon: /* @__PURE__ */ f(K, {}), iconPosition: "end", children: "Export" }) }); }, ne = ({ columns: a = [], data: e = [], legends: s = { enable: !1, style: {} }, search: t = { enable: !1, searchText: "", setSearchText: (n) => n, placeholder: "Search table..." }, exportButton: r = { enable: !1, exportFileName: "", pdfFontOptions: { fontUrl: "", fontFileName: "", fontName: "", fontStyles: [], fallbackFont: "" } } }) => (s.enable || t.enable || r.enable) && /* @__PURE__ */ k( "div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", rowGap: 12, columnGap: 12 }, children: [ s.enable && /* @__PURE__ */ f( _, { columns: a, legendStyle: s.style } ), t.enable && t.searchText !== void 0 && t.setSearchText && /* @__PURE__ */ f( "div", { style: { flex: 1, minWidth: 300 }, children: /* @__PURE__ */ f( z, { style: { width: "100%" }, placeholder: t.placeholder || "Search table...", value: t.searchText, onChange: (n) => t.setSearchText(n.target.value), size: "middle", suffix: /* @__PURE__ */ f(Y, {}) } ) } ), r.enable && /* @__PURE__ */ f( te, { data: e, columns: a, exportFileName: r.exportFileName, pdfFontOptions: r.pdfFontOptions } ) ] } ), L = (a, e) => { if (!Array.isArray(a) || e == null) return; const s = w(e); for (const t of a) if (t.color !== void 0) { if (s === "string" || s === "date" || s === "dayOfWeek") { if (t.regEx) try { if (new RegExp(t.regEx, "i").test(String(e))) return t.color; } catch (r) { console.warn("Invalid regex pattern:", t.regEx), console.error(r); continue; } else if (t.str !== void 0) { const r = String(e).toLowerCase(), n = t.str.toLowerCase(); if (t.exactMatch) { if (r === n) return t.color; } else if (r.includes(n)) return t.color; } continue; } if (s === "number" || s === "currency" || s === "id" || s === "percentage") { if (t.min === void 0 && t.max === void 0) continue; const n = v(e).number; if (n == null) continue; const o = t.min ?? -1 / 0, l = t.max ?? 1 / 0; if (o === l) { if (n === o) return t.color; continue; } const d = t.inclusiveMin !== !1 ? n >= o : n > o, c = t.inclusiveMax === !0 ? n <= l : n < l; if (d && c) return t.color; } } }, re = (a, e) => { const s = L(e, a); return /* @__PURE__ */ f( "span", { style: { color: s }, children: a } ); }, se = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"], T = [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ], oe = { sun: "sunday", mon: "monday", tue: "tuesday", wed: "wednesday", thu: "thursday", fri: "friday", sat: "saturday" }, A = (a) => { const e = String(a).trim(); if (/^\d{1,2}[/-]\d{1,2}[/-]\d{2,4}$/.test(e)) { const [t, r, n] = e.split(/[/-]/).map(Number), o = n < 100 ? n > 50 ? 1900 + n : 2e3 + n : n; return new Date(o, r - 1, t).getTime(); } return new Date(e).getTime(); }, ae = (a) => { if (typeof a.sorter == "function" || typeof a.sorter == "boolean") return a.sorter; const e = a.dataIndex; return typeof e != "string" && typeof e != "number" ? !1 : (s, t) => { const r = s[e], n = t[e], o = w(r); if (r == null) return -1; if (n == null) return 1; switch (o) { case "number": return Number(r) - Number(n); case "percentage": case "currency": case "id": { const l = v(r), d = v(n), c = l.number, i = d.number; return c === null ? i === null ? 0 : -1 : i === null ? 1 : c - i; } case "date": return A(String(r)) - A(String(n)); case "dayOfWeek": { const l = (i) => { const u = String(i).toLowerCase(); return oe[u] || u; }, d = l(r), c = l(n); return T.indexOf(d) - T.indexOf(c); } case "string": return String(r).localeCompare(String(n)); default: return 0; } }; }, w = (a) => { if (typeof a == "number") return "number"; if (a == null) return "string"; const e = String(a).trim().toLowerCase(); if (/^\s*[+-]?\d+(\.\d+)?\s*%$/.test(e)) return "percentage"; if (/^[$€£¥₹]\s*-?\s*\d+(\.\d+)?$/.test(e) || // Plain: $5000 /^-?\s*\d+(\.\d+)?\s*[$€£¥₹]$/.test(e) || // Plain: 5000$ /^[$€£¥₹]\s*-?\s*\d{1,3}(,\d{3})+(\.\d+)?$/.test(e) || // Intl: $1,000,000 /^[$€£¥₹]\s*-?\s*\d{1,3}(,\d{2})+(,\d{3})*(\.\d+)?$/.test(e) || // Indian mixed: ₹3,00,000,000 /^[$€£¥₹]\s*-?\s*\d{1,3}(,\d{2})+(\.\d+)?$/.test(e) || // Pure Indian: ₹3,50,000 /^-?\s*\d{1,3}(,\d{3})+(\.\d+)?\s*[$€£¥₹]$/.test(e) || // Intl: 1,000,000$ /^-?\s*\d{1,3}(,\d{2})+(,\d{3})*(\.\d+)?\s*[$€£¥₹]$/.test(e) || // Indian mixed: 3,00,000,000₹ /^-?\s*\d{1,3}(,\d{2})+(\.\d+)?\s*[$€£¥₹]$/.test(e)) return "currency"; if (/^#\d+$|^id-\d+$/i.test(e)) return "id"; if (T.includes(e) || se.includes(e)) return "dayOfWeek"; const s = /^\s*(\d{1,2})[/-](\d{1,2})[/-](\d{2}|\d{4})\s*$/, t = /^\d{4}-\d{2}-\d{2}(T.*)?$/, r = /^[A-Za-z]{3,9} \d{1,2},? \d{4}$/, n = e.match(s); if (n) { const o = Number(n[1]), l = Number(n[2]), d = Number(n[3]), c = d < 100 ? d > 50 ? 1900 + d : 2e3 + d : d, i = new Date(c, l - 1, o); if (i.getDate() === o && i.getMonth() === l - 1 && i.getFullYear() === c) return "date"; } else if (t.test(e) || r.test(e)) { const o = new Date(e); if (!isNaN(o.getTime())) return "date"; } return /^\s*[+-]?(\d+(\.\d+)?|\.\d+)\s*$/.test(e) ? "number" : "string"; }, M = (a, e) => { const s = Math.pow(10, e); return (Math.round((a + Number.EPSILON) * s) / s).toFixed(e); }, B = (a) => a.map((e) => { const s = Array.isArray(e.children) && e.children.length > 0, t = { ...e, children: s ? B(e.children) : void 0 }; return s || (t.sorter = ae(e), e.render || (t.render = (r) => { let n = r; const o = w(n); if (e.roundOff !== void 0 && ["number", "percentage", "currency"].includes(o)) { const l = v(n); l.number !== null && (n = `${o === "currency" ? l.symbol : ""}${M( l.number, e.roundOff )}${o !== "currency" ? l.symbol : ""}`); } return e.colorConfig && (n = re( n, e.colorConfig )), n; }), e.filters && !e.onFilter && (t.onFilter = (r, n) => { const o = v( n[e.dataIndex] ).number; if (["id", "number", "percentage", "currency"].includes( w(o) )) { if (o == null) return !1; if (typeof r == "string" && r.includes("-")) { const [l, d] = r.split("-").map(Number); return o >= l && o <= d; } return o === Number(r); } return !1; })), t; }), ie = (a) => { const e = {}; for (const t of a) e[t] = (e[t] || 0) + 1; return Object.entries(e).sort((t, r) => r[1] - t[1])[0]?.[0] || ""; }, v = (a) => { if (a == null) return { number: null, symbol: "" }; const e = String(a).trim().replace(/,/g, ""), s = w(e); let t = null, r = ""; switch (s) { case "number": t = Number(e); break; case "percentage": t = parseFloat(e.replace("%", "")), r = "%"; break; case "currency": { r = e.match(/([^0-9.-]*)-?[\d,.]+/)?.[1] || "", t = parseFloat(e.replace(/[^0-9.-]/g, "")); break; } case "id": { r = e.match(/([^0-9]*)\d+/)?.[1] || "", t = parseInt(e.replace(/[^0-9]/g, ""), 10); break; } default: t = null; break; } return { number: isNaN(t) ? null : t, symbol: r }; }, R = (a) => a.flatMap( (e) => e.children ? R(e.children) : [e] ); function le(a, e) { return Array.isArray(e) ? e.reduce((s, t) => { if (s && t in s) return s[t]; }, a) : a?.[e]; } const ce = (a, e) => { const s = {}; return e.forEach((t) => { const r = t.summaryOperation, n = t.dataIndex; if (!r) return; const o = a.map( (c) => c[n] ), l = w(o.find((c) => c != null)); if (r === "count") { s[n] = o.length; return; } if (["number", "percentage", "currency", "id"].includes(l)) { const c = [], i = o.map((m) => { const { number: y, symbol: h } = v(m); return y != null && c.push(h), y; }).filter((m) => typeof m == "number"), u = ie(c), b = (m) => t.roundOff !== void 0 ? M(m, t.roundOff) : m, p = (m) => `${l === "currency" || l === "id" ? u : ""}${m}${l !== "currency" && l !== "id" ? u : ""}`; switch (r) { case "sum": { const m = i.reduce((y, h) => y + h, 0); s[n] = p(b(m)); break; } case "average": { const m = i.reduce((y, h) => y + h, 0) / (i.length || 1); s[n] = p(b(m)); break; } case "max": { const m = Math.max(...i); s[n] = p(b(m)); break; } case "min": { const m = Math.min(...i); s[n] = p(b(m)); break; } } } if (l === "date") { const c = o.map((i) => new Date(String(i))).filter((i) => !isNaN(i.getTime())); if (c.length === 0) return; switch (r) { case "max": s[n] = c.reduce((i, u) => i > u ? i : u).toLocaleDateString(); break; case "min": s[n] = c.reduce((i, u) => i < u ? i : u).toLocaleDateString(); break; } } }), s; }, de = ({ data: a = [], columns: e = [], defaultSummaryRowStyle: s = {} }) => { const t = ce(a, e); return /* @__PURE__ */ f(N.Summary.Row, { style: s, children: e.map((r, n) => { const o = String(r.dataIndex), l = n === 0 ? `Summary ${t[o] || ""}` : t[o] || "-", d = L( r.colorConfig, l ); return /* @__PURE__ */ f(N.Summary.Cell, { index: n, align: r.align, children: /* @__PURE__ */ k( "span", { style: { textOverflow: r.ellipsis ? "ellipsis" : void 0, whiteSpace: r.ellipsis ? "nowrap" : void 0, overflow: r.ellipsis ? "hidden" : void 0, maxWidth: r.ellipsis ? r.width : void 0, display: r.ellipsis ? "block" : void 0, color: d }, title: String(l), children: [ l, " ", r.displaySummaryOperationInSummary && /* @__PURE__ */ k("sub", { children: [ "(", r.summaryOperation, ")" ] }) ] } ) }, n); }) }); }; function ue(a, e) { return e.reduce((s, t) => t(s), a); } const Se = ({ columns: a = [], dataSource: e = [], dataTransform: s = ({ pipeline: c }) => c([]), tableThemeConfig: t = { defaultSummaryRow: {}, legends: {}, searchBox: {}, exportButton: {}, exportButtonDropdown: {} }, defaultSummary: r = { enable: !1, fixed: !1 }, enableLegends: n = !1, search: o = { enable: !1, placeholder: "Search table...", onSearch: () => !1 }, tableExport: l = { enable: !1, exportFileName: "", pdfFontOptions: { fontUrl: "", fontFileName: "", fontName: "", fontStyles: [], fallbackFont: "" } }, ...d }) => { const c = B(a), i = R(c), [u, b] = V(""), p = E(() => { const y = e ?? []; return s ? s({ pipeline: (h) => ue([...y], h) }) : y; }, [e, s]), m = E(() => { const y = u.trim().toLowerCase(); return y ? p?.filter((h) => o.onSearch ? o.onSearch(y, h, i) : i.some((C) => { const I = C.dataIndex; if (I === void 0) return !1; const g = le(h, I); if (g == null) return !1; let x = null; const S = w(g); if (S === "string" || typeof g == "boolean") x = String(g); else if (S === "number" || S === "currency" || S === "id" || S === "percentage" || typeof g == "number") { const $ = v(g); $.number !== null && (x = `${S === "currency" ? $.symbol : ""}${C.roundOff ? M($.number, C.roundOff) : $.number}${S !== "currency" ? $.symbol : ""}`); } else if (Array.isArray(g)) x = g.map(($) => String($)).join(", "); else if (g instanceof Date) x = g.toLocaleString(); else if (typeof g == "object" && g !== null) try { x = JSON.stringify(g); } catch { x = null; } return x?.toLowerCase().includes(y) ?? !1; })) : p; }, [o, u, p, i]); return /* @__PURE__ */ f( U, { theme: { components: { Table: t, Input: t?.searchBox, Button: t?.exportButton, Dropdown: t?.exportButtonDropdown } }, children: /* @__PURE__ */ k( "div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [ /* @__PURE__ */ f( ne, { columns: i, data: m, legends: { enable: n, style: t?.legends || {} }, search: { enable: o.enable, searchText: u, setSearchText: b, placeholder: o.placeholder || "Search table..." }, exportButton: { enable: l.enable, exportFileName: l.exportFileName, pdfFontOptions: l.pdfFontOptions } } ), /* @__PURE__ */ f( N, { columns: c, dataSource: m, summary: d.summary ? d.summary : r.enable ? (y) => /* @__PURE__ */ f( N.Summary, { fixed: r.fixed, children: /* @__PURE__ */ f( de, { data: y, columns: i, defaultSummaryRowStyle: t?.defaultSummaryRow || {} } ) } ) : void 0, ...d } ) ] } ) } ); }; export { Se as IntelligentTable };