MIF_E31232465/page_modules/table_utils.py

272 lines
5.4 KiB
Python

from html import escape
import pandas as pd
import streamlit as st
TABLE_CSS = """
<style>
.sd-table-card {
background: #ffffff;
border: 1.5px solid #e2e8f0;
border-radius: 12px;
box-shadow: 0 2px 6px rgba(15,23,42,0.06);
overflow: hidden;
margin-top: 0.25rem;
}
.sd-table-scroll {
overflow: auto;
background: #ffffff;
}
.sd-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
color: #0f172a;
}
.sd-table thead th {
position: sticky;
top: 0;
z-index: 2;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
color: #475569;
font-size: 0.72rem;
font-weight: 800;
padding: 0.72rem 0.78rem;
text-align: left;
white-space: nowrap;
}
.sd-table tbody td {
border-top: 1px solid #eef2f7;
color: #334155;
font-size: 0.78rem;
line-height: 1.55;
padding: 0.78rem;
vertical-align: top;
overflow-wrap: anywhere;
}
.sd-table tbody tr:first-child td {
border-top: 0;
}
.sd-table tbody tr:hover td {
background: #f8fafc;
}
.sd-table .sd-table-right {
text-align: right;
}
.sd-table .sd-table-nowrap {
white-space: nowrap;
overflow-wrap: normal;
}
.sd-table .sd-table-wide {
min-width: 240px;
}
.sd-table-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 88px;
border-radius: 999px;
padding: 0.25rem 0.62rem;
font-size: 0.74rem;
font-weight: 800;
line-height: 1.2 !important;
white-space: nowrap;
}
.sd-badge-green {
background: #f0fdf4;
border: 1px solid #bbf7d0;
color: #16a34a !important;
}
.sd-badge-red {
background: #fef2f2;
border: 1px solid #fecaca;
color: #dc2626 !important;
}
.sd-badge-slate {
background: #f8fafc;
border: 1px solid #cbd5e1;
color: #475569 !important;
}
.sd-badge-blue {
background: #eef2ff;
border: 1px solid #c7d2fe;
color: #3b6cf7 !important;
}
</style>
"""
TEXT_COLUMN_HINTS = (
"tweet",
"teks",
"isi",
"keterangan",
"bersih",
"clean",
"hasil akhir",
)
NOWRAP_COLUMN_HINTS = (
"id",
"no",
"tanggal",
"waktu",
"status",
"sentimen",
"keyakinan",
"database",
)
def _plain_value(value):
if value is None:
return "-"
try:
if pd.isna(value):
return "-"
except (TypeError, ValueError):
pass
return str(value)
def _badge_class(value):
text = value.lower()
if "negatif" in text or "gagal" in text:
return "sd-badge-red"
if "positif" in text or "berhasil" in text:
return "sd-badge-green"
if "netral" in text:
return "sd-badge-slate"
return "sd-badge-blue"
def _is_text_column(column):
name = column.lower()
return any(hint in name for hint in TEXT_COLUMN_HINTS)
def _is_nowrap_column(column):
name = column.lower()
return any(hint in name for hint in NOWRAP_COLUMN_HINTS)
def render_standard_table(
df,
*,
height=380,
min_width=760,
right_align=None,
nowrap=None,
badge_columns=None,
wide_columns=None,
column_widths=None,
):
if df.empty:
st.info("Tidak ada data untuk ditampilkan.")
return
right_align = set(right_align or [])
nowrap = set(nowrap or [])
badge_columns = set(badge_columns or [])
wide_columns = set(wide_columns or [])
column_widths = column_widths or {}
columns = list(df.columns)
colgroup = ""
if column_widths:
col_tags = []
for column in columns:
width = column_widths.get(column)
style = f' style="width:{escape(str(width))};"' if width else ""
col_tags.append(f"<col{style}>")
colgroup = f"<colgroup>{''.join(col_tags)}</colgroup>"
header_cells = []
for column in columns:
classes = []
if column in right_align:
classes.append("sd-table-right")
header_cells.append(
f'<th class="{" ".join(classes)}">{escape(str(column))}</th>'
)
body_rows = []
for _, row in df.iterrows():
cells = []
for column in columns:
value = _plain_value(row[column])
classes = []
if column in right_align:
classes.append("sd-table-right")
if column in nowrap or _is_nowrap_column(str(column)):
classes.append("sd-table-nowrap")
if column in wide_columns or _is_text_column(str(column)):
classes.append("sd-table-wide")
class_attr = f' class="{" ".join(classes)}"' if classes else ""
if column in badge_columns:
badge_class = _badge_class(value)
cell_html = (
f'<span class="sd-table-badge {badge_class}">'
f"{escape(value)}</span>"
)
else:
cell_html = escape(value)
cells.append(f"<td{class_attr}>{cell_html}</td>")
body_rows.append(f"<tr>{''.join(cells)}</tr>")
table_html = f"""
{TABLE_CSS}
<div class="sd-table-card">
<div class="sd-table-scroll" style="max-height:{int(height)}px;">
<table class="sd-table" style="min-width:{int(min_width)}px;">
{colgroup}
<thead>
<tr>{''.join(header_cells)}</tr>
</thead>
<tbody>
{''.join(body_rows)}
</tbody>
</table>
</div>
</div>
"""
st.markdown(table_html, unsafe_allow_html=True)