"use strict"; (self["webpackChunk"] = self["webpackChunk"] || []).push([[792],{ /***/ 170 (__unused_webpack_module, __unused_webpack___webpack_exports__, __webpack_require__) { ;// ../assets/scripts/utils/dom.js /** * Adminator DOM Utility Functions * Provides jQuery-like functionality using vanilla JavaScript * * @module utils/dom * @example * import { DOM } from './utils/dom'; * * // Select elements * const button = DOM.select('.my-button'); * const items = DOM.selectAll('.list-item'); * * // Add event listeners * DOM.on(button, 'click', () => console.log('Clicked!')); * * // Manipulate classes * DOM.addClass(button, 'active'); * DOM.toggleClass(button, 'loading'); */ /** * DOM utility object providing jQuery-like methods * @namespace */ const DOM = { /** * Select a single element matching the selector * Replaces jQuery's $('selector').first() * * @param {string} selector - CSS selector * @param {Document|Element} [context=document] - Context to search within * @returns {Element|null} The matched element or null * * @example * const header = DOM.select('.header'); * const navItem = DOM.select('.nav-item', sidebar); */ select: function (selector) { let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document; return context.querySelector(selector); }, /** * Select all elements matching the selector * Replaces jQuery's $('selector') * * @param {string} selector - CSS selector * @param {Document|Element} [context=document] - Context to search within * @returns {Element[]} Array of matched elements * * @example * const buttons = DOM.selectAll('.btn'); * buttons.forEach(btn => DOM.addClass(btn, 'initialized')); */ selectAll: function (selector) { let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document; return Array.from(context.querySelectorAll(selector)); }, /** * Check if an element matching the selector exists * * @param {string} selector - CSS selector * @returns {boolean} True if element exists * * @example * if (DOM.exists('.sidebar')) { * initSidebar(); * } */ exists: selector => { return document.querySelector(selector) !== null; }, /** * Add an event listener to an element * Replaces jQuery's $.on() * * @param {Element|string} element - Element or selector * @param {string} event - Event name (e.g., 'click', 'change') * @param {Function} handler - Event handler function * @param {Object} [options={}] - addEventListener options * * @example * DOM.on('.btn', 'click', handleClick); * DOM.on(button, 'click', handleClick, { once: true }); */ on: function (element, event, handler) { let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; if (typeof element === 'string') { element = document.querySelector(element); } if (element) { element.addEventListener(event, handler, options); } }, /** * Remove an event listener from an element * Replaces jQuery's $.off() * * @param {Element|string} element - Element or selector * @param {string} event - Event name * @param {Function} handler - Event handler to remove * * @example * DOM.off(button, 'click', handleClick); */ off: (element, event, handler) => { if (typeof element === 'string') { element = document.querySelector(element); } if (element) { element.removeEventListener(event, handler); } }, /** * Add a class to an element * Replaces jQuery's $.addClass() * * @param {Element|string} element - Element or selector * @param {string} className - Class name to add * * @example * DOM.addClass('.menu', 'open'); */ addClass: (element, className) => { if (typeof element === 'string') { element = document.querySelector(element); } if (element) { element.classList.add(className); } }, /** * Remove a class from an element * Replaces jQuery's $.removeClass() * * @param {Element|string} element - Element or selector * @param {string} className - Class name to remove * * @example * DOM.removeClass('.menu', 'open'); */ removeClass: (element, className) => { if (typeof element === 'string') { element = document.querySelector(element); } if (element) { element.classList.remove(className); } }, /** * Toggle a class on an element * Replaces jQuery's $.toggleClass() * * @param {Element|string} element - Element or selector * @param {string} className - Class name to toggle * * @example * DOM.toggleClass('.dropdown', 'show'); */ toggleClass: (element, className) => { if (typeof element === 'string') { element = document.querySelector(element); } if (element) { element.classList.toggle(className); } }, /** * Check if an element has a class * Replaces jQuery's $.hasClass() * * @param {Element|string} element - Element or selector * @param {string} className - Class name to check * @returns {boolean} True if element has the class * * @example * if (DOM.hasClass('.menu', 'open')) { * closeMenu(); * } */ hasClass: (element, className) => { if (typeof element === 'string') { element = document.querySelector(element); } return element ? element.classList.contains(className) : false; }, /** * Get or set an attribute on an element * Replaces jQuery's $.attr() * * @param {Element|string} element - Element or selector * @param {string} name - Attribute name * @param {string} [value] - Value to set (omit to get) * @returns {string|Element|null} Attribute value when getting, element when setting * * @example * // Get attribute * const href = DOM.attr(link, 'href'); * * // Set attribute * DOM.attr(link, 'href', '/new-page'); */ attr: (element, name, value) => { if (typeof element === 'string') { element = document.querySelector(element); } if (!element) return null; if (value === undefined) { return element.getAttribute(name); } else { element.setAttribute(name, value); return element; } }, /** * Get or set a data attribute on an element * Replaces jQuery's $.data() * * @param {Element|string} element - Element or selector * @param {string} name - Data attribute name (without 'data-' prefix) * @param {string} [value] - Value to set (omit to get) * @returns {string|Element|null} Data value when getting, element when setting * * @example * // Get data attribute * const id = DOM.data(row, 'id'); // Gets data-id * * // Set data attribute * DOM.data(row, 'id', '123'); */ data: (element, name, value) => { if (typeof element === 'string') { element = document.querySelector(element); } if (!element) return null; const dataName = `data-${name}`; if (value === undefined) { return element.getAttribute(dataName); } else { element.setAttribute(dataName, value); return element; } }, /** * Get or set text content of an element * Replaces jQuery's $.text() * * @param {Element|string} element - Element or selector * @param {string} [content] - Text to set (omit to get) * @returns {string|Element|null} Text content when getting, element when setting * * @example * const text = DOM.text('.title'); * DOM.text('.title', 'New Title'); */ text: (element, content) => { if (typeof element === 'string') { element = document.querySelector(element); } if (!element) return null; if (content === undefined) { return element.textContent; } else { element.textContent = content; return element; } }, /** * Get or set HTML content of an element * Replaces jQuery's $.html() * * @param {Element|string} element - Element or selector * @param {string} [content] - HTML to set (omit to get) * @returns {string|Element|null} HTML content when getting, element when setting * * @example * const html = DOM.html('.container'); * DOM.html('.container', '

New content

'); */ html: (element, content) => { if (typeof element === 'string') { element = document.querySelector(element); } if (!element) return null; if (content === undefined) { return element.innerHTML; } else { element.innerHTML = content; return element; } }, /** * Hide an element * Replaces jQuery's $.hide() * * @param {Element|string} element - Element or selector * * @example * DOM.hide('.modal'); */ hide: element => { if (typeof element === 'string') { element = document.querySelector(element); } if (element) { element.style.display = 'none'; } }, /** * Show an element * Replaces jQuery's $.show() * * @param {Element|string} element - Element or selector * @param {string} [display='block'] - Display value to use * * @example * DOM.show('.modal'); * DOM.show('.flex-item', 'flex'); */ show: function (element) { let display = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'block'; if (typeof element === 'string') { element = document.querySelector(element); } if (element) { element.style.display = display; } }, /** * Toggle element visibility * Replaces jQuery's $.toggle() * * @param {Element|string} element - Element or selector * @param {string} [display='block'] - Display value when showing * * @example * DOM.toggle('.menu'); */ toggle: function (element) { let display = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'block'; if (typeof element === 'string') { element = document.querySelector(element); } if (element) { if (element.style.display === 'none') { element.style.display = display; } else { element.style.display = 'none'; } } }, /** * Animate element sliding up (collapsing) * Replaces jQuery's $.slideUp() * * @param {Element|string} element - Element or selector * @param {number} [duration=300] - Animation duration in ms * @returns {Promise} Resolves when animation completes * * @example * await DOM.slideUp('.panel'); */ slideUp: function (element) { let duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 300; if (typeof element === 'string') { element = document.querySelector(element); } if (!element) return Promise.resolve(); return new Promise(resolve => { const height = element.scrollHeight; element.style.height = `${height}px`; element.style.overflow = 'hidden'; element.animate([{ height: `${height}px` }, { height: '0px' }], { duration, easing: 'ease-in-out' }).onfinish = () => { element.style.display = 'none'; element.style.height = ''; element.style.overflow = ''; resolve(); }; }); }, /** * Animate element sliding down (expanding) * Replaces jQuery's $.slideDown() * * @param {Element|string} element - Element or selector * @param {number} [duration=300] - Animation duration in ms * @returns {Promise} Resolves when animation completes * * @example * await DOM.slideDown('.panel'); */ slideDown: function (element) { let duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 300; if (typeof element === 'string') { element = document.querySelector(element); } if (!element) return Promise.resolve(); return new Promise(resolve => { element.style.display = 'block'; element.style.height = '0px'; element.style.overflow = 'hidden'; const height = element.scrollHeight; element.animate([{ height: '0px' }, { height: `${height}px` }], { duration, easing: 'ease-in-out' }).onfinish = () => { element.style.height = 'auto'; element.style.overflow = 'visible'; resolve(); }; }); }, /** * Animate element fading in * Replaces jQuery's $.fadeIn() * * @param {Element|string} element - Element or selector * @param {number} [duration=300] - Animation duration in ms * @returns {Promise} Resolves when animation completes * * @example * await DOM.fadeIn('.modal'); */ fadeIn: function (element) { let duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 300; if (typeof element === 'string') { element = document.querySelector(element); } if (!element) return Promise.resolve(); return new Promise(resolve => { element.style.opacity = '0'; element.style.display = 'block'; element.animate([{ opacity: 0 }, { opacity: 1 }], { duration, easing: 'ease-in-out' }).onfinish = () => { element.style.opacity = ''; resolve(); }; }); }, /** * Animate element fading out * Replaces jQuery's $.fadeOut() * * @param {Element|string} element - Element or selector * @param {number} [duration=300] - Animation duration in ms * @returns {Promise} Resolves when animation completes * * @example * await DOM.fadeOut('.modal'); */ fadeOut: function (element) { let duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 300; if (typeof element === 'string') { element = document.querySelector(element); } if (!element) return Promise.resolve(); return new Promise(resolve => { element.animate([{ opacity: 1 }, { opacity: 0 }], { duration, easing: 'ease-in-out' }).onfinish = () => { element.style.display = 'none'; element.style.opacity = ''; resolve(); }; }); }, /** * Get element dimensions and position relative to viewport * * @param {Element|string} element - Element or selector * @returns {Object|null} Dimensions object or null * @property {number} width - Element width * @property {number} height - Element height * @property {number} top - Distance from viewport top * @property {number} left - Distance from viewport left * @property {number} bottom - Distance from viewport bottom * @property {number} right - Distance from viewport right * * @example * const { width, height, top, left } = DOM.dimensions('.card'); */ dimensions: element => { if (typeof element === 'string') { element = document.querySelector(element); } if (!element) return null; const rect = element.getBoundingClientRect(); return { width: rect.width, height: rect.height, top: rect.top, left: rect.left, bottom: rect.bottom, right: rect.right }; }, /** * Execute callback when DOM is ready * Replaces jQuery's $(document).ready() * * @param {Function} callback - Function to execute when DOM is ready * * @example * DOM.ready(() => { * initApp(); * }); */ ready: callback => { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', callback); } else { callback(); } }, /** * Create an element with optional attributes and children * * @param {string} tag - HTML tag name * @param {Object} [attrs={}] - Attributes to set * @param {Array} [children=[]] - Child elements or text * @returns {Element} The created element * * @example * const button = DOM.create('button', { class: 'btn', type: 'submit' }, ['Submit']); */ create: function (tag) { let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; let children = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; const el = document.createElement(tag); Object.entries(attrs).forEach(_ref => { let [key, value] = _ref; if (key === 'class') { el.className = value; } else if (key.startsWith('data-')) { el.setAttribute(key, value); } else { el[key] = value; } }); children.forEach(child => { if (typeof child === 'string') { el.appendChild(document.createTextNode(child)); } else if (child instanceof Element) { el.appendChild(child); } }); return el; }, /** * Find the closest ancestor matching a selector * * @param {Element|string} element - Element or selector * @param {string} selector - Selector to match * @returns {Element|null} Closest matching ancestor or null * * @example * const form = DOM.closest(input, 'form'); */ closest: (element, selector) => { if (typeof element === 'string') { element = document.querySelector(element); } return element ? element.closest(selector) : null; } }; /* harmony default export */ const dom = ((/* unused pure expression or super */ null && (DOM))); // EXTERNAL MODULE: ../../node_modules/dayjs/dayjs.min.js var dayjs_min = __webpack_require__(464); var dayjs_min_default = /*#__PURE__*/__webpack_require__.n(dayjs_min); // EXTERNAL MODULE: ../../node_modules/dayjs/plugin/utc.js var utc = __webpack_require__(657); var utc_default = /*#__PURE__*/__webpack_require__.n(utc); // EXTERNAL MODULE: ../../node_modules/dayjs/plugin/timezone.js var timezone = __webpack_require__(168); var timezone_default = /*#__PURE__*/__webpack_require__.n(timezone); // EXTERNAL MODULE: ../../node_modules/dayjs/plugin/relativeTime.js var relativeTime = __webpack_require__(562); var relativeTime_default = /*#__PURE__*/__webpack_require__.n(relativeTime); // EXTERNAL MODULE: ../../node_modules/dayjs/plugin/customParseFormat.js var customParseFormat = __webpack_require__(630); var customParseFormat_default = /*#__PURE__*/__webpack_require__.n(customParseFormat); // EXTERNAL MODULE: ../../node_modules/dayjs/plugin/advancedFormat.js var advancedFormat = __webpack_require__(258); var advancedFormat_default = /*#__PURE__*/__webpack_require__.n(advancedFormat); ;// ../assets/scripts/utils/date.js /** * Modern Date Utilities * Using Day.js (2KB) instead of Moment.js (67KB) - 97% size reduction * Provides consistent date formatting and manipulation across the application */ // Enable Day.js plugins dayjs_min_default().extend((utc_default())); dayjs_min_default().extend((timezone_default())); dayjs_min_default().extend((relativeTime_default())); dayjs_min_default().extend((customParseFormat_default())); dayjs_min_default().extend((advancedFormat_default())); const DateUtils = { /** * Get current date/time */ now: () => dayjs_min_default()(), /** * Parse date from string or Date object */ parse: function (input) { let format = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; return format ? dayjs_min_default()(input, format) : dayjs_min_default()(input); }, /** * Format date for display */ format: function (date) { let format = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'YYYY-MM-DD'; return dayjs_min_default()(date).format(format); }, /** * Common date formatting presets */ formatters: { // Dashboard display formats shortDate: date => dayjs_min_default()(date).format('MMM DD, YYYY'), longDate: date => dayjs_min_default()(date).format('MMMM DD, YYYY'), dateTime: date => dayjs_min_default()(date).format('MMM DD, YYYY h:mm A'), // Calendar formats calendarDate: date => dayjs_min_default()(date).format('YYYY-MM-DD'), calendarDateTime: date => dayjs_min_default()(date).format('YYYY-MM-DD HH:mm:ss'), // Form input formats inputDate: date => dayjs_min_default()(date).format('YYYY-MM-DD'), inputDateTime: date => dayjs_min_default()(date).format('YYYY-MM-DDTHH:mm'), // Display formats timeOnly: date => dayjs_min_default()(date).format('h:mm A'), monthYear: date => dayjs_min_default()(date).format('MMMM YYYY'), dayMonth: date => dayjs_min_default()(date).format('DD MMM'), // Relative time relative: date => dayjs_min_default()(date).fromNow(), relativeCalendar: date => { const now = dayjs_min_default()(); const target = dayjs_min_default()(date); const diffDays = now.diff(target, 'day'); if (diffDays === 0) return 'Today'; if (diffDays === 1) return 'Yesterday'; if (diffDays === -1) return 'Tomorrow'; if (diffDays > 1 && diffDays < 7) return `${diffDays} days ago`; if (diffDays < -1 && diffDays > -7) return `In ${Math.abs(diffDays)} days`; return target.format('MMM DD, YYYY'); } }, /** * Date manipulation */ add: (date, amount, unit) => dayjs_min_default()(date).add(amount, unit), subtract: (date, amount, unit) => dayjs_min_default()(date).subtract(amount, unit), startOf: (date, unit) => dayjs_min_default()(date).startOf(unit), endOf: (date, unit) => dayjs_min_default()(date).endOf(unit), /** * Date comparison */ isBefore: (date1, date2) => dayjs_min_default()(date1).isBefore(dayjs_min_default()(date2)), isAfter: (date1, date2) => dayjs_min_default()(date1).isAfter(dayjs_min_default()(date2)), isSame: function (date1, date2) { let unit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'day'; return dayjs_min_default()(date1).isSame(dayjs_min_default()(date2), unit); }, isBetween: (date, start, end) => dayjs_min_default()(date).isBetween(dayjs_min_default()(start), dayjs_min_default()(end)), /** * Date validation */ isValid: date => dayjs_min_default()(date).isValid(), /** * Timezone utilities */ timezone: { convert: (date, tz) => dayjs_min_default()(date).tz(tz), utc: date => dayjs_min_default()(date).utc(), local: date => dayjs_min_default()(date).local(), guess: () => dayjs_min_default().tz.guess() }, /** * Calendar utilities */ calendar: { // Get calendar month data for building calendar views getMonthData: function () { let date = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; const target = date ? dayjs_min_default()(date) : dayjs_min_default()(); const startOfMonth = target.startOf('month'); const endOfMonth = target.endOf('month'); const startOfCalendar = startOfMonth.startOf('week'); const endOfCalendar = endOfMonth.endOf('week'); const days = []; let current = startOfCalendar; while (current.isBefore(endOfCalendar) || current.isSame(endOfCalendar, 'day')) { days.push({ date: current.format('YYYY-MM-DD'), day: current.date(), isCurrentMonth: current.isSame(target, 'month'), isToday: current.isSame(dayjs_min_default()(), 'day'), dayjs: current.clone() }); current = current.add(1, 'day'); } return { month: target.format('MMMM YYYY'), year: target.year(), monthIndex: target.month(), days }; }, // Get week data getWeekData: function () { let date = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; const target = date ? dayjs_min_default()(date) : dayjs_min_default()(); const startOfWeek = target.startOf('week'); const endOfWeek = target.endOf('week'); const days = []; let current = startOfWeek; while (current.isBefore(endOfWeek) || current.isSame(endOfWeek, 'day')) { days.push({ date: current.format('YYYY-MM-DD'), day: current.date(), dayName: current.format('dddd'), shortDayName: current.format('ddd'), isToday: current.isSame(dayjs_min_default()(), 'day'), dayjs: current.clone() }); current = current.add(1, 'day'); } return { weekStart: startOfWeek.format('MMM DD'), weekEnd: endOfWeek.format('MMM DD, YYYY'), days }; } }, /** * Form utilities */ form: { // Convert date to HTML5 input format toInputValue: date => dayjs_min_default()(date).format('YYYY-MM-DD'), toDateTimeInputValue: date => dayjs_min_default()(date).format('YYYY-MM-DDTHH:mm'), // Parse from HTML5 input fromInputValue: value => dayjs_min_default()(value), // Validate date input validateDateInput: value => { const parsed = dayjs_min_default()(value); return parsed.isValid() && value.length >= 8; // Basic validation } }, /** * Chart/Data utilities */ charts: { // Generate date ranges for charts generateDateRange: function (start, end) { let interval = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'day'; const dates = []; let current = dayjs_min_default()(start); const endDate = dayjs_min_default()(end); while (current.isBefore(endDate) || current.isSame(endDate, interval)) { dates.push({ date: current.format('YYYY-MM-DD'), label: current.format('MMM DD'), value: current.toISOString(), dayjs: current.clone() }); current = current.add(1, interval); } return dates; }, // Get common chart date labels getChartLabels: function () { let period = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'week'; const now = dayjs_min_default()(); switch (period) { case 'week': return Array.from({ length: 7 }, (_, i) => now.subtract(6 - i, 'day').format('ddd')); case 'month': return Array.from({ length: 30 }, (_, i) => now.subtract(29 - i, 'day').format('DD')); case 'year': return Array.from({ length: 12 }, (_, i) => now.subtract(11 - i, 'month').format('MMM')); default: return []; } } } }; // Export dayjs instance for direct use when needed // Default export /* harmony default export */ const date = (DateUtils); ;// ../assets/scripts/utils/theme.js /** * Adminator Theme Manager * Handles light/dark mode switching with localStorage persistence * * @module utils/theme * @example * // Get current theme * const current = Theme.current(); // 'light' or 'dark' * * // Toggle theme * Theme.toggle(); * * // Apply specific theme * Theme.apply('dark'); * * // Listen for theme changes * window.addEventListener('adminator:themeChanged', (e) => { * console.log('New theme:', e.detail.theme); * }); */ /* global Chart */ /** @constant {string} Storage key for theme preference */ const THEME_KEY = 'adminator-theme'; /** @constant {string[]} Valid theme values */ const VALID_THEMES = ['light', 'dark']; /** * Safe localStorage wrapper * Handles cases where localStorage is unavailable (private browsing, etc.) * @private */ const Storage = { /** * Get item from localStorage * @param {string} key - Storage key * @returns {string|null} Stored value or null */ get(key) { try { return localStorage.getItem(key); } catch { return null; } }, /** * Set item in localStorage * @param {string} key - Storage key * @param {string} value - Value to store * @returns {boolean} Success status */ set(key, value) { try { localStorage.setItem(key, value); return true; } catch { return false; } } }; /** * Theme Manager * @namespace */ const Theme = { /** * Apply a theme to the document * Updates Chart.js defaults if available * * @param {('light'|'dark')} theme - Theme to apply * @fires adminator:themeChanged * @returns {boolean} Success status * * @example * Theme.apply('dark'); */ apply(theme) { // Validate theme if (!VALID_THEMES.includes(theme)) { console.warn(`[Adminator] Invalid theme "${theme}". Using "light".`); theme = 'light'; } // Apply to document document.documentElement.setAttribute('data-theme', theme); // Update Chart.js defaults if available if (window.Chart && Chart.defaults) { const isDark = theme === 'dark'; const textColor = isDark ? '#FFFFFF' : '#212529'; const mutedColor = isDark ? '#D1D5DB' : '#6C757D'; const borderColor = isDark ? '#374151' : '#E2E5E8'; const gridColor = isDark ? 'rgba(209, 213, 219, 0.15)' : 'rgba(0, 0, 0, 0.05)'; const tooltipBg = isDark ? '#1F2937' : 'rgba(255, 255, 255, 0.95)'; // Set global defaults Chart.defaults.color = textColor; Chart.defaults.borderColor = borderColor; Chart.defaults.backgroundColor = tooltipBg; // Set plugin defaults Chart.defaults.plugins.legend.labels.color = textColor; Chart.defaults.plugins.tooltip.backgroundColor = tooltipBg; Chart.defaults.plugins.tooltip.titleColor = textColor; Chart.defaults.plugins.tooltip.bodyColor = textColor; Chart.defaults.plugins.tooltip.borderColor = borderColor; // Set scale defaults const scales = ['category', 'linear', 'logarithmic', 'time', 'radialLinear']; scales.forEach(scale => { if (Chart.defaults.scales[scale]) { Chart.defaults.scales[scale].ticks.color = mutedColor; Chart.defaults.scales[scale].grid.color = gridColor; } }); // RadialLinear specific if (Chart.defaults.scales.radialLinear) { Chart.defaults.scales.radialLinear.pointLabels.color = mutedColor; Chart.defaults.scales.radialLinear.angleLines.color = gridColor; } } // Persist to storage Storage.set(THEME_KEY, theme); // Update toggle accessibility state if exists const toggle = document.getElementById('theme-toggle'); if (toggle) { toggle.setAttribute('aria-checked', theme === 'dark' ? 'true' : 'false'); } // Dispatch event for other components window.dispatchEvent(new CustomEvent('adminator:themeChanged', { detail: { theme } })); return true; }, /** * Toggle between light and dark themes * * @returns {('light'|'dark')} The new theme * * @example * const newTheme = Theme.toggle(); * console.log('Switched to:', newTheme); */ toggle() { const next = this.current() === 'dark' ? 'light' : 'dark'; this.apply(next); return next; }, /** * Get the current theme * * @returns {('light'|'dark')} Current theme * * @example * if (Theme.current() === 'dark') { * // Dark mode specific logic * } */ current() { const stored = Storage.get(THEME_KEY); return VALID_THEMES.includes(stored) ? stored : 'light'; }, /** * Initialize the theme system * Detects OS preference on first visit, otherwise uses stored preference * * @returns {('light'|'dark')} The applied theme * * @example * // Call once on app initialization * Theme.init(); */ init() { const stored = Storage.get(THEME_KEY); if (!stored) { // First visit - detect OS preference const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; const theme = prefersDark ? 'dark' : 'light'; this.apply(theme); return theme; } // Use stored preference this.apply(this.current()); return this.current(); }, /** * Get a CSS variable value from the document * * @param {string} varName - CSS variable name (with or without --) * @returns {string} The CSS variable value * * @example * const bgColor = Theme.getCSSVar('--c-bkg-body'); */ getCSSVar(varName) { const name = varName.startsWith('--') ? varName : `--${varName}`; return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); }, /** * Get theme-aware colors for vector maps * * @returns {Object} Vector map color configuration * @property {string} backgroundColor - Map background color * @property {string} borderColor - Border color * @property {string} regionColor - Default region fill color * @property {string} markerFill - Marker fill color * @property {string} markerStroke - Marker stroke color * @property {string} hoverColor - Region hover color * @property {string} selectedColor - Selected region color * @property {string} scaleStart - Scale gradient start * @property {string} scaleEnd - Scale gradient end */ getVectorMapColors() { return { backgroundColor: this.getCSSVar('--vmap-bg-color'), borderColor: this.getCSSVar('--vmap-border-color'), regionColor: this.getCSSVar('--vmap-region-color'), markerFill: this.getCSSVar('--vmap-marker-fill'), markerStroke: this.getCSSVar('--vmap-marker-stroke'), hoverColor: this.getCSSVar('--vmap-hover-color'), selectedColor: this.getCSSVar('--vmap-selected-color'), scaleStart: this.getCSSVar('--vmap-scale-start'), scaleEnd: this.getCSSVar('--vmap-scale-end'), scaleLight: this.getCSSVar('--vmap-scale-light'), scaleDark: this.getCSSVar('--vmap-scale-dark') }; }, /** * Get theme-aware colors for sparkline charts * * @returns {Object} Sparkline color configuration */ getSparklineColors() { return { success: this.getCSSVar('--sparkline-success'), purple: this.getCSSVar('--sparkline-purple'), info: this.getCSSVar('--sparkline-info'), danger: this.getCSSVar('--sparkline-danger'), light: this.getCSSVar('--sparkline-light') }; }, /** * Get theme-aware colors for Chart.js charts * * @returns {Object} Chart color configuration * @property {string} textColor - Main text color * @property {string} mutedColor - Muted/secondary text color * @property {string} borderColor - Border color * @property {string} gridColor - Grid line color * @property {string} tooltipBg - Tooltip background color */ getChartColors() { const isDark = this.current() === 'dark'; return { textColor: isDark ? '#FFFFFF' : '#212529', mutedColor: isDark ? '#D1D5DB' : '#6C757D', borderColor: isDark ? '#374151' : '#E2E5E8', gridColor: isDark ? 'rgba(209, 213, 219, 0.15)' : 'rgba(0, 0, 0, 0.05)', tooltipBg: isDark ? '#1F2937' : 'rgba(255, 255, 255, 0.95)' }; }, /** * Check if dark mode is currently active * * @returns {boolean} True if dark mode is active * * @example * if (Theme.isDark()) { * // Apply dark-specific styles * } */ isDark() { return this.current() === 'dark'; }, /** * Check if light mode is currently active * * @returns {boolean} True if light mode is active */ isLight() { return this.current() === 'light'; } }; /* harmony default export */ const theme = (Theme); ;// ../assets/scripts/utils/events.js /** * Adminator Event Utilities * Provides efficient event handling with delegation and cleanup * * @module utils/events */ /** * Store for event handlers to enable proper cleanup * @type {WeakMap>>} */ const handlerRegistry = new WeakMap(); /** * Store for AbortControllers to enable easy cleanup * @type {WeakMap} */ const controllerRegistry = new WeakMap(); /** * Event utilities namespace * @namespace */ const Events = { /** * Add an event listener with automatic cleanup support * Uses AbortController for efficient removal * * @param {Element} element - Target element * @param {string} event - Event type * @param {Function} handler - Event handler * @param {Object} [options={}] - Event listener options * @returns {Function} Cleanup function to remove the listener * * @example * const cleanup = Events.on(button, 'click', handleClick); * // Later: cleanup() to remove */ on(element, event, handler) { let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; if (!element) return () => {}; // Get or create AbortController for this element let controller = controllerRegistry.get(element); if (!controller) { controller = new AbortController(); controllerRegistry.set(element, controller); } // Register handler if (!handlerRegistry.has(element)) { handlerRegistry.set(element, new Map()); } const elementHandlers = handlerRegistry.get(element); if (!elementHandlers.has(event)) { elementHandlers.set(event, new Set()); } elementHandlers.get(event).add(handler); // Add listener with abort signal element.addEventListener(event, handler, { ...options, signal: controller.signal }); // Return cleanup function return () => { var _handlerRegistry$get; element.removeEventListener(event, handler, options); const handlers = (_handlerRegistry$get = handlerRegistry.get(element)) === null || _handlerRegistry$get === void 0 ? void 0 : _handlerRegistry$get.get(event); if (handlers) { handlers.delete(handler); } }; }, /** * Remove all event listeners from an element * * @param {Element} element - Target element * * @example * Events.off(element); // Removes all listeners */ off(element) { if (!element) return; const controller = controllerRegistry.get(element); if (controller) { controller.abort(); controllerRegistry.delete(element); } handlerRegistry.delete(element); }, /** * Add event delegation - listen on parent for events from children * More efficient than adding listeners to many elements * * @param {Element} parent - Parent element to listen on * @param {string} event - Event type * @param {string} selector - CSS selector for target elements * @param {Function} handler - Event handler (receives event and matched element) * @param {Object} [options={}] - Event listener options * @returns {Function} Cleanup function * * @example * // Instead of adding click to every .btn * Events.delegate(container, 'click', '.btn', (e, btn) => { * console.log('Button clicked:', btn); * }); */ delegate(parent, event, selector, handler) { let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; if (!parent) return () => {}; const delegatedHandler = e => { const target = e.target.closest(selector); if (target && parent.contains(target)) { handler.call(target, e, target); } }; return this.on(parent, event, delegatedHandler, options); }, /** * Add a one-time event listener * * @param {Element} element - Target element * @param {string} event - Event type * @param {Function} handler - Event handler * @returns {Function} Cleanup function * * @example * Events.once(button, 'click', handleFirstClick); */ once(element, event, handler) { return this.on(element, event, handler, { once: true }); }, /** * Create a debounced event handler * * @param {Function} handler - Original handler * @param {number} [delay=250] - Debounce delay in ms * @returns {Function} Debounced handler * * @example * const debouncedResize = Events.debounce(handleResize, 200); * window.addEventListener('resize', debouncedResize); */ debounce(handler) { let delay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 250; let timeoutId; return function debounced() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } clearTimeout(timeoutId); timeoutId = setTimeout(() => handler.apply(this, args), delay); }; }, /** * Create a throttled event handler * * @param {Function} handler - Original handler * @param {number} [limit=250] - Throttle limit in ms * @returns {Function} Throttled handler * * @example * const throttledScroll = Events.throttle(handleScroll, 100); * window.addEventListener('scroll', throttledScroll); */ throttle(handler) { let limit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 250; let inThrottle; return function throttled() { if (!inThrottle) { for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } handler.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }, /** * Dispatch a custom event * * @param {Element|Window} target - Target to dispatch on * @param {string} eventName - Event name * @param {Object} [detail={}] - Event detail data * @param {Object} [options={}] - Event options (bubbles, cancelable) * @returns {boolean} Whether the event was not cancelled * * @example * Events.emit(element, 'custom:event', { data: 'value' }); */ emit(target, eventName) { let detail = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; const event = new CustomEvent(eventName, { detail, bubbles: options.bubbles ?? true, cancelable: options.cancelable ?? true }); return target.dispatchEvent(event); } }; /* harmony default export */ const events = (Events); ;// ../assets/scripts/utils/performance.js /** * Adminator Performance Utilities * Provides ResizeObserver, IntersectionObserver, and lazy loading utilities * * @module utils/performance */ /** * Store for ResizeObserver instances * @type {WeakMap} */ const resizeCallbacks = new WeakMap(); /** * Shared ResizeObserver instance for efficiency * @type {ResizeObserver|null} */ let sharedResizeObserver = null; /** * Store for IntersectionObserver callbacks * @type {WeakMap} */ const intersectionCallbacks = new WeakMap(); /** * Map of IntersectionObservers by threshold * @type {Map} */ const intersectionObservers = new Map(); /** * Performance utilities namespace * @namespace */ const Performance = { /** * Observe element resize events efficiently * Uses shared ResizeObserver for better performance * * @param {Element} element - Element to observe * @param {Function} callback - Callback receiving { width, height, entry } * @returns {Function} Cleanup function to stop observing * * @example * const unobserve = Performance.onResize(chart, ({ width, height }) => { * chart.resize(width, height); * }); */ onResize(element, callback) { if (!element || typeof callback !== 'function') { return () => {}; } // Create shared observer if needed if (!sharedResizeObserver) { sharedResizeObserver = new ResizeObserver(entries => { for (const entry of entries) { const cb = resizeCallbacks.get(entry.target); if (cb) { const { width, height } = entry.contentRect; cb({ width, height, entry }); } } }); } // Store callback and observe resizeCallbacks.set(element, callback); sharedResizeObserver.observe(element); // Return cleanup function return () => { var _sharedResizeObserver; resizeCallbacks.delete(element); (_sharedResizeObserver = sharedResizeObserver) === null || _sharedResizeObserver === void 0 || _sharedResizeObserver.unobserve(element); }; }, /** * Observe when element enters/exits viewport * Useful for lazy loading and animations * * @param {Element} element - Element to observe * @param {Function} callback - Callback receiving { isIntersecting, entry } * @param {Object} [options={}] - IntersectionObserver options * @param {number} [options.threshold=0] - Visibility threshold (0-1) * @param {string} [options.rootMargin='0px'] - Root margin * @returns {Function} Cleanup function to stop observing * * @example * const unobserve = Performance.onVisible(element, ({ isIntersecting }) => { * if (isIntersecting) { * loadContent(); * unobserve(); // Stop after first trigger * } * }); */ onVisible(element, callback) { let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; if (!element || typeof callback !== 'function') { return () => {}; } const { threshold = 0, rootMargin = '0px' } = options; const key = `${threshold}-${rootMargin}`; // Get or create observer for this threshold if (!intersectionObservers.has(key)) { const observer = new IntersectionObserver(entries => { for (const entry of entries) { const cb = intersectionCallbacks.get(entry.target); if (cb) { cb({ isIntersecting: entry.isIntersecting, ratio: entry.intersectionRatio, entry }); } } }, { threshold, rootMargin }); intersectionObservers.set(key, observer); } const observer = intersectionObservers.get(key); // Store callback and observe intersectionCallbacks.set(element, callback); observer.observe(element); // Return cleanup function return () => { intersectionCallbacks.delete(element); observer.unobserve(element); }; }, /** * Lazy load an element when it becomes visible * Automatically handles cleanup after loading * * @param {Element} element - Element to lazy load * @param {Function} loadFn - Function to call when visible * @param {Object} [options={}] - Options * @param {string} [options.rootMargin='100px'] - Preload margin * @returns {Function} Cleanup function * * @example * Performance.lazyLoad(chartContainer, () => { * initializeChart(chartContainer); * }); */ lazyLoad(element, loadFn) { let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; const { rootMargin = '100px' } = options; let loaded = false; const unobserve = this.onVisible(element, _ref => { let { isIntersecting } = _ref; if (isIntersecting && !loaded) { loaded = true; loadFn(); unobserve(); } }, { rootMargin }); return unobserve; }, /** * Batch DOM reads and writes to prevent layout thrashing * * @param {Function} readFn - Function that reads from DOM * @param {Function} writeFn - Function that writes to DOM * * @example * Performance.batch( * () => element.offsetHeight, // Read * (height) => element.style.minHeight = height + 'px' // Write * ); */ batch(readFn, writeFn) { // Use requestAnimationFrame for batching requestAnimationFrame(() => { const value = readFn(); requestAnimationFrame(() => { writeFn(value); }); }); }, /** * Execute callback on next animation frame * * @param {Function} callback - Function to execute * @returns {number} Request ID for cancellation * * @example * const id = Performance.nextFrame(() => updateUI()); * // Cancel: cancelAnimationFrame(id); */ nextFrame(callback) { return requestAnimationFrame(callback); }, /** * Execute callback when browser is idle * Falls back to setTimeout if requestIdleCallback not available * * @param {Function} callback - Function to execute * @param {Object} [options={}] - Options * @param {number} [options.timeout=1000] - Maximum wait time * @returns {number} Request ID for cancellation * * @example * Performance.whenIdle(() => { * // Non-critical work * preloadNextPage(); * }); */ whenIdle(callback) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const { timeout = 1000 } = options; if ('requestIdleCallback' in window) { return requestIdleCallback(callback, { timeout }); } // Fallback for Safari return setTimeout(callback, 1); }, /** * Cancel an idle callback * * @param {number} id - Request ID from whenIdle */ cancelIdle(id) { if ('cancelIdleCallback' in window) { cancelIdleCallback(id); } else { clearTimeout(id); } }, /** * Preload a resource (image, script, etc.) * * @param {string} url - URL to preload * @param {string} [as='image'] - Resource type (image, script, style, font) * @returns {Promise} Resolves when loaded * * @example * await Performance.preload('/images/hero.jpg', 'image'); */ preload(url) { let as = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'image'; return new Promise((resolve, reject) => { const link = document.createElement('link'); link.rel = 'preload'; link.as = as; link.href = url; link.onload = resolve; link.onerror = reject; document.head.appendChild(link); }); }, /** * Measure execution time of a function * * @param {string} label - Label for the measurement * @param {Function} fn - Function to measure * @returns {*} Return value of the function * * @example * const result = Performance.measure('render', () => renderChart()); */ measure(label, fn) { const start = performance.now(); const result = fn(); const end = performance.now(); if (false) // removed by dead control flow {} return result; }, /** * Cleanup all observers * Call this when destroying the app */ cleanup() { // Cleanup resize observer if (sharedResizeObserver) { sharedResizeObserver.disconnect(); sharedResizeObserver = null; } // Cleanup intersection observers for (const observer of intersectionObservers.values()) { observer.disconnect(); } intersectionObservers.clear(); } }; /* harmony default export */ const utils_performance = (Performance); ;// ../assets/scripts/utils/logger.js /** * Adminator Logger Utility * Development-only logging utility for debugging * * @module utils/logger */ /** * Check if we're in development mode * @returns {boolean} */ const isDev = () => { try { return false || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'; } catch { return false; } }; /** * Logger object with development-only output * All methods are no-ops in production for zero overhead */ const Logger = { /** * Log informational messages (development only) * @param {string} message - The message to log * @param {Object} [context] - Additional context data */ info(message, context) { if (isDev()) { console.info(`[Adminator] ${message}`, context || ''); } }, /** * Log warning messages (development only) * @param {string} message - The warning message * @param {Object} [context] - Additional context data */ warn(message, context) { if (isDev()) { console.warn(`[Adminator] ${message}`, context || ''); } }, /** * Log error messages (development only) * @param {string} message - The error message * @param {Error|Object} [context] - Error object or context data */ error(message, context) { if (isDev()) { console.error(`[Adminator] ${message}`, context || ''); } }, /** * Log debug messages (development only) * @param {string} message - The debug message * @param {Object} [context] - Additional context data */ debug(message, context) { if (isDev()) { console.debug(`[Adminator] ${message}`, context || ''); } }, /** * Group related log messages (development only) * @param {string} label - Group label */ group(label) { if (isDev()) { console.group(`[Adminator] ${label}`); } }, /** * End a log group (development only) */ groupEnd() { if (isDev()) { console.groupEnd(); } }, /** * Log with timing information (development only) * @param {string} label - Timer label */ time(label) { if (isDev()) { console.time(`[Adminator] ${label}`); } }, /** * End timing and log result (development only) * @param {string} label - Timer label (must match time() call) */ timeEnd(label) { if (isDev()) { console.timeEnd(`[Adminator] ${label}`); } }, /** * Log a table of data (development only) * @param {Array|Object} data - Data to display as table */ table(data) { if (isDev()) { console.table(data); } } }; /* harmony default export */ const logger = (Logger); ;// ../assets/scripts/components/Sidebar.js /** * Modern Sidebar Component * Replaces jQuery-based sidebar functionality with vanilla JavaScript */ class Sidebar { constructor() { this.sidebar = document.querySelector('.sidebar'); this.sidebarMenu = document.querySelector('.sidebar .sidebar-menu'); this.sidebarToggleLinks = document.querySelectorAll('.sidebar-toggle a'); this.sidebarToggleById = document.querySelector('#sidebar-toggle'); this.app = document.querySelector('.app'); this.init(); } init() { if (!this.sidebar || !this.sidebarMenu) { return; } this.setupMenuToggle(); this.setupSidebarToggle(); this.setActiveLink(); } /** * Setup dropdown menu functionality */ setupMenuToggle() { const menuLinks = this.sidebarMenu.querySelectorAll('li a'); menuLinks.forEach(link => { link.addEventListener('click', e => { const listItem = link.parentElement; const dropdownMenu = listItem.querySelector('.dropdown-menu'); // If this is a regular navigation link (not dropdown), allow normal navigation if (!dropdownMenu) { // Don't prevent default for regular navigation links return; } // Only prevent default for dropdown toggles e.preventDefault(); if (listItem.classList.contains('open')) { this.closeDropdown(listItem, dropdownMenu); } else { this.closeAllDropdowns(); this.openDropdown(listItem, dropdownMenu); } }); }); } /** * Open dropdown with smooth animation */ openDropdown(listItem, dropdownMenu) { listItem.classList.add('open'); dropdownMenu.style.display = 'block'; dropdownMenu.style.height = '0px'; dropdownMenu.style.overflow = 'hidden'; // Get the natural height const height = dropdownMenu.scrollHeight; // Animate to full height dropdownMenu.animate([{ height: '0px' }, { height: `${height}px` }], { duration: 200, easing: 'ease-out' }).onfinish = () => { dropdownMenu.style.height = 'auto'; dropdownMenu.style.overflow = 'visible'; }; } /** * Close dropdown with smooth animation */ closeDropdown(listItem, dropdownMenu) { const height = dropdownMenu.scrollHeight; dropdownMenu.style.height = `${height}px`; dropdownMenu.style.overflow = 'hidden'; dropdownMenu.animate([{ height: `${height}px` }, { height: '0px' }], { duration: 200, easing: 'ease-in' }).onfinish = () => { listItem.classList.remove('open'); dropdownMenu.style.display = 'none'; dropdownMenu.style.height = ''; dropdownMenu.style.overflow = ''; }; } /** * Close all open dropdowns */ closeAllDropdowns() { const openItems = this.sidebarMenu.querySelectorAll('li.open'); openItems.forEach(item => { const dropdownMenu = item.querySelector('.dropdown-menu'); if (dropdownMenu) { this.closeDropdown(item, dropdownMenu); } // Also remove the has-active-child class item.classList.remove('has-active-child'); }); } /** * Setup sidebar toggle functionality */ setupSidebarToggle() { // Handle mobile sidebar toggle links (inside .sidebar-toggle divs) this.sidebarToggleLinks.forEach(link => { if (link && this.app) { link.addEventListener('click', e => { e.preventDefault(); this.toggleSidebar(); }); } }); // Handle the main topbar sidebar toggle if (this.sidebarToggleById && this.app) { this.sidebarToggleById.addEventListener('click', e => { e.preventDefault(); this.toggleSidebar(); }); } } /** * Toggle sidebar and handle resize events properly */ toggleSidebar() { this.app.classList.toggle('is-collapsed'); // Only trigger resize for masonry, but avoid chart redraw issues setTimeout(() => { // Dispatch a custom event instead of generic resize to avoid chart issues window.dispatchEvent(new CustomEvent('sidebar:toggle', { detail: { collapsed: this.app.classList.contains('is-collapsed') } })); // Still trigger resize for masonry but with a specific check if (window.EVENT) { window.dispatchEvent(window.EVENT); } }, 300); } /** * Set active link based on current URL */ setActiveLink() { // Remove active class from all nav items (including dropdown items) const allNavItems = this.sidebar.querySelectorAll('.nav-item'); allNavItems.forEach(item => { item.classList.remove('actived'); }); // Close all dropdowns first this.closeAllDropdowns(); // Get current page filename const currentPath = window.location.pathname; const currentPage = currentPath.split('/').pop() || 'index.html'; // Find and activate the correct nav item const allLinks = this.sidebar.querySelectorAll('a[href]'); allLinks.forEach(link => { const href = link.getAttribute('href'); if (!href || href === 'javascript:void(0);' || href === 'javascript:void(0)') return; // Extract filename from href const linkPage = href.split('/').pop(); if (linkPage === currentPage) { const navItem = link.closest('.nav-item'); if (navItem) { navItem.classList.add('actived'); // If this is inside a dropdown, handle parent dropdown specially const parentDropdown = navItem.closest('.dropdown-menu'); if (parentDropdown) { const parentDropdownItem = parentDropdown.closest('.nav-item.dropdown'); if (parentDropdownItem) { // Open the parent dropdown parentDropdownItem.classList.add('open'); parentDropdown.style.display = 'block'; // Add special styling to indicate parent has active child parentDropdownItem.classList.add('has-active-child'); } } } } }); } /** * Public method to refresh active links (useful for SPA navigation) */ refreshActiveLink() { this.setActiveLink(); } /** * Public method to toggle sidebar programmatically */ toggle() { if (this.app) { this.app.classList.toggle('is-collapsed'); } } /** * Public method to check if sidebar is collapsed */ isCollapsed() { return this.app ? this.app.classList.contains('is-collapsed') : false; } } /* harmony default export */ const components_Sidebar = (Sidebar); // EXTERNAL MODULE: ../../node_modules/chart.js/dist/chart.js + 1 modules var dist_chart = __webpack_require__(371); ;// ../assets/scripts/constants/colors.js const COLORS = { 'white': '#ffffff', 'red-50': '#ffebee', 'red-100': '#ffcdd2', 'red-200': '#ef9a9a', 'red-300': '#e57373', 'red-400': '#ef5350', 'red-500': '#f44336', 'red-600': '#e53935', 'red-700': '#d32f2f', 'red-800': '#c62828', 'red-900': '#b71c1c', 'red-a100': '#ff8a80', 'red-a200': '#ff5252', 'red-a400': '#ff1744', 'red-a700': '#d50000', 'pink-50': '#fce4ec', 'pink-100': '#f8bbd0', 'pink-200': '#f48fb1', 'pink-300': '#f06292', 'pink-400': '#ec407a', 'pink-500': '#e91e63', 'pink-600': '#d81b60', 'pink-700': '#c2185b', 'pink-800': '#ad1457', 'pink-900': '#880e4f', 'pink-a100': '#ff80ab', 'pink-a200': '#ff4081', 'pink-a400': '#f50057', 'pink-a700': '#c51162', 'purple-50': '#f3e5f5', 'purple-100': '#e1bee7', 'purple-200': '#ce93d8', 'purple-300': '#ba68c8', 'purple-400': '#ab47bc', 'purple-500': '#9c27b0', 'purple-600': '#8e24aa', 'purple-700': '#7b1fa2', 'purple-800': '#6a1b9a', 'purple-900': '#4a148c', 'purple-a100': '#ea80fc', 'purple-a200': '#e040fb', 'purple-a400': '#d500f9', 'purple-a700': '#aa00ff', 'deep-purple-50': '#ede7f6', 'deep-purple-100': '#d1c4e9', 'deep-purple-200': '#b39ddb', 'deep-purple-300': '#9575cd', 'deep-purple-400': '#7e57c2', 'deep-purple-500': '#673ab7', 'deep-purple-600': '#5e35b1', 'deep-purple-700': '#512da8', 'deep-purple-800': '#4527a0', 'deep-purple-900': '#311b92', 'deep-purple-a100': '#b388ff', 'deep-purple-a200': '#7c4dff', 'deep-purple-a400': '#651fff', 'deep-purple-a700': '#6200ea', 'indigo-50': '#e8eaf6', 'indigo-100': '#c5cae9', 'indigo-200': '#9fa8da', 'indigo-300': '#7986cb', 'indigo-400': '#5c6bc0', 'indigo-500': '#3f51b5', 'indigo-600': '#3949ab', 'indigo-700': '#303f9f', 'indigo-800': '#283593', 'indigo-900': '#1a237e', 'indigo-a100': '#8c9eff', 'indigo-a200': '#536dfe', 'indigo-a400': '#3d5afe', 'indigo-a700': '#304ffe', 'blue-50': '#e3f2fd', 'blue-100': '#bbdefb', 'blue-200': '#90caf9', 'blue-300': '#64b5f6', 'blue-400': '#42a5f5', 'blue-500': '#2196f3', 'blue-600': '#1e88e5', 'blue-700': '#1976d2', 'blue-800': '#1565c0', 'blue-900': '#0d47a1', 'blue-a100': '#82b1ff', 'blue-a200': '#448aff', 'blue-a400': '#2979ff', 'blue-a700': '#2962ff', 'light-blue-50': '#e1f5fe', 'light-blue-100': '#b3e5fc', 'light-blue-200': '#81d4fa', 'light-blue-300': '#4fc3f7', 'light-blue-400': '#29b6f6', 'light-blue-500': '#03a9f4', 'light-blue-600': '#039be5', 'light-blue-700': '#0288d1', 'light-blue-800': '#0277bd', 'light-blue-900': '#01579b', 'light-blue-a100': '#80d8ff', 'light-blue-a200': '#40c4ff', 'light-blue-a400': '#00b0ff', 'light-blue-a700': '#0091ea', 'cyan-50': '#e0f7fa', 'cyan-100': '#b2ebf2', 'cyan-200': '#80deea', 'cyan-300': '#4dd0e1', 'cyan-400': '#26c6da', 'cyan-500': '#00bcd4', 'cyan-600': '#00acc1', 'cyan-700': '#0097a7', 'cyan-800': '#00838f', 'cyan-900': '#006064', 'cyan-a100': '#84ffff', 'cyan-a200': '#18ffff', 'cyan-a400': '#00e5ff', 'cyan-a700': '#00b8d4', 'teal-50': '#e0f2f1', 'teal-100': '#b2dfdb', 'teal-200': '#80cbc4', 'teal-300': '#4db6ac', 'teal-400': '#26a69a', 'teal-500': '#009688', 'teal-600': '#00897b', 'teal-700': '#00796b', 'teal-800': '#00695c', 'teal-900': '#004d40', 'teal-a100': '#a7ffeb', 'teal-a200': '#64ffda', 'teal-a400': '#1de9b6', 'teal-a700': '#00bfa5', 'green-50': '#e8f5e9', 'green-100': '#c8e6c9', 'green-200': '#a5d6a7', 'green-300': '#81c784', 'green-400': '#66bb6a', 'green-500': '#4caf50', 'green-600': '#43a047', 'green-700': '#388e3c', 'green-800': '#2e7d32', 'green-900': '#1b5e20', 'green-a100': '#b9f6ca', 'green-a200': '#69f0ae', 'green-a400': '#00e676', 'green-a700': '#00c853', 'light-green-50': '#f1f8e9', 'light-green-100': '#dcedc8', 'light-green-200': '#c5e1a5', 'light-green-300': '#aed581', 'light-green-400': '#9ccc65', 'light-green-500': '#8bc34a', 'light-green-600': '#7cb342', 'light-green-700': '#689f38', 'light-green-800': '#558b2f', 'light-green-900': '#33691e', 'light-green-a100': '#ccff90', 'light-green-a200': '#b2ff59', 'light-green-a400': '#76ff03', 'light-green-a700': '#64dd17', 'lime-50': '#f9fbe7', 'lime-100': '#f0f4c3', 'lime-200': '#e6ee9c', 'lime-300': '#dce775', 'lime-400': '#d4e157', 'lime-500': '#cddc39', 'lime-600': '#c0ca33', 'lime-700': '#afb42b', 'lime-800': '#9e9d24', 'lime-900': '#827717', 'lime-a100': '#f4ff81', 'lime-a200': '#eeff41', 'lime-a400': '#c6ff00', 'lime-a700': '#aeea00', 'yellow-50': '#fffde7', 'yellow-100': '#fff9c4', 'yellow-200': '#fff59d', 'yellow-300': '#fff176', 'yellow-400': '#ffee58', 'yellow-500': '#ffeb3b', 'yellow-600': '#fdd835', 'yellow-700': '#fbc02d', 'yellow-800': '#f9a825', 'yellow-900': '#f57f17', 'yellow-a100': '#ffff8d', 'yellow-a200': '#ffff00', 'yellow-a400': '#ffea00', 'yellow-a700': '#ffd600', 'amber-50': '#fff8e1', 'amber-100': '#ffecb3', 'amber-200': '#ffe082', 'amber-300': '#ffd54f', 'amber-400': '#ffca28', 'amber-500': '#ffc107', 'amber-600': '#ffb300', 'amber-700': '#ffa000', 'amber-800': '#ff8f00', 'amber-900': '#ff6f00', 'amber-a100': '#ffe57f', 'amber-a200': '#ffd740', 'amber-a400': '#ffc400', 'amber-a700': '#ffab00', 'orange-50': '#fff3e0', 'orange-100': '#ffe0b2', 'orange-200': '#ffcc80', 'orange-300': '#ffb74d', 'orange-400': '#ffa726', 'orange-500': '#ff9800', 'orange-600': '#fb8c00', 'orange-700': '#f57c00', 'orange-800': '#ef6c00', 'orange-900': '#e65100', 'orange-a100': '#ffd180', 'orange-a200': '#ffab40', 'orange-a400': '#ff9100', 'orange-a700': '#ff6d00', 'deep-orange-50': '#fbe9e7', 'deep-orange-100': '#ffccbc', 'deep-orange-200': '#ffab91', 'deep-orange-300': '#ff8a65', 'deep-orange-400': '#ff7043', 'deep-orange-500': '#ff5722', 'deep-orange-600': '#f4511e', 'deep-orange-700': '#e64a19', 'deep-orange-800': '#d84315', 'deep-orange-900': '#bf360c', 'deep-orange-a100': '#ff9e80', 'deep-orange-a200': '#ff6e40', 'deep-orange-a400': '#ff3d00', 'deep-orange-a700': '#dd2c00', 'brown-50': '#efebe9', 'brown-100': '#d7ccc8', 'brown-200': '#bcaaa4', 'brown-300': '#a1887f', 'brown-400': '#8d6e63', 'brown-500': '#795548', 'brown-600': '#6d4c41', 'brown-700': '#5d4037', 'brown-800': '#4e342e', 'brown-900': '#3e2723', 'grey-50': '#fafafa', 'grey-100': '#f5f5f5', 'grey-200': '#eeeeee', 'grey-300': '#e0e0e0', 'grey-400': '#bdbdbd', 'grey-500': '#9e9e9e', 'grey-600': '#757575', 'grey-700': '#616161', 'grey-800': '#424242', 'grey-900': '#212121', 'blue-grey-50': '#eceff1', 'blue-grey-100': '#cfd8dc', 'blue-grey-200': '#b0bec5', 'blue-grey-300': '#90a4ae', 'blue-grey-400': '#78909c', 'blue-grey-500': '#607d8b', 'blue-grey-600': '#546e7a', 'blue-grey-700': '#455a64', 'blue-grey-800': '#37474f', 'blue-grey-900': '#263238' }; const GREYS = { 'grey-100': '#f9fafb', 'grey-200': '#f2f3f5', 'grey-300': '#e6eaf0', 'grey-400': '#d3d9e3', 'grey-500': '#b9c2d0', 'grey-600': '#7c8695', 'grey-700': '#72777a', 'grey-800': '#565a5c', 'grey-900': '#313435' }; ;// ../assets/scripts/components/Chart.js /** * Modern Chart Component * Replaces jQuery Sparkline with Chart.js */ // Register Chart.js components dist_chart/* Chart */.t1.register(...dist_chart/* registerables */.$L); class ChartComponent { constructor() { this.charts = new Map(); // Store chart instances this.debounceTimer = null; this.init(); } init() { // Only disable resizing for small sparkline charts this.createSparklines(); this.createOtherCharts(); this.setupResizeHandler(); } /** * Create sparklines (only for dashboard page) */ createSparklines() { // Only create sparklines if we're on a page that has them const sparklineExists = document.getElementById('sparklinedash'); if (!sparklineExists) { return; } const sparklineConfigs = [{ id: 'sparklinedash', data: [0, 5, 6, 10, 9, 12, 4, 9], color: '#4caf50' }, { id: 'sparklinedash2', data: [0, 5, 6, 10, 9, 12, 4, 9], color: '#9675ce' }, { id: 'sparklinedash3', data: [0, 5, 6, 10, 9, 12, 4, 9], color: '#03a9f3' }, { id: 'sparklinedash4', data: [0, 5, 6, 10, 9, 12, 4, 9], color: '#f96262' }]; sparklineConfigs.forEach(config => { // Only create if the target element exists if (document.getElementById(config.id)) { this.createSparklineChart(config); } }); } /** * Create sparkline chart from configuration */ createSparklineChart(_ref) { let { id, data, color } = _ref; let canvas = document.getElementById(id); // Only proceed if we have a valid target element if (!canvas) { return; } // If element exists but isn't a canvas, replace it with canvas if (canvas.tagName !== 'CANVAS') { const parent = canvas.parentNode; if (!parent) { return; } // Create new canvas element const newCanvas = document.createElement('canvas'); newCanvas.id = id; newCanvas.width = 100; newCanvas.height = 20; newCanvas.style.width = '100px'; newCanvas.style.height = '20px'; // Replace the span with canvas parent.replaceChild(newCanvas, canvas); canvas = newCanvas; } else { // Set canvas dimensions to match original sparkline canvas.width = 100; canvas.height = 20; canvas.style.width = '100px'; canvas.style.height = '20px'; } const ctx = canvas.getContext('2d'); const chart = new dist_chart/* Chart */.t1(ctx, { type: 'bar', data: { labels: data.map((_, i) => i), datasets: [{ data, backgroundColor: color, borderColor: color, borderWidth: 0, barPercentage: 0.6, categoryPercentage: 0.8 }] }, options: { responsive: false, maintainAspectRatio: false, animation: false, events: [], onResize: null, scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: false } }, elements: { bar: { borderRadius: 1 } } } }); this.charts.set(id, chart); } /** * Create other chart types (only if they exist on the page) */ createOtherCharts() { // Determine if we're on the dashboard or charts page const isChartsPage = document.getElementById('area-chart') !== null; const isDashboard = !isChartsPage && document.getElementById('line-chart') !== null; // Create Monthly Stats chart with enhanced dual-line data (dashboard only) if (isDashboard) { this.createMonthlyStatsChart(); } // Charts page specific charts (only on charts page) if (isChartsPage) { this.createChartsPageCharts(); } // Only create charts if their target elements exist if (document.getElementById('sparkline')) { this.createLineChart('sparkline', [5, 6, 7, 9, 9, 5, 3, 2, 2, 4, 6, 7]); } if (document.getElementById('compositebar')) { this.createCompositeChart('compositebar', [4, 1, 5, 7, 9, 9, 8, 7, 6, 6, 4, 7, 8, 4, 3, 2, 2, 5, 6, 7]); } // Regular sparklines with custom colors (only on pages that have them) this.createCustomSparklines(); // Easy Pie Charts (only if they exist) this.createEasyPieCharts(); } /** * Create enhanced Monthly Stats chart with dual lines and more data */ createMonthlyStatsChart() { const canvas = document.getElementById('line-chart'); if (!canvas) return; const ctx = canvas.getContext('2d'); // Enhanced data for monthly stats const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const salesData = [120, 135, 145, 165, 180, 195, 210, 225, 240, 220, 200, 185]; const profitData = [45, 52, 58, 62, 68, 75, 82, 88, 92, 85, 78, 72]; const chart = new dist_chart/* Chart */.t1(ctx, { type: 'line', data: { labels: months, datasets: [{ label: 'Sales ($K)', data: salesData, borderColor: '#4caf50', backgroundColor: 'rgba(76, 175, 80, 0.1)', borderWidth: 3, pointRadius: 5, pointHoverRadius: 7, pointBackgroundColor: '#4caf50', pointBorderColor: '#ffffff', pointBorderWidth: 2, tension: 0.4, fill: false }, { label: 'Profit ($K)', data: profitData, borderColor: '#2196f3', backgroundColor: 'rgba(33, 150, 243, 0.1)', borderWidth: 3, pointRadius: 5, pointHoverRadius: 7, pointBackgroundColor: '#2196f3', pointBorderColor: '#ffffff', pointBorderWidth: 2, tension: 0.4, fill: false }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top', labels: { padding: 20, font: { size: 12, weight: '600' } } }, tooltip: { enabled: true, cornerRadius: 8, displayColors: true, intersect: false, mode: 'index', callbacks: { label(context) { return `${context.dataset.label}: $${context.parsed.y}K`; } } } }, scales: { x: { grid: { display: false }, ticks: { font: { size: 11 } } }, y: { beginAtZero: true, grid: { borderDash: [5, 5] }, ticks: { font: { size: 11 }, callback(value) { return `$${value}K`; } } } }, interaction: { intersect: false, mode: 'index' } } }); this.charts.set('line-chart', chart); } /** * Create line chart (only if target exists) */ createLineChart(id, data) { let canvas = document.getElementById(id); // Only proceed if target element exists if (!canvas) { return; } // If element exists but isn't a canvas, replace it with canvas if (canvas.tagName !== 'CANVAS') { const parent = canvas.parentNode; if (!parent) { return; } // Create new canvas element const newCanvas = document.createElement('canvas'); newCanvas.id = id; newCanvas.width = 100; newCanvas.height = 20; newCanvas.style.width = '100px'; newCanvas.style.height = '20px'; // Replace element with canvas parent.replaceChild(newCanvas, canvas); canvas = newCanvas; } else { canvas.width = 100; canvas.height = 20; canvas.style.width = '100px'; canvas.style.height = '20px'; } const ctx = canvas.getContext('2d'); const chart = new dist_chart/* Chart */.t1(ctx, { type: 'line', data: { labels: data.map((_, i) => i), datasets: [{ data, borderColor: COLORS['blue-500'], backgroundColor: 'transparent', borderWidth: 1, pointRadius: 0, tension: 0.4 }] }, options: { responsive: false, maintainAspectRatio: false, animation: false, events: [], onResize: null, scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: false } } } }); this.charts.set(id, chart); } /** * Create composite chart (only if target exists) */ createCompositeChart(id, data) { let canvas = document.getElementById(id); // Only proceed if target element exists if (!canvas) { return; } // If element exists but isn't a canvas, replace it with canvas if (canvas.tagName !== 'CANVAS') { const parent = canvas.parentNode; if (!parent) { return; } // Create new canvas element const newCanvas = document.createElement('canvas'); newCanvas.id = id; newCanvas.width = 100; newCanvas.height = 20; newCanvas.style.width = '100px'; newCanvas.style.height = '20px'; // Replace element with canvas parent.replaceChild(newCanvas, canvas); canvas = newCanvas; } else { canvas.width = 100; canvas.height = 20; canvas.style.width = '100px'; canvas.style.height = '20px'; } const ctx = canvas.getContext('2d'); const chart = new dist_chart/* Chart */.t1(ctx, { type: 'bar', data: { labels: data.map((_, i) => i), datasets: [{ type: 'bar', data, backgroundColor: '#aaf', borderColor: '#aaf', borderWidth: 0 }, { type: 'line', data, borderColor: 'red', backgroundColor: 'transparent', borderWidth: 1, pointRadius: 0, tension: 0.4 }] }, options: { responsive: false, maintainAspectRatio: false, animation: false, events: [], onResize: null, scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: false } } } }); this.charts.set(id, chart); } /** * Create custom sparklines for different elements (only if they exist) */ createCustomSparklines() { const sparklineElements = document.querySelectorAll('.sparkline'); const sparkbarElements = document.querySelectorAll('.sparkbar'); const sparktriElements = document.querySelectorAll('.sparktri'); const sparkdiscElements = document.querySelectorAll('.sparkdisc'); const sparkbullElements = document.querySelectorAll('.sparkbull'); const sparkboxElements = document.querySelectorAll('.sparkbox'); // Only create if we have elements if (sparklineElements.length === 0 && sparkbarElements.length === 0 && sparktriElements.length === 0 && sparkdiscElements.length === 0 && sparkbullElements.length === 0 && sparkboxElements.length === 0) { return; } const values = [5, 4, 5, -2, 0, 3, -5, 6, 7, 9, 9, 5, -3, -2, 2, -4]; const valuesAlt = [1, 1, 0, 1, -1, -1, 1, -1, 0, 0, 1, 1]; sparklineElements.forEach((element, index) => { this.createCustomLineChart(element, values, `sparkline-${index}`); }); sparkbarElements.forEach((element, index) => { this.createCustomBarChart(element, values, `sparkbar-${index}`); }); sparktriElements.forEach((element, index) => { this.createTristateChart(element, valuesAlt, `sparktri-${index}`); }); sparkdiscElements.forEach((element, index) => { this.createDiscreteChart(element, values, `sparkdisc-${index}`); }); sparkbullElements.forEach((element, index) => { this.createBulletChart(element, values, `sparkbull-${index}`); }); sparkboxElements.forEach((element, index) => { this.createBoxChart(element, values, `sparkbox-${index}`); }); } /** * Create custom line chart for sparkline elements */ createCustomLineChart(element, data, id) { // Create canvas if it doesn't exist let canvas = element.querySelector('canvas'); if (!canvas) { canvas = document.createElement('canvas'); canvas.width = 100; canvas.height = 20; canvas.style.width = '100px'; canvas.style.height = '20px'; element.appendChild(canvas); } const ctx = canvas.getContext('2d'); const chart = new dist_chart/* Chart */.t1(ctx, { type: 'line', data: { labels: data.map((_, i) => i), datasets: [{ data, borderColor: COLORS['red-500'], backgroundColor: 'transparent', borderWidth: 2, pointRadius: 3, pointBackgroundColor: COLORS['red-500'], tension: 0.4 }] }, options: { responsive: false, maintainAspectRatio: false, animation: false, // Disable animations to prevent resize triggers events: [], // Disable all events to prevent resize onResize: null, // Explicitly disable resize callback scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: false } // Disable tooltip to prevent events } } }); this.charts.set(id, chart); } /** * Create custom bar chart for sparkbar elements */ createCustomBarChart(element, data, id) { // Create canvas if it doesn't exist let canvas = element.querySelector('canvas'); if (!canvas) { canvas = document.createElement('canvas'); canvas.width = 100; canvas.height = 20; canvas.style.width = '100px'; canvas.style.height = '20px'; element.appendChild(canvas); } const ctx = canvas.getContext('2d'); const chart = new dist_chart/* Chart */.t1(ctx, { type: 'bar', data: { labels: data.map((_, i) => i), datasets: [{ data, backgroundColor: data.map(val => val < 0 ? COLORS['deep-purple-500'] : '#39f'), borderColor: data.map(val => val < 0 ? COLORS['deep-purple-500'] : '#39f'), borderWidth: 1, barPercentage: 0.8 }] }, options: { responsive: false, maintainAspectRatio: false, scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: true, callbacks: { label: context => `${context.parsed.y}Β°Celsius` } } } } }); this.charts.set(id, chart); } /** * Setup resize handler for charts */ setupResizeHandler() { // Setup responsive resize for large charts only window.addEventListener('resize', () => { this.debounceResize(); }); // Listen for sidebar toggle events window.addEventListener('sidebar:toggle', () => { this.debounceResize(); }); } /** * Debounced resize handler */ debounceResize() { if (this.debounceTimer) { clearTimeout(this.debounceTimer); } this.debounceTimer = setTimeout(() => { this.redrawLargeChartsOnly(); }, 150); } /** * Redraw only large charts, not sparklines */ redrawLargeChartsOnly() { const largeChartIds = ['line-chart', 'area-chart', 'scatter-chart', 'bar-chart', 'doughnut-chart', 'polar-chart', 'radar-chart', 'mixed-chart', 'bubble-chart']; largeChartIds.forEach(id => { const chart = this.charts.get(id); if (chart && chart.options.responsive) { chart.resize(); } }); } /** * Redraw all charts (used sparingly) */ redrawCharts() { this.charts.forEach(chart => { if (chart.options.responsive) { chart.resize(); } }); } /** * Update chart data */ updateChart(id, newData) { const chart = this.charts.get(id); if (chart) { chart.data.datasets[0].data = newData; chart.update(); } } /** * Create charts for the charts.html page */ createChartsPageCharts() { // Line Chart this.createLargeChart('line-chart', 'line', { labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], datasets: [{ label: 'Dataset 1', data: [65, 59, 80, 81, 56, 55, 40], borderColor: 'rgb(75, 192, 192)', backgroundColor: 'rgba(75, 192, 192, 0.2)', tension: 0.4 }] }); // Area Chart this.createLargeChart('area-chart', 'line', { labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], datasets: [{ label: 'Dataset 1', data: [65, 59, 80, 81, 56, 55, 40], borderColor: 'rgb(54, 162, 235)', backgroundColor: 'rgba(54, 162, 235, 0.4)', fill: true, tension: 0.4 }] }); // Scatter Chart with more data points this.createLargeChart('scatter-chart', 'scatter', { datasets: [{ label: 'Dataset 1', data: [{ x: -15, y: 8 }, { x: -12, y: 12 }, { x: -8, y: 3 }, { x: -5, y: 15 }, { x: -2, y: 7 }, { x: 0, y: 10 }, { x: 3, y: 18 }, { x: 6, y: 5 }, { x: 9, y: 22 }, { x: 12, y: 8 }, { x: 15, y: 14 }, { x: 18, y: 19 }, { x: -10, y: 0 }, { x: 10, y: 5 }, { x: 0.5, y: 5.5 }, { x: 7, y: 12 }, { x: -7, y: 17 }, { x: 4, y: 9 }, { x: 11, y: 16 }, { x: -3, y: 11 }], backgroundColor: 'rgba(255, 99, 132, 0.7)', borderColor: 'rgb(255, 99, 132)', borderWidth: 1 }, { label: 'Dataset 2', data: [{ x: -13, y: 4 }, { x: -9, y: 8 }, { x: -6, y: 13 }, { x: -1, y: 6 }, { x: 2, y: 11 }, { x: 5, y: 15 }, { x: 8, y: 2 }, { x: 13, y: 17 }, { x: 16, y: 9 }, { x: -4, y: 14 }, { x: 1, y: 20 }, { x: 14, y: 4 }], backgroundColor: 'rgba(54, 162, 235, 0.7)', borderColor: 'rgb(54, 162, 235)', borderWidth: 1 }] }); // Bar Chart this.createLargeChart('bar-chart', 'bar', { labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], datasets: [{ label: '# of Votes', data: [12, 19, 3, 5, 2, 3], backgroundColor: ['rgba(255, 99, 132, 0.6)', 'rgba(54, 162, 235, 0.6)', 'rgba(255, 205, 86, 0.6)', 'rgba(75, 192, 192, 0.6)', 'rgba(153, 102, 255, 0.6)', 'rgba(255, 159, 64, 0.6)'], borderColor: ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 205, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)'], borderWidth: 1 }] }); // Doughnut Chart this.createLargeChart('doughnut-chart', 'doughnut', { labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], datasets: [{ label: 'My First Dataset', data: [300, 50, 100, 75, 120, 60], backgroundColor: ['rgba(255, 99, 132, 0.8)', 'rgba(54, 162, 235, 0.8)', 'rgba(255, 205, 86, 0.8)', 'rgba(75, 192, 192, 0.8)', 'rgba(153, 102, 255, 0.8)', 'rgba(255, 159, 64, 0.8)'], borderColor: ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 205, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)'], borderWidth: 2, hoverOffset: 10 }] }); // Polar Area Chart this.createLargeChart('polar-chart', 'polarArea', { labels: ['Red', 'Green', 'Yellow', 'Grey', 'Blue'], datasets: [{ label: 'My First Dataset', data: [11, 16, 7, 3, 14], backgroundColor: ['rgba(255, 99, 132, 0.7)', 'rgba(75, 192, 192, 0.7)', 'rgba(255, 205, 86, 0.7)', 'rgba(201, 203, 207, 0.7)', 'rgba(54, 162, 235, 0.7)'], borderColor: ['rgb(255, 99, 132)', 'rgb(75, 192, 192)', 'rgb(255, 205, 86)', 'rgb(201, 203, 207)', 'rgb(54, 162, 235)'], borderWidth: 2 }] }); // Radar Chart this.createLargeChart('radar-chart', 'radar', { labels: ['Speed', 'Reliability', 'Comfort', 'Safety', 'Efficiency', 'Innovation'], datasets: [{ label: 'Product A', data: [65, 59, 90, 81, 56, 55], fill: true, backgroundColor: 'rgba(54, 162, 235, 0.2)', borderColor: 'rgb(54, 162, 235)', borderWidth: 2, pointBackgroundColor: 'rgb(54, 162, 235)', pointBorderColor: '#fff', pointHoverBackgroundColor: '#fff', pointHoverBorderColor: 'rgb(54, 162, 235)' }, { label: 'Product B', data: [28, 48, 40, 95, 86, 27], fill: true, backgroundColor: 'rgba(255, 99, 132, 0.2)', borderColor: 'rgb(255, 99, 132)', borderWidth: 2, pointBackgroundColor: 'rgb(255, 99, 132)', pointBorderColor: '#fff', pointHoverBackgroundColor: '#fff', pointHoverBorderColor: 'rgb(255, 99, 132)' }] }); // Mixed Chart (Bar + Line) this.createLargeChart('mixed-chart', 'bar', { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], datasets: [{ type: 'bar', label: 'Sales', data: [12, 19, 3, 5, 2, 3], backgroundColor: 'rgba(54, 162, 235, 0.7)', borderColor: 'rgb(54, 162, 235)', borderWidth: 1 }, { type: 'line', label: 'Revenue', data: [18, 25, 8, 15, 12, 18], fill: false, borderColor: 'rgb(255, 99, 132)', backgroundColor: 'rgba(255, 99, 132, 0.2)', borderWidth: 3, tension: 0.4, pointRadius: 5, pointHoverRadius: 7 }] }); // Bubble Chart this.createLargeChart('bubble-chart', 'bubble', { datasets: [{ label: 'First Dataset', data: [{ x: 20, y: 30, r: 15 }, { x: 40, y: 10, r: 10 }, { x: 30, y: 40, r: 20 }, { x: 50, y: 35, r: 12 }, { x: 10, y: 50, r: 8 }, { x: 60, y: 20, r: 18 }, { x: 25, y: 25, r: 14 }], backgroundColor: 'rgba(54, 162, 235, 0.6)', borderColor: 'rgb(54, 162, 235)', borderWidth: 2 }, { label: 'Second Dataset', data: [{ x: 15, y: 45, r: 12 }, { x: 35, y: 15, r: 16 }, { x: 45, y: 25, r: 9 }, { x: 55, y: 45, r: 14 }, { x: 25, y: 35, r: 11 }], backgroundColor: 'rgba(255, 99, 132, 0.6)', borderColor: 'rgb(255, 99, 132)', borderWidth: 2 }] }); } /** * Create large chart for charts page */ createLargeChart(id, type, data) { const canvas = document.getElementById(id); if (!canvas) return; const ctx = canvas.getContext('2d'); // Define chart-specific options const chartOptions = this.getChartOptions(type); const chart = new dist_chart/* Chart */.t1(ctx, { type, data, options: chartOptions }); this.charts.set(id, chart); } /** * Get chart-specific options based on chart type */ getChartOptions(type) { const baseOptions = { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top', labels: { padding: 20, font: { size: 12, weight: '600' } } }, tooltip: { enabled: true, cornerRadius: 8, displayColors: true } } }; // Chart type specific configurations switch (type) { case 'doughnut': case 'pie': return { ...baseOptions, plugins: { ...baseOptions.plugins, legend: { ...baseOptions.plugins.legend, position: 'right' } }, interaction: { intersect: false } }; case 'polarArea': return { ...baseOptions, scales: { r: { pointLabels: { display: true, centerPointLabels: true, font: { size: 10 } }, grid: {} } } }; case 'radar': return { ...baseOptions, scales: { r: { angleLines: { display: true }, grid: {}, pointLabels: { font: { size: 11 } }, ticks: { display: true, font: { size: 10 } } } } }; case 'bubble': return { ...baseOptions, scales: { x: { type: 'linear', position: 'bottom', grid: { borderDash: [5, 5] }, ticks: { font: { size: 11 } } }, y: { beginAtZero: true, grid: { borderDash: [5, 5] }, ticks: { font: { size: 11 } } } }, plugins: { ...baseOptions.plugins, tooltip: { ...baseOptions.plugins.tooltip, callbacks: { label(context) { return `${context.dataset.label}: (${context.parsed.x}, ${context.parsed.y}), Size: ${context.parsed._custom}`; } } } } }; case 'scatter': return { ...baseOptions, scales: { x: { type: 'linear', position: 'bottom', grid: { borderDash: [5, 5] }, ticks: { font: { size: 11 } } }, y: { grid: { borderDash: [5, 5] }, ticks: { font: { size: 11 } } } } }; default: // For line, bar, area, mixed charts return { ...baseOptions, scales: { x: { grid: { borderDash: [5, 5] }, ticks: { font: { size: 11 } } }, y: { beginAtZero: true, grid: { borderDash: [5, 5] }, ticks: { font: { size: 11 } } } } }; } } /** * Create tristate chart (for .sparktri elements) */ createTristateChart(element, data, id) { let canvas = element.querySelector('canvas'); if (!canvas) { canvas = document.createElement('canvas'); canvas.width = 100; canvas.height = 20; canvas.style.width = '100px'; canvas.style.height = '20px'; element.appendChild(canvas); } const ctx = canvas.getContext('2d'); const chart = new dist_chart/* Chart */.t1(ctx, { type: 'bar', data: { labels: data.map((_, i) => i), datasets: [{ data: data.map(val => Math.abs(val)), backgroundColor: data.map(val => { if (val > 0) return COLORS['light-blue-500']; if (val < 0) return '#f90'; return '#000'; }), borderColor: data.map(val => { if (val > 0) return COLORS['light-blue-500']; if (val < 0) return '#f90'; return '#000'; }), borderWidth: 1, barPercentage: 0.8 }] }, options: { responsive: false, maintainAspectRatio: false, scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: true, callbacks: { label: context => `${context.parsed.y}Β°Celsius` } } } } }); this.charts.set(id, chart); } /** * Create discrete chart (for .sparkdisc elements) */ createDiscreteChart(element, data, id) { let canvas = element.querySelector('canvas'); if (!canvas) { canvas = document.createElement('canvas'); canvas.width = 100; canvas.height = 20; canvas.style.width = '100px'; canvas.style.height = '20px'; element.appendChild(canvas); } const ctx = canvas.getContext('2d'); const chart = new dist_chart/* Chart */.t1(ctx, { type: 'scatter', data: { datasets: [{ data: data.map((val, index) => ({ x: index, y: val })), backgroundColor: '#9f0', borderColor: '#9f0', pointRadius: 2, showLine: false }] }, options: { responsive: false, maintainAspectRatio: false, scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: true, callbacks: { label: context => `${context.parsed.y}Β°Celsius` } } } } }); this.charts.set(id, chart); } /** * Create bullet chart (for .sparkbull elements) */ createBulletChart(element, data, id) { let canvas = element.querySelector('canvas'); if (!canvas) { canvas = document.createElement('canvas'); canvas.width = 100; canvas.height = 20; canvas.style.width = '100px'; canvas.style.height = '20px'; element.appendChild(canvas); } const ctx = canvas.getContext('2d'); // Simplified bullet chart as horizontal bar const chart = new dist_chart/* Chart */.t1(ctx, { type: 'bar', data: { labels: [''], datasets: [{ data: [Math.max(...data)], backgroundColor: COLORS['amber-500'], borderColor: COLORS['amber-500'], borderWidth: 1, barPercentage: 0.6 }] }, options: { responsive: false, maintainAspectRatio: false, indexAxis: 'y', scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: true, callbacks: { label: context => `${context.parsed.x}Β°Celsius` } } } } }); this.charts.set(id, chart); } /** * Create box chart (for .sparkbox elements) */ createBoxChart(element, data, id) { let canvas = element.querySelector('canvas'); if (!canvas) { canvas = document.createElement('canvas'); canvas.width = 100; canvas.height = 20; canvas.style.width = '100px'; canvas.style.height = '20px'; element.appendChild(canvas); } const ctx = canvas.getContext('2d'); // Box plot simplified as bar chart showing quartiles const sortedData = [...data].sort((a, b) => a - b); const q1 = sortedData[Math.floor(sortedData.length * 0.25)]; const median = sortedData[Math.floor(sortedData.length * 0.5)]; const q3 = sortedData[Math.floor(sortedData.length * 0.75)]; const chart = new dist_chart/* Chart */.t1(ctx, { type: 'bar', data: { labels: ['Q1', 'Med', 'Q3'], datasets: [{ data: [q1, median, q3], backgroundColor: '#9f0', borderColor: '#9f0', borderWidth: 1, barPercentage: 0.8 }] }, options: { responsive: false, maintainAspectRatio: false, scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: true, callbacks: { label: context => `${context.parsed.y}Β°Celsius` } } } } }); this.charts.set(id, chart); } /** * Create Easy Pie Charts (replaces jQuery Easy Pie Chart) */ createEasyPieCharts() { const easyPieElements = document.querySelectorAll('.easy-pie-chart'); easyPieElements.forEach((element, index) => { const size = parseInt(element.dataset.size) || 80; const percent = parseInt(element.dataset.percent) || 0; const barColor = element.dataset.barColor || '#f44336'; // Create canvas for the pie chart let canvas = element.querySelector('canvas'); if (!canvas) { canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; canvas.style.width = `${size}px`; canvas.style.height = `${size}px`; element.appendChild(canvas); } // Create percentage display const percentDisplay = element.querySelector('span'); if (percentDisplay) { percentDisplay.textContent = `${percent}%`; percentDisplay.style.position = 'absolute'; percentDisplay.style.top = '50%'; percentDisplay.style.left = '50%'; percentDisplay.style.transform = 'translate(-50%, -50%)'; percentDisplay.style.fontSize = '14px'; percentDisplay.style.fontWeight = 'bold'; } // Set element position to relative for absolute positioning of text element.style.position = 'relative'; element.style.display = 'inline-block'; const ctx = canvas.getContext('2d'); const chart = new dist_chart/* Chart */.t1(ctx, { type: 'doughnut', data: { datasets: [{ data: [percent, 100 - percent], backgroundColor: [barColor, '#f0f0f0'], borderWidth: 0, cutout: '70%' }] }, options: { responsive: false, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { enabled: false } } } }); this.charts.set(`easy-pie-${index}`, chart); }); } /** * Destroy all charts */ destroy() { this.charts.forEach(chart => { chart.destroy(); }); this.charts.clear(); if (this.debounceTimer) { clearTimeout(this.debounceTimer); } } } /* harmony default export */ const components_Chart = (ChartComponent); // EXTERNAL MODULE: ../../node_modules/@fullcalendar/core/index.js var core = __webpack_require__(932); // EXTERNAL MODULE: ../../node_modules/@fullcalendar/interaction/index.js var interaction = __webpack_require__(453); // EXTERNAL MODULE: ../../node_modules/@fullcalendar/daygrid/index.js var daygrid = __webpack_require__(699); // EXTERNAL MODULE: ../../node_modules/@fullcalendar/timegrid/index.js + 1 modules var timegrid = __webpack_require__(747); // EXTERNAL MODULE: ../../node_modules/@fullcalendar/list/index.js + 1 modules var list = __webpack_require__(408); ;// ../assets/scripts/fullcalendar/index.js document.addEventListener('DOMContentLoaded', function () { const calendarEl = document.getElementById('calendar'); // element found in dom ? if (calendarEl == null) { return; } const calendar = new core/* Calendar */.Vv(calendarEl, { plugins: [interaction/* default */.Ay, daygrid/* default */.A, timegrid/* default */.A, list/* default */.A], headerToolbar: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek' }, initialDate: date.format(date.now(), 'YYYY-MM-DD'), navLinks: true, // can click day/week names to navigate views editable: true, dayMaxEvents: true, // allow "more" link when too many events events: [{ title: 'All Day Event', start: date.format(date.now(), 'YYYY-MM-DD') }, { title: 'Long Event', start: date.format(date.add(date.now(), 1, 'day'), 'YYYY-MM-DD'), end: date.format(date.add(date.now(), 4, 'day'), 'YYYY-MM-DD') }, { groupId: 999, title: 'Repeating Event', start: date.format(date.add(date.now(), 2, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T16:00') }, { groupId: 999, title: 'Repeating Event', start: date.format(date.add(date.now(), 9, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T16:00') }, { title: 'Conference', start: date.format(date.add(date.now(), 5, 'day'), 'YYYY-MM-DD'), end: date.format(date.add(date.now(), 7, 'day'), 'YYYY-MM-DD') }, { title: 'Meeting', start: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T10:30'), end: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T12:30') }, { title: 'Lunch', start: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T12:00') }, { title: 'Meeting', start: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T14:30') }, { title: 'Happy Hour', start: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T17:30') }, { title: 'Dinner', start: date.format(date.add(date.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T20:00') }, { title: 'Birthday Party', start: date.format(date.add(date.now(), 4, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T07:00') }, { title: 'Click for Google', url: 'http://google.com/', start: date.format(date.add(date.now(), 14, 'day'), 'YYYY-MM-DD') }] }); calendar.render(); }); // EXTERNAL MODULE: ../../node_modules/masonry-layout/masonry.js var masonry = __webpack_require__(201); var masonry_default = /*#__PURE__*/__webpack_require__.n(masonry); ;// ../assets/scripts/masonry/index.js /* harmony default export */ const scripts_masonry = ((function () { window.addEventListener('load', () => { const masonryElement = document.querySelector('.masonry'); if (masonryElement) { new (masonry_default())(masonryElement, { itemSelector: '.masonry-item', columnWidth: '.masonry-sizer', percentPosition: true }); } }); })()); ;// ../assets/scripts/popover/index.js // Simple vanilla JS tooltip and popover implementation /* harmony default export */ const popover = ((function () { // Simple tooltip implementation function initTooltips() { const tooltipElements = document.querySelectorAll('[data-bs-toggle="tooltip"]'); tooltipElements.forEach(element => { const tooltipText = element.getAttribute('data-bs-title') || element.getAttribute('title'); if (tooltipText) { element.addEventListener('mouseenter', function () { const tooltip = document.createElement('div'); tooltip.className = 'custom-tooltip'; tooltip.textContent = tooltipText; tooltip.style.cssText = ` position: absolute; background: #000; color: #fff; padding: 4px 8px; border-radius: 4px; font-size: 12px; z-index: 1050; pointer-events: none; white-space: nowrap; `; document.body.appendChild(tooltip); const rect = element.getBoundingClientRect(); tooltip.style.left = `${rect.left + rect.width / 2 - tooltip.offsetWidth / 2}px`; tooltip.style.top = `${rect.top - tooltip.offsetHeight - 5}px`; element._tooltip = tooltip; }); element.addEventListener('mouseleave', function () { if (element._tooltip) { element._tooltip.remove(); element._tooltip = null; } }); } }); } // Simple popover implementation function initPopovers() { const popoverElements = document.querySelectorAll('[data-bs-toggle="popover"]'); popoverElements.forEach(element => { const popoverContent = element.getAttribute('data-bs-content'); const popoverTitle = element.getAttribute('data-bs-title'); if (popoverContent) { element.addEventListener('click', function (e) { e.preventDefault(); // Remove existing popover if (element._popover) { element._popover.remove(); element._popover = null; return; } const popover = document.createElement('div'); popover.className = 'custom-popover'; popover.innerHTML = ` ${popoverTitle ? `
${popoverTitle}
` : ''}
${popoverContent}
`; popover.style.cssText = ` position: absolute; background: #fff; border: 1px solid #ccc; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); z-index: 1050; min-width: 200px; max-width: 300px; `; document.body.appendChild(popover); const rect = element.getBoundingClientRect(); popover.style.left = `${rect.left}px`; popover.style.top = `${rect.bottom + 5}px`; element._popover = popover; }); } }); } // Initialize both initTooltips(); initPopovers(); // Close popovers when clicking outside document.addEventListener('click', function (e) { const popovers = document.querySelectorAll('.custom-popover'); popovers.forEach(popover => { if (!popover.contains(e.target)) { popover.remove(); } }); }); })()); // EXTERNAL MODULE: ../../node_modules/perfect-scrollbar/dist/perfect-scrollbar.esm.js var perfect_scrollbar_esm = __webpack_require__(530); ;// ../assets/scripts/scrollbar/index.js /* harmony default export */ const scrollbar = ((function () { const scrollables = document.querySelectorAll('.scrollable'); if (scrollables.length > 0) { scrollables.forEach(el => { new perfect_scrollbar_esm/* default */.A(el); }); } })()); ;// ../assets/scripts/search/index.js /* harmony default export */ const search = ((function () { const searchToggle = document.querySelector('.search-toggle'); const searchBox = document.querySelector('.search-box'); const searchInput = document.querySelector('.search-input'); const searchInputField = document.querySelector('.search-input input'); if (searchToggle && searchBox && searchInput && searchInputField) { searchToggle.addEventListener('click', e => { searchBox.classList.toggle('active'); searchInput.classList.toggle('active'); searchInputField.focus(); e.preventDefault(); }); } })()); // EXTERNAL MODULE: ../../node_modules/skycons/skycons.js var skycons = __webpack_require__(658); var skycons_default = /*#__PURE__*/__webpack_require__.n(skycons); ;// ../assets/scripts/skycons/index.js const Skycons = skycons_default()(window); /* harmony default export */ const scripts_skycons = ((function () { let icons; const initSkycons = () => { const skyconsColor = theme.getCSSVar('--skycons-color'); if (icons) { icons.pause(); icons.remove('all'); } icons = new Skycons({ 'color': skyconsColor }); const list = ['clear-day', 'clear-night', 'partly-cloudy-day', 'partly-cloudy-night', 'cloudy', 'rain', 'sleet', 'snow', 'wind', 'fog']; let i = list.length; while (i--) { const weatherType = list[i], elements = document.getElementsByClassName(weatherType); let j = elements.length; while (j--) { icons.set(elements[j], weatherType); } } icons.play(); }; // Initialize skycons initSkycons(); // Listen for theme changes window.addEventListener('adminator:themeChanged', initSkycons); })()); // EXTERNAL MODULE: ../../node_modules/jsvectormap/dist/jsvectormap.esm.js var jsvectormap_esm = __webpack_require__(82); // EXTERNAL MODULE: ../../node_modules/jsvectormap/dist/maps/world.js var world = __webpack_require__(177); ;// ../assets/scripts/utils/storage.js /** * Adminator Storage Utilities * Safe localStorage wrapper with error handling * * @module utils/storage */ /** * Check if localStorage is available * @returns {boolean} */ const isAvailable = () => { try { const test = '__storage_test__'; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch { return false; } }; /** * In-memory fallback when localStorage is unavailable * @type {Map} */ const memoryStorage = new Map(); /** * Storage utility with safe localStorage access * Falls back to in-memory storage when localStorage is unavailable * (e.g., private browsing, storage quota exceeded) * * @namespace */ const storage_Storage = { /** * Check if storage is available * @returns {boolean} */ isAvailable, /** * Get an item from storage * * @param {string} key - Storage key * @returns {string|null} Stored value or null * * @example * const theme = Storage.get('theme'); */ get(key) { try { if (isAvailable()) { return localStorage.getItem(key); } return memoryStorage.get(key) ?? null; } catch { return memoryStorage.get(key) ?? null; } }, /** * Set an item in storage * * @param {string} key - Storage key * @param {string} value - Value to store * @returns {boolean} Success status * * @example * Storage.set('theme', 'dark'); */ set(key, value) { try { if (isAvailable()) { localStorage.setItem(key, value); return true; } memoryStorage.set(key, value); return true; } catch { // Fallback to memory storage memoryStorage.set(key, value); return true; } }, /** * Remove an item from storage * * @param {string} key - Storage key * @returns {boolean} Success status * * @example * Storage.remove('theme'); */ remove(key) { try { if (isAvailable()) { localStorage.removeItem(key); } memoryStorage.delete(key); return true; } catch { memoryStorage.delete(key); return true; } }, /** * Clear all storage * * @returns {boolean} Success status * * @example * Storage.clear(); */ clear() { try { if (isAvailable()) { localStorage.clear(); } memoryStorage.clear(); return true; } catch { memoryStorage.clear(); return true; } }, /** * Get a JSON object from storage * * @param {string} key - Storage key * @param {*} [defaultValue=null] - Default value if key doesn't exist or parse fails * @returns {*} Parsed object or default value * * @example * const settings = Storage.getJSON('settings', { theme: 'light' }); */ getJSON(key) { let defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; try { const value = this.get(key); if (value === null) { return defaultValue; } return JSON.parse(value); } catch { return defaultValue; } }, /** * Set a JSON object in storage * * @param {string} key - Storage key * @param {*} value - Value to store (will be JSON stringified) * @returns {boolean} Success status * * @example * Storage.setJSON('settings', { theme: 'dark', sidebar: 'expanded' }); */ setJSON(key, value) { try { return this.set(key, JSON.stringify(value)); } catch { return false; } }, /** * Check if a key exists in storage * * @param {string} key - Storage key * @returns {boolean} * * @example * if (Storage.has('theme')) { * // Use stored theme * } */ has(key) { return this.get(key) !== null; }, /** * Get all keys in storage * * @returns {string[]} Array of keys * * @example * const keys = Storage.keys(); */ keys() { try { if (isAvailable()) { return Object.keys(localStorage); } return Array.from(memoryStorage.keys()); } catch { return Array.from(memoryStorage.keys()); } }, /** * Get storage size in bytes (approximate) * * @returns {number} Size in bytes * * @example * console.log(`Storage used: ${Storage.size()} bytes`); */ size() { try { let total = 0; const keys = this.keys(); for (const key of keys) { const value = this.get(key); if (value) { total += key.length + value.length; } } return total * 2; // UTF-16 uses 2 bytes per character } catch { return 0; } } }; /* harmony default export */ const storage = ((/* unused pure expression or super */ null && (storage_Storage))); ;// ../assets/scripts/utils/sanitize.js /** * Adminator Sanitization Utilities * HTML and input sanitization for security * * @module utils/sanitize */ /** * HTML entities map for encoding * @type {Object} */ const HTML_ENTITIES = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' }; /** * Sanitization utilities namespace * @namespace */ const Sanitize = { /** * Escape HTML entities to prevent XSS * * @param {string} str - String to escape * @returns {string} Escaped string safe for HTML insertion * * @example * const safe = Sanitize.html(''); * // Returns: <script>alert("xss")</script> */ html(str) { if (typeof str !== 'string') { return String(str ?? ''); } return str.replace(/[&<>"'`=/]/g, char => HTML_ENTITIES[char]); }, /** * Escape string for use in HTML attributes * * @param {string} str - String to escape * @returns {string} Escaped string safe for attribute values * * @example * element.setAttribute('data-name', Sanitize.attr(userInput)); */ attr(str) { return this.html(str); }, /** * Sanitize URL to prevent javascript: and data: URLs * * @param {string} url - URL to sanitize * @param {string[]} [allowedProtocols=['http:', 'https:', 'mailto:', 'tel:']] - Allowed protocols * @returns {string} Sanitized URL or empty string if invalid * * @example * const safeUrl = Sanitize.url(userProvidedUrl); * if (safeUrl) { * window.location.href = safeUrl; * } */ url(url) { let allowedProtocols = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['http:', 'https:', 'mailto:', 'tel:']; if (typeof url !== 'string') { return ''; } // Trim and normalize const trimmed = url.trim().toLowerCase(); // Block dangerous protocols const dangerousProtocols = ['javascript:', 'data:', 'vbscript:']; for (const protocol of dangerousProtocols) { if (trimmed.startsWith(protocol)) { return ''; } } // Check for allowed protocols try { const parsed = new URL(url, window.location.origin); if (!allowedProtocols.includes(parsed.protocol)) { // Allow relative URLs if (!url.startsWith('/') && !url.startsWith('./') && !url.startsWith('../')) { return ''; } } return url; } catch { // If URL parsing fails, check if it's a relative URL if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../') || url.startsWith('#')) { return url; } return ''; } }, /** * Strip HTML tags from a string * * @param {string} str - String with HTML * @returns {string} String with HTML tags removed * * @example * const text = Sanitize.stripTags('

