fmatituy/selectospromanager
0
1from nicegui import ui
2from modules.personal.services_personal import registrar_novedad, get_empleados, db_query
3from services.auth import require_auth, get_current_user
4from modules.layout import SelectosLayout
5from datetime import datetime
6
7@ui.page('/personal/novedades')
8@require_auth
9def novedades_page():
10 user = get_current_user()
11 if user.get('rol') not in ["Administrador", "Gerente", "Supervisor", "SST"]:
12 return ui.navigate.to('/')
13
14 empleados = {e['id']: e['nombre'] for e in get_empleados(activos_only=True)}
15 tipos = ['Incapacidad', 'Permiso', 'Ausencia', 'Retraso', 'Llamado de atención']
16
17 def open_modal():
18 with ui.dialog() as diag, ui.card().classes('w-[500px] p-8 rounded-3xl'):
19 ui.label('Registrar Novedad').classes('text-2xl font-black mb-6')
20
21 with ui.column().classes('w-full gap-4'):
22 emp = ui.select(empleados, label='Empleado').props('outlined rounded-xl').classes('w-full')
23 tip = ui.select(tipos, label='Tipo de Novedad').props('outlined rounded-xl').classes('w-full')
24 des = ui.textarea('Detalles / Justificación').props('outlined rounded-xl').classes('w-full')
25 with ui.row().classes('w-full gap-4'):
26 f_i = ui.input('Desde', value=datetime.now().strftime('%Y-%m-%d')).props('type=date outlined rounded-xl').classes('flex-1')
27 f_f = ui.input('Hasta', value=datetime.now().strftime('%Y-%m-%d')).props('type=date outlined rounded-xl').classes('flex-1')
28
29 async def salvar():
30 if not emp.value or not tip.value:
31 return ui.notify('Seleccione empleado y tipo', type='warning')
32
33 payload = {
34 'empleado_id': emp.value,
35 'tipo': tip.value,
36 'descripcion': des.value,
37 'fecha_inicio': f_i.value,
38 'fecha_fin': f_f.value,
39 'registrada_por': user['username']
40 }
41 registrar_novedad(payload)
42 ui.notify('Novedad registrada exitosamente', type='positive')
43 diag.close()
44 refresh_history()
45
46 ui.button('Confirmar Registro', on_click=salvar).props('unelevated color=rose-600 rounded-xl').classes('w-full mt-4 py-3 font-bold')
47 diag.open()
48
49 def refresh_history():
50 grid.clear()
51 with grid:
52 novs = db_query("SELECT n.*, e.nombre as empleado_nombre FROM novedades n JOIN empleados e ON n.empleado_id = e.id ORDER BY n.id DESC")
53 if not novs:
54 ui.label('No hay novedades registradas.').classes('text-slate-400 italic mt-4')
55 else:
56 for n in novs:
57 with ui.card().classes('w-full p-6 rounded-2xl border border-slate-100 shadow-sm bg-white mb-4'):
58 with ui.row().classes('w-full items-center justify-between'):
59 with ui.column().classes('gap-0'):
60 ui.label(n['empleado_nombre']).classes('text-base font-black text-slate-800')
61 ui.label(f"Registrado por: {n['registrada_por']}").classes('text-[10px] text-slate-400 uppercase font-bold tracking-widest')
62
63 ui.chip(n['tipo'], color='rose-100', text_color='rose-700').classes('font-bold text-[10px] rounded-pill')
64
65 ui.label(n['descripcion']).classes('text-sm text-slate-600 mt-4 bg-slate-50 p-3 rounded-xl border border-dotted border-slate-200 w-full')
66
67 with ui.row().classes('w-full mt-4 items-center gap-2 text-slate-400'):
68 ui.icon('event', size='xs')
69 ui.label(f"Válido del {n['fecha_inicio']} al {n['fecha_fin']}").classes('text-xs font-bold')
70
71 with SelectosLayout('Gestión de Novedades'):
72 with ui.row().classes('w-full justify-between items-center mb-10'):
73 with ui.column().classes('gap-1'):
74 ui.label('Novedades e Incidencias').classes('text-3xl font-black text-slate-800')
75 ui.label('Incapacidades, permisos y llamados de atención centralizados.').classes('text-sm text-slate-400 font-bold')
76 ui.button('Nueva Novedad', icon='add_alert', on_click=open_modal).props('unelevated color=rose-600 rounded-xl').classes('font-bold shadow-lg shadow-rose-200')
77
78 grid = ui.column().classes('w-full')
79 refresh_history()
80 