frontend changes

This commit is contained in:
Chandini 2025-09-12 17:48:47 +05:30
parent 298e9c2170
commit f0cc077b63
3 changed files with 118 additions and 43 deletions

View File

@ -3,6 +3,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Checkbox } from '@/components/ui/checkbox' 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 [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
const [showAIModal, setShowAIModal] = useState(false) const [showAIModal, setShowAIModal] = useState(false)
const [editingFeature, setEditingFeature] = useState<TemplateFeature | null>(null)
const load = async () => { const load = async () => {
try { try {
@ -169,6 +171,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
try { try {
await updateFeature(template.id, f.id, updates) await updateFeature(template.id, f.id, updates)
await load() await load()
setEditingFeature(null)
} catch (err) { } catch (err) {
console.error('Error updating feature:', err) console.error('Error updating feature:', err)
const message = err instanceof Error ? err.message : 'Failed to update feature' 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) => { const handleDelete = async (f: TemplateFeature) => {
try { try {
if (!confirm(`Delete feature "${f.name}"? This cannot be undone.`)) return
await deleteFeature(template.id, f.id) await deleteFeature(template.id, f.id)
setSelectedIds((prev) => { setSelectedIds((prev) => {
const next = new Set(prev) const next = new Set(prev)
@ -230,13 +234,12 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
/> />
<span>{f.name}</span> <span>{f.name}</span>
</div> </div>
{f.feature_type === 'custom' && (
<div className="flex gap-1"> <div className="flex gap-1">
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
className="border-blue-500 text-blue-300 hover:bg-blue-500/10" 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" /> <Edit className="h-3 w-3" />
</Button> </Button>
@ -244,12 +247,11 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
size="sm" size="sm"
variant="outline" variant="outline"
className="border-red-500 text-red-300 hover:bg-red-500/10" 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" /> <Trash2 className="h-3 w-3" />
</Button> </Button>
</div> </div>
)}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="text-white/80 text-sm space-y-2"> <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)} {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 && ( {showAIModal && (
<AICustomFeatureCreator <AICustomFeatureCreator
projectType={template.type || template.title} projectType={template.type || template.title}

View File

@ -313,7 +313,7 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
}) })
const MAX_TITLE_CHARS = 25 const MAX_TITLE_CHARS = 25
const MAX_DESCRIPTION_PREVIEW_CHARS = 220 const MAX_DESCRIPTION_PREVIEW_CHARS = 90
const TemplateCard = ({ template }: { template: AdminTemplate }) => { const TemplateCard = ({ template }: { template: AdminTemplate }) => {
const [descExpanded, setDescExpanded] = useState(false) 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) } }} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleManageFeatures(template) } }}
> >
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between gap-3">
<div className="space-y-2 flex-1"> <div className="space-y-2 flex-1 min-w-0">
<CardTitle className="text-lg text-white group-hover:text-orange-400 transition-colors"> <CardTitle className="text-lg text-white group-hover:text-orange-400 transition-colors">
<Tooltip content={title}> <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> </Tooltip>
</CardTitle> </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"> <Badge variant="outline" className="text-xs bg-white/5 border-white/10 text-white/80">
{template.type} {template.type}
</Badge> </Badge>
@ -351,7 +351,7 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
</Badge> </Badge>
</div> </div>
</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 <Button
variant="outline" variant="outline"
size="sm" size="sm"
@ -373,7 +373,7 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
</div> </div>
</div> </div>
{fullDesc && ( {fullDesc && (
<div className="text-white/70 text-sm"> <div className="text-white/70 text-sm break-words hyphens-auto">
<span>{shownDesc}</span> <span>{shownDesc}</span>
{needsClamp && ( {needsClamp && (
<button <button
@ -695,7 +695,7 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
{/* Edit Template Modal */} {/* Edit Template Modal */}
<Dialog open={showEditModal} onOpenChange={setShowEditModal}> <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> <DialogHeader>
<DialogTitle className="text-white">Edit Template</DialogTitle> <DialogTitle className="text-white">Edit Template</DialogTitle>
</DialogHeader> </DialogHeader>
@ -704,12 +704,18 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
className="space-y-4" className="space-y-4"
onSubmit={async (e) => { onSubmit={async (e) => {
e.preventDefault() e.preventDefault()
const formData = new FormData(e.currentTarget as HTMLFormElement) const form = e.currentTarget as HTMLFormElement
const formData = new FormData(form)
const payload = { const payload = {
title: String(formData.get('title') || ''), title: String(formData.get('title') || ''),
description: String(formData.get('description') || ''), description: String(formData.get('description') || ''),
category: String(formData.get('category') || ''), category: String(formData.get('category') || ''),
type: String(formData.get('type') || ''), 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 { try {
await adminApi.updateAdminTemplate(editingTemplate.id, payload) 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="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm">Category</label> <label className="text-sm font-medium text-white">Title *</label>
<Input name="category" defaultValue={editingTemplate.category || ''} className="bg-white/5 border-white/10 text-white" /> <Input name="title" defaultValue={editingTemplate.title || ''} className="bg-white/5 border-white/10 text-white" required />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm">Type</label> <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" /> <Input name="type" defaultValue={editingTemplate.type || ''} className="bg-white/5 border-white/10 text-white" required />
</div> </div>
</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"> <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="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> <Button type="submit" className="bg-orange-500 text-black hover:bg-orange-600">Save</Button>

View File

@ -411,7 +411,8 @@ export const adminApi = {
subtext?: string; subtext?: string;
} }
): Promise<AdminTemplate> => { ): 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', method: 'PUT',
body: JSON.stringify(updateData), body: JSON.stringify(updateData),
}); });
@ -420,7 +421,8 @@ export const adminApi = {
// Delete an admin template (main templates table) // Delete an admin template (main templates table)
deleteAdminTemplate: async (templateId: string): Promise<{ success: boolean }> => { 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', method: 'DELETE',
}); });
return response.data; return response.data;