Skip to content

[ENHANCEMENT] - getRemoteResources #446

@BizBigs

Description

@BizBigs

Hello @aldabil21

The feature getRemoteEvents is workly perfectly and is a great feature to the scheduler ! It is usefull for performance issue and avoiding to reload the Scheduler.

However, is it possible to add getRemoteResources as well ? Would be a great adding this to an already awesome library (if it's possible) 💯

I explain:

  • I have filters to display or not Resources, and each time I filter, to update resources => I have to reload the scheduler through key prop.
    here is the code:
import React, { useRef } from "react";
import { useSchedulerData } from "./useSchedulerData";
import DataTableSheet from "@/components/DataTable/Sheet/DataTableSheet";
import { Roles } from "@/enum/roles.enum";
import "./scheduler-fix.css";
import CustomEvent from "./CustomEvent/CustomEvent";
import { DefaultResource, SchedulerRef } from "@aldabil/react-scheduler/types";
import { Scheduler } from "@aldabil/react-scheduler";
import SchedulerWeekConfig from "./Config/WeekConfig";
import CustomResourceHeader from "./CustomResourceHeader/CustomResourceHeader";
import HeaderBar from "./Header/Header";

const DEFAULT_HOUR_FORMAT = "24";

interface CustomSchedulerProps {
  selectedFilters: any;
}

const CustomScheduler: React.FC<CustomSchedulerProps> = ({
  selectedFilters,
}) => {
  const calendarRef = useRef<SchedulerRef | null>(null);

  const {
    resources,
    resourceFields,
    pointings,
    reloadKey,
    setReloadKey,
    view,
    setView,
    handleCellClick,
    handleEventClick,
    isSheetOpen,
    handleClose,
    selectedRowData,
    isLoading,
    weekDays,
    currentDate,
    setCurrentDate,
    resourceViewMode,
    setResourceViewMode,
    fetchRemote,
  } = useSchedulerData(selectedFilters);

  return (
    <>
      <HeaderBar
        currentDate={currentDate}
        setCurrentDate={setCurrentDate}
        view={view}
        setView={setView}
        resourceViewMode={resourceViewMode}
        setResourceViewMode={setResourceViewMode}
      />
      {resources.length ? (
        <Scheduler
          key={`${reloadKey}-${currentDate.toString()}-${view}-${resourceViewMode}`}
          resourceViewMode={resourceViewMode}
          ref={calendarRef}
          getRemoteEvents={fetchRemote}
          selectedDate={currentDate}
          onSelectedDateChange={setCurrentDate}
          loading={isLoading}
          events={pointings}
          resources={resources}
          hourFormat={DEFAULT_HOUR_FORMAT}
          resourceFields={resourceFields}
          view={view}
          onViewChange={setView}
          week={SchedulerWeekConfig({ weekDays })}
          day={null}
          agenda={false}
          editable={false}
          deletable={false}
          draggable={false}
          disableViewer={true}
          resourceHeaderComponent={(resource: DefaultResource) => (
            <CustomResourceHeader resource={resource} />
          )}
          onCellClick={(
            start: Date,
            end: Date,
            resourceVal?: string | number,
            resourceKey?: string,
          ) => handleCellClick(start, end, resourceVal, resourceKey)}
          onEventClick={handleEventClick}
          eventRenderer={(event) => (
            <CustomEvent event={event} onClick={handleEventClick} />
          )}
        />
      ) : null}

      <DataTableSheet
        open={isSheetOpen}
        onOpenChange={handleClose}
        selectedRowData={selectedRowData}
        formKey="pointing"
        reloadData={() => setReloadKey(reloadKey + 1)}
        minimumAllowedRoleToEdit={Roles.ADMIN}
      />
    </>
  );
};

export default CustomScheduler;

My hooks:

import React, { useState, useEffect } from "react";
import { ProcessedEvent, WeekDays } from "@aldabil/react-scheduler/types";
import { getEmployees } from "@/utils/employee.utils";
import { getPointings } from "@/utils/pointing.utils";
import { EmployeeForScheduler } from "@/types/entities/Employee";
import { Pointing } from "@/types/entities/Ponting";
import { View } from "@aldabil/react-scheduler/components/nav/Navigation";

const DEFAULT_WEEK_DAYS: WeekDays[] = [0, 1, 2, 3, 4];

// Custom hook to fetch scheduler data
export const useSchedulerData = (selectedFilters?: Record<string, any[]>) => {
  const [resources, setResources] = useState<EmployeeForScheduler[]>([]);
  const [pointings, setPointings] = useState<ProcessedEvent[]>([]);
  const [reloadKey, setReloadKey] = useState(0);
  const [isSheetOpen, setIsSheetOpen] = useState(false);
  const [selectedRowData, setSelectedRowData] = useState({});
  const [view, setView] = useState<View>("week");
  const [resourceViewMode, setResourceViewMode] = useState<
    "default" | "vertical" | "tabs" | undefined
  >("default");
  const [isLoading, setIsLoading] = useState(true);
  const [weekDays, setWeekDays] = useState<WeekDays[]>(DEFAULT_WEEK_DAYS);
  const [currentDate, setCurrentDate] = useState(new Date());

  const resourceFields = {
    idField: "employee_id",
    textField: "firstname",
    subTextField: "lastname",
    avatarField: "firstname",
  };

  const handleCellClick = (
    start: Date,
    end: Date,
    resourceVal?: string | number,
    resourceKey?: string,
  ) => {
    setSelectedRowData({
      startDate: start,
      endDate: end,
      employeeId: resourceKey,
      schedulerIdResource: resourceVal,
      creation: true,
    });
    setIsSheetOpen(true);
  };
  const handleEventClick = (event: any) => {
    setIsSheetOpen(true);
    setSelectedRowData(event);
  };
  const handleClose = () => {
    setIsSheetOpen(false);
    setSelectedRowData({});
  };

  const filterEmployees = (employees: EmployeeForScheduler[]) => {
    if (!selectedFilters) return employees;

    return employees.filter((employee) => {
      // Agency filter
      if (
        selectedFilters.agencyId?.length &&
        !selectedFilters.agencyId.includes(employee.agencyId)
      ) {
        return false;
      }

      // Entity filter (comes from employee.agency.entityId)
      if (
        selectedFilters.entityId?.length &&
        !selectedFilters.entityId.includes(employee.agency?.entityId)
      ) {
        return false;
      }

      // ContractType filter (assuming you later add `contractType` on employees)
      // if (
      //   selectedFilters.contractType?.length &&
      //   !selectedFilters.contractType.includes(employee.contractType)
      // ) {
      //   return false;
      // }

      return true;
    });
  };

  const fetchEmployees = async () => {
    setIsLoading(true);
    try {
      const employeeResponse = await getEmployees();

      if (Array.isArray(employeeResponse) && employeeResponse.length > 0) {
        const employeesWithResourceId: EmployeeForScheduler[] =
          employeeResponse.map((employee) => ({
            ...employee,
            employee_id: employee.id,
          }));

        const filtered = filterEmployees(employeesWithResourceId);
        setResources(filtered);
      } else {
        console.error("Employees API response is not an array or is empty");
      }
    } catch (error) {
      console.error("Error fetching employees or pointings:", error);
    } finally {
      setReloadKey((prev) => prev + 1);
      setIsLoading(false);
    }
  };

  const fetchRemote = async (): Promise<ProcessedEvent[] | undefined> => {
    try {
      const pointingResponse = await getPointings();

      if (Array.isArray(pointingResponse)) {
        const mappedEvents: ProcessedEvent[] = pointingResponse
          .filter(
            (pointing: Pointing) => pointing.startDate && pointing.endDate,
          )
          .map((pointing: Pointing) => ({
            event_id: pointing.id,
            title: pointing.clientName || `Pointing ${pointing.id}`,
            start: new Date(pointing.startDate!),
            end: new Date(pointing.endDate!),
            employee_id: pointing.employeeId,
            id: pointing.id,
            notes: pointing.notes,
            breakTime: pointing.breakTime,
            hasTransport: pointing.hasTransport,
            hasFood: pointing.hasFood,
            employeeId: pointing.employeeId,
            employee: pointing.employee,
            startDate: pointing.startDate,
            endDate: pointing.endDate,
            clientName: pointing.clientName,
          }));
        return mappedEvents;
      } else {
        console.error("Pointings API response is not an array");
      }
    } catch (error) {
      console.error("Error fetching pointings:", error);
    }
  };

  useEffect(() => {
    fetchEmployees();
  }, [JSON.stringify(selectedFilters)]);

  return {
    resources,
    resourceFields,
    pointings,
    fetchEmployees,
    reloadKey,
    setReloadKey,
    isSheetOpen,
    setIsSheetOpen,
    handleCellClick,
    handleEventClick,
    handleClose,
    selectedRowData,
    setSelectedRowData,
    view,
    setView,
    isLoading,
    setIsLoading,
    weekDays,
    setWeekDays,
    currentDate,
    setCurrentDate,
    resourceViewMode,
    setResourceViewMode,
    fetchRemote,
  };
};

Here is the line to reload otherwise, my resources can't be updated:
setReloadKey((prev) => prev + 1); in fetchEmployees function.

The idea is to have the getRemoteResources i can callback when my filters are updated and avoid reloading the entire component :)

Thanks and have a nice day !

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions