@kodeme-io/next-core-analytics
Version:
Analytics, charts, dashboards, and reporting for Next.js applications
1,130 lines (1,120 loc) • 57 kB
JavaScript
"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