Skip to content

Commit

Permalink
feat: Implement NgRx Entity Adapter for CRUD operations on Article, M…
Browse files Browse the repository at this point in the history
…ember and Event entities; improve custom sort algorithm to take in secondary key & set as last name in members table
  • Loading branch information
mwiraszka committed Jan 19, 2025
1 parent 80109df commit b007a20
Show file tree
Hide file tree
Showing 62 changed files with 828 additions and 715 deletions.
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@
"@angular/service-worker": "19.0.2",
"@googlemaps/js-api-loader": "1.16.8",
"@ngneat/until-destroy": "10.0.0",
"@ngrx/effects": "19.0.0-beta.0",
"@ngrx/operators": "19.0.0-beta.0",
"@ngrx/router-store": "19.0.0-beta.0",
"@ngrx/store": "19.0.0-beta.0",
"@ngrx/store-devtools": "19.0.0-beta.0",
"@ngrx/effects": "19.0.0",
"@ngrx/entity": "19.0.0",
"@ngrx/operators": "19.0.0",
"@ngrx/router-store": "19.0.0",
"@ngrx/store": "19.0.0",
"@ngrx/store-devtools": "19.0.0",
"@types/lodash": "4.17.13",
"@types/ws": "8.5.13",
"@webcomponents/webcomponentsjs": "2.8.0",
Expand Down Expand Up @@ -87,4 +88,4 @@
"typescript-eslint": "^8.18.2",
"yargs": "17.7.2"
}
}
}
187 changes: 99 additions & 88 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/app/components/article-grid/article-grid.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ lcc-link-list,
position: absolute;
margin: 4px;
color: var(--lcc-color--articleGrid-bookmarkIcon);
height: 18px;
width: 18px;
height: 24px;
width: 24px;
}

.article-summary-container {
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/member-form/member-form.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
}
</div>

