Handout management
This commit is contained in:
@@ -8,6 +8,7 @@ import { AuthGuard } from './core/guards/auth.guard';
|
|||||||
import { UserModule } from './modules/user/user.module';
|
import { UserModule } from './modules/user/user.module';
|
||||||
import { RoleModule } from './modules/role/role.module';
|
import { RoleModule } from './modules/role/role.module';
|
||||||
import { KeyModule } from './modules/key/key.module';
|
import { KeyModule } from './modules/key/key.module';
|
||||||
|
import { CustomerModule } from './modules/customer/customer.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -20,6 +21,7 @@ import { KeyModule } from './modules/key/key.module';
|
|||||||
UserModule,
|
UserModule,
|
||||||
RoleModule,
|
RoleModule,
|
||||||
KeyModule,
|
KeyModule,
|
||||||
|
CustomerModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService, AuthGuard],
|
providers: [AppService, AuthGuard],
|
||||||
|
|||||||
13
api/src/model/dto/handover-key.dto.ts
Normal file
13
api/src/model/dto/handover-key.dto.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
import { Customer, Key } from '../entitites';
|
||||||
|
|
||||||
|
export class HandoverKeyDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
key: Key;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
customer: Customer;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
direction: 'out' | 'return';
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './login.dto';
|
export * from './login.dto';
|
||||||
export * from './auth-code.dto';
|
export * from './auth-code.dto';
|
||||||
export * from './create-key-system.dto';
|
export * from './create-key-system.dto';
|
||||||
|
export * from './handover-key.dto';
|
||||||
|
|||||||
33
api/src/model/entitites/customer.entity.ts
Normal file
33
api/src/model/entitites/customer.entity.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Key } from './key.entity';
|
||||||
|
import { KeySystem } from './system.entity';
|
||||||
|
import { ICustomer } from '../interface';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Customer implements ICustomer {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ nullable: false, unique: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@OneToMany(() => Key, (key) => key.customer)
|
||||||
|
keys: Key[];
|
||||||
|
|
||||||
|
@ManyToOne(() => KeySystem)
|
||||||
|
system: KeySystem;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updatet_at' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@@ -4,3 +4,5 @@ export * from './role.entity';
|
|||||||
export * from './cylinder.entity';
|
export * from './cylinder.entity';
|
||||||
export * from './key.entity';
|
export * from './key.entity';
|
||||||
export * from './key_activity.entity';
|
export * from './key_activity.entity';
|
||||||
|
export * from './customer.entity';
|
||||||
|
export * from './key-handout.entity';
|
||||||
|
|||||||
38
api/src/model/entitites/key-handout.entity.ts
Normal file
38
api/src/model/entitites/key-handout.entity.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
BeforeInsert,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Key } from './key.entity';
|
||||||
|
import { Customer } from './customer.entity';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class KeyHandout {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Key)
|
||||||
|
key: Key;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
direction: 'out' | 'return';
|
||||||
|
|
||||||
|
@ManyToOne(() => Customer)
|
||||||
|
customer: Customer;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
timestamp: Date;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
created: Date;
|
||||||
|
|
||||||
|
@BeforeInsert()
|
||||||
|
insertTimestamp() {
|
||||||
|
if (this.timestamp == null) {
|
||||||
|
this.timestamp = new Date();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { Cylinder } from './cylinder.entity';
|
import { Cylinder } from './cylinder.entity';
|
||||||
import { IKey } from '../interface/key.interface';
|
import { IKey } from '../interface/key.interface';
|
||||||
|
import { Customer } from './customer.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Key implements IKey {
|
export class Key implements IKey {
|
||||||
@@ -26,6 +27,9 @@ export class Key implements IKey {
|
|||||||
@ManyToOne(() => Cylinder, (cylinder) => cylinder.keys)
|
@ManyToOne(() => Cylinder, (cylinder) => cylinder.keys)
|
||||||
cylinder: Cylinder;
|
cylinder: Cylinder;
|
||||||
|
|
||||||
|
@ManyToOne(() => Customer, (customer) => customer.keys)
|
||||||
|
customer: Customer;
|
||||||
|
|
||||||
@CreateDateColumn({ name: 'created_at' })
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
import { User } from './user.entity';
|
import { User } from './user.entity';
|
||||||
import { IKey } from '../interface/key.interface';
|
import { IKey } from '../interface/key.interface';
|
||||||
import { Cylinder } from './cylinder.entity';
|
import { Cylinder } from './cylinder.entity';
|
||||||
|
import { Customer } from './customer.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class KeyActivity implements IKey {
|
export class KeyActivity implements IKey {
|
||||||
@@ -29,6 +30,9 @@ export class KeyActivity implements IKey {
|
|||||||
@ManyToOne(() => Cylinder)
|
@ManyToOne(() => Cylinder)
|
||||||
cylinder: Cylinder;
|
cylinder: Cylinder;
|
||||||
|
|
||||||
|
@ManyToOne(() => Customer)
|
||||||
|
customer: Customer;
|
||||||
|
|
||||||
@CreateDateColumn({ name: 'created_at' })
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
|
|||||||
11
api/src/model/interface/customer.interface.ts
Normal file
11
api/src/model/interface/customer.interface.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Key } from 'readline';
|
||||||
|
import { KeySystem } from '../entitites/system.entity';
|
||||||
|
|
||||||
|
export interface ICustomer {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
keys: Key[];
|
||||||
|
system: KeySystem;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@@ -2,3 +2,4 @@ export * from './user.interface';
|
|||||||
export * from './external-access-token.payload.interface';
|
export * from './external-access-token.payload.interface';
|
||||||
export * from './payload.interface';
|
export * from './payload.interface';
|
||||||
export * from './key-system.interface';
|
export * from './key-system.interface';
|
||||||
|
export * from './customer.interface';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Cylinder } from '../entitites';
|
import { Customer, Cylinder } from '../entitites';
|
||||||
|
|
||||||
export interface IKey {
|
export interface IKey {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -6,5 +6,6 @@ export interface IKey {
|
|||||||
nr: number;
|
nr: number;
|
||||||
handedOut: boolean;
|
handedOut: boolean;
|
||||||
cylinder: Cylinder;
|
cylinder: Cylinder;
|
||||||
|
customer: Customer;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
10
api/src/model/repositories/customer.repository.ts
Normal file
10
api/src/model/repositories/customer.repository.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Repository, DataSource } from 'typeorm';
|
||||||
|
import { Customer } from '../entitites';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CustomerRepository extends Repository<Customer> {
|
||||||
|
constructor(dataSource: DataSource) {
|
||||||
|
super(Customer, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,3 +5,4 @@ export * from './system.repository';
|
|||||||
export * from './cylinder.repository';
|
export * from './cylinder.repository';
|
||||||
export * from './key.repository';
|
export * from './key.repository';
|
||||||
export * from './key_activity.repository';
|
export * from './key_activity.repository';
|
||||||
|
export * from './customer.repository';
|
||||||
|
|||||||
10
api/src/model/repositories/key-handout.repository.ts
Normal file
10
api/src/model/repositories/key-handout.repository.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Repository, DataSource } from 'typeorm';
|
||||||
|
import { KeyHandout } from '../entitites';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class KeyHandoutRepository extends Repository<KeyHandout> {
|
||||||
|
constructor(dataSource: DataSource) {
|
||||||
|
super(KeyHandout, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
||||||
20
api/src/modules/customer/customer.controller.ts
Normal file
20
api/src/modules/customer/customer.controller.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from 'src/core/guards/auth.guard';
|
||||||
|
import { AuthenticatedRequest } from 'src/model/interface/authenticated-request.interface';
|
||||||
|
import { CustomerService } from './customer.service';
|
||||||
|
|
||||||
|
@Controller('customer')
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
|
export class CustomerController {
|
||||||
|
constructor(private service: CustomerService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
getCustomers(@Req() req: AuthenticatedRequest) {
|
||||||
|
return this.service.getCustomers(req.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
createCustomer(@Req() req: AuthenticatedRequest, @Body() body: any) {
|
||||||
|
return this.service.createCustomer(req.user, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
api/src/modules/customer/customer.module.ts
Normal file
12
api/src/modules/customer/customer.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { CustomerController } from './customer.controller';
|
||||||
|
import { CustomerService } from './customer.service';
|
||||||
|
import { DatabaseModule } from 'src/shared/database/database.module';
|
||||||
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [CustomerController],
|
||||||
|
providers: [CustomerService],
|
||||||
|
imports: [DatabaseModule, AuthModule],
|
||||||
|
})
|
||||||
|
export class CustomerModule {}
|
||||||
24
api/src/modules/customer/customer.service.ts
Normal file
24
api/src/modules/customer/customer.service.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
|
import { Customer } from 'src/model/entitites';
|
||||||
|
import { IUser } from 'src/model/interface';
|
||||||
|
import { CustomerRepository } from 'src/model/repositories';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CustomerService {
|
||||||
|
constructor(private customerRepository: CustomerRepository) {}
|
||||||
|
|
||||||
|
createCustomer(user: IUser, data: any) {
|
||||||
|
if (!user || !data.name || data.name.length == 0 || !data.system) {
|
||||||
|
throw new HttpException('invalid', HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.customerRepository.save(this.customerRepository.create(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomers(user: IUser): Promise<Customer[]> {
|
||||||
|
return this.customerRepository.find({
|
||||||
|
where: { system: { managers: { id: user.id } } },
|
||||||
|
order: { name: { direction: 'ASC' } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
Req,
|
Req,
|
||||||
@@ -12,6 +13,7 @@ import { AuthenticatedRequest } from 'src/model/interface/authenticated-request.
|
|||||||
import { AuthGuard } from 'src/core/guards/auth.guard';
|
import { AuthGuard } from 'src/core/guards/auth.guard';
|
||||||
import { Key } from 'src/model/entitites';
|
import { Key } from 'src/model/entitites';
|
||||||
import { CreateKeySystemDto } from 'src/model/dto/create-key-system.dto';
|
import { CreateKeySystemDto } from 'src/model/dto/create-key-system.dto';
|
||||||
|
import { HandoverKeyDto } from 'src/model/dto';
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@Controller('key')
|
@Controller('key')
|
||||||
@@ -34,7 +36,24 @@ export class KeyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('system')
|
@Post('system')
|
||||||
createKeySystem(@Req() req: AuthenticatedRequest, @Body() body: CreateKeySystemDto) {
|
createKeySystem(
|
||||||
|
@Req() req: AuthenticatedRequest,
|
||||||
|
@Body() body: CreateKeySystemDto,
|
||||||
|
) {
|
||||||
return this.service.createKeySystem(req.user, body);
|
return this.service.createKeySystem(req.user, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post(':id/handover')
|
||||||
|
handoutKey(
|
||||||
|
@Req() req: AuthenticatedRequest,
|
||||||
|
@Body() body: any,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
return this.service.handoverKey(req.user, body, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/handover')
|
||||||
|
getKeyHandouts(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
|
||||||
|
return this.service.getKeyHandovers(req.user, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
import { CreateKeySystemDto } from 'src/model/dto';
|
import { CreateKeySystemDto } from 'src/model/dto';
|
||||||
import { Cylinder, Key, User } from 'src/model/entitites';
|
import { Cylinder, Key, User } from 'src/model/entitites';
|
||||||
|
import { IUser } from 'src/model/interface';
|
||||||
import {
|
import {
|
||||||
CylinderRepository,
|
CylinderRepository,
|
||||||
KeyActivityRepository,
|
KeyActivityRepository,
|
||||||
KeyRepository,
|
KeyRepository,
|
||||||
KeySystemRepository,
|
KeySystemRepository,
|
||||||
} from 'src/model/repositories';
|
} from 'src/model/repositories';
|
||||||
|
import { KeyHandoutRepository } from 'src/model/repositories/key-handout.repository';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class KeyService {
|
export class KeyService {
|
||||||
@@ -15,12 +17,13 @@ export class KeyService {
|
|||||||
private readonly cylinderRepository: CylinderRepository,
|
private readonly cylinderRepository: CylinderRepository,
|
||||||
private readonly systemRepo: KeySystemRepository,
|
private readonly systemRepo: KeySystemRepository,
|
||||||
private activityRepo: KeyActivityRepository,
|
private activityRepo: KeyActivityRepository,
|
||||||
|
private handoverRepo: KeyHandoutRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getUsersKeys(user: User): Promise<Key[]> {
|
async getUsersKeys(user: User): Promise<Key[]> {
|
||||||
return this.keyrepository.find({
|
return this.keyrepository.find({
|
||||||
where: { cylinder: { system: { managers: { id: user.id } } } },
|
where: { cylinder: { system: { managers: { id: user.id } } } },
|
||||||
relations: ['cylinder', 'cylinder.system'],
|
relations: ['cylinder', 'cylinder.system', 'customer'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,4 +77,35 @@ export class KeyService {
|
|||||||
throw new HttpException(e.code, HttpStatus.UNPROCESSABLE_ENTITY);
|
throw new HttpException(e.code, HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handoverKey(user: IUser, data: any, keyID: string) {
|
||||||
|
const key: Key = await this.keyrepository.findOneOrFail({
|
||||||
|
where: { id: keyID, cylinder: { system: { managers: { id: user.id } } } },
|
||||||
|
});
|
||||||
|
|
||||||
|
key.handedOut = data.direction == 'out';
|
||||||
|
this.keyrepository.save(key);
|
||||||
|
|
||||||
|
return this.handoverRepo.save(
|
||||||
|
this.handoverRepo.create({
|
||||||
|
customer: data.customer,
|
||||||
|
direction: data.direction,
|
||||||
|
timestamp: data.timestamp,
|
||||||
|
key: key,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeyHandovers(user: User, keyID: string) {
|
||||||
|
return this.handoverRepo.find({
|
||||||
|
where: {
|
||||||
|
key: { cylinder: { system: { managers: { id: user.id } } }, id: keyID },
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
timestamp: { direction: 'DESC' },
|
||||||
|
created: { direction: 'DESC' },
|
||||||
|
},
|
||||||
|
relations: ['customer'],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { RoleController } from './role.controller';
|
|
||||||
|
|
||||||
describe('RoleController', () => {
|
|
||||||
let controller: RoleController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [RoleController],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<RoleController>(RoleController);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(controller).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { RoleService } from './role.service';
|
|
||||||
|
|
||||||
describe('RoleService', () => {
|
|
||||||
let service: RoleService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [RoleService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<RoleService>(RoleService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,8 +1,18 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { Cylinder, Key, KeyActivity, Role, SSOUser, User } from 'src/model/entitites';
|
import {
|
||||||
|
Customer,
|
||||||
|
Cylinder,
|
||||||
|
Key,
|
||||||
|
KeyActivity,
|
||||||
|
KeyHandout,
|
||||||
|
Role,
|
||||||
|
SSOUser,
|
||||||
|
User,
|
||||||
|
} from 'src/model/entitites';
|
||||||
import { KeySystem } from 'src/model/entitites/system.entity';
|
import { KeySystem } from 'src/model/entitites/system.entity';
|
||||||
import {
|
import {
|
||||||
|
CustomerRepository,
|
||||||
CylinderRepository,
|
CylinderRepository,
|
||||||
KeyActivityRepository,
|
KeyActivityRepository,
|
||||||
KeyRepository,
|
KeyRepository,
|
||||||
@@ -11,8 +21,19 @@ import {
|
|||||||
SsoUserRepository,
|
SsoUserRepository,
|
||||||
UserRepository,
|
UserRepository,
|
||||||
} from 'src/model/repositories';
|
} from 'src/model/repositories';
|
||||||
|
import { KeyHandoutRepository } from 'src/model/repositories/key-handout.repository';
|
||||||
|
|
||||||
const ENTITIES = [User, SSOUser, Role, KeySystem, Key, Cylinder, KeyActivity];
|
const ENTITIES = [
|
||||||
|
User,
|
||||||
|
SSOUser,
|
||||||
|
Role,
|
||||||
|
KeySystem,
|
||||||
|
Key,
|
||||||
|
Cylinder,
|
||||||
|
KeyActivity,
|
||||||
|
Customer,
|
||||||
|
KeyHandout,
|
||||||
|
];
|
||||||
const REPOSITORIES = [
|
const REPOSITORIES = [
|
||||||
UserRepository,
|
UserRepository,
|
||||||
SsoUserRepository,
|
SsoUserRepository,
|
||||||
@@ -21,6 +42,8 @@ const REPOSITORIES = [
|
|||||||
KeyRepository,
|
KeyRepository,
|
||||||
CylinderRepository,
|
CylinderRepository,
|
||||||
KeyActivityRepository,
|
KeyActivityRepository,
|
||||||
|
CustomerRepository,
|
||||||
|
KeyHandoutRepository,
|
||||||
];
|
];
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
|||||||
41
client/package-lock.json
generated
41
client/package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"@angular/core": "^18.0.0",
|
"@angular/core": "^18.0.0",
|
||||||
"@angular/forms": "^18.0.0",
|
"@angular/forms": "^18.0.0",
|
||||||
"@angular/material": "^18.2.4",
|
"@angular/material": "^18.2.4",
|
||||||
|
"@angular/material-moment-adapter": "^18.2.9",
|
||||||
"@angular/platform-browser": "^18.0.0",
|
"@angular/platform-browser": "^18.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^18.0.0",
|
"@angular/platform-browser-dynamic": "^18.0.0",
|
||||||
"@angular/router": "^18.0.0",
|
"@angular/router": "^18.0.0",
|
||||||
@@ -355,9 +356,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular/cdk": {
|
"node_modules/@angular/cdk": {
|
||||||
"version": "18.2.4",
|
"version": "18.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.9.tgz",
|
||||||
"integrity": "sha512-o+TuxZDqStfkviEkCR05pVyP6R2RIruEs/45Cms76hlsIheMoxRaxir/yrHdh4tZESJJhcO/EVE+aymNIRWAfg==",
|
"integrity": "sha512-hV2dXpvy2TLwCsRtI/ZXkb2EoaJiellRr+kbcnKwO15LFoz3mTAOhKtsvu7yOyURkaPiI605qiIZrPP4zLL1qw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -498,15 +500,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular/material": {
|
"node_modules/@angular/material": {
|
||||||
"version": "18.2.4",
|
"version": "18.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.9.tgz",
|
||||||
"integrity": "sha512-F09145mI/EAHY9ngdnQTo3pFRmUoU/50i6cmddtL4cse0WidatoodQr0gZCksxhmpJgRy5mTcjh/LU2hShOgcA==",
|
"integrity": "sha512-M2oCgPPIMMd6BLgEJCD+FvdC7gRDeCjj9yktNn3ctHmkKUWRvpJ3xRBH/WjVXb+9fPCCW1iNwZI7+bN1fHE7cA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/animations": "^18.0.0 || ^19.0.0",
|
"@angular/animations": "^18.0.0 || ^19.0.0",
|
||||||
"@angular/cdk": "18.2.4",
|
"@angular/cdk": "18.2.9",
|
||||||
"@angular/common": "^18.0.0 || ^19.0.0",
|
"@angular/common": "^18.0.0 || ^19.0.0",
|
||||||
"@angular/core": "^18.0.0 || ^19.0.0",
|
"@angular/core": "^18.0.0 || ^19.0.0",
|
||||||
"@angular/forms": "^18.0.0 || ^19.0.0",
|
"@angular/forms": "^18.0.0 || ^19.0.0",
|
||||||
@@ -514,6 +517,20 @@
|
|||||||
"rxjs": "^6.5.3 || ^7.4.0"
|
"rxjs": "^6.5.3 || ^7.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@angular/material-moment-adapter": {
|
||||||
|
"version": "18.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-18.2.9.tgz",
|
||||||
|
"integrity": "sha512-GjvqMoVcPPP1xpqMPSKEL1eSSfG2omULTdYnN3xFUroKmo8ZPS9+rgcbIi3At3ErnWctayEB0BUycoZwYtwg2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/core": "^18.0.0 || ^19.0.0",
|
||||||
|
"@angular/material": "18.2.9",
|
||||||
|
"moment": "^2.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@angular/platform-browser": {
|
"node_modules/@angular/platform-browser": {
|
||||||
"version": "18.2.4",
|
"version": "18.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.4.tgz",
|
||||||
@@ -9207,6 +9224,16 @@
|
|||||||
"mkdirp": "bin/cmd.js"
|
"mkdirp": "bin/cmd.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||||
|
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mrmime": {
|
"node_modules/mrmime": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"@angular/core": "^18.0.0",
|
"@angular/core": "^18.0.0",
|
||||||
"@angular/forms": "^18.0.0",
|
"@angular/forms": "^18.0.0",
|
||||||
"@angular/material": "^18.2.4",
|
"@angular/material": "^18.2.4",
|
||||||
|
"@angular/material-moment-adapter": "^18.2.9",
|
||||||
"@angular/platform-browser": "^18.0.0",
|
"@angular/platform-browser": "^18.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^18.0.0",
|
"@angular/platform-browser-dynamic": "^18.0.0",
|
||||||
"@angular/router": "^18.0.0",
|
"@angular/router": "^18.0.0",
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Component, inject, LOCALE_ID } from '@angular/core';
|
import { Component, inject, LOCALE_ID } from '@angular/core';
|
||||||
|
import { MAT_DATE_LOCALE } from '@angular/material/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet,],
|
imports: [RouterOutlet,],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: LOCALE_ID, useValue: 'de-DE' },
|
{ provide: LOCALE_ID, useValue: 'de-DE' },
|
||||||
|
{ provide: MAT_DATE_LOCALE, useValue: 'de-DE' }
|
||||||
],
|
],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss'
|
styleUrl: './app.component.scss'
|
||||||
@@ -18,8 +17,6 @@ import { RouterOutlet } from '@angular/router';
|
|||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'client';
|
title = 'client';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private http: HttpClient = inject(HttpClient);
|
private http: HttpClient = inject(HttpClient);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -28,9 +25,4 @@ export class AppComponent {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { provideHotToastConfig } from '@ngxpert/hot-toast';
|
import { provideHotToastConfig } from '@ngxpert/hot-toast';
|
||||||
|
|
||||||
@@ -17,5 +17,7 @@ export const appConfig: ApplicationConfig = {
|
|||||||
autoClose: true,
|
autoClose: true,
|
||||||
dismissible: false,
|
dismissible: false,
|
||||||
duration: 5000
|
duration: 5000
|
||||||
}), provideAnimationsAsync()]
|
}),
|
||||||
|
provideAnimationsAsync()
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
@if (isLoading) {
|
||||||
|
<div class="loading-spinner">
|
||||||
|
<mat-spinner></mat-spinner>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h2 mat-dialog-title>Übergaben {{ data.name }}</h2>
|
||||||
|
<mat-dialog-content>
|
||||||
|
|
||||||
|
<h6>Historie:</h6>
|
||||||
|
<table class="handouts">
|
||||||
|
<tr>
|
||||||
|
<th>Kunde</th>
|
||||||
|
<th >Datum</th>
|
||||||
|
<th>
|
||||||
|
Übergabe
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
@if (handovers.length == 0) {
|
||||||
|
<tr>
|
||||||
|
<td>-</td>
|
||||||
|
<td>-</td>
|
||||||
|
<td>-</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
@for (item of handovers; track $index) {
|
||||||
|
<tr>
|
||||||
|
<td>{{ item.customer.name }}</td>
|
||||||
|
<td>{{ item.timestamp | date}}</td>
|
||||||
|
<td>{{ item.direction == 'out' ? 'Ausgabe' : 'Rückgabe'}}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<form [formGroup]="handoverForm" class="flex-column" style="margin-top: 48px;">
|
||||||
|
<h6>Neue Übergabe anlegen:</h6>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Kunde</mat-label>
|
||||||
|
<input type="text"
|
||||||
|
matInput
|
||||||
|
formControlName="customer"
|
||||||
|
[matAutocomplete]="auto">
|
||||||
|
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
|
||||||
|
@for (option of filteredCustomers | async; track option) {
|
||||||
|
<mat-option [value]="option.name">{{option.name}}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-autocomplete>
|
||||||
|
<mat-hint>Wähle den Empfänger oder tippe einen neuen Namen ein</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div style="margin: 24px 0;">
|
||||||
|
Der Schlüssel wurde
|
||||||
|
<mat-radio-group formControlName="direction" class="flex-column" style="align-items: flex-start; justify-content: flex-start;">
|
||||||
|
<mat-radio-button [value]="'out'">Ausgegeben</mat-radio-button>
|
||||||
|
<mat-radio-button [value]="'return'">Zurückgegeben</mat-radio-button>
|
||||||
|
</mat-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Datum der Übergabe</mat-label>
|
||||||
|
<input matInput [matDatepicker]="picker" formControlName="timestamp">
|
||||||
|
<mat-hint>TT/MM/JJJJ</mat-hint>
|
||||||
|
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #picker></mat-datepicker>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-button mat-dialog-close color="warn" >Schließen</button>
|
||||||
|
<button mat-button (click)="save()" [disabled]="handoverForm.invalid">Speichern</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
:host {
|
||||||
|
width: min(calc(100vw - 24px), 700px);
|
||||||
|
max-height: calc(100vh - 24px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handouts{
|
||||||
|
//margin-top: 32px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HandoverDialogComponent } from './handover-dialog.component';
|
||||||
|
|
||||||
|
describe('HandoverDialogComponent', () => {
|
||||||
|
let component: HandoverDialogComponent;
|
||||||
|
let fixture: ComponentFixture<HandoverDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [HandoverDialogComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(HandoverDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import { Component, inject, LOCALE_ID } from '@angular/core';
|
||||||
|
import { ApiService } from '../../../../shared/api.service';
|
||||||
|
import { IKey } from '../../../../model/interface/key.interface';
|
||||||
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MAT_DATE_LOCALE, provideNativeDateAdapter } from '@angular/material/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
|
import { map, Observable, startWith, timestamp } from 'rxjs';
|
||||||
|
import {
|
||||||
|
MatBottomSheet,
|
||||||
|
MatBottomSheetModule,
|
||||||
|
MatBottomSheetRef,
|
||||||
|
} from '@angular/material/bottom-sheet';
|
||||||
|
import {MatListModule} from '@angular/material/list';
|
||||||
|
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||||
|
import {MatRadioModule} from '@angular/material/radio';
|
||||||
|
import { HotToastService } from '@ngxpert/hot-toast';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-handover-dialog',
|
||||||
|
standalone: true,
|
||||||
|
imports: [FormsModule, ReactiveFormsModule, MatDatepickerModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatDialogModule, CommonModule, MatAutocompleteModule, MatProgressSpinnerModule, MatRadioModule],
|
||||||
|
providers: [
|
||||||
|
provideNativeDateAdapter(),
|
||||||
|
{ provide: LOCALE_ID, useValue: 'de-DE' },
|
||||||
|
{ provide: MAT_DATE_LOCALE, useValue: 'de-DE' },
|
||||||
|
],
|
||||||
|
templateUrl: './handover-dialog.component.html',
|
||||||
|
styleUrl: './handover-dialog.component.scss'
|
||||||
|
})
|
||||||
|
export class HandoverDialogComponent {
|
||||||
|
|
||||||
|
private api: ApiService = inject(ApiService);
|
||||||
|
readonly dialogRef = inject(MatDialogRef<HandoverDialogComponent>);
|
||||||
|
readonly data = inject<IKey>(MAT_DIALOG_DATA);
|
||||||
|
private _bottomSheet = inject(MatBottomSheet);
|
||||||
|
private toast: HotToastService = inject(HotToastService);
|
||||||
|
|
||||||
|
isLoading: boolean = false;
|
||||||
|
|
||||||
|
customers: { name: string, id: string }[] = [];
|
||||||
|
filteredCustomers: Observable<any[]> = new Observable();
|
||||||
|
|
||||||
|
handovers: any[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
handoverForm = new FormGroup({
|
||||||
|
customer: new FormControl<any>(null, Validators.required),
|
||||||
|
key: new FormControl(this.data),
|
||||||
|
direction: new FormControl('out', Validators.required),
|
||||||
|
timestamp: new FormControl(new Date(), Validators.required)
|
||||||
|
});
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData() {
|
||||||
|
this.isLoading = true;
|
||||||
|
const promises: Observable<any>[] = [
|
||||||
|
this.getHandovers(),
|
||||||
|
this.loadCustomers()
|
||||||
|
];
|
||||||
|
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getHandovers() {
|
||||||
|
const promise = this.api.getHandovers(this.data.id)
|
||||||
|
|
||||||
|
promise.subscribe({
|
||||||
|
next: n => {
|
||||||
|
this.handovers = n;
|
||||||
|
if (n && n.length > 0) {
|
||||||
|
this.handoverForm.controls.customer.patchValue(n[0].customer.name);
|
||||||
|
this.handoverForm.controls.direction.patchValue(n[0].direction == 'out' ? 'return' : 'out')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCustomers() {
|
||||||
|
const promise = this.api.getCustomers()
|
||||||
|
|
||||||
|
promise.subscribe({
|
||||||
|
next: customers => {
|
||||||
|
this.customers = customers;
|
||||||
|
this.filteredCustomers = this.handoverForm.controls.customer.valueChanges.pipe(
|
||||||
|
startWith(''),
|
||||||
|
map(value => this._filter(value || '')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filter(value: string): any[] {
|
||||||
|
const filterValue = value.toLowerCase();
|
||||||
|
|
||||||
|
return this.customers.filter(option => option.name.toLowerCase().includes(filterValue) || option.id.toLowerCase().includes(filterValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
const val = this.handoverForm.value;
|
||||||
|
const dto = {
|
||||||
|
key: this.data,
|
||||||
|
customer: this.customers.find(c => c.name == val.customer || c.id == val.customer),
|
||||||
|
timestamp: val.timestamp,
|
||||||
|
direction: val.direction
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dto.customer == null) {
|
||||||
|
this._bottomSheet.open(BottomSheetCreateCustomer).afterDismissed().subscribe({
|
||||||
|
next: async n => {
|
||||||
|
if (!n) { return; }
|
||||||
|
await this.createCustomer(val.customer);
|
||||||
|
this.saveIt(dto);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.saveIt(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveIt(data: any) {
|
||||||
|
this.api.handoverKey(data)
|
||||||
|
.pipe(
|
||||||
|
this.toast.observe({
|
||||||
|
loading: 'Speichern...',
|
||||||
|
error: 'Konnte nicht gespeichert werden. Bitte versuche es später erneut',
|
||||||
|
success: 'Gespeichert'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: n => {
|
||||||
|
this.dialogRef.close(data.direction == 'out')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createCustomer(name: string) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.api.createCustomer({ name, system: this.data.cylinder.system}).subscribe({
|
||||||
|
next: n => {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<mat-nav-list>
|
||||||
|
<a mat-list-item (click)="openLink($event, true)">
|
||||||
|
<span matListItemTitle>Anlegen</span>
|
||||||
|
<span matLine>Neuen Empfänger anlegen</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a mat-list-item (click)="openLink($event)">
|
||||||
|
<span matListItemTitle>Abbrechen</span>
|
||||||
|
<span matLine>Zurück zur Auswahl</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</mat-nav-list>
|
||||||
|
`,
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatInputModule, MatListModule],
|
||||||
|
})
|
||||||
|
export class BottomSheetCreateCustomer {
|
||||||
|
private _bottomSheetRef =
|
||||||
|
inject<MatBottomSheetRef<BottomSheetCreateCustomer>>(MatBottomSheetRef);
|
||||||
|
|
||||||
|
openLink(event: MouseEvent, data?: boolean): void {
|
||||||
|
this._bottomSheetRef.dismiss(data);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import { HotToastService } from '@ngxpert/hot-toast';
|
|||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||||
import { CreateKeyComponent } from './create/create.component';
|
import { CreateKeyComponent } from './create/create.component';
|
||||||
|
import { AgOpenHandoutComponent } from '../../shared/ag-grid/components/ag-open-handout/ag-open-handout.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-keys',
|
selector: 'app-keys',
|
||||||
@@ -32,7 +33,12 @@ export class KeysComponent {
|
|||||||
localeText: AG_GRID_LOCALE_DE,
|
localeText: AG_GRID_LOCALE_DE,
|
||||||
rowData: [],
|
rowData: [],
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
{ field: 'handedOut' , headerName: 'Ausgegeben', width: 100,editable: true, filter: true, headerTooltip: 'Ausgegeben' },
|
{
|
||||||
|
cellRenderer: AgOpenHandoutComponent,
|
||||||
|
width: 100,
|
||||||
|
headerName: 'Übergabe'
|
||||||
|
},
|
||||||
|
{ field: 'handedOut' , headerName: 'Ausgegeben', width: 100, editable: false, filter: false, headerTooltip: 'Ausgegeben' },
|
||||||
{ field: 'name' , headerName: 'Name', flex: 1, editable: true, sort: 'asc', filter: true },
|
{ field: 'name' , headerName: 'Name', flex: 1, editable: true, sort: 'asc', filter: true },
|
||||||
{ field: 'nr' , headerName: 'Schlüsselnummer', flex: 1, editable: true, filter: true },
|
{ field: 'nr' , headerName: 'Schlüsselnummer', flex: 1, editable: true, filter: true },
|
||||||
{ field: 'cylinder' , headerName: 'Zylinder', flex: 1, editable: true, filter: true, cellRenderer: (data: any) => {return data.value?.name}, cellEditor: 'agSelectCellEditor',
|
{ field: 'cylinder' , headerName: 'Zylinder', flex: 1, editable: true, filter: true, cellRenderer: (data: any) => {return data.value?.name}, cellEditor: 'agSelectCellEditor',
|
||||||
@@ -60,9 +66,10 @@ export class KeysComponent {
|
|||||||
, type: 'date'
|
, type: 'date'
|
||||||
, cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value)) : '-'
|
, cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value)) : '-'
|
||||||
, tooltipValueGetter: (data: any) => this.datePipe.transform(new Date(data.value), 'medium')
|
, tooltipValueGetter: (data: any) => this.datePipe.transform(new Date(data.value), 'medium')
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
loading: true,
|
loading: true,
|
||||||
|
rowHeight: 48,
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -71,7 +78,6 @@ export class KeysComponent {
|
|||||||
this.cylinders = n;
|
this.cylinders = n;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.api.postKeySystem({ name: 'Development' }).subscribe()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadKeys() {
|
loadKeys() {
|
||||||
@@ -79,7 +85,6 @@ export class KeysComponent {
|
|||||||
this.api.getKeys().subscribe(res => {
|
this.api.getKeys().subscribe(res => {
|
||||||
this.gridApi.setGridOption("rowData", res);
|
this.gridApi.setGridOption("rowData", res);
|
||||||
this.gridApi.setGridOption("loading", false);
|
this.gridApi.setGridOption("loading", false);
|
||||||
res.map((r: any) => console.log(r.updatedAt))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +96,6 @@ export class KeysComponent {
|
|||||||
|
|
||||||
cellEditEnd(event: CellEditingStoppedEvent) {
|
cellEditEnd(event: CellEditingStoppedEvent) {
|
||||||
const key: IKey = event.data;
|
const key: IKey = event.data;
|
||||||
console.log(event)
|
|
||||||
|
|
||||||
if (!event.valueChanged || event.newValue == event.oldValue) { return; }
|
if (!event.valueChanged || event.newValue == event.oldValue) { return; }
|
||||||
|
|
||||||
this.gridApi.setGridOption("loading", true);
|
this.gridApi.setGridOption("loading", true);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<div class="handover icon-btn-sm" (click)="openDialog()"></div>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handover {
|
||||||
|
background-image: url('../../../../../assets/img/handover.svg');
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Component, HostBinding, inject } from '@angular/core';
|
||||||
|
import { ICellRendererAngularComp } from 'ag-grid-angular';
|
||||||
|
import { ICellRendererParams } from 'ag-grid-community';
|
||||||
|
import { IKey } from '../../../../model/interface/key.interface';
|
||||||
|
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { HandoverDialogComponent } from '../../../../modules/keys/components/handover-dialog/handover-dialog.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-ag-open-handout',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatDialogModule],
|
||||||
|
templateUrl: './ag-open-handout.component.html',
|
||||||
|
styleUrl: './ag-open-handout.component.scss'
|
||||||
|
})
|
||||||
|
export class AgOpenHandoutComponent implements ICellRendererAngularComp {
|
||||||
|
private dialog: MatDialog = inject(MatDialog);
|
||||||
|
key!: IKey;
|
||||||
|
params!: ICellRendererParams<any, any, any>;
|
||||||
|
|
||||||
|
agInit(params: ICellRendererParams<any, any, any>): void {
|
||||||
|
this.params = params;
|
||||||
|
this.key = params.data;
|
||||||
|
}
|
||||||
|
refresh(params: ICellRendererParams<any, any, any>): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
openDialog() {
|
||||||
|
this.dialog.open(HandoverDialogComponent, {
|
||||||
|
data: this.key,
|
||||||
|
autoFocus: false,
|
||||||
|
maxWidth: '100vw',
|
||||||
|
maxHeight: '100vh'
|
||||||
|
}).afterClosed().subscribe({
|
||||||
|
next: n => {
|
||||||
|
if (n != null) {
|
||||||
|
this.key.handedOut = n;
|
||||||
|
this.params.api.refreshCells();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,4 +43,21 @@ export class ApiService {
|
|||||||
postKeySystem(keySystem: any) {
|
postKeySystem(keySystem: any) {
|
||||||
return this.http.post('api/key/system', keySystem);
|
return this.http.post('api/key/system', keySystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handoverKey(data: any) {
|
||||||
|
return this.http.post(`api/key/${data.key.id}/handover`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
getHandovers(keyID: string): Observable<any[]> {
|
||||||
|
return this.http.get<any[]>(`api/key/${keyID}/handover`);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCustomer(data: { name: string, system: any}) {
|
||||||
|
return this.http.post('api/customer', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomers(): Observable<any[]> {
|
||||||
|
return this.http.get<any[]>('api/customer')
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
1
client/src/assets/img/handover.svg
Normal file
1
client/src/assets/img/handover.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.1 KiB |
@@ -8,6 +8,50 @@ html, body {
|
|||||||
background-color: #e2e2e2;
|
background-color: #e2e2e2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-column {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn-sm {
|
||||||
|
box-shadow: 0 0 0 4px transparent,0 1px 2px #0c111d11;
|
||||||
|
border: 1px solid #d0d5dd;
|
||||||
|
background-color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-size: 28px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.4);
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Core Data Grid CSS */
|
/* Core Data Grid CSS */
|
||||||
@import "ag-grid-community/styles/ag-grid.css";
|
@import "ag-grid-community/styles/ag-grid.css";
|
||||||
/* Quartz Theme Specific CSS */
|
/* Quartz Theme Specific CSS */
|
||||||
|
|||||||
Reference in New Issue
Block a user