+
+
@if (application.profileImage) {
+ class="profile-photo" />
}
+
{{ application.fullName }}
Age: {{ application.age }}
City: {{ application.cityOrRegion }}
-
+ (click)="onDeleteClick($event, application.id)"
+ [attr.aria-label]="'Delete ' + application.fullName">
delete
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
-
+ file_upload
+
Browse
- Drag and drop here
+ Drag and drop here
}
-
+
-
+
+