npm package: @libs-ui/components-datetime-dropdown
Description: > Component chọn ngày/tháng/năm/quý dạng dropdown, hỗ trợ single và multi (from-to) selection với validation tự động.
Latest version: 0.2.356-23

# Datetime Dropdown

> Component chọn ngày/tháng/năm/quý dạng dropdown, hỗ trợ single và multi (from-to) selection với validation tự động.

## Version

`0.2.355-14`

## Khi nào sử dụng

- Cần chọn tháng/năm/quý từ dropdown thay vì calendar picker
- Cần chọn khoảng thời gian (from-to) với validation tự động
- Cần tùy chỉnh format hiển thị (`year`, `month`, `quarter`, `year-month`, `month-day`, `year-quarter`)
- Cần responsive layout tự động cho form nhỏ

## Important Notes

- `selectedDateTime` sử dụng `WritableSignal` — cần tạo signal trước khi truyền vào
- `typeFormat` quyết định số lượng và loại dropdown hiển thị
- `isMultiple=true` sẽ hiển thị 2 nhóm dropdown (From/To) với validation so sánh
- Component tự động responsive khi không đủ không gian

## Cài đặt & Import

```typescript
import {
  LibsUiComponentsDatetimeDropdownComponent,
  IEmitSingleDateDropdown,
  IEmitMultiDateDropdown,
  IDateDropdownFunctionControlEvent,
  TYPE_DATE_DROPDOWN_FORMAT,
  IFromAndToDateLabel,
  IValidDateDropdown,
} from '@libs-ui/components-datetime-dropdown';
```

## Ví dụ sử dụng

### 1. Basic Usage (month-day single)

```html
<libs_ui-components-datetime-dropdown
  [typeFormat]="'month-day'"
  (outChooseSingleDate)="onSingleDateSelected($event)"
></libs_ui-components-datetime-dropdown>
```

```typescript
import { signal } from '@angular/core';
import { IEmitSingleDateDropdown } from '@libs-ui/components-datetime-dropdown';

onSingleDateSelected(event: IEmitSingleDateDropdown) {
  console.log('Month:', event.month, 'Day:', event.day);
}
```

### 2. Different Formats

```html
<!-- Chỉ chọn năm -->
<libs_ui-components-datetime-dropdown
  [typeFormat]="'year'"
  [reverseYear]="true"
  [minYear]="2020"
  [maxYear]="2030"
  (outChooseSingleDate)="onYearSelected($event)"
></libs_ui-components-datetime-dropdown>

<!-- Chỉ chọn tháng -->
<libs_ui-components-datetime-dropdown
  [typeFormat]="'month'"
  (outChooseSingleDate)="onMonthSelected($event)"
></libs_ui-components-datetime-dropdown>

<!-- Chọn năm-tháng -->
<libs_ui-components-datetime-dropdown
  [typeFormat]="'year-month'"
  (outChooseSingleDate)="onYearMonthSelected($event)"
></libs_ui-components-datetime-dropdown>

<!-- Chọn năm-quý -->
<libs_ui-components-datetime-dropdown
  [typeFormat]="'year-quarter'"
  (outChooseSingleDate)="onYearQuarterSelected($event)"
></libs_ui-components-datetime-dropdown>
```

### 3. Multi Date Range (from-to)

```html
<libs_ui-components-datetime-dropdown
  [typeFormat]="'year-month'"
  [isMultiple]="true"
  [fromAndToDateLabel]="{ from: 'i18n_label_from', to: 'i18n_to' }"
  [selectedDateTime]="selectedRange"
  (outChooseMultiDate)="onMultiDateSelected($event)"
  (outFunctionsControl)="onFunctionsControl($event)"
></libs_ui-components-datetime-dropdown>
```

```typescript
import { signal, WritableSignal } from '@angular/core';
import {
  IEmitMultiDateDropdown,
  IEmitSingleDateDropdown,
  IDateDropdownFunctionControlEvent,
} from '@libs-ui/components-datetime-dropdown';

selectedRange: IEmitMultiDateDropdown = {
  from: signal<IEmitSingleDateDropdown>({ year: 2024, month: 1 }),
  to: signal<IEmitSingleDateDropdown>({ year: 2024, month: 12 }),
};

functionsControl!: IDateDropdownFunctionControlEvent;

onMultiDateSelected(event: IEmitMultiDateDropdown) {
  console.log('From:', event.from(), 'To:', event.to());
}

onFunctionsControl(event: IDateDropdownFunctionControlEvent) {
  this.functionsControl = event;
}
```

### 4. With Validation

```html
<libs_ui-components-datetime-dropdown
  [typeFormat]="'year-month'"
  [isMultiple]="true"
  [validRequired]="{ message: 'i18n_required', messageValidCompareTime: 'i18n_invalid_range' }"
  [ignoreAllowTimeEqual]="true"
  [ignoreValidTimeCompare]="false"
  [isCheckErrorWhenSelectItem]="true"
  [isBorderError]="isBorderError"
  (outFunctionsControl)="onFunctionsControl($event)"
  (outChooseMultiDate)="onMultiDateSelected($event)"
></libs_ui-components-datetime-dropdown>
```

