// 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 (
);
});
// 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}
);
});
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' ? (
) : (
)
) : (
)}
);
});