Add drag-and-drop reordering for selected services
Implements drag-and-drop functionality to reorder selected services in ServicesManager.js, including state management and event handlers. Updates main.css with styles for dragging, placeholders, and drag handles to enhance the user experience.
This commit is contained in:
1 parent
000ec07851
commit
97fc60360b
2 files changed
+228
-4
No files matched your search
@@ -21,8 +21,17 @@ export class ServicesManager {
|
|||||||
|
|
||||||
this.allServices = [];
|
this.allServices = [];
|
||||||
this.selectedServices = new Set();
|
this.selectedServices = new Set();
|
||||||
|
this.selectedServicesOrder = []; // Track order for drag functionality
|
||||||
this.searchTimeout = null;
|
this.searchTimeout = null;
|
||||||
|
|
||||||
|
// Drag state
|
||||||
|
this.dragState = {
|
||||||
|
isDragging: false,
|
||||||
|
draggedElement: null,
|
||||||
|
draggedIndex: -1,
|
||||||
|
placeholder: null,
|
||||||
|
};
|
||||||
|
|
||||||
this.elements = {
|
this.elements = {
|
||||||
searchInput: document.getElementById("search-input"),
|
searchInput: document.getElementById("search-input"),
|
||||||
searchResults: document.getElementById("search-results"),
|
searchResults: document.getElementById("search-results"),
|
||||||
@@ -115,6 +124,26 @@ export class ServicesManager {
|
|||||||
this.elements.selectedServices.addEventListener("click", (e) => {
|
this.elements.selectedServices.addEventListener("click", (e) => {
|
||||||
this.handleSelectedServiceClick(e);
|
this.handleSelectedServiceClick(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle drag events
|
||||||
|
this.setupDragListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up drag and drop event listeners
|
||||||
|
*/
|
||||||
|
setupDragListeners() {
|
||||||
|
this.elements.selectedServices.addEventListener("mousedown", (e) => {
|
||||||
|
this.handleDragStart(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("mousemove", (e) => {
|
||||||
|
this.handleDragMove(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("mouseup", (e) => {
|
||||||
|
this.handleDragEnd(e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -207,6 +236,7 @@ export class ServicesManager {
|
|||||||
addService(serviceName) {
|
addService(serviceName) {
|
||||||
if (!this.selectedServices.has(serviceName)) {
|
if (!this.selectedServices.has(serviceName)) {
|
||||||
this.selectedServices.add(serviceName);
|
this.selectedServices.add(serviceName);
|
||||||
|
this.selectedServicesOrder.push(serviceName);
|
||||||
this.updateUI();
|
this.updateUI();
|
||||||
this.clearSearch();
|
this.clearSearch();
|
||||||
this.triggerSelectionChange();
|
this.triggerSelectionChange();
|
||||||
@@ -220,6 +250,10 @@ export class ServicesManager {
|
|||||||
removeService(serviceName) {
|
removeService(serviceName) {
|
||||||
if (this.selectedServices.has(serviceName)) {
|
if (this.selectedServices.has(serviceName)) {
|
||||||
this.selectedServices.delete(serviceName);
|
this.selectedServices.delete(serviceName);
|
||||||
|
const index = this.selectedServicesOrder.indexOf(serviceName);
|
||||||
|
if (index > -1) {
|
||||||
|
this.selectedServicesOrder.splice(index, 1);
|
||||||
|
}
|
||||||
this.updateUI();
|
this.updateUI();
|
||||||
this.triggerSelectionChange();
|
this.triggerSelectionChange();
|
||||||
}
|
}
|
||||||
@@ -265,8 +299,8 @@ export class ServicesManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedArray = Array.from(this.selectedServices).sort();
|
// Use ordered array instead of sorted Set
|
||||||
this.elements.selectedServices.innerHTML = selectedArray
|
this.elements.selectedServices.innerHTML = this.selectedServicesOrder
|
||||||
.map(
|
.map(
|
||||||
(serviceName, index) => `
|
(serviceName, index) => `
|
||||||
<div class="selected-service-item" data-index="${index}">
|
<div class="selected-service-item" data-index="${index}">
|
||||||
@@ -291,7 +325,7 @@ export class ServicesManager {
|
|||||||
* @returns {Array<string>} Array of selected service names
|
* @returns {Array<string>} Array of selected service names
|
||||||
*/
|
*/
|
||||||
getSelectedServices() {
|
getSelectedServices() {
|
||||||
return Array.from(this.selectedServices);
|
return [...this.selectedServicesOrder];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -311,4 +345,139 @@ export class ServicesManager {
|
|||||||
isLoaded() {
|
isLoaded() {
|
||||||
return this.allServices.length > 0;
|
return this.allServices.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle drag start
|
||||||
|
* @param {MouseEvent} event - Mouse event
|
||||||
|
*/
|
||||||
|
handleDragStart(event) {
|
||||||
|
const dragHandle = event.target.closest(".service-drag-handle");
|
||||||
|
if (!dragHandle) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const serviceItem = dragHandle.closest(".selected-service-item");
|
||||||
|
if (!serviceItem) return;
|
||||||
|
|
||||||
|
this.dragState.isDragging = true;
|
||||||
|
this.dragState.draggedElement = serviceItem;
|
||||||
|
this.dragState.draggedIndex = parseInt(serviceItem.dataset.index);
|
||||||
|
|
||||||
|
// Create placeholder
|
||||||
|
this.dragState.placeholder = document.createElement("div");
|
||||||
|
this.dragState.placeholder.className =
|
||||||
|
"selected-service-item drag-placeholder";
|
||||||
|
this.dragState.placeholder.innerHTML = `
|
||||||
|
<div class="service-info">
|
||||||
|
<i class="fas fa-grip-lines service-drag-handle"></i>
|
||||||
|
<span class="service-name">Drop here</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add dragging class
|
||||||
|
serviceItem.classList.add("dragging");
|
||||||
|
document.body.classList.add("dragging");
|
||||||
|
|
||||||
|
// Insert placeholder
|
||||||
|
serviceItem.parentNode.insertBefore(
|
||||||
|
this.dragState.placeholder,
|
||||||
|
serviceItem.nextSibling
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle drag move
|
||||||
|
* @param {MouseEvent} event - Mouse event
|
||||||
|
*/
|
||||||
|
handleDragMove(event) {
|
||||||
|
if (!this.dragState.isDragging) return;
|
||||||
|
|
||||||
|
const serviceItems = Array.from(
|
||||||
|
this.elements.selectedServices.querySelectorAll(
|
||||||
|
".selected-service-item:not(.dragging):not(.drag-placeholder)"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let targetIndex = serviceItems.length; // Default to end
|
||||||
|
|
||||||
|
for (let i = 0; i < serviceItems.length; i++) {
|
||||||
|
const item = serviceItems[i];
|
||||||
|
const rect = item.getBoundingClientRect();
|
||||||
|
const itemCenter = rect.top + rect.height / 2;
|
||||||
|
|
||||||
|
if (event.clientY < itemCenter) {
|
||||||
|
targetIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move placeholder to new position
|
||||||
|
const targetItem = serviceItems[targetIndex];
|
||||||
|
if (targetItem) {
|
||||||
|
targetItem.parentNode.insertBefore(
|
||||||
|
this.dragState.placeholder,
|
||||||
|
targetItem
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Insert at end
|
||||||
|
this.elements.selectedServices.appendChild(this.dragState.placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle drag end
|
||||||
|
* @param {MouseEvent} event - Mouse event
|
||||||
|
*/
|
||||||
|
handleDragEnd(event) {
|
||||||
|
if (!this.dragState.isDragging) return;
|
||||||
|
|
||||||
|
// Calculate new index based on placeholder position
|
||||||
|
const allItems = Array.from(
|
||||||
|
this.elements.selectedServices.querySelectorAll(
|
||||||
|
".selected-service-item, .drag-placeholder"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const placeholderIndex = allItems.indexOf(this.dragState.placeholder);
|
||||||
|
const newIndex =
|
||||||
|
placeholderIndex > this.dragState.draggedIndex
|
||||||
|
? placeholderIndex - 1
|
||||||
|
: placeholderIndex;
|
||||||
|
|
||||||
|
// Update services order
|
||||||
|
this.reorderServices(this.dragState.draggedIndex, newIndex);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
this.dragState.draggedElement.classList.remove("dragging");
|
||||||
|
document.body.classList.remove("dragging");
|
||||||
|
if (this.dragState.placeholder && this.dragState.placeholder.parentNode) {
|
||||||
|
this.dragState.placeholder.parentNode.removeChild(
|
||||||
|
this.dragState.placeholder
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset drag state
|
||||||
|
this.dragState = {
|
||||||
|
isDragging: false,
|
||||||
|
draggedElement: null,
|
||||||
|
draggedIndex: -1,
|
||||||
|
placeholder: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update UI and trigger change event
|
||||||
|
this.renderSelectedServices();
|
||||||
|
this.triggerSelectionChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder services array
|
||||||
|
* @param {number} fromIndex - Original index
|
||||||
|
* @param {number} toIndex - Target index
|
||||||
|
*/
|
||||||
|
reorderServices(fromIndex, toIndex) {
|
||||||
|
if (fromIndex === toIndex) return;
|
||||||
|
|
||||||
|
const item = this.selectedServicesOrder[fromIndex];
|
||||||
|
this.selectedServicesOrder.splice(fromIndex, 1);
|
||||||
|
this.selectedServicesOrder.splice(toIndex, 0, item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+56
-1
@@ -1574,5 +1574,60 @@ body::before {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
Global Styles
|
Drag and drop styles
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
.dragging .selected-service-item {
|
||||||
|
opacity: 0.5;
|
||||||
|
transform: rotate(5deg);
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-placeholder {
|
||||||
|
border: 2px dashed var(--primary-color) !important;
|
||||||
|
background: rgba(37, 99, 235, 0.1) !important;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-placeholder .service-name {
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dragging {
|
||||||
|
cursor: grabbing !important;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dragging * {
|
||||||
|
cursor: grabbing !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-drag-handle {
|
||||||
|
cursor: grab;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-drag-handle:hover {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-drag-handle:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-service-item.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
transform: rotate(5deg);
|
||||||
|
transition: none;
|
||||||
|
z-index: 1000;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark theme drag styles */
|
||||||
|
[data-theme="dark"] .drag-placeholder {
|
||||||
|
border-color: var(--primary-color) !important;
|
||||||
|
background: rgba(37, 99, 235, 0.15) !important;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user