@if (!vm.isSafeMode) {
@if (!(isSafeMode$ | async)) {
<label for="email-input">Email:</label>
<input
id="email-input"
Expand Down Expand Up @@ -207,7 +207,7 @@

<aside class="lcc-required-fields-legend">Required fields</aside>

@if (vm.isSafeMode) {
@if (isSafeMode$ | async) {
<aside class="safe-mode-notice">
<i-feather
name="check-circle"
Expand Down
6 changes: 5 additions & 1 deletion src/app/components/member-form/member-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type {
MemberFormGroup,
} from '@app/models';
import { DialogService } from '@app/services';
import { AppSelectors } from '@app/store/app';
import { MembersActions, MembersSelectors } from '@app/store/members';
import { isDefined } from '@app/utils';
import {
Expand All @@ -53,9 +54,12 @@ import { newMemberFormTemplate } from './new-member-form-template';
],
})
export class MemberFormComponent implements OnInit {
// TODO: Figure out why this doesn't work when a part of the config$ view model selector
public readonly isSafeMode$ = this.store.select(AppSelectors.selectIsSafeMode);
public readonly memberFormViewModel$ = this.store.select(
MembersSelectors.selectMemberFormViewModel,
);

public form: FormGroup<MemberFormGroup<MemberFormData>> | null = null;

private controlMode: ControlMode | null = null;
Expand Down Expand Up @@ -173,7 +177,7 @@ export class MemberFormComponent implements OnInit {
}),
rating: new FormControl(memberFormData.rating, {
nonNullable: true,
validators: [Validators.required, ratingValidator, Validators.max(3000)],
validators: [Validators.required, ratingValidator],
}),
dateJoined: new FormControl(memberFormData.dateJoined, {
nonNullable: true,
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/member-form/new-member-form-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const newMemberFormTemplate: MemberFormData = {
lastName: '',
city: 'London',
rating: '1000/0',
peakRating: '1000/0',
peakRating: '',
dateJoined: moment().toISOString(),
isActive: true,
chessComUsername: '',
Expand Down
206 changes: 104 additions & 102 deletions src/app/components/members-table/members-table.component.html
Original file line number Diff line number Diff line change
@@ -1,109 +1,111 @@
@if (membersTableViewModel$ | async; as vm) {
@if (vm.isAdmin) {
<lcc-link-list [links]="[addMemberLink]"></lcc-link-list>
}
@if (members$ | async; as members) {
@if (config$ | async; as config) {
@if (config.isAdmin) {
<lcc-link-list [links]="[addMemberLink]"></lcc-link-list>
}

<div class="lcc-table-wrapper">
<table
class="lcc-table"
[class.lcc-show-admin-columns]="vm.isAdmin && !vm.isSafeMode">
@if (vm.members.displayed.length) {
<thead>
<tr>
<th class="row-number"></th>
@for (
header of vm.isAdmin && !vm.isSafeMode
? tableHeaders
: tableHeaders.slice(0, -4);
track header
) {
<th
[ngClass]="header | kebabCase"
(click)="onSelectTableHeader(header)">
<span>{{ header }}</span>
<div class="sort-icon-container">
@if (vm.sortedBy === (header | camelCase)) {
<i-feather
class="sort-icon"
[name]="vm.isAscending ? 'chevron-up' : 'chevron-down'">
</i-feather>
}
</div>
</th>
}
</tr>
</thead>
}
<div class="lcc-table-wrapper">
<table
class="lcc-table"
[class.lcc-show-admin-columns]="config.isAdmin && !(isSafeMode$ | async)">
@if (members.displayed.length) {
<thead>
<tr>
<th class="row-number"></th>
@for (
header of config.isAdmin && !(isSafeMode$ | async)
? tableHeaders
: tableHeaders.slice(0, -4);
track header
) {
<th
[ngClass]="header | kebabCase"
(click)="onSelectTableHeader(header)">
<span>{{ header }}</span>
<div class="sort-icon-container">
@if (config.sortedBy === (header | camelCase)) {
<i-feather
class="sort-icon"
[name]="config.isAscending ? 'chevron-up' : 'chevron-down'">
</i-feather>
}
</div>
</th>
}
</tr>
</thead>
}

<tbody>
@for (
member of vm.members.displayed;
track member.id;
let i = $index;
let last = $last
) {
<tr
[adminControls]="vm.isAdmin ? getAdminControlsConfig(member) : null"
[ngClass]="{ 'last-row': last }"
[class.inactive]="!member.isActive">
<td class="row-number">
{{ i + vm.startIndex + 1 }}
</td>
<td class="first-name lcc-truncate">{{ member.firstName }}</td>
<td class="last-name lcc-truncate">{{ member.lastName }}</td>
<td class="rating">{{ member.rating }}</td>
<td class="peak-rating">{{ member.peakRating }}</td>
<td class="city lcc-truncate">{{ member.city }}</td>
<td class="chess-com-username lcc-truncate">
{{ member?.chessComUsername }}
</td>
<td class="lichess-username lcc-truncate">
{{ member?.lichessUsername }}
</td>
<td class="last-updated">
{{ member.modificationInfo.dateLastEdited | formatDate: 'short no-time' }}
</td>
@if (vm.isAdmin) {
<td class="born">{{ member?.yearOfBirth }}</td>
<td class="email">{{ member?.email }}</td>
<td class="phone-number">{{ member?.phoneNumber }}</td>
<td class="date-joined">
{{ member.dateJoined | formatDate: 'short no-time' }}
<tbody>
@for (
member of members.displayed;
track member.id;
let i = $index;
let last = $last
) {
<tr
[adminControls]="config.isAdmin ? getAdminControlsConfig(member) : null"
[ngClass]="{ 'last-row': last }"
[class.inactive]="!member.isActive">
<td class="row-number">
{{ i + config.startIndex + 1 }}
</td>
}
</tr>
}
</tbody>
<td class="first-name lcc-truncate">{{ member.firstName }}</td>
<td class="last-name lcc-truncate">{{ member.lastName }}</td>
<td class="rating">{{ member.rating }}</td>
<td class="peak-rating">{{ member.peakRating }}</td>
<td class="city lcc-truncate">{{ member.city }}</td>
<td class="chess-com-username lcc-truncate">
{{ member?.chessComUsername }}
</td>
<td class="lichess-username lcc-truncate">
{{ member?.lichessUsername }}
</td>
<td class="last-updated">
{{ member.modificationInfo.dateLastEdited | formatDate: 'short no-time' }}
</td>
@if (config.isAdmin) {
<td class="born">{{ member?.yearOfBirth }}</td>
<td class="email">{{ member?.email }}</td>
<td class="phone-number">{{ member?.phoneNumber }}</td>
<td class="date-joined">
{{ member.dateJoined | formatDate: 'short no-time' }}
</td>
}
</tr>
}
</tbody>

<tfoot>
<tr>
<td colspan="100%">
{{ vm.members.all.length }} members ({{ vm.members.active.length }}
active)
</td>
</tr>
</tfoot>
</table>
</div>
<tfoot>
<tr>
<td colspan="100%">
{{ members.all.length }} members ({{ members.active.length }}
active)
</td>
</tr>
</tfoot>
</table>
</div>

<lcc-paginator
typeOfItems="members"
[pageNum]="vm.pagination.pageNum"
[pageSize]="vm.pagination.pageSize"
[totalItems]="vm.members.filtered.length"
(pageChange)="onChangePage($event)"
(pageSizeChange)="onChangePageSize($event)">
</lcc-paginator>
<lcc-paginator
typeOfItems="members"
[pageNum]="config.pageNum"
[pageSize]="config.pageSize"
[totalItems]="members.filtered.length"
(pageChange)="onChangePage($event)"
(pageSizeChange)="onChangePageSize($event)">
</lcc-paginator>

<aside>
<h5>inactive members *</h5>
<div class="checkbox-section">
<label for="active-only">Show active members only</label>
<input
id="active-only"
type="checkbox"
[checked]="vm.showActiveOnly"
(change)="onToggleInactiveMembers()" />
</div>
</aside>
<aside>
<h5>inactive members *</h5>
<div class="checkbox-section">
<label for="active-only">Show active members only</label>
<input
id="active-only"
type="checkbox"
[checked]="config.showActiveOnly"
(change)="onToggleInactiveMembers()" />
</div>
</aside>
}
}
10 changes: 8 additions & 2 deletions src/app/components/members-table/members-table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
} from '@app/models';
import { CamelCasePipe, FormatDatePipe, KebabCasePipe } from '@app/pipes';
import { DialogService } from '@app/services';
import { AppSelectors } from '@app/store/app';
import { MembersActions, MembersSelectors } from '@app/store/members';

@Component({
Expand All @@ -41,8 +42,13 @@ export class MembersTableComponent implements OnInit {
text: 'Add a member',
icon: 'plus-circle',
};
public readonly membersTableViewModel$ = this.store.select(
MembersSelectors.selectMembersTableViewModel,
public readonly config$ = this.store.select(
MembersSelectors.selectMembersTableConfigViewModel,
);
// TODO: Figure out why this doesn't work when a part of the config$ view model selector
public readonly isSafeMode$ = this.store.select(AppSelectors.selectIsSafeMode);
public readonly members$ = this.store.select(
MembersSelectors.selectMembersTableMembersViewModel,
);
public readonly tableHeaders = [
'First Name',
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/schedule/schedule.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<tbody>
@for (
event of vm.showPastEvents && allowTogglePastEvents
? vm.events
? vm.allEvents
: vm.upcomingEvents.slice(0, upcomingEventLimit ?? vm.upcomingEvents.length);
track event.id
) {
Expand Down
1 change: 0 additions & 1 deletion src/app/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,5 @@ export type {
PasswordChangeResponse,
} from './password-change.model';
export type { Photo } from './photo.model';
export type { StoreFeature } from './store-features.model';
export type { Toast } from './toast.model';
export type { AdminUser, UnverifiedUser, User } from './user.model';
14 changes: 0 additions & 14 deletions src/app/models/store-features.model.ts

This file was deleted.

5 changes: 2 additions & 3 deletions src/app/store/app/app-store.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';

import { AppEffects } from './app.effects';
import { appReducer } from './app.reducer';
import { AppState } from './app.state';
import { AppState, appReducer } from './app.reducer';

@NgModule({
imports: [
CommonModule,
EffectsModule.forFeature([AppEffects]),
StoreModule.forFeature<AppState>('app', appReducer),
StoreModule.forFeature<AppState>('appState', appReducer),
],
})
export class AppStoreModule {}
2 changes: 1 addition & 1 deletion src/app/store/app/app.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const itemSetInLocalStorage = createAction(
);

export const itemRemovedFromLocalStorage = createAction(
'[Images] Item removed from local storage',
'[App] Item removed from local storage',
props<{ key: string }>(),
);

Expand Down
Loading

0 comments on commit b007a20

Please sign in to comment.