/** * Site List View * Refactored to use TablePageTemplate with table view as default * Supports table and grid view toggle */ import React, { useState, useEffect, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import PageMeta from '../../components/common/PageMeta'; import PageHeader from '../../components/common/PageHeader'; import TablePageTemplate from '../../templates/TablePageTemplate'; import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; import Badge from '../../components/ui/badge/Badge'; import Alert from '../../components/ui/alert/Alert'; import Switch from '../../components/form/switch/Switch'; import ViewToggle from '../../components/common/ViewToggle'; import ModuleNavigationTabs from '../../components/navigation/ModuleNavigationTabs'; import WorkflowGuide from '../../components/onboarding/WorkflowGuide'; import { PlusIcon, PencilIcon, EyeIcon, TrashBinIcon, GridIcon, PlugInIcon, FileIcon, PageIcon, TableIcon, ChevronDownIcon, ChevronUpIcon } from '../../icons'; import { fetchSites, createSite, updateSite, deleteSite, setActiveSite, selectSectorsForSite, fetchIndustries, fetchSiteSectors, Site as SiteType, Industry, fetchAPI, } from '../../services/api'; import { useToast } from '../../components/ui/toast/ToastContainer'; import SiteTypeBadge from '../../components/sites/SiteTypeBadge'; interface Site extends SiteType { page_count?: number; integration_count?: number; has_wordpress_integration?: boolean; domain?: string; description?: string; industry_name?: string; active_sectors_count?: number; } type ViewType = 'table' | 'grid'; export default function SiteList() { const navigate = useNavigate(); const toast = useToast(); const [sites, setSites] = useState([]); const [filteredSites, setFilteredSites] = useState([]); const [loading, setLoading] = useState(true); const [viewType, setViewType] = useState('grid'); const [showWelcomeGuide, setShowWelcomeGuide] = useState(false); // Site Management Modals const [selectedSite, setSelectedSite] = useState(null); const [togglingSiteId, setTogglingSiteId] = useState(null); // Filters const [searchTerm, setSearchTerm] = useState(''); const [siteTypeFilter, setSiteTypeFilter] = useState(''); const [hostingTypeFilter, setHostingTypeFilter] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const [integrationFilter, setIntegrationFilter] = useState(''); useEffect(() => { loadSites(); }, []); const loadUserPreferences = async () => { try { const { fetchAccountSetting } = await import('../../services/api'); const setting = await fetchAccountSetting('user_preferences'); const preferences = setting.config as { selectedIndustry?: string; selectedSectors?: string[] } | undefined; if (preferences) { setUserPreferences(preferences); } } catch (error: any) { // 404 means preferences don't exist yet - that's fine // 500 and other errors should be handled silently - user can still use the page if (error?.status === 404) { // Preferences don't exist yet - this is expected for new users return; } // Silently handle other errors (500, network errors, etc.) - don't spam console // User can still use the page without preferences } }; useEffect(() => { applyFilters(); }, [sites, searchTerm, siteTypeFilter, hostingTypeFilter, statusFilter, integrationFilter]); const loadSites = async () => { try { setLoading(true); const response = await fetchSites(); const data = response.results || response || []; if (Array.isArray(data)) { // Check for WordPress integrations const sitesWithIntegrations = await Promise.all( data.map(async (site: Site) => { if (site.hosting_type === 'wordpress') { try { const integrations = await fetchAPI(`/v1/integration/integrations/?site=${site.id}&platform=wordpress`); return { ...site, has_wordpress_integration: integrations?.results?.length > 0 || integrations?.length > 0, }; } catch { return { ...site, has_wordpress_integration: false }; } } return site; }) ); setSites(sitesWithIntegrations); } } catch (error: any) { toast.error(`Failed to load sites: ${error.message}`); } finally { setLoading(false); } }; const applyFilters = () => { let filtered = [...sites]; // Search filter if (searchTerm) { filtered = filtered.filter( (site) => site.name.toLowerCase().includes(searchTerm.toLowerCase()) || site.slug.toLowerCase().includes(searchTerm.toLowerCase()) ); } // Site type filter if (siteTypeFilter) { filtered = filtered.filter((site) => site.site_type === siteTypeFilter); } // Hosting type filter if (hostingTypeFilter) { filtered = filtered.filter((site) => site.hosting_type === hostingTypeFilter); } // Status filter if (statusFilter) { if (statusFilter === 'active') { filtered = filtered.filter((site) => site.is_active); } else if (statusFilter === 'inactive') { filtered = filtered.filter((site) => !site.is_active); } else { filtered = filtered.filter((site) => site.status === statusFilter); } } // Integration filter if (integrationFilter === 'has_integrations') { filtered = filtered.filter((site) => (site.integration_count || 0) > 0); } else if (integrationFilter === 'no_integrations') { filtered = filtered.filter((site) => (site.integration_count || 0) === 0); } setFilteredSites(filtered); }; const handleSettings = (site: Site) => { setSelectedSite(site); setShowSectorsModal(true); loadSiteSectors(site); }; const handleToggle = async (siteId: number, enabled: boolean) => { if (togglingSiteId !== null) { toast.error('Please wait for the current operation to complete'); return; } try { setTogglingSiteId(siteId); if (enabled) { await setActiveSite(siteId); toast.success('Site activated successfully'); } else { const site = sites.find(s => s.id === siteId); if (site) { await updateSite(siteId, { is_active: false }); toast.success('Site deactivated successfully'); } } await loadSites(); } catch (error: any) { toast.error(`Failed to update site: ${error.message}`); } finally { setTogglingSiteId(null); } }; const loadSiteSectors = async (site: Site) => { try { const sectors = await fetchSiteSectors(site.id); const sectorSlugs = sectors.map((s: any) => s.slug); setSelectedSectors(sectorSlugs); if (site.industry_slug) { setSelectedIndustry(site.industry_slug); } else { for (const industry of industries) { const matchingSectors = industry.sectors.filter(s => sectorSlugs.includes(s.slug)); if (matchingSectors.length > 0) { setSelectedIndustry(industry.slug); break; } } } } catch (error: any) { console.error('Failed to load site sectors:', error); } }; const handleDeleteSite = async (siteId: number) => { try { await deleteSite(siteId); toast.success('Site deleted successfully'); await loadSites(); } catch (error: any) { toast.error(`Failed to delete site: ${error.message}`); } }; const getIndustrySectors = () => { if (!selectedIndustry) return []; const industry = industries.find(i => i.slug === selectedIndustry); let sectors = industry?.sectors || []; // Filter to show only user's pre-selected sectors from account preferences if (userPreferences?.selectedSectors && userPreferences.selectedSectors.length > 0) { sectors = sectors.filter(s => userPreferences.selectedSectors!.includes(s.slug)); } return sectors; }; const clearFilters = () => { setSearchTerm(''); setSiteTypeFilter(''); setHostingTypeFilter(''); setStatusFilter(''); setIntegrationFilter(''); }; const SITE_TYPES = [ { value: '', label: 'All Types' }, { value: 'marketing', label: 'Marketing' }, { value: 'ecommerce', label: 'Ecommerce' }, { value: 'blog', label: 'Blog' }, { value: 'portfolio', label: 'Portfolio' }, { value: 'corporate', label: 'Corporate' }, ]; const HOSTING_TYPES = [ { value: '', label: 'All Hosting' }, { value: 'igny8_sites', label: 'IGNY8 Sites' }, { value: 'wordpress', label: 'WordPress' }, { value: 'shopify', label: 'Shopify' }, { value: 'multi', label: 'Multi-Destination' }, ]; const STATUS_OPTIONS = [ { value: '', label: 'All Status' }, { value: 'active', label: 'Active' }, { value: 'inactive', label: 'Inactive' }, ]; const INTEGRATION_OPTIONS = [ { value: '', label: 'All Sites' }, { value: 'has_integrations', label: 'Has Integrations' }, { value: 'no_integrations', label: 'No Integrations' }, ]; // Table columns configuration const tableColumns = useMemo(() => [ { key: 'name', label: 'Site Name', sortable: true, render: (value: string, row: Site) => (
{row.name}
{row.domain && (
{row.domain}
)}
), }, { key: 'hosting_type', label: 'Hosting', sortable: true, render: (value: string, row: Site) => , }, { key: 'industry_name', label: 'Industry', sortable: false, render: (value: string, row: Site) => ( row.industry_name ? ( {row.industry_name} ) : ( - ) ), }, { key: 'active_sectors_count', label: 'Sectors', sortable: false, render: (value: number, row: Site) => ( {row.active_sectors_count || 0} / 5 ), }, { key: 'integration_count', label: 'Integrations', sortable: false, render: (value: number, row: Site) => ( row.integration_count && row.integration_count > 0 ? ( {row.integration_count} ) : ( - ) ), }, { key: 'is_active', label: 'Status', sortable: true, render: (value: boolean, row: Site) => (
{row.is_active ? 'Active' : 'Inactive'}
), }, ], []); // Grid view component const renderGridView = () => (
{filteredSites.map((site) => (

{site.name}

{site.description || 'No description'}

{site.domain && (

{site.domain}

)}
{site.industry_name && ( {site.industry_name} )} {site.integration_count && site.integration_count > 0 && ( {site.integration_count} integration{site.integration_count > 1 ? 's' : ''} )}
handleToggle(site.id, enabled)} disabled={togglingSiteId === site.id} /> {site.is_active ? 'Active' : 'Inactive'}
))}
); const hasActiveFilters = searchTerm || siteTypeFilter || hostingTypeFilter || statusFilter || integrationFilter; if (loading) { return (
Loading sites...
); } // Navigation tabs for Sites module const sitesTabs = [ { label: 'All Sites', path: '/sites', icon: }, ]; return (
, color: 'blue' }} hideSiteSector={true} navigation={} /> {/* Custom Header Actions - Add Site button and view toggle */}
{/* Welcome Guide - Collapsible */} {showWelcomeGuide && (
{ loadSites(); setShowWelcomeGuide(false); }} />
)} {/* Table View */} {viewType === 'table' ? ( { if (key === 'search') setSearchTerm(value); else if (key === 'site_type') setSiteTypeFilter(value); else if (key === 'hosting_type') setHostingTypeFilter(value); else if (key === 'status') setStatusFilter(value); else if (key === 'integration') setIntegrationFilter(value); }} onFilterReset={clearFilters} onDelete={async (id) => { await handleDeleteSite(id); }} getItemDisplayName={(row) => row.name} /> ) : ( <> {/* Standard Filters Bar for Grid View - Matches Table View */}
setSearchTerm(e.target.value)} className="flex-1 min-w-[200px] h-9 px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" />
{hasActiveFilters && ( )}
{/* Grid View */} {filteredSites.length === 0 ? (

{hasActiveFilters ? 'No sites match your filters' : 'No sites created yet'}

{hasActiveFilters ? ( ) : ( )}
) : ( renderGridView() )} )}
); }