Dużo poprawek wszystko powinno być ok

This commit is contained in:
2026-02-23 16:31:03 +01:00
parent e793d81cae
commit 838c4be94e
2 changed files with 140 additions and 65 deletions

205
app.py
View File

@@ -213,6 +213,14 @@ def update_status(expense_id, is_paid):
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()
@@ -223,11 +231,14 @@ def delete_expense(expense_id):
init_db()
# --- CALLBACKS ---
if "view_date_picker" not in st.session_state:
st.session_state.view_date_picker = date.today()
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.view_date_picker = date.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")
@@ -264,13 +275,50 @@ def confirm_status_change(row):
st.write(f"📅 Data nowej płatności: **{next_date}**")
st.divider()
if st.button("Zatwierdź zmianę", type="primary"):
update_status(row['id'], new_status)
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}**?")
@@ -320,9 +368,10 @@ if page == "Lista & Budżet":
st.title("📊 Finanse & Budżet")
if not df.empty:
col_btn, col_date, _ = st.columns([1, 2, 3])
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="view_date_picker", format="DD.MM.YYYY")
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)
@@ -334,6 +383,13 @@ if page == "Lista & Budżet":
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)
@@ -353,80 +409,99 @@ if page == "Lista & Budżet":
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)
# --- NOWA LOGIKA WYŚWIETLANIA BUDŻETU ---
diff = spent - limit
# Jeśli budżet jest 0 lub ujemny (błąd), traktujemy jako 100% przekroczenia
if limit > 0:
percent = spent / limit
else:
percent = 1.0 if spent > 0 else 0.0
if limit > 0: percent = spent / limit
else: percent = 1.0 if spent > 0 else 0.0
if diff > 0:
# PRZEKROCZENIE - CZERWONY TEKST I PEŁNY PASEK
st.markdown(f"**{cat_name}**: {spent:.2f} / {limit:.2f} PLN :red[**⚠️ Przekroczono o {diff:.2f} PLN!**]")
st.progress(1.0)
else:
# W NORMIE
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.")
# Tabela
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
# --- ZAKŁADKI: Tabela vs Podsumowanie ---
tab1, tab2 = st.tabs(["📋 Lista Płatności", "📈 Zestawienie Kategorii"])
df_view = df_filtered.sort_values(by=['zaplacone', 'termin'])
editor_key = f"main_editor_{view_date.strftime('%Y%m')}"
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
edited_df = st.data_editor(
df_view,
column_config={
"id": None, "interwal_ilosc": None, "interwal_typ": None,
"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"),
},
disabled=["id", "nazwa", "kategoria", "kwota", "termin", "cykliczne", "dni_opoznienia", "interwal_ilosc", "interwal_typ"],
hide_index=True, use_container_width=True, key=editor_key
)
df_view = df_filtered.sort_values(by=['zaplacone', 'termin'])
# Sync
old_status = dict(zip(df_filtered['id'], df_filtered['zaplacone']))
new_status = dict(zip(edited_df['id'], edited_df['zaplacone']))
changes_made = False
for eid, paid in new_status.items():
if old_status.get(eid) != paid:
update_status(eid, paid)
changes_made = True
row = df_filtered[df_filtered['id'] == eid].iloc[0]
if paid 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.caption("👈 Kliknij w kwadracik po lewej stronie wiersza, aby go zaznaczyć i wywołać opcje edycji/usuwania.")
current_date_dt = pd.to_datetime(row['termin'])
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)
else: offset = pd.DateOffset(months=1)
next_date = (current_date_dt + offset).date()
add_expense(row['nazwa'], row['kategoria'], row['kwota'], next_date, True, i_ilosc, i_typ)
st.toast(f"Automatycznie dodano kolejną płatność: {next_date}")
if changes_made: st.rerun()
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! 🎉")
with st.expander("🗑️ Usuwanie"):
options = {f"{row['nazwa']} ({row['kwota']} zł)": row['id'] for _, row in df_filtered.iterrows()}
selected = st.multiselect("Wybierz pozycje:", list(options.keys()))
if st.button("Usuń zaznaczone") and selected:
for label in selected: delete_expense(options[label])
st.rerun()
else: st.info("Brak wpisów.")
# --- WIDOK 2: KALENDARZ ---
@@ -435,7 +510,7 @@ elif page == "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="view_date_picker")
selected_date = st.date_input("Miesiąc", key="cal_date_picker")
year, month = selected_date.year, selected_date.month
cal = calendar.Calendar(firstweekday=0)

Binary file not shown.