This commit is contained in:
Dominik Schäfer
2026-08-26 15:26:49 +07:00
parent 5ab4fc5123
commit 908ed56830
5 changed files with 303 additions and 115 deletions
+114 -115
View File
@@ -1,116 +1,115 @@
import streamlit as st import streamlit as st
import pandas as pd import pandas as pd
from pathlib import Path from pathlib import Path
from datetime import date, time from datetime import date, time
DATA_DIR = Path("data") DATA_DIR = Path("data")
TODO_FILE = DATA_DIR / "todo.csv" TODO_FILE = DATA_DIR / "todo.csv"
CALENDAR_FILE = DATA_DIR / "calendar.csv" CALENDAR_FILE = DATA_DIR / "calendar.csv"
DATA_DIR.mkdir(parents=True, exist_ok=True) DATA_DIR.mkdir(parents=True, exist_ok=True)
TODO_COLUMNS = [ TODO_COLUMNS = [
"id","titel","beschreibung","kategorie","prioritaet","status", "id","titel","beschreibung","kategorie","prioritaet","status",
"faellig_am","wiederholen","notiz" "faellig_am","wiederholen","notiz"
] ]
CALENDAR_COLUMNS = [ CALENDAR_COLUMNS = [
"id","titel","datum","uhrzeit","dauer_min","kalender","kategorie", "id","titel","datum","uhrzeit","dauer_min","kalender","kategorie",
"erinnerung_min","wiederholen","notiz" "erinnerung_min","wiederholen","notiz"
] ]
def load_csv(path: Path, columns: list[str]) -> pd.DataFrame: def load_csv(path: Path, columns: list[str]) -> pd.DataFrame:
if path.exists(): if path.exists():
df = pd.read_csv(path) df = pd.read_csv(path)
for col in columns: for col in columns:
if col not in df.columns: if col not in df.columns:
df[col] = "" df[col] = ""
return df[columns] return df[columns]
return pd.DataFrame(columns=columns) return pd.DataFrame(columns=columns)
def save_csv(df: pd.DataFrame, path: Path) -> None: def save_csv(df: pd.DataFrame, path: Path) -> None:
df.to_csv(path, index=False) df.to_csv(path, index=False)
def next_id(df: pd.DataFrame) -> int: def next_id(df: pd.DataFrame) -> int:
if df.empty or "id" not in df.columns: if df.empty or "id" not in df.columns:
return 1 return 1
try: try:
return int(df["id"].max()) + 1 return int(df["id"].max()) + 1
except Exception: except Exception:
return 1 return 1
st.set_page_config(page_title="Bitcoin-Programm", layout="wide") st.set_page_config(page_title="Bitcoin-Programm", layout="wide")
st.title("Bitcoin-Programm – To-do & Kalender") st.title("Bitcoin-Programm – To-do & Kalender")
tab1, tab2, tab3 = st.tabs(["To-do anlegen", "Kalendertermin anlegen", "Daten ansehen"]) tab1, tab2, tab3 = st.tabs(["To-do anlegen", "Kalendertermin anlegen", "Daten ansehen"])
with tab1: with tab1:
st.subheader("Neues To-do") st.subheader("Neues To-do")
todo_df = load_csv(TODO_FILE, TODO_COLUMNS) todo_df = load_csv(TODO_FILE, TODO_COLUMNS)
new_todo_id = next_id(todo_df) new_todo_id = next_id(todo_df)
with st.form("todo_form", clear_on_submit=True): with st.form("todo_form", clear_on_submit=True):
titel = st.text_input("Titel") titel = st.text_input("Titel")
beschreibung = st.text_area("Beschreibung") beschreibung = st.text_area("Beschreibung")
kategorie = st.selectbox("Kategorie", ["Allgemein", "Bitcoin", "Dokumentation", "Kalender", "Admin", "Sonstiges"]) kategorie = st.selectbox("Kategorie", ["Allgemein", "Bitcoin", "Dokumentation", "Kalender", "Admin", "Sonstiges"])
prioritaet = st.selectbox("Priorität", ["Niedrig", "Mittel", "Hoch"]) prioritaet = st.selectbox("Priorität", ["Niedrig", "Mittel", "Hoch"])
status = st.selectbox("Status", ["Offen", "In Arbeit", "Wartet", "Erledigt"]) status = st.selectbox("Status", ["Offen", "In Arbeit", "Wartet", "Erledigt"])
faellig_am = st.date_input("Fällig am", value=date.today()) faellig_am = st.date_input("Fällig am", value=date.today())
wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"]) wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"])
notiz = st.text_area("Notiz") notiz = st.text_area("Notiz")
submitted = st.form_submit_button("To-do speichern") submitted = st.form_submit_button("To-do speichern")
if submitted: if submitted:
if not titel.strip(): if not titel.strip():
st.error("Bitte mindestens einen Titel eingeben.") st.error("Bitte mindestens einen Titel eingeben.")
else: else:
new_row = pd.DataFrame([{ new_row = pd.DataFrame([{
"id": new_todo_id, "id": new_todo_id,
"titel": titel, "titel": titel,
"beschreibung": beschreibung, "beschreibung": beschreibung,
"kategorie": kategorie, "kategorie": kategorie,
"prioritaet": prioritaet, "prioritaet": prioritaet,
"status": status, "status": status,
"faellig_am": faellig_am.isoformat(), "faellig_am": faellig_am.isoformat(),
"wiederholen": wiederholen, "wiederholen": wiederholen,
"notiz": notiz, "notiz": notiz,
}]) }])
todo_df = pd.concat([todo_df, new_row], ignore_index=True) todo_df = pd.concat([todo_df, new_row], ignore_index=True)
save_csv(todo_df, TODO_FILE) save_csv(todo_df, TODO_FILE)
st.success("To-do gespeichert.") st.success("To-do gespeichert.")
with tab2: with tab2:
st.subheader("Neuen Kalendereintrag") st.subheader("Neuen Kalendereintrag")
cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS) cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS)
new_cal_id = next_id(cal_df) new_cal_id = next_id(cal_df)
with st.form("calendar_form", clear_on_submit=True): with st.form("calendar_form", clear_on_submit=True):
titel = st.text_input("Titel") titel = st.text_input("Titel")
datum = st.date_input("Datum", value=date.today()) datum = st.date_input("Datum", value=date.today())
uhrzeit = st.time_input("Uhrzeit", value=time(9, 0)) 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) 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"]) kalender = st.selectbox("Kalender", ["Arbeit", "Privat", "Bitcoin", "Todo"])
kategorie = st.selectbox("Kategorie", ["Meeting", "Aufgabe", "Erinnerung", "Termin", "Sonstiges"]) 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) 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"]) wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"])
notiz = st.text_area("Notiz") notiz = st.text_area("Notiz")
submitted = st.form_submit_button("Kalendertermin speichern") submitted = st.form_submit_button("Kalendertermin speichern")
if submitted: if submitted:
if not titel.strip(): if not titel.strip():
st.error("Bitte mindestens einen Titel eingeben.") st.error("Bitte mindestens einen Titel eingeben.")
else: else:
new_row = pd.DataFrame([{ new_row = pd.DataFrame([{
"id": new_cal_id, "id": new_cal_id,
"titel": titel, "titel": titel,
"datum": datum.isoformat(), "datum": datum.isoformat(),
"uhrzeit": uhrzeit.strftime("%H:%M"), "uhrzeit": uhrzeit.strftime("%H:%M"),
"dauer_min": int(dauer_min), "dauer_min": int(dauer_min),
"kalender": kalender, "kalender": kalender,
"kategorie": kategorie, "kategorie": kategorie,
"erinnerung_min": int(erinnerung_min), "erinnerung_min": int(erinnerung_min),
"wiederholen": wiederholen, "wiederholen": wiederholen,
"notiz": notiz, "notiz": notiz,
}]) }])
cal_df = pd.concat([cal_df, new_row], ignore_index=True) cal_df = pd.concat([cal_df, new_row], ignore_index=True)
save_csv(cal_df, CALENDAR_FILE) save_csv(cal_df, CALENDAR_FILE)
st.success("Kalendertermin gespeichert.") st.success("Kalendertermin gespeichert.")
with tab3: with tab3:
st.subheader("Gespeicherte Daten") st.subheader("Gespeicherte Daten")
col1, col2 = st.columns(2) col1, col2 = st.columns(2)
with col1: with col1:
st.markdown("### To-dos") st.markdown("### To-dos")
todo_df = load_csv(TODO_FILE, TODO_COLUMNS) todo_df = load_csv(TODO_FILE, TODO_COLUMNS)
st.dataframe(todo_df, use_container_width=True) 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") 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: with col2:
st.markdown("### Kalender") st.markdown("### Kalender")
cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS) cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS)
st.dataframe(cal_df, use_container_width=True) 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") st.download_button("Kalender CSV herunterladen", data=cal_df.to_csv(index=False).encode("utf-8"), file_name="calendar.csv", mime="text/csv")
+12
View File
@@ -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"
1 id titel datum uhrzeit dauer_min kalender kategorie erinnerung_min wiederholen notiz
2 1 Horror Game (Sprint 0-1) 2026-08-26 17:00 180 Arbeit Meeting 30 Täglich Game muss entwickelt werden, Fokus AN.
3 2 Gym 2026-08-26 13:00 60 Privat Aufgabe 0 Täglich Gesundheit
4 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.
5 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
6 5 Pattaya Marathon 2026 2026-09-27 04:00 240 Privat Aufgabe 60 Nein one Marathon one year older
7 6 Reminder Call Family 2026-08-29 13:00 30 Privat Aufgabe 0 Wöchentlich
8 7 Geburtstag Dominik Schaefer 2026-09-02 00:00 60 Privat Erinnerung 0 Nein mein Geburtstag, werde 27 Jahre alt.
9 8 Yupins Geburtstag 2026-09-16 00:00 60 Privat Erinnerung 0 Nein Was besonderes unternehmen, Essen gehen, Shoppen.
10 9 Stichtag für die Wohnung (Miete / Nebenkosten) 2026-09-05 00:00 1440 Privat Aufgabe 0 Monatlich
11 10 Rechnung für den Kredit 2026-08-30 00:00 15 Bitcoin Aufgabe 0 Monatlich
12 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
+83
View File
@@ -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
+85
View File
@@ -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}")
+9
View File
@@ -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