frontend migration to jinja, alpine.js
This commit is contained in:
@@ -32,8 +32,9 @@ function adminComponents() {
|
||||
{ id: 'cards', name: 'Cards', icon: 'collection' },
|
||||
{ id: 'badges', name: 'Badges', icon: 'tag' },
|
||||
{ id: 'tables', name: 'Tables', icon: 'table' },
|
||||
{ id: 'modals', name: 'Modals', icon: 'window' },
|
||||
{ id: 'alerts', name: 'Alerts', icon: 'exclamation' }
|
||||
{ id: 'modals', name: 'Modals', icon: 'view-grid-add' },
|
||||
{ id: 'alerts', name: 'Alerts', icon: 'exclamation' },
|
||||
{ id: 'charts', name: 'Charts', icon: 'chart-pie' }
|
||||
],
|
||||
|
||||
// Sample form data for examples
|
||||
@@ -53,6 +54,10 @@ function adminComponents() {
|
||||
required: 'This field is required'
|
||||
},
|
||||
|
||||
// Modal state variables for examples
|
||||
showExampleModal: false,
|
||||
showFormModal: false,
|
||||
|
||||
// ✅ CRITICAL: Proper initialization with guard
|
||||
async init() {
|
||||
componentsLog.info('=== COMPONENTS PAGE INITIALIZING ===');
|
||||
@@ -72,6 +77,11 @@ function adminComponents() {
|
||||
this.setActiveSectionFromHash();
|
||||
});
|
||||
|
||||
// Initialize charts after DOM is ready
|
||||
this.$nextTick(() => {
|
||||
this.initializeCharts();
|
||||
});
|
||||
|
||||
componentsLog.info('=== COMPONENTS PAGE INITIALIZATION COMPLETE ===');
|
||||
},
|
||||
|
||||
@@ -114,11 +124,18 @@ function adminComponents() {
|
||||
async copyCode(code) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
Utils.showToast('Code copied to clipboard!', 'success');
|
||||
// Use the global Utils.showToast function
|
||||
if (typeof Utils !== 'undefined' && Utils.showToast) {
|
||||
Utils.showToast('Code copied to clipboard!', 'success');
|
||||
} else {
|
||||
componentsLog.warn('Utils.showToast not available');
|
||||
}
|
||||
componentsLog.debug('Code copied to clipboard');
|
||||
} catch (error) {
|
||||
componentsLog.error('Failed to copy code:', error);
|
||||
Utils.showToast('Failed to copy code', 'error');
|
||||
if (typeof Utils !== 'undefined' && Utils.showToast) {
|
||||
Utils.showToast('Failed to copy code', 'error');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -132,7 +149,145 @@ function adminComponents() {
|
||||
warning: 'Please review your input.',
|
||||
info: 'Here is some information.'
|
||||
};
|
||||
Utils.showToast(messages[type] || messages.info, type);
|
||||
|
||||
if (typeof Utils !== 'undefined' && Utils.showToast) {
|
||||
Utils.showToast(messages[type] || messages.info, type);
|
||||
} else {
|
||||
componentsLog.error('Utils.showToast not available');
|
||||
alert(messages[type] || messages.info); // Fallback to alert
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize Chart.js charts
|
||||
*/
|
||||
initializeCharts() {
|
||||
try {
|
||||
// Check if Chart.js is loaded
|
||||
if (typeof Chart === 'undefined') {
|
||||
componentsLog.warn('Chart.js not loaded, skipping chart initialization');
|
||||
return;
|
||||
}
|
||||
|
||||
componentsLog.info('Initializing charts...');
|
||||
|
||||
// Pie Chart
|
||||
const pieCanvas = document.getElementById('examplePieChart');
|
||||
if (pieCanvas) {
|
||||
const pieConfig = {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
datasets: [{
|
||||
data: [33, 33, 33],
|
||||
backgroundColor: ['#0694a2', '#7e3af2', '#1c64f2'],
|
||||
label: 'Dataset 1',
|
||||
}],
|
||||
labels: ['Shoes', 'Shirts', 'Bags'],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
cutoutPercentage: 80,
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
new Chart(pieCanvas, pieConfig);
|
||||
componentsLog.debug('Pie chart initialized');
|
||||
}
|
||||
|
||||
// Line Chart
|
||||
const lineCanvas = document.getElementById('exampleLineChart');
|
||||
if (lineCanvas) {
|
||||
const lineConfig = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
||||
datasets: [{
|
||||
label: 'Organic',
|
||||
backgroundColor: '#0694a2',
|
||||
borderColor: '#0694a2',
|
||||
data: [43, 48, 40, 54, 67, 73, 70],
|
||||
fill: false,
|
||||
}, {
|
||||
label: 'Paid',
|
||||
fill: false,
|
||||
backgroundColor: '#7e3af2',
|
||||
borderColor: '#7e3af2',
|
||||
data: [24, 50, 64, 74, 52, 51, 65],
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
hover: {
|
||||
mode: 'nearest',
|
||||
intersect: true,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'Month',
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'Value',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
new Chart(lineCanvas, lineConfig);
|
||||
componentsLog.debug('Line chart initialized');
|
||||
}
|
||||
|
||||
// Bar Chart
|
||||
const barCanvas = document.getElementById('exampleBarChart');
|
||||
if (barCanvas) {
|
||||
const barConfig = {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
||||
datasets: [{
|
||||
label: 'Shoes',
|
||||
backgroundColor: '#0694a2',
|
||||
borderColor: '#0694a2',
|
||||
borderWidth: 1,
|
||||
data: [43, 48, 40, 54, 67, 73, 70],
|
||||
}, {
|
||||
label: 'Bags',
|
||||
backgroundColor: '#7e3af2',
|
||||
borderColor: '#7e3af2',
|
||||
borderWidth: 1,
|
||||
data: [24, 50, 64, 74, 52, 51, 65],
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
new Chart(barCanvas, barConfig);
|
||||
componentsLog.debug('Bar chart initialized');
|
||||
}
|
||||
|
||||
componentsLog.info('All charts initialized successfully');
|
||||
} catch (error) {
|
||||
componentsLog.error('Error initializing charts:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
278
static/admin/js/vendor-theme.js
Normal file
278
static/admin/js/vendor-theme.js
Normal file
@@ -0,0 +1,278 @@
|
||||
// static/admin/js/vendor-theme.js
|
||||
/**
|
||||
* Vendor Theme Management Component
|
||||
* Follows the established Alpine.js pattern from FRONTEND_ALPINE_PAGE_TEMPLATE.md
|
||||
*/
|
||||
|
||||
const THEME_LOG_LEVEL = 3;
|
||||
|
||||
const themeLog = {
|
||||
error: (...args) => THEME_LOG_LEVEL >= 1 && console.error('❌ [THEME ERROR]', ...args),
|
||||
warn: (...args) => THEME_LOG_LEVEL >= 2 && console.warn('⚠️ [THEME WARN]', ...args),
|
||||
info: (...args) => THEME_LOG_LEVEL >= 3 && console.info('ℹ️ [THEME INFO]', ...args),
|
||||
debug: (...args) => THEME_LOG_LEVEL >= 4 && console.log('🔍 [THEME DEBUG]', ...args)
|
||||
};
|
||||
|
||||
// Theme presets
|
||||
const THEME_PRESETS = {
|
||||
modern: {
|
||||
colors: {
|
||||
primary: "#6366f1",
|
||||
secondary: "#8b5cf6",
|
||||
accent: "#ec4899"
|
||||
},
|
||||
fonts: {
|
||||
heading: "Inter, sans-serif",
|
||||
body: "Inter, sans-serif"
|
||||
},
|
||||
layout: {
|
||||
style: "grid",
|
||||
header: "fixed"
|
||||
}
|
||||
},
|
||||
classic: {
|
||||
colors: {
|
||||
primary: "#1e40af",
|
||||
secondary: "#7c3aed",
|
||||
accent: "#dc2626"
|
||||
},
|
||||
fonts: {
|
||||
heading: "Georgia, serif",
|
||||
body: "Arial, sans-serif"
|
||||
},
|
||||
layout: {
|
||||
style: "list",
|
||||
header: "static"
|
||||
}
|
||||
},
|
||||
minimal: {
|
||||
colors: {
|
||||
primary: "#000000",
|
||||
secondary: "#404040",
|
||||
accent: "#666666"
|
||||
},
|
||||
fonts: {
|
||||
heading: "Helvetica, sans-serif",
|
||||
body: "Helvetica, sans-serif"
|
||||
},
|
||||
layout: {
|
||||
style: "grid",
|
||||
header: "transparent"
|
||||
}
|
||||
},
|
||||
vibrant: {
|
||||
colors: {
|
||||
primary: "#f59e0b",
|
||||
secondary: "#ef4444",
|
||||
accent: "#8b5cf6"
|
||||
},
|
||||
fonts: {
|
||||
heading: "Poppins, sans-serif",
|
||||
body: "Open Sans, sans-serif"
|
||||
},
|
||||
layout: {
|
||||
style: "masonry",
|
||||
header: "fixed"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function vendorThemeData() {
|
||||
return {
|
||||
// ✅ CRITICAL: Inherit base layout functionality
|
||||
...data(),
|
||||
|
||||
// ✅ CRITICAL: Set page identifier
|
||||
currentPage: 'vendor-theme',
|
||||
|
||||
// Page state
|
||||
loading: false,
|
||||
saving: false,
|
||||
vendor: null,
|
||||
vendorCode: window.location.pathname.split('/')[3], // Extract from /admin/vendors/{code}/theme
|
||||
|
||||
// Theme data
|
||||
themeData: {
|
||||
colors: {
|
||||
primary: "#6366f1",
|
||||
secondary: "#8b5cf6",
|
||||
accent: "#ec4899"
|
||||
},
|
||||
fonts: {
|
||||
heading: "Inter, sans-serif",
|
||||
body: "Inter, sans-serif"
|
||||
},
|
||||
layout: {
|
||||
style: "grid",
|
||||
header: "fixed"
|
||||
},
|
||||
custom_css: ""
|
||||
},
|
||||
|
||||
originalTheme: null, // For detecting changes
|
||||
|
||||
// ✅ CRITICAL: Proper initialization with guard
|
||||
async init() {
|
||||
themeLog.info('=== VENDOR THEME PAGE INITIALIZING ===');
|
||||
|
||||
// Prevent multiple initializations
|
||||
if (window._vendorThemeInitialized) {
|
||||
themeLog.warn('Page already initialized, skipping...');
|
||||
return;
|
||||
}
|
||||
window._vendorThemeInitialized = true;
|
||||
|
||||
// Load data
|
||||
await this.loadVendor();
|
||||
await this.loadTheme();
|
||||
|
||||
themeLog.info('=== VENDOR THEME PAGE INITIALIZATION COMPLETE ===');
|
||||
},
|
||||
|
||||
// Load vendor info
|
||||
async loadVendor() {
|
||||
themeLog.info('Loading vendor:', this.vendorCode);
|
||||
|
||||
try {
|
||||
// ✅ CRITICAL: Use lowercase apiClient
|
||||
const response = await apiClient.get(`/api/v1/admin/vendors/${this.vendorCode}`);
|
||||
this.vendor = response;
|
||||
themeLog.info('Vendor loaded:', this.vendor.name);
|
||||
} catch (error) {
|
||||
themeLog.error('Failed to load vendor:', error);
|
||||
Utils.showToast('Failed to load vendor', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
// Load theme configuration
|
||||
async loadTheme() {
|
||||
themeLog.info('Loading theme...');
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Get vendor's theme config from vendor object
|
||||
if (this.vendor && this.vendor.theme_config) {
|
||||
this.themeData = {
|
||||
colors: this.vendor.theme_config.colors || this.themeData.colors,
|
||||
fonts: this.vendor.theme_config.fonts || this.themeData.fonts,
|
||||
layout: this.vendor.theme_config.layout || this.themeData.layout,
|
||||
custom_css: this.vendor.theme_config.custom_css || ""
|
||||
};
|
||||
} else {
|
||||
themeLog.info('No theme config found, using defaults');
|
||||
}
|
||||
|
||||
// Store original for change detection
|
||||
this.originalTheme = JSON.parse(JSON.stringify(this.themeData));
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
themeLog.info(`Theme loaded in ${duration}ms`, this.themeData);
|
||||
|
||||
} catch (error) {
|
||||
themeLog.error('Failed to load theme:', error);
|
||||
Utils.showToast('Failed to load theme', 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// Save theme configuration
|
||||
async saveTheme() {
|
||||
themeLog.info('Saving theme...');
|
||||
this.saving = true;
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Update vendor with new theme_config
|
||||
const updateData = {
|
||||
theme_config: this.themeData
|
||||
};
|
||||
|
||||
const response = await apiClient.put(
|
||||
`/api/v1/admin/vendors/${this.vendorCode}`,
|
||||
updateData
|
||||
);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
themeLog.info(`Theme saved in ${duration}ms`);
|
||||
|
||||
// Update vendor data
|
||||
this.vendor = response;
|
||||
this.originalTheme = JSON.parse(JSON.stringify(this.themeData));
|
||||
|
||||
Utils.showToast('Theme saved successfully', 'success');
|
||||
|
||||
} catch (error) {
|
||||
themeLog.error('Failed to save theme:', error);
|
||||
Utils.showToast('Failed to save theme', 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
// Apply preset theme
|
||||
applyPreset(presetName) {
|
||||
themeLog.info('Applying preset:', presetName);
|
||||
|
||||
if (!THEME_PRESETS[presetName]) {
|
||||
themeLog.error('Unknown preset:', presetName);
|
||||
return;
|
||||
}
|
||||
|
||||
const preset = THEME_PRESETS[presetName];
|
||||
|
||||
// Apply preset values
|
||||
this.themeData.colors = { ...preset.colors };
|
||||
this.themeData.fonts = { ...preset.fonts };
|
||||
this.themeData.layout = { ...preset.layout };
|
||||
|
||||
Utils.showToast(`Applied ${presetName} theme preset`, 'success');
|
||||
},
|
||||
|
||||
// Reset to default theme
|
||||
resetToDefault() {
|
||||
themeLog.info('Resetting to default theme');
|
||||
|
||||
// Confirm with user
|
||||
if (!confirm('Are you sure you want to reset to the default theme? This will discard all customizations.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.themeData = {
|
||||
colors: {
|
||||
primary: "#6366f1",
|
||||
secondary: "#8b5cf6",
|
||||
accent: "#ec4899"
|
||||
},
|
||||
fonts: {
|
||||
heading: "Inter, sans-serif",
|
||||
body: "Inter, sans-serif"
|
||||
},
|
||||
layout: {
|
||||
style: "grid",
|
||||
header: "fixed"
|
||||
},
|
||||
custom_css: ""
|
||||
};
|
||||
|
||||
Utils.showToast('Theme reset to default', 'info');
|
||||
},
|
||||
|
||||
// Check if theme has unsaved changes
|
||||
hasChanges() {
|
||||
if (!this.originalTheme) return false;
|
||||
return JSON.stringify(this.themeData) !== JSON.stringify(this.originalTheme);
|
||||
},
|
||||
|
||||
// Format date helper
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '-';
|
||||
return Utils.formatDate(dateString);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
themeLog.info('Vendor theme module loaded');
|
||||
Reference in New Issue
Block a user