summaryrefslogtreecommitdiff
path: root/web/static/js/app.js
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-12 14:28:50 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-12 14:28:50 -1000
commit06c7485a7d05de86f9898e388161e8d932d5f3e6 (patch)
tree376083a75278c9758f53c0062742062dedb75633 /web/static/js/app.js
parent9ef5b7f37883f846f105da9dc5d2ba1415e594e3 (diff)
Modernize frontend with tabs, HTMX, and Tailwind build pipeline
Complete UI overhaul implementing modern design patterns with HTMX for dynamic updates, proper Tailwind build pipeline, and improved UX. Build Pipeline: - Add npm + PostCSS + Tailwind CSS configuration - Custom design system with brand colors - Compiled CSS: 27KB (vs 3MB CDN), 99% reduction - Makefile for unified build commands - Inter font for improved typography Tab Interface: - Separate Tasks tab from Notes tab using HTMX - Partial page updates without full refreshes - Tab state management with proper refresh handling - New endpoints: /tabs/tasks, /tabs/notes, /tabs/refresh Template Architecture: - Modular partials system (7 reusable components) - Cleaner separation of concerns Empty Board Management: - Active boards in main 3-column grid - Empty boards in collapsible section - Reduces visual clutter Visual Design Enhancements: - Inter font, brand color accents - Improved typography hierarchy and spacing - Enhanced card styling with hover effects Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'web/static/js/app.js')
-rw-r--r--web/static/js/app.js190
1 files changed, 146 insertions, 44 deletions
diff --git a/web/static/js/app.js b/web/static/js/app.js
index a96c05d..b68b12a 100644
--- a/web/static/js/app.js
+++ b/web/static/js/app.js
@@ -1,77 +1,179 @@
-// Personal Dashboard JavaScript
+// Personal Dashboard JavaScript with HTMX Integration
-// Auto-refresh every 5 minutes
+// Constants
const AUTO_REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes in milliseconds
-// Initialize auto-refresh on page load
+// Track current active tab
+let currentTab = 'tasks';
+let autoRefreshTimer = null;
+
+// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
- // Set up auto-refresh
- setInterval(autoRefresh, AUTO_REFRESH_INTERVAL);
+ console.log('Dashboard initialized');
+
+ // Set up HTMX event listeners
+ setupHtmxListeners();
+
+ // Start auto-refresh
+ startAutoRefresh();
});
-// Auto-refresh function
-async function autoRefresh() {
- console.log('Auto-refreshing data...');
- try {
- const response = await fetch('/api/refresh', {
- method: 'POST'
- });
+// HTMX Event Listeners
+function setupHtmxListeners() {
+ // Before HTMX request
+ document.body.addEventListener('htmx:beforeRequest', function(evt) {
+ const target = evt.detail.target;
+ if (target.id === 'tab-content') {
+ // Show loading state
+ target.classList.add('opacity-50', 'pointer-events-none');
+ }
+ });
- if (response.ok) {
- // Reload the page to show updated data
- window.location.reload();
+ // After HTMX request completes
+ document.body.addEventListener('htmx:afterRequest', function(evt) {
+ const target = evt.detail.target;
+ if (target.id === 'tab-content') {
+ // Hide loading state
+ target.classList.remove('opacity-50', 'pointer-events-none');
+
+ // Update timestamp
+ updateLastUpdatedTime();
}
- } catch (error) {
- console.error('Auto-refresh failed:', error);
- }
+ });
+
+ // Handle HTMX errors
+ document.body.addEventListener('htmx:responseError', function(evt) {
+ console.error('HTMX request failed:', evt.detail);
+ alert('Failed to load content. Please try again.');
+
+ // Remove loading state
+ const target = evt.detail.target;
+ if (target) {
+ target.classList.remove('opacity-50', 'pointer-events-none');
+ }
+ });
+}
+
+// Tab Management
+function setActiveTab(button) {
+ // Remove active class from all tabs
+ document.querySelectorAll('.tab-button').forEach(tab => {
+ tab.classList.remove('tab-button-active');
+ });
+
+ // Add active class to clicked tab
+ button.classList.add('tab-button-active');
+
+ // Extract tab name from hx-get attribute
+ const endpoint = button.getAttribute('hx-get');
+ currentTab = endpoint.split('/').pop(); // "tasks" or "notes"
+
+ console.log('Switched to tab:', currentTab);
+
+ // Reset auto-refresh timer when switching tabs
+ resetAutoRefresh();
}
-// Manual refresh function
+// Manual Refresh
async function refreshData() {
- const button = event.target;
- const originalText = button.textContent;
+ const button = event.target.closest('button');
+ const refreshText = document.getElementById('refresh-text');
+ const originalText = refreshText.textContent;
// Show loading state
button.disabled = true;
- button.innerHTML = 'Refreshing...<span class="spinner"></span>';
+ refreshText.innerHTML = `
+ <svg class="animate-spin h-5 w-5 inline mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
+ </svg>
+ Refreshing...
+ `;
try {
- const response = await fetch('/api/refresh', {
+ // Refresh current tab
+ const response = await fetch(`/tabs/refresh?tab=${currentTab}`, {
method: 'POST'
});
- if (response.ok) {
- // Update last updated time
- const now = new Date();
- const timeString = now.toLocaleTimeString('en-US', {
- hour: 'numeric',
- minute: '2-digit'
- });
- document.getElementById('last-updated').textContent = timeString;
-
- // Reload the page to show updated data
- setTimeout(() => {
- window.location.reload();
- }, 500);
- } else {
- throw new Error('Refresh failed');
- }
+ if (!response.ok) throw new Error('Refresh failed');
+
+ // Get HTML response and update tab content
+ const html = await response.text();
+ document.getElementById('tab-content').innerHTML = html;
+
+ // Update timestamp
+ updateLastUpdatedTime();
+
+ // Reset auto-refresh timer
+ resetAutoRefresh();
+
+ console.log('Manual refresh successful');
+
} catch (error) {
console.error('Refresh failed:', error);
alert('Failed to refresh data. Please try again.');
+ } finally {
+ // Restore button state
button.disabled = false;
- button.textContent = originalText;
+ refreshText.textContent = originalText;
+ }
+}
+
+// Auto-refresh Functions
+function startAutoRefresh() {
+ if (autoRefreshTimer) {
+ clearInterval(autoRefreshTimer);
+ }
+ autoRefreshTimer = setInterval(autoRefresh, AUTO_REFRESH_INTERVAL);
+ console.log('Auto-refresh started (5 min interval)');
+}
+
+function resetAutoRefresh() {
+ clearInterval(autoRefreshTimer);
+ startAutoRefresh();
+}
+
+async function autoRefresh() {
+ console.log(`Auto-refreshing ${currentTab} tab...`);
+
+ try {
+ const response = await fetch(`/tabs/refresh?tab=${currentTab}`, {
+ method: 'POST'
+ });
+
+ if (response.ok) {
+ const html = await response.text();
+ document.getElementById('tab-content').innerHTML = html;
+ updateLastUpdatedTime();
+ console.log('Auto-refresh successful');
+ }
+ } catch (error) {
+ console.error('Auto-refresh failed:', error);
+ }
+}
+
+// Update Last Updated Time
+function updateLastUpdatedTime() {
+ const now = new Date();
+ const timeString = now.toLocaleTimeString('en-US', {
+ hour: 'numeric',
+ minute: '2-digit'
+ });
+ const element = document.getElementById('last-updated');
+ if (element) {
+ element.textContent = timeString;
}
}
-// Filter tasks by status
+// Filter tasks by status (Phase 2 feature)
function filterTasks(status) {
- // This will be implemented in Phase 2
console.log('Filter tasks:', status);
+ // To be implemented in Phase 2
}
-// Toggle task completion
+// Toggle task completion (Phase 2 feature)
function toggleTask(taskId) {
- // This will be implemented in Phase 2
console.log('Toggle task:', taskId);
+ // To be implemented in Phase 2
}