@memberjunction/ng-scheduling 6.1.4 → 6.2.0-edge.1

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.
@@ -233,7 +233,7 @@ export class ScheduledJobEditorComponent extends BaseAngularComponent {
233
233
  this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;
234
234
  }
235
235
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobEditorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
236
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", 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" }, usesInheritance: true, 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\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"RunImmediatelyIfNeverRun\" />\n Run immediately on first poll if never run\n </label>\n <div class=\"hint\">When checked, a job that has never run will execute on the next polling cycle instead of waiting for the next cron tick. Useful for newly-seeded jobs.</div>\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 var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n background: var(--mj-bg-surface);\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.section-title i {\n color: var(--mj-brand-primary);\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: var(--mj-text-secondary);\n margin-bottom: 4px;\n}\n\n.required {\n color: var(--mj-status-error);\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 var(--mj-border-strong);\n border-radius: 6px;\n font-size: 13px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\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: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\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: var(--mj-text-muted);\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: var(--mj-text-primary);\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: var(--mj-text-primary);\n}\n\n.stat-value.success {\n color: var(--mj-status-success);\n}\n\n.stat-value.error {\n color: var(--mj-status-error);\n}\n\n.stat-label {\n display: block;\n font-size: 11px;\n color: var(--mj-text-muted);\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 var(--mj-border-default);\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: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.btn-secondary {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: var(--mj-bg-surface-active);\n}\n\n.btn-danger {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.btn-danger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 20%, var(--mj-bg-surface));\n}\n\n.confirm-overlay {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.confirm-dialog {\n background: var(--mj-bg-surface);\n border-radius: 10px;\n padding: 24px;\n max-width: 360px;\n text-align: center;\n box-shadow: var(--mj-shadow-lg);\n}\n\n.confirm-dialog p {\n margin: 0 0 16px;\n color: var(--mj-text-primary);\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 });
236
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", 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" }, usesInheritance: true, 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\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"RunImmediatelyIfNeverRun\" />\n Run immediately on first poll if never run\n </label>\n <div class=\"hint\">When checked, a job that has never run will execute on the next polling cycle instead of waiting for the next cron tick. Useful for newly-seeded jobs.</div>\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 var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n background: var(--mj-bg-surface);\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.section-title i {\n color: var(--mj-brand-primary);\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: var(--mj-text-secondary);\n margin-bottom: 4px;\n}\n\n.required {\n color: var(--mj-status-error);\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 var(--mj-border-strong);\n border-radius: 6px;\n font-size: 13px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\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: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\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: var(--mj-text-muted);\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: var(--mj-text-primary);\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: var(--mj-text-primary);\n}\n\n.stat-value.success {\n color: var(--mj-status-success);\n}\n\n.stat-value.error {\n color: var(--mj-status-error);\n}\n\n.stat-label {\n display: block;\n font-size: 11px;\n color: var(--mj-text-muted);\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 var(--mj-border-default);\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: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.btn-secondary {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: var(--mj-bg-surface-active);\n}\n\n.btn-danger {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.btn-danger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 20%, var(--mj-bg-surface));\n}\n\n.confirm-overlay {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.confirm-dialog {\n background: var(--mj-bg-surface);\n border-radius: 10px;\n padding: 24px;\n max-width: 360px;\n text-align: center;\n box-shadow: var(--mj-shadow-lg);\n}\n\n.confirm-dialog p {\n margin: 0 0 16px;\n color: var(--mj-text-primary);\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", "text", "ShowText", "showText", "AnimationDuration", "animationDuration", "Size", "size", "TextColor", "textColor", "LogoColor", "logoColor", "LogoGradient", "logoGradient", "Animation", "animation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
237
237
  }
