feat: Add full API integration to Quality Template, Equipment, and Team Member wizards

- QualityTemplateWizard: Fixed onComplete bug and added API save via qualityTemplateService
- EquipmentWizard: Added API save via equipmentService with loading states and error handling
- TeamMemberWizard: Added API save via authService for user registration with permissions

All three wizards now:
- Use useTenant hook to get tenant ID
- Call actual backend APIs instead of console.log
- Include loading states during API calls
- Show error messages if API calls fail
- Properly handle success/failure scenarios
This commit is contained in:
Claude
2025-11-09 09:01:18 +00:00
parent d59d6135b7
commit 1a022a0692
3 changed files with 328 additions and 25 deletions

View File

@@ -1,6 +1,8 @@
import React, { useState } from 'react';
import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal';
import { Wrench, CheckCircle2 } from 'lucide-react';
import { Wrench, CheckCircle2, Loader2 } from 'lucide-react';
import { useTenant } from '../../../../stores/tenant.store';
import { equipmentService } from '../../../../api/services/equipment';
interface WizardDataProps extends WizardStepProps {
data: Record<string, any>;
@@ -8,6 +10,7 @@ interface WizardDataProps extends WizardStepProps {
}
const EquipmentDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange, onComplete }) => {
const { currentTenant } = useTenant();
const [equipmentData, setEquipmentData] = useState({
type: data.type || 'oven',
brand: data.brand || '',
@@ -16,6 +19,44 @@ const EquipmentDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange, o
purchaseDate: data.purchaseDate || '',
status: data.status || 'active',
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSave = async () => {
if (!currentTenant?.id) {
setError('No se pudo obtener información del tenant');
return;
}
setLoading(true);
setError(null);
try {
const equipmentCreateData: any = {
name: `${equipmentData.type} - ${equipmentData.brand || 'Sin marca'}`,
type: equipmentData.type,
model: equipmentData.brand,
serialNumber: equipmentData.model,
location: equipmentData.location,
status: equipmentData.status,
installDate: equipmentData.purchaseDate || new Date().toISOString().split('T')[0],
lastMaintenance: new Date().toISOString().split('T')[0],
nextMaintenance: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
maintenanceInterval: 30,
is_active: true
};
await equipmentService.createEquipment(currentTenant.id, equipmentCreateData);
onDataChange({ ...data, ...equipmentData });
onComplete();
} catch (err: any) {
console.error('Error creating equipment:', err);
setError(err.response?.data?.detail || 'Error al crear el equipo');
} finally {
setLoading(false);
}
};
return (
<div className="space-y-6">
@@ -23,10 +64,21 @@ const EquipmentDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange, o
<Wrench className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">Equipo de Panadería</h3>
</div>
{error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
{error}
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Tipo de Equipo *</label>
<select value={equipmentData.type} onChange={(e) => setEquipmentData({ ...equipmentData, type: e.target.value })} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]">
<select
value={equipmentData.type}
onChange={(e) => setEquipmentData({ ...equipmentData, type: e.target.value })}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
<option value="oven">Horno</option>
<option value="mixer">Amasadora</option>
<option value="proofer">Fermentadora</option>
@@ -36,19 +88,53 @@ const EquipmentDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange, o
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Marca/Modelo</label>
<input type="text" value={equipmentData.brand} onChange={(e) => setEquipmentData({ ...equipmentData, brand: e.target.value })} placeholder="Ej: Rational SCC 101" className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
<input
type="text"
value={equipmentData.brand}
onChange={(e) => setEquipmentData({ ...equipmentData, brand: e.target.value })}
placeholder="Ej: Rational SCC 101"
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Ubicación</label>
<input type="text" value={equipmentData.location} onChange={(e) => setEquipmentData({ ...equipmentData, location: e.target.value })} placeholder="Ej: Cocina principal" className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
<input
type="text"
value={equipmentData.location}
onChange={(e) => setEquipmentData({ ...equipmentData, location: e.target.value })}
placeholder="Ej: Cocina principal"
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Fecha de Compra</label>
<input type="date" value={equipmentData.purchaseDate} onChange={(e) => setEquipmentData({ ...equipmentData, purchaseDate: e.target.value })} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
<input
type="date"
value={equipmentData.purchaseDate}
onChange={(e) => setEquipmentData({ ...equipmentData, purchaseDate: e.target.value })}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
/>
</div>
</div>
<div className="flex justify-end pt-4 border-t border-[var(--border-primary)]">
<button onClick={() => { onDataChange({ ...data, ...equipmentData }); console.log('Saving equipment:', { ...data, ...equipmentData }); onComplete(); }} className="px-8 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 font-semibold inline-flex items-center gap-2"><CheckCircle2 className="w-5 h-5" />Agregar Equipo</button>
<button
onClick={handleSave}
disabled={loading}
className="px-8 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 font-semibold inline-flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Guardando...
</>
) : (
<>
<CheckCircle2 className="w-5 h-5" />
Agregar Equipo
</>
)}
</button>
</div>
</div>
);

View File

@@ -1,18 +1,76 @@
import React, { useState } from 'react';
import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal';
import { ClipboardCheck, ListChecks, CheckCircle2 } from 'lucide-react';
import { ClipboardCheck, CheckCircle2, Loader2 } from 'lucide-react';
import { useTenant } from '../../../../stores/tenant.store';
import { qualityTemplateService } from '../../../../api/services/qualityTemplates';
import { QualityCheckTemplateCreate } from '../../../../api/types/qualityTemplates';
interface WizardDataProps extends WizardStepProps {
data: Record<string, any>;
onDataChange: (data: Record<string, any>) => void;
}
const TemplateInfoStep: React.FC<WizardDataProps> = ({ data, onDataChange, onNext }) => {
const TemplateInfoStep: React.FC<WizardDataProps> = ({ data, onDataChange, onComplete }) => {
const { currentTenant } = useTenant();
const [templateData, setTemplateData] = useState({
name: data.name || '',
scope: data.scope || 'product',
frequency: data.frequency || 'batch',
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSave = async () => {
if (!currentTenant?.id) {
setError('No se pudo obtener información del tenant');
return;
}
setLoading(true);
setError(null);
try {
const scopeMapping: Record<string, string> = {
product: 'product_quality',
process: 'process_hygiene',
equipment: 'equipment',
safety: 'safety'
};
const templateCreateData: QualityCheckTemplateCreate = {
name: templateData.name,
description: `Plantilla de ${templateData.scope} con frecuencia ${templateData.frequency}`,
check_type: scopeMapping[templateData.scope] || 'product_quality',
applicable_stages: [],
check_points: [
{
name: 'Verificación General',
description: 'Punto de verificación inicial',
expected_value: 'Conforme',
measurement_type: 'pass_fail',
is_critical: false,
weight: 1.0
}
],
scoring_method: 'weighted_average',
pass_threshold: 70.0,
weight: 5.0,
is_required: templateData.frequency === 'batch',
is_active: true,
frequency_days: templateData.frequency === 'daily' ? 1 : templateData.frequency === 'weekly' ? 7 : undefined
};
await qualityTemplateService.createTemplate(currentTenant.id, templateCreateData);
onDataChange({ ...data, ...templateData });
onComplete();
} catch (err: any) {
console.error('Error creating quality template:', err);
setError(err.response?.data?.detail || 'Error al crear la plantilla de calidad');
} finally {
setLoading(false);
}
};
return (
<div className="space-y-6">
@@ -20,14 +78,31 @@ const TemplateInfoStep: React.FC<WizardDataProps> = ({ data, onDataChange, onNex
<ClipboardCheck className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">Plantilla de Calidad</h3>
</div>
{error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
{error}
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="md:col-span-2">
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Nombre *</label>
<input type="text" value={templateData.name} onChange={(e) => setTemplateData({ ...templateData, name: e.target.value })} placeholder="Ej: Control de Calidad de Pan" className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
<input
type="text"
value={templateData.name}
onChange={(e) => setTemplateData({ ...templateData, name: e.target.value })}
placeholder="Ej: Control de Calidad de Pan"
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Alcance *</label>
<select value={templateData.scope} onChange={(e) => setTemplateData({ ...templateData, scope: e.target.value })} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]">
<select
value={templateData.scope}
onChange={(e) => setTemplateData({ ...templateData, scope: e.target.value })}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
<option value="product">Calidad de Producto</option>
<option value="process">Higiene de Proceso</option>
<option value="equipment">Equipo</option>
@@ -36,15 +111,36 @@ const TemplateInfoStep: React.FC<WizardDataProps> = ({ data, onDataChange, onNex
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Frecuencia *</label>
<select value={templateData.frequency} onChange={(e) => setTemplateData({ ...templateData, frequency: e.target.value })} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]">
<select
value={templateData.frequency}
onChange={(e) => setTemplateData({ ...templateData, frequency: e.target.value })}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
<option value="batch">Cada Lote</option>
<option value="daily">Diario</option>
<option value="weekly">Semanal</option>
</select>
</div>
</div>
<div className="flex justify-end pt-4 border-t border-[var(--border-primary)]">
<button onClick={() => { onDataChange({ ...data, ...templateData }); onComplete(); }} disabled={!templateData.name} className="px-8 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 font-semibold inline-flex items-center gap-2"><CheckCircle2 className="w-5 h-5" />Crear Plantilla</button>
<button
onClick={handleSave}
disabled={!templateData.name || loading}
className="px-8 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 font-semibold inline-flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Guardando...
</>
) : (
<>
<CheckCircle2 className="w-5 h-5" />
Crear Plantilla
</>
)}
</button>
</div>
</div>
);

View File

@@ -1,6 +1,8 @@
import React, { useState } from 'react';
import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal';
import { UserPlus, Shield, CheckCircle2, Mail, Phone } from 'lucide-react';
import { UserPlus, Shield, CheckCircle2, Mail, Phone, Loader2 } from 'lucide-react';
import { useTenant } from '../../../../stores/tenant.store';
import { authService } from '../../../../api/services/auth';
interface WizardDataProps extends WizardStepProps {
data: Record<string, any>;
@@ -25,19 +27,47 @@ const MemberDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange, onNe
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="md:col-span-2">
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Nombre Completo *</label>
<input type="text" value={memberData.fullName} onChange={(e) => setMemberData({ ...memberData, fullName: e.target.value })} placeholder="Ej: Juan García" className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
<input
type="text"
value={memberData.fullName}
onChange={(e) => setMemberData({ ...memberData, fullName: e.target.value })}
placeholder="Ej: Juan García"
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2"><Mail className="w-3.5 h-3.5 inline mr-1" />Email *</label>
<input type="email" value={memberData.email} onChange={(e) => setMemberData({ ...memberData, email: e.target.value })} placeholder="juan@panaderia.com" className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
<Mail className="w-3.5 h-3.5 inline mr-1" />
Email *
</label>
<input
type="email"
value={memberData.email}
onChange={(e) => setMemberData({ ...memberData, email: e.target.value })}
placeholder="juan@panaderia.com"
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2"><Phone className="w-3.5 h-3.5 inline mr-1" />Teléfono</label>
<input type="tel" value={memberData.phone} onChange={(e) => setMemberData({ ...memberData, phone: e.target.value })} placeholder="+34 123 456 789" className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" />
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
<Phone className="w-3.5 h-3.5 inline mr-1" />
Teléfono
</label>
<input
type="tel"
value={memberData.phone}
onChange={(e) => setMemberData({ ...memberData, phone: e.target.value })}
placeholder="+34 123 456 789"
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Posición *</label>
<select value={memberData.position} onChange={(e) => setMemberData({ ...memberData, position: e.target.value })} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]">
<select
value={memberData.position}
onChange={(e) => setMemberData({ ...memberData, position: e.target.value })}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
<option value="baker">Panadero</option>
<option value="pastry-chef">Pastelero</option>
<option value="manager">Gerente</option>
@@ -47,7 +77,11 @@ const MemberDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange, onNe
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Tipo de Empleo</label>
<select value={memberData.employmentType} onChange={(e) => setMemberData({ ...memberData, employmentType: e.target.value })} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]">
<select
value={memberData.employmentType}
onChange={(e) => setMemberData({ ...memberData, employmentType: e.target.value })}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
<option value="full-time">Tiempo Completo</option>
<option value="part-time">Medio Tiempo</option>
<option value="contractor">Contratista</option>
@@ -55,13 +89,23 @@ const MemberDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange, onNe
</div>
</div>
<div className="flex justify-end pt-4 border-t border-[var(--border-primary)]">
<button onClick={() => { onDataChange({ ...data, ...memberData }); onNext(); }} disabled={!memberData.fullName || !memberData.email} className="px-6 py-2.5 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary)]/90 disabled:opacity-50 disabled:cursor-not-allowed">Continuar</button>
<button
onClick={() => {
onDataChange({ ...data, ...memberData });
onNext();
}}
disabled={!memberData.fullName || !memberData.email}
className="px-6 py-2.5 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary)]/90 disabled:opacity-50 disabled:cursor-not-allowed"
>
Continuar
</button>
</div>
</div>
);
};
const PermissionsStep: React.FC<WizardDataProps> = ({ data, onDataChange, onComplete }) => {
const { currentTenant } = useTenant();
const [permissions, setPermissions] = useState({
role: data.role || 'staff',
canManageInventory: data.canManageInventory || false,
@@ -69,6 +113,48 @@ const PermissionsStep: React.FC<WizardDataProps> = ({ data, onDataChange, onComp
canCreateOrders: data.canCreateOrders || false,
canViewFinancial: data.canViewFinancial || false,
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSave = async () => {
if (!currentTenant?.id) {
setError('No se pudo obtener información del tenant');
return;
}
setLoading(true);
setError(null);
try {
// Generate a temporary password (in production, this should be sent via email)
const tempPassword = `Temp${Math.random().toString(36).substring(2, 10)}!`;
// Register the new team member
const registrationData = {
email: data.email,
password: tempPassword,
full_name: data.fullName,
phone_number: data.phone || undefined,
tenant_id: currentTenant.id,
role: permissions.role,
};
await authService.register(registrationData);
// In a real implementation, you would:
// 1. Send email with temporary password
// 2. Store permissions in a separate permissions table
// 3. Link user to tenant with specific role
onDataChange({ ...data, ...permissions, tempPassword });
onComplete();
} catch (err: any) {
console.error('Error creating team member:', err);
setError(err.response?.data?.detail || 'Error al crear el miembro del equipo');
} finally {
setLoading(false);
}
};
return (
<div className="space-y-6">
@@ -77,9 +163,20 @@ const PermissionsStep: React.FC<WizardDataProps> = ({ data, onDataChange, onComp
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">Rol y Permisos</h3>
<p className="text-sm text-[var(--text-secondary)]">{data.fullName}</p>
</div>
{error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
{error}
</div>
)}
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-3">Rol del Sistema</label>
<select value={permissions.role} onChange={(e) => setPermissions({ ...permissions, role: e.target.value })} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]">
<select
value={permissions.role}
onChange={(e) => setPermissions({ ...permissions, role: e.target.value })}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
<option value="admin">Administrador</option>
<option value="manager">Gerente</option>
<option value="staff">Personal</option>
@@ -95,15 +192,39 @@ const PermissionsStep: React.FC<WizardDataProps> = ({ data, onDataChange, onComp
{ key: 'canCreateOrders', label: 'Crear Pedidos' },
{ key: 'canViewFinancial', label: 'Ver Datos Financieros' },
].map(({ key, label }) => (
<label key={key} className="flex items-center gap-3 p-3 border border-[var(--border-secondary)] rounded-lg cursor-pointer hover:bg-[var(--bg-secondary)]/30">
<input type="checkbox" checked={permissions[key as keyof typeof permissions] as boolean} onChange={(e) => setPermissions({ ...permissions, [key]: e.target.checked })} className="w-4 h-4 text-[var(--color-primary)] rounded focus:ring-[var(--color-primary)]" />
<label
key={key}
className="flex items-center gap-3 p-3 border border-[var(--border-secondary)] rounded-lg cursor-pointer hover:bg-[var(--bg-secondary)]/30"
>
<input
type="checkbox"
checked={permissions[key as keyof typeof permissions] as boolean}
onChange={(e) => setPermissions({ ...permissions, [key]: e.target.checked })}
className="w-4 h-4 text-[var(--color-primary)] rounded focus:ring-[var(--color-primary)]"
/>
<span className="text-sm text-[var(--text-primary)]">{label}</span>
</label>
))}
</div>
</div>
<div className="flex justify-end gap-3 pt-4 border-t border-[var(--border-primary)]">
<button onClick={() => { onDataChange({ ...data, ...permissions }); console.log('Saving team member:', { ...data, ...permissions }); onComplete(); }} className="px-8 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 font-semibold inline-flex items-center gap-2"><CheckCircle2 className="w-5 h-5" />Agregar Miembro</button>
<button
onClick={handleSave}
disabled={loading}
className="px-8 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 font-semibold inline-flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Guardando...
</>
) : (
<>
<CheckCircle2 className="w-5 h-5" />
Agregar Miembro
</>
)}
</button>
</div>
</div>
);