Files
dash-api/src/components/data_table.py

140 lines
5.3 KiB
Python
Raw Normal View History

2025-08-24 21:28:33 +02:00
import pandas as pd
from dash import Dash, dcc, html, dash_table, Input, Output, State, callback_context
from datetime import datetime
import os
import dash_bootstrap_components as dbc
2025-08-24 21:28:33 +02:00
2025-09-10 22:00:21 +02:00
from ..data.loader_gz import MTBFSchema
2025-08-24 21:28:33 +02:00
from . import ids
def render(app: Dash, data: pd.DataFrame) -> html.Div:
@app.callback(
2025-09-06 07:27:08 +02:00
Output(ids.DATA_TABLE, "children"),
2025-08-24 21:28:33 +02:00
[
Input(ids.YEAR_DROPDOWN, "value"),
Input(ids.WEEK_DROPDOWN, "value"),
2025-09-10 22:00:21 +02:00
Input(ids.HITS_SELECTOR, "value"),
Input(ids.SINGLE_HITTER_FILTER, "value"),
2025-08-24 21:28:33 +02:00
],
)
2025-09-05 05:46:52 +02:00
def update_data_table(
2025-09-10 22:00:21 +02:00
years: list[str], weeks: list[str], hits_type: str, single_hitter_filter: list[str]
2025-09-06 07:07:08 +02:00
) -> html.Div:
2025-08-24 21:28:33 +02:00
filtered_data = data.query(
2025-09-10 22:00:21 +02:00
"year in @years and week in @weeks"
2025-08-24 21:28:33 +02:00
)
if filtered_data.shape[0] == 0:
2025-09-06 07:27:08 +02:00
return html.Div("No data selected.")
2025-09-10 22:00:21 +02:00
if hits_type == "p-hits":
hits_col = MTBFSchema.P_HITS
else:
hits_col = MTBFSchema.U_HITS
# Count 'Y' values
hits_data = filtered_data[filtered_data[hits_col] == 'Y']
table_data = hits_data.groupby(MTBFSchema.AIR).agg(
count=(hits_col, 'size'),
2025-09-11 05:49:26 +02:00
air_issue_description=(MTBFSchema.AIR_ISSUE_DESCRIPTION, lambda x: ', '.join(x.dropna().astype(str).unique())),
close_notes=(MTBFSchema.CLOSE_NOTES, lambda x: ', '.join(x.dropna().astype(str).unique()))
2025-09-10 22:00:21 +02:00
).reset_index()
2025-08-24 21:28:33 +02:00
2025-09-10 22:00:21 +02:00
if 'remove_single' in single_hitter_filter:
table_data = table_data[table_data['count'] > 1]
else:
table_data = table_data[table_data['count'] == 1]
table_data = table_data.sort_values('count', ascending=False)
# Reorder columns
table_data = table_data[[MTBFSchema.AIR, 'air_issue_description', 'close_notes', 'count']]
2025-09-06 07:27:08 +02:00
return dash_table.DataTable(
2025-09-10 22:00:21 +02:00
id=ids.CATEGORY_TABLE, # Using this ID for feedback callbacks
data=table_data.to_dict("records"),
columns=[{"name": i, "id": i} for i in table_data.columns],
page_size=10,
row_selectable='single',
2025-09-11 05:49:26 +02:00
selected_rows=[],
filter_action='native',
sort_action='native'
2025-09-06 07:27:08 +02:00
)
2025-09-05 05:46:52 +02:00
@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"),
)
2025-09-10 22:00:21 +02:00
def handle_feedback_modal(selected_rows, save_clicks, close_clicks, is_open, table_data, comment):
ctx = callback_context
if not ctx.triggered:
return False, ""
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:
2025-09-10 22:00:21 +02:00
selected_row_data = table_data[selected_rows[0]]
air_value = selected_row_data[MTBFSchema.AIR]
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
if not comment:
comment = ""
2025-09-10 22:00:21 +02:00
feedback_data = f'{timestamp},dashboard_table,"AIR: {air_value} - {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)
2025-09-10 22:00:21 +02:00
message = f"Feedback for AIR '{air_value}' 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
)
2025-09-10 22:00:21 +02:00
def update_feedback_air_label(selected_rows, data):
if selected_rows:
2025-09-10 22:00:21 +02:00
air_value = data[selected_rows[0]][MTBFSchema.AIR]
return f"AIR: {air_value}"
return "AIR: "
modal = dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("Submit Feedback")),
dbc.ModalBody([
2025-09-10 22:00:21 +02:00
html.H6("AIR: ", 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
])