UNPKG

@kodeme-io/next-core-analytics

Version:

Analytics, charts, dashboards, and reporting for Next.js applications

1,130 lines (1,120 loc) 57 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { AnalyticsErrorBoundary: () => AnalyticsErrorBoundary, AnalyticsRequestSchema: () => AnalyticsRequestSchema, ChartConfigSchema: () => ChartConfigSchema, ChartContainer: () => ChartContainer, ChartDataPointSchema: () => ChartDataPointSchema, ChartErrorBoundary: () => ChartErrorBoundary, Dashboard: () => Dashboard, DashboardErrorBoundary: () => DashboardErrorBoundary, DashboardLayoutSchema: () => DashboardLayoutSchema, DashboardWidgetSchema: () => DashboardWidgetSchema, ExportButton: () => ExportButton, ExportConfigSchema: () => ExportConfigSchema, KPICard: () => KPICard, KPIErrorBoundary: () => KPIErrorBoundary, KPIMetricSchema: () => KPIMetricSchema, MultiSeriesDataPointSchema: () => MultiSeriesDataPointSchema, aggregateData: () => aggregateData, calculateComparison: () => calculateComparison, createFormatter: () => createFormatter, createValidationMonitor: () => createValidationMonitor, currencyFormatters: () => currencyFormatters, formatDuration: () => formatDuration, formatTrendValue: () => formatTrendValue, formatValue: () => formatValue, getValidationErrors: () => getValidationErrors, numberFormatters: () => numberFormatters, percentageFormatters: () => percentageFormatters, processTimeSeries: () => processTimeSeries, safeValidate: () => safeValidate, useAnalytics: () => useAnalytics, useErrorHandler: () => useErrorHandler, validateAnalyticsRequest: () => validateAnalyticsRequest, validateChartConfig: () => validateChartConfig, validateChartData: () => validateChartData, validateDashboardLayout: () => validateDashboardLayout, validateExportConfig: () => validateExportConfig, validateKPI: () => validateKPI, validateKPIs: () => validateKPIs, validationMonitor: () => validationMonitor, version: () => version }); module.exports = __toCommonJS(index_exports); // src/components/kpi-card.tsx var import_react = require("react"); // src/utils/formatters.ts var DEFAULT_LOCALE = "en-US"; var DEFAULT_CURRENCY = "USD"; function formatValue(value, options = {}) { if (typeof value === "string") return value; const { format, locale = DEFAULT_LOCALE, currency = DEFAULT_CURRENCY, prefix, suffix, numberFormatOptions } = options; let formatted = value.toString(); switch (format) { case "currency": { const currencyOptions = { style: "currency", currency, minimumFractionDigits: 0, maximumFractionDigits: 2, ...numberFormatOptions }; try { formatted = new Intl.NumberFormat(locale, currencyOptions).format(value); } catch (error) { formatted = new Intl.NumberFormat(DEFAULT_LOCALE, currencyOptions).format(value); } break; } case "percentage": { const percentageOptions = { style: "percent", minimumFractionDigits: 1, maximumFractionDigits: 2, ...numberFormatOptions }; const percentageValue = value > 1 ? value / 100 : value; try { formatted = new Intl.NumberFormat(locale, percentageOptions).format(percentageValue); } catch (error) { formatted = new Intl.NumberFormat(DEFAULT_LOCALE, percentageOptions).format(percentageValue); } break; } case "number": { const numberOptions = { minimumFractionDigits: 0, maximumFractionDigits: 2, ...numberFormatOptions }; try { formatted = new Intl.NumberFormat(locale, numberOptions).format(value); } catch (error) { formatted = new Intl.NumberFormat(DEFAULT_LOCALE, numberOptions).format(value); } break; } case "duration": { formatted = formatDuration(value); break; } default: try { formatted = new Intl.NumberFormat(locale).format(value); } catch (error) { formatted = new Intl.NumberFormat(DEFAULT_LOCALE).format(value); } } if (prefix) formatted = `${prefix}${formatted}`; if (suffix) formatted = `${formatted}${suffix}`; return formatted; } function formatDuration(seconds) { const days = Math.floor(seconds / 86400); const hours = Math.floor(seconds % 86400 / 3600); const minutes = Math.floor(seconds % 3600 / 60); const remainingSeconds = Math.floor(seconds % 60); const parts = []; if (days > 0) parts.push(`${days}d`); if (hours > 0) parts.push(`${hours}h`); if (minutes > 0) parts.push(`${minutes}m`); if (remainingSeconds > 0 || parts.length === 0) parts.push(`${remainingSeconds}s`); return parts.join(" "); } function formatTrendValue(value, format = "percentage") { const sign = value > 0 ? "+" : value < 0 ? "-" : ""; const absValue = Math.abs(value); if (format === "percentage") { return `${sign}${absValue.toFixed(1)}%`; } return `${sign}${absValue}`; } function createFormatter(options) { return (value, additionalOptions = {}) => { return formatValue(value, { ...options, ...additionalOptions }); }; } var currencyFormatters = { USD: createFormatter({ format: "currency", currency: "USD", locale: "en-US" }), EUR: createFormatter({ format: "currency", currency: "EUR", locale: "de-DE" }), GBP: createFormatter({ format: "currency", currency: "GBP", locale: "en-GB" }), JPY: createFormatter({ format: "currency", currency: "JPY", locale: "ja-JP" }), CNY: createFormatter({ format: "currency", currency: "CNY", locale: "zh-CN" }), IDR: createFormatter({ format: "currency", currency: "IDR", locale: "id-ID" }) }; var numberFormatters = { default: createFormatter({ format: "number", locale: "en-US" }), european: createFormatter({ format: "number", locale: "de-DE" }), asian: createFormatter({ format: "number", locale: "zh-CN" }) }; var percentageFormatters = { default: createFormatter({ format: "percentage", locale: "en-US" }), european: createFormatter({ format: "percentage", locale: "de-DE" }) }; // src/utils/validation.ts var import_zod = require("zod"); var ChartDataPointSchema = import_zod.z.object({ name: import_zod.z.string(), value: import_zod.z.number() }); var MultiSeriesDataPointSchema = import_zod.z.record(import_zod.z.string(), import_zod.z.union([import_zod.z.string(), import_zod.z.number()])); var KPIMetricSchema = import_zod.z.object({ id: import_zod.z.string().min(1, "KPI ID is required"), label: import_zod.z.string().min(1, "KPI label is required"), value: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]), previousValue: import_zod.z.number().optional(), format: import_zod.z.enum(["number", "currency", "percentage", "duration"]).optional(), trend: import_zod.z.enum(["up", "down", "neutral"]).optional(), trendValue: import_zod.z.number().optional(), color: import_zod.z.string().optional(), prefix: import_zod.z.string().optional(), suffix: import_zod.z.string().optional(), locale: import_zod.z.string().optional(), currency: import_zod.z.string().optional(), numberFormatOptions: import_zod.z.record(import_zod.z.any()).optional(), icon: import_zod.z.any().optional() }); var ChartConfigSchema = import_zod.z.object({ type: import_zod.z.enum(["line", "bar", "area", "pie", "donut", "scatter", "radar"]), data: import_zod.z.array(import_zod.z.any()), xKey: import_zod.z.string().optional(), yKey: import_zod.z.union([import_zod.z.string(), import_zod.z.array(import_zod.z.string())]).optional(), colors: import_zod.z.array(import_zod.z.string()).optional(), legend: import_zod.z.boolean().optional(), grid: import_zod.z.boolean().optional(), tooltip: import_zod.z.boolean().optional(), title: import_zod.z.string().optional(), subtitle: import_zod.z.string().optional() }); var DashboardWidgetSchema = import_zod.z.object({ id: import_zod.z.string().min(1, "Widget ID is required"), type: import_zod.z.enum(["chart", "kpi", "table", "custom"]), title: import_zod.z.string().min(1, "Widget title is required"), description: import_zod.z.string().optional(), config: import_zod.z.any(), span: import_zod.z.object({ cols: import_zod.z.number().min(1).max(12).optional(), rows: import_zod.z.number().min(1).max(6).optional() }).optional() }); var DashboardLayoutSchema = import_zod.z.object({ id: import_zod.z.string().min(1, "Dashboard ID is required"), name: import_zod.z.string().min(1, "Dashboard name is required"), widgets: import_zod.z.array(DashboardWidgetSchema), refreshInterval: import_zod.z.number().positive().optional(), filters: import_zod.z.array(import_zod.z.object({ id: import_zod.z.string(), type: import_zod.z.enum(["date", "select", "multiselect", "range"]), label: import_zod.z.string(), value: import_zod.z.any(), options: import_zod.z.array(import_zod.z.object({ label: import_zod.z.string(), value: import_zod.z.any() })).optional() })).optional() }); var ExportConfigSchema = import_zod.z.object({ format: import_zod.z.enum(["pdf", "excel", "csv", "json"]), filename: import_zod.z.string().optional(), data: import_zod.z.array(import_zod.z.any()), columns: import_zod.z.array(import_zod.z.string()).optional(), title: import_zod.z.string().optional(), includeCharts: import_zod.z.boolean().optional() }); var AnalyticsRequestSchema = import_zod.z.object({ endpoint: import_zod.z.string().url("Invalid URL provided for endpoint"), params: import_zod.z.record(import_zod.z.any()).optional(), method: import_zod.z.enum(["GET", "POST"]).optional(), headers: import_zod.z.record(import_zod.z.string()).optional(), cache: import_zod.z.enum(["default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached"]).optional() }); function validateChartData(data) { return data.map((item) => ChartDataPointSchema.parse(item)); } function validateKPI(metric) { return KPIMetricSchema.parse(metric); } function validateKPIs(metrics) { return metrics.map((metric) => KPIMetricSchema.parse(metric)); } function validateChartConfig(config) { return ChartConfigSchema.parse(config); } function validateDashboardLayout(layout) { return DashboardLayoutSchema.parse(layout); } function validateExportConfig(config) { return ExportConfigSchema.parse(config); } function validateAnalyticsRequest(options) { return AnalyticsRequestSchema.parse(options); } function safeValidate(schema, data) { const result = schema.safeParse(data); if (result.success) { return { success: true, data: result.data }; } else { return { success: false, errors: result.error }; } } function getValidationErrors(error) { return error.errors.map((err) => `${err.path.join(".")}: ${err.message}`); } function createValidationMonitor() { const validationStats = { totalValidations: 0, failedValidations: 0, totalValidationTime: 0 }; return { validate: (schema, data) => { const startTime = performance.now(); validationStats.totalValidations++; try { const result = schema.parse(data); const endTime = performance.now(); validationStats.totalValidationTime += endTime - startTime; return result; } catch (error) { validationStats.failedValidations++; const endTime = performance.now(); validationStats.totalValidationTime += endTime - startTime; throw error; } }, getStats: () => ({ ...validationStats, averageValidationTime: validationStats.totalValidations > 0 ? validationStats.totalValidationTime / validationStats.totalValidations : 0, successRate: validationStats.totalValidations > 0 ? (validationStats.totalValidations - validationStats.failedValidations) / validationStats.totalValidations * 100 : 100 }), reset: () => { validationStats.totalValidations = 0; validationStats.failedValidations = 0; validationStats.totalValidationTime = 0; } }; } var validationMonitor = createValidationMonitor(); // src/components/kpi-card.tsx var import_jsx_runtime = require("react/jsx-runtime"); var getTrendIcon = (trend) => { if (!trend || trend === "neutral") return null; return trend === "up" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fillRule: "evenodd", d: "M5.293 7.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L6.707 7.707a1 1 0 01-1.414 0z", clipRule: "evenodd" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fillRule: "evenodd", d: "M14.707 12.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l2.293-2.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }); }; var getTrendColor = (trend) => { switch (trend) { case "up": return "text-green-600"; case "down": return "text-red-600"; default: return "text-gray-500"; } }; var renderIcon = (icon) => { if (!icon) return null; if (typeof icon === "function") { const IconComponent = icon; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconComponent, { className: "w-6 h-6" }); } return icon; }; var KPICard = (0, import_react.memo)(({ metric, variant = "default", className = "" }) => { const validation = (0, import_react.useMemo)(() => safeValidate(KPIMetricSchema, metric), [metric]); if (!validation.success) { console.error("Invalid KPI metric:", validation.errors); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `bg-red-50 border border-red-200 rounded-lg p-4 ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-red-600", children: "Invalid KPI metric configuration" }) }); } const validatedMetric = validation.data; const trendColor = (0, import_react.useMemo)(() => getTrendColor(validatedMetric.trend), [validatedMetric.trend]); const trendIcon = (0, import_react.useMemo)(() => getTrendIcon(validatedMetric.trend), [validatedMetric.trend]); const iconElement = (0, import_react.useMemo)(() => renderIcon(validatedMetric.icon), [validatedMetric.icon]); const formattedValue = (0, import_react.useMemo)(() => formatValue(validatedMetric.value, { format: validatedMetric.format, locale: validatedMetric.locale, currency: validatedMetric.currency, prefix: validatedMetric.prefix, suffix: validatedMetric.suffix, numberFormatOptions: validatedMetric.numberFormatOptions }), [validatedMetric.value, validatedMetric.format, validatedMetric.locale, validatedMetric.currency, validatedMetric.prefix, validatedMetric.suffix, validatedMetric.numberFormatOptions]); const formattedPreviousValue = (0, import_react.useMemo)( () => validatedMetric.previousValue !== void 0 ? formatValue(validatedMetric.previousValue, { format: validatedMetric.format, locale: validatedMetric.locale, currency: validatedMetric.currency, prefix: validatedMetric.prefix, suffix: validatedMetric.suffix, numberFormatOptions: validatedMetric.numberFormatOptions }) : null, [validatedMetric.previousValue, validatedMetric.format, validatedMetric.locale, validatedMetric.currency, validatedMetric.prefix, validatedMetric.suffix, validatedMetric.numberFormatOptions] ); const formattedTrendValue = (0, import_react.useMemo)( () => validatedMetric.trendValue !== void 0 ? formatTrendValue(validatedMetric.trendValue) : null, [validatedMetric.trendValue] ); if (variant === "compact") { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `bg-white rounded-lg border p-4 ${className}`, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm text-gray-600", children: validatedMetric.label }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-2xl font-semibold mt-1", children: formatValue(validatedMetric.value, { format: validatedMetric.format, locale: validatedMetric.locale, currency: validatedMetric.currency, prefix: validatedMetric.prefix, suffix: validatedMetric.suffix, numberFormatOptions: validatedMetric.numberFormatOptions }) }) ] }), iconElement && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `ml-4 ${validatedMetric.color || "text-blue-500"}`, children: iconElement }) ] }), validatedMetric.trendValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `flex items-center gap-1 mt-2 text-sm ${trendColor}`, children: [ trendIcon, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatTrendValue(validatedMetric.trendValue) }) ] }) ] }); } if (variant === "detailed") { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `bg-white rounded-lg border p-6 ${className}`, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between mb-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm font-medium text-gray-600", children: metric.label }), iconElement && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `mt-2 ${metric.color || "text-blue-500"}`, children: iconElement }) ] }), metric.trendValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `flex items-center gap-1 text-sm font-medium ${trendColor}`, children: [ trendIcon, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatTrendValue(metric.trendValue) }) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-3xl font-bold", children: formatValue(metric.value, { format: metric.format, locale: metric.locale, currency: metric.currency, prefix: metric.prefix, suffix: metric.suffix, numberFormatOptions: metric.numberFormatOptions }) }), metric.previousValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-sm text-gray-500 mt-2", children: [ "Previous: ", formatValue(metric.previousValue, { format: metric.format, locale: metric.locale, currency: metric.currency, prefix: metric.prefix, suffix: metric.suffix, numberFormatOptions: metric.numberFormatOptions }) ] }) ] }); } return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "div", { className: `bg-white rounded-lg border p-5 ${className}`, role: "region", "aria-label": `${metric.label}: ${formatValue(metric.value, { format: metric.format, locale: metric.locale, currency: metric.currency, prefix: metric.prefix, suffix: metric.suffix, numberFormatOptions: metric.numberFormatOptions })}`, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between mb-3", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm font-medium text-gray-600", children: metric.label }), iconElement && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `${metric.color || "text-blue-500"}`, children: iconElement }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-end justify-between", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-3xl font-semibold", children: formatValue(metric.value, { format: metric.format, locale: metric.locale, currency: metric.currency, prefix: metric.prefix, suffix: metric.suffix, numberFormatOptions: metric.numberFormatOptions }) }), metric.trendValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `flex items-center gap-1 text-sm font-medium ${trendColor}`, children: [ trendIcon, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatTrendValue(metric.trendValue) }) ] }) ] }) ] } ); }); KPICard.displayName = "KPICard"; // src/components/chart-container.tsx var import_react2 = require("react"); var import_recharts = require("recharts"); var import_jsx_runtime2 = require("react/jsx-runtime"); var ChartContainer = (0, import_react2.memo)(({ config, loading = false, error, className = "" }) => { const memoizedData = (0, import_react2.useMemo)(() => config.data, [config.data]); const commonProps = (0, import_react2.useMemo)(() => ({ data: memoizedData, margin: { top: 5, right: 30, left: 20, bottom: 5 } }), [memoizedData]); const renderMultipleSeries = (0, import_react2.useMemo)(() => (Component2) => { if (Array.isArray(config.yKey)) { return config.yKey.map((key, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( Component2, { type: "monotone", dataKey: key, stroke: config.colors?.[index] || `hsl(${index * 60}, 70%, 50%)`, fill: config.colors?.[index] || `hsl(${index * 60}, 70%, 50%)`, fillOpacity: Component2 === import_recharts.Area ? 0.3 : 1, strokeWidth: 2 }, key )); } else { return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( Component2, { type: "monotone", dataKey: config.yKey, stroke: config.colors?.[0] || "#3b82f6", fill: config.colors?.[0] || "#3b82f6", fillOpacity: Component2 === import_recharts.Area ? 0.3 : 1, strokeWidth: 2 } ); } }, [config.yKey, config.colors]); if (loading) { return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: `flex items-center justify-center p-8 ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-gray-500", children: "Loading..." }) }); } if (error) { return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: `p-4 border border-red-200 rounded-lg ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-red-600", children: error }) }); } const renderChart = () => { const commonProps2 = { data: config.data, margin: { top: 5, right: 30, left: 20, bottom: 5 } }; const renderMultipleSeries2 = (Component2, baseKey) => { if (Array.isArray(config.yKey)) { return config.yKey.map((key, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( Component2, { type: "monotone", dataKey: key, stroke: config.colors?.[index] || `hsl(${index * 60}, 70%, 50%)`, fill: config.colors?.[index] || `hsl(${index * 60}, 70%, 50%)`, fillOpacity: Component2 === import_recharts.Area ? 0.3 : 1, strokeWidth: 2 }, key )); } else { return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( Component2, { type: "monotone", dataKey: config.yKey, stroke: config.colors?.[0] || "#3b82f6", fill: config.colors?.[0] || "#3b82f6", fillOpacity: Component2 === import_recharts.Area ? 0.3 : 1, strokeWidth: 2 } ); } }; switch (config.type) { case "line": return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_recharts.LineChart, { ...commonProps2, children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.XAxis, { dataKey: config.xKey }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.YAxis, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Tooltip, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Legend, {}), renderMultipleSeries2(import_recharts.Line, "line") ] }); case "bar": return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_recharts.BarChart, { ...commonProps2, children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.XAxis, { dataKey: config.xKey }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.YAxis, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Tooltip, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Legend, {}), Array.isArray(config.yKey) ? config.yKey.map((key, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( import_recharts.Bar, { dataKey: key, fill: config.colors?.[index] || `hsl(${index * 60}, 70%, 50%)` }, key )) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Bar, { dataKey: config.yKey, fill: config.colors?.[0] || "#3b82f6" }) ] }); case "pie": return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_recharts.PieChart, { children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( import_recharts.Pie, { data: config.data, dataKey: config.yKey, nameKey: config.xKey, cx: "50%", cy: "50%", outerRadius: 80, fill: config.colors?.[0] || "#3b82f6" } ), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Tooltip, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Legend, {}) ] }); case "donut": return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_recharts.PieChart, { children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( import_recharts.Pie, { data: config.data, dataKey: config.yKey, nameKey: config.xKey, cx: "50%", cy: "50%", outerRadius: 80, innerRadius: 40, fill: config.colors?.[0] || "#3b82f6" } ), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Tooltip, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Legend, {}) ] }); case "area": return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_recharts.AreaChart, { ...commonProps2, children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.XAxis, { dataKey: config.xKey }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.YAxis, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Tooltip, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Legend, {}), renderMultipleSeries2(import_recharts.Area, "area") ] }); case "scatter": return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_recharts.ScatterChart, { ...commonProps2, children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.XAxis, { dataKey: config.xKey }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.YAxis, { dataKey: config.yKey }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Tooltip, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Legend, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( import_recharts.Scatter, { name: "Data Points", data: config.data, fill: config.colors?.[0] || "#3b82f6" } ) ] }); case "radar": return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_recharts.RadarChart, { ...commonProps2, children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.PolarGrid, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.PolarAngleAxis, { dataKey: config.xKey }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.PolarRadiusAxis, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( import_recharts.Radar, { name: "Values", dataKey: config.yKey, stroke: config.colors?.[0] || "#3b82f6", fill: config.colors?.[0] || "#3b82f6", fillOpacity: 0.3 } ), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Tooltip, {}), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.Legend, {}) ] }); default: return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [ "Unsupported chart type: ", config.type ] }); } }; return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)( "div", { className: `bg-white p-4 rounded-lg border ${className}`, role: "img", "aria-label": config.title || `Chart showing ${config.type} data`, children: [ config.title && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-lg font-semibold", children: config.title }), config.subtitle && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-sm text-gray-600", children: config.subtitle }) ] }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_recharts.ResponsiveContainer, { width: "100%", height: 300, children: renderChart() }) ] } ); }); ChartContainer.displayName = "ChartContainer"; // src/components/dashboard.tsx var import_react3 = require("react"); var import_jsx_runtime3 = require("react/jsx-runtime"); var Dashboard = ({ layout, onRefresh, className = "" }) => { const [filters, setFilters] = (0, import_react3.useState)(layout.filters || []); (0, import_react3.useEffect)(() => { if (layout.refreshInterval && onRefresh) { const interval = setInterval(onRefresh, layout.refreshInterval * 1e3); return () => clearInterval(interval); } }, [layout.refreshInterval, onRefresh]); const handleFilterChange = (filterId, value) => { setFilters( (prev) => prev.map( (filter) => filter.id === filterId ? { ...filter, value } : filter ) ); }; return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `p-6 ${className}`, children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mb-6", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { className: "text-2xl font-bold", children: layout.name }) }), filters.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mb-6 flex flex-wrap gap-4", children: filters.map((filter) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium mb-1", children: filter.label }), filter.type === "date" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( "input", { type: "date", value: filter.value || "", onChange: (e) => handleFilterChange(filter.id, e.target.value), className: "px-3 py-2 border rounded-md", "aria-label": filter.label } ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( "input", { type: "text", value: filter.value || "", onChange: (e) => handleFilterChange(filter.id, e.target.value), className: "px-3 py-2 border rounded-md", "aria-label": filter.label } ) ] }, filter.id)) }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6", children: layout.widgets.map((widget) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "div", { className: ` ${widget.span?.cols ? `col-span-${widget.span.cols}` : ""} ${widget.span?.rows ? `row-span-${widget.span.rows}` : ""} `, children: [ widget.type === "kpi" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "bg-white p-4 rounded-lg border", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-lg font-semibold mb-3", children: widget.title }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "space-y-4", children: widget.config.metrics?.map((metric) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(KPICard, { metric }, metric.id)) }) ] }), widget.type === "chart" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "bg-white p-4 rounded-lg border", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-lg font-semibold mb-3", children: widget.title }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChartContainer, { config: widget.config }) ] }), widget.type !== "kpi" && widget.type !== "chart" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "bg-white p-4 rounded-lg border", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-lg font-semibold", children: widget.title }), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-gray-500", children: [ "Unknown widget type: ", widget.type ] }) ] }) ] }, widget.id )) }) ] }); }; // src/components/export-button.tsx var import_react4 = require("react"); var import_jspdf = __toESM(require("jspdf")); var XLSX = __toESM(require("xlsx")); var import_jsx_runtime4 = require("react/jsx-runtime"); var ExportButton = ({ config, className = "" }) => { const [isExporting, setIsExporting] = (0, import_react4.useState)(false); const [error, setError] = (0, import_react4.useState)(null); const handleExport = async () => { setIsExporting(true); setError(null); try { switch (config.format) { case "pdf": await exportToPDF(config); break; case "excel": await exportToExcel(config); break; case "csv": await exportToCSV(config); break; case "json": await exportToJSON(config); break; default: throw new Error(`Unsupported export format: ${config.format}`); } } catch (err) { setError(err instanceof Error ? err.message : "Export failed"); } finally { setIsExporting(false); } }; return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `inline-block ${className}`, children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "button", { onClick: handleExport, disabled: isExporting, className: "px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50", role: "button", "aria-label": `Export to ${config.format}`, children: isExporting ? "Exporting..." : `Export to ${config.format.charAt(0).toUpperCase() + config.format.slice(1)}` } ), error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-2 text-sm text-red-600", role: "alert", children: error }) ] }); }; async function exportToPDF(config) { const doc = new import_jspdf.default(); if (config.title) { doc.setFontSize(16); doc.text(config.title, 10, 20); } if (config.data && config.data.length > 0) { const headers = config.columns || Object.keys(config.data[0]); const rows = config.data.map( (item) => headers.map((header) => String(item[header] || "")) ); doc.autoTable({ head: [headers], body: rows, startY: config.title ? 30 : 20, theme: "striped", styles: { fontSize: 10 } }); } doc.save(`${config.filename || "export"}.pdf`); } async function exportToExcel(config) { const ws = XLSX.utils.json_to_sheet(config.data); const wb = XLSX.utils.book_new(); if (config.title) { XLSX.utils.book_append_sheet(wb, ws, config.title); } else { XLSX.utils.book_append_sheet(wb, ws, "Data"); } XLSX.writeFile(wb, `${config.filename || "export"}.xlsx`); } async function exportToJSON(config) { const jsonString = JSON.stringify(config.data, null, 2); const blob = new Blob([jsonString], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${config.filename || "export"}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } async function exportToCSV(config) { const headers = config.columns || Object.keys(config.data[0] || {}); const csvContent = [ headers.join(","), ...config.data.map( (row) => headers.map((header) => { const value = row[header]; return typeof value === "string" && value.includes(",") ? `"${value}"` : value; }).join(",") ) ].join("\n"); const blob = new Blob([csvContent], { type: "text/csv" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${config.filename}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // src/components/error-boundary.tsx var import_react5 = __toESM(require("react")); var import_jsx_runtime5 = require("react/jsx-runtime"); var AnalyticsErrorBoundary = class extends import_react5.Component { constructor(props) { super(props); this.handleReset = () => { this.setState({ hasError: false, error: void 0, errorInfo: void 0 }); }; this.state = { hasError: false }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { this.setState({ error, errorInfo }); if (this.props.onError) { this.props.onError(error, errorInfo); } if (process.env.NODE_ENV === "development") { console.error("Analytics Error Boundary caught an error:", error, errorInfo); } } render() { if (this.state.hasError) { if (this.props.fallback) { return this.props.fallback; } return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6 m-4", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-start", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "svg", { className: "h-6 w-6 text-red-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" } ) } ) }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ml-3 flex-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "text-sm font-medium text-red-800", children: "Analytics Component Error" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mt-2 text-sm text-red-700", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "An error occurred while rendering the analytics component." }), this.props.showDetails && this.state.error && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("details", { className: "mt-2", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("summary", { className: "cursor-pointer font-medium", children: "Error Details" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mt-2 p-2 bg-red-100 rounded text-xs font-mono", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-red-900", children: this.state.error.message }), this.state.errorInfo && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mt-1 text-red-700", children: [ "Component Stack:", /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { className: "whitespace-pre-wrap", children: this.state.errorInfo.componentStack }) ] }) ] }) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mt-4 flex space-x-3", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "button", { type: "button", onClick: this.handleReset, className: "bg-red-100 text-red-700 px-4 py-2 rounded-md text-sm font-medium hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2", children: "Try Again" } ), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "button", { type: "button", onClick: () => window.location.reload(), className: "text-red-700 px-4 py-2 rounded-md text-sm font-medium hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2", children: "Reload Page" } ) ] }) ] }) ] }) }); } return this.props.children; } }; function KPIErrorBoundary({ children }) { return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( AnalyticsErrorBoundary, { fallback: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "bg-yellow-50 border border-yellow-200 rounded-lg p-4 m-2", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "svg", { className: "h-5 w-5 text-yellow-400 mr-2", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" } ) } ), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-yellow-800 text-sm", children: "Unable to display KPI data" }) ] }) }), onError: (error) => { console.warn("KPI Card Error:", error); }, children } ); } function ChartErrorBoundary({ children }) { return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( AnalyticsErrorBoundary, { fallback: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "bg-red-50 border border-red-200 rounded-lg p-8 m-2 text-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "svg", { className: "h-12 w-12 text-red-400 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" } ) } ), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "text-lg font-medium text-red-800 mb-2", children: "Chart Display Error" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-red-600 text-sm", children: "Unable to render chart due to an error" }) ] }), onError: (error) => { console.warn("Chart Error:", error); }, children } ); } function DashboardErrorBoundary({ children }) { return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( AnalyticsErrorBoundary, { fallback: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "bg-gray-50 border border-gray-200 rounded-lg p-8 m-4 text-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "svg", { className: "h-16 w-16 text-gray-400 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" } ) } ), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "text-xl font-medium text-gray-900 mb-2", children: "Dashboard Error" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-gray-600 mb-4", children: "The dashboard encountered an error and could not be displayed" }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "button", { onClick: () => window.location.reload(), className: "bg-blue-100 text-blue-700 px-6 py-2 rounded-md text-sm font-medium hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2", children: "Reload Dashboard" } ) ] }), onError: (error) => { console.error("Dashboard Error:", error); }, showDetails: process.env.NODE_ENV === "development", children } ); } function useErrorHandler() { const [error, setError] = import_react5.default.useState(null); const resetError = import_react5.default.useCallback(() => { setError(null); }, []); const captureError = import_react5.default.useCallback((error2) => { console.error("Analytics Hook Error:", error2); setError(error2); }, []); return { error, captureError, resetError }; } // src/hooks/use-analytics.ts var import_react6 = require("react"); var useAnalytics = (options) => { const [data, setData] = (0, import_react6.useState)(null); const [loading, setLoading] = (0, import_react6.useState)(true); const [error, setError] = (0, import_react6.useState)(null); const [subscriptions, setSubscriptions] = (0, import_react6.useState)({}); const fetchData = (0, import_react6.useCallback)(async () => { if (!options.endpoint) { throw new Error("Endpoi