Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,134 +1,133 @@
|
|
| 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.
|
| 9 |
|
| 10 |
-
|
| 11 |
|
| 12 |
-
#
|
| 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 |
-
|
| 19 |
-
|
| 20 |
return df
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
def load_saved_lists():
|
| 24 |
if os.path.exists(SAVED_LISTS_FILE):
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
"
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
updated.to_csv(SAVED_LISTS_FILE, index=False)
|
| 41 |
-
|
| 42 |
-
# ---------- UI: Filter + Tick ----------
|
| 43 |
def contact_filter_ui():
|
| 44 |
-
st.
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
#
|
| 53 |
if selected_tags:
|
| 54 |
-
if
|
| 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 |
-
|
| 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 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
return
|
| 107 |
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
-
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
-
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
| 3 |
+
import json
|
| 4 |
import os
|
| 5 |
+
from datetime import datetime
|
| 6 |
|
|
|
|
| 7 |
CONTACT_FILE = "contacts.xlsx"
|
| 8 |
MATRIX_FILE = "channel_matrix.xlsx"
|
| 9 |
+
SAVED_LISTS_FILE = "saved_lists.json"
|
| 10 |
|
| 11 |
+
st.set_page_config(page_title="Skype Contacts Manager", layout="wide")
|
| 12 |
|
| 13 |
+
# Load contacts
|
| 14 |
@st.cache_data
|
| 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 |
+
if "tags" not in df.columns:
|
| 21 |
+
df["tags"] = ""
|
| 22 |
return df
|
| 23 |
|
| 24 |
+
df = load_contacts()
|
| 25 |
+
if "checkbox" not in df.columns:
|
| 26 |
+
df["checkbox"] = False
|
| 27 |
+
|
| 28 |
+
# Load saved lists
|
| 29 |
def load_saved_lists():
|
| 30 |
if os.path.exists(SAVED_LISTS_FILE):
|
| 31 |
+
with open(SAVED_LISTS_FILE, "r") as f:
|
| 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 |
+
if logic_mode == "AND":
|
| 57 |
mask = df["tags"].apply(lambda x: all(tag in str(x).split() for tag in selected_tags))
|
| 58 |
else:
|
| 59 |
mask = df["tags"].apply(lambda x: any(tag in str(x).split() for tag in selected_tags))
|
| 60 |
+
filtered_df = df[mask].copy()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
else:
|
| 62 |
+
filtered_df = df.copy()
|
| 63 |
+
|
| 64 |
+
# Editable Table
|
| 65 |
+
with st.expander("β
Tick off contacts as you check them", expanded=False):
|
| 66 |
+
edited_df = st.data_editor(
|
| 67 |
+
filtered_df[["display_name", "country", "checkbox"]],
|
| 68 |
+
num_rows="dynamic",
|
| 69 |
+
key="editor"
|
| 70 |
+
)
|
| 71 |
+
for idx in edited_df.index:
|
| 72 |
+
df.loc[df["display_name"] == edited_df.loc[idx, "display_name"], "checkbox"] = edited_df.loc[idx, "checkbox"]
|
| 73 |
+
|
| 74 |
+
st.divider()
|
| 75 |
+
|
| 76 |
+
# Save List Section
|
| 77 |
+
def save_list_ui():
|
| 78 |
+
st.subheader("πΎ Save Selected List")
|
| 79 |
+
ticked_contacts = df[df["checkbox"]]["display_name"].tolist()
|
| 80 |
+
|
| 81 |
+
if not ticked_contacts:
|
| 82 |
+
st.info("β
Tick contacts in the above section to create a list.")
|
| 83 |
return
|
| 84 |
|
| 85 |
+
list_name = st.text_input("List name")
|
| 86 |
+
if st.button("Save List") and list_name:
|
| 87 |
+
save_list(list_name, ticked_contacts)
|
| 88 |
+
st.success(f"List '{list_name}' saved!")
|
| 89 |
|
| 90 |
+
# Manage Saved Lists
|
| 91 |
+
def load_list_ui():
|
| 92 |
+
st.subheader("π Load or Manage a Saved List")
|
| 93 |
+
saved_lists = load_saved_lists()
|
| 94 |
+
if not saved_lists:
|
| 95 |
+
st.info("No saved lists yet.")
|
| 96 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
+
list_name = st.selectbox("Select a List", list(saved_lists.keys()))
|
| 99 |
+
if list_name:
|
| 100 |
+
contacts = saved_lists[list_name]["contacts"]
|
| 101 |
+
st.write(f"List: **{list_name}** β {len(contacts)} contacts")
|
| 102 |
+
st.write(", ".join(contacts))
|
| 103 |
|
| 104 |
+
if st.button(f"β Delete '{list_name}'"):
|
| 105 |
+
saved_lists.pop(list_name)
|
| 106 |
+
with open(SAVED_LISTS_FILE, "w") as f:
|
| 107 |
+
json.dump(saved_lists, f)
|
| 108 |
+
st.success("List deleted!")
|
| 109 |
|
| 110 |
+
# Channel Matrix
|
| 111 |
+
def channel_matrix_ui():
|
| 112 |
+
st.header("π Channel Matrix")
|
| 113 |
+
matrix_df = pd.read_excel(MATRIX_FILE)
|
| 114 |
+
matrix_df = matrix_df.rename(columns={matrix_df.columns[0]: "Operator"})
|
| 115 |
+
charterers = list(matrix_df.columns[1:])
|
| 116 |
+
selected = st.selectbox("Select a Charterer", charterers)
|
| 117 |
+
|
| 118 |
+
yes_ops = matrix_df[matrix_df[selected].astype(str).str.upper() == "YES"]["Operator"]
|
| 119 |
+
no_ops = matrix_df[matrix_df[selected].astype(str).str.upper() == "NO"]["Operator"]
|
| 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()
|