Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,133 +1,140 @@
|
|
| 1 |
-
import streamlit as st
|
| 2 |
import pandas as pd
|
| 3 |
-
import
|
|
|
|
| 4 |
import os
|
| 5 |
-
|
| 6 |
|
| 7 |
-
|
|
|
|
|
|
|
| 8 |
MATRIX_FILE = "channel_matrix.xlsx"
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
|
|
|
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
def load_contacts():
|
| 16 |
-
df = pd.read_excel(CONTACT_FILE)
|
| 17 |
-
df["display_name"] = df["display_name"].astype(str).str.strip().str.lower()
|
| 18 |
if "country" not in df.columns:
|
| 19 |
df["country"] = "N/A"
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
return json.load(f)
|
| 33 |
-
return {}
|
| 34 |
-
|
| 35 |
-
def save_list(name, contact_names, tags=[]):
|
| 36 |
-
lists = load_saved_lists()
|
| 37 |
-
lists[name] = {
|
| 38 |
-
"contacts": contact_names,
|
| 39 |
-
"tags": tags,
|
| 40 |
-
"timestamp": datetime.now().isoformat()
|
| 41 |
-
}
|
| 42 |
-
with open(SAVED_LISTS_FILE, "w") as f:
|
| 43 |
-
json.dump(lists, f)
|
| 44 |
-
|
| 45 |
-
# UI: Filter & Editable Contact Table
|
| 46 |
-
def contact_filter_ui():
|
| 47 |
-
st.header("π Skype Contact Manager")
|
| 48 |
-
|
| 49 |
-
# Sidebar Tag Filter
|
| 50 |
-
all_tags = sorted(set(tag for row in df["tags"] for tag in str(row).split()))
|
| 51 |
-
selected_tags = st.sidebar.multiselect("π Filter by Tags", options=all_tags)
|
| 52 |
-
logic_mode = st.sidebar.radio("Tag Filter Logic", ["AND", "OR"])
|
| 53 |
-
|
| 54 |
-
# Filter Contacts
|
| 55 |
if selected_tags:
|
| 56 |
-
|
| 57 |
-
|
|
|
|
|
|
|
| 58 |
else:
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
else:
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
#
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
col1, col2 = st.columns(2)
|
| 122 |
-
with col1:
|
| 123 |
-
st.success("β
Operators who work with us")
|
| 124 |
-
st.dataframe(pd.DataFrame({"Operator": yes_ops}), use_container_width=True)
|
| 125 |
-
with col2:
|
| 126 |
-
st.error("β Operators who won't work with us")
|
| 127 |
-
st.dataframe(pd.DataFrame({"Operator": no_ops}), use_container_width=True)
|
| 128 |
-
|
| 129 |
-
# Page Layout
|
| 130 |
-
contact_filter_ui()
|
| 131 |
-
save_list_ui()
|
| 132 |
-
load_list_ui()
|
| 133 |
-
channel_matrix_ui()
|
|
|
|
|
|
|
| 1 |
import pandas as pd
|
| 2 |
+
import streamlit as st
|
| 3 |
+
import re
|
| 4 |
import os
|
| 5 |
+
import json
|
| 6 |
|
| 7 |
+
st.set_page_config(page_title="Skype Tool + Charterer Matrix", layout="wide")
|
| 8 |
+
|
| 9 |
+
CONTACT_FILE = "contacts.csv"
|
| 10 |
MATRIX_FILE = "channel_matrix.xlsx"
|
| 11 |
+
SAVE_FILE = "saved_lists.json"
|
| 12 |
+
|
| 13 |
+
tabs = st.tabs(["Contact Filter", "Channel Matrix"])
|
| 14 |
+
|
| 15 |
+
# Load or initialize saved lists
|
| 16 |
+
if os.path.exists(SAVE_FILE):
|
| 17 |
+
with open(SAVE_FILE, "r") as f:
|
| 18 |
+
saved_data = json.load(f)
|
| 19 |
+
else:
|
| 20 |
+
saved_data = {}
|
| 21 |
+
|
| 22 |
+
# TAB 1: Contact Filter Tool
|
| 23 |
+
with tabs[0]:
|
| 24 |
+
st.title("π Skype Contact Filter Tool")
|
| 25 |
|
| 26 |
+
TAGS = ["+mini", "+hdy", "+smx", "+pmx", "+cape", "+med", "+atl", "+rsea",
|
| 27 |
+
"+safr", "+pg", "+wci", "+eci", "+seas", "+feast", "+nopac", "+aus", "+aust"]
|
| 28 |
|
| 29 |
+
df = pd.read_csv(CONTACT_FILE)
|
| 30 |
+
df["display_name"] = df["display_name"].astype(str).str.lower()
|
|
|
|
|
|
|
|
|
|
| 31 |
if "country" not in df.columns:
|
| 32 |
df["country"] = "N/A"
|
| 33 |
+
|
| 34 |
+
filter_mode = st.radio("Filter Mode", ["AND", "OR"], horizontal=True)
|
| 35 |
+
|
| 36 |
+
st.markdown("### Select Tags to Filter")
|
| 37 |
+
selected_tags = []
|
| 38 |
+
cols = st.columns(6)
|
| 39 |
+
for i, tag in enumerate(TAGS):
|
| 40 |
+
if cols[i % 6].checkbox(tag):
|
| 41 |
+
selected_tags.append(tag)
|
| 42 |
+
|
| 43 |
+
ticked_contacts = []
|
| 44 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
if selected_tags:
|
| 46 |
+
filtered_df = df.copy()
|
| 47 |
+
if filter_mode == "AND":
|
| 48 |
+
for tag in selected_tags:
|
| 49 |
+
filtered_df = filtered_df[filtered_df["display_name"].str.contains(re.escape(tag))]
|
| 50 |
else:
|
| 51 |
+
pattern = "|".join(re.escape(tag) for tag in selected_tags)
|
| 52 |
+
filtered_df = filtered_df[filtered_df["display_name"].str.contains(pattern)]
|
| 53 |
+
|
| 54 |
+
st.success(f"Found {len(filtered_df)} matching contacts.")
|
| 55 |
+
|
| 56 |
+
with st.expander("β
Tick off contacts as you check them", expanded=False):
|
| 57 |
+
for i, row in filtered_df.iterrows():
|
| 58 |
+
ticked = st.checkbox(f"**{row['display_name'].title()}** _( {row['country']} )_", key=f"contact_{i}")
|
| 59 |
+
if ticked:
|
| 60 |
+
ticked_contacts.append({"display_name": row["display_name"], "country": row["country"]})
|
| 61 |
+
|
| 62 |
+
# Save preset block
|
| 63 |
+
if ticked_contacts:
|
| 64 |
+
st.markdown("### πΎ Save this list as a preset")
|
| 65 |
+
preset_name = st.text_input("Enter a name for this contact list")
|
| 66 |
+
if st.button("Save List"):
|
| 67 |
+
if preset_name:
|
| 68 |
+
saved_data[preset_name] = {
|
| 69 |
+
"tags": selected_tags,
|
| 70 |
+
"contacts": ticked_contacts
|
| 71 |
+
}
|
| 72 |
+
with open(SAVE_FILE, "w") as f:
|
| 73 |
+
json.dump(saved_data, f, indent=2)
|
| 74 |
+
st.success(f"List '{preset_name}' saved successfully!")
|
| 75 |
+
else:
|
| 76 |
+
st.warning("Please enter a name before saving.")
|
| 77 |
+
|
| 78 |
+
# Download button
|
| 79 |
+
csv_data = pd.DataFrame(ticked_contacts).to_csv(index=False)
|
| 80 |
+
st.download_button("π₯ Download Ticked Contacts as CSV", data=csv_data, file_name="ticked_contacts.csv", mime="text/csv")
|
| 81 |
else:
|
| 82 |
+
st.info("Select at least one tag to filter the contacts.")
|
| 83 |
+
|
| 84 |
+
# Load and manage saved lists
|
| 85 |
+
st.markdown("---")
|
| 86 |
+
st.markdown("### π Load or Manage a Saved List")
|
| 87 |
+
if saved_data:
|
| 88 |
+
selected_preset = st.selectbox("Select a saved contact list", list(saved_data.keys()))
|
| 89 |
+
if selected_preset:
|
| 90 |
+
st.markdown(f"**Tags:** `{', '.join(saved_data[selected_preset]['tags'])}`")
|
| 91 |
+
st.markdown("**Contacts in this list:**")
|
| 92 |
+
for c in saved_data[selected_preset]["contacts"]:
|
| 93 |
+
st.markdown(f"- **{c['display_name'].title()}** _( {c['country']} )_")
|
| 94 |
+
|
| 95 |
+
st.markdown("#### ποΈ Delete or Rename List")
|
| 96 |
+
col1, col2 = st.columns(2)
|
| 97 |
+
with col1:
|
| 98 |
+
if st.button("β Delete This List"):
|
| 99 |
+
del saved_data[selected_preset]
|
| 100 |
+
with open(SAVE_FILE, "w") as f:
|
| 101 |
+
json.dump(saved_data, f, indent=2)
|
| 102 |
+
st.success(f"Deleted list '{selected_preset}'. Please reload the page.")
|
| 103 |
+
with col2:
|
| 104 |
+
new_name = st.text_input("Rename List As", value=selected_preset)
|
| 105 |
+
if st.button("βοΈ Rename"):
|
| 106 |
+
if new_name and new_name != selected_preset:
|
| 107 |
+
saved_data[new_name] = saved_data.pop(selected_preset)
|
| 108 |
+
with open(SAVE_FILE, "w") as f:
|
| 109 |
+
json.dump(saved_data, f, indent=2)
|
| 110 |
+
st.success(f"Renamed to '{new_name}'. Please reload the page.")
|
| 111 |
+
else:
|
| 112 |
+
st.info("No saved lists available yet.")
|
| 113 |
+
|
| 114 |
+
# TAB 2: Charterer-Operator Matrix
|
| 115 |
+
with tabs[1]:
|
| 116 |
+
st.title("π Channel Matrix Checker")
|
| 117 |
+
|
| 118 |
+
try:
|
| 119 |
+
matrix_df = pd.read_excel(MATRIX_FILE)
|
| 120 |
+
matrix_df = matrix_df.rename(columns={matrix_df.columns[0]: "Operator"})
|
| 121 |
+
|
| 122 |
+
charterers = list(matrix_df.columns[1:])
|
| 123 |
+
selected_charterer = st.selectbox("Select a Charterer", charterers)
|
| 124 |
+
|
| 125 |
+
if selected_charterer:
|
| 126 |
+
yes_ops = matrix_df[matrix_df[selected_charterer].astype(str).str.upper() == "YES"]["Operator"]
|
| 127 |
+
no_ops = matrix_df[matrix_df[selected_charterer].astype(str).str.upper() == "NO"]["Operator"]
|
| 128 |
+
|
| 129 |
+
col1, col2 = st.columns(2)
|
| 130 |
+
with col1:
|
| 131 |
+
st.success(f"β
Operators who work with us for {selected_charterer}'s cargoes:")
|
| 132 |
+
for name in yes_ops:
|
| 133 |
+
st.markdown(f"- **{name}**")
|
| 134 |
+
with col2:
|
| 135 |
+
st.error(f"β Operators who won't work with us for {selected_charterer}'s cargoes:")
|
| 136 |
+
for name in no_ops:
|
| 137 |
+
st.markdown(f"- {name}")
|
| 138 |
+
except Exception as e:
|
| 139 |
+
st.warning("Could not load matrix file. Please ensure 'channel_matrix.xlsx' exists.")
|
| 140 |
+
st.text(f"Error: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|