Our pure JavaScript Scheduler component


Post by khalid irfani »

Hi Support,

I want to export my grid with the following ways

1- i want you to make Excel Export > but using all the columns that are visible to the grid. Not the one that you have specified
2- If the user hide a column, or appear another make it in dynamic way

here is my code snipper i make it but it show me the error msg always this i also installed the npm but not working perfect.

npm install @bryntum/schedulerpro write-xlsx-file

import * as generalMethods from "../../configs/generalMethods";
import { WS_TYPES } from "../../constants/webSocketEnums";
import { DateHelper, Model, StateProvider, XlsProviderBase } from "@bryntum/schedulerpro";
import PopupComponent from "../components/PopupComponent";
import { destinationCityStore, originCityStore } from "../MockUrl";
import { gridTransferToolTip } from "../components/bryntumConfig";
import writeXlsxFile from "write-excel-file";
import toast from "react-hot-toast";

// Define the data model
class MyModel extends Model {
  static get fields() {
    return [
      { name: "id" }, // Ensure 'id' is defined
      // {name: 'originName', dataSource: 'origin.name'},
      { name: "origin", type: 'object' },
      // {name: "originName", dataSource: 'origin.name'},
      { name: "destination", type: 'object' },
      { name: "paymentMethod", type: 'object', },
      { name: "vehicleCategory", type: 'object' },
      { name: "client", type: 'object' },
      { name: "assignee", type: 'object' },
      { name: 'transferPrice', type: 'number', defaultValue: 0 },
      { name: 'startDate', type: 'date', format: 'DD/MM/YYYY HH:mm', defaultValue: new Date(), required: true },
      { name: 'endServiceTime', type: 'date', format: 'DD/MM/YYYY HH:mm', defaultValue: new Date(), required: false },
      // {name: 'intermediateStops', type: 'array'}
    ];
  }
  get unattested() {
    return operatorSummaries[WS_TYPES.CLIENTS].find(record => record.id === this.client)?.name;
  }
}

// Fetch operator summaries for dropdowns
const operatorSummaries = generalMethods.getOperatorSummaries();

// Autocomplete combo configuration
const autocompleteComboConfig = {
  type: 'combo',
  // Creates problem when hitting ESC
  //clearable: true,
  editable: true,
  autoExpand: true,
  minChars: 1,
  keyStrokeFilterDelay: 800,
  required: true,
  displayField: "name",
  valueField: null,
};

// Generic combo configuration
const comboConfig = {
  type: 'combo',
  // Creates problem when hitting ESC
  //clearable: true,
  hide: false,
  autoExpand: true,
  aligned: 'center',
  // primaryFilter: {
  //   filterBy(record) {
  //     if (this.value == null) {
  //       return true;
  //     }
  //     return record.name.toLowerCase().includes(this.value.toLowerCase());
  //   },
  //   style: {
  //     display: 'flex',
  //     flexDirection: 'column',
  //     gap: '2px',
  //   },
  // },
  displayField: 'name',
  valueField: null,
};

const stateProvider = StateProvider.setup({
  storage: 'local',
  prefix: 'myApp-v1:'
});

const data = [
  ...(operatorSummaries[WS_TYPES.DRIVERS] || []),
  ...(operatorSummaries[WS_TYPES.TRANSFER_OPERATORS] || [])
];

// Define the type mapping for Excel export
const typeMap = {
  string: String,
  number: Number,
  date: Date,
  boolean: Boolean,
  bool: Boolean
};

// Custom Excel export provider class
class MyXlsProvider extends XlsProviderBase {
  static async write({ filename, columns, rows, exporterConfig }) {
    // Format the rows for Excel
    rows.forEach(row => {
      row.forEach(cell => {
        // Convert cell type as library expects it
        cell.type = typeMap[cell.type] || String;

    // Convert non-string values to strings for string cells
    if (cell.type === String && typeof cell.value !== 'string') {
      cell.value = `${cell.value}`;
    }

    // Format date cells
    if (cell.type === Date && cell.value) {
      cell.format = 'yyyy-mm-dd hh:mm';
    }
  });
});

// Style the column headers
columns.forEach(cell => {
  // Headers are always text
  delete cell.type;

  // Add styling to headers
  cell.fontWeight = 'bold';
  cell.align = 'center';
  cell.backgroundColor = '#e6e6e6';
});

// Export the file using write-excel-file library
try {
  await writeXlsxFile(
    [columns, ...rows],
    {
      dateFormat: 'yyyy-mm-dd hh:mm',
      fileName: filename || 'export.xlsx',
      columns: columns.map(cell => ({
        width: Math.round((cell.width || 100) / 8)
      }))
    }
  );
  toast.success('Export completed successfully');
} catch (error) {
  console.error('Export failed:', error);
  toast.error('Export failed: ' + error.message);
}
  }
}

