cleanup internal marked exports
This commit is contained in:
parent
e09ed758ff
commit
c47dc67a84
26 changed files with 1246 additions and 1031 deletions
|
|
@ -256,6 +256,225 @@
|
||||||
background: var(--bg-primary, #fff);
|
background: var(--bg-primary, #fff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Toolbar: context (load + name) is fluid with ellipsis; actions stay right-aligned. */
|
||||||
|
.canvasHeaderRow {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.canvasHeaderRow {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderContext {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Closed <select> width must not follow the longest option label. */
|
||||||
|
.canvasHeaderWorkflowSelect {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 12.5rem;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0.4rem 0.5rem;
|
||||||
|
min-height: 2rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
border: 1px solid var(--border-color, #ccc);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--bg-primary, #fff);
|
||||||
|
color: var(--text-primary, #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderTitleBlock {
|
||||||
|
flex: 1 1 8rem;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderTitle,
|
||||||
|
.canvasHeaderTitle input {
|
||||||
|
margin: 0;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary, #1a1a1a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderTitle {
|
||||||
|
line-height: 1.2;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderTitleMuted {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 500;
|
||||||
|
opacity: 0.65;
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderTitle input {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0.25rem 0.4rem;
|
||||||
|
border: 1px solid var(--primary-color, #007bff);
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: none;
|
||||||
|
background: var(--bg-primary, #fff);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderActionPanel {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.4rem;
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color, #e0e0e0);
|
||||||
|
background: var(--bg-secondary, #f8f9fa);
|
||||||
|
flex: 0 1 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .retryButton sets margin-top for legacy error stacks — not wanted in the toolbar. */
|
||||||
|
.canvasHeaderActionPanel button {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Run label switches between "Ausführen", "Ausführen…", "Pflicht-Felder fehlen" — reserve space. */
|
||||||
|
.canvasHeaderRunButton {
|
||||||
|
min-width: 12.5rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.canvasHeaderActionPanel {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderVersionRow {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
border-top: 1px solid var(--border-color, #e8e8e8);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderVersionRow button {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderVersionLabel {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderVersionSelect {
|
||||||
|
width: 11rem;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0.3rem 0.45rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
min-height: 1.9rem;
|
||||||
|
border: 1px solid var(--border-color, #ccc);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--bg-primary, #fff);
|
||||||
|
color: var(--text-primary, #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderSysadmin {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
padding: 0.2rem 0.45rem;
|
||||||
|
border: 1px dashed var(--border-color, #ccc);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderNewSplit {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderSplitPair {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderNewSplitMain {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderNewSplitMenu {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
padding-right: 0.4rem;
|
||||||
|
border-left: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderMenuDropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--bg-primary, #fff);
|
||||||
|
border: 1px solid var(--border-color, #e0e0e0);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||||
|
min-width: 11rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderMenuItem {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-primary, #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderMenuItem:hover {
|
||||||
|
background: var(--bg-hover, #e9ecef);
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvasHeaderMenuItem + .canvasHeaderMenuItem {
|
||||||
|
border-top: 1px solid var(--border-color, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
.canvasTitle {
|
.canvasTitle {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|
@ -507,20 +726,32 @@
|
||||||
cursor: copy;
|
cursor: copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Node Config Panel */
|
/* Node Config Panel
|
||||||
|
* Fixed-width side panel. The `box-sizing: border-box` + `overflow-x: hidden`
|
||||||
|
* pair acts as a safety net so long unbreakable strings (type names like
|
||||||
|
* `List[ActionDocument]`, hashed IDs, refs like `← node.path → field`) can
|
||||||
|
* never push content out of the panel frame. Children rely on this; e.g.
|
||||||
|
* `RequiredAttributePicker` lays out label/badge so the badge wraps below
|
||||||
|
* a long label rather than escaping to the right.
|
||||||
|
*/
|
||||||
.nodeConfigPanel {
|
.nodeConfigPanel {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: var(--bg-primary, #fff);
|
background: var(--bg-primary, #fff);
|
||||||
border-left: 1px solid var(--border-color, #e0e0e0);
|
border-left: 1px solid var(--border-color, #e0e0e0);
|
||||||
width: 280px;
|
width: 280px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodeConfigPanel h4 {
|
.nodeConfigPanel h4 {
|
||||||
margin: 0 0 0.75rem 0;
|
margin: 0 0 0.75rem 0;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodeConfigNameRow {
|
.nodeConfigNameRow {
|
||||||
|
|
@ -547,6 +778,8 @@
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--text-secondary, #666);
|
color: var(--text-secondary, #666);
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodeConfigPanel label {
|
.nodeConfigPanel label {
|
||||||
|
|
@ -572,7 +805,8 @@
|
||||||
min-height: 60px;
|
min-height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kein Primär-Button-Stil für Zeitplan-Karten / Wochentage / Monat-Jahr-Chips */
|
/* Kein Primär-Button-Stil für Zeitplan-Karten / Wochentage / Monat-Jahr-Chips
|
||||||
|
(DataPicker-Dialog wird per createPortal an document.body gehangen — nicht hier). */
|
||||||
.nodeConfigPanel
|
.nodeConfigPanel
|
||||||
button:not(.scheduleModeCard):not(.scheduleDayOn):not(.scheduleDayOff):not(.scheduleSubModeBtn) {
|
button:not(.scheduleModeCard):not(.scheduleDayOn):not(.scheduleDayOff):not(.scheduleSubModeBtn) {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
|
|
@ -1284,53 +1518,112 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Data Picker */
|
/* Data Picker — rendered with createPortal(document.body) so it is not affected
|
||||||
|
by .nodeConfigPanel’s generic CTA `button` styles. */
|
||||||
|
|
||||||
.dataPickerOverlay {
|
.dataPickerOverlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: rgba(0, 0, 0, 0.35);
|
background: rgba(0, 0, 0, 0.4);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 1000;
|
z-index: 11000;
|
||||||
|
padding: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataPickerModal {
|
.dataPickerModal {
|
||||||
background: var(--bg-primary, #fff);
|
background: var(--bg-primary, #fff);
|
||||||
border-radius: 8px;
|
color: var(--text-primary, #1a1a1a);
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
border-radius: 10px;
|
||||||
max-width: 420px;
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||||
max-height: 80vh;
|
border: 1px solid var(--border-color, #e0e0e0);
|
||||||
|
max-width: min(420px, 100vw - 2rem);
|
||||||
|
width: 100%;
|
||||||
|
max-height: min(80vh, 640px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataPickerHeader {
|
.dataPickerHeader {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 1rem 1.25rem;
|
gap: 0.75rem;
|
||||||
|
padding: 1rem 1.15rem;
|
||||||
border-bottom: 1px solid var(--border-color, #e0e0e0);
|
border-bottom: 1px solid var(--border-color, #e0e0e0);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataPickerHeaderControls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataPickerTitle {
|
.dataPickerTitle {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: var(--text-primary, #1a1a1a);
|
||||||
|
line-height: 1.35;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem 0.4rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataPickerTypeBadge {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 400;
|
||||||
|
font-family: ui-monospace, 'Cascadia Code', monospace;
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
background: var(--bg-secondary, #f0f0f0);
|
||||||
|
border: 1px solid var(--border-color, #ddd);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.1rem 0.45rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataPickerStrictLabel {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataPickerClose {
|
.dataPickerClose {
|
||||||
background: none;
|
display: inline-flex;
|
||||||
border: none;
|
align-items: center;
|
||||||
font-size: 1.5rem;
|
justify-content: center;
|
||||||
cursor: pointer;
|
width: 2rem;
|
||||||
color: var(--text-secondary, #666);
|
height: 2rem;
|
||||||
padding: 0 0.25rem;
|
flex-shrink: 0;
|
||||||
|
background: var(--bg-secondary, #f5f5f5);
|
||||||
|
border: 1px solid var(--border-color, #d0d0d0);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1.25rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-primary, #333);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataPickerClose:hover {
|
.dataPickerClose:hover {
|
||||||
color: var(--text-primary, #333);
|
background: var(--bg-hover, #e9ecef);
|
||||||
|
color: var(--text-primary, #1a1a1a);
|
||||||
|
border-color: var(--border-color, #b8b8b8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataPickerBody {
|
.dataPickerBody {
|
||||||
|
|
@ -1345,24 +1638,35 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataPickerNodeSection {
|
.dataPickerNodeSection {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Expandable source row: neutral “list row”, not a primary CTA. */
|
||||||
.dataPickerNodeHeader {
|
.dataPickerNodeHeader {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5rem 0;
|
box-sizing: border-box;
|
||||||
background: none;
|
padding: 0.5rem 0.6rem;
|
||||||
border: none;
|
background: var(--bg-secondary, #f4f5f7);
|
||||||
|
border: 1px solid var(--border-color, #dde1e5);
|
||||||
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.875rem;
|
font-size: 0.85rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
color: var(--text-primary, #1a1a1a);
|
||||||
|
margin: 0;
|
||||||
|
transition: background 0.12s, border-color 0.12s, box-shadow 0.12s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataPickerNodeHeader:hover {
|
.dataPickerNodeHeader:hover {
|
||||||
background: var(--bg-hover, #f5f5f5);
|
background: var(--bg-hover, #e9ebef);
|
||||||
border-radius: 4px;
|
border-color: var(--border-color, #c8cfd6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataPickerNodeHeader:focus-visible {
|
||||||
|
outline: 2px solid var(--primary-color, #4a6fa5);
|
||||||
|
outline-offset: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataPickerExpandIcon {
|
.dataPickerExpandIcon {
|
||||||
|
|
@ -1401,6 +1705,43 @@
|
||||||
border-color: var(--primary-color, #007bff);
|
border-color: var(--primary-color, #007bff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hover safety net: every nested span in a leaf inherits the white text so
|
||||||
|
* type-hints and meta info stay readable on the blue hover background. */
|
||||||
|
.dataPickerLeaf:hover * {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline type-hint after a leaf label, e.g. "documents (List[ActionDocument])". */
|
||||||
|
.dataPickerLeafType {
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
font-size: 10px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Schema-name hint on the node-section header row. */
|
||||||
|
.dataPickerNodeSchemaHint {
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
font-size: 10px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "iterieren" affordance — visually distinct (subtle accent), readable on
|
||||||
|
* the picker's white background and on the leaf's blue hover background. */
|
||||||
|
.dataPickerIterateBtn {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
background: var(--bg-secondary, #f5f7fa);
|
||||||
|
color: var(--primary-color, #007bff);
|
||||||
|
border: 1px solid var(--border-color, #e0e0e0);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataPickerIterateBtn:hover {
|
||||||
|
background: var(--primary-color, #007bff);
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--primary-color, #007bff);
|
||||||
|
}
|
||||||
|
|
||||||
/* Dynamic Value Field */
|
/* Dynamic Value Field */
|
||||||
.dynamicValueField {
|
.dynamicValueField {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ import type { UdbContext, UdbTab } from '../../../components/UnifiedDataBar';
|
||||||
import styles from './Automation2FlowEditor.module.css';
|
import styles from './Automation2FlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
|
import { useToast } from '../../../contexts/ToastContext';
|
||||||
|
|
||||||
const LOG = '[Automation2]';
|
const LOG = '[Automation2]';
|
||||||
|
|
||||||
|
|
@ -90,6 +91,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
onSourcesChanged,
|
onSourcesChanged,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
const { showError } = useToast();
|
||||||
const { request } = useApiRequest();
|
const { request } = useApiRequest();
|
||||||
const { prompt: promptInput, PromptDialog } = usePrompt();
|
const { prompt: promptInput, PromptDialog } = usePrompt();
|
||||||
const [nodeTypes, setNodeTypes] = useState<NodeType[]>([]);
|
const [nodeTypes, setNodeTypes] = useState<NodeType[]>([]);
|
||||||
|
|
@ -137,6 +139,15 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
const [sidebarWidth, setSidebarWidth] = useState(() => {
|
const [sidebarWidth, setSidebarWidth] = useState(() => {
|
||||||
try { const v = parseInt(localStorage.getItem('flowEditor.sidebarWidth') ?? ''); return v >= 200 && v <= 500 ? v : 280; } catch { return 280; }
|
try { const v = parseInt(localStorage.getItem('flowEditor.sidebarWidth') ?? ''); return v >= 200 && v <= 500 ? v : 280; } catch { return 280; }
|
||||||
});
|
});
|
||||||
|
// Verbose schema toggle: shows the static type-reference block (input/output
|
||||||
|
// schema) and parameter type-badges in NodeConfigPanel. Only the
|
||||||
|
// CanvasHeader exposes the toggle (sysadmin-only); persisted to localStorage.
|
||||||
|
const [verboseSchema, setVerboseSchema] = useState(() => {
|
||||||
|
try { return localStorage.getItem('flowEditor.verboseSchema') === '1'; } catch { return false; }
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
try { localStorage.setItem('flowEditor.verboseSchema', verboseSchema ? '1' : '0'); } catch { /* ignore */ }
|
||||||
|
}, [verboseSchema]);
|
||||||
const resizingRef = useRef<{ target: 'left' | 'right'; startX: number; startW: number } | null>(null);
|
const resizingRef = useRef<{ target: 'left' | 'right'; startX: number; startW: number } | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -655,9 +666,11 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
await updateWorkflow(request, instanceId, workflowId, { label: newName });
|
await updateWorkflow(request, instanceId, workflowId, { label: newName });
|
||||||
setWorkflows((prev) => prev.map((w) => w.id === workflowId ? { ...w, label: newName } : w));
|
setWorkflows((prev) => prev.map((w) => w.id === workflowId ? { ...w, label: newName } : w));
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
const msg = e instanceof Error ? e.message : String(e);
|
||||||
console.error(`${LOG} rename failed`, e);
|
console.error(`${LOG} rename failed`, e);
|
||||||
|
showError(t('Workflow umbenennen fehlgeschlagen: {msg}', { msg }));
|
||||||
}
|
}
|
||||||
}, [request, instanceId]);
|
}, [request, instanceId, showError, t]);
|
||||||
|
|
||||||
const handleAutoLayout = useCallback(() => {
|
const handleAutoLayout = useCallback(() => {
|
||||||
setCanvasNodes((prev) => computeAutoLayout(prev, canvasConnections));
|
setCanvasNodes((prev) => computeAutoLayout(prev, canvasConnections));
|
||||||
|
|
@ -821,6 +834,8 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
onNewFromTemplate={() => setTemplatePickerOpen(true)}
|
onNewFromTemplate={() => setTemplatePickerOpen(true)}
|
||||||
onWorkflowRename={handleWorkflowRename}
|
onWorkflowRename={handleWorkflowRename}
|
||||||
onAutoLayout={handleAutoLayout}
|
onAutoLayout={handleAutoLayout}
|
||||||
|
verboseSchema={verboseSchema}
|
||||||
|
onVerboseSchemaChange={setVerboseSchema}
|
||||||
/>
|
/>
|
||||||
<div className={styles.canvasArea} style={{ display: 'flex', flex: 1, minWidth: 0 }}>
|
<div className={styles.canvasArea} style={{ display: 'flex', flex: 1, minWidth: 0 }}>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
|
@ -875,6 +890,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
onNodeUpdate={handleNodeUpdate}
|
onNodeUpdate={handleNodeUpdate}
|
||||||
instanceId={instanceId}
|
instanceId={instanceId}
|
||||||
request={request}
|
request={request}
|
||||||
|
verboseSchema={verboseSchema}
|
||||||
/>
|
/>
|
||||||
</Automation2DataFlowProvider>
|
</Automation2DataFlowProvider>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import type { Automation2Workflow, ExecuteGraphResponse, AutoVersion, AutoTempla
|
||||||
import styles from './Automation2FlowEditor.module.css';
|
import styles from './Automation2FlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
|
import { getUserDataCache } from '../../../utils/userCache';
|
||||||
|
|
||||||
interface CanvasHeaderProps {
|
interface CanvasHeaderProps {
|
||||||
workflows: Automation2Workflow[];
|
workflows: Automation2Workflow[];
|
||||||
|
|
@ -40,6 +41,10 @@ interface CanvasHeaderProps {
|
||||||
onNewFromTemplate?: () => void;
|
onNewFromTemplate?: () => void;
|
||||||
onWorkflowRename?: (workflowId: string, newName: string) => void;
|
onWorkflowRename?: (workflowId: string, newName: string) => void;
|
||||||
onAutoLayout?: () => void;
|
onAutoLayout?: () => void;
|
||||||
|
/** Sysadmin-only: when true, NodeConfigPanel renders the static
|
||||||
|
* "Schema (Typ-Referenz)" block and per-parameter type-badges. */
|
||||||
|
verboseSchema?: boolean;
|
||||||
|
onVerboseSchemaChange?: (next: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getStatusBadge(t: (key: string) => string): Record<string, { label: string; color: string }> {
|
function _getStatusBadge(t: (key: string) => string): Record<string, { label: string; color: string }> {
|
||||||
|
|
@ -77,8 +82,11 @@ export const CanvasHeader: React.FC<CanvasHeaderProps> = ({ workflows,
|
||||||
onNewFromTemplate,
|
onNewFromTemplate,
|
||||||
onWorkflowRename,
|
onWorkflowRename,
|
||||||
onAutoLayout,
|
onAutoLayout,
|
||||||
|
verboseSchema,
|
||||||
|
onVerboseSchemaChange,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
const _isSysAdmin = getUserDataCache()?.isSysAdmin === true;
|
||||||
const statusBadge = _getStatusBadge(t);
|
const statusBadge = _getStatusBadge(t);
|
||||||
const currentVersion = versions?.find((v) => v.id === currentVersionId);
|
const currentVersion = versions?.find((v) => v.id === currentVersionId);
|
||||||
const currentStatus = currentVersion?.status || 'draft';
|
const currentStatus = currentVersion?.status || 'draft';
|
||||||
|
|
@ -137,35 +145,59 @@ export const CanvasHeader: React.FC<CanvasHeaderProps> = ({ workflows,
|
||||||
[t]
|
[t]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const _titleHint =
|
||||||
|
onWorkflowRename && currentWorkflow
|
||||||
|
? `${currentWorkflow.label} — ${t('Klicken zum Umbenennen')}`
|
||||||
|
: currentWorkflow?.label;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.canvasHeader}>
|
<div className={styles.canvasHeader}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
|
<div className={styles.canvasHeaderRow}>
|
||||||
{/* Workflow name: inline editable */}
|
<div className={styles.canvasHeaderContext}>
|
||||||
|
<select
|
||||||
|
className={styles.canvasHeaderWorkflowSelect}
|
||||||
|
value={currentWorkflowId ?? ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const id = e.target.value ? e.target.value : null;
|
||||||
|
onWorkflowSelect(id);
|
||||||
|
}}
|
||||||
|
aria-label={t('Workflow laden')}
|
||||||
|
title={t('Workflow laden')}
|
||||||
|
>
|
||||||
|
<option value="">{t('Workflow laden')}</option>
|
||||||
|
{workflows.map((w) => (
|
||||||
|
<option key={w.id} value={w.id}>
|
||||||
|
{w.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<div className={styles.canvasHeaderTitleBlock}>
|
||||||
{currentWorkflowId && currentWorkflow ? (
|
{currentWorkflowId && currentWorkflow ? (
|
||||||
editingName ? (
|
editingName ? (
|
||||||
<input
|
<input
|
||||||
ref={nameInputRef}
|
ref={nameInputRef}
|
||||||
|
className={styles.canvasHeaderTitle}
|
||||||
value={nameValue}
|
value={nameValue}
|
||||||
onChange={(e) => setNameValue(e.target.value)}
|
onChange={(e) => setNameValue(e.target.value)}
|
||||||
onBlur={_commitNameEdit}
|
onBlur={_commitNameEdit}
|
||||||
onKeyDown={(e) => { if (e.key === 'Enter') _commitNameEdit(); if (e.key === 'Escape') setEditingName(false); }}
|
onKeyDown={(e) => { if (e.key === 'Enter') _commitNameEdit(); if (e.key === 'Escape') setEditingName(false); }}
|
||||||
style={{ padding: '0.25rem 0.4rem', fontSize: '0.95rem', fontWeight: 600, border: '1px solid var(--primary-color, #007bff)', borderRadius: 4, outline: 'none', minWidth: 140, maxWidth: 300 }}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<h4
|
<h4
|
||||||
className={styles.canvasTitle}
|
className={styles.canvasHeaderTitle}
|
||||||
style={{ margin: 0, cursor: onWorkflowRename ? 'pointer' : 'default', fontSize: '0.95rem', fontWeight: 600 }}
|
style={{ cursor: onWorkflowRename ? 'pointer' : 'default' }}
|
||||||
onClick={_startNameEdit}
|
onClick={_startNameEdit}
|
||||||
title={onWorkflowRename ? t('Klicken zum Umbenennen') : undefined}
|
title={_titleHint}
|
||||||
>
|
>
|
||||||
{currentWorkflow.label}
|
{currentWorkflow.label}
|
||||||
</h4>
|
</h4>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<h4 className={styles.canvasTitle} style={{ margin: 0, fontStyle: 'italic', opacity: 0.6 }}>
|
<h4 className={`${styles.canvasHeaderTitle} ${styles.canvasHeaderTitleMuted}`}>
|
||||||
{t('Neuer Workflow')}
|
{t('Neuer Workflow')}
|
||||||
</h4>
|
</h4>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
{onWorkflowSettings && (
|
{onWorkflowSettings && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -177,37 +209,45 @@ export const CanvasHeader: React.FC<CanvasHeaderProps> = ({ workflows,
|
||||||
<FaCog />
|
<FaCog />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Split "Neu" button */}
|
<div className={styles.canvasHeaderActionPanel} role="toolbar" aria-label={t('Workflow-Aktionen')}>
|
||||||
<div ref={newMenuRef} style={{ position: 'relative', display: 'inline-block' }}>
|
<div ref={newMenuRef} className={styles.canvasHeaderNewSplit}>
|
||||||
<div style={{ display: 'flex' }}>
|
<div className={styles.canvasHeaderSplitPair}>
|
||||||
<button type="button" className={styles.retryButton} onClick={onNew} style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}>
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`${styles.retryButton} ${styles.canvasHeaderNewSplitMain}`}
|
||||||
|
onClick={onNew}
|
||||||
|
>
|
||||||
{t('Neu')}
|
{t('Neu')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.retryButton}
|
className={`${styles.retryButton} ${styles.canvasHeaderNewSplitMenu}`}
|
||||||
onClick={() => setNewMenuOpen((p) => !p)}
|
onClick={() => setNewMenuOpen((p) => !p)}
|
||||||
style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0, paddingLeft: 4, paddingRight: 6, borderLeft: '1px solid rgba(0,0,0,0.15)' }}
|
|
||||||
title={t('Neu aus Vorlage')}
|
title={t('Neu aus Vorlage')}
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-expanded={newMenuOpen}
|
||||||
>
|
>
|
||||||
<FaCaretDown style={{ fontSize: '0.7rem' }} />
|
<FaCaretDown style={{ fontSize: '0.7rem' }} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{newMenuOpen && (
|
{newMenuOpen && (
|
||||||
<div style={{ position: 'absolute', top: '100%', left: 0, zIndex: 100, background: 'var(--bg-primary, #fff)', border: '1px solid var(--border-color, #e0e0e0)', borderRadius: 6, boxShadow: '0 4px 12px rgba(0,0,0,0.15)', minWidth: 180, marginTop: 4 }}>
|
<div className={styles.canvasHeaderMenuDropdown} role="menu">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
className={styles.canvasHeaderMenuItem}
|
||||||
onClick={() => { onNew(); setNewMenuOpen(false); }}
|
onClick={() => { onNew(); setNewMenuOpen(false); }}
|
||||||
style={{ display: 'block', width: '100%', textAlign: 'left', padding: '8px 12px', border: 'none', background: 'transparent', cursor: 'pointer', fontSize: '0.85rem' }}
|
role="menuitem"
|
||||||
>
|
>
|
||||||
{t('Leerer Workflow')}
|
{t('Leerer Workflow')}
|
||||||
</button>
|
</button>
|
||||||
{onNewFromTemplate && (
|
{onNewFromTemplate && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
className={styles.canvasHeaderMenuItem}
|
||||||
onClick={() => { onNewFromTemplate(); setNewMenuOpen(false); }}
|
onClick={() => { onNewFromTemplate(); setNewMenuOpen(false); }}
|
||||||
style={{ display: 'block', width: '100%', textAlign: 'left', padding: '8px 12px', border: 'none', background: 'transparent', cursor: 'pointer', fontSize: '0.85rem', borderTop: '1px solid var(--border-color, #e0e0e0)' }}
|
role="menuitem"
|
||||||
>
|
>
|
||||||
{t('Aus Vorlage…')}
|
{t('Aus Vorlage…')}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -220,9 +260,6 @@ export const CanvasHeader: React.FC<CanvasHeaderProps> = ({ workflows,
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.retryButton}
|
className={styles.retryButton}
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
// Phase-4 Schicht-4: Save niemals blockieren — work-in-progress muss
|
|
||||||
// jederzeit persistierbar sein. Nur während des Save-Requests selbst
|
|
||||||
// sperren wir den Button, um Doppelklicks zu verhindern.
|
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
title={!hasNodes ? t('Workflow ist leer — Speichern legt einen leeren Workflow an.') : undefined}
|
title={!hasNodes ? t('Workflow ist leer — Speichern legt einen leeren Workflow an.') : undefined}
|
||||||
>
|
>
|
||||||
|
|
@ -242,26 +279,28 @@ export const CanvasHeader: React.FC<CanvasHeaderProps> = ({ workflows,
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Save as template */}
|
|
||||||
{currentWorkflowId && onSaveAsTemplate && (
|
{currentWorkflowId && onSaveAsTemplate && (
|
||||||
<div ref={templateMenuRef} style={{ position: 'relative', display: 'inline-block' }}>
|
<div ref={templateMenuRef} className={styles.canvasHeaderNewSplit}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.retryButton}
|
className={styles.retryButton}
|
||||||
onClick={() => setTemplateMenuOpen((p) => !p)}
|
onClick={() => setTemplateMenuOpen((p) => !p)}
|
||||||
disabled={templateSaving}
|
disabled={templateSaving}
|
||||||
title={t('Als Vorlage speichern')}
|
title={t('Als Vorlage speichern')}
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-expanded={templateMenuOpen}
|
||||||
>
|
>
|
||||||
{templateSaving ? <FaSpinner className={styles.spinner} /> : <><FaBookmark style={{ marginRight: 4 }} />{t('Als Vorlage')}</>}
|
{templateSaving ? <FaSpinner className={styles.spinner} /> : <><FaBookmark style={{ marginRight: 4 }} />{t('Als Vorlage')}</>}
|
||||||
</button>
|
</button>
|
||||||
{templateMenuOpen && (
|
{templateMenuOpen && (
|
||||||
<div style={{ position: 'absolute', top: '100%', left: 0, zIndex: 100, background: 'var(--bg-primary, #fff)', border: '1px solid var(--border-color, #e0e0e0)', borderRadius: 6, boxShadow: '0 4px 12px rgba(0,0,0,0.15)', minWidth: 180, marginTop: 4 }}>
|
<div className={styles.canvasHeaderMenuDropdown} role="menu">
|
||||||
{(['user', 'instance', 'mandate'] as const).map((s) => (
|
{(['user', 'instance', 'mandate'] as const).map((s) => (
|
||||||
<button
|
<button
|
||||||
key={s}
|
key={s}
|
||||||
type="button"
|
type="button"
|
||||||
|
className={styles.canvasHeaderMenuItem}
|
||||||
onClick={() => { onSaveAsTemplate(s); setTemplateMenuOpen(false); }}
|
onClick={() => { onSaveAsTemplate(s); setTemplateMenuOpen(false); }}
|
||||||
style={{ display: 'block', width: '100%', textAlign: 'left', padding: '8px 12px', border: 'none', background: 'transparent', cursor: 'pointer', fontSize: '0.85rem', borderTop: s !== 'user' ? '1px solid var(--border-color, #e0e0e0)' : undefined }}
|
role="menuitem"
|
||||||
>
|
>
|
||||||
{scopeLabels[s]}
|
{scopeLabels[s]}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -270,24 +309,10 @@ export const CanvasHeader: React.FC<CanvasHeaderProps> = ({ workflows,
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<select
|
|
||||||
value={currentWorkflowId ?? ''}
|
|
||||||
onChange={(e) => {
|
|
||||||
const id = e.target.value ? e.target.value : null;
|
|
||||||
onWorkflowSelect(id);
|
|
||||||
}}
|
|
||||||
style={{ padding: '0.4rem', minWidth: 180 }}
|
|
||||||
>
|
|
||||||
<option value="">{t('Workflow laden')}</option>
|
|
||||||
{workflows.map((w) => (
|
|
||||||
<option key={w.id} value={w.id}>
|
|
||||||
{w.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.retryButton}
|
className={`${styles.retryButton} ${styles.canvasHeaderRunButton}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (executeBlockedReason) {
|
if (executeBlockedReason) {
|
||||||
onExecuteBlockedClick?.();
|
onExecuteBlockedClick?.();
|
||||||
|
|
@ -311,17 +336,17 @@ export const CanvasHeader: React.FC<CanvasHeaderProps> = ({ workflows,
|
||||||
>
|
>
|
||||||
{executing ? (
|
{executing ? (
|
||||||
<>
|
<>
|
||||||
<FaSpinner className={styles.spinner} style={{ marginRight: '0.5rem', display: 'inline-block' }} />
|
<FaSpinner className={styles.spinner} style={{ flexShrink: 0 }} />
|
||||||
{t('Ausführen…')}
|
{t('Ausführen…')}
|
||||||
</>
|
</>
|
||||||
) : executeBlockedReason ? (
|
) : executeBlockedReason ? (
|
||||||
<>
|
<>
|
||||||
<FaPlay style={{ marginRight: '0.5rem', opacity: 0.5 }} />
|
<FaPlay style={{ opacity: 0.5, flexShrink: 0 }} />
|
||||||
{t('Pflicht-Felder fehlen')}
|
{t('Pflicht-Felder fehlen')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<FaPlay style={{ marginRight: '0.5rem' }} />
|
<FaPlay style={{ flexShrink: 0 }} />
|
||||||
{t('Ausführen')}
|
{t('Ausführen')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
@ -332,17 +357,32 @@ export const CanvasHeader: React.FC<CanvasHeaderProps> = ({ workflows,
|
||||||
{t('Workspace')}
|
{t('Workspace')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{_isSysAdmin && onVerboseSchemaChange && (
|
||||||
|
<label
|
||||||
|
className={styles.canvasHeaderSysadmin}
|
||||||
|
title={t('Sysadmin-Ansicht: zeigt im Node-Panel das statische Typ-Schema (Eingabe/Ausgabe) und Parameter-Typ-Badges.')}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={!!verboseSchema}
|
||||||
|
onChange={(e) => onVerboseSchemaChange(e.target.checked)}
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
/>
|
||||||
|
{t('Schema-Details')}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Version Selector */}
|
|
||||||
{currentWorkflowId && versions && versions.length > 0 && (
|
{currentWorkflowId && versions && versions.length > 0 && (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginTop: '0.5rem', flexWrap: 'wrap' }}>
|
<div className={styles.canvasHeaderVersionRow}>
|
||||||
<span style={{ fontSize: '0.8rem', fontWeight: 600, color: 'var(--text-secondary, #666)' }}>{t('Version:')}</span>
|
<span className={styles.canvasHeaderVersionLabel}>{t('Version:')}</span>
|
||||||
<select
|
<select
|
||||||
|
className={styles.canvasHeaderVersionSelect}
|
||||||
value={currentVersionId ?? ''}
|
value={currentVersionId ?? ''}
|
||||||
onChange={(e) => onVersionSelect?.(e.target.value || null)}
|
onChange={(e) => onVersionSelect?.(e.target.value || null)}
|
||||||
style={{ padding: '0.3rem', minWidth: 140, fontSize: '0.85rem' }}
|
|
||||||
disabled={versionLoading}
|
disabled={versionLoading}
|
||||||
|
aria-label={t('Version')}
|
||||||
>
|
>
|
||||||
<option value="">{t('Aktuelle')}</option>
|
<option value="">{t('Aktuelle')}</option>
|
||||||
{versions.map((v) => (
|
{versions.map((v) => (
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ interface NodeConfigPanelProps {
|
||||||
onNodeUpdate?: (nodeId: string, updates: Partial<Pick<CanvasNode, 'title' | 'comment'>>) => void;
|
onNodeUpdate?: (nodeId: string, updates: Partial<Pick<CanvasNode, 'title' | 'comment'>>) => void;
|
||||||
instanceId?: string;
|
instanceId?: string;
|
||||||
request?: ApiRequestFunction;
|
request?: ApiRequestFunction;
|
||||||
|
/** When true, render developer-oriented sections (Schema-Typ-Referenz,
|
||||||
|
* parameter type-badges). Toggle in CanvasHeader, sysadmin-only. */
|
||||||
|
verboseSchema?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
|
|
@ -35,6 +38,7 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
onNodeUpdate,
|
onNodeUpdate,
|
||||||
instanceId,
|
instanceId,
|
||||||
request,
|
request,
|
||||||
|
verboseSchema = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const [params, setParams] = useState<Record<string, unknown>>({});
|
const [params, setParams] = useState<Record<string, unknown>>({});
|
||||||
|
|
@ -88,11 +92,28 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
}, [nodeType?.parameters]);
|
}, [nodeType?.parameters]);
|
||||||
|
|
||||||
// Pre-compute which required params are unbound on this node so we can
|
// Pre-compute which required params are unbound on this node so we can
|
||||||
// surface a panel-level summary banner.
|
// surface a panel-level summary banner. The hidden-param safety net lives
|
||||||
|
// inside `findRequiredErrors` so banner, canvas badges and Run-button stay
|
||||||
|
// in lockstep.
|
||||||
|
// Banner labels are kept short (`param.name`); the full description is
|
||||||
|
// attached as the tooltip below.
|
||||||
const requiredErrors = useMemo(() => {
|
const requiredErrors = useMemo(() => {
|
||||||
if (!node || !nodeType) return [];
|
if (!node || !nodeType) return [];
|
||||||
return findRequiredErrors(node, nodeType, (p) => getLabel(p.description, language) || p.name);
|
return findRequiredErrors(node, nodeType, (p) => p.name);
|
||||||
}, [node, nodeType, language]);
|
}, [node, nodeType]);
|
||||||
|
|
||||||
|
// Resolve full descriptions per missing param (for the banner tooltip).
|
||||||
|
const requiredErrorTooltip = useMemo(() => {
|
||||||
|
if (!requiredErrors.length || !nodeType) return '';
|
||||||
|
const byName = new Map((nodeType.parameters ?? []).map((p) => [p.name, p]));
|
||||||
|
return requiredErrors
|
||||||
|
.map((e) => {
|
||||||
|
const p = byName.get(e.paramName);
|
||||||
|
const desc = p ? (getLabel(p.description, language) || '') : '';
|
||||||
|
return desc ? `${e.paramName}: ${desc}` : e.paramName;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
}, [requiredErrors, nodeType, language]);
|
||||||
|
|
||||||
if (!node || !nodeType) return null;
|
if (!node || !nodeType) return null;
|
||||||
|
|
||||||
|
|
@ -129,23 +150,23 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
{getLabel(nodeType.description, language)}
|
{getLabel(nodeType.description, language)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{hasPortInfo && (
|
{hasPortInfo && verboseSchema && (
|
||||||
<details className={styles.nodeConfigPorts ?? ''} style={{ margin: '0 0 0.75rem', fontSize: '0.7rem' }}>
|
<details className={styles.nodeConfigPorts ?? ''} style={{ margin: '0 0 0.75rem', fontSize: '0.7rem' }}>
|
||||||
<summary
|
<summary
|
||||||
style={{
|
style={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
color: 'var(--text-secondary, #888)',
|
color: 'var(--text-secondary)',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
padding: '0.15rem 0',
|
padding: '0.15rem 0',
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
}}
|
}}
|
||||||
title={t('Statische Schema-Referenz f\u00fcr diesen Node-Typ \u2014 keine Live-Daten')}
|
title={t('Statische Schema-Referenz f\u00fcr diesen Node-Typ \u2014 keine Live-Daten')}
|
||||||
>
|
>
|
||||||
{t('Schema (Typ-Referenz)')}
|
{t('Schema (Typ-Referenz, Sysadmin-Ansicht)')}
|
||||||
</summary>
|
</summary>
|
||||||
{inputPortEntries.length > 0 && (
|
{inputPortEntries.length > 0 && (
|
||||||
<div style={{ marginTop: '0.4rem' }}>
|
<div style={{ marginTop: '0.4rem' }}>
|
||||||
<div style={{ color: 'var(--text-secondary, #666)', fontWeight: 600, marginBottom: 2 }}>
|
<div style={{ color: 'var(--text-secondary)', fontWeight: 600, marginBottom: 2 }}>
|
||||||
{'\u2B07'} {t('Eingabe')}
|
{'\u2B07'} {t('Eingabe')}
|
||||||
</div>
|
</div>
|
||||||
{inputPortEntries.map(([idx, def]) => (
|
{inputPortEntries.map(([idx, def]) => (
|
||||||
|
|
@ -162,7 +183,7 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
)}
|
)}
|
||||||
{outputPortEntries.length > 0 && (
|
{outputPortEntries.length > 0 && (
|
||||||
<div style={{ marginTop: '0.4rem' }}>
|
<div style={{ marginTop: '0.4rem' }}>
|
||||||
<div style={{ color: 'var(--text-secondary, #666)', fontWeight: 600, marginBottom: 2 }}>
|
<div style={{ color: 'var(--text-secondary)', fontWeight: 600, marginBottom: 2 }}>
|
||||||
{'\u2B06'} {t('Ausgabe')}
|
{'\u2B06'} {t('Ausgabe')}
|
||||||
</div>
|
</div>
|
||||||
{outputPortEntries.map(([idx, def]) => (
|
{outputPortEntries.map(([idx, def]) => (
|
||||||
|
|
@ -189,13 +210,19 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: 'var(--danger-color, #dc3545)',
|
color: 'var(--danger-color, #dc3545)',
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word',
|
||||||
}}
|
}}
|
||||||
|
title={requiredErrorTooltip || undefined}
|
||||||
>
|
>
|
||||||
{t('Pflicht-Felder ohne Quelle:')}{' '}
|
{t('Pflicht-Felder ohne Quelle:')}{' '}
|
||||||
<strong>{requiredErrors.map((e) => e.paramLabel).join(', ')}</strong>
|
<strong>{requiredErrors.map((e) => e.paramLabel).join(', ')}</strong>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{parameters.map((param: NodeTypeParameter) => {
|
{parameters.map((param: NodeTypeParameter) => {
|
||||||
|
// Safety net: hidden params have no UI footprint at all — no row,
|
||||||
|
// no required-mark, no type-badge. Their value is system-set.
|
||||||
|
if (param.frontendType === 'hidden') return null;
|
||||||
const useRequiredPicker = _shouldUseRequiredPicker(param);
|
const useRequiredPicker = _shouldUseRequiredPicker(param);
|
||||||
if (useRequiredPicker) {
|
if (useRequiredPicker) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -212,26 +239,40 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
const frontendType = param.frontendType || 'text';
|
const frontendType = param.frontendType || 'text';
|
||||||
const Renderer = FRONTEND_TYPE_RENDERERS[frontendType] ?? FRONTEND_TYPE_RENDERERS.text;
|
const Renderer = FRONTEND_TYPE_RENDERERS[frontendType] ?? FRONTEND_TYPE_RENDERERS.text;
|
||||||
return (
|
return (
|
||||||
<div key={param.name} style={{ marginBottom: 4 }}>
|
<div key={param.name} style={{ marginBottom: 4, minWidth: 0, maxWidth: '100%' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 2 }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
marginBottom: 2,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
minWidth: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{param.required && (
|
{param.required && (
|
||||||
<span
|
<span
|
||||||
title={t('Pflichtfeld')}
|
title={t('Pflichtfeld')}
|
||||||
style={{ color: 'var(--danger-color, #dc3545)', fontWeight: 700 }}
|
style={{ color: 'var(--danger-color, #dc3545)', fontWeight: 700, flexShrink: 0 }}
|
||||||
>
|
>
|
||||||
*
|
*
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{param.type && (
|
{verboseSchema && param.type && (
|
||||||
<span
|
<span
|
||||||
title={t('Parameter-Typ')}
|
title={t('Parameter-Typ')}
|
||||||
style={{
|
style={{
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: '#555',
|
color: 'var(--text-secondary)',
|
||||||
background: '#eee',
|
background: 'var(--bg-secondary)',
|
||||||
|
border: '1px solid var(--border-color)',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
padding: '1px 6px',
|
padding: '1px 6px',
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{param.type}
|
{param.type}
|
||||||
|
|
@ -261,6 +302,9 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
function _shouldUseRequiredPicker(param: NodeTypeParameter): boolean {
|
function _shouldUseRequiredPicker(param: NodeTypeParameter): boolean {
|
||||||
if (!param.required) return false;
|
if (!param.required) return false;
|
||||||
if (!param.type) return false;
|
if (!param.type) return false;
|
||||||
|
// Hidden params never get a picker — they are system-set or rendered to
|
||||||
|
// nothing on purpose. The render loop above also skips hidden rows entirely.
|
||||||
|
if (param.frontendType === 'hidden') return false;
|
||||||
// Always defer to specialized FE renderers when explicitly chosen.
|
// Always defer to specialized FE renderers when explicitly chosen.
|
||||||
if (param.frontendType && _LEGACY_RENDERERS_THAT_HANDLE_BINDINGS.has(param.frontendType)) {
|
if (param.frontendType && _LEGACY_RENDERERS_THAT_HANDLE_BINDINGS.has(param.frontendType)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -273,6 +317,7 @@ function _shouldUseRequiredPicker(param: NodeTypeParameter): boolean {
|
||||||
|
|
||||||
const _LEGACY_RENDERERS_THAT_HANDLE_BINDINGS = new Set([
|
const _LEGACY_RENDERERS_THAT_HANDLE_BINDINGS = new Set([
|
||||||
'userConnection',
|
'userConnection',
|
||||||
|
'featureInstance',
|
||||||
'sharepointFolder',
|
'sharepointFolder',
|
||||||
'sharepointFile',
|
'sharepointFile',
|
||||||
'clickupList',
|
'clickupList',
|
||||||
|
|
@ -308,7 +353,7 @@ const _PortFieldList: React.FC<_PortFieldListProps> = ({ portIndex, schemaNames,
|
||||||
if (!schemaNames.length) return null;
|
if (!schemaNames.length) return null;
|
||||||
return (
|
return (
|
||||||
<div style={{ marginLeft: 4, marginBottom: 4 }}>
|
<div style={{ marginLeft: 4, marginBottom: 4 }}>
|
||||||
<div style={{ color: '#888', fontSize: '0.7rem' }}>
|
<div style={{ color: 'var(--text-secondary)', fontSize: '0.7rem' }}>
|
||||||
{`#${portIndex} `}{schemaNames.join(' | ')}
|
{`#${portIndex} `}{schemaNames.join(' | ')}
|
||||||
</div>
|
</div>
|
||||||
{schemaNames.map((name) => {
|
{schemaNames.map((name) => {
|
||||||
|
|
@ -316,14 +361,14 @@ const _PortFieldList: React.FC<_PortFieldListProps> = ({ portIndex, schemaNames,
|
||||||
const fields = schema?.fields ?? [];
|
const fields = schema?.fields ?? [];
|
||||||
if (name === 'Transit') {
|
if (name === 'Transit') {
|
||||||
return (
|
return (
|
||||||
<div key={name} style={{ marginLeft: 8, color: '#999', fontStyle: 'italic', fontSize: '0.7rem' }}>
|
<div key={name} style={{ marginLeft: 8, color: 'var(--text-tertiary)', fontStyle: 'italic', fontSize: '0.7rem' }}>
|
||||||
{'\u00B7 Transit (durchgereichte Daten)'}
|
{'\u00B7 Transit (durchgereichte Daten)'}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!fields.length) {
|
if (!fields.length) {
|
||||||
return (
|
return (
|
||||||
<div key={name} style={{ marginLeft: 8, color: '#bbb', fontSize: '0.7rem' }}>
|
<div key={name} style={{ marginLeft: 8, color: 'var(--text-tertiary)', fontSize: '0.7rem' }}>
|
||||||
{`\u00B7 ${emptyLabel}`}
|
{`\u00B7 ${emptyLabel}`}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -331,12 +376,12 @@ const _PortFieldList: React.FC<_PortFieldListProps> = ({ portIndex, schemaNames,
|
||||||
return (
|
return (
|
||||||
<ul key={name} style={{ margin: '2px 0 4px 16px', padding: 0, listStyle: 'none' }}>
|
<ul key={name} style={{ margin: '2px 0 4px 16px', padding: 0, listStyle: 'none' }}>
|
||||||
{fields.map((f) => (
|
{fields.map((f) => (
|
||||||
<li key={f.name} style={{ fontSize: '0.7rem', lineHeight: 1.4, color: '#555' }}>
|
<li key={f.name} style={{ fontSize: '0.7rem', lineHeight: 1.4, color: 'var(--text-secondary)' }}>
|
||||||
<span style={{ fontFamily: 'monospace', color: '#222' }}>{f.name}</span>
|
<span style={{ fontFamily: 'monospace', color: 'var(--text-primary)' }}>{f.name}</span>
|
||||||
<span style={{ color: '#999' }}>{`: ${f.type}`}</span>
|
<span style={{ color: 'var(--text-tertiary)' }}>{`: ${f.type}`}</span>
|
||||||
{!f.required && <span style={{ color: '#bbb' }}>{' (optional)'}</span>}
|
{!f.required && <span style={{ color: 'var(--text-tertiary)' }}>{' (optional)'}</span>}
|
||||||
{f.description && (
|
{f.description && (
|
||||||
<div style={{ color: '#888', marginLeft: 4 }}>
|
<div style={{ color: 'var(--text-secondary)', marginLeft: 4 }}>
|
||||||
{getLabel(f.description, language)}
|
{getLabel(f.description, language)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
/**
|
||||||
|
* FeatureInstancePicker — renderer for frontendType="featureInstance".
|
||||||
|
*
|
||||||
|
* Modeled on ConnectionPicker. Loads mandate-scoped FeatureInstances filtered
|
||||||
|
* by `frontendOptions.featureCode` (e.g. "trustee", "redmine") via
|
||||||
|
* GET /api/workflows/{instanceId}/options/feature.instance?featureCode=<code>
|
||||||
|
*
|
||||||
|
* Behavior matches the rest of the editor:
|
||||||
|
* - 0 results -> hint to create a feature instance for this mandate
|
||||||
|
* - 1 result -> auto-pick (no manual click required)
|
||||||
|
* - N results -> <select>
|
||||||
|
*
|
||||||
|
* The bound value is a plain `<id>` string so backend adapters can keep
|
||||||
|
* using `featureInstanceId` lookups unchanged. Type stays
|
||||||
|
* `FeatureInstanceRef[<code>]` on the parameter so DataPicker / RequiredAttributePicker
|
||||||
|
* filter correctly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
import type { FieldRendererProps } from './index';
|
||||||
|
|
||||||
|
type FeatureInstanceOption = { id: string; label: string };
|
||||||
|
|
||||||
|
export const FeatureInstancePicker: React.FC<FieldRendererProps> = ({
|
||||||
|
param,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
instanceId,
|
||||||
|
request,
|
||||||
|
}) => {
|
||||||
|
const { t } = useLanguage();
|
||||||
|
const featureCode =
|
||||||
|
(param.frontendOptions?.featureCode as string | undefined) || undefined;
|
||||||
|
const [instances, setInstances] = React.useState<FeatureInstanceOption[]>([]);
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
const [loadError, setLoadError] = React.useState<string | null>(null);
|
||||||
|
const autoSingleRef = React.useRef(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!instanceId || !request || !featureCode) return;
|
||||||
|
setLoading(true);
|
||||||
|
setLoadError(null);
|
||||||
|
request({
|
||||||
|
url: `/api/workflows/${instanceId}/options/feature.instance?featureCode=${encodeURIComponent(featureCode)}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
.then((res: unknown) => {
|
||||||
|
const data = res as { options?: Array<{ value: string; label: string }> };
|
||||||
|
setInstances((data?.options || []).map((o) => ({ id: o.value, label: o.label })));
|
||||||
|
})
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
console.error('FeatureInstancePicker: failed to load instances', err);
|
||||||
|
setInstances([]);
|
||||||
|
setLoadError(err instanceof Error ? err.message : String(err));
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [instanceId, request, featureCode]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (instances.length !== 1 || autoSingleRef.current) return;
|
||||||
|
if (value !== '' && value !== undefined && value !== null) return;
|
||||||
|
autoSingleRef.current = true;
|
||||||
|
onChange(instances[0].id);
|
||||||
|
}, [instances, value, onChange]);
|
||||||
|
|
||||||
|
const strVal = typeof value === 'string' ? value : '';
|
||||||
|
const codeLabel = featureCode ?? t('Feature');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 8, minWidth: 0, maxWidth: '100%' }}>
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
fontSize: 12,
|
||||||
|
marginBottom: 2,
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{param.description || param.name}
|
||||||
|
</label>
|
||||||
|
{loading && (
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--text-secondary)' }}>{t('Lade…')}</div>
|
||||||
|
)}
|
||||||
|
{!loading && instances.length === 0 && !loadError && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: 'var(--text-secondary)',
|
||||||
|
marginBottom: 4,
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Keine {code}-Instanz im aktiven Mandanten — bitte in der Admin-Konsole anlegen.', { code: codeLabel })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!loading && instances.length === 1 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
marginBottom: 4,
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
background: 'var(--bg-secondary)',
|
||||||
|
border: '1px solid var(--border-color)',
|
||||||
|
borderRadius: 4,
|
||||||
|
padding: '4px 8px',
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
}}
|
||||||
|
title={`${t('Einziger {code}-Mandant — automatisch gewählt.', { code: codeLabel })} — ${instances[0].label}`}
|
||||||
|
>
|
||||||
|
{instances[0].label}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!loading && instances.length > 1 && (
|
||||||
|
<select
|
||||||
|
value={strVal}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '100%',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
padding: '4px 8px',
|
||||||
|
borderRadius: 4,
|
||||||
|
border: '1px solid var(--border-color)',
|
||||||
|
background: 'var(--bg-primary)',
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="">{t('{code}-Mandant wählen', { code: codeLabel })}</option>
|
||||||
|
{instances.map((c) => (
|
||||||
|
<option key={c.id} value={c.id}>{c.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
{loadError && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: 'var(--danger-color, #c00)',
|
||||||
|
marginTop: 2,
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Mandanten-Liste konnte nicht geladen werden')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FeatureInstancePicker;
|
||||||
|
|
@ -31,6 +31,7 @@ import { toApiGraph } from '../shared/graphUtils';
|
||||||
import { postUpstreamPaths } from '../../../../api/workflowApi';
|
import { postUpstreamPaths } from '../../../../api/workflowApi';
|
||||||
import type { CanvasNode } from '../../editor/FlowCanvas';
|
import type { CanvasNode } from '../../editor/FlowCanvas';
|
||||||
import { DataRefRenderer } from './DataRefRenderer';
|
import { DataRefRenderer } from './DataRefRenderer';
|
||||||
|
import { FeatureInstancePicker } from './FeatureInstancePicker';
|
||||||
|
|
||||||
const TextInput: React.FC<FieldRendererProps> = ({ param, value, onChange }) => (
|
const TextInput: React.FC<FieldRendererProps> = ({ param, value, onChange }) => (
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
|
@ -755,6 +756,7 @@ export const FRONTEND_TYPE_RENDERERS: Record<string, FieldRendererComponent> = {
|
||||||
hidden: HiddenInput,
|
hidden: HiddenInput,
|
||||||
dataRef: DataRefRenderer,
|
dataRef: DataRefRenderer,
|
||||||
userConnection: ConnectionPicker,
|
userConnection: ConnectionPicker,
|
||||||
|
featureInstance: FeatureInstancePicker,
|
||||||
sharepointFolder: SharepointPathPicker,
|
sharepointFolder: SharepointPathPicker,
|
||||||
sharepointFile: SharepointPathPicker,
|
sharepointFile: SharepointPathPicker,
|
||||||
clickupList: FolderPicker,
|
clickupList: FolderPicker,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
import { createRef, createSystemVar, type DataRef, type SystemVarRef, isCompatible } from './dataRef';
|
import { createRef, createSystemVar, type DataRef, type SystemVarRef, isCompatible } from './dataRef';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
||||||
import type { GraphDefinedSchemaRef, NodeType, PortSchema } from '../../../../api/workflowApi';
|
import type { GraphDefinedSchemaRef, NodeType, PortSchema } from '../../../../api/workflowApi';
|
||||||
|
|
@ -254,33 +255,35 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const _dialog = (
|
||||||
<div className={styles.dataPickerOverlay} onClick={onClose}>
|
<div
|
||||||
<div className={styles.dataPickerModal} onClick={(e) => e.stopPropagation()}>
|
className={styles.dataPickerOverlay}
|
||||||
|
onClick={onClose}
|
||||||
|
onKeyDown={(e) => e.key === 'Escape' && onClose()}
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={styles.dataPickerModal}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="automation2DataPickerTitle"
|
||||||
|
>
|
||||||
<div className={styles.dataPickerHeader}>
|
<div className={styles.dataPickerHeader}>
|
||||||
<h4 className={styles.dataPickerTitle}>
|
<h4 className={styles.dataPickerTitle} id="automation2DataPickerTitle">
|
||||||
{t('Datenquelle wählen')}
|
{t('Datenquelle wählen')}
|
||||||
{expectedParamType && (
|
{expectedParamType && (
|
||||||
<span
|
<span
|
||||||
style={{
|
className={styles.dataPickerTypeBadge}
|
||||||
marginLeft: 8,
|
|
||||||
fontSize: 11,
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
color: '#555',
|
|
||||||
background: '#eee',
|
|
||||||
borderRadius: 4,
|
|
||||||
padding: '1px 6px',
|
|
||||||
fontWeight: 400,
|
|
||||||
}}
|
|
||||||
title={t('Erwarteter Typ')}
|
title={t('Erwarteter Typ')}
|
||||||
>
|
>
|
||||||
{expectedParamType}
|
{expectedParamType}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</h4>
|
</h4>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div className={styles.dataPickerHeaderControls}>
|
||||||
{expectedParamType && (
|
{expectedParamType && (
|
||||||
<label style={{ fontSize: 11, color: '#666', display: 'flex', alignItems: 'center', gap: 4 }}>
|
<label className={styles.dataPickerStrictLabel}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={strictFilter}
|
checked={strictFilter}
|
||||||
|
|
@ -315,7 +318,7 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<div key={loopId} style={{ marginBottom: 6 }}>
|
<div key={loopId} style={{ marginBottom: 6 }}>
|
||||||
<div style={{ fontSize: 11, color: '#666', marginBottom: 2 }}>{loopLabel}</div>
|
<div style={{ fontSize: 11, color: 'var(--text-secondary)', marginBottom: 2 }}>{loopLabel}</div>
|
||||||
{loopPaths.map((p, i) => {
|
{loopPaths.map((p, i) => {
|
||||||
const compat = expectedParamType && p.type
|
const compat = expectedParamType && p.type
|
||||||
? isCompatible(p.type, expectedParamType)
|
? isCompatible(p.type, expectedParamType)
|
||||||
|
|
@ -330,7 +333,7 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
>
|
>
|
||||||
{p.label}
|
{p.label}
|
||||||
{p.type && (
|
{p.type && (
|
||||||
<span style={{ color: '#888', fontSize: 10, marginLeft: 4 }}>
|
<span className={styles.dataPickerLeafType}>
|
||||||
({p.type})
|
({p.type})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -364,7 +367,7 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
onClick={() => handlePickSystemVar(key)}
|
onClick={() => handlePickSystemVar(key)}
|
||||||
title={info.description}
|
title={info.description}
|
||||||
>
|
>
|
||||||
{key} <span style={{ color: '#888', fontSize: 10 }}>({info.type})</span>
|
{key} <span className={styles.dataPickerLeafType}>({info.type})</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -418,7 +421,7 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
<span className={styles.dataPickerExpandIcon}>{isExpanded ? '▼' : '▶'}</span>
|
<span className={styles.dataPickerExpandIcon}>{isExpanded ? '▼' : '▶'}</span>
|
||||||
<span className={styles.dataPickerNodeLabel}>{label}</span>
|
<span className={styles.dataPickerNodeLabel}>{label}</span>
|
||||||
{resolvedSchema && (
|
{resolvedSchema && (
|
||||||
<span style={{ color: '#888', fontSize: 10, marginLeft: 4 }}>
|
<span className={styles.dataPickerNodeSchemaHint}>
|
||||||
({resolvedSchema.name})
|
({resolvedSchema.name})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -426,7 +429,7 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div className={styles.dataPickerTree}>
|
<div className={styles.dataPickerTree}>
|
||||||
{paths.length === 0 && (
|
{paths.length === 0 && (
|
||||||
<div style={{ fontSize: 11, color: '#999', padding: '4px 8px' }}>
|
<div style={{ fontSize: 11, color: 'var(--text-secondary)', padding: '4px 8px' }}>
|
||||||
{t('(keine kompatiblen Felder — Filter „Nur kompatible“ deaktivieren)')}
|
{t('(keine kompatiblen Felder — Filter „Nur kompatible“ deaktivieren)')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -446,7 +449,7 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
>
|
>
|
||||||
{p.label}
|
{p.label}
|
||||||
{p.type && (
|
{p.type && (
|
||||||
<span style={{ color: '#888', fontSize: 10, marginLeft: 4 }}>
|
<span className={styles.dataPickerLeafType}>
|
||||||
({p.type})
|
({p.type})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -454,15 +457,8 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
{p.iterable && (
|
{p.iterable && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.dataPickerLeaf}
|
className={`${styles.dataPickerLeaf} ${styles.dataPickerIterateBtn}`}
|
||||||
onClick={() => handlePickIterate(nodeId, p.path, expectedParamType)}
|
onClick={() => handlePickIterate(nodeId, p.path, expectedParamType)}
|
||||||
style={{
|
|
||||||
fontSize: 10,
|
|
||||||
padding: '2px 6px',
|
|
||||||
background: 'rgba(0,123,255,0.10)',
|
|
||||||
color: 'var(--primary-color, #007bff)',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
}}
|
|
||||||
title={t('Pro Element der Liste iterieren (Loop)')}
|
title={t('Pro Element der Liste iterieren (Loop)')}
|
||||||
>
|
>
|
||||||
{t('iterieren')}
|
{t('iterieren')}
|
||||||
|
|
@ -481,4 +477,6 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return createPortal(_dialog, document.body);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,30 @@ export const RequiredAttributePicker: React.FC<RequiredAttributePickerProps> = (
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.requiredAttributePicker} style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
<div
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
className={styles.requiredAttributePicker}
|
||||||
<label style={{ fontSize: 12, fontWeight: 600 }}>
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
minWidth: 0,
|
||||||
|
maxWidth: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header: label always takes the full row (flex-basis 100 %), badge
|
||||||
|
wraps below — prevents long type names like List[ActionDocument]
|
||||||
|
from escaping the panel frame on the right. */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'baseline', gap: 6, flexWrap: 'wrap' }}>
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
flex: '1 1 100%',
|
||||||
|
minWidth: 0,
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
<span style={{ color: 'var(--danger-color, #dc3545)', marginLeft: 2 }}>*</span>
|
<span style={{ color: 'var(--danger-color, #dc3545)', marginLeft: 2 }}>*</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -103,10 +124,15 @@ export const RequiredAttributePicker: React.FC<RequiredAttributePickerProps> = (
|
||||||
style={{
|
style={{
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
color: '#555',
|
color: 'var(--text-secondary, #555)',
|
||||||
background: '#eee',
|
background: 'var(--bg-secondary, #eee)',
|
||||||
|
border: '1px solid var(--border-color, #e0e0e0)',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
padding: '1px 6px',
|
padding: '1px 6px',
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{expectedType}
|
{expectedType}
|
||||||
|
|
@ -115,8 +141,9 @@ export const RequiredAttributePicker: React.FC<RequiredAttributePickerProps> = (
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isBoundRef ? (
|
{isBoundRef ? (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap', minWidth: 0 }}>
|
||||||
<span
|
<span
|
||||||
|
title={typeof boundLabel === 'string' ? boundLabel : undefined}
|
||||||
style={{
|
style={{
|
||||||
padding: '2px 8px',
|
padding: '2px 8px',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
|
|
@ -124,6 +151,10 @@ export const RequiredAttributePicker: React.FC<RequiredAttributePickerProps> = (
|
||||||
color: 'var(--success-color, #28a745)',
|
color: 'var(--success-color, #28a745)',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
lineHeight: 1.4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{boundLabel}
|
{boundLabel}
|
||||||
|
|
@ -132,7 +163,7 @@ export const RequiredAttributePicker: React.FC<RequiredAttributePickerProps> = (
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.retryButton}
|
className={styles.retryButton}
|
||||||
onClick={() => setPickerOpen(true)}
|
onClick={() => setPickerOpen(true)}
|
||||||
style={{ fontSize: 11, padding: '2px 8px' }}
|
style={{ fontSize: 11, padding: '2px 8px', flexShrink: 0 }}
|
||||||
>
|
>
|
||||||
{t('Andere wählen…')}
|
{t('Andere wählen…')}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -140,7 +171,7 @@ export const RequiredAttributePicker: React.FC<RequiredAttributePickerProps> = (
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.retryButton}
|
className={styles.retryButton}
|
||||||
onClick={() => onChange(null)}
|
onClick={() => onChange(null)}
|
||||||
style={{ fontSize: 11, padding: '2px 8px' }}
|
style={{ fontSize: 11, padding: '2px 8px', flexShrink: 0 }}
|
||||||
title={t('Bindung entfernen')}
|
title={t('Bindung entfernen')}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
|
|
@ -150,29 +181,41 @@ export const RequiredAttributePicker: React.FC<RequiredAttributePickerProps> = (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'flex-start',
|
||||||
gap: 6,
|
gap: 6,
|
||||||
padding: '4px 8px',
|
padding: '4px 8px',
|
||||||
background: 'rgba(220,53,69,0.12)',
|
background: 'rgba(220,53,69,0.12)',
|
||||||
color: 'var(--danger-color, #dc3545)',
|
color: 'var(--danger-color, #dc3545)',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
minWidth: 0,
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">⚠</span>
|
<span aria-hidden="true" style={{ flexShrink: 0 }}>⚠</span>
|
||||||
<span>
|
<span style={{ minWidth: 0 }}>
|
||||||
{t('Keine typkompatible Quelle vorhanden — füge zuerst einen Knoten ein, der ')}
|
{t('Keine typkompatible Quelle vorhanden — füge zuerst einen Knoten ein, der ')}
|
||||||
<code style={{ fontFamily: 'monospace' }}>{expectedType ?? '?'}</code>
|
<code style={{ fontFamily: 'monospace', overflowWrap: 'anywhere' }}>{expectedType ?? '?'}</code>
|
||||||
{t(' liefert.')}
|
{t(' liefert.')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : single ? (
|
) : single ? (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap', minWidth: 0 }}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.retryButton}
|
className={styles.retryButton}
|
||||||
onClick={handleAutoBind}
|
onClick={handleAutoBind}
|
||||||
style={{ fontSize: 11, padding: '3px 10px' }}
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
padding: '3px 10px',
|
||||||
|
maxWidth: '100%',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
textAlign: 'left',
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
}}
|
||||||
title={t('Einzige passende Quelle übernehmen')}
|
title={t('Einzige passende Quelle übernehmen')}
|
||||||
>
|
>
|
||||||
{t('Vorschlag übernehmen:')}{' '}
|
{t('Vorschlag übernehmen:')}{' '}
|
||||||
|
|
@ -186,25 +229,36 @@ export const RequiredAttributePicker: React.FC<RequiredAttributePickerProps> = (
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.retryButton}
|
className={styles.retryButton}
|
||||||
onClick={() => setPickerOpen(true)}
|
onClick={() => setPickerOpen(true)}
|
||||||
style={{ fontSize: 11, padding: '3px 10px' }}
|
style={{ fontSize: 11, padding: '3px 10px', flexShrink: 0 }}
|
||||||
>
|
>
|
||||||
{t('Andere…')}
|
{t('Andere…')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap', minWidth: 0 }}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.retryButton}
|
className={styles.retryButton}
|
||||||
onClick={() => setPickerOpen(true)}
|
onClick={() => setPickerOpen(true)}
|
||||||
style={{ fontSize: 11, padding: '3px 10px' }}
|
style={{ fontSize: 11, padding: '3px 10px', maxWidth: '100%' }}
|
||||||
>
|
>
|
||||||
{t('Quelle wählen…')} <span style={{ opacity: 0.6 }}>({candidateCount})</span>
|
{t('Quelle wählen…')} <span style={{ opacity: 0.6 }}>({candidateCount})</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{description && <div style={{ fontSize: 11, color: '#888' }}>{description}</div>}
|
{description && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: 'var(--text-tertiary, #888)',
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{pickerOpen && (
|
{pickerOpen && (
|
||||||
<DataPicker
|
<DataPicker
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,20 @@ describe('findRequiredErrors', () => {
|
||||||
const node = _makeNode('n1', 'ghost.node');
|
const node = _makeNode('n1', 'ghost.node');
|
||||||
expect(findRequiredErrors(node, undefined)).toEqual([]);
|
expect(findRequiredErrors(node, undefined)).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('skips required params with frontendType="hidden" (UI safety net)', () => {
|
||||||
|
// Hidden params have no UI surface, so reporting them as
|
||||||
|
// "Pflichtfeld ohne Quelle" would create a phantom error the user can
|
||||||
|
// not resolve. They are auto-set by adapters / system defaults.
|
||||||
|
const node = _makeNode('n1', 'trustee.extractFromFiles', {});
|
||||||
|
const nodeType = _makeNodeType('trustee.extractFromFiles', 'AiResult', [
|
||||||
|
{ name: 'prompt', type: 'str', required: true },
|
||||||
|
{ name: 'systemContext', type: 'str', required: true, frontendType: 'hidden' },
|
||||||
|
]);
|
||||||
|
const errs = findRequiredErrors(node, nodeType);
|
||||||
|
expect(errs).toHaveLength(1);
|
||||||
|
expect(errs[0]!.paramName).toBe('prompt');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('findGraphErrors', () => {
|
describe('findGraphErrors', () => {
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,14 @@ export interface RequiredParamError {
|
||||||
paramType?: string;
|
paramType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Walk a node's parameter spec + values and flag every required-but-unbound. */
|
/** Walk a node's parameter spec + values and flag every required-but-unbound.
|
||||||
|
*
|
||||||
|
* Safety net: params with `frontendType: 'hidden'` are excluded — they have
|
||||||
|
* no UI surface (the panel skips them entirely), so reporting them as
|
||||||
|
* "Pflichtfeld ohne Quelle" would create a phantom error the user cannot
|
||||||
|
* resolve. Hidden-required params should be auto-set by the adapter or
|
||||||
|
* caught in tests, never surfaced to end users.
|
||||||
|
*/
|
||||||
export function findRequiredErrors(
|
export function findRequiredErrors(
|
||||||
node: CanvasNode,
|
node: CanvasNode,
|
||||||
nodeType: NodeType | undefined,
|
nodeType: NodeType | undefined,
|
||||||
|
|
@ -75,6 +82,7 @@ export function findRequiredErrors(
|
||||||
const values = node.parameters ?? {};
|
const values = node.parameters ?? {};
|
||||||
for (const param of nodeType.parameters ?? []) {
|
for (const param of nodeType.parameters ?? []) {
|
||||||
if (!param.required) continue;
|
if (!param.required) continue;
|
||||||
|
if (param.frontendType === 'hidden') continue;
|
||||||
if (isParamBound(values[param.name])) continue;
|
if (isParamBound(values[param.name])) continue;
|
||||||
errors.push({ paramName: param.name, paramLabel: resolveLabel(param), paramType: param.type });
|
errors.push({ paramName: param.name, paramLabel: resolveLabel(param), paramType: param.type });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -338,12 +338,6 @@
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fkLoading {
|
|
||||||
color: var(--color-text);
|
|
||||||
opacity: 0.6;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rows */
|
/* Rows */
|
||||||
.tr {
|
.tr {
|
||||||
transition: background-color 0.12s ease;
|
transition: background-color 0.12s ease;
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,11 @@ import type { AttributeType } from '../../../utils/attributeTypeMapper';
|
||||||
import { FaFilter } from 'react-icons/fa';
|
import { FaFilter } from 'react-icons/fa';
|
||||||
import api from '../../../api';
|
import api from '../../../api';
|
||||||
|
|
||||||
// FK Cache type: maps fkSource -> { id -> displayLabel }
|
/** A filter value can be a plain string, null (for empty/missing), or a
|
||||||
type FkCacheType = Record<string, Record<string, string>>;
|
* {value, label} object returned by FK-aware filter-values endpoints. */
|
||||||
|
type FilterValue = string | null | { value: string | null; label: string };
|
||||||
|
|
||||||
|
const _EMPTY_FILTER_SENTINEL = '__EMPTY__';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stringify any cell value for display.
|
* Stringify any cell value for display.
|
||||||
|
|
@ -111,8 +114,10 @@ export interface ColumnConfig {
|
||||||
filterOptions?: string[]; // For enum/select filters
|
filterOptions?: string[]; // For enum/select filters
|
||||||
filterLabelResolver?: (value: string) => string; // Map filter value to display label in dropdown
|
filterLabelResolver?: (value: string) => string; // Map filter value to display label in dropdown
|
||||||
cellClassName?: (value: any, row: any) => string; // For custom cell styling
|
cellClassName?: (value: any, row: any) => string; // For custom cell styling
|
||||||
fkSource?: string; // API endpoint for FK resolution (e.g., "/api/users/")
|
|
||||||
fkDisplayField?: string; // Which field of FK target to display (e.g., "username", "name", "roleLabel")
|
/** Backend-enriched label column, e.g. `mandateId` → `mandateIdLabel` (set by `attributeUtils` / API). */
|
||||||
|
displayField?: string;
|
||||||
|
|
||||||
// Backend-provided render hints (gateway/.../attributeUtils.py).
|
// Backend-provided render hints (gateway/.../attributeUtils.py).
|
||||||
// Excel-style format string applied by ``applyFrontendFormat`` to numeric/int
|
// Excel-style format string applied by ``applyFrontendFormat`` to numeric/int
|
||||||
// values, e.g. "R:#'###.00", "M:b" (bytes), "L:0.000". Empty = default rendering.
|
// values, e.g. "R:#'###.00", "M:b" (bytes), "L:0.000". Empty = default rendering.
|
||||||
|
|
@ -211,9 +216,29 @@ export interface FormGeneratorTableProps<T = any> {
|
||||||
|
|
||||||
const _FILTER_PAGE_SIZE = 100;
|
const _FILTER_PAGE_SIZE = 100;
|
||||||
|
|
||||||
|
/** Normalize a FilterValue to {value, label}. */
|
||||||
|
function _normalizeFilterValue(
|
||||||
|
fv: FilterValue,
|
||||||
|
resolveLabel?: (value: string) => string,
|
||||||
|
): { value: string | null; label: string } {
|
||||||
|
if (fv === null) {
|
||||||
|
return { value: null, label: '(Leer)' };
|
||||||
|
}
|
||||||
|
if (typeof fv === 'object' && 'value' in fv && 'label' in fv) {
|
||||||
|
return fv as { value: string | null; label: string };
|
||||||
|
}
|
||||||
|
const str = String(fv);
|
||||||
|
return { value: str, label: resolveLabel ? resolveLabel(str) : str };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a scrollable list of filter values with IntersectionObserver-based lazy loading.
|
* Renders a scrollable list of filter values with IntersectionObserver-based lazy loading.
|
||||||
* Shows _FILTER_PAGE_SIZE items initially, loads more as the user scrolls.
|
* Shows _FILTER_PAGE_SIZE items initially, loads more as the user scrolls.
|
||||||
|
*
|
||||||
|
* Supports three value formats:
|
||||||
|
* - `string` — plain value (legacy)
|
||||||
|
* - `null` — empty/NULL sentinel (rendered as "(Leer)")
|
||||||
|
* - `{value, label}` — backend-resolved FK with display label
|
||||||
*/
|
*/
|
||||||
function FilterValuesList({
|
function FilterValuesList({
|
||||||
columnKey,
|
columnKey,
|
||||||
|
|
@ -223,7 +248,7 @@ function FilterValuesList({
|
||||||
resolveLabel,
|
resolveLabel,
|
||||||
}: {
|
}: {
|
||||||
columnKey: string;
|
columnKey: string;
|
||||||
allValues: string[];
|
allValues: FilterValue[];
|
||||||
activeFilter: any;
|
activeFilter: any;
|
||||||
onSelect: (value: string) => void;
|
onSelect: (value: string) => void;
|
||||||
resolveLabel?: (value: string) => string;
|
resolveLabel?: (value: string) => string;
|
||||||
|
|
@ -242,14 +267,19 @@ function FilterValuesList({
|
||||||
searchInputRef.current?.focus();
|
searchInputRef.current?.focus();
|
||||||
}, [columnKey]);
|
}, [columnKey]);
|
||||||
|
|
||||||
|
// Normalize all values to {value, label} pairs, filtering out nulls
|
||||||
|
// (null entries are handled separately as the "(Leer)" option)
|
||||||
|
const normalizedValues = useMemo(() => {
|
||||||
|
return allValues
|
||||||
|
.filter(fv => fv !== null)
|
||||||
|
.map(fv => _normalizeFilterValue(fv, resolveLabel));
|
||||||
|
}, [allValues, resolveLabel]);
|
||||||
|
|
||||||
const filteredValues = useMemo(() => {
|
const filteredValues = useMemo(() => {
|
||||||
if (!searchTerm.trim()) return allValues;
|
if (!searchTerm.trim()) return normalizedValues;
|
||||||
const term = searchTerm.toLowerCase();
|
const term = searchTerm.toLowerCase();
|
||||||
return allValues.filter(value => {
|
return normalizedValues.filter(nv => nv.label.toLowerCase().includes(term));
|
||||||
const label = resolveLabel ? resolveLabel(value) : value;
|
}, [normalizedValues, searchTerm]);
|
||||||
return label.toLowerCase().includes(term);
|
|
||||||
});
|
|
||||||
}, [allValues, searchTerm, resolveLabel]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sentinel = sentinelRef.current;
|
const sentinel = sentinelRef.current;
|
||||||
|
|
@ -293,16 +323,16 @@ function FilterValuesList({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{visibleValues.map(value => {
|
{visibleValues.map(nv => {
|
||||||
const label = resolveLabel ? resolveLabel(value) : value;
|
const selectValue = nv.value === null ? _EMPTY_FILTER_SENTINEL : nv.value;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={value}
|
key={nv.value ?? '__null__'}
|
||||||
className={`${styles.filterOption} ${activeFilter === value ? styles.filterOptionSelected : ''}`}
|
className={`${styles.filterOption} ${activeFilter === nv.value ? styles.filterOptionSelected : ''}`}
|
||||||
onClick={() => onSelect(value)}
|
onClick={() => onSelect(selectValue)}
|
||||||
title={label}
|
title={nv.label}
|
||||||
>
|
>
|
||||||
{label.length > 30 ? label.substring(0, 30) + '...' : label}
|
{nv.label.length > 30 ? nv.label.substring(0, 30) + '...' : nv.label}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -519,11 +549,6 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(() => new Set());
|
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(() => new Set());
|
||||||
const [groupsInitialized, setGroupsInitialized] = useState(false);
|
const [groupsInitialized, setGroupsInitialized] = useState(false);
|
||||||
|
|
||||||
// FK Resolution: Cache for resolved FK values (fkSource -> { id -> displayLabel })
|
|
||||||
const [fkCache, setFkCache] = useState<FkCacheType>({});
|
|
||||||
const [fkLoading, setFkLoading] = useState<Record<string, boolean>>({});
|
|
||||||
const fkLoadedSourcesRef = useRef<Set<string>>(new Set());
|
|
||||||
|
|
||||||
// Generate a storage key based on column names for localStorage persistence
|
// Generate a storage key based on column names for localStorage persistence
|
||||||
const storageKey = useMemo(() => {
|
const storageKey = useMemo(() => {
|
||||||
if (detectedColumns.length === 0) return null;
|
if (detectedColumns.length === 0) return null;
|
||||||
|
|
@ -586,7 +611,7 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
// Note: Date/timestamp filters are disabled in column config, so they won't appear here
|
// Note: Date/timestamp filters are disabled in column config, so they won't appear here
|
||||||
const activeFilters: Record<string, any> = {};
|
const activeFilters: Record<string, any> = {};
|
||||||
Object.entries(filters).forEach(([key, value]) => {
|
Object.entries(filters).forEach(([key, value]) => {
|
||||||
if (value !== undefined && value !== '') {
|
if (value !== undefined) {
|
||||||
activeFilters[key] = value;
|
activeFilters[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -739,133 +764,6 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
}
|
}
|
||||||
}).current;
|
}).current;
|
||||||
|
|
||||||
const convertToDisplayString = useCallback((fieldValue: any, _language: string): string => {
|
|
||||||
if (fieldValue === null || fieldValue === undefined) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boolean → language-neutral symbols (✓/✗)
|
|
||||||
if (typeof fieldValue === 'boolean') {
|
|
||||||
return fieldValue ? '✓' : '✗';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number → String
|
|
||||||
if (typeof fieldValue === 'number') {
|
|
||||||
return String(fieldValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// String → direct
|
|
||||||
if (typeof fieldValue === 'string') {
|
|
||||||
return fieldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof fieldValue === 'object' && fieldValue !== null) {
|
|
||||||
return _objectToDisplayString(fieldValue as Record<string, unknown>);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback
|
|
||||||
return String(fieldValue);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// FK Resolution: Load FK data in bulk for columns with fkSource
|
|
||||||
useEffect(() => {
|
|
||||||
if (data.length === 0 || detectedColumns.length === 0) return;
|
|
||||||
|
|
||||||
// Find columns with fkSource that haven't been loaded yet
|
|
||||||
const fkColumns = detectedColumns.filter(col =>
|
|
||||||
col.fkSource && !fkLoadedSourcesRef.current.has(col.fkSource)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (fkColumns.length === 0) return;
|
|
||||||
|
|
||||||
// For each FK column, collect unique IDs from data and fetch them
|
|
||||||
const loadFkData = async () => {
|
|
||||||
for (const column of fkColumns) {
|
|
||||||
const fkSource = column.fkSource!;
|
|
||||||
const displayField = column.fkDisplayField; // Explicit field from Pydantic model
|
|
||||||
|
|
||||||
// Skip if already loading
|
|
||||||
if (fkLoading[fkSource]) continue;
|
|
||||||
|
|
||||||
// Collect unique IDs from data for this column
|
|
||||||
const uniqueIds = new Set<string>();
|
|
||||||
data.forEach(row => {
|
|
||||||
const value = row[column.key];
|
|
||||||
if (value && typeof value === 'string' && value.length > 0) {
|
|
||||||
uniqueIds.add(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (uniqueIds.size === 0) {
|
|
||||||
fkLoadedSourcesRef.current.add(fkSource);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark as loading
|
|
||||||
setFkLoading(prev => ({ ...prev, [fkSource]: true }));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Fetch all items from the FK source endpoint
|
|
||||||
const response = await api.get(fkSource);
|
|
||||||
|
|
||||||
// Build cache: id -> display label
|
|
||||||
const cacheForSource: Record<string, string> = {};
|
|
||||||
const items = Array.isArray(response.data) ? response.data : response.data?.items || [];
|
|
||||||
|
|
||||||
items.forEach((item: any) => {
|
|
||||||
if (!item || !item.id) return;
|
|
||||||
|
|
||||||
let displayLabel = item.id; // Fallback to ID
|
|
||||||
|
|
||||||
// Use the EXPLICIT display field from Pydantic model (fkDisplayField)
|
|
||||||
if (displayField && item[displayField] != null && item[displayField] !== '') {
|
|
||||||
displayLabel = convertToDisplayString(item[displayField], currentLanguage);
|
|
||||||
} else {
|
|
||||||
// Fallback: if no displayField specified, try common fields
|
|
||||||
// This should rarely happen if models are properly configured
|
|
||||||
const fallbackFields = ['name', 'label', 'username', 'roleLabel', 'title'];
|
|
||||||
for (const field of fallbackFields) {
|
|
||||||
if (item[field] !== undefined) {
|
|
||||||
displayLabel = convertToDisplayString(item[field], currentLanguage);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheForSource[item.id] = displayLabel;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update cache
|
|
||||||
setFkCache(prev => ({
|
|
||||||
...prev,
|
|
||||||
[fkSource]: { ...(prev[fkSource] || {}), ...cacheForSource }
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mark as loaded
|
|
||||||
fkLoadedSourcesRef.current.add(fkSource);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to load FK data from ${fkSource}:`, error);
|
|
||||||
// Mark as loaded to prevent infinite retries
|
|
||||||
fkLoadedSourcesRef.current.add(fkSource);
|
|
||||||
} finally {
|
|
||||||
setFkLoading(prev => ({ ...prev, [fkSource]: false }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadFkData();
|
|
||||||
}, [data, detectedColumns, currentLanguage, fkLoading, convertToDisplayString]);
|
|
||||||
|
|
||||||
// Helper function to resolve FK value to display label
|
|
||||||
const resolveFkValue = useCallback((value: string, fkSource: string): string => {
|
|
||||||
const sourceCache = fkCache[fkSource];
|
|
||||||
if (sourceCache && sourceCache[value]) {
|
|
||||||
return sourceCache[value];
|
|
||||||
}
|
|
||||||
// Return truncated ID while loading or if not found
|
|
||||||
return value.length > 8 ? `${value.substring(0, 8)}...` : value;
|
|
||||||
}, [fkCache]);
|
|
||||||
|
|
||||||
// Data is already filtered, sorted, and paginated by the backend.
|
// Data is already filtered, sorted, and paginated by the backend.
|
||||||
// Client-side only filters out rows that were just optimistically deleted
|
// Client-side only filters out rows that were just optimistically deleted
|
||||||
// so the UI updates instantly before the server's next refetch response.
|
// so the UI updates instantly before the server's next refetch response.
|
||||||
|
|
@ -986,8 +884,10 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
const handleFilter = (key: string, value: any, keepOpen = false) => {
|
const handleFilter = (key: string, value: any, keepOpen = false) => {
|
||||||
setFilters(prev => {
|
setFilters(prev => {
|
||||||
const newFilters = { ...prev };
|
const newFilters = { ...prev };
|
||||||
if (value === undefined || value === '' || value === null) {
|
if (value === undefined) {
|
||||||
delete newFilters[key];
|
delete newFilters[key];
|
||||||
|
} else if (value === _EMPTY_FILTER_SENTINEL) {
|
||||||
|
newFilters[key] = null;
|
||||||
} else {
|
} else {
|
||||||
newFilters[key] = value;
|
newFilters[key] = value;
|
||||||
}
|
}
|
||||||
|
|
@ -1024,7 +924,7 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
// Track which filter columns show all values (expanded beyond initial 100)
|
// Track which filter columns show all values (expanded beyond initial 100)
|
||||||
const [asyncFilterValues, setAsyncFilterValues] = useState<Record<string, string[]>>({});
|
const [asyncFilterValues, setAsyncFilterValues] = useState<Record<string, FilterValue[]>>({});
|
||||||
const [filterValuesLoading, setFilterValuesLoading] = useState<Record<string, boolean>>({});
|
const [filterValuesLoading, setFilterValuesLoading] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
// Invalidate cached filter values when filters change (cross-filtering)
|
// Invalidate cached filter values when filters change (cross-filtering)
|
||||||
|
|
@ -1045,9 +945,8 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
// Skip if column has static filterOptions (enum) – those are used directly
|
// Skip if column has static filterOptions (enum) – those are used directly
|
||||||
if (column?.filterOptions && column.filterOptions.length > 0) return;
|
if (column?.filterOptions && column.filterOptions.length > 0) return;
|
||||||
|
|
||||||
// FK columns with backend pagination: still fetch from backend (data is only one page)
|
// displayField + local full dataset: filter values are derived from `data` (see getUniqueValuesForColumn)
|
||||||
// FK columns without backend pagination: skip (data is the full dataset, extracted below)
|
if (column?.displayField && !supportsBackendPagination) return;
|
||||||
if (column?.fkSource && !supportsBackendPagination) return;
|
|
||||||
|
|
||||||
// Skip if already loaded or currently loading
|
// Skip if already loaded or currently loading
|
||||||
if (asyncFilterValues[openFilterColumn] || filterValuesLoading[openFilterColumn]) return;
|
if (asyncFilterValues[openFilterColumn] || filterValuesLoading[openFilterColumn]) return;
|
||||||
|
|
@ -1055,7 +954,7 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
const _fetchValues = async (columnKey: string) => {
|
const _fetchValues = async (columnKey: string) => {
|
||||||
setFilterValuesLoading(prev => ({ ...prev, [columnKey]: true }));
|
setFilterValuesLoading(prev => ({ ...prev, [columnKey]: true }));
|
||||||
try {
|
try {
|
||||||
let values: string[];
|
let values: FilterValue[];
|
||||||
if (hookData?.fetchFilterValues && typeof hookData.fetchFilterValues === 'function') {
|
if (hookData?.fetchFilterValues && typeof hookData.fetchFilterValues === 'function') {
|
||||||
const crossFilters: Record<string, any> = {};
|
const crossFilters: Record<string, any> = {};
|
||||||
Object.entries(filters).forEach(([k, v]) => {
|
Object.entries(filters).forEach(([k, v]) => {
|
||||||
|
|
@ -1089,27 +988,28 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
// 3) data — ONLY when no backend pagination (data = full dataset)
|
// 3) data — ONLY when no backend pagination (data = full dataset)
|
||||||
// With backend pagination, data is a single page, so extracting filter
|
// With backend pagination, data is a single page, so extracting filter
|
||||||
// values from it would be incomplete and misleading.
|
// values from it would be incomplete and misleading.
|
||||||
const getUniqueValuesForColumn = useCallback((columnKey: string): string[] => {
|
const getUniqueValuesForColumn = useCallback((columnKey: string): FilterValue[] => {
|
||||||
const column = detectedColumns.find(c => c.key === columnKey);
|
const column = detectedColumns.find(c => c.key === columnKey);
|
||||||
|
|
||||||
if (column?.filterOptions && column.filterOptions.length > 0) {
|
if (column?.filterOptions && column.filterOptions.length > 0) {
|
||||||
return column.filterOptions;
|
return column.filterOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FK columns without backend pagination: extract from full local data
|
// displayField + local full dataset: { value, label } from enriched rows
|
||||||
if (column?.fkSource && !supportsBackendPagination) {
|
if (column?.displayField && !supportsBackendPagination) {
|
||||||
const seen = new Set<string>();
|
const showKey = column.displayField;
|
||||||
|
const byVal = new Map<string, string>();
|
||||||
data.forEach(row => {
|
data.forEach(row => {
|
||||||
const val = row[columnKey];
|
const val = row[columnKey];
|
||||||
if (val && typeof val === 'string' && val.trim()) {
|
if (val == null || val === '') return;
|
||||||
seen.add(val);
|
const raw = String(val);
|
||||||
}
|
const d = row[showKey];
|
||||||
});
|
const label = d != null && d !== '' ? String(d) : `NA(${raw})`;
|
||||||
return Array.from(seen).sort((a, b) => {
|
if (!byVal.has(raw)) byVal.set(raw, label);
|
||||||
const labelA = fkCache[column.fkSource!]?.[a] || a;
|
|
||||||
const labelB = fkCache[column.fkSource!]?.[b] || b;
|
|
||||||
return labelA.localeCompare(labelB);
|
|
||||||
});
|
});
|
||||||
|
return Array.from(byVal.entries())
|
||||||
|
.sort((a, b) => a[1].localeCompare(b[1]))
|
||||||
|
.map(([value, label]) => ({ value, label }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asyncFilterValues[columnKey] && asyncFilterValues[columnKey].length > 0) {
|
if (asyncFilterValues[columnKey] && asyncFilterValues[columnKey].length > 0) {
|
||||||
|
|
@ -1125,7 +1025,7 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}, [detectedColumns, asyncFilterValues, apiEndpoint, hookData, data, fkCache]);
|
}, [detectedColumns, asyncFilterValues, apiEndpoint, hookData, data, supportsBackendPagination]);
|
||||||
|
|
||||||
// Close filter dropdown when clicking outside
|
// Close filter dropdown when clicking outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -1500,10 +1400,11 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
return detectedColumns.map(col => {
|
return detectedColumns.map(col => {
|
||||||
let cellValue = row[col.key];
|
let cellValue = row[col.key];
|
||||||
|
|
||||||
// FK resolution
|
if (col.displayField) {
|
||||||
if (col.fkSource && typeof cellValue === 'string' && cellValue.length > 0) {
|
const displayValue = row[col.displayField];
|
||||||
const resolved = fkCache[col.fkSource]?.[cellValue];
|
if (displayValue != null && displayValue !== '') {
|
||||||
if (resolved) cellValue = resolved;
|
cellValue = displayValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timestamp formatting
|
// Timestamp formatting
|
||||||
|
|
@ -1541,7 +1442,7 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
} finally {
|
} finally {
|
||||||
setCsvExporting(false);
|
setCsvExporting(false);
|
||||||
}
|
}
|
||||||
}, [csvExporting, detectedColumns, apiEndpoint, currentLanguage, fkCache]);
|
}, [csvExporting, detectedColumns, apiEndpoint, currentLanguage]);
|
||||||
|
|
||||||
// Check if inline editing is allowed for a column (based on RBAC permissions)
|
// Check if inline editing is allowed for a column (based on RBAC permissions)
|
||||||
const canInlineEdit = useMemo(() => {
|
const canInlineEdit = useMemo(() => {
|
||||||
|
|
@ -1725,6 +1626,15 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
return column.formatter(value, row);
|
return column.formatter(value, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// displayField: backend-enriched label takes priority over raw value.
|
||||||
|
// Falls back to the raw value when displayField is null/undefined (unresolved FK).
|
||||||
|
if (column.displayField) {
|
||||||
|
const displayValue = row[column.displayField];
|
||||||
|
if (displayValue != null && displayValue !== '') return String(displayValue);
|
||||||
|
if (value != null && value !== '') return `NA(${value})`;
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
return '-';
|
return '-';
|
||||||
}
|
}
|
||||||
|
|
@ -1750,19 +1660,6 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
return renderBooleanCell(value, column, row);
|
return renderBooleanCell(value, column, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FK Resolution: If column has fkSource and value is a string (UUID), resolve to display label
|
|
||||||
if (column.fkSource && typeof value === 'string' && value.length > 0) {
|
|
||||||
const resolvedLabel = resolveFkValue(value, column.fkSource);
|
|
||||||
const isLoading = fkLoading[column.fkSource];
|
|
||||||
|
|
||||||
// Show loading indicator or resolved label
|
|
||||||
if (isLoading && !fkCache[column.fkSource]?.[value]) {
|
|
||||||
return <span className={styles.fkLoading}>{value.substring(0, 8)}...</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolvedLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is an ID or hash field that should be truncated and copyable
|
// Check if this is an ID or hash field that should be truncated and copyable
|
||||||
// Do this BEFORE checking for custom formatters to ensure IDs/hashes are always copyable
|
// Do this BEFORE checking for custom formatters to ensure IDs/hashes are always copyable
|
||||||
const isId = isIdField(column.key);
|
const isId = isIdField(column.key);
|
||||||
|
|
@ -2098,10 +1995,10 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
{/* Filter icon */}
|
{/* Filter icon */}
|
||||||
{filterable && column.filterable !== false && (
|
{filterable && column.filterable !== false && (
|
||||||
<button
|
<button
|
||||||
className={`${styles.filterIcon} ${filters[column.key] ? styles.filterActive : ''}`}
|
className={`${styles.filterIcon} ${column.key in filters ? styles.filterActive : ''}`}
|
||||||
onClick={(e) => toggleFilterDropdown(column.key, e)}
|
onClick={(e) => toggleFilterDropdown(column.key, e)}
|
||||||
title={filters[column.key]
|
title={column.key in filters
|
||||||
? t('Filter: {value}', { value: String(filters[column.key]) })
|
? (filters[column.key] === null ? t('Filter: (Leer)') : t('Filter: {value}', { value: String(filters[column.key]) }))
|
||||||
: t('Zum Filtern klicken')
|
: t('Zum Filtern klicken')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -2151,7 +2048,7 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
>
|
>
|
||||||
<div className={styles.filterDropdownHeader}>
|
<div className={styles.filterDropdownHeader}>
|
||||||
<span>{t('Filter')}: {column.label}</span>
|
<span>{t('Filter')}: {column.label}</span>
|
||||||
{filters[column.key] && (
|
{column.key in filters && (
|
||||||
<button
|
<button
|
||||||
className={styles.filterClearBtn}
|
className={styles.filterClearBtn}
|
||||||
onClick={() => clearFilter(column.key)}
|
onClick={() => clearFilter(column.key)}
|
||||||
|
|
@ -2244,11 +2141,17 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`${styles.filterOption} ${!filters[column.key] ? styles.filterOptionSelected : ''}`}
|
className={`${styles.filterOption} ${filters[column.key] === undefined ? styles.filterOptionSelected : ''}`}
|
||||||
onClick={() => clearFilter(column.key)}
|
onClick={() => clearFilter(column.key)}
|
||||||
>
|
>
|
||||||
({t('Alle')})
|
({t('Alle')})
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.filterOption} ${filters[column.key] === null ? styles.filterOptionSelected : ''}`}
|
||||||
|
onClick={() => handleFilter(column.key, _EMPTY_FILTER_SENTINEL)}
|
||||||
|
>
|
||||||
|
({t('Leer')})
|
||||||
|
</div>
|
||||||
{filterValuesLoading[column.key] ? (
|
{filterValuesLoading[column.key] ? (
|
||||||
<div className={styles.filterOptionMore} style={{ textAlign: 'center', padding: '8px' }}>
|
<div className={styles.filterOptionMore} style={{ textAlign: 'center', padding: '8px' }}>
|
||||||
{t('Lade Filterwerte...')}
|
{t('Lade Filterwerte...')}
|
||||||
|
|
@ -2259,7 +2162,7 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
||||||
allValues={getUniqueValuesForColumn(column.key)}
|
allValues={getUniqueValuesForColumn(column.key)}
|
||||||
activeFilter={filters[column.key]}
|
activeFilter={filters[column.key]}
|
||||||
onSelect={(value) => handleFilter(column.key, value)}
|
onSelect={(value) => handleFilter(column.key, value)}
|
||||||
resolveLabel={column.filterLabelResolver || (column.fkSource ? (val) => fkCache[column.fkSource!]?.[val] || val : undefined)}
|
resolveLabel={column.filterLabelResolver}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ export function useAdminMandates() {
|
||||||
return await fetchMandateByIdApi(request, mandateId);
|
return await fetchMandateByIdApi(request, mandateId);
|
||||||
}, [request]);
|
}, [request]);
|
||||||
|
|
||||||
// Generate columns from attributes (including fkSource/fkDisplayField for FK resolution)
|
// Generate columns from attributes (displayField = backend {field}Label for FK columns)
|
||||||
const columns = attributes.map(attr => ({
|
const columns = attributes.map(attr => ({
|
||||||
key: attr.name,
|
key: attr.name,
|
||||||
label: attr.label || attr.name,
|
label: attr.label || attr.name,
|
||||||
|
|
@ -164,8 +164,7 @@ export function useAdminMandates() {
|
||||||
width: attr.width || 150,
|
width: attr.width || 150,
|
||||||
minWidth: attr.minWidth || 100,
|
minWidth: attr.minWidth || 100,
|
||||||
maxWidth: attr.maxWidth || 400,
|
maxWidth: attr.maxWidth || 400,
|
||||||
fkSource: (attr as any).fkSource, // API endpoint for FK data
|
displayField: (attr as any).displayField,
|
||||||
fkDisplayField: (attr as any).fkDisplayField, // Which field of FK target to display
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Create mandate
|
// Create mandate
|
||||||
|
|
|
||||||
|
|
@ -436,10 +436,12 @@ const _DashboardTab: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
const resp = await api.get('/api/system/workflow-runs/metrics');
|
const resp = await api.get('/api/system/workflow-runs/metrics');
|
||||||
setMetrics(resp.data);
|
setMetrics(resp.data);
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
|
const msg = e?.response?.data?.detail || e?.message || String(e);
|
||||||
console.error('[automations] metrics load failed', e);
|
console.error('[automations] metrics load failed', e);
|
||||||
|
showError(t('Metriken konnten nicht geladen werden: {msg}', { msg }));
|
||||||
}
|
}
|
||||||
}, []);
|
}, [showError, t]);
|
||||||
|
|
||||||
const _loadRuns = useCallback(async (paginationParams?: any) => {
|
const _loadRuns = useCallback(async (paginationParams?: any) => {
|
||||||
if (paginationParams !== undefined) {
|
if (paginationParams !== undefined) {
|
||||||
|
|
@ -543,8 +545,7 @@ const _DashboardTab: React.FC = () => {
|
||||||
width: 140,
|
width: 140,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
fkSource: '/api/mandates/',
|
displayField: 'mandateLabel',
|
||||||
fkDisplayField: 'label',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'featureInstanceId',
|
key: 'featureInstanceId',
|
||||||
|
|
@ -553,8 +554,7 @@ const _DashboardTab: React.FC = () => {
|
||||||
width: 140,
|
width: 140,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
fkSource: '/api/features/instances',
|
displayField: 'instanceLabel',
|
||||||
fkDisplayField: 'label',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'status',
|
key: 'status',
|
||||||
|
|
@ -818,30 +818,45 @@ const _WorkflowsTab: React.FC = () => {
|
||||||
const _handleExecute = useCallback(async (row: SystemWorkflow) => {
|
const _handleExecute = useCallback(async (row: SystemWorkflow) => {
|
||||||
if (!row.featureInstanceId) return;
|
if (!row.featureInstanceId) return;
|
||||||
setExecutingId(row.id);
|
setExecutingId(row.id);
|
||||||
|
// Track outcome of the fire-and-forget executeGraph promise so the
|
||||||
|
// intermediate "Workflow gestartet" toast is only shown when the call has
|
||||||
|
// not already failed/finished within the 1s observation window. Without
|
||||||
|
// this we always toasted "gestartet" — even when the run had already
|
||||||
|
// errored — producing contradictory toasts and hiding real failures.
|
||||||
|
let observedFailure = false;
|
||||||
|
let observedSuccess = false;
|
||||||
try {
|
try {
|
||||||
const invs = row.invocations || [];
|
const invs = row.invocations || [];
|
||||||
const primary =
|
const primary =
|
||||||
invs.find((i) => i.enabled && i.kind === 'manual') ||
|
invs.find((i) => i.enabled && i.kind === 'manual') ||
|
||||||
invs.find((i) => i.enabled && (i.kind === 'form' || i.kind === 'api'));
|
invs.find((i) => i.enabled && (i.kind === 'form' || i.kind === 'api'));
|
||||||
const emptyGraph = { nodes: [], connections: [] };
|
const emptyGraph = { nodes: [], connections: [] };
|
||||||
executeGraph(request, row.featureInstanceId, emptyGraph as any, row.id, {
|
const exec = executeGraph(request, row.featureInstanceId, emptyGraph as any, row.id, {
|
||||||
...(primary ? { entryPointId: primary.id } : {}),
|
...(primary ? { entryPointId: primary.id } : {}),
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
|
observedSuccess = true;
|
||||||
showSuccess(result?.paused
|
showSuccess(result?.paused
|
||||||
? t('Workflow pausiert bei Human Task.')
|
? t('Workflow pausiert bei Human Task.')
|
||||||
: t('Workflow abgeschlossen'));
|
: t('Workflow abgeschlossen'));
|
||||||
} else {
|
} else {
|
||||||
|
observedFailure = true;
|
||||||
showError(result?.error || t('Ausführung fehlgeschlagen'));
|
showError(result?.error || t('Ausführung fehlgeschlagen'));
|
||||||
}
|
}
|
||||||
_load();
|
_load();
|
||||||
}).catch((e: any) => {
|
}).catch((e: any) => {
|
||||||
|
observedFailure = true;
|
||||||
showError(t('Fehler: {msg}', { msg: e?.message || t('Ausführung fehlgeschlagen') }));
|
showError(t('Fehler: {msg}', { msg: e?.message || t('Ausführung fehlgeschlagen') }));
|
||||||
_load();
|
_load();
|
||||||
});
|
});
|
||||||
await new Promise((r) => setTimeout(r, 1000));
|
await Promise.race([
|
||||||
|
exec,
|
||||||
|
new Promise((r) => setTimeout(r, 1000)),
|
||||||
|
]);
|
||||||
await _load();
|
await _load();
|
||||||
|
if (!observedFailure && !observedSuccess) {
|
||||||
showSuccess(t('Workflow gestartet'));
|
showSuccess(t('Workflow gestartet'));
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setExecutingId(null);
|
setExecutingId(null);
|
||||||
}
|
}
|
||||||
|
|
@ -870,8 +885,24 @@ const _WorkflowsTab: React.FC = () => {
|
||||||
|
|
||||||
const _columns: ColumnConfig[] = useMemo(() => [
|
const _columns: ColumnConfig[] = useMemo(() => [
|
||||||
{ key: 'label', label: t('Workflow'), type: 'string', width: 200, sortable: true, filterable: true },
|
{ key: 'label', label: t('Workflow'), type: 'string', width: 200, sortable: true, filterable: true },
|
||||||
{ key: 'mandateId', label: t('Mandant'), type: 'string', width: 140, sortable: true, filterable: true, fkSource: '/api/mandates/', fkDisplayField: 'label' },
|
{
|
||||||
{ key: 'featureInstanceId', label: t('Instanz'), type: 'string', width: 140, sortable: true, filterable: true, fkSource: '/api/features/instances', fkDisplayField: 'label' },
|
key: 'mandateId',
|
||||||
|
label: t('Mandant'),
|
||||||
|
type: 'string',
|
||||||
|
width: 140,
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
displayField: 'mandateLabel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'featureInstanceId',
|
||||||
|
label: t('Instanz'),
|
||||||
|
type: 'string',
|
||||||
|
width: 140,
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
displayField: 'instanceLabel',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'active',
|
key: 'active',
|
||||||
label: t('Aktiv'),
|
label: t('Aktiv'),
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,7 @@ export const AdminUsersPage: React.FC = () => {
|
||||||
width: attr.width || 150,
|
width: attr.width || 150,
|
||||||
minWidth: attr.minWidth || 100,
|
minWidth: attr.minWidth || 100,
|
||||||
maxWidth: attr.maxWidth || 400,
|
maxWidth: attr.maxWidth || 400,
|
||||||
fkSource: (attr as any).fkSource,
|
displayField: (attr as any).displayField,
|
||||||
fkDisplayField: (attr as any).fkDisplayField,
|
|
||||||
}));
|
}));
|
||||||
}, [attributes]);
|
}, [attributes]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,15 +67,12 @@ export const ConnectionsPage: React.FC = () => {
|
||||||
width: attr.width || 150,
|
width: attr.width || 150,
|
||||||
minWidth: attr.minWidth || 100,
|
minWidth: attr.minWidth || 100,
|
||||||
maxWidth: attr.maxWidth || 400,
|
maxWidth: attr.maxWidth || 400,
|
||||||
fkSource: (attr as any).fkSource,
|
displayField: (attr as any).displayField,
|
||||||
fkDisplayField: (attr as any).fkDisplayField,
|
|
||||||
frontendFormat: (attr as any).frontendFormat,
|
frontendFormat: (attr as any).frontendFormat,
|
||||||
frontendFormatLabels: (attr as any).frontendFormatLabels,
|
frontendFormatLabels: (attr as any).frontendFormatLabels,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (attr.name === 'userId') {
|
if (attr.name === 'userId') {
|
||||||
col.fkSource = '/api/users/';
|
|
||||||
col.fkDisplayField = 'username';
|
|
||||||
col.label = t('Benutzer');
|
col.label = t('Benutzer');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -210,8 +210,7 @@ export const FilesPage: React.FC = () => {
|
||||||
width: attr.width || 150,
|
width: attr.width || 150,
|
||||||
minWidth: attr.minWidth || 100,
|
minWidth: attr.minWidth || 100,
|
||||||
maxWidth: attr.maxWidth || 400,
|
maxWidth: attr.maxWidth || 400,
|
||||||
fkSource: (attr as any).fkSource,
|
displayField: (attr as any).displayField,
|
||||||
fkDisplayField: (attr as any).fkDisplayField,
|
|
||||||
frontendFormat: (attr as any).frontendFormat,
|
frontendFormat: (attr as any).frontendFormat,
|
||||||
frontendFormatLabels: (attr as any).frontendFormatLabels,
|
frontendFormatLabels: (attr as any).frontendFormatLabels,
|
||||||
}));
|
}));
|
||||||
|
|
@ -225,8 +224,7 @@ export const FilesPage: React.FC = () => {
|
||||||
width: 150,
|
width: 150,
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
maxWidth: 250,
|
maxWidth: 250,
|
||||||
fkSource: '/api/users/',
|
displayField: 'sysCreatedByLabel',
|
||||||
fkDisplayField: 'username',
|
|
||||||
} as any);
|
} as any);
|
||||||
return cols;
|
return cols;
|
||||||
}, [attributes, t]);
|
}, [attributes, t]);
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,7 @@ export const PromptsPage: React.FC = () => {
|
||||||
width: attr.name === 'content' ? 300 : attr.width || 150,
|
width: attr.name === 'content' ? 300 : attr.width || 150,
|
||||||
minWidth: attr.minWidth || 100,
|
minWidth: attr.minWidth || 100,
|
||||||
maxWidth: attr.name === 'content' ? 500 : attr.maxWidth || 400,
|
maxWidth: attr.name === 'content' ? 500 : attr.maxWidth || 400,
|
||||||
fkSource: (attr as any).fkSource,
|
displayField: (attr as any).displayField,
|
||||||
fkDisplayField: (attr as any).fkDisplayField,
|
|
||||||
frontendFormat: (attr as any).frontendFormat,
|
frontendFormat: (attr as any).frontendFormat,
|
||||||
frontendFormatLabels: (attr as any).frontendFormatLabels,
|
frontendFormatLabels: (attr as any).frontendFormatLabels,
|
||||||
}));
|
}));
|
||||||
|
|
@ -100,8 +99,7 @@ export const PromptsPage: React.FC = () => {
|
||||||
width: 150,
|
width: 150,
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
maxWidth: 250,
|
maxWidth: 250,
|
||||||
fkSource: '/api/users/',
|
displayField: 'sysCreatedByLabel',
|
||||||
fkDisplayField: 'username',
|
|
||||||
frontendFormat: undefined,
|
frontendFormat: undefined,
|
||||||
frontendFormatLabels: undefined,
|
frontendFormatLabels: undefined,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
* Keeps the CommCoach dossier/coaching page mounted across route changes.
|
* Keeps the CommCoach dossier/coaching page mounted across route changes.
|
||||||
* Visibility is toggled via CSS so session state, messages, and input state
|
* Visibility is toggled via CSS so session state, messages, and input state
|
||||||
* stay alive when the user leaves and later returns.
|
* stay alive when the user leaves and later returns.
|
||||||
|
*
|
||||||
|
* Persistence is scoped per `(mandateId, instanceId)` — switching to a
|
||||||
|
* different mandate or instance via the navigator unmounts the previous
|
||||||
|
* view and mounts a fresh one.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
|
@ -30,6 +34,7 @@ export const CommcoachKeepAlive: React.FC<CommcoachKeepAliveProps> = ({ isVisibl
|
||||||
const mandateId = cachedMandateIdRef.current;
|
const mandateId = cachedMandateIdRef.current;
|
||||||
const instanceId = cachedInstanceIdRef.current;
|
const instanceId = cachedInstanceIdRef.current;
|
||||||
if (!mandateId || !instanceId) return null;
|
if (!mandateId || !instanceId) return null;
|
||||||
|
const scopeKey = `${mandateId}:${instanceId}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -44,6 +49,7 @@ export const CommcoachKeepAlive: React.FC<CommcoachKeepAliveProps> = ({ isVisibl
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CommcoachDossierView
|
<CommcoachDossierView
|
||||||
|
key={scopeKey}
|
||||||
persistentInstanceId={instanceId}
|
persistentInstanceId={instanceId}
|
||||||
persistentMandateId={mandateId}
|
persistentMandateId={mandateId}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright (c) 2025 Patrick Motsch
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Persistence is per (mandateId, instanceId): switching to a different mandate
|
||||||
|
// or instance must remount the editor page so its internal state (loaded
|
||||||
|
// workflow, currentWorkflowId, …) is reset and saves go to the right tenant.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { render, screen, act } from '@testing-library/react';
|
||||||
|
import { MemoryRouter, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
const _mountCount = { value: 0 };
|
||||||
|
|
||||||
|
vi.mock('./GraphicalEditorPage', () => ({
|
||||||
|
GraphicalEditorPage: ({ persistentMandateId, persistentInstanceId }: { persistentMandateId?: string; persistentInstanceId?: string }) => {
|
||||||
|
React.useEffect(() => {
|
||||||
|
_mountCount.value += 1;
|
||||||
|
}, []);
|
||||||
|
return <div data-testid="ge-page">{persistentMandateId}::{persistentInstanceId}</div>;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { GraphicalEditorKeepAlive } from './GraphicalEditorKeepAlive';
|
||||||
|
|
||||||
|
let _navigateTo: ((path: string) => void) | null = null;
|
||||||
|
const _NavCapture: React.FC = () => {
|
||||||
|
_navigateTo = useNavigate();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function _renderHarness(initialPath: string) {
|
||||||
|
return render(
|
||||||
|
<MemoryRouter initialEntries={[initialPath]}>
|
||||||
|
<_NavCapture />
|
||||||
|
<GraphicalEditorKeepAlive isVisible />
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _navigate(path: string) {
|
||||||
|
act(() => {
|
||||||
|
_navigateTo?.(path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('GraphicalEditorKeepAlive — persistence per (mandate, instance)', () => {
|
||||||
|
it('remounts the page when the mandate changes', () => {
|
||||||
|
_mountCount.value = 0;
|
||||||
|
_renderHarness('/mandates/mA/graphicalEditor/iA/editor');
|
||||||
|
expect(_mountCount.value).toBe(1);
|
||||||
|
expect(screen.getByTestId('ge-page').textContent).toBe('mA::iA');
|
||||||
|
|
||||||
|
_navigate('/mandates/mB/graphicalEditor/iA/editor');
|
||||||
|
|
||||||
|
expect(_mountCount.value).toBe(2);
|
||||||
|
expect(screen.getByTestId('ge-page').textContent).toBe('mB::iA');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('remounts the page when the instance changes', () => {
|
||||||
|
_mountCount.value = 0;
|
||||||
|
_renderHarness('/mandates/mA/graphicalEditor/iA/editor');
|
||||||
|
expect(_mountCount.value).toBe(1);
|
||||||
|
|
||||||
|
_navigate('/mandates/mA/graphicalEditor/iZ/editor');
|
||||||
|
|
||||||
|
expect(_mountCount.value).toBe(2);
|
||||||
|
expect(screen.getByTestId('ge-page').textContent).toBe('mA::iZ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT remount when the route stays on the same (mandate, instance)', () => {
|
||||||
|
_mountCount.value = 0;
|
||||||
|
_renderHarness('/mandates/mA/graphicalEditor/iA/editor');
|
||||||
|
expect(_mountCount.value).toBe(1);
|
||||||
|
|
||||||
|
_navigate('/mandates/mA/graphicalEditor/iA/editor');
|
||||||
|
|
||||||
|
expect(_mountCount.value).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the cached page mounted (no remount) when the user navigates AWAY and BACK to the same scope', () => {
|
||||||
|
_mountCount.value = 0;
|
||||||
|
_renderHarness('/mandates/mA/graphicalEditor/iA/editor');
|
||||||
|
expect(_mountCount.value).toBe(1);
|
||||||
|
|
||||||
|
// Away to a non-editor route: the regex match fails, refs keep their
|
||||||
|
// previous values — the cached page must not remount.
|
||||||
|
_navigate('/admin/languages');
|
||||||
|
expect(_mountCount.value).toBe(1);
|
||||||
|
expect(screen.getByTestId('ge-page').textContent).toBe('mA::iA');
|
||||||
|
|
||||||
|
// Back to the same (mandate, instance) — still no remount.
|
||||||
|
_navigate('/mandates/mA/graphicalEditor/iA/editor');
|
||||||
|
expect(_mountCount.value).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -4,9 +4,16 @@
|
||||||
* Keeps the GraphicalEditorPage mounted across route changes so the canvas
|
* Keeps the GraphicalEditorPage mounted across route changes so the canvas
|
||||||
* state, SSE connections, and editor context survive navigation to ANY page
|
* state, SSE connections, and editor context survive navigation to ANY page
|
||||||
* (other features, admin, settings, etc.).
|
* (other features, admin, settings, etc.).
|
||||||
* Visibility is toggled via CSS `display` instead of mount / unmount.
|
*
|
||||||
* Cached mandateId/instanceId are passed as props so the page does not
|
* Persistence is scoped per `(mandateId, instanceId)`: when the user switches
|
||||||
* depend on URL params (which disappear on non-feature routes).
|
* to a DIFFERENT mandate or instance via the navigator, the previous editor
|
||||||
|
* mount is discarded and a fresh page is mounted. Otherwise stale state from
|
||||||
|
* mandate A leaks into mandate B and saves end up hitting the wrong tenant
|
||||||
|
* (HTTP 404 / "not found").
|
||||||
|
*
|
||||||
|
* Implementation: feeds the cached `(mandate, instance)` tuple into both
|
||||||
|
* `props` and `key`. React reuses the mount as long as the tuple stays
|
||||||
|
* identical and unmounts/remounts on change.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
|
@ -34,6 +41,10 @@ export const GraphicalEditorKeepAlive: React.FC<GraphicalEditorKeepAliveProps> =
|
||||||
|
|
||||||
if (!hasEverMountedRef.current) return null;
|
if (!hasEverMountedRef.current) return null;
|
||||||
|
|
||||||
|
const mandateId = cachedMandateIdRef.current;
|
||||||
|
const instanceId = cachedInstanceIdRef.current;
|
||||||
|
const scopeKey = `${mandateId}:${instanceId}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -48,8 +59,9 @@ export const GraphicalEditorKeepAlive: React.FC<GraphicalEditorKeepAliveProps> =
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<GraphicalEditorPage
|
<GraphicalEditorPage
|
||||||
persistentInstanceId={cachedInstanceIdRef.current}
|
key={scopeKey}
|
||||||
persistentMandateId={cachedMandateIdRef.current}
|
persistentInstanceId={instanceId}
|
||||||
|
persistentMandateId={mandateId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -200,8 +200,7 @@ export const GraphicalEditorTemplatesPage: React.FC = () => {
|
||||||
label: t('Erstellt von'),
|
label: t('Erstellt von'),
|
||||||
type: 'string',
|
type: 'string',
|
||||||
width: 140,
|
width: 140,
|
||||||
fkSource: '/api/users/',
|
displayField: 'sysCreatedByLabel',
|
||||||
fkDisplayField: 'username',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'sysCreatedAt',
|
key: 'sysCreatedAt',
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,6 @@ export const TrusteePositionDocumentsView: React.FC = () => {
|
||||||
}
|
}
|
||||||
}, [instanceId]);
|
}, [instanceId]);
|
||||||
|
|
||||||
// Generate columns from attributes (like TrusteePositionsView)
|
|
||||||
// Map frontend_options to fkSource for FK resolution
|
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
if (!attributes || attributes.length === 0) return [];
|
if (!attributes || attributes.length === 0) return [];
|
||||||
|
|
||||||
|
|
@ -60,14 +58,7 @@ export const TrusteePositionDocumentsView: React.FC = () => {
|
||||||
|
|
||||||
return attributes
|
return attributes
|
||||||
.filter((attr: any) => !excludedFields.includes(attr.name))
|
.filter((attr: any) => !excludedFields.includes(attr.name))
|
||||||
.map((attr: any) => {
|
.map((attr: any) => ({
|
||||||
// Replace {instanceId} placeholder in options URL
|
|
||||||
let fkSource = attr.options;
|
|
||||||
if (typeof fkSource === 'string' && instanceId) {
|
|
||||||
fkSource = fkSource.replace('{instanceId}', instanceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: attr.name,
|
key: attr.name,
|
||||||
label: attr.label || attr.name,
|
label: attr.label || attr.name,
|
||||||
type: attr.type as any,
|
type: attr.type as any,
|
||||||
|
|
@ -77,12 +68,9 @@ export const TrusteePositionDocumentsView: React.FC = () => {
|
||||||
width: attr.width || 200,
|
width: attr.width || 200,
|
||||||
minWidth: attr.minWidth || 100,
|
minWidth: attr.minWidth || 100,
|
||||||
maxWidth: attr.maxWidth || 400,
|
maxWidth: attr.maxWidth || 400,
|
||||||
// Use frontend_options as fkSource for FK resolution
|
displayField: attr.displayField,
|
||||||
fkSource: typeof fkSource === 'string' ? fkSource : undefined,
|
}));
|
||||||
fkDisplayField: 'label',
|
}, [attributes]);
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [attributes, instanceId]);
|
|
||||||
|
|
||||||
// Check permissions (general level)
|
// Check permissions (general level)
|
||||||
// Row-level permissions are handled automatically by FormGeneratorTable
|
// Row-level permissions are handled automatically by FormGeneratorTable
|
||||||
|
|
|
||||||
|
|
@ -129,8 +129,7 @@ export const TrusteeDataTab: React.FC<TrusteeDataTabProps> = ({
|
||||||
width: attr.width || 150,
|
width: attr.width || 150,
|
||||||
minWidth: attr.minWidth || 100,
|
minWidth: attr.minWidth || 100,
|
||||||
maxWidth: attr.maxWidth || 400,
|
maxWidth: attr.maxWidth || 400,
|
||||||
fkSource: attr.fkSource,
|
displayField: attr.displayField,
|
||||||
fkDisplayField: attr.fkDisplayField,
|
|
||||||
frontendFormat: attr.frontendFormat,
|
frontendFormat: attr.frontendFormat,
|
||||||
frontendFormatLabels: attr.frontendFormatLabels,
|
frontendFormatLabels: attr.frontendFormatLabels,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@
|
||||||
* survives route changes. Visibility is toggled via CSS `display`
|
* survives route changes. Visibility is toggled via CSS `display`
|
||||||
* instead of mount / unmount, preserving messages, SSE connections,
|
* instead of mount / unmount, preserving messages, SSE connections,
|
||||||
* files, and all other workspace state.
|
* files, and all other workspace state.
|
||||||
|
*
|
||||||
|
* Persistence is scoped per `(mandateId, instanceId)` — switching to a
|
||||||
|
* different mandate or instance via the navigator unmounts the previous
|
||||||
|
* page and mounts a fresh one (otherwise stale state from tenant A
|
||||||
|
* leaks into tenant B).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
|
@ -19,15 +24,19 @@ interface WorkspaceKeepAliveProps {
|
||||||
|
|
||||||
export const WorkspaceKeepAlive: React.FC<WorkspaceKeepAliveProps> = ({ isVisible }) => {
|
export const WorkspaceKeepAlive: React.FC<WorkspaceKeepAliveProps> = ({ isVisible }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const cachedMandateIdRef = useRef<string>('');
|
||||||
const cachedInstanceIdRef = useRef<string>('');
|
const cachedInstanceIdRef = useRef<string>('');
|
||||||
|
|
||||||
const match = location.pathname.match(_WORKSPACE_ROUTE_RE);
|
const match = location.pathname.match(_WORKSPACE_ROUTE_RE);
|
||||||
if (match?.[2]) {
|
if (match?.[1] && match?.[2]) {
|
||||||
|
cachedMandateIdRef.current = match[1];
|
||||||
cachedInstanceIdRef.current = match[2];
|
cachedInstanceIdRef.current = match[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mandateId = cachedMandateIdRef.current;
|
||||||
const instanceId = cachedInstanceIdRef.current;
|
const instanceId = cachedInstanceIdRef.current;
|
||||||
if (!instanceId) return null;
|
if (!instanceId) return null;
|
||||||
|
const scopeKey = `${mandateId}:${instanceId}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -42,7 +51,7 @@ export const WorkspaceKeepAlive: React.FC<WorkspaceKeepAliveProps> = ({ isVisibl
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WorkspacePage persistentInstanceId={instanceId} />
|
<WorkspacePage key={scopeKey} persistentInstanceId={instanceId} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
489
tsc-out.txt
489
tsc-out.txt
|
|
@ -1,489 +0,0 @@
|
||||||
src/components/FlowEditor/nodes/configs/CommentNodeConfig.tsx(8,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/configs/CommentNodeConfig.tsx(17,22): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/ConfirmationNodeConfig.tsx(8,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/configs/ConfirmationNodeConfig.tsx(17,22): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/ConfirmationNodeConfig.tsx(21,15): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(9,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(61,39): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(61,70): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(78,72): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(78,114): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(96,48): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(97,51): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(98,48): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(115,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(119,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(123,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(127,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(131,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(135,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(139,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(143,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(147,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(151,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(161,51): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(176,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(180,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(184,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(188,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(198,45): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(217,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(225,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(230,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx(234,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/FileCreateNodeConfig.tsx(12,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/configs/FileCreateNodeConfig.tsx(46,17): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/FileCreateNodeConfig.tsx(52,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/FileCreateNodeConfig.tsx(58,22): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/FileCreateNodeConfig.tsx(59,27): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/FileCreateNodeConfig.tsx(96,17): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/FileCreateNodeConfig.tsx(109,17): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(13,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(157,50): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(157,86): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(168,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(179,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(194,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(195,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(213,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(217,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(225,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/SharePointNodeConfig.tsx(233,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(10,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(36,17): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(40,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(47,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(53,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(53,74): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(62,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(70,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(74,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(83,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/TrusteeNodeConfig.tsx(87,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/UploadNodeConfig.tsx(11,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/configs/UploadNodeConfig.tsx(42,17): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/configs/UploadNodeConfig.tsx(60,17): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/form/FormNodeConfig.tsx(11,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/form/FormNodeConfig.tsx(78,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/form/FormNodeConfig.tsx(135,48): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/form/FormNodeConfig.tsx(136,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/form/FormNodeConfig.tsx(153,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/form/FormNodeConfig.tsx(200,32): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(28,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(157,13): error TS2345: Argument of type 'string' is not assignable to parameter of type 'ApiRequestOptions<any>'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(172,27): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(216,34): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(218,33): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(219,33): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(225,141): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(260,142): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(284,140): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(296,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(299,68): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(312,32): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(313,31): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(314,31): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(316,34): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(317,38): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(318,36): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(319,37): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(341,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(343,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(347,144): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(362,32): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(364,39): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(365,36): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(366,39): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(367,31): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx(368,31): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/ifElse/IfElseNodeConfig.tsx(15,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/ifElse/IfElseNodeConfig.tsx(102,78): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/ifElse/IfElseNodeConfig.tsx(122,33): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/ifElse/IfElseNodeConfig.tsx(144,25): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/ifElse/IfElseNodeConfig.tsx(145,25): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/shared/DataPicker.tsx(14,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/shared/DataPicker.tsx(142,51): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/shared/DataPicker.tsx(143,98): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/shared/DataPicker.tsx(184,61): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/shared/DataPicker.tsx(192,43): error TS2345: Argument of type 'CanvasConnection[]' is not assignable to parameter of type '{ source: string; target: string; sourceOutput?: number | undefined; }[]'.
|
|
||||||
Type 'CanvasConnection' is missing the following properties from type '{ source: string; target: string; sourceOutput?: number | undefined; }': source, target
|
|
||||||
src/components/FlowEditor/nodes/shared/DynamicValueField.tsx(17,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/shared/DynamicValueField.tsx(59,54): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/shared/DynamicValueField.tsx(104,26): error TS2345: Argument of type 'DataRef | SystemVarRef' is not assignable to parameter of type 'DataRef | null'.
|
|
||||||
Type 'SystemVarRef' is missing the following properties from type 'DataRef': nodeId, path
|
|
||||||
src/components/FlowEditor/nodes/shared/HybridStaticRefField.tsx(16,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/shared/HybridStaticRefField.tsx(87,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/shared/LoopItemsSelect.tsx(12,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/shared/LoopItemsSelect.tsx(186,15): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/switch/SwitchNodeConfig.tsx(15,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/FlowEditor/nodes/switch/SwitchNodeConfig.tsx(116,29): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/switch/SwitchNodeConfig.tsx(156,29): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/switch/SwitchNodeConfig.tsx(157,33): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/switch/SwitchNodeConfig.tsx(158,34): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/switch/SwitchNodeConfig.tsx(191,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/switch/SwitchNodeConfig.tsx(197,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/switch/SwitchNodeConfig.tsx(202,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/FlowEditor/nodes/switch/SwitchNodeConfig.tsx(208,17): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/NotificationBell/NotificationBell.tsx(13,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/NotificationBell/NotificationBell.tsx(161,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/NotificationBell/NotificationBell.tsx(175,48): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/NotificationBell/NotificationBell.tsx(185,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/NotificationBell/NotificationBell.tsx(255,33): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/OnboardingWizard.tsx(4,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/OnboardingWizard.tsx(26,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/OnboardingWizard.tsx(48,64): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/OnboardingWizard.tsx(62,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/OnboardingWizard.tsx(77,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/OnboardingWizard.tsx(92,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/OnboardingWizard.tsx(116,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/OnboardingWizard.tsx(116,65): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/ProviderSelector/ProviderSelector.tsx(21,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/ProviderSelector/ProviderSelector.tsx(145,27): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/ProviderSelector/ProviderSelector.tsx(283,16): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/ProviderSelector/ProviderSelector.tsx(304,46): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/ProviderSelector/ProviderSelector.tsx(348,51): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/RbacExportImport/RbacExportImport.tsx(90,53): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/AddressAutocomplete/AddressAutocomplete.tsx(7,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/UiComponents/AddressAutocomplete/AddressAutocomplete.tsx(278,55): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/AddressAutocomplete/AddressAutocomplete.tsx(288,57): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/AutoScroll/AutoScroll.tsx(4,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/UiComponents/AutoScroll/AutoScroll.tsx(152,23): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/BauvorschriftenSection/BauvorschriftenSection.tsx(5,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/UiComponents/BauvorschriftenSection/BauvorschriftenSection.tsx(44,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/BauvorschriftenSection/BauvorschriftenSection.tsx(56,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/BauvorschriftenSection/BauvorschriftenSection.tsx(68,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/BauvorschriftenSection/BauvorschriftenSection.tsx(74,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/BauvorschriftenSection/BauvorschriftenSection.tsx(80,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ConnectedFilesList/ConnectedFilesList.tsx(15,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/UiComponents/ConnectedFilesList/ConnectedFilesList.tsx(186,39): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ConnectedFilesList/ConnectedFilesList.tsx(233,52): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ConnectedFilesList/ConnectedFilesList.tsx(233,100): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ConnectedFilesList/ConnectedFilesList.tsx(240,42): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ConnectedFilesList/ConnectedFilesList.tsx(240,82): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ConnectedFilesList/ConnectedFilesList.tsx(250,60): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ConnectedFilesList/ConnectedFilesList.tsx(250,95): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/MapView/MapViewLeaflet.tsx(28,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/UiComponents/MapView/MapViewLeaflet.tsx(196,47): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/MapView/MapViewLeaflet.tsx(196,107): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/MapView/MapViewLeaflet.tsx(209,45): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/MapView/MapViewLeaflet.tsx(209,105): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/Messages/ChatMessages/ChatMessage.tsx(12,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/UiComponents/Messages/ChatMessages/ChatMessage.tsx(83,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/Messages/ChatMessages/ChatMessage.tsx(203,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/Messages/ChatMessages/ChatMessage.tsx(211,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/Messages/ChatMessages/ChatMessage.tsx(221,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(10,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(229,59): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(268,34): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(319,59): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(332,59): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(346,60): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(364,44): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(382,38): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(406,73): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(423,73): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(442,73): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(493,74): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(534,34): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(585,59): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(598,59): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(612,60): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(630,44): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(648,38): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(672,73): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(689,73): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(708,73): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(757,74): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(844,47): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/ParcelInfoPanel/ParcelInfoPanel.tsx(1083,47): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/Toast/Toast.tsx(23,11): error TS6133: 't' is declared but its value is never read.
|
|
||||||
src/components/UiComponents/Toast/Toast.tsx(57,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UiComponents/WorkflowStatus/WorkflowStatus.tsx(36,11): error TS6133: 't' is declared but its value is never read.
|
|
||||||
src/components/UiComponents/WorkflowStatus/WorkflowStatus.tsx(73,55): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/ChatsTab.tsx(6,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/UnifiedDataBar/ChatsTab.tsx(311,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/ChatsTab.tsx(333,56): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/ChatsTab.tsx(341,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/ChatsTab.tsx(346,119): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/ChatsTab.tsx(438,36): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/ChatsTab.tsx(438,75): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/FilesTab.tsx(9,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/UnifiedDataBar/FilesTab.tsx(235,56): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/FilesTab.tsx(263,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/FilesTab.tsx(286,22): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/FilesTab.tsx(327,28): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/FilesTab.tsx(327,65): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(23,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(855,42): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(855,90): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(862,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(958,32): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(988,53): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(988,84): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(995,36): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(1031,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(1031,80): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(1038,32): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(1168,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(1174,82): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(1431,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(1437,80): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(1577,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/components/UnifiedDataBar/SourcesTab.tsx(1583,82): error TS2304: Cannot find name 't'.
|
|
||||||
src/hooks/usePlayground.ts(2,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'Workflow'.
|
|
||||||
src/hooks/usePlayground.ts(3,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'WorkflowMessage'.
|
|
||||||
src/hooks/usePlayground.ts(4,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'WorkflowLog'.
|
|
||||||
src/hooks/usePlayground.ts(5,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'StartWorkflowRequest'.
|
|
||||||
src/hooks/usePlayground.ts(6,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'StartWorkflowResponse'.
|
|
||||||
src/hooks/usePlayground.ts(7,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'ChatDataResponse'.
|
|
||||||
src/hooks/useWorkflows.ts(4,3): error TS2724: '"../api/workflowApi"' has no exported member named 'deleteWorkflowApi'. Did you mean 'deleteWorkflow'?
|
|
||||||
src/hooks/useWorkflows.ts(5,3): error TS2724: '"../api/workflowApi"' has no exported member named 'deleteWorkflowsApi'. Did you mean 'deleteWorkflow'?
|
|
||||||
src/hooks/useWorkflows.ts(6,3): error TS2724: '"../api/workflowApi"' has no exported member named 'updateWorkflowApi'. Did you mean 'updateWorkflow'?
|
|
||||||
src/hooks/useWorkflows.ts(9,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'fetchAttributes'.
|
|
||||||
src/hooks/useWorkflows.ts(10,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'startWorkflowApi'.
|
|
||||||
src/hooks/useWorkflows.ts(11,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'stopWorkflowApi'.
|
|
||||||
src/hooks/useWorkflows.ts(12,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'deleteMessageApi'.
|
|
||||||
src/hooks/useWorkflows.ts(13,3): error TS2305: Module '"../api/workflowApi"' has no exported member 'deleteFileFromMessageApi'.
|
|
||||||
src/hooks/useWorkflows.ts(14,8): error TS2305: Module '"../api/workflowApi"' has no exported member 'Workflow'.
|
|
||||||
src/hooks/useWorkflows.ts(15,8): error TS2305: Module '"../api/workflowApi"' has no exported member 'AttributeDefinition'.
|
|
||||||
src/hooks/useWorkflows.ts(16,8): error TS2305: Module '"../api/workflowApi"' has no exported member 'StartWorkflowRequest'.
|
|
||||||
src/hooks/useWorkflows.ts(32,15): error TS2305: Module '"../api/workflowApi"' has no exported member 'AttributeDefinition'.
|
|
||||||
src/hooks/useWorkflows.ts(132,49): error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'string'.
|
|
||||||
src/hooks/useWorkflows.ts(195,72): error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
|
|
||||||
Type 'undefined' is not assignable to type 'string'.
|
|
||||||
src/hooks/useWorkflows.ts(196,14): error TS2352: Conversion of type 'Automation2Workflow' to type 'UserWorkflow' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
|
|
||||||
Type 'Automation2Workflow' is missing the following properties from type 'UserWorkflow': mandateId, status
|
|
||||||
src/hooks/useWorkflows.ts(247,40): error TS7006: Parameter 'opt' implicitly has an 'any' type.
|
|
||||||
src/hooks/useWorkflows.ts(263,40): error TS7006: Parameter 'opt' implicitly has an 'any' type.
|
|
||||||
src/layouts/FeatureLayout.tsx(23,9): error TS2304: Cannot find name 't'.
|
|
||||||
src/layouts/FeatureLayout.tsx(39,10): error TS2304: Cannot find name 't'.
|
|
||||||
src/layouts/FeatureLayout.tsx(62,11): error TS6133: 't' is declared but its value is never read.
|
|
||||||
src/pages/admin/AdminFeatureInstanceUsersPage.tsx(13,26): error TS6133: 'FaUsers' is declared but its value is never read.
|
|
||||||
src/pages/admin/AdminInvitationsPage.tsx(13,26): error TS6133: 'FaEnvelopeOpenText' is declared but its value is never read.
|
|
||||||
src/pages/admin/AdminMandatesPage.tsx(20,26): error TS6133: 'FaBuilding' is declared but its value is never read.
|
|
||||||
src/pages/admin/AdminUserMandatesPage.tsx(12,26): error TS6133: 'FaUsers' is declared but its value is never read.
|
|
||||||
src/pages/admin/AdminUsersPage.tsx(12,26): error TS6133: 'FaUsers' is declared but its value is never read.
|
|
||||||
src/pages/basedata/ConnectionsPage.tsx(12,18): error TS6133: 'FaPlug' is declared but its value is never read.
|
|
||||||
src/pages/basedata/FilesPage.tsx(17,18): error TS6133: 'FaFolder' is declared but its value is never read.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(12,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(69,56): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(73,44): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(87,14): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(89,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(109,14): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(111,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(131,14): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(133,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(189,14): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(190,41): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(197,46): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(199,55): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingDashboard.tsx(201,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(19,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(47,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(48,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(49,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(72,62): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(72,89): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(134,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(136,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(140,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(218,45): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(229,55): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(231,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(254,55): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(256,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(268,30): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingMandateView.tsx(268,58): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingTransactions.tsx(12,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/billing/BillingTransactions.tsx(98,14): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingTransactions.tsx(99,41): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingTransactions.tsx(106,55): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingTransactions.tsx(108,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingTransactions.tsx(116,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingTransactions.tsx(118,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingTransactions.tsx(122,57): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingTransactions.tsx(140,30): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingTransactions.tsx(140,63): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(20,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/billing/BillingUserView.tsx(93,31): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(108,31): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(121,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(122,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(123,51): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(125,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(144,70): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(227,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(228,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(230,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(234,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(314,41): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(323,55): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(325,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(350,55): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(352,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(368,30): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/BillingUserView.tsx(368,55): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(413,11): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(414,11): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(416,39): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(416,79): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(417,46): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(417,81): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(418,45): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(418,80): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(448,56): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(475,46): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(481,29): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(481,75): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(499,48): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(503,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(513,46): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/billing/SubscriptionTab.tsx(515,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Dashboard.tsx(18,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/Dashboard.tsx(74,16): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Dashboard.tsx(75,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Dashboard.tsx(84,14): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/FeatureView.tsx(54,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/FeatureView.tsx(69,27): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/FeatureView.tsx(69,72): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/FeatureView.tsx(73,46): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/FeatureView.tsx(77,27): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/FeatureView.tsx(84,27): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/FeatureView.tsx(84,75): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/FeatureView.tsx(90,10): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/FeatureView.tsx(91,9): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/FeatureView.tsx(97,10): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/FeatureView.tsx(98,9): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(29,11): error TS6133: 't' is declared but its value is never read.
|
|
||||||
src/pages/GDPR.tsx(165,48): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(168,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(169,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(190,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(191,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(212,20): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(213,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(282,48): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(283,65): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(288,22): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(298,22): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/GDPR.tsx(308,22): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Register.tsx(47,11): error TS6133: 't' is declared but its value is never read.
|
|
||||||
src/pages/Register.tsx(142,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Register.tsx(195,115): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Register.tsx(199,25): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Register.tsx(219,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Reset.tsx(40,11): error TS6133: 't' is declared but its value is never read.
|
|
||||||
src/pages/Reset.tsx(111,45): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Reset.tsx(123,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Reset.tsx(151,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Reset.tsx(163,57): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Reset.tsx(178,106): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Reset.tsx(196,120): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Reset.tsx(210,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Store.tsx(140,38): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/Store.tsx(140,65): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(32,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(252,46): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(263,35): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(263,82): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(295,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(306,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(314,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(320,41): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(323,43): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(326,47): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(330,53): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(330,90): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(332,95): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(340,16): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(341,15): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(342,90): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(358,162): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(359,163): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(363,54): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(363,93): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(376,38): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(388,21): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(391,61): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(419,58): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(433,44): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(433,91): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(435,38): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(435,74): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(438,63): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(438,105): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(441,63): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(441,103): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(490,86): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(490,131): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(603,36): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(617,34): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(642,148): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(672,34): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(699,152): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(776,30): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(782,57): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(782,98): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(786,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(815,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(822,95): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(822,125): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(832,47): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/commcoach/CommcoachDossierView.tsx(850,49): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(120,29): error TS2339: Property 'filter' does not exist on type 'Automation2Workflow[] | { items: Automation2Workflow[]; pagination: any; }'.
|
|
||||||
Property 'filter' does not exist on type '{ items: Automation2Workflow[]; pagination: any; }'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(121,14): error TS7006: Parameter 'w' implicitly has an 'any' type.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(376,47): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(391,38): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(476,83): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(486,29): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(600,39): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(634,22): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(648,33): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(648,83): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(855,41): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(855,94): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(871,29): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(871,79): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(879,17): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(920,47): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/graphicalEditor/GraphicalEditorWorkflowsTasksPage.tsx(926,47): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(368,58): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(368,110): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(485,44): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(492,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(507,31): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(507,64): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(527,40): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(533,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(557,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(567,32): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(574,46): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(574,85): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(588,32): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(595,46): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(595,85): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(614,39): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(614,72): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(621,48): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(657,46): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(667,35): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/neutralization/NeutralizationView.tsx(682,50): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/ChatStream.tsx(16,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/views/workspace/ChatStream.tsx(377,85): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/FilePreview.tsx(16,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/views/workspace/FilePreview.tsx(102,91): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/NeutralizationPanel.tsx(4,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/views/workspace/NeutralizationPanel.tsx(295,92): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/NeutralizationPanel.tsx(439,26): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/NeutralizationPanel.tsx(452,15): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/NeutralizationPanel.tsx(453,15): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/WorkspaceGeneralSettings.tsx(11,1): error TS6133: 'useLanguage' is declared but its value is never read.
|
|
||||||
src/pages/views/workspace/WorkspaceGeneralSettings.tsx(69,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/WorkspaceGeneralSettings.tsx(83,18): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/WorkspaceGeneralSettings.tsx(97,45): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/WorkspaceGeneralSettings.tsx(104,39): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/WorkspaceGeneralSettings.tsx(132,24): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/WorkspaceGeneralSettings.tsx(153,19): error TS2304: Cannot find name 't'.
|
|
||||||
src/pages/views/workspace/WorkspaceGeneralSettings.tsx(153,61): error TS2304: Cannot find name 't'.
|
|
||||||
Loading…
Reference in a new issue