@ieeesa-npm/dashboard 0.0.1 → 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 (38) hide show
  1. package/esm2022/lib/assets/constants.json +23 -6
  2. package/esm2022/lib/assets/dashboard-mock.mjs +5 -1
  3. package/esm2022/lib/assets/dashboard-route-paths.mock.mjs +7 -5
  4. package/esm2022/lib/components/event-group-card/event-group-card.component.mjs +168 -31
  5. package/esm2022/lib/components/meeting-attendance-row/meeting-attendance-row.component.mjs +25 -24
  6. package/esm2022/lib/components/upcoming-meetings-events/upcoming-meetings-events.component.mjs +103 -5
  7. package/esm2022/lib/components/welcome-header/dashboard-welcome-header.component.mjs +3 -3
  8. package/esm2022/lib/components/your-groups/your-groups.component.mjs +157 -120
  9. package/esm2022/lib/dashboard.component.mjs +54 -14
  10. package/esm2022/lib/models/event-meeting-list-item.model.mjs +2 -0
  11. package/esm2022/lib/models/event-meeting-list-payload.model.mjs +8 -0
  12. package/esm2022/lib/models/group-row.model.mjs +1 -1
  13. package/esm2022/lib/models/meeting-row.model.mjs +1 -1
  14. package/esm2022/lib/models/my-groups-view.model.mjs +2 -0
  15. package/esm2022/lib/models/upcoming-event.model.mjs +13 -2
  16. package/esm2022/lib/services/dashboard.service.mjs +229 -35
  17. package/esm2022/lib/utils/date-format.util.mjs +110 -0
  18. package/esm2022/lib/utils/event-meeting-mapper.util.mjs +96 -0
  19. package/esm2022/public-api.mjs +3 -1
  20. package/fesm2022/ieeesa-npm-dashboard.mjs +1193 -446
  21. package/fesm2022/ieeesa-npm-dashboard.mjs.map +1 -1
  22. package/lib/components/event-group-card/event-group-card.component.d.ts +100 -132
  23. package/lib/components/meeting-attendance-row/meeting-attendance-row.component.d.ts +8 -132
  24. package/lib/components/member-level-badge/member-level-badge.component.d.ts +19 -3
  25. package/lib/components/upcoming-meetings-events/upcoming-meetings-events.component.d.ts +63 -126
  26. package/lib/components/your-groups/your-groups.component.d.ts +104 -55
  27. package/lib/dashboard.component.d.ts +48 -10
  28. package/lib/models/event-meeting-list-item.model.d.ts +107 -0
  29. package/lib/models/event-meeting-list-payload.model.d.ts +40 -0
  30. package/lib/models/group-row.model.d.ts +25 -0
  31. package/lib/models/meeting-row.model.d.ts +18 -2
  32. package/lib/models/my-groups-view.model.d.ts +56 -0
  33. package/lib/models/upcoming-event.model.d.ts +40 -2
  34. package/lib/services/dashboard.service.d.ts +97 -14
  35. package/lib/utils/date-format.util.d.ts +60 -0
  36. package/lib/utils/event-meeting-mapper.util.d.ts +50 -0
  37. package/package.json +1 -1
  38. package/public-api.d.ts +2 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Raw list item returned by the real "events & meetings" backend
