From b8546c0eea86d3b8acce57b6e8966c0851fbe8eb Mon Sep 17 00:00:00 2001 From: nic Date: Fri, 30 May 2025 15:42:22 -0400 Subject: [PATCH] feat: basic dashboard drag'n'drop --- static/css/styles.css | 81 +++++++++++++++++++ static/js/dashboard-drag.js | 154 ++++++++++++++++++++++++++++++++++++ templates/dashboard.html | 26 ++++-- templates/layout.html | 1 + 4 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 static/js/dashboard-drag.js diff --git a/static/css/styles.css b/static/css/styles.css index 80d9f53..6441eba 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -903,4 +903,85 @@ html { /* Class for the CSV preview to display properly when initialized */ .csv-preview-active { display: block; +} + +/* Dashboard Drag and Drop Styles */ +.dashboard-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.dashboard-header h2 { + margin: 0; +} + +.customize-btn { + background-color: var(--btn-primary-bg); + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 0.25rem; + cursor: pointer; + font-size: 0.875rem; + transition: background-color 0.2s; +} + +.customize-btn:hover { + background-color: var(--btn-primary-hover); +} + +.customize-btn.saving { + background-color: var(--btn-success-bg); +} + +.customize-btn.saving:hover { + background-color: var(--btn-success-hover); +} + +.customization-help { + background-color: var(--content-bg); + border: 1px solid var(--btn-primary-bg); + border-radius: 0.25rem; + padding: 0.75rem; + margin-bottom: 1rem; + color: var(--content-text); +} + +.draggable-card { + position: relative; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.draggable-card.customizable { + cursor: move; +} + +.draggable-card.customizable:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.draggable-card.dragging { + opacity: 0.5; + transform: rotate(3deg); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); +} + +.drag-handle { + position: absolute; + top: 0.5rem; + right: 0.5rem; + font-size: 1.2rem; + color: var(--btn-primary-bg); + cursor: grab; + background: var(--dashboard-bg); + padding: 0.25rem; + border-radius: 0.25rem; + z-index: 10; +} + +.drag-handle:active { + cursor: grabbing; } \ No newline at end of file diff --git a/static/js/dashboard-drag.js b/static/js/dashboard-drag.js new file mode 100644 index 0000000..8f863d0 --- /dev/null +++ b/static/js/dashboard-drag.js @@ -0,0 +1,154 @@ +// Simple Dashboard Drag and Drop +let isCustomizing = false; +let draggedCard = null; + +// Initialize on page load +document.addEventListener('DOMContentLoaded', function () { + loadLayout(); +}); + +function toggleCustomize() { + const btn = document.getElementById('customize-btn'); + const help = document.getElementById('customization-help'); + const cards = document.querySelectorAll('.draggable-card'); + const handles = document.querySelectorAll('.drag-handle'); + + isCustomizing = !isCustomizing; + + if (isCustomizing) { + btn.textContent = '💾 Save Layout'; + btn.classList.add('saving'); + help.style.display = 'block'; + + // Enable dragging + cards.forEach(card => { + card.draggable = true; + card.classList.add('customizable'); + card.addEventListener('dragstart', handleDragStart); + card.addEventListener('dragover', handleDragOver); + card.addEventListener('drop', handleDrop); + card.addEventListener('dragend', handleDragEnd); + }); + + // Show handles + handles.forEach(handle => handle.style.display = 'block'); + + } else { + btn.textContent = '📝 Customize Layout'; + btn.classList.remove('saving'); + help.style.display = 'none'; + + // Disable dragging + cards.forEach(card => { + card.draggable = false; + card.classList.remove('customizable'); + card.removeEventListener('dragstart', handleDragStart); + card.removeEventListener('dragover', handleDragOver); + card.removeEventListener('drop', handleDrop); + card.removeEventListener('dragend', handleDragEnd); + }); + + // Hide handles + handles.forEach(handle => handle.style.display = 'none'); + + // Save the current layout + saveLayout(); + showMessage('Layout saved!', 'success'); + } +} + +function handleDragStart(e) { + draggedCard = e.target.closest('.draggable-card'); + draggedCard.classList.add('dragging'); + e.dataTransfer.effectAllowed = 'move'; +} + +function handleDragOver(e) { + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + + const card = e.target.closest('.draggable-card'); + if (card && card !== draggedCard) { + const grid = document.getElementById('dashboard-grid'); + const cards = Array.from(grid.children); + const draggedIndex = cards.indexOf(draggedCard); + const targetIndex = cards.indexOf(card); + + if (draggedIndex < targetIndex) { + grid.insertBefore(draggedCard, card.nextSibling); + } else { + grid.insertBefore(draggedCard, card); + } + } +} + +function handleDrop(e) { + e.preventDefault(); +} + +function handleDragEnd(e) { + if (draggedCard) { + draggedCard.classList.remove('dragging'); + draggedCard = null; + } +} + +function saveLayout() { + const cards = document.querySelectorAll('.draggable-card'); + const layout = Array.from(cards).map((card, index) => ({ + id: card.dataset.widgetId, + position: index + })); + localStorage.setItem('dashboard-layout', JSON.stringify(layout)); +} + +function loadLayout() { + const saved = localStorage.getItem('dashboard-layout'); + if (!saved) return; + + try { + const layout = JSON.parse(saved); + const grid = document.getElementById('dashboard-grid'); + + // Sort layout by position + layout.sort((a, b) => a.position - b.position); + + // Reorder cards + layout.forEach(item => { + const card = document.querySelector(`[data-widget-id="${item.id}"]`); + if (card) grid.appendChild(card); + }); + } catch (e) { + console.warn('Failed to load layout:', e); + } +} + +function showMessage(text, type) { + // Create message element + const msg = document.createElement('div'); + msg.textContent = text; + msg.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + padding: 12px 20px; + border-radius: 4px; + color: white; + font-weight: bold; + z-index: 10000; + background: ${type === 'success' ? '#10b981' : '#ef4444'}; + transform: translateX(300px); + transition: transform 0.3s ease; + `; + + document.body.appendChild(msg); + + // Show message + setTimeout(() => msg.style.transform = 'translateX(0)', 100); + + // Hide message + setTimeout(() => { + msg.style.transform = 'translateX(300px)'; + setTimeout(() => document.body.removeChild(msg), 300); + }, 2000); +} \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html index 0eac151..05572b8 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -1,17 +1,31 @@ {{define "content"}} -

Dashboard

+
+

Dashboard

+ +
+

Welcome to the ServiceTrade Tools Dashboard.

-
-
+ + +
+
+ {{template "invoice_search" .}}
-
+ +
+ {{template "document_upload" .}}
-
+ +
+ {{template "document_remove" .}}
-
{{end}} \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html index f9404bb..0509e08 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -8,6 +8,7 @@ +