fix: implement Blazor-native login form to properly update authentication state
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:
2026-07-03 13:03:53 +09:00
parent 041d3cae96
commit 40cffb3beb
326 changed files with 327714 additions and 47 deletions
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');
});
+5
View File
@@ -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;">&nbsp;</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> &#x2714;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> &#x2714;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",
"AIready by design: prewritten prompt instructions included",
"An advanced, jQueryfree Bootstrap 5 Admin Dashboard UI",
"Built for the next generation of enterprise web applications",
"Clean, scalable, and engineered for futureforward 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();
+185
View File
@@ -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();
});
+104
View File
@@ -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);
});
});