```typescript
async onSubmit() {
  const isValid = await this.functionsControl.checkIsValid();
  if (!isValid) {
    return;
  }
  // Tiến hành submit
}

resetForm() {
  this.functionsControl.resetTime?.();
  this.functionsControl.resetError?.();
}
```

## API Reference

### Inputs

| Tên | Kiểu | Mặc định | Mô tả |
| --- | --- | --- | --- |
| `[classIncludeTextDisplayWhenNoSelect]` | `string \| undefined` | `undefined` | CSS class tùy chỉnh text hiển thị khi chưa chọn giá trị |
| `[disable]` | `boolean` | `false` | Vô hiệu hóa toàn bộ component |
| `[disableSecond]` | `boolean` | `false` | Vô hiệu hóa dropdown thứ hai cho đến khi dropdown đầu tiên được chọn |
| `[fromAndToDateLabel]` | `IFromAndToDateLabel` | `{ from: 'i18n_label_from', to: 'i18n_to' }` | Tùy chỉnh label From/To khi `isMultiple=true` |
| `[getItemYearDisplay]` | `(year: string) => string` | — | Hàm tùy chỉnh hiển thị label cho item năm |
| `[hiddenDate]` | `IHiddenDate \| undefined` | `undefined` | Ẩn các giá trị ngày/tháng cụ thể khỏi dropdown |
| `[ignoreAllowTimeEqual]` | `boolean` | `true` | Bỏ qua cho phép thời gian bằng nhau khi so sánh from/to. `true` = from phải nhỏ hơn to (không cho bằng) |
| `[ignoreConvertYearSelected]` | `boolean` | `false` | Bỏ qua chuyển đổi label năm đã chọn (hiển thị raw value) |
| `[ignoreFromAndToDateLabel]` | `boolean` | `false` | Ẩn label From/To khi `isMultiple=true` |
| `[ignoreRequiredValueSecondWhenNotValidRequired]` | `boolean` | `false` | Bỏ qua validate giá trị thứ 2 khi `validRequired` không được set |
| `[ignoreValidTimeCompare]` | `boolean` | `false` | Bỏ qua validation so sánh thời gian from/to |
| `[isBorderError]` | `boolean` | `false` | Hiển thị viền lỗi trên component |
| `[isCheckErrorWhenSelectItem]` | `boolean` | `true` | Kiểm tra lỗi mỗi khi chọn item |
| `[isEmitAfterChanged]` | `boolean` | `false` | Emit `outSelectedDropdown` mỗi lần chọn giá trị (bao gồm lần đầu init) |
| `[isMultiple]` | `boolean` | `false` | Chế độ multi selection (From/To) — hiển thị 2 nhóm dropdown |
| `[labelConfig]` | `ILabel \| undefined` | `undefined` | Cấu hình label (từ `@libs-ui/components-label`) |
| `[listHasButtonUnSelectOption]` | `boolean` | `false` | Hiển thị nút bỏ chọn trong dropdown list |
| `[listKeysDisable]` | `IDateDropdownDisableKeys \| undefined` | `undefined` | Danh sách key bị vô hiệu hóa cho từng dropdown (from/to) |
| `[listMaxItemShow]` | `{ year?: number; month?: number; quarter?: number; day?: number } \| undefined` | `undefined` | Giới hạn số item hiển thị cho từng loại dropdown |
| `[maxYear]` | `number` | `0` (sử dụng DEFAULT_MAX_YEAR) | Năm tối đa hiển thị trong dropdown năm |
| `[minWidth]` | `number \| undefined` | `undefined` | Chiều rộng tối thiểu của component (px) |
| `[minYear]` | `number` | `0` (sử dụng DEFAULT_MIN_YEAR) | Năm tối thiểu hiển thị trong dropdown năm |
| `[readonly]` | `boolean` | `false` | Chế độ chỉ đọc |
| `[reverseYear]` | `boolean \| undefined` | `undefined` | Đảo ngược thứ tự danh sách năm (năm mới nhất lên đầu) |
| `[selectedDateTime]` | `IEmitMultiDateDropdown \| undefined` | `undefined` | Giá trị khởi tạo — sử dụng `WritableSignal` bên trong |
| `[typeFormat]` | `TYPE_DATE_DROPDOWN_FORMAT` | `'month-day'` | Loại format hiển thị: `'year'`, `'month'`, `'quarter'`, `'year-month'`, `'month-day'`, `'year-quarter'` |
| `[validRequired]` | `IValidDateDropdown` | — | Cấu hình validation bắt buộc với message lỗi tùy chỉnh |
| `[widthByElement]` | `boolean` | `false` | Tính width dropdown theo phần tử cha |
| `[widthDropdown]` | `number` | `136` | Chiều rộng mỗi dropdown (px) |
| `[zIndex]` | `number` | `1200` | z-index của dropdown overlay |

