Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,129 +1,134 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
| 3 |
-
import json
|
| 4 |
import os
|
| 5 |
-
import datetime
|
| 6 |
|
| 7 |
-
#
|
| 8 |
CONTACT_FILE = "contacts.xlsx"
|
| 9 |
MATRIX_FILE = "channel_matrix.xlsx"
|
| 10 |
-
|
| 11 |
|
| 12 |
-
|
| 13 |
-
st.markdown("<style>div[data-testid='stDataFrame'] div[contenteditable='true']{pointer-events: none;}</style>", unsafe_allow_html=True)
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
# ββββββββββββββββ LOAD CONTACTS ββββββββββββββββ
|
| 18 |
@st.cache_data
|
| 19 |
def load_contacts():
|
| 20 |
df = pd.read_excel(CONTACT_FILE)
|
| 21 |
-
df["display_name"] = df["display_name"].astype(str).str.strip()
|
| 22 |
if "country" not in df.columns:
|
| 23 |
df["country"] = "N/A"
|
|
|
|
|
|
|
| 24 |
return df
|
| 25 |
|
| 26 |
-
#
|
| 27 |
-
@st.cache_data
|
| 28 |
-
def load_matrix():
|
| 29 |
-
return pd.read_excel(MATRIX_FILE)
|
| 30 |
-
|
| 31 |
-
# ββββββββββββββββ LOAD SAVED LISTS ββββββββββββββββ
|
| 32 |
def load_saved_lists():
|
| 33 |
-
if os.path.exists(
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
df = load_contacts()
|
| 50 |
-
df["checkbox"] = False
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
if
|
| 62 |
-
|
|
|
|
|
|
|
| 63 |
else:
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
filtered_df = df[df.apply(tag_match, axis=1)]
|
| 67 |
-
if query:
|
| 68 |
-
filtered_df = filtered_df[filtered_df["display_name"].str.contains(query)]
|
| 69 |
-
|
| 70 |
-
with st.expander("β
Tick off contacts as you check them:", expanded=False):
|
| 71 |
-
edited_df = st.data_editor(filtered_df[["display_name", "country", "checkbox"]],
|
| 72 |
-
key="editor", use_container_width=True, num_rows="dynamic")
|
| 73 |
-
|
| 74 |
-
# Save ticked contacts
|
| 75 |
-
ticked_contacts = edited_df[edited_df["checkbox"] == True]
|
| 76 |
-
list_name = st.text_input("πΎ Name this list:")
|
| 77 |
-
if st.button("Save List") and list_name:
|
| 78 |
-
saved_lists = load_saved_lists()
|
| 79 |
-
contact_names = ticked_contacts["display_name"].tolist()
|
| 80 |
-
tag_str = ", ".join(selected_tags)
|
| 81 |
-
saved_lists[list_name] = {
|
| 82 |
-
"contacts": contact_names,
|
| 83 |
-
"tags": tag_str,
|
| 84 |
-
"timestamp": str(datetime.datetime.now())
|
| 85 |
-
}
|
| 86 |
-
save_lists(saved_lists)
|
| 87 |
-
st.success(f"List '{list_name}' saved!")
|
| 88 |
|
| 89 |
# Show saved lists
|
| 90 |
st.markdown("---")
|
| 91 |
-
st.
|
| 92 |
-
|
| 93 |
-
if
|
| 94 |
-
selected = st.selectbox("Select a list:", list(saved_lists.keys()))
|
| 95 |
-
if selected:
|
| 96 |
-
st.markdown(f"**Contacts in '{selected}':**")
|
| 97 |
-
st.write(saved_lists[selected]["contacts"])
|
| 98 |
-
col1, col2 = st.columns(2)
|
| 99 |
-
with col1:
|
| 100 |
-
if st.button("β Delete this list"):
|
| 101 |
-
del saved_lists[selected]
|
| 102 |
-
save_lists(saved_lists)
|
| 103 |
-
st.success("List deleted.")
|
| 104 |
-
with col2:
|
| 105 |
-
if st.button("π Rename this list"):
|
| 106 |
-
new_name = st.text_input("Enter new name:")
|
| 107 |
-
if new_name:
|
| 108 |
-
saved_lists[new_name] = saved_lists.pop(selected)
|
| 109 |
-
save_lists(saved_lists)
|
| 110 |
-
st.success("List renamed.")
|
| 111 |
-
else:
|
| 112 |
st.info("No saved lists yet.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
st.title("π Channel Matrix Checker")
|
| 117 |
-
|
| 118 |
-
matrix_df = load_matrix()
|
| 119 |
-
charterers = list(matrix_df.columns)[1:]
|
| 120 |
-
charterer = st.selectbox("Select a Charterer:", charterers)
|
| 121 |
-
|
| 122 |
-
yes_df = matrix_df[matrix_df[charterer].str.upper() == "YES"]
|
| 123 |
-
no_df = matrix_df[matrix_df[charterer].str.upper() == "NO"]
|
| 124 |
-
|
| 125 |
-
st.markdown(f"### β
Operators who work with us for **{charterer}**'s cargoes:")
|
| 126 |
-
st.dataframe(yes_df.iloc[:, [0]], use_container_width=True)
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
|
|
|
| 3 |
import os
|
|
|
|
| 4 |
|
| 5 |
+
# ---------- Constants ----------
|
| 6 |
CONTACT_FILE = "contacts.xlsx"
|
| 7 |
MATRIX_FILE = "channel_matrix.xlsx"
|
| 8 |
+
SAVED_LISTS_FILE = "saved_lists.csv"
|
| 9 |
|
| 10 |
+
TAGS = ["+mini", "+hdy", "+smx", "+pmx", "+cape", "+med", "+atl", "+rsea", "+safr", "+pg", "+wci", "+eci", "+seas", "+feast", "+nopac", "+aus", "+indo"]
|
|
|
|
| 11 |
|
| 12 |
+
# ---------- Load Contacts ----------
|
|
|
|
|
|
|
| 13 |
@st.cache_data
|
| 14 |
def load_contacts():
|
| 15 |
df = pd.read_excel(CONTACT_FILE)
|
|
|
|
| 16 |
if "country" not in df.columns:
|
| 17 |
df["country"] = "N/A"
|
| 18 |
+
df["display_name"] = df["display_name"].astype(str)
|
| 19 |
+
df["checkbox"] = False
|
| 20 |
return df
|
| 21 |
|
| 22 |
+
# ---------- Load Saved Lists ----------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
def load_saved_lists():
|
| 24 |
+
if os.path.exists(SAVED_LISTS_FILE):
|
| 25 |
+
return pd.read_csv(SAVED_LISTS_FILE)
|
| 26 |
+
return pd.DataFrame(columns=["list_name", "contacts", "tags", "timestamp"])
|
| 27 |
+
|
| 28 |
+
def save_list(list_name, selected_contacts, tags):
|
| 29 |
+
existing = load_saved_lists()
|
| 30 |
+
contacts_str = ";".join(selected_contacts)
|
| 31 |
+
tags_str = ";".join(tags)
|
| 32 |
+
timestamp = pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 33 |
+
new_row = pd.DataFrame([{
|
| 34 |
+
"list_name": list_name,
|
| 35 |
+
"contacts": contacts_str,
|
| 36 |
+
"tags": tags_str,
|
| 37 |
+
"timestamp": timestamp
|
| 38 |
+
}])
|
| 39 |
+
updated = pd.concat([existing, new_row], ignore_index=True)
|
| 40 |
+
updated.to_csv(SAVED_LISTS_FILE, index=False)
|
| 41 |
+
|
| 42 |
+
# ---------- UI: Filter + Tick ----------
|
| 43 |
+
def contact_filter_ui():
|
| 44 |
+
st.markdown("### π Filter Contacts")
|
| 45 |
+
st.markdown("Use the checkboxes below to filter by tags. Use AND / OR logic to combine tags.")
|
| 46 |
+
|
| 47 |
+
logic = st.radio("Matching logic:", ["AND", "OR"], horizontal=True)
|
| 48 |
+
selected_tags = st.multiselect("Filter by Tags", options=TAGS)
|
| 49 |
|
| 50 |
df = load_contacts()
|
|
|
|
| 51 |
|
| 52 |
+
# Tag filtering
|
| 53 |
+
if selected_tags:
|
| 54 |
+
if logic == "AND":
|
| 55 |
+
mask = df["tags"].apply(lambda x: all(tag in str(x).split() for tag in selected_tags))
|
| 56 |
+
else:
|
| 57 |
+
mask = df["tags"].apply(lambda x: any(tag in str(x).split() for tag in selected_tags))
|
| 58 |
+
df = df[mask]
|
| 59 |
+
|
| 60 |
+
# Display
|
| 61 |
+
st.markdown("### β
Tick Contacts to Save in a List")
|
| 62 |
+
edited_df = st.data_editor(
|
| 63 |
+
df[["display_name", "country", "checkbox"]],
|
| 64 |
+
use_container_width=True,
|
| 65 |
+
key="editor"
|
| 66 |
+
)
|
| 67 |
|
| 68 |
+
ticked = edited_df[edited_df["checkbox"] == True]["display_name"].tolist()
|
| 69 |
|
| 70 |
+
st.markdown("---")
|
| 71 |
+
st.markdown("### πΎ Save Current Selection")
|
| 72 |
+
list_name = st.text_input("List Name")
|
| 73 |
+
if st.button("Save List"):
|
| 74 |
+
if not list_name:
|
| 75 |
+
st.error("Please enter a name for the list.")
|
| 76 |
+
elif not ticked:
|
| 77 |
+
st.warning("No contacts selected.")
|
| 78 |
else:
|
| 79 |
+
save_list(list_name, ticked, selected_tags)
|
| 80 |
+
st.success(f"List '{list_name}' saved!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
# Show saved lists
|
| 83 |
st.markdown("---")
|
| 84 |
+
st.markdown("### π Manage Saved Lists")
|
| 85 |
+
saved_df = load_saved_lists()
|
| 86 |
+
if saved_df.empty:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
st.info("No saved lists yet.")
|
| 88 |
+
else:
|
| 89 |
+
for _, row in saved_df.iterrows():
|
| 90 |
+
with st.expander(f"π {row['list_name']} ({row['timestamp']})"):
|
| 91 |
+
st.write("Tags:", row["tags"])
|
| 92 |
+
st.write("Contacts:")
|
| 93 |
+
for c in row["contacts"].split(";"):
|
| 94 |
+
st.markdown(f"- {c}")
|
| 95 |
+
|
| 96 |
+
# ---------- UI: Channel Matrix ----------
|
| 97 |
+
def channel_matrix_ui():
|
| 98 |
+
st.markdown("## π§ Channel Matrix")
|
| 99 |
+
try:
|
| 100 |
+
matrix_df = pd.read_excel(MATRIX_FILE)
|
| 101 |
+
matrix_df = matrix_df.rename(columns={matrix_df.columns[0]: "Operator"})
|
| 102 |
+
charterers = list(matrix_df.columns[1:])
|
| 103 |
+
except Exception as e:
|
| 104 |
+
st.error("Could not load Channel Matrix. Check file formatting.")
|
| 105 |
+
st.exception(e)
|
| 106 |
+
return
|
| 107 |
+
|
| 108 |
+
selected_charterer = st.selectbox("Select a Charterer", charterers)
|
| 109 |
+
|
| 110 |
+
if selected_charterer:
|
| 111 |
+
yes_ops = matrix_df[matrix_df[selected_charterer].astype(str).str.upper() == "YES"]["Operator"]
|
| 112 |
+
no_ops = matrix_df[matrix_df[selected_charterer].astype(str).str.upper() == "NO"]["Operator"]
|
| 113 |
+
|
| 114 |
+
col1, col2 = st.columns(2)
|
| 115 |
+
|
| 116 |
+
with col1:
|
| 117 |
+
st.success(f"β
Operators who work with us for {selected_charterer}'s cargoes:")
|
| 118 |
+
st.dataframe(yes_ops.reset_index(drop=True), use_container_width=True)
|
| 119 |
+
|
| 120 |
+
with col2:
|
| 121 |
+
st.error(f"β Operators who won't work with us for {selected_charterer}'s cargoes:")
|
| 122 |
+
st.dataframe(no_ops.reset_index(drop=True), use_container_width=True)
|
| 123 |
+
|
| 124 |
+
# ---------- Main App ----------
|
| 125 |
+
st.set_page_config(page_title="Skype Contacts", layout="wide")
|
| 126 |
+
st.title("π Skype Contacts & Channel Matrix Tool")
|
| 127 |
+
|
| 128 |
+
tabs = st.tabs(["π Contact Filter", "π Channel Matrix"])
|
| 129 |
|
| 130 |
+
with tabs[0]:
|
| 131 |
+
contact_filter_ui()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
+
with tabs[1]:
|
| 134 |
+
channel_matrix_ui()
|