Untitled

 avatar
unknown
plain_text
a month ago
29 kB
3
Indexable
import React, { useState, useMemo } from "react";

// Types
interface Booking {
  id: string;
  guestName: string;
  phone: string;
  email: string;
  date: string;
  time: string;
  partySize: number;
  tableNumber: number | null;
  status: "confirmed" | "pending" | "cancelled" | "completed";
  notes: string;
  createdAt: string;
}

type BookingStatus = Booking["status"];

// Sample data
const initialBookings: Booking[] = [
  {
    id: "1",
    guestName: "Sarah Johnson",
    phone: "(555) 123-4567",
    email: "[email protected]",
    date: "2026-05-19",
    time: "18:30",
    partySize: 4,
    tableNumber: 5,
    status: "confirmed",
    notes: "Anniversary dinner, window seat preferred",
    createdAt: "2026-05-15T10:30:00Z",
  },
  {
    id: "2",
    guestName: "Michael Chen",
    phone: "(555) 234-5678",
    email: "[email protected]",
    date: "2026-05-19",
    time: "19:00",
    partySize: 2,
    tableNumber: 3,
    status: "confirmed",
    notes: "",
    createdAt: "2026-05-16T14:20:00Z",
  },
  {
    id: "3",
    guestName: "Emma Williams",
    phone: "(555) 345-6789",
    email: "[email protected]",
    date: "2026-05-19",
    time: "20:00",
    partySize: 6,
    tableNumber: null,
    status: "pending",
    notes: "Birthday celebration, need high chair",
    createdAt: "2026-05-17T09:15:00Z",
  },
  {
    id: "4",
    guestName: "David Brown",
    phone: "(555) 456-7890",
    email: "[email protected]",
    date: "2026-05-20",
    time: "12:30",
    partySize: 3,
    tableNumber: 8,
    status: "confirmed",
    notes: "Gluten-free menu required",
    createdAt: "2026-05-17T11:45:00Z",
  },
  {
    id: "5",
    guestName: "Lisa Martinez",
    phone: "(555) 567-8901",
    email: "[email protected]",
    date: "2026-05-19",
    time: "17:00",
    partySize: 2,
    tableNumber: 1,
    status: "cancelled",
    notes: "",
    createdAt: "2026-05-14T16:30:00Z",
  },
  {
    id: "6",
    guestName: "James Wilson",
    phone: "(555) 678-9012",
    email: "[email protected]",
    date: "2026-05-21",
    time: "19:30",
    partySize: 8,
    tableNumber: null,
    status: "pending",
    notes: "Business dinner, private area if possible",
    createdAt: "2026-05-18T08:00:00Z",
  },
  {
    id: "7",
    guestName: "Amanda Taylor",
    phone: "(555) 789-0123",
    email: "[email protected]",
    date: "2026-05-18",
    time: "20:30",
    partySize: 4,
    tableNumber: 6,
    status: "completed",
    notes: "",
    createdAt: "2026-05-12T13:20:00Z",
  },
];

// Status badge component
const StatusBadge: React.FC<{ status: BookingStatus }> = ({ status }) => {
  const styles: Record<BookingStatus, string> = {
    confirmed: "bg-green-100 text-green-800 border-green-200",
    pending: "bg-yellow-100 text-yellow-800 border-yellow-200",
    cancelled: "bg-red-100 text-red-800 border-red-200",
    completed: "bg-gray-100 text-gray-800 border-gray-200",
  };

  return (
    <span
      className={`px-2.5 py-1 text-xs font-medium rounded-full border ${styles[status]}`}
    >
      {status.charAt(0).toUpperCase() + status.slice(1)}
    </span>
  );
};

// Stat card component
const StatCard: React.FC<{
  title: string;
  value: number | string;
  icon: React.ReactNode;
  color: string;
}> = ({ title, value, icon, color }) => (
  <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-5 hover:shadow-md transition-shadow">
    <div className="flex items-center justify-between">
      <div>
        <p className="text-sm font-medium text-gray-500">{title}</p>
        <p className="text-2xl font-bold text-gray-900 mt-1">{value}</p>
      </div>
      <div className={`p-3 rounded-lg ${color}`}>{icon}</div>
    </div>
  </div>
);

