228 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			228 lines
		
	
	
		
			8.8 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 otroDestinoService from '../../services/Distribucion/otroDestinoService'; | ||
|  | import type { OtroDestinoDto } from '../../models/dtos/Distribucion/OtroDestinoDto'; | ||
|  | import type { CreateOtroDestinoDto } from '../../models/dtos/Distribucion/CreateOtroDestinoDto'; | ||
|  | import type { UpdateOtroDestinoDto } from '../../models/dtos/Distribucion/UpdateOtroDestinoDto'; | ||
|  | import OtroDestinoFormModal from '../../components/Modals/Distribucion/OtroDestinoFormModal'; | ||
|  | import { usePermissions } from '../../hooks/usePermissions'; | ||
|  | import axios from 'axios'; | ||
|  | 
 | ||
|  | const GestionarOtrosDestinosPage: React.FC = () => { | ||
|  |   const [otrosDestinos, setOtrosDestinos] = useState<OtroDestinoDto[]>([]); | ||
|  |   const [loading, setLoading] = useState(true); | ||
|  |   const [error, setError] = useState<string | null>(null); | ||
|  |   const [filtroNombre, setFiltroNombre] = useState(''); | ||
|  | 
 | ||
|  |   const [modalOpen, setModalOpen] = useState(false); | ||
|  |   const [editingDestino, setEditingDestino] = useState<OtroDestinoDto | 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 [selectedDestinoRow, setSelectedDestinoRow] = useState<OtroDestinoDto | null>(null); | ||
|  | 
 | ||
|  |   const { tienePermiso, isSuperAdmin } = usePermissions(); | ||
|  | 
 | ||
|  |   // Permisos para Otros Destinos (OD001 a OD004) - Revisa tus códigos de permiso
 | ||
|  |   const puedeVer = isSuperAdmin || tienePermiso("OD001"); // Asumiendo OD001 es ver entidad
 | ||
|  |   const puedeCrear = isSuperAdmin || tienePermiso("OD002"); | ||
|  |   const puedeModificar = isSuperAdmin || tienePermiso("OD003"); | ||
|  |   const puedeEliminar = isSuperAdmin || tienePermiso("OD004"); | ||
|  | 
 | ||
|  |   const cargarOtrosDestinos = 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 otroDestinoService.getAllOtrosDestinos(filtroNombre); | ||
|  |       setOtrosDestinos(data); | ||
|  |     } catch (err) { | ||
|  |       console.error(err); | ||
|  |       setError('Error al cargar los otros destinos.'); | ||
|  |     } finally { | ||
|  |       setLoading(false); | ||
|  |     } | ||
|  |   }, [filtroNombre, puedeVer]); | ||
|  | 
 | ||
|  |   useEffect(() => { | ||
|  |     cargarOtrosDestinos(); | ||
|  |   }, [cargarOtrosDestinos]); | ||
|  | 
 | ||
