@ieeesa-npm/dashboard 0.0.0-stage → 0.0.2

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 (73) hide show
  1. package/README.md +75 -3
  2. package/esm2022/ieeesa-npm-dashboard.mjs +5 -0
  3. package/esm2022/lib/assets/constants.json +158 -0
  4. package/esm2022/lib/assets/dashboard-mock.mjs +221 -0
  5. package/esm2022/lib/assets/dashboard-route-paths.mock.mjs +14 -0
  6. package/esm2022/lib/assets/dashboard-user-permissions.mock.mjs +23 -0
  7. package/esm2022/lib/assets/date-formats.mjs +28 -0
  8. package/esm2022/lib/components/event-group-card/event-group-card.component.mjs +414 -0
  9. package/esm2022/lib/components/meeting-attendance-row/meeting-attendance-row.component.mjs +182 -0
  10. package/esm2022/lib/components/member-level-badge/member-level-badge.component.mjs +79 -0
  11. package/esm2022/lib/components/upcoming-meetings-events/upcoming-meetings-events.component.mjs +261 -0
  12. package/esm2022/lib/components/welcome-header/dashboard-welcome-header.component.mjs +151 -0
  13. package/esm2022/lib/components/your-groups/your-groups.component.mjs +243 -0
  14. package/esm2022/lib/dashboard.component.mjs +417 -0
  15. package/esm2022/lib/dashboard.module.mjs +16 -0
  16. package/esm2022/lib/dashboard.routes.mjs +7 -0
  17. package/esm2022/lib/models/attendance-action-permissions.model.mjs +2 -0
  18. package/esm2022/lib/models/attendance-state.model.mjs +7 -0
  19. package/esm2022/lib/models/dashboard-list-response.model.mjs +2 -0
  20. package/esm2022/lib/models/dashboard-route-config.model.mjs +2 -0
  21. package/esm2022/lib/models/dashboard-section-state.model.mjs +9 -0
  22. package/esm2022/lib/models/dashboard-user-info.model.mjs +2 -0
  23. package/esm2022/lib/models/event-meeting-list-item.model.mjs +2 -0
  24. package/esm2022/lib/models/event-meeting-list-payload.model.mjs +8 -0
  25. package/esm2022/lib/models/group-row.model.mjs +2 -0
  26. package/esm2022/lib/models/meeting-row.model.mjs +2 -0
  27. package/esm2022/lib/models/my-groups-view.model.mjs +2 -0
  28. package/esm2022/lib/models/page-change.model.mjs +2 -0
  29. package/esm2022/lib/models/upcoming-event.model.mjs +13 -0
  30. package/esm2022/lib/services/attendance-reducers.mjs +54 -0
  31. package/esm2022/lib/services/dashboard.service.mjs +402 -0
  32. package/esm2022/lib/services/user-permissions.service.mjs +50 -0
  33. package/esm2022/lib/utils/attendance-action.util.mjs +53 -0
  34. package/esm2022/lib/utils/date-format.util.mjs +110 -0
  35. package/esm2022/lib/utils/event-meeting-mapper.util.mjs +96 -0
  36. package/esm2022/public-api.mjs +36 -0
  37. package/fesm2022/ieeesa-npm-dashboard.mjs +2981 -0
  38. package/fesm2022/ieeesa-npm-dashboard.mjs.map +1 -0
  39. package/index.d.ts +5 -0
  40. package/lib/assets/dashboard-mock.d.ts +31 -0
  41. package/lib/assets/dashboard-route-paths.mock.d.ts +2 -0
  42. package/lib/assets/dashboard-user-permissions.mock.d.ts +2 -0
  43. package/lib/assets/date-formats.d.ts +27 -0
  44. package/lib/components/event-group-card/event-group-card.component.d.ts +268 -0
  45. package/lib/components/meeting-attendance-row/meeting-attendance-row.component.d.ts +115 -0
  46. package/lib/components/member-level-badge/member-level-badge.component.d.ts +177 -0
  47. package/lib/components/upcoming-meetings-events/upcoming-meetings-events.component.d.ts +175 -0
  48. package/lib/components/welcome-header/dashboard-welcome-header.component.d.ts +98 -0
  49. package/lib/components/your-groups/your-groups.component.d.ts +159 -0
  50. package/lib/dashboard.component.d.ts +414 -0
  51. package/lib/dashboard.module.d.ts +7 -0
  52. package/lib/dashboard.routes.d.ts +2 -0
  53. package/lib/models/attendance-action-permissions.model.d.ts +4 -0
  54. package/lib/models/attendance-state.model.d.ts +4 -0
  55. package/lib/models/dashboard-list-response.model.d.ts +5 -0
  56. package/lib/models/dashboard-route-config.model.d.ts +7 -0
  57. package/lib/models/dashboard-section-state.model.d.ts +6 -0
  58. package/lib/models/dashboard-user-info.model.d.ts +5 -0
  59. package/lib/models/event-meeting-list-item.model.d.ts +107 -0
  60. package/lib/models/event-meeting-list-payload.model.d.ts +40 -0
  61. package/lib/models/group-row.model.d.ts +33 -0
  62. package/lib/models/meeting-row.model.d.ts +28 -0
  63. package/lib/models/my-groups-view.model.d.ts +56 -0
  64. package/lib/models/page-change.model.d.ts +4 -0
  65. package/lib/models/upcoming-event.model.d.ts +51 -0
  66. package/lib/services/attendance-reducers.d.ts +43 -0
  67. package/lib/services/dashboard.service.d.ts +202 -0
  68. package/lib/services/user-permissions.service.d.ts +31 -0
  69. package/lib/utils/attendance-action.util.d.ts +38 -0
  70. package/lib/utils/date-format.util.d.ts +60 -0
  71. package/lib/utils/event-meeting-mapper.util.d.ts +50 -0
  72. package/package.json +30 -3
  73. package/public-api.d.ts +26 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Read-only aggregate view returned by GET /v1/groups/my-groups, mirroring the
