import { useEffect, useState } from 'react' import * as z from 'zod' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { Plus, Edit, Trash2, Save } from 'lucide-react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Switch } from '@/components/ui/switch' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { StatusBadge } from '@/components/status-badge' import { SettingsSection } from '../components/settings-section' import { useUpdateOption } from '../hooks/use-update-option' type ApiInfo = { id: number url: string route: string description: string color: string } type ApiInfoSectionProps = { enabled: boolean data: string } const createApiInfoSchema = (t: (key: string) => string) => z.object({ url: z.string().url(t('Must be a valid URL')), route: z.string().min(1, t('Route is required')), description: z.string().min(1, t('Description is required')), color: z.string().min(1, t('Color is required')), }) type ApiInfoFormValues = z.infer> const colorOptions = [ { value: 'blue', label: 'Blue', bgClass: 'bg-blue-500' }, { value: 'green', label: 'Green', bgClass: 'bg-green-500' }, { value: 'cyan', label: 'Cyan', bgClass: 'bg-cyan-500' }, { value: 'purple', label: 'Purple', bgClass: 'bg-purple-500' }, { value: 'pink', label: 'Pink', bgClass: 'bg-pink-500' }, { value: 'red', label: 'Red', bgClass: 'bg-red-500' }, { value: 'orange', label: 'Orange', bgClass: 'bg-orange-500' }, { value: 'amber', label: 'Amber', bgClass: 'bg-amber-500' }, { value: 'yellow', label: 'Yellow', bgClass: 'bg-yellow-500' }, { value: 'lime', label: 'Lime', bgClass: 'bg-lime-500' }, { value: 'teal', label: 'Teal', bgClass: 'bg-teal-500' }, { value: 'indigo', label: 'Indigo', bgClass: 'bg-indigo-500' }, { value: 'violet', label: 'Violet', bgClass: 'bg-violet-500' }, { value: 'slate', label: 'Slate', bgClass: 'bg-slate-500' }, ] export function ApiInfoSection({ enabled, data }: ApiInfoSectionProps) { const { t } = useTranslation() const updateOption = useUpdateOption() const apiInfoSchema = createApiInfoSchema(t) const [apiInfoList, setApiInfoList] = useState([]) const [isEnabled, setIsEnabled] = useState(enabled) const [hasChanges, setHasChanges] = useState(false) const [selectedIds, setSelectedIds] = useState([]) const [showDialog, setShowDialog] = useState(false) const [showDeleteDialog, setShowDeleteDialog] = useState(false) const [editingApiInfo, setEditingApiInfo] = useState(null) const [deleteTarget, setDeleteTarget] = useState<'single' | 'batch'>('single') const form = useForm({ resolver: zodResolver(apiInfoSchema), defaultValues: { url: '', route: '', description: '', color: 'blue', }, }) useEffect(() => { try { const parsed = JSON.parse(data || '[]') if (Array.isArray(parsed)) { setApiInfoList( parsed.map((item, idx) => ({ ...item, id: item.id || idx + 1, })) ) } } catch { setApiInfoList([]) } }, [data]) useEffect(() => { setIsEnabled(enabled) }, [enabled]) const handleToggleEnabled = async (checked: boolean) => { try { await updateOption.mutateAsync({ key: 'console_setting.api_info_enabled', value: checked, }) setIsEnabled(checked) toast.success(t('Setting saved')) } catch { toast.error(t('Failed to update setting')) } } const handleAdd = () => { setEditingApiInfo(null) form.reset({ url: '', route: '', description: '', color: 'blue', }) setShowDialog(true) } const handleEdit = (apiInfo: ApiInfo) => { setEditingApiInfo(apiInfo) form.reset({ url: apiInfo.url, route: apiInfo.route, description: apiInfo.description, color: apiInfo.color, }) setShowDialog(true) } const handleDelete = (apiInfo: ApiInfo) => { setEditingApiInfo(apiInfo) setDeleteTarget('single') setShowDeleteDialog(true) } const handleBatchDelete = () => { if (selectedIds.length === 0) { toast.error(t('Please select items to delete')) return } setDeleteTarget('batch') setShowDeleteDialog(true) } const confirmDelete = () => { if (deleteTarget === 'single' && editingApiInfo) { setApiInfoList((prev) => prev.filter((item) => item.id !== editingApiInfo.id) ) setHasChanges(true) toast.success(t('API info deleted. Click "Save Settings" to apply.')) } else if (deleteTarget === 'batch') { setApiInfoList((prev) => prev.filter((item) => !selectedIds.includes(item.id)) ) setSelectedIds([]) setHasChanges(true) toast.success( t('{{count}} API entries deleted. Click "Save Settings" to apply.', { count: selectedIds.length, }) ) } setShowDeleteDialog(false) setEditingApiInfo(null) } const handleSubmitForm = (values: ApiInfoFormValues) => { if (editingApiInfo) { setApiInfoList((prev) => prev.map((item) => item.id === editingApiInfo.id ? { ...item, ...values } : item ) ) toast.success(t('API info updated. Click "Save Settings" to apply.')) } else { const newId = Math.max(...apiInfoList.map((item) => item.id), 0) + 1 setApiInfoList((prev) => [...prev, { id: newId, ...values }]) toast.success(t('API info added. Click "Save Settings" to apply.')) } setHasChanges(true) setShowDialog(false) } const handleSaveAll = async () => { try { await updateOption.mutateAsync({ key: 'console_setting.api_info', value: JSON.stringify(apiInfoList), }) setHasChanges(false) toast.success(t('API info saved successfully')) } catch { toast.error(t('Failed to save API info')) } } const toggleSelectAll = (checked: boolean) => { setSelectedIds(checked ? apiInfoList.map((item) => item.id) : []) } const toggleSelectOne = (id: number, checked: boolean) => { setSelectedIds((prev) => checked ? [...prev, id] : prev.filter((item) => item !== id) ) } const getColorClass = (color: string) => { return ( colorOptions.find((opt) => opt.value === color)?.bgClass || 'bg-blue-500' ) } return (
{t('Enabled')}
0 } onCheckedChange={toggleSelectAll} /> {t('URL')} {t('Route')} {t('Description')} {t('Color')} {t('Actions')} {apiInfoList.length === 0 ? ( {t('No API Domains yet. Click "Add API" to create one.')} ) : ( apiInfoList.map((apiInfo) => ( toggleSelectOne(apiInfo.id, checked as boolean) } /> {apiInfo.description}
{apiInfo.color}
)) )}
{editingApiInfo ? t('Edit API Shortcut') : t('Add API Shortcut')} {t('Configure API documentation links for the dashboard')}
( {t('API URL')} )} /> ( {t('Route Description')} )} /> ( {t('Description')} )} /> ( {t('Badge Color')} {t('Visual indicator color for the API card')} )} />
{t('Are you sure?')} {deleteTarget === 'single' ? 'This API shortcut will be removed from the list.' : `${selectedIds.length} API shortcuts will be removed from the list.`} {t('Cancel')} {t('Delete')}
) }