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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
if (fechaHasta.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(" AND sb.FechaRemito <= @FechaHastaParam"); // O FechaEstado
|
||||
parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); // Hasta el final del día
|
||||
sqlBuilder.Append(" AND sb.FechaRemito <= @FechaHastaParam");
|
||||
parameters.Add("FechaHastaParam", fechaHasta.Value.Date);
|
||||
}
|
||||
|
||||
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.");
|
||||
|
||||
// --- INICIO DE CAMBIO EN VALIDACIÓN ---
|
||||
// Permitir eliminar si está Disponible (1) o Dañada (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);
|
||||
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 sqlInsertHistorico = @"
|
||||
|
||||
@@ -5,6 +5,6 @@ namespace GestionIntegral.Api.Dtos.Distribucion
|
||||
public int IdPublicacion { get; set; }
|
||||
public string Nombre { 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 string NombreEmpresa { get; set; } = string.Empty; // Para mostrar en UI
|
||||
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<PublicacionDto>> ObtenerPublicacionesPorDiaSemanaAsync(byte diaSemana); // Devolvemos el DTO completo
|
||||
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(
|
||||
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||
int? idUsuarioModifico, string? tipoModificacion,
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
IdEmpresa = data.Publicacion.IdEmpresa,
|
||||
NombreEmpresa = data.NombreEmpresa ?? "Empresa Desconocida", // Manejar null para NombreEmpresa
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
.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
|
||||
Nombre = d.Publicacion!.Nombre,
|
||||
NombreEmpresa = d.NombreEmpresa ?? "Empresa Desconocida",
|
||||
Habilitada = d.Publicacion!.Habilitada ?? true // Si necesitas filtrar por esto
|
||||
Habilitada = d.Publicacion!.Habilitada
|
||||
})
|
||||
.OrderBy(p => p.Nombre)
|
||||
.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 } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import {
|
||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||
CircularProgress, Alert, FormControl, InputLabel, Select
|
||||
CircularProgress, Alert, FormControl, InputLabel, Select, FormControlLabel, Checkbox
|
||||
} from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
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 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 { CambiarEstadoBobinaDto } from '../../models/dtos/Impresion/CambiarEstadoBobinaDto';
|
||||
import type { TipoBobinaDto } from '../../models/dtos/Impresion/TipoBobinaDto';
|
||||
import type { PlantaDropdownDto } from '../../models/dtos/Impresion/PlantaDropdownDto';
|
||||
import type { EstadoBobinaDropdownDto } from '../../models/dtos/Impresion/EstadoBobinaDropdownDto';
|
||||
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||
import type { EstadoBobinaDto } from '../../models/dtos/Impresion/EstadoBobinaDto';
|
||||
|
||||
import StockBobinaIngresoFormModal from '../../components/Modals/Impresion/StockBobinaIngresoFormModal';
|
||||
import StockBobinaEditFormModal from '../../components/Modals/Impresion/StockBobinaEditFormModal';
|
||||
@@ -37,33 +38,39 @@ const ID_ESTADO_DANADA = 3;
|
||||
|
||||
const GestionarStockBobinasPage: React.FC = () => {
|
||||
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 [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Estados de los filtros
|
||||
const [filtroTipoBobina, setFiltroTipoBobina] = useState<number | string>('');
|
||||
const [filtroNroBobina, setFiltroNroBobina] = useState('');
|
||||
const [filtroPlanta, setFiltroPlanta] = useState<number | string>('');
|
||||
const [filtroEstadoBobina, setFiltroEstadoBobina] = useState<number | string>('');
|
||||
const [filtroRemito, setFiltroRemito] = useState('');
|
||||
const [filtroFechaHabilitado, setFiltroFechaHabilitado] = useState<boolean>(false); // <-- NUEVO
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = 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 [plantas, setPlantas] = useState<PlantaDropdownDto[]>([]);
|
||||
const [estadosBobina, setEstadosBobina] = useState<EstadoBobinaDropdownDto[]>([]);
|
||||
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||
const [estadosBobina, setEstadosBobina] = useState<EstadoBobinaDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
|
||||
// Estados de los modales
|
||||
const [ingresoModalOpen, setIngresoModalOpen] = useState(false);
|
||||
const [editModalOpen, setEditModalOpen] = 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 [rowsPerPage, setRowsPerPage] = useState(25);
|
||||
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 puedeVer = isSuperAdmin || tienePermiso("IB001");
|
||||
@@ -72,13 +79,16 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
const puedeModificarDatos = isSuperAdmin || tienePermiso("IB004");
|
||||
const puedeEliminar = isSuperAdmin || tienePermiso("IB005");
|
||||
|
||||
const lastOpenedMenuButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const fetchFiltersDropdownData = useCallback(async () => {
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
// Asumiendo que estos servicios existen y devuelven los DTOs correctos
|
||||
const [tiposData, plantasData, estadosData] = await Promise.all([
|
||||
tipoBobinaService.getAllDropdownTiposBobina(),
|
||||
plantaService.getPlantasForDropdown(),
|
||||
estadoBobinaService.getAllDropdownEstadosBobina()
|
||||
tipoBobinaService.getAllTiposBobina(),
|
||||
plantaService.getAllPlantas(),
|
||||
estadoBobinaService.getAllEstadosBobina()
|
||||
]);
|
||||
setTiposBobina(tiposData);
|
||||
setPlantas(plantasData);
|
||||
@@ -95,12 +105,15 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
fetchFiltersDropdownData();
|
||||
}, [fetchFiltersDropdownData]);
|
||||
|
||||
|
||||
const cargarStock = useCallback(async () => {
|
||||
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 {
|
||||
const params = {
|
||||
idTipoBobina: filtroTipoBobina ? Number(filtroTipoBobina) : null,
|
||||
@@ -108,19 +121,39 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
idPlanta: filtroPlanta ? Number(filtroPlanta) : null,
|
||||
idEstadoBobina: filtroEstadoBobina ? Number(filtroEstadoBobina) : null,
|
||||
remitoFilter: filtroRemito || null,
|
||||
fechaDesde: filtroFechaDesde || null,
|
||||
fechaHasta: filtroFechaHasta || null,
|
||||
fechaDesde: filtroFechaHabilitado ? filtroFechaDesde : null,
|
||||
fechaHasta: filtroFechaHabilitado ? filtroFechaHasta : null,
|
||||
};
|
||||
const data = await stockBobinaService.getAllStockBobinas(params);
|
||||
setStock(data);
|
||||
if (data.length === 0) {
|
||||
setError("No se encontraron resultados con los filtros aplicados.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err); setError('Error al cargar el stock de bobinas.');
|
||||
} finally { setLoading(false); }
|
||||
}, [puedeVer, filtroTipoBobina, filtroNroBobina, filtroPlanta, filtroEstadoBobina, filtroRemito, filtroFechaDesde, filtroFechaHasta]);
|
||||
console.error(err);
|
||||
setError('Error al cargar el stock de bobinas.');
|
||||
} 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]);
|
||||
};
|
||||
|
||||
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 handleCloseIngresoModal = () => setIngresoModalOpen(false);
|
||||
@@ -139,13 +172,10 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
const handleCloseEditModal = () => {
|
||||
setEditModalOpen(false);
|
||||
setSelectedBobina(null);
|
||||
// Devolver el foco al botón que abrió el menú (si el modal se abrió desde el menú)
|
||||
if (lastOpenedMenuButtonRef.current) {
|
||||
setTimeout(() => { // setTimeout puede ayudar
|
||||
lastOpenedMenuButtonRef.current?.focus();
|
||||
}, 0);
|
||||
setTimeout(() => { lastOpenedMenuButtonRef.current?.focus(); }, 0);
|
||||
}
|
||||
};
|
||||
};
|
||||
const handleSubmitEditModal = async (idBobina: number, data: UpdateStockBobinaDto) => {
|
||||
setApiErrorMessage(null);
|
||||
try { await stockBobinaService.updateDatosBobinaDisponible(idBobina, data); cargarStock(); }
|
||||
@@ -158,7 +188,7 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
setApiErrorMessage(null);
|
||||
setCambioEstadoModalOpen(true);
|
||||
};
|
||||
const handleCloseCambioEstadoModal = () => { setCambioEstadoModalOpen(false); setSelectedBobina(null); };
|
||||
const handleCloseCambioEstadoModal = () => setCambioEstadoModalOpen(false);
|
||||
const handleSubmitCambioEstadoModal = async (idBobina: number, data: CambiarEstadoBobinaDto) => {
|
||||
setApiErrorMessage(null);
|
||||
try { await stockBobinaService.cambiarEstadoBobina(idBobina, data); cargarStock(); }
|
||||
@@ -167,7 +197,6 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
|
||||
const handleDeleteBobina = async (bobina: StockBobinaDto | null) => {
|
||||
if (!bobina) return;
|
||||
// Permitir eliminar si está Disponible (1) o Dañada (3)
|
||||
if (bobina.idEstadoBobina !== ID_ESTADO_DISPONIBLE && bobina.idEstadoBobina !== ID_ESTADO_DANADA) {
|
||||
alert("Solo se pueden eliminar bobinas en estado 'Disponible' o 'Dañada'.");
|
||||
handleMenuClose();
|
||||
@@ -181,26 +210,16 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
const lastOpenedMenuButtonRef = React.useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLButtonElement>, bobina: StockBobinaDto) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
setSelectedBobinaForRowMenu(bobina);
|
||||
lastOpenedMenuButtonRef.current = event.currentTarget; // Guardar el botón que abrió el menú
|
||||
lastOpenedMenuButtonRef.current = event.currentTarget;
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
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);
|
||||
|
||||
// Devolver el foco al botón que abrió el menú si existe
|
||||
if (lastOpenedMenuButtonRef.current) {
|
||||
setTimeout(() => { // Pequeño retraso para asegurar que el menú se haya cerrado visualmente
|
||||
lastOpenedMenuButtonRef.current?.focus();
|
||||
}, 0);
|
||||
setTimeout(() => { lastOpenedMenuButtonRef.current?.focus(); }, 0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -209,15 +228,26 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||
};
|
||||
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 (
|
||||
<Box sx={{ p: 1 }}>
|
||||
<Typography variant="h5" gutterBottom>Stock de Bobinas</Typography>
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
{/* ... (Filtros sin cambios) ... */}
|
||||
<Typography variant="h6" gutterBottom>Filtros</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }}>
|
||||
@@ -243,17 +273,37 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
</Select>
|
||||
</FormControl>
|
||||
<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>
|
||||
{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>
|
||||
|
||||
{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>}
|
||||
|
||||
{!loading && !error && puedeVer && (
|
||||
{!loading && !error && (
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead><TableRow>
|
||||
@@ -262,12 +312,11 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
<TableCell>F. Remito</TableCell><TableCell>F. Estado</TableCell>
|
||||
<TableCell>Publicación</TableCell><TableCell>Sección</TableCell>
|
||||
<TableCell>Obs.</TableCell>
|
||||
{/* Mostrar columna de acciones si tiene algún permiso de acción */}
|
||||
{(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
{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) => (
|
||||
<TableRow key={b.idBobina} hover>
|
||||
@@ -283,10 +332,9 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
{(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) && (
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={(e) => handleMenuOpen(e, b)}
|
||||
// El botón de menú se deshabilita si no hay NINGUNA acción posible para esa fila
|
||||
disabled={
|
||||
!(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)
|
||||
}
|
||||
><MoreVertIcon /></IconButton>
|
||||
@@ -310,21 +358,17 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
<EditIcon fontSize="small" sx={{ mr: 1 }} /> Editar Datos
|
||||
</MenuItem>
|
||||
)}
|
||||
{/* --- CAMBIO: Permitir cambiar estado incluso si está Dañada --- */}
|
||||
{selectedBobinaForRowMenu && puedeCambiarEstado && (
|
||||
<MenuItem onClick={() => { handleOpenCambioEstadoModal(selectedBobinaForRowMenu); handleMenuClose(); }}>
|
||||
<SwapHorizIcon fontSize="small" sx={{ mr: 1 }} /> Cambiar Estado
|
||||
</MenuItem>
|
||||
)}
|
||||
{/* --- CAMBIO: Permitir eliminar si está Disponible o Dañada --- */}
|
||||
{selectedBobinaForRowMenu && puedeEliminar &&
|
||||
(selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DISPONIBLE || selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DANADA) && (
|
||||
<MenuItem onClick={() => handleDeleteBobina(selectedBobinaForRowMenu)}>
|
||||
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar Ingreso
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{/* Lógica para el MenuItem "Sin acciones" */}
|
||||
{selectedBobinaForRowMenu &&
|
||||
!((selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DISPONIBLE && puedeModificarDatos)) &&
|
||||
!(puedeCambiarEstado) &&
|
||||
@@ -333,6 +377,7 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
}
|
||||
</Menu>
|
||||
|
||||
{/* Modales sin cambios */}
|
||||
<StockBobinaIngresoFormModal
|
||||
open={ingresoModalOpen} onClose={handleCloseIngresoModal} onSubmit={handleSubmitIngresoModal}
|
||||
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 {
|
||||
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';
|
||||
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 reportesService from '../../services/Reportes/reportesService';
|
||||
import type { ReporteDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ReporteDistribucionCanillasResponseDto';
|
||||
@@ -11,7 +11,7 @@ import * as XLSX from 'xlsx';
|
||||
import axios from 'axios';
|
||||
|
||||
// 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>;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ const DataGridSection: React.FC<DataGridSectionProps> = ({ title, data, columns,
|
||||
}
|
||||
|
||||
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> = {};
|
||||
@@ -50,7 +50,7 @@ const DataGridSection: React.FC<DataGridSectionProps> = ({ title, data, columns,
|
||||
return (
|
||||
<>
|
||||
<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
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
@@ -60,7 +60,7 @@ const DataGridSection: React.FC<DataGridSectionProps> = ({ title, data, columns,
|
||||
slots={slotsProp} // Usar el objeto slotsProp
|
||||
hideFooterSelectedRowCount={!!footerComponent}
|
||||
autoHeight={!!footerComponent}
|
||||
sx={!footerComponent ? {} : {
|
||||
sx={!footerComponent ? {} : {
|
||||
'& .MuiTablePagination-root': { display: 'none' },
|
||||
'& .MuiDataGrid-selectedRowCount': { display: 'none' },
|
||||
}}
|
||||
@@ -90,7 +90,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
const [totalesAccionistas, setTotalesAccionistas] = useState<TotalesComunes>(initialTotals);
|
||||
const [totalesCanillasOtraFecha, setTotalesCanillasOtraFecha] = useState<TotalesComunes>(initialTotals);
|
||||
const [totalesAccionistasOtraFecha, setTotalesAccionistasOtraFecha] = useState<TotalesComunes>(initialTotals);
|
||||
|
||||
const [totalesResumen, setTotalesResumen] = useState<TotalesComunes>(initialTotals);
|
||||
|
||||
const currencyFormatter = (value: number | null | undefined) =>
|
||||
value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '';
|
||||
@@ -121,7 +121,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
setApiErrorParams(null);
|
||||
const empresaService = (await import('../../services/Distribucion/empresaService')).default;
|
||||
const empData = await empresaService.getEmpresaById(params.idEmpresa);
|
||||
setCurrentParams({...params, nombreEmpresa: empData?.nombre});
|
||||
setCurrentParams({ ...params, nombreEmpresa: empData?.nombre });
|
||||
setReportData(null);
|
||||
|
||||
// Resetear totales
|
||||
@@ -129,11 +129,12 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
setTotalesAccionistas(initialTotals);
|
||||
setTotalesCanillasOtraFecha(initialTotals);
|
||||
setTotalesAccionistasOtraFecha(initialTotals);
|
||||
setTotalesResumen(initialTotals);
|
||||
|
||||
try {
|
||||
const data = await reportesService.getReporteDistribucionCanillas(params);
|
||||
|
||||
const addIds = <T extends Record<string, any>>(arr: T[] | undefined, prefix: string): Array<T & { id: string }> =>
|
||||
|
||||
const addIds = <T extends Record<string, any>>(arr: T[] | undefined, prefix: string): Array<T & { id: string }> =>
|
||||
(arr || []).map((item, index) => ({ ...item, id: `${prefix}-${item.publicacion || item.tipoVendedor || item.remito || item.devueltos || 'item'}-${index}-${Math.random().toString(36).substring(7)}` }));
|
||||
|
||||
const processedData = {
|
||||
@@ -142,7 +143,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
canillasTodos: addIds(data.canillasTodos, 'all'), // Aún necesita IDs para DataGridSection
|
||||
canillasLiquidadasOtraFecha: addIds(data.canillasLiquidadasOtraFecha, 'canliq'),
|
||||
canillasAccionistasLiquidadasOtraFecha: addIds(data.canillasAccionistasLiquidadasOtraFecha, 'accliq'),
|
||||
controlDevolucionesDetalle: addIds(data.controlDevolucionesDetalle, 'cdd'),
|
||||
controlDevolucionesDetalle: addIds(data.controlDevolucionesDetalle, 'cdd'),
|
||||
controlDevolucionesRemitos: addIds(data.controlDevolucionesRemitos, 'cdr'),
|
||||
controlDevolucionesOtrosDias: addIds(data.controlDevolucionesOtrosDias, 'cdo')
|
||||
};
|
||||
@@ -152,7 +153,8 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
calculateAndSetTotals(processedData.canillasAccionistas, setTotalesAccionistas);
|
||||
calculateAndSetTotals(processedData.canillasLiquidadasOtraFecha, setTotalesCanillasOtraFecha);
|
||||
calculateAndSetTotals(processedData.canillasAccionistasLiquidadasOtraFecha, setTotalesAccionistasOtraFecha);
|
||||
|
||||
calculateAndSetTotals(processedData.canillasTodos, setTotalesResumen);
|
||||
|
||||
const noDataFound = Object.values(processedData).every(arr => !arr || arr.length === 0);
|
||||
if (noDataFound) {
|
||||
setError("No se encontraron datos para los parámetros seleccionados.");
|
||||
@@ -183,41 +185,41 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
const wb = XLSX.utils.book_new();
|
||||
|
||||
const formatAndSheet = (data: any[], sheetName: string, fields: Record<string, string>) => {
|
||||
if (data && data.length > 0) {
|
||||
const exportedData = data.map(item => {
|
||||
const row: Record<string, any> = {};
|
||||
// Excluir el 'id' generado para DataGrid si existe
|
||||
const { id, ...itemData } = item;
|
||||
Object.keys(fields).forEach(key => {
|
||||
row[fields[key]] = (itemData as any)[key]; // Usar itemData
|
||||
if (key === 'fecha' && (itemData as any)[key]) {
|
||||
row[fields[key]] = new Date((itemData as any)[key]).toLocaleDateString('es-AR', { timeZone: 'UTC' });
|
||||
}
|
||||
if ((key === 'totalRendir') && (itemData as any)[key] != null) {
|
||||
row[fields[key]] = parseFloat((itemData as any)[key]).toFixed(2);
|
||||
}
|
||||
if (key === 'vendidos' && itemData.totalCantSalida != null && itemData.totalCantEntrada != null) {
|
||||
row[fields[key]] = itemData.totalCantSalida - itemData.totalCantEntrada;
|
||||
}
|
||||
});
|
||||
return row;
|
||||
});
|
||||
const ws = XLSX.utils.json_to_sheet(exportedData);
|
||||
const headers = Object.values(fields);
|
||||
ws['!cols'] = headers.map(h => {
|
||||
const maxLen = Math.max(...exportedData.map(row => (row[h]?.toString() ?? '').length), h.length);
|
||||
return { wch: maxLen + 2 };
|
||||
});
|
||||
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||
XLSX.utils.book_append_sheet(wb, ws, sheetName);
|
||||
}
|
||||
if (data && data.length > 0) {
|
||||
const exportedData = data.map(item => {
|
||||
const row: Record<string, any> = {};
|
||||
// Excluir el 'id' generado para DataGrid si existe
|
||||
const { id, ...itemData } = item;
|
||||
Object.keys(fields).forEach(key => {
|
||||
row[fields[key]] = (itemData as any)[key]; // Usar itemData
|
||||
if (key === 'fecha' && (itemData as any)[key]) {
|
||||
row[fields[key]] = new Date((itemData as any)[key]).toLocaleDateString('es-AR', { timeZone: 'UTC' });
|
||||
}
|
||||
if ((key === 'totalRendir') && (itemData as any)[key] != null) {
|
||||
row[fields[key]] = parseFloat((itemData as any)[key]).toFixed(2);
|
||||
}
|
||||
if (key === 'vendidos' && itemData.totalCantSalida != null && itemData.totalCantEntrada != null) {
|
||||
row[fields[key]] = itemData.totalCantSalida - itemData.totalCantEntrada;
|
||||
}
|
||||
});
|
||||
return row;
|
||||
});
|
||||
const ws = XLSX.utils.json_to_sheet(exportedData);
|
||||
const headers = Object.values(fields);
|
||||
ws['!cols'] = headers.map(h => {
|
||||
const maxLen = Math.max(...exportedData.map(row => (row[h]?.toString() ?? '').length), h.length);
|
||||
return { wch: maxLen + 2 };
|
||||
});
|
||||
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||
XLSX.utils.book_append_sheet(wb, ws, sheetName);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 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 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" };
|
||||
|
||||
|
||||
formatAndSheet(reportData.canillas, "Canillitas_Dia", fieldsCanillaAccionista);
|
||||
formatAndSheet(reportData.canillasAccionistas, "Accionistas_Dia", fieldsCanillaAccionista);
|
||||
formatAndSheet(reportData.canillasTodos, "Resumen_Dia", fieldsTodos);
|
||||
@@ -273,13 +275,13 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
{ 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)) },
|
||||
];
|
||||
|
||||
|
||||
const commonColumnsWithFecha: GridColDef[] = [
|
||||
{ field: 'publicacion', headerName: 'Publicación', width: 200, flex: 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: '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: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) },
|
||||
];
|
||||
@@ -288,11 +290,11 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
{ field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1.2 },
|
||||
{ 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: '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: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) },
|
||||
];
|
||||
|
||||
|
||||
// --- Custom Footers ---
|
||||
const createCustomFooterComponent = (totals: TotalesComunes, columnsDef: GridColDef[]): CustomFooterType => { // Especificar el tipo de retorno
|
||||
const getCellStyle = (colConfig: GridColDef | undefined, isPlaceholder: boolean = false) => {
|
||||
@@ -303,11 +305,11 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
flex: colConfig.flex || undefined,
|
||||
minWidth: colConfig.minWidth || colConfig.width || defaultWidth,
|
||||
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',
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const FooterComponent: CustomFooterType = (props) => ( // El componente debe aceptar props
|
||||
<GridFooterContainer {...props} sx={{ // Pasar props y combinar sx
|
||||
@@ -316,21 +318,21 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
borderTop: (theme) => `1px solid ${theme.palette.divider}`, minHeight: '52px',
|
||||
}}>
|
||||
<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 sx={{
|
||||
p: theme => theme.spacing(0, 1), display: 'flex', alignItems: 'center',
|
||||
<Box sx={{
|
||||
p: theme => theme.spacing(0, 1), display: 'flex', alignItems: 'center',
|
||||
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>
|
||||
{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 === '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 === '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>
|
||||
</GridFooterContainer>
|
||||
);
|
||||
@@ -341,7 +343,7 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
const FooterAccionistas = useMemo(() => createCustomFooterComponent(totalesAccionistas, commonColumns), [totalesAccionistas]);
|
||||
const FooterCanillasOtraFecha = useMemo(() => createCustomFooterComponent(totalesCanillasOtraFecha, commonColumnsWithFecha), [totalesCanillasOtraFecha]);
|
||||
const FooterAccionistasOtraFecha = useMemo(() => createCustomFooterComponent(totalesAccionistasOtraFecha, commonColumnsWithFecha), [totalesAccionistasOtraFecha]);
|
||||
|
||||
const FooterResumen = useMemo(() => createCustomFooterComponent(totalesResumen, columnsTodos), [totalesResumen, columnsTodos]);
|
||||
|
||||
if (showParamSelector) {
|
||||
return (
|
||||
@@ -357,16 +359,16 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<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' }}>
|
||||
<Button onClick={() => handleGenerarYAbrirPdf(false)} variant="contained" disabled={loadingPdf || !reportData || !!error} size="small">
|
||||
{loadingPdf && !pdfSoloTotales ? <CircularProgress size={20} color="inherit" /> : "PDF Detalle"}
|
||||
</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"}
|
||||
</Button>
|
||||
<Button onClick={handleExportToExcel} variant="outlined" disabled={!reportData || !!error} size="small">
|
||||
@@ -378,27 +380,33 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{loading && <Box sx={{ textAlign: 'center', my:2 }}><CircularProgress /></Box>}
|
||||
{error && !loading && <Alert severity="info" sx={{ my: 2 }}>{error}</Alert>}
|
||||
{loading && <Box sx={{ textAlign: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||
{error && !loading && <Alert severity="info" sx={{ my: 2 }}>{error}</Alert>}
|
||||
|
||||
{!loading && !error && reportData && (
|
||||
<>
|
||||
<DataGridSection title="Canillitas" data={reportData.canillas || []} columns={commonColumns} footerComponent={FooterCanillas} />
|
||||
<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 &&
|
||||
<DataGridSection title="Canillitas (Liquidados de Otras Fechas)" data={reportData.canillasLiquidadasOtraFecha} columns={commonColumnsWithFecha} footerComponent={FooterCanillasOtraFecha} />}
|
||||
|
||||
|
||||
{reportData.canillasAccionistasLiquidadasOtraFecha && reportData.canillasAccionistasLiquidadasOtraFecha.length > 0 &&
|
||||
<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) &&
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ const SeleccionaReporteListadoDistribucionCanillas: React.FC<SeleccionaReporteLi
|
||||
const fetchPublicaciones = async () => {
|
||||
setLoadingDropdowns(true);
|
||||
try {
|
||||
const data = await publicacionService.getAllPublicaciones(undefined, undefined, true);
|
||||
const data = await publicacionService.getAllPublicaciones(undefined, undefined);
|
||||
setPublicaciones(data.map(p => p));
|
||||
} catch (error) {
|
||||
console.error("Error al cargar publicaciones:", error);
|
||||
|
||||
@@ -38,7 +38,7 @@ const SeleccionaReporteListadoDistribucionCanillasImporte: React.FC<SeleccionaRe
|
||||
const fetchPublicaciones = async () => {
|
||||
setLoadingDropdowns(true);
|
||||
try {
|
||||
const data = await publicacionService.getAllPublicaciones(undefined, undefined, true);
|
||||
const data = await publicacionService.getAllPublicaciones(undefined, undefined);
|
||||
setPublicaciones(data.map(p => p));
|
||||
} catch (error) {
|
||||
console.error("Error al cargar publicaciones:", error);
|
||||
|
||||
@@ -52,7 +52,7 @@ const getPublicacionesPorDiaSemana = async (diaSemana: number): Promise<Publicac
|
||||
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 } });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user