@ieeesa-npm/groups 0.15.7 → 0.15.9
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.
- package/esm2022/lib/assets/constants.json +62 -0
- package/esm2022/lib/assets/roster-import-mocks.mjs +98 -0
- package/esm2022/lib/components/add-user-roster/add-user-roster.component.mjs +86 -35
- package/esm2022/lib/components/create-group/create-group.component.mjs +2 -2
- package/esm2022/lib/components/edit-user-roles/edit-user-roles.component.mjs +94 -51
- package/esm2022/lib/components/org-multi-select/org-multi-select.component.mjs +455 -0
- package/esm2022/lib/components/roster-import/roster-import.component.mjs +376 -0
- package/esm2022/lib/components/view-roster/view-roster.component.mjs +87 -7
- package/esm2022/lib/groups.component.mjs +58 -3
- package/esm2022/lib/models/group-function-type.model.mjs +2 -1
- package/esm2022/lib/models/roster-import.model.mjs +26 -0
- package/esm2022/public-api.mjs +3 -1
- package/fesm2022/ieeesa-npm-groups.mjs +1323 -94
- package/fesm2022/ieeesa-npm-groups.mjs.map +1 -1
- package/lib/assets/roster-import-mocks.d.ts +27 -0
- package/lib/components/add-user-roster/add-user-roster.component.d.ts +30 -0
- package/lib/components/edit-user-roles/edit-user-roles.component.d.ts +31 -0
- package/lib/components/org-multi-select/org-multi-select.component.d.ts +212 -0
- package/lib/components/roster-import/roster-import.component.d.ts +179 -0
- package/lib/components/view-roster/view-roster.component.d.ts +38 -3
- package/lib/groups.component.d.ts +8 -0
- package/lib/models/group-function-type.model.d.ts +2 -1
- package/lib/models/roster-import.model.d.ts +55 -0
- package/package.json +1 -1
- package/public-api.d.ts +2 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { RosterImportMockScenario, RosterUploadError, RosterUploadResponse } from '../models/roster-import.model';
|
|
2
|
+
/**
|
|
3
|
+
* Mock responses for the roster import flow, used to build and exercise the UI
|
|
4
|
+
* before the real backend API is available. Each scenario maps to one of the
|
|
5
|
+
* Figma result screens.
|
|
6
|
+
*
|
|
7
|
+
* Once the API is wired up, `RosterImportComponent` will call the real service
|
|
8
|
+
* method instead of `RosterImportMocks.getMockResponse(...)`.
|
|
9
|
+
*/
|
|
10
|
+
export declare class RosterImportMocks {
|
|
11
|
+
/** Row errors mirroring the Figma "All Rows are skipped" screen. */
|
|
12
|
+
static readonly mockRowErrors: RosterUploadError[];
|
|
13
|
+
/** All 30 rows added successfully (Figma: 30 / 0). */
|
|
14
|
+
static readonly allSuccess: RosterUploadResponse;
|
|
15
|
+
/** Partial import: some added, some skipped. */
|
|
16
|
+
static readonly partial: RosterUploadResponse;
|
|
17
|
+
/** Every row skipped (Figma: 0 / 30, "All Rows are skipped"). */
|
|
18
|
+
static readonly allSkipped: RosterUploadResponse;
|
|
19
|
+
/** Headers only, zero data rows (Figma: "No roster data found"). */
|
|
20
|
+
static readonly empty: RosterUploadResponse;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the mock upload response for a given scenario.
|
|
23
|
+
* @param {RosterImportMockScenario} scenario - The scenario to simulate.
|
|
24
|
+
* @return {RosterUploadResponse}
|
|
25
|
+
*/
|
|
26
|
+
static getMockResponse(scenario: RosterImportMockScenario): RosterUploadResponse;
|
|
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,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
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { EventEmitter, OnDestroy } from '@angular/core';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
import { RosterUploadError, RosterUploadResponse } from '../../models/roster-import.model';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
/** States of the roster import flow. */
|
|
6
|
+
type RosterImportState = 'upload' | 'processing' | 'result';
|
|
7
|
+
/**
|
|
8
|
+
* Roster Import dialog (MOCK data phase).
|
|
9
|
+
*
|
|
10
|
+
* Lets an admin upload a CSV/XLS/XLSX roster file, shows a processing progress
|
|
11
|
+
* 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.
|
|
15
|
+
*
|
|
16
|
+
* @export
|
|
17
|
+
* @class RosterImportComponent
|
|
18
|
+
*/
|
|
19
|
+
export declare class RosterImportComponent implements OnDestroy {
|
|
20
|
+
/** Carries the { groupId, groupName } payload from the opening dialog. */
|
|
21
|
+
componentData: any;
|
|
22
|
+
/** Emitted after an import that added at least one row (parent refreshes). */
|
|
23
|
+
submitFormResponse: EventEmitter<any>;
|
|
24
|
+
/** Emitted when the user cancels/closes without a successful add. */
|
|
25
|
+
formCancel: EventEmitter<void>;
|
|
26
|
+
destroy$: Subject<boolean>;
|
|
27
|
+
constants: any;
|
|
28
|
+
screenTexts: any;
|
|
29
|
+
/** Current state of the import flow. */
|
|
30
|
+
importState: RosterImportState;
|
|
31
|
+
/** The selected file. */
|
|
32
|
+
selectedFile: File | null;
|
|
33
|
+
/** File drag-over state. */
|
|
34
|
+
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;
|
|
39
|
+
/** Row-level errors for the completed job. */
|
|
40
|
+
jobErrors: RosterUploadError[];
|
|
41
|
+
/** Inline validation / API error message. */
|
|
42
|
+
errorMessage: string | null;
|
|
43
|
+
/**
|
|
44
|
+
* Info banner shown when the file has headers but no data rows
|
|
45
|
+
* (Figma: "No roster data found...").
|
|
46
|
+
*/
|
|
47
|
+
noDataMessage: string | null;
|
|
48
|
+
/** Maximum file size in bytes (5 MB). */
|
|
49
|
+
readonly MAX_FILE_SIZE = 5242880;
|
|
50
|
+
/** Accepted file extensions. */
|
|
51
|
+
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;
|
|
59
|
+
/** Required column headers for display. */
|
|
60
|
+
get requiredColumns(): string[];
|
|
61
|
+
/** Comma-separated accepted file types for the input element. */
|
|
62
|
+
get acceptedFileTypes(): string;
|
|
63
|
+
/**
|
|
64
|
+
* Cleans up subscriptions when the component is destroyed.
|
|
65
|
+
* @memberof RosterImportComponent
|
|
66
|
+
*/
|
|
67
|
+
ngOnDestroy(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Handles a file selected via the browse dialog.
|
|
70
|
+
* @param {Event} event - The change event from the hidden file input.
|
|
71
|
+
* @memberof RosterImportComponent
|
|
72
|
+
*/
|
|
73
|
+
onFileSelected(event: Event): void;
|
|
74
|
+
/**
|
|
75
|
+
* Handles a file dropped onto the dropzone.
|
|
76
|
+
* @param {DragEvent} event - The drop event.
|
|
77
|
+
* @memberof RosterImportComponent
|
|
78
|
+
*/
|
|
79
|
+
onFileDrop(event: DragEvent): void;
|
|
80
|
+
/**
|
|
81
|
+
* Handles drag-over on the dropzone.
|
|
82
|
+
* @param {DragEvent} event - The dragover event.
|
|
83
|
+
* @memberof RosterImportComponent
|
|
84
|
+
*/
|
|
85
|
+
onDragOver(event: DragEvent): void;
|
|
86
|
+
/**
|
|
87
|
+
* Handles drag-leave on the dropzone.
|
|
88
|
+
* @param {DragEvent} event - The dragleave event.
|
|
89
|
+
* @memberof RosterImportComponent
|
|
90
|
+
*/
|
|
91
|
+
onDragLeave(event: DragEvent): void;
|
|
92
|
+
/**
|
|
93
|
+
* Validates the file type and size, then either sets it or shows an error.
|
|
94
|
+
* @param {File} file - The candidate file.
|
|
95
|
+
* @memberof RosterImportComponent
|
|
96
|
+
*/
|
|
97
|
+
processFile(file: File): void;
|
|
98
|
+
/**
|
|
99
|
+
* Shows the single-file-only validation message.
|
|
100
|
+
* @memberof RosterImportComponent
|
|
101
|
+
*/
|
|
102
|
+
showSingleFileOnlyError(): void;
|
|
103
|
+
/**
|
|
104
|
+
* Removes the currently selected file and resets to the upload state.
|
|
105
|
+
* @memberof RosterImportComponent
|
|
106
|
+
*/
|
|
107
|
+
onRemoveFile(): void;
|
|
108
|
+
/**
|
|
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.
|
|
112
|
+
* @memberof RosterImportComponent
|
|
113
|
+
*/
|
|
114
|
+
onImport(): void;
|
|
115
|
+
/**
|
|
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.
|
|
120
|
+
* @memberof RosterImportComponent
|
|
121
|
+
*/
|
|
122
|
+
finalizeResult(body: RosterUploadResponse): void;
|
|
123
|
+
/**
|
|
124
|
+
* Whether the result state should show the summary cards (added / skipped).
|
|
125
|
+
* Hidden for the "no data records" case.
|
|
126
|
+
* @return {boolean}
|
|
127
|
+
* @memberof RosterImportComponent
|
|
128
|
+
*/
|
|
129
|
+
get showSummaryCards(): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Whether every processed row was skipped (drives the "All Rows are skipped"
|
|
132
|
+
* header on the errors card).
|
|
133
|
+
* @return {boolean}
|
|
134
|
+
* @memberof RosterImportComponent
|
|
135
|
+
*/
|
|
136
|
+
get allRowsSkipped(): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Builds a client-side CSV sample file from the required columns and triggers
|
|
139
|
+
* a browser download. (Client-side generation until the template API exists.)
|
|
140
|
+
* @memberof RosterImportComponent
|
|
141
|
+
*/
|
|
142
|
+
downloadSampleFile(): void;
|
|
143
|
+
/**
|
|
144
|
+
* Builds a CSV from the row-level errors and triggers a browser download so
|
|
145
|
+
* the user can correct and re-upload.
|
|
146
|
+
* @memberof RosterImportComponent
|
|
147
|
+
*/
|
|
148
|
+
downloadErrorLog(): void;
|
|
149
|
+
/**
|
|
150
|
+
* Escapes a value for safe inclusion in a CSV cell.
|
|
151
|
+
* @param {(string | number | undefined | null)} value - The raw cell value.
|
|
152
|
+
* @return {string} - the CSV-safe cell string.
|
|
153
|
+
* @memberof RosterImportComponent
|
|
154
|
+
*/
|
|
155
|
+
toCsvCell(value: string | number | undefined | null): string;
|
|
156
|
+
/**
|
|
157
|
+
* Closes the dialog. Emits submitFormResponse when at least one row was added
|
|
158
|
+
* so the parent refreshes; otherwise emits formCancel.
|
|
159
|
+
* @memberof RosterImportComponent
|
|
160
|
+
*/
|
|
161
|
+
onCancel(): void;
|
|
162
|
+
/**
|
|
163
|
+
* Triggers a browser download for a generated blob.
|
|
164
|
+
* @param {Blob} blob - The file content.
|
|
165
|
+
* @param {string} fileName - The suggested filename.
|
|
166
|
+
* @memberof RosterImportComponent
|
|
167
|
+
*/
|
|
168
|
+
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
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<RosterImportComponent, never>;
|
|
177
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<RosterImportComponent, "ieeesa-roster-import", never, { "componentData": { "alias": "componentData"; "required": false; }; }, { "submitFormResponse": "submitFormResponse"; "formCancel": "formCancel"; }, never, never, true, never>;
|
|
178
|
+
}
|
|
179
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { AfterViewInit, ChangeDetectorRef, OnDestroy, OnInit } from '@angular/core';
|
|
1
|
+
import { AfterViewInit, ChangeDetectorRef, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
|
2
2
|
import { Subject } from 'rxjs';
|
|
3
3
|
import { Sort } from '@angular/material/sort';
|
|
4
|
+
import { MatMenuTrigger } from '@angular/material/menu';
|
|
4
5
|
import { MatDialog } from '@angular/material/dialog';
|
|
5
6
|
import { TableColumnSchema, TableActions, AlignType, ChipOption, CheckboxOption, FiltersAndOptions, ExportFormat } from '@ieeesa-npm/presentation';
|
|
6
7
|
import { AlertsService, GlobalErrorHandlerService } from '@ieeesa-npm/utility';
|
|
@@ -26,6 +27,10 @@ export declare class ViewRosterComponent implements AfterViewInit, OnDestroy, On
|
|
|
26
27
|
private globalErrorHandlerService;
|
|
27
28
|
rosterExportService: RosterExportService;
|
|
28
29
|
destroy$: Subject<boolean>;
|
|
30
|
+
/** Trigger for the "Add" menu (Add Manually / Bulk Import). */
|
|
31
|
+
addMenuTrigger?: MatMenuTrigger;
|
|
32
|
+
/** Invisible anchor element the "Add" menu attaches to. */
|
|
33
|
+
addMenuAnchor?: ElementRef<HTMLElement>;
|
|
29
34
|
constants: any;
|
|
30
35
|
viewRosterConstants: any;
|
|
31
36
|
dialogRef: any;
|
|
@@ -149,10 +154,40 @@ export declare class ViewRosterComponent implements AfterViewInit, OnDestroy, On
|
|
|
149
154
|
*/
|
|
150
155
|
onEditUserRoles(selectedUserInfo: GroupUserInfo[]): void;
|
|
151
156
|
/**
|
|
152
|
-
*
|
|
157
|
+
* Handles a click on the data-table's add-user icon. The add icon lives inside
|
|
158
|
+
* the shared DataTableComponent and only emits (addRow), so we can't attach a
|
|
159
|
+
* menu trigger to it directly. Instead we position an invisible, fixed-position
|
|
160
|
+
* anchor at the add icon's on-screen location, then open the menu from that
|
|
161
|
+
* anchor — so the menu appears directly beneath the add icon (matching the
|
|
162
|
+
* Figma) rather than at the far-right export area.
|
|
153
163
|
* @memberof ViewRosterComponent
|
|
154
164
|
*/
|
|
155
|
-
|
|
165
|
+
onAddIconClick(): void;
|
|
166
|
+
/**
|
|
167
|
+
* Closes the "Add" menu on window resize/scroll. The menu is anchored to a
|
|
168
|
+
* point measured at open time, so a layout change (resize/scroll) would leave
|
|
169
|
+
* it detached from the add icon; closing it avoids showing it in a stale spot.
|
|
170
|
+
* The user can simply click the add icon again.
|
|
171
|
+
* @memberof ViewRosterComponent
|
|
172
|
+
*/
|
|
173
|
+
closeAddMenuOnViewportChange(): void;
|
|
174
|
+
/**
|
|
175
|
+
* Places the add-menu anchor at the add-user icon's bottom edge. Locates the
|
|
176
|
+
* icon by finding the add-user sprite (icon-add-user) within the roster's
|
|
177
|
+
* data-table action row.
|
|
178
|
+
* @memberof ViewRosterComponent
|
|
179
|
+
*/
|
|
180
|
+
private positionAddMenuAnchorAtAddIcon;
|
|
181
|
+
/**
|
|
182
|
+
* "Add Manually" menu option — opens the existing add-user-to-roster flow.
|
|
183
|
+
* @memberof ViewRosterComponent
|
|
184
|
+
*/
|
|
185
|
+
onAddManually(): void;
|
|
186
|
+
/**
|
|
187
|
+
* "Bulk Import" menu option — opens the bulk roster import dialog.
|
|
188
|
+
* @memberof ViewRosterComponent
|
|
189
|
+
*/
|
|
190
|
+
onImportRoster(): void;
|
|
156
191
|
/**
|
|
157
192
|
* Handles alerts for success, error, warning.
|
|
158
193
|
* @param {(string | string[])} message -error or warning message
|
|
@@ -109,6 +109,14 @@ export declare class GroupsComponent implements OnInit, OnDestroy {
|
|
|
109
109
|
* @memberof GroupsComponent
|
|
110
110
|
*/
|
|
111
111
|
openAddUserRosterModal(selectedGroupInfo: GroupInfo): void;
|
|
112
|
+
/**
|
|
113
|
+
* Sets the dialog options for the bulk roster import modal and opens it.
|
|
114
|
+
* Passes the selected group's id/name to the dynamically-instantiated
|
|
115
|
+
* RosterImportComponent via the ModalComponent dynamic-input pattern.
|
|
116
|
+
* @param {GroupInfo} selectedGroupInfo
|
|
117
|
+
* @memberof GroupsComponent
|
|
118
|
+
*/
|
|
119
|
+
openRosterImportModal(selectedGroupInfo: GroupInfo): void;
|
|
112
120
|
/**
|
|
113
121
|
* Sets the dialog options for users modal and calls openModal method.
|
|
114
122
|
* @param {GroupInfo} group
|
|
@@ -7,5 +7,6 @@ export declare enum GroupFunctionType {
|
|
|
7
7
|
SEARCH = "SEARCH",
|
|
8
8
|
REASSIGN_PARENT_GROUP = "REASSIGN_PARENT_GROUP",
|
|
9
9
|
EDIT_ROSTER_USER_ROLE = "EDIT_ROSTER_USER_ROLE",
|
|
10
|
-
ADD_USER_TO_ROSTER = "ADD_USER_TO_ROSTER"
|
|
10
|
+
ADD_USER_TO_ROSTER = "ADD_USER_TO_ROSTER",
|
|
11
|
+
IMPORT_ROSTER = "IMPORT_ROSTER"
|
|
11
12
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal status of a roster bulk-upload job.
|
|
3
|
+
*/
|
|
4
|
+
export declare enum RosterUploadStatus {
|
|
5
|
+
PROCESSING = "PROCESSING",
|
|
6
|
+
COMPLETED = "COMPLETED",
|
|
7
|
+
FAILED = "FAILED"
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A single row-level error from the roster import validation.
|
|
11
|
+
*/
|
|
12
|
+
export interface RosterUploadError {
|
|
13
|
+
/** 1-based row number in the uploaded file. */
|
|
14
|
+
rowNumber: number;
|
|
15
|
+
/** The field/column the error relates to (e.g. "Name", "Email"). */
|
|
16
|
+
field?: string;
|
|
17
|
+
/** The offending value, when relevant (e.g. an unrecognized role). */
|
|
18
|
+
value?: string;
|
|
19
|
+
/** Human-readable error message shown in the row-errors list. */
|
|
20
|
+
errorMessage: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Terminal result of a roster bulk-upload job.
|
|
24
|
+
*
|
|
25
|
+
* NOTE: This is a MOCK-friendly shape used to build the UI before the real API
|
|
26
|
+
* is available. It will be reconciled with the backend contract once shared.
|
|
27
|
+
*/
|
|
28
|
+
export interface RosterUploadResponse {
|
|
29
|
+
/** Job identifier (unused by the mock flow, kept for parity with the API). */
|
|
30
|
+
uploadJobId?: string;
|
|
31
|
+
/** Terminal job status. */
|
|
32
|
+
status: RosterUploadStatus;
|
|
33
|
+
/** Total data rows detected in the file (excludes the header row). */
|
|
34
|
+
totalRecords: number;
|
|
35
|
+
/** Rows successfully added/updated. */
|
|
36
|
+
successCount: number;
|
|
37
|
+
/** Rows skipped due to validation errors. */
|
|
38
|
+
failureCount: number;
|
|
39
|
+
/** Row-level errors (present when failureCount > 0). */
|
|
40
|
+
rowErrors: RosterUploadError[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The kind of mock scenario to simulate, so the UI can be exercised against
|
|
44
|
+
* each Figma state without a backend.
|
|
45
|
+
*/
|
|
46
|
+
export declare enum RosterImportMockScenario {
|
|
47
|
+
/** All rows imported successfully (Figma: 30 added / 0 skipped). */
|
|
48
|
+
ALL_SUCCESS = "ALL_SUCCESS",
|
|
49
|
+
/** Some rows imported, some skipped with errors (partial). */
|
|
50
|
+
PARTIAL = "PARTIAL",
|
|
51
|
+
/** Every row skipped with errors (Figma: 0 added / 30 skipped). */
|
|
52
|
+
ALL_SKIPPED = "ALL_SKIPPED",
|
|
53
|
+
/** File contained headers but zero data rows (Figma: "No roster data found"). */
|
|
54
|
+
EMPTY = "EMPTY"
|
|
55
|
+
}
|