✅ Nueva Funcionalidad: 3 Estados para Adjuntos (Ninguno/Opcional/Obligatorio)
He implementado el sistema de 3 estados para el requisito de fotos/archivos que solicitaste. Problema Original: Solo había 2 estados: ✅ Permitir fotos (checkbox activado) ❌ No permitir fotos (checkbox desactivado) Faltaba: Fotos opcionales vs obligatorias Solución Implementada: 3 Estados disponibles: 🚫 No permitir adjuntos (photo_requirement = 'none') No se muestra el input de fotos El mecánico NO puede adjuntar archivos 📎 Opcional (photo_requirement = 'optional') Se muestra el input de fotos El mecánico PUEDE adjuntar si lo desea No es obligatorio para continuar ⚠️ Obligatorio (photo_requirement = 'required') Se muestra el input de fotos con etiqueta "OBLIGATORIO" El mecánico DEBE adjuntar al menos 1 archivo Validación bloquea continuar sin adjuntos
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "checklist-frontend",
|
||||
"private": true,
|
||||
"version": "1.0.99",
|
||||
"version": "1.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Service Worker para PWA con detección de actualizaciones
|
||||
// IMPORTANTE: Actualizar esta versión cada vez que se despliegue una nueva versión
|
||||
const CACHE_NAME = 'ayutec-v1.0.99';
|
||||
const CACHE_NAME = 'ayutec-v1.1.0';
|
||||
const urlsToCache = [
|
||||
'/',
|
||||
'/index.html'
|
||||
|
||||
@@ -1058,6 +1058,7 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
]
|
||||
},
|
||||
allow_photos: true,
|
||||
photo_requirement: 'optional',
|
||||
max_photos: 3,
|
||||
requires_comment_on_fail: false,
|
||||
send_notification: false,
|
||||
@@ -1149,6 +1150,7 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
]
|
||||
},
|
||||
allow_photos: true,
|
||||
photo_requirement: 'optional',
|
||||
max_photos: 3,
|
||||
requires_comment_on_fail: false,
|
||||
send_notification: false,
|
||||
@@ -1182,6 +1184,7 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
]
|
||||
},
|
||||
allow_photos: question.allow_photos ?? true,
|
||||
photo_requirement: question.photo_requirement || 'optional',
|
||||
max_photos: question.max_photos || 3,
|
||||
requires_comment_on_fail: question.requires_comment_on_fail || false,
|
||||
send_notification: question.send_notification || false,
|
||||
@@ -1212,6 +1215,7 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
points: parseInt(formData.points),
|
||||
options: formData.options,
|
||||
allow_photos: formData.allow_photos,
|
||||
photo_requirement: formData.photo_requirement,
|
||||
max_photos: parseInt(formData.max_photos),
|
||||
requires_comment_on_fail: formData.requires_comment_on_fail,
|
||||
send_notification: formData.send_notification,
|
||||
@@ -1236,6 +1240,7 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
]
|
||||
},
|
||||
allow_photos: true,
|
||||
photo_requirement: 'optional',
|
||||
max_photos: 3,
|
||||
requires_comment_on_fail: false,
|
||||
send_notification: false,
|
||||
@@ -1847,30 +1852,50 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.allow_photos}
|
||||
onChange={(e) => setFormData({ ...formData, allow_photos: e.target.checked })}
|
||||
className="w-4 h-4 text-purple-600 border-gray-300 rounded focus:ring-purple-500"
|
||||
/>
|
||||
<label className="ml-2 text-sm text-gray-700">
|
||||
Permitir fotos
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Máx. fotos
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="10"
|
||||
value={formData.max_photos}
|
||||
onChange={(e) => setFormData({ ...formData, max_photos: parseInt(e.target.value) })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
disabled={!formData.allow_photos}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Configuración de fotos/archivos */}
|
||||
<div className="bg-indigo-50 border border-indigo-200 rounded-lg p-4">
|
||||
<h4 className="text-sm font-semibold text-indigo-900 mb-3">
|
||||
📷 Fotos y Archivos Adjuntos
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Requisito de adjuntos
|
||||
</label>
|
||||
<select
|
||||
value={formData.photo_requirement || 'optional'}
|
||||
onChange={(e) => setFormData({ ...formData, photo_requirement: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 bg-white"
|
||||
>
|
||||
<option value="none">🚫 No permitir adjuntos</option>
|
||||
<option value="optional">📎 Opcional (puede adjuntar si quiere)</option>
|
||||
<option value="required">⚠️ Obligatorio (debe adjuntar)</option>
|
||||
</select>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
{formData.photo_requirement === 'none' && '• No se podrán adjuntar fotos/archivos'}
|
||||
{formData.photo_requirement === 'optional' && '• El mecánico puede adjuntar si lo desea'}
|
||||
{formData.photo_requirement === 'required' && '• El mecánico DEBE adjuntar al menos 1 archivo'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Máx. archivos
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="10"
|
||||
value={formData.max_photos}
|
||||
onChange={(e) => setFormData({ ...formData, max_photos: parseInt(e.target.value) })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
disabled={formData.photo_requirement === 'none'}
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Cantidad máxima de fotos/PDFs permitidos
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1992,7 +2017,15 @@ function QuestionsManagerModal({ checklist, onClose }) {
|
||||
{question.type}
|
||||
</span>
|
||||
<span>{question.points} pts</span>
|
||||
{question.allow_photos && (
|
||||
{question.photo_requirement === 'required' && (
|
||||
<span className="px-2 py-1 bg-red-100 text-red-800 rounded text-xs">
|
||||
⚠️ Fotos obligatorias
|
||||
</span>
|
||||
)}
|
||||
{question.photo_requirement === 'optional' && (
|
||||
<span>📷 Máx {question.max_photos} fotos</span>
|
||||
)}
|
||||
{(!question.photo_requirement || question.allow_photos) && !question.photo_requirement && (
|
||||
<span>📷 Máx {question.max_photos} fotos</span>
|
||||
)}
|
||||
{question.send_notification && (
|
||||
@@ -4396,12 +4429,20 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
const validateAllAnswered = () => {
|
||||
const visibleQuestions = getVisibleQuestions()
|
||||
const unanswered = visibleQuestions.filter(q => {
|
||||
const answer = answers[q.id]
|
||||
|
||||
// Para preguntas tipo photo_only, solo validar que tenga fotos
|
||||
if (q.options?.type === 'photo_only') {
|
||||
return !answers[q.id]?.photos?.length
|
||||
return !answer?.photos?.length
|
||||
}
|
||||
|
||||
// Validar fotos obligatorias (photo_requirement = 'required')
|
||||
if (q.photo_requirement === 'required' && (!answer?.photos || answer.photos.length === 0)) {
|
||||
return true // Falta adjuntar fotos obligatorias
|
||||
}
|
||||
|
||||
// Para otros tipos, validar que tenga respuesta
|
||||
return !answers[q.id]?.value
|
||||
return !answer?.value
|
||||
})
|
||||
return unanswered
|
||||
}
|
||||
@@ -5088,13 +5129,23 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
)}
|
||||
|
||||
{/* Photos */}
|
||||
{currentQuestion.allow_photos && (
|
||||
{(currentQuestion.photo_requirement !== 'none' || currentQuestion.allow_photos) && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Fotografías / Documentos *
|
||||
Fotografías / Documentos
|
||||
{currentQuestion.photo_requirement === 'required' && (
|
||||
<span className="ml-2 text-xs text-red-600 font-semibold">
|
||||
⚠️ OBLIGATORIO
|
||||
</span>
|
||||
)}
|
||||
{currentQuestion.photo_requirement === 'optional' && (
|
||||
<span className="ml-2 text-xs text-gray-600">
|
||||
(opcional)
|
||||
</span>
|
||||
)}
|
||||
{currentQuestion.max_photos && (
|
||||
<span className="ml-2 text-xs text-gray-600">
|
||||
(máximo {currentQuestion.max_photos} archivo{currentQuestion.max_photos > 1 ? 's' : ''})
|
||||
- máx {currentQuestion.max_photos} archivo{currentQuestion.max_photos > 1 ? 's' : ''}
|
||||
</span>
|
||||
)}
|
||||
{(checklist.ai_mode === 'assisted' || checklist.ai_mode === 'full') && (
|
||||
@@ -5115,7 +5166,7 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
}}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
||||
disabled={aiAnalyzing}
|
||||
required={!answers[currentQuestion.id]?.photos?.length}
|
||||
required={currentQuestion.photo_requirement === 'required' && !answers[currentQuestion.id]?.photos?.length}
|
||||
/>
|
||||
|
||||
{/* Photo Previews */}
|
||||
|
||||
Reference in New Issue
Block a user