erp-platform/ui/src/components/developerKit/Dashboard.tsx
2025-11-05 12:02:16 +03:00

529 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 {
FaDatabase,
FaBolt,
FaServer,
FaPuzzlePiece,
FaCog,
FaChartLine,
FaCode,
FaCheckCircle,
FaArrowRight,
FaExclamationCircle,
FaWifi,
FaWindowClose,
} from 'react-icons/fa'
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: FaDatabase,
color: 'text-blue-600',
bgColor: 'bg-blue-100',
href: ROUTES_ENUM.protected.saas.developerKit.entities,
},
{
name: translate('::App.DeveloperKit.Dashboard.Stats.Migrations'),
value: migrations.filter((m) => m.status === 'pending').length,
total: migrations.length,
icon: FaBolt,
color: 'text-yellow-600',
bgColor: 'bg-yellow-100',
href: ROUTES_ENUM.protected.saas.developerKit.migrations,
},
{
name: translate('::App.DeveloperKit.Dashboard.Stats.APIs'),
value: generatedEndpoints.filter((e) => e.isActive).length,
total: generatedEndpoints.length,
icon: FaServer,
color: 'text-emerald-600',
bgColor: 'bg-emerald-100',
href: ROUTES_ENUM.protected.saas.developerKit.endpoints,
},
{
name: translate('::App.DeveloperKit.Components'),
value: components?.filter((c) => c.isActive).length,
total: components?.length,
icon: FaPuzzlePiece,
color: 'text-purple-600',
bgColor: 'bg-purple-100',
href: ROUTES_ENUM.protected.saas.developerKit.components,
},
]
const developmentFlow = [
{
step: 1,
title: translate('::App.DeveloperKit.Entity.CreateEntity'),
description: translate('::App.DeveloperKit.Dashboard.Flow.CreateEntity.Desc'),
icon: FaDatabase,
color: 'bg-blue-600',
href: ROUTES_ENUM.protected.saas.developerKit.entitiesNew,
status: 'ready',
},
{
step: 2,
title: translate('::App.DeveloperKit.Dashboard.Flow.GenerateMigration'),
description: translate('::App.DeveloperKit.Dashboard.Flow.GenerateMigration.Desc'),
icon: FaBolt,
color: 'bg-yellow-600',
href: ROUTES_ENUM.protected.saas.developerKit.migrations,
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: FaCheckCircle,
color: 'bg-green-600',
href: ROUTES_ENUM.protected.saas.developerKit.migrations,
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: FaServer,
color: 'bg-emerald-600',
href: ROUTES_ENUM.protected.saas.developerKit.endpoints,
status: 'ready',
},
{
step: 5,
title: translate('::App.DeveloperKit.Dashboard.Flow.BuildComponent'),
description: translate('::App.DeveloperKit.Dashboard.Flow.BuildComponent.Desc'),
icon: FaPuzzlePiece,
color: 'bg-purple-600',
href: ROUTES_ENUM.protected.saas.developerKit.componentsNew,
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: FaCode,
},
{
name: translate('::App.DeveloperKit.Dashboard.SystemHealth.Backend'),
status: isOnline
? translate('::App.DeveloperKit.Dashboard.SystemHealth.Healthy')
: translate('::App.DeveloperKit.Dashboard.SystemHealth.Offline'),
icon: FaServer,
},
{
name: translate('::App.DeveloperKit.Dashboard.SystemHealth.Database'),
status: isOnline
? translate('::App.DeveloperKit.Dashboard.SystemHealth.Healthy')
: translate('::App.DeveloperKit.Dashboard.SystemHealth.Unknown'),
icon: FaDatabase,
},
{
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: FaBolt,
},
]
return (
<div className="space-y-4">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-slate-900">
{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 ? (
<>
<FaWifi className="w-4 h-4" />
{translate('::App.DeveloperKit.Dashboard.SystemHealth.Online')}
</>
) : (
<>
<FaWindowClose 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-4">
{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-4 hover:shadow-md transition-all duration-200 group"
>
<div className="flex items-center justify-between">
<div
className={`${stat.bgColor} ${stat.color} p-3 rounded-lg group-hover:scale-110 transition-transform`}
>
<Icon className="w-6 h-6" />
</div>
<FaArrowRight 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-4">
<div className="flex items-center gap-2 mb-4">
<FaCog 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-8">
{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-4 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-xs 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">
<FaExclamationCircle 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">
<FaArrowRight className="w-6 h-6 text-slate-300" />
</div>
)}
</Link>
)
})}
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
{/* System Health */}
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4">
<div className="flex items-center gap-2 mb-4">
<FaCog 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.developerKit.entities}
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.developerKit.entitiesEdit.replace(
':id',
entity.id,
)}
className="text-blue-600 hover:text-blue-700"
>
<FaDatabase className="w-4 h-4" />
</Link>
</div>
</div>
))
) : (
<div className="text-center py-8">
<FaDatabase 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.developerKit.entitiesNew}
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.developerKit.migrations}
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.developerKit.migrations}
className="text-yellow-600 hover:text-yellow-700"
>
<FaBolt className="w-4 h-4" />
</Link>
</div>
</div>
))
) : (
<div className="text-center py-8">
<FaBolt 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.developerKit.entitiesNew}
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.developerKit.endpoints}
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">
<FaServer className="w-4 h-4" />
</Link>
</div>
</div>
))
) : (
<div className="text-center py-8">
<FaServer 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.developerKit.endpointsNew}
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