Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -24,11 +24,6 @@ def normalize_columns(cols):
|
|
| 24 |
return [c.strip().title() for c in cols]
|
| 25 |
|
| 26 |
def load_sheet_df(name):
|
| 27 |
-
"""
|
| 28 |
-
Load a sheet into a DataFrame without confusing duplicates in
|
| 29 |
-
the header row. We fetch all values, dedupe the first row,
|
| 30 |
-
then build a DataFrame.
|
| 31 |
-
"""
|
| 32 |
ws = client.open_by_url(SHEET_URL).worksheet(name)
|
| 33 |
data = ws.get_all_values()
|
| 34 |
if not data:
|
|
@@ -131,7 +126,6 @@ def compute_insights():
|
|
| 131 |
# -------------------- USER MANAGEMENT --------------------
|
| 132 |
def load_users():
|
| 133 |
df = load_sheet_df("Users")
|
| 134 |
-
# select & rename your columns as needed
|
| 135 |
want = [
|
| 136 |
"Id", "Email", "Name", "Business", "Role",
|
| 137 |
"Daily Phone Call Target", "Daily Phone Appointment Target",
|
|
@@ -148,9 +142,66 @@ def load_users():
|
|
| 148 |
def save_users(df):
|
| 149 |
ws = client.open_by_url(SHEET_URL).worksheet("Users")
|
| 150 |
ws.clear()
|
| 151 |
-
set_with_dataframe(ws, df)
|
| 152 |
return "β
Users saved!"
|
| 153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
# -------------------- UI LAYOUT --------------------
|
| 155 |
with gr.Blocks(title="Graffiti Admin Dashboard") as app:
|
| 156 |
gr.Markdown("# π Graffiti Admin Dashboard")
|
|
@@ -198,6 +249,45 @@ with gr.Blocks(title="Graffiti Admin Dashboard") as app:
|
|
| 198 |
det_l = gr.Dataframe(label="π Details")
|
| 199 |
btn_l.click(lambda: (get_leads_summary(), get_leads_detail()), None, [sum_l, det_l])
|
| 200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
# -- Insights --
|
| 202 |
with gr.Tab("Insights"):
|
| 203 |
btn_i = gr.Button("Generate Insights")
|
|
|
|
| 24 |
return [c.strip().title() for c in cols]
|
| 25 |
|
| 26 |
def load_sheet_df(name):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
ws = client.open_by_url(SHEET_URL).worksheet(name)
|
| 28 |
data = ws.get_all_values()
|
| 29 |
if not data:
|
|
|
|
| 126 |
# -------------------- USER MANAGEMENT --------------------
|
| 127 |
def load_users():
|
| 128 |
df = load_sheet_df("Users")
|
|
|
|
| 129 |
want = [
|
| 130 |
"Id", "Email", "Name", "Business", "Role",
|
| 131 |
"Daily Phone Call Target", "Daily Phone Appointment Target",
|
|
|
|
| 142 |
def save_users(df):
|
| 143 |
ws = client.open_by_url(SHEET_URL).worksheet("Users")
|
| 144 |
ws.clear()
|
| 145 |
+
set_with_dataframe(ws, df)
|
| 146 |
return "β
Users saved!"
|
| 147 |
|
| 148 |
+
# -------------------- QUOTES TAB UTILS --------------------
|
| 149 |
+
def get_quotes_df():
|
| 150 |
+
df = load_sheet_df("LiveQuotes")
|
| 151 |
+
df.columns = [c.strip() for c in df.columns]
|
| 152 |
+
return df
|
| 153 |
+
|
| 154 |
+
def rep_choices_quotes():
|
| 155 |
+
df = get_quotes_df()
|
| 156 |
+
return sorted(df["Rep"].dropna().unique().tolist()) if "Rep" in df else []
|
| 157 |
+
|
| 158 |
+
def quote_year_choices():
|
| 159 |
+
df = get_quotes_df()
|
| 160 |
+
if "Year" in df.columns:
|
| 161 |
+
years = sorted(df["Year"].dropna().unique().astype(str))
|
| 162 |
+
return years
|
| 163 |
+
if "Date" in df.columns:
|
| 164 |
+
years = pd.to_datetime(df["Date"], errors="coerce").dt.year.dropna().unique()
|
| 165 |
+
return sorted(years.astype(str))
|
| 166 |
+
return []
|
| 167 |
+
|
| 168 |
+
def quote_month_choices(year=None):
|
| 169 |
+
df = get_quotes_df()
|
| 170 |
+
if year and "Year" in df.columns and "Month" in df.columns:
|
| 171 |
+
months = df[df["Year"].astype(str) == str(year)]["Month"].dropna().unique()
|
| 172 |
+
months = [str(int(m)) for m in sorted(pd.to_numeric(months, errors="coerce").dropna().unique())]
|
| 173 |
+
return months
|
| 174 |
+
return []
|
| 175 |
+
|
| 176 |
+
def quotes_summary(year=None, month=None):
|
| 177 |
+
df = get_quotes_df()
|
| 178 |
+
if "Rep" not in df.columns or "Total" not in df.columns:
|
| 179 |
+
return pd.DataFrame([{"Error": "Missing Rep or Total column"}])
|
| 180 |
+
if year and "Year" in df.columns:
|
| 181 |
+
df = df[df["Year"].astype(str) == str(year)]
|
| 182 |
+
if month and "Month" in df.columns:
|
| 183 |
+
df = df[df["Month"].astype(str) == str(month)]
|
| 184 |
+
df["Total"] = pd.to_numeric(df["Total"].astype(str).str.replace(",", ""), errors="coerce")
|
| 185 |
+
summary = (
|
| 186 |
+
df.groupby("Rep")
|
| 187 |
+
.agg({"Document No.": "count", "Total": "sum"})
|
| 188 |
+
.rename(columns={"Document No.": "Total Quotes", "Total": "Total Value"})
|
| 189 |
+
.reset_index()
|
| 190 |
+
)
|
| 191 |
+
summary["Total Value"] = summary["Total Value"].fillna(0).round(2)
|
| 192 |
+
return summary
|
| 193 |
+
|
| 194 |
+
def get_rep_quotes_filtered(rep, year=None, month=None):
|
| 195 |
+
df = get_quotes_df()
|
| 196 |
+
if "Rep" not in df.columns:
|
| 197 |
+
return pd.DataFrame([{"Error": "Missing Rep column"}])
|
| 198 |
+
df = df[df["Rep"] == rep]
|
| 199 |
+
if year and "Year" in df.columns:
|
| 200 |
+
df = df[df["Year"].astype(str) == str(year)]
|
| 201 |
+
if month and "Month" in df.columns:
|
| 202 |
+
df = df[df["Month"].astype(str) == str(month)]
|
| 203 |
+
return df
|
| 204 |
+
|
| 205 |
# -------------------- UI LAYOUT --------------------
|
| 206 |
with gr.Blocks(title="Graffiti Admin Dashboard") as app:
|
| 207 |
gr.Markdown("# π Graffiti Admin Dashboard")
|
|
|
|
| 249 |
det_l = gr.Dataframe(label="π Details")
|
| 250 |
btn_l.click(lambda: (get_leads_summary(), get_leads_detail()), None, [sum_l, det_l])
|
| 251 |
|
| 252 |
+
# -- Quotes Tab (NEW) --
|
| 253 |
+
with gr.Tab("Quotes"):
|
| 254 |
+
gr.Markdown("### π Quotes Summary by Rep")
|
| 255 |
+
year_qs = gr.Dropdown(choices=[""] + quote_year_choices(), label="Year (optional)", value="")
|
| 256 |
+
month_qs = gr.Dropdown(choices=[""], label="Month (optional, needs year)", value="")
|
| 257 |
+
btn_qs = gr.Button("Show Quotes Summary")
|
| 258 |
+
sum_qs = gr.Dataframe(label="Summary by Rep")
|
| 259 |
+
|
| 260 |
+
# Dynamic month options for summary
|
| 261 |
+
def update_month_choices_summary(year):
|
| 262 |
+
if year:
|
| 263 |
+
return gr.Dropdown.update(choices=[""] + quote_month_choices(year), value="")
|
| 264 |
+
else:
|
| 265 |
+
return gr.Dropdown.update(choices=[""], value="")
|
| 266 |
+
year_qs.change(update_month_choices_summary, year_qs, month_qs)
|
| 267 |
+
|
| 268 |
+
def quotes_summary_wrapper(year, month):
|
| 269 |
+
return quotes_summary(year if year else None, month if month else None)
|
| 270 |
+
btn_qs.click(quotes_summary_wrapper, [year_qs, month_qs], sum_qs)
|
| 271 |
+
|
| 272 |
+
gr.Markdown("### π View All Quotes for a Rep, Year, and Month")
|
| 273 |
+
rep_q = gr.Dropdown(choices=rep_choices_quotes(), label="Select Rep")
|
| 274 |
+
year_q = gr.Dropdown(choices=[""] + quote_year_choices(), label="Year (optional)", value="")
|
| 275 |
+
month_q = gr.Dropdown(choices=[""], label="Month (optional, needs year)", value="")
|
| 276 |
+
btn_qr = gr.Button("Show Quotes")
|
| 277 |
+
tbl_qr = gr.Dataframe(label="Quotes for Selection")
|
| 278 |
+
|
| 279 |
+
# Dynamic month options for rep quotes
|
| 280 |
+
def update_month_choices(year):
|
| 281 |
+
if year:
|
| 282 |
+
return gr.Dropdown.update(choices=[""] + quote_month_choices(year), value="")
|
| 283 |
+
else:
|
| 284 |
+
return gr.Dropdown.update(choices=[""], value="")
|
| 285 |
+
year_q.change(update_month_choices, year_q, month_q)
|
| 286 |
+
|
| 287 |
+
def get_rep_quotes_filtered_wrapper(rep, year, month):
|
| 288 |
+
return get_rep_quotes_filtered(rep, year if year else None, month if month else None)
|
| 289 |
+
btn_qr.click(get_rep_quotes_filtered_wrapper, [rep_q, year_q, month_q], tbl_qr)
|
| 290 |
+
|
| 291 |
# -- Insights --
|
| 292 |
with gr.Tab("Insights"):
|
| 293 |
btn_i = gr.Button("Generate Insights")
|