| 
									
										
										
										
											2025-07-03 11:44:10 -03:00
										 |  |  | import React, { useState, useRef } from 'react'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   Box, CircularProgress, Alert, Paper, Typography, Dialog, | 
					
						
							|  |  |  |   DialogTitle, DialogContent, IconButton | 
					
						
							|  |  |  | } from '@mui/material'; | 
					
						
							|  |  |  | import { PiChartLineUpBold } from "react-icons/pi"; | 
					
						
							|  |  |  | import CloseIcon from '@mui/icons-material/Close'; | 
					
						
							| 
									
										
										
										
											2025-07-01 16:05:26 -03:00
										 |  |  | import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; | 
					
						
							|  |  |  | import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; | 
					
						
							|  |  |  | import RemoveIcon from '@mui/icons-material/Remove'; | 
					
						
							|  |  |  | import { GiSunflower, GiWheat, GiCorn, GiGrain } from "react-icons/gi"; | 
					
						
							|  |  |  | import { TbGrain } from "react-icons/tb"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import type { CotizacionGrano } from '../models/mercadoModels'; | 
					
						
							|  |  |  | import { useApiData } from '../hooks/useApiData'; | 
					
						
							| 
									
										
										
										
											2025-07-03 11:44:10 -03:00
										 |  |  | import { formatInteger, formatDateOnly } from '../utils/formatters'; | 
					
						
							|  |  |  | import { GrainsHistoricalChartWidget } from './GrainsHistoricalChartWidget'; | 
					
						
							| 
									
										
										
										
											2025-07-01 16:05:26 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | const getGrainIcon = (nombre: string) => { | 
					
						
							| 
									
										
										
										
											2025-07-03 11:44:10 -03:00
										 |  |  |   switch (nombre.toLowerCase()) { | 
					
						
							|  |  |  |     case 'girasol': return <GiSunflower size={28} color="#fbc02d" />; | 
					
						
							|  |  |  |     case 'trigo': return <GiWheat size={28} color="#fbc02d" />; | 
					
						
							|  |  |  |     case 'sorgo': return <TbGrain size={28} color="#fbc02d" />; | 
					
						
							|  |  |  |     case 'maiz': return <GiCorn size={28} color="#fbc02d" />; | 
					
						
							|  |  |  |     default: return <GiGrain size={28} color="#fbc02d" />; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-01 16:05:26 -03:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Subcomponente para una única tarjeta de grano
 | 
					
						
							| 
									
										
										
										
											2025-07-03 11:44:10 -03:00
										 |  |  | const GranoCard = ({ grano, onChartClick }: { grano: CotizacionGrano, onChartClick: (event: React.MouseEvent<HTMLButtonElement>) => void }) => { | 
					
						
							|  |  |  |   const isPositive = grano.variacionPrecio > 0; | 
					
						
							|  |  |  |   const isNegative = grano.variacionPrecio < 0; | 
					
						
							|  |  |  |   const color = isPositive ? 'success.main' : isNegative ? 'error.main' : 'text.secondary'; | 
					
						
							|  |  |  |   const Icon = isPositive ? ArrowUpwardIcon : isNegative ? ArrowDownwardIcon : RemoveIcon; | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <Paper | 
					
						
							|  |  |  |       elevation={2} | 
					
						
							|  |  |  |       sx={{ | 
					
						
							|  |  |  |         position: 'relative', | 
					
						
							|  |  |  |         p: 2, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', | 
					
						
							|  |  |  |         flex: '1 1 180px', minWidth: '180px', maxWidth: '220px', height: '160px', | 
					
						
							|  |  |  |         borderTop: `4px solid ${isPositive ? '#2e7d32' : isNegative ? '#d32f2f' : '#bdbdbd'}` | 
					
						
							|  |  |  |       }} | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       <IconButton | 
					
						
							|  |  |  |         aria-label={`ver historial de ${grano.nombre}`} | 
					
						
							|  |  |  |         onClick={onChartClick} | 
					
						
							|  |  |  |         sx={{ | 
					
						
							|  |  |  |           position: 'absolute', | 
					
						
							|  |  |  |           top: 8, | 
					
						
							|  |  |  |           right: 8, | 
					
						
							|  |  |  |           backgroundColor: 'rgba(255, 255, 255, 0.7)', // Fondo semitransparente
 | 
					
						
							|  |  |  |           backdropFilter: 'blur(2px)', // Efecto "frosty glass" para el fondo
 | 
					
						
							|  |  |  |           border: '1px solid rgba(0, 0, 0, 0.1)', | 
					
						
							|  |  |  |           boxShadow: '0 2px 5px rgba(0,0,0,0.1)', | 
					
						
							|  |  |  |           transition: 'all 0.2s ease-in-out', // Transición suave para todos los cambios
 | 
					
						
							|  |  |  |           '&:hover': { | 
					
						
							|  |  |  |             transform: 'translateY(-2px)', // Se eleva un poco
 | 
					
						
							|  |  |  |             boxShadow: '0 4px 10px rgba(0,0,0,0.2)', // La sombra se hace más grande
 | 
					
						
							|  |  |  |             backgroundColor: 'rgba(255, 255, 255, 0.9)', | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }} | 
					
						
							|  |  |  |       > | 
					
						
							|  |  |  |         <PiChartLineUpBold size="20" /> | 
					
						
							|  |  |  |       </IconButton> | 
					
						
							|  |  |  |       <Box sx={{ display: 'flex', alignItems: 'center', mb: 1, pr: 5 }}> | 
					
						
							|  |  |  |         {getGrainIcon(grano.nombre)} | 
					
						
							|  |  |  |         <Typography variant="h6" component="h3" sx={{ fontWeight: 'bold', ml: 1 }}> | 
					
						
							|  |  |  |             {grano.nombre} | 
					
						
							|  |  |  |         </Typography> | 
					
						
							|  |  |  |       </Box> | 
					
						
							| 
									
										
										
										
											2025-07-01 16:05:26 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-03 11:44:10 -03:00
										 |  |  |       <Box sx={{ textAlign: 'center', my: 1 }}> | 
					
						
							|  |  |  |         <Typography variant="h5" component="p" sx={{ fontWeight: 'bold' }}> | 
					
						
							|  |  |  |           ${formatInteger(grano.precio)} | 
					
						
							|  |  |  |         </Typography> | 
					
						
							|  |  |  |         <Typography variant="caption" color="text.secondary"> | 
					
						
							|  |  |  |           por Tonelada | 
					
						
							|  |  |  |         </Typography> | 
					
						
							|  |  |  |       </Box> | 
					
						
							| 
									
										
										
										
											2025-07-01 16:05:26 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-03 11:44:10 -03:00
										 |  |  |       <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color }}> | 
					
						
							|  |  |  |         <Icon sx={{ fontSize: '1.1rem', mr: 0.5 }} /> | 
					
						
							|  |  |  |         <Typography variant="body2" sx={{ fontWeight: 'bold' }}> | 
					
						
							|  |  |  |           {formatInteger(grano.variacionPrecio)} | 
					
						
							|  |  |  |         </Typography> | 
					
						
							|  |  |  |       </Box> | 
					
						
							|  |  |  |       <Typography variant="caption" align="center" sx={{ mt: 1, color: 'text.secondary' }}> | 
					
						
							|  |  |  |         Operación: {formatDateOnly(grano.fechaOperacion)} | 
					
						
							|  |  |  |       </Typography> | 
					
						
							|  |  |  |     </Paper> | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2025-07-01 16:05:26 -03:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const GranosCardWidget = () => { | 
					
						
							|  |  |  |   const { data, loading, error } = useApiData<CotizacionGrano[]>('/mercados/granos'); | 
					
						
							| 
									
										
										
										
											2025-07-03 11:44:10 -03:00
										 |  |  |   const [selectedGrano, setSelectedGrano] = useState<string | null>(null); | 
					
						
							|  |  |  |   const triggerButtonRef = useRef<HTMLButtonElement | null>(null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleChartClick = (nombreGrano: string, event: React.MouseEvent<HTMLButtonElement>) => { | 
					
						
							|  |  |  |     triggerButtonRef.current = event.currentTarget; | 
					
						
							|  |  |  |     setSelectedGrano(nombreGrano); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleCloseDialog = () => { | 
					
						
							|  |  |  |     setSelectedGrano(null); | 
					
						
							|  |  |  |     setTimeout(() => { | 
					
						
							|  |  |  |         triggerButtonRef.current?.focus(); | 
					
						
							|  |  |  |     }, 0); | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2025-07-01 16:05:26 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |   if (loading) { | 
					
						
							|  |  |  |     return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}><CircularProgress /></Box>; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (error) { | 
					
						
							|  |  |  |     return <Alert severity="error">{error}</Alert>; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!data || data.length === 0) { | 
					
						
							|  |  |  |     return <Alert severity="info">No hay datos de granos disponibles.</Alert>; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ( | 
					
						
							| 
									
										
										
										
											2025-07-03 11:44:10 -03:00
										 |  |  |     <> | 
					
						
							|  |  |  |     <Box | 
					
						
							|  |  |  |       sx={{ | 
					
						
							|  |  |  |         display: 'flex', | 
					
						
							|  |  |  |         flexWrap: 'wrap', | 
					
						
							|  |  |  |         // Usamos el objeto para definir gaps responsivos
 | 
					
						
							|  |  |  |         gap: { | 
					
						
							|  |  |  |           xs: 4, // 16px de gap en pantallas extra pequeñas (afecta el espaciado vertical)
 | 
					
						
							|  |  |  |           sm: 4, // 16px en pantallas pequeñas
 | 
					
						
							|  |  |  |           md: 3, // 24px en pantallas medianas (afecta el espaciado horizontal)
 | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         justifyContent: 'center' | 
					
						
							|  |  |  |       }} | 
					
						
							|  |  |  |     > | 
					
						
							| 
									
										
										
										
											2025-07-01 16:05:26 -03:00
										 |  |  |       {data.map((grano) => ( | 
					
						
							| 
									
										
										
										
											2025-07-03 11:44:10 -03:00
										 |  |  |           <GranoCard key={grano.nombre} grano={grano} onChartClick={(event) => handleChartClick(grano.nombre, event)} /> | 
					
						
							|  |  |  |         ))} | 
					
						
							|  |  |  |       </Box> | 
					
						
							|  |  |  |       <Dialog open={Boolean(selectedGrano)} onClose={handleCloseDialog} maxWidth="md" fullWidth sx={{ '& .MuiDialog-paper': { overflow: 'visible' } }}> | 
					
						
							|  |  |  |         <IconButton | 
					
						
							|  |  |  |                     aria-label="close" | 
					
						
							|  |  |  |                     onClick={handleCloseDialog} | 
					
						
							|  |  |  |                     sx={{ | 
					
						
							|  |  |  |                         position: 'absolute', | 
					
						
							|  |  |  |                         top: -15, // Mueve el botón hacia arriba, fuera del Dialog
 | 
					
						
							|  |  |  |                         right: -15, // Mueve el botón hacia la derecha, fuera del Dialog
 | 
					
						
							|  |  |  |                         color: (theme) => theme.palette.grey[500], | 
					
						
							|  |  |  |                         backgroundColor: 'white', | 
					
						
							|  |  |  |                         boxShadow: 3, // Añade una sombra para que destaque
 | 
					
						
							|  |  |  |                         '&:hover': { | 
					
						
							|  |  |  |                             backgroundColor: 'grey.100', // Un leve cambio de color al pasar el mouse
 | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                     }} | 
					
						
							|  |  |  |                 > | 
					
						
							|  |  |  |                     <CloseIcon /> | 
					
						
							|  |  |  |                 </IconButton> | 
					
						
							|  |  |  |         <DialogTitle sx={{ m: 0, p: 2 }}>Mensual de {selectedGrano}</DialogTitle> | 
					
						
							|  |  |  |         <DialogContent dividers> | 
					
						
							|  |  |  |           {selectedGrano && <GrainsHistoricalChartWidget nombre={selectedGrano} />} | 
					
						
							|  |  |  |         </DialogContent> | 
					
						
							|  |  |  |       </Dialog> | 
					
						
							|  |  |  |     </> | 
					
						
							| 
									
										
										
										
											2025-07-01 16:05:26 -03:00
										 |  |  |   ); | 
					
						
							|  |  |  | }; |