Spaces:
Sleeping
Sleeping
| import mysql.connector, os, datetime, base64, time, requests, gradio as gr, pandas as pd | |
| from io import BytesIO | |
| from PIL import Image | |
| from dotenv import load_dotenv | |
| from typing import List, Tuple, Any | |
| from prxm_uploader import PrxmGcsUploaderClient | |
| from settings_config import settings | |
| from mysql.connector import errorcode | |
| # Load env variables | |
| load_dotenv() | |
| class GarmentDataManager: | |
| def __init__(self): | |
| self.user = os.getenv("user") | |
| self.password = os.getenv("password") | |
| self.host = os.getenv("host") | |
| self.database = os.getenv("database") | |
| self.df = pd.DataFrame() | |
| self.error_message = None | |
| self.cache_version = int(time.time()) | |
| self.refresh_data() | |
| def get_connection(self): | |
| """Creates and returns a MySQL connection.""" | |
| try: | |
| return mysql.connector.connect( | |
| user = self.user, | |
| password = self.password, | |
| host = self.host, | |
| database = self.database | |
| ) | |
| except mysql.connector.Error as err: | |
| print(f"❌ Connection Failed: {err}") | |
| raise err | |
| def refresh_data(self) -> str: | |
| """Fetches data from MySQL and updates the internal DataFrame.""" | |
| try: | |
| cnx = self.get_connection() | |
| cursor = cnx.cursor() | |
| query = """ | |
| SELECT id, sku, clothes_type, collection, season, brand, description, | |
| url_clothes_img, created_at, collection_id, clothing_type_id, validated | |
| FROM clothes | |
| """ | |
| cursor.execute(query) | |
| data = cursor.fetchall() | |
| columns = [i[0] for i in cursor.description] | |
| cursor.close() | |
| cnx.close() | |
| # Create DataFrame | |
| temp_df = pd.DataFrame(data, columns = columns) | |
| # Normalize Columns | |
| cols_needed = ["id", "sku", "clothes_type", "collection", "season", "brand", "description", "url_clothes_img", "created_at", "collection_id", "clothing_type_id", "validated"] | |
| for col in cols_needed: | |
| if col not in temp_df.columns: | |
| temp_df[col] = "" # Ensure all columns exist | |
| # Type Conversions | |
| if "id" in temp_df.columns: | |
| temp_df["id"] = temp_df["id"].astype("Int64") | |
| self.df = temp_df | |
| self.error_message = None | |
| self.cache_version = int(time.time()) # Update cache version on refresh | |
| return "✅ Datos actualizados correctamente de MySQL." | |
| except Exception as e: | |
| self.error_message = f"❌ Error al conectar con la Base de Datos: {str(e)}" | |
| print(self.error_message) | |
| # Initialize empty DF to prevent crashes | |
| if self.df.empty: | |
| self.df = pd.DataFrame(columns = ["id", "sku", "clothes_type", "collection", "season", "brand", "description", "url_clothes_img", "created_at", "validated"]) | |
| return self.error_message | |
| def get_dropdown_options(self): | |
| """Returns sorted unique values for filters.""" | |
| if self.df.empty: | |
| return [], [], [] | |
| types = sorted(self.df["clothes_type"].dropna().astype(str).unique().tolist()) | |
| collections = sorted(self.df["collection"].dropna().astype(str).unique().tolist()) | |
| brands = sorted(self.df["brand"].dropna().astype(str).unique().tolist()) | |
| return types, collections, brands | |
| def get_gallery_items(self, filter_col = None, order_type = "A -> Z") -> Tuple[List[Tuple[str, str]], pd.DataFrame]: | |
| """Filters and returns gallery items + the new view DataFrame for state.""" | |
| if self.df.empty: | |
| return [], pd.DataFrame() | |
| df_temp = self.df.copy() | |
| if filter_col == 'Prendas invalidadas': | |
| # Logic form original: df_temp['validated'].astype(str).isin(['0', '0.0']) | |
| df_temp = df_temp[df_temp['validated'].astype(str).isin(['0', '0.0'])] | |
| mapping = { | |
| 'Marca (Default)': 'brand', | |
| 'Tipo de ropa': 'clothes_type', | |
| 'Colección': 'collection', | |
| 'Temporada': 'season', | |
| 'Fecha de creación': 'created_at', | |
| 'Prendas invalidadas': 'brand' | |
| } | |
| col_sort = mapping.get(filter_col, 'brand') | |
| ascending = (order_type == "A -> Z") | |
| if col_sort == 'created_at': | |
| df_temp[col_sort] = pd.to_datetime(df_temp[col_sort], errors = 'coerce') | |
| view_df = df_temp.sort_values(by = col_sort, ascending = ascending, na_position = 'last').reset_index(drop = True) | |
| # Use persistent cache version to prevent reload on every interaction | |
| items = [(f"{row['url_clothes_img']}?v={self.cache_version}&ignoreCache=1", f"{row['brand']}, {row['clothes_type']}, {row['season']}") | |
| for _, row in view_df.iterrows()] | |
| return items, view_df | |
| def get_garment_details(self, idx, view_df): | |
| """Returns details for a specific garment index (from the provided view_df).""" | |
| if view_df is None or view_df.empty or idx >= len(view_df): | |
| return None | |
| row = view_df.iloc[idx] | |
| is_validated = (str(row.get('validated')) == "1") | |
| return { | |
| "validated": is_validated, | |
| "sku": row.get('sku', ''), | |
| "created_at": row.get('created_at', ''), | |
| "clothes_type": row.get('clothes_type', ''), | |
| "brand": row.get('brand', ''), | |
| "season": row.get('season', ''), | |
| "collection": row.get('collection', ''), | |
| "description": row.get('description', ''), | |
| "idx": idx | |
| } | |
| def generate_sku(self, brand, clothes_type, season, year_code): | |
| """Generates a new SKU based on business logic.""" | |
| # Paso 1: Marca, brand[0] V = VC o C = CC | |
| brand = str(brand).upper().strip() or "XX" | |
| vocales = "AEIOU" | |
| lista_v = [c for c in brand if c in vocales] | |
| lista_c = [c for c in brand if c not in vocales and c.isalpha()] | |
| prefix = "XX" | |
| if brand and brand[0] in vocales and lista_v and lista_c: | |
| prefix = lista_v[0] + lista_c[0] | |
| elif len(lista_c) >= 2: | |
| prefix = lista_c[0] + lista_c[1] | |
| # Paso 2: Tipo de ropa | |
| type_code = "00" | |
| if not self.df.empty: | |
| existing_type = self.df[self.df["clothes_type"].astype(str).str.upper() == clothes_type.upper()] | |
| if not existing_type.empty: | |
| first_sku = str(existing_type.iloc[0]["sku"]) | |
| if len(first_sku) >= 4: | |
| type_code = first_sku[2:4] | |
| else: | |
| try: | |
| codigos = self.df["sku"].astype(str).str[2:4].dropna() | |
| codigos = codigos[codigos.str.isdigit()].astype(int) | |
| if not codigos.empty: | |
| type_code = str(codigos.max() + 1).zfill(2) | |
| except: | |
| pass | |
| # Paso 3: Temporada | |
| s_map = {"PRIMAVERA": "1", "VERANO": "2", "OTOÑO": "3", "INVIERNO": "4"} | |
| season_code = s_map.get(season.upper(), "0") | |
| # Paso 4: Año | |
| chain = f"{prefix}{type_code}{season_code}{year_code}" | |
| # Paso 5: Si existe prenda similar + 1 | |
| correlative = "000" | |
| if not self.df.empty: | |
| matches = self.df[self.df["sku"].astype(str).str.startswith(chain)] | |
| if not matches.empty: | |
| nums = matches["sku"].astype(str).apply(lambda x: int(x[-3:]) if x[-3:].isdigit() else 0) | |
| if not nums.empty: | |
| correlative = f"{nums.max() + 1:03d}" | |
| return chain + correlative | |
| def generate_img_url_and_load(self, file, sku): | |
| if isinstance(file, str): | |
| if not file.lower().endswith('.png'): | |
| # gr.Info("⚠️ Error: El archivo debe ser una imagen png.") | |
| return False, "⚠️ Error: El archivo debe ser una imagen png.", None | |
| try: | |
| with open(file, "rb") as f: | |
| file_bytes = f.read() | |
| except Exception as e: | |
| # gr.Info("⚠️ Error: No se pudo leer el archivo.") | |
| return False, f"⚠️ Error leyendo archivo: {str(e)}", None | |
| else: | |
| if isinstance(file, bytes): | |
| file_bytes = file | |
| elif hasattr(file, "read"): | |
| file_bytes = file.read() | |
| else: | |
| # gr.Info("⚠️ Error: Formato de archivo no válido.") | |
| return False, "⚠️ Error: Formato de archivo no válido.", None | |
| try: | |
| bucket = settings.gcs.bucketName | |
| subfolder = settings.gcs.subfolderName | |
| img_base64 = base64.b64encode(file_bytes).decode('utf-8') | |
| formatted_image = f"data:image/png;base64,{img_base64}" | |
| img_url = PrxmGcsUploaderClient.prxm_upload_using_asset_id(bucket, subfolder, sku, formatted_image) | |
| # gr.Info("✅ Img url generado correctamente.") | |
| return True, None , img_url | |
| except Exception as e: | |
| return False, f"❌ Error al intentar actualizar la imagen: {str(e)}", None | |
| def update_garment(self, idx, new_data: dict, view_df): | |
| """Updates garment in MySQL and refreshes local Master DF.""" | |
| conn = None | |
| try: | |
| if view_df is None or view_df.empty: | |
| return False, "⚠️ Error: No hay datos en la vista actual." | |
| sku_target = view_df.iloc[int(idx)]['sku'] | |
| brand = self.df.loc[self.df['sku'] == sku_target, 'brand'].values[0] | |
| duplicates = self.df[ | |
| (self.df['clothes_type'] == new_data['clothes_type']) & | |
| (self.df['collection'] == new_data['collection']) & | |
| (self.df['season'] == new_data['season']) & | |
| (self.df['brand'] == brand) & | |
| (self.df['description'] == new_data['description']) | |
| ] | |
| if not duplicates.empty and not new_data['url_clothes_img']: | |
| existing_sku = duplicates.iloc[0]['sku'] | |
| return False, f"⚠️ Error: Ya existe una prenda con estos datos exactos. (SKU: {existing_sku})" | |
| sku = self.df.loc[self.df['sku'] == sku_target, 'sku'].values[0] | |
| new_data['sku'] = sku | |
| current_clothes_type = self.df.loc[self.df['sku'] == sku_target, 'clothes_type'].values[0] | |
| current_season = self.df.loc[self.df['sku'] == sku_target, 'season'].values[0] | |
| if (new_data.get("clothes_type").upper() != current_clothes_type) or (new_data.get("season").upper() != current_season): #Actualizar SKU & URL | |
| actual_brand = self.df.loc[self.df['sku'] == sku_target, 'brand'].values[0] | |
| actual_time = self.df.loc[self.df['sku'] == sku_target, 'created_at'].values[0] | |
| actual_year_code = str(actual_time)[2:4] | |
| new_clothes_type = new_data.get("clothes_type").upper() | |
| new_season = new_data.get("season").upper() | |
| new_sku = self.generate_sku(actual_brand, new_clothes_type, new_season, actual_year_code) | |
| new_data['sku'] = new_sku | |
| if new_data.get("url_clothes_img"): #Cambiar la imagen | |
| # gr.Info("Actualizando imagen & sku & datos...") | |
| img = new_data["url_clothes_img"] | |
| success_img, err_msg, new_url = self.generate_img_url_and_load(img, new_sku) | |
| if not success_img: | |
| return False, err_msg | |
| new_data['url_clothes_img'] = new_url | |
| else: #Mantener imagen pero dif sku | |
| # gr.Info("Actualizando sku & datos...") | |
| current_url = self.df.loc[self.df['sku'] == sku_target, 'url_clothes_img'].values[0] | |
| respuesta = requests.get(current_url) | |
| respuesta.raise_for_status() | |
| img_bytes = respuesta.content | |
| success_img, err_msg, new_url = self.generate_img_url_and_load(img_bytes, new_sku) | |
| if not success_img: | |
| return False, err_msg | |
| new_data['url_clothes_img'] = new_url | |
| else: #No se modifica SKU por lo que no se modifica la URL | |
| if new_data.get("url_clothes_img"): #Cambia solo la imagen | |
| # gr.Info("Actualizando imagen || datos...") | |
| img = new_data["url_clothes_img"] | |
| success_img, err_msg, new_url = self.generate_img_url_and_load(img, sku) | |
| if not success_img: | |
| return False, err_msg | |
| new_data["url_clothes_img"] = new_url | |
| else: #Mantener imagen | |
| # gr.Info("Actualizando solo datos...") | |
| current_url = self.df.loc[self.df['sku'] == sku_target, 'url_clothes_img'].values[0] | |
| new_data["url_clothes_img"] = current_url | |
| sql = """ | |
| UPDATE clothes | |
| SET sku = %s, | |
| clothes_type = %s, | |
| collection = %s, | |
| season = %s, | |
| description = %s, | |
| url_clothes_img = %s, | |
| validated = %s | |
| WHERE sku = %s | |
| """ | |
| val = ( | |
| new_data['sku'], | |
| new_data['clothes_type'].upper(), | |
| new_data['collection'].upper(), | |
| new_data['season'].upper(), | |
| new_data['description'], | |
| new_data['url_clothes_img'], | |
| 0, | |
| sku_target | |
| ) | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(sql, val) | |
| conn.commit() | |
| cursor.close() | |
| conn.close() | |
| master_idx = self.df[self.df['sku'] == sku_target].index[0] | |
| self.df.at[master_idx, 'sku'] = new_data['sku'] | |
| self.df.at[master_idx, 'clothes_type'] = new_data['clothes_type'].upper() | |
| self.df.at[master_idx, 'collection'] = new_data['collection'].upper() | |
| self.df.at[master_idx, 'season'] = new_data['season'].upper() | |
| self.df.at[master_idx, 'description'] = new_data['description'] | |
| self.df.at[master_idx, 'url_clothes_img'] = new_data['url_clothes_img'] | |
| self.df.at[master_idx, 'validated'] = 0 | |
| self.df.at[master_idx, 'url_clothes_img'] = new_data['url_clothes_img'] | |
| self.df.at[master_idx, 'validated'] = 0 | |
| self.cache_version = int(time.time()) | |
| return True, "✅ Cambios guardados correctamente." | |
| except Exception as e: | |
| gr.Info("⚠️ Entre a la excepción de update garment") | |
| if conn: conn.rollback() | |
| return False, f"❌ Error al actualizar: {str(e)}" | |
| def create_garment(self, img, new_data: dict): | |
| """Inserts new garment into MySQL.""" | |
| conn = None | |
| try: | |
| clothes_type = str(new_data['clothes_type']).upper() | |
| collection = str(new_data['collection']).upper() | |
| season = str(new_data['season']).upper() | |
| brand = str(new_data['brand']).upper() | |
| description = new_data['description'] | |
| if not self.df.empty: | |
| duplicates = self.df[ | |
| (self.df['clothes_type'] == clothes_type) & | |
| (self.df['collection'] == collection) & | |
| (self.df['season'] == season) & | |
| (self.df['brand'] == brand) & | |
| (self.df['description'] == description) | |
| ] | |
| if not duplicates.empty: | |
| existing_sku = duplicates.iloc[0]['sku'] | |
| return False, f"⚠️ Error: Ya existe una prenda con estos datos exactos. (SKU: {existing_sku})", None | |
| created_time = datetime.datetime.now() | |
| year_code = str(created_time.year)[-2:] | |
| new_sku = self.generate_sku(brand, clothes_type, season, year_code) | |
| created_time_str = created_time.strftime('%Y-%m-%d %H:%M:%S') | |
| success_img, err_msg, img_url = self.generate_img_url_and_load(img, new_sku) | |
| if not success_img: | |
| return False, err_msg, None | |
| sql = """ | |
| INSERT INTO clothes (sku, clothes_type, collection, season, brand, description, url_clothes_img, created_at, validated) | |
| VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) | |
| """ | |
| val = (new_sku, clothes_type, collection, season, brand, description, img_url, created_time_str, 0) | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(sql, val) | |
| conn.commit() | |
| new_row = { | |
| "sku": new_sku, | |
| "clothes_type": clothes_type, | |
| "collection": collection, | |
| "season": season, | |
| "brand": brand, | |
| "description": description, | |
| "url_clothes_img": img_url, | |
| "created_at": created_time_str, | |
| "validated": 0 | |
| } | |
| self.df = pd.concat([self.df, pd.DataFrame([new_row])], ignore_index = True).fillna("") | |
| self.cache_version = int(time.time()) | |
| return True, f"✅ Nueva prenda guardada (SKU: {new_sku})", new_sku | |
| except Exception as e: | |
| if conn: conn.rollback() | |
| return False, f"❌ Error al crear prenda: {str(e)}", None | |
| def invalidate_garment(self, idx, view_df): | |
| """Sets validated = 0 in DB.""" | |
| conn = None | |
| try: | |
| sku_target = view_df.iloc[int(idx)]['sku'] | |
| sql = "UPDATE clothes SET validated = 0 WHERE sku = %s" | |
| val = (sku_target,) | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(sql, val) | |
| conn.commit() | |
| cursor.close() | |
| conn.close() | |
| master_idx = self.df[self.df['sku'] == sku_target].index[0] | |
| self.df.at[master_idx, 'validated'] = 0 | |
| self.cache_version = int(time.time()) # Update cache version | |
| return True, "✅ Prenda invalidada correctamente." | |
| except Exception as e: | |
| return False, f"❌ Error al invalidar: {str(e)}" | |
| dm = GarmentDataManager() | |
| custom_css = """ | |
| .gradio-container h3 { | |
| font-weight: 900 !important; | |
| font-style: italic !important; | |
| font-size: 2em !important; | |
| color: #eece25 !important; text-decoration: underline; letter-spacing: 2px; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| } | |
| .gradio-container label span, .gradio-container .block-label, .gradio-container span[data-testid="block-info"] { | |
| font-weight: bold !important; font-style: italic !important; font-size: 1.1em !important; | |
| color: #ffffff !important; margin-bottom: 4px !important; display: block !important; | |
| } | |
| .gradio-container input, .gradio-container .single-select, .gradio-container .select-wrap span, .gradio-container .secondary-label { | |
| color: #ffffff !important; font-weight: 600 !important; | |
| } | |
| .gradio-container .tab-nav button { color: #000000 !important; font-weight: bold !important; opacity: 1 !important; } | |
| .gradio-container .tab-nav button.selected { color: #eece25 !important; border-bottom: 2px solid #eece25 !important; } | |
| .check-centrado { display: flex !important; justify-content: center !important; align-items: center !important; border: none !important; } | |
| .check-centrado label { justify-content: center !important; width: auto !important; gap: 10px !important; } | |
| #btn_save { background-color: #12a822 !important; color: white !important; } | |
| #btn_edit { background-color: #7000c8 !important; color: white !important; } | |
| #btn_new { background-color: #0067fd !important; color: white !important; } | |
| #btn_cancel { background-color: #eba428 !important; color: white !important; } | |
| """ | |
| # --- INTERFACE FUNCTIONS --- | |
| def refresh_all_data(): | |
| """Manual refresh button handler.""" | |
| msg = dm.refresh_data() | |
| types, colls, brands = dm.get_dropdown_options() | |
| gallery_items, new_view_df = dm.get_gallery_items() | |
| if "Error" in msg: | |
| gr.Warning(msg) | |
| else: | |
| gr.Info(msg) | |
| return ( | |
| gr.update(choices = types), # New Type | |
| gr.update(choices = colls), # New Collection | |
| gr.update(choices = brands), # New Brand | |
| gallery_items, # Gallery | |
| gr.update(visible = ("Error" in msg), value = f"### {msg}"), # Error Banner | |
| new_view_df # Update State | |
| ) | |
| def ui_load_gallery(filter_col, order_type): | |
| items, new_view_df = dm.get_gallery_items(filter_col, order_type) | |
| new_filters = {"col": filter_col, "order": order_type} | |
| return items, new_view_df, new_filters | |
| def ui_get_details(evt: gr.SelectData, is_editing, view_df): | |
| if is_editing: | |
| gr.Warning("⚠️ Debes Guardar o Cancelar la edición actual antes de seleccionar otra prenda.") | |
| return [gr.update()] * 14 | |
| idx = evt.index | |
| data = dm.get_garment_details(idx, view_df) | |
| if not data: | |
| return [gr.update()] * 14 | |
| return ( | |
| data['validated'], # out_val | |
| data['sku'], # out_sku | |
| data['created_at'], # out_date | |
| data['clothes_type'], # out_type | |
| data['brand'], # out_brand | |
| data['season'], # out_season | |
| data['collection'], # out_coll | |
| data['description'], # out_desc | |
| None, # edit_img (empty) | |
| data['clothes_type'], # edit_type | |
| data['season'], # edit_season | |
| data['collection'], # edit_coll | |
| data['description'], # edit_desc | |
| data['idx'] # selected_idx_holder | |
| ) | |
| def ui_save_edit(img, clothes_type, season, collection, description, idx, view_df, current_filters): | |
| if idx == -1: return gr.update(), gr.update() # Return gallery update, state update | |
| success, msg = dm.update_garment(idx, { | |
| "clothes_type": clothes_type, | |
| "collection": collection, | |
| "season": season, | |
| "description": description, | |
| "url_clothes_img": img | |
| }, view_df) | |
| if success: | |
| gr.Info(msg) | |
| else: | |
| gr.Error(msg) | |
| # Refresh gallery restoring filters | |
| f_col = current_filters.get("col", "Marca (Default)") | |
| f_ord = current_filters.get("order", "A -> Z") | |
| items, new_view_df = dm.get_gallery_items(f_col, f_ord) | |
| return items, new_view_df | |
| def ui_save_new(img, type, coll, season, brand, desc, current_filters): | |
| if not all([img, type, coll, season, brand]): | |
| gr.Warning("❌ Error: Faltan campos obligatorios.") | |
| f_col = current_filters.get("col", "Marca (Default)") | |
| f_ord = current_filters.get("order", "A -> Z") | |
| items, v_df = dm.get_gallery_items(f_col, f_ord) | |
| return items, None, v_df | |
| success, msg, new_sku = dm.create_garment(img, { | |
| "clothes_type": type, | |
| "collection": coll, | |
| "season": season, | |
| "brand": brand, | |
| "description": desc | |
| }) | |
| if success: | |
| gr.Info(msg) | |
| else: | |
| gr.Error(msg) | |
| f_col = current_filters.get("col", "Marca (Default)") | |
| f_ord = current_filters.get("order", "A -> Z") | |
| items, new_view_df = dm.get_gallery_items(f_col, f_ord) | |
| return items, new_sku, new_view_df | |
| def ui_delete_confirm(idx, view_df, current_filters): | |
| if idx == -1: return gr.update(), gr.update() | |
| success, msg = dm.invalidate_garment(idx, view_df) | |
| if success: gr.Info(msg) | |
| else: gr.Error(msg) | |
| f_col = current_filters.get("col", "Marca (Default)") | |
| f_ord = current_filters.get("order", "A -> Z") | |
| items, new_view_df = dm.get_gallery_items(f_col, f_ord) | |
| return items, new_view_df | |
| # --- BUILD BLOCK --- | |
| with gr.Blocks(title = "Garment DB", css = custom_css) as demo: | |
| # State | |
| is_editing = gr.State(value = False) | |
| selected_idx_holder = gr.State(value = -1) | |
| new_sku_holder = gr.State() | |
| # State for multi-user support | |
| stored_view_df = gr.State() | |
| current_filters = gr.State(value = {"col": "Marca (Default)", "order": "A -> Z"}) | |
| error_banner = gr.Markdown(visible = False) | |
| if dm.error_message: | |
| error_banner.value = f"### {dm.error_message}" | |
| error_banner.visible = True | |
| with gr.Row(): | |
| with gr.Column(scale = 2): | |
| gallery = gr.Gallery(columns = 3, height = 600, interactive = False) | |
| btn_refresh = gr.Button("🔄 Refrescar Datos", variant = "secondary", size = "sm") | |
| with gr.Column(scale = 1): | |
| with gr.Tabs() as tabs_interface: | |
| # TAB 0: DETALLES | |
| with gr.Tab("Detalles", id = 0): | |
| with gr.Group(): | |
| gr.Markdown("<h3 style = 'text-align: center;'>🔎 Filtros</h3>") | |
| with gr.Row(): | |
| filter_type = gr.Dropdown(label = "Buscar por:", choices = ['Marca (Default)', 'Tipo de ropa', 'Colección','Temporada', 'Fecha de creación', 'Prendas invalidadas'], value = 'Marca (Default)', allow_custom_value = False) | |
| filter_order = gr.Dropdown(label = "Organizar por:", choices = ['A -> Z', 'Z -> A'], value = 'A -> Z', allow_custom_value = False) | |
| with gr.Group(): | |
| gr.Markdown("<h3 style='text-align: center;'>📄 Detalles</h3>") | |
| out_val = gr.Checkbox(label = "Prenda Validada", interactive = False, elem_classes = "check-centrado") | |
| with gr.Row(): | |
| out_sku = gr.Textbox(label = "SKU:", interactive = False, text_align = "center") | |
| out_date = gr.Textbox(label = "Fecha Creación:", interactive = False, text_align = "center") | |
| with gr.Row(): | |
| out_type = gr.Textbox(label = "Tipo:", interactive = False, text_align = "center") | |
| out_brand = gr.Textbox(label = "Marca:", interactive = False, text_align = "center") | |
| with gr.Row(): | |
| out_season = gr.Textbox(label = "Temporada:", interactive = False, text_align = "center") | |
| out_coll = gr.Textbox(label = "Colección:", interactive = False, text_align = "center") | |
| out_desc = gr.Textbox(label = "Descripción:", interactive = False, lines = 2, max_lines = 10, autoscroll = False) | |
| with gr.Row(): | |
| btn_goto_edit = gr.Button("📝 Editar") | |
| btn_goto_new = gr.Button("➕ Nueva", variant = "primary") | |
| # TAB 1: EDITAR | |
| with gr.Tab("Editar", id = 1): | |
| with gr.Group(): | |
| gr.Markdown("<h3 style = 'text-align: center;'>📝 Editar Prenda</h3>") | |
| edit_img = gr.File(label = "Remplazar Imagen", interactive = True, height = 120) | |
| edit_type = gr.Textbox(label = "Tipo:") | |
| edit_season = gr.Textbox(label = "Temporada:") | |
| edit_coll = gr.Textbox(label = "Colección:") | |
| edit_desc = gr.Textbox(label = "Descripción:", lines = 2, max_lines = 10) | |
| with gr.Row(): | |
| btn_edit_cancel = gr.Button("⬅️ Cancelar", elem_id = "btn_cancel") | |
| btn_edit_save = gr.Button("💾 Guardar Cambios", variant = "primary", elem_id = "btn_save") | |
| btn_edit_delete = gr.Button("🗑️ Borrar Prenda", variant = "stop") | |
| with gr.Group(visible = False) as confirm_group: | |
| gr.Markdown("### ⚠️ ¿Seguro que quieres invalidar la prenda?") | |
| with gr.Row(height = 45): | |
| btn_del_yes = gr.Button("✅ Sí", variant = "stop") | |
| btn_del_no = gr.Button("❌ No") | |
| # TAB 2: NUEVA | |
| with gr.Tab("Nueva", id = 2): | |
| with gr.Group(): | |
| gr.Markdown("<h3 style = 'text-align: center;'>➕ Nueva Prenda</h3>") | |
| unique_types, unique_colls, unique_brands = dm.get_dropdown_options() | |
| new_img = gr.File(label = "Subir Imagen", type = "filepath", height = 120) | |
| new_type = gr.Dropdown(label = "Tipo:", choices = unique_types, allow_custom_value = True) | |
| new_coll = gr.Dropdown(label = "Colección:", choices = unique_colls, allow_custom_value = True) | |
| new_brand = gr.Dropdown(label = "Marca:", choices = unique_brands, allow_custom_value = True) | |
| new_season = gr.Dropdown(label = "Temporada:", choices = ['N/A', 'PRIMAVERA', 'VERANO', 'OTOÑO', 'INVIERNO']) | |
| new_desc = gr.Textbox(label = "Descripción:", lines = 2, max_lines = 10) | |
| with gr.Row(): | |
| btn_new_cancel = gr.Button("⬅️ Cancelar", elem_id = "btn_cancel") | |
| btn_new_save = gr.Button("💾 Guardar Nueva", variant = "primary", elem_id = "btn_save") | |
| def init_load(): | |
| items, v_df = dm.get_gallery_items() | |
| return items, v_df | |
| demo.load(init_load, outputs = [gallery, stored_view_df]) | |
| filter_type.change(ui_load_gallery, inputs = [filter_type, filter_order], outputs = [gallery, stored_view_df, current_filters]) | |
| filter_order.change(ui_load_gallery, inputs = [filter_type, filter_order], outputs = [gallery, stored_view_df, current_filters]) | |
| btn_refresh.click(refresh_all_data, outputs = [new_type, new_coll, new_brand, gallery, error_banner, stored_view_df]) | |
| # 2. Navigation | |
| nav_outputs = [tabs_interface, is_editing, new_img, new_type, new_coll, new_brand, new_season, new_desc] | |
| def nav_main(): return gr.update(selected = 0), False, None, "", "", "", "N/A", "" | |
| def nav_edit(): return gr.update(selected = 1), True | |
| def nav_new(): return gr.update(selected = 2), False | |
| def cancel_del(): return gr.update(visible = True), gr.update(visible = False) | |
| btn_goto_edit.click(nav_edit, outputs = [tabs_interface, is_editing]) | |
| btn_goto_new.click(nav_new, outputs = [tabs_interface, is_editing]) | |
| btn_edit_cancel.click(nav_main, outputs = nav_outputs) | |
| btn_new_cancel.click(nav_main, outputs = nav_outputs) | |
| # 3. Selection | |
| gallery.select(ui_get_details, | |
| inputs = [is_editing, stored_view_df], | |
| outputs = [out_val, out_sku, out_date, out_type, out_brand, out_season, out_coll, out_desc, | |
| edit_img, edit_type, edit_season, edit_coll, edit_desc, | |
| selected_idx_holder]) | |
| # 4. Save Edit | |
| btn_edit_save.click(ui_save_edit, | |
| inputs = [edit_img, edit_type, edit_season, edit_coll, edit_desc, selected_idx_holder, stored_view_df, current_filters], | |
| outputs = [gallery, stored_view_df] | |
| ).then(nav_main, outputs = nav_outputs) | |
| # 5. Save New | |
| btn_new_save.click(ui_save_new, | |
| inputs = [new_img, new_type, new_coll, new_season, new_brand, new_desc, current_filters], | |
| outputs = [gallery, new_sku_holder, stored_view_df] | |
| ).then(refresh_all_data, outputs = [new_type, new_coll, new_brand, gallery, error_banner, stored_view_df]) \ | |
| .then(nav_main, outputs = nav_outputs) | |
| # 6. Delete (Invalidate) | |
| btn_edit_delete.click(lambda: (gr.update(visible = False), gr.update(visible = True)), | |
| outputs = [btn_edit_delete, confirm_group]) | |
| btn_del_yes.click(ui_delete_confirm, inputs = [selected_idx_holder, stored_view_df, current_filters], outputs = [gallery, stored_view_df]) \ | |
| .then(cancel_del, outputs = [btn_edit_delete, confirm_group]) \ | |
| .then(nav_main, outputs = nav_outputs) | |
| btn_del_no.click(cancel_del, outputs = [btn_edit_delete, confirm_group]) | |
| if __name__ == "__main__": | |
| demo.launch() |