EmberClone/scripts/phase17_features.py

167 lines
6.4 KiB
Python
Raw Permalink 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-17: drag-drop-reorder, calendar-month, batch-rename, customer-merge, smart-filter-suggestions."""
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
PHASE17_STATE = ROOT / ".phase17-state.json"
FEATURES: list[Feature] = [
Feature(
name="calendar-month-view",
description="Monatsansicht für TimeEntries (Grid 6 weeks × 7 days)",
files=[
FileGen(
path="apps/web/src/pages/Calendar.tsx",
purpose=(
"ERWEITERT — behalte Week-View. Füge View-Toggle (Week / Month). "
"Month-View: Grid 7 Spalten, 6 Reihen, jeder Cell zeigt Tagesnummer + total-hours Badge. "
"Vor/zurück-Buttons für Monat-Navigation. Klick auf Day-Cell wechselt zu Week-View dieser Woche."
),
refs=["apps/web/src/pages/Calendar.tsx"],
),
],
),
Feature(
name="batch-rename-projects",
description="Bulk-Select + rename Projects via Mutation",
files=[
FileGen(
path="apps/web/src/pages/Projects.tsx",
purpose=(
"ERWEITERT — füge Checkbox-Spalte. Wenn min 1 selektiert, Action-Bar oben: "
"'Rename mit Prefix...' Input → bei Submit ruft api.bulkRenameProjects(ids, prefix). "
"Plus Bulk-Delete-Button."
),
refs=["apps/web/src/pages/Projects.tsx"],
),
FileGen(
path="apps/api/src/routes/projects.ts",
purpose=(
"ERWEITERT — füge POST /bulk-rename (body: {ids, prefix}): updated jedes Project's name = prefix + ' ' + currentName. "
"Plus POST /bulk-delete (ids[]) → delete all. Behalte alles."
),
refs=["apps/api/src/routes/projects.ts"],
),
],
),
Feature(
name="customer-merge",
description="Merge zwei Customers: source-Projects auf target umhängen, dann source löschen",
files=[
FileGen(
path="apps/api/src/routes/customers.ts",
purpose=(
"ERWEITERT — füge POST /:id/merge (body: {targetId}): "
"alle projects vom source-customer (id) bekommen targetId als customerId, dann delete source-customer. "
"Admin-only. Behalte alles."
),
refs=["apps/api/src/routes/customers.ts"],
),
FileGen(
path="apps/web/src/pages/Customers.tsx",
purpose=(
"ERWEITERT — Merge-Button pro Row (admin-only). Modal mit Target-Customer-Picker, Submit → "
"api.mergeCustomers(sourceId, targetId), refetch."
),
refs=["apps/web/src/pages/Customers.tsx"],
),
],
),
Feature(
name="smart-filter-suggestions",
description="Saved-Views-Vorschläge basierend auf häufig benutzten Filters",
files=[
FileGen(
path="apps/web/src/components/SmartFilters.tsx",
purpose=(
"SmartFilters-Component. Zeigt 3-4 vorgeschlagene Filter-Buttons: "
"'Diese Woche', 'Letzter Monat', 'Heute', 'Nur ohne Projekt'. "
"Props: onApply(filterObject). Klick wendet Filter sofort an."
),
),
FileGen(
path="apps/web/src/pages/TimeEntries.tsx",
purpose=(
"ERWEITERT — behalte alles. Füge <SmartFilters onApply={setFilters} /> oberhalb Filter-Bar."
),
refs=["apps/web/src/pages/TimeEntries.tsx"],
),
],
),
Feature(
name="time-entry-quick-edit",
description="Inline-Edit für TimeEntry-Description (Klick auf Description = Input)",
files=[
FileGen(
path="apps/web/src/pages/TimeEntries.tsx",
purpose=(
"ERWEITERT — in der Liste: Description-Zelle wird beim Klick zu Input (useState editingId). "
"On Blur oder Enter: api.updateTimeEntry(id, {description}). Cancel via Escape."
),
refs=["apps/web/src/pages/TimeEntries.tsx"],
),
],
),
Feature(
name="api-client-phase17",
description="API um bulk-rename, customer-merge erweitert",
files=[
FileGen(
path="apps/web/src/lib/api.ts",
purpose=(
"ERWEITERT — behalte ALLES. Füge: bulkRenameProjects(ids, prefix), bulkDeleteProjects(ids), "
"mergeCustomers(sourceId, targetId)."
),
refs=["apps/web/src/lib/api.ts"],
),
],
),
]
def load_state() -> dict:
if PHASE17_STATE.exists():
return json.loads(PHASE17_STATE.read_text())
return {"completed_features": [], "current_feature": None, "started_at": datetime.datetime.now().isoformat()}
def save_state(state: dict) -> None:
PHASE17_STATE.write_text(json.dumps(state, indent=2))
async def main() -> int:
log_section("🚀 Phase-17 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-17 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()))