From 6e5b89469f4607223b25b7f3577001c351682c75 Mon Sep 17 00:00:00 2001 From: Vasa Date: Tue, 26 Aug 2025 16:28:27 +0300 Subject: [PATCH] wip --- README.md | 5 +- .../application-list.component.html | 11 ++++- .../application-list.component.scss | 27 +++++++++++ .../application-list.component.ts | 44 +++++++++++++---- .../application/application.component.html | 19 ++++---- .../application/application.component.ts | 47 ++++++++++++++----- .../components/landing/landing.component.html | 13 ++++- .../components/landing/landing.component.scss | 8 ++++ .../components/landing/landing.component.ts | 21 ++++++--- .../registration/registration.component.html | 2 +- .../registration/registration.component.ts | 4 +- src/app/services/candidate-data.service.ts | 7 +++ src/app/services/socket-io.service.ts | 21 ++++++++- src/app/services/stats.service.ts | 36 ++++++++++++++ 14 files changed, 218 insertions(+), 47 deletions(-) create mode 100644 src/app/services/stats.service.ts diff --git a/README.md b/README.md index e2350ca..30a7ecf 100644 --- a/README.md +++ b/README.md @@ -62,11 +62,12 @@ Angular CLI does not come with an end-to-end testing framework by default. You c 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. --implement total visits and total clicks on register button -minor things +-fix upload of the image in edit mode +-add deletion button to the card -centralize scss check the same styles for differnet buttons -adjust to mobile format + ****animations transitions (prev next cards) diff --git a/src/app/components/application-list/application-list.component.html b/src/app/components/application-list/application-list.component.html index 4bfee01..333dcf0 100644 --- a/src/app/components/application-list/application-list.component.html +++ b/src/app/components/application-list/application-list.component.html @@ -1,7 +1,7 @@
-

Applications

