feat(frontend): update generated API types and Geschichte routes for JourneyItem model
- api.ts: add GeschichteType, JourneyItem, GeschichteSummary schemas; remove documentId param from list endpoint; change list response to GeschichteSummary[]; add type + items to Geschichte; remove documents field - GeschichteEditor: remove DocumentMultiSelect + documentIds from payload (journey items are managed via the future Lesereisen editor, not here) - GET /geschichten page: remove documentId filter from server load + URL logic - geschichten/new: remove documentId pre-population from server load - geschichten/[id]: replace g.documents with g.items (document-backed JourneyItems) - geschichten/new + [id]/edit: remove documentIds from submit payload type Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2016,7 +2016,6 @@ export interface components {
|
|||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
status?: "DRAFT" | "PUBLISHED";
|
status?: "DRAFT" | "PUBLISHED";
|
||||||
personIds?: string[];
|
personIds?: string[];
|
||||||
documentIds?: string[];
|
|
||||||
};
|
};
|
||||||
Geschichte: {
|
Geschichte: {
|
||||||
/** Format: uuid */
|
/** Format: uuid */
|
||||||
@@ -2025,9 +2024,11 @@ export interface components {
|
|||||||
body?: string;
|
body?: string;
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
status: "DRAFT" | "PUBLISHED";
|
status: "DRAFT" | "PUBLISHED";
|
||||||
|
/** @enum {string} */
|
||||||
|
type: "STORY" | "JOURNEY";
|
||||||
author?: components["schemas"]["AppUser"];
|
author?: components["schemas"]["AppUser"];
|
||||||
persons?: components["schemas"]["Person"][];
|
persons?: components["schemas"]["Person"][];
|
||||||
documents?: components["schemas"]["Document"][];
|
items?: components["schemas"]["JourneyItem"][];
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
@@ -2035,6 +2036,32 @@ export interface components {
|
|||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
publishedAt?: string;
|
publishedAt?: string;
|
||||||
};
|
};
|
||||||
|
JourneyItem: {
|
||||||
|
/** Format: uuid */
|
||||||
|
id: string;
|
||||||
|
/** Format: int32 */
|
||||||
|
position: number;
|
||||||
|
/** Format: uuid */
|
||||||
|
documentId?: string;
|
||||||
|
note?: string;
|
||||||
|
};
|
||||||
|
GeschichteSummary: {
|
||||||
|
/** Format: uuid */
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
/** @enum {string} */
|
||||||
|
status: "DRAFT" | "PUBLISHED";
|
||||||
|
/** @enum {string} */
|
||||||
|
type: "STORY" | "JOURNEY";
|
||||||
|
author?: {
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
body?: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
publishedAt?: string;
|
||||||
|
};
|
||||||
CreateTranscriptionBlockDTO: {
|
CreateTranscriptionBlockDTO: {
|
||||||
/** Format: int32 */
|
/** Format: int32 */
|
||||||
pageNumber?: number;
|
pageNumber?: number;
|
||||||
@@ -3576,7 +3603,6 @@ export interface operations {
|
|||||||
query?: {
|
query?: {
|
||||||
status?: "DRAFT" | "PUBLISHED";
|
status?: "DRAFT" | "PUBLISHED";
|
||||||
personId?: string[];
|
personId?: string[];
|
||||||
documentId?: string;
|
|
||||||
limit?: number;
|
limit?: number;
|
||||||
};
|
};
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -3591,7 +3617,7 @@ export interface operations {
|
|||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"*/*": components["schemas"]["Geschichte"][];
|
"*/*": components["schemas"]["GeschichteSummary"][];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,33 +6,23 @@ import StarterKit from '@tiptap/starter-kit';
|
|||||||
import { m } from '$lib/paraglide/messages.js';
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
import type { components } from '$lib/generated/api';
|
import type { components } from '$lib/generated/api';
|
||||||
import PersonMultiSelect from '$lib/person/PersonMultiSelect.svelte';
|
import PersonMultiSelect from '$lib/person/PersonMultiSelect.svelte';
|
||||||
import DocumentMultiSelect from '$lib/document/DocumentMultiSelect.svelte';
|
|
||||||
|
|
||||||
type Geschichte = components['schemas']['Geschichte'];
|
type Geschichte = components['schemas']['Geschichte'];
|
||||||
type Person = components['schemas']['Person'];
|
type Person = components['schemas']['Person'];
|
||||||
type Document = components['schemas']['Document'];
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
geschichte?: Geschichte | null;
|
geschichte?: Geschichte | null;
|
||||||
initialPersons?: Person[];
|
initialPersons?: Person[];
|
||||||
initialDocuments?: Document[];
|
|
||||||
onSubmit: (payload: {
|
onSubmit: (payload: {
|
||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
status: 'DRAFT' | 'PUBLISHED';
|
status: 'DRAFT' | 'PUBLISHED';
|
||||||
personIds: string[];
|
personIds: string[];
|
||||||
documentIds: string[];
|
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
submitting?: boolean;
|
submitting?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let { geschichte = null, initialPersons = [], onSubmit, submitting = false }: Props = $props();
|
||||||
geschichte = null,
|
|
||||||
initialPersons = [],
|
|
||||||
initialDocuments = [],
|
|
||||||
onSubmit,
|
|
||||||
submitting = false
|
|
||||||
}: Props = $props();
|
|
||||||
|
|
||||||
// Initial-state snapshot from incoming props. The editor owns these values
|
// Initial-state snapshot from incoming props. The editor owns these values
|
||||||
// after mount; the parent should re-mount the component with a different
|
// after mount; the parent should re-mount the component with a different
|
||||||
@@ -44,9 +34,6 @@ let status: 'DRAFT' | 'PUBLISHED' = $state(geschichte?.status ?? 'DRAFT');
|
|||||||
let selectedPersons: Person[] = $state(
|
let selectedPersons: Person[] = $state(
|
||||||
geschichte?.persons ? Array.from(geschichte.persons) : initialPersons
|
geschichte?.persons ? Array.from(geschichte.persons) : initialPersons
|
||||||
);
|
);
|
||||||
let selectedDocuments: Document[] = $state(
|
|
||||||
geschichte?.documents ? Array.from(geschichte.documents) : initialDocuments
|
|
||||||
);
|
|
||||||
|
|
||||||
let dirty = $state(false);
|
let dirty = $state(false);
|
||||||
let titleTouched = $state(false);
|
let titleTouched = $state(false);
|
||||||
@@ -122,8 +109,7 @@ async function save(nextStatus: 'DRAFT' | 'PUBLISHED') {
|
|||||||
title: title.trim(),
|
title: title.trim(),
|
||||||
body,
|
body,
|
||||||
status: nextStatus,
|
status: nextStatus,
|
||||||
personIds: selectedPersons.map((p) => p.id!).filter(Boolean),
|
personIds: selectedPersons.map((p) => p.id!).filter(Boolean)
|
||||||
documentIds: selectedDocuments.map((d) => d.id!).filter(Boolean)
|
|
||||||
});
|
});
|
||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
@@ -269,14 +255,6 @@ function exec(action: () => void) {
|
|||||||
<p class="mb-3 font-sans text-xs text-ink-3">{m.geschichte_editor_personen_hint()}</p>
|
<p class="mb-3 font-sans text-xs text-ink-3">{m.geschichte_editor_personen_hint()}</p>
|
||||||
<PersonMultiSelect bind:selectedPersons={selectedPersons} />
|
<PersonMultiSelect bind:selectedPersons={selectedPersons} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="rounded border border-line bg-surface p-4 shadow-sm">
|
|
||||||
<h2 class="mb-2 font-sans text-xs font-bold tracking-widest text-ink-3 uppercase">
|
|
||||||
{m.geschichte_editor_dokumente_heading()}
|
|
||||||
</h2>
|
|
||||||
<p class="mb-3 font-sans text-xs text-ink-3">{m.geschichte_editor_dokumente_hint()}</p>
|
|
||||||
<DocumentMultiSelect bind:selectedDocuments={selectedDocuments} />
|
|
||||||
</section>
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -9,15 +9,13 @@ type Person = components['schemas']['Person'];
|
|||||||
export const load: PageServerLoad = async ({ url, fetch }) => {
|
export const load: PageServerLoad = async ({ url, fetch }) => {
|
||||||
const api = createApiClient(fetch);
|
const api = createApiClient(fetch);
|
||||||
const personIds = url.searchParams.getAll('personId');
|
const personIds = url.searchParams.getAll('personId');
|
||||||
const documentId = url.searchParams.get('documentId') ?? undefined;
|
|
||||||
|
|
||||||
const [listResult, ...personResults] = await Promise.all([
|
const [listResult, ...personResults] = await Promise.all([
|
||||||
api.GET('/api/geschichten', {
|
api.GET('/api/geschichten', {
|
||||||
params: {
|
params: {
|
||||||
query: {
|
query: {
|
||||||
status: 'PUBLISHED',
|
status: 'PUBLISHED',
|
||||||
personId: personIds.length ? personIds : undefined,
|
personId: personIds.length ? personIds : undefined
|
||||||
documentId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -34,7 +32,6 @@ export const load: PageServerLoad = async ({ url, fetch }) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
geschichten: listResult.data ?? [],
|
geschichten: listResult.data ?? [],
|
||||||
personFilters,
|
personFilters
|
||||||
documentFilter: documentId ?? null
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,12 +11,11 @@ let { data }: { data: PageData } = $props();
|
|||||||
let showPersonPicker = $state(false);
|
let showPersonPicker = $state(false);
|
||||||
|
|
||||||
const selectedPersonIds = $derived(data.personFilters.map((p) => p.id!));
|
const selectedPersonIds = $derived(data.personFilters.map((p) => p.id!));
|
||||||
const hasFilters = $derived(data.personFilters.length > 0 || !!data.documentFilter);
|
const hasFilters = $derived(data.personFilters.length > 0);
|
||||||
|
|
||||||
function rebuildUrl(personIds: string[]) {
|
function rebuildUrl(personIds: string[]) {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.delete('personId');
|
url.searchParams.delete('personId');
|
||||||
url.searchParams.delete('documentId');
|
|
||||||
for (const id of personIds) url.searchParams.append('personId', id);
|
for (const id of personIds) url.searchParams.append('personId', id);
|
||||||
return url.pathname + url.search;
|
return url.pathname + url.search;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,25 +96,20 @@ async function handleDelete() {
|
|||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Dokumente -->
|
<!-- Dokumente (JourneyItems) -->
|
||||||
{#if g.documents && g.documents.length > 0}
|
{#if g.items && g.items.some((i) => i.documentId)}
|
||||||
<section class="mt-8 border-t border-line pt-6">
|
<section class="mt-8 border-t border-line pt-6">
|
||||||
<h2 class="mb-3 font-sans text-xs font-bold tracking-widest text-ink-2 uppercase">
|
<h2 class="mb-3 font-sans text-xs font-bold tracking-widest text-ink-2 uppercase">
|
||||||
{m.geschichten_documents_section()}
|
{m.geschichten_documents_section()}
|
||||||
</h2>
|
</h2>
|
||||||
<ul class="flex flex-col gap-2">
|
<ul class="flex flex-col gap-2">
|
||||||
{#each g.documents as d (d.id)}
|
{#each g.items.filter((i) => i.documentId) as item (item.id)}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="/documents/{d.id}"
|
href="/documents/{item.documentId}"
|
||||||
class="block rounded border border-line bg-surface px-4 py-3 font-serif text-base text-ink hover:bg-muted"
|
class="block rounded border border-line bg-surface px-4 py-3 font-serif text-base text-ink hover:bg-muted"
|
||||||
>
|
>
|
||||||
{d.title}
|
{item.note ?? item.documentId}
|
||||||
{#if d.documentDate}
|
|
||||||
<span class="ml-2 font-sans text-xs text-ink-3">
|
|
||||||
{formatDate(d.documentDate, 'short')}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ async function handleSubmit(payload: {
|
|||||||
body: string;
|
body: string;
|
||||||
status: 'DRAFT' | 'PUBLISHED';
|
status: 'DRAFT' | 'PUBLISHED';
|
||||||
personIds: string[];
|
personIds: string[];
|
||||||
documentIds: string[];
|
|
||||||
}) {
|
}) {
|
||||||
submitting = true;
|
submitting = true;
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
|
|||||||
@@ -10,24 +10,14 @@ export const load: PageServerLoad = async ({ url, fetch, parent }) => {
|
|||||||
|
|
||||||
const api = createApiClient(fetch);
|
const api = createApiClient(fetch);
|
||||||
const personId = url.searchParams.get('personId');
|
const personId = url.searchParams.get('personId');
|
||||||
const documentId = url.searchParams.get('documentId');
|
|
||||||
|
|
||||||
const [personResult, documentResult] = await Promise.all([
|
const personResult = personId
|
||||||
personId
|
? await api.GET('/api/persons/{id}', { params: { path: { id: personId } } })
|
||||||
? api.GET('/api/persons/{id}', { params: { path: { id: personId } } })
|
: null;
|
||||||
: Promise.resolve(null),
|
|
||||||
documentId
|
|
||||||
? api.GET('/api/documents/{id}', { params: { path: { id: documentId } } })
|
|
||||||
: Promise.resolve(null)
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Silently ignore 404/403 to avoid leaking entity existence on unknown IDs.
|
// Silently ignore 404/403 to avoid leaking entity existence on unknown IDs.
|
||||||
const initialPersons =
|
const initialPersons =
|
||||||
personResult && personResult.response.ok && personResult.data ? [personResult.data] : [];
|
personResult && personResult.response.ok && personResult.data ? [personResult.data] : [];
|
||||||
const initialDocuments =
|
|
||||||
documentResult && documentResult.response.ok && documentResult.data
|
|
||||||
? [documentResult.data]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return { initialPersons, initialDocuments };
|
return { initialPersons };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ async function handleSubmit(payload: {
|
|||||||
body: string;
|
body: string;
|
||||||
status: 'DRAFT' | 'PUBLISHED';
|
status: 'DRAFT' | 'PUBLISHED';
|
||||||
personIds: string[];
|
personIds: string[];
|
||||||
documentIds: string[];
|
|
||||||
}) {
|
}) {
|
||||||
submitting = true;
|
submitting = true;
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
@@ -58,7 +57,6 @@ async function handleSubmit(payload: {
|
|||||||
|
|
||||||
<GeschichteEditor
|
<GeschichteEditor
|
||||||
initialPersons={data.initialPersons}
|
initialPersons={data.initialPersons}
|
||||||
initialDocuments={data.initialDocuments}
|
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitting={submitting}
|
submitting={submitting}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user