Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
db8c3ff
Added methods for getting and updating the current sanctioned entitie…
robgordula Jan 15, 2024
466a645
Added endpoints for getting and updating the current sanctioned entit…
robgordula Jan 15, 2024
641e851
Added the bootstrap js into the architecture scripts
robgordula Jan 15, 2024
229a559
Added the uuid package
robgordula Jan 15, 2024
e77da27
Added reference to the current-count component for showing the curren…
robgordula Jan 15, 2024
8c48c7b
Extracted the current count view into its own component
robgordula Jan 15, 2024
2a53e1a
Added the component for the current count view
robgordula Jan 15, 2024
cce6ebd
Added a component for the modal view for adding new sactioned entity
robgordula Jan 15, 2024
7cf39dc
Added a component for the main sanctioned entity details form
robgordula Jan 15, 2024
de3b822
Added a button to show a modal form which can be used for adding a ne…
robgordula Jan 15, 2024
cb56677
Added methods for getting and updating the current count from the web…
robgordula Jan 15, 2024
9152675
Added reference to all the new components
robgordula Jan 15, 2024
a3df04a
Moved the increment button and all functionality into the current cou…
robgordula Jan 16, 2024
121c781
Added the add new entity endpoint with data validation and duplicate …
robgordula Jan 16, 2024
f79ea58
Moved the increment button and all functionality into the current cou…
robgordula Jan 16, 2024
38bfda3
Removed reference to uuid
robgordula Jan 16, 2024
f7eb88c
Added an error response handler and used it when posting a new entity
robgordula Jan 16, 2024
891d398
Added components and service for adding new sanctioned entity
robgordula Jan 16, 2024
02ba12c
Fixed failing tests
robgordula Jan 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ClientApp/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css"
],
"scripts": []
"scripts": [
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
]
},
"configurations": {
"production": {
Expand Down
2,328 changes: 2,139 additions & 189 deletions ClientApp/package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions ClientApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
"@types/jasmine": "~4.0.3",
"@types/jasminewd2": "~2.0.10",
"@types/node": "^18.0.0",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
"eslint": "^8.56.0",
"jasmine-core": "~4.2.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.1",
Expand All @@ -49,6 +53,5 @@
},
"overrides": {
"autoprefixer": "10.4.5"
},
"optionalDependencies": {}
}
}
11 changes: 9 additions & 2 deletions ClientApp/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';

Expand All @@ -10,6 +10,9 @@ import { HomeComponent } from './components/home/home.component';
import { CounterComponent } from './components/counter/counter.component';
import { SanctionedEntitiesComponent } from './components/sanctioned-entities/sanctioned-entities.component';
import { JumbotronCounterComponent } from './components/jumbotron-counter/jumbotron-counter.component';
import { CurrentCountComponent } from './components/current-count/current-count.component';
import { SanctionedEntityModal } from './components/sanctioned-entity-modal/sanctioned-entity-modal.component';
import { SanctionedEntityEditor } from './components/sanctioned-entity-editor/sanctioned-entity-editor.component';


@NgModule({
Expand All @@ -19,12 +22,16 @@ import { JumbotronCounterComponent } from './components/jumbotron-counter/jumbot
HomeComponent,
CounterComponent,
SanctionedEntitiesComponent,
JumbotronCounterComponent
JumbotronCounterComponent,
CurrentCountComponent,
SanctionedEntityModal,
SanctionedEntityEditor
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
FormsModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'counter', component: CounterComponent },
Expand Down
4 changes: 1 addition & 3 deletions ClientApp/src/app/components/counter/counter.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@ <h1>Counter</h1>

<p>This is a simple example of an Angular component.</p>

<p aria-live="polite">Current count: <strong>{{ currentCount }}</strong></p>

<button class="btn btn-primary" (click)="incrementCounter()">Increment</button>
<counter-current-count></counter-current-count>
14 changes: 3 additions & 11 deletions ClientApp/src/app/components/counter/counter.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CounterComponent } from './counter.component';
Expand All @@ -8,7 +9,8 @@ describe('CounterComponent', () => {

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ CounterComponent ]
declarations: [CounterComponent],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
});
Expand All @@ -23,14 +25,4 @@ describe('CounterComponent', () => {
const titleText = fixture.nativeElement.querySelector('h1').textContent;
expect(titleText).toEqual('Counter');
});

