mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 12:43: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>
294 lines
8.5 KiB
TypeScript
294 lines
8.5 KiB
TypeScript
export type IamPermRow = {
|
|
code: string;
|
|
label: string;
|
|
module?: string;
|
|
verb?: string;
|
|
group?: string;
|
|
};
|
|
|
|
export type IamVerb = "view" | "create" | "update" | "delete";
|
|
|
|
export type IamMatrixCell = {
|
|
code: string;
|
|
label: string;
|
|
hint: string;
|
|
};
|
|
|
|
export type IamMatrixRow = {
|
|
key: string;
|
|
label: string;
|
|
hint?: string;
|
|
cells: Partial<Record<IamVerb, IamMatrixCell>>;
|
|
};
|
|
|
|
export type IamMatrixModule = {
|
|
key: string;
|
|
label: string;
|
|
description: string;
|
|
rows: IamMatrixRow[];
|
|
codes: string[];
|
|
verbs: IamVerb[];
|
|
};
|
|
|
|
const VERB_ORDER: IamVerb[] = ["view", "create", "update", "delete"];
|
|
|
|
export const IAM_VERB_LABELS: Record<IamVerb, string> = {
|
|
view: "Ver",
|
|
create: "Crear",
|
|
update: "Editar",
|
|
delete: "Eliminar",
|
|
};
|
|
|
|
export const IAM_VERB_HINTS: Record<IamVerb, string> = {
|
|
view: "Consultar y abrir pantallas",
|
|
create: "Registrar datos nuevos",
|
|
update: "Modificar lo existente",
|
|
delete: "Eliminar o dar de baja",
|
|
};
|
|
|
|
const MODULE_META: Record<string, { label: string; description: string }> = {
|
|
workers: {
|
|
label: "Personal",
|
|
description: "Padrón de trabajadores, expediente y tablero kanban de altas.",
|
|
},
|
|
kanban: {
|
|
label: "Kanban",
|
|
description: "Tablero visual de seguimiento de personal.",
|
|
},
|
|
projects: {
|
|
label: "Obras",
|
|
description: "Alta, consulta y edición de proyectos/obras del tenant.",
|
|
},
|
|
companies: {
|
|
label: "Empresas",
|
|
description: "Empresas contratistas y subcontratos asociados a obras.",
|
|
},
|
|
budget: {
|
|
label: "Presupuesto",
|
|
description: "Partidas, importes y estructura económica de cada obra.",
|
|
},
|
|
work_program: {
|
|
label: "Programa de obra",
|
|
description: "Cronograma, avance físico e importación del programa.",
|
|
},
|
|
cost_control: {
|
|
label: "Control de costos",
|
|
description: "Comparativo programado vs real y tableros de costo.",
|
|
},
|
|
expenses: {
|
|
label: "Gastos",
|
|
description: "Captura y seguimiento de egresos por obra.",
|
|
},
|
|
warehouse: {
|
|
label: "Almacén",
|
|
description: "Entradas, salidas, stock y transferencias de materiales.",
|
|
},
|
|
payroll: {
|
|
label: "Nómina",
|
|
description: "Periodos, pagos, préstamos y líneas de nómina.",
|
|
},
|
|
documents: {
|
|
label: "Documentos y gafetes",
|
|
description: "Expediente documental de trabajadores y credenciales.",
|
|
},
|
|
project_docs: {
|
|
label: "Documentos de obra",
|
|
description: "Expediente por categoría: planos, contratos, permisos, etc.",
|
|
},
|
|
users: {
|
|
label: "Usuarios IAM",
|
|
description: "Administración de cuentas, roles y accesos del tenant.",
|
|
},
|
|
settings: {
|
|
label: "Configuración",
|
|
description: "Parámetros generales del tenant (solo propietario).",
|
|
},
|
|
reports: {
|
|
label: "Reportes e inicio",
|
|
description: "Tablero inicial y reportes operativos.",
|
|
},
|
|
};
|
|
|
|
const PROJECT_DOC_GROUP_LABELS: Record<string, string> = {
|
|
tecnico: "Planos y técnicos",
|
|
contrato: "Contrato",
|
|
permisos: "Permisos",
|
|
ambiental: "Ambiental",
|
|
imss: "IMSS",
|
|
sst: "SST",
|
|
otro: "Otros",
|
|
};
|
|
|
|
const MODULE_ORDER = [
|
|
"reports",
|
|
"projects",
|
|
"work_program",
|
|
"budget",
|
|
"cost_control",
|
|
"expenses",
|
|
"workers",
|
|
"kanban",
|
|
"documents",
|
|
"project_docs",
|
|
"warehouse",
|
|
"payroll",
|
|
"companies",
|
|
"users",
|
|
"settings",
|
|
];
|
|
|
|
function verbHint(module: string, verb: IamVerb, label: string): string {
|
|
if (module === "project_docs" && verb === "create") return "Subir archivos en esta categoría";
|
|
if (module === "warehouse" && verb === "update") return "Salidas, transferencias y cierres";
|
|
if (module === "payroll" && verb === "update") return "Armar periodo y registrar pagos";
|
|
return label;
|
|
}
|
|
|
|
export function buildIamMatrixModules(catalog: IamPermRow[]): IamMatrixModule[] {
|
|
const byModule = new Map<string, IamPermRow[]>();
|
|
for (const p of catalog) {
|
|
if (!p.module || !p.verb) continue;
|
|
const list = byModule.get(p.module) ?? [];
|
|
list.push(p);
|
|
byModule.set(p.module, list);
|
|
}
|
|
|
|
const modules: IamMatrixModule[] = [];
|
|
|
|
for (const moduleKey of MODULE_ORDER) {
|
|
const perms = byModule.get(moduleKey);
|
|
if (!perms?.length) continue;
|
|
|
|
const meta = MODULE_META[moduleKey] ?? {
|
|
label: moduleKey.replace(/_/g, " "),
|
|
description: "Permisos del módulo.",
|
|
};
|
|
|
|
const verbs = VERB_ORDER.filter((v) => perms.some((p) => p.verb === v));
|
|
const rows: IamMatrixRow[] = [];
|
|
|
|
if (moduleKey === "project_docs") {
|
|
const groups = new Map<string, IamPermRow[]>();
|
|
for (const p of perms) {
|
|
const g = p.group ?? "otro";
|
|
const list = groups.get(g) ?? [];
|
|
list.push(p);
|
|
groups.set(g, list);
|
|
}
|
|
for (const [groupKey, groupPerms] of groups) {
|
|
const cells: Partial<Record<IamVerb, IamMatrixCell>> = {};
|
|
for (const p of groupPerms) {
|
|
const verb = p.verb as IamVerb;
|
|
cells[verb] = {
|
|
code: p.code,
|
|
label: p.label,
|
|
hint: verbHint(moduleKey, verb, p.label),
|
|
};
|
|
}
|
|
rows.push({
|
|
key: groupKey,
|
|
label: PROJECT_DOC_GROUP_LABELS[groupKey] ?? groupKey,
|
|
cells,
|
|
});
|
|
}
|
|
} else {
|
|
const cells: Partial<Record<IamVerb, IamMatrixCell>> = {};
|
|
for (const p of perms) {
|
|
const verb = p.verb as IamVerb;
|
|
cells[verb] = {
|
|
code: p.code,
|
|
label: p.label,
|
|
hint: verbHint(moduleKey, verb, p.label),
|
|
};
|
|
}
|
|
rows.push({ key: moduleKey, label: meta.label, hint: meta.description, cells });
|
|
}
|
|
|
|
modules.push({
|
|
key: moduleKey,
|
|
label: meta.label,
|
|
description: meta.description,
|
|
rows,
|
|
codes: perms.map((p) => p.code),
|
|
verbs,
|
|
});
|
|
}
|
|
|
|
return modules;
|
|
}
|
|
|
|
export function filterAssignableCatalog(catalog: IamPermRow[], isOwner: boolean): IamPermRow[] {
|
|
if (isOwner) return catalog;
|
|
return catalog.filter(
|
|
(p) => !p.code.startsWith("users.") && !p.code.startsWith("settings."),
|
|
);
|
|
}
|
|
|
|
export function toggleCodes(selected: string[], codes: string[], on: boolean): string[] {
|
|
const set = new Set(selected);
|
|
for (const code of codes) {
|
|
if (on) set.add(code);
|
|
else set.delete(code);
|
|
}
|
|
return [...set];
|
|
}
|
|
|
|
export function moduleSelectionState(selected: Set<string>, codes: string[]): "none" | "partial" | "all" {
|
|
const hit = codes.filter((c) => selected.has(c)).length;
|
|
if (hit === 0) return "none";
|
|
if (hit === codes.length) return "all";
|
|
return "partial";
|
|
}
|
|
|
|
export function viewOnlyCodes(codes: string[], catalog: IamPermRow[]): string[] {
|
|
const viewSet = new Set(catalog.filter((p) => p.verb === "view").map((p) => p.code));
|
|
return codes.filter((c) => viewSet.has(c));
|
|
}
|
|
|
|
/** Conjuntos predefinidos (mismas claves que api/iam_catalog.ts ROLE_TEMPLATES). */
|
|
export const IAM_ROLE_TEMPLATES: Record<string, { label: string; permissions: string[] }> = {
|
|
admin_operativo: {
|
|
label: "Administrador operativo",
|
|
permissions: [
|
|
"workers.view", "workers.create", "workers.update", "workers.delete", "kanban.view",
|
|
"projects.view", "projects.create", "projects.update",
|
|
"companies.view", "companies.create", "companies.update",
|
|
"budget.view", "budget.create", "budget.update", "budget.delete",
|
|
"work_program.view", "work_program.create", "work_program.update",
|
|
"cost_control.view",
|
|
"expenses.view", "expenses.create", "expenses.update", "expenses.delete",
|
|
"warehouse.view", "warehouse.create", "warehouse.update",
|
|
"payroll.view", "payroll.create", "payroll.update", "payroll.delete",
|
|
"documents.view", "documents.create",
|
|
"project_docs.tecnico.view", "project_docs.tecnico.create",
|
|
"project_docs.contrato.view", "project_docs.contrato.create",
|
|
"project_docs.permisos.view", "project_docs.permisos.create",
|
|
"project_docs.ambiental.view", "project_docs.ambiental.create",
|
|
"project_docs.imss.view", "project_docs.imss.create",
|
|
"project_docs.sst.view", "project_docs.sst.create",
|
|
"project_docs.otro.view", "project_docs.otro.create",
|
|
"reports.view",
|
|
],
|
|
},
|
|
supervisor_obra: {
|
|
label: "Supervisor de obra",
|
|
permissions: [
|
|
"projects.view", "work_program.view", "work_program.update",
|
|
"project_docs.tecnico.view", "reports.view",
|
|
],
|
|
},
|
|
contador: {
|
|
label: "Contador",
|
|
permissions: [
|
|
"expenses.view", "expenses.create", "expenses.update", "expenses.delete",
|
|
"cost_control.view", "reports.view", "payroll.view",
|
|
],
|
|
},
|
|
operativo: {
|
|
label: "Operativo",
|
|
permissions: [
|
|
"workers.view", "workers.create", "workers.update",
|
|
"documents.view", "documents.create", "reports.view", "kanban.view",
|
|
],
|
|
},
|
|
};
|