167 lines
6.4 KiB
Python
167 lines
6.4 KiB
Python
#!/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()))
|