* 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
This commit is contained in:
2025-05-13 19:28:21 +02:00
committed by GitHub
parent 53bfeab116
commit 6ab3598d38
16 changed files with 75 additions and 48 deletions

2
package-lock.json generated
View File

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

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ import { News } from '../types/news';
import { AKey } from '../types/key'; import { AKey } from '../types/key';
import * as moment from 'moment'; import * as moment from 'moment';
import { IUSettings } from './settings/settings.component'; import { IUSettings } from './settings/settings.component';
import User from '../types/user';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -121,17 +122,7 @@ export class AdminCommService {
accs = { accs = {
getAccs: () => { getAccs: () => {
return this.http.get<{ return this.http.get<{
users: { users: Omit<User, "pass">[],
_id: string;
uname: string;
pass: string;
room?: string;
admin?: number;
locked?: boolean;
fname?: string;
surname?: string;
groups: string[];
}[],
groups: Group[] groups: Group[]
}>(environment.apiEndpoint+`/admin/accs`, {withCredentials: true}) }>(environment.apiEndpoint+`/admin/accs`, {withCredentials: true})
}, },
@@ -236,13 +227,16 @@ export class AdminCommService {
}, },
attendence: { attendence: {
getUsers: (room: string) => { 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}) return this.http.post<Status>(environment.apiEndpoint+`/admin/clean/attendence/${room}`, attendence, {withCredentials: true})
}, },
getSummary: () => { 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>
<div matColumnDef="hours"> <div matColumnDef="hours">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Godziny</th> <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> </div>
<tr mat-header-row *matHeaderRowDef="collumns"></tr> <tr mat-header-row *matHeaderRowDef="collumns"></tr>
<tr mat-row *matRowDef="let rowData; columns: 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 { export class AttendenceSummaryComponent implements OnInit {
data: MatTableDataSource<{room: string, hours: string[]}> = new MatTableDataSource<{room: string, hours: string[]}>(); data: MatTableDataSource<{room: string, hours: string[], notes: string}> = new MatTableDataSource<{room: string, hours: string[], notes: string}>();
collumns = ['room', 'hours'] collumns = ['room', 'hours', 'actions']
constructor (private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute, private ac: AdminCommService) { constructor (private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute, private ac: AdminCommService) {
this.toolbar.comp = this 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 { ngOnInit(): void {
this.ac.clean.attendence.getSummary().subscribe(v => { this.ac.clean.attendence.getSummary().subscribe(v => {
this.data.data = v this.data.data = v

View File

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

View File

@@ -10,13 +10,13 @@ import { AdminCommService } from '../../admin-comm.service';
}) })
export class AttendenceComponent implements OnInit { 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 { ngOnInit(): void {
this.room = this.data.room this.room = this.data.room
this.ac.clean.attendence.getUsers(this.room).subscribe(query => { this.ac.clean.attendence.getUsers(this.room).subscribe(query => {
query.users.forEach(v => { 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({ this.users.push(this.fb.group({
id: v._id, id: v._id,
label: `${v.fname} ${v.surname}`, label: `${v.fname} ${v.surname}`,
@@ -24,6 +24,7 @@ export class AttendenceComponent implements OnInit {
hour: this.fb.control(att ? att.hour : ""), 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 = ""; room: string = "";
form: FormGroup = this.fb.group({ form: FormGroup = this.fb.group({
users: this.fb.array([]) users: this.fb.array([]),
notes: this.fb.control(""),
}) })
get users() { get users() {

View File

@@ -164,7 +164,8 @@ export class GradesComponent implements OnInit, OnDestroy {
} }
attendence() { 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}[]} = { let x: {room: string, users: {id: string, hour?: string}[]} = {
room: v.room, room: v.room,
users: [] users: []
@@ -174,7 +175,7 @@ export class GradesComponent implements OnInit, OnDestroy {
x.users.push({id: i.id, hour: i.hour}) 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) { if (s.status == 200) {
this.sb.open("Zapisano obecność!", undefined, {duration: 1500}) 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-dialog-content>
<mat-radio-group [(ngModel)]="type"> <mat-radio-group [(ngModel)]="type">
<mat-radio-button value="day">Dzień</mat-radio-button> <mat-radio-button value="day">Dzień</mat-radio-button>
@@ -28,6 +28,6 @@
</div> </div>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions> <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> <button mat-button mat-dialog-close>Anuluj</button>
</mat-dialog-actions> </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 { FormControl, FormGroup } from '@angular/forms';
import { MAT_DATE_RANGE_SELECTION_STRATEGY } from '@angular/material/datepicker'; import { MAT_DATE_RANGE_SELECTION_STRATEGY } from '@angular/material/datepicker';
import { Moment } from 'moment'; import { Moment } from 'moment';
@@ -54,10 +54,10 @@ export class MenuNewComponent {
if (data) { if (data) {
switch (data.type) { switch (data.type) {
case "day": case "day":
this.ac.menu.new.single(data.value).subscribe() this.ac.menu.new.single(data.value).subscribe(s => this.refreshIfGood(s))
break; break;
case "week": 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; break;
case "file": case "file":
this.requestData() this.requestData()
@@ -87,7 +87,7 @@ export class MenuNewComponent {
} }
private refreshIfGood(s: Status) { private refreshIfGood(s: Status) {
if (s.status == 200) { if (s.status.toString().match(/2\d\d/)) {
this.requestData() 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) { 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) { 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) { 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) { 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") { getStat(day: moment.Moment, m: "ob" | "kol") {
@@ -126,6 +121,6 @@ export class MenuNewComponent {
} }
remove(id: string) { 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 { :host {
width: 100%; width: 100%;
@supports (-webkit-touch-callout: none) {
height: 95vh;
}
height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -13,7 +13,7 @@
<div cdkDropList class="vertical" (cdkDropListDropped)="drop($event)"> <div cdkDropList class="vertical" (cdkDropListDropped)="drop($event)">
@for (item of workList; track $index) { @for (item of workList; track $index) {
<span cdkDrag> <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]"> <select *ngIf="dropdown" [(ngModel)]="workList[$index]">
<option *ngFor="let option of options" [value]="option.id" [selected]="option.id == item">{{option.text}}</option> <option *ngFor="let option of options" [value]="option.id" [selected]="option.id == item">{{option.text}}</option>
</select> </select>

View File

@@ -1,5 +1,5 @@
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; 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({ @Component({
selector: 'app-list-editor[list], app-list-editor[converter]', selector: 'app-list-editor[list], app-list-editor[converter]',
@@ -15,10 +15,15 @@ export class ListEditorComponent implements OnChanges {
@Input() dropdown?: boolean; @Input() dropdown?: boolean;
@Input() dataList?: string; @Input() dataList?: string;
@Output() edit = new EventEmitter<string[]>(); @Output() edit = new EventEmitter<string[]>();
@ViewChildren('input') inputList!: QueryList<ElementRef>
protected _list: string[] = []; protected _list: string[] = [];
workList: string[] = []; workList: string[] = [];
focused = false; focused = false;
constructor (private cdRef: ChangeDetectorRef) {}
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (this.list) { if (this.list) {
this._list = [...this.list] this._list = [...this.list]
@@ -54,6 +59,8 @@ export class ListEditorComponent implements OnChanges {
addPos(index: number) { addPos(index: number) {
this.workList.splice(index+1, 0, '') this.workList.splice(index+1, 0, '')
this.cdRef.detectChanges()
this.inputList.get(index+1)?.nativeElement.focus()
} }
trackByIndex(index: number, _entry:any) { 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 = { export const environment = {
apiEndpoint: `${window.location.origin}/api`, apiEndpoint: `${window.location.origin}/api`,
version: "v1.0.0", version: "v1.0.1",
production: true production: true
}; };