vkashti / app / admin / reservations / DayColumn.tsx
DayColumn.tsx
Raw
import React from 'react';
import { useReservations } from './ReservationsProvider';
import { Reservation } from '@/types/types';
import TimeSlot from './TimeSlot';
import ReservationCard from './ReservationCard';

const HOUR_HEIGHT = 50;
const MIN_CARD_HEIGHT = 100;
const DAY_START = 10;
const TOTAL_HOURS = 14;
const TIMELINE_HEIGHT = TOTAL_HOURS * HOUR_HEIGHT;

interface ReservationLayout {
  reservation: Reservation;
  column: number;
  columnSpan: number;
  totalColumns: number;
}

const calculateReservationLayout = (
  reservations: Reservation[]
): ReservationLayout[] => {
  if (reservations.length === 0) return [];

  const sorted = [...reservations].sort((a, b) => {
    const startA = new Date(a.from_date).getTime();
    const startB = new Date(b.from_date).getTime();

    if (startA === startB) {
      const durationA = new Date(a.to_date).getTime() - startA;
      const durationB = new Date(b.to_date).getTime() - startB;
      return durationB - durationA;
    }

    return startA - startB;
  });

  const layouts: ReservationLayout[] = [];
  const columns: (number | null)[][] = [];

  sorted.forEach((reservation) => {
    const startTime = new Date(reservation.from_date).getTime();
    const endTime = new Date(reservation.to_date).getTime();

    let assignedColumn = -1;

    for (let i = 0; i < columns.length; i++) {
      const column = columns[i];
      let canPlace = true;

      for (let j = 0; j < column.length; j++) {
        const existingEventEndTime = column[j];
        if (existingEventEndTime !== null && startTime < existingEventEndTime) {
          canPlace = false;
          break;
        }
      }

      if (canPlace) {
        assignedColumn = i;
        break;
      }
    }

    if (assignedColumn === -1) {
      assignedColumn = columns.length;
      columns.push([]);
    }

    columns[assignedColumn].push(endTime);

    layouts.push({
      reservation,
      column: assignedColumn,
      columnSpan: 1,
      totalColumns: columns.length
    });
  });

  return layouts;
};

interface DayColumnProps {
  date: Date;
  reservations: Reservation[];
  showHours?: 'left' | 'right';
  onReservationClick: (reservation: Reservation) => void;
}

export default function DayColumn({
  date,
  reservations,
  showHours,
  onReservationClick
}: DayColumnProps) {
  const { updateReservation, deleteReservation, view } = useReservations();
  const reservationLayouts = calculateReservationLayout(reservations);

  const handleDrop = (
    reservation: Reservation,
    startTime: Date,
    endTime: Date
  ) => {
    updateReservation(reservation.id, {
      from_date: startTime.toISOString(),
      to_date: endTime.toISOString()
    });
  };

  return (
    <div className="h-full">
      <div className="text-center font-bold mb-2">
        <div className="flex flex-col items-center">
          <span className="text-gray-600 text-xs">{date.toLocaleDateString('bg-BG', { weekday: 'short' }).toLowerCase()}</span>
          <span className="text-gray-900 text-sm">
            {date.toLocaleDateString('bg-BG', { day: '2-digit', month: '2-digit' }).replace('/', '.')}
          </span>
        </div>
      </div>
      {view === 'grid' ? (
        <div className="relative" style={{ height: TIMELINE_HEIGHT }}>
          {Array.from({ length: TOTAL_HOURS }, (_, i) => (
            <TimeSlot
              key={i}
              hour={i + DAY_START}
              date={date}
              showHour={showHours}
              onDrop={handleDrop}
            />
          ))}
          {reservationLayouts.map((layout) => (
            <ReservationCard
              key={layout.reservation.id}
              variant="kanban"
              reservation={layout.reservation}
              style={{
                position: 'absolute',
                top: `${
                  ((new Date(layout.reservation.from_date).getHours() -
                    DAY_START) *
                    60 +
                    new Date(layout.reservation.from_date).getMinutes()) *
                  (HOUR_HEIGHT / 60)
                }px`,
                height: `${Math.max(
                  ((new Date(layout.reservation.to_date).getTime() -
                    new Date(layout.reservation.from_date).getTime()) /
                    (1000 * 60 * 60)) *
                    HOUR_HEIGHT,
                  MIN_CARD_HEIGHT
                )}px`,
                left: `${(layout.column * 100) / layout.totalColumns}%`,
                width: `${(layout.columnSpan * 100) / layout.totalColumns}%`
              }}
              onClick={(e) => {
                e.stopPropagation();
                onReservationClick(layout.reservation);
              }}
            />
          ))}
        </div>
      ) : (
        <div className="flex flex-col gap-2">
          {reservations
            .sort((a, b) => new Date(a.from_date).getTime() - new Date(b.from_date).getTime())
            .map((reservation) => (
              <ReservationCard
                key={reservation.id}
                variant="list"
                reservation={reservation}
                onEdit={onReservationClick}
                onDelete={(id) => {
                  deleteReservation(id);
                }}
                onApprove={(id) => {
                  updateReservation(parseInt(id), { approved: true });
                }}
                onUnapprove={(id) => {
                  updateReservation(parseInt(id), { approved: false });
                }}
              />
            ))}
          {reservations.length === 0 && (
            <div className="text-center text-gray-500 py-4">No reservations</div>
          )}
        </div>
      )}
    </div>
  );
}