vkashti / app / admin / reservations / TimeSlot.tsx
TimeSlot.tsx
Raw
import React, { useState, useRef } from 'react';
import { useDrop } from 'react-dnd';
import { useReservations } from './ReservationsProvider';
import { Reservation } from '@/types/types';

const HOUR_HEIGHT = 50; // pixels per hour

export const roundToNearest15Min = (date: Date) => {
  const minutes = date.getMinutes();
  const remainder = minutes % 15;
  const roundedMinutes =
    remainder < 8 ? minutes - remainder : minutes + (15 - remainder);
  const rounded = new Date(date);
  rounded.setMinutes(roundedMinutes);
  rounded.setSeconds(0);
  rounded.setMilliseconds(0);
  return rounded;
};

export type TimeSlotProps = {
  hour: number;
  date: Date;
  onDrop: (reservation: Reservation, startTime: Date, endTime: Date) => void;
  showHour?: 'left' | 'right';
};

export default function TimeSlot({ hour, date, onDrop, showHour }: TimeSlotProps) {
  const { createReservation } = useReservations();
  const [isDrawing, setIsDrawing] = useState(false);
  const [startY, setStartY] = useState<number | null>(null);
  const timeSlotRef = useRef<HTMLDivElement>(null);

  const handleMouseDown = (e: React.MouseEvent) => {
    setIsDrawing(true);
    setStartY(e.clientY);
  };

  const handleMouseUp = (e: React.MouseEvent) => {
    if (isDrawing && startY !== null) {
      const rect = e.currentTarget.getBoundingClientRect();
      const startTime = new Date(date);
      const endTime = new Date(date);

      const startMinute = Math.floor(((startY - rect.top) / HOUR_HEIGHT) * 60);
      const endMinute = Math.floor(((e.clientY - rect.top) / HOUR_HEIGHT) * 60);

      startTime.setHours(hour, Math.min(startMinute, endMinute));
      endTime.setHours(hour + 2, Math.max(startMinute, endMinute));

      const roundedStart = roundToNearest15Min(startTime);
      const roundedEnd = roundToNearest15Min(endTime);

      createReservation({
        from_date: roundedStart.toISOString(),
        to_date: roundedEnd.toISOString()
      });
    }
    setIsDrawing(false);
    setStartY(null);
  };

  const [{ isOver }, drop] = useDrop(() => ({
    accept: 'RESERVATION',
    drop: (item: any, monitor) => {
      const dropOffset = monitor.getClientOffset();
      if (!dropOffset) return;

      const rect = timeSlotRef.current?.getBoundingClientRect();
      if (!rect) return;

      const offsetY = dropOffset.y - rect.top - item.offsetY;
      const minute = Math.floor((offsetY / HOUR_HEIGHT) * 60);

      const startTimeUnrounded = new Date(date);
      startTimeUnrounded.setHours(hour, minute);
      const startTime = roundToNearest15Min(startTimeUnrounded);

      const duration =
        new Date(item.to_date).getTime() - new Date(item.from_date).getTime();
      const endTimeUnrounded = new Date(startTime.getTime() + duration);
      const endTime = roundToNearest15Min(endTimeUnrounded);

      onDrop(item, startTime, endTime);
    },
    collect: (monitor) => ({
      isOver: monitor.isOver()
    })
  }));

  drop(timeSlotRef);

  return (
    <div
      ref={timeSlotRef}
      onMouseDown={handleMouseDown}
      onMouseUp={handleMouseUp}
      className={`border-b border-gray-200 relative ${
        isOver ? 'bg-orange-100' : ''
      }`}
      style={{ height: HOUR_HEIGHT }}
    >
      {showHour && (
        <span
          className={`absolute ${showHour === 'left' ? '-left-0' : '-right-0'} top-0 text-xs text-gray-500`}
        >
          {hour.toString().padStart(2, '0')}:00
        </span>
      )}
    </div>
  );
}