3
+ * (`POST {apiBaseUrl}/meetings/eventsAndMeetings`). A single list may contain
4
+ * both EVENT rows (`isMeetingEvent === true` / `hasChildren === true`, which own
5
+ * child meetings) and STANDALONE MEETING rows (`isMeetingEvent === false`).
6
+ *
7
+ * Dates are epoch milliseconds (not ISO strings) and must be formatted with
8
+ * luxon `DateTime.fromMillis`. This shape mirrors the `events-and-meetings`
9
+ * library's `EventsAndMeetings` list model so the dashboard reuses the exact
10
+ * backend contract referenced by the existing
11
+ * `view-events-meetings-list` screen.
12
+ */
13
+ export interface EventMeetingListItem {
14
+ /** Stable identifier of the event or standalone meeting. */
15
+ id: string;
16
+ /** Event / meeting display name. */
17
+ name: string;
18
+ /** Start date-time as an ISO-8601 string (or epoch ms for legacy fixtures). */
19
+ startDateTime: string | number;
20
+ /** End date-time as an ISO-8601 string (or epoch ms for legacy fixtures). */
21
+ endDateTime: string | number;
22
+ /** IANA / offset-prefixed timezone string, e.g. "(UTC+05:30) Asia/Kolkata". */
23
+ timeZone: string;
24
+ /** Owning group id. */
25
+ groupId?: string;
26
+ /** Owning group display name. */
27
+ groupName?: string;
28
+ /** Owning group acronym, when provided. */
29
+ groupAcronym?: string;
30
+ /** Room / location text, when provided. */
31
+ meetingLocation?: string | null;
32
+ /** True when this row is an EVENT that owns child meetings. */
33
+ hasChildren?: boolean;
34
+ /**
35
+ * True when the row represents an EVENT (vs. a standalone meeting). Combined
36
+ * with {@link hasChildren} this is the EVENT-vs-STANDALONE discriminator.
37
+ */
38
+ isMeetingEvent?: boolean;
39
+ /** Lifecycle status, e.g. "In Progress", "Upcoming", "Starts Soon". */
40
+ status?: string;
41
+ /** True when the signed-in user is a member of the owning group. */
42
+ isMemberOfGroup?: boolean;
43
+ /** True when the current user is currently attending (drives the pill). */
44
+ isCurrentlyAttending?: boolean;
45
+ /** True when the row is open for attendance logging. */
46
+ isOpenForAttendance?: boolean;
47
+ /** Event id a standalone/child meeting belongs to, when applicable. */
48
+ eventId?: string;
49
+ /** Meeting id, when the row is a meeting. */
50
+ meetingId?: string;
51
+ /** Alternate name key some payloads use for meetings. */
52
+ meetingTitle?: string;
53
+ /** Alternate status key (the list screen copies status here). */
54
+ contentUnderLink?: string;
55
+ /** Alternate location key used by some meeting payloads. */
56
+ room?: string | null;
57
+ /** Count of child meetings for an event, when the backend provides it. */
58
+ meetingCount?: number;
59
+ /** Count of child meetings open for attendance, when provided. */
60
+ openForAttendanceCount?: number;
61
+ /** "Day X of Y" indicator string, when the backend provides it. */
62
+ dayIndicator?: string;
63
+ }
64
+ /**
65
+ * Response envelope for the dashboard events-&-meetings list endpoint, matching
66
+ * the `events-and-meetings` library's `GetEventsAndMeetingsResponse` body shape.
67
+ */
68
+ export interface EventMeetingListResponse {
69
+ status: boolean;
70
+ message?: string;
71
+ body: {
72
+ totalRecords: number;
73
+ eventMeetingList: EventMeetingListItem[];
74
+ };
75
+ }
76
+ /**
77
+ * Child-meeting list item returned by the per-event expand endpoint
78
+ * (`POST {apiBaseUrl}/meetings/meetingsDetails`). Dates are epoch milliseconds.
79
+ * Mirrors the `events-and-meetings` library's `Meeting` model.
80
+ */
81
+ export interface EventMeetingDetailItem {
82
+ meetingId: string;
83
+ meetingTitle: string;
84
+ meetingStDate: string | number;
85
+ meetingEdDate: string | number;
86
+ meetingLocation?: string;
87
+ groupId?: string;
88
+ groupName?: string;
89
+ groupAcronym?: string;
90
+ timeZone?: string;
91
+ status?: string;
92
+ isMemberOfGroup?: boolean;
93
+ isCurrentlyAttending?: boolean;
94
+ isOpenForAttendance?: boolean;
95
+ }
96
+ /**
97
+ * Response envelope for the per-event child-meetings expand endpoint, matching
98
+ * the `events-and-meetings` library's `GetMeetingsByEventIdResponse` body shape.
99
+ */
100
+ export interface EventMeetingDetailResponse {
101
+ status: boolean;
102
+ message?: string;
103
+ body: {
104
+ totalRecords: number;
105
+ meetings: EventMeetingDetailItem[];
106
+ };
107
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Request payloads for the dashboard "events & meetings" backend calls. These
3
+ * mirror the `events-and-meetings` library's `GetEventsAndMeetingsPayload` /
4
+ * `GetMeetingsByEventIdPayload` so the dashboard reuses the exact contract the
5
+ * existing `view-events-meetings-list` screen posts.
6
+ */
7
+ /** Filter block shared by the list and expand payloads. */
8
+ export interface EventMeetingFilter {
9
+ /** Lifecycle statuses to include, e.g. ["In Progress", "Upcoming"]. */
10
+ status: string[];
11
+ /** Whether every meeting (regardless of membership) should be returned. */
12
+ isAllMeetingSelected: boolean;
13
+ /** Optional group-id scope. */
14
+ groupIds?: string[];
15
+ }
16
+ /** Payload for `POST {apiBaseUrl}/meetings/eventsAndMeetings` (the section list). */
17
+ export interface EventMeetingListPayload {
18
+ payload: {
19
+ /** Null for the top-level list; an event id when scoping to one event. */
20
+ eventId: string | null;
21
+ sortColumn: string;
22
+ ascendingOrder: boolean;
23
+ page: number;
24
+ size: number;
25
+ searchTerm?: string | null;
26
+ filter: EventMeetingFilter;
27
+ };
28
+ }
29
+ /** Payload for `POST {apiBaseUrl}/meetings/meetingsDetails` (per-event expand). */
30
+ export interface EventMeetingDetailPayload {
31
+ payload: {
32
+ eventId: string;
33
+ sortColumn: string;
34
+ ascendingOrder: boolean;
35
+ page: number;
36
+ size: number;
37
+ searchTerm?: string | null;
38
+ filter: EventMeetingFilter;
39
+ };
40
+ }
@@ -1,3 +1,26 @@
1
+ /**
2
+ * A single node in a group's ancestor hierarchy (root→leaf), as returned by
3
+ * GET /v1/groups/my-groups. Used to render the expandable hierarchy rows in the
4
+ * Your Groups table.
5
+ */
6
+ export interface GroupHierarchyNode {
7
+ groupId: string;
8
+ groupName: string;
9
+ groupShortName?: string;
10
+ groupAcronym?: string;
11
+ groupLevel?: number;
12
+ groupParentId?: string | null;
13
+ groupType?: string;
14
+ groupTypeName?: string | null;
15
+ groupDesc?: string | null;
16
+ hasChildren?: boolean | null;
17
+ }
18
+ /**
19
+ * Table-facing view of a group in the Your Groups section. The display fields
20
+ * are mapped from the backend; `hierarchy` carries the group's ancestor path so
21
+ * the row can expand to show the hierarchy (matching the My Profile Find Groups
22
+ * tree).
23
+ */
1
24
  export interface GroupRow {
2
25
  groupId: string;
3
26
  group: string;
@@ -5,4 +28,6 @@ export interface GroupRow {
5
28
  officerRole?: string;
6
29
  affiliation?: string;
7
30
  memberLevel: string;
31
+ /** Ancestor hierarchy (root→leaf) for the expandable hierarchy rows. */
32
+ hierarchy?: GroupHierarchyNode[];
8
33
  }
@@ -1,12 +1,28 @@
1
1
  import { AttendanceState } from './attendance-state.model';
2
+ /**
3
+ * View model for a single meeting row, used both for an event's expanded child
4
+ * meetings and (via {@link UpcomingEvent}) for standalone meeting cards.
5
+ *
6
+ * `startEpochMs` / `endEpochMs` carry the raw epoch-milliseconds values from the
7
+ * backend and are formatted with luxon `DateTime.fromMillis`. The legacy ISO
8
+ * `meetingDate` / `meetingTime` fields are retained for existing fixtures/tests.
9
+ */
2
10
  export interface MeetingRow {
3
11
  meetingId: string;
4
12
  meetingName: string;
5
13
  groupOrCommittee?: string;
6
14
  roomOrLocation?: string;
7
- meetingDate: string;
8
- meetingTime: string;
15
+ /** Start date-time as an ISO-8601 string (or epoch ms for fixtures). */
16
+ startDateTime?: string | number;
17
+ /** End date-time as an ISO-8601 string (or epoch ms for fixtures). */
18
+ endDateTime?: string | number;
19
+ /** Legacy ISO meeting date, retained for fixtures/tests. */
20
+ meetingDate?: string;
21
+ /** Legacy ISO meeting time, retained for fixtures/tests. */
22
+ meetingTime?: string;
9
23
  timezone: string;
24
+ status?: string;
25
+ isMemberOfGroup?: boolean;
10
26
  isOpenForAttendance?: boolean;
11
27
  attendanceState?: AttendanceState;
12
28
  }
@@ -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
+ }
@@ -1,13 +1,51 @@
1
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
+ */
2
25
  export interface UpcomingEvent {
3
26
  eventId: string;
27
+ /** EVENT vs. STANDALONE_MEETING render discriminator. */
28
+ kind: UpcomingItemKind;
4
29
  title: string;
5
30
  status: string;
6
31
  dayIndicator?: string;
7
32
  meetingCount: number;
8
33
  openForAttendanceCount: number;
9
- startDate: string;
10
- endDate: string;
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;
11
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). */
12
50
  meetings: MeetingRow[];
13
51
  }
