taotanapol commited on
Commit
6a3cce2
·
verified ·
1 Parent(s): d006321

Add search box to Item Tab

Browse files
Files changed (1) hide show
  1. streamlit_app.py +74 -27
streamlit_app.py CHANGED
@@ -141,7 +141,11 @@ LANG = {
141
  "it_chart_qty": "Top {n} items by quantity ordered",
142
  "it_by_cat": "Items by sub-type",
143
  "it_by_protein": "Items by Protein",
 
144
  "it_detail": "Item detail",
 
 
 
145
  },
146
  "th": {
147
  # Tab labels
@@ -245,7 +249,11 @@ LANG = {
245
  "it_chart_qty": "{n} รายการที่ขายดีที่สุด (จำนวน)",
246
  "it_by_cat": "รายการแบ่งตามประเภทย่อย",
247
  "it_by_protein": "รายการแบ่งตามโปรตีน",
 
248
  "it_detail": "รายละเอียดรายการ",
 
 
 
249
  },
250
  }
251
 
@@ -2014,8 +2022,26 @@ with tab_items:
2014
  with fc3:
2015
  top_n = st.slider(t("it_top_n"), 5, 50, 20, key="it_top_n")
2016
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2017
  if it_view.empty:
2018
- st.info(t("it_no_data"))
 
2019
  else:
2020
  # ── KPI tiles ────────────────────────────────────────────
2021
  total_qty = int(it_view["Qty"].sum()) if "Qty" in it_view.columns else 0
@@ -2061,6 +2087,49 @@ with tab_items:
2061
  # Sub-type = the menu category (Teppan, Sushi, etc.).
2062
  # Group1 = the protein family (Seafood, Duck, Fish, etc.)
2063
  # — captured by fact_items' Group1 column for Food rows.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2064
  pie_left, pie_right = st.columns(2)
2065
 
2066
  with pie_left:
@@ -2070,20 +2139,9 @@ with tab_items:
2070
  .sort_values("Qty", ascending=False)
2071
  )
2072
  cat_agg = cat_agg[cat_agg["Qty"] > 0]
 
2073
  if len(cat_agg) >= 2:
2074
- st.markdown(f"**{t('it_by_cat')}**")
2075
- fig = px.pie(
2076
- cat_agg, names="SubType", values="Qty", hole=0.55,
2077
- color_discrete_sequence=BRAND_SEQUENCE,
2078
- )
2079
- fig.update_traces(
2080
- textposition="inside",
2081
- texttemplate="%{label}<br>%{value:,.0f}<br>%{percent}",
2082
- insidetextfont=dict(size=11),
2083
- )
2084
- st.plotly_chart(
2085
- style_plotly(fig, height=380), use_container_width=True,
2086
- )
2087
 
2088
  with pie_right:
2089
  if "Group1" in it_view.columns:
@@ -2092,20 +2150,9 @@ with tab_items:
2092
  .sort_values("Qty", ascending=False)
2093
  )
2094
  protein_agg = protein_agg[protein_agg["Qty"] > 0]
 
2095
  if len(protein_agg) >= 2:
2096
- st.markdown(f"**{t('it_by_protein')}**")
2097
- fig = px.pie(
2098
- protein_agg, names="Group1", values="Qty", hole=0.55,
2099
- color_discrete_sequence=BRAND_SEQUENCE,
2100
- )
2101
- fig.update_traces(
2102
- textposition="inside",
2103
- texttemplate="%{label}<br>%{value:,.0f}<br>%{percent}",
2104
- insidetextfont=dict(size=11),
2105
- )
2106
- st.plotly_chart(
2107
- style_plotly(fig, height=380), use_container_width=True,
2108
- )
2109
 
2110
  # ── Item detail table ───────────────────────────────────
2111
  st.markdown(f"**{t('it_detail')}**")
 
141
  "it_chart_qty": "Top {n} items by quantity ordered",
142
  "it_by_cat": "Items by sub-type",
143
  "it_by_protein": "Items by Protein",
144
+ "it_other": "Other",
145
  "it_detail": "Item detail",
146
+ "it_search": "Search item",
147
+ "it_search_help": "Type any part of the item name (English or Thai). Case-insensitive.",
148
+ "it_search_no_match": "No items match \"{q}\". Clear the search box to see everything.",
149
  },
150
  "th": {
151
  # Tab labels
 
249
  "it_chart_qty": "{n} รายการที่ขายดีที่สุด (จำนวน)",
250
  "it_by_cat": "รายการแบ่งตามประเภทย่อย",
251
  "it_by_protein": "รายการแบ่งตามโปรตีน",
252
+ "it_other": "อื่นๆ",
253
  "it_detail": "รายละเอียดรายการ",
254
+ "it_search": "ค้นหารายการ",
255
+ "it_search_help": "พิมพ์ส่วนใดส่วนหนึ่งของชื่อรายการ (ภาษาไทยหรืออังกฤษ) ไม่สนใจตัวพิมพ์เล็ก-ใหญ่",
256
+ "it_search_no_match": "ไม่พบรายการที่ตรงกับ \"{q}\" ล้างช่องค้นหาเพื่อแสดงทั้งหมด",
257
  },
