Implementación AnomalIA - Fix de dropdowns y permisos.
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 5m17s
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 5m17s
This commit is contained in:
@@ -44,6 +44,7 @@ const getModuloConceptualDelPermiso = (permisoModulo: string): string => {
|
||||
if (moduloLower.includes("impresión tiradas") ||
|
||||
moduloLower.includes("impresión bobinas") || // Cubre "Impresión Bobinas" y "Tipos Bobinas"
|
||||
moduloLower.includes("impresión plantas") ||
|
||||
moduloLower.includes("estados bobinas") ||
|
||||
moduloLower.includes("tipos bobinas")) { // Añadido explícitamente
|
||||
return "Impresión";
|
||||
}
|
||||
|
||||
@@ -228,15 +228,12 @@ const UsuarioFormModal: React.FC<UsuarioFormModalProps> = ({
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', mt: 0.5 }}> {/* Fila 5 (Checkboxes) */}
|
||||
<Box sx={{ flex: 1, minWidth: 'calc(50% - 8px)'}}>
|
||||
<FormControlLabel control={<Checkbox checked={supAdmin} onChange={(e) => setSupAdmin(e.target.checked)} disabled={loading}/>} label="Super Administrador" />
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', mt: 0.5 }}>
|
||||
<Box sx={{ flex: 1, minWidth: 'calc(50% - 8px)'}}>
|
||||
<FormControlLabel control={<Checkbox checked={debeCambiarClave} onChange={(e) => setDebeCambiarClave(e.target.checked)} disabled={loading}/>} label="Debe Cambiar Clave" />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box> {/* Fin contenedor principal de campos */}
|
||||
</Box>
|
||||
|
||||
|
||||
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { createContext, useState, useContext, type ReactNode, useEffect } from 'react';
|
||||
import type { LoginResponseDto } from '../models/dtos/Usuarios/LoginResponseDto';
|
||||
import React, { createContext, useState, useContext, type ReactNode, useEffect, useCallback } from 'react';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import { getAlertas, marcarAlertaLeida, marcarGrupoComoLeido, type AlertaGenericaDto } from '../services/Anomalia/alertaService';
|
||||
|
||||
// Interfaz para los datos del usuario que guardaremos en el contexto
|
||||
export interface UserContextData {
|
||||
userId: number;
|
||||
username: string;
|
||||
@@ -11,33 +10,37 @@ export interface UserContextData {
|
||||
debeCambiarClave: boolean;
|
||||
perfil: string;
|
||||
idPerfil: number;
|
||||
permissions: string[]; // Guardamos los codAcc
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
// Interfaz para el payload decodificado del JWT
|
||||
interface DecodedJwtPayload {
|
||||
sub: string; // User ID (viene como string)
|
||||
name: string; // Username
|
||||
given_name?: string; // Nombre (estándar, pero verifica tu token)
|
||||
family_name?: string; // Apellido (estándar, pero verifica tu token)
|
||||
role: string | string[]; // Puede ser uno o varios roles
|
||||
sub: string;
|
||||
name: string;
|
||||
given_name?: string;
|
||||
family_name?: string;
|
||||
role: string | string[];
|
||||
perfil: string;
|
||||
idPerfil: string; // (viene como string)
|
||||
debeCambiarClave: string; // (viene como string "True" o "False")
|
||||
permission?: string | string[]; // Nuestros claims de permiso (codAcc)
|
||||
[key: string]: any; // Para otros claims
|
||||
idPerfil: string;
|
||||
debeCambiarClave: string;
|
||||
permission?: string | string[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
isAuthenticated: boolean;
|
||||
user: UserContextData | null; // Usar el tipo extendido
|
||||
user: UserContextData | null;
|
||||
token: string | null;
|
||||
isLoading: boolean;
|
||||
alertas: AlertaGenericaDto[];
|
||||
showForcedPasswordChangeModal: boolean;
|
||||
isPasswordChangeForced: boolean;
|
||||
|
||||
marcarAlertaComoLeida: (idAlerta: number) => Promise<void>;
|
||||
marcarGrupoDeAlertasLeido: (tipoAlerta: string, idEntidad: number) => Promise<void>;
|
||||
|
||||
setShowForcedPasswordChangeModal: (show: boolean) => void;
|
||||
passwordChangeCompleted: () => void;
|
||||
login: (apiLoginResponse: LoginResponseDto) => void; // Recibe el DTO de la API
|
||||
login: (apiLoginResponse: any) => void; // DTO no definido aquí, usamos any
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
@@ -50,24 +53,57 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [showForcedPasswordChangeModal, setShowForcedPasswordChangeModal] = useState<boolean>(false);
|
||||
const [isPasswordChangeForced, setIsPasswordChangeForced] = useState<boolean>(false);
|
||||
const [alertas, setAlertas] = useState<AlertaGenericaDto[]>([]);
|
||||
|
||||
const processTokenAndSetUser = (jwtToken: string) => {
|
||||
const fetchAlertas = useCallback(async (currentUser: UserContextData | null) => {
|
||||
if (currentUser && (currentUser.esSuperAdmin || currentUser.permissions.includes('AL001'))) {
|
||||
try {
|
||||
const data = await getAlertas();
|
||||
setAlertas(data || []);
|
||||
} catch (error) {
|
||||
console.error("Error al obtener alertas en AuthContext:", error);
|
||||
setAlertas([]);
|
||||
}
|
||||
} else {
|
||||
setAlertas([]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const marcarAlertaComoLeida = async (idAlerta: number) => {
|
||||
try {
|
||||
await marcarAlertaLeida(idAlerta);
|
||||
await fetchAlertas(user); // Refresca el estado global
|
||||
} catch (error) {
|
||||
console.error("Error al marcar alerta como leída:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const marcarGrupoDeAlertasLeido = async (tipoAlerta: string, idEntidad: number) => {
|
||||
try {
|
||||
await marcarGrupoComoLeido({ tipoAlerta, idEntidad });
|
||||
await fetchAlertas(user); // Refresca el estado global
|
||||
} catch (error) {
|
||||
console.error(`Error al marcar grupo ${tipoAlerta}/${idEntidad} como leído:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
const logout = useCallback(() => {
|
||||
localStorage.removeItem('authToken');
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
setIsAuthenticated(false);
|
||||
setShowForcedPasswordChangeModal(false);
|
||||
setIsPasswordChangeForced(false);
|
||||
setAlertas([]);
|
||||
}, []);
|
||||
|
||||
const processTokenAndSetUser = useCallback((jwtToken: string) => {
|
||||
try {
|
||||
const decodedToken = jwtDecode<DecodedJwtPayload>(jwtToken);
|
||||
|
||||
// Verificar expiración (opcional, pero buena práctica aquí también)
|
||||
const currentTime = Date.now() / 1000;
|
||||
if (decodedToken.exp && decodedToken.exp < currentTime) {
|
||||
console.warn("Token expirado al procesar.");
|
||||
logout(); // Llama a logout que limpia todo
|
||||
return;
|
||||
logout(); return;
|
||||
}
|
||||
|
||||
let permissions: string[] = [];
|
||||
if (decodedToken.permission) {
|
||||
permissions = Array.isArray(decodedToken.permission) ? decodedToken.permission : [decodedToken.permission];
|
||||
}
|
||||
|
||||
const userForContext: UserContextData = {
|
||||
userId: parseInt(decodedToken.sub, 10),
|
||||
username: decodedToken.name,
|
||||
@@ -75,27 +111,23 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
esSuperAdmin: decodedToken.role === "SuperAdmin" || (Array.isArray(decodedToken.role) && decodedToken.role.includes("SuperAdmin")),
|
||||
debeCambiarClave: decodedToken.debeCambiarClave?.toLowerCase() === 'true',
|
||||
idPerfil: decodedToken.idPerfil ? parseInt(decodedToken.idPerfil, 10) : 0,
|
||||
permissions: permissions,
|
||||
perfil: decodedToken.perfil || 'Usuario' // Asignar un valor por defecto si no existe
|
||||
permissions: Array.isArray(decodedToken.permission) ? decodedToken.permission : (decodedToken.permission ? [decodedToken.permission] : []),
|
||||
perfil: decodedToken.perfil || 'Usuario'
|
||||
};
|
||||
|
||||
setToken(jwtToken);
|
||||
setUser(userForContext);
|
||||
setIsAuthenticated(true);
|
||||
localStorage.setItem('authToken', jwtToken);
|
||||
localStorage.setItem('authUser', JSON.stringify(userForContext)); // Guardar el usuario procesado
|
||||
|
||||
// Lógica para el modal de cambio de clave
|
||||
if (userForContext.debeCambiarClave) {
|
||||
setShowForcedPasswordChangeModal(true);
|
||||
setIsPasswordChangeForced(true);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error al decodificar o procesar token:", error);
|
||||
logout(); // Limpiar estado si el token es inválido
|
||||
console.error("Error al decodificar token:", error);
|
||||
logout();
|
||||
}
|
||||
};
|
||||
}, [logout]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
@@ -104,20 +136,18 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
processTokenAndSetUser(storedToken);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
}, [processTokenAndSetUser]);
|
||||
|
||||
const login = (apiLoginResponse: LoginResponseDto) => {
|
||||
processTokenAndSetUser(apiLoginResponse.token); // Procesar el token recibido
|
||||
};
|
||||
useEffect(() => {
|
||||
if (user && isAuthenticated) {
|
||||
fetchAlertas(user);
|
||||
const intervalId = setInterval(() => fetchAlertas(user), 300000); // Refresca cada 5 mins
|
||||
return () => clearInterval(intervalId);
|
||||
}
|
||||
}, [user, isAuthenticated, fetchAlertas]);
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem('authToken');
|
||||
localStorage.removeItem('authUser');
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
setIsAuthenticated(false);
|
||||
setShowForcedPasswordChangeModal(false);
|
||||
setIsPasswordChangeForced(false);
|
||||
const login = (apiLoginResponse: any) => {
|
||||
processTokenAndSetUser(apiLoginResponse.token);
|
||||
};
|
||||
|
||||
const passwordChangeCompleted = () => {
|
||||
@@ -138,6 +168,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
<AuthContext.Provider value={{
|
||||
isAuthenticated, user, token, isLoading,
|
||||
showForcedPasswordChangeModal, isPasswordChangeForced,
|
||||
alertas, marcarAlertaComoLeida, marcarGrupoDeAlertasLeido,
|
||||
setShowForcedPasswordChangeModal, passwordChangeCompleted,
|
||||
login, logout
|
||||
}}>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// src/layouts/MainLayout.tsx
|
||||
import React, { type ReactNode, useState, useEffect, useMemo } // << AÑADIR useMemo
|
||||
from 'react';
|
||||
import React, { type ReactNode, useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
Box, AppBar, Toolbar, Typography, Tabs, Tab, Paper,
|
||||
IconButton, Menu, MenuItem, ListItemIcon, ListItemText, Divider,
|
||||
Button
|
||||
Button, Badge
|
||||
} from '@mui/material';
|
||||
import AccountCircle from '@mui/icons-material/AccountCircle';
|
||||
import LockResetIcon from '@mui/icons-material/LockReset';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import NotificationsIcon from '@mui/icons-material/Notifications';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import ChangePasswordModal from '../components/Modals/Usuarios/ChangePasswordModal';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
@@ -18,6 +17,16 @@ interface MainLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// --- Helper para dar nombres legibles a los tipos de alerta ---
|
||||
const getTipoAlertaLabel = (tipoAlerta: string): string => {
|
||||
switch (tipoAlerta) {
|
||||
case 'DevolucionAnomala': return 'Devoluciones Anómalas';
|
||||
case 'ComportamientoSistema': return 'Anomalías del Sistema';
|
||||
case 'FaltaDeDatos': return 'Falta de Datos';
|
||||
default: return tipoAlerta;
|
||||
}
|
||||
};
|
||||
|
||||
// Definición original de módulos
|
||||
const allAppModules = [
|
||||
{ label: 'Inicio', path: '/', requiredPermission: null }, // Inicio siempre visible
|
||||
@@ -31,22 +40,36 @@ const allAppModules = [
|
||||
];
|
||||
|
||||
const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
||||
// Obtenemos todo lo necesario del AuthContext, INCLUYENDO LAS ALERTAS
|
||||
const {
|
||||
user, // user ya está disponible aquí
|
||||
logout,
|
||||
isAuthenticated,
|
||||
isPasswordChangeForced,
|
||||
showForcedPasswordChangeModal,
|
||||
setShowForcedPasswordChangeModal,
|
||||
passwordChangeCompleted
|
||||
user, logout, isAuthenticated, isPasswordChangeForced,
|
||||
showForcedPasswordChangeModal, setShowForcedPasswordChangeModal,
|
||||
passwordChangeCompleted,
|
||||
alertas
|
||||
} = useAuth();
|
||||
|
||||
const { tienePermiso, isSuperAdmin } = usePermissions(); // <<--- OBTENER HOOK DE PERMISOS
|
||||
|
||||
// El resto de los hooks locales no cambian
|
||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState<number | false>(false);
|
||||
const [anchorElUserMenu, setAnchorElUserMenu] = useState<null | HTMLElement>(null);
|
||||
const [anchorElAlertasMenu, setAnchorElAlertasMenu] = useState<null | HTMLElement>(null);
|
||||
|
||||
// --- Agrupación de alertas para el menú ---
|
||||
const gruposDeAlertas = useMemo(() => {
|
||||
if (!alertas || !Array.isArray(alertas)) return [];
|
||||
|
||||
const groups = alertas.reduce((acc, alerta) => {
|
||||
const label = getTipoAlertaLabel(alerta.tipoAlerta);
|
||||
acc[label] = (acc[label] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
return Object.entries(groups); // Devuelve [['Devoluciones Anómalas', 5], ...]
|
||||
}, [alertas]);
|
||||
|
||||
const numAlertas = alertas.length;
|
||||
|
||||
const accessibleModules = useMemo(() => {
|
||||
if (!isAuthenticated) return [];
|
||||
@@ -92,6 +115,17 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
||||
setAnchorElUserMenu(null);
|
||||
};
|
||||
|
||||
// Handlers para el nuevo menú de alertas
|
||||
const handleOpenAlertasMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorElAlertasMenu(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleCloseAlertasMenu = () => {
|
||||
setAnchorElAlertasMenu(null);
|
||||
};
|
||||
|
||||
const handleNavigateToAlertas = () => { navigate('/anomalias/alertas'); handleCloseAlertasMenu(); };
|
||||
|
||||
const handleChangePasswordClick = () => {
|
||||
setShowForcedPasswordChangeModal(true);
|
||||
handleCloseUserMenu();
|
||||
@@ -133,7 +167,6 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Si no hay módulos accesibles después del login (y no es el cambio de clave forzado)
|
||||
// Esto podría pasar si un usuario no tiene permiso para NINGUNA sección, ni siquiera Inicio.
|
||||
// Deberías redirigir a login o mostrar un mensaje de "Sin acceso".
|
||||
@@ -162,6 +195,37 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<>
|
||||
<IconButton onClick={handleOpenAlertasMenu} color="inherit">
|
||||
<Badge badgeContent={numAlertas} color="error">
|
||||
<NotificationsIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
id="alertas-menu"
|
||||
anchorEl={anchorElAlertasMenu}
|
||||
open={Boolean(anchorElAlertasMenu)}
|
||||
onClose={() => setAnchorElAlertasMenu(null)}
|
||||
>
|
||||
<MenuItem disabled>
|
||||
<ListItemText primary={`Tienes ${numAlertas} alertas pendientes.`} />
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
|
||||
{gruposDeAlertas.map(([label, count]) => (
|
||||
<MenuItem key={label} onClick={handleNavigateToAlertas}>
|
||||
<ListItemIcon><Badge badgeContent={count} color="error" sx={{mr: 2}} /></ListItemIcon>
|
||||
<ListItemText>{label}</ListItemText>
|
||||
</MenuItem>
|
||||
))}
|
||||
|
||||
{numAlertas > 0 && <Divider />}
|
||||
|
||||
<MenuItem onClick={handleNavigateToAlertas}>
|
||||
<ListItemText sx={{textAlign: 'center'}}>Ver Todas las Alertas</ListItemText>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<IconButton
|
||||
size="large"
|
||||
aria-label="Cuenta del usuario"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface CanillaDropdownDto {
|
||||
idCanilla: number;
|
||||
legajo?: number | null;
|
||||
nomApe: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface OtroDestinoDropdownDto {
|
||||
idDestino: number;
|
||||
nombre: string;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface PublicacionDropdownDto {
|
||||
idPublicacion: number;
|
||||
nombre: string;
|
||||
nombreEmpresa: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface EstadoBobinaDropdownDto {
|
||||
idEstadoBobina: number;
|
||||
denominacion: string;
|
||||
}
|
||||
129
Frontend/src/pages/Anomalia/AlertasPage.tsx
Normal file
129
Frontend/src/pages/Anomalia/AlertasPage.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { DataGrid, type GridColDef } from '@mui/x-data-grid';
|
||||
import { Button, Box, Typography, Paper, Accordion, AccordionSummary, AccordionDetails } from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
import { esES } from '@mui/x-data-grid/locales';
|
||||
import type { AlertaGenericaDto } from '../../services/Anomalia/alertaService';
|
||||
|
||||
const getTipoAlertaLabel = (tipoAlerta: string): string => {
|
||||
switch (tipoAlerta) {
|
||||
case 'DevolucionAnomala': return 'Devoluciones Anómalas';
|
||||
case 'ConsumoBobinaExcesivo': return 'Consumo de Bobinas Anómalo';
|
||||
case 'ComportamientoSistema': return 'Anomalías Generales del Sistema';
|
||||
case 'FaltaDeDatos': return 'Falta de Registros Críticos';
|
||||
default: return tipoAlerta;
|
||||
}
|
||||
};
|
||||
|
||||
const AlertasPage: React.FC = () => {
|
||||
const { alertas, marcarAlertaComoLeida, marcarGrupoDeAlertasLeido, isLoading } = useAuth();
|
||||
|
||||
const gruposPorTipo = useMemo(() => {
|
||||
if (!Array.isArray(alertas)) return [];
|
||||
return alertas.reduce((acc, alerta) => {
|
||||
(acc[alerta.tipoAlerta] = acc[alerta.tipoAlerta] || []).push(alerta);
|
||||
return acc;
|
||||
}, {} as Record<string, AlertaGenericaDto[]>);
|
||||
}, [alertas]);
|
||||
|
||||
const getColumnsForType = (tipoAlerta: string): GridColDef[] => {
|
||||
const baseColumns: GridColDef[] = [
|
||||
{ field: 'fechaAnomalia', headerName: 'Fecha Evento', width: 150, valueFormatter: (value) => new Date(value as string).toLocaleDateString('es-AR') },
|
||||
{ field: 'mensaje', headerName: 'Descripción', flex: 1 }
|
||||
];
|
||||
|
||||
// Columnas específicas para 'DevolucionAnomala'
|
||||
if (tipoAlerta === 'DevolucionAnomala') {
|
||||
baseColumns.push(
|
||||
{ field: 'cantidadEnviada', headerName: 'Llevados', width: 120 },
|
||||
{ field: 'cantidadDevuelta', headerName: 'Devueltos', width: 120 },
|
||||
{ field: 'porcentajeDevolucion', headerName: '% Dev.', width: 120, valueFormatter: (value) => `${Number(value).toFixed(2)}%` }
|
||||
);
|
||||
}
|
||||
|
||||
baseColumns.push({
|
||||
field: 'actions',
|
||||
headerName: 'Acciones',
|
||||
width: 150,
|
||||
sortable: false,
|
||||
renderCell: (params) => (
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
// Llamamos a la función del contexto para marcar una SOLA alerta
|
||||
onClick={() => marcarAlertaComoLeida(params.row.idAlerta)}
|
||||
>
|
||||
Marcar Leída
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
|
||||
return baseColumns;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant="h5" gutterBottom>Centro de Alertas del Sistema</Typography>
|
||||
|
||||
{Object.entries(gruposPorTipo).map(([tipoAlerta, alertasDelGrupo]) => {
|
||||
const gruposPorEntidad = alertasDelGrupo.reduce((acc, alerta) => {
|
||||
(acc[alerta.idEntidad] = acc[alerta.idEntidad] || []).push(alerta);
|
||||
return acc;
|
||||
}, {} as Record<number, AlertaGenericaDto[]>);
|
||||
|
||||
return (
|
||||
<Accordion key={tipoAlerta} defaultExpanded>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography variant="h6">{getTipoAlertaLabel(tipoAlerta)} ({alertasDelGrupo.length})</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{display: 'flex', flexDirection: 'column', gap: 2}}>
|
||||
{Object.entries(gruposPorEntidad).map(([idEntidad, alertasDeEntidad]) => {
|
||||
const primeraAlerta = alertasDeEntidad[0];
|
||||
// Para obtener un nombre de canillita legible en el título del grupo
|
||||
const nombreEntidad = primeraAlerta.entidad === 'Canillita'
|
||||
? primeraAlerta.mensaje.match(/'([^']+)'/)?.[1] || `ID ${idEntidad}`
|
||||
: `ID ${idEntidad}`;
|
||||
|
||||
const tituloGrupo = primeraAlerta.entidad === 'Sistema'
|
||||
? 'Alertas Generales del Sistema'
|
||||
: `${primeraAlerta.entidad}: ${nombreEntidad}`;
|
||||
|
||||
const rows = alertasDeEntidad.map(a => ({ ...a, id: a.idAlerta }));
|
||||
|
||||
return (
|
||||
<Paper key={idEntidad} variant="outlined" sx={{ p: 2 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1, flexWrap: 'wrap', gap: 1 }}>
|
||||
<Typography variant="subtitle1" sx={{fontWeight: 'bold'}}>{tituloGrupo} ({alertasDeEntidad.length} alertas)</Typography>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onClick={() => marcarGrupoDeAlertasLeido(tipoAlerta, Number(idEntidad))}>
|
||||
Marcar todas como leídas
|
||||
</Button>
|
||||
</Box>
|
||||
<Box sx={{ height: 300, width: '100%' }}>
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={getColumnsForType(tipoAlerta)}
|
||||
loading={isLoading}
|
||||
localeText={esES.components.MuiDataGrid.defaultProps.localeText}
|
||||
density="compact"
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
})}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
})}
|
||||
|
||||
{alertas.length === 0 && !isLoading && (
|
||||
<Typography sx={{mt: 3, textAlign: 'center', fontStyle: 'italic'}}>No hay alertas pendientes.</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AlertasPage;
|
||||
@@ -16,13 +16,14 @@ import empresaService from '../../services/Distribucion/empresaService';
|
||||
import type { ControlDevolucionesDto } from '../../models/dtos/Distribucion/ControlDevolucionesDto';
|
||||
import type { CreateControlDevolucionesDto } from '../../models/dtos/Distribucion/CreateControlDevolucionesDto';
|
||||
import type { UpdateControlDevolucionesDto } from '../../models/dtos/Distribucion/UpdateControlDevolucionesDto';
|
||||
import type { EmpresaDto } from '../../models/dtos/Distribucion/EmpresaDto';
|
||||
import type { EmpresaDropdownDto } from '../../models/dtos/Distribucion/EmpresaDropdownDto';
|
||||
|
||||
import ControlDevolucionesFormModal from '../../components/Modals/Distribucion/ControlDevolucionesFormModal';
|
||||
import { usePermissions } from '../../hooks/usePermissions';
|
||||
import axios from 'axios';
|
||||
|
||||
const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
// ... (estados sin cambios) ...
|
||||
const [controles, setControles] = useState<ControlDevolucionesDto[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -32,8 +33,8 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
||||
|
||||
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
const [empresas, setEmpresas] = useState<EmpresaDropdownDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(true); // << CAMBIO: Iniciar en true
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editingControl, setEditingControl] = useState<ControlDevolucionesDto | null>(null);
|
||||
@@ -47,42 +48,58 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
const puedeVer = isSuperAdmin || tienePermiso("CD001");
|
||||
const puedeCrear = isSuperAdmin || tienePermiso("CD002");
|
||||
const puedeModificar = isSuperAdmin || tienePermiso("CD003");
|
||||
const puedeEliminar = isSuperAdmin || tienePermiso("CD003");
|
||||
// << CAMBIO: Permiso de eliminar debe ser diferente
|
||||
const puedeEliminar = isSuperAdmin || tienePermiso("CD004"); // Asumiendo que CD004 es para eliminar
|
||||
|
||||
// CORREGIDO: Función para formatear la fecha
|
||||
// ... (formatDate sin cambios) ...
|
||||
const formatDate = (dateString?: string | null): string => {
|
||||
if (!dateString) return '-';
|
||||
// Asumimos que dateString viene del backend como "YYYY-MM-DD" o "YYYY-MM-DDTHH:mm:ss..."
|
||||
const datePart = dateString.split('T')[0]; // Tomar solo la parte YYYY-MM-DD
|
||||
const datePart = dateString.split('T')[0];
|
||||
const parts = datePart.split('-');
|
||||
if (parts.length === 3) {
|
||||
// parts[0] = YYYY, parts[1] = MM, parts[2] = DD
|
||||
return `${parts[2]}/${parts[1]}/${parts[0]}`; // Formato DD/MM/YYYY
|
||||
return `${parts[2]}/${parts[1]}/${parts[0]}`;
|
||||
}
|
||||
return datePart; // Fallback si el formato no es el esperado
|
||||
return datePart;
|
||||
};
|
||||
|
||||
|
||||
const fetchFiltersDropdownData = useCallback(async () => {
|
||||
// << CAMBIO: Guardián de permisos para la carga de filtros
|
||||
if (!puedeVer) {
|
||||
setError("No tiene permiso para ver esta sección.");
|
||||
setLoading(false); // Detiene el spinner principal
|
||||
setLoadingFiltersDropdown(false); // Detiene el spinner de filtros
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
const empresasData = await empresaService.getAllEmpresas();
|
||||
const empresasData = await empresaService.getEmpresasDropdown();
|
||||
setEmpresas(empresasData);
|
||||
} catch (err) {
|
||||
console.error("Error cargando empresas para filtro:", err);
|
||||
// El error principal se manejará en cargarControles si también falla
|
||||
setError("Error al cargar opciones de filtro.");
|
||||
} finally {
|
||||
setLoadingFiltersDropdown(false);
|
||||
}
|
||||
}, []);
|
||||
}, [puedeVer]); // << CAMBIO: Añadir `puedeVer` como dependencia
|
||||
|
||||
useEffect(() => { fetchFiltersDropdownData(); }, [fetchFiltersDropdownData]);
|
||||
useEffect(() => {
|
||||
fetchFiltersDropdownData();
|
||||
}, [fetchFiltersDropdownData]);
|
||||
|
||||
const cargarControles = useCallback(async () => {
|
||||
// El guardián aquí ya estaba y es correcto.
|
||||
if (!puedeVer) {
|
||||
setError("No tiene permiso para ver esta sección."); setLoading(false); return;
|
||||
// Si ya se estableció el error en el fetch de filtros, no lo sobrescribimos.
|
||||
if (!error) setError("No tiene permiso para ver esta sección.");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setLoading(true); setError(null); setApiErrorMessage(null);
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
const params = {
|
||||
fechaDesde: filtroFechaDesde || null,
|
||||
@@ -92,19 +109,27 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
const data = await controlDevolucionesService.getAllControlesDevoluciones(params);
|
||||
setControles(data);
|
||||
} catch (err) {
|
||||
console.error(err); setError('Error al cargar los controles de devoluciones.');
|
||||
} finally { setLoading(false); }
|
||||
}, [puedeVer, filtroFechaDesde, filtroFechaHasta, filtroIdEmpresa]);
|
||||
console.error(err);
|
||||
setError('Error al cargar los controles de devoluciones.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [puedeVer, filtroFechaDesde, filtroFechaHasta, filtroIdEmpresa, error]); // << CAMBIO: Añadido `error` a dependencias
|
||||
|
||||
useEffect(() => { cargarControles(); }, [cargarControles]);
|
||||
useEffect(() => {
|
||||
// Solo cargar controles si los filtros se han cargado (o intentado cargar)
|
||||
if (!loadingFiltersDropdown) {
|
||||
cargarControles();
|
||||
}
|
||||
}, [cargarControles, loadingFiltersDropdown]); // << CAMBIO: Depende de la carga de filtros
|
||||
|
||||
// ... (resto de los handlers sin cambios) ...
|
||||
const handleOpenModal = (item?: ControlDevolucionesDto) => {
|
||||
setEditingControl(item || null); setApiErrorMessage(null); setModalOpen(true);
|
||||
};
|
||||
const handleCloseModal = () => {
|
||||
setModalOpen(false); setEditingControl(null);
|
||||
};
|
||||
|
||||
const handleSubmitModal = async (data: CreateControlDevolucionesDto | UpdateControlDevolucionesDto, idControl?: number) => {
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
@@ -119,7 +144,6 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
setApiErrorMessage(message); throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (idControl: number) => {
|
||||
if (window.confirm(`¿Seguro de eliminar este control de devoluciones (ID: ${idControl})?`)) {
|
||||
setApiErrorMessage(null);
|
||||
@@ -133,26 +157,35 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
}
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, item: ControlDevolucionesDto) => {
|
||||
setAnchorEl(event.currentTarget); setSelectedRow(item);
|
||||
};
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null); setSelectedRow(null);
|
||||
};
|
||||
|
||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 25)); setPage(0);
|
||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||
};
|
||||
// displayData ahora usará la 'controles' directamente, el formato se aplica en el renderizado
|
||||
const displayData = controles.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||
|
||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||
|
||||
// Si no tiene permiso, muestra solo la alerta y nada más.
|
||||
if (!puedeVer) {
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Alert severity="error">
|
||||
{error || "No tiene permiso para acceder a esta sección."}
|
||||
</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 1 }}>
|
||||
<Typography variant="h5" gutterBottom>Control de Devoluciones a Empresa</Typography>
|
||||
|
||||
{/* El resto del JSX se renderizará solo si 'puedeVer' es true */}
|
||||
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
||||
@@ -169,12 +202,12 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
{puedeCrear && (<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()}>Registrar Control</Button>)}
|
||||
</Paper>
|
||||
|
||||
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||
{(loading || loadingFiltersDropdown) && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
||||
|
||||
{!loading && !error && puedeVer && (
|
||||
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}> {/* Ajusta maxHeight según sea necesario */}
|
||||
{!loading && !loadingFiltersDropdown && !error && (
|
||||
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 300px)' }}> {/* Ajusta maxHeight */}
|
||||
<Table stickyHeader size="small">
|
||||
<TableHead><TableRow>
|
||||
<TableCell>Fecha</TableCell><TableCell>Empresa</TableCell>
|
||||
@@ -186,7 +219,7 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
{displayData.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={puedeModificar || puedeEliminar ? 7 : 6} align="center">No se encontraron controles.</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={puedeModificar || puedeEliminar ? 7 : 6} align="center">No se encontraron controles con los filtros aplicados.</TableCell></TableRow>
|
||||
) : (
|
||||
displayData.map((c) => (
|
||||
<TableRow key={c.idControl} hover>
|
||||
@@ -217,7 +250,10 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
{puedeModificar && selectedRow && (
|
||||
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{ mr: 1 }} /> Modificar</MenuItem>)}
|
||||
{puedeEliminar && selectedRow && (
|
||||
<MenuItem onClick={() => handleDelete(selectedRow.idControl)}><DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar</MenuItem>)}
|
||||
<MenuItem onClick={() => { if (selectedRow) handleDelete(selectedRow.idControl) }}>
|
||||
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar
|
||||
</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
|
||||
<ControlDevolucionesFormModal
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react'; // << Añadido useMemo
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||
@@ -21,7 +21,7 @@ import canillaService from '../../services/Distribucion/canillaService';
|
||||
import type { EntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/EntradaSalidaCanillaDto';
|
||||
import type { UpdateEntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/UpdateEntradaSalidaCanillaDto';
|
||||
import type { PublicacionDropdownDto } from '../../models/dtos/Distribucion/PublicacionDropdownDto';
|
||||
import type { CanillaDto } from '../../models/dtos/Distribucion/CanillaDto';
|
||||
import type { CanillaDropdownDto } from '../../models/dtos/Distribucion/CanillaDropdownDto';
|
||||
import type { LiquidarMovimientosCanillaRequestDto } from '../../models/dtos/Distribucion/LiquidarMovimientosCanillaDto';
|
||||
|
||||
import EntradaSalidaCanillaFormModal from '../../components/Modals/Distribucion/EntradaSalidaCanillaFormModal';
|
||||
@@ -44,8 +44,8 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
|
||||
const [loadingTicketPdf, setLoadingTicketPdf] = useState(false);
|
||||
const [publicaciones, setPublicaciones] = useState<PublicacionDropdownDto[]>([]);
|
||||
const [destinatariosDropdown, setDestinatariosDropdown] = useState<CanillaDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
const [destinatariosDropdown, setDestinatariosDropdown] = useState<CanillaDropdownDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false)
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editingMovimiento, setEditingMovimiento] = useState<EntradaSalidaCanillaDto | null>(null);
|
||||
@@ -81,29 +81,40 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPublicaciones = async () => {
|
||||
const fetchDropdownData = async () => {
|
||||
if (!puedeVer) {
|
||||
setError("No tiene permiso para ver esta sección.");
|
||||
setLoading(false); // Detiene el spinner principal
|
||||
setLoadingFiltersDropdown(false); // Detiene el spinner de los filtros
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingFiltersDropdown(true);
|
||||
setError(null);
|
||||
try {
|
||||
const pubsData = await publicacionService.getPublicacionesForDropdown(true);
|
||||
setPublicaciones(pubsData);
|
||||
// La carga de destinatarios se hará en el otro useEffect
|
||||
} catch (err) {
|
||||
console.error("Error cargando publicaciones para filtro:",err);
|
||||
console.error("Error cargando publicaciones para filtro:", err);
|
||||
setError("Error al cargar publicaciones.");
|
||||
} finally {
|
||||
// No setLoadingFiltersDropdown(false) acá, esperar a la otra carga
|
||||
// La carga finaliza cuando se cargan los destinatarios también.
|
||||
}
|
||||
};
|
||||
fetchPublicaciones();
|
||||
}, []);
|
||||
fetchDropdownData();
|
||||
}, [puedeVer]); // << CAMBIO: Añadir `puedeVer` como dependencia
|
||||
|
||||
const fetchDestinatariosParaDropdown = useCallback(async () => {
|
||||
if (!puedeVer) { return; }
|
||||
|
||||
setLoadingFiltersDropdown(true);
|
||||
setFiltroIdCanillitaSeleccionado('');
|
||||
setDestinatariosDropdown([]);
|
||||
setError(null);
|
||||
try {
|
||||
const esAccionistaFilter = filtroTipoDestinatario === 'accionistas';
|
||||
const data = await canillaService.getAllCanillas(undefined, undefined, true, esAccionistaFilter);
|
||||
const data = await canillaService.getAllDropdownCanillas(true, esAccionistaFilter);
|
||||
setDestinatariosDropdown(data);
|
||||
} catch (err) {
|
||||
console.error("Error cargando destinatarios para filtro:", err);
|
||||
@@ -111,21 +122,23 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
} finally {
|
||||
setLoadingFiltersDropdown(false);
|
||||
}
|
||||
}, [filtroTipoDestinatario]);
|
||||
}, [filtroTipoDestinatario, puedeVer]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDestinatariosParaDropdown();
|
||||
}, [fetchDestinatariosParaDropdown]);
|
||||
|
||||
|
||||
const cargarMovimientos = useCallback(async () => {
|
||||
if (!puedeVer) { setError("No tiene permiso para ver esta sección."); setLoading(false); return; }
|
||||
if (!puedeVer) {
|
||||
setError("No tiene permiso para ver esta sección.");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (!filtroFecha || !filtroIdCanillitaSeleccionado) {
|
||||
if (loading) setLoading(false);
|
||||
setMovimientos([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true); setError(null); setApiErrorMessage(null);
|
||||
try {
|
||||
const params = {
|
||||
@@ -148,6 +161,7 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
}
|
||||
}, [puedeVer, filtroFecha, filtroIdPublicacion, filtroIdCanillitaSeleccionado]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (filtroFecha && filtroIdCanillitaSeleccionado) {
|
||||
cargarMovimientos();
|
||||
@@ -156,8 +170,7 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
if (loading) setLoading(false);
|
||||
}
|
||||
}, [cargarMovimientos, filtroFecha, filtroIdCanillitaSeleccionado]);
|
||||
|
||||
|
||||
|
||||
const handleOpenModal = (item?: EntradaSalidaCanillaDto) => {
|
||||
if (!puedeCrear && !item) {
|
||||
setApiErrorMessage("No tiene permiso para registrar nuevos movimientos.");
|
||||
@@ -195,7 +208,6 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
}
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, item: EntradaSalidaCanillaDto) => {
|
||||
event.currentTarget.setAttribute('data-rowid', item.idParte.toString());
|
||||
setAnchorEl(event.currentTarget);
|
||||
@@ -258,17 +270,15 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
await entradaSalidaCanillaService.liquidarMovimientos(liquidarDto);
|
||||
setOpenLiquidarDialog(false);
|
||||
const primerIdParteLiquidado = Array.from(selectedIdsParaLiquidar)[0];
|
||||
// Necesitamos encontrar el movimiento en la lista ANTES de recargar
|
||||
const movimientoParaTicket = movimientos.find(m => m.idParte === primerIdParteLiquidado);
|
||||
|
||||
await cargarMovimientos(); // Recargar la lista para reflejar el estado liquidado
|
||||
await cargarMovimientos();
|
||||
|
||||
// Usar la fecha del movimiento original para el ticket
|
||||
if (movimientoParaTicket && !movimientoParaTicket.canillaEsAccionista) {
|
||||
console.log("Liquidación exitosa, generando ticket para canillita NO accionista:", movimientoParaTicket.idCanilla);
|
||||
await handleImprimirTicketLiquidacion(
|
||||
movimientoParaTicket.idCanilla,
|
||||
movimientoParaTicket.fecha, // Usar la fecha del movimiento
|
||||
movimientoParaTicket.fecha,
|
||||
false
|
||||
);
|
||||
} else if (movimientoParaTicket && movimientoParaTicket.canillaEsAccionista) {
|
||||
@@ -328,7 +338,6 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
} finally { setLoadingTicketPdf(false); }
|
||||
}, []);
|
||||
|
||||
|
||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||
@@ -339,8 +348,14 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
displayData.filter(m => !m.liquidado).reduce((sum, item) => sum + item.montoARendir, 0)
|
||||
, [displayData]);
|
||||
|
||||
if (!loading && !puedeVer && !loadingFiltersDropdown && movimientos.length === 0 && !filtroFecha && !filtroIdCanillitaSeleccionado ) {
|
||||
return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||
if (!puedeVer) {
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Alert severity="error">
|
||||
{error || "No tiene permiso para acceder a esta sección."}
|
||||
</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const numSelectedToLiquidate = selectedIdsParaLiquidar.size;
|
||||
@@ -352,7 +367,6 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
||||
{/* ... (Filtros sin cambios) ... */}
|
||||
<TextField label="Fecha" type="date" size="small" value={filtroFecha}
|
||||
onChange={(e) => setFiltroFecha(e.target.value)}
|
||||
InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }}
|
||||
|
||||
@@ -17,8 +17,8 @@ import distribuidorService from '../../services/Distribucion/distribuidorService
|
||||
import type { EntradaSalidaDistDto } from '../../models/dtos/Distribucion/EntradaSalidaDistDto';
|
||||
import type { CreateEntradaSalidaDistDto } from '../../models/dtos/Distribucion/CreateEntradaSalidaDistDto';
|
||||
import type { UpdateEntradaSalidaDistDto } from '../../models/dtos/Distribucion/UpdateEntradaSalidaDistDto';
|
||||
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||
import type { DistribuidorDto } from '../../models/dtos/Distribucion/DistribuidorDto';
|
||||
import type { PublicacionDropdownDto } from '../../models/dtos/Distribucion/PublicacionDropdownDto';
|
||||
import type { DistribuidorDropdownDto } from '../../models/dtos/Distribucion/DistribuidorDropdownDto';
|
||||
|
||||
import EntradaSalidaDistFormModal from '../../components/Modals/Distribucion/EntradaSalidaDistFormModal';
|
||||
import { usePermissions } from '../../hooks/usePermissions';
|
||||
@@ -36,8 +36,8 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
||||
const [filtroIdDistribuidor, setFiltroIdDistribuidor] = useState<number | string>('');
|
||||
const [filtroTipoMov, setFiltroTipoMov] = useState<'Salida' | 'Entrada' | ''>('');
|
||||
|
||||
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||
const [distribuidores, setDistribuidores] = useState<DistribuidorDto[]>([]);
|
||||
const [publicaciones, setPublicaciones] = useState<PublicacionDropdownDto[]>([]);
|
||||
const [distribuidores, setDistribuidores] = useState<DistribuidorDropdownDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
@@ -69,8 +69,8 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
const [pubsData, distData] = await Promise.all([
|
||||
publicacionService.getAllPublicaciones(undefined, undefined, true),
|
||||
distribuidorService.getAllDistribuidores()
|
||||
publicacionService.getPublicacionesForDropdown(true),
|
||||
distribuidorService.getAllDistribuidoresDropdown()
|
||||
]);
|
||||
setPublicaciones(pubsData);
|
||||
setDistribuidores(distData);
|
||||
|
||||
@@ -16,8 +16,8 @@ import otroDestinoService from '../../services/Distribucion/otroDestinoService';
|
||||
import type { SalidaOtroDestinoDto } from '../../models/dtos/Distribucion/SalidaOtroDestinoDto';
|
||||
import type { CreateSalidaOtroDestinoDto } from '../../models/dtos/Distribucion/CreateSalidaOtroDestinoDto';
|
||||
import type { UpdateSalidaOtroDestinoDto } from '../../models/dtos/Distribucion/UpdateSalidaOtroDestinoDto';
|
||||
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||
import type { OtroDestinoDto } from '../../models/dtos/Distribucion/OtroDestinoDto';
|
||||
import type { PublicacionDropdownDto } from '../../models/dtos/Distribucion/PublicacionDropdownDto';
|
||||
import type { OtroDestinoDropdownDto } from '../../models/dtos/Distribucion/OtroDestinoDropdownDto';
|
||||
|
||||
import SalidaOtroDestinoFormModal from '../../components/Modals/Distribucion/SalidaOtroDestinoFormModal';
|
||||
import { usePermissions } from '../../hooks/usePermissions';
|
||||
@@ -34,8 +34,8 @@ const GestionarSalidasOtrosDestinosPage: React.FC = () => {
|
||||
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
||||
const [filtroIdDestino, setFiltroIdDestino] = useState<number | string>('');
|
||||
|
||||
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||
const [otrosDestinos, setOtrosDestinos] = useState<OtroDestinoDto[]>([]);
|
||||
const [publicaciones, setPublicaciones] = useState<PublicacionDropdownDto[]>([]);
|
||||
const [otrosDestinos, setOtrosDestinos] = useState<OtroDestinoDropdownDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
@@ -68,8 +68,8 @@ const GestionarSalidasOtrosDestinosPage: React.FC = () => {
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
const [pubsData, destinosData] = await Promise.all([
|
||||
publicacionService.getAllPublicaciones(undefined, undefined, true),
|
||||
otroDestinoService.getAllOtrosDestinos()
|
||||
publicacionService.getPublicacionesForDropdown(true),
|
||||
otroDestinoService.getAllDropdownOtrosDestinos()
|
||||
]);
|
||||
setPublicaciones(pubsData);
|
||||
setOtrosDestinos(destinosData);
|
||||
|
||||
@@ -36,7 +36,6 @@ const GestionarEstadosBobinaPage: React.FC = () => {
|
||||
|
||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||
|
||||
// Permisos para Estados de Bobina (ej: IB010 a IB013)
|
||||
const puedeVer = isSuperAdmin || tienePermiso("IB010");
|
||||
const puedeCrear = isSuperAdmin || tienePermiso("IB011");
|
||||
const puedeModificar = isSuperAdmin || tienePermiso("IB012");
|
||||
|
||||
@@ -21,8 +21,8 @@ import type { CreateStockBobinaDto } from '../../models/dtos/Impresion/CreateSto
|
||||
import type { UpdateStockBobinaDto } from '../../models/dtos/Impresion/UpdateStockBobinaDto';
|
||||
import type { CambiarEstadoBobinaDto } from '../../models/dtos/Impresion/CambiarEstadoBobinaDto';
|
||||
import type { TipoBobinaDto } from '../../models/dtos/Impresion/TipoBobinaDto';
|
||||
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||
import type { EstadoBobinaDto } from '../../models/dtos/Impresion/EstadoBobinaDto';
|
||||
import type { PlantaDropdownDto } from '../../models/dtos/Impresion/PlantaDropdownDto';
|
||||
import type { EstadoBobinaDropdownDto } from '../../models/dtos/Impresion/EstadoBobinaDropdownDto';
|
||||
|
||||
import StockBobinaIngresoFormModal from '../../components/Modals/Impresion/StockBobinaIngresoFormModal';
|
||||
import StockBobinaEditFormModal from '../../components/Modals/Impresion/StockBobinaEditFormModal';
|
||||
@@ -50,8 +50,8 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
|
||||
const [tiposBobina, setTiposBobina] = useState<TipoBobinaDto[]>([]);
|
||||
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||
const [estadosBobina, setEstadosBobina] = useState<EstadoBobinaDto[]>([]);
|
||||
const [plantas, setPlantas] = useState<PlantaDropdownDto[]>([]);
|
||||
const [estadosBobina, setEstadosBobina] = useState<EstadoBobinaDropdownDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
|
||||
const [ingresoModalOpen, setIngresoModalOpen] = useState(false);
|
||||
@@ -76,9 +76,9 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
const [tiposData, plantasData, estadosData] = await Promise.all([
|
||||
tipoBobinaService.getAllTiposBobina(),
|
||||
plantaService.getAllPlantas(),
|
||||
estadoBobinaService.getAllEstadosBobina()
|
||||
tipoBobinaService.getAllDropdownTiposBobina(),
|
||||
plantaService.getPlantasForDropdown(),
|
||||
estadoBobinaService.getAllDropdownEstadosBobina()
|
||||
]);
|
||||
setTiposBobina(tiposData);
|
||||
setPlantas(plantasData);
|
||||
|
||||
@@ -18,8 +18,8 @@ import plantaService from '../../services/Impresion/plantaService'; // Para filt
|
||||
|
||||
import type { TiradaDto } from '../../models/dtos/Impresion/TiradaDto';
|
||||
import type { CreateTiradaRequestDto } from '../../models/dtos/Impresion/CreateTiradaRequestDto';
|
||||
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||
import type { PublicacionDropdownDto } from '../../models/dtos/Distribucion/PublicacionDropdownDto';
|
||||
import type { PlantaDropdownDto } from '../../models/dtos/Impresion/PlantaDropdownDto';
|
||||
|
||||
import TiradaFormModal from '../../components/Modals/Impresion/TiradaFormModal';
|
||||
import { usePermissions } from '../../hooks/usePermissions';
|
||||
@@ -36,8 +36,8 @@ const GestionarTiradasPage: React.FC = () => {
|
||||
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
||||
const [filtroIdPlanta, setFiltroIdPlanta] = useState<number | string>('');
|
||||
|
||||
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||
const [publicaciones, setPublicaciones] = useState<PublicacionDropdownDto[]>([]);
|
||||
const [plantas, setPlantas] = useState<PlantaDropdownDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
@@ -52,8 +52,8 @@ const GestionarTiradasPage: React.FC = () => {
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
const [pubsData, plantasData] = await Promise.all([
|
||||
publicacionService.getAllPublicaciones(undefined, undefined, true),
|
||||
plantaService.getAllPlantas()
|
||||
publicacionService.getPublicacionesForDropdown(true),
|
||||
plantaService.getPlantasForDropdown()
|
||||
]);
|
||||
setPublicaciones(pubsData);
|
||||
setPlantas(plantasData);
|
||||
|
||||
@@ -8,67 +8,68 @@ 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'; // Renombrar para evitar conflicto
|
||||
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 === "SS002") return "Contables";
|
||||
if (codAcc === "SS003") return "Impresión";
|
||||
if (codAcc === "SS004") return "Reportes";
|
||||
if (codAcc === "SS005") return "Radios";
|
||||
if (codAcc === "SS006") return "Usuarios";
|
||||
return null;
|
||||
if (codAcc === "SS001") return "Distribución";
|
||||
if (codAcc === "SS002") return "Contables";
|
||||
if (codAcc === "SS003") return "Impresión";
|
||||
if (codAcc === "SS004") return "Reportes";
|
||||
if (codAcc === "SS005") return "Radios";
|
||||
if (codAcc === "SS006") return "Usuarios";
|
||||
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("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("tipos bobinas")) {
|
||||
return "Impresión";
|
||||
}
|
||||
if (moduloLower.includes("radios")) {
|
||||
return "Radios";
|
||||
}
|
||||
if (moduloLower.includes("usuarios") ||
|
||||
moduloLower.includes("perfiles")) {
|
||||
return "Usuarios";
|
||||
}
|
||||
if (moduloLower.includes("reportes")) {
|
||||
return "Reportes";
|
||||
}
|
||||
if (moduloLower.includes("permisos")) {
|
||||
return "Permisos (Definición)";
|
||||
}
|
||||
return permisoModulo;
|
||||
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("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("radios")) {
|
||||
return "Radios";
|
||||
}
|
||||
if (moduloLower.includes("usuarios") ||
|
||||
moduloLower.includes("perfiles")) {
|
||||
return "Usuarios";
|
||||
}
|
||||
if (moduloLower.includes("reportes")) {
|
||||
return "Reportes";
|
||||
}
|
||||
if (moduloLower.includes("permisos")) {
|
||||
return "Permisos (Definición)";
|
||||
}
|
||||
return permisoModulo;
|
||||
};
|
||||
|
||||
const AsignarPermisosAPerfilPage: React.FC = () => {
|
||||
const { idPerfil } = useParams<{ idPerfil: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { tienePermiso: tienePermisoPagina, isSuperAdmin } = usePagePermissions(); // Renombrado
|
||||
const { tienePermiso: tienePermisoPagina, isSuperAdmin } = usePagePermissions();
|
||||
|
||||
const puedeAsignar = isSuperAdmin || tienePermisoPagina("PU004");
|
||||
|
||||
@@ -124,76 +125,75 @@ const AsignarPermisosAPerfilPage: React.FC = () => {
|
||||
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 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);
|
||||
})
|
||||
: [];
|
||||
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 (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 (!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));
|
||||
}
|
||||
|
||||
if (successMessage) setSuccessMessage(null);
|
||||
if (error) setError(null);
|
||||
return newSelected;
|
||||
} 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 () => {
|
||||
// ... (sin cambios) ...
|
||||
if (!puedeAsignar || !perfil) return;
|
||||
setSaving(true); setError(null); setSuccessMessage(null);
|
||||
try {
|
||||
@@ -214,54 +214,54 @@ const AsignarPermisosAPerfilPage: React.FC = () => {
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}><CircularProgress /></Box>;
|
||||
}
|
||||
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>;
|
||||
}
|
||||
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>
|
||||
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>}
|
||||
{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>
|
||||
<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;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { Box, Tabs, Tab, Paper, Typography } from '@mui/material';
|
||||
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { usePermissions } from '../../hooks/usePermissions';
|
||||
|
||||
const usuariosSubModules = [
|
||||
{ label: 'Perfiles', path: 'perfiles' },
|
||||
@@ -13,37 +14,54 @@ const UsuariosIndexPage: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [selectedSubTab, setSelectedSubTab] = useState<number | false>(false);
|
||||
const { isSuperAdmin } = usePermissions();
|
||||
|
||||
// --- Filtrar solo lo que puede ver este usuario ---
|
||||
const availableSubModules = useMemo(
|
||||
() =>
|
||||
usuariosSubModules.filter(sub => {
|
||||
// Estos dos ítems solo para superadmins
|
||||
if (
|
||||
(sub.path === 'permisos' || sub.path === 'auditoria-usuarios')
|
||||
&& !isSuperAdmin
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
[isSuperAdmin]
|
||||
);
|
||||
|
||||
// --- Ajustar la pestaña activa según la ruta ---
|
||||
useEffect(() => {
|
||||
const currentBasePath = '/usuarios';
|
||||
const subPath = location.pathname.startsWith(currentBasePath + '/')
|
||||
? location.pathname.substring(currentBasePath.length + 1).split('/')[0] // Tomar solo la primera parte de la subruta
|
||||
: (location.pathname === currentBasePath ? usuariosSubModules[0]?.path : undefined);
|
||||
|
||||
const activeTabIndex = usuariosSubModules.findIndex(
|
||||
(subModule) => subModule.path === subPath
|
||||
);
|
||||
|
||||
if (activeTabIndex !== -1) {
|
||||
setSelectedSubTab(activeTabIndex);
|
||||
} else {
|
||||
if (location.pathname === currentBasePath && usuariosSubModules.length > 0) {
|
||||
navigate(usuariosSubModules[0].path, { replace: true });
|
||||
setSelectedSubTab(0);
|
||||
} else {
|
||||
setSelectedSubTab(false);
|
||||
}
|
||||
const base = '/usuarios';
|
||||
let subPath: string | undefined;
|
||||
if (location.pathname.startsWith(base + '/')) {
|
||||
subPath = location.pathname.slice(base.length + 1).split('/')[0];
|
||||
} else if (location.pathname === base) {
|
||||
subPath = availableSubModules[0]?.path;
|
||||
}
|
||||
}, [location.pathname, navigate]);
|
||||
const idx = availableSubModules.findIndex(m => m.path === subPath);
|
||||
if (idx !== -1) {
|
||||
setSelectedSubTab(idx);
|
||||
} else if (location.pathname === base && availableSubModules.length) {
|
||||
navigate(availableSubModules[0].path, { replace: true });
|
||||
setSelectedSubTab(0);
|
||||
} else {
|
||||
setSelectedSubTab(false);
|
||||
}
|
||||
}, [location.pathname, navigate, availableSubModules]);
|
||||
|
||||
const handleSubTabChange = (_event: React.SyntheticEvent, newValue: number) => {
|
||||
const handleSubTabChange = (_: any, newValue: number) => {
|
||||
setSelectedSubTab(newValue);
|
||||
navigate(usuariosSubModules[newValue].path);
|
||||
navigate(availableSubModules[newValue].path);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>Módulo de Usuarios y Seguridad</Typography>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Módulo de Usuarios y Seguridad
|
||||
</Typography>
|
||||
<Paper square elevation={1}>
|
||||
<Tabs
|
||||
value={selectedSubTab}
|
||||
@@ -54,8 +72,8 @@ const UsuariosIndexPage: React.FC = () => {
|
||||
scrollButtons="auto"
|
||||
aria-label="sub-módulos de usuarios"
|
||||
>
|
||||
{usuariosSubModules.map((subModule) => (
|
||||
<Tab key={subModule.path} label={subModule.label} />
|
||||
{availableSubModules.map(sub => (
|
||||
<Tab key={sub.path} label={sub.label} />
|
||||
))}
|
||||
</Tabs>
|
||||
</Paper>
|
||||
@@ -66,4 +84,4 @@ const UsuariosIndexPage: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default UsuariosIndexPage;
|
||||
export default UsuariosIndexPage;
|
||||
|
||||
@@ -76,6 +76,9 @@ import GestionarNovedadesCanillaPage from '../pages/Distribucion/GestionarNoveda
|
||||
import ReporteNovedadesCanillasPage from '../pages/Reportes/ReporteNovedadesCanillasPage';
|
||||
import ReporteListadoDistMensualPage from '../pages/Reportes/ReporteListadoDistMensualPage';
|
||||
|
||||
// Anonalías
|
||||
import AlertasPage from '../pages/Anomalia/AlertasPage';
|
||||
|
||||
// Auditorias
|
||||
import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage';
|
||||
import AuditoriaGeneralPage from '../pages/Auditoria/AuditoriaGeneralPage';
|
||||
@@ -130,6 +133,19 @@ const AppRoutes = () => {
|
||||
{/* Rutas hijas que se renderizarán en el Outlet de MainLayoutWrapper */}
|
||||
<Route index element={<HomePage />} /> {/* Para la ruta exacta "/" */}
|
||||
|
||||
{/* Módulo de Anomalías */}
|
||||
<Route
|
||||
path="anomalias"
|
||||
element={
|
||||
<SectionProtectedRoute requiredPermission="AL001" sectionName="Anomalías">
|
||||
<Outlet />
|
||||
</SectionProtectedRoute>
|
||||
}
|
||||
>
|
||||
<Route index element={<Navigate to="alertas" replace />} />
|
||||
<Route path="alertas" element={<AlertasPage />} />
|
||||
</Route>
|
||||
|
||||
{/* Módulo de Distribución (anidado) */}
|
||||
<Route
|
||||
path="distribucion"
|
||||
|
||||
49
Frontend/src/services/Anomalia/alertaService.ts
Normal file
49
Frontend/src/services/Anomalia/alertaService.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import apiClient from '../apiClient';
|
||||
|
||||
// El contrato que define la estructura de una alerta genérica
|
||||
export interface AlertaGenericaDto {
|
||||
idAlerta: number;
|
||||
fechaDeteccion: string;
|
||||
tipoAlerta: string;
|
||||
entidad: string;
|
||||
idEntidad: number;
|
||||
mensaje: string;
|
||||
fechaAnomalia: string;
|
||||
leida: boolean;
|
||||
cantidadEnviada?: number;
|
||||
cantidadDevuelta?: number;
|
||||
porcentajeDevolucion?: number;
|
||||
}
|
||||
|
||||
// DTO para el request de marcar un grupo como leído
|
||||
export interface MarcarGrupoLeidoRequestDto {
|
||||
tipoAlerta: string;
|
||||
idEntidad: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene todas las alertas no leídas del sistema.
|
||||
*/
|
||||
export const getAlertas = async (): Promise<AlertaGenericaDto[]> => {
|
||||
try {
|
||||
const response = await apiClient.get<AlertaGenericaDto[]>('/alertas');
|
||||
return response.data || [];
|
||||
} catch (error) {
|
||||
console.error("Error en getAlertas:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Marca una única alerta como leída.
|
||||
*/
|
||||
export const marcarAlertaLeida = async (idAlerta: number): Promise<void> => {
|
||||
await apiClient.post(`/alertas/${idAlerta}/marcar-leida`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Marca un grupo completo de alertas como leídas.
|
||||
*/
|
||||
export const marcarGrupoComoLeido = async (request: MarcarGrupoLeidoRequestDto): Promise<void> => {
|
||||
await apiClient.post('/alertas/marcar-grupo-leido', request);
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import type { CanillaDto } from '../../models/dtos/Distribucion/CanillaDto';
|
||||
import type { CreateCanillaDto } from '../../models/dtos/Distribucion/CreateCanillaDto';
|
||||
import type { UpdateCanillaDto } from '../../models/dtos/Distribucion/UpdateCanillaDto';
|
||||
import type { ToggleBajaCanillaDto } from '../../models/dtos/Distribucion/ToggleBajaCanillaDto';
|
||||
import type { CanillaDropdownDto } from '../../models/dtos/Distribucion/CanillaDropdownDto';
|
||||
|
||||
|
||||
const getAllCanillas = async (
|
||||
@@ -15,12 +16,24 @@ const getAllCanillas = async (
|
||||
if (nomApeFilter) params.nomApe = nomApeFilter;
|
||||
if (legajoFilter !== undefined && legajoFilter !== null) params.legajo = legajoFilter;
|
||||
if (soloActivos !== undefined) params.soloActivos = soloActivos;
|
||||
if (esAccionistaFilter !== undefined) params.esAccionista = esAccionistaFilter; // <<-- ¡CLAVE! Verifica esto.
|
||||
if (esAccionistaFilter !== undefined) params.esAccionista = esAccionistaFilter;
|
||||
|
||||
const response = await apiClient.get<CanillaDto[]>('/canillas', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getAllDropdownCanillas = async (
|
||||
soloActivos?: boolean,
|
||||
esAccionistaFilter?: boolean // Asegúrate que esté aquí
|
||||
): Promise<CanillaDropdownDto[]> => {
|
||||
const params: Record<string, string | number | boolean> = {};
|
||||
if (soloActivos !== undefined) params.soloActivos = soloActivos;
|
||||
if (esAccionistaFilter !== undefined) params.esAccionista = esAccionistaFilter;
|
||||
|
||||
const response = await apiClient.get<CanillaDropdownDto[]>('/canillas/dropdown', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getCanillaById = async (id: number): Promise<CanillaDto> => {
|
||||
const response = await apiClient.get<CanillaDto>(`/canillas/${id}`);
|
||||
return response.data;
|
||||
@@ -43,6 +56,7 @@ const toggleBajaCanilla = async (id: number, data: ToggleBajaCanillaDto): Promis
|
||||
|
||||
const canillaService = {
|
||||
getAllCanillas,
|
||||
getAllDropdownCanillas,
|
||||
getCanillaById,
|
||||
createCanilla,
|
||||
updateCanilla,
|
||||
|
||||
@@ -2,6 +2,7 @@ import apiClient from '../apiClient';
|
||||
import type { OtroDestinoDto } from '../../models/dtos/Distribucion/OtroDestinoDto';
|
||||
import type { CreateOtroDestinoDto } from '../../models/dtos/Distribucion/CreateOtroDestinoDto';
|
||||
import type { UpdateOtroDestinoDto } from '../../models/dtos/Distribucion/UpdateOtroDestinoDto';
|
||||
import type { OtroDestinoDropdownDto } from '../../models/dtos/Distribucion/OtroDestinoDropdownDto';
|
||||
|
||||
const getAllOtrosDestinos = async (nombreFilter?: string): Promise<OtroDestinoDto[]> => {
|
||||
const params: Record<string, string> = {};
|
||||
@@ -12,6 +13,12 @@ const getAllOtrosDestinos = async (nombreFilter?: string): Promise<OtroDestinoDt
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getAllDropdownOtrosDestinos = async (): Promise<OtroDestinoDto[]> => {
|
||||
// Llama a GET /api/otrosdestinos/dropdown
|
||||
const response = await apiClient.get<OtroDestinoDropdownDto[]>('/otrosdestinos/dropdown');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getOtroDestinoById = async (id: number): Promise<OtroDestinoDto> => {
|
||||
// Llama a GET /api/otrosdestinos/{id}
|
||||
const response = await apiClient.get<OtroDestinoDto>(`/otrosdestinos/${id}`);
|
||||
@@ -36,6 +43,7 @@ const deleteOtroDestino = async (id: number): Promise<void> => {
|
||||
|
||||
const otroDestinoService = {
|
||||
getAllOtrosDestinos,
|
||||
getAllDropdownOtrosDestinos,
|
||||
getOtroDestinoById,
|
||||
createOtroDestino,
|
||||
updateOtroDestino,
|
||||
|
||||
@@ -2,6 +2,7 @@ import apiClient from '../apiClient';
|
||||
import type { EstadoBobinaDto } from '../../models/dtos/Impresion/EstadoBobinaDto';
|
||||
import type { CreateEstadoBobinaDto } from '../../models/dtos/Impresion/CreateEstadoBobinaDto';
|
||||
import type { UpdateEstadoBobinaDto } from '../../models/dtos/Impresion/UpdateEstadoBobinaDto';
|
||||
import type { EstadoBobinaDropdownDto } from '../../models/dtos/Impresion/EstadoBobinaDropdownDto';
|
||||
|
||||
const getAllEstadosBobina = async (denominacionFilter?: string): Promise<EstadoBobinaDto[]> => {
|
||||
const params: Record<string, string> = {};
|
||||
@@ -11,6 +12,11 @@ const getAllEstadosBobina = async (denominacionFilter?: string): Promise<EstadoB
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getAllDropdownEstadosBobina = async (): Promise<EstadoBobinaDropdownDto[]> => {
|
||||
const response = await apiClient.get<EstadoBobinaDropdownDto[]>('/estadosbobina/dropdown');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getEstadoBobinaById = async (id: number): Promise<EstadoBobinaDto> => {
|
||||
const response = await apiClient.get<EstadoBobinaDto>(`/estadosbobina/${id}`);
|
||||
return response.data;
|
||||
@@ -31,6 +37,7 @@ const deleteEstadoBobina = async (id: number): Promise<void> => {
|
||||
|
||||
const estadoBobinaService = {
|
||||
getAllEstadosBobina,
|
||||
getAllDropdownEstadosBobina,
|
||||
getEstadoBobinaById,
|
||||
createEstadoBobina,
|
||||
updateEstadoBobina,
|
||||
|
||||
@@ -12,6 +12,12 @@ const getAllTiposBobina = async (denominacionFilter?: string): Promise<TipoBobin
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getAllDropdownTiposBobina = async (): Promise<TipoBobinaDto[]> => {
|
||||
// Llama a GET /api/tiposbobina/dropdown
|
||||
const response = await apiClient.get<TipoBobinaDto[]>('/tiposbobina/dropdown');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getTipoBobinaById = async (id: number): Promise<TipoBobinaDto> => {
|
||||
// Llama a GET /api/tiposbobina/{id}
|
||||
const response = await apiClient.get<TipoBobinaDto>(`/tiposbobina/${id}`);
|
||||
@@ -36,6 +42,7 @@ const deleteTipoBobina = async (id: number): Promise<void> => {
|
||||
|
||||
const tipoBobinaService = {
|
||||
getAllTiposBobina,
|
||||
getAllDropdownTiposBobina,
|
||||
getTipoBobinaById,
|
||||
createTipoBobina,
|
||||
updateTipoBobina,
|
||||
|
||||
Reference in New Issue
Block a user