@ieeesa-npm/groups 0.15.8 → 0.15.10

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 (41) hide show
  1. package/esm2022/lib/assets/constants.json +75 -4
  2. package/esm2022/lib/components/add-user-roster/add-user-roster.component.mjs +86 -35
  3. package/esm2022/lib/components/edit-user-roles/edit-user-roles.component.mjs +94 -51
  4. package/esm2022/lib/components/interest-form/interest-form.component.mjs +104 -0
  5. package/esm2022/lib/components/interest-form-management/interest-form-management.component.mjs +63 -0
  6. package/esm2022/lib/components/interested-rosters/interested-rosters.component.mjs +28 -0
  7. package/esm2022/lib/components/org-multi-select/org-multi-select.component.mjs +455 -0
  8. package/esm2022/lib/components/roster-import/roster-import.component.mjs +149 -82
  9. package/esm2022/lib/components/roster-import-history/roster-import-history.component.mjs +232 -0
  10. package/esm2022/lib/components/tree-view/tree-view.component.mjs +26 -5
  11. package/esm2022/lib/components/view-roster/view-roster.component.mjs +10 -3
  12. package/esm2022/lib/groups.component.mjs +60 -6
  13. package/esm2022/lib/models/group-action.model.mjs +2 -1
  14. package/esm2022/lib/models/group-function-type.model.mjs +2 -1
  15. package/esm2022/lib/models/permission-type.model.mjs +2 -1
  16. package/esm2022/lib/models/roster-import.model.mjs +34 -17
  17. package/esm2022/lib/services/roster-import-api.service.mjs +174 -0
  18. package/esm2022/public-api.mjs +6 -1
  19. package/fesm2022/ieeesa-npm-groups.mjs +1562 -308
  20. package/fesm2022/ieeesa-npm-groups.mjs.map +1 -1
  21. package/lib/components/add-user-roster/add-user-roster.component.d.ts +30 -0
  22. package/lib/components/edit-user-roles/edit-user-roles.component.d.ts +31 -0
  23. package/lib/components/interest-form/interest-form.component.d.ts +75 -0
  24. package/lib/components/interest-form-management/interest-form-management.component.d.ts +31 -0
  25. package/lib/components/interested-rosters/interested-rosters.component.d.ts +17 -0
  26. package/lib/components/org-multi-select/org-multi-select.component.d.ts +212 -0
  27. package/lib/components/roster-import/roster-import.component.d.ts +77 -38
  28. package/lib/components/roster-import-history/roster-import-history.component.d.ts +102 -0
  29. package/lib/components/tree-view/tree-view.component.d.ts +6 -3
  30. package/lib/components/view-roster/view-roster.component.d.ts +5 -0
  31. package/lib/groups.component.d.ts +17 -0
  32. package/lib/models/group-action.model.d.ts +2 -1
  33. package/lib/models/group-function-type.model.d.ts +2 -1
  34. package/lib/models/permission-type.model.d.ts +2 -1
  35. package/lib/models/roster-import.model.d.ts +104 -21
  36. package/lib/services/groups.service.d.ts +4 -0
  37. package/lib/services/roster-import-api.service.d.ts +108 -0
  38. package/package.json +1 -1
  39. package/public-api.d.ts +5 -0
  40. package/esm2022/lib/assets/roster-import-mocks.mjs +0 -98
  41. package/lib/assets/roster-import-mocks.d.ts +0 -27
