first commit
This commit is contained in:
16
frontend/.dockerignore
Normal file
16
frontend/.dockerignore
Normal file
@@ -0,0 +1,16 @@
|
||||
# Frontend .dockerignore
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
Dockerfile.prod
|
||||
README.md
|
||||
.env
|
||||
.env.*
|
||||
*.md
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.DS_Store
|
||||
@@ -1,3 +1,4 @@
|
||||
# Build stage
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
@@ -5,26 +6,19 @@ WORKDIR /app
|
||||
# Copiar package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Instalar todas las dependencias (necesitamos las dev para el build)
|
||||
# Instalar dependencias
|
||||
RUN npm install
|
||||
|
||||
# Copiar código
|
||||
# Copiar código fuente
|
||||
COPY . .
|
||||
|
||||
# Build argument para la URL de la API
|
||||
ARG VITE_API_URL=http://checklist-rons-0e8a3a-63dbc4-72-61-106-199.traefik.me
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
|
||||
# Mostrar la URL que se está usando (para debug)
|
||||
RUN echo "Building with VITE_API_URL=${VITE_API_URL}"
|
||||
|
||||
# Construir la aplicación
|
||||
# Build de producción
|
||||
RUN npm run build
|
||||
|
||||
# Etapa de producción con Nginx
|
||||
# Production stage
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copiar archivos construidos
|
||||
# Copiar build al nginx
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# Copiar configuración de nginx
|
||||
@@ -33,5 +27,5 @@ COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
# Exponer puerto 80
|
||||
EXPOSE 80
|
||||
|
||||
# Comando para iniciar nginx
|
||||
# Comando por defecto
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Checklist Inteligente - Sistema de Inspecciones</title>
|
||||
<title>Syntria - Sistema Inteligente de Inspecciones</title>
|
||||
<meta name="description" content="Syntria: Sistema avanzado de inspecciones vehiculares con inteligencia artificial" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
@@ -11,24 +11,30 @@ server {
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
|
||||
|
||||
# SPA routing - todas las rutas van a index.html
|
||||
# Proxy al backend
|
||||
location /api/ {
|
||||
proxy_pass http://backend:8000/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
|
||||
# SPA fallback
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Cache para assets estáticos
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# No cache para index.html
|
||||
location = /index.html {
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
add_header Expires 0;
|
||||
}
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
|
||||
1036
frontend/src/App.jsx
1036
frontend/src/App.jsx
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,19 @@
|
||||
export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, setSidebarOpen, onLogout }) {
|
||||
return (
|
||||
<aside className={`bg-gray-900 text-white transition-all duration-300 ${sidebarOpen ? 'w-64' : 'w-16'} flex flex-col fixed h-full z-10`}>
|
||||
<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`}>
|
||||
{/* Sidebar Header */}
|
||||
<div className={`p-4 flex items-center ${sidebarOpen ? 'justify-between' : 'justify-center'} border-b border-gray-700`}>
|
||||
{sidebarOpen && <h2 className="text-xl font-bold">Sistema</h2>}
|
||||
<div className={`p-4 flex items-center ${sidebarOpen ? 'justify-between' : 'justify-center'} border-b border-indigo-800/50`}>
|
||||
{sidebarOpen && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-indigo-500 to-purple-500 rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-bold text-lg">S</span>
|
||||
</div>
|
||||
<h2 className="text-xl font-bold bg-gradient-to-r from-indigo-400 to-purple-400 bg-clip-text text-transparent">Syntria</h2>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
className="p-2 rounded-lg hover:bg-gray-800 transition"
|
||||
className="p-2 rounded-lg hover:bg-indigo-800/50 transition"
|
||||
title={sidebarOpen ? 'Ocultar sidebar' : 'Mostrar sidebar'}
|
||||
>
|
||||
{sidebarOpen ? '☰' : '☰'}
|
||||
@@ -21,8 +28,8 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se
|
||||
onClick={() => setActiveTab('checklists')}
|
||||
className={`w-full flex items-center ${sidebarOpen ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition ${
|
||||
activeTab === 'checklists'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'text-gray-300 hover:bg-gray-800'
|
||||
? 'bg-gradient-to-r from-indigo-600 to-purple-600 text-white shadow-lg'
|
||||
: 'text-indigo-200 hover:bg-indigo-900/50'
|
||||
}`}
|
||||
title={!sidebarOpen ? 'Checklists' : ''}
|
||||
>
|
||||
@@ -35,8 +42,8 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se
|
||||
onClick={() => setActiveTab('inspections')}
|
||||
className={`w-full flex items-center ${sidebarOpen ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition ${
|
||||
activeTab === 'inspections'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'text-gray-300 hover:bg-gray-800'
|
||||
? 'bg-gradient-to-r from-indigo-600 to-purple-600 text-white shadow-lg'
|
||||
: 'text-indigo-200 hover:bg-indigo-900/50'
|
||||
}`}
|
||||
title={!sidebarOpen ? 'Inspecciones' : ''}
|
||||
>
|
||||
@@ -51,8 +58,8 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se
|
||||
onClick={() => setActiveTab('users')}
|
||||
className={`w-full flex items-center ${sidebarOpen ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition ${
|
||||
activeTab === 'users'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'text-gray-300 hover:bg-gray-800'
|
||||
? 'bg-gradient-to-r from-indigo-600 to-purple-600 text-white shadow-lg'
|
||||
: 'text-indigo-200 hover:bg-indigo-900/50'
|
||||
}`}
|
||||
title={!sidebarOpen ? 'Usuarios' : ''}
|
||||
>
|
||||
@@ -65,8 +72,8 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se
|
||||
onClick={() => setActiveTab('reports')}
|
||||
className={`w-full flex items-center ${sidebarOpen ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition ${
|
||||
activeTab === 'reports'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'text-gray-300 hover:bg-gray-800'
|
||||
? 'bg-gradient-to-r from-indigo-600 to-purple-600 text-white shadow-lg'
|
||||
: 'text-indigo-200 hover:bg-indigo-900/50'
|
||||
}`}
|
||||
title={!sidebarOpen ? 'Reportes' : ''}
|
||||
>
|
||||
@@ -74,27 +81,41 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se
|
||||
{sidebarOpen && <span>Reportes</span>}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
onClick={() => setActiveTab('settings')}
|
||||
className={`w-full flex items-center ${sidebarOpen ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition ${
|
||||
activeTab === 'settings'
|
||||
? 'bg-gradient-to-r from-indigo-600 to-purple-600 text-white shadow-lg'
|
||||
: 'text-indigo-200 hover:bg-indigo-900/50'
|
||||
}`}
|
||||
title={!sidebarOpen ? 'Configuración' : ''}
|
||||
>
|
||||
<span className="text-xl">⚙️</span>
|
||||
{sidebarOpen && <span>Configuración</span>}
|
||||
</button>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/* User Info */}
|
||||
<div className="p-4 border-t border-gray-700">
|
||||
<div className="p-4 border-t border-indigo-800/50">
|
||||
<div className={`flex items-center gap-3 ${!sidebarOpen && 'justify-center'}`}>
|
||||
<div className="w-10 h-10 bg-blue-600 rounded-full flex items-center justify-center text-white font-bold flex-shrink-0">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-500 rounded-full flex items-center justify-center text-white font-bold flex-shrink-0 shadow-lg">
|
||||
{user.username.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
{sidebarOpen && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium truncate">{user.full_name || user.username}</p>
|
||||
<p className="text-xs text-gray-400">{user.role === 'admin' ? 'Admin' : 'Mecánico'}</p>
|
||||
<p className="text-sm font-medium truncate text-white">{user.full_name || user.username}</p>
|
||||
<p className="text-xs text-indigo-300">{user.role === 'admin' ? '👑 Admin' : '🔧 Mecánico'}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={onLogout}
|
||||
className={`mt-3 w-full px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition flex items-center justify-center gap-2`}
|
||||
className={`mt-3 w-full px-4 py-2 bg-gradient-to-r from-red-500 to-pink-500 text-white rounded-lg hover:from-red-600 hover:to-pink-600 transition-all transform hover:scale-105 flex items-center justify-center gap-2 shadow-lg`}
|
||||
title={!sidebarOpen ? 'Cerrar Sesión' : ''}
|
||||
>
|
||||
{sidebarOpen ? (
|
||||
|
||||
Reference in New Issue
Block a user