diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 8739003e..4b567f4b 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -4,21 +4,20 @@ import { PageWelcomeComponent } from './components/root/page-welcome/page-welcom import { PageNotFoundComponent } from './components/root/page-not-found/page-not-found.component'; import { PageForbiddenComponent } from './components/root/page-forbidden/page-forbidden.component'; import { ROUTES } from '../routes/routes'; -import { PagePrivacyPolicyComponent } from './components/privacy-policy/page-privacy-policy/page-privacy-policy.component'; const routes: Routes = [ - { path: '', component: PageWelcomeComponent, pathMatch: 'full' }, + { path: '', component: PageWelcomeComponent, pathMatch: 'full' }, { path: '403', component: PageForbiddenComponent }, { path: '404', component: PageNotFoundComponent }, { path: ROUTES.privacyPolicy, loadChildren: () => import('./components/privacy-policy/privacy-policy.module').then((m) => m.PrivacyPolicyModule) }, - { path: ROUTES.news, loadChildren: () => import('./components/news/news.module').then((m) => m.NewsModule)}, - { path: ROUTES.about, loadChildren: () => import('./components/about/about.module').then((m) => m.AboutModule)}, - { path: ROUTES.results, loadChildren: () => import('./components/results/results.module').then((m) => m.ResultsModule)}, - { path: ROUTES.tasks._, loadChildren: () => import('./components/tasks/tasks.module').then((m) => m.TasksModule)}, - { path: ROUTES.discussion, loadChildren: () => import('./components/discussion/discussion.module').then((m) => m.DiscussionModule)}, - { path: ROUTES.profile._, loadChildren: () => import('./components/profile/profile.module').then((m) => m.ProfileModule)}, - { path: ROUTES.achievements, loadChildren: () => import('./components/achievements/achievements.module').then((m) => m.AchievementsModule)}, - { path: ROUTES.admin._, loadChildren: () => import('./components/admin/admin.module').then((m) => m.AdminModule)}, + { path: ROUTES.news, loadChildren: () => import('./components/news/news.module').then((m) => m.NewsModule) }, + { path: ROUTES.about, loadChildren: () => import('./components/about/about.module').then((m) => m.AboutModule) }, + { path: ROUTES.results, loadChildren: () => import('./components/results/results.module').then((m) => m.ResultsModule) }, + { path: ROUTES.tasks._, loadChildren: () => import('./components/tasks/tasks.module').then((m) => m.TasksModule) }, + { path: ROUTES.discussion, loadChildren: () => import('./components/discussion/discussion.module').then((m) => m.DiscussionModule) }, + { path: ROUTES.profile._, loadChildren: () => import('./components/profile/profile.module').then((m) => m.ProfileModule) }, + { path: ROUTES.achievements, loadChildren: () => import('./components/achievements/achievements.module').then((m) => m.AchievementsModule) }, + { path: ROUTES.admin._, loadChildren: () => import('./components/admin/admin.module').then((m) => m.AdminModule) }, { path: '**', component: PageNotFoundComponent } ]; diff --git a/src/app/components/achievements/page-achievements/page-achievements.component.html b/src/app/components/achievements/page-achievements/page-achievements.component.html index 532d6815..c3f9ebe1 100644 --- a/src/app/components/achievements/page-achievements/page-achievements.component.html +++ b/src/app/components/achievements/page-achievements/page-achievements.component.html @@ -2,13 +2,11 @@

{{'achievements.title' | translate}}

- + {{achievement.title}}

