diff --git a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.css b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.css index ea69858..f70b3d2 100644 --- a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.css +++ b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.css @@ -289,15 +289,21 @@ position: sticky; bottom: 0; display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; + flex-direction: column; + gap: 6px; margin: 4px -12px -12px; padding: 8px 12px; border-top: 1px solid #e2e8f0; background: #ffffff; } +.inputs-panel-savebar-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + .inputs-panel-savebar-status { min-width: 0; overflow: hidden; @@ -420,17 +426,21 @@ color: #64748b; } -/* One message for the whole panel, kept above the fields it concerns. */ +/* + * One message for the whole panel, in the sticky bar beside the button that triggered it: at the + * top of the panel it could be scrolled out of sight by the very fields it was about. + */ .inputs-panel-alert { display: flex; align-items: flex-start; gap: 6px; - padding: 7px 9px; + padding: 6px 8px; border: 1px solid #fecaca; border-radius: 6px; background: #fef2f2; color: #b91c1c; - font-size: 12px; + font-size: 11.5px; + line-height: 1.35; } .inputs-panel-alert .mat-icon { diff --git a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.html b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.html index 9fa4af7..2e585a3 100644 --- a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.html +++ b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.html @@ -71,13 +71,6 @@
No manual inputs required.
} @else { - @if (saveError(); as error) { - - } -
Inputs
@@ -113,16 +106,24 @@ @if (editableInputs().length && !readOnly()) {
- - @if (pendingCount()) { - {{ pendingCount() }} unsaved {{ pendingCount() === 1 ? 'change' : 'changes' }} - } @else { - All changes saved - } - - + @if (saveError(); as error) { + + } +
+ + @if (pendingCount()) { + {{ pendingCount() }} unsaved {{ pendingCount() === 1 ? 'change' : 'changes' }} + } @else { + All changes saved + } + + +
} } diff --git a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.spec.ts b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.spec.ts index 1336470..b3c7ffa 100644 --- a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.spec.ts +++ b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.spec.ts @@ -269,11 +269,20 @@ describe('TaskExecutionInputsPanelComponent', () => { makeInput({ key: 'g:a', inputName: 'a' }), makeInput({ key: 'g:b', inputName: 'b' }), makeInput({ key: 'g:c', inputName: 'c' }) - ], { saveError: 'Could not save the global inputs, so none of them were saved.' }); + ], { + pendingKeys: ['g:a', 'g:b', 'g:c'], + saveError: 'Could not save the global inputs, so none of them were saved.' + }); const alerts = fixture.nativeElement.querySelectorAll('.inputs-panel-alert'); expect(alerts.length).toBe(1); expect(alerts[0].textContent).toContain('none of them were saved'); + // In the sticky bar, next to the button that triggered it: at the top of a long panel it could + // be scrolled out of sight by the very fields it was about. + expect(fixture.nativeElement.querySelector('.inputs-panel-savebar .inputs-panel-alert')).not.toBeNull(); + // The edits are still pending, so the bar still counts them: the save is retryable. + expect(fixture.nativeElement.textContent).toContain('3 unsaved changes'); + expect(fixture.componentInstance.canSubmitAll()).toBe(true); }); it('shows no message when nothing has failed', async () => {