// Main grid configuration
const gridConfig = {
  // stateId: 'mainGrid',
  // stateProvider: stateProvider,
  store: {
    data: [],
    modelClass: MyModel,
    autoCommit: false,
  },
  sortFeature: "startDate",
  keyMap: {
    ' ': {
      handler: async (props) => {
        if (props.widget.selectedRecord && !props.widget.features.cellEdit.isEditing) {
          await PopupComponent(props.widget, props.widget.selectedRecord).show();
        }
      },
    },
    'Escape': {
      handler: (props) => {
        props.widget.deselectAll()
      },
    },
  },
  stripeFeature: true,
  showDirty: {
    duringEdit: true,
    // fields     : true,
    // newRecords : false
  },
  // When i was grouping by Vehicle for example > it show number without the following code.
  groupFeature: {
    // headerHeight: 40,
    renderer: (props) => {
      if (props.record.isGroupHeader && props.isFirstColumn) {
        if (props.groupColumn.data.field === "assignee") {
          if (props.groupRowFor === '!!novalue!!') {
            return "Unassigned";
          } else {
            return props.groupColumn.data.editor.store.data.find(record => record.id === props.groupRowFor.id).name
          }
        } else if (props.groupColumn.data.field === "origin" || props.groupColumn.data.field === "destination") {
          // return props.source.owner.store.data
          //   .find(record => record[props.groupColumn.data.field].data.placeId === props.groupRowFor.data.placeId)
          //   [props.groupColumn.data.field].data.name
        } else if (props.groupColumn.data.field === "status") {
          return props.groupRowFor.name;
        } else {
          return props.groupColumn.data.editor.store.data.find(record => record.id === props.groupRowFor.id).name
        }
      }
    }
  },
  listeners: {
    cellDblClick(doubleClickProps) {
      if (!doubleClickProps.source.readOnly && !doubleClickProps.source.features.cellEdit) {
        PopupComponent(doubleClickProps.source, doubleClickProps.record).show();
      }
    },
    cellMenuBeforeShow({ record }) {
      // Blocks right click on Group Headers (Request, Transfer etc)
      if (record.isSpecialRow) {
        return false;
      }
    },
    beforeCellEditStart(props) {
      if (props.editorContext.editor.name === 'assignee' && props.editorContext.grid.selectedRecord.isPhantom) {
        return false;
      }
    }
  },
  // rowCopyPasteFeature: false,
  // cellCopyPasteFeature: false,
  cellMenuFeature: {
    processItems({ items, record }) {
      items.showSessionItem = {
        weight: 1,
        text: 'Share',
        icon: 'b-fa-share-alt',
        async onItem({ source: grid, record: record, index: index, event: event }) {
          if (grid.selectedRows.length === 0) {
            await generalMethods.schedulerShareTransfer([record.data])
          } else {
            await generalMethods.schedulerShareTransfer(grid.selectedRows);
          }
        },
      };
      items.outsourceSpecificOperator = {
        weight: 2,
        text: 'Outsource To Operator',
        cls: 'b-separator',
        icon: 'b-fa-share-square',
        async onItem({ source: grid }) {
          const idList = grid.selectedRecords.map(obj => obj.id);
          grid.store.trigger('mmp_grid_outsource_specific_operator', { transferList: idList });
        },
      };
      items.requestInOrder = {
        weight: 3,
        text: 'Search In Order',
        icon: 'b-fa-search',
        async onItem({ source: grid }) {
          const idList = grid.selectedRecords.map(obj => obj.id);
          grid.store.trigger('mmp_grid_outsource_in_order', { transferList: idList });
        },
      };
      items.cancelRequest = {
        weight: 4,
        text: 'Cancel Request',
        icon: 'b-fa-times',
        async onItem({ source: grid }) {
          const idList = grid.selectedRecords.map(obj => obj.id);
          grid.store.trigger('mmp_grid_cancel_requests', { transferList: idList });
        },
      };
      items.edit = {
        weight: 5,
        cls: 'b-separator',
        text: 'Edit',
        icon: 'b-fa-edit',
        async onItem({ source: grid, record: record, index: index, event: event }) {
          await PopupComponent(grid, record).show();
        },
      };
    },
    items: {
      cut: null,
      filterFeature: true,
      cellEditFeature: true,
      copy: true,
      paste: true,
      predecessorsTab: false,
      notesTab: false,
      advancedTab: false,
      removeRow: null,
    },
  },
  cellTooltipFeature: {
    // Time that mouse needs to be over cell before tooltip is shown
    hoverDelay: 1000,
    // Time after mouse out to hide the tooltip, 0 = instantly
    hideDelay: 0,
    tooltipRenderer({ record, tip }) {
      return gridTransferToolTip(record);
    }
  },
  columns: [
    {
      id: "id-field", text: "Id", field: "id", align: "center", groupable: false,
    },
    {
      id: "pickUpTime-field",
      groupable: false,
      text: "Date & Time",
      field: "startDate",
      align: "center",
      style: 'margin-right: .5em',
      editor: {
        // type: 'datetimefield',
        editable: true,
        type: 'date',
        format: 'DD/MM/YY HH:mm',
        keepTime: 'entered',
        required: true,
      },
      minWidth: 175,
      autoHeight: true,
      autoWidth: true,
      renderer(props) {
        if (props.record.isDisposal) {
          return `${DateHelper.format(props.value, 'DD/MM/YY HH:mm')} Until ${DateHelper.format(props.record.endServiceTime, 'DD/MM/YY HH:mm')}`;
        } else {
          return DateHelper.format(props.value, 'DD/MM/YY HH:mm');
        }
      }
    },
    {
      id: "endServiceTime-field",
      groupable: false,
      text: "End Service",
      field: "endServiceTime",
      align: "center",
      style: 'margin-right: .5em',
      editor: {
        clearable: true,
        editable: true,
        type: 'date',
        format: 'DD/MM/YY HH:mm',
        keepTime: 'entered',
      },
      // autoWidth: true,
      minWidth: 175,
      autoWidth: true,
      hidden: true,
    },
    {
      id: "originDetails-field",
      groupable: false,
      text: "Pick-Up Details",
      field: "originDetails",
      align: "center",
      autoWidth: true,
      hidden: true,
    },
    {
      id: "origin-field",
      text: "Pick-Up",
      align: "center",
      field: "origin",
      groupable: false,
      maxWidth: 350,
      renderer(data) {
        if (data.value) {
          return data.value.name ? data.value.name : data.value;
        }
        return '';
      },
      editor: {
        ...autocompleteComboConfig,
        store: originCityStore,
      },
      autoHeight: true,
      autoWidth: true,
    },
    {
      id: 'destinationDetails-field',
      text: "Drop-Off Details",
      field: "destinationDetails",
      groupable: false,
      // align: "center",
      autoWidth: true,
      hidden: true,
    },
    {
      id: 'destination-field',
      text: "Drop-Off",
      field: "destination",
      groupable: false,
      align: "center",
      autoHeight: true,
      autoWidth: true,
      renderer(data) {
        if (data.value) {
          return data.value.name ? data.value.name : data.value;
        }
        return '';
      },
      editor: {
        ...autocompleteComboConfig,
        store: destinationCityStore,
      },
    },
    {
      id: "passengerName-field",
      text: "Passenger Name",
      field: "passengerName",
      groupable: false,
      align: "center",
      autoWidth: true,
      autoHeight: true,
      hidden: true,
    },
    {
      id: 'client-field',
      text: 'Client',
      align: "center",
      field: 'client',
      minWidth: 150,
      renderer(props) {
        if (props.record.isOutsourced) {
          return props.value.name;
        } else {
          if (typeof props.value === "object") {
            return operatorSummaries[WS_TYPES.CLIENTS]
              .find(record => record.id === props.value.data.id)?.name
          } else if (Number.isInteger(props.value)) {
            return operatorSummaries[WS_TYPES.CLIENTS]
              .find(record => record.id === props.value)?.name
          }
        }
      },
      editor: {
        ...comboConfig,
        items: operatorSummaries[WS_TYPES.CLIENTS],
        required: true,
      },
      autoWidth: true,
    },
    {
      id: "vehicleCategory-field",
      text: 'Vehicle',
      field: 'vehicleCategory',
      minWidth: 150,
      align: "center",
      renderer(props) {
        if (typeof props.value === "object") {
          return operatorSummaries[WS_TYPES.VEHICLE_CATEGORIES]
            .find(record => record.id === props.value.data.id)?.name
        } else if (Number.isInteger(props.value)) {
          return operatorSummaries[WS_TYPES.VEHICLE_CATEGORIES]
            .find(record => record.id === props.value)?.name
        }
      },
      editor: {
        ...comboConfig,
        required: true,
        store: {
          data: operatorSummaries[WS_TYPES.VEHICLE_CATEGORIES],
          grouper: [{ field: "name" }]
        }
      },
      autoWidth: true,
    },
    {
      id: 'paymentMethod-field',
      text: 'Payment Method',
      field: 'paymentMethod',
      align: "center",
      renderer(props) {
        if (typeof props.value === "object") {
          return operatorSummaries[WS_TYPES.PAYMENT_METHODS]
            .find(record => record.id === props.value.data.id)?.name
        } else if (Number.isInteger(props.value)) {
          return operatorSummaries[WS_TYPES.PAYMENT_METHODS]
            .find(record => record.id === props.value)?.name
        }
      },
      editor: {
        ...comboConfig,
        required: true,
        store: {
          data: operatorSummaries[WS_TYPES.PAYMENT_METHODS],
          //grouper: [{field: "name"}]
        }
      },
      autoWidth: true,
    },
    {
      id: 'status-field',
      text: 'Status',
      field: 'status',
      groupable: false,
      align: "center",
      autoWidth: true,
      autoHeight: true,
      readOnly: true,
      minWidth: 100,
      renderer(props) {
        return props.value?.name + " ";
      }
    },
    {
      id: 'assignee-field',
      text: "Assignee",
      field: "assignee",
      align: "center",
      minWidth: 150,
      autoHeight: true,
      autoWidth: true,
      editor: {
        ...comboConfig,
        clearable: true,
        store: {
          data,
          groupers: [{ field: "type" }]
        },
        onClear(props) {
          props.source.up('grid').store.trigger('mmp_grid_update_assignee', {
            assignee: null,
            assigneeType: props.source.initialValue.data.type,
            transferId: props.source.up().record.id,
          })
          props.source.up().cancelEdit();
          //props.source.up().finishEditing();
        },
        onChange(props) {
          if (props.userAction) {
            console.log("onChange")
            const grid = props.source.up('grid');
            const record = props.source.up().record;
            if (!record.isPhantom && Number.isInteger(props.value?.id) && !record.isPhantom) {
              grid.store.trigger('mmp_grid_update_assignee', {
                assignee: props.value,
                assigneeType: props.value.type,
                transferId: record.id,
              }
              );
              props.source.up().cancelEdit();
              // props.source.up().finishEditing();
            }
          }
        }
      },
      renderer(props) {
        if (typeof props.value === "object") {
          if (props.value?.data.type === WS_TYPES.DRIVERS) {
            return operatorSummaries[WS_TYPES.DRIVERS]
              .find(record => record.id === props.value.data.id)?.name
          } else if (props.value?.data.type === WS_TYPES.OPERATORS) {
            return operatorSummaries[WS_TYPES.TRANSFER_OPERATORS]
              .find(record => record.id === props.value.data.id)?.name
          }
        } else if (Number.isInteger(props.value)) {
          if (props.value?.data.type === WS_TYPES.DRIVERS) {
            return operatorSummaries[WS_TYPES.DRIVERS]
              .find(record => record.id === props.value.data.id)?.name
          } else if (props.value?.data.type === WS_TYPES.TRANSFER_OPERATORS) {
            return operatorSummaries[WS_TYPES.TRANSFER_OPERATORS]
              .find(record => record.id === props.value.data.id)?.name
          }
        }
      },
    },
    {
      id: 'transferPrice-field',
      text: 'Price',
      field: 'transferPrice',
      type: 'aggregate',
      sum: 'sum',
      align: 'center',
      groupable: false,
      hidden: true,
      minWidth: 150,
      format: {
        style: 'currency',
        currency: 'EUR',
        fraction: 2,
      },
      editor: {
        required: true
      },
      autoWidth: true,
    },
    {
      id: "driverDetails-field",
      groupable: false,
      text: "Driver Details",
      field: "driverDetails",
      align: "center",
      autoHeight: true,
      autoWidth: true,
      hidden: true,
    },
    {
      id: "supplierDetails-field",
      groupable: false,
      text: "Supplier Details",
      field: "supplierDetails",
      align: "center",
      autoHeight: true,
      autoWidth: true,
      hidden: true,
    },
    {
      id: "privateDetails-field",
      groupable: false,
      text: "Private Details",
      field: "privateDetails",
      align: "center",
      autoHeight: true,
      autoWidth: true,
      hidden: true,
    },
  ],
  //Display Aggregations
  summaryFeature: true,

  // Configure Excel exporter feature
  excelExporterFeature: {
    excelExporter: {
      excelExporter: {
        xlsProvider: MyXlsProvider,
        filename: 'Transfers_Export.xlsx',
        exporterConfig: {
          includeFilteredRecordsOnly: true,
          columnFieldMap: {
            origin: 'origin.name',
            destination: 'destination.name',
            client: 'client.name',
            vehicleCategory: 'vehicleCategory.name',
            paymentMethod: 'paymentMethod.name',
            status: 'status.name',
            assignee: 'assignee.name'
          }
        }
      }
    },
  },

  // Toolbar with export buttons
  tbar: [
    {
      type: 'button',
      text: 'Export All',
      icon: 'b-fa b-fa-file-excel',
      tooltip: 'Export all records to Excel',
      onAction: ({ source }) => {
        const grid = source.up('grid');
        grid.features.excelExporter.export({
          filename: 'All_Transfers_Export.xlsx',
          includeFilteredRecordsOnly: false
        });
      }
    },
    {
      type: 'button',
      text: 'Export Filtered',
      icon: 'b-fa b-fa-filter',
      tooltip: 'Export only filtered/visible records',
      onAction: ({ source }) => {
        const grid = source.up('grid');
        grid.features.excelExporter.export({
          filename: 'Filtered_Transfers_Export.xlsx',
          includeFilteredRecordsOnly: true
        });
      }
    },
    {
      type: 'button',
      text: 'Export Selected',
      icon: 'b-fa b-fa-check-square',
      tooltip: 'Export only selected records',
      onAction: ({ source }) => {
        const grid = source.up('grid');
        const selectedRecords = grid.selectedRecords;
        if (selectedRecords.length === 0) {
          toast.error('No records selected. Please select at least one record.');
          return;
        }
        grid.features.excelExporter.export({
          filename: 'Selected_Transfers_Export.xlsx',
          records: selectedRecords
        });
      }
    }
  ],
};

// Helper function to process data for export
async function processDataForExport(records, options = {}) {
  const { includeHidden = false } = options;

  // Transform records to export-friendly format
  return records.map(record => {
    const exportRecord = {};

// Process each field based on its type
for (const field of Object.keys(record)) {
  // Skip internal fields or hidden fields if not explicitly included
  if (field.startsWith('_') || (!includeHidden && field.hidden)) {
    continue;
  }

  const value = record[field];

  // Handle special field types
  if (typeof value === 'object' && value !== null) {
    // Process date objects
    if (value instanceof Date) {
      exportRecord[field] = DateHelper.format(value, 'DD/MM/YY HH:mm');
    }
    // Handle nested objects
    else if (value.name) {
      exportRecord[field] = value.name;
    }
    // Handle data objects with nested structure
    else if (value.data) {
      exportRecord[field] = value.data.name || value.data.id || JSON.stringify(value.data);
    }
  } else {
    exportRecord[field] = value;
  }
}

return exportRecord;
  });
}

export { gridConfig, MyXlsProvider, processDataForExport };

Attachments
erro1.png
erro1.png (159.3 KiB) Viewed 213 times
error.png
error.png (72.6 KiB) Viewed 213 times
grid.png
grid.png (141.34 KiB) Viewed 213 times

Post by alex.l »

Hi,

Please review this topic as a first step viewtopic.php?p=165114#p165114
https://bryntum.com/products/grid/docs/api/Grid/feature/experimental/ExcelExporter#event-beforeExcelExport is a god place to amange settings before export, also tune columns set

All the best,
Alex Lazarev

How to ask for help? Please read our Support Policy

We do not write the code in bounds of forum support. If you need help with development, contact us via bryntum.com/services


Post Reply