258
  }
259
 
 
2022
  with fc3:
2023
  top_n = st.slider(t("it_top_n"), 5, 50, 20, key="it_top_n")
2024
 
2025
+ # ── Item search — case-insensitive substring match ────────────
2026
+ # Applied AFTER the Type/SubType filters so it narrows whatever
2027
+ # subset those produced. Empty search = pass-through.
2028
+ search_q = st.text_input(
2029
+ t("it_search"),
2030
+ key="it_search",
2031
+ placeholder=t("it_search_help"),
2032
+ ).strip()
2033
+ if search_q and "Item" in it_view.columns:
2034
+ _matched = (
2035
+ it_view["Item"].astype(str)
2036
+ .str.contains(search_q, case=False, na=False, regex=False)
2037
+ )
2038
+ it_view = it_view[_matched]
2039
+ if it_view.empty:
2040
+ st.info(t("it_search_no_match", q=search_q))
2041
+
2042
  if it_view.empty:
2043
+ if not search_q:
2044
+ st.info(t("it_no_data"))
2045
  else:
2046
  # ── KPI tiles ────────────────────────────────────────────
2047
  total_qty = int(it_view["Qty"].sum()) if "Qty" in it_view.columns else 0
 
2087
  # Sub-type = the menu category (Teppan, Sushi, etc.).
2088
  # Group1 = the protein family (Seafood, Duck, Fish, etc.)
2089
  # — captured by fact_items' Group1 column for Food rows.
2090
+ # Aggregate everything past the top N into a single
2091
+ # "Other" slice so the legend stays compact instead of
2092
+ # filling half the chart with single-item categories.
2093
+ def _top_n_with_other(df: pd.DataFrame, name_col: str,
2094
+ value_col: str, n: int = 8) -> pd.DataFrame:
2095
+ if df.empty or len(df) <= n:
2096
+ return df
2097
+ top = df.nlargest(n, value_col)
2098
+ rest = df[~df[name_col].isin(top[name_col])][value_col].sum()
2099
+ if rest > 0:
2100
+ other = pd.DataFrame({name_col: [t("it_other")],
2101
+ value_col: [rest]})
2102
+ return pd.concat([top, other], ignore_index=True)
2103
+ return top
2104
+
2105
+ def _render_donut(df: pd.DataFrame, name_col: str, title: str) -> None:
2106
+ """Pie chart with bottom-horizontal legend so the
2107
+ chart stays roughly square instead of being squeezed
2108
+ by a tall right-side legend."""
2109
+ fig = px.pie(
2110
+ df, names=name_col, values="Qty", hole=0.55,
2111
+ color_discrete_sequence=BRAND_SEQUENCE,
2112
+ )
2113
+ fig.update_traces(
2114
+ textposition="inside",
2115
+ texttemplate="%{label}<br>%{value:,.0f}<br>%{percent}",
2116
+ insidetextfont=dict(size=11),
2117
+ )
2118
+ fig.update_layout(
2119
+ title=None,
2120
+ margin=dict(l=10, r=10, t=20, b=80),
2121
+ legend=dict(
2122
+ orientation="h",
2123
+ yanchor="top", y=-0.05,
2124
+ xanchor="center", x=0.5,
2125
+ font=dict(size=11),
2126
+ ),
2127
+ )
2128
+ st.markdown(f"**{title}**")
2129
+ st.plotly_chart(
2130
+ style_plotly(fig, height=420), use_container_width=True,
2131
+ )
2132
+
2133
  pie_left, pie_right = st.columns(2)
2134
 
2135
  with pie_left:
 
2139
  .sort_values("Qty", ascending=False)
2140
  )
2141
  cat_agg = cat_agg[cat_agg["Qty"] > 0]
2142
+ cat_agg = _top_n_with_other(cat_agg, "SubType", "Qty", n=8)
2143
  if len(cat_agg) >= 2:
2144
+ _render_donut(cat_agg, "SubType", t("it_by_cat"))
 
 
 
 
 
 
 
 
 
 
 
 
2145
 
2146
  with pie_right:
2147
  if "Group1" in it_view.columns:
 
2150
  .sort_values("Qty", ascending=False)
2151
  )
2152
  protein_agg = protein_agg[protein_agg["Qty"] > 0]
2153
+ protein_agg = _top_n_with_other(protein_agg, "Group1", "Qty", n=8)
2154
  if len(protein_agg) >= 2:
2155
+ _render_donut(protein_agg, "Group1", t("it_by_protein"))
 
 
 
 
 
 
 
 
 
 
 
 
2156
 
2157
  # ── Item detail table ───────────────────────────────────
2158
  st.markdown(f"**{t('it_detail')}**")