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 };