Dużo poprawek wszystko powinno być ok
This commit is contained in:
205
app.py
205
app.py
@@ -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)
|
||||
|
||||
BIN
data/finanse.db
BIN
data/finanse.db
Binary file not shown.
Reference in New Issue
Block a user