GuglielmoTor commited on
Commit
d5c03ab
·
verified ·
1 Parent(s): c88513d

Update ui/ui_generators.py

Browse files
Files changed (1) hide show
  1. ui/ui_generators.py +472 -355
ui/ui_generators.py CHANGED
@@ -21,7 +21,7 @@ from config import (
21
  )
22
 
23
  # Configure logging for this module if not already configured at app level
24
- # logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
25
 
26
  # --- Constants for Button Icons/Text ---
27
  # These are also defined/imported in app.py, ensure consistency
@@ -41,404 +41,521 @@ def build_home_tab_ui():
41
  Graphs, Reports, and OKR Table, allowing app.py to
42
  attach click handlers for tab navigation.
43
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  with gr.Column(scale=1, elem_classes="home-page-container"):
45
- # Main header with welcome message
46
- gr.Markdown("""
47
- <div style="text-align: center; padding: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; margin-bottom: 30px; box-shadow: 0 8px 25px rgba(0,0,0,0.15); color: white;">
48
- <h1 style="color: white; margin-bottom: 20px; font-size: 2.5em; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">
49
- 🚀 LinkedIn Employer Brand Analytics Dashboard
50
- </h1>
51
- <p style="font-size: 1.3em; line-height: 1.8; margin-bottom: 15px; text-shadow: 1px 1px 2px rgba(0,0,0,0.3);">
52
- Transform your LinkedIn presence with data-driven insights and actionable strategies
53
- </p>
54
- <p style="font-size: 1.1em; opacity: 0.9; text-shadow: 1px 1px 2px rgba(0,0,0,0.3);">
55
- Measure, analyze, and enhance your employer brand to attract top talent
56
- </p>
 
 
 
 
 
57
  </div>
58
  """)
59
 
60
- # Overview section
61
- gr.Markdown("""
62
- <div style="background-color: #f8f9fa; padding: 25px; border-radius: 12px; margin-bottom: 25px; border-left: 5px solid #007bff;">
63
- <h2 style="color: #2c3e50; margin-bottom: 15px; display: flex; align-items: center;">
64
- <span style="margin-right: 10px;">📊</span> What This Dashboard Offers
65
  </h2>
66
- <p style="font-size: 1.1em; color: #495057; line-height: 1.7; margin-bottom: 15px;">
67
- Our comprehensive analytics platform helps you understand and optimize your LinkedIn employer brand performance through:
68
  </p>
69
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; margin-top: 20px;">
70
- <div style="display: flex; align-items: center; padding: 10px;">
71
- <span style="font-size: 1.5em; margin-right: 12px;">📈</span>
72
- <span style="color: #495057;">Real-time data visualization and trend analysis</span>
73
  </div>
74
- <div style="display: flex; align-items: center; padding: 10px;">
75
- <span style="font-size: 1.5em; margin-right: 12px;">📋</span>
76
- <span style="color: #495057;">Automated quarterly and weekly performance reports</span>
77
  </div>
78
- <div style="display: flex; align-items: center; padding: 10px;">
79
- <span style="font-size: 1.5em; margin-right: 12px;">🎯</span>
80
- <span style="color: #495057;">AI-powered OKRs and actionable recommendations</span>
81
  </div>
82
- <div style="display: flex; align-items: center; padding: 10px;">
83
- <span style="font-size: 1.5em; margin-right: 12px;">🚀</span>
84
- <span style="color: #495057;">Strategic insights to improve employer branding</span>
85
  </div>
86
  </div>
87
  </div>
88
  """)
89
 
90
- # Main navigation cards
91
- with gr.Row(equal_height=True):
92
- with gr.Column():
93
- gr.Markdown("""
94
- <div style="background: linear-gradient(135deg, #4CAF50, #45a049); padding: 25px; border-radius: 15px; min-height: 220px; display: flex; flex-direction: column; justify-content: space-between; box-shadow: 0 6px 20px rgba(76, 175, 80, 0.3); transition: transform 0.3s ease;">
95
- <div>
96
- <h3 style="color: white; margin-bottom: 15px; font-size: 1.4em; display: flex; align-items: center;">
97
- <span style="font-size: 1.8em; margin-right: 12px;">📈</span> Interactive Graphs
98
- </h3>
99
- <p style="color: rgba(255,255,255,0.95); line-height: 1.6; font-size: 1.05em; margin-bottom: 20px;">
100
- Explore dynamic visualizations of your LinkedIn performance metrics. Track post engagement,
101
- follower growth, mentions sentiment, and identify trends over time with interactive charts
102
- and filtering options.
103
- </p>
 
 
 
 
 
104
  </div>
105
- <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 15px;">
106
- <div style="color: rgba(255,255,255,0.8); font-size: 0.9em;">
107
- ✨ Real-time analytics<br/>
108
- 📊 Multiple chart types<br/>
109
- 🔍 Advanced filtering
110
- </div>
111
- <div style="background: rgba(255,255,255,0.2); padding: 8px; border-radius: 8px;">
112
- <span style="font-size: 2em;">📊</span>
113
- </div>
114
  </div>
115
  </div>
116
- """)
117
- btn_graphs = gr.Button("🚀 Explore Graphs", variant="primary", size="lg",
118
- elem_classes="nav-button", scale=1)
119
 