3
+ * groups-api `MyGroupsView` DTO. The dashboard "Your Groups" section consumes
4
+ * the `groups` collection; `interests` is not shown on the dashboard.
5
+ */
6
+ export interface MyGroupsView {
7
+ groups: MyGroupView[];
8
+ interests?: MyInterestView[];
9
+ }
10
+ /**
11
+ * A single officer role the user holds in a group, as returned in the
12
+ * `officer` array of GET /v1/groups/my-groups.
13
+ */
14
+ export interface GroupOfficer {
15
+ officerId: string;
16
+ officerName: string;
17
+ }
18
+ /**
19
+ * A single group the user is a roster member of. Carries the group's name,
20
+ * type, affiliations, the user's member level, and the user's officer role(s)
21
+ * for the group (the `officer` array — empty when the user holds no officer
22
+ * role).
23
+ */
24
+ export interface MyGroupView {
25
+ groupId: string;
26
+ groupName: string;
27
+ groupType?: string;
28
+ groupTypeName?: string | null;
29
+ /** Ancestor hierarchy (root→leaf) of the group, for the expandable rows. */
30
+ hierarchy?: MyGroupHierarchyItem[];
31
+ affiliations?: string[];
32
+ memberLevelId?: string | null;
33
+ memberLevel?: string | null;
34
+ /** Officer roles the user holds in this group (e.g. "Chair"); empty if none. */
35
+ officer?: GroupOfficer[];
36
+ }
37
+ /** A single node in a group's ancestor hierarchy (root→leaf). */
38
+ export interface MyGroupHierarchyItem {
39
+ groupId: string;
40
+ groupName: string;
41
+ groupShortName?: string;
42
+ groupAcronym?: string;
43
+ groupLevel?: number;
44
+ groupParentId?: string | null;
45
+ groupType?: string;
46
+ groupTypeName?: string | null;
47
+ groupDesc?: string | null;
48
+ hasChildren?: boolean | null;
49
+ }
50
+ /** A single interest the user has expressed (not shown on the dashboard). */
51
+ export interface MyInterestView {
52
+ interestId: string;
53
+ groupId: string;
54
+ groupName: string;
55
+ status: string;
56
+ }
@@ -0,0 +1,4 @@
1
+ export interface PageChange {
2
+ pageIndex: number;
3
+ pageSize: number;
4
+ }
@@ -0,0 +1,51 @@
1
+ import { MeetingRow } from './meeting-row.model';
2
+ /**
3
+ * Discriminates how a row in the "Upcoming Meetings & Events" list is rendered.
4
+ * EVENT rows render as an expandable event block that owns child meetings;
5
+ * STANDALONE_MEETING rows render as a single meeting card and always sort LAST
6
+ * within the list.
7
+ */
8
+ export declare enum UpcomingItemKind {
9
+ EVENT = "EVENT",
10
+ STANDALONE_MEETING = "STANDALONE_MEETING"
11
+ }
12
+ /**
13
+ * View model for a single entry in the "Upcoming Meetings & Events" section.
14
+ *
15
+ * When {@link kind} is `EVENT` the entry renders as the expandable event block
16
+ * (header badge, title, "Day X of Y", meeting-count sub-line, dates/timezone,
17
+ * View button, and the show/hide child-meeting list). When `kind` is
18
+ * `STANDALONE_MEETING` the entry renders as a single standalone meeting card
19
+ * (rendered after all events).
20
+ *
21
+ * `startEpochMs` / `endEpochMs` carry the raw epoch-milliseconds values from the
22
+ * backend and are formatted with luxon `DateTime.fromMillis`. The legacy ISO
23
+ * `startDate` / `endDate` fields are retained for existing fixtures/tests.
24
+ */
25
+ export interface UpcomingEvent {
26
+ eventId: string;
27
+ /** EVENT vs. STANDALONE_MEETING render discriminator. */
28
+ kind: UpcomingItemKind;
29
+ title: string;
30
+ status: string;
31
+ dayIndicator?: string;
32
+ meetingCount: number;
33
+ openForAttendanceCount: number;
34
+ /** Start date-time as an ISO-8601 string (or epoch ms for fixtures). */
35
+ startDateTime?: string | number;
36
+ /** End date-time as an ISO-8601 string (or epoch ms for fixtures). */
37
+ endDateTime?: string | number;
38
+ /** Legacy ISO start date, retained for fixtures/tests. */
39
+ startDate?: string;
40
+ /** Legacy ISO end date, retained for fixtures/tests. */
41
+ endDate?: string;
42
+ timezone: string;
43
+ /** Owning group label, shown on standalone-meeting cards. */
44
+ groupOrCommittee?: string;
45
+ /** Room / location, shown on standalone-meeting cards. */
46
+ roomOrLocation?: string;
47
+ /** True when the user is a member of the owning group. */
48
+ isMemberOfGroup?: boolean;
49
+ /** The event's child meetings (empty / lazily loaded for EVENT rows). */
50
+ meetings: MeetingRow[];
51
+ }
@@ -0,0 +1,43 @@
1
+ import { AttendanceState } from '../models/attendance-state.model';
2
+ /**
3
+ * Pure reducers for the dashboard attendance state. These functions never
4
+ * mutate their inputs; each returns a new object/set so they can be exercised
5
+ * directly by property tests and composed by the DashboardService.
6
+ */
7
+ /**
8
+ * Produce a new attendance-state map with the target meeting set to
9
+ * CURRENTLY_ATTENDING. Every other entry is copied unchanged (frame property).
10
+ *
11
+ * @param {Record<string, AttendanceState>} map The current attendance-state map (not mutated).
12
+ * @param {string} meetingId The meeting whose state transitions to CURRENTLY_ATTENDING.
13
+ * @return {Record<string, AttendanceState>} A new map reflecting the successful log-attendance action.
14
+ */
15
+ export declare const applyLogAttendanceSuccess: (map: Record<string, AttendanceState>, meetingId: string) => Record<string, AttendanceState>;
16
+ /**
17
+ * Produce a new attendance-state map with the target meeting set to
18
+ * NOT_ATTENDING. Every other entry is copied unchanged (frame property).
19
+ *
20
+ * @param {Record<string, AttendanceState>} map The current attendance-state map (not mutated).
21
+ * @param {string} meetingId The meeting whose state transitions to NOT_ATTENDING.
22
+ * @return {Record<string, AttendanceState>} A new map reflecting the successful leave-meeting action.
23
+ */
24
+ export declare const applyLeaveMeetingSuccess: (map: Record<string, AttendanceState>, meetingId: string) => Record<string, AttendanceState>;
25
+ /**
26
+ * Add a meeting to the in-flight set, returning a new set. Prior membership is
27
+ * retained so the displayed attendance state is preserved while the request
28
+ * resolves.
29
+ *
30
+ * @param {ReadonlySet<string>} set The current in-flight meeting-id set (not mutated).
31
+ * @param {string} meetingId The meeting whose attendance action has started.
32
+ * @return {Set<string>} A new set that additionally contains the meeting id.
33
+ */
34
+ export declare const beginAttendanceAction: (set: ReadonlySet<string>, meetingId: string) => Set<string>;
35
+ /**
36
+ * Remove a meeting from the in-flight set, returning a new set. All other
37
+ * in-flight ids are retained.
38
+ *
39
+ * @param {ReadonlySet<string>} set The current in-flight meeting-id set (not mutated).
40
+ * @param {string} meetingId The meeting whose attendance action has resolved.
41
+ * @return {Set<string>} A new set that no longer contains the meeting id.
42
+ */
43
+ export declare const endAttendanceAction: (set: ReadonlySet<string>, meetingId: string) => Set<string>;
@@ -0,0 +1,202 @@
1
+ import { Observable } from 'rxjs';
2
+ import { HttpService } from '@ieeesa-npm/utility';
3
+ import { UserPermission } from '@ieeesa-npm/models';
4
+ import { AttendanceState } from '../models/attendance-state.model';
5
+ import { DashboardListResponse } from '../models/dashboard-list-response.model';
6
+ import { DashboardSectionState } from '../models/dashboard-section-state.model';
7
+ import { GroupRow } from '../models/group-row.model';
8
+ import { MyGroupsView } from '../models/my-groups-view.model';
9
+ import { UpcomingEvent } from '../models/upcoming-event.model';
10
+ import { EventMeetingDetailResponse, EventMeetingListResponse } from '../models/event-meeting-list-item.model';
11
+ import * as i0 from "@angular/core";
12
+ /**
13
+ * DashboardService is the single source of truth for the dashboard landing
14
+ * page's data and attendance state. It mirrors the `events-and-meetings`
15
+ * service conventions: it is a `providedIn: 'root'` singleton, holds the host
16
+ * `apiBaseUrl` / `userPermissions` set via {@link setAppConfig}, owns its state
17
+ * via `BehaviorSubject`s exposed as observables, and reaches the backend only
18
+ * through the shared `HttpService` from `@ieeesa-npm/utility` (never
19
+ * `HttpClient` directly).
20
+ *
21
+ * The upcoming-events load calls the real `POST /meetings/eventsAndMeetings`
22
+ * endpoint (child meetings are lazily loaded per-event via
23
+ * `POST /meetings/meetingsDetails`); the groups load calls `GET
24
+ * /groups/my-groups`. The attendance actions compose the pure reducers from
25
+ * `attendance-reducers.ts` so the displayed `AttendanceState` only advances on a
26
+ * successful resolution and the prior state is retained on failure.
27
+ * @export
28
+ * @class DashboardService
29
+ */
30
+ export declare class DashboardService {
31
+ private httpService;
32
+ apiBaseUrl: string;
33
+ userPermissions: UserPermission[];
34
+ private readonly upcomingEvents;
35
+ private readonly groups;
36
+ private readonly upcomingState;
37
+ private readonly groupsState;
38
+ private readonly attendanceStates;
39
+ private readonly inFlightMeetingIds;
40
+ /** Emits the list of the user's upcoming events. */
41
+ upcomingEvents$: Observable<UpcomingEvent[]>;
42
+ /** Emits the list of the user's groups. */
43
+ groups$: Observable<GroupRow[]>;
44
+ /** Emits the load/empty/error state of the upcoming-events section. */
45
+ upcomingState$: Observable<DashboardSectionState>;
46
+ /** Emits the load/empty/error state of the groups section. */
47
+ groupsState$: Observable<DashboardSectionState>;
48
+ /** Emits the per-meeting attendance-state map. */
49
+ attendanceStates$: Observable<Record<string, AttendanceState>>;
50
+ /** Emits the set of meeting ids with an attendance action in flight. */
51
+ inFlightMeetingIds$: Observable<ReadonlySet<string>>;
52
+ constructor(httpService: HttpService);
53
+ /**
54
+ * Stores the host-supplied API base URL and user permissions used to build
55
+ * request URLs and gate permission-controlled actions. Mirrors the
56
+ * `events-and-meetings` service `setAppConfig`.
57
+ * @param {string} apiBaseURL - The host application's API base URL.
58
+ * @param {UserPermission[]} userPermissions - The signed-in user's permissions.
59
+ * @return {void}
60
+ * @memberof DashboardService
61
+ */
62
+ setAppConfig(apiBaseURL: string, userPermissions: UserPermission[]): void;
63
+ /**
64
+ * Loads the user's upcoming events & meetings from the real backend via
65
+ * `POST {apiBaseUrl}/meetings/eventsAndMeetings`, the same endpoint the
66
+ * `view-events-meetings-list` screen uses. The response is mapped into the
67
+ * dashboard view models (with STANDALONE_MEETING rows ordered last), the
68
+ * per-meeting attendance-state map is seeded, and the section advances through
69
+ * LOADING → LOADED/EMPTY on success, leaving the caller to set ERROR on
70
+ * failure. Follows the shared `pipe(map, catchError(throw))` pattern.
71
+ * @return {Observable<EventMeetingListResponse>} The upcoming events & meetings response envelope.
72
+ * @memberof DashboardService
73
+ */
74
+ loadUpcomingEvents(): Observable<EventMeetingListResponse>;
75
+ /**
76
+ * Lazily loads (and paginates) the child meetings of an EVENT row via
77
+ * `POST {apiBaseUrl}/meetings/meetingsDetails` — the per-event expand endpoint
78
+ * used by the reference screen. On success the loaded meetings are mapped and
79
+ * merged onto the matching event in the `upcomingEvents` list (with its
80
+ * `meetingCount` / `openForAttendanceCount` refreshed), and the per-meeting
81
+ * attendance-state map is extended. Each event paginates independently.
82
+ * @param {string} eventId - The event whose meetings to load.
83
+ * @param {number} [page=1] - The one-based page to request.
84
+ * @param {number} [size=EVENT_MEETINGS_PAGE_SIZE] - The page size.
85
+ * @return {Observable<EventMeetingDetailResponse>} The child-meetings response envelope.
86
+ * @memberof DashboardService
87
+ */
88
+ loadEventMeetings(eventId: string, page?: number, size?: number): Observable<EventMeetingDetailResponse>;
89
+ /**
90
+ * Merges a freshly loaded page of child meetings onto the matching event in
91
+ * the `upcomingEvents` list, refreshing the event's `meetings`,
92
+ * `meetingCount` (from the backend `totalRecords`), and
93
+ * `openForAttendanceCount`. Leaves all other events untouched.
94
+ * @param {string} eventId - The event to merge meetings onto.
95
+ * @param {MeetingRow[]} meetings - The mapped child meetings for the page.
96
+ * @param {number} totalRecords - The event's total meeting count from the backend.
97
+ * @return {void}
98
+ * @memberof DashboardService
99
+ */
100
+ private mergeEventMeetings;
101
+ /**
102
+ * Eagerly fetches each EVENT's total meeting count and open-for-attendance
103
+ * count by requesting a large first page of its child meetings, then merging
104
+ * only the counts (not the rows) so the collapsed card summaries are accurate
105
+ * before the user expands. Standalone meetings are skipped. Failures per event
106
+ * are swallowed so one failing count never breaks the list.
107
+ * @param {UpcomingEvent[]} events - The loaded events to fetch counts for.
108
+ * @return {void}
109
+ * @memberof DashboardService
110
+ */
111
+ private loadEventCounts;
112
+ /**
113
+ * Merges only the meeting counts onto the matching event, leaving its
114
+ * `meetings` rows untouched (used by the eager count prefetch).
115
+ * @param {string} eventId - The event to update.
116
+ * @param {number} totalRecords - The event's total meeting count.
117
+ * @param {number} openForAttendanceCount - The open-for-attendance count.
118
+ * @return {void}
119
+ * @memberof DashboardService
120
+ */
121
+ private mergeEventCounts;
122
+ /**
123
+ * Builds and posts the `meetingsDetails` request for a single event page,
124
+ * returning the raw response envelope. Shared by {@link loadEventMeetings} and
125
+ * the eager count prefetch.
126
+ * @param {string} eventId - The event whose meetings to fetch.
127
+ * @param {number} page - The one-based page to request.
128
+ * @param {number} size - The page size.
129
+ * @return {Observable<EventMeetingDetailResponse>} The child-meetings response.
130
+ * @memberof DashboardService
131
+ */
132
+ private fetchEventMeetingsPage;
133
+ /**
134
+ * Loads the user's groups, driving the groups section through
135
+ * LOADING → LOADED/EMPTY on success and leaving the caller to set ERROR on
136
+ * failure. Follows the `pipe(map, catchError(throw))` pattern.
137
+ *
138
+ * TODO: Replace mock with real API call when BE is ready
139
+ * @return {Observable<DashboardListResponse<GroupRow[]>>} The groups response envelope.
140
+ * @memberof DashboardService
141
+ */
142
+ loadGroups(): Observable<DashboardListResponse<MyGroupsView>>;
143
+ /**
144
+ * Maps a backend {@link MyGroupView} to the table-facing {@link GroupRow}.
145
+ * Mirrors the My Groups profile tab: the type falls back to "Unassigned" when
146
+ * `groupTypeName` is null, affiliations are comma-joined, empty officer role /
147
+ * affiliation render as a placeholder, and a blank member level becomes
148
+ * "Unassigned" (rendered as the warning pill by the Your Groups component).
149
+ * @param {MyGroupView} group - The backend group view.
150
+ * @return {GroupRow}
151
+ * @memberof DashboardService
152
+ */
153
+ private mapGroup;
154
+ /**
155
+ * Logs attendance for a meeting. Marks the meeting in-flight (retaining the
156
+ * prior displayed state), posts through `HttpService`, applies the
157
+ * log-attendance-success reducer on success, clears the in-flight flag on
158
+ * completion, and rethrows while retaining prior state on failure.
159
+ * @param {string} meetingId - The meeting to log attendance for.
160
+ * @return {Observable<DashboardListResponse<unknown>>} The attendance response envelope.
161
+ * @memberof DashboardService
162
+ */
163
+ logAttendance(meetingId: string): Observable<DashboardListResponse<unknown>>;
164
+ /**
165
+ * Leaves a meeting. Marks the meeting in-flight (retaining the prior
166
+ * displayed state), posts through `HttpService`, applies the
167
+ * leave-meeting-success reducer on success, clears the in-flight flag on
168
+ * completion, and rethrows while retaining prior state on failure.
169
+ * @param {string} meetingId - The meeting to leave.
170
+ * @return {Observable<DashboardListResponse<unknown>>} The attendance response envelope.
171
+ * @memberof DashboardService
172
+ */
173
+ leaveMeeting(meetingId: string): Observable<DashboardListResponse<unknown>>;
174
+ /**
175
+ * Returns a synchronous snapshot of the per-meeting attendance-state map.
176
+ * @return {Record<string, AttendanceState>} The current attendance-state map.
177
+ * @memberof DashboardService
178
+ */
179
+ getAttendanceStatesSnapshot(): Record<string, AttendanceState>;
180
+ /**
181
+ * Seeds the attendance-state map from a freshly loaded list of events, using
182
+ * each meeting's `attendanceState` (defaulting to `NOT_ATTENDING`). Keeps the
183
+ * displayed state in sync with the data load without overwriting ids that are
184
+ * not present in the new list.
185
+ * @param {UpcomingEvent[]} events - The loaded upcoming events.
186
+ * @return {void}
187
+ * @memberof DashboardService
188
+ */
189
+ private seedAttendanceStates;
190
+ /**
191
+ * Seeds the attendance-state map from a list of meeting rows, using each
192
+ * meeting's `attendanceState` (defaulting to `NOT_ATTENDING`) without
193
+ * overwriting ids absent from the new list. Shared by the list load and the
194
+ * per-event expand so freshly loaded child meetings pick up their state.
195
+ * @param {MeetingRow[]} meetings - The meeting rows to seed from.
196
+ * @return {void}
197
+ * @memberof DashboardService
198
+ */
199
+ private seedAttendanceStatesFromMeetings;
200
+ static ɵfac: i0.ɵɵFactoryDeclaration<DashboardService, never>;
201
+ static ɵprov: i0.ɵɵInjectableDeclaration<DashboardService>;
202
+ }
@@ -0,0 +1,31 @@
1
+ import { UserPermission } from '@ieeesa-npm/models';
2
+ import { AttendanceActionPermissions } from '../models/attendance-action-permissions.model';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Permission identifiers (by name) that gate the dashboard attendance actions.
6
+ * Mirrors the permission-type pattern used by the `events-and-meetings` and
7
+ * `groups` user-permissions services.
8
+ */
9
+ export declare enum DashboardPermissionType {
10
+ LOG_ATTENDANCE = "Log Attendance",
11
+ LEAVE_MEETING = "Leave Meeting"
12
+ }
13
+ export declare class UserPermissionsService {
14
+ /**
15
+ * Returns access provision for the passed action/feature based on whether the signed in user has appropriate permission.
16
+ * @param {DashboardPermissionType} permission
17
+ * @param {UserPermission[]} userPermissions
18
+ * @return {boolean}
19
+ * @memberof UserPermissionsService
20
+ */
21
+ validateAccessPermission(permission: DashboardPermissionType, userPermissions: UserPermission[]): boolean;
22
+ /**
23
+ * Maps the host-supplied user permissions to the attendance-action permissions consumed by the dashboard rows.
24
+ * @param {UserPermission[]} userPermissions
25
+ * @return {AttendanceActionPermissions}
26
+ * @memberof UserPermissionsService
27
+ */
28
+ mapToAttendanceActionPermissions(userPermissions: UserPermission[]): AttendanceActionPermissions;
29
+ static ɵfac: i0.ɵɵFactoryDeclaration<UserPermissionsService, never>;
30
+ static ɵprov: i0.ɵɵInjectableDeclaration<UserPermissionsService>;
31
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pure helpers for the per-meeting attendance action state machine. No Angular /
3
+ * service dependencies so they are trivially testable (property-tested as
4
+ * Property 1) and reusable by `MeetingAttendanceRowComponent`.
5
+ *
6
+ * The handler is the single source of truth for what a meeting row's action
7
+ * button should emit when activated. The displayed `AttendanceState` only
8
+ * advances on a successful resolution (handled by the service reducers), so this
9
+ * function is a pure mapping from the current inputs to the action to emit.
10
+ */
11
+ import { AttendanceState } from '../models/attendance-state.model';
12
+ /**
13
+ * The action a meeting row's action button should emit when activated.
14
+ * `NONE` means the activation is non-actionable (empty id or in-flight) and the
15
+ * row must emit no `logAttendance` / `leaveMeeting` / navigation event.
16
+ */
17
+ export declare enum AttendanceAction {
18
+ LOG_ATTENDANCE = "LOG_ATTENDANCE",
19
+ LEAVE_MEETING = "LEAVE_MEETING",
20
+ NONE = "NONE"
21
+ }
22
+ /**
23
+ * Resolves the attendance action to emit for a meeting row, following the
24
+ * attendance action state machine (see design: Attendance Action State Machine).
25
+ *
26
+ * Behavior:
27
+ * - Returns `NONE` when `meetingId` is empty or undefined (non-actionable row).
28
+ * - Returns `NONE` while the row is in-flight (re-entrancy guard — no further
29
+ * event is emitted until the current action resolves).
30
+ * - Returns `LEAVE_MEETING` when the state is `CURRENTLY_ATTENDING`.
31
+ * - Returns `LOG_ATTENDANCE` when the state is `NOT_ATTENDING` or undefined.
32
+ *
33
+ * @param {AttendanceState | undefined} attendanceState The row's current attendance state.
34
+ * @param {boolean} isInFlight Whether an attendance action for this row is awaiting resolution.
35
+ * @param {string | undefined} meetingId The row's meeting identifier.
36
+ * @return {AttendanceAction} The action to emit, or `NONE` when non-actionable.
37
+ */
38
+ export declare function resolveAttendanceAction(attendanceState: AttendanceState | undefined, isInFlight: boolean, meetingId: string | undefined): AttendanceAction;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Pure luxon date/time formatting helpers for the dashboard library.
3
+ *
4
+ * The real "events & meetings" backend returns date-times as ISO-8601 strings
5
+ * (e.g. "2026-01-01T00:00:00") together with a display timezone string
6
+ * (e.g. "(UTC-04:00) America/New_York"). These helpers parse the ISO value and
7
+ * render it in the item's own zone (never moment). A numeric epoch-milliseconds
8
+ * value is also accepted for backward compatibility with fixtures/tests. All
9
+ * helpers fall back gracefully to an empty string when the value is missing or
10
+ * unparseable.
11
+ */
12
+ import { DateTime } from 'luxon';
13
+ /**
14
+ * Extracts a luxon-usable IANA zone from a backend timezone string such as
15
+ * "(UTC-04:00) America/New_York" or "America/New_York". Returns `undefined`
16
+ * when no usable zone can be derived so luxon falls back to the local zone.
17
+ * @param {string | undefined} timezone - The backend timezone string.
18
+ * @return {string | undefined} The IANA zone id, or undefined.
19
+ */
20
+ export declare function extractIanaZone(timezone: string | undefined): string | undefined;
21
+ /**
22
+ * Parses a backend date-time value — either an ISO-8601 string
23
+ * (e.g. "2026-01-01T00:00:00") or an epoch-milliseconds number — into a luxon
24
+ * {@link DateTime} in the supplied timezone. The ISO value is treated as a wall
25
+ * time in the item's zone so the rendered time matches the backend's intent.
26
+ * Returns an invalid DateTime when the value is missing/unparseable.
27
+ * @param {string | number | undefined | null} value - The ISO string or epoch ms.
28
+ * @param {string | undefined} timezone - The backend timezone string.
29
+ * @return {DateTime} The parsed luxon DateTime (may be invalid).
30
+ */
31
+ export declare function parseBackendDateTime(value: string | number | undefined | null, timezone?: string): DateTime;
32
+ /**
33
+ * Formats a backend date-time value (ISO string or epoch ms) with luxon using
34
+ * the supplied format token, honouring the supplied timezone. Returns an empty
35
+ * string when the value is missing or invalid.
36
+ * @param {string | number | undefined | null} value - The ISO string or epoch ms.
37
+ * @param {string} format - The luxon `toFormat` token.
38
+ * @param {string | undefined} timezone - The backend timezone string.
39
+ * @return {string} The formatted value, or an empty string.
40
+ */
41
+ export declare function formatBackendDateTime(value: string | number | undefined | null, format: string, timezone?: string): string;
42
+ /**
43
+ * Backward-compatible alias kept for existing callers. Accepts ISO strings or
44
+ * epoch ms and formats them in the item's zone.
45
+ * @param {string | number | undefined | null} value - The ISO string or epoch ms.
46
+ * @param {string} format - The luxon `toFormat` token.
47
+ * @param {string | undefined} timezone - The backend timezone string.
48
+ * @return {string} The formatted value, or an empty string.
49
+ */
50
+ export declare function formatEpoch(value: string | number | undefined | null, format: string, timezone?: string): string;
51
+ /**
52
+ * Computes a human "Day X of Y" indicator for a multi-day event from its start
53
+ * and end date-times, relative to "now". Returns an empty string when the dates
54
+ * are missing/invalid or the event spans a single day.
55
+ * @param {string | number | undefined | null} start - Event start (ISO or epoch ms).
56
+ * @param {string | number | undefined | null} end - Event end (ISO or epoch ms).
57
+ * @param {string | undefined} timezone - The backend timezone string.
58
+ * @return {string} The "Day X of Y" label, or an empty string.
59
+ */
60
+ export declare function computeDayIndicator(start: string | number | undefined | null, end: string | number | undefined | null, timezone?: string): string;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Pure mappers converting the raw "events & meetings" backend list/detail items
3
+ * into the dashboard view models. No Angular / service dependencies so they are
4
+ * trivially unit-testable and shared by {@link DashboardService}.
5
+ *
6
+ * Backend dates are epoch milliseconds; callers format them with luxon
7
+ * `DateTime.fromMillis` at render time. The list mapper enforces the dashboard
8
+ * rule that STANDALONE_MEETING rows always sort LAST within the section.
9
+ */
10
+ import { EventMeetingDetailItem, EventMeetingListItem } from '../models/event-meeting-list-item.model';
11
+ import { MeetingRow } from '../models/meeting-row.model';
12
+ import { UpcomingEvent, UpcomingItemKind } from '../models/upcoming-event.model';
13
+ /**
14
+ * Determines whether a raw list item is an EVENT (owns child meetings) or a
15
+ * STANDALONE_MEETING. An item is an EVENT when the backend flags it with
16
+ * `isMeetingEvent === true` or `hasChildren === true`.
17
+ * @param {EventMeetingListItem} item - The raw list item.
18
+ * @return {UpcomingItemKind} The resolved render kind.
19
+ */
20
+ export declare function resolveItemKind(item: EventMeetingListItem): UpcomingItemKind;
21
+ /**
22
+ * Maps a raw backend list item to the {@link UpcomingEvent} view model. EVENT
23
+ * rows start with an empty `meetings` array (child meetings are lazily loaded on
24
+ * expand); STANDALONE_MEETING rows carry their own group/location fields.
25
+ * @param {EventMeetingListItem} item - The raw list item.
26
+ * @return {UpcomingEvent} The mapped view model.
27
+ */
28
+ export declare function mapListItemToUpcomingEvent(item: EventMeetingListItem): UpcomingEvent;
29
+ /**
30
+ * Maps a raw backend list of events & meetings into the dashboard view models,
31
+ * enforcing that STANDALONE_MEETING rows always sort LAST while preserving the
32
+ * backend's within-group ordering for each partition (stable sort).
33
+ * @param {EventMeetingListItem[]} items - The raw list items.
34
+ * @return {UpcomingEvent[]} The mapped, standalone-last ordered view models.
35
+ */
36
+ export declare function mapEventMeetingList(items: EventMeetingListItem[]): UpcomingEvent[];
37
+ /**
38
+ * Maps a raw backend child-meeting detail item to the {@link MeetingRow} view
39
+ * model used by the event block's expanded meeting list.
40
+ * @param {EventMeetingDetailItem} item - The raw child-meeting item.
41
+ * @return {MeetingRow} The mapped meeting row.
42
+ */
43
+ export declare function mapDetailItemToMeetingRow(item: EventMeetingDetailItem): MeetingRow;
44
+ /**
45
+ * Maps a raw backend list of child-meeting detail items to {@link MeetingRow}
46
+ * view models.
47
+ * @param {EventMeetingDetailItem[]} items - The raw child-meeting items.
48
+ * @return {MeetingRow[]} The mapped meeting rows.
49
+ */
50
+ export declare function mapEventMeetingDetails(items: EventMeetingDetailItem[]): MeetingRow[];
package/package.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "name": "@ieeesa-npm/dashboard",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.2",
4
+ "peerDependencies": {
5
+ "@angular/common": "^16.1.0",
6
+ "@angular/core": "^16.1.0",
7
+ "@angular/material": "^16.1.3",
8
+ "@angular/router": "^16.1.0",
9
+ "@ieeesa-npm/models": ">=0.1.4",
10
+ "@ieeesa-npm/presentation": ">=0.33.33",
11
+ "@ieeesa-npm/shared-styles": ">=0.7.5",
12
+ "@ieeesa-npm/utility": ">=0.12.17",
13
+ "luxon": "^3.7.2",
14
+ "rxjs": "~7.8.0"
15
+ },
16
+ "dependencies": {
17
+ "tslib": "^2.3.0"
18
+ },
19
+ "sideEffects": false,
20
+ "module": "fesm2022/ieeesa-npm-dashboard.mjs",
21
+ "typings": "index.d.ts",
22
+ "exports": {
23
+ "./package.json": {
24
+ "default": "./package.json"
25
+ },
26
+ ".": {
27
+ "types": "./index.d.ts",
28
+ "esm2022": "./esm2022/ieeesa-npm-dashboard.mjs",
29
+ "esm": "./esm2022/ieeesa-npm-dashboard.mjs",
30
+ "default": "./fesm2022/ieeesa-npm-dashboard.mjs"
31
+ }
32
+ }
6
33
  }