@@ -45,6 +45,25 @@ export declare class AddUserRosterComponent implements OnInit, OnDestroy {
45
45
  isUserExist: boolean;
46
46
  selectedOrganizations: AutoCompleteOption[];
47
47
  timeOutId: ReturnType<typeof setTimeout>;
48
+ /**
49
+ * True when the selected group's roster type is INDIVIDUAL (the only roster
50
+ * type that shows the Representing Organization field).
51
+ * @readonly
52
+ * @memberof AddUserRosterComponent
53
+ */
54
+ get isIndividualRoster(): boolean;
55
+ /**
56
+ * True when the org field holds an uncommitted, invalid free-text token. Used
57
+ * to keep Save disabled even when a valid chip already exists.
58
+ */
59
+ hasInvalidOrgFreeText: boolean;
60
+ /**
61
+ * Handles the org field's invalid-free-text state and recomputes Save-button
62
+ * state so the form cannot be submitted while an invalid token remains.
63
+ * @param {boolean} isInvalid -whether an uncommitted invalid token is present.
64
+ * @memberof AddUserRosterComponent
65
+ */
66
+ onOrgInvalidFreeText(isInvalid: boolean): void;
48
67
  dynamicFormComponent: DynamicFormComponent;
49
68
  submitFormResponse: EventEmitter<any>;
50
69
  formCancel: EventEmitter<any>;
@@ -132,6 +151,17 @@ export declare class AddUserRosterComponent implements OnInit, OnDestroy {
132
151
  * @memberof AddUserRosterComponent
133
152
  */
134
153
  getAddUserFormGroupRef(form: FormGroup[]): void;
154
+ /**
155
+ * Registers the `representingOrg` reactive control on the dynamic form's
156
+ * FormGroup for INDIVIDUAL rosters. The field itself is rendered by the custom
157
+ * OrgMultiSelectComponent (not the dynamic form), but keeping this control on
158
+ * the same FormGroup — with the original validators — means all existing
159
+ * submit/Save-button logic that reads `form.get('representingOrg')` keeps
160
+ * working unchanged. Its value is the selected-organizations array, kept in
161
+ * sync by getSelectedOrganizations().
162
+ * @memberof AddUserRosterComponent
163
+ */
164
+ registerRepresentingOrgControl(): void;
135
165
  /**
136
166
  * Handles user role selection in add user form.
137
167
  * @param {CheckboxOption} event
@@ -40,6 +40,18 @@ export declare class EditUserRolesComponent implements OnInit, OnDestroy {
40
40
  groupRosterType: GroupRosterType;
41
41
  representingOrganizations: SelectOption[];
42
42
  orgSearchValue: string;
43
+ /**
44
+ * True when the selected group's roster type is INDIVIDUAL (the only roster
45
+ * type that shows the Representing Organization field).
46
+ * @readonly
47
+ * @memberof EditUserRolesComponent
48
+ */
49
+ get isIndividualRoster(): boolean;
50
+ /**
51
+ * True when the org field holds an uncommitted, invalid free-text token. Used
52
+ * to keep Save disabled even when a valid chip already exists.
53
+ */
54
+ hasInvalidOrgFreeText: boolean;
43
55
  dynamicFormComponent: DynamicFormComponent;
44
56
  submitFormResponse: EventEmitter<any>;
45
57
  formCancel: EventEmitter<any>;
@@ -66,6 +78,13 @@ export declare class EditUserRolesComponent implements OnInit, OnDestroy {
66
78
  * @memberof EditUserRolesComponent
67
79
  */
68
80
  getSelectedOrganizations(selectedOrg: AutoCompleteOption[]): void;
81
+ /**
82
+ * Handles the org field's invalid-free-text state. Disables Save while an
83
+ * uncommitted invalid token remains; re-evaluates when it clears.
84
+ * @param {boolean} isInvalid -whether an uncommitted invalid token is present.
85
+ * @memberof EditUserRolesComponent
86
+ */
87
+ onOrgInvalidFreeText(isInvalid: boolean): void;
69
88
  /**
70
89
  * Updates the edit user form group controls by adding fieldConfig at required index.
71
90
  * @param {number} indexPosition
@@ -103,6 +122,18 @@ export declare class EditUserRolesComponent implements OnInit, OnDestroy {
103
122
  * @memberof EditUserRolesComponent
104
123
  */
105
124
  getFormGroupReference(form: FormGroup[]): void;
125
+ /**
126
+ * Registers the `representingOrg` reactive control on the dynamic form's
127
+ * FormGroup for INDIVIDUAL rosters. The field itself is rendered by the custom
128
+ * OrgMultiSelectComponent (not the dynamic form), but keeping this control on
129
+ * the same FormGroup — with the original validators — means all existing
130
+ * submit/Save-button logic that reads `form.get('representingOrg')` keeps
131
+ * working unchanged. Its value is the selected-organizations array, kept in
132
+ * sync by getSelectedOrganizations(). Prefilled with the user's existing
133
+ * organizations on edit.
134
+ * @memberof EditUserRolesComponent
135
+ */
136
+ registerRepresentingOrgControl(): void;
106
137
  /**
107
138
  * Handles edit user role form change event.
108
139
  * @param {FormGroup[]} form
@@ -0,0 +1,75 @@
1
+ import { OnInit } from '@angular/core';
2
+ import { MatDialogRef } from '@angular/material/dialog';
3
+ import { RadioOption, SlideToggleOption } from '@ieeesa-npm/presentation';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Standard (locked) question row shown in the Interest Form tab.
7
+ * @export
8
+ * @interface StandardQuestionRow
9
+ */
10
+ export interface StandardQuestionRow {
11
+ id: number;
12
+ label: string;
13
+ tag: string;
14
+ }
15
+ /**
16
+ * "Interest Form" tab content of the Roster Interests dialog (SP3M-317). The
17
+ * group admin configures the interest form here: enable/disable the form, view
18
+ * the locked standard questions, choose the default membership level and manage
19
+ * custom questions. Interactive controls reuse the shared presentation
20
+ * components (slide toggle, radio). Persistence via a mocked service and the
21
+ * custom-question builder will be added in subsequent increments.
22
+ * @export
23
+ * @class InterestFormComponent
24
+ */
25
+ export declare class InterestFormComponent implements OnInit {
26
+ private dialogRef;
27
+ constants: any;
28
+ interestFormTexts: any;
29
+ changesNotice: any;
30
+ sectionTitle: any;
31
+ sectionDescription: any;
32
+ standardQuestionsTitle: any;
33
+ standardQuestionsHint: any;
34
+ defaultMembershipTitle: any;
35
+ defaultMembershipDescription: any;
36
+ customQuestionsTitle: any;
37
+ addQuestionLabel: any;
38
+ standardTag: any;
39
+ cancelLabel: any;
40
+ previewLabel: any;
41
+ enableFormToggle: SlideToggleOption;
42
+ standardQuestions: StandardQuestionRow[];
43
+ membershipLevels: RadioOption[];
44
+ isFormEnabled: boolean;
45
+ constructor(dialogRef: MatDialogRef<unknown>);
46
+ /**
47
+ * Initializes the toggle, locked standard questions and default membership
48
+ * level options from constants.
49
+ * @memberof InterestFormComponent
50
+ */
51
+ ngOnInit(): void;
52
+ /**
53
+ * Updates the form-enabled state when the toggle changes so the dependent
54
+ * sections show or hide accordingly.
55
+ * @param {SlideToggleOption} toggle
56
+ * @memberof InterestFormComponent
57
+ */
58
+ onEnableFormToggle(toggle: SlideToggleOption): void;
59
+ /**
60
+ * Closes the Roster Interests dialog (Cancel action).
61
+ * @memberof InterestFormComponent
62
+ */
63
+ onCancel(): void;
64
+ /**
65
+ * Returns the unique identifier for a standard question row for ngFor
66
+ * tracking.
67
+ * @param {number} index
68
+ * @param {StandardQuestionRow} question
69
+ * @return {number}
70
+ * @memberof InterestFormComponent
71
+ */
72
+ trackByFn(index: number, question: StandardQuestionRow): number;
73
+ static ɵfac: i0.ɵɵFactoryDeclaration<InterestFormComponent, [{ optional: true; }]>;
74
+ static ɵcmp: i0.ɵɵComponentDeclaration<InterestFormComponent, "ieeesa-interest-form", never, {}, {}, never, never, true, never>;
75
+ }
@@ -0,0 +1,31 @@
1
+ import { MatDialogRef } from '@angular/material/dialog';
2
+ import { Tab } from '@ieeesa-npm/presentation';
3
+ import { GroupInfo } from '../../models/group-info.model';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Roster Interests dialog host (SP3M-317). Opened from the "Manage Interest
7
+ * Form" action on a group row. Renders a two-tab layout using the shared
8
+ * TabsComponent: "Interested Rosters" (submissions list) and "Interest Form"
9
+ * (admin configuration). The group being managed is provided via the dialog
10
+ * data. The tab content components are rendered dynamically by TabsComponent
11
+ * through each tab's componentRef.
12
+ * @export
13
+ * @class InterestFormManagementComponent
14
+ */
15
+ export declare class InterestFormManagementComponent {
16
+ dialogRef: MatDialogRef<InterestFormManagementComponent>;
17
+ group: GroupInfo;
18
+ constants: any;
19
+ rosterInterestsTexts: any;
20
+ heading: any;
21
+ closeAriaLabel: any;
22
+ tabs: Tab[];
23
+ constructor(dialogRef: MatDialogRef<InterestFormManagementComponent>, group: GroupInfo);
24
+ /**
25
+ * Closes the Roster Interests dialog.
26
+ * @memberof InterestFormManagementComponent
27
+ */
28
+ onClose(): void;
29
+ static ɵfac: i0.ɵɵFactoryDeclaration<InterestFormManagementComponent, never>;
30
+ static ɵcmp: i0.ɵɵComponentDeclaration<InterestFormManagementComponent, "ieeesa-interest-form-management", never, {}, {}, never, never, true, never>;
31
+ }
@@ -0,0 +1,17 @@
1
+ import * as i0 from "@angular/core";
2
+ /**
3
+ * "Interested Rosters" tab content of the Roster Interests dialog (SP3M-317).
4
+ * Displays the list of users who expressed interest in the group. This is a
5
+ * placeholder shell for now; the submissions table will be implemented in a
6
+ * later increment once the backend is available (data will be mocked in the
7
+ * service layer meanwhile).
8
+ * @export
9
+ * @class InterestedRostersComponent
10
+ */
11
+ export declare class InterestedRostersComponent {
12
+ constants: any;
13
+ interestedRostersTexts: any;
14
+ placeholderMessage: any;
15
+ static ɵfac: i0.ɵɵFactoryDeclaration<InterestedRostersComponent, never>;
16
+ static ɵcmp: i0.ɵɵComponentDeclaration<InterestedRostersComponent, "ieeesa-interested-rosters", never, {}, {}, never, never, true, never>;
17
+ }
@@ -0,0 +1,212 @@
1
+ import { ElementRef, EventEmitter, OnDestroy, OnInit } from '@angular/core';
2
+ import { SelectOption } from '@ieeesa-npm/presentation';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * A self-contained multi-select organization field used inside the Add/Edit
6
+ * roster popups.
7
+ *
8
+ * It intentionally does NOT reuse the shared MultiSelectAutocompleteComponent
9
+ * (which is built on mat-autocomplete). Material's autocomplete closes its panel
10
+ * and overwrites the input on every option pick, which made multi-selection
11
+ * feel single-select and cleared the user's typed text. This component uses a
12
+ * fully-owned custom dropdown (<div> + checkboxes) so:
13
+ * - the panel stays open while the user ticks several results,
14
+ * - the typed search text is never overwritten by a pick,
15
+ * - pressing Enter adds the typed value as a free-text chip,
16
+ * - clicking outside clears any leftover typed text (old behavior),
17
+ * - selected values render as removable chips (old behavior).
18
+ *
19
+ * The parent supplies search results via {@link options} and reacts to
20
+ * {@link search} (debounced typed term) and {@link selectionChange}.
21
+ * @export
22
+ * @class OrgMultiSelectComponent
23
+ */
24
+ export declare class OrgMultiSelectComponent implements OnInit, OnDestroy {
25
+ /** Field label (e.g. "Representing Organization *"). */
26
+ label: string;
27
+ /** Placeholder for the input. */
28
+ placeholder: string;
29
+ /** Support/help text shown below the field. */
30
+ supportMessage: string;
31
+ /** aria-label for the input. */
32
+ ariaLabel: string;
33
+ /** Search results supplied by the parent (repopulated on each search). */
34
+ set options(value: SelectOption[]);
35
+ get options(): SelectOption[];
36
+ private _options;
37
+ /** Pre-selected options (edit prefill). Rendered as chips. */
38
+ set selectedOptions(value: SelectOption[]);
39
+ get selectedOptions(): SelectOption[];
40
+ private _selectedOptions;
41
+ /** Marks the field required (drives the required message + error border). */
42
+ required: boolean;
43
+ /**
44
+ * Icon URLs (same sprite refs the shared multi-select field used). Defaulted
45
+ * so the popups don't need to pass them; overridable via input if needed.
46
+ */
47
+ removeIconURL: string;
48
+ clearAllIconURL: string;
49
+ /** Emits the trimmed typed term (debounced) so the parent can call the API. */
50
+ search: EventEmitter<string>;
51
+ /** Emits the current selection array whenever it changes. */
52
+ selectionChange: EventEmitter<SelectOption[]>;
53
+ /** Emitted when the field is blurred (parent can re-evaluate validation). */
54
+ fieldBlur: EventEmitter<void>;
55
+ /**
56
+ * Emits true when the input holds an uncommitted free-text token that fails
57
+ * validation (too short / too long / invalid pattern), and false when the
58
+ * input is empty or the token is valid. The parent uses this to keep the Save
59
+ * button disabled while an invalid token remains — otherwise a previously
60
+ * added valid chip would keep the parent control valid and let the form submit
61
+ * with an invalid token still in the input.
62
+ */
63
+ invalidFreeText: EventEmitter<boolean>;
64
+ /** Tracks the last emitted invalid-token state to avoid duplicate emits. */
65
+ private hasInvalidFreeText;
66
+ searchInput: ElementRef<HTMLInputElement>;
67
+ constants: any;
68
+ addUserRosterTexts: any;
69
+ /** Whether the results dropdown is open. */
70
+ isDropdownOpen: boolean;
71
+ /** The current typed search text. */
72
+ searchText: string;
73
+ /** Inline validation message for the free-text (Enter) path. */
74
+ errorMessage: string;
75
+ /** Whether the field has been touched (to show required error after blur). */
76
+ isTouched: boolean;
77
+ /**
78
+ * Index of the keyboard-highlighted result row (-1 = none). Enables arrow-key
79
+ * navigation and Enter-to-toggle for keyboard users, mirroring the mat-option
80
+ * behavior this custom dropdown replaces.
81
+ */
82
+ activeOptionIndex: number;
83
+ /** Stable id prefix for option rows (used for aria-activedescendant). */
84
+ readonly optionIdPrefix = "ieeesa-org-option-";
85
+ private destroy$;
86
+ private searchDebounce?;
87
+ private blurTimer?;
88
+ private readonly debounceMs;
89
+ private readonly minCharToSearch;
90
+ private separatorKeys;
91
+ ngOnInit(): void;
92
+ /**
93
+ * Handles typing in the search input: tracks the text, opens/closes the
94
+ * dropdown and debounces the search emit to the parent.
95
+ * @param {Event} event -input event.
96
+ * @memberof OrgMultiSelectComponent
97
+ */
98
+ onInput(event: Event): void;
99
+ /**
100
+ * Emits the invalid-free-text state to the parent (deduped). True means an
101
+ * uncommitted token is present and Save should stay disabled.
102
+ * @param {boolean} isInvalid -whether an uncommitted token is present.
103
+ * @memberof OrgMultiSelectComponent
104
+ */
105
+ private emitInvalidState;
106
+ /**
107
+ * Opens the dropdown on focus only when there are results AND the input still
108
+ * holds a valid search term (at or above minCharToSearch). This prevents
109
+ * re-opening stale results — e.g. a late API response that populated options
110
+ * after the input was cleared — when the user simply refocuses the field.
111
+ * @memberof OrgMultiSelectComponent
112
+ */
113
+ onFocus(): void;
114
+ /**
115
+ * Called by the parent (via a setter re-assign) or internally after new
116
+ * options arrive so the dropdown opens to show them.
117
+ * @memberof OrgMultiSelectComponent
118
+ */
119
+ showResults(): void;
120
+ /**
121
+ * Handles keydown for the Enter/comma "add typed value" path.
122
+ * @param {KeyboardEvent} event -keyboard event.
123
+ * @memberof OrgMultiSelectComponent
124
+ */
125
+ onKeydown(event: KeyboardEvent): void;
126
+ /**
127
+ * Moves the keyboard highlight up/down the results list, wrapping at the ends.
128
+ * @param {number} delta -+1 to move down, -1 to move up.
129
+ * @memberof OrgMultiSelectComponent
130
+ */
131
+ private moveActiveOption;
132
+ /**
133
+ * Whether the given result index is the keyboard-highlighted row.
134
+ * @param {number} index -the option's index.
135
+ * @return {boolean}
136
+ * @memberof OrgMultiSelectComponent
137
+ */
138
+ isActiveOption(index: number): boolean;
139
+ /**
140
+ * Adds the typed free-text value as a selection (chip) after validating its
141
+ * length and pattern (parity with the previous chip-input behavior).
142
+ * @memberof OrgMultiSelectComponent
143
+ */
144
+ addTypedValue(): void;
145
+ /**
146
+ * Validates a free-text organization value against the shared min/max/pattern
147
+ * rules. Returns an error message when invalid, otherwise an empty string.
148
+ * @param {string} value -the typed value.
149
+ * @return {string}
150
+ * @memberof OrgMultiSelectComponent
151
+ */
152
+ validateFreeText(value: string): string;
153
+ /**
154
+ * Toggles selection of a result option. Keeps the dropdown open and the typed
155
+ * text intact so the user can keep selecting.
156
+ * @param {SelectOption} option -the clicked result option.
157
+ * @memberof OrgMultiSelectComponent
158
+ */
159
+ toggleOption(option: SelectOption): void;
160
+ /**
161
+ * Whether an option is currently selected (drives the checkbox checked state).
162
+ * @param {SelectOption} option -option to test.
163
+ * @return {boolean}
164
+ * @memberof OrgMultiSelectComponent
165
+ */
166
+ isSelected(option: SelectOption): boolean;
167
+ /**
168
+ * Removes a selected chip.
169
+ * @param {SelectOption} option -the option to remove.
170
+ * @memberof OrgMultiSelectComponent
171
+ */
172
+ removeOption(option: SelectOption): void;
173
+ /**
174
+ * Clears all selected chips.
175
+ * @memberof OrgMultiSelectComponent
176
+ */
177
+ clearAll(): void;
178
+ /**
179
+ * Clears the leftover typed text on blur (old behavior) and closes the
180
+ * dropdown. Deferred so a click on a dropdown option (which also blurs the
181
+ * input) is handled first.
182
+ * @memberof OrgMultiSelectComponent
183
+ */
184
+ onBlur(): void;
185
+ /**
186
+ * Whether the required error should show (touched, no selection).
187
+ * @return {boolean}
188
+ * @memberof OrgMultiSelectComponent
189
+ */
190
+ get showRequiredError(): boolean;
191
+ /**
192
+ * Clears the typed search text and hides the results.
193
+ * @memberof OrgMultiSelectComponent
194
+ */
195
+ private clearSearchText;
196
+ /**
197
+ * Emits the current selection to the parent.
198
+ * @memberof OrgMultiSelectComponent
199
+ */
200
+ private emitSelection;
201
+ /**
202
+ * TrackBy for both result and chip lists.
203
+ * @param {number} index -list index.
204
+ * @param {SelectOption} option -the option.
205
+ * @return {(string | number)}
206
+ * @memberof OrgMultiSelectComponent
207
+ */
208
+ trackByFn(index: number, option: SelectOption): string | number;
209
+ ngOnDestroy(): void;
210
+ static ɵfac: i0.ɵɵFactoryDeclaration<OrgMultiSelectComponent, never>;
211
+ static ɵcmp: i0.ɵɵComponentDeclaration<OrgMultiSelectComponent, "ieeesa-org-multi-select", never, { "label": { "alias": "label"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "supportMessage": { "alias": "supportMessage"; "required": false; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; }; "options": { "alias": "options"; "required": false; }; "selectedOptions": { "alias": "selectedOptions"; "required": false; }; "required": { "alias": "required"; "required": false; }; "removeIconURL": { "alias": "removeIconURL"; "required": false; }; "clearAllIconURL": { "alias": "clearAllIconURL"; "required": false; }; }, { "search": "search"; "selectionChange": "selectionChange"; "fieldBlur": "fieldBlur"; "invalidFreeText": "invalidFreeText"; }, never, never, true, never>;
212
+ }
@@ -1,22 +1,46 @@
1
- import { EventEmitter, OnDestroy } from '@angular/core';
1
+ import { EventEmitter, OnDestroy, OnInit } from '@angular/core';
2
2
  import { Subject } from 'rxjs';
3
- import { RosterUploadError, RosterUploadResponse } from '../../models/roster-import.model';
3
+ import { RosterImportJob } from '../../models/roster-import.model';
4
+ import { RosterImportApiService } from '../../services/roster-import-api.service';
5
+ import { GroupsService } from '../../services/groups.service';
4
6
  import * as i0 from "@angular/core";
5
7
  /** States of the roster import flow. */
6
8
  type RosterImportState = 'upload' | 'processing' | 'result';
7
9
  /**
8
- * Roster Import dialog (MOCK data phase).
10
+ * View-model for the result screen. Populated from the confirmed
11
+ * {@link RosterImportJob} returned by the backend.
12
+ */
13
+ interface RosterImportResult {
14
+ totalRecords: number;
15
+ successCount: number;
16
+ failureCount: number;
17
+ }
18
+ /** Row error shown on the result screen (mapped from the BE error report). */
19
+ interface RosterImportRowError {
20
+ rowNumber: number;
21
+ field?: string;
22
+ errorMessage: string;
23
+ }
24
+ /**
25
+ * Roster Import dialog.
9
26
  *
10
- * Lets an admin upload a CSV/XLS/XLSX roster file, shows a processing progress
27
+ * Lets an admin upload a CSV/XLSX roster file, shows a processing progress
11
28
  * bar, then a result summary (added / skipped) with per-row errors and an
12
- * error-log download. Mirrors the events-and-meetings bulk-import-participants
13
- * flow. The real backend is not wired yet — {@link onImport} resolves a mock
14
- * response after a short delay so every Figma state can be exercised.
29
+ * error-log download.
30
+ *
31
+ * Wired to the real backend roster-import state machine
32
+ * (sa-saapps-groups-api `RosterImportController`) via
33
+ * {@link RosterImportApiService}: on Import it runs
34
+ * upload → validate → confirm and renders the confirmed job's terminal counts
35
+ * and per-row errors. Server-side validation errors (1072 unsupported format,
36
+ * 1073 too large, 1074 row limit, 1075 invalid status) are surfaced inline.
15
37
  *
16
38
  * @export
17
39
  * @class RosterImportComponent
18
40
  */
19
- export declare class RosterImportComponent implements OnDestroy {
41
+ export declare class RosterImportComponent implements OnInit, OnDestroy {
42
+ private rosterImportApiService;
43
+ private groupService;
20
44
  /** Carries the { groupId, groupName } payload from the opening dialog. */
21
45
  componentData: any;
22
46
  /** Emitted after an import that added at least one row (parent refreshes). */
@@ -32,12 +56,10 @@ export declare class RosterImportComponent implements OnDestroy {
32
56
  selectedFile: File | null;
33
57
  /** File drag-over state. */
34
58
  isDragOver: boolean;
35
- /** Processing progress percentage (0–100) shown next to the progress bar. */
36
- progressValue: number;
37
- /** Terminal upload result (mock). */
38
- uploadResult: RosterUploadResponse | null;
59
+ /** Terminal upload result. */
60
+ uploadResult: RosterImportResult | null;
39
61
  /** Row-level errors for the completed job. */
40
- jobErrors: RosterUploadError[];
62
+ jobErrors: RosterImportRowError[];
41
63
  /** Inline validation / API error message. */
42
64
  errorMessage: string | null;
43
65
  /**
@@ -45,21 +67,24 @@ export declare class RosterImportComponent implements OnDestroy {
45
67
  * (Figma: "No roster data found...").
46
68
  */
47
69
  noDataMessage: string | null;
48
- /** Maximum file size in bytes (5 MB). */
49
- readonly MAX_FILE_SIZE = 5242880;
50
- /** Accepted file extensions. */
70
+ /** The import job id returned by upload (kept so cancel can clean it up). */
71
+ private importId;
72
+ /** Maximum file size in bytes (10 MB) — matches the BE 10MB limit. */
73
+ readonly MAX_FILE_SIZE = 10485760;
74
+ /** Accepted file extensions (must match the BE contract: only .csv / .xlsx). */
51
75
  readonly ACCEPTED_EXTENSIONS: string[];
52
- /**
53
- * MOCK ONLY: which scenario the next import simulates. Cycles through the
54
- * scenarios so QA can see every result state by importing repeatedly. Remove
55
- * when the real API is wired in.
56
- */
57
- private mockScenarios;
58
- private mockScenarioIndex;
76
+ constructor(rosterImportApiService: RosterImportApiService, groupService: GroupsService);
59
77
  /** Required column headers for display. */
60
78
  get requiredColumns(): string[];
61
79
  /** Comma-separated accepted file types for the input element. */
62
80
  get acceptedFileTypes(): string;
81
+ /** The target group id from the dialog payload (falls back to selection). */
82
+ private get groupId();
83
+ /**
84
+ * Sets the API base URL on the import service.
85
+ * @memberof RosterImportComponent
86
+ */
87
+ ngOnInit(): void;
63
88
  /**
64
89
  * Cleans up subscriptions when the component is destroyed.
65
90
  * @memberof RosterImportComponent
@@ -106,20 +131,41 @@ export declare class RosterImportComponent implements OnDestroy {
106
131
  */
107
132
  onRemoveFile(): void;
108
133
  /**
109
- * MOCK: "uploads" the file by simulating a short processing delay, then
110
- * resolves the next mock scenario. Replace the timer + mock lookup with the
111
- * real service call once the API is available.
134
+ * Runs the real import: upload → validate → confirm. The progress bar is
135
+ * shown in indeterminate mode while the chain is in flight, then the
136
+ * confirmed job's terminal counts + per-row errors drive the result screen.
112
137
  * @memberof RosterImportComponent
113
138
  */
114
139
  onImport(): void;
115
140
  /**
116
- * Stores the terminal result and moves to the result state. When the file
117
- * had no data rows, shows the "no roster data" info banner instead of the
118
- * summary cards.
119
- * @param {RosterUploadResponse} body - The terminal (mock) result.
141
+ * Maps a failed import call to an inline error message and returns the UI to
142
+ * the upload state so the user can retry. Known BE error codes (1072–1075)
143
+ * are mapped via constants; anything else uses the generic message.
144
+ * @param {*} error - The HttpErrorResponse.
120
145
  * @memberof RosterImportComponent
121
146
  */
122
- finalizeResult(body: RosterUploadResponse): void;
147
+ private handleImportError;
148
+ /**
149
+ * Stores the terminal result and moves to the result state. When the job had
150
+ * no data rows, shows the "no roster data" info banner instead of the summary
151
+ * cards.
152
+ * @param {RosterImportJob} job - The confirmed import job.
153
+ * @memberof RosterImportComponent
154
+ */
155
+ finalizeResult(job: RosterImportJob): void;
156
+ /**
157
+ * Parses the BE `errorReport` raw JSON string into row-error view models.
158
+ *
159
+ * The BE stores the report as a JSONB document wrapping the per-row entries:
160
+ * `{ "rows": [ { "row": 2, "field": "Email", "message": "..." } ] }`
161
+ * (serialized by its `ErrorReportWrapper`) and returns it verbatim via
162
+ * `@JsonRawValue`. So we read `parsed.rows`. A bare top-level array is also
163
+ * accepted as a fallback in case the contract is ever flattened.
164
+ * @param {(string | undefined)} errorReport - Raw JSON error report string.
165
+ * @return {RosterImportRowError[]}
166
+ * @memberof RosterImportComponent
167
+ */
168
+ private parseErrorReport;
123
169
  /**
124
170
  * Whether the result state should show the summary cards (added / skipped).
125
171
  * Hidden for the "no data records" case.
@@ -166,13 +212,6 @@ export declare class RosterImportComponent implements OnDestroy {
166
212
  * @memberof RosterImportComponent
167
213
  */
168
214
  private triggerDownload;
169
- /**
170
- * MOCK ONLY: returns the next scenario in the cycle so repeated imports show
171
- * every result state.
172
- * @return {RosterImportMockScenario}
173
- * @memberof RosterImportComponent
174
- */
175
- private nextMockScenario;
176
215
  static ɵfac: i0.ɵɵFactoryDeclaration<RosterImportComponent, never>;
177
216
  static ɵcmp: i0.ɵɵComponentDeclaration<RosterImportComponent, "ieeesa-roster-import", never, { "componentData": { "alias": "componentData"; "required": false; }; }, { "submitFormResponse": "submitFormResponse"; "formCancel": "formCancel"; }, never, never, true, never>;
178
217
  }