it('should start with count 0, then increments by 1 when clicked', () => {
const countElement = fixture.nativeElement.querySelector('strong');
expect(countElement.textContent).toEqual('0');

const incrementButton = fixture.nativeElement.querySelector('button');
incrementButton.click();
fixture.detectChanges();
expect(countElement.textContent).toEqual('1');
});
});
5 changes: 0 additions & 5 deletions ClientApp/src/app/components/counter/counter.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,4 @@ import { Component } from '@angular/core';
templateUrl: './counter.component.html'
})
export class CounterComponent {
public currentCount = 0;

public incrementCounter() {
this.currentCount++;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<p aria-live="polite">Current count: <strong>{{ currentCount }}</strong></p>

<button class="btn btn-primary" (click)="incrementCounter()">Increment</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';

import { CurrentCountComponent } from './current-count.component';
import { SanctionedEntitiesService } from '../../services/sanctioned-entities.service';

describe('CurrentCountComponent', () => {
let component: CurrentCountComponent;
let fixture: ComponentFixture<CurrentCountComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
CurrentCountComponent
],
imports: [
HttpClientTestingModule
],
providers: [
SanctionedEntitiesService,
{ provide: 'BASE_URL', useValue: '/' }
]
})
.compileComponents();

fixture = TestBed.createComponent(CurrentCountComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should start with count 0, then increments by 1 when clicked', () => {
const countElement = fixture.nativeElement.querySelector('strong');
expect(countElement.textContent).toEqual('0');

const incrementButton = fixture.nativeElement.querySelector('button');
incrementButton.click();
fixture.detectChanges();
expect(countElement.textContent).toEqual('1');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Component } from '@angular/core';

import { SanctionedEntitiesService } from '../../services/sanctioned-entities.service';

@Component({
selector: 'counter-current-count',
templateUrl: './current-count.component.html'
})
export class CurrentCountComponent {
public currentCount: number = 0;

constructor(private entitiesService: SanctionedEntitiesService) {
this.entitiesService.getSanctionedEntitiesCount()
.subscribe(count => this.currentCount = count);
}

public incrementCounter() {
this.currentCount++;
this.entitiesService.updateSanctionedEntitiesCount(this.currentCount)
.subscribe();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<div class="jumbotron text-center">
<p>Current count:</p>
<p><strong>Please include the counter here</strong></p>
<!-- <p>Current count:</p> -->
<!-- <p><strong>Please include the counter here</strong></p> -->
<counter-current-count></counter-current-count>
</div>
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { Component } from '@angular/core';

import { SanctionedEntitiesService } from '../../services/sanctioned-entities.service';

@Component({
selector: 'app-jumbotron-counter',
templateUrl: './jumbotron-counter.component.html'
})
export class JumbotronCounterComponent {
public counterCurrentCount: number = 0;

constructor(private entitiesService: SanctionedEntitiesService) {
this.entitiesService.getSanctionedEntitiesCount()
.subscribe(count => this.counterCurrentCount = count);
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
<h1 id="tableLabel">Sanctioned Entities</h1>
<div class="container">
<div class="row align-items-center">
<div class="col">
<h1 id="tableLabel">Sanctioned Entities</h1>
</div>
<div class="col">
<!-- <button class="btn btn-primary float-end">Add Entity</button> -->
<sanctioned-entity-modal [entities]="entities" (entityAdded)="onEntityAdded($event)"></sanctioned-entity-modal>
</div>
</div>
</div>

<p *ngIf="!entities"><em>Loading...</em></p>

<table class='table table-striped' aria-labelledby="tableLabel" *ngIf="entities">
<thead>
<tr>
<th>Name</th>
<th>Domicile</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let entity of entities">
<td>{{ entity.name }}</td>
<td>{{ entity.domicile }}</td>
<td *ngIf="entity.accepted" class="text-success">Accepted</td>
<td *ngIf="!entity.accepted" class="text-danger">Rejected</td>
</tr>
</tbody>
<thead>
<tr>
<th>Name</th>
<th>Domicile</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let entity of entities">
<td>{{ entity.name }}</td>
<td>{{ entity.domicile }}</td>
<td *ngIf="entity.accepted" class="text-success">Accepted</td>
<td *ngIf="!entity.accepted" class="text-danger">Rejected</td>
</tr>
</tbody>
</table>
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ export class SanctionedEntitiesComponent {
public entities: SanctionedEntity[] = [];

constructor(private entitiesService: SanctionedEntitiesService) {
entitiesService.getSanctionedEntities().subscribe(entities => {
this.entitiesService.getSanctionedEntities().subscribe(entities => {
this.entities = entities;
});
}

onEntityAdded(entity: SanctionedEntity) {
this.entities.push(entity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.ng-valid[required], .ng-valid.required {
border-left: 5px solid #42A948; /* green */
}

.ng-invalid:not(form) {
border-left: 5px solid #a94442; /* red */
}

.alert div {
background-color: #fed3d3;
color: #820000;
padding: 1rem;
margin-bottom: 1rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<form [formGroup]="entityForm">
<div class="mb-3 row">
<label for="entityName" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="entityName" formControlName="name" required />
<div *ngIf="entityForm.get('name')?.invalid && (entityForm.get('name')?.dirty || entityForm.get('name')?.touched)" class="alert alert-danger">
Name is required.
</div>
</div>
</div>
<div class="mb-3 row">
<label for="domicile" class="col-sm-2 col-form-label">Domicile</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="domicile" formControlName="domicile" required />
<div *ngIf="entityForm.get('domicile')?.invalid && (entityForm.get('domicile')?.dirty || entityForm.get('domicile')?.touched)" class="alert alert-danger">
Domicile is required.
</div>
</div>
</div>
<div class="mb-3 row align-items-center">
<label for="entityStatus" class="col-sm-2 col-form-label">Status</label>
<div class="col-sm-10">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="entityStatus" formControlName="accepted" (change)="statusSwitchChange($event)">
<label class="form-check-label" for="entityStatus">{{statusLabel}}</label>
</div>
</div>
</div>
</form>
<div *ngIf="addEntityFailed" class="alert alert-danger d-flex align-items-center" role="alert">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-exclamation-triangle-fill flex-shrink-0 me-2" viewBox="0 0 16 16" role="img" aria-label="Danger:">
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
</svg>
<div>
An entity with the name <strong>{{ entityForm.get('name')?.value }}</strong> already exist in <strong>{{ entityForm.get('name')?.value }}</strong>...
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Component, Output, EventEmitter, OnDestroy } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Validators } from '@angular/forms';

import { Subscription } from 'rxjs';

import { SanctionedEntity } from '../../models/sanctioned-entity';
import { SanctionedEntityEditorService } from '../../services/sanctioned-entity-editor.service';

@Component({
selector: 'sanctioned-entity-editor',
templateUrl: './sanctioned-entity-editor.component.html',
styleUrls: ['./sanctioned-entity-editor.component.css']
})
export class SanctionedEntityEditor implements OnDestroy {
private entityFormStatusChangeSubs: Subscription;
private addEntityRequestSubs: Subscription;
private addEntityResultSubs: Subscription;

@Output() addEntityRequest = new EventEmitter<SanctionedEntity>();
@Output() formStatusChanged = new EventEmitter<boolean>();

public addEntityFailed: boolean = false;
public statusLabel: string = 'Rejected';
public entityForm = this.formBuilder.group({
id: [''],
name: ['', Validators.required],
domicile: ['', Validators.required],
accepted: [false]
});

constructor(
private formBuilder: FormBuilder,
private entityEditorService: SanctionedEntityEditorService)
{
this.entityFormStatusChangeSubs = this.entityForm.statusChanges
.subscribe(() => this.formStatusChanged.emit(this.entityForm.valid));

this.addEntityRequestSubs = this.entityEditorService.addEntityRequest$
.subscribe(() => this.submit());

this.addEntityResultSubs = this.entityEditorService.addEntityResult$
.subscribe(success => this.submitResult(success));
}

statusSwitchChange(e: any) {
//console.info(e);
this.statusLabel = e.target.checked ? 'Accepted' : 'Rejected';
}

submit() {
const entity = <SanctionedEntity>Object.setPrototypeOf(this.entityForm.value, null);
// console.info(entity);
this.addEntityRequest.emit(entity);
}

submitResult(success: boolean) {
this.addEntityFailed = !success;
console.info(`Submit new sanctioned entity: ${success}`);
if (success) this.entityForm.reset();
}

ngOnDestroy() {
// prevent memory leak when component destroyed
this.entityFormStatusChangeSubs.unsubscribe();
this.addEntityRequestSubs.unsubscribe();
this.addEntityResultSubs.unsubscribe();
}
}
Loading