daterangepicker

mail@pastecode.io avatar
unknown
typescript
a year ago
6.3 kB
10
Indexable
"use client";
import React, { useState, useCallback, useEffect, Fragment } from "react";
import { Datepicker, CalendarPrev, CalendarNav, CalendarNext, CalendarToday, setOptions, localeNl, formatDate } from "@mobiscroll/react";
import { cn } from "@/lib/utils";

import useDateStore from "../../../state/useDateStore";
import { Button } from "./ui/Button";

import "@/styles/datepicker.modules.scss";
import "@mobiscroll/react/dist/css/mobiscroll.min.css";
import useSWR from "swr";

type DateRangeSelectorProps = {
  openChange: (isOpen: boolean) => void;
  isOpen: boolean;
};

type ValidProps = {
  start: Date;
  end: Date;
};

setOptions({
  locale: localeNl,
  theme: "ios",
  themeVariant: "light",
});

const now = new Date();
const dateFormat = "YYYY-MM-DD";
let startingFromDate = formatDate(dateFormat, new Date(now.getFullYear(), now.getMonth(), 1)); // format datum naar YYYY-MM-DD
let startingEndDate = formatDate(dateFormat, new Date(now.getFullYear(), now.getMonth() + 2, 0)); // format datum naar YYYY-MM-DD

/**
 * Updates the valid dates for the DateRangeSelector based on the arrivals data.
 * @param arrivals - An array of arrival data.
 * @returns An array of ValidProps objects representing the valid date ranges.
 */
const updateValidDates = (arrivals: any[]): ValidProps[] => {
  const valids: ValidProps[] = [];

  arrivals.map((arrival: any) => {
    if (arrival.departures) {
      arrival.departures.map((depature: any) => {
        valids.push({ start: arrival.date, end: depature.date });
      });
    }
  });

  return valids;
};

/**
 * A component that allows the user to select a date range, it fetches arrival data from the API and updates the valid dates accordingly.
 * Then the user can select a date range that is only within the valid dates.
 * @param openChange - A callback function to handle changes in the open state of the DateRangeSelector.
 * @param isOpen - A boolean indicating whether the DateRangeSelector is open or not.
 * @returns A DateRangeSelector component.
 */
export const DateRangeSelector = ({ openChange, isOpen }: DateRangeSelectorProps) => {
  const addDate = useDateStore((state) => state.addDate);
  const [fromDate, setFromDate] = useState(startingFromDate);
  const [tillDate, setTillDate] = useState(startingEndDate);
  const [selectedDate, setSelectedDate] = useState<[string, string] | null>(null);
  const [inputValue, setInputValue] = useState();
  const [calenderOpen, setCalenderOpen] = useState(false);

  /**
   * Handles the new date range on page switch, and updates the state accordingly.
   * @param event - The event object containing information about the next selected date.
   */
  const handlePageChange = (event: any) => {
    const newCurrMonth = event.firstDay.toLocaleDateString("en-CA");
    const newNextMonth = new Date(event.firstDay.getFullYear(), event.firstDay.getMonth() + 2, 0).toLocaleDateString("en-CA");

    // setTimeout nodig om errors in the console te voorkomen
    setTimeout(() => {
      setFromDate(newCurrMonth);
      setTillDate(newNextMonth);
    }, 0);
  };

  function fetcher(url: string) {
    return fetch(url).then((res) => res.json());
  }

  function useFetchDates() {
    const { data } = useSWR(`/api/availability?fromDate=${fromDate}&tillDate=${tillDate}`, fetcher);
    return data;
  }

  /**
   * Retrieves the availability data for the given date range.
   * @param fromDate - The start date of the date range.
   * @param tillDate - The end date of the date range.
   */

  function useAvailableDates(data: any) {
    const [valid, setValid] = useState<ValidProps[]>([]);

    useEffect(() => {
      if (isOpen && data) {
        const valids = updateValidDates(data.data.arrivals);
        setValid(valids);
      }
    }, [isOpen, data]);

    return valid;
  }

  const valid = useAvailableDates(useFetchDates());

  const applyClick = useCallback(() => {
    if (!selectedDate) return;
    openChange(false);
    setCalenderOpen(false);
    addDate(selectedDate);
  }, [selectedDate]);

  const onDateChange = useCallback(
    (event: any) => {
      const date = event.value;
      const selectedFrom = formatDate(dateFormat, new Date(date[0] as string));
      const selectedTill = formatDate(dateFormat, new Date(date[1] as string));
      setSelectedDate([selectedFrom, selectedTill]);
    },
    [selectedDate]
  );

  const cancelClick = () => {
    openChange(false);
  };

  const calendarHeader = useCallback(() => {
    return (
      <Fragment>
        <CalendarNav />
        <CalendarPrev />
        <CalendarToday />
        <CalendarNext />
      </Fragment>
    );
  }, []);

  return (
    <div
      className={cn(
        `
            ${isOpen === true ? "block" : "hidden"} 
            fixed 
            left-0 
            top-0 
            bg-white 
            w-full
            h-full
            flex 
            flex-col 
            z-20
            overflow-y-scroll
            `
      )}
    >
      <Datepicker
        controls={["calendar"]}
        cssClass="md-book-rental"
        select="range"
        display="inline"
        calendarType="month"
        calendarSize={2}
        refDate={startingFromDate}
        min={now}
        startInput={now}
        showRangeLabels={false}
        inRangeInvalid={false}
        rangeEndInvalid={true}
        valid={valid}
        onClose={cancelClick}
        renderCalendarHeader={calendarHeader}
        onPageChange={handlePageChange}
        onChange={onDateChange}
      />

      <div
        className={cn(
          `
                h-20
                p-5 
                flex
                space-around
                justify-around 
                bg-white
                fixed
                md:absolute
                z-50
                bottom-0
                left-0
                w-full
                !shadow-lg
                !border-t-2
                !border-gray-300
                `
        )}
      >
        <Button onClick={cancelClick} variant="outline" style={{ backgroundColor: "var(--theme-color-tertiary)" }}>
          Selectie wissen
        </Button>
        <Button onClick={applyClick} style={{ backgroundColor: "var(--theme-color-primary)" }}>
          Selecteren
        </Button>
      </div>
    </div>
  );
};

export default DateRangeSelector;