From 34170896d32ccd8abdd99aa9eebf2add3975d991 Mon Sep 17 00:00:00 2001 From: Alex Sorafumo Date: Fri, 2 Oct 2026 13:12:35 +1000 Subject: [PATCH 1/4] fix(signage-manager): keep templates on their live ID - Open a linked template past the first page. - Keep templates under their live ID and hold pending drafts until they are approved, undone or deleted. - Show a list error with Retry and the server total. - Confirm before undoing changes. - Show loading and errors when applying templates. - Decode names in the approved template and mapping lists. - Place floating layouts with no position at 50%, as the player does. - Drop a deleted template from the list and leave its route. --- apps/signage-manager/USER_STORIES.md | 5 +- .../template-approve-modal.component.ts | 18 +- .../shared/template-edit-modal.component.ts | 3 +- .../template-mapping-modal.component.ts | 3 +- .../app/shared/template-mappings.component.ts | 10 +- ...mplate-request-approval-modal.component.ts | 40 +- .../src/app/signage-plugin.service.ts | 7 +- .../app/templates/signage-template.service.ts | 193 +++++++++- .../templates/template-header.component.ts | 6 +- .../src/app/templates/template-layout.util.ts | 8 +- .../app/templates/template-list.component.ts | 16 +- .../templates/template-preview.component.ts | 7 +- .../src/app/templates/templates.component.ts | 87 ++++- ...emplate-approval-preview.component.spec.ts | 4 +- .../template-edit-modal.component.spec.ts | 11 + .../template-mapping-modal.component.spec.ts | 14 + .../template-mappings.component.spec.ts | 13 + ...e-request-approval-modal.component.spec.ts | 21 +- .../src/tests/signage-plugin.service.spec.ts | 29 ++ .../signage-template.service.spec.ts | 356 +++++++++++++++++- .../template-header.component.spec.ts | 36 ++ .../templates/template-layout.util.spec.ts | 10 + .../templates/template-list.component.spec.ts | 28 +- .../templates/templates.component.spec.ts | 92 ++++- shared/assets/locale/en-AU.json | 1 + 25 files changed, 917 insertions(+), 101 deletions(-) create mode 100644 apps/signage-manager/src/tests/templates/template-header.component.spec.ts diff --git a/apps/signage-manager/USER_STORIES.md b/apps/signage-manager/USER_STORIES.md index 8ecda384721..a84e65c4226 100644 --- a/apps/signage-manager/USER_STORIES.md +++ b/apps/signage-manager/USER_STORIES.md @@ -213,13 +213,16 @@ These stories cover the current app workflows: **Acceptance Criteria:** - The templates page shows a searchable template list and loads more templates as the user scrolls. +- The header shows the number of templates that match, including templates that are not loaded yet. +- If the list cannot load, the page shows an error with a retry button, not an empty list. +- A link to a template that is not in the loaded pages opens that template. - Users with create or update permission can create templates and edit their layout items. - Users with create permission can duplicate a template. The copy has the same settings and saved layouts. It is not approved and has no template mappings. - Template rows show approval-required and awaiting-review states. - Users with approval permission can review and approve a selected template. - Users without approval permission can select an approver and request template approval with a message. - The approval preview shows only changed layout items from the pending and approved templates, including the applicable X and Y values. It shows a no-older-version placeholder when no distinct approved version exists. -- Users with update permission can discard pending changes when an approved version exists. +- Users with update permission can discard pending changes when an approved version exists. They must confirm first. - Users must confirm before they leave a template that has unsaved layout changes. Confirming discards the changes. The browser warns before a reload or tab close drops them. --- diff --git a/apps/signage-manager/src/app/shared/template-approve-modal.component.ts b/apps/signage-manager/src/app/shared/template-approve-modal.component.ts index 5e99d7921a2..b21d1590da8 100644 --- a/apps/signage-manager/src/app/shared/template-approve-modal.component.ts +++ b/apps/signage-manager/src/app/shared/template-approve-modal.component.ts @@ -136,21 +136,15 @@ export class TemplateApproveModalComponent { public readonly has_previous_version = () => this.template_versions().length > 1; + /** Discard the pending version. The service asks the user to confirm. */ public async undoChanges() { const previous_version = this.template_versions()[1]; if (!previous_version) return; - this.loading.set(i18n('SIGNAGE_MANAGER.UNDOING_CHANGES')); - this._dialog_ref.disableClose = true; - try { - const undone = await this._template_service.undoTemplateChanges( - this._data.template.id, - previous_version, - ); - if (undone) this._dialog_ref.close(true); - } finally { - this.loading.set(''); - this._dialog_ref.disableClose = false; - } + const undone = await this._template_service.undoTemplateChanges( + this._data.template.id, + previous_version, + ); + if (undone) this._dialog_ref.close(true); } /** Approve the pending version. Blocked when the versions failed to load. */ diff --git a/apps/signage-manager/src/app/shared/template-edit-modal.component.ts b/apps/signage-manager/src/app/shared/template-edit-modal.component.ts index 19b9be997ca..fd3eed73711 100644 --- a/apps/signage-manager/src/app/shared/template-edit-modal.component.ts +++ b/apps/signage-manager/src/app/shared/template-edit-modal.component.ts @@ -308,11 +308,10 @@ export class TemplateEditModalComponent { this._dialog_ref.disableClose = false; this._dialog_ref.close(result); notifySuccess(i18n('SIGNAGE_MANAGER.TEMPLATE_SAVED')); - } catch (e) { + } catch { this._dialog_ref.disableClose = false; this.loading.set(false); notifyError(i18n('SIGNAGE_MANAGER.TEMPLATE_SAVE_ERROR')); - throw e; } }); } diff --git a/apps/signage-manager/src/app/shared/template-mapping-modal.component.ts b/apps/signage-manager/src/app/shared/template-mapping-modal.component.ts index 310dfda9a9d..5a334661c68 100644 --- a/apps/signage-manager/src/app/shared/template-mapping-modal.component.ts +++ b/apps/signage-manager/src/app/shared/template-mapping-modal.component.ts @@ -257,13 +257,12 @@ export class TemplateMappingModalComponent { notifySuccess( i18n('SIGNAGE_MANAGER.SVC_TEMPLATE_MAPPING_SAVED'), ); - } catch (error) { + } catch { this._dialog_ref.disableClose = false; this.loading.set(false); notifyError( i18n('SIGNAGE_MANAGER.SVC_TEMPLATE_MAPPING_SAVE_ERROR'), ); - throw error; } }); } diff --git a/apps/signage-manager/src/app/shared/template-mappings.component.ts b/apps/signage-manager/src/app/shared/template-mappings.component.ts index 96179069300..b627754841a 100644 --- a/apps/signage-manager/src/app/shared/template-mappings.component.ts +++ b/apps/signage-manager/src/app/shared/template-mappings.component.ts @@ -1,5 +1,6 @@ import { Component, computed, inject, input, resource } from '@angular/core'; import { MatRippleModule } from '@angular/material/core'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatTooltipModule } from '@angular/material/tooltip'; import { RouterLink } from '@angular/router'; import { i18n } from '@placeos/common'; @@ -52,11 +53,16 @@ export type TemplateMappingTargetType = 'display' | 'zone'; 'SIGNAGE_MANAGER.APPLY_TEMPLATE' | translate " (click)="editMapping()" + [disabled]="opening()" [attr.aria-label]=" 'SIGNAGE_MANAGER.APPLY_TEMPLATE' | translate " > - add + @if (opening()) { + + } @else { + add + } } @@ -320,6 +326,7 @@ export type TemplateMappingTargetType = 'display' | 'zone'; ], imports: [ MatRippleModule, + MatProgressSpinnerModule, MatTooltipModule, AuthenticatedImageDirective, IconComponent, @@ -335,6 +342,7 @@ export class TemplateMappingsComponent { public readonly target_type = input('display'); public readonly target_id = input(''); public readonly can_update = this._context.can_update; + public readonly opening = this._template_service.template_mapping_opening; private readonly _mappings = resource({ params: () => ({ diff --git a/apps/signage-manager/src/app/shared/template-request-approval-modal.component.ts b/apps/signage-manager/src/app/shared/template-request-approval-modal.component.ts index 5f0dcece958..7a5693219bc 100644 --- a/apps/signage-manager/src/app/shared/template-request-approval-modal.component.ts +++ b/apps/signage-manager/src/app/shared/template-request-approval-modal.component.ts @@ -143,9 +143,18 @@ export interface TemplateRequestApprovalModalResult { }} @if (show_preview()) { - + @if (versions_error()) { +

+ {{ + 'SIGNAGE_MANAGER.TEMPLATE_VERSIONS_LOAD_ERROR' + | translate + }} +

+ } @else { + + } }