@@ -5,7 +5,9 @@ import { AttendanceState } from '../models/attendance-state.model';
5
5
  import { DashboardListResponse } from '../models/dashboard-list-response.model';
6
6
  import { DashboardSectionState } from '../models/dashboard-section-state.model';
7
7
  import { GroupRow } from '../models/group-row.model';
8
+ import { MyGroupsView } from '../models/my-groups-view.model';
8
9
  import { UpcomingEvent } from '../models/upcoming-event.model';
10
+ import { EventMeetingDetailResponse, EventMeetingListResponse } from '../models/event-meeting-list-item.model';
9
11
  import * as i0 from "@angular/core";
10
12
  /**
11
13
  * DashboardService is the single source of truth for the dashboard landing
@@ -16,11 +18,12 @@ import * as i0 from "@angular/core";
16
18
  * through the shared `HttpService` from `@ieeesa-npm/utility` (never
17
19
  * `HttpClient` directly).
18
20
  *
19
- * The upcoming-events and groups loads currently return annotated mock data
20
- * because their endpoints are not yet available; the attendance actions compose
21
- * the pure reducers from `attendance-reducers.ts` so the displayed
22
- * `AttendanceState` only advances on a successful resolution and the prior
23
- * state is retained on failure.
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.
24
27
  * @export
25
28
  * @class DashboardService
26
29
  */
