fix: implement Blazor-native login form to properly update authentication state
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m26s
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m26s
Problem: JavaScript login form saved tokens to localStorage but didn't notify CustomAuthenticationStateProvider, causing [Authorize] pages to remain in 'loading' state indefinitely. The provider only reads tokens when: 1. GetAuthenticationStateAsync() is called (page load) 2. NotifyAuthenticationStateChanged() is triggered (UI updates) But JavaScript login didn't trigger either, leaving the authentication state stale. Solution: Convert AdminLoginForm from HTML+JavaScript to pure Blazor component. Now the login flow is: 1. User enters credentials in Blazor form 2. HttpClient POST to /api/auth/login 3. Save tokens to localStorage 4. Call CustomAuthenticationStateProvider.LoginAsync() directly 5. Blazor detects auth state change and re-evaluates [Authorize] pages 6. Dashboard [Authorize] page renders successfully Result: Immediate authentication state update, no loading timeout on protected pages. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,197 @@
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Basic Box & Whisker Chart
|
||||
if (document.getElementById('basic-box-whisker-chart')) {
|
||||
const basicBoxOptions = {
|
||||
series: [{
|
||||
type: 'boxPlot',
|
||||
data: [
|
||||
{
|
||||
x: 'Jan 2015',
|
||||
y: [54, 66, 69, 75, 88]
|
||||
},
|
||||
{
|
||||
x: 'Jan 2016',
|
||||
y: [43, 65, 69, 76, 81]
|
||||
},
|
||||
{
|
||||
x: 'Jan 2017',
|
||||
y: [31, 39, 45, 51, 59]
|
||||
},
|
||||
{
|
||||
x: 'Jan 2018',
|
||||
y: [39, 46, 55, 65, 71]
|
||||
},
|
||||
{
|
||||
x: 'Jan 2019',
|
||||
y: [29, 31, 35, 39, 44]
|
||||
},
|
||||
{
|
||||
x: 'Jan 2020',
|
||||
y: [41, 49, 58, 61, 67]
|
||||
},
|
||||
{
|
||||
x: 'Jan 2021',
|
||||
y: [54, 59, 66, 71, 88]
|
||||
}
|
||||
]
|
||||
}],
|
||||
chart: {
|
||||
type: 'boxPlot',
|
||||
height: 350,
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
boxPlot: {
|
||||
colors: {
|
||||
upper: window.colorMap.primary[500].hex,
|
||||
lower: window.colorMap.primary[300].hex
|
||||
}
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
colors: [window.colorMap.bootstrapVars.bodyColor.hex]
|
||||
},
|
||||
xaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
padding: {
|
||||
left: -5,
|
||||
right: 0,
|
||||
top: -20,
|
||||
bottom: -5
|
||||
},
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
}
|
||||
};
|
||||
|
||||
const basicBoxChart = new ApexCharts(
|
||||
document.getElementById('basic-box-whisker-chart'),
|
||||
basicBoxOptions
|
||||
);
|
||||
basicBoxChart.render();
|
||||
}
|
||||
|
||||
// Horizontal Boxplot Chart
|
||||
if (document.getElementById('horizontal-boxplot-chart')) {
|
||||
const horizontalBoxplotOptions = {
|
||||
series: [{
|
||||
data: [
|
||||
{
|
||||
x: 'Category A',
|
||||
y: [54, 66, 69, 75, 88]
|
||||
},
|
||||
{
|
||||
x: 'Category B',
|
||||
y: [43, 65, 69, 76, 81]
|
||||
},
|
||||
{
|
||||
x: 'Category C',
|
||||
y: [31, 39, 45, 51, 59]
|
||||
},
|
||||
{
|
||||
x: 'Category D',
|
||||
y: [39, 46, 55, 65, 71]
|
||||
},
|
||||
{
|
||||
x: 'Category E',
|
||||
y: [29, 31, 35, 39, 44]
|
||||
},
|
||||
{
|
||||
x: 'Category F',
|
||||
y: [41, 49, 58, 61, 67]
|
||||
},
|
||||
{
|
||||
x: 'Category G',
|
||||
y: [54, 59, 66, 71, 88]
|
||||
}
|
||||
]
|
||||
}],
|
||||
chart: {
|
||||
type: 'boxPlot',
|
||||
height: 350,
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: true,
|
||||
barHeight: '50%'
|
||||
},
|
||||
boxPlot: {
|
||||
colors: {
|
||||
upper: window.colorMap.primary[500].hex,
|
||||
lower: window.colorMap.primary[300].hex
|
||||
},
|
||||
horizontal: true
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
colors: [window.colorMap.bootstrapVars.bodyColor.hex]
|
||||
},
|
||||
xaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
}
|
||||
};
|
||||
|
||||
const horizontalBoxplotChart = new ApexCharts(
|
||||
document.getElementById('horizontal-boxplot-chart'),
|
||||
horizontalBoxplotOptions
|
||||
);
|
||||
horizontalBoxplotChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Function to generate bubble chart data
|
||||
function generateBubbleData(baseval, count, yrange) {
|
||||
let i = 0;
|
||||
const series = [];
|
||||
while (i < count) {
|
||||
const x = Math.floor(Math.random() * (45 - 20 + 1)) + 20;
|
||||
const y = Math.floor(Math.random() * (yrange.max - yrange.min + 1)) + yrange.min;
|
||||
const z = Math.floor(Math.random() * (75 - 15 + 1)) + 15;
|
||||
|
||||
series.push([x, y, z]);
|
||||
baseval += 86400000;
|
||||
i++;
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
// Simple Bubble Chart
|
||||
if (document.getElementById('simple-bubble-chart')) {
|
||||
const simpleBubbleOptions = {
|
||||
series: [{
|
||||
name: 'Bubble 1',
|
||||
data: generateBubbleData(new Date('11 Feb 2022 GMT').getTime(), 20, {
|
||||
min: 10,
|
||||
max: 60
|
||||
})
|
||||
}, {
|
||||
name: 'Bubble 2',
|
||||
data: generateBubbleData(new Date('11 Feb 2022 GMT').getTime(), 20, {
|
||||
min: 10,
|
||||
max: 60
|
||||
})
|
||||
}, {
|
||||
name: 'Bubble 3',
|
||||
data: generateBubbleData(new Date('11 Feb 2022 GMT').getTime(), 20, {
|
||||
min: 10,
|
||||
max: 60
|
||||
})
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'bubble',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.8
|
||||
},
|
||||
xaxis: {
|
||||
tickAmount: 12,
|
||||
type: 'category',
|
||||
},
|
||||
yaxis: {
|
||||
max: 70,
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex],
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const simpleBubbleChart = new ApexCharts(
|
||||
document.getElementById('simple-bubble-chart'),
|
||||
simpleBubbleOptions
|
||||
);
|
||||
simpleBubbleChart.render();
|
||||
}
|
||||
|
||||
// 3D Bubble Chart
|
||||
if (document.getElementById('3d-bubble-chart')) {
|
||||
// Generate data for 3D bubble chart
|
||||
function generateData(count, yrange) {
|
||||
let i = 0;
|
||||
const series = [];
|
||||
while (i < count) {
|
||||
const x = (Math.floor(Math.random() * 100) + 1).toString();
|
||||
const y = Math.floor(Math.random() * (yrange.max - yrange.min + 1)) + yrange.min;
|
||||
const z = Math.floor(Math.random() * 20) + 10;
|
||||
|
||||
series.push({
|
||||
x: x,
|
||||
y: y,
|
||||
z: z
|
||||
});
|
||||
i++;
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
const bubbleChart3dOptions = {
|
||||
series: [{
|
||||
name: 'Product 1',
|
||||
data: generateData(30, {
|
||||
min: 10,
|
||||
max: 80
|
||||
})
|
||||
}, {
|
||||
name: 'Product 2',
|
||||
data: generateData(30, {
|
||||
min: 10,
|
||||
max: 80
|
||||
})
|
||||
}, {
|
||||
name: 'Product 3',
|
||||
data: generateData(30, {
|
||||
min: 10,
|
||||
max: 80
|
||||
})
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'bubble',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
type: 'vertical',
|
||||
gradientToColors: [window.colorMap.bootstrapVars.bodyBg.hex],
|
||||
opacityFrom: 0.8,
|
||||
opacityTo: 0.5
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
tickAmount: 10,
|
||||
},
|
||||
yaxis: {
|
||||
max: 90,
|
||||
tickAmount: 7,
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex],
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const bubbleChart3d = new ApexCharts(
|
||||
document.getElementById('3d-bubble-chart'),
|
||||
bubbleChart3dOptions
|
||||
);
|
||||
bubbleChart3d.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,519 @@
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Sample data for candlestick charts
|
||||
const seriesData = [{
|
||||
x: new Date(2016, 1, 1),
|
||||
y: [51.98, 56.29, 51.59, 53.85]
|
||||
}, {
|
||||
x: new Date(2016, 2, 1),
|
||||
y: [53.66, 54.99, 51.35, 52.95]
|
||||
}, {
|
||||
x: new Date(2016, 3, 1),
|
||||
y: [52.96, 53.78, 51.54, 52.48]
|
||||
}, {
|
||||
x: new Date(2016, 4, 1),
|
||||
y: [52.54, 52.79, 47.88, 49.24]
|
||||
}, {
|
||||
x: new Date(2016, 5, 1),
|
||||
y: [49.10, 52.86, 47.70, 52.78]
|
||||
}, {
|
||||
x: new Date(2016, 6, 1),
|
||||
y: [52.83, 53.48, 50.32, 52.29]
|
||||
}, {
|
||||
x: new Date(2016, 7, 1),
|
||||
y: [52.20, 54.48, 51.64, 52.58]
|
||||
}, {
|
||||
x: new Date(2016, 8, 1),
|
||||
y: [52.76, 57.35, 52.15, 57.03]
|
||||
}, {
|
||||
x: new Date(2016, 9, 1),
|
||||
y: [57.04, 58.15, 48.88, 56.19]
|
||||
}, {
|
||||
x: new Date(2016, 10, 1),
|
||||
y: [56.09, 58.85, 55.48, 58.79]
|
||||
}, {
|
||||
x: new Date(2016, 11, 1),
|
||||
y: [58.78, 59.65, 58.23, 59.05]
|
||||
}, {
|
||||
x: new Date(2017, 0, 1),
|
||||
y: [59.37, 61.11, 59.35, 60.34]
|
||||
}, {
|
||||
x: new Date(2017, 1, 1),
|
||||
y: [60.40, 60.52, 56.71, 56.93]
|
||||
}, {
|
||||
x: new Date(2017, 2, 1),
|
||||
y: [57.02, 59.71, 56.04, 56.82]
|
||||
}, {
|
||||
x: new Date(2017, 3, 1),
|
||||
y: [56.97, 59.62, 54.77, 59.30]
|
||||
}, {
|
||||
x: new Date(2017, 4, 1),
|
||||
y: [59.11, 62.29, 59.10, 59.85]
|
||||
}, {
|
||||
x: new Date(2017, 5, 1),
|
||||
y: [59.97, 60.11, 55.66, 58.42]
|
||||
}, {
|
||||
x: new Date(2017, 6, 1),
|
||||
y: [58.34, 60.93, 56.75, 57.42]
|
||||
}, {
|
||||
x: new Date(2017, 7, 1),
|
||||
y: [57.76, 58.08, 51.18, 54.71]
|
||||
}, {
|
||||
x: new Date(2017, 8, 1),
|
||||
y: [54.80, 61.42, 53.18, 57.35]
|
||||
}, {
|
||||
x: new Date(2017, 9, 1),
|
||||
y: [57.56, 63.09, 57.00, 62.99]
|
||||
}, {
|
||||
x: new Date(2017, 10, 1),
|
||||
y: [62.89, 63.42, 59.72, 61.76]
|
||||
}, {
|
||||
x: new Date(2017, 11, 1),
|
||||
y: [61.71, 64.15, 61.29, 63.04]
|
||||
}];
|
||||
|
||||
// Basic Candlestick Chart
|
||||
if (document.getElementById('basic-candlestick-chart')) {
|
||||
const basicCandlestickOptions = {
|
||||
series: [{
|
||||
data: seriesData
|
||||
}],
|
||||
chart: {
|
||||
type: 'candlestick',
|
||||
height: 350,
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
// title: {
|
||||
// text: 'Basic Candlestick Chart',
|
||||
// align: 'left'
|
||||
// },
|
||||
tooltip: {
|
||||
theme: 'dark',
|
||||
y: {
|
||||
formatter: function(y) {
|
||||
if (typeof y !== "undefined") {
|
||||
return "$" + y.toFixed(2);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return new Date(val).toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
||||
},
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
tooltip: {
|
||||
enabled: true
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
candlestick: {
|
||||
colors: {
|
||||
upward: window.colorMap.primary[500].hex,
|
||||
downward: window.colorMap.danger[500].hex
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const basicCandlestickChart = new ApexCharts(
|
||||
document.getElementById('basic-candlestick-chart'),
|
||||
basicCandlestickOptions
|
||||
);
|
||||
basicCandlestickChart.render();
|
||||
}
|
||||
|
||||
// Combo Candlestick Chart
|
||||
if (document.getElementById('combo-candlestick-chart')) {
|
||||
// Generate volume data
|
||||
const volumeData = [];
|
||||
for (let i = 0; i < seriesData.length; i++) {
|
||||
volumeData.push({
|
||||
x: seriesData[i].x,
|
||||
y: Math.floor(Math.random() * 1000000) + 200000
|
||||
});
|
||||
}
|
||||
|
||||
const comboCandlestickOptions = {
|
||||
series: [{
|
||||
name: 'candle',
|
||||
type: 'candlestick',
|
||||
data: seriesData
|
||||
}, {
|
||||
name: 'volume',
|
||||
type: 'bar',
|
||||
data: volumeData
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'line',
|
||||
stacked: false,
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: [1, 1]
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return new Date(val).toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
||||
},
|
||||
}
|
||||
},
|
||||
yaxis: [{
|
||||
seriesName: 'candle',
|
||||
title: {
|
||||
text: 'Price',
|
||||
style: {
|
||||
color: window.colorMap.primary[500].hex
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return '$' + val.toFixed(2);
|
||||
},
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}, {
|
||||
seriesName: 'volume',
|
||||
opposite: true,
|
||||
title: {
|
||||
text: 'Volume',
|
||||
style: {
|
||||
color: window.colorMap.success[500].hex
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return Intl.NumberFormat('en-US', { notation: 'compact', compactDisplay: 'short' }).format(val);
|
||||
},
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
}],
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
shared: true
|
||||
},
|
||||
plotOptions: {
|
||||
candlestick: {
|
||||
colors: {
|
||||
upward: window.colorMap.primary[500].hex,
|
||||
downward: window.colorMap.danger[500].hex
|
||||
}
|
||||
},
|
||||
bar: {
|
||||
columnWidth: '80%',
|
||||
colors: {
|
||||
ranges: [{
|
||||
from: 0,
|
||||
to: 999999999,
|
||||
color: window.colorMap.success[100].hex
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
colors: [ window.colorMap.primary[500].hex, window.colorMap.success[100].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const comboCandlestickChart = new ApexCharts(
|
||||
document.getElementById('combo-candlestick-chart'),
|
||||
comboCandlestickOptions
|
||||
);
|
||||
comboCandlestickChart.render();
|
||||
}
|
||||
|
||||
// Category X-axis Candlestick Chart
|
||||
if (document.getElementById('category-x-axis-candlestick-chart')) {
|
||||
// Data with categories
|
||||
const categorySeriesData = [
|
||||
{
|
||||
x: 'Jan',
|
||||
y: [1500, 1700, 1400, 1650]
|
||||
},
|
||||
{
|
||||
x: 'Feb',
|
||||
y: [1650, 1850, 1600, 1700]
|
||||
},
|
||||
{
|
||||
x: 'Mar',
|
||||
y: [1700, 2000, 1680, 1880]
|
||||
},
|
||||
{
|
||||
x: 'Apr',
|
||||
y: [1880, 2050, 1750, 1800]
|
||||
},
|
||||
{
|
||||
x: 'May',
|
||||
y: [1800, 1980, 1750, 1890]
|
||||
},
|
||||
{
|
||||
x: 'Jun',
|
||||
y: [1890, 2100, 1850, 2050]
|
||||
},
|
||||
{
|
||||
x: 'Jul',
|
||||
y: [2050, 2200, 1900, 2100]
|
||||
},
|
||||
{
|
||||
x: 'Aug',
|
||||
y: [2100, 2300, 2050, 2200]
|
||||
},
|
||||
{
|
||||
x: 'Sep',
|
||||
y: [2200, 2400, 2150, 2300]
|
||||
},
|
||||
{
|
||||
x: 'Oct',
|
||||
y: [2300, 2500, 2200, 2450]
|
||||
},
|
||||
{
|
||||
x: 'Nov',
|
||||
y: [2450, 2600, 2350, 2550]
|
||||
},
|
||||
{
|
||||
x: 'Dec',
|
||||
y: [2550, 2750, 2500, 2700]
|
||||
}
|
||||
];
|
||||
|
||||
const categoryXAxisOptions = {
|
||||
series: [{
|
||||
name: 'Product A',
|
||||
data: categorySeriesData
|
||||
}],
|
||||
chart: {
|
||||
type: 'candlestick',
|
||||
height: 350,
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Monthly Performance Candlestick',
|
||||
align: 'left'
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark',
|
||||
custom: function({ seriesIndex, dataPointIndex, w }) {
|
||||
const data = w.globals.initialSeries[seriesIndex].data[dataPointIndex];
|
||||
return '<div class="p-2">' +
|
||||
'<span>Month: ' + data.x + '</span><br>' +
|
||||
'<span>Open: $' + data.y[0] + '</span><br>' +
|
||||
'<span>High: $' + data.y[1] + '</span><br>' +
|
||||
'<span>Low: $' + data.y[2] + '</span><br>' +
|
||||
'<span>Close: $' + data.y[3] + '</span>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
candlestick: {
|
||||
colors: {
|
||||
upward: window.colorMap.primary[500].hex,
|
||||
downward: window.colorMap.danger[500].hex
|
||||
}
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
labels: {
|
||||
rotate: -45,
|
||||
rotateAlways: false,
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
tooltip: {
|
||||
enabled: true
|
||||
},
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return '$' + val;
|
||||
},
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const categoryXAxisChart = new ApexCharts(
|
||||
document.getElementById('category-x-axis-candlestick-chart'),
|
||||
categoryXAxisOptions
|
||||
);
|
||||
categoryXAxisChart.render();
|
||||
}
|
||||
|
||||
// Candlestick with Line Chart
|
||||
if (document.getElementById('candlestick-with-line-chart')) {
|
||||
// Generate line series data (moving average)
|
||||
const lineData = [];
|
||||
|
||||
for (let i = 0; i < seriesData.length; i++) {
|
||||
if (i >= 4) { // 5-day moving average
|
||||
let sum = 0;
|
||||
for (let j = i; j > i - 5; j--) {
|
||||
sum += seriesData[j].y[3]; // use the closing price
|
||||
}
|
||||
lineData.push({
|
||||
x: seriesData[i].x,
|
||||
y: +(sum / 5).toFixed(2)
|
||||
});
|
||||
} else {
|
||||
lineData.push({
|
||||
x: seriesData[i].x,
|
||||
y: seriesData[i].y[3]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const candlestickWithLineOptions = {
|
||||
series: [{
|
||||
name: 'Candle',
|
||||
type: 'candlestick',
|
||||
data: seriesData
|
||||
}, {
|
||||
name: '5-day MA',
|
||||
type: 'line',
|
||||
data: lineData
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'line',
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: [1, 3]
|
||||
},
|
||||
tooltip: {
|
||||
shared: true
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return new Date(val).toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
||||
},
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: [{
|
||||
tooltip: {
|
||||
enabled: true
|
||||
},
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return '$' + val.toFixed(2);
|
||||
},
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
}],
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
plotOptions: {
|
||||
candlestick: {
|
||||
colors: {
|
||||
upward: window.colorMap.primary[500].hex,
|
||||
downward: window.colorMap.danger[500].hex
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.success[300].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const candlestickWithLineChart = new ApexCharts(
|
||||
document.getElementById('candlestick-with-line-chart'),
|
||||
candlestickWithLineOptions
|
||||
);
|
||||
candlestickWithLineChart.render();
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,262 @@
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Funnel Chart
|
||||
if (document.getElementById('funnel-chart')) {
|
||||
const funnelOptions = {
|
||||
series: [
|
||||
{
|
||||
name: "Funnel",
|
||||
data: [1380, 1100, 690, 580, 380, 170]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 350,
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
borderRadius: 0,
|
||||
horizontal: true,
|
||||
distributed: true,
|
||||
barHeight: '80%',
|
||||
isFunnel: true,
|
||||
}
|
||||
},
|
||||
states: {
|
||||
hover: {
|
||||
filter: {
|
||||
type: 'darken',
|
||||
value: 0.85
|
||||
}
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
formatter: function(val, opt) {
|
||||
return opt.w.globals.labels[opt.dataPointIndex] + ': ' + val
|
||||
},
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
colors: ['#fff']
|
||||
},
|
||||
dropShadow: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Website Conversion Funnel',
|
||||
align: 'left',
|
||||
style: {
|
||||
fontSize: '16px',
|
||||
fontWeight: 600,
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
text: 'Sales Conversion Path Analysis',
|
||||
align: 'left',
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
fontWeight: 400,
|
||||
color: window.colorMap.bootstrapVars.bodyColor.rgba(0.6)
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: [
|
||||
'Visits',
|
||||
'Unique Visitors',
|
||||
'Signup',
|
||||
'Demo Requests',
|
||||
'Trials',
|
||||
'Purchases'
|
||||
],
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark',
|
||||
y: {
|
||||
formatter: function(value) {
|
||||
return value.toLocaleString();
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.primary[500].hex,
|
||||
window.colorMap.primary[400].hex,
|
||||
window.colorMap.primary[300].hex,
|
||||
window.colorMap.primary[200].hex,
|
||||
window.colorMap.primary[100].hex,
|
||||
window.colorMap.danger[500].hex
|
||||
],
|
||||
grid: {
|
||||
padding: {
|
||||
left: 20,
|
||||
right: 20
|
||||
},
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.2)
|
||||
}
|
||||
};
|
||||
|
||||
const funnelChart = new ApexCharts(
|
||||
document.getElementById('funnel-chart'),
|
||||
funnelOptions
|
||||
);
|
||||
funnelChart.render();
|
||||
}
|
||||
|
||||
// Pyramid Chart
|
||||
if (document.getElementById('pyramid-chart')) {
|
||||
const pyramidOptions = {
|
||||
series: [
|
||||
{
|
||||
name: "Pyramid",
|
||||
data: [120, 220, 350, 480, 580, 690, 1100]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 380,
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: true,
|
||||
distributed: true,
|
||||
barHeight: '85%',
|
||||
isFunnel: true,
|
||||
flipFunnel: true
|
||||
}
|
||||
},
|
||||
states: {
|
||||
hover: {
|
||||
filter: {
|
||||
type: 'darken',
|
||||
value: 0.85
|
||||
}
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
formatter: function(val, opt) {
|
||||
return opt.w.globals.labels[opt.dataPointIndex] + ': ' + val
|
||||
},
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
colors: ['#fff']
|
||||
},
|
||||
offsetX: 15,
|
||||
dropShadow: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Organization Structure',
|
||||
align: 'left',
|
||||
style: {
|
||||
fontSize: '16px',
|
||||
fontWeight: 600,
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
text: 'Employee Distribution by Level',
|
||||
align: 'left',
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
fontWeight: 400,
|
||||
color: window.colorMap.bootstrapVars.bodyColor.rgba(0.6)
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
padding: {
|
||||
left: 20,
|
||||
right: 20
|
||||
},
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.2)
|
||||
},
|
||||
xaxis: {
|
||||
categories: [
|
||||
'CEO',
|
||||
'Directors',
|
||||
'Managers',
|
||||
'Team Leads',
|
||||
'Senior Staff',
|
||||
'Junior Staff',
|
||||
'Interns'
|
||||
],
|
||||
labels: {
|
||||
show: false,
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
axisBorder: {
|
||||
show: false
|
||||
},
|
||||
axisTicks: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark',
|
||||
y: {
|
||||
formatter: function(value) {
|
||||
return value + ' employees';
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.danger[500].hex,
|
||||
window.colorMap.primary[100].hex,
|
||||
window.colorMap.primary[200].hex,
|
||||
window.colorMap.primary[300].hex,
|
||||
window.colorMap.primary[400].hex,
|
||||
window.colorMap.primary[500].hex,
|
||||
window.colorMap.primary[600].hex
|
||||
]
|
||||
};
|
||||
|
||||
const pyramidChart = new ApexCharts(
|
||||
document.getElementById('pyramid-chart'),
|
||||
pyramidOptions
|
||||
);
|
||||
pyramidChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,527 @@
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
function generateData(count, yrange) {
|
||||
var i = 0;
|
||||
var series = [];
|
||||
while (i < count) {
|
||||
var x = 'w' + (i + 1).toString();
|
||||
var y = Math.floor(Math.random() * (yrange.max - yrange.min + 1)) + yrange.min;
|
||||
|
||||
series.push({
|
||||
x: x,
|
||||
y: y
|
||||
});
|
||||
i++;
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
// Basic Heatmap Chart
|
||||
if (document.getElementById('basic-heatmap-chart')) {
|
||||
const basicHeatmapOptions = {
|
||||
series: [{
|
||||
name: 'Metric 1',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 2',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 3',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 4',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 5',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 6',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 7',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 8',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 9',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'heatmap',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
grid: {
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.2)
|
||||
},
|
||||
xaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const basicHeatmapChart = new ApexCharts(
|
||||
document.getElementById('basic-heatmap-chart'),
|
||||
basicHeatmapOptions
|
||||
);
|
||||
basicHeatmapChart.render();
|
||||
}
|
||||
|
||||
// Heatmap Chart with Multiple Series
|
||||
if (document.getElementById('multiple-series-heatmap-chart')) {
|
||||
const multipleSeriesHeatmapOptions = {
|
||||
series: [{
|
||||
name: 'Jan',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Feb',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Mar',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Apr',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'May',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Jun',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Jul',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Aug',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Sep',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'heatmap',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
categories: ['10:00', '10:30', '11:00', '11:30', '12:00', '12:30', '01:00', '01:30', '02:00', '02:30', '03:00', '03:30', '04:00', '04:30', '05:00', '05:30', '06:00', '06:30', '07:00', '07:30'],
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.2)
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
legend: {
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const multipleSeriesHeatmapChart = new ApexCharts(
|
||||
document.getElementById('multiple-series-heatmap-chart'),
|
||||
multipleSeriesHeatmapOptions
|
||||
);
|
||||
multipleSeriesHeatmapChart.render();
|
||||
}
|
||||
|
||||
// Heatmap Chart with Color Range
|
||||
if (document.getElementById('color-range-heatmap-chart')) {
|
||||
const colorRangeHeatmapOptions = {
|
||||
series: [{
|
||||
name: 'Jan',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Feb',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Mar',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Apr',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'May',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Jun',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Jul',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Aug',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Sep',
|
||||
data: generateData(20, {
|
||||
min: -30,
|
||||
max: 55
|
||||
})
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'heatmap',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
heatmap: {
|
||||
shadeIntensity: 0.5,
|
||||
radius: 0,
|
||||
useFillColorAsStroke: true,
|
||||
colorScale: {
|
||||
ranges: [{
|
||||
from: -30,
|
||||
to: 5,
|
||||
name: 'Low',
|
||||
color: window.colorMap.success[100].hex
|
||||
},
|
||||
{
|
||||
from: 6,
|
||||
to: 20,
|
||||
name: 'Medium',
|
||||
color: window.colorMap.primary[300].hex
|
||||
},
|
||||
{
|
||||
from: 21,
|
||||
to: 45,
|
||||
name: 'High',
|
||||
color: window.colorMap.primary[500].hex
|
||||
},
|
||||
{
|
||||
from: 46,
|
||||
to: 55,
|
||||
name: 'Extreme',
|
||||
color: window.colorMap.danger[500].hex
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
xaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.2)
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
legend: {
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const colorRangeHeatmapChart = new ApexCharts(
|
||||
document.getElementById('color-range-heatmap-chart'),
|
||||
colorRangeHeatmapOptions
|
||||
);
|
||||
colorRangeHeatmapChart.render();
|
||||
}
|
||||
|
||||
// Heatmap Chart with Rounded Corners
|
||||
if (document.getElementById('rounded-heatmap-chart')) {
|
||||
const roundedHeatmapOptions = {
|
||||
series: [{
|
||||
name: 'Metric 1',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 2',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 3',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 4',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 5',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 6',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 7',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 8',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'Metric 9',
|
||||
data: generateData(18, {
|
||||
min: 0,
|
||||
max: 90
|
||||
})
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'heatmap',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
heatmap: {
|
||||
radius: 15,
|
||||
enableShades: false,
|
||||
colorScale: {
|
||||
ranges: [{
|
||||
from: 0,
|
||||
to: 30,
|
||||
color: window.colorMap.primary[300].hex
|
||||
},
|
||||
{
|
||||
from: 31,
|
||||
to: 60,
|
||||
color: window.colorMap.primary[500].hex
|
||||
},
|
||||
{
|
||||
from: 61,
|
||||
to: 90,
|
||||
color: window.colorMap.primary[700].hex
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
xaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.2)
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
legend: {
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const roundedHeatmapChart = new ApexCharts(
|
||||
document.getElementById('rounded-heatmap-chart'),
|
||||
roundedHeatmapOptions
|
||||
);
|
||||
roundedHeatmapChart.render();
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,770 @@
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Line and Column Chart
|
||||
if (document.getElementById('line-column-chart')) {
|
||||
const lineColumnOptions = {
|
||||
series: [{
|
||||
name: 'Website',
|
||||
type: 'column',
|
||||
data: [440, 505, 414, 671, 227, 413, 201, 352, 752, 320, 257, 160]
|
||||
}, {
|
||||
name: 'Social Media',
|
||||
type: 'line',
|
||||
data: [23, 42, 35, 27, 43, 22, 17, 31, 22, 22, 12, 16]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'line',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: [0, 4]
|
||||
},
|
||||
title: {
|
||||
text: 'Traffic Sources',
|
||||
align: 'left',
|
||||
style: {
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
enabledOnSeries: [1]
|
||||
},
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
},
|
||||
yaxis: [{
|
||||
title: {
|
||||
text: 'Website Visits',
|
||||
style: {
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
}, {
|
||||
opposite: true,
|
||||
title: {
|
||||
text: 'Social Media',
|
||||
style: {
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
}],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.danger[500].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const lineColumnChart = new ApexCharts(
|
||||
document.getElementById('line-column-chart'),
|
||||
lineColumnOptions
|
||||
);
|
||||
lineColumnChart.render();
|
||||
}
|
||||
|
||||
// Multiple Y-Axis Chart
|
||||
if (document.getElementById('multiple-y-axis-chart')) {
|
||||
const multipleYAxisOptions = {
|
||||
series: [{
|
||||
name: 'Income',
|
||||
type: 'column',
|
||||
data: [1.4, 2.1, 4.9, 6.5, 8.2, 7.1, 5.6, 4.2, 3.5, 2.8, 2.3, 1.8]
|
||||
}, {
|
||||
name: 'Cashflow',
|
||||
type: 'column',
|
||||
data: [1.1, 3.2, 4.3, 5.8, 8.4, 6.5, 4.9, 3.9, 3.1, 2.5, 2.0, 1.5]
|
||||
}, {
|
||||
name: 'Revenue',
|
||||
type: 'line',
|
||||
data: [20, 29, 37, 46, 56, 42, 35, 30, 25, 22, 17, 15]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'line',
|
||||
stacked: false,
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
stroke: {
|
||||
width: [1, 1, 4]
|
||||
},
|
||||
title: {
|
||||
text: 'Financial Overview (Multiple Y-axis)',
|
||||
align: 'left',
|
||||
},
|
||||
xaxis: {
|
||||
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
},
|
||||
yaxis: [
|
||||
{
|
||||
axisTicks: {
|
||||
show: true,
|
||||
},
|
||||
axisBorder: {
|
||||
show: true,
|
||||
color: window.colorMap.primary[500].hex
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.primary[500].hex,
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: "Income (thousand $)",
|
||||
style: {
|
||||
color: window.colorMap.primary[500].hex,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
{
|
||||
seriesName: 'Income',
|
||||
opposite: true,
|
||||
axisTicks: {
|
||||
show: true,
|
||||
},
|
||||
axisBorder: {
|
||||
show: true,
|
||||
color: window.colorMap.primary[300].hex
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.primary[300].hex,
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: "Cashflow (thousand $)",
|
||||
style: {
|
||||
color: window.colorMap.primary[300].hex,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
seriesName: 'Revenue',
|
||||
opposite: true,
|
||||
axisTicks: {
|
||||
show: true,
|
||||
},
|
||||
axisBorder: {
|
||||
show: true,
|
||||
color: window.colorMap.danger[500].hex
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.danger[500].hex,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
text: "Revenue (thousand $)",
|
||||
style: {
|
||||
color: window.colorMap.danger[500].hex,
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function (val) {
|
||||
return val + " thousand $";
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
horizontalAlign: 'center',
|
||||
position: 'bottom',
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[300].hex, window.colorMap.danger[500].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const multipleYAxisChart = new ApexCharts(
|
||||
document.getElementById('multiple-y-axis-chart'),
|
||||
multipleYAxisOptions
|
||||
);
|
||||
multipleYAxisChart.render();
|
||||
}
|
||||
|
||||
// Line and Area Chart
|
||||
if (document.getElementById('line-area-chart')) {
|
||||
const lineAreaOptions = {
|
||||
series: [{
|
||||
name: 'TEAM A',
|
||||
type: 'area',
|
||||
data: [44, 55, 31, 47, 31, 43, 26, 41, 31, 47, 33]
|
||||
}, {
|
||||
name: 'TEAM B',
|
||||
type: 'line',
|
||||
data: [55, 69, 45, 61, 43, 54, 37, 52, 44, 61, 43]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'line',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
curve: 'smooth',
|
||||
width: [3, 3]
|
||||
},
|
||||
fill: {
|
||||
type: ['gradient', 'solid'],
|
||||
opacity: [0.35, 1],
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
type: 'vertical',
|
||||
shadeIntensity: 0.4,
|
||||
gradientToColors: [window.colorMap.bootstrapVars.bodyBg.hex],
|
||||
inverseColors: false,
|
||||
opacityFrom: 0.7,
|
||||
opacityTo: 0.2,
|
||||
stops: [0, 100]
|
||||
}
|
||||
},
|
||||
labels: ['Dec 01', 'Dec 02', 'Dec 03', 'Dec 04', 'Dec 05', 'Dec 06', 'Dec 07', 'Dec 08', 'Dec 09', 'Dec 10', 'Dec 11'],
|
||||
markers: {
|
||||
size: 0
|
||||
},
|
||||
title: {
|
||||
text: 'Team Performance',
|
||||
align: 'left',
|
||||
},
|
||||
yaxis: [
|
||||
{
|
||||
title: {
|
||||
text: 'Series A',
|
||||
style: {
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
opposite: true,
|
||||
title: {
|
||||
text: 'Series B',
|
||||
style: {
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
theme: 'dark',
|
||||
shared: true,
|
||||
intersect: false,
|
||||
y: {
|
||||
formatter: function (val) {
|
||||
return val.toFixed(0) + " points";
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
horizontalAlign: 'center',
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.danger[500].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const lineAreaChart = new ApexCharts(
|
||||
document.getElementById('line-area-chart'),
|
||||
lineAreaOptions
|
||||
);
|
||||
lineAreaChart.render();
|
||||
}
|
||||
|
||||
// Line Column Area Chart
|
||||
if (document.getElementById('line-column-area-chart')) {
|
||||
const lineColumnAreaOptions = {
|
||||
series: [{
|
||||
name: 'Revenue',
|
||||
type: 'column',
|
||||
data: [1.4, 2.3, 3.5, 4.2, 5.1, 4.3, 3.8, 3.2, 2.9, 3.5, 4.2, 4.8]
|
||||
}, {
|
||||
name: 'Free Cash Flow',
|
||||
type: 'area',
|
||||
data: [0.9, 1.5, 2.2, 3.1, 3.8, 3.5, 3.2, 2.8, 2.5, 2.9, 3.5, 4.0]
|
||||
}, {
|
||||
name: 'Operating Margin',
|
||||
type: 'line',
|
||||
data: [15, 18, 20, 22, 25, 23, 21, 19, 20, 22, 24, 26]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'line',
|
||||
stacked: false,
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: [0, 2, 4],
|
||||
curve: 'smooth'
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: '50%'
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
type: ['solid', 'gradient', 'solid'],
|
||||
opacity: [0.85, 0.25, 1],
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
type: 'vertical',
|
||||
shadeIntensity: 0.4,
|
||||
gradientToColors: [window.colorMap.bootstrapVars.bodyBg.hex],
|
||||
inverseColors: false,
|
||||
opacityFrom: 0.7,
|
||||
opacityTo: 0.2,
|
||||
stops: [0, 100]
|
||||
}
|
||||
},
|
||||
markers: {
|
||||
size: 0
|
||||
},
|
||||
xaxis: {
|
||||
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
},
|
||||
yaxis: [
|
||||
{
|
||||
title: {
|
||||
text: 'Revenue (billions)',
|
||||
style: {
|
||||
color: window.colorMap.primary[500].hex
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return '$' + val.toFixed(1) + 'B';
|
||||
},
|
||||
style: {
|
||||
colors: window.colorMap.primary[500].hex
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
seriesName: 'Free Cash Flow',
|
||||
title: {
|
||||
text: 'Free Cash Flow (billions)',
|
||||
style: {
|
||||
color: window.colorMap.primary[300].hex
|
||||
}
|
||||
},
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return '$' + val.toFixed(1) + 'B';
|
||||
},
|
||||
style: {
|
||||
colors: window.colorMap.primary[300].hex
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
seriesName: 'Operating Margin',
|
||||
title: {
|
||||
text: 'Operating Margin (%)',
|
||||
style: {
|
||||
color: window.colorMap.danger[500].hex
|
||||
}
|
||||
},
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return val.toFixed(0) + '%';
|
||||
},
|
||||
style: {
|
||||
colors: window.colorMap.danger[500].hex
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false,
|
||||
y: [
|
||||
{
|
||||
formatter: function(y) {
|
||||
if (typeof y !== "undefined") {
|
||||
return '$' + y.toFixed(2) + ' billion';
|
||||
}
|
||||
return y;
|
||||
}
|
||||
},
|
||||
{
|
||||
formatter: function(y) {
|
||||
if (typeof y !== "undefined") {
|
||||
return '$' + y.toFixed(2) + ' billion';
|
||||
}
|
||||
return y;
|
||||
}
|
||||
},
|
||||
{
|
||||
formatter: function(y) {
|
||||
if (typeof y !== "undefined") {
|
||||
return y.toFixed(1) + '%';
|
||||
}
|
||||
return y;
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
title: {
|
||||
text: 'Financial Performance Overview',
|
||||
align: 'left',
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
horizontalAlign: 'center',
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[300].hex, window.colorMap.danger[500].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const lineColumnAreaChart = new ApexCharts(
|
||||
document.getElementById('line-column-area-chart'),
|
||||
lineColumnAreaOptions
|
||||
);
|
||||
lineColumnAreaChart.render();
|
||||
}
|
||||
|
||||
// Candlestick Line Chart
|
||||
if (document.getElementById('candlestick-line-chart')) {
|
||||
const candlestickLineOptions = {
|
||||
series: [{
|
||||
name: 'Candle',
|
||||
type: 'candlestick',
|
||||
data: [
|
||||
{ x: new Date(2016, 1, 1), y: [51.98, 56.29, 51.59, 53.85] },
|
||||
{ x: new Date(2016, 2, 1), y: [53.66, 54.99, 51.35, 52.95] },
|
||||
{ x: new Date(2016, 3, 1), y: [52.96, 53.78, 51.54, 52.48] },
|
||||
{ x: new Date(2016, 4, 1), y: [52.54, 52.79, 47.88, 49.24] },
|
||||
{ x: new Date(2016, 5, 1), y: [49.10, 52.86, 47.70, 52.78] },
|
||||
{ x: new Date(2016, 6, 1), y: [52.83, 53.48, 50.32, 52.29] },
|
||||
{ x: new Date(2016, 7, 1), y: [52.20, 54.48, 51.64, 52.58] },
|
||||
{ x: new Date(2016, 8, 1), y: [52.76, 57.35, 52.15, 57.03] },
|
||||
{ x: new Date(2016, 9, 1), y: [57.04, 58.15, 48.88, 56.19] },
|
||||
{ x: new Date(2016, 10, 1), y: [56.09, 58.85, 55.48, 58.79] },
|
||||
{ x: new Date(2016, 11, 1), y: [58.78, 59.65, 58.23, 59.05] },
|
||||
{ x: new Date(2017, 0, 1), y: [59.37, 61.11, 59.35, 60.34] },
|
||||
{ x: new Date(2017, 1, 1), y: [60.40, 60.52, 56.71, 56.93] },
|
||||
{ x: new Date(2017, 2, 1), y: [57.02, 59.71, 56.04, 56.82] },
|
||||
{ x: new Date(2017, 3, 1), y: [56.97, 59.62, 54.77, 59.30] },
|
||||
{ x: new Date(2017, 4, 1), y: [59.11, 62.29, 59.10, 59.85] }
|
||||
]
|
||||
}, {
|
||||
name: 'Moving Average',
|
||||
type: 'line',
|
||||
data: [
|
||||
{ x: new Date(2016, 1, 1), y: 53.85 },
|
||||
{ x: new Date(2016, 2, 1), y: 53.40 },
|
||||
{ x: new Date(2016, 3, 1), y: 52.93 },
|
||||
{ x: new Date(2016, 4, 1), y: 51.08 },
|
||||
{ x: new Date(2016, 5, 1), y: 51.94 },
|
||||
{ x: new Date(2016, 6, 1), y: 52.05 },
|
||||
{ x: new Date(2016, 7, 1), y: 52.32 },
|
||||
{ x: new Date(2016, 8, 1), y: 54.71 },
|
||||
{ x: new Date(2016, 9, 1), y: 55.44 },
|
||||
{ x: new Date(2016, 10, 1), y: 57.14 },
|
||||
{ x: new Date(2016, 11, 1), y: 58.02 },
|
||||
{ x: new Date(2017, 0, 1), y: 59.17 },
|
||||
{ x: new Date(2017, 1, 1), y: 58.63 },
|
||||
{ x: new Date(2017, 2, 1), y: 56.93 },
|
||||
{ x: new Date(2017, 3, 1), y: 57.41 },
|
||||
{ x: new Date(2017, 4, 1), y: 59.63 }
|
||||
]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'line',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Stock Market Analysis',
|
||||
align: 'left',
|
||||
},
|
||||
stroke: {
|
||||
width: [0, 3]
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
yaxis: {
|
||||
tooltip: {
|
||||
enabled: true
|
||||
},
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return '$' + val.toFixed(2);
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
custom: [{
|
||||
seriesIndex: 0,
|
||||
formatter: function(opts) {
|
||||
const data = opts.ctx.w.globals.initialSeries[opts.seriesIndex].data[opts.dataPointIndex];
|
||||
return `
|
||||
<div class="p-2">
|
||||
<div>Date: ${new Date(data.x).toLocaleDateString()}</div>
|
||||
<div>Open: $${data.y[0].toFixed(2)}</div>
|
||||
<div>High: $${data.y[1].toFixed(2)}</div>
|
||||
<div>Low: $${data.y[2].toFixed(2)}</div>
|
||||
<div>Close: $${data.y[3].toFixed(2)}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}, {
|
||||
seriesIndex: 1,
|
||||
formatter: function(opts) {
|
||||
const data = opts.ctx.w.globals.initialSeries[opts.seriesIndex].data[opts.dataPointIndex];
|
||||
return `
|
||||
<div class="p-2">
|
||||
<div>Date: ${new Date(data.x).toLocaleDateString()}</div>
|
||||
<div>MA: $${data.y.toFixed(2)}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}],
|
||||
},
|
||||
plotOptions: {
|
||||
candlestick: {
|
||||
colors: {
|
||||
upward: window.colorMap.primary[500].hex,
|
||||
downward: window.colorMap.danger[500].hex
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.danger[500].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const candlestickLineChart = new ApexCharts(
|
||||
document.getElementById('candlestick-line-chart'),
|
||||
candlestickLineOptions
|
||||
);
|
||||
candlestickLineChart.render();
|
||||
}
|
||||
|
||||
// Line Scatter Chart
|
||||
if (document.getElementById('line-scatter-chart')) {
|
||||
// Generate sample data for the scatter plot
|
||||
const generateScatterData = (count, min, max) => {
|
||||
const data = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const x = min + Math.random() * (max - min);
|
||||
const y = min + Math.random() * (max - min);
|
||||
data.push({ x, y });
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const lineScatterOptions = {
|
||||
series: [{
|
||||
name: 'Trend Line',
|
||||
type: 'line',
|
||||
data: [
|
||||
{ x: 0, y: 2 },
|
||||
{ x: 1, y: 3 },
|
||||
{ x: 2, y: 5 },
|
||||
{ x: 3, y: 6 },
|
||||
{ x: 4, y: 8 },
|
||||
{ x: 5, y: 9 },
|
||||
{ x: 6, y: 11 },
|
||||
{ x: 7, y: 12 },
|
||||
{ x: 8, y: 14 },
|
||||
{ x: 9, y: 15 },
|
||||
{ x: 10, y: 16 }
|
||||
]
|
||||
}, {
|
||||
name: 'Group A',
|
||||
type: 'scatter',
|
||||
data: generateScatterData(20, 0, 5)
|
||||
}, {
|
||||
name: 'Group B',
|
||||
type: 'scatter',
|
||||
data: generateScatterData(15, 3, 8)
|
||||
}, {
|
||||
name: 'Group C',
|
||||
type: 'scatter',
|
||||
data: generateScatterData(10, 6, 10)
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'line',
|
||||
toolbar: {
|
||||
show: true
|
||||
},
|
||||
zoom: {
|
||||
enabled: true,
|
||||
type: 'xy'
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: [3, 0, 0, 0],
|
||||
curve: 'straight'
|
||||
},
|
||||
fill: {
|
||||
opacity: [1, 0.7, 0.7, 0.7]
|
||||
},
|
||||
markers: {
|
||||
size: [0, 6, 6, 6],
|
||||
hover: {
|
||||
size: 8
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Line and Scatter Combination',
|
||||
align: 'left',
|
||||
},
|
||||
subtitle: {
|
||||
text: 'Linear Trend with Data Points',
|
||||
align: 'left',
|
||||
},
|
||||
xaxis: {
|
||||
type: 'numeric',
|
||||
min: 0,
|
||||
max: 10,
|
||||
tickAmount: 10,
|
||||
title: {
|
||||
text: 'X Axis',
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: 20,
|
||||
title: {
|
||||
text: 'Y Axis',
|
||||
},
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return val.toFixed(1);
|
||||
},
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: false,
|
||||
intersect: true,
|
||||
x: {
|
||||
show: true,
|
||||
format: '0.0'
|
||||
},
|
||||
y: {
|
||||
formatter: function(val) {
|
||||
return val.toFixed(2);
|
||||
}
|
||||
},
|
||||
marker: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
horizontalAlign: 'center',
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex, window.colorMap.danger[500].hex]
|
||||
};
|
||||
|
||||
const lineScatterChart = new ApexCharts(
|
||||
document.getElementById('line-scatter-chart'),
|
||||
lineScatterOptions
|
||||
);
|
||||
lineScatterChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,748 @@
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Simple Pie Chart
|
||||
if (document.getElementById('simple-pie-chart')) {
|
||||
const simplePieOptions = {
|
||||
series: [44, 55, 13, 43, 22],
|
||||
chart: {
|
||||
width: '100%',
|
||||
height: 380,
|
||||
type: 'pie',
|
||||
},
|
||||
title: {
|
||||
text: 'Simple Pie Chart',
|
||||
align: 'left',
|
||||
style: {
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
labels: ['Team A', 'Team B', 'Team C', 'Team D', 'Team E'],
|
||||
legend: {
|
||||
position: 'right',
|
||||
offsetY: 0,
|
||||
height: 230,
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 300
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex, window.colorMap.primary[600].hex],
|
||||
};
|
||||
|
||||
const simplePieChart = new ApexCharts(
|
||||
document.getElementById('simple-pie-chart'),
|
||||
simplePieOptions
|
||||
);
|
||||
simplePieChart.render();
|
||||
}
|
||||
|
||||
// Simple Donut Chart
|
||||
if (document.getElementById('simple-donut-chart')) {
|
||||
const simpleDonutOptions = {
|
||||
series: [44, 55, 41, 17, 15],
|
||||
chart: {
|
||||
width: '100%',
|
||||
height: 380,
|
||||
type: 'donut',
|
||||
},
|
||||
title: {
|
||||
text: 'Simple Donut Chart',
|
||||
align: 'left',
|
||||
},
|
||||
labels: ['Team A', 'Team B', 'Team C', 'Team D', 'Team E'],
|
||||
legend: {
|
||||
position: 'right',
|
||||
offsetY: 0,
|
||||
height: 230,
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 300
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex, window.colorMap.primary[600].hex],
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
}
|
||||
};
|
||||
|
||||
const simpleDonutChart = new ApexCharts(
|
||||
document.getElementById('simple-donut-chart'),
|
||||
simpleDonutOptions
|
||||
);
|
||||
simpleDonutChart.render();
|
||||
}
|
||||
|
||||
// Donut Update Chart
|
||||
if (document.getElementById('donut-update-chart')) {
|
||||
const donutUpdateOptions = {
|
||||
series: [44, 55, 13, 33],
|
||||
chart: {
|
||||
width: '100%',
|
||||
height: 380,
|
||||
type: 'donut',
|
||||
},
|
||||
title: {
|
||||
text: 'Donut Update Chart (Click to Update)',
|
||||
align: 'left',
|
||||
},
|
||||
labels: ['Team A', 'Team B', 'Team C', 'Team D'],
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
formatter: function(val, opts) {
|
||||
return opts.w.config.series[opts.seriesIndex] + '%';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'right',
|
||||
offsetY: 0,
|
||||
height: 230,
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 300
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex],
|
||||
};
|
||||
|
||||
const donutUpdateChart = new ApexCharts(
|
||||
document.getElementById('donut-update-chart'),
|
||||
donutUpdateOptions
|
||||
);
|
||||
donutUpdateChart.render();
|
||||
|
||||
// Add click event for updating the chart
|
||||
document.getElementById('donut-update-chart').addEventListener('click', function() {
|
||||
// Generate random values for the update
|
||||
donutUpdateChart.updateSeries([
|
||||
Math.floor(Math.random() * 91) + 10,
|
||||
Math.floor(Math.random() * 91) + 10,
|
||||
Math.floor(Math.random() * 91) + 10,
|
||||
Math.floor(Math.random() * 91) + 10
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
// Monochrome Pie Chart - keep only the bottom example
|
||||
if (document.getElementById('monochrome-pie-chart')) {
|
||||
const monochromePieOptions = {
|
||||
series: [12.7, 7.6, 22.3, 27.9, 20.8, 8.5],
|
||||
chart: {
|
||||
width: '100%',
|
||||
height: 380,
|
||||
type: 'pie',
|
||||
},
|
||||
title: {
|
||||
text: 'Monochrome Pie Chart',
|
||||
align: 'left',
|
||||
},
|
||||
labels: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
theme: {
|
||||
monochrome: {
|
||||
enabled: true,
|
||||
color: window.colorMap.primary[300].hex,
|
||||
shadeTo: 'dark',
|
||||
shadeIntensity: 0.65
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'right',
|
||||
offsetY: 0,
|
||||
height: 230,
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
formatter: function(val, opts) {
|
||||
return val.toFixed(1) + '%';
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 300
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
const monochromePieChart = new ApexCharts(
|
||||
document.getElementById('monochrome-pie-chart'),
|
||||
monochromePieOptions
|
||||
);
|
||||
monochromePieChart.render();
|
||||
}
|
||||
|
||||
// Gradient Donut Chart - make gradient much more visible
|
||||
if (document.getElementById('gradient-donut-chart')) {
|
||||
const gradientDonutOptions = {
|
||||
series: [25.5, 32, 21.8, 9.9, 8.7],
|
||||
chart: {
|
||||
width: '100%',
|
||||
height: 380,
|
||||
type: 'donut',
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
startAngle: 0,
|
||||
endAngle: 360,
|
||||
donut: {
|
||||
size: '65%',
|
||||
labels: {
|
||||
show: true,
|
||||
name: {
|
||||
show: true,
|
||||
},
|
||||
value: {
|
||||
show: true,
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
formatter: function (val) {
|
||||
return val.toFixed(1) + '%';
|
||||
}
|
||||
},
|
||||
total: {
|
||||
show: true,
|
||||
label: 'Total',
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
formatter: function (w) {
|
||||
return w.globals.seriesTotals.reduce((a, b) => {
|
||||
return a + b;
|
||||
}, 0).toFixed(1) + '%';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
gradientToColors: ['var(--primary-900)', 'var(--success-900)', 'var(--info-900)', 'var(--danger-900)', 'var(--warning-900)'],
|
||||
shade: 'dark',
|
||||
type: 'vertical',
|
||||
inverseColors: true,
|
||||
opacityFrom: 1,
|
||||
opacityTo: 0.6,
|
||||
stops: [0, 100]
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: 0
|
||||
},
|
||||
legend: {
|
||||
position: 'right',
|
||||
offsetY: 0,
|
||||
height: 230,
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Gradient Donut Chart',
|
||||
align: 'left',
|
||||
},
|
||||
labels: ['Products', 'Services', 'Support', 'Marketing', 'R&D'],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.success[500].hex, window.colorMap.info[500].hex, window.colorMap.danger[500].hex, window.colorMap.warning[500].hex],
|
||||
};
|
||||
|
||||
const gradientDonutChart = new ApexCharts(
|
||||
document.getElementById('gradient-donut-chart'),
|
||||
gradientDonutOptions
|
||||
);
|
||||
gradientDonutChart.render();
|
||||
}
|
||||
|
||||
// Donut with Pattern chart
|
||||
if (document.getElementById('donut-with-pattern-chart')) {
|
||||
const patternDonutOptions = {
|
||||
series: [44, 55, 41, 17, 15],
|
||||
chart: {
|
||||
width: '100%',
|
||||
height: 380,
|
||||
type: 'donut',
|
||||
dropShadow: {
|
||||
enabled: true,
|
||||
color: '#111',
|
||||
top: -1,
|
||||
left: 3,
|
||||
blur: 3,
|
||||
opacity: 0.2
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: 0,
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
donut: {
|
||||
labels: {
|
||||
show: true,
|
||||
name: {
|
||||
show: true,
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
fontSize: '14px'
|
||||
},
|
||||
value: {
|
||||
show: true,
|
||||
fontSize: '16px',
|
||||
formatter: function (val) {
|
||||
return val + '%';
|
||||
},
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
background: {
|
||||
enabled: true,
|
||||
foreColor: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
total: {
|
||||
show: true,
|
||||
showAlways: true,
|
||||
label: 'Total',
|
||||
fontSize: '16px',
|
||||
fontWeight: 600,
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
formatter: function (w) {
|
||||
return w.globals.seriesTotals.reduce((a, b) => {
|
||||
return a + b;
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
labels: ['Comedy', 'Action', 'SciFi', 'Drama', 'Horror'],
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
dropShadow: {
|
||||
blur: 3,
|
||||
opacity: 0.8
|
||||
},
|
||||
background: {
|
||||
enabled: true,
|
||||
foreColor: '#000',
|
||||
padding: 4,
|
||||
borderRadius: 2,
|
||||
borderWidth: 1,
|
||||
opacity: 0.9
|
||||
},
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
colors: ['#fff']
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
type: 'pattern',
|
||||
opacity: 1,
|
||||
pattern: {
|
||||
enabled: true,
|
||||
style: ['verticalLines', 'squares', 'horizontalLines', 'circles', 'slantedLines'],
|
||||
}
|
||||
},
|
||||
states: {
|
||||
hover: {
|
||||
filter: 'none'
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
palette: 'palette2'
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.primary[500].hex,
|
||||
window.colorMap.info[500].hex,
|
||||
window.colorMap.success[500].hex,
|
||||
window.colorMap.danger[500].hex,
|
||||
window.colorMap.warning[500].hex
|
||||
],
|
||||
title: {
|
||||
text: 'Favorite Movie Genre',
|
||||
align: 'left',
|
||||
style: {
|
||||
color: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'right',
|
||||
offsetY: 0,
|
||||
height: 230,
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 300
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
const patternDonutChart = new ApexCharts(
|
||||
document.getElementById('donut-with-pattern-chart'),
|
||||
patternDonutOptions
|
||||
);
|
||||
patternDonutChart.render();
|
||||
}
|
||||
|
||||
// Basic Pie Chart
|
||||
if (document.getElementById('basic-pie-chart')) {
|
||||
const basicPieOptions = {
|
||||
series: [44, 55, 13, 43, 22],
|
||||
chart: {
|
||||
width: 380,
|
||||
type: 'pie',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
labels: ['Team A', 'Team B', 'Team C', 'Team D', 'Team E'],
|
||||
title: {
|
||||
text: 'Basic Pie Chart',
|
||||
align: 'left'
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 300
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex, window.colorMap.primary[600].hex]
|
||||
};
|
||||
|
||||
const basicPieChart = new ApexCharts(
|
||||
document.getElementById('basic-pie-chart'),
|
||||
basicPieOptions
|
||||
);
|
||||
basicPieChart.render();
|
||||
}
|
||||
|
||||
// Pie Chart with Labels
|
||||
if (document.getElementById('pie-with-labels-chart')) {
|
||||
const pieWithLabelsOptions = {
|
||||
series: [44, 55, 13, 43, 22],
|
||||
chart: {
|
||||
width: 380,
|
||||
type: 'pie',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
labels: ['Team A', 'Team B', 'Team C', 'Team D', 'Team E'],
|
||||
title: {
|
||||
text: 'Pie Chart with Custom Labels',
|
||||
align: 'left'
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
formatter: function(val, opts) {
|
||||
return opts.w.config.series[opts.seriesIndex] + ' (' + val.toFixed(1) + '%)';
|
||||
},
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
dropShadow: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 300
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex, window.colorMap.primary[600].hex]
|
||||
};
|
||||
|
||||
const pieWithLabelsChart = new ApexCharts(
|
||||
document.getElementById('pie-with-labels-chart'),
|
||||
pieWithLabelsOptions
|
||||
);
|
||||
pieWithLabelsChart.render();
|
||||
}
|
||||
|
||||
// Basic Donut Chart
|
||||
if (document.getElementById('basic-donut-chart')) {
|
||||
const basicDonutOptions = {
|
||||
series: [44, 55, 41, 17, 15],
|
||||
chart: {
|
||||
width: 380,
|
||||
type: 'donut',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
labels: ['Products', 'Services', 'Support', 'Marketing', 'R&D'],
|
||||
title: {
|
||||
text: 'Basic Donut Chart',
|
||||
align: 'left'
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 300
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex, window.colorMap.primary[600].hex]
|
||||
};
|
||||
|
||||
const basicDonutChart = new ApexCharts(
|
||||
document.getElementById('basic-donut-chart'),
|
||||
basicDonutOptions
|
||||
);
|
||||
basicDonutChart.render();
|
||||
}
|
||||
|
||||
// Donut Chart with Pattern
|
||||
if (document.getElementById('donut-pattern-chart')) {
|
||||
const donutPatternOptions = {
|
||||
series: [44, 55, 41, 17, 15],
|
||||
chart: {
|
||||
width: 380,
|
||||
type: 'donut',
|
||||
dropShadow: {
|
||||
enabled: true,
|
||||
color: '#111',
|
||||
top: -1,
|
||||
left: 3,
|
||||
blur: 3,
|
||||
opacity: 0.2
|
||||
},
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
labels: ['Products', 'Services', 'Support', 'Marketing', 'R&D'],
|
||||
title: {
|
||||
text: 'Donut Chart with Pattern',
|
||||
align: 'left'
|
||||
},
|
||||
stroke: {
|
||||
width: 0,
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
fill: {
|
||||
type: 'pattern',
|
||||
opacity: 1,
|
||||
pattern: {
|
||||
enabled: true,
|
||||
style: ['verticalLines', 'squares', 'horizontalLines', 'circles', 'slantedLines']
|
||||
}
|
||||
},
|
||||
states: {
|
||||
hover: {
|
||||
filter: {
|
||||
type: 'none'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 300
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex, window.colorMap.primary[600].hex]
|
||||
};
|
||||
|
||||
const donutPatternChart = new ApexCharts(
|
||||
document.getElementById('donut-pattern-chart'),
|
||||
donutPatternOptions
|
||||
);
|
||||
donutPatternChart.render();
|
||||
}
|
||||
|
||||
// Semi Donut Chart - fix spacing issue
|
||||
if (document.getElementById('semi-donut-chart')) {
|
||||
const semiDonutOptions = {
|
||||
series: [44, 55, 41, 17, 15],
|
||||
chart: {
|
||||
width: '100%',
|
||||
height: 300,
|
||||
type: 'donut',
|
||||
},
|
||||
title: {
|
||||
text: 'Semi Donut Chart',
|
||||
align: 'left',
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
startAngle: -90,
|
||||
endAngle: 90,
|
||||
offsetY: -20,
|
||||
donut: {
|
||||
size: '65%',
|
||||
labels: {
|
||||
show: true,
|
||||
name: {
|
||||
show: true,
|
||||
},
|
||||
value: {
|
||||
show: true,
|
||||
formatter: function (val) {
|
||||
return val + '%';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
padding: {
|
||||
bottom: -100
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'right',
|
||||
offsetY: 40,
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
labels: ['Team A', 'Team B', 'Team C', 'Team D', 'Team E'],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex, window.colorMap.primary[600].hex],
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
}
|
||||
};
|
||||
|
||||
const semiDonutChart = new ApexCharts(
|
||||
document.getElementById('semi-donut-chart'),
|
||||
semiDonutOptions
|
||||
);
|
||||
semiDonutChart.render();
|
||||
}
|
||||
|
||||
// Pie with Image Chart
|
||||
if (document.getElementById('pie-with-image-chart')) {
|
||||
const pieWithImageOptions = {
|
||||
series: [44, 33, 54, 45],
|
||||
chart: {
|
||||
width: '100%',
|
||||
height: 380,
|
||||
type: 'pie',
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex],
|
||||
fill: {
|
||||
type: 'image',
|
||||
opacity: 0.85,
|
||||
image: {
|
||||
src: ['img/demo/gallery/15.jpg', 'img/demo/gallery/7.jpg', 'img/demo/gallery/10.jpg', 'img/demo/gallery/20.jpg']
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: 4
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
style: {
|
||||
colors: ['#111']
|
||||
},
|
||||
background: {
|
||||
enabled: true,
|
||||
foreColor: '#fff',
|
||||
borderWidth: 0
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'right',
|
||||
offsetY: 0,
|
||||
height: 230,
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: "Image Fill Pie Chart",
|
||||
align: "left",
|
||||
}
|
||||
};
|
||||
|
||||
const pieWithImageChart = new ApexCharts(
|
||||
document.getElementById('pie-with-image-chart'),
|
||||
pieWithImageOptions
|
||||
);
|
||||
pieWithImageChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,185 @@
|
||||
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
'use strict';
|
||||
|
||||
// Basic Polar Area Chart
|
||||
if (document.getElementById('basic-polar-area-chart')) {
|
||||
const basicPolarAreaOptions = {
|
||||
series: [14, 23, 21, 17, 15, 10, 12, 17, 21],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'polarArea',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
colors: [window.colorMap.bootstrapVars.bodyBg.hex]
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.8
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 200
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
labels: ['Category A', 'Category B', 'Category C', 'Category D', 'Category E', 'Category F', 'Category G', 'Category H', 'Category I'],
|
||||
colors: [
|
||||
window.colorMap.primary[50].hex,
|
||||
window.colorMap.primary[100].hex,
|
||||
window.colorMap.primary[200].hex,
|
||||
window.colorMap.primary[300].hex,
|
||||
window.colorMap.primary[400].hex,
|
||||
window.colorMap.primary[500].hex,
|
||||
window.colorMap.primary[600].hex,
|
||||
window.colorMap.info[50].hex,
|
||||
window.colorMap.info[100].hex
|
||||
],
|
||||
};
|
||||
|
||||
const basicPolarAreaChart = new ApexCharts(
|
||||
document.getElementById('basic-polar-area-chart'),
|
||||
basicPolarAreaOptions
|
||||
);
|
||||
basicPolarAreaChart.render();
|
||||
}
|
||||
|
||||
// Polar Area with Monochrome Theme
|
||||
if (document.getElementById('monochrome-polar-area-chart')) {
|
||||
const monochromePolarAreaOptions = {
|
||||
series: [42, 47, 52, 58, 65],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'polarArea',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
labels: ['Market Research', 'Direct Sales', 'Email Marketing', 'Social Media', 'Referrals'],
|
||||
fill: {
|
||||
opacity: 1
|
||||
},
|
||||
stroke: {
|
||||
width: 1,
|
||||
colors: undefined
|
||||
},
|
||||
yaxis: {
|
||||
show: false
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
plotOptions: {
|
||||
polarArea: {
|
||||
rings: {
|
||||
strokeWidth: 0
|
||||
},
|
||||
spokes: {
|
||||
strokeWidth: 0
|
||||
},
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
monochrome: {
|
||||
enabled: true,
|
||||
shadeTo: 'light',
|
||||
shadeIntensity: 0.6,
|
||||
color: window.colorMap.primary[500].hex
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const monochromePolarAreaChart = new ApexCharts(
|
||||
document.getElementById('monochrome-polar-area-chart'),
|
||||
monochromePolarAreaOptions
|
||||
);
|
||||
monochromePolarAreaChart.render();
|
||||
}
|
||||
|
||||
// Polar Area with Gradient
|
||||
if (document.getElementById('gradient-polar-area-chart')) {
|
||||
const gradientPolarAreaOptions = {
|
||||
series: [30, 40, 35, 50, 49, 60, 70, 91, 125],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'polarArea',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September'],
|
||||
fill: {
|
||||
opacity: 1,
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
type: 'vertical',
|
||||
shadeIntensity: 0.5,
|
||||
gradientToColors: undefined,
|
||||
inverseColors: false,
|
||||
opacityFrom: 0.8,
|
||||
opacityTo: 0.5,
|
||||
stops: [0, 100]
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: 1,
|
||||
colors: [window.colorMap.bootstrapVars.bodyBg.hex]
|
||||
},
|
||||
yaxis: {
|
||||
show: false
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
plotOptions: {
|
||||
polarArea: {
|
||||
rings: {
|
||||
strokeWidth: 1,
|
||||
strokeColor: window.colorMap.bootstrapVars.bodyBg.hex
|
||||
},
|
||||
spokes: {
|
||||
strokeWidth: 1,
|
||||
connectorColors: window.colorMap.bootstrapVars.bodyBg.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function (val) {
|
||||
return "$" + val + "K";
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.primary[50].hex,
|
||||
window.colorMap.primary[100].hex,
|
||||
window.colorMap.primary[200].hex,
|
||||
window.colorMap.primary[300].hex,
|
||||
window.colorMap.primary[400].hex,
|
||||
window.colorMap.primary[500].hex,
|
||||
window.colorMap.primary[600].hex,
|
||||
window.colorMap.primary[700].hex,
|
||||
window.colorMap.primary[800].hex
|
||||
]
|
||||
};
|
||||
|
||||
const gradientPolarAreaChart = new ApexCharts(
|
||||
document.getElementById('gradient-polar-area-chart'),
|
||||
gradientPolarAreaOptions
|
||||
);
|
||||
gradientPolarAreaChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,161 @@
|
||||
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Basic Radar Chart
|
||||
if (document.getElementById('basic-radar-chart')) {
|
||||
const basicRadarOptions = {
|
||||
series: [{
|
||||
name: 'Basic Radar',
|
||||
data: [80, 50, 30, 40, 100, 20],
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'radar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: ['January', 'February', 'March', 'April', 'May', 'June'],
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
},
|
||||
markers: {
|
||||
size: 4,
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
strokeColors: '#fff',
|
||||
strokeWidth: 2
|
||||
},
|
||||
};
|
||||
|
||||
const basicRadarChart = new ApexCharts(
|
||||
document.getElementById('basic-radar-chart'),
|
||||
basicRadarOptions
|
||||
);
|
||||
basicRadarChart.render();
|
||||
}
|
||||
|
||||
// Multi-series Radar Chart
|
||||
if (document.getElementById('multi-series-radar-chart')) {
|
||||
const multiSeriesRadarOptions = {
|
||||
series: [{
|
||||
name: 'Series 1',
|
||||
data: [80, 50, 30, 40, 100, 20],
|
||||
}, {
|
||||
name: 'Series 2',
|
||||
data: [20, 30, 40, 80, 20, 80],
|
||||
}, {
|
||||
name: 'Series 3',
|
||||
data: [44, 76, 78, 13, 43, 10],
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'radar',
|
||||
dropShadow: {
|
||||
enabled: true,
|
||||
blur: 1,
|
||||
left: 1,
|
||||
top: 1
|
||||
},
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: 2
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.1
|
||||
},
|
||||
markers: {
|
||||
size: 0
|
||||
},
|
||||
xaxis: {
|
||||
categories: ['January', 'February', 'March', 'April', 'May', 'June'],
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[300].hex, window.colorMap.primary[200].hex],
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'top',
|
||||
}
|
||||
};
|
||||
|
||||
const multiSeriesRadarChart = new ApexCharts(
|
||||
document.getElementById('multi-series-radar-chart'),
|
||||
multiSeriesRadarOptions
|
||||
);
|
||||
multiSeriesRadarChart.render();
|
||||
}
|
||||
|
||||
// Polygon Fill Radar Chart
|
||||
if (document.getElementById('polygon-fill-radar-chart')) {
|
||||
const polygonFillRadarOptions = {
|
||||
series: [{
|
||||
name: 'Series 1',
|
||||
data: [20, 100, 40, 30, 50, 80, 33],
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'radar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true
|
||||
},
|
||||
plotOptions: {
|
||||
radar: {
|
||||
size: 140,
|
||||
polygons: {
|
||||
strokeColors: window.colorMap.bootstrapVars.bodyColor.rgba(0.2),
|
||||
fill: {
|
||||
colors: [window.colorMap.bootstrapVars.bodyBg.rgba(0.1), '#fff']
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
markers: {
|
||||
size: 4,
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
strokeColors: '#fff',
|
||||
strokeWidth: 2
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
},
|
||||
yaxis: {
|
||||
tickAmount: 7,
|
||||
}
|
||||
};
|
||||
|
||||
const polygonFillRadarChart = new ApexCharts(
|
||||
document.getElementById('polygon-fill-radar-chart'),
|
||||
polygonFillRadarOptions
|
||||
);
|
||||
polygonFillRadarChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,452 @@
|
||||
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Basic RadialBar Chart
|
||||
if (document.getElementById('basic-radialbar-chart')) {
|
||||
const basicRadialbarOptions = {
|
||||
series: [70],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'radialBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
hollow: {
|
||||
size: '70%',
|
||||
}
|
||||
},
|
||||
},
|
||||
labels: ['Progress'],
|
||||
colors: [window.colorMap.success[500].hex]
|
||||
};
|
||||
|
||||
const basicRadialbarChart = new ApexCharts(
|
||||
document.getElementById('basic-radialbar-chart'),
|
||||
basicRadialbarOptions
|
||||
);
|
||||
basicRadialbarChart.render();
|
||||
}
|
||||
|
||||
// Multiple RadialBar Chart
|
||||
if (document.getElementById('multiple-radialbar-chart')) {
|
||||
const multipleRadialbarOptions = {
|
||||
series: [44, 55, 67, 83],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'radialBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
dataLabels: {
|
||||
name: {
|
||||
fontSize: '22px',
|
||||
},
|
||||
value: {
|
||||
fontSize: '16px',
|
||||
},
|
||||
total: {
|
||||
show: true,
|
||||
label: 'Total',
|
||||
formatter: function(w) {
|
||||
return Math.round(w.globals.seriesTotals.reduce((a, b) => a + b, 0) / w.globals.series.length) + '%';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
labels: ['Apples', 'Oranges', 'Bananas', 'Berries'],
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.info[500].hex, window.colorMap.success[500].hex, window.colorMap.warning[500].hex]
|
||||
};
|
||||
|
||||
const multipleRadialbarChart = new ApexCharts(
|
||||
document.getElementById('multiple-radialbar-chart'),
|
||||
multipleRadialbarOptions
|
||||
);
|
||||
multipleRadialbarChart.render();
|
||||
}
|
||||
|
||||
// Custom Angle Circle Chart
|
||||
if (document.getElementById('custom-angle-circle-chart')) {
|
||||
const customAngleOptions = {
|
||||
series: [76, 67, 61, 90],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'radialBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
offsetY: 0,
|
||||
startAngle: 0,
|
||||
endAngle: 270,
|
||||
hollow: {
|
||||
margin: 5,
|
||||
size: '30%',
|
||||
background: 'transparent',
|
||||
image: undefined,
|
||||
},
|
||||
dataLabels: {
|
||||
name: {
|
||||
show: false,
|
||||
},
|
||||
value: {
|
||||
show: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.info[500].hex, window.colorMap.success[500].hex, window.colorMap.warning[500].hex],
|
||||
labels: ['Vimeo', 'Messenger', 'Facebook', 'LinkedIn'],
|
||||
legend: {
|
||||
show: true,
|
||||
floating: true,
|
||||
fontSize: '16px',
|
||||
position: 'left',
|
||||
offsetX: 160,
|
||||
offsetY: 15,
|
||||
labels: {
|
||||
useSeriesColors: true,
|
||||
},
|
||||
markers: {
|
||||
size: 0
|
||||
},
|
||||
formatter: function(seriesName, opts) {
|
||||
return seriesName + ": " + opts.w.globals.series[opts.seriesIndex] + "%";
|
||||
},
|
||||
itemMargin: {
|
||||
vertical: 3
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
const customAngleChart = new ApexCharts(
|
||||
document.getElementById('custom-angle-circle-chart'),
|
||||
customAngleOptions
|
||||
);
|
||||
customAngleChart.render();
|
||||
}
|
||||
|
||||
// Gradient RadialBar Chart
|
||||
if (document.getElementById('gradient-radialbar-chart')) {
|
||||
const gradientRadialbarOptions = {
|
||||
series: [75],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'radialBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
startAngle: -135,
|
||||
endAngle: 225,
|
||||
hollow: {
|
||||
margin: 0,
|
||||
size: '70%',
|
||||
background: '#fff',
|
||||
image: undefined,
|
||||
imageOffsetX: 0,
|
||||
imageOffsetY: 0,
|
||||
position: 'front',
|
||||
dropShadow: {
|
||||
enabled: true,
|
||||
top: 3,
|
||||
left: 0,
|
||||
blur: 4,
|
||||
opacity: 0.24
|
||||
}
|
||||
},
|
||||
track: {
|
||||
background: '#fff',
|
||||
strokeWidth: '67%',
|
||||
margin: 0,
|
||||
dropShadow: {
|
||||
enabled: true,
|
||||
top: -3,
|
||||
left: 0,
|
||||
blur: 4,
|
||||
opacity: 0.35
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
show: true,
|
||||
name: {
|
||||
offsetY: -10,
|
||||
show: true,
|
||||
color: '#888',
|
||||
fontSize: '17px'
|
||||
},
|
||||
value: {
|
||||
formatter: function(val) {
|
||||
return parseInt(val) + '%';
|
||||
},
|
||||
color: '#111',
|
||||
fontSize: '36px',
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'dark',
|
||||
type: 'horizontal',
|
||||
shadeIntensity: 0.5,
|
||||
gradientToColors: [window.colorMap.primary[500].hex],
|
||||
inverseColors: true,
|
||||
opacityFrom: 1,
|
||||
opacityTo: 1,
|
||||
stops: [0, 100]
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
lineCap: 'round'
|
||||
},
|
||||
labels: ['Percent'],
|
||||
};
|
||||
|
||||
const gradientRadialbarChart = new ApexCharts(
|
||||
document.getElementById('gradient-radialbar-chart'),
|
||||
gradientRadialbarOptions
|
||||
);
|
||||
gradientRadialbarChart.render();
|
||||
}
|
||||
|
||||
// RadialBar with Image
|
||||
if (document.getElementById('radialbar-with-image-chart')) {
|
||||
const radialbarWithImageOptions = {
|
||||
series: [67],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'radialBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
startAngle: -135,
|
||||
endAngle: 225,
|
||||
hollow: {
|
||||
margin: 0,
|
||||
size: '70%',
|
||||
background: '#fff',
|
||||
image: 'img/logo.png',
|
||||
imageWidth: 150,
|
||||
imageHeight: 150,
|
||||
imageClipped: false,
|
||||
position: 'front',
|
||||
dropShadow: {
|
||||
enabled: true,
|
||||
top: 3,
|
||||
left: 0,
|
||||
blur: 4,
|
||||
opacity: 0.24
|
||||
}
|
||||
},
|
||||
track: {
|
||||
background: '#fff',
|
||||
strokeWidth: '67%',
|
||||
margin: 0,
|
||||
dropShadow: {
|
||||
enabled: true,
|
||||
top: -3,
|
||||
left: 0,
|
||||
blur: 4,
|
||||
opacity: 0.35
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
show: true,
|
||||
name: {
|
||||
offsetY: -10,
|
||||
show: true,
|
||||
color: '#888',
|
||||
fontSize: '17px'
|
||||
},
|
||||
value: {
|
||||
formatter: function(val) {
|
||||
return parseInt(val) + '%';
|
||||
},
|
||||
color: '#111',
|
||||
fontSize: '36px',
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'dark',
|
||||
type: 'horizontal',
|
||||
shadeIntensity: 0.5,
|
||||
gradientToColors: [window.colorMap.primary[600].hex],
|
||||
inverseColors: true,
|
||||
opacityFrom: 1,
|
||||
opacityTo: 1,
|
||||
stops: [0, 100]
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
lineCap: 'round'
|
||||
},
|
||||
labels: ['Performance'],
|
||||
};
|
||||
|
||||
const radialbarWithImageChart = new ApexCharts(
|
||||
document.getElementById('radialbar-with-image-chart'),
|
||||
radialbarWithImageOptions
|
||||
);
|
||||
radialbarWithImageChart.render();
|
||||
}
|
||||
|
||||
// Stroked Gauge Chart
|
||||
if (document.getElementById('stroked-gauge-chart')) {
|
||||
const strokedGaugeOptions = {
|
||||
series: [67],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'radialBar',
|
||||
offsetY: -10,
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
startAngle: -135,
|
||||
endAngle: 135,
|
||||
dataLabels: {
|
||||
name: {
|
||||
fontSize: '16px',
|
||||
color: undefined,
|
||||
offsetY: 120
|
||||
},
|
||||
value: {
|
||||
offsetY: 76,
|
||||
fontSize: '22px',
|
||||
color: undefined,
|
||||
formatter: function(val) {
|
||||
return val + "%";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'dark',
|
||||
shadeIntensity: 0.15,
|
||||
inverseColors: false,
|
||||
opacityFrom: 1,
|
||||
opacityTo: 1,
|
||||
stops: [0, 50, 65, 91]
|
||||
},
|
||||
},
|
||||
stroke: {
|
||||
dashArray: 4
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
labels: ['Performance'],
|
||||
};
|
||||
|
||||
const strokedGaugeChart = new ApexCharts(
|
||||
document.getElementById('stroked-gauge-chart'),
|
||||
strokedGaugeOptions
|
||||
);
|
||||
strokedGaugeChart.render();
|
||||
}
|
||||
|
||||
// Semi Circle Gauge Chart
|
||||
if (document.getElementById('semi-circle-gauge-chart')) {
|
||||
const semiCircleGaugeOptions = {
|
||||
series: [76],
|
||||
chart: {
|
||||
type: 'radialBar',
|
||||
height: 350,
|
||||
offsetY: -20,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
startAngle: -90,
|
||||
endAngle: 90,
|
||||
track: {
|
||||
background: "#e7e7e7",
|
||||
strokeWidth: '97%',
|
||||
margin: 5,
|
||||
dropShadow: {
|
||||
enabled: true,
|
||||
top: 2,
|
||||
left: 0,
|
||||
color: '#999',
|
||||
opacity: 1,
|
||||
blur: 2
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
name: {
|
||||
show: false
|
||||
},
|
||||
value: {
|
||||
offsetY: -2,
|
||||
fontSize: '22px'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
padding: {
|
||||
top: -10
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
shadeIntensity: 0.4,
|
||||
inverseColors: false,
|
||||
opacityFrom: 1,
|
||||
opacityTo: 1,
|
||||
stops: [0, 50, 53, 91]
|
||||
},
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
labels: ['Average Results'],
|
||||
};
|
||||
|
||||
const semiCircleGaugeChart = new ApexCharts(
|
||||
document.getElementById('semi-circle-gauge-chart'),
|
||||
semiCircleGaugeOptions
|
||||
);
|
||||
semiCircleGaugeChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,599 @@
|
||||
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Basic Range Area Chart
|
||||
if (document.getElementById('basic-range-area-chart')) {
|
||||
const basicRangeAreaOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Temperature Range',
|
||||
data: [
|
||||
{ x: 'Jan', y: [5, 15] },
|
||||
{ x: 'Feb', y: [7, 18] },
|
||||
{ x: 'Mar', y: [10, 22] },
|
||||
{ x: 'Apr', y: [15, 25] },
|
||||
{ x: 'May', y: [18, 30] },
|
||||
{ x: 'Jun', y: [22, 35] },
|
||||
{ x: 'Jul', y: [25, 38] },
|
||||
{ x: 'Aug', y: [23, 36] },
|
||||
{ x: 'Sep', y: [20, 32] },
|
||||
{ x: 'Oct', y: [15, 26] },
|
||||
{ x: 'Nov', y: [10, 20] },
|
||||
{ x: 'Dec', y: [6, 16] }
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
type: 'rangeArea',
|
||||
height: 350,
|
||||
zoom: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
colors: ['#3F80EA'],
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
color: 'var(--primary-500)',
|
||||
width: 1
|
||||
},
|
||||
markers: {
|
||||
hover: {
|
||||
sizeOffset: 5
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return val + '°C';
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: false,
|
||||
custom: function({ series, seriesIndex, dataPointIndex, w }) {
|
||||
const data = w.globals.initialSeries[seriesIndex].data[dataPointIndex];
|
||||
return '<div class="p-2">' +
|
||||
'<span>Month: ' + data.x + '</span><br>' +
|
||||
'<span>Min: ' + data.y[0] + '°C</span><br>' +
|
||||
'<span>Max: ' + data.y[1] + '°C</span>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const basicRangeAreaChart = new ApexCharts(
|
||||
document.getElementById('basic-range-area-chart'),
|
||||
basicRangeAreaOptions
|
||||
);
|
||||
basicRangeAreaChart.render();
|
||||
}
|
||||
|
||||
// Range Area with Datetime X-axis
|
||||
if (document.getElementById('datetime-range-area-chart')) {
|
||||
const datetimeRangeAreaOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Price Range',
|
||||
data: [
|
||||
{
|
||||
x: new Date('2019-01-01').getTime(),
|
||||
y: [30, 45]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-02-01').getTime(),
|
||||
y: [35, 50]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-03-01').getTime(),
|
||||
y: [40, 55]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-04-01').getTime(),
|
||||
y: [45, 62]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-05-01').getTime(),
|
||||
y: [50, 70]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-06-01').getTime(),
|
||||
y: [55, 72]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-07-01').getTime(),
|
||||
y: [58, 75]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-08-01').getTime(),
|
||||
y: [60, 80]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-09-01').getTime(),
|
||||
y: [55, 75]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-10-01').getTime(),
|
||||
y: [50, 65]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-11-01').getTime(),
|
||||
y: [45, 60]
|
||||
},
|
||||
{
|
||||
x: new Date('2019-12-01').getTime(),
|
||||
y: [40, 55]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-01-01').getTime(),
|
||||
y: [45, 60]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-02-01').getTime(),
|
||||
y: [50, 65]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-03-01').getTime(),
|
||||
y: [55, 70]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-04-01').getTime(),
|
||||
y: [60, 75]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-05-01').getTime(),
|
||||
y: [65, 80]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-06-01').getTime(),
|
||||
y: [70, 85]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-07-01').getTime(),
|
||||
y: [75, 90]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-08-01').getTime(),
|
||||
y: [80, 95]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-09-01').getTime(),
|
||||
y: [75, 90]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-10-01').getTime(),
|
||||
y: [70, 85]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-11-01').getTime(),
|
||||
y: [65, 80]
|
||||
},
|
||||
{
|
||||
x: new Date('2020-12-01').getTime(),
|
||||
y: [60, 75]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'rangeArea',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
curve: 'smooth',
|
||||
width: 2
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
markers: {
|
||||
size: 0,
|
||||
hover: {
|
||||
size: 6
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.info[500].hex],
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) {
|
||||
return "$" + val;
|
||||
}
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shadeIntensity: 1,
|
||||
inverseColors: false,
|
||||
opacityFrom: 0.6,
|
||||
opacityTo: 0.2,
|
||||
stops: [0, 100]
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
yaxis: {
|
||||
title: {
|
||||
text: 'Price ($)'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const datetimeRangeAreaChart = new ApexCharts(
|
||||
document.getElementById('datetime-range-area-chart'),
|
||||
datetimeRangeAreaOptions
|
||||
);
|
||||
datetimeRangeAreaChart.render();
|
||||
}
|
||||
|
||||
// Multiple Range Area Charts
|
||||
if (document.getElementById('multiple-range-area-chart')) {
|
||||
const multipleRangeAreaOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Temperature New York',
|
||||
data: [
|
||||
{
|
||||
x: 'Jan',
|
||||
y: [-5, 5]
|
||||
},
|
||||
{
|
||||
x: 'Feb',
|
||||
y: [-3, 7]
|
||||
},
|
||||
{
|
||||
x: 'Mar',
|
||||
y: [2, 12]
|
||||
},
|
||||
{
|
||||
x: 'Apr',
|
||||
y: [8, 18]
|
||||
},
|
||||
{
|
||||
x: 'May',
|
||||
y: [15, 25]
|
||||
},
|
||||
{
|
||||
x: 'Jun',
|
||||
y: [20, 30]
|
||||
},
|
||||
{
|
||||
x: 'Jul',
|
||||
y: [22, 32]
|
||||
},
|
||||
{
|
||||
x: 'Aug',
|
||||
y: [21, 31]
|
||||
},
|
||||
{
|
||||
x: 'Sep',
|
||||
y: [16, 26]
|
||||
},
|
||||
{
|
||||
x: 'Oct',
|
||||
y: [10, 20]
|
||||
},
|
||||
{
|
||||
x: 'Nov',
|
||||
y: [5, 15]
|
||||
},
|
||||
{
|
||||
x: 'Dec',
|
||||
y: [-2, 8]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Temperature London',
|
||||
data: [
|
||||
{
|
||||
x: 'Jan',
|
||||
y: [2, 8]
|
||||
},
|
||||
{
|
||||
x: 'Feb',
|
||||
y: [3, 9]
|
||||
},
|
||||
{
|
||||
x: 'Mar',
|
||||
y: [5, 12]
|
||||
},
|
||||
{
|
||||
x: 'Apr',
|
||||
y: [7, 15]
|
||||
},
|
||||
{
|
||||
x: 'May',
|
||||
y: [10, 18]
|
||||
},
|
||||
{
|
||||
x: 'Jun',
|
||||
y: [14, 22]
|
||||
},
|
||||
{
|
||||
x: 'Jul',
|
||||
y: [16, 24]
|
||||
},
|
||||
{
|
||||
x: 'Aug',
|
||||
y: [16, 24]
|
||||
},
|
||||
{
|
||||
x: 'Sep',
|
||||
y: [14, 22]
|
||||
},
|
||||
{
|
||||
x: 'Oct',
|
||||
y: [10, 18]
|
||||
},
|
||||
{
|
||||
x: 'Nov',
|
||||
y: [6, 14]
|
||||
},
|
||||
{
|
||||
x: 'Dec',
|
||||
y: [4, 10]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'rangeArea',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
width: [2, 2]
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
markers: {
|
||||
hover: {
|
||||
sizeOffset: 5
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.success[500].hex, window.colorMap.warning[500].hex],
|
||||
fill: {
|
||||
opacity: [0.5, 0.5],
|
||||
type: 'solid'
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category'
|
||||
},
|
||||
yaxis: {
|
||||
title: {
|
||||
text: 'Temperature (°C)'
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const multipleRangeAreaChart = new ApexCharts(
|
||||
document.getElementById('multiple-range-area-chart'),
|
||||
multipleRangeAreaOptions
|
||||
);
|
||||
multipleRangeAreaChart.render();
|
||||
}
|
||||
|
||||
// Combo Range Area Chart
|
||||
if (document.getElementById('combo-range-area-chart')) {
|
||||
const comboRangeAreaOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Temperature Range',
|
||||
type: 'rangeArea',
|
||||
data: [
|
||||
{ x: 'Jan', y: [5, 15] },
|
||||
{ x: 'Feb', y: [7, 18] },
|
||||
{ x: 'Mar', y: [10, 22] },
|
||||
{ x: 'Apr', y: [15, 25] },
|
||||
{ x: 'May', y: [18, 30] },
|
||||
{ x: 'Jun', y: [22, 35] },
|
||||
{ x: 'Jul', y: [25, 38] },
|
||||
{ x: 'Aug', y: [23, 36] },
|
||||
{ x: 'Sep', y: [20, 32] },
|
||||
{ x: 'Oct', y: [15, 26] },
|
||||
{ x: 'Nov', y: [10, 20] },
|
||||
{ x: 'Dec', y: [6, 16] }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Average Temperature',
|
||||
type: 'line',
|
||||
data: [
|
||||
{ x: 'Jan', y: 10 },
|
||||
{ x: 'Feb', y: 12.5 },
|
||||
{ x: 'Mar', y: 16 },
|
||||
{ x: 'Apr', y: 20 },
|
||||
{ x: 'May', y: 24 },
|
||||
{ x: 'Jun', y: 28.5 },
|
||||
{ x: 'Jul', y: 31.5 },
|
||||
{ x: 'Aug', y: 29.5 },
|
||||
{ x: 'Sep', y: 26 },
|
||||
{ x: 'Oct', y: 20.5 },
|
||||
{ x: 'Nov', y: 15 },
|
||||
{ x: 'Dec', y: 11 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Precipitation',
|
||||
type: 'column',
|
||||
data: [
|
||||
{ x: 'Jan', y: 76 },
|
||||
{ x: 'Feb', y: 85 },
|
||||
{ x: 'Mar', y: 101 },
|
||||
{ x: 'Apr', y: 98 },
|
||||
{ x: 'May', y: 87 },
|
||||
{ x: 'Jun', y: 105 },
|
||||
{ x: 'Jul', y: 91 },
|
||||
{ x: 'Aug', y: 114 },
|
||||
{ x: 'Sep', y: 94 },
|
||||
{ x: 'Oct', y: 86 },
|
||||
{ x: 'Nov', y: 115 },
|
||||
{ x: 'Dec', y: 91 }
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'rangeArea',
|
||||
stacked: false
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.danger[500].hex, window.colorMap.primary[500].hex],
|
||||
stroke: {
|
||||
curve: 'smooth',
|
||||
width: [0, 3, 0]
|
||||
},
|
||||
fill: {
|
||||
opacity: [0.2, 1, 1]
|
||||
},
|
||||
markers: {
|
||||
size: [0, 4, 0],
|
||||
strokeWidth: 2,
|
||||
hover: {
|
||||
size: 6
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: '40%',
|
||||
borderRadius: 2
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
custom: function({ series, seriesIndex, dataPointIndex, w }) {
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const month = months[dataPointIndex];
|
||||
const rangeData = w.globals.initialSeries[0].data[dataPointIndex];
|
||||
const avgTemp = w.globals.initialSeries[1].data[dataPointIndex].y;
|
||||
const precip = w.globals.initialSeries[2].data[dataPointIndex].y;
|
||||
|
||||
return '<div class="p-2">' +
|
||||
'<span>Month: ' + month + '</span><br>' +
|
||||
'<span><span style="color:#3F80EA">■</span> Min-Max Temp: ' + rangeData.y[0] + '-' + rangeData.y[1] + '°C</span><br>' +
|
||||
'<span><span style="color:#E63946">■</span> Avg Temp: ' + avgTemp + '°C</span><br>' +
|
||||
'<span><span style="color:#6D9EEB">■</span> Precipitation: ' + precip + ' mm</span>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
horizontalAlign: 'center',
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
},
|
||||
yaxis: [
|
||||
{
|
||||
seriesName: 'Temperature Range',
|
||||
title: {
|
||||
text: 'Temperature (°C)',
|
||||
style: {
|
||||
color: window.colorMap.danger[500].hex,
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return val + '°C';
|
||||
},
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
seriesName: 'Average Temperature',
|
||||
show: false
|
||||
},
|
||||
{
|
||||
opposite: true,
|
||||
seriesName: 'Precipitation',
|
||||
title: {
|
||||
text: 'Precipitation (mm)',
|
||||
style: {
|
||||
color: window.colorMap.primary[500].hex,
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return val + ' mm';
|
||||
},
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const comboRangeAreaChart = new ApexCharts(
|
||||
document.getElementById('combo-range-area-chart'),
|
||||
comboRangeAreaOptions
|
||||
);
|
||||
comboRangeAreaChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,479 @@
|
||||
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Basic Scatter Chart
|
||||
if (document.getElementById('basic-scatter-chart')) {
|
||||
const basicScatterOptions = {
|
||||
series: [{
|
||||
name: "Sample A",
|
||||
data: [
|
||||
[16.4, 5.4], [21.7, 2.1], [25.4, 3.0], [19.0, 2.5], [10.9, 1.4],
|
||||
[13.6, 3.2], [10.9, 7.1], [10.9, 0], [10.9, 8.7], [16.4, 0],
|
||||
[16.4, 1.8], [13.6, 0.3], [13.6, 0], [29.9, 0], [27.1, 2.3],
|
||||
[16.4, 0], [13.6, 3.7], [10.9, 5.2], [16.4, 6.5], [10.9, 0],
|
||||
[24.5, 7.1], [10.9, 0], [8.1, 4.7], [19.0, 0], [21.7, 1.8],
|
||||
[27.1, 0], [24.5, 0], [27.1, 0], [29.9, 1.5], [27.1, 0.8],
|
||||
[22.1, 2.0]
|
||||
]
|
||||
},{
|
||||
name: "Sample B",
|
||||
data: [
|
||||
[36.4, 13.4], [1.7, 11.1], [5.4, 8.0], [9.0, 17.5], [1.9, 4.4],
|
||||
[3.6, 12.2], [1.9, 14.1], [1.9, 9.0], [1.9, 13.7], [1.4, 7.0],
|
||||
[6.4, 8.8], [3.6, 4.3], [1.6, 10.0], [9.9, 2.0], [7.1, 15.3],
|
||||
[1.4, 0], [3.6, 13.7], [1.9, 15.2], [6.4, 16.5], [0.9, 10.0],
|
||||
[4.5, 17.1], [10.9, 10.0], [0.1, 14.7], [9.0, 10.0], [12.7, 11.8],
|
||||
[2.1, 10.0], [2.5, 10.0], [27.1, 10.0], [2.9, 11.5], [7.1, 10.8],
|
||||
[2.1, 12.0]
|
||||
]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'scatter',
|
||||
zoom: {
|
||||
enabled: true,
|
||||
type: 'xy'
|
||||
},
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
tickAmount: 10
|
||||
},
|
||||
yaxis: {
|
||||
tickAmount: 7
|
||||
},
|
||||
title: {
|
||||
text: 'Basic Scatter Chart',
|
||||
align: 'left'
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
row: {
|
||||
colors: [window.colorMap.bootstrapVars.bodyColor.rgba(0.05), 'transparent'],
|
||||
opacity: 0.5
|
||||
},
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.danger[500].hex],
|
||||
markers: {
|
||||
size: 6
|
||||
}
|
||||
};
|
||||
|
||||
const basicScatterChart = new ApexCharts(
|
||||
document.getElementById('basic-scatter-chart'),
|
||||
basicScatterOptions
|
||||
);
|
||||
basicScatterChart.render();
|
||||
}
|
||||
|
||||
// Scatter Chart with Categories
|
||||
if (document.getElementById('category-scatter-chart')) {
|
||||
const categoryScatterOptions = {
|
||||
series: [{
|
||||
name: "TEAM A",
|
||||
data: [
|
||||
[1, 5.4], [2, 7.1], [3, 9.0], [4, 9.5], [5, 8.9],
|
||||
[6, 12.2], [7, 14.3], [8, 16.5], [9, 18.0], [10, 15.8]
|
||||
]
|
||||
},{
|
||||
name: "TEAM B",
|
||||
data: [
|
||||
[1, 3.2], [2, 4.5], [3, 5.8], [4, 6.3], [5, 7.0],
|
||||
[6, 7.5], [7, 8.0], [8, 8.5], [9, 9.1], [10, 9.8]
|
||||
]
|
||||
},{
|
||||
name: "TEAM C",
|
||||
data: [
|
||||
[1, 11.1], [2, 12.5], [3, 11.8], [4, 10.5], [5, 12.0],
|
||||
[6, 13.5], [7, 11.7], [8, 10.8], [9, 13.2], [10, 14.5]
|
||||
]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'scatter',
|
||||
zoom: {
|
||||
enabled: true,
|
||||
type: 'xy'
|
||||
},
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
tickAmount: 10,
|
||||
labels: {
|
||||
formatter: function(val) {
|
||||
return parseInt(val) + "";
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
tickAmount: 7,
|
||||
title: {
|
||||
text: 'Performance Score'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Scatter Chart with Categories',
|
||||
align: 'left'
|
||||
},
|
||||
legend: {
|
||||
position: 'top'
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.danger[500].hex, window.colorMap.warning[500].hex],
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) {
|
||||
return val + " points";
|
||||
}
|
||||
},
|
||||
x: {
|
||||
formatter: function(val) {
|
||||
return "Round " + val;
|
||||
}
|
||||
}
|
||||
},
|
||||
markers: {
|
||||
size: 7
|
||||
}
|
||||
};
|
||||
|
||||
const categoryScatterChart = new ApexCharts(
|
||||
document.getElementById('category-scatter-chart'),
|
||||
categoryScatterOptions
|
||||
);
|
||||
categoryScatterChart.render();
|
||||
}
|
||||
|
||||
// Scatter Chart with Images
|
||||
if (document.getElementById('image-scatter-chart')) {
|
||||
const imageScatterOptions = {
|
||||
series: [{
|
||||
name: 'Messenger',
|
||||
data: [
|
||||
[16.4, 5.4],
|
||||
[21.7, 4.1],
|
||||
[25.4, 3.0],
|
||||
[19.0, 2.5],
|
||||
[10.9, 1.4]
|
||||
]
|
||||
}, {
|
||||
name: 'LinkedIn',
|
||||
data: [
|
||||
[6.4, 5.4],
|
||||
[11.7, 4.1],
|
||||
[15.4, 3.0],
|
||||
[9.0, 2.5],
|
||||
[10.9, 1.4]
|
||||
]
|
||||
}, {
|
||||
name: 'Facebook',
|
||||
data: [
|
||||
[36.4, 13.4],
|
||||
[1.7, 11.1],
|
||||
[5.4, 8.0],
|
||||
[9.0, 17.0],
|
||||
[1.9, 4.4]
|
||||
]
|
||||
}, {
|
||||
name: 'Instagram',
|
||||
data: [
|
||||
[26.4, 16.4],
|
||||
[28.7, 18.1],
|
||||
[29.4, 20.0],
|
||||
[25.0, 22.5],
|
||||
[20.9, 19.4]
|
||||
]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'scatter',
|
||||
animations: {
|
||||
enabled: false,
|
||||
},
|
||||
zoom: {
|
||||
enabled: true,
|
||||
type: 'xy'
|
||||
},
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
tickAmount: 10,
|
||||
min: 0,
|
||||
max: 40
|
||||
},
|
||||
yaxis: {
|
||||
tickAmount: 7,
|
||||
min: 0,
|
||||
max: 25,
|
||||
title: {
|
||||
text: 'Engagement Rate (%)'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Social Media Engagement (with custom markers)',
|
||||
align: 'left'
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.danger[500].hex, window.colorMap.warning[500].hex, window.colorMap.success[500].hex],
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) {
|
||||
return val + "%";
|
||||
}
|
||||
},
|
||||
x: {
|
||||
formatter: function(val) {
|
||||
return val + "M users";
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
markers: {
|
||||
size: 20,
|
||||
shape: "circle",
|
||||
radius: 2
|
||||
},
|
||||
legend: {
|
||||
position: 'top'
|
||||
}
|
||||
};
|
||||
|
||||
const imageScatterChart = new ApexCharts(
|
||||
document.getElementById('image-scatter-chart'),
|
||||
imageScatterOptions
|
||||
);
|
||||
imageScatterChart.render();
|
||||
}
|
||||
|
||||
// Datetime Scatter Chart
|
||||
if (document.getElementById('datetime-scatter-chart')) {
|
||||
const generateDayWiseTimeSeries = function(startDate, count, range) {
|
||||
let i = 0;
|
||||
const series = [];
|
||||
while (i < count) {
|
||||
const x = new Date(startDate.getTime() + i * 86400000); // add 1 day
|
||||
const y = Math.floor(Math.random() * (range.max - range.min + 1)) + range.min;
|
||||
series.push([x, y]);
|
||||
i++;
|
||||
}
|
||||
return series;
|
||||
};
|
||||
|
||||
const datetimeScatterOptions = {
|
||||
series: [{
|
||||
name: 'Sales',
|
||||
data: generateDayWiseTimeSeries(new Date('01 Jan 2025'), 30, { min: 10, max: 90 })
|
||||
}, {
|
||||
name: 'Web Traffic',
|
||||
data: generateDayWiseTimeSeries(new Date('01 Jan 2025'), 30, { min: 50, max: 150 })
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'scatter',
|
||||
zoom: {
|
||||
enabled: true,
|
||||
type: 'xy'
|
||||
},
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
datetimeUTC: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
title: {
|
||||
text: 'Value'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Scatter Chart with Datetime',
|
||||
align: 'left'
|
||||
},
|
||||
grid: {
|
||||
row: {
|
||||
colors: [window.colorMap.bootstrapVars.bodyColor.rgba(0.05), 'transparent'],
|
||||
opacity: 0.5
|
||||
},
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.danger[500].hex],
|
||||
tooltip: {
|
||||
x: {
|
||||
format: 'dd MMM yyyy'
|
||||
}
|
||||
},
|
||||
markers: {
|
||||
size: 6
|
||||
}
|
||||
};
|
||||
|
||||
const datetimeScatterChart = new ApexCharts(
|
||||
document.getElementById('datetime-scatter-chart'),
|
||||
datetimeScatterOptions
|
||||
);
|
||||
datetimeScatterChart.render();
|
||||
}
|
||||
|
||||
// Images Scatter Chart
|
||||
if (document.getElementById('images-scatter-chart')) {
|
||||
function generateData(count, yrange) {
|
||||
let i = 0;
|
||||
const series = [];
|
||||
while (i < count) {
|
||||
const x = (Math.random() * 100).toFixed(0);
|
||||
const y = (Math.random() * yrange).toFixed(0);
|
||||
series.push({
|
||||
x: x,
|
||||
y: y
|
||||
});
|
||||
i++;
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
const imagesScatterOptions = {
|
||||
series: [{
|
||||
name: 'Brave',
|
||||
data: generateData(10, 80)
|
||||
}, {
|
||||
name: 'Chrome',
|
||||
data: generateData(10, 60)
|
||||
}, {
|
||||
name: 'Firefox',
|
||||
data: generateData(10, 40)
|
||||
}, {
|
||||
name: 'Safari',
|
||||
data: generateData(10, 100)
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'scatter',
|
||||
animations: {
|
||||
enabled: false,
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
},
|
||||
toolbar: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.danger[500].hex, window.colorMap.warning[500].hex, window.colorMap.success[500].hex],
|
||||
xaxis: {
|
||||
tickAmount: 20,
|
||||
min: 0,
|
||||
max: 10
|
||||
},
|
||||
yaxis: {
|
||||
tickAmount: 5
|
||||
},
|
||||
markers: {
|
||||
size: 20,
|
||||
shape: 'circle',
|
||||
strokeWidth: 0,
|
||||
hover: {
|
||||
size: 25,
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
type: 'image',
|
||||
opacity: 1,
|
||||
borderRadius: 0,
|
||||
image: {
|
||||
src: [
|
||||
'img/browsers/brave.png',
|
||||
'img/browsers/chrome.png',
|
||||
'img/browsers/firefox.png',
|
||||
'img/browsers/safari.png'
|
||||
],
|
||||
width: 30,
|
||||
height: 30,
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Social Media Usage Distribution',
|
||||
align: 'left'
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
custom: function({ series, seriesIndex, dataPointIndex, w }) {
|
||||
const platforms = ['Brave', 'Chrome', 'Firefox', 'Safari'];
|
||||
const data = w.globals.initialSeries[seriesIndex].data[dataPointIndex];
|
||||
return '<div class="p-2">' +
|
||||
'<div style="text-align: center; margin-bottom: 5px;"><strong>' + platforms[seriesIndex] + '</strong></div>' +
|
||||
'<span>Usage score: ' + data.y + '</span><br>' +
|
||||
'<span>Satisfaction: ' + data.x + '</span>' +
|
||||
'</div>';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const imagesScatterChart = new ApexCharts(
|
||||
document.getElementById('images-scatter-chart'),
|
||||
imagesScatterOptions
|
||||
);
|
||||
imagesScatterChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,261 @@
|
||||
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Basic Slope Chart
|
||||
if (document.getElementById('basic-slope-chart')) {
|
||||
const basicSlopeOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Sales',
|
||||
data: [
|
||||
{
|
||||
x: '2019',
|
||||
y: 40
|
||||
},
|
||||
{
|
||||
x: '2023',
|
||||
y: 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Revenue',
|
||||
data: [
|
||||
{
|
||||
x: '2019',
|
||||
y: 25
|
||||
},
|
||||
{
|
||||
x: '2023',
|
||||
y: 70
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Customers',
|
||||
data: [
|
||||
{
|
||||
x: '2019',
|
||||
y: 15
|
||||
},
|
||||
{
|
||||
x: '2023',
|
||||
y: 65
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
type: 'line',
|
||||
height: 350,
|
||||
zoom: {
|
||||
enabled: false
|
||||
},
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
offsetY: -5,
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
fontWeight: 'normal',
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Basic Slope Chart',
|
||||
align: 'left'
|
||||
},
|
||||
legend: {
|
||||
position: 'top'
|
||||
},
|
||||
colors: [window.colorMap.primary[100].hex, window.colorMap.primary[300].hex, window.colorMap.primary[600].hex],
|
||||
stroke: {
|
||||
width: 3
|
||||
},
|
||||
grid: {
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
markers: {
|
||||
size: 5
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
tickAmount: 2,
|
||||
tickPlacement: 'on',
|
||||
axisTicks: {
|
||||
show: false
|
||||
},
|
||||
axisBorder: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
tickAmount: 5,
|
||||
}
|
||||
};
|
||||
|
||||
const basicSlopeChart = new ApexCharts(
|
||||
document.getElementById('basic-slope-chart'),
|
||||
basicSlopeOptions
|
||||
);
|
||||
basicSlopeChart.render();
|
||||
}
|
||||
|
||||
// Multi-group Slope Chart
|
||||
if (document.getElementById('multi-group-slope-chart')) {
|
||||
const multiGroupSlopeOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'USA',
|
||||
data: [
|
||||
{
|
||||
x: '2010',
|
||||
y: 71
|
||||
},
|
||||
{
|
||||
x: '2020',
|
||||
y: 76
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'UK',
|
||||
data: [
|
||||
{
|
||||
x: '2010',
|
||||
y: 55
|
||||
},
|
||||
{
|
||||
x: '2020',
|
||||
y: 81
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Germany',
|
||||
data: [
|
||||
{
|
||||
x: '2010',
|
||||
y: 50
|
||||
},
|
||||
{
|
||||
x: '2020',
|
||||
y: 65
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Japan',
|
||||
data: [
|
||||
{
|
||||
x: '2010',
|
||||
y: 40
|
||||
},
|
||||
{
|
||||
x: '2020',
|
||||
y: 85
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'India',
|
||||
data: [
|
||||
{
|
||||
x: '2010',
|
||||
y: 45
|
||||
},
|
||||
{
|
||||
x: '2020',
|
||||
y: 70
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
type: 'line',
|
||||
height: 350,
|
||||
zoom: {
|
||||
enabled: false
|
||||
},
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
fontWeight: 'normal',
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Multi-group Slope Chart',
|
||||
align: 'left'
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
legend: {
|
||||
position: 'top'
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.info[500].hex, window.colorMap.success[500].hex, window.colorMap.warning[500].hex, window.colorMap.danger[500].hex],
|
||||
stroke: {
|
||||
width: 3
|
||||
},
|
||||
grid: {
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
markers: {
|
||||
size: 6
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
tickAmount: 2,
|
||||
axisTicks: {
|
||||
show: false
|
||||
},
|
||||
axisBorder: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
tickAmount: 5,
|
||||
}
|
||||
};
|
||||
|
||||
const multiGroupSlopeChart = new ApexCharts(
|
||||
document.getElementById('multi-group-slope-chart'),
|
||||
multiGroupSlopeOptions
|
||||
);
|
||||
multiGroupSlopeChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,425 @@
|
||||
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Basic Sparkline Chart
|
||||
if (document.getElementById('basic-sparkline-chart')) {
|
||||
// Generate random data for sparklines
|
||||
function generateSparklineData(count, min, max) {
|
||||
const data = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
data.push(Math.floor(Math.random() * (max - min + 1)) + min);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// Create sparkline layout
|
||||
const sparklineContainer = document.getElementById('basic-sparkline-chart');
|
||||
sparklineContainer.innerHTML = `
|
||||
<div class="row m-0">
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Sales</h5>
|
||||
<span class="text-muted">Monthly</span>
|
||||
</div>
|
||||
<h3 class="text-success mb-0">+24%</h3>
|
||||
</div>
|
||||
<div id="spark1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Revenue</h5>
|
||||
<span class="text-muted">Quarterly</span>
|
||||
</div>
|
||||
<h3 class="text-success mb-0">+17%</h3>
|
||||
</div>
|
||||
<div id="spark2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Customers</h5>
|
||||
<span class="text-muted">Daily</span>
|
||||
</div>
|
||||
<h3 class="text-danger mb-0">-3%</h3>
|
||||
</div>
|
||||
<div id="spark3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-0">
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Orders</h5>
|
||||
<span class="text-muted">Weekly</span>
|
||||
</div>
|
||||
<h3 class="text-success mb-0">+12%</h3>
|
||||
</div>
|
||||
<div id="spark4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Engagement</h5>
|
||||
<span class="text-muted">Monthly</span>
|
||||
</div>
|
||||
<h3 class="text-success mb-0">+8%</h3>
|
||||
</div>
|
||||
<div id="spark5"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Conversion</h5>
|
||||
<span class="text-muted">Monthly</span>
|
||||
</div>
|
||||
<h3 class="text-success mb-0">+5%</h3>
|
||||
</div>
|
||||
<div id="spark6"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Sparkline 1 - Line
|
||||
const spark1Options = {
|
||||
series: [{
|
||||
data: generateSparklineData(30, 30, 90)
|
||||
}],
|
||||
chart: {
|
||||
type: 'line',
|
||||
height: 80,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 800,
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: 2,
|
||||
curve: 'smooth'
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
tooltip: {
|
||||
fixed: {
|
||||
enabled: false
|
||||
},
|
||||
x: {
|
||||
show: false
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
formatter: function() {
|
||||
return 'Sales:';
|
||||
}
|
||||
}
|
||||
},
|
||||
marker: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const spark1Chart = new ApexCharts(document.querySelector("#spark1"), spark1Options);
|
||||
spark1Chart.render();
|
||||
|
||||
// Sparkline 2 - Column
|
||||
const spark2Options = {
|
||||
series: [{
|
||||
data: generateSparklineData(20, 10, 60)
|
||||
}],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 80,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 800,
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[400].hex],
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: '60%'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
fixed: {
|
||||
enabled: false
|
||||
},
|
||||
x: {
|
||||
show: false
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
formatter: function() {
|
||||
return 'Revenue:';
|
||||
}
|
||||
}
|
||||
},
|
||||
marker: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const spark2Chart = new ApexCharts(document.querySelector("#spark2"), spark2Options);
|
||||
spark2Chart.render();
|
||||
|
||||
// Sparkline 3 - Area
|
||||
const spark3Options = {
|
||||
series: [{
|
||||
data: generateSparklineData(30, 20, 50)
|
||||
}],
|
||||
chart: {
|
||||
type: 'area',
|
||||
height: 80,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 800,
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
width: 2
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
type: 'vertical',
|
||||
shadeIntensity: 0.4,
|
||||
gradientToColors: [window.colorMap.bootstrapVars.bodyBg.hex],
|
||||
inverseColors: false,
|
||||
opacityFrom: 0.7,
|
||||
opacityTo: 0.2,
|
||||
stops: [0, 100]
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.danger[500].hex],
|
||||
tooltip: {
|
||||
fixed: {
|
||||
enabled: false
|
||||
},
|
||||
x: {
|
||||
show: false
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
formatter: function() {
|
||||
return 'Customers:';
|
||||
}
|
||||
}
|
||||
},
|
||||
marker: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const spark3Chart = new ApexCharts(document.querySelector("#spark3"), spark3Options);
|
||||
spark3Chart.render();
|
||||
|
||||
// Sparkline 4 - Line with markers
|
||||
const spark4Options = {
|
||||
series: [{
|
||||
data: generateSparklineData(15, 40, 100)
|
||||
}],
|
||||
chart: {
|
||||
type: 'line',
|
||||
height: 80,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 800,
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: 2,
|
||||
curve: 'straight'
|
||||
},
|
||||
colors: [window.colorMap.success[500].hex],
|
||||
markers: {
|
||||
size: 4,
|
||||
colors: [window.colorMap.success[500].hex],
|
||||
strokeColors: '#ffffff',
|
||||
strokeWidth: 2,
|
||||
hover: {
|
||||
size: 6
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
fixed: {
|
||||
enabled: false
|
||||
},
|
||||
x: {
|
||||
show: false
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
formatter: function() {
|
||||
return 'Orders:';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const spark4Chart = new ApexCharts(document.querySelector("#spark4"), spark4Options);
|
||||
spark4Chart.render();
|
||||
|
||||
// Sparkline 5 - Area with negative values
|
||||
const spark5Data = generateSparklineData(15, -10, 40).map(val => val - 15);
|
||||
const spark5Options = {
|
||||
series: [{
|
||||
data: spark5Data
|
||||
}],
|
||||
chart: {
|
||||
type: 'area',
|
||||
height: 80,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 800,
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
curve: 'smooth',
|
||||
width: 2
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
type: 'vertical',
|
||||
shadeIntensity: 0.4,
|
||||
gradientToColors: [window.colorMap.bootstrapVars.bodyBg.hex],
|
||||
inverseColors: false,
|
||||
opacityFrom: 0.7,
|
||||
opacityTo: 0.2,
|
||||
stops: [0, 100]
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
tooltip: {
|
||||
fixed: {
|
||||
enabled: false
|
||||
},
|
||||
x: {
|
||||
show: false
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
formatter: function() {
|
||||
return 'Engagement:';
|
||||
}
|
||||
}
|
||||
},
|
||||
marker: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const spark5Chart = new ApexCharts(document.querySelector("#spark5"), spark5Options);
|
||||
spark5Chart.render();
|
||||
|
||||
// Sparkline 6 - Bar with colors
|
||||
const spark6Data = generateSparklineData(24, 10, 50);
|
||||
const spark6Colors = spark6Data.map(value => {
|
||||
if (value >= 40) return window.colorMap.primary[500].hex;
|
||||
if (value >= 30) return window.colorMap.primary[100].hex;
|
||||
if (value >= 20) return window.colorMap.danger[500].hex;
|
||||
return window.colorMap.warning[500].hex;
|
||||
});
|
||||
|
||||
const spark6Options = {
|
||||
series: [{
|
||||
data: spark6Data
|
||||
}],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 80,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 800,
|
||||
}
|
||||
},
|
||||
colors: spark6Colors,
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: '80%',
|
||||
distributed: true
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
fixed: {
|
||||
enabled: false
|
||||
},
|
||||
x: {
|
||||
show: false
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
formatter: function() {
|
||||
return 'Conversion:';
|
||||
}
|
||||
}
|
||||
},
|
||||
marker: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const spark6Chart = new ApexCharts(document.querySelector("#spark6"), spark6Options);
|
||||
spark6Chart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,641 @@
|
||||
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Basic Timeline Chart
|
||||
if (document.getElementById('basic-timeline-chart')) {
|
||||
const basicTimelineOptions = {
|
||||
series: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
x: 'Code',
|
||||
y: [
|
||||
new Date('2019-03-02').getTime(),
|
||||
new Date('2019-03-04').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Test',
|
||||
y: [
|
||||
new Date('2019-03-04').getTime(),
|
||||
new Date('2019-03-08').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Validation',
|
||||
y: [
|
||||
new Date('2019-03-08').getTime(),
|
||||
new Date('2019-03-12').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Deployment',
|
||||
y: [
|
||||
new Date('2019-03-12').getTime(),
|
||||
new Date('2019-03-18').getTime()
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'rangeBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: true
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const basicTimelineChart = new ApexCharts(
|
||||
document.getElementById('basic-timeline-chart'),
|
||||
basicTimelineOptions
|
||||
);
|
||||
basicTimelineChart.render();
|
||||
}
|
||||
|
||||
// Custom Colors Timeline Chart
|
||||
if (document.getElementById('custom-colors-timeline-chart')) {
|
||||
const customColorsTimelineOptions = {
|
||||
series: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
x: 'Research',
|
||||
y: [
|
||||
new Date('2019-03-01').getTime(),
|
||||
new Date('2019-03-05').getTime()
|
||||
],
|
||||
fillColor: window.colorMap.primary[500].hex
|
||||
},
|
||||
{
|
||||
x: 'Design',
|
||||
y: [
|
||||
new Date('2019-03-05').getTime(),
|
||||
new Date('2019-03-09').getTime()
|
||||
],
|
||||
fillColor: window.colorMap.success[500].hex
|
||||
},
|
||||
{
|
||||
x: 'Development',
|
||||
y: [
|
||||
new Date('2019-03-09').getTime(),
|
||||
new Date('2019-03-15').getTime()
|
||||
],
|
||||
fillColor: window.colorMap.warning[600].hex
|
||||
},
|
||||
{
|
||||
x: 'Testing',
|
||||
y: [
|
||||
new Date('2019-03-15').getTime(),
|
||||
new Date('2019-03-20').getTime()
|
||||
],
|
||||
fillColor: window.colorMap.danger[600].hex
|
||||
},
|
||||
{
|
||||
x: 'Deployment',
|
||||
y: [
|
||||
new Date('2019-03-20').getTime(),
|
||||
new Date('2019-03-25').getTime()
|
||||
],
|
||||
fillColor: window.colorMap.info[500].hex
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'rangeBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: true,
|
||||
distributed: true,
|
||||
dataLabels: {
|
||||
hideOverflowingLabels: false
|
||||
}
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
formatter: function(val, opts) {
|
||||
const label = opts.w.globals.labels[opts.dataPointIndex];
|
||||
const a = new Date(val[0]);
|
||||
const b = new Date(val[1]);
|
||||
const diff = Math.floor((b - a) / (1000 * 60 * 60 * 24));
|
||||
return label + ': ' + diff + (diff > 1 ? ' days' : ' day');
|
||||
},
|
||||
style: {
|
||||
colors: ['#f3f4f5', '#fff']
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const customColorsTimelineChart = new ApexCharts(
|
||||
document.getElementById('custom-colors-timeline-chart'),
|
||||
customColorsTimelineOptions
|
||||
);
|
||||
customColorsTimelineChart.render();
|
||||
}
|
||||
|
||||
// Multi-series Timeline Chart
|
||||
if (document.getElementById('multi-series-timeline-chart')) {
|
||||
const multiSeriesTimelineOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Development Team',
|
||||
data: [
|
||||
{
|
||||
x: 'Design',
|
||||
y: [
|
||||
new Date('2019-03-01').getTime(),
|
||||
new Date('2019-03-05').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Code',
|
||||
y: [
|
||||
new Date('2019-03-06').getTime(),
|
||||
new Date('2019-03-15').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Test',
|
||||
y: [
|
||||
new Date('2019-03-16').getTime(),
|
||||
new Date('2019-03-22').getTime()
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Testing Team',
|
||||
data: [
|
||||
{
|
||||
x: 'Review',
|
||||
y: [
|
||||
new Date('2019-03-05').getTime(),
|
||||
new Date('2019-03-08').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'QA Testing',
|
||||
y: [
|
||||
new Date('2019-03-14').getTime(),
|
||||
new Date('2019-03-25').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'User Testing',
|
||||
y: [
|
||||
new Date('2019-03-20').getTime(),
|
||||
new Date('2019-03-28').getTime()
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'rangeBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: true
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true
|
||||
},
|
||||
legend: {
|
||||
position: 'top'
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const multiSeriesTimelineChart = new ApexCharts(
|
||||
document.getElementById('multi-series-timeline-chart'),
|
||||
multiSeriesTimelineOptions
|
||||
);
|
||||
multiSeriesTimelineChart.render();
|
||||
}
|
||||
|
||||
// Advanced Timeline Chart
|
||||
if (document.getElementById('advanced-timeline-chart')) {
|
||||
const advancedTimelineOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Bob',
|
||||
data: [
|
||||
{
|
||||
x: 'Design',
|
||||
y: [
|
||||
new Date('2019-03-05').getTime(),
|
||||
new Date('2019-03-08').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Code',
|
||||
y: [
|
||||
new Date('2019-03-08').getTime(),
|
||||
new Date('2019-03-11').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Test',
|
||||
y: [
|
||||
new Date('2019-03-11').getTime(),
|
||||
new Date('2019-03-16').getTime()
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Alice',
|
||||
data: [
|
||||
{
|
||||
x: 'Design',
|
||||
y: [
|
||||
new Date('2019-03-02').getTime(),
|
||||
new Date('2019-03-05').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Code',
|
||||
y: [
|
||||
new Date('2019-03-06').getTime(),
|
||||
new Date('2019-03-09').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Test',
|
||||
y: [
|
||||
new Date('2019-03-10').getTime(),
|
||||
new Date('2019-03-19').getTime()
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'rangeBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: true
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
legend: {
|
||||
position: 'top'
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.danger[500].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const advancedTimelineChart = new ApexCharts(
|
||||
document.getElementById('advanced-timeline-chart'),
|
||||
advancedTimelineOptions
|
||||
);
|
||||
advancedTimelineChart.render();
|
||||
}
|
||||
|
||||
// Multiple Series - Group Rows Timeline Chart
|
||||
if (document.getElementById('multiple-series-group-timeline-chart')) {
|
||||
const multipleSeriesGroupTimelineOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Frontend',
|
||||
data: [
|
||||
{
|
||||
x: 'Design',
|
||||
y: [
|
||||
new Date('2019-03-02').getTime(),
|
||||
new Date('2019-03-08').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Development',
|
||||
y: [
|
||||
new Date('2019-03-08').getTime(),
|
||||
new Date('2019-03-20').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Testing',
|
||||
y: [
|
||||
new Date('2019-03-20').getTime(),
|
||||
new Date('2019-03-28').getTime()
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Backend',
|
||||
data: [
|
||||
{
|
||||
x: 'API Design',
|
||||
y: [
|
||||
new Date('2019-03-01').getTime(),
|
||||
new Date('2019-03-10').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Implementation',
|
||||
y: [
|
||||
new Date('2019-03-10').getTime(),
|
||||
new Date('2019-03-22').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Integration Testing',
|
||||
y: [
|
||||
new Date('2019-03-22').getTime(),
|
||||
new Date('2019-04-05').getTime()
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Database',
|
||||
data: [
|
||||
{
|
||||
x: 'Schema Design',
|
||||
y: [
|
||||
new Date('2019-03-05').getTime(),
|
||||
new Date('2019-03-12').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Data Migration',
|
||||
y: [
|
||||
new Date('2019-03-15').getTime(),
|
||||
new Date('2019-03-25').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Performance Testing',
|
||||
y: [
|
||||
new Date('2019-03-25').getTime(),
|
||||
new Date('2019-04-10').getTime()
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'rangeBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: true,
|
||||
barHeight: '70%',
|
||||
rangeBarGroupRows: true
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
legend: {
|
||||
position: 'top'
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark',
|
||||
custom: function({series, seriesIndex, dataPointIndex, w}) {
|
||||
const task = w.globals.initialSeries[seriesIndex].data[dataPointIndex];
|
||||
const from = new Date(task.y[0]);
|
||||
const to = new Date(task.y[1]);
|
||||
|
||||
const formatDate = (date) => {
|
||||
return date.getDate() + ' ' + date.toLocaleString('default', { month: 'short' }) + ' ' + date.getFullYear();
|
||||
};
|
||||
|
||||
return (
|
||||
'<div class="p-2">' +
|
||||
'<div><b>' + w.globals.initialSeries[seriesIndex].name + ': ' + task.x + '</b></div>' +
|
||||
'<div>Start: ' + formatDate(from) + '</div>' +
|
||||
'<div>End: ' + formatDate(to) + '</div>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[100].hex],
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const multipleSeriesGroupTimelineChart = new ApexCharts(
|
||||
document.getElementById('multiple-series-group-timeline-chart'),
|
||||
multipleSeriesGroupTimelineOptions
|
||||
);
|
||||
multipleSeriesGroupTimelineChart.render();
|
||||
}
|
||||
|
||||
// Dumbbell Chart (Horizontal)
|
||||
if (document.getElementById('dumbbell-timeline-chart')) {
|
||||
const dumbbellTimelineOptions = {
|
||||
series: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
x: 'Project A',
|
||||
y: [
|
||||
new Date('2019-01-01').getTime(),
|
||||
new Date('2019-03-15').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Project B',
|
||||
y: [
|
||||
new Date('2019-02-15').getTime(),
|
||||
new Date('2019-06-01').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Project C',
|
||||
y: [
|
||||
new Date('2019-04-01').getTime(),
|
||||
new Date('2019-08-15').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Project D',
|
||||
y: [
|
||||
new Date('2019-05-15').getTime(),
|
||||
new Date('2019-09-01').getTime()
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 'Project E',
|
||||
y: [
|
||||
new Date('2019-07-01').getTime(),
|
||||
new Date('2019-12-31').getTime()
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'rangeBar',
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: true,
|
||||
isDumbbell: true,
|
||||
dumbbellColors: [[window.colorMap.primary[500].hex, window.colorMap.danger[500].hex]]
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function (val) {
|
||||
return val
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark',
|
||||
custom: function({series, seriesIndex, dataPointIndex, w}) {
|
||||
const data = w.globals.initialSeries[seriesIndex].data[dataPointIndex];
|
||||
const startDate = new Date(data.y[0]);
|
||||
const endDate = new Date(data.y[1]);
|
||||
|
||||
const formatDate = (date) => {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
return `
|
||||
<div class="p-2">
|
||||
<div><b>${data.x}</b></div>
|
||||
<div>Start: ${formatDate(startDate)}</div>
|
||||
<div>End: ${formatDate(endDate)}</div>
|
||||
<div>Duration: ${Math.floor((endDate - startDate) / (1000 * 60 * 60 * 24))} days</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const dumbbellTimelineChart = new ApexCharts(
|
||||
document.getElementById('dumbbell-timeline-chart'),
|
||||
dumbbellTimelineOptions
|
||||
);
|
||||
dumbbellTimelineChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,310 @@
|
||||
|
||||
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Basic Treemap Chart
|
||||
if (document.getElementById('basic-treemap-chart')) {
|
||||
const basicTreemapOptions = {
|
||||
series: [{
|
||||
data: [
|
||||
{
|
||||
x: 'New Delhi',
|
||||
y: 218
|
||||
}, {
|
||||
x: 'Kolkata',
|
||||
y: 149
|
||||
}, {
|
||||
x: 'Mumbai',
|
||||
y: 184
|
||||
}, {
|
||||
x: 'Ahmedabad',
|
||||
y: 55
|
||||
}, {
|
||||
x: 'Bangalore',
|
||||
y: 84
|
||||
}, {
|
||||
x: 'Pune',
|
||||
y: 31
|
||||
}, {
|
||||
x: 'Chennai',
|
||||
y: 70
|
||||
}, {
|
||||
x: 'Jaipur',
|
||||
y: 30
|
||||
}, {
|
||||
x: 'Surat',
|
||||
y: 44
|
||||
}, {
|
||||
x: 'Hyderabad',
|
||||
y: 68
|
||||
}, {
|
||||
x: 'Lucknow',
|
||||
y: 28
|
||||
}, {
|
||||
x: 'Indore',
|
||||
y: 19
|
||||
}, {
|
||||
x: 'Kanpur',
|
||||
y: 29
|
||||
}
|
||||
]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'treemap',
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[400].hex, window.colorMap.primary[100].hex],
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
}
|
||||
};
|
||||
|
||||
const basicTreemapChart = new ApexCharts(
|
||||
document.getElementById('basic-treemap-chart'),
|
||||
basicTreemapOptions
|
||||
);
|
||||
basicTreemapChart.render();
|
||||
}
|
||||
|
||||
// Multiple Series Treemap Chart
|
||||
if (document.getElementById('multiple-series-treemap-chart')) {
|
||||
const multiSeriesTreemapOptions = {
|
||||
series: [{
|
||||
name: 'Desktops',
|
||||
data: [
|
||||
{
|
||||
x: 'ABC',
|
||||
y: 10
|
||||
}, {
|
||||
x: 'DEF',
|
||||
y: 60
|
||||
}, {
|
||||
x: 'XYZ',
|
||||
y: 41
|
||||
}
|
||||
]
|
||||
}, {
|
||||
name: 'Mobile',
|
||||
data: [
|
||||
{
|
||||
x: 'ABCD',
|
||||
y: 10
|
||||
}, {
|
||||
x: 'DEFG',
|
||||
y: 20
|
||||
}, {
|
||||
x: 'WXYZ',
|
||||
y: 51
|
||||
}, {
|
||||
x: 'PQR',
|
||||
y: 30
|
||||
}, {
|
||||
x: 'MNO',
|
||||
y: 20
|
||||
}, {
|
||||
x: 'CDE',
|
||||
y: 30
|
||||
}
|
||||
]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'treemap',
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
colors: [window.colorMap.primary[500].hex, window.colorMap.primary[100].hex],
|
||||
plotOptions: {
|
||||
treemap: {
|
||||
distributed: false,
|
||||
enableShades: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const multiSeriesTreemapChart = new ApexCharts(
|
||||
document.getElementById('multiple-series-treemap-chart'),
|
||||
multiSeriesTreemapOptions
|
||||
);
|
||||
multiSeriesTreemapChart.render();
|
||||
}
|
||||
|
||||
// Color Range Treemap Chart
|
||||
if (document.getElementById('color-range-treemap-chart')) {
|
||||
const colorRangeOptions = {
|
||||
series: [{
|
||||
data: [
|
||||
{
|
||||
x: 'INTC',
|
||||
y: 1.2
|
||||
}, {
|
||||
x: 'GS',
|
||||
y: 0.4
|
||||
}, {
|
||||
x: 'CVX',
|
||||
y: -1.4
|
||||
}, {
|
||||
x: 'GE',
|
||||
y: 2.7
|
||||
}, {
|
||||
x: 'CAT',
|
||||
y: -0.3
|
||||
}, {
|
||||
x: 'RTX',
|
||||
y: 5.1
|
||||
}, {
|
||||
x: 'CSCO',
|
||||
y: -2.3
|
||||
}, {
|
||||
x: 'JNJ',
|
||||
y: 2.1
|
||||
}, {
|
||||
x: 'PG',
|
||||
y: 0.3
|
||||
}, {
|
||||
x: 'TRV',
|
||||
y: 0.12
|
||||
}, {
|
||||
x: 'MMM',
|
||||
y: -2.31
|
||||
}, {
|
||||
x: 'NKE',
|
||||
y: 3.98
|
||||
}, {
|
||||
x: 'IYT',
|
||||
y: 1.67
|
||||
}
|
||||
]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'treemap',
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
plotOptions: {
|
||||
treemap: {
|
||||
distributed: true,
|
||||
enableShades: false,
|
||||
colorScale: {
|
||||
ranges: [{
|
||||
from: -6,
|
||||
to: 0,
|
||||
color: window.colorMap.danger[500].hex
|
||||
}, {
|
||||
from: 0.001,
|
||||
to: 6,
|
||||
color: window.colorMap.primary[500].hex
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const colorRangeTreemapChart = new ApexCharts(
|
||||
document.getElementById('color-range-treemap-chart'),
|
||||
colorRangeOptions
|
||||
);
|
||||
colorRangeTreemapChart.render();
|
||||
}
|
||||
|
||||
// Distributed Treemap Chart
|
||||
if (document.getElementById('distributed-treemap-chart')) {
|
||||
const distributedTreemapOptions = {
|
||||
series: [{
|
||||
data: [
|
||||
{
|
||||
x: 'New Delhi',
|
||||
y: 218
|
||||
}, {
|
||||
x: 'Kolkata',
|
||||
y: 149
|
||||
}, {
|
||||
x: 'Mumbai',
|
||||
y: 184
|
||||
}, {
|
||||
x: 'Ahmedabad',
|
||||
y: 55
|
||||
}, {
|
||||
x: 'Bangalore',
|
||||
y: 84
|
||||
}, {
|
||||
x: 'Pune',
|
||||
y: 31
|
||||
}, {
|
||||
x: 'Chennai',
|
||||
y: 70
|
||||
}, {
|
||||
x: 'Jaipur',
|
||||
y: 30
|
||||
}, {
|
||||
x: 'Surat',
|
||||
y: 44
|
||||
}, {
|
||||
x: 'Hyderabad',
|
||||
y: 68
|
||||
}, {
|
||||
x: 'Lucknow',
|
||||
y: 28
|
||||
}, {
|
||||
x: 'Indore',
|
||||
y: 19
|
||||
}, {
|
||||
x: 'Kanpur',
|
||||
y: 29
|
||||
}
|
||||
]
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'treemap',
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.primary[800].hex, window.colorMap.primary[500].hex, window.colorMap.success[500].hex, window.colorMap.success[700].hex,
|
||||
window.colorMap.danger[500].hex, window.colorMap.warning[500].hex, window.colorMap.info[500].hex, window.colorMap.primary[100].hex
|
||||
],
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
},
|
||||
plotOptions: {
|
||||
treemap: {
|
||||
distributed: true,
|
||||
enableShades: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const distributedTreemapChart = new ApexCharts(
|
||||
document.getElementById('distributed-treemap-chart'),
|
||||
distributedTreemapOptions
|
||||
);
|
||||
distributedTreemapChart.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
VANTA.HALO({
|
||||
el: "#net",
|
||||
mouseControls: false,
|
||||
touchControls: false,
|
||||
gyroControls: false,
|
||||
color: 0xfd3995,
|
||||
size: 1.6,
|
||||
scale: 0.75,
|
||||
xOffset: 0.22,
|
||||
scaleMobile: 0.50,
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
const switchToTokenButton = document.querySelector('#switchToToken');
|
||||
const switchToRegularButton = document.querySelector('#switchToRegular');
|
||||
const regularLogin = document.querySelector('#regular-login');
|
||||
const tokenLogin = document.querySelector('#token-login');
|
||||
|
||||
switchToTokenButton.addEventListener('click', function () {
|
||||
regularLogin.classList.add('d-none');
|
||||
tokenLogin.classList.remove('d-none');
|
||||
});
|
||||
|
||||
switchToRegularButton.addEventListener('click', function () {
|
||||
tokenLogin.classList.add('d-none');
|
||||
regularLogin.classList.remove('d-none');
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
//
|
||||
|
||||
console.log('Blank page initialized');
|
||||
|
||||
console.log('blank.js loaded');
|
||||
@@ -0,0 +1,33 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Set the indeterminate state for the checkbox
|
||||
document.getElementById("defaultIndeterminate").indeterminate = true;
|
||||
// Get buttons
|
||||
const checkboxToggleBtn = document.getElementById("js-checkbox-toggle");
|
||||
const radioToggleBtn = document.getElementById("js-radio-toggle");
|
||||
// Add click event listeners
|
||||
if (checkboxToggleBtn) {
|
||||
checkboxToggleBtn.addEventListener("click", toggleCheckbox);
|
||||
}
|
||||
if (radioToggleBtn) {
|
||||
radioToggleBtn.addEventListener("click", toggleRadio);
|
||||
}
|
||||
|
||||
// Function to toggle checkbox styles
|
||||
function toggleCheckbox() {
|
||||
let checkboxes = document.querySelectorAll(".demo-checkbox .form-check-input");
|
||||
toggleText(this, "Change to CIRCLE", "Change back to default");
|
||||
checkboxes.forEach(checkbox => checkbox.classList.toggle("rounded-circle"));
|
||||
}
|
||||
|
||||
// Function to toggle radio button styles
|
||||
function toggleRadio() {
|
||||
let radios = document.querySelectorAll(".demo-radio .form-check-input");
|
||||
toggleText(this, "Change to ROUNDED", "Change back to default");
|
||||
radios.forEach(radio => radio.classList.toggle("rounded"));
|
||||
}
|
||||
|
||||
// Function to toggle button text
|
||||
function toggleText(element, text1, text2) {
|
||||
element.textContent = element.textContent.trim() === text1 ? text2 : text1;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
hljs.highlightAll();
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* Easy Pie Chart Implementation
|
||||
* Vanilla JavaScript implementation for easy pie charts
|
||||
*/
|
||||
|
||||
|
||||
// Global registry to keep track of all chart instances
|
||||
window.easyPieChartRegistry = {
|
||||
instances: [],
|
||||
elements: []
|
||||
};
|
||||
|
||||
// Global color state
|
||||
window.easyPieChartColors = {
|
||||
currentColors: {},
|
||||
previousColors: {}
|
||||
};
|
||||
|
||||
// Extract colors from the theme
|
||||
function extractEasyPieChartColors() {
|
||||
// Store previous colors
|
||||
window.easyPieChartColors.previousColors = {...window.easyPieChartColors.currentColors};
|
||||
|
||||
// Extract new colors
|
||||
window.easyPieChartColors.currentColors = {};
|
||||
|
||||
if (window.colorMap) {
|
||||
// Define color categories to extract
|
||||
const colorCategories = ['primary', 'success', 'warning', 'danger', 'info'];
|
||||
|
||||
// Get all available color shades for each category
|
||||
colorCategories.forEach(category => {
|
||||
if (window.colorMap[category]) {
|
||||
// For each category, find all available shades
|
||||
Object.keys(window.colorMap[category]).forEach(shade => {
|
||||
if (window.colorMap[category][shade]?.hex) {
|
||||
// Store each shade with a category-shade key
|
||||
const colorKey = `${category}-${shade}`;
|
||||
window.easyPieChartColors.currentColors[colorKey] = window.colorMap[category][shade].hex;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Also add bodyColor as it's commonly used
|
||||
if (window.colorMap.bootstrapVars?.bodyColor?.hex) {
|
||||
window.easyPieChartColors.currentColors['bodyColor'] = window.colorMap.bootstrapVars.bodyColor.hex;
|
||||
window.easyPieChartColors.currentColors['bodyColor-alpha'] = window.colorMap.bootstrapVars.bodyColor.rgba(0.07);
|
||||
}
|
||||
}
|
||||
|
||||
return window.easyPieChartColors.currentColors;
|
||||
}
|
||||
|
||||
// Helper to update a color value based on the before/after color mapping
|
||||
function updateEasyPieChartColorValue(color) {
|
||||
if (!color || typeof color !== 'string') return color;
|
||||
|
||||
// First, direct match from previous colors to current colors
|
||||
if (window.easyPieChartColors.previousColors) {
|
||||
for (const [key, oldColor] of Object.entries(window.easyPieChartColors.previousColors)) {
|
||||
if (color === oldColor) {
|
||||
return window.easyPieChartColors.currentColors[key] || color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no direct match, check if it's a color from the theme
|
||||
if (window.colorMap) {
|
||||
const colorCategories = ['primary', 'success', 'warning', 'danger', 'info'];
|
||||
|
||||
// Try to match with any shade in each category
|
||||
for (const category of colorCategories) {
|
||||
if (window.colorMap[category]) {
|
||||
for (const shade in window.colorMap[category]) {
|
||||
if (window.colorMap[category][shade]?.hex === color) {
|
||||
// We found a match - use the updated color for this category/shade
|
||||
return window.colorMap[category][shade].hex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle rgba colors
|
||||
if (color.includes('rgba') && window.colorMap?.bootstrapVars?.bodyColor) {
|
||||
const opacityMatch = color.match(/rgba\([^)]+,\s*([\d.]+)\)/);
|
||||
if (opacityMatch && opacityMatch[1]) {
|
||||
const opacity = parseFloat(opacityMatch[1]);
|
||||
|
||||
// Common opacities to check against
|
||||
const standardOpacities = [0.07, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9];
|
||||
const closestOpacity = standardOpacities.reduce((prev, curr) =>
|
||||
Math.abs(curr - opacity) < Math.abs(prev - opacity) ? curr : prev
|
||||
);
|
||||
|
||||
// If this is a very close match to a standard opacity, use exact value
|
||||
if (Math.abs(closestOpacity - opacity) < 0.02) {
|
||||
return window.colorMap.bootstrapVars.bodyColor.rgba(closestOpacity);
|
||||
}
|
||||
|
||||
// Otherwise use the exact opacity provided
|
||||
return window.colorMap.bootstrapVars.bodyColor.rgba(opacity);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle CSS variables
|
||||
if (color.startsWith('var(')) {
|
||||
// Try to extract the variable value
|
||||
const varName = color.replace('var(', '').replace(')', '').trim();
|
||||
const computedStyle = getComputedStyle(document.documentElement);
|
||||
const varValue = computedStyle.getPropertyValue(varName);
|
||||
if (varValue) {
|
||||
return varValue.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
// Global function to update all Easy Pie Chart colors
|
||||
window.updateEasyPieCharts = function() {
|
||||
console.log(`EasyPieChart: Updating colors for ${window.easyPieChartRegistry.instances.length} charts`);
|
||||
|
||||
// Update color maps
|
||||
extractEasyPieChartColors();
|
||||
|
||||
if (window.easyPieChartRegistry.instances.length === 0) {
|
||||
console.warn('EasyPieChart: No charts found to update');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update each chart with new colors
|
||||
window.easyPieChartRegistry.instances.forEach((chart, index) => {
|
||||
try {
|
||||
if (!chart) {
|
||||
console.warn(`EasyPieChart: Chart #${index + 1} cannot be updated`);
|
||||
return;
|
||||
}
|
||||
|
||||
const element = window.easyPieChartRegistry.elements[index];
|
||||
if (!element) return;
|
||||
|
||||
// Get computed style
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
|
||||
// Update barColor
|
||||
const currentBarColor = chart.options.barColor;
|
||||
const newBarColor = computedStyle.color || currentBarColor;
|
||||
const updatedBarColor = updateEasyPieChartColorValue(newBarColor);
|
||||
|
||||
// Update trackColor
|
||||
let updatedTrackColor;
|
||||
try {
|
||||
if (window.colorMap && window.colorMap.bootstrapVars && window.colorMap.bootstrapVars.bodyColorRgb) {
|
||||
updatedTrackColor = window.colorMap.bootstrapVars.bodyColorRgb.rgba(0.07);
|
||||
} else {
|
||||
updatedTrackColor = 'rgba(0,0,0,0.04)';
|
||||
}
|
||||
} catch (e) {
|
||||
updatedTrackColor = 'rgba(0,0,0,0.04)';
|
||||
}
|
||||
|
||||
// Update scaleColor
|
||||
const currentScaleColor = chart.options.scaleColor;
|
||||
const newScaleColor = element.dataset.scalecolor || computedStyle.color || currentScaleColor;
|
||||
const updatedScaleColor = updateEasyPieChartColorValue(newScaleColor);
|
||||
|
||||
// Apply the new colors using Canvas API's getImageData for efficiency
|
||||
const canvas = chart.renderer.getCanvas();
|
||||
const ctx = chart.renderer.getCtx();
|
||||
|
||||
// If canvas context is available, use pixel manipulation
|
||||
if (ctx && ctx.getImageData && ctx.putImageData) {
|
||||
// Store current canvas state
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const data = imageData.data;
|
||||
|
||||
// Parse colors to RGB
|
||||
const barRGB = parseColorToRGB(updatedBarColor);
|
||||
const oldBarRGB = parseColorToRGB(currentBarColor);
|
||||
const trackRGB = parseColorToRGB(updatedTrackColor);
|
||||
|
||||
// Update pixels - only change bar color (non-transparent, non-track pixels)
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
// Only process non-transparent pixels
|
||||
if (data[i + 3] > 0) {
|
||||
// Check if pixel is close to the old bar color and not track color
|
||||
const isBarPixel =
|
||||
(Math.abs(data[i] - oldBarRGB.r) < 15 &&
|
||||
Math.abs(data[i + 1] - oldBarRGB.g) < 15 &&
|
||||
Math.abs(data[i + 2] - oldBarRGB.b) < 15) ||
|
||||
(Math.abs(data[i] - trackRGB.r) > 10 ||
|
||||
Math.abs(data[i + 1] - trackRGB.g) > 10 ||
|
||||
Math.abs(data[i + 2] - trackRGB.b) > 10);
|
||||
|
||||
if (isBarPixel) {
|
||||
// Update to new bar color
|
||||
data[i] = barRGB.r;
|
||||
data[i + 1] = barRGB.g;
|
||||
data[i + 2] = barRGB.b;
|
||||
// Keep original alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Put the modified image data back
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
|
||||
// Update the chart's options
|
||||
chart.options.barColor = updatedBarColor;
|
||||
chart.options.trackColor = updatedTrackColor;
|
||||
chart.options.scaleColor = updatedScaleColor;
|
||||
|
||||
console.log(`EasyPieChart: Updated colors for chart #${index + 1} using pixel manipulation`);
|
||||
} else {
|
||||
// Fallback to standard update method
|
||||
chart.options.barColor = updatedBarColor;
|
||||
chart.options.trackColor = updatedTrackColor;
|
||||
chart.options.scaleColor = updatedScaleColor;
|
||||
|
||||
// Redraw the chart with new colors
|
||||
chart.update(chart.currentValue);
|
||||
|
||||
console.log(`EasyPieChart: Updated colors for chart #${index + 1} using redraw`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`EasyPieChart: Error updating chart #${index + 1}:`, e);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('EasyPieChart: Chart color update complete');
|
||||
};
|
||||
|
||||
// Helper function to parse color string to RGB
|
||||
function parseColorToRGB(color) {
|
||||
// For hex colors
|
||||
if (color.startsWith('#')) {
|
||||
const hex = color.substring(1);
|
||||
const bigint = parseInt(hex, 16);
|
||||
return {
|
||||
r: (bigint >> 16) & 255,
|
||||
g: (bigint >> 8) & 255,
|
||||
b: bigint & 255
|
||||
};
|
||||
}
|
||||
// For rgb/rgba colors
|
||||
else if (color.startsWith('rgb')) {
|
||||
const matches = color.match(/(\d+),\s*(\d+),\s*(\d+)/);
|
||||
if (matches) {
|
||||
return {
|
||||
r: parseInt(matches[1]),
|
||||
g: parseInt(matches[2]),
|
||||
b: parseInt(matches[3])
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return { r: 0, g: 0, b: 0 };
|
||||
}
|
||||
|
||||
// Initialize colors on load
|
||||
extractEasyPieChartColors();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
/*
|
||||
Browsers optimize canvas rendering for drawing, not for reading pixel data.
|
||||
When you use getImageData() repeatedly (as some chart libraries do for animations or gradients),
|
||||
it can cause readback performance hits.
|
||||
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas
|
||||
*/
|
||||
(function patchEasyPieCanvasContext() {
|
||||
const originalGetContext = HTMLCanvasElement.prototype.getContext;
|
||||
HTMLCanvasElement.prototype.getContext = function(type, options) {
|
||||
if (type === '2d') {
|
||||
options = { ...options, willReadFrequently: true };
|
||||
}
|
||||
return originalGetContext.call(this, type, options);
|
||||
};
|
||||
})();
|
||||
|
||||
/* Easy pie chart initialization
|
||||
Pure JavaScript implementation with no jQuery dependencies
|
||||
*/
|
||||
document.querySelectorAll('.js-easy-pie-chart').forEach(function(element) {
|
||||
// Get element properties using vanilla JS
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
const barcolor = computedStyle.color || 'var(--primary-700)';
|
||||
|
||||
// Check if window.colorMap exists, if not use a fallback
|
||||
let trackcolor;
|
||||
try {
|
||||
if (window.colorMap && window.colorMap.bootstrapVars && window.colorMap.bootstrapVars.bodyColorRgb) {
|
||||
trackcolor = window.colorMap.bootstrapVars.bodyColorRgb.rgba(0.07);
|
||||
} else {
|
||||
trackcolor = 'rgba(0,0,0,0.04)';
|
||||
}
|
||||
} catch (e) {
|
||||
trackcolor = 'rgba(0,0,0,0.04)';
|
||||
}
|
||||
|
||||
// Read dataset attributes with fallbacks
|
||||
const size = parseInt(element.dataset.piesize) || 50;
|
||||
const scalecolor = element.dataset.scalecolor || computedStyle.color;
|
||||
const scalelength = parseInt(element.dataset.scalelength) || 0;
|
||||
const linewidth = parseInt(element.dataset.linewidth) || parseInt(size / 8.5);
|
||||
const linecap = element.dataset.linecap || 'butt'; // butt, round and square.
|
||||
|
||||
// Create EasyPieChart instance
|
||||
const chart = new EasyPieChart(element, {
|
||||
size: size,
|
||||
barColor: barcolor,
|
||||
trackColor: trackcolor,
|
||||
scaleColor: scalecolor,
|
||||
scaleLength: scalelength,
|
||||
lineCap: linecap,
|
||||
lineWidth: linewidth,
|
||||
animate: {
|
||||
duration: 1500,
|
||||
enabled: true
|
||||
},
|
||||
easing: 'easeOutQuad', // Use our built-in easing function
|
||||
onStep: function(from, to, percent) {
|
||||
// Find the percentage element and update its text
|
||||
const percentElement = element.querySelector('.js-percent');
|
||||
if (percentElement) {
|
||||
percentElement.textContent = Math.round(percent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Register the chart instance
|
||||
window.easyPieChartRegistry.instances.push(chart);
|
||||
window.easyPieChartRegistry.elements.push(element);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,726 @@
|
||||
/*!
|
||||
* FullCalendar Implementation for SmartAdmin WebApp
|
||||
* ©2025 SmartAdmin WebApp
|
||||
*/
|
||||
|
||||
// Sample events data
|
||||
const eventData = [
|
||||
{ id: '1', title: 'Team Meeting', start: new Date(new Date().setHours(10, 0)), end: new Date(new Date().setHours(11, 30)), backgroundColor: 'var(--primary-500)', borderColor: 'var(--primary-600)', description: 'Weekly team status meeting', location: 'Conference Room A' },
|
||||
{ id: '2', title: 'Client Call', start: new Date(new Date().setDate(new Date().getDate() + 1)), allDay: true, backgroundColor: 'var(--success-500)', borderColor: 'var(--success-600)', description: 'Quarterly review with major client', location: 'Zoom Meeting' },
|
||||
{ id: '3', title: 'Product Launch', start: new Date(new Date().setDate(new Date().getDate() + 3)), end: new Date(new Date().setDate(new Date().getDate() + 3)).setHours(15, 0), backgroundColor: 'var(--danger-500)', borderColor: 'var(--danger-600)', description: 'New product launch event', location: 'Main Auditorium' },
|
||||
{ id: '4', title: 'Deadline: Q3 Report', start: new Date(new Date().setDate(new Date().getDate() + 5)), allDay: true, backgroundColor: 'var(--warning-500)', borderColor: 'var(--warning-600)', description: 'Submit quarterly financial reports', location: 'Finance Department' },
|
||||
{ id: '5', title: 'Training Session', start: new Date(new Date().setDate(new Date().getDate() - 2)), end: new Date(new Date().setDate(new Date().getDate() - 2)).setHours(16, 0), backgroundColor: 'var(--info-500)', borderColor: 'var(--info-600)', description: 'New software training for all employees', location: 'Training Room B' },
|
||||
{ id: '6', title: 'Board Meeting', start: new Date(new Date().setDate(new Date().getDate() + 7)), allDay: true, backgroundColor: 'var(--danger-500)', borderColor: 'var(--danger-700)', description: 'Annual board meeting with stakeholders', location: 'Executive Boardroom' },
|
||||
{ id: '7', title: 'Website Maintenance', start: new Date(new Date().setDate(new Date().getDate() - 1)).setHours(23, 0), end: new Date(new Date().setDate(new Date().getDate())).setHours(5, 0), backgroundColor: 'var(--info-500)', borderColor: 'var(--info-600)', description: 'Scheduled website maintenance window', location: 'IT Department' },
|
||||
{ id: '8', title: 'Team Building', start: new Date(new Date().setDate(new Date().getDate() + 12)), end: new Date(new Date().setDate(new Date().getDate() + 12)).setHours(16, 0), backgroundColor: 'var(--primary-300)', borderColor: 'var(--primary-400)', description: 'Annual team building activities', location: 'City Park' },
|
||||
{ id: '9', title: 'Client Dinner', start: new Date(new Date().setDate(new Date().getDate() + 4)).setHours(19, 0), end: new Date(new Date().setDate(new Date().getDate() + 4)).setHours(21, 0), backgroundColor: 'var(--success-300)', borderColor: 'var(--success-400)', description: 'Dinner with potential investors', location: 'Downtown Restaurant' },
|
||||
{ id: '10', title: 'Vacation', start: new Date(new Date().setDate(new Date().getDate() + 14)), end: new Date(new Date().setDate(new Date().getDate() + 21)), backgroundColor: 'var(--bs-teal)', borderColor: 'var(--bs-teal)', description: 'Annual vacation time', location: 'Beach Resort' },
|
||||
{ id: '11', title: 'Marketing Campaign', start: new Date(new Date().setDate(new Date().getDate() + 2)), end: new Date(new Date().setDate(new Date().getDate() + 2)).setHours(17, 0), backgroundColor: 'var(--bs-purple)', borderColor: 'var(--bs-purple)', description: 'Launch new product marketing campaign', location: 'Marketing Department', category: 'marketing' },
|
||||
{ id: '12', title: 'Sales Meeting', start: new Date(new Date().setDate(new Date().getDate() + 3)).setHours(9, 0), end: new Date(new Date().setDate(new Date().getDate() + 3)).setHours(10, 30), backgroundColor: 'var(--success-600)', borderColor: 'var(--success-700)', description: 'Monthly sales team catchup', location: 'Conference Room B', category: 'sales' },
|
||||
{ id: '13', title: 'Development Sprint Review', start: new Date(new Date().setDate(new Date().getDate() + 6)).setHours(14, 0), end: new Date(new Date().setDate(new Date().getDate() + 6)).setHours(16, 0), backgroundColor: 'var(--bs-indigo)', borderColor: 'var(--bs-indigo)', description: 'End of sprint review with stakeholders', location: 'Dev Team Area', category: 'development' },
|
||||
{ id: '14', title: 'Tech Conference', start: new Date(new Date().setDate(new Date().getDate() + 8)), end: new Date(new Date().setDate(new Date().getDate() + 10)), backgroundColor: 'var(--bs-indigo)', borderColor: 'var(--bs-indigo)', description: 'Annual tech conference for industry professionals', location: 'Convention Center', category: 'conference' },
|
||||
{ id: '15', title: 'Dentist Appointment', start: new Date(new Date().setDate(new Date().getDate() + 2)).setHours(14, 0), end: new Date(new Date().setDate(new Date().getDate() + 2)).setHours(15, 0), backgroundColor: 'var(--warning-500)', borderColor: 'var(--warning-600)', description: 'Routine dental checkup', location: 'Downtown Clinic', category: 'personal' },
|
||||
{ id: '16', title: 'Project Milestone: Beta Release', start: new Date(new Date().setDate(new Date().getDate() + 15)), allDay: true, backgroundColor: 'var(--success-500)', borderColor: 'var(--success-600)', description: 'Beta release of the new app', location: 'Development Team', category: 'development' },
|
||||
{ id: '17', title: 'Company Announcement', start: new Date(new Date().setDate(new Date().getDate() + 1)).setHours(11, 0), end: new Date(new Date().setDate(new Date().getDate() + 1)).setHours(11, 30), backgroundColor: 'var(--primary-500)', borderColor: 'var(--primary-600)', description: 'Announcement of new company policies', location: 'Main Hall', category: 'announcement' },
|
||||
{ id: '18', title: 'Weekly Team Sync', start: new Date(new Date().setDate(new Date().getDate() + 4)).setHours(9, 0), end: new Date(new Date().setDate(new Date().getDate() + 4)).setHours(9, 30), backgroundColor: 'var(--primary-400)', borderColor: 'var(--primary-500)', description: 'Recurring weekly sync for project updates', location: 'Meeting Room C', category: 'team', extendedProps: { recurrence: 'weekly' } }
|
||||
];
|
||||
|
||||
// Define fixed event categories
|
||||
const eventCategories = [
|
||||
{ id: 'general', name: 'General', color: 'var(--primary-500)' },
|
||||
{ id: 'meeting', name: 'Meetings', color: 'var(--success-500)' },
|
||||
{ id: 'task', name: 'Tasks', color: 'var(--warning-500)' },
|
||||
{ id: 'deadline', name: 'Deadlines', color: 'var(--danger-500)' },
|
||||
{ id: 'marketing', name: 'Marketing', color: 'var(--bs-purple)' },
|
||||
{ id: 'sales', name: 'Sales', color: 'var(--success-600)' },
|
||||
{ id: 'development', name: 'Development', color: 'var(--bs-indigo)' }
|
||||
];
|
||||
|
||||
// Initialize calendar after all scripts are loaded
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Initialize event modals
|
||||
const eventModal = new bootstrap.Modal(document.getElementById('eventModal'));
|
||||
const deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'));
|
||||
const categoryModal = new bootstrap.Modal(document.getElementById('categoryModal'));
|
||||
|
||||
// Form element references
|
||||
const eventForm = document.getElementById('eventForm');
|
||||
const eventTitle = document.getElementById('eventTitle');
|
||||
const eventStart = document.getElementById('eventStart');
|
||||
const eventEnd = document.getElementById('eventEnd');
|
||||
const eventAllDay = document.getElementById('eventAllDay');
|
||||
const eventColor = document.getElementById('eventColor');
|
||||
const eventDescription = document.getElementById('eventDescription');
|
||||
const eventLocation = document.getElementById('eventLocation');
|
||||
const eventCategory = document.getElementById('eventCategory');
|
||||
const eventId = document.getElementById('eventId');
|
||||
const deleteId = document.getElementById('deleteEventId');
|
||||
|
||||
// Populate category select dropdown
|
||||
populateCategoryDropdown();
|
||||
|
||||
// Pre-process events to ensure all have a category
|
||||
eventData.forEach(event => {
|
||||
// Initialize extendedProps if it doesn't exist
|
||||
if (!event.extendedProps) {
|
||||
event.extendedProps = {};
|
||||
}
|
||||
|
||||
// Set category in extendedProps if missing
|
||||
if (!event.extendedProps.category) {
|
||||
event.extendedProps.category = event.category || 'general';
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize the calendar
|
||||
const calendarEl = document.getElementById('calendar');
|
||||
const calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
initialView: 'dayGridMonth',
|
||||
themeSystem: 'bootstrap',
|
||||
headerToolbar: {
|
||||
right: 'today prev,next',
|
||||
left: 'title'
|
||||
},
|
||||
events: [],
|
||||
// Apply custom styling after render
|
||||
datesSet: function () {
|
||||
setTimeout(() => {
|
||||
// Replace fc-button-group with btn-group
|
||||
document.querySelectorAll('.fc-button-group').forEach((el) => {
|
||||
el.classList.remove('fc-button-group');
|
||||
el.classList.add('btn-group');
|
||||
});
|
||||
|
||||
// Optional: Replace fc-button styles with Bootstrap
|
||||
document.querySelectorAll('.fc-button').forEach((btn) => {
|
||||
btn.classList.remove('fc-button');
|
||||
btn.classList.add('btn', 'btn-outline-default', 'btn-sm');
|
||||
});
|
||||
|
||||
document.querySelectorAll('.fc-button-primary').forEach((btn) => {
|
||||
btn.classList.remove('fc-button-primary');
|
||||
});
|
||||
|
||||
// Highlight active button
|
||||
const activeBtn = document.querySelector('.fc-button-active');
|
||||
if (activeBtn) {
|
||||
activeBtn.classList.remove('fc-button-active');
|
||||
activeBtn.classList.add('active');
|
||||
}
|
||||
|
||||
const groupBtn = document.querySelector('.fc-button-group');
|
||||
if (groupBtn) {
|
||||
groupBtn.className = 'btn-group';
|
||||
}
|
||||
|
||||
const prevBtn = document.querySelector('.fc-prev-button');
|
||||
if (prevBtn) {
|
||||
prevBtn.className = 'btn btn-outline-default btn-sm';
|
||||
}
|
||||
|
||||
const nextBtn = document.querySelector('.fc-next-button');
|
||||
if (nextBtn) {
|
||||
nextBtn.className = 'btn btn-outline-default btn-sm';
|
||||
}
|
||||
|
||||
const todayBtn = document.querySelector('.fc-today-button');
|
||||
if (todayBtn) {
|
||||
todayBtn.className = 'btn btn-outline-default btn-sm';
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
|
||||
footerToolbar: {
|
||||
left: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
|
||||
},
|
||||
buttonText: {
|
||||
today: 'Today',
|
||||
month: 'Month',
|
||||
week: 'Week',
|
||||
day: 'Day',
|
||||
list: 'List'
|
||||
},
|
||||
dayMaxEvents: 2, // Show only 2 events per day, then show "more" link
|
||||
height: undefined, // Let the calendar calculate its own height
|
||||
minHeight: '750px', // Set minimum height for desktop
|
||||
editable: true,
|
||||
droppable: true, // allow draggable events
|
||||
selectable: true, // allow selection for new events
|
||||
businessHours: {
|
||||
daysOfWeek: [1, 2, 3, 4, 5], // Monday - Friday
|
||||
startTime: '9:00',
|
||||
endTime: '17:00'
|
||||
},
|
||||
eventSources: [
|
||||
{ events: eventData }
|
||||
],
|
||||
eventTimeFormat: {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
meridiem: 'short'
|
||||
},
|
||||
// Handle date selection for new events
|
||||
select: function (info) {
|
||||
// Reset form
|
||||
eventForm.reset();
|
||||
eventId.value = '';
|
||||
|
||||
// Set initial form values from selection
|
||||
const startDate = info.start;
|
||||
const endDate = info.end;
|
||||
|
||||
eventStart.value = formatDateForInput(startDate);
|
||||
eventEnd.value = formatDateForInput(endDate);
|
||||
eventAllDay.checked = info.allDay;
|
||||
eventColor.value = 'var(--primary-500)';
|
||||
|
||||
// Show the modal
|
||||
document.getElementById('eventModalTitle').textContent = 'Add New Event';
|
||||
document.getElementById('deleteEvent').style.display = 'none';
|
||||
eventModal.show();
|
||||
},
|
||||
// Handle event click for editing
|
||||
eventClick: function (info) {
|
||||
const event = info.event;
|
||||
console.log("Clicked event:", event); // Debug
|
||||
|
||||
// Populate form with event data
|
||||
eventId.value = event.id;
|
||||
eventTitle.value = event.title;
|
||||
eventStart.value = formatDateForInput(event.start);
|
||||
eventEnd.value = event.end ? formatDateForInput(event.end) : '';
|
||||
eventAllDay.checked = event.allDay;
|
||||
eventColor.value = event.backgroundColor || 'var(--primary-500)';
|
||||
|
||||
// Make sure extendedProps is defined before accessing properties
|
||||
if (event.extendedProps) {
|
||||
eventDescription.value = event.extendedProps.description || '';
|
||||
eventLocation.value = event.extendedProps.location || '';
|
||||
eventCategory.value = event.extendedProps.category || '';
|
||||
|
||||
console.log("Event category:", event.extendedProps.category); // Debug
|
||||
} else {
|
||||
eventDescription.value = '';
|
||||
eventLocation.value = '';
|
||||
eventCategory.value = '';
|
||||
}
|
||||
|
||||
// Show the modal
|
||||
document.getElementById('eventModalTitle').textContent = 'Edit Event';
|
||||
document.getElementById('deleteEvent').style.display = 'block';
|
||||
eventModal.show();
|
||||
},
|
||||
// Allow event dragging and resizing
|
||||
eventDrop: function (info) {
|
||||
showToast('Event moved: ' + info.event.title);
|
||||
},
|
||||
eventResize: function (info) {
|
||||
showToast('Event duration changed: ' + info.event.title);
|
||||
},
|
||||
// Handle external draggables
|
||||
drop: function (info) {
|
||||
// Remove the dragged element from the list if the checkbox is checked
|
||||
if (document.getElementById('removeAfterDrop').checked) {
|
||||
info.draggedEl.parentNode.removeChild(info.draggedEl);
|
||||
}
|
||||
showToast('New event added via drag & drop');
|
||||
},
|
||||
// Add hover tooltips to events
|
||||
eventDidMount: function (info) {
|
||||
const event = info.event;
|
||||
|
||||
// Create tooltip with rich content
|
||||
if (event.extendedProps.description || event.extendedProps.location) {
|
||||
const tooltipContent = document.createElement('div');
|
||||
tooltipContent.classList.add('fc-event-tooltip');
|
||||
|
||||
const titleEl = document.createElement('div');
|
||||
titleEl.classList.add('tooltip-title');
|
||||
titleEl.textContent = event.title;
|
||||
tooltipContent.appendChild(titleEl);
|
||||
|
||||
if (event.extendedProps.description) {
|
||||
const descEl = document.createElement('div');
|
||||
descEl.classList.add('tooltip-desc');
|
||||
descEl.textContent = event.extendedProps.description;
|
||||
tooltipContent.appendChild(descEl);
|
||||
}
|
||||
|
||||
if (event.extendedProps.location) {
|
||||
const locEl = document.createElement('div');
|
||||
locEl.classList.add('tooltip-location');
|
||||
locEl.innerHTML = '<i class="fa fa-map-marker-alt me-1"></i> ' + event.extendedProps.location;
|
||||
tooltipContent.appendChild(locEl);
|
||||
}
|
||||
|
||||
// Show time for non-all-day events
|
||||
if (!event.allDay && event.start) {
|
||||
const timeEl = document.createElement('div');
|
||||
timeEl.classList.add('tooltip-time');
|
||||
timeEl.innerHTML = '<i class="fa fa-clock me-1"></i> ' +
|
||||
event.start.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
if (event.end) {
|
||||
timeEl.innerHTML += ' - ' + event.end.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
tooltipContent.appendChild(timeEl);
|
||||
}
|
||||
|
||||
// Initialize Bootstrap tooltip
|
||||
new bootstrap.Tooltip(info.el, {
|
||||
title: tooltipContent.outerHTML,
|
||||
placement: 'top',
|
||||
trigger: 'hover',
|
||||
html: true,
|
||||
container: 'body'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// First initialize the calendar, then setup filters
|
||||
calendar.render();
|
||||
|
||||
// Set up filter controls after calendar initialization
|
||||
setupFilterControls();
|
||||
|
||||
// Initialize draggable events
|
||||
const draggableEl = document.getElementById('external-events');
|
||||
new FullCalendar.Draggable(draggableEl, {
|
||||
itemSelector: '.external-event',
|
||||
eventData: function (eventEl) {
|
||||
return {
|
||||
title: eventEl.innerText.trim(),
|
||||
backgroundColor: window.getComputedStyle(eventEl).backgroundColor,
|
||||
borderColor: window.getComputedStyle(eventEl).borderColor,
|
||||
description: eventEl.dataset.description || '',
|
||||
location: eventEl.dataset.location || '',
|
||||
category: eventEl.dataset.category || ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Handle form submission
|
||||
eventForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Prepare event data
|
||||
const eventData = {
|
||||
title: eventTitle.value,
|
||||
start: new Date(eventStart.value),
|
||||
end: eventEnd.value ? new Date(eventEnd.value) : null,
|
||||
allDay: eventAllDay.checked,
|
||||
backgroundColor: eventColor.value,
|
||||
borderColor: eventColor.value,
|
||||
extendedProps: {
|
||||
description: eventDescription.value,
|
||||
location: eventLocation.value,
|
||||
category: eventCategory.value || 'general'
|
||||
}
|
||||
};
|
||||
|
||||
// If a category is selected, use its color
|
||||
if (eventCategory.value) {
|
||||
const category = eventCategories.find(cat => cat.id === eventCategory.value);
|
||||
if (category) {
|
||||
eventData.backgroundColor = category.color;
|
||||
eventData.borderColor = category.color;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Form submitted with data:", eventData); // Debug
|
||||
console.log("Event ID:", eventId.value); // Debug
|
||||
|
||||
if (eventId.value) {
|
||||
// Update existing event
|
||||
const existingEvent = calendar.getEventById(eventId.value);
|
||||
console.log("Existing event:", existingEvent); // Debug
|
||||
|
||||
if (existingEvent) {
|
||||
// Remove the old event and add a new one to ensure all properties are updated
|
||||
existingEvent.remove();
|
||||
|
||||
// Add the updated event with the same ID
|
||||
eventData.id = eventId.value;
|
||||
calendar.addEvent(eventData);
|
||||
|
||||
showToast('Event updated successfully');
|
||||
} else {
|
||||
console.error("Could not find event with ID:", eventId.value);
|
||||
showToast('Error updating event: Event not found');
|
||||
}
|
||||
} else {
|
||||
// Create new event
|
||||
eventData.id = 'event-' + new Date().getTime();
|
||||
calendar.addEvent(eventData);
|
||||
showToast('Event added successfully');
|
||||
}
|
||||
|
||||
// Close the modal
|
||||
eventModal.hide();
|
||||
|
||||
// Refresh filters to ensure visibility
|
||||
filterEvents();
|
||||
});
|
||||
|
||||
// Handle delete button click
|
||||
const deleteEventBtn = document.getElementById('deleteEvent');
|
||||
if (deleteEventBtn) {
|
||||
deleteEventBtn.addEventListener('click', function () {
|
||||
deleteId.value = eventId.value;
|
||||
eventModal.hide();
|
||||
deleteModal.show();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle confirm delete
|
||||
const confirmDeleteBtn = document.getElementById('confirmDelete');
|
||||
if (confirmDeleteBtn) {
|
||||
confirmDeleteBtn.addEventListener('click', function () {
|
||||
const event = calendar.getEventById(deleteId.value);
|
||||
if (event) {
|
||||
event.remove();
|
||||
showToast('Event deleted successfully');
|
||||
}
|
||||
deleteModal.hide();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle category management modal
|
||||
const manageCategoriesBtn = document.getElementById('manageCategoriesBtn');
|
||||
if (manageCategoriesBtn) {
|
||||
manageCategoriesBtn.addEventListener('click', function () {
|
||||
populateCategoryList();
|
||||
categoryModal.show();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle category form submission
|
||||
const categoryForm = document.getElementById('categoryForm');
|
||||
if (categoryForm) {
|
||||
categoryForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const categoryName = document.getElementById('categoryName').value;
|
||||
const categoryColor = document.getElementById('categoryColor').value;
|
||||
|
||||
// Simple ID generation
|
||||
const categoryId = 'cat-' + new Date().getTime();
|
||||
|
||||
// Add to categories
|
||||
eventCategories.push({
|
||||
id: categoryId,
|
||||
name: categoryName,
|
||||
color: categoryColor
|
||||
});
|
||||
|
||||
// Update dropdowns
|
||||
populateCategoryDropdown();
|
||||
populateCategoryList();
|
||||
|
||||
showToast('Category added successfully');
|
||||
document.getElementById('categoryName').value = '';
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle all category filters
|
||||
const toggleAllFiltersBtn = document.getElementById('toggleAllFilters');
|
||||
if (toggleAllFiltersBtn) {
|
||||
toggleAllFiltersBtn.addEventListener('click', function () {
|
||||
const checkboxes = document.querySelectorAll('.category-filter');
|
||||
// Check if all are currently checked
|
||||
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
||||
|
||||
// Toggle all checkboxes to the opposite state
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = !allChecked;
|
||||
});
|
||||
|
||||
// Update icon based on new state
|
||||
const icon = this.querySelector('i');
|
||||
if (icon) {
|
||||
icon.className = !allChecked ? 'fa fa-check-square' : 'fa fa-square';
|
||||
}
|
||||
|
||||
// Trigger filtering
|
||||
filterEvents();
|
||||
|
||||
showToast(!allChecked ? 'All categories enabled' : 'All categories disabled');
|
||||
});
|
||||
}
|
||||
|
||||
// Filter events when checkboxes change
|
||||
const categoryFilters = document.querySelectorAll('.category-filter');
|
||||
if (categoryFilters && categoryFilters.length > 0) {
|
||||
categoryFilters.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', filterEvents);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// Format dates for input elements
|
||||
function formatDateForInput(date) {
|
||||
if (!date) return '';
|
||||
|
||||
const localDate = new Date(date);
|
||||
|
||||
// Format: YYYY-MM-DDTHH:MM
|
||||
const year = localDate.getFullYear();
|
||||
const month = String(localDate.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(localDate.getDate()).padStart(2, '0');
|
||||
const hours = String(localDate.getHours()).padStart(2, '0');
|
||||
const minutes = String(localDate.getMinutes()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
// Show toast notifications
|
||||
function showToast(message) {
|
||||
const toastContainer = document.getElementById('toast-container');
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast align-items-center show text-white bg-primary border-0';
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.setAttribute('aria-live', 'assertive');
|
||||
toast.setAttribute('aria-atomic', 'true');
|
||||
|
||||
toast.innerHTML = `
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">
|
||||
${message}
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
toastContainer.appendChild(toast);
|
||||
|
||||
// Auto-remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Populate category dropdown in event form
|
||||
function populateCategoryDropdown() {
|
||||
const select = document.getElementById('eventCategory');
|
||||
select.innerHTML = '<option value="">Select Category</option>';
|
||||
|
||||
eventCategories.forEach(category => {
|
||||
const option = document.createElement('option');
|
||||
option.value = category.id;
|
||||
option.textContent = category.name;
|
||||
option.style.color = category.color;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// Populate category list in management modal
|
||||
function populateCategoryList() {
|
||||
const list = document.getElementById('categoryList');
|
||||
list.innerHTML = '';
|
||||
|
||||
eventCategories.forEach((category, index) => {
|
||||
// Don't allow deletion of default categories (first 7)
|
||||
const isDefaultCategory = index < 7;
|
||||
|
||||
const item = document.createElement('li');
|
||||
item.className = 'list-group-item d-flex align-items-center justify-content-between';
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="badge d-block rounded-circle p-1" style="background-color: ${category.color};"></span>
|
||||
<span class="ms-2">${category.name}</span>
|
||||
</div>
|
||||
${!isDefaultCategory ?
|
||||
`<button type="button" class="btn btn-xs btn-outline-danger delete-category" data-id="${category.id}">
|
||||
Delete
|
||||
</button>` :
|
||||
''}
|
||||
`;
|
||||
|
||||
list.appendChild(item);
|
||||
});
|
||||
|
||||
// Add event listeners to delete buttons
|
||||
document.querySelectorAll('.delete-category').forEach(button => {
|
||||
button.addEventListener('click', function () {
|
||||
const categoryId = this.getAttribute('data-id');
|
||||
deleteCategory(categoryId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Delete a category
|
||||
function deleteCategory(categoryId) {
|
||||
// Find the index of the category
|
||||
const index = eventCategories.findIndex(cat => cat.id === categoryId);
|
||||
if (index === -1 || index < 7) return; // Don't delete default categories
|
||||
|
||||
// Get the category name for the confirmation message
|
||||
const categoryName = eventCategories[index].name;
|
||||
|
||||
// Remove the category
|
||||
eventCategories.splice(index, 1);
|
||||
|
||||
// Update the UI
|
||||
populateCategoryDropdown();
|
||||
populateCategoryList();
|
||||
setupFilterControls();
|
||||
|
||||
// Update events that used this category
|
||||
calendar.getEvents().forEach(event => {
|
||||
if (event.extendedProps.category === categoryId) {
|
||||
event.setExtendedProp('category', 'general');
|
||||
event.setProp('backgroundColor', eventCategories[0].color);
|
||||
event.setProp('borderColor', eventCategories[0].color);
|
||||
}
|
||||
});
|
||||
|
||||
showToast(`Category "${categoryName}" deleted`);
|
||||
}
|
||||
|
||||
// Set up filter controls
|
||||
function setupFilterControls() {
|
||||
const container = document.getElementById('category-filters');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
eventCategories.forEach(category => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'form-check';
|
||||
|
||||
div.innerHTML = `
|
||||
<input class="form-check-input category-filter" type="checkbox" value="${category.id}" id="filter-${category.id}">
|
||||
<label class="form-check-label d-flex align-items-center" for="filter-${category.id}">
|
||||
<span class="badge rounded-circle p-1 me-2" style="background-color: ${category.color}; width: 0.25rem; height: 0.25rem;"> </span>
|
||||
${category.name}
|
||||
</label>
|
||||
`;
|
||||
|
||||
container.appendChild(div);
|
||||
});
|
||||
|
||||
// Add event listeners to checkboxes after they're created
|
||||
document.querySelectorAll('.category-filter').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', filterEvents);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter events based on category checkboxes
|
||||
function filterEvents() {
|
||||
if (!calendar) return; // Prevent execution if calendar is not initialized
|
||||
|
||||
const selectedCategories = [];
|
||||
document.querySelectorAll('.category-filter:checked').forEach(checkbox => {
|
||||
selectedCategories.push(checkbox.value);
|
||||
});
|
||||
|
||||
console.log("Selected categories:", selectedCategories); // For debugging
|
||||
|
||||
// If general is selected, make sure any uncategorized events show up
|
||||
const includeGeneral = selectedCategories.includes('general');
|
||||
|
||||
// Handle all events, including those without a category
|
||||
calendar.getEvents().forEach(event => {
|
||||
// Get the event's category, defaulting to 'general' if not set
|
||||
let eventCategory = event.extendedProps && event.extendedProps.category ?
|
||||
event.extendedProps.category : 'general';
|
||||
|
||||
console.log(`Event: ${event.title}, Category: ${eventCategory}`); // For debugging
|
||||
|
||||
// Show all events if no categories are selected OR the event's category is selected
|
||||
// OR if the event has no category and general is selected
|
||||
if (selectedCategories.length === 0 ||
|
||||
selectedCategories.includes(eventCategory) ||
|
||||
(eventCategory === 'general' && includeGeneral)) {
|
||||
event.setProp('display', 'auto');
|
||||
} else {
|
||||
event.setProp('display', 'none');
|
||||
}
|
||||
});
|
||||
|
||||
// Try both update methods to ensure rendering
|
||||
calendar.updateSize();
|
||||
}
|
||||
|
||||
// Export events as JSON
|
||||
const exportEventsBtn = document.getElementById('exportEvents');
|
||||
if (exportEventsBtn) {
|
||||
exportEventsBtn.addEventListener('click', function () {
|
||||
const events = calendar.getEvents().map(event => {
|
||||
return {
|
||||
id: event.id,
|
||||
title: event.title,
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
allDay: event.allDay,
|
||||
backgroundColor: event.backgroundColor,
|
||||
borderColor: event.borderColor,
|
||||
description: event.extendedProps.description,
|
||||
location: event.extendedProps.location,
|
||||
category: event.extendedProps.category
|
||||
};
|
||||
});
|
||||
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(events, null, 2));
|
||||
const downloadAnchor = document.createElement('a');
|
||||
downloadAnchor.setAttribute("href", dataStr);
|
||||
downloadAnchor.setAttribute("download", "calendar-events.json");
|
||||
document.body.appendChild(downloadAnchor);
|
||||
downloadAnchor.click();
|
||||
downloadAnchor.remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Import events from JSON
|
||||
const importEventsBtn = document.getElementById('importEvents');
|
||||
if (importEventsBtn) {
|
||||
importEventsBtn.addEventListener('change', function (e) {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
try {
|
||||
const events = JSON.parse(e.target.result);
|
||||
|
||||
// Remove current events
|
||||
calendar.getEvents().forEach(event => event.remove());
|
||||
|
||||
// Add imported events
|
||||
events.forEach(event => {
|
||||
calendar.addEvent({
|
||||
id: event.id || 'imported-' + new Date().getTime() + Math.random().toString(36).substr(2, 5),
|
||||
title: event.title,
|
||||
start: new Date(event.start),
|
||||
end: event.end ? new Date(event.end) : null,
|
||||
allDay: event.allDay,
|
||||
backgroundColor: event.backgroundColor,
|
||||
borderColor: event.borderColor,
|
||||
extendedProps: {
|
||||
description: event.description,
|
||||
location: event.location,
|
||||
category: event.category
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
showToast('Events imported successfully');
|
||||
} catch (err) {
|
||||
showToast('Error importing events: ' + err.message);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
// Print calendar button
|
||||
const printCalendarBtn = document.getElementById('printCalendar');
|
||||
if (printCalendarBtn) {
|
||||
printCalendarBtn.addEventListener('click', function () {
|
||||
window.print();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,350 @@
|
||||
// NOTE: The scripts is used for all iconography pages and for demo purposes, if you wish you may use any part of the code for your own project
|
||||
|
||||
// Global variables (add new ones)
|
||||
let currentSvgWeight = 'sa-thin'; // Default weight for SVG icons
|
||||
let isNoFill = false; // Default no-fill state for SVG icons
|
||||
// Global variables
|
||||
let allIcons = [];
|
||||
let searchHistory = new Set();
|
||||
let searchTimeout = null;
|
||||
let currentToast = null;
|
||||
let iconMappings = {};
|
||||
let currentSearchTerm = '';
|
||||
let currentIconSet = 'sa';
|
||||
|
||||
// Semantic matches
|
||||
function findSemanticMatches(searchTerm) {
|
||||
searchTerm = searchTerm.toLowerCase().replace(/\s+/g, '-');
|
||||
if (iconMappings[searchTerm]) return iconMappings[searchTerm];
|
||||
|
||||
const partialMatches = Object.keys(iconMappings).filter(key => {
|
||||
const normalizedKey = key.toLowerCase().replace(/\s+/g, '-').replace(/^-/, ''); // Strip leading hyphen
|
||||
return normalizedKey.includes(searchTerm) || searchTerm.includes(normalizedKey);
|
||||
});
|
||||
if (partialMatches.length > 0) return partialMatches.flatMap(key => iconMappings[key]);
|
||||
|
||||
for (const category in iconMappings) {
|
||||
const categoryMatches = Object.keys(iconMappings[category]).filter(key => {
|
||||
const normalizedKey = key.toLowerCase().replace(/\s+/g, '-').replace(/^-/, ''); // Strip leading hyphen
|
||||
return normalizedKey.includes(searchTerm) || searchTerm.includes(normalizedKey);
|
||||
});
|
||||
if (categoryMatches.length > 0) return categoryMatches.flatMap(key => iconMappings[category][key]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Suggestions with Levenshtein distance
|
||||
function findSuggestions(searchTerm) {
|
||||
if (!searchTerm || searchTerm.length < 2) return [];
|
||||
searchTerm = searchTerm.replace(/\s+/g, '-').replace(/^-/, ''); // Strip leading hyphen
|
||||
const semanticMatches = findSemanticMatches(searchTerm);
|
||||
if (semanticMatches) {
|
||||
return [...new Set(semanticMatches)].filter(icon =>
|
||||
allIcons.includes(`-${icon}`) || allIcons.includes(icon)
|
||||
).map(icon => icon.replace(/^-/, '')); // Clean icons in results
|
||||
}
|
||||
return allIcons
|
||||
.map(icon => {
|
||||
const iconName = icon.startsWith('-') ? icon.substring(1) : icon;
|
||||
return {
|
||||
name: iconName,
|
||||
distance: levenshteinDistance(searchTerm.toLowerCase(), iconName.toLowerCase())
|
||||
};
|
||||
})
|
||||
.filter(item => {
|
||||
const maxDistance = Math.min(Math.floor(searchTerm.length * 0.4), 3);
|
||||
return item.distance > 0 && item.distance <= maxDistance;
|
||||
})
|
||||
.sort((a, b) => a.distance - b.distance)
|
||||
.slice(0, 3)
|
||||
.map(item => item.name);
|
||||
}
|
||||
|
||||
function levenshteinDistance(a, b) {
|
||||
if (a.length === 0) return b.length;
|
||||
if (b.length === 0) return a.length;
|
||||
const matrix = Array(b.length + 1).fill(null).map(() => Array(a.length + 1).fill(null));
|
||||
for (let i = 0; i <= b.length; i++) matrix[i][0] = i;
|
||||
for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
|
||||
for (let i = 1; i <= b.length; i++) {
|
||||
for (let j = 1; j <= a.length; j++) {
|
||||
matrix[i][j] = b.charAt(i - 1) === a.charAt(j - 1) ?
|
||||
matrix[i - 1][j - 1] :
|
||||
Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
||||
}
|
||||
}
|
||||
return matrix[b.length][a.length];
|
||||
}
|
||||
|
||||
// Filter logic
|
||||
function initializeFilter() {
|
||||
const searchInput = document.getElementById('searchIcons');
|
||||
searchInput.addEventListener('input', function () {
|
||||
currentSearchTerm = this.value.trim().replace(/^-/, ''); // Strip leading hyphen
|
||||
filterIcons();
|
||||
});
|
||||
}
|
||||
|
||||
function filterIcons() {
|
||||
const searchTerms = currentSearchTerm.toLowerCase().split(/\s+/).filter(term => term.length > 0).map(term => term.replace(/^-/, '')); // Strip leading hyphens
|
||||
document.querySelectorAll('#iconList li').forEach(item => {
|
||||
const text = item.textContent.toLowerCase().replace(/^-/, ''); // Strip leading hyphen
|
||||
const matches = searchTerms.every(term => text.includes(term));
|
||||
item.classList.toggle('js-filter-hide', !matches);
|
||||
});
|
||||
|
||||
updateVisibleCount();
|
||||
|
||||
if (searchTimeout) clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => updateSearchHistory(currentSearchTerm), 1000);
|
||||
|
||||
const visibleIcons = document.querySelectorAll('#iconList li:not(.js-filter-hide)').length;
|
||||
const suggestionsContainer = document.getElementById('suggestions');
|
||||
if (currentSearchTerm.length >= 2 && visibleIcons < 10) {
|
||||
const suggestions = findSuggestions(currentSearchTerm);
|
||||
suggestionsContainer.innerHTML = suggestions.length > 0 ?
|
||||
`<span class="suggest-title">Did you mean?</span> ${suggestions.map(s =>
|
||||
`<span class="suggestion px-1" onclick="applySearch('${s}')">${s}</span>`).join(' ')}` :
|
||||
'';
|
||||
} else {
|
||||
suggestionsContainer.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Load icon set
|
||||
async function loadIconSet(iconSet = 'sa') {
|
||||
const iconSets = {
|
||||
'sa': { icons: 'json/sa-icons.json', mappings: 'json/sa-mappings.json', prefix: 'sa' },
|
||||
'base': { icons: 'json/sa-base.json', mappings: 'json/sa-mappings.json', prefix: 'sa' },
|
||||
'svg': { icons: 'json/sa-svg-icons.json', mappings: 'json/sa-svg-mappings.json', prefix: 'svg' },
|
||||
'fal': { icons: 'json/fa-icons.json', mappings: 'json/fa-mappings.json', prefix: 'fal' },
|
||||
'fas': { icons: 'json/fa-icons.json', mappings: 'json/fa-mappings.json', prefix: 'fas' },
|
||||
'far': { icons: 'json/fa-icons.json', mappings: 'json/fa-mappings.json', prefix: 'far' },
|
||||
'fad': { icons: 'json/fa-duotone.json', mappings: 'json/fa-mappings.json', prefix: 'fad' },
|
||||
'fab': { icons: 'json/fa-brands.json', mappings: 'json/fa-mappings.json', prefix: 'fab' },
|
||||
'material': { icons: 'json/material-icons.json', mappings: 'json/material-mappings.json', prefix: 'material' }
|
||||
};
|
||||
|
||||
const selectedSet = iconSets[iconSet] || iconSets['sa'];
|
||||
|
||||
// If first load or switching between different icon families, fetch new data
|
||||
if (!allIcons.length || (currentIconSet === 'sa' || currentIconSet === 'svg') !== (iconSet === 'sa' || iconSet === 'svg')) {
|
||||
try {
|
||||
const [iconsResponse, mappingsResponse] = await Promise.all([
|
||||
fetch(selectedSet.icons),
|
||||
fetch(selectedSet.mappings)
|
||||
]);
|
||||
if (!iconsResponse.ok || !mappingsResponse.ok) throw new Error('Failed to load resources');
|
||||
allIcons = (await iconsResponse.json()).map(icon => icon.replace(/^-/, '')); // Strip leading hyphen from all icons
|
||||
iconMappings = await mappingsResponse.json();
|
||||
currentIconSet = iconSet;
|
||||
generateIconList(allIcons, selectedSet.prefix);
|
||||
initializeFilter();
|
||||
} catch (error) {
|
||||
console.error('Error loading icon set:', error);
|
||||
}
|
||||
} else {
|
||||
// Update style for same icon family
|
||||
currentIconSet = iconSet;
|
||||
document.querySelectorAll('#iconList li').forEach(item => {
|
||||
const iconName = item.dataset.iconName.replace(/^-/, ''); // Strip leading hyphen
|
||||
const iconElement = item.querySelector('.icon-container');
|
||||
iconElement.innerHTML = getIconClass(iconSet, iconName);
|
||||
});
|
||||
}
|
||||
|
||||
// Re-apply filter if search term exists
|
||||
if (currentSearchTerm) filterIcons();
|
||||
updateVisibleCount();
|
||||
}
|
||||
|
||||
// Generate icon list
|
||||
function generateIconList(icons, iconPrefix) {
|
||||
const iconList = document.getElementById('iconList');
|
||||
iconList.innerHTML = icons.map(icon => {
|
||||
const cleanIconName = icon.replace(/^-/, ''); // Ensure no leading hyphen
|
||||
const iconClass = getIconClass(iconPrefix, cleanIconName);
|
||||
const displayName = cleanIconName;
|
||||
const isSvg = iconPrefix === 'svg';
|
||||
return `
|
||||
<li class="col-4 col-sm-3 col-md-3 col-lg-2 col-xl-2 col-xxl-1 d-flex justify-content-center align-items-center mb-g" data-icon-name="${displayName}">
|
||||
<a href="#" class="js-showcase-icon rounded color-fusion-300 p-0 m-0 d-flex flex-column w-100 shadow-hover-2 ${isSvg ? 'has-svg' : ''}">
|
||||
<div class="icon-preview rounded-top w-100 position-relative">
|
||||
<div class="icon-container rounded-top d-flex align-items-center justify-content-center w-100 pt-3 pb-3 pe-2 ps-2 position-absolute">
|
||||
${iconClass}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-bottom p-1 w-100 d-flex justify-content-center align-items-center text-center mt-auto">
|
||||
<span class="nav-link-text small text-muted text-truncate">${displayName}</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
}).join('');
|
||||
updateVisibleCount();
|
||||
addIconClickHandlers();
|
||||
}
|
||||
|
||||
// Search history
|
||||
function updateSearchHistory(term) {
|
||||
if (term && term.length >= 2) {
|
||||
searchHistory.add(term.replace(/^-/, '')); // Strip leading hyphen
|
||||
if (searchHistory.size > 5) searchHistory.delete([...searchHistory][0]);
|
||||
renderSearchHistory();
|
||||
}
|
||||
}
|
||||
|
||||
function renderSearchHistory() {
|
||||
const historyContainer = document.getElementById('searchHistory');
|
||||
historyContainer.innerHTML = [...searchHistory].map(term =>
|
||||
`<span class="badge bg-secondary me-1" onclick="applySearch('${term}')">
|
||||
<span class="text-truncate-xs overflow-hidden">${term}</span>
|
||||
<i class="sa sa-close ms-1" onclick="event.stopPropagation(); removeFromHistory('${term}')"></i>
|
||||
</span>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
function removeFromHistory(term) {
|
||||
searchHistory.delete(term);
|
||||
renderSearchHistory();
|
||||
}
|
||||
|
||||
function applySearch(term) {
|
||||
const searchIcons = document.getElementById('searchIcons');
|
||||
searchIcons.value = term.replace(/^-/, ''); // Strip leading hyphen
|
||||
currentSearchTerm = term.replace(/^-/, ''); // Strip leading hyphen
|
||||
filterIcons();
|
||||
}
|
||||
|
||||
// Icon utilities
|
||||
function updateVisibleCount() {
|
||||
const visibleIcons = document.querySelectorAll('#iconList li:not(.js-filter-hide)').length;
|
||||
document.querySelector('.results-count').textContent =
|
||||
`Showing ${visibleIcons} of ${allIcons.length} icons`;
|
||||
}
|
||||
|
||||
// Update getIconClass to handle SVG classes dynamically (add this at the end of the existing getIconClass function)
|
||||
const getIconClass = (prefix, icon) => {
|
||||
const cleanIcon = icon.replace(/^-/, ''); // Ensure no leading hyphen
|
||||
switch (prefix) {
|
||||
case 'svg':
|
||||
const weightClass = currentSvgWeight; // Use the current weight from dropdown
|
||||
const fillClass = isNoFill ? ' sa-nofill' : ''; // Add no-fill class if checkbox is checked
|
||||
return `<svg class="sa-icon ${weightClass}${fillClass}"><use href="icons/sprite.svg#${cleanIcon}"></use></svg>`;
|
||||
case 'fal':
|
||||
return `<i class="fal fa-${cleanIcon}"></i>`;
|
||||
case 'fas':
|
||||
return `<i class="fas fa-${cleanIcon}"></i>`;
|
||||
case 'far':
|
||||
return `<i class="far fa-${cleanIcon}"></i>`;
|
||||
case 'fad':
|
||||
return `<i class="fad fa-${cleanIcon}"></i>`;
|
||||
case 'fab':
|
||||
return `<i class="fab fa-${cleanIcon}"></i>`;
|
||||
case 'material':
|
||||
return `<i class="material-icons">${cleanIcon}</i>`;
|
||||
case 'sa':
|
||||
return `<i class="sa sa-${cleanIcon}"></i>`; // SmartAdmin format: sa sa-iconname
|
||||
case 'base':
|
||||
return `<i class="sa base-${cleanIcon}"></i>`;
|
||||
default:
|
||||
return `<i class="${prefix} ${prefix}-${cleanIcon}"></i>`;
|
||||
}
|
||||
};
|
||||
|
||||
function addIconClickHandlers() {
|
||||
document.querySelectorAll('.js-showcase-icon').forEach(iconElement => {
|
||||
iconElement.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
const iconEl = this.querySelector('svg') || this.querySelector('i');
|
||||
if (iconEl) {
|
||||
if (iconEl.tagName.toLowerCase() === 'svg') {
|
||||
const useEl = iconEl.querySelector('use');
|
||||
const iconClass = useEl ? useEl.getAttribute('href') : null;
|
||||
if (iconClass) {
|
||||
// Include current weight and fill classes in the copied SVG
|
||||
const weightClass = currentSvgWeight;
|
||||
const fillClass = isNoFill ? ' sa-nofill' : '';
|
||||
const svgMarkup = `<svg class="sa-icon ${weightClass}${fillClass}"><use href="${iconClass}"></use></svg>`;
|
||||
copyToClipboard(svgMarkup);
|
||||
showToast(svgMarkup);
|
||||
}
|
||||
} else {
|
||||
const iconClass = iconEl.className;
|
||||
copyToClipboard(iconClass);
|
||||
showToast(iconClass);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
let copyText = text;
|
||||
if (text.includes('sprite.svg#')) {
|
||||
// Keep the weight and fill classes when copying SVG markup
|
||||
const weightClass = currentSvgWeight;
|
||||
const fillClass = isNoFill ? ' sa-nofill' : '';
|
||||
copyText = `<svg class="sa-icon ${weightClass}${fillClass}"><use href="${text.split('href="')[1].split('"')[0]}"></use></svg>`;
|
||||
}
|
||||
navigator.clipboard.writeText(copyText).then(() =>
|
||||
console.log('Icon class/markup copied:', copyText)
|
||||
).catch(err => console.error('Failed to copy:', err));
|
||||
}
|
||||
|
||||
function escapeHTML(str) {
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function showToast(iconClass) {
|
||||
if (currentToast) {
|
||||
currentToast.hide();
|
||||
}
|
||||
|
||||
const toastElement = document.getElementById('liveToast');
|
||||
const toastBody = toastElement.querySelector('.toast-body');
|
||||
|
||||
const isSvg = iconClass.includes('sprite.svg#');
|
||||
// For SVGs, we'll only show the icon once in the message
|
||||
if (isSvg) {
|
||||
const iconName = iconClass.split('#')[1].split('"')[0]; // Get "iconname"
|
||||
|
||||
toastBody.innerHTML = `${iconClass}<span class="fw-bold ms-2"><span class="disabled">'#${iconName}'</span> ✔copied</span>`;
|
||||
} else {
|
||||
const iconDisplay = `<i class="${escapeHTML(iconClass)} display-1 p-1 m-0 me-2 text-primary"></i>`;
|
||||
toastBody.innerHTML = `${iconDisplay}<span class="fw-bold"><span class="disabled">'${escapeHTML(iconClass)}'</span> ✔copied</span>`;
|
||||
}
|
||||
|
||||
|
||||
currentToast = new bootstrap.Toast(toastElement, {
|
||||
delay: 2500,
|
||||
animation: true,
|
||||
autohide: true
|
||||
});
|
||||
|
||||
toastElement.addEventListener('hidden.bs.toast', () => {
|
||||
currentToast = null;
|
||||
}, { once: true });
|
||||
|
||||
currentToast.show();
|
||||
}
|
||||
|
||||
function updateSvgClasses() {
|
||||
document.querySelectorAll('#iconList li').forEach(item => {
|
||||
const svgElement = item.querySelector('svg');
|
||||
if (svgElement) {
|
||||
const weightClass = currentSvgWeight;
|
||||
const fillClass = isNoFill ? ' sa-nofill' : '';
|
||||
// Update classes using classList
|
||||
svgElement.setAttribute('class', `sa-icon ${weightClass}${fillClass}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
// document.addEventListener('DOMContentLoaded', function() {
|
||||
// loadIconSet('sa')
|
||||
// .then(() => console.log('Icon set loaded successfully'))
|
||||
// .catch(error => console.error('Failed to load icon set:', error));
|
||||
// });
|
||||
@@ -0,0 +1,68 @@
|
||||
|
||||
VANTA.HALO({
|
||||
el: "#net",
|
||||
mouseControls: false,
|
||||
touchControls: false,
|
||||
gyroControls: false,
|
||||
color: 0xfd3995,
|
||||
size: 1.6,
|
||||
scale: 0.75,
|
||||
xOffset: 0.22,
|
||||
scaleMobile: 0.50,
|
||||
});
|
||||
|
||||
|
||||
// Typewriter effect script (ES5)
|
||||
var textElement = document.getElementById('typewriter-text'); // Element to display text
|
||||
var messages = [
|
||||
"The world's first Admin WebApp built with Artificial Intelligence",
|
||||
"AI‑ready by design: pre‑written prompt instructions included",
|
||||
"An advanced, jQuery‑free Bootstrap 5 Admin Dashboard UI",
|
||||
"Built for the next generation of enterprise web applications",
|
||||
"Clean, scalable, and engineered for future‑forward growth",
|
||||
"Continuously updated by a team of expert developers",
|
||||
"Enterprise-grade performance with beautiful design",
|
||||
"One dashboard template, unlimited use cases",
|
||||
"Modern, responsive UI built for serious development"
|
||||
];
|
||||
|
||||
var currentMessageIndex = 0;
|
||||
var isDeleting = false;
|
||||
var typingSpeed = 15; // Speed of typing/deleting in milliseconds
|
||||
var pauseSpeed = 3000; // Pause between cycles in milliseconds
|
||||
function typeWrite() {
|
||||
var fullText = messages[currentMessageIndex];
|
||||
var currentText = textElement.textContent;
|
||||
var isEndOfMessage = !isDeleting && currentText === fullText;
|
||||
var isStartOfMessage = isDeleting && currentText === '';
|
||||
if (isEndOfMessage) {
|
||||
setTimeout(function () {
|
||||
isDeleting = true;
|
||||
typeWrite();
|
||||
}, pauseSpeed);
|
||||
return;
|
||||
}
|
||||
if (isStartOfMessage) {
|
||||
isDeleting = false;
|
||||
currentMessageIndex = (currentMessageIndex + 1) % messages.length;
|
||||
typeWrite();
|
||||
return;
|
||||
}
|
||||
if (isDeleting) {
|
||||
currentText = currentText.slice(0, -1);
|
||||
}
|
||||
else {
|
||||
currentText = fullText.slice(0, currentText.length + 1);
|
||||
}
|
||||
textElement.textContent = currentText;
|
||||
setTimeout(typeWrite, typingSpeed);
|
||||
}
|
||||
|
||||
// Start the typewriter effect
|
||||
if (textElement) {
|
||||
textElement.textContent = '';
|
||||
typeWrite();
|
||||
}
|
||||
else {
|
||||
console.error('Typewriter text element not found');
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Add styles for suggestions
|
||||
const style = document.createElement('style');
|
||||
style.textContent = ['.suggestions-container {', ' margin: -0.5rem 0 1rem 0;', ' font-size: 0.9rem;', ' color: #666;', ' padding: 0', '}', '.did-you-mean {', ' padding: 0.25rem 0;', ' font-weight: 500;', ' color: var(--danger-500);', '}', '.did-you-mean a {', ' color: #2196F3;', ' text-decoration: none;', ' font-weight: 500;', '}', '.did-you-mean a:hover {', ' text-decoration: underline;', '}'].join('\n');
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Helper function to safely initialize ListFilter
|
||||
function initListFilter(listId, inputId, options) {
|
||||
const list = document.querySelector(listId);
|
||||
const input = document.querySelector(inputId);
|
||||
if (!list || !input) {
|
||||
console.warn('ListFilter initialization failed: ' + (!list ? 'List element' : 'Input element') + ' not found');
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Add suggestions container if it doesn't exist
|
||||
const suggestionsContainer = document.createElement('div');
|
||||
suggestionsContainer.id = inputId + 'Suggestions';
|
||||
suggestionsContainer.className = 'suggestions-container';
|
||||
// Find the input-group div and insert the suggestions container after it
|
||||
const inputGroup = input.closest('.input-group');
|
||||
if (inputGroup && inputGroup.parentNode) {
|
||||
inputGroup.parentNode.insertBefore(suggestionsContainer, inputGroup.nextSibling);
|
||||
}
|
||||
// Add Levenshtein distance function
|
||||
function levenshteinDistance(a, b) {
|
||||
if (a.length === 0) return b.length;
|
||||
if (b.length === 0) return a.length;
|
||||
const matrix = Array(b.length + 1).fill(null).map(() => Array(a.length + 1).fill(null));
|
||||
for (let i = 0; i <= b.length; i++) matrix[i][0] = i;
|
||||
for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
|
||||
for (let i = 1; i <= b.length; i++) {
|
||||
for (let j = 1; j <= a.length; j++) {
|
||||
matrix[i][j] = b.charAt(i - 1) === a.charAt(j - 1) ? matrix[i - 1][j - 1] : Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
||||
}
|
||||
}
|
||||
return matrix[b.length][a.length];
|
||||
}
|
||||
// Add find suggestions function
|
||||
function findSuggestions(searchTerm, items) {
|
||||
if (!searchTerm || searchTerm.length < 2) return [];
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
return items.map(item => {
|
||||
const text = item.querySelector('.nav-link-text')?.textContent.toLowerCase() || '';
|
||||
return {
|
||||
text: text,
|
||||
distance: levenshteinDistance(searchTerm, text)
|
||||
};
|
||||
}).filter(item => {
|
||||
const maxDistance = Math.min(Math.floor(searchTerm.length * 0.4), 3);
|
||||
return item.distance > 0 && item.distance <= maxDistance;
|
||||
}).sort((a, b) => a.distance - b.distance).slice(0, 3).map(item => item.text);
|
||||
}
|
||||
// Modify the onFilter callback to include suggestions
|
||||
const originalOnFilter = options.onFilter;
|
||||
options.onFilter = function (filter) {
|
||||
if (originalOnFilter) originalOnFilter(filter);
|
||||
const suggestionsContainer = document.getElementById(inputId + 'Suggestions');
|
||||
if (!suggestionsContainer) {
|
||||
console.error('Suggestions container not found');
|
||||
return;
|
||||
}
|
||||
const visibleItems = list.querySelectorAll('li:not(.js-filter-hide)');
|
||||
const suggestions = findSuggestions(filter, Array.from(list.querySelectorAll('li')));
|
||||
if (filter.length >= 2 && visibleItems.length < 10 && suggestions.length > 0) {
|
||||
// Get the first suggestion and capitalize it
|
||||
const suggestion = suggestions[0];
|
||||
const capitalizedSuggestion = suggestion.charAt(0).toUpperCase() + suggestion.slice(1);
|
||||
suggestionsContainer.innerHTML = ['<div class="did-you-mean">', ' Did you mean: <a href="#" class="suggestion-link">' + capitalizedSuggestion + '</a>', '</div>'].join('\n');
|
||||
// Add click event listener to the suggestion link
|
||||
const suggestionLink = suggestionsContainer.querySelector('.suggestion-link');
|
||||
if (suggestionLink) {
|
||||
suggestionLink.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
input.value = suggestion;
|
||||
input.dispatchEvent(new Event('input',
|
||||
{
|
||||
bubbles: true
|
||||
}));
|
||||
suggestionsContainer.innerHTML = '';
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
suggestionsContainer.innerHTML = '';
|
||||
}
|
||||
};
|
||||
return new ListFilter(listId, inputId, options);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('ListFilter initialization error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// File Explorer Example with Nested Structure
|
||||
initListFilter('#fileExplorer', '#fileFilterInput',
|
||||
{
|
||||
messageSelector: '#fileFilterMessage',
|
||||
debounceWait: 150,
|
||||
minLength: 1,
|
||||
onFilter: function (filter) {
|
||||
console.log('File filter:', filter);
|
||||
}
|
||||
});
|
||||
// Did you mean suggestions example
|
||||
initListFilter('#suggestionsNav', '#suggestionsFilterInput',
|
||||
{
|
||||
messageSelector: '#suggestionsFilterMessage',
|
||||
debounceWait: 200,
|
||||
minLength: 1,
|
||||
onFilter: function (filter) {
|
||||
console.log('Suggestions filter:', filter);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,866 @@
|
||||
|
||||
// All imports for the Marketing Dashboard
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
import { SmartTables } from '../optional/smartTables/smartTables.bundle.js';
|
||||
import { PeityAPI } from './../thirdparty/peity.es6.js';
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
'use strict';
|
||||
|
||||
/***************************************************************/
|
||||
/* Marketing Performance Chart */
|
||||
/***************************************************************/
|
||||
|
||||
const marketingPerformanceChart = new ApexCharts(document.querySelector('#marketing-profits-chart'), {
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'line',
|
||||
|
||||
stacked: false,
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 800
|
||||
},
|
||||
parentHeightOffset: 0,
|
||||
fontFamily: 'inherit'
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.primary[300].hex,
|
||||
window.colorMap.warning[300].hex,
|
||||
window.colorMap.success[300].hex
|
||||
],
|
||||
stroke: {
|
||||
width: [0, 3, 3],
|
||||
curve: 'smooth'
|
||||
},
|
||||
fill: {
|
||||
type: ['gradient', 'solid', 'solid'],
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
type: 'vertical',
|
||||
shadeIntensity: 0.5,
|
||||
gradientToColors: [window.colorMap.primary[200].hex],
|
||||
inverseColors: false,
|
||||
opacityFrom: 1,
|
||||
opacityTo: 0.3
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
padding: {
|
||||
left: -5,
|
||||
right: 0,
|
||||
top: -20,
|
||||
bottom: -5
|
||||
},
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: '50%',
|
||||
endingShape: 'rounded',
|
||||
borderRadius: 5,
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
markers: {
|
||||
size: 4,
|
||||
strokeWidth: 0,
|
||||
hover: {
|
||||
size: 6
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
categories: ['Jan 2013', 'Apr 2013', 'Jul 2013', 'Oct 2013', 'Jan 2014', 'Apr 2014', 'Jul 2014', 'Oct 2014', 'Jan 2015', 'Apr 2015', 'Jul 2015', 'Oct 2015', 'Jan 2016', 'Apr 2016'],
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.rgba(0.7),
|
||||
fontSize: '10px'
|
||||
}
|
||||
},
|
||||
axisBorder: {
|
||||
show: false
|
||||
},
|
||||
axisTicks: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: 1200,
|
||||
tickAmount: 6,
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.rgba(0.7),
|
||||
fontSize: '10px'
|
||||
},
|
||||
formatter: function (val) {
|
||||
return val.toFixed(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false,
|
||||
y: {
|
||||
formatter: function (y) {
|
||||
if (typeof y !== "undefined") {
|
||||
return y.toFixed(0);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Target Profit',
|
||||
type: 'column',
|
||||
data: [150, 650, 200, 650, 800, 1050, 350, 750, 500, 250, 650, 250, 350, 350]
|
||||
},
|
||||
{
|
||||
name: 'Actual Profit',
|
||||
type: 'line',
|
||||
data: [50, 70, 90, 80, 300, 950, 800, 700, 650, 30, 100, 80, 30, 30]
|
||||
},
|
||||
{
|
||||
name: 'User Signups',
|
||||
type: 'line',
|
||||
data: [650, 430, 800, 350, 450, 450, 450, 470, 250, 830, 650, 250, 350, 350]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
marketingPerformanceChart.render();
|
||||
|
||||
/***************************************************************/
|
||||
/* Returning Target Chart */
|
||||
/***************************************************************/
|
||||
|
||||
const returningTargetChart = new ApexCharts(document.querySelector('#returning-target-chart'), {
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'area',
|
||||
stacked: false,
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 800
|
||||
},
|
||||
parentHeightOffset: 0,
|
||||
fontFamily: 'inherit'
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.success[200].hex, // New Customer (darker blue)
|
||||
window.colorMap.primary[200].hex, // Returning Customer (lighter teal)
|
||||
],
|
||||
stroke: {
|
||||
width: 2,
|
||||
curve: 'straight'
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
type: 'vertical',
|
||||
shadeIntensity: 0.4,
|
||||
opacityFrom: 0.8,
|
||||
opacityTo: 0.1,
|
||||
stops: [0, 90, 100]
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.07),
|
||||
strokeDashArray: 0,
|
||||
position: 'back',
|
||||
padding: {
|
||||
left: 5
|
||||
},
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
markers: {
|
||||
size: 4,
|
||||
strokeWidth: 0,
|
||||
hover: {
|
||||
size: 6
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'numeric',
|
||||
categories: [0, 100, 200, 300, 400, 500, 600, 700],
|
||||
tickAmount: 8,
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.rgba(0.7),
|
||||
fontSize: '10px'
|
||||
}
|
||||
},
|
||||
axisBorder: {
|
||||
show: false
|
||||
},
|
||||
axisTicks: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
show: false,
|
||||
max: 180,
|
||||
tickAmount: 10,
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false,
|
||||
y: {
|
||||
formatter: function (y) {
|
||||
if (typeof y !== "undefined") {
|
||||
return y.toFixed(0);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
horizontalAlign: 'left',
|
||||
fontSize: '12px',
|
||||
fontFamily: 'inherit',
|
||||
offsetY: 10,
|
||||
offsetX: -35,
|
||||
itemMargin: {
|
||||
horizontal: 10,
|
||||
vertical: 0
|
||||
},
|
||||
markers: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
radius: 0,
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Returning Customer',
|
||||
data: [140, 120, 95, 80, 60, 95, 70, 50]
|
||||
},
|
||||
{
|
||||
name: 'New Customer',
|
||||
data: [110, 90, 70, 55, 50, 75, 50, 30]
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
returningTargetChart.render();
|
||||
|
||||
/***************************************************************/
|
||||
/* Efficiency Metrics Chart */
|
||||
/***************************************************************/
|
||||
|
||||
const efficiencyMetricsChart = new ApexCharts(document.querySelector('#efficiency-metrics-chart'), {
|
||||
chart: {
|
||||
height: 259,
|
||||
type: 'area',
|
||||
stacked: false,
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 800
|
||||
},
|
||||
parentHeightOffset: 0,
|
||||
fontFamily: 'inherit'
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.primary[300].hex, // Sessions (blue)
|
||||
window.colorMap.success[300].hex, // New Sessions (teal)
|
||||
window.colorMap.warning[300].hex, // Bounce Rate (yellow)
|
||||
window.colorMap.info[300].hex // Clickthrough (cyan)
|
||||
],
|
||||
stroke: {
|
||||
width: 2,
|
||||
curve: 'straight'
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'light',
|
||||
type: 'vertical',
|
||||
shadeIntensity: 0.4,
|
||||
opacityFrom: 0.8,
|
||||
opacityTo: 0.1,
|
||||
stops: [0, 90, 100]
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.08),
|
||||
strokeDashArray: 5,
|
||||
position: 'back',
|
||||
padding: {
|
||||
top: -25,
|
||||
},
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
markers: {
|
||||
size: 3,
|
||||
strokeWidth: 0,
|
||||
hover: {
|
||||
size: 5
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
categories: ['2am', '3am', '4am', '5am', '6am', '7am', '8am', '9am', '1pm', '2pm', '3pm', '4pm'],
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.rgba(0.7),
|
||||
fontSize: '10px'
|
||||
}
|
||||
},
|
||||
axisBorder: {
|
||||
show: false
|
||||
},
|
||||
axisTicks: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: 300000,
|
||||
tickAmount: 3,
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.rgba(0.7),
|
||||
fontSize: '10px'
|
||||
},
|
||||
formatter: function (val) {
|
||||
if (val >= 1000) {
|
||||
return (val / 1000) + 'K';
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false,
|
||||
y: {
|
||||
formatter: function (y) {
|
||||
if (typeof y !== "undefined") {
|
||||
if (y >= 1000) {
|
||||
return Math.round(y).toLocaleString();
|
||||
}
|
||||
return y.toFixed(0);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Sessions',
|
||||
data: [20000, 25000, 40000, 50000, 60000, 150000, 190000, 180000, 200000, 100000, 100000, 90000]
|
||||
},
|
||||
{
|
||||
name: 'New Sessions',
|
||||
data: [15000, 30000, 35000, 60000, 80000, 110000, 230000, 200000, 180000, 230000, 160000, 100000]
|
||||
},
|
||||
{
|
||||
name: 'Bounce Rate',
|
||||
data: [10000, 15000, 20000, 25000, 30000, 40000, 60000, 80000, 100000, 120000, 140000, 160000]
|
||||
},
|
||||
{
|
||||
name: 'Clickthrough',
|
||||
data: [5000, 10000, 15000, 20000, 25000, 30000, 40000, 50000, 60000, 70000, 80000, 90000]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
efficiencyMetricsChart.render();
|
||||
|
||||
/***************************************************************/
|
||||
/* Peity Charts */
|
||||
/***************************************************************/
|
||||
|
||||
// Global default settings for charts
|
||||
const defaults = {
|
||||
// Default background color for donut and pie charts (replaces the gray)
|
||||
donutBackground: 'var(--bs-border-color)', // Light purple background instead of gray
|
||||
pieBackground: 'var(--bs-border-color)' // Same for pie charts
|
||||
};
|
||||
|
||||
// Helper to create peity charts (similar to jQuery plugin style)
|
||||
function createPeity(selector, type, customOptions = {}) {
|
||||
document.querySelectorAll(selector).forEach(element => {
|
||||
// Parse data-peity attribute for options
|
||||
let options = { ...customOptions };
|
||||
const dataAttr = element.getAttribute('data-peity');
|
||||
|
||||
if (dataAttr) {
|
||||
try {
|
||||
options = { ...options, ...JSON.parse(dataAttr) };
|
||||
} catch (e) {
|
||||
console.warn('Invalid data-peity format for', selector);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply defaults for donut and pie charts background colors
|
||||
if (type === 'donut' || type === 'pie') {
|
||||
// If fill is not defined or is an empty array, set default colors
|
||||
if (!options.fill || options.fill.length === 0) {
|
||||
options.fill = type === 'donut' ?
|
||||
['var(--primary-500)', defaults.donutBackground] :
|
||||
['var(--primary-500)', defaults.pieBackground];
|
||||
}
|
||||
// If fill is defined but doesn't include a background color (for fractions like "1/4")
|
||||
else if (Array.isArray(options.fill) && options.fill.length === 1) {
|
||||
options.fill.push(type === 'donut' ? defaults.donutBackground : defaults.pieBackground);
|
||||
}
|
||||
// If data contains multiple values (e.g., "10,4,4,6"), don't add background
|
||||
}
|
||||
|
||||
PeityAPI.create(element, type, options);
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Simple chart initializations with default settings
|
||||
createPeity('.peity-pie', 'pie');
|
||||
createPeity('.peity-donut', 'donut');
|
||||
createPeity('.peity-line', 'line');
|
||||
createPeity('.peity-bar', 'bar');
|
||||
|
||||
// Updating chart with animation
|
||||
const updatingChart = document.querySelector('.updating-chart');
|
||||
if (updatingChart) {
|
||||
const values = updatingChart.textContent.split(',').map(Number);
|
||||
const chart = PeityAPI.create(updatingChart, 'line', {
|
||||
width: 200,
|
||||
height: 40,
|
||||
stroke: 'var(--info-500)',
|
||||
fill: 'var(--info-200)',
|
||||
min: 0,
|
||||
max: 10
|
||||
});
|
||||
|
||||
setInterval(function () {
|
||||
values.shift();
|
||||
values.push(Math.floor(Math.random() * 10));
|
||||
updatingChart.textContent = values.join(',');
|
||||
chart.draw();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Bar charts with negative values (red/green coloring)
|
||||
document.querySelectorAll('.bar-negative').forEach(element => {
|
||||
const values = element.textContent.split(',').map(Number);
|
||||
PeityAPI.create(element, 'bar', {
|
||||
height: 40,
|
||||
width: 110,
|
||||
fill: values.map(value => value > 0 ? 'var(--success-500)' : 'var(--danger-500)')
|
||||
});
|
||||
});
|
||||
|
||||
// Bar charts with color transitions
|
||||
document.querySelectorAll('.bar-transition').forEach(element => {
|
||||
const values = element.textContent.split(',').map(Number);
|
||||
PeityAPI.create(element, 'bar', {
|
||||
height: 40,
|
||||
width: 110,
|
||||
fill: values.map((_, i, all) => {
|
||||
const g = parseInt((i / all.length) * 255);
|
||||
return `rgb(255, ${g}, ${g})`;
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Process all remaining elements with data-peity attribute
|
||||
document.querySelectorAll('[data-peity]').forEach(element => {
|
||||
// Skip elements already handled by specific selectors
|
||||
if (element.classList.contains('peity-pie') ||
|
||||
element.classList.contains('peity-donut') ||
|
||||
element.classList.contains('peity-line') ||
|
||||
element.classList.contains('peity-bar') ||
|
||||
element.classList.contains('updating-chart') ||
|
||||
element.classList.contains('bar-negative') ||
|
||||
element.classList.contains('bar-transition')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-detect chart type based on content
|
||||
const content = element.textContent.trim();
|
||||
let type;
|
||||
|
||||
if (content.includes('/')) {
|
||||
type = 'donut'; // Fraction data is best for donut
|
||||
} else if (content.includes(',')) {
|
||||
// If it has commas, it's likely a series
|
||||
const hasNegative = content.split(',').some(val => parseFloat(val) < 0);
|
||||
type = hasNegative ? 'bar' : 'line'; // Bars handle negative values better visually
|
||||
} else {
|
||||
type = 'pie'; // Default fallback
|
||||
}
|
||||
|
||||
// Get options from data attribute
|
||||
let options = {};
|
||||
const dataAttr = element.getAttribute('data-peity');
|
||||
if (dataAttr) {
|
||||
try {
|
||||
options = JSON.parse(dataAttr);
|
||||
} catch (e) {
|
||||
console.warn('Invalid data-peity format for element', element);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply global background color for donut/pie charts
|
||||
if ((type === 'donut' || type === 'pie') && content.includes('/')) {
|
||||
// For fraction notation (e.g. "1/4"), make sure we have a background color
|
||||
if (!options.fill || !Array.isArray(options.fill) || options.fill.length < 2) {
|
||||
options.fill = options.fill || [];
|
||||
// If we have one color already, keep it and add the background
|
||||
if (options.fill.length === 1) {
|
||||
options.fill.push(type === 'donut' ? defaults.donutBackground : defaults.pieBackground);
|
||||
} else {
|
||||
// No colors defined yet, set defaults
|
||||
options.fill = [
|
||||
type === 'donut' ? 'var(--primary-500)' : 'var(--success-500)',
|
||||
type === 'donut' ? defaults.donutBackground : defaults.pieBackground
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create chart
|
||||
PeityAPI.create(element, type, options);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error initializing Peity charts:', error);
|
||||
}
|
||||
|
||||
/***************************************************************/
|
||||
/* Sales Performance Table */
|
||||
/***************************************************************/
|
||||
|
||||
// Initialize SmartTables with the table ID and options
|
||||
const table = new SmartTables('sales-performance-table', {
|
||||
data: {
|
||||
type: "json",
|
||||
source: "json/MOCK_DATA_SALES_PERF.json",
|
||||
columns: [
|
||||
{ data: "CustomerID", title: "ID" },
|
||||
{
|
||||
data: "Name",
|
||||
title: "Full Name",
|
||||
render: function (data) {
|
||||
// Assuming data is in format "LastName, FirstName"
|
||||
if (!data) return '';
|
||||
|
||||
const nameParts = data.split(',');
|
||||
if (nameParts.length === 2) {
|
||||
const lastName = nameParts[0].trim();
|
||||
const firstName = nameParts[1].trim();
|
||||
return firstName + ' ' + lastName;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{
|
||||
data: "PurchaseDate",
|
||||
title: "Purchase Date",
|
||||
render: function (data) {
|
||||
if (!data) return '';
|
||||
|
||||
try {
|
||||
const date = new Date(data);
|
||||
if (isNaN(date.getTime())) return data;
|
||||
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const year = date.getFullYear();
|
||||
|
||||
return `${day}-${month}-${year}`;
|
||||
} catch (e) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
data: "CustomerCVV",
|
||||
title: "CVV",
|
||||
render: function (data) {
|
||||
if (!data) return '';
|
||||
|
||||
const cvvId = 'cvv-' + Math.random().toString(36).substring(2, 10);
|
||||
return `
|
||||
<div class="d-flex align-items-center">
|
||||
<span id="${cvvId}-masked">***</span>
|
||||
<span id="${cvvId}-actual" class="d-none">${data}</span>
|
||||
<button class="btn btn-link btn-sm p-0 ms-2 cvv-toggle" data-cvv-id="${cvvId}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
|
||||
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
|
||||
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
{
|
||||
data: "Country",
|
||||
title: "Country",
|
||||
render: function (data) {
|
||||
if (!data) return '';
|
||||
|
||||
// Get country code (assuming data has the country name)
|
||||
let countryCode = '';
|
||||
|
||||
// Simple mapping for common countries
|
||||
const countryMap = {
|
||||
'United States': 'us',
|
||||
'USA': 'us',
|
||||
'US': 'us',
|
||||
'United Kingdom': 'gb',
|
||||
'UK': 'gb',
|
||||
'Canada': 'ca',
|
||||
'Australia': 'au',
|
||||
'Germany': 'de',
|
||||
'France': 'fr',
|
||||
'Italy': 'it',
|
||||
'Spain': 'es',
|
||||
'Japan': 'jp',
|
||||
'China': 'cn',
|
||||
'India': 'in',
|
||||
'Brazil': 'br',
|
||||
'Mexico': 'mx',
|
||||
'Russia': 'ru'
|
||||
};
|
||||
|
||||
// Try to get code from map
|
||||
countryCode = countryMap[data] || data.toLowerCase().substring(0, 2);
|
||||
|
||||
// Using a div placeholder instead of img to completely prevent loading
|
||||
return `
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flag-placeholder d-inline-block me-1 border-faded"
|
||||
data-country="${countryCode}"
|
||||
style="width: 20px; height: 15px; background-color: #f5f5f5;">
|
||||
</div>
|
||||
<span>${data}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
{ data: "InvoiceAmount", title: "Invoice Amount" },
|
||||
{ data: "CustomerEmail", title: "Email" }
|
||||
]
|
||||
},
|
||||
export: true,
|
||||
print: true,
|
||||
responsive: true,
|
||||
hooks: {
|
||||
afterInit: function () {
|
||||
// Add event listener for CVV toggles
|
||||
document.addEventListener('click', function (e) {
|
||||
if (e.target.closest('.cvv-toggle')) {
|
||||
const button = e.target.closest('.cvv-toggle');
|
||||
const cvvId = button.getAttribute('data-cvv-id');
|
||||
const maskedElement = document.getElementById(cvvId + '-masked');
|
||||
const actualElement = document.getElementById(cvvId + '-actual');
|
||||
|
||||
if (maskedElement && actualElement) {
|
||||
// Toggle visibility
|
||||
if (maskedElement.classList.contains('d-none')) {
|
||||
maskedElement.classList.remove('d-none');
|
||||
actualElement.classList.add('d-none');
|
||||
// Change icon to eye
|
||||
button.innerHTML = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
|
||||
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
|
||||
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
|
||||
</svg>
|
||||
`;
|
||||
} else {
|
||||
maskedElement.classList.add('d-none');
|
||||
actualElement.classList.remove('d-none');
|
||||
// Change icon to eye-slash
|
||||
button.innerHTML = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye-slash" viewBox="0 0 16 16">
|
||||
<path d="M13.359 11.238C15.06 9.72 16 8 16 8s-3-5.5-8-5.5a7.028 7.028 0 0 0-2.79.588l.77.771A5.944 5.944 0 0 1 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.134 13.134 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755-.165.165-.337.328-.517.486l.708.709z"/>
|
||||
<path d="M11.297 9.176a3.5 3.5 0 0 0-4.474-4.474l.823.823a2.5 2.5 0 0 1 2.829 2.829l.822.822zm-2.943 1.299.822.822a3.5 3.5 0 0 1-4.474-4.474l.823.823a2.5 2.5 0 0 0 2.829 2.829z"/>
|
||||
<path d="M3.35 5.47c-.18.16-.353.322-.518.487A13.134 13.134 0 0 0 1.172 8l.195.288c.335.48.83 1.12 1.465 1.755C4.121 11.332 5.881 12.5 8 12.5c.716 0 1.39-.133 2.02-.36l.77.772A7.029 7.029 0 0 1 8 13.5C3 13.5 0 8 0 8s.939-1.721 2.641-3.238l.708.709zm10.296 8.884-12-12 .708-.708 12 12-.708.708z"/>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the table to be fully rendered before processing flags
|
||||
// Use a longer timeout to ensure the table is completely initialized
|
||||
setTimeout(function () {
|
||||
// Only load flags for the first page after complete initialization
|
||||
loadFlagsForVisibleRows();
|
||||
}, 1000);
|
||||
|
||||
// Set up event listeners for search input
|
||||
const searchInputs = document.querySelectorAll('.st-search');
|
||||
searchInputs.forEach(function (input) {
|
||||
input.addEventListener('input', function () {
|
||||
// Debounce the search - wait until typing stops
|
||||
clearTimeout(input.searchTimer);
|
||||
input.searchTimer = setTimeout(function () {
|
||||
// After search completes, load flags
|
||||
setTimeout(loadFlagsForVisibleRows, 100);
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
|
||||
// Set up event listeners for sorting
|
||||
const tableHeaders = document.querySelectorAll('#sales-performance-table th');
|
||||
tableHeaders.forEach(function (th) {
|
||||
th.addEventListener('click', function () {
|
||||
// After sorting completes, load flags
|
||||
setTimeout(loadFlagsForVisibleRows, 100);
|
||||
});
|
||||
});
|
||||
},
|
||||
onPaginate: function () {
|
||||
// This is called when user navigates to a new page
|
||||
// Use a shorter timeout as pagination is usually faster
|
||||
setTimeout(function () {
|
||||
loadFlagsForVisibleRows();
|
||||
}, 50);
|
||||
},
|
||||
onFilter: function () {
|
||||
// This is called when filtering is applied
|
||||
setTimeout(function () {
|
||||
loadFlagsForVisibleRows();
|
||||
}, 100);
|
||||
},
|
||||
afterDraw: function () {
|
||||
// This catches any other table redraw events
|
||||
setTimeout(function () {
|
||||
loadFlagsForVisibleRows();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Global flag to track if initial flags have been loaded
|
||||
// This prevents multiple loads during initialization
|
||||
let initialFlagsLoaded = false;
|
||||
|
||||
// Track if we're currently loading flags to prevent multiple simultaneous loads
|
||||
let isLoadingFlags = false;
|
||||
|
||||
// Function to load flag images only for visible rows
|
||||
function loadFlagsForVisibleRows() {
|
||||
// Prevent multiple simultaneous calls
|
||||
if (isLoadingFlags) return;
|
||||
isLoadingFlags = true;
|
||||
|
||||
try {
|
||||
// Get all flag placeholders
|
||||
const flagPlaceholders = document.querySelectorAll('.flag-placeholder');
|
||||
let loadedCount = 0;
|
||||
|
||||
// Process each placeholder
|
||||
flagPlaceholders.forEach(function (placeholder) {
|
||||
// Check if the placeholder is in a visible row
|
||||
const row = placeholder.closest('tr');
|
||||
if (row && row.offsetParent !== null) {
|
||||
// Get country code
|
||||
const countryCode = placeholder.getAttribute('data-country');
|
||||
if (!countryCode) return;
|
||||
|
||||
// Create and set up the image element
|
||||
const img = document.createElement('img');
|
||||
img.src = `https://lipis.github.io/flag-icons/flags/4x3/${countryCode}.svg`;
|
||||
img.alt = countryCode;
|
||||
img.className = 'd-inline-block me-1 border-faded';
|
||||
img.style.width = '20px';
|
||||
img.style.height = 'auto';
|
||||
|
||||
// Replace the placeholder with the image
|
||||
placeholder.parentNode.replaceChild(img, placeholder);
|
||||
loadedCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (loadedCount > 0) {
|
||||
console.log(`Loaded ${loadedCount} flags for current view`);
|
||||
}
|
||||
initialFlagsLoaded = true;
|
||||
} finally {
|
||||
// Reset loading flag
|
||||
isLoadingFlags = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Make function accessible globally for manual triggering if needed
|
||||
window.loadTableFlags = loadFlagsForVisibleRows;
|
||||
});
|
||||
@@ -0,0 +1,497 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// DOM Elements
|
||||
const chatContainer = document.getElementById('chat_container');
|
||||
const messageInput = document.getElementById('msgr_input');
|
||||
const sendButton = document.getElementById('send_button');
|
||||
const emojiButtons = document.querySelectorAll('.emoji');
|
||||
const storyCircles = document.querySelectorAll('.story-circle');
|
||||
|
||||
// Mock data
|
||||
const mockResponses = [
|
||||
"That's interesting. Tell me more.",
|
||||
"I completely understand what you mean.",
|
||||
"I hadn't thought about it that way before.",
|
||||
"That's great news!",
|
||||
"LOL 😂 That's hilarious!",
|
||||
"Really? I'm surprised to hear that.",
|
||||
"I'm not sure I agree, but I see your point.",
|
||||
"Let's discuss this further when we meet.",
|
||||
"Thanks for letting me know!",
|
||||
"Sorry to hear that. Is there anything I can do to help?",
|
||||
"Can we talk about this tomorrow? I need some time to think.",
|
||||
"Wow! That's amazing! 👍",
|
||||
"So how are you liking SmartAdmin?"
|
||||
];
|
||||
|
||||
const mockImages = [
|
||||
'./img/demo/gallery/1.jpg',
|
||||
'./img/demo/gallery/2.jpg',
|
||||
'./img/demo/gallery/3.jpg',
|
||||
'./img/demo/gallery/4.jpg',
|
||||
'./img/demo/gallery/5.jpg',
|
||||
];
|
||||
|
||||
//const mockEmojis = ['👍', '❤️', '😂', '👏', '😍', '🎉', '👌', '✨'];
|
||||
|
||||
// const mockFiles = [
|
||||
// { name: 'Project_Brief.pdf', type: 'pdf', size: '1.2 MB' },
|
||||
// { name: 'Meeting_Notes.docx', type: 'doc', size: '425 KB' },
|
||||
// { name: 'Presentation.pptx', type: 'ppt', size: '3.8 MB' },
|
||||
// { name: 'Budget.xlsx', type: 'xls', size: '890 KB' }
|
||||
// ];
|
||||
|
||||
// Initialize Story Circles (top horizontal scrolling avatars)
|
||||
if (storyCircles.length) {
|
||||
storyCircles.forEach(circle => {
|
||||
circle.addEventListener('click', () => {
|
||||
// Deactivate all stories
|
||||
storyCircles.forEach(c => c.classList.remove('active'));
|
||||
// Activate clicked story
|
||||
circle.classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle emoji selection
|
||||
if (emojiButtons.length) {
|
||||
emojiButtons.forEach(emoji => {
|
||||
emoji.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
// Get emoji value from the data attribute or class
|
||||
const emojiType = emoji.classList.contains('emoji--like') ? '👍' :
|
||||
emoji.classList.contains('emoji--love') ? '❤️' :
|
||||
emoji.classList.contains('emoji--haha') ? '😂' :
|
||||
emoji.classList.contains('emoji--yay') ? '🎉' :
|
||||
emoji.classList.contains('emoji--wow') ? '😮' :
|
||||
emoji.classList.contains('emoji--sad') ? '😢' :
|
||||
emoji.classList.contains('emoji--angry') ? '😡' : '';
|
||||
|
||||
if (emojiType) {
|
||||
// Send the emoji directly as a message
|
||||
sendMessage(emojiType);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Send message function
|
||||
function sendMessage(messageContent = null) {
|
||||
// Get message from input or passed content
|
||||
const message = messageContent || messageInput.value.trim();
|
||||
|
||||
if (!message) return;
|
||||
|
||||
// Clear input if using the input field
|
||||
if (!messageContent) messageInput.value = '';
|
||||
|
||||
// Create message HTML
|
||||
const currentTime = new Date();
|
||||
const timeString = currentTime.getHours().toString().padStart(2, '0') + ':' +
|
||||
currentTime.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
// Add message to chat
|
||||
appendMessage(message, 'sent', timeString);
|
||||
|
||||
// Scroll to bottom
|
||||
scrollToBottom();
|
||||
|
||||
// Show typing indicator
|
||||
showTypingIndicator();
|
||||
|
||||
// After a delay, show response
|
||||
const responseDelay = 1000 + Math.random() * 2000; // 1-3 seconds
|
||||
setTimeout(() => {
|
||||
hideTypingIndicator();
|
||||
generateResponse();
|
||||
}, responseDelay);
|
||||
}
|
||||
|
||||
// Generate a random response
|
||||
function generateResponse() {
|
||||
// Decide what type of response to generate
|
||||
const responseType = Math.random();
|
||||
|
||||
if (responseType < 0.6) { // 60% chance of text
|
||||
const randomResponse = mockResponses[Math.floor(Math.random() * mockResponses.length)];
|
||||
appendMessage(randomResponse, 'get', getCurrentTime());
|
||||
} else if (responseType < 0.7) { // 10% chance of image
|
||||
const randomImage = mockImages[Math.floor(Math.random() * mockImages.length)];
|
||||
appendImageMessage(randomImage, 'get', getCurrentTime());
|
||||
} else if (responseType < 0.9) { // 20% chance of emoji only
|
||||
const animatedEmojis = ['👍', '❤️', '😂', '🎉', '😮', '😢', '😡'];
|
||||
const randomEmoji = animatedEmojis[Math.floor(Math.random() * animatedEmojis.length)];
|
||||
appendMessage(randomEmoji, 'get', getCurrentTime());
|
||||
}
|
||||
// else { // 10% chance of file
|
||||
// const randomFile = mockFiles[Math.floor(Math.random() * mockFiles.length)];
|
||||
// appendFileMessage(randomFile, 'get', getCurrentTime());
|
||||
// }
|
||||
|
||||
// Scroll to bottom
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
// Show typing indicator
|
||||
function showTypingIndicator() {
|
||||
const typingDiv = document.createElement('div');
|
||||
typingDiv.className = 'chat-segment chat-segment-get typing-indicator';
|
||||
typingDiv.innerHTML = `
|
||||
<div class="chat-message">
|
||||
<div class="typing">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
chatContainer.appendChild(typingDiv);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
// Hide typing indicator
|
||||
function hideTypingIndicator() {
|
||||
const typingIndicator = document.querySelector('.typing-indicator');
|
||||
if (typingIndicator) {
|
||||
typingIndicator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Append message to chat
|
||||
function appendMessage(message, type, time) {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `chat-segment chat-segment-${type}`;
|
||||
|
||||
// Check if message is just an emoji
|
||||
const isJustEmoji = /^(\p{Emoji}\uFE0F?)+$/u.test(message);
|
||||
|
||||
// Check if message is one of our specific emojis
|
||||
const emojiType = message === '👍' ? 'like' :
|
||||
message === '❤️' ? 'love' :
|
||||
message === '😂' ? 'haha' :
|
||||
message === '🎉' ? 'yay' :
|
||||
message === '😮' ? 'wow' :
|
||||
message === '😢' ? 'sad' :
|
||||
message === '😡' ? 'angry' : null;
|
||||
|
||||
let messageHtml;
|
||||
if (emojiType) {
|
||||
// Create the animated emoji without chat-message wrapper
|
||||
messageHtml = `
|
||||
<div class="emoji emoji--${emojiType}">
|
||||
${emojiType === 'like' ?
|
||||
`<div class="emoji__hand">
|
||||
<div class="emoji__thumb"></div>
|
||||
</div>` :
|
||||
emojiType === 'love' ?
|
||||
`<div class="emoji__heart"></div>` :
|
||||
emojiType === 'haha' ?
|
||||
`<div class="emoji__face">
|
||||
<div class="emoji__eyes"></div>
|
||||
<div class="emoji__mouth">
|
||||
<div class="emoji__tongue"></div>
|
||||
</div>
|
||||
</div>` :
|
||||
emojiType === 'yay' ?
|
||||
`<div class="emoji__face">
|
||||
<div class="emoji__eyebrows"></div>
|
||||
<div class="emoji__mouth"></div>
|
||||
</div>` :
|
||||
emojiType === 'wow' ?
|
||||
`<div class="emoji__face">
|
||||
<div class="emoji__eyebrows"></div>
|
||||
<div class="emoji__eyes"></div>
|
||||
<div class="emoji__mouth"></div>
|
||||
</div>` :
|
||||
emojiType === 'sad' ?
|
||||
`<div class="emoji__face">
|
||||
<div class="emoji__eyebrows"></div>
|
||||
<div class="emoji__eyes"></div>
|
||||
<div class="emoji__mouth"></div>
|
||||
</div>` :
|
||||
emojiType === 'angry' ?
|
||||
`<div class="emoji__face">
|
||||
<div class="emoji__eyebrows"></div>
|
||||
<div class="emoji__eyes"></div>
|
||||
<div class="emoji__mouth"></div>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
<div class="${type === 'sent' ? 'text-end' : ''} fw-300 text-muted mt-1 fs-xs">
|
||||
${time}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
messageHtml = `
|
||||
<div class="chat-message ${isJustEmoji ? 'emoji-only' : ''}">
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
<div class="${type === 'sent' ? 'text-end' : ''} fw-300 text-muted mt-1 fs-xs">
|
||||
${time}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
messageDiv.innerHTML = messageHtml;
|
||||
chatContainer.appendChild(messageDiv);
|
||||
}
|
||||
|
||||
// Append image message
|
||||
function appendImageMessage(imageSrc, type, time) {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `chat-segment chat-segment-${type}`;
|
||||
|
||||
messageDiv.innerHTML = `
|
||||
<div class="chat-message">
|
||||
<p><img src="${imageSrc}" class="img-fluid rounded" alt="Shared image" style="max-height: 200px;"></p>
|
||||
</div>
|
||||
<div class="${type === 'sent' ? 'text-end' : ''} fw-300 text-muted mt-1 fs-xs">
|
||||
${time}
|
||||
</div>
|
||||
`;
|
||||
chatContainer.appendChild(messageDiv);
|
||||
}
|
||||
|
||||
// Append file message
|
||||
function appendFileMessage(file, type, time) {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `chat-segment chat-segment-${type}`;
|
||||
|
||||
const fileIconClass = file.type === 'pdf' ? 'file-pdf text-danger' :
|
||||
file.type === 'doc' ? 'file-word text-primary' :
|
||||
file.type === 'xls' ? 'file-excel text-success' :
|
||||
file.type === 'ppt' ? 'file-powerpoint text-warning' : 'file text-muted';
|
||||
|
||||
messageDiv.innerHTML = `
|
||||
<div class="chat-message">
|
||||
<div class="d-flex align-items-center p-2 rounded bg-white">
|
||||
<i class="sa sa-${fileIconClass} fs-2x me-2"></i>
|
||||
<div class="flex-grow-1">
|
||||
<div class="text-truncate fw-500">${file.name}</div>
|
||||
<small class="text-muted">${file.size}</small>
|
||||
</div>
|
||||
<a href="javascript:void(0);" class="btn btn-sm btn-icon">
|
||||
<i class="sa sa-download"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="${type === 'sent' ? 'text-end' : ''} fw-300 text-muted mt-1 fs-xs">
|
||||
${time}
|
||||
</div>
|
||||
`;
|
||||
chatContainer.appendChild(messageDiv);
|
||||
}
|
||||
|
||||
// Helper to get current time string
|
||||
function getCurrentTime() {
|
||||
const now = new Date();
|
||||
return now.getHours().toString().padStart(2, '0') + ':' +
|
||||
now.getMinutes().toString().padStart(2, '0');
|
||||
}
|
||||
|
||||
// Scroll chat to bottom
|
||||
function scrollToBottom() {
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
if (messageInput) {
|
||||
messageInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sendButton) {
|
||||
sendButton.addEventListener('click', () => {
|
||||
sendMessage();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize with some random messages
|
||||
function populateInitialChat() {
|
||||
// Clear existing messages except for timestamps
|
||||
const existingMessages = chatContainer.querySelectorAll('.chat-segment:not(.d-flex)');
|
||||
existingMessages.forEach(msg => msg.remove());
|
||||
|
||||
// Add some initial messages
|
||||
const messages = [
|
||||
{ text: "Hi there! How's your day going?", type: 'get', delay: 0 },
|
||||
{ text: "Pretty good, thanks for asking! Just finished a big project.", type: 'sent', delay: 300 },
|
||||
{ text: "That's great to hear! Is this the same one you mentioned last week?", type: 'get', delay: 600 },
|
||||
{ text: "Yes, finally wrapped it up. The client was really happy with the results.", type: 'sent', delay: 900 },
|
||||
//{ file: mockFiles[0], type: 'sent', isFile: true, delay: 1200 },
|
||||
{ text: "Thanks for sharing the document! I'll take a look at it.", type: 'get', delay: 1500 },
|
||||
{ text: "Let me know if you need any clarification.", type: 'sent', delay: 1800 },
|
||||
{ image: mockImages[2], type: 'get', isImage: true, delay: 2100 },
|
||||
{ text: "That looks amazing! Is that from the project?", type: 'sent', delay: 2400 },
|
||||
{ text: "Yes, it's the final design we went with 😊", type: 'get', delay: 2700 }
|
||||
];
|
||||
|
||||
let cumulativeDelay = 0;
|
||||
messages.forEach(msg => {
|
||||
setTimeout(() => {
|
||||
if (msg.isImage) {
|
||||
appendImageMessage(msg.image, msg.type, getCurrentTime());
|
||||
} else if (msg.isFile) {
|
||||
appendFileMessage(msg.file, msg.type, getCurrentTime());
|
||||
} else {
|
||||
appendMessage(msg.text, msg.type, getCurrentTime());
|
||||
}
|
||||
scrollToBottom();
|
||||
}, cumulativeDelay);
|
||||
cumulativeDelay += msg.delay;
|
||||
});
|
||||
}
|
||||
|
||||
// Add CSS for the typing indicator
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.typing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 17px;
|
||||
}
|
||||
.typing span {
|
||||
background-color: #90949c;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
margin: 0 2px;
|
||||
display: block;
|
||||
animation: typing 1.3s infinite ease-in-out;
|
||||
}
|
||||
.typing span:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
.typing span:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.typing span:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
@keyframes typing {
|
||||
0%, 60%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
30% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
.emoji-only {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
/* Improved conversation list styling */
|
||||
#js-slide-right .list-group-item {
|
||||
transition: background-color 0.2s ease;
|
||||
border-radius: 8px;
|
||||
margin: 4px 8px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.unread-badge {
|
||||
background-color: var(--primary-500);
|
||||
color: white;
|
||||
font-size: 0.7rem;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Story circles */
|
||||
.story-circle {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.story-circle.active .profile-image {
|
||||
border-color: var(--primary-500);
|
||||
}
|
||||
|
||||
.story-circle .profile-image {
|
||||
border: 2px solid var(--bs-body-bg);
|
||||
}
|
||||
|
||||
.story-circle.has-story .profile-image {
|
||||
border: 2px solid var(--primary-500);
|
||||
}
|
||||
|
||||
/* Enhanced Emoji Styles */
|
||||
.chat-segment .emoji {
|
||||
transform: scale(2);
|
||||
margin: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.chat-segment-sent .emoji {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.chat-segment-get .emoji {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Initialize the chat
|
||||
populateInitialChat();
|
||||
|
||||
// Add send button if it doesn't exist
|
||||
if (!sendButton) {
|
||||
const inputGroup = messageInput.parentElement;
|
||||
const newSendButton = document.createElement('button');
|
||||
newSendButton.id = 'send_button';
|
||||
newSendButton.className = 'btn btn-icon fs-xl width-1 flex-shrink-0';
|
||||
newSendButton.setAttribute('type', 'button');
|
||||
newSendButton.setAttribute('data-bs-toggle', 'tooltip');
|
||||
newSendButton.setAttribute('data-bs-original-title', 'Send');
|
||||
newSendButton.setAttribute('data-bs-placement', 'top');
|
||||
newSendButton.innerHTML = '<svg class="sa-icon sa-bold sa-icon-subtlelight"><use href="icons/sprite.svg#send"></use></svg>';
|
||||
|
||||
// Append to input group instead of trying to insert before a specific element
|
||||
inputGroup.appendChild(newSendButton);
|
||||
|
||||
// Add click handler
|
||||
newSendButton.addEventListener('click', () => {
|
||||
sendMessage();
|
||||
});
|
||||
}
|
||||
|
||||
function insertEmoji(element) {
|
||||
// Get the emoji character from the data attribute
|
||||
const emoji = element.getAttribute('data-emoji');
|
||||
|
||||
// Get the chat input
|
||||
const chatInput = document.getElementById('msgr_input');
|
||||
|
||||
// Insert the emoji at cursor position or append to end
|
||||
if (chatInput) {
|
||||
const startPos = chatInput.selectionStart;
|
||||
const endPos = chatInput.selectionEnd;
|
||||
const text = chatInput.value;
|
||||
const before = text.substring(0, startPos);
|
||||
const after = text.substring(endPos, text.length);
|
||||
|
||||
chatInput.value = before + emoji + after;
|
||||
|
||||
// Move cursor position after the inserted emoji
|
||||
chatInput.selectionStart = startPos + emoji.length;
|
||||
chatInput.selectionEnd = startPos + emoji.length;
|
||||
|
||||
// Focus back on the input
|
||||
chatInput.focus();
|
||||
}
|
||||
|
||||
// Close the dropdown if it's open
|
||||
const dropdown = bootstrap.Dropdown.getInstance(element.closest('.dropdown-menu').previousElementSibling);
|
||||
if (dropdown) {
|
||||
dropdown.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
You can use this wrapper to initialize peity charts globally. It will automatically detect the chart
|
||||
type based on the content of the element.
|
||||
|
||||
- You can also use the data-peity attribute to pass in options for the chart.
|
||||
- The data-peity attribute can be a JSON string or a string that can be parsed into a JSON object.
|
||||
- The wrapper will also handle the:
|
||||
-- updating of the chart with new data.
|
||||
-- negative values for the bar charts.
|
||||
-- color transitions for the bar charts.
|
||||
-- updating of the chart with new data.
|
||||
*/
|
||||
|
||||
import { PeityAPI } from './../thirdparty/peity.es6.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
'use strict';
|
||||
|
||||
// Global default settings for charts
|
||||
const defaults = {
|
||||
// Default background color for donut and pie charts (replaces the gray)
|
||||
donutBackground: 'var(--bs-border-color)', // Light purple background instead of gray
|
||||
pieBackground: 'var(--bs-border-color)' // Same for pie charts
|
||||
};
|
||||
|
||||
// Helper to create peity charts (similar to jQuery plugin style)
|
||||
function createPeity(selector, type, customOptions = {}) {
|
||||
document.querySelectorAll(selector).forEach(element => {
|
||||
// Parse data-peity attribute for options
|
||||
let options = {...customOptions};
|
||||
const dataAttr = element.getAttribute('data-peity');
|
||||
|
||||
if (dataAttr) {
|
||||
try {
|
||||
options = {...options, ...JSON.parse(dataAttr)};
|
||||
} catch (e) {
|
||||
console.warn('Invalid data-peity format for', selector);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply defaults for donut and pie charts background colors
|
||||
if (type === 'donut' || type === 'pie') {
|
||||
// If fill is not defined or is an empty array, set default colors
|
||||
if (!options.fill || options.fill.length === 0) {
|
||||
options.fill = type === 'donut' ?
|
||||
['var(--primary-500)', defaults.donutBackground] :
|
||||
['var(--primary-500)', defaults.pieBackground];
|
||||
}
|
||||
// If fill is defined but doesn't include a background color (for fractions like "1/4")
|
||||
else if (Array.isArray(options.fill) && options.fill.length === 1) {
|
||||
options.fill.push(type === 'donut' ? defaults.donutBackground : defaults.pieBackground);
|
||||
}
|
||||
// If data contains multiple values (e.g., "10,4,4,6"), don't add background
|
||||
}
|
||||
|
||||
PeityAPI.create(element, type, options);
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Simple chart initializations with default settings
|
||||
createPeity('.peity-pie', 'pie');
|
||||
createPeity('.peity-donut', 'donut');
|
||||
createPeity('.peity-line', 'line');
|
||||
createPeity('.peity-bar', 'bar');
|
||||
|
||||
// Updating chart with animation
|
||||
const updatingChart = document.querySelector('.updating-chart');
|
||||
if (updatingChart) {
|
||||
const values = updatingChart.textContent.split(',').map(Number);
|
||||
const chart = PeityAPI.create(updatingChart, 'line', {
|
||||
width: 200,
|
||||
height: 40,
|
||||
stroke: 'var(--info-500)',
|
||||
fill: 'var(--info-200)',
|
||||
min: 0,
|
||||
max: 10
|
||||
});
|
||||
|
||||
setInterval(function() {
|
||||
values.shift();
|
||||
values.push(Math.floor(Math.random() * 10));
|
||||
updatingChart.textContent = values.join(',');
|
||||
chart.draw();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Bar charts with negative values (red/green coloring)
|
||||
document.querySelectorAll('.bar-negative').forEach(element => {
|
||||
const values = element.textContent.split(',').map(Number);
|
||||
PeityAPI.create(element, 'bar', {
|
||||
height: 40,
|
||||
width: 110,
|
||||
fill: values.map(value => value > 0 ? 'var(--success-500)' : 'var(--danger-500)')
|
||||
});
|
||||
});
|
||||
|
||||
// Bar charts with color transitions
|
||||
document.querySelectorAll('.bar-transition').forEach(element => {
|
||||
const values = element.textContent.split(',').map(Number);
|
||||
PeityAPI.create(element, 'bar', {
|
||||
height: 40,
|
||||
width: 110,
|
||||
fill: values.map((_, i, all) => {
|
||||
const g = parseInt((i / all.length) * 255);
|
||||
return `rgb(255, ${g}, ${g})`;
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Process all remaining elements with data-peity attribute
|
||||
document.querySelectorAll('[data-peity]').forEach(element => {
|
||||
// Skip elements already handled by specific selectors
|
||||
if (element.classList.contains('peity-pie') ||
|
||||
element.classList.contains('peity-donut') ||
|
||||
element.classList.contains('peity-line') ||
|
||||
element.classList.contains('peity-bar') ||
|
||||
element.classList.contains('updating-chart') ||
|
||||
element.classList.contains('bar-negative') ||
|
||||
element.classList.contains('bar-transition')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-detect chart type based on content
|
||||
const content = element.textContent.trim();
|
||||
let type;
|
||||
|
||||
if (content.includes('/')) {
|
||||
type = 'donut'; // Fraction data is best for donut
|
||||
} else if (content.includes(',')) {
|
||||
// If it has commas, it's likely a series
|
||||
const hasNegative = content.split(',').some(val => parseFloat(val) < 0);
|
||||
type = hasNegative ? 'bar' : 'line'; // Bars handle negative values better visually
|
||||
} else {
|
||||
type = 'pie'; // Default fallback
|
||||
}
|
||||
|
||||
// Get options from data attribute
|
||||
let options = {};
|
||||
const dataAttr = element.getAttribute('data-peity');
|
||||
if (dataAttr) {
|
||||
try {
|
||||
options = JSON.parse(dataAttr);
|
||||
} catch (e) {
|
||||
console.warn('Invalid data-peity format for element', element);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply global background color for donut/pie charts
|
||||
if ((type === 'donut' || type === 'pie') && content.includes('/')) {
|
||||
// For fraction notation (e.g. "1/4"), make sure we have a background color
|
||||
if (!options.fill || !Array.isArray(options.fill) || options.fill.length < 2) {
|
||||
options.fill = options.fill || [];
|
||||
// If we have one color already, keep it and add the background
|
||||
if (options.fill.length === 1) {
|
||||
options.fill.push(type === 'donut' ? defaults.donutBackground : defaults.pieBackground);
|
||||
} else {
|
||||
// No colors defined yet, set defaults
|
||||
options.fill = [
|
||||
type === 'donut' ? 'var(--primary-500)' : 'var(--success-500)',
|
||||
type === 'donut' ? defaults.donutBackground : defaults.pieBackground
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create chart
|
||||
PeityAPI.create(element, type, options);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error initializing Peity charts:', error);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
function swapPositions() {
|
||||
// Toggle the 'position-relative' class on the element with id 'js-position-change'
|
||||
var positionChangeEl = document.getElementById("js-position-change");
|
||||
if (positionChangeEl) {
|
||||
positionChangeEl.classList.toggle("position-relative");
|
||||
}
|
||||
// Helper function to toggle text content between two given strings
|
||||
function toggleText(element, text1, text2) {
|
||||
if (!element) return;
|
||||
// Check current text (trimmed of whitespace)
|
||||
if (element.textContent.trim() === text1) {
|
||||
element.textContent = text2;
|
||||
}
|
||||
else {
|
||||
element.textContent = text1;
|
||||
}
|
||||
}
|
||||
// Toggle the text for the element with id 'js-position-text'
|
||||
var positionTextEl = document.getElementById("js-position-text");
|
||||
toggleText(positionTextEl, ".position-static", ".position-relative");
|
||||
// Toggle the text for the element with id 'js-position-btn'
|
||||
var positionBtnEl = document.getElementById("js-position-btn");
|
||||
toggleText(positionBtnEl, "Change to RELATIVE", "Change to STATIC");
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
|
||||
|
||||
|
||||
// Show comment actions when input is focused
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const commentInput = document.querySelector('.comment-input-wrapper input');
|
||||
const commentActions = document.querySelector('.comment-actions');
|
||||
if (commentInput && commentActions) {
|
||||
commentInput.addEventListener('focus', function () {
|
||||
commentActions.classList.remove('d-none');
|
||||
});
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!commentInput.contains(e.target) && !commentActions.contains(e.target)) {
|
||||
if (commentInput.value.length === 0) {
|
||||
commentActions.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Form validation for all modals
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Get all forms that need validation
|
||||
const forms = document.querySelectorAll('.needs-validation');
|
||||
// Function to handle form submission
|
||||
function handleFormSubmit(event) {
|
||||
const form = event.target.closest('.modal').querySelector('form');
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
else {
|
||||
// If form is valid, you can add AJAX submission here
|
||||
// For demo purposes, we'll just close the modal
|
||||
const modal = bootstrap.Modal.getInstance(event.target.closest('.modal'));
|
||||
modal.hide();
|
||||
|
||||
// Show success message (optional)
|
||||
// This would be replaced with actual form submission in a real application
|
||||
// alert('Form submitted successfully!');
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
}
|
||||
|
||||
// Attach click event handlers to submit buttons
|
||||
document.getElementById('submitNewsForm').addEventListener('click', handleFormSubmit);
|
||||
document.getElementById('submitProjectForm').addEventListener('click', handleFormSubmit);
|
||||
document.getElementById('submitProfileForm').addEventListener('click', handleFormSubmit);
|
||||
|
||||
// Optional: Add validation as user types for better UX
|
||||
Array.from(forms).forEach(form => {
|
||||
const inputs = form.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('input', function () {
|
||||
if (!this.checkValidity()) {
|
||||
this.classList.add('is-invalid');
|
||||
}
|
||||
else {
|
||||
this.classList.remove('is-invalid');
|
||||
this.classList.add('is-valid');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
// Your inline JavaScript code here
|
||||
// Refresh panel content
|
||||
function updatePanelContent(panel) {
|
||||
const panelContent = panel.querySelector('.panel-content > .js-refreshed-content');
|
||||
const timestamp = new Date().toLocaleString(); // Get current date and time
|
||||
panelContent.innerHTML = 'Refreshed content - ' + timestamp;
|
||||
}
|
||||
// generate random Graph
|
||||
function getRandomGraphUrl() {
|
||||
var chartConfig = {
|
||||
type: 'bar',
|
||||
data:
|
||||
{
|
||||
labels: ['A', 'B', 'C', 'D', 'E', 'F'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Random Data',
|
||||
data: [
|
||||
Math.floor(Math.random() * 100),
|
||||
Math.floor(Math.random() * 100),
|
||||
Math.floor(Math.random() * 100),
|
||||
Math.floor(Math.random() * 100),
|
||||
Math.floor(Math.random() * 100),
|
||||
Math.floor(Math.random() * 100)
|
||||
],
|
||||
backgroundColor: ['rgba(33, 150, 243, 0.5)', // #2196F3
|
||||
'rgba(29, 201, 183, 0.5)', // #1dc9b7
|
||||
'rgba(170, 134, 209, 0.5)', // #a86d1
|
||||
'rgba(255, 194, 65, 0.5)', // #ffc241
|
||||
'rgba(253, 57, 149, 0.5)', // #fd3995
|
||||
'rgba(158, 158, 158, 0.5)' // grey for the 6th bar
|
||||
],
|
||||
borderColor: ['rgba(33, 150, 243, 1)', // #2196F3
|
||||
'rgba(29, 201, 183, 1)', // #1dc9b7
|
||||
'rgba(170, 134, 209, 1)', // #a86d1
|
||||
'rgba(255, 194, 65, 1)', // #ffc241
|
||||
'rgba(253, 57, 149, 1)', // #fd3995
|
||||
'rgba(158, 158, 158, 1)' // grey for the 6th bar
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options:
|
||||
{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales:
|
||||
{
|
||||
y:
|
||||
{
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var encodedConfig = encodeURIComponent(JSON.stringify(chartConfig));
|
||||
return 'https://quickchart.io/chart?c=' + encodedConfig;
|
||||
}
|
||||
|
||||
function loadRandomGraph() {
|
||||
var imgElement = document.getElementById('randomGraph');
|
||||
imgElement.src = getRandomGraphUrl();
|
||||
// add timestamp to the panel
|
||||
const panelContent = document.querySelector('.js-last-refresh');
|
||||
const timestamp = new Date().toLocaleString(); // Get current date and time
|
||||
panelContent.innerHTML = 'Chart updated at <strong>' + timestamp + '</strong>';
|
||||
}
|
||||
loadRandomGraph();
|
||||
@@ -0,0 +1,185 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Image Preview Modal Functionality
|
||||
const imageTab = document.querySelector('#tab-images');
|
||||
if (imageTab) {
|
||||
// Create modal container with proper Bootstrap modal structure
|
||||
const modalContainer = document.createElement('div');
|
||||
modalContainer.className = 'modal fade image-preview-modal';
|
||||
modalContainer.id = 'imagePreviewModal';
|
||||
modalContainer.tabIndex = '-1';
|
||||
modalContainer.setAttribute('aria-hidden', 'true');
|
||||
|
||||
// Create modal content
|
||||
modalContainer.innerHTML = `
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body p-0 position-relative d-flex align-items-center justify-content-center">
|
||||
<div class="d-flex flex-column flex-lg-row shadow rounded overflow-hidden border border-3 border-light">
|
||||
<!-- Left Info Panel -->
|
||||
<div class="order-2 order-lg-1 flex-shrink-0" style="width: 300px;">
|
||||
<div class="d-flex flex-column h-100 p-0">
|
||||
<div class="flex-grow-1 p-3">
|
||||
<h5 class="image-title fw-bold mb-3"></h5>
|
||||
<p class="image-description text-muted mb-4"></p>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="text-muted mb-1 fs-sm">Date</div>
|
||||
<div class="image-date"></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="text-muted mb-1 fs-sm">Source</div>
|
||||
<div class="image-source text-primary text-decoration-underline link-offset-1 link-underline link-underline-opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="text-muted mb-1 fs-sm">Tags</div>
|
||||
<div class="image-category badge bg-secondary"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-3 pt-2 pb-0">
|
||||
<p class="text-muted fs-nano mb-2">
|
||||
Images may be subject to copyright. Please check the source for more information.
|
||||
</p>
|
||||
<div class="d-flex gap-2 pb-3">
|
||||
<button type="button" class="btn btn-default btn-sm flex-grow-1">
|
||||
Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm flex-grow-1">
|
||||
Share
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Image Container -->
|
||||
<div class="position-relative bg-light order-1 order-lg-2" style="width: auto; height: auto; max-height: 90vh;">
|
||||
<button type="button" class="btn btn-icon btn-danger border border-dark position-absolute top-0 end-0 m-2 z-1" data-bs-dismiss="modal" aria-label="Close">
|
||||
<svg class="sa-icon sa-bold sa-icon-2x sa-icon-light">
|
||||
<use href="icons/sprite.svg#x"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="d-flex align-items-center justify-content-center h-100 p-0">
|
||||
<button type="button" class="btn btn-icon align-items-center justify-content-center text-light btn-dark bg-dark bg-opacity-50 rounded-circle position-absolute top-50 start-0 translate-middle-y ms-4 d-none d-sm-flex fs-3 z-1" id="prevImage">
|
||||
<i class="sa sa-chevron-left"></i>
|
||||
</button>
|
||||
|
||||
<img src="" class="img-preview" style="max-height: 90vh; max-width: 100%; object-fit: contain;" alt="Preview">
|
||||
|
||||
<button type="button" class="btn btn-icon align-items-center justify-content-center text-light btn-dark bg-dark bg-opacity-50 rounded-circle position-absolute top-50 end-0 translate-middle-y me-4 d-none d-sm-flex fs-3 z-1" id="nextImage">
|
||||
<i class="sa sa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modalContainer);
|
||||
|
||||
// Initialize Bootstrap modal
|
||||
const modal = new bootstrap.Modal(modalContainer);
|
||||
|
||||
// Get all preview images
|
||||
const previewImages = imageTab.querySelectorAll('a[href="#"]');
|
||||
let currentImageIndex = 0;
|
||||
|
||||
// Add click event to each preview image
|
||||
previewImages.forEach((link, index) => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const imgElement = link.querySelector('img');
|
||||
const imgSrc = imgElement.src;
|
||||
// Add -big before the file extension
|
||||
const bigImgSrc = imgSrc.replace(/(\.[^.]+)$/, '-big$1');
|
||||
showPreview(bigImgSrc, link);
|
||||
currentImageIndex = index;
|
||||
updateNavigationButtons();
|
||||
});
|
||||
});
|
||||
|
||||
// Navigation buttons
|
||||
const prevBtn = modalContainer.querySelector('#prevImage');
|
||||
const nextBtn = modalContainer.querySelector('#nextImage');
|
||||
|
||||
prevBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (currentImageIndex > 0) {
|
||||
currentImageIndex--;
|
||||
const link = previewImages[currentImageIndex];
|
||||
const imgElement = link.querySelector('img');
|
||||
const imgSrc = imgElement.src;
|
||||
const bigImgSrc = imgSrc.replace(/(\.[^.]+)$/, '-big$1');
|
||||
updatePreview(bigImgSrc, link);
|
||||
updateNavigationButtons();
|
||||
}
|
||||
});
|
||||
|
||||
nextBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (currentImageIndex < previewImages.length - 1) {
|
||||
currentImageIndex++;
|
||||
const link = previewImages[currentImageIndex];
|
||||
const imgElement = link.querySelector('img');
|
||||
const imgSrc = imgElement.src;
|
||||
const bigImgSrc = imgSrc.replace(/(\.[^.]+)$/, '-big$1');
|
||||
updatePreview(bigImgSrc, link);
|
||||
updateNavigationButtons();
|
||||
}
|
||||
});
|
||||
|
||||
// Helper functions
|
||||
function showPreview(imgSrc, link) {
|
||||
updatePreview(imgSrc, link);
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function updatePreview(imgSrc, link) {
|
||||
const previewImg = modalContainer.querySelector('.img-preview');
|
||||
previewImg.src = imgSrc;
|
||||
|
||||
// Update image information
|
||||
modalContainer.querySelector('.image-title').textContent = link.dataset.imgTitle || '';
|
||||
modalContainer.querySelector('.image-description').textContent = link.dataset.imgDescription || '';
|
||||
modalContainer.querySelector('.image-category').textContent = link.dataset.imgCategory || '';
|
||||
modalContainer.querySelector('.image-date').textContent = link.dataset.imgDate || '';
|
||||
modalContainer.querySelector('.image-source').textContent = link.dataset.imgSource || '';
|
||||
}
|
||||
|
||||
function hidePreview() {
|
||||
modal.hide();
|
||||
}
|
||||
|
||||
function updateNavigationButtons() {
|
||||
prevBtn.classList.toggle('hidden', currentImageIndex === 0);
|
||||
nextBtn.classList.toggle('hidden', currentImageIndex === previewImages.length - 1);
|
||||
}
|
||||
|
||||
// Keyboard navigation
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!modalContainer.classList.contains('show')) return;
|
||||
|
||||
switch(e.key) {
|
||||
case 'Escape':
|
||||
hidePreview();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
if (currentImageIndex > 0) prevBtn.click();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
if (currentImageIndex < previewImages.length - 1) nextBtn.click();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up modal backdrop when modal is hidden
|
||||
modalContainer.addEventListener('hidden.bs.modal', function () {
|
||||
const backdrop = document.querySelector('.modal-backdrop');
|
||||
if (backdrop) {
|
||||
backdrop.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { SmartTables } from '../optional/smartTables/smartTables.bundle.js';
|
||||
|
||||
// Wait for the DOM to load before initializing
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize SmartTables with the table ID and options
|
||||
const table = new SmartTables('myTable',
|
||||
{});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { SmartTables } from '../optional/smartTables/smartTables.bundle.js';
|
||||
|
||||
// Wait for the DOM to load before initializing
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Initialize SmartTables with the table ID and options
|
||||
const table = new SmartTables('myTable', {
|
||||
responsive: {
|
||||
enabled: true,
|
||||
breakpoint: 768,
|
||||
columnPrikorities: {
|
||||
0: 1, // TradeID - highest priority (never hide)
|
||||
1: 2, // Symbol - second highest priority
|
||||
2: 3, // Qty - third priority
|
||||
3: 4, // BuyPrice - fourth priority
|
||||
4: 5, // SellPrice - fifth priority
|
||||
5: 6 // BuyDate - sixth priority
|
||||
}
|
||||
},
|
||||
debug: true,
|
||||
// Add hooks for customizing cell rendering
|
||||
hooks: {
|
||||
afterInit: function () {
|
||||
// Get all table cells after initialization
|
||||
const tbody = document.querySelector('#myTable tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
// Process each row
|
||||
Array.from(tbody.querySelectorAll('tr')).forEach(row => {
|
||||
// Apply formatting to specific columns
|
||||
Array.from(row.cells).forEach((cell, index) => {
|
||||
const text = cell.textContent.trim();
|
||||
|
||||
// Check for null values in any column
|
||||
if (text === 'null') {
|
||||
cell.textContent = 'null'; // Replace with em dash
|
||||
cell.classList.add('text-muted', 'fst-italic');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format Profit column (7)
|
||||
if (index === 7) {
|
||||
if (text.includes('-')) {
|
||||
cell.classList.add('text-danger', 'fw-bold');
|
||||
} else {
|
||||
cell.classList.add('text-success', 'fw-bold');
|
||||
}
|
||||
}
|
||||
|
||||
// Format Net column (9)
|
||||
if (index === 9) {
|
||||
if (text.includes('-')) {
|
||||
cell.classList.add('text-danger', 'fw-bold');
|
||||
} else {
|
||||
cell.classList.add('text-success', 'fw-bold');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,291 @@
|
||||
import { SmartTables } from '../optional/smartTables/smartTables.bundle.js';
|
||||
|
||||
// Wait for the DOM to load before initializing
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// DOM elements
|
||||
const tableContainer = document.getElementById('tableContainer');
|
||||
const thresholdSlider = document.getElementById('threshold');
|
||||
const thresholdValue = document.getElementById('thresholdValue');
|
||||
const minMatchLengthSlider = document.getElementById('minMatchLength');
|
||||
const minMatchLengthValue = document.getElementById('minMatchLengthValue');
|
||||
const multiWordThresholdSlider = document.getElementById('multiWordThreshold');
|
||||
const multiWordThresholdValue = document.getElementById('multiWordThresholdValue');
|
||||
const maxDistanceSlider = document.getElementById('maxDistance');
|
||||
const maxDistanceValue = document.getElementById('maxDistanceValue');
|
||||
const descriptionEl = document.getElementById('description');
|
||||
const buildTableBtn = document.getElementById('buildTable');
|
||||
const destroyTableBtn = document.getElementById('destroyTable');
|
||||
const searchExample = document.getElementById('searchExample');
|
||||
const resetBtn = document.getElementById('resetSettings');
|
||||
|
||||
// Table instance
|
||||
let table = null;
|
||||
|
||||
// Initialize sliders with default values and ranges
|
||||
thresholdSlider.min = 0;
|
||||
thresholdSlider.max = 1;
|
||||
thresholdSlider.step = 0.1;
|
||||
thresholdSlider.value = 0;
|
||||
|
||||
minMatchLengthSlider.min = 1;
|
||||
minMatchLengthSlider.max = 5;
|
||||
minMatchLengthSlider.step = 1;
|
||||
minMatchLengthSlider.value = 1;
|
||||
|
||||
multiWordThresholdSlider.min = 0;
|
||||
multiWordThresholdSlider.max = 1;
|
||||
multiWordThresholdSlider.step = 0.1;
|
||||
multiWordThresholdSlider.value = 1;
|
||||
|
||||
maxDistanceSlider.min = 0;
|
||||
maxDistanceSlider.max = 10;
|
||||
maxDistanceSlider.step = 1;
|
||||
maxDistanceSlider.value = 0;
|
||||
|
||||
// Function to toggle controls state
|
||||
function toggleControlsState(disabled) {
|
||||
// Disable/enable all sliders
|
||||
thresholdSlider.disabled = disabled;
|
||||
minMatchLengthSlider.disabled = disabled;
|
||||
multiWordThresholdSlider.disabled = disabled;
|
||||
maxDistanceSlider.disabled = disabled;
|
||||
resetBtn.disabled = disabled;
|
||||
}
|
||||
|
||||
// Update slider display values
|
||||
function updateSliderValues() {
|
||||
thresholdValue.textContent = thresholdSlider.value;
|
||||
minMatchLengthValue.textContent = minMatchLengthSlider.value;
|
||||
multiWordThresholdValue.textContent = multiWordThresholdSlider.value;
|
||||
maxDistanceValue.textContent = maxDistanceSlider.value;
|
||||
|
||||
updateDescription();
|
||||
}
|
||||
|
||||
// Provide an explanation based on current settings
|
||||
function updateDescription() {
|
||||
const threshold = parseFloat(thresholdSlider.value);
|
||||
const minMatchLength = parseInt(minMatchLengthSlider.value);
|
||||
const multiWordThreshold = parseFloat(multiWordThresholdSlider.value);
|
||||
const maxDistance = parseInt(maxDistanceSlider.value);
|
||||
|
||||
let desc = "";
|
||||
|
||||
// Threshold description
|
||||
if (threshold === 0) {
|
||||
desc += "• <strong>Exact matching only</strong> - No partial matches<br>";
|
||||
} else if (threshold < 0.3) {
|
||||
desc += "• <strong>Minor fuzzy matching</strong> - Requires high similarity<br>";
|
||||
} else if (threshold < 0.6) {
|
||||
desc += "• <strong>Moderate fuzzy matching</strong> - Allows reasonable variations<br>";
|
||||
} else if (threshold < 1) {
|
||||
desc += "• <strong>High fuzzy matching</strong> - Will find loosely related terms<br>";
|
||||
} else {
|
||||
desc += "• <strong>Maximum fuzzy matching</strong> - Will track down distant relatives like an overenthusiastic family reunion<br>";
|
||||
}
|
||||
|
||||
// Min Match Length description
|
||||
if (minMatchLength === 1) {
|
||||
desc += "• Will match even with a <strong>single character</strong><br>";
|
||||
} else {
|
||||
desc += "• Requires at least <strong>" + minMatchLength + " characters</strong> to match<br>";
|
||||
}
|
||||
|
||||
// Multi Word Threshold description
|
||||
if (multiWordThreshold === 1) {
|
||||
desc += "• <strong>All words</strong> in a multi-word search must match<br>";
|
||||
} else if (multiWordThreshold > 0.5) {
|
||||
desc += "• <strong>Most words</strong> in a multi-word search must match<br>";
|
||||
} else if (multiWordThreshold > 0) {
|
||||
desc += "• <strong>Some words</strong> in a multi-word search must match<br>";
|
||||
} else {
|
||||
desc += "• <strong>Any word</strong> in a multi-word search can match<br>";
|
||||
}
|
||||
|
||||
// Max Distance description
|
||||
if (maxDistance === 0) {
|
||||
desc += "• <strong>No typo tolerance</strong> - Characters must match exactly<br>";
|
||||
} else if (maxDistance === 1) {
|
||||
desc += "• <strong>Single typo tolerance</strong> - Allows 1 character mismatch<br>";
|
||||
} else if (maxDistance < 5) {
|
||||
desc += "• <strong>Moderate typo tolerance</strong> - Allows " + maxDistance + " character mismatches<br>";
|
||||
} else {
|
||||
desc += "• <strong>High typo tolerance</strong> - Allows many character mismatches<br>";
|
||||
}
|
||||
|
||||
// Example
|
||||
let example = "";
|
||||
if (threshold === 0 && maxDistance === 0) {
|
||||
example = "Try searching for exact terms like 'John' or 'Do'";
|
||||
} else if (threshold > 0 || maxDistance > 0) {
|
||||
if (maxDistance > 0) {
|
||||
example = "Try searches with typos like 'Jhon' instead of 'John' or 'Subrey' instead of 'Surgery'";
|
||||
} else {
|
||||
example = "Try partial searches like 'Jo' for 'John' or 'Surg' for 'Surgery'";
|
||||
}
|
||||
}
|
||||
searchExample.textContent = example;
|
||||
|
||||
descriptionEl.innerHTML = desc;
|
||||
}
|
||||
|
||||
// Add event listeners to sliders
|
||||
thresholdSlider.addEventListener('input', updateSliderValues);
|
||||
minMatchLengthSlider.addEventListener('input', updateSliderValues);
|
||||
multiWordThresholdSlider.addEventListener('input', updateSliderValues);
|
||||
maxDistanceSlider.addEventListener('input', updateSliderValues);
|
||||
|
||||
// Reset settings to default values
|
||||
resetBtn.addEventListener('click', () => {
|
||||
thresholdSlider.value = 0.7;
|
||||
minMatchLengthSlider.value = 2;
|
||||
multiWordThresholdSlider.value = 0.5;
|
||||
maxDistanceSlider.value = 2;
|
||||
updateSliderValues();
|
||||
});
|
||||
|
||||
// Build table with current settings
|
||||
buildTableBtn.addEventListener('click', () => {
|
||||
// If table already exists, destroy it first
|
||||
if (table) {
|
||||
table.destroy();
|
||||
table = null;
|
||||
}
|
||||
|
||||
// Get current slider values
|
||||
const fuzzySettings = {
|
||||
threshold: parseFloat(thresholdSlider.value),
|
||||
minMatchLength: parseInt(minMatchLengthSlider.value),
|
||||
multiWordThreshold: parseFloat(multiWordThresholdSlider.value),
|
||||
maxDistance: parseInt(maxDistanceSlider.value)
|
||||
};
|
||||
|
||||
// Log settings to confirm values
|
||||
console.log('Building table with fuzzy search settings:', fuzzySettings);
|
||||
|
||||
// Show table container
|
||||
tableContainer.classList.remove('d-none');
|
||||
|
||||
// Initialize SmartTables with the table ID and fuzzy settings
|
||||
table = new SmartTables('myTable', {
|
||||
data: {
|
||||
type: "json",
|
||||
source: "json/MOCK_DATA_HOSPITAL.json",
|
||||
columns: [
|
||||
{ data: "PatientID", title: "ID" },
|
||||
{ data: "PatientName", title: "First Name" },
|
||||
{ data: "Phone", title: "Phone" },
|
||||
{ data: "DOB", title: "DOB" },
|
||||
{ data: "Service", title: "Service" },
|
||||
{ data: "ServiceDate", title: "Service Date" },
|
||||
{
|
||||
data: "Severity",
|
||||
title: "Severity",
|
||||
render: function(value) {
|
||||
let severityClass = {
|
||||
"Mild": "badge bg-success",
|
||||
"Moderate": "badge bg-warning text-dark",
|
||||
"Severe": "badge bg-danger"
|
||||
}[value] || "badge bg-dark";
|
||||
return '<span class="' + severityClass + '">' + value + '</span>';
|
||||
}
|
||||
},
|
||||
{ data: "BillPaid", title: "Bill Paid" },
|
||||
{ data: "BillDue", title: "Bill Due" },
|
||||
{ data: "Department", title: "Department" },
|
||||
{ data: "Staff", title: "Doctor" },
|
||||
{ data: "Nurse", title: "Nurse" },
|
||||
]
|
||||
},
|
||||
fuzzyMatch: fuzzySettings,
|
||||
perPage: 6,
|
||||
search: true,
|
||||
sort: true,
|
||||
pagination: true,
|
||||
debug: false,
|
||||
loading: {
|
||||
enabled: true,
|
||||
duration: 800
|
||||
},
|
||||
responsive: {
|
||||
enabled: true,
|
||||
breakpoint: 768,
|
||||
columnPriorities: {
|
||||
0: 1, // ID - highest priority (never hide)
|
||||
1: 2, // First Name - second highest priority
|
||||
2: 3, // Last Name - third priority
|
||||
3: 4, // Gender - fourth priority
|
||||
4: 5, // Phone - fifth priority
|
||||
5: 6, // DOB - sixth priority
|
||||
6: 7, // Service - seventh priority
|
||||
7: 8, // Service Date - eighth priority
|
||||
8: 9, // Severity - ninth priority
|
||||
9: 10, // Bill Paid - tenth priority
|
||||
10: 11, // Bill Due - eleventh priority
|
||||
11: 12 // Department - twelfth priority
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update button states
|
||||
buildTableBtn.disabled = true;
|
||||
destroyTableBtn.disabled = false;
|
||||
|
||||
// Disable all fuzzy settings controls
|
||||
toggleControlsState(true);
|
||||
|
||||
// Log the actual fuzzy settings used by the table
|
||||
console.log('Table created with actual fuzzy settings:', table.options.fuzzyMatch);
|
||||
|
||||
// Add additional verification for search functionality
|
||||
// setTimeout(() => {
|
||||
// console.log('Verifying table search functionality:');
|
||||
// console.log('- Search method type:', typeof table.handleSearch);
|
||||
// console.log('- Fuzzy match method type:', typeof table.fuzzyMatch);
|
||||
// console.log('- Is server-side:', table.options.data.serverSide);
|
||||
|
||||
// // Log detailed fuzzy search settings
|
||||
// console.log('DETAILED FUZZY SEARCH SETTINGS:');
|
||||
// console.log('- Threshold:', table.options.fuzzyMatch.threshold, '(0-1, higher = more fuzzy)');
|
||||
// console.log('- Min Match Length:', table.options.fuzzyMatch.minMatchLength, '(min characters to match)');
|
||||
// console.log('- Multi-word Threshold:', table.options.fuzzyMatch.multiWordThreshold, '(0-1, lower = more lenient)');
|
||||
// console.log('- Max Distance:', table.options.fuzzyMatch.maxDistance, '(max character mismatches allowed)');
|
||||
|
||||
// // Add a test search to demonstrate fuzzy matching
|
||||
// const testSearchTerms = ["john", "jon", "medical", "surgery", "surgry"];
|
||||
// console.log('FUZZY SEARCH TEST EXAMPLES:');
|
||||
// testSearchTerms.forEach(term => {
|
||||
// console.log(`Testing search term: "${term}"`);
|
||||
// // Create a temp input to simulate search
|
||||
// const tempInput = document.createElement('input');
|
||||
// tempInput.value = term;
|
||||
// // Call the search method directly
|
||||
// const searchFunction = table.handleSearch.bind(table);
|
||||
// searchFunction(term);
|
||||
// console.log(`Results found: ${table.filteredRows.length} rows`);
|
||||
// });
|
||||
// }, 1000);
|
||||
});
|
||||
|
||||
// Destroy table
|
||||
destroyTableBtn.addEventListener('click', () => {
|
||||
if (table) {
|
||||
table.destroy();
|
||||
table = null;
|
||||
}
|
||||
tableContainer.classList.add('d-none');
|
||||
|
||||
// Update button states
|
||||
buildTableBtn.disabled = false;
|
||||
destroyTableBtn.disabled = true;
|
||||
|
||||
// Re-enable all fuzzy settings controls
|
||||
toggleControlsState(false);
|
||||
});
|
||||
|
||||
// Initialize slider values and description
|
||||
updateSliderValues();
|
||||
|
||||
// Disable destroy button initially since no table exists
|
||||
destroyTableBtn.disabled = true;
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { SmartTables } from '../optional/smartTables/smartTables.bundle.js';
|
||||
|
||||
// Wait for the DOM to load before initializing
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const importBtn = document.getElementById('importBtn');
|
||||
const tableContainer = document.getElementById('tableContainer');
|
||||
let table = null;
|
||||
let errorContainer = document.getElementById('errorContainer');
|
||||
|
||||
// Initialize the table immediately but keep the container hidden
|
||||
table = new SmartTables('myTable', {
|
||||
import: true,
|
||||
export: true,
|
||||
responsive: {
|
||||
enabled: true,
|
||||
breakpoint: 768
|
||||
}
|
||||
});
|
||||
|
||||
// Handle import button click
|
||||
importBtn.addEventListener('click', () => {
|
||||
|
||||
// Hide the import data container
|
||||
const hideImportData = document.getElementById('hide-import-data');
|
||||
hideImportData.classList.add('d-none');
|
||||
|
||||
// Show the table container
|
||||
tableContainer.classList.remove('d-none');
|
||||
|
||||
try {
|
||||
// First, create the import modal if it doesn't exist
|
||||
if (typeof table.createImportModal === 'function') {
|
||||
table.createImportModal();
|
||||
|
||||
// Now find the modal and show it using Bootstrap's Modal API
|
||||
const importModal = document.getElementById('importModal');
|
||||
if (importModal) {
|
||||
// Create a new Bootstrap modal instance and show it
|
||||
const bsModal = new bootstrap.Modal(importModal);
|
||||
bsModal.show();
|
||||
console.log('Import modal opened successfully');
|
||||
} else {
|
||||
throw new Error('Import modal element not found after creation');
|
||||
}
|
||||
} else {
|
||||
throw new Error('createImportModal method not found on table instance');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to open import modal:', error);
|
||||
errorContainer.classList.remove('d-none');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
import { SmartTables } from '../optional/smartTables/smartTables.bundle.js';
|
||||
|
||||
// Wait for the DOM to load before initializing
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('Initializing SmartTables with JSON data source');
|
||||
|
||||
function getEmailProviderIcon(email) {
|
||||
const domain = email.toLowerCase().split('@')[1]?.split('.')[0]; // Extract domain (e.g., "hotmail" from "user@hotmail.com")
|
||||
switch (domain) {
|
||||
case 'hotmail':
|
||||
case 'outlook':
|
||||
return 'fab fa-microsoft'; // Microsoft icon for Hotmail/Outlook
|
||||
case 'gmail':
|
||||
return 'fab fa-google'; // Google icon for Gmail
|
||||
case 'yahoo':
|
||||
return 'fab fa-yahoo'; // Yahoo icon for Yahoo
|
||||
default:
|
||||
return 'fas fa-envelope'; // Default email icon for other providers
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize SmartTables with the table ID and options
|
||||
const table = new SmartTables('myTable', {
|
||||
data: {
|
||||
type: "json",
|
||||
source: "json/smarttable-data.json",
|
||||
columns: [
|
||||
{ data: "ID", title: "ID" },
|
||||
{ data: "FirstName", title: "First Name" },
|
||||
{ data: "LastName", title: "Last Name" },
|
||||
{ data: "Gender", title: "Gender" },
|
||||
{ data: "Phone", title: "Phone" },
|
||||
{
|
||||
data: "Status",
|
||||
title: "Payment",
|
||||
render: function(value) {
|
||||
let statusClass = {
|
||||
"Success": "badge bg-success",
|
||||
"Pending": "badge bg-warning text-dark",
|
||||
"Rejected": "badge bg-danger",
|
||||
"Cash": "badge bg-secondary"
|
||||
}[value] || "badge bg-dark";
|
||||
return '<span class="' + statusClass + '">' + value + '</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: "Due",
|
||||
title: "Sales",
|
||||
render: function(value) {
|
||||
return '$' + parseFloat(value).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: "Product",
|
||||
title: "Product",
|
||||
render: function(value, row) {
|
||||
if (!value) return ''; // Handle null/undefined values
|
||||
// Check if the row has a ProductImage
|
||||
if (row.ProductImage) {
|
||||
return `<a href="#" class="product-name text-decoration-underline" data-bs-toggle="popover" data-bs-trigger="hover" data-bs-html="true" data-bs-content="<img src='${row.ProductImage}' alt='${value}' style='max-width: 200px; max-height: 200px;' />">${value}</a>`;
|
||||
}
|
||||
return value; // Return the product name as plain text if no image
|
||||
}
|
||||
},
|
||||
{ data: "Department", title: "POS" },
|
||||
{
|
||||
data: "Email",
|
||||
title: "Email",
|
||||
render: function (value) {
|
||||
if (!value) return ''; // Handle null/undefined values
|
||||
const lowerCaseEmail = value.toLowerCase(); // Convert email to lowercase
|
||||
const iconClass = getEmailProviderIcon(lowerCaseEmail); // Get the appropriate icon
|
||||
return `<span class="bg-secondary bg-opacity-10 border border-success border-opacity-50 py-1 px-2 fs-sm rounded"><i class="${iconClass} me-2 text-success"></i>${lowerCaseEmail}</span>`;
|
||||
}
|
||||
},
|
||||
{ data: "City", title: "Area" },
|
||||
{ data: "Address", title: "Address" },
|
||||
{ data: "Company", title: "Company" },
|
||||
{ data: "CreatedDate", title: "Created Date" }
|
||||
]
|
||||
},
|
||||
perPage: 15,
|
||||
search: true,
|
||||
sort: true,
|
||||
pagination: true,
|
||||
export: true,
|
||||
print: true,
|
||||
import: true,
|
||||
debug: false,
|
||||
responsive: {
|
||||
enabled: true,
|
||||
breakpoint: 768,
|
||||
columnPriorities: {
|
||||
0: 1, // ID - highest priority (never hide)
|
||||
1: 2, // First Name - second highest priority
|
||||
2: 3, // Last Name - third priority
|
||||
3: 4, // Gender - fourth priority
|
||||
4: 5, // Phone - fifth priority
|
||||
5: 6 // Status - lowest priority (hide first)
|
||||
}
|
||||
},
|
||||
hooks: {
|
||||
beforeInit: function() {
|
||||
console.log('SmartTables: Before initialization');
|
||||
},
|
||||
afterInit: function() {
|
||||
console.log('SmartTables: After initialization complete');
|
||||
},
|
||||
afterDraw: function() {
|
||||
console.log('SmartTables: Table drawn');
|
||||
|
||||
// Initialize popovers for products with images
|
||||
const popoverElements = document.querySelectorAll('.product-name');
|
||||
popoverElements.forEach(element => {
|
||||
// Destroy any existing popover to avoid duplicates
|
||||
if (element._popover) {
|
||||
bootstrap.Popover.getInstance(element).dispose();
|
||||
}
|
||||
|
||||
// Initialize new popover with explicit top placement and high z-index
|
||||
new bootstrap.Popover(element, {
|
||||
placement: 'top', // Forces the popover to appear on top
|
||||
trigger: 'hover', // Ensures popover appears on hover
|
||||
customClass: 'popover-body-p-0', // Custom class to control styling
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,144 @@
|
||||
import { SmartTables } from '../optional/smartTables/smartTables.bundle.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Define column definitions
|
||||
let columnDefs = [
|
||||
{ data: "id", title: "ID" },
|
||||
{ data: "name", title: "Name" },
|
||||
{ data: "age", title: "Age" },
|
||||
{
|
||||
data: "salary",
|
||||
title: "Salary",
|
||||
render: function(value) {
|
||||
let salary = parseFloat(value);
|
||||
if (isNaN(salary)) return "$0.00";
|
||||
return "$" + salary.toLocaleString('en-US', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
data: "bonus",
|
||||
title: "Bonus",
|
||||
render: function(value) {
|
||||
let bonus = parseFloat(value) || 0;
|
||||
|
||||
let formattedBonus = "$" + bonus.toLocaleString('en-US', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
|
||||
// Check if bonus is less than $3,000
|
||||
let shouldBeRed = bonus < 3000;
|
||||
|
||||
return shouldBeRed ?
|
||||
`<span class="text-danger fw-bold">${formattedBonus}</span>` :
|
||||
formattedBonus;
|
||||
}
|
||||
},
|
||||
{
|
||||
data: "performance",
|
||||
title: "Performance",
|
||||
render: function(value) {
|
||||
let performance = Math.min(Math.max(parseInt(value), 1), 100);
|
||||
let progressClass;
|
||||
if (performance < 30) {
|
||||
progressClass = "bg-danger";
|
||||
} else if (performance > 70) {
|
||||
progressClass = "bg-success-700";
|
||||
} else if (performance >= 50 && performance <= 70) {
|
||||
progressClass = "bg-success-500";
|
||||
} else {
|
||||
progressClass = "bg-warning text-dark";
|
||||
}
|
||||
return '<div class="progress" style="height: 20px;">' +
|
||||
'<div class="progress-bar ' + progressClass + '" ' +
|
||||
'role="progressbar" ' +
|
||||
'data-width="' + performance + '" ' +
|
||||
'aria-valuenow="' + performance + '" ' +
|
||||
'aria-valuemin="0" ' +
|
||||
'aria-valuemax="100" ' +
|
||||
'style="width: ' + performance + '%;">' +
|
||||
performance + '%' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
{ data: "department", title: "Department" },
|
||||
{ data: "hire_date", title: "Hired" },
|
||||
{
|
||||
data: "overtime_hours",
|
||||
title: "Overtime Hrs",
|
||||
render: function(value) {
|
||||
let hours = parseFloat(value) || 0;
|
||||
const maxHours = 50;
|
||||
let widthPercent = Math.min((hours / maxHours) * 100, 100);
|
||||
let barColor = hours > 40 ? "bg-danger" :
|
||||
hours > 20 ? "bg-warning text-dark" :
|
||||
"bg-success";
|
||||
return `
|
||||
<div class="progress" style="height: 20px; width: 100%;">
|
||||
<div class="progress-bar ${barColor}"
|
||||
role="progressbar"
|
||||
style="width: ${widthPercent}%;"
|
||||
aria-valuenow="${hours}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="${maxHours}">
|
||||
${hours} hrs
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
{ data: "projects_completed", title: "Completed" },
|
||||
{
|
||||
data: "satisfaction_score",
|
||||
title: "Score",
|
||||
render: function(value) {
|
||||
let score = parseFloat(value) || 0;
|
||||
score = Math.min(Math.max(score, 0), 10);
|
||||
|
||||
let badgeClass, label;
|
||||
if (score <= 2) {
|
||||
badgeClass = "bg-danger-300";
|
||||
label = "Poor";
|
||||
} else if (score <= 4) {
|
||||
badgeClass = "bg-warning-300";
|
||||
label = "Fair";
|
||||
} else if (score <= 6) {
|
||||
badgeClass = "bg-success-400";
|
||||
label = "Average";
|
||||
} else if (score <= 8) {
|
||||
badgeClass = "bg-success-500";
|
||||
label = "Good";
|
||||
} else {
|
||||
badgeClass = "bg-success-700";
|
||||
label = "Excellent";
|
||||
}
|
||||
|
||||
return `<span class="badge ${badgeClass}">${label} (${score})</span>`;
|
||||
}
|
||||
},
|
||||
{ data: "remote_work_days", title: "Remote Work Days" },
|
||||
{ data: "training_hours", title: "Training Hrs" },
|
||||
{ data: "email", title: "Email" },
|
||||
];
|
||||
// Initilize tables
|
||||
const smartTable = new SmartTables('demo-table', {
|
||||
data: {
|
||||
type: 'ajax',
|
||||
source: 'https://getwebora.com/smartadmin/json//mock-server.php?scenario=success',
|
||||
serverSide: true,
|
||||
method: 'GET',
|
||||
columns: columnDefs
|
||||
},
|
||||
perPage: 10,
|
||||
debug: true,
|
||||
responsive: {
|
||||
enabled: true,
|
||||
breakpoint: 768,
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,235 @@
|
||||
// IndexedDB setup
|
||||
let db;
|
||||
const DB_NAME = 'IconStackDB';
|
||||
const STORE_NAME = 'savedIcons';
|
||||
const DB_VERSION = 1;
|
||||
|
||||
// Toast management
|
||||
let currentToast = null;
|
||||
|
||||
// Initialize IndexedDB
|
||||
function initDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => {
|
||||
db = request.result;
|
||||
resolve(db);
|
||||
};
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = event.target.result;
|
||||
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
||||
const store = db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
|
||||
store.createIndex('name', 'name', { unique: true });
|
||||
store.createIndex('createdAt', 'createdAt', { unique: false });
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Function to retrieve all saved icons
|
||||
async function getAllSavedIcons() {
|
||||
const transaction = db.transaction([STORE_NAME], 'readonly');
|
||||
const store = transaction.objectStore(STORE_NAME);
|
||||
const request = store.getAll();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to show toast notifications
|
||||
function showToast(message, type = 'primary') {
|
||||
if (currentToast) {
|
||||
currentToast.hide();
|
||||
}
|
||||
|
||||
// Check if toast container exists, create if not
|
||||
let toastContainer = document.querySelector('.toast-container');
|
||||
if (!toastContainer) {
|
||||
toastContainer = document.createElement('div');
|
||||
toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3';
|
||||
document.body.appendChild(toastContainer);
|
||||
}
|
||||
|
||||
// Create toast element
|
||||
const toastId = 'toast-' + Date.now();
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast hide align-items-center border-0 py-2 px-3 bg-${type} text-white`;
|
||||
toast.id = toastId;
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.setAttribute('aria-live', 'assertive');
|
||||
toast.setAttribute('aria-atomic', 'true');
|
||||
toast.style.setProperty('--bs-toast-max-width', 'auto');
|
||||
|
||||
toast.innerHTML = `
|
||||
<div class="d-flex">
|
||||
<div class="toast-body d-flex align-items-center justify-content-center">
|
||||
${message}
|
||||
</div>
|
||||
<button type="button" class="btn btn-system ms-auto" data-bs-dismiss="toast" aria-label="Close">
|
||||
<svg class="sa-icon sa-icon-light">
|
||||
<use href="icons/sprite.svg#x"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
toastContainer.appendChild(toast);
|
||||
|
||||
// Initialize and show the toast
|
||||
currentToast = new bootstrap.Toast(toast, {
|
||||
autohide: true,
|
||||
delay: 3000
|
||||
});
|
||||
|
||||
// Remove toast after it's hidden
|
||||
toast.addEventListener('hidden.bs.toast', function() {
|
||||
currentToast = null;
|
||||
toast.remove();
|
||||
});
|
||||
|
||||
currentToast.show();
|
||||
}
|
||||
|
||||
// Function to delete an icon from IndexedDB
|
||||
async function deleteIcon(iconId) {
|
||||
try {
|
||||
if (confirm('Are you sure you want to delete this icon?')) {
|
||||
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
||||
const store = transaction.objectStore(STORE_NAME);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const request = store.delete(iconId);
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
|
||||
showToast('Icon deleted successfully', 'success');
|
||||
await populateIconList(); // Refresh the list
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting icon:', error);
|
||||
showToast('Failed to delete icon', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to copy icon HTML to clipboard
|
||||
function copyIconToClipboard(iconHTML) {
|
||||
navigator.clipboard.writeText(iconHTML)
|
||||
.then(() => showToast('Icon copied to clipboard!', 'success'))
|
||||
.catch(() => showToast('Failed to copy icon', 'danger'));
|
||||
}
|
||||
|
||||
// Function to populate the icon list
|
||||
async function populateIconList() {
|
||||
try {
|
||||
const icons = await getAllSavedIcons();
|
||||
const iconList = document.getElementById('iconList');
|
||||
|
||||
// Clear existing content
|
||||
iconList.innerHTML = '';
|
||||
|
||||
// Sort icons by creation date (newest first)
|
||||
icons.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
|
||||
icons.forEach(icon => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'col-4 col-sm-3 col-md-3 col-lg-2 col-xl-2 col-xxl-1 mb-g';
|
||||
|
||||
li.innerHTML = `
|
||||
<div class="d-flex flex-column align-items-center p-2 m-0 w-100 shadow-hover-2 border rounded position-relative show-child-on-hover overflow-hidden" style="font-size: 4rem;">
|
||||
<div class="show-on-hover-parent bg-secondary bg-opacity-50 position-absolute top-0 start-0 w-100 h-100 z-1">
|
||||
<div class="d-flex flex-row align-items-end justify-content-center h-100 gap-1 pb-2">
|
||||
<button type="button" class="btn btn-xs btn-success copy-btn">
|
||||
COPY
|
||||
</button>
|
||||
<button type="button" class="btn btn-xs btn-danger delete-btn">
|
||||
DEL
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pb-1 d-flex icon-container">
|
||||
<div class="stack-icon">
|
||||
${icon.html}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted fs-nano icon-name">
|
||||
${icon.name}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
iconList.appendChild(li);
|
||||
|
||||
// Add event listeners after the element is added to the DOM
|
||||
const copyBtn = li.querySelector('.copy-btn');
|
||||
const deleteBtn = li.querySelector('.delete-btn');
|
||||
const iconContainer = li.querySelector('.icon-container');
|
||||
|
||||
// Format icon HTML for copying
|
||||
const iconHTML = `<div class="stack-icon">${icon.html}</div>`;
|
||||
|
||||
// Add click handlers
|
||||
copyBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation(); // Prevent event bubbling
|
||||
copyIconToClipboard(iconHTML);
|
||||
});
|
||||
|
||||
deleteBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation(); // Prevent event bubbling
|
||||
deleteIcon(icon.id);
|
||||
});
|
||||
|
||||
// Keep the container click for convenience
|
||||
iconContainer.addEventListener('click', () => {
|
||||
copyIconToClipboard(iconHTML);
|
||||
});
|
||||
});
|
||||
|
||||
// Show message if no icons found
|
||||
if (icons.length === 0) {
|
||||
iconList.innerHTML = `
|
||||
<div class="col-12 text-center text-muted py-5">
|
||||
<h4>No saved icons found</h4>
|
||||
<p>Create and save some icons using the <a href="stackgenerator.html">Stack Generator</a> to see them here.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading icons:', error);
|
||||
showToast('Failed to load icons', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to filter icons based on search input
|
||||
function filterIcons() {
|
||||
const searchTerm = document.getElementById('searchIcons').value.toLowerCase();
|
||||
const icons = document.querySelectorAll('#iconList li');
|
||||
|
||||
icons.forEach(icon => {
|
||||
const name = icon.querySelector('.icon-name').textContent.toLowerCase();
|
||||
const matches = name.includes(searchTerm);
|
||||
icon.style.display = matches ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
try {
|
||||
await initDB();
|
||||
await populateIconList();
|
||||
|
||||
// Add search functionality
|
||||
const searchInput = document.getElementById('searchIcons');
|
||||
searchInput.addEventListener('input', filterIcons);
|
||||
|
||||
console.log('Stack Library initialized successfully');
|
||||
} catch (error) {
|
||||
console.error('Error initializing Stack Library:', error);
|
||||
showToast('Failed to initialize Stack Library', 'danger');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
// Import the streamline module
|
||||
import { initStreamlines } from '../thirdparty/streamline.es6.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all streamline charts
|
||||
initStreamlines();
|
||||
});
|
||||
@@ -0,0 +1,727 @@
|
||||
import ApexCharts from '../thirdparty/apexchartsWrapper.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
'use strict';
|
||||
|
||||
/***************************************************************/
|
||||
/* Subscription Dashboard Chart #subscription-chart */
|
||||
/***************************************************************/
|
||||
|
||||
if (document.getElementById('subscription-chart')) {
|
||||
const categories = ['2025-01', '2025-02', '2025-03', '2025-04', '2025-05', '2025-06', '2025-07', '2025-08', '2025-09', '2025-10', '2025-11', '2025-12'];
|
||||
const visitsData = [23686, 30820, 59622, 146465, 78160, 79520, 36148, 48721, 158303, 155174, 104830, 86895];
|
||||
const subscriptionsData = [1545, 1350, 1270, 1830, 1955, 1865, 2034, 2544, 1956, 2211, 1540, 1670];
|
||||
|
||||
const subscriptionChartOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Visits',
|
||||
type: 'area',
|
||||
data: visitsData
|
||||
},
|
||||
{
|
||||
name: 'Subscriptions',
|
||||
type: 'line',
|
||||
data: subscriptionsData
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 335,
|
||||
type: 'line',
|
||||
zoom: {
|
||||
enabled: false
|
||||
},
|
||||
stacked: false,
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
fontFamily: 'inherit',
|
||||
parentHeightOffset: 0
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.bootstrapVars.bodyColor.rgba(0.1), // Visits (gray, area)
|
||||
window.colorMap.primary[400].hex // Subscriptions (teal, line)
|
||||
],
|
||||
stroke: {
|
||||
width: [1, 2],
|
||||
curve: 'smooth',
|
||||
colors: [window.colorMap.bootstrapVars.bodyColor.rgba(0.8), window.colorMap.primary[400].hex],
|
||||
dashArray: [4, 0], // Visits dashed, Subscriptions solid
|
||||
},
|
||||
fill: {
|
||||
type: ['solid', 'solid'],
|
||||
opacity: [0.15, 1],
|
||||
},
|
||||
markers: {
|
||||
size: [3, 3],
|
||||
colors: [window.colorMap.bootstrapVars.bodyColor.rgba(0.7), window.colorMap.primary[600].hex],
|
||||
strokeColors: [window.colorMap.bootstrapVars.bodyColor.rgba(0.7), window.colorMap.primary[600].hex],
|
||||
strokeWidth: 2,
|
||||
hover: {
|
||||
sizeOffset: 2
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: categories,
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
fontSize: '12px'
|
||||
}
|
||||
},
|
||||
axisBorder: {
|
||||
show: false
|
||||
},
|
||||
axisTicks: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: [
|
||||
{
|
||||
seriesName: 'Visits',
|
||||
min: 20000,
|
||||
max: 170000,
|
||||
tickAmount: 6,
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
fontSize: '12px'
|
||||
},
|
||||
formatter: function (val) {
|
||||
return val.toLocaleString();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
seriesName: 'Subscriptions',
|
||||
opposite: true,
|
||||
min: 1200,
|
||||
max: 2700,
|
||||
tickAmount: 6,
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
fontSize: '12px'
|
||||
},
|
||||
formatter: function (val) {
|
||||
return val.toLocaleString();
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
horizontalAlign: 'center',
|
||||
fontSize: '14px',
|
||||
fontFamily: 'inherit',
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
},
|
||||
markers: {
|
||||
width: 18,
|
||||
height: 6,
|
||||
radius: 2
|
||||
},
|
||||
itemMargin: {
|
||||
horizontal: 12,
|
||||
vertical: 0
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.1),
|
||||
strokeDashArray: 3,
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false,
|
||||
theme: 'dark',
|
||||
y: [
|
||||
{
|
||||
formatter: function (val) {
|
||||
return val.toLocaleString();
|
||||
}
|
||||
},
|
||||
{
|
||||
formatter: function (val) {
|
||||
return val.toLocaleString();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const subscriptionChart = new ApexCharts(
|
||||
document.getElementById('subscription-chart'),
|
||||
subscriptionChartOptions
|
||||
);
|
||||
subscriptionChart.render();
|
||||
}
|
||||
|
||||
/***************************************************************/
|
||||
/* User Activity Chart #user-activity-chart */
|
||||
/***************************************************************/
|
||||
|
||||
if (document.getElementById('user-activity-chart')) {
|
||||
const categories = ['Blogging', 'Videos', 'Ads', 'Comments', 'Shares', 'Likes', 'Funny'];
|
||||
|
||||
const userActivityChartOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Morning',
|
||||
data: [65, 59, 90, 81, 56, 55, 40]
|
||||
},
|
||||
{
|
||||
name: 'Night',
|
||||
data: [28, 48, 40, 19, 96, 27, 100]
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
height: 350,
|
||||
width: '100%',
|
||||
type: 'radar',
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
fontFamily: 'inherit',
|
||||
parentHeightOffset: 0,
|
||||
sparkline: {
|
||||
enabled: false
|
||||
},
|
||||
margin: 0,
|
||||
padding: {
|
||||
top: -10,
|
||||
right: -10,
|
||||
bottom: -10,
|
||||
left: -10
|
||||
}
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.success[400].hex, // Morning (light purple)
|
||||
window.colorMap.primary[400].hex // Night (teal)
|
||||
],
|
||||
stroke: {
|
||||
width: 0,
|
||||
colors: [window.colorMap.success[400].hex, window.colorMap.primary[400].hex]
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.2
|
||||
},
|
||||
markers: {
|
||||
size: 5,
|
||||
colors: [window.colorMap.success[400].hex, window.colorMap.primary[400].hex],
|
||||
strokeColors: '#fff',
|
||||
strokeWidth: 2,
|
||||
hover: {
|
||||
sizeOffset: 2
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: categories,
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
fontSize: '10px'
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
max: 100,
|
||||
tickAmount: 5,
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
fontSize: '10px'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
horizontalAlign: 'center',
|
||||
fontSize: '14px',
|
||||
fontFamily: 'inherit',
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
},
|
||||
markers: {
|
||||
width: 18,
|
||||
height: 6,
|
||||
radius: 2
|
||||
},
|
||||
itemMargin: {
|
||||
horizontal: 12,
|
||||
vertical: 0
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark',
|
||||
y: {
|
||||
formatter: function (val) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.1),
|
||||
strokeDashArray: 3
|
||||
},
|
||||
plotOptions: {
|
||||
radar: {
|
||||
size: undefined,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
padding: 0,
|
||||
polygons: {
|
||||
strokeColors: window.colorMap.bootstrapVars.bodyColor.rgba(0.1),
|
||||
connectorColors: window.colorMap.bootstrapVars.bodyColor.rgba(0.1),
|
||||
fill: {
|
||||
colors: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const userActivityChart = new ApexCharts(
|
||||
document.getElementById('user-activity-chart'),
|
||||
userActivityChartOptions
|
||||
);
|
||||
userActivityChart.render();
|
||||
}
|
||||
|
||||
/***************************************************************/
|
||||
/* Data Stream Chart #data-stream-chart */
|
||||
/***************************************************************/
|
||||
|
||||
if (document.getElementById('data-stream-chart')) {
|
||||
const categories = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];
|
||||
const positiveData = [0, 25, 70, 85, 25, 0, 60];
|
||||
const negativeData = [-45, -50, -30, -25, -60, -120, 0];
|
||||
|
||||
const dataStreamChartOptions = {
|
||||
series: [
|
||||
{
|
||||
name: 'Positive',
|
||||
data: positiveData
|
||||
},
|
||||
{
|
||||
name: 'Negative',
|
||||
data: negativeData
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 350,
|
||||
maxHeight: '100%',
|
||||
stacked: true,
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
fontFamily: 'inherit',
|
||||
parentHeightOffset: 0
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.success[400].hex, // Positive (teal)
|
||||
window.colorMap.primary[500].hex // Negative (blue)
|
||||
],
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: '50%',
|
||||
borderRadius: 1
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
xaxis: {
|
||||
categories: categories,
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
fontSize: '12px'
|
||||
}
|
||||
},
|
||||
axisBorder: {
|
||||
show: false
|
||||
},
|
||||
axisTicks: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: -150,
|
||||
max: 150,
|
||||
tickAmount: 6,
|
||||
labels: {
|
||||
style: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex,
|
||||
fontSize: '12px'
|
||||
},
|
||||
formatter: function (val) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
horizontalAlign: 'center',
|
||||
fontSize: '14px',
|
||||
fontFamily: 'inherit',
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
},
|
||||
markers: {
|
||||
width: 18,
|
||||
height: 6,
|
||||
radius: 2
|
||||
},
|
||||
itemMargin: {
|
||||
horizontal: 12,
|
||||
vertical: 0
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
borderColor: window.colorMap.bootstrapVars.bodyColor.rgba(0.1),
|
||||
strokeDashArray: 3,
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false,
|
||||
theme: 'dark',
|
||||
y: {
|
||||
formatter: function (val) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const dataStreamChart = new ApexCharts(
|
||||
document.getElementById('data-stream-chart'),
|
||||
dataStreamChartOptions
|
||||
);
|
||||
dataStreamChart.render();
|
||||
}
|
||||
|
||||
/***************************************************************/
|
||||
/* Demographic Marketing Chart #demographic-marketing-chart */
|
||||
/***************************************************************/
|
||||
|
||||
if (document.getElementById('demographic-marketing-chart')) {
|
||||
const countries = ['USA', 'Germany', 'Australia', 'Canada', 'France'];
|
||||
const data = [25, 30, 15, 10, 20]; // Percentages
|
||||
|
||||
const demographicMarketingChartOptions = {
|
||||
series: data,
|
||||
chart: {
|
||||
type: 'pie',
|
||||
height: 350,
|
||||
maxHeight: '100%',
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
fontFamily: 'inherit',
|
||||
parentHeightOffset: 0
|
||||
},
|
||||
colors: [
|
||||
window.colorMap.primary[100].hex, // USA (light purple)
|
||||
window.colorMap.primary[400].hex, // Germany (darker purple)
|
||||
window.colorMap.success[100].hex, // Australia (light blue)
|
||||
window.colorMap.success[300].hex, // Canada (light teal)
|
||||
window.colorMap.success[500].hex // France (teal)
|
||||
],
|
||||
labels: countries,
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
donut: {
|
||||
size: '0%'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
horizontalAlign: 'center',
|
||||
fontSize: '14px',
|
||||
fontFamily: 'inherit',
|
||||
labels: {
|
||||
colors: window.colorMap.bootstrapVars.bodyColor.hex
|
||||
},
|
||||
markers: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
radius: 2
|
||||
},
|
||||
itemMargin: {
|
||||
horizontal: 10,
|
||||
vertical: 2
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
theme: 'dark',
|
||||
y: {
|
||||
formatter: function (val) {
|
||||
return val + '%';
|
||||
}
|
||||
},
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
fontFamily: 'inherit'
|
||||
},
|
||||
custom: function ({ series, seriesIndex, dataPointIndex, w }) {
|
||||
const country = w.globals.labels[seriesIndex];
|
||||
const value = series[seriesIndex];
|
||||
return '<div class="apexcharts-tooltip-box" style="padding: 2px 7px; background-color: var(--bs-body-bg);">' +
|
||||
'<span style="color: var(--bs-body-color); font-size: 0.825rem !important;">' + country + ': ' + value + '%</span>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
width: 1,
|
||||
colors: ['var(--bs-body-bg)']
|
||||
}
|
||||
};
|
||||
|
||||
const demographicMarketingChart = new ApexCharts(
|
||||
document.getElementById('demographic-marketing-chart'),
|
||||
demographicMarketingChartOptions
|
||||
);
|
||||
demographicMarketingChart.render();
|
||||
}
|
||||
|
||||
/***************************************************************/
|
||||
/* Campaign Modal Functionality */
|
||||
/***************************************************************/
|
||||
|
||||
const discountSlider = document.getElementById('discountAmount');
|
||||
const discountValue = document.getElementById('discountAmountValue');
|
||||
|
||||
if (discountSlider && discountValue) {
|
||||
discountSlider.addEventListener('input', function () {
|
||||
discountValue.textContent = this.value;
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle discount amount field visibility based on offer type
|
||||
const offerTypeRadios = document.querySelectorAll('input[name="offerType"]');
|
||||
const discountAmountContainer = document.getElementById('discountAmount')?.closest('.mb-3');
|
||||
|
||||
if (offerTypeRadios.length && discountAmountContainer) {
|
||||
offerTypeRadios.forEach(radio => {
|
||||
radio.addEventListener('change', function () {
|
||||
// Only show discount slider for discount type offers
|
||||
discountAmountContainer.style.display = (this.value === 'discount') ? 'block' : 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Show/hide custom audience options when "Custom Segment" is selected
|
||||
const targetAudienceSelect = document.getElementById('targetAudience');
|
||||
if (targetAudienceSelect) {
|
||||
targetAudienceSelect.addEventListener('change', function () {
|
||||
if (this.value === 'custom') {
|
||||
// Check if custom audience options already exist
|
||||
if (!document.getElementById('customAudienceOptions')) {
|
||||
const customOptions = document.createElement('div');
|
||||
customOptions.id = 'customAudienceOptions';
|
||||
customOptions.className = 'mt-3 p-3 border border-light rounded';
|
||||
customOptions.innerHTML = `
|
||||
<p class="fw-bold mb-2">Define Custom Audience</p>
|
||||
<div class="mb-2">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="segmentRecent">
|
||||
<label class="form-check-label" for="segmentRecent">
|
||||
Recently active (last 30 days)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="segmentHighValue">
|
||||
<label class="form-check-label" for="segmentHighValue">
|
||||
High-value subscribers
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="segmentLocation">
|
||||
<label class="form-check-label" for="segmentLocation">
|
||||
Specific locations
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
targetAudienceSelect.parentNode.appendChild(customOptions);
|
||||
} else {
|
||||
document.getElementById('customAudienceOptions').style.display = 'block';
|
||||
}
|
||||
} else if (document.getElementById('customAudienceOptions')) {
|
||||
document.getElementById('customAudienceOptions').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update form fields based on campaign type
|
||||
const campaignTypeSelect = document.getElementById('campaignType');
|
||||
if (campaignTypeSelect) {
|
||||
campaignTypeSelect.addEventListener('change', function () {
|
||||
// Get offer type radio buttons
|
||||
const offerRadios = document.querySelectorAll('input[name="offerType"]');
|
||||
|
||||
switch (this.value) {
|
||||
case 'acquisition':
|
||||
// For acquisition, preselect free trial
|
||||
offerRadios.forEach(radio => {
|
||||
if (radio.value === 'freeTrial') radio.checked = true;
|
||||
else radio.checked = false;
|
||||
});
|
||||
// Trigger change to update visibility
|
||||
document.getElementById('offerFreeTrial').dispatchEvent(new Event('change'));
|
||||
break;
|
||||
|
||||
case 'retention':
|
||||
// For retention, preselect discount
|
||||
offerRadios.forEach(radio => {
|
||||
if (radio.value === 'discount') radio.checked = true;
|
||||
else radio.checked = false;
|
||||
});
|
||||
// Trigger change to update visibility
|
||||
document.getElementById('offerDiscount').dispatchEvent(new Event('change'));
|
||||
// Set a higher default discount for retention
|
||||
if (discountSlider) {
|
||||
discountSlider.value = 25;
|
||||
discountValue.textContent = '25';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'upgrade':
|
||||
// For upgrade, preselect upgrade promotion
|
||||
offerRadios.forEach(radio => {
|
||||
if (radio.value === 'upgrade') radio.checked = true;
|
||||
else radio.checked = false;
|
||||
});
|
||||
// Trigger change to update visibility
|
||||
document.getElementById('offerUpgrade').dispatchEvent(new Event('change'));
|
||||
break;
|
||||
|
||||
case 'winback':
|
||||
// For winback, preselect bundle offer
|
||||
offerRadios.forEach(radio => {
|
||||
if (radio.value === 'bundle') radio.checked = true;
|
||||
else radio.checked = false;
|
||||
});
|
||||
// Trigger change to update visibility
|
||||
document.getElementById('offerBundle').dispatchEvent(new Event('change'));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle form submission - Save Draft
|
||||
const saveDraftBtn = document.getElementById('saveDraftBtn');
|
||||
if (saveDraftBtn) {
|
||||
saveDraftBtn.addEventListener('click', function () {
|
||||
const campaignName = document.getElementById('campaignName').value;
|
||||
if (!campaignName) {
|
||||
showAlert('danger', 'Please enter a campaign name');
|
||||
return;
|
||||
}
|
||||
|
||||
showAlert('success', 'Campaign draft saved successfully');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle form submission - Launch Campaign
|
||||
const launchCampaignBtn = document.getElementById('launchCampaignBtn');
|
||||
if (launchCampaignBtn) {
|
||||
launchCampaignBtn.addEventListener('click', function () {
|
||||
const campaignName = document.getElementById('campaignName').value;
|
||||
const campaignType = document.getElementById('campaignType').value;
|
||||
const startDate = document.getElementById('startDate').value;
|
||||
const targetAudience = document.getElementById('targetAudience').value;
|
||||
|
||||
// Basic validation
|
||||
if (!campaignName || !campaignType || !startDate || !targetAudience) {
|
||||
showAlert('danger', 'Please fill in all required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
// Here you would normally send data to server
|
||||
// For demo, we'll just show success and close modal
|
||||
showAlert('success', 'Campaign launched successfully!');
|
||||
|
||||
// Close the modal after brief delay
|
||||
setTimeout(() => {
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('buildCampaignModal'));
|
||||
if (modal) modal.hide();
|
||||
}, 1500);
|
||||
});
|
||||
}
|
||||
|
||||
// Update discount value display
|
||||
document.getElementById('discountAmount').addEventListener('input', function () {
|
||||
document.getElementById('discountAmountValue').textContent = this.value + '%';
|
||||
});
|
||||
|
||||
// Toggle discount section based on offer type selection
|
||||
document.querySelectorAll('input[name="offerType"]').forEach(radio => {
|
||||
radio.addEventListener('change', function () {
|
||||
document.getElementById('discountSection').style.display =
|
||||
this.value === 'discount' ? 'block' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Show alert function
|
||||
function showAlert(type, message) {
|
||||
// Check if alert container exists, if not create it
|
||||
let alertContainer = document.querySelector('.campaign-alert-container');
|
||||
if (!alertContainer) {
|
||||
alertContainer = document.createElement('div');
|
||||
alertContainer.className = 'campaign-alert-container position-fixed top-0 end-0 p-3';
|
||||
alertContainer.style.zIndex = '5000';
|
||||
document.body.appendChild(alertContainer);
|
||||
}
|
||||
|
||||
// Create alert element
|
||||
const alertElement = document.createElement('div');
|
||||
alertElement.className = `alert alert-${type} alert-dismissible fade show`;
|
||||
alertElement.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
`;
|
||||
|
||||
// Add alert to container
|
||||
alertContainer.appendChild(alertElement);
|
||||
|
||||
// Auto dismiss after 5 seconds
|
||||
setTimeout(() => {
|
||||
alertElement.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
alertElement.remove();
|
||||
}, 150);
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,420 @@
|
||||
// Cache DOM elements
|
||||
let emailList;
|
||||
let selectAllCheckbox;
|
||||
let refreshButton;
|
||||
let deleteButton;
|
||||
let spamButton;
|
||||
let composeModal;
|
||||
let sendButton;
|
||||
let saveDraftButton;
|
||||
let showMoreButton;
|
||||
let attachmentsContainer;
|
||||
|
||||
// Initialize the application
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
initializeElements();
|
||||
loadEmails();
|
||||
setupEventListeners();
|
||||
setupAttachments();
|
||||
});
|
||||
|
||||
// Initialize DOM elements
|
||||
function initializeElements() {
|
||||
emailList = document.getElementById('js-emails');
|
||||
selectAllCheckbox = document.getElementById('js-msg-select-all');
|
||||
refreshButton = document.querySelector('.js-refresh').closest('button');
|
||||
deleteButton = document.querySelector('.js-delete').closest('button');
|
||||
spamButton = document.querySelector('.js-spam').closest('button');
|
||||
composeModal = document.getElementById('default-example-modal-lg-center');
|
||||
sendButton = composeModal?.querySelector('.btn-primary');
|
||||
saveDraftButton = composeModal?.querySelector('.btn-secondary');
|
||||
showMoreButton = document.querySelector('a.fs-xs.text-secondary');
|
||||
attachmentsContainer = document.getElementById('message-attachments');
|
||||
}
|
||||
|
||||
// Setup event listeners
|
||||
function setupEventListeners() {
|
||||
// Star toggling
|
||||
emailList.addEventListener('click', handleEmailInteractions);
|
||||
|
||||
// Checkbox selection
|
||||
selectAllCheckbox?.addEventListener('change', handleSelectAll);
|
||||
|
||||
// Refresh button
|
||||
refreshButton?.addEventListener('click', handleRefresh);
|
||||
|
||||
// Delete functionality
|
||||
deleteButton?.addEventListener('click', handleDelete);
|
||||
|
||||
// Spam functionality
|
||||
spamButton?.addEventListener('click', handleSpam);
|
||||
|
||||
// Send email
|
||||
sendButton?.addEventListener('click', handleSendEmail);
|
||||
|
||||
// Save draft
|
||||
saveDraftButton?.addEventListener('click', handleSaveDraft);
|
||||
|
||||
// Show more attachments
|
||||
showMoreButton?.addEventListener('click', handleShowMore);
|
||||
}
|
||||
|
||||
// Handle all email interactions (star, click to read)
|
||||
function handleEmailInteractions(e) {
|
||||
// Handle star clicking
|
||||
const starIcon = e.target.closest('.mail-starred');
|
||||
if (starIcon) {
|
||||
const listItem = starIcon.closest('li');
|
||||
const emailId = listItem.querySelector('.form-check-input').id.replace('msg-', '');
|
||||
|
||||
// Toggle starred class
|
||||
listItem.classList.toggle('starred');
|
||||
|
||||
// Update in localStorage
|
||||
const emails = JSON.parse(localStorage.getItem('emails') || '[]');
|
||||
const email = emails.find(email => email.id === emailId);
|
||||
if (email) {
|
||||
email.starred = !email.starred;
|
||||
localStorage.setItem('emails', JSON.stringify(emails));
|
||||
}
|
||||
|
||||
// Prevent further handling
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle clicking on email content to read
|
||||
const mailSender = e.target.closest('.js-email-content');
|
||||
|
||||
if (mailSender) {
|
||||
// Navigate to read page
|
||||
window.location.href = 'systemmail-read.html';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle select all checkbox
|
||||
function handleSelectAll(e) {
|
||||
const checkboxes = emailList.querySelectorAll('.form-check-input');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = e.target.checked;
|
||||
});
|
||||
}
|
||||
|
||||
// Handle refresh button
|
||||
function handleRefresh() {
|
||||
emailList.classList.add('refreshing');
|
||||
|
||||
// Show loading spinner
|
||||
refreshButton.querySelector('i').classList.add('fa-spin');
|
||||
|
||||
// Simulate refresh
|
||||
setTimeout(() => {
|
||||
loadEmails(); // Reload emails
|
||||
emailList.classList.remove('refreshing');
|
||||
refreshButton.querySelector('i').classList.remove('fa-spin');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Handle delete functionality
|
||||
function handleDelete() {
|
||||
const selectedEmails = emailList.querySelectorAll('.form-check-input:checked');
|
||||
if (selectedEmails.length === 0) return;
|
||||
|
||||
// Count for toast message
|
||||
const count = selectedEmails.length;
|
||||
|
||||
// Process each selected email
|
||||
selectedEmails.forEach(checkbox => {
|
||||
const listItem = checkbox.closest('li');
|
||||
|
||||
// Add deleting class for animation
|
||||
listItem.style.overflow = 'hidden';
|
||||
listItem.classList.add('deleting');
|
||||
|
||||
// Remove after animation completes
|
||||
setTimeout(() => {
|
||||
listItem.remove();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Show toast message
|
||||
showToast(`${count} message${count > 1 ? 's' : ''} deleted`);
|
||||
}
|
||||
|
||||
// Handle spam functionality
|
||||
function handleSpam() {
|
||||
const selectedEmails = emailList.querySelectorAll('.form-check-input:checked');
|
||||
if (selectedEmails.length === 0) return;
|
||||
|
||||
// Count for toast message
|
||||
const count = selectedEmails.length;
|
||||
|
||||
// Process each selected email
|
||||
selectedEmails.forEach(checkbox => {
|
||||
const listItem = checkbox.closest('li');
|
||||
|
||||
// Add deleting class for animation
|
||||
listItem.style.overflow = 'hidden';
|
||||
listItem.classList.add('deleting');
|
||||
|
||||
// Remove after animation completes
|
||||
setTimeout(() => {
|
||||
listItem.remove();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Show toast message
|
||||
showToast(`${count} message${count > 1 ? 's' : ''} moved to spam`);
|
||||
}
|
||||
|
||||
// Handle send email
|
||||
function handleSendEmail(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data
|
||||
const recipients = document.getElementById('message-to').value;
|
||||
const subject = document.querySelector('input[placeholder="Subject"]').value;
|
||||
const content = document.getElementById('fake_textarea').innerHTML;
|
||||
|
||||
// Validate
|
||||
if (!recipients || !subject || !content) {
|
||||
alert('Please fill in all required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
// Close modal
|
||||
const modal = bootstrap.Modal.getInstance(composeModal);
|
||||
modal.hide();
|
||||
|
||||
// Show success toast
|
||||
showToast('Email sent successfully!');
|
||||
|
||||
// Reset form completely
|
||||
resetComposeForm(true);
|
||||
}
|
||||
|
||||
// Handle save draft
|
||||
function handleSaveDraft(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Close modal
|
||||
const modal = bootstrap.Modal.getInstance(composeModal);
|
||||
modal.hide();
|
||||
|
||||
// Show success toast with check mark
|
||||
showToast('<i class="fas fa-check me-2"></i> Draft saved', 'success');
|
||||
}
|
||||
|
||||
// Show toast message
|
||||
function showToast(message, type = 'success') {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `mail-toast bg-${type}-500`;
|
||||
toast.innerHTML = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// Show toast
|
||||
setTimeout(() => toast.classList.add('show'), 100);
|
||||
|
||||
// Remove toast
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Reset compose form
|
||||
function resetComposeForm(clearAll = false) {
|
||||
document.getElementById('message-to').value = '';
|
||||
document.getElementById('message-to-cc').value = '';
|
||||
document.querySelector('input[placeholder="Subject"]').value = '';
|
||||
|
||||
// Reset attachments
|
||||
if (attachmentsContainer) {
|
||||
if (clearAll) {
|
||||
// Clear all attachments
|
||||
attachmentsContainer.innerHTML = '';
|
||||
|
||||
// Add back the "show more" link
|
||||
const showMoreLink = document.createElement('a');
|
||||
showMoreLink.href = '#';
|
||||
showMoreLink.className = 'fs-xs text-secondary';
|
||||
showMoreLink.textContent = 'show 3 more';
|
||||
showMoreLink.addEventListener('click', handleShowMore);
|
||||
attachmentsContainer.appendChild(showMoreLink);
|
||||
|
||||
// Hide the container if it should be empty
|
||||
attachmentsContainer.classList.add('if-empty-display-none');
|
||||
} else {
|
||||
// Keep only the first two default attachments
|
||||
const attachments = attachmentsContainer.querySelectorAll('.alert');
|
||||
attachments.forEach((attachment, index) => {
|
||||
if (index > 1) {
|
||||
attachment.remove();
|
||||
} else {
|
||||
attachment.classList.remove('hidden-attachment');
|
||||
}
|
||||
});
|
||||
|
||||
// Reset show more link
|
||||
if (showMoreButton) {
|
||||
showMoreButton.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset textarea with signature
|
||||
document.getElementById('fake_textarea').innerHTML = `
|
||||
<p><br></p>
|
||||
<p><br></p>
|
||||
<p>Best regards,</p>
|
||||
<div class="d-flex d-column align-items-start mb-3 gap-2">
|
||||
<img src="img/demo/avatars/avatar-admin.png" alt="SmartAdmin WebApp" class="me-3 mt-1 rounded-circle width-2">
|
||||
<div class="border-left pl-3">
|
||||
<span class="fw-500 fs-lg d-block l-h-n">Sunny A.</span>
|
||||
<span class="fw-400 fs-nano d-block l-h-n mb-1">Software Engineer</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted fs-nano">
|
||||
PRIVATE AND CONFIDENTIAL. This e-mail, its contents and attachments are private and confidential and is intended for the recipient only. Any disclosure, copying or unauthorized use of such information is prohibited. If you receive this message in error, please notify us immediately and delete the original and any copies and attachments.
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Handle show more attachments
|
||||
function handleShowMore(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const hiddenAttachments = document.querySelectorAll('.hidden-attachment');
|
||||
|
||||
// If there are hidden attachments, show them
|
||||
if (hiddenAttachments.length > 0) {
|
||||
hiddenAttachments.forEach(attachment => {
|
||||
attachment.classList.remove('hidden-attachment');
|
||||
});
|
||||
e.target.textContent = 'hide attachments';
|
||||
}
|
||||
// Otherwise, hide all but the first two attachments
|
||||
else {
|
||||
const allAttachments = attachmentsContainer.querySelectorAll('.alert');
|
||||
allAttachments.forEach((attachment, index) => {
|
||||
if (index > 1) {
|
||||
attachment.classList.add('hidden-attachment');
|
||||
}
|
||||
});
|
||||
|
||||
// Update show more text
|
||||
updateShowMoreText();
|
||||
}
|
||||
}
|
||||
|
||||
// Setup attachments in the compose modal
|
||||
function setupAttachments() {
|
||||
if (!attachmentsContainer) return;
|
||||
|
||||
// Create additional hidden attachments
|
||||
const hiddenAttachments = [
|
||||
{ name: 'report.docx', type: 'primary' },
|
||||
{ name: 'presentation.pptx', type: 'primary' },
|
||||
{ name: 'data.xlsx', type: 'primary' }
|
||||
];
|
||||
|
||||
// Add hidden attachments
|
||||
hiddenAttachments.forEach((attachment, index) => {
|
||||
const attachmentEl = document.createElement('div');
|
||||
attachmentEl.className = `alert m-0 p-0 badge bg-${attachment.type}-50 border-${attachment.type} ps-2 ${index > 0 ? 'hidden-attachment' : ''}`;
|
||||
attachmentEl.innerHTML = `${attachment.name} <button data-bs-dismiss="alert" class="btn btn-icon btn-xs ms-1 rounded-0 border border-${attachment.type} border-top-0 border-bottom-0 border-end-0" type="button">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>`;
|
||||
|
||||
// Insert before the "show more" link
|
||||
attachmentsContainer.insertBefore(attachmentEl, showMoreButton);
|
||||
});
|
||||
|
||||
// Update show more link text
|
||||
updateShowMoreText();
|
||||
}
|
||||
|
||||
// Update "show more" text based on hidden attachments
|
||||
function updateShowMoreText() {
|
||||
if (!showMoreButton) return;
|
||||
|
||||
const hiddenAttachments = document.querySelectorAll('.hidden-attachment');
|
||||
if (hiddenAttachments.length > 0) {
|
||||
showMoreButton.textContent = `show ${hiddenAttachments.length} more`;
|
||||
showMoreButton.style.display = '';
|
||||
} else {
|
||||
showMoreButton.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Load emails from JSON
|
||||
function loadEmails() {
|
||||
fetch('./json/MOCK_MAIL.json')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok ' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (!emailList) {
|
||||
console.error('Email list container not found!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Store in localStorage for star functionality
|
||||
localStorage.setItem('emails', JSON.stringify(data));
|
||||
|
||||
// Clear existing content
|
||||
emailList.innerHTML = '';
|
||||
|
||||
// Iterate over each email
|
||||
data.forEach((email, index) => {
|
||||
const li = document.createElement('li');
|
||||
li.className = `${email.read ? '' : 'unread'} ${email.starred ? 'starred' : ''}`.trim();
|
||||
li.style.cursor = 'pointer';
|
||||
|
||||
const checkboxId = `msg-${email.id}`;
|
||||
const time = new Date(email.timestamp).toLocaleTimeString([], {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
|
||||
li.innerHTML = `
|
||||
<div class="d-flex align-items-center px-3 px-sm-4 py-2 py-lg-0 height-4 height-mobile-auto gap-2">
|
||||
<div class="form-check form-check-hitbox me-2 order-1 mb-0">
|
||||
<input type="checkbox" class="form-check-input" id="${checkboxId}">
|
||||
<label class="form-check-label" for="${checkboxId}"></label>
|
||||
</div>
|
||||
<div class="d-flex align-self-end align-self-lg-center order-3 order-lg-2 me-lg-3 me-0 mb-1 mb-lg-0 flex-shrink-0">
|
||||
<svg class="mail-starred sa-icon">
|
||||
<use href="icons/sprite.svg#star"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="js-email-content d-flex flex-column flex-lg-row flex-grow-1 align-items-stretch order-2 order-lg-3" style="min-width: 0;">
|
||||
<div class="mail-sender flex-shrink-0 align-self-start align-self-lg-center width-sm width-max-sm text-truncate">${email.sender}</div>
|
||||
<div class="d-flex flex-column flex-lg-row flex-grow-1 w-100 overflow-hidden">
|
||||
<div class="mail-subject flex-shrink-0 align-self-start align-self-lg-center me-2 text-truncate width-max-100">${email.subject}</div>
|
||||
<div class="d-flex align-items-center flex-grow-1 w-100 overflow-hidden">
|
||||
<div class="mail-body d-block text-truncate w-100 pe-lg-5 text-muted">
|
||||
<span class="hidden-sm">-</span> ${email.bodyPreview}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fs-sm text-muted ms-auto hide-on-hover-parent order-4 position-on-mobile-absolute pos-top pos-right pt-1 pt-lg-0 mt-2 me-3 me-sm-4 mt-lg-0 me-lg-0 flex-shrink-0">${time}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Prevent checkbox from triggering navigation
|
||||
const checkbox = li.querySelector('.form-check-input');
|
||||
checkbox.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
emailList.appendChild(li);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading emails:', error));
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Table Style Generator
|
||||
* Handles real-time style updates for Bootstrap tables
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Get elements
|
||||
const tableStyleForm = document.getElementById('tableStyleForm');
|
||||
const stylePreviewTable = document.getElementById('stylePreviewTable');
|
||||
const generatedClasses = document.getElementById('generatedClasses');
|
||||
const codeClasses = document.getElementById('codeClasses');
|
||||
const copyStyleBtn = document.getElementById('copyStyleBtn');
|
||||
const resetStylesBtn = document.getElementById('resetStylesBtn');
|
||||
const showRowClasses = document.getElementById('showRowClasses');
|
||||
const collapseColumnsBtn = document.getElementById('collapseColumnsBtn');
|
||||
const expandColumnsBtn = document.getElementById('expandColumnsBtn');
|
||||
const tableContainer = document.getElementById('tableContainer');
|
||||
const headerAccent = document.getElementById('headerAccent');
|
||||
const tableHeader = stylePreviewTable.querySelector('thead tr');
|
||||
const styleBordered = document.getElementById('styleBordered');
|
||||
const styleBorderless = document.getElementById('styleBorderless');
|
||||
|
||||
// Handle conflicts between bordered and borderless
|
||||
styleBordered.addEventListener('change', function() {
|
||||
if (this.checked && styleBorderless.checked) {
|
||||
styleBorderless.checked = false;
|
||||
}
|
||||
updateTableStyle();
|
||||
});
|
||||
|
||||
styleBorderless.addEventListener('change', function() {
|
||||
if (this.checked && styleBordered.checked) {
|
||||
styleBordered.checked = false;
|
||||
}
|
||||
updateTableStyle();
|
||||
});
|
||||
|
||||
// Table rows with contextual classes
|
||||
const tableRows = stylePreviewTable.querySelectorAll('tbody tr');
|
||||
const contextualClasses = ['table-primary', 'table-secondary', 'table-success', 'table-danger', 'table-warning', 'table-info'];
|
||||
const rowClasses = Array.from(tableRows).map((row, index) => {
|
||||
// Assign a specific contextual class based on index
|
||||
return contextualClasses[index % contextualClasses.length];
|
||||
});
|
||||
|
||||
// Function to update the table style based on form values
|
||||
function updateTableStyle() {
|
||||
// Start with base class
|
||||
const classes = ['table'];
|
||||
let tableWrapperClass = '';
|
||||
|
||||
// Get theme (table-dark, table-light)
|
||||
const theme = document.querySelector('input[name="tableTheme"]:checked').value;
|
||||
if (theme) {
|
||||
classes.push(theme);
|
||||
}
|
||||
|
||||
// Get styles (striped, hover, bordered, borderless)
|
||||
const styleCheckboxes = document.querySelectorAll('input[name="tableStyle"]:checked');
|
||||
styleCheckboxes.forEach(checkbox => {
|
||||
if (checkbox.value === 'table-responsive') {
|
||||
tableWrapperClass = 'table-responsive';
|
||||
} else {
|
||||
classes.push(checkbox.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Get size (sm, nano)
|
||||
const size = document.querySelector('input[name="tableSize"]:checked').value;
|
||||
if (size) {
|
||||
classes.push(size);
|
||||
}
|
||||
|
||||
// Get accent color for the whole table
|
||||
const accent = document.getElementById('tableAccent').value;
|
||||
if (accent) {
|
||||
classes.push(accent);
|
||||
}
|
||||
|
||||
// Apply header accent using classes only, not inline styles
|
||||
const headerAccentValue = headerAccent.value;
|
||||
// First remove any existing background classes
|
||||
tableHeader.classList.remove(
|
||||
'bg-primary', 'bg-secondary', 'bg-success',
|
||||
'bg-danger', 'bg-warning', 'bg-info',
|
||||
'bg-dark', 'bg-light', 'text-white'
|
||||
);
|
||||
// Add the new one if selected
|
||||
if (headerAccentValue) {
|
||||
tableHeader.classList.add(headerAccentValue);
|
||||
// If we're using a dark background, make the text white
|
||||
if (['bg-primary', 'bg-secondary', 'bg-success', 'bg-danger', 'bg-dark'].includes(headerAccentValue)) {
|
||||
tableHeader.classList.add('text-white');
|
||||
}
|
||||
}
|
||||
|
||||
// Get caption position (now using radio buttons)
|
||||
const captionPosition = document.querySelector('input[name="captionPosition"]:checked').value;
|
||||
if (captionPosition) {
|
||||
stylePreviewTable.classList.remove('caption-top', 'caption-bottom');
|
||||
stylePreviewTable.classList.add(captionPosition);
|
||||
} else {
|
||||
stylePreviewTable.classList.remove('caption-top', 'caption-bottom');
|
||||
}
|
||||
|
||||
// Check if we should show row contextual classes
|
||||
if (showRowClasses.checked) {
|
||||
tableRows.forEach((row, index) => {
|
||||
// First remove any existing contextual classes
|
||||
row.classList.remove('table-primary', 'table-secondary', 'table-success',
|
||||
'table-danger', 'table-warning', 'table-info');
|
||||
// Add the contextual class based on index
|
||||
if (index < rowClasses.length) {
|
||||
row.classList.add(rowClasses[index]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Remove all contextual classes from rows
|
||||
tableRows.forEach(row => {
|
||||
row.classList.remove('table-primary', 'table-secondary', 'table-success',
|
||||
'table-danger', 'table-warning', 'table-info');
|
||||
});
|
||||
}
|
||||
|
||||
// Update the table classes
|
||||
stylePreviewTable.className = classes.join(' ');
|
||||
|
||||
// Update wrapper for responsiveness
|
||||
if (tableWrapperClass && !tableContainer.classList.contains(tableWrapperClass)) {
|
||||
tableContainer.className = tableWrapperClass;
|
||||
} else if (!tableWrapperClass) {
|
||||
tableContainer.className = '';
|
||||
}
|
||||
|
||||
// Update the generated classes display and note about additional header styling if applied
|
||||
const displayClasses = classes.join(' ');
|
||||
let displayText = displayClasses;
|
||||
|
||||
// Add note about header styling if applied
|
||||
if (headerAccentValue) {
|
||||
displayText += `\n\n<!-- Additional header styling -->\n<thead>\n <tr class="${headerAccentValue}${['bg-primary', 'bg-secondary', 'bg-success', 'bg-danger', 'bg-dark'].includes(headerAccentValue) ? ' text-white' : ''}">\n <!-- your header cells here -->\n </tr>\n</thead>`;
|
||||
}
|
||||
|
||||
// Add note about contextual row classes if enabled
|
||||
if (showRowClasses.checked) {
|
||||
displayText += `\n\n<!-- Row contextual classes -->\n<tbody>\n <tr class="table-primary">...</tr>\n <tr class="table-secondary">...</tr>\n <tr class="table-success">...</tr>\n <tr class="table-danger">...</tr>\n <tr class="table-warning">...</tr>\n <tr class="table-info">...</tr>\n</tbody>`;
|
||||
}
|
||||
|
||||
generatedClasses.textContent = displayText;
|
||||
codeClasses.textContent = displayClasses;
|
||||
}
|
||||
|
||||
// Add event listeners to all form controls
|
||||
tableStyleForm.addEventListener('change', updateTableStyle);
|
||||
|
||||
// Copy button functionality
|
||||
copyStyleBtn.addEventListener('click', function() {
|
||||
// Create a textarea element to copy from
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = generatedClasses.textContent;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
// Execute copy command
|
||||
document.execCommand('copy');
|
||||
|
||||
// Show success feedback
|
||||
const originalText = copyStyleBtn.innerHTML;
|
||||
copyStyleBtn.innerHTML = '<svg class="sa-icon me-1"><use href="icons/sprite.svg#check"/></svg> Copied!';
|
||||
|
||||
// Reset button text after delay
|
||||
setTimeout(() => {
|
||||
copyStyleBtn.innerHTML = originalText;
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Could not copy text: ', err);
|
||||
}
|
||||
|
||||
// Remove the temporary textarea
|
||||
document.body.removeChild(textarea);
|
||||
});
|
||||
|
||||
// Reset button functionality
|
||||
resetStylesBtn.addEventListener('click', function() {
|
||||
// Reset form controls
|
||||
document.getElementById('themeDefault').checked = true;
|
||||
document.getElementById('sizeDefault').checked = true;
|
||||
document.getElementById('tableAccent').value = '';
|
||||
document.getElementById('headerAccent').value = '';
|
||||
document.getElementById('captionDefault').checked = true;
|
||||
document.getElementById('showRowClasses').checked = false;
|
||||
document.getElementById('expandColumnsBtn').checked = true;
|
||||
|
||||
// Reset checkboxes
|
||||
const checkboxes = document.querySelectorAll('input[name="tableStyle"]');
|
||||
checkboxes.forEach(checkbox => {
|
||||
if (checkbox.id === 'styleResponsive') {
|
||||
checkbox.checked = true;
|
||||
} else {
|
||||
checkbox.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Restore columns to default view
|
||||
expandColumns();
|
||||
|
||||
// Update table style
|
||||
updateTableStyle();
|
||||
});
|
||||
|
||||
// Function to hide columns
|
||||
function collapseColumns() {
|
||||
// Hide columns with d-md-table-cell and d-lg-table-cell classes
|
||||
const mdCells = document.querySelectorAll('.d-md-table-cell');
|
||||
mdCells.forEach(cell => {
|
||||
cell.classList.add('d-none');
|
||||
cell.classList.remove('d-md-table-cell');
|
||||
});
|
||||
|
||||
const lgCells = document.querySelectorAll('.d-lg-table-cell');
|
||||
lgCells.forEach(cell => {
|
||||
cell.classList.add('d-none');
|
||||
cell.classList.remove('d-lg-table-cell');
|
||||
});
|
||||
}
|
||||
|
||||
// Function to show all columns
|
||||
function expandColumns() {
|
||||
// Restore hidden columns
|
||||
const hiddenCells = document.querySelectorAll('td.d-none, th.d-none');
|
||||
hiddenCells.forEach(cell => {
|
||||
if (cell.textContent.includes('Address') || cell.textContent.includes('City') ||
|
||||
cell.parentElement.cells[4] === cell || cell.parentElement.cells[5] === cell) {
|
||||
cell.classList.remove('d-none');
|
||||
cell.classList.add('d-md-table-cell');
|
||||
} else if (cell.textContent.includes('State') || cell.textContent.includes('Country') ||
|
||||
cell.parentElement.cells[6] === cell || cell.parentElement.cells[7] === cell) {
|
||||
cell.classList.remove('d-none');
|
||||
cell.classList.add('d-lg-table-cell');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle columns for responsive preview - updated for radio buttons
|
||||
collapseColumnsBtn.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
collapseColumns();
|
||||
}
|
||||
});
|
||||
|
||||
expandColumnsBtn.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
expandColumns();
|
||||
}
|
||||
});
|
||||
|
||||
// Hide row contextual classes by default
|
||||
if (!showRowClasses.checked) {
|
||||
tableRows.forEach(row => {
|
||||
row.classList.remove('table-primary', 'table-secondary', 'table-success',
|
||||
'table-danger', 'table-warning', 'table-info');
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize the table with default styles
|
||||
updateTableStyle();
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
(function() {
|
||||
// Function to create the tree view
|
||||
function createTreeView(data, container, level) {
|
||||
var treeItem = document.createElement('div');
|
||||
treeItem.className = 'tree-item ' + (data.type === 'directory' ? 'directory-item' : 'file-item');
|
||||
|
||||
// Auto-expand directories up to level 4
|
||||
var isExpanded = data.type === 'directory' && level <= 2;
|
||||
if (isExpanded) {
|
||||
treeItem.className += ' expanded';
|
||||
} else if (data.type === 'directory') {
|
||||
treeItem.className += ' collapsed';
|
||||
}
|
||||
|
||||
var itemContent = document.createElement('div');
|
||||
itemContent.className = 'tree-item-content';
|
||||
|
||||
var toggleIcon = document.createElement('span');
|
||||
toggleIcon.className = 'toggle-icon';
|
||||
itemContent.appendChild(toggleIcon);
|
||||
|
||||
var icon = document.createElement('span');
|
||||
icon.className = 'tree-item-icon ' + (data.type === 'directory' ? 'directory-icon' : 'file-icon');
|
||||
icon.innerHTML = data.type === 'directory' ? '📁' : '📄';
|
||||
itemContent.appendChild(icon);
|
||||
|
||||
var name = document.createElement('span');
|
||||
name.className = 'tree-item-name';
|
||||
name.textContent = data.name;
|
||||
itemContent.appendChild(name);
|
||||
|
||||
if (data.path) {
|
||||
var path = document.createElement('span');
|
||||
path.className = 'path';
|
||||
path.textContent = data.path;
|
||||
itemContent.appendChild(path);
|
||||
}
|
||||
|
||||
treeItem.appendChild(itemContent);
|
||||
|
||||
if (data.type === 'directory' && data.children && data.children.length > 0) {
|
||||
var childrenContainer = document.createElement('div');
|
||||
childrenContainer.className = 'tree-item-children';
|
||||
|
||||
// Sort children: directories first, then files, both alphabetically
|
||||
var sortedChildren = data.children.slice();
|
||||
sortedChildren.sort(function(a, b) {
|
||||
if (a.type === b.type) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
return a.type === 'directory' ? -1 : 1;
|
||||
});
|
||||
|
||||
for (var i = 0; i < sortedChildren.length; i++) {
|
||||
createTreeView(sortedChildren[i], childrenContainer, level + 1);
|
||||
}
|
||||
|
||||
treeItem.appendChild(childrenContainer);
|
||||
|
||||
// Add click event to toggle directory
|
||||
itemContent.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
if (treeItem.classList.contains('expanded')) {
|
||||
treeItem.classList.remove('expanded');
|
||||
treeItem.classList.add('collapsed');
|
||||
} else {
|
||||
treeItem.classList.remove('collapsed');
|
||||
treeItem.classList.add('expanded');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
container.appendChild(treeItem);
|
||||
}
|
||||
|
||||
// Function to load directory data using AJAX
|
||||
function loadDirectoryData() {
|
||||
var treeViewContainer = document.getElementById('tree-view');
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var directoryData = JSON.parse(xhr.responseText);
|
||||
treeViewContainer.innerHTML = '';
|
||||
createTreeView(directoryData, treeViewContainer, 1);
|
||||
} catch (e) {
|
||||
treeViewContainer.innerHTML = '<div class="error">Error parsing JSON data: ' + e.message + '</div>';
|
||||
}
|
||||
} else {
|
||||
treeViewContainer.innerHTML = '<div class="error">Failed to load directory data. Status: ' + xhr.status + '</div>';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.open('GET', 'json/directory-tree.json', true);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
// Load the directory data when the page loads
|
||||
window.addEventListener('load', loadDirectoryData);
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Use direct filtering implementation instead of SmartFilter
|
||||
implementDirectFiltering();
|
||||
|
||||
// Function to remove classes with a specific prefix
|
||||
function removeClassPrefix(elements, prefix) {
|
||||
elements.forEach(element => {
|
||||
const classes = Array.from(element.classList);
|
||||
classes.forEach(cls => {
|
||||
if (cls.startsWith(prefix)) {
|
||||
element.classList.remove(cls);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Select all radio inputs with name="contactview"
|
||||
const radioButtons = document.querySelectorAll('input[type="radio"][name="contactview"]');
|
||||
|
||||
radioButtons.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
const jsContacts = document.querySelector('#js-contacts');
|
||||
const cards = jsContacts.querySelectorAll('.card');
|
||||
const colXlElements = jsContacts.querySelectorAll('[class*="col-xl-"]');
|
||||
const expandButtons = jsContacts.querySelectorAll('.js-expand-btn');
|
||||
const doubleCardBodies = jsContacts.querySelectorAll('.card-body + .card-body');
|
||||
|
||||
if (this.value === 'grid') {
|
||||
// Handle cards
|
||||
removeClassPrefix(cards, 'mb-');
|
||||
cards.forEach(card => card.classList.add('mb-g'));
|
||||
|
||||
// Handle col-xl classes
|
||||
removeClassPrefix(colXlElements, 'col-xl-');
|
||||
colXlElements.forEach(el => el.classList.add('col-xl-4'));
|
||||
|
||||
// Handle expand buttons
|
||||
expandButtons.forEach(btn => btn.classList.add('d-none'));
|
||||
|
||||
// Handle double card bodies
|
||||
doubleCardBodies.forEach(body => body.classList.add('show'));
|
||||
}
|
||||
else if (this.value === 'table') {
|
||||
// Handle cards
|
||||
removeClassPrefix(cards, 'mb-');
|
||||
cards.forEach(card => card.classList.add('mb-1'));
|
||||
|
||||
// Handle col-xl classes
|
||||
removeClassPrefix(colXlElements, 'col-xl-');
|
||||
colXlElements.forEach(el => el.classList.add('col-xl-12'));
|
||||
|
||||
// Handle expand buttons
|
||||
expandButtons.forEach(btn => btn.classList.remove('d-none'));
|
||||
|
||||
// Handle double card bodies
|
||||
doubleCardBodies.forEach(body => body.classList.remove('show'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Direct filtering implementation that doesn't rely on SmartFilter
|
||||
function implementDirectFiltering() {
|
||||
const filterInput = document.getElementById('js-filter-contacts');
|
||||
const clearBtn = document.getElementById('js-clear-filter');
|
||||
const counterEl = document.getElementById('filter-result-counter');
|
||||
|
||||
// Input filtering
|
||||
filterInput.addEventListener('input', function() {
|
||||
const filterValue = this.value.toLowerCase();
|
||||
const cards = document.querySelectorAll('#js-contacts .card');
|
||||
const columns = document.querySelectorAll('#js-contacts [class*="col-xl-"]');
|
||||
let visibleCount = 0;
|
||||
|
||||
// First, hide all columns
|
||||
columns.forEach(col => {
|
||||
col.style.display = 'none';
|
||||
});
|
||||
|
||||
// Filter cards based on their data-filter-tags attribute
|
||||
cards.forEach(card => {
|
||||
const filterTags = card.getAttribute('data-filter-tags') || '';
|
||||
const parentColumn = card.closest('[class*="col-xl-"]');
|
||||
|
||||
if (filterValue === '' || filterTags.toLowerCase().includes(filterValue)) {
|
||||
if (parentColumn) {
|
||||
parentColumn.style.display = ''; // Show the column
|
||||
}
|
||||
visibleCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// Update UI
|
||||
if (filterValue) {
|
||||
counterEl.textContent = `Showing ${visibleCount} of ${cards.length} contacts`;
|
||||
filterInput.classList.add('border-primary');
|
||||
clearBtn.classList.remove('d-none');
|
||||
} else {
|
||||
counterEl.textContent = '';
|
||||
filterInput.classList.remove('border-primary');
|
||||
clearBtn.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
// Clear button functionality
|
||||
clearBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
filterInput.value = '';
|
||||
|
||||
// Show all columns
|
||||
const columns = document.querySelectorAll('#js-contacts [class*="col-xl-"]');
|
||||
columns.forEach(col => {
|
||||
col.style.display = '';
|
||||
});
|
||||
|
||||
// Reset UI
|
||||
counterEl.textContent = '';
|
||||
filterInput.classList.remove('border-primary');
|
||||
clearBtn.classList.add('d-none');
|
||||
});
|
||||
|
||||
// Set up keyboard events for convenience
|
||||
filterInput.addEventListener('keydown', function(e) {
|
||||
// Clear on Escape key
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
this.value = '';
|
||||
clearBtn.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,241 @@
|
||||
// Show/hide display type section based on visibility mode
|
||||
document.querySelectorAll('input[name="visibilityMode"]').forEach(radio => {
|
||||
radio.addEventListener('change', function () {
|
||||
const displayTypeSection = document.getElementById('displayTypeSection');
|
||||
displayTypeSection.style.display = this.value === 'visible' ? 'block' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Handle "All Breakpoints" checkbox logic
|
||||
const allCheckbox = document.getElementById('all');
|
||||
const breakpointCheckboxes = document.querySelectorAll('.breakpoint-checkbox');
|
||||
|
||||
allCheckbox.addEventListener('change', function () {
|
||||
const isChecked = this.checked;
|
||||
breakpointCheckboxes.forEach(cb => {
|
||||
cb.checked = isChecked;
|
||||
});
|
||||
});
|
||||
|
||||
breakpointCheckboxes.forEach(cb => {
|
||||
cb.addEventListener('change', function () {
|
||||
const allChecked = Array.from(breakpointCheckboxes).every(cb => cb.checked);
|
||||
allCheckbox.checked = allChecked;
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('generateBtn').addEventListener('click', function () {
|
||||
// Get visibility mode
|
||||
const visibilityMode = document.querySelector('input[name="visibilityMode"]:checked').value;
|
||||
|
||||
// Get selected breakpoints
|
||||
const selectedBreakpoints = Array.from(document.querySelectorAll('.breakpoint-checkbox'))
|
||||
.filter(cb => cb.checked)
|
||||
.map(cb => cb.value);
|
||||
|
||||
// Get selected display type (for visible mode, default to 'block' for hidden mode visibility)
|
||||
const displayType = visibilityMode === 'visible' ?
|
||||
document.querySelector('input[name="displayType"]:checked').value : 'block';
|
||||
|
||||
// Validate selections
|
||||
if (selectedBreakpoints.length === 0) {
|
||||
document.getElementById('classOutput').textContent = 'Please select at least one breakpoint.';
|
||||
document.getElementById('cssOutput').textContent = '/* No CSS generated */';
|
||||
document.getElementById('testDiv').className = 'd-none'; // Hide test div
|
||||
return;
|
||||
}
|
||||
|
||||
// Define all breakpoints
|
||||
const allBreakpoints = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
|
||||
const mediaQueries = {
|
||||
'sm': '@media (min-width: 576px)',
|
||||
'md': '@media (min-width: 768px)',
|
||||
'lg': '@media (min-width: 992px)',
|
||||
'xl': '@media (min-width: 1200px)',
|
||||
'xxl': '@media (min-width: 1400px)'
|
||||
};
|
||||
|
||||
// Determine unselected breakpoints
|
||||
const unselectedBreakpoints = allBreakpoints.filter(bp => !selectedBreakpoints.includes(bp));
|
||||
|
||||
// Generate BS5 classes based on visibility mode
|
||||
const classes = [];
|
||||
const cssLines = [];
|
||||
const selector = '.my-element'; // Example selector
|
||||
|
||||
if (visibilityMode === 'hidden') {
|
||||
// Default: visible (using displayType)
|
||||
classes.push(`d-${displayType}`);
|
||||
cssLines.push(`${selector} { display: ${displayType} !important; }`);
|
||||
|
||||
// Hide on selected breakpoints
|
||||
selectedBreakpoints.forEach(bp => {
|
||||
if (bp === 'xs') {
|
||||
classes[0] = 'd-none'; // Override default for xs
|
||||
classes.push('d-sm-block');
|
||||
cssLines[0] = `${selector} { display: none !important; }`;
|
||||
cssLines.push(`${mediaQueries['sm']} {`);
|
||||
cssLines.push(` ${selector} { display: ${displayType} !important; }`);
|
||||
cssLines.push('}');
|
||||
} else {
|
||||
classes.push(`d-${bp}-none`);
|
||||
cssLines.push(`${mediaQueries[bp]} {`);
|
||||
cssLines.push(` ${selector} { display: none !important; }`);
|
||||
cssLines.push('}');
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure visibility on unselected breakpoints after hiding
|
||||
unselectedBreakpoints.forEach(bp => {
|
||||
if (bp !== 'xs' && selectedBreakpoints.includes(allBreakpoints[allBreakpoints.indexOf(bp) - 1])) {
|
||||
classes.push(`d-${bp}-${displayType}`);
|
||||
cssLines.push(`${mediaQueries[bp]} {`);
|
||||
cssLines.push(` ${selector} { display: ${displayType} !important; }`);
|
||||
cssLines.push('}');
|
||||
}
|
||||
});
|
||||
} else { // visibilityMode === 'visible'
|
||||
// Default: hidden
|
||||
classes.push('d-none');
|
||||
cssLines.push(`${selector} { display: none !important; }`);
|
||||
|
||||
// Show on selected breakpoints
|
||||
selectedBreakpoints.forEach(bp => {
|
||||
const prefix = bp === 'xs' ? 'd' : `d-${bp}`;
|
||||
classes.push(`${prefix}-${displayType}`);
|
||||
if (bp !== 'xs') {
|
||||
cssLines.push(`${mediaQueries[bp]} {`);
|
||||
cssLines.push(` ${selector} { display: ${displayType} !important; }`);
|
||||
cssLines.push('}');
|
||||
} else {
|
||||
cssLines[0] = `${selector} { display: ${displayType} !important; }`;
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure hidden on unselected breakpoints after showing
|
||||
unselectedBreakpoints.forEach(bp => {
|
||||
if (bp !== 'xs' && selectedBreakpoints.includes(allBreakpoints[allBreakpoints.indexOf(bp) - 1])) {
|
||||
classes.push(`d-${bp}-none`);
|
||||
cssLines.push(`${mediaQueries[bp]} {`);
|
||||
cssLines.push(` ${selector} { display: none !important; }`);
|
||||
cssLines.push('}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Apply classes to the test div
|
||||
const testDiv = document.getElementById('testDiv');
|
||||
testDiv.className = 'd-none'; // Reset classes
|
||||
testDiv.classList.add(...classes);
|
||||
|
||||
// Output the classes
|
||||
document.getElementById('classOutput').textContent = classes.join(' ');
|
||||
|
||||
// Output the CSS equivalent
|
||||
document.getElementById('cssOutput').textContent = cssLines.join('\n');
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Additional script to enhance the page functionality
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Track current breakpoint
|
||||
const currentBreakpointEl = document.getElementById('currentBreakpoint');
|
||||
const breakpointIndicators = document.querySelectorAll('.breakpoint-indicator');
|
||||
|
||||
// Copy buttons functionality
|
||||
const copyClassesBtn = document.getElementById('copyClasses');
|
||||
const copyCSSBtn = document.getElementById('copyCSS');
|
||||
const classOutput = document.getElementById('classOutput');
|
||||
const cssOutput = document.getElementById('cssOutput');
|
||||
|
||||
// Copy functionality
|
||||
copyClassesBtn.addEventListener('click', function () {
|
||||
const text = classOutput.textContent;
|
||||
copyToClipboard(text, this);
|
||||
});
|
||||
|
||||
copyCSSBtn.addEventListener('click', function () {
|
||||
const text = cssOutput.textContent;
|
||||
copyToClipboard(text, this);
|
||||
});
|
||||
|
||||
function copyToClipboard(text, button) {
|
||||
const originalText = button.innerHTML;
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
button.innerHTML = '<i class="fal fa-check me-1"></i> Copied!';
|
||||
setTimeout(function () {
|
||||
button.innerHTML = originalText;
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
// Show/hide display type section based on visibility mode
|
||||
const visibilityRadios = document.querySelectorAll('input[name="visibilityMode"]');
|
||||
const displayTypeSection = document.getElementById('displayTypeSection');
|
||||
|
||||
visibilityRadios.forEach(radio => {
|
||||
radio.addEventListener('change', function () {
|
||||
if (this.value === 'visible') {
|
||||
displayTypeSection.style.display = 'block';
|
||||
} else {
|
||||
displayTypeSection.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Detect current breakpoint
|
||||
function updateCurrentBreakpoint() {
|
||||
let current = 'xs';
|
||||
const width = window.innerWidth;
|
||||
|
||||
if (width >= 1400) current = 'xxl';
|
||||
else if (width >= 1200) current = 'xl';
|
||||
else if (width >= 992) current = 'lg';
|
||||
else if (width >= 768) current = 'md';
|
||||
else if (width >= 576) current = 'sm';
|
||||
|
||||
currentBreakpointEl.textContent = current;
|
||||
|
||||
// Update indicators
|
||||
breakpointIndicators.forEach(indicator => {
|
||||
const bp = indicator.getAttribute('data-bp');
|
||||
if ((bp === 'xs' && width < 576) ||
|
||||
(bp === 'sm' && width >= 576) ||
|
||||
(bp === 'md' && width >= 768) ||
|
||||
(bp === 'lg' && width >= 992) ||
|
||||
(bp === 'xl' && width >= 1200) ||
|
||||
(bp === 'xxl' && width >= 1400)) {
|
||||
indicator.classList.add('active');
|
||||
} else {
|
||||
indicator.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial update and listen for resize
|
||||
updateCurrentBreakpoint();
|
||||
window.addEventListener('resize', updateCurrentBreakpoint);
|
||||
|
||||
// Select all checkboxes when "All Breakpoints" is checked
|
||||
const allCheckbox = document.getElementById('all');
|
||||
const breakpointCheckboxes = document.querySelectorAll('.breakpoint-checkbox');
|
||||
|
||||
allCheckbox.addEventListener('change', function () {
|
||||
breakpointCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = this.checked;
|
||||
checkbox.disabled = this.checked;
|
||||
});
|
||||
});
|
||||
|
||||
// When a class is generated, show the copy buttons
|
||||
const generateBtn = document.getElementById('generateBtn');
|
||||
generateBtn.addEventListener('click', function () {
|
||||
setTimeout(() => {
|
||||
if (classOutput.textContent !== 'Select options and click "Generate Classes" to see the result.') {
|
||||
copyClassesBtn.classList.remove('d-none');
|
||||
copyCSSBtn.classList.remove('d-none');
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user