import streamlit as st from components.buttons import render_top_buttons from components.card import render_card from components.header import render_header from services.api import delete_card as api_delete_card from services.api import load_cards, load_tabs, save_card, save_tab, update_card from utils.time_utils import build_card_payload, normalize_cards, sort_cards_by_upcoming_time st.set_page_config( page_title="Time Card Generator", page_icon="⏱️", layout="wide", initial_sidebar_state="collapsed", ) def load_css() -> None: with open("assets/style.css", "r", encoding="utf-8") as css_file: st.markdown(f"", unsafe_allow_html=True) def initialize_state() -> None: if "cards" not in st.session_state: st.session_state.cards = [] if "tabs" not in st.session_state: st.session_state.tabs = [] if "cards_loaded" not in st.session_state: st.session_state.cards_loaded = False if "status_message" not in st.session_state: st.session_state.status_message = None if "status_type" not in st.session_state: st.session_state.status_type = "info" if "current_tab_id" not in st.session_state: st.session_state.current_tab_id = "" if "new_tab_counter" not in st.session_state: st.session_state.new_tab_counter = 1 if "new_tab_name" not in st.session_state: st.session_state.new_tab_name = "" def set_status(message: str, status_type: str = "info") -> None: st.session_state.status_message = message st.session_state.status_type = status_type def clear_status() -> None: st.session_state.status_message = None st.session_state.status_type = "info" def fetch_cards_once() -> None: if st.session_state.cards_loaded: return try: st.session_state.tabs = load_tabs() if not st.session_state.tabs: default_tab = save_tab({"title": "Tab 1"}) st.session_state.tabs = [default_tab] st.session_state.new_tab_counter = 2 else: st.session_state.new_tab_counter = len(st.session_state.tabs) + 1 if not st.session_state.current_tab_id: st.session_state.current_tab_id = st.session_state.tabs[0]["id"] st.session_state.cards = normalize_cards(load_cards()) assign_missing_tab_ids() st.session_state.cards_loaded = True clear_status() except Exception as exc: # noqa: BLE001 st.session_state.cards = [] st.session_state.tabs = [] st.session_state.cards_loaded = True set_status(f"Unable to load saved cards: {exc}", "error") def reload_cards() -> None: try: st.session_state.tabs = load_tabs() if not st.session_state.tabs: default_tab = save_tab({"title": "Tab 1"}) st.session_state.tabs = [default_tab] tab_ids = {tab["id"] for tab in st.session_state.tabs} if st.session_state.current_tab_id not in tab_ids: st.session_state.current_tab_id = st.session_state.tabs[0]["id"] st.session_state.new_tab_counter = len(st.session_state.tabs) + 1 st.session_state.cards = normalize_cards(load_cards()) assign_missing_tab_ids() st.session_state.cards_loaded = True clear_status() set_status("Cards refreshed.", "success") except Exception as exc: # noqa: BLE001 set_status(f"Refresh failed: {exc}", "error") def add_card() -> None: card = build_card_payload() card["tabId"] = st.session_state.current_tab_id st.session_state.cards.append(card) clear_status() def add_tab() -> None: try: typed_title = st.session_state.get("new_tab_name", "").strip() title = typed_title or f"Tab {st.session_state.new_tab_counter}" new_tab = save_tab({"title": title}) st.session_state.tabs.append(new_tab) st.session_state.current_tab_id = new_tab["id"] st.session_state.new_tab_counter += 1 st.session_state.new_tab_name = "" clear_status() set_status("Tab created.", "success") except Exception as exc: # noqa: BLE001 set_status(f"Unable to create tab: {exc}", "error") def assign_missing_tab_ids() -> None: if not st.session_state.tabs: return fallback_tab_id = st.session_state.tabs[0]["id"] for card in st.session_state.cards: if not card.get("tabId"): card["tabId"] = fallback_tab_id def delete_local_card(index: int) -> None: st.session_state.cards.pop(index) clear_status() def handle_delete(index: int) -> None: card = st.session_state.cards[index] card_id = card.get("id") if not card_id: delete_local_card(index) return try: api_delete_card(card_id) delete_local_card(index) set_status("Card deleted.", "success") except Exception as exc: # noqa: BLE001 set_status(f"Delete failed: {exc}", "error") def handle_save(index: int) -> None: card = st.session_state.cards[index] if not card["title"].strip(): set_status("Title is required before saving.", "warning") return try: if card.get("id"): updated_card = update_card(card) st.session_state.cards[index] = updated_card set_status("Card updated.", "success") else: saved_card = save_card(card) st.session_state.cards[index] = saved_card set_status("Card saved.", "success") except Exception as exc: # noqa: BLE001 set_status(f"Save failed: {exc}", "error") load_css() initialize_state() fetch_cards_once() render_top_buttons(add_card, add_tab, reload_cards) render_header() if st.session_state.status_message: getattr(st, st.session_state.status_type)(st.session_state.status_message) if st.session_state.tabs: tab_title_by_id = {tab["id"]: tab["title"] for tab in st.session_state.tabs} tab_ids = [tab["id"] for tab in st.session_state.tabs] current_tab_id = ( st.session_state.current_tab_id if st.session_state.current_tab_id in tab_ids else tab_ids[0] ) selected_tab_id = st.radio( "Tabs", options=tab_ids, format_func=lambda tab_id: tab_title_by_id.get(tab_id, "Untitled Tab"), horizontal=True, key="selected_tab_id", index=tab_ids.index(current_tab_id), label_visibility="collapsed", ) st.session_state.current_tab_id = selected_tab_id visible_cards = [ card for card in st.session_state.cards if card.get("tabId", "") == st.session_state.current_tab_id ] visible_cards = sort_cards_by_upcoming_time(visible_cards) visible_card_ids = [card.get("id") or card.get("clientKey") for card in visible_cards] display_indices = [] for visible_id in visible_card_ids: for index, card in enumerate(st.session_state.cards): candidate_id = card.get("id") or card.get("clientKey") if card.get("tabId", "") == st.session_state.current_tab_id and candidate_id == visible_id: display_indices.append(index) break if not display_indices: st.markdown( """
Add a card or switch to another tab.