Repository: Hacker0x01/react-datepicker (ts)

Issue:
When showQuarterYearPicker: true, minDate/maxDate doesn't always grey out arrows
**Describe the bug**
A clear and concise description of what the bug is.
When I have a `minDate` and `maxDate` using Monthly or Yearly picker, these dates work correctly and my left and right arrows will grey out appropriately.  However, if I use `showQuarterYearPicker`, I can have situations in which years above my maxDate and years below my MinDate still will show the black arrow and I can click and navigate to the "fully disabled" year where all quarters are greyed out and the user cannot select anything.

**To Reproduce**
Steps to reproduce the behavior:

Set showQuarterYearPicker=true and minDate 1/1/2023 and maxDate 12/31/2023.  Now click on the input to show the datepicker and notice that you can click the backwards arrow, for example, to 2022 where all quarters are not selectable, leaving the user with nothing to do on that screen.

**Expected behavior**
A clear and concise description of what you expected to happen.
The expected behavior is that since there is no data in 2022, the arrow should either be hidden (or disabled if using `showDisabledMonthNavigation`).  It should be consistent like the non-quarterly modes.

**Screenshots**
If applicable, add screenshots to help explain your problem.
<img width="860" alt="image" src="https://github.com/Hacker0x01/react-datepicker/assets/129972591/6ee58d20-150d-42fa-8e47-5eb03179d696">



**Desktop (please complete the following information):**

- OS: iOS
- Browser: chrome
- Version: 123.0.6312.59


**Additional context**
n/a


Pull request description:
Fix #4644: Handle the visibility of the previous and the next year navigation when showQuarterYearPicker is enabled 
## Description
**Linked issue**: #4644

**Problem**
As mentioned in the linked issue, the QuarterYearPicker has some issue with the visibility of the next and the previous arrow buttons based on the minDate and the maxDate.  That's because we are not handling the Quarter year picker separately, but handling it with the month picker logic

**Changes**
- Handled the quarter-year picker separately
- Added helpers in the `date_utils.js` to achieve the result
- Verified the change by test coverage

