@masterteam/home-page 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-white:#fff;--spacing:.25rem;--container-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-semibold:600;--radius-xl:.75rem;--radius-2xl:1rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-auto{margin-top:auto}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.block{display:block}.flex{display:flex}.grid{display:grid}.h-3{height:calc(var(--spacing) * 3)}.h-5{height:calc(var(--spacing) * 5)}.h-10{height:calc(var(--spacing) * 10)}.h-full{height:100%}.min-h-40{min-height:calc(var(--spacing) * 40)}.min-h-\[28rem\]{min-height:28rem}.\!w-\[32rem\]{width:32rem!important}.\!w-\[58rem\]{width:58rem!important}.w-3{width:calc(var(--spacing) * 3)}.w-5{width:calc(var(--spacing) * 5)}.w-10{width:calc(var(--spacing) * 10)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.min-w-0{min-width:calc(var(--spacing) * 0)}.flex-1{flex:1}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-start{justify-content:flex-start}.gap-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-red-200{border-color:var(--color-red-200)}.border-surface{border-color:var(--p-content-border-color)}.bg-red-50{background-color:var(--color-red-50)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-3{padding-block:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.text-center{text-align:center}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-primary{color:var(--p-primary-color)}@supports (color:color-mix(in lab, red, red)){.text-primary{color:color-mix(in srgb, var(--p-primary-color) calc(100% * 1), transparent)}}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-surface-500{color:var(--p-surface-500)}@supports (color:color-mix(in lab, red, red)){.text-surface-500{color:color-mix(in srgb, var(--p-surface-500) calc(100% * 1), transparent)}}.text-surface-900{color:var(--p-surface-900)}@supports (color:color-mix(in lab, red, red)){.text-surface-900{color:color-mix(in srgb, var(--p-surface-900) calc(100% * 1), transparent)}}.text-white{color:var(--color-white)}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.md\:items-center{align-items:center}.md\:justify-end{justify-content:flex-end}.md\:p-5{padding:calc(var(--spacing) * 5)}.md\:px-5{padding-inline:calc(var(--spacing) * 5)}.md\:py-4{padding-block:calc(var(--spacing) * 4)}.md\:pb-5{padding-bottom:calc(var(--spacing) * 5)}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}}}@keyframes enter{0%{opacity:var(--p-enter-opacity,1);transform:translate3d(var(--p-enter-translate-x,0), var(--p-enter-translate-y,0), 0) scale3d(var(--p-enter-scale,1), var(--p-enter-scale,1), var(--p-enter-scale,1)) rotate(var(--p-enter-rotate,0))}}@keyframes leave{to{opacity:var(--p-leave-opacity,1);transform:translate3d(var(--p-leave-translate-x,0), var(--p-leave-translate-y,0), 0) scale3d(var(--p-leave-scale,1), var(--p-leave-scale,1), var(--p-leave-scale,1)) rotate(var(--p-leave-rotate,0))}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}
|
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, Injectable, signal, computed, ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
4
|
+
import * as i1$1 from 'angular-gridster2';
|
|
5
|
+
import { GridsterModule } from 'angular-gridster2';
|
|
6
|
+
import { Button } from '@masterteam/components/button';
|
|
7
|
+
import { Card } from '@masterteam/components/card';
|
|
8
|
+
import { ModalService } from '@masterteam/components/modal';
|
|
9
|
+
import { Icon } from '@masterteam/icons';
|
|
10
|
+
import * as i1 from '@angular/forms';
|
|
11
|
+
import { FormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
12
|
+
import { ValidatorConfig } from '@masterteam/components';
|
|
13
|
+
import { ModalRef } from '@masterteam/components/dialog';
|
|
14
|
+
import { DynamicForm } from '@masterteam/forms/dynamic-form';
|
|
15
|
+
import { map, finalize } from 'rxjs';
|
|
16
|
+
import { HttpClient } from '@angular/common/http';
|
|
17
|
+
import { ToggleField } from '@masterteam/components/toggle-field';
|
|
18
|
+
|
|
19
|
+
class HomePageApi {
|
|
20
|
+
http = inject(HttpClient);
|
|
21
|
+
getRuntime() {
|
|
22
|
+
return this.http
|
|
23
|
+
.get('home/runtime')
|
|
24
|
+
.pipe(map((response) => this.unwrapResponse(response, 'Failed to load home page runtime.')));
|
|
25
|
+
}
|
|
26
|
+
updatePage(pageId, request) {
|
|
27
|
+
return this.http
|
|
28
|
+
.put(`home/pages/${pageId}`, request)
|
|
29
|
+
.pipe(map((response) => this.unwrapResponse(response, 'Failed to update home page.')));
|
|
30
|
+
}
|
|
31
|
+
unwrapResponse(response, fallbackMessage) {
|
|
32
|
+
if (response.code === 2) {
|
|
33
|
+
throw new Error(response.errors?.message || response.message || fallbackMessage);
|
|
34
|
+
}
|
|
35
|
+
return response.data;
|
|
36
|
+
}
|
|
37
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePageApi, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
38
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePageApi, providedIn: 'root' });
|
|
39
|
+
}
|
|
40
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePageApi, decorators: [{
|
|
41
|
+
type: Injectable,
|
|
42
|
+
args: [{ providedIn: 'root' }]
|
|
43
|
+
}] });
|
|
44
|
+
|
|
45
|
+
const DEFAULT_WIDGET_SIZE = { w: 4, h: 4 };
|
|
46
|
+
const DEFAULT_WIDGET_ICON = 'chart.bar-chart-01';
|
|
47
|
+
const AVAILABLE_WIDGETS = [
|
|
48
|
+
{
|
|
49
|
+
id: 'project-status',
|
|
50
|
+
name: 'Project Status',
|
|
51
|
+
description: 'Overview of project delivery and health.',
|
|
52
|
+
icon: 'chart.pie-chart-01',
|
|
53
|
+
color: '#12B3C7',
|
|
54
|
+
category: 'chart',
|
|
55
|
+
layout: DEFAULT_WIDGET_SIZE,
|
|
56
|
+
reportId: 'project-status',
|
|
57
|
+
chartComponentId: 'project-status',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'risk-matrix',
|
|
61
|
+
name: 'Risk Matrix',
|
|
62
|
+
description: 'Snapshot of current risk exposure by priority.',
|
|
63
|
+
icon: 'chart.bar-chart-01',
|
|
64
|
+
color: '#F59E0B',
|
|
65
|
+
category: 'chart',
|
|
66
|
+
layout: DEFAULT_WIDGET_SIZE,
|
|
67
|
+
reportId: 'risk-matrix',
|
|
68
|
+
chartComponentId: 'risk-matrix',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'budget-utilization',
|
|
72
|
+
name: 'Budget Utilization',
|
|
73
|
+
description: 'Planned versus consumed budget trend.',
|
|
74
|
+
icon: 'finance.wallet-03',
|
|
75
|
+
color: '#10B981',
|
|
76
|
+
category: 'chart',
|
|
77
|
+
layout: { w: 6, h: 4 },
|
|
78
|
+
reportId: 'budget-utilization',
|
|
79
|
+
chartComponentId: 'budget-utilization',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'gantt-chart',
|
|
83
|
+
name: 'Gantt Chart',
|
|
84
|
+
description: 'Project schedule and timeline overview.',
|
|
85
|
+
icon: 'layout.layout-grid-01',
|
|
86
|
+
color: '#6366F1',
|
|
87
|
+
category: 'chart',
|
|
88
|
+
layout: { w: 6, h: 4 },
|
|
89
|
+
reportId: 'gantt-chart',
|
|
90
|
+
chartComponentId: 'gantt-chart',
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
class HomePageService {
|
|
94
|
+
api = inject(HomePageApi);
|
|
95
|
+
page = signal(null, ...(ngDevMode ? [{ debugName: "page" }] : []));
|
|
96
|
+
availableWidgets = signal(AVAILABLE_WIDGETS, ...(ngDevMode ? [{ debugName: "availableWidgets" }] : []));
|
|
97
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
98
|
+
saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
|
|
99
|
+
loadingAvailableWidgets = signal(false, ...(ngDevMode ? [{ debugName: "loadingAvailableWidgets" }] : []));
|
|
100
|
+
error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
101
|
+
widgetCatalogError = signal(null, ...(ngDevMode ? [{ debugName: "widgetCatalogError" }] : []));
|
|
102
|
+
selectedPage = computed(() => this.page(), ...(ngDevMode ? [{ debugName: "selectedPage" }] : []));
|
|
103
|
+
selectedWidgets = computed(() => this.toWidgets(this.page()?.schema.cards ?? []).sort((a, b) => a.y !== b.y ? a.y - b.y : a.x - b.x), ...(ngDevMode ? [{ debugName: "selectedWidgets" }] : []));
|
|
104
|
+
constructor() {
|
|
105
|
+
this.load();
|
|
106
|
+
}
|
|
107
|
+
load() {
|
|
108
|
+
this.loading.set(true);
|
|
109
|
+
this.error.set(null);
|
|
110
|
+
this.api
|
|
111
|
+
.getRuntime()
|
|
112
|
+
.pipe(finalize(() => this.loading.set(false)))
|
|
113
|
+
.subscribe({
|
|
114
|
+
next: (runtime) => this.page.set(runtime.selectedPage),
|
|
115
|
+
error: (error) => this.error.set(this.getMessage(error, 'Failed to load home page.')),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
reload() {
|
|
119
|
+
this.load();
|
|
120
|
+
}
|
|
121
|
+
loadAvailableWidgets() {
|
|
122
|
+
this.availableWidgets.set(AVAILABLE_WIDGETS);
|
|
123
|
+
this.widgetCatalogError.set(null);
|
|
124
|
+
this.loadingAvailableWidgets.set(false);
|
|
125
|
+
}
|
|
126
|
+
updatePage(pageId, draft) {
|
|
127
|
+
const page = this.page();
|
|
128
|
+
if (!page || page.id !== pageId) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const name = draft.name.trim() || page.name.display;
|
|
132
|
+
const key = draft.key?.trim() || page.key;
|
|
133
|
+
this.savePage(page.id, {
|
|
134
|
+
key,
|
|
135
|
+
name: { en: name, ar: name },
|
|
136
|
+
}, 'Failed to update home page.');
|
|
137
|
+
}
|
|
138
|
+
isWidgetEnabled(widgetId) {
|
|
139
|
+
return this.selectedWidgets().some((widget) => widget.widgetId === widgetId);
|
|
140
|
+
}
|
|
141
|
+
toggleWidget(widgetId, enabled) {
|
|
142
|
+
if (enabled) {
|
|
143
|
+
this.addWidget(widgetId);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.removeWidget(widgetId);
|
|
147
|
+
}
|
|
148
|
+
addWidget(widgetId) {
|
|
149
|
+
const page = this.page();
|
|
150
|
+
const widget = this.availableWidgets().find((item) => item.id === widgetId);
|
|
151
|
+
if (!page || !widget || this.isWidgetEnabled(widgetId)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const cards = [...page.schema.cards];
|
|
155
|
+
const layout = this.findNextLayout(widget.layout.w, widget.layout.h, cards);
|
|
156
|
+
const key = this.uniqueKey(cards, widget.id);
|
|
157
|
+
this.savePage(page.id, {
|
|
158
|
+
schema: {
|
|
159
|
+
version: page.schema.version,
|
|
160
|
+
cards: [
|
|
161
|
+
...cards,
|
|
162
|
+
{
|
|
163
|
+
key,
|
|
164
|
+
type: 'chart',
|
|
165
|
+
layout,
|
|
166
|
+
config: {},
|
|
167
|
+
props: {
|
|
168
|
+
name: widget.name,
|
|
169
|
+
description: widget.description,
|
|
170
|
+
icon: widget.icon,
|
|
171
|
+
color: widget.color,
|
|
172
|
+
category: widget.category,
|
|
173
|
+
widgetId: widget.id,
|
|
174
|
+
reportId: widget.reportId,
|
|
175
|
+
dashboardId: widget.reportId,
|
|
176
|
+
chartComponentId: widget.chartComponentId,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
}, 'Failed to add widget to home page.');
|
|
182
|
+
}
|
|
183
|
+
removeWidget(widgetId) {
|
|
184
|
+
const page = this.page();
|
|
185
|
+
if (!page) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.savePage(page.id, {
|
|
189
|
+
schema: {
|
|
190
|
+
version: page.schema.version,
|
|
191
|
+
cards: page.schema.cards.filter((card) => this.getWidgetId(card) !== widgetId),
|
|
192
|
+
},
|
|
193
|
+
}, 'Failed to remove widget from home page.');
|
|
194
|
+
}
|
|
195
|
+
updateWidgetLayout(widgetId, layout) {
|
|
196
|
+
const page = this.page();
|
|
197
|
+
if (!page) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.savePage(page.id, {
|
|
201
|
+
schema: {
|
|
202
|
+
version: page.schema.version,
|
|
203
|
+
cards: page.schema.cards.map((card) => this.getWidgetId(card) === widgetId
|
|
204
|
+
? {
|
|
205
|
+
...card,
|
|
206
|
+
layout: {
|
|
207
|
+
x: layout.x,
|
|
208
|
+
y: layout.y,
|
|
209
|
+
w: layout.cols,
|
|
210
|
+
h: layout.rows,
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
: card),
|
|
214
|
+
},
|
|
215
|
+
}, 'Failed to save widget layout.');
|
|
216
|
+
}
|
|
217
|
+
savePage(pageId, request, fallbackMessage) {
|
|
218
|
+
this.error.set(null);
|
|
219
|
+
this.saving.set(true);
|
|
220
|
+
this.api
|
|
221
|
+
.updatePage(pageId, request)
|
|
222
|
+
.pipe(finalize(() => this.saving.set(false)))
|
|
223
|
+
.subscribe({
|
|
224
|
+
next: (page) => this.page.set(page),
|
|
225
|
+
error: (error) => this.error.set(this.getMessage(error, fallbackMessage)),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
toWidgets(cards) {
|
|
229
|
+
return cards.map((card) => {
|
|
230
|
+
const props = (card.props ?? {});
|
|
231
|
+
return {
|
|
232
|
+
widgetId: this.getWidgetId(card),
|
|
233
|
+
key: card.key,
|
|
234
|
+
type: card.type,
|
|
235
|
+
x: card.layout?.x ?? 0,
|
|
236
|
+
y: card.layout?.y ?? 0,
|
|
237
|
+
cols: card.layout?.w ?? DEFAULT_WIDGET_SIZE.w,
|
|
238
|
+
rows: card.layout?.h ?? DEFAULT_WIDGET_SIZE.h,
|
|
239
|
+
config: card.config,
|
|
240
|
+
props: {
|
|
241
|
+
...props,
|
|
242
|
+
name: props.name || card.key,
|
|
243
|
+
description: props.description ||
|
|
244
|
+
`Chart component ${props.chartComponentId || card.key}`,
|
|
245
|
+
icon: props.icon || DEFAULT_WIDGET_ICON,
|
|
246
|
+
color: props.color || null,
|
|
247
|
+
widgetId: props.widgetId || card.key,
|
|
248
|
+
reportId: props.reportId ?? props.dashboardId ?? null,
|
|
249
|
+
dashboardId: props.dashboardId ?? props.reportId ?? null,
|
|
250
|
+
chartComponentId: props.chartComponentId || null,
|
|
251
|
+
},
|
|
252
|
+
permissions: card.permissions,
|
|
253
|
+
style: card.style,
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
getWidgetId(card) {
|
|
258
|
+
const props = (card.props ?? {});
|
|
259
|
+
if (props.widgetId) {
|
|
260
|
+
return props.widgetId;
|
|
261
|
+
}
|
|
262
|
+
const reportId = props.reportId ?? props.dashboardId;
|
|
263
|
+
const chartComponentId = props.chartComponentId;
|
|
264
|
+
return reportId && chartComponentId
|
|
265
|
+
? `report:${reportId}:${chartComponentId}`
|
|
266
|
+
: card.key;
|
|
267
|
+
}
|
|
268
|
+
findNextLayout(w, h, cards) {
|
|
269
|
+
let y = 0;
|
|
270
|
+
while (true) {
|
|
271
|
+
for (let x = 0; x <= 12 - w; x += 1) {
|
|
272
|
+
const hasOverlap = cards.some((card) => {
|
|
273
|
+
const current = card.layout || {
|
|
274
|
+
x: 0,
|
|
275
|
+
y: 0,
|
|
276
|
+
w: DEFAULT_WIDGET_SIZE.w,
|
|
277
|
+
h: DEFAULT_WIDGET_SIZE.h,
|
|
278
|
+
};
|
|
279
|
+
return !(current.x + current.w <= x ||
|
|
280
|
+
x + w <= current.x ||
|
|
281
|
+
current.y + current.h <= y ||
|
|
282
|
+
y + h <= current.y);
|
|
283
|
+
});
|
|
284
|
+
if (!hasOverlap) {
|
|
285
|
+
return { x, y, w, h };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
y += 1;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
uniqueKey(cards, base) {
|
|
292
|
+
let key = base;
|
|
293
|
+
let index = 2;
|
|
294
|
+
while (cards.some((card) => card.key === key)) {
|
|
295
|
+
key = `${base}-${index}`;
|
|
296
|
+
index += 1;
|
|
297
|
+
}
|
|
298
|
+
return key;
|
|
299
|
+
}
|
|
300
|
+
getMessage(error, fallbackMessage) {
|
|
301
|
+
return (error?.error?.message ||
|
|
302
|
+
error?.message ||
|
|
303
|
+
fallbackMessage);
|
|
304
|
+
}
|
|
305
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
306
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePageService, providedIn: 'root' });
|
|
307
|
+
}
|
|
308
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePageService, decorators: [{
|
|
309
|
+
type: Injectable,
|
|
310
|
+
args: [{ providedIn: 'root' }]
|
|
311
|
+
}], ctorParameters: () => [] });
|
|
312
|
+
|
|
313
|
+
class HomePagePageDialog {
|
|
314
|
+
modalService = inject(ModalService);
|
|
315
|
+
ref = inject(ModalRef);
|
|
316
|
+
homePageService = inject(HomePageService);
|
|
317
|
+
formControl = new FormControl({}, { nonNullable: true });
|
|
318
|
+
formConfig = {
|
|
319
|
+
sections: [
|
|
320
|
+
{
|
|
321
|
+
key: 'page-form',
|
|
322
|
+
type: 'none',
|
|
323
|
+
columns: 12,
|
|
324
|
+
order: 1,
|
|
325
|
+
fields: [
|
|
326
|
+
{
|
|
327
|
+
key: 'name',
|
|
328
|
+
label: 'Page Name',
|
|
329
|
+
type: 'text',
|
|
330
|
+
placeholder: 'Operations Home',
|
|
331
|
+
validators: [ValidatorConfig.required()],
|
|
332
|
+
colSpan: 12,
|
|
333
|
+
order: 1,
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
key: 'key',
|
|
337
|
+
label: 'Page Key',
|
|
338
|
+
type: 'text',
|
|
339
|
+
placeholder: 'operations-home',
|
|
340
|
+
colSpan: 12,
|
|
341
|
+
order: 2,
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
};
|
|
347
|
+
constructor() {
|
|
348
|
+
const page = this.homePageService.selectedPage();
|
|
349
|
+
if (!page) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
this.formControl.patchValue({
|
|
353
|
+
name: page.name.display,
|
|
354
|
+
key: page.key,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
save() {
|
|
358
|
+
if (this.formControl.invalid) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const page = this.homePageService.selectedPage();
|
|
362
|
+
if (!page) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const value = this.formControl.getRawValue();
|
|
366
|
+
this.homePageService.updatePage(page.id, {
|
|
367
|
+
name: String(value['name'] ?? ''),
|
|
368
|
+
key: String(value['key'] ?? ''),
|
|
369
|
+
});
|
|
370
|
+
this.ref.close(true);
|
|
371
|
+
}
|
|
372
|
+
close() {
|
|
373
|
+
this.ref.close(false);
|
|
374
|
+
}
|
|
375
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePagePageDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
376
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.3", type: HomePagePageDialog, isStandalone: true, selector: "mt-home-page-page-dialog", ngImport: i0, template: "<div [class]=\"[modalService.contentClass, 'p-4']\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\" />\r\n</div>\r\n\r\n<div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n label=\"Cancel\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"homePageService.saving()\"\r\n (onClick)=\"close()\"\r\n />\r\n <mt-button\r\n label=\"Save\"\r\n severity=\"primary\"\r\n [disabled]=\"formControl.invalid || homePageService.saving()\"\r\n (onClick)=\"save()\"\r\n />\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys"], outputs: ["runtimeMessagesChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
377
|
+
}
|
|
378
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePagePageDialog, decorators: [{
|
|
379
|
+
type: Component,
|
|
380
|
+
args: [{ selector: 'mt-home-page-page-dialog', standalone: true, imports: [CommonModule, ReactiveFormsModule, DynamicForm, Button], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [class]=\"[modalService.contentClass, 'p-4']\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\" />\r\n</div>\r\n\r\n<div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n label=\"Cancel\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"homePageService.saving()\"\r\n (onClick)=\"close()\"\r\n />\r\n <mt-button\r\n label=\"Save\"\r\n severity=\"primary\"\r\n [disabled]=\"formControl.invalid || homePageService.saving()\"\r\n (onClick)=\"save()\"\r\n />\r\n</div>\r\n" }]
|
|
381
|
+
}], ctorParameters: () => [] });
|
|
382
|
+
|
|
383
|
+
class HomePageWidgetDialog {
|
|
384
|
+
modalService = inject(ModalService);
|
|
385
|
+
ref = inject(ModalRef);
|
|
386
|
+
homePageService = inject(HomePageService);
|
|
387
|
+
pageLabel = () => this.homePageService.selectedPage()?.name.display ?? 'Home';
|
|
388
|
+
constructor() {
|
|
389
|
+
this.homePageService.loadAvailableWidgets();
|
|
390
|
+
}
|
|
391
|
+
availableWidgets = this.homePageService.availableWidgets;
|
|
392
|
+
isLoading = this.homePageService.loadingAvailableWidgets;
|
|
393
|
+
errorMessage = this.homePageService.widgetCatalogError;
|
|
394
|
+
isEnabled(widgetId) {
|
|
395
|
+
return this.homePageService.isWidgetEnabled(widgetId);
|
|
396
|
+
}
|
|
397
|
+
onToggle(widget, value) {
|
|
398
|
+
this.homePageService.toggleWidget(widget.id, !!value);
|
|
399
|
+
}
|
|
400
|
+
close() {
|
|
401
|
+
this.ref.close(true);
|
|
402
|
+
}
|
|
403
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePageWidgetDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
404
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: HomePageWidgetDialog, isStandalone: true, selector: "mt-home-page-widget-dialog", ngImport: i0, template: "<div [class]=\"[modalService.contentClass, 'p-5']\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"grid gap-4 p-4\">\r\n <div class=\"flex flex-wrap items-center justify-between gap-3\">\r\n <p class=\"text-sm text-surface-500\">\r\n Toggle widgets for\r\n <strong class=\"text-surface-900\">{{ pageLabel() }}</strong>\r\n </p>\r\n </div>\r\n\r\n @if (isLoading()) {\r\n <div\r\n class=\"flex min-h-40 items-center justify-center text-sm text-surface-500\"\r\n >\r\n Loading available widgets...\r\n </div>\r\n } @else if (errorMessage()) {\r\n <div\r\n class=\"rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600\"\r\n >\r\n {{ errorMessage() }}\r\n </div>\r\n } @else if (availableWidgets().length) {\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n @for (widget of availableWidgets(); track widget.id) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"widget.name\"\r\n [descriptionCard]=\"widget.description\"\r\n [icon]=\"widget.icon || 'chart.bar-chart-01'\"\r\n [ngModel]=\"isEnabled(widget.id)\"\r\n (ngModelChange)=\"onToggle(widget, $event)\"\r\n >\r\n <ng-template #toggleCardBottom>\r\n <div\r\n class=\"mt-4 flex items-center justify-between gap-3 border-t border-surface pt-4 text-xs text-surface-500\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span\r\n class=\"h-3 w-3 rounded-full\"\r\n [style.background]=\"\r\n widget.color || 'var(--p-primary-color)'\r\n \"\r\n ></span>\r\n <span>{{ widget.category }}</span>\r\n </div>\r\n <span\r\n >{{ widget.chartComponentId }} | {{ widget.layout.w }} x\r\n {{ widget.layout.h }}</span\r\n >\r\n </div>\r\n </ng-template>\r\n </mt-toggle-field>\r\n }\r\n </div>\r\n } @else {\r\n <div\r\n class=\"flex min-h-40 items-center justify-center text-sm text-surface-500\"\r\n >\r\n No widgets are available for this page.\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n</div>\r\n\r\n<div [class]=\"modalService.footerClass\">\r\n <div class=\"text-sm text-surface-500\">\r\n Changes are applied immediately to the selected page.\r\n </div>\r\n\r\n <mt-button label=\"Close\" severity=\"primary\" (onClick)=\"close()\" />\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
405
|
+
}
|
|
406
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePageWidgetDialog, decorators: [{
|
|
407
|
+
type: Component,
|
|
408
|
+
args: [{ selector: 'mt-home-page-widget-dialog', standalone: true, imports: [CommonModule, FormsModule, ToggleField, Button, Card], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [class]=\"[modalService.contentClass, 'p-5']\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"grid gap-4 p-4\">\r\n <div class=\"flex flex-wrap items-center justify-between gap-3\">\r\n <p class=\"text-sm text-surface-500\">\r\n Toggle widgets for\r\n <strong class=\"text-surface-900\">{{ pageLabel() }}</strong>\r\n </p>\r\n </div>\r\n\r\n @if (isLoading()) {\r\n <div\r\n class=\"flex min-h-40 items-center justify-center text-sm text-surface-500\"\r\n >\r\n Loading available widgets...\r\n </div>\r\n } @else if (errorMessage()) {\r\n <div\r\n class=\"rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600\"\r\n >\r\n {{ errorMessage() }}\r\n </div>\r\n } @else if (availableWidgets().length) {\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n @for (widget of availableWidgets(); track widget.id) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"widget.name\"\r\n [descriptionCard]=\"widget.description\"\r\n [icon]=\"widget.icon || 'chart.bar-chart-01'\"\r\n [ngModel]=\"isEnabled(widget.id)\"\r\n (ngModelChange)=\"onToggle(widget, $event)\"\r\n >\r\n <ng-template #toggleCardBottom>\r\n <div\r\n class=\"mt-4 flex items-center justify-between gap-3 border-t border-surface pt-4 text-xs text-surface-500\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span\r\n class=\"h-3 w-3 rounded-full\"\r\n [style.background]=\"\r\n widget.color || 'var(--p-primary-color)'\r\n \"\r\n ></span>\r\n <span>{{ widget.category }}</span>\r\n </div>\r\n <span\r\n >{{ widget.chartComponentId }} | {{ widget.layout.w }} x\r\n {{ widget.layout.h }}</span\r\n >\r\n </div>\r\n </ng-template>\r\n </mt-toggle-field>\r\n }\r\n </div>\r\n } @else {\r\n <div\r\n class=\"flex min-h-40 items-center justify-center text-sm text-surface-500\"\r\n >\r\n No widgets are available for this page.\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n</div>\r\n\r\n<div [class]=\"modalService.footerClass\">\r\n <div class=\"text-sm text-surface-500\">\r\n Changes are applied immediately to the selected page.\r\n </div>\r\n\r\n <mt-button label=\"Close\" severity=\"primary\" (onClick)=\"close()\" />\r\n</div>\r\n" }]
|
|
409
|
+
}], ctorParameters: () => [] });
|
|
410
|
+
|
|
411
|
+
const HOME_PAGE_GRID_COLUMNS = 12;
|
|
412
|
+
const HOME_PAGE_GRID_ROW_HEIGHT = 60;
|
|
413
|
+
const HOME_PAGE_GRID_GAP = 10;
|
|
414
|
+
const HOME_PAGE_GRID_MIN_HEIGHT = 500;
|
|
415
|
+
class HomePage {
|
|
416
|
+
welcomeName = input.required(...(ngDevMode ? [{ debugName: "welcomeName" }] : []));
|
|
417
|
+
subtitle = input('Quickly access your workspace essentials.', ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
|
|
418
|
+
isCustomizing = signal(false, ...(ngDevMode ? [{ debugName: "isCustomizing" }] : []));
|
|
419
|
+
modalService = inject(ModalService);
|
|
420
|
+
homePageService = inject(HomePageService);
|
|
421
|
+
gridColumns = HOME_PAGE_GRID_COLUMNS;
|
|
422
|
+
gridGap = HOME_PAGE_GRID_GAP;
|
|
423
|
+
gridRowHeight = HOME_PAGE_GRID_ROW_HEIGHT;
|
|
424
|
+
isLoading = this.homePageService.loading;
|
|
425
|
+
isSaving = this.homePageService.saving;
|
|
426
|
+
errorMessage = this.homePageService.error;
|
|
427
|
+
selectedPage = this.homePageService.selectedPage;
|
|
428
|
+
widgets = this.homePageService.selectedWidgets;
|
|
429
|
+
hasWidgets = computed(() => this.widgets().length > 0, ...(ngDevMode ? [{ debugName: "hasWidgets" }] : []));
|
|
430
|
+
previewLayoutHeight = computed(() => {
|
|
431
|
+
const items = this.widgets();
|
|
432
|
+
if (!items.length) {
|
|
433
|
+
return `${HOME_PAGE_GRID_MIN_HEIGHT}px`;
|
|
434
|
+
}
|
|
435
|
+
const maxRow = Math.max(...items.map((item) => item.y + item.rows));
|
|
436
|
+
return `${Math.max(maxRow * HOME_PAGE_GRID_ROW_HEIGHT, HOME_PAGE_GRID_MIN_HEIGHT)}px`;
|
|
437
|
+
}, ...(ngDevMode ? [{ debugName: "previewLayoutHeight" }] : []));
|
|
438
|
+
gridOptions = {
|
|
439
|
+
gridType: 'verticalFixed',
|
|
440
|
+
compactType: 'none',
|
|
441
|
+
draggable: {
|
|
442
|
+
enabled: true,
|
|
443
|
+
ignoreContent: false,
|
|
444
|
+
stop: (item) => this.onWidgetLayoutChange(item),
|
|
445
|
+
},
|
|
446
|
+
resizable: {
|
|
447
|
+
enabled: true,
|
|
448
|
+
stop: (item) => this.onWidgetLayoutChange(item),
|
|
449
|
+
},
|
|
450
|
+
pushItems: true,
|
|
451
|
+
swap: true,
|
|
452
|
+
displayGrid: 'always',
|
|
453
|
+
minCols: HOME_PAGE_GRID_COLUMNS,
|
|
454
|
+
maxCols: HOME_PAGE_GRID_COLUMNS,
|
|
455
|
+
minRows: 6,
|
|
456
|
+
maxRows: 1000,
|
|
457
|
+
fixedRowHeight: HOME_PAGE_GRID_ROW_HEIGHT,
|
|
458
|
+
margin: HOME_PAGE_GRID_GAP,
|
|
459
|
+
outerMargin: false,
|
|
460
|
+
mobileBreakpoint: 0,
|
|
461
|
+
useTransformPositioning: true,
|
|
462
|
+
};
|
|
463
|
+
startCustomize() {
|
|
464
|
+
this.isCustomizing.set(true);
|
|
465
|
+
}
|
|
466
|
+
stopCustomize() {
|
|
467
|
+
this.isCustomizing.set(false);
|
|
468
|
+
}
|
|
469
|
+
openEditPage() {
|
|
470
|
+
if (!this.selectedPage()) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
this.modalService.openModal(HomePagePageDialog, 'dialog', {
|
|
474
|
+
header: 'Edit Page',
|
|
475
|
+
styleClass: '!w-[32rem]',
|
|
476
|
+
modal: true,
|
|
477
|
+
closable: true,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
openAddWidget() {
|
|
481
|
+
if (!this.selectedPage()) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
this.homePageService.loadAvailableWidgets();
|
|
485
|
+
this.modalService.openModal(HomePageWidgetDialog, 'dialog', {
|
|
486
|
+
header: 'Add Widget',
|
|
487
|
+
styleClass: '!w-[58rem]',
|
|
488
|
+
modal: true,
|
|
489
|
+
closable: true,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
removeWidget(widgetId) {
|
|
493
|
+
this.homePageService.removeWidget(widgetId);
|
|
494
|
+
}
|
|
495
|
+
reload() {
|
|
496
|
+
this.homePageService.reload();
|
|
497
|
+
}
|
|
498
|
+
getPreviewColumn(widget) {
|
|
499
|
+
return `${widget.x + 1} / span ${widget.cols}`;
|
|
500
|
+
}
|
|
501
|
+
getPreviewRow(widget) {
|
|
502
|
+
return `${widget.y + 1} / span ${widget.rows}`;
|
|
503
|
+
}
|
|
504
|
+
onWidgetLayoutChange(item) {
|
|
505
|
+
const widget = item;
|
|
506
|
+
this.homePageService.updateWidgetLayout(widget.widgetId, {
|
|
507
|
+
x: widget.x ?? 0,
|
|
508
|
+
y: widget.y ?? 0,
|
|
509
|
+
cols: widget.cols ?? 1,
|
|
510
|
+
rows: widget.rows ?? 1,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
514
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: HomePage, isStandalone: true, selector: "mt-home-page", inputs: { welcomeName: { classPropertyName: "welcomeName", publicName: "welcomeName", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block h-full w-full" }, ngImport: i0, template: "<section class=\"h-full w-full p-4 md:p-5\">\r\n <mt-card class=\"block h-full w-full\">\r\n <ng-template #headless>\r\n <div class=\"flex h-full flex-col\">\r\n <div\r\n class=\"grid gap-3 p-4 md:grid-cols-[minmax(0,1fr)_auto] md:items-center md:px-5 md:py-4\"\r\n >\r\n <div class=\"min-w-0 space-y-1\">\r\n <h2 class=\"text-base font-semibold text-surface-900 md:text-lg\">\r\n Welcome,\r\n <span class=\"text-primary\">{{ welcomeName() }}</span>\r\n </h2>\r\n <p class=\"max-w-2xl text-sm text-surface-500\">\r\n {{ subtitle() }}\r\n </p>\r\n </div>\r\n\r\n @if (isCustomizing()) {\r\n <div class=\"flex flex-wrap justify-start gap-2 md:justify-end\">\r\n <mt-button\r\n icon=\"general.edit-03\"\r\n label=\"Edit Page\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"isLoading() || isSaving() || !selectedPage()\"\r\n (onClick)=\"openEditPage()\"\r\n />\r\n <mt-button\r\n icon=\"layout.layout-grid-01\"\r\n label=\"Add Widget\"\r\n size=\"small\"\r\n severity=\"primary\"\r\n [disabled]=\"isLoading() || isSaving() || !selectedPage()\"\r\n (onClick)=\"openAddWidget()\"\r\n />\r\n <mt-button\r\n icon=\"general.x-close\"\r\n label=\"Stop Customize\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"isSaving()\"\r\n (onClick)=\"stopCustomize()\"\r\n />\r\n </div>\r\n } @else {\r\n <div class=\"flex justify-start md:justify-end\">\r\n <mt-button\r\n icon=\"layout.layout-grid-01\"\r\n label=\"Customize\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"isLoading() || !selectedPage()\"\r\n (onClick)=\"startCustomize()\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"flex-1 border-t border-surface px-4 pb-4 pt-4 md:px-5 md:pb-5\"\r\n >\r\n @if (errorMessage() && selectedPage()) {\r\n <div\r\n class=\"mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600\"\r\n >\r\n {{ errorMessage() }}\r\n </div>\r\n }\r\n\r\n @if (isSaving()) {\r\n <div class=\"mb-4 text-sm text-surface-500\">\r\n Saving home page changes...\r\n </div>\r\n }\r\n\r\n @if (isLoading() && !selectedPage()) {\r\n <div\r\n class=\"flex min-h-[28rem] items-center justify-center text-sm text-surface-500\"\r\n >\r\n Loading home page...\r\n </div>\r\n } @else if (errorMessage() && !selectedPage()) {\r\n <div\r\n class=\"flex min-h-[28rem] flex-col items-center justify-center gap-3 text-center\"\r\n >\r\n <p class=\"text-sm text-red-500\">{{ errorMessage() }}</p>\r\n <mt-button\r\n label=\"Retry\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n (onClick)=\"reload()\"\r\n />\r\n </div>\r\n } @else if (hasWidgets()) {\r\n @if (isCustomizing()) {\r\n <gridster\r\n [options]=\"gridOptions\"\r\n class=\"block w-full\"\r\n [style.min-height]=\"previewLayoutHeight()\"\r\n >\r\n @for (widget of widgets(); track widget.key) {\r\n <gridster-item [item]=\"widget\">\r\n <mt-card class=\"block h-full w-full\">\r\n <ng-template #headless>\r\n <div class=\"grid h-full gap-4 p-4\">\r\n <div class=\"flex items-start justify-between gap-3\">\r\n <div class=\"flex items-start gap-3\">\r\n <span\r\n class=\"flex h-10 w-10 items-center justify-center rounded-2xl text-white\"\r\n [style.background]=\"\r\n widget.props.color || 'var(--p-primary-color)'\r\n \"\r\n >\r\n <mt-icon\r\n [icon]=\"\r\n widget.props.icon || 'chart.bar-chart-01'\r\n \"\r\n class=\"h-5 w-5\"\r\n />\r\n </span>\r\n\r\n <div class=\"min-w-0 space-y-1\">\r\n <h3\r\n class=\"truncate text-base font-semibold text-surface-900\"\r\n >\r\n {{ widget.props.name }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ widget.props.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n severity=\"danger\"\r\n [text]=\"true\"\r\n (onClick)=\"removeWidget(widget.widgetId)\"\r\n />\r\n </div>\r\n\r\n <div class=\"grid gap-1 text-sm text-surface-500\">\r\n <span>Type: {{ widget.type }}</span>\r\n <span>\r\n Report:\r\n {{\r\n widget.props.dashboardId ||\r\n widget.props.reportId ||\r\n \"-\"\r\n }}\r\n </span>\r\n <span>\r\n Component:\r\n {{ widget.props.chartComponentId || widget.key }}\r\n </span>\r\n </div>\r\n\r\n <div\r\n class=\"mt-auto flex flex-wrap gap-4 text-sm text-surface-500\"\r\n >\r\n <span\r\n >Position: {{ widget.x }}, {{ widget.y }}</span\r\n >\r\n <span\r\n >Size: {{ widget.cols }} x {{ widget.rows }}</span\r\n >\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n </gridster-item>\r\n }\r\n </gridster>\r\n } @else {\r\n <div\r\n class=\"grid\"\r\n [style.min-height]=\"previewLayoutHeight()\"\r\n [style.grid-template-columns]=\"\r\n 'repeat(' + gridColumns + ', minmax(0, 1fr))'\r\n \"\r\n [style.grid-auto-rows]=\"gridRowHeight + 'px'\"\r\n [style.gap.px]=\"gridGap\"\r\n >\r\n @for (widget of widgets(); track widget.key) {\r\n <div\r\n class=\"h-full\"\r\n [style.grid-column]=\"getPreviewColumn(widget)\"\r\n [style.grid-row]=\"getPreviewRow(widget)\"\r\n >\r\n <mt-card class=\"block h-full w-full\">\r\n <ng-template #headless>\r\n <div class=\"grid h-full gap-4 p-4\">\r\n <div class=\"flex items-start gap-3\">\r\n <span\r\n class=\"flex h-10 w-10 items-center justify-center rounded-2xl text-white\"\r\n [style.background]=\"\r\n widget.props.color || 'var(--p-primary-color)'\r\n \"\r\n >\r\n <mt-icon\r\n [icon]=\"\r\n widget.props.icon || 'chart.bar-chart-01'\r\n \"\r\n class=\"h-5 w-5\"\r\n />\r\n </span>\r\n\r\n <div class=\"min-w-0 space-y-1\">\r\n <h3\r\n class=\"truncate text-base font-semibold text-surface-900\"\r\n >\r\n {{ widget.props.name }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ widget.props.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"grid gap-1 text-sm text-surface-500\">\r\n <span>Type: {{ widget.type }}</span>\r\n <span>\r\n Report:\r\n {{\r\n widget.props.dashboardId ||\r\n widget.props.reportId ||\r\n \"-\"\r\n }}\r\n </span>\r\n <span>\r\n Component:\r\n {{ widget.props.chartComponentId || widget.key }}\r\n </span>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n </div>\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div\r\n class=\"flex min-h-[28rem] items-center justify-center text-sm text-surface-500\"\r\n >\r\n @if (isCustomizing()) {\r\n Use Add Widget to place charts in the grid builder.\r\n } @else {\r\n No widgets on this page.\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n</section>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: GridsterModule }, { kind: "component", type: i1$1.GridsterComponent, selector: "gridster", inputs: ["options"] }, { kind: "component", type: i1$1.GridsterItemComponent, selector: "gridster-item", inputs: ["item"], outputs: ["itemInit", "itemChange", "itemResize"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
515
|
+
}
|
|
516
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HomePage, decorators: [{
|
|
517
|
+
type: Component,
|
|
518
|
+
args: [{ selector: 'mt-home-page', standalone: true, imports: [CommonModule, GridsterModule, Button, Card, Icon], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
519
|
+
class: 'block h-full w-full',
|
|
520
|
+
}, template: "<section class=\"h-full w-full p-4 md:p-5\">\r\n <mt-card class=\"block h-full w-full\">\r\n <ng-template #headless>\r\n <div class=\"flex h-full flex-col\">\r\n <div\r\n class=\"grid gap-3 p-4 md:grid-cols-[minmax(0,1fr)_auto] md:items-center md:px-5 md:py-4\"\r\n >\r\n <div class=\"min-w-0 space-y-1\">\r\n <h2 class=\"text-base font-semibold text-surface-900 md:text-lg\">\r\n Welcome,\r\n <span class=\"text-primary\">{{ welcomeName() }}</span>\r\n </h2>\r\n <p class=\"max-w-2xl text-sm text-surface-500\">\r\n {{ subtitle() }}\r\n </p>\r\n </div>\r\n\r\n @if (isCustomizing()) {\r\n <div class=\"flex flex-wrap justify-start gap-2 md:justify-end\">\r\n <mt-button\r\n icon=\"general.edit-03\"\r\n label=\"Edit Page\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"isLoading() || isSaving() || !selectedPage()\"\r\n (onClick)=\"openEditPage()\"\r\n />\r\n <mt-button\r\n icon=\"layout.layout-grid-01\"\r\n label=\"Add Widget\"\r\n size=\"small\"\r\n severity=\"primary\"\r\n [disabled]=\"isLoading() || isSaving() || !selectedPage()\"\r\n (onClick)=\"openAddWidget()\"\r\n />\r\n <mt-button\r\n icon=\"general.x-close\"\r\n label=\"Stop Customize\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"isSaving()\"\r\n (onClick)=\"stopCustomize()\"\r\n />\r\n </div>\r\n } @else {\r\n <div class=\"flex justify-start md:justify-end\">\r\n <mt-button\r\n icon=\"layout.layout-grid-01\"\r\n label=\"Customize\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"isLoading() || !selectedPage()\"\r\n (onClick)=\"startCustomize()\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"flex-1 border-t border-surface px-4 pb-4 pt-4 md:px-5 md:pb-5\"\r\n >\r\n @if (errorMessage() && selectedPage()) {\r\n <div\r\n class=\"mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600\"\r\n >\r\n {{ errorMessage() }}\r\n </div>\r\n }\r\n\r\n @if (isSaving()) {\r\n <div class=\"mb-4 text-sm text-surface-500\">\r\n Saving home page changes...\r\n </div>\r\n }\r\n\r\n @if (isLoading() && !selectedPage()) {\r\n <div\r\n class=\"flex min-h-[28rem] items-center justify-center text-sm text-surface-500\"\r\n >\r\n Loading home page...\r\n </div>\r\n } @else if (errorMessage() && !selectedPage()) {\r\n <div\r\n class=\"flex min-h-[28rem] flex-col items-center justify-center gap-3 text-center\"\r\n >\r\n <p class=\"text-sm text-red-500\">{{ errorMessage() }}</p>\r\n <mt-button\r\n label=\"Retry\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n (onClick)=\"reload()\"\r\n />\r\n </div>\r\n } @else if (hasWidgets()) {\r\n @if (isCustomizing()) {\r\n <gridster\r\n [options]=\"gridOptions\"\r\n class=\"block w-full\"\r\n [style.min-height]=\"previewLayoutHeight()\"\r\n >\r\n @for (widget of widgets(); track widget.key) {\r\n <gridster-item [item]=\"widget\">\r\n <mt-card class=\"block h-full w-full\">\r\n <ng-template #headless>\r\n <div class=\"grid h-full gap-4 p-4\">\r\n <div class=\"flex items-start justify-between gap-3\">\r\n <div class=\"flex items-start gap-3\">\r\n <span\r\n class=\"flex h-10 w-10 items-center justify-center rounded-2xl text-white\"\r\n [style.background]=\"\r\n widget.props.color || 'var(--p-primary-color)'\r\n \"\r\n >\r\n <mt-icon\r\n [icon]=\"\r\n widget.props.icon || 'chart.bar-chart-01'\r\n \"\r\n class=\"h-5 w-5\"\r\n />\r\n </span>\r\n\r\n <div class=\"min-w-0 space-y-1\">\r\n <h3\r\n class=\"truncate text-base font-semibold text-surface-900\"\r\n >\r\n {{ widget.props.name }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ widget.props.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n severity=\"danger\"\r\n [text]=\"true\"\r\n (onClick)=\"removeWidget(widget.widgetId)\"\r\n />\r\n </div>\r\n\r\n <div class=\"grid gap-1 text-sm text-surface-500\">\r\n <span>Type: {{ widget.type }}</span>\r\n <span>\r\n Report:\r\n {{\r\n widget.props.dashboardId ||\r\n widget.props.reportId ||\r\n \"-\"\r\n }}\r\n </span>\r\n <span>\r\n Component:\r\n {{ widget.props.chartComponentId || widget.key }}\r\n </span>\r\n </div>\r\n\r\n <div\r\n class=\"mt-auto flex flex-wrap gap-4 text-sm text-surface-500\"\r\n >\r\n <span\r\n >Position: {{ widget.x }}, {{ widget.y }}</span\r\n >\r\n <span\r\n >Size: {{ widget.cols }} x {{ widget.rows }}</span\r\n >\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n </gridster-item>\r\n }\r\n </gridster>\r\n } @else {\r\n <div\r\n class=\"grid\"\r\n [style.min-height]=\"previewLayoutHeight()\"\r\n [style.grid-template-columns]=\"\r\n 'repeat(' + gridColumns + ', minmax(0, 1fr))'\r\n \"\r\n [style.grid-auto-rows]=\"gridRowHeight + 'px'\"\r\n [style.gap.px]=\"gridGap\"\r\n >\r\n @for (widget of widgets(); track widget.key) {\r\n <div\r\n class=\"h-full\"\r\n [style.grid-column]=\"getPreviewColumn(widget)\"\r\n [style.grid-row]=\"getPreviewRow(widget)\"\r\n >\r\n <mt-card class=\"block h-full w-full\">\r\n <ng-template #headless>\r\n <div class=\"grid h-full gap-4 p-4\">\r\n <div class=\"flex items-start gap-3\">\r\n <span\r\n class=\"flex h-10 w-10 items-center justify-center rounded-2xl text-white\"\r\n [style.background]=\"\r\n widget.props.color || 'var(--p-primary-color)'\r\n \"\r\n >\r\n <mt-icon\r\n [icon]=\"\r\n widget.props.icon || 'chart.bar-chart-01'\r\n \"\r\n class=\"h-5 w-5\"\r\n />\r\n </span>\r\n\r\n <div class=\"min-w-0 space-y-1\">\r\n <h3\r\n class=\"truncate text-base font-semibold text-surface-900\"\r\n >\r\n {{ widget.props.name }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ widget.props.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"grid gap-1 text-sm text-surface-500\">\r\n <span>Type: {{ widget.type }}</span>\r\n <span>\r\n Report:\r\n {{\r\n widget.props.dashboardId ||\r\n widget.props.reportId ||\r\n \"-\"\r\n }}\r\n </span>\r\n <span>\r\n Component:\r\n {{ widget.props.chartComponentId || widget.key }}\r\n </span>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n </div>\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div\r\n class=\"flex min-h-[28rem] items-center justify-center text-sm text-surface-500\"\r\n >\r\n @if (isCustomizing()) {\r\n Use Add Widget to place charts in the grid builder.\r\n } @else {\r\n No widgets on this page.\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n</section>\r\n" }]
|
|
521
|
+
}], propDecorators: { welcomeName: [{ type: i0.Input, args: [{ isSignal: true, alias: "welcomeName", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }] } });
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Generated bundle index. Do not edit.
|
|
525
|
+
*/
|
|
526
|
+
|
|
527
|
+
export { HomePage, HomePagePageDialog, HomePageService, HomePageWidgetDialog };
|
|
528
|
+
//# sourceMappingURL=masterteam-home-page.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masterteam-home-page.mjs","sources":["../../../../packages/masterteam/home-page/src/lib/home-page.api.ts","../../../../packages/masterteam/home-page/src/lib/home-page.service.ts","../../../../packages/masterteam/home-page/src/lib/home-page-page-dialog/home-page-page-dialog.ts","../../../../packages/masterteam/home-page/src/lib/home-page-page-dialog/home-page-page-dialog.html","../../../../packages/masterteam/home-page/src/lib/home-page-widget-dialog/home-page-widget-dialog.ts","../../../../packages/masterteam/home-page/src/lib/home-page-widget-dialog/home-page-widget-dialog.html","../../../../packages/masterteam/home-page/src/lib/home-page/home-page.ts","../../../../packages/masterteam/home-page/src/lib/home-page/home-page.html","../../../../packages/masterteam/home-page/src/masterteam-home-page.ts"],"sourcesContent":["import { HttpClient } from '@angular/common/http';\r\nimport { Injectable, inject } from '@angular/core';\r\nimport { map, type Observable } from 'rxjs';\r\nimport type {\r\n AppResponseViewModel,\r\n HomePageDefinitionDto,\r\n HomePageRuntimeDto,\r\n HomePageUpdateRequest,\r\n} from './home-page.models';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class HomePageApi {\r\n private readonly http = inject(HttpClient);\r\n\r\n getRuntime(): Observable<HomePageRuntimeDto> {\r\n return this.http\r\n .get<AppResponseViewModel<HomePageRuntimeDto>>('home/runtime')\r\n .pipe(\r\n map((response) =>\r\n this.unwrapResponse(response, 'Failed to load home page runtime.'),\r\n ),\r\n );\r\n }\r\n\r\n updatePage(\r\n pageId: number,\r\n request: HomePageUpdateRequest,\r\n ): Observable<HomePageDefinitionDto> {\r\n return this.http\r\n .put<\r\n AppResponseViewModel<HomePageDefinitionDto>\r\n >(`home/pages/${pageId}`, request)\r\n .pipe(\r\n map((response) =>\r\n this.unwrapResponse(response, 'Failed to update home page.'),\r\n ),\r\n );\r\n }\r\n\r\n private unwrapResponse<T>(\r\n response: AppResponseViewModel<T>,\r\n fallbackMessage: string,\r\n ): T {\r\n if (response.code === 2) {\r\n throw new Error(\r\n response.errors?.message || response.message || fallbackMessage,\r\n );\r\n }\r\n\r\n return response.data;\r\n }\r\n}\r\n","import { Injectable, computed, inject, signal } from '@angular/core';\r\nimport { finalize } from 'rxjs';\r\nimport { HomePageApi } from './home-page.api';\r\nimport type {\r\n HomePageCardDto,\r\n HomePageDefinitionDto,\r\n HomePagePageDraft,\r\n HomePageUpdateRequest,\r\n HomePageWidgetCatalogItem,\r\n HomePageWidgetInstance,\r\n HomePageWidgetProps,\r\n} from './home-page.models';\r\n\r\nconst DEFAULT_WIDGET_SIZE = { w: 4, h: 4 };\r\nconst DEFAULT_WIDGET_ICON = 'chart.bar-chart-01';\r\nconst AVAILABLE_WIDGETS: HomePageWidgetCatalogItem[] = [\r\n {\r\n id: 'project-status',\r\n name: 'Project Status',\r\n description: 'Overview of project delivery and health.',\r\n icon: 'chart.pie-chart-01',\r\n color: '#12B3C7',\r\n category: 'chart',\r\n layout: DEFAULT_WIDGET_SIZE,\r\n reportId: 'project-status',\r\n chartComponentId: 'project-status',\r\n },\r\n {\r\n id: 'risk-matrix',\r\n name: 'Risk Matrix',\r\n description: 'Snapshot of current risk exposure by priority.',\r\n icon: 'chart.bar-chart-01',\r\n color: '#F59E0B',\r\n category: 'chart',\r\n layout: DEFAULT_WIDGET_SIZE,\r\n reportId: 'risk-matrix',\r\n chartComponentId: 'risk-matrix',\r\n },\r\n {\r\n id: 'budget-utilization',\r\n name: 'Budget Utilization',\r\n description: 'Planned versus consumed budget trend.',\r\n icon: 'finance.wallet-03',\r\n color: '#10B981',\r\n category: 'chart',\r\n layout: { w: 6, h: 4 },\r\n reportId: 'budget-utilization',\r\n chartComponentId: 'budget-utilization',\r\n },\r\n {\r\n id: 'gantt-chart',\r\n name: 'Gantt Chart',\r\n description: 'Project schedule and timeline overview.',\r\n icon: 'layout.layout-grid-01',\r\n color: '#6366F1',\r\n category: 'chart',\r\n layout: { w: 6, h: 4 },\r\n reportId: 'gantt-chart',\r\n chartComponentId: 'gantt-chart',\r\n },\r\n];\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class HomePageService {\r\n private readonly api = inject(HomePageApi);\r\n private readonly page = signal<HomePageDefinitionDto | null>(null);\r\n\r\n readonly availableWidgets =\r\n signal<HomePageWidgetCatalogItem[]>(AVAILABLE_WIDGETS);\r\n readonly loading = signal(false);\r\n readonly saving = signal(false);\r\n readonly loadingAvailableWidgets = signal(false);\r\n readonly error = signal<string | null>(null);\r\n readonly widgetCatalogError = signal<string | null>(null);\r\n\r\n readonly selectedPage = computed(() => this.page());\r\n readonly selectedWidgets = computed(() =>\r\n this.toWidgets(this.page()?.schema.cards ?? []).sort((a, b) =>\r\n a.y !== b.y ? a.y - b.y : a.x - b.x,\r\n ),\r\n );\r\n\r\n constructor() {\r\n this.load();\r\n }\r\n\r\n load(): void {\r\n this.loading.set(true);\r\n this.error.set(null);\r\n\r\n this.api\r\n .getRuntime()\r\n .pipe(finalize(() => this.loading.set(false)))\r\n .subscribe({\r\n next: (runtime) => this.page.set(runtime.selectedPage),\r\n error: (error) =>\r\n this.error.set(this.getMessage(error, 'Failed to load home page.')),\r\n });\r\n }\r\n\r\n reload(): void {\r\n this.load();\r\n }\r\n\r\n loadAvailableWidgets(): void {\r\n this.availableWidgets.set(AVAILABLE_WIDGETS);\r\n this.widgetCatalogError.set(null);\r\n this.loadingAvailableWidgets.set(false);\r\n }\r\n\r\n updatePage(pageId: number, draft: HomePagePageDraft): void {\r\n const page = this.page();\r\n if (!page || page.id !== pageId) {\r\n return;\r\n }\r\n\r\n const name = draft.name.trim() || page.name.display;\r\n const key = draft.key?.trim() || page.key;\r\n\r\n this.savePage(\r\n page.id,\r\n {\r\n key,\r\n name: { en: name, ar: name },\r\n },\r\n 'Failed to update home page.',\r\n );\r\n }\r\n\r\n isWidgetEnabled(widgetId: string): boolean {\r\n return this.selectedWidgets().some(\r\n (widget) => widget.widgetId === widgetId,\r\n );\r\n }\r\n\r\n toggleWidget(widgetId: string, enabled: boolean): void {\r\n if (enabled) {\r\n this.addWidget(widgetId);\r\n return;\r\n }\r\n\r\n this.removeWidget(widgetId);\r\n }\r\n\r\n addWidget(widgetId: string): void {\r\n const page = this.page();\r\n const widget = this.availableWidgets().find((item) => item.id === widgetId);\r\n\r\n if (!page || !widget || this.isWidgetEnabled(widgetId)) {\r\n return;\r\n }\r\n\r\n const cards = [...page.schema.cards];\r\n const layout = this.findNextLayout(widget.layout.w, widget.layout.h, cards);\r\n const key = this.uniqueKey(cards, widget.id);\r\n\r\n this.savePage(\r\n page.id,\r\n {\r\n schema: {\r\n version: page.schema.version,\r\n cards: [\r\n ...cards,\r\n {\r\n key,\r\n type: 'chart',\r\n layout,\r\n config: {},\r\n props: {\r\n name: widget.name,\r\n description: widget.description,\r\n icon: widget.icon,\r\n color: widget.color,\r\n category: widget.category,\r\n widgetId: widget.id,\r\n reportId: widget.reportId,\r\n dashboardId: widget.reportId,\r\n chartComponentId: widget.chartComponentId,\r\n },\r\n },\r\n ],\r\n },\r\n },\r\n 'Failed to add widget to home page.',\r\n );\r\n }\r\n\r\n removeWidget(widgetId: string): void {\r\n const page = this.page();\r\n if (!page) {\r\n return;\r\n }\r\n\r\n this.savePage(\r\n page.id,\r\n {\r\n schema: {\r\n version: page.schema.version,\r\n cards: page.schema.cards.filter(\r\n (card) => this.getWidgetId(card) !== widgetId,\r\n ),\r\n },\r\n },\r\n 'Failed to remove widget from home page.',\r\n );\r\n }\r\n\r\n updateWidgetLayout(\r\n widgetId: string,\r\n layout: Pick<HomePageWidgetInstance, 'x' | 'y' | 'cols' | 'rows'>,\r\n ): void {\r\n const page = this.page();\r\n if (!page) {\r\n return;\r\n }\r\n\r\n this.savePage(\r\n page.id,\r\n {\r\n schema: {\r\n version: page.schema.version,\r\n cards: page.schema.cards.map((card) =>\r\n this.getWidgetId(card) === widgetId\r\n ? {\r\n ...card,\r\n layout: {\r\n x: layout.x,\r\n y: layout.y,\r\n w: layout.cols,\r\n h: layout.rows,\r\n },\r\n }\r\n : card,\r\n ),\r\n },\r\n },\r\n 'Failed to save widget layout.',\r\n );\r\n }\r\n\r\n private savePage(\r\n pageId: number,\r\n request: HomePageUpdateRequest,\r\n fallbackMessage: string,\r\n ): void {\r\n this.error.set(null);\r\n this.saving.set(true);\r\n\r\n this.api\r\n .updatePage(pageId, request)\r\n .pipe(finalize(() => this.saving.set(false)))\r\n .subscribe({\r\n next: (page) => this.page.set(page),\r\n error: (error) =>\r\n this.error.set(this.getMessage(error, fallbackMessage)),\r\n });\r\n }\r\n\r\n private toWidgets(cards: HomePageCardDto[]): HomePageWidgetInstance[] {\r\n return cards.map((card) => {\r\n const props = (card.props ?? {}) as HomePageWidgetProps;\r\n\r\n return {\r\n widgetId: this.getWidgetId(card),\r\n key: card.key,\r\n type: card.type,\r\n x: card.layout?.x ?? 0,\r\n y: card.layout?.y ?? 0,\r\n cols: card.layout?.w ?? DEFAULT_WIDGET_SIZE.w,\r\n rows: card.layout?.h ?? DEFAULT_WIDGET_SIZE.h,\r\n config: card.config,\r\n props: {\r\n ...props,\r\n name: props.name || card.key,\r\n description:\r\n props.description ||\r\n `Chart component ${props.chartComponentId || card.key}`,\r\n icon: props.icon || DEFAULT_WIDGET_ICON,\r\n color: props.color || null,\r\n widgetId: props.widgetId || card.key,\r\n reportId: props.reportId ?? props.dashboardId ?? null,\r\n dashboardId: props.dashboardId ?? props.reportId ?? null,\r\n chartComponentId: props.chartComponentId || null,\r\n },\r\n permissions: card.permissions,\r\n style: card.style,\r\n };\r\n });\r\n }\r\n\r\n private getWidgetId(card: HomePageCardDto): string {\r\n const props = (card.props ?? {}) as HomePageWidgetProps;\r\n if (props.widgetId) {\r\n return props.widgetId;\r\n }\r\n\r\n const reportId = props.reportId ?? props.dashboardId;\r\n const chartComponentId = props.chartComponentId;\r\n\r\n return reportId && chartComponentId\r\n ? `report:${reportId}:${chartComponentId}`\r\n : card.key;\r\n }\r\n\r\n private findNextLayout(w: number, h: number, cards: HomePageCardDto[]) {\r\n let y = 0;\r\n\r\n while (true) {\r\n for (let x = 0; x <= 12 - w; x += 1) {\r\n const hasOverlap = cards.some((card) => {\r\n const current = card.layout || {\r\n x: 0,\r\n y: 0,\r\n w: DEFAULT_WIDGET_SIZE.w,\r\n h: DEFAULT_WIDGET_SIZE.h,\r\n };\r\n\r\n return !(\r\n current.x + current.w <= x ||\r\n x + w <= current.x ||\r\n current.y + current.h <= y ||\r\n y + h <= current.y\r\n );\r\n });\r\n\r\n if (!hasOverlap) {\r\n return { x, y, w, h };\r\n }\r\n }\r\n\r\n y += 1;\r\n }\r\n }\r\n\r\n private uniqueKey(cards: HomePageCardDto[], base: string): string {\r\n let key = base;\r\n let index = 2;\r\n\r\n while (cards.some((card) => card.key === key)) {\r\n key = `${base}-${index}`;\r\n index += 1;\r\n }\r\n\r\n return key;\r\n }\r\n\r\n private getMessage(error: unknown, fallbackMessage: string): string {\r\n return (\r\n (error as { error?: { message?: string } })?.error?.message ||\r\n (error as { message?: string })?.message ||\r\n fallbackMessage\r\n );\r\n }\r\n}\r\n","import { CommonModule } from '@angular/common';\r\nimport { ChangeDetectionStrategy, Component, inject } from '@angular/core';\r\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\r\nimport { DynamicFormConfig, ValidatorConfig } from '@masterteam/components';\r\nimport { ModalRef } from '@masterteam/components/dialog';\r\nimport { Button } from '@masterteam/components/button';\r\nimport { ModalService } from '@masterteam/components/modal';\r\nimport { DynamicForm } from '@masterteam/forms/dynamic-form';\r\nimport { HomePageService } from '../home-page.service';\r\n\r\n@Component({\r\n selector: 'mt-home-page-page-dialog',\r\n standalone: true,\r\n imports: [CommonModule, ReactiveFormsModule, DynamicForm, Button],\r\n templateUrl: './home-page-page-dialog.html',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n})\r\nexport class HomePagePageDialog {\r\n protected readonly modalService = inject(ModalService);\r\n private readonly ref = inject(ModalRef);\r\n protected readonly homePageService = inject(HomePageService);\r\n\r\n readonly formControl = new FormControl<Record<string, unknown>>(\r\n {},\r\n { nonNullable: true },\r\n );\r\n\r\n readonly formConfig: DynamicFormConfig = {\r\n sections: [\r\n {\r\n key: 'page-form',\r\n type: 'none',\r\n columns: 12,\r\n order: 1,\r\n fields: [\r\n {\r\n key: 'name',\r\n label: 'Page Name',\r\n type: 'text',\r\n placeholder: 'Operations Home',\r\n validators: [ValidatorConfig.required()],\r\n colSpan: 12,\r\n order: 1,\r\n },\r\n {\r\n key: 'key',\r\n label: 'Page Key',\r\n type: 'text',\r\n placeholder: 'operations-home',\r\n colSpan: 12,\r\n order: 2,\r\n },\r\n ],\r\n },\r\n ],\r\n };\r\n\r\n constructor() {\r\n const page = this.homePageService.selectedPage();\r\n if (!page) {\r\n return;\r\n }\r\n\r\n this.formControl.patchValue({\r\n name: page.name.display,\r\n key: page.key,\r\n });\r\n }\r\n\r\n save(): void {\r\n if (this.formControl.invalid) {\r\n return;\r\n }\r\n\r\n const page = this.homePageService.selectedPage();\r\n if (!page) {\r\n return;\r\n }\r\n\r\n const value = this.formControl.getRawValue();\r\n this.homePageService.updatePage(page.id, {\r\n name: String(value['name'] ?? ''),\r\n key: String(value['key'] ?? ''),\r\n });\r\n this.ref.close(true);\r\n }\r\n\r\n close(): void {\r\n this.ref.close(false);\r\n }\r\n}\r\n","<div [class]=\"[modalService.contentClass, 'p-4']\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\" />\r\n</div>\r\n\r\n<div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n label=\"Cancel\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"homePageService.saving()\"\r\n (onClick)=\"close()\"\r\n />\r\n <mt-button\r\n label=\"Save\"\r\n severity=\"primary\"\r\n [disabled]=\"formControl.invalid || homePageService.saving()\"\r\n (onClick)=\"save()\"\r\n />\r\n</div>\r\n","import { CommonModule } from '@angular/common';\r\nimport { ChangeDetectionStrategy, Component, inject } from '@angular/core';\r\nimport { FormsModule } from '@angular/forms';\r\nimport { Button } from '@masterteam/components/button';\r\nimport { Card } from '@masterteam/components/card';\r\nimport { ModalRef } from '@masterteam/components/dialog';\r\nimport { ModalService } from '@masterteam/components/modal';\r\nimport { ToggleField } from '@masterteam/components/toggle-field';\r\nimport type { HomePageWidgetCatalogItem } from '../home-page.models';\r\nimport { HomePageService } from '../home-page.service';\r\n\r\n@Component({\r\n selector: 'mt-home-page-widget-dialog',\r\n standalone: true,\r\n imports: [CommonModule, FormsModule, ToggleField, Button, Card],\r\n templateUrl: './home-page-widget-dialog.html',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n})\r\nexport class HomePageWidgetDialog {\r\n protected readonly modalService = inject(ModalService);\r\n private readonly ref = inject(ModalRef);\r\n protected readonly homePageService = inject(HomePageService);\r\n\r\n readonly pageLabel = () =>\r\n this.homePageService.selectedPage()?.name.display ?? 'Home';\r\n\r\n constructor() {\r\n this.homePageService.loadAvailableWidgets();\r\n }\r\n\r\n readonly availableWidgets = this.homePageService.availableWidgets;\r\n readonly isLoading = this.homePageService.loadingAvailableWidgets;\r\n readonly errorMessage = this.homePageService.widgetCatalogError;\r\n\r\n isEnabled(widgetId: string): boolean {\r\n return this.homePageService.isWidgetEnabled(widgetId);\r\n }\r\n\r\n onToggle(widget: HomePageWidgetCatalogItem, value: boolean | null): void {\r\n this.homePageService.toggleWidget(widget.id, !!value);\r\n }\r\n\r\n close(): void {\r\n this.ref.close(true);\r\n }\r\n}\r\n","<div [class]=\"[modalService.contentClass, 'p-5']\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"grid gap-4 p-4\">\r\n <div class=\"flex flex-wrap items-center justify-between gap-3\">\r\n <p class=\"text-sm text-surface-500\">\r\n Toggle widgets for\r\n <strong class=\"text-surface-900\">{{ pageLabel() }}</strong>\r\n </p>\r\n </div>\r\n\r\n @if (isLoading()) {\r\n <div\r\n class=\"flex min-h-40 items-center justify-center text-sm text-surface-500\"\r\n >\r\n Loading available widgets...\r\n </div>\r\n } @else if (errorMessage()) {\r\n <div\r\n class=\"rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600\"\r\n >\r\n {{ errorMessage() }}\r\n </div>\r\n } @else if (availableWidgets().length) {\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n @for (widget of availableWidgets(); track widget.id) {\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"widget.name\"\r\n [descriptionCard]=\"widget.description\"\r\n [icon]=\"widget.icon || 'chart.bar-chart-01'\"\r\n [ngModel]=\"isEnabled(widget.id)\"\r\n (ngModelChange)=\"onToggle(widget, $event)\"\r\n >\r\n <ng-template #toggleCardBottom>\r\n <div\r\n class=\"mt-4 flex items-center justify-between gap-3 border-t border-surface pt-4 text-xs text-surface-500\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span\r\n class=\"h-3 w-3 rounded-full\"\r\n [style.background]=\"\r\n widget.color || 'var(--p-primary-color)'\r\n \"\r\n ></span>\r\n <span>{{ widget.category }}</span>\r\n </div>\r\n <span\r\n >{{ widget.chartComponentId }} | {{ widget.layout.w }} x\r\n {{ widget.layout.h }}</span\r\n >\r\n </div>\r\n </ng-template>\r\n </mt-toggle-field>\r\n }\r\n </div>\r\n } @else {\r\n <div\r\n class=\"flex min-h-40 items-center justify-center text-sm text-surface-500\"\r\n >\r\n No widgets are available for this page.\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n</div>\r\n\r\n<div [class]=\"modalService.footerClass\">\r\n <div class=\"text-sm text-surface-500\">\r\n Changes are applied immediately to the selected page.\r\n </div>\r\n\r\n <mt-button label=\"Close\" severity=\"primary\" (onClick)=\"close()\" />\r\n</div>\r\n","import { CommonModule } from '@angular/common';\r\nimport {\r\n ChangeDetectionStrategy,\r\n Component,\r\n computed,\r\n inject,\r\n input,\r\n signal,\r\n} from '@angular/core';\r\nimport {\r\n GridsterConfig,\r\n GridsterItem,\r\n GridsterModule,\r\n} from 'angular-gridster2';\r\nimport { Button } from '@masterteam/components/button';\r\nimport { Card } from '@masterteam/components/card';\r\nimport { ModalService } from '@masterteam/components/modal';\r\nimport { Icon } from '@masterteam/icons';\r\nimport type { HomePageWidgetInstance } from '../home-page.models';\r\nimport { HomePagePageDialog } from '../home-page-page-dialog/home-page-page-dialog';\r\nimport { HomePageService } from '../home-page.service';\r\nimport { HomePageWidgetDialog } from '../home-page-widget-dialog/home-page-widget-dialog';\r\n\r\nconst HOME_PAGE_GRID_COLUMNS = 12;\r\nconst HOME_PAGE_GRID_ROW_HEIGHT = 60;\r\nconst HOME_PAGE_GRID_GAP = 10;\r\nconst HOME_PAGE_GRID_MIN_HEIGHT = 500;\r\n\r\n@Component({\r\n selector: 'mt-home-page',\r\n standalone: true,\r\n imports: [CommonModule, GridsterModule, Button, Card, Icon],\r\n templateUrl: './home-page.html',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n host: {\r\n class: 'block h-full w-full',\r\n },\r\n})\r\nexport default class HomePage {\r\n readonly welcomeName = input.required<string>();\r\n readonly subtitle = input('Quickly access your workspace essentials.');\r\n readonly isCustomizing = signal(false);\r\n\r\n private readonly modalService = inject(ModalService);\r\n protected readonly homePageService = inject(HomePageService);\r\n\r\n readonly gridColumns = HOME_PAGE_GRID_COLUMNS;\r\n readonly gridGap = HOME_PAGE_GRID_GAP;\r\n readonly gridRowHeight = HOME_PAGE_GRID_ROW_HEIGHT;\r\n readonly isLoading = this.homePageService.loading;\r\n readonly isSaving = this.homePageService.saving;\r\n readonly errorMessage = this.homePageService.error;\r\n readonly selectedPage = this.homePageService.selectedPage;\r\n readonly widgets = this.homePageService.selectedWidgets;\r\n readonly hasWidgets = computed(() => this.widgets().length > 0);\r\n readonly previewLayoutHeight = computed(() => {\r\n const items = this.widgets();\r\n if (!items.length) {\r\n return `${HOME_PAGE_GRID_MIN_HEIGHT}px`;\r\n }\r\n\r\n const maxRow = Math.max(...items.map((item) => item.y + item.rows));\r\n return `${Math.max(maxRow * HOME_PAGE_GRID_ROW_HEIGHT, HOME_PAGE_GRID_MIN_HEIGHT)}px`;\r\n });\r\n\r\n readonly gridOptions: GridsterConfig = {\r\n gridType: 'verticalFixed',\r\n compactType: 'none',\r\n draggable: {\r\n enabled: true,\r\n ignoreContent: false,\r\n stop: (item) => this.onWidgetLayoutChange(item),\r\n },\r\n resizable: {\r\n enabled: true,\r\n stop: (item) => this.onWidgetLayoutChange(item),\r\n },\r\n pushItems: true,\r\n swap: true,\r\n displayGrid: 'always',\r\n minCols: HOME_PAGE_GRID_COLUMNS,\r\n maxCols: HOME_PAGE_GRID_COLUMNS,\r\n minRows: 6,\r\n maxRows: 1000,\r\n fixedRowHeight: HOME_PAGE_GRID_ROW_HEIGHT,\r\n margin: HOME_PAGE_GRID_GAP,\r\n outerMargin: false,\r\n mobileBreakpoint: 0,\r\n useTransformPositioning: true,\r\n };\r\n\r\n startCustomize(): void {\r\n this.isCustomizing.set(true);\r\n }\r\n\r\n stopCustomize(): void {\r\n this.isCustomizing.set(false);\r\n }\r\n\r\n openEditPage(): void {\r\n if (!this.selectedPage()) {\r\n return;\r\n }\r\n\r\n this.modalService.openModal(HomePagePageDialog, 'dialog', {\r\n header: 'Edit Page',\r\n styleClass: '!w-[32rem]',\r\n modal: true,\r\n closable: true,\r\n });\r\n }\r\n\r\n openAddWidget(): void {\r\n if (!this.selectedPage()) {\r\n return;\r\n }\r\n\r\n this.homePageService.loadAvailableWidgets();\r\n this.modalService.openModal(HomePageWidgetDialog, 'dialog', {\r\n header: 'Add Widget',\r\n styleClass: '!w-[58rem]',\r\n modal: true,\r\n closable: true,\r\n });\r\n }\r\n\r\n removeWidget(widgetId: string): void {\r\n this.homePageService.removeWidget(widgetId);\r\n }\r\n\r\n reload(): void {\r\n this.homePageService.reload();\r\n }\r\n\r\n getPreviewColumn(widget: HomePageWidgetInstance): string {\r\n return `${widget.x + 1} / span ${widget.cols}`;\r\n }\r\n\r\n getPreviewRow(widget: HomePageWidgetInstance): string {\r\n return `${widget.y + 1} / span ${widget.rows}`;\r\n }\r\n\r\n private onWidgetLayoutChange(item: GridsterItem): void {\r\n const widget = item as HomePageWidgetInstance;\r\n this.homePageService.updateWidgetLayout(widget.widgetId, {\r\n x: widget.x ?? 0,\r\n y: widget.y ?? 0,\r\n cols: widget.cols ?? 1,\r\n rows: widget.rows ?? 1,\r\n });\r\n }\r\n}\r\n","<section class=\"h-full w-full p-4 md:p-5\">\r\n <mt-card class=\"block h-full w-full\">\r\n <ng-template #headless>\r\n <div class=\"flex h-full flex-col\">\r\n <div\r\n class=\"grid gap-3 p-4 md:grid-cols-[minmax(0,1fr)_auto] md:items-center md:px-5 md:py-4\"\r\n >\r\n <div class=\"min-w-0 space-y-1\">\r\n <h2 class=\"text-base font-semibold text-surface-900 md:text-lg\">\r\n Welcome,\r\n <span class=\"text-primary\">{{ welcomeName() }}</span>\r\n </h2>\r\n <p class=\"max-w-2xl text-sm text-surface-500\">\r\n {{ subtitle() }}\r\n </p>\r\n </div>\r\n\r\n @if (isCustomizing()) {\r\n <div class=\"flex flex-wrap justify-start gap-2 md:justify-end\">\r\n <mt-button\r\n icon=\"general.edit-03\"\r\n label=\"Edit Page\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"isLoading() || isSaving() || !selectedPage()\"\r\n (onClick)=\"openEditPage()\"\r\n />\r\n <mt-button\r\n icon=\"layout.layout-grid-01\"\r\n label=\"Add Widget\"\r\n size=\"small\"\r\n severity=\"primary\"\r\n [disabled]=\"isLoading() || isSaving() || !selectedPage()\"\r\n (onClick)=\"openAddWidget()\"\r\n />\r\n <mt-button\r\n icon=\"general.x-close\"\r\n label=\"Stop Customize\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"isSaving()\"\r\n (onClick)=\"stopCustomize()\"\r\n />\r\n </div>\r\n } @else {\r\n <div class=\"flex justify-start md:justify-end\">\r\n <mt-button\r\n icon=\"layout.layout-grid-01\"\r\n label=\"Customize\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n [disabled]=\"isLoading() || !selectedPage()\"\r\n (onClick)=\"startCustomize()\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"flex-1 border-t border-surface px-4 pb-4 pt-4 md:px-5 md:pb-5\"\r\n >\r\n @if (errorMessage() && selectedPage()) {\r\n <div\r\n class=\"mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600\"\r\n >\r\n {{ errorMessage() }}\r\n </div>\r\n }\r\n\r\n @if (isSaving()) {\r\n <div class=\"mb-4 text-sm text-surface-500\">\r\n Saving home page changes...\r\n </div>\r\n }\r\n\r\n @if (isLoading() && !selectedPage()) {\r\n <div\r\n class=\"flex min-h-[28rem] items-center justify-center text-sm text-surface-500\"\r\n >\r\n Loading home page...\r\n </div>\r\n } @else if (errorMessage() && !selectedPage()) {\r\n <div\r\n class=\"flex min-h-[28rem] flex-col items-center justify-center gap-3 text-center\"\r\n >\r\n <p class=\"text-sm text-red-500\">{{ errorMessage() }}</p>\r\n <mt-button\r\n label=\"Retry\"\r\n severity=\"secondary\"\r\n [outlined]=\"true\"\r\n (onClick)=\"reload()\"\r\n />\r\n </div>\r\n } @else if (hasWidgets()) {\r\n @if (isCustomizing()) {\r\n <gridster\r\n [options]=\"gridOptions\"\r\n class=\"block w-full\"\r\n [style.min-height]=\"previewLayoutHeight()\"\r\n >\r\n @for (widget of widgets(); track widget.key) {\r\n <gridster-item [item]=\"widget\">\r\n <mt-card class=\"block h-full w-full\">\r\n <ng-template #headless>\r\n <div class=\"grid h-full gap-4 p-4\">\r\n <div class=\"flex items-start justify-between gap-3\">\r\n <div class=\"flex items-start gap-3\">\r\n <span\r\n class=\"flex h-10 w-10 items-center justify-center rounded-2xl text-white\"\r\n [style.background]=\"\r\n widget.props.color || 'var(--p-primary-color)'\r\n \"\r\n >\r\n <mt-icon\r\n [icon]=\"\r\n widget.props.icon || 'chart.bar-chart-01'\r\n \"\r\n class=\"h-5 w-5\"\r\n />\r\n </span>\r\n\r\n <div class=\"min-w-0 space-y-1\">\r\n <h3\r\n class=\"truncate text-base font-semibold text-surface-900\"\r\n >\r\n {{ widget.props.name }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ widget.props.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n severity=\"danger\"\r\n [text]=\"true\"\r\n (onClick)=\"removeWidget(widget.widgetId)\"\r\n />\r\n </div>\r\n\r\n <div class=\"grid gap-1 text-sm text-surface-500\">\r\n <span>Type: {{ widget.type }}</span>\r\n <span>\r\n Report:\r\n {{\r\n widget.props.dashboardId ||\r\n widget.props.reportId ||\r\n \"-\"\r\n }}\r\n </span>\r\n <span>\r\n Component:\r\n {{ widget.props.chartComponentId || widget.key }}\r\n </span>\r\n </div>\r\n\r\n <div\r\n class=\"mt-auto flex flex-wrap gap-4 text-sm text-surface-500\"\r\n >\r\n <span\r\n >Position: {{ widget.x }}, {{ widget.y }}</span\r\n >\r\n <span\r\n >Size: {{ widget.cols }} x {{ widget.rows }}</span\r\n >\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n </gridster-item>\r\n }\r\n </gridster>\r\n } @else {\r\n <div\r\n class=\"grid\"\r\n [style.min-height]=\"previewLayoutHeight()\"\r\n [style.grid-template-columns]=\"\r\n 'repeat(' + gridColumns + ', minmax(0, 1fr))'\r\n \"\r\n [style.grid-auto-rows]=\"gridRowHeight + 'px'\"\r\n [style.gap.px]=\"gridGap\"\r\n >\r\n @for (widget of widgets(); track widget.key) {\r\n <div\r\n class=\"h-full\"\r\n [style.grid-column]=\"getPreviewColumn(widget)\"\r\n [style.grid-row]=\"getPreviewRow(widget)\"\r\n >\r\n <mt-card class=\"block h-full w-full\">\r\n <ng-template #headless>\r\n <div class=\"grid h-full gap-4 p-4\">\r\n <div class=\"flex items-start gap-3\">\r\n <span\r\n class=\"flex h-10 w-10 items-center justify-center rounded-2xl text-white\"\r\n [style.background]=\"\r\n widget.props.color || 'var(--p-primary-color)'\r\n \"\r\n >\r\n <mt-icon\r\n [icon]=\"\r\n widget.props.icon || 'chart.bar-chart-01'\r\n \"\r\n class=\"h-5 w-5\"\r\n />\r\n </span>\r\n\r\n <div class=\"min-w-0 space-y-1\">\r\n <h3\r\n class=\"truncate text-base font-semibold text-surface-900\"\r\n >\r\n {{ widget.props.name }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ widget.props.description }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"grid gap-1 text-sm text-surface-500\">\r\n <span>Type: {{ widget.type }}</span>\r\n <span>\r\n Report:\r\n {{\r\n widget.props.dashboardId ||\r\n widget.props.reportId ||\r\n \"-\"\r\n }}\r\n </span>\r\n <span>\r\n Component:\r\n {{ widget.props.chartComponentId || widget.key }}\r\n </span>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n </div>\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div\r\n class=\"flex min-h-[28rem] items-center justify-center text-sm text-surface-500\"\r\n >\r\n @if (isCustomizing()) {\r\n Use Add Widget to place charts in the grid builder.\r\n } @else {\r\n No widgets on this page.\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n</section>\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@masterteam/home-page",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"directory": "../../../dist/masterteam/home-page",
|
|
6
|
+
"linkDirectory": false,
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"peerDependencies": {
|
|
10
|
+
"@angular/common": "^21.0.3",
|
|
11
|
+
"@angular/core": "^21.0.3",
|
|
12
|
+
"@angular/forms": "^21.0.3",
|
|
13
|
+
"@primeuix/themes": "^2.0.2",
|
|
14
|
+
"@tailwindcss/postcss": "^4.1.17",
|
|
15
|
+
"angular-gridster2": "^18.0.1",
|
|
16
|
+
"postcss": "^8.5.6",
|
|
17
|
+
"primeng": "21.0.1",
|
|
18
|
+
"rxjs": "^7.8.2",
|
|
19
|
+
"tailwindcss": "^4.1.17",
|
|
20
|
+
"tailwindcss-primeui": "^0.6.1",
|
|
21
|
+
"@masterteam/components": "^0.0.116",
|
|
22
|
+
"@masterteam/forms": "^0.0.56",
|
|
23
|
+
"@masterteam/icons": "^0.0.14"
|
|
24
|
+
},
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"exports": {
|
|
27
|
+
"./assets/home-page.css": {
|
|
28
|
+
"style": "./assets/home-page.css"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": {
|
|
31
|
+
"default": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./types/masterteam-home-page.d.ts",
|
|
35
|
+
"default": "./fesm2022/masterteam-home-page.mjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"module": "fesm2022/masterteam-home-page.mjs",
|
|
39
|
+
"typings": "types/masterteam-home-page.d.ts",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"tslib": "^2.8.1"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import * as _masterteam_home_page from '@masterteam/home-page';
|
|
2
|
+
import * as _angular_core from '@angular/core';
|
|
3
|
+
import { GridsterConfig } from 'angular-gridster2';
|
|
4
|
+
import { MTIcon } from '@masterteam/icons';
|
|
5
|
+
import { FormControl } from '@angular/forms';
|
|
6
|
+
import { DynamicFormConfig } from '@masterteam/components';
|
|
7
|
+
import { ModalService } from '@masterteam/components/modal';
|
|
8
|
+
|
|
9
|
+
interface AppResponseViewModel<T> {
|
|
10
|
+
endpoint: string | null;
|
|
11
|
+
status: number;
|
|
12
|
+
code: 1 | 2;
|
|
13
|
+
locale: string | null;
|
|
14
|
+
message: string | null;
|
|
15
|
+
errors: {
|
|
16
|
+
message?: string | null;
|
|
17
|
+
} | null;
|
|
18
|
+
data: T;
|
|
19
|
+
cacheSession: string | null;
|
|
20
|
+
}
|
|
21
|
+
interface HomePageName {
|
|
22
|
+
display: string;
|
|
23
|
+
en: string;
|
|
24
|
+
ar: string;
|
|
25
|
+
}
|
|
26
|
+
interface HomePageCardLayout {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
w: number;
|
|
30
|
+
h: number;
|
|
31
|
+
}
|
|
32
|
+
interface HomePageCardDto {
|
|
33
|
+
key: string;
|
|
34
|
+
type: string;
|
|
35
|
+
layout?: HomePageCardLayout;
|
|
36
|
+
config?: Record<string, unknown>;
|
|
37
|
+
props?: Record<string, unknown>;
|
|
38
|
+
permissions?: Record<string, unknown>;
|
|
39
|
+
style?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
interface HomePageSchemaDto {
|
|
42
|
+
version: number;
|
|
43
|
+
cards: HomePageCardDto[];
|
|
44
|
+
}
|
|
45
|
+
interface HomePageSummaryDto {
|
|
46
|
+
id: number;
|
|
47
|
+
key: string;
|
|
48
|
+
name: HomePageName;
|
|
49
|
+
order: number;
|
|
50
|
+
isActive: boolean;
|
|
51
|
+
isDefault: boolean;
|
|
52
|
+
}
|
|
53
|
+
interface HomePageDefinitionDto extends HomePageSummaryDto {
|
|
54
|
+
schema: HomePageSchemaDto;
|
|
55
|
+
}
|
|
56
|
+
interface HomePageRuntimeDto {
|
|
57
|
+
pages: HomePageSummaryDto[];
|
|
58
|
+
selectedPage: HomePageDefinitionDto;
|
|
59
|
+
}
|
|
60
|
+
interface HomePageUpdateRequest {
|
|
61
|
+
key?: string;
|
|
62
|
+
name?: Omit<HomePageName, 'display'>;
|
|
63
|
+
order?: number;
|
|
64
|
+
isActive?: boolean;
|
|
65
|
+
schema?: HomePageSchemaDto;
|
|
66
|
+
}
|
|
67
|
+
type HomePageWidgetCategory = 'chart';
|
|
68
|
+
interface HomePageWidgetCatalogItem {
|
|
69
|
+
id: string;
|
|
70
|
+
name: string;
|
|
71
|
+
description: string;
|
|
72
|
+
icon: MTIcon;
|
|
73
|
+
color: string | null;
|
|
74
|
+
category: HomePageWidgetCategory;
|
|
75
|
+
layout: Pick<HomePageCardLayout, 'w' | 'h'>;
|
|
76
|
+
reportId: number | string;
|
|
77
|
+
chartComponentId: string;
|
|
78
|
+
}
|
|
79
|
+
interface HomePageWidgetProps {
|
|
80
|
+
name?: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
icon?: MTIcon | null;
|
|
83
|
+
color?: string | null;
|
|
84
|
+
category?: string | null;
|
|
85
|
+
widgetId?: string | null;
|
|
86
|
+
reportId?: number | string | null;
|
|
87
|
+
dashboardId?: number | string | null;
|
|
88
|
+
chartComponentId?: string | null;
|
|
89
|
+
[key: string]: unknown;
|
|
90
|
+
}
|
|
91
|
+
interface HomePageWidgetInstance {
|
|
92
|
+
widgetId: string;
|
|
93
|
+
key: string;
|
|
94
|
+
type: string;
|
|
95
|
+
x: number;
|
|
96
|
+
y: number;
|
|
97
|
+
cols: number;
|
|
98
|
+
rows: number;
|
|
99
|
+
config?: Record<string, unknown>;
|
|
100
|
+
props: HomePageWidgetProps;
|
|
101
|
+
permissions?: Record<string, unknown>;
|
|
102
|
+
style?: Record<string, unknown>;
|
|
103
|
+
}
|
|
104
|
+
interface HomePagePageDraft {
|
|
105
|
+
name: string;
|
|
106
|
+
key?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
declare class HomePageService {
|
|
110
|
+
private readonly api;
|
|
111
|
+
private readonly page;
|
|
112
|
+
readonly availableWidgets: _angular_core.WritableSignal<HomePageWidgetCatalogItem[]>;
|
|
113
|
+
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
114
|
+
readonly saving: _angular_core.WritableSignal<boolean>;
|
|
115
|
+
readonly loadingAvailableWidgets: _angular_core.WritableSignal<boolean>;
|
|
116
|
+
readonly error: _angular_core.WritableSignal<string | null>;
|
|
117
|
+
readonly widgetCatalogError: _angular_core.WritableSignal<string | null>;
|
|
118
|
+
readonly selectedPage: _angular_core.Signal<HomePageDefinitionDto | null>;
|
|
119
|
+
readonly selectedWidgets: _angular_core.Signal<HomePageWidgetInstance[]>;
|
|
120
|
+
constructor();
|
|
121
|
+
load(): void;
|
|
122
|
+
reload(): void;
|
|
123
|
+
loadAvailableWidgets(): void;
|
|
124
|
+
updatePage(pageId: number, draft: HomePagePageDraft): void;
|
|
125
|
+
isWidgetEnabled(widgetId: string): boolean;
|
|
126
|
+
toggleWidget(widgetId: string, enabled: boolean): void;
|
|
127
|
+
addWidget(widgetId: string): void;
|
|
128
|
+
removeWidget(widgetId: string): void;
|
|
129
|
+
updateWidgetLayout(widgetId: string, layout: Pick<HomePageWidgetInstance, 'x' | 'y' | 'cols' | 'rows'>): void;
|
|
130
|
+
private savePage;
|
|
131
|
+
private toWidgets;
|
|
132
|
+
private getWidgetId;
|
|
133
|
+
private findNextLayout;
|
|
134
|
+
private uniqueKey;
|
|
135
|
+
private getMessage;
|
|
136
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<HomePageService, never>;
|
|
137
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<HomePageService>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
declare class HomePage {
|
|
141
|
+
readonly welcomeName: _angular_core.InputSignal<string>;
|
|
142
|
+
readonly subtitle: _angular_core.InputSignal<string>;
|
|
143
|
+
readonly isCustomizing: _angular_core.WritableSignal<boolean>;
|
|
144
|
+
private readonly modalService;
|
|
145
|
+
protected readonly homePageService: HomePageService;
|
|
146
|
+
readonly gridColumns = 12;
|
|
147
|
+
readonly gridGap = 10;
|
|
148
|
+
readonly gridRowHeight = 60;
|
|
149
|
+
readonly isLoading: _angular_core.WritableSignal<boolean>;
|
|
150
|
+
readonly isSaving: _angular_core.WritableSignal<boolean>;
|
|
151
|
+
readonly errorMessage: _angular_core.WritableSignal<string | null>;
|
|
152
|
+
readonly selectedPage: _angular_core.Signal<_masterteam_home_page.HomePageDefinitionDto | null>;
|
|
153
|
+
readonly widgets: _angular_core.Signal<HomePageWidgetInstance[]>;
|
|
154
|
+
readonly hasWidgets: _angular_core.Signal<boolean>;
|
|
155
|
+
readonly previewLayoutHeight: _angular_core.Signal<string>;
|
|
156
|
+
readonly gridOptions: GridsterConfig;
|
|
157
|
+
startCustomize(): void;
|
|
158
|
+
stopCustomize(): void;
|
|
159
|
+
openEditPage(): void;
|
|
160
|
+
openAddWidget(): void;
|
|
161
|
+
removeWidget(widgetId: string): void;
|
|
162
|
+
reload(): void;
|
|
163
|
+
getPreviewColumn(widget: HomePageWidgetInstance): string;
|
|
164
|
+
getPreviewRow(widget: HomePageWidgetInstance): string;
|
|
165
|
+
private onWidgetLayoutChange;
|
|
166
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<HomePage, never>;
|
|
167
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<HomePage, "mt-home-page", never, { "welcomeName": { "alias": "welcomeName"; "required": true; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
declare class HomePagePageDialog {
|
|
171
|
+
protected readonly modalService: ModalService;
|
|
172
|
+
private readonly ref;
|
|
173
|
+
protected readonly homePageService: HomePageService;
|
|
174
|
+
readonly formControl: FormControl<Record<string, unknown>>;
|
|
175
|
+
readonly formConfig: DynamicFormConfig;
|
|
176
|
+
constructor();
|
|
177
|
+
save(): void;
|
|
178
|
+
close(): void;
|
|
179
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<HomePagePageDialog, never>;
|
|
180
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<HomePagePageDialog, "mt-home-page-page-dialog", never, {}, {}, never, never, true, never>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
declare class HomePageWidgetDialog {
|
|
184
|
+
protected readonly modalService: ModalService;
|
|
185
|
+
private readonly ref;
|
|
186
|
+
protected readonly homePageService: HomePageService;
|
|
187
|
+
readonly pageLabel: () => string;
|
|
188
|
+
constructor();
|
|
189
|
+
readonly availableWidgets: _angular_core.WritableSignal<HomePageWidgetCatalogItem[]>;
|
|
190
|
+
readonly isLoading: _angular_core.WritableSignal<boolean>;
|
|
191
|
+
readonly errorMessage: _angular_core.WritableSignal<string | null>;
|
|
192
|
+
isEnabled(widgetId: string): boolean;
|
|
193
|
+
onToggle(widget: HomePageWidgetCatalogItem, value: boolean | null): void;
|
|
194
|
+
close(): void;
|
|
195
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<HomePageWidgetDialog, never>;
|
|
196
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<HomePageWidgetDialog, "mt-home-page-widget-dialog", never, {}, {}, never, never, true, never>;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { HomePage, HomePagePageDialog, HomePageService, HomePageWidgetDialog };
|
|
200
|
+
export type { AppResponseViewModel, HomePageCardDto, HomePageCardLayout, HomePageDefinitionDto, HomePageName, HomePagePageDraft, HomePageRuntimeDto, HomePageSchemaDto, HomePageSummaryDto, HomePageUpdateRequest, HomePageWidgetCatalogItem, HomePageWidgetCategory, HomePageWidgetInstance, HomePageWidgetProps };
|