import { useEffect, useState } from 'react'; import { Card, ListGroup, Badge, Button, Form, Tooltip, OverlayTrigger } from 'react-bootstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faIdCard, faUser, faSunPlantWilt, faPhone, faClipboard, faAt, faEllipsisVertical, faEdit, faTrash, faMoneyBill, faCheck, faXmark, faCalendar, faKey } from '@fortawesome/free-solid-svg-icons'; import { motion as _motion } from 'framer-motion'; import PropTypes from 'prop-types'; import AnimatedDropdown from '../../components/AnimatedDropdown'; import '../../css/SocioCard.css'; import TipoSocioDropdown from './TipoSocioDropdown'; import { getNowAsLocalDatetime } from '../../util/date'; import { generateSecurePassword } from '../../util/passwordGenerator'; import { DateParser } from '../../util/parsers/dateParser'; import { renderErrorAlert } from '../../util/alertHelpers'; import { useDataContext } from "../../hooks/useDataContext"; import SpanishDateTimePicker from '../SpanishDateTimePicker'; const renderDateField = (label, icon, dateValue, editMode, fieldKey, handleChange) => { if (!editMode && !dateValue) return null; return ( {label} {editMode ? ( date ? handleChange(fieldKey, date.toISOString().slice(0, 16)) : handleChange(fieldKey, null) } /> ) : ( {DateParser.isoToStringWithTime(dateValue)} )} ); }; const getFechas = (formData, editMode, handleChange) => { const { createdAt, assignedAt, deactivatedAt } = formData; // Si no hay fechas y no está en modo edición, no muestres nada if (!editMode && !createdAt && !assignedAt && !deactivatedAt) return null; return ( {renderDateField("ALTA", faCalendar, createdAt, editMode, "createdAt", handleChange)} {renderDateField("ENTREGA", faCalendar, assignedAt, editMode, "assignedAt", handleChange)} {renderDateField("BAJA", faCalendar, deactivatedAt, editMode, "deactivatedAt", handleChange)} ); }; const getBadgeColor = (estado) => estado === 1 ? 'success' : 'danger'; const getHeaderColor = (estado) => estado === 1 ? 'bg-light-green' : 'bg-light-red'; const getEstado = (estado) => estado === 1 ? ( <> ACTIVO ) : ( <> INACTIVO ); const parseNull = (attr) => attr === null || attr === '' ? 'NO' : attr; const getPFP = (tipo) => { const base = '/images/icons/'; const map = { 1: 'farmer.svg', 2: 'green_house.svg', 0: 'list.svg', 3: 'join.svg', 4: 'subvencion4.svg', 5: 'programmer.svg' }; return base + (map[tipo] || 'farmer.svg'); }; const MotionCard = _motion.create(Card); const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCancel, onViewIncomes, error, onClearError, positionIfWaitlist }) => { const createMode = isNew; const [editMode, setEditMode] = useState(isNew); const [showPassword, setShowPassword] = useState(false); const [latestNumber, setLatestNumber] = useState(null); const { getData } = useDataContext(); const [formData, setFormData] = useState({ displayName: identity?.user.displayName, userName: identity?.account.username, email: identity?.account.email || '', dni: identity?.metadata.dni, phone: identity?.metadata.phone, memberNumber: identity?.metadata.memberNumber || latestNumber, plotNumber: identity?.metadata.plotNumber, notes: identity?.metadata.notes || '', status: identity?.account.status, type: identity?.metadata.type, createdAt: identity?.metadata.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), assignedAt: identity?.metadata.assignedAt?.slice(0, 16) || undefined, deactivatedAt: identity?.metadata.deactivatedAt?.slice(0, 16) || undefined, globalRole: 0, password: createMode && !editMode ? generateSecurePassword() : null, }); useEffect(() => { if (!editMode) { setFormData({ displayName: identity.user.displayName, userName: identity.account.username, email: identity.account.email || '', dni: identity.metadata.dni, phone: identity.metadata.phone, memberNumber: identity.metadata.memberNumber, plotNumber: identity.metadata.plotNumber, notes: identity.metadata.notes || '', status: identity.account.status, type: identity.metadat.type, createdAt: identity.metadata.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), assignedAt: identity.metadata.assignedAt?.slice(0, 16) || undefined, deactivatedAt: identity.metadata.deactivatedAt?.slice(0, 16) || undefined, globalRole: 0, password: createMode ? generateSecurePassword() : '' }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [identity, editMode]); useEffect(() => { const fetchLastNumber = async () => { try { if (!(createMode || editMode)) return; const latestNumber = await getData("http://localhost:8081/v2/huertos/users/latest-number"); const nuevoNumero = latestNumber + 1; setLatestNumber(nuevoNumero); setFormData(prev => ({ ...prev, memberNumber: prev.memberNumber || nuevoNumero })); } catch (err) { console.error("Error al obtener el número de socio:", err); } }; fetchLastNumber(); }, [createMode, editMode, getData]); const handleEdit = () => { if (onClearError) onClearError(); setEditMode(true); }; const handleDelete = () => typeof onDelete === "function" && onDelete(identity.user.userId); const handleCancel = () => { if (onClearError) onClearError(); if (isNew && typeof onCancel === 'function') return onCancel(); setEditMode(false); }; const handleSave = () => { if (onClearError) onClearError(); const newSocio = { ...identity, ...formData }; if (createMode && typeof onCreate === 'function') return onCreate(newSocio); if (typeof onUpdate === 'function') return onUpdate(newSocio, identity.user.userId); }; const handleChange = (field, value) => { if (["memberNumber"].includes(field)) { value = value === "" ? latestNumber : parseInt(value); } if (field === "displayName") { value = value.toUpperCase(); } if (field === "dni") { value = value.toUpperCase(); } setFormData(prev => ({ ...prev, [field]: value })); }; const handleViewIncomes = () => { onViewIncomes(identity.user.userId); } return (
{editMode ? ( handleChange('type', val)} /> ) : ( positionIfWaitlist && identity.metadata.type === 0 ? ( {positionIfWaitlist} en la lista de espera }> PFP ) : ( PFP ) )}
{editMode ? ( handleChange('displayName', e.target.value)} style={{ maxWidth: '220px' }} /> ) : formData.displayName} {editMode ? ( handleChange('status', parseInt(e.target.value))} style={{ maxWidth: '8rem' }}> ) : ( {getEstado(formData.status)} )}
{!createMode && !editMode && ( }> {({ closeDropdown }) => ( <>
{ handleEdit(); closeDropdown(); }}> Editar
{ handleViewIncomes(); closeDropdown(); }}> Ver ingresos

{ handleDelete(); closeDropdown(); }}> Eliminar
)}
)}
{(editMode || createMode) && renderErrorAlert(error)} {[{ label: 'DNI', clazz: '', icon: faIdCard, value: formData.dni, field: 'dni', type: 'text', maxWidth: '180px' }, { label: 'SOCIO Nº', clazz: '', icon: faUser, value: formData.memberNumber || latestNumber, field: 'memberNumber', type: 'number', maxWidth: '100px' }, { label: 'HUERTO Nº', clazz: '', icon: faSunPlantWilt, value: formData.plotNumber, field: 'plotNumber', type: 'number', maxWidth: '100px' }, { label: 'TLF.', clazz: '', icon: faPhone, value: formData.phone, field: 'phone', type: 'number', maxWidth: '200px' }, { label: 'EMAIL', clazz: 'text-truncate', icon: faAt, value: formData.email, field: 'email', type: 'text', maxWidth: '250px' }].map(({ label, clazz, icon, value, field, type, maxWidth }) => ( {label} {editMode ? ( handleChange(field, e.target.value)} style={{ maxWidth }} /> ) : ( {parseNull(value)} )} ))} {editMode && ( CONTRASEÑA
handleChange('password', e.target.value)} style={{ maxWidth: '200px' }} />
)}
{getFechas(formData, editMode, handleChange)} {editMode ? ( <>NOTAS (máx. 256) ) : ( <>NOTAS )} {editMode ? ( handleChange('notes', e.target.value)} /> ) : ( {parseNull(formData.notes)} )} {editMode && (
)}
); }; SocioCard.propTypes = { socio: PropTypes.object.isRequired, isNew: PropTypes.bool, onCancel: PropTypes.func, onCreate: PropTypes.func, onUpdate: PropTypes.func, onDelete: PropTypes.func, onViewIncomes: PropTypes.func, error: PropTypes.string, onClearError: PropTypes.func, positionIfWaitlist: PropTypes.number }; export default SocioCard;