EmberClone/scripts/phase28_features.py

121 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Phase-28: share-link, customer-contact, billing-rate, entry-clone, breadcrumbs."""
from __future__ import annotations
import asyncio, datetime, json, sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from phase2_features import Feature, FileGen, ROOT, log, log_section
from phase3_features import run_feature_v2
PHASE_STATE = ROOT / ".phase28-state.json"
FEATURES: list[Feature] = [
Feature(
name="customer-contact-info",
description="Customers: contactEmail + contactPhone optional fields",
files=[FileGen(
path="apps/api/src/db/schema.ts",
purpose=(
"WICHTIG: BEHALTE ALLE bestehenden Tabellen und Spalten. Füge nur 2 Spalten zu customers: "
"`contactEmail: text('contact_email')` (nullable), `contactPhone: text('contact_phone')` (nullable). "
"BEHALTE: alle Tabellen inkl. apiKeys, savedViews, webhooks, auditLog, appSettings, documents, passwordResetTokens, invitations, holidays."
),
refs=["apps/api/src/db/schema.ts"],
), FileGen(
path="apps/web/src/pages/Customers.tsx",
purpose=(
"ERWEITERT — füge Email/Phone Inputs ins Create-Form (optional). Zeige als kleine grey Zeile unter Name in Liste."
),
refs=["apps/web/src/pages/Customers.tsx"],
)],
),
Feature(
name="project-billing-rate",
description="Project bekommt billingRate (€/h)",
files=[FileGen(
path="apps/api/src/db/schema.ts",
purpose=(
"WICHTIG: BEHALTE alle Tabellen + Spalten. Füge nur Spalte `billingRate: integer('billing_rate')` (nullable, Cent-Werte) zu projects."
),
refs=["apps/api/src/db/schema.ts"],
), FileGen(
path="apps/web/src/pages/Projects.tsx",
purpose=(
"ERWEITERT — füge Billing-Rate-Input (€/h) ins Create-Form. Speichert in Cent (×100). Anzeige in Liste."
),
refs=["apps/web/src/pages/Projects.tsx"],
)],
),
Feature(
name="time-entry-clone",
description="Clone-Button pro TimeEntry (copy + reset time)",
files=[FileGen(
path="apps/web/src/pages/TimeEntries.tsx",
purpose=(
"ERWEITERT — füge Clone-Icon (Copy lucide-react) pro entry-row. Klick: api.createTimeEntry mit description/projectId vom original aber startTime=now. "
"Behalte alles."
),
refs=["apps/web/src/pages/TimeEntries.tsx"],
)],
),
Feature(
name="breadcrumbs-everywhere",
description="Breadcrumb auf allen List-Pages",
files=[FileGen(
path="apps/web/src/pages/Customers.tsx",
purpose=(
"ERWEITERT — füge <Breadcrumb items={[{label:'Dashboard',to:'/'},{label:'Customers'}]} /> ganz oben. Behalte alles."
),
refs=["apps/web/src/pages/Customers.tsx"],
), FileGen(
path="apps/web/src/pages/Projects.tsx",
purpose=(
"ERWEITERT — füge <Breadcrumb items={[{label:'Dashboard',to:'/'},{label:'Projects'}]} /> ganz oben. Behalte alles."
),
refs=["apps/web/src/pages/Projects.tsx"],
), FileGen(
path="apps/web/src/pages/TimeEntries.tsx",
purpose=(
"ERWEITERT — füge <Breadcrumb items={[{label:'Dashboard',to:'/'},{label:'Time Entries'}]} /> oben. Behalte alles."
),
refs=["apps/web/src/pages/TimeEntries.tsx"],
)],
),
]
def load_state():
if PHASE_STATE.exists():
return json.loads(PHASE_STATE.read_text())
return {"completed_features": [], "current_feature": None, "started_at": datetime.datetime.now().isoformat()}
def save_state(state):
PHASE_STATE.write_text(json.dumps(state, indent=2))
async def main():
log_section("🚀 Phase-28 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)
state.setdefault("completed_features" if success else "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-28 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()))