153 lines
5.9 KiB
Python
153 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Phase-13: undo-toast, breadcrumb, in-app-changelog, ARIA, kpi-comparison."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import datetime
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from phase2_features import Feature, FileGen, ROOT, log, log_section # noqa: E402
|
|
from phase3_features import run_feature_v2 # noqa: E402
|
|
|
|
PHASE13_STATE = ROOT / ".phase13-state.json"
|
|
|
|
FEATURES: list[Feature] = [
|
|
Feature(
|
|
name="undo-toast",
|
|
description="Toast mit Undo-Action für deletes",
|
|
files=[
|
|
FileGen(
|
|
path="apps/web/src/components/Toast.tsx",
|
|
purpose=(
|
|
"ERWEITERT — behalte alle bestehenden Methoden (success/error/info). Füge eine: "
|
|
"undoable({message, onUndo}) — zeigt Toast mit message + 'Rückgängig'-Button, "
|
|
"wenn Klick: ruft onUndo, schließt Toast. Auto-dismiss nach 8s (länger als normal)."
|
|
),
|
|
refs=["apps/web/src/components/Toast.tsx"],
|
|
),
|
|
],
|
|
),
|
|
Feature(
|
|
name="breadcrumb-navigation",
|
|
description="Breadcrumb-Komponente, top der Detail-Pages",
|
|
files=[
|
|
FileGen(
|
|
path="apps/web/src/components/Breadcrumb.tsx",
|
|
purpose=(
|
|
"Breadcrumb-Component. Props: items: [{label, to?}] (last item ohne to = current page). "
|
|
"Render: Home > Customers > Customer-Name als Text-Links + chevron. "
|
|
"Tailwind text-sm text-gray-500 with hover-darker."
|
|
),
|
|
),
|
|
FileGen(
|
|
path="apps/web/src/pages/CustomerDetail.tsx",
|
|
purpose=(
|
|
"ERWEITERT — behalte alles. Füge <Breadcrumb items={[{label:'Dashboard',to:'/'},{label:'Customers',to:'/customers'},{label:customer.name}]} /> "
|
|
"ganz oben."
|
|
),
|
|
refs=["apps/web/src/pages/CustomerDetail.tsx"],
|
|
),
|
|
FileGen(
|
|
path="apps/web/src/pages/ProjectDetail.tsx",
|
|
purpose=(
|
|
"ERWEITERT — behalte alles. Füge analog Breadcrumb mit Projects-Pfad."
|
|
),
|
|
refs=["apps/web/src/pages/ProjectDetail.tsx"],
|
|
),
|
|
],
|
|
),
|
|
Feature(
|
|
name="in-app-changelog",
|
|
description="Changelog-Modal mit Versions-History",
|
|
files=[
|
|
FileGen(
|
|
path="apps/web/src/components/ChangelogModal.tsx",
|
|
purpose=(
|
|
"Changelog-Modal. Inline-Array CHANGELOG = [{version, date, changes: [strings]}] mit "
|
|
"5 fake Einträgen (z.B. 0.0.1 Initial release, 0.0.2 added Projects, etc). "
|
|
"Trigger via prop {open, onClose}. Tailwind fixed inset-0 + centered card mit Versionsliste."
|
|
),
|
|
),
|
|
FileGen(
|
|
path="apps/web/src/components/VersionBadge.tsx",
|
|
purpose=(
|
|
"ERWEITERT — behalte VersionBadge. Klick öffnet jetzt <ChangelogModal /> (useState open). "
|
|
"Statt new-tab-Link."
|
|
),
|
|
refs=["apps/web/src/components/VersionBadge.tsx"],
|
|
),
|
|
],
|
|
),
|
|
Feature(
|
|
name="aria-improvements",
|
|
description="Accessibility-Improvements (ARIA labels) in Nav + Forms",
|
|
files=[
|
|
FileGen(
|
|
path="apps/web/src/components/Nav.tsx",
|
|
purpose=(
|
|
"ERWEITERT — füge aria-label zu allen icon-only-Buttons (Theme-Toggle, Lang-Toggle, Logout, CommandPalette-trigger). "
|
|
"role='navigation' aufs nav-element. Active-Link mit aria-current='page'. "
|
|
"Behalte alles, nur a11y polish."
|
|
),
|
|
refs=["apps/web/src/components/Nav.tsx"],
|
|
),
|
|
],
|
|
),
|
|
Feature(
|
|
name="kpi-comparison",
|
|
description="Dashboard KPI-Karten mit Vergleich zur Vorwoche",
|
|
files=[
|
|
FileGen(
|
|
path="apps/web/src/pages/Dashboard.tsx",
|
|
purpose=(
|
|
"ERWEITERT — behalte alles (Stats-Cards, Chart, ActivityFeed, Export-Button). "
|
|
"Pro Stats-Karte: zusätzliche Mini-Zeile 'vs. Vorwoche: +12%' (grün) oder '-5%' (rot). "
|
|
"Berechnung: fetch time-entries letzte 14 Tage, split current/previous week, calc delta."
|
|
),
|
|
refs=["apps/web/src/pages/Dashboard.tsx"],
|
|
),
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
def load_state() -> dict:
|
|
if PHASE13_STATE.exists():
|
|
return json.loads(PHASE13_STATE.read_text())
|
|
return {"completed_features": [], "current_feature": None, "started_at": datetime.datetime.now().isoformat()}
|
|
|
|
|
|
def save_state(state: dict) -> None:
|
|
PHASE13_STATE.write_text(json.dumps(state, indent=2))
|
|
|
|
|
|
async def main() -> int:
|
|
log_section("🚀 Phase-13 Codegen-Run gestartet")
|
|
state = load_state()
|
|
for feature in FEATURES:
|
|
if feature.name in state.get("completed_features", []):
|
|
continue
|
|
state["current_feature"] = feature.name; save_state(state)
|
|
try:
|
|
success = await run_feature_v2(feature)
|
|
if success:
|
|
state.setdefault("completed_features", []).append(feature.name)
|
|
else:
|
|
state.setdefault("attempted_features", []).append(feature.name)
|
|
save_state(state)
|
|
except Exception as e:
|
|
log(f"❌ {feature.name} crashed: {e}", level="ERROR")
|
|
state.setdefault("attempted_features", []).append(feature.name); save_state(state)
|
|
|
|
log_section("Phase-13 Run beendet")
|
|
log(f"OK: {len(state.get('completed_features', []))}, Attempted: {len(state.get('attempted_features', []))}, Total: {len(FEATURES)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(asyncio.run(main()))
|