@@ -0,0 +1,26 @@
1
+ export * from './lib/dashboard.component';
2
+ export * from './lib/components/welcome-header/dashboard-welcome-header.component';
3
+ export * from './lib/components/upcoming-meetings-events/upcoming-meetings-events.component';
4
+ export * from './lib/components/event-group-card/event-group-card.component';
5
+ export * from './lib/components/meeting-attendance-row/meeting-attendance-row.component';
6
+ export * from './lib/components/your-groups/your-groups.component';
7
+ export * from './lib/components/member-level-badge/member-level-badge.component';
8
+ export * from './lib/dashboard.module';
9
+ export * from './lib/dashboard.routes';
10
+ export * from './lib/models/dashboard-route-config.model';
11
+ export * from './lib/models/attendance-state.model';
12
+ export * from './lib/models/dashboard-section-state.model';
13
+ export * from './lib/models/page-change.model';
14
+ export * from './lib/models/dashboard-user-info.model';
15
+ export * from './lib/models/meeting-row.model';
16
+ export * from './lib/models/upcoming-event.model';
17
+ export * from './lib/models/event-meeting-list-item.model';
18
+ export * from './lib/models/event-meeting-list-payload.model';
19
+ export * from './lib/models/group-row.model';
20
+ export * from './lib/models/attendance-action-permissions.model';
21
+ export * from './lib/models/dashboard-list-response.model';
22
+ export * from './lib/services/dashboard.service';
23
+ export * from './lib/services/user-permissions.service';
24
+ export * from './lib/assets/dashboard-route-paths.mock';
25
+ export * from './lib/assets/dashboard-user-permissions.mock';
26
+ export * from './lib/assets/dashboard-mock';