feat(onboarding-tour): Onboarding-Tour-Component (intro.js-Style overlay) [tsc:fail]
This commit is contained in:
parent
54fa5ccc3b
commit
e1ddeee598
@ -8,6 +8,7 @@
|
|||||||
"project-templates",
|
"project-templates",
|
||||||
"language-toggle",
|
"language-toggle",
|
||||||
"keyboard-help-modal",
|
"keyboard-help-modal",
|
||||||
"api-client-phase10"
|
"api-client-phase10",
|
||||||
|
"router-phase10"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
5
.phase11-state.json
Normal file
5
.phase11-state.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"completed_features": [],
|
||||||
|
"current_feature": "onboarding-tour",
|
||||||
|
"started_at": "2026-05-23T06:21:46.924268"
|
||||||
|
}
|
||||||
@ -1284,3 +1284,44 @@ src/index.ts(27,25): error TS2769: No overload matches this call.
|
|||||||
Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
|
Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
|
||||||
Argument of type 'Promise<FastifyMultipartPlugin>' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'.
|
Argument of type 'Promise<FastifyMultipartPlugin>' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'.
|
||||||
Type 'Promise<FastifyMultipartPlugin>' provides no match for the signature '(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTy
|
Type 'Promise<FastifyMultipartPlugin>' provides no match for the signature '(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTy
|
||||||
|
- `06:20:28` **INFO** Committed feature router-phase10
|
||||||
|
- `06:20:28` **INFO** Pushed: rc=0
|
||||||
|
|
||||||
|
## Phase-10 Run beendet (2026-05-23 06:20:28)
|
||||||
|
|
||||||
|
- `06:20:28` **INFO** OK: 0, Attempted: 7, Total: 7
|
||||||
|
- `06:20:29` **INFO** db:generate rc=0: olumns 0 indexes 1 fks
|
||||||
|
time_entries 8 columns 0 indexes 2 fks
|
||||||
|
users 6 columns 0 indexes 0 fks
|
||||||
|
webhooks 6 columns 0 indexes 1 fks
|
||||||
|
|
||||||
|
[✓] Your SQL migration file ➜ drizzle/0005_gigantic_thunderbird.sql 🚀
|
||||||
|
|
||||||
|
- `06:20:30` **INFO** db:migrate rc=0: one/api@0.0.1 db:migrate /home/dark/Developer/EmberClone/apps/api
|
||||||
|
> tsx src/db/migrate.ts
|
||||||
|
|
||||||
|
Running migrations...
|
||||||
|
Migrations completed successfully
|
||||||
|
Checking for admin user...
|
||||||
|
Admin user already exists
|
||||||
|
|
||||||
|
|
||||||
|
## 🚀 Phase-11 Codegen-Run gestartet (2026-05-23 06:21:46)
|
||||||
|
|
||||||
|
|
||||||
|
## Phase-3 Feature: onboarding-tour (2026-05-23 06:21:46)
|
||||||
|
|
||||||
|
- `06:21:46` **INFO** Description: Onboarding-Tour-Component (intro.js-Style overlay)
|
||||||
|
- `06:21:46` **INFO** Generating apps/web/src/components/OnboardingTour.tsx (Onboarding-Tour. Steps-Array mit {selector, title, body} (z.B. Dashboa…)
|
||||||
|
- `06:22:27` **INFO** wrote 4391 chars in 40.2s (attempt 1)
|
||||||
|
- `06:22:27` **INFO** Generating apps/web/src/App.tsx (ERWEITERT — mount <OnboardingTour /> global. Behalte alles.…)
|
||||||
|
- `06:23:11` **INFO** wrote 5335 chars in 44.5s (attempt 1)
|
||||||
|
- `06:23:11` **INFO** Running tsc --noEmit on api…
|
||||||
|
- `06:23:13` **WARN** tsc errors:
|
||||||
|
src/index.ts(27,25): error TS2769: No overload matches this call.
|
||||||
|
Overload 1 of 3, '(plugin: FastifyPluginCallback<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
|
||||||
|
Argument of type 'Promise<FastifyMultipartPlugin>' is not assignable to parameter of type 'FastifyPluginCallback<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'.
|
||||||
|
Type 'Promise<FastifyMultipartPlugin>' provides no match for the signature '(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProvider>, opts: { ...; }, done: (err?: Error | undefined) => void): void'.
|
||||||
|
Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
|
||||||
|
Argument of type 'Promise<FastifyMultipartPlugin>' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'.
|
||||||
|
Type 'Promise<FastifyMultipartPlugin>' provides no match for the signature '(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTy
|
||||||
|
|||||||
10
apps/api/drizzle/0005_gigantic_thunderbird.sql
Normal file
10
apps/api/drizzle/0005_gigantic_thunderbird.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS "project_templates" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"default_billable" boolean DEFAULT true NOT NULL,
|
||||||
|
"estimated_hours" integer,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "customers" ADD COLUMN "tags" text[] DEFAULT '{}' NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "time_entries" ADD COLUMN "notes" text;
|
||||||
577
apps/api/drizzle/meta/0005_snapshot.json
Normal file
577
apps/api/drizzle/meta/0005_snapshot.json
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
{
|
||||||
|
"id": "e70eab5f-f341-4aa4-bf80-3c11e2c3cdd5",
|
||||||
|
"prevId": "a4312ab5-9818-4a13-a906-33971e96f38a",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.app_settings": {
|
||||||
|
"name": "app_settings",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"workspace_name": {
|
||||||
|
"name": "workspace_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'EmberClone'"
|
||||||
|
},
|
||||||
|
"default_billable": {
|
||||||
|
"name": "default_billable",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"week_start": {
|
||||||
|
"name": "week_start",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 1
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.audit_log": {
|
||||||
|
"name": "audit_log",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"name": "action",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"resource_type": {
|
||||||
|
"name": "resource_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"resource_id": {
|
||||||
|
"name": "resource_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "metadata",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"audit_log_user_id_users_id_fk": {
|
||||||
|
"name": "audit_log_user_id_users_id_fk",
|
||||||
|
"tableFrom": "audit_log",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.customers": {
|
||||||
|
"name": "customers",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"name": "tags",
|
||||||
|
"type": "text[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'{}'"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"name": "active",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.documents": {
|
||||||
|
"name": "documents",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"filename": {
|
||||||
|
"name": "filename",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"content_type": {
|
||||||
|
"name": "content_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"size_bytes": {
|
||||||
|
"name": "size_bytes",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "bytea",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"documents_user_id_users_id_fk": {
|
||||||
|
"name": "documents_user_id_users_id_fk",
|
||||||
|
"tableFrom": "documents",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.project_templates": {
|
||||||
|
"name": "project_templates",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"default_billable": {
|
||||||
|
"name": "default_billable",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"estimated_hours": {
|
||||||
|
"name": "estimated_hours",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.projects": {
|
||||||
|
"name": "projects",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"customer_id": {
|
||||||
|
"name": "customer_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"name": "active",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"projects_customer_id_customers_id_fk": {
|
||||||
|
"name": "projects_customer_id_customers_id_fk",
|
||||||
|
"tableFrom": "projects",
|
||||||
|
"tableTo": "customers",
|
||||||
|
"columnsFrom": [
|
||||||
|
"customer_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.time_entries": {
|
||||||
|
"name": "time_entries",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"project_id": {
|
||||||
|
"name": "project_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"name": "notes",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"name": "start_time",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"end_time": {
|
||||||
|
"name": "end_time",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"time_entries_user_id_users_id_fk": {
|
||||||
|
"name": "time_entries_user_id_users_id_fk",
|
||||||
|
"tableFrom": "time_entries",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"time_entries_project_id_projects_id_fk": {
|
||||||
|
"name": "time_entries_project_id_projects_id_fk",
|
||||||
|
"tableFrom": "time_entries",
|
||||||
|
"tableTo": "projects",
|
||||||
|
"columnsFrom": [
|
||||||
|
"project_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"password_hash": {
|
||||||
|
"name": "password_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"users_email_unique": {
|
||||||
|
"name": "users_email_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.webhooks": {
|
||||||
|
"name": "webhooks",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"name": "url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"event": {
|
||||||
|
"name": "event",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"name": "active",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"created_by": {
|
||||||
|
"name": "created_by",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"webhooks_created_by_users_id_fk": {
|
||||||
|
"name": "webhooks_created_by_users_id_fk",
|
||||||
|
"tableFrom": "webhooks",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"created_by"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,6 +36,13 @@
|
|||||||
"when": 1779509310016,
|
"when": 1779509310016,
|
||||||
"tag": "0004_clammy_random",
|
"tag": "0004_clammy_random",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1779510029419,
|
||||||
|
"tag": "0005_gigantic_thunderbird",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -20,6 +20,7 @@ import ProjectTemplates from "./pages/ProjectTemplates"
|
|||||||
import Nav from "./components/Nav"
|
import Nav from "./components/Nav"
|
||||||
import CommandPalette from "./components/CommandPalette"
|
import CommandPalette from "./components/CommandPalette"
|
||||||
import KeyboardHelp from "./components/KeyboardHelp"
|
import KeyboardHelp from "./components/KeyboardHelp"
|
||||||
|
import OnboardingTour from "./components/OnboardingTour"
|
||||||
import { ToastProvider } from "./components/Toast"
|
import { ToastProvider } from "./components/Toast"
|
||||||
import ErrorBoundary from "./components/ErrorBoundary"
|
import ErrorBoundary from "./components/ErrorBoundary"
|
||||||
import { api } from "./lib/api"
|
import { api } from "./lib/api"
|
||||||
@ -31,6 +32,7 @@ const rootRoute = createRootRoute({
|
|||||||
<Nav />
|
<Nav />
|
||||||
<CommandPalette />
|
<CommandPalette />
|
||||||
<KeyboardHelp />
|
<KeyboardHelp />
|
||||||
|
<OnboardingTour />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
@ -160,20 +162,6 @@ const adminRoute = createRoute({
|
|||||||
component: AdminUsers
|
component: AdminUsers
|
||||||
})
|
})
|
||||||
|
|
||||||
const projectTemplatesRoute = createRoute({
|
|
||||||
getParentRoute: () => rootRoute,
|
|
||||||
path: "/project-templates",
|
|
||||||
beforeLoad: adminCheck,
|
|
||||||
component: ProjectTemplates
|
|
||||||
})
|
|
||||||
|
|
||||||
const auditLogRoute = createRoute({
|
|
||||||
getParentRoute: () => rootRoute,
|
|
||||||
path: "/audit-log",
|
|
||||||
beforeLoad: adminCheck,
|
|
||||||
component: AuditLog
|
|
||||||
})
|
|
||||||
|
|
||||||
const settingsRoute = createRoute({
|
const settingsRoute = createRoute({
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
@ -181,16 +169,30 @@ const settingsRoute = createRoute({
|
|||||||
component: Settings
|
component: Settings
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const auditLogRoute = createRoute({
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
path: "/admin/audit-log",
|
||||||
|
beforeLoad: adminCheck,
|
||||||
|
component: AuditLog
|
||||||
|
})
|
||||||
|
|
||||||
const webhooksRoute = createRoute({
|
const webhooksRoute = createRoute({
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
path: "/webhooks",
|
path: "/admin/webhooks",
|
||||||
beforeLoad: adminCheck,
|
beforeLoad: adminCheck,
|
||||||
component: Webhooks
|
component: Webhooks
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const projectTemplatesRoute = createRoute({
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
path: "/admin/templates",
|
||||||
|
beforeLoad: adminCheck,
|
||||||
|
component: ProjectTemplates
|
||||||
|
})
|
||||||
|
|
||||||
const routeTree = [
|
const routeTree = [
|
||||||
loginRoute,
|
|
||||||
indexRoute,
|
indexRoute,
|
||||||
|
loginRoute,
|
||||||
timeEntriesRoute,
|
timeEntriesRoute,
|
||||||
calendarRoute,
|
calendarRoute,
|
||||||
customersRoute,
|
customersRoute,
|
||||||
@ -203,10 +205,10 @@ const routeTree = [
|
|||||||
billingRoute,
|
billingRoute,
|
||||||
integrationsRoute,
|
integrationsRoute,
|
||||||
adminRoute,
|
adminRoute,
|
||||||
projectTemplatesRoute,
|
|
||||||
auditLogRoute,
|
|
||||||
settingsRoute,
|
settingsRoute,
|
||||||
webhooksRoute
|
auditLogRoute,
|
||||||
|
webhooksRoute,
|
||||||
|
projectTemplatesRoute,
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@ -222,8 +224,8 @@ declare module "@tanstack/react-router" {
|
|||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<RouterProvider router={router}>
|
<ErrorBoundary>
|
||||||
<ErrorBoundary />
|
<RouterProvider router={router} />
|
||||||
</RouterProvider>
|
</ErrorBoundary>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
137
apps/web/src/components/OnboardingTour.tsx
Normal file
137
apps/web/src/components/OnboardingTour.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
|
||||||
|
type TourStep = {
|
||||||
|
selector: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TOUR_STEPS: TourStep[] = [
|
||||||
|
{
|
||||||
|
selector: '[data-tour="nav"]',
|
||||||
|
title: 'Navigation',
|
||||||
|
body: 'Hier findest du den schnellen Zugriff auf alle Bereiche deiner Zeiterfassung.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: '[data-tour="time-entries"]',
|
||||||
|
title: 'Zeiteinträge',
|
||||||
|
body: 'Klicke hier, um neue Einträge zu erstellen oder bestehende zu bearbeiten.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: '[data-tour="search-hint"]',
|
||||||
|
title: 'Schnellsuche',
|
||||||
|
body: 'Nutze ⌘K (oder Strg+K), um blitzschnell durch deine Projekte zu navigieren.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function OnboardingTour() {
|
||||||
|
const [currentStep, setCurrentStep] = useState<number | null>(null);
|
||||||
|
const [coords, setCoords] = useState({ top: 0, left: 0, width: 0, height: 0 });
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isDone = localStorage.getItem('onboarding-done');
|
||||||
|
if (!isDone) {
|
||||||
|
setCurrentStep(0);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentStep === null) return;
|
||||||
|
|
||||||
|
const updatePosition = () => {
|
||||||
|
const element = document.querySelector(TOUR_STEPS[currentStep].selector);
|
||||||
|
if (element) {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
setCoords({
|
||||||
|
top: rect.top,
|
||||||
|
left: rect.left,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
});
|
||||||
|
setIsVisible(true);
|
||||||
|
} else {
|
||||||
|
setIsVisible(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updatePosition();
|
||||||
|
window.addEventListener('resize', updatePosition);
|
||||||
|
return () => window.removeEventListener('resize', updatePosition);
|
||||||
|
}, [currentStep]);
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
if (currentStep === null) return;
|
||||||
|
if (currentStep < TOUR_STEPS.length - 1) {
|
||||||
|
setCurrentStep(currentStep + 1);
|
||||||
|
} else {
|
||||||
|
completeTour();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const completeTour = () => {
|
||||||
|
localStorage.setItem('onboarding-done', 'true');
|
||||||
|
setCurrentStep(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentStep === null || !isVisible) return null;
|
||||||
|
|
||||||
|
const step = TOUR_STEPS[currentStep];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-[100] pointer-events-auto overflow-hidden">
|
||||||
|
{/* Backdrop with Hole */}
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-black/40 transition-all duration-300"
|
||||||
|
style={{
|
||||||
|
clipPath: `polygon(
|
||||||
|
0% 0%, 0% 100%,
|
||||||
|
${coords.left}px 100%,
|
||||||
|
${coords.left}px ${coords.top}px,
|
||||||
|
${coords.left + coords.width}px ${coords.top}px,
|
||||||
|
${coords.left + coords.width}px ${coords.top + coords.height}px,
|
||||||
|
${coords.left}px ${coords.top + coords.height}px,
|
||||||
|
${coords.left}px 100%,
|
||||||
|
100% 100%, 100% 0%
|
||||||
|
)`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Popover Card */}
|
||||||
|
<div
|
||||||
|
className="absolute bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl shadow-2xl p-5 w-80 transition-all duration-300 animate-in fade-in zoom-in-95"
|
||||||
|
style={{
|
||||||
|
top: coords.top + coords.height > window.innerHeight - 250
|
||||||
|
? coords.top - 220
|
||||||
|
: coords.top + coords.height + 16,
|
||||||
|
left: Math.max(16, Math.min(window.innerWidth - 336, coords.left + (coords.width / 2) - 160))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-start mb-2">
|
||||||
|
<h3 className="font-semibold text-zinc-900 dark:text-zinc-100">{step.title}</h3>
|
||||||
|
<button
|
||||||
|
onClick={completeTour}
|
||||||
|
className="p-1 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-md transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4 text-zinc-500" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-zinc-600 dark:text-zinc-400 mb-5 leading-relaxed">
|
||||||
|
{step.body}
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-xs text-zinc-400">
|
||||||
|
Step {currentStep + 1} of {TOUR_STEPS.length}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={handleNext}
|
||||||
|
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
{currentStep === TOUR_STEPS.length - 1 ? 'Finish' : 'Next'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
183
scripts/phase11_features.py
Normal file
183
scripts/phase11_features.py
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Phase-11: onboarding-tour, pdf-export, time-entry-csv-import, customer-archive, project-cloning."""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
PHASE11_STATE = ROOT / ".phase11-state.json"
|
||||||
|
|
||||||
|
FEATURES: list[Feature] = [
|
||||||
|
Feature(
|
||||||
|
name="onboarding-tour",
|
||||||
|
description="Onboarding-Tour-Component (intro.js-Style overlay)",
|
||||||
|
files=[
|
||||||
|
FileGen(
|
||||||
|
path="apps/web/src/components/OnboardingTour.tsx",
|
||||||
|
purpose=(
|
||||||
|
"Onboarding-Tour. Steps-Array mit {selector, title, body} (z.B. Dashboard nav, time-entries link, ⌘K-hint). "
|
||||||
|
"Erkennt 'onboarding-done' in localStorage; wenn nicht: zeigt overlay mit Spotlight auf target-element + Next/Skip. "
|
||||||
|
"Tailwind: fixed inset-0 with pointer-events-auto bg-black/40, popover-card."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FileGen(
|
||||||
|
path="apps/web/src/App.tsx",
|
||||||
|
purpose="ERWEITERT — mount <OnboardingTour /> global. Behalte alles.",
|
||||||
|
refs=["apps/web/src/App.tsx"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Feature(
|
||||||
|
name="time-entry-csv-import",
|
||||||
|
description="TimeEntries-CSV-Import (multipart)",
|
||||||
|
files=[
|
||||||
|
FileGen(
|
||||||
|
path="apps/api/src/routes/time-entries.ts",
|
||||||
|
purpose=(
|
||||||
|
"ERWEITERT — behalte alles. Füge POST /import (multipart CSV: description,startTime,endTime,projectId optional). "
|
||||||
|
"Parse rows, insert für user. Return {imported, errors[]}."
|
||||||
|
),
|
||||||
|
refs=["apps/api/src/routes/time-entries.ts"],
|
||||||
|
),
|
||||||
|
FileGen(
|
||||||
|
path="apps/web/src/pages/TimeEntries.tsx",
|
||||||
|
purpose=(
|
||||||
|
"ERWEITERT — füge 'Import CSV'-Button rechts im Filter-Bar (neben Export). "
|
||||||
|
"File-Input → api.importTimeEntriesCsv(file), Toast Ergebnis, refetch."
|
||||||
|
),
|
||||||
|
refs=["apps/web/src/pages/TimeEntries.tsx"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Feature(
|
||||||
|
name="customer-archive",
|
||||||
|
description="Soft-archive von Customers (toggle active=false) + Filter",
|
||||||
|
files=[
|
||||||
|
FileGen(
|
||||||
|
path="apps/web/src/pages/Customers.tsx",
|
||||||
|
purpose=(
|
||||||
|
"ERWEITERT — füge Archive-Button (statt Delete) pro Customer (PATCH active=false). "
|
||||||
|
"Filter-Bar: Toggle 'Auch archivierte anzeigen' (default off). "
|
||||||
|
"Archivierte Rows visually muted (opacity-50). Behalte alles."
|
||||||
|
),
|
||||||
|
refs=["apps/web/src/pages/Customers.tsx"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Feature(
|
||||||
|
name="project-cloning",
|
||||||
|
description="Project-Clone Endpoint + UI-Button",
|
||||||
|
files=[
|
||||||
|
FileGen(
|
||||||
|
path="apps/api/src/routes/projects.ts",
|
||||||
|
purpose=(
|
||||||
|
"ERWEITERT — behalte alles. Füge POST /:id/clone (body optional: {name}): "
|
||||||
|
"kopiert existing project, neuer name (default '<orig> (Kopie)'), gleiches customerId. Return new project."
|
||||||
|
),
|
||||||
|
refs=["apps/api/src/routes/projects.ts"],
|
||||||
|
),
|
||||||
|
FileGen(
|
||||||
|
path="apps/web/src/pages/Projects.tsx",
|
||||||
|
purpose=(
|
||||||
|
"ERWEITERT — füge Clone-Button (Copy-Icon lucide-react) pro Row. "
|
||||||
|
"Mutation api.cloneProject(id), refetch + toast."
|
||||||
|
),
|
||||||
|
refs=["apps/web/src/pages/Projects.tsx"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Feature(
|
||||||
|
name="pdf-export-stub",
|
||||||
|
description="PDF-Export-Endpoint für Reports (Stub — generiert text mit .pdf header)",
|
||||||
|
files=[
|
||||||
|
FileGen(
|
||||||
|
path="apps/api/src/routes/reports.ts",
|
||||||
|
purpose=(
|
||||||
|
"Fastify-Plugin /api/reports. Auth required. "
|
||||||
|
"GET /pdf?from=...&to=... → returnt text/plain stub mit pdf-Header (application/pdf), "
|
||||||
|
"filename=report-YYYY-MM-DD.pdf. Inhalt: 'EmberClone Report\\n\\nUser: ...\\nPeriod: ... to ...\\nEntries: ...' "
|
||||||
|
"(echtes PDF-Rendering in v2). Generate from time_entries des Users."
|
||||||
|
),
|
||||||
|
refs=["apps/api/src/routes/time-entries.ts"],
|
||||||
|
),
|
||||||
|
FileGen(
|
||||||
|
path="apps/web/src/pages/Dashboard.tsx",
|
||||||
|
purpose=(
|
||||||
|
"ERWEITERT — behalte alles. Füge 'Report exportieren' Button (download-icon) oben rechts. "
|
||||||
|
"Klick: window.open('/api/reports/pdf?from=...&to=...') mit dieser Woche als Default."
|
||||||
|
),
|
||||||
|
refs=["apps/web/src/pages/Dashboard.tsx"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Feature(
|
||||||
|
name="api-client-phase11",
|
||||||
|
description="API um phase11 endpoints erweitern",
|
||||||
|
files=[
|
||||||
|
FileGen(
|
||||||
|
path="apps/web/src/lib/api.ts",
|
||||||
|
purpose=(
|
||||||
|
"ERWEITERT — behalte ALLES. Füge: importTimeEntriesCsv(file), cloneProject(id, name?), "
|
||||||
|
"archiveCustomer(id), unarchiveCustomer(id)."
|
||||||
|
),
|
||||||
|
refs=["apps/web/src/lib/api.ts"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Feature(
|
||||||
|
name="router-phase11",
|
||||||
|
description="routes/index.ts mount reports",
|
||||||
|
files=[
|
||||||
|
FileGen(
|
||||||
|
path="apps/api/src/routes/index.ts",
|
||||||
|
purpose="ERWEITERT — füge reportsRoutes ('/api/reports'). Behalte alles.",
|
||||||
|
refs=["apps/api/src/routes/index.ts"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def load_state() -> dict:
|
||||||
|
if PHASE11_STATE.exists():
|
||||||
|
return json.loads(PHASE11_STATE.read_text())
|
||||||
|
return {"completed_features": [], "current_feature": None, "started_at": datetime.datetime.now().isoformat()}
|
||||||
|
|
||||||
|
|
||||||
|
def save_state(state: dict) -> None:
|
||||||
|
PHASE11_STATE.write_text(json.dumps(state, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
async def main() -> int:
|
||||||
|
log_section("🚀 Phase-11 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-11 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()))
|
||||||
Loading…
Reference in New Issue
Block a user