Add locking

pull/2/head
Max Ehrlicher-Schmidt 4 years ago
parent 9ded4854af
commit 9e9a4d7588

@ -3,7 +3,7 @@ module.exports = {
excludes: ['src/generated/*'], // ignore the generated files excludes: ['src/generated/*'], // ignore the generated files
service: { service: {
name: "flotte_project", name: "flotte_project",
url: "http://localhost:4000/graphql" url: "http://173.212.197.169:4000/graphql"
} }
} }
}; };

@ -1,6 +1,6 @@
overwrite: true overwrite: true
watch: true watch: true
schema: "http://localhost:4000/graphql" schema: "http://173.212.197.169:4002/graphql"
documents: "src/app/graphqlOperations/**/*.graphql" documents: "src/app/graphqlOperations/**/*.graphql"
generates: generates:
src/generated/graphql.ts: src/generated/graphql.ts:

File diff suppressed because it is too large Load Diff

@ -21,6 +21,8 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import {MatCardModule} from '@angular/material/card'; import {MatCardModule} from '@angular/material/card';
import {MatGridListModule} from '@angular/material/grid-list'; import {MatGridListModule} from '@angular/material/grid-list';
import {MatTooltipModule} from '@angular/material/tooltip';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
@ -70,6 +72,7 @@ import { NavService }from './components/menu-list-item/nav.service';
MatCheckboxModule, MatCheckboxModule,
GraphQLModule, GraphQLModule,
DragDropModule, DragDropModule,
MatTooltipModule
], ],
providers: [NavService], providers: [NavService],
bootstrap: [AppComponent], bootstrap: [AppComponent],

@ -8,6 +8,7 @@ import {
} from '@apollo/client/core'; } from '@apollo/client/core';
import { HttpLink } from 'apollo-angular/http'; import { HttpLink } from 'apollo-angular/http';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { DefaultOptions } from '@apollo/client/core/ApolloClient';
const uri = environment.apiUrl + '/graphql'; // <-- add the URL of the GraphQL server here const uri = environment.apiUrl + '/graphql'; // <-- add the URL of the GraphQL server here
@ -24,10 +25,22 @@ const authMiddleware = new ApolloLink((operation, forward) => {
}); });
}); });
const defaultOptions: DefaultOptions = {
watchQuery: {
fetchPolicy: 'no-cache',
errorPolicy: 'ignore',
},
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all',
},
}
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> { export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
return { return {
link: concat(authMiddleware, httpLink.create({ uri })), link: concat(authMiddleware, httpLink.create({ uri })),
cache: new InMemoryCache({}), cache: new InMemoryCache({}),
defaultOptions: defaultOptions,
}; };
} }

@ -9,3 +9,15 @@ mutation UpdateCargoBike($bike: CargoBikeUpdateInput!) {
...CargoBikeFields ...CargoBikeFields
} }
} }
mutation LockCargoBike($id: ID!) {
lockCargoBike(id: $id) {
...CargoBikeFields
}
}
mutation UnlockCargoBike($id: ID!) {
unlockCargoBike(id: $id) {
...CargoBikeFields
}
}

@ -6,8 +6,6 @@ fragment CargoBikeFieldsMutable on CargoBike {
billing billing
hasFixedRate hasFixedRate
} }
lockedBy
lockedUntil
dimensionsAndLoad { dimensionsAndLoad {
bikeLength bikeLength
bikeWeight bikeWeight
@ -43,4 +41,8 @@ fragment CargoBikeFields on CargoBike {
date date
id id
} }
isLocked
isLockedByMe
lockedBy
lockedUntil
} }

