From a99c9a53cc3d31e22376acd219c412052997eae1 Mon Sep 17 00:00:00 2001 From: yami <310851010@qq.com> Date: Thu, 4 Apr 2024 19:47:23 +0800 Subject: [PATCH 01/10] feat(module:date-picker): quarter selection of date picker(#7818,#7380) --- components/core/time/candy-date.ts | 34 +++- .../date-picker/date-picker.component.ts | 5 +- components/date-picker/date-picker.module.ts | 7 +- components/date-picker/date-picker.service.ts | 1 + components/date-picker/demo/basic.ts | 2 + components/date-picker/demo/date-render.ts | 18 +- components/date-picker/demo/disabled-date.ts | 2 + components/date-picker/demo/format.ts | 3 + components/date-picker/demo/inline.ts | 16 ++ components/date-picker/demo/range-picker.ts | 2 + components/date-picker/demo/size.ts | 2 + components/date-picker/demo/switch.ts | 1 + components/date-picker/doc/index.en-US.md | 2 +- components/date-picker/doc/index.zh-CN.md | 2 +- .../date-picker/inner-popup.component.ts | 36 ++++ .../date-picker/lib/lib-packer.module.ts | 10 +- components/date-picker/lib/public-api.ts | 2 + .../lib/quarter-header.component.ts | 39 +++++ .../lib/quarter-table.component.ts | 128 ++++++++++++++ components/date-picker/public-api.ts | 1 + .../quarter-picker.component.spec.ts | 163 ++++++++++++++++++ .../date-picker/quarter-picker.component.ts | 19 ++ components/date-picker/standard-types.ts | 2 +- components/i18n/date-helper.service.ts | 6 +- components/i18n/languages/default.ts | 1 + components/i18n/languages/en_US.ts | 1 + components/i18n/languages/zh_CN.ts | 1 + components/i18n/languages/zh_HK.ts | 1 + components/i18n/languages/zh_TW.ts | 1 + components/i18n/nz-i18n.interface.ts | 1 + 30 files changed, 493 insertions(+), 16 deletions(-) create mode 100644 components/date-picker/lib/quarter-header.component.ts create mode 100644 components/date-picker/lib/quarter-table.component.ts create mode 100644 components/date-picker/quarter-picker.component.spec.ts create mode 100644 components/date-picker/quarter-picker.component.ts diff --git a/components/core/time/candy-date.ts b/components/core/time/candy-date.ts index 62165b19964..873ced432ea 100644 --- a/components/core/time/candy-date.ts +++ b/components/core/time/candy-date.ts @@ -9,6 +9,7 @@ import { differenceInCalendarDays, differenceInCalendarMonths, differenceInCalendarYears, + differenceInCalendarQuarters, differenceInHours, differenceInMinutes, differenceInSeconds, @@ -20,19 +21,23 @@ import { isSameMonth, isSameSecond, isSameYear, + isSameQuarter, isToday, isValid, setDay, setMonth, setYear, startOfMonth, - startOfWeek + startOfWeek, + getQuarter, + setQuarter, + addQuarters } from 'date-fns'; import { warn } from 'ng-zorro-antd/core/logger'; import { IndexableObject, NzSafeAny } from 'ng-zorro-antd/core/types'; -export type CandyDateMode = 'decade' | 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'; +export type CandyDateMode = 'decade' | 'year' | 'quarter' | 'month' | 'day' | 'hour' | 'minute' | 'second'; export type NormalizedMode = 'decade' | 'year' | 'month'; export type WeekDayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6; export type CandyDateType = CandyDate | Date | null; @@ -192,6 +197,18 @@ export class CandyDate implements IndexableObject { return new CandyDate(date); } + getQuarter(): number { + return getQuarter(this.nativeDate); + } + + setQuarter(quarter: number): CandyDate { + return new CandyDate(setQuarter(this.nativeDate, quarter)); + } + + addQuarter(quarter: number): CandyDate { + return new CandyDate(addQuarters(this.nativeDate, quarter)); + } + addDays(amount: number): CandyDate { return this.setDate(this.getDate() + amount); } @@ -218,6 +235,9 @@ export class CandyDate implements IndexableObject { case 'year': fn = isSameYear; break; + case 'quarter': + fn = isSameQuarter; + break; case 'month': fn = isSameMonth; break; @@ -243,6 +263,9 @@ export class CandyDate implements IndexableObject { isSameYear(date: CandyDateType): boolean { return this.isSame(date, 'year'); } + isSameQuarter(date: CandyDateType): boolean { + return this.isSame(date, 'quarter'); + } isSameMonth(date: CandyDateType): boolean { return this.isSame(date, 'month'); @@ -273,6 +296,9 @@ export class CandyDate implements IndexableObject { case 'year': fn = differenceInCalendarYears; break; + case 'quarter': + fn = differenceInCalendarQuarters; + break; case 'month': fn = differenceInCalendarMonths; break; @@ -299,6 +325,10 @@ export class CandyDate implements IndexableObject { return this.isBefore(date, 'year'); } + isBeforeQuarter(date: CandyDateType): boolean { + return this.isBefore(date, 'quarter'); + } + isBeforeMonth(date: CandyDateType): boolean { return this.isBefore(date, 'month'); } diff --git a/components/date-picker/date-picker.component.ts b/components/date-picker/date-picker.component.ts index 6fbb63e89c0..7b3c3928eda 100644 --- a/components/date-picker/date-picker.component.ts +++ b/components/date-picker/date-picker.component.ts @@ -95,7 +95,7 @@ export type NzPlacement = 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight'; @Component({ encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, - selector: 'nz-date-picker,nz-week-picker,nz-month-picker,nz-year-picker,nz-range-picker', + selector: 'nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker', exportAs: 'nzDatePicker', template: ` @if (!nzInline) { @@ -763,6 +763,7 @@ export class NzDatePickerComponent implements OnInit, OnChanges, AfterViewInit, setModeAndFormat(): void { const inputFormats: { [key in NzDateMode]?: string } = { year: 'yyyy', + quarter: 'yyyy-QQ', month: 'yyyy-MM', week: 'YYYY-ww', date: this.nzShowTime ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd' @@ -834,6 +835,7 @@ export class NzDatePickerComponent implements OnInit, OnChanges, AfterViewInit, if (!this.isCustomPlaceHolder && this.nzLocale) { const defaultPlaceholder: { [key in NzDateMode]?: string } = { year: this.getPropertyOfLocale('yearPlaceholder'), + quarter: this.getPropertyOfLocale('quarterPlaceholder'), month: this.getPropertyOfLocale('monthPlaceholder'), week: this.getPropertyOfLocale('weekPlaceholder'), date: this.getPropertyOfLocale('placeholder') @@ -841,6 +843,7 @@ export class NzDatePickerComponent implements OnInit, OnChanges, AfterViewInit, const defaultRangePlaceholder: { [key in NzDateMode]?: string[] } = { year: this.getPropertyOfLocale('rangeYearPlaceholder'), + quarter: this.getPropertyOfLocale('rangeQuarterPlaceholder'), month: this.getPropertyOfLocale('rangeMonthPlaceholder'), week: this.getPropertyOfLocale('rangeWeekPlaceholder'), date: this.getPropertyOfLocale('rangePlaceholder') diff --git a/components/date-picker/date-picker.module.ts b/components/date-picker/date-picker.module.ts index f46af7345fb..ebfd7ce96f3 100644 --- a/components/date-picker/date-picker.module.ts +++ b/components/date-picker/date-picker.module.ts @@ -10,6 +10,7 @@ import { NzDatePickerComponent } from './date-picker.component'; import { DateRangePopupComponent } from './date-range-popup.component'; import { InnerPopupComponent } from './inner-popup.component'; import { NzMonthPickerComponent } from './month-picker.component'; +import { NzQuarterPickerComponent } from './quarter-picker.component'; import { NzRangePickerComponent } from './range-picker.component'; import { NzWeekPickerComponent } from './week-picker.component'; import { NzYearPickerComponent } from './year-picker.component'; @@ -23,14 +24,16 @@ import { NzYearPickerComponent } from './year-picker.component'; NzRangePickerComponent, CalendarFooterComponent, InnerPopupComponent, - DateRangePopupComponent + DateRangePopupComponent, + NzQuarterPickerComponent ], exports: [ NzDatePickerComponent, NzRangePickerComponent, NzMonthPickerComponent, NzYearPickerComponent, - NzWeekPickerComponent + NzWeekPickerComponent, + NzQuarterPickerComponent ] }) export class NzDatePickerModule {} diff --git a/components/date-picker/date-picker.service.ts b/components/date-picker/date-picker.service.ts index d38b6d452cf..6efab918ec9 100644 --- a/components/date-picker/date-picker.service.ts +++ b/components/date-picker/date-picker.service.ts @@ -51,6 +51,7 @@ export class DatePickerService implements OnDestroy { const parentPanels: { [key in NzDateMode]?: NormalizedMode } = { date: 'month', month: 'year', + quarter: 'year', year: 'decade' }; if (this.isRange) { diff --git a/components/date-picker/demo/basic.ts b/components/date-picker/demo/basic.ts index 0f218270223..430e40ccad6 100644 --- a/components/date-picker/demo/basic.ts +++ b/components/date-picker/demo/basic.ts @@ -13,6 +13,8 @@ import { en_US, NzI18nService, zh_CN } from 'ng-zorro-antd/i18n'; + + Switch language for all pickers diff --git a/components/date-picker/demo/date-render.ts b/components/date-picker/demo/date-render.ts index ddf9dda33e7..f19b29cece8 100644 --- a/components/date-picker/demo/date-render.ts +++ b/components/date-picker/demo/date-render.ts @@ -7,9 +7,13 @@ import { Component } from '@angular/core'; - - {{ current.getDate() }} - + {{ current.getDate() }} + + + + + + {{ getQuarter(current) }} `, styles: [ @@ -25,4 +29,10 @@ import { Component } from '@angular/core'; ` ] }) -export class NzDemoDatePickerDateRenderComponent {} +export class NzDemoDatePickerDateRenderComponent { + getQuarter(date: Date): string { + const quarter = Math.floor((date.getMonth() + 3) / 3); + const quarterMapper: Record = { 1: '一', 2: '二', 3: '三', 4: '四' }; + return `${quarterMapper[quarter]}季度`; + } +} diff --git a/components/date-picker/demo/disabled-date.ts b/components/date-picker/demo/disabled-date.ts index 92cfb433bed..7dc000f3e99 100644 --- a/components/date-picker/demo/disabled-date.ts +++ b/components/date-picker/demo/disabled-date.ts @@ -16,6 +16,8 @@ import { DisabledTimeFn, DisabledTimePartial } from 'ng-zorro-antd/date-picker'; + + + + `, styles: [ @@ -21,4 +23,5 @@ import { Component } from '@angular/core'; export class NzDemoDatePickerFormatComponent { dateFormat = 'yyyy/MM/dd'; monthFormat = 'yyyy/MM'; + quarterFormat = 'yyyy/QQ'; } diff --git a/components/date-picker/demo/inline.ts b/components/date-picker/demo/inline.ts index 8c2451f126d..a49fa348298 100644 --- a/components/date-picker/demo/inline.ts +++ b/components/date-picker/demo/inline.ts @@ -15,6 +15,14 @@ import { getISOWeek } from 'date-fns'; + + + @@ -45,6 +53,14 @@ import { getISOWeek } from 'date-fns'; (ngModelChange)="onChange($event)" > + + + + + `, styles: [ diff --git a/components/date-picker/demo/size.ts b/components/date-picker/demo/size.ts index ba5997f04e5..7e9d0903b2a 100644 --- a/components/date-picker/demo/size.ts +++ b/components/date-picker/demo/size.ts @@ -16,6 +16,8 @@ import { Component } from '@angular/core'; + + `, styles: [ diff --git a/components/date-picker/demo/switch.ts b/components/date-picker/demo/switch.ts index c577559c1ab..c43167e5ef8 100644 --- a/components/date-picker/demo/switch.ts +++ b/components/date-picker/demo/switch.ts @@ -8,6 +8,7 @@ import { Component } from '@angular/core'; + diff --git a/components/date-picker/doc/index.en-US.md b/components/date-picker/doc/index.en-US.md index 265797f8e83..20216e4dd56 100644 --- a/components/date-picker/doc/index.en-US.md +++ b/components/date-picker/doc/index.en-US.md @@ -46,7 +46,7 @@ The following APIs are shared by nz-date-picker, nz-range-picker. | `[nzFormat]` | to set the date format, see `nzFormat special instructions` | `string` | - | | `[nzInputReadOnly]` | set the readonly attribute of the input tag (avoids virtual keyboard on touch devices) | `boolean` | `false` | - | | `[nzLocale]` | localization configuration | `object` | [default](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | - | -| `[nzMode]` | Set picker mode | `'date'` \| `'week'` \| `'month'` \| `'year'` | `'date'` | +| `[nzMode]` | Set picker mode | `'date' \| 'week' \| 'month' \| 'quarter' \| 'year'` | `'date'` | | `[nzPlaceHolder]` | placeholder of date input | `string` \| `string[]` | - | | `[nzPopupStyle]` | to customize the style of the popup calendar | `object` | `{}` | - | | `[nzRenderExtraFooter]` | render extra footer in panel | `TemplateRef \| string \| (() => TemplateRef \| string)` | - | diff --git a/components/date-picker/doc/index.zh-CN.md b/components/date-picker/doc/index.zh-CN.md index 8338c9db0dd..b37486b1948 100644 --- a/components/date-picker/doc/index.zh-CN.md +++ b/components/date-picker/doc/index.zh-CN.md @@ -46,7 +46,7 @@ registerLocaleData(zh); | `[nzFormat]` | 展示的日期格式,见`nzFormat特别说明` | `string` | - | | `[nzInputReadOnly]` | 为 input 标签设置只读属性(避免在移动设备上触发小键盘) | `boolean` | `false` | - | | `[nzLocale]` | 国际化配置 | `object` | [默认配置](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | - | -| `[nzMode]` | 选择模式 | `'date'` \| `'week'` \| `'month'` \| `'year'` | `'date'` | +| `[nzMode]` | 选择模式 | `'date' \| 'week' \| 'month' \| 'quarter' \| 'year'` | `'date'` | | `[nzPlaceHolder]` | 输入框提示文字 | `string \| string[]` | - | - | | `[nzPopupStyle]` | 额外的弹出日历样式 | `object` | `{}` | - | | `[nzRenderExtraFooter]` | 在面板中添加额外的页脚 | `TemplateRef \| string \| (() => TemplateRef \| string)` | - | diff --git a/components/date-picker/inner-popup.component.ts b/components/date-picker/inner-popup.component.ts index 6c862c5be25..2beb7a808fd 100644 --- a/components/date-picker/inner-popup.component.ts +++ b/components/date-picker/inner-popup.component.ts @@ -104,6 +104,31 @@ import { PREFIX_CLASS } from './util'; /> } + @case ('quarter') { + + + + + } @default { this.changeMode('year'), + label: this.dateHelper.format(this.value.nativeDate, transCompatFormat(this.locale.yearFormat)) + } + ]; + } +} diff --git a/components/date-picker/lib/quarter-table.component.ts b/components/date-picker/lib/quarter-table.component.ts new file mode 100644 index 00000000000..705cd82468a --- /dev/null +++ b/components/date-picker/lib/quarter-table.component.ts @@ -0,0 +1,128 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +import { NgClass, NgForOf, NgIf, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet } from '@angular/common'; +import { ChangeDetectionStrategy, Component, OnChanges, OnInit, ViewEncapsulation } from '@angular/core'; + +import { CandyDate } from 'ng-zorro-antd/core/time'; +import { isNonEmptyString, isTemplateRef, valueFunctionProp } from 'ng-zorro-antd/core/util'; +import { DateHelperService } from 'ng-zorro-antd/i18n'; + +import { AbstractTable } from './abstract-table'; +import { DateBodyRow, DateCell } from './interface'; + +@Component({ + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'quarter-table', + exportAs: 'quarterTable', + templateUrl: 'abstract-table.html', + standalone: true, + imports: [NgIf, NgForOf, NgClass, NgSwitch, NgSwitchCase, NgTemplateOutlet, NgSwitchDefault] +}) +export class QuarterTableComponent extends AbstractTable implements OnChanges, OnInit { + override MAX_ROW = 1; + override MAX_COL = 4; + + constructor(private dateHelper: DateHelperService) { + super(); + } + + private changeValueFromInside(value: CandyDate): void { + this.activeDate = value.clone(); + this.valueChange.emit(this.activeDate); + + if (!this.activeDate.isSameQuarter(this.value)) { + this.render(); + } + } + + makeHeadRow(): DateCell[] { + return []; + } + + makeBodyRows(): DateBodyRow[] { + const dateCells: DateCell[] = []; + const months: DateBodyRow[] = [{ dateCells, trackByIndex: 0 }]; + let quarterValue = 1; + + for (let colIndex = 1; colIndex <= this.MAX_COL; colIndex++, quarterValue++) { + const date = this.activeDate.setQuarter(quarterValue); + const isDisabled = this.isDisabledQuarter(date); + const content = this.dateHelper.format(date.nativeDate, 'QQ'); + const cell: DateCell = { + trackByIndex: colIndex, + value: date.nativeDate, + isDisabled, + isSelected: date.isSameQuarter(this.value), + content, + title: content, + classMap: {}, + cellRender: valueFunctionProp(this.cellRender!, date), + fullCellRender: valueFunctionProp(this.fullCellRender!, date), + // onClick: () => this.chooseQuarter(quarter.getQuarter()), + onClick: () => this.changeValueFromInside(date), + onMouseEnter: () => this.cellHover.emit(date) + }; + + this.addCellProperty(cell, date); + dateCells.push(cell); + } + return months; + } + + private isDisabledQuarter(quarter: CandyDate): boolean { + if (!this.disabledDate) { + return false; + } + + const firstOfQuarter = quarter.setQuarter(1); + + for (let date = firstOfQuarter; date.getQuarter() === quarter.getQuarter(); date = date.addQuarter(1)) { + if (!this.disabledDate(date.nativeDate)) { + return false; + } + } + + return true; + } + + private addCellProperty(cell: DateCell, month: CandyDate): void { + cell.isTemplateRef = isTemplateRef(cell.cellRender); + cell.isNonEmptyString = isNonEmptyString(cell.cellRender); + + if (this.hasRangeValue()) { + const [startHover, endHover] = this.hoverValue; + const [startSelected, endSelected] = this.selectedValue; + + if (startSelected?.isSameQuarter(month)) { + cell.isSelectedStart = true; + cell.isSelected = true; + } + + if (endSelected?.isSameQuarter(month)) { + cell.isSelectedEnd = true; + cell.isSelected = true; + } + + if (startHover && endHover) { + cell.isHoverStart = startHover.isSameQuarter(month); + cell.isHoverEnd = endHover.isSameQuarter(month); + cell.isLastCellInPanel = month.getQuarter() === 4; + cell.isFirstCellInPanel = month.getQuarter() === 1; + cell.isInHoverRange = startHover.isBeforeQuarter(month) && month.isBeforeQuarter(endHover); + } + cell.isStartSingle = startSelected && !endSelected; + cell.isEndSingle = !startSelected && endSelected; + cell.isInSelectedRange = startSelected?.isBeforeQuarter(month) && month?.isBeforeQuarter(endSelected); + cell.isRangeStartNearHover = startSelected && cell.isInHoverRange; + cell.isRangeEndNearHover = endSelected && cell.isInHoverRange; + } else if (month.isSameQuarter(this.value)) { + cell.isSelected = true; + } + cell.classMap = this.getClassMap(cell); + } +} diff --git a/components/date-picker/public-api.ts b/components/date-picker/public-api.ts index c3f1bd78b9b..c05d06f99b4 100644 --- a/components/date-picker/public-api.ts +++ b/components/date-picker/public-api.ts @@ -10,6 +10,7 @@ export { NzDatePickerModule } from './date-picker.module'; export { NzDatePickerComponent, NzDatePickerSizeType } from './date-picker.component'; export { NzRangePickerComponent } from './range-picker.component'; export { NzMonthPickerComponent } from './month-picker.component'; +export { NzQuarterPickerComponent } from './quarter-picker.component'; export { NzWeekPickerComponent } from './week-picker.component'; export { NzYearPickerComponent } from './year-picker.component'; export { DatePickerService as ɵDatePickerService } from './date-picker.service'; diff --git a/components/date-picker/quarter-picker.component.spec.ts b/components/date-picker/quarter-picker.component.spec.ts new file mode 100644 index 00000000000..afdaee7758e --- /dev/null +++ b/components/date-picker/quarter-picker.component.spec.ts @@ -0,0 +1,163 @@ +import { OverlayContainer } from '@angular/cdk/overlay'; +import { Component } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +import { dispatchMouseEvent } from 'ng-zorro-antd/core/testing'; +import { CandyDate } from 'ng-zorro-antd/core/time'; +import { getPickerInput } from 'ng-zorro-antd/date-picker/testing/util'; + +import { NzDatePickerModule } from './date-picker.module'; + +describe('NzQuarterPickerComponent', () => { + let fixture: ComponentFixture; + let fixtureInstance: NzTestQuarterPickerComponent; + let overlayContainer: OverlayContainer; + let overlayContainerElement: HTMLElement; + + beforeEach(fakeAsync(() => { + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule, NzDatePickerModule, FormsModule], + declarations: [NzTestQuarterPickerComponent] + }); + + TestBed.compileComponents(); + })); + + beforeEach(inject([OverlayContainer], (oc: OverlayContainer) => { + overlayContainer = oc; + overlayContainerElement = oc.getContainerElement(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NzTestQuarterPickerComponent); + fixtureInstance = fixture.componentInstance; + // set initial mode + fixtureInstance.useSuite = 1; + fixture.detectChanges(); + }); + + afterEach(() => { + overlayContainer.ngOnDestroy(); + }); + + it('should show quarter panel', fakeAsync(() => { + fixtureInstance.nzFormat = undefined; // cover branch + fixture.detectChanges(); + openPickerByClickTrigger(); + expect(queryFromOverlay('.ant-picker-quarter-panel')).toBeDefined(); + })); + + it('should change input value when click quarter', fakeAsync(() => { + fixtureInstance.nzValue = new Date('2024-04-04'); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + openPickerByClickTrigger(); + dispatchMouseEvent(queryFromOverlay('.ant-picker-cell'), 'click'); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(getPickerInput(fixture.debugElement).value).toBe('2024-Q1'); + })); + + it('should specified date provide by "value" be choosed', fakeAsync(() => { + fixtureInstance.useSuite = 3; + fixtureInstance.nzValue = new Date('2024-04-30'); + fixture.detectChanges(); + flush(); // Wait writeValue() tobe done + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + var sss = queryFromOverlay('.ant-picker-quarter-panel td.ant-picker-cell-selected'); + console.log(sss); + expect(queryFromOverlay('.ant-picker-quarter-panel td.ant-picker-cell-selected').textContent).toContain('Q2'); + + // Click the first cell to change ngModel + const cell = queryFromOverlay('.ant-picker-quarter-panel td.ant-picker-cell:nth-child(1) .ant-picker-cell-inner'); + const cellText = cell.textContent!.trim(); + dispatchMouseEvent(cell, 'click'); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(`Q${new CandyDate(fixtureInstance.nzValue).setMonth(0).getQuarter().toString()}`).toBe(cellText); + })); + + it('should nz-quarter-picker work', fakeAsync(() => { + fixtureInstance.useSuite = 2; + fixture.whenRenderingDone().then(() => { + tick(500); + fixture.detectChanges(); + openPickerByClickTrigger(); + dispatchMouseEvent(queryFromOverlay('.ant-picker-cell'), 'click'); + tick(500); + expect(getPickerContainer()).not.toBeNull(); + const pickerInput = getPickerInput(fixture.debugElement); + expect(pickerInput).not.toBeNull(); // + }); + })); + + it('should nz-range-picker "nzValue" work', fakeAsync(() => { + fixtureInstance.useSuite = 4; + fixtureInstance.nzValue = [new Date('2024-04-30'), new Date('2025-12-30')]; + fixture.whenRenderingDone().then(() => { + tick(500); + fixture.detectChanges(); + const panels = overlayContainerElement.querySelectorAll('.ant-picker-quarter-panel'); + expect(panels).not.toBeNull(); + expect(panels.length).toBe(2); + + tick(500); + fixture.detectChanges(); + const firstCell = panels[0].querySelector('td.ant-picker-cell-selected')!; + expect(firstCell).not.toBeNull(); + expect(firstCell.textContent!.trim()).toBe('Q2'); + + const secondCell = panels[1].querySelector('td.ant-picker-cell-selected')!; + expect(secondCell).not.toBeNull(); + expect(secondCell.textContent!.trim()).toBe('Q4'); + }); + })); + + //////////// + + function queryFromOverlay(selector: string): HTMLElement { + return overlayContainerElement.querySelector(selector) as HTMLElement; + } + + function openPickerByClickTrigger(): void { + dispatchMouseEvent(getPickerInput(fixture.debugElement), 'click'); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + } + + function getPickerContainer(): HTMLElement { + return queryFromOverlay('.ant-picker-quarter-panel') as HTMLElement; + } +}); + +@Component({ + template: ` + @switch (useSuite) { + @case (1) { + + } + @case (2) { + + } + @case (3) { + + } + @case (4) { + + } + } + ` +}) +export class NzTestQuarterPickerComponent { + useSuite!: 1 | 2 | 3 | 4; + nzFormat?: string; + nzValue: Date | Date[] | null = null; +} diff --git a/components/date-picker/quarter-picker.component.ts b/components/date-picker/quarter-picker.component.ts new file mode 100644 index 00000000000..31e44888e5c --- /dev/null +++ b/components/date-picker/quarter-picker.component.ts @@ -0,0 +1,19 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +import { Directive, Host, Optional } from '@angular/core'; + +import { NzDatePickerComponent } from './date-picker.component'; + +@Directive({ + selector: 'nz-quarter-picker', + exportAs: 'nzQuarterPicker', + standalone: true +}) +export class NzQuarterPickerComponent { + constructor(@Optional() @Host() public datePicker: NzDatePickerComponent) { + this.datePicker.nzMode = 'quarter'; + } +} diff --git a/components/date-picker/standard-types.ts b/components/date-picker/standard-types.ts index bf27cfb7ea8..d53d976a048 100644 --- a/components/date-picker/standard-types.ts +++ b/components/date-picker/standard-types.ts @@ -9,7 +9,7 @@ export type DisabledDateFn = (d: Date) => boolean; export type DisabledTimePartial = 'start' | 'end'; -export type NzDateMode = 'decade' | 'year' | 'month' | 'week' | 'date' | 'time'; +export type NzDateMode = 'decade' | 'year' | 'quarter' | 'month' | 'week' | 'date' | 'time'; export type RangePartType = 'left' | 'right'; diff --git a/components/i18n/date-helper.service.ts b/components/i18n/date-helper.service.ts index f210a33a151..74f53434087 100644 --- a/components/i18n/date-helper.service.ts +++ b/components/i18n/date-helper.service.ts @@ -6,7 +6,7 @@ import { formatDate } from '@angular/common'; import { Inject, Injectable, Optional, inject } from '@angular/core'; -import { format as fnsFormat, getISOWeek as fnsGetISOWeek, parse as fnsParse } from 'date-fns'; +import { format as fnsFormat, getISOWeek as fnsGetISOWeek, parse as fnsParse, getQuarter } from 'date-fns'; import { WeekDayIndex, ɵNgTimeParser } from 'ng-zorro-antd/core/time'; @@ -107,7 +107,9 @@ export class DateHelperByDatePipe extends DateHelperService { } format(date: Date | null, formatStr: string): string { - return date ? formatDate(date, formatStr, this.i18n.getLocaleId())! : ''; + return date + ? formatDate(date, formatStr, this.i18n.getLocaleId())!.replace(/Q(?=[^Q]*$)/, getQuarter(date).toString()) + : ''; } parseDate(text: string): Date { diff --git a/components/i18n/languages/default.ts b/components/i18n/languages/default.ts index 57ae72bfe91..1e5149a68f7 100644 --- a/components/i18n/languages/default.ts +++ b/components/i18n/languages/default.ts @@ -27,6 +27,7 @@ export default { weekPlaceholder: 'Select week', rangePlaceholder: ['Start date', 'End date'], rangeYearPlaceholder: ['Start year', 'End year'], + rangeQuarterPlaceholder: ['Start quarter', 'End quarter'], rangeMonthPlaceholder: ['Start month', 'End month'], rangeWeekPlaceholder: ['Start week', 'End week'], locale: 'en_US', diff --git a/components/i18n/languages/en_US.ts b/components/i18n/languages/en_US.ts index 8f0f56de745..37cb6b2320d 100644 --- a/components/i18n/languages/en_US.ts +++ b/components/i18n/languages/en_US.ts @@ -27,6 +27,7 @@ export default { weekPlaceholder: 'Select week', rangePlaceholder: ['Start date', 'End date'], rangeYearPlaceholder: ['Start year', 'End year'], + rangeQuarterPlaceholder: ['Start quarter', 'End quarter'], rangeMonthPlaceholder: ['Start month', 'End month'], rangeWeekPlaceholder: ['Start week', 'End week'], locale: 'en_US', diff --git a/components/i18n/languages/zh_CN.ts b/components/i18n/languages/zh_CN.ts index 9fad0e8bc2f..988174adae9 100755 --- a/components/i18n/languages/zh_CN.ts +++ b/components/i18n/languages/zh_CN.ts @@ -27,6 +27,7 @@ export default { weekPlaceholder: '请选择周', rangePlaceholder: ['开始日期', '结束日期'], rangeYearPlaceholder: ['开始年份', '结束年份'], + rangeQuarterPlaceholder: ['开始季度', '结束季度'], rangeMonthPlaceholder: ['开始月份', '结束月份'], rangeWeekPlaceholder: ['开始周', '结束周'], locale: 'zh_CN', diff --git a/components/i18n/languages/zh_HK.ts b/components/i18n/languages/zh_HK.ts index c6e89dd3c17..90fabc3ec9e 100644 --- a/components/i18n/languages/zh_HK.ts +++ b/components/i18n/languages/zh_HK.ts @@ -53,6 +53,7 @@ export default { monthPlaceholder: '請選擇月份', weekPlaceholder: '請選擇周', rangeYearPlaceholder: ['開始年份', '結束年份'], + rangeQuarterPlaceholder: ['開始季度', '開始季度'], rangeMonthPlaceholder: ['開始月份', '結束月份'], rangeWeekPlaceholder: ['開始周', '結束周'] }, diff --git a/components/i18n/languages/zh_TW.ts b/components/i18n/languages/zh_TW.ts index 01cda1c8c50..883730860b7 100755 --- a/components/i18n/languages/zh_TW.ts +++ b/components/i18n/languages/zh_TW.ts @@ -98,6 +98,7 @@ export default { monthPlaceholder: '請選擇月份', weekPlaceholder: '請選擇周', rangeYearPlaceholder: ['開始年份', '結束年份'], + rangeQuarterPlaceholder: ['開始季度', '開始季度'], rangeMonthPlaceholder: ['開始月份', '結束月份'], rangeWeekPlaceholder: ['開始周', '結束周'] }, diff --git a/components/i18n/nz-i18n.interface.ts b/components/i18n/nz-i18n.interface.ts index f1eec4d8f7e..51e25a39eaa 100644 --- a/components/i18n/nz-i18n.interface.ts +++ b/components/i18n/nz-i18n.interface.ts @@ -66,6 +66,7 @@ export interface NzDatePickerLangI18nInterface extends NzCalendarI18nInterface { weekPlaceholder?: string; rangePlaceholder?: string[]; rangeYearPlaceholder?: string[]; + rangeQuarterPlaceholder?: string[]; rangeMonthPlaceholder?: string[]; rangeWeekPlaceholder?: string[]; } From 754aaf2fbf774dc0dfe982cb8618fc6953a596f1 Mon Sep 17 00:00:00 2001 From: yami <310851010@qq.com> Date: Thu, 4 Apr 2024 20:55:38 +0800 Subject: [PATCH 02/10] feat(module:date-picker): quarter selection of date picker --- .../quarter-picker.component.spec.ts | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/components/date-picker/quarter-picker.component.spec.ts b/components/date-picker/quarter-picker.component.spec.ts index afdaee7758e..bab59d2e816 100644 --- a/components/date-picker/quarter-picker.component.spec.ts +++ b/components/date-picker/quarter-picker.component.spec.ts @@ -4,9 +4,12 @@ import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick } from '@angu import { FormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import isBefore from 'date-fns/isBefore'; + import { dispatchMouseEvent } from 'ng-zorro-antd/core/testing'; import { CandyDate } from 'ng-zorro-antd/core/time'; import { getPickerInput } from 'ng-zorro-antd/date-picker/testing/util'; +import { PREFIX_CLASS } from 'ng-zorro-antd/date-picker/util'; import { NzDatePickerModule } from './date-picker.module'; @@ -120,6 +123,61 @@ describe('NzQuarterPickerComponent', () => { }); })); + it('should support year panel changes', fakeAsync(() => { + fixtureInstance.useSuite = 3; + + fixtureInstance.nzValue = new Date('2024-04-30'); + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + openPickerByClickTrigger(); + // Click year select to show year panel + dispatchMouseEvent(queryFromOverlay('.ant-picker-header-quarter-btn'), 'click'); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(queryFromOverlay('.ant-picker-year-panel')).toBeDefined(); + expect(queryFromOverlay('.ant-picker-header-year-btn').textContent).toContain('2020'); + expect(queryFromOverlay('.ant-picker-header-year-btn').textContent).toContain('2029'); + // Goto previous year + dispatchMouseEvent(getSuperPreBtn(), 'click'); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(queryFromOverlay('.ant-picker-header-year-btn').textContent).toContain('2010'); + expect(queryFromOverlay('.ant-picker-header-year-btn').textContent).toContain('2019'); + // Goto next year * 2 + dispatchMouseEvent(getSuperNextBtn(), 'click'); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + dispatchMouseEvent(getSuperNextBtn(), 'click'); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(queryFromOverlay('.ant-picker-header-year-btn').textContent).toContain('2030'); + expect(queryFromOverlay('.ant-picker-header-year-btn').textContent).toContain('2039'); + })); + + it('should support nzDisabledDate', fakeAsync(() => { + fixtureInstance.useSuite = 1; + fixtureInstance.nzValue = null; + fixture.detectChanges(); + const compareDate = new Date('2024-8-01'); + fixtureInstance.nzValue = new Date('2024-04-01'); + fixtureInstance.nzDisabledDate = (current: Date) => isBefore(current, compareDate); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + + openPickerByClickTrigger(); + const allDisabledCells = overlayContainerElement.querySelectorAll( + '.ant-picker-quarter-panel tr td.ant-picker-cell-disabled' + ); + const disabledCell = allDisabledCells[allDisabledCells.length - 1]; + expect(disabledCell.textContent).toContain('Q4'); + })); + //////////// function queryFromOverlay(selector: string): HTMLElement { @@ -136,13 +194,27 @@ describe('NzQuarterPickerComponent', () => { function getPickerContainer(): HTMLElement { return queryFromOverlay('.ant-picker-quarter-panel') as HTMLElement; } + + function getSuperPreBtn(): HTMLElement { + return queryFromOverlay(`.${PREFIX_CLASS}-header-super-prev-btn`); + } + + function getSuperNextBtn(): HTMLElement { + return queryFromOverlay(`.${PREFIX_CLASS}-header-super-next-btn`); + } }); @Component({ template: ` @switch (useSuite) { @case (1) { - + } @case (2) { @@ -160,4 +232,6 @@ export class NzTestQuarterPickerComponent { useSuite!: 1 | 2 | 3 | 4; nzFormat?: string; nzValue: Date | Date[] | null = null; + nzDisabled: boolean = false; + nzDisabledDate!: (d: Date) => boolean; } From f6a62f34aad6a002b0a0ca3dbc96d202fc3be58e Mon Sep 17 00:00:00 2001 From: yami <310851010@qq.com> Date: Fri, 5 Apr 2024 00:00:40 +0800 Subject: [PATCH 03/10] feat(module:date-picker): format quarter --- components/date-picker/date-picker.component.ts | 2 +- components/date-picker/demo/format.ts | 2 +- .../date-picker/lib/quarter-table.component.ts | 2 +- components/i18n/date-helper.service.ts | 17 +++++++++++++---- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/components/date-picker/date-picker.component.ts b/components/date-picker/date-picker.component.ts index 7b3c3928eda..83ef83d1354 100644 --- a/components/date-picker/date-picker.component.ts +++ b/components/date-picker/date-picker.component.ts @@ -763,7 +763,7 @@ export class NzDatePickerComponent implements OnInit, OnChanges, AfterViewInit, setModeAndFormat(): void { const inputFormats: { [key in NzDateMode]?: string } = { year: 'yyyy', - quarter: 'yyyy-QQ', + quarter: 'yyyy-[Q]Q', month: 'yyyy-MM', week: 'YYYY-ww', date: this.nzShowTime ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd' diff --git a/components/date-picker/demo/format.ts b/components/date-picker/demo/format.ts index bc7e9c997a5..4825be2ce79 100644 --- a/components/date-picker/demo/format.ts +++ b/components/date-picker/demo/format.ts @@ -23,5 +23,5 @@ import { Component } from '@angular/core'; export class NzDemoDatePickerFormatComponent { dateFormat = 'yyyy/MM/dd'; monthFormat = 'yyyy/MM'; - quarterFormat = 'yyyy/QQ'; + quarterFormat = 'yyyy/[Q]Q'; } diff --git a/components/date-picker/lib/quarter-table.component.ts b/components/date-picker/lib/quarter-table.component.ts index 705cd82468a..77d7834eb63 100644 --- a/components/date-picker/lib/quarter-table.component.ts +++ b/components/date-picker/lib/quarter-table.component.ts @@ -52,7 +52,7 @@ export class QuarterTableComponent extends AbstractTable implements OnChanges, O for (let colIndex = 1; colIndex <= this.MAX_COL; colIndex++, quarterValue++) { const date = this.activeDate.setQuarter(quarterValue); const isDisabled = this.isDisabledQuarter(date); - const content = this.dateHelper.format(date.nativeDate, 'QQ'); + const content = this.dateHelper.format(date.nativeDate, '[Q]Q'); const cell: DateCell = { trackByIndex: colIndex, value: date.nativeDate, diff --git a/components/i18n/date-helper.service.ts b/components/i18n/date-helper.service.ts index 74f53434087..7838bfae8d9 100644 --- a/components/i18n/date-helper.service.ts +++ b/components/i18n/date-helper.service.ts @@ -6,7 +6,7 @@ import { formatDate } from '@angular/common'; import { Inject, Injectable, Optional, inject } from '@angular/core'; -import { format as fnsFormat, getISOWeek as fnsGetISOWeek, parse as fnsParse, getQuarter } from 'date-fns'; +import { format as fnsFormat, getISOWeek as fnsGetISOWeek, parse as fnsParse } from 'date-fns'; import { WeekDayIndex, ɵNgTimeParser } from 'ng-zorro-antd/core/time'; @@ -107,9 +107,8 @@ export class DateHelperByDatePipe extends DateHelperService { } format(date: Date | null, formatStr: string): string { - return date - ? formatDate(date, formatStr, this.i18n.getLocaleId())!.replace(/Q(?=[^Q]*$)/, getQuarter(date).toString()) - : ''; + // angular formatDate does not support the quarter format parameter. This is to be compatible with the quarter format "Q" of date-fns. + return date ? this.replaceQuarter(formatDate(date, formatStr, this.i18n.getLocaleId())!, date) : ''; } parseDate(text: string): Date { @@ -120,4 +119,14 @@ export class DateHelperByDatePipe extends DateHelperService { const parser = new ɵNgTimeParser(formatStr, this.i18n.getLocaleId()); return parser.toDate(text); } + + private getQuarter(date: Date): number { + return Math.floor((date.getMonth() + 3) / 3); + } + + private replaceQuarter(dateStr: string, date: Date): string { + const quarter = this.getQuarter(date).toString(); + const record: Record = { Q: quarter, QQ: `0${quarter}`, QQQ: `Q${quarter}` }; + return dateStr.replace(/Q+(?![^\[]*])/g, match => record[match] ?? quarter).replace(/\[([^[\]]*Q[^[\]]*)]/g, '$1'); + } } From 7c0b0caf1c2ddd04e1923774f64c270f93471ea2 Mon Sep 17 00:00:00 2001 From: yami <310851010@qq.com> Date: Fri, 5 Apr 2024 21:25:59 +0800 Subject: [PATCH 04/10] feat(module:date-picker): quarter disable date --- components/date-picker/lib/quarter-table.component.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/components/date-picker/lib/quarter-table.component.ts b/components/date-picker/lib/quarter-table.component.ts index 77d7834eb63..10e1420d7d3 100644 --- a/components/date-picker/lib/quarter-table.component.ts +++ b/components/date-picker/lib/quarter-table.component.ts @@ -79,17 +79,19 @@ export class QuarterTableComponent extends AbstractTable implements OnChanges, O return false; } - const firstOfQuarter = quarter.setQuarter(1); - - for (let date = firstOfQuarter; date.getQuarter() === quarter.getQuarter(); date = date.addQuarter(1)) { + const firstDayOfQuarter = new CandyDate(new Date(quarter.getYear(), this.getQuarterStartMonth(quarter.getMonth()))); + for (let date = firstDayOfQuarter; date.getQuarter() === quarter.getQuarter(); date = date.addMonths(1)) { if (!this.disabledDate(date.nativeDate)) { return false; } } - return true; } + private getQuarterStartMonth(month: number): number { + return Math.floor(month / 3) * 3; + } + private addCellProperty(cell: DateCell, month: CandyDate): void { cell.isTemplateRef = isTemplateRef(cell.cellRender); cell.isNonEmptyString = isNonEmptyString(cell.cellRender); From 15788f5622cb0f6b0e34c7eec28a846bfa076df3 Mon Sep 17 00:00:00 2001 From: yami <310851010@qq.com> Date: Fri, 5 Apr 2024 21:30:52 +0800 Subject: [PATCH 05/10] feat(module:date-picker): use date-fns getQuarter --- components/i18n/date-helper.service.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/components/i18n/date-helper.service.ts b/components/i18n/date-helper.service.ts index 7838bfae8d9..c97d40533b8 100644 --- a/components/i18n/date-helper.service.ts +++ b/components/i18n/date-helper.service.ts @@ -6,7 +6,7 @@ import { formatDate } from '@angular/common'; import { Inject, Injectable, Optional, inject } from '@angular/core'; -import { format as fnsFormat, getISOWeek as fnsGetISOWeek, parse as fnsParse } from 'date-fns'; +import { format as fnsFormat, getISOWeek as fnsGetISOWeek, getQuarter, parse as fnsParse } from 'date-fns'; import { WeekDayIndex, ɵNgTimeParser } from 'ng-zorro-antd/core/time'; @@ -120,13 +120,16 @@ export class DateHelperByDatePipe extends DateHelperService { return parser.toDate(text); } - private getQuarter(date: Date): number { - return Math.floor((date.getMonth() + 3) / 3); - } - private replaceQuarter(dateStr: string, date: Date): string { - const quarter = this.getQuarter(date).toString(); + const quarter = getQuarter(date).toString(); const record: Record = { Q: quarter, QQ: `0${quarter}`, QQQ: `Q${quarter}` }; - return dateStr.replace(/Q+(?![^\[]*])/g, match => record[match] ?? quarter).replace(/\[([^[\]]*Q[^[\]]*)]/g, '$1'); + // Q Pattern format compatible with date-fns (quarter). + return ( + dateStr + // Match Q+ outside of brackets, then replace it with the specified quarterly format + .replace(/Q+(?![^\[]*])/g, match => record[match] ?? quarter) + // Match the Q+ surrounded by bracket, then remove bracket. + .replace(/\[(Q+)]/g, '$1') + ); } } From 65c3c5d813520c093828aad1d533c3ac409bd64a Mon Sep 17 00:00:00 2001 From: yami <310851010@qq.com> Date: Fri, 5 Apr 2024 22:00:52 +0800 Subject: [PATCH 06/10] feat(module:date-picker): use date-fns startOfQuarter --- components/date-picker/lib/quarter-table.component.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/components/date-picker/lib/quarter-table.component.ts b/components/date-picker/lib/quarter-table.component.ts index 10e1420d7d3..860c7e7b991 100644 --- a/components/date-picker/lib/quarter-table.component.ts +++ b/components/date-picker/lib/quarter-table.component.ts @@ -6,6 +6,8 @@ import { NgClass, NgForOf, NgIf, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, Component, OnChanges, OnInit, ViewEncapsulation } from '@angular/core'; +import { startOfQuarter } from 'date-fns'; + import { CandyDate } from 'ng-zorro-antd/core/time'; import { isNonEmptyString, isTemplateRef, valueFunctionProp } from 'ng-zorro-antd/core/util'; import { DateHelperService } from 'ng-zorro-antd/i18n'; @@ -63,7 +65,6 @@ export class QuarterTableComponent extends AbstractTable implements OnChanges, O classMap: {}, cellRender: valueFunctionProp(this.cellRender!, date), fullCellRender: valueFunctionProp(this.fullCellRender!, date), - // onClick: () => this.chooseQuarter(quarter.getQuarter()), onClick: () => this.changeValueFromInside(date), onMouseEnter: () => this.cellHover.emit(date) }; @@ -79,7 +80,7 @@ export class QuarterTableComponent extends AbstractTable implements OnChanges, O return false; } - const firstDayOfQuarter = new CandyDate(new Date(quarter.getYear(), this.getQuarterStartMonth(quarter.getMonth()))); + const firstDayOfQuarter = new CandyDate(startOfQuarter(quarter.nativeDate)); for (let date = firstDayOfQuarter; date.getQuarter() === quarter.getQuarter(); date = date.addMonths(1)) { if (!this.disabledDate(date.nativeDate)) { return false; @@ -88,10 +89,6 @@ export class QuarterTableComponent extends AbstractTable implements OnChanges, O return true; } - private getQuarterStartMonth(month: number): number { - return Math.floor(month / 3) * 3; - } - private addCellProperty(cell: DateCell, month: CandyDate): void { cell.isTemplateRef = isTemplateRef(cell.cellRender); cell.isNonEmptyString = isNonEmptyString(cell.cellRender); From 87ec5bc80b40700cc501fc16223b8d977801d3e7 Mon Sep 17 00:00:00 2001 From: yami <310851010@qq.com> Date: Sat, 6 Apr 2024 21:19:38 +0800 Subject: [PATCH 07/10] feat(module:date-picker): fixed test --- components/date-picker/quarter-picker.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/date-picker/quarter-picker.component.spec.ts b/components/date-picker/quarter-picker.component.spec.ts index bab59d2e816..384a94fd97c 100644 --- a/components/date-picker/quarter-picker.component.spec.ts +++ b/components/date-picker/quarter-picker.component.spec.ts @@ -175,7 +175,7 @@ describe('NzQuarterPickerComponent', () => { '.ant-picker-quarter-panel tr td.ant-picker-cell-disabled' ); const disabledCell = allDisabledCells[allDisabledCells.length - 1]; - expect(disabledCell.textContent).toContain('Q4'); + expect(disabledCell.textContent).toContain('Q2'); })); //////////// From 82f825afcd831723477e04e93ddba2dc73649ecd Mon Sep 17 00:00:00 2001 From: yami <310851010@qq.com> Date: Mon, 8 Apr 2024 19:48:52 +0800 Subject: [PATCH 08/10] feat(module:date-picker): add test case --- components/core/time/candy-date.ts | 7 +---- .../date-picker/inner-popup.component.ts | 9 ++---- .../quarter-picker.component.spec.ts | 30 +++++++++++++++++-- components/i18n/date-helper.service.spec.ts | 10 +++++++ 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/components/core/time/candy-date.ts b/components/core/time/candy-date.ts index 873ced432ea..78104903c8d 100644 --- a/components/core/time/candy-date.ts +++ b/components/core/time/candy-date.ts @@ -30,8 +30,7 @@ import { startOfMonth, startOfWeek, getQuarter, - setQuarter, - addQuarters + setQuarter } from 'date-fns'; import { warn } from 'ng-zorro-antd/core/logger'; @@ -205,10 +204,6 @@ export class CandyDate implements IndexableObject { return new CandyDate(setQuarter(this.nativeDate, quarter)); } - addQuarter(quarter: number): CandyDate { - return new CandyDate(addQuarters(this.nativeDate, quarter)); - } - addDays(amount: number): CandyDate { return this.setDate(this.getDate() + amount); } diff --git a/components/date-picker/inner-popup.component.ts b/components/date-picker/inner-popup.component.ts index 2beb7a808fd..0efe53f7eed 100644 --- a/components/date-picker/inner-popup.component.ts +++ b/components/date-picker/inner-popup.component.ts @@ -252,13 +252,8 @@ export class InnerPopupComponent implements OnChanges { onChooseQuarter(value: CandyDate): void { this.activeDate = this.activeDate.setQuarter(value.getQuarter()); - if (this.endPanelMode === 'quarter') { - this.value = value; - this.selectDate.emit(value); - } else { - this.headerChange.emit(value); - this.panelModeChange.emit(this.endPanelMode); - } + this.value = value; + this.selectDate.emit(value); } onChooseYear(value: CandyDate): void { diff --git a/components/date-picker/quarter-picker.component.spec.ts b/components/date-picker/quarter-picker.component.spec.ts index 384a94fd97c..686b20d2a6e 100644 --- a/components/date-picker/quarter-picker.component.spec.ts +++ b/components/date-picker/quarter-picker.component.spec.ts @@ -73,8 +73,6 @@ describe('NzQuarterPickerComponent', () => { fixture.detectChanges(); tick(500); fixture.detectChanges(); - var sss = queryFromOverlay('.ant-picker-quarter-panel td.ant-picker-cell-selected'); - console.log(sss); expect(queryFromOverlay('.ant-picker-quarter-panel td.ant-picker-cell-selected').textContent).toContain('Q2'); // Click the first cell to change ngModel @@ -178,8 +176,36 @@ describe('NzQuarterPickerComponent', () => { expect(disabledCell.textContent).toContain('Q2'); })); + it('should support hover date cell style', fakeAsync(() => { + fixtureInstance.useSuite = 4; + fixture.detectChanges(); + openPickerByClickTrigger(); + tick(500); + fixture.detectChanges(); + + const left = getFirstCell('left'); // Use the first cell + dispatchMouseEvent(left, 'click'); + const rightInNextMonth = queryFromRightPanel('table tr td.ant-picker-cell'); + dispatchMouseEvent(rightInNextMonth, 'mouseenter'); + fixture.detectChanges(); + expect(rightInNextMonth.classList.contains('ant-picker-cell-range-hover-end')).toBeTruthy(); + })); + //////////// + function queryFromRightPanel(selector: string): HTMLElement { + return overlayContainerElement + .querySelector('.ant-picker-panel:last-child')! + .querySelector(selector) as HTMLElement; + } + + function getFirstCell(partial: 'left' | 'right'): HTMLElement { + const flg = partial === 'left' ? 'first' : 'last'; + return queryFromOverlay( + `.ant-picker-quarter-panel:${flg}-child td:first-child .ant-picker-cell-inner` + ) as HTMLElement; + } + function queryFromOverlay(selector: string): HTMLElement { return overlayContainerElement.querySelector(selector) as HTMLElement; } diff --git a/components/i18n/date-helper.service.spec.ts b/components/i18n/date-helper.service.spec.ts index 7ed100a5741..b92fea7fb71 100644 --- a/components/i18n/date-helper.service.spec.ts +++ b/components/i18n/date-helper.service.spec.ts @@ -41,6 +41,16 @@ describe('DateHelperService', () => { expect(dateHelper.parseTime('14:00', 'HH:mm')?.toTimeString().substr(0, 5)).toBe('14:00'); expect(dateHelper.parseTime('4:00', 'H:mm')?.toTimeString().substr(0, 5)).toBe('04:00'); }); + + it('should do formatting quarter', () => { + const date = new Date('2024-04-08 18:18:10'); + expect(dateHelper.format(date, 'yyyy-Q')).toBe('2024-2'); + expect(dateHelper.format(date, 'yyyy-QQ')).toBe('2024-02'); + expect(dateHelper.format(date, 'yyyy-QQQ')).toBe('2024-Q2'); + expect(dateHelper.format(date, 'yyyy-QQQQ')).toBe('2024-2'); + expect(dateHelper.format(date, 'yyyy-[Q]Q')).toBe('2024-Q2'); + expect(dateHelper.format(date, 'yyyy-[QQ]Q')).toBe('2024-QQ2'); + }); }); describe('Formatting with Data-fns', () => { From ca5194f9a4ccd8421cc376a94c9dfb42abfa7b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=A9=AC=E5=90=8C=E5=AD=A6?= <310851010@qq.com> Date: Fri, 31 May 2024 15:59:42 +0800 Subject: [PATCH 09/10] Update zh_HK.ts --- components/i18n/languages/zh_HK.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/i18n/languages/zh_HK.ts b/components/i18n/languages/zh_HK.ts index 90fabc3ec9e..e2e8cfb766f 100644 --- a/components/i18n/languages/zh_HK.ts +++ b/components/i18n/languages/zh_HK.ts @@ -53,7 +53,7 @@ export default { monthPlaceholder: '請選擇月份', weekPlaceholder: '請選擇周', rangeYearPlaceholder: ['開始年份', '結束年份'], - rangeQuarterPlaceholder: ['開始季度', '開始季度'], + rangeQuarterPlaceholder: ['開始季度', '結束季度'], rangeMonthPlaceholder: ['開始月份', '結束月份'], rangeWeekPlaceholder: ['開始周', '結束周'] }, From f2d301732c1c86e0136ec7ff55d7ae809e5b5895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=A9=AC=E5=90=8C=E5=AD=A6?= <310851010@qq.com> Date: Fri, 31 May 2024 16:10:08 +0800 Subject: [PATCH 10/10] Update zh_TW.ts --- components/i18n/languages/zh_TW.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/i18n/languages/zh_TW.ts b/components/i18n/languages/zh_TW.ts index 883730860b7..924db8409a7 100755 --- a/components/i18n/languages/zh_TW.ts +++ b/components/i18n/languages/zh_TW.ts @@ -98,7 +98,7 @@ export default { monthPlaceholder: '請選擇月份', weekPlaceholder: '請選擇周', rangeYearPlaceholder: ['開始年份', '結束年份'], - rangeQuarterPlaceholder: ['開始季度', '開始季度'], + rangeQuarterPlaceholder: ['開始季度', '結束季度'], rangeMonthPlaceholder: ['開始月份', '結束月份'], rangeWeekPlaceholder: ['開始周', '結束周'] },