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:
2025-11-30 22:59:38 -03:00
parent 7820f143ac
commit 45ad650bac
3 changed files with 85 additions and 47 deletions

View File

@@ -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"
>
&#8592;
@@ -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"
>
&#8594;
@@ -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>

View File

@@ -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>
</>
)
}