@ -1,6 +1,18 @@
<div class="table-page-wrapper"> <div class="table-page-wrapper">
<div class="table-control"> <div class="table-control">
<button mat-raised-button color="primary">Alle ausgewählten Fahrräder bearbeiten</button> <button mat-raised-button color="primary" class="table-control-button" i18n>
Alle ausgewählten Fahrräder bearbeiten
</button>
<button
mat-raised-button
class="table-control-button"
matTooltip="Tabllendaten aktualisieren. Achtung! Alle ungespeicherten Änderungen gehen verloren."
(click)="reloadTable()"
[disabled]="reloadingTable"
i18n
>
<mat-icon class="spin">sync</mat-icon>
</button>
</div> </div>
<div class="table-container"> <div class="table-container">
<table <table
@ -40,7 +52,7 @@
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let element">
<app-cell <app-cell
[editable]="element.isGettingEdited" [editable]="element.isLockedByMe"
[(value)]="element.name" [(value)]="element.name"
></app-cell> ></app-cell>
</td> </td>
@ -54,10 +66,12 @@
<!-- FrameNumber Column --> <!-- FrameNumber Column -->
<ng-container matColumnDef="frameNumber"> <ng-container matColumnDef="frameNumber">
<th mat-header-cell cdkDrag *matHeaderCellDef mat-sort-header>Fahrgestellnummer</th> <th mat-header-cell cdkDrag *matHeaderCellDef mat-sort-header>
Fahrgestellnummer
</th>
<td mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let element">
<app-cell <app-cell
[editable]="element.isGettingEdited" [editable]="element.isLockedByMe"
[(value)]="element.security.frameNumber" [(value)]="element.security.frameNumber"
></app-cell> ></app-cell>
</td> </td>
@ -65,10 +79,12 @@
<!-- NumberOfChildren Column --> <!-- NumberOfChildren Column -->
<ng-container matColumnDef="numberOfChildren"> <ng-container matColumnDef="numberOfChildren">
<th mat-header-cell cdkDrag *matHeaderCellDef mat-sort-header>Anzahl Kinder</th> <th mat-header-cell cdkDrag *matHeaderCellDef mat-sort-header>
Anzahl Kinder
</th>
<td mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let element">
<app-cell <app-cell
[editable]="element.isGettingEdited" [editable]="element.isLockedByMe"
[(value)]="element.numberOfChildren" [(value)]="element.numberOfChildren"
inputType="number" inputType="number"
></app-cell> ></app-cell>
@ -77,10 +93,12 @@
<!-- NumberOfWheels Column --> <!-- NumberOfWheels Column -->
<ng-container matColumnDef="numberOfWheels"> <ng-container matColumnDef="numberOfWheels">
<th mat-header-cell cdkDrag *matHeaderCellDef i18n="WheelCount">Räderanzahl</th> <th mat-header-cell cdkDrag *matHeaderCellDef i18n="WheelCount">
Räderanzahl
</th>
<td mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let element">
<app-cell <app-cell
[editable]="element.isGettingEdited" [editable]="element.isLockedByMe"
[(value)]="element.numberOfWheels" [(value)]="element.numberOfWheels"
inputType="number" inputType="number"
></app-cell> ></app-cell>
@ -96,10 +114,11 @@
mat-icon-button mat-icon-button
(click)="edit(element)" (click)="edit(element)"
*ngIf=" *ngIf="
!element.isGettingEdited && !element.isLockedByMe &&
!element.waitingForEditPermissions && !element.waitingForEditPermissions &&
!element.locked && !element.isLocked &&
!element.saving !element.saving &&
!element.unlocking
" "
> >
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>
@ -107,10 +126,11 @@
<button <button
mat-icon-button mat-icon-button
*ngIf=" *ngIf="
!element.isGettingEdited && !element.isLockedByMe &&
!element.waitingForEditPermissions && !element.waitingForEditPermissions &&
!element.locked && !element.isLocked &&
!element.saving !element.saving &&
!element.unlocking
" "
> >
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
@ -118,25 +138,38 @@
<mat-spinner <mat-spinner
[diameter]="32" [diameter]="32"
*ngIf="element.waitingForEditPermissions || element.saving" *ngIf="
element.waitingForEditPermissions ||
element.saving ||
element.unlocking
"
></mat-spinner> ></mat-spinner>
<button <button
mat-icon-button mat-icon-button
*ngIf="element.isGettingEdited" *ngIf="
element.isLockedByMe && !element.unlocking && !element.saving
"
(click)="save(element)" (click)="save(element)"
> >
<mat-icon>save</mat-icon> <mat-icon>save</mat-icon>
</button> </button>
<button <button
mat-icon-button mat-icon-button
*ngIf="element.isGettingEdited" matTooltip="Alle ungespeicherten Änderungen verwerfen."
*ngIf="
element.isLockedByMe && !element.unlocking && !element.saving
"
(click)="cancel(element)" (click)="cancel(element)"
> >
<mat-icon>cancel</mat-icon> <mat-icon>cancel</mat-icon>
</button> </button>
<mat-icon *ngIf="element.locked">locked</mat-icon> <mat-icon
*ngIf="element.isLocked"
matTooltip="Dieser Eintrag wird gerade von einem anderen Bearbeiter editiert. Aktualisieren Sie die Tabelle, um den neuen Status abzurufen."
>locked</mat-icon
>
</div> </div>
</td> </td>
</ng-container> </ng-container>

