Skip to content

Commit 16e7c2b

Browse files
authored
Merge branch 'main' into ahmad/stock-market-dashboard
2 parents fc84cc7 + 9441b8b commit 16e7c2b

22 files changed

+829
-9
lines changed

.github/workflows/create-artifacts.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,4 @@ jobs:
7272
upload_url: ${{ github.event.release.upload_url }}
7373
asset_path: ./templates.json
7474
asset_name: templates.json
75-
asset_content_type: application/json
75+
asset_content_type: application/json

.github/workflows/deploy.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ jobs:
3838
stock_market_dashboard)
3939
echo "EXTRA_ARGS=" >> $GITHUB_ENV
4040
;;
41+
business_analytics_dashboard)
42+
echo "EXTRA_ARGS=" >> $GITHUB_ENV
43+
;;
4144
customer_data_app)
4245
cat .deploy/temporary_db.py >> ${{ matrix.folder }}/customer_data/customer_data.py
4346
echo "EXTRA_ARGS=--vmtype ${{ vars.CUSTOMER_DATA_VM_TYPE }}" >> $GITHUB_ENV
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Publish Templates
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
environment:
7+
description: 'Choose deployment environment'
8+
required: true
9+
type: choice
10+
options:
11+
- development
12+
- staging
13+
- production
14+
release_tag:
15+
description: 'Release tag (leave empty for latest)'
16+
required: false
17+
type: string
18+
19+
jobs:
20+
prepare-release-info:
21+
runs-on: ubuntu-latest
22+
outputs:
23+
release_info: ${{ steps.get-release.outputs.result }}
24+
steps:
25+
- name: Get release info
26+
id: get-release
27+
uses: actions/github-script@v6
28+
with:
29+
github-token: ${{ secrets.GITHUB_TOKEN }}
30+
result-encoding: json
31+
script: |
32+
let release;
33+
if ('${{ github.event.inputs.release_tag }}') {
34+
release = await github.rest.repos.getReleaseByTag({
35+
owner: context.repo.owner,
36+
repo: context.repo.repo,
37+
tag: '${{ github.event.inputs.release_tag }}'
38+
});
39+
} else {
40+
release = await github.rest.repos.getLatestRelease({
41+
owner: context.repo.owner,
42+
repo: context.repo.repo
43+
});
44+
}
45+
console.log(release.data.tag_name);
46+
return release.data.tag_name;
47+
48+
trigger-second-repo:
49+
needs: prepare-release-info
50+
runs-on: ubuntu-latest
51+
steps:
52+
- name: Trigger second repo
53+
uses: peter-evans/repository-dispatch@v2
54+
with:
55+
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
56+
repository: reflex-dev/flexgen
57+
event-type: release
58+
client-payload: '{"release": ${{ needs.prepare-release-info.outputs.release_info }}, "environment": "${{ github.event.inputs.environment }}"}'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.web
2+
*.db
3+
.states
4+
assets/external/
5+
*.py[cod]
6+
__pycache__/
7+
.DS_Store
8+
.idea/

business_analytics_dashboard/assets/favicon.ico

Whitespace-only changes.

business_analytics_dashboard/business_analytics_dashboard/__init__.py

