* initial commit

This commit is contained in:
Danyi Dávid 2018-11-11 12:00:45 +01:00
commit 3a86fe09ab
174 changed files with 17197 additions and 0 deletions

61
.angular-cli.json Normal file
View File

@ -0,0 +1,61 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "frontend"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"../semantic/dist/semantic.css",
"styles.css"
],
"scripts": [
"../node_modules/jquery/dist/jquery.js",
"../semantic/dist/semantic.js"
],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json"
},
{
"project": "src/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"component": {}
}
}

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

43
.gitignore vendored Normal file
View File

@ -0,0 +1,43 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist-beik
/tmp
/out-tsc
/semantic
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store
Thumbs.db

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# webnaplo BEIK frontend
modules
* auth
* fault
* report
* shared ?

17
conf/.htaccess Normal file
View File

@ -0,0 +1,17 @@
RewriteEngine On
# The following rule tells Apache that if the requested filename
# exists, simply serve it.
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
# The following rewrites all other queries to index.html. The
# condition ensures that if you are using Apache aliases to do
# mass virtual hosting, the base path will be prepended to
# allow proper resolution of the index.html file; it will work
# in non-aliased environments as well, providing a safe, one-size
# fits all solution.
RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
RewriteRule ^(.*) - [E=BASE:%1]
RewriteRule ^(.*)$ %{ENV:BASE}index.html [NC,L]

56
deploy.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace Deployer;
require 'recipe/common.php';
// Configuration
// set('repository', 'git@domain.com:username/repository.git');
set('shared_files', []);
set('shared_dirs', []);
set('writable_dirs', []);
set('keep_releases', 3);
set('default_stage', 'production');
// Servers
host('lxuz.hu')
->stage('production')
->user('latuzcs_beik_frontend_access')
->forwardAgent()
->set('ng_target', 'production')
->set('ng_environment', 'gulbaba')
->set('deploy_path', '/var/www/clients/client5/web19/deploy');
// Tasks
desc('Prepare release');
task('deploy:ng-prepare', function() {
runLocally("ng build --target={{ng_target}} --environment=prod --output-path=dist-{{ng_environment}}");
runLocally("tar -C dist-{{ng_environment}} -cJf dist-{{ng_environment}}.tar.xz .");
});
desc('Upload release');
task('deploy:ng-upload', function() {
upload("dist-{{ng_environment}}.tar.xz", "{{release_path}}/dist.tar.xz");
run("tar -C {{release_path}} -xJf {{release_path}}/dist.tar.xz");
run("rm -f {{release_path}}/dist.tar.xz");
runLocally("rm -f dist-{{ng_environment}}.tar.xz");
upload("conf/.htaccess", "{{release_path}}/.htaccess");
});
desc('Deploy your project');
task('deploy', [
'deploy:prepare',
'deploy:lock',
'deploy:release',
'deploy:ng-prepare',
'deploy:ng-upload',
'deploy:shared',
'deploy:clear_paths',
'deploy:symlink',
'deploy:unlock',
'cleanup',
]);
after('deploy', 'success');

14
e2e/app.e2e-spec.ts Normal file
View File

@ -0,0 +1,14 @@
import { AppPage } from './app.po';
describe('frontend App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!');
});
});

11
e2e/app.po.ts Normal file
View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

14
e2e/tsconfig.e2e.json Normal file
View File

@ -0,0 +1,14 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"baseUrl": "./",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

33
karma.conf.js Normal file
View File

@ -0,0 +1,33 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

11249
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

53
package.json Normal file
View File

@ -0,0 +1,53 @@
{
"name": "frontend",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "^4.2.4",
"@angular/common": "^4.2.4",
"@angular/compiler": "^4.2.4",
"@angular/core": "^4.2.4",
"@angular/forms": "^4.2.4",
"@angular/http": "^4.2.4",
"@angular/platform-browser": "^4.2.4",
"@angular/platform-browser-dynamic": "^4.2.4",
"@angular/router": "^4.2.4",
"@auth0/angular-jwt": "^1.0.0-beta.9",
"@types/jquery": "^3.2.12",
"core-js": "^2.4.1",
"jquery": "^3.2.1",
"rxjs": "^5.4.2",
"semantic-ui": "^2.2.13",
"zone.js": "^0.8.14"
},
"devDependencies": {
"@angular/cli": "1.4.1",
"@angular/compiler-cli": "^4.2.4",
"@angular/language-service": "^4.2.4",
"@types/jasmine": "~2.5.53",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"codelyzer": "~3.1.1",
"jasmine-core": "~2.6.2",
"jasmine-spec-reporter": "~4.1.0",
"karma": "~1.7.0",
"karma-chrome-launcher": "~2.1.1",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.2",
"ts-node": "~3.2.0",
"tslint": "~5.3.2",
"typescript": "~2.3.3"
}
}

28
protractor.conf.js Normal file
View File

@ -0,0 +1,28 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

22
semantic.json Normal file
View File

@ -0,0 +1,22 @@
{
"base": "semantic/",
"paths": {
"source": {
"config": "src/theme.config",
"definitions": "src/definitions/",
"site": "src/site/",
"themes": "src/themes/"
},
"output": {
"packaged": "dist/",
"uncompressed": "dist/components/",
"compressed": "dist/components/",
"themes": "dist/themes/"
},
"clean": "dist/"
},
"permission": false,
"autoInstall": true,
"rtl": false,
"version": "2.2.13"
}

View File

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from "./home/home.component";
import { AuthGuardService } from "./auth/auth-guard.service";
import { PageNotFoundComponent } from "./page-not-found/page-not-found.component";
const routes: Routes = [
{path: '', component: HomeComponent, canActivate: [AuthGuardService]},
{path: '**', component: PageNotFoundComponent},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}

View File

View File

@ -0,0 +1,2 @@
<app-navigation *ngIf="isLoggedIn"></app-navigation>
<router-outlet></router-outlet>

View File

@ -0,0 +1,31 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

51
src/app/app.component.ts Normal file
View File

@ -0,0 +1,51 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { AuthService } from "./auth/auth.service";
import { Subscription } from "rxjs/Subscription";
import { TimerObservable } from "rxjs/observable/TimerObservable";
import { FaultManagerService } from "./fault-manager/fault-manager.service";
// timers are in milliseconds
const RENEW_TIMER_INITIAL = 300000; // 5min
const RENEW_TIMER_PERIOD = 300000; // 5min
const FAULT_RELOAD_PERIOD = 5000;
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
private authRenewTimer: Subscription;
private faultReloadTimer: Subscription;
constructor(private authService: AuthService,
private faultManager: FaultManagerService) {}
ngOnInit(): void {
let authRenewObservable = TimerObservable.create(RENEW_TIMER_INITIAL, RENEW_TIMER_PERIOD);
this.authRenewTimer = authRenewObservable.subscribe(() => {
if (this.authService.isLoggedIn) {
this.authService.renew();
}
});
let faultReloadObservable = TimerObservable.create(FAULT_RELOAD_PERIOD, FAULT_RELOAD_PERIOD);
this.faultReloadTimer = faultReloadObservable.subscribe(() => {
if (this.authService.isLoggedIn) {
this.faultManager.list().subscribe(faults => this.faultManager.faults = faults);
}
});
}
ngOnDestroy(): void {
this.authRenewTimer.unsubscribe();
this.faultReloadTimer.unsubscribe();
}
get isLoggedIn(): boolean {
return this.authService.isLoggedIn;
}
}

42
src/app/app.module.ts Normal file
View File

@ -0,0 +1,42 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from "@angular/common/http";
import { FormsModule } from "@angular/forms";
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FaultManagerModule } from "./fault-manager/fault-manager.module";
import { AuthModule } from "./auth/auth.module";
import { NavigationModule } from "./navigation/navigation.module";
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { HomeComponent } from './home/home.component';
import { SharedModule } from "./shared/shared.module";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { UserModule } from "./user/user.module";
import { HelpModule } from "./help/help.module";
import { MaintenanceModule } from "./maintenance/maintenance.module";
@NgModule({
declarations: [
AppComponent,
PageNotFoundComponent,
HomeComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
FormsModule,
SharedModule,
AuthModule,
NavigationModule,
FaultManagerModule,
UserModule,
HelpModule,
MaintenanceModule,
AppRoutingModule, // must be last for ** route to work as intended
],
bootstrap: [AppComponent]
})
export class AppModule {
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { AuthGuardService } from './auth-guard.service';
describe('AuthGuardService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AuthGuardService]
});
});
it('should be created', inject([AuthGuardService], (service: AuthGuardService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from "@angular/router";
import { AuthService } from "./auth.service";
@Injectable()
export class AuthGuardService {
constructor(private authService: AuthService,
private router: Router) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) {
return true;
}
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Navigate to the login page with extras
this.router.navigate(['/bejelentkezes']);
return false;
}
}

View File

@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthComponent } from "./auth/auth.component";
const routes: Routes = [
{path: 'bejelentkezes', component: AuthComponent},
{path: 'kijelentkezes', component: AuthComponent},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AuthRoutingModule {
}

View File

@ -0,0 +1,40 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from "@angular/forms";
import { JwtModule } from '@auth0/angular-jwt';
import { AuthRoutingModule } from './auth-routing.module';
import { AuthComponent } from './auth/auth.component';
import { AuthService } from './auth.service';
import { AuthGuardService } from './auth-guard.service';
import { RoleGuardService } from './role-guard.service';
// required for angular cli compiler, tokenGetter can't be lambda
export function tokenGetterFunctionWrapper() {
return localStorage.getItem('token');
}
@NgModule({
imports: [
JwtModule.forRoot({
config: {
tokenGetter: tokenGetterFunctionWrapper,
whitelistedDomains: [
"localhost:8888",
"api.beik.lxuz.hu",
],
},
}),
CommonModule,
FormsModule,
AuthRoutingModule,
],
declarations: [AuthComponent],
providers: [
AuthService,
AuthGuardService,
RoleGuardService,
]
})
export class AuthModule {
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AuthService]
});
});
it('should be created', inject([AuthService], (service: AuthService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,91 @@
import { Injectable } from '@angular/core';
import { Router } from "@angular/router";
import { HttpClient } from "@angular/common/http";
import 'rxjs/Rx';
import { JwtHelperService } from '@auth0/angular-jwt';
import { environment } from '../../environments/environment';
const TOKEN_LS_NAME = 'token';
@Injectable()
export class AuthService {
private url = environment.apiUrl + '/api/auth';
public redirectUrl: string;
public isLoading: boolean = false;
public hasError: boolean = false;
public errorMessage: string = "";
constructor(private httpService: HttpClient,
private jwtHelperService: JwtHelperService,
private router: Router) {
}
get token(): string {
return localStorage.getItem(TOKEN_LS_NAME);
}
get isLoggedIn(): boolean {
try {
return !this.jwtHelperService.isTokenExpired(this.token);
} catch (ex) {
return false;
}
}
get tokenData() {
return this.jwtHelperService.decodeToken(this.token);
}
public authRedirect() {
this.router.navigate(['/bejelentkezes']);
}
public login(login: string, password: string) {
this.hasError = false;
this.isLoading = true;
this.httpService.post<AuthResponse>(this.url + '/login',{
'login': login,
'password': password,
}).subscribe(
apiResponse => {
this.isLoading = false;
if (apiResponse.message == 'login') {
localStorage.setItem(TOKEN_LS_NAME, apiResponse.token);
this.router.navigate(['/']);
} else {
this.hasError = true;
this.errorMessage = "Sikertelen bejelentkezés.";
}
},
err => {
console.log(err);
this.hasError = true;
this.errorMessage = "Hiba történt bejelentkezés közben.";
this.isLoading = false;
}
);
}
public renew() {
this.httpService.get<AuthResponse>(this.url + '/renew')
.subscribe(
apiResponse => {
if (apiResponse.message == 'renew') {
localStorage.setItem(TOKEN_LS_NAME, apiResponse.token);
}
},
err => console.log(err)
);
}
public logout() {
localStorage.removeItem(TOKEN_LS_NAME);
}
}
class AuthResponse {
public message: string;
public token: string;
}

View File

@ -0,0 +1,15 @@
body {
background-color: #DADADA;
}
body > .grid {
height: 100%;
}
.ui.middle.aligned {
margin-top: 50px;
}
.image {
margin-top: -100px;
}
.column {
max-width: 450px;
}

View File

@ -0,0 +1,28 @@
<div class="ui middle aligned center aligned grid">
<div class="column">
<h2 class="ui teal image header">
<i class="circular inverted tiny olive file text icon"></i>
<div class="content">
WN BEIK Bejelentkezés
</div>
</h2>
<form (ngSubmit)="doSubmit()" class="ui large form" [class.error]="hasError" [class.loading]="isLoading">
<div class="ui raised segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" #loginField name="login" placeholder="E-mail cím">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" #passwordField name="password" placeholder="Jelszó">
</div>
</div>
<button class="ui fluid large teal submit button">Bejelentkezés</button>
</div>
<div class="ui error message">{{errorMessage}}</div>
</form>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AuthComponent } from './auth.component';
describe('AuthComponent', () => {
let component: AuthComponent;
let fixture: ComponentFixture<AuthComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AuthComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AuthComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,50 @@
import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from "../auth.service";
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.css']
})
export class AuthComponent implements OnInit {
@ViewChild('loginField') login: ElementRef;
@ViewChild('passwordField') password: ElementRef;
constructor(private authService: AuthService,
private router: Router) {
switch (this.router.url) {
case '/kijelentkezes':
this.authService.logout();
this.authService.authRedirect();
return;
}
}
ngOnInit() {
if (this.authService.isLoggedIn) {
this.router.navigate(['/']);
}
}
public doSubmit() {
this.authService.login(
this.login.nativeElement.value,
this.password.nativeElement.value
);
}
get hasError(): boolean {
return this.authService.hasError;
}
get isLoading(): boolean {
return this.authService.isLoading;
}
get errorMessage(): string {
return this.authService.errorMessage;
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { RoleGuardService } from './role-guard.service';
describe('RoleGuardService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [RoleGuardService]
});
});
it('should be created', inject([RoleGuardService], (service: RoleGuardService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,150 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from "@angular/router";
import { AuthService } from "./auth.service";
@Injectable()
export class RoleGuardService{
private roleDef = {
// üzemeltetési főosztály
'ufo': [
new RegExp("/hiba/lista", "i"),
new RegExp("/hiba/feladat-lista", "i"),
new RegExp("/hiba/nagyerteku-jovahagyas/[\\d]+", "i"),
new RegExp("/hibatarolo/[\\w]+", "i"),
new RegExp("/hiba/megjelenites/[\\d]+", "i"),
new RegExp("/beallitasok", "i"),
],
'uzemeltetesi_vezeto': [
new RegExp("/hiba/lista", "i"),
new RegExp("/hiba/feladat-lista", "i"),
new RegExp("/hiba/rogzites", "i"),
new RegExp("/hiba/javitas-lezaras/[\\d]+", "i"),
new RegExp("/hiba/elfogadas-visszaigazolas/[\\d]+", "i"),
new RegExp("/hibatarolo/[\\w]+", "i"),
new RegExp("/hiba/megjelenites/[\\d]+", "i"),
new RegExp("/beallitasok", "i"),
new RegExp("/karbantartas/(idoszeru|teljes)-lista", "i"),
],
'projektvezeto': [
new RegExp("/hiba/lista", "i"),
new RegExp("/hiba/feladat-lista", "i"),
new RegExp("/hiba/visszaigazolas/[\\d]+", "i"),
new RegExp("/hiba/javitas-lezaras/[\\d]+", "i"),
new RegExp("/hibatarolo/[\\w]+", "i"),
new RegExp("/hiba/megjelenites/[\\d]+", "i"),
new RegExp("/beallitasok", "i"),
new RegExp("/riportok/szures", "i"),
new RegExp("/riportok/havi-zaras", "i"),
new RegExp("/karbantartas/(idoszeru|teljes)-lista", "i"),
new RegExp("/karbantartas/szerkesztes", "i"),
],
// readonly
'betekinto': [
new RegExp("/hiba/lista", "i"),
new RegExp("/hibatarolo/[\\w]+", "i"),
new RegExp("/hiba/megjelenites/[\\d]+", "i"),
new RegExp("/beallitasok", "i"),
],
};
constructor(private authService: AuthService,
private router: Router) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let tokenData = this.authService.tokenData;
return this.roleCanAccessRoute(tokenData.roles, state.url);
}
public roleCanAccessRoute(roles: Array<string>, url: string): boolean {
if (roles.indexOf('admin') != -1) {
return true;
}
for (let i = 0; i < roles.length; i++) {
if (this.roleDef[roles[i]]) {
for (let j = 0; j < this.roleDef[roles[i]].length; j++) {
if (this.roleDef[roles[i]][j].test(url)) {
return true;
}
}
} else {
console.warn('Role definition not found: ' + roles[i]);
}
}
this.router.navigate(['/']);
return false;
}
// @todo this is really primitive, should look for some rbacl implementation
public userCanAccessResource(resource: string): boolean {
let roles = this.authService.tokenData.roles;
if (roles.indexOf('admin') != -1) {
return true;
}
switch (resource) {
case 'recordFault':
if (roles.indexOf('uzemeltetesi_vezeto') != -1) {
return true;
}
break;
case 'confirm':
if (roles.indexOf('projektvezeto') != -1) {
return true;
}
break;
case 'confirmExtraCost':
if (roles.indexOf('ufo') != -1) {
return true;
}
break;
case 'karbantartas':
if (roles.indexOf('projektvezeto') != -1) {
return true;
}
break;
case 'repair':
if (roles.indexOf('uzemeltetesi_vezeto') != -1) {
return true;
}
case 'finishRepair':
if (roles.indexOf('projektvezeto') != -1) {
return true;
}
break;
case 'acknowledge':
if (roles.indexOf('uzemeltetesi_vezeto') != -1) {
return true;
}
break;
case 'monthlyClose':
if (roles.indexOf('projektvezeto') != -1) {
return true;
}
break;
case 'riportok':
if (roles.indexOf('projektvezeto') != -1) {
return true;
}
if (roles.indexOf('uzemeltetesi_vezeto') != -1) {
return true;
}
if (roles.indexOf('ufo') != -1) {
return true;
}
break;
case 'taskList':
return roles.indexOf('betekinto') == -1;
}
return false;
}
public userHasRole(role: string) {
return this.authService.tokenData.roles.indexOf(role) != -1;
}
}

View File

@ -0,0 +1,3 @@
label {
white-space: nowrap;
}

View File

@ -0,0 +1,90 @@
<div class="ui main container">
<h1 class="ui dividing header">{{fault.facilityLocation.roomNumber}} - {{fault.facilityLocation.name}}</h1>
<div class="ui segment">
<div class="ui dimmer" [class.active]="submitInProgress">
<div class="ui indeterminate text loader">Mentés</div>
</div>
<form class="ui form" #confirmForm>
<h4 class="ui dividing header">Nagyértékű munka jóváhagyása</h4>
<div class="field">
<label>Elutasítás indoklása</label>
<textarea rows="3"
placeholder=""
name="rejectReason"
[(ngModel)]="rejectReason"></textarea>
</div>
<div class="four inline fields">
<div class="two wide field">
<button class="ui fluid button" [class.negative]="canReject" [class.disabled]="!canReject" (click)="reject()">Elvetés</button>
</div>
<div class="three wide field">
<button class="ui fluid button"
[class.positive]="!submitInProgress"
[class.disabled]="submitInProgress"
(click)="confirm()">Jóváhagyás</button>
</div>
<div class="five wide field">
<div class="ui checkbox">
<input type="checkbox" id="mustLowerCost" name="mustLowerCost"
[(ngModel)]="fault.mustLowerCost">
<label for="mustLowerCost">Költség csökkentés szükséges</label>
</div>
</div>
<div class="six wide field" [class.error]="noAllocatedExpense" *ngIf="fault.mustLowerCost">
<label>Keret összeg:</label>
<input type="number" name="allocatedExpense" [(ngModel)]="fault.allocatedExpense">
</div>
</div>
<h4 class="ui dividing header">Hiba adatai</h4>
<table class="ui red definition table">
<tr>
<td class="collapsing">Becsült munkaköltség</td>
<td>{{fault.workCostEstimate|currencyFormat}}</td>
</tr>
<tr>
<td class="collapsing">Becsült alapanyagköltség</td>
<td>{{numericMaterialCostEstimate|currencyFormat}}</td>
</tr>
<tr>
<td class="collapsing">Részletes költségvetés</td>
<td><a [href]="expensePdfDownloadUrl" target="_blank"><i class="file pdf outline icon"></i>Letöltés</a>
</td>
</tr>
<tr>
<td class="collapsing">Hiba helye</td>
<td><b>{{fault.facilityLocation.roomNumber}}</b>-{{fault.facilityLocation.name}}</td>
</tr>
<tr>
<td class="collapsing">Hiba helyének részletes leírása</td>
<td>{{fault.facilityLocationDescription}}</td>
</tr>
<tr>
<td class="collapsing">Hiba szakág</td>
<td>{{fault.errorCategory.name}}</td>
</tr>
<tr>
<td class="collapsing">Hiba leírás</td>
<td>{{fault.errorDescription}}</td>
</tr>
<tr>
<td class="collapsing">Hiba eredete</td>
<td>{{fault.errorOrigin.name}}</td>
</tr>
<tr>
<td class="collapsing">Hibajavítás sürgőssége</td>
<td>{{fault.solutionTimeInterval.name}}</td>
</tr>
</table>
<h4 class="ui dividing header" *ngIf="imageAttachments?.length">Csatolmányok</h4>
<div #photoAttachments class="photoAttachments" class="ui bordered images segment"
*ngIf="imageAttachments?.length">
<a *ngFor="let image of imageAttachments; let idx = index" [href]="imageUrl(image.id)"
target="_blank"><img
class="ui image" [src]="imageUrl(image.id)" height="200"/></a>
</div>
<h4 class="ui dividing header"></h4>
</form>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ConfirmExpensiveFaultComponent } from './confirm-expensive-fault.component';
describe('ConfirmExpensiveFaultComponent', () => {
let component: ConfirmExpensiveFaultComponent;
let fixture: ComponentFixture<ConfirmExpensiveFaultComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ConfirmExpensiveFaultComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ConfirmExpensiveFaultComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,76 @@
import { Component, OnInit } from '@angular/core';
import { Title } from "@angular/platform-browser";
import { ActivatedRoute, Router } from "@angular/router";
import { environment } from "../../../environments/environment";
import { FaultManagerService } from "../fault-manager.service";
import { Fault } from "../shared/fault";
@Component({
selector: 'app-confirm-expensive-fault',
templateUrl: './confirm-expensive-fault.component.html',
styleUrls: ['./confirm-expensive-fault.component.css']
})
export class ConfirmExpensiveFaultComponent implements OnInit {
private attachmentUrl = environment.apiUrl + '/show-attachment';
public fault: Fault = null;
public rejectReason: string = '';
public submitInProgress: boolean = false;
constructor(private faultManager: FaultManagerService,
private titleService: Title,
private route: ActivatedRoute,
private router: Router) {
}
ngOnInit() {
this.titleService.setTitle('Webnapló : Nagyértékű munka jóváhagyása');
this.route.data.subscribe((data: { fault: Fault }) => this.fault = data.fault);
}
public confirm() {
this.submitInProgress = true;
this.faultManager.confirmFault(this.fault)
.finally(() => this.submitInProgress = false)
.subscribe(() => this.router.navigate(['/hiba/feladat-lista']));
}
public reject() {
this.submitInProgress = true;
this.faultManager.reject(this.fault, 'rej', this.rejectReason)
.finally(() => this.submitInProgress = false)
.subscribe(() => this.router.navigate(['/hiba/feladat-lista']));
}
get expensePdfDownloadUrl(): string {
let expensePdfId = this.fault.attachments.filter(attachment => attachment.type == 'expense');
if(expensePdfId.length > 0) {
return this.attachmentUrl + '/' + expensePdfId[0].id;
}
return "";
}
get imageAttachments() {
return this.fault.attachments.filter(attachment => attachment.type == 'image');
}
public imageUrl(image: number): string {
return this.attachmentUrl + '/' + image;
}
get noAllocatedExpense(): boolean {
return this.fault.allocatedExpense == 0;
}
get canReject(): boolean {
return this.rejectReason.length > 0
&& !this.submitInProgress;
}
get numericMaterialCostEstimate(): number {
return this.fault.materialCostEstimate
? this.fault.materialCostEstimate
: 0;
}
}

View File

@ -0,0 +1,91 @@
<div class="ui main container">
<h1 class="ui dividing header">{{fault.facilityLocation.roomNumber}} - {{fault.facilityLocation.name}}</h1>
<div class="ui segment">
<div class="ui dimmer" [class.active]="submitInProgress">
<div class="ui indeterminate text loader">Mentés</div>
</div>
<form class="ui form" #confirmForm>
<h4 class="ui dividing header">Hiba visszaigazolása</h4>
<div class="three fields">
<div class="four wide field" [class.error]="!statusAccepted">
<div class="ui checkbox">
<input type="checkbox" id="statusAccepted" name="statusAccepted" [(ngModel)]="statusAccepted">
<label for="statusAccepted">Visszaigazolható</label>
</div>
</div>
<div class="six wide field" [class.error]="noMaterialCostEstimate">
<input type="number" name="costEstimate"
placeholder="Alapanyagköltség nagyságrendileg"
[(ngModel)]="fault.materialCostEstimate">
</div>
<div class="six wide field" [class.error]="noWorkCostEstimate">
<input type="number" name="costEstimate"
placeholder="Munkaköltség nagyságrendileg"
[(ngModel)]="fault.workCostEstimate">
</div>
</div>
<div class="field" [class.error]="hasNoPdfAttachment" [hidden]="!highExpense">
<h4 class="ui dividing header">Költség becslés pdf formátumban</h4>
<input #pdfUpload type="file" accept="application/pdf"/>
</div>
<div class="ui error message" [class.visible]="hasErrors">
<p>A pirossal jelölt mezők kitöltése kötelező.</p>
</div>
<button *ngIf="!highExpense" class="ui button"
[class.positive]="canConfirm"
[class.disabled]="!canConfirm" tabindex="0"
(click)="confirm()">Visszaigazolás
</button>
<button *ngIf="highExpense" class="ui button"
[class.orange]="canHighExpense"
[class.disabled]="!canHighExpense" tabindex="0"
(click)="setHighExpenseFault()">Nagyértékű munka továbbküldése jóváhagyásra
</button>
<h4 class="ui dividing header">Hiba helye</h4>
<table class="ui red definition table">
<tr>
<td class="collapsing">Hiba helye</td>
<td><b>{{fault.facilityLocation.roomNumber}}</b>-{{fault.facilityLocation.name}}</td>
</tr>
<tr>
<td class="collapsing">Hiba helyének részletes leírása</td>
<td>{{fault.facilityLocationDescription}}</td>
</tr>
<tr>
<td class="collapsing">Hiba szakág</td>
<td>{{fault.errorCategory.name}}</td>
</tr>
<tr>
<td class="collapsing">Hiba leírás</td>
<td>{{fault.errorDescription}}</td>
</tr>
<tr>
<td class="collapsing">Hiba eredete</td>
<td>{{fault.errorOrigin.name}}</td>
</tr>
<tr>
<td class="collapsing">Hibajavítás sürgőssége</td>
<td>{{fault.solutionTimeInterval.name}}</td>
</tr>
</table>
<h4 class="ui dividing header" *ngIf="imageAttachments?.length">Csatolmányok</h4>
<div class="field">
<input #photoUpload type="file" accept="image/*" multiple/>
</div>
<div class="field">
<button class="ui button" [class.positive]="canAttach" [class.disabled]="!canAttach" tabindex="0"
type="button" (click)="attachImages()">Kép feltöltése
</button>
</div>
<div #photoAttachments class="photoAttachments" class="ui bordered images segment"
*ngIf="imageAttachments.length || rawImageData.length">
<a *ngFor="let image of imageAttachments; let idx = index" [href]="imageUrl(image.id)" target="_blank"><img
class="ui image" [src]="imageUrl(image.id)" height="200"/></a>
<img class="ui image" *ngFor="let image of rawImageData; let idx = index" [src]="image" height="200"/>
</div>
<h4 class="ui dividing header"></h4>
</form>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ConfirmFaultComponent } from './confirm-fault.component';
describe('ConfirmFaultComponent', () => {
let component: ConfirmFaultComponent;
let fixture: ComponentFixture<ConfirmFaultComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ConfirmFaultComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ConfirmFaultComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,147 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { Title } from "@angular/platform-browser";
import { ActivatedRoute, Router } from "@angular/router";
import { environment } from "../../../environments/environment";
import { FaultManagerService } from "../fault-manager.service";
import { Fault } from "../shared/fault";
@Component({
selector: 'app-confirm-fault',
templateUrl: './confirm-fault.component.html',
styleUrls: ['./confirm-fault.component.css']
})
export class ConfirmFaultComponent implements OnInit {
@ViewChild('pdfUpload') pdfUpload: ElementRef;
@ViewChild('photoUpload') photoUpload: ElementRef;
@ViewChild('photoAttachments') photoAttachments: ElementRef;
private attachmentUrl = environment.apiUrl + '/show-attachment';
public fault: Fault = null;
public submitInProgress: boolean = false;
public disApproveNote: string = '';
public statusAccepted: boolean = false;
public rawImageData: Array<any> = [];
constructor(private faultManager: FaultManagerService,
private titleService: Title,
private route: ActivatedRoute,
private router: Router) {
}
ngOnInit() {
this.titleService.setTitle('Webnapló : Hiba visszaigazolása');
this.route.data.subscribe((data: { fault: Fault }) => this.fault = data.fault);
jQuery(this.pdfUpload.nativeElement).on('change', changeEvent => {
for (let i = 0; i < this.pdfUpload.nativeElement.files.length; i++) {
if (!/\.(pdf)$/i.test(this.pdfUpload.nativeElement.files[i].name)) {
this.pdfUpload.nativeElement.value = null;
break;
}
}
});
jQuery(this.photoUpload.nativeElement).on('change', changeEvent => {
this.rawImageData = [];
for (let i = 0; i < this.photoUpload.nativeElement.files.length; i++) {
if (!/\.(jpe?g|png|gif)$/i.test(this.photoUpload.nativeElement.files[i].name)) {
continue;
}
let reader = new FileReader();
reader.addEventListener("load", () => {
this.rawImageData.push(reader.result);
});
reader.readAsDataURL(this.photoUpload.nativeElement.files[i]);
}
});
}
public confirm() {
this.submitInProgress = true;
// this.fault.costEstimate = this.costEstimate;
this.faultManager.confirmFault(this.fault)
.finally(() => this.submitInProgress = false)
.subscribe(() => this.router.navigate(['/hiba/feladat-lista']));
}
public setHighExpenseFault() {
this.submitInProgress = true;
// this.fault.costEstimate = this.costEstimate;
this.faultManager.setHighExpenseFault(this.fault)
.finally(() => this.submitInProgress = false)
.subscribe(fault => {
this.faultManager.attachPdf(this.fault.id, this.pdfUpload.nativeElement.files).subscribe(
attRes => this.router.navigate(['/hiba/feladat-lista']),
attErr => console.log("File upload error: ", attErr)
);
});
}
public attachImages() {
this.submitInProgress = true;
this.faultManager.attachImages(this.fault.id, this.photoUpload.nativeElement.files)
.finally(() => this.submitInProgress = false)
.subscribe(
attRes => this.photoUpload.nativeElement.value = null,
attErr => alert("Hiba történt a képfeltöltés közben.")
);
}
public reject() {
}
get canConfirm(): boolean {
return this.statusAccepted
&& !this.noWorkCostEstimate
&& !this.noMaterialCostEstimate
&& !this.submitInProgress;
}
get canHighExpense(): boolean {
return this.statusAccepted
&& this.pdfUpload.nativeElement.files.length > 0
&& !this.submitInProgress;
}
get highExpense(): boolean {
return this.fault.workCostEstimate > 100000
|| this.fault.materialCostEstimate > 200000;
}
get imageAttachments() {
return this.fault.attachments.filter(attachment => attachment.type == 'image');
}
public imageUrl(image: number): string {
return this.attachmentUrl + '/' + image;
}
get canAttach(): boolean {
return this.photoUpload.nativeElement.files.length > 0
&& !this.submitInProgress;
}
get noWorkCostEstimate(): boolean {
return this.fault.workCostEstimate == null;
}
get noMaterialCostEstimate(): boolean {
return this.fault.materialCostEstimate == null
&& this.noWorkCostEstimate;
}
get hasNoPdfAttachment(): boolean {
return this.pdfUpload.nativeElement.files.length == 0;
}
get hasErrors(): boolean {
return this.highExpense
? !(this.statusAccepted
&& this.pdfUpload.nativeElement.files.length > 0)
: !(this.statusAccepted
&& !this.noWorkCostEstimate
&& !this.noMaterialCostEstimate);
}
}

View File

@ -0,0 +1,103 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuardService } from "../auth/auth-guard.service";
import { RoleGuardService } from "../auth/role-guard.service";
import { ListFaultsComponent } from "./list-faults/list-faults.component";
import { ListTasksComponent } from "./list-tasks/list-tasks.component";
import { RecordFaultComponent } from "./record-fault/record-fault.component";
import { ConfirmFaultComponent } from "./confirm-fault/confirm-fault.component";
import { ConfirmExpensiveFaultComponent } from "./confirm-expensive-fault/confirm-expensive-fault.component";
import { RepairFaultComponent } from "./repair-fault/repair-fault.component";
import { RepairCompleteComponent } from './repair-complete/repair-complete.component';
import { ErrorCategoryService } from "./providers/error-category.service";
import { ErrorOriginService } from "./providers/error-origin.service";
import { FacilityLocationService } from "./providers/facility-location.service";
import { SolutionTimeIntervalService } from "./providers/solution-time-interval.service";
import { FaultManagerService } from "./fault-manager.service";
import { FaultResolverService } from "./fault-resolver.service";
import { ReadonlyDisplayComponent } from "./readonly-display/readonly-display.component";
import { FaultStorageComponent } from "./fault-storage/fault-storage.component";
import { UserService } from "../user/user.service";
// import { MaintenanceManagerService } from "../maintenance/maintenance-manager.service";
const routes: Routes = [
{
path: 'hiba/feladat-lista',
component: ListTasksComponent,
canActivate: [AuthGuardService, RoleGuardService],
resolve: {
faults: FaultManagerService,
// maintenances: MaintenanceManagerService,
},
},{
path: 'hiba/lista',
component: ListFaultsComponent,
canActivate: [AuthGuardService, RoleGuardService],
resolve: {
faults: FaultManagerService,
// maintenances: MaintenanceManagerService,
},
},{
path: 'hiba/rogzites',
component: RecordFaultComponent,
canActivate: [AuthGuardService, RoleGuardService],
resolve: {
errorCategories: ErrorCategoryService,
errorOrigins: ErrorOriginService,
facilityLocations: FacilityLocationService,
solutionTimeIntervals: SolutionTimeIntervalService,
},
},{
path: 'hiba/visszaigazolas/:id',
component: ConfirmFaultComponent,
canActivate: [AuthGuardService, RoleGuardService],
resolve: {
fault: FaultResolverService,
},
},{
path: 'hiba/nagyerteku-jovahagyas/:id',
component: ConfirmExpensiveFaultComponent,
canActivate: [AuthGuardService, RoleGuardService],
resolve: {
fault: FaultResolverService,
},
},{
path: 'hiba/javitas-lezaras/:id',
component: RepairFaultComponent,
canActivate: [AuthGuardService, RoleGuardService],
resolve: {
fault: FaultResolverService,
users: UserService,
},
},{
path: 'hiba/elfogadas-visszaigazolas/:id',
component: RepairCompleteComponent,
canActivate: [AuthGuardService, RoleGuardService],
resolve: {
fault: FaultResolverService,
},
},{
path: 'hibatarolo/:type',
component: FaultStorageComponent,
canActivate: [AuthGuardService, RoleGuardService],
resolve: {
faults: FaultManagerService,
},
},{
path: 'hiba/megjelenites/:id',
component: ReadonlyDisplayComponent,
canActivate: [AuthGuardService, RoleGuardService],
resolve: {
fault: FaultResolverService,
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class FaultManagerRoutingModule {}

View File

@ -0,0 +1,54 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from "@angular/forms";
import { FaultManagerRoutingModule } from './fault-manager-routing.module';
import { SharedModule } from "../shared/shared.module";
import { RecordFaultComponent } from './record-fault/record-fault.component';
import { ListFaultsComponent } from './list-faults/list-faults.component';
import { ErrorCategoryService } from "./providers/error-category.service";
import { ErrorOriginService } from "./providers/error-origin.service";
import { FacilityLocationService } from "./providers/facility-location.service";
import { SolutionTimeIntervalService } from "./providers/solution-time-interval.service";
import { FaultManagerService } from './fault-manager.service';
import { TableViewComponent } from './table-view/table-view.component';
import { ListTasksComponent } from './list-tasks/list-tasks.component';
import { ConfirmFaultComponent } from './confirm-fault/confirm-fault.component';
import { FaultResolverService } from './fault-resolver.service';
import { ConfirmExpensiveFaultComponent } from './confirm-expensive-fault/confirm-expensive-fault.component';
import { RepairFaultComponent } from './repair-fault/repair-fault.component';
import { RepairCompleteComponent } from './repair-complete/repair-complete.component';
import { ReadonlyDisplayComponent } from './readonly-display/readonly-display.component';
import { FaultStorageComponent } from './fault-storage/fault-storage.component';
@NgModule({
imports: [
CommonModule,
FormsModule,
FaultManagerRoutingModule,
SharedModule,
],
declarations: [
RecordFaultComponent,
ListFaultsComponent,
TableViewComponent,
ListTasksComponent,
ConfirmFaultComponent,
ConfirmExpensiveFaultComponent,
RepairFaultComponent,
RepairCompleteComponent,
ReadonlyDisplayComponent,
FaultStorageComponent,
],
providers: [
ErrorCategoryService,
ErrorOriginService,
FacilityLocationService,
SolutionTimeIntervalService,
FaultManagerService,
FaultResolverService,
],
})
export class FaultManagerModule {
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { FaultManagerService } from './fault-manager.service';
describe('FaultManagerService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [FaultManagerService]
});
});
it('should be created', inject([FaultManagerService], (service: FaultManagerService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,215 @@
import { Injectable } from '@angular/core';
import { Observable } from "rxjs/Observable";
import { HttpClient, HttpHeaders, HttpRequest } from "@angular/common/http";
import { environment } from "../../environments/environment";
import { Fault } from "./shared/fault";
import { SimpleFault } from "./shared/simple-fault";
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from "@angular/router";
@Injectable()
export class FaultManagerService implements Resolve<Array<Fault>> {
private apiEndpoint = environment.apiUrl + '/api/fault';
private apiEndpointReject = environment.apiUrl + '/api/fault-reject';
private apiEndpointComment = environment.apiUrl + '/api/fault-comment';
private apiEndpointAttachment = environment.apiUrl + '/api/fault-attachment';
private cachedFaults: Array<Fault> = [];
constructor(private httpClient: HttpClient) {
}
/**
* Resolver for the route
*
* @param {ActivatedRouteSnapshot} route
* @param {RouterStateSnapshot} state
* @returns {Promise<Array<Fault>>}
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Array<Fault>> {
return this.list().toPromise();
}
/**
* List all faults
*
* @returns {Observable<Array<Fault>>}
*/
public list(): Observable<Array<Fault>> {
return this.httpClient.get<Array<Fault>>(this.apiEndpoint);
}
/**
* Create new fault
*
* @param {SimpleFault} fault
* @returns {Observable<Fault>}
*/
public create(fault: SimpleFault): Observable<Fault> {
return this.httpClient.post<Fault>(this.apiEndpoint, fault);
}
/**
* Update records in fault, not changing status
*
* @param {Fault} fault
* @returns {Observable<Fault>}
*/
public update(fault: Fault): Observable<Fault> {
return this.httpClient.put<Fault>(this.apiEndpoint + '/' + fault.id, this.flattenFault(fault));
}
/**
* Confirm a recorded fault
*
* @param {Fault} fault
* @returns {Observable<Fault>}
*/
public confirmFault(fault: Fault): Observable<Fault> {
fault.state = 'cnf';
return this.httpClient.put<Fault>(this.apiEndpoint + '/' + fault.id.toString(), this.flattenFault(fault));
}
/**
* Set status of the fault to high-expense, it has to go through an extra process of confirmation
*
* @param {Fault} fault
* @returns {Observable<Fault>}
*/
public setHighExpenseFault(fault: Fault): Observable<Fault> {
fault.state = 'exp';
return this.httpClient.put<Fault>(this.apiEndpoint + '/' + fault.id.toString(), this.flattenFault(fault));
}
/**
*
* @param {Fault} fault
* @returns {Observable<Fault>}
*/
public repairDone(fault: Fault): Observable<Fault> {
fault.state = 'wip';
return this.httpClient.put<Fault>(this.apiEndpoint + '/' + fault.id.toString(), this.flattenFault(fault));
}
/**
*
* @param {Fault} fault
* @returns {Observable<Fault>}
*/
public acknowledgeRepair(fault: Fault): Observable<Fault> {
fault.state = 'fin';
return this.httpClient.put<Fault>(this.apiEndpoint + '/' + fault.id.toString(), this.flattenFault(fault));
}
/**
*
* @param {number} id
* @param {string} comment
* @returns {Observable<Fault>}
*/
public addComment(id: number, comment: string): Observable<Fault> {
let payload = {
comment: comment,
};
return this.httpClient.post<Fault>(this.apiEndpointComment + '/' + id.toString(), payload);
}
/**
*
* @param {Fault} fault
* @param {string} state
* @param {string} reason
* @returns {Observable<Fault>}
*/
public reject(fault: Fault, state: string, reason: string): Observable<Fault> {
let payload = {
state: state,
__reason: reason,
};
return this.httpClient.post<Fault>(this.apiEndpointReject + '/' + fault.id, payload);
}
public attachPdf(faultId: number, files: FileList): Observable<Fault> {
return this.addFaultAttachments(faultId, files, 'expense');
}
public attachImages(faultId: number, files: FileList): Observable<Fault> {
return this.addFaultAttachments(faultId, files, 'image');
}
private addFaultAttachments(faultId: number, files: FileList, type: string): Observable<Fault> {
let fileForm = new FormData();
for (let i = 0; i < files.length; i++) {
fileForm.append('file[]', files[i]);
}
return this.httpClient.post<Fault>(this.apiEndpointAttachment + '/' + faultId + '/' + type, fileForm);
}
/**
* Change order of faults in the listing (server side)
*
* @todo implement this...
* @param {string} fieldName
*/
public changeOrder(fieldName: string) {
}
get faults(): Array<Fault> {
return this.cachedFaults;
}
set faults(faults: Array<Fault>) {
this.cachedFaults = faults;
}
get newFaults(): Array<Fault> {
return this.cachedFaults.filter(fault => fault.state == 'new');
}
get expFaults(): Array<Fault> {
return this.cachedFaults.filter(fault => fault.state == 'exp');
}
get cnfFaults(): Array<Fault> {
return this.cachedFaults.filter(fault => fault.state == 'cnf');
}
get wipFaults(): Array<Fault> {
return this.cachedFaults.filter(fault => fault.state == 'wip');
}
get finFaults(): Array<Fault> {
return this.cachedFaults.filter(fault => fault.state == 'fin');
}
/**
* Should exclude:
* - comments
*
* @param {Fault} fault
* @returns {SimpleFault}
*/
private flattenFault(fault: Fault): SimpleFault {
return {
id: fault.id,
workSheetNumber: fault.worksheetNumber,
errorOrigin: fault.errorOrigin.id,
errorCategory: fault.errorCategory.id,
solutionTimeInterval: fault.solutionTimeInterval.id,
facilityLocation: fault.facilityLocation.id,
facilityLocationDescription: fault.facilityLocationDescription,
errorDescription: fault.errorDescription,
reportAccepted: fault.reportAccepted ? fault.reportAccepted.date : null,
createdAt: fault.createdAt ? fault.createdAt.date : null,
state: fault.state,
workCostEstimate: fault.workCostEstimate,
materialCostEstimate: fault.materialCostEstimate,
usedMaterials: fault.usedMaterials,
worker: fault.worker ? fault.worker.id : null,
timeSpent: fault.timeSpent,
mustLowerCost: fault.mustLowerCost,
allocatedExpense: fault.allocatedExpense,
}
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { FaultResolverService } from './fault-resolver.service';
describe('FaultResolverService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [FaultResolverService]
});
});
it('should be created', inject([FaultResolverService], (service: FaultResolverService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from "@angular/router";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Observable";
import { Fault } from "./shared/fault";
import { environment } from "../../environments/environment";
@Injectable()
export class FaultResolverService implements Resolve<Fault> {
private apiEndpoint = environment.apiUrl + '/api/fault';
constructor(private httpClient: HttpClient) {
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Fault> {
return this.getFault(route.params['id']).toPromise().then(result => result ? result : false);
}
public getFault(id: number): Observable<Fault> {
return this.httpClient.get<Fault>(`${this.apiEndpoint}/${id}`);
}
}

View File

@ -0,0 +1,3 @@
.selectable {
cursor: pointer;
}

View File

@ -0,0 +1,24 @@
<div class="ui main container">
<h1 class="ui dividing header">{{pageHeader}}</h1>
<table *ngIf="faults?.length" class="ui celled table">
<thead>
<tr>
<th>Intézményrész</th>
<th>Hiba szakág</th>
<th class="collapsing">Javítás ideje</th>
<th>Kép</th>
</tr>
</thead>
<tbody>
<tr class="positive selectable" *ngFor="let fault of faults" [routerLink]="['/hiba/megjelenites', fault.id]">
<td><b>{{fault.facilityLocation.roomNumber}}</b> - {{fault.facilityLocation.name}}</td>
<td>{{fault.errorCategory.name}}</td>
<td class="collapsing">{{fault.solutionTimeInterval.name}}</td>
<td class="collapsing" [class.negative]="!hasImage(fault)">
<i class="icon" [class.checkmark]="hasImage(fault)" [class.close]="!hasImage(fault)"></i>
</td>
</tr>
</tbody>
</table>
<p *ngIf="faults?.length==0">A tároló üres</p>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FaultStorageComponent } from './fault-storage.component';
describe('FaultStorageComponent', () => {
let component: FaultStorageComponent;
let fixture: ComponentFixture<FaultStorageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FaultStorageComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FaultStorageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,63 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from "@angular/router";
import { Title } from "@angular/platform-browser";
import { FaultManagerService } from "../fault-manager.service";
import { Fault } from "../shared/fault";
@Component({
selector: 'app-fault-storage',
templateUrl: './fault-storage.component.html',
styleUrls: ['./fault-storage.component.css']
})
export class FaultStorageComponent implements OnInit {
private type: string = '';
constructor(private faultService: FaultManagerService,
private route: ActivatedRoute,
private titleService: Title) {
}
ngOnInit() {
this.route.params.subscribe((params: Params) => {
this.type = params['type'];
this.titleService.setTitle(this.pageHeader);
});
this.route.data.subscribe((data: { faults: Array<Fault> }) => this.faults = data.faults);
}
get pageHeader(): string {
switch (this.type) {
case 'lezart':
return 'Lezárt hibák';
case 'elutasitott':
return 'Elutasított hibák';
case 'torolt':
return 'Törölt hibák';
}
}
get typeMap(): string {
switch (this.type) {
case 'lezart':
return 'cls';
case 'elutasitott':
return 'rej';
case 'torolt':
return 'del';
}
}
get faults(): Array<Fault> {
return this.faultService.faults.filter(fault => fault.state == this.typeMap);
}
set faults(faults: Array<Fault>) {
this.faultService.faults = faults;
}
public hasImage(fault: Fault): boolean {
return fault.attachments.filter(attachment => attachment.type == 'image').length > 0;
}
}

View File

@ -0,0 +1,25 @@
<div class="ui main container">
<h1 class="ui dividing header">Hibák</h1>
<div class="ui raised segments">
<div app-table-view class="ui segment" [editor]="hasPermission('confirm')"
(actionClicked)="actionClickHandler($event)" [actions]="actionButtons['new']"
[faults]="newFaults" noRecordMessage="Nincs visszaigazolásra váró hiba."
heading="Visszaigazolásra váró hiba jelentések"></div>
<div app-table-view class="ui segment" [editor]="hasPermission('confirmExtraCost')"
(actionClicked)="actionClickHandler($event)" [actions]="actionButtons['exp']"
[faults]="expFaults" noRecordMessage="Nincs jóváhagyásra váró nagyértékű munka."
heading="Jóváhagyásra váró nagyértékű munkák"></div>
<div app-table-view class="ui segment" showWorksheetNumber="true" [editor]="hasPermission('repair')"
(actionClicked)="actionClickHandler($event)" [actions]="actionButtons['cnf']"
[faults]="cnfFaults" noRecordMessage="Nincs javításra váró hiba."
heading="Javításra váró hibák"></div>
<div app-table-view class="ui segment" showWorksheetNumber="true" [editor]="hasPermission('acknowledge')"
(actionClicked)="actionClickHandler($event)" [actions]="actionButtons['wip']"
[faults]="wipFaults" noRecordMessage="Nincs visszaigazolásra váró hibajavítás."
heading="Visszaigazolásra váró hibajavítások"></div>
<!--<div app-table-view class="ui segment" showWorksheetNumber="true" [editor]="hasPermission('close')"-->
<!--(actionClicked)="actionClickHandler($event)" [actions]="actionButtons['ack']"-->
<!--[faults]="finFaults" noRecordMessage="Nincs lezárt hibajavítás."-->
<!--heading="Lezárt hibajavítások"></div>-->
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ListFaultsComponent } from './list-faults.component';
describe('ListFaultsComponent', () => {
let component: ListFaultsComponent;
let fixture: ComponentFixture<ListFaultsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ListFaultsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ListFaultsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,95 @@
import { Component, OnInit } from '@angular/core';
import { Title } from "@angular/platform-browser";
import { ActivatedRoute } from "@angular/router";
import { Fault } from "../shared/fault";
import { FaultManagerService } from "../fault-manager.service";
import { RoleGuardService } from "../../auth/role-guard.service";
import { environment } from "../../../environments/environment";
@Component({
selector: 'app-list-faults',
templateUrl: './list-faults.component.html',
styleUrls: ['./list-faults.component.css']
})
export class ListFaultsComponent implements OnInit {
public actionButtons = {
'new': [{
'class': 'large orange link icon write',
'title': 'Visszaigazolás',
'route': '/hiba/visszaigazolas',
}],
'exp': [{
'class': 'large orange link icon write',
'title': 'Nagyértékű jóváhagyás',
'route': '/hiba/nagyerteku-jovahagyas',
}],
'cnf': [{
'class': 'large orange link icon write',
'title': 'Javítás,lezárás',
'route': '/hiba/javitas-lezaras',
}, {
'class': 'large blue link icon checked calendar',
'title': 'Hibajegy megnyitása új lapon',
'external': environment.apiUrl + '/hibajegy-pdf/:id',
}],
'wip': [{
'class': 'large orange link icon write',
'title': 'Elfogadás,visszaigazolás',
'route': '/hiba/elfogadas-visszaigazolas',
}],
'ack': [{
'class': 'large teal link zoom icon',
'title': 'Megtekintés',
'route': '/hiba-megjelenites',
}],
};
constructor(private faultManager: FaultManagerService,
private titleService: Title,
private route: ActivatedRoute,
private roleGuard: RoleGuardService) {
}
ngOnInit() {
this.titleService.setTitle('Webnapló : Hiba lista');
this.route.data.subscribe((data: { faults: Array<Fault> }) => this.faults = data.faults);
}
public actionClickHandler(e) {
}
public hasPermission(resourceName: string): boolean {
return this.roleGuard.userCanAccessResource(resourceName);
}
get faults(): Array<Fault> {
return this.faultManager.faults;
}
set faults(faults: Array<Fault>) {
this.faultManager.faults = faults;
}
get newFaults(): Array<Fault> {
return this.faultManager.newFaults;
}
get expFaults(): Array<Fault> {
return this.faultManager.expFaults;
}
get cnfFaults(): Array<Fault> {
return this.faultManager.cnfFaults;
}
get wipFaults(): Array<Fault> {
return this.faultManager.wipFaults;
}
get finFaults(): Array<Fault> {
return this.faultManager.finFaults;
}
}

View File

@ -0,0 +1,25 @@
<div class="ui main container">
<h1 class="ui dividing header">Feladatok</h1>
<div class="ui raised segments">
<div app-table-view class="ui segment" *ngIf="hasPermission('confirm')"
(actionClicked)="actionClickHandler($event)" [actions]="actionButtons['new']"
[faults]="newFaults" noRecordMessage="Nincs visszaigazolásra váró hiba."
heading="Visszaigazolásra váró hiba jelentések"></div>
<div app-table-view class="ui segment" *ngIf="hasPermission('confirmExtraCost')"
(actionClicked)="actionClickHandler($event)" [actions]="actionButtons['exp']"
[faults]="expFaults" noRecordMessage="Nincs jóváhagyásra váró nagyértékű munka."
heading="Jóváhagyásra váró nagyértékű munkák"></div>
<div app-table-view class="ui segment" showWorksheetNumber="true" *ngIf="hasPermission('repair')"
(actionClicked)="actionClickHandler($event)" [actions]="actionButtons['cnf']"
[faults]="cnfFaults" noRecordMessage="Nincs javításra váró hiba."
heading="Javításra váró hibák"></div>
<div app-table-view class="ui segment" showWorksheetNumber="true" *ngIf="hasPermission('acknowledge')"
(actionClicked)="actionClickHandler($event)" [actions]="actionButtons['wip']"
[faults]="wipFaults" noRecordMessage="Nincs visszaigazolásra váró hibajavítás."
heading="Visszaigazolásra váró hibajavítások"></div>
<!--<div app-table-view class="ui segment" showWorksheetNumber="true" *ngIf="hasPermission('close')"-->
<!--(actionClicked)="actionClickHandler($event)" [actions]="actionButtons['ack']"-->
<!--[faults]="finFaults" noRecordMessage="Nincs lezárt hibajavítás."-->
<!--heading="Lezárt hibajavítások"></div>-->
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ListTasksComponent } from './list-tasks.component';
describe('ListTasksComponent', () => {
let component: ListTasksComponent;
let fixture: ComponentFixture<ListTasksComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ListTasksComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ListTasksComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,112 @@
import { Component, OnInit } from '@angular/core';
import { Title } from "@angular/platform-browser";
import { ActivatedRoute } from "@angular/router";
import { Fault } from "../shared/fault";
import { FaultManagerService } from "../fault-manager.service";
import { RoleGuardService } from "../../auth/role-guard.service";
import { environment } from "../../../environments/environment";
import { DeviceGroup } from "../../maintenance/shared/device-group";
import { MaintenanceManagerService } from "../../maintenance/maintenance-manager.service";
@Component({
selector: 'app-list-tasks',
templateUrl: './list-tasks.component.html',
styleUrls: ['./list-tasks.component.css']
})
export class ListTasksComponent implements OnInit {
public actionButtons = {
'new': [{
'class': 'large orange link icon write',
'title': 'Visszaigazolás',
'route': '/hiba/visszaigazolas',
}],
'exp': [{
'class': 'large orange link icon write',
'title': 'Nagyértékű jóváhagyás',
'route': '/hiba/nagyerteku-jovahagyas',
}],
'cnf': [{
'class': 'large orange link icon write',
'title': 'Javítás,lezárás',
'route': '/hiba/javitas-lezaras',
}, {
'class': 'large blue link icon checked calendar',
'title': 'Hibajegy megnyitása új lapon',
'external': environment.apiUrl + '/hibajegy-pdf/:id',
}],
'wip': [{
'class': 'large orange link icon write',
'title': 'Elfogadás,visszaigazolás',
'route': '/hiba/elfogadas-visszaigazolas',
}],
'ack': [{
'class': 'large teal link zoom icon',
'title': 'Megtekintés',
'route': '/hiba-megjelenites',
}],
};
constructor(private faultManager: FaultManagerService,
// private maintenanceManager: MaintenanceManagerService,
private titleService: Title,
private route: ActivatedRoute,
private roleGuard: RoleGuardService) {
}
ngOnInit() {
this.titleService.setTitle('Webnapló : Feladat lista');
this.route.data.subscribe((data: {
faults: Array<Fault>,
// maintenances: Array<DeviceGroup>,
}) => {
this.faults = data.faults;
// this.maintenances = data.maintenances;
});
}
public actionClickHandler(e) {
}
public hasPermission(resourceName: string): boolean {
return this.roleGuard.userCanAccessResource(resourceName);
}
get faults(): Array<Fault> {
return this.faultManager.faults;
}
set faults(faults: Array<Fault>) {
this.faultManager.faults = faults;
}
// get maintenances(): Array<DeviceGroup> {
// return this.maintenanceManager.maintenances;
// }
//
// set maintenances(maintenances: Array<DeviceGroup>) {
// this.maintenanceManager.maintenances = maintenances;
// }
get newFaults(): Array<Fault> {
return this.faultManager.newFaults;
}
get expFaults(): Array<Fault> {
return this.faultManager.expFaults;
}
get cnfFaults(): Array<Fault> {
return this.faultManager.cnfFaults;
}
get wipFaults(): Array<Fault> {
return this.faultManager.wipFaults;
}
get finFaults(): Array<Fault> {
return this.faultManager.finFaults;
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { ErrorCategoryService } from './error-category.service';
describe('ErrorCategoryService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ErrorCategoryService]
});
});
it('should be created', inject([ErrorCategoryService], (service: ErrorCategoryService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from "@angular/router";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Observable";
import 'rxjs/Rx';
import { environment } from "../../../environments/environment";
import { ErrorCategory } from "../shared";
@Injectable()
export class ErrorCategoryService implements Resolve<Array<ErrorCategory>> {
private url = environment.apiUrl + '/api/error-category';
constructor(private httpService: HttpClient) {
}
public resolve(route: ActivatedRouteSnapshot): Promise<Array<ErrorCategory>> {
return this.getList().toPromise().then(result => result ? result : false);
}
public getList(): Observable<ErrorCategory[]> {
return this.httpService.get<ErrorCategory[]>(this.url);
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { ErrorOriginService } from './error-origin.service';
describe('ErrorOriginService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ErrorOriginService]
});
});
it('should be created', inject([ErrorOriginService], (service: ErrorOriginService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from "@angular/router";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Observable";
import 'rxjs/Rx';
import { environment } from "../../../environments/environment";
import { ErrorOrigin } from "../shared/error-origin";
@Injectable()
export class ErrorOriginService implements Resolve<Array<ErrorOrigin>> {
private url = environment.apiUrl + '/api/error-origin';
constructor(private httpService: HttpClient) {
}
public resolve(route: ActivatedRouteSnapshot): Promise<Array<ErrorOrigin>> {
return this.getList().toPromise().then(result => result ? result : false);
}
public getList(): Observable<ErrorOrigin[]> {
return this.httpService.get<ErrorOrigin[]>(this.url);
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { FacilityLocationService } from './facility-location.service';
describe('FacilityLocationService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [FacilityLocationService]
});
});
it('should be created', inject([FacilityLocationService], (service: FacilityLocationService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from "@angular/router";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Observable";
import 'rxjs/Rx';
import { environment } from "../../../environments/environment";
import { FacilityLocation } from "../shared";
@Injectable()
export class FacilityLocationService implements Resolve<Array<FacilityLocation>> {
private url = environment.apiUrl + '/api/facility-location';
constructor(private httpService: HttpClient) {
}
public resolve(route: ActivatedRouteSnapshot): Promise<Array<FacilityLocation>> {
return this.getList().toPromise().then(result => result ? result : false);
}
public getList(): Observable<Array<FacilityLocation>> {
return this.httpService.get<FacilityLocation[]>(this.url);
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { SolutionTimeIntervalService } from './solution-time-interval.service';
describe('SolutionTimeIntervalService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [SolutionTimeIntervalService]
});
});
it('should be created', inject([SolutionTimeIntervalService], (service: SolutionTimeIntervalService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from "@angular/router";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Observable";
import 'rxjs/Rx';
import { environment } from "../../../environments/environment";
import { SolutionTimeInterval } from "../shared/solution-time-interval";
@Injectable()
export class SolutionTimeIntervalService implements Resolve<Array<SolutionTimeInterval>> {
private url = environment.apiUrl + '/api/solution-time-interval';
constructor(private httpService: HttpClient) {
}
public resolve(route: ActivatedRouteSnapshot): Promise<Array<SolutionTimeInterval>> {
return this.getList().toPromise().then(result => result ? result : false);
}
public getList(): Observable<SolutionTimeInterval[]> {
return this.httpService.get<SolutionTimeInterval[]>(this.url);
}
}

View File

@ -0,0 +1,105 @@
<div class="ui main container">
<h1 class="ui dividing header">Hiba megtekintése</h1>
<div class="ui segment">
<table class="ui inverted red table" *ngIf="isDeleted">
<thead>
<tr>
<th>Törölt bejelentés</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{snapshotReason}}</td>
</tr>
</tbody>
</table>
<table class="ui inverted orange table" *ngIf="isRejected">
<thead>
<tr>
<th>Elutasított bejelentés</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{snapshotReason}}</td>
</tr>
</tbody>
</table>
<ng-container *ngIf="fault?.comments.length">
<h4 class="ui dividing header">Megjegyzések</h4>
<div class="ui divided items">
<div class="item" *ngFor="let comm of fault.comments">
<div class="middle aligned content">
<div class="header">
<p>{{comm.user.name}} - {{comm.createdAt.date|date:"y-MM-dd HH:mm"}}</p>
</div>
<div class="description">
{{comm.comment}}
</div>
</div>
</div>
</div>
</ng-container>
<h4 class="ui dividing header">Hiba adatai</h4>
<table class="ui red definition table">
<tr *ngIf="fault?.worksheetNumber">
<td class="collapsing">Munkalap száma</td>
<td>{{fault?.worksheetNumber}}</td>
</tr>
<tr>
<td class="collapsing">Intézményrész</td>
<td>{{fault?.facilityLocation?.name}}</td>
</tr>
<tr *ngIf="fault?.facilityLocationDescription">
<td class="collapsing">Intézményrész pontosítva</td>
<td>{{fault?.facilityLocationDescription}}</td>
</tr>
<tr>
<td class="collapsing">Hiba szakág</td>
<td>{{fault?.errorCategory?.name}}</td>
</tr>
<tr>
<td class="collapsing">Hiba eredet</td>
<td>{{fault?.errorOrigin?.name}}</td>
</tr>
<tr *ngIf="fault?.errorDescription">
<td class="collapsing">Hibaleírás</td>
<td>{{fault?.errorDescription}}</td>
</tr>
<tr>
<td class="collapsing">Hibajavítás megkezdése</td>
<td>{{fault?.solutionTimeInterval?.name}}</td>
</tr>
<tr *ngIf="!canUploadImage && faultImages?.length">
<td class="collapsing">Képek</td>
<td>
<a *ngFor="let image of faultImages; let idx = index" [href]="imageUrl(image.id)"
target="_blank"><img class="ui image" [src]="imageUrl(image.id)" height="200"/></a>
</td>
</tr>
</table>
<form [hidden]="!canUploadImage" class="ui form" #attachmentForm>
<h4 class="ui dividing header" *ngIf="imageAttachments.length">Képek</h4>
<div class="field">
<input #photoUpload type="file" accept="image/*" multiple/>
</div>
<div class="field">
<button class="ui button" [class.positive]="canAttach" [class.disabled]="!canAttach" tabindex="0"
type="button" (click)="attachImages()">Kép feltöltése
</button>
</div>
<div #photoAttachments class="photoAttachments" class="ui bordered images segment"
*ngIf="imageAttachments.length || rawImageData.length">
<a *ngFor="let image of imageAttachments; let idx = index" [href]="imageUrl(image.id)"
target="_blank"><img
class="ui image" [src]="imageUrl(image.id)" height="200"/></a>
<img class="ui image" *ngFor="let image of rawImageData; let idx = index" [src]="image" height="200"/>
</div>
<h4 class="ui dividing header"></h4>
</form>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ReadonlyDisplayComponent } from './readonly-display.component';
describe('ReadonlyDisplayComponent', () => {
let component: ReadonlyDisplayComponent;
let fixture: ComponentFixture<ReadonlyDisplayComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ReadonlyDisplayComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ReadonlyDisplayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,93 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from "@angular/router";
import { Title } from "@angular/platform-browser";
import { environment } from "../../../environments/environment";
import { Fault } from "../shared/fault";
import { AuthService } from "../../auth/auth.service";
import { FaultManagerService } from "../fault-manager.service";
@Component({
selector: 'app-readonly-display',
templateUrl: './readonly-display.component.html',
styleUrls: ['./readonly-display.component.css']
})
export class ReadonlyDisplayComponent implements OnInit {
@ViewChild('photoUpload') photoUpload: ElementRef;
@ViewChild('photoAttachments') photoAttachments: ElementRef;
private attachmentUrl = environment.apiUrl + '/show-attachment';
public rawImageData: Array<any> = [];
public submitInProgress: boolean = false;
public fault: Fault;
constructor(private route: ActivatedRoute,
private faultManager: FaultManagerService,
private titleService: Title,
private authService: AuthService) {
}
ngOnInit() {
this.titleService.setTitle('Webnaplo : Hiba megtekintése');
this.route.data.subscribe((data: { fault: Fault }) => this.fault = data.fault);
jQuery(this.photoUpload.nativeElement).on('change', changeEvent => {
this.rawImageData = [];
for (let i = 0; i < this.photoUpload.nativeElement.files.length; i++) {
if (!/\.(jpe?g|png|gif)$/i.test(this.photoUpload.nativeElement.files[i].name)) {
continue;
}
let reader = new FileReader();
reader.addEventListener("load", () => {
this.rawImageData.push(reader.result);
});
reader.readAsDataURL(this.photoUpload.nativeElement.files[i]);
}
});
}
get snapshotReason(): string {
return '';
}
get isRejected(): boolean {
return this.fault.state == 'rej';
}
get isDeleted(): boolean {
return this.fault.state == 'del';
}
get faultImages() {
return this.fault.attachments.filter(attachment => attachment.type == 'image');
}
public imageUrl(image: number): string {
return this.attachmentUrl + '/' + image;
}
get canUploadImage(): boolean {
let roles = this.authService.tokenData.roles;
return this.fault.state == 'wip' && roles.indexOf('projektvezeto') != -1;
}
get imageAttachments() {
return this.fault.attachments.filter(attachment => attachment.type == 'image');
}
get canAttach(): boolean {
return this.photoUpload.nativeElement.files.length > 0
&& !this.submitInProgress;
}
public attachImages() {
this.submitInProgress = true;
this.faultManager.attachImages(this.fault.id, this.photoUpload.nativeElement.files)
.finally(() => this.submitInProgress = false)
.subscribe(
attRes => this.photoUpload.nativeElement.value = null,
attErr => alert("Hiba történt a képfeltöltés közben.")
);
}
}

View File

@ -0,0 +1,7 @@
.ui.selection.dropdown .menu > .item.depth2 {
padding: 0.78571429rem 2.5rem !important;
}
.ui.selection.dropdown .menu > .item.depth3 {
padding: 0.78571429rem 3.5rem !important;
}

View File

@ -0,0 +1,96 @@
<div class="ui main container">
<h1 class="ui dividing header">Új hiba felvétele</h1>
<div class="ui segment">
<div class="ui dimmer" [class.active]="submitInProgress">
<div class="ui indeterminate text loader">Mentés</div>
</div>
<form class="ui form" (ngSubmit)="doSubmit()">
<h4 class="ui dividing header">Hiba helye</h4>
<div class="field" [class.error]="noFacilityLocation">
<label>Intézményrész</label>
<div class="ui fluid search selection dropdown" #facilityLocation>
<input type="hidden" name="facilityLocation" #facilityLocationId>
<i class="dropdown icon"></i>
<div class="default text">Válasszon</div>
<div class="menu">
<div class="item {{flClass(facilityLocation)}}" [class.disabled]="facilityLocation.level==2"
*ngFor="let facilityLocation of facilityLocations"
[attr.data-value]="facilityLocation?.id">
<strong>{{facilityLocation?.roomNumber}}</strong> - {{facilityLocation?.name}}
</div>
</div>
</div>
</div>
<div class="field" [class.error]="noFacilityLocationDescription">
<label>Intézményrész pontosítása</label>
<textarea rows="3"
placeholder=""
#facilityLocationDescription
name="facilityLocationDescription"
[(ngModel)]="facilityLocationText"></textarea>
</div>
<h4 class="ui dividing header">Hiba</h4>
<div class="two fields">
<div class="field" [class.error]="noErrorCategory">
<label>Hiba szakág</label>
<app-semantic-dropdown
[(ngModel)]="errorCategory"
[data]="errorCategories"
valueField="id"
textField="name"
name="errorCategoryId"
placeHolder="Válasszon"></app-semantic-dropdown>
</div>
<div class="field" [class.error]="noErrorOrigin">
<label>Hiba eredet</label>
<app-semantic-dropdown
[(ngModel)]="errorOrigin"
[data]="errorOrigins"
valueField="id"
textField="name"
name="errorOriginId"
placeHolder="Válasszon"></app-semantic-dropdown>
</div>
</div>
<div class="field" [class.error]="noErrorDescription">
<label>Hibaleírás</label>
<textarea rows="3"
placeholder=""
#errorDescription
name="errorDescription"
[(ngModel)]="errorDescriptionText"></textarea>
</div>
<h4 class="ui dividing header">Hiba javítás ideje</h4>
<div class="field" [class.error]="noSolutionTimeInterval">
<label>Hibajavítás megkezdése</label>
<app-semantic-dropdown
[(ngModel)]="solutionTimeInterval"
[data]="solutionTimeIntervals"
valueField="id"
textField="name"
name="solutionTimeIntervalId"
placeHolder="Válasszon"></app-semantic-dropdown>
</div>
<h4 class="ui dividing header">Csatolmányok</h4>
<div class="field" [class.error]="noAttachments">
<input #photoUpload type="file" accept="image/*" multiple/>
</div>
<div #photoAttachments class="photoAttachments" class="ui bordered images segment"
*ngIf="rawImageData?.length">
<img class="ui image" *ngFor="let image of rawImageData; let idx = index" [src]="image" height="200"/>
</div>
<h4 class="ui dividing header"></h4>
<div class="ui error message" [class.visible]="!hasNoFormError">
<p>A pirossal jelölt mezők kitöltése kötelező.</p>
</div>
<button class="ui button"
[class.positive]="canSubmit"
[class.disabled]="!canSubmit"
tabindex="0" type="submit">Rögzítés</button>
</form>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RecordFaultComponent } from './record-fault.component';
describe('RecordFaultComponent', () => {
let component: RecordFaultComponent;
let fixture: ComponentFixture<RecordFaultComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RecordFaultComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RecordFaultComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,156 @@
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from "@angular/router";
import { ErrorCategory } from "../shared/error-category";
import { ErrorOrigin } from "../shared/error-origin";
import { FacilityLocation } from "../shared/facility-location";
import { SolutionTimeInterval } from "../shared/solution-time-interval";
import { FaultManagerService } from "../fault-manager.service";
import { Fault } from "../shared/fault";
import { SimpleFault } from "../shared/simple-fault";
import { Title } from "@angular/platform-browser";
@Component({
selector: 'app-record-fault',
templateUrl: './record-fault.component.html',
styleUrls: ['./record-fault.component.css']
})
export class RecordFaultComponent implements OnInit, AfterViewInit {
@ViewChild('facilityLocation') facilityLocationElement: ElementRef;
@ViewChild('facilityLocationId') facilityLocationIdElement: ElementRef;
@ViewChild('photoUpload') photoUpload: ElementRef;
@ViewChild('photoAttachments') photoAttachments: ElementRef;
public submitInProgress: boolean = false;
public facilityLocationText: string = '';
public errorCategory: ErrorCategory;
public errorOrigin: ErrorOrigin;
public errorDescriptionText: string = '';
public solutionTimeInterval: SolutionTimeInterval;
public errorCategories: Array<ErrorCategory> = [];
public errorOrigins: Array<ErrorOrigin> = [];
public facilityLocations: Array<FacilityLocation> = [];
public solutionTimeIntervals: Array<SolutionTimeInterval> = [];
public rawImageData: Array<any> = [];
constructor(private activatedRoute: ActivatedRoute,
private router: Router,
private faultService: FaultManagerService,
private titleService: Title) {
}
ngOnInit() {
this.titleService.setTitle("Webnapló : Hiba rögzítése");
this.activatedRoute.data.subscribe((data: {
errorCategories: Array<ErrorCategory>,
errorOrigins: Array<ErrorOrigin>,
facilityLocations: Array<FacilityLocation>,
solutionTimeIntervals: Array<SolutionTimeInterval>,
}) => {
this.errorCategories = data.errorCategories;
this.errorOrigins = data.errorOrigins;
this.facilityLocations = data.facilityLocations;
this.solutionTimeIntervals = data.solutionTimeIntervals;
});
jQuery(this.photoUpload.nativeElement).on('change', changeEvent => {
this.rawImageData = [];
for (let i = 0; i < this.photoUpload.nativeElement.files.length; i++) {
if (!/\.(jpe?g|png|gif)$/i.test(this.photoUpload.nativeElement.files[i].name)) {
continue;
}
let reader = new FileReader();
reader.addEventListener("load", () => {
this.rawImageData.push(reader.result);
});
reader.readAsDataURL(this.photoUpload.nativeElement.files[i]);
}
});
}
ngAfterViewInit(): void {
jQuery(this.facilityLocationElement.nativeElement).dropdown();
}
public flClass(fl: FacilityLocation) {
return 'depth' + (fl.level - 1).toString();
}
public doSubmit() {
this.submitInProgress = true;
let fault: SimpleFault = {
// id: null,
facilityLocation: parseInt(this.facilityLocationIdElement.nativeElement.value),
facilityLocationDescription: this.facilityLocationText,
errorCategory: this.errorCategory.id,
errorOrigin: this.errorOrigin.id,
errorDescription: this.errorDescriptionText,
solutionTimeInterval: this.solutionTimeInterval.id
};
this.faultService.create(fault).subscribe(fault => {
if (this.photoUpload.nativeElement.files.length > 0) {
this.faultService.attachImages(fault.id, this.photoUpload.nativeElement.files).finally(
() => this.submitInProgress = false
).subscribe(
attRes => this.router.navigate(['/hiba/feladat-lista']),
attErr => {
console.log("File upload error");
alert("Hiba történt a kép feltöltésnél");
this.router.navigate(['/hiba/feladat-lista']);
}
);
} else {
this.router.navigate(['/hiba/feladat-lista']);
}
});
}
get hasNoFormError(): boolean {
return [
this.facilityLocationIdElement.nativeElement.value,
this.facilityLocationText,
this.errorDescriptionText,
].every(item => item.trim().length > 0) && [
this.facilityLocationText,
this.errorCategory,
this.errorOrigin,
this.errorDescriptionText,
this.solutionTimeInterval
].every(item => typeof item !== 'undefined');
// && this.photoUpload.nativeElement.files.length > 0;
}
get canSubmit(): boolean {
return this.hasNoFormError && !this.submitInProgress;
}
get noFacilityLocation(): boolean {
return this.facilityLocationIdElement.nativeElement.value.trim().length == 0;
}
get noFacilityLocationDescription(): boolean {
return this.facilityLocationText.trim().length == 0;
}
get noErrorCategory(): boolean {
return this.errorCategory == null;
}
get noErrorOrigin(): boolean {
return this.errorOrigin == null;
}
get noErrorDescription(): boolean {
return this.errorDescriptionText.trim().length == 0;
}
get noSolutionTimeInterval(): boolean {
return this.solutionTimeInterval == null;
}
get noAttachments(): boolean {
return this.rawImageData.length == 0;
}
}

View File

@ -0,0 +1,108 @@
<div class="ui main container">
<h1 class="ui dividing header">{{fault.facilityLocation.roomNumber}} - {{fault.facilityLocation.name}}</h1>
<div class="ui segment">
<div class="ui dimmer" [class.active]="submitInProgress">
<div class="ui indeterminate text loader">Mentés</div>
</div>
<form class="ui form" #confirmForm>
<h4 class="ui dividing header">Javítás visszaigazolása</h4>
<div class="field">
<label>Elutasítás indoklása</label>
<textarea rows="3"
placeholder=""
name="rejectReason"
[(ngModel)]="rejectReason"></textarea>
</div>
<button class="ui button"
[class.negative]="canReject"
[class.disabled]="!canReject"
(click)="reject()">Elutasítás</button>
<button class="ui button"
[class.positive]="!submitInProgress"
[class.disabled]="submitInProgress"
(click)="confirm()">Visszaigazolás</button>
<h4 class="ui dividing header" *ngIf="fault.worker && fault.usedMaterials.length">Javítás adatai</h4>
<table class="ui olive celled striped table" *ngIf="fault.worker">
<thead>
<tr>
<th>Karbantartó</th>
<th>Munkaóra</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{fault.worker.name}}</td>
<td>{{fault.timeSpent}}</td>
</tr>
</tbody>
</table>
<table class="ui teal celled striped table" *ngIf="fault.usedMaterials.length">
<thead>
<tr>
<th>Felhasznált anyag neve</th>
<th>Felhasznált mennyiség</th>
<th>Nettó egységár</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let material of fault.usedMaterials">
<td>{{material.name}}</td>
<td>{{material.amount}}{{material.unit}}</td>
<td>{{material.nettPrice|currencyFormat}}</td>
</tr>
</tbody>
</table>
<h4 class="ui dividing header">Hiba adatai</h4>
<table class="ui red definition table">
<tr>
<td class="collapsing">Becsült munkaköltség</td>
<td>{{fault.workCostEstimate|currencyFormat}}</td>
</tr>
<tr>
<td class="collapsing">Becsült alapanyagköltség</td>
<td>{{numericMaterialCostEstimate|currencyFormat}}</td>
</tr>
<tr *ngIf="hasDetailedCalculation">
<td class="collapsing">Részletes költségvetés</td>
<td><a [href]="expensePdfDownloadUrl" target="_blank"><i class="file pdf outline icon"></i>Letöltés</a></td>
</tr>
<tr>
<td class="collapsing">Hiba helye</td>
<td><b>{{fault.facilityLocation.roomNumber}}</b>-{{fault.facilityLocation.name}}</td>
</tr>
<tr>
<td class="collapsing">Hiba helyének részletes leírása</td>
<td>{{fault.facilityLocationDescription}}</td>
</tr>
<tr>
<td class="collapsing">Hiba szakág</td>
<td>{{fault.errorCategory.name}}</td>
</tr>
<tr>
<td class="collapsing">Hiba leírás</td>
<td>{{fault.errorDescription}}</td>
</tr>
<tr>
<td class="collapsing">Hiba eredete</td>
<td>{{fault.errorOrigin.name}}</td>
</tr>
<tr>
<td class="collapsing">Hibajavítás sürgőssége</td>
<td>{{fault.solutionTimeInterval.name}}</td>
</tr>
</table>
<h4 class="ui dividing header" *ngIf="imageAttachments?.length">Csatolmányok</h4>
<div #photoAttachments class="photoAttachments" class="ui bordered images segment"
*ngIf="imageAttachments?.length">
<a *ngFor="let image of imageAttachments; let idx = index" [href]="imageUrl(image.id)"
target="_blank"><img
class="ui image" [src]="imageUrl(image.id)" height="200"/></a>
</div>
<h4 class="ui dividing header"></h4>
</form>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RepairCompleteComponent } from './repair-complete.component';
describe('RepairCompleteComponent', () => {
let component: RepairCompleteComponent;
let fixture: ComponentFixture<RepairCompleteComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RepairCompleteComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RepairCompleteComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,76 @@
import { Component, OnInit } from '@angular/core';
import { environment } from "../../../environments/environment";
import { Fault } from "../shared/fault";
import { FaultManagerService } from "../fault-manager.service";
import { Title } from "@angular/platform-browser";
import { ActivatedRoute, Router } from "@angular/router";
@Component({
selector: 'app-repair-complete',
templateUrl: './repair-complete.component.html',
styleUrls: ['./repair-complete.component.css']
})
export class RepairCompleteComponent implements OnInit {
private attachmentUrl = environment.apiUrl + '/show-attachment';
public fault: Fault = null;
public rejectReason: string = '';
public submitInProgress: boolean = false;
constructor(private faultManager: FaultManagerService,
private titleService: Title,
private route: ActivatedRoute,
private router: Router) {
}
ngOnInit() {
this.titleService.setTitle('Webnapló : Javítás visszaigazolása');
this.route.data.subscribe((data: { fault: Fault }) => this.fault = data.fault);
}
public confirm() {
this.submitInProgress = true;
this.faultManager.acknowledgeRepair(this.fault)
.finally(() => this.submitInProgress = false)
.subscribe(() => this.router.navigate(['/hiba/feladat-lista']));
}
public reject() {
this.submitInProgress = true;
this.faultManager.reject(this.fault, 'cnf', this.rejectReason)
.finally(() => this.submitInProgress = false)
.subscribe(() => this.router.navigate(['/hiba/feladat-lista']));
}
get hasDetailedCalculation(): boolean {
return this.fault.workCostEstimate > 100000
|| this.fault.materialCostEstimate > 200000;
}
get expensePdfDownloadUrl(): string {
let expensePdfId = this.fault.attachments.filter(attachment => attachment.type == 'expense');
if (expensePdfId.length > 0) {
return this.attachmentUrl + '/' + expensePdfId[0].id;
}
return "";
}
get imageAttachments() {
return this.fault.attachments.filter(attachment => attachment.type == 'image');
}
public imageUrl(image: number): string {
return this.attachmentUrl + '/' + image;
}
get canReject(): boolean {
return this.rejectReason.length > 0
&& !this.submitInProgress;
}
get numericMaterialCostEstimate(): number {
return this.fault.materialCostEstimate
? this.fault.materialCostEstimate
: 0;
}
}

View File

@ -0,0 +1,190 @@
<div class="ui main container">
<h1 class="ui dividing header">{{fault.facilityLocation.roomNumber}} - {{fault.facilityLocation.name}}</h1>
<div class="ui segment">
<div class="ui dimmer" [class.active]="submitInProgress">
<div class="ui indeterminate text loader">Mentés</div>
</div>
<form class="ui form" #commentForm>
<h4 class="ui dividing header">Komunikáció</h4>
<div class="field">
<label>Hozzászólás</label>
<textarea rows="3" name="disApproveNote" placeholder="" [(ngModel)]="comment"></textarea>
</div>
<div class="field">
<button class="ui button" [class.primary]="canComment" [class.disabled]="!canComment"
(click)="addComment()">Küldés
</button>
</div>
<div class="ui divided items" *ngIf="comments.length">
<div class="item" *ngFor="let comm of comments">
<div class="middle aligned content">
<div class="header">
{{comm.comment}}
</div>
<div class="description">
<p>{{comm.user.name}} - {{comm.createdAt.date|date:"y-MM-dd HH:mm"}}</p>
</div>
</div>
</div>
</div>
<br>
</form>
<ng-template [ngIf]="hasPermission('finishRepair')">
<form class="ui form" #confirmForm>
<h4 class="ui dividing header">Hiba javítás befejezve, jóváhagyás</h4>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="statusAccepted" name="statusAccepted" [(ngModel)]="statusAccepted">
<label for="statusAccepted">Hibajavítás kész</label>
</div>
</div>
<div class="field">
<button class="ui button" [class.positive]="statusAccepted" [class.disabled]="!statusAccepted"
(click)="confirmRepair()">Jóváhagyás
</button>
</div>
<br>
</form>
<form class="ui form" #costForm>
<h4 class="ui dividing header">Felhasznált anyagok</h4>
<div class="five fields">
<div class="one wide field">
<button class="ui fluid icon button" [class.disabled]="!canAddMaterial"
[class.positive]="canAddMaterial" (click)="addMaterial()"><i class="plus icon"></i>
</button>
</div>
<div class="seven wide field">
<input type="text" name="materialName" #materialName [(ngModel)]="materialModel.name"
placeholder="Anyag megnevezése">
</div>
<div class="four wide field">
<input type="number" name="materialAmount" #materialAmount [(ngModel)]="materialModel.amount"
placeholder="Felhasznált mennyiség">
</div>
<div class="two wide field">
<input type="text" name="materialUnit" #materialUnit [(ngModel)]="materialModel.unit"
placeholder="Egység">
</div>
<div class="four wide field">
<input type="number" name="materialPrice" #materialPrice [(ngModel)]="materialModel.nettPrice"
placeholder="Netto egységár">
</div>
</div>
<div class="two fields">
<div class="two wide field">
<button class="ui fluid button" [class.positive]="canSaveWorkData"
[class.disabled]="!canSaveWorkData"
(click)="saveWorkData()">Mentés
</button>
</div>
<div class="three wide field">
<input type="number" name="timeSpent" #timeSpent [(ngModel)]="fault.timeSpent"
(change)="doCostChange()"
placeholder="Felhasznált munkaóra">
</div>
<div class="eleven wide field">
<app-semantic-dropdown
[(ngModel)]="fault.worker"
[data]="workers"
valueField="id"
textField="name"
name="worker"
placeHolder="Válasszon"></app-semantic-dropdown>
</div>
</div>
<table class="ui celled table" *ngIf="fault.usedMaterials?.length">
<thead>
<tr>
<th></th>
<th>Anyag megnevezés</th>
<th class="right aligned">Mennyiség</th>
<th class="right aligned">Netto egység ár</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let material of fault.usedMaterials; let idx=index">
<td class="collapsing selectable"><a title="Törlés" (click)="removeMaterial(idx)"><i
class="red trash icon"></i></a></td>
<td>{{material.name}}</td>
<td class="right aligned">{{material.amount}}</td>
<td class="right aligned">{{material.nettPrice|currencyFormat}}</td>
</tr>
</tbody>
<tfoot>
<tr>
<th></th>
<th></th>
<th></th>
<th class="right aligned">{{materialSumPrice|currencyFormat}}</th>
</tr>
</tfoot>
</table>
</form>
</ng-template>
<h4 class="ui dividing header">Hiba adatai</h4>
<table class="ui red definition table">
<tr>
<td class="collapsing">Becsült munkaköltség</td>
<td>{{fault.workCostEstimate|currencyFormat}}</td>
</tr>
<tr>
<td class="collapsing">Becsült alapanyagköltség</td>
<td>{{numericMaterialCostEstimate|currencyFormat}}</td>
</tr>
<tr *ngIf="hasDetailedCalculation">
<td class="collapsing">Részletes költségvetés</td>
<td><a [href]="expensePdfDownloadUrl" target="_blank"><i class="file pdf outline icon"></i>Letöltés</a>
</td>
</tr>
<tr>
<td class="collapsing">Hiba helye</td>
<td><b>{{fault.facilityLocation.roomNumber}}</b>-{{fault.facilityLocation.name}}</td>
</tr>
<tr>
<td class="collapsing">Hiba helyének részletes leírása</td>
<td>{{fault.facilityLocationDescription}}</td>
</tr>
<tr>
<td class="collapsing">Hiba szakág</td>
<td>{{fault.errorCategory.name}}</td>
</tr>
<tr>
<td class="collapsing">Hiba leírás</td>
<td>{{fault.errorDescription}}</td>
</tr>
<tr>
<td class="collapsing">Hiba eredete</td>
<td>{{fault.errorOrigin.name}}</td>
</tr>
<tr>
<td class="collapsing">Hibajavítás sürgőssége</td>
<td>{{fault.solutionTimeInterval.name}}</td>
</tr>
</table>
<form class="ui form" #attachmentForm>
<h4 class="ui dividing header" *ngIf="imageAttachments.length">Csatolmányok</h4>
<div class="field">
<input #photoUpload type="file" accept="image/*" multiple/>
</div>
<div class="field">
<button class="ui button" [class.positive]="canAttach" [class.disabled]="!canAttach" tabindex="0"
type="button" (click)="attachImages()">Kép feltöltése
</button>
</div>
<div #photoAttachments class="photoAttachments" class="ui bordered images segment"
*ngIf="imageAttachments.length || rawImageData.length">
<a *ngFor="let image of imageAttachments; let idx = index" [href]="imageUrl(image.id)"
target="_blank"><img
class="ui image" [src]="imageUrl(image.id)" height="200"/></a>
<img class="ui image" *ngFor="let image of rawImageData; let idx = index" [src]="image" height="200"/>
</div>
<h4 class="ui dividing header"></h4>
</form>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RepairFaultComponent } from './repair-fault.component';
describe('RepairFaultComponent', () => {
let component: RepairFaultComponent;
let fixture: ComponentFixture<RepairFaultComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RepairFaultComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RepairFaultComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,186 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { Title } from "@angular/platform-browser";
import { ActivatedRoute, Router } from "@angular/router";
import { environment } from "../../../environments/environment";
import { FaultManagerService } from "../fault-manager.service";
import { RoleGuardService } from "../../auth/role-guard.service";
import { Fault } from "../shared/fault";
import { UsedMaterial } from "../shared/used-material";
import { User } from "../../user/shared/user";
import { Comment } from "../shared/comment";
@Component({
selector: 'app-repair-fault',
templateUrl: './repair-fault.component.html',
styleUrls: ['./repair-fault.component.css']
})
export class RepairFaultComponent implements OnInit {
@ViewChild('photoUpload') photoUpload: ElementRef;
@ViewChild('photoAttachments') photoAttachments: ElementRef;
private attachmentUrl = environment.apiUrl + '/show-attachment';
public comments: Array<Comment> = [];
public fault: Fault = null;
public comment: string = '';
public statusAccepted: boolean = false;
public submitInProgress: boolean = false;
public costChanged: boolean = false;
public rawImageData: Array<any> = [];
public materialModel: UsedMaterial = new UsedMaterial();
public workers: Array<User>;
constructor(private faultManager: FaultManagerService,
private titleService: Title,
private route: ActivatedRoute,
private router: Router,
private roleGuard: RoleGuardService) {
}
ngOnInit() {
this.titleService.setTitle('Webnapló : Hibajavítás, lezárás');
this.route.data.subscribe((data: {
fault: Fault,
users: Array<User>,
}) => {
this.fault = data.fault;
this.comments = data.fault.comments.reverse();
this.workers = data.users.filter(user => user.roles.indexOf('karbantarto') != -1);
});
jQuery(this.photoUpload.nativeElement).on('change', changeEvent => {
this.rawImageData = [];
for (let i = 0; i < this.photoUpload.nativeElement.files.length; i++) {
if (!/\.(jpe?g|png|gif)$/i.test(this.photoUpload.nativeElement.files[i].name)) {
continue;
}
let reader = new FileReader();
reader.addEventListener("load", () => {
this.rawImageData.push(reader.result);
});
reader.readAsDataURL(this.photoUpload.nativeElement.files[i]);
}
});
}
public hasPermission(resource: string): boolean {
return this.roleGuard.userCanAccessResource(resource);
}
get canComment(): boolean {
return this.comment.trim().length > 0
&& !this.submitInProgress;
}
public addComment() {
this.submitInProgress = true;
this.faultManager.addComment(this.fault.id, this.comment.trim())
.finally(() => this.submitInProgress = false)
.subscribe(fault => {
this.fault = fault;
this.comments = fault.comments.reverse();
this.comment = '';
});
}
public attachImages() {
this.submitInProgress = true;
this.faultManager.attachImages(this.fault.id, this.photoUpload.nativeElement.files)
.finally(() => this.submitInProgress = false)
.subscribe(
attRes => this.photoUpload.nativeElement.value = null,
attErr => alert("Hiba történt a képfeltöltés közben.")
);
}
public confirmRepair() {
this.submitInProgress = true;
this.faultManager.repairDone(this.fault)
.finally(() => this.submitInProgress = false)
.subscribe(() => this.router.navigate(['/hiba/feladat-lista']));
}
get expensePdfDownloadUrl(): string {
let expensePdfId = this.fault.attachments.filter(attachment => attachment.type == 'expense');
if (expensePdfId.length > 0) {
return this.attachmentUrl + '/' + expensePdfId[0].id;
}
return "";
}
get imageAttachments() {
return this.fault.attachments.filter(attachment => attachment.type == 'image');
}
public imageUrl(image: number): string {
return this.attachmentUrl + '/' + image;
}
get hasDetailedCalculation(): boolean {
return this.fault.workCostEstimate > 100000
|| this.fault.materialCostEstimate > 200000;
}
get canAttach(): boolean {
return this.photoUpload.nativeElement.files.length > 0
&& !this.submitInProgress;
}
get canAddMaterial(): boolean {
return [
this.materialModel.nettPrice,
this.materialModel.amount,
].every(item => item > 0) && [
this.materialModel.name,
this.materialModel.unit
].every(item => item.trim().length > 0)
&& !this.submitInProgress;
}
public doCostChange() {
this.costChanged = true;
}
public addMaterial() {
this.costChanged = true;
this.fault.usedMaterials.push(this.materialModel);
this.materialModel = new UsedMaterial();
}
public removeMaterial(idx: number) {
this.costChanged = true;
this.fault.usedMaterials.splice(idx, 1);
}
get canSaveWorkData(): boolean {
return (this.fault.usedMaterials.length > 0
|| this.fault.worker != null
|| this.fault.timeSpent > 0)
&& this.costChanged
&& !this.submitInProgress;
}
saveWorkData() {
this.submitInProgress = true;
this.faultManager.update(this.fault)
.finally(() => {
this.submitInProgress = false;
this.costChanged = false;
})
.subscribe(fault => this.fault = fault);
}
get materialSumPrice(): number {
return this.fault.usedMaterials.reduce((sum, material) => sum + material.amount * material.nettPrice, 0);
}
get numericMaterialCostEstimate(): number {
return this.fault.materialCostEstimate
? this.fault.materialCostEstimate
: 0;
}
}

View File

@ -0,0 +1,8 @@
import { User } from "../../user/shared/user";
export class Comment {
public id: number;
public user: User;
public comment: string;
public createdAt: any;
}

View File

@ -0,0 +1,6 @@
export class ErrorCategory {
public id: number;
public name: string;
public type: string;
public active: boolean;
}

View File

@ -0,0 +1,6 @@
export class ErrorOrigin {
public id: number;
public code: string;
public name: string;
public active: boolean;
}

View File

@ -0,0 +1,12 @@
export class FacilityLocation {
public id: number;
public roomNumber: string;
public name: string;
public size: number;
public level: number;
public active: boolean;
// get labelClass(): string {
// return 'depth' + (this.level-1).toString();
// }
}

View File

@ -0,0 +1,36 @@
import { FacilityLocation } from "./facility-location";
import { ErrorCategory } from "./error-category";
import { ErrorOrigin } from "./error-origin";
import { SolutionTimeInterval } from "./solution-time-interval";
import { Comment } from "./comment";
import { User } from "../../user/shared/user";
import { UsedMaterial } from "./used-material";
export class Fault {
public id?: number;
public worksheetNumber: string;
public facilityLocation: FacilityLocation;
public facilityLocationDescription: string;
public errorCategory: ErrorCategory;
public errorOrigin: ErrorOrigin;
public errorDescription: string;
public solutionTimeInterval: SolutionTimeInterval;
public attachments: Array<Attachment>;
public reportAccepted: any;
public createdAt: any;
public state: string;
public workCostEstimate: number;
public materialCostEstimate: number;
public comments?: Array<Comment>;
public worker?: User;
public usedMaterials?: Array<UsedMaterial> = [];
public timeSpent?: number = 0;
public mustLowerCost?: boolean = false;
public allocatedExpense?: number = 0;
}
export class Attachment {
public id: number;
public type: string;
public active: boolean;
}

View File

@ -0,0 +1,4 @@
export * from './error-category';
export * from './error-origin';
export * from './facility-location';
export * from './solution-time-interval';

View File

@ -0,0 +1,25 @@
import {UsedMaterial} from "./used-material";
import {Comment} from "./comment";
export class SimpleFault {
public id?: number;
public workSheetNumber?: string;
public facilityLocation: number;
public facilityLocationDescription: string;
public errorCategory: number;
public errorOrigin: number;
public errorDescription: string;
public solutionTimeInterval: number;
public attachments?: string[];
public reportAccepted?: any;
public createdAt?: any;
public state?: string;
public workCostEstimate?: number;
public materialCostEstimate?: number;
public comments?: Array<Comment>;
public worker?: number;
public usedMaterials?: Array<UsedMaterial>;
public timeSpent?: number;
public mustLowerCost?: boolean = false;
public allocatedExpense?: number = 0;
}

View File

@ -0,0 +1,6 @@
export class SolutionTimeInterval {
public id: number;
public name: string;
public code: string;
public active: boolean;
}

View File

@ -0,0 +1,6 @@
export class UsedMaterial {
public name: string = '';
public amount: number;
public unit: string = '';
public nettPrice: number;
}

View File

@ -0,0 +1,3 @@
.ui.table thead th.clickable {
cursor: pointer;
}

View File

@ -0,0 +1,44 @@
<h4 class="ui dividing header">{{heading}}</h4>
<table *ngIf="faults?.length" class="ui celled definition table">
<thead>
<tr>
<th></th>
<th class="clickable" (click)="order('worksheetNumber')" *ngIf="showWorksheetNumber">ID</th>
<th class="clickable" (click)="order('facilityLocation')">Intézményrész</th>
<th>Hiba leírás</th>
<th (click)="order('solutionTimeInterval')" class="collapsing clickable">Javítás ideje</th>
<th (click)="order('createdAt')" class="collapsing clickable">Bejelentve</th>
<th>Kép</th>
</tr>
</thead>
<tbody>
<tr class="positive" *ngFor="let fault of faults">
<td class="collapsing" *ngIf="!editor">
<a [routerLink]="['/hiba/megjelenites', fault.id]" title="Hiba megtekintése"><i
class="large zoom link icon"></i></a>
<a *ngIf="showPdf(fault)" [href]="getWorksheetGenUrl(fault.id)"
title="Hibajegy megtekintése"><i class="large blue link icon checked calendar"></i></a>
</td>
<td class="collapsing" *ngIf="editor">
<ng-template ngFor let-action [ngForOf]="actions">
<a *ngIf="action.route" [routerLink]="[action.route, fault.id]" [title]="action.title"><i
[class]="action.class"></i></a>
<a *ngIf="action.emit" (click)="emit(action.emit, fault.id)" [title]="action.title"><i
[class]="action.class"></i></a>
<a *ngIf="action.external" [href]="injectId(action.external, fault.id)" target="_blank"
[title]="action.title"><i [class]="action.class"></i></a>
</ng-template>
</td>
<td *ngIf="showWorksheetNumber">{{fault.worksheetNumber}}</td>
<td><b>{{fault.facilityLocation.roomNumber}}</b> - {{fault.facilityLocation.name}}</td>
<td>{{fault.errorDescription}}</td>
<td class="collapsing">{{fault.solutionTimeInterval.name}}</td>
<td class="collapsing">{{fault.createdAt.date|date:'y-MM-dd H:m'}}
<ng-template [ngIf]="fault.reportAccepted"><br>{{fault.reportAccepted.date|date:'y-MM-dd H:m'}}</ng-template>
</td>
<td class="collapsing" [class.negative]="!hasImage(fault)"><i class="icon" [class.checkmark]="hasImage(fault)"
[class.close]="!hasImage(fault)"></i></td>
</tr>
</tbody>
</table>
<p *ngIf="faults?.length==0">{{noRecordMessage}}</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TableViewComponent } from './table-view.component';
describe('TableViewComponent', () => {
let component: TableViewComponent;
let fixture: ComponentFixture<TableViewComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TableViewComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TableViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,62 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Fault } from "../shared/fault";
import { FaultManagerService } from "../fault-manager.service";
import { environment } from "../../../environments/environment";
@Component({
selector: 'app-table-view,[app-table-view]',
templateUrl: './table-view.component.html',
styleUrls: ['./table-view.component.css']
})
export class TableViewComponent implements OnInit {
@Input() showWorksheetNumber: boolean = false;
@Input() editor: boolean = true;
@Input() faults: Array<Fault> = [];
@Input() heading: string = '';
@Input() noRecordMessage: string = '';
@Input() actions: Array<Action> = [];
@Output() actionClicked = new EventEmitter();
constructor(private faultManager: FaultManagerService) {
}
ngOnInit() {
}
public hasImage(fault: Fault) {
return fault.attachments && fault.attachments.length > 0;
}
public emit(event: string, id: number) {
this.actionClicked.emit({
action: event,
id: id
});
}
public injectId(uri: string, id: number) {
return uri.replace(':id', id.toString());
}
public order(field: string) {
this.faultManager.changeOrder(field);
}
public getWorksheetGenUrl(id: number): string {
return environment.apiUrl + '/hibajegy-pdf/' + id;
}
public showPdf(fault: Fault): boolean {
return this.showWorksheetNumber && fault.state == 'wip';
}
}
export interface Action {
class: string;
title: string;
route?: string;
callback?: Function;
emit: string;
external: string;
}

View File

@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { UserGuideComponent } from "./user-guide/user-guide.component";
const routes: Routes = [
{
path: 'sugo/felhasznaloi-segedlet',
component: UserGuideComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HelpRoutingModule { }

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HelpRoutingModule } from './help-routing.module';
import { UserGuideComponent } from './user-guide/user-guide.component';
@NgModule({
imports: [
CommonModule,
HelpRoutingModule
],
declarations: [UserGuideComponent]
})
export class HelpModule { }

View File

@ -0,0 +1,69 @@
<div class="ui main container">
<h1 class="ui dividing header">Felhasználói segédlet</h1>
<div class="ui segment">
<h2 class="ui header">Általános</h2>
A hibák menüben két lista nézet található minden felhasználó számára.<br>
A “hiba lista” az összes nyitott állapotban lévő hibát tartalmazza, azokat is, melyeken a felhasználónak
nincs joga módosításokat végezni.<br>
A “feladat lista” nézet ezzel szemben csak azokat a hibákat tartalmazza, amelyekkel a felhasználó
dolgozhat.
<h2 class="ui dividing header">Üzemeltetési vezető (ÜV)</h2>
<h3>Új hiba hozzáadása</h3>
A felső menüben a hiba menüpont alatt a “Hiba rögzítése” menüpontra kattintva lehet új hibabejelentéstt felvinni
a rendszerbe.<br>
A hibabejelentő űrlap pirossal jelölt mezőit kötelező kitölteni, beleértve a kép feltöltést is. A képfeltöltő
fájl kiválasztó párbeszédablakban a `CTRL` gomb nyomva tartásával egyszerre több kép is kiválasztható. (A képek
össz mérete nem haladhatja meg a 60MB méretet)
<h3>Javításra váró hibák</h3>
Itt találhatóak a folyamatban lévő hibajavítások. A ceruza ikonra kattintva megnyitható a hiba adatlap nézete,
amin belül lehetőség van az ÜV és PV közti hibával kapcsolatos kommunikációra. Az új üzenet írása email
értesítést küld a kommunikációban érintett résztvevőknek a rendszerben megadott email címükre.
<h3>Visszaigazolásra váró hibajavítások</h3>
Itt találhatóak a befejezett állapotban lévő hibajavítások. A ceruza ikonra kattintva megnyíló űrlap nézetben az
ÜV-nek lehetősége nyílik a hibajavítást elfogadni a zöld “Visszaigazolás” gombbal, vagy elutasítani azt - ezt a
felette lévő szöveges mezőben indokolni kell, az elutasítás gomb csak akkor lesz aktív. Elutasítás esetén az
elutasítás szövege hozzászólásként bekerül a hiba adatlapjára, és a hiba visszakerül a “Javításra váró hibák”
állapotba.
<h2 class="ui dividing header">Projektvezető (PV)</h2>
<h3>Visszaigazolásra váró hiba jelentések</h3>
Ebben a nézetben érhetőek el a frissen bejelentett hibák. A ceruza ikonra kattintva megnyílik a hibalap. A PV a
“visszaigazolható” mező bepipálásával és a becsült költségek megadásával visszaigazolhatja a hiba bejelentését.
Amennyiben a becsült költségek a hibát nagyértékű javítás kategóriába sorolják, úgy a PV-nek csatolnia kell a
pdf formátumú TERC költségvetési tervet.<br>
Az oldal alján további képeket lehet a hibához csatolni, ugyanúgy ahogy az ÜV tette azt a hibabejelentésnél. A
`CTRL` gomb nyomva tartásával egyszerre több kép is kijelölhető. (A képek össz mérete nem haladhatja meg a 60MB
méretet egy feltöltésen belül).
<h3>Javításra váró hibák</h3>
Ebben a nézetben érhetőek el a visszaigazolt és jóváhagyott hibajelentések. A ceruza ikonra kattintva megnyílik
a hibalap. A kommunikáció blokkban a PV és ÜV tudnak szöveges formában üzeneteket fűzni a hibajavításhoz.<br>
A “felhasznált anyagok” blokkban lehetséges a hibajavítás elvégzőjének és idejének megadása(tört szám megadása
lehetséges, a tizedes elválasztó pont, nem vessző).<br>
A felhasznált anyagoknál mind a négy mezőt ki kell tölteni, majd az aktívvá váló zöld “+” gombbal hozzáadni az
űrlap tartalmát az anyag használati listához.<br>
Az ebben a blokkban történt változások elmentése az itt található mentés gombbal lehetséges, csak azok az
alapanyagok kerülnek mentésre, amiket a mentés gomb alatti anyag táblázat tartalmaz.
A hibalap alján a PV további képeket csatolhat a hiba űrlapjához.<br>
A “hibajavítás kész” pipát bejelölve és a “jóváhagyás” gombra kattintva a hibajavítást a PV befejezett állapotba
állítja, az tovább kerül az ÜV felhasználóhoz, módosítani a PV ezután már nem tudja.<br>
<h3>Havi zárás</h3>
A hó végi zárást a PV a “Riportok” menü “havi zárás” menüpontjában tudja kezelni
<h2 class="ui dividing header">Üzemeltetési főosztály (ÜFO)</h2>
Az üzemeltetési főosztály lát mindent, amit az ÜV jogosultságú felhasználó elér, ezen felül számára elérhető egy
további hiba állapot kezelése, a nagyértékű munka jóváhagyása.
<h3>Jóváhagyásra váró nagyértékű munkák</h3>
Amennyiben a projektvezető a hiba költség becslésekor átlépi a nagyértékű munka értékhatárt, a hiba nagyértékű
munkának minősül és bekerül a nagyértékű munka jóváhagyási állapotba.<br>
Az ÜFO felhasználó a ceruza ikonra kattintva tudja megnyitni a munka adatlapját, ahol eléri a hibához csatolt
költségbecslési dokumentumot. Ezután a zöld “jóváhagyás” gombbal elfogadhatja a hibát, ami ez esetben tovább
kerül a projektvezetőhöz, a “Költség csökkentés szükséges” pipát bejelölve és egy értékhatárt megadva hagyhatja
jóvá, vagy indoklást megadva elutasíthatja a hibajavítást, ekkor a hiba a törölt hibák tárolóba kerül.
<h2 class="ui dividing header">Betekintő</h2>
A betekintő jogosultságú felhasználó a rendszerben lévő hibajelentéseket csak olvasható módban látja, sem
szerkeszteni sem hozzászólni nem tud az egyes hibákhoz. Csak a hiba listát, az archív hiba tárolókat láthatják.
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { UserGuideComponent } from './user-guide.component';
describe('UserGuideComponent', () => {
let component: UserGuideComponent;
let fixture: ComponentFixture<UserGuideComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ UserGuideComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(UserGuideComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

Some files were not shown because too many files have changed in this diff Show More