WEB-604: Add hour/minute time selection for partial day cashier allocation (#3108)
- Added hour (00-23) and minute (00-59) dropdowns to Create Cashier form - Added hour/minute dropdowns to Edit Cashier form with existing time parsing - Time fields shown conditionally when Full Day is unchecked - Added end-time-after-start-time validation - Clear stale invalidTimeRange error before re-validating - Renamed setEditChargeForm to setEditCashierForm - Added Hour and Minute i18n keys to all 13 locale files
This commit is contained in:
@@ -83,7 +83,63 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<div class="space flex-48">
|
||||
@if (!createCashierForm.value.isFullDay) {
|
||||
<div class="flex-48 layout-row gap-10px">
|
||||
<mat-form-field class="flex-50">
|
||||
<mat-label
|
||||
>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label
|
||||
>
|
||||
<mat-select formControlName="hourStartTime">
|
||||
@for (hour of hours; track hour) {
|
||||
<mat-option [value]="hour">{{ hour }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="flex-50">
|
||||
<mat-label
|
||||
>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label
|
||||
>
|
||||
<mat-select formControlName="minStartTime">
|
||||
@for (minute of minutes; track minute) {
|
||||
<mat-option [value]="minute">{{ minute }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="flex-48 layout-row gap-10px">
|
||||
<mat-form-field class="flex-50">
|
||||
<mat-label
|
||||
>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label
|
||||
>
|
||||
<mat-select formControlName="hourEndTime">
|
||||
@for (hour of hours; track hour) {
|
||||
<mat-option [value]="hour">{{ hour }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="flex-50">
|
||||
<mat-label
|
||||
>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label
|
||||
>
|
||||
<mat-select formControlName="minEndTime">
|
||||
@for (minute of minutes; track minute) {
|
||||
<mat-option [value]="minute">{{ minute }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@if (createCashierForm.hasError('invalidTimeRange')) {
|
||||
<div class="flex-48">
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.End Time' | translate }} {{ 'labels.commons.must' | translate }}
|
||||
{{ 'labels.commons.be' | translate }} {{ 'labels.commons.greater than' | translate }}
|
||||
{{ 'labels.inputs.Start Time' | translate }}
|
||||
</mat-error>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<div class="space flex-100">
|
||||
<mat-checkbox labelPosition="before" formControlName="isFullDay">
|
||||
{{ 'labels.inputs.Full Day' | translate }}?
|
||||
</mat-checkbox>
|
||||
|
||||
@@ -46,6 +46,10 @@ export class CreateCashierComponent implements OnInit {
|
||||
cashierTemplate: any;
|
||||
/** Create cashier form. */
|
||||
createCashierForm: UntypedFormGroup;
|
||||
/** Hours options for time selection (00-23). */
|
||||
hours: string[] = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0'));
|
||||
/** Minutes options for time selection (00-59). */
|
||||
minutes: string[] = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0'));
|
||||
|
||||
/**
|
||||
* Fetches cashier template from `resolve`
|
||||
@@ -85,7 +89,11 @@ export class CreateCashierComponent implements OnInit {
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
isFullDay: [false]
|
||||
isFullDay: [true],
|
||||
hourStartTime: ['00'],
|
||||
minStartTime: ['00'],
|
||||
hourEndTime: ['00'],
|
||||
minEndTime: ['00']
|
||||
});
|
||||
}
|
||||
|
||||
@@ -104,11 +112,40 @@ export class CreateCashierComponent implements OnInit {
|
||||
if (createCashierFormData.endDate instanceof Date) {
|
||||
createCashierFormData.endDate = this.dateUtils.formatDate(prevEndDate, dateFormat);
|
||||
}
|
||||
const data = {
|
||||
...createCashierFormData,
|
||||
const data: any = {
|
||||
staffId: createCashierFormData.staffId,
|
||||
description: createCashierFormData.description,
|
||||
startDate: createCashierFormData.startDate,
|
||||
endDate: createCashierFormData.endDate,
|
||||
isFullDay: createCashierFormData.isFullDay,
|
||||
dateFormat,
|
||||
locale
|
||||
};
|
||||
// Clear stale time-range errors before re-validating
|
||||
if (this.createCashierForm.hasError('invalidTimeRange')) {
|
||||
const { invalidTimeRange, ...rest } = this.createCashierForm.errors ?? {};
|
||||
this.createCashierForm.setErrors(Object.keys(rest).length ? rest : null);
|
||||
}
|
||||
// Add time fields only when not full day
|
||||
if (!createCashierFormData.isFullDay) {
|
||||
const hourStart = createCashierFormData.hourStartTime;
|
||||
const minStart = createCashierFormData.minStartTime;
|
||||
const hourEnd = createCashierFormData.hourEndTime;
|
||||
const minEnd = createCashierFormData.minEndTime;
|
||||
// Validate that end time is after start time
|
||||
const startMinutes = Number(hourStart) * 60 + Number(minStart);
|
||||
const endMinutes = Number(hourEnd) * 60 + Number(minEnd);
|
||||
if (Number.isNaN(startMinutes) || Number.isNaN(endMinutes) || endMinutes <= startMinutes) {
|
||||
this.createCashierForm.setErrors({ invalidTimeRange: true });
|
||||
return;
|
||||
}
|
||||
data.hourStartTime = hourStart;
|
||||
data.minStartTime = minStart;
|
||||
data.hourEndTime = hourEnd;
|
||||
data.minEndTime = minEnd;
|
||||
data.startTime = `${hourStart}:${minStart}`;
|
||||
data.endTime = `${hourEnd}:${minEnd}`;
|
||||
}
|
||||
this.organizationService.createCashier(this.cashierTemplate.tellerId, data).subscribe((response: any) => {
|
||||
this.router.navigate(['../'], { relativeTo: this.route });
|
||||
});
|
||||
|
||||
@@ -92,7 +92,63 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<div class="space flex-48">
|
||||
@if (!editCashierForm.value.isFullDay) {
|
||||
<div class="flex-48 layout-row gap-10px">
|
||||
<mat-form-field class="flex-50">
|
||||
<mat-label
|
||||
>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label
|
||||
>
|
||||
<mat-select formControlName="hourStartTime">
|
||||
@for (hour of hours; track hour) {
|
||||
<mat-option [value]="hour">{{ hour }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="flex-50">
|
||||
<mat-label
|
||||
>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label
|
||||
>
|
||||
<mat-select formControlName="minStartTime">
|
||||
@for (minute of minutes; track minute) {
|
||||
<mat-option [value]="minute">{{ minute }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="flex-48 layout-row gap-10px">
|
||||
<mat-form-field class="flex-50">
|
||||
<mat-label
|
||||
>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label
|
||||
>
|
||||
<mat-select formControlName="hourEndTime">
|
||||
@for (hour of hours; track hour) {
|
||||
<mat-option [value]="hour">{{ hour }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="flex-50">
|
||||
<mat-label
|
||||
>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label
|
||||
>
|
||||
<mat-select formControlName="minEndTime">
|
||||
@for (minute of minutes; track minute) {
|
||||
<mat-option [value]="minute">{{ minute }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@if (editCashierForm.hasError('invalidTimeRange')) {
|
||||
<div class="flex-48">
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.End Time' | translate }} {{ 'labels.commons.must' | translate }}
|
||||
{{ 'labels.commons.be' | translate }} {{ 'labels.commons.greater than' | translate }}
|
||||
{{ 'labels.inputs.Start Time' | translate }}
|
||||
</mat-error>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<div class="space flex-100">
|
||||
<mat-checkbox labelPosition="before" formControlName="isFullDay">
|
||||
{{ 'labels.inputs.Full Day' | translate }}?
|
||||
</mat-checkbox>
|
||||
|
||||
@@ -48,6 +48,10 @@ export class EditCashierComponent implements OnInit {
|
||||
minDate = new Date(2000, 0, 1);
|
||||
/** Maximum Date allowed. */
|
||||
maxDate = new Date();
|
||||
/** Hours options for time selection (00-23). */
|
||||
hours: string[] = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0'));
|
||||
/** Minutes options for time selection (00-59). */
|
||||
minutes: string[] = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0'));
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -70,28 +74,49 @@ export class EditCashierComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
this.maxDate = this.settingsService.maxFutureDate;
|
||||
this.setEditChargeForm();
|
||||
this.setEditCashierForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Edit Charge Form.
|
||||
* Sets Edit Cashier Form.
|
||||
*/
|
||||
setEditChargeForm() {
|
||||
setEditCashierForm() {
|
||||
// Extract hours and minutes from existing time data with safe defaults
|
||||
const startTime = this.cashierData.data.startTime || '09:00';
|
||||
const endTime = this.cashierData.data.endTime || '17:00';
|
||||
const [
|
||||
hourStart = '09',
|
||||
minStart = '00'
|
||||
] = startTime.includes(':') ? startTime.split(':') : [
|
||||
'09',
|
||||
'00'
|
||||
];
|
||||
const [
|
||||
hourEnd = '17',
|
||||
minEnd = '00'
|
||||
] = endTime.includes(':') ? endTime.split(':') : [
|
||||
'17',
|
||||
'00'
|
||||
];
|
||||
this.editCashierForm = this.formBuilder.group({
|
||||
staffId: [{ value: this.cashierData.data.staffId, disabled: true }],
|
||||
description: [this.cashierData.data.description],
|
||||
startDate: [
|
||||
this.cashierData.data.startDate && new Date(this.cashierData.data.startDate),
|
||||
this.cashierData.data.startDate ? new Date(this.cashierData.data.startDate) : null,
|
||||
Validators.required
|
||||
],
|
||||
endDate: [
|
||||
this.cashierData.data.endDate && new Date(this.cashierData.data.endDate),
|
||||
this.cashierData.data.endDate ? new Date(this.cashierData.data.endDate) : null,
|
||||
Validators.required
|
||||
],
|
||||
isFullDay: [
|
||||
this.cashierData.data.isFullDay,
|
||||
Validators.required
|
||||
]
|
||||
],
|
||||
hourStartTime: [hourStart.padStart(2, '0')],
|
||||
minStartTime: [minStart.padStart(2, '0')],
|
||||
hourEndTime: [hourEnd.padStart(2, '0')],
|
||||
minEndTime: [minEnd.padStart(2, '0')]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,12 +135,40 @@ export class EditCashierComponent implements OnInit {
|
||||
if (editCashierFormData.endDate instanceof Date) {
|
||||
editCashierFormData.endDate = this.dateUtils.formatDate(prevEndDate, dateFormat);
|
||||
}
|
||||
const data = {
|
||||
...editCashierFormData,
|
||||
const data: any = {
|
||||
staffId: this.cashierData.data.staffId,
|
||||
description: editCashierFormData.description,
|
||||
startDate: editCashierFormData.startDate,
|
||||
endDate: editCashierFormData.endDate,
|
||||
isFullDay: editCashierFormData.isFullDay,
|
||||
dateFormat,
|
||||
locale
|
||||
};
|
||||
// Clear stale time-range errors before re-validating
|
||||
if (this.editCashierForm.hasError('invalidTimeRange')) {
|
||||
const { invalidTimeRange, ...rest } = this.editCashierForm.errors ?? {};
|
||||
this.editCashierForm.setErrors(Object.keys(rest).length ? rest : null);
|
||||
}
|
||||
// Add time fields only when not full day
|
||||
if (!editCashierFormData.isFullDay) {
|
||||
const hourStart = editCashierFormData.hourStartTime;
|
||||
const minStart = editCashierFormData.minStartTime;
|
||||
const hourEnd = editCashierFormData.hourEndTime;
|
||||
const minEnd = editCashierFormData.minEndTime;
|
||||
// Validate that end time is after start time
|
||||
const startMinutes = Number(hourStart) * 60 + Number(minStart);
|
||||
const endMinutes = Number(hourEnd) * 60 + Number(minEnd);
|
||||
if (Number.isNaN(startMinutes) || Number.isNaN(endMinutes) || endMinutes <= startMinutes) {
|
||||
this.editCashierForm.setErrors({ invalidTimeRange: true });
|
||||
return;
|
||||
}
|
||||
data.hourStartTime = hourStart;
|
||||
data.minStartTime = minStart;
|
||||
data.hourEndTime = hourEnd;
|
||||
data.minEndTime = minEnd;
|
||||
data.startTime = `${hourStart}:${minStart}`;
|
||||
data.endTime = `${hourEnd}:${minEnd}`;
|
||||
}
|
||||
this.organizationService
|
||||
.updateCashier(this.cashierData.data.tellerId, this.cashierData.data.id, data)
|
||||
.subscribe((response: any) => {
|
||||
|
||||
Reference in New Issue
Block a user