120
- with gr.Column():
121
- gr.Markdown("""
122
- <div style="background: linear-gradient(135deg, #2196F3, #1976D2); padding: 25px; border-radius: 15px; min-height: 220px; display: flex; flex-direction: column; justify-content: space-between; box-shadow: 0 6px 20px rgba(33, 150, 243, 0.3); transition: transform 0.3s ease;">
123
- <div>
124
- <h3 style="color: white; margin-bottom: 15px; font-size: 1.4em; display: flex; align-items: center;">
125
- <span style="font-size: 1.8em; margin-right: 12px;">📊</span> Analysis Reports
126
- </h3>
127
- <p style="color: rgba(255,255,255,0.95); line-height: 1.6; font-size: 1.05em; margin-bottom: 20px;">
128
- Access comprehensive quarterly and weekly reports powered by AI analysis. Get detailed
129
- insights into your employer brand performance, competitor analysis, and market positioning
130
- with automated report generation.
131
- </p>
 
 
 
 
132
  </div>
133
- <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 15px;">
134
- <div style="color: rgba(255,255,255,0.8); font-size: 0.9em;">
135
- 📋 Automated reports<br/>
136
- 🤖 AI-powered insights<br/>
137
- 📅 Weekly & quarterly
138
- </div>
139
- <div style="background: rgba(255,255,255,0.2); padding: 8px; border-radius: 8px;">
140
- <span style="font-size: 2em;">📄</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  </div>
142
  </div>
143
  </div>
144
- """)
 
 
 
 
 
 
 
 
 
 
145
  btn_reports = gr.Button("📊 View Reports", variant="primary", size="lg",
146
  elem_classes="nav-button", scale=1)
147
 
148
  with gr.Row(equal_height=True):
149
  with gr.Column():
150
- gr.Markdown("""
151
- <div style="background: linear-gradient(135deg, #FF9800, #F57C00); padding: 25px; border-radius: 15px; min-height: 220px; display: flex; flex-direction: column; justify-content: space-between; box-shadow: 0 6px 20px rgba(255, 152, 0, 0.3); transition: transform 0.3s ease;">
152
- <div>
153
- <h3 style="color: white; margin-bottom: 15px; font-size: 1.4em; display: flex; align-items: center;">
154
- <span style="font-size: 1.8em; margin-right: 12px;">🎯</span> OKR Action Plan
155
- </h3>
156
- <p style="color: rgba(255,255,255,0.95); line-height: 1.6; font-size: 1.05em; margin-bottom: 20px;">
157
- Discover AI-generated Objectives and Key Results (OKRs) with concrete action items.
158
- Transform data insights into measurable goals and strategic initiatives to enhance
159
- your employer brand effectively.
160
- </p>
161
- </div>
162
- <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 15px;">
163
- <div style="color: rgba(255,255,255,0.8); font-size: 0.9em;">
164
- 🎯 Strategic objectives<br/>
165
- ✅ Actionable tasks<br/>
166
- 📈 Measurable outcomes
167
- </div>
168
- <div style="background: rgba(255,255,255,0.2); padding: 8px; border-radius: 8px;">
169
- <span style="font-size: 2em;">🎯</span>
170
- </div>
171
- </div>
172
- </div>
173
- """)
174
  btn_okr = gr.Button("🎯 Access OKRs", variant="primary", size="lg",
175
  elem_classes="nav-button", scale=1)
176
 
177
- # Quick stats or tips column
178
  with gr.Column():
179
- gr.Markdown("""
180
- <div style="background: linear-gradient(135deg, #9C27B0, #7B1FA2); padding: 25px; border-radius: 15px; min-height: 220px; display: flex; flex-direction: column; justify-content: space-between; box-shadow: 0 6px 20px rgba(156, 39, 176, 0.3);">
181
- <div>
182
- <h3 style="color: white; margin-bottom: 15px; font-size: 1.4em; display: flex; align-items: center;">
183
- <span style="font-size: 1.8em; margin-right: 12px;">💡</span> Getting Started
184
- </h3>
185
- <p style="color: rgba(255,255,255,0.95); line-height: 1.6; font-size: 1.05em;">
186
- New to employer brand analytics? Start with the <strong>Graphs</strong> section to
187
- understand your current performance, then check <strong>Reports</strong> for detailed
188
- analysis, and finally explore <strong>OKRs</strong> for actionable next steps.
189
- </p>
190
- </div>
191
- <div style="margin-top: 20px; padding: 15px; background: rgba(255,255,255,0.1); border-radius: 8px;">
192
- <div style="color: rgba(255,255,255,0.9); font-size: 0.95em; text-align: center;">
193
- <strong>💪 Pro Tip:</strong><br/>
194
- <span style="font-size: 0.9em;">Regular monitoring leads to 40% better employer brand performance</span>
195
- </div>
196
- </div>
197
- </div>
198
- """)
199
- # Optional: Add a help or documentation button
200
  btn_help = gr.Button("📚 Documentation", variant="secondary", size="lg",
201
  elem_classes="nav-button", scale=1)
202
 
