from datetime import date
import json
import streamlit as st
st.set_page_config(page_title="Mon App Budget CHF/EUR", page_icon="💰")
st.title("💸 Gestion de Budget (CHF & EUR)")
# --- SAUVEGARDE COMPATIBLE ---
def get_saved_data(key, default_value):
params = st.experimental_get_query_params()
if key in params:
try:
return json.loads(params[key][0])
except:
pass
return default_value
def save_data(key, data):
st.experimental_set_query_params(**{key: json.dumps(data, default=str)})
# --- CONVERTISSEUR DE DEVISE & TAUX ---
with st.expander("💱 Convertisseur de devises & Taux de change"):
taux_chf_eur = st.number_input(
"Taux de change (1 CHF en EUR)", value=1.065, step=0.005, format="%.3f"
)
c_conv1, c_conv2 = st.columns(2)
with c_conv1:
m_chf = st.number_input(
"Convertir CHF en EUR", min_value=0.0, value=100.0, step=10.0
)
st.write(f"**{m_chf} CHF** = *{m_chf * taux_chf_eur:.2f} €*")
with c_conv2:
m_eur = st.number_input(
"Convertir EUR en CHF", min_value=0.0, value=100.0, step=10.0
)
st.write(f"**{m_eur} €** = *{m_eur / taux_chf_eur:.2f} CHF*")
st.markdown("---")
# --- CHARGEMENT ---
default_revenus = [{"id": 1, "source": "Salaire Suisse", "montant": 4000.0, "devise": "CHF"}]
default_budgets = [{"id": 1, "nom": "Courses", "plafond": 1200.0, "devise": "CHF"}]
default_dep_var = [{"id": 1, "id_budget": 1, "nom": "Migros", "montant": 500.0, "devise": "CHF"}]
default_dep_fixes = [
{
"id": 1,
"nom": "Loyer",
"montant": 1200.0,
"devise": "CHF",
"date": "2026-10-01",
"paiement": "Virement",
"paye": True,
}
]
if "revenus" not in st.session_state:
st.session_state.revenus = get_saved_data("rev", default_revenus)
if "budgets_variables" not in st.session_state:
st.session_state.budgets_variables = get_saved_data("bv", default_budgets)
if "depenses_variables" not in st.session_state:
st.session_state.depenses_variables = get_saved_data("dv", default_dep_var)
if "depenses_fixes" not in st.session_state:
st.session_state.depenses_fixes = get_saved_data("df", default_dep_fixes)
def actualiser_sauvegarde():
# Pour éviter les conflits d'URL multiples avec experimental_set_query_params,
# on sauvegarde l'ensemble dans un seul paquet global 'save'
tout_stocker = {
"rev": st.session_state.revenus,
"bv": st.session_state.budgets_variables,
"dv": st.session_state.depenses_variables,
"df": st.session_state.depenses_fixes,
}
st.experimental_set_query_params(data=json.dumps(tout_stocker, default=str))
# Chargement global si présent
if "data" in st.experimental_get_query_params():
try:
chargement = json.loads(
st.experimental_get_query_params()["data"][0]
)
st.session_state.revenus = chargement.get("rev", default_revenus)
st.session_state.budgets_variables = chargement.get("bv", default_budgets)
st.session_state.depenses_variables = chargement.get("dv", default_dep_var)
st.session_state.depenses_fixes = chargement.get("df", default_dep_fixes)
except:
pass
# --- 1. REVENUS ---
st.subheader("1️⃣ Mes Revenus")
with st.form("form_rev", clear_on_submit=True):
cr1, cr2, cr3 = st.columns([2, 1, 1])
with cr1:
src = st.text_input("Source")
with cr2:
val = st.number_input("Montant", min_value=0.0, step=10.0, format="%.2f")
with cr3:
dev = st.selectbox("Devise", ["CHF", "EUR"], key="dev_rev")
if st.form_submit_button("Ajouter revenu") and src and val > 0:
new_id = max([r["id"] for r in st.session_state.revenus], default=0) + 1
st.session_state.revenus.append(
{"id": new_id, "source": src, "montant": val, "devise": dev}
)
actualiser_sauvegarde()
st.rerun()
total_rev_eur = 0
for r in st.session_state.revenus:
m_eur = r["montant"] if r["devise"] == "EUR" else r["montant"] * taux_chf_eur
total_rev_eur += m_eur
rc1, rc2, rc3 = st.columns([3, 2, 1])
rc1.write(f"💼 **{r['source']}** ({r['montant']} {r['devise']})")
rc2.caption(f"≈ {m_eur:.2f} €")
if rc3.button("🗑️", key=f"del_r_{r['id']}"):
st.session_state.revenus = [
x for x in st.session_state.revenus if x["id"] != r["id"]
]
actualiser_sauvegarde()
st.rerun()
st.info(f"**Total Revenus :** {total_rev_eur:,.2f} €")
st.markdown("---")
# --- 2. BUDGETS VARIABLES ---
st.subheader("2️⃣ Suivi de Budget Variable (ex: Courses)")
with st.form("form_env", clear_on_submit=True):
ce1, ce2, ce3 = st.columns([2, 1, 1])
with ce1:
nom_env = st.text_input("Nom du budget (ex: Courses)")
with ce2:
plafond_env = st.number_input("Plafond max", min_value=0.0, step=50.0)
with ce3:
dev_env = st.selectbox("Devise", ["CHF", "EUR"], key="dev_env")
if st.form_submit_button("Créer l'enveloppe") and nom_env and plafond_env > 0:
nid = (
max(
[b["id"] for b in st.session_state.budgets_variables], default=0
)
+ 1
)
st.session_state.budgets_variables.append(
{"id": nid, "nom": nom_env, "plafond": plafond_env, "devise": dev_env}
)
actualiser_sauvegarde()
st.rerun()
total_var_depense_eur = 0
for b in st.session_state.budgets_variables:
st.markdown(f"### 🛒 Enveloppe : {b['nom']} (Max: {b['plafond']} {b['devise']})")
with st.form(f"form_add_var_{b['id']}", clear_on_submit=True):
ca1, ca2 = st.columns([2, 1])
with ca1:
lib_dep = st.text_input("Détail (ex: Migros)", key=f"lib_{b['id']}")
with ca2:
val_dep = st.number_input(
"Montant dépensé", min_value=0.0, step=5.0, key=f"val_{b['id']}"
)
if st.form_submit_button("Ajouter à ce budget") and lib_dep and val_dep > 0:
nv_id = (
max(
[d["id"] for d in st.session_state.depenses_variables], default=0
)
+ 1
)
st.session_state.depenses_variables.append({
"id": nv_id,
"id_budget": b["id"],
"nom": lib_dep,
"montant": val_dep,
"devise": b["devise"],
})
actualiser_sauvegarde()
st.rerun()
depenses_de_cette_env = [
d for d in st.session_state.depenses_variables if d["id_budget"] == b["id"]
]
total_depense_env = sum(d["montant"] for d in depenses_de_cette_env)
reste_env = b["plafond"] - total_depense_env
total_depense_env_eur = (
total_depense_env
if b["devise"] == "EUR"
else total_depense_env * taux_chf_eur
)
total_var_depense_eur += total_depense_env_eur
for d in depenses_de_cette_env:
dc1, dc2, dc3 = st.columns([3, 2, 1])
dc1.write(f"- {d['nom']}")
dc2.write(f"{d['montant']:.2f} {d['devise']}")
if dc3.button("🗑️", key=f"del_dv_{d['id']}"):
st.session_state.depenses_variables = [
x for x in st.session_state.depenses_variables if x["id"] != d["id"]
]
actualiser_sauvegarde()
st.rerun()
progression = (
min(total_depense_env / b["plafond"], 1.0) if b["plafond"] > 0 else 0
)
st.progress(progression)
st.write(
f"**Dépensé :** {total_depense_env:.2f} {b['devise']} / **Reste :**"
f" `{reste_env:.2f} {b['devise']}`"
)
st.markdown("---")
# --- 3. CHARGES FIXES ---
st.subheader("3️⃣ Charges Fixes & Échéances")
with st.form("form_fixe", clear_on_submit=True):
fc1, fc2, fc3 = st.columns(3)
with fc1:
nom_f = st.text_input("Nom (ex: Loyer)")
val_f = st.number_input("Montant", min_value=0.0, step=5.0, format="%.2f")
with fc2:
dev_f = st.selectbox("Devise", ["CHF", "EUR"], key="dev_fixe")
date_f = st.date_input("Date de prélèvement", value=date.today())
with fc3:
paiement_f = st.selectbox(
"Moyen de paiement", ["Carte Bleue", "Virement", "Espèces", "Chèque"]
)
if st.form_submit_button("Ajouter l'échéance") and nom_f and val_f > 0:
nf_id = (
max([df["id"] for df in st.session_state.depenses_fixes], default=0) + 1
)
st.session_state.depenses_fixes.append({
"id": nf_id,
"nom": nom_f,
"montant": val_f,
"devise": dev_f,
"date": str(date_f),
"paiement": paiement_f,
"paye": False,
})
actualiser_sauvegarde()
st.rerun()
st.session_state.depenses_fixes = sorted(
st.session_state.depenses_fixes, key=lambda x: x["date"]
)
total_fixes_prevues_eur = 0
total_fixes_payees_eur = 0
for df in st.session_state.depenses_fixes:
m_eur = df["montant"] if df["devise"] == "EUR" else df["montant"] * taux_chf_eur
total_fixes_prevues_eur += m_eur
if df["paye"]:
total_fixes_payees_eur += m_eur
icone_paiement = {
"Carte Bleue": "💳",
"Virement": "🏦",
"Espèces": "💵",
"Chèque": "📄",
}.get(df["paiement"], "💰")
date_aff = (
date.fromisoformat(df["date"])
if isinstance(df["date"], str)
else df["date"]
)
fc1, fc2, fc3 = st.columns([3, 2, 1])
with fc1:
st.write(
f"📅 **{date_aff.strftime('%d/%m/%Y')}** : {df['nom']}"
f" ({df['montant']} {df['devise']})"
)
st.caption(f"{icone_paiement} {df['paiement']}")
with fc2:
st_paye = st.checkbox("Payé", value=df["paye"], key=f"chk_f_{df['id']}")
if st_paye != df["paye"]:
df["paye"] = st_paye
actualiser_sauvegarde()
st.rerun()
with fc3:
if st.button("🗑️", key=f"del_f_{df['id']}"):
st.session_state.depenses_fixes = [
x for x in st.session_state.depenses_fixes if x["id"] != df["id"]
]
actualiser_sauvegarde()
st.rerun()
# --- 4. TABLEAU DE BORD ---
st.markdown("---")
st.subheader("📊 Résumé & Reste à vivre")
reste_fixes_a_payer = total_fixes_prevues_eur - total_fixes_payees_eur
reste_a_vivre = total_rev_eur - (reste_fixes_a_payer + total_var_depense_eur)
m1, m2 = st.columns(2)
m1.metric("Charges Fixes Restantes", f"{reste_fixes_a_payer:,.2f} €")
m2.metric("Reste à Vivre Global", f"{reste_a_vivre:,.2f} €")