Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -11,10 +11,15 @@ client = gspread.authorize(creds)
|
|
11 |
sheet_url = "https://docs.google.com/spreadsheets/d/1if4KoVQvw5ZbhknfdZbzMkcTiPfsD6bz9V3a1th-bwQ"
|
12 |
|
13 |
# -------------------- UTILS --------------------
|
|
|
|
|
|
|
|
|
14 |
def load_sheet(sheet_name):
|
15 |
try:
|
16 |
sheet = client.open_by_url(sheet_url).worksheet(sheet_name)
|
17 |
df = pd.DataFrame(sheet.get_all_records())
|
|
|
18 |
return df
|
19 |
except Exception as e:
|
20 |
return pd.DataFrame([{"Error": str(e)}])
|
@@ -29,22 +34,22 @@ def filter_week(df, date_column, rep_column=None, rep=None):
|
|
29 |
df[date_column] = pd.to_datetime(df[date_column], errors='coerce').dt.date
|
30 |
start, end = get_current_week_range()
|
31 |
filtered = df[(df[date_column] >= start) & (df[date_column] <= end)]
|
32 |
-
if rep
|
33 |
filtered = filtered[filtered[rep_column] == rep]
|
34 |
return filtered
|
35 |
|
36 |
def filter_date(df, date_column, rep_column, y, m, d, rep):
|
37 |
try:
|
38 |
-
|
39 |
except:
|
40 |
return pd.DataFrame([{"Error": "Invalid date input"}])
|
41 |
-
df[date_column] = pd.to_datetime(df[date_column], errors='coerce').dt.date
|
42 |
-
filtered = df[df[date_column] ==
|
43 |
-
if rep
|
44 |
-
filtered = filtered[
|
45 |
return filtered
|
46 |
|
47 |
-
# -------------------- FUNCTIONS --------------------
|
48 |
def get_calls(rep=None):
|
49 |
df = load_sheet("Calls")
|
50 |
if "Call Date" not in df.columns:
|
@@ -69,11 +74,38 @@ def search_appointments_by_date(y, m, d, rep):
|
|
69 |
return pd.DataFrame([{"Error": "Missing 'Appointment Date' column"}])
|
70 |
return filter_date(df, "Appointment Date", "Rep", y, m, d, rep)
|
71 |
|
72 |
-
def
|
73 |
df = load_sheet("AllocatedLeads")
|
74 |
-
|
|
|
|
|
75 |
return pd.DataFrame([{"Error": "Missing 'Assigned Rep' or 'Company Name' column"}])
|
76 |
-
return df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
# -------------------- DROPDOWN OPTIONS --------------------
|
79 |
def rep_options(sheet_name, rep_col):
|
@@ -82,7 +114,7 @@ def rep_options(sheet_name, rep_col):
|
|
82 |
return sorted(df[rep_col].dropna().unique().tolist())
|
83 |
return []
|
84 |
|
85 |
-
# -------------------- UI --------------------
|
86 |
with gr.Blocks(title="Graffiti Admin Dashboard") as app:
|
87 |
gr.Markdown("# 📆 Graffiti Admin Dashboard")
|
88 |
|
@@ -102,19 +134,42 @@ with gr.Blocks(title="Graffiti Admin Dashboard") as app:
|
|
102 |
with gr.Tab("Appointments Report"):
|
103 |
rep_appt = gr.Dropdown(label="Optional Rep Filter", choices=rep_options("Appointments", "Rep"), allow_custom_value=True)
|
104 |
appt_btn = gr.Button("Load Current Week Appointments")
|
105 |
-
|
106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
|
108 |
gr.Markdown("### 🔍 Search Appointments by Specific Date")
|
109 |
y2, m2, d2 = gr.Textbox(label="Year"), gr.Textbox(label="Month"), gr.Textbox(label="Day")
|
110 |
rep2 = gr.Dropdown(label="Optional Rep Filter", choices=rep_options("Appointments", "Rep"), allow_custom_value=True)
|
111 |
appt_date_btn = gr.Button("Search Appointments by Date")
|
112 |
-
|
113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
|
115 |
with gr.Tab("Appointed Leads"):
|
116 |
-
leads_btn
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
sheet_url = "https://docs.google.com/spreadsheets/d/1if4KoVQvw5ZbhknfdZbzMkcTiPfsD6bz9V3a1th-bwQ"
|
12 |
|
13 |
# -------------------- UTILS --------------------
|
14 |
+
def normalize_columns(df):
|
15 |
+
df.columns = df.columns.str.strip().str.title() # e.g. “appointment date ” → “Appointment Date”
|
16 |
+
return df
|
17 |
+
|
18 |
def load_sheet(sheet_name):
|
19 |
try:
|
20 |
sheet = client.open_by_url(sheet_url).worksheet(sheet_name)
|
21 |
df = pd.DataFrame(sheet.get_all_records())
|
22 |
+
df = normalize_columns(df)
|
23 |
return df
|
24 |
except Exception as e:
|
25 |
return pd.DataFrame([{"Error": str(e)}])
|
|
|
34 |
df[date_column] = pd.to_datetime(df[date_column], errors='coerce').dt.date
|
35 |
start, end = get_current_week_range()
|
36 |
filtered = df[(df[date_column] >= start) & (df[date_column] <= end)]
|
37 |
+
if rep:
|
38 |
filtered = filtered[filtered[rep_column] == rep]
|
39 |
return filtered
|
40 |
|
41 |
def filter_date(df, date_column, rep_column, y, m, d, rep):
|
42 |
try:
|
43 |
+
target = datetime(int(y), int(m), int(d)).date()
|
44 |
except:
|
45 |
return pd.DataFrame([{"Error": "Invalid date input"}])
|
46 |
+
df[date_column] = pd.to_datetime(df[date_column], errors='coerce').dt.date
|
47 |
+
filtered = df[df[date_column] == target]
|
48 |
+
if rep:
|
49 |
+
filtered = filtered[filtered[rep_column] == rep]
|
50 |
return filtered
|
51 |
|
52 |
+
# -------------------- REPORT FUNCTIONS --------------------
|
53 |
def get_calls(rep=None):
|
54 |
df = load_sheet("Calls")
|
55 |
if "Call Date" not in df.columns:
|
|
|
74 |
return pd.DataFrame([{"Error": "Missing 'Appointment Date' column"}])
|
75 |
return filter_date(df, "Appointment Date", "Rep", y, m, d, rep)
|
76 |
|
77 |
+
def get_leads_detail():
|
78 |
df = load_sheet("AllocatedLeads")
|
79 |
+
# normalize expected names if necessary:
|
80 |
+
df = df.rename(columns={"Assigned Rep": "Assigned Rep", "Company Name": "Company Name"})
|
81 |
+
if "Assigned Rep" not in df.columns or "Company Name" not in df.columns:
|
82 |
return pd.DataFrame([{"Error": "Missing 'Assigned Rep' or 'Company Name' column"}])
|
83 |
+
return df
|
84 |
+
|
85 |
+
def get_leads_summary():
|
86 |
+
df = get_leads_detail()
|
87 |
+
if "Error" in df.columns:
|
88 |
+
return df
|
89 |
+
# count number of leads per rep
|
90 |
+
summary = df.groupby("Assigned Rep").size().reset_index(name="Leads Count")
|
91 |
+
return summary
|
92 |
+
|
93 |
+
# -------------------- INSIGHTS (Top Performers) --------------------
|
94 |
+
def compute_insights():
|
95 |
+
calls = get_calls()
|
96 |
+
appts = get_appointments()
|
97 |
+
leads = get_leads_detail()
|
98 |
+
|
99 |
+
top_calls = calls.groupby("Rep").size().idxmax() if not calls.empty else "N/A"
|
100 |
+
top_appts = appts.groupby("Rep").size().idxmax() if not appts.empty else "N/A"
|
101 |
+
top_leads = leads.groupby("Assigned Rep").size().idxmax() if "Assigned Rep" in leads.columns else "N/A"
|
102 |
+
|
103 |
+
insights = pd.DataFrame([
|
104 |
+
{"Metric": "Most Calls This Week", "Rep": top_calls},
|
105 |
+
{"Metric": "Most Appointments This Week", "Rep": top_appts},
|
106 |
+
{"Metric": "Most Leads Allocated", "Rep": top_leads},
|
107 |
+
])
|
108 |
+
return insights
|
109 |
|
110 |
# -------------------- DROPDOWN OPTIONS --------------------
|
111 |
def rep_options(sheet_name, rep_col):
|
|
|
114 |
return sorted(df[rep_col].dropna().unique().tolist())
|
115 |
return []
|
116 |
|
117 |
+
# -------------------- UI LAYOUT --------------------
|
118 |
with gr.Blocks(title="Graffiti Admin Dashboard") as app:
|
119 |
gr.Markdown("# 📆 Graffiti Admin Dashboard")
|
120 |
|
|
|
134 |
with gr.Tab("Appointments Report"):
|
135 |
rep_appt = gr.Dropdown(label="Optional Rep Filter", choices=rep_options("Appointments", "Rep"), allow_custom_value=True)
|
136 |
appt_btn = gr.Button("Load Current Week Appointments")
|
137 |
+
appt_summary = gr.Dataframe(label="📊 Weekly Appointments Summary by Rep")
|
138 |
+
appt_table = gr.Dataframe()
|
139 |
+
appt_btn.click(
|
140 |
+
fn=lambda rep: (get_appointments(rep).groupby("Rep").size().reset_index(name="Count"),
|
141 |
+
get_appointments(rep)),
|
142 |
+
inputs=rep_appt,
|
143 |
+
outputs=[appt_summary, appt_table]
|
144 |
+
)
|
145 |
|
146 |
gr.Markdown("### 🔍 Search Appointments by Specific Date")
|
147 |
y2, m2, d2 = gr.Textbox(label="Year"), gr.Textbox(label="Month"), gr.Textbox(label="Day")
|
148 |
rep2 = gr.Dropdown(label="Optional Rep Filter", choices=rep_options("Appointments", "Rep"), allow_custom_value=True)
|
149 |
appt_date_btn = gr.Button("Search Appointments by Date")
|
150 |
+
appt_date_summary = gr.Dataframe(label="📊 Appointments Summary for Date by Rep")
|
151 |
+
appt_date_table = gr.Dataframe()
|
152 |
+
appt_date_btn.click(
|
153 |
+
fn=lambda y,m,d,rep: (
|
154 |
+
search_appointments_by_date(y,m,d,rep).groupby("Rep").size().reset_index(name="Count"),
|
155 |
+
search_appointments_by_date(y,m,d,rep)
|
156 |
+
),
|
157 |
+
inputs=[y2, m2, d2, rep2],
|
158 |
+
outputs=[appt_date_summary, appt_date_table]
|
159 |
+
)
|
160 |
|
161 |
with gr.Tab("Appointed Leads"):
|
162 |
+
leads_btn = gr.Button("View Appointed Leads")
|
163 |
+
leads_summary= gr.Dataframe(label="📊 Leads Count by Rep")
|
164 |
+
leads_detail = gr.Dataframe(label="🔎 Detailed Leads")
|
165 |
+
leads_btn.click(
|
166 |
+
fn=lambda: (get_leads_summary(), get_leads_detail()),
|
167 |
+
outputs=[leads_summary, leads_detail]
|
168 |
+
)
|
169 |
+
|
170 |
+
with gr.Tab("Insights"):
|
171 |
+
insights_btn = gr.Button("Generate Insights")
|
172 |
+
insights_tbl = gr.Dataframe()
|
173 |
+
insights_btn.click(fn=compute_insights, outputs=insights_tbl)
|
174 |
+
|
175 |
+
app.launch()
|