wip
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[]>([]);
|
||||
@@ -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',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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,7 +20,8 @@ import { SocketIOService } from '../../services/socket-io.service';
|
||||
MatProgressSpinnerModule,
|
||||
MatCardModule,
|
||||
MatChipsModule,
|
||||
CommonModule
|
||||
CommonModule,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './application.component.html',
|
||||
styleUrls: ['./application.component.scss']
|
||||
@@ -28,8 +30,10 @@ 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',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -168,7 +168,5 @@ export class RegistrationComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
goBack(): void {
|
||||
this.router.navigate(['/landing']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
36
src/app/services/stats.service.ts
Normal file
36
src/app/services/stats.service.ts
Normal 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`, {});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user