phase 6 ,7,9
This commit is contained in:
304
frontend/src/pages/Sites/Dashboard.tsx
Normal file
304
frontend/src/pages/Sites/Dashboard.tsx
Normal file
@@ -0,0 +1,304 @@
|
||||
/**
|
||||
* 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 { siteId } = useParams<{ siteId: 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}/integrations`,
|
||||
},
|
||||
{
|
||||
label: 'Deployments',
|
||||
value: stats?.deployments_count || 0,
|
||||
icon: <TrendingUpIcon className="w-5 h-5" />,
|
||||
color: 'teal',
|
||||
link: `/sites/${siteId}/deployments`,
|
||||
},
|
||||
{
|
||||
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}/integrations`)}
|
||||
className="justify-start"
|
||||
>
|
||||
<PlugIcon className="w-4 h-4 mr-2" />
|
||||
Manage Integrations
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/sites/${siteId}/deploy`)}
|
||||
className="justify-start"
|
||||
>
|
||||
<TrendingUpIcon className="w-4 h-4 mr-2" />
|
||||
Deploy 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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user