|  |   const handleOpenModal = (destino?: OtroDestinoDto) => { | ||
|  |     setEditingDestino(destino || null); | ||
|  |     setApiErrorMessage(null); | ||
|  |     setModalOpen(true); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const handleCloseModal = () => { | ||
|  |     setModalOpen(false); | ||
|  |     setEditingDestino(null); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const handleSubmitModal = async (data: CreateOtroDestinoDto | (UpdateOtroDestinoDto & { idDestino: number })) => { | ||
|  |     setApiErrorMessage(null); | ||
|  |     try { | ||
|  |       if (editingDestino && 'idDestino' in data) { | ||
|  |         await otroDestinoService.updateOtroDestino(editingDestino.idDestino, data); | ||
|  |       } else { | ||
|  |         await otroDestinoService.createOtroDestino(data as CreateOtroDestinoDto); | ||
|  |       } | ||
|  |       cargarOtrosDestinos(); | ||
|  |     } catch (err: any) { | ||
|  |       const message = axios.isAxiosError(err) && err.response?.data?.message | ||
|  |                         ? err.response.data.message | ||
|  |                         : 'Ocurrió un error inesperado al guardar el destino.'; | ||
|  |       setApiErrorMessage(message); | ||
|  |       throw err; | ||
|  |     } | ||
|  |   }; | ||
|  | 
 | ||
|  |   const handleDelete = async (id: number) => { | ||
|  |     if (window.confirm(`¿Está seguro de que desea eliminar este destino (ID: ${id})?`)) { | ||
|  |        setApiErrorMessage(null); | ||
|  |        try { | ||
|  |         await otroDestinoService.deleteOtroDestino(id); | ||
|  |         cargarOtrosDestinos(); | ||
|  |       } catch (err: any) { | ||
|  |          const message = axios.isAxiosError(err) && err.response?.data?.message | ||
|  |                          ? err.response.data.message | ||
|  |                          : 'Ocurrió un error inesperado al eliminar el destino.'; | ||
|  |          setApiErrorMessage(message); | ||
|  |       } | ||
|  |     } | ||
|  |     handleMenuClose(); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, destino: OtroDestinoDto) => { | ||
|  |     setAnchorEl(event.currentTarget); | ||
|  |     setSelectedDestinoRow(destino); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const handleMenuClose = () => { | ||
|  |     setAnchorEl(null); | ||
|  |     setSelectedDestinoRow(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 = otrosDestinos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); | ||
|  | 
 | ||
|  |   if (!loading && !puedeVer) { | ||
|  |       return ( | ||
|  |            <Box sx={{ p: 2 }}> | ||
|  |               <Typography variant="h4" gutterBottom>Gestionar Otros Destinos</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 Otros Destinos</Typography> | ||
|  |       <Paper sx={{ p: 2, mb: 2 }}> | ||
|  |          <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}> | ||
|  |             <TextField | ||
|  |                 label="Filtrar por Nombre" | ||
|  |                 variant="outlined" | ||
|  |                 size="small" | ||
|  |                 value={filtroNombre} | ||
|  |                 onChange={(e) => setFiltroNombre(e.target.value)} | ||
|  |             /> | ||
|  |          </Box> | ||
|  |          {puedeCrear && ( | ||
|  |           <Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}> | ||
|  |              <Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}> | ||
|  |                 Agregar Nuevo Destino | ||
|  |              </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>Nombre</TableCell> | ||
|  |                  <TableCell>Observación</TableCell> | ||
|  |                  {(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>} | ||
|  |                </TableRow> | ||
|  |              </TableHead> | ||
|  |              <TableBody> | ||
|  |                {displayData.length === 0 && !loading ? ( | ||
|  |                   <TableRow><TableCell colSpan={(puedeModificar || puedeEliminar) ? 3 : 2} align="center">No se encontraron otros destinos.</TableCell></TableRow> | ||
|  |                ) : ( | ||
|  |                  displayData.map((destino) => ( | ||
|  |                      <TableRow key={destino.idDestino}> | ||
|  |                      <TableCell>{destino.nombre}</TableCell> | ||
|  |                      <TableCell>{destino.obs || '-'}</TableCell> | ||
|  |                      {(puedeModificar || puedeEliminar) && ( | ||
|  |                         <TableCell align="right"> | ||
|  |                             <IconButton onClick={(e) => handleMenuOpen(e, destino)} disabled={!puedeModificar && !puedeEliminar}> | ||
|  |                                 <MoreVertIcon /> | ||
|  |                             </IconButton> | ||
|  |                         </TableCell> | ||
|  |                      )} | ||
|  |                      </TableRow> | ||
|  |                  )) | ||
|  |                )} | ||
|  |              </TableBody> | ||
|  |            </Table> | ||
|  |            <TablePagination | ||
|  |              rowsPerPageOptions={[5, 10, 25]} | ||
|  |              component="div" | ||
|  |              count={otrosDestinos.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(selectedDestinoRow!); handleMenuClose(); }}>Modificar</MenuItem> | ||
|  |         )} | ||
|  |         {puedeEliminar && ( | ||
|  |             <MenuItem onClick={() => handleDelete(selectedDestinoRow!.idDestino)}>Eliminar</MenuItem> | ||
|  |         )} | ||
|  |         {(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>} | ||
|  |       </Menu> | ||
|  | 
 | ||
|  |       <OtroDestinoFormModal | ||
|  |         open={modalOpen} | ||
|  |         onClose={handleCloseModal} | ||
|  |         onSubmit={handleSubmitModal} | ||
|  |         initialData={editingDestino} | ||
|  |         errorMessage={apiErrorMessage} | ||
|  |         clearErrorMessage={() => setApiErrorMessage(null)} | ||
|  |       /> | ||
|  |     </Box> | ||
|  |   ); | ||
|  | }; | ||
|  | 
 | ||
|  | export default GestionarOtrosDestinosPage; |