commit
This commit is contained in:
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
@@ -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
|
||||||
@@ -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}")
|
||||||
Reference in New Issue
Block a user