Fix: Cambios solicitados. Parte 1
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m18s
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m18s
This commit is contained in:
@@ -61,13 +61,13 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
}
|
}
|
||||||
if (fechaDesde.HasValue)
|
if (fechaDesde.HasValue)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND sb.FechaRemito >= @FechaDesdeParam"); // O FechaEstado según el contexto del filtro
|
sqlBuilder.Append(" AND sb.FechaRemito >= @FechaDesdeParam");
|
||||||
parameters.Add("FechaDesdeParam", fechaDesde.Value.Date);
|
parameters.Add("FechaDesdeParam", fechaDesde.Value.Date);
|
||||||
}
|
}
|
||||||
if (fechaHasta.HasValue)
|
if (fechaHasta.HasValue)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND sb.FechaRemito <= @FechaHastaParam"); // O FechaEstado
|
sqlBuilder.Append(" AND sb.FechaRemito <= @FechaHastaParam");
|
||||||
parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); // Hasta el final del día
|
parameters.Add("FechaHastaParam", fechaHasta.Value.Date);
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlBuilder.Append(" ORDER BY sb.FechaRemito DESC, sb.NroBobina;");
|
sqlBuilder.Append(" ORDER BY sb.FechaRemito DESC, sb.NroBobina;");
|
||||||
@@ -224,14 +224,12 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
|
|
||||||
if (actual == null) throw new KeyNotFoundException("Bobina no encontrada para eliminar.");
|
if (actual == null) throw new KeyNotFoundException("Bobina no encontrada para eliminar.");
|
||||||
|
|
||||||
// --- INICIO DE CAMBIO EN VALIDACIÓN ---
|
|
||||||
// Permitir eliminar si está Disponible (1) o Dañada (3)
|
// Permitir eliminar si está Disponible (1) o Dañada (3)
|
||||||
if (actual.IdEstadoBobina != 1 && actual.IdEstadoBobina != 3)
|
if (actual.IdEstadoBobina != 1 && actual.IdEstadoBobina != 3)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Intento de eliminar bobina {IdBobina} que no está en estado 'Disponible' o 'Dañada'. Estado actual: {EstadoActual}", idBobina, actual.IdEstadoBobina);
|
_logger.LogWarning("Intento de eliminar bobina {IdBobina} que no está en estado 'Disponible' o 'Dañada'. Estado actual: {EstadoActual}", idBobina, actual.IdEstadoBobina);
|
||||||
return false; // Devolver false si no cumple la condición para ser eliminada
|
return false; // Devolver false si no cumple la condición para ser eliminada
|
||||||
}
|
}
|
||||||
// --- FIN DE CAMBIO EN VALIDACIÓN ---
|
|
||||||
|
|
||||||
const string sqlDelete = "DELETE FROM dbo.bob_StockBobinas WHERE Id_Bobina = @IdBobinaParam";
|
const string sqlDelete = "DELETE FROM dbo.bob_StockBobinas WHERE Id_Bobina = @IdBobinaParam";
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ namespace GestionIntegral.Api.Dtos.Distribucion
|
|||||||
public int IdPublicacion { get; set; }
|
public int IdPublicacion { get; set; }
|
||||||
public string Nombre { get; set; } = string.Empty;
|
public string Nombre { get; set; } = string.Empty;
|
||||||
public string NombreEmpresa { get; set; } = string.Empty;
|
public string NombreEmpresa { get; set; } = string.Empty;
|
||||||
public bool Habilitada { get; set; }
|
public bool? Habilitada { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,6 @@ namespace GestionIntegral.Api.Dtos.Distribucion
|
|||||||
public int IdEmpresa { get; set; }
|
public int IdEmpresa { get; set; }
|
||||||
public string NombreEmpresa { get; set; } = string.Empty; // Para mostrar en UI
|
public string NombreEmpresa { get; set; } = string.Empty; // Para mostrar en UI
|
||||||
public bool CtrlDevoluciones { get; set; }
|
public bool CtrlDevoluciones { get; set; }
|
||||||
public bool Habilitada { get; set; } // Simplificamos a bool, el backend manejará el default si es null
|
public bool? Habilitada { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
Task<IEnumerable<PublicacionDiaSemanaDto>> ObtenerConfiguracionDiasAsync(int idPublicacion);
|
Task<IEnumerable<PublicacionDiaSemanaDto>> ObtenerConfiguracionDiasAsync(int idPublicacion);
|
||||||
Task<IEnumerable<PublicacionDto>> ObtenerPublicacionesPorDiaSemanaAsync(byte diaSemana); // Devolvemos el DTO completo
|
Task<IEnumerable<PublicacionDto>> ObtenerPublicacionesPorDiaSemanaAsync(byte diaSemana); // Devolvemos el DTO completo
|
||||||
Task<(bool Exito, string? Error)> ActualizarConfiguracionDiasAsync(int idPublicacion, UpdatePublicacionDiasSemanaRequestDto requestDto, int idUsuario);
|
Task<(bool Exito, string? Error)> ActualizarConfiguracionDiasAsync(int idPublicacion, UpdatePublicacionDiasSemanaRequestDto requestDto, int idUsuario);
|
||||||
Task<IEnumerable<PublicacionDropdownDto>> ObtenerParaDropdownAsync(bool soloHabilitadas = true);
|
Task<IEnumerable<PublicacionDropdownDto>> ObtenerParaDropdownAsync(bool soloHabilitadas);
|
||||||
Task<IEnumerable<PublicacionHistorialDto>> ObtenerHistorialAsync(
|
Task<IEnumerable<PublicacionHistorialDto>> ObtenerHistorialAsync(
|
||||||
DateTime? fechaDesde, DateTime? fechaHasta,
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
int? idUsuarioModifico, string? tipoModificacion,
|
int? idUsuarioModifico, string? tipoModificacion,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
IdEmpresa = data.Publicacion.IdEmpresa,
|
IdEmpresa = data.Publicacion.IdEmpresa,
|
||||||
NombreEmpresa = data.NombreEmpresa ?? "Empresa Desconocida", // Manejar null para NombreEmpresa
|
NombreEmpresa = data.NombreEmpresa ?? "Empresa Desconocida", // Manejar null para NombreEmpresa
|
||||||
CtrlDevoluciones = data.Publicacion.CtrlDevoluciones,
|
CtrlDevoluciones = data.Publicacion.CtrlDevoluciones,
|
||||||
Habilitada = data.Publicacion.Habilitada ?? true // Asumir true si es null desde BD
|
Habilitada = data.Publicacion.Habilitada
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,9 +76,9 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
return MapToDto(data);
|
return MapToDto(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<PublicacionDropdownDto>> ObtenerParaDropdownAsync(bool soloHabilitadas = true)
|
public async Task<IEnumerable<PublicacionDropdownDto>> ObtenerParaDropdownAsync(bool soloHabilitadas)
|
||||||
{
|
{
|
||||||
var data = await _publicacionRepository.GetAllAsync(null, null, soloHabilitadas ? (bool?)true : null);
|
var data = await _publicacionRepository.GetAllAsync(null, null, soloHabilitadas);
|
||||||
|
|
||||||
return data
|
return data
|
||||||
.Where(p => p.Publicacion != null) // Asegurar que la publicación no sea null
|
.Where(p => p.Publicacion != null) // Asegurar que la publicación no sea null
|
||||||
@@ -87,7 +87,7 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
IdPublicacion = d.Publicacion!.IdPublicacion, // Usar ! si estás seguro que no es null después del Where
|
IdPublicacion = d.Publicacion!.IdPublicacion, // Usar ! si estás seguro que no es null después del Where
|
||||||
Nombre = d.Publicacion!.Nombre,
|
Nombre = d.Publicacion!.Nombre,
|
||||||
NombreEmpresa = d.NombreEmpresa ?? "Empresa Desconocida",
|
NombreEmpresa = d.NombreEmpresa ?? "Empresa Desconocida",
|
||||||
Habilitada = d.Publicacion!.Habilitada ?? true // Si necesitas filtrar por esto
|
Habilitada = d.Publicacion!.Habilitada
|
||||||
})
|
})
|
||||||
.OrderBy(p => p.Nombre)
|
.OrderBy(p => p.Nombre)
|
||||||
.ToList(); // O ToListAsync si el método del repo es async y devuelve IQueryable
|
.ToList(); // O ToListAsync si el método del repo es async y devuelve IQueryable
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
// src/pages/Impresion/GestionarStockBobinasPage.tsx
|
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
|
||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert, FormControl, InputLabel, Select
|
CircularProgress, Alert, FormControl, InputLabel, Select, FormControlLabel, Checkbox
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
|
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import ClearIcon from '@mui/icons-material/Clear';
|
||||||
|
|
||||||
import stockBobinaService from '../../services/Impresion/stockBobinaService';
|
import stockBobinaService from '../../services/Impresion/stockBobinaService';
|
||||||
import tipoBobinaService from '../../services/Impresion/tipoBobinaService';
|
import tipoBobinaService from '../../services/Impresion/tipoBobinaService';
|
||||||
@@ -21,8 +22,8 @@ import type { CreateStockBobinaDto } from '../../models/dtos/Impresion/CreateSto
|
|||||||
import type { UpdateStockBobinaDto } from '../../models/dtos/Impresion/UpdateStockBobinaDto';
|
import type { UpdateStockBobinaDto } from '../../models/dtos/Impresion/UpdateStockBobinaDto';
|
||||||
import type { CambiarEstadoBobinaDto } from '../../models/dtos/Impresion/CambiarEstadoBobinaDto';
|
import type { CambiarEstadoBobinaDto } from '../../models/dtos/Impresion/CambiarEstadoBobinaDto';
|
||||||
import type { TipoBobinaDto } from '../../models/dtos/Impresion/TipoBobinaDto';
|
import type { TipoBobinaDto } from '../../models/dtos/Impresion/TipoBobinaDto';
|
||||||
import type { PlantaDropdownDto } from '../../models/dtos/Impresion/PlantaDropdownDto';
|
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||||
import type { EstadoBobinaDropdownDto } from '../../models/dtos/Impresion/EstadoBobinaDropdownDto';
|
import type { EstadoBobinaDto } from '../../models/dtos/Impresion/EstadoBobinaDto';
|
||||||
|
|
||||||
import StockBobinaIngresoFormModal from '../../components/Modals/Impresion/StockBobinaIngresoFormModal';
|
import StockBobinaIngresoFormModal from '../../components/Modals/Impresion/StockBobinaIngresoFormModal';
|
||||||
import StockBobinaEditFormModal from '../../components/Modals/Impresion/StockBobinaEditFormModal';
|
import StockBobinaEditFormModal from '../../components/Modals/Impresion/StockBobinaEditFormModal';
|
||||||
@@ -37,33 +38,39 @@ const ID_ESTADO_DANADA = 3;
|
|||||||
|
|
||||||
const GestionarStockBobinasPage: React.FC = () => {
|
const GestionarStockBobinasPage: React.FC = () => {
|
||||||
const [stock, setStock] = useState<StockBobinaDto[]>([]);
|
const [stock, setStock] = useState<StockBobinaDto[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(false); // No carga al inicio
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Estados de los filtros
|
||||||
const [filtroTipoBobina, setFiltroTipoBobina] = useState<number | string>('');
|
const [filtroTipoBobina, setFiltroTipoBobina] = useState<number | string>('');
|
||||||
const [filtroNroBobina, setFiltroNroBobina] = useState('');
|
const [filtroNroBobina, setFiltroNroBobina] = useState('');
|
||||||
const [filtroPlanta, setFiltroPlanta] = useState<number | string>('');
|
const [filtroPlanta, setFiltroPlanta] = useState<number | string>('');
|
||||||
const [filtroEstadoBobina, setFiltroEstadoBobina] = useState<number | string>('');
|
const [filtroEstadoBobina, setFiltroEstadoBobina] = useState<number | string>('');
|
||||||
const [filtroRemito, setFiltroRemito] = useState('');
|
const [filtroRemito, setFiltroRemito] = useState('');
|
||||||
|
const [filtroFechaHabilitado, setFiltroFechaHabilitado] = useState<boolean>(false); // <-- NUEVO
|
||||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
|
||||||
|
// Estados para datos de dropdowns
|
||||||
const [tiposBobina, setTiposBobina] = useState<TipoBobinaDto[]>([]);
|
const [tiposBobina, setTiposBobina] = useState<TipoBobinaDto[]>([]);
|
||||||
const [plantas, setPlantas] = useState<PlantaDropdownDto[]>([]);
|
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||||
const [estadosBobina, setEstadosBobina] = useState<EstadoBobinaDropdownDto[]>([]);
|
const [estadosBobina, setEstadosBobina] = useState<EstadoBobinaDto[]>([]);
|
||||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||||
|
|
||||||
|
// Estados de los modales
|
||||||
const [ingresoModalOpen, setIngresoModalOpen] = useState(false);
|
const [ingresoModalOpen, setIngresoModalOpen] = useState(false);
|
||||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||||
const [cambioEstadoModalOpen, setCambioEstadoModalOpen] = useState(false);
|
const [cambioEstadoModalOpen, setCambioEstadoModalOpen] = useState(false);
|
||||||
|
|
||||||
const [selectedBobina, setSelectedBobina] = useState<StockBobinaDto | null>(null); // Para los modales
|
// Estado para la bobina seleccionada en un modal o menú
|
||||||
|
const [selectedBobina, setSelectedBobina] = useState<StockBobinaDto | null>(null);
|
||||||
|
|
||||||
|
// Estados para la paginación y el menú de acciones
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const [rowsPerPage, setRowsPerPage] = useState(25);
|
const [rowsPerPage, setRowsPerPage] = useState(25);
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
const [selectedBobinaForRowMenu, setSelectedBobinaForRowMenu] = useState<StockBobinaDto | null>(null); // Para el menú contextual
|
const [selectedBobinaForRowMenu, setSelectedBobinaForRowMenu] = useState<StockBobinaDto | null>(null);
|
||||||
|
|
||||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||||
const puedeVer = isSuperAdmin || tienePermiso("IB001");
|
const puedeVer = isSuperAdmin || tienePermiso("IB001");
|
||||||
@@ -72,13 +79,16 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
const puedeModificarDatos = isSuperAdmin || tienePermiso("IB004");
|
const puedeModificarDatos = isSuperAdmin || tienePermiso("IB004");
|
||||||
const puedeEliminar = isSuperAdmin || tienePermiso("IB005");
|
const puedeEliminar = isSuperAdmin || tienePermiso("IB005");
|
||||||
|
|
||||||
|
const lastOpenedMenuButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
const fetchFiltersDropdownData = useCallback(async () => {
|
const fetchFiltersDropdownData = useCallback(async () => {
|
||||||
setLoadingFiltersDropdown(true);
|
setLoadingFiltersDropdown(true);
|
||||||
try {
|
try {
|
||||||
|
// Asumiendo que estos servicios existen y devuelven los DTOs correctos
|
||||||
const [tiposData, plantasData, estadosData] = await Promise.all([
|
const [tiposData, plantasData, estadosData] = await Promise.all([
|
||||||
tipoBobinaService.getAllDropdownTiposBobina(),
|
tipoBobinaService.getAllTiposBobina(),
|
||||||
plantaService.getPlantasForDropdown(),
|
plantaService.getAllPlantas(),
|
||||||
estadoBobinaService.getAllDropdownEstadosBobina()
|
estadoBobinaService.getAllEstadosBobina()
|
||||||
]);
|
]);
|
||||||
setTiposBobina(tiposData);
|
setTiposBobina(tiposData);
|
||||||
setPlantas(plantasData);
|
setPlantas(plantasData);
|
||||||
@@ -95,12 +105,15 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
fetchFiltersDropdownData();
|
fetchFiltersDropdownData();
|
||||||
}, [fetchFiltersDropdownData]);
|
}, [fetchFiltersDropdownData]);
|
||||||
|
|
||||||
|
|
||||||
const cargarStock = useCallback(async () => {
|
const cargarStock = useCallback(async () => {
|
||||||
if (!puedeVer) {
|
if (!puedeVer) {
|
||||||
setError("No tiene permiso para ver esta sección."); setLoading(false); return;
|
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 {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
idTipoBobina: filtroTipoBobina ? Number(filtroTipoBobina) : null,
|
idTipoBobina: filtroTipoBobina ? Number(filtroTipoBobina) : null,
|
||||||
@@ -108,19 +121,39 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
idPlanta: filtroPlanta ? Number(filtroPlanta) : null,
|
idPlanta: filtroPlanta ? Number(filtroPlanta) : null,
|
||||||
idEstadoBobina: filtroEstadoBobina ? Number(filtroEstadoBobina) : null,
|
idEstadoBobina: filtroEstadoBobina ? Number(filtroEstadoBobina) : null,
|
||||||
remitoFilter: filtroRemito || null,
|
remitoFilter: filtroRemito || null,
|
||||||
fechaDesde: filtroFechaDesde || null,
|
fechaDesde: filtroFechaHabilitado ? filtroFechaDesde : null,
|
||||||
fechaHasta: filtroFechaHasta || null,
|
fechaHasta: filtroFechaHabilitado ? filtroFechaHasta : null,
|
||||||
};
|
};
|
||||||
const data = await stockBobinaService.getAllStockBobinas(params);
|
const data = await stockBobinaService.getAllStockBobinas(params);
|
||||||
setStock(data);
|
setStock(data);
|
||||||
|
if (data.length === 0) {
|
||||||
|
setError("No se encontraron resultados con los filtros aplicados.");
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err); setError('Error al cargar el stock de bobinas.');
|
console.error(err);
|
||||||
} finally { setLoading(false); }
|
setError('Error al cargar el stock de bobinas.');
|
||||||
}, [puedeVer, filtroTipoBobina, filtroNroBobina, filtroPlanta, filtroEstadoBobina, filtroRemito, filtroFechaDesde, filtroFechaHasta]);
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [puedeVer, filtroTipoBobina, filtroNroBobina, filtroPlanta, filtroEstadoBobina, filtroRemito, filtroFechaHabilitado, filtroFechaDesde, filtroFechaHasta]);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleBuscarClick = () => {
|
||||||
|
setPage(0); // Resetear la paginación al buscar
|
||||||
cargarStock();
|
cargarStock();
|
||||||
}, [cargarStock]);
|
};
|
||||||
|
|
||||||
|
const handleLimpiarFiltros = () => {
|
||||||
|
setFiltroTipoBobina('');
|
||||||
|
setFiltroNroBobina('');
|
||||||
|
setFiltroPlanta('');
|
||||||
|
setFiltroEstadoBobina('');
|
||||||
|
setFiltroRemito('');
|
||||||
|
setFiltroFechaHabilitado(false);
|
||||||
|
setFiltroFechaDesde(new Date().toISOString().split('T')[0]);
|
||||||
|
setFiltroFechaHasta(new Date().toISOString().split('T')[0]);
|
||||||
|
setStock([]); // Limpiar los resultados actuales
|
||||||
|
setError(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleOpenIngresoModal = () => { setApiErrorMessage(null); setIngresoModalOpen(true); };
|
const handleOpenIngresoModal = () => { setApiErrorMessage(null); setIngresoModalOpen(true); };
|
||||||
const handleCloseIngresoModal = () => setIngresoModalOpen(false);
|
const handleCloseIngresoModal = () => setIngresoModalOpen(false);
|
||||||
@@ -139,13 +172,10 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
const handleCloseEditModal = () => {
|
const handleCloseEditModal = () => {
|
||||||
setEditModalOpen(false);
|
setEditModalOpen(false);
|
||||||
setSelectedBobina(null);
|
setSelectedBobina(null);
|
||||||
// Devolver el foco al botón que abrió el menú (si el modal se abrió desde el menú)
|
|
||||||
if (lastOpenedMenuButtonRef.current) {
|
if (lastOpenedMenuButtonRef.current) {
|
||||||
setTimeout(() => { // setTimeout puede ayudar
|
setTimeout(() => { lastOpenedMenuButtonRef.current?.focus(); }, 0);
|
||||||
lastOpenedMenuButtonRef.current?.focus();
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleSubmitEditModal = async (idBobina: number, data: UpdateStockBobinaDto) => {
|
const handleSubmitEditModal = async (idBobina: number, data: UpdateStockBobinaDto) => {
|
||||||
setApiErrorMessage(null);
|
setApiErrorMessage(null);
|
||||||
try { await stockBobinaService.updateDatosBobinaDisponible(idBobina, data); cargarStock(); }
|
try { await stockBobinaService.updateDatosBobinaDisponible(idBobina, data); cargarStock(); }
|
||||||
@@ -158,7 +188,7 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
setApiErrorMessage(null);
|
setApiErrorMessage(null);
|
||||||
setCambioEstadoModalOpen(true);
|
setCambioEstadoModalOpen(true);
|
||||||
};
|
};
|
||||||
const handleCloseCambioEstadoModal = () => { setCambioEstadoModalOpen(false); setSelectedBobina(null); };
|
const handleCloseCambioEstadoModal = () => setCambioEstadoModalOpen(false);
|
||||||
const handleSubmitCambioEstadoModal = async (idBobina: number, data: CambiarEstadoBobinaDto) => {
|
const handleSubmitCambioEstadoModal = async (idBobina: number, data: CambiarEstadoBobinaDto) => {
|
||||||
setApiErrorMessage(null);
|
setApiErrorMessage(null);
|
||||||
try { await stockBobinaService.cambiarEstadoBobina(idBobina, data); cargarStock(); }
|
try { await stockBobinaService.cambiarEstadoBobina(idBobina, data); cargarStock(); }
|
||||||
@@ -167,7 +197,6 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
|
|
||||||
const handleDeleteBobina = async (bobina: StockBobinaDto | null) => {
|
const handleDeleteBobina = async (bobina: StockBobinaDto | null) => {
|
||||||
if (!bobina) return;
|
if (!bobina) return;
|
||||||
// Permitir eliminar si está Disponible (1) o Dañada (3)
|
|
||||||
if (bobina.idEstadoBobina !== ID_ESTADO_DISPONIBLE && bobina.idEstadoBobina !== ID_ESTADO_DANADA) {
|
if (bobina.idEstadoBobina !== ID_ESTADO_DISPONIBLE && bobina.idEstadoBobina !== ID_ESTADO_DANADA) {
|
||||||
alert("Solo se pueden eliminar bobinas en estado 'Disponible' o 'Dañada'.");
|
alert("Solo se pueden eliminar bobinas en estado 'Disponible' o 'Dañada'.");
|
||||||
handleMenuClose();
|
handleMenuClose();
|
||||||
@@ -181,26 +210,16 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
handleMenuClose();
|
handleMenuClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const lastOpenedMenuButtonRef = React.useRef<HTMLButtonElement | null>(null);
|
|
||||||
|
|
||||||
const handleMenuOpen = (event: React.MouseEvent<HTMLButtonElement>, bobina: StockBobinaDto) => {
|
const handleMenuOpen = (event: React.MouseEvent<HTMLButtonElement>, bobina: StockBobinaDto) => {
|
||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
setSelectedBobinaForRowMenu(bobina);
|
setSelectedBobinaForRowMenu(bobina);
|
||||||
lastOpenedMenuButtonRef.current = event.currentTarget; // Guardar el botón que abrió el menú
|
lastOpenedMenuButtonRef.current = event.currentTarget;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMenuClose = () => {
|
const handleMenuClose = () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
// No es estrictamente necesario limpiar selectedBobinaForRowMenu aquí,
|
|
||||||
// ya que se actualiza en el siguiente handleMenuOpen.
|
|
||||||
// Pero se puede ser explícito:
|
|
||||||
setSelectedBobinaForRowMenu(null);
|
setSelectedBobinaForRowMenu(null);
|
||||||
|
|
||||||
// Devolver el foco al botón que abrió el menú si existe
|
|
||||||
if (lastOpenedMenuButtonRef.current) {
|
if (lastOpenedMenuButtonRef.current) {
|
||||||
setTimeout(() => { // Pequeño retraso para asegurar que el menú se haya cerrado visualmente
|
setTimeout(() => { lastOpenedMenuButtonRef.current?.focus(); }, 0);
|
||||||
lastOpenedMenuButtonRef.current?.focus();
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -209,15 +228,26 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||||
};
|
};
|
||||||
const displayData = stock.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
const displayData = stock.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||||
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR') : '-';
|
const formatDate = (dateString?: string | null) => {
|
||||||
|
if (!dateString) return '-';
|
||||||
|
const date = new Date(dateString);
|
||||||
|
if (isNaN(date.getTime())) return '-';
|
||||||
|
|
||||||
if (!loading && !puedeVer) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
const options: Intl.DateTimeFormatOptions = {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
timeZone: 'UTC'
|
||||||
|
};
|
||||||
|
return new Intl.DateTimeFormat('es-AR', options).format(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!puedeVer) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 1 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h5" gutterBottom>Stock de Bobinas</Typography>
|
<Typography variant="h5" gutterBottom>Stock de Bobinas</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
{/* ... (Filtros sin cambios) ... */}
|
|
||||||
<Typography variant="h6" gutterBottom>Filtros</Typography>
|
<Typography variant="h6" gutterBottom>Filtros</Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||||
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }}>
|
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }}>
|
||||||
@@ -243,17 +273,37 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<TextField label="Remito" size="small" value={filtroRemito} onChange={(e) => setFiltroRemito(e.target.value)} sx={{ minWidth: 150, flexGrow: 1 }} />
|
<TextField label="Remito" size="small" value={filtroRemito} onChange={(e) => setFiltroRemito(e.target.value)} sx={{ minWidth: 150, flexGrow: 1 }} />
|
||||||
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170, flexGrow: 1 }} />
|
|
||||||
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170, flexGrow: 1 }} />
|
|
||||||
</Box>
|
</Box>
|
||||||
{puedeIngresar && (<Button variant="contained" startIcon={<AddIcon />} onClick={handleOpenIngresoModal} sx={{ mt: 2 }}>Ingresar Bobina</Button>)}
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox checked={filtroFechaHabilitado} onChange={(e) => setFiltroFechaHabilitado(e.target.checked)} />}
|
||||||
|
label="Filtrar por Fechas de Remitos"
|
||||||
|
/>
|
||||||
|
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} disabled={!filtroFechaHabilitado} />
|
||||||
|
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} disabled={!filtroFechaHabilitado} />
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 2,
|
||||||
|
mb: 2,
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button variant="contained" startIcon={<SearchIcon />} onClick={handleBuscarClick} disabled={loading}>Buscar</Button>
|
||||||
|
<Button variant="outlined" startIcon={<ClearIcon />} onClick={handleLimpiarFiltros} disabled={loading}>Limpiar Filtros</Button>
|
||||||
|
</Box>
|
||||||
|
{puedeIngresar && (<Button variant="contained" startIcon={<AddIcon />} onClick={handleOpenIngresoModal} sx={{ ml: 'auto' }}>Ingresar Bobina</Button>)}
|
||||||
|
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||||
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
{error && !loading && <Alert severity="warning" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
|
||||||
{!loading && !error && puedeVer && (
|
{!loading && !error && (
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table size="small">
|
<Table size="small">
|
||||||
<TableHead><TableRow>
|
<TableHead><TableRow>
|
||||||
@@ -262,12 +312,11 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
<TableCell>F. Remito</TableCell><TableCell>F. Estado</TableCell>
|
<TableCell>F. Remito</TableCell><TableCell>F. Estado</TableCell>
|
||||||
<TableCell>Publicación</TableCell><TableCell>Sección</TableCell>
|
<TableCell>Publicación</TableCell><TableCell>Sección</TableCell>
|
||||||
<TableCell>Obs.</TableCell>
|
<TableCell>Obs.</TableCell>
|
||||||
{/* Mostrar columna de acciones si tiene algún permiso de acción */}
|
|
||||||
{(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
{(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
||||||
</TableRow></TableHead>
|
</TableRow></TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{displayData.length === 0 ? (
|
{displayData.length === 0 ? (
|
||||||
<TableRow><TableCell colSpan={(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) ? 12 : 11} align="center">No se encontraron bobinas con los filtros aplicados.</TableCell></TableRow>
|
<TableRow><TableCell colSpan={(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) ? 12 : 11} align="center">No se encontraron bobinas con los filtros aplicados. Haga clic en "Buscar" para iniciar una consulta.</TableCell></TableRow>
|
||||||
) : (
|
) : (
|
||||||
displayData.map((b) => (
|
displayData.map((b) => (
|
||||||
<TableRow key={b.idBobina} hover>
|
<TableRow key={b.idBobina} hover>
|
||||||
@@ -283,10 +332,9 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
{(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) && (
|
{(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) && (
|
||||||
<TableCell align="right">
|
<TableCell align="right">
|
||||||
<IconButton onClick={(e) => handleMenuOpen(e, b)}
|
<IconButton onClick={(e) => handleMenuOpen(e, b)}
|
||||||
// El botón de menú se deshabilita si no hay NINGUNA acción posible para esa fila
|
|
||||||
disabled={
|
disabled={
|
||||||
!(b.idEstadoBobina === ID_ESTADO_DISPONIBLE && puedeModificarDatos) &&
|
!(b.idEstadoBobina === ID_ESTADO_DISPONIBLE && puedeModificarDatos) &&
|
||||||
!(puedeCambiarEstado) && // Siempre se puede intentar cambiar estado (el modal lo validará)
|
!(puedeCambiarEstado) &&
|
||||||
!((b.idEstadoBobina === ID_ESTADO_DISPONIBLE || b.idEstadoBobina === ID_ESTADO_DANADA) && puedeEliminar)
|
!((b.idEstadoBobina === ID_ESTADO_DISPONIBLE || b.idEstadoBobina === ID_ESTADO_DANADA) && puedeEliminar)
|
||||||
}
|
}
|
||||||
><MoreVertIcon /></IconButton>
|
><MoreVertIcon /></IconButton>
|
||||||
@@ -310,21 +358,17 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
<EditIcon fontSize="small" sx={{ mr: 1 }} /> Editar Datos
|
<EditIcon fontSize="small" sx={{ mr: 1 }} /> Editar Datos
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{/* --- CAMBIO: Permitir cambiar estado incluso si está Dañada --- */}
|
|
||||||
{selectedBobinaForRowMenu && puedeCambiarEstado && (
|
{selectedBobinaForRowMenu && puedeCambiarEstado && (
|
||||||
<MenuItem onClick={() => { handleOpenCambioEstadoModal(selectedBobinaForRowMenu); handleMenuClose(); }}>
|
<MenuItem onClick={() => { handleOpenCambioEstadoModal(selectedBobinaForRowMenu); handleMenuClose(); }}>
|
||||||
<SwapHorizIcon fontSize="small" sx={{ mr: 1 }} /> Cambiar Estado
|
<SwapHorizIcon fontSize="small" sx={{ mr: 1 }} /> Cambiar Estado
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{/* --- CAMBIO: Permitir eliminar si está Disponible o Dañada --- */}
|
|
||||||
{selectedBobinaForRowMenu && puedeEliminar &&
|
{selectedBobinaForRowMenu && puedeEliminar &&
|
||||||
(selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DISPONIBLE || selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DANADA) && (
|
(selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DISPONIBLE || selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DANADA) && (
|
||||||
<MenuItem onClick={() => handleDeleteBobina(selectedBobinaForRowMenu)}>
|
<MenuItem onClick={() => handleDeleteBobina(selectedBobinaForRowMenu)}>
|
||||||
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar Ingreso
|
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar Ingreso
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Lógica para el MenuItem "Sin acciones" */}
|
|
||||||
{selectedBobinaForRowMenu &&
|
{selectedBobinaForRowMenu &&
|
||||||
!((selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DISPONIBLE && puedeModificarDatos)) &&
|
!((selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DISPONIBLE && puedeModificarDatos)) &&
|
||||||
!(puedeCambiarEstado) &&
|
!(puedeCambiarEstado) &&
|
||||||
@@ -333,6 +377,7 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
|
{/* Modales sin cambios */}
|
||||||
<StockBobinaIngresoFormModal
|
<StockBobinaIngresoFormModal
|
||||||
open={ingresoModalOpen} onClose={handleCloseIngresoModal} onSubmit={handleSubmitIngresoModal}
|
open={ingresoModalOpen} onClose={handleCloseIngresoModal} onSubmit={handleSubmitIngresoModal}
|
||||||
errorMessage={apiErrorMessage} clearErrorMessage={() => setApiErrorMessage(null)}
|
errorMessage={apiErrorMessage} clearErrorMessage={() => setApiErrorMessage(null)}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useCallback, useMemo, type JSXElementConstructor, type HTMLAttributes } from 'react'; // Añadido JSXElementConstructor, HTMLAttributes
|
import React, { useState, useCallback, useMemo, type JSXElementConstructor, type HTMLAttributes } from 'react';
|
||||||
import {
|
import {
|
||||||
Box, Typography, Paper, CircularProgress, Alert, Button, type SxProps, type Theme // Añadido SxProps, Theme
|
Box, Typography, Paper, CircularProgress, Alert, Button, type SxProps, type Theme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { DataGrid, type GridColDef, GridFooterContainer, GridFooter, type GridSlotsComponent } from '@mui/x-data-grid'; // Añadido GridSlotsComponent
|
import { DataGrid, type GridColDef, GridFooterContainer, GridFooter, type GridSlotsComponent } from '@mui/x-data-grid';
|
||||||
import { esES } from '@mui/x-data-grid/locales';
|
import { esES } from '@mui/x-data-grid/locales';
|
||||||
import reportesService from '../../services/Reportes/reportesService';
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
import type { ReporteDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ReporteDistribucionCanillasResponseDto';
|
import type { ReporteDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ReporteDistribucionCanillasResponseDto';
|
||||||
@@ -11,7 +11,7 @@ import * as XLSX from 'xlsx';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
// Para el tipo del footer en DataGridSectionProps
|
// Para el tipo del footer en DataGridSectionProps
|
||||||
type FooterPropsOverrides = {}; // Puedes extender esto si tus footers tienen props específicos
|
type FooterPropsOverrides = {};
|
||||||
type CustomFooterType = JSXElementConstructor<HTMLAttributes<HTMLDivElement> & { sx?: SxProps<Theme> } & FooterPropsOverrides>;
|
type CustomFooterType = JSXElementConstructor<HTMLAttributes<HTMLDivElement> & { sx?: SxProps<Theme> } & FooterPropsOverrides>;
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ const DataGridSection: React.FC<DataGridSectionProps> = ({ title, data, columns,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!rows || rows.length === 0) {
|
if (!rows || rows.length === 0) {
|
||||||
return <Typography sx={{ mt: 1, fontStyle: 'italic', mb:2 }}>No hay datos para {title.toLowerCase()}.</Typography>;
|
return <Typography sx={{ mt: 1, fontStyle: 'italic', mb: 2 }}>No hay datos para {title.toLowerCase()}.</Typography>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const slotsProp: Partial<GridSlotsComponent> = {};
|
const slotsProp: Partial<GridSlotsComponent> = {};
|
||||||
@@ -50,7 +50,7 @@ const DataGridSection: React.FC<DataGridSectionProps> = ({ title, data, columns,
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography variant="subtitle1" gutterBottom sx={{ mt: 2, fontWeight: 'bold' }}>{title}</Typography>
|
<Typography variant="subtitle1" gutterBottom sx={{ mt: 2, fontWeight: 'bold' }}>{title}</Typography>
|
||||||
<Paper sx={{ height: footerComponent ? 'auto' : height, width: '100%', mb: 2, '& .MuiDataGrid-footerContainer': { minHeight: footerComponent ? '52px' : undefined} }}>
|
<Paper sx={{ height: footerComponent ? 'auto' : height, width: '100%', mb: 2, '& .MuiDataGrid-footerContainer': { minHeight: footerComponent ? '52px' : undefined } }}>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -60,7 +60,7 @@ const DataGridSection: React.FC<DataGridSectionProps> = ({ title, data, columns,
|
|||||||
slots={slotsProp} // Usar el objeto slotsProp
|
slots={slotsProp} // Usar el objeto slotsProp
|
||||||
hideFooterSelectedRowCount={!!footerComponent}
|
hideFooterSelectedRowCount={!!footerComponent}
|
||||||
autoHeight={!!footerComponent}
|
autoHeight={!!footerComponent}
|
||||||
sx={!footerComponent ? {} : {
|
sx={!footerComponent ? {} : {
|
||||||
'& .MuiTablePagination-root': { display: 'none' },
|
'& .MuiTablePagination-root': { display: 'none' },
|
||||||
'& .MuiDataGrid-selectedRowCount': { display: 'none' },
|
'& .MuiDataGrid-selectedRowCount': { display: 'none' },
|
||||||
}}
|
}}
|
||||||
@@ -90,7 +90,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
const [totalesAccionistas, setTotalesAccionistas] = useState<TotalesComunes>(initialTotals);
|
const [totalesAccionistas, setTotalesAccionistas] = useState<TotalesComunes>(initialTotals);
|
||||||
const [totalesCanillasOtraFecha, setTotalesCanillasOtraFecha] = useState<TotalesComunes>(initialTotals);
|
const [totalesCanillasOtraFecha, setTotalesCanillasOtraFecha] = useState<TotalesComunes>(initialTotals);
|
||||||
const [totalesAccionistasOtraFecha, setTotalesAccionistasOtraFecha] = useState<TotalesComunes>(initialTotals);
|
const [totalesAccionistasOtraFecha, setTotalesAccionistasOtraFecha] = useState<TotalesComunes>(initialTotals);
|
||||||
|
const [totalesResumen, setTotalesResumen] = useState<TotalesComunes>(initialTotals);
|
||||||
|
|
||||||
const currencyFormatter = (value: number | null | undefined) =>
|
const currencyFormatter = (value: number | null | undefined) =>
|
||||||
value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '';
|
value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '';
|
||||||
@@ -121,7 +121,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
setApiErrorParams(null);
|
setApiErrorParams(null);
|
||||||
const empresaService = (await import('../../services/Distribucion/empresaService')).default;
|
const empresaService = (await import('../../services/Distribucion/empresaService')).default;
|
||||||
const empData = await empresaService.getEmpresaById(params.idEmpresa);
|
const empData = await empresaService.getEmpresaById(params.idEmpresa);
|
||||||
setCurrentParams({...params, nombreEmpresa: empData?.nombre});
|
setCurrentParams({ ...params, nombreEmpresa: empData?.nombre });
|
||||||
setReportData(null);
|
setReportData(null);
|
||||||
|
|
||||||
// Resetear totales
|
// Resetear totales
|
||||||
@@ -129,6 +129,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
setTotalesAccionistas(initialTotals);
|
setTotalesAccionistas(initialTotals);
|
||||||
setTotalesCanillasOtraFecha(initialTotals);
|
setTotalesCanillasOtraFecha(initialTotals);
|
||||||
setTotalesAccionistasOtraFecha(initialTotals);
|
setTotalesAccionistasOtraFecha(initialTotals);
|
||||||
|
setTotalesResumen(initialTotals);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await reportesService.getReporteDistribucionCanillas(params);
|
const data = await reportesService.getReporteDistribucionCanillas(params);
|
||||||
@@ -152,6 +153,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
calculateAndSetTotals(processedData.canillasAccionistas, setTotalesAccionistas);
|
calculateAndSetTotals(processedData.canillasAccionistas, setTotalesAccionistas);
|
||||||
calculateAndSetTotals(processedData.canillasLiquidadasOtraFecha, setTotalesCanillasOtraFecha);
|
calculateAndSetTotals(processedData.canillasLiquidadasOtraFecha, setTotalesCanillasOtraFecha);
|
||||||
calculateAndSetTotals(processedData.canillasAccionistasLiquidadasOtraFecha, setTotalesAccionistasOtraFecha);
|
calculateAndSetTotals(processedData.canillasAccionistasLiquidadasOtraFecha, setTotalesAccionistasOtraFecha);
|
||||||
|
calculateAndSetTotals(processedData.canillasTodos, setTotalesResumen);
|
||||||
|
|
||||||
const noDataFound = Object.values(processedData).every(arr => !arr || arr.length === 0);
|
const noDataFound = Object.values(processedData).every(arr => !arr || arr.length === 0);
|
||||||
if (noDataFound) {
|
if (noDataFound) {
|
||||||
@@ -183,39 +185,39 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
const wb = XLSX.utils.book_new();
|
const wb = XLSX.utils.book_new();
|
||||||
|
|
||||||
const formatAndSheet = (data: any[], sheetName: string, fields: Record<string, string>) => {
|
const formatAndSheet = (data: any[], sheetName: string, fields: Record<string, string>) => {
|
||||||
if (data && data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
const exportedData = data.map(item => {
|
const exportedData = data.map(item => {
|
||||||
const row: Record<string, any> = {};
|
const row: Record<string, any> = {};
|
||||||
// Excluir el 'id' generado para DataGrid si existe
|
// Excluir el 'id' generado para DataGrid si existe
|
||||||
const { id, ...itemData } = item;
|
const { id, ...itemData } = item;
|
||||||
Object.keys(fields).forEach(key => {
|
Object.keys(fields).forEach(key => {
|
||||||
row[fields[key]] = (itemData as any)[key]; // Usar itemData
|
row[fields[key]] = (itemData as any)[key]; // Usar itemData
|
||||||
if (key === 'fecha' && (itemData as any)[key]) {
|
if (key === 'fecha' && (itemData as any)[key]) {
|
||||||
row[fields[key]] = new Date((itemData as any)[key]).toLocaleDateString('es-AR', { timeZone: 'UTC' });
|
row[fields[key]] = new Date((itemData as any)[key]).toLocaleDateString('es-AR', { timeZone: 'UTC' });
|
||||||
}
|
}
|
||||||
if ((key === 'totalRendir') && (itemData as any)[key] != null) {
|
if ((key === 'totalRendir') && (itemData as any)[key] != null) {
|
||||||
row[fields[key]] = parseFloat((itemData as any)[key]).toFixed(2);
|
row[fields[key]] = parseFloat((itemData as any)[key]).toFixed(2);
|
||||||
}
|
}
|
||||||
if (key === 'vendidos' && itemData.totalCantSalida != null && itemData.totalCantEntrada != null) {
|
if (key === 'vendidos' && itemData.totalCantSalida != null && itemData.totalCantEntrada != null) {
|
||||||
row[fields[key]] = itemData.totalCantSalida - itemData.totalCantEntrada;
|
row[fields[key]] = itemData.totalCantSalida - itemData.totalCantEntrada;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
const ws = XLSX.utils.json_to_sheet(exportedData);
|
const ws = XLSX.utils.json_to_sheet(exportedData);
|
||||||
const headers = Object.values(fields);
|
const headers = Object.values(fields);
|
||||||
ws['!cols'] = headers.map(h => {
|
ws['!cols'] = headers.map(h => {
|
||||||
const maxLen = Math.max(...exportedData.map(row => (row[h]?.toString() ?? '').length), h.length);
|
const maxLen = Math.max(...exportedData.map(row => (row[h]?.toString() ?? '').length), h.length);
|
||||||
return { wch: maxLen + 2 };
|
return { wch: maxLen + 2 };
|
||||||
});
|
});
|
||||||
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||||
XLSX.utils.book_append_sheet(wb, ws, sheetName);
|
XLSX.utils.book_append_sheet(wb, ws, sheetName);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Definición de campos para la exportación
|
// Definición de campos para la exportación
|
||||||
const fieldsCanillaAccionista = { publicacion: "Publicación", canilla: "Canilla", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" };
|
const fieldsCanillaAccionista = { publicacion: "Publicación", canilla: "Canilla", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" };
|
||||||
const fieldsCanillaAccionistaFechaLiq = { publicacion: "Publicación", canilla: "Canilla", fecha:"Fecha Mov.", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" };
|
const fieldsCanillaAccionistaFechaLiq = { publicacion: "Publicación", canilla: "Canilla", fecha: "Fecha Mov.", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" };
|
||||||
const fieldsTodos = { publicacion: "Publicación", tipoVendedor: "Tipo", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" };
|
const fieldsTodos = { publicacion: "Publicación", tipoVendedor: "Tipo", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" };
|
||||||
|
|
||||||
formatAndSheet(reportData.canillas, "Canillitas_Dia", fieldsCanillaAccionista);
|
formatAndSheet(reportData.canillas, "Canillitas_Dia", fieldsCanillaAccionista);
|
||||||
@@ -279,7 +281,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
{ field: 'canilla', headerName: 'Canillita', width: 220, flex: 1.1 },
|
{ field: 'canilla', headerName: 'Canillita', width: 220, flex: 1.1 },
|
||||||
{ field: 'fecha', headerName: 'Fecha Mov.', width: 120, flex: 0.7, valueFormatter: (value) => value ? new Date(value as string).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-' },
|
{ field: 'fecha', headerName: 'Fecha Mov.', width: 120, flex: 0.7, valueFormatter: (value) => value ? new Date(value as string).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-' },
|
||||||
{ field: 'totalCantSalida', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) },
|
{ field: 'totalCantSalida', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) },
|
||||||
{ field: 'totalCantEntrada', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value))},
|
{ field: 'totalCantEntrada', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) },
|
||||||
{ field: 'vendidos', headerName: 'Vendidos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueGetter: (_value, row) => (row.totalCantSalida || 0) - (row.totalCantEntrada || 0), valueFormatter: (value) => numberFormatter(Number(value)) },
|
{ field: 'vendidos', headerName: 'Vendidos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueGetter: (_value, row) => (row.totalCantSalida || 0) - (row.totalCantEntrada || 0), valueFormatter: (value) => numberFormatter(Number(value)) },
|
||||||
{ field: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) },
|
{ field: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) },
|
||||||
];
|
];
|
||||||
@@ -288,7 +290,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
{ field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1.2 },
|
{ field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1.2 },
|
||||||
{ field: 'tipoVendedor', headerName: 'Tipo Vendedor', width: 150, flex: 0.8 },
|
{ field: 'tipoVendedor', headerName: 'Tipo Vendedor', width: 150, flex: 0.8 },
|
||||||
{ field: 'totalCantSalida', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) },
|
{ field: 'totalCantSalida', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) },
|
||||||
{ field: 'totalCantEntrada', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value))},
|
{ field: 'totalCantEntrada', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) },
|
||||||
{ field: 'vendidos', headerName: 'Vendidos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueGetter: (_value, row) => (row.totalCantSalida || 0) - (row.totalCantEntrada || 0), valueFormatter: (value) => numberFormatter(Number(value)) },
|
{ field: 'vendidos', headerName: 'Vendidos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueGetter: (_value, row) => (row.totalCantSalida || 0) - (row.totalCantEntrada || 0), valueFormatter: (value) => numberFormatter(Number(value)) },
|
||||||
{ field: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) },
|
{ field: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) },
|
||||||
];
|
];
|
||||||
@@ -303,7 +305,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
flex: colConfig.flex || undefined,
|
flex: colConfig.flex || undefined,
|
||||||
minWidth: colConfig.minWidth || colConfig.width || defaultWidth,
|
minWidth: colConfig.minWidth || colConfig.width || defaultWidth,
|
||||||
textAlign: (colConfig.align || 'right') as 'right' | 'left' | 'center',
|
textAlign: (colConfig.align || 'right') as 'right' | 'left' | 'center',
|
||||||
pr: isPlaceholder || colConfig.field === columnsDef[columnsDef.length-1].field ? 0 : 1,
|
pr: isPlaceholder || colConfig.field === columnsDef[columnsDef.length - 1].field ? 0 : 1,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -316,13 +318,13 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
borderTop: (theme) => `1px solid ${theme.palette.divider}`, minHeight: '52px',
|
borderTop: (theme) => `1px solid ${theme.palette.divider}`, minHeight: '52px',
|
||||||
}}>
|
}}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<GridFooter sx={{ borderTop: 'none', '& .MuiTablePagination-root, & .MuiDataGrid-selectedRowCount': { display: 'none' }}} />
|
<GridFooter sx={{ borderTop: 'none', '& .MuiTablePagination-root, & .MuiDataGrid-selectedRowCount': { display: 'none' } }} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
p: theme => theme.spacing(0, 1), display: 'flex', alignItems: 'center',
|
p: theme => theme.spacing(0, 1), display: 'flex', alignItems: 'center',
|
||||||
fontWeight: 'bold', marginLeft: 'auto', whiteSpace: 'nowrap', overflowX: 'auto',
|
fontWeight: 'bold', marginLeft: 'auto', whiteSpace: 'nowrap', overflowX: 'auto',
|
||||||
}}>
|
}}>
|
||||||
<Typography variant="subtitle2" sx={{ ...getCellStyle(columnsDef.find(c => c.field === 'publicacion' || c.field === columnsDef[0].field)), textAlign:'right' }}>TOTALES:</Typography>
|
<Typography variant="subtitle2" sx={{ ...getCellStyle(columnsDef.find(c => c.field === 'publicacion' || c.field === columnsDef[0].field)), textAlign: 'right' }}>TOTALES:</Typography>
|
||||||
<Typography variant="subtitle2" sx={{ ...getCellStyle(columnsDef.find(c => c.field === 'canilla' || c.field === 'tipoVendedor' || c.field === columnsDef[1].field), true) }}></Typography>
|
<Typography variant="subtitle2" sx={{ ...getCellStyle(columnsDef.find(c => c.field === 'canilla' || c.field === 'tipoVendedor' || c.field === columnsDef[1].field), true) }}></Typography>
|
||||||
{columnsDef.some(c => c.field === 'fecha') &&
|
{columnsDef.some(c => c.field === 'fecha') &&
|
||||||
<Typography variant="subtitle2" sx={{ ...getCellStyle(columnsDef.find(c => c.field === 'fecha'), true) }}></Typography>
|
<Typography variant="subtitle2" sx={{ ...getCellStyle(columnsDef.find(c => c.field === 'fecha'), true) }}></Typography>
|
||||||
@@ -330,7 +332,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
<Typography variant="subtitle2" sx={getCellStyle(columnsDef.find(c => c.field === 'totalCantSalida'))}>{numberFormatter(totals.totalCantSalida)}</Typography>
|
<Typography variant="subtitle2" sx={getCellStyle(columnsDef.find(c => c.field === 'totalCantSalida'))}>{numberFormatter(totals.totalCantSalida)}</Typography>
|
||||||
<Typography variant="subtitle2" sx={getCellStyle(columnsDef.find(c => c.field === 'totalCantEntrada'))}>{numberFormatter(totals.totalCantEntrada)}</Typography>
|
<Typography variant="subtitle2" sx={getCellStyle(columnsDef.find(c => c.field === 'totalCantEntrada'))}>{numberFormatter(totals.totalCantEntrada)}</Typography>
|
||||||
<Typography variant="subtitle2" sx={getCellStyle(columnsDef.find(c => c.field === 'vendidos'))}>{numberFormatter(totals.vendidos)}</Typography>
|
<Typography variant="subtitle2" sx={getCellStyle(columnsDef.find(c => c.field === 'vendidos'))}>{numberFormatter(totals.vendidos)}</Typography>
|
||||||
<Typography variant="subtitle2" sx={{...getCellStyle(columnsDef.find(c => c.field === 'totalRendir')), pr:0 }}>{currencyFormatter(totals.totalRendir)}</Typography>
|
<Typography variant="subtitle2" sx={{ ...getCellStyle(columnsDef.find(c => c.field === 'totalRendir')), pr: 0 }}>{currencyFormatter(totals.totalRendir)}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</GridFooterContainer>
|
</GridFooterContainer>
|
||||||
);
|
);
|
||||||
@@ -341,7 +343,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
const FooterAccionistas = useMemo(() => createCustomFooterComponent(totalesAccionistas, commonColumns), [totalesAccionistas]);
|
const FooterAccionistas = useMemo(() => createCustomFooterComponent(totalesAccionistas, commonColumns), [totalesAccionistas]);
|
||||||
const FooterCanillasOtraFecha = useMemo(() => createCustomFooterComponent(totalesCanillasOtraFecha, commonColumnsWithFecha), [totalesCanillasOtraFecha]);
|
const FooterCanillasOtraFecha = useMemo(() => createCustomFooterComponent(totalesCanillasOtraFecha, commonColumnsWithFecha), [totalesCanillasOtraFecha]);
|
||||||
const FooterAccionistasOtraFecha = useMemo(() => createCustomFooterComponent(totalesAccionistasOtraFecha, commonColumnsWithFecha), [totalesAccionistasOtraFecha]);
|
const FooterAccionistasOtraFecha = useMemo(() => createCustomFooterComponent(totalesAccionistasOtraFecha, commonColumnsWithFecha), [totalesAccionistasOtraFecha]);
|
||||||
|
const FooterResumen = useMemo(() => createCustomFooterComponent(totalesResumen, columnsTodos), [totalesResumen, columnsTodos]);
|
||||||
|
|
||||||
if (showParamSelector) {
|
if (showParamSelector) {
|
||||||
return (
|
return (
|
||||||
@@ -361,12 +363,12 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 2 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
||||||
<Typography variant="h5">Reporte: Detalle Distribución Canillitas ({currentParams?.nombreEmpresa}) - {currentParams?.fecha ? new Date(currentParams.fecha + 'T00:00:00').toLocaleDateString('es-AR', {timeZone:'UTC'}) : ''}</Typography>
|
<Typography variant="h5">Reporte: Detalle Distribución Canillitas ({currentParams?.nombreEmpresa}) - {currentParams?.fecha ? new Date(currentParams.fecha + 'T00:00:00').toLocaleDateString('es-AR', { timeZone: 'UTC' }) : ''}</Typography>
|
||||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||||
<Button onClick={() => handleGenerarYAbrirPdf(false)} variant="contained" disabled={loadingPdf || !reportData || !!error} size="small">
|
<Button onClick={() => handleGenerarYAbrirPdf(false)} variant="contained" disabled={loadingPdf || !reportData || !!error} size="small">
|
||||||
{loadingPdf && !pdfSoloTotales ? <CircularProgress size={20} color="inherit" /> : "PDF Detalle"}
|
{loadingPdf && !pdfSoloTotales ? <CircularProgress size={20} color="inherit" /> : "PDF Detalle"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => handleGenerarYAbrirPdf(true)} variant="contained" color="secondary" disabled={loadingPdf || !reportData || !!error} size="small">
|
<Button onClick={() => handleGenerarYAbrirPdf(true)} variant="contained" color="secondary" disabled={loadingPdf || !reportData || !!error} size="small">
|
||||||
{loadingPdf && pdfSoloTotales ? <CircularProgress size={20} color="inherit" /> : "PDF Totales"}
|
{loadingPdf && pdfSoloTotales ? <CircularProgress size={20} color="inherit" /> : "PDF Totales"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleExportToExcel} variant="outlined" disabled={!reportData || !!error} size="small">
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={!reportData || !!error} size="small">
|
||||||
@@ -378,7 +380,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{loading && <Box sx={{ textAlign: 'center', my:2 }}><CircularProgress /></Box>}
|
{loading && <Box sx={{ textAlign: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||||
{error && !loading && <Alert severity="info" sx={{ my: 2 }}>{error}</Alert>}
|
{error && !loading && <Alert severity="info" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
|
|
||||||
{!loading && !error && reportData && (
|
{!loading && !error && reportData && (
|
||||||
@@ -386,7 +388,13 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
<DataGridSection title="Canillitas" data={reportData.canillas || []} columns={commonColumns} footerComponent={FooterCanillas} />
|
<DataGridSection title="Canillitas" data={reportData.canillas || []} columns={commonColumns} footerComponent={FooterCanillas} />
|
||||||
<DataGridSection title="Accionistas" data={reportData.canillasAccionistas || []} columns={commonColumns} footerComponent={FooterAccionistas} />
|
<DataGridSection title="Accionistas" data={reportData.canillasAccionistas || []} columns={commonColumns} footerComponent={FooterAccionistas} />
|
||||||
|
|
||||||
<DataGridSection title="Resumen por Tipo de Vendedor" data={reportData.canillasTodos || []} columns={columnsTodos} height={220}/>
|
<DataGridSection
|
||||||
|
title="Resumen por Tipo de Vendedor"
|
||||||
|
data={reportData.canillasTodos || []}
|
||||||
|
columns={columnsTodos}
|
||||||
|
footerComponent={FooterResumen} // <-- PASAR EL FOOTER
|
||||||
|
height={220} // El height ya no es necesario si autoHeight está activado por tener footer
|
||||||
|
/>
|
||||||
|
|
||||||
{reportData.canillasLiquidadasOtraFecha && reportData.canillasLiquidadasOtraFecha.length > 0 &&
|
{reportData.canillasLiquidadasOtraFecha && reportData.canillasLiquidadasOtraFecha.length > 0 &&
|
||||||
<DataGridSection title="Canillitas (Liquidados de Otras Fechas)" data={reportData.canillasLiquidadasOtraFecha} columns={commonColumnsWithFecha} footerComponent={FooterCanillasOtraFecha} />}
|
<DataGridSection title="Canillitas (Liquidados de Otras Fechas)" data={reportData.canillasLiquidadasOtraFecha} columns={commonColumnsWithFecha} footerComponent={FooterCanillasOtraFecha} />}
|
||||||
@@ -395,10 +403,10 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
|||||||
<DataGridSection title="Accionistas (Liquidados de Otras Fechas)" data={reportData.canillasAccionistasLiquidadasOtraFecha} columns={commonColumnsWithFecha} footerComponent={FooterAccionistasOtraFecha} />}
|
<DataGridSection title="Accionistas (Liquidados de Otras Fechas)" data={reportData.canillasAccionistasLiquidadasOtraFecha} columns={commonColumnsWithFecha} footerComponent={FooterAccionistasOtraFecha} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!loading && !error && reportData &&
|
{!loading && !error && reportData &&
|
||||||
Object.values(reportData).every(arr => !arr || arr.length === 0) &&
|
Object.values(reportData).every(arr => !arr || arr.length === 0) &&
|
||||||
<Typography sx={{mt: 2, fontStyle: 'italic'}}>No se encontraron datos para los criterios seleccionados.</Typography>
|
<Typography sx={{ mt: 2, fontStyle: 'italic' }}>No se encontraron datos para los criterios seleccionados.</Typography>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const SeleccionaReporteListadoDistribucionCanillas: React.FC<SeleccionaReporteLi
|
|||||||
const fetchPublicaciones = async () => {
|
const fetchPublicaciones = async () => {
|
||||||
setLoadingDropdowns(true);
|
setLoadingDropdowns(true);
|
||||||
try {
|
try {
|
||||||
const data = await publicacionService.getAllPublicaciones(undefined, undefined, true);
|
const data = await publicacionService.getAllPublicaciones(undefined, undefined);
|
||||||
setPublicaciones(data.map(p => p));
|
setPublicaciones(data.map(p => p));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error al cargar publicaciones:", error);
|
console.error("Error al cargar publicaciones:", error);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const SeleccionaReporteListadoDistribucionCanillasImporte: React.FC<SeleccionaRe
|
|||||||
const fetchPublicaciones = async () => {
|
const fetchPublicaciones = async () => {
|
||||||
setLoadingDropdowns(true);
|
setLoadingDropdowns(true);
|
||||||
try {
|
try {
|
||||||
const data = await publicacionService.getAllPublicaciones(undefined, undefined, true);
|
const data = await publicacionService.getAllPublicaciones(undefined, undefined);
|
||||||
setPublicaciones(data.map(p => p));
|
setPublicaciones(data.map(p => p));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error al cargar publicaciones:", error);
|
console.error("Error al cargar publicaciones:", error);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const getPublicacionesPorDiaSemana = async (diaSemana: number): Promise<Publicac
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPublicacionesForDropdown = async (soloHabilitadas: boolean = true): Promise<PublicacionDropdownDto[]> => { // << NUEVA FUNCIÓN
|
const getPublicacionesForDropdown = async (soloHabilitadas: boolean): Promise<PublicacionDropdownDto[]> => {
|
||||||
const response = await apiClient.get<PublicacionDropdownDto[]>('/publicaciones/dropdown', { params: { soloHabilitadas } });
|
const response = await apiClient.get<PublicacionDropdownDto[]>('/publicaciones/dropdown', { params: { soloHabilitadas } });
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user