v1.0.63 Backend / v1.0.57 Frontend - Edición y auditoría de preguntas
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "checklist-frontend",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.57",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -917,6 +917,9 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [showCreateForm, setShowCreateForm] = useState(false)
|
||||
const [editingQuestion, setEditingQuestion] = useState(null)
|
||||
const [viewingAudit, setViewingAudit] = useState(null)
|
||||
const [auditHistory, setAuditHistory] = useState([])
|
||||
const [loadingAudit, setLoadingAudit] = useState(false)
|
||||
const [formData, setFormData] = useState({
|
||||
section: '',
|
||||
text: '',
|
||||
@@ -962,6 +965,31 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
}
|
||||
}
|
||||
|
||||
const loadAuditHistory = async (questionId) => {
|
||||
setLoadingAudit(true)
|
||||
try {
|
||||
const token = localStorage.getItem('token')
|
||||
const API_URL = import.meta.env.VITE_API_URL || ''
|
||||
|
||||
const response = await fetch(`${API_URL}/api/questions/${questionId}/audit`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setAuditHistory(data)
|
||||
setViewingAudit(questionId)
|
||||
} else {
|
||||
alert('Error al cargar historial')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading audit history:', error)
|
||||
alert('Error al cargar historial')
|
||||
} finally {
|
||||
setLoadingAudit(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateQuestion = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -1539,6 +1567,13 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-4 flex gap-2">
|
||||
<button
|
||||
onClick={() => loadAuditHistory(question.id)}
|
||||
className="text-gray-600 hover:text-gray-700 text-sm"
|
||||
title="Ver historial de cambios"
|
||||
>
|
||||
📜 Historial
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEditQuestion(question)}
|
||||
className="text-blue-600 hover:text-blue-700 text-sm"
|
||||
@@ -1572,6 +1607,118 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Audit History Modal */}
|
||||
{viewingAudit && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-lg max-w-4xl w-full max-h-[80vh] flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="bg-gray-700 text-white p-4 rounded-t-lg flex justify-between items-center">
|
||||
<h3 className="text-lg font-bold">📜 Historial de Cambios - Pregunta #{viewingAudit}</h3>
|
||||
<button
|
||||
onClick={() => {
|
||||
setViewingAudit(null)
|
||||
setAuditHistory([])
|
||||
}}
|
||||
className="text-white hover:bg-gray-600 rounded-lg p-2 transition"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
{loadingAudit ? (
|
||||
<div className="text-center py-8 text-gray-500">Cargando historial...</div>
|
||||
) : auditHistory.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">No hay cambios registrados</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{auditHistory.map((log) => (
|
||||
<div key={log.id} className="border border-gray-200 rounded-lg p-4 hover:bg-gray-50">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-semibold ${
|
||||
log.action === 'created' ? 'bg-green-100 text-green-800' :
|
||||
log.action === 'updated' ? 'bg-blue-100 text-blue-800' :
|
||||
'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{log.action === 'created' ? '➕ Creado' :
|
||||
log.action === 'updated' ? '✏️ Modificado' :
|
||||
'🗑️ Eliminado'}
|
||||
</span>
|
||||
{log.field_name && (
|
||||
<span className="text-sm text-gray-600">
|
||||
Campo: <strong>{log.field_name}</strong>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">
|
||||
{new Date(log.created_at).toLocaleString('es-PY', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{log.field_name && (
|
||||
<div className="grid grid-cols-2 gap-4 mt-3">
|
||||
<div>
|
||||
<div className="text-xs text-gray-500 mb-1">Valor anterior:</div>
|
||||
<div className="bg-red-50 border border-red-200 rounded p-2 text-sm">
|
||||
{log.old_value || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-gray-500 mb-1">Valor nuevo:</div>
|
||||
<div className="bg-green-50 border border-green-200 rounded p-2 text-sm">
|
||||
{log.new_value || '-'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!log.field_name && (log.old_value || log.new_value) && (
|
||||
<div className="mt-2 text-sm text-gray-700">
|
||||
{log.old_value || log.new_value}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{log.comment && (
|
||||
<div className="mt-2 text-sm text-gray-600 italic">
|
||||
💬 {log.comment}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{log.user && (
|
||||
<div className="mt-2 text-xs text-gray-500">
|
||||
Por: {log.user.full_name || log.user.username}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="border-t p-4 bg-gray-50">
|
||||
<button
|
||||
onClick={() => {
|
||||
setViewingAudit(null)
|
||||
setAuditHistory([])
|
||||
}}
|
||||
className="w-full px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition"
|
||||
>
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user