Files
Kwesi Banson Jnr b71f5c7553 Initial commit
2026-07-17 09:58:21 +00:00

6423 lines
182 KiB
JavaScript

"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', '<p>New content</p>');
*/
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<void>} 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<void>} 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<void>} 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<void>} 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<Element|string>} [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<Element, Map<string, Set<Function>>>}
*/
const handlerRegistry = new WeakMap();
/**
* Store for AbortControllers to enable easy cleanup
* @type {WeakMap<Element, AbortController>}
*/
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<Element, Function>}
*/
const resizeCallbacks = new WeakMap();
/**
* Shared ResizeObserver instance for efficiency
* @type {ResizeObserver|null}
*/
let sharedResizeObserver = null;
/**
* Store for IntersectionObserver callbacks
* @type {WeakMap<Element, Function>}
*/
const intersectionCallbacks = new WeakMap();
/**
* Map of IntersectionObservers by threshold
* @type {Map<string, IntersectionObserver>}
*/
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<void>} 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 ? `<div class="popover-title">${popoverTitle}</div>` : ''}
<div class="popover-content">${popoverContent}</div>
`;
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<string, string>}
*/
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<string, string>}
*/
const HTML_ENTITIES = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;',
'`': '&#x60;',
'=': '&#x3D;'
};
/**
* 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('<script>alert("xss")</script>');
* // Returns: &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;
*/
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('<p>Hello <b>World</b></p>');
* // 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`<div>${userInput}</div>`;
*/
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 = `
<div style="
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background: ${colors.backgroundColor};
border: 1px solid ${colors.borderColor};
border-radius: 8px;
color: ${colors.textColor};
font-size: 14px;
">
<div style="text-align: center;">
<div style="font-size: 24px; margin-bottom: 8px;">🗺️</div>
<div>World Map</div>
<div style="font-size: 12px; margin-top: 4px;">Interactive map will load here</div>
</div>
</div>
`;
}
};
// 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 += `<div class="popover-header" style="padding: 8px 14px; margin-bottom: 0; font-size: 14px; background-color: #f7f7f7; border-bottom: 1px solid #ebebeb; border-radius: 5px 5px 0 0; font-weight: 600;">${title}</div>`;
}
popoverContent += `<div class="popover-body" style="padding: 9px 14px; word-wrap: break-word;">${content}</div>`;
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 = `<div class="tooltip-inner">${title}</div>`;
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 = `
<div class="form-check form-switch d-flex ai-c" style="margin: 0; padding: 0;" role="group" aria-label="Theme switcher">
<label class="form-check-label me-2 text-nowrap c-grey-700" for="theme-toggle" style="font-size: 12px; margin-right: 8px;">
<i class="ti-sun" aria-hidden="true" style="margin-right: 4px;"></i><span class="theme-label">Light</span>
</label>
<input class="form-check-input" type="checkbox" id="theme-toggle"
role="switch"
aria-checked="${isDark}"
aria-label="Toggle dark mode"
${isDark ? 'checked' : ''}
style="margin: 0;">
<label class="form-check-label ms-2 text-nowrap c-grey-700" for="theme-toggle" style="font-size: 12px; margin-left: 8px;">
<span class="theme-label">Dark</span><i class="ti-moon" aria-hidden="true" style="margin-left: 4px;"></i>
</label>
</div>
`;
// 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 = `
<label>
Search:
<input type="text" class="form-control" placeholder="Search...">
</label>
`;
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();
/******/ }
]);