Add drag-to-reorder for nomenclatures
Nomenclatures can be reordered by dragging in the left-hand list, which PATCHes sort_order on the rows that moved. PATCH rather than PUT, since a full update recreates the nested fields collection. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -121,6 +121,7 @@
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: background 0.15s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.nm-list-item:hover {
|
||||
@@ -132,6 +133,15 @@
|
||||
border-left: 3px solid #667eea;
|
||||
}
|
||||
|
||||
.nm-list-item--dragging {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* Inset shadow rather than a border, so the drop line adds no row height */
|
||||
.nm-list-item--drag-over {
|
||||
box-shadow: inset 0 2px 0 #667eea;
|
||||
}
|
||||
|
||||
.nm-list-item-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
@@ -22,6 +22,8 @@ function NomenclatureManager({ onClose }) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [dragIndex, setDragIndex] = useState(null);
|
||||
const [dragOverIndex, setDragOverIndex] = useState(null);
|
||||
|
||||
// Load nomenclature list
|
||||
const loadNomenclatures = useCallback(async () => {
|
||||
@@ -164,6 +166,60 @@ function NomenclatureManager({ onClose }) {
|
||||
}));
|
||||
};
|
||||
|
||||
// --- Drag to reorder the nomenclature list ---
|
||||
const handleDragStart = (index, e) => {
|
||||
setDragIndex(index);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
// Firefox will not start a drag unless some data is set
|
||||
e.dataTransfer.setData('text/plain', String(index));
|
||||
};
|
||||
|
||||
const handleDragOver = (index, e) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
setDragOverIndex(index);
|
||||
};
|
||||
|
||||
const handleDragEnd = () => {
|
||||
setDragIndex(null);
|
||||
setDragOverIndex(null);
|
||||
};
|
||||
|
||||
const handleDrop = async (targetIndex, e) => {
|
||||
e.preventDefault();
|
||||
const sourceIndex = dragIndex;
|
||||
setDragIndex(null);
|
||||
setDragOverIndex(null);
|
||||
|
||||
if (sourceIndex === null || sourceIndex === targetIndex) return;
|
||||
|
||||
const previous = nomenclatures;
|
||||
const reordered = [...previous];
|
||||
const [moved] = reordered.splice(sourceIndex, 1);
|
||||
reordered.splice(targetIndex, 0, moved);
|
||||
|
||||
// Renumber and apply optimistically so the list does not snap back
|
||||
const withOrder = reordered.map((nom, i) => ({ ...nom, sort_order: i }));
|
||||
setNomenclatures(withOrder);
|
||||
|
||||
// PATCH only the rows whose position actually changed. PATCH (not PUT) is
|
||||
// required: a full update recreates the nested fields collection.
|
||||
const changed = withOrder.filter(
|
||||
(nom, i) => previous[i]?.id !== nom.id || previous[i]?.sort_order !== i
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.all(
|
||||
changed.map(nom =>
|
||||
api.patch(`/api/nomenclatures/${nom.id}/`, { sort_order: nom.sort_order })
|
||||
)
|
||||
);
|
||||
} catch {
|
||||
setError('Failed to save order');
|
||||
await loadNomenclatures();
|
||||
}
|
||||
};
|
||||
|
||||
const handleOverlayClick = (e) => {
|
||||
if (e.target.className === 'nm-overlay') {
|
||||
onClose();
|
||||
@@ -187,11 +243,21 @@ function NomenclatureManager({ onClose }) {
|
||||
{isLoading && <div className="nm-loading">Loading...</div>}
|
||||
|
||||
<div className="nm-list">
|
||||
{nomenclatures.map(nom => (
|
||||
{nomenclatures.map((nom, idx) => (
|
||||
<div
|
||||
key={nom.id}
|
||||
className={`nm-list-item ${selectedId === nom.id ? 'nm-list-item--active' : ''}`}
|
||||
className={[
|
||||
'nm-list-item',
|
||||
selectedId === nom.id ? 'nm-list-item--active' : '',
|
||||
dragIndex === idx ? 'nm-list-item--dragging' : '',
|
||||
dragOverIndex === idx && dragIndex !== idx ? 'nm-list-item--drag-over' : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
onClick={() => handleSelect(nom)}
|
||||
draggable={canEdit}
|
||||
onDragStart={e => handleDragStart(idx, e)}
|
||||
onDragOver={e => handleDragOver(idx, e)}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDrop={e => handleDrop(idx, e)}
|
||||
>
|
||||
<div className="nm-list-item-info">
|
||||
<span className="nm-list-item-name">{nom.name}</span>
|
||||
|
||||
Reference in New Issue
Block a user