From 14073db2d9e439ef40f5a32e72902c820f1839ab Mon Sep 17 00:00:00 2001 From: ronalds Date: Tue, 25 Nov 2025 05:55:45 -0300 Subject: [PATCH] Logo feature 1.0.27r --- backend/app/main.py | 43 +++++ frontend/src/App.jsx | 411 +++++++++++++++++++++---------------------- 2 files changed, 248 insertions(+), 206 deletions(-) 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 */}
- {/* Logo S de Syntria */} -
- - - - - - - - - -
+ {logoUrl ? ( + Logo + ) : ( +
Sin logo
+ )}

Syntria

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 }) {
{/* Logo y Nombre del Sistema */}
-
- - - - - - - - - -
+ {logoUrl ? ( + Logo + ) : ( +
Sin logo
+ )}

Syntria

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 (
-
-

Configuración de IA

-

- Configura el proveedor y modelo de IA para análisis de imágenes -

-
- - {loading ? ( -
-
Cargando configuración...
-
- ) : ( -
- {/* Provider Selection */} -
-

Proveedor de IA

- -
- - - -
-
- - {/* API Key */} -
-

API Key

- + +
+

Logo del Sistema

+
+ {logoUrl ? ( + Logo + ) : ( +
Sin logo
+ )}
- - setFormData({ ...formData, api_key: e.target.value })} - className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" - placeholder={formData.provider === 'openai' ? 'sk-...' : 'AIza...'} - required - /> -

- {formData.provider === 'openai' ? ( - <>Obtén tu API key en OpenAI Platform - ) : ( - <>Obtén tu API key en Google AI Studio - )} -

+ + {logoUploading && Subiendo...}
- - {/* Model Selection */} -
-

Modelo de IA

- -
- {filteredModels.map((model) => ( - - ))} -
-
- - {/* Current Status */} - {aiConfig && ( -
-
- -
-
Configuración Activa
-
- Proveedor: {aiConfig.provider} | - Modelo: {aiConfig.model_name} -
-
- Configurado el {new Date(aiConfig.created_at).toLocaleDateString('es-ES')} -
-
-
-
- )} - - {/* Save Button */} -
+

El logo se mostrará en el login y en la página principal.

+
+
+

Configuración de IA

+

Configura el proveedor y modelo de IA para análisis de imágenes

+
+
- - )} +
+
+

API Key

+
+ + setFormData({ ...formData, api_key: e.target.value })} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder={formData.provider === 'openai' ? 'sk-...' : 'AIza...'} + required + /> +

+ {formData.provider === 'openai' ? ( + <>Obtén tu API key en OpenAI Platform + ) : ( + <>Obtén tu API key en Google AI Studio + )} +

+
+
+
+

Modelo de IA

+
+ {filteredModels.map((model) => ( + + ))} +
+
+
+ +
+
- ) + ); } function APITokensTab({ user }) {