This commit is contained in:
2025-08-26 16:28:27 +03:00
parent ef93d51f77
commit 6e5b89469f
14 changed files with 218 additions and 47 deletions

View File

@@ -1,7 +1,7 @@
<div class="container app-list">
<div class="header-row">
<div class="list-header-top">
<button mat-button type="button" (click)="goBack()" class="back-button">
<button mat-button type="button" routerLink="/landing" class="back-button">
Go Back
</button>
<h2 class="page-title">Applications</h2>
@@ -92,6 +92,15 @@
<p>Age: {{ application.age }}</p>
<p>City: {{ application.cityOrRegion }}</p>
</div>
<button
mat-icon-button
color="warn"
class="delete-button"
(click)="onDeleteClick($event ,application.id)"
[attr.aria-label]="'Delete ' + application.fullName"
>
<mat-icon>delete</mat-icon>
</button>
</div>
}
</div>

View File

@@ -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;

View File

@@ -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<any[]>([]);
@@ -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',
});
},
});
}
}

View File

@@ -1,6 +1,6 @@
<div class="container application">
<header class="header">
<button mat-button type="button" (click)="goBack()" class="back-button">
<button mat-button type="button" routerLink="/application-list" class="back-button">
<mat-icon>arrow_back</mat-icon>
Go Back
</button>
@@ -15,7 +15,6 @@
} @else if (hasApplicationData) {
<div class="content-wrapper">
<!-- Navigation -->
<div class="navigation-controls">
<button
mat-icon-button
@@ -42,10 +41,8 @@
</button>
</div>
<!-- Application Card -->
<div class="application-card">
<!-- Profile Section -->
<div class="profile-section">
@if (currentApplication().profileImage) {
<div class="profile-image-container">
@@ -84,7 +81,6 @@
</div>
</div>
<!-- Application Details -->
<div class="details-grid">
@if (getHobbiesArray().length > 0) {
<div class="detail-section">
@@ -114,7 +110,6 @@
}
</div>
<!-- Edit Button or Info -->
<div class="edit-controls">
@if (canEdit()) {
<button
@@ -128,9 +123,17 @@
} @else {
<p class="edit-info">You can edit your application only during 3 days</p>
}
<button
mat-raised-button
color="warn"
class="delete-button"
(click)="onDeleteClick($event ,currentApplication().id)"
[attr.aria-label]="'Delete ' + currentApplication().fullName"
>
Delete Application
</button>
</div>
<!-- Raw JSON Data -->
<details class="raw-data-section">
<summary>View Raw Data</summary>
<pre class="raw-data">{{ currentApplication() | json }}</pre>
@@ -141,7 +144,7 @@
<div class="no-data">
<mat-icon class="no-data-icon">inbox</mat-icon>
<p>No application data available</p>
<button mat-button (click)="goBack()" class="back-button">
<button mat-button routerLink="/application-list" class="back-button">
<mat-icon>arrow_back</mat-icon>
Return to List
</button>

View File

@@ -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<any>(null);
applicationList = signal<any[]>([]);
currentIndex = signal<number>(-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',
});
},
});
}
}

View File

@@ -1,14 +1,23 @@
<div class="container landing">
<h2>Welcome to IISA</h2>
<p class="intro">Join the mission or track your application below.</p>
<div class="button-group">
<button mat-button routerLink="/application/new" class="primary-btn">
<button
mat-button
routerLink="/application/new"
class="primary-btn"
(click)="onRegisterClick()">
Apply for Registration
</button>
<button mat-button routerLink="/application-list" class="secondary-btn">
View Applications
</button>
</div>
<div class="stats">
<p>👀 Total Visits: {{ stats.totalVisits() }}</p>
<p>🖱️ Register Button Clicks: {{ stats.totalClicks() }}</p>
</div>
</div>

View File

@@ -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 {

View File

@@ -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();
}
}

View File

@@ -7,7 +7,7 @@
Cancel Editing
</button>
}@else {
<button mat-button type="button" (click)="goBack()" class="button">
<button mat-button type="button" routerLink="/landing" class="button">
Go Back
</button>
}

View File

@@ -168,7 +168,5 @@ export class RegistrationComponent implements OnInit {
}
}
goBack(): void {
this.router.navigate(['/landing']);
}
}

View File

@@ -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);
})
);
}
}

View File

@@ -25,10 +25,27 @@ export class SocketIOService {
});
}
disconnect() {
this.socket.disconnect();
onCandidateDeleted(): Observable<any> {
return new Observable(observer => {
this.socket.on('candidateDeleted', (data) => {
observer.next(data);
});
});
}
onStatsUpdated(): Observable<any> {
return new Observable(observer => {
this.socket.on('statsUpdated', (data) => {
observer.next(data);
});
});
}
// disconnect() {
// this.socket.disconnect();
// }
}

View File

@@ -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<any>(`${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`, {});
}
}