11import { CommonModule } from '@angular/common' ;
22import {
3- AfterViewInit ,
43 Component ,
4+ computed ,
55 forwardRef ,
66 input ,
77 model ,
88 OnChanges ,
99 OnInit ,
1010 SimpleChanges ,
11- viewChild ,
1211} from '@angular/core' ;
1312import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ;
1413import { MatFormFieldModule } from '@angular/material/form-field' ;
15- import { MatMenuModule , MatMenuTrigger } from '@angular/material/menu' ;
16- import { formatDuration , getTimezoneOffsetString } from '@placeos/common' ;
14+ import { MatMenuModule } from '@angular/material/menu' ;
15+ import {
16+ formatDuration ,
17+ getTimeInTimezone ,
18+ getTimezoneOffsetString ,
19+ } from '@placeos/common' ;
1720import { addMinutes } from 'date-fns' ;
1821import { IconComponent } from 'libs/components/src/lib/icon.component' ;
1922
@@ -48,9 +51,9 @@ export interface DurationOption {
4851 : ''
4952 }}{{ selected?.name }}{{ selected?.date ? ')' : '' }}
5053 </div>
51- @if (timezone() && tz) {
54+ @if (timezone() && tz() ) {
5255 <div class="truncate text-xs opacity-30">
53- {{ selected?.date | date: time_format + ' (z)' : tz }}
56+ {{ selected?.date | date: time_format + ' (z)' : tz() }}
5457 </div>
5558 }
5659 </div>
@@ -60,7 +63,6 @@ export interface DurationOption {
6063 @for (option of duration_options; track option.id) {
6164 <button
6265 mat-menu-item
63- [attr.data-duration]="option.id"
6466 class="text-left"
6567 (click)="setValue(option.id)"
6668 >
@@ -80,13 +82,13 @@ export interface DurationOption {
8082 }}{{ option.name
8183 }}{{ option.date ? ')' : '' }}
8284 </div>
83- @if (timezone() && tz) {
85+ @if (timezone() && tz() ) {
8486 <div class="truncate text-xs opacity-30">
8587 {{
8688 option.date
8789 | date
8890 : time_format + ' (z)'
89- : tz
91+ : tz()
9092 }}
9193 </div>
9294 }
@@ -125,7 +127,7 @@ export interface DurationOption {
125127 imports : [ MatMenuModule , MatFormFieldModule , CommonModule , IconComponent ] ,
126128} )
127129export class DurationFieldComponent
128- implements OnInit , OnChanges , AfterViewInit , ControlValueAccessor
130+ implements OnInit , OnChanges , ControlValueAccessor
129131{
130132 /** Maximum duration option available */
131133 public readonly max = input ( 240 ) ;
@@ -158,8 +160,6 @@ export class DurationFieldComponent
158160 private _onChange : ( _ : number ) => void ;
159161 /** Form control on touch handler */
160162 private _onTouch : ( _ : number ) => void ;
161- /** Menu trigger for the duration selection dropdown */
162- private readonly _menu_trigger = viewChild ( MatMenuTrigger ) ;
163163
164164 public get time_format ( ) {
165165 return this . use_24hr ( ) ? 'HH : mm' : 'h : mm a' ;
@@ -173,12 +173,12 @@ export class DurationFieldComponent
173173 Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone ,
174174 ) ;
175175
176- public get tz ( ) {
176+ public readonly tz = computed ( ( ) => {
177177 const tz = this . timezone ( ) ;
178178 if ( ! tz ) return '' ;
179179 const tz_offset = getTimezoneOffsetString ( tz ) ;
180180 return tz_offset === this . _local_tz ? '' : tz_offset ;
181- }
181+ } ) ;
182182
183183 public ngOnInit ( ) : void {
184184 this . duration_options = this . generateDurationOptions (
@@ -210,36 +210,6 @@ export class DurationFieldComponent
210210 }
211211 }
212212
213- public ngAfterViewInit ( ) : void {
214- const trigger = this . _menu_trigger ( ) ;
215- if ( trigger ) {
216- trigger . menuOpened . subscribe ( ( ) => {
217- this . _scrollToSelectedDuration ( ) ;
218- } ) ;
219- }
220- }
221-
222- /** Scroll the menu to the selected duration option */
223- private _scrollToSelectedDuration ( ) : void {
224- // Use requestAnimationFrame for immediate execution after render
225- requestAnimationFrame ( ( ) => {
226- const panel = document . querySelector ( '.mat-mdc-menu-panel' ) ;
227- if ( ! panel ) return ;
228-
229- // Find the selected duration element
230- const target_element = panel . querySelector (
231- `[data-duration="${ this . duration } "]` ,
232- ) ;
233-
234- if ( target_element ) {
235- target_element . scrollIntoView ( {
236- block : 'center' ,
237- behavior : 'instant' ,
238- } ) ;
239- }
240- } ) ;
241- }
242-
243213 /**
244214 * Update the form field value
245215 * @param new_value New value to set on the form field
@@ -355,8 +325,11 @@ export class DurationFieldComponent
355325 if ( end_time === undefined || end_time === null || ! time_value ) {
356326 return max ;
357327 }
358- const date = new Date ( time_value ) ;
359- const start_minutes = date . getHours ( ) * 60 + date . getMinutes ( ) ;
328+ // Use building timezone to compute start minutes since end_time
329+ // is in building timezone minutes-since-midnight
330+ const tz = this . timezone ( ) || undefined ;
331+ const { hours, minutes } = getTimeInTimezone ( time_value , tz ) ;
332+ const start_minutes = hours * 60 + minutes ;
360333 return Math . max ( 0 , Math . min ( max , end_time - start_minutes ) ) ;
361334 }
362335}
0 commit comments