Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Stack: React 19, TypeScript 4.9, Material-UI (MUI) 7, styled-components, react-d
| **Element-Editoren** | `src/components/PropertyEditor/editors/*` (Text, Boolean, String, SingleSelection, Number, Date, File, Group, Array, ChipGroup, Custom, Visibility) |
| **Palette** | `src/components/ElementPalette/ElementPalette.tsx` (ziehbare Element-Typen) |
| **Seiten** | `src/components/PageNavigator/PageNavigator.tsx`, `EditPageDialog.tsx` |
| **Flow-Metadaten** | `src/App.tsx` (hält Flow-State + leeres Template), `src/components/Navigation/Navigation.tsx`, `WorkflowNameDialog/` |
| **Flow-Metadaten** | `src/App.tsx` (hält Flow-State + leeres Template), `src/components/Navigation/Navigation.tsx`, `FlowMetadataDialog/` (id/url-key/name/title/icon, Validierung in `utils/flowMetadata.ts`) |
| **Export/UUID** | `src/utils/uuidUtils.ts` (Export-Transform — strippt `uuid`) |
| **Utilities** | `SubflowManager.ts`, `normalizeUtils.ts`, `visibilityUtils.ts`, `deepCloneUtils.ts`, `pathUtils.ts` |

Expand Down
37 changes: 35 additions & 2 deletions docs/technical_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,48 @@ interface ListingFlow {
interface Page {
pattern_type: string; // "Page" oder "CustomUIElement"
id: string;
layout?: string; // z.B. "2_COL_RIGHT_FILL" für Edit-Seiten
related_pages?: RelatedPage[]; // Verknüpfung zu korrespondierenden Seiten
layout?: string; // siehe "Seiten-Layout" unten
related_pages?: RelatedPage[]; // Verknüpfung Edit-/View-Seite, siehe unten
short_title?: TranslatableString; // Kurztitel für kompakte Anzeige
title?: TranslatableString; // Vollständiger Seitentitel
icon?: string; // Material Design Icon-Name
elements: PatternLibraryElement[];
}
```

##### Seiten-Layout (`layout`)

Das `layout`-Feld steuert, wie portal die rechte Spalte einer Seite rendert. **portal
unterstützt genau drei Werte** (Quelle: `portal-applications` → `d-fc-page-default.vue`); jeder
andere Wert läuft dort in den „unbekannter Typ"-Zweig:

| Wert | Bedeutung |
|---|---|
| _(nicht gesetzt)_ | **Standard** — rechte Spalte zentriert |
| `2_COL_RIGHT_WIDER` | zwei Spalten, rechte Spalte breiter zentriert |
| `2_COL_RIGHT_FILL` | zwei Spalten, genau ein rechtes Element füllt die Spalte |

Single Source of Truth im Editor: `src/components/PageNavigator/pageLayouts.ts`. Der Editor bietet
nur diese Werte an (mit Live-Vorschau via `LayoutPreview`); ein abwesendes Layout wird als
„Standard" dargestellt. Frühere Optionen `2_COL_LEFT_WIDER`/`1_COL` waren Schema-Drift und wurden
entfernt.

##### Korrespondierende Seiten (`related_pages`)

```typescript
interface RelatedPage {
viewing_context: 'VIEW' | 'EDIT'; // Kontext der referenzierten Seite
page_id: string; // ID der referenzierten Seite
}
```

Jeder Flow hält Seiten zweimal: `pages_edit` (Eingabe) und `pages_view` (Anzeige). `related_pages`
verknüpft eine Edit-Seite mit ihrer View-Seite und umgekehrt — `viewing_context` gibt an, in welchem
Kontext die referenzierte Seite steht. Der Editor pflegt diese Verknüpfung automatisch: Beim
Speichern einer Edit-Seite mit aktivem „Im View-Modus anzeigen" wird die zugehörige View-Seite
erzeugt/aktualisiert und über `related_pages` (bzw. die ID-Konvention `edit-…` ↔ `view-…`)
zugeordnet (siehe `findCorrespondingViewPage()`).

#### TranslatableString
```typescript
interface TranslatableString {
Expand Down
2 changes: 1 addition & 1 deletion plans/ux-overhaul.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Editor-Factory-Muster, `AccordionSection`, `TabbedTranslatableFields`, `ElementP

## Phase 5 — Seiten & Flow-Metadaten
- [x] **Layout-Vorschau + Layout-Wahl bei Seitenanlage** *(PR Phase 5a)*: Zentrale `pageLayouts.ts` (Single Source of Truth, unit-getestet) + SVG-`LayoutPreview`. Schema-Abgleich mit portal (`d-fc-page-default.vue`): nur `null`/Standard, `2_COL_RIGHT_WIDER`, `2_COL_RIGHT_FILL` werden gerendert — der Editor bot zusätzlich `2_COL_LEFT_WIDER`/`1_COL` an (**Schema-Drift, entfernt**). Layout jetzt mit Vorschau in `EditPageDialog` **und** schon im Neue-Seite-Dialog wählbar; abwesendes Layout = „Standard"; nicht unterstützte Alt-Werte werden als deaktivierter „⚠ Nicht unterstützt"-Eintrag sichtbar gemacht. **`pattern_type`-Auswahl bewusst verworfen**: portal verzweigt nicht über den Page-`pattern_type` (alle Top-Level-Seiten laufen durch `d-fc-page-default`), ein Selektor wäre wirkungslos und driftgefährdet.
- [ ] Flow-Metadaten editierbar (id/url-key mit Validierung, Flow-Icon, Title de/en); `related_pages` dokumentieren. *(Phase 5b — Flow-`description` entfällt: nicht im `ListingFlow`-Schema von portal.)*
- [x] **Flow-Metadaten editierbar** *(PR Phase 5b)*: Neuer `FlowMetadataDialog` (über `DialogBase`) ersetzt den reinen Namens-Dialog und macht `id`, `url-key`, `name`, `title` (de/en) und `icon` editierbar; `id`/`url-key` werden als slug-artige Identifier validiert (`utils/flowMetadata.ts`, unit-getestet), mit „aus Name"-Ableitung. Befund: vorher leitete der Dialog `id`/`url-key`/`title` still aus dem Namen ab und das Icon war gar nicht editierbar. `related_pages` + Layout-Vertrag in `docs/technical_documentation.md` dokumentiert. Flow-`description` bewusst weggelassen — nicht im `ListingFlow`-Schema von portal.

---

Expand Down
65 changes: 27 additions & 38 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid';
import { tokens } from './theme/tokens';
import { generateUUID, transformFlowForExport } from './utils/uuidUtils';
import styled from 'styled-components';
import WorkflowNameDialog from './components/WorkflowNameDialog/WorkflowNameDialog';
import FlowMetadataDialog, { FlowMetadata } from './components/FlowMetadataDialog/FlowMetadataDialog';
import ModuleManagerDialog from './components/ModuleManager/ModuleManagerDialog';

import Navigation from './components/Navigation/Navigation';
Expand Down Expand Up @@ -1638,7 +1638,7 @@ const createElement = (type: string): PatternLibraryElement => {
const AppContent: React.FC = () => {
const { state, dispatch } = useEditor();
const [selectedElementPath, setSelectedElementPath] = useState<number[]>([]);
const [showWorkflowNameDialog, setShowWorkflowNameDialog] = useState<boolean>(false);
const [showFlowMetadataDialog, setShowFlowMetadataDialog] = useState<boolean>(false);
const [showModuleManager, setShowModuleManager] = useState<boolean>(false);
const [showShortcuts, setShowShortcuts] = useState<boolean>(false);
// Erstkontakt-Onboarding: beim ersten Start automatisch zeigen (einmalig via localStorage).
Expand Down Expand Up @@ -2218,42 +2218,25 @@ const AppContent: React.FC = () => {
}
};

// Handler für das Bearbeiten des Workflow-Namens
// Handler für das Bearbeiten der Flow-Metadaten
const handleEditWorkflowName = () => {
setShowWorkflowNameDialog(true);
setShowFlowMetadataDialog(true);
};

// Handler für das Speichern des Workflow-Namens
const handleSaveWorkflowName = (name: string) => {
if (!name) return;

const newFlow = {
...emptyFlow,
id: name.toLowerCase().replace(/\s+/g, '-'),
'url-key': name.toLowerCase().replace(/\s+/g, '-'),
name: name,
title: {
de: name,
en: name
}
// Handler für das Speichern der Flow-Metadaten (id, url-key, name, title, icon)
const handleSaveFlowMetadata = (meta: FlowMetadata) => {
const fields = {
id: meta.id,
'url-key': meta.urlKey,
name: meta.name,
title: { de: meta.titleDe, en: meta.titleEn || meta.titleDe },
icon: meta.icon,
};

// Wenn es bereits einen Flow gibt, aktualisieren wir nur den Namen
if (state.currentFlow) {
const updatedFlow = {
...state.currentFlow,
id: name.toLowerCase().replace(/\s+/g, '-'),
'url-key': name.toLowerCase().replace(/\s+/g, '-'),
name: name,
title: {
de: name,
en: name
}
};
dispatch({ type: 'UPDATE_FLOW', flow: updatedFlow });
dispatch({ type: 'UPDATE_FLOW', flow: { ...state.currentFlow, ...fields } });
} else {
// Ansonsten erstellen wir einen neuen Flow
dispatch({ type: 'SET_FLOW', flow: newFlow });
dispatch({ type: 'SET_FLOW', flow: { ...emptyFlow, ...fields } });
}
};

Expand Down Expand Up @@ -2402,13 +2385,19 @@ const AppContent: React.FC = () => {
<KeyboardShortcutsDialog open={showShortcuts} onClose={() => setShowShortcuts(false)} />
<OnboardingDialog open={showOnboarding} onClose={closeOnboarding} />

{/* Workflow-Namen-Dialog */}
<WorkflowNameDialog
open={showWorkflowNameDialog}
initialName={state.currentFlow?.name || ""}
onClose={() => setShowWorkflowNameDialog(false)}
onSave={handleSaveWorkflowName}
isFirstTime={false}
{/* Flow-Metadaten-Dialog */}
<FlowMetadataDialog
open={showFlowMetadataDialog}
onClose={() => setShowFlowMetadataDialog(false)}
onSave={handleSaveFlowMetadata}
initial={{
name: state.currentFlow?.name ?? emptyFlow.name,
id: state.currentFlow?.id ?? emptyFlow.id,
urlKey: state.currentFlow?.['url-key'] ?? emptyFlow['url-key'],
titleDe: state.currentFlow?.title?.de ?? '',
titleEn: state.currentFlow?.title?.en ?? '',
icon: state.currentFlow?.icon ?? emptyFlow.icon,
}}
/>

{/* Modul-Katalog-Manager */}
Expand Down
152 changes: 152 additions & 0 deletions src/components/FlowMetadataDialog/FlowMetadataDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React, { useEffect, useState } from 'react';
import { Box, Button, TextField, Typography } from '@mui/material';
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
import DialogBase from '../common/DialogBase';
import IconField from '../PropertyEditor/common/IconField';
import { slugify, validateSlug, validateRequired } from '../../utils/flowMetadata';

export interface FlowMetadata {
name: string;
id: string;
urlKey: string;
titleDe: string;
titleEn: string;
icon: string;
}

interface FlowMetadataDialogProps {
open: boolean;
initial: FlowMetadata;
onClose: () => void;
onSave: (meta: FlowMetadata) => void;
}

/**
* Editor für die Flow-Metadaten (`id`, `url-key`, `name`, `title` de/en, `icon`).
* Löst den früheren reinen Namens-Dialog ab, der `id`/`url-key`/`title` still aus dem
* Namen ableitete und das Icon gar nicht editierbar machte. `id`/`url-key` werden als
* slug-artige Identifier validiert.
*/
const FlowMetadataDialog: React.FC<FlowMetadataDialogProps> = ({ open, initial, onClose, onSave }) => {
const [name, setName] = useState(initial.name);
const [id, setId] = useState(initial.id);
const [urlKey, setUrlKey] = useState(initial.urlKey);
const [titleDe, setTitleDe] = useState(initial.titleDe);
const [titleEn, setTitleEn] = useState(initial.titleEn);
const [icon, setIcon] = useState(initial.icon);

// Felder beim Öffnen auf den aktuellen Flow-Zustand zurücksetzen.
useEffect(() => {
if (open) {
setName(initial.name);
setId(initial.id);
setUrlKey(initial.urlKey);
setTitleDe(initial.titleDe);
setTitleEn(initial.titleEn);
setIcon(initial.icon);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);

const nameError = validateRequired(name, 'Name');
const idError = validateSlug(id, 'ID');
const urlKeyError = validateSlug(urlKey, 'URL-Key');
const titleDeError = validateRequired(titleDe, 'Titel (Deutsch)');
const hasError = !!(nameError || idError || urlKeyError || titleDeError);

const handleConfirm = () => {
if (hasError) return;
onSave({
name: name.trim(),
id: id.trim(),
urlKey: urlKey.trim(),
titleDe: titleDe.trim(),
titleEn: titleEn.trim(),
icon,
});
onClose();
};

return (
<DialogBase
open={open}
onClose={onClose}
title="Flow-Eigenschaften"
onConfirm={handleConfirm}
confirmLabel="Speichern"
confirmDisabled={hasError}
>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
<TextField
autoFocus
label="Name"
fullWidth
value={name}
onChange={(e) => setName(e.target.value)}
error={!!nameError}
helperText={nameError || 'Interner Anzeigename des Flows.'}
/>

<Box sx={{ display: 'flex', gap: 1, alignItems: 'flex-start' }}>
<TextField
label="ID"
fullWidth
value={id}
onChange={(e) => setId(e.target.value)}
error={!!idError}
helperText={idError || 'Technischer Identifier (z. B. doorbit_esg).'}
/>
<Button
size="small"
startIcon={<AutoFixHighIcon />}
onClick={() => setId(slugify(name))}
sx={{ mt: 1, flexShrink: 0 }}
>
aus Name
</Button>
</Box>

<Box sx={{ display: 'flex', gap: 1, alignItems: 'flex-start' }}>
<TextField
label="URL-Key"
fullWidth
value={urlKey}
onChange={(e) => setUrlKey(e.target.value)}
error={!!urlKeyError}
helperText={urlKeyError || 'Identifier in der URL (z. B. new-flow).'}
/>
<Button
size="small"
startIcon={<AutoFixHighIcon />}
onClick={() => setUrlKey(slugify(name))}
sx={{ mt: 1, flexShrink: 0 }}
>
aus Name
</Button>
</Box>

<Typography variant="subtitle2" sx={{ mt: 1 }}>
Titel (nutzersichtbar)
</Typography>
<TextField
label="Titel (Deutsch)"
fullWidth
value={titleDe}
onChange={(e) => setTitleDe(e.target.value)}
error={!!titleDeError}
helperText={titleDeError || undefined}
/>
<TextField
label="Titel (Englisch)"
fullWidth
value={titleEn}
onChange={(e) => setTitleEn(e.target.value)}
/>

<IconField value={icon} onChange={setIcon} label="Flow-Icon" fullWidth />
</Box>
</DialogBase>
);
};

export default FlowMetadataDialog;
2 changes: 1 addition & 1 deletion src/components/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const Navigation: React.FC<NavigationProps> = ({
Speichern
</Button>
</Tooltip>
<Tooltip title="Workflow-Namen bearbeiten">
<Tooltip title="Flow-Eigenschaften bearbeiten (Name, ID, URL-Key, Titel, Icon)">
<Button
variant="contained"
startIcon={<EditIcon />}
Expand Down
64 changes: 0 additions & 64 deletions src/components/WorkflowNameDialog/WorkflowNameDialog.tsx

This file was deleted.

Loading
Loading