238
238
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobEditorComponent, decorators: [{
239
239
  type: Component,
@@ -69,7 +69,7 @@ export class ScheduledJobSummaryComponent {
69
69
  this.cdr.markForCheck();
70
70
  }
71
71
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobSummaryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
72
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", 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 <mj-empty-state class=\"summary-empty\" Size=\"compact\"\n Icon=\"fa-regular fa-calendar-xmark\"\n Title=\"No schedule configured\" />\n}\n", styles: [".summary-loading {\n display: flex;\n justify-content: center;\n padding: 16px;\n}\n\n.summary-card {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 14px;\n background: var(--mj-bg-surface);\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: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.summary-name i {\n color: var(--mj-brand-primary);\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: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.status-paused {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n\n.status-disabled {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.status-pending {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\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: var(--mj-text-secondary);\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 var(--mj-border-subtle);\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: var(--mj-text-primary);\n}\n\n.stat-num.success {\n color: var(--mj-status-success);\n}\n\n.stat-num.error {\n color: var(--mj-status-error);\n}\n\n.stat-lbl {\n display: block;\n font-size: 10px;\n color: var(--mj-text-muted);\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 var(--mj-border-subtle);\n}\n\n.edit-btn {\n background: none;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n padding: 6px 12px;\n font-size: 12px;\n color: var(--mj-brand-primary);\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: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.summary-empty {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n color: var(--mj-text-muted);\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"] }, { kind: "component", type: i2.MJEmptyStateComponent, selector: "mj-empty-state", inputs: ["Icon", "Title", "Message", "ActionText", "ActionIcon", "ActionVariant", "Variant", "Size", "Role"], outputs: ["Action"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
72
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", 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 <mj-empty-state class=\"summary-empty\" Size=\"compact\"\n Icon=\"fa-regular fa-calendar-xmark\"\n Title=\"No schedule configured\" />\n}\n", styles: [".summary-loading {\n display: flex;\n justify-content: center;\n padding: 16px;\n}\n\n.summary-card {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 14px;\n background: var(--mj-bg-surface);\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: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.summary-name i {\n color: var(--mj-brand-primary);\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: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.status-paused {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n\n.status-disabled {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.status-pending {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\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: var(--mj-text-secondary);\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 var(--mj-border-subtle);\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: var(--mj-text-primary);\n}\n\n.stat-num.success {\n color: var(--mj-status-success);\n}\n\n.stat-num.error {\n color: var(--mj-status-error);\n}\n\n.stat-lbl {\n display: block;\n font-size: 10px;\n color: var(--mj-text-muted);\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 var(--mj-border-subtle);\n}\n\n.edit-btn {\n background: none;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n padding: 6px 12px;\n font-size: 12px;\n color: var(--mj-brand-primary);\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: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.summary-empty {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n color: var(--mj-text-muted);\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", "text", "ShowText", "showText", "AnimationDuration", "animationDuration", "Size", "size", "TextColor", "textColor", "LogoColor", "logoColor", "LogoGradient", "logoGradient", "Animation", "animation"] }, { kind: "component", type: i2.MJEmptyStateComponent, selector: "mj-empty-state", inputs: ["Icon", "Title", "Message", "ActionText", "ActionIcon", "ActionVariant", "Variant", "Size", "Role"], outputs: ["Action"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
73
73
  }
74
74
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobSummaryComponent, decorators: [{
75
75
  type: Component,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-scheduling",
3
- "version": "6.1.4",
3
+ "version": "6.2.0-edge.1",
4
4
  "description": "MemberJunction: Reusable Angular components for viewing and editing Scheduled Jobs - panels, slide-in, dialog, and service",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -18,7 +18,7 @@
18
18
  "devDependencies": {
19
19
  "@angular/compiler": "21.2.22",
20
20
  "@angular/compiler-cli": "21.2.22",
21
- "@memberjunction/ng-test-utils": "6.1.4",
21
+ "@memberjunction/ng-test-utils": "6.2.0-edge.1",
22
22
  "vitest": "^4.0.18"
23
23
  },
24
24
  "peerDependencies": {
@@ -27,13 +27,13 @@
27
27
  "@angular/forms": "^21.2.22"
28
28
  },
29
29
  "dependencies": {
30
- "@memberjunction/core": "6.1.4",
31
- "@memberjunction/ng-base-types": "6.1.4",
32
- "@memberjunction/core-entities": "6.1.4",
33
- "@memberjunction/global": "6.1.4",
34
- "@memberjunction/ng-notifications": "6.1.4",
35
- "@memberjunction/ng-shared-generic": "6.1.4",
36
- "@memberjunction/ng-ui-components": "6.1.4",
30
+ "@memberjunction/core": "6.2.0-edge.1",
31
+ "@memberjunction/ng-base-types": "6.2.0-edge.1",
32
+ "@memberjunction/core-entities": "6.2.0-edge.1",
33
+ "@memberjunction/global": "6.2.0-edge.1",
34
+ "@memberjunction/ng-notifications": "6.2.0-edge.1",
35
+ "@memberjunction/ng-shared-generic": "6.2.0-edge.1",
36
+ "@memberjunction/ng-ui-components": "6.2.0-edge.1",
37
37
  "tslib": "^2.8.1",
38
38
  "rxjs": "^7.8.2"
39
39
  },