First of all thank you for this great tool. Probably my fault, I have not been able to use it efficiently yet. Following is my case:
I use Axios to fetch data from the api like the following code, payload determines the security and the query:
User clicks on the Search button. Search button makes the api call according to filters on the page, and a similar snapshot looks like this is displayed:
1- Later the grid filled out if any state change happens the template columns (Arrival,Departure,Docusign Status) loose their content:
2- If I remove the following condition the grid never displayed and the page crashes for after timeout.
3- The grid at the top image can be exported to pdf however if there are more rows (about 50-60) PDF Export not working and I see a log in console like the following:
import React, { useReducer, useState } from "react"
import {
IonButton,
IonItem,
IonLabel,
IonProgressBar,
IonSpinner,
IonSelect,
IonSelectOption,
IonInput,
IonToast,
IonModal,
IonGrid,
IonRow,
IonCol,
IonContent,
IonIcon,
} from "@ionic/react"
import axios from "axios"
import Moment from "moment"
import { IProperty, IToast } from "../lib/sharedInterface"
import DatePicker from "../components/DatePicker"
import Reservation from "../components/Reservation"
import {
ColumnDirective,
ColumnsDirective,
GridComponent,
Inject,
Sort,
Filter,
PdfExport,
ExcelExport,
Toolbar,
Resize,
} from "@syncfusion/ej2-react-grids"
import { App } from "@capacitor/app"
import { Capacitor } from "@capacitor/core"
import { download } from "ionicons/icons"
interface IState {
toast: IToast | null
showToast: boolean
}
const Reservations: React.FC = () => {
const [data, setData] = useState<any>({ result: [], count: 0 })
const [loading, setLoading] = useState<boolean>(true)
const [propertyIdList, setPropertyIdList] = useState<number[] | null>(null)
const [propertyList, setPropertyList] = useState<IProperty[]>([])
const [guest, setGuest] = useState("")
const [dateFrom, setDateFrom] = useState<string | null>(
Moment(new Date()).format("yyyy-MM-DD")
)
const [dateTo, setDateTo] = useState<string | null>(null)
const [state, setState] = useReducer(
(state: IState, newState: Partial<IState>) => ({
...state,
...newState,
}),
{
toast: null,
showToast: false,
}
)
const [reservationId, setReservationId] = useState<number | null>(null)
const ios = Capacitor.getPlatform() === "ios"
let grid: GridComponent | null
const toolbar = ["PdfExport", "ExcelExport"]
const toolbarClick = (args: { item: { id: string } }) => {
if (grid) {
if (args.item.id === "grid_pdfexport") {
grid.pdfExport()
} else if (args.item.id === "grid_excelexport") {
grid.excelExport()
}
}
}
const template = (args: any) => {
switch (args.column.field) {
case "checkIn":
return <div>{Moment(args.checkIn).format("L")}</div>
case "checkOut":
return <div>{Moment(args.checkOut).format("L")}</div>
case "docusignStatus":
return (
<div>
{args.docusignStatus}
{args.docusignStatus !== "" && (
<IonIcon
icon={download}
slot="end"
onClick={() => DownloadDocusignDocument(args.docusignId ?? "")}
size="large"
/>
)}
{args.docusignButton && (
<IonButton onClick={() => SendDocusignDocument(args)}>
Send
</IonButton>
)}
</div>
)
}
}
const dataBound = () => {
if (grid) {
grid.autoFitColumns([
"checkIn",
"checkOut",
"property",
"guestName",
"guestEmail",
"guestPhone",
"website",
"status",
])
}
}
const handleRefresh = () => {
if (grid) {
grid.refresh()
}
}
React.useEffect(() => {
const payload = {
token: localStorage.getItem("PM_TOKEN"),
adminId: localStorage.getItem("PM_ADMINID"),
fields: {
id: true,
property: true,
},
}
axios
.post(process.env.REACT_APP_API_BASE_URL + "property/list", payload)
.then(response => {
let list = response.data.map((p: IProperty) => ({
id: p.id,
property: p.property,
}))
setPropertyList(list)
let listId: number[] = []
response.data.map((p: IProperty) => (listId = listId.concat(p.id ?? 0)))
setPropertyIdList(listId)
setLoading(false)
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const DownloadDocusignDocument = (docusignId: string) => {
setLoading(true)
const payload = {
token: localStorage.getItem("PM_TOKEN"),
docusignAccessToken: localStorage.getItem("PM_DOCUSIGNACCESSTOKEN"),
adminId: localStorage.getItem("PM_ADMINID"),
docusignId: docusignId,
}
axios
.post(
process.env.REACT_APP_API_BASE_URL +
"reservation/downloadDocusignDocument",
payload,
{ responseType: "blob" }
)
.then(response => {
const file = new Blob([response.data], { type: "application/pdf" }) //Build a URL from the file
const fileURL = URL.createObjectURL(file) //Open the URL on new Window
window.open(fileURL)
setLoading(false)
})
.catch(() => {
setLoading(false)
})
}
const SendDocusignDocument = (args: any) => {
setLoading(true)
const payload = {
token: localStorage.getItem("PM_TOKEN"),
docusignAccessToken: localStorage.getItem("PM_DOCUSIGNACCESSTOKEN"),
adminId: localStorage.getItem("PM_ADMINID"),
reservationId: args.id,
}
axios
.post(
process.env.REACT_APP_API_BASE_URL + "reservation/sendDocusignDocument",
payload
)
.then(response => {
if (response.data.response) {
args.docusignButton = false
args.docusignStatus = "sent"
}
setState({
toast: {
explanationType: response.data.explanationType,
explanation: response.data.explanation,
},
showToast: true,
})
setLoading(false)
})
.catch(() => {
setState({
toast: {
explanationType: "danger",
explanation: "Error occured",
},
showToast: true,
})
setLoading(false)
})
}
const Search = () => {
setData({ result: [], count: 0 })
setLoading(true)
const payload = {
token: localStorage.getItem("PM_TOKEN"),
docusignAccessToken: localStorage.getItem("PM_DOCUSIGNACCESSTOKEN"),
adminId: localStorage.getItem("PM_ADMINID"),
fields: {
id: true,
website: true,
property: true,
checkIn: true,
checkOut: true,
guestName: true,
guestEmail: true,
guestPhone: true,
guestProblem: true,
status: true,
docusignId: true,
docusignStatus: true,
docusignButton: true,
},
filters: {
propertyIdList: propertyIdList,
guestName: guest === "" ? null : guest,
checkInMin:
dateFrom === null ? null : Moment(dateFrom).format("yyyy-MM-DD"),
checkInMax:
dateTo === null ? null : Moment(dateTo).format("yyyy-MM-DD"),
},
}
axios
.post(process.env.REACT_APP_API_BASE_URL + "reservation/list", payload)
.then(response => {
setData({ result: response.data, count: response.data.length })
setLoading(false)
})
.catch(() => {
setLoading(false)
})
}
React.useEffect(() => {
if (reservationId === null && !loading) {
Search()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reservationId])
React.useEffect(() => {
App.addListener("backButton", () => {
setReservationId(null)
})
return () => {
App.removeAllListeners()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ios])
return (
<IonContent>
<IonGrid>
<IonRow>
<IonCol sizeXs="12" sizeMd="8">
<IonLabel>Properties</IonLabel>
{propertyIdList === null && <IonSpinner name="bubbles" />}
{propertyIdList !== null && (
<IonSelect
value={propertyIdList ?? []}
multiple={true}
cancelText="Cancel"
okText="Done"
onIonChange={e => setPropertyIdList(e.detail.value)}
>
{propertyList.map((p, index) => {
return (
<IonSelectOption key={p.id} value={p.id}>
{p.property}
</IonSelectOption>
)
})}
</IonSelect>
)}
</IonCol>
<IonCol sizeXs="12" sizeMd="4">
<IonInput
value={guest}
placeholder="Guest"
onIonChange={e => setGuest(e.detail.value!)}
clearInput
/>
</IonCol>
</IonRow>
<IonRow>
<IonCol sizeXs="12" sizeSm="6" sizeMd="4">
<DatePicker
id="dpReservations1"
label="Date From"
dateValue={dateFrom}
setDateValue={(val: string | null) =>
setDateFrom(
val === null ? null : Moment(val).format("MM/DD/yyyy")
)
}
presentation="date"
side="bottom"
/>
</IonCol>
<IonCol sizeXs="12" sizeSm="6" sizeMd="4">
<DatePicker
id="dpReservations2"
label="Date To"
dateValue={dateTo}
setDateValue={(val: string | null) =>
setDateTo(
val === null ? null : Moment(val).format("MM/DD/yyyy")
)
}
presentation="date"
side="bottom"
/>
</IonCol>
<IonCol sizeXs="12" sizeMd="4">
<IonButton
onClick={() => Search()}
expand="full"
disabled={loading}
>
Search
</IonButton>
</IonCol>
</IonRow>
</IonGrid>
{data.count > 0 && (
<IonItem class="ion-no-padding">
<GridComponent
id="grid"
dataSource={data}
ref={g => (grid = g)}
toolbar={toolbar}
allowPdfExport={true}
allowExcelExport={true}
toolbarClick={toolbarClick}
allowFiltering={false}
allowSorting={true}
loadingIndicator={{ indicatorType: "Shimmer" }}
dataBound={dataBound}
dataStateChange={handleRefresh}
>
<Inject
services={[Toolbar, PdfExport, ExcelExport, Sort, Filter, Resize]}
/>
<ColumnsDirective>
<ColumnDirective
key={0}
field="checkIn"
headerText="Arrival"
width={100}
template={template}
/>
<ColumnDirective
key={1}
field="checkOut"
headerText="Departure"
width={100}
template={template}
/>
<ColumnDirective
key={2}
field="property"
headerText="Property"
width={100}
/>
<ColumnDirective
key={3}
field="guestName"
headerText="Guest"
width={100}
/>
<ColumnDirective
key={4}
field="guestEmail"
headerText="Email"
width={100}
/>
<ColumnDirective
key={5}
field="guestPhone"
headerText="Phone"
width={100}
/>
<ColumnDirective
key={6}
field="website"
headerText="Channel"
width={100}
/>
<ColumnDirective
key={7}
field="status"
headerText="Status"
width={100}
/>
<ColumnDirective
key={8}
field="docusignStatus"
headerText="Docusign Status"
width={150}
textAlign="Center"
template={template}
/>
</ColumnsDirective>
</GridComponent>
</IonItem>
)}
{loading && <IonProgressBar type="indeterminate"></IonProgressBar>}
<IonModal
isOpen={reservationId !== null}
onDidDismiss={() => setReservationId(null)}
>
<Reservation id={reservationId} close={() => setReservationId(null)} />
</IonModal>
<IonToast
isOpen={state.showToast}
onDidDismiss={() => setState({ showToast: false })}
message={state.toast?.explanation}
duration={2000}
color={state.toast?.explanationType}
/>
</IonContent>
)
}
export default Reservations
Please advise, thank you.