diff --git a/backend/app/main.py b/backend/app/main.py index bc54904..e63fa35 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,3 +1,46 @@ + +# ============= LOGO CONFIGURABLE ============= +from fastapi import FastAPI, Form +app = FastAPI() + +@app.post("/api/config/logo", response_model=dict) +async def upload_logo( + file: UploadFile = File(...), + db: Session = Depends(get_db), + current_user: models.User = Depends(get_current_user) +): + """Sube un logo y lo guarda en MinIO, actualiza la configuración.""" + if current_user.role != "admin": + raise HTTPException(status_code=403, detail="Solo administradores pueden cambiar el logo") + + # Subir imagen a MinIO + file_extension = file.filename.split(".")[-1] + now = datetime.now() + folder = f"logo" + file_name = f"logo_{now.strftime('%Y%m%d_%H%M%S')}.{file_extension}" + s3_key = f"{folder}/{file_name}" + s3_client.upload_fileobj(file.file, S3_IMAGE_BUCKET, s3_key, ExtraArgs={"ContentType": file.content_type}) + logo_url = f"{S3_ENDPOINT}/{S3_IMAGE_BUCKET}/{s3_key}" + + # Guardar en configuración (puedes tener una tabla Config o usar AIConfiguration) + config = db.query(models.AIConfiguration).filter(models.AIConfiguration.is_active == True).first() + if config: + config.logo_url = logo_url + db.commit() + db.refresh(config) + # Si no hay config, solo retorna la url + return {"logo_url": logo_url} + +# Endpoint para obtener el logo +@app.get("/api/config/logo", response_model=dict) +def get_logo_url( + db: Session = Depends(get_db) +): + config = db.query(models.AIConfiguration).filter(models.AIConfiguration.is_active == True).first() + if config and getattr(config, "logo_url", None): + return {"logo_url": config.logo_url} + # Default logo (puedes poner una url por defecto) + return {"logo_url": f"{S3_ENDPOINT}/{S3_IMAGE_BUCKET}/logo/default_logo.png"} from fastapi import FastAPI, Depends, HTTPException, status, UploadFile, File from fastapi.middleware.cors import CORSMiddleware from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 3ab3461..62ca7f3 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -45,6 +45,20 @@ function LoginPage({ setUser }) { const [password, setPassword] = useState('') const [error, setError] = useState('') const [loading, setLoading] = useState(false) + const [logoUrl, setLogoUrl] = useState(null); + useEffect(() => { + const fetchLogo = async () => { + try { + const API_URL = import.meta.env.VITE_API_URL || ''; + const res = await fetch(`${API_URL}/api/config/logo`); + if (res.ok) { + const data = await res.json(); + setLogoUrl(data.logo_url); + } + } catch {} + }; + fetchLogo(); + }, []); const handleLogin = async (e) => { e.preventDefault() @@ -86,22 +100,11 @@ function LoginPage({ setUser }) { {/* Header con Logo */}
Sistema Inteligente de Inspecciones
@@ -174,6 +177,20 @@ function DashboardPage({ user, setUser }) { const [activeTab, setActiveTab] = useState('checklists') const [activeInspection, setActiveInspection] = useState(null) const [sidebarOpen, setSidebarOpen] = useState(true) + const [logoUrl, setLogoUrl] = useState(null); + useEffect(() => { + const fetchLogo = async () => { + try { + const API_URL = import.meta.env.VITE_API_URL || ''; + const res = await fetch(`${API_URL}/api/config/logo`); + if (res.ok) { + const data = await res.json(); + setLogoUrl(data.logo_url); + } + } catch {} + }; + fetchLogo(); + }, []); useEffect(() => { loadData() @@ -273,21 +290,11 @@ function DashboardPage({ user, setUser }) {Sistema Inteligente de Inspecciones
@@ -363,65 +370,106 @@ function DashboardPage({ user, setUser }) { } function SettingsTab({ user }) { - const [aiConfig, setAiConfig] = useState(null) - const [availableModels, setAvailableModels] = useState([]) - const [loading, setLoading] = useState(true) - const [saving, setSaving] = useState(false) + // Estado para el logo + const [logoUrl, setLogoUrl] = useState(null); + const [logoUploading, setLogoUploading] = useState(false); + const [aiConfig, setAiConfig] = useState(null); + const [availableModels, setAvailableModels] = useState([]); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); const [formData, setFormData] = useState({ provider: 'openai', api_key: '', model_name: 'gpt-4o' - }) + }); useEffect(() => { - loadSettings() - }, []) + const fetchLogo = async () => { + try { + const API_URL = import.meta.env.VITE_API_URL || ''; + const token = localStorage.getItem('token'); + const res = await fetch(`${API_URL}/api/config/logo`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + if (res.ok) { + const data = await res.json(); + setLogoUrl(data.logo_url); + } + } catch {} + }; + fetchLogo(); + }, []); + + useEffect(() => { + loadSettings(); + }, []); const loadSettings = async () => { try { - const token = localStorage.getItem('token') - const API_URL = import.meta.env.VITE_API_URL || '' - + const token = localStorage.getItem('token'); + const API_URL = import.meta.env.VITE_API_URL || ''; // Cargar modelos disponibles const modelsRes = await fetch(`${API_URL}/api/ai/models`, { headers: { 'Authorization': `Bearer ${token}` } - }) - + }); if (modelsRes.ok) { - const models = await modelsRes.json() - setAvailableModels(models) + const models = await modelsRes.json(); + setAvailableModels(models); } - // Cargar configuración actual const configRes = await fetch(`${API_URL}/api/ai/configuration`, { headers: { 'Authorization': `Bearer ${token}` } - }) - + }); if (configRes.ok) { - const config = await configRes.json() - setAiConfig(config) + const config = await configRes.json(); + setAiConfig(config); setFormData({ provider: config.provider, api_key: config.api_key, model_name: config.model_name - }) + }); } - - setLoading(false) + setLoading(false); } catch (error) { - console.error('Error loading settings:', error) - setLoading(false) + console.error('Error loading settings:', error); + setLoading(false); } - } + }; + + const handleLogoUpload = async (e) => { + const file = e.target.files[0]; + if (!file) return; + setLogoUploading(true); + try { + const API_URL = import.meta.env.VITE_API_URL || ''; + const token = localStorage.getItem('token'); + const formDataLogo = new FormData(); + formDataLogo.append('file', file); + const res = await fetch(`${API_URL}/api/config/logo`, { + method: 'POST', + headers: { 'Authorization': `Bearer ${token}` }, + body: formDataLogo + }); + if (res.ok) { + const data = await res.json(); + setLogoUrl(data.logo_url); + alert('Logo actualizado correctamente'); + } else { + alert('Error al subir el logo'); + } + } catch { + alert('Error al subir el logo'); + } finally { + setLogoUploading(false); + } + }; const handleSave = async (e) => { - e.preventDefault() - setSaving(true) - + e.preventDefault(); + setSaving(true); try { - const token = localStorage.getItem('token') - const API_URL = import.meta.env.VITE_API_URL || '' - + const token = localStorage.getItem('token'); + const API_URL = import.meta.env.VITE_API_URL || ''; const response = await fetch(`${API_URL}/api/ai/configuration`, { method: 'POST', headers: { @@ -429,168 +477,119 @@ function SettingsTab({ user }) { 'Content-Type': 'application/json', }, body: JSON.stringify(formData), - }) - + }); if (response.ok) { - alert('Configuración guardada correctamente') - loadSettings() + alert('Configuración guardada correctamente'); + loadSettings(); } else { - alert('Error al guardar configuración') + alert('Error al guardar configuración'); } } catch (error) { - console.error('Error:', error) - alert('Error al guardar configuración') + console.error('Error:', error); + alert('Error al guardar configuración'); } finally { - setSaving(false) + setSaving(false); } - } + }; - const filteredModels = availableModels.filter(m => m.provider === formData.provider) + const filteredModels = availableModels.filter(m => m.provider === formData.provider); return (- Configura el proveedor y modelo de IA para análisis de imágenes -
-