@memberjunction/ng-scheduling 0.0.1 → 5.13.0

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.
Files changed (30) hide show
  1. package/dist/lib/dialogs/scheduled-job-dialog.component.d.ts +25 -0
  2. package/dist/lib/dialogs/scheduled-job-dialog.component.d.ts.map +1 -0
  3. package/dist/lib/dialogs/scheduled-job-dialog.component.js +79 -0
  4. package/dist/lib/dialogs/scheduled-job-dialog.component.js.map +1 -0
  5. package/dist/lib/panels/scheduled-job-editor/scheduled-job-editor.component.d.ts +56 -0
  6. package/dist/lib/panels/scheduled-job-editor/scheduled-job-editor.component.d.ts.map +1 -0
  7. package/dist/lib/panels/scheduled-job-editor/scheduled-job-editor.component.js +249 -0
  8. package/dist/lib/panels/scheduled-job-editor/scheduled-job-editor.component.js.map +1 -0
  9. package/dist/lib/panels/scheduled-job-summary/scheduled-job-summary.component.d.ts +27 -0
  10. package/dist/lib/panels/scheduled-job-summary/scheduled-job-summary.component.d.ts.map +1 -0
  11. package/dist/lib/panels/scheduled-job-summary/scheduled-job-summary.component.js +83 -0
  12. package/dist/lib/panels/scheduled-job-summary/scheduled-job-summary.component.js.map +1 -0
  13. package/dist/lib/scheduling.module.d.ts +16 -0
  14. package/dist/lib/scheduling.module.d.ts.map +1 -0
  15. package/dist/lib/scheduling.module.js +55 -0
  16. package/dist/lib/scheduling.module.js.map +1 -0
  17. package/dist/lib/services/scheduled-job.service.d.ts +32 -0
  18. package/dist/lib/services/scheduled-job.service.d.ts.map +1 -0
  19. package/dist/lib/services/scheduled-job.service.js +81 -0
  20. package/dist/lib/services/scheduled-job.service.js.map +1 -0
  21. package/dist/lib/slide-panel/scheduled-job-slide-panel.component.d.ts +21 -0
  22. package/dist/lib/slide-panel/scheduled-job-slide-panel.component.d.ts.map +1 -0
  23. package/dist/lib/slide-panel/scheduled-job-slide-panel.component.js +56 -0
  24. package/dist/lib/slide-panel/scheduled-job-slide-panel.component.js.map +1 -0
  25. package/dist/public-api.d.ts +17 -0
  26. package/dist/public-api.d.ts.map +1 -0
  27. package/dist/public-api.js +22 -0
  28. package/dist/public-api.js.map +1 -0
  29. package/package.json +45 -6
  30. package/README.md +0 -45
