initial commit
This commit is contained in:
parent
1790106c30
commit
03b0dd8422
33 changed files with 1006 additions and 513 deletions
47
src/app/api.service.spec.ts
Normal file
47
src/app/api.service.spec.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { MODULES } from './mocks/modules';
|
||||||
|
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
|
||||||
|
describe('ApiService', () => {
|
||||||
|
let service: ApiService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ApiService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all modules', () => {
|
||||||
|
service.getModules().subscribe(modules => {
|
||||||
|
expect(modules.length).toBe(MODULES.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return module with id 1', () => {
|
||||||
|
service.getModule(1).subscribe(module => {
|
||||||
|
expect(module).toBeTruthy();
|
||||||
|
expect(module?.id).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined when module with id 100 does not exist', () => {
|
||||||
|
service.getModule(100).subscribe(module => {
|
||||||
|
expect(module).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return ordered modules', () => {
|
||||||
|
const mod1 = MODULES[0];
|
||||||
|
const mod2 = MODULES[1];
|
||||||
|
|
||||||
|
const orderedModules = service.orderModules([mod1, mod2]);
|
||||||
|
|
||||||
|
expect(orderedModules.length).toBe(2);
|
||||||
|
expect(orderedModules[0]).toBe(mod1);
|
||||||
|
expect(orderedModules[1]).toBe(mod2);
|
||||||
|
});
|
||||||
|
});
|
||||||
26
src/app/api.service.ts
Normal file
26
src/app/api.service.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
|
||||||
|
import { Module } from './interfaces/module';
|
||||||
|
import { MODULES } from './mocks/modules';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ApiService {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
getModules(): Observable<Module[]> {
|
||||||
|
return of(MODULES);
|
||||||
|
}
|
||||||
|
|
||||||
|
getModule(id: number): Observable<Module|undefined> {
|
||||||
|
return of(MODULES.find(module => module.id === id));
|
||||||
|
}
|
||||||
|
|
||||||
|
orderModules(modules: Module[]): Module[] {
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
const routes: Routes = [];
|
import { HomeComponent } from './home/home.component';
|
||||||
|
import { ModuleDetailComponent } from './module-detail/module-detail.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', component: HomeComponent },
|
||||||
|
{ path: 'module/:id', component: ModuleDetailComponent },
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes)],
|
imports: [RouterModule.forRoot(routes)],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
:host {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
margin: 82px auto 32px;
|
||||||
|
padding: 0 16px 200px;
|
||||||
|
max-width: 960px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-inner {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,484 +1,12 @@
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * Delete the template below * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * to get started with your project! * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacer {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 60px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #1976d2;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar img {
|
|
||||||
margin: 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar #twitter-logo {
|
|
||||||
height: 40px;
|
|
||||||
margin: 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar #youtube-logo {
|
|
||||||
height: 40px;
|
|
||||||
margin: 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar #twitter-logo:hover,
|
|
||||||
.toolbar #youtube-logo:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
margin: 82px auto 32px;
|
|
||||||
padding: 0 16px;
|
|
||||||
max-width: 960px;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg.material-icons {
|
|
||||||
height: 24px;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg.material-icons:not(:last-child) {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card svg.material-icons path {
|
|
||||||
fill: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
all: unset;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
background-color: #fafafa;
|
|
||||||
height: 40px;
|
|
||||||
width: 200px;
|
|
||||||
margin: 0 8px 16px;
|
|
||||||
padding: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container .card:not(:last-child) {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card.card-small {
|
|
||||||
height: 16px;
|
|
||||||
width: 168px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container .card:not(.highlight-card) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container .card:not(.highlight-card):hover {
|
|
||||||
transform: translateY(-3px);
|
|
||||||
box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container .card:not(.highlight-card):hover .material-icons path {
|
|
||||||
fill: rgb(105, 103, 103);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card.highlight-card {
|
|
||||||
background-color: #1976d2;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
border: none;
|
|
||||||
width: auto;
|
|
||||||
min-width: 30%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card.card.highlight-card span {
|
|
||||||
margin-left: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg#rocket {
|
|
||||||
width: 80px;
|
|
||||||
position: absolute;
|
|
||||||
left: -10px;
|
|
||||||
top: -24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg#rocket-smoke {
|
|
||||||
height: calc(100vh - 95px);
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 180px;
|
|
||||||
z-index: -10;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
a:visited,
|
|
||||||
a:hover {
|
|
||||||
color: #1976d2;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #125699;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal {
|
|
||||||
position: relative;
|
|
||||||
width: 80%;
|
|
||||||
max-width: 600px;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding-top: 45px;
|
|
||||||
margin-top: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: rgb(15, 15, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal::before {
|
|
||||||
content: "\2022 \2022 \2022";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 4px;
|
|
||||||
background: rgb(58, 58, 58);
|
|
||||||
color: #c2c3c4;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 2rem;
|
|
||||||
line-height: 0;
|
|
||||||
padding: 14px 0;
|
|
||||||
text-indent: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal pre {
|
|
||||||
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
|
|
||||||
color: white;
|
|
||||||
padding: 0 1rem 1rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle-link {
|
|
||||||
height: 40px;
|
|
||||||
width: 40px;
|
|
||||||
border-radius: 40px;
|
|
||||||
margin: 8px;
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid #eeeeee;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
|
||||||
transition: 1s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle-link:hover {
|
|
||||||
transform: translateY(-0.25rem);
|
|
||||||
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
margin-top: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.github-star-badge {
|
|
||||||
color: #24292e;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 3px 10px;
|
|
||||||
border: 1px solid rgba(27,31,35,.2);
|
|
||||||
border-radius: 3px;
|
|
||||||
background-image: linear-gradient(-180deg,#fafbfc,#eff3f6 90%);
|
|
||||||
margin-left: 4px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.github-star-badge:hover {
|
|
||||||
background-image: linear-gradient(-180deg,#f0f3f6,#e6ebf1 90%);
|
|
||||||
border-color: rgba(27,31,35,.35);
|
|
||||||
background-position: -.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.github-star-badge .material-icons {
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg#clouds {
|
|
||||||
position: fixed;
|
|
||||||
bottom: -160px;
|
|
||||||
left: -230px;
|
|
||||||
z-index: -10;
|
|
||||||
width: 1920px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Styles */
|
|
||||||
@media screen and (max-width: 767px) {
|
|
||||||
.card-container > *:not(.circle-link) ,
|
|
||||||
.terminal {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:not(.highlight-card) {
|
|
||||||
height: 16px;
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card.highlight-card span {
|
|
||||||
margin-left: 72px;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg#rocket-smoke {
|
|
||||||
right: 120px;
|
|
||||||
transform: rotate(-5deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 575px) {
|
|
||||||
svg#rocket-smoke {
|
|
||||||
display: none;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- Toolbar -->
|
|
||||||
<div class="toolbar" role="banner">
|
|
||||||
<img
|
|
||||||
width="40"
|
|
||||||
alt="Angular Logo"
|
|
||||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
|
|
||||||
/>
|
|
||||||
<span>Welcome</span>
|
|
||||||
<div class="spacer"></div>
|
|
||||||
<a aria-label="Angular on twitter" target="_blank" rel="noopener" href="https://twitter.com/angular" title="Twitter">
|
|
||||||
<svg id="twitter-logo" height="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
|
|
||||||
<rect width="400" height="400" fill="none"/>
|
|
||||||
<path d="M153.62,301.59c94.34,0,145.94-78.16,145.94-145.94,0-2.22,0-4.43-.15-6.63A104.36,104.36,0,0,0,325,122.47a102.38,102.38,0,0,1-29.46,8.07,51.47,51.47,0,0,0,22.55-28.37,102.79,102.79,0,0,1-32.57,12.45,51.34,51.34,0,0,0-87.41,46.78A145.62,145.62,0,0,1,92.4,107.81a51.33,51.33,0,0,0,15.88,68.47A50.91,50.91,0,0,1,85,169.86c0,.21,0,.43,0,.65a51.31,51.31,0,0,0,41.15,50.28,51.21,51.21,0,0,1-23.16.88,51.35,51.35,0,0,0,47.92,35.62,102.92,102.92,0,0,1-63.7,22A104.41,104.41,0,0,1,75,278.55a145.21,145.21,0,0,0,78.62,23" fill="#fff"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
<a aria-label="Angular on YouTube" target="_blank" rel="noopener" href="https://youtube.com/angular" title="YouTube">
|
|
||||||
<svg id="youtube-logo" height="24" width="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff">
|
|
||||||
<path d="M0 0h24v24H0V0z" fill="none"/>
|
|
||||||
<path d="M21.58 7.19c-.23-.86-.91-1.54-1.77-1.77C18.25 5 12 5 12 5s-6.25 0-7.81.42c-.86.23-1.54.91-1.77 1.77C2 8.75 2 12 2 12s0 3.25.42 4.81c.23.86.91 1.54 1.77 1.77C5.75 19 12 19 12 19s6.25 0 7.81-.42c.86-.23 1.54-.91 1.77-1.77C22 15.25 22 12 22 12s0-3.25-.42-4.81zM10 15V9l5.2 3-5.2 3z"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content" role="main">
|
<div class="content" role="main">
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
|
||||||
<!-- Highlight Card -->
|
<div class="content-inner">
|
||||||
<div class="card highlight-card card-small">
|
<app-modules [style]="'all'"></app-modules>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
<svg id="rocket" xmlns="http://www.w3.org/2000/svg" width="101.678" height="101.678" viewBox="0 0 101.678 101.678">
|
<app-modules [style]="'cluster'"></app-modules>
|
||||||
<title>Rocket Ship</title>
|
|
||||||
<g id="Group_83" data-name="Group 83" transform="translate(-141 -696)">
|
|
||||||
<circle id="Ellipse_8" data-name="Ellipse 8" cx="50.839" cy="50.839" r="50.839" transform="translate(141 696)" fill="#dd0031"/>
|
|
||||||
<g id="Group_47" data-name="Group 47" transform="translate(165.185 720.185)">
|
|
||||||
<path id="Path_33" data-name="Path 33" d="M3.4,42.615a3.084,3.084,0,0,0,3.553,3.553,21.419,21.419,0,0,0,12.215-6.107L9.511,30.4A21.419,21.419,0,0,0,3.4,42.615Z" transform="translate(0.371 3.363)" fill="#fff"/>
|
|
||||||
<path id="Path_34" data-name="Path 34" d="M53.3,3.221A3.09,3.09,0,0,0,50.081,0,48.227,48.227,0,0,0,18.322,13.437c-6-1.666-14.991-1.221-18.322,7.218A33.892,33.892,0,0,1,9.439,25.1l-.333.666a3.013,3.013,0,0,0,.555,3.553L23.985,43.641a2.9,2.9,0,0,0,3.553.555l.666-.333A33.892,33.892,0,0,1,32.647,53.3c8.55-3.664,8.884-12.326,7.218-18.322A48.227,48.227,0,0,0,53.3,3.221ZM34.424,9.772a6.439,6.439,0,1,1,9.106,9.106,6.368,6.368,0,0,1-9.106,0A6.467,6.467,0,0,1,34.424,9.772Z" transform="translate(0 0.005)" fill="#fff"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<span>{{ title }} app is running!</span>
|
|
||||||
|
|
||||||
<svg id="rocket-smoke" xmlns="http://www.w3.org/2000/svg" width="516.119" height="1083.632" viewBox="0 0 516.119 1083.632">
|
|
||||||
<title>Rocket Ship Smoke</title>
|
|
||||||
<path id="Path_40" data-name="Path 40" d="M644.6,141S143.02,215.537,147.049,870.207s342.774,201.755,342.774,201.755S404.659,847.213,388.815,762.2c-27.116-145.51-11.551-384.124,271.9-609.1C671.15,139.365,644.6,141,644.6,141Z" transform="translate(-147.025 -140.939)" fill="#f5f5f5"/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Resources -->
|
<app-cart></app-cart>
|
||||||
<h2>Resources</h2>
|
|
||||||
<p>Here are some links to help you get started:</p>
|
|
||||||
|
|
||||||
<div class="card-container">
|
|
||||||
<a class="card" target="_blank" rel="noopener" href="https://angular.io/tutorial">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82zM12 3L1 9l11 6 9-4.91V17h2V9L12 3z"/></svg>
|
|
||||||
<span>Learn Angular</span>
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg> </a>
|
|
||||||
|
|
||||||
<a class="card" target="_blank" rel="noopener" href="https://angular.io/cli">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
|
|
||||||
<span>CLI Documentation</span>
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="card" target="_blank" rel="noopener" href="https://material.angular.io">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" style="margin-right: 8px" width="21.813" height="23.453" viewBox="0 0 179.2 192.7"><path fill="#ffa726" d="M89.4 0 0 32l13.5 118.4 75.9 42.3 76-42.3L179.2 32 89.4 0z"/><path fill="#fb8c00" d="M89.4 0v192.7l76-42.3L179.2 32 89.4 0z"/><path fill="#ffe0b2" d="m102.9 146.3-63.3-30.5 36.3-22.4 63.7 30.6-36.7 22.3z"/><path fill="#fff3e0" d="M102.9 122.8 39.6 92.2l36.3-22.3 63.7 30.6-36.7 22.3z"/><path fill="#fff" d="M102.9 99.3 39.6 68.7l36.3-22.4 63.7 30.6-36.7 22.4z"/></svg>
|
|
||||||
<span>Angular Material</span>
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="card" target="_blank" rel="noopener" href="https://blog.angular.io/">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/></svg>
|
|
||||||
<span>Angular Blog</span>
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="card" target="_blank" rel="noopener" href="https://angular.io/devtools/">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M14.73,13.31C15.52,12.24,16,10.93,16,9.5C16,5.91,13.09,3,9.5,3S3,5.91,3,9.5C3,13.09,5.91,16,9.5,16 c1.43,0,2.74-0.48,3.81-1.27L19.59,21L21,19.59L14.73,13.31z M9.5,14C7.01,14,5,11.99,5,9.5S7.01,5,9.5,5S14,7.01,14,9.5 S11.99,14,9.5,14z"/><polygon points="10.29,8.44 9.5,6 8.71,8.44 6.25,8.44 8.26,10.03 7.49,12.5 9.5,10.97 11.51,12.5 10.74,10.03 12.75,8.44"/></g></g></svg>
|
|
||||||
<span>Angular DevTools</span>
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Next Steps -->
|
|
||||||
<h2>Next Steps</h2>
|
|
||||||
<p>What do you want to do next with your app?</p>
|
|
||||||
|
|
||||||
<input type="hidden" #selection>
|
|
||||||
|
|
||||||
<div class="card-container">
|
|
||||||
<button class="card card-small" (click)="selection.value = 'component'" tabindex="0">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
|
||||||
<span>New Component</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="card card-small" (click)="selection.value = 'material'" tabindex="0">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
|
||||||
<span>Angular Material</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="card card-small" (click)="selection.value = 'pwa'" tabindex="0">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
|
||||||
<span>Add PWA Support</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="card card-small" (click)="selection.value = 'dependency'" tabindex="0">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
|
||||||
<span>Add Dependency</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="card card-small" (click)="selection.value = 'test'" tabindex="0">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
|
||||||
<span>Run and Watch Tests</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="card card-small" (click)="selection.value = 'build'" tabindex="0">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
|
||||||
<span>Build for Production</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Terminal -->
|
|
||||||
<div class="terminal" [ngSwitch]="selection.value">
|
|
||||||
<pre *ngSwitchDefault>ng generate component xyz</pre>
|
|
||||||
<pre *ngSwitchCase="'material'">ng add @angular/material</pre>
|
|
||||||
<pre *ngSwitchCase="'pwa'">ng add @angular/pwa</pre>
|
|
||||||
<pre *ngSwitchCase="'dependency'">ng add _____</pre>
|
|
||||||
<pre *ngSwitchCase="'test'">ng test</pre>
|
|
||||||
<pre *ngSwitchCase="'build'">ng build</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Links -->
|
|
||||||
<div class="card-container">
|
|
||||||
<a class="circle-link" title="Find a Local Meetup" href="https://www.meetup.com/find/?keywords=angular" target="_blank" rel="noopener">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24.607" height="23.447" viewBox="0 0 24.607 23.447">
|
|
||||||
<title>Meetup Logo</title>
|
|
||||||
<path id="logo--mSwarm" d="M21.221,14.95A4.393,4.393,0,0,1,17.6,19.281a4.452,4.452,0,0,1-.8.069c-.09,0-.125.035-.154.117a2.939,2.939,0,0,1-2.506,2.091,2.868,2.868,0,0,1-2.248-.624.168.168,0,0,0-.245-.005,3.926,3.926,0,0,1-2.589.741,4.015,4.015,0,0,1-3.7-3.347,2.7,2.7,0,0,1-.043-.38c0-.106-.042-.146-.143-.166a3.524,3.524,0,0,1-1.516-.69A3.623,3.623,0,0,1,2.23,14.557a3.66,3.66,0,0,1,1.077-3.085.138.138,0,0,0,.026-.2,3.348,3.348,0,0,1-.451-1.821,3.46,3.46,0,0,1,2.749-3.28.44.44,0,0,0,.355-.281,5.072,5.072,0,0,1,3.863-3,5.028,5.028,0,0,1,3.555.666.31.31,0,0,0,.271.03A4.5,4.5,0,0,1,18.3,4.7a4.4,4.4,0,0,1,1.334,2.751,3.658,3.658,0,0,1,.022.706.131.131,0,0,0,.1.157,2.432,2.432,0,0,1,1.574,1.645,2.464,2.464,0,0,1-.7,2.616c-.065.064-.051.1-.014.166A4.321,4.321,0,0,1,21.221,14.95ZM13.4,14.607a2.09,2.09,0,0,0,1.409,1.982,4.7,4.7,0,0,0,1.275.221,1.807,1.807,0,0,0,.9-.151.542.542,0,0,0,.321-.545.558.558,0,0,0-.359-.534,1.2,1.2,0,0,0-.254-.078c-.262-.047-.526-.086-.787-.138a.674.674,0,0,1-.617-.75,3.394,3.394,0,0,1,.218-1.109c.217-.658.509-1.286.79-1.918a15.609,15.609,0,0,0,.745-1.86,1.95,1.95,0,0,0,.06-1.073,1.286,1.286,0,0,0-1.051-1.033,1.977,1.977,0,0,0-1.521.2.339.339,0,0,1-.446-.042c-.1-.092-.2-.189-.307-.284a1.214,1.214,0,0,0-1.643-.061,7.563,7.563,0,0,1-.614.512A.588.588,0,0,1,10.883,8c-.215-.115-.437-.215-.659-.316a2.153,2.153,0,0,0-.695-.248A2.091,2.091,0,0,0,7.541,8.562a9.915,9.915,0,0,0-.405.986c-.559,1.545-1.015,3.123-1.487,4.7a1.528,1.528,0,0,0,.634,1.777,1.755,1.755,0,0,0,1.5.211,1.35,1.35,0,0,0,.824-.858c.543-1.281,1.032-2.584,1.55-3.875.142-.355.28-.712.432-1.064a.548.548,0,0,1,.851-.24.622.622,0,0,1,.185.539,2.161,2.161,0,0,1-.181.621c-.337.852-.68,1.7-1.018,2.552a2.564,2.564,0,0,0-.173.528.624.624,0,0,0,.333.71,1.073,1.073,0,0,0,.814.034,1.22,1.22,0,0,0,.657-.655q.758-1.488,1.511-2.978.35-.687.709-1.37a1.073,1.073,0,0,1,.357-.434.43.43,0,0,1,.463-.016.373.373,0,0,1,.153.387.7.7,0,0,1-.057.236c-.065.157-.127.316-.2.469-.42.883-.846,1.763-1.262,2.648A2.463,2.463,0,0,0,13.4,14.607Zm5.888,6.508a1.09,1.09,0,0,0-2.179.006,1.09,1.09,0,0,0,2.179-.006ZM1.028,12.139a1.038,1.038,0,1,0,.01-2.075,1.038,1.038,0,0,0-.01,2.075ZM13.782.528a1.027,1.027,0,1,0-.011,2.055A1.027,1.027,0,0,0,13.782.528ZM22.21,6.95a.882.882,0,0,0-1.763.011A.882.882,0,0,0,22.21,6.95ZM4.153,4.439a.785.785,0,1,0,.787-.78A.766.766,0,0,0,4.153,4.439Zm8.221,18.22a.676.676,0,1,0-.677.666A.671.671,0,0,0,12.374,22.658ZM22.872,12.2a.674.674,0,0,0-.665.665.656.656,0,0,0,.655.643.634.634,0,0,0,.655-.644A.654.654,0,0,0,22.872,12.2ZM7.171-.123A.546.546,0,0,0,6.613.43a.553.553,0,1,0,1.106,0A.539.539,0,0,0,7.171-.123ZM24.119,9.234a.507.507,0,0,0-.493.488.494.494,0,0,0,.494.494.48.48,0,0,0,.487-.483A.491.491,0,0,0,24.119,9.234Zm-19.454,9.7a.5.5,0,0,0-.488-.488.491.491,0,0,0-.487.5.483.483,0,0,0,.491.479A.49.49,0,0,0,4.665,18.936Z" transform="translate(0 0.123)" fill="#f64060"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="circle-link" title="Join the Conversation on Discord" href="https://discord.gg/angular" target="_blank" rel="noopener">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 245 240">
|
|
||||||
<title>Discord Logo</title>
|
|
||||||
<path d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/>
|
|
||||||
<path d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<footer>
|
|
||||||
Love Angular?
|
|
||||||
<a href="https://github.com/angular/angular" target="_blank" rel="noopener"> Give our repo a star.
|
|
||||||
<div class="github-star-badge">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
|
|
||||||
Star
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/angular/angular" target="_blank" rel="noopener">
|
|
||||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" fill="#1976d2"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<svg id="clouds" xmlns="http://www.w3.org/2000/svg" width="2611.084" height="485.677" viewBox="0 0 2611.084 485.677">
|
|
||||||
<title>Gray Clouds Background</title>
|
|
||||||
<path id="Path_39" data-name="Path 39" d="M2379.709,863.793c10-93-77-171-168-149-52-114-225-105-264,15-75,3-140,59-152,133-30,2.83-66.725,9.829-93.5,26.25-26.771-16.421-63.5-23.42-93.5-26.25-12-74-77-130-152-133-39-120-212-129-264-15-54.084-13.075-106.753,9.173-138.488,48.9-31.734-39.726-84.4-61.974-138.487-48.9-52-114-225-105-264,15a162.027,162.027,0,0,0-103.147,43.044c-30.633-45.365-87.1-72.091-145.206-58.044-52-114-225-105-264,15-75,3-140,59-152,133-53,5-127,23-130,83-2,42,35,72,70,86,49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33,61.112,8.015,113.854-5.72,150.492-29.764a165.62,165.62,0,0,0,110.861-3.236c47,94,178,113,251,33,31.385,4.116,60.563,2.495,86.487-3.311,25.924,5.806,55.1,7.427,86.488,3.311,73,80,204,61,251-33a165.625,165.625,0,0,0,120,0c51,13,108,15,157-5a147.188,147.188,0,0,0,33.5-18.694,147.217,147.217,0,0,0,33.5,18.694c49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33C2446.709,1093.793,2554.709,922.793,2379.709,863.793Z" transform="translate(142.69 -634.312)" fill="#eee"/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
|
||||||
beforeEach(() => TestBed.configureTestingModule({
|
|
||||||
imports: [RouterTestingModule],
|
|
||||||
declarations: [AppComponent]
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should create the app', () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.componentInstance;
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should have as title 'module-configurator'`, () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.componentInstance;
|
|
||||||
expect(app.title).toEqual('module-configurator');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render title', () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
fixture.detectChanges();
|
|
||||||
const compiled = fixture.nativeElement as HTMLElement;
|
|
||||||
expect(compiled.querySelector('.content span')?.textContent).toContain('module-configurator app is running!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { Component } from '@angular/core';
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
|
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'module-configurator';
|
title = 'Modul Konfigurator';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,32 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
|
||||||
|
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
import { CartModule } from './cart/cart.module';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
import { ModulesComponent } from './modules/modules.component';
|
||||||
|
import { ModulesItemComponent } from './modules-item/modules-item.component';
|
||||||
|
|
||||||
|
import { HomeComponent } from './home/home.component';
|
||||||
|
import { ModuleDetailComponent } from './module-detail/module-detail.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent
|
AppComponent,
|
||||||
|
ModulesComponent,
|
||||||
|
HomeComponent,
|
||||||
|
ModuleDetailComponent,
|
||||||
|
ModulesItemComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule
|
AppRoutingModule,
|
||||||
|
CartModule,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
|
|
|
||||||
29
src/app/cart-item/cart-item.component.css
Normal file
29
src/app/cart-item/cart-item.component.css
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
.cluster {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: #222;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-actions button {
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
margin-left: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
18
src/app/cart-item/cart-item.component.html
Normal file
18
src/app/cart-item/cart-item.component.html
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<div *ngIf="item" class="item">
|
||||||
|
<div class="item-name">
|
||||||
|
<div>{{item.name}}</div>
|
||||||
|
<div class="cluster">{{item.cluster}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-actions">
|
||||||
|
<button
|
||||||
|
[disabled]="isFirst(item)"
|
||||||
|
(click)="moveUp(item)">🡅</button>
|
||||||
|
<button
|
||||||
|
[disabled]="isLast(item)"
|
||||||
|
(click)="moveDown(item)">🡇</button>
|
||||||
|
<button
|
||||||
|
(click)="removeFromCart(item)"
|
||||||
|
>✖</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
37
src/app/cart-item/cart-item.component.ts
Normal file
37
src/app/cart-item/cart-item.component.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Module } from '../interfaces/module';
|
||||||
|
import { CartService } from '../cart/cart.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-cart-item',
|
||||||
|
templateUrl: './cart-item.component.html',
|
||||||
|
styleUrls: ['./cart-item.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export class CartItemComponent {
|
||||||
|
@Input() item?: Module;
|
||||||
|
|
||||||
|
constructor(private cart: CartService) { }
|
||||||
|
|
||||||
|
removeFromCart(module: Module): void {
|
||||||
|
this.cart.remove(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirst(module: Module): boolean {
|
||||||
|
return this.cart.isFirst(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
isLast(module: Module): boolean {
|
||||||
|
return this.cart.isLast(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveUp(module: Module): void {
|
||||||
|
this.cart.moveUp(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveDown(module: Module): void {
|
||||||
|
this.cart.moveDown(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
38
src/app/cart/cart.component.css
Normal file
38
src/app/cart/cart.component.css
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
.cart {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-content.hidden {
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-content {
|
||||||
|
transition: max-height 0.3s ease-out;
|
||||||
|
max-height: 380px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-items {
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 300px;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-actions {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-actions button {
|
||||||
|
margin-left: 0.2rem;
|
||||||
|
}
|
||||||
13
src/app/cart/cart.component.html
Normal file
13
src/app/cart/cart.component.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<div class="cart">
|
||||||
|
<h4 class="headline" (click)="toggle()">Konfiguration ({{items.length}}) <span>{{ isOpen ? '🡇' : '🡅' }}</span></h4>
|
||||||
|
<div class="cart-content" [class]="{hidden: !isOpen}">
|
||||||
|
<div class="cart-items">
|
||||||
|
<app-cart-item *ngFor="let item of items" [item]="item"></app-cart-item>
|
||||||
|
</div>
|
||||||
|
<div class="cart-actions">
|
||||||
|
<button
|
||||||
|
[disabled]="!hasAllClusters()"
|
||||||
|
(click)="makeOrder()">Konfiguration abschicken</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
44
src/app/cart/cart.component.ts
Normal file
44
src/app/cart/cart.component.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { CartService } from './cart.service';
|
||||||
|
import { ApiService } from '../api.service';
|
||||||
|
import { Module } from '../interfaces/module';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-cart',
|
||||||
|
templateUrl: './cart.component.html',
|
||||||
|
styleUrls: ['./cart.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
export class CartComponent {
|
||||||
|
items = this.cart.items;
|
||||||
|
availableModules: Module[] = [];
|
||||||
|
isOpen = true;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private cart: CartService,
|
||||||
|
private api: ApiService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.api.getModules().subscribe(allModules => {
|
||||||
|
this.availableModules = allModules;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(): void {
|
||||||
|
this.isOpen = !this.isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAllClusters(): boolean {
|
||||||
|
return this.cart.hasAllClusters();
|
||||||
|
}
|
||||||
|
|
||||||
|
makeOrder(): void {
|
||||||
|
try {
|
||||||
|
const orderedModules = this.cart.order();
|
||||||
|
console.log(orderedModules);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/app/cart/cart.module.ts
Normal file
20
src/app/cart/cart.module.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { CartComponent } from './cart.component';
|
||||||
|
import { CartItemComponent } from '../cart-item/cart-item.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CartComponent,
|
||||||
|
CartItemComponent,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
CartComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export class CartModule { }
|
||||||
190
src/app/cart/cart.service.spec.ts
Normal file
190
src/app/cart/cart.service.spec.ts
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CartService } from './cart.service';
|
||||||
|
|
||||||
|
import { Module, Cluster } from '../interfaces/module';
|
||||||
|
|
||||||
|
describe('CartService', () => {
|
||||||
|
let service: CartService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(CartService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add module to cart', () => {
|
||||||
|
const mod: Module = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Module 1',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 1'
|
||||||
|
};
|
||||||
|
|
||||||
|
service.add(mod);
|
||||||
|
expect(service.items).toContain(mod);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add the same module to cart twice', () => {
|
||||||
|
const mod: Module = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Module 1',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 1'
|
||||||
|
};
|
||||||
|
|
||||||
|
service.add(mod);
|
||||||
|
service.add(mod);
|
||||||
|
|
||||||
|
expect(service.items).toContain(mod);
|
||||||
|
expect(service.items.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error when trying to order without all clusters', () => {
|
||||||
|
const mod1: Module = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Module 1',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 1'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mod2: Module = {
|
||||||
|
id: 2,
|
||||||
|
name: 'Module 2',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 2'
|
||||||
|
};
|
||||||
|
|
||||||
|
service.add(mod1);
|
||||||
|
service.add(mod2);
|
||||||
|
|
||||||
|
expect(() => service.order()).toThrowError('Not all clusters are selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all ordered modules if all clusters are selected', () => {
|
||||||
|
const clusters: Cluster[] = ['FMEA', 'Control-Plan', 'Fertigungsprüfung', 'Reklamationsmanagement', 'Maßnahmenmanagement'];
|
||||||
|
|
||||||
|
clusters.forEach((cluster, idx) => {
|
||||||
|
let mod: Module = {
|
||||||
|
id: idx,
|
||||||
|
name: `Module ${idx}`,
|
||||||
|
cluster,
|
||||||
|
description: 'Description 1'
|
||||||
|
};
|
||||||
|
|
||||||
|
service.add(mod);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(service.order().length).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove module from cart', () => {
|
||||||
|
const mod: Module = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Module 1',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 1'
|
||||||
|
};
|
||||||
|
|
||||||
|
service.add(mod);
|
||||||
|
service.remove(mod);
|
||||||
|
|
||||||
|
expect(service.items).not.toContain(mod);
|
||||||
|
expect(service.items.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move module up', () => {
|
||||||
|
const mod1: Module = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Module 1',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 1'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mod2: Module = {
|
||||||
|
id: 2,
|
||||||
|
name: 'Module 2',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 2'
|
||||||
|
};
|
||||||
|
|
||||||
|
service.add(mod1);
|
||||||
|
service.add(mod2);
|
||||||
|
|
||||||
|
service.moveUp(mod2);
|
||||||
|
|
||||||
|
expect(service.items[0]).toBe(mod2);
|
||||||
|
expect(service.items[1]).toBe(mod1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move module down', () => {
|
||||||
|
const mod1: Module = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Module 1',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 1'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mod2: Module = {
|
||||||
|
id: 2,
|
||||||
|
name: 'Module 2',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 2'
|
||||||
|
};
|
||||||
|
|
||||||
|
service.add(mod1);
|
||||||
|
service.add(mod2);
|
||||||
|
|
||||||
|
service.moveDown(mod1);
|
||||||
|
|
||||||
|
expect(service.items[0]).toBe(mod2);
|
||||||
|
expect(service.items[1]).toBe(mod1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if module is first', () => {
|
||||||
|
const mod1: Module = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Module 1',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 1'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mod2: Module = {
|
||||||
|
id: 2,
|
||||||
|
name: 'Module 2',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 2'
|
||||||
|
};
|
||||||
|
|
||||||
|
service.add(mod1);
|
||||||
|
service.add(mod2);
|
||||||
|
|
||||||
|
expect(service.isFirst(mod1)).toBe(true);
|
||||||
|
expect(service.isFirst(mod2)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if module is last', () => {
|
||||||
|
const mod1: Module = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Module 1',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 1'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mod2: Module = {
|
||||||
|
id: 2,
|
||||||
|
name: 'Module 2',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Description 2'
|
||||||
|
};
|
||||||
|
|
||||||
|
service.add(mod1);
|
||||||
|
service.add(mod2);
|
||||||
|
|
||||||
|
expect(service.isLast(mod1)).toBe(false);
|
||||||
|
expect(service.isLast(mod2)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
75
src/app/cart/cart.service.ts
Normal file
75
src/app/cart/cart.service.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Module } from '../interfaces/module';
|
||||||
|
import { ApiService } from '../api.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class CartService {
|
||||||
|
items: Module[] = [];
|
||||||
|
numberAllClusters: number = 0;
|
||||||
|
|
||||||
|
constructor(private api: ApiService) {
|
||||||
|
this.api.getModules().subscribe(allModules => {
|
||||||
|
this.numberAllClusters = [...new Set(allModules.map(module => module.cluster))].length;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
add(module: Module): void {
|
||||||
|
if (!this.isInCart(module)) {
|
||||||
|
this.items.push(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(module: Module): void {
|
||||||
|
const index = this.items.indexOf(module);
|
||||||
|
if (index > -1) {
|
||||||
|
this.items.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isInCart(module: Module): boolean {
|
||||||
|
return this.items.includes(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAllClusters(): boolean {
|
||||||
|
let clusters = [...new Set(this.items.map(module => module.cluster))];
|
||||||
|
|
||||||
|
return clusters.length === this.numberAllClusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirst(module: Module): boolean {
|
||||||
|
return this.items.indexOf(module) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLast(module: Module): boolean {
|
||||||
|
return this.items.indexOf(module) === this.items.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveUp(module: Module): void {
|
||||||
|
const index = this.items.indexOf(module);
|
||||||
|
|
||||||
|
if (index > 0) {
|
||||||
|
this.items.splice(index, 1);
|
||||||
|
this.items.splice(index - 1, 0, module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveDown(module: Module): void {
|
||||||
|
const index = this.items.indexOf(module);
|
||||||
|
|
||||||
|
if (index < this.items.length - 1) {
|
||||||
|
this.items.splice(index, 1);
|
||||||
|
this.items.splice(index + 1, 0, module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
order(): Module[] {
|
||||||
|
if (!this.hasAllClusters()) {
|
||||||
|
throw new Error('Not all clusters are selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.api.orderModules(this.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/app/home/home.component.css
Normal file
4
src/app/home/home.component.css
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
div {
|
||||||
|
padding: 0.5rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
8
src/app/home/home.component.html
Normal file
8
src/app/home/home.component.html
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hier können Sie eine individuelle Modulkonfiguration vornehmen.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Sie müssen mindestens ein Modul aus jedem Cluster ausgewählt haben, bevor eine Angebotsanfrage verschickt werden kann.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
9
src/app/home/home.component.ts
Normal file
9
src/app/home/home.component.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home',
|
||||||
|
templateUrl: './home.component.html',
|
||||||
|
styleUrls: ['./home.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
export class HomeComponent { }
|
||||||
8
src/app/interfaces/module.ts
Normal file
8
src/app/interfaces/module.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export type Cluster = 'FMEA'|'Control-Plan'|'Fertigungsprüfung'|'Reklamationsmanagement'|'Maßnahmenmanagement'
|
||||||
|
|
||||||
|
export type Module = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
cluster: Cluster
|
||||||
|
description: string
|
||||||
|
}
|
||||||
124
src/app/mocks/modules.ts
Normal file
124
src/app/mocks/modules.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { Module } from '../interfaces/module';
|
||||||
|
|
||||||
|
export const MODULES: Module[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Musterprüfung',
|
||||||
|
cluster: 'Control-Plan',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Erstbemusterung',
|
||||||
|
cluster: 'Control-Plan',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'CAD Integration',
|
||||||
|
cluster: 'Fertigungsprüfung',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Vorbeugende Instandhaltung',
|
||||||
|
cluster: 'Fertigungsprüfung',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'Wareneingangs- / Warenausgangsprüfung',
|
||||||
|
cluster: 'Fertigungsprüfung',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'Prüfmittelmanagement',
|
||||||
|
cluster: 'Fertigungsprüfung',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: 'Prüfmittelfähigkeitsuntersuchung',
|
||||||
|
cluster: 'Fertigungsprüfung',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: 'Warranty Management',
|
||||||
|
cluster: 'Reklamationsmanagement',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: 'CAPA Management',
|
||||||
|
cluster: 'Reklamationsmanagement',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: 'Quality Cockpit',
|
||||||
|
cluster: 'Maßnahmenmanagement',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: 'Schulungsmanagement',
|
||||||
|
cluster: 'Maßnahmenmanagement',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: 'Qualifikationsmanagement',
|
||||||
|
cluster: 'Maßnahmenmanagement',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: 'Aufgabenmanagement',
|
||||||
|
cluster: 'Maßnahmenmanagement',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
name: 'Checklisten',
|
||||||
|
cluster: 'Maßnahmenmanagement',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: 'Lieferanten Cockpit',
|
||||||
|
cluster: 'Maßnahmenmanagement',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 16,
|
||||||
|
name: 'Auditmanagement',
|
||||||
|
cluster: 'Maßnahmenmanagement',
|
||||||
|
description: 'Unternehmen nutzen Audits, um im eigenen Haus oder bei Lieferanten die Wirksamkeit eines Managementsystems zu überprüfen und sicherzustellen. Es wird untersucht, ob die unternehmensinternen Prozesse sowie die Anforderungen und Richtlinien den Standards entsprechen. So kann nicht nur der Ist-Zustand analysiert, sondern Verbesserungspotenzial effektiv aufgedeckt werden.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 17,
|
||||||
|
name: 'Prozessmanagement',
|
||||||
|
cluster: 'Maßnahmenmanagement',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 18,
|
||||||
|
name: 'Dokumentenlenkung',
|
||||||
|
cluster: 'Maßnahmenmanagement',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 19,
|
||||||
|
name: 'APQP Projektmanagement',
|
||||||
|
cluster: 'FMEA',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl quis tincidunt ultricies, nunc nisl ultricies nunc, quis ultricies ni',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
7
src/app/module-detail/module-detail.component.css
Normal file
7
src/app/module-detail/module-detail.component.css
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
.module-detail {
|
||||||
|
padding: 0.5rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
19
src/app/module-detail/module-detail.component.html
Normal file
19
src/app/module-detail/module-detail.component.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<div *ngIf="module" class="module-detail">
|
||||||
|
<h2>{{module.name}}</h2>
|
||||||
|
<div>
|
||||||
|
{{ module.description }}
|
||||||
|
</div>
|
||||||
|
<button *ngIf="!isInCart()"
|
||||||
|
(click)="addToCart()">
|
||||||
|
Hinzufügen
|
||||||
|
</button>
|
||||||
|
<button *ngIf="isInCart()"
|
||||||
|
(click)="removeFromCart()">
|
||||||
|
Entfernen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="!module" class="module-detail">
|
||||||
|
<h2>Kein Modul ausgewählt oder nicht gefunden</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
69
src/app/module-detail/module-detail.component.ts
Normal file
69
src/app/module-detail/module-detail.component.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
|
||||||
|
import { Module } from '../interfaces/module';
|
||||||
|
|
||||||
|
import { ApiService } from '../api.service';
|
||||||
|
import { CartService } from '../cart/cart.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-module-detail',
|
||||||
|
templateUrl: './module-detail.component.html',
|
||||||
|
styleUrls: ['./module-detail.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ModuleDetailComponent implements OnInit {
|
||||||
|
module: Module | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private api: ApiService,
|
||||||
|
private location: Location,
|
||||||
|
private cart: CartService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.getModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
getModule(): void {
|
||||||
|
this.route.params.subscribe((params) => {
|
||||||
|
if (params?.['id']) {
|
||||||
|
let id = Number(params['id']);
|
||||||
|
this.api.getModule(id).subscribe(module => {
|
||||||
|
if (!module) {
|
||||||
|
this.location.go('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.module = module;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isInCart(): boolean {
|
||||||
|
if (!this.module) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.cart.isInCart(this.module);
|
||||||
|
}
|
||||||
|
|
||||||
|
addToCart(): void {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cart.add(this.module);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFromCart(): void {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cart.remove(this.module);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/app/modules-item/modules-item.component.css
Normal file
22
src/app/modules-item/modules-item.component.css
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
.module {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.1rem 0;
|
||||||
|
align-items: center;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.add span {
|
||||||
|
display: inline-block;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.remove {
|
||||||
|
background-color: rgba(255, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
19
src/app/modules-item/modules-item.component.html
Normal file
19
src/app/modules-item/modules-item.component.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<div *ngIf="module"
|
||||||
|
class="module">
|
||||||
|
<a routerLink="/module/{{module.id}}">
|
||||||
|
{{module.name}}
|
||||||
|
</a>
|
||||||
|
<button *ngIf="!isInCart(module)"
|
||||||
|
class="add"
|
||||||
|
(click)="addToCart(module)"
|
||||||
|
>
|
||||||
|
<span>✖</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button *ngIf="isInCart(module)"
|
||||||
|
class="remove"
|
||||||
|
(click)="removeFromCart(module)"
|
||||||
|
>
|
||||||
|
<span>✖</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
27
src/app/modules-item/modules-item.component.ts
Normal file
27
src/app/modules-item/modules-item.component.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Module } from '../interfaces/module';
|
||||||
|
import { CartService } from '../cart/cart.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-modules-item',
|
||||||
|
templateUrl: './modules-item.component.html',
|
||||||
|
styleUrls: ['./modules-item.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ModulesItemComponent {
|
||||||
|
@Input() module?: Module;
|
||||||
|
|
||||||
|
constructor(private cart: CartService) { }
|
||||||
|
|
||||||
|
addToCart(module: Module): void {
|
||||||
|
this.cart.add(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFromCart(module: Module): void {
|
||||||
|
this.cart.remove(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
isInCart(module: Module): boolean {
|
||||||
|
return this.cart.isInCart(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/app/modules/modules.component.css
Normal file
36
src/app/modules/modules.component.css
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
.module.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary, .summary {
|
||||||
|
padding: 0.5rem 0.5rem 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
details > summary {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
details > summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary:after {
|
||||||
|
content: "►";
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open] summary:after {
|
||||||
|
content: "▼";
|
||||||
|
}
|
||||||
|
|
||||||
|
summary * {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
22
src/app/modules/modules.component.html
Normal file
22
src/app/modules/modules.component.html
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<div *ngIf="style === 'all'">
|
||||||
|
<div class="summary">
|
||||||
|
<h4>Alle Module</h4>
|
||||||
|
</div>
|
||||||
|
<app-modules-item *ngFor="let module of modules"
|
||||||
|
[module]="module"
|
||||||
|
class="module">
|
||||||
|
</app-modules-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="style === 'cluster'">
|
||||||
|
<details *ngFor="let cluster of clusters">
|
||||||
|
<summary><h4>{{cluster}}</h4></summary>
|
||||||
|
<app-modules-item
|
||||||
|
*ngFor="let module of modules"
|
||||||
|
class="module"
|
||||||
|
[module]="module"
|
||||||
|
[class]="{hidden: module.cluster !== cluster}"
|
||||||
|
>
|
||||||
|
</app-modules-item>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
36
src/app/modules/modules.component.ts
Normal file
36
src/app/modules/modules.component.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Module } from '../interfaces/module';
|
||||||
|
import { ApiService } from '../api.service';
|
||||||
|
import { CartService } from '../cart/cart.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-modules',
|
||||||
|
templateUrl: './modules.component.html',
|
||||||
|
styleUrls: ['./modules.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ModulesComponent {
|
||||||
|
@Input() style?: string = 'all';
|
||||||
|
clusters: string[] = [];
|
||||||
|
modules: Module[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private api: ApiService,
|
||||||
|
private cart: CartService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
addToCart(module: Module): void {
|
||||||
|
this.cart.add(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeModules(): void {
|
||||||
|
this.api.getModules().subscribe(allModules => {
|
||||||
|
this.modules = allModules.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
this.clusters = [...new Set(allModules.map(module => module.cluster))].sort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initializeModules();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>ModuleConfigurator</title>
|
<title>Modul Konfigurator</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
|
|
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue