Dużo poprawek wszystko powinno być ok
This commit is contained in:
177
app.py
177
app.py
@@ -213,6 +213,14 @@ def update_status(expense_id, is_paid):
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
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):
|
def delete_expense(expense_id):
|
||||||
conn = sqlite3.connect(DB_FILE)
|
conn = sqlite3.connect(DB_FILE)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
@@ -223,11 +231,14 @@ def delete_expense(expense_id):
|
|||||||
init_db()
|
init_db()
|
||||||
|
|
||||||
# --- CALLBACKS ---
|
# --- CALLBACKS ---
|
||||||
if "view_date_picker" not in st.session_state:
|
if "list_date_picker" not in st.session_state:
|
||||||
st.session_state.view_date_picker = date.today()
|
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():
|
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 ---
|
# --- DIALOGI ---
|
||||||
@st.dialog("Zmień status płatności")
|
@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.write(f"📅 Data nowej płatności: **{next_date}**")
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
if st.button("Zatwierdź zmianę", type="primary"):
|
c1, c2 = st.columns(2)
|
||||||
update_status(row['id'], new_status)
|
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:
|
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)
|
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.toast(f"Zaplanowano następną płatność na {next_date}", icon="🚀")
|
||||||
st.rerun()
|
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")
|
@st.dialog("Usuń budżet")
|
||||||
def confirm_delete_budget(category):
|
def confirm_delete_budget(category):
|
||||||
st.warning(f"Czy na pewno chcesz usunąć limit budżetowy dla kategorii: **{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")
|
st.title("📊 Finanse & Budżet")
|
||||||
|
|
||||||
if not df.empty:
|
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_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)
|
start_month = view_date.replace(day=1)
|
||||||
_, last_day = calendar.monthrange(view_date.year, view_date.month)
|
_, 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()
|
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
|
# KPI
|
||||||
mask_unpaid_view = ~df_filtered['zaplacone']
|
mask_unpaid_view = ~df_filtered['zaplacone']
|
||||||
mask_overdue_view = mask_unpaid_view & (df_filtered['termin'] < today)
|
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()
|
spending_by_cat = df_month_only.groupby('kategoria')['kwota'].sum()
|
||||||
for cat_name, limit in budgets.items():
|
for cat_name, limit in budgets.items():
|
||||||
spent = spending_by_cat.get(cat_name, 0.0)
|
spent = spending_by_cat.get(cat_name, 0.0)
|
||||||
|
|
||||||
# --- NOWA LOGIKA WYŚWIETLANIA BUDŻETU ---
|
|
||||||
diff = spent - limit
|
diff = spent - limit
|
||||||
|
|
||||||
# Jeśli budżet jest 0 lub ujemny (błąd), traktujemy jako 100% przekroczenia
|
if limit > 0: percent = spent / limit
|
||||||
if limit > 0:
|
else: percent = 1.0 if spent > 0 else 0.0
|
||||||
percent = spent / limit
|
|
||||||
else:
|
|
||||||
percent = 1.0 if spent > 0 else 0.0
|
|
||||||
|
|
||||||
if diff > 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.markdown(f"**{cat_name}**: {spent:.2f} / {limit:.2f} PLN :red[**⚠️ Przekroczono o {diff:.2f} PLN!**]")
|
||||||
st.progress(1.0)
|
st.progress(1.0)
|
||||||
else:
|
else:
|
||||||
# W NORMIE
|
|
||||||
st.write(f"**{cat_name}**: {spent:.2f} / {limit:.2f} PLN ({percent*100:.0f}%)")
|
st.write(f"**{cat_name}**: {spent:.2f} / {limit:.2f} PLN ({percent*100:.0f}%)")
|
||||||
st.progress(min(percent, 1.0))
|
st.progress(min(percent, 1.0))
|
||||||
else:
|
else:
|
||||||
st.info("Brak budżetów. Ustaw je w zakładce Ustawienia.")
|
st.info("Brak budżetów. Ustaw je w zakładce Ustawienia.")
|
||||||
|
|
||||||
# Tabela
|
# --- 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'])
|
termin_as_dt = pd.to_datetime(df_filtered['termin'])
|
||||||
today_as_dt = pd.to_datetime(today)
|
today_as_dt = pd.to_datetime(today)
|
||||||
df_filtered['dni_opoznienia'] = (today_as_dt - termin_as_dt).dt.days
|
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_filtered.loc[df_filtered['zaplacone'] | (df_filtered['dni_opoznienia'] <= 0), 'dni_opoznienia'] = None
|
||||||
|
|
||||||
df_view = df_filtered.sort_values(by=['zaplacone', 'termin'])
|
df_view = df_filtered.sort_values(by=['zaplacone', 'termin'])
|
||||||
editor_key = f"main_editor_{view_date.strftime('%Y%m')}"
|
|
||||||
|
|
||||||
edited_df = st.data_editor(
|
st.caption("👈 Kliknij w kwadracik po lewej stronie wiersza, aby go zaznaczyć i wywołać opcje edycji/usuwania.")
|
||||||
|
|
||||||
|
event = st.dataframe(
|
||||||
df_view,
|
df_view,
|
||||||
|
on_select="rerun",
|
||||||
|
selection_mode="single-row",
|
||||||
column_config={
|
column_config={
|
||||||
"id": None, "interwal_ilosc": None, "interwal_typ": None,
|
"id": None, "interwal_ilosc": None, "interwal_typ": None,
|
||||||
|
"kategoria": "Kategoria",
|
||||||
"kwota": st.column_config.NumberColumn(format="%.2f PLN"),
|
"kwota": st.column_config.NumberColumn(format="%.2f PLN"),
|
||||||
"termin": st.column_config.DateColumn(format="YYYY-MM-DD"),
|
"termin": st.column_config.DateColumn(format="YYYY-MM-DD"),
|
||||||
"zaplacone": st.column_config.CheckboxColumn(label="Zapłacone?"),
|
"zaplacone": st.column_config.CheckboxColumn(label="Zapłacone?"),
|
||||||
"dni_opoznienia": st.column_config.NumberColumn(label="Spóźnienie", format="%d dni"),
|
"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"],
|
use_container_width=True,
|
||||||
hide_index=True, use_container_width=True, key=editor_key
|
hide_index=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sync
|
# Obsługa zaznaczonego wiersza
|
||||||
old_status = dict(zip(df_filtered['id'], df_filtered['zaplacone']))
|
selected_rows = event.selection.rows
|
||||||
new_status = dict(zip(edited_df['id'], edited_df['zaplacone']))
|
if selected_rows:
|
||||||
changes_made = False
|
selected_idx = selected_rows[0]
|
||||||
for eid, paid in new_status.items():
|
selected_row = df_view.iloc[selected_idx]
|
||||||
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'))
|
|
||||||
|
|
||||||
current_date_dt = pd.to_datetime(row['termin'])
|
st.markdown(f"Wybrana pozycja: **{selected_row['nazwa']}**")
|
||||||
if i_typ == 'dni': offset = pd.DateOffset(days=i_ilosc)
|
|
||||||
elif i_typ == 'tygodnie': offset = pd.DateOffset(weeks=i_ilosc)
|
col_ed, col_del, col_status, _ = st.columns([1, 1, 2, 3])
|
||||||
elif i_typ == 'miesiące': offset = pd.DateOffset(months=i_ilosc)
|
|
||||||
elif i_typ == 'lata': offset = pd.DateOffset(years=i_ilosc)
|
if col_ed.button("✏️ Edytuj", key="btn_edit"):
|
||||||
else: offset = pd.DateOffset(months=1)
|
edit_expense_dialog(selected_row, categories_list)
|
||||||
next_date = (current_date_dt + offset).date()
|
|
||||||
add_expense(row['nazwa'], row['kategoria'], row['kwota'], next_date, True, i_ilosc, i_typ)
|
if col_del.button("🗑️ Usuń", key="btn_delete"):
|
||||||
st.toast(f"Automatycznie dodano kolejną płatność: {next_date}")
|
delete_expense_dialog(selected_row)
|
||||||
if changes_made: st.rerun()
|
|
||||||
|
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.")
|
else: st.info("Brak wpisów.")
|
||||||
|
|
||||||
# --- WIDOK 2: KALENDARZ ---
|
# --- WIDOK 2: KALENDARZ ---
|
||||||
@@ -435,7 +510,7 @@ elif page == "Kalendarz":
|
|||||||
col_btn, col_date, _ = st.columns([1, 2, 3])
|
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_btn: st.button("📅 Dzisiaj", on_click=set_today, key="cal_today_btn")
|
||||||
with col_date:
|
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
|
year, month = selected_date.year, selected_date.month
|
||||||
|
|
||||||
cal = calendar.Calendar(firstweekday=0)
|
cal = calendar.Calendar(firstweekday=0)
|
||||||
|
|||||||
BIN
data/finanse.db
BIN
data/finanse.db
Binary file not shown.
Reference in New Issue
Block a user