1 Commits

Author SHA1 Message Date
6ab3598d38 v1.0.1 (#16)
* fix: Moved user type to a separate file. fix #7, fix #8

* fix: added more filters

* fix: Added attendence clear button and notes. fix #9, fix #10

* fix: bumped version number

* fix: Changed wording. Resolves #12

* fix: Resolve #13

* fix: Safari no longer displays system ui over lower guide.

* fix: Resolved #14
2025-05-13 19:28:21 +02:00
16 changed files with 75 additions and 48 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "ipwa",
"version": "1.0.0",
"version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "ipwa",
"version": "1.0.0",
"version": "1.0.1",
"license": "GPL-3.0-or-later",
"scripts": {
"ng": "ng",

View File

@@ -10,6 +10,7 @@ import { catchError, throwError } from 'rxjs';
import { UserResetComponent } from './user-reset/user-reset.component';
import { LocalStorageService } from 'src/app/services/local-storage.service';
import { Group } from 'src/app/types/group';
import User from 'src/app/types/user';
@Component({
selector: 'app-account-mgmt',
@@ -20,21 +21,20 @@ import { Group } from 'src/app/types/group';
export class AccountMgmtComponent implements OnInit, AfterViewInit {
protected groups: Group[] = []
users: MatTableDataSource<any>
users: MatTableDataSource<Omit<User, "pass">>
loading = false
@ViewChild(MatPaginator) paginator!: MatPaginator
constructor(readonly ac:AdminCommService, private dialog: MatDialog, private sb: MatSnackBar, protected readonly ls: LocalStorageService) {
this.users = new MatTableDataSource<any>();
this.users = new MatTableDataSource<Omit<User, "pass">>();
this.users.filterPredicate = (data: Record<string, any>, filter: string): boolean => {
const dataStr = Object.keys(data).reduce((curr: string, key: string) => {
if (key == "_id") {
return ''
if (["_id", "admin", "groups", "__v", "locked"].find(v => v == key)) {
return curr + ''
}
return curr + data[key] + '⫂'
}, '').toLowerCase()
const filternew = filter.trim().toLowerCase()
return dataStr.indexOf(filternew) != -1
}
}

View File

@@ -11,6 +11,7 @@ import { News } from '../types/news';
import { AKey } from '../types/key';
import * as moment from 'moment';
import { IUSettings } from './settings/settings.component';
import User from '../types/user';
@Injectable({
providedIn: 'root'
@@ -121,17 +122,7 @@ export class AdminCommService {
accs = {
getAccs: () => {
return this.http.get<{
users: {
_id: string;
uname: string;
pass: string;
room?: string;
admin?: number;
locked?: boolean;
fname?: string;
surname?: string;
groups: string[];
}[],
users: Omit<User, "pass">[],
groups: Group[]
}>(environment.apiEndpoint+`/admin/accs`, {withCredentials: true})
},
@@ -236,13 +227,16 @@ export class AdminCommService {
},
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})
return this.http.get<{users: {fname: string, surname: string, _id: string}[], attendence?: {auto: {id: string, hour?: string}[], notes: string}}>(environment.apiEndpoint+`/admin/clean/attendence/${room}`, {withCredentials: true})
},
postAttendence: (room: string, attendence: {id: string, hour?: string}[]) => {
postAttendence: (room: string, attendence: {auto: {id: string, hour?: string}[], notes: 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})
return this.http.get<{room: string, hours: string[], notes: string}[]>(environment.apiEndpoint+`/admin/clean/attendenceSummary`, {withCredentials: true})
},
deleteRoom: (room: string) => {
return this.http.delete<Status>(environment.apiEndpoint+`/admin/clean/attendence/${room}`, {withCredentials: true})
}
}
}

View File

@@ -14,7 +14,11 @@
</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>
<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><span>{{item.notes}}</span></td>
</div>
<div matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Usuń</th>
<td mat-cell *matCellDef="let item"><button mat-mini-fab color="warn" (click)="delete(item.room)"><mat-icon>delete</mat-icon></button></td>
</div>
<tr mat-header-row *matHeaderRowDef="collumns"></tr>
<tr mat-row *matRowDef="let rowData; columns: collumns"></tr>

View File

@@ -11,8 +11,8 @@ import { AdminCommService } from '../../admin-comm.service';
})
export class AttendenceSummaryComponent implements OnInit {
data: MatTableDataSource<{room: string, hours: string[]}> = new MatTableDataSource<{room: string, hours: string[]}>();
collumns = ['room', 'hours']
data: MatTableDataSource<{room: string, hours: string[], notes: string}> = new MatTableDataSource<{room: string, hours: string[], notes: string}>();
collumns = ['room', 'hours', 'actions']
constructor (private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute, private ac: AdminCommService) {
this.toolbar.comp = this
@@ -21,6 +21,12 @@ export class AttendenceSummaryComponent implements OnInit {
]
}
delete(room: string) {
this.ac.clean.attendence.deleteRoom(room).subscribe(() => {
this.ngOnInit()
})
}
ngOnInit(): void {
this.ac.clean.attendence.getSummary().subscribe(v => {
this.data.data = v

View File

@@ -11,6 +11,10 @@
</div>
}
</div>
<mat-form-field>
<mat-label>Notatki</mat-label>
<input type="text" matInput formControlName="notes">
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions>

View File

@@ -10,13 +10,13 @@ import { AdminCommService } from '../../admin-comm.service';
})
export class AttendenceComponent implements OnInit {
constructor (private fb: FormBuilder, @Inject(MAT_DIALOG_DATA) public data: {room: string}, public dialogRef: MatDialogRef<AttendenceComponent>, private ac: AdminCommService) {}
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
var att = query.attendence ? query.attendence.auto.find(z => z.id == v._id) : false
this.users.push(this.fb.group({
id: v._id,
label: `${v.fname} ${v.surname}`,
@@ -24,6 +24,7 @@ export class AttendenceComponent implements OnInit {
hour: this.fb.control(att ? att.hour : ""),
}))
})
this.form.get('notes')?.setValue(query.attendence?.notes)
})
}
@@ -37,7 +38,8 @@ export class AttendenceComponent implements OnInit {
room: string = "";
form: FormGroup = this.fb.group({
users: this.fb.array([])
users: this.fb.array([]),
notes: this.fb.control(""),
})
get users() {

View File

@@ -164,7 +164,8 @@ export class GradesComponent implements OnInit, OnDestroy {
}
attendence() {
this.dialog.open(AttendenceComponent, {data: {room: this.room}}).afterClosed().subscribe((v: {room: string, users: {att: boolean, id: string, hour: string}[]}) => {
this.dialog.open(AttendenceComponent, {data: {room: this.room}}).afterClosed().subscribe((v: {room: string, users: {att: boolean, id: string, hour: string}[], notes: string}) => {
if (!v) return
let x: {room: string, users: {id: string, hour?: string}[]} = {
room: v.room,
users: []
@@ -174,7 +175,7 @@ export class GradesComponent implements OnInit, OnDestroy {
x.users.push({id: i.id, hour: i.hour})
}
})
this.ac.clean.attendence.postAttendence(x.room, x.users).subscribe((s) => {
this.ac.clean.attendence.postAttendence(x.room, {auto: x.users, notes: v.notes}).subscribe((s) => {
if (s.status == 200) {
this.sb.open("Zapisano obecność!", undefined, {duration: 1500})
}

View File

@@ -1,4 +1,4 @@
<h1 mat-dialog-title>Dodawanie</h1>
<h1 mat-dialog-title>Tworzenie wpisów do jadłospisu</h1>
<mat-dialog-content>
<mat-radio-group [(ngModel)]="type">
<mat-radio-button value="day">Dzień</mat-radio-button>
@@ -28,6 +28,6 @@
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-raised-button color="accent" (click)="submit()">Wyślij</button>
<button mat-raised-button color="accent" (click)="submit()">Utwórz pozycje</button>
<button mat-button mat-dialog-close>Anuluj</button>
</mat-dialog-actions>

View File

@@ -1,4 +1,4 @@
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { MAT_DATE_RANGE_SELECTION_STRATEGY } from '@angular/material/datepicker';
import { Moment } from 'moment';
@@ -54,10 +54,10 @@ export class MenuNewComponent {
if (data) {
switch (data.type) {
case "day":
this.ac.menu.new.single(data.value).subscribe()
this.ac.menu.new.single(data.value).subscribe(s => this.refreshIfGood(s))
break;
case "week":
this.ac.menu.new.range(data.value.start, data.value.count).subscribe()
this.ac.menu.new.range(data.value.start, data.value.count).subscribe(s => this.refreshIfGood(s))
break;
case "file":
this.requestData()
@@ -87,7 +87,7 @@ export class MenuNewComponent {
}
private refreshIfGood(s: Status) {
if (s.status == 200) {
if (s.status.toString().match(/2\d\d/)) {
this.requestData()
}
}
@@ -100,25 +100,20 @@ export class MenuNewComponent {
})
}
editDay(v: string | string[], element: Menu) {
v = v as string
element.day = moment(v, "DD.MM.YYYY", true).utc(true).startOf('day')
}
editSn(id: string) {
this.ac.menu.editSn(id, this.dataSource.data.find(v => v._id == id)?.sn).subscribe(this.refreshIfGood)
this.ac.menu.editSn(id, this.dataSource.data.find(v => v._id == id)?.sn).subscribe(s => this.refreshIfGood(s))
}
editOb(id: string) {
this.ac.menu.editOb(id, this.dataSource.data.find(v => v._id == id)?.ob).subscribe(this.refreshIfGood)
this.ac.menu.editOb(id, this.dataSource.data.find(v => v._id == id)?.ob).subscribe(s => this.refreshIfGood(s))
}
editKol(id: string) {
this.ac.menu.editKol(id, this.dataSource.data.find(v => v._id == id)?.kol).subscribe(this.refreshIfGood)
this.ac.menu.editKol(id, this.dataSource.data.find(v => v._id == id)?.kol).subscribe(s => this.refreshIfGood(s))
}
editTitle(id: string) {
this.ac.menu.editTitle(id, this.dataSource.data.find(v => v._id == id)?.dayTitle).subscribe(this.refreshIfGood)
this.ac.menu.editTitle(id, this.dataSource.data.find(v => v._id == id)?.dayTitle).subscribe(s => this.refreshIfGood(s))
}
getStat(day: moment.Moment, m: "ob" | "kol") {
@@ -126,6 +121,6 @@ export class MenuNewComponent {
}
remove(id: string) {
this.ac.menu.rm(id).subscribe(this.refreshIfGood)
this.ac.menu.rm(id).subscribe(s => this.refreshIfGood(s))
}
}

View File

@@ -16,6 +16,9 @@
:host {
width: 100%;
@supports (-webkit-touch-callout: none) {
height: 95vh;
}
height: 100vh;
display: flex;
flex-direction: column;

View File

@@ -13,7 +13,7 @@
<div cdkDropList class="vertical" (cdkDropListDropped)="drop($event)">
@for (item of workList; track $index) {
<span cdkDrag>
<input type="text" [(ngModel)]="workList[$index]" [attr.list]="dataList">
<input type="text" [(ngModel)]="workList[$index]" [attr.list]="dataList" (keyup.enter)="addPos($index)" #input>
<select *ngIf="dropdown" [(ngModel)]="workList[$index]">
<option *ngFor="let option of options" [value]="option.id" [selected]="option.id == item">{{option.text}}</option>
</select>

View File

@@ -1,5 +1,5 @@
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, OnChanges, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
@Component({
selector: 'app-list-editor[list], app-list-editor[converter]',
@@ -15,10 +15,15 @@ export class ListEditorComponent implements OnChanges {
@Input() dropdown?: boolean;
@Input() dataList?: string;
@Output() edit = new EventEmitter<string[]>();
@ViewChildren('input') inputList!: QueryList<ElementRef>
protected _list: string[] = [];
workList: string[] = [];
focused = false;
constructor (private cdRef: ChangeDetectorRef) {}
ngOnChanges(changes: SimpleChanges): void {
if (this.list) {
this._list = [...this.list]
@@ -54,6 +59,8 @@ export class ListEditorComponent implements OnChanges {
addPos(index: number) {
this.workList.splice(index+1, 0, '')
this.cdRef.detectChanges()
this.inputList.get(index+1)?.nativeElement.focus()
}
trackByIndex(index: number, _entry:any) {

11
src/app/types/user.ts Normal file
View File

@@ -0,0 +1,11 @@
export default interface User {
_id: string;
uname: string;
pass: string;
room?: string;
admin?: number;
locked?: boolean;
fname?: string;
surname?: string;
groups: string[];
}

View File

@@ -1,5 +1,5 @@
export const environment = {
apiEndpoint: `${window.location.origin}/api`,
version: "v1.0.0",
version: "v1.0.1",
production: true
};