mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 14:13:18 +00:00
Replace the flat checkbox list with modules grouped in a Ver/Crear/Editar/ Eliminar grid, module descriptions, search, view-only shortcuts, and quick template apply. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
132 lines
4.3 KiB
Vue
132 lines
4.3 KiB
Vue
<template>
|
|
<div v-if="detail.kind === 'iam-user' && detail.iamUser" class="iam-detail">
|
|
<div class="section-title">Usuario</div>
|
|
<p><strong>{{ detail.iamUser.display_name }}</strong></p>
|
|
<p class="muted">{{ detail.iamUser.username }}</p>
|
|
<p>Rol: {{ detail.iamUser.role_label || detail.iamUser.role_code }}</p>
|
|
<p>Estado: {{ detail.iamUser.status }}</p>
|
|
<p v-if="detail.iamUser.is_owner" class="owner-badge">Cuenta propietaria (solo SaaS)</p>
|
|
<template v-if="!detail.iamUser.is_owner">
|
|
<div class="section-title mt-3">Alcance</div>
|
|
<p v-if="detail.iamUser.scope_all_projects">Todas las obras</p>
|
|
<p v-else>Obras asignadas: {{ (detail.iamUser.project_ids ?? []).length }}</p>
|
|
<p>Central: {{ detail.iamUser.warehouse_central ? "Sí" : "No" }}</p>
|
|
<p>Obras: {{ detail.iamUser.warehouse_projects !== false ? "Sí" : "No" }}</p>
|
|
</template>
|
|
</div>
|
|
|
|
<div v-else-if="detail.kind === 'iam-role' && detail.iamRole" class="iam-detail">
|
|
<div class="section-title">Matriz del rol</div>
|
|
<p class="role-heading"><strong>{{ detail.iamRole.label }}</strong> ({{ detail.iamRole.code }})</p>
|
|
<Message v-if="detail.iamRole.is_system" severity="info" :closable="false">Rol de sistema: no editable</Message>
|
|
<template v-else>
|
|
<p class="matrix-intro">
|
|
Marque columnas <strong>Ver / Crear / Editar / Eliminar</strong> por módulo. Pase el cursor sobre cada casilla para ver qué habilita.
|
|
</p>
|
|
<IamPermissionMatrix
|
|
v-model="localPerms"
|
|
:catalog="assignableCatalog"
|
|
:templates="roleTemplates"
|
|
:disabled="!canEdit"
|
|
/>
|
|
<Button
|
|
v-if="canEdit"
|
|
label="Guardar permisos"
|
|
icon="pi pi-save"
|
|
class="mt-3 w-full"
|
|
size="small"
|
|
@click="savePerms"
|
|
/>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { displayErrorMessage } from "~/composables/api-response";
|
|
import {
|
|
filterAssignableCatalog,
|
|
IAM_ROLE_TEMPLATES,
|
|
type IamPermRow,
|
|
} from "~/composables/iamPermissionMatrix";
|
|
|
|
const props = defineProps<{
|
|
detail: {
|
|
kind: string;
|
|
iamUser?: Record<string, unknown>;
|
|
iamRole?: Record<string, unknown> & { id?: number; is_system?: boolean };
|
|
};
|
|
}>();
|
|
|
|
const { api } = useApi();
|
|
const toast = useToast();
|
|
const perms = usePermissions();
|
|
const catalog = ref<IamPermRow[]>([]);
|
|
const localPerms = ref<string[]>([]);
|
|
|
|
const canEdit = computed(() =>
|
|
perms.can("users.update") && props.detail.kind === "iam-role" && !props.detail.iamRole?.is_system,
|
|
);
|
|
|
|
const assignableCatalog = computed(() => filterAssignableCatalog(catalog.value, perms.isOwner.value));
|
|
|
|
const roleTemplates = computed(() => {
|
|
if (!perms.isOwner.value) {
|
|
const { admin_operativo: _a, ...rest } = IAM_ROLE_TEMPLATES;
|
|
return rest;
|
|
}
|
|
return IAM_ROLE_TEMPLATES;
|
|
});
|
|
|
|
async function loadCatalog() {
|
|
const r = await api<{ permissions?: IamPermRow[] }>("/v1/iam/permissions");
|
|
catalog.value = (r.permissions ?? []).map((p) => ({
|
|
code: p.code,
|
|
label: p.label,
|
|
module: p.module,
|
|
verb: p.verb,
|
|
group: (p as { group?: string }).group,
|
|
}));
|
|
}
|
|
|
|
async function loadRolePerms(roleId: number) {
|
|
const r = await api<{ permissions?: string[] }>(`/v1/iam/roles/${roleId}/permissions`);
|
|
localPerms.value = r.permissions ?? [];
|
|
}
|
|
|
|
async function savePerms() {
|
|
const roleId = props.detail.iamRole?.id;
|
|
if (!roleId) return;
|
|
try {
|
|
await api(`/v1/iam/roles/${roleId}/permissions`, {
|
|
method: "PUT",
|
|
json: { permissions: localPerms.value },
|
|
});
|
|
toast.add({ severity: "success", summary: "Permisos guardados", life: 2500 });
|
|
} catch (e) {
|
|
toast.add({ severity: "error", summary: displayErrorMessage((e as { data?: unknown }).data ?? e), life: 4000 });
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => props.detail,
|
|
async (d) => {
|
|
if (!catalog.value.length) await loadCatalog();
|
|
if (d.kind === "iam-role" && d.iamRole?.id) await loadRolePerms(Number(d.iamRole.id));
|
|
},
|
|
{ immediate: true, deep: true },
|
|
);
|
|
</script>
|
|
|
|
<style scoped>
|
|
.iam-detail { font-size: 13px; }
|
|
.section-title { font-weight: 600; margin-bottom: 4px; }
|
|
.role-heading { margin: 0 0 8px; }
|
|
.matrix-intro {
|
|
font-size: 11px;
|
|
line-height: 1.4;
|
|
color: var(--text-color-secondary);
|
|
margin: 0 0 10px;
|
|
}
|
|
.muted { color: var(--text-color-secondary); }
|
|
.owner-badge { color: var(--orange-500); font-weight: 600; }
|
|
</style>
|