Skip to content

Commit 88cc9c8

Browse files
committed
feat: add charts page with routing and initialization
1 parent ee9e2a4 commit 88cc9c8

File tree

8 files changed

+545
-2
lines changed

8 files changed

+545
-2
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@profullstack/spa-router": "^1.11.1",
3131
"@supabase/supabase-js": "^2.49.4",
3232
"chalk": "^5.3.0",
33+
"charts.css": "^1.1.0",
3334
"commander": "^12.0.0",
3435
"docx": "^9.5.0",
3536
"dotenv": "^16.5.0",

public/css/charts.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/i18n/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,9 @@
123123
"refund": "Refund Policy",
124124
"company_name": "Profullstack, Inc.",
125125
"all_rights_reserved": "All rights reserved."
126+
},
127+
"charts": {
128+
"title": "Charts Demo",
129+
"description": "Interactive data visualization using Charts.css library"
126130
}
127131
}

public/js/modules/spa-router/src/renderer.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,44 @@ export function createRenderer(options = {}) {
105105
}
106106
}
107107

108+
// Clean up previous view styles to prevent accumulation
109+
const previousViewStyles = document.head.querySelectorAll('style[data-view-style]');
110+
previousViewStyles.forEach(style => {
111+
try {
112+
const styleId = style.getAttribute('data-view-style-id');
113+
style.remove();
114+
console.log(`Removed previous view style: ${styleId}`);
115+
} catch (error) {
116+
console.warn('Error removing view style:', error);
117+
}
118+
});
119+
120+
// Extract and process style tags from both head and body sections
121+
// Views might have style tags at the root level or in the body
122+
const headStyleTags = Array.from(doc.head.querySelectorAll('style'));
123+
const bodyStyleTags = Array.from(doc.body.querySelectorAll('style'));
124+
const allStyleTags = [...headStyleTags, ...bodyStyleTags];
125+
126+
console.log(`Found ${allStyleTags.length} style tags in view (${headStyleTags.length} in head, ${bodyStyleTags.length} in body)`);
127+
128+
// Process style tags from the view
129+
allStyleTags.forEach((styleTag, index) => {
130+
const styleId = `view-style-${Date.now()}-${index}`;
131+
const newStyle = document.createElement('style');
132+
newStyle.textContent = styleTag.textContent;
133+
newStyle.setAttribute('data-view-style', 'true');
134+
newStyle.setAttribute('data-view-style-id', styleId);
135+
136+
// Add to document head
137+
document.head.appendChild(newStyle);
138+
console.log(`Added view style tag: ${styleId} with content: ${styleTag.textContent.substring(0, 100)}...`);
139+
140+
// Remove the style tag from the original location to prevent duplication
141+
if (styleTag.parentNode) {
142+
styleTag.parentNode.removeChild(styleTag);
143+
}
144+
});
145+
108146
// Get all existing web components in the current DOM to preserve
109147
const existingComponents = {};
110148
const customElements = element.querySelectorAll('*').filter(el => el.tagName.includes('-'));

public/js/page-initializers.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1013,7 +1013,9 @@ export function initTestFixPage() {
10131013
submitButton.disabled = false;
10141014
}
10151015
});
1016-
}/**
1016+
}
1017+
1018+
/**
10171019
* Initialize manage subscription page
10181020
*/
10191021
export async function initManageSubscriptionPage() {
@@ -1093,6 +1095,23 @@ export function initTestFix2Page() {
10931095
}
10941096
});
10951097
}
1098+
1099+
/**
1100+
* Initialize the charts page
1101+
*/
1102+
export async function initChartsPage() {
1103+
console.log('Initializing charts page');
1104+
1105+
try {
1106+
// Import and initialize the charts page functionality
1107+
const { initChartsInteractivity } = await import('./views/charts.js');
1108+
initChartsInteractivity();
1109+
console.log('Charts page initialized successfully');
1110+
} catch (error) {
1111+
console.error('Error initializing charts page:', error);
1112+
}
1113+
}
1114+
10961115
/**
10971116
* Initialize reset password page
10981117
*/

