All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m51s
- Se añade la lista de asignación de permisos de Suscripciones a la UI - Se añade el permiso de acceso a los reportes de suscripciones
271 lines
11 KiB
TypeScript
271 lines
11 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
import {
|
|
Box, Typography, Button, Paper, CircularProgress, Alert
|
|
} from '@mui/material';
|
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|
import SaveIcon from '@mui/icons-material/Save';
|
|
import perfilService from '../../services/Usuarios/perfilService';
|
|
import type { PermisoAsignadoDto } from '../../models/dtos/Usuarios/PermisoAsignadoDto';
|
|
import type { PerfilDto } from '../../models/dtos/Usuarios/PerfilDto';
|
|
import { usePermissions as usePagePermissions } from '../../hooks/usePermissions';
|
|
import axios from 'axios';
|
|
import PermisosChecklist from '../../components/Modals/Usuarios/PermisosChecklist';
|
|
|
|
const SECCION_PERMISSIONS_PREFIX = "SS";
|
|
|
|
const getModuloFromSeccionCodAcc = (codAcc: string): string | null => {
|
|
if (codAcc === "SS001") return "Distribución";
|
|
if (codAcc === "SS007") return "Suscripciones";
|
|
if (codAcc === "SS002") return "Contables";
|
|
if (codAcc === "SS003") return "Impresión";
|
|
if (codAcc === "SS004") return "Reportes";
|
|
if (codAcc === "SS006") return "Usuarios";
|
|
if (codAcc === "SS005") return "Radios";
|
|
return null;
|
|
};
|
|
|
|
const getModuloConceptualDelPermiso = (permisoModulo: string): string => {
|
|
const moduloLower = permisoModulo.toLowerCase();
|
|
if (moduloLower.includes("distribuidores") ||
|
|
moduloLower.includes("canillas") ||
|
|
moduloLower.includes("publicaciones distribución") ||
|
|
moduloLower.includes("zonas distribuidores") ||
|
|
moduloLower.includes("movimientos distribuidores") ||
|
|
moduloLower.includes("empresas") ||
|
|
moduloLower.includes("otros destinos") ||
|
|
moduloLower.includes("ctrl. devoluciones") ||
|
|
moduloLower.includes("movimientos canillas") ||
|
|
moduloLower.includes("salidas otros destinos")) {
|
|
return "Distribución";
|
|
}
|
|
if (moduloLower.includes("suscripciones")) {
|
|
return "Suscripciones";
|
|
}
|
|
if (moduloLower.includes("cuentas pagos") ||
|
|
moduloLower.includes("cuentas notas") ||
|
|
moduloLower.includes("cuentas tipos pagos")) {
|
|
return "Contables";
|
|
}
|
|
if (moduloLower.includes("impresión tiradas") ||
|
|
moduloLower.includes("impresión bobinas") ||
|
|
moduloLower.includes("impresión plantas") ||
|
|
moduloLower.includes("estados bobinas") ||
|
|
moduloLower.includes("tipos bobinas")) {
|
|
return "Impresión";
|
|
}
|
|
if (moduloLower.includes("usuarios") ||
|
|
moduloLower.includes("perfiles")) {
|
|
return "Usuarios";
|
|
}
|
|
if (moduloLower.includes("reportes")) {
|
|
return "Reportes";
|
|
}
|
|
if (moduloLower.includes("permisos")) {
|
|
return "Permisos (Definición)";
|
|
}
|
|
if (moduloLower.includes("radios")) {
|
|
return "Radios";
|
|
}
|
|
return permisoModulo;
|
|
};
|
|
|
|
const AsignarPermisosAPerfilPage: React.FC = () => {
|
|
const { idPerfil } = useParams<{ idPerfil: string }>();
|
|
const navigate = useNavigate();
|
|
const { tienePermiso: tienePermisoPagina, isSuperAdmin } = usePagePermissions();
|
|
|
|
const puedeAsignar = isSuperAdmin || tienePermisoPagina("PU004");
|
|
|
|
const [perfil, setPerfil] = useState<PerfilDto | null>(null);
|
|
const [permisosDisponibles, setPermisosDisponibles] = useState<PermisoAsignadoDto[]>([]);
|
|
const [permisosSeleccionados, setPermisosSeleccionados] = useState<Set<number>>(new Set());
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
|
|
|
const idPerfilNum = Number(idPerfil);
|
|
|
|
const cargarDatos = useCallback(async () => {
|
|
if (!puedeAsignar) {
|
|
setError("Acceso denegado. No tiene permiso para asignar permisos.");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
if (isNaN(idPerfilNum)) {
|
|
setError("ID de Perfil inválido.");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
setLoading(true); setError(null); setSuccessMessage(null);
|
|
try {
|
|
const [perfilData, permisosData] = await Promise.all([
|
|
perfilService.getPerfilById(idPerfilNum),
|
|
perfilService.getPermisosPorPerfil(idPerfilNum) // Esto devuelve todos los permisos con su estado 'asignado'
|
|
]);
|
|
setPerfil(perfilData);
|
|
setPermisosDisponibles(permisosData);
|
|
setPermisosSeleccionados(new Set(permisosData.filter(p => p.asignado).map(p => p.id)));
|
|
} catch (err) {
|
|
console.error(err);
|
|
setError('Error al cargar datos del perfil o permisos.');
|
|
if (axios.isAxiosError(err) && err.response?.status === 404) {
|
|
setError(`Perfil con ID ${idPerfilNum} no encontrado.`);
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [idPerfilNum, puedeAsignar]);
|
|
|
|
useEffect(() => {
|
|
cargarDatos();
|
|
}, [cargarDatos]);
|
|
|
|
const handlePermisoChange = useCallback((
|
|
permisoId: number,
|
|
asignadoViaCheckboxHijo: boolean, // Este valor es el 'e.target.checked' si el clic fue en un hijo
|
|
esPermisoSeccionClick = false,
|
|
moduloConceptualAsociado?: string // Este es el módulo conceptual del padre SSxxx o del grupo del hijo
|
|
) => {
|
|
setPermisosSeleccionados(prevSelected => {
|
|
const newSelected = new Set(prevSelected);
|
|
const permisoActual = permisosDisponibles.find(p => p.id === permisoId);
|
|
if (!permisoActual) return prevSelected;
|
|
|
|
const permisosDelModuloHijo = moduloConceptualAsociado
|
|
? permisosDisponibles.filter(p => {
|
|
const mc = getModuloConceptualDelPermiso(p.modulo); // Usar la función helper
|
|
return mc === moduloConceptualAsociado && !p.codAcc.startsWith(SECCION_PERMISSIONS_PREFIX);
|
|
})
|
|
: [];
|
|
|
|
if (esPermisoSeccionClick && moduloConceptualAsociado) {
|
|
const idPermisoSeccion = permisoActual.id;
|
|
const estabaSeccionSeleccionada = prevSelected.has(idPermisoSeccion);
|
|
const todosHijosEstabanSeleccionados = permisosDelModuloHijo.length > 0 && permisosDelModuloHijo.every(p => prevSelected.has(p.id));
|
|
const ningunHijoEstabaSeleccionado = permisosDelModuloHijo.every(p => !prevSelected.has(p.id));
|
|
|
|
|
|
if (!estabaSeccionSeleccionada) { // Estaba Off, pasa a "Solo Sección" (Indeterminate si hay hijos)
|
|
newSelected.add(idPermisoSeccion);
|
|
// NO se marcan los hijos
|
|
} else if (estabaSeccionSeleccionada && (ningunHijoEstabaSeleccionado || !todosHijosEstabanSeleccionados) && permisosDelModuloHijo.length > 0) {
|
|
// Estaba "Solo Sección" o "Parcial Hijos", pasa a "Sección + Todos los Hijos"
|
|
newSelected.add(idPermisoSeccion); // Asegurar
|
|
permisosDelModuloHijo.forEach(p => newSelected.add(p.id));
|
|
} else { // Estaba "Sección + Todos los Hijos" (o no había hijos), pasa a Off
|
|
newSelected.delete(idPermisoSeccion);
|
|
permisosDelModuloHijo.forEach(p => newSelected.delete(p.id));
|
|
}
|
|
|
|
} else if (!esPermisoSeccionClick && moduloConceptualAsociado) { // Clic en un permiso hijo
|
|
if (asignadoViaCheckboxHijo) {
|
|
newSelected.add(permisoId);
|
|
const permisoSeccionPadre = permisosDisponibles.find(
|
|
ps => ps.codAcc.startsWith(SECCION_PERMISSIONS_PREFIX) && getModuloFromSeccionCodAcc(ps.codAcc) === moduloConceptualAsociado
|
|
);
|
|
if (permisoSeccionPadre && !newSelected.has(permisoSeccionPadre.id)) {
|
|
newSelected.add(permisoSeccionPadre.id); // Marcar padre si no estaba
|
|
}
|
|
} else { // Desmarcando un hijo
|
|
newSelected.delete(permisoId);
|
|
const permisoSeccionPadre = permisosDisponibles.find(
|
|
ps => ps.codAcc.startsWith(SECCION_PERMISSIONS_PREFIX) && getModuloFromSeccionCodAcc(ps.codAcc) === moduloConceptualAsociado
|
|
);
|
|
if (permisoSeccionPadre) {
|
|
const algunOtroHijoSeleccionado = permisosDelModuloHijo.some(p => p.id !== permisoId && newSelected.has(p.id));
|
|
if (!algunOtroHijoSeleccionado && newSelected.has(permisoSeccionPadre.id)) {
|
|
// Si era el último hijo y el padre estaba marcado, NO desmarcamos el padre automáticamente.
|
|
// El estado indeterminate se encargará visualmente.
|
|
// Si quisiéramos que se desmarque el padre, aquí iría: newSelected.delete(permisoSeccionPadre.id);
|
|
}
|
|
}
|
|
}
|
|
} else { // Permiso sin módulo conceptual asociado (ej: "Permisos (Definición)")
|
|
if (asignadoViaCheckboxHijo) {
|
|
newSelected.add(permisoId);
|
|
} else {
|
|
newSelected.delete(permisoId);
|
|
}
|
|
}
|
|
|
|
if (successMessage) setSuccessMessage(null);
|
|
if (error) setError(null);
|
|
return newSelected;
|
|
});
|
|
}, [permisosDisponibles, successMessage, error]);
|
|
|
|
|
|
const handleGuardarCambios = async () => {
|
|
if (!puedeAsignar || !perfil) return;
|
|
setSaving(true); setError(null); setSuccessMessage(null);
|
|
try {
|
|
await perfilService.updatePermisosPorPerfil(perfil.id, {
|
|
permisosIds: Array.from(permisosSeleccionados)
|
|
});
|
|
setSuccessMessage('Permisos actualizados correctamente.');
|
|
await cargarDatos();
|
|
} catch (err: any) {
|
|
console.error(err);
|
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
|
? err.response.data.message
|
|
: 'Error al guardar los permisos.';
|
|
setError(message);
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}><CircularProgress /></Box>;
|
|
}
|
|
|
|
if (error && !perfil) {
|
|
return <Alert severity="error" sx={{ m: 2 }}>{error}</Alert>;
|
|
}
|
|
if (!puedeAsignar) {
|
|
return <Alert severity="error" sx={{ m: 2 }}>Acceso denegado.</Alert>;
|
|
}
|
|
if (!perfil && !loading) {
|
|
return <Alert severity="warning" sx={{ m: 2 }}>Perfil no encontrado o error al cargar.</Alert>;
|
|
}
|
|
|
|
return (
|
|
<Box sx={{ p: 1 }}>
|
|
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate('/usuarios/perfiles')} sx={{ mb: 2 }}>
|
|
Volver a Perfiles
|
|
</Button>
|
|
<Typography variant="h5" gutterBottom>
|
|
Asignar Permisos al Perfil: {perfil?.nombrePerfil || 'Cargando...'}
|
|
</Typography>
|
|
<Typography variant="body2" color="textSecondary" gutterBottom>
|
|
ID Perfil: {perfil?.id}
|
|
</Typography>
|
|
|
|
{error && !successMessage && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
|
{successMessage && <Alert severity="success" sx={{ mb: 2 }}>{successMessage}</Alert>}
|
|
|
|
<Paper sx={{ p: { xs: 1, sm: 2 }, mt: 2 }}>
|
|
<PermisosChecklist
|
|
permisosDisponibles={permisosDisponibles}
|
|
permisosSeleccionados={permisosSeleccionados}
|
|
onPermisoChange={handlePermisoChange}
|
|
disabled={saving}
|
|
/>
|
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end' }}>
|
|
<Button
|
|
variant="contained"
|
|
color="primary"
|
|
startIcon={saving ? <CircularProgress size={20} color="inherit" /> : <SaveIcon />}
|
|
onClick={handleGuardarCambios}
|
|
disabled={saving || !puedeAsignar}
|
|
>
|
|
Guardar Cambios
|
|
</Button>
|
|
</Box>
|
|
</Paper>
|
|
</Box>
|
|
);
|
|
};
|
|
export default AsignarPermisosAPerfilPage; |