frontend changes
This commit is contained in:
parent
298e9c2170
commit
f0cc077b63
@ -3,6 +3,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
@ -84,6 +85,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
|
||||
})
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
|
||||
const [showAIModal, setShowAIModal] = useState(false)
|
||||
const [editingFeature, setEditingFeature] = useState<TemplateFeature | null>(null)
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
@ -169,6 +171,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
|
||||
try {
|
||||
await updateFeature(template.id, f.id, updates)
|
||||
await load()
|
||||
setEditingFeature(null)
|
||||
} catch (err) {
|
||||
console.error('Error updating feature:', err)
|
||||
const message = err instanceof Error ? err.message : 'Failed to update feature'
|
||||
@ -178,6 +181,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
|
||||
|
||||
const handleDelete = async (f: TemplateFeature) => {
|
||||
try {
|
||||
if (!confirm(`Delete feature "${f.name}"? This cannot be undone.`)) return
|
||||
await deleteFeature(template.id, f.id)
|
||||
setSelectedIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
@ -230,13 +234,12 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
|
||||
/>
|
||||
<span>{f.name}</span>
|
||||
</div>
|
||||
{f.feature_type === 'custom' && (
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="border-blue-500 text-blue-300 hover:bg-blue-500/10"
|
||||
onClick={() => {/* Edit functionality */}}
|
||||
onClick={(e) => { e.stopPropagation(); setEditingFeature(f) }}
|
||||
>
|
||||
<Edit className="h-3 w-3" />
|
||||
</Button>
|
||||
@ -244,12 +247,11 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="border-red-500 text-red-300 hover:bg-red-500/10"
|
||||
onClick={() => handleDelete(f)}
|
||||
onClick={(e) => { e.stopPropagation(); handleDelete(f) }}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-white/80 text-sm space-y-2">
|
||||
@ -289,6 +291,27 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
|
||||
|
||||
{section('Your Custom Features', customFeatures)}
|
||||
|
||||
{/* Edit Feature Modal reusing AI-Powered Feature Creator */}
|
||||
{editingFeature && (
|
||||
<AICustomFeatureCreator
|
||||
projectType={template.type || template.title}
|
||||
editingFeature={editingFeature as any}
|
||||
onAdd={async (payload) => {
|
||||
await handleUpdate(editingFeature, {
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
complexity: payload.complexity,
|
||||
// Map AI form structure to backend fields
|
||||
business_rules: payload.business_rules,
|
||||
// Flatten logic rules to store if backend supports it on update
|
||||
logic_rules: payload.logic_rules,
|
||||
} as any)
|
||||
setEditingFeature(null)
|
||||
}}
|
||||
onClose={() => setEditingFeature(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showAIModal && (
|
||||
<AICustomFeatureCreator
|
||||
projectType={template.type || template.title}
|
||||
|
||||
@ -313,7 +313,7 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
|
||||
})
|
||||
|
||||
const MAX_TITLE_CHARS = 25
|
||||
const MAX_DESCRIPTION_PREVIEW_CHARS = 220
|
||||
const MAX_DESCRIPTION_PREVIEW_CHARS = 90
|
||||
|
||||
const TemplateCard = ({ template }: { template: AdminTemplate }) => {
|
||||
const [descExpanded, setDescExpanded] = useState(false)
|
||||
@ -335,14 +335,14 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleManageFeatures(template) } }}
|
||||
>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-2 flex-1">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="space-y-2 flex-1 min-w-0">
|
||||
<CardTitle className="text-lg text-white group-hover:text-orange-400 transition-colors">
|
||||
<Tooltip content={title}>
|
||||
<span title={undefined}>{truncatedTitle}</span>
|
||||
<span title={undefined} className="block max-w-full overflow-hidden text-ellipsis whitespace-nowrap">{truncatedTitle}</span>
|
||||
</Tooltip>
|
||||
</CardTitle>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant="outline" className="text-xs bg-white/5 border-white/10 text-white/80">
|
||||
{template.type}
|
||||
</Badge>
|
||||
@ -351,7 +351,7 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex items-center space-x-2 shrink-0" onClick={(e) => e.stopPropagation()}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@ -373,7 +373,7 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
|
||||
</div>
|
||||
</div>
|
||||
{fullDesc && (
|
||||
<div className="text-white/70 text-sm">
|
||||
<div className="text-white/70 text-sm break-words hyphens-auto">
|
||||
<span>{shownDesc}</span>
|
||||
{needsClamp && (
|
||||
<button
|
||||
@ -695,7 +695,7 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
|
||||
|
||||
{/* Edit Template Modal */}
|
||||
<Dialog open={showEditModal} onOpenChange={setShowEditModal}>
|
||||
<DialogContent className="bg-gray-900 border-gray-800 text-white max-w-xl">
|
||||
<DialogContent className="bg-gray-900 border-gray-800 text-white max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">Edit Template</DialogTitle>
|
||||
</DialogHeader>
|
||||
@ -704,12 +704,18 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
|
||||
className="space-y-4"
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault()
|
||||
const formData = new FormData(e.currentTarget as HTMLFormElement)
|
||||
const form = e.currentTarget as HTMLFormElement
|
||||
const formData = new FormData(form)
|
||||
const payload = {
|
||||
title: String(formData.get('title') || ''),
|
||||
description: String(formData.get('description') || ''),
|
||||
category: String(formData.get('category') || ''),
|
||||
type: String(formData.get('type') || ''),
|
||||
icon: String(formData.get('icon') || ''),
|
||||
gradient: String(formData.get('gradient') || ''),
|
||||
border: String(formData.get('border') || ''),
|
||||
text: String(formData.get('text') || ''),
|
||||
subtext: String(formData.get('subtext') || ''),
|
||||
}
|
||||
try {
|
||||
await adminApi.updateAdminTemplate(editingTemplate.id, payload)
|
||||
@ -722,24 +728,68 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm">Title</label>
|
||||
<Input name="title" defaultValue={editingTemplate.title || ''} className="bg-white/5 border-white/10 text-white" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm">Description</label>
|
||||
<textarea name="description" defaultValue={editingTemplate.description || ''} className="w-full px-3 py-2 bg-white/5 border border-white/10 text-white rounded-md min-h-[80px]" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm">Category</label>
|
||||
<Input name="category" defaultValue={editingTemplate.category || ''} className="bg-white/5 border-white/10 text-white" />
|
||||
<label className="text-sm font-medium text-white">Title *</label>
|
||||
<Input name="title" defaultValue={editingTemplate.title || ''} className="bg-white/5 border-white/10 text-white" required />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm">Type</label>
|
||||
<Input name="type" defaultValue={editingTemplate.type || ''} className="bg-white/5 border-white/10 text-white" />
|
||||
<label className="text-sm font-medium text-white">Type *</label>
|
||||
<Input name="type" defaultValue={editingTemplate.type || ''} className="bg-white/5 border-white/10 text-white" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white">Description *</label>
|
||||
<textarea name="description" defaultValue={editingTemplate.description || ''} className="w-full px-3 py-2 bg-white/5 border border-white/10 text-white 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 defaultValue={editingTemplate.category || ''} onValueChange={(v) => {
|
||||
const input = document.querySelector<HTMLInputElement>('input[name=\'category\']')
|
||||
if (input) input.value = v
|
||||
}}>
|
||||
<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={`edit-category-${index}`} value={category} className="text-white">
|
||||
{category}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<input type="hidden" name="category" defaultValue={editingTemplate.category || ''} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white">Icon</label>
|
||||
<Input name="icon" defaultValue={editingTemplate.icon || ''} className="bg-white/5 border-white/10 text-white" />
|
||||
</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</label>
|
||||
<Input name="gradient" defaultValue={editingTemplate.gradient || ''} className="bg-white/5 border-white/10 text-white" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white">Border</label>
|
||||
<Input name="border" defaultValue={editingTemplate.border || ''} className="bg-white/5 border-white/10 text-white" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white">Text Color</label>
|
||||
<Input name="text" defaultValue={editingTemplate.text || ''} className="bg-white/5 border-white/10 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white">Subtext</label>
|
||||
<Input name="subtext" defaultValue={editingTemplate.subtext || ''} className="bg-white/5 border-white/10 text-white" />
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-2">
|
||||
<Button type="button" variant="outline" onClick={() => setShowEditModal(false)} className="border-white/20 text-white">Cancel</Button>
|
||||
<Button type="submit" className="bg-orange-500 text-black hover:bg-orange-600">Save</Button>
|
||||
|
||||
@ -411,7 +411,8 @@ export const adminApi = {
|
||||
subtext?: string;
|
||||
}
|
||||
): Promise<AdminTemplate> => {
|
||||
const response = await apiCall<AdminTemplate>(`/api/admin/templates/${templateId}`, {
|
||||
// Backend update endpoint is /api/templates/:id (not under /api/admin)
|
||||
const response = await apiCall<AdminTemplate>(`/api/templates/${templateId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(updateData),
|
||||
});
|
||||
@ -420,7 +421,8 @@ export const adminApi = {
|
||||
|
||||
// Delete an admin template (main templates table)
|
||||
deleteAdminTemplate: async (templateId: string): Promise<{ success: boolean }> => {
|
||||
const response = await apiCall<{ success: boolean }>(`/api/admin/templates/${templateId}`, {
|
||||
// Backend delete endpoint is /api/templates/:id
|
||||
const response = await apiCall<{ success: boolean }>(`/api/templates/${templateId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
return response.data;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user