// Icons
const CalendarIcon = () => (
  <svg
    className="w-6 h-6"
    fill="none"
    stroke="currentColor"
    viewBox="0 0 24 24"
  >
    <path
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth={2}
      d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
    />
  </svg>
);

const UsersIcon = () => (
  <svg
    className="w-6 h-6"
    fill="none"
    stroke="currentColor"
    viewBox="0 0 24 24"
  >
    <path
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth={2}
      d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
    />
  </svg>
);

const ClockIcon = () => (
  <svg
    className="w-6 h-6"
    fill="none"
    stroke="currentColor"
    viewBox="0 0 24 24"
  >
    <path
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth={2}
      d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
    />
  </svg>
);

const CheckCircleIcon = () => (
  <svg
    className="w-6 h-6"
    fill="none"
    stroke="currentColor"
    viewBox="0 0 24 24"
  >
    <path
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth={2}
      d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
    />
  </svg>
);

const PlusIcon = () => (
  <svg
    className="w-5 h-5"
    fill="none"
    stroke="currentColor"
    viewBox="0 0 24 24"
  >
    <path
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth={2}
      d="M12 4v16m8-8H4"
    />
  </svg>
);

const SearchIcon = () => (
  <svg
    className="w-5 h-5"
    fill="none"
    stroke="currentColor"
    viewBox="0 0 24 24"
  >
    <path
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth={2}
      d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
    />
  </svg>
);

const XIcon = () => (
  <svg
    className="w-5 h-5"
    fill="none"
    stroke="currentColor"
    viewBox="0 0 24 24"
  >
    <path
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth={2}
      d="M6 18L18 6M6 6l12 12"
    />
  </svg>
);

// Booking form modal
const BookingModal: React.FC<{
  booking: Booking | null;
  isOpen: boolean;
  onClose: () => void;
  onSave: (booking: Booking) => void;
}> = ({ booking, isOpen, onClose, onSave }) => {
  const [formData, setFormData] = useState<Omit<Booking, "id" | "createdAt">>({
    guestName: "",
    phone: "",
    email: "",
    date: "",
    time: "",
    partySize: 2,
    tableNumber: null,
    status: "pending",
    notes: "",
  });

  React.useEffect(() => {
    if (booking) {
      setFormData({
        guestName: booking.guestName,
        phone: booking.phone,
        email: booking.email,
        date: booking.date,
        time: booking.time,
        partySize: booking.partySize,
        tableNumber: booking.tableNumber,
        status: booking.status,
        notes: booking.notes,
      });
    } else {
      setFormData({
        guestName: "",
        phone: "",
        email: "",
        date: "",
        time: "",
        partySize: 2,
        tableNumber: null,
        status: "pending",
        notes: "",
      });
    }
  }, [booking, isOpen]);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    onSave({
      id: booking?.id || Date.now().toString(),
      ...formData,
      createdAt: booking?.createdAt || new Date().toISOString(),
    });
    onClose();
  };

  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
      <div className="bg-white rounded-2xl shadow-xl w-full max-w-lg max-h-[90vh] overflow-y-auto">
        <div className="flex items-center justify-between p-5 border-b border-gray-100">
          <h2 className="text-xl font-semibold text-gray-900">
            {booking ? "Edit Booking" : "New Booking"}
          </h2>
          <button
            onClick={onClose}
            className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
          >
            <XIcon />
          </button>
        </div>

        <form onSubmit={handleSubmit} className="p-5 space-y-4">
          <div className="grid grid-cols-2 gap-4">
            <div className="col-span-2">
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Guest Name *
              </label>
              <input
                type="text"
                required
                value={formData.guestName}
                onChange={(e) =>
                  setFormData({ ...formData, guestName: e.target.value })
                }
                className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none"
              />
            </div>

            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Phone *
              </label>
              <input
                type="tel"
                required
                value={formData.phone}
                onChange={(e) =>
                  setFormData({ ...formData, phone: e.target.value })
                }
                className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none"
              />
            </div>

            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Email
              </label>
              <input
                type="email"
                value={formData.email}
                onChange={(e) =>
                  setFormData({ ...formData, email: e.target.value })
                }
                className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none"
              />
            </div>

            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Date *
              </label>
              <input
                type="date"
                required
                value={formData.date}
                onChange={(e) =>
                  setFormData({ ...formData, date: e.target.value })
                }
                className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none"
              />
            </div>

            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Time *
              </label>
              <input
                type="time"
                required
                value={formData.time}
                onChange={(e) =>
                  setFormData({ ...formData, time: e.target.value })
                }
                className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none"
              />
            </div>

            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Party Size *
              </label>
              <input
                type="number"
                required
                min={1}
                max={20}
                value={formData.partySize}
                onChange={(e) =>
                  setFormData({
                    ...formData,
                    partySize: parseInt(e.target.value),
                  })
                }
                className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none"
              />
            </div>

            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Table Number
              </label>
              <input
                type="number"
                min={1}
                value={formData.tableNumber || ""}
                onChange={(e) =>
                  setFormData({
                    ...formData,
                    tableNumber: e.target.value
                      ? parseInt(e.target.value)
                      : null,
                  })
                }
                className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none"
                placeholder="Assign later"
              />
            </div>

            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Status
              </label>
              <select
                value={formData.status}
                onChange={(e) =>
                  setFormData({
                    ...formData,
                    status: e.target.value as BookingStatus,
                  })
                }
                className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none bg-white"
              >
                <option value="pending">Pending</option>
                <option value="confirmed">Confirmed</option>
                <option value="completed">Completed</option>
                <option value="cancelled">Cancelled</option>
              </select>
            </div>

            <div className="col-span-2">
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Notes
              </label>
              <textarea
                value={formData.notes}
                onChange={(e) =>
                  setFormData({ ...formData, notes: e.target.value })
                }
                rows={3}
                className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none resize-none"
                placeholder="Special requests, dietary requirements, etc."
              />
            </div>
          </div>

          <div className="flex gap-3 pt-4">
            <button
              type="button"
              onClick={onClose}
              className="flex-1 px-4 py-2.5 border border-gray-200 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors font-medium"
            >
              Cancel
            </button>
            <button
              type="submit"
              className="flex-1 px-4 py-2.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium"
            >
              {booking ? "Save Changes" : "Create Booking"}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

