conflits résolusé
This commit is contained in:
commit
39de6138d6
30 changed files with 1535 additions and 58 deletions
|
|
@ -10,6 +10,8 @@ import { HomeComponent } from './components/home/home.component';
|
|||
|
||||
import { MatchesComponent } from './components/match/matches/matches.component';
|
||||
import { MatchesIdComponent } from './components/match/matches-id/matches-id.component';
|
||||
import { MatchesAddComponent } from './components/match/matches-add/matches-add.component';
|
||||
import { MatchesDelComponent } from './components/match/matches-del/matches-del.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'login', component: LoginComponent },
|
||||
|
|
@ -19,6 +21,16 @@ const routes: Routes = [
|
|||
{ path: 'home', component: HomeComponent },
|
||||
|
||||
{ path: 'matches', component: MatchesComponent },
|
||||
{
|
||||
path: 'matches/add',
|
||||
component: MatchesAddComponent,
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
{
|
||||
path: 'matches/del/:id',
|
||||
component: MatchesDelComponent,
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
{ path: 'matches/:id', component: MatchesIdComponent },
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
<app-header></app-header>
|
||||
<div class="container mt-4">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
<app-footer></app-footer>
|
||||
|
|
@ -11,6 +11,9 @@ import { LoginComponent } from './components/essentials/login/login.component';
|
|||
import { LogoutComponent } from './components/essentials/logout/logout.component';
|
||||
import { MatchesComponent } from './components/match/matches/matches.component';
|
||||
import { MatchesIdComponent } from './components/match/matches-id/matches-id.component';
|
||||
import { MatchesAddComponent } from './components/match/matches-add/matches-add.component';
|
||||
import { SponsortComponent } from './templates/sponsort/sponsort.component';
|
||||
import { MatchesDelComponent } from './components/match/matches-del/matches-del.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
@ -22,6 +25,9 @@ import { MatchesIdComponent } from './components/match/matches-id/matches-id.com
|
|||
LogoutComponent,
|
||||
MatchesComponent,
|
||||
MatchesIdComponent,
|
||||
MatchesAddComponent,
|
||||
SponsortComponent,
|
||||
MatchesDelComponent,
|
||||
],
|
||||
imports: [BrowserModule, AppRoutingModule, FormsModule],
|
||||
providers: [],
|
||||
|
|
|
|||
|
|
@ -15,31 +15,25 @@
|
|||
>
|
||||
<span class="me-3">Suivez-nous</span>
|
||||
<a
|
||||
href="#"
|
||||
class="text-white me-2 d-flex align-items-center justify-content-center rounded-circle bg-light p-2"
|
||||
style="width: 40px; height: 40px"
|
||||
><i class="bi bi-rss text-dark"></i
|
||||
></a>
|
||||
<a
|
||||
href="#"
|
||||
href="https://x.com/ffescrime"
|
||||
class="text-white me-2 d-flex align-items-center justify-content-center rounded-circle bg-light p-2"
|
||||
style="width: 40px; height: 40px"
|
||||
><i class="bi bi-twitter-x text-dark"></i
|
||||
></a>
|
||||
<a
|
||||
href="#"
|
||||
href="https://www.facebook.com/ffescrime/"
|
||||
class="text-white me-2 d-flex align-items-center justify-content-center rounded-circle bg-light p-2"
|
||||
style="width: 40px; height: 40px"
|
||||
><i class="bi bi-facebook text-dark"></i
|
||||
></a>
|
||||
<a
|
||||
href="#"
|
||||
href="https://www.youtube.com/channel/UCSjeeZaP92mdOICSXsaGdiw"
|
||||
class="text-white me-2 d-flex align-items-center justify-content-center rounded-circle bg-light p-2"
|
||||
style="width: 40px; height: 40px"
|
||||
><i class="bi bi-youtube text-dark"></i
|
||||
></a>
|
||||
<a
|
||||
href="#"
|
||||
href="https://www.linkedin.com/company/ffescrime/"
|
||||
class="text-white d-flex align-items-center justify-content-center rounded-circle bg-light p-2"
|
||||
style="width: 40px; height: 40px"
|
||||
><i class="bi bi-linkedin text-dark"></i
|
||||
|
|
|
|||
|
|
@ -21,6 +21,15 @@
|
|||
Matchs
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="authService.isAuthenticated()">
|
||||
<a
|
||||
class="nav-link"
|
||||
routerLink="/matches/add"
|
||||
routerLinkActive="active-link"
|
||||
>
|
||||
MatchAdd
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="nav-item"
|
||||
*ngIf="authService.isAuthenticated(); else notLoggedIn"
|
||||
|
|
|
|||
|
|
@ -25,8 +25,7 @@
|
|||
}
|
||||
|
||||
.login-header {
|
||||
background: url("https://www.lejdd.fr/lmnr/var/jdd/public/media/image/2022/07/19/12/escrime-comment-les-francais-apprivoisent-leurs-armes.jpg?VersionId=Mw31AogdDUwVLuCaVdxMozm9kito7TTP")
|
||||
no-repeat center/cover;
|
||||
background: url("https://www.lejdd.fr/lmnr/var/jdd/public/media/image/2022/07/19/12/escrime-comment-les-francais-apprivoisent-leurs-armes.jpg?VersionId=Mw31AogdDUwVLuCaVdxMozm9kito7TTP") no-repeat center/cover;
|
||||
position: relative;
|
||||
padding: 4rem 1rem;
|
||||
display: flex;
|
||||
|
|
@ -40,8 +39,11 @@
|
|||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.3); /* voile sombre */
|
||||
backdrop-filter: blur(2px); /* effet de flou */
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
/* voile sombre */
|
||||
-webkit-backdrop-filter: blur(2px);
|
||||
backdrop-filter: blur(2px);
|
||||
/* effet de flou */
|
||||
z-index: 1;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
|
@ -68,18 +70,48 @@
|
|||
gap: 1rem;
|
||||
}
|
||||
|
||||
.login-form h3 {
|
||||
text-align: center;
|
||||
color: #235d4f;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
position: relative;
|
||||
background-color: #235d4f;
|
||||
border-radius: 30px;
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
border: none;
|
||||
border-bottom: 1px solid white;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.input-group input:focus~label,
|
||||
.input-group input:valid~label {
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: #235d4f;
|
||||
font-size: 12px;
|
||||
padding: .6rem;
|
||||
}
|
||||
|
||||
.input-group input .form-control:focus {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
left: 0;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
pointer-events: none;
|
||||
transition: 0.5s;
|
||||
padding: .6rem;
|
||||
}
|
||||
|
||||
.input-group .icon {
|
||||
|
|
@ -87,15 +119,6 @@
|
|||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
border: none;
|
||||
background: none;
|
||||
color: white;
|
||||
width: 100%;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
@ -121,4 +144,4 @@
|
|||
|
||||
.login-btn:hover {
|
||||
background-color: #1b4b3d;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,28 +5,18 @@
|
|||
</div>
|
||||
<form (ngSubmit)="onSubmit()" class="login-form">
|
||||
<div class="input-group">
|
||||
<span class="icon">👤</span>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
[(ngModel)]="email"
|
||||
placeholder="Nom utilisateur"
|
||||
required
|
||||
/>
|
||||
<input type="email" name="email" [(ngModel)]="email" placeholder="Nom utilisateur" value="user@test.com"
|
||||
required />
|
||||
<label class="icon">👤</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<span class="icon">🔒</span>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
[(ngModel)]="password"
|
||||
placeholder="Mot de passe"
|
||||
required
|
||||
/>
|
||||
<input type="password" name="password" [(ngModel)]="password" placeholder="Mot de passe" value="password123"
|
||||
required />
|
||||
<label class="icon">🔒</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="login-btn">Connexion</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,568 @@
|
|||
.match-card {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
border: 2px solid #daa520;
|
||||
padding: 24px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
height: 80vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.match-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, #daa520, #ffd700, #daa520);
|
||||
background-size: 200% 100%;
|
||||
animation: gradientShift 15s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.match-card {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 48px rgba(218, 165, 32, 0.2), 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.match-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.background-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
filter: grayscale(30%) brightness(0.4) contrast(1.2);
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.match-card:hover .background-image {
|
||||
filter: grayscale(10%) brightness(0.3) contrast(1.4);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.image-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(26, 26, 46, 0.85) 0%,
|
||||
rgba(0, 0, 0, 0.7) 50%,
|
||||
rgba(22, 33, 62, 0.85) 100%
|
||||
);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.match-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.match-weapon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #daa520;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.weapon-type {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Status */
|
||||
.match-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: #22c55e;
|
||||
border: 1px solid #22c55e;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: #ef4444;
|
||||
border: 1px solid #ef4444;
|
||||
}
|
||||
|
||||
.status-secondary {
|
||||
background: rgba(59, 130, 246, 0.2);
|
||||
color: #3b82f6;
|
||||
border: 1px solid #3b82f6;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Versus Section */
|
||||
.match-versus {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 24px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fencer {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.fencer-name {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
color: #daa520;
|
||||
}
|
||||
|
||||
.fencer-club {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.score {
|
||||
font-size: 32px;
|
||||
font-weight: 900;
|
||||
color: white;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.vs-divider {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.vs-text {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #6b7280;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.crossed-swords {
|
||||
font-size: 20px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Details */
|
||||
.match-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-item .icon {
|
||||
font-size: 16px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-item .label {
|
||||
color: #9ca3af;
|
||||
font-weight: 500;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.detail-item .value {
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.match-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.match-versus {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.vs-divider {
|
||||
transform: rotate(90deg);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fencer {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.combat-arena {
|
||||
margin-top: 24px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(220, 38, 38, 0.1) 0%,
|
||||
rgba(0, 0, 0, 0.8) 50%,
|
||||
rgba(255, 215, 0, 0.1) 100%
|
||||
);
|
||||
border: 2px solid;
|
||||
border-image: linear-gradient(45deg, #dc2626, #ffd700, #dc2626) 1;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.combat-arena::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: repeating-conic-gradient(
|
||||
from 0deg at 50% 50%,
|
||||
transparent 0deg 2deg,
|
||||
rgba(220, 38, 38, 0.05) 2deg 4deg
|
||||
);
|
||||
animation: rotate 20s linear infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header Arena */
|
||||
.arena-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.arena-title {
|
||||
font-family: "Orbitron", monospace;
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
color: #ffd700;
|
||||
letter-spacing: 3px;
|
||||
text-shadow: 0 0 10px #ffd700, 0 0 20px #ffd700, 0 0 30px #ffd700;
|
||||
animation: titleGlow 2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes titleGlow {
|
||||
from {
|
||||
text-shadow: 0 0 10px #ffd700, 0 0 20px #ffd700, 0 0 30px #ffd700;
|
||||
}
|
||||
to {
|
||||
text-shadow: 0 0 20px #ffd700, 0 0 30px #ffd700, 0 0 40px #ffd700;
|
||||
}
|
||||
}
|
||||
|
||||
.arena-pulse {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #dc2626;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
animation: pulse-danger 1s infinite;
|
||||
}
|
||||
|
||||
.arena-pulse::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
right: -5px;
|
||||
bottom: -5px;
|
||||
background: #dc2626;
|
||||
border-radius: 50%;
|
||||
opacity: 0.3;
|
||||
animation: pulse-ring 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-danger {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-ring {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Combat Grid */
|
||||
.combat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.combat-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(0, 0, 0, 0.8) 0%,
|
||||
rgba(26, 26, 46, 0.9) 50%,
|
||||
rgba(0, 0, 0, 0.8) 100%
|
||||
);
|
||||
border: 1px solid rgba(255, 215, 0, 0.3);
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.combat-stat:hover {
|
||||
transform: translateX(8px);
|
||||
border-color: #ffd700;
|
||||
box-shadow: 0 4px 20px rgba(255, 215, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 215, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Icons animés */
|
||||
.stat-icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.location-radar {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 2px solid #10b981;
|
||||
border-radius: 50%;
|
||||
border-top-color: transparent;
|
||||
animation: radar-spin 2s linear infinite;
|
||||
}
|
||||
|
||||
.referee-badge {
|
||||
position: absolute;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
background: linear-gradient(45deg, #ffd700, #ffed4a);
|
||||
clip-path: polygon(
|
||||
50% 0%,
|
||||
61% 35%,
|
||||
98% 35%,
|
||||
68% 57%,
|
||||
79% 91%,
|
||||
50% 70%,
|
||||
21% 91%,
|
||||
32% 57%,
|
||||
2% 35%,
|
||||
39% 35%
|
||||
);
|
||||
animation: badge-pulse 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.time-ring {
|
||||
position: absolute;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: 3px solid #dc2626;
|
||||
border-radius: 50%;
|
||||
border-left-color: transparent;
|
||||
animation: time-tick 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes radar-spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes badge-pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1) rotate(5deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes time-tick {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-family: "Orbitron", monospace;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #6b7280;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.stat-sub {
|
||||
font-size: 11px;
|
||||
color: #9ca3af;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Glows spécifiques */
|
||||
.venue .stat-value {
|
||||
color: #10b981;
|
||||
}
|
||||
.referee .stat-value {
|
||||
color: #ffd700;
|
||||
}
|
||||
.timing .stat-value {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.stat-glow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 8px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.combat-stat:hover .stat-glow {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.venue-glow {
|
||||
box-shadow: inset 0 0 20px rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
.referee-glow {
|
||||
box-shadow: inset 0 0 20px rgba(255, 215, 0, 0.2);
|
||||
}
|
||||
.timing-glow {
|
||||
box-shadow: inset 0 0 20px rgba(220, 38, 38, 0.2);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (min-width: 768px) {
|
||||
.combat-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.combat-stat {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.combat-stat:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,114 @@
|
|||
<p>HELLO !</p>
|
||||
<div class="match-card" *ngIf="latestMatch">
|
||||
<div class="match-background">
|
||||
<img src="./assets/duel.webp" alt="Escrime" class="background-image" />
|
||||
<div class="image-overlay"></div>
|
||||
</div>
|
||||
|
||||
<div class="match-header">
|
||||
<div class="match-weapon">
|
||||
<i class="sword-icon">⚔️</i>
|
||||
<span class="weapon-type">{{ latestMatch.weapon }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="match-status"
|
||||
[ngClass]="'status-' + getMatchStateColor(latestMatch.state)"
|
||||
>
|
||||
<span class="status-dot"></span>
|
||||
{{ getMatchStateLabel(latestMatch.state) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="match-versus">
|
||||
<div class="fencer fencer-left">
|
||||
<div class="fencer-name">
|
||||
{{ player1?.name }} {{ player1?.firstName }}
|
||||
</div>
|
||||
<div class="fencer-club">{{ player1?.club }}</div>
|
||||
<div class="score">{{ latestMatch.score1 }}</div>
|
||||
</div>
|
||||
|
||||
<div class="vs-divider">
|
||||
<span class="vs-text">VS</span>
|
||||
<div class="crossed-swords">⚔️</div>
|
||||
</div>
|
||||
|
||||
<div class="fencer fencer-right">
|
||||
<div class="fencer-name">
|
||||
{{ player2?.name }} {{ player2?.firstName }}
|
||||
</div>
|
||||
<div class="fencer-club">{{ player2?.club }}</div>
|
||||
<div class="score">{{ latestMatch.score2 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="combat-arena">
|
||||
<div class="arena-header">
|
||||
<span class="arena-title">ARENA COMBAT</span>
|
||||
<div class="arena-pulse"></div>
|
||||
</div>
|
||||
|
||||
<div class="combat-grid">
|
||||
<div class="combat-stat venue">
|
||||
<div class="stat-icon">
|
||||
<div class="location-radar"></div>
|
||||
<span>🏛️</span>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-label">BATTLEFIELD</span>
|
||||
<span class="stat-value">{{ latestMatch.city }}</span>
|
||||
<span class="stat-sub">{{ latestMatch.country }}</span>
|
||||
</div>
|
||||
<div class="stat-glow venue-glow"></div>
|
||||
</div>
|
||||
|
||||
<div class="combat-stat referee">
|
||||
<div class="stat-icon">
|
||||
<div class="referee-badge"></div>
|
||||
<span>⚖️</span>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-label">COMBAT JUDGE</span>
|
||||
<span class="stat-value"
|
||||
>{{ referee?.name }} {{ referee?.firstName }}</span
|
||||
>
|
||||
<span class="stat-sub">OFFICIAL REFEREE</span>
|
||||
</div>
|
||||
<div class="stat-glow referee-glow"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="combat-stat timing"
|
||||
*ngIf="latestMatch?.state === MatchState.NOT_STARTED"
|
||||
>
|
||||
<div class="stat-icon">
|
||||
<div class="time-ring"></div>
|
||||
<span>⚡</span>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-label">COMBAT START</span>
|
||||
<span class="stat-value">{{ getTimeUntilMatch() }}</span>
|
||||
<span class="stat-sub">LIVE BATTLE</span>
|
||||
</div>
|
||||
<div class="stat-glow timing-glow"></div>
|
||||
</div>
|
||||
<div
|
||||
class="combat-stat funfact"
|
||||
*ngIf="latestMatch?.state !== MatchState.NOT_STARTED"
|
||||
>
|
||||
<div class="stat-icon">
|
||||
<span>🎯</span>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-label">FUN FACT</span>
|
||||
<span class="stat-value">
|
||||
Saviez-vous que l'escrime est l'un des sports les plus anciens aux
|
||||
Jeux Olympiques ?
|
||||
</span>
|
||||
<span class="stat-sub">Inspiration et tradition au rendez-vous</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-sponsort></app-sponsort>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
import { MatchesService } from '@services/matches/matches.service';
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
import { Matches, MatchState } from '@interfaces/matches';
|
||||
|
||||
import { PlayerService } from '@services/player/player.service';
|
||||
import { Player } from '@interfaces/player';
|
||||
|
||||
import { RefereeService } from '@services/referee/referee.service';
|
||||
import { Referee, RefereeLevel } from '@interfaces/referee';
|
||||
>>>>>>> feature/front
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
|
|
@ -9,9 +19,144 @@ import { MatchesService } from '@services/matches/matches.service';
|
|||
styleUrl: './home.component.css',
|
||||
})
|
||||
export class HomeComponent {
|
||||
constructor(private matchesService: MatchesService) {}
|
||||
latestMatch?: Matches;
|
||||
player1?: Player;
|
||||
player2?: Player;
|
||||
referee?: Referee;
|
||||
MatchState = MatchState;
|
||||
intervalId?: any;
|
||||
timeUntilMatch: string | null = null;
|
||||
|
||||
send(): void {
|
||||
this.matchesService.sendCurrentMatches();
|
||||
constructor(
|
||||
private matchesService: MatchesService,
|
||||
private playerService: PlayerService,
|
||||
private refereeService: RefereeService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getLatestMatch();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
}
|
||||
|
||||
private getLatestMatch(): void {
|
||||
this.matchesService.getMatches().subscribe((matches) => {
|
||||
if (matches.length > 0) {
|
||||
this.latestMatch = matches[matches.length - 1];
|
||||
console.log(
|
||||
'[HomeComponent] 🏁 Dernier match ajouté :',
|
||||
this.latestMatch
|
||||
);
|
||||
|
||||
// Récupération des données liées
|
||||
this.loadRelatedEntities(this.latestMatch);
|
||||
|
||||
if (this.latestMatch.state === MatchState.NOT_STARTED) {
|
||||
this.updateTimeUntilMatch(); // initial
|
||||
this.intervalId = setInterval(() => {
|
||||
this.updateTimeUntilMatch();
|
||||
}, 60000); // chaque minute
|
||||
}
|
||||
} else {
|
||||
console.warn('[HomeComponent] Aucun match disponible');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateTimeUntilMatch(): void {
|
||||
if (!this.latestMatch) return;
|
||||
|
||||
const now = new Date();
|
||||
const matchDate = new Date(this.latestMatch.date);
|
||||
const diffMs = matchDate.getTime() - now.getTime();
|
||||
|
||||
if (diffMs <= 0) {
|
||||
this.timeUntilMatch = null;
|
||||
clearInterval(this.intervalId);
|
||||
return;
|
||||
}
|
||||
|
||||
const totalMinutes = Math.floor(diffMs / (1000 * 60));
|
||||
const days = Math.floor(totalMinutes / (60 * 24));
|
||||
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
|
||||
let result = '';
|
||||
if (days > 0) result += `${days}j `;
|
||||
if (hours > 0 || days > 0) result += `${hours}h `;
|
||||
result += `${minutes}min`;
|
||||
|
||||
this.timeUntilMatch = result.trim();
|
||||
}
|
||||
|
||||
private loadRelatedEntities(match: Matches): void {
|
||||
this.playerService.getPlayerById(match.player1ID).subscribe((p1) => {
|
||||
this.player1 = p1!;
|
||||
});
|
||||
|
||||
this.playerService.getPlayerById(match.player2ID).subscribe((p2) => {
|
||||
this.player2 = p2!;
|
||||
});
|
||||
|
||||
this.refereeService.getRefereeById(match.refereeID).subscribe((ref) => {
|
||||
this.referee = ref!;
|
||||
});
|
||||
}
|
||||
|
||||
getMatchStateLabel(state: MatchState): string {
|
||||
switch (state) {
|
||||
case MatchState.NOT_STARTED:
|
||||
return 'À venir';
|
||||
case MatchState.ONGOING:
|
||||
return 'En cours';
|
||||
case MatchState.OVER:
|
||||
return 'Terminé';
|
||||
default:
|
||||
return 'Inconnu';
|
||||
}
|
||||
}
|
||||
|
||||
getMatchStateColor(state: MatchState): string {
|
||||
switch (state) {
|
||||
case MatchState.NOT_STARTED:
|
||||
return 'secondary';
|
||||
case MatchState.ONGOING:
|
||||
return 'warning';
|
||||
case MatchState.OVER:
|
||||
return 'success';
|
||||
default:
|
||||
return 'light';
|
||||
}
|
||||
}
|
||||
|
||||
getTimeUntilMatch(): string | null {
|
||||
if (
|
||||
!this.latestMatch ||
|
||||
this.latestMatch.state !== MatchState.NOT_STARTED
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const matchDate = new Date(this.latestMatch.date);
|
||||
const diffMs = matchDate.getTime() - now.getTime();
|
||||
|
||||
if (diffMs <= 0) return null;
|
||||
|
||||
const totalMinutes = Math.floor(diffMs / (1000 * 60));
|
||||
const days = Math.floor(totalMinutes / (60 * 24));
|
||||
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
|
||||
let result = '';
|
||||
if (days > 0) result += `${days}j `;
|
||||
if (hours > 0 || days > 0) result += `${hours}h `;
|
||||
result += `${minutes}min`;
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
form fieldset {
|
||||
background-color: #f8f9fa; /* gris clair */
|
||||
}
|
||||
|
||||
form legend {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
form label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
form button {
|
||||
width: 100%;
|
||||
padding: 0.6rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
<h2 class="mb-4">Ajouter un match</h2>
|
||||
|
||||
<form (ngSubmit)="onSubmit()" class="needs-validation" novalidate>
|
||||
<fieldset class="border p-3 mb-4 rounded">
|
||||
<legend class="w-auto px-2">Joueur 1</legend>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Prénom :</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.player1FirstName"
|
||||
name="player1FirstName"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nom :</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.player1Name"
|
||||
name="player1Name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Club :</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.player1Club"
|
||||
name="player1Club"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="border p-3 mb-4 rounded">
|
||||
<legend class="w-auto px-2">Joueur 2</legend>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Prénom :</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.player2FirstName"
|
||||
name="player2FirstName"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nom :</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.player2Name"
|
||||
name="player2Name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Club :</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.player2Club"
|
||||
name="player2Club"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="border p-3 mb-4 rounded">
|
||||
<legend class="w-auto px-2">Arbitre</legend>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Prénom :</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.refereeFirstName"
|
||||
name="refereeFirstName"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nom :</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.refereeName"
|
||||
name="refereeName"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Niveau :</label>
|
||||
<select
|
||||
class="form-select"
|
||||
[(ngModel)]="formData.refereelevel"
|
||||
name="refereelevel"
|
||||
required
|
||||
>
|
||||
<option [value]="0">Départemental</option>
|
||||
<option [value]="1">Régional</option>
|
||||
<option [value]="2">National</option>
|
||||
<option [value]="3">International</option>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="border p-3 mb-4 rounded">
|
||||
<legend class="w-auto px-2">Match</legend>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Ville :</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.city"
|
||||
name="city"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Pays :</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.country"
|
||||
name="country"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Arme :</label>
|
||||
<select
|
||||
class="form-select"
|
||||
[(ngModel)]="formData.weapon"
|
||||
name="weapon"
|
||||
required
|
||||
>
|
||||
<option value="Épée">Épée</option>
|
||||
<option value="Sabre">Sabre</option>
|
||||
<option value="Fleuret">Fleuret</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Date :</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
class="form-control"
|
||||
[(ngModel)]="formData.date"
|
||||
name="date"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Créer le match</button>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MatchesAddComponent } from './matches-add.component';
|
||||
|
||||
describe('MatchesAddComponent', () => {
|
||||
let component: MatchesAddComponent;
|
||||
let fixture: ComponentFixture<MatchesAddComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [MatchesAddComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MatchesAddComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { MatchesService } from '@services/matches/matches.service';
|
||||
import { MatchState } from '@interfaces/matches';
|
||||
|
||||
import { PlayerService } from '@services/player/player.service';
|
||||
|
||||
import { RefereeService } from '@services/referee/referee.service';
|
||||
import { Referee, RefereeLevel } from '@interfaces/referee';
|
||||
|
||||
@Component({
|
||||
selector: 'app-matches-add',
|
||||
standalone: false,
|
||||
templateUrl: './matches-add.component.html',
|
||||
styleUrl: './matches-add.component.css',
|
||||
})
|
||||
export class MatchesAddComponent {
|
||||
formData = {
|
||||
player1Id: 0,
|
||||
player1FirstName: '',
|
||||
player1Name: '',
|
||||
player1Club: '',
|
||||
|
||||
player2Id: 0,
|
||||
player2FirstName: '',
|
||||
player2Name: '',
|
||||
player2Club: '',
|
||||
|
||||
refereeId: 0,
|
||||
refereeFirstName: '',
|
||||
refereeName: '',
|
||||
refereelevel: 0,
|
||||
|
||||
city: '',
|
||||
country: '',
|
||||
weapon: '',
|
||||
date: '',
|
||||
};
|
||||
|
||||
constructor(
|
||||
private matchService: MatchesService,
|
||||
private playerService: PlayerService,
|
||||
private refereeService: RefereeService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
onSubmit(): void {
|
||||
this.playerService
|
||||
.getOrCreateByName(
|
||||
this.formData.player1FirstName,
|
||||
this.formData.player1Name,
|
||||
this.formData.player1Club
|
||||
)
|
||||
.subscribe((player1) => {
|
||||
this.formData['player1Id'] = player1.id;
|
||||
|
||||
this.playerService
|
||||
.getOrCreateByName(
|
||||
this.formData.player2FirstName,
|
||||
this.formData.player2Name,
|
||||
this.formData.player2Club
|
||||
)
|
||||
.subscribe((player2) => {
|
||||
this.formData['player2Id'] = player2.id;
|
||||
|
||||
this.refereeService
|
||||
.getOrCreateByName(
|
||||
this.formData.refereeFirstName,
|
||||
this.formData.refereeName,
|
||||
this.formData.refereelevel
|
||||
)
|
||||
.subscribe((referee) => {
|
||||
this.formData['refereeId'] = referee.id;
|
||||
|
||||
const matchPayload = {
|
||||
refereeID: referee.id,
|
||||
player1ID: player1.id,
|
||||
score1: 0,
|
||||
player2ID: player2.id,
|
||||
score2: 0,
|
||||
country: this.formData.country,
|
||||
city: this.formData.city,
|
||||
weapon: this.formData.weapon,
|
||||
date: new Date(this.formData.date),
|
||||
state: MatchState.NOT_STARTED,
|
||||
};
|
||||
|
||||
this.matchService.create(matchPayload).subscribe(() => {
|
||||
this.router.navigate(['/matches']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<p>matches-del works!</p>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MatchesDelComponent } from './matches-del.component';
|
||||
|
||||
describe('MatchesDelComponent', () => {
|
||||
let component: MatchesDelComponent;
|
||||
let fixture: ComponentFixture<MatchesDelComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [MatchesDelComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MatchesDelComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { AuthService } from '@services/auth/auth.service';
|
||||
import { MatchesService } from '@services/matches/matches.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-matches-del',
|
||||
standalone: false,
|
||||
templateUrl: './matches-del.component.html',
|
||||
styleUrl: './matches-del.component.css',
|
||||
})
|
||||
export class MatchesDelComponent {
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
public authService: AuthService,
|
||||
private router: Router,
|
||||
private matchService: MatchesService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
const id = Number(this.route.snapshot.paramMap.get('id'));
|
||||
|
||||
if (!isNaN(id)) {
|
||||
this.matchService.deleteMatch(id).subscribe({
|
||||
next: () => {
|
||||
console.log(`Match ${id} supprimé`);
|
||||
this.router.navigate(['/matches']); // redirection après suppression
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Erreur lors de la suppression :', err);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.error('ID de match invalide');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,3 +92,5 @@
|
|||
<div class="container my-5 text-center text-danger" *ngIf="!match">
|
||||
<p>Match non trouvé.</p>
|
||||
</div>
|
||||
|
||||
<a (click)="goToMatchDel()" *ngIf="authService.isAuthenticated()">Delete</a>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { AuthService } from '@services/auth/auth.service';
|
||||
|
||||
import { MatchesService } from '@services/matches/matches.service';
|
||||
import { MatchState, Matches } from '@interfaces/matches';
|
||||
|
||||
import { PlayerService } from '@services/player/player.service';
|
||||
import { Player } from '@interfaces/player';
|
||||
|
||||
import { RefereeService } from '@services/referee/referee.service';
|
||||
import { Referee, RefereeLevel } from '@interfaces/referee';
|
||||
|
||||
import { getMatchStateLabel, getMatchStateColor } from '@utils/getState';
|
||||
|
||||
@Component({
|
||||
selector: 'app-matches-id',
|
||||
standalone: false,
|
||||
|
|
@ -26,7 +30,8 @@ export class MatchesIdComponent implements OnInit {
|
|||
private matchService: MatchesService,
|
||||
private playerService: PlayerService,
|
||||
private refereeService: RefereeService,
|
||||
public authService: AuthService
|
||||
public authService: AuthService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
|
@ -138,4 +143,11 @@ export class MatchesIdComponent implements OnInit {
|
|||
|
||||
this.updateScores();
|
||||
}
|
||||
|
||||
goToMatchDel(): void {
|
||||
this.router.navigate([
|
||||
'/matches/del/',
|
||||
this.route.snapshot.paramMap.get('id'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export class MatchesService {
|
|||
private socket?: WebSocket;
|
||||
private matchUpdates$ = new Subject<Matches[]>();
|
||||
private allMatches: Matches[] = [];
|
||||
private nextMatchId = Math.max(...this.matches.map((m) => m.id)) + 1;
|
||||
|
||||
constructor() {
|
||||
this.connectToMatchUpdatesWebSocket();
|
||||
|
|
@ -122,4 +123,29 @@ export class MatchesService {
|
|||
ngOnDestroy(): void {
|
||||
this.socket?.close();
|
||||
}
|
||||
|
||||
create(match: Omit<Matches, 'id'>): Observable<Matches> {
|
||||
const newMatch: Matches = {
|
||||
...match,
|
||||
id: this.nextMatchId++,
|
||||
};
|
||||
|
||||
this.matches.push(newMatch);
|
||||
console.log('[MatchesService] Match created:', newMatch);
|
||||
return of(newMatch);
|
||||
}
|
||||
|
||||
deleteMatch(id: number): Observable<boolean> {
|
||||
const index = this.matches.findIndex((m) => m.id === id);
|
||||
|
||||
if (index !== -1) {
|
||||
const deletedMatch = this.matches.splice(index, 1)[0];
|
||||
console.log(`[MatchesService] Match ${id} supprimé :`, deletedMatch);
|
||||
|
||||
return of(true);
|
||||
} else {
|
||||
console.warn(`[MatchesService] Match ${id} introuvable pour suppression`);
|
||||
return of(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,4 +27,35 @@ export class PlayerService {
|
|||
console.log(`[PlayerService] Fetching player ID: ${id}`, player);
|
||||
return of(player);
|
||||
}
|
||||
|
||||
getOrCreateByName(
|
||||
firstName: string,
|
||||
name: string,
|
||||
club: string = 'Inconnu'
|
||||
): Observable<Player> {
|
||||
const existing = this.players.find(
|
||||
(p) =>
|
||||
p.firstName.toLowerCase() === firstName.toLowerCase() &&
|
||||
p.name.toLowerCase() === name.toLowerCase()
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
console.log('[PlayerService] Joueur trouvé :', existing);
|
||||
return of(existing);
|
||||
}
|
||||
|
||||
const newPlayer: Player = {
|
||||
id:
|
||||
this.players.length > 0
|
||||
? Math.max(...this.players.map((p) => p.id)) + 1
|
||||
: 1,
|
||||
firstName,
|
||||
name,
|
||||
club,
|
||||
};
|
||||
|
||||
this.players.push(newPlayer);
|
||||
console.log('[PlayerService] Nouveau joueur créé :', newPlayer);
|
||||
return of(newPlayer);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,4 +39,35 @@ export class RefereeService {
|
|||
console.log(`[RefereeService] Fetching referee ID: ${id}`, ref);
|
||||
return of(ref);
|
||||
}
|
||||
|
||||
getOrCreateByName(
|
||||
firstName: string,
|
||||
name: string,
|
||||
level: RefereeLevel
|
||||
): Observable<Referee> {
|
||||
const existing = this.referees.find(
|
||||
(r) =>
|
||||
r.firstName.toLowerCase() === firstName.toLowerCase() &&
|
||||
r.name.toLowerCase() === name.toLowerCase()
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
console.log('[RefereeService] Arbitre trouvé :', existing);
|
||||
return of(existing);
|
||||
}
|
||||
|
||||
const newPlayer: Referee = {
|
||||
id:
|
||||
this.referees.length > 0
|
||||
? Math.max(...this.referees.map((r) => r.id)) + 1
|
||||
: 1,
|
||||
firstName,
|
||||
name,
|
||||
level,
|
||||
};
|
||||
|
||||
this.referees.push(newPlayer);
|
||||
console.log('[RefereeService] Nouveau arbitre créé :', newPlayer);
|
||||
return of(newPlayer);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
@keyframes slides {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
.logos {
|
||||
overflow: hidden;
|
||||
padding: 30px 0px;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logos:before,
|
||||
.logos:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
content: "";
|
||||
width: 250px;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.logos:before {
|
||||
left: 0;
|
||||
background: linear-gradient(
|
||||
to left,
|
||||
rgba(255, 255, 255, 0),
|
||||
rgb(255, 255, 255)
|
||||
);
|
||||
}
|
||||
|
||||
.logos:after {
|
||||
right: 0;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgba(255, 255, 255, 0),
|
||||
rgb(255, 255, 255)
|
||||
);
|
||||
}
|
||||
|
||||
.logo_items {
|
||||
display: inline-block;
|
||||
animation: 35s slides infinite linear;
|
||||
}
|
||||
|
||||
.logos:hover .logo_items {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.logo_items img {
|
||||
height: 100px;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<div class="logos">
|
||||
<div class="logo_items">
|
||||
<img
|
||||
src="https://upload.wikimedia.org/wikipedia/en/9/93/French_Fencing_Federation_logo.png"
|
||||
/>
|
||||
<img
|
||||
src="https://apollon.de/wp-content/uploads/2022/11/allstar_Logo_red_4c-1.png"
|
||||
/>
|
||||
<img
|
||||
src="https://oca.asia/media/sports/federations/logos/1920px-International_Fencing_Federation_logo.svg.png"
|
||||
/>
|
||||
<img
|
||||
src="https://www.leonpaul.com/media/wysiwyg/logos/Round_Leon-Paul_London-outlines800-W_B.jpg"
|
||||
/>
|
||||
<img src="https://trademarks.justia.com/media/image.php?serial=79024409" />
|
||||
<img
|
||||
src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSKR6GOyFsui_qK69EuxP7iGvrh590QkDTciA&s"
|
||||
/>
|
||||
<img
|
||||
src="https://www.prieur-sports.us/assets/images/prieur-sport-fencing-equipment.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SponsortComponent } from './sponsort.component';
|
||||
|
||||
describe('SponsortComponent', () => {
|
||||
let component: SponsortComponent;
|
||||
let fixture: ComponentFixture<SponsortComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [SponsortComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SponsortComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sponsort',
|
||||
standalone: false,
|
||||
templateUrl: './sponsort.component.html',
|
||||
styleUrl: './sponsort.component.css'
|
||||
})
|
||||
export class SponsortComponent {
|
||||
|
||||
}
|
||||
27
FencerJudgeFront/src/app/utils/getState.ts
Normal file
27
FencerJudgeFront/src/app/utils/getState.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { MatchState } from '@interfaces/matches';
|
||||
|
||||
export function getMatchStateLabel(state: MatchState): string {
|
||||
switch (state) {
|
||||
case MatchState.NOT_STARTED:
|
||||
return 'À venir';
|
||||
case MatchState.ONGOING:
|
||||
return 'En cours';
|
||||
case MatchState.OVER:
|
||||
return 'Terminé';
|
||||
default:
|
||||
return 'Inconnu';
|
||||
}
|
||||
}
|
||||
|
||||
export function getMatchStateColor(state: MatchState): string {
|
||||
switch (state) {
|
||||
case MatchState.NOT_STARTED:
|
||||
return 'secondary';
|
||||
case MatchState.ONGOING:
|
||||
return 'warning';
|
||||
case MatchState.OVER:
|
||||
return 'success';
|
||||
default:
|
||||
return 'light';
|
||||
}
|
||||
}
|
||||
BIN
FencerJudgeFront/src/assets/duel.webp
Normal file
BIN
FencerJudgeFront/src/assets/duel.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
6
db.lock.db
Normal file
6
db.lock.db
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#FileLock
|
||||
#Mon Jun 02 17:40:34 CEST 2025
|
||||
hostName=ExostFlash
|
||||
id=197313393c88a931fa21bf5ce1281b7253870b00683
|
||||
method=file
|
||||
server=192.167.2.100\:54366
|
||||
Loading…
Add table
Reference in a new issue