194 lines
6.0 KiB
JavaScript
194 lines
6.0 KiB
JavaScript
import React, { useState, useRef, useEffect } from "react";
|
|
import {THRESH_HOLD} from "../../utils/constants"
|
|
|
|
const TimePicker = ({ label, onChange, interval = 10, value,checkInTime,checkOutTime }) => {
|
|
const [time, setTime] = useState(value || "");
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const dropdownRef = useRef(null);
|
|
const slotRefs = useRef({});
|
|
|
|
const getCurrentTime = () => {
|
|
const now = new Date();
|
|
const minutes = now.getMinutes();
|
|
const roundedMinutes = Math.round(minutes / interval) * interval;
|
|
now.setMinutes(roundedMinutes);
|
|
now.setSeconds(0);
|
|
now.setMilliseconds(0);
|
|
return now;
|
|
};
|
|
|
|
const formatTime = (date) => {
|
|
let hours = date.getHours();
|
|
const minutes = date.getMinutes();
|
|
const period = hours >= 12 ? "PM" : "AM";
|
|
|
|
hours = hours % 12; // Convert 24-hour format to 12-hour format
|
|
hours = hours ? hours : 12; // The hour '0' should be '12'
|
|
const hoursFormatted = hours < 10 ? `0${hours}` : hours; // Ensure two-digit hour
|
|
const minutesFormatted = minutes < 10 ? `0${minutes}` : minutes; // Ensure two-digit minutes
|
|
|
|
return `${hoursFormatted}:${minutesFormatted} ${period}`;
|
|
};
|
|
|
|
const generateTimeSlots = () => {
|
|
const slots = [];
|
|
const now = new Date();
|
|
const currentSlot = new Date();
|
|
currentSlot.setMinutes(
|
|
Math.floor(currentSlot.getMinutes() / interval) * interval
|
|
);
|
|
currentSlot.setSeconds(0);
|
|
currentSlot.setMilliseconds(0);
|
|
|
|
const checkInDate = checkInTime ? new Date(checkInTime) : null;
|
|
|
|
const checkOutDate = checkOutTime ? new Date(checkOutTime) : null;
|
|
|
|
const dayStart = new Date();
|
|
const dayEnd = new Date();
|
|
if (checkInDate) {
|
|
dayStart.setTime(checkInDate.getTime());
|
|
dayEnd.setTime(checkInDate.getTime());
|
|
}
|
|
dayStart.setHours(0, 0, 0, 0);
|
|
dayEnd.setHours(23, 59, 59, 999);
|
|
|
|
let minSelectable = null;
|
|
let maxSelectable = null;
|
|
|
|
// Activity 0: Time slots based on current time
|
|
if (!checkInDate) {
|
|
// Case 1: No check-in time (new check-in)
|
|
minSelectable = new Date(dayStart);
|
|
maxSelectable = new Date(currentSlot);
|
|
}
|
|
// Activity 1: Time slots based on check-in time today
|
|
else if (checkInDate && !checkOutDate) {
|
|
const hoursDiff = (now - checkInDate) / (1000 * 60 * 60);
|
|
if (hoursDiff < THRESH_HOLD) {
|
|
// Case 2: Check-in present, no checkout, within THRESH_HOLD hours
|
|
minSelectable = new Date(checkInDate);
|
|
maxSelectable = new Date(currentSlot);
|
|
} else {
|
|
// Case 4: Check-in present, no checkout, more than THRESH_HOLD hours
|
|
minSelectable = new Date(checkInDate);
|
|
maxSelectable = new Date(dayEnd);
|
|
}
|
|
} else if (checkInDate && checkOutDate) {
|
|
// Case 3: Both check-in and checkout present
|
|
const isSameDay = new Date(checkOutDate).toDateString() === new Date().toDateString();
|
|
minSelectable = new Date(checkOutDate);
|
|
maxSelectable = isSameDay ? new Date(dayEnd) : new Date(currentSlot);
|
|
}
|
|
|
|
const slot = new Date(dayStart.getTime());
|
|
while (slot <= dayEnd) {
|
|
const formatted = formatTime(slot);
|
|
|
|
const isSelectable = slot >= minSelectable && slot <= maxSelectable;
|
|
slots.push({ time: formatted, isSelectable });
|
|
|
|
slot.setMinutes(slot.getMinutes() + interval);
|
|
}
|
|
|
|
return slots;
|
|
};
|
|
|
|
const handleSelect = (selectedTime) => {
|
|
setTime(selectedTime);
|
|
if (onChange) onChange(selectedTime);
|
|
setIsOpen(false);
|
|
};
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event) => {
|
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
}, []);
|
|
|
|
|
|
useEffect(() => {
|
|
if (!value) {
|
|
const defaultTime = formatTime(getCurrentTime());
|
|
setTime(defaultTime);
|
|
if (onChange) onChange(defaultTime);
|
|
}
|
|
}, [value, interval, onChange]);
|
|
|
|
|
|
useEffect(() => {
|
|
if (isOpen && time && slotRefs.current[time]) {
|
|
const selectedSlot = slotRefs.current[time];
|
|
selectedSlot.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "center",
|
|
});
|
|
}
|
|
}, [isOpen, time]);
|
|
|
|
|
|
return (
|
|
<div className="position-relative w-100" ref={dropdownRef}>
|
|
{label && <label className="form-label">{label}</label>}
|
|
<div className="position-relative">
|
|
<input
|
|
type="text"
|
|
className="form-control pe-5"
|
|
value={time}
|
|
readOnly
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
style={{ cursor: "pointer" }}
|
|
/>
|
|
<span
|
|
className="position-absolute end-0 top-50 translate-middle-y pe-3 d-flex align-items-center"
|
|
style={{ cursor: "pointer" }}
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
>
|
|
<i className="bx bx-time fs-4"></i>
|
|
</span>
|
|
</div>
|
|
|
|
{isOpen && (
|
|
<div
|
|
className="dropdown-menu show shadow-lg border rounded p-2"
|
|
style={{
|
|
position: "absolute",
|
|
width: "auto",
|
|
background: "#fff",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: "5px",
|
|
maxHeight: "200px",
|
|
overflowY: "auto",
|
|
}}
|
|
>
|
|
{generateTimeSlots().map(({ time: slotTime, isSelectable }, index) => (
|
|
<button
|
|
key={index}
|
|
className={`dropdown-item px-3 py-1 text-center rounded-1 ${
|
|
time === slotTime ? "btn-primary" : ""
|
|
}`}
|
|
ref={(el) => (slotRefs.current[slotTime] = el)}
|
|
onClick={(event) => {
|
|
event.preventDefault();
|
|
if (isSelectable) handleSelect(slotTime);
|
|
}}
|
|
disabled={!isSelectable}
|
|
style={
|
|
!isSelectable ? { opacity: 0.5, cursor: "not-allowed" } : {}
|
|
}
|
|
>
|
|
{slotTime}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default TimePicker; |