diff --git a/frontend/src/pages/Sites/List.tsx b/frontend/src/pages/Sites/List.tsx
index 3db5916f..2f71ea43 100644
--- a/frontend/src/pages/Sites/List.tsx
+++ b/frontend/src/pages/Sites/List.tsx
@@ -1,22 +1,20 @@
/**
* Site List View
- * Phase 7: UI Components & Prompt Management
- * Advanced site list with filters and search
+ * Refactored to use TablePageTemplate with table view as default
+ * Supports table and grid view toggle
*/
-import React, { useState, useEffect } from 'react';
+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 SelectDropdown from '../../components/form/SelectDropdown';
-import { useToast } from '../../components/ui/toast/ToastContainer';
-import { fetchAPI } from '../../services/api';
-import SiteTypeBadge from '../../components/sites/SiteTypeBadge';
import Badge from '../../components/ui/badge/Badge';
import FormModal, { FormField } from '../../components/common/FormModal';
import Alert from '../../components/ui/alert/Alert';
import Switch from '../../components/form/switch/Switch';
+import ViewToggle from '../../components/common/ViewToggle';
import {
PlusIcon,
PencilIcon,
@@ -25,8 +23,8 @@ import {
GridIcon,
PlugInIcon,
FileIcon,
- MoreDotIcon,
- PageIcon
+ PageIcon,
+ TableIcon
} from '../../icons';
import {
fetchSites,
@@ -39,7 +37,10 @@ import {
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;
@@ -51,18 +52,20 @@ interface Site extends SiteType {
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('table');
// Site Management Modals
const [selectedSite, setSelectedSite] = useState(null);
const [showSiteModal, setShowSiteModal] = useState(false);
const [showSectorsModal, setShowSectorsModal] = useState(false);
- const [showDetailsModal, setShowDetailsModal] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [togglingSiteId, setTogglingSiteId] = useState(null);
const [industries, setIndustries] = useState([]);
@@ -168,7 +171,7 @@ export default function SiteList() {
}
}
- // Integration filter (has integrations or not)
+ // Integration filter
if (integrationFilter === 'has_integrations') {
filtered = filtered.filter((site) => (site.integration_count || 0) > 0);
} else if (integrationFilter === 'no_integrations') {
@@ -189,18 +192,7 @@ export default function SiteList() {
setShowSiteModal(true);
};
- const handleIntegration = (siteId: number) => {
- navigate(`/sites/${siteId}/settings?tab=integrations`);
- };
-
- const handleEdit = (siteId: number) => {
- const site = sites.find(s => s.id === siteId);
- if (site) {
- handleEditSite(site);
- }
- };
-
- const handleEditSite = (site: Site) => {
+ const handleEdit = (site: Site) => {
setSelectedSite(site);
setFormData({
name: site.name || '',
@@ -211,13 +203,10 @@ export default function SiteList() {
setShowSiteModal(true);
};
- const handleSettings = (siteId: number) => {
- const site = sites.find(s => s.id === siteId);
- if (site) {
- setSelectedSite(site);
- setShowSectorsModal(true);
- loadSiteSectors(site);
- }
+ const handleSettings = (site: Site) => {
+ setSelectedSite(site);
+ setShowSectorsModal(true);
+ loadSiteSectors(site);
};
const handleToggle = async (siteId: number, enabled: boolean) => {
@@ -268,45 +257,6 @@ export default function SiteList() {
}
};
- const handleDetails = (site: Site) => {
- setSelectedSite(site);
- setFormData({
- name: site.name || '',
- domain: site.domain || '',
- description: site.description || '',
- is_active: site.is_active || false,
- });
- setShowDetailsModal(true);
- };
-
- const handleSaveDetails = async () => {
- if (!selectedSite) return;
-
- try {
- setIsSaving(true);
- const normalizedFormData = {
- ...formData,
- domain: formData.domain ? normalizeDomain(formData.domain) : formData.domain,
- };
- await updateSite(selectedSite.id, normalizedFormData);
- toast.success('Site updated successfully');
- setShowDetailsModal(false);
- await loadSites();
- } catch (error: any) {
- toast.error(`Failed to update site: ${error.message}`);
- } finally {
- setIsSaving(false);
- }
- };
-
- const normalizeDomain = (domain: string): string => {
- if (!domain || !domain.trim()) return domain;
- const trimmed = domain.trim();
- if (trimmed.startsWith('https://')) return trimmed;
- if (trimmed.startsWith('http://')) return trimmed.replace('http://', 'https://');
- return `https://${trimmed}`;
- };
-
const handleSaveSite = async () => {
try {
setIsSaving(true);
@@ -345,6 +295,14 @@ export default function SiteList() {
}
};
+ const normalizeDomain = (domain: string): string => {
+ if (!domain || !domain.trim()) return domain;
+ const trimmed = domain.trim();
+ if (trimmed.startsWith('https://')) return trimmed;
+ if (trimmed.startsWith('http://')) return trimmed.replace('http://', 'https://');
+ return `https://${trimmed}`;
+ };
+
const handleSelectSectors = async () => {
if (!selectedSite || !selectedIndustry || selectedSectors.length === 0) {
toast.error('Please select an industry and at least one sector');
@@ -373,19 +331,11 @@ export default function SiteList() {
}
};
- const handleDeleteSite = async (site: Site) => {
- if (!window.confirm(`Are you sure you want to delete "${site.name}"? This action cannot be undone.`)) {
- return;
- }
-
+ const handleDeleteSite = async (siteId: number) => {
try {
- await deleteSite(site.id);
+ await deleteSite(siteId);
toast.success('Site deleted successfully');
await loadSites();
- if (showDetailsModal) {
- setShowDetailsModal(false);
- setSelectedSite(null);
- }
} catch (error: any) {
toast.error(`Failed to delete site: ${error.message}`);
}
@@ -440,24 +390,6 @@ export default function SiteList() {
return industry?.sectors || [];
};
- const handleView = (siteId: number) => {
- navigate(`/sites/${siteId}`);
- };
-
- const handleDelete = async (siteId: number) => {
- if (!confirm('Are you sure you want to delete this site?')) return;
-
- try {
- await fetchAPI(`/v1/auth/sites/${siteId}/`, {
- method: 'DELETE',
- });
- toast.success('Site deleted successfully');
- loadSites();
- } catch (error: any) {
- toast.error(`Failed to delete site: ${error.message}`);
- }
- };
-
const clearFilters = () => {
setSearchTerm('');
setSiteTypeFilter('');
@@ -487,9 +419,6 @@ export default function SiteList() {
{ value: '', label: 'All Status' },
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
- { value: 'active', label: 'Active Status' },
- { value: 'inactive', label: 'Inactive Status' },
- { value: 'suspended', label: 'Suspended' },
];
const INTEGRATION_OPTIONS = [
@@ -498,6 +427,197 @@ export default function SiteList() {
{ 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.active_sectors_count || 0} / 5 Sectors
+
+ {site.integration_count && site.integration_count > 0 && (
+
+ {site.integration_count} integration{site.integration_count > 1 ? 's' : ''}
+
+ )}
+
+
+
+ {site.is_active ? 'Active' : 'Inactive'}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
handleToggle(site.id, enabled)}
+ disabled={togglingSiteId === site.id}
+ />
+
+
+
+ ))}
+
+ );
+
const hasActiveFilters = searchTerm || siteTypeFilter || hostingTypeFilter || statusFilter || integrationFilter;
if (loading) {
@@ -517,26 +637,11 @@ export default function SiteList() {
, color: 'blue' }}
+ hideSiteSector={true}
/>
+ {/* Info Alert */}
-
-
- Manage your sites, configure industries, and select sectors. Multiple sites can be active simultaneously.
-
-
-
-
-
-
-
- {/* Info Alert */}
- {/* Filters */}
-
-
-
-
-
-
- Filters
-
- {hasActiveFilters && (
-