codenuk_frontend_mine/src/components/admin/admin-templates-list.tsx

683 lines
24 KiB
TypeScript

"use client"
import { useState, useEffect } from 'react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import {
Search,
Settings,
RefreshCw,
AlertCircle,
Zap,
Globe,
BarChart3,
Code,
ShoppingCart,
Briefcase,
GraduationCap,
Plus,
Save,
X
} from 'lucide-react'
import { adminApi, formatDate, getComplexityColor } from '@/lib/api/admin'
import { getApiUrl } from '@/config/backend'
import { AdminTemplate, AdminStats } from '@/types/admin.types'
import { AdminFeatureSelection } from './admin-feature-selection'
interface AdminTemplatesListProps {
onTemplateSelect?: (template: AdminTemplate) => void
}
export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps) {
const [templates, setTemplates] = useState<AdminTemplate[]>([])
const [stats, setStats] = useState<AdminStats | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const [categoryFilter, setCategoryFilter] = useState<string>('all')
const [showFeaturesManager, setShowFeaturesManager] = useState(false)
const [selectedTemplate, setSelectedTemplate] = useState<any | null>(null)
const [showFeatureSelection, setShowFeatureSelection] = useState(false)
const [showCreateModal, setShowCreateModal] = useState(false)
// Create template form state
const [newTemplate, setNewTemplate] = useState({
type: '',
title: '',
description: '',
category: '',
icon: '',
gradient: '',
border: '',
text: '',
subtext: ''
})
const categories = [
"Food Delivery",
"E-commerce",
"SaaS Platform",
"Mobile App",
"Dashboard",
"CRM System",
"Learning Platform",
"Healthcare",
"Real Estate",
"Travel",
"Entertainment",
"Finance",
"Social Media",
"Marketplace",
"Other"
]
// Load templates and stats
const loadData = async () => {
try {
setLoading(true)
setError(null)
console.log('Loading admin templates data...')
const [templatesResponse, statsResponse] = await Promise.all([
adminApi.getAdminTemplates(50, 0, categoryFilter, searchQuery),
adminApi.getAdminTemplateStats()
])
console.log('Admin templates response:', templatesResponse)
console.log('Admin template stats response:', statsResponse)
setTemplates(templatesResponse || [])
setStats(statsResponse)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load admin templates')
console.error('Error loading admin templates:', err)
} finally {
setLoading(false)
}
}
useEffect(() => {
loadData()
}, [categoryFilter, searchQuery])
// Handle template selection for features
const handleManageFeatures = (template: AdminTemplate) => {
// Convert AdminTemplate to Template format for feature management
const templateForFeatures = {
id: template.id,
title: template.title,
description: template.description,
type: template.type,
category: template.category,
icon: template.icon,
gradient: template.gradient,
border: template.border,
text: template.text,
subtext: template.subtext
}
setSelectedTemplate(templateForFeatures)
setShowFeatureSelection(true)
}
// Handle create template form submission
const handleCreateTemplate = async (e: React.FormEvent) => {
e.preventDefault()
try {
setLoading(true)
// Create template payload
const templateData = {
...newTemplate,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}
// Call API to create template using backend base URL
const response = await fetch(getApiUrl('/api/templates'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(templateData)
})
if (!response.ok) {
throw new Error('Failed to create template')
}
// Reset form and hide it
setNewTemplate({
type: '',
title: '',
description: '',
category: '',
icon: '',
gradient: '',
border: '',
text: '',
subtext: ''
})
setShowCreateModal(false)
// Reload data to show new template
await loadData()
} catch (error) {
console.error('Error creating template:', error)
setError(error instanceof Error ? error.message : 'Failed to create template')
} finally {
setLoading(false)
}
}
// Get category icon
const getCategoryIcon = (category: string) => {
switch (category.toLowerCase()) {
case 'food delivery':
return ShoppingCart
case 'e-commerce':
case 'ecommerce':
return ShoppingCart
case 'saas platform':
return Code
case 'mobile app':
return Code
case 'dashboard':
return BarChart3
case 'crm system':
return Briefcase
case 'learning platform':
return GraduationCap
case 'healthcare':
return AlertCircle
case 'real estate':
return Globe
case 'travel':
return Globe
case 'entertainment':
return Zap
case 'finance':
return BarChart3
case 'social media':
return Zap
case 'marketplace':
return ShoppingCart
default:
return Globe
}
}
// Get category stats for filters
const getCategoryStats = () => {
// Start with "All Templates"
const categoryStats = [
{ id: 'all', name: 'All Templates', count: templates.length, icon: Globe }
]
// Add categories based on your actual categories array
categories.forEach(category => {
if (category !== 'Other') { // Skip 'Other' as it will be handled separately
categoryStats.push({
id: category.toLowerCase().replace(/\s+/g, '-'),
name: category,
count: 0,
icon: getCategoryIcon(category)
})
}
})
// Add 'Other' category at the end
categoryStats.push({
id: 'other',
name: 'Other',
count: 0,
icon: Globe
})
// Count templates by category
templates.forEach(template => {
const templateCategory = template.category
if (templateCategory) {
const categoryItem = categoryStats.find(cat =>
cat.name.toLowerCase() === templateCategory.toLowerCase() ||
cat.id === templateCategory.toLowerCase().replace(/\s+/g, '-')
)
if (categoryItem) {
categoryItem.count++
} else {
// If category doesn't match any predefined category, add to 'Other'
const otherCategory = categoryStats.find(cat => cat.id === 'other')
if (otherCategory) {
otherCategory.count++
}
}
}
})
return categoryStats
}
// Filter templates based on search and category
const filteredTemplates = templates.filter(template => {
const matchesSearch = !searchQuery ||
template.title?.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.type?.toLowerCase().includes(searchQuery.toLowerCase())
const matchesCategory = categoryFilter === 'all' ||
template.category?.toLowerCase() === categoryFilter.toLowerCase() ||
template.category?.toLowerCase().replace(/\s+/g, '-') === categoryFilter ||
(categoryFilter === 'other' && !categories.some(cat =>
cat.toLowerCase() === template.category?.toLowerCase()
))
return matchesSearch && matchesCategory
})
const TemplateCard = ({ template }: { template: AdminTemplate }) => (
<Card className="group hover:shadow-md transition-all bg-gray-900 border-gray-800">
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="space-y-2 flex-1">
<CardTitle className="text-lg text-white group-hover:text-orange-400 transition-colors">
{template.title}
</CardTitle>
<div className="flex items-center space-x-2">
<Badge variant="outline" className="text-xs bg-white/5 border-white/10 text-white/80">
{template.type}
</Badge>
<Badge variant="outline" className="text-xs bg-white/5 border-white/10 text-white/70">
{template.category}
</Badge>
</div>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleManageFeatures(template)}
className="text-green-400 hover:text-green-300 border-green-400/30 hover:border-green-300/50"
>
<Settings className="h-4 w-4 mr-1" />
Features
</Button>
</div>
{template.description && (
<p className="text-white/70 text-sm">{template.description}</p>
)}
</CardHeader>
<CardContent className="space-y-3 text-white/80">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center space-x-1">
<Zap className="h-4 w-4 text-orange-400" />
<span>{(template as any).feature_count || 0} features</span>
</div>
<div className="text-white/60">
{template.created_at && formatDate(template.created_at)}
</div>
</div>
{template.gradient && (
<div className="text-xs text-white/60">
<span className="font-medium">Style:</span> {template.gradient}
</div>
)}
</CardContent>
</Card>
)
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="flex items-center space-x-2">
<RefreshCw className="h-6 w-6 animate-spin text-orange-400" />
<span className="text-white">Loading admin templates...</span>
</div>
</div>
)
}
if (error) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<Card className="w-full max-w-md bg-gray-900 border-gray-800">
<CardContent className="pt-6">
<div className="flex items-center space-x-2 text-red-400">
<AlertCircle className="h-6 w-6" />
<span>Error loading admin templates</span>
</div>
<p className="mt-2 text-sm text-gray-400">{error}</p>
<Button onClick={loadData} className="mt-4 bg-orange-500 text-black hover:bg-orange-600">
<RefreshCw className="h-4 w-4 mr-2" />
Retry
</Button>
</CardContent>
</Card>
</div>
)
}
// Show feature selection view if a template is selected
if (showFeatureSelection && selectedTemplate) {
return (
<AdminFeatureSelection
template={selectedTemplate}
onBack={() => {
setShowFeatureSelection(false)
setSelectedTemplate(null)
}}
/>
)
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-white">Admin Templates</h1>
<p className="text-white/70">Manage features for your templates</p>
</div>
<div className="flex items-center space-x-2">
<Button
onClick={() => setShowCreateModal(true)}
className="bg-green-500 text-black hover:bg-green-600"
>
<Plus className="h-4 w-4 mr-2" />
Create Template
</Button>
<Button onClick={loadData} className="bg-orange-500 text-black hover:bg-orange-600">
<RefreshCw className="h-4 w-4 mr-2" />
Refresh
</Button>
</div>
</div>
{/* Stats Cards */}
{stats && (
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<Card className="bg-gray-900 border-gray-800">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-white">Total Templates</CardTitle>
<Globe className="h-4 w-4 text-white/60" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">{(stats as any).total_templates || templates.length}</div>
</CardContent>
</Card>
<Card className="bg-gray-900 border-gray-800">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-white">Categories</CardTitle>
<BarChart3 className="h-4 w-4 text-white/60" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">{(stats as any).total_categories || categories.length - 1}</div>
</CardContent>
</Card>
<Card className="bg-gray-900 border-gray-800">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-white">Avg Features</CardTitle>
<Zap className="h-4 w-4 text-orange-400" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">
{Math.round((stats as any).avg_features_per_template || 0)}
</div>
</CardContent>
</Card>
<Card className="bg-gray-900 border-gray-800">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-white">With Features</CardTitle>
<Settings className="h-4 w-4 text-green-400" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">{(stats as any).templates_with_features || 0}</div>
</CardContent>
</Card>
</div>
)}
{/* Filters */}
<div className="flex items-center space-x-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder="Search templates..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 bg-white/5 border-white/10 text-white"
/>
</div>
</div>
<Select value={categoryFilter} onValueChange={setCategoryFilter}>
<SelectTrigger className="w-48 bg-white/5 border-white/10 text-white">
<SelectValue placeholder="Filter by category" />
</SelectTrigger>
<SelectContent>
{getCategoryStats().map((category) => (
<SelectItem key={category.id} value={category.id}>
<div className="flex items-center space-x-2">
<category.icon className="h-4 w-4" />
<span>{category.name} ({category.count})</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Category Filters removed; using only dropdown above */}
{/* Create Template Modal */}
<Dialog open={showCreateModal} onOpenChange={setShowCreateModal}>
<DialogContent className="bg-gray-900 border-gray-800 text-white max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-white">Create New Template</DialogTitle>
</DialogHeader>
<form onSubmit={handleCreateTemplate} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-medium text-white">Template Type *</label>
<Input
placeholder="e.g., multi_restaurant_food_delivery"
value={newTemplate.type}
onChange={(e) => setNewTemplate(prev => ({ ...prev, type: e.target.value }))}
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
required
/>
<p className="text-xs text-white/60">Unique identifier for the template</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-white">Title *</label>
<Input
placeholder="e.g., Multi-Restaurant Food Delivery App"
value={newTemplate.title}
onChange={(e) => setNewTemplate(prev => ({ ...prev, title: e.target.value }))}
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
required
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-white">Description *</label>
<textarea
placeholder="Describe your template and its key features..."
value={newTemplate.description}
onChange={(e) => setNewTemplate(prev => ({ ...prev, description: e.target.value }))}
className="w-full px-3 py-2 bg-white/5 border border-white/10 text-white placeholder:text-white/40 rounded-md min-h-[100px]"
required
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-medium text-white">Category *</label>
<Select value={newTemplate.category} onValueChange={(value) => setNewTemplate(prev => ({ ...prev, category: value }))}>
<SelectTrigger className="bg-white/5 border-white/10 text-white">
<SelectValue placeholder="Select a category" />
</SelectTrigger>
<SelectContent className="bg-gray-900 border-white/10">
{categories.map((category, index) => (
<SelectItem key={`category-${index}`} value={category} className="text-white">
{category}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-white">Icon (optional)</label>
<Input
placeholder="e.g., restaurant, shopping-cart, users"
value={newTemplate.icon}
onChange={(e) => setNewTemplate(prev => ({ ...prev, icon: e.target.value }))}
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<label className="text-sm font-medium text-white">Gradient (optional)</label>
<Input
placeholder="e.g., from-orange-400 to-red-500"
value={newTemplate.gradient}
onChange={(e) => setNewTemplate(prev => ({ ...prev, gradient: e.target.value }))}
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-white">Border (optional)</label>
<Input
placeholder="e.g., border-orange-500"
value={newTemplate.border}
onChange={(e) => setNewTemplate(prev => ({ ...prev, border: e.target.value }))}
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-white">Text Color (optional)</label>
<Input
placeholder="e.g., text-orange-500"
value={newTemplate.text}
onChange={(e) => setNewTemplate(prev => ({ ...prev, text: e.target.value }))}
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-white">Subtext (optional)</label>
<Input
placeholder="e.g., Perfect for food delivery startups"
value={newTemplate.subtext}
onChange={(e) => setNewTemplate(prev => ({ ...prev, subtext: e.target.value }))}
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
/>
</div>
<div className="flex justify-end space-x-3 pt-4">
<Button
type="button"
variant="outline"
onClick={() => setShowCreateModal(false)}
className="border-white/20 text-white hover:bg-white/10 cursor-pointer"
disabled={loading}
>
<X className="mr-2 h-4 w-4" />
Cancel
</Button>
<Button
type="submit"
className="bg-orange-500 hover:bg-orange-400 text-black font-semibold cursor-pointer"
disabled={loading}
>
<Save className="mr-2 h-4 w-4" />
{loading ? "Creating..." : "Create Template"}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
{/* Feature Selection View */}
{showFeatureSelection && selectedTemplate && (
<AdminFeatureSelection
template={selectedTemplate}
onBack={() => {
setShowFeatureSelection(false)
setSelectedTemplate(null)
}}
/>
)}
{/* Features Manager Modal */}
{showFeaturesManager && selectedTemplate && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<div className="bg-gray-900 rounded-lg max-w-6xl w-full max-h-[90vh] overflow-y-auto">
<div className="p-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold text-white">Manage Features - {selectedTemplate.title}</h2>
<Button
onClick={() => {
setShowFeaturesManager(false)
setSelectedTemplate(null)
}}
variant="outline"
className="text-white border-gray-600 hover:bg-gray-800"
>
Close
</Button>
</div>
<AdminFeatureSelection
template={selectedTemplate}
onBack={() => {
setShowFeaturesManager(false)
setSelectedTemplate(null)
}}
/>
</div>
</div>
</div>
)}
{/* Templates Grid */}
<div className="space-y-4">
{filteredTemplates.length === 0 ? (
<Card className="bg-gray-900 border-gray-800">
<CardContent className="pt-6">
<div className="text-center text-gray-400">
<Globe className="h-12 w-12 mx-auto mb-4 text-gray-600" />
<p>No templates found</p>
<p className="text-sm">Try adjusting your search or filters.</p>
</div>
</CardContent>
</Card>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredTemplates.map((template) => (
<TemplateCard key={template.id} template={template} />
))}
</div>
)}
</div>
</div>
)
}