203
- # Additional information section
204
- gr.Markdown("""
205
- <div style="background-color: #e8f4fd; padding: 20px; border-radius: 12px; margin-top: 25px; border: 1px solid #b8daff;">
206
- <h3 style="color: #004085; margin-bottom: 15px; display: flex; align-items: center;">
207
- <span style="margin-right: 10px;">ℹ️</span> How It Works
208
  </h3>
209
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;">
210
- <div style="display: flex; align-items: start;">
211
- <div style="background: #007bff; color: white; border-radius: 50%; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; margin-right: 15px; flex-shrink: 0; font-weight: bold;">1</div>
212
- <div>
213
- <strong style="color: #004085;">Data Collection</strong><br/>
214
- <span style="color: #495057; font-size: 0.95em;">Automatically syncs with your LinkedIn organization data</span>
215
  </div>
216
  </div>
217
- <div style="display: flex; align-items: start;">
218
- <div style="background: #007bff; color: white; border-radius: 50%; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; margin-right: 15px; flex-shrink: 0; font-weight: bold;">2</div>
219
- <div>
220
- <strong style="color: #004085;">AI Analysis</strong><br/>
221
- <span style="color: #495057; font-size: 0.95em;">Advanced algorithms analyze trends and generate insights</span>
222
  </div>
223
  </div>
224
- <div style="display: flex; align-items: start;">
225
- <div style="background: #007bff; color: white; border-radius: 50%; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; margin-right: 15px; flex-shrink: 0; font-weight: bold;">3</div>
226
- <div>
227
- <strong style="color: #004085;">Actionable Results</strong><br/>
228
- <span style="color: #495057; font-size: 0.95em;">Receive specific recommendations and measurable goals</span>
229
  </div>
230
  </div>
231
  </div>
232
  </div>
233
  """)
