Compare commits
	
		
			6 Commits
		
	
	
		
			594e7c3cce
			...
			new_featur
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e1b817252c | |||
| 2d73ae8d63 | |||
| ab1d541e2c | |||
| cc8de33780 | |||
| 1c41f666dc | |||
| d76db631fd | 
| @ -58,4 +58,18 @@ hr { | |||||||
|     .dropdown-container { |     .dropdown-container { | ||||||
|         grid-template-columns: 1fr; |         grid-template-columns: 1fr; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .feedback-button { | ||||||
|  |     background-color: #007bff; | ||||||
|  |     color: white; | ||||||
|  |     border: none; | ||||||
|  |     padding: 5px 10px; | ||||||
|  |     text-align: center; | ||||||
|  |     text-decoration: none; | ||||||
|  |     display: inline-block; | ||||||
|  |     font-size: 12px; | ||||||
|  |     margin: 2px 2px; | ||||||
|  |     cursor: pointer; | ||||||
|  |     border-radius: 4px; | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								data/feedback.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								data/feedback.csv
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | timestamp,category,comment | ||||||
|  | 2025-09-07 20:02:38,groceries,"ABZT please change this air to P1231313" | ||||||
| 
 | 
							
								
								
									
										5
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								main.py
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| # initial commit | # initial commit | ||||||
| from dash import Dash | from dash import Dash | ||||||
| from dash_bootstrap_components.themes import BOOTSTRAP | import dash_bootstrap_components as dbc | ||||||
|  |  | ||||||
| from src.components.layout import create_layout | from src.components.layout import create_layout | ||||||
| from src.data.loader_gz import load_spc_data | from src.data.loader_gz import load_spc_data | ||||||
| @ -17,10 +17,11 @@ with open(config_file) as config_f: | |||||||
|  |  | ||||||
| def main() -> None: | def main() -> None: | ||||||
|     print(os.getenv("MY_ENV_VAR")) |     print(os.getenv("MY_ENV_VAR")) | ||||||
|  |     print(config["Startup"]) | ||||||
|     # load the data and create the data manager |     # load the data and create the data manager | ||||||
|     data = load_spc_data(config["DATA_PATH"]) |     data = load_spc_data(config["DATA_PATH"]) | ||||||
|  |  | ||||||
|     app = Dash(external_stylesheets=[BOOTSTRAP]) |     app = Dash(external_stylesheets=[dbc.themes.LUX]) | ||||||
|     app.title = "Reliability Dashboard" |     app.title = "Reliability Dashboard" | ||||||
|     app.layout = create_layout(app, data) |     app.layout = create_layout(app, data) | ||||||
|     app.run() |     app.run() | ||||||
|  | |||||||
| @ -7,4 +7,5 @@ requires-python = ">=3.11" | |||||||
| dependencies = [ | dependencies = [ | ||||||
|     "dash>=3.2.0", |     "dash>=3.2.0", | ||||||
|     "dash-bootstrap-components>=2.0.4", |     "dash-bootstrap-components>=2.0.4", | ||||||
|  |     "dotenv>=0.9.9", | ||||||
| ] | ] | ||||||
|  | |||||||
| @ -12,15 +12,15 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div: | |||||||
|         Output(ids.BAR_CHART, "children"), |         Output(ids.BAR_CHART, "children"), | ||||||
|         [ |         [ | ||||||
|             Input(ids.YEAR_DROPDOWN, "value"), |             Input(ids.YEAR_DROPDOWN, "value"), | ||||||
|             Input(ids.MONTH_DROPDOWN, "value"), |             Input(ids.WEEK_DROPDOWN, "value"), | ||||||
|             Input(ids.CATEGORY_DROPDOWN, "value"), |             Input(ids.CATEGORY_DROPDOWN, "value"), | ||||||
|         ], |         ], | ||||||
|     ) |     ) | ||||||
|     def update_bar_chart( |     def update_bar_chart( | ||||||
|         years: list[str], months: list[str], categories: list[str] |         years: list[str], weeks: list[str], categories: list[str] | ||||||
|     ) -> html.Div: |     ) -> html.Div: | ||||||
|         filtered_data = data.query( |         filtered_data = data.query( | ||||||
|             "year in @years and month in @months and category in @categories" |             "year in @years and week in @weeks and category in @categories" | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         if filtered_data.shape[0] == 0: |         if filtered_data.shape[0] == 0: | ||||||
| @ -45,4 +45,4 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div: | |||||||
|  |  | ||||||
|         return html.Div(dcc.Graph(figure=fig), id=ids.BAR_CHART) |         return html.Div(dcc.Graph(figure=fig), id=ids.BAR_CHART) | ||||||
|  |  | ||||||
|     return html.Div(id=ids.BAR_CHART) |     return html.Div(id=ids.BAR_CHART) | ||||||
| @ -14,12 +14,12 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div: | |||||||
|         Output(ids.CATEGORY_DROPDOWN, "value"), |         Output(ids.CATEGORY_DROPDOWN, "value"), | ||||||
|         [ |         [ | ||||||
|             Input(ids.YEAR_DROPDOWN, "value"), |             Input(ids.YEAR_DROPDOWN, "value"), | ||||||
|             Input(ids.MONTH_DROPDOWN, "value"), |             Input(ids.WEEK_DROPDOWN, "value"), | ||||||
|             Input(ids.SELECT_ALL_CATEGORIES_BUTTON, "n_clicks"), |             Input(ids.SELECT_ALL_CATEGORIES_BUTTON, "n_clicks"), | ||||||
|         ], |         ], | ||||||
|     ) |     ) | ||||||
|     def select_all_categories(years: list[str], months: list[str], _: int) -> list[str]: |     def select_all_categories(years: list[str], weeks: list[str], _: int) -> list[str]: | ||||||
|         filtered_data = data.query("year in @years and month in @months") |         filtered_data = data.query("year in @years and week in @weeks") | ||||||
|         return sorted(set(filtered_data[DataSchema.CATEGORY].tolist())) |         return sorted(set(filtered_data[DataSchema.CATEGORY].tolist())) | ||||||
|  |  | ||||||
|     return html.Div( |     return html.Div( | ||||||
| @ -42,4 +42,4 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div: | |||||||
|                 n_clicks=0, |                 n_clicks=0, | ||||||
|             ), |             ), | ||||||
|         ], |         ], | ||||||
|     ) |     ) | ||||||
| @ -1,48 +1,123 @@ | |||||||
| import pandas as pd | import pandas as pd | ||||||
| import plotly.express as px | from dash import Dash, dcc, html, dash_table, Input, Output, State, callback_context | ||||||
| from dash import Dash, dcc, html | from datetime import datetime | ||||||
| from dash.dependencies import Input, Output | import os | ||||||
|  | import dash_bootstrap_components as dbc | ||||||
|  |  | ||||||
| from ..data.loader import DataSchema | from ..data.loader import DataSchema | ||||||
| from . import ids | from . import ids | ||||||
|  |  | ||||||
|  |  | ||||||
| def render(app: Dash, data: pd.DataFrame) -> html.Div: | def render(app: Dash, data: pd.DataFrame) -> html.Div: | ||||||
|     @app.callback( |     @app.callback( | ||||||
|         Output(ids.BAR_CHART, "children"), |         Output(ids.DATA_TABLE, "children"), | ||||||
|         [ |         [ | ||||||
|             Input(ids.YEAR_DROPDOWN, "value"), |             Input(ids.YEAR_DROPDOWN, "value"), | ||||||
|             Input(ids.MONTH_DROPDOWN, "value"), |             Input(ids.WEEK_DROPDOWN, "value"), | ||||||
|             Input(ids.CATEGORY_DROPDOWN, "value"), |             Input(ids.CATEGORY_DROPDOWN, "value"), | ||||||
|         ], |         ], | ||||||
|     ) |     ) | ||||||
|     def update_bar_chart( |     def update_data_table( | ||||||
|         years: list[str], months: list[str], categories: list[str] |         years: list[str], weeks: list[str], categories: list[str] | ||||||
|     ) -> html.Div: |     ) -> html.Div: | ||||||
|         filtered_data = data.query( |         filtered_data = data.query( | ||||||
|             "year in @years and month in @months and category in @categories" |             "year in @years and week in @weeks and category in @categories" | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         if filtered_data.shape[0] == 0: |         if filtered_data.shape[0] == 0: | ||||||
|             return html.Div("No data selected.", id=ids.BAR_CHART) |             return html.Div("No data selected.") | ||||||
|  |  | ||||||
|         def create_pivot_table() -> pd.DataFrame: |         pt = filtered_data.pivot_table( | ||||||
|             pt = filtered_data.pivot_table( |             values=DataSchema.AMOUNT, | ||||||
|                 values=DataSchema.AMOUNT, |             index=[DataSchema.CATEGORY], | ||||||
|                 index=[DataSchema.CATEGORY], |             aggfunc="sum", | ||||||
|                 aggfunc="sum", |             fill_value=0, | ||||||
|                 fill_value=0, |             dropna=False, | ||||||
|                 dropna=False, |         ).reset_index().sort_values(DataSchema.AMOUNT, ascending=False) | ||||||
|             ) |  | ||||||
|             return pt.reset_index().sort_values(DataSchema.AMOUNT, ascending=False) |  | ||||||
|  |  | ||||||
|         fig = px.bar( |         columns = [{"name": i.capitalize(), "id": i} for i in pt.columns] | ||||||
|             create_pivot_table(), |  | ||||||
|             x=DataSchema.CATEGORY, |         return dash_table.DataTable( | ||||||
|             y=DataSchema.AMOUNT, |             id=ids.CATEGORY_TABLE, | ||||||
|             color=DataSchema.CATEGORY, |             data=pt.to_dict("records"), | ||||||
|  |             columns=columns, | ||||||
|  |             row_selectable='single', | ||||||
|  |             selected_rows=[] | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         return html.Div(dcc.Graph(figure=fig), id=ids.BAR_CHART) |     @app.callback( | ||||||
|  |         Output(ids.FEEDBACK_MODAL, "is_open"), | ||||||
|  |         Output(ids.FEEDBACK_MESSAGE, "children"), | ||||||
|  |         Input(ids.CATEGORY_TABLE, "selected_rows"), | ||||||
|  |         Input(ids.SAVE_FEEDBACK_BUTTON_POPUP, "n_clicks"), | ||||||
|  |         Input(ids.CLOSE_FEEDBACK_BUTTON_POPUP, "n_clicks"), | ||||||
|  |         State(ids.FEEDBACK_MODAL, "is_open"), | ||||||
|  |         State(ids.CATEGORY_TABLE, "data"), | ||||||
|  |         State(ids.FEEDBACK_INPUT, "value"), | ||||||
|  |     ) | ||||||
|  |     def handle_feedback_modal(selected_rows, save_clicks, close_clicks, is_open, data, comment): | ||||||
|  |         ctx = callback_context | ||||||
|  |         if not ctx.triggered: | ||||||
|  |             return False, "" | ||||||
|  |  | ||||||
|     return html.Div(id=ids.BAR_CHART) |         triggered_id = ctx.triggered[0]['prop_id'].split('.')[0] | ||||||
|  |          | ||||||
|  |         if triggered_id == ids.CATEGORY_TABLE and selected_rows: | ||||||
|  |             return True, "" | ||||||
|  |  | ||||||
|  |         if triggered_id == ids.SAVE_FEEDBACK_BUTTON_POPUP and selected_rows: | ||||||
|  |             selected_row_data = data[selected_rows[0]] | ||||||
|  |             category = selected_row_data[DataSchema.CATEGORY] | ||||||
|  |             timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') | ||||||
|  |             if not comment: | ||||||
|  |                 comment = "" | ||||||
|  |             feedback_data = f'{timestamp},{category},"{comment}"\n' | ||||||
|  |             file_path = "data/feedback.csv" | ||||||
|  |             try: | ||||||
|  |                 is_new_file = not os.path.exists(file_path) or os.path.getsize(file_path) == 0 | ||||||
|  |                 with open(file_path, "a") as f: | ||||||
|  |                     if is_new_file: | ||||||
|  |                         f.write("timestamp,category,comment\n") | ||||||
|  |                     f.write(feedback_data) | ||||||
|  |                 message = f"Feedback for category '{category}' has been saved successfully at {timestamp}." | ||||||
|  |                 return False, message | ||||||
|  |             except Exception as e: | ||||||
|  |                 return True, "An error occurred while saving feedback." | ||||||
|  |  | ||||||
|  |         if triggered_id == ids.CLOSE_FEEDBACK_BUTTON_POPUP: | ||||||
|  |             return False, "" | ||||||
|  |  | ||||||
|  |         return is_open, "" | ||||||
|  |  | ||||||
|  |     @app.callback( | ||||||
|  |         Output("feedback-category-label", "children"), | ||||||
|  |         Input(ids.CATEGORY_TABLE, "selected_rows"), | ||||||
|  |         State(ids.CATEGORY_TABLE, "data"), | ||||||
|  |         prevent_initial_call=True | ||||||
|  |     ) | ||||||
|  |     def update_feedback_category_label(selected_rows, data): | ||||||
|  |         if selected_rows: | ||||||
|  |             category = data[selected_rows[0]][DataSchema.CATEGORY] | ||||||
|  |             return f"Category: {category}" | ||||||
|  |         return "Category: " | ||||||
|  |  | ||||||
|  |     modal = dbc.Modal( | ||||||
|  |         [ | ||||||
|  |             dbc.ModalHeader(dbc.ModalTitle("Submit Feedback")), | ||||||
|  |             dbc.ModalBody([ | ||||||
|  |                 html.H6("Category: ", id="feedback-category-label"), | ||||||
|  |                 dcc.Input(id=ids.FEEDBACK_INPUT, type='text', placeholder='Enter feedback...', style={'width': '100%'}), | ||||||
|  |             ]), | ||||||
|  |             dbc.ModalFooter([ | ||||||
|  |                 dbc.Button("Save", id=ids.SAVE_FEEDBACK_BUTTON_POPUP, className="ms-auto", n_clicks=0), | ||||||
|  |                 dbc.Button("Close", id=ids.CLOSE_FEEDBACK_BUTTON_POPUP, className="ms-auto", n_clicks=0) | ||||||
|  |             ]), | ||||||
|  |         ], | ||||||
|  |         id=ids.FEEDBACK_MODAL, | ||||||
|  |         is_open=False, | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     return html.Div([ | ||||||
|  |         html.Div(id=ids.DATA_TABLE), | ||||||
|  |         html.Div(id=ids.FEEDBACK_MESSAGE), | ||||||
|  |         modal | ||||||
|  |     ]) | ||||||
							
								
								
									
										22
									
								
								src/components/explanation_tab.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/components/explanation_tab.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | from dash import Dash, dcc, html | ||||||
|  |  | ||||||
|  | def render(_: Dash) -> html.Div: | ||||||
|  |     return html.Div([ | ||||||
|  |         dcc.Markdown(""" | ||||||
|  |             ### How to use this Dashboard | ||||||
|  |  | ||||||
|  |             **Dashboard Tab:** | ||||||
|  |             - Use the dropdowns at the top to filter the data by year, week, and category. | ||||||
|  |             - The bar chart shows the total amount per category for the selected period. | ||||||
|  |             - The data table shows the detailed data. | ||||||
|  |  | ||||||
|  |             **Feedback:** | ||||||
|  |             - In the data table on the Dashboard tab, you can select a row to provide feedback. | ||||||
|  |             - A popup will appear where you can enter your comments for the selected category. | ||||||
|  |             - Click "Save" to store your feedback. | ||||||
|  |  | ||||||
|  |             **Feedback Tab:** | ||||||
|  |             - This tab displays all the feedback that has been submitted. | ||||||
|  |             - The table on this tab updates automatically every 5 seconds. | ||||||
|  |         """) | ||||||
|  |     ]) | ||||||
							
								
								
									
										27
									
								
								src/components/feedback_tab.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/components/feedback_tab.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | from dash import Dash, dcc, html, dash_table | ||||||
|  | from dash.dependencies import Input, Output | ||||||
|  | import pandas as pd | ||||||
|  |  | ||||||
|  | def render(app: Dash) -> html.Div: | ||||||
|  |     @app.callback( | ||||||
|  |         Output("feedback-table-content", "children"), | ||||||
|  |         Input("feedback-interval", "n_intervals") | ||||||
|  |     ) | ||||||
|  |     def update_feedback_table(_): | ||||||
|  |         try: | ||||||
|  |             feedback_df = pd.read_csv("data/feedback.csv") | ||||||
|  |             return dash_table.DataTable( | ||||||
|  |                 data=feedback_df.to_dict("records"), | ||||||
|  |                 columns=[{"name": i, "id": i} for i in feedback_df.columns], | ||||||
|  |                 page_size=10, | ||||||
|  |             ) | ||||||
|  |         except FileNotFoundError: | ||||||
|  |             return html.P("No feedback submitted yet.") | ||||||
|  |         except Exception as e: | ||||||
|  |             return html.P(f"An error occurred: {e}") | ||||||
|  |  | ||||||
|  |     return html.Div([ | ||||||
|  |         html.H4("Submitted Feedback"), | ||||||
|  |         html.Div(id="feedback-table-content"), | ||||||
|  |         dcc.Interval(id="feedback-interval", interval=5 * 1000, n_intervals=0) # 5 seconds | ||||||
|  |     ]) | ||||||
| @ -5,8 +5,16 @@ DATA_TABLE = "data-table" | |||||||
| SELECT_ALL_CATEGORIES_BUTTON = "select-all-categories-button" | SELECT_ALL_CATEGORIES_BUTTON = "select-all-categories-button" | ||||||
| CATEGORY_DROPDOWN = "category-dropdown" | CATEGORY_DROPDOWN = "category-dropdown" | ||||||
|  |  | ||||||
| SELECT_ALL_MONTHS_BUTTON = "select-all-months-button" |  | ||||||
| MONTH_DROPDOWN = "month-dropdown" |  | ||||||
|  |  | ||||||
| YEAR_DROPDOWN = "year-dropdown" | YEAR_DROPDOWN = "year-dropdown" | ||||||
| SELECT_ALL_YEARS_BUTTON = "select-all-years-button" | SELECT_ALL_YEARS_BUTTON = "select-all-years-button" | ||||||
|  |  | ||||||
|  | WEEK_DROPDOWN = "week-dropdown" | ||||||
|  | SELECT_ALL_WEEKS_BUTTON = "select-all-weeks-button" | ||||||
|  |  | ||||||
|  | CATEGORY_TABLE = "category-table" | ||||||
|  |  | ||||||
|  | FEEDBACK_INPUT = "feedback-input" | ||||||
|  | FEEDBACK_MESSAGE = "feedback-message" | ||||||
|  | FEEDBACK_MODAL = "feedback-modal" | ||||||
|  | SAVE_FEEDBACK_BUTTON_POPUP = "save-feedback-button-popup" | ||||||
|  | CLOSE_FEEDBACK_BUTTON_POPUP = "close-feedback-button-popup" | ||||||
| @ -1,29 +1,48 @@ | |||||||
| import pandas as pd | import pandas as pd | ||||||
| from dash import Dash, html | from dash import Dash, dcc, html | ||||||
| from src.components import ( | from src.components import ( | ||||||
|     bar_chart, |     bar_chart, | ||||||
|     category_dropdown, |     data_table, | ||||||
|     month_dropdown, |  | ||||||
|     pie_chart, |  | ||||||
|     year_dropdown, |     year_dropdown, | ||||||
|  |     week_dropdown, | ||||||
|  |     category_dropdown, | ||||||
|  |     feedback_tab, | ||||||
|  |     explanation_tab, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_layout(app: Dash, data: pd.DataFrame) -> html.Div: | def create_layout(app: Dash, data: pd.DataFrame) -> html.Div: | ||||||
|  |     tab_content_style = {'height': 'calc(100vh - 220px)', 'overflowY': 'auto', 'padding': '15px'} | ||||||
|  |  | ||||||
|     return html.Div( |     return html.Div( | ||||||
|         className="app-div", |         className="app-div", | ||||||
|         children=[ |         children=[ | ||||||
|             html.H1(app.title), |             html.H1(app.title), | ||||||
|             html.Hr(), |             html.Hr(), | ||||||
|             html.Div( |             dcc.Tabs(id="tabs", value='tab-dashboard', children=[ | ||||||
|                 className="dropdown-container", |                 dcc.Tab(label='Dashboard', value='tab-dashboard', children=[ | ||||||
|                 children=[ |                     html.Div(style=tab_content_style, children=[ | ||||||
|                     year_dropdown.render(app, data), |                         html.Div( | ||||||
|                     month_dropdown.render(app, data), |                             className="dropdown-container", | ||||||
|                     category_dropdown.render(app, data), |                             children=[ | ||||||
|                 ], |                                 year_dropdown.render(app, data), | ||||||
|             ), |                                 week_dropdown.render(app, data), | ||||||
|             bar_chart.render(app, data), |                                 category_dropdown.render(app, data), | ||||||
|             pie_chart.render(app, data), |                             ], | ||||||
|  |                         ), | ||||||
|  |                         bar_chart.render(app, data), | ||||||
|  |                         data_table.render(app, data), | ||||||
|  |                     ]) | ||||||
|  |                 ]), | ||||||
|  |                 dcc.Tab(label='Feedback', value='tab-feedback', children=[ | ||||||
|  |                     html.Div(style=tab_content_style, children=[ | ||||||
|  |                         feedback_tab.render(app) | ||||||
|  |                     ]) | ||||||
|  |                 ]), | ||||||
|  |                 dcc.Tab(label='Explanation', value='tab-explanation', children=[ | ||||||
|  |                     html.Div(style=tab_content_style, children=[ | ||||||
|  |                         explanation_tab.render(app) | ||||||
|  |                     ]) | ||||||
|  |                 ]), | ||||||
|  |             ]), | ||||||
|         ], |         ], | ||||||
|     ) |     ) | ||||||
| @ -1,40 +0,0 @@ | |||||||
| import pandas as pd |  | ||||||
| from dash import Dash, dcc, html |  | ||||||
| from dash.dependencies import Input, Output |  | ||||||
|  |  | ||||||
| from ..data.loader import DataSchema |  | ||||||
| from . import ids |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def render(app: Dash, data: pd.DataFrame) -> html.Div: |  | ||||||
|     all_months: list[str] = data[DataSchema.MONTH].tolist() |  | ||||||
|     unique_months = sorted(set(all_months)) |  | ||||||
|  |  | ||||||
|     @app.callback( |  | ||||||
|         Output(ids.MONTH_DROPDOWN, "value"), |  | ||||||
|         [ |  | ||||||
|             Input(ids.YEAR_DROPDOWN, "value"), |  | ||||||
|             Input(ids.SELECT_ALL_MONTHS_BUTTON, "n_clicks"), |  | ||||||
|         ], |  | ||||||
|     ) |  | ||||||
|     def select_all_months(years: list[str], _: int) -> list[str]: |  | ||||||
|         filtered_data = data.query("year in @years") |  | ||||||
|         return sorted(set(filtered_data[DataSchema.MONTH].tolist())) |  | ||||||
|  |  | ||||||
|     return html.Div( |  | ||||||
|         children=[ |  | ||||||
|             html.H6("Month"), |  | ||||||
|             dcc.Dropdown( |  | ||||||
|                 id=ids.MONTH_DROPDOWN, |  | ||||||
|                 options=[{"label": month, "value": month} for month in unique_months], |  | ||||||
|                 value=unique_months, |  | ||||||
|                 multi=True, |  | ||||||
|             ), |  | ||||||
|             html.Button( |  | ||||||
|                 className="dropdown-button", |  | ||||||
|                 children=["Select All"], |  | ||||||
|                 id=ids.SELECT_ALL_MONTHS_BUTTON, |  | ||||||
|                 n_clicks=0, |  | ||||||
|             ), |  | ||||||
|         ] |  | ||||||
|     ) |  | ||||||
							
								
								
									
										53
									
								
								src/components/week_dropdown.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/components/week_dropdown.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | |||||||
|  | import pandas as pd | ||||||
|  | from dash import Dash, dcc, html | ||||||
|  | from dash.dependencies import Input, Output | ||||||
|  | from datetime import datetime, timedelta | ||||||
|  |  | ||||||
|  | from ..data.loader import DataSchema | ||||||
|  | from . import ids | ||||||
|  |  | ||||||
|  | def render(app: Dash, data: pd.DataFrame) -> html.Div: | ||||||
|  |     all_weeks: list[str] = data[DataSchema.WEEK].tolist() | ||||||
|  |     unique_weeks = sorted(set(all_weeks), reverse=True) | ||||||
|  |  | ||||||
|  |     # determine default weeks (last 13) | ||||||
|  |     today = datetime.now() | ||||||
|  |     last_13_weeks = [] | ||||||
|  |     for i in range(13): | ||||||
|  |         date = today - timedelta(weeks=i) | ||||||
|  |         year, week, _ = date.isocalendar() | ||||||
|  |         last_13_weeks.append(f"{year}{week:02d}") | ||||||
|  |  | ||||||
|  |     default_weeks = [week for week in last_13_weeks if week in unique_weeks] | ||||||
|  |     if not default_weeks and unique_weeks: | ||||||
|  |         default_weeks = unique_weeks[:13] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @app.callback( | ||||||
|  |         Output(ids.WEEK_DROPDOWN, "value"), | ||||||
|  |         [ | ||||||
|  |             Input(ids.YEAR_DROPDOWN, "value"), | ||||||
|  |             Input(ids.SELECT_ALL_WEEKS_BUTTON, "n_clicks"), | ||||||
|  |         ], | ||||||
|  |     ) | ||||||
|  |     def select_all_weeks(years: list[str], _: int) -> list[str]: | ||||||
|  |         filtered_data = data.query("year in @years") | ||||||
|  |         return sorted(set(filtered_data[DataSchema.WEEK].tolist())) | ||||||
|  |  | ||||||
|  |     return html.Div( | ||||||
|  |         children=[ | ||||||
|  |             html.H6("Week"), | ||||||
|  |             dcc.Dropdown( | ||||||
|  |                 id=ids.WEEK_DROPDOWN, | ||||||
|  |                 options=[{"label": f"{week[:4]}-{week[4:]}", "value": week} for week in unique_weeks], | ||||||
|  |                 value=default_weeks, | ||||||
|  |                 multi=True, | ||||||
|  |             ), | ||||||
|  |             html.Button( | ||||||
|  |                 className="dropdown-button", | ||||||
|  |                 children=["Select All"], | ||||||
|  |                 id=ids.SELECT_ALL_WEEKS_BUTTON, | ||||||
|  |                 n_clicks=0, | ||||||
|  |             ), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
| @ -7,6 +7,7 @@ class DataSchema: | |||||||
|     DATE = "date" |     DATE = "date" | ||||||
|     MONTH = "month" |     MONTH = "month" | ||||||
|     YEAR = "year" |     YEAR = "year" | ||||||
|  |     WEEK = "week" | ||||||
|  |  | ||||||
|  |  | ||||||
| def load_transaction_data(path: str) -> pd.DataFrame: | def load_transaction_data(path: str) -> pd.DataFrame: | ||||||
| @ -22,4 +23,8 @@ def load_transaction_data(path: str) -> pd.DataFrame: | |||||||
|     ) |     ) | ||||||
|     data[DataSchema.YEAR] = data[DataSchema.DATE].dt.year.astype(str) |     data[DataSchema.YEAR] = data[DataSchema.DATE].dt.year.astype(str) | ||||||
|     data[DataSchema.MONTH] = data[DataSchema.DATE].dt.month.astype(str) |     data[DataSchema.MONTH] = data[DataSchema.DATE].dt.month.astype(str) | ||||||
|     return data |     data[DataSchema.WEEK] = ( | ||||||
|  |         data[DataSchema.DATE].dt.isocalendar().year.astype(str) | ||||||
|  |         + data[DataSchema.DATE].dt.isocalendar().week.astype(str).str.zfill(2) | ||||||
|  |     ) | ||||||
|  |     return data | ||||||
| @ -6,6 +6,7 @@ class DataSchema: | |||||||
|     DATE = "date" |     DATE = "date" | ||||||
|     MONTH = "month" |     MONTH = "month" | ||||||
|     YEAR = "year" |     YEAR = "year" | ||||||
|  |     WEEK = "week" | ||||||
|  |  | ||||||
| class SPC_Schema: | class SPC_Schema: | ||||||
|     FC = "FC" |     FC = "FC" | ||||||
| @ -30,4 +31,8 @@ def load_spc_data(path: str) -> pd.DataFrame: | |||||||
|     ) |     ) | ||||||
|     data[DataSchema.YEAR] = data[DataSchema.DATE].dt.year.astype(str) |     data[DataSchema.YEAR] = data[DataSchema.DATE].dt.year.astype(str) | ||||||
|     data[DataSchema.MONTH] = data[DataSchema.DATE].dt.month.astype(str) |     data[DataSchema.MONTH] = data[DataSchema.DATE].dt.month.astype(str) | ||||||
|     return data |     data[DataSchema.WEEK] = ( | ||||||
|  |         data[DataSchema.DATE].dt.isocalendar().year.astype(str) | ||||||
|  |         + data[DataSchema.DATE].dt.isocalendar().week.astype(str).str.zfill(2) | ||||||
|  |     ) | ||||||
|  |     return data | ||||||
							
								
								
									
										22
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										22
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @ -9,12 +9,14 @@ source = { virtual = "." } | |||||||
| dependencies = [ | dependencies = [ | ||||||
|     { name = "dash" }, |     { name = "dash" }, | ||||||
|     { name = "dash-bootstrap-components" }, |     { name = "dash-bootstrap-components" }, | ||||||
|  |     { name = "dotenv" }, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [package.metadata] | [package.metadata] | ||||||
| requires-dist = [ | requires-dist = [ | ||||||
|     { name = "dash", specifier = ">=3.2.0" }, |     { name = "dash", specifier = ">=3.2.0" }, | ||||||
|     { name = "dash-bootstrap-components", specifier = ">=2.0.4" }, |     { name = "dash-bootstrap-components", specifier = ">=2.0.4" }, | ||||||
|  |     { name = "dotenv", specifier = ">=0.9.9" }, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| @ -141,6 +143,17 @@ wheels = [ | |||||||
|     { url = "https://files.pythonhosted.org/packages/d6/38/1efeec8b4d741c09ccd169baf8a00c07a0176b58e418d4cd0c30dffedd22/dash_bootstrap_components-2.0.4-py3-none-any.whl", hash = "sha256:767cf0084586c1b2b614ccf50f79fe4525fdbbf8e3a161ed60016e584a14f5d1", size = 204044 }, |     { url = "https://files.pythonhosted.org/packages/d6/38/1efeec8b4d741c09ccd169baf8a00c07a0176b58e418d4cd0c30dffedd22/dash_bootstrap_components-2.0.4-py3-none-any.whl", hash = "sha256:767cf0084586c1b2b614ccf50f79fe4525fdbbf8e3a161ed60016e584a14f5d1", size = 204044 }, | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "dotenv" | ||||||
|  | version = "0.9.9" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "python-dotenv" }, | ||||||
|  | ] | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892 }, | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "flask" | name = "flask" | ||||||
| version = "3.1.2" | version = "3.1.2" | ||||||
| @ -288,6 +301,15 @@ wheels = [ | |||||||
|     { url = "https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl", hash = "sha256:7ad806edce9d3cdd882eaebaf97c0c9e252043ed1ed3d382c3e3520ec07806d4", size = 9791257 }, |     { url = "https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl", hash = "sha256:7ad806edce9d3cdd882eaebaf97c0c9e252043ed1ed3d382c3e3520ec07806d4", size = 9791257 }, | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "python-dotenv" | ||||||
|  | version = "1.1.1" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978 } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556 }, | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "requests" | name = "requests" | ||||||
| version = "2.32.5" | version = "2.32.5" | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user