Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extra time tab #1791

Merged
merged 4 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@let state = stateSignal();

<p i18n>The resulting additional time of all descendants of this group:</p>

@if (state.isError) {
<alg-error
class="alg-flex-1"
icon="ph-duotone ph-warning-circle"
i18n-message message="Unable to extra time information"
i18n-buttonCaption buttonCaption="Retry"
[showRefreshButton]="true"
(refresh)="refresh()"
></alg-error>
}
@else if (state.data === undefined) {
<alg-loading></alg-loading>
}
@else { <!-- state ready or fetching-->
@let data = state.data;
<p-table
class="alg-table"
[value]="data"
[loading]="state.isFetching"
responsiveLayout="scroll"
>
<ng-template pTemplate="header">
@if (data && data.length > 0) {
<tr>
<th i18n>Name</th>
<th i18n>Participant-specific</th>
<th i18n>Total</th>
</tr>
}
</ng-template>

<ng-template
pTemplate="body"
let-descendantExtraTime
>
<tr>
<td>{{ descendantExtraTime.name }}</td>
<td>{{ descendantExtraTime.additionalTime }}s</td>
<td>{{ descendantExtraTime.totalAdditionalTime }}s</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td>
<div class="empty-message" i18n>
This group has no descendants.
</div>
</td>
</tr>
</ng-template>
</p-table>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ChangeDetectionStrategy, Component, input, OnDestroy } from '@angular/core';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { TableModule } from 'primeng/table';
import { combineLatest, Subject, switchMap } from 'rxjs';
import { ExtraTimeService } from 'src/app/items/data-access/extra-time.service';
import { ErrorComponent } from 'src/app/ui-components/error/error.component';
import { LoadingComponent } from 'src/app/ui-components/loading/loading.component';
import { mapToFetchState } from 'src/app/utils/operators/state';

/**
* Display extra time given to group descendants on the given item.
* This component assumes the parent component has validated that the request is valid.
*/
@Component({
selector: 'alg-item-extra-time-for-descendants',
standalone: true,
imports: [
TableModule,
ErrorComponent,
LoadingComponent,
],
templateUrl: './item-extra-time-for-descendants.component.html',
styleUrl: './item-extra-time-for-descendants.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ItemExtraTimeForDescendantsComponent implements OnDestroy {
itemId = input.required<string>();
groupId = input.required<string>();

private refreshSubject = new Subject<void>();

private state$ = combineLatest([ toObservable(this.itemId), toObservable(this.groupId) ]).pipe(
switchMap(([ itemId, groupId ]) => this.extraTimeService.getForGroupDescendant(itemId, groupId)),
mapToFetchState({ resetter: this.refreshSubject }),
);
stateSignal = toSignal(this.state$, { requireSync: true });

constructor(
private extraTimeService: ExtraTimeService,
){}

ngOnDestroy(): void {
this.refreshSubject.complete();
}

refresh(): void {
this.refreshSubject.next(undefined);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@let item = itemData().item;
@let observedGroupInfo = observedGroupInfoSignal();

@if (!(item | isTimeLimitedActivity) ) {
<alg-error i18n-message message="This content is not time-limited."></alg-error>
}
@else if (!(item | canCurrentUserSetExtraTime)) {
<alg-error i18n-message message="You do not have the permissions to set extra time on this activity."></alg-error>
}
@else if (observedGroupInfo === null) {
<p i18n>This activity has a duration of {{ item.duration | readable }}. To list and add extra-time to some groups or users, start observation on them.</p>
}
@else if (observedGroupInfo === undefined || false) { <!-- loading-->
<alg-loading></alg-loading>
}
@else if (false) { <!-- error-->
<alg-error i18n-message message="Unable to load the extra time given to this group"></alg-error>
}
@else {
<p i18n>
The extra-time given to "{{ observedGroupInfo.name }}" (i.e., including those given to its ancestors) is TODO,
the resulting extra-time (so including its ancestor groups) is TODO.
</p>
<!-- to be implemented: way to change that number -->

@if (!(observedGroupInfo.route | isUser)) {
<alg-item-extra-time-for-descendants [itemId]="item.id" [groupId]="observedGroupInfo.route.id"></alg-item-extra-time-for-descendants>
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { ItemData } from '../../models/item-data';
import { CanCurrentUserSetExtraTimePipe, IsTimeLimitedActivityPipe } from '../../models/time-limited-activity';
import { DurationToReadablePipe } from 'src/app/pipes/duration';
import { ErrorComponent } from 'src/app/ui-components/error/error.component';
import { Store } from '@ngrx/store';
import { fromObservation } from 'src/app/store/observation';
import { GroupIsUserPipe } from 'src/app/pipes/groupIsUser';
import { LoadingComponent } from 'src/app/ui-components/loading/loading.component';
import { ItemExtraTimeForDescendantsComponent } from './item-extra-time-for-descendants/item-extra-time-for-descendants.component';

@Component({
selector: 'alg-item-extra-time',
standalone: true,
imports: [
IsTimeLimitedActivityPipe,
CanCurrentUserSetExtraTimePipe,
ItemExtraTimeForDescendantsComponent,
DurationToReadablePipe,
GroupIsUserPipe,
ErrorComponent,
LoadingComponent,
],
templateUrl: './item-extra-time.component.html',
styleUrl: './item-extra-time.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ItemExtraTimeComponent {
itemData = input.required<ItemData>();

observedGroupInfoSignal = this.store.selectSignal(fromObservation.selectObservedGroupInfo);

constructor(
private store: Store,
) {}
}
35 changes: 35 additions & 0 deletions src/app/items/data-access/extra-time.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { appConfig } from 'src/app/utils/config';
import { z } from 'zod';
import { decodeSnakeCaseZod } from 'src/app/utils/operators/decode';

const groupAdditionalTimesSchema = z.array(
z.object({
groupId: z.string(),
name: z.string(),
type: z.string(),
additionalTime: z.number(),
totalAdditionalTime: z.number(),
})
);

type GroupAdditionalTimes = z.infer<typeof groupAdditionalTimesSchema>;

@Injectable({
providedIn: 'root'
})
export class ExtraTimeService {

constructor(private http: HttpClient) {}

getForGroupDescendant(itemId: string, groupId: string): Observable<GroupAdditionalTimes> {
return this.http
.get<unknown>(`${appConfig.apiUrl}/contests/${itemId}/groups/${groupId}/members/additional-times`)
.pipe(
decodeSnakeCaseZod(groupAdditionalTimesSchema),
);
}

}
Loading