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}}
-
+
\ 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
+
+
+

+
+
+
\ 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
+ }}
+
+
+
+
\ 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 }}
+
+
+
+
+
\ 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'
};