-
+ \ No newline at end of file diff --git a/src/app/components/admin/admin-routing.module.ts b/src/app/components/admin/admin-routing.module.ts index ae2b7cff..a416ba61 100644 --- a/src/app/components/admin/admin-routing.module.ts +++ b/src/app/components/admin/admin-routing.module.ts @@ -5,18 +5,31 @@ import { ROUTES } from '../../../routes/routes'; import { PageAdminTasksComponent } from './page-admin-tasks/page-admin-tasks.component'; import { PageAdminMonitorComponent } from './page-admin-monitor/page-admin-monitor.component'; import { PageAdminEmailComponent } from './page-admin-email/page-admin-email.component'; -import {PageAdminDiscussionComponent} from './page-admin-discussion/page-admin-discussion.component'; -import {PageAdminAchievementsComponent} from './page-admin-achievements/page-admin-achievements.component'; -import {PageAdminInstanceConfigComponent} from './page-admin-instance-config/page-admin-instance-config.component'; +import { PageAdminDiscussionComponent } from './page-admin-discussion/page-admin-discussion.component'; +import { PageAdminAchievementsComponent } from './page-admin-achievements/page-admin-achievements.component'; +import { PageAdminInstanceConfigComponent } from './page-admin-instance-config/page-admin-instance-config.component'; +import { PageAdminWavesComponent } from './page-admin-waves/page-admin-waves.component'; +import { PageAdminWavesEditComponent } from './page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component'; +import { PageAdminYearsComponent } from './page-admin-years/page-admin-years.component'; +import { PageAdminYearsEditComponent } from './page-admin-years/page-admin-years-edit/page-admin-years-edit.component'; +import { PageAdminArticlesComponent } from './page-admin-articles/page-admin-articles.component'; +import { PageAdminArticleEditComponent } from './page-admin-articles/page-admin-article-edit/page-admin-article-edit.component'; const routes: Routes = [ - {path: '', component: PageAdminRootComponent}, - {path: ROUTES.admin.tasks, component: PageAdminTasksComponent}, - {path: ROUTES.admin.monitor, component: PageAdminMonitorComponent}, - {path: ROUTES.admin.email, component: PageAdminEmailComponent}, - {path: ROUTES.admin.discussion, component: PageAdminDiscussionComponent}, - {path: ROUTES.admin.achievements, component: PageAdminAchievementsComponent}, - {path: ROUTES.admin.instanceConfig, component: PageAdminInstanceConfigComponent}, + { path: '', component: PageAdminRootComponent }, + { path: ROUTES.admin.tasks, component: PageAdminTasksComponent }, + { path: ROUTES.admin.monitor, component: PageAdminMonitorComponent }, + { path: ROUTES.admin.email, component: PageAdminEmailComponent }, + { path: ROUTES.admin.discussion, component: PageAdminDiscussionComponent }, + { path: ROUTES.admin.achievements, component: PageAdminAchievementsComponent }, + { path: ROUTES.admin.instanceConfig, component: PageAdminInstanceConfigComponent }, + { path: ROUTES.admin.waves._, component: PageAdminWavesComponent }, + { path: ROUTES.admin.waves._ + "/" + ROUTES.admin.waves.edit + "/:waveId", component: PageAdminWavesEditComponent }, + { path: ROUTES.admin.years._, component: PageAdminYearsComponent }, + { path: ROUTES.admin.years._ + "/" + ROUTES.admin.years.edit + "/:yearId", component: PageAdminYearsEditComponent }, + { path: ROUTES.admin.articles._, component: PageAdminArticlesComponent }, + { path: ROUTES.admin.articles._ + "/" + ROUTES.admin.articles.edit + "/:articleId", component: PageAdminArticleEditComponent }, + ]; @NgModule({ diff --git a/src/app/components/admin/admin-sidebar/admin-sidebar.component.html b/src/app/components/admin/admin-sidebar/admin-sidebar.component.html index 5d17fd3c..e4ead2d6 100644 --- a/src/app/components/admin/admin-sidebar/admin-sidebar.component.html +++ b/src/app/components/admin/admin-sidebar/admin-sidebar.component.html @@ -1,32 +1,50 @@
- + \ No newline at end of file diff --git a/src/app/components/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/components/admin/admin-sidebar/admin-sidebar.component.ts index df3c2331..7c7c0fa6 100644 --- a/src/app/components/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/components/admin/admin-sidebar/admin-sidebar.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit, ChangeDetectionStrategy, OnDestroy } from '@angular/core'; import { IconService, RoutesService, UserService, WindowService } from '../../../services'; -import {BehaviorSubject, combineLatest, Observable, Subscription} from 'rxjs'; -import {filter, map, mergeMap} from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; +import { filter, map, mergeMap } from 'rxjs/operators'; import { Router } from '@angular/router'; import { environment } from 'src/environments/environment'; @@ -23,10 +23,8 @@ export class AdminSidebarComponent implements OnInit, OnDestroy { oldFrontendButtons = [ ['users/', 'Uživatelé'], ['years/', 'Ročníky'], - ['waves/', 'Vlny'], ['execs/', 'Spuštění'], ['opravovani/', 'Opravování'], - ['achievements/', 'Trofeje'], ] constructor( diff --git a/src/app/components/admin/admin.module.ts b/src/app/components/admin/admin.module.ts index b3a3bafe..7df0af37 100644 --- a/src/app/components/admin/admin.module.ts +++ b/src/app/components/admin/admin.module.ts @@ -12,12 +12,19 @@ import { SharedModule } from '../shared/shared.module'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import { PageAdminEmailComponent } from './page-admin-email/page-admin-email.component'; import { QuillModule } from 'ngx-quill'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { AccordionModule } from 'ngx-bootstrap/accordion'; import { PageAdminDiscussionComponent } from './page-admin-discussion/page-admin-discussion.component'; import { AdminWaveSelectorComponent } from './shared/admin-wave-selector/admin-wave-selector.component'; import { PageAdminAchievementsComponent } from './page-admin-achievements/page-admin-achievements.component'; import { PageAdminInstanceConfigComponent } from './page-admin-instance-config/page-admin-instance-config.component'; +import { PageAdminWavesComponent } from './page-admin-waves/page-admin-waves.component'; +import { PageAdminWavesEditComponent } from './page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component'; +import { DefaultService } from 'src/api/backend'; +import { PageAdminYearsComponent } from './page-admin-years/page-admin-years.component'; +import { PageAdminYearsEditComponent } from './page-admin-years/page-admin-years-edit/page-admin-years-edit.component'; +import { PageAdminArticlesComponent } from './page-admin-articles/page-admin-articles.component'; +import { PageAdminArticleEditComponent } from './page-admin-articles/page-admin-article-edit/page-admin-article-edit.component'; @NgModule({ @@ -31,7 +38,13 @@ import { PageAdminInstanceConfigComponent } from './page-admin-instance-config/p PageAdminDiscussionComponent, AdminWaveSelectorComponent, PageAdminAchievementsComponent, - PageAdminInstanceConfigComponent + PageAdminInstanceConfigComponent, + PageAdminWavesComponent, + PageAdminWavesEditComponent, + PageAdminYearsComponent, + PageAdminYearsEditComponent, + PageAdminArticlesComponent, + PageAdminArticleEditComponent, ], imports: [ CommonModule, @@ -42,7 +55,11 @@ import { PageAdminInstanceConfigComponent } from './page-admin-instance-config/p QuillModule, ReactiveFormsModule, AccordionModule, - FormsModule - ] + FormsModule, + ], + providers: [ + DefaultService // Add DefaultService to the providers array + ], + }) export class AdminModule { } diff --git a/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.html b/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.html new file mode 100644 index 00000000..1d7a5788 --- /dev/null +++ b/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.html @@ -0,0 +1,13 @@ + + +
+
+

Work in progress...

+ Zpět na přehled + +
+ Work in progress +
+
+
\ No newline at end of file diff --git a/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.scss b/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.scss new file mode 100644 index 00000000..0b578dd1 --- /dev/null +++ b/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.scss @@ -0,0 +1,4 @@ +@import "src/app/styles/vars"; +@import "src/app/styles/mixins"; + +@include page-admin; \ No newline at end of file diff --git a/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.spec.ts b/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.spec.ts new file mode 100644 index 00000000..f9bffc0f --- /dev/null +++ b/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageAdminArticleEditComponent } from './page-admin-article-edit.component'; + +describe('PageAdminArticleEditComponent', () => { + let component: PageAdminArticleEditComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageAdminArticleEditComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageAdminArticleEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.ts b/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.ts new file mode 100644 index 00000000..d3b47f8d --- /dev/null +++ b/src/app/components/admin/page-admin-articles/page-admin-article-edit/page-admin-article-edit.component.ts @@ -0,0 +1,64 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { Router } from '@angular/router'; +import { EditMode } from 'src/app/models/EditMode'; +import { IconService, RoutesService, YearsService } from 'src/app/services'; +import { AdminArticlesService } from 'src/app/services/admin/admin-articles.service'; +import { AdminWavesService } from 'src/app/services/admin/admin-waves.service'; + +@Component({ + selector: 'ksi-page-admin-article-edit', + templateUrl: './page-admin-article-edit.component.html', + styleUrls: ['./page-admin-article-edit.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PageAdminArticleEditComponent implements OnInit { + editMode: EditMode; + EditMode = { + New: 'New', + Edit: 'Edit' + }; + + articleId: number; + + form = this.fb.group({ + title: [''], + body: [''], + published: [false], + year: [this.years.selected?.id], // User won't set this - it's for better DevEx when submitting + time_published: [null], // Store as ISO string + picture: [null] + }); + + constructor( + public icon: IconService, + public routes: RoutesService, + public years: YearsService, + public router: Router, + public fb: FormBuilder, + private cdRef: ChangeDetectorRef, + private adminArticlesService: AdminArticlesService + ) { } + + ngOnInit(): void { + this.articleId = Number.parseInt(this.router.url.split('/').pop() || '0', 10); + + this.editMode = this.articleId == 0 ? EditMode.New : EditMode.Update; + + if (this.editMode === EditMode.Update) { + this.adminArticlesService.getArticleById(this.articleId).subscribe({ + next: (article) => { + if (article) { + this.form.patchValue(article); + this.cdRef.markForCheck(); + } else { + alert('Article not found'); + this.router.navigate([this.routes.routes.admin._, this.routes.routes.admin.articles._]); + } + } + }); + } + + } + +} diff --git a/src/app/components/admin/page-admin-articles/page-admin-articles.component.html b/src/app/components/admin/page-admin-articles/page-admin-articles.component.html new file mode 100644 index 00000000..a23211d9 --- /dev/null +++ b/src/app/components/admin/page-admin-articles/page-admin-articles.component.html @@ -0,0 +1,38 @@ + +
+
+ +

Správa článků

+ + {{ icon.ADD }} {{ 'admin.articles.new' | translate }} + + + + + + + + + + + + + + + + + + +
{{ 'admin.articles.head.title' | translate}}{{ 'admin.articles.head.published_date' | translate}}{{ 'admin.articles.head.released' | translate}}{{ 'admin.articles.head.actions' | translate}}
{{article.title}} + {{(article.time_published | date : 'medium') || "---"}}{{article.published ? icon.CHECKMARK : icon.CROSS}} + {{ + icon.EDIT }} + + +
+
+
\ No newline at end of file diff --git a/src/app/components/admin/page-admin-articles/page-admin-articles.component.scss b/src/app/components/admin/page-admin-articles/page-admin-articles.component.scss new file mode 100644 index 00000000..0b578dd1 --- /dev/null +++ b/src/app/components/admin/page-admin-articles/page-admin-articles.component.scss @@ -0,0 +1,4 @@ +@import "src/app/styles/vars"; +@import "src/app/styles/mixins"; + +@include page-admin; \ No newline at end of file diff --git a/src/app/components/admin/page-admin-articles/page-admin-articles.component.spec.ts b/src/app/components/admin/page-admin-articles/page-admin-articles.component.spec.ts new file mode 100644 index 00000000..42b2f64f --- /dev/null +++ b/src/app/components/admin/page-admin-articles/page-admin-articles.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageAdminArticlesComponent } from './page-admin-articles.component'; + +describe('PageAdminArticlesComponent', () => { + let component: PageAdminArticlesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageAdminArticlesComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageAdminArticlesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/admin/page-admin-articles/page-admin-articles.component.ts b/src/app/components/admin/page-admin-articles/page-admin-articles.component.ts new file mode 100644 index 00000000..13e4f61a --- /dev/null +++ b/src/app/components/admin/page-admin-articles/page-admin-articles.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Article } from 'src/api/backend'; +import { IconService, RoutesService, YearsService } from 'src/app/services'; +import { AdminArticlesService } from 'src/app/services/admin/admin-articles.service'; +import { AdminWavesService } from 'src/app/services/admin/admin-waves.service'; + +@Component({ + selector: 'ksi-page-admin-articles', + templateUrl: './page-admin-articles.component.html', + styleUrls: ['./page-admin-articles.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PageAdminArticlesComponent implements OnInit { + + constructor( + public icon: IconService, + public routes: RoutesService, + public years: YearsService, + private cdr: ChangeDetectorRef, + private adminArticlesService: AdminArticlesService + ) { } + + articles$: Observable; + + + ngOnInit(): void { + this.reloadArticles(); + } + + reloadArticles() { + this.articles$ = this.adminArticlesService.getArticles(); + this.cdr.markForCheck(); + } + + notImplemented(): void { + alert(`Feature is not implemented yet.`); + } + + deleteArticle(article: Article): void { + if (confirm(`Are you sure you want to delete article "${article.title}"?`)) { + this.adminArticlesService.deleteArticle(article.id).subscribe({ + next: () => { + this.reloadArticles(); + }, + error: (err) => { + alert(`Error deleting article: ${err?.message || err}`); + } + }); + } + } + +} diff --git a/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.html b/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.html new file mode 100644 index 00000000..8e14acaa --- /dev/null +++ b/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.html @@ -0,0 +1,55 @@ + + +
+
+

{{ editMode === EditMode.New ? ('admin.waves.edit_page.new' | translate) : ('admin.waves.edit_page.edit' | + translate) }}

+ {{ 'admin.waves.back' | translate + }} + +
+ +
+ + +
+ {{ 'admin.waves.caption.required' | translate }} +
+
+ +
+ + +
+ {{ 'admin.waves.garant.required' | translate }} +
+
+ +
+ + +
+ {{ 'admin.waves.time_published.required' | translate }} +
+
+ +
+ + +
+ {{ 'admin.waves.index.required' | translate }} +
+
+ + +
+
+
\ No newline at end of file diff --git a/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.scss b/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.scss new file mode 100644 index 00000000..0b578dd1 --- /dev/null +++ b/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.scss @@ -0,0 +1,4 @@ +@import "src/app/styles/vars"; +@import "src/app/styles/mixins"; + +@include page-admin; \ No newline at end of file diff --git a/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.spec.ts b/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.spec.ts new file mode 100644 index 00000000..6818a8fa --- /dev/null +++ b/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageAdminWavesEditComponent } from './page-admin-waves-edit.component'; + +describe('PageAdminWavesEditComponent', () => { + let component: PageAdminWavesEditComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageAdminWavesEditComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageAdminWavesEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.ts b/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.ts new file mode 100644 index 00000000..f409b530 --- /dev/null +++ b/src/app/components/admin/page-admin-waves/page-admin-waves-edit/page-admin-waves-edit.component.ts @@ -0,0 +1,107 @@ +import { Component, OnInit, ChangeDetectionStrategy, ViewChild, ChangeDetectorRef, ElementRef } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import { shareReplay } from 'rxjs/operators'; +import { User, WaveCreationRequest } from 'src/api/backend'; +import { EditMode } from 'src/app/models/EditMode'; +import { IconService, RoutesService, YearsService } from 'src/app/services'; +import { AdminWavesService } from 'src/app/services/admin/admin-waves.service'; +import { NgbAlertModule, NgbDatepickerModule, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'ksi-page-admin-waves-edit', + templateUrl: './page-admin-waves-edit.component.html', + styleUrls: ['./page-admin-waves-edit.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PageAdminWavesEditComponent implements OnInit { + editMode: EditMode; + EditMode = { + New: 'New', + Edit: 'Edit' + }; + + @ViewChild('timePublishedInput') timePublishedInput: ElementRef; + + form = this.fb.group({ + index: [7, [Validators.required]], + caption: ["", [Validators.required]], + time_published: [null, [Validators.required]], // Store as ISO string + garant: [null, [Validators.required]], + year: [this.years.selected?.id] // User won't set this - it's for better DevEx when submitting + }); + + adminUsers$: Observable; + waveId: number; + + constructor( + public icon: IconService, + public routes: RoutesService, + public years: YearsService, + public router: Router, + public fb: FormBuilder, + private cdRef: ChangeDetectorRef, + private adminWavesService: AdminWavesService + ) { } + + ngOnInit(): void { + this.waveId = Number.parseInt(this.router.url.split('/').pop() || '0', 10); + + this.editMode = this.waveId == 0 ? EditMode.New : EditMode.Update; + this.adminUsers$ = this.years.organisators$.pipe( + shareReplay(1) + ); + + if (this.editMode === EditMode.Update) { + const waveId = Number.parseInt(this.router.url.split('/').pop() || '', 10); + this.adminWavesService.getWaveById(waveId).subscribe(waveResponse => { + if (waveResponse) { + this.form.patchValue(waveResponse.wave); + (this.timePublishedInput.nativeElement as HTMLInputElement).value = new Date(waveResponse.wave.time_published as string).toISOString().split('T')[0]; + } else { + alert('Wave not found. Redirecting to the waves list.'); + this.router.navigate(['/', this.routes.routes.admin._, this.routes.routes.admin.waves._]); + return; + } + + }, error => { + console.error('Error fetching wave data:', error); + alert('Failed to load wave data. Please try again.'); + }); + } + + } + + onDateInput(event: Event): void { + const input = event.target as HTMLInputElement; + const isoDate = input.value ? new Date(input.value).toISOString() : null; + this.form.patchValue({ time_published: isoDate }); + } + + save() { + if (!this.form.valid) { + this.form.markAllAsTouched(); + return; + } + + const waveData: WaveCreationRequest = { wave: this.form.value as WaveCreationRequest['wave'] }; + + if (this.editMode === EditMode.New) { + this.adminWavesService.createWave(waveData).subscribe(() => { + this.router.navigate(['/', this.routes.routes.admin._, this.routes.routes.admin.waves._]); + }, error => { + console.error('Error creating wave:', error); + alert('Failed to create wave. Please try again.'); + }); + } else { + this.adminWavesService.updateWave(waveData, this.waveId).subscribe(() => { + this.router.navigate(['/', this.routes.routes.admin._, this.routes.routes.admin.waves._]); + }, error => { + console.error('Error updating wave:', error); + alert('Failed to update wave. Please try again.'); + }); + } + } + +} diff --git a/src/app/components/admin/page-admin-waves/page-admin-waves.component.html b/src/app/components/admin/page-admin-waves/page-admin-waves.component.html new file mode 100644 index 00000000..5bccd47a --- /dev/null +++ b/src/app/components/admin/page-admin-waves/page-admin-waves.component.html @@ -0,0 +1,70 @@ + +
+
+ +

{{ 'admin.waves.title' | translate }}

+ + {{ icon.ADD }} {{ 'admin.waves.new' | translate }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'admin.waves.head.wave' | translate }}{{ 'admin.waves.head.name' | translate }}{{ 'admin.waves.head.guarantor' | translate }}{{ 'admin.waves.head.release' | translate }}{{ 'admin.waves.head.max-points' | translate }}{{ 'admin.waves.head.task-count' | translate }}{{ 'admin.waves.head.actions' | translate }}
{{wave.index}}{{wave.caption}} + + + {{(wave.time_published | date : 'medium') || "---"}}{{wave.sum_points}}{{wave.tasks_cnt}} + + {{ + icon.EDIT }} + + +
{{ totalPoints$ | async }}{{ tasksCount$ | async }}
+ +
+

{{ 'admin.waves.warning.title' | translate }}: {{ 'admin.waves.warning.message' | translate }} +

+
+ +
+
\ No newline at end of file diff --git a/src/app/components/admin/page-admin-waves/page-admin-waves.component.scss b/src/app/components/admin/page-admin-waves/page-admin-waves.component.scss new file mode 100644 index 00000000..0b578dd1 --- /dev/null +++ b/src/app/components/admin/page-admin-waves/page-admin-waves.component.scss @@ -0,0 +1,4 @@ +@import "src/app/styles/vars"; +@import "src/app/styles/mixins"; + +@include page-admin; \ No newline at end of file diff --git a/src/app/components/admin/page-admin-waves/page-admin-waves.component.spec.ts b/src/app/components/admin/page-admin-waves/page-admin-waves.component.spec.ts new file mode 100644 index 00000000..e6f93b5d --- /dev/null +++ b/src/app/components/admin/page-admin-waves/page-admin-waves.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageAdminWavesComponent } from './page-admin-waves.component'; + +describe('PageAdminWavesComponent', () => { + let component: PageAdminWavesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageAdminWavesComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageAdminWavesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/admin/page-admin-waves/page-admin-waves.component.ts b/src/app/components/admin/page-admin-waves/page-admin-waves.component.ts new file mode 100644 index 00000000..bab1be25 --- /dev/null +++ b/src/app/components/admin/page-admin-waves/page-admin-waves.component.ts @@ -0,0 +1,72 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map, } from 'rxjs/operators'; +import { Wave } from 'src/api/backend'; +import { IconService, RoutesService, YearsService } from 'src/app/services'; +import { AdminWavesService } from 'src/app/services/admin/admin-waves.service'; + +@Component({ + selector: 'ksi-page-admin-waves', + templateUrl: './page-admin-waves.component.html', + styleUrls: ['./page-admin-waves.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PageAdminWavesComponent implements OnInit { + constructor( + public icon: IconService, + public routes: RoutesService, + public years: YearsService, + private cdr: ChangeDetectorRef, + private adminWavesService: AdminWavesService + ) { } + + waves$: Observable; + totalPoints$: Observable; + tasksCount$: Observable; + + ngOnInit(): void { + this.adminWavesService.getWaves(); + this.reloadWaves(); + + this.totalPoints$ = this.waves$.pipe( + map(waves => waves.reduce((sum, wave) => sum + (wave.sum_points || 0), 0)) + ); + + this.tasksCount$ = this.waves$.pipe( + map(waves => waves.reduce((count, wave) => count + (wave.tasks_cnt || 0), 0)) + ); + } + + reloadWaves() { + console.log('Reloading waves...'); + this.waves$ = this.adminWavesService.getWaves().pipe(map(response => response.waves)); + this.cdr.markForCheck(); + } + + notImplemented(): void { + alert(`Feature is not implemented yet.`); + } + + deleteWave(wave: Wave): void { + if (confirm(`Are you sure you want to delete wave "${wave.caption}"?`)) { + this.adminWavesService.deleteWave(wave.id).subscribe({ + next: () => { + this.reloadWaves(); + }, + error: (err) => { + alert(`Error deleting wave: ${err?.message || err}`); + } + }); + } + } + + waveIsReleased(wave: Wave): boolean { + return new Date(wave.time_published!) < new Date(); + } + + waveHasTasks(wave: Wave): boolean { + return wave.tasks_cnt > 0; + } + + +} diff --git a/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.html b/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.html new file mode 100644 index 00000000..f10304d3 --- /dev/null +++ b/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.html @@ -0,0 +1,66 @@ + + +
+
+

{{ editMode === EditMode.New ? ('admin.years.editPage.newYear' | translate) : ('admin.years.editPage.edit' | + translate) }}

+ + {{ 'admin.years.editPage.backToOverview' | translate }} + + +
+
+ + +
+ {{ 'admin.years.editPage.indexRequired' | translate }} +
+
+ +
+ + +
+ {{ 'admin.years.editPage.yearRequired' | translate }} +
+
+ +
+ +
+ +
+ + +
+ {{ 'admin.years.editPage.pointPadInvalid' | translate }} +
+
+ +
+ +
    +
  • + +
  • +
+ +
+ {{ 'admin.years.editPage.activeOrgsRequired' | translate }} +
+
+ + +
+
+
\ No newline at end of file diff --git a/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.scss b/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.scss new file mode 100644 index 00000000..0b578dd1 --- /dev/null +++ b/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.scss @@ -0,0 +1,4 @@ +@import "src/app/styles/vars"; +@import "src/app/styles/mixins"; + +@include page-admin; \ No newline at end of file diff --git a/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.spec.ts b/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.spec.ts new file mode 100644 index 00000000..3fef287c --- /dev/null +++ b/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageAdminYearsEditComponent } from './page-admin-years-edit.component'; + +describe('PageAdminYearsEditComponent', () => { + let component: PageAdminYearsEditComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageAdminYearsEditComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageAdminYearsEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.ts b/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.ts new file mode 100644 index 00000000..4fe64c9d --- /dev/null +++ b/src/app/components/admin/page-admin-years/page-admin-years-edit/page-admin-years-edit.component.ts @@ -0,0 +1,123 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { Observable, Subscription } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { User } from 'src/api/backend'; +import { EditMode } from 'src/app/models/EditMode'; +import { IconService, RoutesService } from 'src/app/services'; +import { AdminYearsService } from 'src/app/services/admin/admin-years.service'; + +@Component({ + selector: 'ksi-page-admin-years-edit', + templateUrl: './page-admin-years-edit.component.html', + styleUrls: ['./page-admin-years-edit.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PageAdminYearsEditComponent implements OnInit { + + editMode: EditMode; + EditMode = { + New: 'New', + Edit: 'Edit' + }; + + yearId: number; + + form = this.fb.group({ + index: [0, [Validators.required]], + year: ["", [Validators.required]], + sealed: [false], + point_pad: [null, [Validators.required]], + active_orgs: [[]] + }); + + adminUsers$: Observable; + subscriptions: Subscription[] = []; + + constructor( + public icon: IconService, + public routes: RoutesService, + public adminYears: AdminYearsService, + private fb: FormBuilder, + private router: Router, + private cdr: ChangeDetectorRef, + ) { } + + + ngOnInit(): void { + this.yearId = Number.parseInt(this.router.url.split('/').pop() || '0', 10); + this.editMode = this.yearId == 0 ? EditMode.New : EditMode.Update; + + if (this.editMode === EditMode.Update) { + this.adminYears.getYearById(this.yearId).subscribe({ + next: (year) => { + if (year) { + this.form.patchValue(year); + } else { + alert('Year not found'); + this.router.navigate([this.routes.routes.admin.years]); + } + }, + error: (err) => { + alert(`Error loading year: ${err?.message || err}`); + this.router.navigate([this.routes.routes.admin.years]); + } + }); + } + + this.adminUsers$ = this.adminYears.getAllOrganisators(); + + let x = this.adminUsers$.pipe( + tap(users => { + const activeUsers = users.filter(user => user.seasons?.includes(this.yearId)).map(user => '' + user.id); + this.form.patchValue({ active_orgs: activeUsers }); + }) + ).subscribe(); + + this.subscriptions.push(x); + } + + save() { + if (this.form.invalid) { + this.form.markAllAsTouched(); + return; + } + + const saveOperation = this.editMode === EditMode.New + ? this.adminYears.createYear({ year: this.form.value }) + : this.adminYears.updateYear({ year: this.form.value }, this.yearId); + + console.log('Saving year:', this.form.value); + let x = saveOperation.subscribe({ + next: () => { + this.router.navigate(['/', this.routes.routes.admin._, this.routes.routes.admin.years._]); + }, + error: (err) => { + alert(`Error: ${err?.message || err}`); + } + }); + + this.subscriptions.push(x); + } + + + isUserActive(userId: number): boolean { + return this.form.value.active_orgs.includes('' + userId); + } + + toggleUserActive(userId: number) { + const activeOrgs = this.form.value.active_orgs; + if (this.isUserActive(userId)) { + this.form.patchValue({ active_orgs: activeOrgs.filter((id: string) => id !== '' + userId) }); + } else { + this.form.patchValue({ active_orgs: [...activeOrgs, '' + userId] }); + } + this.cdr.markForCheck(); + } + + ngOnDestroy() { + this.subscriptions.forEach(sub => sub.unsubscribe()); + } + +} diff --git a/src/app/components/admin/page-admin-years/page-admin-years.component.html b/src/app/components/admin/page-admin-years/page-admin-years.component.html new file mode 100644 index 00000000..661db97d --- /dev/null +++ b/src/app/components/admin/page-admin-years/page-admin-years.component.html @@ -0,0 +1,54 @@ + +
+
+ +

{{ 'admin.years.title' | translate }}

+ + {{ icon.ADD }} {{ 'admin.years.newYear' | translate }} + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'admin.years.index' | translate }}{{ 'admin.years.year' | translate }}{{ 'admin.years.maxPoints' | translate }}{{ 'admin.years.pointPad' | translate }}{{ 'admin.years.taskCount' | translate }}{{ 'admin.years.sealed' | translate }}{{ 'admin.years.actions' | translate }}
{{year.id}}{{year.year}} + {{year.sum_points}}{{year.point_pad}}{{year.tasks_cnt}}{{ year.sealed ? icon.CHECKMARK : icon.CROSS }} + {{ + icon.EDIT }} {{ 'admin.years.edit' | translate }} + + + +
+ +
+ {{ 'admin.years.warningTitle' | translate }}: {{ 'admin.years.warningMessage' | translate }} +
+ +
+
\ No newline at end of file diff --git a/src/app/components/admin/page-admin-years/page-admin-years.component.scss b/src/app/components/admin/page-admin-years/page-admin-years.component.scss new file mode 100644 index 00000000..0b578dd1 --- /dev/null +++ b/src/app/components/admin/page-admin-years/page-admin-years.component.scss @@ -0,0 +1,4 @@ +@import "src/app/styles/vars"; +@import "src/app/styles/mixins"; + +@include page-admin; \ No newline at end of file diff --git a/src/app/components/admin/page-admin-years/page-admin-years.component.spec.ts b/src/app/components/admin/page-admin-years/page-admin-years.component.spec.ts new file mode 100644 index 00000000..7f837f90 --- /dev/null +++ b/src/app/components/admin/page-admin-years/page-admin-years.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageAdminYearsComponent } from './page-admin-years.component'; + +describe('PageAdminYearsComponent', () => { + let component: PageAdminYearsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageAdminYearsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageAdminYearsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/admin/page-admin-years/page-admin-years.component.ts b/src/app/components/admin/page-admin-years/page-admin-years.component.ts new file mode 100644 index 00000000..54bcac01 --- /dev/null +++ b/src/app/components/admin/page-admin-years/page-admin-years.component.ts @@ -0,0 +1,47 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Observable } from 'rxjs'; +import { IYear } from 'src/app/models'; +import { IconService, RoutesService, YearsService } from 'src/app/services'; +import { AdminWavesService } from 'src/app/services/admin/admin-waves.service'; +import { AdminYearsService } from 'src/app/services/admin/admin-years.service'; + +@Component({ + selector: 'ksi-page-admin-years', + templateUrl: './page-admin-years.component.html', + styleUrls: ['./page-admin-years.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PageAdminYearsComponent implements OnInit { + + constructor( + public icon: IconService, + public routes: RoutesService, + public adminYears: AdminYearsService, + private cdr: ChangeDetectorRef, + ) { } + + years$: Observable; + + ngOnInit(): void { + this.reloadYears(); + } + + reloadYears() { + this.years$ = this.adminYears.getYears(); + this.cdr.markForCheck(); + } + + deleteYear(yearId: number): void { + if (confirm(`Are you sure you want to delete this year?`)) { + this.adminYears.deleteYear(yearId).subscribe({ + next: () => { + this.reloadYears(); + }, + error: (err) => { + alert(`Error deleting year: ${err?.message || err}`); + } + }); + } + } + +} diff --git a/src/app/components/shared/shared.module.ts b/src/app/components/shared/shared.module.ts index f020373b..886dfd43 100644 --- a/src/app/components/shared/shared.module.ts +++ b/src/app/components/shared/shared.module.ts @@ -28,7 +28,7 @@ import { ModalFeedbackComponent } from './modal-feedback/modal-feedback.componen import { FeedbackComponent } from './feedback/feedback.component'; import { RatingModule } from 'ngx-bootstrap/rating'; import { ClickableUserNameComponent } from './clickable-user-name/clickable-user-name.component'; -import {KsiDatePipe} from '../../pipes/shared/ksi-date.pipe'; +import { KsiDatePipe } from '../../pipes/shared/ksi-date.pipe'; import { TermsOfUseComponent } from './terms-of-use/terms-of-use.component'; @NgModule({ diff --git a/src/app/models/EditMode.ts b/src/app/models/EditMode.ts new file mode 100644 index 00000000..07636fb8 --- /dev/null +++ b/src/app/models/EditMode.ts @@ -0,0 +1,4 @@ +export enum EditMode { + New = 'New', + Update = 'Update' +} \ No newline at end of file diff --git a/src/app/models/routes.ts b/src/app/models/routes.ts index 1bced790..9871863c 100644 --- a/src/app/models/routes.ts +++ b/src/app/models/routes.ts @@ -26,6 +26,18 @@ export interface IRoutes { discussion: string; achievements: string; instanceConfig: string; + waves: { + _: string; + edit: string; + }, + years: { + _: string; + edit: string; + }, + articles: { + _: string; + edit: string; + } }; privacyPolicy: string; } diff --git a/src/app/services/admin/admin-articles.service.spec.ts b/src/app/services/admin/admin-articles.service.spec.ts new file mode 100644 index 00000000..0332b54f --- /dev/null +++ b/src/app/services/admin/admin-articles.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AdminArticlesService } from './admin-articles.service'; + +describe('AdminArticlesService', () => { + let service: AdminArticlesService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AdminArticlesService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/admin/admin-articles.service.ts b/src/app/services/admin/admin-articles.service.ts new file mode 100644 index 00000000..890a340a --- /dev/null +++ b/src/app/services/admin/admin-articles.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@angular/core'; +import { BackendService, YearsService } from '../shared'; +import { Observable } from 'rxjs'; +import { Article, ArticleCreationRequest } from 'src/api/backend'; +import { map, switchMap, tap } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class AdminArticlesService { + + constructor( + private backend: BackendService, + private yearsService: YearsService + ) { } + + public getArticles(): Observable { + return this.yearsService.selected$.pipe( + switchMap(year => this.backend.http.articlesGetAll(undefined, undefined, undefined, year?.id).pipe( + map(response => response.articles || []), + tap(articles => console.log('Fetched articles:', articles)) + )) + ); + } + + public getArticleById(articleId: number): Observable
{ + return this.backend.http.articlesGetSingle(articleId).pipe( + map(response => response.article) + ); + } + + public createArticle(article: ArticleCreationRequest): Observable { + return this.backend.http.articlesCreateNew(article); + } + + public updateArticle(articleId: number, article: ArticleCreationRequest): Observable
{ + return this.backend.http.articlesEditSingle(article, articleId).pipe( + map(response => response.article) + ); + } + + public deleteArticle(articleId: number): Observable { + return this.backend.http.articlesDeleteSingle(articleId).pipe( + map(() => undefined) + ); + } +} diff --git a/src/app/services/admin/admin-waves.service.spec.ts b/src/app/services/admin/admin-waves.service.spec.ts new file mode 100644 index 00000000..f21ed00b --- /dev/null +++ b/src/app/services/admin/admin-waves.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AdminWavesService } from './admin-waves.service'; + +describe('AdminWavesService', () => { + let service: AdminWavesService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AdminWavesService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/admin/admin-waves.service.ts b/src/app/services/admin/admin-waves.service.ts new file mode 100644 index 00000000..49c6e7dd --- /dev/null +++ b/src/app/services/admin/admin-waves.service.ts @@ -0,0 +1,41 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { EmptyDict, Wave, WaveCreate, WaveCreationRequest, WaveResponse, Waves, WaveUpdateRequest } from 'src/api/backend'; +import { BackendService, YearsService } from '../shared'; +import { map, switchMap } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class AdminWavesService { + + constructor( + private backend: BackendService, + private yearsService: YearsService + ) { } + + public getWaves(): Observable { + return this.yearsService.selected$.pipe( + switchMap(year => this.backend.http.wavesGetAll(year?.id)) + ); + } + + public createWave(wave: WaveCreationRequest): Observable { + return this.backend.http.wavesCreateNew(wave); + } + + public deleteWave(waveId: number): Observable { + return this.backend.http.wavesDeleteSingle(waveId); + } + + public getWaveById(waveId: number): Observable { + return this.backend.http.wavesGetSingle(waveId); + } + + public updateWave(wave: WaveUpdateRequest, waveId: number): Observable { + return this.backend.http.wavesUpdateSingle(wave, waveId); + } + + +} diff --git a/src/app/services/admin/admin-years.service.spec.ts b/src/app/services/admin/admin-years.service.spec.ts new file mode 100644 index 00000000..eb8671f4 --- /dev/null +++ b/src/app/services/admin/admin-years.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AdminYearsService } from './admin-years.service'; + +describe('AdminYearsService', () => { + let service: AdminYearsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AdminYearsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/admin/admin-years.service.ts b/src/app/services/admin/admin-years.service.ts new file mode 100644 index 00000000..2db677fc --- /dev/null +++ b/src/app/services/admin/admin-years.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@angular/core'; +import { BackendService, YearsService } from '../shared'; +import { map, switchMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { User, Year, YearCreationRequest, YearResponse, YearUpdateRequest } from 'src/api/backend'; + +@Injectable({ + providedIn: 'root' +}) +export class AdminYearsService { + + constructor( + private backend: BackendService, + ) { } + + public getYears(): Observable { + return this.backend.http.yearsGetAll().pipe( + map(response => response.years) + ); + } + + public getYearById(yearId: number): Observable { + return this.backend.http.yearsGetSingle(yearId).pipe( + map(response => response ? response.year : undefined) + ); + } + + public createYear(year: YearCreationRequest): Observable { + return this.backend.http.yearsCreateNew(year).pipe( + map(response => response.year) + ); + } + + public updateYear(year: YearUpdateRequest, yearId: number): Observable { + return this.backend.http.yearsUpdateSingle(year, yearId).pipe( + map(response => response.year) + ); + } + + public deleteYear(yearId: number): Observable { + return this.backend.http.yearsDeleteSingle(yearId).pipe( + map(() => undefined) + ); + } + + public getAllOrganisators(): Observable { + return this.backend.http.usersGetAll('organisators').pipe( + map(response => response.users) + ); + } + +} diff --git a/src/app/services/shared/icon.service.ts b/src/app/services/shared/icon.service.ts index 23c9c3f6..2e965d14 100644 --- a/src/app/services/shared/icon.service.ts +++ b/src/app/services/shared/icon.service.ts @@ -17,4 +17,7 @@ export class IconService { public readonly CROSS = '☓'; public readonly WARNING = '⚠'; public readonly DISCORD = '💬'; + public readonly DELETE = '🗑'; + public readonly ADD = '+'; + } diff --git a/src/assets/i18n/cs.json b/src/assets/i18n/cs.json index f283efa6..6333b0d6 100644 --- a/src/assets/i18n/cs.json +++ b/src/assets/i18n/cs.json @@ -53,14 +53,14 @@ "text": "KSI, neboli Korespondenční Seminář z Informatiky, je celoroční soutěž pro středoškoláky z České i Slovenské republiky organizovaná studenty Fakulty informatiky Masarykovy univerzity. Cílem semináře je seznámit řešitele se zajímavými oblastmi informatiky a procvičit programátorské, matematické a logické myšlení. Seminář je uzpůsoben jak pro úplné začátečníky, kteří si na jednoduchých příkladech procvičí danou problematiku, tak pro zkušenější řešitele, kteří se pokusí pokořit hlavní soutěžní úlohy." }, "kscuk": { - "title": "Zúčastni se soustředění a KSÍletů", + "title": "Zúčastni se soustředění a KSÍletů", "text": "Při řešení našeho semináře nemusíš jen sedět doma za počítačem. Během roku pro všechny řešitele pravidelně pořádáme výlety (Ksílety), na kterých se můžeš seznámit s ostatními řešiteli, osobně poznat organizátory a zahrát si kupu zajímavých her. V září se mohou úspěšní řešitelé těšit na týdenní soustředění K-SCUK, které je společné pro KSI a biologický seminář IBIS. Náplní soustředění je jak odborný program, díky kterému se opět naučíš něco nového z informatiky, tak bohatý doprovodný program." }, "achievements": { "title": "Získej všechny trofeje", "text": "Za každou vyřešenou úlohu dostáváš body. Pokud budeš mít na konci roku alespoň 60 % z celkového možného počtu bodů, staneš se úspěšným řešitelem. V průběhu řešení semináře můžeš navíc za překonávání milníků, odevzdávání zajímavých řešení nebo třeba za účast na KSI akcích obdržet nejrůznější odznáčky. Podaří se ti jich získat co nejvíc? Ulovíš i ty nejvzácnější? Soutěž s ostatními řešiteli a posbírej je všechny! " }, - "begin": "Začni řešit", + "begin": "Začni řešit", "lets-start": "Jdu na to", "more-info": "Zjisti více" }, @@ -250,7 +250,7 @@ "much": "Moc" } }, - "graph": { + "graph": { "task": "Úloha {{ name }}" }, "settings": { @@ -466,7 +466,104 @@ "email": "E-mail", "discussion": "Diskuze", "achievement": "Trofeje", - "instance-config": "Server Admin" + "instance-config": "Server Admin", + "waves": "Vlny", + "years": "Ročníky", + "articles": "Články" + }, + "articles": { + "title": "Správa článků", + "new": "Přidat nový článek", + "edit": { + "title": "Upravit článek", + "save": "Uložit změny" + }, + "head": { + "title": "Titulek", + "published_date": "Termín publikace", + "released": "Zveřejněn", + "actions": "Akce" + }, + "delete": { + "btn": "Smazat článek", + "confirmation": "Opravdu chcete smazat tento článek?" + }, + "warning": { + "title": "Upozornění", + "message": "Maximální počet bodů nezahrnuje bonusové body." + } + }, + "waves": { + "title": "Správa vln", + "new": "Přidat novou vlnu", + "edit": { + "title": "Upravit vlnu", + "save": "Uložit změny" + }, + "head": { + "wave": "Vlna", + "name": "Název", + "guarantor": "Garant", + "release": "Zveřejnění", + "max-points": "Max. bodů", + "task-count": "#úloh", + "actions": "Akce" + }, + "warning": { + "title": "Upozornění", + "message": "Maximum bodů nezahrnuje bonusové body." + }, + "delete": { + "btn": "Smazat vlnu", + "confirmation": "Opravdu chceš smazat tuto vlnu?" + }, + "edit_page": { + "new": "Nová vlna", + "edit": "Úprava vlny" + }, + "back": "Zpět na přehled", + "caption": "Název", + "caption.required": "Název je povinný.", + "garant": "Garant", + "garant.required": "Garant je povinný.", + "time_published": "Datum vydání", + "time_published.required": "Datum vydání je povinné.", + "index": "Index", + "index.required": "Index je povinný.", + "create": "Vytvořit", + "save": "Uložit" + }, + "years": { + "title": "Správa ročníků", + "newYear": "Nový ročník", + "index": "Index", + "year": "Ročník", + "maxPoints": "Max. bodů", + "pointPad": "Bodová vycpávka", + "taskCount": "#úloh", + "sealed": "Zapečetěn", + "actions": "Akce", + "edit": "Upravit", + "delete": "Smazat", + "cannotDeleteSealed": "Nelze smazat zapečetěný ročník", + "warningTitle": "Upozornění", + "warningMessage": "Maximum bodů nezahrnuje bonusové body.", + "editPage": { + "newYear": "Vytvoření nového ročníku", + "edit": "Úprava ročníku", + "backToOverview": "Zpět na přehled", + "index": "Index", + "indexRequired": "Index je povinný.", + "year": "Rok", + "yearRequired": "Rok je povinný.", + "sealed": "Zapečetěný", + "pointPad": "Bodová vycpávka", + "pointPadInvalid": "Bodová vycpávka musí být číslo.", + "activeOrgs": "Aktivní organizátoři", + "activeOrgsRequired": "Aktivní organizátoři jsou povinní.", + "create": "Vytvořit", + "save": "Uložit" + } }, "root": { "title": "Organizátorské rozhraní", @@ -624,4 +721,4 @@ "privacy-policy": { "title": "Zásady zpracování osobních údajů" } -} +} \ No newline at end of file diff --git a/src/routes/routes.cs.ts b/src/routes/routes.cs.ts index 2790a48c..a6b8c499 100644 --- a/src/routes/routes.cs.ts +++ b/src/routes/routes.cs.ts @@ -28,7 +28,19 @@ export const ROUTES: IRoutes = { email: 'email', discussion: 'forum', achievements: 'trofeje', - instanceConfig: 'nastaveni-serveru' + instanceConfig: 'nastaveni-serveru', + waves: { + _: 'waves', + edit: 'upravit' + }, + years: { + _: 'years', + edit: 'edit' + }, + articles: { + _: 'articles', + edit: 'upravit' + } }, privacyPolicy: 'zpracovani-osobnich-udaju' }; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index ee99c5e8..6ec44733 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -27,7 +27,15 @@ export const ROUTES: IRoutes = { email: 'email', discussion: 'discussion', achievements: 'achievements', - instanceConfig: 'instanceConfig' + instanceConfig: 'instanceConfig', + waves: { + _: 'waves', + edit: 'edit' + }, + years: { + _: 'years', + edit: 'edit' + } }, privacyPolicy: 'privacy' };