2025-08-24 21:28:33 +02:00
|
|
|
import pandas as pd
|
2025-09-07 20:16:56 +02:00
|
|
|
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"),
|
2025-09-07 20:16:56 +02:00
|
|
|
Input(ids.WEEK_DROPDOWN, "value"),
|
2025-09-11 06:22:08 +02:00
|
|
|
Input(ids.PU_HITS_SELECTOR, "value"),
|
2025-09-10 22:00:21 +02:00
|
|
|
Input(ids.SINGLE_HITTER_FILTER, "value"),
|
2025-09-18 05:20:13 +02:00
|
|
|
Input(ids.SYSTEM_FILTER, "value"),
|
|
|
|
Input("span", "value"),
|
2025-08-24 21:28:33 +02:00
|
|
|
],
|
|
|
|
)
|
2025-09-05 05:46:52 +02:00
|
|
|
def update_data_table(
|
2025-09-18 05:20:13 +02:00
|
|
|
year: int, week: int, u_hits: bool, remove_single: bool, systems: list[str], span: int
|
2025-09-06 07:07:08 +02:00
|
|
|
) -> html.Div:
|
2025-09-18 05:20:13 +02:00
|
|
|
try:
|
|
|
|
print(f'year: {year}, week: {week}, span: {span}, systems: {systems}')
|
|
|
|
if not all([year, week, span]):
|
|
|
|
return html.Div("No data selected.")
|
|
|
|
|
|
|
|
years = [year]
|
|
|
|
|
|
|
|
start_week = week - span + 1
|
|
|
|
weeks = list(range(start_week, week + 1))
|
|
|
|
|
|
|
|
filtered_data = data.query(
|
|
|
|
"year in @years and Week_Number in @weeks and System in @systems"
|
|
|
|
)
|
|
|
|
if filtered_data.shape[0] == 0:
|
|
|
|
return html.Div("No data selected.")
|
2025-09-06 07:27:08 +02:00
|
|
|
|
2025-09-18 05:20:13 +02:00
|
|
|
hits_col = MTBFSchema.U_HITS if u_hits else MTBFSchema.P_HITS
|
2025-09-10 22:00:21 +02:00
|
|
|
|
2025-09-18 05:20:13 +02:00
|
|
|
# Count 'Y' values
|
|
|
|
hits_data = filtered_data[filtered_data[hits_col] == 'Y']
|
|
|
|
|
|
|
|
table_data = hits_data.groupby([MTBFSchema.AIR, MTBFSchema.SYSTEM]).agg(
|
|
|
|
count=(hits_col, 'size'),
|
|
|
|
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()))
|
|
|
|
).reset_index()
|
|
|
|
|
|
|
|
if remove_single:
|
|
|
|
table_data = table_data[table_data['count'] > 1]
|
|
|
|
|
|
|
|
table_data['air_issue_description'] = table_data['air_issue_description'].apply(lambda s: s[:80] + '...' if len(s) > 80 else s)
|
2025-08-24 21:28:33 +02:00
|
|
|
|
2025-09-18 05:20:13 +02:00
|
|
|
table_data = table_data.sort_values('count', ascending=False)
|
2025-09-10 22:00:21 +02:00
|
|
|
|
2025-09-18 05:20:13 +02:00
|
|
|
# Reorder columns
|
|
|
|
table_data = table_data[[MTBFSchema.AIR, MTBFSchema.SYSTEM, 'air_issue_description', 'close_notes', 'count']]
|
2025-09-06 07:27:08 +02:00
|
|
|
|
2025-09-18 05:20:13 +02:00
|
|
|
return dash_table.DataTable(
|
|
|
|
id=ids.MTBF_PAR_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',
|
|
|
|
selected_rows=[],
|
|
|
|
filter_action='native',
|
|
|
|
sort_action='native',
|
|
|
|
style_cell_conditional=[
|
|
|
|
{
|
|
|
|
'if': {'column_id': c},
|
|
|
|
'textAlign': 'left'
|
|
|
|
} for c in ['Close_notes', 'count']
|
|
|
|
]
|
|
|
|
)
|
|
|
|
except Exception as e:
|
|
|
|
return html.Div(f"An error occurred in data_table: {e}")
|
2025-09-05 05:46:52 +02:00
|
|
|
|
2025-09-07 20:16:56 +02:00
|
|
|
@app.callback(
|
|
|
|
Output(ids.FEEDBACK_MODAL, "is_open"),
|
|
|
|
Output(ids.FEEDBACK_MESSAGE, "children"),
|
2025-09-13 09:29:08 +02:00
|
|
|
Input(ids.MTBF_PAR_TABLE, "selected_rows"),
|
2025-09-07 20:16:56 +02:00
|
|
|
Input(ids.SAVE_FEEDBACK_BUTTON_POPUP, "n_clicks"),
|
|
|
|
Input(ids.CLOSE_FEEDBACK_BUTTON_POPUP, "n_clicks"),
|
|
|
|
State(ids.FEEDBACK_MODAL, "is_open"),
|
2025-09-13 09:29:08 +02:00
|
|
|
State(ids.MTBF_PAR_TABLE, "data"),
|
2025-09-07 20:16:56 +02:00
|
|
|
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):
|
2025-09-07 20:16:56 +02:00
|
|
|
ctx = callback_context
|
|
|
|
if not ctx.triggered:
|
|
|
|
return False, ""
|
|
|
|
|
|
|
|
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
|
|
|
|
|
2025-09-13 09:29:08 +02:00
|
|
|
if triggered_id == ids.MTBF_PAR_TABLE and selected_rows:
|
2025-09-07 20:16:56 +02:00
|
|
|
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]
|
2025-09-07 20:16:56 +02:00
|
|
|
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'
|
2025-09-07 20:16:56 +02:00
|
|
|
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}."
|
2025-09-07 20:16:56 +02:00
|
|
|
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"),
|
2025-09-13 09:29:08 +02:00
|
|
|
Input(ids.MTBF_PAR_TABLE, "selected_rows"),
|
|
|
|
State(ids.MTBF_PAR_TABLE, "data"),
|
2025-09-07 20:16:56 +02:00
|
|
|
prevent_initial_call=True
|
|
|
|
)
|
2025-09-10 22:00:21 +02:00
|
|
|
def update_feedback_air_label(selected_rows, data):
|
2025-09-07 20:16:56 +02:00
|
|
|
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: "
|
2025-09-07 20:16:56 +02:00
|
|
|
|
|
|
|
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"),
|
2025-09-07 20:16:56 +02:00
|
|
|
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
|
|
|
|
])
|