Browse Source

Update TabFilters component

* Add a renderTabsTimeFlightTab() function to render the time of flight tab
* Add a renderTabsTypeOfAirlines() function to render the type of airlines tab
* Add a handleChangeAirlines() function to handle the change of airlines
main
John Doe 1 year ago
parent
commit
eccac26e97
  1. 858
      src/app/(flight-listings)/TabFilters.tsx

858
src/app/(flight-listings)/TabFilters.tsx

@ -0,0 +1,858 @@
"use client";
import React, { Fragment, useState } from "react";
import { Dialog, Popover, Tab, Transition } from "@headlessui/react";
import ButtonPrimary from "@/shared/ButtonPrimary";
import ButtonThird from "@/shared/ButtonThird";
import ButtonClose from "@/shared/ButtonClose";
import Checkbox from "@/shared/Checkbox";
import convertNumbThousand from "@/utils/convertNumbThousand";
import Slider from "rc-slider";
import { XMarkIcon } from "@heroicons/react/24/outline";
// DEMO DATA
const typeOfAirlines = [
{
name: "Star Alliance",
},
{
name: "Air China",
},
{
name: "Air India",
},
{
name: "Air New Zealand",
},
{
name: "Asiana",
},
{
name: "Bangkok Airways",
},
];
const stopPoints = [
{
name: "Nonstop",
},
{
name: "Up to 1 stops",
},
{
name: "Up to 2 stops",
},
{
name: "Any number of stops",
},
];
//
const TabFilters = () => {
const [isOpenMoreFilter, setisOpenMoreFilter] = useState(false);
//
const [isOnSale, setIsOnSale] = useState(true);
const [rangePrices, setRangePrices] = useState([100, 5000]);
const [tripTimes, setTripTimes] = useState(10);
const [stopPontsStates, setStopPontsStates] = useState<string[]>([]);
const [airlinesStates, setAirlinesStates] = useState<string[]>([]);
//
let [catTimes, setCatTimes] = useState({
"Take Off": {
Departure: [0, 24],
Arrival: [0, 24],
},
Landing: {
Departure: [0, 24],
Arrival: [0, 24],
},
});
//
const closeModalMoreFilter = () => setisOpenMoreFilter(false);
const openModalMoreFilter = () => setisOpenMoreFilter(true);
//
const handleChangeStopPoint = (checked: boolean, name: string) => {
checked
? setStopPontsStates([...stopPontsStates, name])
: setStopPontsStates(stopPontsStates.filter((i) => i !== name));
};
const handleChangeAirlines = (checked: boolean, name: string) => {
checked
? setAirlinesStates([...airlinesStates, name])
: setAirlinesStates(airlinesStates.filter((i) => i !== name));
};
//
const renderXClear = () => {
return (
<span className="w-4 h-4 rounded-full bg-primary-500 text-white flex items-center justify-center ml-3 cursor-pointer">
<XMarkIcon className="h-3 w-3" />
</span>
);
};
const renderTabsTimeFlightTab = () => {
return (
<div>
<Tab.Group>
<Tab.List className="flex p-1 space-x-1 bg-primary-900/10 rounded-xl">
{Object.keys(catTimes).map((category) => (
<Tab
key={category}
className={({ selected }) =>
`w-full py-2.5 text-sm leading-5 font-medium text-primary-700 dark:text-primary-400 rounded-lg focus:outline-none focus:ring-2 ring-offset-2 ring-offset-blue-400 ring-white ring-opacity-60 ${
selected
? "bg-white dark:bg-neutral-800 shadow"
: " hover:bg-white/[0.15] dark:hover:bg-neutral-800"
}`
}
>
{category}
</Tab>
))}
</Tab.List>
<Tab.Panels className="mt-2">
{Object.values(catTimes).map((posts, idx) => {
return (
<Tab.Panel
key={idx}
className={
"bg-neutral-50 dark:bg-neutral-900 rounded-xl p-3 space-y-8 focus:outline-none focus:ring-2 ring-offset-2 ring-offset-blue-400 ring-white ring-opacity-60"
}
>
<span className=" text-neutral-6000 dark:text-neutral-300 text-sm">
{idx ? " Tokyo to Singapore" : " Singapore to Tokyo"}
</span>
<div></div>
<div className="space-y-3">
<div className="flex space-x-2">
<i className="text-lg las la-plane-departure"></i>
<span className="text-xs">Departure time:</span>
<span className="text-xs text-primary-500 dark:text-primary-400">
{posts.Departure[0]}:00 - {posts.Departure[1]}
:00
</span>
</div>
<Slider
range
min={0}
max={24}
defaultValue={posts.Departure}
onChange={(val) =>
setCatTimes((catTimes) =>
!idx
? {
...catTimes,
"Take Off": {
...posts,
Departure: val as [number, number],
},
}
: {
...catTimes,
Landing: {
...posts,
Departure: val as [number, number],
},
}
)
}
allowCross={false}
/>
</div>
<div className="space-y-3">
<div className="flex space-x-2">
<i className="text-lg las la-plane-arrival"></i>
<span className="text-xs">Arrival time:</span>
<span className="text-xs text-primary-500 dark:text-primary-400">
{posts.Arrival[0]}:00 - {posts.Arrival[1]}:00
</span>
</div>
<Slider
range
min={0}
max={24}
defaultValue={posts.Arrival}
onChange={(val) =>
setCatTimes((catTimes) =>
!idx
? {
...catTimes,
"Take Off": {
...posts,
Arrival: val as [number, number],
},
}
: {
...catTimes,
Landing: {
...posts,
Arrival: val as [number, number],
},
}
)
}
allowCross={false}
/>
</div>
</Tab.Panel>
);
})}
</Tab.Panels>
</Tab.Group>
</div>
);
};
const renderTabsTypeOfAirlines = () => {
return (
<Popover className="relative">
{({ open, close }) => (
<>
<Popover.Button
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-neutral-300 dark:border-neutral-700 focus:outline-none
${open ? "!border-primary-500 " : ""}
${
!!airlinesStates.length
? "!border-primary-500 bg-primary-50"
: ""
}
`}
>
<span>Airlines</span>
{!airlinesStates.length ? (
<i className="las la-angle-down ml-2"></i>
) : (
<span onClick={() => setAirlinesStates([])}>
{renderXClear()}
</span>
)}
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 lg:max-w-md">
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700">
<div className="relative flex flex-col px-5 py-6 space-y-5">
<Checkbox
name="All Airlines"
label="All Airlines"
defaultChecked={airlinesStates.includes("All Airlines")}
onChange={(checked) =>
handleChangeAirlines(checked, "All Airlines")
}
/>
<hr />
{typeOfAirlines.map((item) => (
<div key={item.name} className="">
<Checkbox
name={item.name}
label={item.name}
defaultChecked={airlinesStates.includes(item.name)}
onChange={(checked) =>
handleChangeAirlines(checked, item.name)
}
/>
</div>
))}
</div>
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between">
<ButtonThird
onClick={() => {
close();
setAirlinesStates([]);
}}
sizeClass="px-4 py-2 sm:px-5"
>
Clear
</ButtonThird>
<ButtonPrimary
onClick={close}
sizeClass="px-4 py-2 sm:px-5"
>
Apply
</ButtonPrimary>
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
);
};
const renderTabsStopPoints = () => {
return (
<Popover className="relative">
{({ open, close }) => (
<>
<Popover.Button
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-neutral-300 dark:border-neutral-700 focus:outline-none
${open ? "!border-primary-500 " : ""}
${
!!stopPontsStates.length
? "!border-primary-500 bg-primary-50"
: ""
}
`}
>
<span>Stop points</span>
{!stopPontsStates.length ? (
<i className="las la-angle-down ml-2"></i>
) : (
<span onClick={() => setStopPontsStates([])}>
{renderXClear()}
</span>
)}
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 lg:max-w-md">
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700">
<div className="relative flex flex-col px-5 py-6 space-y-5">
{stopPoints.map((item) => (
<div key={item.name} className="">
<Checkbox
name={item.name}
label={item.name}
defaultChecked={stopPontsStates.includes(item.name)}
onChange={(checked) =>
handleChangeStopPoint(checked, item.name)
}
/>
</div>
))}
</div>
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between">
<ButtonThird
onClick={() => {
close();
setStopPontsStates([]);
}}
sizeClass="px-4 py-2 sm:px-5"
>
Clear
</ButtonThird>
<ButtonPrimary
onClick={close}
sizeClass="px-4 py-2 sm:px-5"
>
Apply
</ButtonPrimary>
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
);
};
const renderTabsTimeFlight = () => {
return (
<Popover className="relative">
{({ open, close }) => (
<>
<Popover.Button
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-neutral-300 dark:border-neutral-700 focus:outline-none ${
open ? "!border-primary-500 " : ""
}`}
>
<span>Flight time</span>
<i className="las la-angle-down ml-2"></i>
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 lg:max-w-md">
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700">
<div className="relative flex flex-col px-5 py-6 space-y-5">
{renderTabsTimeFlightTab()}
</div>
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between">
<ButtonThird onClick={close} sizeClass="px-4 py-2 sm:px-5">
Clear
</ButtonThird>
<ButtonPrimary
onClick={close}
sizeClass="px-4 py-2 sm:px-5"
>
Apply
</ButtonPrimary>
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
);
};
const renderTabsTripTime = () => {
return (
<Popover className="relative">
{({ open, close }) => (
<>
<Popover.Button
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-primary-500 bg-primary-50 text-primary-700 focus:outline-none `}
>
<span>less than {tripTimes} hours</span>
{renderXClear()}
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 ">
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700">
<div className="relative flex flex-col px-5 py-6 space-y-8">
<div className="space-y-5">
<div className="font-medium">
Trip time:
<span className="text-sm font-normal ml-1 text-primary-500">{` <${tripTimes} hours`}</span>
</div>
<Slider
min={1}
max={72}
defaultValue={tripTimes}
onChange={(e) => setTripTimes(e as number)}
/>
</div>
</div>
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between">
<ButtonThird onClick={close} sizeClass="px-4 py-2 sm:px-5">
Clear
</ButtonThird>
<ButtonPrimary
onClick={close}
sizeClass="px-4 py-2 sm:px-5"
>
Apply
</ButtonPrimary>
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
);
};
const renderTabsPriceRage = () => {
return (
<Popover className="relative">
{({ open, close }) => (
<>
<Popover.Button
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-primary-500 bg-primary-50 text-primary-700 focus:outline-none `}
>
<span>
{`$${convertNumbThousand(
rangePrices[0]
)} - $${convertNumbThousand(rangePrices[1])}`}{" "}
</span>
{renderXClear()}
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 ">
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700">
<div className="relative flex flex-col px-5 py-6 space-y-8">
<div className="space-y-5">
<span className="font-medium">Price per person</span>
<Slider
range
min={100}
max={5000}
defaultValue={[rangePrices[0], rangePrices[1]]}
allowCross={false}
onChange={(e) => setRangePrices(e as number[])}
/>
</div>
<div className="flex justify-between space-x-5">
<div>
<label
htmlFor="minPrice"
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300"
>
Min price
</label>
<div className="mt-1 relative rounded-md">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span className="text-neutral-500 sm:text-sm">
$
</span>
</div>
<input
type="text"
name="minPrice"
disabled
id="minPrice"
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900"
value={rangePrices[0]}
/>
</div>
</div>
<div>
<label
htmlFor="maxPrice"
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300"
>
Max price
</label>
<div className="mt-1 relative rounded-md">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span className="text-neutral-500 sm:text-sm">
$
</span>
</div>
<input
type="text"
disabled
name="maxPrice"
id="maxPrice"
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900"
value={rangePrices[1]}
/>
</div>
</div>
</div>
</div>
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between">
<ButtonThird onClick={close} sizeClass="px-4 py-2 sm:px-5">
Clear
</ButtonThird>
<ButtonPrimary
onClick={close}
sizeClass="px-4 py-2 sm:px-5"
>
Apply
</ButtonPrimary>
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
);
};
const renderTabOnSale = () => {
return (
<div
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border focus:outline-none cursor-pointer transition-all ${
isOnSale
? "border-primary-500 bg-primary-50 text-primary-700"
: "border-neutral-300 dark:border-neutral-700"
}`}
onClick={() => setIsOnSale(!isOnSale)}
>
<span>On sale</span>
{isOnSale && renderXClear()}
</div>
);
};
const renderMoreFilterItem = (
data: {
name: string;
description?: string;
defaultChecked?: boolean;
}[]
) => {
const list1 = data.filter((_, i) => i < data.length / 2);
const list2 = data.filter((_, i) => i >= data.length / 2);
return (
<div className="grid grid-cols-2 gap-8">
<div className="flex flex-col space-y-5">
{list1.map((item) => (
<Checkbox
key={item.name}
name={item.name}
subLabel={item.description}
label={item.name}
defaultChecked={!!item.defaultChecked}
/>
))}
</div>
<div className="flex flex-col space-y-5">
{list2.map((item) => (
<Checkbox
key={item.name}
name={item.name}
subLabel={item.description}
label={item.name}
defaultChecked={!!item.defaultChecked}
/>
))}
</div>
</div>
);
};
// FOR RESPONSIVE MOBILE
const renderTabMobileFilter = () => {
return (
<div>
<div
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-primary-500 bg-primary-50 text-primary-700 focus:outline-none cursor-pointer`}
onClick={openModalMoreFilter}
>
<span>
<span className="hidden sm:inline">Flights</span> filters (3)
</span>
{renderXClear()}
</div>
<Transition appear show={isOpenMoreFilter} as={Fragment}>
<Dialog
as="div"
className="fixed inset-0 z-50 overflow-y-auto"
onClose={closeModalMoreFilter}
>
<div className="min-h-screen text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-40 dark:bg-opacity-60" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span
className="inline-block h-screen align-middle"
aria-hidden="true"
>
&#8203;
</span>
<Transition.Child
className="inline-block py-8 px-2 h-screen w-full max-w-4xl"
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div className="inline-flex flex-col w-full max-w-4xl text-left align-middle transition-all transform overflow-hidden rounded-2xl bg-white dark:bg-neutral-900 dark:border dark:border-neutral-700 dark:text-neutral-100 shadow-xl h-full">
<div className="relative flex-shrink-0 px-6 py-4 border-b border-neutral-200 dark:border-neutral-800 text-center">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
>
Flight filters
</Dialog.Title>
<span className="absolute left-3 top-3">
<ButtonClose onClick={closeModalMoreFilter} />
</span>
</div>
<div className="flex-grow overflow-y-auto">
<div className="px-4 md:px-10 divide-y divide-neutral-200 dark:divide-neutral-800">
{/* --------- */}
{/* ---- */}
<div className="py-7">
<h3 className="text-xl font-medium">Airlines</h3>
<div className="mt-6 relative ">
{renderMoreFilterItem(typeOfAirlines)}
</div>
</div>
{/* --------- */}
{/* ---- */}
<div className="py-7">
<h3 className="text-xl font-medium">Stop points</h3>
<div className="mt-6 relative ">
{renderMoreFilterItem(stopPoints)}
</div>
</div>
{/* --------- */}
{/* ---- */}
<div className="py-7">
<h3 className="text-xl font-medium">Range Prices</h3>
<div className="mt-6 relative ">
<div className="relative flex flex-col space-y-8">
<div className="space-y-5">
<Slider
range
className="text-red-400"
min={0}
max={2000}
defaultValue={[0, 1000]}
allowCross={false}
onChange={(e) => setRangePrices(e as number[])}
/>
</div>
<div className="flex justify-between space-x-5">
<div>
<label
htmlFor="minPrice"
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300"
>
Min price
</label>
<div className="mt-1 relative rounded-md">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span className="text-neutral-500 sm:text-sm">
$
</span>
</div>
<input
type="text"
name="minPrice"
disabled
id="minPrice"
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900"
value={rangePrices[0]}
/>
</div>
</div>
<div>
<label
htmlFor="maxPrice"
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300"
>
Max price
</label>
<div className="mt-1 relative rounded-md">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span className="text-neutral-500 sm:text-sm">
$
</span>
</div>
<input
type="text"
disabled
name="maxPrice"
id="maxPrice"
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900"
value={rangePrices[1]}
/>
</div>
</div>
</div>
</div>
</div>
</div>
{/* --------- */}
{/* ---- */}
<div className="py-7">
<h3 className="text-xl font-medium">
Strip times
<span className="text-sm font-normal ml-1 text-primary-500">{` <${tripTimes} hours`}</span>
</h3>
<div className="mt-6 relative ">
<Slider
min={1}
max={72}
defaultValue={tripTimes}
onChange={(e) => setTripTimes(e as number)}
/>
</div>
</div>
{/* --------- */}
{/* ---- */}
<div className="py-7">
<h3 className="text-xl font-medium">Flight times</h3>
<div className="relative flex flex-col py-5 space-y-5">
{renderTabsTimeFlightTab()}
</div>
</div>
</div>
</div>
<div className="p-4 sm:p-6 flex-shrink-0 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between">
<ButtonThird
onClick={closeModalMoreFilter}
sizeClass="px-4 py-2 sm:px-5"
>
Clear
</ButtonThird>
<ButtonPrimary
onClick={closeModalMoreFilter}
sizeClass="px-4 py-2 sm:px-5"
>
Apply
</ButtonPrimary>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition>
</div>
);
};
return (
<div className="flex lg:space-x-4">
{/* FOR DESKTOP */}
<div className="hidden lg:flex space-x-4">
{renderTabsTypeOfAirlines()}
{renderTabsTripTime()}
{renderTabsStopPoints()}
{renderTabsPriceRage()}
{renderTabsTimeFlight()}
{renderTabOnSale()}
</div>
{/* FOR RESPONSIVE MOBILE */}
<div className="flex lg:hidden space-x-4">
{renderTabMobileFilter()}
{renderTabOnSale()}
</div>
</div>
);
};
export default TabFilters;
Loading…
Cancel
Save