mirror of
https://github.com/marcogll/AnchorOS.git
synced 2026-03-15 20:24:34 +00:00
## Sistema de Kiosko ✅ - Nuevo rol 'kiosk' en enum user_role - Tabla kiosks con autenticación por API key (64 caracteres) - Funciones SQL: generate_kiosk_api_key(), is_kiosk(), get_available_resources_with_priority() - API Routes: authenticate, bookings (GET/POST), confirm, resources/available, walkin - Componentes UI: BookingConfirmation, WalkInFlow, ResourceAssignment - Página kiosko: /kiosk/[locationId]/page.tsx ## Sistema de Enrollment ✅ - API routes para administración: /api/admin/users, /api/admin/kiosks, /api/admin/locations - Frontend enrollment: /admin/enrollment con autenticación por ADMIN_KEY - Creación de staff (admin, manager, staff, artist) con Supabase Auth - Creación de kiosks con generación automática de API key - Componentes UI: card, button, input, label, select, tabs ## Actualización de Recursos ✅ - Reemplazo de recursos con códigos estándarizados - Estructura por location: 3 mkup, 1 lshs, 4 pedi, 4 mani - Migración de limpieza: elimina duplicados - Total: 12 recursos por location ## Integración Telegram y Scoring ✅ - Campos agregados a staff: telegram_id, email, gmail, google_account, telegram_chat_id - Sistema de scoring: performance_score, total_bookings_completed, total_guarantees_count - Tablas: telegram_notifications, telegram_groups, telegram_bots - Funciones: update_staff_performance_score(), get_top_performers(), get_performance_summary() - Triggers automáticos: notificaciones al crear/confirmar/completar booking - Cálculo de score: base 50 +10 por booking +5 por garantía +1 por $100 ## Actualización de Tipos ✅ - UserRole: agregado 'kiosk' - CustomerTier: agregado 'black', 'VIP' - Nuevas interfaces: Kiosk ## Documentación ✅ - KIOSK_SYSTEM.md: Documentación completa del sistema - KIOSK_IMPLEMENTATION.md: Guía rápida - ENROLLMENT_SYSTEM.md: Sistema de enrollment - RESOURCES_UPDATE.md: Actualización de recursos - PROJECT_UPDATE_JAN_2026.md: Resumen de proyecto ## Componentes UI (7) - button.tsx, card.tsx, input.tsx, label.tsx, select.tsx, tabs.tsx ## Migraciones SQL (4) - 20260116000000_add_kiosk_system.sql - 20260116010000_update_resources.sql - 20260116020000_cleanup_and_fix_resources.sql - 20260116030000_telegram_integration.sql ## Métricas - ~7,500 líneas de código - 32 archivos creados/modificados - 7 componentes UI - 10 API routes - 4 migraciones SQL
157 lines
5.1 KiB
TypeScript
157 lines
5.1 KiB
TypeScript
'use client'
|
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Clock, MapPin } from 'lucide-react'
|
|
|
|
interface ResourceAssignmentProps {
|
|
resources: Array<{
|
|
resource_id: string
|
|
resource_name: string
|
|
resource_type: string
|
|
capacity: number
|
|
priority: number
|
|
}>
|
|
start_time: string
|
|
end_time: string
|
|
}
|
|
|
|
export function ResourceAssignment({ resources, start_time, end_time }: ResourceAssignmentProps) {
|
|
const formatDateTime = (dateTime: string) => {
|
|
const date = new Date(dateTime)
|
|
return new Intl.DateTimeFormat('es-MX', {
|
|
dateStyle: 'full',
|
|
timeStyle: 'short',
|
|
timeZone: 'America/Monterrey'
|
|
}).format(date)
|
|
}
|
|
|
|
const getPriorityColor = (priority: number) => {
|
|
switch (priority) {
|
|
case 1:
|
|
return 'bg-green-100 text-green-700 border-green-300'
|
|
case 2:
|
|
return 'bg-blue-100 text-blue-700 border-blue-300'
|
|
case 3:
|
|
return 'bg-gray-100 text-gray-700 border-gray-300'
|
|
default:
|
|
return 'bg-gray-100 text-gray-700 border-gray-300'
|
|
}
|
|
}
|
|
|
|
const getPriorityLabel = (priority: number) => {
|
|
switch (priority) {
|
|
case 1:
|
|
return 'Alta'
|
|
case 2:
|
|
return 'Media'
|
|
case 3:
|
|
return 'Baja'
|
|
default:
|
|
return 'Normal'
|
|
}
|
|
}
|
|
|
|
const getTypeLabel = (type: string) => {
|
|
switch (type) {
|
|
case 'station':
|
|
return 'Estación'
|
|
case 'room':
|
|
return 'Sala'
|
|
case 'equipment':
|
|
return 'Equipo'
|
|
default:
|
|
return type
|
|
}
|
|
}
|
|
|
|
const getRecommendedResource = () => {
|
|
return resources.length > 0 ? resources[0] : null
|
|
}
|
|
|
|
const recommended = getRecommendedResource()
|
|
|
|
return (
|
|
<Card className="w-full max-w-2xl">
|
|
<CardHeader>
|
|
<CardTitle>Espacios Disponibles</CardTitle>
|
|
<CardDescription>
|
|
{formatDateTime(start_time)} - {new Date(end_time).toLocaleTimeString('es-MX', { timeZone: 'America/Monterrey' })}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{resources.length === 0 ? (
|
|
<div className="p-4 bg-red-50 border border-red-200 rounded-md text-center">
|
|
<p className="text-red-700">No hay espacios disponibles para este horario</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{recommended && (
|
|
<div className="p-4 bg-green-50 border-2 border-green-300 rounded-md">
|
|
<div className="flex items-start justify-between mb-2">
|
|
<div>
|
|
<Badge className="mb-2 bg-green-600">
|
|
Recomendado
|
|
</Badge>
|
|
<h3 className="font-semibold text-lg">{recommended.resource_name}</h3>
|
|
</div>
|
|
<Badge className={getPriorityColor(recommended.priority)}>
|
|
{getPriorityLabel(recommended.priority)}
|
|
</Badge>
|
|
</div>
|
|
<div className="space-y-1 text-sm text-muted-foreground">
|
|
<div className="flex items-center gap-2">
|
|
<MapPin className="w-4 h-4" />
|
|
<span>{getTypeLabel(recommended.resource_type)}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Clock className="w-4 h-4" />
|
|
<span>Capacidad: {recommended.capacity} persona(s)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{resources.length > 1 && (
|
|
<>
|
|
<h4 className="text-sm font-medium text-muted-foreground">
|
|
Otros espacios disponibles:
|
|
</h4>
|
|
<div className="space-y-2">
|
|
{resources.slice(1).map((resource, index) => (
|
|
<div
|
|
key={resource.resource_id}
|
|
className="p-3 bg-gray-50 border border-gray-200 rounded-md flex justify-between items-center"
|
|
>
|
|
<div>
|
|
<p className="font-medium">{resource.resource_name}</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
{getTypeLabel(resource.resource_type)} • Capacidad: {resource.capacity}
|
|
</p>
|
|
</div>
|
|
<Badge className={getPriorityColor(resource.priority)}>
|
|
{getPriorityLabel(resource.priority)}
|
|
</Badge>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
<div className="p-3 bg-blue-50 border border-blue-200 rounded-md text-sm">
|
|
<p className="font-medium text-blue-900 mb-1">
|
|
Prioridad de asignación:
|
|
</p>
|
|
<ul className="space-y-1 text-blue-800">
|
|
<li>1. Estaciones (prioridad alta)</li>
|
|
<li>2. Salas (prioridad media)</li>
|
|
<li>3. Equipo (prioridad baja)</li>
|
|
</ul>
|
|
</div>
|
|
</>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|