dritter commit, hoffentlich sind grundfunktionen da
This commit is contained in:
@@ -1,79 +1,21 @@
|
|||||||
bitcoin-programm
|
# bitcoin-programm
|
||||||
|
|
||||||
Lokales Projekt zur strukturierten Erfassung und Verwaltung von:
|
Lokales Projekt zur strukturierten Erfassung und Verwaltung von:
|
||||||
|
- To-dos
|
||||||
• To-dos
|
- Kalendereinträgen
|
||||||
• Kalendereinträgen
|
- Dokumentation
|
||||||
• Dokumentation
|
- später: Bitcoin-Transaktionen und Auswertungen
|
||||||
• später: Bitcoin-Transaktionen und Auswertungen
|
## Ziel
|
||||||
|
|
||||||
Ziel
|
|
||||||
|
|
||||||
Das Projekt dient als zentrales Arbeitsverzeichnis für persönliche Verwaltung und spätere Automatisierung.
|
Das Projekt dient als zentrales Arbeitsverzeichnis für persönliche Verwaltung und spätere Automatisierung.
|
||||||
|
|
||||||
Geplant ist:
|
Geplant ist:
|
||||||
|
- eine einfache Eingabemaske für To-dos und Kalendertermine
|
||||||
• eine einfache Eingabemaske für To-dos und Kalendertermine
|
- CSV als einfache Zwischen- und Exportform
|
||||||
• CSV als einfache Zwischen- und Exportform
|
- später ein SQLite-Backend für Auswertung und Dashboard
|
||||||
• später ein SQLite-Backend für Auswertung und Dashboard
|
- optional Sync mit Google Calendar oder iCloud Kalender
|
||||||
• optional Sync mit Google Calendar oder iCloud Kalender
|
## Aktueller Fokus
|
||||||
|
|
||||||
Aktueller Fokus
|
|
||||||
|
|
||||||
Der aktuelle Fokus liegt auf:
|
Der aktuelle Fokus liegt auf:
|
||||||
|
|
||||||
1. Dokumentation
|
1. Dokumentation
|
||||||
2. To-do-Erfassung
|
2. To-do-Erfassung
|
||||||
3. Kalender-Erfassung
|
3. Kalender-Erfassung
|
||||||
4. saubere Datenstruktur für späteren Import nach SQLite
|
4. saubere Datenstruktur für späteren Import nach SQLite
|
||||||
|
|
||||||
Bitcoin-Tracking kann später ergänzt werden.
|
Bitcoin-Tracking kann später ergänzt werden.
|
||||||
|
|
||||||
Projektstruktur
|
|
||||||
|
|
||||||
Empfohlene Struktur:
|
|
||||||
|
|
||||||
─ text
|
|
||||||
bitcoin-programm/
|
|
||||||
├─ README.md
|
|
||||||
├─ docs/
|
|
||||||
├─ data/
|
|
||||||
├─ templates/
|
|
||||||
├─ scripts/
|
|
||||||
├─ sqlite/
|
|
||||||
├─ dashboard/
|
|
||||||
└─ notes/
|
|
||||||
|
|
||||||
Datenprinzip
|
|
||||||
|
|
||||||
• Rohdaten werden getrennt von Verarbeitung gespeichert
|
|
||||||
• CSV-Dateien dienen als einfache Austausch- und Importform
|
|
||||||
• SQLite wird später als strukturierte Datenbasis genutzt
|
|
||||||
• Kalender- und To-do-Daten bleiben nachvollziehbar und versionierbar
|
|
||||||
|
|
||||||
Bedienung
|
|
||||||
|
|
||||||
Geplant ist eine lokale Eingabemaske, mit der Einträge einfacher gepflegt werden können als direkt über CSV.
|
|
||||||
|
|
||||||
Ziele der Maske:
|
|
||||||
|
|
||||||
• Pflichtfelder
|
|
||||||
• Dropdowns für feste Werte
|
|
||||||
• weniger Tippfehler
|
|
||||||
• saubere CSV-Ausgabe
|
|
||||||
• später einfache Anbindung an SQLite
|
|
||||||
|
|
||||||
Langfristige Erweiterung
|
|
||||||
|
|
||||||
Später sollen möglich sein:
|
|
||||||
|
|
||||||
• Kalender-Synchronisation
|
|
||||||
• Wiederholungen
|
|
||||||
• Erinnerungen
|
|
||||||
• Dashboard-Ansicht
|
|
||||||
• Bitcoin-Tracking
|
|
||||||
• Auswertung und Filter
|
|
||||||
|
|
||||||
Hinweise
|
|
||||||
|
|
||||||
Dieses Repo ist bewusst schlank gehalten und wächst schrittweise mit den Anforderungen.
|
|
||||||
|
|||||||
@@ -1,35 +1,20 @@
|
|||||||
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",
|
"id","titel","beschreibung","kategorie","prioritaet","status",
|
||||||
"titel",
|
"faellig_am","wiederholen","notiz"
|
||||||
"beschreibung",
|
]
|
||||||
"kategorie",
|
CALENDAR_COLUMNS = [
|
||||||
"prioritaet",
|
"id","titel","datum","uhrzeit","dauer_min","kalender","kategorie",
|
||||||
"status",
|
"erinnerung_min","wiederholen","notiz"
|
||||||
"faellig_am",
|
]
|
||||||
"wiederholen",
|
def load_csv(path: Path, columns: list[str]) -> pd.DataFrame:
|
||||||
"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():
|
if path.exists():
|
||||||
df = pd.read_csv(path)
|
df = pd.read_csv(path)
|
||||||
for col in columns:
|
for col in columns:
|
||||||
@@ -37,20 +22,19 @@ def load_csv(path: Path, columns: list[str]) -> pd.DataFrame:
|
|||||||
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"])
|
||||||
# --- To-do Formular ---
|
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)
|
||||||
@@ -82,8 +66,7 @@ with tab1:
|
|||||||
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.")
|
||||||
# --- Kalender Formular ---
|
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)
|
||||||
@@ -117,27 +100,17 @@ with tab2:
|
|||||||
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.")
|
||||||
# --- Daten ansehen ---
|
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")
|
||||||
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(
|
st.download_button("Kalender CSV herunterladen", data=cal_df.to_csv(index=False).encode("utf-8"), file_name="calendar.csv", mime="text/csv")
|
||||||
"To-do CSV herunterladen",
|
|
||||||
data=todo_df.to_csv(index=False).encode("utf-8"),
|
|
||||||
file_name="todo.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,386 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
|
from urllib.error import HTTPError, URLError
|
||||||
|
REPO_PATH = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path.cwd()
|
||||||
|
GITEA_BASE = os.environ.get("GITEA_BASE", "https://gitea.schaefinik.xyz")
|
||||||
|
GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "").strip()
|
||||||
|
FILES = {
|
||||||
|
"README.md": """# bitcoin-programm
|
||||||
|
Lokales Projekt zur strukturierten Erfassung und Verwaltung von:
|
||||||
|
- To-dos
|
||||||
|
- Kalendereinträgen
|
||||||
|
- Dokumentation
|
||||||
|
- später: Bitcoin-Transaktionen und Auswertungen
|
||||||
|
## Ziel
|
||||||
|
Das Projekt dient als zentrales Arbeitsverzeichnis für persönliche Verwaltung und spätere Automatisierung.
|
||||||
|
Geplant ist:
|
||||||
|
- eine einfache Eingabemaske für To-dos und Kalendertermine
|
||||||
|
- CSV als einfache Zwischen- und Exportform
|
||||||
|
- später ein SQLite-Backend für Auswertung und Dashboard
|
||||||
|
- optional Sync mit Google Calendar oder iCloud Kalender
|
||||||
|
## Aktueller Fokus
|
||||||
|
Der aktuelle Fokus liegt auf:
|
||||||
|
1. Dokumentation
|
||||||
|
2. To-do-Erfassung
|
||||||
|
3. Kalender-Erfassung
|
||||||
|
4. saubere Datenstruktur für späteren Import nach SQLite
|
||||||
|
Bitcoin-Tracking kann später ergänzt werden.
|
||||||
|
""",
|
||||||
|
"docs/00_overview.md": """# Überblick
|
||||||
|
Dieses Projekt ist ein lokales Arbeits- und Dokumentationsprojekt für persönliche Organisation.
|
||||||
|
## Zweck
|
||||||
|
Das Projekt soll folgende Bereiche abdecken:
|
||||||
|
- To-dos
|
||||||
|
- Kalendertermine
|
||||||
|
- Projektdokumentation
|
||||||
|
- später Bitcoin-Tracking
|
||||||
|
- später SQLite-basierte Auswertung
|
||||||
|
## Leitprinzipien
|
||||||
|
1. Daten sauber strukturieren
|
||||||
|
2. Eingaben einfach halten
|
||||||
|
3. CSV als Zwischenformat nutzbar machen
|
||||||
|
4. später SQL-Backend ergänzen
|
||||||
|
5. lokale Kontrolle behalten
|
||||||
|
## Arbeitsweise
|
||||||
|
### 1. Erfassung
|
||||||
|
Neue Einträge werden über eine Eingabemaske erfasst.
|
||||||
|
### 2. Speicherung
|
||||||
|
Die Daten werden in CSV abgelegt und später in SQLite überführt.
|
||||||
|
## Zielbild
|
||||||
|
Am Ende soll das Projekt enthalten:
|
||||||
|
- eine einfache Eingabemaske
|
||||||
|
- eine saubere Dokumentation
|
||||||
|
- exportierbare Daten
|
||||||
|
- ein lokales Dashboard
|
||||||
|
- Kalenderanbindung
|
||||||
|
- To-do-Verwaltung
|
||||||
|
- später Bitcoin-Verwaltung
|
||||||
|
## Status
|
||||||
|
Der Fokus liegt aktuell auf Dokumentation, Kalender und To-do. Bitcoin-Tracking wird später ergänzt.
|
||||||
|
""",
|
||||||
|
"docs/01_requirements.md": """# Anforderungen
|
||||||
|
## Hauptziel
|
||||||
|
Das Projekt soll eine einfache und verlässliche Verwaltung von To-dos und Kalenderterminen ermöglichen.
|
||||||
|
## Funktionale Anforderungen
|
||||||
|
### To-dos
|
||||||
|
- To-dos sollen über eine Eingabemaske erfasst werden
|
||||||
|
- Pflichtfelder müssen vorhanden sein
|
||||||
|
- feste Werte sollen per Dropdown gewählt werden
|
||||||
|
- die Daten sollen als CSV exportierbar sein
|
||||||
|
### Kalender
|
||||||
|
- Kalendertermine sollen über eine Eingabemaske erfasst werden
|
||||||
|
- Datum und Uhrzeit müssen wählbar sein
|
||||||
|
- Terminarten sollen per Dropdown auswählbar sein
|
||||||
|
- Wiederholungen sollen unterstützt werden
|
||||||
|
- Erinnerungen sollen vorbereitet sein
|
||||||
|
### Datenhaltung
|
||||||
|
- CSV als Zwischenspeicher
|
||||||
|
- später SQLite als Datenbasis
|
||||||
|
- klare Trennung von Rohdaten und verarbeiteten Daten
|
||||||
|
## Nicht-funktionale Anforderungen
|
||||||
|
- lokale Nutzung ohne Cloud-Zwang
|
||||||
|
- später synchronisierbar mit Google Calendar oder iCloud
|
||||||
|
- leicht verständlich und wartbar
|
||||||
|
- importfreundlich für SQLite
|
||||||
|
- dokumentierbar und versionierbar
|
||||||
|
## UI-Anforderungen
|
||||||
|
Die Eingabemaske soll enthalten:
|
||||||
|
- Pflichtfelder
|
||||||
|
- Dropdowns
|
||||||
|
- Datumsauswahl
|
||||||
|
- Uhrzeitauswahl
|
||||||
|
- Textfelder für Notizen
|
||||||
|
- klare Trennung zwischen To-do und Kalender
|
||||||
|
## Datenqualität
|
||||||
|
- einheitliche Schreibweisen
|
||||||
|
- feste Statuswerte
|
||||||
|
- saubere Datumsformate
|
||||||
|
- keine freien Sonderformate in Pflichtfeldern
|
||||||
|
- keine Mischfelder für unterschiedliche Datentypen
|
||||||
|
""",
|
||||||
|
"docs/02_todo_calendar.md": """# To-do- und Kalenderkonzept
|
||||||
|
## Ziel
|
||||||
|
Dieses Projekt soll eine einfache Möglichkeit bieten, Aufgaben und Termine zentral zu erfassen.
|
||||||
|
## To-do-Felder
|
||||||
|
- id
|
||||||
|
- titel
|
||||||
|
- beschreibung
|
||||||
|
- kategorie
|
||||||
|
- prioritaet
|
||||||
|
- status
|
||||||
|
- faellig_am
|
||||||
|
- wiederholen
|
||||||
|
- notiz
|
||||||
|
## Kalender-Felder
|
||||||
|
- id
|
||||||
|
- titel
|
||||||
|
- datum
|
||||||
|
- uhrzeit
|
||||||
|
- dauer_min
|
||||||
|
- kalender
|
||||||
|
- kategorie
|
||||||
|
- erinnerung_min
|
||||||
|
- wiederholen
|
||||||
|
- notiz
|
||||||
|
## Dropdown-Werte
|
||||||
|
### Priorität
|
||||||
|
- Niedrig
|
||||||
|
- Mittel
|
||||||
|
- Hoch
|
||||||
|
### Status
|
||||||
|
- Offen
|
||||||
|
- In Arbeit
|
||||||
|
- Wartet
|
||||||
|
- Erledigt
|
||||||
|
### Wiederholen
|
||||||
|
- Nein
|
||||||
|
- Täglich
|
||||||
|
- Wöchentlich
|
||||||
|
- Monatlich
|
||||||
|
### Kalender
|
||||||
|
- Arbeit
|
||||||
|
- Privat
|
||||||
|
- Bitcoin
|
||||||
|
- Todo
|
||||||
|
### Kategorie
|
||||||
|
- Allgemein
|
||||||
|
- Dokumentation
|
||||||
|
- Erinnerung
|
||||||
|
- Termin
|
||||||
|
- Aufgabe
|
||||||
|
- Sonstiges
|
||||||
|
## Eingaberegeln
|
||||||
|
- Pflichtfelder müssen ausgefüllt sein
|
||||||
|
- freie Texte nur dort, wo sie sinnvoll sind
|
||||||
|
- Datum und Uhrzeit strikt getrennt
|
||||||
|
- Wiederholungen nur aus erlaubten Werten
|
||||||
|
- Kalenderziel muss auswählbar sein
|
||||||
|
## Workflow
|
||||||
|
1. Eintrag über Maske anlegen
|
||||||
|
2. Daten als CSV speichern
|
||||||
|
3. später nach SQLite importieren
|
||||||
|
4. optional an Google Calendar oder iCloud übergeben
|
||||||
|
""",
|
||||||
|
"sqlite/schema.sql": """PRAGMA foreign_keys = ON;
|
||||||
|
CREATE TABLE IF NOT EXISTS todo (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
titel TEXT NOT NULL,
|
||||||
|
beschreibung TEXT,
|
||||||
|
kategorie TEXT NOT NULL DEFAULT 'Allgemein',
|
||||||
|
prioritaet TEXT NOT NULL DEFAULT 'Mittel',
|
||||||
|
status TEXT NOT NULL DEFAULT 'Offen',
|
||||||
|
faellig_am TEXT,
|
||||||
|
wiederholen TEXT NOT NULL DEFAULT 'Nein',
|
||||||
|
notiz TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS calendar_event (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
titel TEXT NOT NULL,
|
||||||
|
datum TEXT NOT NULL,
|
||||||
|
uhrzeit TEXT NOT NULL,
|
||||||
|
dauer_min INTEGER NOT NULL DEFAULT 60,
|
||||||
|
kalender TEXT NOT NULL DEFAULT 'Privat',
|
||||||
|
kategorie TEXT NOT NULL DEFAULT 'Sonstiges',
|
||||||
|
erinnerung_min INTEGER NOT NULL DEFAULT 30,
|
||||||
|
wiederholen TEXT NOT NULL DEFAULT 'Nein',
|
||||||
|
notiz TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_todo_faellig_am ON todo(faellig_am);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_todo_status ON todo(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_calendar_event_datum ON calendar_event(datum);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_calendar_event_kalender ON calendar_event(kalender);
|
||||||
|
""",
|
||||||
|
"requirements.txt": """streamlit
|
||||||
|
pandas
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
APP_PY = """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")
|
||||||
|
"""
|
||||||
|
def run(cmd, check=True, capture=True):
|
||||||
|
return subprocess.run(cmd, cwd=REPO_PATH, check=check, text=True, capture_output=capture)
|
||||||
|
def git(*args, check=True):
|
||||||
|
return run(["git", *args], check=check)
|
||||||
|
def write_file(path, content):
|
||||||
|
full = REPO_PATH / path
|
||||||
|
full.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
full.write_text(content, encoding="utf-8")
|
||||||
|
print(f"OK wrote {path}")
|
||||||
|
def get_fetch_remote():
|
||||||
|
remotes = git("remote", "-v").stdout.strip().splitlines()
|
||||||
|
if not remotes:
|
||||||
|
raise SystemExit("No git remotes configured")
|
||||||
|
for line in remotes:
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) >= 3 and parts[2] == "(fetch)":
|
||||||
|
return parts[0], parts[1]
|
||||||
|
parts = remotes[0].split()
|
||||||
|
return parts[0], parts[1]
|
||||||
|
def get_repo_owner_name():
|
||||||
|
_, remote_url = get_fetch_remote()
|
||||||
|
if remote_url.endswith(".git"):
|
||||||
|
remote_url = remote_url[:-4]
|
||||||
|
if "://" in remote_url:
|
||||||
|
tail = remote_url.split("://", 1)[1]
|
||||||
|
tail = tail.split("/", 1)[1]
|
||||||
|
else:
|
||||||
|
tail = remote_url.split(":", 1)[1]
|
||||||
|
owner, repo = tail.split("/", 1)
|
||||||
|
return owner, repo
|
||||||
|
def main():
|
||||||
|
if not REPO_PATH.exists():
|
||||||
|
raise SystemExit(f"Repo path not found: {REPO_PATH}")
|
||||||
|
if not (REPO_PATH / ".git").exists():
|
||||||
|
raise SystemExit(f"No .git directory in {REPO_PATH}")
|
||||||
|
print("Repo path:", REPO_PATH)
|
||||||
|
print("Branch:", git("branch", "--show-current").stdout.strip())
|
||||||
|
print("Status:\n" + (git("status", "--short").stdout.strip() or "(clean)"))
|
||||||
|
print("Remotes:\n" + git("remote", "-v").stdout.strip())
|
||||||
|
remote_name, remote_url = get_fetch_remote()
|
||||||
|
print(f"Using remote: {remote_name} -> {remote_url}")
|
||||||
|
owner, repo = get_repo_owner_name()
|
||||||
|
print(f"Detected repo: {owner}/{repo}")
|
||||||
|
for path, content in FILES.items():
|
||||||
|
write_file(path, content)
|
||||||
|
write_file("app.py", APP_PY)
|
||||||
|
py = subprocess.run([sys.executable, "-m", "py_compile", "app.py"], cwd=REPO_PATH, text=True, capture_output=True)
|
||||||
|
if py.returncode != 0:
|
||||||
|
print(py.stdout)
|
||||||
|
print(py.stderr)
|
||||||
|
raise SystemExit("app.py syntax check failed")
|
||||||
|
git("add", "README.md", "docs", "sqlite", "requirements.txt", "app.py")
|
||||||
|
if git("diff", "--cached", "--quiet", check=False).returncode == 0:
|
||||||
|
print("No changes to commit.")
|
||||||
|
else:
|
||||||
|
git("commit", "-m", "docs: add todo-calendar workflow and streamlit form")
|
||||||
|
print("Committed.")
|
||||||
|
branch = git("branch", "--show-current").stdout.strip()
|
||||||
|
git("push", remote_name, branch)
|
||||||
|
print("Pushed to origin remote:", remote_name)
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+21
-41
@@ -1,52 +1,32 @@
|
|||||||
|
# Überblick
|
||||||
Überblick
|
|
||||||
|
|
||||||
Dieses Projekt ist ein lokales Arbeits- und Dokumentationsprojekt für persönliche Organisation.
|
Dieses Projekt ist ein lokales Arbeits- und Dokumentationsprojekt für persönliche Organisation.
|
||||||
|
## Zweck
|
||||||
Zweck
|
|
||||||
|
|
||||||
Das Projekt soll folgende Bereiche abdecken:
|
Das Projekt soll folgende Bereiche abdecken:
|
||||||
|
- To-dos
|
||||||
• To-dos
|
- Kalendertermine
|
||||||
• Kalendertermine
|
- Projektdokumentation
|
||||||
• Projektdokumentation
|
- später Bitcoin-Tracking
|
||||||
• später Bitcoin-Tracking
|
- später SQLite-basierte Auswertung
|
||||||
• später SQLite-basierte Auswertung
|
## Leitprinzipien
|
||||||
|
|
||||||
Leitprinzipien
|
|
||||||
|
|
||||||
1. Daten sauber strukturieren
|
1. Daten sauber strukturieren
|
||||||
2. Eingaben einfach halten
|
2. Eingaben einfach halten
|
||||||
3. CSV als Zwischenformat nutzbar machen
|
3. CSV als Zwischenformat nutzbar machen
|
||||||
4. später SQL-Backend ergänzen
|
4. später SQL-Backend ergänzen
|
||||||
5. lokale Kontrolle behalten
|
5. lokale Kontrolle behalten
|
||||||
|
## Arbeitsweise
|
||||||
Arbeitsweise
|
### 1. Erfassung
|
||||||
|
|
||||||
Es gibt zwei Ebenen:
|
|
||||||
|
|
||||||
1. Erfassung
|
|
||||||
|
|
||||||
Neue Einträge werden über eine Eingabemaske erfasst.
|
Neue Einträge werden über eine Eingabemaske erfasst.
|
||||||
|
### 2. Speicherung
|
||||||
2. Speicherung
|
|
||||||
|
|
||||||
Die Daten werden in CSV abgelegt und später in SQLite überführt.
|
Die Daten werden in CSV abgelegt und später in SQLite überführt.
|
||||||
|
## Zielbild
|
||||||
Zielbild
|
|
||||||
|
|
||||||
Am Ende soll das Projekt enthalten:
|
Am Ende soll das Projekt enthalten:
|
||||||
|
- eine einfache Eingabemaske
|
||||||
• eine einfache Eingabemaske
|
- eine saubere Dokumentation
|
||||||
• eine saubere Dokumentation
|
- exportierbare Daten
|
||||||
• exportierbare Daten
|
- ein lokales Dashboard
|
||||||
• ein lokales Dashboard
|
- Kalenderanbindung
|
||||||
• Kalenderanbindung
|
- To-do-Verwaltung
|
||||||
• To-do-Verwaltung
|
- später Bitcoin-Verwaltung
|
||||||
• später Bitcoin-Verwaltung
|
## Status
|
||||||
|
Der Fokus liegt aktuell auf Dokumentation, Kalender und To-do. Bitcoin-Tracking wird später ergänzt.
|
||||||
Status
|
|
||||||
|
|
||||||
Der Fokus liegt aktuell auf Dokumentation, Kalender und To-do.
|
|
||||||
Bitcoin-Tracking wird später ergänzt.
|
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Anforderungen
|
||||||
|
## Hauptziel
|
||||||
|
Das Projekt soll eine einfache und verlässliche Verwaltung von To-dos und Kalenderterminen ermöglichen.
|
||||||
|
## Funktionale Anforderungen
|
||||||
|
### To-dos
|
||||||
|
- To-dos sollen über eine Eingabemaske erfasst werden
|
||||||
|
- Pflichtfelder müssen vorhanden sein
|
||||||
|
- feste Werte sollen per Dropdown gewählt werden
|
||||||
|
- die Daten sollen als CSV exportierbar sein
|
||||||
|
### Kalender
|
||||||
|
- Kalendertermine sollen über eine Eingabemaske erfasst werden
|
||||||
|
- Datum und Uhrzeit müssen wählbar sein
|
||||||
|
- Terminarten sollen per Dropdown auswählbar sein
|
||||||
|
- Wiederholungen sollen unterstützt werden
|
||||||
|
- Erinnerungen sollen vorbereitet sein
|
||||||
|
### Datenhaltung
|
||||||
|
- CSV als Zwischenspeicher
|
||||||
|
- später SQLite als Datenbasis
|
||||||
|
- klare Trennung von Rohdaten und verarbeiteten Daten
|
||||||
|
## Nicht-funktionale Anforderungen
|
||||||
|
- lokale Nutzung ohne Cloud-Zwang
|
||||||
|
- später synchronisierbar mit Google Calendar oder iCloud
|
||||||
|
- leicht verständlich und wartbar
|
||||||
|
- importfreundlich für SQLite
|
||||||
|
- dokumentierbar und versionierbar
|
||||||
|
## UI-Anforderungen
|
||||||
|
Die Eingabemaske soll enthalten:
|
||||||
|
- Pflichtfelder
|
||||||
|
- Dropdowns
|
||||||
|
- Datumsauswahl
|
||||||
|
- Uhrzeitauswahl
|
||||||
|
- Textfelder für Notizen
|
||||||
|
- klare Trennung zwischen To-do und Kalender
|
||||||
|
## Datenqualität
|
||||||
|
- einheitliche Schreibweisen
|
||||||
|
- feste Statuswerte
|
||||||
|
- saubere Datumsformate
|
||||||
|
- keine freien Sonderformate in Pflichtfeldern
|
||||||
|
- keine Mischfelder für unterschiedliche Datentypen
|
||||||
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# To-do- und Kalenderkonzept
|
||||||
|
## Ziel
|
||||||
|
Dieses Projekt soll eine einfache Möglichkeit bieten, Aufgaben und Termine zentral zu erfassen.
|
||||||
|
## To-do-Felder
|
||||||
|
- id
|
||||||
|
- titel
|
||||||
|
- beschreibung
|
||||||
|
- kategorie
|
||||||
|
- prioritaet
|
||||||
|
- status
|
||||||
|
- faellig_am
|
||||||
|
- wiederholen
|
||||||
|
- notiz
|
||||||
|
## Kalender-Felder
|
||||||
|
- id
|
||||||
|
- titel
|
||||||
|
- datum
|
||||||
|
- uhrzeit
|
||||||
|
- dauer_min
|
||||||
|
- kalender
|
||||||
|
- kategorie
|
||||||
|
- erinnerung_min
|
||||||
|
- wiederholen
|
||||||
|
- notiz
|
||||||
|
## Dropdown-Werte
|
||||||
|
### Priorität
|
||||||
|
- Niedrig
|
||||||
|
- Mittel
|
||||||
|
- Hoch
|
||||||
|
### Status
|
||||||
|
- Offen
|
||||||
|
- In Arbeit
|
||||||
|
- Wartet
|
||||||
|
- Erledigt
|
||||||
|
### Wiederholen
|
||||||
|
- Nein
|
||||||
|
- Täglich
|
||||||
|
- Wöchentlich
|
||||||
|
- Monatlich
|
||||||
|
### Kalender
|
||||||
|
- Arbeit
|
||||||
|
- Privat
|
||||||
|
- Bitcoin
|
||||||
|
- Todo
|
||||||
|
### Kategorie
|
||||||
|
- Allgemein
|
||||||
|
- Dokumentation
|
||||||
|
- Erinnerung
|
||||||
|
- Termin
|
||||||
|
- Aufgabe
|
||||||
|
- Sonstiges
|
||||||
|
## Eingaberegeln
|
||||||
|
- Pflichtfelder müssen ausgefüllt sein
|
||||||
|
- freie Texte nur dort, wo sie sinnvoll sind
|
||||||
|
- Datum und Uhrzeit strikt getrennt
|
||||||
|
- Wiederholungen nur aus erlaubten Werten
|
||||||
|
- Kalenderziel muss auswählbar sein
|
||||||
|
## Workflow
|
||||||
|
1. Eintrag über Maske anlegen
|
||||||
|
2. Daten als CSV speichern
|
||||||
|
3. später nach SQLite importieren
|
||||||
|
4. optional an Google Calendar oder iCloud übergeben
|
||||||
|
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
streamlit
|
streamlit
|
||||||
pandas
|
pandas
|
||||||
|
|
||||||
+2
-2
@@ -1,5 +1,4 @@
|
|||||||
─ sql
|
PRAGMA foreign_keys = ON;
|
||||||
PRAGMA foreign_keys = ON;
|
|
||||||
CREATE TABLE IF NOT EXISTS todo (
|
CREATE TABLE IF NOT EXISTS todo (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
titel TEXT NOT NULL,
|
titel TEXT NOT NULL,
|
||||||
@@ -35,3 +34,4 @@
|
|||||||
CREATE INDEX IF NOT EXISTS idx_todo_status ON todo(status);
|
CREATE INDEX IF NOT EXISTS idx_todo_status ON todo(status);
|
||||||
CREATE INDEX IF NOT EXISTS idx_calendar_event_datum ON calendar_event(datum);
|
CREATE INDEX IF NOT EXISTS idx_calendar_event_datum ON calendar_event(datum);
|
||||||
CREATE INDEX IF NOT EXISTS idx_calendar_event_kalender ON calendar_event(kalender);
|
CREATE INDEX IF NOT EXISTS idx_calendar_event_kalender ON calendar_event(kalender);
|
||||||
|
|
||||||
@@ -0,0 +1,424 @@
|
|||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
|
from urllib.error import HTTPError, URLError
|
||||||
|
REPO_PATH = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path.cwd()
|
||||||
|
GITEA_BASE = os.environ.get("GITEA_BASE", "https://gitea.schaefinik.xyz")
|
||||||
|
GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "").strip()
|
||||||
|
FILES = {
|
||||||
|
"README.md": """# bitcoin-programm
|
||||||
|
Lokales Projekt zur strukturierten Erfassung und Verwaltung von:
|
||||||
|
- To-dos
|
||||||
|
- Kalendereinträgen
|
||||||
|
- Dokumentation
|
||||||
|
- später: Bitcoin-Transaktionen und Auswertungen
|
||||||
|
## Ziel
|
||||||
|
Das Projekt dient als zentrales Arbeitsverzeichnis für persönliche Verwaltung und spätere Automatisierung.
|
||||||
|
Geplant ist:
|
||||||
|
- eine einfache Eingabemaske für To-dos und Kalendertermine
|
||||||
|
- CSV als einfache Zwischen- und Exportform
|
||||||
|
- später ein SQLite-Backend für Auswertung und Dashboard
|
||||||
|
- optional Sync mit Google Calendar oder iCloud Kalender
|
||||||
|
## Aktueller Fokus
|
||||||
|
Der aktuelle Fokus liegt auf:
|
||||||
|
1. Dokumentation
|
||||||
|
2. To-do-Erfassung
|
||||||
|
3. Kalender-Erfassung
|
||||||
|
4. saubere Datenstruktur für späteren Import nach SQLite
|
||||||
|
Bitcoin-Tracking kann später ergänzt werden.
|
||||||
|
""",
|
||||||
|
"docs/00_overview.md": """# Überblick
|
||||||
|
Dieses Projekt ist ein lokales Arbeits- und Dokumentationsprojekt für persönliche Organisation.
|
||||||
|
## Zweck
|
||||||
|
Das Projekt soll folgende Bereiche abdecken:
|
||||||
|
- To-dos
|
||||||
|
- Kalendertermine
|
||||||
|
- Projektdokumentation
|
||||||
|
- später Bitcoin-Tracking
|
||||||
|
- später SQLite-basierte Auswertung
|
||||||
|
## Leitprinzipien
|
||||||
|
1. Daten sauber strukturieren
|
||||||
|
2. Eingaben einfach halten
|
||||||
|
3. CSV als Zwischenformat nutzbar machen
|
||||||
|
4. später SQL-Backend ergänzen
|
||||||
|
5. lokale Kontrolle behalten
|
||||||
|
## Arbeitsweise
|
||||||
|
### 1. Erfassung
|
||||||
|
Neue Einträge werden über eine Eingabemaske erfasst.
|
||||||
|
### 2. Speicherung
|
||||||
|
Die Daten werden in CSV abgelegt und später in SQLite überführt.
|
||||||
|
## Zielbild
|
||||||
|
Am Ende soll das Projekt enthalten:
|
||||||
|
- eine einfache Eingabemaske
|
||||||
|
- eine saubere Dokumentation
|
||||||
|
- exportierbare Daten
|
||||||
|
- ein lokales Dashboard
|
||||||
|
- Kalenderanbindung
|
||||||
|
- To-do-Verwaltung
|
||||||
|
- später Bitcoin-Verwaltung
|
||||||
|
## Status
|
||||||
|
Der Fokus liegt aktuell auf Dokumentation, Kalender und To-do. Bitcoin-Tracking wird später ergänzt.
|
||||||
|
""",
|
||||||
|
"docs/01_requirements.md": """# Anforderungen
|
||||||
|
## Hauptziel
|
||||||
|
Das Projekt soll eine einfache und verlässliche Verwaltung von To-dos und Kalenderterminen ermöglichen.
|
||||||
|
## Funktionale Anforderungen
|
||||||
|
### To-dos
|
||||||
|
- To-dos sollen über eine Eingabemaske erfasst werden
|
||||||
|
- Pflichtfelder müssen vorhanden sein
|
||||||
|
- feste Werte sollen per Dropdown gewählt werden
|
||||||
|
- die Daten sollen als CSV exportierbar sein
|
||||||
|
### Kalender
|
||||||
|
- Kalendertermine sollen über eine Eingabemaske erfasst werden
|
||||||
|
- Datum und Uhrzeit müssen wählbar sein
|
||||||
|
- Terminarten sollen per Dropdown auswählbar sein
|
||||||
|
- Wiederholungen sollen unterstützt werden
|
||||||
|
- Erinnerungen sollen vorbereitet sein
|
||||||
|
### Datenhaltung
|
||||||
|
- CSV als Zwischenspeicher
|
||||||
|
- später SQLite als Datenbasis
|
||||||
|
- klare Trennung von Rohdaten und verarbeiteten Daten
|
||||||
|
## Nicht-funktionale Anforderungen
|
||||||
|
- lokale Nutzung ohne Cloud-Zwang
|
||||||
|
- später synchronisierbar mit Google Calendar oder iCloud
|
||||||
|
- leicht verständlich und wartbar
|
||||||
|
- importfreundlich für SQLite
|
||||||
|
- dokumentierbar und versionierbar
|
||||||
|
## UI-Anforderungen
|
||||||
|
Die Eingabemaske soll enthalten:
|
||||||
|
- Pflichtfelder
|
||||||
|
- Dropdowns
|
||||||
|
- Datumsauswahl
|
||||||
|
- Uhrzeitauswahl
|
||||||
|
- Textfelder für Notizen
|
||||||
|
- klare Trennung zwischen To-do und Kalender
|
||||||
|
## Datenqualität
|
||||||
|
- einheitliche Schreibweisen
|
||||||
|
- feste Statuswerte
|
||||||
|
- saubere Datumsformate
|
||||||
|
- keine freien Sonderformate in Pflichtfeldern
|
||||||
|
- keine Mischfelder für unterschiedliche Datentypen
|
||||||
|
""",
|
||||||
|
"docs/02_todo_calendar.md": """# To-do- und Kalenderkonzept
|
||||||
|
## Ziel
|
||||||
|
Dieses Projekt soll eine einfache Möglichkeit bieten, Aufgaben und Termine zentral zu erfassen.
|
||||||
|
## To-do-Felder
|
||||||
|
- id
|
||||||
|
- titel
|
||||||
|
- beschreibung
|
||||||
|
- kategorie
|
||||||
|
- prioritaet
|
||||||
|
- status
|
||||||
|
## Kalender-Felder
|
||||||
|
• später synchronisierbar mit Google Calendar oder iCloud
|
||||||
|
• leicht verständlich und wartbar
|
||||||
|
• importfreundlich für SQLite
|
||||||
|
• dokumentierbar und versionierbar
|
||||||
|
|
||||||
|
UI-Anforderungen
|
||||||
|
|
||||||
|
Die Eingabemaske soll enthalten:
|
||||||
|
|
||||||
|
• Pflichtfelder
|
||||||
|
• Dropdowns
|
||||||
|
• Datumsauswahl
|
||||||
|
• Uhrzeitauswahl
|
||||||
|
• Textfelder für Notizen
|
||||||
|
• klare Trennung zwischen To-do und Kalender
|
||||||
|
|
||||||
|
Datenqualität
|
||||||
|
|
||||||
|
• einheitliche Schreibweisen
|
||||||
|
• feste Statuswerte
|
||||||
|
• saubere Datumsformate
|
||||||
|
• keine freien Sonderformate in Pflichtfeldern
|
||||||
|
• keine Mischfelder für unterschiedliche Datentypen
|
||||||
|
|
||||||
|
""",
|
||||||
|
"docs/02_todo_calendar.md": """# To-do- und Kalenderkonzept
|
||||||
|
|
||||||
|
Ziel
|
||||||
|
|
||||||
|
Dieses Projekt soll eine einfache Möglichkeit bieten, Aufgaben und Termine zentral zu erfassen.
|
||||||
|
|
||||||
|
To-do-Felder
|
||||||
|
|
||||||
|
• id
|
||||||
|
• titel
|
||||||
|
• beschreibung
|
||||||
|
• kategorie
|
||||||
|
• prioritaet
|
||||||
|
• status
|
||||||
|
• faellig_am
|
||||||
|
• wiederholen
|
||||||
|
• notiz
|
||||||
|
|
||||||
|
Kalender-Felder
|
||||||
|
|
||||||
|
• id
|
||||||
|
• titel
|
||||||
|
• datum
|
||||||
|
• uhrzeit
|
||||||
|
• dauer_min
|
||||||
|
• kalender
|
||||||
|
• kategorie
|
||||||
|
• erinnerung_min
|
||||||
|
• wiederholen
|
||||||
|
• notiz
|
||||||
|
|
||||||
|
Dropdown-Werte
|
||||||
|
|
||||||
|
Priorität
|
||||||
|
|
||||||
|
• Niedrig
|
||||||
|
• Mittel
|
||||||
|
• Hoch
|
||||||
|
|
||||||
|
Status
|
||||||
|
|
||||||
|
• Offen
|
||||||
|
• In Arbeit
|
||||||
|
Kategorie
|
||||||
|
|
||||||
|
• Pflichtfelder müssen ausgefüllt sein
|
||||||
|
2. Daten als CSV speichern
|
||||||
|
beschreibung TEXT,
|
||||||
|
faellig_am TEXT,
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT, (
|
||||||
|
|
||||||
|
"requirements.txt": """streamlit
|
||||||
|
|
||||||
|
|
||||||
|
return 1
|
||||||
|
ig(page_title="Bitcoin-Programm", layout="wide")
|
||||||
|
st.set_page_config(page_title="Bitcoin-Programm", layout="wide") nt(kalender);
|
||||||
|
faellig_am = st.date_input("Fällig am", value=date.today())
|
||||||
|
|
||||||
|
"id": new_todo_id,
|
||||||
|
"faellig_am": faellig_am.isoformat(),
|
||||||
|
st.markdown("### To-dos")
|
||||||
|
st.download_button("Kalender CSV herunterladen", data=cal_df.to_csv(index=False).encode("utf-8"),
|
||||||
|
tail = tail.split("/", 1)[1]
|
||||||
|
tail = remote_url.split(":", 1)[1]
|
||||||
|
prioritaetPTEXTeNOTtNULL DEFAULT 'Mittel', ("utf-8"),
|
||||||
|
status TEXT NOT NULL DEFAULTt'Offen',und: {REPO_PATH}")
|
||||||
|
faellig_am TEXT,
|
||||||
|
wiederholen TEXT NOT NULL DEFAULT 'Nein', {REPO_PATH}")
|
||||||
|
notiz TEXT,
|
||||||
|
prcreated_atpTEXT"NOTENULLADEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULTs(datetime('now'))out.strip()) de("utf-8"),
|
||||||
|
); "(clean)"))
|
||||||
|
CREATE TABLE IF NOT EXISTS calendar_event (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
retitelnTEXT NOToNULL,l = get_fetch_remote()
|
||||||
|
datum TEXT NOT NULL, te_url}") utput=capture)
|
||||||
|
uhrzeit TEXT NOT NULL,
|
||||||
|
dauer_min INTEGER NOT NULL DEFAULT}60,
|
||||||
|
kalender TEXT NOT NULL DEFAULT 'Privat',
|
||||||
|
fokategorieoTEXTtNOT NULLSDEFAULT)'Sonstiges',
|
||||||
|
erinnerung_min INTEGER NOT NULL DEFAULT 30,
|
||||||
|
wiederholen TEXT NOT NULL DEFAULT 'Nein',
|
||||||
|
notiz TEXT,
|
||||||
|
pycreated_ateTEXTuNOTsNULLxDEFAULTe(datetime('now')),e", "app.py"], cwd=REPO_PATH, text=True,
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
); != 0:
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL"app.py syntax check failed")
|
||||||
|
);
|
||||||
|
CREATE"INDEX IFENOTEEXISTS"idx_todo_faellig_ameONitodo(faellig_am);p.py")
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_todo_status ON todo(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_calendar_event_datum ON calendar_event(datum);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_calendar_event_kalender ON calendar_event(kalender);
|
||||||
|
""", it("commit", "-m", "docs: add todo-calendar workflow and streamlit form")
|
||||||
|
"requirements.txt": """streamlit
|
||||||
|
pandas
|
||||||
|
"""ranch = git("branch", "--show-current").stdout.strip()
|
||||||
|
}
|
||||||
|
APP_PY = """import streamlit as st, remote_name)
|
||||||
|
import pandas as pd
|
||||||
|
iffromapathlib importnPath
|
||||||
|
from datetime import date, time
|
||||||
|
─ ◉ DATA_DIR = Path("data") e
|
||||||
|
TODO_FILE = DATA_DIR / "todo.csv"
|
||||||
|
CALENDAR_FILEz=zDATA_DIRe/a"calendar.csv"
|
||||||
|
DATA_DIR.modir(parents=True, exist_ok=True)rn dein benannter Remote `bitcoin-programm`
|
||||||
|
TODO_COLUMNS = [ t a d t e
|
||||||
|
─ ( "id","titel","beschreibung","kategorie","prioritaet","status", e
|
||||||
|
─ "faellig_am","wiederholen","notiz" e
|
||||||
|
]-
|
||||||
|
CALENDAR_COLUMNSe=u[g / Kontext-Zusammenfassung
|
||||||
|
P "id","titel","datum","uhrzeit","dauer_min","kalender","kategorie",
|
||||||
|
─ ٩ "erinnerung_min","wiederholen","notiz"o-dos, Kalender und später Bitcoin-Tracking {"title
|
||||||
|
─ ( ] e : T o e b s L ext=True, {"title
|
||||||
|
bit def load_csv(pathe Path, columns: list[str]) -> pd.DataFrame: ` chaefinik.xyz/admin/bitcoin`
|
||||||
|
─ ( if path.exists(): e
|
||||||
|
**Sicherdfi=spd.read_csv(path)cht im Chat, nur via `GITEA_TOKEN` in der Host-Umgebung
|
||||||
|
─ ( for col in columns: e r u s i r d r pt statt direkter e
|
||||||
|
if col not in df.columns:
|
||||||
|
ridf[col]c=i"" lokal ausführen, dann `git status`/Commit/Push prüfen
|
||||||
|
return df[columns] n o n k r Z nfassung für
|
||||||
|
─ ( • return pd.DatmFrame(columns=columns) e E e {"title
|
||||||
|
def save_csv(df: p .DataFram , path: Path) -> None:
|
||||||
|
df.to_csv(path, index=False)
|
||||||
|
─ (´・def)next_id(df: pd.DataFrame)p->5int:ini none │ 94k/272k │ [████░░░░░░] 35% │ 2h 20m ─ {"title
|
||||||
|
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")
|
||||||
|
"""
|
||||||
|
def run(cmd, check=True, capture=True):
|
||||||
|
return subprocess.run(cmd, cwd=REPO_PATH, check=check, text=True, capture_output=capture)
|
||||||
|
def git(*args, check=True):
|
||||||
|
return run(["git", *args], check=check)
|
||||||
|
def write_file(path, content):
|
||||||
|
full = REPO_PATH / path
|
||||||
|
full.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
full.write_text(content, encoding="utf-8")
|
||||||
|
print(f"OK wrote {path}")
|
||||||
|
def get_fetch_remote():
|
||||||
|
remotes = git("remote", "-v").stdout.strip().splitlines()
|
||||||
|
if not remotes:
|
||||||
|
raise SystemExit("No git remotes configured")
|
||||||
|
for line in remotes:
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) >= 3 and parts[2] == "(fetch)":
|
||||||
|
return parts[0], parts[1]
|
||||||
|
parts = remotes[0].split()
|
||||||
|
return parts[0], parts[1]
|
||||||
|
def get_repo_owner_name():
|
||||||
|
_, remote_url = get_fetch_remote()
|
||||||
|
if remote_url.endswith(".git"):
|
||||||
|
remote_url = remote_url[:-4]
|
||||||
|
if "://" in remote_url:
|
||||||
|
tail = remote_url.split("://", 1)[1]
|
||||||
|
tail = tail.split("/", 1)[1]
|
||||||
|
else:
|
||||||
|
tail = remote_url.split(":", 1)[1]
|
||||||
|
owner, repo = tail.split("/", 1)
|
||||||
|
return owner, repo
|
||||||
|
def main():
|
||||||
|
if not REPO_PATH.exists():
|
||||||
|
raise SystemExit(f"Repo path not found: {REPO_PATH}")
|
||||||
|
if not (REPO_PATH / ".git").exists():
|
||||||
|
raise SystemExit(f"No .git directory in {REPO_PATH}")
|
||||||
|
print("Repo path:", REPO_PATH)
|
||||||
|
print("Branch:", git("branch", "--show-current").stdout.strip())
|
||||||
|
print("Status:\n" + (git("status", "--short").stdout.strip() or "(clean)"))
|
||||||
|
print("Remotes:\n" + git("remote", "-v").stdout.strip())
|
||||||
|
remote_name, remote_url = get_fetch_remote()
|
||||||
|
print(f"Using remote: {remote_name} -> {remote_url}")
|
||||||
|
owner, repo = get_repo_owner_name()
|
||||||
|
print(f"Detected repo: {owner}/{repo}")
|
||||||
|
for path, content in FILES.items():
|
||||||
|
write_file(path, content)
|
||||||
|
write_file("app.py", APP_PY)
|
||||||
|
py = subprocess.run([sys.executable, "-m", "py_compile", "app.py"], cwd=REPO_PATH, text=True,
|
||||||
|
capture_output=True)
|
||||||
|
if py.returncode != 0:
|
||||||
|
print(py.stdout)
|
||||||
|
print(py.stderr)
|
||||||
|
raise SystemExit("app.py syntax check failed")
|
||||||
|
git("add", "README.md", "docs", "sqlite", "requirements.txt", "app.py")
|
||||||
|
if git("diff", "--cached", "--quiet", check=False).returncode == 0:
|
||||||
|
print("No changes to commit.")
|
||||||
|
else:
|
||||||
|
git("commit", "-m", "docs: add todo-calendar workflow and streamlit form")
|
||||||
|
print("Committed.")
|
||||||
|
branch = git("branch", "--show-current").stdout.strip()
|
||||||
|
git("push", remote_name, branch)
|
||||||
|
print("Pushed to origin remote:", remote_name)
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user