web-mojo 2.2.15 → 2.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.es.js +9 -9
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +3 -3
- package/dist/chunks/{ChatView-BJK6SF8T.js → ChatView-BqViL7e-.js} +2 -2
- package/dist/chunks/{ChatView-BJK6SF8T.js.map → ChatView-BqViL7e-.js.map} +1 -1
- package/dist/chunks/{ChatView-BPVE1u2i.js → ChatView-DMC-gH-t.js} +37 -9
- package/dist/chunks/{ChatView-BPVE1u2i.js.map → ChatView-DMC-gH-t.js.map} +1 -1
- package/dist/chunks/{Collection-zmb3xHhH.js → Collection-sSP1JF8d.js} +2 -2
- package/dist/chunks/{Collection-zmb3xHhH.js.map → Collection-sSP1JF8d.js.map} +1 -1
- package/dist/chunks/{ContextMenu-DPjJuxpq.js → ContextMenu-C9qnhdFg.js} +2 -2
- package/dist/chunks/{ContextMenu-DPjJuxpq.js.map → ContextMenu-C9qnhdFg.js.map} +1 -1
- package/dist/chunks/{Dialog-RzLLLfJD.js → Dialog-DgYEebtR.js} +3 -3
- package/dist/chunks/{Dialog-RzLLLfJD.js.map → Dialog-DgYEebtR.js.map} +1 -1
- package/dist/chunks/{Dialog--hl_Uh6X.js → Dialog-VyLukswR.js} +2 -2
- package/dist/chunks/{Dialog--hl_Uh6X.js.map → Dialog-VyLukswR.js.map} +1 -1
- package/dist/chunks/{FormView-C1emfj3B.js → FormView-DCI29KlM.js} +3 -3
- package/dist/chunks/FormView-DCI29KlM.js.map +1 -0
- package/dist/chunks/{FormView--WuITh01.js → FormView-GO8yahxE.js} +39 -5
- package/dist/chunks/FormView-GO8yahxE.js.map +1 -0
- package/dist/chunks/{ListView-BBZw7GUS.js → ListView-0fx1T87y.js} +2 -2
- package/dist/chunks/{ListView-BBZw7GUS.js.map → ListView-0fx1T87y.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-DoxqoF1X.js → MetricsMiniChartWidget-JLmDm3sm.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-DoxqoF1X.js.map → MetricsMiniChartWidget-JLmDm3sm.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-DyVs4Wt0.js → MetricsMiniChartWidget-XTU45k_e.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-DyVs4Wt0.js.map → MetricsMiniChartWidget-XTU45k_e.js.map} +1 -1
- package/dist/chunks/{PDFViewer-CHX2NLkG.js → PDFViewer-DMpN-d6N.js} +2 -2
- package/dist/chunks/{PDFViewer-CHX2NLkG.js.map → PDFViewer-DMpN-d6N.js.map} +1 -1
- package/dist/chunks/{PDFViewer-BxFcG82d.js → PDFViewer-DbPamhVj.js} +2 -2
- package/dist/chunks/{PDFViewer-BxFcG82d.js.map → PDFViewer-DbPamhVj.js.map} +1 -1
- package/dist/chunks/{TokenManager-DFWJ-cuN.js → TokenManager-A0zSdQ6i.js} +4 -4
- package/dist/chunks/{TokenManager-DFWJ-cuN.js.map → TokenManager-A0zSdQ6i.js.map} +1 -1
- package/dist/chunks/{TokenManager-Bi5eDY8Y.js → TokenManager-DH4-kElJ.js} +2 -2
- package/dist/chunks/{TokenManager-Bi5eDY8Y.js.map → TokenManager-DH4-kElJ.js.map} +1 -1
- package/dist/chunks/{version-BzRzH4mJ.js → version-BpfLBsbA.js} +2 -2
- package/dist/chunks/{version-BzRzH4mJ.js.map → version-BpfLBsbA.js.map} +1 -1
- package/dist/chunks/{version-S1OYxk1c.js → version-iQsUQBiz.js} +4 -4
- package/dist/chunks/{version-S1OYxk1c.js.map → version-iQsUQBiz.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +5 -5
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +11 -11
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +4 -4
- package/dist/map.es.js +1 -1
- package/dist/timeline.es.js +2 -2
- package/package.json +1 -1
- package/dist/chunks/FormView--WuITh01.js.map +0 -1
- package/dist/chunks/FormView-C1emfj3B.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { V as View } from "./Rest-0oRgqNjX.js";
|
|
2
|
-
import { C as Collection } from "./Collection-
|
|
2
|
+
import { C as Collection } from "./Collection-sSP1JF8d.js";
|
|
3
3
|
class ListViewItem extends View {
|
|
4
4
|
constructor(options = {}) {
|
|
5
5
|
super({
|
|
@@ -492,4 +492,4 @@ export {
|
|
|
492
492
|
ListViewItem as L,
|
|
493
493
|
ListView as a
|
|
494
494
|
};
|
|
495
|
-
//# sourceMappingURL=ListView-
|
|
495
|
+
//# sourceMappingURL=ListView-0fx1T87y.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListView-BBZw7GUS.js","sources":["../../src/core/views/list/ListViewItem.js","../../src/core/views/list/ListView.js"],"sourcesContent":["/**\n * ListViewItem - Individual item view for ListView\n *\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n *\n * Events:\n * - 'item:click' - Emitted when item is clicked\n * - 'item:select' - Emitted when item is selected\n * - 'item:deselect' - Emitted when item is deselected\n *\n * @example\n * const item = new ListViewItem({\n * model: userModel,\n * template: '<div class=\"user-item\">{{name}} - {{email}}</div>'\n * });\n */\n\nimport View from '@core/View.js';\n\nclass ListViewItem extends View {\n constructor(options = {}) {\n super({\n className: 'list-view-item',\n ...options\n });\n\n // Item-specific properties\n this.selected = false;\n this.index = options.index ?? 0;\n this.listView = options.listView ?? null;\n\n // Default template if none provided\n if (!this.template) {\n this.template = `\n <div class=\"list-item-content\" data-action=\"select\">\n {{#model}}\n {{#id}}<span class=\"item-id\">{{id}}</span>{{/id}}\n {{#name}}<span class=\"item-name\">{{name}}</span>{{/name}}\n {{#title}}<span class=\"item-title\">{{title}}</span>{{/title}}\n {{#label}}<span class=\"item-label\">{{label}}</span>{{/label}}\n {{#description}}<p class=\"item-description\">{{description}}</p>{{/description}}\n {{/model}}\n {{^model}}\n <span class=\"item-empty\">No data</span>\n {{/model}}\n </div>\n `;\n }\n }\n\n /**\n * Handle item selection action\n */\n async onActionSelect(event, _element) {\n event.stopPropagation();\n\n if (this.selected) {\n this.deselect();\n } else {\n this.select();\n }\n }\n\n /**\n * Select this item\n */\n select() {\n if (this.selected) return;\n\n this.selected = true;\n this.addClass('selected');\n\n // Emit selection event with item data\n this.emit('item:select', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:select', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Deselect this item\n */\n deselect() {\n if (!this.selected) return;\n\n this.selected = false;\n this.removeClass('selected');\n\n // Emit deselection event\n this.emit('item:deselect', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:deselect', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Handle click events on the item\n */\n async onActionDefault(action, _event, _element) {\n // Emit click event for any action not specifically handled\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: action,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: action,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Set the item's index in the list\n */\n setIndex(index) {\n this.index = index;\n this.element.setAttribute('data-index', index);\n return this;\n }\n\n /**\n * Update the item's selection state\n */\n setSelected(selected) {\n if (selected) {\n this.select();\n } else {\n this.deselect();\n }\n return this;\n }\n\n /**\n * Override destroy to clean up references\n */\n async destroy() {\n // Remove reference to parent ListView\n this.listView = null;\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListViewItem;\n","/**\n * ListView - Visual list component for Collections\n *\n * Manages a collection of ListViewItem views, each with its own model.\n * When a model changes, only its corresponding ListViewItem re-renders.\n *\n * Events:\n * - 'item:click' - Emitted when any item is clicked\n * - 'item:select' - Emitted when an item is selected\n * - 'item:deselect' - Emitted when an item is deselected\n * - 'selection:change' - Emitted when selection changes\n * - 'list:empty' - Emitted when list becomes empty\n * - 'list:loaded' - Emitted when list is populated\n *\n * @example\n * // Basic usage with custom item template\n * const listView = new ListView({\n * collection: userCollection,\n * itemTemplate: '<div class=\"user-item\">{{name}} - {{email}}</div>',\n * selectionMode: 'single'\n * });\n *\n * // Custom template with model fields\n * const productList = new ListView({\n * collection: productCollection,\n * itemTemplate: `\n * <div class=\"product-card\" data-action=\"select\">\n * <h4>{{name}}</h4>\n * <p class=\"price\">{{price|currency}}</p>\n * <p>{{description|truncate(100)}}</p>\n * </div>\n * `,\n * selectionMode: 'multiple'\n * });\n *\n * // Using custom item class with template\n * const customList = new ListView({\n * collection: myCollection,\n * itemClass: CustomListItem, // Your custom ListViewItem subclass\n * itemTemplate: '<div>{{title}}</div>', // Passed as 'template' to itemClass constructor\n * selectionMode: 'none'\n * });\n *\n * // Dynamic template update\n * listView.setItemTemplate('<div class=\"compact\">{{name}}</div>', true);\n */\n\nimport View from '@core/View.js';\nimport Collection from '@core/Collection.js';\nimport ListViewItem from './ListViewItem.js';\n\nclass ListView extends View {\n constructor(options = {}) {\n super({\n className: 'list-view',\n template: `\n <div class=\"list-view-container\">\n {{#loading}}\n <div class=\"list-loading\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n Loading...\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"list-empty\">\n {{emptyMessage}}\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"list-items\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // ListView specific properties\n this.collection = null;\n this.itemViews = new Map(); // Map of model.id -> ListViewItem\n this.selectedItems = new Set(); // Set of selected item IDs\n\n // Configuration\n this.itemTemplate = options.itemTemplate || null; // Template passed to each item's view\n this.itemClass = options.itemClass || ListViewItem; // Class for creating item views\n this.selectionMode = options.selectionMode || 'none'; // none, single, multiple\n this.emptyMessage = options.emptyMessage || 'No items to display';\n this.loading = false;\n this.isEmpty = true;\n\n }\n\n /**\n * Override onInit to set up initial state\n */\n async onInit() {\n // Initial render will happen automatically\n this._initCollection(this.options.collection || this.options.Collection);\n }\n\n\n /**\n * Initialize the collection\n */\n _initCollection(collectionOrClass) {\n if (!collectionOrClass) {\n console.log('Collection not provided');\n return;\n };\n\n // Check if it's already a Collection instance\n if (collectionOrClass instanceof Collection) {\n this.setCollection(collectionOrClass);\n }\n // Check if it's a Collection class\n else if (typeof collectionOrClass === 'function') {\n const collection = new collectionOrClass();\n this.setCollection(collection);\n }\n // Check if it's an array of data\n else if (Array.isArray(collectionOrClass)) {\n const collection = new Collection(null, {}, collectionOrClass);\n this.setCollection(collection);\n }\n }\n\n /**\n * Set the collection for this list view\n */\n setCollection(collection) {\n if (this.collection === collection) return this;\n\n // Clean up old collection listeners\n if (this.collection) {\n this.collection.off('add', this._onModelsAdded, this);\n this.collection.off('remove', this._onModelsRemoved, this);\n this.collection.off('reset', this._onCollectionReset, this);\n this.collection.off('fetch:start', this._onFetchStart, this);\n this.collection.off('fetch:end', this._onFetchEnd, this);\n }\n\n this.collection = collection;\n\n if (this.options.defaultQuery && !this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.defaultQuery };\n }\n\n if (this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.collectionParams };\n }\n\n // Set up new collection listeners\n if (this.collection) {\n this.collection.on('add', this._onModelsAdded, this);\n this.collection.on('remove', this._onModelsRemoved, this);\n this.collection.on('reset', this._onCollectionReset, this);\n this.collection.on('fetch:start', this._onFetchStart, this);\n this.collection.on('fetch:end', this._onFetchEnd, this);\n\n // Build items for existing models\n this._buildItems();\n }\n\n return this;\n }\n\n async _renderChildren() {\n await super._renderChildren();\n const itemsContainer = this.getChildElement(\"items\");\n if (!itemsContainer) {\n // console.warn('ListView: items container not found');\n return;\n }\n this.forEachItem((item, index) => {\n itemsContainer.appendChild(item.element);\n item.render(false);\n });\n }\n\n /**\n * Build item views for all models in collection\n */\n _buildItems() {\n // Clear existing items\n this._clearItems();\n\n if (!this.collection || this.collection.isEmpty()) {\n this.isEmpty = true;\n this.emit('list:empty');\n return;\n }\n\n this.isEmpty = false;\n\n // Create item views for each model\n this.collection.forEach((model, index) => {\n this._createItemView(model, index);\n });\n\n this.emit('list:loaded', { count: this.collection.length() });\n\n // Render if already mounted\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Create an item view for a model\n * The itemTemplate is passed as the template option to the itemClass constructor\n */\n _createItemView(model, index) {\n // Don't create duplicate views\n if (this.itemViews.has(model.id)) return;\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate, // Pass the itemTemplate to the item view\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:select', this._onItemSelect.bind(this));\n itemView.on('item:deselect', this._onItemDeselect.bind(this));\n\n return itemView;\n }\n\n /**\n * Clear all item views\n */\n _clearItems() {\n this.forEachItem(itemView => {\n this.removeChild(itemView.id);\n });\n this.itemViews.clear();\n this.selectedItems.clear();\n }\n\n /**\n * Handle models added to collection\n */\n _onModelsAdded(event) {\n const { models } = event;\n\n models.forEach(model => {\n const index = this.collection.models.indexOf(model);\n this._createItemView(model, index);\n });\n\n this.isEmpty = this.collection.isEmpty();\n\n // Re-render to show new items\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle models removed from collection\n */\n _onModelsRemoved(event) {\n const { models } = event;\n\n models.forEach(model => {\n const itemView = this.itemViews.get(model.id);\n if (itemView) {\n this.removeChild(itemView.id);\n this.itemViews.delete(model.id);\n this.selectedItems.delete(model.id);\n }\n });\n\n this.isEmpty = this.collection.isEmpty();\n\n // Re-render to update display\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n\n if (this.isEmpty) {\n this.emit('list:empty');\n }\n }\n\n /**\n * Handle collection reset\n */\n _onCollectionReset(_event) {\n this._buildItems();\n }\n\n /**\n * Handle fetch start\n */\n _onFetchStart() {\n this.loading = true;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle fetch end\n */\n _onFetchEnd() {\n this.loading = false;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle item selection\n */\n _onItemSelect(event) {\n const { model, item } = event;\n\n if (this.selectionMode === 'none') {\n item.deselect();\n return;\n }\n\n if (this.selectionMode === 'single') {\n // Deselect all other items\n this.itemViews.forEach((view, id) => {\n if (id !== model.id && view.selected) {\n view.deselect();\n }\n });\n this.selectedItems.clear();\n }\n\n this.selectedItems.add(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: item,\n model: model\n });\n }\n\n /**\n * Handle item deselection\n */\n _onItemDeselect(event) {\n const { model } = event;\n\n this.selectedItems.delete(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: event.item,\n model: model\n });\n }\n\n /**\n * Get selected items\n */\n getSelectedItems() {\n const selected = [];\n this.selectedItems.forEach(id => {\n const itemView = this.itemViews.get(id);\n if (itemView) {\n selected.push({\n view: itemView,\n model: itemView.model,\n data: itemView.model?.toJSON ? itemView.model.toJSON() : itemView.model\n });\n }\n });\n return selected;\n }\n\n /**\n * Iterate over each item view in the list\n * @param {function} callback - Function to execute for each item (itemView, model, index)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {ListView} Returns the ListView for chaining\n */\n forEachItem(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n let index = 0;\n this.itemViews.forEach((itemView, modelId) => {\n callback.call(thisArg, itemView, itemView.model, index++);\n });\n\n return this;\n }\n\n /**\n * Clear selection\n */\n clearSelection() {\n this.forEachItem(itemView => {\n if (itemView.selected) {\n itemView.deselect();\n }\n });\n this.selectedItems.clear();\n\n this.emit('selection:change', {\n selected: []\n });\n }\n\n /**\n * Select item by model ID\n */\n selectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.select();\n }\n return this;\n }\n\n /**\n * Deselect item by model ID\n */\n deselectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.deselect();\n }\n return this;\n }\n\n /**\n * Set or update the item template\n * @param {string} template - New template string for items\n * @param {boolean} rerender - Whether to re-render existing items with new template\n * @returns {ListView} Returns the ListView for chaining\n */\n setItemTemplate(template, rerender = false) {\n this.itemTemplate = template;\n\n if (rerender && this.itemViews.size > 0) {\n // Update template for all existing item views\n this.forEachItem((itemView) => {\n itemView.setTemplate(template);\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n }\n\n return this;\n }\n\n async onAfterMount() {\n await super.onAfterMount();\n if (this.collection && (this.options.fetchOnMount || !this.collection.lastFetchTime)) {\n this.collection.fetch();\n }\n }\n\n /**\n * Refresh the list (re-fetch if collection supports it)\n */\n async refresh() {\n if (this.collection && this.collection.restEnabled) {\n return await this.collection.fetch();\n }\n this._buildItems();\n }\n\n /**\n * Override destroy to clean up\n */\n async destroy() {\n // Clean up collection listeners\n if (this.collection) {\n this.collection.off('add', this._onModelsAdded, this);\n this.collection.off('remove', this._onModelsRemoved, this);\n this.collection.off('reset', this._onCollectionReset, this);\n this.collection.off('fetch:start', this._onFetchStart, this);\n this.collection.off('fetch:end', this._onFetchEnd, this);\n }\n\n // Clear items\n this._clearItems();\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListView;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ListView-0fx1T87y.js","sources":["../../src/core/views/list/ListViewItem.js","../../src/core/views/list/ListView.js"],"sourcesContent":["/**\n * ListViewItem - Individual item view for ListView\n *\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n *\n * Events:\n * - 'item:click' - Emitted when item is clicked\n * - 'item:select' - Emitted when item is selected\n * - 'item:deselect' - Emitted when item is deselected\n *\n * @example\n * const item = new ListViewItem({\n * model: userModel,\n * template: '<div class=\"user-item\">{{name}} - {{email}}</div>'\n * });\n */\n\nimport View from '@core/View.js';\n\nclass ListViewItem extends View {\n constructor(options = {}) {\n super({\n className: 'list-view-item',\n ...options\n });\n\n // Item-specific properties\n this.selected = false;\n this.index = options.index ?? 0;\n this.listView = options.listView ?? null;\n\n // Default template if none provided\n if (!this.template) {\n this.template = `\n <div class=\"list-item-content\" data-action=\"select\">\n {{#model}}\n {{#id}}<span class=\"item-id\">{{id}}</span>{{/id}}\n {{#name}}<span class=\"item-name\">{{name}}</span>{{/name}}\n {{#title}}<span class=\"item-title\">{{title}}</span>{{/title}}\n {{#label}}<span class=\"item-label\">{{label}}</span>{{/label}}\n {{#description}}<p class=\"item-description\">{{description}}</p>{{/description}}\n {{/model}}\n {{^model}}\n <span class=\"item-empty\">No data</span>\n {{/model}}\n </div>\n `;\n }\n }\n\n /**\n * Handle item selection action\n */\n async onActionSelect(event, _element) {\n event.stopPropagation();\n\n if (this.selected) {\n this.deselect();\n } else {\n this.select();\n }\n }\n\n /**\n * Select this item\n */\n select() {\n if (this.selected) return;\n\n this.selected = true;\n this.addClass('selected');\n\n // Emit selection event with item data\n this.emit('item:select', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:select', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Deselect this item\n */\n deselect() {\n if (!this.selected) return;\n\n this.selected = false;\n this.removeClass('selected');\n\n // Emit deselection event\n this.emit('item:deselect', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:deselect', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Handle click events on the item\n */\n async onActionDefault(action, _event, _element) {\n // Emit click event for any action not specifically handled\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: action,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: action,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Set the item's index in the list\n */\n setIndex(index) {\n this.index = index;\n this.element.setAttribute('data-index', index);\n return this;\n }\n\n /**\n * Update the item's selection state\n */\n setSelected(selected) {\n if (selected) {\n this.select();\n } else {\n this.deselect();\n }\n return this;\n }\n\n /**\n * Override destroy to clean up references\n */\n async destroy() {\n // Remove reference to parent ListView\n this.listView = null;\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListViewItem;\n","/**\n * ListView - Visual list component for Collections\n *\n * Manages a collection of ListViewItem views, each with its own model.\n * When a model changes, only its corresponding ListViewItem re-renders.\n *\n * Events:\n * - 'item:click' - Emitted when any item is clicked\n * - 'item:select' - Emitted when an item is selected\n * - 'item:deselect' - Emitted when an item is deselected\n * - 'selection:change' - Emitted when selection changes\n * - 'list:empty' - Emitted when list becomes empty\n * - 'list:loaded' - Emitted when list is populated\n *\n * @example\n * // Basic usage with custom item template\n * const listView = new ListView({\n * collection: userCollection,\n * itemTemplate: '<div class=\"user-item\">{{name}} - {{email}}</div>',\n * selectionMode: 'single'\n * });\n *\n * // Custom template with model fields\n * const productList = new ListView({\n * collection: productCollection,\n * itemTemplate: `\n * <div class=\"product-card\" data-action=\"select\">\n * <h4>{{name}}</h4>\n * <p class=\"price\">{{price|currency}}</p>\n * <p>{{description|truncate(100)}}</p>\n * </div>\n * `,\n * selectionMode: 'multiple'\n * });\n *\n * // Using custom item class with template\n * const customList = new ListView({\n * collection: myCollection,\n * itemClass: CustomListItem, // Your custom ListViewItem subclass\n * itemTemplate: '<div>{{title}}</div>', // Passed as 'template' to itemClass constructor\n * selectionMode: 'none'\n * });\n *\n * // Dynamic template update\n * listView.setItemTemplate('<div class=\"compact\">{{name}}</div>', true);\n */\n\nimport View from '@core/View.js';\nimport Collection from '@core/Collection.js';\nimport ListViewItem from './ListViewItem.js';\n\nclass ListView extends View {\n constructor(options = {}) {\n super({\n className: 'list-view',\n template: `\n <div class=\"list-view-container\">\n {{#loading}}\n <div class=\"list-loading\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n Loading...\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"list-empty\">\n {{emptyMessage}}\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"list-items\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // ListView specific properties\n this.collection = null;\n this.itemViews = new Map(); // Map of model.id -> ListViewItem\n this.selectedItems = new Set(); // Set of selected item IDs\n\n // Configuration\n this.itemTemplate = options.itemTemplate || null; // Template passed to each item's view\n this.itemClass = options.itemClass || ListViewItem; // Class for creating item views\n this.selectionMode = options.selectionMode || 'none'; // none, single, multiple\n this.emptyMessage = options.emptyMessage || 'No items to display';\n this.loading = false;\n this.isEmpty = true;\n\n }\n\n /**\n * Override onInit to set up initial state\n */\n async onInit() {\n // Initial render will happen automatically\n this._initCollection(this.options.collection || this.options.Collection);\n }\n\n\n /**\n * Initialize the collection\n */\n _initCollection(collectionOrClass) {\n if (!collectionOrClass) {\n console.log('Collection not provided');\n return;\n };\n\n // Check if it's already a Collection instance\n if (collectionOrClass instanceof Collection) {\n this.setCollection(collectionOrClass);\n }\n // Check if it's a Collection class\n else if (typeof collectionOrClass === 'function') {\n const collection = new collectionOrClass();\n this.setCollection(collection);\n }\n // Check if it's an array of data\n else if (Array.isArray(collectionOrClass)) {\n const collection = new Collection(null, {}, collectionOrClass);\n this.setCollection(collection);\n }\n }\n\n /**\n * Set the collection for this list view\n */\n setCollection(collection) {\n if (this.collection === collection) return this;\n\n // Clean up old collection listeners\n if (this.collection) {\n this.collection.off('add', this._onModelsAdded, this);\n this.collection.off('remove', this._onModelsRemoved, this);\n this.collection.off('reset', this._onCollectionReset, this);\n this.collection.off('fetch:start', this._onFetchStart, this);\n this.collection.off('fetch:end', this._onFetchEnd, this);\n }\n\n this.collection = collection;\n\n if (this.options.defaultQuery && !this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.defaultQuery };\n }\n\n if (this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.collectionParams };\n }\n\n // Set up new collection listeners\n if (this.collection) {\n this.collection.on('add', this._onModelsAdded, this);\n this.collection.on('remove', this._onModelsRemoved, this);\n this.collection.on('reset', this._onCollectionReset, this);\n this.collection.on('fetch:start', this._onFetchStart, this);\n this.collection.on('fetch:end', this._onFetchEnd, this);\n\n // Build items for existing models\n this._buildItems();\n }\n\n return this;\n }\n\n async _renderChildren() {\n await super._renderChildren();\n const itemsContainer = this.getChildElement(\"items\");\n if (!itemsContainer) {\n // console.warn('ListView: items container not found');\n return;\n }\n this.forEachItem((item, index) => {\n itemsContainer.appendChild(item.element);\n item.render(false);\n });\n }\n\n /**\n * Build item views for all models in collection\n */\n _buildItems() {\n // Clear existing items\n this._clearItems();\n\n if (!this.collection || this.collection.isEmpty()) {\n this.isEmpty = true;\n this.emit('list:empty');\n return;\n }\n\n this.isEmpty = false;\n\n // Create item views for each model\n this.collection.forEach((model, index) => {\n this._createItemView(model, index);\n });\n\n this.emit('list:loaded', { count: this.collection.length() });\n\n // Render if already mounted\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Create an item view for a model\n * The itemTemplate is passed as the template option to the itemClass constructor\n */\n _createItemView(model, index) {\n // Don't create duplicate views\n if (this.itemViews.has(model.id)) return;\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate, // Pass the itemTemplate to the item view\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:select', this._onItemSelect.bind(this));\n itemView.on('item:deselect', this._onItemDeselect.bind(this));\n\n return itemView;\n }\n\n /**\n * Clear all item views\n */\n _clearItems() {\n this.forEachItem(itemView => {\n this.removeChild(itemView.id);\n });\n this.itemViews.clear();\n this.selectedItems.clear();\n }\n\n /**\n * Handle models added to collection\n */\n _onModelsAdded(event) {\n const { models } = event;\n\n models.forEach(model => {\n const index = this.collection.models.indexOf(model);\n this._createItemView(model, index);\n });\n\n this.isEmpty = this.collection.isEmpty();\n\n // Re-render to show new items\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle models removed from collection\n */\n _onModelsRemoved(event) {\n const { models } = event;\n\n models.forEach(model => {\n const itemView = this.itemViews.get(model.id);\n if (itemView) {\n this.removeChild(itemView.id);\n this.itemViews.delete(model.id);\n this.selectedItems.delete(model.id);\n }\n });\n\n this.isEmpty = this.collection.isEmpty();\n\n // Re-render to update display\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n\n if (this.isEmpty) {\n this.emit('list:empty');\n }\n }\n\n /**\n * Handle collection reset\n */\n _onCollectionReset(_event) {\n this._buildItems();\n }\n\n /**\n * Handle fetch start\n */\n _onFetchStart() {\n this.loading = true;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle fetch end\n */\n _onFetchEnd() {\n this.loading = false;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle item selection\n */\n _onItemSelect(event) {\n const { model, item } = event;\n\n if (this.selectionMode === 'none') {\n item.deselect();\n return;\n }\n\n if (this.selectionMode === 'single') {\n // Deselect all other items\n this.itemViews.forEach((view, id) => {\n if (id !== model.id && view.selected) {\n view.deselect();\n }\n });\n this.selectedItems.clear();\n }\n\n this.selectedItems.add(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: item,\n model: model\n });\n }\n\n /**\n * Handle item deselection\n */\n _onItemDeselect(event) {\n const { model } = event;\n\n this.selectedItems.delete(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: event.item,\n model: model\n });\n }\n\n /**\n * Get selected items\n */\n getSelectedItems() {\n const selected = [];\n this.selectedItems.forEach(id => {\n const itemView = this.itemViews.get(id);\n if (itemView) {\n selected.push({\n view: itemView,\n model: itemView.model,\n data: itemView.model?.toJSON ? itemView.model.toJSON() : itemView.model\n });\n }\n });\n return selected;\n }\n\n /**\n * Iterate over each item view in the list\n * @param {function} callback - Function to execute for each item (itemView, model, index)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {ListView} Returns the ListView for chaining\n */\n forEachItem(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n let index = 0;\n this.itemViews.forEach((itemView, modelId) => {\n callback.call(thisArg, itemView, itemView.model, index++);\n });\n\n return this;\n }\n\n /**\n * Clear selection\n */\n clearSelection() {\n this.forEachItem(itemView => {\n if (itemView.selected) {\n itemView.deselect();\n }\n });\n this.selectedItems.clear();\n\n this.emit('selection:change', {\n selected: []\n });\n }\n\n /**\n * Select item by model ID\n */\n selectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.select();\n }\n return this;\n }\n\n /**\n * Deselect item by model ID\n */\n deselectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.deselect();\n }\n return this;\n }\n\n /**\n * Set or update the item template\n * @param {string} template - New template string for items\n * @param {boolean} rerender - Whether to re-render existing items with new template\n * @returns {ListView} Returns the ListView for chaining\n */\n setItemTemplate(template, rerender = false) {\n this.itemTemplate = template;\n\n if (rerender && this.itemViews.size > 0) {\n // Update template for all existing item views\n this.forEachItem((itemView) => {\n itemView.setTemplate(template);\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n }\n\n return this;\n }\n\n async onAfterMount() {\n await super.onAfterMount();\n if (this.collection && (this.options.fetchOnMount || !this.collection.lastFetchTime)) {\n this.collection.fetch();\n }\n }\n\n /**\n * Refresh the list (re-fetch if collection supports it)\n */\n async refresh() {\n if (this.collection && this.collection.restEnabled) {\n return await this.collection.fetch();\n }\n this._buildItems();\n }\n\n /**\n * Override destroy to clean up\n */\n async destroy() {\n // Clean up collection listeners\n if (this.collection) {\n this.collection.off('add', this._onModelsAdded, this);\n this.collection.off('remove', this._onModelsRemoved, this);\n this.collection.off('reset', this._onCollectionReset, this);\n this.collection.off('fetch:start', this._onFetchStart, this);\n this.collection.off('fetch:end', this._onFetchEnd, this);\n }\n\n // Clear items\n this._clearItems();\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListView;\n"],"names":[],"mappings":"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"}
|
package/dist/chunks/{MetricsMiniChartWidget-DoxqoF1X.js → MetricsMiniChartWidget-JLmDm3sm.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const t=require("./Dialog--hl_Uh6X.js"),e=require("./Rest-P-KCJpjB.js"),s=require("./WebSocketClient-BbPsISrp.js");class BaseChart extends e.View{constructor(t={}){super({...t,className:`chart-component ${t.className||""}`,tagName:"div"}),this.chart=null,this.chartType=t.chartType||"line",this.endpoint=t.endpoint||null,this.data=t.data||null,this.dataTransform=t.dataTransform||null,this.refreshInterval=t.refreshInterval||null,this.autoRefresh=!1!==t.autoRefresh,this.refreshTimer=null,this.websocketUrl=t.websocketUrl||null,this.websocket=null,this.websocketReconnect=!1!==t.websocketReconnect,this.width=t.width||null,this.height=t.height||null,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),void 0===t.maintainAspectRatio&&(t.maintainAspectRatio=!0),this.title=t.title||"",this.chartTitle=t.chartTitle||"",this.chartOptions={responsive:!0,maintainAspectRatio:t.maintainAspectRatio,interaction:{intersect:!1,mode:"index"},plugins:{legend:{display:!1!==t.showLegend,position:t.legendPosition||"top"},title:{display:!!this.chartTitle,text:this.chartTitle},tooltip:{enabled:!1!==t.showTooltips,backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",bodyColor:"#fff",borderColor:"rgba(255,255,255,0.1)",borderWidth:1}},...t.chartOptions},this.xAxis=t.xAxis||null,this.yAxis=t.yAxis||null,this.tooltipFormatters=t.tooltip||{},this.theme=t.theme||"light",this.colorScheme=t.colorScheme||"default",this.animations=!1!==t.animations,this.exportEnabled=!0===t.exportEnabled,this.exportFormats=t.exportFormats||["png","jpg","csv"],this.isLoading=!1,this.hasError=!1,this.lastFetch=null,this.dataPoints=0,this.canvas=null,this.chartJsCdn=t.chartJsCdn||"https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js",this.dataFormatter=e.dataFormatter,this._essentialListeners=[]}get refreshEnabled(){return!(!this.endpoint&&!this.websocketUrl)}buildDefaultHeaderConfig(){return{titleHtml:this.title||"",chartTitle:this.chartTitle||"",showExport:!0===this.exportEnabled,showRefresh:this.refreshEnabled,showTheme:!0,controls:[]}}async getTemplate(){return'\n <div class="chart-container" data-theme="{{theme}}">\n <div class="chart-header mb-3">\n <div data-container="header"></div>\n <div class="chart-header-aux mt-2">\n <div data-container="header-aux"></div>\n </div>\n </div>\n\n <div class="chart-content position-relative" {{#contentStyle}}style="{{contentStyle}}"{{/contentStyle}}>\n <canvas class="chart-canvas" data-container="canvas"></canvas>\n\n \x3c!-- Loading overlay --\x3e\n <div class="chart-overlay d-none" data-loading>\n <div class="d-flex flex-column align-items-center">\n <div class="spinner-border text-primary mb-2" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n <small class="text-muted">Loading chart data...</small>\n </div>\n </div>\n\n \x3c!-- Error overlay --\x3e\n <div class="chart-overlay d-none" data-error>\n <div class="alert alert-danger mb-0" role="alert">\n <div class="d-flex align-items-center">\n <i class="bi bi-exclamation-triangle me-2"></i>\n <div class="flex-grow-1">\n <strong>Error:</strong> <span class="error-message">Failed to load chart data</span>\n </div>\n <button class="btn btn-sm btn-outline-danger ms-2" data-action="retry-load">\n <i class="bi bi-arrow-clockwise"></i> Retry\n </button>\n </div>\n </div>\n </div>\n\n \x3c!-- No data overlay --\x3e\n <div class="chart-overlay d-none" data-no-data>\n <div class="text-center text-muted">\n <i class="bi bi-bar-chart display-4 mb-3 opacity-50"></i>\n <p class="mb-0">No data available</p>\n {{#refreshEnabled}}\n <button class="btn btn-sm btn-outline-secondary mt-2" data-action="refresh-chart">\n <i class="bi bi-arrow-clockwise"></i> Refresh\n </button>\n {{/refreshEnabled}}\n </div>\n </div>\n\n \x3c!-- WebSocket status indicator --\x3e\n <div class="position-absolute top-0 end-0 mt-2 me-2">\n <span class="badge bg-success websocket-status" style="display: none;" data-websocket-status>\n <i class="bi bi-wifi"></i> Live\n </span>\n </div>\n </div>\n\n <div class="chart-footer mt-2" style="display: none;">\n <div class="row">\n <div class="col">\n <small class="text-muted">\n <i class="bi bi-graph-up me-1"></i>\n <span class="data-points">0 data points</span>\n </small>\n </div>\n <div class="col text-end">\n <small class="text-muted refresh-info">\n Auto-refresh: <span class="refresh-status">Off</span>\n </small>\n </div>\n </div>\n </div>\n </div>\n '}async onInit(){await this.initializeChartJS();try{const t=this.headerConfig||(this.buildDefaultHeaderConfig?this.buildDefaultHeaderConfig():null);t&&(this.headerView=new ChartHeaderView({...t,containerId:"header"}),this.addChild(this.headerView))}catch(t){}}async onAfterRender(){this.canvas=this.element.querySelector(".chart-canvas"),this.titleElement=this.element.querySelector(".chart-title"),this.contentElement=this.element.querySelector(".chart-content"),this.footerElement=this.element.querySelector(".chart-footer"),this.loadingOverlay=this.element.querySelector("[data-loading]"),this.errorOverlay=this.element.querySelector("[data-error]"),this.noDataOverlay=this.element.querySelector("[data-no-data]"),this.websocketStatus=this.element.querySelector("[data-websocket-status]"),this.refreshBtn=this.element.querySelector(".refresh-btn"),this.themeToggle=this.element.querySelector(".theme-toggle"),this.applyTheme(),this.endpoint?(await this.fetchData(),await this.updateChart(this.data,!0),(this.height||this.width)&&this._updateChartDimensions()):this.data?(await this.updateChart(this.data,!0),(this.height||this.width)&&this._updateChartDimensions()):this.showNoData(),this.autoRefresh&&this.refreshInterval&&this.endpoint&&this.startAutoRefresh(),this.websocketUrl&&await this.connectWebSocket(),this.setupResizeObserver(),this.showFooter()}async initializeChartJS(){try{return void 0===window.Chart&&await this.loadChartJS(),!0}catch(t){return console.error("Failed to initialize Chart.js:",t),this.showError("Failed to initialize charting library"),!1}}async loadChartJS(){return new Promise((t,e)=>{const s=document.createElement("script");s.src=this.chartJsCdn,s.onload=()=>{t()},s.onerror=()=>{e(new Error("Failed to load Chart.js"))},document.head.appendChild(s)})}async handleActionRefreshChart(){await this.fetchData()}async handleActionRetryLoad(){this.hideError(),await this.fetchData()}async handleActionExportChart(t,e){const s=e.getAttribute("data-format")||"png";this.exportChart(s)}async handleActionToggleTheme(){this.toggleTheme()}async handleActionSetChartType(t,e){const s=e.getAttribute("data-type");s&&this.setChartType&&await this.setChartType(s)}async fetchData(){if(this.endpoint){this.showLoading(),this.setRefreshButtonState(!0);try{const t=await fetch(this.endpoint,{method:"GET",headers:{"Content-Type":"application/json",Accept:"application/json"}});if(!t.ok)throw new Error(`HTTP ${t.status}: ${t.statusText}`);let e=await t.json();this.dataTransform&&"function"==typeof this.dataTransform&&(e=this.dataTransform(e)),this.lastFetch=/* @__PURE__ */new Date,this.data=e,this.updateLastUpdatedTime();const s=this.getApp()?.events;s&&s.emit("chart:data-loaded",{chart:this,data:e,source:"http",endpoint:this.endpoint})}catch(t){console.error("Failed to fetch chart data:",t),this.showError(`Failed to load data: ${t.message}`),this.emit("chart:error",{chart:this,error:t,source:"http",endpoint:this.endpoint})}finally{this.hideLoading(),this.setRefreshButtonState(!1)}}}async connectWebSocket(){if(this.websocketUrl)try{this.websocket=new s.WebSocketClient({url:this.websocketUrl,autoReconnect:this.websocketReconnect,dataTransform:this.dataTransform,eventBus:this.getApp()?.events,debug:!1}),this.websocket.on("connected",()=>{this.showWebSocketStatus(!0)}),this.websocket.on("disconnected",()=>{this.showWebSocketStatus(!1)}),this.websocket.on("data",async t=>{await this.updateChart(t),this.updateLastUpdatedTime(),this.emit("chart:data-updated",{chart:this,data:t,source:"websocket"})}),this.websocket.on("error",t=>{console.error("WebSocket error:",t),this.showWebSocketStatus(!1,"error")}),await this.websocket.connect()}catch(t){console.error("Failed to connect WebSocket:",t),this.showWebSocketStatus(!1,"error")}}async updateChart(t,e=!1){if(!t)return void this.showNoData();if(this.data=t,!this.canvas||void 0===window.Chart)return;this.hideAllOverlays();const s=this.processChartData(t);e&&this.chart&&(this.chart.destroy(),this.chart=null),this.chart?(this.chart.data=s,this.chart.update("none")):await this.createChart(s),this.updateDataStats(s),(this.height||this.width)&&this._updateChartDimensions()}processChartData(t){let e={...t};const s=this.normalizeAxis(this.xAxis);return s&&s.formatter&&e.labels&&(e.labels=e.labels.map(t=>this.dataFormatter.pipe(t,s.formatter))),e}async createChart(t){if(!this.canvas||void 0===window.Chart)throw new Error("Chart.js not loaded or canvas not found");const e={type:this.chartType,data:t,options:this.buildChartOptions()};try{this.chart=new window.Chart(this.canvas,e),this.setupChartEventHandlers()}catch(s){throw console.error("Failed to create chart:",s),s}}buildChartOptions(){const t={...this.chartOptions};(this.width||this.height)&&(t.responsive=!0,t.maintainAspectRatio=!1);const e=this.normalizeAxis(this.xAxis),s=this.normalizeAxis(this.yAxis);return t.scales=t.scales||{},t.scales.x={type:this._detectAxisType(this.data,e,"x"),display:!0,title:{display:!!e.label,text:e.label||""},grid:{display:!0},ticks:{}},e.formatter&&(t.scales.x.ticks.callback=this._createFormatterCallback(e.formatter)),t.scales.y={type:this._detectAxisType(this.data,s,"y"),display:!0,beginAtZero:!1!==s.beginAtZero,title:{display:!!s.label,text:s.label||""},grid:{display:!0},ticks:{}},s.formatter&&(t.scales.y.ticks.callback=this._createFormatterCallback(s.formatter)),this.applyThemeToOptions(t),(this.tooltipFormatters.x||this.tooltipFormatters.y)&&(t.plugins=t.plugins||{},t.plugins.tooltip=t.plugins.tooltip||{},t.plugins.tooltip.callbacks=t.plugins.tooltip.callbacks||{},this.tooltipFormatters.x&&(t.plugins.tooltip.callbacks.title=t=>{const e=t[0]?.label;return e?this.dataFormatter.pipe(e,this.tooltipFormatters.x):e}),this.tooltipFormatters.y&&(t.plugins.tooltip.callbacks.label=t=>{const e=t.raw,s=this.dataFormatter.pipe(e,this.tooltipFormatters.y);return`${t.dataset.label}: ${s}`})),"function"==typeof this.applySubclassChartOptions&&this.applySubclassChartOptions(t),t}_createFormatterCallback(t){return t?e=>{try{return this.dataFormatter.pipe(e,t)}catch(s){return console.warn("Chart formatter error:",s),e}}:null}normalizeAxis(t){if(!t)return{};if("string"==typeof t)return{formatter:t};if("object"==typeof t){const{formatter:e,label:s,type:a,beginAtZero:i,...r}=t;return{formatter:e,label:s,type:a,beginAtZero:i,...r}}return{}}_detectAxisType(t,e,s="x"){if(e&&e.type)return e.type;if(e&&e.formatter){const t=e.formatter.toLowerCase();if(t.includes("date")||t.includes("time"))return"time"}if(t){if("x"===s&&t.labels&&t.labels.length>0){const e=t.labels[0];return"string"!=typeof e||/^\d+\.?\d*$/.test(e.trim())?e instanceof Date||"string"==typeof e&&!isNaN(Date.parse(e))?"time":"linear":"category"}if("y"===s&&t.datasets&&t.datasets.length>0){const e=t.datasets[0];if(e.data&&e.data.length>0){const t=e.data[0];return"number"!=typeof t&&isNaN(parseFloat(t))?"category":"linear"}}}return"x"===s?"category":"linear"}setupChartEventHandlers(){this.chart&&(this.chart.options.onClick=(t,e)=>{if(e.length>0){const t=e[0],s=t.datasetIndex,a=t.index,i=this.chart.data.datasets[s].data[a],r=this.chart.data.labels[a];this.emit("chart:point-clicked",{chart:this,datasetIndex:s,index:a,value:i,label:r,dataset:this.chart.data.datasets[s]})}},this.chart.options.onHover=(t,e)=>{this.canvas.style.cursor=e.length>0?"pointer":"default"})}applyTheme(){this.element.setAttribute("data-theme",this.theme),this.chart&&(this.chart.options=this.buildChartOptions(),this.chart.update("none"))}applyThemeToOptions(t){const e="dark"===this.theme;t.scales&&Object.keys(t.scales).forEach(s=>{const a=t.scales[s];a.grid=a.grid||{},a.ticks=a.ticks||{},a.grid.color=e?"rgba(255,255,255,0.1)":"rgba(0,0,0,0.1)",a.ticks.color=e?"#e9ecef":"#495057"}),t.plugins?.legend&&(t.plugins.legend.labels=t.plugins.legend.labels||{},t.plugins.legend.labels.color=e?"#e9ecef":"#495057"),t.plugins?.title&&(t.plugins.title.color=e?"#ffffff":"#212529")}toggleTheme(){this.theme="light"===this.theme?"dark":"light",this.applyTheme(),this.emit("chart:theme-changed",{chart:this,theme:this.theme})}startAutoRefresh(){this.endpoint&&this.refreshInterval&&(this.stopAutoRefresh(),this.refreshTimer=setInterval(()=>{this.fetchData()},this.refreshInterval),this.updateRefreshStatus(!0))}stopAutoRefresh(){this.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null),this.updateRefreshStatus(!1)}exportChart(t="png"){if(this.chart)try{if("csv"===t)this.exportCSV();else{const e=this.chart.toBase64Image("image/"+t,1),s=document.createElement("a");s.download=`chart-${Date.now()}.${t}`,s.href=e,s.click(),this.emit("chart:exported",{chart:this,format:t,filename:s.download})}}catch(e){console.error("Failed to export chart:",e),this.showError("Failed to export chart")}}exportCSV(){if(this.chart&&this.chart.data)try{const t=this.generateCSV(),e=new Blob([t],{type:"text/csv;charset=utf-8;"}),s=URL.createObjectURL(e),a=document.createElement("a");a.download=`chart-data-${Date.now()}.csv`,a.href=s,a.click(),URL.revokeObjectURL(s),this.emit("chart:exported",{chart:this,format:"csv",filename:a.download})}catch(t){console.error("Failed to export CSV:",t),this.showError("Failed to export CSV")}}generateCSV(){const t=this.chart.data,e=t.labels||[],s=t.datasets||[];let a="Label";return s.forEach(t=>{a+=","+(t.label||"Data")}),a+="\n",e.forEach((t,e)=>{a+=`"${t}"`,s.forEach(t=>{const s=t.data[e]||"";a+=","+s}),a+="\n"}),a}showLoading(){this.isLoading=!0,this.hideAllOverlays(),this.loadingOverlay?.classList.remove("d-none")}hideLoading(){this.isLoading=!1,this.loadingOverlay?.classList.add("d-none")}showError(t){this.hasError=!0,this.hideAllOverlays();const e=this.errorOverlay?.querySelector(".error-message");e&&(e.textContent=t),this.errorOverlay?.classList.remove("d-none")}hideError(){this.hasError=!1,this.errorOverlay?.classList.add("d-none")}showNoData(){this.hideAllOverlays(),this.noDataOverlay?.classList.remove("d-none")}hideAllOverlays(){this.loadingOverlay?.classList.add("d-none"),this.errorOverlay?.classList.add("d-none"),this.noDataOverlay?.classList.add("d-none")}showWebSocketStatus(t,e="connected"){this.websocketStatus&&(t?(this.websocketStatus.className="badge bg-success",this.websocketStatus.innerHTML='<i class="bi bi-wifi"></i> Live'):(this.websocketStatus.className="error"===e?"badge bg-danger":"badge bg-secondary",this.websocketStatus.innerHTML="error"===e?'<i class="bi bi-wifi-off"></i> Error':'<i class="bi bi-wifi-off"></i> Offline'),this.websocketStatus.style.display="inline-block")}setRefreshButtonState(t){if(!this.refreshBtn)return;const e=this.refreshBtn.querySelector("i");t?(this.refreshBtn.disabled=!0,e?.classList.add("spin")):(this.refreshBtn.disabled=!1,e?.classList.remove("spin"))}updateLastUpdatedTime(){const t=this.element.querySelector(".last-updated"),e=this.element.querySelector(".timestamp");t&&e&&(e.textContent=/* @__PURE__ */(new Date).toLocaleTimeString(),t.style.display="block")}updateRefreshStatus(t){const e=this.element.querySelector(".refresh-status");e&&(e.textContent=t?`Every ${this.refreshInterval/1e3}s`:"Off")}updateDataStats(t){let e=0;t.datasets&&(e=t.datasets.reduce((t,e)=>t+(e.data?e.data.length:0),0)),this.dataPoints=e;const s=this.element.querySelector(".data-points");s&&(s.textContent=`${e} data point${1!==e?"s":""}`)}showFooter(){this.footerElement&&(this.footerElement.style.display="block")}setupResizeObserver(){if(!window.ResizeObserver||!this.contentElement)return;const t=new ResizeObserver(()=>{this.chart&&this.chart.resize()});t.observe(this.contentElement),this._resizeObserver=t}async onBeforeDestroy(){this.stopAutoRefresh(),this.websocket&&(this.websocket.disconnect(),this.websocket=null),this.chart&&(this.chart.destroy(),this.chart=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._essentialListeners&&(this._essentialListeners.forEach(({el:t,type:e,fn:s})=>{t&&t.removeEventListener(e,s)}),this._essentialListeners=[]),this.emit("chart:destroyed",{chart:this})}setData(t){return this.data=t,this.updateChart(t)}setEndpoint(t){if(this.endpoint=t,t)return this.fetchData()}setWebSocketUrl(t){if(this.websocket&&this.websocket.disconnect(),this.websocketUrl=t,t)return this.connectWebSocket()}setWidth(t){this.width=t,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),this.contentElement&&this._updateChartDimensions()}setHeight(t){this.height=t,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),this.contentElement&&this._updateChartDimensions()}setDimensions(t,e){this.width=t,this.height=e,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),this.contentElement&&this._updateChartDimensions()}_updateChartDimensions(){this.chart&&(this.width||this.height?(this.chart.options.responsive=!0,this.chart.options.maintainAspectRatio=!1,this.width&&this.contentElement&&(this.contentElement.style.width=this.width?this.width+"px":""),this.height&&this.contentElement&&(this.contentElement.style.height=this.height?this.height+"px":"")):(this.chart.options.responsive=!0,this.chart.options.maintainAspectRatio=this.chartOptions.maintainAspectRatio),this.chart.resize())}resize(){this.chart&&this.chart.resize()}refresh(){return this.fetchData()}export(t="png"){return this.exportChart(t)}setTheme(t){this.theme=t,this.applyTheme()}getStats(){return{isLoading:this.isLoading,hasError:this.hasError,dataPoints:this.dataPoints,lastFetch:this.lastFetch,theme:this.theme,chartType:this.chartType,autoRefresh:!!this.refreshTimer,websocketConnected:this.websocket?.isConnected||!1}}}class ChartHeaderView extends e.View{constructor(t={}){super({...t,className:`mojo-chart-header ${t.className||""}`,tagName:"div"}),this.titleHtml=t.titleHtml||"",this.chartTitle=t.chartTitle||"",this.showExport=!0===t.showExport,this.showRefresh=!!t.showRefresh,this.showTheme=!1,this.showTheme=!0===t.showTheme,this.controls=Array.isArray(t.controls)?t.controls:[],this.controlsHtml=this._buildControlsHtml(this.controls)}async getTemplate(){return'\n <div class="d-flex justify-content-between align-items-center">\n <div class="chart-title-section">\n <h5 class="mb-2 chart-title">{{{titleHtml}}}</h5>\n <small class="text-muted last-updated" style="display: none;">\n Last updated: <span class="timestamp"></span>\n </small>\n </div>\n\n <div class="chart-controls">\n <div class="btn-toolbar" role="toolbar">\n {{{controlsHtml}}}\n\n <div class="btn-group btn-group-sm" role="group">\n\n {{#showTheme}}\n <button type="button" class="btn btn-outline-secondary theme-toggle" data-action="toggle-theme" title="Toggle Theme">\n <i class="bi bi-palette"></i>\n </button>\n {{/showTheme}}\n\n {{#showExport}}\n <div class="btn-group btn-group-sm" role="group">\n <button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" title="Export Chart">\n <i class="bi bi-download"></i>\n </button>\n <ul class="dropdown-menu">\n <li><a class="dropdown-item" href="#" data-action="export-chart" data-format="png">\n <i class="bi bi-image"></i> PNG\n </a></li>\n <li><a class="dropdown-item" href="#" data-action="export-chart" data-format="jpg">\n <i class="bi bi-image"></i> JPEG\n </a></li>\n <li><a class="dropdown-item" href="#" data-action="export-chart" data-format="csv">\n <i class="bi bi-file-earmark-spreadsheet"></i> CSV\n </a></li>\n </ul>\n </div>\n {{/showExport}}\n\n {{#showRefresh}}\n <button type="button" class="btn btn-outline-secondary refresh-btn" data-action="refresh-chart" title="Refresh Data">\n <i class="bi bi-arrow-clockwise"></i>\n </button>\n {{/showRefresh}}\n </div>\n </div>\n </div>\n </div>\n '}_buildControlsHtml(t){if(!Array.isArray(t)||0===t.length)return"";const e=[];return t.forEach(t=>{if(t&&t.type)switch(t.type){case"select":{const s=`form-select${"md"===t.size?"":" form-select-sm"} ${t.className||""}`.trim(),a=(t.options||[]).map(t=>`<option value="${this._escapeAttr(t.value)}"${t.selected?" selected":""}>${this._escapeHtml(t.label)}</option>`).join("");e.push(`\n <div class="btn-group btn-group-sm me-2" role="group">\n <select class="${s}" data-change-action="${this._escapeAttr(t.action||t.name||"select-changed")}" style="width: auto;">\n ${a}\n </select>\n </div>\n `);break}case"button":{const{variant:s="outline-secondary",size:a="sm"}=t,i=`btn btn-${s}${"md"===a?"":" btn-sm"} ${t.className||""}`.trim(),r=t.title?` title="${this._escapeAttr(t.title)}"`:"",h=this._buildDataAttrs(t.data);e.push(`\n <div class="btn-group btn-group-sm me-2" role="group">\n <button type="button" class="${i}" data-action="${this._escapeAttr(t.action||"button-action")}"${r}${h}>\n ${t.labelHtml||""}\n </button>\n </div>\n `);break}case"buttongroup":{const s=t.size||"sm",a=`btn-group btn-group-${s} me-2 ${t.className||""}`.trim(),i=(t.buttons||[]).map(t=>{const e=`btn btn-${t.variant||"outline-secondary"}${"md"===s?"":" btn-sm"} ${t.className||""}`.trim(),a=t.title?` title="${this._escapeAttr(t.title)}"`:"",i=this._buildDataAttrs(t.data);return`<button type="button" class="${e}" data-action="${this._escapeAttr(t.action||"button-action")}"${a}${i}>${t.labelHtml||""}</button>`}).join("");e.push(`\n <div class="${a}" role="group">\n ${i}\n </div>\n `);break}case"divider":e.push('<div class="vr mx-2"></div>');break;case"html":{const s=t.html||"";e.push(`<div class="me-2 d-inline-block">${s}</div>`);break}}}),e.join("\n")}_buildDataAttrs(t){return t&&"object"==typeof t?Object.entries(t).map(([t,e])=>` data-${this._kebabCase(String(t))}="${this._escapeAttr(String(e))}"`).join(""):""}_kebabCase(t){return t.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,"$1-$2").toLowerCase().replace(/[^a-z0-9\-]/g,"-").replace(/--+/g,"-").replace(/^-|-$/g,"")}_escapeAttr(t){return String(t).replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}_escapeHtml(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}}const a=["rgba(52, 152, 219, 0.85)","rgba(231, 76, 60, 0.85)","rgba(46, 204, 113, 0.85)","rgba(241, 196, 15, 0.85)","rgba(155, 89, 182, 0.85)","rgba(230, 126, 34, 0.85)","rgba(26, 188, 156, 0.85)","rgba(52, 73, 94, 0.85)","rgba(243, 156, 18, 0.85)","rgba(142, 68, 173, 0.85)","rgba(39, 174, 96, 0.85)","rgba(41, 128, 185, 0.85)","rgba(192, 57, 43, 0.85)","rgba(127, 140, 141, 0.85)","rgba(22, 160, 133, 0.85)","rgba(211, 84, 0, 0.85)","rgba(44, 62, 80, 0.85)","rgba(214, 69, 65, 0.85)","rgba(149, 165, 166, 0.85)","rgba(52, 232, 158, 0.85)"];class SeriesChart extends BaseChart{constructor(t={}){super({...t,chartType:t.chartType||"line"}),this.showTypeSwitch=!0,void 0!==t.showTypeSwitch&&(this.showTypeSwitch=t.showTypeSwitch),this.orientation=t.orientation||"vertical",this.stacked=t.stacked||!1,this.stepped=t.stepped||!1,this.tension=t.tension||.4,this.fill=t.fill||!1,this.showRefreshButton=!1!==t.showRefreshButton,this.headerConfig||(this.headerConfig={titleHtml:this.title||"",chartTitle:this.chartTitle||"",showExport:this.exportEnabled,showRefresh:this.refreshEnabled,showTheme:!0,controls:[]}),this.series=t.series||[],this.xField=t.xField||"x",this.yField=t.yField||"y";const e=Array.isArray(t.colors)&&t.colors.length?t.colors:a;this.colors=[...e],this.tooltipFormatters=t.tooltip||{}}getColor(t){return this.ensureColorPool(t+1),this.colors[t]}ensureColorPool(t){if(!(this.colors.length>=t))for(;this.colors.length<t;){const t=`hsla(${37*this.colors.length%360}, 70%, 55%, 0.85)`;this.colors.push(t)}}withAlpha(t,e=.4){if(!t)return t;const s=t.match(/rgba?\(([^)]+)\)/i);if(s){const t=s[1].split(",").map(t=>t.trim()),[a,i,r]=t;return`rgba(${a}, ${i}, ${r}, ${e})`}const a=t.match(/hsla?\(([^)]+)\)/i);if(a){const t=a[1].split(",").map(t=>t.trim()),[s,i,r]=t;return`hsla(${s}, ${i}, ${r}, ${e})`}return t}async getTemplate(){return await super.getTemplate()}async onInit(){this.showTypeSwitch&&this.headerConfig.controls.push({type:"buttongroup",size:"sm",buttons:[{action:"set-chart-type",labelHtml:'<i class="bi bi-graph-up"></i>',title:"Line",variant:"line"===this.chartType?"primary":"outline-primary",data:{type:"line"}},{action:"set-chart-type",labelHtml:'<i class="bi bi-bar-chart"></i>',title:"Bar",variant:"bar"===this.chartType?"primary":"outline-primary",data:{type:"bar"}}]}),await super.onInit()}async onActionSetChartType(t,e){t.stopPropagation();const s=e.getAttribute("data-type");s&&s!==this.chartType&&await this.setChartType(s)}async rebuildChart(){if(this.chart&&this.data){this.chart.destroy(),this.chart=null;const t=this.processChartData(this.data);await this.createChart(t)}}async setChartType(t){if(!["line","bar"].includes(t))throw new Error(`Unsupported chart type: ${t}`);const e=this.chartType;if(this.chartType=t,this.chart&&this.data){this.chart.destroy(),this.chart=null;const t=this.processChartData(this.data);await this.createChart(t)}this._updateTypeSwitcherButtons();const s=this.getApp()?.events;s&&s.emit("chart:type-changed",{chart:this,oldType:e,newType:this.chartType})}processChartData(t){if(!t)return t;let e;return e=Array.isArray(t)?this.processArrayData(t):t.labels&&t.datasets?this.processChartJSData(t):t.series?this.processSeriesData(t):t,this.applyFormattersToData(e)}processArrayData(t){const e=[],s=[];t.forEach(t=>{const a=t[this.xField],i=t[this.yField];e.push(a),s.push(i)}),this.ensureColorPool(1);const a=this.getColor(0),i="line"===this.chartType?.25:.65;return{labels:e,datasets:[{label:this.title||"Data",data:s,backgroundColor:this.withAlpha(a,i),borderColor:a,borderWidth:2,tension:"line"===this.chartType?this.tension:0,fill:"line"===this.chartType&&this.fill,stepped:"line"===this.chartType&&this.stepped}]}}processChartJSData(t){const e={...t};if(!e.datasets)return e.datasets=[],e;const s=e.datasets.length;this.ensureColorPool(s);const a="line"===this.chartType?.25:.65;return e.datasets=e.datasets.map((t,e)=>{const s=t.borderColor||this.getColor(e);return{...t,backgroundColor:t.backgroundColor||this.withAlpha(s,a),borderColor:s,borderWidth:t.borderWidth||2,tension:"line"===this.chartType?t.tension??this.tension:0,fill:"line"===this.chartType&&(t.fill??this.fill),stepped:"line"===this.chartType&&(t.stepped??this.stepped)}}),e}processSeriesData(t){const e=t.labels||[],s=[],a=t.series?.length||0;this.ensureColorPool(a);const i="line"===this.chartType?.25:.65;return t.series.forEach((t,e)=>{const a=t.borderColor||this.getColor(e);s.push({label:t.name||t.label||`Series ${e+1}`,data:t.data||[],backgroundColor:t.backgroundColor||this.withAlpha(a,i),borderColor:a,borderWidth:t.borderWidth||2,tension:"line"===this.chartType?t.tension??this.tension:0,fill:"line"===this.chartType&&(t.fill??this.fill),stepped:"line"===this.chartType&&(t.stepped??this.stepped)})}),{labels:e,datasets:s}}applyFormattersToData(t){if(!t)return t;const e={...t},s=this.normalizeAxis?this.normalizeAxis(this.xAxis):{};return s.formatter&&e.labels&&(e.labels=e.labels.map(t=>this.dataFormatter.pipe(t,s.formatter))),e}applySubclassChartOptions(t){this.stacked&&"bar"===this.chartType&&t.scales&&(t.scales.x&&(t.scales.x.stacked=!0),t.scales.y&&(t.scales.y.stacked=!0)),"bar"===this.chartType&&"horizontal"===this.orientation&&(t.indexAxis="y"),t.interaction=t.interaction||{},t.interaction.intersect=!1,t.interaction.mode="line"===this.chartType?"index":"nearest",t.elements=t.elements||{},t.elements.line={...t.elements.line||{},tension:this.tension,borderWidth:2},t.elements.point={...t.elements.point||{},radius:"line"===this.chartType?4:0,hoverRadius:6,hitRadius:8},t.elements.bar={...t.elements.bar||{},borderWidth:1,borderSkipped:!1}}processAxisConfig(t){return t?"string"==typeof t?{formatter:t}:"object"==typeof t?{formatter:t.formatter,label:t.label,type:t.type,beginAtZero:t.beginAtZero,...t}:{}:{}}_updateTypeSwitcherButtons(){const t=this.element?.querySelectorAll('[data-action="set-chart-type"]');t&&0!==t.length&&t.forEach(t=>{const e=t.getAttribute("data-type")===this.chartType;t.classList.toggle("btn-primary",e),t.classList.toggle("btn-outline-primary",!e),t.classList.toggle("active",e)})}setOrientation(t){if(!["vertical","horizontal"].includes(t))throw new Error(`Invalid orientation: ${t}`);if(this.orientation=t,this.chart&&(this.chart.destroy(),this.chart=null,this.data)){const t=this.processChartData(this.data);this.createChart(t)}}setStacked(t){this.stacked=t,this.chart&&(this.chart.options.scales.x.stacked=t,this.chart.options.scales.y.stacked=t,this.chart.update())}addSeries(t){if(!this.data||!this.data.datasets)return;const e={label:t.label||t.name||`Series ${this.data.datasets.length+1}`,data:t.data||[],backgroundColor:t.backgroundColor||this.colors[this.data.datasets.length%this.colors.length].replace("0.8","0.6"),borderColor:t.borderColor||this.colors[this.data.datasets.length%this.colors.length],borderWidth:t.borderWidth||2,tension:"line"===this.chartType?t.tension??this.tension:0,fill:"line"===this.chartType&&(t.fill??this.fill)};this.data.datasets.push(e),this.chart&&(this.chart.data.datasets.push(e),this.chart.update());const s=this.getApp()?.events;s&&s.emit("chart:series-added",{chart:this,series:e})}removeSeries(t){if(!this.data||!this.data.datasets||t<0||t>=this.data.datasets.length)return;const e=this.data.datasets.splice(t,1)[0];this.chart&&(this.chart.data.datasets.splice(t,1),this.chart.update());const s=this.getApp()?.events;s&&s.emit("chart:series-removed",{chart:this,series:e,index:t})}static async showDialog(e={}){const{title:s="Chart Viewer",size:a="xl",...i}=e,r=new SeriesChart({...i,title:s}),h=new t.Dialog({title:s,body:r,size:a,centered:!0,backdrop:"static",keyboard:!0,buttons:[{text:"Export PNG",action:"export",class:"btn btn-outline-primary"},{text:"Close",action:"close",class:"btn btn-secondary",dismiss:!0}]});return await h.render(),document.body.appendChild(h.element),await h.mount(),h.show(),new Promise(t=>{h.on("hidden",()=>{h.destroy(),t(r)}),h.on("action:export",()=>{r.exportChart("png")}),h.on("action:close",()=>{h.hide()})})}}class PieChart extends BaseChart{constructor(t={}){super({...t,chartType:"pie"}),this.cutout=t.cutout||0,this.rotation=t.rotation||0,this.circumference=t.circumference||360,this.borderWidth=t.borderWidth||2,this.borderColor=t.borderColor||"#ffffff",this.hoverBorderWidth=t.hoverBorderWidth||3,this.showLabels=!1!==t.showLabels,this.labelPosition=t.labelPosition||"outside",this.labelFormatter=t.labelFormatter||null,this.valueFormatter=t.valueFormatter||null,this.labelField=t.labelField||"label",this.valueField=t.valueField||"value",this.colors=t.colors||["#FF6384","#36A2EB","#FFCE56","#4BC0C0","#9966FF","#FF9F40","#C9CBCF","#4BC0C0","#FF6384","#36A2EB"],this.animateRotate=!1!==t.animateRotate,this.animateScale=t.animateScale||!1,this.clickable=!1!==t.clickable,this.hoverable=!1!==t.hoverable,this.selectedSegment=null,this.highlightedSegments=/* @__PURE__ */new Set,this.valueFormatter=t.valueFormatter||null}processChartData(t){if(!t)return t;let e;return e=Array.isArray(t)?this.processArrayData(t):t.labels&&t.datasets?this.processChartJSData(t):"object"!=typeof t||t.labels?t:this.processObjectData(t),this.applyFormattersToData(e)}processArrayData(t){const e=[],s=[];return t.forEach(t=>{const a=t[this.labelField],i=t[this.valueField];void 0!==a&&void 0!==i&&(e.push(a),s.push(i))}),{labels:e,datasets:[{data:s,backgroundColor:this.generateColors(e.length),borderColor:this.borderColor,borderWidth:this.borderWidth,hoverBorderWidth:this.hoverBorderWidth}]}}processChartJSData(t){const e={...t};return e.datasets=e.datasets.map(t=>({...t,backgroundColor:t.backgroundColor||this.generateColors(e.labels.length),borderColor:t.borderColor||this.borderColor,borderWidth:t.borderWidth||this.borderWidth,hoverBorderWidth:t.hoverBorderWidth||this.hoverBorderWidth})),e}processObjectData(t){const e=Object.keys(t);return{labels:e,datasets:[{data:Object.values(t),backgroundColor:this.generateColors(e.length),borderColor:this.borderColor,borderWidth:this.borderWidth,hoverBorderWidth:this.hoverBorderWidth}]}}applyFormattersToData(t){if(!t)return t;const e={...t};return this.labelFormatter&&e.labels&&(e.labels=e.labels.map(t=>this.dataFormatter.pipe(t,this.labelFormatter))),e}generateColors(t){const e=[];for(let s=0;s<t;s++)e.push(this.colors[s%this.colors.length]);return e}buildChartOptions(){const t=super.buildChartOptions();return t.cutout=this.cutout,t.rotation=this.rotation,t.circumference=this.circumference,t.animation={animateRotate:this.animateRotate,animateScale:this.animateScale,duration:this.animations?1e3:0},t.plugins={...t.plugins,legend:{...t.plugins.legend,position:t.plugins.legend.position||"right",labels:{...t.plugins.legend.labels,usePointStyle:!0,padding:20,generateLabels:t=>{const e=t.data;return e.labels.length&&e.datasets.length?e.labels.map((t,s)=>{const a=e.datasets[0],i=a.data[s],r=a.backgroundColor[s];return{text:`${t} (${(i/a.data.reduce((t,e)=>t+e,0)*100).toFixed(1)}%)`,fillStyle:r,strokeStyle:r,lineWidth:0,hidden:!1,index:s}}):[]}}},tooltip:{...t.plugins.tooltip,callbacks:{...t.plugins.tooltip.callbacks,label:t=>{const e=t.label||"",s=t.raw,a=(s/t.dataset.data.reduce((t,e)=>t+e,0)*100).toFixed(1);let i=s;return this.valueFormatter?i=this.dataFormatter.pipe(s,this.valueFormatter):this.tooltipFormatters&&this.tooltipFormatters.y&&(i=this.dataFormatter.pipe(s,this.tooltipFormatters.y)),`${e}: ${i} (${a}%)`}}}},delete t.scales,t}setupChartEventHandlers(){super.setupChartEventHandlers(),this.chart&&this.clickable&&(this.chart.options.onClick=(t,e)=>{if(e.length>0){const t=e[0].index,s=this.chart.data.datasets[0],a=this.chart.data.labels[t],i=s.data[t],r=(i/s.data.reduce((t,e)=>t+e,0)*100).toFixed(1);this.toggleSegmentSelection(t);const h=this.getApp()?.events;h&&h.emit("chart:segment-clicked",{chart:this,index:t,label:a,value:i,percentage:parseFloat(r),isSelected:this.selectedSegment===t})}},this.hoverable&&(this.chart.options.onHover=(t,e)=>{if(this.canvas.style.cursor=e.length>0?"pointer":"default",e.length>0){const t=e[0].index,s=this.getApp()?.events;s&&s.emit("chart:segment-hover",{chart:this,index:t,label:this.chart.data.labels[t],value:this.chart.data.datasets[0].data[t]})}}))}toggleSegmentSelection(t){this.selectedSegment===t?(this.selectedSegment=null,this.resetSegmentStyle(t)):(null!==this.selectedSegment&&this.resetSegmentStyle(this.selectedSegment),this.selectedSegment=t,this.highlightSegment(t))}highlightSegment(t){if(!this.chart)return;const e=this.chart.getDatasetMeta(0).data[t];e&&(e.outerRadius+=10,this.chart.update("none"))}resetSegmentStyle(t){if(!this.chart)return;const e=this.chart.getDatasetMeta(0).data[t];e&&(e.outerRadius-=10,this.chart.update("none"))}highlightSegments(t){Array.isArray(t)||(t=[t]),this.highlightedSegments.clear(),t.forEach(t=>{this.highlightedSegments.add(t),this.highlightSegment(t)})}clearHighlights(){this.highlightedSegments.forEach(t=>{this.resetSegmentStyle(t)}),this.highlightedSegments.clear(),null!==this.selectedSegment&&(this.resetSegmentStyle(this.selectedSegment),this.selectedSegment=null)}selectSegment(t){t>=0&&t<this.chart?.data?.labels?.length&&this.toggleSegmentSelection(t)}getSegmentData(t){if(!this.chart||!this.chart.data)return null;const e=this.chart.data.datasets[0],s=this.chart.data.labels[t],a=e.data[t],i=(a/e.data.reduce((t,e)=>t+e,0)*100).toFixed(1);return{index:t,label:s,value:a,percentage:parseFloat(i),color:e.backgroundColor[t],isSelected:this.selectedSegment===t}}getAllSegments(){return this.chart&&this.chart.data?this.chart.data.labels.map((t,e)=>this.getSegmentData(e)):[]}updateSegmentColor(t,e){if(!this.chart||!this.chart.data.datasets[0])return;this.chart.data.datasets[0].backgroundColor[t]=e,this.chart.update("none");const s=this.getApp()?.events;s&&s.emit("chart:segment-color-changed",{chart:this,index:t,color:e,segment:this.getSegmentData(t)})}addSegment(t,e,s=null){if(!this.chart||!this.chart.data)return;const a=this.chart.data.datasets[0],i=s||this.colors[this.chart.data.labels.length%this.colors.length];this.chart.data.labels.push(t),a.data.push(e),a.backgroundColor.push(i),this.chart.update();const r=this.getApp()?.events;r&&r.emit("chart:segment-added",{chart:this,label:t,value:e,color:i,index:this.chart.data.labels.length-1})}removeSegment(t){if(!this.chart||!this.chart.data||t<0||t>=this.chart.data.labels.length)return;const e=this.chart.data.datasets[0],s=this.chart.data.labels[t],a=e.data[t];this.chart.data.labels.splice(t,1),e.data.splice(t,1),e.backgroundColor.splice(t,1),this.selectedSegment===t?this.selectedSegment=null:this.selectedSegment>t&&this.selectedSegment--,this.chart.update();const i=this.getApp()?.events;i&&i.emit("chart:segment-removed",{chart:this,label:s,value:a,index:t,removedSegment:{label:s,value:a,index:t}})}applyThemeToOptions(t){super.applyThemeToOptions(t);const e="dark"===this.theme;this.borderColor=e?"#404449":"#ffffff"}static async showDialog(e={}){const{title:s="Pie Chart",size:a="lg",...i}=e,r=new PieChart({...i,title:s}),h=new t.Dialog({title:s,body:r,size:a,centered:!0,backdrop:"static",keyboard:!0,buttons:[{text:"Export PNG",action:"export",class:"btn btn-outline-primary"},{text:"Close",action:"close",class:"btn btn-secondary",dismiss:!0}]});return await h.render(),document.body.appendChild(h.element),await h.mount(),h.show(),new Promise(t=>{h.on("hidden",()=>{h.destroy(),t(r)}),h.on("action:export",()=>{r.exportChart("png")}),h.on("action:close",()=>{h.hide()})})}}class MiniChart extends e.View{constructor(t={}){super({className:"mini-chart",...t}),this.chartType=t.chartType||"line",this.data=t.data||[],this.width=t.width||"100%",this.height=t.height||30,this.maintainAspectRatio=t.maintainAspectRatio||!1,this.color=t.color||"rgba(54, 162, 235, 1)",this.fillColor=t.fillColor||"rgba(54, 162, 235, 0.1)",this.strokeWidth=t.strokeWidth||2,this.barGap=t.barGap||2,this.fill=!1!==t.fill,this.smoothing=t.smoothing||.3,this.padding=t.padding||2,this.minValue=t.minValue,this.maxValue=t.maxValue,this.showDots=t.showDots||!1,this.dotRadius=t.dotRadius||2,this.animate=!1!==t.animate,this.animationDuration=t.animationDuration||300,this.showTooltip=!1!==t.showTooltip,this.tooltipFormatter=t.tooltipFormatter||null,this.tooltipTemplate=t.tooltipTemplate||null,this.valueFormat=t.valueFormat||null,this.labelFormat=t.labelFormat||null,this.showCrosshair=!1!==t.showCrosshair,this.crosshairColor=t.crosshairColor||"rgba(0, 0, 0, 0.2)",this.crosshairWidth=t.crosshairWidth||1,this.showXAxis=t.showXAxis||!1,this.xAxisColor=t.xAxisColor||this.color,this.xAxisWidth=t.xAxisWidth||1,this.xAxisDashed=!1!==t.xAxisDashed,this.tooltip=null,this.crosshair=null,this.hoveredIndex=-1,this.dataFormatter=e.dataFormatter,this.labels=t.labels||null}getTemplate(){const t="number"==typeof this.width?`${this.width}px`:this.width,e="number"==typeof this.height?`${this.height}px`:this.height,s=this.maintainAspectRatio?"xMidYMid meet":"none";return`\n <div class="mini-chart-wrapper" style="position: relative; display: block; width: ${t}; height: ${e};">\n <svg\n class="mini-chart-svg"\n width="100%"\n height="100%"\n viewBox="0 0 100 ${this.height}"\n preserveAspectRatio="${s}"\n style="display: block;">\n </svg>\n ${this.showTooltip?'<div class="mini-chart-tooltip" style="display: none;"></div>':""}\n </div>\n `}async onAfterRender(){this.svg=this.element.querySelector(".mini-chart-svg"),this.tooltip=this.element.querySelector(".mini-chart-tooltip"),this.updateDimensions(),this.data&&this.data.length>0&&this.renderChart(),this.showTooltip&&this.svg&&this.setupTooltip(),this.setupResizeObserver()}updateDimensions(){if(!this.svg)return;const t=this.svg.getBoundingClientRect();this.actualWidth=t.width||100,this.actualHeight=t.height||this.height,this.svg.setAttribute("viewBox",`0 0 ${this.actualWidth} ${this.actualHeight}`)}setupResizeObserver(){"undefined"!=typeof ResizeObserver&&(this.resizeObserver=new ResizeObserver(()=>{this.updateDimensions(),this.data&&this.data.length>0&&this.renderChart()}),this.svg&&this.resizeObserver.observe(this.svg))}renderChart(){if(!this.svg||!this.data||0===this.data.length)return;this.svg.innerHTML="";const{min:t,max:e}=this.calculateBounds();if(this.showXAxis&&this.renderXAxis(t,e),"line"===this.chartType?this.renderLine(t,e):"bar"===this.chartType&&this.renderBar(t,e),this.showCrosshair){const t=this.getActualHeight();this.crosshair=this.createSVGElement("line",{x1:0,y1:0,x2:0,y2:t,stroke:this.crosshairColor,"stroke-width":this.crosshairWidth,"stroke-dasharray":"3,3",style:"display: none; pointer-events: none;"}),this.svg.appendChild(this.crosshair)}this.showTooltip&&this.tooltip&&this.setupTooltip(),this.animate&&this.applyAnimation()}renderXAxis(t,e){const s=this.getActualWidth(),a=this.getActualHeight();let i;if(t<=0&&e>=0){const s=e-t,r=(a-2*this.padding)/s;i=a-this.padding-(0-t)*r}else i=a-this.padding;const r=this.createSVGElement("line",{x1:this.padding,y1:i,x2:s-this.padding,y2:i,stroke:this.xAxisColor,"stroke-width":this.xAxisWidth,"stroke-dasharray":this.xAxisDashed?"2,2":"none","stroke-opacity":"0.5"});this.svg.appendChild(r)}calculateBounds(){const t=this.data.map(t=>"object"==typeof t?t.value:t);let e=void 0!==this.minValue?this.minValue:Math.min(...t),s=void 0!==this.maxValue?this.maxValue:Math.max(...t);return 0===s-e&&("bar"===this.chartType&&0===e?(e=0,s=1):(e-=1,s+=1)),{min:e,max:s}}getActualWidth(){return this.actualWidth||this.width||100}getActualHeight(){return this.actualHeight||this.height||30}renderLine(t,e){const s=this.data.map(t=>"object"==typeof t?t.value:t),a=this.calculatePoints(s,t,e);if(this.fill){const t=this.createAreaPath(a),e=this.createSVGElement("path",{d:t,fill:this.fillColor,stroke:"none"});this.svg.appendChild(e)}const i=this.smoothing>0?this.createSmoothPath(a):this.createLinePath(a),r=this.createSVGElement("path",{d:i,fill:"none",stroke:this.color,"stroke-width":this.strokeWidth,"stroke-linecap":"round","stroke-linejoin":"round"});this.svg.appendChild(r),this.showDots&&a.forEach(t=>{const e=this.createSVGElement("circle",{cx:t.x,cy:t.y,r:this.dotRadius,fill:this.color});this.svg.appendChild(e)})}renderBar(t,e){const s=this.data.map(t=>"object"==typeof t?t.value:t),a=this.calculatePoints(s,t,e),i=this.getActualWidth(),r=this.getActualHeight(),h=(i-2*this.padding-this.barGap*(s.length-1))/s.length;a.forEach((t,e)=>{const s=r-2*this.padding-t.y+this.padding,a=t.x-h/2,i=t.y,n=this.createSVGElement("rect",{x:a,y:i,width:h,height:s,fill:this.color,rx:1,"data-bar-index":e,class:"mini-chart-bar"});this.svg.appendChild(n)})}calculatePoints(t,e,s){const a=s-e,i=this.getActualWidth(),r=this.getActualHeight(),h=(i-2*this.padding)/(t.length-1||1),n=(r-2*this.padding)/a;return t.map((t,s)=>({x:this.padding+s*h,y:r-this.padding-(t-e)*n}))}createLinePath(t){if(0===t.length)return"";let e=`M ${t[0].x},${t[0].y}`;for(let s=1;s<t.length;s++)e+=` L ${t[s].x},${t[s].y}`;return e}createSmoothPath(t){if(t.length<2)return this.createLinePath(t);let e=`M ${t[0].x},${t[0].y}`;for(let s=0;s<t.length-1;s++){const a=t[s],i=t[s+1];e+=` C ${a.x+(i.x-a.x)*this.smoothing},${a.y} ${i.x-(i.x-a.x)*this.smoothing},${i.y} ${i.x},${i.y}`}return e}createAreaPath(t){if(0===t.length)return"";const e=this.smoothing>0?this.createSmoothPath(t):this.createLinePath(t),s=t[t.length-1],a=t[0],i=this.getActualHeight();return`${e} L ${s.x},${i-this.padding} L ${a.x},${i-this.padding} Z`}createSVGElement(t,e={}){const s=document.createElementNS("http://www.w3.org/2000/svg",t);return Object.entries(e).forEach(([t,e])=>{s.setAttribute(t,e)}),s}applyAnimation(){this.svg.querySelectorAll("path").forEach(t=>{const e=t.getTotalLength();t.style.strokeDasharray=e,t.style.strokeDashoffset=e,t.style.animation=`mini-chart-draw ${this.animationDuration}ms ease-out forwards`}),this.svg.querySelectorAll("rect").forEach((t,e)=>{t.style.transformOrigin="bottom",t.style.animation=`mini-chart-bar-grow ${this.animationDuration}ms ease-out ${20*e}ms forwards`,t.style.transform="scaleY(0)"})}setupTooltip(){if(!this.svg||!this.tooltip)return;const t=this.data.map(t=>"object"==typeof t?t.value:t),e=this.calculatePoints(t,...Object.values(this.calculateBounds())),s=this.getActualWidth(),a=this.getActualHeight(),i=s/t.length;e.forEach((t,e)=>{const s=this.createSVGElement("rect",{x:e*i,y:0,width:i,height:a,fill:"transparent",style:"cursor: pointer;"});s.addEventListener("mouseenter",t=>{this.showTooltipAtIndex(e,t)}),s.addEventListener("mousemove",t=>{this.updateTooltipPosition(t)}),s.addEventListener("mouseleave",()=>{this.hideTooltip()}),this.svg.appendChild(s)})}showTooltipAtIndex(t,e){if(!this.tooltip)return;this.hoveredIndex=t;const s="object"==typeof this.data[t]?this.data[t].value:this.data[t],a="object"==typeof this.data[t]?this.data[t].label:null,i=this.labels?this.labels[t]:a;let r;if(this.tooltipTemplate&&"function"==typeof this.tooltipTemplate)r=this.tooltipTemplate({value:s,label:i,index:t,data:this.data[t]});else{let e=s;e=this.valueFormat&&this.dataFormatter?this.dataFormatter.pipe(s,this.valueFormat):this.tooltipFormatter&&"function"==typeof this.tooltipFormatter?this.tooltipFormatter(s,t):"number"==typeof s?s.toLocaleString():s;let a=i;i&&this.labelFormat&&this.dataFormatter&&(a=this.dataFormatter.pipe(i,this.labelFormat)),r=`<strong>${e}</strong>`,a&&(r=`<div class="mini-chart-tooltip-label">${a}</div>${r}`)}if(this.tooltip.innerHTML=r,this.tooltip.style.display="block",this.updateTooltipPosition(e),"bar"===this.chartType&&this.highlightBar(t),this.crosshair&&this.showCrosshair){const e=this.getActualWidth()/this.data.length,s=t*e+e/2;this.crosshair.setAttribute("x1",s),this.crosshair.setAttribute("x2",s),this.crosshair.style.display="block"}}updateTooltipPosition(t){if(!this.tooltip||"none"===this.tooltip.style.display)return;const e=this.svg.getBoundingClientRect(),s=t.clientX-e.left,a=t.clientY-e.top;this.tooltip.style.left=`${s}px`,this.tooltip.style.top=a-10+"px",this.tooltip.style.transform="translate(-50%, -100%)"}hideTooltip(){this.tooltip&&(this.tooltip.style.display="none",this.hoveredIndex=-1),"bar"===this.chartType&&this.unhighlightBars(),this.crosshair&&(this.crosshair.style.display="none")}highlightBar(t){if(!this.svg)return;this.unhighlightBars();const e=this.svg.querySelector(`rect.mini-chart-bar[data-bar-index="${t}"]`);e&&(e.style.opacity="0.7")}unhighlightBars(){this.svg&&this.svg.querySelectorAll("rect.mini-chart-bar").forEach(t=>{t.style.opacity="1"})}setData(t){this.data=t,this.svg&&this.renderChart()}setColor(t){this.color=t,this.svg&&this.renderChart()}setType(t){["line","bar"].includes(t)&&(this.chartType=t,this.svg&&this.renderChart())}resize(t,e){this.width=t,this.height=e,this.updateDimensions(),this.svg&&this.renderChart()}async onBeforeDestroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),await super.onBeforeDestroy()}}class MetricsMiniChart extends MiniChart{constructor(t={}){super(t),this.endpoint=t.endpoint||"/api/metrics/fetch",this.account=t.account||"global",this.granularity=t.granularity||"hours",this.slugs=t.slugs||null,this.category=t.category||null,this.dateStart=t.dateStart||null,this.dateEnd=t.dateEnd||null,this.defaultDateRange=t.defaultDateRange||null,this.isLoading=!1,this.lastFetch=null,this.refreshInterval=t.refreshInterval,!this.defaultDateRange||this.dateStart||this.dateEnd||this.setQuickRange(this.defaultDateRange),this.slugs&&!Array.isArray(this.slugs)&&(this.slugs=[this.slugs])}async onAfterRender(){await super.onAfterRender(),!this.endpoint||this.data&&0!==this.data.length||this.fetchData(),this.refreshInterval&&this.endpoint&&this.startAutoRefresh()}buildApiParams(){const t={granularity:this.granularity,account:this.account,with_labels:!0};return this.slugs&&this.slugs.length>0&&this.slugs.forEach(e=>{t["slugs[]"]||(t["slugs[]"]=[]),t["slugs[]"].push(e)}),this.category&&(t.category=this.category),this.dateStart&&(t.dr_start=Math.floor(this.dateStart.getTime()/1e3)),this.dateEnd&&(t.dr_end=Math.floor(this.dateEnd.getTime()/1e3)),t._=Date.now(),t}async fetchData(){if(this.endpoint){this.isLoading=!0;try{const t=this.getApp()?.rest;if(!t)throw new Error("No REST client available");const e=this.buildApiParams(),s=await t.GET(this.endpoint,e);if(!s.success)throw new Error(s.message||"Network error");if(!s.data?.status)throw new Error(s.data?.error||"Server error");const a=s.data.data;this.processMetricsData(a),this.lastFetch=/* @__PURE__ */new Date,await this.render(),this.emit("metrics:loaded",{chart:this,data:a,params:e})}catch(t){console.error("Failed to fetch metrics:",t),this.emit("metrics:error",{chart:this,error:t})}finally{this.isLoading=!1}}}processMetricsData(t){const{data:e,labels:s}=t;if(!e)return;const a=Object.keys(e);if(0===a.length)return;const i=e[a[0]].map(t=>null==t||""===t?0:"number"==typeof t?t:parseFloat(t)||0);this.labels=s||null,this.setData(i)}setQuickRange(t){const e=/* @__PURE__ */new Date;let s;switch(t){case"1h":s=new Date(e.getTime()-36e5);break;case"24h":default:s=new Date(e.getTime()-864e5);break;case"7d":s=new Date(e.getTime()-6048e5);break;case"30d":s=new Date(e.getTime()-2592e6)}this.dateStart=s,this.dateEnd=e}startAutoRefresh(){this.refreshTimer&&clearInterval(this.refreshTimer),this.refreshTimer=setInterval(()=>{this.fetchData()},this.refreshInterval)}stopAutoRefresh(){this.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null)}setGranularity(t){return this.granularity=t,this.fetchData()}setDateRange(t,e){return this.dateStart=new Date(t),this.dateEnd=new Date(e),this.fetchData()}setMetrics(t){return this.slugs=Array.isArray(t)?t:[t],this.fetchData()}refresh(){return this.fetchData()}async onBeforeDestroy(){this.stopAutoRefresh(),await super.onBeforeDestroy()}}class SettingsView extends e.View{constructor(t={}){super({tagName:"div",className:"metrics-chart-settings-content",...t}),this.granularity=t.granularity,this.chartType=t.chartType,this.dateStart=t.dateStart,this.dateEnd=t.dateEnd,this.showDateRange=t.showDateRange}formatDateForInput(t){if(!t)return"";if("string"==typeof t&&/^\d{4}-\d{2}-\d{2}$/.test(t))return t;const e=t instanceof Date?t:new Date(t);return isNaN(e.getTime())?"":`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}getTemplate(){return`\n <div style="min-width: 220px;">\n <div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">\n <h6 class="mb-0">Chart Settings</h6>\n <button type="button" class="btn-close btn-close-sm" data-action="close" aria-label="Close"></button>\n </div>\n\n <label class="form-label small mb-1">Granularity</label>\n <select class="form-select form-select-sm mb-2" data-setting="granularity">\n <option value="hours" ${"hours"===this.granularity?"selected":""}>Hours</option>\n <option value="days" ${"days"===this.granularity?"selected":""}>Days</option>\n <option value="weeks" ${"weeks"===this.granularity?"selected":""}>Weeks</option>\n <option value="months" ${"months"===this.granularity?"selected":""}>Months</option>\n <option value="years" ${"years"===this.granularity?"selected":""}>Years</option>\n </select>\n \n <label class="form-label small mb-1">Chart Type</label>\n <select class="form-select form-select-sm mb-2" data-setting="chartType">\n <option value="line" ${"line"===this.chartType?"selected":""}>Line</option>\n <option value="bar" ${"bar"===this.chartType?"selected":""}>Bar</option>\n </select>\n \n ${this.showDateRange?`\n <label class="form-label small mb-1">Date Range</label>\n <input type="date" class="form-control form-control-sm mb-1" data-setting="dateStart" value="${this.formatDateForInput(this.dateStart)}" />\n <input type="date" class="form-control form-control-sm mb-2" data-setting="dateEnd" value="${this.formatDateForInput(this.dateEnd)}" />\n `:""}\n \n <div class="d-grid gap-2">\n <button type="button" class="btn btn-sm btn-primary" data-action="apply">Apply</button>\n <button type="button" class="btn btn-sm btn-outline-secondary" data-action="cancel">Cancel</button>\n </div>\n </div>\n `}async onActionApply(){const t=this.element.querySelector('[data-setting="granularity"]')?.value,e=this.element.querySelector('[data-setting="chartType"]')?.value,s=this.element.querySelector('[data-setting="dateStart"]')?.value,a=this.element.querySelector('[data-setting="dateEnd"]')?.value;this.emit("settings:apply",{granularity:t,chartType:e,dateStart:s,dateEnd:a})}async onActionCancel(){this.emit("settings:cancel")}async onActionClose(){this.emit("settings:cancel")}}class MetricsMiniChartWidget extends e.View{constructor(t={}){super({...t,tagName:"div",className:`metrics-mini-chart-widget ${t.className||""}`.trim()}),this.icon=t.icon||null,this.title=t.title||"",this.subtitle=t.subtitle||"",this.background=t.background||null,this.textColor=t.textColor||null,this.showSettings=t.showSettings||!1,this.settingsKey=t.settingsKey||null,this.showDateRange=t.showDateRange||!1,this.showRefresh=!1!==t.showRefresh,this.showTrending=!!t.showTrending,this.trendRange=t.trendRange??null,this.trendOffset=t.trendOffset??0,this.prevTrendOffset=t.prevTrendOffset??0,this.total=0,this.lastValue=0,this.prevValue=0,this.trendingPercent=0,this.trendingUp=null,this.hasTrending=!1,this.trendingClass="metrics-mini-chart-trending-text",this.trendingIcon="",this.trendingLabel="",this.chartOptions={endpoint:t.endpoint,account:t.account,granularity:t.granularity,slugs:t.slugs,category:t.category,dateStart:t.dateStart,dateEnd:t.dateEnd,defaultDateRange:t.defaultDateRange,refreshInterval:t.refreshInterval,chartType:t.chartType||"line",showTooltip:void 0===t.showTooltip||t.showTooltip,showXAxis:t.showXAxis||!1,height:t.height||80,width:t.chartWidth||t.width||"100%",color:t.color,fill:void 0===t.fill||t.fill,fillColor:t.fillColor,smoothing:t.smoothing??.3,strokeWidth:t.strokeWidth,barGap:t.barGap,valueFormat:t.valueFormat,labelFormat:t.labelFormat,tooltipFormatter:t.tooltipFormatter,tooltipTemplate:t.tooltipTemplate,showCrosshair:t.showCrosshair,crosshairColor:t.crosshairColor,crosshairWidth:t.crosshairWidth,xAxisColor:t.xAxisColor,xAxisWidth:t.xAxisWidth,xAxisDashed:t.xAxisDashed,padding:t.padding,minValue:t.minValue,maxValue:t.maxValue,showDots:t.showDots,dotRadius:t.dotRadius,animate:t.animate,animationDuration:t.animationDuration}}async onInit(){this.showSettings&&this.settingsKey&&this._loadSettings(),this.chart=new MetricsMiniChart({...this.chartOptions,containerId:"chart"}),this.addChild(this.chart),this.header=new e.View({containerId:"chart-header",title:this.title,icon:this.icon,template:`\n <div class="d-flex justify-content-between align-items-start mb-2">\n <div class="flex-grow-1">\n <h6 class="card-title mb-1" style="${this.textColor?`color: ${this.textColor}`:""}">${this.title}</h6>\n <div class="metrics-mini-chart-subtitle" style="${this.textColor?`color: ${this.textColor}`:""}">${this.subtitle}</div>\n {{#hasTrending}}\n <div class="{{trendingClass}}" style="${this.textColor?`color: ${this.textColor}`:""}">\n <i class="{{trendingIcon}} me-1"></i>{{trendingLabel}}\n </div>\n {{/hasTrending}}\n </div>\n ${this.icon?`<i class="${this.icon} fs-4 flex-shrink-0" aria-hidden="true" style="${this.textColor?`color: ${this.textColor}`:""}"></i>`:""}\n </div>`}),this.addChild(this.header),this.showSettings&&(this.settingsView=new SettingsView({containerId:"settings",granularity:this.chartOptions.granularity,chartType:this.chartOptions.chartType,dateStart:this.chartOptions.dateStart,dateEnd:this.chartOptions.dateEnd,showDateRange:this.showDateRange}),this.settingsView.on("settings:apply",t=>this._handleSettingsApply(t)),this.settingsView.on("settings:cancel",()=>this._handleSettingsCancel()),this.addChild(this.settingsView)),this.chart?.on&&this.chart.on("metrics:loaded",this.onChildMetricsLoaded,this),this.updateFromChartData({render:!1})}async onAfterRender(){await super.onAfterRender(),this.showSettings&&this.settingsView&&this._initSettingsPopover()}onChildMetricsLoaded(){this.updateFromChartData({render:!0})}updateFromChartData({render:t=!0}={}){const e=Array.isArray(this.chart?.data)?this.chart.data:null;if(!e||0===e.length)return this.total=0,this.hasTrending=!1,this.header.title=this.title,void(t&&this.render());const s=e.map(t=>{if("number"==typeof t)return t;if(t&&"number"==typeof t.value)return t.value;const e=parseFloat(t);return Number.isNaN(e)?0:e});this.header.title=this.title,this.header.total=s.reduce((t,e)=>t+e,0);const a=Math.max(0,parseInt(this.trendOffset||0,10)||0),i=Math.max(0,s.length-1-a);this.header.now_value=s[i],this._updateGranularityLabels();let r=!1,h=0,n=0;const o=this.trendRange&&this.trendRange>=2?Math.max(1,Math.floor(this.trendRange/2)):1;if(i>=0){const t=i,e=t-(o-1);let a,l;if(this.prevTrendOffset&&this.prevTrendOffset>0?(a=e-this.prevTrendOffset,l=t-this.prevTrendOffset):(l=e-1,a=l-(o-1)),e>=0&&a>=0){const i=(t,e,s)=>{let a=0;for(let i=e;i<=s;i++)a+=t[i]||0;return a};h=i(s,e,t),n=i(s,a,l),r=!0}}if(!r){const t=i-(this.prevTrendOffset&&this.prevTrendOffset>0?this.prevTrendOffset:1);t>=0&&(h=s[i],n=s[t],r=!0)}if(r){this.header.lastValue=h,this.header.prevValue=n;let t=0;t=0===n?h>0?100:0:(h-n)/Math.abs(n)*100,this.header.trendingPercent=t,this.header.trendingUp=t>=0,this.textColor?this.header.trendingClass="":this.header.trendingClass=this.header.trendingUp?"text-success":"text-danger",this.header.trendingIcon=this.header.trendingUp?"bi bi-arrow-up":"bi bi-arrow-down";const e=t>0?"+":"";this.header.trendingLabel=`${e}${t.toFixed(1)}%`,this.header.hasTrending=this.showTrending}else this.header.hasTrending=!1;t&&this.header.render()}_updateGranularityLabels(){const t=this.chartOptions.granularity||"days";this.header.now_label={hours:"This Hour",days:"Today",weeks:"This Week",months:"This Month",years:"This Year"}[t]||"Current",this.header.total_label={hours:"Total (24h)",days:"Total (Period)",weeks:"Total (Period)",months:"Total (Period)",years:"Total (Period)"}[t]||"Total"}get cardStyle(){const t=[];return this.background&&t.push(`background: ${this.background}`),this.textColor&&t.push(`color: ${this.textColor}`),t.push("border: 0"),t.join("; ")}async getTemplate(){return`\n <div class="card h-100 shadow-sm" style="${this.cardStyle}; position: relative;">\n ${this.showRefresh||this.showSettings?`\n <div class="metrics-chart-actions">\n ${this.showRefresh?`\n <button class="btn btn-link p-0 text-muted metrics-refresh-btn" type="button" data-action="refresh-chart" style="${this.textColor?`color: ${this.textColor} !important`:""}">\n <i class="bi bi-arrow-clockwise"></i>\n </button>\n `:""}\n ${this.showSettings?`\n <button class="btn btn-link p-0 text-muted metrics-settings-btn" type="button" data-action="toggle-settings" style="${this.textColor?`color: ${this.textColor} !important`:""}">\n <i class="bi bi-gear-fill"></i>\n </button>\n `:""}\n </div>\n `:""}\n <div class="card-body p-3">\n <div data-container="chart-header"></div>\n <div data-container="chart"></div>\n <div data-container="settings" style="display: none;"></div>\n </div>\n </div>\n `}async onBeforeDestroy(){this._settingsPopover&&(this._settingsPopover.dispose(),this._settingsPopover=null),this.chart?.off&&this.chart.off("metrics:loaded",this.onChildMetricsLoaded,this),await super.onBeforeDestroy()}async onActionToggleSettings(t,e){this._settingsPopover||this._initSettingsPopover(),this._settingsPopover?.toggle()}_initSettingsPopover(){const t=this.element.querySelector('[data-action="toggle-settings"]');t&&this.settingsView&&this.settingsView.element&&(this._settingsPopover||(this._settingsPopover=new bootstrap.Popover(t,{content:this.settingsView.element,html:!0,placement:"bottom",trigger:"manual",sanitize:!1,customClass:"metrics-chart-settings-popover"})))}async _handleSettingsApply(t){this._settingsPopover&&this._settingsPopover.hide();let e=!1,s=!1,a=!1;if((t.dateStart&&t.dateStart!==this.chartOptions.dateStart||t.dateEnd&&t.dateEnd!==this.chartOptions.dateEnd)&&(a=!0),t.granularity&&t.granularity!==this.chartOptions.granularity&&(this.chartOptions.granularity=t.granularity,this.chart.granularity=t.granularity,s=!0,e=!0),t.chartType&&t.chartType!==this.chartOptions.chartType&&(this.chartOptions.chartType=t.chartType,this.chart.chartType=t.chartType,e=!0),a)t.dateStart&&(this.chartOptions.dateStart=new Date(t.dateStart),this.chart.dateStart=new Date(t.dateStart)),t.dateEnd&&(this.chartOptions.dateEnd=new Date(t.dateEnd),this.chart.dateEnd=new Date(t.dateEnd)),e=!0;else if(s&&(this.chartOptions.dateStart||this.chartOptions.dateEnd)){const e=/* @__PURE__ */new Date;let s;switch(t.granularity){case"hours":s=new Date(e.getTime()-864e5);break;case"days":default:s=new Date(e.getTime()-2592e6);break;case"weeks":s=new Date(e.getTime()-72576e5);break;case"months":s=new Date(e),s.setMonth(s.getMonth()-12);break;case"years":s=new Date(e),s.setFullYear(s.getFullYear()-5)}this.chartOptions.dateStart=s,this.chart.dateStart=s,this.chartOptions.dateEnd=e,this.chart.dateEnd=e}e&&(this._saveSettings(),await this.chart.refresh())}_handleSettingsCancel(){this._settingsPopover&&this._settingsPopover.hide()}async onActionRefreshChart(t,e){const s=e.querySelector("i");s&&s.classList.add("spin"),this.chart&&(this.account&&(this.chart.account=this.account),await this.chart.refresh()),s&&s.classList.remove("spin")}refresh(){this.chart&&(this.account&&(this.chart.account=this.account),this.chart.refresh())}_loadSettings(){if(this.settingsKey)try{const t=localStorage.getItem(`metrics-chart-${this.settingsKey}`);if(t){const e=JSON.parse(t);e.granularity&&(this.chartOptions.granularity=e.granularity),e.chartType&&(this.chartOptions.chartType=e.chartType),void 0!==e.dateStart&&(this.chartOptions.dateStart=e.dateStart),void 0!==e.dateEnd&&(this.chartOptions.dateEnd=e.dateEnd)}}catch(t){console.error("Failed to load chart settings:",t)}}_saveSettings(){if(this.settingsKey)try{const t={granularity:this.chartOptions.granularity,chartType:this.chartOptions.chartType,dateStart:this.chartOptions.dateStart,dateEnd:this.chartOptions.dateEnd};localStorage.setItem(`metrics-chart-${this.settingsKey}`,JSON.stringify(t))}catch(t){console.error("Failed to save chart settings:",t)}}}exports.BaseChart=BaseChart,exports.MetricsChart=class extends SeriesChart{constructor(t={}){super({...t,chartType:t.chartType||"line",title:t.title||"Metrics",colors:t.colors,yAxis:t.yAxis||{label:"Count",beginAtZero:!0},tooltip:t.tooltip||{y:"number"},width:t.width,height:t.height}),this.endpoint=t.endpoint||"/api/metrics/fetch",this.account=t.account||"global",this.granularity=t.granularity||"hours",this.slugs=t.slugs||null,this.category=t.category||null,this.dateStart=t.dateStart||null,this.dateEnd=t.dateEnd||null,this.defaultDateRange=t.defaultDateRange||"24h",this.showGranularity=!1!==t.showGranularity,this.showDateRange=!1!==t.showDateRange,this.granularityOptions=t.granularityOptions||[{value:"minutes",label:"Minutes"},{value:"hours",label:"Hours"},{value:"days",label:"Days"},{value:"weeks",label:"Weeks"},{value:"months",label:"Months"}],this.quickRanges=t.quickRanges||[{value:"1h",label:"1H"},{value:"24h",label:"24H"},{value:"7d",label:"7D"},{value:"30d",label:"30D"}],this.availableMetrics=t.availableMetrics||[{value:"api_calls",label:"API Calls"},{value:"api_errors",label:"API Errors"},{value:"incident_evt",label:"System Events"},{value:"incidents",label:"Incidents"}],this.maxDatasets=Number.isFinite(t.maxDatasets)?t.maxDatasets:null,this.groupRemainingLabel=t.groupRemainingLabel||"Other",this.isLoading=!1,this.lastFetch=null,this.dateStart&&this.dateEnd||this.setQuickRange(this.defaultDateRange)}async onInit(){const t=[];this.showGranularity&&t.push({type:"select",name:"granularity",action:"granularity-changed",size:"sm",options:this.granularityOptions.map(t=>({value:t.value,label:t.label,selected:t.value===this.granularity}))}),this.showDateRange&&t.push({type:"button",action:"show-date-range-dialog",labelHtml:`<i class="bi bi-calendar-range me-1"></i>${this.formatDateRangeDisplay()}`,title:"Select Date Range",variant:"outline-secondary",size:"sm"}),this.headerConfig={titleHtml:this.title||"Metrics",chartTitle:this.chartTitle||"",showExport:!0===this.exportEnabled,showRefresh:this.refreshEnabled,showTheme:!1,controls:t},await super.onInit()}async onActionGranularityChanged(t,e){const s=e.value;s&&s!==this.granularity&&(this.granularity=s,await this.fetchData())}async onActionShowDateRangeDialog(){try{const e=await t.Dialog.showForm({title:"Select Date Range",size:"md",fields:[{name:"dateRange",type:"daterange",label:"Date Range",startName:"dt_start",endName:"dt_end",startDate:this.formatDateTimeLocal(this.dateStart),endDate:this.formatDateTimeLocal(this.dateEnd),required:!0}],formConfig:{options:{submitButton:!1,resetButton:!1}}});if(e&&e.startDate&&e.endDate){this.dateStart=new Date(e.startDate),this.dateEnd=new Date(e.endDate);const t=this.element?.querySelector('[data-action="show-date-range-dialog"]');t&&(t.innerHTML=`<i class="bi bi-calendar-range me-1"></i>${this.formatDateRangeDisplay()}`),await this.fetchData()}}catch(e){console.error("Date range dialog error:",e)}}buildApiParams(){const t={granularity:this.granularity,account:this.account,with_labels:!0};return this.slugs&&this.slugs.forEach(e=>{t["slugs[]"]||(t["slugs[]"]=[]),t["slugs[]"].push(e)}),this.category&&(t.category=this.category),this.dateStart&&(t.dr_start=Math.floor(this.dateStart.getTime()/1e3)),this.dateEnd&&(t.dr_end=Math.floor(this.dateEnd.getTime()/1e3)),t._=Date.now(),t}async fetchData(){if(this.endpoint){this.isLoading=!0,this.showLoading();try{const t=this.getApp()?.rest;if(!t)throw new Error("No REST client available");const e=this.buildApiParams(),s=await t.GET(this.endpoint,e);if(!s.success)throw new Error(s.message||"Network error");if(!s.data?.status)throw new Error(s.data?.error||"Server error");const a=s.data.data,i=this.processMetricsData(a);await this.setData(i),this.lastFetch=/* @__PURE__ */new Date,this.emit("metrics:data-loaded",{chart:this,data:a,params:e})}catch(t){console.error("Failed to fetch metrics data:",t),this.showError(`Failed to load metrics: ${t.message}`),this.emit("metrics:error",{chart:this,error:t})}finally{this.isLoading=!1,this.hideLoading()}}}processMetricsData(t){const{data:e,labels:s}=t,a=Object.entries(e||{}).map(([t,e])=>{const s=e.map(t=>null==t||""===t?0:"number"==typeof t?t:parseFloat(t)||0),a=s.reduce((t,e)=>t+e,0);return{metric:t,values:s,total:a}});a.sort((t,e)=>e.total-t.total);let i=a,r=null;if(this.maxDatasets&&this.maxDatasets>0&&a.length>this.maxDatasets){i=a.slice(0,this.maxDatasets);const t=a.slice(this.maxDatasets),e=s.map((e,s)=>t.reduce((t,e)=>t+(e.values[s]||0),0));r={metric:this.groupRemainingLabel,values:e,total:e.reduce((t,e)=>t+e,0),isGrouped:!0}}const h=[],n=r?[...i,r]:i;this.ensureColorPool(n.length);const o="line"===this.chartType?.25:.65;return n.forEach((t,e)=>{const s=this.getColor(e);h.push({label:this.formatMetricLabel(t.metric),data:t.values,backgroundColor:this.withAlpha(s,o),borderColor:s,borderWidth:2,tension:"line"===this.chartType?.4:0,fill:!1,pointRadius:"line"===this.chartType?3:0,pointHoverRadius:5})}),{labels:s,datasets:h}}formatMetricLabel(t){return t.split("_").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}setQuickRange(t){const e=/* @__PURE__ */new Date;let s;switch(t){case"1h":s=new Date(e.getTime()-36e5);break;case"24h":default:s=new Date(e.getTime()-864e5);break;case"7d":s=new Date(e.getTime()-6048e5);break;case"30d":s=new Date(e.getTime()-2592e6)}this.dateStart=s,this.dateEnd=e}formatDateTimeLocal(t){return t?`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}T${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`:""}setGranularity(t){return this.granularity=t,this.fetchData()}setDateRange(t,e){return this.dateStart=new Date(t),this.dateEnd=new Date(e),this.fetchData()}setMetrics(t){return this.slugs=[...t],this.fetchData()}getStats(){return{...super.getStats(),lastFetch:this.lastFetch,granularity:this.granularity,slugs:[...this.slugs],dateRange:{start:this.dateStart,end:this.dateEnd}}}},exports.MetricsMiniChart=MetricsMiniChart,exports.MetricsMiniChartWidget=MetricsMiniChartWidget,exports.MiniChart=MiniChart,exports.PieChart=PieChart,exports.SeriesChart=SeriesChart;
|
|
2
|
-
//# sourceMappingURL=MetricsMiniChartWidget-
|
|
1
|
+
"use strict";const t=require("./Dialog-VyLukswR.js"),e=require("./Rest-P-KCJpjB.js"),s=require("./WebSocketClient-BbPsISrp.js");class BaseChart extends e.View{constructor(t={}){super({...t,className:`chart-component ${t.className||""}`,tagName:"div"}),this.chart=null,this.chartType=t.chartType||"line",this.endpoint=t.endpoint||null,this.data=t.data||null,this.dataTransform=t.dataTransform||null,this.refreshInterval=t.refreshInterval||null,this.autoRefresh=!1!==t.autoRefresh,this.refreshTimer=null,this.websocketUrl=t.websocketUrl||null,this.websocket=null,this.websocketReconnect=!1!==t.websocketReconnect,this.width=t.width||null,this.height=t.height||null,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),void 0===t.maintainAspectRatio&&(t.maintainAspectRatio=!0),this.title=t.title||"",this.chartTitle=t.chartTitle||"",this.chartOptions={responsive:!0,maintainAspectRatio:t.maintainAspectRatio,interaction:{intersect:!1,mode:"index"},plugins:{legend:{display:!1!==t.showLegend,position:t.legendPosition||"top"},title:{display:!!this.chartTitle,text:this.chartTitle},tooltip:{enabled:!1!==t.showTooltips,backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",bodyColor:"#fff",borderColor:"rgba(255,255,255,0.1)",borderWidth:1}},...t.chartOptions},this.xAxis=t.xAxis||null,this.yAxis=t.yAxis||null,this.tooltipFormatters=t.tooltip||{},this.theme=t.theme||"light",this.colorScheme=t.colorScheme||"default",this.animations=!1!==t.animations,this.exportEnabled=!0===t.exportEnabled,this.exportFormats=t.exportFormats||["png","jpg","csv"],this.isLoading=!1,this.hasError=!1,this.lastFetch=null,this.dataPoints=0,this.canvas=null,this.chartJsCdn=t.chartJsCdn||"https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js",this.dataFormatter=e.dataFormatter,this._essentialListeners=[]}get refreshEnabled(){return!(!this.endpoint&&!this.websocketUrl)}buildDefaultHeaderConfig(){return{titleHtml:this.title||"",chartTitle:this.chartTitle||"",showExport:!0===this.exportEnabled,showRefresh:this.refreshEnabled,showTheme:!0,controls:[]}}async getTemplate(){return'\n <div class="chart-container" data-theme="{{theme}}">\n <div class="chart-header mb-3">\n <div data-container="header"></div>\n <div class="chart-header-aux mt-2">\n <div data-container="header-aux"></div>\n </div>\n </div>\n\n <div class="chart-content position-relative" {{#contentStyle}}style="{{contentStyle}}"{{/contentStyle}}>\n <canvas class="chart-canvas" data-container="canvas"></canvas>\n\n \x3c!-- Loading overlay --\x3e\n <div class="chart-overlay d-none" data-loading>\n <div class="d-flex flex-column align-items-center">\n <div class="spinner-border text-primary mb-2" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n <small class="text-muted">Loading chart data...</small>\n </div>\n </div>\n\n \x3c!-- Error overlay --\x3e\n <div class="chart-overlay d-none" data-error>\n <div class="alert alert-danger mb-0" role="alert">\n <div class="d-flex align-items-center">\n <i class="bi bi-exclamation-triangle me-2"></i>\n <div class="flex-grow-1">\n <strong>Error:</strong> <span class="error-message">Failed to load chart data</span>\n </div>\n <button class="btn btn-sm btn-outline-danger ms-2" data-action="retry-load">\n <i class="bi bi-arrow-clockwise"></i> Retry\n </button>\n </div>\n </div>\n </div>\n\n \x3c!-- No data overlay --\x3e\n <div class="chart-overlay d-none" data-no-data>\n <div class="text-center text-muted">\n <i class="bi bi-bar-chart display-4 mb-3 opacity-50"></i>\n <p class="mb-0">No data available</p>\n {{#refreshEnabled}}\n <button class="btn btn-sm btn-outline-secondary mt-2" data-action="refresh-chart">\n <i class="bi bi-arrow-clockwise"></i> Refresh\n </button>\n {{/refreshEnabled}}\n </div>\n </div>\n\n \x3c!-- WebSocket status indicator --\x3e\n <div class="position-absolute top-0 end-0 mt-2 me-2">\n <span class="badge bg-success websocket-status" style="display: none;" data-websocket-status>\n <i class="bi bi-wifi"></i> Live\n </span>\n </div>\n </div>\n\n <div class="chart-footer mt-2" style="display: none;">\n <div class="row">\n <div class="col">\n <small class="text-muted">\n <i class="bi bi-graph-up me-1"></i>\n <span class="data-points">0 data points</span>\n </small>\n </div>\n <div class="col text-end">\n <small class="text-muted refresh-info">\n Auto-refresh: <span class="refresh-status">Off</span>\n </small>\n </div>\n </div>\n </div>\n </div>\n '}async onInit(){await this.initializeChartJS();try{const t=this.headerConfig||(this.buildDefaultHeaderConfig?this.buildDefaultHeaderConfig():null);t&&(this.headerView=new ChartHeaderView({...t,containerId:"header"}),this.addChild(this.headerView))}catch(t){}}async onAfterRender(){this.canvas=this.element.querySelector(".chart-canvas"),this.titleElement=this.element.querySelector(".chart-title"),this.contentElement=this.element.querySelector(".chart-content"),this.footerElement=this.element.querySelector(".chart-footer"),this.loadingOverlay=this.element.querySelector("[data-loading]"),this.errorOverlay=this.element.querySelector("[data-error]"),this.noDataOverlay=this.element.querySelector("[data-no-data]"),this.websocketStatus=this.element.querySelector("[data-websocket-status]"),this.refreshBtn=this.element.querySelector(".refresh-btn"),this.themeToggle=this.element.querySelector(".theme-toggle"),this.applyTheme(),this.endpoint?(await this.fetchData(),await this.updateChart(this.data,!0),(this.height||this.width)&&this._updateChartDimensions()):this.data?(await this.updateChart(this.data,!0),(this.height||this.width)&&this._updateChartDimensions()):this.showNoData(),this.autoRefresh&&this.refreshInterval&&this.endpoint&&this.startAutoRefresh(),this.websocketUrl&&await this.connectWebSocket(),this.setupResizeObserver(),this.showFooter()}async initializeChartJS(){try{return void 0===window.Chart&&await this.loadChartJS(),!0}catch(t){return console.error("Failed to initialize Chart.js:",t),this.showError("Failed to initialize charting library"),!1}}async loadChartJS(){return new Promise((t,e)=>{const s=document.createElement("script");s.src=this.chartJsCdn,s.onload=()=>{t()},s.onerror=()=>{e(new Error("Failed to load Chart.js"))},document.head.appendChild(s)})}async handleActionRefreshChart(){await this.fetchData()}async handleActionRetryLoad(){this.hideError(),await this.fetchData()}async handleActionExportChart(t,e){const s=e.getAttribute("data-format")||"png";this.exportChart(s)}async handleActionToggleTheme(){this.toggleTheme()}async handleActionSetChartType(t,e){const s=e.getAttribute("data-type");s&&this.setChartType&&await this.setChartType(s)}async fetchData(){if(this.endpoint){this.showLoading(),this.setRefreshButtonState(!0);try{const t=await fetch(this.endpoint,{method:"GET",headers:{"Content-Type":"application/json",Accept:"application/json"}});if(!t.ok)throw new Error(`HTTP ${t.status}: ${t.statusText}`);let e=await t.json();this.dataTransform&&"function"==typeof this.dataTransform&&(e=this.dataTransform(e)),this.lastFetch=/* @__PURE__ */new Date,this.data=e,this.updateLastUpdatedTime();const s=this.getApp()?.events;s&&s.emit("chart:data-loaded",{chart:this,data:e,source:"http",endpoint:this.endpoint})}catch(t){console.error("Failed to fetch chart data:",t),this.showError(`Failed to load data: ${t.message}`),this.emit("chart:error",{chart:this,error:t,source:"http",endpoint:this.endpoint})}finally{this.hideLoading(),this.setRefreshButtonState(!1)}}}async connectWebSocket(){if(this.websocketUrl)try{this.websocket=new s.WebSocketClient({url:this.websocketUrl,autoReconnect:this.websocketReconnect,dataTransform:this.dataTransform,eventBus:this.getApp()?.events,debug:!1}),this.websocket.on("connected",()=>{this.showWebSocketStatus(!0)}),this.websocket.on("disconnected",()=>{this.showWebSocketStatus(!1)}),this.websocket.on("data",async t=>{await this.updateChart(t),this.updateLastUpdatedTime(),this.emit("chart:data-updated",{chart:this,data:t,source:"websocket"})}),this.websocket.on("error",t=>{console.error("WebSocket error:",t),this.showWebSocketStatus(!1,"error")}),await this.websocket.connect()}catch(t){console.error("Failed to connect WebSocket:",t),this.showWebSocketStatus(!1,"error")}}async updateChart(t,e=!1){if(!t)return void this.showNoData();if(this.data=t,!this.canvas||void 0===window.Chart)return;this.hideAllOverlays();const s=this.processChartData(t);e&&this.chart&&(this.chart.destroy(),this.chart=null),this.chart?(this.chart.data=s,this.chart.update("none")):await this.createChart(s),this.updateDataStats(s),(this.height||this.width)&&this._updateChartDimensions()}processChartData(t){let e={...t};const s=this.normalizeAxis(this.xAxis);return s&&s.formatter&&e.labels&&(e.labels=e.labels.map(t=>this.dataFormatter.pipe(t,s.formatter))),e}async createChart(t){if(!this.canvas||void 0===window.Chart)throw new Error("Chart.js not loaded or canvas not found");const e={type:this.chartType,data:t,options:this.buildChartOptions()};try{this.chart=new window.Chart(this.canvas,e),this.setupChartEventHandlers()}catch(s){throw console.error("Failed to create chart:",s),s}}buildChartOptions(){const t={...this.chartOptions};(this.width||this.height)&&(t.responsive=!0,t.maintainAspectRatio=!1);const e=this.normalizeAxis(this.xAxis),s=this.normalizeAxis(this.yAxis);return t.scales=t.scales||{},t.scales.x={type:this._detectAxisType(this.data,e,"x"),display:!0,title:{display:!!e.label,text:e.label||""},grid:{display:!0},ticks:{}},e.formatter&&(t.scales.x.ticks.callback=this._createFormatterCallback(e.formatter)),t.scales.y={type:this._detectAxisType(this.data,s,"y"),display:!0,beginAtZero:!1!==s.beginAtZero,title:{display:!!s.label,text:s.label||""},grid:{display:!0},ticks:{}},s.formatter&&(t.scales.y.ticks.callback=this._createFormatterCallback(s.formatter)),this.applyThemeToOptions(t),(this.tooltipFormatters.x||this.tooltipFormatters.y)&&(t.plugins=t.plugins||{},t.plugins.tooltip=t.plugins.tooltip||{},t.plugins.tooltip.callbacks=t.plugins.tooltip.callbacks||{},this.tooltipFormatters.x&&(t.plugins.tooltip.callbacks.title=t=>{const e=t[0]?.label;return e?this.dataFormatter.pipe(e,this.tooltipFormatters.x):e}),this.tooltipFormatters.y&&(t.plugins.tooltip.callbacks.label=t=>{const e=t.raw,s=this.dataFormatter.pipe(e,this.tooltipFormatters.y);return`${t.dataset.label}: ${s}`})),"function"==typeof this.applySubclassChartOptions&&this.applySubclassChartOptions(t),t}_createFormatterCallback(t){return t?e=>{try{return this.dataFormatter.pipe(e,t)}catch(s){return console.warn("Chart formatter error:",s),e}}:null}normalizeAxis(t){if(!t)return{};if("string"==typeof t)return{formatter:t};if("object"==typeof t){const{formatter:e,label:s,type:a,beginAtZero:i,...r}=t;return{formatter:e,label:s,type:a,beginAtZero:i,...r}}return{}}_detectAxisType(t,e,s="x"){if(e&&e.type)return e.type;if(e&&e.formatter){const t=e.formatter.toLowerCase();if(t.includes("date")||t.includes("time"))return"time"}if(t){if("x"===s&&t.labels&&t.labels.length>0){const e=t.labels[0];return"string"!=typeof e||/^\d+\.?\d*$/.test(e.trim())?e instanceof Date||"string"==typeof e&&!isNaN(Date.parse(e))?"time":"linear":"category"}if("y"===s&&t.datasets&&t.datasets.length>0){const e=t.datasets[0];if(e.data&&e.data.length>0){const t=e.data[0];return"number"!=typeof t&&isNaN(parseFloat(t))?"category":"linear"}}}return"x"===s?"category":"linear"}setupChartEventHandlers(){this.chart&&(this.chart.options.onClick=(t,e)=>{if(e.length>0){const t=e[0],s=t.datasetIndex,a=t.index,i=this.chart.data.datasets[s].data[a],r=this.chart.data.labels[a];this.emit("chart:point-clicked",{chart:this,datasetIndex:s,index:a,value:i,label:r,dataset:this.chart.data.datasets[s]})}},this.chart.options.onHover=(t,e)=>{this.canvas.style.cursor=e.length>0?"pointer":"default"})}applyTheme(){this.element.setAttribute("data-theme",this.theme),this.chart&&(this.chart.options=this.buildChartOptions(),this.chart.update("none"))}applyThemeToOptions(t){const e="dark"===this.theme;t.scales&&Object.keys(t.scales).forEach(s=>{const a=t.scales[s];a.grid=a.grid||{},a.ticks=a.ticks||{},a.grid.color=e?"rgba(255,255,255,0.1)":"rgba(0,0,0,0.1)",a.ticks.color=e?"#e9ecef":"#495057"}),t.plugins?.legend&&(t.plugins.legend.labels=t.plugins.legend.labels||{},t.plugins.legend.labels.color=e?"#e9ecef":"#495057"),t.plugins?.title&&(t.plugins.title.color=e?"#ffffff":"#212529")}toggleTheme(){this.theme="light"===this.theme?"dark":"light",this.applyTheme(),this.emit("chart:theme-changed",{chart:this,theme:this.theme})}startAutoRefresh(){this.endpoint&&this.refreshInterval&&(this.stopAutoRefresh(),this.refreshTimer=setInterval(()=>{this.fetchData()},this.refreshInterval),this.updateRefreshStatus(!0))}stopAutoRefresh(){this.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null),this.updateRefreshStatus(!1)}exportChart(t="png"){if(this.chart)try{if("csv"===t)this.exportCSV();else{const e=this.chart.toBase64Image("image/"+t,1),s=document.createElement("a");s.download=`chart-${Date.now()}.${t}`,s.href=e,s.click(),this.emit("chart:exported",{chart:this,format:t,filename:s.download})}}catch(e){console.error("Failed to export chart:",e),this.showError("Failed to export chart")}}exportCSV(){if(this.chart&&this.chart.data)try{const t=this.generateCSV(),e=new Blob([t],{type:"text/csv;charset=utf-8;"}),s=URL.createObjectURL(e),a=document.createElement("a");a.download=`chart-data-${Date.now()}.csv`,a.href=s,a.click(),URL.revokeObjectURL(s),this.emit("chart:exported",{chart:this,format:"csv",filename:a.download})}catch(t){console.error("Failed to export CSV:",t),this.showError("Failed to export CSV")}}generateCSV(){const t=this.chart.data,e=t.labels||[],s=t.datasets||[];let a="Label";return s.forEach(t=>{a+=","+(t.label||"Data")}),a+="\n",e.forEach((t,e)=>{a+=`"${t}"`,s.forEach(t=>{const s=t.data[e]||"";a+=","+s}),a+="\n"}),a}showLoading(){this.isLoading=!0,this.hideAllOverlays(),this.loadingOverlay?.classList.remove("d-none")}hideLoading(){this.isLoading=!1,this.loadingOverlay?.classList.add("d-none")}showError(t){this.hasError=!0,this.hideAllOverlays();const e=this.errorOverlay?.querySelector(".error-message");e&&(e.textContent=t),this.errorOverlay?.classList.remove("d-none")}hideError(){this.hasError=!1,this.errorOverlay?.classList.add("d-none")}showNoData(){this.hideAllOverlays(),this.noDataOverlay?.classList.remove("d-none")}hideAllOverlays(){this.loadingOverlay?.classList.add("d-none"),this.errorOverlay?.classList.add("d-none"),this.noDataOverlay?.classList.add("d-none")}showWebSocketStatus(t,e="connected"){this.websocketStatus&&(t?(this.websocketStatus.className="badge bg-success",this.websocketStatus.innerHTML='<i class="bi bi-wifi"></i> Live'):(this.websocketStatus.className="error"===e?"badge bg-danger":"badge bg-secondary",this.websocketStatus.innerHTML="error"===e?'<i class="bi bi-wifi-off"></i> Error':'<i class="bi bi-wifi-off"></i> Offline'),this.websocketStatus.style.display="inline-block")}setRefreshButtonState(t){if(!this.refreshBtn)return;const e=this.refreshBtn.querySelector("i");t?(this.refreshBtn.disabled=!0,e?.classList.add("spin")):(this.refreshBtn.disabled=!1,e?.classList.remove("spin"))}updateLastUpdatedTime(){const t=this.element.querySelector(".last-updated"),e=this.element.querySelector(".timestamp");t&&e&&(e.textContent=/* @__PURE__ */(new Date).toLocaleTimeString(),t.style.display="block")}updateRefreshStatus(t){const e=this.element.querySelector(".refresh-status");e&&(e.textContent=t?`Every ${this.refreshInterval/1e3}s`:"Off")}updateDataStats(t){let e=0;t.datasets&&(e=t.datasets.reduce((t,e)=>t+(e.data?e.data.length:0),0)),this.dataPoints=e;const s=this.element.querySelector(".data-points");s&&(s.textContent=`${e} data point${1!==e?"s":""}`)}showFooter(){this.footerElement&&(this.footerElement.style.display="block")}setupResizeObserver(){if(!window.ResizeObserver||!this.contentElement)return;const t=new ResizeObserver(()=>{this.chart&&this.chart.resize()});t.observe(this.contentElement),this._resizeObserver=t}async onBeforeDestroy(){this.stopAutoRefresh(),this.websocket&&(this.websocket.disconnect(),this.websocket=null),this.chart&&(this.chart.destroy(),this.chart=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._essentialListeners&&(this._essentialListeners.forEach(({el:t,type:e,fn:s})=>{t&&t.removeEventListener(e,s)}),this._essentialListeners=[]),this.emit("chart:destroyed",{chart:this})}setData(t){return this.data=t,this.updateChart(t)}setEndpoint(t){if(this.endpoint=t,t)return this.fetchData()}setWebSocketUrl(t){if(this.websocket&&this.websocket.disconnect(),this.websocketUrl=t,t)return this.connectWebSocket()}setWidth(t){this.width=t,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),this.contentElement&&this._updateChartDimensions()}setHeight(t){this.height=t,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),this.contentElement&&this._updateChartDimensions()}setDimensions(t,e){this.width=t,this.height=e,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),this.contentElement&&this._updateChartDimensions()}_updateChartDimensions(){this.chart&&(this.width||this.height?(this.chart.options.responsive=!0,this.chart.options.maintainAspectRatio=!1,this.width&&this.contentElement&&(this.contentElement.style.width=this.width?this.width+"px":""),this.height&&this.contentElement&&(this.contentElement.style.height=this.height?this.height+"px":"")):(this.chart.options.responsive=!0,this.chart.options.maintainAspectRatio=this.chartOptions.maintainAspectRatio),this.chart.resize())}resize(){this.chart&&this.chart.resize()}refresh(){return this.fetchData()}export(t="png"){return this.exportChart(t)}setTheme(t){this.theme=t,this.applyTheme()}getStats(){return{isLoading:this.isLoading,hasError:this.hasError,dataPoints:this.dataPoints,lastFetch:this.lastFetch,theme:this.theme,chartType:this.chartType,autoRefresh:!!this.refreshTimer,websocketConnected:this.websocket?.isConnected||!1}}}class ChartHeaderView extends e.View{constructor(t={}){super({...t,className:`mojo-chart-header ${t.className||""}`,tagName:"div"}),this.titleHtml=t.titleHtml||"",this.chartTitle=t.chartTitle||"",this.showExport=!0===t.showExport,this.showRefresh=!!t.showRefresh,this.showTheme=!1,this.showTheme=!0===t.showTheme,this.controls=Array.isArray(t.controls)?t.controls:[],this.controlsHtml=this._buildControlsHtml(this.controls)}async getTemplate(){return'\n <div class="d-flex justify-content-between align-items-center">\n <div class="chart-title-section">\n <h5 class="mb-2 chart-title">{{{titleHtml}}}</h5>\n <small class="text-muted last-updated" style="display: none;">\n Last updated: <span class="timestamp"></span>\n </small>\n </div>\n\n <div class="chart-controls">\n <div class="btn-toolbar" role="toolbar">\n {{{controlsHtml}}}\n\n <div class="btn-group btn-group-sm" role="group">\n\n {{#showTheme}}\n <button type="button" class="btn btn-outline-secondary theme-toggle" data-action="toggle-theme" title="Toggle Theme">\n <i class="bi bi-palette"></i>\n </button>\n {{/showTheme}}\n\n {{#showExport}}\n <div class="btn-group btn-group-sm" role="group">\n <button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" title="Export Chart">\n <i class="bi bi-download"></i>\n </button>\n <ul class="dropdown-menu">\n <li><a class="dropdown-item" href="#" data-action="export-chart" data-format="png">\n <i class="bi bi-image"></i> PNG\n </a></li>\n <li><a class="dropdown-item" href="#" data-action="export-chart" data-format="jpg">\n <i class="bi bi-image"></i> JPEG\n </a></li>\n <li><a class="dropdown-item" href="#" data-action="export-chart" data-format="csv">\n <i class="bi bi-file-earmark-spreadsheet"></i> CSV\n </a></li>\n </ul>\n </div>\n {{/showExport}}\n\n {{#showRefresh}}\n <button type="button" class="btn btn-outline-secondary refresh-btn" data-action="refresh-chart" title="Refresh Data">\n <i class="bi bi-arrow-clockwise"></i>\n </button>\n {{/showRefresh}}\n </div>\n </div>\n </div>\n </div>\n '}_buildControlsHtml(t){if(!Array.isArray(t)||0===t.length)return"";const e=[];return t.forEach(t=>{if(t&&t.type)switch(t.type){case"select":{const s=`form-select${"md"===t.size?"":" form-select-sm"} ${t.className||""}`.trim(),a=(t.options||[]).map(t=>`<option value="${this._escapeAttr(t.value)}"${t.selected?" selected":""}>${this._escapeHtml(t.label)}</option>`).join("");e.push(`\n <div class="btn-group btn-group-sm me-2" role="group">\n <select class="${s}" data-change-action="${this._escapeAttr(t.action||t.name||"select-changed")}" style="width: auto;">\n ${a}\n </select>\n </div>\n `);break}case"button":{const{variant:s="outline-secondary",size:a="sm"}=t,i=`btn btn-${s}${"md"===a?"":" btn-sm"} ${t.className||""}`.trim(),r=t.title?` title="${this._escapeAttr(t.title)}"`:"",h=this._buildDataAttrs(t.data);e.push(`\n <div class="btn-group btn-group-sm me-2" role="group">\n <button type="button" class="${i}" data-action="${this._escapeAttr(t.action||"button-action")}"${r}${h}>\n ${t.labelHtml||""}\n </button>\n </div>\n `);break}case"buttongroup":{const s=t.size||"sm",a=`btn-group btn-group-${s} me-2 ${t.className||""}`.trim(),i=(t.buttons||[]).map(t=>{const e=`btn btn-${t.variant||"outline-secondary"}${"md"===s?"":" btn-sm"} ${t.className||""}`.trim(),a=t.title?` title="${this._escapeAttr(t.title)}"`:"",i=this._buildDataAttrs(t.data);return`<button type="button" class="${e}" data-action="${this._escapeAttr(t.action||"button-action")}"${a}${i}>${t.labelHtml||""}</button>`}).join("");e.push(`\n <div class="${a}" role="group">\n ${i}\n </div>\n `);break}case"divider":e.push('<div class="vr mx-2"></div>');break;case"html":{const s=t.html||"";e.push(`<div class="me-2 d-inline-block">${s}</div>`);break}}}),e.join("\n")}_buildDataAttrs(t){return t&&"object"==typeof t?Object.entries(t).map(([t,e])=>` data-${this._kebabCase(String(t))}="${this._escapeAttr(String(e))}"`).join(""):""}_kebabCase(t){return t.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,"$1-$2").toLowerCase().replace(/[^a-z0-9\-]/g,"-").replace(/--+/g,"-").replace(/^-|-$/g,"")}_escapeAttr(t){return String(t).replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}_escapeHtml(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}}const a=["rgba(52, 152, 219, 0.85)","rgba(231, 76, 60, 0.85)","rgba(46, 204, 113, 0.85)","rgba(241, 196, 15, 0.85)","rgba(155, 89, 182, 0.85)","rgba(230, 126, 34, 0.85)","rgba(26, 188, 156, 0.85)","rgba(52, 73, 94, 0.85)","rgba(243, 156, 18, 0.85)","rgba(142, 68, 173, 0.85)","rgba(39, 174, 96, 0.85)","rgba(41, 128, 185, 0.85)","rgba(192, 57, 43, 0.85)","rgba(127, 140, 141, 0.85)","rgba(22, 160, 133, 0.85)","rgba(211, 84, 0, 0.85)","rgba(44, 62, 80, 0.85)","rgba(214, 69, 65, 0.85)","rgba(149, 165, 166, 0.85)","rgba(52, 232, 158, 0.85)"];class SeriesChart extends BaseChart{constructor(t={}){super({...t,chartType:t.chartType||"line"}),this.showTypeSwitch=!0,void 0!==t.showTypeSwitch&&(this.showTypeSwitch=t.showTypeSwitch),this.orientation=t.orientation||"vertical",this.stacked=t.stacked||!1,this.stepped=t.stepped||!1,this.tension=t.tension||.4,this.fill=t.fill||!1,this.showRefreshButton=!1!==t.showRefreshButton,this.headerConfig||(this.headerConfig={titleHtml:this.title||"",chartTitle:this.chartTitle||"",showExport:this.exportEnabled,showRefresh:this.refreshEnabled,showTheme:!0,controls:[]}),this.series=t.series||[],this.xField=t.xField||"x",this.yField=t.yField||"y";const e=Array.isArray(t.colors)&&t.colors.length?t.colors:a;this.colors=[...e],this.tooltipFormatters=t.tooltip||{}}getColor(t){return this.ensureColorPool(t+1),this.colors[t]}ensureColorPool(t){if(!(this.colors.length>=t))for(;this.colors.length<t;){const t=`hsla(${37*this.colors.length%360}, 70%, 55%, 0.85)`;this.colors.push(t)}}withAlpha(t,e=.4){if(!t)return t;const s=t.match(/rgba?\(([^)]+)\)/i);if(s){const t=s[1].split(",").map(t=>t.trim()),[a,i,r]=t;return`rgba(${a}, ${i}, ${r}, ${e})`}const a=t.match(/hsla?\(([^)]+)\)/i);if(a){const t=a[1].split(",").map(t=>t.trim()),[s,i,r]=t;return`hsla(${s}, ${i}, ${r}, ${e})`}return t}async getTemplate(){return await super.getTemplate()}async onInit(){this.showTypeSwitch&&this.headerConfig.controls.push({type:"buttongroup",size:"sm",buttons:[{action:"set-chart-type",labelHtml:'<i class="bi bi-graph-up"></i>',title:"Line",variant:"line"===this.chartType?"primary":"outline-primary",data:{type:"line"}},{action:"set-chart-type",labelHtml:'<i class="bi bi-bar-chart"></i>',title:"Bar",variant:"bar"===this.chartType?"primary":"outline-primary",data:{type:"bar"}}]}),await super.onInit()}async onActionSetChartType(t,e){t.stopPropagation();const s=e.getAttribute("data-type");s&&s!==this.chartType&&await this.setChartType(s)}async rebuildChart(){if(this.chart&&this.data){this.chart.destroy(),this.chart=null;const t=this.processChartData(this.data);await this.createChart(t)}}async setChartType(t){if(!["line","bar"].includes(t))throw new Error(`Unsupported chart type: ${t}`);const e=this.chartType;if(this.chartType=t,this.chart&&this.data){this.chart.destroy(),this.chart=null;const t=this.processChartData(this.data);await this.createChart(t)}this._updateTypeSwitcherButtons();const s=this.getApp()?.events;s&&s.emit("chart:type-changed",{chart:this,oldType:e,newType:this.chartType})}processChartData(t){if(!t)return t;let e;return e=Array.isArray(t)?this.processArrayData(t):t.labels&&t.datasets?this.processChartJSData(t):t.series?this.processSeriesData(t):t,this.applyFormattersToData(e)}processArrayData(t){const e=[],s=[];t.forEach(t=>{const a=t[this.xField],i=t[this.yField];e.push(a),s.push(i)}),this.ensureColorPool(1);const a=this.getColor(0),i="line"===this.chartType?.25:.65;return{labels:e,datasets:[{label:this.title||"Data",data:s,backgroundColor:this.withAlpha(a,i),borderColor:a,borderWidth:2,tension:"line"===this.chartType?this.tension:0,fill:"line"===this.chartType&&this.fill,stepped:"line"===this.chartType&&this.stepped}]}}processChartJSData(t){const e={...t};if(!e.datasets)return e.datasets=[],e;const s=e.datasets.length;this.ensureColorPool(s);const a="line"===this.chartType?.25:.65;return e.datasets=e.datasets.map((t,e)=>{const s=t.borderColor||this.getColor(e);return{...t,backgroundColor:t.backgroundColor||this.withAlpha(s,a),borderColor:s,borderWidth:t.borderWidth||2,tension:"line"===this.chartType?t.tension??this.tension:0,fill:"line"===this.chartType&&(t.fill??this.fill),stepped:"line"===this.chartType&&(t.stepped??this.stepped)}}),e}processSeriesData(t){const e=t.labels||[],s=[],a=t.series?.length||0;this.ensureColorPool(a);const i="line"===this.chartType?.25:.65;return t.series.forEach((t,e)=>{const a=t.borderColor||this.getColor(e);s.push({label:t.name||t.label||`Series ${e+1}`,data:t.data||[],backgroundColor:t.backgroundColor||this.withAlpha(a,i),borderColor:a,borderWidth:t.borderWidth||2,tension:"line"===this.chartType?t.tension??this.tension:0,fill:"line"===this.chartType&&(t.fill??this.fill),stepped:"line"===this.chartType&&(t.stepped??this.stepped)})}),{labels:e,datasets:s}}applyFormattersToData(t){if(!t)return t;const e={...t},s=this.normalizeAxis?this.normalizeAxis(this.xAxis):{};return s.formatter&&e.labels&&(e.labels=e.labels.map(t=>this.dataFormatter.pipe(t,s.formatter))),e}applySubclassChartOptions(t){this.stacked&&"bar"===this.chartType&&t.scales&&(t.scales.x&&(t.scales.x.stacked=!0),t.scales.y&&(t.scales.y.stacked=!0)),"bar"===this.chartType&&"horizontal"===this.orientation&&(t.indexAxis="y"),t.interaction=t.interaction||{},t.interaction.intersect=!1,t.interaction.mode="line"===this.chartType?"index":"nearest",t.elements=t.elements||{},t.elements.line={...t.elements.line||{},tension:this.tension,borderWidth:2},t.elements.point={...t.elements.point||{},radius:"line"===this.chartType?4:0,hoverRadius:6,hitRadius:8},t.elements.bar={...t.elements.bar||{},borderWidth:1,borderSkipped:!1}}processAxisConfig(t){return t?"string"==typeof t?{formatter:t}:"object"==typeof t?{formatter:t.formatter,label:t.label,type:t.type,beginAtZero:t.beginAtZero,...t}:{}:{}}_updateTypeSwitcherButtons(){const t=this.element?.querySelectorAll('[data-action="set-chart-type"]');t&&0!==t.length&&t.forEach(t=>{const e=t.getAttribute("data-type")===this.chartType;t.classList.toggle("btn-primary",e),t.classList.toggle("btn-outline-primary",!e),t.classList.toggle("active",e)})}setOrientation(t){if(!["vertical","horizontal"].includes(t))throw new Error(`Invalid orientation: ${t}`);if(this.orientation=t,this.chart&&(this.chart.destroy(),this.chart=null,this.data)){const t=this.processChartData(this.data);this.createChart(t)}}setStacked(t){this.stacked=t,this.chart&&(this.chart.options.scales.x.stacked=t,this.chart.options.scales.y.stacked=t,this.chart.update())}addSeries(t){if(!this.data||!this.data.datasets)return;const e={label:t.label||t.name||`Series ${this.data.datasets.length+1}`,data:t.data||[],backgroundColor:t.backgroundColor||this.colors[this.data.datasets.length%this.colors.length].replace("0.8","0.6"),borderColor:t.borderColor||this.colors[this.data.datasets.length%this.colors.length],borderWidth:t.borderWidth||2,tension:"line"===this.chartType?t.tension??this.tension:0,fill:"line"===this.chartType&&(t.fill??this.fill)};this.data.datasets.push(e),this.chart&&(this.chart.data.datasets.push(e),this.chart.update());const s=this.getApp()?.events;s&&s.emit("chart:series-added",{chart:this,series:e})}removeSeries(t){if(!this.data||!this.data.datasets||t<0||t>=this.data.datasets.length)return;const e=this.data.datasets.splice(t,1)[0];this.chart&&(this.chart.data.datasets.splice(t,1),this.chart.update());const s=this.getApp()?.events;s&&s.emit("chart:series-removed",{chart:this,series:e,index:t})}static async showDialog(e={}){const{title:s="Chart Viewer",size:a="xl",...i}=e,r=new SeriesChart({...i,title:s}),h=new t.Dialog({title:s,body:r,size:a,centered:!0,backdrop:"static",keyboard:!0,buttons:[{text:"Export PNG",action:"export",class:"btn btn-outline-primary"},{text:"Close",action:"close",class:"btn btn-secondary",dismiss:!0}]});return await h.render(),document.body.appendChild(h.element),await h.mount(),h.show(),new Promise(t=>{h.on("hidden",()=>{h.destroy(),t(r)}),h.on("action:export",()=>{r.exportChart("png")}),h.on("action:close",()=>{h.hide()})})}}class PieChart extends BaseChart{constructor(t={}){super({...t,chartType:"pie"}),this.cutout=t.cutout||0,this.rotation=t.rotation||0,this.circumference=t.circumference||360,this.borderWidth=t.borderWidth||2,this.borderColor=t.borderColor||"#ffffff",this.hoverBorderWidth=t.hoverBorderWidth||3,this.showLabels=!1!==t.showLabels,this.labelPosition=t.labelPosition||"outside",this.labelFormatter=t.labelFormatter||null,this.valueFormatter=t.valueFormatter||null,this.labelField=t.labelField||"label",this.valueField=t.valueField||"value",this.colors=t.colors||["#FF6384","#36A2EB","#FFCE56","#4BC0C0","#9966FF","#FF9F40","#C9CBCF","#4BC0C0","#FF6384","#36A2EB"],this.animateRotate=!1!==t.animateRotate,this.animateScale=t.animateScale||!1,this.clickable=!1!==t.clickable,this.hoverable=!1!==t.hoverable,this.selectedSegment=null,this.highlightedSegments=/* @__PURE__ */new Set,this.valueFormatter=t.valueFormatter||null}processChartData(t){if(!t)return t;let e;return e=Array.isArray(t)?this.processArrayData(t):t.labels&&t.datasets?this.processChartJSData(t):"object"!=typeof t||t.labels?t:this.processObjectData(t),this.applyFormattersToData(e)}processArrayData(t){const e=[],s=[];return t.forEach(t=>{const a=t[this.labelField],i=t[this.valueField];void 0!==a&&void 0!==i&&(e.push(a),s.push(i))}),{labels:e,datasets:[{data:s,backgroundColor:this.generateColors(e.length),borderColor:this.borderColor,borderWidth:this.borderWidth,hoverBorderWidth:this.hoverBorderWidth}]}}processChartJSData(t){const e={...t};return e.datasets=e.datasets.map(t=>({...t,backgroundColor:t.backgroundColor||this.generateColors(e.labels.length),borderColor:t.borderColor||this.borderColor,borderWidth:t.borderWidth||this.borderWidth,hoverBorderWidth:t.hoverBorderWidth||this.hoverBorderWidth})),e}processObjectData(t){const e=Object.keys(t);return{labels:e,datasets:[{data:Object.values(t),backgroundColor:this.generateColors(e.length),borderColor:this.borderColor,borderWidth:this.borderWidth,hoverBorderWidth:this.hoverBorderWidth}]}}applyFormattersToData(t){if(!t)return t;const e={...t};return this.labelFormatter&&e.labels&&(e.labels=e.labels.map(t=>this.dataFormatter.pipe(t,this.labelFormatter))),e}generateColors(t){const e=[];for(let s=0;s<t;s++)e.push(this.colors[s%this.colors.length]);return e}buildChartOptions(){const t=super.buildChartOptions();return t.cutout=this.cutout,t.rotation=this.rotation,t.circumference=this.circumference,t.animation={animateRotate:this.animateRotate,animateScale:this.animateScale,duration:this.animations?1e3:0},t.plugins={...t.plugins,legend:{...t.plugins.legend,position:t.plugins.legend.position||"right",labels:{...t.plugins.legend.labels,usePointStyle:!0,padding:20,generateLabels:t=>{const e=t.data;return e.labels.length&&e.datasets.length?e.labels.map((t,s)=>{const a=e.datasets[0],i=a.data[s],r=a.backgroundColor[s];return{text:`${t} (${(i/a.data.reduce((t,e)=>t+e,0)*100).toFixed(1)}%)`,fillStyle:r,strokeStyle:r,lineWidth:0,hidden:!1,index:s}}):[]}}},tooltip:{...t.plugins.tooltip,callbacks:{...t.plugins.tooltip.callbacks,label:t=>{const e=t.label||"",s=t.raw,a=(s/t.dataset.data.reduce((t,e)=>t+e,0)*100).toFixed(1);let i=s;return this.valueFormatter?i=this.dataFormatter.pipe(s,this.valueFormatter):this.tooltipFormatters&&this.tooltipFormatters.y&&(i=this.dataFormatter.pipe(s,this.tooltipFormatters.y)),`${e}: ${i} (${a}%)`}}}},delete t.scales,t}setupChartEventHandlers(){super.setupChartEventHandlers(),this.chart&&this.clickable&&(this.chart.options.onClick=(t,e)=>{if(e.length>0){const t=e[0].index,s=this.chart.data.datasets[0],a=this.chart.data.labels[t],i=s.data[t],r=(i/s.data.reduce((t,e)=>t+e,0)*100).toFixed(1);this.toggleSegmentSelection(t);const h=this.getApp()?.events;h&&h.emit("chart:segment-clicked",{chart:this,index:t,label:a,value:i,percentage:parseFloat(r),isSelected:this.selectedSegment===t})}},this.hoverable&&(this.chart.options.onHover=(t,e)=>{if(this.canvas.style.cursor=e.length>0?"pointer":"default",e.length>0){const t=e[0].index,s=this.getApp()?.events;s&&s.emit("chart:segment-hover",{chart:this,index:t,label:this.chart.data.labels[t],value:this.chart.data.datasets[0].data[t]})}}))}toggleSegmentSelection(t){this.selectedSegment===t?(this.selectedSegment=null,this.resetSegmentStyle(t)):(null!==this.selectedSegment&&this.resetSegmentStyle(this.selectedSegment),this.selectedSegment=t,this.highlightSegment(t))}highlightSegment(t){if(!this.chart)return;const e=this.chart.getDatasetMeta(0).data[t];e&&(e.outerRadius+=10,this.chart.update("none"))}resetSegmentStyle(t){if(!this.chart)return;const e=this.chart.getDatasetMeta(0).data[t];e&&(e.outerRadius-=10,this.chart.update("none"))}highlightSegments(t){Array.isArray(t)||(t=[t]),this.highlightedSegments.clear(),t.forEach(t=>{this.highlightedSegments.add(t),this.highlightSegment(t)})}clearHighlights(){this.highlightedSegments.forEach(t=>{this.resetSegmentStyle(t)}),this.highlightedSegments.clear(),null!==this.selectedSegment&&(this.resetSegmentStyle(this.selectedSegment),this.selectedSegment=null)}selectSegment(t){t>=0&&t<this.chart?.data?.labels?.length&&this.toggleSegmentSelection(t)}getSegmentData(t){if(!this.chart||!this.chart.data)return null;const e=this.chart.data.datasets[0],s=this.chart.data.labels[t],a=e.data[t],i=(a/e.data.reduce((t,e)=>t+e,0)*100).toFixed(1);return{index:t,label:s,value:a,percentage:parseFloat(i),color:e.backgroundColor[t],isSelected:this.selectedSegment===t}}getAllSegments(){return this.chart&&this.chart.data?this.chart.data.labels.map((t,e)=>this.getSegmentData(e)):[]}updateSegmentColor(t,e){if(!this.chart||!this.chart.data.datasets[0])return;this.chart.data.datasets[0].backgroundColor[t]=e,this.chart.update("none");const s=this.getApp()?.events;s&&s.emit("chart:segment-color-changed",{chart:this,index:t,color:e,segment:this.getSegmentData(t)})}addSegment(t,e,s=null){if(!this.chart||!this.chart.data)return;const a=this.chart.data.datasets[0],i=s||this.colors[this.chart.data.labels.length%this.colors.length];this.chart.data.labels.push(t),a.data.push(e),a.backgroundColor.push(i),this.chart.update();const r=this.getApp()?.events;r&&r.emit("chart:segment-added",{chart:this,label:t,value:e,color:i,index:this.chart.data.labels.length-1})}removeSegment(t){if(!this.chart||!this.chart.data||t<0||t>=this.chart.data.labels.length)return;const e=this.chart.data.datasets[0],s=this.chart.data.labels[t],a=e.data[t];this.chart.data.labels.splice(t,1),e.data.splice(t,1),e.backgroundColor.splice(t,1),this.selectedSegment===t?this.selectedSegment=null:this.selectedSegment>t&&this.selectedSegment--,this.chart.update();const i=this.getApp()?.events;i&&i.emit("chart:segment-removed",{chart:this,label:s,value:a,index:t,removedSegment:{label:s,value:a,index:t}})}applyThemeToOptions(t){super.applyThemeToOptions(t);const e="dark"===this.theme;this.borderColor=e?"#404449":"#ffffff"}static async showDialog(e={}){const{title:s="Pie Chart",size:a="lg",...i}=e,r=new PieChart({...i,title:s}),h=new t.Dialog({title:s,body:r,size:a,centered:!0,backdrop:"static",keyboard:!0,buttons:[{text:"Export PNG",action:"export",class:"btn btn-outline-primary"},{text:"Close",action:"close",class:"btn btn-secondary",dismiss:!0}]});return await h.render(),document.body.appendChild(h.element),await h.mount(),h.show(),new Promise(t=>{h.on("hidden",()=>{h.destroy(),t(r)}),h.on("action:export",()=>{r.exportChart("png")}),h.on("action:close",()=>{h.hide()})})}}class MiniChart extends e.View{constructor(t={}){super({className:"mini-chart",...t}),this.chartType=t.chartType||"line",this.data=t.data||[],this.width=t.width||"100%",this.height=t.height||30,this.maintainAspectRatio=t.maintainAspectRatio||!1,this.color=t.color||"rgba(54, 162, 235, 1)",this.fillColor=t.fillColor||"rgba(54, 162, 235, 0.1)",this.strokeWidth=t.strokeWidth||2,this.barGap=t.barGap||2,this.fill=!1!==t.fill,this.smoothing=t.smoothing||.3,this.padding=t.padding||2,this.minValue=t.minValue,this.maxValue=t.maxValue,this.showDots=t.showDots||!1,this.dotRadius=t.dotRadius||2,this.animate=!1!==t.animate,this.animationDuration=t.animationDuration||300,this.showTooltip=!1!==t.showTooltip,this.tooltipFormatter=t.tooltipFormatter||null,this.tooltipTemplate=t.tooltipTemplate||null,this.valueFormat=t.valueFormat||null,this.labelFormat=t.labelFormat||null,this.showCrosshair=!1!==t.showCrosshair,this.crosshairColor=t.crosshairColor||"rgba(0, 0, 0, 0.2)",this.crosshairWidth=t.crosshairWidth||1,this.showXAxis=t.showXAxis||!1,this.xAxisColor=t.xAxisColor||this.color,this.xAxisWidth=t.xAxisWidth||1,this.xAxisDashed=!1!==t.xAxisDashed,this.tooltip=null,this.crosshair=null,this.hoveredIndex=-1,this.dataFormatter=e.dataFormatter,this.labels=t.labels||null}getTemplate(){const t="number"==typeof this.width?`${this.width}px`:this.width,e="number"==typeof this.height?`${this.height}px`:this.height,s=this.maintainAspectRatio?"xMidYMid meet":"none";return`\n <div class="mini-chart-wrapper" style="position: relative; display: block; width: ${t}; height: ${e};">\n <svg\n class="mini-chart-svg"\n width="100%"\n height="100%"\n viewBox="0 0 100 ${this.height}"\n preserveAspectRatio="${s}"\n style="display: block;">\n </svg>\n ${this.showTooltip?'<div class="mini-chart-tooltip" style="display: none;"></div>':""}\n </div>\n `}async onAfterRender(){this.svg=this.element.querySelector(".mini-chart-svg"),this.tooltip=this.element.querySelector(".mini-chart-tooltip"),this.updateDimensions(),this.data&&this.data.length>0&&this.renderChart(),this.showTooltip&&this.svg&&this.setupTooltip(),this.setupResizeObserver()}updateDimensions(){if(!this.svg)return;const t=this.svg.getBoundingClientRect();this.actualWidth=t.width||100,this.actualHeight=t.height||this.height,this.svg.setAttribute("viewBox",`0 0 ${this.actualWidth} ${this.actualHeight}`)}setupResizeObserver(){"undefined"!=typeof ResizeObserver&&(this.resizeObserver=new ResizeObserver(()=>{this.updateDimensions(),this.data&&this.data.length>0&&this.renderChart()}),this.svg&&this.resizeObserver.observe(this.svg))}renderChart(){if(!this.svg||!this.data||0===this.data.length)return;this.svg.innerHTML="";const{min:t,max:e}=this.calculateBounds();if(this.showXAxis&&this.renderXAxis(t,e),"line"===this.chartType?this.renderLine(t,e):"bar"===this.chartType&&this.renderBar(t,e),this.showCrosshair){const t=this.getActualHeight();this.crosshair=this.createSVGElement("line",{x1:0,y1:0,x2:0,y2:t,stroke:this.crosshairColor,"stroke-width":this.crosshairWidth,"stroke-dasharray":"3,3",style:"display: none; pointer-events: none;"}),this.svg.appendChild(this.crosshair)}this.showTooltip&&this.tooltip&&this.setupTooltip(),this.animate&&this.applyAnimation()}renderXAxis(t,e){const s=this.getActualWidth(),a=this.getActualHeight();let i;if(t<=0&&e>=0){const s=e-t,r=(a-2*this.padding)/s;i=a-this.padding-(0-t)*r}else i=a-this.padding;const r=this.createSVGElement("line",{x1:this.padding,y1:i,x2:s-this.padding,y2:i,stroke:this.xAxisColor,"stroke-width":this.xAxisWidth,"stroke-dasharray":this.xAxisDashed?"2,2":"none","stroke-opacity":"0.5"});this.svg.appendChild(r)}calculateBounds(){const t=this.data.map(t=>"object"==typeof t?t.value:t);let e=void 0!==this.minValue?this.minValue:Math.min(...t),s=void 0!==this.maxValue?this.maxValue:Math.max(...t);return 0===s-e&&("bar"===this.chartType&&0===e?(e=0,s=1):(e-=1,s+=1)),{min:e,max:s}}getActualWidth(){return this.actualWidth||this.width||100}getActualHeight(){return this.actualHeight||this.height||30}renderLine(t,e){const s=this.data.map(t=>"object"==typeof t?t.value:t),a=this.calculatePoints(s,t,e);if(this.fill){const t=this.createAreaPath(a),e=this.createSVGElement("path",{d:t,fill:this.fillColor,stroke:"none"});this.svg.appendChild(e)}const i=this.smoothing>0?this.createSmoothPath(a):this.createLinePath(a),r=this.createSVGElement("path",{d:i,fill:"none",stroke:this.color,"stroke-width":this.strokeWidth,"stroke-linecap":"round","stroke-linejoin":"round"});this.svg.appendChild(r),this.showDots&&a.forEach(t=>{const e=this.createSVGElement("circle",{cx:t.x,cy:t.y,r:this.dotRadius,fill:this.color});this.svg.appendChild(e)})}renderBar(t,e){const s=this.data.map(t=>"object"==typeof t?t.value:t),a=this.calculatePoints(s,t,e),i=this.getActualWidth(),r=this.getActualHeight(),h=(i-2*this.padding-this.barGap*(s.length-1))/s.length;a.forEach((t,e)=>{const s=r-2*this.padding-t.y+this.padding,a=t.x-h/2,i=t.y,n=this.createSVGElement("rect",{x:a,y:i,width:h,height:s,fill:this.color,rx:1,"data-bar-index":e,class:"mini-chart-bar"});this.svg.appendChild(n)})}calculatePoints(t,e,s){const a=s-e,i=this.getActualWidth(),r=this.getActualHeight(),h=(i-2*this.padding)/(t.length-1||1),n=(r-2*this.padding)/a;return t.map((t,s)=>({x:this.padding+s*h,y:r-this.padding-(t-e)*n}))}createLinePath(t){if(0===t.length)return"";let e=`M ${t[0].x},${t[0].y}`;for(let s=1;s<t.length;s++)e+=` L ${t[s].x},${t[s].y}`;return e}createSmoothPath(t){if(t.length<2)return this.createLinePath(t);let e=`M ${t[0].x},${t[0].y}`;for(let s=0;s<t.length-1;s++){const a=t[s],i=t[s+1];e+=` C ${a.x+(i.x-a.x)*this.smoothing},${a.y} ${i.x-(i.x-a.x)*this.smoothing},${i.y} ${i.x},${i.y}`}return e}createAreaPath(t){if(0===t.length)return"";const e=this.smoothing>0?this.createSmoothPath(t):this.createLinePath(t),s=t[t.length-1],a=t[0],i=this.getActualHeight();return`${e} L ${s.x},${i-this.padding} L ${a.x},${i-this.padding} Z`}createSVGElement(t,e={}){const s=document.createElementNS("http://www.w3.org/2000/svg",t);return Object.entries(e).forEach(([t,e])=>{s.setAttribute(t,e)}),s}applyAnimation(){this.svg.querySelectorAll("path").forEach(t=>{const e=t.getTotalLength();t.style.strokeDasharray=e,t.style.strokeDashoffset=e,t.style.animation=`mini-chart-draw ${this.animationDuration}ms ease-out forwards`}),this.svg.querySelectorAll("rect").forEach((t,e)=>{t.style.transformOrigin="bottom",t.style.animation=`mini-chart-bar-grow ${this.animationDuration}ms ease-out ${20*e}ms forwards`,t.style.transform="scaleY(0)"})}setupTooltip(){if(!this.svg||!this.tooltip)return;const t=this.data.map(t=>"object"==typeof t?t.value:t),e=this.calculatePoints(t,...Object.values(this.calculateBounds())),s=this.getActualWidth(),a=this.getActualHeight(),i=s/t.length;e.forEach((t,e)=>{const s=this.createSVGElement("rect",{x:e*i,y:0,width:i,height:a,fill:"transparent",style:"cursor: pointer;"});s.addEventListener("mouseenter",t=>{this.showTooltipAtIndex(e,t)}),s.addEventListener("mousemove",t=>{this.updateTooltipPosition(t)}),s.addEventListener("mouseleave",()=>{this.hideTooltip()}),this.svg.appendChild(s)})}showTooltipAtIndex(t,e){if(!this.tooltip)return;this.hoveredIndex=t;const s="object"==typeof this.data[t]?this.data[t].value:this.data[t],a="object"==typeof this.data[t]?this.data[t].label:null,i=this.labels?this.labels[t]:a;let r;if(this.tooltipTemplate&&"function"==typeof this.tooltipTemplate)r=this.tooltipTemplate({value:s,label:i,index:t,data:this.data[t]});else{let e=s;e=this.valueFormat&&this.dataFormatter?this.dataFormatter.pipe(s,this.valueFormat):this.tooltipFormatter&&"function"==typeof this.tooltipFormatter?this.tooltipFormatter(s,t):"number"==typeof s?s.toLocaleString():s;let a=i;i&&this.labelFormat&&this.dataFormatter&&(a=this.dataFormatter.pipe(i,this.labelFormat)),r=`<strong>${e}</strong>`,a&&(r=`<div class="mini-chart-tooltip-label">${a}</div>${r}`)}if(this.tooltip.innerHTML=r,this.tooltip.style.display="block",this.updateTooltipPosition(e),"bar"===this.chartType&&this.highlightBar(t),this.crosshair&&this.showCrosshair){const e=this.getActualWidth()/this.data.length,s=t*e+e/2;this.crosshair.setAttribute("x1",s),this.crosshair.setAttribute("x2",s),this.crosshair.style.display="block"}}updateTooltipPosition(t){if(!this.tooltip||"none"===this.tooltip.style.display)return;const e=this.svg.getBoundingClientRect(),s=t.clientX-e.left,a=t.clientY-e.top;this.tooltip.style.left=`${s}px`,this.tooltip.style.top=a-10+"px",this.tooltip.style.transform="translate(-50%, -100%)"}hideTooltip(){this.tooltip&&(this.tooltip.style.display="none",this.hoveredIndex=-1),"bar"===this.chartType&&this.unhighlightBars(),this.crosshair&&(this.crosshair.style.display="none")}highlightBar(t){if(!this.svg)return;this.unhighlightBars();const e=this.svg.querySelector(`rect.mini-chart-bar[data-bar-index="${t}"]`);e&&(e.style.opacity="0.7")}unhighlightBars(){this.svg&&this.svg.querySelectorAll("rect.mini-chart-bar").forEach(t=>{t.style.opacity="1"})}setData(t){this.data=t,this.svg&&this.renderChart()}setColor(t){this.color=t,this.svg&&this.renderChart()}setType(t){["line","bar"].includes(t)&&(this.chartType=t,this.svg&&this.renderChart())}resize(t,e){this.width=t,this.height=e,this.updateDimensions(),this.svg&&this.renderChart()}async onBeforeDestroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),await super.onBeforeDestroy()}}class MetricsMiniChart extends MiniChart{constructor(t={}){super(t),this.endpoint=t.endpoint||"/api/metrics/fetch",this.account=t.account||"global",this.granularity=t.granularity||"hours",this.slugs=t.slugs||null,this.category=t.category||null,this.dateStart=t.dateStart||null,this.dateEnd=t.dateEnd||null,this.defaultDateRange=t.defaultDateRange||null,this.isLoading=!1,this.lastFetch=null,this.refreshInterval=t.refreshInterval,!this.defaultDateRange||this.dateStart||this.dateEnd||this.setQuickRange(this.defaultDateRange),this.slugs&&!Array.isArray(this.slugs)&&(this.slugs=[this.slugs])}async onAfterRender(){await super.onAfterRender(),!this.endpoint||this.data&&0!==this.data.length||this.fetchData(),this.refreshInterval&&this.endpoint&&this.startAutoRefresh()}buildApiParams(){const t={granularity:this.granularity,account:this.account,with_labels:!0};return this.slugs&&this.slugs.length>0&&this.slugs.forEach(e=>{t["slugs[]"]||(t["slugs[]"]=[]),t["slugs[]"].push(e)}),this.category&&(t.category=this.category),this.dateStart&&(t.dr_start=Math.floor(this.dateStart.getTime()/1e3)),this.dateEnd&&(t.dr_end=Math.floor(this.dateEnd.getTime()/1e3)),t._=Date.now(),t}async fetchData(){if(this.endpoint){this.isLoading=!0;try{const t=this.getApp()?.rest;if(!t)throw new Error("No REST client available");const e=this.buildApiParams(),s=await t.GET(this.endpoint,e);if(!s.success)throw new Error(s.message||"Network error");if(!s.data?.status)throw new Error(s.data?.error||"Server error");const a=s.data.data;this.processMetricsData(a),this.lastFetch=/* @__PURE__ */new Date,await this.render(),this.emit("metrics:loaded",{chart:this,data:a,params:e})}catch(t){console.error("Failed to fetch metrics:",t),this.emit("metrics:error",{chart:this,error:t})}finally{this.isLoading=!1}}}processMetricsData(t){const{data:e,labels:s}=t;if(!e)return;const a=Object.keys(e);if(0===a.length)return;const i=e[a[0]].map(t=>null==t||""===t?0:"number"==typeof t?t:parseFloat(t)||0);this.labels=s||null,this.setData(i)}setQuickRange(t){const e=/* @__PURE__ */new Date;let s;switch(t){case"1h":s=new Date(e.getTime()-36e5);break;case"24h":default:s=new Date(e.getTime()-864e5);break;case"7d":s=new Date(e.getTime()-6048e5);break;case"30d":s=new Date(e.getTime()-2592e6)}this.dateStart=s,this.dateEnd=e}startAutoRefresh(){this.refreshTimer&&clearInterval(this.refreshTimer),this.refreshTimer=setInterval(()=>{this.fetchData()},this.refreshInterval)}stopAutoRefresh(){this.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null)}setGranularity(t){return this.granularity=t,this.fetchData()}setDateRange(t,e){return this.dateStart=new Date(t),this.dateEnd=new Date(e),this.fetchData()}setMetrics(t){return this.slugs=Array.isArray(t)?t:[t],this.fetchData()}refresh(){return this.fetchData()}async onBeforeDestroy(){this.stopAutoRefresh(),await super.onBeforeDestroy()}}class SettingsView extends e.View{constructor(t={}){super({tagName:"div",className:"metrics-chart-settings-content",...t}),this.granularity=t.granularity,this.chartType=t.chartType,this.dateStart=t.dateStart,this.dateEnd=t.dateEnd,this.showDateRange=t.showDateRange}formatDateForInput(t){if(!t)return"";if("string"==typeof t&&/^\d{4}-\d{2}-\d{2}$/.test(t))return t;const e=t instanceof Date?t:new Date(t);return isNaN(e.getTime())?"":`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}getTemplate(){return`\n <div style="min-width: 220px;">\n <div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">\n <h6 class="mb-0">Chart Settings</h6>\n <button type="button" class="btn-close btn-close-sm" data-action="close" aria-label="Close"></button>\n </div>\n\n <label class="form-label small mb-1">Granularity</label>\n <select class="form-select form-select-sm mb-2" data-setting="granularity">\n <option value="hours" ${"hours"===this.granularity?"selected":""}>Hours</option>\n <option value="days" ${"days"===this.granularity?"selected":""}>Days</option>\n <option value="weeks" ${"weeks"===this.granularity?"selected":""}>Weeks</option>\n <option value="months" ${"months"===this.granularity?"selected":""}>Months</option>\n <option value="years" ${"years"===this.granularity?"selected":""}>Years</option>\n </select>\n \n <label class="form-label small mb-1">Chart Type</label>\n <select class="form-select form-select-sm mb-2" data-setting="chartType">\n <option value="line" ${"line"===this.chartType?"selected":""}>Line</option>\n <option value="bar" ${"bar"===this.chartType?"selected":""}>Bar</option>\n </select>\n \n ${this.showDateRange?`\n <label class="form-label small mb-1">Date Range</label>\n <input type="date" class="form-control form-control-sm mb-1" data-setting="dateStart" value="${this.formatDateForInput(this.dateStart)}" />\n <input type="date" class="form-control form-control-sm mb-2" data-setting="dateEnd" value="${this.formatDateForInput(this.dateEnd)}" />\n `:""}\n \n <div class="d-grid gap-2">\n <button type="button" class="btn btn-sm btn-primary" data-action="apply">Apply</button>\n <button type="button" class="btn btn-sm btn-outline-secondary" data-action="cancel">Cancel</button>\n </div>\n </div>\n `}async onActionApply(){const t=this.element.querySelector('[data-setting="granularity"]')?.value,e=this.element.querySelector('[data-setting="chartType"]')?.value,s=this.element.querySelector('[data-setting="dateStart"]')?.value,a=this.element.querySelector('[data-setting="dateEnd"]')?.value;this.emit("settings:apply",{granularity:t,chartType:e,dateStart:s,dateEnd:a})}async onActionCancel(){this.emit("settings:cancel")}async onActionClose(){this.emit("settings:cancel")}}class MetricsMiniChartWidget extends e.View{constructor(t={}){super({...t,tagName:"div",className:`metrics-mini-chart-widget ${t.className||""}`.trim()}),this.icon=t.icon||null,this.title=t.title||"",this.subtitle=t.subtitle||"",this.background=t.background||null,this.textColor=t.textColor||null,this.showSettings=t.showSettings||!1,this.settingsKey=t.settingsKey||null,this.showDateRange=t.showDateRange||!1,this.showRefresh=!1!==t.showRefresh,this.showTrending=!!t.showTrending,this.trendRange=t.trendRange??null,this.trendOffset=t.trendOffset??0,this.prevTrendOffset=t.prevTrendOffset??0,this.total=0,this.lastValue=0,this.prevValue=0,this.trendingPercent=0,this.trendingUp=null,this.hasTrending=!1,this.trendingClass="metrics-mini-chart-trending-text",this.trendingIcon="",this.trendingLabel="",this.chartOptions={endpoint:t.endpoint,account:t.account,granularity:t.granularity,slugs:t.slugs,category:t.category,dateStart:t.dateStart,dateEnd:t.dateEnd,defaultDateRange:t.defaultDateRange,refreshInterval:t.refreshInterval,chartType:t.chartType||"line",showTooltip:void 0===t.showTooltip||t.showTooltip,showXAxis:t.showXAxis||!1,height:t.height||80,width:t.chartWidth||t.width||"100%",color:t.color,fill:void 0===t.fill||t.fill,fillColor:t.fillColor,smoothing:t.smoothing??.3,strokeWidth:t.strokeWidth,barGap:t.barGap,valueFormat:t.valueFormat,labelFormat:t.labelFormat,tooltipFormatter:t.tooltipFormatter,tooltipTemplate:t.tooltipTemplate,showCrosshair:t.showCrosshair,crosshairColor:t.crosshairColor,crosshairWidth:t.crosshairWidth,xAxisColor:t.xAxisColor,xAxisWidth:t.xAxisWidth,xAxisDashed:t.xAxisDashed,padding:t.padding,minValue:t.minValue,maxValue:t.maxValue,showDots:t.showDots,dotRadius:t.dotRadius,animate:t.animate,animationDuration:t.animationDuration}}async onInit(){this.showSettings&&this.settingsKey&&this._loadSettings(),this.chart=new MetricsMiniChart({...this.chartOptions,containerId:"chart"}),this.addChild(this.chart),this.header=new e.View({containerId:"chart-header",title:this.title,icon:this.icon,template:`\n <div class="d-flex justify-content-between align-items-start mb-2">\n <div class="flex-grow-1">\n <h6 class="card-title mb-1" style="${this.textColor?`color: ${this.textColor}`:""}">${this.title}</h6>\n <div class="metrics-mini-chart-subtitle" style="${this.textColor?`color: ${this.textColor}`:""}">${this.subtitle}</div>\n {{#hasTrending}}\n <div class="{{trendingClass}}" style="${this.textColor?`color: ${this.textColor}`:""}">\n <i class="{{trendingIcon}} me-1"></i>{{trendingLabel}}\n </div>\n {{/hasTrending}}\n </div>\n ${this.icon?`<i class="${this.icon} fs-4 flex-shrink-0" aria-hidden="true" style="${this.textColor?`color: ${this.textColor}`:""}"></i>`:""}\n </div>`}),this.addChild(this.header),this.showSettings&&(this.settingsView=new SettingsView({containerId:"settings",granularity:this.chartOptions.granularity,chartType:this.chartOptions.chartType,dateStart:this.chartOptions.dateStart,dateEnd:this.chartOptions.dateEnd,showDateRange:this.showDateRange}),this.settingsView.on("settings:apply",t=>this._handleSettingsApply(t)),this.settingsView.on("settings:cancel",()=>this._handleSettingsCancel()),this.addChild(this.settingsView)),this.chart?.on&&this.chart.on("metrics:loaded",this.onChildMetricsLoaded,this),this.updateFromChartData({render:!1})}async onAfterRender(){await super.onAfterRender(),this.showSettings&&this.settingsView&&this._initSettingsPopover()}onChildMetricsLoaded(){this.updateFromChartData({render:!0})}updateFromChartData({render:t=!0}={}){const e=Array.isArray(this.chart?.data)?this.chart.data:null;if(!e||0===e.length)return this.total=0,this.hasTrending=!1,this.header.title=this.title,void(t&&this.render());const s=e.map(t=>{if("number"==typeof t)return t;if(t&&"number"==typeof t.value)return t.value;const e=parseFloat(t);return Number.isNaN(e)?0:e});this.header.title=this.title,this.header.total=s.reduce((t,e)=>t+e,0);const a=Math.max(0,parseInt(this.trendOffset||0,10)||0),i=Math.max(0,s.length-1-a);this.header.now_value=s[i],this._updateGranularityLabels();let r=!1,h=0,n=0;const o=this.trendRange&&this.trendRange>=2?Math.max(1,Math.floor(this.trendRange/2)):1;if(i>=0){const t=i,e=t-(o-1);let a,l;if(this.prevTrendOffset&&this.prevTrendOffset>0?(a=e-this.prevTrendOffset,l=t-this.prevTrendOffset):(l=e-1,a=l-(o-1)),e>=0&&a>=0){const i=(t,e,s)=>{let a=0;for(let i=e;i<=s;i++)a+=t[i]||0;return a};h=i(s,e,t),n=i(s,a,l),r=!0}}if(!r){const t=i-(this.prevTrendOffset&&this.prevTrendOffset>0?this.prevTrendOffset:1);t>=0&&(h=s[i],n=s[t],r=!0)}if(r){this.header.lastValue=h,this.header.prevValue=n;let t=0;t=0===n?h>0?100:0:(h-n)/Math.abs(n)*100,this.header.trendingPercent=t,this.header.trendingUp=t>=0,this.textColor?this.header.trendingClass="":this.header.trendingClass=this.header.trendingUp?"text-success":"text-danger",this.header.trendingIcon=this.header.trendingUp?"bi bi-arrow-up":"bi bi-arrow-down";const e=t>0?"+":"";this.header.trendingLabel=`${e}${t.toFixed(1)}%`,this.header.hasTrending=this.showTrending}else this.header.hasTrending=!1;t&&this.header.render()}_updateGranularityLabels(){const t=this.chartOptions.granularity||"days";this.header.now_label={hours:"This Hour",days:"Today",weeks:"This Week",months:"This Month",years:"This Year"}[t]||"Current",this.header.total_label={hours:"Total (24h)",days:"Total (Period)",weeks:"Total (Period)",months:"Total (Period)",years:"Total (Period)"}[t]||"Total"}get cardStyle(){const t=[];return this.background&&t.push(`background: ${this.background}`),this.textColor&&t.push(`color: ${this.textColor}`),t.push("border: 0"),t.join("; ")}async getTemplate(){return`\n <div class="card h-100 shadow-sm" style="${this.cardStyle}; position: relative;">\n ${this.showRefresh||this.showSettings?`\n <div class="metrics-chart-actions">\n ${this.showRefresh?`\n <button class="btn btn-link p-0 text-muted metrics-refresh-btn" type="button" data-action="refresh-chart" style="${this.textColor?`color: ${this.textColor} !important`:""}">\n <i class="bi bi-arrow-clockwise"></i>\n </button>\n `:""}\n ${this.showSettings?`\n <button class="btn btn-link p-0 text-muted metrics-settings-btn" type="button" data-action="toggle-settings" style="${this.textColor?`color: ${this.textColor} !important`:""}">\n <i class="bi bi-gear-fill"></i>\n </button>\n `:""}\n </div>\n `:""}\n <div class="card-body p-3">\n <div data-container="chart-header"></div>\n <div data-container="chart"></div>\n <div data-container="settings" style="display: none;"></div>\n </div>\n </div>\n `}async onBeforeDestroy(){this._settingsPopover&&(this._settingsPopover.dispose(),this._settingsPopover=null),this.chart?.off&&this.chart.off("metrics:loaded",this.onChildMetricsLoaded,this),await super.onBeforeDestroy()}async onActionToggleSettings(t,e){this._settingsPopover||this._initSettingsPopover(),this._settingsPopover?.toggle()}_initSettingsPopover(){const t=this.element.querySelector('[data-action="toggle-settings"]');t&&this.settingsView&&this.settingsView.element&&(this._settingsPopover||(this._settingsPopover=new bootstrap.Popover(t,{content:this.settingsView.element,html:!0,placement:"bottom",trigger:"manual",sanitize:!1,customClass:"metrics-chart-settings-popover"})))}async _handleSettingsApply(t){this._settingsPopover&&this._settingsPopover.hide();let e=!1,s=!1,a=!1;if((t.dateStart&&t.dateStart!==this.chartOptions.dateStart||t.dateEnd&&t.dateEnd!==this.chartOptions.dateEnd)&&(a=!0),t.granularity&&t.granularity!==this.chartOptions.granularity&&(this.chartOptions.granularity=t.granularity,this.chart.granularity=t.granularity,s=!0,e=!0),t.chartType&&t.chartType!==this.chartOptions.chartType&&(this.chartOptions.chartType=t.chartType,this.chart.chartType=t.chartType,e=!0),a)t.dateStart&&(this.chartOptions.dateStart=new Date(t.dateStart),this.chart.dateStart=new Date(t.dateStart)),t.dateEnd&&(this.chartOptions.dateEnd=new Date(t.dateEnd),this.chart.dateEnd=new Date(t.dateEnd)),e=!0;else if(s&&(this.chartOptions.dateStart||this.chartOptions.dateEnd)){const e=/* @__PURE__ */new Date;let s;switch(t.granularity){case"hours":s=new Date(e.getTime()-864e5);break;case"days":default:s=new Date(e.getTime()-2592e6);break;case"weeks":s=new Date(e.getTime()-72576e5);break;case"months":s=new Date(e),s.setMonth(s.getMonth()-12);break;case"years":s=new Date(e),s.setFullYear(s.getFullYear()-5)}this.chartOptions.dateStart=s,this.chart.dateStart=s,this.chartOptions.dateEnd=e,this.chart.dateEnd=e}e&&(this._saveSettings(),await this.chart.refresh())}_handleSettingsCancel(){this._settingsPopover&&this._settingsPopover.hide()}async onActionRefreshChart(t,e){const s=e.querySelector("i");s&&s.classList.add("spin"),this.chart&&(this.account&&(this.chart.account=this.account),await this.chart.refresh()),s&&s.classList.remove("spin")}refresh(){this.chart&&(this.account&&(this.chart.account=this.account),this.chart.refresh())}_loadSettings(){if(this.settingsKey)try{const t=localStorage.getItem(`metrics-chart-${this.settingsKey}`);if(t){const e=JSON.parse(t);e.granularity&&(this.chartOptions.granularity=e.granularity),e.chartType&&(this.chartOptions.chartType=e.chartType),void 0!==e.dateStart&&(this.chartOptions.dateStart=e.dateStart),void 0!==e.dateEnd&&(this.chartOptions.dateEnd=e.dateEnd)}}catch(t){console.error("Failed to load chart settings:",t)}}_saveSettings(){if(this.settingsKey)try{const t={granularity:this.chartOptions.granularity,chartType:this.chartOptions.chartType,dateStart:this.chartOptions.dateStart,dateEnd:this.chartOptions.dateEnd};localStorage.setItem(`metrics-chart-${this.settingsKey}`,JSON.stringify(t))}catch(t){console.error("Failed to save chart settings:",t)}}}exports.BaseChart=BaseChart,exports.MetricsChart=class extends SeriesChart{constructor(t={}){super({...t,chartType:t.chartType||"line",title:t.title||"Metrics",colors:t.colors,yAxis:t.yAxis||{label:"Count",beginAtZero:!0},tooltip:t.tooltip||{y:"number"},width:t.width,height:t.height}),this.endpoint=t.endpoint||"/api/metrics/fetch",this.account=t.account||"global",this.granularity=t.granularity||"hours",this.slugs=t.slugs||null,this.category=t.category||null,this.dateStart=t.dateStart||null,this.dateEnd=t.dateEnd||null,this.defaultDateRange=t.defaultDateRange||"24h",this.showGranularity=!1!==t.showGranularity,this.showDateRange=!1!==t.showDateRange,this.granularityOptions=t.granularityOptions||[{value:"minutes",label:"Minutes"},{value:"hours",label:"Hours"},{value:"days",label:"Days"},{value:"weeks",label:"Weeks"},{value:"months",label:"Months"}],this.quickRanges=t.quickRanges||[{value:"1h",label:"1H"},{value:"24h",label:"24H"},{value:"7d",label:"7D"},{value:"30d",label:"30D"}],this.availableMetrics=t.availableMetrics||[{value:"api_calls",label:"API Calls"},{value:"api_errors",label:"API Errors"},{value:"incident_evt",label:"System Events"},{value:"incidents",label:"Incidents"}],this.maxDatasets=Number.isFinite(t.maxDatasets)?t.maxDatasets:null,this.groupRemainingLabel=t.groupRemainingLabel||"Other",this.isLoading=!1,this.lastFetch=null,this.dateStart&&this.dateEnd||this.setQuickRange(this.defaultDateRange)}async onInit(){const t=[];this.showGranularity&&t.push({type:"select",name:"granularity",action:"granularity-changed",size:"sm",options:this.granularityOptions.map(t=>({value:t.value,label:t.label,selected:t.value===this.granularity}))}),this.showDateRange&&t.push({type:"button",action:"show-date-range-dialog",labelHtml:`<i class="bi bi-calendar-range me-1"></i>${this.formatDateRangeDisplay()}`,title:"Select Date Range",variant:"outline-secondary",size:"sm"}),this.headerConfig={titleHtml:this.title||"Metrics",chartTitle:this.chartTitle||"",showExport:!0===this.exportEnabled,showRefresh:this.refreshEnabled,showTheme:!1,controls:t},await super.onInit()}async onActionGranularityChanged(t,e){const s=e.value;s&&s!==this.granularity&&(this.granularity=s,await this.fetchData())}async onActionShowDateRangeDialog(){try{const e=await t.Dialog.showForm({title:"Select Date Range",size:"md",fields:[{name:"dateRange",type:"daterange",label:"Date Range",startName:"dt_start",endName:"dt_end",startDate:this.formatDateTimeLocal(this.dateStart),endDate:this.formatDateTimeLocal(this.dateEnd),required:!0}],formConfig:{options:{submitButton:!1,resetButton:!1}}});if(e&&e.startDate&&e.endDate){this.dateStart=new Date(e.startDate),this.dateEnd=new Date(e.endDate);const t=this.element?.querySelector('[data-action="show-date-range-dialog"]');t&&(t.innerHTML=`<i class="bi bi-calendar-range me-1"></i>${this.formatDateRangeDisplay()}`),await this.fetchData()}}catch(e){console.error("Date range dialog error:",e)}}buildApiParams(){const t={granularity:this.granularity,account:this.account,with_labels:!0};return this.slugs&&this.slugs.forEach(e=>{t["slugs[]"]||(t["slugs[]"]=[]),t["slugs[]"].push(e)}),this.category&&(t.category=this.category),this.dateStart&&(t.dr_start=Math.floor(this.dateStart.getTime()/1e3)),this.dateEnd&&(t.dr_end=Math.floor(this.dateEnd.getTime()/1e3)),t._=Date.now(),t}async fetchData(){if(this.endpoint){this.isLoading=!0,this.showLoading();try{const t=this.getApp()?.rest;if(!t)throw new Error("No REST client available");const e=this.buildApiParams(),s=await t.GET(this.endpoint,e);if(!s.success)throw new Error(s.message||"Network error");if(!s.data?.status)throw new Error(s.data?.error||"Server error");const a=s.data.data,i=this.processMetricsData(a);await this.setData(i),this.lastFetch=/* @__PURE__ */new Date,this.emit("metrics:data-loaded",{chart:this,data:a,params:e})}catch(t){console.error("Failed to fetch metrics data:",t),this.showError(`Failed to load metrics: ${t.message}`),this.emit("metrics:error",{chart:this,error:t})}finally{this.isLoading=!1,this.hideLoading()}}}processMetricsData(t){const{data:e,labels:s}=t,a=Object.entries(e||{}).map(([t,e])=>{const s=e.map(t=>null==t||""===t?0:"number"==typeof t?t:parseFloat(t)||0),a=s.reduce((t,e)=>t+e,0);return{metric:t,values:s,total:a}});a.sort((t,e)=>e.total-t.total);let i=a,r=null;if(this.maxDatasets&&this.maxDatasets>0&&a.length>this.maxDatasets){i=a.slice(0,this.maxDatasets);const t=a.slice(this.maxDatasets),e=s.map((e,s)=>t.reduce((t,e)=>t+(e.values[s]||0),0));r={metric:this.groupRemainingLabel,values:e,total:e.reduce((t,e)=>t+e,0),isGrouped:!0}}const h=[],n=r?[...i,r]:i;this.ensureColorPool(n.length);const o="line"===this.chartType?.25:.65;return n.forEach((t,e)=>{const s=this.getColor(e);h.push({label:this.formatMetricLabel(t.metric),data:t.values,backgroundColor:this.withAlpha(s,o),borderColor:s,borderWidth:2,tension:"line"===this.chartType?.4:0,fill:!1,pointRadius:"line"===this.chartType?3:0,pointHoverRadius:5})}),{labels:s,datasets:h}}formatMetricLabel(t){return t.split("_").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}setQuickRange(t){const e=/* @__PURE__ */new Date;let s;switch(t){case"1h":s=new Date(e.getTime()-36e5);break;case"24h":default:s=new Date(e.getTime()-864e5);break;case"7d":s=new Date(e.getTime()-6048e5);break;case"30d":s=new Date(e.getTime()-2592e6)}this.dateStart=s,this.dateEnd=e}formatDateTimeLocal(t){return t?`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}T${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`:""}setGranularity(t){return this.granularity=t,this.fetchData()}setDateRange(t,e){return this.dateStart=new Date(t),this.dateEnd=new Date(e),this.fetchData()}setMetrics(t){return this.slugs=[...t],this.fetchData()}getStats(){return{...super.getStats(),lastFetch:this.lastFetch,granularity:this.granularity,slugs:[...this.slugs],dateRange:{start:this.dateStart,end:this.dateEnd}}}},exports.MetricsMiniChart=MetricsMiniChart,exports.MetricsMiniChartWidget=MetricsMiniChartWidget,exports.MiniChart=MiniChart,exports.PieChart=PieChart,exports.SeriesChart=SeriesChart;
|
|
2
|
+
//# sourceMappingURL=MetricsMiniChartWidget-JLmDm3sm.js.map
|