- Changed `siteId` to `id` in `useParams` across multiple site-related components for consistency. - Removed "Sites" from the sidebar settings menu. - Updated navigation links in `SiteDashboard` to reflect new paths for integrations and deployments. - Enhanced `SiteList` component with improved site management features, including modals for site creation and sector configuration. - Added functionality to handle industry and sector selection for sites, with validation for maximum selections. - Improved UI elements and alerts for better user experience in site management.
305 lines
8.6 KiB
TypeScript
305 lines
8.6 KiB
TypeScript
/**
|
|
* Site Dashboard (Advanced)
|
|
* Phase 7: UI Components & Prompt Management
|
|
* Site overview with statistics and analytics
|
|
*/
|
|
import React, { useState, useEffect } from 'react';
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
import {
|
|
EyeIcon,
|
|
FileTextIcon,
|
|
PlugIcon,
|
|
TrendingUpIcon,
|
|
CalendarIcon,
|
|
GlobeIcon
|
|
} from 'lucide-react';
|
|
import PageMeta from '../../components/common/PageMeta';
|
|
import { Card } from '../../components/ui/card';
|
|
import Button from '../../components/ui/button/Button';
|
|
import { useToast } from '../../components/ui/toast/ToastContainer';
|
|
import { fetchAPI } from '../../services/api';
|
|
|
|
interface Site {
|
|
id: number;
|
|
name: string;
|
|
slug: string;
|
|
site_type: string;
|
|
hosting_type: string;
|
|
status: string;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
updated_at: string;
|
|
domain?: string;
|
|
}
|
|
|
|
interface SiteStats {
|
|
total_pages: number;
|
|
published_pages: number;
|
|
draft_pages: number;
|
|
total_content: number;
|
|
published_content: number;
|
|
integrations_count: number;
|
|
deployments_count: number;
|
|
last_deployment?: string;
|
|
views_count?: number;
|
|
visitors_count?: number;
|
|
}
|
|
|
|
export default function SiteDashboard() {
|
|
const { id: siteId } = useParams<{ id: string }>();
|
|
const navigate = useNavigate();
|
|
const toast = useToast();
|
|
const [site, setSite] = useState<Site | null>(null);
|
|
const [stats, setStats] = useState<SiteStats | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
if (siteId) {
|
|
loadSiteData();
|
|
}
|
|
}, [siteId]);
|
|
|
|
const loadSiteData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const [siteData, statsData] = await Promise.all([
|
|
fetchAPI(`/v1/auth/sites/${siteId}/`),
|
|
fetchSiteStats(),
|
|
]);
|
|
|
|
if (siteData) {
|
|
setSite(siteData);
|
|
}
|
|
|
|
if (statsData) {
|
|
setStats(statsData);
|
|
}
|
|
} catch (error: any) {
|
|
toast.error(`Failed to load site data: ${error.message}`);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const fetchSiteStats = async (): Promise<SiteStats | null> => {
|
|
try {
|
|
// TODO: Create backend endpoint for site stats
|
|
// For now, return mock data structure
|
|
return {
|
|
total_pages: 0,
|
|
published_pages: 0,
|
|
draft_pages: 0,
|
|
total_content: 0,
|
|
published_content: 0,
|
|
integrations_count: 0,
|
|
deployments_count: 0,
|
|
};
|
|
} catch (error) {
|
|
console.error('Error fetching site stats:', error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="p-6">
|
|
<PageMeta title="Site Dashboard" />
|
|
<div className="flex items-center justify-center h-64">
|
|
<div className="text-gray-500">Loading site dashboard...</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!site) {
|
|
return (
|
|
<div className="p-6">
|
|
<PageMeta title="Site Not Found" />
|
|
<Card className="p-12 text-center">
|
|
<p className="text-gray-600 dark:text-gray-400 mb-4">Site not found</p>
|
|
<Button onClick={() => navigate('/sites')} variant="outline">
|
|
Back to Sites
|
|
</Button>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const statCards = [
|
|
{
|
|
label: 'Total Pages',
|
|
value: stats?.total_pages || 0,
|
|
icon: <FileTextIcon className="w-5 h-5" />,
|
|
color: 'blue',
|
|
link: `/sites/${siteId}/pages`,
|
|
},
|
|
{
|
|
label: 'Published Pages',
|
|
value: stats?.published_pages || 0,
|
|
icon: <GlobeIcon className="w-5 h-5" />,
|
|
color: 'green',
|
|
link: `/sites/${siteId}/pages?status=published`,
|
|
},
|
|
{
|
|
label: 'Draft Pages',
|
|
value: stats?.draft_pages || 0,
|
|
icon: <FileTextIcon className="w-5 h-5" />,
|
|
color: 'amber',
|
|
link: `/sites/${siteId}/pages?status=draft`,
|
|
},
|
|
{
|
|
label: 'Integrations',
|
|
value: stats?.integrations_count || 0,
|
|
icon: <PlugIcon className="w-5 h-5" />,
|
|
color: 'purple',
|
|
link: `/sites/${siteId}/settings?tab=integrations`,
|
|
},
|
|
{
|
|
label: 'Deployments',
|
|
value: stats?.deployments_count || 0,
|
|
icon: <TrendingUpIcon className="w-5 h-5" />,
|
|
color: 'teal',
|
|
link: `/sites/${siteId}/preview`,
|
|
},
|
|
{
|
|
label: 'Total Content',
|
|
value: stats?.total_content || 0,
|
|
icon: <FileTextIcon className="w-5 h-5" />,
|
|
color: 'indigo',
|
|
link: `/sites/${siteId}/content`,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<PageMeta title={`${site.name} - Dashboard`} />
|
|
|
|
{/* Header */}
|
|
<div className="mb-6 flex justify-between items-start">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
|
{site.name}
|
|
</h1>
|
|
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
|
{site.slug} • {site.site_type} • {site.hosting_type}
|
|
</p>
|
|
{site.domain && (
|
|
<p className="text-sm text-gray-500 dark:text-gray-500 mt-1">
|
|
<GlobeIcon className="w-4 h-4 inline mr-1" />
|
|
{site.domain}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => navigate(`/sites/${siteId}/preview`)}
|
|
>
|
|
<EyeIcon className="w-4 h-4 mr-2" />
|
|
Preview
|
|
</Button>
|
|
<Button
|
|
variant="primary"
|
|
onClick={() => navigate(`/sites/${siteId}/settings`)}
|
|
>
|
|
Settings
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
|
|
{statCards.map((stat, index) => (
|
|
<Card
|
|
key={index}
|
|
className="p-4 hover:shadow-lg transition-shadow cursor-pointer"
|
|
onClick={() => stat.link && navigate(stat.link)}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">
|
|
{stat.label}
|
|
</p>
|
|
<p className="text-2xl font-bold text-gray-900 dark:text-white">
|
|
{stat.value}
|
|
</p>
|
|
</div>
|
|
<div className={`text-${stat.color}-500`}>
|
|
{stat.icon}
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
{/* Quick Actions */}
|
|
<Card className="p-6 mb-6">
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
|
Quick Actions
|
|
</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => navigate(`/sites/${siteId}/pages`)}
|
|
className="justify-start"
|
|
>
|
|
<FileTextIcon className="w-4 h-4 mr-2" />
|
|
Manage Pages
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => navigate(`/sites/${siteId}/content`)}
|
|
className="justify-start"
|
|
>
|
|
<FileTextIcon className="w-4 h-4 mr-2" />
|
|
Manage Content
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => navigate(`/sites/${siteId}/settings?tab=integrations`)}
|
|
className="justify-start"
|
|
>
|
|
<PlugIcon className="w-4 h-4 mr-2" />
|
|
Manage Integrations
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => navigate(`/sites/${siteId}/editor`)}
|
|
className="justify-start"
|
|
>
|
|
<FileTextIcon className="w-4 h-4 mr-2" />
|
|
Edit Site
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Recent Activity */}
|
|
<Card className="p-6">
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
|
Recent Activity
|
|
</h2>
|
|
<div className="space-y-3">
|
|
{stats?.last_deployment ? (
|
|
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
|
<CalendarIcon className="w-5 h-5 text-gray-400" />
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
|
Last Deployment
|
|
</p>
|
|
<p className="text-xs text-gray-600 dark:text-gray-400">
|
|
{new Date(stats.last_deployment).toLocaleString()}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
No recent activity
|
|
</p>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|