erp-platform/ui/src/components/developerKit/Dashboard.tsx
2025-08-11 00:31:39 +03:00

535 lines
21 KiB
TypeScript

import React from "react";
import { Link } from "react-router-dom";
import { useComponents } from "../../contexts/ComponentContext";
import { useEntities } from "../../contexts/EntityContext";
import { useSystemHealth } from "../../utils/hooks/useDeveloperKit";
import {
Database,
Zap,
Server,
Puzzle,
Activity,
Code,
CheckCircle,
ArrowRight,
AlertCircle,
Wifi,
WifiOff,
} from "lucide-react";
import { ROUTES_ENUM } from "@/routes/route.constant";
import { useLocalization } from "@/utils/hooks/useLocalization";
const Dashboard: React.FC = () => {
const { components } = useComponents();
const { entities, migrations, generatedEndpoints } = useEntities();
const { isOnline, lastCheck, recheckHealth } = useSystemHealth();
const { translate } = useLocalization()
const stats = [
{
name: translate('::App.DeveloperKit.Dashboard.Stats.Entities'),
value: entities.filter((e) => e.isActive).length,
total: entities.length,
icon: Database,
color: "text-blue-600",
bgColor: "bg-blue-100",
href: ROUTES_ENUM.protected.saas.developerKitEntities,
},
{
name: translate('::App.DeveloperKit.Dashboard.Stats.Migrations'),
value: migrations.filter((m) => m.status === "pending").length,
total: migrations.length,
icon: Zap,
color: "text-yellow-600",
bgColor: "bg-yellow-100",
href: ROUTES_ENUM.protected.saas.developerKitMigrations,
},
{
name: translate('::App.DeveloperKit.Dashboard.Stats.APIs'),
value: generatedEndpoints.filter((e) => e.isActive).length,
total: generatedEndpoints.length,
icon: Server,
color: "text-emerald-600",
bgColor: "bg-emerald-100",
href: ROUTES_ENUM.protected.saas.developerKitEndpoints,
},
{
name: translate('::App.DeveloperKit.Dashboard.Stats.Components'),
value: components?.filter((c) => c.isActive).length,
total: components?.length,
icon: Puzzle,
color: "text-purple-600",
bgColor: "bg-purple-100",
href: ROUTES_ENUM.protected.saas.developerKitComponents,
},
];
const developmentFlow = [
{
step: 1,
title: translate('::App.DeveloperKit.Dashboard.Flow.CreateEntity'),
description: translate('::App.DeveloperKit.Dashboard.Flow.CreateEntity.Desc'),
icon: Database,
color: "bg-blue-600",
href: ROUTES_ENUM.protected.saas.developerKitEntitiesNew,
status: "ready",
},
{
step: 2,
title: translate('::App.DeveloperKit.Dashboard.Flow.GenerateMigration'),
description: translate('::App.DeveloperKit.Dashboard.Flow.GenerateMigration.Desc'),
icon: Zap,
color: "bg-yellow-600",
href: ROUTES_ENUM.protected.saas.developerKitMigrations,
status: entities.some((e) => e.migrationStatus === "pending")
? "action-needed"
: "ready",
},
{
step: 3,
title: translate('::App.DeveloperKit.Dashboard.Flow.ApplyMigration'),
description: translate('::App.DeveloperKit.Dashboard.Flow.ApplyMigration.Desc'),
icon: CheckCircle,
color: "bg-green-600",
href: ROUTES_ENUM.protected.saas.developerKitMigrations,
status: migrations.some((m) => m.status === "pending")
? "action-needed"
: "ready",
},
{
step: 4,
title: translate('::App.DeveloperKit.Dashboard.Flow.GenerateAPI'),
description: translate('::App.DeveloperKit.Dashboard.Flow.GenerateAPI.Desc'),
icon: Server,
color: "bg-emerald-600",
href: ROUTES_ENUM.protected.saas.developerKitEndpoints,
status: "ready",
},
{
step: 5,
title: translate('::App.DeveloperKit.Dashboard.Flow.BuildComponent'),
description: translate('::App.DeveloperKit.Dashboard.Flow.BuildComponent.Desc'),
icon: Puzzle,
color: "bg-purple-600",
href: ROUTES_ENUM.protected.saas.developerKitComponentsNew,
status: "ready",
},
];
const recentEntities = entities.slice(0, 3);
const recentMigrations = migrations.slice(0, 3);
const recentEndpoints = [
...generatedEndpoints.map((e) => ({
id: e.id,
name: `${e.entityName} ${e.operationType}`,
method: e.method,
path: e.path,
description: `Generated ${e.operationType} for ${e.entityName}`,
isActive: e.isActive,
lastModificationTime: e.lastModificationTime,
creationTime: e.creationTime,
})),
].slice(0, 3);
const systemHealth = [
{ name: translate('::App.DeveloperKit.Dashboard.SystemHealth.Frontend'), status: translate('::App.DeveloperKit.Dashboard.SystemHealth.Healthy'), icon: Code },
{
name: translate('::App.DeveloperKit.Dashboard.SystemHealth.Backend'),
status: isOnline ? translate('::App.DeveloperKit.Dashboard.SystemHealth.Healthy') : translate('::App.DeveloperKit.Dashboard.SystemHealth.Offline'),
icon: Server,
},
{
name: translate('::App.DeveloperKit.Dashboard.SystemHealth.Database'),
status: isOnline ? translate('::App.DeveloperKit.Dashboard.SystemHealth.Healthy') : translate('::App.DeveloperKit.Dashboard.SystemHealth.Unknown'),
icon: Database,
},
{
name: translate('::App.DeveloperKit.Dashboard.SystemHealth.Migrations'),
status: migrations.some((m) => m.status === "failed")
? translate('::App.DeveloperKit.Dashboard.SystemHealth.Warning')
: translate('::App.DeveloperKit.Dashboard.SystemHealth.Healthy'),
icon: Zap,
},
];
return (
<div className="space-y-8">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-slate-900 mb-2">
{translate('::App.DeveloperKit.Dashboard.Title')}
</h1>
<p className="text-slate-600">
{translate('::App.DeveloperKit.Dashboard.Description')}
</p>
</div>
<div className="flex items-center gap-3">
<button
onClick={recheckHealth}
className={`flex items-center gap-2 px-3 py-1 rounded-full text-sm font-medium transition-colors duration-200 ${
isOnline
? "bg-green-100 text-green-700 hover:bg-green-200"
: "bg-red-100 text-red-700 hover:bg-red-200"
}`}
title={`Son kontrol: ${lastCheck.toLocaleTimeString()}`}
>
<div
className={`w-2 h-2 rounded-full ${
isOnline
? "bg-green-500 animate-pulse"
: "bg-red-500 animate-pulse"
}`}
/>
{isOnline ? (
<>
<Wifi className="w-4 h-4" />
{translate('::App.DeveloperKit.Dashboard.SystemHealth.Online')}
</>
) : (
<>
<WifiOff className="w-4 h-4" />
{translate('::App.DeveloperKit.Dashboard.SystemHealth.OfflineStatus')}
</>
)}
</button>
</div>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{stats.map((stat) => {
const Icon = stat.icon;
return (
<Link
key={stat.name}
to={stat.href}
className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 hover:shadow-md transition-all duration-200 group"
>
<div className="flex items-center justify-between mb-4">
<div
className={`${stat.bgColor} ${stat.color} p-3 rounded-lg group-hover:scale-110 transition-transform`}
>
<Icon className="w-6 h-6" />
</div>
<ArrowRight className="w-4 h-4 text-slate-400 group-hover:text-slate-600 transition-colors" />
</div>
<div>
<p className="text-sm font-medium text-slate-600">
{stat.name}
</p>
<p className="text-2xl font-bold text-slate-900 mt-1">
{stat.value}
<span className="text-sm font-normal text-slate-500 ml-1">
/ {stat.total}
</span>
</p>
</div>
</Link>
);
})}
</div>
{/* Development Flow */}
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-8">
<div className="flex items-center gap-2 mb-6">
<Activity className="w-6 h-6 text-blue-600" />
<h2 className="text-xl font-semibold text-slate-900">
{translate('::App.DeveloperKit.Dashboard.Flow.Title')}
</h2>
</div>
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6">
{developmentFlow.map((flow, index) => {
const Icon = flow.icon;
return (
<Link key={flow.step} to={flow.href} className="group relative">
<div
className={`${flow.color} text-white p-6 rounded-lg transition-all duration-200 transform group-hover:scale-105`}
>
<div className="flex items-center justify-between mb-4">
<div className="bg-white bg-opacity-20 rounded-lg p-2">
<Icon className="w-6 h-6" />
</div>
<div className="bg-white bg-opacity-20 rounded-full w-8 h-8 flex items-center justify-center">
<span className="text-sm font-bold">{flow.step}</span>
</div>
</div>
<h3 className="font-semibold text-lg mb-2 text-white">{flow.title}</h3>
<p className="text-sm opacity-90">{flow.description}</p>
{flow.status === "action-needed" && (
<div className="absolute -top-2 -right-2">
<div className="bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center">
<AlertCircle className="w-4 h-4" />
</div>
</div>
)}
</div>
{index < developmentFlow.length - 1 && (
<div className="hidden lg:block absolute top-1/2 -right-3 transform -translate-y-1/2">
<ArrowRight className="w-6 h-6 text-slate-300" />
</div>
)}
</Link>
);
})}
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
{/* System Health */}
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
<div className="flex items-center gap-2 mb-4">
<Activity className="w-5 h-5 text-green-500" />
<h3 className="text-lg font-semibold text-slate-900">
{translate('::App.DeveloperKit.Dashboard.SystemHealth.Title') }
</h3>
</div>
<div className="space-y-3">
{systemHealth.map((system) => {
const Icon = system.icon;
return (
<div
key={system.name}
className="flex items-center justify-between p-3 bg-slate-50 rounded-lg"
>
<div className="flex items-center gap-3">
<Icon className="w-4 h-4 text-slate-600" />
<span className="font-medium text-slate-900">
{system.name}
</span>
</div>
<div className="flex items-center gap-2">
<div
className={`w-2 h-2 rounded-full ${
system.status === "healthy"
? "bg-green-500"
: system.status === "warning"
? "bg-yellow-500"
: system.status === "offline"
? "bg-red-500"
: "bg-gray-500"
}`}
/>
<span
className={`text-sm capitalize ${
system.status === "healthy"
? "text-green-600"
: system.status === "warning"
? "text-yellow-600"
: system.status === "offline"
? "text-red-600"
: "text-gray-600"
}`}
>
{system.status === "offline" ? "Offline" : system.status}
</span>
</div>
</div>
);
})}
</div>
</div>
{/* Recent Entities */}
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-slate-900">
{translate('::App.DeveloperKit.Dashboard.RecentEntities.Title')}
</h3>
<Link
to={ROUTES_ENUM.protected.saas.developerKitEntities}
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
>
{translate('::App.DeveloperKit.Dashboard.ViewAll')}
</Link>
</div>
<div className="space-y-3">
{recentEntities.length > 0 ? (
recentEntities.map((entity) => (
<div
key={entity.id}
className="flex items-center justify-between p-3 bg-slate-50 rounded-lg hover:bg-slate-100 transition-colors"
>
<div>
<p className="font-medium text-slate-900">
{entity.displayName}
</p>
<p className="text-sm text-slate-500">
{entity.fields.length} fields {entity.migrationStatus}
</p>
</div>
<div className="flex items-center gap-2">
<div
className={`w-2 h-2 rounded-full ${
entity.migrationStatus === "applied"
? "bg-green-500"
: entity.migrationStatus === "pending"
? "bg-yellow-500"
: "bg-red-500"
}`}
/>
<Link
to={ROUTES_ENUM.protected.saas.developerKitEntitiesEdit.replace(':id', entity.id)}
className="text-blue-600 hover:text-blue-700"
>
<Database className="w-4 h-4" />
</Link>
</div>
</div>
))
) : (
<div className="text-center py-8">
<Database className="w-12 h-12 text-slate-300 mx-auto mb-2" />
<p className="text-slate-500 mb-2">{translate('::App.DeveloperKit.Dashboard.Empty.Entity')}</p>
<Link
to={ROUTES_ENUM.protected.saas.developerKitEntitiesNew}
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
>
Create your first entity
</Link>
</div>
)}
</div>
</div>
{/* Recent Migrations */}
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-slate-900">
{translate('::App.DeveloperKit.Dashboard.RecentMigrations.Title')}
</h3>
<Link
to={ROUTES_ENUM.protected.saas.developerKitMigrations}
className="text-yellow-600 hover:text-yellow-700 text-sm font-medium"
>
{translate('::App.DeveloperKit.Dashboard.ViewAll')}
</Link>
</div>
<div className="space-y-3">
{recentMigrations.length > 0 ? (
recentMigrations.map((migration) => (
<div
key={migration.id}
className="flex items-center justify-between p-3 bg-slate-50 rounded-lg hover:bg-slate-100 transition-colors"
>
<div>
<p className="font-medium text-slate-900">
{migration.entityName}
</p>
<p className="text-sm text-slate-500">
{migration.status} {" "}
{new Date(migration.creationTime).toLocaleDateString()}
</p>
</div>
<div className="flex items-center gap-2">
<div
className={`w-2 h-2 rounded-full ${
migration.status === "applied"
? "bg-green-500"
: migration.status === "pending"
? "bg-yellow-500"
: "bg-red-500"
}`}
/>
<Link
to={ROUTES_ENUM.protected.saas.developerKitMigrations}
className="text-yellow-600 hover:text-yellow-700"
>
<Zap className="w-4 h-4" />
</Link>
</div>
</div>
))
) : (
<div className="text-center py-8">
<Zap className="w-12 h-12 text-slate-300 mx-auto mb-2" />
<p className="text-slate-500 mb-2">{translate('::App.DeveloperKit.Dashboard.Empty.Migration')}</p>
<Link
to={ROUTES_ENUM.protected.saas.developerKitEntitiesNew}
className="text-yellow-600 hover:text-yellow-700 text-sm font-medium"
>
{translate('::App.DeveloperKit.Dashboard.Action.GenerateMigrations')}
</Link>
</div>
)}
</div>
</div>
{/* Recent API Endpoints */}
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-slate-900">
{translate('::App.DeveloperKit.Dashboard.RecentEndpoints.Title')}
</h3>
<Link
to={ROUTES_ENUM.protected.saas.developerKitEndpoints}
className="text-emerald-600 hover:text-emerald-700 text-sm font-medium"
>
{translate('::App.DeveloperKit.Dashboard.ViewAll')}
</Link>
</div>
<div className="space-y-3">
{recentEndpoints.length > 0 ? (
recentEndpoints.map((endpoint) => (
<div
key={endpoint.id}
className="flex items-center justify-between p-3 bg-slate-50 rounded-lg hover:bg-slate-100 transition-colors"
>
<div>
<div className="flex items-center gap-2 mb-1">
<span
className={`px-2 py-0.5 text-xs font-medium rounded ${
endpoint.method === "GET"
? "bg-blue-100 text-blue-800"
: endpoint.method === "POST"
? "bg-green-100 text-green-800"
: endpoint.method === "PUT"
? "bg-yellow-100 text-yellow-800"
: "bg-red-100 text-red-800"
}`}
>
{endpoint.method}
</span>
<p className="font-medium text-slate-900 text-sm">
{endpoint.name}
</p>
</div>
<p className="text-xs text-slate-500">{endpoint.path}</p>
</div>
<div className="flex items-center gap-2">
<div
className={`w-2 h-2 rounded-full ${
endpoint.isActive ? "bg-green-500" : "bg-slate-300"
}`}
/>
<Link
to="/docs"
className="text-emerald-600 hover:text-emerald-700"
>
<Server className="w-4 h-4" />
</Link>
</div>
</div>
))
) : (
<div className="text-center py-8">
<Server className="w-12 h-12 text-slate-300 mx-auto mb-2" />
<p className="text-slate-500 mb-2">{translate('::App.DeveloperKit.Dashboard.Empty.Endpoint')}</p>
<Link
to={ROUTES_ENUM.protected.saas.developerKitEndpointsNew}
className="text-emerald-600 hover:text-emerald-700 text-sm font-medium"
>
{translate('::App.DeveloperKit.Dashboard.Action.CreateEntity')}
</Link>
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default Dashboard;