From 5105aad107bfddad758dcae3666a4460fd905ee6 Mon Sep 17 00:00:00 2001 From: Vasa Date: Thu, 28 Aug 2025 17:07:20 +0300 Subject: [PATCH] fix --- README.md | 6 - .../application-list.component.html | 178 +++++++++++------- .../application-list.component.scss | 31 +++ .../application-list.component.ts | 5 +- .../candidates-map.component.html | 0 .../candidates-map.component.scss | 11 ++ .../candidates-map.component.ts | 71 +++++++ .../image-input/image-input.component.html | 19 +- .../registration/registration.component.html | 78 ++++---- 9 files changed, 276 insertions(+), 123 deletions(-) create mode 100644 src/app/components/candidates-map/candidates-map.component.html create mode 100644 src/app/components/candidates-map/candidates-map.component.scss create mode 100644 src/app/components/candidates-map/candidates-map.component.ts diff --git a/README.md b/README.md index db430c4..2ecbcee 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ ng e2e Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. ## Additional Resources -## TODO For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. @@ -64,9 +63,4 @@ For more information on using the Angular CLI, including detailed command refere --centralize scss check the same styles for differnet buttons - -****animations transitions (prev next cards) - ---DEPLOY AND WRITE README diff --git a/src/app/components/application-list/application-list.component.html b/src/app/components/application-list/application-list.component.html index 15708d5..512643f 100644 --- a/src/app/components/application-list/application-list.component.html +++ b/src/app/components/application-list/application-list.component.html @@ -1,104 +1,140 @@
+
-

Applications

