This commit is contained in:
Hlars 2025-07-08 07:51:06 +02:00
parent ca90d1c422
commit e4b26961b0
18 changed files with 507 additions and 59 deletions

View File

@ -37,6 +37,16 @@
<div>Comment Value: {{parseComment(form)}}</div>
<div>
<mat-form-field>
<mat-label>Currency Input</mat-label>
<app-currency-input [formControl]="currencyFormControl"></app-currency-input>
</mat-form-field>
<pre>Value: {{currencyFormControl.value}}</pre>
</div>
<mat-form-field appearance="outline">
<mat-label>Choose an option</mat-label>
<mat-select multiple [formControl]="selectedItemsList">

View File

@ -1,11 +1,10 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { CommentBoxComponent } from "./comment-box/comment-box.component";
import { CommentBoxComponent, MentionItem } from "./comment-box/comment-box.component";
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { ProgressStepperComponent } from "./progress-stepper/progress-stepper.component";
import { Observable, startWith, map } from 'rxjs';
import { MatOptionModule } from '@angular/material/core';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatChipsModule } from '@angular/material/chips';
@ -19,10 +18,11 @@ import { GanttComponent, GanttItem } from "./gantt/gantt.component";
import { GrossNetCalculatorComponent } from "./gross-net-calculator/gross-net-calculator.component";
import { GrossNetCalculatorInputDirective } from './gross-net-calculator/gross-net-calculator-input.directive';
import { GrossNetCalculatorToggleButtonComponent } from "./gross-net-calculator/toggle-button/toggle-button.component";
import { CurrencyInputComponent } from "./currency-input/currency-input.component";
@Component({
selector: 'app-root',
imports: [RouterOutlet, MatToolbarModule, GrossNetCalculatorInputDirective, MatIconModule, MatButtonModule, CommentBoxComponent, MatFormFieldModule, MatInputModule, ReactiveFormsModule, ProgressStepperComponent, MatOptionModule, MatSelectModule, MatAutocompleteModule, MatChipsModule, AsyncPipe, FormsModule, SelectFilterComponent, GanttComponent, GrossNetCalculatorComponent, GrossNetCalculatorToggleButtonComponent],
imports: [RouterOutlet, MatToolbarModule, GrossNetCalculatorInputDirective, MatIconModule, MatButtonModule, CommentBoxComponent, MatFormFieldModule, MatInputModule, ReactiveFormsModule, ProgressStepperComponent, MatOptionModule, MatSelectModule, MatAutocompleteModule, MatChipsModule, AsyncPipe, FormsModule, SelectFilterComponent, GanttComponent, GrossNetCalculatorComponent, GrossNetCalculatorToggleButtonComponent, CurrencyInputComponent],
providers: [GrossNetCalculatorInputDirective],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
@ -30,9 +30,11 @@ import { GrossNetCalculatorToggleButtonComponent } from "./gross-net-calculator/
export class AppComponent {
title = 'material-mentions';
selectionList = ["Franz Kaiser", "Melanie Ochse", "Peter Pfeifer", "Hans Wurst", "Hans Ochse", "Max Hintern", "Hansi Hinterseer", "Gustav Gans"];
selectionList: MentionItem[] = [{ title: "Franz Kaiser" }, { title: "Melanie Ochse", color: 'red' }, { title: "Peter Pfeifer" }, { title: "Hans Wurst" }, { title: "Hans Ochse" }, { title: "Max Hintern" }, { title: "Hansi Hinterseer" }, { title: "Gustav Gans" }];
form = new FormControl("fdaf <div class=\"mention\">@Franz Kaiser</div><span class=\"focused\">&nbsp;</span>");
currencyFormControl = new FormControl<number | null>(null, [Validators.required]);
test = '';
parseComment(comment: FormControl): any {
@ -56,43 +58,224 @@ export class AppComponent {
ganttItems: GanttItem[] = [{
id: '000',
title: 'Test 1',
items: [{
title: '124',
start: 1743487875,
duration_sec: 7200,
}, {
title: 'F242',
start: 1743660675,
duration_sec: 16 * 3600,
}
items: [
{
"title": "dznAFoN",
"orig_start": 1735718400,
"orig_end": 1735790400,
"duration": 72000,
"start": 1735718400,
"end": 1735920000,
"effective_duration": 201600
},
{
"title": "FddMLSo",
"orig_start": 1735693200,
"orig_end": 1735758000,
"duration": 64800,
"start": 1735920000,
"end": 1736337600,
"effective_duration": 417600
},
{
"title": "I8UNM7Q",
"orig_start": 1735704000,
"orig_end": 1735714800,
"duration": 10800,
"start": 1736337600,
"end": 1736352000,
"effective_duration": 14400
},
{
"title": "lDpkEvQ",
"orig_start": 1735700400,
"orig_end": 1735765200,
"duration": 64800,
"start": 1736352000,
"end": 1736769600,
"effective_duration": 417600
},
{
"title": "j3awoij",
"orig_start": 1735689600,
"orig_end": 1735747200,
"duration": 57600,
"start": 1736769600,
"end": 1736953200,
"effective_duration": 183600
},
{
"title": "6Em6nMj",
"orig_start": 1735689600,
"orig_end": 1735714800,
"duration": 25200,
"start": 1736953200,
"end": 1737039600,
"effective_duration": 86400
},
{
"title": "stzG5kS",
"orig_start": 1735700400,
"orig_end": 1735747200,
"duration": 46800,
"start": 1737039600,
"end": 1737381600,
"effective_duration": 342000
},
{
"title": "XqZVBX3",
"orig_start": 1735725600,
"orig_end": 1735729200,
"duration": 3600,
"start": 1737381600,
"end": 1737385200,
"effective_duration": 3600
},
{
"title": "YFLNNja",
"orig_start": 1735758000,
"orig_end": 1735815600,
"duration": 57600,
"start": 1737385200,
"end": 1737565200,
"effective_duration": 180000
},
{
"title": "mSP8cLK",
"orig_start": 1735786800,
"orig_end": 1735833600,
"duration": 46800,
"start": 1737565200,
"end": 1737734400,
"effective_duration": 169200
},
{
"title": "4Clgbi7",
"orig_start": 1735768800,
"orig_end": 1735772400,
"duration": 3600,
"start": 1737734400,
"end": 1737738000,
"effective_duration": 3600
},
{
"title": "zNEqjve",
"orig_start": 1735768800,
"orig_end": 1735815600,
"duration": 46800,
"start": 1737738000,
"end": 1738080000,
"effective_duration": 342000
},
{
"title": "JDWUyY4",
"orig_start": 1735804800,
"orig_end": 1735858800,
"duration": 54000,
"start": 1738080000,
"end": 1738256400,
"effective_duration": 176400
},
{
"title": "gzCfIBH",
"orig_start": 1735815600,
"orig_end": 1735869600,
"duration": 54000,
"start": 1738256400,
"end": 1738663200,
"effective_duration": 406800
},
{
"title": "9BcHUE1",
"orig_start": 1735819200,
"orig_end": 1735830000,
"duration": 10800,
"start": 1738663200,
"end": 1738677600,
"effective_duration": 14400
},
{
"title": "cqY6QW7",
"orig_start": 1735819200,
"orig_end": 1735891200,
"duration": 72000,
"start": 1738677600,
"end": 1738929600,
"effective_duration": 252000
},
{
"title": "kWlI5wm",
"orig_start": 1735758000,
"orig_end": 1735776000,
"duration": 18000,
"start": 1738929600,
"end": 1739181600,
"effective_duration": 252000
},
{
"title": "KGHy9oL",
"orig_start": 1735797600,
"orig_end": 1735869600,
"duration": 72000,
"start": 1739181600,
"end": 1739379600,
"effective_duration": 198000
},
{
"title": "1yTL2to",
"orig_start": 1735826400,
"orig_end": 1735898400,
"duration": 72000,
"start": 1739379600,
"end": 1739808000,
"effective_duration": 428400
},
{
"title": "JuVrwIN",
"orig_start": 1735844400,
"orig_end": 1735916400,
"duration": 72000,
"start": 1739808000,
"end": 1740063600,
"effective_duration": 255600
}
]
},
{
id: '001',
title: 'Test 2',
items: [{
title: '124',
start: 1743487875,
duration_sec: 7200,
}, {
title: 'F242',
start: 1743660675,
duration_sec: 72000,
"title": "",
"orig_start": 1735898400,
"orig_end": 1735934400,
"duration": 36000,
"start": 1738335600,
"end": 1738663200,
"effective_duration": 327600
},
{
"title": "",
"orig_start": 1735938000,
"orig_end": 1735984800,
"duration": 46800,
"start": 1738663200,
"end": 1738774800,
"effective_duration": 111600
}
]
},
{
id: 'dfaf',
title: 'WeekTest',
items: [
{ title: '1', start: 1744005600, duration_sec: 72000 },
{ title: '2', start: 1744092000, duration_sec: 72000 },
{ title: '3', start: 1744178400, duration_sec: 72000 },
{ title: '4', start: 1744264800, duration_sec: 72000 },
{ title: '5', start: 1744351200, duration_sec: 72000 },
{ title: '6', start: 1744437600, duration_sec: 72000 },
{ title: '7', start: 1744524000, duration_sec: 72000 }
]
}
// {
// id: 'dfaf',
// title: 'WeekTest',
// items: [
// { title: '1', start: 1744005600, duration: 72000 },
// { title: '2', start: 1744092000, duration: 72000 },
// { title: '3', start: 1744178400, duration: 72000 },
// { title: '4', start: 1744264800, duration: 72000 },
// { title: '5', start: 1744351200, duration: 72000 },
// { title: '6', start: 1744437600, duration: 72000 },
// { title: '7', start: 1744524000, duration: 72000 }
// ]
// }
]
}

View File

@ -32,7 +32,7 @@
}}
<!-- Draw gantt bars -->
@for (bar of item.items; track $index) {
<div class="bar" [style.width]="durationToWidth(bar.duration_sec)"
<div class="bar" [style.width]="durationToWidth(bar.effective_duration)"
[style.left]="startToOffset(bar.start, {dstCorrection: true})">
{{bar.title}}</div>
}
@ -120,7 +120,7 @@
<!-- Draw gantt bars -->
<div class="bar-container">
@for (bar of item.items; track $index) {
<div class="bar" [style.width]="durationToWidth(bar.duration_sec)"
<div class="bar" [style.width]="durationToWidth(bar.effective_duration)"
[style.left]="startToOffset(bar.start, {dstCorrection: true})">
{{bar.title}}</div>
}
@ -148,6 +148,13 @@
<div class="container new">
<div class="info-box">
<div class="flex head-controls">
<div class="flex">
<button mat-icon-button (click)="view.back()"><mat-icon>chevron_left</mat-icon></button>
<button mat-button (click)="view.today()">Heute</button>
</div>
<button mat-icon-button (click)="view.next()"><mat-icon>chevron_right</mat-icon></button>
</div>
<div>
<button (click)="view.back()">Back</button><button (click)="view.next()">Next</button>
<select #viewSelect value="week" (change)="changeViewType(viewSelect.value)">
@ -203,7 +210,7 @@
<!-- Draw gantt bars -->
<div class="bar-container">
@for (bar of item.items; track $index) {
<div class="bar" [style.width]="view.durationToWidth(bar.duration_sec)"
<div class="bar" [style.width]="view.durationToWidth(bar.effective_duration)"
[style.left]="view.getOffset(bar.start)">
{{bar.title}}</div>
}

View File

@ -119,11 +119,10 @@
top: 0;
left: 0;
height: var(--row-height);
background-color: blue;
background-color: #6082b6;
color: #fff;
box-sizing: border-box;
border-radius: 0.2rem;
box-sizing: content-box;
border: solid thin #dbd7d2;
display: flex;
align-items: center;
@ -134,7 +133,7 @@
white-space: nowrap;
&:hover {
border: solid thin #fff;
border: solid thin #fd673a;
z-index: 99;
cursor: default;
}
@ -232,7 +231,6 @@
&:last-child {
.tick {
color: red;
&:last-child {
border-right: none;
}
@ -306,3 +304,13 @@
font-size: 0.95rem;
}
}
// DESIGN
.flex {
display: flex;
align-items: center;
}
.head-controls {
padding: 0.25rem;
}

View File

@ -2,6 +2,9 @@ import { ChangeDetectorRef, Component, ElementRef, Input, ViewChild } from '@ang
import { Observable } from 'rxjs';
import { DateTime } from 'luxon';
import { GanttView } from './views/view';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
export interface GanttItem {
id: string;
@ -11,8 +14,12 @@ export interface GanttItem {
export interface GanttBarElement {
title: string,
orig_start: number,
orig_end: number,
duration: number,
start: number,
duration_sec: number,
end: number,
effective_duration: number,
}
interface WorkTime {
@ -27,7 +34,7 @@ interface IntervalDescriptor {
@Component({
selector: 'app-gantt',
imports: [],
imports: [MatButtonModule, MatIconModule, MatButtonToggleModule],
templateUrl: './gantt.component.html',
styleUrl: './gantt.component.scss'
})
@ -43,16 +50,58 @@ export class GanttComponent {
startDate: DateTime;
weekOpenTime: WorkTime[][] = [
[{ start_day_sec: 8 * 3600, duration_sec: 8 * 3600 }],
[{ start_day_sec: 8 * 3600, duration_sec: 8 * 3600 }],
[{ start_day_sec: 8 * 3600, duration_sec: 8 * 3600 }],
[{ start_day_sec: 8 * 3600, duration_sec: 8 * 3600 }],
[{ start_day_sec: 8 * 3600, duration_sec: 8 * 3600 }],
[{ start_day_sec: 8 * 3600, duration_sec: 8 * 3600 }],
[{ start_day_sec: 8 * 3600, duration_sec: 8 * 3600 }],
[{ start_day_sec: 8 * 3600, duration_sec: 8 * 3600 }],
];
weekOpenTime: { [key: number]: WorkTime[] } = {
"0": [
{
"start_day_sec": 32400,
"duration_sec": 10800
},
{
"start_day_sec": 46800,
"duration_sec": 14400
}
],
"1": [
{
"start_day_sec": 32400,
"duration_sec": 10800
},
{
"start_day_sec": 46800,
"duration_sec": 14400
}
],
"2": [
{
"start_day_sec": 32400,
"duration_sec": 10800
},
{
"start_day_sec": 46800,
"duration_sec": 14400
}
],
"3": [
{
"start_day_sec": 32400,
"duration_sec": 10800
},
{
"start_day_sec": 46800,
"duration_sec": 14400
}
],
"4": [
{
"start_day_sec": 32400,
"duration_sec": 10800
},
{
"start_day_sec": 46800,
"duration_sec": 14400
}
]
};
constructor(private changeDetectorRef: ChangeDetectorRef) {
this.startDate = DateTime.now().minus({ weeks: 1 }).startOf('week').startOf('day').setLocale('de');

View File

@ -1,7 +1,10 @@
import { DateTime, Settings } from "luxon";
import { DescriptorSet } from "./descriptors";
export const defaultAvailable = [{ start_day_sec: 8 * 3600, duration_sec: 8 * 3600, onSaturday: false, onSunOrHoliday: false }];
export const defaultAvailable = [
{ start_day_sec: 9 * 3600, duration_sec: 3 * 3600, onSaturday: false, onSunOrHoliday: false },
{ start_day_sec: 13 * 3600, duration_sec: 4 * 3600, onSaturday: false, onSunOrHoliday: false }
];
export abstract class GanttViewType {

View File

@ -0,0 +1,8 @@
import { GrossNetCalculatorInputDirective } from './gross-net-calculator-input.directive';
describe('GrossNetCalculatorInputDirective', () => {
it('should create an instance', () => {
const directive = new GrossNetCalculatorInputDirective();
expect(directive).toBeTruthy();
});
});

View File

@ -0,0 +1,63 @@
import { Component, ComponentFactoryResolver, Directive, inject, Input, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';
import { GrossNetCalculatorComponent } from './gross-net-calculator.component';
import { MatButton } from '@angular/material/button';
import { GrossNetCalculatorToggleButtonComponent } from './toggle-button/toggle-button.component';
import { ComponentPortal, Portal, TemplatePortal } from '@angular/cdk/portal';
@Component({
selector: 'component-portal-example',
template: '<ng-template [cdkPortalOutlet]="portal"></ng-template>',
})
export class ComponentPortalExample { }
@Directive({
selector: 'input[grossNetCalculator]',
})
export class GrossNetCalculatorInputDirective {
private _viewContainerRef = inject(ViewContainerRef);
@ViewChild(TemplateRef) _template!: TemplateRef<unknown>;
private _portal!: ComponentPortal<any>;
@Input()
set grossNetCalculator(calculator: GrossNetCalculatorComponent) {
console.log(calculator)
if (calculator) {
this._calculator = calculator;
// this._ariaOwns.set(datepicker.opened ? datepicker.id : null);
// this._closedSubscription = datepicker.closedStream.subscribe(() => {
// this._onTouched();
// this._ariaOwns.set(null);
// });
// this._openedSubscription = datepicker.openedStream.subscribe(() => {
// this._ariaOwns.set(datepicker.id);
// });
// this._registerModel(datepicker.registerInput(this));
}
}
_calculator?: GrossNetCalculatorComponent;
constructor() {
console.log(this._viewContainerRef);
this._viewContainerRef.createComponent(GrossNetCalculatorToggleButtonComponent)
this._viewContainerRef.createComponent(ComponentPortalExample)
}
ngAfterViewInit() {
this._portal = new ComponentPortal(GrossNetCalculatorComponent);
}
protected _openPopup(): void {
if (this._calculator) {
// this._datepicker.open();
}
}
}

View File

@ -0,0 +1,34 @@
<!-- <ng-template cdkConnectedOverlay cdkConnectedOverlayFlexibleDimensions="true" [cdkConnectedOverlayOpen]="isOpen"
[cdkConnectedOverlayPositions]="[
{
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'top',
offsetX: menuOffsetX,
offsetY: menuOffsetY
},
{
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'bottom',
offsetX: menuOffsetX,
offsetY: menuOffsetY - menuOpenAboveYShift
}
]" (overlayOutsideClick)="clickOutside($event)">
<div class="mat-mdc-menu-panel" #menuContent>
<div class="mat-mdc-menu-content">
´´´
Bla bla
</div>
</div>
</ng-template> -->
<ng-template [cdkPortalOutlet]="templatePortal" (overlayOutsideClick)="clickOutside($event)">
<div class="mat-mdc-menu-panel" #menuContent>
<div class="mat-mdc-menu-content">
Bla bla
</div>
</div>
</ng-template>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GrossNetCalculatorComponent } from './gross-net-calculator.component';
describe('GrossNetCalculatorComponent', () => {
let component: GrossNetCalculatorComponent;
let fixture: ComponentFixture<GrossNetCalculatorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GrossNetCalculatorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GrossNetCalculatorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,13 @@
import { OverlayModule } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { Component, inject, ViewContainerRef } from '@angular/core';
@Component({
selector: 'gross-net-calculator',
imports: [OverlayModule],
templateUrl: './gross-net-calculator.component.html',
styleUrl: './gross-net-calculator.component.scss'
})
export class GrossNetCalculatorComponent {
}

View File

@ -0,0 +1,4 @@
<!-- <button #button mat-icon-button type="button" matPrefix>
<mat-icon>calculate</mat-icon>
</button> -->
<button mat-icon-button><mat-icon>calculate</mat-icon></button>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GrossNetCalculatorToggleButtonComponent } from './toggle-button.component';
describe('ToggleButtonComponent', () => {
let component: GrossNetCalculatorToggleButtonComponent;
let fixture: ComponentFixture<GrossNetCalculatorToggleButtonComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GrossNetCalculatorToggleButtonComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GrossNetCalculatorToggleButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,14 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector: 'gross-net-calculator-toggle-button',
imports: [MatIconModule, MatButtonModule],
templateUrl: './toggle-button.component.html',
styleUrl: './toggle-button.component.scss',
encapsulation: ViewEncapsulation.None,
})
export class GrossNetCalculatorToggleButtonComponent {
}

View File

@ -1,6 +1,6 @@
<div class="step-progress">
<div class="container" #container>
@for (item of steps; track item; let index = $index) {
@for (item of steps; track $index; let index = $index) {
<div class="check-box" [class]="{
'complete': index < status.progressIndex,
'active': index === status.progressIndex && status.progressStatus === STATUS.InProgress,

View File

@ -1,4 +1,10 @@
/* You can add global styles to this file, and also import other style files */
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}