3 Commits

Author SHA1 Message Date
40186f76b3 readme actualizado para la Rama 2025-12-10 11:26:03 -03:00
9df97a144a Merge branch 'develop' 2025-12-09 02:52:45 -03:00
e7d64e0094 Merge pull request 'develop' (#1) from develop into main
Reviewed-on: #1
2025-11-26 01:15:20 +00:00
20 changed files with 8858 additions and 20 deletions

207
MIGRATION_GUIDE.md Normal file
View File

@@ -0,0 +1,207 @@
# Guía de Migración a TypeScript + shadcn/ui
## ✅ Sprint 0 Completado - Setup Inicial
Se han creado los siguientes archivos de configuración:
### Archivos TypeScript
-`frontend/tsconfig.json` - Configuración principal de TypeScript
-`frontend/tsconfig.node.json` - Configuración para archivos de Node (vite.config)
-`frontend/vite.config.ts` - Vite config migrado a TypeScript
-`frontend/tailwind.config.ts` - Tailwind config migrado a TypeScript
-`frontend/src/vite-env.d.ts` - Types para variables de entorno
### shadcn/ui
-`frontend/components.json` - Configuración de shadcn/ui
-`frontend/src/lib/utils.ts` - Helper cn() para clases de Tailwind
## 📋 Siguientes Pasos
### 1. Instalar Dependencias
Ejecuta en PowerShell desde `c:\Users\ADM\Downloads\checklist-mvp\frontend`:
```powershell
# Instalar TypeScript y tipos
npm install -D typescript @types/react @types/react-dom @types/node
npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
# Instalar shadcn/ui y dependencias
npm install @radix-ui/react-avatar @radix-ui/react-dialog @radix-ui/react-dropdown-menu
npm install @radix-ui/react-label @radix-ui/react-select @radix-ui/react-separator
npm install @radix-ui/react-slot @radix-ui/react-tabs @radix-ui/react-toast
npm install class-variance-authority tailwind-merge tailwindcss-animate vaul
# Verificar que ya tienes instalados (según package.json actual)
# - clsx (ya instalado)
# - lucide-react (ya instalado)
# - react 18.2.0 (ya instalado)
# - tailwindcss (ya instalado)
```
### 2. Instalar Componentes shadcn/ui
```powershell
# Ejecutar desde frontend/
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card
npx shadcn-ui@latest add badge
npx shadcn-ui@latest add input
npx shadcn-ui@latest add select
npx shadcn-ui@latest add dialog
npx shadcn-ui@latest add table
npx shadcn-ui@latest add tabs
npx shadcn-ui@latest add toast
npx shadcn-ui@latest add separator
npx shadcn-ui@latest add dropdown-menu
```
Esto creará automáticamente los componentes en `frontend/src/components/ui/`
### 3. Actualizar CSS (index.css)
Agregar al **inicio** de `frontend/src/index.css` (antes del código existente):
```css
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
```
### 4. Renombrar Archivos (IMPORTANTE)
```powershell
# Renombrar main.jsx a main.tsx
Rename-Item frontend/src/main.jsx frontend/src/main.tsx
# Eliminar archivos .js antiguos (ya tenemos .ts)
Remove-Item frontend/vite.config.js
Remove-Item frontend/tailwind.config.js
```
### 5. Actualizar index.html
Cambiar la línea del script en `frontend/index.html`:
**De:**
```html
<script type="module" src="/src/main.jsx"></script>
```
**A:**
```html
<script type="module" src="/src/main.tsx"></script>
```
### 6. Verificar que Compila
```powershell
cd frontend
npm run dev
```
Si todo está bien, deberías ver:
```
VITE v5.0.11 ready in XXX ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
```
### 7. Crear Rama Git
```powershell
# Desde la raíz del proyecto
git checkout -b feature/typescript-shadcn-migration
git add frontend/tsconfig.json frontend/tsconfig.node.json
git add frontend/vite.config.ts frontend/tailwind.config.ts
git add frontend/components.json frontend/src/vite-env.d.ts
git add frontend/src/lib/utils.ts MIGRATION_GUIDE.md
git commit -m "feat(frontend): configure TypeScript + shadcn/ui setup
- Add tsconfig.json with strict mode
- Configure path aliases (@/*)
- Add shadcn/ui configuration
- Create lib/utils.ts helper
- Add TypeScript environment types
Backend version: sin cambios
Frontend version: 1.4.0 -> 1.5.0-beta"
git push -u origin feature/typescript-shadcn-migration
```
## 🎯 Estado Actual
- ✅ Configuración TypeScript creada
- ✅ Configuración shadcn/ui creada
- ⏳ Pendiente: Instalar dependencias npm
- ⏳ Pendiente: Renombrar archivos
- ⏳ Pendiente: Instalar componentes shadcn
- ⏳ Pendiente: Actualizar index.css
- ⏳ Pendiente: Verificar compilación
## 📝 Notas
- El sistema actual (App.jsx) seguirá funcionando mientras no se renombre a .tsx
- Los archivos TypeScript coexisten con JavaScript hasta migración completa
- shadcn/ui se puede usar inmediatamente después de instalar componentes
- No hay cambios en el backend por ahora
## 🚀 Siguiente Sprint
Una vez completados estos pasos, continuaremos con:
- Sprint 1: Crear types para el módulo de Checklists
- Sprint 2: Backend del módulo de Recambios
- Sprint 3: Frontend del módulo de Recambios con TypeScript + shadcn

410
README_RECAMBIOS.md Normal file
View File

@@ -0,0 +1,410 @@
# 📦 Sistema de Gestión de Recambios - Feature Branch
> **Rama**: `feature/typescript-shadcn-migration`
> **Estado**: 🚧 En Desarrollo
> **Stack**: TypeScript + shadcn/ui + React 18 + FastAPI
---
## 🎯 Objetivo de esta Rama
Desarrollar un **Sistema Integral de Gestión de Recambios** para talleres mecánicos, integrado con el sistema existente de checklists. Este módulo añade capacidades completas de gestión de pedidos, proveedores, inventario y facturación.
## 🏗️ Arquitectura del Módulo
### Frontend (TypeScript + shadcn/ui)
```
frontend/src/
├── modules/
│ └── recambios/
│ ├── RecambiosApp.tsx ✅ Componente principal con navegación
│ ├── pages/
│ │ ├── KanbanPedidos.tsx 🚧 Tablero Kanban de estados
│ │ ├── PanelProveedor.tsx 🚧 Gestión de proveedores
│ │ ├── GestionAlbaranes.tsx 🚧 Registro de albaranes
│ │ ├── GestionClientes.tsx 🚧 Base de datos clientes
│ │ ├── Inventario.tsx 🚧 Control de stock
│ │ └── Configuracion.tsx 🚧 Ajustes del módulo
│ ├── components/
│ │ ├── PedidoCard.tsx 🚧 Tarjeta de pedido
│ │ ├── ProveedorForm.tsx 🚧 Formulario proveedor
│ │ ├── AlbaranForm.tsx 🚧 Formulario albarán
│ │ └── EstadoBadge.tsx 🚧 Badge de estado
│ └── types/
│ └── recambios.types.ts 🚧 Interfaces TypeScript
├── components/ui/ ✅ shadcn/ui components
│ ├── button.tsx
│ ├── card.tsx
│ ├── badge.tsx
│ └── tabs.tsx
└── lib/
└── utils.ts ✅ Utilidades (cn helper)
```
### Backend (FastAPI + SQLAlchemy)
```
backend/app/
├── modules/
│ └── recambios/
│ ├── __init__.py
│ ├── models.py 🚧 15+ modelos SQLAlchemy
│ ├── schemas.py 🚧 Pydantic schemas
│ ├── routes.py 🚧 Endpoints REST
│ └── services.py 🚧 Lógica de negocio
└── migrations/
└── add_recambios_tables.sql 🚧 Schema inicial
```
---
## 📊 Modelo de Datos
### Entidades Principales
#### 1. **rec_clientes**
```sql
- id, nombre, telefono, email, direccion, cif_nif
- vehiculos_asociados (JSON)
- historial_pedidos
```
#### 2. **rec_proveedores**
```sql
- id, nombre, contacto, telefono, email
- plazo_entrega_dias, condiciones_pago
- rating, notas
```
#### 3. **rec_referencias**
```sql
- id, codigo_referencia, descripcion
- proveedor_id, precio_compra, precio_venta
- estado (13 estados posibles)
- stock_actual, ubicacion_almacen
```
#### 4. **rec_pedidos_cliente**
```sql
- id, cliente_id, fecha_pedido, total
- estado, prioridad, asesor_id
- vehiculo_datos (JSON)
```
#### 5. **rec_lineas_pedido**
```sql
- id, pedido_cliente_id, referencia_id
- cantidad, precio_unitario, subtotal
- estado_linea
```
#### 6. **rec_pedidos_proveedor**
```sql
- id, proveedor_id, fecha_pedido
- estado, total_pedido, recambista_id
```
#### 7. **rec_albaranes**
```sql
- id, pedido_proveedor_id, numero_albaran
- fecha_recepcion, recibido_por
- observaciones, foto_albaran_url
```
#### 8. **rec_devoluciones**
```sql
- id, referencia_id, motivo, estado
- fecha_devolucion, fecha_abono
- importe_abonado
```
#### 9. **rec_movimientos_stock**
```sql
- id, referencia_id, tipo_movimiento
- cantidad, fecha, usuario_id
```
#### 10. **rec_facturas**
```sql
- id, cliente_id, numero_factura
- fecha_emision, total, estado
- archivo_pdf_url
```
---
## 🔄 Máquina de Estados (13 Estados)
```
┌─────────────────────┐
│ pendiente_decidir │ ← Estado inicial
└──────────┬──────────┘
├─→ pedido_proveedor
├─→ alternativa
├─→ usado
└─→ descartada
┌───────────┴───────────┐
│ │
▼ ▼
recibida recibida_parcial
│ │
├─→ devolucion │
├─→ entregada ──────────┘
└─→ instalada
└─→ facturada
```
### Transiciones de Estado
1. **pendiente_decidir** → Usuario decide acción
2. **pedido_proveedor** → Pedido enviado al proveedor
3. **alternativa** → Buscando opción alternativa
4. **usado** → Referencia de segunda mano
5. **recibida** → Llegó completa del proveedor
6. **recibida_parcial** → Llegó solo parte del pedido
7. **devolucion** → En proceso de devolución
8. **entregada** → Entregada al cliente
9. **instalada** → Instalada en el vehículo
10. **facturada** → Facturada al cliente
11. **descartada** → No se procede con la referencia
12. **pendiente_abono** → Esperando abono de devolución
13. **abonada** → Devolución abonada
---
## 👥 Roles y Permisos
### Asesor de Servicio
- ✅ Crear pedidos de cliente
- ✅ Consultar estado de referencias
- ✅ Modificar prioridades
- ✅ Ver historial de cliente
### Recambista
- ✅ Gestionar pedidos a proveedores
- ✅ Registrar albaranes
- ✅ Gestionar stock
- ✅ Actualizar estados de referencias
- ✅ Procesar devoluciones
### Administración
- ✅ Generar facturas
- ✅ Ver reportes financieros
- ✅ Gestionar proveedores
- ✅ Configurar precios
---
## 🎨 Diseño UI con shadcn/ui
### Componentes Implementados ✅
- **Button** - Botones con variantes (default, outline, ghost, destructive)
- **Card** - Tarjetas con header, content, footer
- **Badge** - Etiquetas de estado con colores
- **Tabs** - Navegación entre secciones
### Componentes Pendientes 🚧
- **Input** - Campos de texto
- **Select** - Dropdowns
- **Dialog** - Modales
- **Table** - Tablas de datos
- **Toast** - Notificaciones
- **Dropdown Menu** - Menús contextuales
- **Date Picker** - Selector de fechas
- **Form** - Formularios validados
---
## 📋 Plan de Implementación
### ✅ Sprint 0: Fundación (COMPLETADO)
- [x] Configuración TypeScript
- [x] Setup shadcn/ui
- [x] Componentes base UI
- [x] Estructura de carpetas
- [x] Prototipo Kanban visual
### 🚧 Sprint 1: Backend Core (EN PROGRESO)
- [ ] Modelos SQLAlchemy (15 tablas)
- [ ] Migraciones de base de datos
- [ ] Schemas Pydantic
- [ ] Endpoints CRUD básicos
- [ ] Autenticación y permisos
### 🔜 Sprint 2: Frontend Básico
- [ ] Páginas principales (6 tabs)
- [ ] Formularios con validación
- [ ] Integración API
- [ ] Gestión de estado
- [ ] Tipos TypeScript completos
### 🔜 Sprint 3: Funcionalidades Avanzadas
- [ ] Kanban drag & drop
- [ ] Upload de fotos (albaranes)
- [ ] Generación de PDFs
- [ ] Sistema de notificaciones
- [ ] Búsqueda y filtros
### 🔜 Sprint 4: Integración y Testing
- [ ] Integración con sistema de checklists
- [ ] Tests unitarios
- [ ] Tests E2E
- [ ] Optimización de rendimiento
- [ ] Documentación completa
---
## 🚀 Comandos de Desarrollo
### Levantar Entorno de Desarrollo
```powershell
# Frontend
cd frontend
npm run dev
# Backend
cd backend
docker-compose up -d
```
### Ver el Módulo de Recambios
1. Inicia sesión como **admin**
2. Haz clic en **📦 Recambios** en el sidebar
3. Navega entre las 6 pestañas
### Compilar TypeScript
```powershell
cd frontend
npm run build
```
---
## 🔧 Tecnologías Utilizadas
### Frontend
- **React 18** - Framework UI
- **TypeScript 5.9** - Type safety
- **shadcn/ui** - Component library
- **Radix UI** - Primitive components
- **Tailwind CSS 3.4** - Utility-first CSS
- **Lucide React** - Icon library
- **Vite 5** - Build tool
### Backend
- **FastAPI** - Python web framework
- **SQLAlchemy** - ORM
- **PostgreSQL 15** - Base de datos
- **Pydantic** - Data validation
- **JWT** - Autenticación
---
## 📝 Convenciones de Código
### TypeScript
```typescript
// Interfaces con prefijo 'I' o sufijo del dominio
interface Pedido {
id: number;
cliente: string;
estado: EstadoPedido;
}
// Tipos para estados
type EstadoPedido = 'pendiente_decidir' | 'pedido_proveedor' | 'recibida';
// Componentes con tipo React.FC
const PedidoCard: React.FC<PedidoCardProps> = ({ pedido }) => { ... }
```
### Python
```python
# Modelos con tabla explícita
class RecCliente(Base):
__tablename__ = "rec_clientes"
# Schemas con validación
class PedidoCreate(BaseModel):
cliente_id: int
referencias: List[LineaPedido]
# Rutas con prefijo /api/recambios
@router.post("/api/recambios/pedidos")
```
---
## 🐛 Debugging
### Frontend
```powershell
# Ver errores TypeScript
npm run dev
# Limpiar caché
rm -r node_modules .vite
npm install
```
### Backend
```powershell
# Ver logs
docker-compose logs -f backend
# Entrar al contenedor
docker-compose exec backend bash
# Verificar tablas
docker-compose exec postgres psql -U user -d checklist_db -c "\dt rec_*"
```
---
## 📚 Documentación Relacionada
- [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) - Guía de migración TypeScript
- [AI_FUNCTIONALITY.md](./AI_FUNCTIONALITY.md) - Sistema de IA (checklists)
- [API_DOCUMENTATION.md](./API_DOCUMENTATION.md) - Documentación API
- [shadcn/ui Docs](https://ui.shadcn.com/) - Documentación componentes
---
## 🎯 Próximos Pasos
1. **Crear modelos de backend** (15 tablas)
2. **Escribir schemas Pydantic**
3. **Implementar endpoints CRUD**
4. **Desarrollar páginas TypeScript**
5. **Conectar frontend con API**
6. **Agregar drag & drop al Kanban**
7. **Implementar búsqueda y filtros**
8. **Testing completo**
---
## 👨‍💻 Contribución
Esta es una rama de desarrollo activo. Para contribuir:
1. Crea una sub-rama desde `feature/typescript-shadcn-migration`
2. Implementa tu funcionalidad
3. Haz commit con mensajes descriptivos
4. Crea PR hacia esta rama (NO hacia main)
---
## 📞 Contacto
**Proyecto**: Ayutec - Sistema Inteligente de Inspecciones
**Versión Recambios**: 0.1.0 (Alpha)
**Última Actualización**: Diciembre 2025

17
frontend/components.json Normal file
View File

@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

7305
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,22 +9,38 @@
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toast": "^1.2.15",
"axios": "^1.6.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.0",
"lucide-react": "^0.303.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.1",
"react-router-dom": "^6.21.1",
"axios": "^1.6.5",
"react-signature-canvas": "^1.0.6",
"lucide-react": "^0.303.0",
"clsx": "^2.1.0",
"react-markdown": "^9.0.1"
"tailwind-merge": "^3.4.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@types/node": "^24.10.2",
"@types/react": "^18.3.27",
"@types/react-dom": "^18.3.7",
"@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.1",
"typescript": "^5.9.3",
"vite": "^5.0.11"
}
}
}

