From 9b308639fda31d6ca895516cfb6edbe7b86dfec3 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Sat, 25 Apr 2026 18:45:40 +0200 Subject: [PATCH] feat(admin/ui): three-section merge plan UI + plan/apply proxy endpoints (D6) --- web/src/lib/api/types.ts | 24 + .../lib/components/admin/ResearchPanel.svelte | 460 +++++++++++++----- .../maerkte/[id]/bearbeiten/+page.svelte | 156 ++---- .../[id]/bearbeiten/research-apply/+server.ts | 22 + .../[id]/bearbeiten/research-plan/+server.ts | 18 + 5 files changed, 454 insertions(+), 226 deletions(-) create mode 100644 web/src/routes/admin/maerkte/[id]/bearbeiten/research-apply/+server.ts create mode 100644 web/src/routes/admin/maerkte/[id]/bearbeiten/research-plan/+server.ts diff --git a/web/src/lib/api/types.ts b/web/src/lib/api/types.ts index 630bc7b..d169aed 100644 --- a/web/src/lib/api/types.ts +++ b/web/src/lib/api/types.ts @@ -252,6 +252,30 @@ export interface FieldSuggestion { reason: string; } +export interface FieldMerge { + field: string; + current: unknown; + suggested: unknown; + confidence: 'high' | 'medium'; + reason: string; + decision: 'auto_apply' | 'review' | 'rejected'; + decision_reason: string; + validation?: 'ok' | 'warn' | 'fail'; +} + +export interface MergePlan { + auto_apply: FieldMerge[]; + review_required: FieldMerge[]; + rejected: FieldMerge[]; + cross_warnings: string[]; + generated_at: string; +} + +export interface PlanResponse { + plan: MergePlan; + research_result: ResearchResult; +} + // Duplicate detection export interface DuplicateMarket { id: string; diff --git a/web/src/lib/components/admin/ResearchPanel.svelte b/web/src/lib/components/admin/ResearchPanel.svelte index 959b595..e64efdf 100644 --- a/web/src/lib/components/admin/ResearchPanel.svelte +++ b/web/src/lib/components/admin/ResearchPanel.svelte @@ -1,17 +1,21 @@
- {#if result.suggestions.length === 0} -

Keine Vorschläge gefunden.

- {:else} -
- {#each result.suggestions as suggestion, i} - - {/each} -
- -
- - + {#if result.plan.cross_warnings.length > 0} +
+

Hinweise

+
    + {#each result.plan.cross_warnings as warning} +
  • {warning}
  • + {/each} +
{/if} - {#if result.sources && result.sources.length > 0} + {#if totalSelectable === 0 && result.plan.rejected.length === 0} +

Keine Vorschläge gefunden.

+ {:else} +
+ + {#if result.plan.auto_apply.length > 0} +
+

+ Auto-übernehmen + + Automatisch + +

+
+ {#each result.plan.auto_apply as item, i} + + {/each} +
+
+ {/if} + + + {#if result.plan.review_required.length > 0} +
+

+ Prüfen + + Prüfen + +

+
+ {#each result.plan.review_required as item, i} + + {/each} +
+
+ {/if} + + + {#if result.plan.rejected.length > 0} +
+

+ Abgelehnt + + Abgelehnt + +

+
+ {#each result.plan.rejected as item} +
+ +
+
+ + {fieldLabels[item.field] ?? item.field} + + + {item.confidence === 'high' ? 'Hoch' : 'Mittel'} + +
+ {#if item.current !== null && item.current !== undefined} +
+ Aktuell: {formatValue(item.current, item.field)} +
+ {/if} +
+ {formatValue(item.suggested, item.field)} +
+ {#if item.decision_reason} +
+ {item.decision_reason} +
+ {/if} +
+
+ {/each} +
+
+ {/if} +
+ + {#if totalSelectable > 0} +
+ + +
+ {/if} + {/if} + + {#if result.research_result.sources && result.research_result.sources.length > 0}

Quellen:

-
{ - researching = true; - return async ({ update }) => { - researching = false; - await update(); - }; - }} - > - -
- {#if form?.error && !form?.research} + + {#if planError} +

{planError}

+ {:else if form?.error && !form?.research}

{form.error}

{/if}
- {#if researchResult} + {#if planResult} { - researchResult = null; - dismissed = true; + planResult = null; }} /> {/if} diff --git a/web/src/routes/admin/maerkte/[id]/bearbeiten/research-apply/+server.ts b/web/src/routes/admin/maerkte/[id]/bearbeiten/research-apply/+server.ts new file mode 100644 index 0000000..0a221f0 --- /dev/null +++ b/web/src/routes/admin/maerkte/[id]/bearbeiten/research-apply/+server.ts @@ -0,0 +1,22 @@ +import { json } from '@sveltejs/kit'; +import { serverFetch } from '$lib/api/client.server.js'; +import type { AdminMarketDetail, ResearchResult } from '$lib/api/types.js'; +import type { RequestHandler } from './$types.js'; + +export const POST: RequestHandler = async ({ cookies, params, request }) => { + try { + const body = (await request.json()) as { research_result: ResearchResult; fields: string[] }; + const res = await serverFetch( + `/admin/markets/${params.id}/research/apply`, + cookies, + { + method: 'POST', + body: JSON.stringify(body) + } + ); + return json(res.data); + } catch (err) { + const message = err instanceof Error ? err.message : 'Anwenden fehlgeschlagen.'; + return json({ error: message }, { status: 502 }); + } +}; diff --git a/web/src/routes/admin/maerkte/[id]/bearbeiten/research-plan/+server.ts b/web/src/routes/admin/maerkte/[id]/bearbeiten/research-plan/+server.ts new file mode 100644 index 0000000..17131e1 --- /dev/null +++ b/web/src/routes/admin/maerkte/[id]/bearbeiten/research-plan/+server.ts @@ -0,0 +1,18 @@ +import { json } from '@sveltejs/kit'; +import { serverFetch } from '$lib/api/client.server.js'; +import type { PlanResponse } from '$lib/api/types.js'; +import type { RequestHandler } from './$types.js'; + +export const POST: RequestHandler = async ({ cookies, params }) => { + try { + const res = await serverFetch( + `/admin/markets/${params.id}/research/plan`, + cookies, + { method: 'POST' } + ); + return json(res.data); + } catch (err) { + const message = err instanceof Error ? err.message : 'Recherche-Plan fehlgeschlagen.'; + return json({ error: message }, { status: 502 }); + } +};