diff --git a/angular.json b/angular.json index 7a4f1ef7..5a06c996 100644 --- a/angular.json +++ b/angular.json @@ -94,5 +94,8 @@ } } } + }, + "cli": { + "analytics": "594a010b-3dfd-4a42-8417-ac6fe54a56d8" } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 02972627..372a397a 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,7 +1,17 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { ListComponent } from './contacts/list/list.component'; +import { AddComponent } from './contacts/add/add.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/:id', component: ViewComponent }, + { path: 'contacts/add', component: AddComponent }, + { path: 'test', component: AddComponent }, + { path: 'contacts/edit/:id', component: EditComponent }, +]; @NgModule({ imports: [RouterModule.forRoot(routes)], diff --git a/src/app/app.component.css b/src/app/app.component.css index 31d9e249..d20b6499 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -1,9 +1,18 @@ -:host { - display: flex; + +body, :host { + background: #f4f6fb; + min-height: 100vh; + font-family: 'Segoe UI', 'Roboto', Arial, sans-serif; + margin: 0; + padding: 0; } .page { - flex: 1; - display: flex; - justify-content: center; + max-width: 700px; + margin: 2em auto; + background: #fff; + border-radius: 12px; + box-shadow: 0 4px 24px rgba(0,0,0,0.08); + padding: 2em 2em 2em 2em; + min-height: 60vh; } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8207184c..192ce8fe 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,10 +4,19 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { LayoutModule } from './layout/layout.module'; +import { CommonModule } from '@angular/common'; +import { ContactsModule } from './contacts/contacts.module'; @NgModule({ declarations: [AppComponent], - imports: [BrowserModule, AppRoutingModule, LayoutModule], + imports: [ + BrowserModule, + AppRoutingModule, + LayoutModule, + CommonModule, + ContactsModule + ], + 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..25be94ac --- /dev/null +++ b/src/app/contacts/add/add.component.css @@ -0,0 +1,47 @@ + + +form div { + margin-bottom: 1.2em; +} + +label { + display: block; + margin-bottom: 0.4em; + font-weight: 400; + color: #333; +} + +input[type="text"] { + width: 100%; + padding: 0.6em; + border-radius: 4px; + font-size: 1em; + transition: border-color 0.2s; + border: 1.5px solid #ccc; +} + +input[type="text"]:focus { + border-color: #007bff; + outline: none; +} + +button[type="submit"] { + background: #007bff; + border: none; + padding: 0.7em 1.5em; + border-radius: 4px; + font-size: 1em; + cursor: pointer; + transition: background 0.2s; + color: #fff; +} + +button[type="submit"]:hover { + background: #0056b3; +} + +h2 { + text-align: center; + color: #007bff; + margin-bottom: 1.5em; +} diff --git a/src/app/contacts/add/add.component.html b/src/app/contacts/add/add.component.html new file mode 100644 index 00000000..58c76731 --- /dev/null +++ b/src/app/contacts/add/add.component.html @@ -0,0 +1,24 @@ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ + +
+
\ 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..bbfc724c --- /dev/null +++ b/src/app/contacts/add/add.component.ts @@ -0,0 +1,39 @@ +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ContactsService } from '../contacts.service'; +import { Contact } from '../models/contacts'; + +@Component({ + selector: 'app-add', + templateUrl: './add.component.html', + styleUrls: ['./add.component.css'] +}) +export class AddComponent { + contactForm: FormGroup; + cservice: ContactsService + + constructor( + private formBuilder: FormBuilder, + private readonly contactService: ContactsService + ) { + this.contactForm = this.formBuilder.group({ + firstname: ['', Validators.required], + lastname: ['', Validators.required], + street: ['', Validators.required], + city: ['', Validators.required] + }); + this.cservice = this.contactService; + } + + addContact(): void { + 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.cservice.AddContact(newContact); + this.contactForm.reset(); + } +} \ No newline at end of file diff --git a/src/app/contacts/contacts.module.ts b/src/app/contacts/contacts.module.ts new file mode 100644 index 00000000..b2b77cc1 --- /dev/null +++ b/src/app/contacts/contacts.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AddComponent } from './add/add.component'; +import { ViewComponent } from './view/view.component'; +import { ListComponent } from './list/list.component'; +import { ReactiveFormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { EditComponent } from './edit/edit.component'; + + + +@NgModule({ + declarations: [AddComponent, ViewComponent, ListComponent, EditComponent], + imports: [CommonModule, ReactiveFormsModule, RouterModule], + exports: [AddComponent, ViewComponent, ListComponent, 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..8176d7f6 --- /dev/null +++ b/src/app/contacts/contacts.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { Contact } from './models/contacts'; +import { CONTACTS } from '../data/contacts'; + + +@Injectable({ + providedIn: 'root' +}) +export class ContactsService { + public contacts: Contact[] = CONTACTS; + + public AddContact(c: Contact) { + c.id = this.contacts.length + 1; + this.contacts.push(c); + } + + public GetContactById(id: number | null) { + const contact = this.contacts.find((contact) => contact.id === id); + if (!contact) { + return null; + } + return contact; + } + + public UpdateContact(updated: Contact) { + const idx = this.contacts.findIndex(c => c.id === updated.id); + if (idx !== -1) { + this.contacts[idx] = updated; + } + } +} \ 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..6deb583f --- /dev/null +++ b/src/app/contacts/edit/edit.component.css @@ -0,0 +1,45 @@ +form div { + margin-bottom: 1.2em; +} + +label { + display: block; + margin-bottom: 0.4em; + font-weight: 400; + color: #333; +} + +input[type="text"] { + width: 100%; + padding: 0.6em; + border-radius: 4px; + font-size: 1em; + transition: border-color 0.2s; + border: 1.5px solid #ccc; +} + +input[type="text"]:focus { + border-color: #007bff; + outline: none; +} + +button[type="submit"] { + background: #007bff; + border: none; + padding: 0.7em 1.5em; + border-radius: 4px; + font-size: 1em; + cursor: pointer; + transition: background 0.2s; + color: #fff; +} + +button[type="submit"]:hover { + background: #0056b3; +} + +h2 { + text-align: center; + color: #007bff; + margin-bottom: 1.5em; +} diff --git a/src/app/contacts/edit/edit.component.html b/src/app/contacts/edit/edit.component.html new file mode 100644 index 00000000..4504c552 --- /dev/null +++ b/src/app/contacts/edit/edit.component.html @@ -0,0 +1,24 @@ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +

Contact not found!

+
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..559ea1d9 --- /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({ + declarations: [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..b23f8988 --- /dev/null +++ b/src/app/contacts/edit/edit.component.ts @@ -0,0 +1,50 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ContactsService } from '../contacts.service'; +import { Contact } from '../models/contacts'; + +@Component({ + selector: 'app-edit', + templateUrl: './edit.component.html', + styleUrls: ['./edit.component.css'] +}) +export class EditComponent implements OnInit { + contactForm: FormGroup; + contactId: number; + contact: Contact | null = null; + + constructor( + private fb: FormBuilder, + private route: ActivatedRoute, + private router: Router, + private contactsService: ContactsService + ) { + this.contactForm = this.fb.group({ + firstname: ['', Validators.required], + lastname: ['', Validators.required], + street: ['', Validators.required], + city: ['', Validators.required] + }); + this.contactId = 0; + } + + ngOnInit(): void { + this.contactId = Number(this.route.snapshot.paramMap.get('id')); + this.contact = this.contactsService.GetContactById(this.contactId); + if (this.contact) { + this.contactForm.patchValue(this.contact); + } + } + + saveContact(): void { + if (this.contact) { + const updatedContact: Contact = { + ...this.contact, + ...this.contactForm.value + }; + this.contactsService.UpdateContact(updatedContact); + 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..552e7bba --- /dev/null +++ b/src/app/contacts/list/list.component.css @@ -0,0 +1,58 @@ +a.edit-btn { + background: #007bff; + color: #fff; + border: none; + border-radius: 4px; + padding: 0.4em 1.1em; + font-size: 0.95em; + font-weight: 500; + text-decoration: none; + transition: background 0.2s, color 0.2s, box-shadow 0.2s; + box-shadow: 0 2px 8px rgba(0,0,0,0.04); + margin-left: 0.5em; + cursor: pointer; + display: inline-block; +} + +a.edit-btn:hover { + background: #0056b3; + color: #fff; + box-shadow: 0 4px 16px rgba(0,0,0,0.10); +} +table { + width: 100%; + border-collapse: collapse; + margin: 2em 0; + background: #fff; + box-shadow: 0 2px 8px rgba(0,0,0,0.08); + border-radius: 8px; + overflow: hidden; +} + +th, td { + padding: 1em; + text-align: left; +} + +thead { + background: #007bff; + color: #fff; +} + +tr { + border-bottom: 1px solid #eee; +} + +tr:last-child { + border-bottom: none; +} + +a { + color: #007bff; + text-decoration: none; + font-weight: 500; +} + +a:hover { + text-decoration: underline; +} diff --git a/src/app/contacts/list/list.component.html b/src/app/contacts/list/list.component.html new file mode 100644 index 00000000..cbe9458e --- /dev/null +++ b/src/app/contacts/list/list.component.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + +
NameStreetCity
+ {{ c.firstname }} {{ c.lastname }} + {{ c.street }}{{ c.city }}Edit
diff --git a/src/app/contacts/list/list.component.ts b/src/app/contacts/list/list.component.ts new file mode 100644 index 00000000..457bce81 --- /dev/null +++ b/src/app/contacts/list/list.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { Contact } from '../models/contacts'; +import { ContactsService } from '../contacts.service'; + +@Component({ + selector: 'app-list', + templateUrl: './list.component.html', + styleUrl: './list.component.css' +}) +export class ListComponent { + contacts: Contact[] = []; + + constructor(private readonly contactService: ContactsService) { + this.contacts = contactService.contacts; + } +} diff --git a/src/app/contacts/models/contacts.ts b/src/app/contacts/models/contacts.ts new file mode 100644 index 00000000..51472b33 --- /dev/null +++ b/src/app/contacts/models/contacts.ts @@ -0,0 +1,7 @@ +export interface Contact { + id: number | null; + firstname: string; + lastname: string; + street: string; + city: string; +} diff --git a/src/app/contacts/view/view.component.css b/src/app/contacts/view/view.component.css new file mode 100644 index 00000000..bf46ad9f --- /dev/null +++ b/src/app/contacts/view/view.component.css @@ -0,0 +1,39 @@ +.contact-view { + max-width: 370px; + margin: 2.5em auto; + padding: 2em 1.5em 1.5em 1.5em; + background: #f9fafb; + border-radius: 14px; + box-shadow: 0 4px 24px rgba(0,0,0,0.06); + font-family: 'Segoe UI', 'Roboto', Arial, sans-serif; + text-align: left; +} + +h2 { + color: #2563eb; + margin-bottom: 1em; + font-size: 1.35em; + font-weight: 700; + letter-spacing: 0.03em; +} +h3 { + color: #222; + margin: 0.7em 0; + font-size: 1.08em; + font-weight: 400; +} +button { + background: #2563eb; + color: #fff; + border: none; + border-radius: 4px; + padding: 0.5em 1.2em; + font-size: 1em; + font-family: inherit; + cursor: pointer; + margin-top: 1.5em; + transition: background 0.2s; +} +button:hover { + background: #1e40af; +} \ No newline at end of file diff --git a/src/app/contacts/view/view.component.html b/src/app/contacts/view/view.component.html new file mode 100644 index 00000000..708b1dff --- /dev/null +++ b/src/app/contacts/view/view.component.html @@ -0,0 +1,8 @@ +
+ +

+ {{ contact.firstname }} {{ contact.lastname }} +

+

Street: {{ contact.street }}

+

City: {{ 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..52a4a560 --- /dev/null +++ b/src/app/contacts/view/view.component.ts @@ -0,0 +1,28 @@ +import { Component } from '@angular/core'; +import { Contact } from '../models/contacts'; +import { ContactsService } from '../contacts.service'; +import { ActivatedRoute } from '@angular/router'; +import { Location } from '@angular/common'; + +@Component({ + selector: 'app-view', + templateUrl: './view.component.html', + styleUrl: './view.component.css' +}) +export class ViewComponent { + contact : Contact | null = null; + + constructor( + private readonly contactService: ContactsService, + private readonly route: ActivatedRoute, + private location: Location + ) { + this.contact = this.contactService.GetContactById( + Number(route.snapshot.paramMap.get('id')) + ); + } + + goBack(): void { + this.location.back(); + } +} diff --git a/src/app/data/contacts.ts b/src/app/data/contacts.ts new file mode 100644 index 00000000..07066e12 --- /dev/null +++ b/src/app/data/contacts.ts @@ -0,0 +1,25 @@ +import { Contact } from '../contacts/models/contacts'; + +export const CONTACTS: Contact[] = [ + { + id: 1, + firstname: 'Lola', + lastname: 'Smith', + street: '123 Dog St', + city: 'Dogtown', + }, + { + id: 2, + firstname: 'Red', + lastname: 'Cat', + street: '456 Cat Ave', + city: 'Cat City', + }, + { + id: 3, + firstname: 'Bella', + lastname: 'Johnson', + street: '789 Catnip Blvd', + city: 'Cat City', + }, +]; diff --git a/src/app/layout/menu/menu.component.css b/src/app/layout/menu/menu.component.css index 75955b3c..f64c0959 100644 --- a/src/app/layout/menu/menu.component.css +++ b/src/app/layout/menu/menu.component.css @@ -1,10 +1,42 @@ -:host { - padding: 0 0.5em; +nav { + background: #f8f9fa; + border-bottom: 1px solid #e0e0e0; + padding: 0.5em 0; + margin-bottom: 2em; } ul { + display: flex; + justify-content: center; + gap: 2em; padding: 0; + margin: 0; } + li { list-style: none; } + +a { + color: #222; + text-decoration: none; + font-weight: 500; + font-size: 1.05em; + padding: 0.5em 1em; + border-radius: 4px; + transition: background 0.2s, color 0.2s; +} + +a:hover { + background: #e0e0e0; + color: #007bff; +} + +h2 { + color: #222; + text-align: center; + font-size: 1.2em; + font-weight: 600; + margin-bottom: 0.5em; + letter-spacing: 0.03em; +} \ No newline at end of file diff --git a/src/app/layout/menu/menu.component.html b/src/app/layout/menu/menu.component.html index 7c5ec7a2..99a43daf 100644 --- a/src/app/layout/menu/menu.component.html +++ b/src/app/layout/menu/menu.component.html @@ -1,5 +1,7 @@ -

Menu

- + \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index c58dc05c..e893cb3e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,5 +3,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.error(err)); +platformBrowserDynamic().bootstrapModule(AppModule, { + ngZoneEventCoalescing: true +}) + .catch(err => console.error(err)); \ No newline at end of file