251 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			251 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | import React, { useState, useEffect, useCallback } from 'react'; | ||
|  | import { | ||
|  |   Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, | ||
|  |   Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination, | ||
|  |   CircularProgress, Alert | ||
|  | } from '@mui/material'; | ||
|  | import AddIcon from '@mui/icons-material/Add'; | ||
|  | import MoreVertIcon from '@mui/icons-material/MoreVert'; | ||
|  | import tipoBobinaService from '../../services/tipoBobinaService'; // Servicio específico
 | ||
|  | import type { TipoBobinaDto } from '../../models/dtos/Impresion/TipoBobinaDto'; | ||
|  | import type { CreateTipoBobinaDto } from '../../models/dtos/Impresion/CreateTipoBobinaDto'; | ||
|  | import type { UpdateTipoBobinaDto } from '../../models/dtos/Impresion/UpdateTipoBobinaDto'; | ||
|  | import TipoBobinaFormModal from '../../components/Modals/TipoBobinaFormModal'; // Modal específico
 | ||
|  | import { usePermissions } from '../../hooks/usePermissions'; | ||
|  | import axios from 'axios'; | ||
|  | 
 | ||
|  | const GestionarTiposBobinaPage: React.FC = () => { | ||
|  |   const [tiposBobina, setTiposBobina] = useState<TipoBobinaDto[]>([]); | ||
|  |   const [loading, setLoading] = useState(true); | ||
|  |   const [error, setError] = useState<string | null>(null); | ||
|  |   const [filtroDenominacion, setFiltroDenominacion] = useState(''); | ||
|  | 
 | ||
|  |   const [modalOpen, setModalOpen] = useState(false); | ||
|  |   const [editingTipoBobina, setEditingTipoBobina] = useState<TipoBobinaDto | null>(null); | ||
|  |   const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null); | ||
|  | 
 | ||
|  |   const [page, setPage] = useState(0); | ||
|  |   const [rowsPerPage, setRowsPerPage] = useState(5); | ||
|  | 
 | ||
|  |   const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | ||
|  |   const [selectedTipoBobinaRow, setSelectedTipoBobinaRow] = useState<TipoBobinaDto | null>(null); | ||
|  | 
 | ||
|  |   const { tienePermiso, isSuperAdmin } = usePermissions(); | ||
|  | 
 | ||
|  |   // Permisos específicos para Tipos de Bobina (IB006 a IB009)
 | ||
|  |   const puedeVer = isSuperAdmin || tienePermiso("IB006"); | ||
|  |   const puedeCrear = isSuperAdmin || tienePermiso("IB007"); | ||
|  |   const puedeModificar = isSuperAdmin || tienePermiso("IB008"); | ||
|  |   const puedeEliminar = isSuperAdmin || tienePermiso("IB009"); | ||
|  | 
 | ||
|  |   const cargarTiposBobina = useCallback(async () => { | ||
|  |     if (!puedeVer) { | ||
|  |       setError("No tiene permiso para ver esta sección."); | ||
|  |       setLoading(false); | ||
|  |       return; | ||
|  |     } | ||
|  |     setLoading(true); | ||
|  |     setError(null); | ||
|  |     setApiErrorMessage(null); | ||
|  |     try { | ||
|  |       const data = await tipoBobinaService.getAllTiposBobina(filtroDenominacion); | ||
|  |       setTiposBobina(data); | ||
|  |     } catch (err) { | ||
|  |       console.error(err); | ||
|  |       setError('Error al cargar los tipos de bobina.'); | ||
|  |     } finally { | ||
|  |       setLoading(false); | ||
|  |     } | ||
|  |   }, [filtroDenominacion, puedeVer]); | ||
|  | 
 | ||
|  |   useEffect(() => { | ||
|  |     cargarTiposBobina(); | ||
|  |   }, [cargarTiposBobina]); | ||
|  | 
 | ||
