diff --git a/App.tsx b/App.tsx index f99578d..19204c5 100644 --- a/App.tsx +++ b/App.tsx @@ -10,7 +10,7 @@ import { ReportDisplay } from './components/ReportDisplay'; import { PrintableReport } from './components/PrintableReport'; import { DownloadIcon, EyeIcon, CogIcon } from './components/Icons'; import { ApiKeySetup } from './components/ApiKeySetup'; -import { AdminCategories } from './components/AdminCategories'; +import { AdminPanel } from './components/AdminPanel'; const App: React.FC = () => { const [viewMode, setViewMode] = useState<'analysis' | 'admin'>('analysis'); @@ -219,7 +219,12 @@ const App: React.FC = () => { ) : viewMode === 'admin' ? ( - + ) : (!hasKey || showSetup) ? ( setShowSetup(false) : undefined} /> ) : !reportsData ? ( diff --git a/components/AdminPanel.tsx b/components/AdminPanel.tsx new file mode 100644 index 0000000..6a44a6f --- /dev/null +++ b/components/AdminPanel.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { AdminCategories } from './AdminCategories'; +import { AdminStandards } from './AdminStandards'; +import type { AIProvider } from '../types/providers'; + +interface AdminPanelProps { + apiKey: string; + provider: AIProvider; + model: string; + endpoint?: string; +} + +export const AdminPanel: React.FC = (props) => { + const [activeTab, setActiveTab] = useState<'categories' | 'standards'>('categories'); + + return ( +
+
+
+ + +
+
+ + {activeTab === 'categories' ? : } +
+ ); +}; diff --git a/components/AdminStandards.tsx b/components/AdminStandards.tsx new file mode 100644 index 0000000..774d8a8 --- /dev/null +++ b/components/AdminStandards.tsx @@ -0,0 +1,228 @@ +import React, { useState, useEffect } from 'react'; +import type { Category } from './AdminCategories'; +import { buildStandardWithAI } from '../services/aiService'; +import type { AIProvider } from '../types/providers'; + +interface AdminStandardsProps { + apiKey: string; + provider: AIProvider; + model: string; + endpoint?: string; +} + +export const AdminStandards: React.FC = ({ apiKey, provider, model, endpoint }) => { + const [categories, setCategories] = useState([]); + const [selectedCategory, setSelectedCategory] = useState(''); + const [standards, setStandards] = useState([]); + const [isLoadingCats, setIsLoadingCats] = useState(true); + + const [isGenerating, setIsGenerating] = useState(false); + const [standardName, setStandardName] = useState(''); + const [generatedJson, setGeneratedJson] = useState(''); + const [jsonError, setJsonError] = useState(null); + + useEffect(() => { + fetchCategories(); + }, []); + + useEffect(() => { + if (selectedCategory) { + fetchStandards(selectedCategory); + } else { + setStandards([]); + } + }, [selectedCategory]); + + const fetchCategories = async () => { + try { + const res = await fetch('/api/categories'); + const data = await res.json(); + setCategories(data); + if (data.length > 0) setSelectedCategory(data[0].id); + } catch (e) { + console.error(e); + } finally { + setIsLoadingCats(false); + } + }; + + const fetchStandards = async (catId: string) => { + try { + const res = await fetch(`/api/standards?categoryId=${catId}`); + const data = await res.json(); + setStandards(data); + } catch (e) { + console.error(e); + } + }; + + const handleGenerate = async () => { + if (!standardName || !selectedCategory) return; + + const cat = categories.find(c => c.id === selectedCategory); + if (!cat) return; + + setIsGenerating(true); + setJsonError(null); + try { + const jsonObj = await buildStandardWithAI( + { provider, apiKey, model, endpoint }, + standardName, + cat.description + ); + setGeneratedJson(JSON.stringify(jsonObj, null, 2)); + } catch (e: any) { + setJsonError(e.message || "Erro ao gerar a norma."); + } finally { + setIsGenerating(false); + } + }; + + const handleSave = async () => { + if (!generatedJson) return; + + try { + const parsed = JSON.parse(generatedJson); + parsed.categoria_id = selectedCategory; + + await fetch('/api/standards', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(parsed) + }); + + setGeneratedJson(''); + setStandardName(''); + fetchStandards(selectedCategory); + } catch (e) { + setJsonError("Erro ao salvar: JSON inválido ou falha na rede."); + } + }; + + const handleDelete = async (id: string) => { + if (!confirm('Excluir esta norma? O app não poderá mais validar relatórios contra ela.')) return; + try { + await fetch(`/api/standards?id=${id}`, { method: 'DELETE' }); + fetchStandards(selectedCategory); + } catch (e) { + console.error(e); + } + }; + + if (isLoadingCats) return
Carregando categorias...
; + + return ( +
+ {/* Selector */} +
+ + +
+ + {selectedCategory && ( +
+ + {/* Builder */} +
+

+ + Construtor Assistido por IA +

+ +
+
+ + setStandardName(e.target.value)} + placeholder="Ex: ASTM A572 Grau 50" + className="w-full rounded-lg border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-4 py-2 text-slate-900 dark:text-white outline-none focus:ring-2 focus:ring-indigo-500" + /> +
+ + + + {!apiKey && ( +

Configure a Chave da API no painel inicial primeiro.

+ )} + + {jsonError && ( +
{jsonError}
+ )} + + {generatedJson && ( +
+ +