### Outputs

| Tên | Kiểu | Mô tả |
| --- | --- | --- |
| `(outChangStageFlagMouse)` | `EventEmitter<IFlagMouse>` | Emit khi trạng thái chuột thay đổi (hover/leave) |
| `(outChooseMultiDate)` | `EventEmitter<IEmitMultiDateDropdown>` | Emit khi chọn đầy đủ cả From và To (chỉ khi `isMultiple=true`) |
| `(outChooseSingleDate)` | `EventEmitter<IEmitSingleDateDropdown>` | Emit khi chọn đầy đủ single date (chỉ khi `isMultiple=false`) |
| `(outFunctionsControl)` | `EventEmitter<IDateDropdownFunctionControlEvent>` | Emit object chứa các hàm điều khiển component từ bên ngoài |
| `(outSelectedDropdown)` | `EventEmitter<IEmitSingleDateDropdown \| IEmitMultiDateDropdown>` | Emit mỗi lần chọn giá trị (cần `isEmitAfterChanged=true`). Lưu ý: cần check `undefined` kỹ khi sử dụng |

### FunctionsControl Methods

Object nhận được từ `(outFunctionsControl)`:

| Method | Kiểu trả về | Mô tả |
| --- | --- | --- |
| `checkIsValid()` | `Promise<boolean>` | Kiểm tra validation toàn bộ component. Trả về `false` nếu có lỗi required hoặc lỗi so sánh from/to |
| `resetError()` | `Promise<void>` | Xóa tất cả trạng thái lỗi hiển thị trên component |
| `resetTime()` | `Promise<void>` | Reset toàn bộ giá trị đã chọn về trạng thái ban đầu |

## Types & Interfaces

### TYPE_DATE_DROPDOWN_FORMAT

```typescript
type TYPE_DATE_DROPDOWN_FORMAT = 'year' | 'month' | 'quarter' | 'year-month' | 'month-day' | 'year-quarter' | string;
```

### TYPE_DATE_FORMAT

```typescript
type TYPE_DATE_FORMAT = 'year' | 'month' | 'quarter' | 'day';
```

### IEmitSingleDateDropdown

```typescript
interface IEmitSingleDateDropdown {
  year?: number;
  month?: number;
  quarter?: number;
  day?: number;
}
```

### IEmitMultiDateDropdown

```typescript
interface IEmitMultiDateDropdown {
  from: WritableSignal<IEmitSingleDateDropdown>;
  to: WritableSignal<IEmitSingleDateDropdown>;
}
```

### IDateDropdownFunctionControlEvent

```typescript
interface IDateDropdownFunctionControlEvent {
  checkIsValid: () => Promise<boolean>;
  resetError?: () => Promise<void>;
  resetTime?: () => Promise<void>;
}
```

### IValidDateDropdown

```typescript
interface IValidDateDropdown {
  message?: string;
  interpolateParams?: { any: object };
  messageValidCompareTime?: string;
  interpolateParamsCompareTime?: { any: object };
}
```

### IFromAndToDateLabel

```typescript
interface IFromAndToDateLabel {
  to?: string;
  from?: string;
  classLabelTo?: string;
  classLabelFrom?: string;
}
```

### IDateDropdownDisableKeys

```typescript
interface IDateDropdownDisableKeys {
  from?: WritableSignal<IDateDropdownDisable>;
  to?: WritableSignal<IDateDropdownDisable>;
}

interface IDateDropdownDisable {
  year?: WritableSignal<Array<number>>;
  month?: WritableSignal<Array<number>>;
  quarter?: WritableSignal<Array<number>>;
  day?: WritableSignal<Array<number>>;
}
```

### IHiddenDate

```typescript
interface IHiddenDate {
  formatDate: TYPE_DATE_DROPDOWN_FORMAT;
  yearMonth?: WritableSignal<Array<WritableSignal<{ hiddenYear: number; hiddenMonths: Array<number> }>>>;
  monthDay?: WritableSignal<Array<WritableSignal<{ hiddenMonth: number; hiddenDays: Array<number> }>>>;
}
```

## Tùy chỉnh style

- Sử dụng `[classIncludeTextDisplayWhenNoSelect]` để tùy chỉnh text display khi chưa chọn giá trị
- Sử dụng `[widthDropdown]` để điều chỉnh chiều rộng mỗi dropdown (mặc định `136px`)
- Sử dụng `[fromAndToDateLabel]` để tùy chỉnh label From/To, bao gồm cả CSS class qua `classLabelFrom` và `classLabelTo`

## Demo

- Local: [http://localhost:4500/datetime/dropdown](http://localhost:4500/datetime/dropdown)

## Test

```bash
npx nx test components-datetime-dropdown
```