- @if (!isLoading) { -
-
-

Age Distribution

- - -
- -
-

City Distribution

- - -
-
- } - - @if (!isLoading) { -
- - Search - - - - - City - - All - @for (city of availableCities(); track city.name) { - {{ city.name }} - } - - - - - Sort by - - @for (option of sortFields; track option.value) { - - {{ option.viewValue }} - - } - - -
- } + +
+ + +
+ + @if (viewMode() === 'map') { + + + } + + + @if (!isLoading && viewMode() === 'charts') { +
+
+

Age Distribution

+ + +
+ +
+

City Distribution

+ + +
+
+ } + + + @if (!isLoading) { +
+ + Search + + + + + City + + All + @for (city of availableCities(); track city.name) { + + {{ city.name }} + + } + + + + + Sort by + + @for (option of sortFields; track option.value) { + + {{ option.viewValue }} + + } + + +
+ } + + @if (isLoading) {

Loading applications...

- } @else { + } + @else { +
@for (application of sortedList(); track application.id) { -
+
+ @if (application.profileImage) { {{ application.fullName }} + class="profile-photo" /> } +

{{ application.fullName }}

Age: {{ application.age }}

City: {{ application.cityOrRegion }}

-
diff --git a/src/app/components/application-list/application-list.component.scss b/src/app/components/application-list/application-list.component.scss index 7a71754..18a305a 100644 --- a/src/app/components/application-list/application-list.component.scss +++ b/src/app/components/application-list/application-list.component.scss @@ -229,3 +229,34 @@ button.secondary-btn:hover { 0% { background-position: -200% center; } 100% { background-position: 200% center; } } + +.view-toggle { + display: flex; + justify-content: center; + gap: 1rem; + margin: 1rem 0 2rem; + + button { + background: transparent; + border: 1px solid #00ffff; + color: #00ffff; + padding: 0.6rem 1.2rem; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; + + &:hover { + background: rgba(0, 255, 255, 0.12); + box-shadow: 0 0 12px #00ffff; + } + + &.active { + background: rgba(0, 255, 255, 0.15); + box-shadow: 0 0 18px #00ffff; + transform: translateY(-2px); + } + } +} diff --git a/src/app/components/application-list/application-list.component.ts b/src/app/components/application-list/application-list.component.ts index d0759cc..b517db8 100644 --- a/src/app/components/application-list/application-list.component.ts +++ b/src/app/components/application-list/application-list.component.ts @@ -14,6 +14,7 @@ import { ChartData, ChartOptions, Chart, registerables } from 'chart.js'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSnackBar } from '@angular/material/snack-bar'; import { MatButtonModule } from '@angular/material/button'; +import { CandidatesMapComponent } from '../candidates-map/candidates-map.component'; Chart.register(...registerables); @@ -29,7 +30,8 @@ Chart.register(...registerables); MatInputModule, MatProgressSpinnerModule, BaseChartDirective, - MatButtonModule + MatButtonModule, + CandidatesMapComponent, ], templateUrl: './application-list.component.html', styleUrls: ['./application-list.component.scss'] @@ -45,6 +47,7 @@ export class ApplicationListComponent implements OnInit { searchTerm = signal(''); filterCity = signal(''); sortField = signal('fullName'); + viewMode = signal<'map' | 'charts'>('map'); availableCities = signal(CITY_LIST); diff --git a/src/app/components/candidates-map/candidates-map.component.html b/src/app/components/candidates-map/candidates-map.component.html new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/candidates-map/candidates-map.component.scss b/src/app/components/candidates-map/candidates-map.component.scss new file mode 100644 index 0000000..c9c739e --- /dev/null +++ b/src/app/components/candidates-map/candidates-map.component.scss @@ -0,0 +1,11 @@ +.map-container { + height: 400px; + width: 90%; + max-width: 900px; + margin: 2rem auto; + border: 2px solid #00ffff; + border-radius: 14px; + box-shadow: 0 0 20px rgba(0, 255, 255, 0.4); + background: rgba(0, 0, 0, 0.3); + overflow: hidden; +} diff --git a/src/app/components/candidates-map/candidates-map.component.ts b/src/app/components/candidates-map/candidates-map.component.ts new file mode 100644 index 0000000..cebb4dc --- /dev/null +++ b/src/app/components/candidates-map/candidates-map.component.ts @@ -0,0 +1,71 @@ +import { Component, AfterViewInit, Input, ElementRef, ViewChild, OnChanges, SimpleChanges, input } from '@angular/core'; +import * as L from 'leaflet'; +import { CommonModule } from '@angular/common'; +import { City } from '../../shared/cities'; + +delete (L.Icon.Default.prototype as any)._getIconUrl; + +L.Icon.Default.mergeOptions({ + iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png', + iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png', + shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png', +}); + +@Component({ + selector: 'app-candidates-map', + standalone: true, + imports: [CommonModule], + template: `
`, + styleUrls: ['./candidates-map.component.scss'] +}) +export class CandidatesMapComponent implements AfterViewInit, OnChanges { + @ViewChild('mapEl', { static: true }) mapEl!: ElementRef; + candidates = input([]); + cities = input([]); + + map!: L.Map; + markersLayer = L.layerGroup(); + + ngAfterViewInit(): void { + this.map = L.map(this.mapEl.nativeElement).setView([31.7683, 35.2137], 7); + + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap contributors', + }).addTo(this.map); + + this.markersLayer.addTo(this.map); + this.renderMarkers(); + } + + ngOnChanges(changes: SimpleChanges): void { + if ((changes['candidates'] || changes['cities']) && this.map) { + this.renderMarkers(); + } + } + + renderMarkers(): void { + this.markersLayer.clearLayers(); + + if (!this.map || !this.candidates()?.length) return; + + const candidatesPerCity: Record = {}; + this.candidates().forEach(c => { + if (c.cityOrRegion) { + candidatesPerCity[c.cityOrRegion] = (candidatesPerCity[c.cityOrRegion] || 0) + 1; + } + }); + + this.cities().forEach(city => { + const count = candidatesPerCity[city.name]; + if (!count) return; + + const marker = L.marker([city.lat, city.lng]); + marker.bindTooltip(`${city.name}: ${count} candidate(s)`, { + permanent: false, + direction: 'top' + }); + marker.addTo(this.markersLayer); + }); + } +} diff --git a/src/app/components/image-input/image-input.component.html b/src/app/components/image-input/image-input.component.html index 2d812ca..eac9d09 100644 --- a/src/app/components/image-input/image-input.component.html +++ b/src/app/components/image-input/image-input.component.html @@ -1,7 +1,8 @@
Profile Photo -
+
+ @if (previewUrl) {
@@ -16,18 +17,22 @@
} - - @if (!previewUrl) { + + @if (!previewUrl) {
- file_upload - - Drag and drop here + Drag and drop here
} - + -
- @if(editMode()){ - - }@else { +
+ + + @if(editMode()) { + + } @else { } - @if(editMode()){ -

Spaceflight Candidate Registration Update

- } - @else { -

Spaceflight Candidate Registration

- } -
- + + + @if(editMode()) { +

Spaceflight Candidate Registration Update

+ } @else { +

Spaceflight Candidate Registration

+ } + +
+
- -
- -
- - +
+ +
+ Full Name @@ -68,21 +68,21 @@ - - - + @if (form.get('cityOrRegion')?.invalid && (form.get('cityOrRegion')?.touched || form.get('cityOrRegion')?.dirty)) { - @if (form.get('cityOrRegion')?.hasError('required')) { - City selection is required + @if (form.get('cityOrRegion')?.hasError('required')) { + + City selection is required + + } + @if (form.get('cityOrRegion')?.hasError('invalidCity')) { + + Please select a valid city from the list + + } } - @if (form.get('cityOrRegion')?.hasError('invalidCity')) { - Please select a valid city from the list - } -} - + Hobbies @@ -104,12 +104,14 @@ } - - + + +
+