@libs-ui/components-datetime-picker 0.2.356-8 → 0.2.357-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,32 +1,34 @@
1
1
  # @libs-ui/components-datetime-picker
2
2
 
3
- > Component chọn ngày tháng với hỗ trợ single date, date range, time picker và custom ranges
3
+ > Component chọn ngày tháng mạnh mẽ, hỗ trợ single date, date range, time picker, quick ranges validation.
4
4
 
5
5
  ## Giới thiệu
6
6
 
7
- `LibsUiComponentsDatetimePickerComponent` là một standalone Angular component cho phép người dùng chọn ngày tháng với nhiều chế độ khác nhau: single date, date range, có hoặc không có time picker.
7
+ `LibsUiComponentsDatetimePickerComponent` là một Angular Standalone Component cho phép người dùng chọn ngày tháng với nhiều chế độ linh hoạt: single date, date range, có hoặc không có time picker. Component tích hợp sẵn quick ranges (hôm nay, tuần này, tháng này...), custom ranges mở rộng, min/max date validation, required validation và hỗ trợ two-way binding qua Signal API.
8
8
 
9
- ### Tính năng
9
+ ## Tính năng
10
10
 
11
- - ✅ Single Date Picker
12
- - ✅ Date Range Picker
13
- - ✅ Time Picker (có thể bật/tắt)
14
- - ✅ Quick Ranges (hôm nay, tuần này, tháng này, ...)
15
- - ✅ Custom Ranges
16
- - ✅ Min/Max Date validation
17
- - ✅ Required validation
18
- - ✅ Localization support
19
- - ✅ Standalone Component
20
- - ✅ OnPush Change Detection
21
- - ✅ Two-way binding với model signals
11
+ - ✅ Single Date Picker — chọn một ngày duy nhất
12
+ - ✅ Date Range Picker — chọn khoảng thời gian từ - đến
13
+ - ✅ Time Picker tích hợp bật/tắt linh hoạt qua `[hasTimePicker]`
14
+ - ✅ Quick Ranges hôm nay, hôm qua, 7 ngày, 30 ngày, tháng này, tháng trước, 90 ngày
15
+ - ✅ Custom Ranges — mở rộng danh sách chọn nhanh qua `[extendRanges]`
16
+ - ✅ Auto Apply — tự động xác nhận khi người dùng chọn xong
17
+ - ✅ Min/Max Date — giới hạn ngày có thể chọn
18
+ - ✅ Required Validation — tích hợp kiểm tra bắt buộc nhập
19
+ - ✅ Function Control API — `checkIsValid()`, `reset()`, `resetError()` qua `(outFunctionsControl)`
20
+ - ✅ Two-way binding — `[(singleDateSelected)]`, `[(dateRangeSelected)]` qua Signal model
21
+ - ✅ Label tích hợp cấu hình label trái/phải qua `[labelConfig]`
22
+ - ✅ Localization — hỗ trợ đa ngôn ngữ Tiếng Việt / English
23
+ - ✅ Standalone + OnPush — hiệu năng tối ưu
22
24
 
23
25
  ## Khi nào sử dụng
24
26
 
25
- - Khi cần chọn một ngày cụ thể
26
- - Khi cần chọn khoảng thời gian
27
- - Khi cần chọn cả ngày và giờ
28
- - Khi cần quick ranges để chọn nhanh
29
- - Khi cần validation cho date input
27
+ - Khi cần chọn một ngày cụ thể (form đơn, filter)
28
+ - Khi cần chọn khoảng thời gian để lọc dữ liệu (báo cáo, thống kê)
29
+ - Khi cần chọn cả ngày và giờ (đặt lịch, nhập thời gian)
30
+ - Khi cần quick ranges để người dùng chọn nhanh (dashboard filter)
31
+ - Khi cần validation bắt buộc nhập ngày trong form
30
32
 
31
33
  ## Cài đặt
32
34
 
@@ -34,211 +36,599 @@
34
36
  npm install @libs-ui/components-datetime-picker
35
37
  ```
36
38
 
37
- ## Sử dụng cơ bản
39
+ ## Import
38
40
 
39
- ### Date Range Picker
41
+ ```typescript
42
+ import { LibsUiComponentsDatetimePickerComponent } from '@libs-ui/components-datetime-picker';
43
+
44
+ // Interfaces & Types
45
+ import {
46
+ IEmitSingleDate,
47
+ IEmitDateRange,
48
+ IDateTimeValid,
49
+ IDateTimePickerFunctionControlEvent,
50
+ IDateRange,
51
+ LocalizationConfig,
52
+ } from '@libs-ui/components-datetime-picker';
53
+
54
+ // Define helpers
55
+ import {
56
+ getDateOptions,
57
+ getDateOptionsDefault,
58
+ getDateRangeDefault,
59
+ DEFAULT_MIN_YEAR,
60
+ DEFAULT_MAX_YEAR,
61
+ } from '@libs-ui/components-datetime-picker';
62
+ ```
63
+
64
+ ## Ví dụ sử dụng
65
+
66
+ ### 1. Date Range Picker cơ bản (không có giờ)
40
67
 
41
68
  ```typescript
42
- import { Component, signal } from '@angular/core';
69
+ import { Component } from '@angular/core';
43
70
  import { LibsUiComponentsDatetimePickerComponent, IEmitDateRange } from '@libs-ui/components-datetime-picker';
44
71
 
