Frontend v1.0.73:
- Implementado drag & drop nativo HTML5 para reordenar preguntas - Agregados estados draggedQuestion y dragOverQuestion - Handlers: handleDragStart, handleDragEnd, handleDragOver, handleDrop - Indicador visual: línea azul en drop zone - Icono de agarre (⋮⋮) con tooltip "Arrastra para reordenar" - Opacidad 50% en elemento arrastrado - Cursor 'move' indica elemento arrastrable - Mantiene función moveQuestion para compatibilidad - Reordenamiento automático al soltar
This commit is contained in:
@@ -928,6 +928,8 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
const [viewingAudit, setViewingAudit] = useState(null)
|
||||
const [auditHistory, setAuditHistory] = useState([])
|
||||
const [loadingAudit, setLoadingAudit] = useState(false)
|
||||
const [draggedQuestion, setDraggedQuestion] = useState(null)
|
||||
const [dragOverQuestion, setDragOverQuestion] = useState(null)
|
||||
const [formData, setFormData] = useState({
|
||||
section: '',
|
||||
text: '',
|
||||
@@ -1212,6 +1214,80 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
}
|
||||
}
|
||||
|
||||
// Drag & Drop handlers
|
||||
const handleDragStart = (e, question) => {
|
||||
setDraggedQuestion(question)
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
e.currentTarget.style.opacity = '0.5'
|
||||
}
|
||||
|
||||
const handleDragEnd = (e) => {
|
||||
e.currentTarget.style.opacity = '1'
|
||||
setDraggedQuestion(null)
|
||||
setDragOverQuestion(null)
|
||||
}
|
||||
|
||||
const handleDragOver = (e, question) => {
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
setDragOverQuestion(question)
|
||||
}
|
||||
|
||||
const handleDragLeave = (e) => {
|
||||
setDragOverQuestion(null)
|
||||
}
|
||||
|
||||
const handleDrop = async (e, targetQuestion) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (!draggedQuestion || draggedQuestion.id === targetQuestion.id) {
|
||||
setDraggedQuestion(null)
|
||||
setDragOverQuestion(null)
|
||||
return
|
||||
}
|
||||
|
||||
const questionsList = Object.values(questionsBySection).flat()
|
||||
const draggedIndex = questionsList.findIndex(q => q.id === draggedQuestion.id)
|
||||
const targetIndex = questionsList.findIndex(q => q.id === targetQuestion.id)
|
||||
|
||||
// Crear nueva lista con el orden actualizado
|
||||
const newList = [...questionsList]
|
||||
const [movedQuestion] = newList.splice(draggedIndex, 1)
|
||||
newList.splice(targetIndex, 0, movedQuestion)
|
||||
|
||||
// Preparar datos para el backend
|
||||
const reorderData = newList.map((q, index) => ({
|
||||
question_id: q.id,
|
||||
new_order: index
|
||||
}))
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token')
|
||||
const API_URL = import.meta.env.VITE_API_URL || ''
|
||||
|
||||
const response = await fetch(`${API_URL}/api/checklists/${checklist.id}/questions/reorder`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(reorderData)
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
loadQuestions()
|
||||
} else {
|
||||
alert('Error al reordenar pregunta')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error)
|
||||
alert('Error al reordenar pregunta')
|
||||
}
|
||||
|
||||
setDraggedQuestion(null)
|
||||
setDragOverQuestion(null)
|
||||
}
|
||||
|
||||
const questionsBySection = questions.reduce((acc, q) => {
|
||||
const section = q.section || 'Sin sección'
|
||||
if (!acc[section]) acc[section] = []
|
||||
@@ -1588,9 +1664,17 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
|
||||
return (
|
||||
<div
|
||||
key={question.id}
|
||||
className={`p-4 hover:bg-gray-50 flex justify-between items-start ${
|
||||
key={question.id}
|
||||
draggable={true}
|
||||
onDragStart={(e) => handleDragStart(e, question)}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={(e) => handleDragOver(e, question)}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={(e) => handleDrop(e, question)}
|
||||
className={`p-4 hover:bg-gray-50 flex justify-between items-start cursor-move transition-all ${
|
||||
isSubQuestion ? 'bg-blue-50 ml-8 border-l-4 border-blue-300' : ''
|
||||
} ${
|
||||
dragOverQuestion?.id === question.id ? 'border-t-4 border-indigo-500' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1">
|
||||
@@ -1628,22 +1712,11 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-4 flex gap-2 items-center">
|
||||
{/* Botones de reordenamiento */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<button
|
||||
onClick={() => moveQuestion(question.id, 'up')}
|
||||
className="text-gray-500 hover:text-indigo-600 text-xs px-2 py-1 hover:bg-indigo-50 rounded transition"
|
||||
title="Mover arriba"
|
||||
>
|
||||
▲
|
||||
</button>
|
||||
<button
|
||||
onClick={() => moveQuestion(question.id, 'down')}
|
||||
className="text-gray-500 hover:text-indigo-600 text-xs px-2 py-1 hover:bg-indigo-50 rounded transition"
|
||||
title="Mover abajo"
|
||||
>
|
||||
▼
|
||||
</button>
|
||||
{/* Indicador de drag */}
|
||||
<div className="text-gray-400 hover:text-gray-600 cursor-move px-2" title="Arrastra para reordenar">
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="h-8 w-px bg-gray-300"></div>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user