diff --git a/web/src/app/org-detail/org-detail.component.html b/web/src/app/org-detail/org-detail.component.html index 01ade7c..1b6645e 100644 --- a/web/src/app/org-detail/org-detail.component.html +++ b/web/src/app/org-detail/org-detail.component.html @@ -16,7 +16,7 @@

{{ (organization$ | async)! | orgTitle }}

mat-mini-fab color="accent" aria-label="Save Edits" - (click)="editMode = false" + (click)="save(); editMode = false" > save @@ -38,7 +38,13 @@

{{ (organization$ | async)! | orgTitle }}

+ + + @@ -67,7 +73,13 @@

{{ (organization$ | async)! | orgTitle }}

+ + + diff --git a/web/src/app/org-detail/org-detail.component.scss b/web/src/app/org-detail/org-detail.component.scss index 2579afa..7d56c6f 100644 --- a/web/src/app/org-detail/org-detail.component.scss +++ b/web/src/app/org-detail/org-detail.component.scss @@ -12,7 +12,3 @@ button[mat-mini-fab] { mat-card { max-width: 500px; } - -table.personnel { - margin-bottom: 2em; -} diff --git a/web/src/app/org-detail/org-detail.component.ts b/web/src/app/org-detail/org-detail.component.ts index 5414c84..113c7a5 100644 --- a/web/src/app/org-detail/org-detail.component.ts +++ b/web/src/app/org-detail/org-detail.component.ts @@ -1,5 +1,5 @@ import { AsyncPipe, NgFor, NgIf } from '@angular/common'; -import { Component, Input, inject } from '@angular/core'; +import { Component, Input, ViewChild, inject } from '@angular/core'; import { MatMiniFabButton } from '@angular/material/button'; import { MatCard, @@ -12,10 +12,13 @@ import { RouterLink } from '@angular/router'; import { BehaviorSubject, Observable, combineLatest, mergeMap } from 'rxjs'; import { OrganizationTitlePipe } from '../core/organization-title.pipe'; +import { Personnel, TacticalCallsign } from '../datatypes/organization'; import { Ics217Service } from '../ics217.service'; import { OrganizationsService } from '../organizations.service'; import { UserInfoService } from '../user-info.service'; +import { PersonnelEditComponent } from './personnel-edit/personnel-edit.component'; import { PersonnelViewComponent } from './personnel-view/personnel-view.component'; +import { TacticalEditComponent } from './tactical-edit/tactical-edit.component'; import { TacticalViewComponent } from './tactical-view/tactical-view.component'; @Component({ @@ -32,14 +35,22 @@ import { TacticalViewComponent } from './tactical-view/tactical-view.component'; MatCardTitle, MatCardContent, PersonnelViewComponent, + PersonnelEditComponent, NgFor, RouterLink, TacticalViewComponent, + TacticalEditComponent, AsyncPipe, OrganizationTitlePipe, ], }) export class OrgDetailComponent { + @ViewChild(PersonnelEditComponent) + personnelEditComponent!: PersonnelEditComponent; + + @ViewChild(TacticalEditComponent) + tacticalEditComponent!: TacticalEditComponent; + private organizationsService = inject(OrganizationsService); private ics217Service = inject(Ics217Service); private userInfoService = inject(UserInfoService); @@ -68,4 +79,9 @@ export class OrgDetailComponent { set orgSlug(orgSlug: string) { this.orgSlug$.next(orgSlug); } + + save() { + // TODO: Implement save + alert('Save not implemented'); + } } diff --git a/web/src/app/org-detail/org-detail.module.ts b/web/src/app/org-detail/org-detail.module.ts index ad0cb40..efef9c3 100644 --- a/web/src/app/org-detail/org-detail.module.ts +++ b/web/src/app/org-detail/org-detail.module.ts @@ -1,12 +1,17 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; import { OrgDetailRoutingModule } from './org-detail-routing.module'; import { OrgDetailComponent } from './org-detail.component'; +import { PersonnelEditRowComponent } from './personnel-edit/personnel-edit-row/personnel-edit-row.component'; +import { PersonnelEditComponent } from './personnel-edit/personnel-edit.component'; import { PersonnelViewComponent } from './personnel-view/personnel-view.component'; +import { TacticalEditComponent } from './tactical-edit/tactical-edit.component'; import { TacticalViewComponent } from './tactical-view/tactical-view.component'; @NgModule({ @@ -16,9 +21,14 @@ import { TacticalViewComponent } from './tactical-view/tactical-view.component'; MatButtonModule, MatIconModule, MatCardModule, + MatInputModule, + ReactiveFormsModule, + PersonnelEditRowComponent, OrgDetailComponent, PersonnelViewComponent, TacticalViewComponent, + PersonnelEditComponent, + TacticalEditComponent, ], }) export class OrgDetailModule {} diff --git a/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.html b/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.html new file mode 100644 index 0000000..c53bfe9 --- /dev/null +++ b/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.html @@ -0,0 +1,17 @@ +
+ + Title + + + + Name + + + + Callsign + + + +
diff --git a/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.scss b/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.scss new file mode 100644 index 0000000..1a88557 --- /dev/null +++ b/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.scss @@ -0,0 +1,7 @@ +.personEditRow { + display: flex; + flex-direction: row; +} +mat-form-field { + margin-right: 5px; +} diff --git a/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.spec.ts b/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.spec.ts new file mode 100644 index 0000000..7728cc6 --- /dev/null +++ b/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PersonnelEditRowComponent } from './personnel-edit-row.component'; + +describe('PersonnelEditRowComponent', () => { + let component: PersonnelEditRowComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PersonnelEditRowComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(PersonnelEditRowComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.ts b/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.ts new file mode 100644 index 0000000..a023377 --- /dev/null +++ b/web/src/app/org-detail/personnel-edit/personnel-edit-row/personnel-edit-row.component.ts @@ -0,0 +1,20 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIcon } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; + +import { Personnel } from '../../../datatypes/organization'; + +@Component({ + selector: 'app-personnel-edit-row', + standalone: true, + imports: [MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatIcon], + templateUrl: './personnel-edit-row.component.html', + styleUrl: './personnel-edit-row.component.scss', +}) +export class PersonnelEditRowComponent { + @Input({ required: true }) form!: FormGroup; + @Input({ required: true }) index!: number; + @Output() delete = new EventEmitter(); +} diff --git a/web/src/app/org-detail/personnel-edit/personnel-edit.component.html b/web/src/app/org-detail/personnel-edit/personnel-edit.component.html new file mode 100644 index 0000000..530d369 --- /dev/null +++ b/web/src/app/org-detail/personnel-edit/personnel-edit.component.html @@ -0,0 +1,10 @@ +@for (form of formArray.controls; track form; let i = $index) { + +} + diff --git a/web/src/app/org-detail/personnel-edit/personnel-edit.component.scss b/web/src/app/org-detail/personnel-edit/personnel-edit.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/src/app/org-detail/personnel-edit/personnel-edit.component.spec.ts b/web/src/app/org-detail/personnel-edit/personnel-edit.component.spec.ts new file mode 100644 index 0000000..6a75550 --- /dev/null +++ b/web/src/app/org-detail/personnel-edit/personnel-edit.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PersonnelEditComponent } from './personnel-edit.component'; + +describe('PersonnelEditComponent', () => { + let component: PersonnelEditComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [PersonnelEditComponent], + }); + fixture = TestBed.createComponent(PersonnelEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/src/app/org-detail/personnel-edit/personnel-edit.component.ts b/web/src/app/org-detail/personnel-edit/personnel-edit.component.ts new file mode 100644 index 0000000..7cdad6c --- /dev/null +++ b/web/src/app/org-detail/personnel-edit/personnel-edit.component.ts @@ -0,0 +1,46 @@ +import { Component, Input, inject } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { MatIcon } from '@angular/material/icon'; + +import { Personnel } from '../../datatypes/organization'; +import { PersonnelEditRowComponent } from './personnel-edit-row/personnel-edit-row.component'; + +@Component({ + selector: 'app-personnel-edit', + templateUrl: './personnel-edit.component.html', + styleUrls: ['./personnel-edit.component.scss'], + standalone: true, + imports: [ReactiveFormsModule, PersonnelEditRowComponent, MatIcon], +}) +export class PersonnelEditComponent { + private formBuilder = inject(FormBuilder); + formArray = this.formBuilder.array([ + // Add an empty row at the end + this.buildFormGroup({} as Personnel), + ]); + + @Input() set personnel(personnel: Personnel[]) { + this.formArray.clear(); + personnel.forEach((p) => this.formArray.push(this.buildFormGroup(p))); + } + + private buildFormGroup(person: Personnel): FormGroup { + return this.formBuilder.group({ + title: [person.title || ''], + name: [person.name || ''], + callsign: [person.callsign || ''], + }); + } + + addRow() { + this.formArray.push(this.buildFormGroup({} as Personnel)); + } + + deleteRow(index: number) { + this.formArray.removeAt(index); + } + + getFormValues(): Personnel[] { + return this.formArray.value; + } +} diff --git a/web/src/app/org-detail/personnel-view/personnel-view.component.html b/web/src/app/org-detail/personnel-view/personnel-view.component.html index d5e20df..2a4b030 100644 --- a/web/src/app/org-detail/personnel-view/personnel-view.component.html +++ b/web/src/app/org-detail/personnel-view/personnel-view.component.html @@ -1,4 +1,4 @@ - +
{{ person.title }}: diff --git a/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.html b/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.html new file mode 100644 index 0000000..0e20cce --- /dev/null +++ b/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.html @@ -0,0 +1,13 @@ +
+ + Title + + + + Callsign + + + +
diff --git a/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.scss b/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.scss new file mode 100644 index 0000000..ec3a74c --- /dev/null +++ b/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.scss @@ -0,0 +1,7 @@ +.tacticalEditRow { + display: flex; + flex-direction: row; +} +mat-form-field { + margin-right: 5px; +} diff --git a/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.spec.ts b/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.spec.ts new file mode 100644 index 0000000..fc16bb9 --- /dev/null +++ b/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TacticalEditRowComponent } from './tactical-edit-row.component'; + +describe('TacticalEditRowComponent', () => { + let component: TacticalEditRowComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TacticalEditRowComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TacticalEditRowComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.ts b/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.ts new file mode 100644 index 0000000..4bd45fa --- /dev/null +++ b/web/src/app/org-detail/tactical-edit/tactical-edit-row/tactical-edit-row.component.ts @@ -0,0 +1,18 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIcon } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; + +@Component({ + selector: 'app-tactical-edit-row', + standalone: true, + imports: [MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatIcon], + templateUrl: './tactical-edit-row.component.html', + styleUrl: './tactical-edit-row.component.scss', +}) +export class TacticalEditRowComponent { + @Input({ required: true }) form!: FormGroup; + @Input({ required: true }) index!: number; + @Output() delete = new EventEmitter(); +} diff --git a/web/src/app/org-detail/tactical-edit/tactical-edit.component.html b/web/src/app/org-detail/tactical-edit/tactical-edit.component.html new file mode 100644 index 0000000..530d369 --- /dev/null +++ b/web/src/app/org-detail/tactical-edit/tactical-edit.component.html @@ -0,0 +1,10 @@ +@for (form of formArray.controls; track form; let i = $index) { + +} + diff --git a/web/src/app/org-detail/tactical-edit/tactical-edit.component.scss b/web/src/app/org-detail/tactical-edit/tactical-edit.component.scss new file mode 100644 index 0000000..1781048 --- /dev/null +++ b/web/src/app/org-detail/tactical-edit/tactical-edit.component.scss @@ -0,0 +1,3 @@ +mat-form-field { + margin-right: 5px; +} diff --git a/web/src/app/org-detail/tactical-edit/tactical-edit.component.spec.ts b/web/src/app/org-detail/tactical-edit/tactical-edit.component.spec.ts new file mode 100644 index 0000000..8c42520 --- /dev/null +++ b/web/src/app/org-detail/tactical-edit/tactical-edit.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TacticalEditComponent } from './tactical-edit.component'; + +describe('TacticalEditComponent', () => { + let component: TacticalEditComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TacticalEditComponent], + }); + fixture = TestBed.createComponent(TacticalEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/src/app/org-detail/tactical-edit/tactical-edit.component.ts b/web/src/app/org-detail/tactical-edit/tactical-edit.component.ts new file mode 100644 index 0000000..37490df --- /dev/null +++ b/web/src/app/org-detail/tactical-edit/tactical-edit.component.ts @@ -0,0 +1,55 @@ +import { Component, Input, inject } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { MatFormField, MatLabel } from '@angular/material/form-field'; +import { MatIcon } from '@angular/material/icon'; +import { MatInput } from '@angular/material/input'; + +import { TacticalCallsign } from '../../datatypes/organization'; +import { TacticalEditRowComponent } from './tactical-edit-row/tactical-edit-row.component'; + +@Component({ + selector: 'app-tactical-edit', + templateUrl: './tactical-edit.component.html', + styleUrls: ['./tactical-edit.component.scss'], + standalone: true, + imports: [ + MatFormField, + MatLabel, + MatInput, + MatIcon, + TacticalEditRowComponent, + ], +}) +export class TacticalEditComponent { + private formBuilder = inject(FormBuilder); + formArray = this.formBuilder.array([ + // Add an empty row at the end + this.buildFormGroup({} as TacticalCallsign), + ]); + + @Input() set tacticalCallsigns(tacticalCallsigns: TacticalCallsign[]) { + this.formArray.clear(); + tacticalCallsigns.forEach((p) => + this.formArray.push(this.buildFormGroup(p)), + ); + } + + private buildFormGroup(person: TacticalCallsign): FormGroup { + return this.formBuilder.group({ + title: [person.title || ''], + callsign: [person.callsign || ''], + }); + } + + addRow() { + this.formArray.push(this.buildFormGroup({} as TacticalCallsign)); + } + + deleteRow(index: number) { + this.formArray.removeAt(index); + } + + getFormValues(): TacticalCallsign[] { + return this.formArray.value; + } +} diff --git a/web/src/app/org-detail/tactical-view/tactical-view.component.html b/web/src/app/org-detail/tactical-view/tactical-view.component.html index 5872e5b..ec7fc08 100644 --- a/web/src/app/org-detail/tactical-view/tactical-view.component.html +++ b/web/src/app/org-detail/tactical-view/tactical-view.component.html @@ -1,4 +1,4 @@ - +
{{ tac.title }}: