Syncfusion Calendar support on IOS device?

Currently, I am able to view the calendar on Android device but when switch to IOS device then it shows empty. So is there any code I can add to support the calendar view on IOS device?

IOS device view:


Android device view:



5 Replies

BS Balasubramanian Sattanathan Syncfusion Team January 11, 2022 04:44 PM UTC

Hi Samantha,

Thanks for contacting us.

We suspect that you might have console error when rendering the scheduler in the IOS device. So kindly share the screenshot of the console window and your latest scheduler code snippet to validate the issue and provide a prompt solution ASAP.

Here is the screenshots of the Scheduler that renders as expected

Android device:


IOS device:


Sample: https://stackblitz.com/edit/react-scheduler-custom-wrok-days-resource?file=index.js

Kindly try to reproduce your problem in the above sample if possible.

Regards,
Balasubramanian S



SA Samantha January 12, 2022 04:29 AM UTC

Hi Balasubramanian S,


Here is the source code

import React, { useCallback, useEffect, useState } from "react";
import "@fullcalendar/daygrid/main.css";
import "@fullcalendar/timegrid/main.css";
import {
  ScheduleComponent,
  Day,
  WorkWeek,
  Week,
  Month,
  TimelineMonth,
  Year,
  Inject,
  ViewsDirective,
  ViewDirective,
  DragAndDrop,
  ResourcesDirective,
  ResourceDirective,
  Resize,
} from "@syncfusion/ej2-react-schedule";
import { CheckBoxComponent } from "@syncfusion/ej2-react-buttons";
import {
  getAppointmentListDoctor,
  getApptDetail,
  getApptLocation,
  getApptResource,
  getApptStatus,
  getApptType,
  getSchedulerOrganizerList,
  reschedule_appt,
  updateDoctorAppt,
} from "./appointmentCrud";
import {
  formatDateStringAsDate2,
  formatDateTimeStringAsDateTime,
  getStartEndDateDifference,
  getMonthDiff,
  SAASDateFormat,
  getYearDiff,
  getWeekDiff,
  getDayDiff,
} from "../../../../_metronic/_helpers/DateHelper";
import { Modal, Button, Card, Accordion} from "react-bootstrap";
import moment from "moment";
import { AddAppointmentForm } from "./add-appointment-dialog/AddAppointmentForm";
import { RemoveAppointmentForm } from "./add-appointment-dialog/RemoveAppointmentForm";
import { FormattedMessage } from "react-intl";
import { useLang } from "../../../../_metronic/i18n";
import { DateRangePickerComponent } from "@syncfusion/ej2-react-calendars";

export function Scheduler(props) {
  const [list, setLists] = useState([]);
  const [doctorList, setDoctorList] = useState([]);
  const [doctorListScheduler, setDoctorListScheduler] = useState([{}]);
  const [doctorResource, setDoctorResource] = useState([{}]);
  const [selectedDoctorList, setSelectedDoctorList] = useState([]);
  const [startDate, setStartDate] = useState(
    formatDateTimeStringAsDateTime(new Date())
  );
  const [endDate, setEndDate] = useState(
    formatDateTimeStringAsDateTime(new Date())
  );
  const [appt, setAppt] = useState({});
  const [startTime, setStartTime] = useState("");
  const [openPopup, setOpenPopup] = useState(false);
  const [openUpdatePopup, setOpenUpdatePopup] = useState(false);
  const [openDeletePopup, setOpenDeletePopup] = useState(false);
  const [appTypeList, setAppType] = useState([]);
  const lang = useLang();
  const [apptLocationList, setApptLocationList] = useState([]);
  const [apptStatusList, setApptStatusList] = useState([]);
  const [locationList, setLocationList] = useState([]);
  const [statusList, setStatusList] = useState([]);
  const [typeList, setTypeList] = useState([]);
  const [minDate] = useState(
    formatDateTimeStringAsDateTime(new Date("01/01/1990"))
  );
  const [maxDate] = useState(
    formatDateTimeStringAsDateTime(new Date("01/01/3000"))
  );
  const [dateInterval, setDateInterval] = useState(1);
  const [schedularView, setSchedularView] = useState("Day");
  const [organizerList, setOrganizerList] = useState([]);

  const getApptList = useCallback(() => {
    getAppointmentListDoctor(
      startDate,
      endDate,
      JSON.stringify(selectedDoctorList),
      JSON.stringify(statusList),
      JSON.stringify(locationList),
      JSON.stringify(typeList)
    ).then((response) => {
      setLists(
        response.data.data.map((list) => {
          return {
            Id: list.id,
            Subject: list.name,
            Doctor: list.doctor.includes("|")
              ? list.doctor.split("|")
              : list.doctor,
            Type: list.type,
            Location: list.location,
            Status: list.apt_status,
            Duration: "00:" + list.duration,
            StartTime: formatDateTimeStringAsDateTime(list.apt_date),
            EndTime: formatDateTimeStringAsDateTime(list.endTime),
            IsBlock: false,
          };
        })
      );
    });
  }, [
    selectedDoctorList,
    startDate,
    endDate,
    statusList,
    locationList,
    typeList,
  ]);

  const getAppType = useCallback(() => {
    getApptType().then((response) => {
      setAppType(response.data);
    });
  }, []);

  const getDoctorList = useCallback(() => {
    getApptResource().then((response) => {
      setDoctorList(response.data);
      setDoctorListScheduler(response.data);
    });
  }, []);

  const getScheduleList = useCallback(() => {
    getSchedulerOrganizerList("").then((response) => {
      setDoctorResource(response.data);
    });
  }, []);

  const getApptLocations = useCallback(() => {
    getApptLocation().then((response) => {
      setApptLocationList(response.data);
    });
  }, []);

  const getAppointmentStatus = useCallback(() => {
    getApptStatus().then((response) => {
      setApptStatusList(response.data);
    });
  }, []);

  useEffect(() => {
    getApptList();
    getScheduleList();
    getAppType();
  }, [getApptList, getScheduleList, getAppType, startDate, endDate]);

  useEffect(() => {
    getDoctorList();
    getApptLocations();
    getAppointmentStatus();
  }, [getDoctorList, getApptLocations, getAppointmentStatus]);

  useEffect(() => {
    getOrganizer();
  }, [list]);

  const closePopup = () => {
    setOpenPopup(false);
  };

  const closeUpdatePopup = () => {
    setOpenUpdatePopup(false);
  };

  const closeDeletePopup = () => {
    setOpenDeletePopup(false);
  };

  const onEventRendered = (e) => {
    if (e.type === "event") {
      appTypeList.forEach((element) => {
        if (e.data.Type === element.type) {
          e.element.style.backgroundColor = element.slotViewColor;
          e.element.style.color = "black";
        }
      });
    } else {
      e.element.lastChild.className = "";
    }
  };

  const onCellRender = (e) => {
    if (e.name === "renderCells") {
      let date = new Date(e.date);
      doctorResource[e.groupIndex].doctorOrganizerList.forEach(
        (element, index) => {
          if (
            date > new Date(element.fromDate[index]) &&
            date < new Date(element.toDate[index])
          ) {
            e.element.style.backgroundColor = "grey";
            e.element.blocked = true;
          }
        }
      );
    }
  };

  const rescheduleDrag = (e) => {
    var date1 = new Date();
    var date2 = new Date(e.data.StartTime);
    var date3 = new Date(e.data.EndTime);
    var startTime = date2.getTime();
    var endTime = date3.getTime();
    var duration_hours = Math.floor(
      ((endTime - startTime) / (1000 * 60 * 60)) % 24
    );
    var duration_minutes = ((endTime - startTime) / (1000 * 60)) % 60;
    var estimatedDuration =
      duration_hours.toString() + ":" + duration_minutes.toString() + ":00";

    if (date2.setHours(0, 0, 0, 0) >= date1.setHours(0, 0, 0, 0)) {
      reschedule_appt(
        e.data.Id,
        moment(e.data.StartTime).add(8, "hours"),
        estimatedDuration
      );
      getApptDetail(e.data.Id).then((response) => {
        updateDoctorAppt(e.data.Id, e.data.Doctor);
      });
    } else {
      e.cancel = true;
    }
  };

  const popUpHandler = (e) => {
    if (e.type === "Editor") {
      e.cancel = true;
      if (e.data.Id !== undefined) {
        getApptDetail(e.data.Id).then((response) => {
          setAppt(response.data);
        });
        setOpenUpdatePopup(true);
      }
    }
    if (e.type === "DeleteAlert") {
      e.cancel = true;
      if (e.data.Id !== undefined) {
        getApptDetail(e.data.Id).then((response) => {
          setAppt(response.data);
        });
        setOpenDeletePopup(true);
      }
    }
    if (e.type === "QuickInfo" && e.target.classList[0] === "e-work-cells") {
      e.cancel = true;
      if (e.data.StartTime !== undefined && e.data.StartTime >= new Date()) {
        setStartTime(e.data.StartTime);
        setOpenPopup(true);
      }
    }
  };

  const checkLocal = () => {
    if (lang === "ms-MY") return "ms";
    else if (lang === "zh-CN") return "zh";
    else return "en";
  };

  const handleResource = (checked, resource) => {
    if (checked === true) {
      setSelectedDoctorList([...selectedDoctorList, resource]);
    } else {
      const newList = selectedDoctorList.filter((item) => item !== resource);
      setSelectedDoctorList(newList);
    }
  };

  const handleLocation = (checked, description) => {
    if (checked === true) {
      setLocationList([...locationList, description]);
    } else {
      const newList = locationList.filter((item) => item !== description);
      setLocationList(newList);
    }
  };

  const handleStatus = (checked, status) => {
    if (checked === true) {
      setStatusList([...statusList, status]);
    } else {
      const newList = statusList.filter((item) => item !== status);
      setStatusList(newList);
    }
  };

  const handleType = (checked, type) => {
    if (checked === true) {
      setTypeList([...typeList, type]);
    } else {
      const newList = typeList.filter((item) => item !== type);
      setTypeList(newList);
    }
  };

  const handleDateRange = (props) => {
    var startDate = props.nativeEvent.text[0];
    var endDate = props.nativeEvent.text[1];
    var formattedStartDate = startDate.toDateString();
    var formattedEndDate = endDate.toDateString();
    setStartDate(formattedStartDate);
    setEndDate(formattedEndDate);
    var difference = getStartEndDateDifference(startDate, endDate);
    setDateInterval(difference + 1);
    setSchedularView("Week");
    setSchedularView("Day");
  };

  const getOrganizer = useCallback(() => {
    getSchedulerOrganizerList().then((response) => {
      var data = response.data;
      setOrganizerList(
        data.map((schedulerBlock) => {
          var startDate = new Date(
            formatDateStringAsDate2(schedulerBlock.startDate)
          );
          var endDate = new Date(
            formatDateStringAsDate2(schedulerBlock.stopDate)
          );
          var count = null;
          var repeatType = schedulerBlock.repeatType.toUpperCase();
          if ((repeatType === "DAILY")) {
            var Difference_In_Days = getDayDiff(startDate, endDate) + 1;
            count = ";COUNT=" + Difference_In_Days;
          } else if ((repeatType === "WEEKLY")) {
            var Difference_In_Weeks = getWeekDiff (startDate, endDate) + 1;
            count = ";COUNT=" + Difference_In_Weeks;
          } else if ((repeatType === "MONTHLY")) {
            var Difference_In_Months = getMonthDiff(startDate, endDate) + 1;
            count = ";COUNT=" + Difference_In_Months;
          } else if ((repeatType === "YEARLY")) {
            var Difference_In_Years = getYearDiff(startDate, endDate) + 1;
            count = ";COUNT=" + Difference_In_Years;
          } else if ((repeatType === "ONE TIME")) {
            repeatType = "DAILY"
            var Difference_In_Days = getDayDiff(startDate, endDate) + 1;
            count = ";COUNT=" + Difference_In_Days;
          }
          var recurrenceRule = "FREQ=" + repeatType + count;
          return {
            Id: schedulerBlock.id,
            Subject: schedulerBlock.reason,
            Doctor: schedulerBlock.doctor,
            StartTime: formatDateTimeStringAsDateTime(schedulerBlock.startDate),
            EndTime: formatDateTimeStringAsDateTime(schedulerBlock.stopTime),
            RecurrenceRule: recurrenceRule,
            IsBlock: true,
          };
        })
      );
    });
  }, []);

  return (
    <>
      <Modal show={openPopup} onHide={closePopup} size="lg" scrollable={true}>
        <AddAppointmentForm
          startTime={startTime}
          close={closePopup}
          reloadList={getApptList}
        />
      </Modal>

      <Modal
        show={openUpdatePopup}
        onHide={closeUpdatePopup}
        size="lg"
        scrollable={true}
      >
        <AddAppointmentForm
          apptData={appt}
          close={closeUpdatePopup}
          reloadList={getApptList}
        />
      </Modal>

      <Modal show={openDeletePopup} onHide={closeDeletePopup} scrollable={true}>
        <RemoveAppointmentForm
          apptData={appt}
          close={closeDeletePopup}
          reloadList={getApptList}
        />
      </Modal>

      <Accordion>
        <Card>
          <Card.Header>
            <Accordion.Toggle as={Button} variant="link" eventKey="0" className="filter-font">
              <FormattedMessage id="GENERAL.FILTER_HERE" />
            </Accordion.Toggle>
           
          </Card.Header>
          <Accordion.Collapse eventKey="0">
            <Card.Body>
              <div className="row w-100">

                {/* Date Filter */}
                <div className="col-lg-2 justify-content-center">
                  <label style={{ display: "contents" }} className="ml-5 filter-font-internal">
                    <FormattedMessage id="GENERAL.DATE_FILTER" />
                  </label>
                  <DateRangePickerComponent
                    min={minDate}
                    max={maxDate}
                    format={SAASDateFormat}
                    onChange={(e) => handleDateRange(e)}
                  ></DateRangePickerComponent>
                </div>

                {/* Doctor Filter */}
                <div className="col-lg-2 justify-content-center">
                  <label style={{ display: "contents" }} className="ml-5 filter-font-internal">
                    <FormattedMessage id="GENERAL.DOCTOR_FILTER" />
                  </label>
                  <table>
                    <tbody>
                      {doctorList.map((data) => (
                        <tr
                          key={data.id}
                          style={{ width: "100%" }}
                        >
                          <td style={{ width: "63.5%" }}>{data.resource}</td>
                          <td
                            style={{
                              backgroundColor: data.blockcolor,
                              width: "20%",
                            }}
                            className="pr-3"
                          />
                          <td style={{ width: "20%" }} className="pl-5">
                            <CheckBoxComponent
                              name={data.resource}
                              onChange={(e) =>
                                handleResource(e.target.checked, data.resource)
                              }
                              value="resource"
                            />
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>

                {/* Appointment Location */}
                <div className="col-lg-2 justify-content-center">
                  <label style={{ display: "contents" }} className="ml-5 filter-font-internal">
                    <FormattedMessage id="GENERAL.APPOINTMENT_LOCATION" />
                  </label>
                  <table>
                    <tbody>
                      {apptLocationList.map((data) => (
                        <tr key={data.id} style={{ width: "100%" }}>
                          <td style={{ width: "63.5%" }}>{data.description}</td>
                          <td
                            style={{
                              backgroundColor: data.slotViewColor,
                              width: "20%",
                            }}
                            className="pr-3"
                          />
                          <td style={{ width: "20%" }} className="pl-5">
                            <CheckBoxComponent
                              name={data.description}
                              onChange={(e) =>
                                handleLocation(e.target.checked, data.description)
                              }
                              value="description"
                            />
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>

                {/* Appointment Status */}
                <div className="col-lg-2 justify-content-center">
                  <label style={{ display: "contents" }} className="ml-5 filter-font-internal">
                    <FormattedMessage id="GENERAL.APPOINTMENT_STATUS" />
                  </label>
                  <table>
                    <tbody>
                      {apptStatusList.map((data) => (
                        <tr key={data.id} style={{ width: "100%" }}>
                          <td style={{ width: "63.5%" }}>{data.statusDesc}</td>
                          <td
                            style={{
                              backgroundColor: data.slotViewColor,
                              width: "20%",
                            }}
                            className="pr-3"
                          />
                          <td style={{ width: "20%" }} className="pl-5">
                            <CheckBoxComponent
                              name={data.statusDesc}
                              onChange={(e) =>
                                handleStatus(e.target.checked, data.statusDesc)
                              }
                              value="statusDesc"
                            />
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>

                {/* Appointment Type */}
                <div className="col-lg-2 justify-content-center">
                  <label style={{ display: "contents" }} className="ml-5 filter-font-internal">
                    <FormattedMessage id="GENERAL.APPOINTMENT_TYPE" />
                  </label>
                  <table>
                    <tbody>
                      {appTypeList.map((data) => (
                        <tr key={data.id} style={{ width: "100%" }}>
                          <td style={{ width: "63.5%" }}>{data.type}</td>
                          <td
                            style={{
                              backgroundColor: data.slotViewColor,
                              width: "20%",
                            }}
                            className="pr-3"
                          />
                          <td style={{ width: "20%" }} className="pl-5">
                            <CheckBoxComponent
                              name={data.type}
                              onChange={(e) =>
                                handleType(e.target.checked, data.type)
                              }
                              value="type"
                            />
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
     
              </div>
            </Card.Body>
            </Accordion.Collapse>
          </Card>
        </Accordion>
           
       
        <div>
          <div className="card card-custom card-stretch">
            <div className="card-body">
              <div>
                <div className="demo-app">
                  <div className="demo-app-top"></div>
                  <div className="demo-app-calendar">
                    <ScheduleComponent
                      cssClass='virtual-scrolling' width='100%' height='650px'
                      resizeStop={rescheduleDrag.bind()}
                      dragStop={rescheduleDrag.bind()}
                      locale={checkLocal()}
                      currentView={schedularView}
                      eventSettings={{ dataSource: list.concat(organizerList) }}
                      renderCell={onCellRender.bind()}
                      eventRendered={onEventRendered.bind()}
                      rowAutoHeight={true}
                      timeScale={{ enable: true, interval: 60, slotCount: 6 }}
                      popupOpen={popUpHandler.bind()}
                      group={{ byDate: true, resources: ["Doctors"] }}
                      selectedDate={startDate}
                    >
                      <ResourcesDirective>
                        <ResourceDirective
                          field="Doctor"
                          name="Doctors"
                          dataSource={
                            selectedDoctorList.length === 0
                              ? doctorListScheduler
                              : doctorListScheduler.filter((data) => {
                                  return selectedDoctorList.includes(
                                    data.resource
                                  );
                                })
                          }
                          textField={"resource"}
                          idField={"resource"}
                          allowMultiple={true}
                        ></ResourceDirective>
                      </ResourcesDirective>

                      <Inject
                        services={[Day, Week, WorkWeek, Month, TimelineMonth, Year, Resize, DragAndDrop]}
                      />
                      <ViewsDirective>
                        <ViewDirective
                          option="Day"
                          endHour="24:00"
                          startHour="08:00"
                          interval={dateInterval}
                        />
                        <ViewDirective
                          option="Week"
                          startHour="08:00"
                          endHour="24:00"
                        />
                        <ViewDirective
                          option="WorkWeek"
                          startHour="08:00"
                          endHour="24:00"
                        />
                        <ViewDirective option="Month" />
                        <ViewDirective option="TimelineMonth" />
                        <ViewDirective option="Year" />
                      </ViewsDirective>
                    </ScheduleComponent>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
    </>
  );
}



SK Satheesh Kumar Balasubramanian Syncfusion Team January 13, 2022 02:28 PM UTC

Hi Samantha, 
  
We have modified the sample based on your shared details to reproduce the reported issue. But, schedule is rendered properly at our end. 
  
  
Could you please share the below details to reproduce the issue? This will help to validate the issue and provide prompt solution at earliest.  
  • How you are switching the view from Android to IOS device?
  • Share issue depicting video
  
Regards, 
Satheesh Kumar B 



SA Samantha replied to Satheesh Kumar Balasubramanian January 14, 2022 02:56 AM UTC

Hi Satheesh Kumar B


How you are switching the view from Android to IOS device?

- We have a live server which we can access around with any devices, it works fine in Android device but not for IOS device in Safari.


Share issue depicting video

- I can't share the video as the size limiting.

- From my IOS device, access to safari to our live server, the calendar view is blank







SK Satheesh Kumar Balasubramanian Syncfusion Team January 18, 2022 01:26 PM UTC

Hi Samantha,

We have checked the sample in real devices and chrome emulator device. But, schedule is rendered properly at our end. Can you please check the sample in real devices (or) chrome simulator device and check whether the issue still occurs?

Regards,
Satheesh Kumar B

Loader.
Up arrow icon