diff --git a/app.py b/app.py index b82777d..1a335a8 100644 --- a/app.py +++ b/app.py @@ -1,116 +1,115 @@ import streamlit as st - import pandas as pd - from pathlib import Path - from datetime import date, time - DATA_DIR = Path("data") - TODO_FILE = DATA_DIR / "todo.csv" - CALENDAR_FILE = DATA_DIR / "calendar.csv" - DATA_DIR.mkdir(parents=True, exist_ok=True) - TODO_COLUMNS = [ - "id","titel","beschreibung","kategorie","prioritaet","status", - "faellig_am","wiederholen","notiz" - ] - CALENDAR_COLUMNS = [ - "id","titel","datum","uhrzeit","dauer_min","kalender","kategorie", - "erinnerung_min","wiederholen","notiz" - ] - def load_csv(path: Path, columns: list[str]) -> pd.DataFrame: - if path.exists(): - df = pd.read_csv(path) - for col in columns: - if col not in df.columns: - df[col] = "" - return df[columns] - return pd.DataFrame(columns=columns) - def save_csv(df: pd.DataFrame, path: Path) -> None: - df.to_csv(path, index=False) - def next_id(df: pd.DataFrame) -> int: - if df.empty or "id" not in df.columns: - return 1 - try: - return int(df["id"].max()) + 1 - except Exception: - return 1 - st.set_page_config(page_title="Bitcoin-Programm", layout="wide") - st.title("Bitcoin-Programm – To-do & Kalender") - tab1, tab2, tab3 = st.tabs(["To-do anlegen", "Kalendertermin anlegen", "Daten ansehen"]) - with tab1: - st.subheader("Neues To-do") - todo_df = load_csv(TODO_FILE, TODO_COLUMNS) - new_todo_id = next_id(todo_df) - with st.form("todo_form", clear_on_submit=True): - titel = st.text_input("Titel") - beschreibung = st.text_area("Beschreibung") - kategorie = st.selectbox("Kategorie", ["Allgemein", "Bitcoin", "Dokumentation", "Kalender", "Admin", "Sonstiges"]) - prioritaet = st.selectbox("Priorität", ["Niedrig", "Mittel", "Hoch"]) - status = st.selectbox("Status", ["Offen", "In Arbeit", "Wartet", "Erledigt"]) - faellig_am = st.date_input("Fällig am", value=date.today()) - wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"]) - notiz = st.text_area("Notiz") - submitted = st.form_submit_button("To-do speichern") - if submitted: - if not titel.strip(): - st.error("Bitte mindestens einen Titel eingeben.") - else: - new_row = pd.DataFrame([{ - "id": new_todo_id, - "titel": titel, - "beschreibung": beschreibung, - "kategorie": kategorie, - "prioritaet": prioritaet, - "status": status, - "faellig_am": faellig_am.isoformat(), - "wiederholen": wiederholen, - "notiz": notiz, - }]) - todo_df = pd.concat([todo_df, new_row], ignore_index=True) - save_csv(todo_df, TODO_FILE) - st.success("To-do gespeichert.") - with tab2: - st.subheader("Neuen Kalendereintrag") - cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS) - new_cal_id = next_id(cal_df) - with st.form("calendar_form", clear_on_submit=True): - titel = st.text_input("Titel") - datum = st.date_input("Datum", value=date.today()) - uhrzeit = st.time_input("Uhrzeit", value=time(9, 0)) - dauer_min = st.number_input("Dauer in Minuten", min_value=5, max_value=1440, value=60, step=5) - kalender = st.selectbox("Kalender", ["Arbeit", "Privat", "Bitcoin", "Todo"]) - kategorie = st.selectbox("Kategorie", ["Meeting", "Aufgabe", "Erinnerung", "Termin", "Sonstiges"]) - erinnerung_min = st.number_input("Erinnerung vorab in Minuten", min_value=0, max_value=10080, value=30, step=5) - wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"]) - notiz = st.text_area("Notiz") - submitted = st.form_submit_button("Kalendertermin speichern") - if submitted: - if not titel.strip(): - st.error("Bitte mindestens einen Titel eingeben.") - else: - new_row = pd.DataFrame([{ - "id": new_cal_id, - "titel": titel, - "datum": datum.isoformat(), - "uhrzeit": uhrzeit.strftime("%H:%M"), - "dauer_min": int(dauer_min), - "kalender": kalender, - "kategorie": kategorie, - "erinnerung_min": int(erinnerung_min), - "wiederholen": wiederholen, - "notiz": notiz, - }]) - cal_df = pd.concat([cal_df, new_row], ignore_index=True) - save_csv(cal_df, CALENDAR_FILE) - st.success("Kalendertermin gespeichert.") - with tab3: - st.subheader("Gespeicherte Daten") - col1, col2 = st.columns(2) - with col1: - st.markdown("### To-dos") - todo_df = load_csv(TODO_FILE, TODO_COLUMNS) - st.dataframe(todo_df, use_container_width=True) - st.download_button("To-do CSV herunterladen", data=todo_df.to_csv(index=False).encode("utf-8"), file_name="todo.csv", mime="text/csv") - with col2: - st.markdown("### Kalender") - cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS) - st.dataframe(cal_df, use_container_width=True) - st.download_button("Kalender CSV herunterladen", data=cal_df.to_csv(index=False).encode("utf-8"), file_name="calendar.csv", mime="text/csv") - \ No newline at end of file +import pandas as pd +from pathlib import Path +from datetime import date, time +DATA_DIR = Path("data") +TODO_FILE = DATA_DIR / "todo.csv" +CALENDAR_FILE = DATA_DIR / "calendar.csv" +DATA_DIR.mkdir(parents=True, exist_ok=True) +TODO_COLUMNS = [ + "id","titel","beschreibung","kategorie","prioritaet","status", + "faellig_am","wiederholen","notiz" +] +CALENDAR_COLUMNS = [ + "id","titel","datum","uhrzeit","dauer_min","kalender","kategorie", + "erinnerung_min","wiederholen","notiz" +] +def load_csv(path: Path, columns: list[str]) -> pd.DataFrame: + if path.exists(): + df = pd.read_csv(path) + for col in columns: + if col not in df.columns: + df[col] = "" + return df[columns] + return pd.DataFrame(columns=columns) +def save_csv(df: pd.DataFrame, path: Path) -> None: + df.to_csv(path, index=False) +def next_id(df: pd.DataFrame) -> int: + if df.empty or "id" not in df.columns: + return 1 + try: + return int(df["id"].max()) + 1 + except Exception: + return 1 +st.set_page_config(page_title="Bitcoin-Programm", layout="wide") +st.title("Bitcoin-Programm – To-do & Kalender") +tab1, tab2, tab3 = st.tabs(["To-do anlegen", "Kalendertermin anlegen", "Daten ansehen"]) +with tab1: + st.subheader("Neues To-do") + todo_df = load_csv(TODO_FILE, TODO_COLUMNS) + new_todo_id = next_id(todo_df) + with st.form("todo_form", clear_on_submit=True): + titel = st.text_input("Titel") + beschreibung = st.text_area("Beschreibung") + kategorie = st.selectbox("Kategorie", ["Allgemein", "Bitcoin", "Dokumentation", "Kalender", "Admin", "Sonstiges"]) + prioritaet = st.selectbox("Priorität", ["Niedrig", "Mittel", "Hoch"]) + status = st.selectbox("Status", ["Offen", "In Arbeit", "Wartet", "Erledigt"]) + faellig_am = st.date_input("Fällig am", value=date.today()) + wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"]) + notiz = st.text_area("Notiz") + submitted = st.form_submit_button("To-do speichern") + if submitted: + if not titel.strip(): + st.error("Bitte mindestens einen Titel eingeben.") + else: + new_row = pd.DataFrame([{ + "id": new_todo_id, + "titel": titel, + "beschreibung": beschreibung, + "kategorie": kategorie, + "prioritaet": prioritaet, + "status": status, + "faellig_am": faellig_am.isoformat(), + "wiederholen": wiederholen, + "notiz": notiz, + }]) + todo_df = pd.concat([todo_df, new_row], ignore_index=True) + save_csv(todo_df, TODO_FILE) + st.success("To-do gespeichert.") +with tab2: + st.subheader("Neuen Kalendereintrag") + cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS) + new_cal_id = next_id(cal_df) + with st.form("calendar_form", clear_on_submit=True): + titel = st.text_input("Titel") + datum = st.date_input("Datum", value=date.today()) + uhrzeit = st.time_input("Uhrzeit", value=time(9, 0)) + dauer_min = st.number_input("Dauer in Minuten", min_value=5, max_value=1440, value=60, step=5) + kalender = st.selectbox("Kalender", ["Arbeit", "Privat", "Bitcoin", "Todo"]) + kategorie = st.selectbox("Kategorie", ["Meeting", "Aufgabe", "Erinnerung", "Termin", "Sonstiges"]) + erinnerung_min = st.number_input("Erinnerung vorab in Minuten", min_value=0, max_value=10080, value=30, step=5) + wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"]) + notiz = st.text_area("Notiz") + submitted = st.form_submit_button("Kalendertermin speichern") + if submitted: + if not titel.strip(): + st.error("Bitte mindestens einen Titel eingeben.") + else: + new_row = pd.DataFrame([{ + "id": new_cal_id, + "titel": titel, + "datum": datum.isoformat(), + "uhrzeit": uhrzeit.strftime("%H:%M"), + "dauer_min": int(dauer_min), + "kalender": kalender, + "kategorie": kategorie, + "erinnerung_min": int(erinnerung_min), + "wiederholen": wiederholen, + "notiz": notiz, + }]) + cal_df = pd.concat([cal_df, new_row], ignore_index=True) + save_csv(cal_df, CALENDAR_FILE) + st.success("Kalendertermin gespeichert.") +with tab3: + st.subheader("Gespeicherte Daten") + col1, col2 = st.columns(2) + with col1: + st.markdown("### To-dos") + todo_df = load_csv(TODO_FILE, TODO_COLUMNS) + st.dataframe(todo_df, use_container_width=True) + st.download_button("To-do CSV herunterladen", data=todo_df.to_csv(index=False).encode("utf-8"), file_name="todo.csv", mime="text/csv") + with col2: + st.markdown("### Kalender") + cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS) + st.dataframe(cal_df, use_container_width=True) + st.download_button("Kalender CSV herunterladen", data=cal_df.to_csv(index=False).encode("utf-8"), file_name="calendar.csv", mime="text/csv") diff --git a/data/calendar.csv b/data/calendar.csv new file mode 100644 index 0000000..a5ec927 --- /dev/null +++ b/data/calendar.csv @@ -0,0 +1,12 @@ +id,titel,datum,uhrzeit,dauer_min,kalender,kategorie,erinnerung_min,wiederholen,notiz +1,Horror Game (Sprint 0-1),2026-08-26,17:00,180,Arbeit,Meeting,30,Täglich,"Game muss entwickelt werden, Fokus AN." +2,Gym,2026-08-26,13:00,60,Privat,Aufgabe,0,Täglich,Gesundheit +3,Yupin zur Arbeit fahren,2026-08-26,15:30,45,Privat,Aufgabe,15,Täglich,"Freundin zur Arbeit mit dem Roller fahren, danach kann Kleinigkeit im Big C gekauft werden." +4,Bitcoin pflegen,2026-08-31,12:00,60,Todo,Erinnerung,0,Wöchentlich,"Bitcoin lokal wissen aufbauen, Endresultat aktuelles Chart mit allen werten Blabla" +5,Pattaya Marathon 2026,2026-09-27,04:00,240,Privat,Aufgabe,60,Nein,one Marathon one year older +6,Reminder Call Family,2026-08-29,13:00,30,Privat,Aufgabe,0,Wöchentlich, +7,Geburtstag Dominik Schaefer,2026-09-02,00:00,60,Privat,Erinnerung,0,Nein,"mein Geburtstag, werde 27 Jahre alt." +8,Yupins Geburtstag,2026-09-16,00:00,60,Privat,Erinnerung,0,Nein,"Was besonderes unternehmen, Essen gehen, Shoppen. " +9,Stichtag für die Wohnung (Miete / Nebenkosten),2026-09-05,00:00,1440,Privat,Aufgabe,0,Monatlich, +10,Rechnung für den Kredit ,2026-08-30,00:00,15,Bitcoin,Aufgabe,0,Monatlich, +11,Rechnung für den BTC-Kredit über 3.200€,2026-09-30,00:00,1440,Bitcoin,Aufgabe,10080,Nein,"firefish.io 0,10 btc" diff --git a/data/calendar.ics b/data/calendar.ics new file mode 100644 index 0000000..191d990 --- /dev/null +++ b/data/calendar.ics @@ -0,0 +1,83 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Hermes Agent//Calendar Exporter//DE +CALSCALE:GREGORIAN +METHOD:PUBLISH +BEGIN:VEVENT +UID:hermes-6854749454929450610@local +DTSTART:20260826T090000 +DTEND:20260826T100000 +SUMMARY:Horror Game (Sprint 0-1) +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:hermes-7102422966172142631@local +DTSTART:20260826T090000 +DTEND:20260826T100000 +SUMMARY:Gym +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:hermes-9140946676080998345@local +DTSTART:20260826T090000 +DTEND:20260826T100000 +SUMMARY:Yupin zur Arbeit fahren +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:hermes-2877415102198682578@local +DTSTART:20260831T090000 +DTEND:20260831T100000 +SUMMARY:Bitcoin pflegen +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:hermes-3211100783371897941@local +DTSTART:20260927T090000 +DTEND:20260927T100000 +SUMMARY:Pattaya Marathon 2026 +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:hermes-1365960028236796904@local +DTSTART:20260829T090000 +DTEND:20260829T100000 +SUMMARY:Reminder Call Family +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:hermes-4380767364730211534@local +DTSTART:20260902T090000 +DTEND:20260902T100000 +SUMMARY:Geburtstag Dominik Schaefer +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:hermes-1504447817045986540@local +DTSTART:20260916T090000 +DTEND:20260916T100000 +SUMMARY:Yupins Geburtstag +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:hermes-1352647901544740608@local +DTSTART:20260905T090000 +DTEND:20260905T100000 +SUMMARY:Stichtag für die Wohnung (Miete / Nebenkosten) +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:hermes-8717647521268059452@local +DTSTART:20260830T090000 +DTEND:20260830T100000 +SUMMARY:Rechnung für den Kredit +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:hermes-7306110145677903484@local +DTSTART:20260930T090000 +DTEND:20260930T100000 +SUMMARY:Rechnung für den BTC-Kredit über 3.200€ +DESCRIPTION: +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/generate_ics.py b/generate_ics.py new file mode 100644 index 0000000..b4a66d3 --- /dev/null +++ b/generate_ics.py @@ -0,0 +1,85 @@ +import csv +import os +from datetime import datetime, timedelta +csv_file = 'data/calendar.csv' +ics_file = 'data/calendar.ics' +if not os.path.exists(csv_file): + print(f"Error: {csv_file} not found. Please ensure your CSV is located at {csv_file}.") + exit(1) +ics_lines = [ + "BEGIN:VCALENDAR", + "VERSION:2.0", + "PRODID:-//Hermes Agent//Calendar Exporter//DE", + "CALSCALE:GREGORIAN", + "METHOD:PUBLISH" +] +days_map = { + "montag": "MO", "dienstag": "TU", "mittwoch": "WE", "donnerstag": "TH", + "freitag": "FR", "samstag": "SA", "sonntag": "SU" +} +with open(csv_file, mode='r', encoding='utf-8') as f: + reader = csv.DictReader(f) + for row in reader: + title = row.get("titel", "Termin") + time_str = row.get("zeit", "09:00") + rhythm = row.get("wochentag", "").strip().lower() + date_str = row.get("datum", "").strip() + desc = row.get("beschreibung", "") + # Zeit parsen + try: + hour, minute = map(int, time_str.split(":")) + except: + hour, minute = 9, 0 + uid = f"hermes-{abs(hash(title + time_str + rhythm + date_str))}@local" + + if rhythm == "täglich": + start_dt = datetime.now().replace(hour=hour, minute=minute, second=0, microsecond=0) + start_str = start_dt.strftime("%Y%m%dT%H%M%S") + end_str = (start_dt + timedelta(hours=1)).strftime("%Y%m%dT%H%M%S") + + ics_lines.extend([ + "BEGIN:VEVENT", + f"UID:{uid}", + f"DTSTART:{start_str}", + f"DTEND:{end_str}", + f"SUMMARY:{title}", + f"DESCRIPTION:{desc}", + "RRULE:FREQ=DAILY", + "END:VEVENT" + ]) + elif rhythm in days_map: + start_dt = datetime.now().replace(hour=hour, minute=minute, second=0, microsecond=0) + start_str = start_dt.strftime("%Y%m%dT%H%M%S") + end_str = (start_dt + timedelta(hours=1)).strftime("%Y%m%dT%H%M%S") + byday = days_map[rhythm] + + ics_lines.extend([ + "BEGIN:VEVENT", + f"UID:{uid}", + f"DTSTART:{start_str}", + f"DTEND:{end_str}", + f"SUMMARY:{title}", + f"DESCRIPTION:{desc}", + f"RRULE:FREQ=WEEKLY;BYDAY={byday}", + "END:VEVENT" + ]) + elif date_str: + try: + dt = datetime.strptime(date_str, "%Y-%m-%d").replace(hour=hour, minute=minute) + start_str = dt.strftime("%Y%m%dT%H%M%S") + end_str = (dt + timedelta(hours=1)).strftime("%Y%m%dT%H%M%S") + ics_lines.extend([ + "BEGIN:VEVENT", + f"UID:{uid}", + f"DTSTART:{start_str}", + f"DTEND:{end_str}", + f"SUMMARY:{title}", + f"DESCRIPTION:{desc}", + "END:VEVENT" + ]) + except Exception as e: + print(f"Skipping row due to date parse error: {row} ({e})") +ics_lines.append("END:VCALENDAR") +with open(ics_file, mode='w', encoding='utf-8') as f: + f.write("\n".join(ics_lines)) +print(f"Erfolgreich generiert: {ics_file}") \ No newline at end of file diff --git a/script.sh b/script.sh new file mode 100644 index 0000000..2e2ffd1 --- /dev/null +++ b/script.sh @@ -0,0 +1,9 @@ + # 1. Create a virtual environment named .venv +python3 -m venv .venv + +# 2. Activate the environment +source .venv/bin/activate + +# 3. Install dependencies and run +pip install -r requirements.txt +streamlit run app.py