Files
finanse/app.py

650 lines
26 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import streamlit as st
import pandas as pd
import sqlite3
import os
import calendar
from datetime import date, datetime
# --- KONFIGURACJA ---
st.set_page_config(page_title="Finanse Domowe", layout="wide", page_icon="💰")
DB_FILE = 'data/finanse.db'
# --- CSS (STYLIZACJA) ---
st.markdown("""
<style>
div[data-testid="stVerticalBlock"] > div[style*="flex-direction: column;"] > div[data-testid="stVerticalBlock"] {
gap: 0.2rem;
}
div[data-testid="stColumn"] {
border-radius: 5px;
}
section[data-testid="stMain"] div[data-testid="stButton"] button {
min-height: 25px !important;
height: auto !important;
padding-top: 4px !important;
padding-bottom: 4px !important;
padding-left: 8px !important;
padding-right: 8px !important;
margin-top: 2px !important;
}
section[data-testid="stMain"] div[data-testid="stButton"] button p {
font-size: 13px !important;
font-weight: 400 !important;
}
div[key="cal_today_btn"] button {
margin-top: 28px !important;
}
.budget-ok { color: green; font-weight: bold; }
.budget-warn { color: orange; font-weight: bold; }
.budget-over { color: red; font-weight: bold; }
</style>
""", unsafe_allow_html=True)
# --- BEZPIECZEŃSTWO ---
def check_password():
if "password_correct" not in st.session_state:
st.session_state.password_correct = False
if st.session_state.password_correct:
return True
col1, col2, col3 = st.columns([1,2,1])
with col2:
st.markdown("### 🔒 Dostęp autoryzowany")
password = st.text_input("Podaj hasło", type="password")
if st.button("Zaloguj"):
secret_pass = os.environ.get("APP_PASSWORD", "admin")
if password == secret_pass:
st.session_state.password_correct = True
st.rerun()
else:
st.error("Błędne hasło!")
return False
if not check_password():
st.stop()
# --- BAZA DANYCH ---
def init_db():
os.makedirs(os.path.dirname(DB_FILE), exist_ok=True)
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS wydatki (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nazwa TEXT NOT NULL,
kategoria TEXT,
kwota REAL,
termin DATE,
cykliczne BOOLEAN,
zaplacone BOOLEAN
)
''')
try: c.execute("ALTER TABLE wydatki ADD COLUMN interwal_ilosc INTEGER DEFAULT 1")
except sqlite3.OperationalError: pass
try: c.execute("ALTER TABLE wydatki ADD COLUMN interwal_typ TEXT DEFAULT 'miesiące'")
except sqlite3.OperationalError: pass
c.execute('''CREATE TABLE IF NOT EXISTS kategorie (nazwa TEXT PRIMARY KEY)''')
c.execute('''CREATE TABLE IF NOT EXISTS budzety (kategoria TEXT PRIMARY KEY, limit_kwota REAL)''')
c.execute('''
CREATE TABLE IF NOT EXISTS cele (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nazwa TEXT NOT NULL,
kwota_cel REAL,
kwota_obecna REAL DEFAULT 0,
termin DATE,
ikona TEXT
)
''')
c.execute("SELECT count(*) FROM kategorie")
if c.fetchone()[0] == 0:
default_cats = [("Domowe",), ("Firmowe",), ("Podatki",), ("Paliwo",), ("Jedzenie",)]
c.executemany("INSERT INTO kategorie (nazwa) VALUES (?)", default_cats)
conn.commit()
conn.close()
def get_categories():
conn = sqlite3.connect(DB_FILE)
df = pd.read_sql_query("SELECT nazwa FROM kategorie ORDER BY nazwa", conn)
conn.close()
return df['nazwa'].tolist()
def add_category_db(name):
conn = sqlite3.connect(DB_FILE)
try:
conn.execute("INSERT INTO kategorie (nazwa) VALUES (?)", (name,))
conn.commit()
return True
except sqlite3.IntegrityError:
return False
finally:
conn.close()
def delete_category_db(name):
conn = sqlite3.connect(DB_FILE)
conn.execute("DELETE FROM kategorie WHERE nazwa = ?", (name,))
conn.execute("DELETE FROM budzety WHERE kategoria = ?", (name,))
conn.commit()
conn.close()
def get_budgets():
conn = sqlite3.connect(DB_FILE)
df = pd.read_sql_query("SELECT * FROM budzety", conn)
conn.close()
if df.empty: return {}
return dict(zip(df['kategoria'], df['limit_kwota']))
def set_budget_db(category, limit):
conn = sqlite3.connect(DB_FILE)
conn.execute("INSERT INTO budzety (kategoria, limit_kwota) VALUES (?, ?) ON CONFLICT(kategoria) DO UPDATE SET limit_kwota=?", (category, limit, limit))
conn.commit()
conn.close()
def delete_budget_db(category):
conn = sqlite3.connect(DB_FILE)
conn.execute("DELETE FROM budzety WHERE kategoria = ?", (category,))
conn.commit()
conn.close()
def add_goal(nazwa, cel, termin, ikona="🎯"):
conn = sqlite3.connect(DB_FILE)
conn.execute("INSERT INTO cele (nazwa, kwota_cel, kwota_obecna, termin, ikona) VALUES (?, ?, ?, ?, ?)", (nazwa, cel, 0.0, termin, ikona))
conn.commit()
conn.close()
def get_goals():
conn = sqlite3.connect(DB_FILE)
df = pd.read_sql_query("SELECT * FROM cele", conn)
conn.close()
if not df.empty:
df['termin'] = pd.to_datetime(df['termin'], errors='coerce').dt.date
return df
def update_goal_amount(goal_id, amount_to_add):
conn = sqlite3.connect(DB_FILE)
conn.execute("UPDATE cele SET kwota_obecna = kwota_obecna + ? WHERE id = ?", (amount_to_add, goal_id))
conn.commit()
conn.close()
def delete_goal(goal_id):
conn = sqlite3.connect(DB_FILE)
conn.execute("DELETE FROM cele WHERE id = ?", (goal_id,))
conn.commit()
conn.close()
def load_data():
conn = sqlite3.connect(DB_FILE)
df = pd.read_sql_query("SELECT * FROM wydatki", conn)
conn.close()
if not df.empty:
df['termin'] = pd.to_datetime(df['termin']).dt.date
df['cykliczne'] = df['cykliczne'].astype(bool)
df['zaplacone'] = df['zaplacone'].astype(bool)
if 'interwal_ilosc' in df.columns:
df['interwal_ilosc'] = pd.to_numeric(df['interwal_ilosc'], errors='coerce')
df['interwal_ilosc'] = df['interwal_ilosc'].fillna(1).astype(int)
if 'interwal_typ' in df.columns:
df['interwal_typ'] = df['interwal_typ'].fillna('miesiące').astype(str)
return df
def add_expense(nazwa, kategoria, kwota, termin, cykliczne, interwal_ilosc=1, interwal_typ='miesiące'):
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
safe_qty = int(interwal_ilosc)
safe_type = str(interwal_typ)
c.execute('''INSERT INTO wydatki (nazwa, kategoria, kwota, termin, cykliczne, zaplacone, interwal_ilosc, interwal_typ)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
(nazwa, kategoria, kwota, termin, cykliczne, False, safe_qty, safe_type))
conn.commit()
conn.close()
def update_status(expense_id, is_paid):
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute("UPDATE wydatki SET zaplacone = ? WHERE id = ?", (is_paid, expense_id))
conn.commit()
conn.close()
def update_expense_details(expense_id, nazwa, kategoria, kwota, termin):
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute("UPDATE wydatki SET nazwa=?, kategoria=?, kwota=?, termin=? WHERE id=?",
(nazwa, kategoria, kwota, termin, expense_id))
conn.commit()
conn.close()
def delete_expense(expense_id):
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
c.execute("DELETE FROM wydatki WHERE id = ?", (expense_id,))
conn.commit()
conn.close()
init_db()
# --- CALLBACKS ---
if "list_date_picker" not in st.session_state:
st.session_state.list_date_picker = date.today()
if "cal_date_picker" not in st.session_state:
st.session_state.cal_date_picker = date.today()
def set_today():
st.session_state.list_date_picker = date.today()
st.session_state.cal_date_picker = date.today()
# --- DIALOGI ---
@st.dialog("Zmień status płatności")
def confirm_status_change(row):
st.write(f"Pozycja: **{row['nazwa']}**")
st.write(f"Kwota: **{row['kwota']:.2f} PLN**")
current_status = row['zaplacone']
new_status = not current_status
action_text = "Oznaczyć jako ZAPŁACONE? 🟢" if not current_status else "Cofnąć do NIEZAPŁACONE? 🔴"
st.markdown(f"### {action_text}")
create_next = False
next_date = None
if not current_status and row['cykliczne']:
try:
i_ilosc = int(row.get('interwal_ilosc', 1))
except:
i_ilosc = 1
i_typ = str(row.get('interwal_typ', 'miesiące'))
st.info(f"🔄 To płatność cykliczna: co {i_ilosc} {i_typ}.")
create_next = st.checkbox("Utwórz automatycznie następną płatność?", value=True)
if create_next:
current_date_dt = pd.to_datetime(row['termin'])
offset = pd.DateOffset(months=1)
if i_typ == 'dni': offset = pd.DateOffset(days=i_ilosc)
elif i_typ == 'tygodnie': offset = pd.DateOffset(weeks=i_ilosc)
elif i_typ == 'miesiące': offset = pd.DateOffset(months=i_ilosc)
elif i_typ == 'lata': offset = pd.DateOffset(years=i_ilosc)
next_date_dt = current_date_dt + offset
next_date = next_date_dt.date()
st.write(f"📅 Data nowej płatności: **{next_date}**")
st.divider()
c1, c2 = st.columns(2)
if c1.button("Nie, anuluj"):
st.rerun()
if c2.button("Tak, zatwierdź", type="primary"):
update_status(int(row['id']), new_status)
if create_next and new_status == True and next_date:
add_expense(row['nazwa'], row['kategoria'], row['kwota'], next_date, True, i_ilosc, i_typ)
st.toast(f"Zaplanowano następną płatność na {next_date}", icon="🚀")
st.rerun()
@st.dialog("✏️ Edytuj płatność")
def edit_expense_dialog(row, categories_list):
with st.form(f"edit_form_{row['id']}"):
e_name = st.text_input("Nazwa", value=row['nazwa'])
cat_index = categories_list.index(row['kategoria']) if row['kategoria'] in categories_list else 0
e_cat = st.selectbox("Kategoria", categories_list, index=cat_index)
e_amt = st.number_input("Kwota", value=float(row['kwota']), min_value=0.0, step=10.0)
try: e_date_val = pd.to_datetime(row['termin']).date()
except: e_date_val = date.today()
e_due = st.date_input("Termin", value=e_date_val)
if st.form_submit_button("Potwierdź zmiany"):
update_expense_details(int(row['id']), e_name, e_cat, e_amt, e_due)
st.success("Zapisano!")
st.rerun()
@st.dialog("🗑️ Potwierdź usunięcie")
def delete_expense_dialog(row):
st.warning(f"Czy na pewno chcesz usunąć płatność: **{row['nazwa']}** na kwotę **{row['kwota']} PLN**?")
c1, c2 = st.columns(2)
if c1.button("Nie, anuluj"):
st.rerun()
if c2.button("Tak, usuń", type="primary"):
delete_expense(int(row['id']))
st.success("Usunięto!")
st.rerun()
@st.dialog("Usuń budżet")
def confirm_delete_budget(category):
st.warning(f"Czy na pewno chcesz usunąć limit budżetowy dla kategorii: **{category}**?")
st.caption("Kategoria oraz przypisane do niej wydatki NIE zostaną usunięte. Usunięty zostanie tylko limit.")
col_no, col_yes = st.columns(2)
with col_no:
if st.button("Anuluj"): st.rerun()
with col_yes:
if st.button("Tak, usuń", type="primary"):
delete_budget_db(category)
st.rerun()
# --- LOGIKA UI ---
categories_list = get_categories()
with st.sidebar:
st.title("🎛️ Nawigacja")
page = st.radio("Widok", ["Lista & Budżet", "Kalendarz", "Cele (Skarbonka)", "Ustawienia"])
st.divider()
if page != "Cele (Skarbonka)":
st.header(" Dodaj wydatek")
is_cyclic_mode = st.toggle("Tryb cykliczny", value=False)
with st.form("add_form", clear_on_submit=True):
name = st.text_input("Nazwa")
cat = st.selectbox("Kategoria", categories_list)
amt = st.number_input("Kwota", min_value=0.0, step=10.0)
due = st.date_input("Termin", value=date.today())
int_qty, int_type = 1, "miesiące"
if is_cyclic_mode:
st.markdown("---")
col_i1, col_i2 = st.columns(2)
with col_i1: int_qty = st.number_input("Co ile?", min_value=1, value=1)
with col_i2: int_type = st.selectbox("Jednostka", ["dni", "tygodnie", "miesiące", "lata"], index=2)
if st.form_submit_button("Zapisz wydatek"):
add_expense(name, cat, amt, due, is_cyclic_mode, int_qty, int_type)
st.success("Dodano!")
st.rerun()
else: st.info("Formularz celów znajduje się na stronie głównej.")
df = load_data()
today = date.today()
# --- WIDOK 1: LISTA I BUDŻET ---
if page == "Lista & Budżet":
st.title("📊 Finanse & Budżet")
if not df.empty:
col_btn, col_date, col_search = st.columns([1, 2, 3])
with col_btn: st.button("📅 Dzisiaj", on_click=set_today)
with col_date: view_date = st.date_input("Pokaż miesiąc:", key="list_date_picker", format="DD.MM.YYYY")
with col_search: search_query = st.text_input("🔍 Szukaj płatności (nazwa, kategoria)...", "")
start_month = view_date.replace(day=1)
_, last_day = calendar.monthrange(view_date.year, view_date.month)
end_month = view_date.replace(day=last_day)
mask_month_strict = (df['termin'] >= start_month) & (df['termin'] <= end_month)
mask_overdue_global = (df['termin'] < today) & (~df['zaplacone'])
final_mask = mask_month_strict | mask_overdue_global
df_filtered = df[final_mask].copy()
# Wyszukiwanie
if search_query:
df_filtered = df_filtered[
df_filtered['nazwa'].str.contains(search_query, case=False, na=False) |
df_filtered['kategoria'].str.contains(search_query, case=False, na=False)
]
# KPI
mask_unpaid_view = ~df_filtered['zaplacone']
mask_overdue_view = mask_unpaid_view & (df_filtered['termin'] < today)
c1, c2 = st.columns(2)
c1.metric("Do zapłacenia (Widok)", f"{df_filtered[mask_unpaid_view]['kwota'].sum():.2f} PLN")
overdue_cnt = len(df_filtered[mask_overdue_view])
c2.metric("Przeterminowane (Pilne)", f"{df_filtered[mask_overdue_view]['kwota'].sum():.2f} PLN",
delta=f"-{overdue_cnt} szt." if overdue_cnt else "Ok", delta_color="normal")
st.divider()
with st.expander("📉 Realizacja Budżetów (Ten miesiąc)", expanded=True):
budgets = get_budgets()
if budgets:
df_month_only = df[mask_month_strict]
spending_by_cat = df_month_only.groupby('kategoria')['kwota'].sum()
for cat_name, limit in budgets.items():
spent = spending_by_cat.get(cat_name, 0.0)
diff = spent - limit
if limit > 0: percent = spent / limit
else: percent = 1.0 if spent > 0 else 0.0
if diff > 0:
st.markdown(f"**{cat_name}**: {spent:.2f} / {limit:.2f} PLN :red[**⚠️ Przekroczono o {diff:.2f} PLN!**]")
st.progress(1.0)
else:
st.write(f"**{cat_name}**: {spent:.2f} / {limit:.2f} PLN ({percent*100:.0f}%)")
st.progress(min(percent, 1.0))
else:
st.info("Brak budżetów. Ustaw je w zakładce Ustawienia.")
# --- ZAKŁADKI: Tabela vs Podsumowanie ---
tab1, tab2 = st.tabs(["📋 Lista Płatności", "📈 Zestawienie Kategorii"])
with tab1:
termin_as_dt = pd.to_datetime(df_filtered['termin'])
today_as_dt = pd.to_datetime(today)
df_filtered['dni_opoznienia'] = (today_as_dt - termin_as_dt).dt.days
df_filtered.loc[df_filtered['zaplacone'] | (df_filtered['dni_opoznienia'] <= 0), 'dni_opoznienia'] = None
df_view = df_filtered.sort_values(by=['zaplacone', 'termin'])
st.caption("👈 Kliknij w kwadracik po lewej stronie wiersza, aby go zaznaczyć i wywołać opcje edycji/usuwania.")
event = st.dataframe(
df_view,
on_select="rerun",
selection_mode="single-row",
column_config={
"id": None, "interwal_ilosc": None, "interwal_typ": None,
"kategoria": "Kategoria",
"kwota": st.column_config.NumberColumn(format="%.2f PLN"),
"termin": st.column_config.DateColumn(format="YYYY-MM-DD"),
"zaplacone": st.column_config.CheckboxColumn(label="Zapłacone?"),
"dni_opoznienia": st.column_config.NumberColumn(label="Spóźnienie", format="%d dni"),
},
use_container_width=True,
hide_index=True
)
# Obsługa zaznaczonego wiersza
selected_rows = event.selection.rows
if selected_rows:
selected_idx = selected_rows[0]
selected_row = df_view.iloc[selected_idx]
st.markdown(f"Wybrana pozycja: **{selected_row['nazwa']}**")
col_ed, col_del, col_status, _ = st.columns([1, 1, 2, 3])
if col_ed.button("✏️ Edytuj", key="btn_edit"):
edit_expense_dialog(selected_row, categories_list)
if col_del.button("🗑️ Usuń", key="btn_delete"):
delete_expense_dialog(selected_row)
status_label = "⭕ Oznacz jako NIEZAPŁACONE" if selected_row['zaplacone'] else "✅ Oznacz jako ZAPŁACONE"
if col_status.button(status_label, key="btn_status", type="primary"):
confirm_status_change(selected_row)
with tab2:
st.subheader("Bieżący miesiąc - Zobowiązania wg kategorii")
summary_data = []
for cat in df_filtered['kategoria'].unique():
cat_df = df_filtered[df_filtered['kategoria'] == cat]
do_zaplaty = cat_df[~cat_df['zaplacone']]['kwota'].sum()
przeterm = cat_df[(~cat_df['zaplacone']) & (cat_df['termin'] < today)]['kwota'].sum()
if do_zaplaty > 0 or przeterm > 0:
summary_data.append({
"Kategoria": cat,
"Do zapłacenia (PLN)": float(do_zaplaty),
"Przeterminowane (PLN)": float(przeterm)
})
if summary_data:
sum_df = pd.DataFrame(summary_data)
st.dataframe(
sum_df,
use_container_width=True,
hide_index=True,
column_config={
"Do zapłacenia (PLN)": st.column_config.NumberColumn(format="%.2f"),
"Przeterminowane (PLN)": st.column_config.NumberColumn(format="%.2f")
}
)
else:
st.success("Wszystko opłacone w tym widoku! 🎉")
else: st.info("Brak wpisów.")
# --- WIDOK 2: KALENDARZ ---
elif page == "Kalendarz":
st.title("📅 Kalendarz")
col_btn, col_date, _ = st.columns([1, 2, 3])
with col_btn: st.button("📅 Dzisiaj", on_click=set_today, key="cal_today_btn")
with col_date:
selected_date = st.date_input("Miesiąc", key="cal_date_picker")
year, month = selected_date.year, selected_date.month
cal = calendar.Calendar(firstweekday=0)
month_days = cal.monthdayscalendar(year, month)
cols = st.columns(7)
for i, d in enumerate(["Pn", "Wt", "Śr", "Cz", "Pt", "Sb", "Nd"]):
cols[i].markdown(f"<div style='text-align:center'><b>{d}</b></div>", unsafe_allow_html=True)
if not df.empty:
df['year'] = pd.to_datetime(df['termin']).dt.year
df['month'] = pd.to_datetime(df['termin']).dt.month
df['day'] = pd.to_datetime(df['termin']).dt.day
monthly_data = df[(df['year'] == year) & (df['month'] == month)]
else:
monthly_data = pd.DataFrame(columns=['day', 'kwota', 'nazwa', 'zaplacone', 'id', 'termin'])
for week in month_days:
cols = st.columns(7)
for i, day_num in enumerate(week):
with cols[i]:
if day_num == 0: continue
is_today = (date(year, month, day_num) == today)
day_header = f":red[**{day_num}**]" if is_today else f"**{day_num}**"
with st.container(height=140, border=True):
st.markdown(day_header)
if not monthly_data.empty:
day_items = monthly_data[monthly_data['day'] == day_num]
for _, row in day_items.iterrows():
kwota = f"{row['kwota']:.0f}"
if row['zaplacone']: label, btn_type = f"{kwota} | {row['nazwa']}", "secondary"
elif row['termin'] < today: label, btn_type = f"⚠️ {kwota} | {row['nazwa']}", "primary"
else: label, btn_type = f"{kwota} | {row['nazwa']}", "secondary"
if st.button(label, key=f"btn_{row['id']}", type=btn_type, use_container_width=True):
confirm_status_change(row)
# --- WIDOK 3: CELE (SKARBONKA) ---
elif page == "Cele (Skarbonka)":
st.title("🎯 Moje Cele Finansowe")
with st.expander(" Dodaj nowy cel", expanded=False):
has_deadline = st.toggle("Ustal termin realizacji?", value=True)
with st.form("new_goal_form"):
c1, c2, c3 = st.columns([2, 1, 1])
with c1: g_name = st.text_input("Nazwa celu (np. Wakacje)")
with c2: g_target = st.number_input("Kwota docelowa", min_value=1.0, step=100.0)
with c3: g_icon = st.selectbox("Ikona", ["🎯", "🚗", "🏠", "💻", "🌴", "💍", "💰", "🆘"])
g_date = None
if has_deadline: g_date = st.date_input("Planowana data", value=date.today())
if st.form_submit_button("Utwórz cel"):
add_goal(g_name, g_target, g_date, g_icon)
st.success("Dodano cel!")
st.rerun()
st.divider()
goals_df = get_goals()
if not goals_df.empty:
grid = st.columns(2)
for idx, row in goals_df.iterrows():
with grid[idx % 2]:
with st.container(border=True):
col_head1, col_head2 = st.columns([4, 1])
with col_head1: st.subheader(f"{row['ikona']} {row['nazwa']}")
with col_head2:
if st.button("🗑️", key=f"del_goal_{row['id']}", help="Usuń ten cel"):
delete_goal(row['id'])
st.rerun()
progress = row['kwota_obecna'] / row['kwota_cel'] if row['kwota_cel'] > 0 else 0
st.progress(min(progress, 1.0))
st.write(f"Uzbierano: **{row['kwota_obecna']:.2f}** / {row['kwota_cel']:.2f} PLN ({progress*100:.1f}%)")
missing = row['kwota_cel'] - row['kwota_obecna']
if missing <= 0:
st.balloons()
st.success("🎉 Cel osiągnięty!")
else:
st.caption(f"Brakuje: {missing:.2f} PLN")
if pd.notnull(row['termin']):
days_left = (row['termin'] - today).days
if days_left > 0: st.caption(f"📅 Czas do końca: {days_left} dni")
else: st.caption(f"📅 Termin minął!")
else: st.caption("📅 Termin: Bezterminowo")
st.divider()
c_in, c_btn = st.columns([2, 1])
with c_in: amount = st.number_input("Kwota", min_value=0.0, step=50.0, key=f"pay_{row['id']}", label_visibility="collapsed")
with c_btn:
if st.button("Wpłać", key=f"deposit_{row['id']}", type="primary"):
update_goal_amount(row['id'], amount)
st.rerun()
if st.button("Wypłać", key=f"withdraw_{row['id']}"):
update_goal_amount(row['id'], -amount)
st.rerun()
else: st.info("Nie masz jeszcze żadnych celów. Dodaj pierwszy powyżej!")
# --- WIDOK 4: USTAWIENIA ---
elif page == "Ustawienia":
st.title("⚙️ Konfiguracja")
col1, col2 = st.columns(2)
with col1:
st.subheader("Kategorie")
new_cat = st.text_input("Nowa kategoria")
if st.button("Dodaj kategorię"):
if new_cat and add_category_db(new_cat):
st.success(f"Dodano: {new_cat}")
st.rerun()
else: st.error("Błąd lub duplikat.")
st.divider()
cat_to_del = st.selectbox("Wybierz do usunięcia", categories_list)
if st.button("Usuń", type="primary"):
delete_category_db(cat_to_del)
st.rerun()
with col2:
st.subheader("Budżety")
current_budgets = get_budgets()
st.caption("Wybierz kategorię, aby ustawić limit:")
selected_cat_budget = st.selectbox("Kategoria", categories_list, key="budget_cat_sel")
current_val = current_budgets.get(selected_cat_budget, 0.0)
with st.form("budget_form"):
new_limit = st.number_input("Limit miesięczny (PLN)", value=current_val, step=100.0, key=f"bud_{selected_cat_budget}")
if st.form_submit_button("Zapisz budżet"):
set_budget_db(selected_cat_budget, new_limit)
st.rerun()
st.divider()
st.markdown("#### Aktualne limity:")
if current_budgets:
for c, l in current_budgets.items():
bc1, bc2 = st.columns([3, 1])
with bc1: st.write(f"**{c}**: {l:.2f} PLN")
with bc2:
if st.button("🗑️", key=f"del_bud_{c}", help="Usuń limit"):
confirm_delete_budget(c)
else:
st.info("Brak zdefiniowanych budżetów.")