@ieeesa-npm/groups 0.15.17 → 0.15.19
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 +27 -1
- package/esm2022/lib/components/add-user-roster/add-user-roster.component.mjs +8 -10
- package/esm2022/lib/components/create-group/create-group.component.mjs +2 -2
- package/esm2022/lib/components/edit-user-roles/edit-user-roles.component.mjs +7 -55
- package/esm2022/lib/components/interest-form/interest-form.component.mjs +3 -3
- package/esm2022/lib/components/interest-form-management/interest-form-management.component.mjs +3 -3
- package/esm2022/lib/components/interest-responses/interest-responses.component.mjs +119 -0
- package/esm2022/lib/components/interested-rosters/interested-rosters.component.mjs +370 -15
- package/esm2022/lib/components/roster-import-history/roster-import-history.component.mjs +106 -8
- package/esm2022/lib/components/view-roster/view-roster.component.mjs +2 -2
- package/esm2022/lib/groups.component.mjs +58 -9
- package/esm2022/lib/models/interest-response.model.mjs +18 -0
- package/esm2022/lib/models/roster-import.model.mjs +1 -1
- package/esm2022/lib/services/groups.service.mjs +91 -7
- package/esm2022/public-api.mjs +3 -1
- package/fesm2022/ieeesa-npm-groups.mjs +821 -212
- package/fesm2022/ieeesa-npm-groups.mjs.map +1 -1
- package/lib/components/edit-user-roles/edit-user-roles.component.d.ts +0 -10
- package/lib/components/interest-responses/interest-responses.component.d.ts +80 -0
- package/lib/components/interested-rosters/interested-rosters.component.d.ts +143 -9
- package/lib/components/roster-import-history/roster-import-history.component.d.ts +42 -4
- package/lib/groups.component.d.ts +9 -4
- package/lib/models/interest-response.model.d.ts +209 -0
- package/lib/models/roster-import.model.d.ts +12 -0
- package/lib/services/groups.service.d.ts +66 -10
- package/package.json +1 -1
- package/public-api.d.ts +2 -0
- package/esm2022/lib/assets/country-calling-codes.mjs +0 -83
- package/lib/assets/country-calling-codes.d.ts +0 -14
|
@@ -113,16 +113,6 @@ export declare class EditUserRolesComponent implements OnInit, OnDestroy {
|
|
|
113
113
|
* @memberof EditUserRolesComponent
|
|
114
114
|
*/
|
|
115
115
|
getEditUserRolePayload(formData: any): AddOrEditUserPayload | AddOrEditUserIndividualRosterPayload;
|
|
116
|
-
/**
|
|
117
|
-
* Appends the optional contact fields (phone country code, phone number and
|
|
118
|
-
* address) to the payload. Reads from the combined PHONE control value
|
|
119
|
-
* ({ countryCode, phoneNumber }) and the address control. Sends the country
|
|
120
|
-
* code only when a number is present; omits empty values.
|
|
121
|
-
* @param {(AddOrEditUserPayload | AddOrEditUserIndividualRosterPayload)} payload
|
|
122
|
-
* @param {*} formData
|
|
123
|
-
* @memberof EditUserRolesComponent
|
|
124
|
-
*/
|
|
125
|
-
appendContactDetailsToPayload(payload: AddOrEditUserPayload | AddOrEditUserIndividualRosterPayload, formData: any): void;
|
|
126
116
|
/**
|
|
127
117
|
* Emits form cancel event.
|
|
128
118
|
* @memberof EditUserRolesComponent
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ChangeDetectorRef, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
+
import { GroupsService } from '../../services/groups.service';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* A single question/answer pair rendered in the interest responses list. Built
|
|
6
|
+
* from a backend {@link InterestFormResponse}; {@code value} is normalized to a
|
|
7
|
+
* display string (CHECKBOX arrays are joined).
|
|
8
|
+
*/
|
|
9
|
+
interface SubmissionAnswer {
|
|
10
|
+
label: string;
|
|
11
|
+
value: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Payload passed into {@link InterestResponsesComponent} by the host
|
|
15
|
+
* {@link ModalComponent} via the dynamic-input pattern.
|
|
16
|
+
*/
|
|
17
|
+
export interface InterestResponsesData {
|
|
18
|
+
/** The interest record whose stored responses are shown. */
|
|
19
|
+
interestId: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* "View Responses" content of the Roster Management "Interested Rosters" tab
|
|
23
|
+
* (SP3M-428). Rendered inside the shared {@link ModalComponent} (which supplies
|
|
24
|
+
* the title row and X close), it lists a requester's submitted interest-form
|
|
25
|
+
* answers as label/value pairs. Responses are loaded on init from
|
|
26
|
+
* {@link GroupsService.getInterestResponses} using the interest id supplied by
|
|
27
|
+
* the host modal. All display text is sourced from constants.json.
|
|
28
|
+
* @export
|
|
29
|
+
* @class InterestResponsesComponent
|
|
30
|
+
*/
|
|
31
|
+
export declare class InterestResponsesComponent implements OnInit, OnDestroy {
|
|
32
|
+
private groupsService;
|
|
33
|
+
private cdr;
|
|
34
|
+
/**
|
|
35
|
+
* Data supplied by the host {@link ModalComponent} via the dynamic-input
|
|
36
|
+
* pattern (`dataInputName: 'componentData'`): carries the interest id whose
|
|
37
|
+
* responses are loaded.
|
|
38
|
+
*/
|
|
39
|
+
componentData: InterestResponsesData | null;
|
|
40
|
+
constants: any;
|
|
41
|
+
texts: any;
|
|
42
|
+
submissionAnswers: SubmissionAnswer[];
|
|
43
|
+
isLoading: boolean;
|
|
44
|
+
errorMessage: string;
|
|
45
|
+
private readonly destroy$;
|
|
46
|
+
constructor(groupsService: GroupsService, cdr: ChangeDetectorRef);
|
|
47
|
+
/**
|
|
48
|
+
* Loads the interest-form responses for the supplied interest id on init.
|
|
49
|
+
* @memberof InterestResponsesComponent
|
|
50
|
+
*/
|
|
51
|
+
ngOnInit(): void;
|
|
52
|
+
ngOnDestroy(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Fetches and maps the interest-form responses into displayable
|
|
55
|
+
* question/answer pairs. Failures surface a user-facing error message and are
|
|
56
|
+
* delegated to GroupsService for alerting, mirroring the platform pattern.
|
|
57
|
+
* @memberof InterestResponsesComponent
|
|
58
|
+
*/
|
|
59
|
+
private loadResponses;
|
|
60
|
+
/**
|
|
61
|
+
* Maps backend responses to label/value pairs, joining multi-select
|
|
62
|
+
* (CHECKBOX) answers and normalizing missing values to an empty string.
|
|
63
|
+
* @param {InterestFormResponse[]} responses
|
|
64
|
+
* @return {SubmissionAnswer[]}
|
|
65
|
+
* @memberof InterestResponsesComponent
|
|
66
|
+
*/
|
|
67
|
+
private mapResponses;
|
|
68
|
+
/**
|
|
69
|
+
* Normalizes an answer value for display. CHECKBOX answers arrive as an array
|
|
70
|
+
* and are joined with commas; all other field types render their string
|
|
71
|
+
* value as-is.
|
|
72
|
+
* @param {InterestFormResponse} response
|
|
73
|
+
* @return {string}
|
|
74
|
+
* @memberof InterestResponsesComponent
|
|
75
|
+
*/
|
|
76
|
+
private formatAnswer;
|
|
77
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<InterestResponsesComponent, never>;
|
|
78
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<InterestResponsesComponent, "ieeesa-interest-responses", never, { "componentData": { "alias": "componentData"; "required": false; }; }, {}, never, never, true, never>;
|
|
79
|
+
}
|
|
80
|
+
export {};
|
|
@@ -1,17 +1,151 @@
|
|
|
1
|
+
import { ChangeDetectorRef, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
+
import { MatDialog } from '@angular/material/dialog';
|
|
3
|
+
import { PageEvent } from '@angular/material/paginator';
|
|
4
|
+
import { Sort } from '@angular/material/sort';
|
|
5
|
+
import { SelectOption } from '@ieeesa-npm/models';
|
|
6
|
+
import { PaginatorConfig, TableActions, TableColumnSchema } from '@ieeesa-npm/presentation';
|
|
7
|
+
import { GroupInfo } from '../../models/group-info.model';
|
|
8
|
+
import { InterestedRoster } from '../../models/interest-response.model';
|
|
9
|
+
import { GroupsService } from '../../services/groups.service';
|
|
1
10
|
import * as i0 from "@angular/core";
|
|
11
|
+
type InterestedRosterColumnSchema = TableColumnSchema & {
|
|
12
|
+
ariaLabel?: (row: InterestedRoster) => string;
|
|
13
|
+
};
|
|
2
14
|
/**
|
|
3
|
-
* "Interested Rosters" tab
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
15
|
+
* "Interested Rosters" tab of the Roster Interests dialog (SP3M-428). Lists the
|
|
16
|
+
* users who expressed interest in the group and lets a Group Admin review each
|
|
17
|
+
* requester's submitted responses and set/override their membership level from
|
|
18
|
+
* the Roster Management screen.
|
|
19
|
+
*
|
|
20
|
+
* The list is rendered through the shared {@link DataTableComponent}: the
|
|
21
|
+
* Name/Email/Affiliations/Submitted On columns are plain TEXT, Member Level is
|
|
22
|
+
* the reusable table's DROPDOWN column (an inline {@code ieeesa-select}) and
|
|
23
|
+
* Form is a LINK that opens the "View Responses" dialog. The Member Level
|
|
24
|
+
* options are sourced from the roles API
|
|
25
|
+
* ({@link GroupsService.getGroupTypeRoles}) filtered to the "Member Level" role
|
|
26
|
+
* type. Interested-roster rows are loaded from
|
|
27
|
+
* {@link GroupsService.getInterestResponsesByGroup} and each requester's stored
|
|
28
|
+
* form responses are loaded on demand from
|
|
29
|
+
* {@link GroupsService.getInterestResponses} using the row's `interestId`. All
|
|
30
|
+
* display text is sourced from constants.json.
|
|
8
31
|
* @export
|
|
9
32
|
* @class InterestedRostersComponent
|
|
10
33
|
*/
|
|
11
|
-
export declare class InterestedRostersComponent {
|
|
34
|
+
export declare class InterestedRostersComponent implements OnInit, OnDestroy {
|
|
35
|
+
private dialog;
|
|
36
|
+
private groupsService;
|
|
37
|
+
private cdr;
|
|
38
|
+
group: GroupInfo | null;
|
|
12
39
|
constants: any;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
40
|
+
texts: any;
|
|
41
|
+
private destroy$;
|
|
42
|
+
/**
|
|
43
|
+
* Member-level dropdown options, sourced from the roles API
|
|
44
|
+
* ({@code GET /groups/config/rolesByGroupType/{groupType}}) filtered to the "Member Level" role type.
|
|
45
|
+
* Each option is {@code { id: roleId, name: roleName }}.
|
|
46
|
+
*/
|
|
47
|
+
memberLevelOptions: SelectOption[];
|
|
48
|
+
rosters: InterestedRoster[];
|
|
49
|
+
tableData: InterestedRoster[];
|
|
50
|
+
columnSchema: InterestedRosterColumnSchema[];
|
|
51
|
+
/** Total interested-roster rows reported by the backend (for paging/counts). */
|
|
52
|
+
totalRecords: number;
|
|
53
|
+
paginatorConfig: PaginatorConfig;
|
|
54
|
+
pageIndex: number;
|
|
55
|
+
defaultSortedColumn: string;
|
|
56
|
+
defaultColumnSortDirection: Sort['direction'];
|
|
57
|
+
private sortedBy;
|
|
58
|
+
private sortedOrderBy;
|
|
59
|
+
/** Read-only table — no search / filter / add / edit / delete actions. */
|
|
60
|
+
tableActions: TableActions;
|
|
61
|
+
isLoading: boolean;
|
|
62
|
+
constructor(dialog: MatDialog, groupsService: GroupsService, cdr: ChangeDetectorRef, group: GroupInfo | null);
|
|
63
|
+
/**
|
|
64
|
+
* Builds the reusable-table column schema and loads the interested-roster
|
|
65
|
+
* list for the current group from the backend.
|
|
66
|
+
* @memberof InterestedRostersComponent
|
|
67
|
+
*/
|
|
68
|
+
ngOnInit(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Cleans up in-flight subscriptions when the component is destroyed.
|
|
71
|
+
* @memberof InterestedRostersComponent
|
|
72
|
+
*/
|
|
73
|
+
ngOnDestroy(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Resolves the current group id from the dialog data (falling back to the
|
|
76
|
+
* service's selected group) and loads the interested-roster rows for it. Maps
|
|
77
|
+
* each backend {@link GroupInterestResponseView} onto the flattened
|
|
78
|
+
* {@link InterestedRoster} shape the reusable table binds to. On failure the
|
|
79
|
+
* table is cleared and the error is delegated to GroupsService for alerting.
|
|
80
|
+
* @memberof InterestedRostersComponent
|
|
81
|
+
*/
|
|
82
|
+
private loadInterestedRosters;
|
|
83
|
+
onPaginationChange(event: PageEvent): void;
|
|
84
|
+
onSortChange(event: Sort): void;
|
|
85
|
+
/**
|
|
86
|
+
* Maps a backend {@link GroupInterestResponseView} to the flattened
|
|
87
|
+
* {@link InterestedRoster} row the table renders: affiliation org names are
|
|
88
|
+
* joined into the affiliations list, the assigned membership level (if any)
|
|
89
|
+
* becomes the role name — which the Member Level dropdown binds as its
|
|
90
|
+
* pre-selected value — the submitted timestamp is formatted for display, and
|
|
91
|
+
* the interest id is carried through for the responses dialog and the level
|
|
92
|
+
* override.
|
|
93
|
+
* @param {GroupInterestResponseView} view
|
|
94
|
+
* @return {InterestedRoster}
|
|
95
|
+
* @memberof InterestedRostersComponent
|
|
96
|
+
*/
|
|
97
|
+
private mapToRoster;
|
|
98
|
+
/**
|
|
99
|
+
* Formats the backend submitted timestamp (ISO date string) for display,
|
|
100
|
+
* falling back to the raw value when it is not a parseable date.
|
|
101
|
+
* @param {string} submittedOn
|
|
102
|
+
* @return {string}
|
|
103
|
+
* @memberof InterestedRostersComponent
|
|
104
|
+
*/
|
|
105
|
+
private formatSubmittedOn;
|
|
106
|
+
/**
|
|
107
|
+
* Loads the Member Level dropdown options from the roles API
|
|
108
|
+
* ({@code GET /groups/config/rolesByGroupType/{groupType}}), keeping only roles whose
|
|
109
|
+
* {@code roleTypeName} is "Member Level" and mapping each to a
|
|
110
|
+
* {@link SelectOption} ({@code { id: roleId, name: roleName }}).
|
|
111
|
+
* @memberof InterestedRostersComponent
|
|
112
|
+
*/
|
|
113
|
+
private loadMemberLevelOptions;
|
|
114
|
+
/**
|
|
115
|
+
* Persists the membership-level override when the admin picks a value in a
|
|
116
|
+
* row's Member Level dropdown (bound to the reusable table's
|
|
117
|
+
* `rowDropdownChange` output). Builds the request from the dialog's current
|
|
118
|
+
* group id, the row's user id and the selected option's id (the role id), and
|
|
119
|
+
* posts it to the backend. The row's displayed level is updated optimistically
|
|
120
|
+
* and reverted if the save fails.
|
|
121
|
+
* @param {{ row: InterestedRoster; option: SelectOption }} event
|
|
122
|
+
* @memberof InterestedRostersComponent
|
|
123
|
+
*/
|
|
124
|
+
onMemberLevelChange(event: {
|
|
125
|
+
row: InterestedRoster;
|
|
126
|
+
option: SelectOption;
|
|
127
|
+
}): void;
|
|
128
|
+
/**
|
|
129
|
+
* Opens the "View Responses" dialog for a roster row (bound to the reusable
|
|
130
|
+
* table's LINK output) using the shared {@link ModalComponent}. The modal
|
|
131
|
+
* supplies the title row and X close; the requester's stored interest-form
|
|
132
|
+
* responses are rendered by {@link InterestResponsesComponent}, which loads
|
|
133
|
+
* them from the backend using the row's {@code interestId} passed via the
|
|
134
|
+
* ModalComponent dynamic-input pattern.
|
|
135
|
+
* @param {InterestedRoster} row
|
|
136
|
+
* @memberof InterestedRostersComponent
|
|
137
|
+
*/
|
|
138
|
+
onFormLinkClick(row: InterestedRoster): void;
|
|
139
|
+
/**
|
|
140
|
+
* Defines the reusable-table columns for the Interested Rosters list. Every
|
|
141
|
+
* `cellData`/`selectedOption` is a `(row) => ...` accessor, matching the
|
|
142
|
+
* shared table's contract. Member Level is the DROPDOWN column seeded with the
|
|
143
|
+
* configured levels plus "None"; Form is a LINK that opens the responses
|
|
144
|
+
* dialog.
|
|
145
|
+
* @memberof InterestedRostersComponent
|
|
146
|
+
*/
|
|
147
|
+
private buildColumnSchema;
|
|
148
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<InterestedRostersComponent, [null, null, null, { optional: true; }]>;
|
|
16
149
|
static ɵcmp: i0.ɵɵComponentDeclaration<InterestedRostersComponent, "ieeesa-interested-rosters", never, {}, {}, never, never, true, never>;
|
|
17
150
|
}
|
|
151
|
+
export {};
|
|
@@ -24,10 +24,11 @@ interface RosterImportHistoryRow {
|
|
|
24
24
|
* Data comes from the real BE endpoint via {@link RosterImportApiService}:
|
|
25
25
|
* `GET /v1/groups/roster/import/history/{groupId}`. The response is a list of
|
|
26
26
|
* `RosterImportJob` whose JSON view exposes `fileName`, `successRows`,
|
|
27
|
-
* `failedRows`, `status` and ids
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
27
|
+
* `failedRows`, `status` and ids. The "Timestamp" and "Person Imported" columns
|
|
28
|
+
* map to `createdOn` / `createdBy`; these are currently `@JsonIgnore` on the BE
|
|
29
|
+
* entity, so until the BE exposes them the columns show a placeholder, and they
|
|
30
|
+
* populate automatically once the response carries the fields. "Target Group"
|
|
31
|
+
* uses the current group's name (history is scoped to one group).
|
|
31
32
|
*
|
|
32
33
|
* @export
|
|
33
34
|
* @class RosterImportHistoryComponent
|
|
@@ -83,6 +84,43 @@ export declare class RosterImportHistoryComponent implements OnInit, OnDestroy {
|
|
|
83
84
|
* @memberof RosterImportHistoryComponent
|
|
84
85
|
*/
|
|
85
86
|
private mapJobToRow;
|
|
87
|
+
/**
|
|
88
|
+
* Formats the BE `createdOn` timestamp for display, converting from UTC to the
|
|
89
|
+
* browser's local timezone.
|
|
90
|
+
*
|
|
91
|
+
* The API sends a UTC wall-clock string in `dd/MM/yyyy HH:mm:ss` with NO zone
|
|
92
|
+
* marker, so it must be parsed EXPLICITLY as UTC (otherwise luxon/JS would
|
|
93
|
+
* assume local time and shift it incorrectly), then rendered in the system
|
|
94
|
+
* (browser) zone via toLocal(). Uses luxon per the project's date standards
|
|
95
|
+
* (moment is legacy — not used for new code).
|
|
96
|
+
* @param {string} value - The raw UTC createdOn value from the BE.
|
|
97
|
+
* @return {string} the localized display string, or the placeholder when
|
|
98
|
+
* blank/invalid.
|
|
99
|
+
* @memberof RosterImportHistoryComponent
|
|
100
|
+
*/
|
|
101
|
+
private formatTimestamp;
|
|
102
|
+
/**
|
|
103
|
+
* Parses a BE timestamp string into a luxon {@link DateTime}, tolerant of the
|
|
104
|
+
* several shapes the backend may send so the UI renders correctly regardless
|
|
105
|
+
* of which format is used.
|
|
106
|
+
*
|
|
107
|
+
* The agreed/expected BE format is a UTC wall-clock string `dd/MM/yyyy HH:mm:ss`
|
|
108
|
+
* with NO zone marker; it is tried FIRST and parsed explicitly in UTC. If the
|
|
109
|
+
* BE instead sends a different shape, these fallbacks are tried in order:
|
|
110
|
+
* 1. `dd/MM/yyyy HH:mm:ss` (UTC) — the expected/agreed format
|
|
111
|
+
* 2. ISO-8601 (e.g. 2026-10-09T14:23:05Z or with offset) — zone from the string,
|
|
112
|
+
* or assumed UTC when the string carries none
|
|
113
|
+
* 3. SQL datetime `yyyy-MM-dd HH:mm:ss` (UTC)
|
|
114
|
+
* 4. `dd/MM/yyyy HH:mm` (UTC, no seconds)
|
|
115
|
+
* 5. RFC-2822 / HTTP date
|
|
116
|
+
* 6. native JS Date (last resort) — assumed UTC
|
|
117
|
+
* The first format that yields a valid DateTime wins. Returns null when none
|
|
118
|
+
* parse.
|
|
119
|
+
* @param {string} value - The raw timestamp string from the BE.
|
|
120
|
+
* @return {(DateTime | null)}
|
|
121
|
+
* @memberof RosterImportHistoryComponent
|
|
122
|
+
*/
|
|
123
|
+
private parseUtcTimestamp;
|
|
86
124
|
/**
|
|
87
125
|
* Maps a failed history request to a user-facing inline error message.
|
|
88
126
|
* A 403 gets the permission-specific message; other errors get the generic
|
|
@@ -6,7 +6,7 @@ import { Subject } from 'rxjs';
|
|
|
6
6
|
import { GroupInfo } from './models/group-info.model';
|
|
7
7
|
import { SelectOption, IconButton, AlignType, SearchType, BreakpointObserverService } from '@ieeesa-npm/presentation';
|
|
8
8
|
import { GroupType, GroupTypeConfigValue } from './models/group-types.model';
|
|
9
|
-
import { AlertsService, GlobalErrorHandlerService } from '@ieeesa-npm/utility';
|
|
9
|
+
import { AlertsService, GlobalErrorHandlerService, StorageService } from '@ieeesa-npm/utility';
|
|
10
10
|
import { AlertType } from '@ieeesa-npm/models';
|
|
11
11
|
import { GroupFunctionType } from './models/group-function-type.model';
|
|
12
12
|
import { GetGroupsPayload } from './models/view-group.model';
|
|
@@ -29,6 +29,7 @@ export declare class GroupsComponent implements OnInit, OnDestroy {
|
|
|
29
29
|
globalErrorHandlerService: GlobalErrorHandlerService;
|
|
30
30
|
private userPermissionsService;
|
|
31
31
|
private groupPermissionsService;
|
|
32
|
+
private storageService;
|
|
32
33
|
destroy$: Subject<boolean>;
|
|
33
34
|
dialogRef: any;
|
|
34
35
|
constants: any;
|
|
@@ -84,7 +85,8 @@ export declare class GroupsComponent implements OnInit, OnDestroy {
|
|
|
84
85
|
trimmedSearchTerm: string;
|
|
85
86
|
userGroupPermissions: GroupPermission[];
|
|
86
87
|
supportMessage: string;
|
|
87
|
-
|
|
88
|
+
private hasAttemptedHistoryRestore;
|
|
89
|
+
constructor(dialog: MatDialog, groupsService: GroupsService, alertService: AlertsService, breakpointObserverService: BreakpointObserverService, globalErrorHandlerService: GlobalErrorHandlerService, userPermissionsService: UserPermissionsService, groupPermissionsService: GroupPermissionsMapService, storageService: StorageService);
|
|
88
90
|
/**
|
|
89
91
|
* Initialize the groups component.
|
|
90
92
|
* @return -returns nothing
|
|
@@ -138,7 +140,7 @@ export declare class GroupsComponent implements OnInit, OnDestroy {
|
|
|
138
140
|
* @param {GroupInfo} selectedGroupInfo
|
|
139
141
|
* @memberof GroupsComponent
|
|
140
142
|
*/
|
|
141
|
-
openRosterImportHistoryModal(selectedGroupInfo: GroupInfo): void;
|
|
143
|
+
openRosterImportHistoryModal(selectedGroupInfo: GroupInfo, isRestored?: boolean): void;
|
|
142
144
|
/**
|
|
143
145
|
* Sets the dialog options for users modal and calls openModal method.
|
|
144
146
|
* @param {GroupInfo} group
|
|
@@ -160,7 +162,7 @@ export declare class GroupsComponent implements OnInit, OnDestroy {
|
|
|
160
162
|
* @return -returns nothing
|
|
161
163
|
* @memberof GroupsComponent
|
|
162
164
|
*/
|
|
163
|
-
openModal(dialogOptions: any, className?: string, groupInfo?: GroupInfo | EditGroupAndParentInfo): void;
|
|
165
|
+
openModal(dialogOptions: any, className?: string, groupInfo?: GroupInfo | EditGroupAndParentInfo, isRestoredReadOnlyDialog?: boolean): void;
|
|
164
166
|
/**
|
|
165
167
|
* Opens the confirmation modal to get user confirmation to delete the selected group.
|
|
166
168
|
* @memberof GroupComponent
|
|
@@ -222,6 +224,9 @@ export declare class GroupsComponent implements OnInit, OnDestroy {
|
|
|
222
224
|
* @memberof GroupsComponent
|
|
223
225
|
*/
|
|
224
226
|
getGroupTypes(): void;
|
|
227
|
+
private get rosterHistoryStorageKey();
|
|
228
|
+
private persistRosterHistory;
|
|
229
|
+
private restoreRosterImportHistoryModal;
|
|
225
230
|
/**
|
|
226
231
|
* Handles alerts for success, error and warning.
|
|
227
232
|
* @param {(string | string[])} message -success or error or warning message.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interest-form field types frozen on each stored response via the backend's
|
|
3
|
+
* {@code field_type_snapshot} column. Mirrors the BE `FieldType` enum
|
|
4
|
+
* (sa-saapps-groups-api: org.ieee.standards.groups.model.FieldType) and drives
|
|
5
|
+
* how {@link InterestFormResponse.answerValue} is rendered in the admin
|
|
6
|
+
* "View Responses" dialog (single value vs. a list for CHECKBOX).
|
|
7
|
+
*/
|
|
8
|
+
export declare enum InterestFieldType {
|
|
9
|
+
TEXT = "TEXT",
|
|
10
|
+
TEXTAREA = "TEXTAREA",
|
|
11
|
+
SELECT = "SELECT",
|
|
12
|
+
CHECKBOX = "CHECKBOX",
|
|
13
|
+
RADIO = "RADIO",
|
|
14
|
+
DATE = "DATE"
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A single submitted answer to an interest-form question, as returned by the
|
|
18
|
+
* backend from
|
|
19
|
+
* {@code GET {apiBaseUrl}/groups/interest/{interestId}/responses}.
|
|
20
|
+
*
|
|
21
|
+
* Mirrors the UI-view projection of the BE `InterestFormResponse` entity
|
|
22
|
+
* (sa-saapps-groups-api: org.ieee.standards.groups.model.InterestFormResponse);
|
|
23
|
+
* only the fields annotated with the BE `@JsonView(UiView)` are serialized —
|
|
24
|
+
* the audit/app_id columns are `@JsonIgnore` and never arrive on the client.
|
|
25
|
+
*
|
|
26
|
+
* {@link questionLabelSnapshot} and {@link fieldTypeSnapshot} freeze the
|
|
27
|
+
* question label and field type as configured at submit time, so later form
|
|
28
|
+
* configuration changes apply only to new submissions (R8.6, R8.8).
|
|
29
|
+
* {@link answerValue} is a JSONB passthrough: it arrives as a plain string for
|
|
30
|
+
* single-value fields (TEXT/TEXTAREA/SELECT/RADIO/DATE) and as a string[] for
|
|
31
|
+
* multi-select (CHECKBOX).
|
|
32
|
+
*/
|
|
33
|
+
export interface InterestFormResponse {
|
|
34
|
+
/** Response row identifier (e.g. "IFR_SAAPPS_..."). */
|
|
35
|
+
responseId: string;
|
|
36
|
+
/** The interest record this answer belongs to (e.g. "GINT_SAAPPS_..."). */
|
|
37
|
+
interestId: string;
|
|
38
|
+
/** The question this answer is for (e.g. "IFQ_SAAPPS_..."), when available. */
|
|
39
|
+
questionId?: string;
|
|
40
|
+
/** Question label frozen at submit time (R8.6, R8.8). */
|
|
41
|
+
questionLabelSnapshot?: string;
|
|
42
|
+
/** Field type frozen at submit time; see {@link InterestFieldType}. */
|
|
43
|
+
fieldTypeSnapshot?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Submitted answer. A string for single-value fields; a string[] for
|
|
46
|
+
* CHECKBOX. `null`/undefined when the question was left unanswered.
|
|
47
|
+
*/
|
|
48
|
+
answerValue?: string | string[] | null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A single organization affiliation on an interested-roster row, as returned
|
|
52
|
+
* inside {@link GroupInterestResponseView.affiliation}. Mirrors the UI-view
|
|
53
|
+
* projection of the BE `Organization` entity (only `orgId`/`orgName` are
|
|
54
|
+
* `@JsonView(UiView)`; the rest are `@JsonIgnore`).
|
|
55
|
+
*/
|
|
56
|
+
export interface InterestAffiliation {
|
|
57
|
+
/** Organization id (e.g. "ORG_SAAPPS_..."). */
|
|
58
|
+
orgId: string;
|
|
59
|
+
/** Organization display name. */
|
|
60
|
+
orgName: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A single membership level (role) on an interested-roster row, as returned
|
|
64
|
+
* inside {@link GroupInterestResponseView.memberLevel}. Mirrors the UI-view
|
|
65
|
+
* projection of the BE role object: the level is a group role, so the fields
|
|
66
|
+
* are role-shaped ({@link roleId}/{@link roleName}). The list carries at most
|
|
67
|
+
* one element — the interest's current/assigned membership level (empty when
|
|
68
|
+
* none is assigned).
|
|
69
|
+
*/
|
|
70
|
+
export interface InterestMembershipLevel {
|
|
71
|
+
/** Role id of the membership level (e.g. "ROLE_SAAPPS_..."). */
|
|
72
|
+
roleId: string;
|
|
73
|
+
/** Role type id, when categorized; `null` otherwise. */
|
|
74
|
+
roleType?: string | null;
|
|
75
|
+
/** Display name of the level (e.g. "Member"). Bound as the dropdown value. */
|
|
76
|
+
roleName: string;
|
|
77
|
+
/** Human-readable role type name; `null` otherwise. */
|
|
78
|
+
roleTypeName?: string | null;
|
|
79
|
+
/** Display order within the group type's role catalog. */
|
|
80
|
+
viewOrder?: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* One interested-users roster row for a group, as returned by the backend from
|
|
84
|
+
* {@code GET {apiBaseUrl}/groups/interest/group/{groupId}/responses}.
|
|
85
|
+
*
|
|
86
|
+
* Mirrors the UI-view projection of the BE `GroupInterestResponseView` read
|
|
87
|
+
* object (sa-saapps-groups-api:
|
|
88
|
+
* org.ieee.standards.groups.model.GroupInterestResponseView). It combines the
|
|
89
|
+
* interest record with the interested user's name/email, their organization
|
|
90
|
+
* affiliations for the group, the submitted timestamp, the group's default
|
|
91
|
+
* membership level (at most one element), and the group's type id, so the whole
|
|
92
|
+
* "Interested" roster renders in a single call (R8.7). Only `@JsonView(UiView)`
|
|
93
|
+
* fields are serialized; tenant app_id and audit columns stay hidden.
|
|
94
|
+
*/
|
|
95
|
+
export interface GroupInterestResponseView {
|
|
96
|
+
/** The interest record id (e.g. "GINT_SAAPPS_..."). */
|
|
97
|
+
interestId: string;
|
|
98
|
+
/** The group the interest belongs to (e.g. "GRP_SAAPPS_..."). */
|
|
99
|
+
groupId?: string;
|
|
100
|
+
/** The interested user's id (e.g. "User_NESC_..."). */
|
|
101
|
+
userId: string;
|
|
102
|
+
/** Interested user's display name. */
|
|
103
|
+
name: string;
|
|
104
|
+
/** Interested user's email address. */
|
|
105
|
+
email: string;
|
|
106
|
+
/** Organization affiliations for the group (id + name). */
|
|
107
|
+
affiliation: InterestAffiliation[];
|
|
108
|
+
/** Submitted timestamp (ISO date string; BE `Date`). */
|
|
109
|
+
submittedOn: string;
|
|
110
|
+
/** Group's default membership level (0 or 1 element). */
|
|
111
|
+
memberLevel: InterestMembershipLevel[];
|
|
112
|
+
/** The group's group type id. */
|
|
113
|
+
groupTypeId?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Request payload for the interested-users roster list endpoint
|
|
117
|
+
* ({@code POST {apiBaseUrl}/groups/interest/group/{groupId}/responses}).
|
|
118
|
+
*
|
|
119
|
+
* Follows the repo payload convention of wrapping the request body in a
|
|
120
|
+
* {@code payload} key (matching FilterUsersPayload, SearchUsersPayload, etc.).
|
|
121
|
+
* Supports server-side search ({@link InterestedRostersRequestBody.searchByNameOrEmail})
|
|
122
|
+
* and pagination/sorting so the "Interested Rosters" table can page and sort
|
|
123
|
+
* large rosters without loading everything client-side.
|
|
124
|
+
*/
|
|
125
|
+
export interface InterestedRostersRequestPayload {
|
|
126
|
+
payload: InterestedRostersRequestBody;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The body of {@link InterestedRostersRequestPayload}. All fields are sent to
|
|
130
|
+
* the backend to filter, page and sort the interested-users roster.
|
|
131
|
+
*/
|
|
132
|
+
export interface InterestedRostersRequestBody {
|
|
133
|
+
/** Free-text search term matched against the requester's name or email. */
|
|
134
|
+
searchByNameOrEmail: string;
|
|
135
|
+
/** Zero-based row offset for the requested page. */
|
|
136
|
+
offset: number;
|
|
137
|
+
/** Number of rows per page. */
|
|
138
|
+
pageSize: number;
|
|
139
|
+
/** Column key to sort by (e.g. "name", "submittedOn"). */
|
|
140
|
+
sortedBy: string;
|
|
141
|
+
/** Sort direction (e.g. "asc" / "desc"). */
|
|
142
|
+
sortedOrderBy: string;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The `body` of the interested-users roster list response, as returned by
|
|
146
|
+
* {@code POST {apiBaseUrl}/groups/interest/group/{groupId}/responses}.
|
|
147
|
+
*
|
|
148
|
+
* The rows live under {@link data}; the remaining fields echo the paging state
|
|
149
|
+
* ({@link totalRecords}, {@link offset}, {@link pageSize}) so the table can
|
|
150
|
+
* drive pagination. The outer envelope ({@code status}/{@code message}/
|
|
151
|
+
* {@code body}) is unwrapped by the HTTP layer, so this models the `body` only.
|
|
152
|
+
*/
|
|
153
|
+
export interface InterestedRostersResponse {
|
|
154
|
+
/** The interested-roster rows for the requested page. */
|
|
155
|
+
data: GroupInterestResponseView[];
|
|
156
|
+
/** Total number of interested-roster rows across all pages. */
|
|
157
|
+
totalRecords: number;
|
|
158
|
+
/** Zero-based row offset of the returned page. */
|
|
159
|
+
offset: number;
|
|
160
|
+
/** Number of rows per page used for this response. */
|
|
161
|
+
pageSize: number;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A single interested-roster row shown in the Roster Management "Interested
|
|
165
|
+
* Rosters" table. Carries the {@link interestId} needed to fetch that
|
|
166
|
+
* requester's stored responses via the interest-responses endpoint and to
|
|
167
|
+
* override their membership level. Rows are produced by mapping the backend
|
|
168
|
+
* {@link GroupInterestResponseView} list onto this flattened display shape.
|
|
169
|
+
*/
|
|
170
|
+
export interface InterestedRoster {
|
|
171
|
+
/** The interest record id (e.g. "GINT_SAAPPS_..."), used for responses + override. */
|
|
172
|
+
interestId: string;
|
|
173
|
+
/** The interested user's id (e.g. "User_NESC_..."), used for the level override. */
|
|
174
|
+
userId: string;
|
|
175
|
+
/** Requester display name. */
|
|
176
|
+
name: string;
|
|
177
|
+
/** Requester email address. */
|
|
178
|
+
email: string;
|
|
179
|
+
/** Requester affiliations (organizations). */
|
|
180
|
+
affiliations: string[];
|
|
181
|
+
/** Submission date, preformatted for display. */
|
|
182
|
+
submittedOn: string;
|
|
183
|
+
/** Current/assigned membership level name, or null when none is assigned. */
|
|
184
|
+
memberLevel: string | null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Request payload for overriding/setting an interested user's membership level
|
|
188
|
+
* ({@code POST {apiBaseUrl}/groups/interest/member-level}).
|
|
189
|
+
*
|
|
190
|
+
* Follows the repo payload convention of wrapping the request body in a
|
|
191
|
+
* {@code payload} key. Identifies the roster row by {@link UpdateMemberLevelBody.groupId}
|
|
192
|
+
* + {@link UpdateMemberLevelBody.userId} and carries the newly selected role in
|
|
193
|
+
* {@link UpdateMemberLevelBody.roleId}.
|
|
194
|
+
*/
|
|
195
|
+
export interface UpdateMemberLevelPayload {
|
|
196
|
+
payload: UpdateMemberLevelBody;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* The body of {@link UpdateMemberLevelPayload}: the group and user being
|
|
200
|
+
* updated plus the role id of the chosen membership level.
|
|
201
|
+
*/
|
|
202
|
+
export interface UpdateMemberLevelBody {
|
|
203
|
+
/** The group the interested user belongs to (e.g. "GRP_SAAPPS_..."). */
|
|
204
|
+
groupId: string;
|
|
205
|
+
/** The interested user whose membership level is being set (e.g. "User_NESC_..."). */
|
|
206
|
+
userId: string;
|
|
207
|
+
/** The selected membership-level role id (e.g. "ROLE_SAAPPS_..."). */
|
|
208
|
+
roleId: string;
|
|
209
|
+
}
|
|
@@ -54,6 +54,18 @@ export interface RosterImportJob {
|
|
|
54
54
|
successRows?: number;
|
|
55
55
|
/** Rows skipped due to validation errors. */
|
|
56
56
|
failedRows?: number;
|
|
57
|
+
/**
|
|
58
|
+
* When the import was created (ISO timestamp). Drives the "Timestamp" column
|
|
59
|
+
* in View History. Present only once the BE exposes it (currently @JsonIgnore);
|
|
60
|
+
* undefined otherwise, in which case the UI shows a placeholder.
|
|
61
|
+
*/
|
|
62
|
+
createdOn?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Who ran the import (user email/name). Drives the "Person Imported" column in
|
|
65
|
+
* View History. Present only once the BE exposes it (currently @JsonIgnore);
|
|
66
|
+
* undefined otherwise, in which case the UI shows a placeholder.
|
|
67
|
+
*/
|
|
68
|
+
createdBy?: string;
|
|
57
69
|
/**
|
|
58
70
|
* Row-level errors from the BE `error_report` JSONB column. Observed to arrive
|
|
59
71
|
* EITHER as a parsed object `{ rows: RosterImportErrorReport[] }` OR as a raw
|