@@ -58,16 +61,75 @@ export declare class DashboardService {
58
61
  */
59
62
  setAppConfig(apiBaseURL: string, userPermissions: UserPermission[]): void;
60
63
  /**
61
- * Loads the user's upcoming events, driving the upcoming section through
62
- * LOADING → LOADED/EMPTY on success and leaving the caller to set ERROR on
63
- * failure. Follows the `pipe(map, catchError(throw))` pattern and seeds the
64
- * per-meeting attendance-state map from the loaded meetings.
65
- *
66
- * TODO: Replace mock with real API call when BE is ready
67
- * @return {Observable<DashboardListResponse<UpcomingEvent[]>>} The upcoming-events response envelope.
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}
68
109
  * @memberof DashboardService
69
110
  */
70
- loadUpcomingEvents(): Observable<DashboardListResponse<UpcomingEvent[]>>;
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;
71
133
  /**
72
134
  * Loads the user's groups, driving the groups section through
73
135
  * LOADING → LOADED/EMPTY on success and leaving the caller to set ERROR on
@@ -77,7 +139,18 @@ export declare class DashboardService {
77
139
  * @return {Observable<DashboardListResponse<GroupRow[]>>} The groups response envelope.
78
140
  * @memberof DashboardService
79
141
  */
80
- loadGroups(): Observable<DashboardListResponse<GroupRow[]>>;
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;
81
154
  /**
82
155
  * Logs attendance for a meeting. Marks the meeting in-flight (retaining the
83
156
  * prior displayed state), posts through `HttpService`, applies the
@@ -114,6 +187,16 @@ export declare class DashboardService {
114
187
  * @memberof DashboardService
115
188
  */
116
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;
117
200
  static ɵfac: i0.ɵɵFactoryDeclaration<DashboardService, never>;
118
201
  static ɵprov: i0.ɵɵInjectableDeclaration<DashboardService>;
119
202
  }
@@ -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,6 @@
1
1
  {
2
2
  "name": "@ieeesa-npm/dashboard",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^16.1.0",
6
6
  "@angular/core": "^16.1.0",
package/public-api.d.ts CHANGED
@@ -14,6 +14,8 @@ export * from './lib/models/page-change.model';
14
14
  export * from './lib/models/dashboard-user-info.model';
15
15
  export * from './lib/models/meeting-row.model';
16
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';
17
19
  export * from './lib/models/group-row.model';
18
20
  export * from './lib/models/attendance-action-permissions.model';
19
21
  export * from './lib/models/dashboard-list-response.model';