@@ -92,6 +92,15 @@

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 94ece74..b53c583 100644 --- a/src/app/components/application-list/application-list.component.scss +++ b/src/app/components/application-list/application-list.component.scss @@ -131,6 +131,33 @@ color: #00ffff; } +.delete-button { + margin-left: 12px; + width: 40px; + height: 40px; + min-width: 40px; + background: rgba(255, 68, 68, 0.1); + border: 1px solid #ff4444; + border-radius: 8px; + color: #ff4444; + cursor: pointer; + transition: all 0.3s ease; + flex-shrink: 0; +} + +.delete-button:hover { + background: rgba(255, 68, 68, 0.2); + color: #ff6666; + box-shadow: 0 0 20px rgba(255, 68, 68, 0.4); + transform: translateY(-2px); + border-color: #ff6666; +} + +.delete-button mat-icon { + font-size: 20px; + width: 20px; + height: 20px; +} .charts-row { display: flex; diff --git a/src/app/components/application-list/application-list.component.ts b/src/app/components/application-list/application-list.component.ts index b5d00b0..7c625cd 100644 --- a/src/app/components/application-list/application-list.component.ts +++ b/src/app/components/application-list/application-list.component.ts @@ -12,6 +12,7 @@ import { SocketIOService } from '../../services/socket-io.service'; import { BaseChartDirective } from 'ng2-charts'; import { ChartData, ChartOptions, Chart, registerables } from 'chart.js'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatSnackBar } from '@angular/material/snack-bar'; Chart.register(...registerables); @@ -32,10 +33,10 @@ Chart.register(...registerables); styleUrls: ['./application-list.component.scss'] }) export class ApplicationListComponent implements OnInit { - private dataService = inject(CandidateDataService); - private socketService = inject(SocketIOService); - private router = inject(Router); - + dataService = inject(CandidateDataService); + socketService = inject(SocketIOService); + router = inject(Router); + snackBar = inject(MatSnackBar) environment = environment; applicationList = signal([]); @@ -94,7 +95,7 @@ export class ApplicationListComponent implements OnInit { constructor() { effect(() => { const data = this.applicationList(); - + if (data.length === 0) { this.ageChartData.set({ labels: [], datasets: [] }); this.cityChartData.set({ labels: [], datasets: [] }); @@ -108,7 +109,7 @@ export class ApplicationListComponent implements OnInit { '46-60': 0, '60+': 0, }; - + data.forEach(app => { if (app.age <= 25) ageGroups['18-25']++; else if (app.age <= 35) ageGroups['26-35']++; @@ -192,11 +193,16 @@ export class ApplicationListComponent implements OnInit { ); this.availableCities.set(this.getUniqueCities(this.applicationList())); }); + + this.socketService.onCandidateDeleted().subscribe(deletedCandidateId => { + this.applicationList.update(list => + list.filter(app => app.id !== deletedCandidateId) + ); + this.availableCities.set(this.getUniqueCities(this.applicationList())); + }); } - goBack(): void { - this.router.navigate(['/landing']); - } + getUniqueCities(data: any[]): City[] { const seen: string[] = []; @@ -209,4 +215,24 @@ export class ApplicationListComponent implements OnInit { }) .sort((a, b) => a.name.localeCompare(b.name)); } + + onDeleteClick(event: any, id: number) { + event.stopPropagation(); + this.dataService.deleteCandidate(id).subscribe({ + next: () => { + this.snackBar.open('✅ Application deleted!', 'Close', { + duration: 5000, + horizontalPosition: 'center', + verticalPosition: 'top', + }); + }, + error: () => { + this.snackBar.open('Error deleting application!', 'Close', { + duration: 5000, + horizontalPosition: 'center', + verticalPosition: 'top', + }); + }, + }); + } } \ No newline at end of file diff --git a/src/app/components/application/application.component.html b/src/app/components/application/application.component.html index 87fad1e..5dc59f1 100644 --- a/src/app/components/application/application.component.html +++ b/src/app/components/application/application.component.html @@ -1,6 +1,6 @@
- @@ -15,7 +15,6 @@ } @else if (hasApplicationData) {
- -
-
@if (currentApplication().profileImage) {
@@ -84,7 +81,6 @@
-
@if (getHobbiesArray().length > 0) {
@@ -114,7 +110,6 @@ }
-
@if (canEdit()) {
-
View Raw Data
{{ currentApplication() | json }}
@@ -141,7 +144,7 @@
inbox

No application data available

- diff --git a/src/app/components/application/application.component.ts b/src/app/components/application/application.component.ts index b2b5aba..43a3fce 100644 --- a/src/app/components/application/application.component.ts +++ b/src/app/components/application/application.component.ts @@ -1,6 +1,6 @@ import { Component, inject, OnInit, signal, computed } from '@angular/core'; import { CandidateDataService } from '../../services/candidate-data.service'; -import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { ActivatedRoute, Router, RouterLink, RouterModule } from '@angular/router'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; @@ -9,6 +9,7 @@ import { MatChipsModule } from '@angular/material/chips'; import { CommonModule } from '@angular/common'; import { environment } from '../../../environments/environment'; import { SocketIOService } from '../../services/socket-io.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; @Component({ selector: 'app-application', @@ -19,17 +20,20 @@ import { SocketIOService } from '../../services/socket-io.service'; MatProgressSpinnerModule, MatCardModule, MatChipsModule, - CommonModule + CommonModule, + RouterLink ], templateUrl: './application.component.html', styleUrls: ['./application.component.scss'] }) -export class ApplicationComponent implements OnInit{ +export class ApplicationComponent implements OnInit { dataService = inject(CandidateDataService); socketService = inject(SocketIOService); activatedRoute = inject(ActivatedRoute); + snackBar = inject(MatSnackBar); router = inject(Router); + currentApplication = signal(null); applicationList = signal([]); currentIndex = signal(-1); @@ -76,13 +80,13 @@ export class ApplicationComponent implements OnInit{ } } - editApplication(): void { + editApplication() { const app = this.currentApplication(); if (!app) return; this.router.navigate(['/application', app.id, 'edit']); } - initializeApplication(): void { + initializeApplication() { const id = this.activatedRoute.snapshot.paramMap.get('id'); if (!id) { alert('Invalid route'); @@ -103,7 +107,7 @@ export class ApplicationComponent implements OnInit{ } } - loadApplication(id: number): void { + loadApplication(id: number) { if (this.currentApplication()?.id === id) { return; } @@ -125,7 +129,7 @@ export class ApplicationComponent implements OnInit{ }); } - goToPrevious(): void { + goToPrevious() { if (this.canGoToPrevious()) { const newIndex = this.currentIndex() - 1; this.currentIndex.set(newIndex); @@ -134,7 +138,7 @@ export class ApplicationComponent implements OnInit{ } } - goToNext(): void { + goToNext() { if (this.canGoToNext()) { const newIndex = this.currentIndex() + 1; this.currentIndex.set(newIndex); @@ -143,9 +147,7 @@ export class ApplicationComponent implements OnInit{ } } - goBack(): void { - this.router.navigate(['/application-list']); - } + getHobbiesArray(): string[] { const app = this.currentApplication(); @@ -157,7 +159,7 @@ export class ApplicationComponent implements OnInit{ return Array.isArray(app.hobbies) ? app.hobbies : []; } - formatDate(dateString: string): string { + formatDate(dateString: string) { if (!dateString) return 'Not specified'; try { return new Date(dateString).toLocaleDateString(); @@ -165,6 +167,25 @@ export class ApplicationComponent implements OnInit{ return dateString; } } + onDeleteClick(event: any, id: number) { + event.stopPropagation(); + this.dataService.deleteCandidate(id).subscribe({ + next: () => { + this.snackBar.open('✅ Application deleted!', 'Close', { + duration: 5000, + horizontalPosition: 'center', + verticalPosition: 'top', + }); + }, + error: () => { + this.snackBar.open('Error deleting application!', 'Close', { + duration: 5000, + horizontalPosition: 'center', + verticalPosition: 'top', + }); + }, + }); + + } - } \ No newline at end of file diff --git a/src/app/components/landing/landing.component.html b/src/app/components/landing/landing.component.html index 847aafe..a4e606b 100644 --- a/src/app/components/landing/landing.component.html +++ b/src/app/components/landing/landing.component.html @@ -1,14 +1,23 @@ -

Welcome to IISA

Join the mission or track your application below.

- +
+ +
+

👀 Total Visits: {{ stats.totalVisits() }}

+

🖱️ Register Button Clicks: {{ stats.totalClicks() }}

+
diff --git a/src/app/components/landing/landing.component.scss b/src/app/components/landing/landing.component.scss index f6d02bc..829c947 100644 --- a/src/app/components/landing/landing.component.scss +++ b/src/app/components/landing/landing.component.scss @@ -93,6 +93,14 @@ button.secondary-btn:hover { transform: translateY(-2px); } +.stats { + margin-top: 20px; + font-size: 16px; + color: #444; +} + + + /* Responsive adjustments */ @media (max-width: 600px) { .landing h2 { diff --git a/src/app/components/landing/landing.component.ts b/src/app/components/landing/landing.component.ts index 9e3657a..076533f 100644 --- a/src/app/components/landing/landing.component.ts +++ b/src/app/components/landing/landing.component.ts @@ -1,15 +1,24 @@ -import { Component } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterLink } from '@angular/router'; -import { RouterLinkActive } from "../../../../node_modules/@angular/router/router_module.d-Bx9ArA6K"; +import { StatsService } from '../../services/stats.service'; @Component({ selector: 'app-landing', - imports: [MatButtonModule, RouterLink,], + standalone: true, + imports: [MatButtonModule, RouterLink], templateUrl: './landing.component.html', - styleUrl: './landing.component.scss' + styleUrls: ['./landing.component.scss'] }) -export class LandingComponent { +export class LandingComponent implements OnInit { + stats = inject(StatsService); - + ngOnInit() { + this.stats.loadInitial(); + this.stats.incrementVisit().subscribe(); + } + + onRegisterClick() { + this.stats.incrementClick().subscribe(); + } } diff --git a/src/app/components/registration/registration.component.html b/src/app/components/registration/registration.component.html index 630a966..18047e3 100644 --- a/src/app/components/registration/registration.component.html +++ b/src/app/components/registration/registration.component.html @@ -7,7 +7,7 @@ Cancel Editing }@else { - } diff --git a/src/app/components/registration/registration.component.ts b/src/app/components/registration/registration.component.ts index 13bdd66..36008d3 100644 --- a/src/app/components/registration/registration.component.ts +++ b/src/app/components/registration/registration.component.ts @@ -168,7 +168,5 @@ export class RegistrationComponent implements OnInit { } } - goBack(): void { - this.router.navigate(['/landing']); - } + } diff --git a/src/app/services/candidate-data.service.ts b/src/app/services/candidate-data.service.ts index 3cc61af..01d0132 100644 --- a/src/app/services/candidate-data.service.ts +++ b/src/app/services/candidate-data.service.ts @@ -42,5 +42,12 @@ export class CandidateDataService { ); } + deleteCandidate(id: number) { + return this.httpClient.delete(`${environment.hostUrl}/app/candidate/${id}`).pipe( + tap(() => { + this.cachedApplicationList = this.cachedApplicationList.filter(c => c.id !== id); + }) + ); + } } \ No newline at end of file diff --git a/src/app/services/socket-io.service.ts b/src/app/services/socket-io.service.ts index 0315594..915781f 100644 --- a/src/app/services/socket-io.service.ts +++ b/src/app/services/socket-io.service.ts @@ -25,10 +25,27 @@ export class SocketIOService { }); } - disconnect() { - this.socket.disconnect(); + onCandidateDeleted(): Observable { + return new Observable(observer => { + this.socket.on('candidateDeleted', (data) => { + observer.next(data); + }); + }); } + onStatsUpdated(): Observable { + return new Observable(observer => { + this.socket.on('statsUpdated', (data) => { + observer.next(data); + }); + }); + } + + + // disconnect() { + // this.socket.disconnect(); + // } + } diff --git a/src/app/services/stats.service.ts b/src/app/services/stats.service.ts new file mode 100644 index 0000000..bf3451b --- /dev/null +++ b/src/app/services/stats.service.ts @@ -0,0 +1,36 @@ +import { Injectable, signal, effect, inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { SocketIOService } from './socket-io.service'; +import { environment } from '../../environments/environment'; + +@Injectable({ providedIn: 'root' }) +export class StatsService { + http = inject(HttpClient); + socket = inject(SocketIOService); + + + totalVisits = signal(0); + totalClicks = signal(0); + + private socketEffect = effect(() => { + this.socket.onStatsUpdated().subscribe(stats => { + this.totalVisits.set(stats.totalVisits); + this.totalClicks.set(stats.totalClicks); + }); + }); + + loadInitial() { + this.http.get(`${environment.hostUrl}/stats`).subscribe(stats => { + this.totalVisits.set(stats.totalVisits); + this.totalClicks.set(stats.totalClicks); + }); + } + + incrementVisit() { + return this.http.post(`${environment.hostUrl}/stats/visit`, {}); + } + + incrementClick() { + return this.http.post(`${environment.hostUrl}/stats/click`, {}); + } +}