diff --git a/angular.json b/angular.json index 7a4f1ef7..be4c78d3 100644 --- a/angular.json +++ b/angular.json @@ -94,5 +94,8 @@ } } } + }, + "cli": { + "analytics": false } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 02972627..45de819e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,7 +1,16 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AddComponent } from './contacts/add/add.component'; +import { ListComponent } from './contacts/list/list.component'; +import { ViewComponent } from './contacts/view/view.component'; +import { EditComponent } from './contacts/edit/edit.component'; -const routes: Routes = []; +const routes: Routes = [ + { path: 'contacts', component: ListComponent }, + { path: 'contacts/add', component: AddComponent }, + { path: 'contacts/:id', component: ViewComponent }, + { path: 'contacts/edit/:id', component: EditComponent } +]; @NgModule({ imports: [RouterModule.forRoot(routes)], diff --git a/src/app/app.component.html b/src/app/app.component.html index 17aaa0c6..e04ea729 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,4 +1,4 @@
- +
diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8207184c..cec2d2a2 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,10 +4,13 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { LayoutModule } from './layout/layout.module'; +import { ContactsModule } from './contacts/contacts.module'; +import { CommonModule } from '@angular/common'; @NgModule({ declarations: [AppComponent], - imports: [BrowserModule, AppRoutingModule, LayoutModule], + imports: [BrowserModule, AppRoutingModule, CommonModule, ContactsModule, LayoutModule], + providers: [], bootstrap: [AppComponent], }) export class AppModule {} diff --git a/src/app/contacts/add/add.component.css b/src/app/contacts/add/add.component.css new file mode 100644 index 00000000..ddc82f51 --- /dev/null +++ b/src/app/contacts/add/add.component.css @@ -0,0 +1,33 @@ +h2 { + font-size: 2em; +} + +form div { + margin-bottom: 1em; + display: flex; + flex-direction: column; +} + +label { + margin-bottom: 0.25em; + font-weight: bold; +} + +input[type="text"] { + padding: 0.5em; + font-size: 1em; +} + +button { + padding: 0.5em 1em; + font-size: 1em; + font-weight: bold; + color: #fff; + background-color: #054992; + cursor: pointer; +} + +button:disabled { + background-color: #999; + cursor: not-allowed; +} diff --git a/src/app/contacts/add/add.component.html b/src/app/contacts/add/add.component.html new file mode 100644 index 00000000..de9583ea --- /dev/null +++ b/src/app/contacts/add/add.component.html @@ -0,0 +1,22 @@ +
+

Add New Contact

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
\ No newline at end of file diff --git a/src/app/contacts/add/add.component.spec.ts b/src/app/contacts/add/add.component.spec.ts new file mode 100644 index 00000000..f4528470 --- /dev/null +++ b/src/app/contacts/add/add.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddComponent } from './add.component'; + +describe('AddComponent', () => { + let component: AddComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AddComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AddComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/contacts/add/add.component.ts b/src/app/contacts/add/add.component.ts new file mode 100644 index 00000000..bf8d0e37 --- /dev/null +++ b/src/app/contacts/add/add.component.ts @@ -0,0 +1,43 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Contact } from 'src/app/models/contact'; +import { ContactsService } from '../contacts.service'; + +@Component({ + selector: 'app-add', + standalone: false, + templateUrl: './add.component.html', + styleUrls: ['./add.component.css'] +}) +export class AddComponent { + contactForm: FormGroup; + + constructor( + private readonly formBuilder: FormBuilder, + private readonly contactsService: ContactsService, + private readonly router: Router + ) { + this.contactForm = this.formBuilder.group({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + street: ['', Validators.required], + city: ['', Validators.required], + }); + } + + addContact() { + const newContact: Contact = { + id: 0, + firstName: this.contactForm.value.firstName, + lastName: this.contactForm.value.lastName, + street: this.contactForm.value.street, + city: this.contactForm.value.city, + }; + this.contactsService.AddContact(newContact); + this.contactForm.reset(); + this.router.navigate(['/contacts']); + } +} + + diff --git a/src/app/contacts/contacts.module.ts b/src/app/contacts/contacts.module.ts new file mode 100644 index 00000000..1444e43f --- /dev/null +++ b/src/app/contacts/contacts.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AddComponent } from './add/add.component'; +import { ListComponent } from './list/list.component'; +import { ViewComponent } from './view/view.component'; +import { EditComponent } from './edit/edit.component'; +import { RouterModule } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; + +@NgModule({ + declarations: [AddComponent, ListComponent, ViewComponent, EditComponent], + imports: [ + CommonModule, + RouterModule, + ReactiveFormsModule + ], + exports: [AddComponent, ListComponent, ViewComponent, EditComponent] +}) +export class ContactsModule { } diff --git a/src/app/contacts/contacts.service.ts b/src/app/contacts/contacts.service.ts new file mode 100644 index 00000000..95c373e7 --- /dev/null +++ b/src/app/contacts/contacts.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { Contact } from '../models/contact'; +import { CONTACTS } from '../data/contacts'; +import { of, Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ContactsService { + public contacts: Contact[] = CONTACTS; + + public AddContact(contact: Contact): void { + this.contacts.push(contact); + } + + public GetContactById(id: number): Observable { + const contact = this.contacts.find(contact => contact.id === id); + return of(contact); + } + + public updateContact(updatedContact: Contact): Observable { + const index = this.contacts.findIndex(contact => contact.id === updatedContact.id); + if (index !== -1) { + this.contacts[index] = updatedContact; + } + return of(undefined); + } +} \ No newline at end of file diff --git a/src/app/contacts/edit/edit.component.css b/src/app/contacts/edit/edit.component.css new file mode 100644 index 00000000..ddc82f51 --- /dev/null +++ b/src/app/contacts/edit/edit.component.css @@ -0,0 +1,33 @@ +h2 { + font-size: 2em; +} + +form div { + margin-bottom: 1em; + display: flex; + flex-direction: column; +} + +label { + margin-bottom: 0.25em; + font-weight: bold; +} + +input[type="text"] { + padding: 0.5em; + font-size: 1em; +} + +button { + padding: 0.5em 1em; + font-size: 1em; + font-weight: bold; + color: #fff; + background-color: #054992; + cursor: pointer; +} + +button:disabled { + background-color: #999; + cursor: not-allowed; +} diff --git a/src/app/contacts/edit/edit.component.html b/src/app/contacts/edit/edit.component.html new file mode 100644 index 00000000..62d5238a --- /dev/null +++ b/src/app/contacts/edit/edit.component.html @@ -0,0 +1,22 @@ +
+

Edit Contact

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
\ No newline at end of file diff --git a/src/app/contacts/edit/edit.component.spec.ts b/src/app/contacts/edit/edit.component.spec.ts new file mode 100644 index 00000000..6676ccfd --- /dev/null +++ b/src/app/contacts/edit/edit.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditComponent } from './edit.component'; + +describe('EditComponent', () => { + let component: EditComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [EditComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(EditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/contacts/edit/edit.component.ts b/src/app/contacts/edit/edit.component.ts new file mode 100644 index 00000000..1a5ddee6 --- /dev/null +++ b/src/app/contacts/edit/edit.component.ts @@ -0,0 +1,53 @@ +import { Component } from '@angular/core'; +import { Router, ActivatedRoute } from '@angular/router'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Contact } from 'src/app/models/contact'; +import { ContactsService } from '../contacts.service'; + +@Component({ + selector: 'app-edit', + standalone: false, + templateUrl: './edit.component.html', + styleUrls: ['./edit.component.css'] +}) +export class EditComponent { + contactForm: FormGroup; + + constructor( + private fb: FormBuilder, + private contactsService: ContactsService, + private router: Router, + private route: ActivatedRoute + ) { + this.contactForm = this.fb.group({ + firstName: [''], + lastName: [''], + street: [''], + city: [''] + }); + } + + ngOnInit(): void { + const id = Number(this.route.snapshot.paramMap.get('id')); + this.contactsService.GetContactById(id).subscribe(contact => { + if (contact) { + this.contactForm.patchValue(contact); + } + }); + } + + updateContact() { + const updatedContact: Contact = { + id: Number(this.route.snapshot.paramMap.get('id')), + firstName: this.contactForm.value.firstName, + lastName: this.contactForm.value.lastName, + street: this.contactForm.value.street, + city: this.contactForm.value.city, + }; + this.contactsService.updateContact(updatedContact).subscribe(() => { + this.contactForm.reset(); + this.router.navigate(['/contacts']); + }); + } +} + diff --git a/src/app/contacts/list/list.component.css b/src/app/contacts/list/list.component.css new file mode 100644 index 00000000..a043e153 --- /dev/null +++ b/src/app/contacts/list/list.component.css @@ -0,0 +1,32 @@ +.contacts-table { + width: 100%; + border-collapse: collapse; + margin-bottom: 2em; + font-family: Arial, sans-serif; +} + +.contacts-table th, +.contacts-table td { + padding: 1em 1em; + text-align: left; + border-bottom: 1px solid #ccc; +} + +.contacts-table th { + background-color: #f0f0f0; + font-weight: 600; +} + +.contacts-table tr:hover { + background-color: #f9f9f9; +} + +.view-button { + padding: 0.5em 1em; + cursor: pointer; + border-radius: 4px; +} + +.view-button:hover { + background-color: #e0e0e0; +} diff --git a/src/app/contacts/list/list.component.html b/src/app/contacts/list/list.component.html new file mode 100644 index 00000000..7bec054b --- /dev/null +++ b/src/app/contacts/list/list.component.html @@ -0,0 +1,21 @@ +

Contacts

+ + + + + + + + + + + + + + + + + + + +
First NameLast NameStreetCityDetails
{{ contact.firstName }}{{ contact.lastName }}{{ contact.street }}{{ contact.city }}
\ No newline at end of file diff --git a/src/app/contacts/list/list.component.spec.ts b/src/app/contacts/list/list.component.spec.ts new file mode 100644 index 00000000..b602c867 --- /dev/null +++ b/src/app/contacts/list/list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ListComponent } from './list.component'; + +describe('ListComponent', () => { + let component: ListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ListComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/contacts/list/list.component.ts b/src/app/contacts/list/list.component.ts new file mode 100644 index 00000000..f5bd040d --- /dev/null +++ b/src/app/contacts/list/list.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { Contact } from '../../models/contact'; +import { ContactsService } from '../contacts.service'; + +@Component({ + selector: 'app-list', + standalone: false, + templateUrl: './list.component.html', + styleUrls: ['./list.component.css'] +}) +export class ListComponent { + contacts: Contact[] = []; + constructor(private readonly contactsService: ContactsService) { + this.contacts = this.contactsService.contacts; + } +} diff --git a/src/app/contacts/view/view.component.css b/src/app/contacts/view/view.component.css new file mode 100644 index 00000000..4837853d --- /dev/null +++ b/src/app/contacts/view/view.component.css @@ -0,0 +1,20 @@ +#edit-button { + padding: 0.5em 1em; + cursor: pointer; + border-radius: 4px; + margin-right: 1em; +} + +#edit-button:hover { + background-color: #e0e0e0; +} + +#back-button { + padding: 0.5em 1em; + cursor: pointer; + border-radius: 4px; +} + +#back-button:hover { + background-color: #e0e0e0; +} diff --git a/src/app/contacts/view/view.component.html b/src/app/contacts/view/view.component.html new file mode 100644 index 00000000..a83b0f21 --- /dev/null +++ b/src/app/contacts/view/view.component.html @@ -0,0 +1,4 @@ +

{{ contact?.firstName }} {{ contact?.lastName }}

+

{{ contact?.street }}, {{ contact?.city }}

+ + diff --git a/src/app/contacts/view/view.component.spec.ts b/src/app/contacts/view/view.component.spec.ts new file mode 100644 index 00000000..380ab164 --- /dev/null +++ b/src/app/contacts/view/view.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ViewComponent } from './view.component'; + +describe('ViewComponent', () => { + let component: ViewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ViewComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/contacts/view/view.component.ts b/src/app/contacts/view/view.component.ts new file mode 100644 index 00000000..32fc6d8c --- /dev/null +++ b/src/app/contacts/view/view.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { Contact } from '../../models/contact'; +import { ActivatedRoute, } from '@angular/router'; +import { ContactsService } from '../contacts.service'; + +@Component({ + selector: 'app-view', + standalone: false, + templateUrl: './view.component.html', + styleUrls: ['./view.component.css'] +}) + +export class ViewComponent implements OnInit { + contact: Contact | null = null; + contactId: number | null = null; + + constructor( + private route: ActivatedRoute, + private contactService: ContactsService + ) {} + + ngOnInit(): void { + this.contactId = Number(this.route.snapshot.paramMap.get('id')); + this.contactService.GetContactById(this.contactId).subscribe(contact => { + this.contact = contact!; + }); + } +} diff --git a/src/app/data/contacts.ts b/src/app/data/contacts.ts new file mode 100644 index 00000000..78403266 --- /dev/null +++ b/src/app/data/contacts.ts @@ -0,0 +1,18 @@ +import { Contact } from '../models/contact'; + +export const CONTACTS: Contact[] = [ + { + id: 1, + firstName: 'John', + lastName: 'Doe', + street: '123 Main St', + city: 'Anytown' + }, + { + id: 2, + firstName: 'Jane', + lastName: 'Smith', + street: '456 Elm St', + city: 'Othertown' + }, +]; \ No newline at end of file diff --git a/src/app/layout/menu/menu.component.css b/src/app/layout/menu/menu.component.css index 75955b3c..6fd952d2 100644 --- a/src/app/layout/menu/menu.component.css +++ b/src/app/layout/menu/menu.component.css @@ -1,10 +1,35 @@ -:host { - padding: 0 0.5em; +h2 { + margin-bottom: 1em; + font-size: 50px; + color: #333; +} + +h2 a { + text-decoration: none; + color: inherit; +} + +h2 a:hover { + text-decoration: underline; } ul { padding: 0; } + li { list-style: none; + text-decoration: none; + border: 1px solid #ccc; + border-radius: 8px; + background: #f9f9f9; + margin-bottom: 0.75em; + padding: 1em; +} + +li a { + text-decoration: none; + font-size: 25px; + font-weight: bold; + color: #333; } diff --git a/src/app/layout/menu/menu.component.html b/src/app/layout/menu/menu.component.html index 7c5ec7a2..aed3259d 100644 --- a/src/app/layout/menu/menu.component.html +++ b/src/app/layout/menu/menu.component.html @@ -1,5 +1,5 @@

Menu

diff --git a/src/app/models/contact.ts b/src/app/models/contact.ts new file mode 100644 index 00000000..8ccb8326 --- /dev/null +++ b/src/app/models/contact.ts @@ -0,0 +1,7 @@ +export interface Contact { + id: number | null; + firstName: string; + lastName: string; + street: string; + city: string; +} \ No newline at end of file