feat: DataGrid y filtro por Fechas en Stock Bobinas
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 2m15s
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 2m15s
Frontend: - Se reemplazó el componente Table por DataGrid para habilitar ordenamiento y filtrado nativo en cliente. - Se agregó la UI para filtrar por rango de "Fecha de Estado". - Se corrigió el tipado de columnas de fecha (`type: 'date'`) implementando un `valueGetter` personalizado que parsea año/mes/día localmente para evitar errores de filtrado por diferencia de Zona Horaria (UTC vs Local). - Se actualizó `stockBobinaService` para enviar los parámetros `fechaEstadoDesde` y `fechaEstadoHasta`. Backend: - Se actualizó `StockBobinasController` para recibir los nuevos parámetros de fecha. - Se modificó `StockBobinaRepository` implementando la lógica SQL para los nuevos filtros.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||
CircularProgress, Alert, FormControl, InputLabel, Select, FormControlLabel, Checkbox
|
||||
Alert, FormControl, InputLabel, Select, FormControlLabel, Checkbox
|
||||
} from '@mui/material';
|
||||
import { DataGrid, type GridColDef, type GridRenderCellParams } from '@mui/x-data-grid';
|
||||
import { esES } from '@mui/x-data-grid/locales';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
@@ -26,8 +28,8 @@ import type { TipoBobinaDto } from '../../models/dtos/Impresion/TipoBobinaDto';
|
||||
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||
import type { EstadoBobinaDto } from '../../models/dtos/Impresion/EstadoBobinaDto';
|
||||
import type { UpdateFechaRemitoLoteDto } from '../../models/dtos/Impresion/UpdateFechaRemitoLoteDto';
|
||||
import StockBobinaFechaRemitoModal from '../../components/Modals/Impresion/StockBobinaFechaRemitoModal';
|
||||
|
||||
import StockBobinaFechaRemitoModal from '../../components/Modals/Impresion/StockBobinaFechaRemitoModal';
|
||||
import StockBobinaIngresoFormModal from '../../components/Modals/Impresion/StockBobinaIngresoFormModal';
|
||||
import StockBobinaEditFormModal from '../../components/Modals/Impresion/StockBobinaEditFormModal';
|
||||
import StockBobinaCambioEstadoModal from '../../components/Modals/Impresion/StockBobinaCambioEstadoModal';
|
||||
@@ -46,16 +48,23 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Estados de los filtros
|
||||
// --- 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('');
|
||||
|
||||
// Filtro Fechas Remito
|
||||
const [filtroFechaHabilitado, setFiltroFechaHabilitado] = useState<boolean>(false);
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
|
||||
// Nuevo Filtro: Fechas Estado
|
||||
const [filtroFechaEstadoHabilitado, setFiltroFechaEstadoHabilitado] = useState<boolean>(false);
|
||||
const [filtroFechaEstadoDesde, setFiltroFechaEstadoDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
const [filtroFechaEstadoHasta, setFiltroFechaEstadoHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
|
||||
// Estados para datos de dropdowns
|
||||
const [tiposBobina, setTiposBobina] = useState<TipoBobinaDto[]>([]);
|
||||
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||
@@ -69,9 +78,7 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
const [loteModalOpen, setLoteModalOpen] = useState(false);
|
||||
const [fechaRemitoModalOpen, setFechaRemitoModalOpen] = useState(false);
|
||||
|
||||
// Estados para la paginación y el menú de acciones
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(25);
|
||||
// Menú de acciones
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [selectedBobinaForRowMenu, setSelectedBobinaForRowMenu] = useState<StockBobinaDto | null>(null);
|
||||
|
||||
@@ -82,8 +89,6 @@ 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 {
|
||||
@@ -123,13 +128,18 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
idPlanta: filtroPlanta ? Number(filtroPlanta) : null,
|
||||
idEstadoBobina: filtroEstadoBobina ? Number(filtroEstadoBobina) : null,
|
||||
remitoFilter: filtroRemito || null,
|
||||
// Fechas Remito
|
||||
fechaDesde: filtroFechaHabilitado ? filtroFechaDesde : null,
|
||||
fechaHasta: filtroFechaHabilitado ? filtroFechaHasta : null,
|
||||
// Fechas Estado (Nuevos parametros, asegurar que el backend los reciba)
|
||||
fechaEstadoDesde: filtroFechaEstadoHabilitado ? filtroFechaEstadoDesde : null,
|
||||
fechaEstadoHasta: filtroFechaEstadoHabilitado ? filtroFechaEstadoHasta : null,
|
||||
};
|
||||
const data = await stockBobinaService.getAllStockBobinas(params);
|
||||
setStock(data);
|
||||
if (data.length === 0) {
|
||||
setError("No se encontraron resultados con los filtros aplicados.");
|
||||
// No setteamos error bloqueante, solo aviso visual si se desea, o dejar tabla vacía.
|
||||
// setError("No se encontraron resultados con los filtros aplicados.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -137,10 +147,14 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [puedeVer, filtroTipoBobina, filtroNroBobina, filtroPlanta, filtroEstadoBobina, filtroRemito, filtroFechaHabilitado, filtroFechaDesde, filtroFechaHasta]);
|
||||
}, [
|
||||
puedeVer,
|
||||
filtroTipoBobina, filtroNroBobina, filtroPlanta, filtroEstadoBobina, filtroRemito,
|
||||
filtroFechaHabilitado, filtroFechaDesde, filtroFechaHasta,
|
||||
filtroFechaEstadoHabilitado, filtroFechaEstadoDesde, filtroFechaEstadoHasta
|
||||
]);
|
||||
|
||||
const handleBuscarClick = () => {
|
||||
setPage(0);
|
||||
cargarStock();
|
||||
};
|
||||
|
||||
@@ -150,21 +164,26 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
setFiltroPlanta('');
|
||||
setFiltroEstadoBobina('');
|
||||
setFiltroRemito('');
|
||||
|
||||
setFiltroFechaHabilitado(false);
|
||||
setFiltroFechaDesde(new Date().toISOString().split('T')[0]);
|
||||
setFiltroFechaHasta(new Date().toISOString().split('T')[0]);
|
||||
|
||||
setFiltroFechaEstadoHabilitado(false);
|
||||
setFiltroFechaEstadoDesde(new Date().toISOString().split('T')[0]);
|
||||
setFiltroFechaEstadoHasta(new Date().toISOString().split('T')[0]);
|
||||
|
||||
setStock([]);
|
||||
setError(null);
|
||||
};
|
||||
|
||||
//const handleOpenIngresoModal = () => { setApiErrorMessage(null); setIngresoModalOpen(true); };
|
||||
const handleCloseIngresoModal = () => setIngresoModalOpen(false);
|
||||
const handleSubmitIngresoModal = async (data: CreateStockBobinaDto) => {
|
||||
setApiErrorMessage(null);
|
||||
try { await stockBobinaService.ingresarBobina(data); cargarStock(); }
|
||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al ingresar bobina.'; setApiErrorMessage(msg); throw err; }
|
||||
};
|
||||
|
||||
|
||||
const handleLoteModalClose = (refrescar: boolean) => {
|
||||
setLoteModalOpen(false);
|
||||
if (refrescar) {
|
||||
@@ -177,7 +196,7 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
try { await stockBobinaService.updateDatosBobinaDisponible(idBobina, data); cargarStock(); }
|
||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al actualizar bobina.'; setApiErrorMessage(msg); throw err; }
|
||||
};
|
||||
|
||||
|
||||
const handleSubmitCambioEstadoModal = async (idBobina: number, data: CambiarEstadoBobinaDto) => {
|
||||
setApiErrorMessage(null);
|
||||
try { await stockBobinaService.cambiarEstadoBobina(idBobina, data); cargarStock(); }
|
||||
@@ -203,90 +222,128 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
}
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
|
||||
const handleSubmitFechaRemitoModal = async (data: UpdateFechaRemitoLoteDto) => {
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
await stockBobinaService.actualizarFechaRemitoLote(data);
|
||||
cargarStock(); // Recargar la grilla para ver el cambio
|
||||
cargarStock();
|
||||
} catch (err: any) {
|
||||
const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al actualizar la fecha del remito.';
|
||||
setApiErrorMessage(msg);
|
||||
throw err;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
// --- Handlers Menú Acciones ---
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLButtonElement>, bobina: StockBobinaDto) => {
|
||||
event.stopPropagation(); // Evitar selección de fila al abrir menú
|
||||
setAnchorEl(event.currentTarget);
|
||||
setSelectedBobinaForRowMenu(bobina);
|
||||
lastOpenedMenuButtonRef.current = event.currentTarget;
|
||||
};
|
||||
|
||||
// 1. handleMenuClose ahora solo cierra el menú. No limpia el estado de la bobina seleccionada.
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
// 2. Handlers para abrir modales. Abren el modal y cierran el menú.
|
||||
const handleOpenEditModal = () => {
|
||||
setEditModalOpen(true);
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
const handleOpenCambioEstadoModal = () => {
|
||||
setCambioEstadoModalOpen(true);
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
const handleOpenFechaRemitoModal = () => {
|
||||
setFechaRemitoModalOpen(true);
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
// 3. Handlers para cerrar modales. Cierran el modal y AHORA limpian el estado de la bobina seleccionada.
|
||||
const handleCloseEditModal = () => {
|
||||
setEditModalOpen(false);
|
||||
setSelectedBobinaForRowMenu(null);
|
||||
};
|
||||
|
||||
const handleCloseCambioEstadoModal = () => {
|
||||
setCambioEstadoModalOpen(false);
|
||||
setSelectedBobinaForRowMenu(null);
|
||||
};
|
||||
|
||||
const handleCloseFechaRemitoModal = () => {
|
||||
setFechaRemitoModalOpen(false);
|
||||
setSelectedBobinaForRowMenu(null);
|
||||
};
|
||||
|
||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||
};
|
||||
|
||||
const displayData = stock.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||
|
||||
const formatDate = (dateString?: string | null) => {
|
||||
if (!dateString) return '-';
|
||||
const date = new Date(dateString);
|
||||
if (isNaN(date.getTime())) return '-';
|
||||
const handleOpenEditModal = () => { setEditModalOpen(true); handleMenuClose(); };
|
||||
const handleOpenCambioEstadoModal = () => { setCambioEstadoModalOpen(true); handleMenuClose(); };
|
||||
const handleOpenFechaRemitoModal = () => { setFechaRemitoModalOpen(true); handleMenuClose(); };
|
||||
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
timeZone: 'UTC'
|
||||
};
|
||||
return new Intl.DateTimeFormat('es-AR', options).format(date);
|
||||
};
|
||||
const handleCloseEditModal = () => { setEditModalOpen(false); setSelectedBobinaForRowMenu(null); };
|
||||
const handleCloseCambioEstadoModal = () => { setCambioEstadoModalOpen(false); setSelectedBobinaForRowMenu(null); };
|
||||
const handleCloseFechaRemitoModal = () => { setFechaRemitoModalOpen(false); setSelectedBobinaForRowMenu(null); };
|
||||
|
||||
// --- Definición de Columnas DataGrid ---
|
||||
const columns = useMemo<GridColDef<StockBobinaDto>[]>(() => [
|
||||
{ field: 'nroBobina', headerName: 'Nro. Bobina', width: 130 },
|
||||
{ field: 'nombreTipoBobina', headerName: 'Tipo', width: 200, flex: 1 },
|
||||
{ field: 'peso', headerName: 'Peso (Kg)', width: 100, align: 'right', headerAlign: 'right', type: 'number' },
|
||||
{ field: 'nombrePlanta', headerName: 'Planta', width: 120 },
|
||||
{
|
||||
field: 'nombreEstadoBobina',
|
||||
headerName: 'Estado',
|
||||
width: 130,
|
||||
renderCell: (params) => {
|
||||
const idEstado = params.row.idEstadoBobina;
|
||||
let color: "success" | "primary" | "error" | "default" = "default";
|
||||
if (idEstado === ID_ESTADO_DISPONIBLE) color = "success";
|
||||
else if (idEstado === ID_ESTADO_UTILIZADA) color = "primary";
|
||||
else if (idEstado === ID_ESTADO_DANADA) color = "error";
|
||||
|
||||
return <Chip label={params.value} size="small" color={color} variant="outlined" />;
|
||||
}
|
||||
},
|
||||
{ field: 'remito', headerName: 'Remito', width: 120 },
|
||||
{
|
||||
field: 'fechaRemito',
|
||||
headerName: 'F. Remito',
|
||||
width: 110,
|
||||
type: 'date',
|
||||
valueGetter: (value: string) => {
|
||||
if (!value) return null;
|
||||
const datePart = value.toString().split('T')[0];
|
||||
const [year, month, day] = datePart.split('-');
|
||||
return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
|
||||
},
|
||||
valueFormatter: (value: Date) => {
|
||||
return value ? value.toLocaleDateString('es-AR') : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'fechaEstado',
|
||||
headerName: 'F. Estado',
|
||||
width: 110,
|
||||
type: 'date',
|
||||
valueGetter: (value: string) => {
|
||||
if (!value) return null;
|
||||
const datePart = value.toString().split('T')[0];
|
||||
const [year, month, day] = datePart.split('-');
|
||||
return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
|
||||
},
|
||||
valueFormatter: (value: Date) => {
|
||||
return value ? value.toLocaleDateString('es-AR') : '-';
|
||||
}
|
||||
},
|
||||
|
||||
{ field: 'nombrePublicacion', headerName: 'Publicación', width: 150 },
|
||||
{ field: 'nombreSeccion', headerName: 'Sección', width: 120 },
|
||||
{ field: 'obs', headerName: 'Obs.', width: 200, flex: 1 },
|
||||
{
|
||||
field: 'acciones',
|
||||
headerName: 'Acciones',
|
||||
width: 80,
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
align: 'right',
|
||||
renderCell: (params: GridRenderCellParams<StockBobinaDto>) => {
|
||||
const b = params.row;
|
||||
const disabled = !(puedeModificarDatos) &&
|
||||
!(puedeCambiarEstado) &&
|
||||
!((b.idEstadoBobina === ID_ESTADO_DISPONIBLE || b.idEstadoBobina === ID_ESTADO_DANADA) && puedeEliminar);
|
||||
|
||||
if (disabled) return null;
|
||||
|
||||
return (
|
||||
<IconButton onClick={(e) => handleMenuOpen(e, b)} size="small">
|
||||
<MoreVertIcon fontSize="small" />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
], [puedeModificarDatos, puedeCambiarEstado, puedeEliminar]);
|
||||
|
||||
if (!puedeVer) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 1 }}>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant="h5" gutterBottom>Stock de Bobinas</Typography>
|
||||
|
||||
{/* Panel de Filtros */}
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>Filtros</Typography>
|
||||
|
||||
{/* Fila 1: Filtros generales */}
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }}>
|
||||
<InputLabel>Tipo Bobina</InputLabel>
|
||||
@@ -312,24 +369,32 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
</FormControl>
|
||||
<TextField label="Remito" size="small" value={filtroRemito} onChange={(e) => setFiltroRemito(e.target.value)} sx={{ minWidth: 150, flexGrow: 1 }} />
|
||||
</Box>
|
||||
<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} />
|
||||
|
||||
{/* Fila 2: Filtros de Fechas */}
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 4, mb: 2, alignItems: 'center' }}>
|
||||
{/* Fechas Remito */}
|
||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', border: '1px dashed #ccc', p: 1, borderRadius: 1 }}>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={filtroFechaHabilitado} onChange={(e) => setFiltroFechaHabilitado(e.target.checked)} />}
|
||||
label="Filtrar por Fecha Remito"
|
||||
/>
|
||||
<TextField label="Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 140 }} disabled={!filtroFechaHabilitado} />
|
||||
<TextField label="Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 140 }} disabled={!filtroFechaHabilitado} />
|
||||
</Box>
|
||||
|
||||
{/* Fechas Estado (Nuevo) */}
|
||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', border: '1px dashed #ccc', p: 1, borderRadius: 1 }}>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={filtroFechaEstadoHabilitado} onChange={(e) => setFiltroFechaEstadoHabilitado(e.target.checked)} />}
|
||||
label="Filtrar por Fecha Estado"
|
||||
/>
|
||||
<TextField label="Desde" type="date" size="small" value={filtroFechaEstadoDesde} onChange={(e) => setFiltroFechaEstadoDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 140 }} disabled={!filtroFechaEstadoHabilitado} />
|
||||
<TextField label="Hasta" type="date" size="small" value={filtroFechaEstadoHasta} onChange={(e) => setFiltroFechaEstadoHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 140 }} disabled={!filtroFechaEstadoHabilitado} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: 2,
|
||||
mt: 2
|
||||
}}
|
||||
>
|
||||
|
||||
{/* Botones de acción del filtro */}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 2, mt: 2 }}>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Button variant="contained" startIcon={<SearchIcon />} onClick={handleBuscarClick} disabled={loading}>
|
||||
Buscar
|
||||
@@ -340,11 +405,6 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
</Box>
|
||||
{puedeIngresar && (
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{/*
|
||||
<Button variant="contained" startIcon={<AddIcon />} onClick={handleOpenIngresoModal}>
|
||||
Ingreso Individual
|
||||
</Button>
|
||||
*/}
|
||||
<Button variant="contained" color="secondary" startIcon={<AddIcon />} onClick={() => setLoteModalOpen(true)}>
|
||||
Ingreso por Remito (Lote)
|
||||
</Button>
|
||||
@@ -353,59 +413,28 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||
{error && !loading && <Alert severity="warning" sx={{ my: 2 }}>{error}</Alert>}
|
||||
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
||||
|
||||
{!loading && !error && (
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead><TableRow>
|
||||
<TableCell>Nro. Bobina</TableCell><TableCell>Tipo</TableCell><TableCell>Peso (Kg)</TableCell>
|
||||
<TableCell>Planta</TableCell><TableCell>Estado</TableCell><TableCell>Remito</TableCell>
|
||||
<TableCell>F. Remito</TableCell><TableCell>F. Estado</TableCell>
|
||||
<TableCell>Publicación</TableCell><TableCell>Sección</TableCell>
|
||||
<TableCell>Obs.</TableCell>
|
||||
{(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. Haga clic en "Buscar" para iniciar una consulta.</TableCell></TableRow>
|
||||
) : (
|
||||
displayData.map((b) => (
|
||||
<TableRow key={b.idBobina} hover>
|
||||
<TableCell>{b.nroBobina}</TableCell><TableCell>{b.nombreTipoBobina}</TableCell>
|
||||
<TableCell align="right">{b.peso}</TableCell><TableCell>{b.nombrePlanta}</TableCell>
|
||||
<TableCell><Chip label={b.nombreEstadoBobina} size="small" color={
|
||||
b.idEstadoBobina === ID_ESTADO_DISPONIBLE ? "success" : b.idEstadoBobina === ID_ESTADO_UTILIZADA ? "primary" : b.idEstadoBobina === ID_ESTADO_DANADA ? "error" : "default"
|
||||
} /></TableCell>
|
||||
<TableCell>{b.remito}</TableCell><TableCell>{formatDate(b.fechaRemito)}</TableCell>
|
||||
<TableCell>{formatDate(b.fechaEstado)}</TableCell>
|
||||
<TableCell>{b.nombrePublicacion || '-'}</TableCell><TableCell>{b.nombreSeccion || '-'}</TableCell>
|
||||
<TableCell>{b.obs || '-'}</TableCell>
|
||||
{(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) && (
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={(e) => handleMenuOpen(e, b)}
|
||||
disabled={
|
||||
!(puedeModificarDatos) && // Simplificado, ya que todas las opciones requieren este permiso
|
||||
!(puedeCambiarEstado) &&
|
||||
!((b.idEstadoBobina === ID_ESTADO_DISPONIBLE || b.idEstadoBobina === ID_ESTADO_DANADA) && puedeEliminar)
|
||||
}
|
||||
><MoreVertIcon /></IconButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
)))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[25, 50, 100]} component="div" count={stock.length}
|
||||
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
|
||||
/>
|
||||
</TableContainer>
|
||||
)}
|
||||
{/* Tabla DataGrid */}
|
||||
<Paper sx={{ width: '100%', height: 600 }}>
|
||||
<DataGrid
|
||||
rows={stock}
|
||||
columns={columns}
|
||||
getRowId={(row) => row.idBobina} // Importante: especificar el ID único
|
||||
loading={loading}
|
||||
localeText={esES.components.MuiDataGrid.defaultProps.localeText}
|
||||
density="compact"
|
||||
disableRowSelectionOnClick
|
||||
initialState={{
|
||||
pagination: { paginationModel: { pageSize: 25 } },
|
||||
}}
|
||||
pageSizeOptions={[25, 50, 100]}
|
||||
sx={{ border: 0 }}
|
||||
/>
|
||||
</Paper>
|
||||
|
||||
{/* Menú Contextual de Fila */}
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||
{selectedBobinaForRowMenu && puedeModificarDatos && (
|
||||
<MenuItem onClick={handleOpenFechaRemitoModal}>
|
||||
|
||||
@@ -14,6 +14,8 @@ interface GetAllStockBobinasParams {
|
||||
remitoFilter?: string | null;
|
||||
fechaDesde?: string | null; // "yyyy-MM-dd"
|
||||
fechaHasta?: string | null; // "yyyy-MM-dd"
|
||||
fechaEstadoDesde?: string | null; // "yyyy-MM-dd"
|
||||
fechaEstadoHasta?: string | null; // "yyyy-MM-dd"
|
||||
}
|
||||
|
||||
const getAllStockBobinas = async (filters: GetAllStockBobinasParams): Promise<StockBobinaDto[]> => {
|
||||
@@ -25,6 +27,8 @@ const getAllStockBobinas = async (filters: GetAllStockBobinasParams): Promise<St
|
||||
if (filters.remitoFilter) params.remito = filters.remitoFilter; // El backend espera remito
|
||||
if (filters.fechaDesde) params.fechaDesde = filters.fechaDesde;
|
||||
if (filters.fechaHasta) params.fechaHasta = filters.fechaHasta;
|
||||
if (filters.fechaEstadoDesde) params.fechaEstadoDesde = filters.fechaEstadoDesde;
|
||||
if (filters.fechaEstadoHasta) params.fechaEstadoHasta = filters.fechaEstadoHasta;
|
||||
|
||||
const response = await apiClient.get<StockBobinaDto[]>('/stockbobinas', { params });
|
||||
return response.data;
|
||||
|
||||
Reference in New Issue
Block a user