@ -5,6 +5,9 @@
.table-control { .table-control {
margin: 0.5em; margin: 0.5em;
flex: none; flex: none;
.table-control-button {
margin: 0.25em;
}
} }
.table-container { .table-container {
flex: 1; flex: 1;

@ -4,13 +4,15 @@ import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { BikesService, CargoBikeResult } from 'src/app/services/bikes.service'; import { BikesService, CargoBikeResult } from 'src/app/services/bikes.service';
import { deepCopy } from 'src/app/helperFunctions/deepCopy'; import { deepCopy } from 'src/app/helperFunctions/deepCopy';
import { filter } from 'graphql-anywhere'; import { filter } from 'graphql-anywhere';
import {CargoBikeFieldsMutableFragmentDoc, CargoBikeUpdateInput} from 'src/generated/graphql'; import {
CargoBikeFieldsMutableFragmentDoc,
CargoBikeUpdateInput,
} from 'src/generated/graphql';
type CargoBikeDataRow = CargoBikeResult & { type CargoBikeDataRow = CargoBikeResult & {
waitingForEditPermissions: boolean; waitingForEditPermissions: boolean;
saving: boolean; saving: boolean;
isGettingEdited: boolean; unlocking: boolean;
locked: boolean;
}; };
@Component({ @Component({
@ -32,44 +34,62 @@ export class BikesComponent {
bikes = <Array<any>>[]; bikes = <Array<any>>[];
selection = new SelectionModel<CargoBikeDataRow>(true, []); selection = new SelectionModel<CargoBikeDataRow>(true, []);
reloadingTable = false;
relockingInterval = null;
relockingDuration = 1000 * 60 * 1;
constructor(private bikesService: BikesService) { constructor(private bikesService: BikesService) {
bikesService.bikes.subscribe((bikes) => { bikesService.bikes.subscribe((bikes) => {
this.reloadingTable = false;
this.bikes = bikes.map((bike) => { this.bikes = bikes.map((bike) => {
return <any>Object.assign({}, deepCopy(bike), { return <any>Object.assign({}, deepCopy(bike), {
waitingForEditPermissions: false, waitingForEditPermissions: false,
isGettingEdited: false, saving: false,
locked: false, unlocking: false,
saving: false
}); });
}); });
if (this.bikes.length > 6) {
this.bikes[5].locked = true;
this.bikes[2].locked = true;
}
}); });
bikesService.loadBikes(); bikesService.loadBikes();
} }
ngOnInit() {
this.relockingInterval = setInterval(() => {
for (const bike of this.bikes) {
if (bike.isLockedByMe) {
this.bikesService.relockBike({ id: bike.id });
}
}
}, this.relockingDuration);
}
ngOnDestroy() {
clearInterval(this.relockingInterval);
}
reloadTable() {
console.log("reload")
this.reloadingTable = true;
this.bikesService.loadBikes();
}
edit(row: CargoBikeDataRow) { edit(row: CargoBikeDataRow) {
row.waitingForEditPermissions = true; row.waitingForEditPermissions = true;
setTimeout(() => { this.bikesService.lockBike({ id: row.id });
row.waitingForEditPermissions = false;
row.isGettingEdited = true;
}, Math.random()*1000);
} }
save(row: CargoBikeDataRow) { save(row: CargoBikeDataRow) {
//TODO: remove lock
row.saving = true; row.saving = true;
row.isGettingEdited = false; const bike: CargoBikeUpdateInput = filter(
const bike: CargoBikeUpdateInput = filter(CargoBikeFieldsMutableFragmentDoc, row) CargoBikeFieldsMutableFragmentDoc,
this.bikesService.updateBike({bike}) row
);
this.bikesService.updateBike({ bike });
} }
cancel(row: CargoBikeDataRow) { cancel(row: CargoBikeDataRow) {
//fetch it again row.unlocking = true;
//TODO: remove lock this.bikesService.unlockBike({ id: row.id });
this.bikesService.reloadBike({ id: row.id });
} }
drop(event: CdkDragDrop<string[]>) { drop(event: CdkDragDrop<string[]>) {

@ -1,17 +1,37 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { GetCargoBikesGQL, GetCargoBikesQuery, GetCargoBikeByIdGQL, GetCargoBikeByIdQueryVariables, UpdateCargoBikeGQL, UpdateCargoBikeMutationVariables, UpdateCargoBikeDocument, CargoBikeFieldsFragmentDoc} from 'src/generated/graphql'; import {
GetCargoBikesGQL,
GetCargoBikesQuery,
GetCargoBikeByIdGQL,
GetCargoBikeByIdQueryVariables,
UpdateCargoBikeGQL,
UpdateCargoBikeMutationVariables,
LockCargoBikeGQL,
LockCargoBikeMutationVariables,
UnlockCargoBikeGQL,
UnlockCargoBikeMutationVariables
} from 'src/generated/graphql';
import { DeepExtractTypeSkipArrays } from 'ts-deep-extract-types'; import { DeepExtractTypeSkipArrays } from 'ts-deep-extract-types';
export type CargoBikeResult = DeepExtractTypeSkipArrays<GetCargoBikesQuery, ["cargoBikes"]>; export type CargoBikeResult = DeepExtractTypeSkipArrays<
GetCargoBikesQuery,
['cargoBikes']
>;
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class BikesService { export class BikesService {
bikes: BehaviorSubject<CargoBikeResult[]> = new BehaviorSubject([]); bikes: BehaviorSubject<CargoBikeResult[]> = new BehaviorSubject([]);
constructor(private getCargoBikesGQL: GetCargoBikesGQL, private getCargoBikeByIdGQL: GetCargoBikeByIdGQL, private updateCargoBikeGQL: UpdateCargoBikeGQL) { } constructor(
private getCargoBikesGQL: GetCargoBikesGQL,
private getCargoBikeByIdGQL: GetCargoBikeByIdGQL,
private updateCargoBikeGQL: UpdateCargoBikeGQL,
private lockCargoBikeGQL: LockCargoBikeGQL,
private unlockCargoBikeGQL: UnlockCargoBikeGQL
) { }
loadBikes() { loadBikes() {
this.getCargoBikesGQL.fetch().subscribe((result) => { this.getCargoBikesGQL.fetch().subscribe((result) => {
@ -22,14 +42,48 @@ export class BikesService {
reloadBike(variables: GetCargoBikeByIdQueryVariables) { reloadBike(variables: GetCargoBikeByIdQueryVariables) {
this.getCargoBikeByIdGQL.fetch(variables).subscribe((result) => { this.getCargoBikeByIdGQL.fetch(variables).subscribe((result) => {
const newBike = result.data.cargoBikeById; const newBike = result.data.cargoBikeById;
this.bikes.next(this.bikes.value.map(bike => (newBike.id === bike.id) ? newBike : bike)); this.bikes.next(
this.bikes.value.map((bike) =>
newBike.id === bike.id ? newBike : bike
)
);
}); });
} }
updateBike(variableValues: UpdateCargoBikeMutationVariables) { updateBike(variableValues: UpdateCargoBikeMutationVariables) {
this.updateCargoBikeGQL.mutate(variableValues).subscribe((result) => { this.updateCargoBikeGQL.mutate(variableValues).subscribe((result) => {
const newBike = result.data.updateCargoBike; const newBike = result.data.updateCargoBike;
this.bikes.next(this.bikes.value.map(bike => (newBike.id === bike.id) ? newBike : bike)); this.bikes.next(
this.bikes.value.map((bike) =>
newBike.id === bike.id ? newBike : bike
)
);
});
}
lockBike(variables: LockCargoBikeMutationVariables) {
this.lockCargoBikeGQL.mutate(variables).subscribe((result) => {
const lockedBike = result.data.lockCargoBike;
this.bikes.next(
this.bikes.value.map((bike) =>
lockedBike.id === bike.id ? lockedBike : bike
)
);
})
}
unlockBike(variables: LockCargoBikeMutationVariables) {
this.unlockCargoBikeGQL.mutate(variables).subscribe((result) => {
const unlockedBike = result.data.unlockCargoBike;
this.bikes.next(
this.bikes.value.map((bike) =>
unlockedBike.id === bike.id ? unlockedBike : bike
)
);
}) })
} }
relockBike(variables: LockCargoBikeMutationVariables) {
this.lockCargoBikeGQL.mutate(variables).subscribe();
}
} }

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save