panels-origin/web-panel/pages/usuarios.vue
Cursor Agent ab89727522
feat: gastos, almacén central/obra y control presupuestal
Implementa el módulo integral de costos según el plan acordado:

- Esquema Liquibase: expense_entries, almacén (central + obra), IVA,
  tenant_cost_settings, permisos IAM y RLS
- Funciones RPC core: gastos, almacén, control presupuestal, sync nómina,
  cierre de almacén al concluir proyecto
- API HTTP: expenses_http, warehouse_http, cost_control_http, iam_http
- Middleware requirePermission con matriz IAM
- UI web-panel: /gastos, /almacen, /control-presupuesto, /usuarios
- Componentes BudgetItemPicker, CostSemaphore y semáforos CSS
- Smoke tests extendidos para gastos/almacén/control

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-04 04:04:17 +00:00

78 lines
2.8 KiB
Vue

<template>
<div class="usuarios-page">
<DeskTableBar v-model="tableQ" placeholder="Buscar usuario">
<Chip :label="`${users.length} usuarios`" icon="pi pi-users" />
</DeskTableBar>
<DataTable :value="listed" data-key="id" paginator :rows="25" scrollable scroll-height="flex">
<Column field="username" header="Usuario" style="width: 10rem" />
<Column field="display_name" header="Nombre" />
<Column field="role_code" header="Rol" style="width: 9rem" />
<Column field="email" header="Correo" style="width: 14rem" />
</DataTable>
<Panel header="Permisos del rol operativo (user)" class="mt-4">
<div class="perm-grid">
<div v-for="perm in permissions" :key="perm.code" class="perm-item">
<Checkbox v-model="rolePerms" :input-id="perm.code" :value="perm.code" />
<label :for="perm.code">{{ perm.label }}</label>
</div>
</div>
<Button label="Guardar permisos" icon="pi pi-save" class="mt-3" @click="saveRolePerms" />
</Panel>
</div>
</template>
<script setup lang="ts">
import { displayErrorMessage } from "~/composables/api-response";
type UserRow = { id: number; username: string; display_name: string; role_code: string; email: string };
type PermRow = { code: string; label: string };
const desk = useDesktop();
const { api } = useApi();
const toast = useToast();
const tableQ = ref("");
const users = ref<UserRow[]>([]);
const permissions = ref<PermRow[]>([]);
const rolePerms = ref<string[]>([]);
const listed = computed(() => {
const q = tableQ.value.toLowerCase();
return users.value.filter((u) => !q || [u.username, u.display_name, u.email].join(" ").toLowerCase().includes(q));
});
async function load() {
const [u, p, rp] = await Promise.all([
api<{ data?: { users?: UserRow[] } }>("/v1/iam/users"),
api<{ data?: { permissions?: PermRow[] } }>("/v1/iam/permissions"),
api<{ data?: { permissions?: string[] } }>("/v1/iam/roles/user/permissions"),
]);
users.value = u.data?.users ?? [];
permissions.value = p.data?.permissions ?? [];
rolePerms.value = rp.data?.permissions ?? [];
}
async function saveRolePerms() {
try {
const r = await api<{ message?: string }>("/v1/iam/roles/user/permissions", {
method: "PUT",
json: { permissions: rolePerms.value },
});
toast.add({ severity: "success", summary: r.message || "Permisos guardados", life: 2500 });
} catch (e) {
toast.add({ severity: "error", summary: displayErrorMessage((e as { data?: unknown }).data ?? e), life: 4000 });
}
}
onMounted(async () => {
desk.configure({ tab: "Usuarios IAM", ribbon: [] });
await load();
});
</script>
<style scoped>
.perm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 8px 16px; }
.perm-item { display: flex; align-items: center; gap: 8px; font-size: 13px; }
</style>