234
- return btn_graphs, btn_reports, btn_okr, btn_help # Return all buttons for click handlers
235
-
236
-
237
- def run_mentions_tab_display(token_state):
238
- """Generates HTML and a plot for the Mentions tab."""
239
- logging.info("Updating Mentions Tab display.")
240
- if not token_state or not token_state.get("token"):
241
- logging.warning("Mentions tab: Access denied. No token.")
242
- return "❌ Access denied. No token available for mentions.", None
243
-
244
- mentions_df = token_state.get("bubble_mentions_df", pd.DataFrame())
245
- if mentions_df.empty:
246
- logging.info("Mentions tab: No mentions data in Bubble.")
247
- return "<p style='text-align:center;'>No mentions data in Bubble. Try syncing.</p>", None
248
-
249
- html_parts = ["<h3 style='text-align:center;'>Recent Mentions</h3>"]
250
- display_columns = [col for col in [BUBBLE_MENTIONS_DATE_COLUMN_NAME, "mention_text", "sentiment_label", BUBBLE_MENTIONS_ID_COLUMN_NAME] if col in mentions_df.columns]
251
-
252
- mentions_df_display = mentions_df.copy()
253
- if BUBBLE_MENTIONS_DATE_COLUMN_NAME in mentions_df_display.columns:
254
- try:
255
- mentions_df_display[BUBBLE_MENTIONS_DATE_COLUMN_NAME] = pd.to_datetime(mentions_df_display[BUBBLE_MENTIONS_DATE_COLUMN_NAME], errors='coerce')
256
- mentions_df_display = mentions_df_display.sort_values(by=BUBBLE_MENTIONS_DATE_COLUMN_NAME, ascending=False)
257
- mentions_df_display[BUBBLE_MENTIONS_DATE_COLUMN_NAME] = mentions_df_display[BUBBLE_MENTIONS_DATE_COLUMN_NAME].dt.strftime(UI_DATE_FORMAT)
258
- except Exception as e:
259
- logging.error(f"Error formatting mention dates for tab display: {e}")
260
- html_parts.append("<p>Error formatting mention dates.</p>")
261
-
262
- if not display_columns or mentions_df_display[display_columns].empty:
263
- html_parts.append("<p>Required columns for mentions display are missing or no data after processing.</p>")
264
- else:
265
- html_parts.append(mentions_df_display[display_columns].head(20).to_html(escape=False, index=False, classes="table table-sm"))
266
-
267
- mentions_html_output = "\n".join(html_parts)
268
- fig = None
269
- fig_plot_local = None
270
- if not mentions_df.empty and "sentiment_label" in mentions_df.columns:
271
- try:
272
- fig_plot_local, ax = plt.subplots(figsize=(6,4)) # Keep figsize for aspect ratio
273
- sentiment_counts = mentions_df["sentiment_label"].value_counts()
274
- sentiment_counts.plot(kind='bar', ax=ax, color=['#4CAF50', '#FFC107', '#F44336', '#9E9E9E', '#2196F3'])
275
- ax.set_title("Mention Sentiment Distribution", y=1.03)
276
- ax.set_ylabel("Count")
277
- plt.xticks(rotation=45, ha='right')
278
-
279
- plt.tight_layout()
280
- fig_plot_local.subplots_adjust(top=0.90)
281
- fig = fig_plot_local
282
- logging.info("Mentions tab: Sentiment distribution plot generated.")
283
- except Exception as e:
284
- logging.error(f"Error generating mentions plot: {e}", exc_info=True)
285
- fig = None
286
- finally:
287
- # Ensure plt.close is called on the figure object, not plt itself if it's not the same
288
- if fig_plot_local and fig_plot_local is not plt: # Check if fig_plot_local is a Figure object
289
- plt.close(fig_plot_local)
290
- return mentions_html_output, fig
291
-
292
-
293
- def run_follower_stats_tab_display(token_state):
294
- """Generates HTML and plots for the Follower Stats tab."""
295
- logging.info("Updating Follower Stats Tab display.")
296
- if not token_state or not token_state.get("token"):
297
- logging.warning("Follower stats tab: Access denied. No token.")
298
- return "❌ Access denied. No token available for follower stats.", None, None, None
299
-
300
- follower_stats_df_orig = token_state.get("bubble_follower_stats_df", pd.DataFrame())
301
- if follower_stats_df_orig.empty:
302
- logging.info("Follower stats tab: No follower stats data in Bubble.")
303
- return "<p style='text-align:center;'>No follower stats data in Bubble. Try syncing.</p>", None, None, None
304
-
305
- follower_stats_df = follower_stats_df_orig.copy()
306
- html_parts = ["<div style='padding:10px;'><h3 style='text-align:center;'>Follower Statistics Overview</h3>"]
307
-
308
- plot_monthly_gains = None
309
- plot_seniority_dist = None
310
- plot_industry_dist = None
311
-
312
- # Monthly Gains Plot
313
- fig_gains_local = None
314
- try:
315
- monthly_gains_df = follower_stats_df[
316
- (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_gains_monthly') &
317
- (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
318
- (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna()) &
319
- (follower_stats_df[FOLLOWER_STATS_PAID_COLUMN].notna())
320
- ].copy()
321
-
322
- if not monthly_gains_df.empty:
323
- monthly_gains_df.loc[:, FOLLOWER_STATS_CATEGORY_COLUMN_DT] = pd.to_datetime(monthly_gains_df[FOLLOWER_STATS_CATEGORY_COLUMN], errors='coerce')
324
- monthly_gains_df_sorted_table = monthly_gains_df.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=False)
325
-
326
- html_parts.append("<h4>Monthly Follower Gains (Last 13 Months):</h4>")
327
- table_display_df = monthly_gains_df_sorted_table.copy()
328
- table_display_df.loc[:,FOLLOWER_STATS_CATEGORY_COLUMN] = table_display_df[FOLLOWER_STATS_CATEGORY_COLUMN_DT].dt.strftime(UI_MONTH_FORMAT)
329
- html_parts.append(table_display_df[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(13).to_html(escape=True, index=False, classes="table table-sm"))
330
-
331
- monthly_gains_df_sorted_plot = monthly_gains_df.sort_values(by=FOLLOWER_STATS_CATEGORY_COLUMN_DT, ascending=True).copy()
332
- monthly_gains_df_sorted_plot.loc[:, '_plot_month'] = monthly_gains_df_sorted_plot[FOLLOWER_STATS_CATEGORY_COLUMN_DT].dt.strftime(UI_MONTH_FORMAT)
333
- plot_data = monthly_gains_df_sorted_plot.groupby('_plot_month').agg(
334
- organic=(FOLLOWER_STATS_ORGANIC_COLUMN, 'sum'),
335
- paid=(FOLLOWER_STATS_PAID_COLUMN, 'sum')
336
- ).reset_index()
337
- plot_data['_plot_month_dt'] = pd.to_datetime(plot_data['_plot_month'], format=UI_MONTH_FORMAT) # Ensure correct month format
338
- plot_data = plot_data.sort_values(by='_plot_month_dt')
339
-
340
-
341
- fig_gains_local, ax_gains = plt.subplots(figsize=(10,5)) # Keep figsize for aspect ratio
342
- ax_gains.plot(plot_data['_plot_month'], plot_data['organic'], marker='o', linestyle='-', label='Organic Gain')
343
- ax_gains.plot(plot_data['_plot_month'], plot_data['paid'], marker='x', linestyle='--', label='Paid Gain')
344
- ax_gains.set_title("Monthly Follower Gains Over Time", y=1.03)
345
- ax_gains.set_ylabel("Follower Count")
346
- ax_gains.set_xlabel("Month (YYYY-MM)")
347
- plt.xticks(rotation=45, ha='right')
348
- ax_gains.legend()
349
- plt.grid(True, linestyle='--', alpha=0.7)
350
-
351
- plt.tight_layout()
352
- fig_gains_local.subplots_adjust(top=0.90)
353
- plot_monthly_gains = fig_gains_local
354
- logging.info("Follower stats tab: Monthly gains plot generated.")
355
- else:
356
- html_parts.append("<p>No monthly follower gain data available or required columns missing.</p>")
357
- except Exception as e:
358
- logging.error(f"Error processing or plotting monthly gains: {e}", exc_info=True)
359
- html_parts.append("<p>Error displaying monthly follower gain data.</p>")
360
- plot_monthly_gains = None
361
- finally:
362
- if fig_gains_local and fig_gains_local is not plt:
363
- plt.close(fig_gains_local)
364
- html_parts.append("<hr/>")
365
-
366
-
367
- # Seniority Plot
368
- fig_seniority_local = None
369
- try:
370
- seniority_df = follower_stats_df[
371
- (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_seniority') &
372
- (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
373
- (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna())
374
- ].copy()
375
- if not seniority_df.empty:
376
- seniority_df_sorted = seniority_df.sort_values(by=FOLLOWER_STATS_ORGANIC_COLUMN, ascending=False)
377
- html_parts.append("<h4>Followers by Seniority (Top 10 Organic):</h4>")
378
- html_parts.append(seniority_df_sorted[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(10).to_html(escape=True, index=False, classes="table table-sm"))
379
-
380
- fig_seniority_local, ax_seniority = plt.subplots(figsize=(8,5)) # Keep figsize for aspect ratio
381
- top_n_seniority = seniority_df_sorted.nlargest(10, FOLLOWER_STATS_ORGANIC_COLUMN)
382
- ax_seniority.bar(top_n_seniority[FOLLOWER_STATS_CATEGORY_COLUMN], top_n_seniority[FOLLOWER_STATS_ORGANIC_COLUMN], color='skyblue')
383
- ax_seniority.set_title("Follower Distribution by Seniority (Top 10 Organic)", y=1.03)
384
- ax_seniority.set_ylabel("Organic Follower Count")
385
- plt.xticks(rotation=45, ha='right')
386
- plt.grid(axis='y', linestyle='--', alpha=0.7)
387
-
388
- plt.tight_layout()
389
- fig_seniority_local.subplots_adjust(top=0.88)
390
- plot_seniority_dist = fig_seniority_local
391
- logging.info("Follower stats tab: Seniority distribution plot generated.")
392
- else:
393
- html_parts.append("<p>No follower seniority data available or required columns missing.</p>")
394
- except Exception as e:
395
- logging.error(f"Error processing or plotting seniority data: {e}", exc_info=True)
396
- html_parts.append("<p>Error displaying follower seniority data.</p>")
397
- plot_seniority_dist = None
398
- finally:
399
- if fig_seniority_local and fig_seniority_local is not plt:
400
- plt.close(fig_seniority_local)
401
- html_parts.append("<hr/>")
402
-
403
- # Industry Plot
404
- fig_industry_local = None
405
- try:
406
- industry_df = follower_stats_df[
407
- (follower_stats_df[FOLLOWER_STATS_TYPE_COLUMN] == 'follower_industry') &
408
- (follower_stats_df[FOLLOWER_STATS_CATEGORY_COLUMN].notna()) &
409
- (follower_stats_df[FOLLOWER_STATS_ORGANIC_COLUMN].notna())
410
- ].copy()
411
- if not industry_df.empty:
412
- industry_df_sorted = industry_df.sort_values(by=FOLLOWER_STATS_ORGANIC_COLUMN, ascending=False)
413
- html_parts.append("<h4>Followers by Industry (Top 10 Organic):</h4>")
414
- html_parts.append(industry_df_sorted[[FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, FOLLOWER_STATS_PAID_COLUMN]].head(10).to_html(escape=True, index=False, classes="table table-sm"))
415
-
416
- fig_industry_local, ax_industry = plt.subplots(figsize=(8,5)) # Keep figsize for aspect ratio
417
- top_n_industry = industry_df_sorted.nlargest(10, FOLLOWER_STATS_ORGANIC_COLUMN)
418
- ax_industry.bar(top_n_industry[FOLLOWER_STATS_CATEGORY_COLUMN], top_n_industry[FOLLOWER_STATS_ORGANIC_COLUMN], color='lightcoral')
419
- ax_industry.set_title("Follower Distribution by Industry (Top 10 Organic)", y=1.03)
420
- ax_industry.set_ylabel("Organic Follower Count")
421
- plt.xticks(rotation=45, ha='right')
422
- plt.grid(axis='y', linestyle='--', alpha=0.7)
423
-
424
- plt.tight_layout()
425
- fig_industry_local.subplots_adjust(top=0.88)
426
- plot_industry_dist = fig_industry_local
427
- logging.info("Follower stats tab: Industry distribution plot generated.")
428
- else:
429
- html_parts.append("<p>No follower industry data available or required columns missing.</p>")
430
- except Exception as e:
431
- logging.error(f"Error processing or plotting industry data: {e}", exc_info=True)
432
- html_parts.append("<p>Error displaying follower industry data.</p>")
433
- plot_industry_dist = None
434
- finally:
435
- if fig_industry_local and fig_industry_local is not plt:
436
- plt.close(fig_industry_local)
437
-
438
-
439
- html_parts.append("</div>")
440
- follower_html_output = "\n".join(html_parts)
441
- return follower_html_output, plot_monthly_gains, plot_seniority_dist, plot_industry_dist
442
 
443
 
444
  def create_analytics_plot_panel(plot_label_str, plot_id_str):
 
21
  )
22
 
23
  # Configure logging for this module if not already configured at app level
24
+ # logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(module)s - %(message)s')
25
 
26
  # --- Constants for Button Icons/Text ---
27
  # These are also defined/imported in app.py, ensure consistency
 
41
  Graphs, Reports, and OKR Table, allowing app.py to
42
  attach click handlers for tab navigation.
43
  """
44
+ # Custom CSS for enhanced styling
45
+ css_styles = """
46
+ <style>
47
+ .hero-section {
48
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
49
+ padding: 60px 40px;
50
+ border-radius: 24px;
51
+ margin-bottom: 40px;
52
+ box-shadow: 0 20px 60px rgba(102, 126, 234, 0.25);
53
+ color: white;
54
+ text-align: center;
55
+ position: relative;
56
+ overflow: hidden;
57
+ }
58
+
59
+ .hero-section::before {
60
+ content: '';
61
+ position: absolute;
62
+ top: 0;
63
+ left: 0;
64
+ right: 0;
65
+ bottom: 0;
66
+ background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.05)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.05)"/><circle cx="50" cy="10" r="0.5" fill="rgba(255,255,255,0.03)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
67
+ opacity: 0.1;
68
+ }
69
+
70
+ .hero-content {
71
+ position: relative;
72
+ z-index: 1;
73
+ }
74
+
75
+ .hero-title {
76
+ font-size: 3.2em;
77
+ font-weight: 800;
78
+ margin-bottom: 24px;
79
+ background: linear-gradient(45deg, #ffffff, #e0e7ff);
80
+ -webkit-background-clip: text;
81
+ -webkit-text-fill-color: transparent;
82
+ background-clip: text;
83
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
84
+ line-height: 1.2;
85
+ }
86
+
87
+ .hero-subtitle {
88
+ font-size: 1.4em;
89
+ line-height: 1.6;
90
+ margin-bottom: 16px;
91
+ opacity: 0.95;
92
+ font-weight: 300;
93
+ }
94
+
95
+ .hero-description {
96
+ font-size: 1.1em;
97
+ opacity: 0.85;
98
+ font-weight: 300;
99
+ max-width: 600px;
100
+ margin: 0 auto;
101
+ }
102
+
103
+ .overview-section {
104
+ background: #ffffff;
105
+ padding: 40px;
106
+ border-radius: 20px;
107
+ margin-bottom: 40px;
108
+ box-shadow: 0 10px 40px rgba(0,0,0,0.08);
109
+ border: 1px solid rgba(0,0,0,0.05);
110
+ }
111
+
112
+ .overview-header {
113
+ color: #1a202c;
114
+ margin-bottom: 24px;
115
+ font-size: 2.2em;
116
+ font-weight: 700;
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 16px;
120
+ }
121
+
122
+ .overview-text {
123
+ font-size: 1.15em;
124
+ color: #4a5568;
125
+ line-height: 1.7;
126
+ margin-bottom: 32px;
127
+ font-weight: 400;
128
+ }
129
+
130
+ .features-grid {
131
+ display: grid;
132
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
133
+ gap: 24px;
134
+ margin-top: 32px;
135
+ }
136
+
137
+ .feature-item {
138
+ display: flex;
139
+ align-items: center;
140
+ padding: 20px;
141
+ background: linear-gradient(135deg, #f8fafc, #f1f5f9);
142
+ border-radius: 16px;
143
+ border: 1px solid rgba(0,0,0,0.06);
144
+ transition: all 0.3s ease;
145
+ }
146
+
147
+ .feature-item:hover {
148
+ transform: translateY(-2px);
149
+ box-shadow: 0 8px 25px rgba(0,0,0,0.1);
150
+ }
151
+
152
+ .feature-icon {
153
+ font-size: 2em;
154
+ margin-right: 16px;
155
+ filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
156
+ }
157
+
158
+ .feature-text {
159
+ color: #2d3748;
160
+ font-weight: 500;
161
+ font-size: 1.05em;
162
+ }
163
+
164
+ .nav-cards-container {
165
+ display: grid;
166
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
167
+ gap: 30px;
168
+ margin-bottom: 40px;
169
+ }
170
+
171
+ .nav-card {
172
+ padding: 36px;
173
+ border-radius: 20px;
174
+ min-height: 280px;
175
+ display: flex;
176
+ flex-direction: column;
177
+ justify-content: space-between;
178
+ box-shadow: 0 15px 45px rgba(0,0,0,0.12);
179
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
180
+ position: relative;
181
+ overflow: hidden;
182
+ }
183
+
184
+ .nav-card::before {
185
+ content: '';
186
+ position: absolute;
187
+ top: 0;
188
+ left: 0;
189
+ right: 0;
190
+ bottom: 0;
191
+ background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
192
+ opacity: 0;
193
+ transition: opacity 0.3s ease;
194
+ }
195
+
196
+ .nav-card:hover::before {
197
+ opacity: 1;
198
+ }
199
+
200
+ .nav-card:hover {
201
+ transform: translateY(-8px) scale(1.02);
202
+ box-shadow: 0 25px 60px rgba(0,0,0,0.15);
203
+ }
204
+
205
+ .nav-card-graphs {
206
+ background: linear-gradient(135deg, #4CAF50, #2E7D32);
207
+ }
208
+
209
+ .nav-card-reports {
210
+ background: linear-gradient(135deg, #2196F3, #1565C0);
211
+ }
212
+
213
+ .nav-card-okr {
214
+ background: linear-gradient(135deg, #FF9800, #E65100);
215
+ }
216
+
217
+ .nav-card-help {
218
+ background: linear-gradient(135deg, #9C27B0, #4A148C);
219
+ }
220
+
221
+ .nav-card-header {
222
+ color: white;
223
+ margin-bottom: 20px;
224
+ font-size: 1.5em;
225
+ font-weight: 700;
226
+ display: flex;
227
+ align-items: center;
228
+ gap: 16px;
229
+ }
230
+
231
+ .nav-card-icon {
232
+ font-size: 2.2em;
233
+ filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
234
+ }
235
+
236
+ .nav-card-description {
237
+ color: rgba(255,255,255,0.95);
238
+ line-height: 1.7;
239
+ font-size: 1.08em;
240
+ margin-bottom: 24px;
241
+ font-weight: 300;
242
+ }
243
+
244
+ .nav-card-footer {
245
+ display: flex;
246
+ justify-content: space-between;
247
+ align-items: center;
248
+ margin-top: 20px;
249
+ position: relative;
250
+ z-index: 1;
251
+ }
252
+
253
+ .nav-card-features {
254
+ color: rgba(255,255,255,0.85);
255
+ font-size: 0.95em;
256
+ line-height: 1.6;
257
+ }
258
+
259
+ .nav-card-badge {
260
+ background: rgba(255,255,255,0.2);
261
+ padding: 12px;
262
+ border-radius: 12px;
263
+ backdrop-filter: blur(10px);
264
+ }
265
+
266
+ .nav-card-badge-icon {
267
+ font-size: 2.2em;
268
+ }
269
+
270
+ .how-it-works-section {
271
+ background: linear-gradient(135deg, #f8fafc, #e2e8f0);
272
+ padding: 40px;
273
+ border-radius: 20px;
274
+ margin-top: 40px;
275
+ border: 1px solid rgba(0,0,0,0.06);
276
+ }
277
+
278
+ .how-it-works-header {
279
+ color: #1a202c;
280
+ margin-bottom: 32px;
281
+ font-size: 2em;
282
+ font-weight: 700;
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 16px;
286
+ }
287
+
288
+ .steps-grid {
289
+ display: grid;
290
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
291
+ gap: 32px;
292
+ }
293
+
294
+ .step-item {
295
+ display: flex;
296
+ align-items: flex-start;
297
+ gap: 20px;
298
+ }
299
+
300
+ .step-number {
301
+ background: linear-gradient(135deg, #667eea, #764ba2);
302
+ color: white;
303
+ border-radius: 50%;
304
+ width: 48px;
305
+ height: 48px;
306
+ display: flex;
307
+ align-items: center;
308
+ justify-content: center;
309
+ flex-shrink: 0;
310
+ font-weight: 700;
311
+ font-size: 1.2em;
312
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3);
313
+ }
314
+
315
+ .step-content {
316
+ flex: 1;
317
+ }
318
+
319
+ .step-title {
320
+ color: #1a202c;
321
+ font-weight: 700;
322
+ font-size: 1.2em;
323
+ margin-bottom: 8px;
324
+ }
325
+
326
+ .step-description {
327
+ color: #4a5568;
328
+ font-size: 1em;
329
+ line-height: 1.6;
330
+ }
331
+
332
+ .button-container {
333
+ margin-top: 24px;
334
+ }
335
+
336
+ @media (max-width: 768px) {
337
+ .hero-title {
338
+ font-size: 2.4em;
339
+ }
340
+
341
+ .nav-cards-container {
342
+ grid-template-columns: 1fr;
343
+ }
344
+
345
+ .nav-card {
346
+ min-height: 240px;
347
+ padding: 28px;
348
+ }
349
+
350
+ .hero-section {
351
+ padding: 40px 24px;
352
+ }
353
+
354
+ .overview-section, .how-it-works-section {
355
+ padding: 28px;
356
+ }
357
+ }
358
+ </style>
359
+ """
360
+
361
  with gr.Column(scale=1, elem_classes="home-page-container"):
362
+ # Inject custom CSS
363
+ gr.HTML(css_styles)
364
+
365
+ # Main hero section
366
+ gr.HTML("""
367
+ <div class="hero-section">
368
+ <div class="hero-content">
369
+ <h1 class="hero-title">
370
+ 🚀 LinkedIn Employer Brand Analytics
371
+ </h1>
372
+ <p class="hero-subtitle">
373
+ Transform your LinkedIn presence with data-driven insights and actionable strategies
374
+ </p>
375
+ <p class="hero-description">
376
+ Measure, analyze, and enhance your employer brand to attract top talent and build a stronger digital presence
377
+ </p>
378
+ </div>
379
  </div>
380
  """)
381
 
382
+ # Overview section with improved spacing
383
+ gr.HTML("""
384
+ <div class="overview-section">
385
+ <h2 class="overview-header">
386
+ <span>📊</span> What This Dashboard Offers
387
  </h2>
388
+ <p class="overview-text">
389
+ Our comprehensive analytics platform helps you understand and optimize your LinkedIn employer brand performance with real-time insights, automated reporting, and AI-powered recommendations.
390
  </p>
391
+ <div class="features-grid">
392
+ <div class="feature-item">
393
+ <span class="feature-icon">📈</span>
394
+ <span class="feature-text">Real-time data visualization and trend analysis</span>
395
  </div>
396
+ <div class="feature-item">
397
+ <span class="feature-icon">📋</span>
398
+ <span class="feature-text">Automated quarterly and weekly performance reports</span>
399
  </div>
400
+ <div class="feature-item">
401
+ <span class="feature-icon">🎯</span>
402
+ <span class="feature-text">AI-powered OKRs and actionable recommendations</span>
403
  </div>
404
+ <div class="feature-item">
405
+ <span class="feature-icon">🚀</span>
406
+ <span class="feature-text">Strategic insights to improve employer branding</span>
407
  </div>
408
  </div>
409
  </div>
410
  """)
411
 
412
+ # Navigation cards with improved layout
413
+ gr.HTML("""
414
+ <div class="nav-cards-container">
415
+ <div class="nav-card nav-card-graphs">
416
+ <div>
417
+ <h3 class="nav-card-header">
418
+ <span class="nav-card-icon">📈</span> Interactive Graphs
419
+ </h3>
420
+ <p class="nav-card-description">
421
+ Explore dynamic visualizations of your LinkedIn performance metrics. Track post engagement,
422
+ follower growth, mentions sentiment, and identify trends over time with interactive charts
423
+ and advanced filtering options.
424
+ </p>
425
+ </div>
426
+ <div class="nav-card-footer">
427
+ <div class="nav-card-features">
428
+ ✨ Real-time analytics<br/>
429
+ 📊 Multiple chart types<br/>
430
+ 🔍 Advanced filtering
431
  </div>
432
+ <div class="nav-card-badge">
433
+ <span class="nav-card-badge-icon">📊</span>
 
 
 
 
 
 
 
434
  </div>
435
  </div>
436
+ </div>
 
 
437
 
438
+ <div class="nav-card nav-card-reports">
439
+ <div>
440
+ <h3 class="nav-card-header">
441
+ <span class="nav-card-icon">📊</span> Analysis Reports
442
+ </h3>
443
+ <p class="nav-card-description">
444
+ Access comprehensive quarterly and weekly reports powered by AI analysis. Get detailed
445
+ insights into your employer brand performance, competitor analysis, and market positioning
446
+ with automated report generation.
447
+ </p>
448
+ </div>
449
+ <div class="nav-card-footer">
450
+ <div class="nav-card-features">
451
+ 📋 Automated reports<br/>
452
+ 🤖 AI-powered insights<br/>
453
+ 📅 Weekly & quarterly
454
  </div>
455
+ <div class="nav-card-badge">
456
+ <span class="nav-card-badge-icon">📄</span>
457
+ </div>
458
+ </div>
459
+ </div>
460
+
461
+ <div class="nav-card nav-card-okr">
462
+ <div>
463
+ <h3 class="nav-card-header">
464
+ <span class="nav-card-icon">🎯</span> OKR Action Plan
465
+ </h3>
466
+ <p class="nav-card-description">
467
+ Discover AI-generated Objectives and Key Results (OKRs) with concrete action items.
468
+ Transform data insights into measurable goals and strategic initiatives to enhance
469
+ your employer brand effectively.
470
+ </p>
471
+ </div>
472
+ <div class="nav-card-footer">
473
+ <div class="nav-card-features">
474
+ 🎯 Strategic objectives<br/>
475
+ ✅ Actionable tasks<br/>
476
+ 📈 Measurable outcomes
477
+ </div>
478
+ <div class="nav-card-badge">
479
+ <span class="nav-card-badge-icon">🎯</span>
480
+ </div>
481
+ </div>
482
+ </div>
483
+
484
+ <div class="nav-card nav-card-help">
485
+ <div>
486
+ <h3 class="nav-card-header">
487
+ <span class="nav-card-icon">💡</span> Getting Started
488
+ </h3>
489
+ <p class="nav-card-description">
490
+ New to employer brand analytics? Start with the Graphs section to
491
+ understand your current performance, then check Reports for detailed
492
+ analysis, and finally explore OKRs for actionable next steps.
493
+ </p>
494
+ </div>
495
+ <div class="nav-card-footer">
496
+ <div style="background: rgba(255,255,255,0.15); padding: 16px; border-radius: 12px; width: 100%; text-align: center;">
497
+ <div style="color: rgba(255,255,255,0.95); font-size: 1em;">
498
+ <strong>💪 Pro Tip:</strong><br/>
499
+ <span style="font-size: 0.9em;">Regular monitoring leads to 40% better employer brand performance</span>
500
  </div>
501
  </div>
502
  </div>
503
+ </div>
504
+ </div>
505
+ """)
506
+
507
+ # Create buttons with better spacing
508
+ with gr.Row(equal_height=True):
509
+ with gr.Column():
510
+ btn_graphs = gr.Button("🚀 Explore Graphs", variant="primary", size="lg",
511
+ elem_classes="nav-button", scale=1)
512
+
513
+ with gr.Column():
514
  btn_reports = gr.Button("📊 View Reports", variant="primary", size="lg",
515
  elem_classes="nav-button", scale=1)
516
 
517
  with gr.Row(equal_height=True):
518
  with gr.Column():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  btn_okr = gr.Button("🎯 Access OKRs", variant="primary", size="lg",
520
  elem_classes="nav-button", scale=1)
521
 
 
522
  with gr.Column():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  btn_help = gr.Button("📚 Documentation", variant="secondary", size="lg",
524
  elem_classes="nav-button", scale=1)
525
 
526
+ # How it works section with enhanced styling
527
+ gr.HTML("""
528
+ <div class="how-it-works-section">
529
+ <h3 class="how-it-works-header">
530
+ <span>ℹ️</span> How It Works
531
  </h3>
532
+ <div class="steps-grid">
533
+ <div class="step-item">
534
+ <div class="step-number">1</div>
535
+ <div class="step-content">
536
+ <div class="step-title">Data Collection</div>
537
+ <div class="step-description">Automatically syncs with your LinkedIn organization data and processes engagement metrics in real-time</div>
538
  </div>
539
  </div>
540
+ <div class="step-item">
541
+ <div class="step-number">2</div>
542
+ <div class="step-content">
543
+ <div class="step-title">AI Analysis</div>
544
+ <div class="step-description">Advanced algorithms analyze trends, sentiment, and performance patterns to generate actionable insights</div>
545
  </div>
546
  </div>
547
+ <div class="step-item">
548
+ <div class="step-number">3</div>
549
+ <div class="step-content">
550
+ <div class="step-title">Actionable Results</div>
551
+ <div class="step-description">Receive specific recommendations, measurable goals, and strategic action plans to improve your employer brand</div>
552
  </div>
553
  </div>
554
  </div>
555
  </div>
556
  """)
557
+
558
+ return btn_graphs, btn_reports, btn_okr, btn_help
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
 
560
 
561
  def create_analytics_plot_panel(plot_label_str, plot_id_str):