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;
|
cursor: pointer;
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
transition: background 0.15s;
|
transition: background 0.15s;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nm-list-item:hover {
|
.nm-list-item:hover {
|
||||||
@@ -132,6 +133,15 @@
|
|||||||
border-left: 3px solid #667eea;
|
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 {
|
.nm-list-item-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ function NomenclatureManager({ onClose }) {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const [dragIndex, setDragIndex] = useState(null);
|
||||||
|
const [dragOverIndex, setDragOverIndex] = useState(null);
|
||||||
|
|
||||||
// Load nomenclature list
|
// Load nomenclature list
|
||||||
const loadNomenclatures = useCallback(async () => {
|
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) => {
|
const handleOverlayClick = (e) => {
|
||||||
if (e.target.className === 'nm-overlay') {
|
if (e.target.className === 'nm-overlay') {
|
||||||
onClose();
|
onClose();
|
||||||
@@ -187,11 +243,21 @@ function NomenclatureManager({ onClose }) {
|
|||||||
{isLoading && <div className="nm-loading">Loading...</div>}
|
{isLoading && <div className="nm-loading">Loading...</div>}
|
||||||
|
|
||||||
<div className="nm-list">
|
<div className="nm-list">
|
||||||
{nomenclatures.map(nom => (
|
{nomenclatures.map((nom, idx) => (
|
||||||
<div
|
<div
|
||||||
key={nom.id}
|
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)}
|
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">
|
<div className="nm-list-item-info">
|
||||||
<span className="nm-list-item-name">{nom.name}</span>
|
<span className="nm-list-item-name">{nom.name}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user