public/js/router.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
initResetPasswordPage,
1414
initManageSubscriptionPage,
1515
initTestFeaturePage,
16-
initFaqsPage
16+
initFaqsPage,
17+
initChartsPage
1718
} from './page-initializers.js';
1819

1920
// Create a DOM fragment with the default layout
@@ -444,6 +445,10 @@ export function defineRoutes(router) {
444445

445446
// Demo routes
446447
'/state-demo': '/views/state-demo.html',
448+
'/charts': {
449+
viewPath: '/views/charts.html',
450+
afterRender: initChartsPage
451+
},
447452
'/simple-state-demo': {
448453
viewPath: '/views/simple-state-demo.html',
449454
afterRender: () => {

public/js/views/charts.js

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/**
2+
* Charts.css Demo page functionality
3+
*/
4+
5+
/**
6+
* Initialize the charts page
7+
*/
8+
function initChartsPage() {
9+
console.log('Initializing charts page');
10+
11+
// Setup interactive chart controls
12+
setupInteractiveControls();
13+
14+
// Add some custom styling for better presentation
15+
addCustomChartStyles();
16+
}
17+
18+
/**
19+
* Setup interactive controls for the demo chart
20+
*/
21+
function setupInteractiveControls() {
22+
const buttons = document.querySelectorAll('.chart-controls button');
23+
const interactiveChart = document.querySelector('#interactive-chart');
24+
25+
if (!interactiveChart) {
26+
console.warn('Interactive chart not found');
27+
return;
28+
}
29+
30+
buttons.forEach((button) => {
31+
button.addEventListener('click', (event) => {
32+
event.preventDefault();
33+
34+
// Toggle the data-clicked attribute on the button
35+
button.toggleAttribute('data-clicked');
36+
37+
// Get the class name from the button
38+
const className = button.className.split(' ')[0]; // Get first class name
39+
40+
// Toggle the class on the interactive chart
41+
interactiveChart.classList.toggle(className);
42+
43+
// Update button text to show current state
44+
updateButtonText(button, className);
45+
46+
console.log(`Toggled ${className} on interactive chart`);
47+
});
48+
});
49+
}
50+
51+
/**
52+
* Update button text to reflect current state
53+
* @param {HTMLElement} button - The button element
54+
* @param {string} className - The class name being toggled
55+
*/
56+
function updateButtonText(button, className) {
57+
const isActive = button.hasAttribute('data-clicked');
58+
const baseText = button.textContent.replace(' (ON)', '').replace(' (OFF)', '');
59+
60+
button.textContent = `${baseText} (${isActive ? 'ON' : 'OFF'})`;
61+
}
62+
63+
/**
64+
* Add custom styles for better chart presentation
65+
*/
66+
function addCustomChartStyles() {
67+
// Create a style element for custom chart styles
68+
const style = document.createElement('style');
69+
style.textContent = `
70+
.charts-container {
71+
max-width: 1200px;
72+
margin: 0 auto;
73+
padding: 2rem;
74+
}
75+
76+
.chart-section {
77+
margin-bottom: 3rem;
78+
padding: 2rem;
79+
border: 1px solid var(--border-color, #e5e7eb);
80+
border-radius: 8px;
81+
background: var(--card-background, #ffffff);
82+
}
83+
84+
.chart-section h2 {
85+
color: var(--heading-color, #1f2937);
86+
margin-bottom: 0.5rem;
87+
font-size: 1.5rem;
88+
}
89+
90+
.chart-section p {
91+
color: var(--text-color, #6b7280);
92+
margin-bottom: 1.5rem;
93+
}
94+
95+
.charts-css {
96+
height: 200px;
97+
max-width: 100%;
98+
margin: 1rem 0;
99+
}
100+
101+
.chart-controls {
102+
display: flex;
103+
flex-wrap: wrap;
104+
gap: 0.5rem;
105+
margin-bottom: 1.5rem;
106+
}
107+
108+
.chart-controls button {
109+
padding: 0.5rem 1rem;
110+
border: 1px solid var(--border-color, #d1d5db);
111+
border-radius: 4px;
112+
background: var(--button-background, #f9fafb);
113+
color: var(--button-text, #374151);
114+
cursor: pointer;
115+
transition: all 0.2s ease;
116+
font-size: 0.875rem;
117+
}
118+
119+
.chart-controls button:hover {
120+
background: var(--button-hover-background, #f3f4f6);
121+
border-color: var(--button-hover-border, #9ca3af);
122+
}
123+
124+
.chart-controls button[data-clicked] {
125+
background: var(--button-active-background, #3b82f6);
126+
color: var(--button-active-text, #ffffff);
127+
border-color: var(--button-active-border, #2563eb);
128+
}
129+
130+
.legend-examples {
131+
display: grid;
132+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
133+
gap: 1rem;
134+
margin-top: 1rem;
135+
}
136+
137+
.legend {
138+
padding: 1rem;
139+
border: 1px solid var(--border-color, #e5e7eb);
140+
border-radius: 4px;
141+
background: var(--legend-background, #f9fafb);
142+
}
143+
144+
.legend > div:first-child {
145+
font-weight: bold;
146+
margin-bottom: 0.5rem;
147+
color: var(--heading-color, #1f2937);
148+
}
149+
150+
/* Custom colors for chart data */
151+
.charts-css.column td:nth-child(2) { --color: #3b82f6; }
152+
.charts-css.column td:nth-child(3) { --color: #ef4444; }
153+
.charts-css.column td:nth-child(4) { --color: #10b981; }
154+
.charts-css.column td:nth-child(5) { --color: #f59e0b; }
155+
.charts-css.column td:nth-child(6) { --color: #8b5cf6; }
156+
157+
.charts-css.bar td:nth-child(2) { --color: #3b82f6; }
158+
.charts-css.bar td:nth-child(3) { --color: #ef4444; }
159+
.charts-css.bar td:nth-child(4) { --color: #10b981; }
160+
.charts-css.bar td:nth-child(5) { --color: #f59e0b; }
161+
.charts-css.bar td:nth-child(6) { --color: #8b5cf6; }
162+
163+
.charts-css.area { --color: #3b82f6; }
164+
.charts-css.line { --color: #ef4444; }
165+
166+
/* Responsive design */
167+
@media (max-width: 768px) {
168+
.charts-container {
169+
padding: 1rem;
170+
}
171+
172+
.chart-section {
173+
padding: 1rem;
174+
}
175+
176+
.charts-css {
177+
height: 150px;
178+
}
179+
180+
.legend-examples {
181+
grid-template-columns: 1fr;
182+
}
183+
}
184+
`;
185+
186+
// Append the style to the document head safely
187+
try {
188+
if (document.head) {
189+
document.head.appendChild(style);
190+
} else {
191+
console.warn('Document head not available for style injection');
192+
}
193+
} catch (error) {
194+
console.error('Error adding custom chart styles:', error);
195+
}
196+
}
197+
198+
/**
199+
* Generate random data for dynamic chart updates (future enhancement)
200+
*/
201+
function generateRandomData() {
202+
return Math.floor(Math.random() * 100) + 1;
203+
}
204+
205+
/**
206+
* Update chart data dynamically (future enhancement)
207+
* @param {HTMLElement} chart - The chart table element
208+
*/
209+
function updateChartData(chart) {
210+
const dataCells = chart.querySelectorAll('tbody td');
211+
212+
dataCells.forEach(cell => {
213+
const newValue = generateRandomData();
214+
const newSize = newValue / 100;
215+
216+
cell.style.setProperty('--size', newSize);
217+
cell.textContent = newValue;
218+
});
219+
}
220+
221+
// Initialize the page when the DOM is loaded
222+
initChartsPage();
223+
224+
// Also initialize on spa-transition-end event for SPA router
225+
document.addEventListener("spa-transition-end", initChartsPage);
226+
227+
// Export functions for potential external use
228+
export {
229+
initChartsPage,
230+
initChartsPage as initChartsInteractivity, // Alias for page initializer
231+
setupInteractiveControls,
232+
updateChartData,
233+
generateRandomData
234+
};

0 commit comments

Comments
 (0)