Browse Source

feat: basic dashboard drag'n'drop

document-upload-removal-layout-update
nic 8 months ago
parent
commit
b8546c0eea
  1. 81
      static/css/styles.css
  2. 154
      static/js/dashboard-drag.js
  3. 26
      templates/dashboard.html
  4. 1
      templates/layout.html

81
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;
}

154
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);
}

26
templates/dashboard.html

@ -1,17 +1,31 @@
{{define "content"}}
<h2>Dashboard</h2>
<div class="dashboard-header">
<h2>Dashboard</h2>
<button id="customize-btn" class="customize-btn" onclick="toggleCustomize()">
📝 Customize Layout
</button>
</div>
<p>Welcome to the ServiceTrade Tools Dashboard.</p>
<div class="dashboard-grid">
<div class="dashboard-item">
<div id="customization-help" class="customization-help" style="display: none;">
💡 <strong>Tip:</strong> Drag and drop the cards to arrange them in your preferred order.
</div>
<div id="dashboard-grid" class="dashboard-grid">
<div class="dashboard-item draggable-card" data-widget-id="invoice-search" data-widget-title="Update Invoice Status">
<div class="drag-handle" style="display: none;">⋮⋮</div>
{{template "invoice_search" .}}
</div>
<div class="dashboard-item">
<div class="dashboard-item draggable-card" data-widget-id="document-upload" data-widget-title="Document Uploads">
<div class="drag-handle" style="display: none;">⋮⋮</div>
{{template "document_upload" .}}
</div>
<div class="dashboard-item">
<div class="dashboard-item draggable-card" data-widget-id="document-remove" data-widget-title="Document Removal">
<div class="drag-handle" style="display: none;">⋮⋮</div>
{{template "document_remove" .}}
</div>
<!-- Add more dashboard items as needed -->
</div>
{{end}}

1
templates/layout.html

@ -8,6 +8,7 @@
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<link rel="stylesheet" href="/static/css/styles.css" />
<link rel="stylesheet" href="/static/css/upload.css" />
<script src="/static/js/dashboard-drag.js"></script>
</head>
<body class="flex h-screen bg-gray-100">

Loading…
Cancel
Save