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
service: {
name: "flotte_project",
url: "http://localhost:4000/graphql"
url: "http://173.212.197.169:4000/graphql"
}
}
};

@ -1,6 +1,6 @@
overwrite: true
watch: true
schema: "http://localhost:4000/graphql"
schema: "http://173.212.197.169:4002/graphql"
documents: "src/app/graphqlOperations/**/*.graphql"
generates:
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 {MatCardModule} from '@angular/material/card';
import {MatGridListModule} from '@angular/material/grid-list';
import {MatTooltipModule} from '@angular/material/tooltip';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@ -70,6 +72,7 @@ import { NavService }from './components/menu-list-item/nav.service';
MatCheckboxModule,
GraphQLModule,
DragDropModule,
MatTooltipModule
],
providers: [NavService],
bootstrap: [AppComponent],

@ -8,6 +8,7 @@ import {
} from '@apollo/client/core';
import { HttpLink } from 'apollo-angular/http';
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
@ -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> {
return {
link: concat(authMiddleware, httpLink.create({ uri })),
cache: new InMemoryCache({}),
defaultOptions: defaultOptions,
};
}

@ -9,3 +9,15 @@ mutation UpdateCargoBike($bike: CargoBikeUpdateInput!) {
...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
hasFixedRate
}
lockedBy
lockedUntil
dimensionsAndLoad {
bikeLength
bikeWeight
@ -43,4 +41,8 @@ fragment CargoBikeFields on CargoBike {
date
id
}
isLocked
isLockedByMe
lockedBy
lockedUntil
}

@ -1,6 +1,18 @@
<div class="table-page-wrapper">
<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 class="table-container">
<table
@ -40,7 +52,7 @@
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let element">
<app-cell
[editable]="element.isGettingEdited"
[editable]="element.isLockedByMe"
[(value)]="element.name"
></app-cell>
</td>
@ -54,10 +66,12 @@
<!-- FrameNumber Column -->
<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">
<app-cell
[editable]="element.isGettingEdited"
[editable]="element.isLockedByMe"
[(value)]="element.security.frameNumber"
></app-cell>
</td>
@ -65,10 +79,12 @@
<!-- NumberOfChildren Column -->
<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">
<app-cell
[editable]="element.isGettingEdited"
[editable]="element.isLockedByMe"
[(value)]="element.numberOfChildren"
inputType="number"
></app-cell>
@ -77,10 +93,12 @@
<!-- NumberOfWheels Column -->
<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">
<app-cell
[editable]="element.isGettingEdited"
[editable]="element.isLockedByMe"
[(value)]="element.numberOfWheels"
inputType="number"
></app-cell>
@ -96,10 +114,11 @@
mat-icon-button
(click)="edit(element)"
*ngIf="
!element.isGettingEdited &&
!element.isLockedByMe &&
!element.waitingForEditPermissions &&
!element.locked &&
!element.saving
!element.isLocked &&
!element.saving &&
!element.unlocking
"
>
<mat-icon>edit</mat-icon>
@ -107,10 +126,11 @@
<button
mat-icon-button
*ngIf="
!element.isGettingEdited &&
!element.isLockedByMe &&
!element.waitingForEditPermissions &&
!element.locked &&
!element.saving
!element.isLocked &&
!element.saving &&
!element.unlocking
"
>
<mat-icon>delete</mat-icon>
@ -118,25 +138,38 @@
<mat-spinner
[diameter]="32"
*ngIf="element.waitingForEditPermissions || element.saving"
*ngIf="
element.waitingForEditPermissions ||
element.saving ||
element.unlocking
"
></mat-spinner>
<button
mat-icon-button
*ngIf="element.isGettingEdited"
*ngIf="
element.isLockedByMe && !element.unlocking && !element.saving
"
(click)="save(element)"
>
<mat-icon>save</mat-icon>
</button>
<button
mat-icon-button
*ngIf="element.isGettingEdited"
matTooltip="Alle ungespeicherten Änderungen verwerfen."
*ngIf="
element.isLockedByMe && !element.unlocking && !element.saving
"
(click)="cancel(element)"
>
<mat-icon>cancel</mat-icon>
</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>
</td>
</ng-container>

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

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

@ -1,17 +1,37 @@
import { Injectable } from '@angular/core';
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';
export type CargoBikeResult = DeepExtractTypeSkipArrays<GetCargoBikesQuery, ["cargoBikes"]>;
export type CargoBikeResult = DeepExtractTypeSkipArrays<
GetCargoBikesQuery,
['cargoBikes']
>;
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class BikesService {
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() {
this.getCargoBikesGQL.fetch().subscribe((result) => {
@ -22,14 +42,48 @@ export class BikesService {
reloadBike(variables: GetCargoBikeByIdQueryVariables) {
this.getCargoBikeByIdGQL.fetch(variables).subscribe((result) => {
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) {
this.updateCargoBikeGQL.mutate(variableValues).subscribe((result) => {
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