CoolFace
Apppublic

fmatituy/selectospromanager

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
usuarios.py127 linesDownload Raw Back to personal
1from nicegui import ui
2from modules.personal.services_personal import get_usuarios_personal, create_or_update_user, get_empleados, delete_user
3from services.auth import require_auth, get_current_user
4from modules.layout import SelectosLayout
5from database.queries import get_all_roles
6
7@ui.page('/personal/usuarios')
8@require_auth
9def usuarios_personal_page():
10    user = get_current_user()
11    if user.get('rol') not in ["Administrador", "Gerente", "SST"]:
12        return ui.navigate.to('/')
13
14    roles = {r['id']: r['nombre'] for r in get_all_roles()}
15    empleados = {e['id']: e['nombre'] for e in get_empleados(activos_only=True)}
16
17    def refresh_table():
18        table.rows[:] = get_usuarios_personal()
19        table.update()
20
21    def abrir_form(usr=None):
22        with ui.dialog() as diag, ui.card().classes('w-[500px] p-0 rounded-[2rem] overflow-hidden shadow-2xl bg-white'):
23            with ui.row().classes('w-full bg-indigo-900 text-white p-8 items-center gap-4'):
24                ui.icon('manage_accounts', size='32px', color='indigo-200')
25                with ui.column().classes('gap-0'):
26                    ui.label('Configuración de Acceso').classes('text-xl font-black')
27                    ui.label('Control de credenciales y roles').classes('text-[10px] font-bold text-indigo-300 uppercase tracking-widest')
28
29            with ui.column().classes('w-full p-8 gap-6'):
30                with ui.column().classes('w-full gap-4'):
31                    ui.label('VINCULACIÓN LABORAL').classes('text-[10px] font-black text-slate-400 tracking-widest')
32                    emp = ui.select(empleados, label='Seleccionar Empleado *', value=usr['empleado_id'] if usr else None).props('outlined rounded-2xl').classes('w-full')
33                    
34                    ui.label('CREDENCIALES DE ACCESO').classes('text-[10px] font-black text-slate-400 tracking-widest mt-2')
35                    with ui.row().classes('w-full grid grid-cols-2 gap-4'):
36                        uname = ui.input('Usuario / Login *', value=usr['username'] if usr else '').props('outlined rounded-2xl').classes('w-full')
37                        with ui.column().classes('gap-1'):
38                            pwd = ui.input('Nueva Contraseña *' if not usr else 'Reestablecer Contraseña', password=True, password_toggle_button=True).props('outlined rounded-2xl').classes('w-full')
39                            if usr:
40                                ui.label('Por seguridad (Hash SHA-256), no se puede ver la anterior. Escriba una nueva si desea cambiarla.').classes('text-[9px] font-bold text-slate-400 leading-tight')
41                    
42                    rol_s = ui.select(roles, label='Nivel de Acceso (Rol) *', value=usr['rol_id'] if usr else None).props('outlined rounded-2xl').classes('w-full')
43                    
44                    with ui.card().classes('w-full p-4 bg-slate-50 border-none rounded-2xl'):
45                        act = ui.switch('Cuenta Habilitada / Activa', value=bool(usr['activo']) if usr else True).props('color=indigo').classes('font-bold text-slate-700')
46
47                async def salvar():
48                    if not uname.value or (not usr and not pwd.value):
49                        return ui.notify('Usuario y contraseña son obligatorios', type='warning', position='top')
50                    
51                    payload = {
52                        'id': usr['id'] if usr else None,
53                        'username': uname.value,
54                        'password': pwd.value if pwd.value else None,
55                        'rol_id': rol_s.value,
56                        'activo': 1 if act.value else 0,
57                        'empleado_id': emp.value,
58                        'nombre': empleados.get(emp.value, 'Usuario')
59                    }
60                    create_or_update_user(payload)
61                    ui.notify('USUARIO CONFIGURADO EXITOSAMENTE', type='positive', position='top')
62                    diag.close()
63                    refresh_table()
64
65                with ui.row().classes('w-full justify-between items-center mt-6 pt-6 border-t border-slate-100'):
66                    ui.button('Cancelar', on_click=diag.close).props('flat color=slate-400')
67                    ui.button('GUARDAR CAMBIOS', icon='check_circle', on_click=salvar).props('unelevated color=indigo-600 rounded-2xl px-6').classes('font-black h-12 shadow-lg shadow-indigo-100')
68        diag.open()
69
70    with SelectosLayout('Gestión de Usuarios'):
71        with ui.row().classes('w-full justify-between items-center mb-10'):
72            with ui.column().classes('gap-1'):
73                ui.label('Accesos al Sistema').classes('text-3xl font-black text-slate-800')
74                ui.label('Administra quién puede entrar y con qué permisos.').classes('text-sm text-slate-400 font-bold')
75            ui.button('Crear Nuevo Usuario', icon='person_add', on_click=lambda: abrir_form()).props('unelevated color=indigo rounded-xl').classes('font-bold shadow-lg shadow-indigo-100')
76
77        cols = [
78            {'name': 'username', 'label': 'Usuario / Login', 'field': 'username', 'align': 'left', 'sortable': True},
79            {'name': 'empleado', 'label': 'Nombre Real', 'field': 'empleado_nombre', 'align': 'left'},
80            {'name': 'rol', 'label': 'Rol Asignado', 'field': 'rol_nombre', 'align': 'center'},
81            {'name': 'activo', 'label': 'Estado', 'field': 'activo', 'align': 'center'},
82            {'name': 'actions', 'label': 'Ajustes', 'field': 'actions', 'align': 'center'},
83        ]
84        
85        table = ui.table(columns=cols, rows=get_usuarios_personal(), row_key='id').classes('w-full border-none shadow-sm rounded-2xl bg-white')
86        table.props('flat header-classes="bg-slate-50 text-slate-400 font-bold uppercase text-[10px]"')
87        
88        table.add_slot('body-cell-activo', '''
89            <q-td :props="props">
90                <q-chip :color="props.value == 1 ? 'indigo-100' : 'slate-200'" :text-color="props.value == 1 ? 'indigo-900' : 'slate-500'" dense class="font-bold text-[10px] uppercase">
91                    {{ props.value == 1 ? 'Activo' : 'Suspendido' }}
92                </q-chip>
93            </q-td>
94        ''')
95        
96        table.add_slot('body-cell-actions', '''
97            <q-td :props="props">
98                <div class="row items-center justify-center gap-2">
99                    <q-btn flat round color="indigo" icon="edit" size="sm" @click="$parent.$emit('manage', props.row)">
100                        <q-tooltip>Modificar acceso o resetear contraseña</q-tooltip>
101                    </q-btn>
102                    <q-btn flat round color="red" icon="delete" size="sm" @click="$parent.$emit('delete_user', props.row)">
103                        <q-tooltip>Eliminar Usuario</q-tooltip>
104                    </q-btn>
105                </div>
106            </q-td>
107        ''')
108        table.on('manage', lambda e: abrir_form(e.args))
109        
110        def confirmar_eliminacion(row):
111            def proceder():
112                delete_user(row['id'])
113                refresh_table()
114                ui.notify(f"Usuario '{row['username']}' eliminado del sistema.", type='positive', icon='delete')
115                conf.close()
116                
117            with ui.dialog() as conf, ui.card().classes('p-6 rounded-[2rem] max-w-sm text-center'):
118                ui.icon('warning', size='xl', color='red')
119                ui.label(f"¿Eliminar cuenta de {row['username']}?").classes('text-xl font-black mt-2')
120                ui.label('Esta acción es irreversible y retirará todos sus permisos.').classes('text-xs text-slate-500 font-bold mb-6')
121                with ui.row().classes('w-full gap-2 justify-center'):
122                    ui.button('Cancelar', on_click=conf.close).props('flat text-color=slate-500 rounded-xl')
123                    ui.button('Eliminar Permanente', on_click=proceder, color='red').props('unelevated rounded-xl font-bold px-6 shadow-md')
124            conf.open()
125            
126        table.on('delete_user', lambda e: confirmar_eliminacion(e.args))
127