## Contribution checklist
- [x] I have followed the [contributing guidelines](https://github.com/Hacker0x01/react-datepicker/blob/main/CONTRIBUTING.md).
- [x] I have added sufficient test coverage for my changes.
- [x] I have formatted my code with Prettier and checked for linting issues with ESLint for code readability.


Patch:
diff --git a/src/calendar.jsx b/src/calendar.jsx
index 83ffa798..6c7c217e 100644
--- a/src/calendar.jsx
+++ b/src/calendar.jsx
@@ -36,6 +36,8 @@ import {
   yearDisabledAfter,
   yearsDisabledAfter,
   yearsDisabledBefore,
+  quarterDisabledBefore,
+  quarterDisabledAfter,
   getEffectiveMinDate,
   getEffectiveMaxDate,
   addZero,
@@ -489,6 +491,12 @@ export default class Calendar extends React.Component {
       case this.props.showYearPicker:
         allPrevDaysDisabled = yearsDisabledBefore(this.state.date, this.props);
         break;
+      case this.props.showQuarterYearPicker:
+        allPrevDaysDisabled = quarterDisabledBefore(
+          this.state.date,
+          this.props,
+        );
+        break;
       default:
         allPrevDaysDisabled = monthDisabledBefore(this.state.date, this.props);
         break;
@@ -586,6 +594,9 @@ export default class Calendar extends React.Component {
       case this.props.showYearPicker:
         allNextDaysDisabled = yearsDisabledAfter(this.state.date, this.props);
         break;
+      case this.props.showQuarterYearPicker:
+        allNextDaysDisabled = quarterDisabledAfter(this.state.date, this.props);
+        break;
       default:
         allNextDaysDisabled = monthDisabledAfter(this.state.date, this.props);
         break;
diff --git a/src/date_utils.js b/src/date_utils.js
index e35b459b..8f6adce4 100644
--- a/src/date_utils.js
+++ b/src/date_utils.js
@@ -34,6 +34,7 @@ import { max } from "date-fns/max";
 import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
 import { differenceInCalendarMonths } from "date-fns/differenceInCalendarMonths";
 import { differenceInCalendarYears } from "date-fns/differenceInCalendarYears";
+import { differenceInCalendarQuarters } from "date-fns/differenceInCalendarQuarters";
 import { startOfDay } from "date-fns/startOfDay";
 import { startOfWeek } from "date-fns/startOfWeek";
 import { startOfMonth } from "date-fns/startOfMonth";
@@ -654,6 +655,36 @@ export function monthDisabledAfter(day, { maxDate, includeDates } = {}) {
   );
 }
 
+export function quarterDisabledBefore(date, { minDate, includeDates } = {}) {
+  const firstDateOfYear = startOfYear(date);
+  const previousQuarter = subQuarters(firstDateOfYear, 1);
+
+  return (
+    (minDate && differenceInCalendarQuarters(minDate, previousQuarter) > 0) ||
+    (includeDates &&
+      includeDates.every(
+        (includeDate) =>
+          differenceInCalendarQuarters(includeDate, previousQuarter) > 0,
+      )) ||
+    false
+  );
+}
+
+export function quarterDisabledAfter(date, { maxDate, includeDates } = {}) {
+  const lastDateOfYear = endOfYear(date);
+  const nextQuarter = addQuarters(lastDateOfYear, 1);
+
+  return (
+    (maxDate && differenceInCalendarQuarters(nextQuarter, maxDate) > 0) ||
+    (includeDates &&
+      includeDates.every(
+        (includeDate) =>
+          differenceInCalendarQuarters(nextQuarter, includeDate) > 0,
+      )) ||
+    false
+  );
+}
+
 export function yearDisabledBefore(day, { minDate, includeDates } = {}) {
   const previousYear = subYears(day, 1);
   return (


Test patch:
diff --git a/test/calendar_test.test.js b/test/calendar_test.test.js
index 6afbe248..f3fbf465 100644
--- a/test/calendar_test.test.js
+++ b/test/calendar_test.test.js
@@ -9,7 +9,7 @@ import DatePicker from "../src/index.jsx";
 import * as utils from "../src/date_utils";
 import { eo } from "date-fns/locale/eo";
 import { fi } from "date-fns/locale/fi";
-import { isSunday } from "date-fns";
+import { endOfYear, isSunday, startOfMonth } from "date-fns";
 import { getKey } from "./test_utils";
 
 // TODO Possibly rename
@@ -1822,6 +1822,27 @@ describe("Calendar", () => {
       });
       expect(utils.getYear(instance.state.date)).toBe(1994);
     });
+
+    it("should hide the previous year navigation arrow button when the minDate falls under the currently visible year ", () => {
+      const { container } = render(
+        <Calendar showQuarterYearPicker minDate={startOfMonth(new Date())} />,
+      );
+      const previous = container.querySelector(
+        ".react-datepicker__navigation--previous",
+      );
+      expect(previous).toBeNull();
+    });
+
+    it("should hide the next year navigation arrow button when the maxDate falls under the currently visible year ", () => {
+      const { container } = render(
+        <Calendar showQuarterYearPicker maxDate={endOfYear(new Date())} />,
+      );
+
+      const next = container.querySelector(
+        ".react-datepicker__navigation--next",
+      );
+      expect(next).toBeNull();
+    });
   });
 
   describe("using click outside", () => {
diff --git a/test/date_utils_test.test.js b/test/date_utils_test.test.js
index 7a1e8175..e77adceb 100644
--- a/test/date_utils_test.test.js
+++ b/test/date_utils_test.test.js
@@ -34,6 +34,8 @@ import {
   setDefaultLocale,
   yearsDisabledAfter,
   yearsDisabledBefore,
+  quarterDisabledBefore,
+  quarterDisabledAfter,
   getWeek,
   safeDateRangeFormat,
   getHolidaysMap,
@@ -665,6 +667,54 @@ describe("date_utils", () => {
     });
   });
 
+  describe("quarterDisabledBefore", () => {
+    it("should return false by default", () => {
+      expect(quarterDisabledBefore(newDate())).toBe(false);
+    });
+
+    it("should return true if min date is in the same year", () => {
+      const day = newDate("2016-02-19");
+      const minDate = newDate("2016-03-01");
+      expect(quarterDisabledBefore(day, { minDate })).toBe(true);
+    });
+
+    it("should return false if min date is in the previous year", () => {
+      const day = newDate("2016-03-19");
+      const minDate = newDate("2015-03-29");
+      expect(quarterDisabledBefore(day, { minDate })).toBe(false);
+    });
+
+    it("should return true if previous year is before include dates", () => {
+      const day = newDate("2016-03-19");
+      const includeDates = [newDate("2016-03-01")];
+      expect(quarterDisabledBefore(day, { includeDates })).toBe(true);
+    });
+  });
+
+  describe("quarterDisabledAfter", () => {
+    it("should return false by default", () => {
+      expect(quarterDisabledAfter(newDate())).toBe(false);
+    });
+
+    it("should return true if max date is in the same year", () => {
+      const day = newDate("2016-03-19");
+      const maxDate = newDate("2016-08-31");
+      expect(quarterDisabledAfter(day, { maxDate })).toBe(true);
+    });
+
+    it("should return false if max date is in the next year", () => {
+      const day = newDate("2016-03-19");
+      const maxDate = newDate("2017-04-01");
+      expect(quarterDisabledAfter(day, { maxDate })).toBe(false);
+    });
+
+    it("should return true if next year is after include dates", () => {
+      const day = newDate("2016-03-19");
+      const includeDates = [newDate("2016-03-01")];
+      expect(quarterDisabledAfter(day, { includeDates })).toBe(true);
+    });
+  });
+
   describe("yearDisabledBefore", () => {
     it("should return false by default", () => {
       expect(yearDisabledBefore(newDate())).toBe(false);