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 [viewingAudit, setViewingAudit] = useState(null)
|
||||||
const [auditHistory, setAuditHistory] = useState([])
|
const [auditHistory, setAuditHistory] = useState([])
|
||||||
const [loadingAudit, setLoadingAudit] = useState(false)
|
const [loadingAudit, setLoadingAudit] = useState(false)
|
||||||
|
const [draggedQuestion, setDraggedQuestion] = useState(null)
|
||||||
|
const [dragOverQuestion, setDragOverQuestion] = useState(null)
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
section: '',
|
section: '',
|
||||||
text: '',
|
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 questionsBySection = questions.reduce((acc, q) => {
|
||||||
const section = q.section || 'Sin sección'
|
const section = q.section || 'Sin sección'
|
||||||
if (!acc[section]) acc[section] = []
|
if (!acc[section]) acc[section] = []
|
||||||
@@ -1588,9 +1664,17 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={question.id}
|
key={question.id}
|
||||||
className={`p-4 hover:bg-gray-50 flex justify-between items-start ${
|
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' : ''
|
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">
|
<div className="flex-1">
|
||||||
@@ -1628,22 +1712,11 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 flex gap-2 items-center">
|
<div className="ml-4 flex gap-2 items-center">
|
||||||
{/* Botones de reordenamiento */}
|
{/* Indicador de drag */}
|
||||||
<div className="flex flex-col gap-1">
|
<div className="text-gray-400 hover:text-gray-600 cursor-move px-2" title="Arrastra para reordenar">
|
||||||
<button
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
onClick={() => moveQuestion(question.id, 'up')}
|
<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>
|
||||||
className="text-gray-500 hover:text-indigo-600 text-xs px-2 py-1 hover:bg-indigo-50 rounded transition"
|
</svg>
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="h-8 w-px bg-gray-300"></div>
|
<div className="h-8 w-px bg-gray-300"></div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user