✅ Mejoras de Responsividad Móvil (v1.0.86)
Sidebar ✅ Oculto por defecto en móvil (window.innerWidth < 1024px) ✅ Overlay oscuro cuando está abierto en móvil (se cierra al tocar fuera) ✅ Deslizable desde la izquierda con transiciones suaves ✅ Siempre visible en desktop (lg: breakpoint) Header ✅ Botón hamburguesa visible solo en móvil (lg:hidden) ✅ Logo escalable: 50px en móvil → 70px en desktop ✅ Título oculto en móvil para ahorrar espacio ✅ Indicador de sección: icono solo en móvil, texto completo en desktop ✅ Padding adaptable: 3px móvil → 4px tablet → 8px desktop Contenido Principal ✅ Sin margin-left en móvil (el sidebar es overlay) ✅ Padding responsive: 3px → 4px → 6px según tamaño ✅ Border-radius adaptable: xl en móvil → 2xl en desktop Modal de Inspección ✅ Ancho completo en móvil con padding mínimo (2px) ✅ Título responsive: lg (móvil) → xl (tablet) → 2xl (desktop) ✅ Altura máxima: 95vh móvil → 90vh desktop Navegador de Preguntas ✅ Botones más pequeños en móvil: 7px/8px círculos ✅ Overflow horizontal con scroll para muchas preguntas ✅ Números responsive: texto sm en móvil → lg en desktop ✅ Gaps reducidos: 1px móvil → 2px desktop Botones de Navegación ✅ Solo flechas en móvil (← →) ✅ Texto completo en desktop ("← Anterior", "Siguiente →") ✅ Padding y texto adaptables: text-sm móvil → text-base desktop ✅ Mejor uso del espacio horizontal Formularios ✅ Espaciado adaptive: space-y-3 móvil → space-y-6 desktop ✅ Labels y texto responsive: xs → sm → base ✅ Banner de modo IA con wrap en móvil La interfaz ahora es completamente funcional en móviles sin scroll horizontal, con todos los elementos accesibles y legibles! 📱✨
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "checklist-frontend",
|
||||
"private": true,
|
||||
"version": "1.0.85",
|
||||
"version": "1.0.86",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -178,7 +178,8 @@ function DashboardPage({ user, setUser }) {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [activeTab, setActiveTab] = useState('checklists')
|
||||
const [activeInspection, setActiveInspection] = useState(null)
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true)
|
||||
// Sidebar cerrado por defecto en móvil
|
||||
const [sidebarOpen, setSidebarOpen] = useState(window.innerWidth >= 1024)
|
||||
const [logoUrl, setLogoUrl] = useState(null);
|
||||
useEffect(() => {
|
||||
const fetchLogo = async () => {
|
||||
@@ -293,34 +294,43 @@ function DashboardPage({ user, setUser }) {
|
||||
/>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarOpen ? 'ml-64' : 'ml-16'}`}>
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarOpen ? 'lg:ml-64' : 'lg:ml-16'}`}>
|
||||
{/* Header */}
|
||||
<header className="bg-gradient-to-r from-indigo-600 to-purple-600 shadow-lg">
|
||||
<div className="px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="px-3 sm:px-4 lg:px-8 py-3 sm:py-4">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{/* Botón hamburguesa (solo móvil) */}
|
||||
<button
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
className="lg:hidden p-2 rounded-lg bg-white/10 hover:bg-white/20 transition"
|
||||
aria-label="Menú"
|
||||
>
|
||||
<span className="text-white text-2xl">☰</span>
|
||||
</button>
|
||||
|
||||
{/* Logo y Nombre del Sistema */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 sm:gap-3 flex-1 min-w-0">
|
||||
{logoUrl ? (
|
||||
<img src={logoUrl} alt="Logo" className="h-[70px] w-[203px] object-contain bg-white rounded-xl shadow-lg" />
|
||||
<img src={logoUrl} alt="Logo" className="h-[50px] w-[145px] sm:h-[70px] sm:w-[203px] object-contain bg-white rounded-lg sm:rounded-xl shadow-lg flex-shrink-0" />
|
||||
) : (
|
||||
<div className="h-[70px] w-[203px] bg-white rounded-xl flex items-center justify-center shadow-lg text-gray-400">Sin logo</div>
|
||||
<div className="h-[50px] w-[145px] sm:h-[70px] sm:w-[203px] bg-white rounded-lg sm:rounded-xl flex items-center justify-center shadow-lg text-gray-400 text-xs flex-shrink-0">Sin logo</div>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">AYUTEC</h1>
|
||||
<div className="hidden sm:block">
|
||||
<h1 className="text-xl sm:text-2xl font-bold text-white">AYUTEC</h1>
|
||||
<p className="text-xs text-indigo-200">Sistema Inteligente de Inspecciones</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sección Activa */}
|
||||
<div className="flex items-center gap-3 bg-white/10 backdrop-blur-sm px-4 py-2 rounded-lg border border-white/20">
|
||||
<span className="text-2xl">
|
||||
<div className="hidden sm:flex items-center gap-2 sm:gap-3 bg-white/10 backdrop-blur-sm px-3 sm:px-4 py-2 rounded-lg border border-white/20">
|
||||
<span className="text-xl sm:text-2xl">
|
||||
{activeTab === 'checklists' && '📋'}
|
||||
{activeTab === 'inspections' && '🔍'}
|
||||
{activeTab === 'users' && '👥'}
|
||||
{activeTab === 'reports' && '📊'}
|
||||
{activeTab === 'settings' && '⚙️'}
|
||||
</span>
|
||||
<span className="text-white font-semibold">
|
||||
<span className="text-white font-semibold text-sm sm:text-base">
|
||||
{activeTab === 'checklists' && 'Checklists'}
|
||||
{activeTab === 'inspections' && 'Inspecciones'}
|
||||
{activeTab === 'users' && 'Usuarios'}
|
||||
@@ -328,14 +338,24 @@ function DashboardPage({ user, setUser }) {
|
||||
{activeTab === 'settings' && 'Configuración'}
|
||||
</span>
|
||||
</div>
|
||||
{/* Indicador móvil (solo icono) */}
|
||||
<div className="sm:hidden flex items-center justify-center w-10 h-10 bg-white/10 backdrop-blur-sm rounded-lg border border-white/20">
|
||||
<span className="text-xl">
|
||||
{activeTab === 'checklists' && '📋'}
|
||||
{activeTab === 'inspections' && '🔍'}
|
||||
{activeTab === 'users' && '👥'}
|
||||
{activeTab === 'reports' && '📊'}
|
||||
{activeTab === 'settings' && '⚙️'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 p-6">
|
||||
<div className="bg-white rounded-2xl shadow-xl overflow-hidden h-full border border-indigo-100">
|
||||
<div className="p-6">
|
||||
<div className="flex-1 p-3 sm:p-4 lg:p-6">
|
||||
<div className="bg-white rounded-xl sm:rounded-2xl shadow-xl overflow-hidden h-full border border-indigo-100">
|
||||
<div className="p-3 sm:p-4 lg:p-6">
|
||||
{loading ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-gray-500">Cargando datos...</div>
|
||||
@@ -4534,12 +4554,12 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
const visibleBlock = visibleQuestions.slice(startIdx, endIdx);
|
||||
|
||||
const QuestionNavigator = () => (
|
||||
<div className="flex items-center gap-2 mb-6 justify-center">
|
||||
<div className="flex items-center gap-1 sm:gap-2 mb-4 sm:mb-6 justify-center overflow-x-auto pb-2">
|
||||
{/* Flecha izquierda */}
|
||||
<button
|
||||
onClick={() => setQuestionPage((p) => Math.max(0, p - 1))}
|
||||
disabled={questionPage === 0}
|
||||
className={`w-8 h-8 rounded-full border flex items-center justify-center text-xl font-bold transition-all select-none bg-gray-700 text-white border-gray-900 shadow-lg disabled:opacity-40 disabled:cursor-not-allowed`}
|
||||
className={`w-7 h-7 sm:w-8 sm:h-8 rounded-full border flex items-center justify-center text-lg sm:text-xl font-bold transition-all select-none bg-gray-700 text-white border-gray-900 shadow-lg disabled:opacity-40 disabled:cursor-not-allowed flex-shrink-0`}
|
||||
title="Anterior"
|
||||
>
|
||||
←
|
||||
@@ -4548,7 +4568,7 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
{visibleBlock.map((q, idx) => {
|
||||
const globalIdx = startIdx + idx;
|
||||
const answered = answers[q.id]?.value;
|
||||
let base = 'w-10 h-10 rounded-full border shadow-lg flex items-center justify-center text-lg font-bold transition-all select-none';
|
||||
let base = 'w-8 h-8 sm:w-10 sm:h-10 rounded-full border shadow-lg flex items-center justify-center text-sm sm:text-lg font-bold transition-all select-none flex-shrink-0';
|
||||
let style = '';
|
||||
if (globalIdx === currentQuestionIndex) {
|
||||
style = 'bg-blue-900 text-white border-blue-900 scale-110';
|
||||
@@ -4572,7 +4592,7 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
<button
|
||||
onClick={() => setQuestionPage((p) => Math.min(totalPages - 1, p + 1))}
|
||||
disabled={questionPage >= totalPages - 1}
|
||||
className={`w-8 h-8 rounded-full border flex items-center justify-center text-xl font-bold transition-all select-none bg-gray-700 text-white border-gray-900 shadow-lg disabled:opacity-40 disabled:cursor-not-allowed`}
|
||||
className={`w-7 h-7 sm:w-8 sm:h-8 rounded-full border flex items-center justify-center text-lg sm:text-xl font-bold transition-all select-none bg-gray-700 text-white border-gray-900 shadow-lg disabled:opacity-40 disabled:cursor-not-allowed flex-shrink-0`}
|
||||
title="Siguiente"
|
||||
>
|
||||
→
|
||||
@@ -4581,17 +4601,17 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
);
|
||||
|
||||
return (
|
||||
<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-[90vh] overflow-y-auto">
|
||||
<div className="p-6">
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-2 sm:p-4">
|
||||
<div className="bg-white rounded-lg w-full max-w-4xl max-h-[95vh] sm:max-h-[90vh] overflow-y-auto">
|
||||
<div className="p-3 sm:p-4 lg:p-6">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-2xl font-bold text-gray-900">
|
||||
<div className="flex justify-between items-start gap-2 mb-3 sm:mb-4">
|
||||
<h2 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
Nueva Inspección: {checklist.name}
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 text-2xl"
|
||||
className="text-gray-400 hover:text-gray-600 text-2xl sm:text-3xl flex-shrink-0"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
@@ -4599,22 +4619,22 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
|
||||
{/* AI Mode Banner */}
|
||||
{checklist.ai_mode !== 'off' && (
|
||||
<div className={`mb-4 p-3 rounded-lg border ${
|
||||
<div className={`mb-3 sm:mb-4 p-2 sm:p-3 rounded-lg border ${
|
||||
checklist.ai_mode === 'full'
|
||||
? 'bg-purple-50 border-purple-200'
|
||||
: 'bg-blue-50 border-blue-200'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl">🤖</span>
|
||||
<div>
|
||||
<p className={`text-sm font-medium ${
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-lg sm:text-xl flex-shrink-0">🤖</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className={`text-xs sm:text-sm font-medium ${
|
||||
checklist.ai_mode === 'full' ? 'text-purple-900' : 'text-blue-900'
|
||||
}`}>
|
||||
{checklist.ai_mode === 'full' ? 'Modo AUTOCOMPLETADO activado' : 'Modo ASISTIDO activado'}
|
||||
</p>
|
||||
<p className={`text-xs ${
|
||||
checklist.ai_mode === 'full' ? 'text-purple-700' : 'text-blue-700'
|
||||
}`}>
|
||||
} mt-1`}>
|
||||
{checklist.ai_mode === 'full'
|
||||
? 'El sistema completará automáticamente las respuestas al cargar documentos. Revisa y ajusta si es necesario.'
|
||||
: 'El sistema sugerirá observaciones al cargar documentos.'}
|
||||
@@ -4786,25 +4806,25 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
)}
|
||||
|
||||
{step === 2 && currentQuestion && (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-3 sm:space-y-4 lg:space-y-6">
|
||||
{/* Barra de navegación de preguntas */}
|
||||
<QuestionNavigator />
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="text-sm text-gray-600 mb-1">
|
||||
<div className="bg-gray-50 p-3 sm:p-4 rounded-lg">
|
||||
<div className="text-xs sm:text-sm text-gray-600 mb-1">
|
||||
Sección: <strong>{currentQuestion.section}</strong>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
<h3 className="text-base sm:text-lg font-semibold text-gray-900 leading-tight">
|
||||
{currentQuestion.text}
|
||||
</h3>
|
||||
{currentQuestion.points > 0 && (
|
||||
<div className="text-sm text-blue-600 mt-1">
|
||||
<div className="text-xs sm:text-sm text-blue-600 mt-1">
|
||||
Puntos: {currentQuestion.points}
|
||||
</div>
|
||||
)}
|
||||
{console.log('Current question:', currentQuestion.id, 'Type:', currentQuestion.type, 'Answer:', answers[currentQuestion.id])}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
{/* Answer input based on type - NO mostrar para photo_only */}
|
||||
{currentQuestion.options?.type !== 'photo_only' && (
|
||||
<div>
|
||||
@@ -4944,7 +4964,7 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-4">
|
||||
<div className="flex gap-2 sm:gap-3 pt-3 sm:pt-4 border-t">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -4954,9 +4974,10 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
}
|
||||
}}
|
||||
disabled={currentQuestionIndex === 0}
|
||||
className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="px-3 sm:px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition disabled:opacity-50 disabled:cursor-not-allowed text-sm sm:text-base"
|
||||
>
|
||||
← Anterior
|
||||
<span className="hidden sm:inline">← Anterior</span>
|
||||
<span className="sm:hidden">←</span>
|
||||
</button>
|
||||
|
||||
{currentQuestionIndex < getVisibleQuestions().length - 1 ? (
|
||||
@@ -4977,9 +4998,10 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
saveAnswer(currentQuestion.id)
|
||||
goToQuestion(currentQuestionIndex + 1)
|
||||
}}
|
||||
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition"
|
||||
className="flex-1 px-3 sm:px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition text-sm sm:text-base"
|
||||
>
|
||||
Siguiente →
|
||||
<span className="hidden sm:inline">Siguiente →</span>
|
||||
<span className="sm:hidden">→</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
@@ -4999,9 +5021,10 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
|
||||
saveAnswer(currentQuestion.id)
|
||||
proceedToSignatures()
|
||||
}}
|
||||
className="flex-1 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition"
|
||||
className="flex-1 px-3 sm:px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition text-sm sm:text-base"
|
||||
>
|
||||
Completar y Firmar →
|
||||
<span className="hidden sm:inline">Completar y Firmar →</span>
|
||||
<span className="sm:hidden">✓ Firmar</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, setSidebarOpen, onLogout }) {
|
||||
return (
|
||||
<aside className={`bg-gradient-to-b from-gray-900 via-indigo-950 to-purple-950 text-white transition-all duration-300 ${sidebarOpen ? 'w-64' : 'w-16'} flex flex-col fixed h-full z-10 shadow-2xl`}>
|
||||
<>
|
||||
{/* Overlay para cerrar sidebar en móvil */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 z-20 lg:hidden"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside className={`bg-gradient-to-b from-gray-900 via-indigo-950 to-purple-950 text-white transition-all duration-300 flex flex-col fixed h-full shadow-2xl
|
||||
${sidebarOpen ? 'w-64' : 'w-16'}
|
||||
${sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
|
||||
z-30 lg:z-10
|
||||
`}>
|
||||
{/* Sidebar Header */}
|
||||
<div className={`p-4 flex items-center ${sidebarOpen ? 'justify-between' : 'justify-center'} border-b border-indigo-800/50`}>
|
||||
{sidebarOpen && (
|
||||
@@ -13,7 +27,7 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se
|
||||
)}
|
||||
<button
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
className="p-2 rounded-lg hover:bg-indigo-800/50 transition"
|
||||
className="p-2 rounded-lg hover:bg-indigo-800/50 transition lg:block"
|
||||
title={sidebarOpen ? 'Ocultar sidebar' : 'Mostrar sidebar'}
|
||||
>
|
||||
{sidebarOpen ? '☰' : '☰'}
|
||||
@@ -150,5 +164,6 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user