// src/components/admin/plans/PlanManager/index.tsx 'use client'; import { AlertCircle, CheckCircle, FileText, XCircle } from 'lucide-react'; import { useState, useMemo, memo, useCallback, useEffect } from 'react'; import { PlanManagerSkeleton } from './PlanManagerSkeleton'; import { usePlans } from '@/hooks/usePlans'; import type { PlanManagerProps, Plan } from '@/types/form'; import { Header } from './Header'; import { EmptyState } from './EmptyState'; import { PlanList } from './PlanList'; import { useDebounce } from '@/hooks/useDebounce'; import { useRouter } from 'next/navigation'; // Componente de Error const ErrorMessage = memo(function ErrorMessage({ message }: { message: string }) { return (

{message}

); }); // Componente de estadísticas const StatsPanel = memo(function StatsPanel({ totalPlans, publishedPlans, draftPlans }: { totalPlans: number; publishedPlans: number; draftPlans: number; }) { return (

Total de planes

{totalPlans}

Publicados

{publishedPlans}

Borradores

{draftPlans}

); }); export const PlanManager = memo(function PlanManager({ onPlanUpdate }: PlanManagerProps) { const router = useRouter(); // Callback para actualizar la interfaz después de modificar un plan const handlePlanUpdate = useCallback((plan: Plan) => { // Forzar una actualización de la interfaz router.refresh(); // Llamar al callback onPlanUpdate si existe if (onPlanUpdate) { onPlanUpdate(plan); } }, [router, onPlanUpdate]); const { plans, isLoading, error, togglePublished, deletePlan: deletePlanHook, duplicatePlan: duplicatePlanHook, refreshPlans } = usePlans(handlePlanUpdate); // Adaptador para deletePlan para que coincida con el tipo esperado por PlanList const deletePlan = useCallback(async (id: string): Promise => { await deletePlanHook(id); }, [deletePlanHook]); // Adaptador para duplicatePlan que asegura que la interfaz se actualice const duplicatePlan = useCallback(async (id: string): Promise => { const result = await duplicatePlanHook(id); // Forzar una actualización de la interfaz después de duplicar if (result) { // Usamos setTimeout para asegurarnos de que la actualización ocurra después de que el estado se haya actualizado setTimeout(() => { refreshPlans(); router.refresh(); }, 100); } return result; }, [duplicatePlanHook, refreshPlans, router]); // Recargar los planes cuando el componente se monta useEffect(() => { refreshPlans(); }, [refreshPlans]); const [searchTerm, setSearchTerm] = useState(''); const [publishedFilter, setPublishedFilter] = useState('all'); const debouncedSearchTerm = useDebounce(searchTerm, 300); // Estadísticas const stats = useMemo(() => { if (!plans) return { total: 0, published: 0, draft: 0 }; const published = plans.filter(plan => plan.published).length; return { total: plans.length, published, draft: plans.length - published }; }, [plans]); // Filtrar y ordenar planes const filteredPlans = useMemo(() => { if (!plans) { return []; } // Primero filtrar por estado de publicación let filtered = [...plans]; if (publishedFilter === 'published') { filtered = filtered.filter(plan => plan.published); } else if (publishedFilter === 'draft') { filtered = filtered.filter(plan => !plan.published); } // Luego filtrar por término de búsqueda if (debouncedSearchTerm.trim()) { const searchTerms = debouncedSearchTerm.toLowerCase().split(' '); filtered = filtered.filter(plan => { const searchableText = ` ${plan.mainTitle} ${plan.destination} ${plan.categoryAlias} ${plan.promotionalText} `.toLowerCase(); return searchTerms.every(term => searchableText.includes(term)); }); } // Ordenar los resultados const result = filtered.sort((a, b) => { // Si hay término de búsqueda, priorizar coincidencias exactas if (debouncedSearchTerm.trim()) { const aTitle = a.mainTitle.toLowerCase(); const bTitle = b.mainTitle.toLowerCase(); const aDestination = a.destination.toLowerCase(); const bDestination = b.destination.toLowerCase(); const aExactMatch = aTitle.includes(debouncedSearchTerm.toLowerCase()) || aDestination.includes(debouncedSearchTerm.toLowerCase()); const bExactMatch = bTitle.includes(debouncedSearchTerm.toLowerCase()) || bDestination.includes(debouncedSearchTerm.toLowerCase()); if (aExactMatch && !bExactMatch) return -1; if (!aExactMatch && bExactMatch) return 1; } // Por defecto, ordenar por fecha de creación (más recientes primero) return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); }); return result; }, [plans, debouncedSearchTerm, publishedFilter]); if (isLoading) return ; if (error) return ; return (
{filteredPlans.length === 0 ? ( searchTerm || publishedFilter !== 'all' ? ( ) : ( ) ) : ( )}
); });