@@ -0,0 +1,25 @@
1
+ import { EventEmitter } from '@angular/core';
2
+ import { MJScheduledJobEntity } from '@memberjunction/core-entities';
3
+ import * as i0 from "@angular/core";
4
+ /** Result emitted when the dialog closes */
5
+ export interface ScheduledJobDialogResult {
6
+ Saved: boolean;
7
+ Job?: MJScheduledJobEntity;
8
+ Deleted?: boolean;
9
+ }
10
+ export declare class ScheduledJobDialogComponent {
11
+ Visible: boolean;
12
+ ScheduledJobID: string | null;
13
+ JobTypeID: string | null;
14
+ DefaultConfiguration: string | null;
15
+ HideJobType: boolean;
16
+ Width: number;
17
+ Close: EventEmitter<ScheduledJobDialogResult>;
18
+ get DialogTitle(): string;
19
+ OnClose(): void;
20
+ OnSaved(job: MJScheduledJobEntity): void;
21
+ OnDeleted(_jobID: string): void;
22
+ static ɵfac: i0.ɵɵFactoryDeclaration<ScheduledJobDialogComponent, never>;
23
+ static ɵcmp: i0.ɵɵComponentDeclaration<ScheduledJobDialogComponent, "mj-scheduled-job-dialog", never, { "Visible": { "alias": "Visible"; "required": false; }; "ScheduledJobID": { "alias": "ScheduledJobID"; "required": false; }; "JobTypeID": { "alias": "JobTypeID"; "required": false; }; "DefaultConfiguration": { "alias": "DefaultConfiguration"; "required": false; }; "HideJobType": { "alias": "HideJobType"; "required": false; }; "Width": { "alias": "Width"; "required": false; }; }, { "Close": "Close"; }, never, never, false, never>;
24
+ }
25
+ //# sourceMappingURL=scheduled-job-dialog.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-job-dialog.component.d.ts","sourceRoot":"","sources":["../../../src/lib/dialogs/scheduled-job-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,YAAY,EAEf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAErE,4CAA4C;AAC5C,MAAM,WAAW,wBAAwB;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,oBAAoB,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,qBAoBa,2BAA2B;IAC3B,OAAO,UAAS;IAChB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IACrC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C,WAAW,UAAS;IACpB,KAAK,SAAO;IAEX,KAAK,yCAAgD;IAE/D,IAAW,WAAW,IAAI,MAAM,CAE/B;IAEM,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,GAAG,EAAE,oBAAoB,GAAG,IAAI;IAIxC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;yCAtB7B,2BAA2B;2CAA3B,2BAA2B;CAyBvC"}
@@ -0,0 +1,79 @@
1
+ import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ import * as i1 from "@progress/kendo-angular-dialog";
4
+ import * as i2 from "../panels/scheduled-job-editor/scheduled-job-editor.component";
5
+ export class ScheduledJobDialogComponent {
6
+ Visible = false;
7
+ ScheduledJobID = null;
8
+ JobTypeID = null;
9
+ DefaultConfiguration = null;
10
+ HideJobType = false;
11
+ Width = 580;
12
+ Close = new EventEmitter();
13
+ get DialogTitle() {
14
+ return this.ScheduledJobID ? 'Edit Schedule' : 'Create Schedule';
15
+ }
16
+ OnClose() {
17
+ this.Close.emit({ Saved: false });
18
+ }
19
+ OnSaved(job) {
20
+ this.Close.emit({ Saved: true, Job: job });
21
+ }
22
+ OnDeleted(_jobID) {
23
+ this.Close.emit({ Saved: false, Deleted: true });
24
+ }
25
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
26
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ScheduledJobDialogComponent, isStandalone: false, selector: "mj-scheduled-job-dialog", inputs: { Visible: "Visible", ScheduledJobID: "ScheduledJobID", JobTypeID: "JobTypeID", DefaultConfiguration: "DefaultConfiguration", HideJobType: "HideJobType", Width: "Width" }, outputs: { Close: "Close" }, ngImport: i0, template: `
27
+ @if (Visible) {
28
+ <kendo-dialog [title]="DialogTitle" (close)="OnClose()" [width]="Width">
29
+ <mj-scheduled-job-editor
30
+ [ScheduledJobID]="ScheduledJobID"
31
+ [JobTypeID]="JobTypeID"
32
+ [DefaultConfiguration]="DefaultConfiguration"
33
+ [HideJobType]="HideJobType"
34
+ (Saved)="OnSaved($event)"
35
+ (Deleted)="OnDeleted($event)"
36
+ (Cancelled)="OnClose()">
37
+ </mj-scheduled-job-editor>
38
+ </kendo-dialog>
39
+ }
40
+ `, isInline: true, dependencies: [{ kind: "component", type: i1.DialogComponent, selector: "kendo-dialog", inputs: ["actions", "actionsLayout", "autoFocusedElement", "title", "width", "minWidth", "maxWidth", "height", "minHeight", "maxHeight", "animation", "themeColor"], outputs: ["action", "close"], exportAs: ["kendoDialog"] }, { kind: "component", type: i2.ScheduledJobEditorComponent, selector: "mj-scheduled-job-editor", inputs: ["ScheduledJobID", "JobTypeID", "DefaultConfiguration", "HideJobType"], outputs: ["Saved", "Deleted", "Cancelled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
41
+ }
42
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobDialogComponent, decorators: [{
43
+ type: Component,
44
+ args: [{
45
+ selector: 'mj-scheduled-job-dialog',
46
+ standalone: false,
47
+ template: `
48
+ @if (Visible) {
49
+ <kendo-dialog [title]="DialogTitle" (close)="OnClose()" [width]="Width">
50
+ <mj-scheduled-job-editor
51
+ [ScheduledJobID]="ScheduledJobID"
52
+ [JobTypeID]="JobTypeID"
53
+ [DefaultConfiguration]="DefaultConfiguration"
54
+ [HideJobType]="HideJobType"
55
+ (Saved)="OnSaved($event)"
56
+ (Deleted)="OnDeleted($event)"
57
+ (Cancelled)="OnClose()">
58
+ </mj-scheduled-job-editor>
59
+ </kendo-dialog>
60
+ }
61
+ `,
62
+ changeDetection: ChangeDetectionStrategy.OnPush,
63
+ }]
64
+ }], propDecorators: { Visible: [{
65
+ type: Input
66
+ }], ScheduledJobID: [{
67
+ type: Input
68
+ }], JobTypeID: [{
69
+ type: Input
70
+ }], DefaultConfiguration: [{
71
+ type: Input
72
+ }], HideJobType: [{
73
+ type: Input
74
+ }], Width: [{
75
+ type: Input
76
+ }], Close: [{
77
+ type: Output
78
+ }] } });
79
+ //# sourceMappingURL=scheduled-job-dialog.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-job-dialog.component.js","sourceRoot":"","sources":["../../../src/lib/dialogs/scheduled-job-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,YAAY,EACZ,uBAAuB,GAC1B,MAAM,eAAe,CAAC;;;;AA8BvB,MAAM,OAAO,2BAA2B;IAC3B,OAAO,GAAG,KAAK,CAAC;IAChB,cAAc,GAAkB,IAAI,CAAC;IACrC,SAAS,GAAkB,IAAI,CAAC;IAChC,oBAAoB,GAAkB,IAAI,CAAC;IAC3C,WAAW,GAAG,KAAK,CAAC;IACpB,KAAK,GAAG,GAAG,CAAC;IAEX,KAAK,GAAG,IAAI,YAAY,EAA4B,CAAC;IAE/D,IAAW,WAAW;QAClB,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACrE,CAAC;IAEM,OAAO;QACV,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACtC,CAAC;IAEM,OAAO,CAAC,GAAyB;QACpC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/C,CAAC;IAEM,SAAS,CAAC,MAAc;QAC3B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;uGAxBQ,2BAA2B;2FAA3B,2BAA2B,qSAjB1B;;;;;;;;;;;;;;KAcT;;2FAGQ,2BAA2B;kBApBvC,SAAS;mBAAC;oBACP,QAAQ,EAAE,yBAAyB;oBACnC,UAAU,EAAE,KAAK;oBACjB,QAAQ,EAAE;;;;;;;;;;;;;;KAcT;oBACD,eAAe,EAAE,uBAAuB,CAAC,MAAM;iBAClD;;sBAEI,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBAEL,MAAM","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { MJScheduledJobEntity } from '@memberjunction/core-entities';\n\n/** Result emitted when the dialog closes */\nexport interface ScheduledJobDialogResult {\n Saved: boolean;\n Job?: MJScheduledJobEntity;\n Deleted?: boolean;\n}\n\n@Component({\n selector: 'mj-scheduled-job-dialog',\n standalone: false,\n template: `\n @if (Visible) {\n <kendo-dialog [title]=\"DialogTitle\" (close)=\"OnClose()\" [width]=\"Width\">\n <mj-scheduled-job-editor\n [ScheduledJobID]=\"ScheduledJobID\"\n [JobTypeID]=\"JobTypeID\"\n [DefaultConfiguration]=\"DefaultConfiguration\"\n [HideJobType]=\"HideJobType\"\n (Saved)=\"OnSaved($event)\"\n (Deleted)=\"OnDeleted($event)\"\n (Cancelled)=\"OnClose()\">\n </mj-scheduled-job-editor>\n </kendo-dialog>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobDialogComponent {\n @Input() Visible = false;\n @Input() ScheduledJobID: string | null = null;\n @Input() JobTypeID: string | null = null;\n @Input() DefaultConfiguration: string | null = null;\n @Input() HideJobType = false;\n @Input() Width = 580;\n\n @Output() Close = new EventEmitter<ScheduledJobDialogResult>();\n\n public get DialogTitle(): string {\n return this.ScheduledJobID ? 'Edit Schedule' : 'Create Schedule';\n }\n\n public OnClose(): void {\n this.Close.emit({ Saved: false });\n }\n\n public OnSaved(job: MJScheduledJobEntity): void {\n this.Close.emit({ Saved: true, Job: job });\n }\n\n public OnDeleted(_jobID: string): void {\n this.Close.emit({ Saved: false, Deleted: true });\n }\n}\n"]}
@@ -0,0 +1,56 @@
1
+ import { EventEmitter, OnInit } from '@angular/core';
2
+ import { MJScheduledJobEntity, MJScheduledJobTypeEntity } from '@memberjunction/core-entities';
3
+ import * as i0 from "@angular/core";
4
+ export declare class ScheduledJobEditorComponent implements OnInit {
5
+ private cdr;
6
+ private scheduledJobService;
7
+ private _scheduledJobID;
8
+ set ScheduledJobID(value: string | null);
9
+ get ScheduledJobID(): string | null;
10
+ JobTypeID: string | null;
11
+ DefaultConfiguration: string | null;
12
+ HideJobType: boolean;
13
+ Saved: EventEmitter<MJScheduledJobEntity>;
14
+ Deleted: EventEmitter<string>;
15
+ Cancelled: EventEmitter<void>;
16
+ IsLoading: boolean;
17
+ IsSaving: boolean;
18
+ IsNew: boolean;
19
+ ShowDeleteConfirm: boolean;
20
+ Job: MJScheduledJobEntity | null;
21
+ JobTypes: MJScheduledJobTypeEntity[];
22
+ Timezones: string[];
23
+ Name: string;
24
+ Description: string;
25
+ SelectedJobTypeID: string;
26
+ CronExpression: string;
27
+ Timezone: string;
28
+ Status: 'Active' | 'Disabled' | 'Expired' | 'Paused' | 'Pending';
29
+ ConcurrencyMode: string;
30
+ Configuration: string;
31
+ NotifyOnSuccess: boolean;
32
+ NotifyOnFailure: boolean;
33
+ TotalRuns: number;
34
+ SuccessRuns: number;
35
+ FailedRuns: number;
36
+ readonly StatusOptions: string[];
37
+ readonly ConcurrencyOptions: string[];
38
+ private isInitialized;
39
+ get CanSave(): boolean;
40
+ ngOnInit(): Promise<void>;
41
+ Save(): Promise<void>;
42
+ ConfirmDelete(): void;
43
+ CancelDelete(): void;
44
+ DeleteJob(): Promise<void>;
45
+ Cancel(): void;
46
+ private loadOrCreateJob;
47
+ private loadExistingJob;
48
+ private initNewJob;
49
+ private populateFormFromEntity;
50
+ private applyFormToEntity;
51
+ private getOrCreateEntity;
52
+ private loadRunStats;
53
+ static ɵfac: i0.ɵɵFactoryDeclaration<ScheduledJobEditorComponent, never>;
54
+ static ɵcmp: i0.ɵɵComponentDeclaration<ScheduledJobEditorComponent, "mj-scheduled-job-editor", never, { "ScheduledJobID": { "alias": "ScheduledJobID"; "required": false; }; "JobTypeID": { "alias": "JobTypeID"; "required": false; }; "DefaultConfiguration": { "alias": "DefaultConfiguration"; "required": false; }; "HideJobType": { "alias": "HideJobType"; "required": false; }; }, { "Saved": "Saved"; "Deleted": "Deleted"; "Cancelled": "Cancelled"; }, never, never, false, never>;
55
+ }
56
+ //# sourceMappingURL=scheduled-job-editor.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-job-editor.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,YAAY,EAGZ,MAAM,EAET,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;;AAuB/F,qBAOa,2BAA4B,YAAW,MAAM;IACtD,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,mBAAmB,CAA+B;IAG1D,OAAO,CAAC,eAAe,CAAuB;IAE9C,IACI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAOtC;IACD,IAAI,cAAc,IAAI,MAAM,GAAG,IAAI,CAElC;IAEQ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C,WAAW,UAAS;IAGnB,KAAK,qCAA4C;IACjD,OAAO,uBAA8B;IACrC,SAAS,qBAA4B;IAGxC,SAAS,UAAS;IAClB,QAAQ,UAAS;IACjB,KAAK,UAAS;IACd,iBAAiB,UAAS;IAE1B,GAAG,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IACxC,QAAQ,EAAE,wBAAwB,EAAE,CAAM;IAC1C,SAAS,EAAE,MAAM,EAAE,CAAoB;IAGvC,IAAI,SAAM;IACV,WAAW,SAAM;IACjB,iBAAiB,SAAM;IACvB,cAAc,SAAM;IACpB,QAAQ,SAAS;IACjB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAY;IAC5E,eAAe,SAAU;IACzB,aAAa,SAAM;IACnB,eAAe,UAAS;IACxB,eAAe,UAAQ;IAGvB,SAAS,SAAK;IACd,WAAW,SAAK;IAChB,UAAU,SAAK;IAEtB,SAAgB,aAAa,WAA+C;IAC5E,SAAgB,kBAAkB,WAAmC;IAErE,OAAO,CAAC,aAAa,CAAS;IAE9B,IAAW,OAAO,IAAI,OAAO,CAI5B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAYlB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC3B,aAAa,IAAI,IAAI;IAKrB,YAAY,IAAI,IAAI;IAKd,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBhC,MAAM,IAAI,IAAI;YAMP,eAAe;YAQf,eAAe;IAW7B,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,iBAAiB;YAaX,iBAAiB;YAUjB,YAAY;yCAlOjB,2BAA2B;2CAA3B,2BAA2B;CAwOvC"}
@@ -0,0 +1,249 @@
1
+ import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef, inject, } from '@angular/core';
2
+ import { Metadata } from '@memberjunction/core';
3
+ import { MJNotificationService } from '@memberjunction/ng-notifications';
4
+ import { ScheduledJobService } from '../../services/scheduled-job.service';
5
+ import * as i0 from "@angular/core";
6
+ import * as i1 from "@angular/forms";
7
+ import * as i2 from "@memberjunction/ng-shared-generic";
8
+ /** IANA timezone list subset for the dropdown */
9
+ const COMMON_TIMEZONES = [
10
+ 'UTC',
11
+ 'America/New_York',
12
+ 'America/Chicago',
13
+ 'America/Denver',
14
+ 'America/Los_Angeles',
15
+ 'America/Phoenix',
16
+ 'America/Anchorage',
17
+ 'Pacific/Honolulu',
18
+ 'Europe/London',
19
+ 'Europe/Paris',
20
+ 'Europe/Berlin',
21
+ 'Asia/Tokyo',
22
+ 'Asia/Shanghai',
23
+ 'Asia/Kolkata',
24
+ 'Australia/Sydney',
25
+ ];
26
+ export class ScheduledJobEditorComponent {
27
+ cdr = inject(ChangeDetectorRef);
28
+ scheduledJobService = inject(ScheduledJobService);
29
+ // ── Inputs ────────────────────────────────────────────────
30
+ _scheduledJobID = null;
31
+ set ScheduledJobID(value) {
32
+ if (value !== this._scheduledJobID) {
33
+ this._scheduledJobID = value;
34
+ if (this.isInitialized) {
35
+ this.loadOrCreateJob();
36
+ }
37
+ }
38
+ }
39
+ get ScheduledJobID() {
40
+ return this._scheduledJobID;
41
+ }
42
+ JobTypeID = null;
43
+ DefaultConfiguration = null;
44
+ HideJobType = false;
45
+ // ── Outputs ───────────────────────────────────────────────
46
+ Saved = new EventEmitter();
47
+ Deleted = new EventEmitter();
48
+ Cancelled = new EventEmitter();
49
+ // ── State ─────────────────────────────────────────────────
50
+ IsLoading = false;
51
+ IsSaving = false;
52
+ IsNew = false;
53
+ ShowDeleteConfirm = false;
54
+ Job = null;
55
+ JobTypes = [];
56
+ Timezones = COMMON_TIMEZONES;
57
+ // Form fields
58
+ Name = '';
59
+ Description = '';
60
+ SelectedJobTypeID = '';
61
+ CronExpression = '';
62
+ Timezone = 'UTC';
63
+ Status = 'Active';
64
+ ConcurrencyMode = 'Skip';
65
+ Configuration = '';
66
+ NotifyOnSuccess = false;
67
+ NotifyOnFailure = true;
68
+ // Stats (edit mode)
69
+ TotalRuns = 0;
70
+ SuccessRuns = 0;
71
+ FailedRuns = 0;
72
+ StatusOptions = ['Pending', 'Active', 'Paused', 'Disabled'];
73
+ ConcurrencyOptions = ['Skip', 'Queue', 'Concurrent'];
74
+ isInitialized = false;
75
+ get CanSave() {
76
+ return this.Name.trim().length > 0
77
+ && this.SelectedJobTypeID.length > 0
78
+ && this.CronExpression.trim().length > 0;
79
+ }
80
+ async ngOnInit() {
81
+ this.IsLoading = true;
82
+ this.cdr.markForCheck();
83
+ this.JobTypes = await this.scheduledJobService.LoadJobTypes();
84
+ await this.loadOrCreateJob();
85
+ this.isInitialized = true;
86
+ this.IsLoading = false;
87
+ this.cdr.markForCheck();
88
+ }
89
+ async Save() {
90
+ if (!this.CanSave || this.IsSaving)
91
+ return;
92
+ this.IsSaving = true;
93
+ this.cdr.markForCheck();
94
+ try {
95
+ const job = await this.getOrCreateEntity();
96
+ this.applyFormToEntity(job);
97
+ const saved = await job.Save();
98
+ if (saved) {
99
+ MJNotificationService.Instance.CreateSimpleNotification(this.IsNew ? 'Scheduled job created successfully' : 'Scheduled job updated successfully', 'success', 3000);
100
+ this.Saved.emit(job);
101
+ }
102
+ else {
103
+ MJNotificationService.Instance.CreateSimpleNotification(`Failed to save: ${job.LatestResult?.Message ?? 'Unknown error'}`, 'error', 5000);
104
+ }
105
+ }
106
+ catch (err) {
107
+ const msg = err instanceof Error ? err.message : String(err);
108
+ MJNotificationService.Instance.CreateSimpleNotification(`Error saving: ${msg}`, 'error', 5000);
109
+ }
110
+ finally {
111
+ this.IsSaving = false;
112
+ this.cdr.markForCheck();
113
+ }
114
+ }
115
+ ConfirmDelete() {
116
+ this.ShowDeleteConfirm = true;
117
+ this.cdr.markForCheck();
118
+ }
119
+ CancelDelete() {
120
+ this.ShowDeleteConfirm = false;
121
+ this.cdr.markForCheck();
122
+ }
123
+ async DeleteJob() {
124
+ if (!this.Job || this.IsNew)
125
+ return;
126
+ this.ShowDeleteConfirm = false;
127
+ this.IsSaving = true;
128
+ this.cdr.markForCheck();
129
+ try {
130
+ const jobID = this.Job.ID;
131
+ const deleted = await this.Job.Delete();
132
+ if (deleted) {
133
+ MJNotificationService.Instance.CreateSimpleNotification('Scheduled job deleted', 'success', 3000);
134
+ this.Deleted.emit(jobID);
135
+ }
136
+ else {
137
+ MJNotificationService.Instance.CreateSimpleNotification('Failed to delete job', 'error', 5000);
138
+ }
139
+ }
140
+ catch (err) {
141
+ const msg = err instanceof Error ? err.message : String(err);
142
+ MJNotificationService.Instance.CreateSimpleNotification(`Error deleting: ${msg}`, 'error', 5000);
143
+ }
144
+ finally {
145
+ this.IsSaving = false;
146
+ this.cdr.markForCheck();
147
+ }
148
+ }
149
+ Cancel() {
150
+ this.Cancelled.emit();
151
+ }
152
+ // ── Private ───────────────────────────────────────────────
153
+ async loadOrCreateJob() {
154
+ if (this._scheduledJobID) {
155
+ await this.loadExistingJob(this._scheduledJobID);
156
+ }
157
+ else {
158
+ this.initNewJob();
159
+ }
160
+ }
161
+ async loadExistingJob(jobID) {
162
+ this.IsNew = false;
163
+ const job = await this.scheduledJobService.LoadJob(jobID);
164
+ if (job) {
165
+ this.Job = job;
166
+ this.populateFormFromEntity(job);
167
+ await this.loadRunStats(jobID);
168
+ }
169
+ this.cdr.markForCheck();
170
+ }
171
+ initNewJob() {
172
+ this.IsNew = true;
173
+ this.Job = null;
174
+ this.Name = '';
175
+ this.Description = '';
176
+ this.SelectedJobTypeID = this.JobTypeID ?? '';
177
+ this.CronExpression = '';
178
+ this.Timezone = 'UTC';
179
+ this.Status = 'Active';
180
+ this.ConcurrencyMode = 'Skip';
181
+ this.Configuration = this.DefaultConfiguration ?? '';
182
+ this.NotifyOnSuccess = false;
183
+ this.NotifyOnFailure = true;
184
+ this.TotalRuns = 0;
185
+ this.SuccessRuns = 0;
186
+ this.FailedRuns = 0;
187
+ this.cdr.markForCheck();
188
+ }
189
+ populateFormFromEntity(job) {
190
+ this.Name = job.Name ?? '';
191
+ this.Description = job.Description ?? '';
192
+ this.SelectedJobTypeID = job.JobTypeID ?? '';
193
+ this.CronExpression = job.CronExpression ?? '';
194
+ this.Timezone = job.Timezone ?? 'UTC';
195
+ this.Status = job.Status ?? 'Active';
196
+ this.ConcurrencyMode = job.Get('ConcurrencyMode') ?? 'Skip';
197
+ this.Configuration = job.Configuration ?? '';
198
+ this.NotifyOnSuccess = job.Get('NotifyOnSuccess') ?? false;
199
+ this.NotifyOnFailure = job.Get('NotifyOnFailure') ?? true;
200
+ }
201
+ applyFormToEntity(job) {
202
+ job.Name = this.Name.trim();
203
+ job.Description = this.Description.trim();
204
+ job.JobTypeID = this.SelectedJobTypeID;
205
+ job.CronExpression = this.CronExpression.trim();
206
+ job.Timezone = this.Timezone;
207
+ job.Status = this.Status;
208
+ job.Set('ConcurrencyMode', this.ConcurrencyMode);
209
+ job.Configuration = this.Configuration;
210
+ job.Set('NotifyOnSuccess', this.NotifyOnSuccess);
211
+ job.Set('NotifyOnFailure', this.NotifyOnFailure);
212
+ }
213
+ async getOrCreateEntity() {
214
+ if (this.Job && !this.IsNew) {
215
+ return this.Job;
216
+ }
217
+ const md = new Metadata();
218
+ const job = await md.GetEntityObject('MJ: Scheduled Jobs');
219
+ job.NewRecord();
220
+ return job;
221
+ }
222
+ async loadRunStats(jobID) {
223
+ const runs = await this.scheduledJobService.LoadJobRuns(jobID, 100);
224
+ this.TotalRuns = runs.length;
225
+ this.SuccessRuns = runs.filter(r => r.Status === 'Completed').length;
226
+ this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;
227
+ }
228
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
229
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ScheduledJobEditorComponent, isStandalone: false, selector: "mj-scheduled-job-editor", inputs: { ScheduledJobID: "ScheduledJobID", JobTypeID: "JobTypeID", DefaultConfiguration: "DefaultConfiguration", HideJobType: "HideJobType" }, outputs: { Saved: "Saved", Deleted: "Deleted", Cancelled: "Cancelled" }, ngImport: i0, template: "@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n", styles: [".editor-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 40px;\n}\n\n.editor-container {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 16px;\n}\n\n.form-section {\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n padding: 16px;\n background: #fff;\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: #334155;\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.section-title i {\n color: #0076b6;\n font-size: 14px;\n}\n\n.form-group {\n margin-bottom: 12px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n font-size: 12px;\n font-weight: 500;\n color: #64748b;\n margin-bottom: 4px;\n}\n\n.required {\n color: #ef4444;\n}\n\n.form-group input,\n.form-group textarea,\n.form-group select {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n font-size: 13px;\n color: #1e293b;\n background: #fff;\n box-sizing: border-box;\n transition: border-color 0.15s;\n}\n\n.form-group input:focus,\n.form-group textarea:focus,\n.form-group select:focus {\n outline: none;\n border-color: #0076b6;\n box-shadow: 0 0 0 2px rgba(0, 118, 182, 0.15);\n}\n\n.code-textarea {\n font-family: 'Consolas', 'Monaco', 'Courier New', monospace;\n font-size: 12px;\n line-height: 1.5;\n}\n\n.hint {\n font-size: 11px;\n color: #94a3b8;\n margin-top: 4px;\n}\n\n.toggle-row {\n margin-bottom: 8px;\n}\n\n.toggle-label {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #334155;\n cursor: pointer;\n}\n\n.toggle-label input[type=\"checkbox\"] {\n width: auto;\n}\n\n.stats-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n display: block;\n font-size: 20px;\n font-weight: 700;\n color: #334155;\n}\n\n.stat-value.success {\n color: #10b981;\n}\n\n.stat-value.error {\n color: #ef4444;\n}\n\n.stat-label {\n display: block;\n font-size: 11px;\n color: #94a3b8;\n margin-top: 2px;\n}\n\n.editor-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 8px;\n border-top: 1px solid #e2e8f0;\n}\n\n.footer-right {\n display: flex;\n gap: 8px;\n margin-left: auto;\n}\n\n.btn {\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: background 0.15s, opacity 0.15s;\n}\n\n.btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.btn-primary {\n background: #0076b6;\n color: #fff;\n}\n\n.btn-primary:hover:not(:disabled) {\n background: #005a8c;\n}\n\n.btn-secondary {\n background: #f1f5f9;\n color: #334155;\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: #e2e8f0;\n}\n\n.btn-danger {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.btn-danger:hover:not(:disabled) {\n background: #fecaca;\n}\n\n.confirm-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.confirm-dialog {\n background: #fff;\n border-radius: 10px;\n padding: 24px;\n max-width: 360px;\n text-align: center;\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);\n}\n\n.confirm-dialog p {\n margin: 0 0 16px;\n color: #334155;\n font-size: 14px;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: center;\n}\n\n@media (max-width: 480px) {\n .stats-grid {\n grid-template-columns: 1fr;\n }\n\n .editor-footer {\n flex-direction: column;\n gap: 8px;\n }\n\n .footer-right {\n margin-left: 0;\n width: 100%;\n }\n\n .footer-right .btn {\n flex: 1;\n justify-content: center;\n }\n}\n"], dependencies: [{ kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { 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: i2.LoadingComponent, selector: "mj-loading", inputs: ["text", "showText", "animationDuration", "size", "textColor", "logoColor", "logoGradient", "animation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
230
+ }
231
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobEditorComponent, decorators: [{
232
+ type: Component,
233
+ args: [{ selector: 'mj-scheduled-job-editor', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n", styles: [".editor-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 40px;\n}\n\n.editor-container {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 16px;\n}\n\n.form-section {\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n padding: 16px;\n background: #fff;\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: #334155;\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.section-title i {\n color: #0076b6;\n font-size: 14px;\n}\n\n.form-group {\n margin-bottom: 12px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n font-size: 12px;\n font-weight: 500;\n color: #64748b;\n margin-bottom: 4px;\n}\n\n.required {\n color: #ef4444;\n}\n\n.form-group input,\n.form-group textarea,\n.form-group select {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n font-size: 13px;\n color: #1e293b;\n background: #fff;\n box-sizing: border-box;\n transition: border-color 0.15s;\n}\n\n.form-group input:focus,\n.form-group textarea:focus,\n.form-group select:focus {\n outline: none;\n border-color: #0076b6;\n box-shadow: 0 0 0 2px rgba(0, 118, 182, 0.15);\n}\n\n.code-textarea {\n font-family: 'Consolas', 'Monaco', 'Courier New', monospace;\n font-size: 12px;\n line-height: 1.5;\n}\n\n.hint {\n font-size: 11px;\n color: #94a3b8;\n margin-top: 4px;\n}\n\n.toggle-row {\n margin-bottom: 8px;\n}\n\n.toggle-label {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #334155;\n cursor: pointer;\n}\n\n.toggle-label input[type=\"checkbox\"] {\n width: auto;\n}\n\n.stats-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n display: block;\n font-size: 20px;\n font-weight: 700;\n color: #334155;\n}\n\n.stat-value.success {\n color: #10b981;\n}\n\n.stat-value.error {\n color: #ef4444;\n}\n\n.stat-label {\n display: block;\n font-size: 11px;\n color: #94a3b8;\n margin-top: 2px;\n}\n\n.editor-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 8px;\n border-top: 1px solid #e2e8f0;\n}\n\n.footer-right {\n display: flex;\n gap: 8px;\n margin-left: auto;\n}\n\n.btn {\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: background 0.15s, opacity 0.15s;\n}\n\n.btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.btn-primary {\n background: #0076b6;\n color: #fff;\n}\n\n.btn-primary:hover:not(:disabled) {\n background: #005a8c;\n}\n\n.btn-secondary {\n background: #f1f5f9;\n color: #334155;\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: #e2e8f0;\n}\n\n.btn-danger {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.btn-danger:hover:not(:disabled) {\n background: #fecaca;\n}\n\n.confirm-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.confirm-dialog {\n background: #fff;\n border-radius: 10px;\n padding: 24px;\n max-width: 360px;\n text-align: center;\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);\n}\n\n.confirm-dialog p {\n margin: 0 0 16px;\n color: #334155;\n font-size: 14px;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: center;\n}\n\n@media (max-width: 480px) {\n .stats-grid {\n grid-template-columns: 1fr;\n }\n\n .editor-footer {\n flex-direction: column;\n gap: 8px;\n }\n\n .footer-right {\n margin-left: 0;\n width: 100%;\n }\n\n .footer-right .btn {\n flex: 1;\n justify-content: center;\n }\n}\n"] }]
234
+ }], propDecorators: { ScheduledJobID: [{
235
+ type: Input
236
+ }], JobTypeID: [{
237
+ type: Input
238
+ }], DefaultConfiguration: [{
239
+ type: Input
240
+ }], HideJobType: [{
241
+ type: Input
242
+ }], Saved: [{
243
+ type: Output
244
+ }], Deleted: [{
245
+ type: Output
246
+ }], Cancelled: [{
247
+ type: Output
248
+ }] } });
249
+ //# sourceMappingURL=scheduled-job-editor.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-job-editor.component.js","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.ts","../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n OnInit,\n inject,\n} from '@angular/core';\nimport { Metadata } from '@memberjunction/core';\nimport { MJScheduledJobEntity, MJScheduledJobTypeEntity } from '@memberjunction/core-entities';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { ScheduledJobService } from '../../services/scheduled-job.service';\n\n/** IANA timezone list subset for the dropdown */\nconst COMMON_TIMEZONES = [\n 'UTC',\n 'America/New_York',\n 'America/Chicago',\n 'America/Denver',\n 'America/Los_Angeles',\n 'America/Phoenix',\n 'America/Anchorage',\n 'Pacific/Honolulu',\n 'Europe/London',\n 'Europe/Paris',\n 'Europe/Berlin',\n 'Asia/Tokyo',\n 'Asia/Shanghai',\n 'Asia/Kolkata',\n 'Australia/Sydney',\n];\n\n@Component({\n selector: 'mj-scheduled-job-editor',\n standalone: false,\n templateUrl: './scheduled-job-editor.component.html',\n styleUrls: ['./scheduled-job-editor.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobEditorComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n private scheduledJobService = inject(ScheduledJobService);\n\n // ── Inputs ────────────────────────────────────────────────\n private _scheduledJobID: string | null = null;\n\n @Input()\n set ScheduledJobID(value: string | null) {\n if (value !== this._scheduledJobID) {\n this._scheduledJobID = value;\n if (this.isInitialized) {\n this.loadOrCreateJob();\n }\n }\n }\n get ScheduledJobID(): string | null {\n return this._scheduledJobID;\n }\n\n @Input() JobTypeID: string | null = null;\n @Input() DefaultConfiguration: string | null = null;\n @Input() HideJobType = false;\n\n // ── Outputs ───────────────────────────────────────────────\n @Output() Saved = new EventEmitter<MJScheduledJobEntity>();\n @Output() Deleted = new EventEmitter<string>();\n @Output() Cancelled = new EventEmitter<void>();\n\n // ── State ─────────────────────────────────────────────────\n public IsLoading = false;\n public IsSaving = false;\n public IsNew = false;\n public ShowDeleteConfirm = false;\n\n public Job: MJScheduledJobEntity | null = null;\n public JobTypes: MJScheduledJobTypeEntity[] = [];\n public Timezones: string[] = COMMON_TIMEZONES;\n\n // Form fields\n public Name = '';\n public Description = '';\n public SelectedJobTypeID = '';\n public CronExpression = '';\n public Timezone = 'UTC';\n public Status: 'Active' | 'Disabled' | 'Expired' | 'Paused' | 'Pending' = 'Active';\n public ConcurrencyMode = 'Skip';\n public Configuration = '';\n public NotifyOnSuccess = false;\n public NotifyOnFailure = true;\n\n // Stats (edit mode)\n public TotalRuns = 0;\n public SuccessRuns = 0;\n public FailedRuns = 0;\n\n public readonly StatusOptions = ['Pending', 'Active', 'Paused', 'Disabled'];\n public readonly ConcurrencyOptions = ['Skip', 'Queue', 'Concurrent'];\n\n private isInitialized = false;\n\n public get CanSave(): boolean {\n return this.Name.trim().length > 0\n && this.SelectedJobTypeID.length > 0\n && this.CronExpression.trim().length > 0;\n }\n\n async ngOnInit(): Promise<void> {\n this.IsLoading = true;\n this.cdr.markForCheck();\n\n this.JobTypes = await this.scheduledJobService.LoadJobTypes();\n await this.loadOrCreateJob();\n\n this.isInitialized = true;\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n\n public async Save(): Promise<void> {\n if (!this.CanSave || this.IsSaving) return;\n\n this.IsSaving = true;\n this.cdr.markForCheck();\n\n try {\n const job = await this.getOrCreateEntity();\n this.applyFormToEntity(job);\n\n const saved = await job.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(\n this.IsNew ? 'Scheduled job created successfully' : 'Scheduled job updated successfully',\n 'success', 3000\n );\n this.Saved.emit(job);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification(\n `Failed to save: ${job.LatestResult?.Message ?? 'Unknown error'}`,\n 'error', 5000\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n MJNotificationService.Instance.CreateSimpleNotification(`Error saving: ${msg}`, 'error', 5000);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public ConfirmDelete(): void {\n this.ShowDeleteConfirm = true;\n this.cdr.markForCheck();\n }\n\n public CancelDelete(): void {\n this.ShowDeleteConfirm = false;\n this.cdr.markForCheck();\n }\n\n public async DeleteJob(): Promise<void> {\n if (!this.Job || this.IsNew) return;\n\n this.ShowDeleteConfirm = false;\n this.IsSaving = true;\n this.cdr.markForCheck();\n\n try {\n const jobID = this.Job.ID;\n const deleted = await this.Job.Delete();\n if (deleted) {\n MJNotificationService.Instance.CreateSimpleNotification('Scheduled job deleted', 'success', 3000);\n this.Deleted.emit(jobID);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification('Failed to delete job', 'error', 5000);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n MJNotificationService.Instance.CreateSimpleNotification(`Error deleting: ${msg}`, 'error', 5000);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public Cancel(): void {\n this.Cancelled.emit();\n }\n\n // ── Private ───────────────────────────────────────────────\n\n private async loadOrCreateJob(): Promise<void> {\n if (this._scheduledJobID) {\n await this.loadExistingJob(this._scheduledJobID);\n } else {\n this.initNewJob();\n }\n }\n\n private async loadExistingJob(jobID: string): Promise<void> {\n this.IsNew = false;\n const job = await this.scheduledJobService.LoadJob(jobID);\n if (job) {\n this.Job = job;\n this.populateFormFromEntity(job);\n await this.loadRunStats(jobID);\n }\n this.cdr.markForCheck();\n }\n\n private initNewJob(): void {\n this.IsNew = true;\n this.Job = null;\n this.Name = '';\n this.Description = '';\n this.SelectedJobTypeID = this.JobTypeID ?? '';\n this.CronExpression = '';\n this.Timezone = 'UTC';\n this.Status = 'Active';\n this.ConcurrencyMode = 'Skip';\n this.Configuration = this.DefaultConfiguration ?? '';\n this.NotifyOnSuccess = false;\n this.NotifyOnFailure = true;\n this.TotalRuns = 0;\n this.SuccessRuns = 0;\n this.FailedRuns = 0;\n this.cdr.markForCheck();\n }\n\n private populateFormFromEntity(job: MJScheduledJobEntity): void {\n this.Name = job.Name ?? '';\n this.Description = job.Description ?? '';\n this.SelectedJobTypeID = job.JobTypeID ?? '';\n this.CronExpression = job.CronExpression ?? '';\n this.Timezone = job.Timezone ?? 'UTC';\n this.Status = job.Status ?? 'Active';\n this.ConcurrencyMode = job.Get('ConcurrencyMode') as string ?? 'Skip';\n this.Configuration = job.Configuration ?? '';\n this.NotifyOnSuccess = job.Get('NotifyOnSuccess') as boolean ?? false;\n this.NotifyOnFailure = job.Get('NotifyOnFailure') as boolean ?? true;\n }\n\n private applyFormToEntity(job: MJScheduledJobEntity): void {\n job.Name = this.Name.trim();\n job.Description = this.Description.trim();\n job.JobTypeID = this.SelectedJobTypeID;\n job.CronExpression = this.CronExpression.trim();\n job.Timezone = this.Timezone;\n job.Status = this.Status;\n job.Set('ConcurrencyMode', this.ConcurrencyMode);\n job.Configuration = this.Configuration;\n job.Set('NotifyOnSuccess', this.NotifyOnSuccess);\n job.Set('NotifyOnFailure', this.NotifyOnFailure);\n }\n\n private async getOrCreateEntity(): Promise<MJScheduledJobEntity> {\n if (this.Job && !this.IsNew) {\n return this.Job;\n }\n const md = new Metadata();\n const job = await md.GetEntityObject<MJScheduledJobEntity>('MJ: Scheduled Jobs');\n job.NewRecord();\n return job;\n }\n\n private async loadRunStats(jobID: string): Promise<void> {\n const runs = await this.scheduledJobService.LoadJobRuns(jobID, 100);\n this.TotalRuns = runs.length;\n this.SuccessRuns = runs.filter(r => r.Status === 'Completed').length;\n this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;\n }\n}\n","@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import { EventEmitter, OnInit } from '@angular/core';
2
+ import { MJScheduledJobEntity, MJScheduledJobRunEntity } from '@memberjunction/core-entities';
3
+ import * as i0 from "@angular/core";
4
+ export declare class ScheduledJobSummaryComponent implements OnInit {
5
+ private cdr;
6
+ private scheduledJobService;
7
+ private _scheduledJobID;
8
+ set ScheduledJobID(value: string | null);
9
+ get ScheduledJobID(): string | null;
10
+ ShowEditButton: boolean;
11
+ EditRequested: EventEmitter<string>;
12
+ IsLoading: boolean;
13
+ Job: MJScheduledJobEntity | null;
14
+ TotalRuns: number;
15
+ SuccessRuns: number;
16
+ FailedRuns: number;
17
+ LastRun: MJScheduledJobRunEntity | null;
18
+ private isInitialized;
19
+ ngOnInit(): Promise<void>;
20
+ OnEditClick(): void;
21
+ get StatusClass(): string;
22
+ get SuccessRate(): string;
23
+ private loadJob;
24
+ static ɵfac: i0.ɵɵFactoryDeclaration<ScheduledJobSummaryComponent, never>;
25
+ static ɵcmp: i0.ɵɵComponentDeclaration<ScheduledJobSummaryComponent, "mj-scheduled-job-summary", never, { "ScheduledJobID": { "alias": "ScheduledJobID"; "required": false; }; "ShowEditButton": { "alias": "ShowEditButton"; "required": false; }; }, { "EditRequested": "EditRequested"; }, never, never, false, never>;
26
+ }
27
+ //# sourceMappingURL=scheduled-job-summary.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-job-summary.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-summary/scheduled-job-summary.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,YAAY,EAIZ,MAAM,EACT,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;;AAG9F,qBAOa,4BAA6B,YAAW,MAAM;IACvD,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,mBAAmB,CAA+B;IAE1D,OAAO,CAAC,eAAe,CAAuB;IAE9C,IACI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAOtC;IACD,IAAI,cAAc,IAAI,MAAM,GAAG,IAAI,CAElC;IAEQ,cAAc,UAAQ;IAErB,aAAa,uBAA8B;IAE9C,SAAS,UAAS;IAClB,GAAG,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IACxC,SAAS,SAAK;IACd,WAAW,SAAK;IAChB,UAAU,SAAK;IACf,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAAQ;IAEtD,OAAO,CAAC,aAAa,CAAS;IAExB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAKxB,WAAW,IAAI,IAAI;IAM1B,IAAW,WAAW,IAAI,MAAM,CAM/B;IAED,IAAW,WAAW,IAAI,MAAM,CAG/B;YAEa,OAAO;yCAxDZ,4BAA4B;2CAA5B,4BAA4B;CA4ExC"}
@@ -0,0 +1,83 @@
1
+ import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef, inject, } from '@angular/core';
2
+ import { ScheduledJobService } from '../../services/scheduled-job.service';
3
+ import * as i0 from "@angular/core";
4
+ import * as i1 from "@memberjunction/ng-shared-generic";
5
+ export class ScheduledJobSummaryComponent {
6
+ cdr = inject(ChangeDetectorRef);
7
+ scheduledJobService = inject(ScheduledJobService);
8
+ _scheduledJobID = null;
9
+ set ScheduledJobID(value) {
10
+ if (value !== this._scheduledJobID) {
11
+ this._scheduledJobID = value;
12
+ if (this.isInitialized) {
13
+ this.loadJob();
14
+ }
15
+ }
16
+ }
17
+ get ScheduledJobID() {
18
+ return this._scheduledJobID;
19
+ }
20
+ ShowEditButton = true;
21
+ EditRequested = new EventEmitter();
22
+ IsLoading = false;
23
+ Job = null;
24
+ TotalRuns = 0;
25
+ SuccessRuns = 0;
26
+ FailedRuns = 0;
27
+ LastRun = null;
28
+ isInitialized = false;
29
+ async ngOnInit() {
30
+ await this.loadJob();
31
+ this.isInitialized = true;
32
+ }
33
+ OnEditClick() {
34
+ if (this._scheduledJobID) {
35
+ this.EditRequested.emit(this._scheduledJobID);
36
+ }
37
+ }
38
+ get StatusClass() {
39
+ const status = this.Job?.Status?.toLowerCase() ?? '';
40
+ if (status === 'active')
41
+ return 'status-active';
42
+ if (status === 'paused')
43
+ return 'status-paused';
44
+ if (status === 'disabled')
45
+ return 'status-disabled';
46
+ return 'status-pending';
47
+ }
48
+ get SuccessRate() {
49
+ if (this.TotalRuns === 0)
50
+ return 'N/A';
51
+ return `${Math.round((this.SuccessRuns / this.TotalRuns) * 100)}%`;
52
+ }
53
+ async loadJob() {
54
+ if (!this._scheduledJobID) {
55
+ this.Job = null;
56
+ this.cdr.markForCheck();
57
+ return;
58
+ }
59
+ this.IsLoading = true;
60
+ this.cdr.markForCheck();
61
+ this.Job = await this.scheduledJobService.LoadJob(this._scheduledJobID);
62
+ const runs = await this.scheduledJobService.LoadJobRuns(this._scheduledJobID, 50);
63
+ this.TotalRuns = runs.length;
64
+ this.SuccessRuns = runs.filter(r => r.Status === 'Completed').length;
65
+ this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;
66
+ this.LastRun = runs.length > 0 ? runs[0] : null;
67
+ this.IsLoading = false;
68
+ this.cdr.markForCheck();
69
+ }
70
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobSummaryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
71
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ScheduledJobSummaryComponent, isStandalone: false, selector: "mj-scheduled-job-summary", inputs: { ScheduledJobID: "ScheduledJobID", ShowEditButton: "ShowEditButton" }, outputs: { EditRequested: "EditRequested" }, ngImport: i0, template: "@if (IsLoading) {\n <div class=\"summary-loading\">\n <mj-loading size=\"small\"></mj-loading>\n </div>\n} @else if (Job) {\n <div class=\"summary-card\">\n <div class=\"summary-header\">\n <div class=\"summary-name\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n {{ Job.Name }}\n </div>\n <span class=\"status-chip\" [class]=\"StatusClass\">{{ Job.Status }}</span>\n </div>\n\n <div class=\"summary-meta\">\n <div class=\"meta-item\">\n <i class=\"fa-regular fa-clock\"></i>\n <span>{{ Job.CronExpression }}</span>\n </div>\n @if (Job.Timezone && Job.Timezone !== 'UTC') {\n <div class=\"meta-item\">\n <i class=\"fa-solid fa-globe\"></i>\n <span>{{ Job.Timezone }}</span>\n </div>\n }\n </div>\n\n <div class=\"summary-stats\">\n <div class=\"stat\">\n <span class=\"stat-num\">{{ TotalRuns }}</span>\n <span class=\"stat-lbl\">Runs</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num success\">{{ SuccessRuns }}</span>\n <span class=\"stat-lbl\">OK</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num error\">{{ FailedRuns }}</span>\n <span class=\"stat-lbl\">Failed</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num\">{{ SuccessRate }}</span>\n <span class=\"stat-lbl\">Rate</span>\n </div>\n </div>\n\n @if (ShowEditButton) {\n <div class=\"summary-actions\">\n <button class=\"edit-btn\" (click)=\"OnEditClick()\">\n <i class=\"fa-solid fa-pen-to-square\"></i> Edit Schedule\n </button>\n </div>\n }\n </div>\n} @else {\n <div class=\"summary-empty\">\n <i class=\"fa-regular fa-calendar-xmark\"></i>\n <span>No schedule configured</span>\n </div>\n}\n", styles: [".summary-loading {\n display: flex;\n justify-content: center;\n padding: 16px;\n}\n\n.summary-card {\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n padding: 14px;\n background: #fff;\n}\n\n.summary-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 10px;\n}\n\n.summary-name {\n font-size: 14px;\n font-weight: 600;\n color: #1e293b;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.summary-name i {\n color: #0076b6;\n}\n\n.status-chip {\n font-size: 11px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.status-active {\n background: #d1fae5;\n color: #065f46;\n}\n\n.status-paused {\n background: #fef3c7;\n color: #92400e;\n}\n\n.status-disabled {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.status-pending {\n background: #e0e7ff;\n color: #3730a3;\n}\n\n.summary-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 10px;\n}\n\n.meta-item {\n display: flex;\n align-items: center;\n gap: 5px;\n font-size: 12px;\n color: #64748b;\n}\n\n.meta-item i {\n font-size: 11px;\n}\n\n.summary-stats {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n border-top: 1px solid #f1f5f9;\n}\n\n.stat {\n text-align: center;\n flex: 1;\n}\n\n.stat-num {\n display: block;\n font-size: 16px;\n font-weight: 700;\n color: #334155;\n}\n\n.stat-num.success {\n color: #10b981;\n}\n\n.stat-num.error {\n color: #ef4444;\n}\n\n.stat-lbl {\n display: block;\n font-size: 10px;\n color: #94a3b8;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.summary-actions {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid #f1f5f9;\n}\n\n.edit-btn {\n background: none;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n padding: 6px 12px;\n font-size: 12px;\n color: #0076b6;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n transition: background 0.15s;\n}\n\n.edit-btn:hover {\n background: #f0f9ff;\n}\n\n.summary-empty {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n color: #94a3b8;\n font-size: 13px;\n}\n\n.summary-empty i {\n font-size: 16px;\n}\n"], dependencies: [{ kind: "component", type: i1.LoadingComponent, selector: "mj-loading", inputs: ["text", "showText", "animationDuration", "size", "textColor", "logoColor", "logoGradient", "animation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
72
+ }
73
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobSummaryComponent, decorators: [{
74
+ type: Component,
75
+ args: [{ selector: 'mj-scheduled-job-summary', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (IsLoading) {\n <div class=\"summary-loading\">\n <mj-loading size=\"small\"></mj-loading>\n </div>\n} @else if (Job) {\n <div class=\"summary-card\">\n <div class=\"summary-header\">\n <div class=\"summary-name\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n {{ Job.Name }}\n </div>\n <span class=\"status-chip\" [class]=\"StatusClass\">{{ Job.Status }}</span>\n </div>\n\n <div class=\"summary-meta\">\n <div class=\"meta-item\">\n <i class=\"fa-regular fa-clock\"></i>\n <span>{{ Job.CronExpression }}</span>\n </div>\n @if (Job.Timezone && Job.Timezone !== 'UTC') {\n <div class=\"meta-item\">\n <i class=\"fa-solid fa-globe\"></i>\n <span>{{ Job.Timezone }}</span>\n </div>\n }\n </div>\n\n <div class=\"summary-stats\">\n <div class=\"stat\">\n <span class=\"stat-num\">{{ TotalRuns }}</span>\n <span class=\"stat-lbl\">Runs</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num success\">{{ SuccessRuns }}</span>\n <span class=\"stat-lbl\">OK</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num error\">{{ FailedRuns }}</span>\n <span class=\"stat-lbl\">Failed</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num\">{{ SuccessRate }}</span>\n <span class=\"stat-lbl\">Rate</span>\n </div>\n </div>\n\n @if (ShowEditButton) {\n <div class=\"summary-actions\">\n <button class=\"edit-btn\" (click)=\"OnEditClick()\">\n <i class=\"fa-solid fa-pen-to-square\"></i> Edit Schedule\n </button>\n </div>\n }\n </div>\n} @else {\n <div class=\"summary-empty\">\n <i class=\"fa-regular fa-calendar-xmark\"></i>\n <span>No schedule configured</span>\n </div>\n}\n", styles: [".summary-loading {\n display: flex;\n justify-content: center;\n padding: 16px;\n}\n\n.summary-card {\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n padding: 14px;\n background: #fff;\n}\n\n.summary-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 10px;\n}\n\n.summary-name {\n font-size: 14px;\n font-weight: 600;\n color: #1e293b;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.summary-name i {\n color: #0076b6;\n}\n\n.status-chip {\n font-size: 11px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.status-active {\n background: #d1fae5;\n color: #065f46;\n}\n\n.status-paused {\n background: #fef3c7;\n color: #92400e;\n}\n\n.status-disabled {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.status-pending {\n background: #e0e7ff;\n color: #3730a3;\n}\n\n.summary-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 10px;\n}\n\n.meta-item {\n display: flex;\n align-items: center;\n gap: 5px;\n font-size: 12px;\n color: #64748b;\n}\n\n.meta-item i {\n font-size: 11px;\n}\n\n.summary-stats {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n border-top: 1px solid #f1f5f9;\n}\n\n.stat {\n text-align: center;\n flex: 1;\n}\n\n.stat-num {\n display: block;\n font-size: 16px;\n font-weight: 700;\n color: #334155;\n}\n\n.stat-num.success {\n color: #10b981;\n}\n\n.stat-num.error {\n color: #ef4444;\n}\n\n.stat-lbl {\n display: block;\n font-size: 10px;\n color: #94a3b8;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.summary-actions {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid #f1f5f9;\n}\n\n.edit-btn {\n background: none;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n padding: 6px 12px;\n font-size: 12px;\n color: #0076b6;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n transition: background 0.15s;\n}\n\n.edit-btn:hover {\n background: #f0f9ff;\n}\n\n.summary-empty {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n color: #94a3b8;\n font-size: 13px;\n}\n\n.summary-empty i {\n font-size: 16px;\n}\n"] }]
76
+ }], propDecorators: { ScheduledJobID: [{
77
+ type: Input
78
+ }], ShowEditButton: [{
79
+ type: Input
80
+ }], EditRequested: [{
81
+ type: Output
82
+ }] } });
83
+ //# sourceMappingURL=scheduled-job-summary.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-job-summary.component.js","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-summary/scheduled-job-summary.component.ts","../../../../src/lib/panels/scheduled-job-summary/scheduled-job-summary.component.html"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,YAAY,EACZ,uBAAuB,EACvB,iBAAiB,EACjB,MAAM,GAET,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;;;AAS3E,MAAM,OAAO,4BAA4B;IAC7B,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAChC,mBAAmB,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAElD,eAAe,GAAkB,IAAI,CAAC;IAE9C,IACI,cAAc,CAAC,KAAoB;QACnC,IAAI,KAAK,KAAK,IAAI,CAAC,eAAe,EAAE,CAAC;YACjC,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;YAC7B,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;gBACrB,IAAI,CAAC,OAAO,EAAE,CAAC;YACnB,CAAC;QACL,CAAC;IACL,CAAC;IACD,IAAI,cAAc;QACd,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;IAEQ,cAAc,GAAG,IAAI,CAAC;IAErB,aAAa,GAAG,IAAI,YAAY,EAAU,CAAC;IAE9C,SAAS,GAAG,KAAK,CAAC;IAClB,GAAG,GAAgC,IAAI,CAAC;IACxC,SAAS,GAAG,CAAC,CAAC;IACd,WAAW,GAAG,CAAC,CAAC;IAChB,UAAU,GAAG,CAAC,CAAC;IACf,OAAO,GAAmC,IAAI,CAAC;IAE9C,aAAa,GAAG,KAAK,CAAC;IAE9B,KAAK,CAAC,QAAQ;QACV,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;IAC9B,CAAC;IAEM,WAAW;QACd,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAClD,CAAC;IACL,CAAC;IAED,IAAW,WAAW;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QACrD,IAAI,MAAM,KAAK,QAAQ;YAAE,OAAO,eAAe,CAAC;QAChD,IAAI,MAAM,KAAK,QAAQ;YAAE,OAAO,eAAe,CAAC;QAChD,IAAI,MAAM,KAAK,UAAU;YAAE,OAAO,iBAAiB,CAAC;QACpD,OAAO,gBAAgB,CAAC;IAC5B,CAAC;IAED,IAAW,WAAW;QAClB,IAAI,IAAI,CAAC,SAAS,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACvC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC;IACvE,CAAC;IAEO,KAAK,CAAC,OAAO;QACjB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;YAChB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YACxB,OAAO;QACX,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAExB,IAAI,CAAC,GAAG,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACxE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,WAAW,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;QAClF,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,MAAM,CAAC;QACrE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;QACjE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAEhD,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;uGA3EQ,4BAA4B;2FAA5B,4BAA4B,kNCpBzC,qoEA4DA;;2FDxCa,4BAA4B;kBAPxC,SAAS;+BACI,0BAA0B,cACxB,KAAK,mBAGA,uBAAuB,CAAC,MAAM;;sBAQ9C,KAAK;;sBAaL,KAAK;;sBAEL,MAAM","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n inject,\n OnInit,\n} from '@angular/core';\nimport { MJScheduledJobEntity, MJScheduledJobRunEntity } from '@memberjunction/core-entities';\nimport { ScheduledJobService } from '../../services/scheduled-job.service';\n\n@Component({\n selector: 'mj-scheduled-job-summary',\n standalone: false,\n templateUrl: './scheduled-job-summary.component.html',\n styleUrls: ['./scheduled-job-summary.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobSummaryComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n private scheduledJobService = inject(ScheduledJobService);\n\n private _scheduledJobID: string | null = null;\n\n @Input()\n set ScheduledJobID(value: string | null) {\n if (value !== this._scheduledJobID) {\n this._scheduledJobID = value;\n if (this.isInitialized) {\n this.loadJob();\n }\n }\n }\n get ScheduledJobID(): string | null {\n return this._scheduledJobID;\n }\n\n @Input() ShowEditButton = true;\n\n @Output() EditRequested = new EventEmitter<string>();\n\n public IsLoading = false;\n public Job: MJScheduledJobEntity | null = null;\n public TotalRuns = 0;\n public SuccessRuns = 0;\n public FailedRuns = 0;\n public LastRun: MJScheduledJobRunEntity | null = null;\n\n private isInitialized = false;\n\n async ngOnInit(): Promise<void> {\n await this.loadJob();\n this.isInitialized = true;\n }\n\n public OnEditClick(): void {\n if (this._scheduledJobID) {\n this.EditRequested.emit(this._scheduledJobID);\n }\n }\n\n public get StatusClass(): string {\n const status = this.Job?.Status?.toLowerCase() ?? '';\n if (status === 'active') return 'status-active';\n if (status === 'paused') return 'status-paused';\n if (status === 'disabled') return 'status-disabled';\n return 'status-pending';\n }\n\n public get SuccessRate(): string {\n if (this.TotalRuns === 0) return 'N/A';\n return `${Math.round((this.SuccessRuns / this.TotalRuns) * 100)}%`;\n }\n\n private async loadJob(): Promise<void> {\n if (!this._scheduledJobID) {\n this.Job = null;\n this.cdr.markForCheck();\n return;\n }\n\n this.IsLoading = true;\n this.cdr.markForCheck();\n\n this.Job = await this.scheduledJobService.LoadJob(this._scheduledJobID);\n const runs = await this.scheduledJobService.LoadJobRuns(this._scheduledJobID, 50);\n this.TotalRuns = runs.length;\n this.SuccessRuns = runs.filter(r => r.Status === 'Completed').length;\n this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;\n this.LastRun = runs.length > 0 ? runs[0] : null;\n\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n}\n","@if (IsLoading) {\n <div class=\"summary-loading\">\n <mj-loading size=\"small\"></mj-loading>\n </div>\n} @else if (Job) {\n <div class=\"summary-card\">\n <div class=\"summary-header\">\n <div class=\"summary-name\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n {{ Job.Name }}\n </div>\n <span class=\"status-chip\" [class]=\"StatusClass\">{{ Job.Status }}</span>\n </div>\n\n <div class=\"summary-meta\">\n <div class=\"meta-item\">\n <i class=\"fa-regular fa-clock\"></i>\n <span>{{ Job.CronExpression }}</span>\n </div>\n @if (Job.Timezone && Job.Timezone !== 'UTC') {\n <div class=\"meta-item\">\n <i class=\"fa-solid fa-globe\"></i>\n <span>{{ Job.Timezone }}</span>\n </div>\n }\n </div>\n\n <div class=\"summary-stats\">\n <div class=\"stat\">\n <span class=\"stat-num\">{{ TotalRuns }}</span>\n <span class=\"stat-lbl\">Runs</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num success\">{{ SuccessRuns }}</span>\n <span class=\"stat-lbl\">OK</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num error\">{{ FailedRuns }}</span>\n <span class=\"stat-lbl\">Failed</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num\">{{ SuccessRate }}</span>\n <span class=\"stat-lbl\">Rate</span>\n </div>\n </div>\n\n @if (ShowEditButton) {\n <div class=\"summary-actions\">\n <button class=\"edit-btn\" (click)=\"OnEditClick()\">\n <i class=\"fa-solid fa-pen-to-square\"></i> Edit Schedule\n </button>\n </div>\n }\n </div>\n} @else {\n <div class=\"summary-empty\">\n <i class=\"fa-regular fa-calendar-xmark\"></i>\n <span>No schedule configured</span>\n </div>\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import * as i0 from "@angular/core";
2
+ import * as i1 from "./panels/scheduled-job-editor/scheduled-job-editor.component";
3
+ import * as i2 from "./panels/scheduled-job-summary/scheduled-job-summary.component";
4
+ import * as i3 from "./slide-panel/scheduled-job-slide-panel.component";
5
+ import * as i4 from "./dialogs/scheduled-job-dialog.component";
6
+ import * as i5 from "@angular/common";
7
+ import * as i6 from "@angular/forms";
8
+ import * as i7 from "@progress/kendo-angular-dialog";
9
+ import * as i8 from "@progress/kendo-angular-buttons";
10
+ import * as i9 from "@memberjunction/ng-shared-generic";
11
+ export declare class SchedulingModule {
12
+ static ɵfac: i0.ɵɵFactoryDeclaration<SchedulingModule, never>;
13
+ static ɵmod: i0.ɵɵNgModuleDeclaration<SchedulingModule, [typeof i1.ScheduledJobEditorComponent, typeof i2.ScheduledJobSummaryComponent, typeof i3.ScheduledJobSlidePanelComponent, typeof i4.ScheduledJobDialogComponent], [typeof i5.CommonModule, typeof i6.FormsModule, typeof i7.DialogModule, typeof i8.ButtonsModule, typeof i9.SharedGenericModule], [typeof i1.ScheduledJobEditorComponent, typeof i2.ScheduledJobSummaryComponent, typeof i3.ScheduledJobSlidePanelComponent, typeof i4.ScheduledJobDialogComponent]>;
14
+ static ɵinj: i0.ɵɵInjectorDeclaration<SchedulingModule>;
15
+ }
16
+ //# sourceMappingURL=scheduling.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduling.module.d.ts","sourceRoot":"","sources":["../../src/lib/scheduling.module.ts"],"names":[],"mappings":";;;;;;;;;;AAYA,qBAqBa,gBAAgB;yCAAhB,gBAAgB;0CAAhB,gBAAgB;0CAAhB,gBAAgB;CAAG"}
@@ -0,0 +1,55 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { DialogModule } from '@progress/kendo-angular-dialog';
5
+ import { ButtonsModule } from '@progress/kendo-angular-buttons';
6
+ import { SharedGenericModule } from '@memberjunction/ng-shared-generic';
7
+ import { ScheduledJobEditorComponent } from './panels/scheduled-job-editor/scheduled-job-editor.component';
8
+ import { ScheduledJobSummaryComponent } from './panels/scheduled-job-summary/scheduled-job-summary.component';
9
+ import { ScheduledJobSlidePanelComponent } from './slide-panel/scheduled-job-slide-panel.component';
10
+ import { ScheduledJobDialogComponent } from './dialogs/scheduled-job-dialog.component';
11
+ import * as i0 from "@angular/core";
12
+ export class SchedulingModule {
13
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: SchedulingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
14
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: SchedulingModule, declarations: [ScheduledJobEditorComponent,
15
+ ScheduledJobSummaryComponent,
16
+ ScheduledJobSlidePanelComponent,
17
+ ScheduledJobDialogComponent], imports: [CommonModule,
18
+ FormsModule,
19
+ DialogModule,
20
+ ButtonsModule,
21
+ SharedGenericModule], exports: [ScheduledJobEditorComponent,
22
+ ScheduledJobSummaryComponent,
23
+ ScheduledJobSlidePanelComponent,
24
+ ScheduledJobDialogComponent] });
25
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: SchedulingModule, imports: [CommonModule,
26
+ FormsModule,
27
+ DialogModule,
28
+ ButtonsModule,
29
+ SharedGenericModule] });
30
+ }
31
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: SchedulingModule, decorators: [{
32
+ type: NgModule,
33
+ args: [{
34
+ declarations: [
35
+ ScheduledJobEditorComponent,
36
+ ScheduledJobSummaryComponent,
37
+ ScheduledJobSlidePanelComponent,
38
+ ScheduledJobDialogComponent,
39
+ ],
40
+ imports: [
41
+ CommonModule,
42
+ FormsModule,
43
+ DialogModule,
44
+ ButtonsModule,
45
+ SharedGenericModule,
46
+ ],
47
+ exports: [
48
+ ScheduledJobEditorComponent,
49
+ ScheduledJobSummaryComponent,
50
+ ScheduledJobSlidePanelComponent,
51
+ ScheduledJobDialogComponent,
52
+ ],
53
+ }]
54
+ }] });
55
+ //# sourceMappingURL=scheduling.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduling.module.js","sourceRoot":"","sources":["../../src/lib/scheduling.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAExE,OAAO,EAAE,2BAA2B,EAAE,MAAM,8DAA8D,CAAC;AAC3G,OAAO,EAAE,4BAA4B,EAAE,MAAM,gEAAgE,CAAC;AAC9G,OAAO,EAAE,+BAA+B,EAAE,MAAM,mDAAmD,CAAC;AACpG,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;;AAuBvF,MAAM,OAAO,gBAAgB;uGAAhB,gBAAgB;wGAAhB,gBAAgB,iBAnBrB,2BAA2B;YAC3B,4BAA4B;YAC5B,+BAA+B;YAC/B,2BAA2B,aAG3B,YAAY;YACZ,WAAW;YACX,YAAY;YACZ,aAAa;YACb,mBAAmB,aAGnB,2BAA2B;YAC3B,4BAA4B;YAC5B,+BAA+B;YAC/B,2BAA2B;wGAGtB,gBAAgB,YAbrB,YAAY;YACZ,WAAW;YACX,YAAY;YACZ,aAAa;YACb,mBAAmB;;2FASd,gBAAgB;kBArB5B,QAAQ;mBAAC;oBACN,YAAY,EAAE;wBACV,2BAA2B;wBAC3B,4BAA4B;wBAC5B,+BAA+B;wBAC/B,2BAA2B;qBAC9B;oBACD,OAAO,EAAE;wBACL,YAAY;wBACZ,WAAW;wBACX,YAAY;wBACZ,aAAa;wBACb,mBAAmB;qBACtB;oBACD,OAAO,EAAE;wBACL,2BAA2B;wBAC3B,4BAA4B;wBAC5B,+BAA+B;wBAC/B,2BAA2B;qBAC9B;iBACJ","sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { DialogModule } from '@progress/kendo-angular-dialog';\nimport { ButtonsModule } from '@progress/kendo-angular-buttons';\nimport { SharedGenericModule } from '@memberjunction/ng-shared-generic';\n\nimport { ScheduledJobEditorComponent } from './panels/scheduled-job-editor/scheduled-job-editor.component';\nimport { ScheduledJobSummaryComponent } from './panels/scheduled-job-summary/scheduled-job-summary.component';\nimport { ScheduledJobSlidePanelComponent } from './slide-panel/scheduled-job-slide-panel.component';\nimport { ScheduledJobDialogComponent } from './dialogs/scheduled-job-dialog.component';\n\n@NgModule({\n declarations: [\n ScheduledJobEditorComponent,\n ScheduledJobSummaryComponent,\n ScheduledJobSlidePanelComponent,\n ScheduledJobDialogComponent,\n ],\n imports: [\n CommonModule,\n FormsModule,\n DialogModule,\n ButtonsModule,\n SharedGenericModule,\n ],\n exports: [\n ScheduledJobEditorComponent,\n ScheduledJobSummaryComponent,\n ScheduledJobSlidePanelComponent,\n ScheduledJobDialogComponent,\n ],\n})\nexport class SchedulingModule {}\n"]}
@@ -0,0 +1,32 @@
1
+ import { MJScheduledJobEntity, MJScheduledJobTypeEntity, MJScheduledJobRunEntity } from '@memberjunction/core-entities';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Service for loading and caching scheduled job data.
5
+ * Safe to call multiple times — caches job types after first load.
6
+ */
7
+ export declare class ScheduledJobService {
8
+ private _jobTypes;
9
+ private _jobTypesLoading;
10
+ /** Cached list of all scheduled job types */
11
+ get JobTypes(): MJScheduledJobTypeEntity[];
12
+ /**
13
+ * Load and cache all job types. Safe to call multiple times —
14
+ * returns cached data after first successful load.
15
+ */
16
+ LoadJobTypes(): Promise<MJScheduledJobTypeEntity[]>;
17
+ /**
18
+ * Load a single scheduled job by ID.
19
+ * Returns null if not found or load fails.
20
+ */
21
+ LoadJob(jobID: string): Promise<MJScheduledJobEntity | null>;
22
+ /**
23
+ * Load recent runs for a given job, ordered by most recent first.
24
+ */
25
+ LoadJobRuns(jobID: string, maxRows?: number): Promise<MJScheduledJobRunEntity[]>;
26
+ /** Clear cached data so next LoadJobTypes call re-fetches */
27
+ ClearCache(): void;
28
+ private fetchJobTypes;
29
+ static ɵfac: i0.ɵɵFactoryDeclaration<ScheduledJobService, never>;
30
+ static ɵprov: i0.ɵɵInjectableDeclaration<ScheduledJobService>;
31
+ }
32
+ //# sourceMappingURL=scheduled-job.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-job.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/scheduled-job.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;;AAExH;;;GAGG;AACH,qBACa,mBAAmB;IAC5B,OAAO,CAAC,SAAS,CAAkC;IACnD,OAAO,CAAC,gBAAgB,CAAoD;IAE5E,6CAA6C;IAC7C,IAAW,QAAQ,IAAI,wBAAwB,EAAE,CAEhD;IAED;;;OAGG;IACU,YAAY,IAAI,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAiBhE;;;OAGG;IACU,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAOzE;;OAEG;IACU,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,MAAW,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAYjG,6DAA6D;IACtD,UAAU,IAAI,IAAI;YAKX,aAAa;yCA9DlB,mBAAmB;6CAAnB,mBAAmB;CAwE/B"}
@@ -0,0 +1,81 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { Metadata, RunView } from '@memberjunction/core';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Service for loading and caching scheduled job data.
6
+ * Safe to call multiple times — caches job types after first load.
7
+ */
8
+ export class ScheduledJobService {
9
+ _jobTypes = [];
10
+ _jobTypesLoading = null;
11
+ /** Cached list of all scheduled job types */
12
+ get JobTypes() {
13
+ return this._jobTypes;
14
+ }
15
+ /**
16
+ * Load and cache all job types. Safe to call multiple times —
17
+ * returns cached data after first successful load.
18
+ */
19
+ async LoadJobTypes() {
20
+ if (this._jobTypes.length > 0) {
21
+ return this._jobTypes;
22
+ }
23
+ if (this._jobTypesLoading) {
24
+ return this._jobTypesLoading;
25
+ }
26
+ this._jobTypesLoading = this.fetchJobTypes();
27
+ try {
28
+ this._jobTypes = await this._jobTypesLoading;
29
+ return this._jobTypes;
30
+ }
31
+ finally {
32
+ this._jobTypesLoading = null;
33
+ }
34
+ }
35
+ /**
36
+ * Load a single scheduled job by ID.
37
+ * Returns null if not found or load fails.
38
+ */
39
+ async LoadJob(jobID) {
40
+ const md = new Metadata();
41
+ const job = await md.GetEntityObject('MJ: Scheduled Jobs');
42
+ const loaded = await job.Load(jobID);
43
+ return loaded ? job : null;
44
+ }
45
+ /**
46
+ * Load recent runs for a given job, ordered by most recent first.
47
+ */
48
+ async LoadJobRuns(jobID, maxRows = 10) {
49
+ const rv = new RunView();
50
+ const result = await rv.RunView({
51
+ EntityName: 'MJ: Scheduled Job Runs',
52
+ ExtraFilter: `ScheduledJobID='${jobID}'`,
53
+ OrderBy: 'StartedAt DESC',
54
+ MaxRows: maxRows,
55
+ ResultType: 'entity_object',
56
+ });
57
+ return result.Success ? result.Results : [];
58
+ }
59
+ /** Clear cached data so next LoadJobTypes call re-fetches */
60
+ ClearCache() {
61
+ this._jobTypes = [];
62
+ this._jobTypesLoading = null;
63
+ }
64
+ async fetchJobTypes() {
65
+ const rv = new RunView();
66
+ const result = await rv.RunView({
67
+ EntityName: 'MJ: Scheduled Job Types',
68
+ ExtraFilter: '',
69
+ OrderBy: 'Name',
70
+ ResultType: 'entity_object',
71
+ });
72
+ return result.Success ? result.Results : [];
73
+ }
74
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
75
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobService, providedIn: 'root' });
76
+ }
77
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobService, decorators: [{
78
+ type: Injectable,
79
+ args: [{ providedIn: 'root' }]
80
+ }] });
81
+ //# sourceMappingURL=scheduled-job.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-job.service.js","sourceRoot":"","sources":["../../../src/lib/services/scheduled-job.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;;AAGzD;;;GAGG;AAEH,MAAM,OAAO,mBAAmB;IACpB,SAAS,GAA+B,EAAE,CAAC;IAC3C,gBAAgB,GAA+C,IAAI,CAAC;IAE5E,6CAA6C;IAC7C,IAAW,QAAQ;QACf,OAAO,IAAI,CAAC,SAAS,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,YAAY;QACrB,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,SAAS,CAAC;QAC1B,CAAC;QACD,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,gBAAgB,CAAC;QACjC,CAAC;QAED,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAC7C,IAAI,CAAC;YACD,IAAI,CAAC,SAAS,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC;YAC7C,OAAO,IAAI,CAAC,SAAS,CAAC;QAC1B,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QACjC,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,OAAO,CAAC,KAAa;QAC9B,MAAM,EAAE,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,eAAe,CAAuB,oBAAoB,CAAC,CAAC;QACjF,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrC,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/B,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,WAAW,CAAC,KAAa,EAAE,UAAkB,EAAE;QACxD,MAAM,EAAE,GAAG,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAA0B;YACrD,UAAU,EAAE,wBAAwB;YACpC,WAAW,EAAE,mBAAmB,KAAK,GAAG;YACxC,OAAO,EAAE,gBAAgB;YACzB,OAAO,EAAE,OAAO;YAChB,UAAU,EAAE,eAAe;SAC9B,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAChD,CAAC;IAED,6DAA6D;IACtD,UAAU;QACb,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;QACpB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;IACjC,CAAC;IAEO,KAAK,CAAC,aAAa;QACvB,MAAM,EAAE,GAAG,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAA2B;YACtD,UAAU,EAAE,yBAAyB;YACrC,WAAW,EAAE,EAAE;YACf,OAAO,EAAE,MAAM;YACf,UAAU,EAAE,eAAe;SAC9B,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAChD,CAAC;uGAvEQ,mBAAmB;2GAAnB,mBAAmB,cADN,MAAM;;2FACnB,mBAAmB;kBAD/B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["import { Injectable } from '@angular/core';\nimport { Metadata, RunView } from '@memberjunction/core';\nimport { MJScheduledJobEntity, MJScheduledJobTypeEntity, MJScheduledJobRunEntity } from '@memberjunction/core-entities';\n\n/**\n * Service for loading and caching scheduled job data.\n * Safe to call multiple times — caches job types after first load.\n */\n@Injectable({ providedIn: 'root' })\nexport class ScheduledJobService {\n private _jobTypes: MJScheduledJobTypeEntity[] = [];\n private _jobTypesLoading: Promise<MJScheduledJobTypeEntity[]> | null = null;\n\n /** Cached list of all scheduled job types */\n public get JobTypes(): MJScheduledJobTypeEntity[] {\n return this._jobTypes;\n }\n\n /**\n * Load and cache all job types. Safe to call multiple times —\n * returns cached data after first successful load.\n */\n public async LoadJobTypes(): Promise<MJScheduledJobTypeEntity[]> {\n if (this._jobTypes.length > 0) {\n return this._jobTypes;\n }\n if (this._jobTypesLoading) {\n return this._jobTypesLoading;\n }\n\n this._jobTypesLoading = this.fetchJobTypes();\n try {\n this._jobTypes = await this._jobTypesLoading;\n return this._jobTypes;\n } finally {\n this._jobTypesLoading = null;\n }\n }\n\n /**\n * Load a single scheduled job by ID.\n * Returns null if not found or load fails.\n */\n public async LoadJob(jobID: string): Promise<MJScheduledJobEntity | null> {\n const md = new Metadata();\n const job = await md.GetEntityObject<MJScheduledJobEntity>('MJ: Scheduled Jobs');\n const loaded = await job.Load(jobID);\n return loaded ? job : null;\n }\n\n /**\n * Load recent runs for a given job, ordered by most recent first.\n */\n public async LoadJobRuns(jobID: string, maxRows: number = 10): Promise<MJScheduledJobRunEntity[]> {\n const rv = new RunView();\n const result = await rv.RunView<MJScheduledJobRunEntity>({\n EntityName: 'MJ: Scheduled Job Runs',\n ExtraFilter: `ScheduledJobID='${jobID}'`,\n OrderBy: 'StartedAt DESC',\n MaxRows: maxRows,\n ResultType: 'entity_object',\n });\n return result.Success ? result.Results : [];\n }\n\n /** Clear cached data so next LoadJobTypes call re-fetches */\n public ClearCache(): void {\n this._jobTypes = [];\n this._jobTypesLoading = null;\n }\n\n private async fetchJobTypes(): Promise<MJScheduledJobTypeEntity[]> {\n const rv = new RunView();\n const result = await rv.RunView<MJScheduledJobTypeEntity>({\n EntityName: 'MJ: Scheduled Job Types',\n ExtraFilter: '',\n OrderBy: 'Name',\n ResultType: 'entity_object',\n });\n return result.Success ? result.Results : [];\n }\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import { EventEmitter } from '@angular/core';
2
+ import { MJScheduledJobEntity } from '@memberjunction/core-entities';
3
+ import * as i0 from "@angular/core";
4
+ export declare class ScheduledJobSlidePanelComponent {
5
+ IsOpen: boolean;
6
+ ScheduledJobID: string | null;
7
+ JobTypeID: string | null;
8
+ DefaultConfiguration: string | null;
9
+ HideJobType: boolean;
10
+ Close: EventEmitter<void>;
11
+ Saved: EventEmitter<MJScheduledJobEntity>;
12
+ Deleted: EventEmitter<string>;
13
+ OnEscKey(): void;
14
+ ClosePanel(): void;
15
+ OnSaved(job: MJScheduledJobEntity): void;
16
+ OnDeleted(jobID: string): void;
17
+ get PanelTitle(): string;
18
+ static ɵfac: i0.ɵɵFactoryDeclaration<ScheduledJobSlidePanelComponent, never>;
19
+ static ɵcmp: i0.ɵɵComponentDeclaration<ScheduledJobSlidePanelComponent, "mj-scheduled-job-slide-panel", never, { "IsOpen": { "alias": "IsOpen"; "required": false; }; "ScheduledJobID": { "alias": "ScheduledJobID"; "required": false; }; "JobTypeID": { "alias": "JobTypeID"; "required": false; }; "DefaultConfiguration": { "alias": "DefaultConfiguration"; "required": false; }; "HideJobType": { "alias": "HideJobType"; "required": false; }; }, { "Close": "Close"; "Saved": "Saved"; "Deleted": "Deleted"; }, never, never, false, never>;
20
+ }
21
+ //# sourceMappingURL=scheduled-job-slide-panel.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-job-slide-panel.component.d.ts","sourceRoot":"","sources":["../../../src/lib/slide-panel/scheduled-job-slide-panel.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,YAAY,EAGf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAErE,qBAOa,+BAA+B;IAC/B,MAAM,UAAS;IACf,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IACrC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C,WAAW,UAAS;IAEnB,KAAK,qBAA4B;IACjC,KAAK,qCAA4C;IACjD,OAAO,uBAA8B;IAGxC,QAAQ,IAAI,IAAI;IAMhB,UAAU,IAAI,IAAI;IAIlB,OAAO,CAAC,GAAG,EAAE,oBAAoB,GAAG,IAAI;IAIxC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIrC,IAAW,UAAU,IAAI,MAAM,CAE9B;yCAhCQ,+BAA+B;2CAA/B,+BAA+B;CAiC3C"}
@@ -0,0 +1,56 @@
1
+ import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, HostListener, } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ import * as i1 from "../panels/scheduled-job-editor/scheduled-job-editor.component";
4
+ export class ScheduledJobSlidePanelComponent {
5
+ IsOpen = false;
6
+ ScheduledJobID = null;
7
+ JobTypeID = null;
8
+ DefaultConfiguration = null;
9
+ HideJobType = false;
10
+ Close = new EventEmitter();
11
+ Saved = new EventEmitter();
12
+ Deleted = new EventEmitter();
13
+ OnEscKey() {
14
+ if (this.IsOpen) {
15
+ this.ClosePanel();
16
+ }
17
+ }
18
+ ClosePanel() {
19
+ this.Close.emit();
20
+ }
21
+ OnSaved(job) {
22
+ this.Saved.emit(job);
23
+ }
24
+ OnDeleted(jobID) {
25
+ this.Deleted.emit(jobID);
26
+ }
27
+ get PanelTitle() {
28
+ return this.ScheduledJobID ? 'Edit Schedule' : 'Create Schedule';
29
+ }
30
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobSlidePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
31
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ScheduledJobSlidePanelComponent, isStandalone: false, selector: "mj-scheduled-job-slide-panel", inputs: { IsOpen: "IsOpen", ScheduledJobID: "ScheduledJobID", JobTypeID: "JobTypeID", DefaultConfiguration: "DefaultConfiguration", HideJobType: "HideJobType" }, outputs: { Close: "Close", Saved: "Saved", Deleted: "Deleted" }, host: { listeners: { "document:keydown.escape": "OnEscKey()" } }, ngImport: i0, template: "@if (IsOpen) {\n <div class=\"slide-panel-backdrop\" (click)=\"ClosePanel()\"></div>\n <div class=\"slide-panel\" [class.open]=\"IsOpen\">\n <div class=\"slide-panel-header\">\n <div class=\"header-title\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n <span>{{ PanelTitle }}</span>\n </div>\n <button class=\"close-btn\" (click)=\"ClosePanel()\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"slide-panel-body\">\n <mj-scheduled-job-editor\n [ScheduledJobID]=\"ScheduledJobID\"\n [JobTypeID]=\"JobTypeID\"\n [DefaultConfiguration]=\"DefaultConfiguration\"\n [HideJobType]=\"HideJobType\"\n (Saved)=\"OnSaved($event)\"\n (Deleted)=\"OnDeleted($event)\"\n (Cancelled)=\"ClosePanel()\">\n </mj-scheduled-job-editor>\n </div>\n </div>\n}\n", styles: [".slide-panel-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.35);\n z-index: 1000;\n animation: fadeIn 0.2s ease;\n}\n\n.slide-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 520px;\n max-width: 90vw;\n background: #f8fafc;\n z-index: 1001;\n display: flex;\n flex-direction: column;\n box-shadow: -4px 0 20px rgba(0, 0, 0, 0.12);\n transform: translateX(100%);\n transition: transform 0.25s ease;\n}\n\n.slide-panel.open {\n transform: translateX(0);\n}\n\n.slide-panel-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 20px;\n background: linear-gradient(135deg, #0d3b66 0%, #1a6fb5 100%);\n color: #fff;\n flex-shrink: 0;\n}\n\n.header-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 16px;\n font-weight: 600;\n}\n\n.close-btn {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.8);\n font-size: 18px;\n cursor: pointer;\n padding: 4px 8px;\n border-radius: 4px;\n transition: color 0.15s, background 0.15s;\n}\n\n.close-btn:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.15);\n}\n\n.slide-panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 0;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@media (max-width: 600px) {\n .slide-panel {\n width: 100vw;\n }\n}\n"], dependencies: [{ kind: "component", type: i1.ScheduledJobEditorComponent, selector: "mj-scheduled-job-editor", inputs: ["ScheduledJobID", "JobTypeID", "DefaultConfiguration", "HideJobType"], outputs: ["Saved", "Deleted", "Cancelled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
32
+ }
33
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobSlidePanelComponent, decorators: [{
34
+ type: Component,
35
+ args: [{ selector: 'mj-scheduled-job-slide-panel', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (IsOpen) {\n <div class=\"slide-panel-backdrop\" (click)=\"ClosePanel()\"></div>\n <div class=\"slide-panel\" [class.open]=\"IsOpen\">\n <div class=\"slide-panel-header\">\n <div class=\"header-title\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n <span>{{ PanelTitle }}</span>\n </div>\n <button class=\"close-btn\" (click)=\"ClosePanel()\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"slide-panel-body\">\n <mj-scheduled-job-editor\n [ScheduledJobID]=\"ScheduledJobID\"\n [JobTypeID]=\"JobTypeID\"\n [DefaultConfiguration]=\"DefaultConfiguration\"\n [HideJobType]=\"HideJobType\"\n (Saved)=\"OnSaved($event)\"\n (Deleted)=\"OnDeleted($event)\"\n (Cancelled)=\"ClosePanel()\">\n </mj-scheduled-job-editor>\n </div>\n </div>\n}\n", styles: [".slide-panel-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.35);\n z-index: 1000;\n animation: fadeIn 0.2s ease;\n}\n\n.slide-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 520px;\n max-width: 90vw;\n background: #f8fafc;\n z-index: 1001;\n display: flex;\n flex-direction: column;\n box-shadow: -4px 0 20px rgba(0, 0, 0, 0.12);\n transform: translateX(100%);\n transition: transform 0.25s ease;\n}\n\n.slide-panel.open {\n transform: translateX(0);\n}\n\n.slide-panel-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 20px;\n background: linear-gradient(135deg, #0d3b66 0%, #1a6fb5 100%);\n color: #fff;\n flex-shrink: 0;\n}\n\n.header-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 16px;\n font-weight: 600;\n}\n\n.close-btn {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.8);\n font-size: 18px;\n cursor: pointer;\n padding: 4px 8px;\n border-radius: 4px;\n transition: color 0.15s, background 0.15s;\n}\n\n.close-btn:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.15);\n}\n\n.slide-panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 0;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@media (max-width: 600px) {\n .slide-panel {\n width: 100vw;\n }\n}\n"] }]
36
+ }], propDecorators: { IsOpen: [{
37
+ type: Input
38
+ }], ScheduledJobID: [{
39
+ type: Input
40
+ }], JobTypeID: [{
41
+ type: Input
42
+ }], DefaultConfiguration: [{
43
+ type: Input
44
+ }], HideJobType: [{
45
+ type: Input
46
+ }], Close: [{
47
+ type: Output
48
+ }], Saved: [{
49
+ type: Output
50
+ }], Deleted: [{
51
+ type: Output
52
+ }], OnEscKey: [{
53
+ type: HostListener,
54
+ args: ['document:keydown.escape']
55
+ }] } });
56
+ //# sourceMappingURL=scheduled-job-slide-panel.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-job-slide-panel.component.js","sourceRoot":"","sources":["../../../src/lib/slide-panel/scheduled-job-slide-panel.component.ts","../../../src/lib/slide-panel/scheduled-job-slide-panel.component.html"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,YAAY,EACZ,uBAAuB,EACvB,YAAY,GACf,MAAM,eAAe,CAAC;;;AAUvB,MAAM,OAAO,+BAA+B;IAC/B,MAAM,GAAG,KAAK,CAAC;IACf,cAAc,GAAkB,IAAI,CAAC;IACrC,SAAS,GAAkB,IAAI,CAAC;IAChC,oBAAoB,GAAkB,IAAI,CAAC;IAC3C,WAAW,GAAG,KAAK,CAAC;IAEnB,KAAK,GAAG,IAAI,YAAY,EAAQ,CAAC;IACjC,KAAK,GAAG,IAAI,YAAY,EAAwB,CAAC;IACjD,OAAO,GAAG,IAAI,YAAY,EAAU,CAAC;IAGxC,QAAQ;QACX,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,UAAU,EAAE,CAAC;QACtB,CAAC;IACL,CAAC;IAEM,UAAU;QACb,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IAEM,OAAO,CAAC,GAAyB;QACpC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAEM,SAAS,CAAC,KAAa;QAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,IAAW,UAAU;QACjB,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACrE,CAAC;uGAhCQ,+BAA+B;2FAA/B,+BAA+B,8XCjB5C,8/BAyBA;;2FDRa,+BAA+B;kBAP3C,SAAS;+BACI,8BAA8B,cAC5B,KAAK,mBAGA,uBAAuB,CAAC,MAAM;;sBAG9C,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBAEL,MAAM;;sBACN,MAAM;;sBACN,MAAM;;sBAEN,YAAY;uBAAC,yBAAyB","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n HostListener,\n} from '@angular/core';\nimport { MJScheduledJobEntity } from '@memberjunction/core-entities';\n\n@Component({\n selector: 'mj-scheduled-job-slide-panel',\n standalone: false,\n templateUrl: './scheduled-job-slide-panel.component.html',\n styleUrls: ['./scheduled-job-slide-panel.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobSlidePanelComponent {\n @Input() IsOpen = false;\n @Input() ScheduledJobID: string | null = null;\n @Input() JobTypeID: string | null = null;\n @Input() DefaultConfiguration: string | null = null;\n @Input() HideJobType = false;\n\n @Output() Close = new EventEmitter<void>();\n @Output() Saved = new EventEmitter<MJScheduledJobEntity>();\n @Output() Deleted = new EventEmitter<string>();\n\n @HostListener('document:keydown.escape')\n public OnEscKey(): void {\n if (this.IsOpen) {\n this.ClosePanel();\n }\n }\n\n public ClosePanel(): void {\n this.Close.emit();\n }\n\n public OnSaved(job: MJScheduledJobEntity): void {\n this.Saved.emit(job);\n }\n\n public OnDeleted(jobID: string): void {\n this.Deleted.emit(jobID);\n }\n\n public get PanelTitle(): string {\n return this.ScheduledJobID ? 'Edit Schedule' : 'Create Schedule';\n }\n}\n","@if (IsOpen) {\n <div class=\"slide-panel-backdrop\" (click)=\"ClosePanel()\"></div>\n <div class=\"slide-panel\" [class.open]=\"IsOpen\">\n <div class=\"slide-panel-header\">\n <div class=\"header-title\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n <span>{{ PanelTitle }}</span>\n </div>\n <button class=\"close-btn\" (click)=\"ClosePanel()\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"slide-panel-body\">\n <mj-scheduled-job-editor\n [ScheduledJobID]=\"ScheduledJobID\"\n [JobTypeID]=\"JobTypeID\"\n [DefaultConfiguration]=\"DefaultConfiguration\"\n [HideJobType]=\"HideJobType\"\n (Saved)=\"OnSaved($event)\"\n (Deleted)=\"OnDeleted($event)\"\n (Cancelled)=\"ClosePanel()\">\n </mj-scheduled-job-editor>\n </div>\n </div>\n}\n"]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Public API Surface for @memberjunction/ng-scheduling
3
+ *
4
+ * Reusable Angular components for viewing and editing MemberJunction Scheduled Jobs.
5
+ * - Plain editor component for embedding in any layout
6
+ * - Slide-in panel for side-panel editing
7
+ * - Dialog component for modal editing
8
+ * - Summary component for compact status display
9
+ * - Service for data loading and caching
10
+ */
11
+ export * from './lib/scheduling.module';
12
+ export * from './lib/panels/scheduled-job-editor/scheduled-job-editor.component';
13
+ export * from './lib/panels/scheduled-job-summary/scheduled-job-summary.component';
14
+ export * from './lib/slide-panel/scheduled-job-slide-panel.component';
15
+ export * from './lib/dialogs/scheduled-job-dialog.component';
16
+ export * from './lib/services/scheduled-job.service';
17
+ //# sourceMappingURL=public-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,cAAc,yBAAyB,CAAC;AAGxC,cAAc,kEAAkE,CAAC;AACjF,cAAc,oEAAoE,CAAC;AAGnF,cAAc,uDAAuD,CAAC;AAGtE,cAAc,8CAA8C,CAAC;AAG7D,cAAc,sCAAsC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Public API Surface for @memberjunction/ng-scheduling
3
+ *
4
+ * Reusable Angular components for viewing and editing MemberJunction Scheduled Jobs.
5
+ * - Plain editor component for embedding in any layout
6
+ * - Slide-in panel for side-panel editing
7
+ * - Dialog component for modal editing
8
+ * - Summary component for compact status display
9
+ * - Service for data loading and caching
10
+ */
11
+ // Module
12
+ export * from './lib/scheduling.module';
13
+ // Panel components (embeddable)
14
+ export * from './lib/panels/scheduled-job-editor/scheduled-job-editor.component';
15
+ export * from './lib/panels/scheduled-job-summary/scheduled-job-summary.component';
16
+ // Slide-in panel
17
+ export * from './lib/slide-panel/scheduled-job-slide-panel.component';
18
+ // Dialog
19
+ export * from './lib/dialogs/scheduled-job-dialog.component';
20
+ // Services
21
+ export * from './lib/services/scheduled-job.service';
22
+ //# sourceMappingURL=public-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,SAAS;AACT,cAAc,yBAAyB,CAAC;AAExC,gCAAgC;AAChC,cAAc,kEAAkE,CAAC;AACjF,cAAc,oEAAoE,CAAC;AAEnF,iBAAiB;AACjB,cAAc,uDAAuD,CAAC;AAEtE,SAAS;AACT,cAAc,8CAA8C,CAAC;AAE7D,WAAW;AACX,cAAc,sCAAsC,CAAC","sourcesContent":["/**\n * Public API Surface for @memberjunction/ng-scheduling\n *\n * Reusable Angular components for viewing and editing MemberJunction Scheduled Jobs.\n * - Plain editor component for embedding in any layout\n * - Slide-in panel for side-panel editing\n * - Dialog component for modal editing\n * - Summary component for compact status display\n * - Service for data loading and caching\n */\n\n// Module\nexport * from './lib/scheduling.module';\n\n// Panel components (embeddable)\nexport * from './lib/panels/scheduled-job-editor/scheduled-job-editor.component';\nexport * from './lib/panels/scheduled-job-summary/scheduled-job-summary.component';\n\n// Slide-in panel\nexport * from './lib/slide-panel/scheduled-job-slide-panel.component';\n\n// Dialog\nexport * from './lib/dialogs/scheduled-job-dialog.component';\n\n// Services\nexport * from './lib/services/scheduled-job.service';\n"]}
package/package.json CHANGED
@@ -1,10 +1,49 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-scheduling",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @memberjunction/ng-scheduling",
3
+ "version": "5.13.0",
4
+ "description": "MemberJunction: Reusable Angular components for viewing and editing Scheduled Jobs - panels, slide-in, dialog, and service",
5
+ "main": "./dist/public-api.js",
6
+ "typings": "./dist/public-api.d.ts",
7
+ "files": [
8
+ "/dist"
9
+ ],
10
+ "scripts": {
11
+ "test": "echo \"No tests configured yet\"",
12
+ "build": "ngc",
13
+ "test:watch": "vitest"
14
+ },
5
15
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
16
+ "memberjunction",
17
+ "scheduling",
18
+ "angular",
19
+ "scheduled-jobs"
20
+ ],
21
+ "author": "",
22
+ "license": "ISC",
23
+ "devDependencies": {
24
+ "@angular/compiler": "21.1.3",
25
+ "@angular/compiler-cli": "21.1.3",
26
+ "vitest": "^4.0.18"
27
+ },
28
+ "peerDependencies": {
29
+ "@angular/common": "21.1.3",
30
+ "@angular/core": "21.1.3",
31
+ "@angular/forms": "21.1.3",
32
+ "@progress/kendo-angular-dialog": "22.0.1",
33
+ "@progress/kendo-angular-buttons": "22.0.1"
34
+ },
35
+ "dependencies": {
36
+ "@memberjunction/core": "5.13.0",
37
+ "@memberjunction/core-entities": "5.13.0",
38
+ "@memberjunction/global": "5.13.0",
39
+ "@memberjunction/ng-notifications": "5.13.0",
40
+ "@memberjunction/ng-shared-generic": "5.13.0",
41
+ "tslib": "^2.8.1",
42
+ "rxjs": "^7.8.2"
43
+ },
44
+ "sideEffects": false,
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/MemberJunction/MJ"
48
+ }
10
49
  }
package/README.md DELETED
@@ -1,45 +0,0 @@
1
- # @memberjunction/ng-scheduling
2
-
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
4
-
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
-
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/ng-scheduling`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**