"""Dashboard interactivo para pm-dashboard β€” Streamlit.""" import streamlit as st import pandas as pd import sys import os sys.path.insert(0, os.path.dirname(__file__)) from airtable_client import AirtableClient st.set_page_config( page_title="PM Dashboard", layout="wide", initial_sidebar_state="expanded", ) @st.cache_data(ttl=300, show_spinner="Cargando datos de Airtable...") def _load_data(): at = AirtableClient() return { "projects": at.get_projects(), "tasks": at.get_tasks(), "clients": at.get_clients(), "areas": at.get_areas(), } data = _load_data() client_name_by_id = {c["id"]: (c["display_name"] or c["company_name"]) for c in data["clients"]} project_name_by_id = {p["id"]: p["project"] for p in data["projects"]} area_name_by_id = {a["id"]: a["area"] for a in data["areas"]} st.title("πŸ“Š PM Dashboard") tab_proyectos, tab_tareas, tab_clientes, tab_areas = st.tabs( ["πŸ“ Proyectos", "βœ… Tareas", "πŸ‘₯ Clientes", "πŸ—‚οΈ Áreas"] ) # ── Proyectos ─────────────────────────────────────────────────────────────── # with tab_proyectos: df = pd.DataFrame(data["projects"]) if df.empty: st.info("No hay proyectos.") else: df["client"] = df["client_ids"].apply( lambda ids: ", ".join(client_name_by_id.get(i, "") for i in ids) ) col1, col2 = st.columns(2) status_filter = col1.multiselect("Status", sorted(df["status"].dropna().unique())) client_filter = col2.multiselect("Cliente", sorted(client_name_by_id.values())) view = df.copy() if status_filter: view = view[view["status"].isin(status_filter)] if client_filter: view = view[view["client"].apply(lambda c: any(cf in c for cf in client_filter))] st.dataframe( view[["project", "status", "client", "project_lead", "kickoff_date", "due_date", "budget", "internal"]], use_container_width=True, hide_index=True, ) st.caption(f"{len(view)} de {len(df)} proyectos") # ── Tareas ────────────────────────────────────────────────────────────────── # with tab_tareas: df = pd.DataFrame(data["tasks"]) if df.empty: st.info("No hay tareas.") else: df["project"] = df["project_ids"].apply( lambda ids: ", ".join(project_name_by_id.get(i, "") for i in ids) ) df["area"] = df["area_ids"].apply( lambda ids: ", ".join(area_name_by_id.get(i, "") for i in ids) ) df["assigned"] = df["assigned_to"].apply(lambda names: ", ".join(names)) col1, col2, col3 = st.columns(3) status_filter = col1.multiselect("Status", sorted(df["status"].dropna().unique())) prioridad_filter = col2.multiselect("Prioridad", sorted(df["prioridad"].dropna().unique())) assigned_filter = col3.multiselect( "Asignado a", sorted({n for names in df["assigned_to"] for n in names}) ) view = df.copy() if status_filter: view = view[view["status"].isin(status_filter)] if prioridad_filter: view = view[view["prioridad"].isin(prioridad_filter)] if assigned_filter: view = view[view["assigned"].apply(lambda a: any(af in a for af in assigned_filter))] st.dataframe( view[["task", "project", "area", "status", "prioridad", "assigned", "due_date", "horas_estimadas"]], use_container_width=True, hide_index=True, ) st.caption(f"{len(view)} de {len(df)} tareas") # ── Clientes ──────────────────────────────────────────────────────────────── # with tab_clientes: df = pd.DataFrame(data["clients"]) if df.empty: st.info("No hay clientes.") else: df["n_proyectos"] = df["project_ids"].apply(len) df["n_tareas"] = df["task_ids"].apply(len) st.dataframe( df[["display_name", "company_name", "n_proyectos", "n_tareas"]], use_container_width=True, hide_index=True, ) # ── Áreas ─────────────────────────────────────────────────────────────────── # with tab_areas: df = pd.DataFrame(data["areas"]) if df.empty: st.info("No hay Γ‘reas.") else: df["n_tareas"] = df["task_ids"].apply(len) st.dataframe( df[["area", "descripcion", "n_tareas"]], use_container_width=True, hide_index=True, )