|  |   const handleOpenModal = (tipoBobina?: TipoBobinaDto) => { | ||
|  |     setEditingTipoBobina(tipoBobina || null); | ||
|  |     setApiErrorMessage(null); | ||
|  |     setModalOpen(true); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const handleCloseModal = () => { | ||
|  |     setModalOpen(false); | ||
|  |     setEditingTipoBobina(null); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const handleSubmitModal = async (data: CreateTipoBobinaDto | (UpdateTipoBobinaDto & { idTipoBobina: number })) => { | ||
|  |     setApiErrorMessage(null); | ||
|  |     try { | ||
|  |       if (editingTipoBobina && 'idTipoBobina' in data) { | ||
|  |         await tipoBobinaService.updateTipoBobina(editingTipoBobina.idTipoBobina, data); | ||
|  |       } else { | ||
|  |         await tipoBobinaService.createTipoBobina(data as CreateTipoBobinaDto); | ||
|  |       } | ||
|  |       cargarTiposBobina(); | ||
|  |     } catch (err: any) { | ||
|  |       console.error("Error en submit modal (padre - TiposBobina):", err); | ||
|  |       const message = axios.isAxiosError(err) && err.response?.data?.message | ||
|  |         ? err.response.data.message | ||
|  |         : 'Ocurrió un error inesperado al guardar el tipo de bobina.'; | ||
|  |       setApiErrorMessage(message); | ||
|  |       throw err; | ||
|  |     } | ||
|  |   }; | ||
|  | 
 | ||
|  |   const handleDelete = async (id: number) => { | ||
|  |     if (window.confirm(`¿Está seguro de que desea eliminar este tipo de bobina (ID: ${id})?`)) { | ||
|  |       setApiErrorMessage(null); | ||
|  |       try { | ||
|  |         await tipoBobinaService.deleteTipoBobina(id); | ||
|  |         cargarTiposBobina(); | ||
|  |       } catch (err: any) { | ||
|  |         console.error("Error al eliminar tipo de bobina:", err); | ||
|  |         const message = axios.isAxiosError(err) && err.response?.data?.message | ||
|  |           ? err.response.data.message | ||
|  |           : 'Ocurrió un error inesperado al eliminar el tipo de bobina.'; | ||
|  |         setApiErrorMessage(message); | ||
|  |       } | ||
|  |     } | ||
|  |     handleMenuClose(); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, tipoBobina: TipoBobinaDto) => { | ||
|  |     setAnchorEl(event.currentTarget); | ||
|  |     setSelectedTipoBobinaRow(tipoBobina); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const handleMenuClose = () => { | ||
|  |     setAnchorEl(null); | ||
|  |     setSelectedTipoBobinaRow(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 = tiposBobina.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); | ||
|  | 
 | ||
|  |   if (!loading && !puedeVer) { | ||
|  |     return ( | ||
|  |       <Box sx={{ p: 2 }}> | ||
|  |         <Typography variant="h4" gutterBottom>Gestionar Tipos de Bobina</Typography> | ||
|  |         <Alert severity="error">{error || "No tiene permiso para acceder a esta sección."}</Alert> | ||
|  |       </Box> | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   return ( | ||
|  |     <Box sx={{ p: 2 }}> | ||
|  |       <Typography variant="h4" gutterBottom> | ||
|  |         Gestionar Tipos de Bobina | ||
|  |       </Typography> | ||
|  | 
 | ||
|  |       <Paper sx={{ p: 2, mb: 2 }}> | ||
|  |         <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}> | ||
|  |           <TextField | ||
|  |             label="Filtrar por Denominación" | ||
|  |             variant="outlined" | ||
|  |             size="small" | ||
|  |             value={filtroDenominacion} | ||
|  |             onChange={(e) => setFiltroDenominacion(e.target.value)} | ||
|  |           /> | ||
|  |           {/* <Button variant="contained" onClick={cargarTiposBobina}>Buscar</Button> */} | ||
|  |         </Box> | ||
|  |         {puedeCrear && ( | ||
|  |           <Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}> | ||
|  |             <Button | ||
|  |               variant="contained" | ||
|  |               startIcon={<AddIcon />} | ||
|  |               onClick={() => handleOpenModal()} | ||
|  |               sx={{ mb: 2 }} | ||
|  |             > | ||
|  |               Agregar Nuevo Tipo | ||
|  |             </Button> | ||
|  |           </Box> | ||
|  |         )} | ||
|  |       </Paper> | ||
|  | 
 | ||
|  |       {loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>} | ||
|  |       {error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>} | ||
|  |       {apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>} | ||
|  | 
 | ||
|  |       {!loading && !error && ( | ||
|  |         <TableContainer component={Paper}> | ||
|  |           <Table> | ||
|  |             <TableHead> | ||
|  |               <TableRow> | ||
|  |                 <TableCell>Denominación</TableCell> | ||
|  |                 {(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>} | ||
|  |               </TableRow> | ||
|  |             </TableHead> | ||
|  |             <TableBody> | ||
|  |               {displayData.length === 0 && !loading ? ( | ||
|  |                 <TableRow><TableCell colSpan={(puedeModificar || puedeEliminar) ? 2 : 1} align="center">No se encontraron tipos de bobina.</TableCell></TableRow> | ||
|  |               ) : ( | ||
|  |                 displayData.map((tipo) => ( | ||
|  |                   <TableRow key={tipo.idTipoBobina}> | ||
|  |                     <TableCell>{tipo.denominacion}</TableCell> | ||
|  |                     {(puedeModificar || puedeEliminar) && ( | ||
|  |                       <TableCell align="right"> | ||
|  |                         <IconButton | ||
|  |                           onClick={(e) => handleMenuOpen(e, tipo)} | ||
|  |                           disabled={!puedeModificar && !puedeEliminar} | ||
|  |                         > | ||
|  |                           <MoreVertIcon /> | ||
|  |                         </IconButton> | ||
|  |                       </TableCell> | ||
|  |                     )} | ||
|  |                   </TableRow> | ||
|  |                 )) | ||
|  |               )} | ||
|  |             </TableBody> | ||
|  |           </Table> | ||
|  |           <TablePagination | ||
|  |             rowsPerPageOptions={[5, 10, 25]} | ||
|  |             component="div" | ||
|  |             count={tiposBobina.length} | ||
|  |             rowsPerPage={rowsPerPage} | ||
|  |             page={page} | ||
|  |             onPageChange={handleChangePage} | ||
|  |             onRowsPerPageChange={handleChangeRowsPerPage} | ||
|  |             labelRowsPerPage="Filas por página:" | ||
|  |           /> | ||
|  |         </TableContainer> | ||
|  |       )} | ||
|  | 
 | ||
|  |       <Menu | ||
|  |         anchorEl={anchorEl} | ||
|  |         open={Boolean(anchorEl)} | ||
|  |         onClose={handleMenuClose} | ||
|  |       > | ||
|  |         {puedeModificar && ( | ||
|  |           <MenuItem onClick={() => { handleOpenModal(selectedTipoBobinaRow!); handleMenuClose(); }}> | ||
|  |             Modificar | ||
|  |           </MenuItem> | ||
|  |         )} | ||
|  |         {puedeEliminar && ( | ||
|  |           <MenuItem onClick={() => handleDelete(selectedTipoBobinaRow!.idTipoBobina)}> | ||
|  |             Eliminar | ||
|  |           </MenuItem> | ||
|  |         )} | ||
|  |         {(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>} | ||
|  |       </Menu> | ||
|  | 
 | ||
|  |       <TipoBobinaFormModal | ||
|  |         open={modalOpen} | ||
|  |         onClose={handleCloseModal} | ||
|  |         onSubmit={handleSubmitModal} | ||
|  |         initialData={editingTipoBobina} | ||
|  |         errorMessage={apiErrorMessage} | ||
|  |         clearErrorMessage={() => setApiErrorMessage(null)} | ||
|  |       /> | ||
|  |     </Box> | ||
|  |   ); | ||
|  | }; | ||
|  | 
 | ||
|  | export default GestionarTiposBobinaPage; |