// Main App
export default function App() {
  const [bookings, setBookings] = useState<Booking[]>(initialBookings);
  const [searchQuery, setSearchQuery] = useState("");
  const [statusFilter, setStatusFilter] = useState<BookingStatus | "all">(
    "all"
  );
  const [dateFilter, setDateFilter] = useState("");
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [editingBooking, setEditingBooking] = useState<Booking | null>(null);

  const today = "2026-05-19";

  // Calculate stats
  const stats = useMemo(() => {
    const todayBookings = bookings.filter((b) => b.date === today);
    const confirmed = todayBookings.filter(
      (b) => b.status === "confirmed"
    ).length;
    const pending = todayBookings.filter((b) => b.status === "pending").length;
    const totalGuests = todayBookings
      .filter((b) => b.status === "confirmed" || b.status === "pending")
      .reduce((sum, b) => sum + b.partySize, 0);

    return { todayTotal: todayBookings.length, confirmed, pending, totalGuests };
  }, [bookings]);

  // Filter bookings
  const filteredBookings = useMemo(() => {
    return bookings.filter((booking) => {
      const matchesSearch =
        booking.guestName.toLowerCase().includes(searchQuery.toLowerCase()) ||
        booking.phone.includes(searchQuery) ||
        booking.email.toLowerCase().includes(searchQuery.toLowerCase());

      const matchesStatus =
        statusFilter === "all" || booking.status === statusFilter;

      const matchesDate = !dateFilter || booking.date === dateFilter;

      return matchesSearch && matchesStatus && matchesDate;
    });
  }, [bookings, searchQuery, statusFilter, dateFilter]);

  const handleSaveBooking = (booking: Booking) => {
    setBookings((prev) => {
      const exists = prev.find((b) => b.id === booking.id);
      if (exists) {
        return prev.map((b) => (b.id === booking.id ? booking : b));
      }
      return [...prev, booking];
    });
  };

  const handleDeleteBooking = (id: string) => {
    if (confirm("Are you sure you want to delete this booking?")) {
      setBookings((prev) => prev.filter((b) => b.id !== id));
    }
  };

  const handleStatusChange = (id: string, status: BookingStatus) => {
    setBookings((prev) =>
      prev.map((b) => (b.id === id ? { ...b, status } : b))
    );
  };

  const openEditModal = (booking: Booking) => {
    setEditingBooking(booking);
    setIsModalOpen(true);
  };

  const openNewModal = () => {
    setEditingBooking(null);
    setIsModalOpen(true);
  };

  const formatTime = (time: string) => {
    const [hours, minutes] = time.split(":");
    const h = parseInt(hours);
    const ampm = h >= 12 ? "PM" : "AM";
    const hour12 = h % 12 || 12;
    return `${hour12}:${minutes} ${ampm}`;
  };

  const formatDate = (dateStr: string) => {
    const date = new Date(dateStr + "T00:00:00");
    return date.toLocaleDateString("en-US", {
      weekday: "short",
      month: "short",
      day: "numeric",
    });
  };

  return (
    <div className="min-h-screen bg-gray-50">
      {/* Header */}
      <header className="bg-white border-b border-gray-100 sticky top-0 z-40">
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
          <div className="flex items-center justify-between h-16">
            <div className="flex items-center gap-3">
              <div className="w-10 h-10 bg-indigo-600 rounded-lg flex items-center justify-center">
                <span className="text-white text-xl">🍽️</span>
              </div>
              <div>
                <h1 className="text-xl font-bold text-gray-900">
                  Booking Manager
                </h1>
                <p className="text-xs text-gray-500">
                  {formatDate(today)} • Today
                </p>
              </div>
            </div>
            <button
              onClick={openNewModal}
              className="flex items-center gap-2 px-4 py-2.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium shadow-sm"
            >
              <PlusIcon />
              <span className="hidden sm:inline">New Booking</span>
            </button>
          </div>
        </div>
      </header>

      <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
        {/* Stats */}
        <div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
          <StatCard
            title="Today's Bookings"
            value={stats.todayTotal}
            icon={<CalendarIcon />}
            color="bg-indigo-100 text-indigo-600"
          />
          <StatCard
            title="Confirmed"
            value={stats.confirmed}
            icon={<CheckCircleIcon />}
            color="bg-green-100 text-green-600"
          />
          <StatCard
            title="Pending"
            value={stats.pending}
            icon={<ClockIcon />}
            color="bg-yellow-100 text-yellow-600"
          />
          <StatCard
            title="Expected Guests"
            value={stats.totalGuests}
            icon={<UsersIcon />}
            color="bg-purple-100 text-purple-600"
          />
        </div>

        {/* Filters */}
        <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-4 mb-6">
          <div className="flex flex-col sm:flex-row gap-4">
            <div className="flex-1 relative">
              <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
                <SearchIcon />
              </div>
              <input
                type="text"
                placeholder="Search by name, phone, or email..."
                value={searchQuery}
                onChange={(e) => setSearchQuery(e.target.value)}
                className="w-full pl-10 pr-4 py-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none"
              />
            </div>

            <div className="flex gap-3">
              <select
                value={statusFilter}
                onChange={(e) =>
                  setStatusFilter(e.target.value as BookingStatus | "all")
                }
                className="px-4 py-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none bg-white min-w-[140px]"
              >
                <option value="all">All Status</option>
                <option value="confirmed">Confirmed</option>
                <option value="pending">Pending</option>
                <option value="completed">Completed</option>
                <option value="cancelled">Cancelled</option>
              </select>

              <input
                type="date"
                value={dateFilter}
                onChange={(e) => setDateFilter(e.target.value)}
                className="px-4 py-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none"
              />

              {(dateFilter || statusFilter !== "all" || searchQuery) && (
                <button
                  onClick={() => {
                    setDateFilter("");
                    setStatusFilter("all");
                    setSearchQuery("");
                  }}
                  className="px-4 py-2.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
                >
                  Clear
                </button>
              )}
            </div>
          </div>
        </div>

        {/* Bookings List */}
        <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
          <div className="overflow-x-auto">
            <table className="w-full">
              <thead>
                <tr className="bg-gray-50 border-b border-gray-100">
                  <th className="text-left px-5 py-3.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
                    Guest
                  </th>
                  <th className="text-left px-5 py-3.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
                    Date & Time
                  </th>
                  <th className="text-left px-5 py-3.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
                    Party
                  </th>
                  <th className="text-left px-5 py-3.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
                    Table
                  </th>
                  <th className="text-left px-5 py-3.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
                    Status
                  </th>
                  <th className="text-right px-5 py-3.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
                    Actions
                  </th>
                </tr>
              </thead>
              <tbody className="divide-y divide-gray-100">
                {filteredBookings.length === 0 ? (
                  <tr>
                    <td
                      colSpan={6}
                      className="px-5 py-12 text-center text-gray-500"
                    >
                      <div className="flex flex-col items-center">
                        <CalendarIcon />
                        <p className="mt-2 font-medium">No bookings found</p>
                        <p className="text-sm">
                          Try adjusting your filters or create a new booking
                        </p>
                      </div>
                    </td>
                  </tr>
                ) : (
                  filteredBookings.map((booking) => (
                    <tr
                      key={booking.id}
                      className="hover:bg-gray-50 transition-colors"
                    >
                      <td className="px-5 py-4">
                        <div>
                          <p className="font-medium text-gray-900">
                            {booking.guestName}
                          </p>
                          <p className="text-sm text-gray-500">
                            {booking.phone}
                          </p>
                          {booking.notes && (
                            <p className="text-xs text-indigo-600 mt-1 truncate max-w-[200px]">
                              📝 {booking.notes}
                            </p>
                          )}
                        </div>
                      </td>
                      <td className="px-5 py-4">
                        <p className="font-medium text-gray-900">
                          {formatDate(booking.date)}
                        </p>
                        <p className="text-sm text-gray-500">
                          {formatTime(booking.time)}
                        </p>
                      </td>
                      <td className="px-5 py-4">
                        <span className="inline-flex items-center gap-1.5 text-gray-900">
                          <UsersIcon />
                          {booking.partySize}
                        </span>
                      </td>
                      <td className="px-5 py-4">
                        {booking.tableNumber ? (
                          <span className="inline-flex items-center justify-center w-8 h-8 bg-indigo-100 text-indigo-700 rounded-lg font-semibold text-sm">
                            {booking.tableNumber}
                          </span>
                        ) : (
                          <span className="text-gray-400 text-sm">—</span>
                        )}
                      </td>
                      <td className="px-5 py-4">
                        <select
                          value={booking.status}
                          onChange={(e) =>
                            handleStatusChange(
                              booking.id,
                              e.target.value as BookingStatus
                            )
                          }
                          className="appearance-none bg-transparent border-0 p-0 focus:ring-0 cursor-pointer"
                        >
                          <option value="pending">🟡 Pending</option>
                          <option value="confirmed">🟢 Confirmed</option>
                          <option value="completed">⚫ Completed</option>
                          <option value="cancelled">🔴 Cancelled</option>
                        </select>
                      </td>
                      <td className="px-5 py-4 text-right">
                        <div className="flex items-center justify-end gap-2">
                          <button
                            onClick={() => openEditModal(booking)}
                            className="px-3 py-1.5 text-sm text-indigo-600 hover:bg-indigo-50 rounded-lg transition-colors font-medium"
                          >
                            Edit
                          </button>
                          <button
                            onClick={() => handleDeleteBooking(booking.id)}
                            className="px-3 py-1.5 text-sm text-red-600 hover:bg-red-50 rounded-lg transition-colors font-medium"
                          >
                            Delete
                          </button>
                        </div>
                      </td>
                    </tr>
                  ))
                )}
              </tbody>
            </table>
          </div>

          {filteredBookings.length > 0 && (
            <div className="px-5 py-3 bg-gray-50 border-t border-gray-100 text-sm text-gray-500">
              Showing {filteredBookings.length} of {bookings.length} bookings
            </div>
          )}
        </div>
      </main>

      {/* Modal */}
      <BookingModal
        booking={editingBooking}
        isOpen={isModalOpen}
        onClose={() => setIsModalOpen(false)}
        onSave={handleSaveBooking}
      />
    </div>
  );
}
Editor is loading...
Leave a Comment