feat: Made additions to grades. Closes #4
This commit is contained in:
811
package-lock.json
generated
811
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -228,6 +228,17 @@ export class AdminCommService {
|
||||
getSummary: (start: moment.Moment, end: moment.Moment) => {
|
||||
return this.http.get<{room: number, avg: number}[]>(environment.apiEndpoint+`/admin/clean/summary/${start.toISOString()}/${end.toISOString()}`, {withCredentials: true})
|
||||
}
|
||||
},
|
||||
attendence: {
|
||||
getUsers: (room: string) => {
|
||||
return this.http.get<{users: {fname: string, surname: string, _id: string}[], attendence?: {id: string, hour?: string}[]}>(environment.apiEndpoint+`/admin/clean/attendence/${room}`, {withCredentials: true})
|
||||
},
|
||||
postAttendence: (room: string, attendence: {id: string, hour?: string}[]) => {
|
||||
return this.http.post<Status>(environment.apiEndpoint+`/admin/clean/attendence/${room}`, attendence, {withCredentials: true})
|
||||
},
|
||||
getSummary: () => {
|
||||
return this.http.get<{room: string, hours: string[]}[]>(environment.apiEndpoint+`/admin/clean/attendenceSummary`, {withCredentials: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<div id="guide">
|
||||
<p><b>Uwaga:</b> Obecność resetuje się o codziennie o 00:00</p>
|
||||
<div id="legend">
|
||||
<b>Legenda: </b>
|
||||
<span class="circle">Wychowanek obecny</span>
|
||||
<span class="circle">Wyjście w ciągu 30 min.</span>
|
||||
<span class="circle">Wychowanek nieobecny</span>
|
||||
</div>
|
||||
</div>
|
||||
<table mat-table [dataSource]="data" matSort>
|
||||
<div matColumnDef="room">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Pokój</th>
|
||||
<td mat-cell *matCellDef="let item">{{item.room}}</td>
|
||||
</div>
|
||||
<div matColumnDef="hours">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Godziny</th>
|
||||
<td mat-cell *matCellDef="let item"><span *ngFor="let i of item.hours.sort().reverse(); let isLast=last"><app-hour-display [value]="i"></app-hour-display>{{ isLast ? '' : ', '}}</span></td>
|
||||
</div>
|
||||
<tr mat-header-row *matHeaderRowDef="collumns"></tr>
|
||||
<tr mat-row *matRowDef="let rowData; columns: collumns"></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,34 @@
|
||||
@use 'sass:list';
|
||||
|
||||
#guide {
|
||||
margin: 1em
|
||||
}
|
||||
|
||||
#legend {
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
* {
|
||||
margin: 2px;
|
||||
}
|
||||
gap: 3ch;
|
||||
}
|
||||
|
||||
.circle {
|
||||
&::before {
|
||||
border-radius: 7.5%;
|
||||
width: 2.5ch;
|
||||
height: 2.5ch;
|
||||
display: inline-block;
|
||||
content: "";
|
||||
vertical-align: middle;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
$list: (red, yellow, green);
|
||||
|
||||
@for $n from 1 through 3 {
|
||||
&:nth-of-type(#{$n})::before {
|
||||
background-color: nth($list, $n);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AttendenceSummaryComponent } from './attendence-summary.component';
|
||||
|
||||
describe('AttendenceSummaryComponent', () => {
|
||||
let component: AttendenceSummaryComponent;
|
||||
let fixture: ComponentFixture<AttendenceSummaryComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [AttendenceSummaryComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AttendenceSummaryComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ToolbarService } from '../../toolbar.service';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { AdminCommService } from '../../admin-comm.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-attendence-summary',
|
||||
templateUrl: './attendence-summary.component.html',
|
||||
styleUrl: './attendence-summary.component.scss'
|
||||
})
|
||||
export class AttendenceSummaryComponent implements OnInit {
|
||||
|
||||
data: MatTableDataSource<{room: string, hours: string[]}> = new MatTableDataSource<{room: string, hours: string[]}>();
|
||||
collumns = ['room', 'hours']
|
||||
|
||||
constructor (private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute, private ac: AdminCommService) {
|
||||
this.toolbar.comp = this
|
||||
this.toolbar.menu = [
|
||||
{check: true, title: "Ocenianie", fn: "goBack", icon: "arrow_back"}
|
||||
]
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.ac.clean.attendence.getSummary().subscribe(v => {
|
||||
this.data.data = v
|
||||
})
|
||||
}
|
||||
|
||||
goBack() {
|
||||
this.router.navigate(['../'], {relativeTo: this.route})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<span [ngStyle]="style()">{{value}}</span>
|
||||
@@ -0,0 +1,8 @@
|
||||
:host {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
span {
|
||||
padding: 2px;
|
||||
border-radius: 7.5%;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HourDisplayComponent } from './hour-display.component';
|
||||
|
||||
describe('HourDisplayComponent', () => {
|
||||
let component: HourDisplayComponent;
|
||||
let fixture: ComponentFixture<HourDisplayComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [HourDisplayComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HourDisplayComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import * as moment from 'moment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hour-display',
|
||||
templateUrl: './hour-display.component.html',
|
||||
styleUrl: './hour-display.component.scss'
|
||||
})
|
||||
export class HourDisplayComponent {
|
||||
@Input() value = "";
|
||||
|
||||
style () {
|
||||
if (/(0+[0-9]|1[0-9]|2[0-3]):(0+[0-9]|[1-5][0-9])/g.test(this.value)) {
|
||||
var diff = moment(this.value, 'HH:mm').diff(moment(), 'minutes');
|
||||
if (diff > 30) {
|
||||
return { "background-color": "red" }
|
||||
} else if (diff > 0) {
|
||||
return { "background-color": "yellow", "color": "black"}
|
||||
} else {
|
||||
return { "background-color": "green"}
|
||||
}
|
||||
} else {
|
||||
return { "color": "gray"}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<span mat-dialog-title>Obecność w {{room}}</span>
|
||||
<mat-dialog-content>
|
||||
<form [formGroup]="this.form">
|
||||
<div formArrayName="users">
|
||||
@for (item of users.controls; track i; let i = $index) {
|
||||
<div [formGroupName]="i">
|
||||
<mat-checkbox formControlName="att" #cb>
|
||||
<span control="label"></span>:
|
||||
<input type="time" formControlName="hour" (input)="cb.writeValue(true)">
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="save()">Zapisz</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AttendenceComponent } from './attendence.component';
|
||||
|
||||
describe('AttendenceComponent', () => {
|
||||
let component: AttendenceComponent;
|
||||
let fixture: ComponentFixture<AttendenceComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [AttendenceComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AttendenceComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
46
src/app/admin-view/grades/attendence/attendence.component.ts
Normal file
46
src/app/admin-view/grades/attendence/attendence.component.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AdminCommService } from '../../admin-comm.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-attendence',
|
||||
templateUrl: './attendence.component.html',
|
||||
styleUrl: './attendence.component.scss'
|
||||
})
|
||||
export class AttendenceComponent implements OnInit {
|
||||
|
||||
constructor (private fb: FormBuilder, @Inject(MAT_DIALOG_DATA) public data: {room: string}, public dialogRef: MatDialogRef<AttendenceComponent>, private ac: AdminCommService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.room = this.data.room
|
||||
this.ac.clean.attendence.getUsers(this.room).subscribe(query => {
|
||||
query.users.forEach(v => {
|
||||
var att = query.attendence ? query.attendence.find(z => z.id == v._id) : false
|
||||
this.users.push(this.fb.group({
|
||||
id: v._id,
|
||||
label: `${v.fname} ${v.surname}`,
|
||||
att: this.fb.control(att),
|
||||
hour: this.fb.control(att ? att.hour : ""),
|
||||
}))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
save() {
|
||||
this.dialogRef.close({
|
||||
room: this.room,
|
||||
...this.form.value
|
||||
})
|
||||
}
|
||||
|
||||
room: string = "";
|
||||
|
||||
form: FormGroup = this.fb.group({
|
||||
users: this.fb.array([])
|
||||
})
|
||||
|
||||
get users() {
|
||||
return this.form.get('users') as FormArray
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,12 @@
|
||||
<form [formGroup]="form">
|
||||
<p>Czystość pokoju {{room}} na dzień {{date.format("dddd")}}</p>
|
||||
<p>Ocena: {{grade}}</p>
|
||||
<button mat-flat-button (click)="downloadData()">Anuluj</button>
|
||||
<!-- <button mat-flat-button (click)="calculate()">Oblicz</button> -->
|
||||
<button mat-flat-button (click)="save()">Zapisz</button>
|
||||
<button mat-raised-button color="warn" (click)="remove()" *ngIf="id">Usuń</button>
|
||||
<div id="buttons">
|
||||
<button mat-mini-fab (click)="downloadData()" color="accent"><mat-icon>cancel</mat-icon></button>
|
||||
<button mat-mini-fab (click)="attendence()" color="accent"><mat-icon>overview</mat-icon></button>
|
||||
<button mat-mini-fab (click)="save()" color="accent"><mat-icon>save</mat-icon></button>
|
||||
<button mat-mini-fab color="warn" (click)="remove()" *ngIf="id"><mat-icon>delete</mat-icon></button>
|
||||
</div>
|
||||
<div *ngFor="let item of things.controls; let i = index" formArrayName="things" id="things">
|
||||
<div formGroupName="{{i}}">
|
||||
<mat-checkbox formControlName="cb" #cb>
|
||||
|
||||
@@ -2,3 +2,9 @@ div#things {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
div#buttons {
|
||||
* {
|
||||
margin: 0 2px 0 2px;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import { weekendFilter } from 'src/app/fd.da';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ToolbarService } from '../toolbar.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { AttendenceComponent } from './attendence/attendence.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-grades',
|
||||
@@ -42,12 +44,13 @@ export class GradesComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
}
|
||||
|
||||
constructor(private ac: AdminCommService, private fb: FormBuilder, private sb: MatSnackBar, private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute) {
|
||||
constructor(private ac: AdminCommService, private fb: FormBuilder, private sb: MatSnackBar, private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute, private dialog: MatDialog) {
|
||||
this.date = moment.utc().startOf('day')
|
||||
if (!this.filter(this.date)) this.date.isoWeekday(8);
|
||||
this.toolbar.comp = this
|
||||
this.toolbar.menu = [
|
||||
{ title: "Podsumowanie", check: true, fn: "summary", icon: "analytics" }
|
||||
{ title: "Podsumowanie", check: true, fn: "summary", icon: "analytics" },
|
||||
{ title: "Obecność", check: true, fn: "attendenceSummary", icon: "overview"}
|
||||
]
|
||||
this.form.valueChanges.subscribe((v) => {
|
||||
this.calculate()
|
||||
@@ -67,6 +70,10 @@ export class GradesComponent implements OnInit, OnDestroy {
|
||||
this.router.navigate(["summary"], { relativeTo: this.route })
|
||||
}
|
||||
|
||||
attendenceSummary() {
|
||||
this.router.navigate(["attendenceSummary"], {relativeTo: this.route})
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.ac.clean.getConfig().subscribe((s) => {
|
||||
this.rooms = s.rooms
|
||||
@@ -139,6 +146,7 @@ export class GradesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.ac.clean.postClean(obj).subscribe((s) => {
|
||||
this.sb.open("Zapisano!", undefined, { duration: 1500 })
|
||||
this.downloadData()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -154,4 +162,23 @@ export class GradesComponent implements OnInit, OnDestroy {
|
||||
this.room = value
|
||||
this.downloadData()
|
||||
}
|
||||
|
||||
attendence() {
|
||||
this.dialog.open(AttendenceComponent, {data: {room: this.room}}).afterClosed().subscribe((v: {room: string, users: {att: boolean, id: string, hour: string}[]}) => {
|
||||
let x: {room: string, users: {id: string, hour?: string}[]} = {
|
||||
room: v.room,
|
||||
users: []
|
||||
}
|
||||
v.users.forEach(i => {
|
||||
if (i.att && i.hour) {
|
||||
x.users.push({id: i.id, hour: i.hour})
|
||||
}
|
||||
})
|
||||
this.ac.clean.attendence.postAttendence(x.room, x.users).subscribe((s) => {
|
||||
if (s.status == 200) {
|
||||
this.sb.open("Zapisano obecność!", undefined, {duration: 1500})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { ToolbarService } from '../../toolbar.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AdminCommService } from '../../admin-comm.service';
|
||||
import * as moment from 'moment';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
|
||||
@Component({
|
||||
selector: 'app-summary',
|
||||
@@ -21,6 +22,10 @@ export class SummaryComponent implements OnInit, OnDestroy {
|
||||
end: this.fb.control(moment.utc().endOf('day'))
|
||||
})
|
||||
|
||||
@ViewChild(MatSort, {static: false}) set content(sort: MatSort) {
|
||||
this.data.sort = sort
|
||||
}
|
||||
|
||||
constructor (private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute, private ac: AdminCommService, private fb: FormBuilder) {
|
||||
this.toolbar.comp = this
|
||||
this.toolbar.menu = [
|
||||
|
||||
@@ -7,6 +7,8 @@ import { AdminCommService } from '../admin-comm.service';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { NewKeyComponent } from './new-key/new-key.component';
|
||||
import { catchError, throwError } from 'rxjs';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-key',
|
||||
@@ -33,7 +35,7 @@ export class AdminKeyComponent implements AfterViewInit, OnInit {
|
||||
loading = true
|
||||
@ViewChild(MatPaginator) paginator!: MatPaginator
|
||||
|
||||
constructor (private ac: AdminCommService, private dialog: MatDialog) {
|
||||
constructor (private ac: AdminCommService, private dialog: MatDialog, private sb: MatSnackBar) {
|
||||
this.filters = []
|
||||
}
|
||||
|
||||
@@ -71,7 +73,12 @@ export class AdminKeyComponent implements AfterViewInit, OnInit {
|
||||
new() {
|
||||
this.dialog.open(NewKeyComponent).afterClosed().subscribe(v => {
|
||||
if (v) {
|
||||
this.ac.keys.postKey(v.room, v.user).subscribe((s) => {
|
||||
this.ac.keys.postKey(v.room, v.user).pipe(catchError((err,caught)=>{
|
||||
if (err.status == 404) {
|
||||
this.sb.open("Nie znaleziono użytkownika", undefined, {duration: 2500})
|
||||
}
|
||||
return throwError(() => new Error(err.message))
|
||||
})).subscribe((s) => {
|
||||
if (s.status == 201) {
|
||||
this.fetchData()
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ import { NewsEditComponent } from './admin-view/news-edit/news-edit.component';
|
||||
import { AccountMgmtComponent } from './admin-view/account-mgmt/account-mgmt.component';
|
||||
import { MenuNewComponent } from './admin-view/menu-new/menu-new.component';
|
||||
import { adminGuard } from './admin.guard';
|
||||
// import { NotificationsComponent } from './admin-view/notifications/notifications.component';
|
||||
import { GroupsComponent } from './admin-view/groups/groups.component';
|
||||
import { StartComponent } from './app-view/start/start.component';
|
||||
import { AdminKeyComponent } from './admin-view/key/key.component';
|
||||
import { GradesComponent } from './admin-view/grades/grades.component';
|
||||
import { SummaryComponent } from './admin-view/grades/summary/summary.component';
|
||||
import { SettingsComponent } from './admin-view/settings/settings.component';
|
||||
import { AttendenceSummaryComponent } from './admin-view/grades/attendence-summary/attendence-summary.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: "", redirectTo: "login", pathMatch: "full"},
|
||||
@@ -37,7 +37,8 @@ const routes: Routes = [
|
||||
{path: "keys", title: "Klucze", component: AdminKeyComponent},
|
||||
{path: "grades", children: [
|
||||
{path: "", pathMatch: "full", title: "Oceny", component: GradesComponent},
|
||||
{path: "summary", title: "Podsumowanie ocen", component: SummaryComponent}
|
||||
{path: "summary", title: "Podsumowanie ocen", component: SummaryComponent},
|
||||
{path: "attendenceSummary", title: "Obecność", component: AttendenceSummaryComponent}
|
||||
]},
|
||||
{path: "settings", title: "Ustawienia", component: SettingsComponent}
|
||||
]}
|
||||
|
||||
@@ -74,6 +74,9 @@ import { FieldEditorComponent } from './commonComponents/field-editor/field-edit
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
import { PortalModule } from '@angular/cdk/portal';
|
||||
import { MatAutocompleteModule } from "@angular/material/autocomplete";
|
||||
import { AttendenceComponent } from './admin-view/grades/attendence/attendence.component';
|
||||
import { AttendenceSummaryComponent } from './admin-view/grades/attendence-summary/attendence-summary.component';
|
||||
import { HourDisplayComponent } from './admin-view/grades/attendence-summary/hour-display/hour-display.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -113,6 +116,9 @@ import { MatAutocompleteModule } from "@angular/material/autocomplete";
|
||||
SettingsComponent,
|
||||
MenuAddComponent,
|
||||
FieldEditorComponent,
|
||||
AttendenceComponent,
|
||||
AttendenceSummaryComponent,
|
||||
HourDisplayComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
Reference in New Issue
Block a user