import React, { useEffect, useState, useMemo } from 'react' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' import CardBoxModal from '../CardBoxModal' import CardBox from "../CardBox"; import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/reservations/reservationsSlice' import { useAppDispatch, useAppSelector } from '../../stores/hooks' import { useRouter } from 'next/router' import { Field, Form, Formik } from "formik"; import { DataGrid, GridColDef, } from '@mui/x-data-grid'; import {loadColumns} from "./configureReservationsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import { getIncompleteFilterHint, isFilterItemIncomplete, pruneHiddenFilterItems } from '../../helpers/entityVisibility' import {dataGridStyles} from "../../styles"; import BigCalendar from "../BigCalendar"; import { SlotInfo } from 'react-big-calendar'; const perPage = 25 const compactColumnVisibilityModel = { tenant: false, organization: false, booking_request: false, unit_type: false, actual_check_in_at: false, actual_check_out_at: false, early_check_in: false, late_check_out: false, monthly_rate: false, currency: false, internal_notes: false, external_notes: false, guests: false, service_requests: false, invoices: false, documents: false, comments: false, } const TableSampleReservations = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); const router = useRouter(); const pagesList = []; const [id, setId] = useState(null); const [currentPage, setCurrentPage] = useState(0); const [filterRequest, setFilterRequest] = React.useState(''); const [columns, setColumns] = useState([]); const [selectedRows, setSelectedRows] = useState([]); const [sortModel, setSortModel] = useState([ { field: '', sort: 'desc', }, ]); const { reservations, loading, count, notify: reservationsNotify, refetch } = useAppSelector((state) => state.reservations) const { currentUser } = useAppSelector((state) => state.auth); const focusRing = useAppSelector((state) => state.style.focusRingColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); const corners = useAppSelector((state) => state.style.corners); const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); for (let i = 0; i < numPages; i++) { pagesList.push(i); } const loadData = async (page = currentPage, request = filterRequest) => { if (page !== currentPage) setCurrentPage(page); if (request !== filterRequest) setFilterRequest(request); const { sort, field } = sortModel[0]; const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; dispatch(fetch({ limit: perPage, page, query })); }; useEffect(() => { if (reservationsNotify.showNotification) { notify(reservationsNotify.typeNotification, reservationsNotify.textNotification); } }, [reservationsNotify.showNotification]); useEffect(() => { if (!currentUser) return; loadData(); }, [sortModel, currentUser]); useEffect(() => { if (refetch) { loadData(0); dispatch(setRefetch(false)); } }, [refetch, dispatch]); const validFilterItems = useMemo(() => pruneHiddenFilterItems(filterItems, filters), [filterItems, filters]); useEffect(() => { if (validFilterItems !== filterItems) { setFilterItems(validFilterItems); } }, [filterItems, setFilterItems, validFilterItems]); const [isModalInfoActive, setIsModalInfoActive] = useState(false) const [isModalTrashActive, setIsModalTrashActive] = useState(false) const handleModalAction = () => { setIsModalInfoActive(false) setIsModalTrashActive(false) } const handleCreateEventAction = ({ start, end }: SlotInfo) => { router.push( `/reservations/reservations-new?dateRangeStart=${start.toISOString()}&dateRangeEnd=${end.toISOString()}`, ); }; const handleDeleteModalAction = (id: string) => { setId(id) setIsModalTrashActive(true) } const handleDeleteAction = async () => { if (id) { await dispatch(deleteItem(id)); await loadData(0); setIsModalTrashActive(false); } }; const reservationCalendarOverview = useMemo(() => { const today = new Date(); const nextSevenDays = new Date(today); nextSevenDays.setDate(nextSevenDays.getDate() + 7); const totals = reservations.reduce( (accumulator, item) => { const checkIn = item?.check_in_at ? new Date(item.check_in_at) : null; const checkOut = item?.check_out_at ? new Date(item.check_out_at) : null; const status = item?.status || 'quoted'; accumulator.total += 1; accumulator[status] = (accumulator[status] || 0) + 1; if (checkIn && checkIn >= today && checkIn <= nextSevenDays) { accumulator.upcomingArrivals += 1; } if (checkOut && checkOut >= today && checkOut <= nextSevenDays) { accumulator.upcomingDepartures += 1; } return accumulator; }, { total: 0, confirmed: 0, checked_in: 0, upcomingArrivals: 0, upcomingDepartures: 0, }, ); return [ { label: 'Visible stays', value: totals.total, hint: 'Loaded in this calendar range', }, { label: 'Confirmed', value: totals.confirmed, hint: 'Booked and upcoming', }, { label: 'In house', value: totals.checked_in, hint: 'Currently checked in', }, { label: 'Departures soon', value: totals.upcomingDepartures, hint: 'Next 7 days', }, ]; }, [reservations]); const generateFilterRequests = useMemo(() => { let request = '&'; validFilterItems.forEach((item) => { const isRangeFilter = filters.find( (filter) => filter.title === item.fields.selectedField && (filter.number || filter.date), ); if (isRangeFilter) { const from = item.fields.filterValueFrom; const to = item.fields.filterValueTo; if (from) { request += `${item.fields.selectedField}Range=${from}&`; } if (to) { request += `${item.fields.selectedField}Range=${to}&`; } } else { const value = item.fields.filterValue; if (value) { request += `${item.fields.selectedField}=${value}&`; } } }); return request; }, [filters, validFilterItems]); const deleteFilter = (value) => { const newItems = validFilterItems.filter((item) => item.id !== value); if (newItems.length) { setFilterItems(newItems); } else { loadData(0, ''); setFilterItems(newItems); } }; const handleSubmit = () => { loadData(0, generateFilterRequests); }; const handleChange = (id) => (e) => { const value = e.target.value; const name = e.target.name; setFilterItems( validFilterItems.map((item) => { if (item.id !== id) return item; if (name === 'selectedField') return { id, fields: { [name]: value } }; return { id, fields: { ...item.fields, [name]: value } } }), ); }; const handleReset = () => { setFilterItems([]); loadData(0, ''); }; const onPageChange = (page: number) => { loadData(page); setCurrentPage(page); }; useEffect(() => { if (!currentUser) return; loadColumns( handleDeleteModalAction, `reservations`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); const handleTableSubmit = async (id: string, data) => { if (!_.isEmpty(data)) { await dispatch(update({ id, data })) .unwrap() .then((res) => res) .catch((err) => { throw new Error(err); }); } }; const onDeleteRows = async (selectedRows) => { await dispatch(deleteItemsByIds(selectedRows)); await loadData(0); }; const controlClasses = 'w-full rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-sm text-slate-100 placeholder:text-slate-400 ' + ' ' + bgColor + ' ' + focusRing + ' ' + corners + ' ' + 'dark:bg-slate-800/80 my-1'; const dataGrid = (
`datagrid--row`} rows={reservations ?? []} columns={columns} initialState={{ pagination: { paginationModel: { pageSize: 25, }, }, columns: { columnVisibilityModel: compactColumnVisibilityModel, }, }} disableRowSelectionOnClick onProcessRowUpdateError={(params) => { console.log('Error', params); }} processRowUpdate={async (newRow, oldRow) => { const data = dataFormatter.dataGridEditFormatter(newRow); try { await handleTableSubmit(newRow.id, data); return newRow; } catch { return oldRow; } }} sortingMode={'server'} checkboxSelection onRowSelectionModelChange={(ids) => { setSelectedRows(ids) }} onSortModelChange={(params) => { params.length ? setSortModel(params) : setSortModel([{ field: '', sort: 'desc' }]); }} rowCount={count} pageSizeOptions={[25]} paginationMode={'server'} loading={loading} onPaginationModelChange={(params) => { onPageChange(params.page); }} />
) return ( <> {validFilterItems && Array.isArray(validFilterItems) && validFilterItems.length ? null} >
<> {validFilterItems && validFilterItems.map((filterItem) => { const showIncompleteHint = isFilterItemIncomplete(filterItem, filters) const incompleteHint = showIncompleteHint ? getIncompleteFilterHint(filterItem, filters) : null return (
Filter
{filters.map((selectOption) => ( ))}
{filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.type === 'enum' ? (
Value
{filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))}
) : filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.number ? (
From
To
) : filters.find( (filter) => filter.title === filterItem?.fields?.selectedField )?.date ? (
From
To
) : (
Contains
)}
Action
{ deleteFilter(filterItem.id) }} /> {incompleteHint ? (

{incompleteHint}

) : null}
) })}
: null }

Are you sure you want to delete this item?

{!showGrid && ( <>
{reservationCalendarOverview.map((item) => (

{item.label}

{item.value}

{item.hint}

))}
{ loadData(0,`&calendarStart=${range.start}&calendarEnd=${range.end}`); }} isLoading={loading} emptyTitle='No reservations in this range' emptyDescription='Adjust the calendar window or add a reservation to start filling the schedule.' entityName={'reservations'} /> )} {showGrid && dataGrid} {selectedRows.length > 0 && createPortal( onDeleteRows(selectedRows)} />, document.getElementById('delete-rows-button'), )} ) } export default TableSampleReservations