View File

@@ -5,6 +5,7 @@ import ReactMarkdown from 'react-markdown'
import Sidebar from './Sidebar'
import QuestionTypeEditor from './QuestionTypeEditor'
import QuestionAnswerInput from './QuestionAnswerInput'
import RecambiosApp from './modules/recambios/RecambiosApp'
function App() {
const [user, setUser] = useState(null)
@@ -70,13 +71,18 @@ function App() {
};
useEffect(() => {
// Verificar si hay token guardado
const token = localStorage.getItem('token')
const userData = localStorage.getItem('user')
if (token && userData) {
setUser(JSON.parse(userData))
// MODO TEST: Auto-login como admin sin verificación
const testUser = {
id: 1,
username: 'admin',
full_name: 'Administrador Test',
role: 'admin',
employee_code: 'ADMIN001'
}
localStorage.setItem('token', 'test-token-bypass')
localStorage.setItem('user', JSON.stringify(testUser))
setUser(testUser)
setLoading(false)
}, [])
@@ -420,6 +426,7 @@ function DashboardPage({ user, setUser }) {
{activeTab === 'inspections' && '🔍'}
{activeTab === 'users' && '👥'}
{activeTab === 'reports' && '📊'}
{activeTab === 'recambios' && '📦'}
{activeTab === 'settings' && '⚙️'}
</span>
<span className="text-white font-semibold text-sm sm:text-base">
@@ -427,6 +434,7 @@ function DashboardPage({ user, setUser }) {
{activeTab === 'inspections' && 'Inspecciones'}
{activeTab === 'users' && 'Usuarios'}
{activeTab === 'reports' && 'Reportes'}
{activeTab === 'recambios' && 'Recambios'}
{activeTab === 'settings' && 'Configuración'}
</span>
</div>
@@ -437,6 +445,7 @@ function DashboardPage({ user, setUser }) {
{activeTab === 'inspections' && '🔍'}
{activeTab === 'users' && '👥'}
{activeTab === 'reports' && '📊'}
{activeTab === 'recambios' && '📦'}
{activeTab === 'settings' && '⚙️'}
</span>
</div>
@@ -461,6 +470,8 @@ function DashboardPage({ user, setUser }) {
/>
) : activeTab === 'inspections' ? (
<InspectionsTab inspections={inspections} user={user} onUpdate={loadData} onContinue={setActiveInspection} />
) : activeTab === 'recambios' ? (
<RecambiosApp />
) : activeTab === 'settings' ? (
<SettingsTab user={user} />
) : activeTab === 'api-tokens' ? (

View File

@@ -101,6 +101,22 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se
</button>
</li>
)}
{user.role === 'admin' && (
<li>
<button
onClick={() => setActiveTab('recambios')}
className={`w-full flex items-center ${sidebarOpen ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition ${
activeTab === 'recambios'
? 'bg-gradient-to-r from-emerald-600 to-teal-600 text-white shadow-lg'
: 'text-indigo-200 hover:bg-indigo-900/50'
}`}
title={!sidebarOpen ? 'Recambios' : ''}
>
<span className="text-xl">📦</span>
{sidebarOpen && <span>Recambios</span>}
</button>
</li>
)}
{user.role === 'admin' && (
<>
<li>

View File

@@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View File

@@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -0,0 +1,79 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -0,0 +1,53 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -2,13 +2,76 @@
@tailwind components;
@tailwind utilities;
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
* {
border-color: hsl(var(--border));
}
body {
background-color: hsl(var(--background));
color: hsl(var(--foreground));
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
code {

View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -0,0 +1,405 @@
import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
Package,
Truck,
ClipboardList,
FileText,
Users,
Settings,
TrendingUp,
AlertCircle,
CheckCircle2,
Clock,
Search
} from 'lucide-react';
interface Pedido {
id: number;
referencia: string;
cliente: string;
estado: 'pendiente_decidir' | 'pedido_proveedor' | 'recibida' | 'entregada';
prioridad: 'alta' | 'media' | 'baja';
total: number;
fecha: string;
}
const RecambiosApp: React.FC = () => {
const [activeTab, setActiveTab] = useState('kanban');
// Datos de ejemplo
const pedidosEjemplo: Pedido[] = [
{
id: 1,
referencia: 'REF-2024-001',
cliente: 'Juan Pérez - VW Golf GTI',
estado: 'pendiente_decidir',
prioridad: 'alta',
total: 450.00,
fecha: '2024-12-09'
},
{
id: 2,
referencia: 'REF-2024-002',
cliente: 'María González - Seat León',
estado: 'pedido_proveedor',
prioridad: 'media',
total: 320.00,
fecha: '2024-12-08'
},
{
id: 3,
referencia: 'REF-2024-003',
cliente: 'Carlos Díaz - Audi A4',
estado: 'recibida',
prioridad: 'alta',
total: 680.00,
fecha: '2024-12-07'
}
];
const getEstadoColor = (estado: Pedido['estado']) => {
const colores = {
pendiente_decidir: 'bg-yellow-100 text-yellow-800 border-yellow-300',
pedido_proveedor: 'bg-blue-100 text-blue-800 border-blue-300',
recibida: 'bg-green-100 text-green-800 border-green-300',
entregada: 'bg-gray-100 text-gray-800 border-gray-300'
};
return colores[estado];
};
const getPrioridadColor = (prioridad: Pedido['prioridad']) => {
const colores = {
alta: 'bg-red-500 text-white',
media: 'bg-orange-500 text-white',
baja: 'bg-green-500 text-white'
};
return colores[prioridad];
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 p-6">
{/* Header */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<div>
<h1 className="text-4xl font-bold text-slate-900 mb-2">
Sistema de Gestión de Recambios
</h1>
<p className="text-slate-600">
Panel unificado para gestión integral de pedidos, proveedores y entregas
</p>
</div>
<div className="flex gap-3">
<Button variant="outline" className="gap-2">
<Search className="w-4 h-4" />
Buscar
</Button>
<Button className="gap-2">
<Package className="w-4 h-4" />
Nuevo Pedido
</Button>
</div>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<Card className="border-l-4 border-l-yellow-500">
<CardHeader className="pb-2">
<CardDescription>Pendientes Decidir</CardDescription>
<CardTitle className="text-3xl">12</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center gap-2 text-sm text-yellow-600">
<AlertCircle className="w-4 h-4" />
<span>Requieren atención</span>
</div>
</CardContent>
</Card>
<Card className="border-l-4 border-l-blue-500">
<CardHeader className="pb-2">
<CardDescription>Pedidos Proveedor</CardDescription>
<CardTitle className="text-3xl">8</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center gap-2 text-sm text-blue-600">
<Clock className="w-4 h-4" />
<span>En proceso</span>
</div>
</CardContent>
</Card>
<Card className="border-l-4 border-l-green-500">
<CardHeader className="pb-2">
<CardDescription>Recibidas</CardDescription>
<CardTitle className="text-3xl">5</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center gap-2 text-sm text-green-600">
<CheckCircle2 className="w-4 h-4" />
<span>Listas para entregar</span>
</div>
</CardContent>
</Card>
<Card className="border-l-4 border-l-purple-500">
<CardHeader className="pb-2">
<CardDescription>Volumen Mes</CardDescription>
<CardTitle className="text-3xl">15.4K</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center gap-2 text-sm text-purple-600">
<TrendingUp className="w-4 h-4" />
<span>+12% vs mes anterior</span>
</div>
</CardContent>
</Card>
</div>
</div>
{/* Tabs Navigation */}
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
<TabsList className="grid grid-cols-6 w-full max-w-4xl">
<TabsTrigger value="kanban" className="gap-2">
<ClipboardList className="w-4 h-4" />
Kanban
</TabsTrigger>
<TabsTrigger value="proveedores" className="gap-2">
<Truck className="w-4 h-4" />
Proveedores
</TabsTrigger>
<TabsTrigger value="albaranes" className="gap-2">
<FileText className="w-4 h-4" />
Albaranes
</TabsTrigger>
<TabsTrigger value="clientes" className="gap-2">
<Users className="w-4 h-4" />
Clientes
</TabsTrigger>
<TabsTrigger value="inventario" className="gap-2">
<Package className="w-4 h-4" />
Inventario
</TabsTrigger>
<TabsTrigger value="config" className="gap-2">
<Settings className="w-4 h-4" />
Config
</TabsTrigger>
</TabsList>
{/* Kanban View */}
<TabsContent value="kanban" className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
{/* Columna: Pendiente Decidir */}
<div className="space-y-3">
<div className="flex items-center gap-2 mb-4">
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
<h3 className="font-semibold text-slate-700">Pendiente Decidir</h3>
<Badge variant="secondary">1</Badge>
</div>
{pedidosEjemplo
.filter(p => p.estado === 'pendiente_decidir')
.map(pedido => (
<Card key={pedido.id} className="cursor-pointer hover:shadow-lg transition-shadow">
<CardHeader className="pb-3">
<div className="flex items-start justify-between mb-2">
<Badge className={getPrioridadColor(pedido.prioridad)}>
{pedido.prioridad}
</Badge>
<span className="text-xs text-slate-500">{pedido.referencia}</span>
</div>
<CardTitle className="text-base">{pedido.cliente}</CardTitle>
</CardHeader>
<CardContent>
<div className="flex justify-between items-center">
<span className="text-lg font-bold text-slate-900">
{pedido.total.toFixed(2)}
</span>
<span className="text-xs text-slate-500">{pedido.fecha}</span>
</div>
</CardContent>
</Card>
))}
</div>
{/* Columna: Pedido Proveedor */}
<div className="space-y-3">
<div className="flex items-center gap-2 mb-4">
<div className="w-3 h-3 rounded-full bg-blue-500"></div>
<h3 className="font-semibold text-slate-700">Pedido Proveedor</h3>
<Badge variant="secondary">1</Badge>
</div>
{pedidosEjemplo
.filter(p => p.estado === 'pedido_proveedor')
.map(pedido => (
<Card key={pedido.id} className="cursor-pointer hover:shadow-lg transition-shadow">
<CardHeader className="pb-3">
<div className="flex items-start justify-between mb-2">
<Badge className={getPrioridadColor(pedido.prioridad)}>
{pedido.prioridad}
</Badge>
<span className="text-xs text-slate-500">{pedido.referencia}</span>
</div>
<CardTitle className="text-base">{pedido.cliente}</CardTitle>
</CardHeader>
<CardContent>
<div className="flex justify-between items-center">
<span className="text-lg font-bold text-slate-900">
{pedido.total.toFixed(2)}
</span>
<span className="text-xs text-slate-500">{pedido.fecha}</span>
</div>
</CardContent>
</Card>
))}
</div>
{/* Columna: Recibida */}
<div className="space-y-3">
<div className="flex items-center gap-2 mb-4">
<div className="w-3 h-3 rounded-full bg-green-500"></div>
<h3 className="font-semibold text-slate-700">Recibida</h3>
<Badge variant="secondary">1</Badge>
</div>
{pedidosEjemplo
.filter(p => p.estado === 'recibida')
.map(pedido => (
<Card key={pedido.id} className="cursor-pointer hover:shadow-lg transition-shadow">
<CardHeader className="pb-3">
<div className="flex items-start justify-between mb-2">
<Badge className={getPrioridadColor(pedido.prioridad)}>
{pedido.prioridad}
</Badge>
<span className="text-xs text-slate-500">{pedido.referencia}</span>
</div>
<CardTitle className="text-base">{pedido.cliente}</CardTitle>
</CardHeader>
<CardContent>
<div className="flex justify-between items-center">
<span className="text-lg font-bold text-slate-900">
{pedido.total.toFixed(2)}
</span>
<span className="text-xs text-slate-500">{pedido.fecha}</span>
</div>
</CardContent>
</Card>
))}
</div>
{/* Columna: Entregada */}
<div className="space-y-3">
<div className="flex items-center gap-2 mb-4">
<div className="w-3 h-3 rounded-full bg-gray-400"></div>
<h3 className="font-semibold text-slate-700">Entregada</h3>
<Badge variant="secondary">0</Badge>
</div>
<Card className="border-dashed border-2 bg-slate-50">
<CardContent className="flex items-center justify-center h-32 text-slate-400">
Sin pedidos entregados
</CardContent>
</Card>
</div>
</div>
</TabsContent>
{/* Proveedores View */}
<TabsContent value="proveedores">
<Card>
<CardHeader>
<CardTitle>Panel de Proveedores</CardTitle>
<CardDescription>
Gestión de proveedores, pedidos y seguimiento de entregas
</CardDescription>
</CardHeader>
<CardContent className="flex items-center justify-center h-64 text-slate-400">
<div className="text-center">
<Truck className="w-16 h-16 mx-auto mb-4 text-slate-300" />
<p>Próximamente: Panel completo de proveedores</p>
</div>
</CardContent>
</Card>
</TabsContent>
{/* Albaranes View */}
<TabsContent value="albaranes">
<Card>
<CardHeader>
<CardTitle>Gestión de Albaranes</CardTitle>
<CardDescription>
Registro y seguimiento de albaranes de entrega
</CardDescription>
</CardHeader>
<CardContent className="flex items-center justify-center h-64 text-slate-400">
<div className="text-center">
<FileText className="w-16 h-16 mx-auto mb-4 text-slate-300" />
<p>Próximamente: Sistema de albaranes</p>
</div>
</CardContent>
</Card>
</TabsContent>
{/* Clientes View */}
<TabsContent value="clientes">
<Card>
<CardHeader>
<CardTitle>Gestión de Clientes</CardTitle>
<CardDescription>
Base de datos de clientes y su historial de pedidos
</CardDescription>
</CardHeader>
<CardContent className="flex items-center justify-center h-64 text-slate-400">
<div className="text-center">
<Users className="w-16 h-16 mx-auto mb-4 text-slate-300" />
<p>Próximamente: Panel de clientes</p>
</div>
</CardContent>
</Card>
</TabsContent>
{/* Inventario View */}
<TabsContent value="inventario">
<Card>
<CardHeader>
<CardTitle>Control de Inventario</CardTitle>
<CardDescription>
Stock, ubicaciones y movimientos de recambios
</CardDescription>
</CardHeader>
<CardContent className="flex items-center justify-center h-64 text-slate-400">
<div className="text-center">
<Package className="w-16 h-16 mx-auto mb-4 text-slate-300" />
<p>Próximamente: Sistema de inventario</p>
</div>
</CardContent>
</Card>
</TabsContent>
{/* Configuración View */}
<TabsContent value="config">
<Card>
<CardHeader>
<CardTitle>Configuración del Sistema</CardTitle>
<CardDescription>
Preferencias, categorías y parámetros del módulo
</CardDescription>
</CardHeader>
<CardContent className="flex items-center justify-center h-64 text-slate-400">
<div className="text-center">
<Settings className="w-16 h-16 mx-auto mb-4 text-slate-300" />
<p>Próximamente: Panel de configuración</p>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
};
export default RecambiosApp;

9
frontend/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

View File

@@ -0,0 +1,80 @@
import type { Config } from 'tailwindcss'
const config = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
} satisfies Config
export default config

31
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,31 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* Path mapping */
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,8 +1,14 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
host: true,
port: 5173,

22
frontend/vite.config.ts Normal file
View File

@@ -0,0 +1,22 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
},
},
})