2025-08-21 14:57:00 +00:00
import React , { useState } from 'react'
import { Link } from 'react-router-dom'
import { useEntities } from '../../contexts/EntityContext'
2025-08-16 19:47:24 +00:00
import {
FaPlus ,
FaSearch ,
FaEdit ,
FaTrashAlt ,
FaEye ,
FaEyeSlash ,
FaFilter ,
FaCalendarAlt ,
FaDatabase ,
FaCheckCircle ,
FaTable ,
2025-08-21 14:57:00 +00:00
FaBolt ,
} from 'react-icons/fa'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
2025-08-11 06:34:44 +00:00
const EntityManager : React.FC = ( ) = > {
2025-08-21 14:57:00 +00:00
const { entities , deleteEntity , toggleEntityActiveStatus } = useEntities ( )
const [ searchTerm , setSearchTerm ] = useState ( '' )
const [ filterActive , setFilterActive ] = useState < 'all' | 'active' | 'inactive' > ( 'all' )
2025-08-11 06:34:44 +00:00
const { translate } = useLocalization ( )
2025-08-21 14:57:00 +00:00
const filteredEntities = entities . filter ( ( entity ) = > {
const matchesSearch =
entity . name . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
entity . displayName . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
( entity . description || '' ) . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
2025-08-11 06:34:44 +00:00
2025-08-21 14:57:00 +00:00
const matchesActiveFilter =
filterActive === 'all' ||
( filterActive === 'active' && entity . isActive ) ||
( filterActive === 'inactive' && ! entity . isActive )
return matchesSearch && matchesActiveFilter
} )
2025-08-11 06:34:44 +00:00
const handleToggleActive = async ( id : string ) = > {
try {
2025-08-21 14:57:00 +00:00
await toggleEntityActiveStatus ( id )
2025-08-11 06:34:44 +00:00
} catch ( err ) {
2025-08-21 14:57:00 +00:00
console . error ( 'Failed to toggle entity status:' , err )
2025-08-11 06:34:44 +00:00
}
2025-08-21 14:57:00 +00:00
}
2025-08-11 06:34:44 +00:00
const handleDelete = async ( id : string , name : string ) = > {
2025-08-21 14:57:00 +00:00
if (
window . confirm (
` Are you sure you want to delete the " ${ name } " entity? \ n \ nThis action will permanently remove: \ n• The entity and all its fields \ n• All related migrations \ n• All related API endpoints \ n \ nThis cannot be undone. ` ,
)
) {
2025-08-11 06:34:44 +00:00
try {
2025-08-21 14:57:00 +00:00
await deleteEntity ( id )
2025-08-11 06:34:44 +00:00
} catch ( err ) {
2025-08-21 14:57:00 +00:00
console . error ( 'Failed to delete entity:' , err )
alert ( translate ( '::App.DeveloperKit.Entity.DeleteFailed' ) )
2025-08-11 06:34:44 +00:00
}
}
2025-08-21 14:57:00 +00:00
}
2025-08-11 06:34:44 +00:00
const stats = {
total : entities.length ,
2025-08-21 14:57:00 +00:00
active : entities.filter ( ( e ) = > e . isActive ) . length ,
migrationsPending : entities.filter ( ( e ) = > e . migrationStatus === 'pending' ) . length ,
endpointsApplied : entities.filter ( ( e ) = > e . endpointStatus === 'applied' ) . length ,
}
2025-08-11 06:34:44 +00:00
return (
< div className = "space-y-8" >
< div className = "flex items-center justify-between mb-8" >
< div >
2025-08-21 14:57:00 +00:00
< h1 className = "text-3xl font-bold text-slate-900 mb-2" >
{ translate ( '::App.DeveloperKit.Entity.Title' ) }
< / h1 >
< p className = "text-slate-600" > { translate ( '::App.DeveloperKit.Entity.Description' ) } < / p >
2025-08-11 06:34:44 +00:00
< / div >
< Link
2025-08-21 14:57:00 +00:00
to = { ROUTES_ENUM . protected . saas . developerKit . entitiesNew }
2025-08-11 06:34:44 +00:00
className = "flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 transition-colors shadow-sm hover:shadow-md"
>
2025-08-16 19:47:24 +00:00
< FaPlus className = "w-4 h-4" / >
2025-08-11 06:34:44 +00:00
{ translate ( '::App.DeveloperKit.Entity.NewEntity' ) }
< / Link >
< / div >
{ /* Stats Cards */ }
< div className = "grid grid-cols-1 md:grid-cols-4 gap-6 mb-8" >
< div className = "bg-white rounded-xl shadow-sm border border-slate-200 p-6" >
< div className = "flex items-center justify-between" >
< div >
2025-08-21 14:57:00 +00:00
< p className = "text-sm font-medium text-slate-600" >
{ translate ( '::App.DeveloperKit.Entity.TotalEntities' ) }
< / p >
2025-08-11 06:34:44 +00:00
< p className = "text-2xl font-bold text-slate-900 mt-1" > { stats . total } < / p >
< / div >
< div className = "bg-blue-100 text-blue-600 p-3 rounded-lg" >
2025-08-16 19:47:24 +00:00
< FaDatabase className = "w-6 h-6" / >
2025-08-11 06:34:44 +00:00
< / div >
< / div >
< / div >
< div className = "bg-white rounded-xl shadow-sm border border-slate-200 p-6" >
< div className = "flex items-center justify-between" >
< div >
2025-08-21 14:57:00 +00:00
< p className = "text-sm font-medium text-slate-600" >
{ translate ( '::App.DeveloperKit.Entity.ActiveEntities' ) }
< / p >
2025-08-11 06:34:44 +00:00
< p className = "text-2xl font-bold text-slate-900 mt-1" >
{ stats . active }
2025-08-21 14:57:00 +00:00
< span className = "text-sm font-normal text-slate-500 ml-1" > / { stats . total } < / span >
2025-08-11 06:34:44 +00:00
< / p >
< / div >
< div className = "bg-green-100 text-green-600 p-3 rounded-lg" >
2025-08-16 19:47:24 +00:00
< FaCheckCircle className = "w-6 h-6" / >
2025-08-11 06:34:44 +00:00
< / div >
< / div >
< / div >
< div className = "bg-white rounded-xl shadow-sm border border-slate-200 p-6" >
< div className = "flex items-center justify-between" >
< div >
2025-08-21 14:57:00 +00:00
< p className = "text-sm font-medium text-slate-600" >
{ translate ( '::App.DeveloperKit.Entity.PendingMigrations' ) }
< / p >
2025-08-11 06:34:44 +00:00
< p className = "text-2xl font-bold text-slate-900 mt-1" > { stats . migrationsPending } < / p >
< / div >
< div className = "bg-purple-100 text-purple-600 p-3 rounded-lg" >
2025-08-16 19:47:24 +00:00
< FaBolt className = "w-6 h-6" / >
2025-08-11 06:34:44 +00:00
< / div >
< / div >
< / div >
< div className = "bg-white rounded-xl shadow-sm border border-slate-200 p-6" >
< div className = "flex items-center justify-between" >
< div >
2025-08-21 14:57:00 +00:00
< p className = "text-sm font-medium text-slate-600" >
{ translate ( '::App.DeveloperKit.Entity.CrudEndpoints' ) }
< / p >
2025-08-11 06:34:44 +00:00
< p className = "text-2xl font-bold text-slate-900 mt-1" > { stats . endpointsApplied } < / p >
< / div >
< div className = "bg-emerald-100 text-emerald-600 p-3 rounded-lg" >
2025-08-16 19:47:24 +00:00
< FaBolt className = "w-6 h-6" / >
2025-08-11 06:34:44 +00:00
< / div >
< / div >
< / div >
< / div >
{ /* Filters */ }
< div className = "bg-white rounded-lg border border-slate-200 p-6 mb-6 shadow-sm" >
< div className = "flex flex-col lg:flex-row gap-4" >
< div className = "flex-1 relative" >
2025-08-16 19:47:24 +00:00
< FaSearch className = "absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-400" / >
2025-08-11 06:34:44 +00:00
< input
type = "text"
placeholder = { translate ( '::App.DeveloperKit.Entity.SearchPlaceholder' ) }
value = { searchTerm }
onChange = { ( e ) = > setSearchTerm ( e . target . value ) }
className = "w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent transition-colors"
/ >
< / div >
< div className = "flex items-center gap-4" >
< div className = "flex items-center gap-2" >
2025-08-16 19:47:24 +00:00
< FaFilter className = "w-5 h-5 text-slate-500" / >
2025-08-11 06:34:44 +00:00
< select
value = { filterActive }
onChange = { ( e ) = > setFilterActive ( e . target . value as 'all' | 'active' | 'inactive' ) }
className = "px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent transition-colors"
>
< option value = "all" > { translate ( '::App.DeveloperKit.Entity.Filter.All' ) } < / option >
2025-08-21 14:57:00 +00:00
< option value = "active" >
{ translate ( '::App.DeveloperKit.Entity.Filter.Active' ) }
< / option >
< option value = "inactive" >
{ translate ( '::App.DeveloperKit.Entity.Filter.Inactive' ) }
< / option >
2025-08-11 06:34:44 +00:00
< / select >
< / div >
< / div >
< / div >
< / div >
{ /* Entities List */ }
{ filteredEntities . length > 0 ? (
< div className = "grid grid-cols-1 xl:grid-cols-2 gap-6" >
{ filteredEntities . map ( ( entity ) = > {
return (
2025-08-21 14:57:00 +00:00
< div
key = { entity . id }
className = "bg-white rounded-lg border border-slate-200 shadow-sm hover:shadow-md transition-all duration-200 group"
>
2025-08-11 06:34:44 +00:00
< div className = "p-6" >
< div className = "flex items-start justify-between mb-4" >
< div className = "flex-1" >
< div className = "flex items-center gap-3 mb-2" >
< div className = "bg-blue-100 text-blue-600 p-2 rounded-lg" >
2025-08-16 19:47:24 +00:00
< FaTable className = "w-5 h-5" / >
2025-08-11 06:34:44 +00:00
< / div >
< div >
< h3 className = "text-lg font-semibold text-slate-900" >
{ entity . displayName }
< / h3 >
2025-08-21 14:57:00 +00:00
< p className = "text-sm text-slate-500" >
{ translate ( '::App.DeveloperKit.Entity.TableLabel' ) } : { entity . tableName }
< / p >
2025-08-11 06:34:44 +00:00
< / div >
< / div >
{ entity . description && (
2025-08-21 14:57:00 +00:00
< p className = "text-slate-600 text-sm mb-3" > { entity . description } < / p >
2025-08-11 06:34:44 +00:00
) }
< div className = "flex items-center gap-4 text-xs text-slate-500 mb-3" >
< div className = "flex items-center gap-1" >
2025-08-16 19:47:24 +00:00
< FaCalendarAlt className = "w-3 h-3" / >
2025-08-11 06:34:44 +00:00
< span >
2025-08-21 14:57:00 +00:00
{ translate ( '::App.DeveloperKit.Entity.Updated' ) } : { ' ' }
{ entity . lastModificationTime
? new Date ( entity . lastModificationTime ) . toLocaleDateString ( )
: 'Never' }
2025-08-11 06:34:44 +00:00
< / span >
< / div >
< div className = "flex items-center gap-1" >
2025-08-16 19:47:24 +00:00
< FaDatabase className = "w-3 h-3" / >
2025-08-21 14:57:00 +00:00
< span >
{ entity . fields . length } { translate ( '::App.DeveloperKit.Entity.Fields' ) }
< / span >
2025-08-11 06:34:44 +00:00
< / div >
< / div >
< / div >
< / div >
{ /* Entity Fields Preview */ }
< div className = "mb-4" >
< div className = "bg-slate-50 rounded-lg p-3" >
2025-08-21 14:57:00 +00:00
< h4 className = "text-sm font-medium text-slate-700 mb-2" >
{ translate ( '::App.DeveloperKit.Entity.FieldsLabel' ) } :
< / h4 >
2025-08-11 06:34:44 +00:00
< div className = "grid grid-cols-2 gap-2 text-xs" >
2025-08-21 14:57:00 +00:00
{ entity . fields . slice ( 0 , 4 ) . map ( ( field ) = > (
2025-08-11 06:34:44 +00:00
< div key = { field . id } className = "flex items-center gap-2" >
2025-08-21 14:57:00 +00:00
< span
className = { ` px-2 py-1 rounded text-xs font-medium ${
field . type === 'string'
? 'bg-blue-100 text-blue-700'
: field . type === 'number' || field . type === 'decimal'
? 'bg-green-100 text-green-700'
: field . type === 'boolean'
? 'bg-purple-100 text-purple-700'
: field . type === 'date'
? 'bg-orange-100 text-orange-700'
: 'bg-slate-100 text-slate-700'
} ` }
>
2025-08-11 06:34:44 +00:00
{ field . type }
< / span >
< span className = "text-slate-600" > { field . name } < / span >
{ field . isRequired && < span className = "text-red-500" > * < / span > }
< / div >
) ) }
{ entity . fields . length > 4 && (
< div className = "text-slate-500 italic" >
2025-08-21 14:57:00 +00:00
+ { entity . fields . length - 4 } { ' ' }
{ translate ( '::App.DeveloperKit.Entity.More' ) }
2025-08-11 06:34:44 +00:00
< / div >
) }
< / div >
< / div >
< / div >
{ /* Migration and Endpoint Status */ }
< div className = "mb-4 space-y-3" >
< div className = "flex items-center justify-between" >
2025-08-21 14:57:00 +00:00
< span className = "text-sm font-medium text-slate-700" >
{ translate ( '::App.DeveloperKit.Entity.MigrationStatus' ) }
< / span >
< span
className = { ` text-xs px-2 py-1 rounded-full ${
entity . migrationStatus === 'applied'
? 'bg-green-100 text-green-700'
: entity . migrationStatus === 'pending'
? 'bg-yellow-100 text-yellow-700'
: 'bg-red-100 text-red-700'
} ` }
>
2025-08-11 06:34:44 +00:00
{ entity . migrationStatus }
< / span >
< / div >
< div className = "flex items-center justify-between" >
2025-08-21 14:57:00 +00:00
< span className = "text-sm font-medium text-slate-700" >
{ translate ( '::App.DeveloperKit.Entity.EndpointStatus' ) }
< / span >
< span
className = { ` text-xs px-2 py-1 rounded-full ${
entity . endpointStatus === 'applied'
? 'bg-blue-100 text-blue-700'
: entity . endpointStatus === 'pending'
? 'bg-orange-100 text-orange-700'
: 'bg-red-100 text-red-700'
} ` }
>
2025-08-11 06:34:44 +00:00
{ entity . endpointStatus }
< / span >
< / div >
< / div >
{ /* Actions */ }
< div className = "flex items-center justify-between pt-4 border-t border-slate-100" >
< div className = "flex items-center gap-2" >
< button
onClick = { ( ) = > handleToggleActive ( entity . id ) }
className = { ` flex items-center gap-1 px-3 py-1 rounded-full text-xs font-medium transition-colors ${
entity . isActive
? 'bg-green-100 text-green-700 hover:bg-green-200'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
} ` }
>
{ entity . isActive ? (
< >
2025-08-16 19:47:24 +00:00
< FaEye className = "w-3 h-3" / >
2025-08-11 06:34:44 +00:00
{ translate ( '::App.DeveloperKit.Entity.Active' ) }
< / >
) : (
< >
2025-08-16 19:47:24 +00:00
< FaEyeSlash className = "w-3 h-3" / >
2025-08-11 06:34:44 +00:00
{ translate ( '::App.DeveloperKit.Entity.Inactive' ) }
< / >
) }
< / button >
< / div >
< div className = "flex items-center gap-1" >
< Link
2025-08-21 14:57:00 +00:00
to = { ROUTES_ENUM . protected . saas . developerKit . entitiesEdit . replace (
':id' ,
entity . id ,
) }
2025-08-11 06:34:44 +00:00
className = "p-2 text-slate-600 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
title = { translate ( '::App.DeveloperKit.Entity.Edit' ) }
>
2025-08-16 19:47:24 +00:00
< FaEdit className = "w-4 h-4" / >
2025-08-11 06:34:44 +00:00
< / Link >
< button
onClick = { ( ) = > handleDelete ( entity . id , entity . displayName ) }
className = "p-2 text-slate-600 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
title = { translate ( '::App.DeveloperKit.Entity.Delete' ) }
>
2025-08-16 19:47:24 +00:00
< FaTrashAlt className = "w-4 h-4" / >
2025-08-11 06:34:44 +00:00
< / button >
< / div >
< / div >
< / div >
< / div >
2025-08-21 14:57:00 +00:00
)
2025-08-11 06:34:44 +00:00
} ) }
< / div >
) : (
< div className = "text-center py-12" >
< div className = "max-w-md mx-auto" >
< div className = "bg-slate-100 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4" >
2025-08-16 19:47:24 +00:00
< FaDatabase className = "w-8 h-8 text-slate-500" / >
2025-08-11 06:34:44 +00:00
< / div >
< h3 className = "text-lg font-medium text-slate-900 mb-2" >
2025-08-21 14:57:00 +00:00
{ searchTerm || filterActive !== 'all'
? translate ( '::App.DeveloperKit.Entity.NoEntitiesFound' )
: translate ( '::App.DeveloperKit.Entity.NoEntitiesYet' ) }
2025-08-11 06:34:44 +00:00
< / h3 >
< p className = "text-slate-600 mb-6" >
{ searchTerm || filterActive !== 'all'
? translate ( '::App.DeveloperKit.Entity.TryAdjustingFilters' )
2025-08-21 14:57:00 +00:00
: translate ( '::App.DeveloperKit.Entity.CreateFirstEntity' ) }
2025-08-11 06:34:44 +00:00
< / p >
2025-08-21 14:57:00 +00:00
{ ! searchTerm && filterActive === 'all' && (
2025-08-11 06:34:44 +00:00
< Link
to = { ROUTES_ENUM . protected . saas . developerKitEntitiesNew }
className = "inline-flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 transition-colors shadow-sm hover:shadow-md"
>
2025-08-16 19:47:24 +00:00
< FaPlus className = "w-4 h-4" / >
2025-08-11 06:34:44 +00:00
{ translate ( '::App.DeveloperKit.Entity.CreateEntity' ) }
< / Link >
) }
< / div >
< / div >
) }
< / div >
2025-08-21 14:57:00 +00:00
)
}
2025-08-11 06:34:44 +00:00
2025-08-21 14:57:00 +00:00
export default EntityManager