Skip to content

[ENHANCEMENT] - getRemoteResources #446

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 !

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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