@@ -0,0 +1,35 @@
|
||||
import { Directive, OnDestroy, OnInit, TemplateRef } from '@angular/core';
|
||||
import { PageHeaderService } from '../services/page-header.service';
|
||||
|
||||
/**
|
||||
* Place on an <ng-template> to project that page's action controls into the
|
||||
* shared system header's right-side slot (rendered by UserPortalComponent):
|
||||
*
|
||||
* <ng-template appPageHeaderActions>
|
||||
* <button kendoButton themeColor="primary" (click)="openAdd()">+ Add</button>
|
||||
* </ng-template>
|
||||
*
|
||||
* The directive publishes the template on init and clears it on destroy, so a
|
||||
* page's controls never leak to the next page.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[appPageHeaderActions]',
|
||||
standalone: true,
|
||||
})
|
||||
export class PageHeaderActionsDirective implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private template: TemplateRef<unknown>,
|
||||
private pageHeader: PageHeaderService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Defer one microtask: the shell consumes the actions signal in a view that
|
||||
// has already been checked by the time this child directive initializes, so
|
||||
// setting it synchronously would trip ExpressionChangedAfterItHasBeenChecked.
|
||||
queueMicrotask(() => this.pageHeader.setActions(this.template));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.pageHeader.clear();
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,23 @@ export const LANGUAGE_OPTIONS: readonly BilingualOption[] = [
|
||||
{ value: 'zh-TW', label: '中文/Chinese' },
|
||||
];
|
||||
|
||||
// Log severities — value must match the C# LogLevelEnum names exactly.
|
||||
export const LOG_LEVEL_OPTIONS: readonly BilingualOption[] = [
|
||||
{ value: 'Trace', label: 'Trace/追蹤' },
|
||||
{ value: 'Debug', label: 'Debug/除錯' },
|
||||
{ value: 'Information', label: 'Information/資訊' },
|
||||
{ value: 'Warning', label: 'Warning/警告' },
|
||||
{ value: 'Error', label: 'Error/錯誤' },
|
||||
{ value: 'Critical', label: 'Critical/嚴重' },
|
||||
];
|
||||
|
||||
// Audit categories — value must match the C# AuditCategories names exactly.
|
||||
export const AUDIT_CATEGORY_OPTIONS: readonly BilingualOption[] = [
|
||||
{ value: 'DataChange', label: 'Data Change/資料異動' },
|
||||
{ value: 'Security', label: 'Security/安全性' },
|
||||
{ value: 'Business', label: 'Business/業務操作' },
|
||||
];
|
||||
|
||||
export const ROLE_OPTIONS: readonly BilingualOption[] = [
|
||||
{ value: 'super_admin', label: 'Super Admin/系統管理員' },
|
||||
{ value: 'pastor', label: 'Pastor/牧師' },
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Injectable, TemplateRef, signal } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Bridges per-page action controls into the shared system header rendered by
|
||||
* UserPortalComponent. A child route cannot project content into its parent
|
||||
* shell through <router-outlet>, so pages register a TemplateRef here and the
|
||||
* shell renders it in the header's right-side actions slot.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PageHeaderService {
|
||||
/** The current page's action controls, or null when the page has none. */
|
||||
readonly actions = signal<TemplateRef<unknown> | null>(null);
|
||||
|
||||
/** Called by a page (typically in ngAfterViewInit) to publish its actions. */
|
||||
public setActions(actionsTemplate: TemplateRef<unknown> | null): void {
|
||||
this.actions.set(actionsTemplate);
|
||||
}
|
||||
|
||||
/** Called by a page in ngOnDestroy so its actions do not leak to the next page. */
|
||||
public clear(): void {
|
||||
this.actions.set(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// =============================================================================
|
||||
// Site-wide layout styles
|
||||
// =============================================================================
|
||||
// Shared, app-level classes used across portal pages. Kept global (not scoped
|
||||
// to any component) so the portal shell and any page can rely on them.
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Unified system header
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rendered once by the portal shell (UserPortalComponent) above the router
|
||||
// outlet, so every child page inherits the same eyebrow + bilingual title.
|
||||
.app-header {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-header__eyebrow {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: var(--kendo-color-primary, #0279cf);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.app-header__title {
|
||||
font-size: clamp(22px, 2.6vw, 30px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.1;
|
||||
color: var(--ink, #1c2a38);
|
||||
|
||||
span {
|
||||
font-weight: 400;
|
||||
color: var(--ink-soft, #50647c);
|
||||
margin-left: 8px;
|
||||
font-size: 0.62em;
|
||||
}
|
||||
}
|
||||
|
||||
// Right-side slot where pages project their own controls (Add buttons,
|
||||
// date-range filters, etc.) via PageHeaderService.
|
||||
.app-header__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
Reference in New Issue
Block a user