45
72
  @Component({
46
- selector: 'app-example',
73
+ selector: 'app-filter',
47
74
  standalone: true,
48
75
  imports: [LibsUiComponentsDatetimePickerComponent],
49
76
  template: `
50
77
  <libs_ui-components-datetime-picker
51
- [(dateRangeSelected)]="dateRangeSelected"
52
- (outSelectDateRange)="onDateRangeSelected($event)" />
78
+ [labelConfig]="{ labelLeft: 'Khoảng thời gian' }"
79
+ [widthByLabel]="false"
80
+ [isSingle]="false"
81
+ [hasTimePicker]="false"
82
+ (outSelectDateRange)="handlerSelectDateRange($event)"
83
+ />
53
84
  `,
54
85
  })
55
- export class ExampleComponent {
56
- readonly dateRangeSelected = signal<IEmitDateRange | undefined>(undefined);
57
-
58
- onDateRangeSelected(event: IEmitDateRange) {
59
- console.log('Date range selected:', event);
86
+ export class FilterComponent {
87
+ handlerSelectDateRange(event: IEmitDateRange): void {
88
+ event.stopPropagation();
89
+ console.log('startDate:', event.startDate);
90
+ console.log('endDate:', event.endDate);
91
+ console.log('quickRangeId:', event.quickRangeId);
60
92
  }
61
93
  }
62
94
  ```
63
95
 
64
- ### Single Date Picker
96
+ ### 2. Single Date Picker với giờ và giá trị mặc định
65
97
 
66
98
  ```typescript
67
99
  import { Component, signal } from '@angular/core';
68
- import { IEmitSingleDate } from '@libs-ui/components-datetime-picker';
100
+ import {
101
+ LibsUiComponentsDatetimePickerComponent,
102
+ IEmitSingleDate,
103
+ } from '@libs-ui/components-datetime-picker';
104
+ import { getDayjs } from '@libs-ui/utils';
69
105
 
70
106
  @Component({
107
+ selector: 'app-date-form',
108
+ standalone: true,
109
+ imports: [LibsUiComponentsDatetimePickerComponent],
71
110
  template: `
72
111
  <libs_ui-components-datetime-picker
112
+ [labelConfig]="{ labelLeft: 'Ngày thực hiện' }"
113
+ [widthByLabel]="false"
73
114
  [isSingle]="true"
74
- [(singleDateSelected)]="singleDateSelected" />
115
+ [hasTimePicker]="true"
116
+ [(singleDateSelected)]="selectedDate"
117
+ (outSelectSingleDate)="handlerSelectSingleDate($event)"
118
+ />
119
+ `,
120
+ })
121
+ export class DateFormComponent {
122
+ readonly selectedDate = signal<IEmitSingleDate>({ date: getDayjs() });
123
+
124
+ handlerSelectSingleDate(event: IEmitSingleDate): void {
125
+ event.stopPropagation();
126
+ console.log('date:', event.date);
127
+ console.log('displayLabel:', event.displayLabel);
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### 3. Date Range với validation bắt buộc và Function Control
133
+
134
+ ```typescript
135
+ import { Component } from '@angular/core';
136
+ import {
137
+ LibsUiComponentsDatetimePickerComponent,
138
+ IEmitDateRange,
139
+ IDateTimePickerFunctionControlEvent,
140
+ } from '@libs-ui/components-datetime-picker';
141
+
142
+ @Component({
143
+ selector: 'app-report-filter',
144
+ standalone: true,
145
+ imports: [LibsUiComponentsDatetimePickerComponent],
146
+ template: `
147
+ <libs_ui-components-datetime-picker
148
+ [labelConfig]="{ labelLeft: 'Kỳ báo cáo' }"
149
+ [widthByLabel]="false"
150
+ [isSingle]="false"
151
+ [hasTimePicker]="false"
152
+ [validRequired]="{ message: 'Vui lòng chọn khoảng thời gian' }"
153
+ (outSelectDateRange)="handlerSelectDateRange($event)"
154
+ (outFunctionsControl)="handlerFunctionsControl($event)"
155
+ />
156
+
157
+ <button (click)="handlerSubmit()">Tạo báo cáo</button>
158
+ `,
159
+ })
160
+ export class ReportFilterComponent {
161
+ private functionControl?: IDateTimePickerFunctionControlEvent;
162
+
163
+ handlerSelectDateRange(event: IEmitDateRange): void {
164
+ event.stopPropagation();
165
+ console.log('range:', event.startDate, '-', event.endDate);
166
+ }
167
+
168
+ handlerFunctionsControl(control: IDateTimePickerFunctionControlEvent): void {
169
+ this.functionControl = control;
170
+ }
171
+
172
+ async handlerSubmit(): Promise<void> {
173
+ const isValid = await this.functionControl?.checkIsValid();
174
+ if (!isValid) return;
175
+ // proceed with report generation
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### 4. Date Range với preset giá trị ban đầu (30 ngày qua)
181
+
182
+ ```typescript
183
+ import { Component, signal } from '@angular/core';
184
+ import {
185
+ LibsUiComponentsDatetimePickerComponent,
186
+ IEmitDateRange,
187
+ } from '@libs-ui/components-datetime-picker';
188
+ import { getDayjs } from '@libs-ui/utils';
189
+
190
+ @Component({
191
+ selector: 'app-dashboard-filter',
192
+ standalone: true,
193
+ imports: [LibsUiComponentsDatetimePickerComponent],
194
+ template: `
195
+ <libs_ui-components-datetime-picker
196
+ [labelConfig]="{ labelLeft: 'Lọc theo ngày' }"
197
+ [widthByLabel]="false"
198
+ [isSingle]="false"
199
+ [hasTimePicker]="false"
200
+ [(dateRangeSelected)]="dateRange"
201
+ (outSelectDateRange)="handlerSelectDateRange($event)"
202
+ />
203
+ `,
204
+ })
205
+ export class DashboardFilterComponent {
206
+ readonly dateRange = signal<IEmitDateRange>({
207
+ quickRangeId: '_30days_ago',
208
+ startDate: getDayjs().subtract(29, 'days').hour(0).minute(0).second(0).millisecond(0).format('YYYY-MM-DDTHH:mm:ss[Z]'),
209
+ endDate: getDayjs().hour(23).minute(59).second(59).format('YYYY-MM-DDTHH:mm:ss[Z]'),
210
+ });
211
+
212
+ handlerSelectDateRange(event: IEmitDateRange): void {
213
+ event.stopPropagation();
214
+ console.log('selected range:', event);
215
+ }
216
+ }
217
+ ```
218
+
219
+ ### 5. Date Range với Custom Ranges mở rộng
220
+
221
+ ```typescript
222
+ import { Component } from '@angular/core';
223
+ import {
224
+ LibsUiComponentsDatetimePickerComponent,
225
+ IEmitDateRange,
226
+ IDateRange,
227
+ } from '@libs-ui/components-datetime-picker';
228
+ import { getDayjs } from '@libs-ui/utils';
229
+
230
+ @Component({
231
+ selector: 'app-custom-range',
232
+ standalone: true,
233
+ imports: [LibsUiComponentsDatetimePickerComponent],
234
+ template: `
235
+ <libs_ui-components-datetime-picker
236
+ [labelConfig]="{ labelLeft: 'Khoảng tùy chỉnh' }"
237
+ [widthByLabel]="false"
238
+ [isSingle]="false"
239
+ [hasTimePicker]="false"
240
+ [extendRanges]="customRanges"
241
+ (outSelectDateRange)="handlerSelectDateRange($event)"
242
+ />
75
243
  `,
76
244
  })
77
- export class ExampleComponent {
78
- readonly singleDateSelected = signal<IEmitSingleDate | undefined>(undefined);
245
+ export class CustomRangeComponent {
246
+ readonly customRanges: IDateRange[] = [
247
+ {
248
+ id: 'this_quarter',
249
+ label: 'Quý này',
250
+ values: [getDayjs().startOf('quarter'), getDayjs().endOf('quarter')],
251
+ },
252
+ {
253
+ id: 'last_quarter',
254
+ label: 'Quý trước',
255
+ values: [
256
+ getDayjs().subtract(1, 'quarter').startOf('quarter'),
257
+ getDayjs().subtract(1, 'quarter').endOf('quarter'),
258
+ ],
259
+ },
260
+ ];
261
+
262
+ handlerSelectDateRange(event: IEmitDateRange): void {
263
+ event.stopPropagation();
264
+ console.log('selected:', event);
265
+ }
79
266
  }
80
267
  ```
81
268
 
82
- ### With Time Picker
269
+ ### 6. Single Date với Min/Max Date
83
270
 
84
271
  ```typescript
85
- <libs_ui-components-datetime-picker
86
- [hasTimePicker]="true"
87
- [(dateRangeSelected)]="dateRangeSelected"
88
- />
272
+ import { Component } from '@angular/core';
273
+ import {
274
+ LibsUiComponentsDatetimePickerComponent,
275
+ IEmitSingleDate,
276
+ } from '@libs-ui/components-datetime-picker';
277
+ import { getDayjs } from '@libs-ui/utils';
278
+
279
+ @Component({
280
+ selector: 'app-birth-date',
281
+ standalone: true,
282
+ imports: [LibsUiComponentsDatetimePickerComponent],
283
+ template: `
284
+ <libs_ui-components-datetime-picker
285
+ [labelConfig]="{ labelLeft: 'Ngày sinh' }"
286
+ [widthByLabel]="false"
287
+ [isSingle]="true"
288
+ [hasTimePicker]="false"
289
+ [minDate]="minDate"
290
+ [maxDate]="maxDate"
291
+ (outSelectSingleDate)="handlerSelectSingleDate($event)"
292
+ />
293
+ `,
294
+ })
295
+ export class BirthDateComponent {
296
+ readonly minDate = getDayjs('1945-01-01');
297
+ readonly maxDate = getDayjs();
298
+
299
+ handlerSelectSingleDate(event: IEmitSingleDate): void {
300
+ event.stopPropagation();
301
+ console.log('birth date:', event.date);
302
+ }
303
+ }
89
304
  ```
90
305
 
91
- ### With Validation
306
+ ### 7. Auto Apply với reset
92
307
 
93
308
  ```typescript
94
- <libs_ui-components-datetime-picker
95
- [validRequired]="{ message: 'Vui lòng chọn ngày' }"
96
- [(dateRangeSelected)]="dateRangeSelected"
97
- />
309
+ import { Component } from '@angular/core';
310
+ import {
311
+ LibsUiComponentsDatetimePickerComponent,
312
+ IEmitDateRange,
313
+ IEmitSingleDate,
314
+ IDateTimePickerFunctionControlEvent,
315
+ } from '@libs-ui/components-datetime-picker';
316
+
317
+ @Component({
318
+ selector: 'app-auto-filter',
319
+ standalone: true,
320
+ imports: [LibsUiComponentsDatetimePickerComponent],
321
+ template: `
322
+ <libs_ui-components-datetime-picker
323
+ [labelConfig]="{ labelLeft: 'Ngày tạo' }"
324
+ [widthByLabel]="false"
325
+ [isSingle]="false"
326
+ [hasTimePicker]="true"
327
+ [autoApply]="true"
328
+ [allowReset]="true"
329
+ (outSelectDateRange)="handlerSelectDateRange($event)"
330
+ (outReset)="handlerReset($event)"
331
+ (outFunctionsControl)="handlerFunctionsControl($event)"
332
+ />
333
+ `,
334
+ })
335
+ export class AutoFilterComponent {
336
+ private functionControl?: IDateTimePickerFunctionControlEvent;
337
+
338
+ handlerSelectDateRange(event: IEmitDateRange): void {
339
+ event.stopPropagation();
340
+ console.log('range:', event);
341
+ }
342
+
343
+ handlerReset(event: IEmitDateRange | IEmitSingleDate | undefined): void {
344
+ event?.stopPropagation?.();
345
+ console.log('picker reset', event);
346
+ }
347
+
348
+ handlerFunctionsControl(control: IDateTimePickerFunctionControlEvent): void {
349
+ this.functionControl = control;
350
+ }
351
+
352
+ async handlerClearPicker(): Promise<void> {
353
+ await this.functionControl?.reset(false);
354
+ }
355
+ }
98
356
  ```
99
357
 
100
- ## API
358
+ ## @Input()
359
+
360
+ | Input | Type | Default | Mô tả | Ví dụ |
361
+ |---|---|---|---|---|
362
+ | `[allowReset]` | `boolean` | `true` | Hiển thị nút reset để xóa ngày đã chọn | `[allowReset]="false"` |
363
+ | `[autoApply]` | `boolean` | `false` | Tự động xác nhận ngay khi người dùng chọn đủ ngày (không cần nhấn Lưu) | `[autoApply]="true"` |
364
+ | `[classInclude]` | `string` | `''` | CSS class thêm vào wrapper ngoài cùng của component | `[classInclude]="'mt-4'"` |
365
+ | `[classIncludeCustomRanges]` | `string` | `''` | CSS class thêm vào vùng danh sách quick ranges | `[classIncludeCustomRanges]="'w-[180px]'"` |
366
+ | `[classPickerContentInclude]` | `string` | `''` | CSS class thêm vào vùng nội dung popover bên trong | `[classPickerContentInclude]="'p-2'"` |
367
+ | `[classPickerInclude]` | `string` | `''` | CSS class thêm vào trigger button hiển thị nhãn ngày | `[classPickerInclude]="'w-full'"` |
368
+ | `[dateOptions]` | `LocalizationConfig` | `getDateOptionsDefault()` | Cấu hình localization: tên tháng, ngày, nhãn nút, danh sách quick ranges | `[dateOptions]="myConfig"` |
369
+ | `[(dateRangeSelected)]` | `IEmitDateRange` | `undefined` | Two-way binding giá trị khoảng ngày đã chọn (dùng cho date range mode) | `[(dateRangeSelected)]="dateRange"` |
370
+ | `[defaultWidth]` | `number` | `undefined` | Ghi đè chiều rộng mặc định của trigger (px). Ưu tiên sau `[widthByLabel]="false"` | `[defaultWidth]="300"` |
371
+ | `[directionPopover]` | `TYPE_POPOVER_DIRECTION` | `'bottom'` | Hướng mở của popover calendar | `[directionPopover]="'top'"` |
372
+ | `[disable]` | `boolean` | `false` | Vô hiệu hoá toàn bộ picker, không cho tương tác | `[disable]="true"` |
373
+ | `[extendRanges]` | `Array<IDateRange>` | `[]` | Danh sách quick ranges tuỳ chỉnh thêm vào ngoài danh sách mặc định | `[extendRanges]="customRanges"` |
374
+ | `[flagMouse]` | `IFlagMouse` (model) | `{ isMouseEnter: false, isMouseEnterContent: false }` | Two-way binding trạng thái hover chuột (dùng khi tích hợp với popover cha) | `[(flagMouse)]="flagMouse"` |
375
+ | `[hasTimePicker]` | `boolean` | `true` | Hiển thị bộ chọn giờ/phút bên cạnh calendar | `[hasTimePicker]="false"` |
376
+ | `[ignoreBorderQuickRange]` | `boolean` | `false` | Ẩn viền ngăn cách giữa quick ranges và calendar | `[ignoreBorderQuickRange]="true"` |
377
+ | `[ignoreStopPropagationEvent]` | `boolean` | `false` | Bỏ qua `stopPropagation` trên sự kiện click (dùng khi component cha cần nhận event bubble) | `[ignoreStopPropagationEvent]="true"` |
378
+ | `[isBorderError]` | `boolean` | `false` | Hiển thị viền đỏ lỗi ngay lập tức mà không cần chạy validation | `[isBorderError]="true"` |
379
+ | `[isNgContent]` | `boolean` | `false` | Chế độ dùng ng-content thay thế trigger mặc định | `[isNgContent]="true"` |
380
+ | `[isSingle]` | `boolean` | `false` | Chuyển sang chế độ chọn single date (thay vì date range) | `[isSingle]="true"` |
381
+ | `[labelConfig]` | `ILabel` | `undefined` | Cấu hình label hiển thị bên trái hoặc bên phải picker | `[labelConfig]="{ labelLeft: 'Ngày tạo' }"` |
382
+ | `[listYearHiddenInputSearch]` | `boolean` | `false` | Ẩn ô tìm kiếm năm trong dropdown chọn năm | `[listYearHiddenInputSearch]="true"` |
383
+ | `[maxDate]` | `Dayjs \| string` | `undefined` | Ngày tối đa người dùng có thể chọn (ngày sau ngày này bị disabled) | `[maxDate]="'2025-12-31'"` |
384
+ | `[minDate]` | `Dayjs \| string` | `undefined` | Ngày tối thiểu người dùng có thể chọn (ngày trước ngày này bị disabled) | `[minDate]="'2020-01-01'"` |
385
+ | `[placeholder]` | `string` | `undefined` | Văn bản gợi ý hiển thị khi chưa chọn ngày | `[placeholder]="'Chọn ngày'"` |
386
+ | `[positionQuickRanges]` | `'left' \| 'right'` | `'left'` | Vị trí hiển thị danh sách quick ranges (trái hay phải so với calendar) | `[positionQuickRanges]="'right'"` |
387
+ | `[rangesPopoverPosition]` | `'start' \| 'center' \| 'end'` | `'start'` | Căn chỉnh ngang của popover so với trigger | `[rangesPopoverPosition]="'end'"` |
388
+ | `[rangesPopoverPositionDistance]` | `number` | `0` | Khoảng cách dịch chuyển ngang của popover (px) | `[rangesPopoverPositionDistance]="8"` |
389
+ | `[readonly]` | `boolean` | `false` | Chế độ chỉ đọc, không cho phép thay đổi ngày | `[readonly]="true"` |
390
+ | `[(singleDateSelected)]` | `IEmitSingleDate` | `undefined` | Two-way binding giá trị ngày đơn đã chọn (dùng cho single mode) | `[(singleDateSelected)]="selectedDate"` |
391
+ | `[showCustomRangeLabel]` | `boolean` | `true` | Hiển thị tuỳ chọn "Tuỳ Chỉnh" ở cuối danh sách quick ranges | `[showCustomRangeLabel]="false"` |
392
+ | `[trackDateRageUpdateLabel]` | `boolean` | `false` | Theo dõi reactive thay đổi `dateRangeSelected` để cập nhật label hiển thị | `[trackDateRageUpdateLabel]="true"` |
393
+ | `[useColorModeExist]` | `boolean` | `false` | Dùng màu sắc theo color mode hệ thống (dark/light theme) | `[useColorModeExist]="true"` |
394
+ | `[validRequired]` | `IDateTimeValid` | `undefined` | Cấu hình validation bắt buộc nhập. Khi không có giá trị và gọi `checkIsValid()` sẽ hiển thị lỗi | `[validRequired]="{ message: 'Bắt buộc' }"` |
395
+ | `[widthByLabel]` | `boolean` | `true` | Khi `true`: chiều rộng trigger co dãn theo label. Khi `false`: dùng chiều rộng cố định tự tính theo mode | `[widthByLabel]="false"` |
396
+ | `[widthByParent]` | `boolean` | `false` | Chiều rộng trigger theo 100% chiều rộng phần tử cha | `[widthByParent]="true"` |
397
+ | `[zIndex]` | `number` | `1200` | z-index của popover calendar | `[zIndex]="1500"` |
398
+
399
+ ## @Output()
400
+
401
+ | Output | Type | Mô tả | Handler TS | Binding HTML |
402
+ |---|---|---|---|---|
403
+ | `(outChangStageFlagMouse)` | `IFlagMouse` | Phát ra khi trạng thái hover chuột thay đổi (tích hợp với popover cha) | `handlerChangStageFlagMouse(e: IFlagMouse): void { e.stopPropagation?.(); ... }` | `(outChangStageFlagMouse)="handlerChangStageFlagMouse($event)"` |
404
+ | `(outFunctionsControl)` | `IDateTimePickerFunctionControlEvent` | Phát ra ngay khi component khởi tạo, cung cấp API kiểm soát: `checkIsValid`, `reset`, `resetError` | `handlerFunctionsControl(e: IDateTimePickerFunctionControlEvent): void { this.funcCtrl = e; }` | `(outFunctionsControl)="handlerFunctionsControl($event)"` |
405
+ | `(outReset)` | `IEmitDateRange \| IEmitSingleDate \| undefined` | Phát ra khi người dùng nhấn nút reset | `handlerReset(e: IEmitDateRange \| IEmitSingleDate \| undefined): void { e?.stopPropagation?.(); ... }` | `(outReset)="handlerReset($event)"` |
406
+ | `(outSelectDateRange)` | `IEmitDateRange` | Phát ra khi người dùng xác nhận chọn khoảng ngày (date range mode) | `handlerSelectDateRange(e: IEmitDateRange): void { e.stopPropagation(); ... }` | `(outSelectDateRange)="handlerSelectDateRange($event)"` |
407
+ | `(outSelectSingleDate)` | `IEmitSingleDate` | Phát ra khi người dùng xác nhận chọn một ngày (single mode) | `handlerSelectSingleDate(e: IEmitSingleDate): void { e.stopPropagation(); ... }` | `(outSelectSingleDate)="handlerSelectSingleDate($event)"` |
408
+
409
+ ## Function Control API
410
+
411
+ Nhận qua `(outFunctionsControl)` khi component khởi tạo. Dùng để tương tác chương trình với picker.
412
+
413
+ ```typescript
414
+ import { IDateTimePickerFunctionControlEvent } from '@libs-ui/components-datetime-picker';
415
+
416
+ export class MyComponent {
417
+ private functionControl?: IDateTimePickerFunctionControlEvent;
418
+
419
+ handlerFunctionsControl(control: IDateTimePickerFunctionControlEvent): void {
420
+ this.functionControl = control;
421
+ }
422
+
423
+ // Kiểm tra hợp lệ (hiển thị lỗi nếu validRequired được cấu hình và chưa chọn ngày)
424
+ async handlerValidate(): Promise<void> {
425
+ const isValid = await this.functionControl?.checkIsValid();
426
+ console.log('isValid:', isValid); // true | false
427
+ }
428
+
429
+ // Reset picker về trạng thái trống
430
+ async handlerClear(): Promise<void> {
431
+ await this.functionControl?.reset(false);
432
+ }
433
+
434
+ // Reset về giá trị cụ thể
435
+ async handlerResetToRange(): Promise<void> {
436
+ await this.functionControl?.reset(false, {
437
+ startDate: '2024-01-01',
438
+ endDate: '2024-01-31',
439
+ });
440
+ }
101
441
 
102
- ### Inputs
442
+ // Xóa thông báo lỗi đang hiển thị
443
+ async handlerClearError(): Promise<void> {
444
+ await this.functionControl?.resetError();
445
+ }
103
446
 
104
- | Property | Type | Default | Description |
105
- | ------------------------ | -------------------- | ----------- | ------------------------------------------ |
106
- | `[isSingle]` | `boolean` | `false` | Chế độ chọn single date thay vì date range |
107
- | `[hasTimePicker]` | `boolean` | `true` | Hiển thị time picker |
108
- | `[dateOptions]` | `LocalizationConfig` | `default` | Cấu hình localization và custom ranges |
109
- | `[allowReset]` | `boolean` | `true` | Cho phép reset date đã chọn |
110
- | `[disable]` | `boolean` | `false` | Disable picker |
111
- | `[readonly]` | `boolean` | `false` | Readonly mode |
112
- | `[minDate]` | `Dayjs \| string` | `undefined` | Ngày tối thiểu có thể chọn |
113
- | `[maxDate]` | `Dayjs \| string` | `undefined` | Ngày tối đa có thể chọn |
114
- | `[placeholder]` | `string` | `undefined` | Placeholder text |
115
- | `[validRequired]` | `IDateTimeValid` | `undefined` | Validation config |
116
- | `[extendRanges]` | `Array<IDateRange>` | `[]` | Custom quick ranges |
117
- | `[autoApply]` | `boolean` | `false` | Tự động apply khi chọn date |
118
- | `[(singleDateSelected)]` | `IEmitSingleDate` | `undefined` | Two-way binding cho single date |
119
- | `[(dateRangeSelected)]` | `IEmitDateRange` | `undefined` | Two-way binding cho date range |
447
+ // Đặt thông báo lỗi tuỳ chỉnh
448
+ async handlerSetError(): Promise<void> {
449
+ await this.functionControl?.resetError('Ngày không hợp lệ với kỳ kế toán');
450
+ }
451
+ }
452
+ ```
120
453
 
121
- ### Outputs
454
+ | Method | Signature | Mô tả |
455
+ |---|---|---|
456
+ | `checkIsValid` | `() => Promise<boolean>` | Chạy validation, hiển thị lỗi nếu `[validRequired]` được cấu hình và chưa chọn ngày. Trả về `true` nếu hợp lệ |
457
+ | `reset` | `(checkIsValid?: boolean, dateRangeReset?: IEmitDateRange, singleDateReset?: IEmitSingleDate) => Promise<void>` | Reset picker. `checkIsValid=true` sẽ chạy lại validation sau reset. Có thể truyền giá trị để reset về ngày cụ thể |
458
+ | `resetError` | `(message?: string, interpolateParams?: TYPE_OBJECT) => Promise<void>` | Xóa hoặc đặt lại thông báo lỗi. Gọi không tham số để xóa lỗi |
122
459
 
123
- | Property | Type | Description |
124
- | ----------------------- | ------------------------------------- | -------------------------- |
125
- | `(outSelectSingleDate)` | `IEmitSingleDate` | Event khi chọn single date |
126
- | `(outSelectDateRange)` | `IEmitDateRange` | Event khi chọn date range |
127
- | `(outReset)` | `IEmitDateRange \| IEmitSingleDate` | Event khi reset |
128
- | `(outFunctionsControl)` | `IDateTimePickerFunctionControlEvent` | Function control event |
460
+ ## Types & Interfaces
129
461
 
130
- ## Interfaces
462
+ ```typescript
463
+ import {
464
+ IEmitSingleDate,
465
+ IEmitDateRange,
466
+ IDateTimeValid,
467
+ IDateTimePickerFunctionControlEvent,
468
+ IDateRange,
469
+ LocalizationConfig,
470
+ TYPE_RANGE_KEY,
471
+ } from '@libs-ui/components-datetime-picker';
472
+ ```
131
473
 
132
474
  ### IEmitSingleDate
133
475
 
476
+ Dữ liệu trả về khi chọn single date.
477
+
134
478
  ```typescript
135
479
  export interface IEmitSingleDate {
136
- date?: Dayjs | string;
137
- displayLabel?: string;
480
+ displayLabel?: string; // Chuỗi hiển thị trên trigger (VD: "15/06/2024 09:30")
481
+ date: string | Dayjs | undefined; // Giá trị ngày đã chọn
482
+ values?: {
483
+ month: string | number;
484
+ year: string | number;
485
+ day: string | number;
486
+ hour?: string | number;
487
+ minute?: string | number;
488
+ };
138
489
  }
139
490
  ```
140
491
 
141
492
  ### IEmitDateRange
142
493
 
494
+ Dữ liệu trả về khi chọn date range.
495
+
143
496
  ```typescript
144
497
  export interface IEmitDateRange {
145
- startDate?: Dayjs | string;
146
- endDate?: Dayjs | string;
147
- displayLabel?: string;
148
- quickRangeId?: string;
498
+ quickRangeId?: string; // ID của quick range đã chọn (VD: 'today', '_7days_ago', 'this_month')
499
+ displayLabel?: string; // Chuỗi hiển thị (VD: "01/06/2024 - 30/06/2024")
500
+ startDate: string | Dayjs | undefined;
501
+ endDate: string | Dayjs | undefined;
149
502
  }
150
503
  ```
151
504
 
152
505
  ### IDateTimeValid
153
506
 
507
+ Cấu hình validation bắt buộc nhập.
508
+
154
509
  ```typescript
155
510
  export interface IDateTimeValid {
156
- message?: string;
157
- interpolateParams?: TYPE_OBJECT;
511
+ message?: string; // Thông báo lỗi tuỳ chỉnh (hỗ trợ i18n key)
512
+ interpolateParams?: Record<string, any>; // Tham số nội suy cho i18n
513
+ ignoreMessage?: boolean; // Ẩn văn bản lỗi, chỉ hiện viền đỏ
514
+ }
515
+ ```
516
+
517
+ ### IDateTimePickerFunctionControlEvent
518
+
519
+ API kiểm soát picker từ bên ngoài, nhận qua `(outFunctionsControl)`.
520
+
521
+ ```typescript
522
+ export interface IDateTimePickerFunctionControlEvent {
523
+ checkIsValid: () => Promise<boolean>;
524
+ resetError: (message?: string, interpolateParams?: TYPE_OBJECT) => Promise<void>;
525
+ reset: (
526
+ checkIsValid?: boolean,
527
+ dateRangeSelected?: IEmitDateRange,
528
+ singleDateReset?: IEmitSingleDate
529
+ ) => Promise<void>;
530
+ }
531
+ ```
532
+
533
+ ### IDateRange
534
+
535
+ Cấu trúc một mục custom range trong `[extendRanges]`.
536
+
537
+ ```typescript
538
+ export interface IDateRange {
539
+ id: string; // ID duy nhất, sẽ được trả về trong IEmitDateRange.quickRangeId
540
+ label: string; // Nhãn hiển thị trong danh sách quick ranges
541
+ values?: Array<Dayjs>; // [startDate, endDate] - nếu không truyền, sẽ hiển thị như "Custom Range"
158
542
  }
159
543
  ```
160
544
 
161
545
  ### LocalizationConfig
162
546
 
547
+ Cấu hình ngôn ngữ và danh sách quick ranges mặc định.
548
+
163
549
  ```typescript
164
550
  export interface LocalizationConfig {
165
- monthNames: string[];
166
- ranges: { [key: string]: string };
167
- daysOfWeek: string[];
168
- applyLabel: string;
169
- cancelLabel: string;
170
- fromLabel: string;
171
- toLabel: string;
172
- customRangeLabel: string;
551
+ monthNames: string[]; // 12 tên tháng
552
+ ranges: { [key: string]: string }; // Map ID quick range → nhãn hiển thị
553
+ daysOfWeek: string[]; // 7 tên ngày trong tuần
554
+ applyLabel: string; // Nhãn nút xác nhận
555
+ cancelLabel: string; // Nhãn nút huỷ
556
+ fromLabel: string; // Nhãn "Từ"
557
+ toLabel: string; // Nhãn "Đến"
558
+ customRangeLabel: string; // Nhãn tùy chọn "Tuỳ Chỉnh"
173
559
  format?: string;
174
560
  direction?: string;
175
561
  separator?: string;
176
562
  weekLabel?: string;
177
563
  clearLabel?: string;
178
- firstDay?: number;
564
+ firstDay?: number; // Ngày đầu tuần (0 = CN, 1 = T2)
179
565
  displayFormat?: string;
180
566
  }
181
567
  ```
182
568
 
183
- ## Ví dụ nâng cao
569
+ ### TYPE_RANGE_KEY
184
570
 
185
- ### Custom Ranges
571
+ Danh sách ID quick range mặc định của hệ thống.
186
572
 
187
573
  ```typescript
188
- import { IDateRange } from '@libs-ui/components-datetime-picker';
189
- import { getDayjs } from '@libs-ui/utils';
574
+ export type TYPE_RANGE_KEY =
575
+ | 'today'
576
+ | 'yesterday'
577
+ | '_7days_ago'
578
+ | '_30days_ago'
579
+ | 'this_month'
580
+ | 'last_month'
581
+ | '_3months_ago';
582
+ ```
190
583
 
191
- const customRanges: IDateRange[] = [
192
- {
193
- id: 'last_7_days',
194
- label: '7 ngày qua',
195
- startDate: getDayjs().subtract(7, 'days'),
196
- endDate: getDayjs()
197
- },
198
- {
199
- id: 'last_30_days',
200
- label: '30 ngày qua',
201
- startDate: getDayjs().subtract(30, 'days'),
202
- endDate: getDayjs()
203
- }
204
- ];
584
+ ## Defines & Helpers
205
585
 
206
- // Sử dụng
207
- <libs_ui-components-datetime-picker
208
- [extendRanges]="customRanges"
209
- [(dateRangeSelected)]="dateRangeSelected"
210
- />
586
+ ```typescript
587
+ import {
588
+ getDateOptions,
589
+ getDateOptionsDefault,
590
+ getDateRangeDefault,
591
+ defaultLocaleConfig,
592
+ DEFAULT_MIN_YEAR,
593
+ DEFAULT_MAX_YEAR,
594
+ } from '@libs-ui/components-datetime-picker';
595
+
596
+ // Lấy LocalizationConfig theo ngôn ngữ hiện tại (VI/EN)
597
+ const config = getDateOptions();
598
+
599
+ // Lấy LocalizationConfig đầy đủ với defaultLocaleConfig merge vào
600
+ const fullConfig = getDateOptionsDefault();
601
+
602
+ // Lấy object mapping ID quick range → [startDayjs, endDayjs]
603
+ const ranges = getDateRangeDefault();
604
+ // ranges.today → [startOfDay, endOfDay]
605
+ // ranges._7days_ago → [6 ngày trước 00:00, hôm nay 23:59]
606
+
607
+ // Năm tối thiểu/tối đa mặc định
608
+ console.log(DEFAULT_MIN_YEAR); // 1945
609
+ console.log(DEFAULT_MAX_YEAR); // năm hiện tại + 5
211
610
  ```
212
611
 
213
- ### Min/Max Date
612
+ ## Lưu ý quan trọng
214
613
 
215
- ```typescript
216
- import { getDayjs } from '@libs-ui/utils';
614
+ ⚠️ **isSingle vs isSingle=false**: Khi `[isSingle]="true"`, lắng nghe `(outSelectSingleDate)` và dùng `[(singleDateSelected)]`. Khi `[isSingle]="false"` (mặc định), lắng nghe `(outSelectDateRange)` và dùng `[(dateRangeSelected)]`. Trộn lẫn hai mode sẽ không hiển thị giá trị đúng.
217
615
 
218
- const minDate = getDayjs().subtract(1, 'year');
219
- const maxDate = getDayjs().add(1, 'year');
616
+ ⚠️ **hasTimePicker mặc định là true**: Nếu không cần giờ phút, hãy truyền rõ `[hasTimePicker]="false"`. Khi `true`, format hiển thị là `DD/MM/YYYY HH:mm`; khi `false` là `DD/MM/YYYY`.
220
617
 
221
- <libs_ui-components-datetime-picker
222
- [minDate]="minDate"
223
- [maxDate]="maxDate"
224
- [(dateRangeSelected)]="dateRangeSelected"
225
- />
226
- ```
618
+ ⚠️ **outFunctionsControl emit khi ngOnInit**: `(outFunctionsControl)` chỉ phát ra một lần duy nhất khi component khởi tạo. Luôn lưu giá trị nhận được vào biến class để dùng sau.
227
619
 
228
- ## Dependencies
620
+ ⚠️ **Preset giá trị ban đầu**: Khi truyền `[dateRangeSelected]` hoặc `[singleDateSelected]` với `quickRangeId`, đảm bảo `quickRangeId` trùng khớp với key trong `dateOptions.ranges`. Nếu không khớp, nhãn hiển thị sẽ tính lại từ `startDate`/`endDate`.
229
621
 
230
- - `@angular/core` >= 18.0.0
231
- - `@angular/common` >= 18.0.0
232
- - `dayjs`
233
- - `@libs-ui/components-label`
234
- - `@libs-ui/components-popover`
235
- - `@libs-ui/utils`
236
- - `@ngx-translate/core`
622
+ ⚠️ **trackDateRageUpdateLabel**: Mặc định `false` label chỉ cập nhật khi người dùng tương tác. Đặt `[trackDateRageUpdateLabel]="true"` nếu cần label cập nhật reactive khi `dateRangeSelected` thay đổi từ bên ngoài (ví dụ: reset programmatic).
237
623
 
238
- ## Demo
624
+ ⚠️ **Format ngày truyền vào**: `minDate`, `maxDate`, `startDate`, `endDate` chấp nhận cả `Dayjs` object lẫn `string` ISO (VD: `'2024-06-15'`, `'2024-06-15T00:00:00Z'`). Khuyến nghị dùng `getDayjs()` từ `@libs-ui/utils` để đảm bảo timezone nhất quán.
239
625
 
240
- Xem demo tại: [http://localhost:4500/datetime/picker](http://localhost:4500/datetime/picker)
626
+ ⚠️ **widthByLabel mặc định là true**: Khi `true`, chiều rộng trigger tự co dãn theo nội dung label ngày. Để có chiều rộng cố định đẹp hơn trong form, đặt `[widthByLabel]="false"`. Chiều rộng tự tính lúc đó: range+time=322px, range=232px, single+time=190px, single=152px.
241
627
 
242
- ## License
628
+ ## Demo
629
+
630
+ ```bash
631
+ npx nx serve core-ui
632
+ ```
243
633
 
244
- MIT
634
+ Truy cập: http://localhost:4500/components/datetime/picker