Whitespace-only changes.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Basic Dashboard App"""
2+
3+
import reflex as rx
4+
5+
from business_analytics_dashboard.components.account_executive_metrics import (
6+
account_executive_metrics_table,
7+
)
8+
from business_analytics_dashboard.components.average_salary_chart import (
9+
average_salary_bar_chart,
10+
)
11+
from business_analytics_dashboard.components.department_pie_chart import (
12+
department_pie_chart,
13+
)
14+
from business_analytics_dashboard.components.sidebar import sidebar
15+
from business_analytics_dashboard.states.dashboard_state import DashboardState
16+
17+
18+
def index() -> rx.Component:
19+
"""The main dashboard page."""
20+
return rx.el.div(
21+
sidebar(),
22+
rx.el.main(
23+
rx.el.h1(
24+
"Business Analytics Dashboard",
25+
class_name="text-3xl font-bold text-gray-800 mb-6",
26+
),
27+
rx.el.div(
28+
department_pie_chart(),
29+
average_salary_bar_chart(),
30+
class_name="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6",
31+
),
32+
account_executive_metrics_table(),
33+
class_name="ml-64 p-8 bg-gray-100 min-h-screen w-full",
34+
on_mount=DashboardState.fetch_dashboard_data,
35+
),
36+
class_name="flex w-full",
37+
)
38+
39+
40+
app = rx.App(theme=rx.theme(appearance="light"))
41+
app.add_page(index)

business_analytics_dashboard/business_analytics_dashboard/components/__init__.py

Whitespace-only changes.
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import reflex as rx
2+
3+
from business_analytics_dashboard.states.dashboard_state import DashboardState
4+
5+
6+
def pagination_controls() -> rx.Component:
7+
"""Component for table pagination controls."""
8+
return rx.el.div(
9+
rx.el.span(
10+
f"Page {DashboardState.current_page} of {DashboardState.total_pages}",
11+
class_name="text-sm text-gray-700 mr-4",
12+
),
13+
rx.el.button(
14+
"Previous",
15+
on_click=DashboardState.previous_page,
16+
disabled=DashboardState.current_page <= 1,
17+
class_name="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed",
18+
),
19+
rx.el.button(
20+
"Next",
21+
on_click=DashboardState.next_page,
22+
disabled=DashboardState.current_page >= DashboardState.total_pages,
23+
class_name="px-4 py-2 text-sm font-medium text-gray-700 bg-white border-t border-b border-r border-gray-300 rounded-r-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed",
24+
),
25+
class_name="flex justify-end items-center mt-4",
26+
)
27+
28+
29+
def account_executive_metrics_table() -> rx.Component:
30+
"""Table displaying account executive metrics (employee data)."""
31+
return rx.el.div(
32+
rx.el.h2(
33+
"Account Executive Metrics",
34+
class_name="text-2xl font-semibold text-gray-800 mb-4",
35+
),
36+
rx.el.div(
37+
rx.el.div(
38+
rx.el.label(
39+
"Filter by Department:",
40+
html_for="department-select",
41+
class_name="text-sm font-medium text-gray-700 mr-2",
42+
),
43+
rx.el.select(
44+
rx.el.option("All", value="All"),
45+
rx.foreach(
46+
DashboardState.departments_for_filter,
47+
lambda dept: rx.el.option(dept, value=dept),
48+
),
49+
id="department-select",
50+
value=DashboardState.selected_department,
51+
on_change=DashboardState.set_selected_department,
52+
class_name="p-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900",
53+
),
54+
class_name="flex items-center",
55+
),
56+
rx.el.input(
57+
type="text",
58+
placeholder="Search in table...",
59+
default_value=DashboardState.search_query,
60+
on_change=DashboardState.set_search_query.debounce(300),
61+
class_name="w-full md:w-auto p-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900 placeholder-gray-400",
62+
),
63+
class_name="flex flex-col md:flex-row justify-between items-center mb-6 space-y-4 md:space-y-0",
64+
),
65+
rx.el.div(
66+
rx.cond(
67+
DashboardState.loading,
68+
rx.el.div(
69+
rx.el.p(
70+
"Loading employee data...",
71+
class_name="text-center text-gray-500 py-4",
72+
),
73+
class_name="w-full",
74+
),
75+
rx.el.div(
76+
rx.el.table(
77+
rx.el.thead(
78+
rx.el.tr(
79+
rx.el.th(
80+
"ID",
81+
class_name="p-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 bg-gray-50 border-b",
82+
),
83+
rx.el.th(
84+
"First Name",
85+
class_name="p-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 bg-gray-50 border-b",
86+
),
87+
rx.el.th(
88+
"Last Name",
89+
class_name="p-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 bg-gray-50 border-b",
90+
),
91+
rx.el.th(
92+
"Email",
93+
class_name="p-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 bg-gray-50 border-b",
94+
),
95+
rx.el.th(
96+
"Department",
97+
class_name="p-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 bg-gray-50 border-b",
98+
),
99+
rx.el.th(
100+
"Salary",
101+
class_name="p-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 bg-gray-50 border-b",
102+
),
103+
rx.el.th(
104+
"Projects Closed",
105+
class_name="p-3 text-center text-xs font-semibold uppercase tracking-wider text-gray-500 bg-gray-50 border-b",
106+
),
107+
rx.el.th(
108+
"Pending Projects",
109+
class_name="p-3 text-center text-xs font-semibold uppercase tracking-wider text-gray-500 bg-gray-50 border-b",
110+
),
111+
class_name="bg-gray-50",
112+
)
113+
),
114+
rx.el.tbody(
115+
rx.foreach(
116+
DashboardState.paginated_employees,
117+
lambda employee: rx.el.tr(
118+
rx.el.td(
119+
employee["employee_id"],
120+
class_name="p-3 border-b text-sm text-gray-700",
121+
),
122+
rx.el.td(
123+
employee["first_name"],
124+
class_name="p-3 border-b text-sm text-gray-700",
125+
),
126+
rx.el.td(
127+
employee["last_name"],
128+
class_name="p-3 border-b text-sm text-gray-700",
129+
),
130+
rx.el.td(
131+
employee["email"],
132+
class_name="p-3 border-b text-sm text-gray-700",
133+
),
134+
rx.el.td(
135+
employee["department"],
136+
class_name="p-3 border-b text-sm text-gray-700",
137+
),
138+
rx.el.td(
139+
"$ " + employee["salary"].to_string(),
140+
class_name="p-3 border-b text-sm text-gray-700",
141+
),
142+
rx.el.td(
143+
employee["projects_closed"],
144+
class_name="p-3 border-b text-sm text-gray-700 text-center",
145+
),
146+
rx.el.td(
147+
employee["pending_projects"],
148+
class_name="p-3 border-b text-sm text-gray-700 text-center",
149+
),
150+
class_name="hover:bg-gray-50 transition-colors",
151+
),
152+
)
153+
),
154+
rx.cond(
155+
~DashboardState.loading
156+
& (DashboardState.filtered_employees.length() == 0),
157+
rx.el.caption(
158+
rx.el.p(
159+
rx.cond(
160+
(DashboardState.search_query != "")
161+
| (DashboardState.selected_department != "All"),
162+
"No employees match your search or filter.",
163+
"No employee data available.",
164+
),
165+
class_name="text-center text-gray-500 py-4",
166+
),
167+
class_name="caption-bottom",
168+
),
169+
rx.fragment(),
170+
),
171+
class_name="w-full border-collapse bg-white rounded-t-lg shadow-md overflow-hidden",
172+
),
173+
rx.cond(
174+
~DashboardState.loading & (DashboardState.total_pages > 1),
175+
pagination_controls(),
176+
rx.fragment(),
177+
),
178+
class_name="overflow-x-auto shadow rounded-lg border border-gray-200",
179+
),
180+
)
181+
),
182+
class_name="bg-white p-6 rounded-lg shadow-md",
183+
)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import reflex as rx
2+
import reflex.components.recharts as recharts
3+
4+
from business_analytics_dashboard.components.tooltip_props import TOOLTIP_PROPS
5+
from business_analytics_dashboard.states.dashboard_state import DashboardState
6+
7+
8+
def average_salary_bar_chart() -> rx.Component:
9+
"""A bar chart showing the average salary per department."""
10+
return rx.el.div(
11+
rx.el.h2(
12+
"Average Salary by Department",
13+
class_name="text-lg font-semibold text-gray-800 mb-4 text-center",
14+
),
15+
rx.cond(
16+
DashboardState.loading,
17+
rx.el.p(
18+
"Loading chart data...",
19+
class_name="text-gray-500 text-center",
20+
),
21+
rx.cond(
22+
DashboardState.average_salary_by_department.length() > 0,
23+
recharts.bar_chart(
24+
recharts.graphing_tooltip(**TOOLTIP_PROPS),
25+
recharts.cartesian_grid(
26+
horizontal=True,
27+
vertical=False,
28+
class_name="opacity-25",
29+
stroke=rx.color("gray", 7),
30+
),
31+
recharts.bar(
32+
rx.foreach(
33+
DashboardState.average_salary_by_department,
34+
lambda data_point, index: recharts.cell(
35+
fill=DashboardState.department_color_map.get(
36+
data_point["department"],
37+
"#8884d8",
38+
)
39+
),
40+
),
41+
data_key="average_salary",
42+
name="Average Salary",
43+
radius=5,
44+
bar_size=30,
45+
),
46+
recharts.x_axis(
47+
data_key="department",
48+
type_="category",
49+
tick_line=False,
50+
axis_line=False,
51+
height=50,
52+
custom_attrs={"fontSize": "12px"},
53+
stroke=rx.color("gray", 9),
54+
interval=0,
55+
angle=-45,
56+
text_anchor="end",
57+
),
58+
recharts.y_axis(
59+
tick_line=False,
60+
axis_line=False,
61+
custom_attrs={"fontSize": "12px"},
62+
stroke=rx.color("gray", 9),
63+
tick_formatter="function(value) { return `$${value.toLocaleString()}`; }",
64+
),
65+
data=DashboardState.average_salary_by_department,
66+
height=350,
67+
width="100%",
68+
margin={
69+
"left": 30,
70+
"right": 30,
71+
"top": 20,
72+
"bottom": 30,
73+
},
74+
class_name="mt-4",
75+
),
76+
rx.el.p(
77+
rx.cond(
78+
DashboardState.selected_department != "All",
79+
f"No salary data available for the '{DashboardState.selected_department}' department.",
80+
"No salary data available to display the chart.",
81+
),
82+
class_name="text-gray-500 text-center py-10",
83+
),
84+
),
85+
),
86+
class_name="bg-white p-6 rounded-lg h-auto min-h-[24rem]",
87+
)

0 commit comments

Comments
 (0)