Hello World

'); * // Returns: 'Hello World' */ stripTags(str) { if (typeof str !== 'string') { return String(str ?? ''); } // Create a temporary element to leverage browser's HTML parser const div = document.createElement('div'); div.innerHTML = str; return div.textContent || div.innerText || ''; }, /** * Sanitize a string for use in CSS * * @param {string} str - String to sanitize * @returns {string} CSS-safe string * * @example * element.style.setProperty('--custom', Sanitize.css(userInput)); */ css(str) { if (typeof str !== 'string') { return ''; } // Remove potentially dangerous CSS values return str.replace(/expression\s*\(/gi, '').replace(/url\s*\(/gi, '').replace(/javascript:/gi, '').replace(/[<>"']/g, ''); }, /** * Sanitize a string for use in a filename * * @param {string} str - String to sanitize * @returns {string} Filename-safe string * * @example * const filename = Sanitize.filename(userInput) + '.txt'; */ filename(str) { if (typeof str !== 'string') { return ''; } return str.replace(/[/\\?%*:|"<>]/g, '-') // Replace dangerous characters .replace(/\.\./g, '-') // Prevent directory traversal .replace(/^\./, '_') // Don't start with dot .slice(0, 255); // Limit length }, /** * Create safe innerHTML by escaping interpolated values * * @param {TemplateStringsArray} strings - Template literal strings * @param {...*} values - Values to interpolate * @returns {string} HTML string with escaped values * * @example * element.innerHTML = Sanitize.template`
${userInput}
`; */ template(strings) { for (var _len = arguments.length, values = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { values[_key - 1] = arguments[_key]; } return strings.reduce((result, str, i) => { const value = values[i - 1]; const escaped = value !== undefined ? this.html(String(value)) : ''; return result + escaped + str; }); }, /** * Validate and sanitize an email address * * @param {string} email - Email to validate * @returns {string} Sanitized email or empty string if invalid * * @example * const email = Sanitize.email(userInput); * if (email) { * sendEmail(email); * } */ email(email) { if (typeof email !== 'string') { return ''; } const trimmed = email.trim().toLowerCase(); // Basic email validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(trimmed)) { return ''; } // Additional checks if (trimmed.length > 254) { return ''; } return trimmed; }, /** * Sanitize a number input * * @param {*} value - Value to sanitize * @param {Object} [options={}] - Options * @param {number} [options.min=-Infinity] - Minimum value * @param {number} [options.max=Infinity] - Maximum value * @param {number} [options.default=0] - Default if invalid * @returns {number} Sanitized number * * @example * const age = Sanitize.number(userInput, { min: 0, max: 120, default: 18 }); */ number(value) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const { min = -Infinity, max = Infinity, default: defaultValue = 0 } = options; const num = parseFloat(value); if (isNaN(num) || !isFinite(num)) { return defaultValue; } return Math.max(min, Math.min(max, num)); }, /** * Sanitize an integer input * * @param {*} value - Value to sanitize * @param {Object} [options={}] - Options * @param {number} [options.min=-Infinity] - Minimum value * @param {number} [options.max=Infinity] - Maximum value * @param {number} [options.default=0] - Default if invalid * @returns {number} Sanitized integer * * @example * const count = Sanitize.integer(userInput, { min: 1, max: 100 }); */ integer(value) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return Math.floor(this.number(value, options)); } }; /* harmony default export */ const sanitize = ((/* unused pure expression or super */ null && (Sanitize))); ;// ../assets/scripts/utils/index.js /** * Adminator Utilities Index * Re-exports all utility modules for convenient importing * * @module utils */ // Re-export all utilities // Legacy initialization code /* harmony default export */ const utils = ((function () { // ------------------------------------------------------ // @Window Resize // ------------------------------------------------------ /** * NOTE: Register resize event for Masonry layout */ const EVENT = document.createEvent('UIEvents'); window.EVENT = EVENT; EVENT.initUIEvent('resize', true, false, window, 0); window.addEventListener('load', () => { /** * Trigger window resize event after page load * for recalculation of masonry layout. */ window.dispatchEvent(EVENT); }); // ------------------------------------------------------ // @External Links // ------------------------------------------------------ // Open external links in new window const externalLinks = document.querySelectorAll('a[href^="http"], a[href^="//"]'); externalLinks.forEach(link => { const href = link.getAttribute('href'); if (href && !href.includes(window.location.host)) { link.setAttribute('rel', 'noopener noreferrer'); link.setAttribute('target', '_blank'); } }); // ------------------------------------------------------ // @Resize Trigger // ------------------------------------------------------ // Trigger resize on any element click document.addEventListener('click', () => { window.dispatchEvent(window.EVENT); }); })()); ;// ../assets/scripts/vectorMaps/index.js /* harmony default export */ const vectorMaps = ((function () { // Store map instance for cleanup let mapInstance = null; // Main initialization function const vectorMapInit = () => { const worldMapContainer = document.getElementById('world-map-marker'); if (!worldMapContainer) return; // Remove existing map const existingMap = document.getElementById('vmap'); if (existingMap) { existingMap.remove(); } // Destroy existing map instance if (mapInstance) { try { mapInstance.destroy(); } catch { // Map instance cleanup } mapInstance = null; } // Get current theme colors - using template colors directly const isDark = theme.current() === 'dark'; const colors = { backgroundColor: isDark ? '#313644' : '#f9fafb', regionColor: isDark ? '#565a5c' : '#e6eaf0', borderColor: isDark ? '#72777a' : '#d3d9e3', hoverColor: isDark ? '#7774e7' : '#0f9aee', selectedColor: isDark ? '#37c936' : '#7774e7', markerFill: isDark ? '#0f9aee' : '#7774e7', markerStroke: isDark ? '#37c936' : '#0f9aee', scaleStart: isDark ? '#b9c2d0' : '#e6eaf0', scaleEnd: isDark ? '#0f9aee' : '#007bff', textColor: isDark ? '#99abb4' : '#72777a' }; // Create new map container const mapContainer = document.createElement('div'); mapContainer.id = 'vmap'; mapContainer.style.height = '490px'; mapContainer.style.position = 'relative'; mapContainer.style.overflow = 'hidden'; mapContainer.style.backgroundColor = colors.backgroundColor; mapContainer.style.borderRadius = '8px'; mapContainer.style.border = `1px solid ${colors.borderColor}`; worldMapContainer.appendChild(mapContainer); // Initialize JSVectorMap try { mapInstance = (0,jsvectormap_esm/* default */.A)({ selector: '#vmap', map: 'world', // Styling options backgroundColor: 'transparent', // Region styling regionStyle: { initial: { fill: colors.regionColor, stroke: colors.borderColor, 'stroke-width': 1, 'stroke-opacity': 0.4 }, hover: { fill: colors.hoverColor, cursor: 'pointer' }, selected: { fill: colors.selectedColor } }, // Marker styling markerStyle: { initial: { r: 7, fill: colors.markerFill, stroke: colors.markerStroke, 'stroke-width': 2, 'stroke-opacity': 0.4 }, hover: { r: 10, fill: colors.hoverColor, 'stroke-opacity': 0.8, cursor: 'pointer' } }, // Markers data markers: [{ name: 'INDIA : 350', coords: [21.00, 78.00] }, { name: 'Australia : 250', coords: [-33.00, 151.00] }, { name: 'USA : 250', coords: [36.77, -119.41] }, { name: 'UK : 250', coords: [55.37, -3.41] }, { name: 'UAE : 250', coords: [25.20, 55.27] }], // Simplified approach - remove series for now to test base colors // series: { // regions: [ // { // attribute: 'fill', // scale: [colors.scaleStart, colors.scaleEnd], // normalizeFunction: 'polynomial', // values: { // 'US': 50, // 'SA': 30, // 'AU': 70, // 'IN': 40, // 'GB': 60, // 'LV': 80, // }, // }, // ], // }, // Interaction options zoomOnScroll: false, zoomButtons: false, // Event handlers onMarkerTooltipShow(event, tooltip, index) { // Safe access to marker data const marker = this.markers && this.markers[index]; const markerName = marker ? marker.name : `Marker ${index + 1}`; tooltip.text(markerName); }, onRegionTooltipShow(event, tooltip, code) { // Safe access to region data const regionName = this.mapData && this.mapData.paths && this.mapData.paths[code] ? this.mapData.paths[code].name || code : code; const value = this.series && this.series.regions && this.series.regions[0] && this.series.regions[0].values ? this.series.regions[0].values[code] : null; tooltip.text(`${regionName}${value ? `: ${value}` : ''}`); }, onLoaded() { // Map loaded successfully } }); // Store instance for theme updates worldMapContainer.mapInstance = mapInstance; } catch { // Error initializing JSVectorMap // Fallback: show a simple message mapContainer.innerHTML = `
πŸ—ΊοΈ
World Map
Interactive map will load here
`; } }; // Theme update function const updateMapTheme = () => { if (mapInstance) { const isDark = theme.current() === 'dark'; const colors = { backgroundColor: isDark ? '#313644' : '#f9fafb', regionColor: isDark ? '#565a5c' : '#e6eaf0', borderColor: isDark ? '#72777a' : '#d3d9e3', hoverColor: isDark ? '#7774e7' : '#0f9aee', selectedColor: isDark ? '#37c936' : '#7774e7', markerFill: isDark ? '#0f9aee' : '#7774e7', markerStroke: isDark ? '#37c936' : '#0f9aee', scaleStart: isDark ? '#b9c2d0' : '#e6eaf0', scaleEnd: isDark ? '#0f9aee' : '#007bff', textColor: isDark ? '#99abb4' : '#72777a' }; try { // Update region styles - commented out series for now // mapInstance.updateSeries('regions', { // attribute: 'fill', // scale: [colors.scaleStart, colors.scaleEnd], // values: { // 'US': 50, // 'SA': 30, // 'AU': 70, // 'IN': 40, // 'GB': 60, // 'LV': 80, // }, // }); // Update container background const container = document.getElementById('vmap'); if (container) { container.style.backgroundColor = colors.backgroundColor; } } catch { // Theme update failed, reinitializing map vectorMapInit(); } } else { vectorMapInit(); } }; // Initialize map vectorMapInit(); // Reinitialize on window resize window.addEventListener('resize', events.debounce(vectorMapInit, 300)); // Listen for theme changes window.addEventListener('adminator:themeChanged', events.debounce(updateMapTheme, 150)); // Cleanup on page unload window.addEventListener('beforeunload', () => { if (mapInstance) { try { mapInstance.destroy(); } catch { // Map cleanup on unload } mapInstance = null; } }); // Return public API return { init: vectorMapInit, updateTheme: updateMapTheme, getInstance: () => mapInstance }; })()); ;// ../assets/scripts/chat/index.js /* harmony default export */ const chat = ((function () { const chatSidebarToggle = document.getElementById('chat-sidebar-toggle'); const chatSidebar = document.getElementById('chat-sidebar'); if (chatSidebarToggle && chatSidebar) { chatSidebarToggle.addEventListener('click', e => { chatSidebar.classList.toggle('open'); e.preventDefault(); }); } })()); ;// ../assets/scripts/email/index.js /* harmony default export */ const email = ((function () { // Email side toggle functionality const emailSideToggle = document.querySelector('.email-side-toggle'); const emailApp = document.querySelector('.email-app'); if (emailSideToggle && emailApp) { emailSideToggle.addEventListener('click', e => { emailApp.classList.toggle('side-active'); e.preventDefault(); }); } // Email list item and back to mailbox functionality const emailListItems = document.querySelectorAll('.email-list-item, .back-to-mailbox'); const emailContent = document.querySelector('.email-content'); if (emailListItems.length > 0 && emailContent) { emailListItems.forEach(item => { item.addEventListener('click', e => { emailContent.classList.toggle('open'); e.preventDefault(); }); }); } })()); // EXTERNAL MODULE: ../../node_modules/load-google-maps-api/index.js var load_google_maps_api = __webpack_require__(95); var load_google_maps_api_default = /*#__PURE__*/__webpack_require__.n(load_google_maps_api); ;// ../assets/scripts/googleMaps/index.js /* harmony default export */ const googleMaps = ((function () { let map, marker; const initGoogleMap = () => { const googleMapElement = document.getElementById('google-map'); if (googleMapElement) { load_google_maps_api_default()({ key: 'AIzaSyDW8td30_gj6sGXjiMU0ALeMu1SDEwUnEA' }).then(() => { const latitude = 26.8206; const longitude = 30.8025; const mapZoom = 5; const { google } = window; const mapOptions = { center: new google.maps.LatLng(latitude, longitude), zoom: mapZoom, mapTypeId: google.maps.MapTypeId.ROADMAP, styles: [{ 'featureType': 'landscape', 'stylers': [{ 'hue': theme.getCSSVar('--gmap-landscape-hue') }, { 'saturation': 43.400000000000006 }, { 'lightness': 37.599999999999994 }, { 'gamma': 1 }] }, { 'featureType': 'road.highway', 'stylers': [{ 'hue': theme.getCSSVar('--gmap-highway-hue') }, { 'saturation': -61.8 }, { 'lightness': 45.599999999999994 }, { 'gamma': 1 }] }, { 'featureType': 'road.arterial', 'stylers': [{ 'hue': theme.getCSSVar('--gmap-road-hue') }, { 'saturation': -100 }, { 'lightness': 51.19999999999999 }, { 'gamma': 1 }] }, { 'featureType': 'road.local', 'stylers': [{ 'hue': theme.getCSSVar('--gmap-road-hue') }, { 'saturation': -100 }, { 'lightness': 52 }, { 'gamma': 1 }] }, { 'featureType': 'water', 'stylers': [{ 'hue': theme.getCSSVar('--gmap-water-hue') }, { 'saturation': -13.200000000000003 }, { 'lightness': 2.4000000000000057 }, { 'gamma': 1 }] }, { 'featureType': 'poi', 'stylers': [{ 'hue': theme.getCSSVar('--gmap-poi-hue') }, { 'saturation': -1.0989010989011234 }, { 'lightness': 11.200000000000017 }, { 'gamma': 1 }] }] }; map = new google.maps.Map(document.getElementById('google-map'), mapOptions); if (marker) { marker.setMap(null); } marker = new google.maps.Marker({ map, position: new google.maps.LatLng(latitude, longitude), visible: true }); }); } }; // Initialize Google Maps initGoogleMap(); // Listen for theme changes window.addEventListener('adminator:themeChanged', initGoogleMap); })()); ;// ../assets/scripts/ui/index.js /** * UI Page Bootstrap Components * Vanilla JavaScript implementations for Bootstrap components */ /* harmony default export */ const ui = ((function () { // Modal functionality class VanillaModal { constructor(element) { this.element = element; this.modal = null; this.backdrop = null; this.isOpen = false; this.init(); } init() { this.modal = document.querySelector(this.element.getAttribute('data-bs-target')); if (this.modal) { this.element.addEventListener('click', e => { e.preventDefault(); this.show(); }); // Close button functionality const closeButtons = this.modal.querySelectorAll('[data-bs-dismiss="modal"]'); closeButtons.forEach(btn => { btn.addEventListener('click', () => this.hide()); }); // Close on backdrop click this.modal.addEventListener('click', e => { if (e.target === this.modal) { this.hide(); } }); } } show() { if (this.isOpen) return; // Create backdrop this.backdrop = document.createElement('div'); this.backdrop.className = 'modal-backdrop fade show'; document.body.appendChild(this.backdrop); // Show modal this.modal.style.display = 'block'; this.modal.classList.add('show'); document.body.classList.add('modal-open'); this.isOpen = true; // Focus the modal this.modal.setAttribute('tabindex', '-1'); this.modal.focus(); // Escape key handler this.escapeHandler = e => { if (e.key === 'Escape') { this.hide(); } }; document.addEventListener('keydown', this.escapeHandler); } hide() { if (!this.isOpen) return; // Hide modal this.modal.classList.remove('show'); this.modal.style.display = 'none'; document.body.classList.remove('modal-open'); // Remove backdrop if (this.backdrop) { this.backdrop.remove(); this.backdrop = null; } this.isOpen = false; // Remove escape handler if (this.escapeHandler) { document.removeEventListener('keydown', this.escapeHandler); this.escapeHandler = null; } } } // Dropdown functionality class VanillaDropdown { constructor(element) { this.element = element; this.menu = null; this.isOpen = false; this.init(); } init() { this.menu = this.element.parentNode.querySelector('.dropdown-menu'); if (this.menu) { this.element.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); this.toggle(); }); // Close on outside click document.addEventListener('click', e => { if (!this.element.parentNode.contains(e.target)) { this.hide(); } }); // Close on escape document.addEventListener('keydown', e => { if (e.key === 'Escape' && this.isOpen) { this.hide(); } }); } } toggle() { if (this.isOpen) { this.hide(); } else { this.show(); } } show() { if (this.isOpen) return; // Close other dropdowns document.querySelectorAll('.dropdown-menu.show').forEach(menu => { menu.classList.remove('show'); }); this.menu.classList.add('show'); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; } hide() { if (!this.isOpen) return; this.menu.classList.remove('show'); this.element.setAttribute('aria-expanded', 'false'); this.isOpen = false; } } // Popover functionality class VanillaPopover { constructor(element) { this.element = element; this.popover = null; this.isOpen = false; this.init(); } init() { this.element.addEventListener('click', e => { e.preventDefault(); this.toggle(); }); // Close on outside click document.addEventListener('click', e => { if (!this.element.contains(e.target) && (!this.popover || !this.popover.contains(e.target))) { this.hide(); } }); } toggle() { if (this.isOpen) { this.hide(); } else { this.show(); } } show() { if (this.isOpen) return; // Close other popovers document.querySelectorAll('.popover').forEach(popover => { popover.remove(); }); const title = this.element.getAttribute('title') || this.element.getAttribute('data-bs-title'); const content = this.element.getAttribute('data-bs-content'); this.popover = document.createElement('div'); this.popover.className = 'popover bs-popover-top show'; this.popover.style.position = 'absolute'; this.popover.style.zIndex = '1070'; this.popover.style.maxWidth = '276px'; this.popover.style.backgroundColor = '#fff'; this.popover.style.border = '1px solid rgba(0,0,0,.2)'; this.popover.style.borderRadius = '6px'; this.popover.style.boxShadow = '0 5px 10px rgba(0,0,0,.2)'; let popoverContent = ''; if (title) { popoverContent += `
${title}
`; } popoverContent += `
${content}
`; this.popover.innerHTML = popoverContent; document.body.appendChild(this.popover); // Position popover const rect = this.element.getBoundingClientRect(); this.popover.style.left = `${rect.left + rect.width / 2 - this.popover.offsetWidth / 2}px`; this.popover.style.top = `${rect.top - this.popover.offsetHeight - 10}px`; this.isOpen = true; } hide() { if (!this.isOpen) return; if (this.popover) { this.popover.remove(); this.popover = null; } this.isOpen = false; } } // Tooltip functionality class VanillaTooltip { constructor(element) { this.element = element; this.tooltip = null; this.init(); } init() { this.element.addEventListener('mouseenter', () => this.show()); this.element.addEventListener('mouseleave', () => this.hide()); this.element.addEventListener('focus', () => this.show()); this.element.addEventListener('blur', () => this.hide()); } show() { if (this.tooltip) return; const title = this.element.getAttribute('title') || this.element.getAttribute('data-bs-title'); const placement = this.element.getAttribute('data-bs-placement') || 'top'; if (!title) return; this.tooltip = document.createElement('div'); this.tooltip.className = `tooltip bs-tooltip-${placement} show`; this.tooltip.style.position = 'absolute'; this.tooltip.style.zIndex = '1070'; this.tooltip.style.maxWidth = '200px'; this.tooltip.style.padding = '4px 8px'; this.tooltip.style.fontSize = '12px'; this.tooltip.style.backgroundColor = '#000'; this.tooltip.style.color = '#fff'; this.tooltip.style.borderRadius = '4px'; this.tooltip.style.pointerEvents = 'none'; this.tooltip.style.whiteSpace = 'nowrap'; this.tooltip.innerHTML = `
${title}
`; document.body.appendChild(this.tooltip); // Position tooltip const rect = this.element.getBoundingClientRect(); switch (placement) { case 'top': this.tooltip.style.left = `${rect.left + rect.width / 2 - this.tooltip.offsetWidth / 2}px`; this.tooltip.style.top = `${rect.top - this.tooltip.offsetHeight - 5}px`; break; case 'bottom': this.tooltip.style.left = `${rect.left + rect.width / 2 - this.tooltip.offsetWidth / 2}px`; this.tooltip.style.top = `${rect.bottom + 5}px`; break; case 'left': this.tooltip.style.left = `${rect.left - this.tooltip.offsetWidth - 5}px`; this.tooltip.style.top = `${rect.top + rect.height / 2 - this.tooltip.offsetHeight / 2}px`; break; case 'right': this.tooltip.style.left = `${rect.right + 5}px`; this.tooltip.style.top = `${rect.top + rect.height / 2 - this.tooltip.offsetHeight / 2}px`; break; } } hide() { if (this.tooltip) { this.tooltip.remove(); this.tooltip = null; } } } // Accordion functionality class VanillaAccordion { constructor(element) { this.element = element; this.accordion = element.closest('.accordion'); this.target = document.querySelector(element.getAttribute('data-bs-target')); this.isOpen = !element.classList.contains('collapsed'); this.init(); } init() { this.element.addEventListener('click', e => { e.preventDefault(); this.toggle(); }); } toggle() { if (this.isOpen) { this.hide(); } else { this.show(); } } show() { if (this.isOpen) return; // Close other accordion items in the same parent const parentAccordion = this.accordion; if (parentAccordion) { const otherItems = parentAccordion.querySelectorAll('.accordion-collapse.show'); otherItems.forEach(item => { if (item !== this.target) { item.classList.remove('show'); const button = parentAccordion.querySelector(`[data-bs-target="#${item.id}"]`); if (button) { button.classList.add('collapsed'); button.setAttribute('aria-expanded', 'false'); } } }); } // Show this item this.target.classList.add('show'); this.element.classList.remove('collapsed'); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; } hide() { if (!this.isOpen) return; this.target.classList.remove('show'); this.element.classList.add('collapsed'); this.element.setAttribute('aria-expanded', 'false'); this.isOpen = false; } } // Initialize all components const initComponents = () => { // Initialize modals document.querySelectorAll('[data-bs-toggle="modal"]').forEach(element => { new VanillaModal(element); }); // Initialize dropdowns document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(element => { new VanillaDropdown(element); }); // Initialize popovers document.querySelectorAll('[data-bs-toggle="popover"]').forEach(element => { new VanillaPopover(element); }); // Initialize tooltips document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(element => { new VanillaTooltip(element); }); // Initialize accordions document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(element => { new VanillaAccordion(element); }); }; // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initComponents); } else { initComponents(); } // Public API return { init: initComponents, Modal: VanillaModal, Dropdown: VanillaDropdown, Popover: VanillaPopover, Tooltip: VanillaTooltip, Accordion: VanillaAccordion }; })()); ;// ../assets/scripts/app.js /** * Modern Adminator Application * Main application entry point with enhanced mobile support * * @module app * @version 2.9.0 */ // Note: Bootstrap 5 CSS is still available via SCSS imports // Bootstrap JS components removed to eliminate jQuery dependency // Import styles // Import other modules that don't need immediate modernization class AdminatorApp { constructor() { this.components = new Map(); this.isInitialized = false; this.themeManager = theme; this.cleanupFunctions = []; // Initialize when DOM is ready DOM.ready(() => { this.init(); }); } /** * Initialize the application */ init() { if (this.isInitialized) return; logger.time('Adminator Init'); try { // Initialize core components this.initSidebar(); this.initCharts(); this.initDataTables(); this.initDatePickers(); this.initTheme(); this.initMobileEnhancements(); // Setup global event listeners using event delegation this.setupGlobalEvents(); this.isInitialized = true; logger.timeEnd('Adminator Init'); logger.info('Application initialized successfully'); // Dispatch custom event for other scripts events.emit(window, 'adminator:ready', { app: this }); } catch (error) { logger.error('Error initializing Adminator App', error); } } /** * Initialize Sidebar component */ initSidebar() { if (DOM.exists('.sidebar')) { const sidebar = new components_Sidebar(); this.components.set('sidebar', sidebar); } } /** * Initialize Chart components */ initCharts() { // Check if we have any chart elements const hasCharts = DOM.exists('#sparklinedash') || DOM.exists('.sparkline') || DOM.exists('.sparkbar') || DOM.exists('.sparktri') || DOM.exists('.sparkdisc') || DOM.exists('.sparkbull') || DOM.exists('.sparkbox') || DOM.exists('.easy-pie-chart') || DOM.exists('#line-chart') || DOM.exists('#area-chart') || DOM.exists('#scatter-chart') || DOM.exists('#bar-chart'); if (hasCharts) { const charts = new components_Chart(); this.components.set('charts', charts); } } /** * Initialize DataTables (modern approach) */ initDataTables() { const dataTableElement = DOM.select('#dataTable'); if (dataTableElement) { // For now, use a lightweight approach // In future iterations, we can replace with a modern table library this.initBasicDataTable(dataTableElement); } } /** * Basic DataTable implementation (placeholder for full modernization) */ initBasicDataTable(table) { // Add basic sorting functionality const headers = DOM.selectAll('th', table); headers.forEach(header => { if (header.textContent.trim()) { header.style.cursor = 'pointer'; header.style.userSelect = 'none'; DOM.on(header, 'click', () => { // Basic sort functionality can be added here // For now, we'll keep the existing DataTables library }); } }); } /** * Initialize Date Pickers (modern approach with Day.js) */ initDatePickers() { const startDatePickers = DOM.selectAll('.start-date'); const endDatePickers = DOM.selectAll('.end-date'); [...startDatePickers, ...endDatePickers].forEach(picker => { // Convert to HTML5 date input for better UX if (picker.type !== 'date') { picker.type = 'date'; picker.classList.add('form-control'); // Clear the placeholder since HTML5 date inputs don't need it picker.removeAttribute('placeholder'); // Set default value to today if no value is set if (!picker.value) { picker.value = date.form.toInputValue(date.now()); } // Make sure the input is clickable and focusable picker.style.pointerEvents = 'auto'; picker.style.cursor = 'pointer'; // Ensure proper styling for HTML5 date input picker.style.minHeight = '38px'; picker.style.lineHeight = '1.5'; // Date picker converted to HTML5 with Day.js support } }); // Add enhanced interaction - handle both field and icon clicks [...startDatePickers, ...endDatePickers].forEach(picker => { // Handle direct field clicks DOM.on(picker, 'click', event => { event.target.focus(); // For mobile browsers, trigger the date picker if (event.target.showPicker && typeof event.target.showPicker === 'function') { try { event.target.showPicker(); } catch { // Fallback if showPicker is not supported } } }); // Handle calendar icon clicks (find the icon in the input group) const inputGroup = picker.closest('.input-group'); if (inputGroup) { const calendarIcon = inputGroup.querySelector('.input-group-text i.ti-calendar'); if (calendarIcon) { DOM.on(calendarIcon, 'click', event => { event.preventDefault(); event.stopPropagation(); picker.focus(); if (picker.showPicker && typeof picker.showPicker === 'function') { try { picker.showPicker(); } catch { // Date picker opened via icon click } } }); } } }); } /** * Initialize theme system with toggle */ initTheme() { // Initializing theme system // Initialize theme system first this.themeManager.init(); // Inject theme toggle if missing - with retry mechanism setTimeout(() => { const navRight = DOM.select('.nav-right'); // Check for nav-right and theme-toggle existence if (navRight && !DOM.exists('#theme-toggle')) { const li = document.createElement('li'); li.className = 'theme-toggle d-flex ai-c'; const currentTheme = this.themeManager.current(); const isDark = currentTheme === 'dark'; li.innerHTML = `
`; // Insert before user dropdown (last item) - safer approach const lastItem = navRight.querySelector('li:last-child'); if (lastItem && lastItem.parentNode === navRight) { navRight.insertBefore(li, lastItem); // Theme toggle inserted before last item } else { navRight.appendChild(li); // Theme toggle appended to nav-right (safer approach) } // Add toggle functionality const toggle = DOM.select('#theme-toggle'); if (toggle) { DOM.on(toggle, 'change', () => { const newTheme = toggle.checked ? 'dark' : 'light'; toggle.setAttribute('aria-checked', toggle.checked ? 'true' : 'false'); this.themeManager.apply(newTheme); }); // Listen for theme changes from other sources window.addEventListener('adminator:themeChanged', event => { const isDark = event.detail.theme === 'dark'; toggle.checked = isDark; toggle.setAttribute('aria-checked', isDark ? 'true' : 'false'); // Update charts when theme changes const charts = this.components.get('charts'); if (charts) charts.redrawCharts(); }); } } else { // No nav-right found or theme-toggle already exists } }, 100); // Wait 100ms for DOM to be fully ready } /** * Initialize mobile-specific enhancements */ initMobileEnhancements() { // Initializing mobile enhancements this.enhanceMobileDropdowns(); this.enhanceMobileSearch(); // Prevent horizontal scroll on mobile if (this.isMobile()) { document.body.style.overflowX = 'hidden'; } } /** * Setup global event listeners using event delegation for performance */ setupGlobalEvents() { // Use event delegation for dropdown clicks (single listener instead of many) const dropdownCleanup = events.delegate(document, 'click', '.nav-right .dropdown-toggle', (e, toggle) => this.handleDropdownClick(e, toggle)); this.cleanupFunctions.push(dropdownCleanup); // Global click handler for closing dropdowns/search const globalClickCleanup = events.on(document, 'click', event => { this.handleGlobalClick(event); }); this.cleanupFunctions.push(globalClickCleanup); // Window resize handler with debouncing using Events utility const debouncedResize = events.debounce(() => this.handleResize(), 250); const resizeCleanup = events.on(window, 'resize', debouncedResize); this.cleanupFunctions.push(resizeCleanup); // Escape key handler using delegation const escapeCleanup = events.on(document, 'keydown', e => { if (e.key === 'Escape') { this.closeAllOverlays(); } }); this.cleanupFunctions.push(escapeCleanup); logger.debug('Global event listeners set up with delegation'); } /** * Handle dropdown toggle clicks * @param {Event} e - Click event * @param {Element} toggle - The clicked toggle element */ handleDropdownClick(e, toggle) { if (!this.isMobile()) return; e.preventDefault(); e.stopPropagation(); const dropdown = toggle.closest('.dropdown'); const menu = dropdown === null || dropdown === void 0 ? void 0 : dropdown.querySelector('.dropdown-menu'); if (!dropdown || !menu) return; // Close search if open this.closeSearch(); // Close other dropdowns DOM.selectAll('.nav-right .dropdown').forEach(d => { if (d !== dropdown) { var _d$querySelector; d.classList.remove('show'); (_d$querySelector = d.querySelector('.dropdown-menu')) === null || _d$querySelector === void 0 || _d$querySelector.classList.remove('show'); } }); // Toggle current dropdown const isOpen = dropdown.classList.contains('show'); dropdown.classList.toggle('show', !isOpen); menu.classList.toggle('show', !isOpen); document.body.style.overflow = isOpen ? '' : 'hidden'; document.body.classList.toggle('mobile-menu-open', !isOpen); } /** * Close all overlays (dropdowns, search) */ closeAllOverlays() { // Close dropdowns DOM.selectAll('.nav-right .dropdown').forEach(dropdown => { var _dropdown$querySelect; dropdown.classList.remove('show'); (_dropdown$querySelect = dropdown.querySelector('.dropdown-menu')) === null || _dropdown$querySelect === void 0 || _dropdown$querySelect.classList.remove('show'); }); // Close search this.closeSearch(); document.body.style.overflow = ''; document.body.classList.remove('mobile-menu-open'); } /** * Close the search overlay */ closeSearch() { const searchBox = DOM.select('.search-box'); const searchInput = DOM.select('.search-input'); if (searchBox && searchInput) { searchBox.classList.remove('active'); searchInput.classList.remove('active'); document.body.classList.remove('search-open'); // Reset icon const searchIcon = searchBox.querySelector('i'); if (searchIcon) { searchIcon.className = 'ti-search'; } // Clear input const field = searchInput.querySelector('input'); if (field) { field.value = ''; field.blur(); } } } /** * Handle window resize events */ handleResize() { // Window resized, updating mobile features // Close all mobile-specific overlays when switching to desktop if (!this.isMobile()) { document.body.style.overflow = ''; document.body.style.overflowX = ''; // Close dropdowns const dropdowns = DOM.selectAll('.nav-right .dropdown'); dropdowns.forEach(dropdown => { dropdown.classList.remove('show'); const menu = dropdown.querySelector('.dropdown-menu'); if (menu) menu.classList.remove('show'); }); // Close search const searchBox = DOM.select('.search-box'); const searchInput = DOM.select('.search-input'); if (searchBox && searchInput) { searchBox.classList.remove('active'); searchInput.classList.remove('active'); } } else { // Re-enable mobile overflow protection document.body.style.overflowX = 'hidden'; } // Re-apply mobile enhancements this.enhanceMobileDropdowns(); this.enhanceMobileSearch(); } /** * Handle global click events */ handleGlobalClick(event) { // Close mobile dropdowns when clicking outside if (!event.target.closest('.dropdown')) { const dropdowns = DOM.selectAll('.nav-right .dropdown'); dropdowns.forEach(dropdown => { dropdown.classList.remove('show'); const menu = dropdown.querySelector('.dropdown-menu'); if (menu) menu.classList.remove('show'); }); document.body.style.overflow = ''; } // Close search when clicking outside if (!event.target.closest('.search-box') && !event.target.closest('.search-input')) { const searchBox = DOM.select('.search-box'); const searchInput = DOM.select('.search-input'); if (searchBox && searchInput) { searchBox.classList.remove('active'); searchInput.classList.remove('active'); document.body.style.overflow = ''; document.body.classList.remove('mobile-menu-open'); } } } /** * Check if we're on a mobile device */ isMobile() { return window.innerWidth <= 768; } /** * Enhanced mobile dropdown handling with improved email layout */ enhanceMobileDropdowns() { if (!this.isMobile()) return; const dropdowns = DOM.selectAll('.nav-right .dropdown'); dropdowns.forEach(dropdown => { const toggle = dropdown.querySelector('.dropdown-toggle'); const menu = dropdown.querySelector('.dropdown-menu'); if (toggle && menu) { // Remove existing listeners to prevent duplicates const newToggle = toggle.cloneNode(true); toggle.replaceWith(newToggle); // Add click functionality for mobile dropdowns DOM.on(newToggle, 'click', e => { e.preventDefault(); e.stopPropagation(); // Close search if open const searchBox = DOM.select('.search-box'); const searchInput = DOM.select('.search-input'); if (searchBox && searchInput) { searchBox.classList.remove('active'); searchInput.classList.remove('active'); } // Close other dropdowns first dropdowns.forEach(otherDropdown => { if (otherDropdown !== dropdown) { otherDropdown.classList.remove('show'); const otherMenu = otherDropdown.querySelector('.dropdown-menu'); if (otherMenu) otherMenu.classList.remove('show'); } }); // Toggle current dropdown const isOpen = dropdown.classList.contains('show'); if (isOpen) { dropdown.classList.remove('show'); menu.classList.remove('show'); document.body.style.overflow = ''; document.body.classList.remove('mobile-menu-open'); } else { dropdown.classList.add('show'); menu.classList.add('show'); document.body.style.overflow = 'hidden'; document.body.classList.add('mobile-menu-open'); } }); // Enhanced mobile close button functionality DOM.on(menu, 'click', e => { // Check if clicked on the close area (::before pseudo-element area) const rect = menu.getBoundingClientRect(); const clickY = e.clientY - rect.top; // If clicked in top 50px (close button area) if (clickY <= 50) { dropdown.classList.remove('show'); menu.classList.remove('show'); document.body.style.overflow = ''; document.body.classList.remove('mobile-menu-open'); e.preventDefault(); e.stopPropagation(); } }); } }); // Close dropdowns on escape key DOM.on(document, 'keydown', e => { if (e.key === 'Escape') { dropdowns.forEach(dropdown => { dropdown.classList.remove('show'); const menu = dropdown.querySelector('.dropdown-menu'); if (menu) menu.classList.remove('show'); }); document.body.style.overflow = ''; document.body.classList.remove('mobile-menu-open'); } }); } /** * Enhanced mobile search handling - Full-width search bar */ enhanceMobileSearch() { const searchBox = DOM.select('.search-box'); const searchInput = DOM.select('.search-input'); if (searchBox && searchInput) { const searchToggle = searchBox.querySelector('a'); const searchField = searchInput.querySelector('input'); if (searchToggle && searchField) { // Setting up full-width search functionality // Remove existing listeners to prevent duplication const newSearchToggle = searchToggle.cloneNode(true); searchToggle.replaceWith(newSearchToggle); DOM.on(newSearchToggle, 'click', e => { e.preventDefault(); e.stopPropagation(); // Full-width search toggle clicked // Close any open dropdowns first const dropdowns = DOM.selectAll('.nav-right .dropdown'); dropdowns.forEach(dropdown => { dropdown.classList.remove('show'); const menu = dropdown.querySelector('.dropdown-menu'); if (menu) menu.classList.remove('show'); }); // Toggle search state const isActive = searchInput.classList.contains('active'); const searchIcon = newSearchToggle.querySelector('i'); if (isActive) { // Close search searchInput.classList.remove('active'); document.body.classList.remove('search-open'); // Change icon back to search if (searchIcon) { searchIcon.className = 'ti-search'; } // Clear input if (searchField) { searchField.value = ''; searchField.blur(); } // Full-width search closed } else { // Open search searchInput.classList.add('active'); document.body.classList.add('search-open'); // Change icon to close if (searchIcon) { searchIcon.className = 'ti-close'; } // Focus the input after a short delay setTimeout(() => { if (searchField) { searchField.focus(); // Search field focused } }, 100); // Full-width search opened } }); // Close search on escape DOM.on(document, 'keydown', e => { if (e.key === 'Escape' && searchInput.classList.contains('active')) { searchInput.classList.remove('active'); document.body.classList.remove('search-open'); // Reset icon const searchIcon = newSearchToggle.querySelector('i'); if (searchIcon) { searchIcon.className = 'ti-search'; } // Clear input if (searchField) { searchField.value = ''; searchField.blur(); } // Full-width search closed via escape } }); // Handle search input DOM.on(searchField, 'keypress', e => { if (e.key === 'Enter') { e.preventDefault(); const query = searchField.value.trim(); if (query) { // Search query submitted // Implement your search logic here // For demo, close search after "searching" searchInput.classList.remove('active'); document.body.classList.remove('search-open'); const searchIcon = newSearchToggle.querySelector('i'); if (searchIcon) { searchIcon.className = 'ti-search'; } searchField.value = ''; searchField.blur(); } } }); // Full-width search functionality initialized } } } /** * Get a component by name */ getComponent(name) { return this.components.get(name); } /** * Check if app is ready */ isReady() { return this.isInitialized; } /** * Destroy the application and clean up all resources */ destroy() { logger.info('Destroying Adminator App'); // Clean up all event listeners this.cleanupFunctions.forEach(cleanup => { if (typeof cleanup === 'function') { cleanup(); } }); this.cleanupFunctions = []; // Destroy all components this.components.forEach((component, name) => { if (typeof component.destroy === 'function') { component.destroy(); logger.debug(`Component destroyed: ${name}`); } }); this.components.clear(); // Cleanup performance observers utils_performance.cleanup(); this.isInitialized = false; logger.info('Adminator App destroyed'); } /** * Refresh/reinitialize the application */ refresh() { logger.info('Refreshing Adminator App'); if (this.isInitialized) { this.destroy(); } setTimeout(() => { this.init(); }, 100); } } // Initialize the application const app = new AdminatorApp(); // Make app globally available for debugging window.AdminatorApp = app; // Export for module usage /* harmony default export */ const scripts_app = ((/* unused pure expression or super */ null && (app))); ;// ../assets/scripts/datatable/index.js // DataTable implementation /* harmony default export */ const datatable = ((function () { // Vanilla JS DataTable implementation class VanillaDataTable { constructor(element) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; this.element = element; this.options = { sortable: true, searchable: true, pagination: true, pageSize: 10, ...options }; this.originalData = []; this.filteredData = []; this.currentPage = 1; this.sortColumn = null; this.sortDirection = 'asc'; this.init(); } init() { this.extractData(); this.createControls(); this.applyStyles(); this.bindEvents(); this.render(); } extractData() { const rows = this.element.querySelectorAll('tbody tr'); this.originalData = Array.from(rows).map(row => { const cells = row.querySelectorAll('td'); return Array.from(cells).map(cell => cell.textContent.trim()); }); this.filteredData = [...this.originalData]; } createControls() { const wrapper = document.createElement('div'); wrapper.className = 'datatable-wrapper'; // Create search input if (this.options.searchable) { const searchWrapper = document.createElement('div'); searchWrapper.className = 'datatable-search'; searchWrapper.innerHTML = ` `; wrapper.appendChild(searchWrapper); } // Create pagination info if (this.options.pagination) { const infoWrapper = document.createElement('div'); infoWrapper.className = 'datatable-info'; wrapper.appendChild(infoWrapper); } // Wrap the table this.element.parentNode.insertBefore(wrapper, this.element); wrapper.appendChild(this.element); // Create pagination controls if (this.options.pagination) { const paginationWrapper = document.createElement('div'); paginationWrapper.className = 'datatable-pagination'; wrapper.appendChild(paginationWrapper); } this.wrapper = wrapper; } applyStyles() { // Apply Bootstrap-like styles this.element.classList.add('table', 'table-striped', 'table-bordered'); // Add custom styles const style = document.createElement('style'); style.textContent = ` .datatable-wrapper { margin: 20px 0; } .datatable-search { margin-bottom: 15px; } .datatable-search input { width: 250px; display: inline-block; margin-left: 5px; } .datatable-info { margin-top: 15px; color: var(--c-text-muted, #6c757d); font-size: 14px; } .datatable-pagination { margin-top: 15px; display: flex; justify-content: center; } .datatable-pagination button { background: var(--c-bkg-card, #fff); border: 1px solid var(--c-border, #dee2e6); color: var(--c-text-base, #333); padding: 6px 12px; margin: 0 2px; cursor: pointer; border-radius: 4px; } .datatable-pagination button:hover { background: var(--c-primary, #007bff); color: white; } .datatable-pagination button.active { background: var(--c-primary, #007bff); color: white; } .datatable-pagination button:disabled { opacity: 0.6; cursor: not-allowed; } .datatable-sort { cursor: pointer; user-select: none; position: relative; } .datatable-sort:hover { background: var(--c-bkg-card, #f8f9fa); } .datatable-sort::after { content: '↕'; position: absolute; right: 8px; opacity: 0.5; } .datatable-sort.asc::after { content: '↑'; opacity: 1; } .datatable-sort.desc::after { content: '↓'; opacity: 1; } `; document.head.appendChild(style); } bindEvents() { // Search functionality if (this.options.searchable) { const searchInput = this.wrapper.querySelector('.datatable-search input'); searchInput.addEventListener('input', e => { this.search(e.target.value); }); } // Sorting functionality if (this.options.sortable) { const headers = this.element.querySelectorAll('thead th'); headers.forEach((header, index) => { header.classList.add('datatable-sort'); header.addEventListener('click', () => { this.sort(index); }); }); } } search(query) { if (!query) { this.filteredData = [...this.originalData]; } else { this.filteredData = this.originalData.filter(row => row.some(cell => cell.toLowerCase().includes(query.toLowerCase()))); } this.currentPage = 1; this.render(); } sort(columnIndex) { if (this.sortColumn === columnIndex) { this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc'; } else { this.sortColumn = columnIndex; this.sortDirection = 'asc'; } this.filteredData.sort((a, b) => { const aVal = a[columnIndex]; const bVal = b[columnIndex]; // Try to parse as numbers const aNum = parseFloat(aVal); const bNum = parseFloat(bVal); let comparison = 0; if (!isNaN(aNum) && !isNaN(bNum)) { comparison = aNum - bNum; } else { comparison = aVal.localeCompare(bVal); } return this.sortDirection === 'asc' ? comparison : -comparison; }); this.updateSortHeaders(); this.render(); } updateSortHeaders() { const headers = this.element.querySelectorAll('thead th'); headers.forEach((header, index) => { header.classList.remove('asc', 'desc'); if (index === this.sortColumn) { header.classList.add(this.sortDirection); } }); } render() { const tbody = this.element.querySelector('tbody'); const startIndex = (this.currentPage - 1) * this.options.pageSize; const endIndex = startIndex + this.options.pageSize; const pageData = this.filteredData.slice(startIndex, endIndex); // Clear tbody tbody.innerHTML = ''; // Add rows pageData.forEach(rowData => { const row = document.createElement('tr'); rowData.forEach(cellData => { const cell = document.createElement('td'); cell.textContent = cellData; row.appendChild(cell); }); tbody.appendChild(row); }); // Update pagination if (this.options.pagination) { this.updatePagination(); } // Update info this.updateInfo(); } updatePagination() { const totalPages = Math.ceil(this.filteredData.length / this.options.pageSize); const paginationWrapper = this.wrapper.querySelector('.datatable-pagination'); paginationWrapper.innerHTML = ''; if (totalPages <= 1) return; // Previous button const prevBtn = document.createElement('button'); prevBtn.textContent = 'Previous'; prevBtn.disabled = this.currentPage === 1; prevBtn.addEventListener('click', () => { if (this.currentPage > 1) { this.currentPage--; this.render(); } }); paginationWrapper.appendChild(prevBtn); // Page numbers for (let i = 1; i <= totalPages; i++) { const pageBtn = document.createElement('button'); pageBtn.textContent = i; pageBtn.classList.toggle('active', i === this.currentPage); pageBtn.addEventListener('click', () => { this.currentPage = i; this.render(); }); paginationWrapper.appendChild(pageBtn); } // Next button const nextBtn = document.createElement('button'); nextBtn.textContent = 'Next'; nextBtn.disabled = this.currentPage === totalPages; nextBtn.addEventListener('click', () => { if (this.currentPage < totalPages) { this.currentPage++; this.render(); } }); paginationWrapper.appendChild(nextBtn); } updateInfo() { const infoWrapper = this.wrapper.querySelector('.datatable-info'); if (!infoWrapper) return; const startIndex = (this.currentPage - 1) * this.options.pageSize + 1; const endIndex = Math.min(startIndex + this.options.pageSize - 1, this.filteredData.length); const total = this.filteredData.length; infoWrapper.textContent = `Showing ${startIndex} to ${endIndex} of ${total} entries`; } destroy() { if (this.wrapper && this.wrapper.parentNode) { this.wrapper.parentNode.replaceChild(this.element, this.wrapper); } } } // Initialize DataTable const initializeDataTable = () => { const tableElement = document.getElementById('dataTable'); if (tableElement) { // Clean up existing instance if (tableElement.dataTableInstance) { tableElement.dataTableInstance.destroy(); } // Create new instance const dataTable = new VanillaDataTable(tableElement, { sortable: true, searchable: true, pagination: true, pageSize: 10 }); // Store instance for cleanup tableElement.dataTableInstance = dataTable; } }; // Initialize on load initializeDataTable(); // Reinitialize on theme change window.addEventListener('adminator:themeChanged', () => { setTimeout(initializeDataTable, 100); }); // Cleanup on page unload window.addEventListener('beforeunload', () => { const tableElement = document.getElementById('dataTable'); if (tableElement && tableElement.dataTableInstance) { tableElement.dataTableInstance.destroy(); } }); // Return public API return { init: initializeDataTable, VanillaDataTable }; })()); ;// ../assets/scripts/index.js /** * Adminator Admin Template * Modern Entry Point - Phase 2 Modernization */ // Import the modern application // Legacy imports that haven't been modernized yet // These will be gradually replaced in future iterations // import './datepicker'; // REMOVED: Replaced with modern day.js implementation in app.js // Note: The following have been modernized and are now handled by app.js: // - sidebar (now Sidebar component) // - charts (now ChartComponent using Chart.js instead of jQuery Sparkline) // - Basic DOM utilities (now DOM utils) /***/ } }, /******/ __webpack_require__ => { // webpackRuntimeModules /******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) /******/ __webpack_require__.O(0, [707,311,96], () => (__webpack_exec__(170))); /******/ var __webpack_exports__ = __webpack_require__.O(); /******/ } ]);