excel fix
This commit is contained in:
parent
03dd5406aa
commit
a100d99f1d
|
|
@ -509,6 +509,7 @@ export class CustomerOrderStatusGrid extends Component {
|
|||
const dayStr = `${day.toString().padStart(2, '0')}/${month}/${year}`;
|
||||
const dayKey = `day_${day}`;
|
||||
|
||||
|
||||
dayColumns.push({
|
||||
title: dayStr,
|
||||
width: 90,
|
||||
|
|
@ -540,6 +541,7 @@ export class CustomerOrderStatusGrid extends Component {
|
|||
const orderQty = pendingOrder + currentOrder;
|
||||
const fgAvailable = parseFloat(row.fg_qty) || 0;
|
||||
const remainingProduction = Math.max(0, orderQty - fgAvailable);
|
||||
const remaining_dispatch_qty = Math.max(0, orderQty - parseFloat(row.dispatch_qty) )
|
||||
|
||||
// Process date-wise production data
|
||||
const dailyProduction = {};
|
||||
|
|
@ -584,7 +586,9 @@ export class CustomerOrderStatusGrid extends Component {
|
|||
dispatch_qty: parseFloat(row.dispatch_qty) || 0,
|
||||
dispatched_qty_kg: parseFloat(row.dispatched_qty_kg) || 0,
|
||||
remaining_production: remainingProduction,
|
||||
remaining_dispatch_qty: remaining_dispatch_qty,
|
||||
date_wise: row.date_wise,
|
||||
produced_qty: row.produced_qty,
|
||||
...dailyProduction
|
||||
};
|
||||
});
|
||||
|
|
@ -1451,9 +1455,9 @@ export class CustomerOrderStatusGrid extends Component {
|
|||
style: { backgroundColor: '#d4edda', textAlign: 'right' }
|
||||
},
|
||||
{
|
||||
title: "Dispatch Qty",
|
||||
title: "Produced Qty",
|
||||
width: 90,
|
||||
dataIndx: "dispatch_qty",
|
||||
dataIndx: "produced_qty",
|
||||
dataType: "float",
|
||||
align: "right",
|
||||
render: (ui) => this.formatInteger(ui.cellData),
|
||||
|
|
@ -1484,6 +1488,36 @@ export class CustomerOrderStatusGrid extends Component {
|
|||
textAlign: 'right'
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Dispatch Qty",
|
||||
width: 90,
|
||||
dataIndx: "dispatch_qty",
|
||||
dataType: "float",
|
||||
align: "right",
|
||||
render: (ui) => this.formatInteger(ui.cellData),
|
||||
summary: {
|
||||
type: "sum",
|
||||
label: "Total:",
|
||||
render: (ui) => this.formatInteger(ui.data)
|
||||
},
|
||||
style: { backgroundColor: '#e3f2fd', textAlign: 'right' }
|
||||
},
|
||||
{
|
||||
title: "Remaining Dispatch",
|
||||
width: 120,
|
||||
dataIndx: "remaining_dispatch_qty",
|
||||
dataType: "float",
|
||||
filter: true,
|
||||
sortable: true,
|
||||
align: "right",
|
||||
render: (ui) => this.formatInteger(ui.cellData),
|
||||
summary: {
|
||||
type: "sum",
|
||||
label: "Total:",
|
||||
render: (ui) => this.formatInteger(ui.data)
|
||||
},
|
||||
style: { textAlign: 'right', backgroundColor: '#fff3cd' }
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -1559,14 +1593,6 @@ export class CustomerOrderStatusGrid extends Component {
|
|||
// EXPORT METHODS
|
||||
// ====================
|
||||
|
||||
// Replace your exportGrid method with this:
|
||||
// ====================
|
||||
// EXPORT METHODS - FIXED FOR DATA EXPORT
|
||||
// ====================
|
||||
|
||||
// ====================
|
||||
// EXPORT METHODS - WITH MERGED CELLS FOR GROUP HEADERS
|
||||
// ====================
|
||||
|
||||
exportGrid(ev, type) {
|
||||
ev.preventDefault();
|
||||
|
|
@ -1582,25 +1608,25 @@ exportGrid(ev, type) {
|
|||
gridData = this.state.gridData;
|
||||
columns = this.getOrderGridColumns();
|
||||
sheetName = 'Customer Order Status';
|
||||
filename = `Customer_Order_Status_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xlsx`;
|
||||
filename = `Customer_Order_Status_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xls`;
|
||||
break;
|
||||
case 'dispatch':
|
||||
gridData = this.state.dispatchData;
|
||||
columns = this.getDispatchGridColumns();
|
||||
sheetName = 'Dispatch Summary';
|
||||
filename = `Dispatch_Summary_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xlsx`;
|
||||
filename = `Dispatch_Summary_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xls`;
|
||||
break;
|
||||
case 'rm':
|
||||
gridData = this.state.rmData;
|
||||
columns = this.getRMGridColumns();
|
||||
sheetName = 'Raw Material Availability';
|
||||
filename = `RM_Availability_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xlsx`;
|
||||
filename = `RM_Availability_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xls`;
|
||||
break;
|
||||
case 'dpr':
|
||||
gridData = this.state.dprData;
|
||||
columns = this.getDPRGridColumns();
|
||||
sheetName = 'Daily Production Report';
|
||||
filename = `DPR_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xlsx`;
|
||||
filename = `DPR_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xls`;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
|
|
@ -1618,7 +1644,7 @@ exportGrid(ev, type) {
|
|||
// Generate HTML table with merged headers
|
||||
const table = this.generateMergedHeaderTable(gridData, columns, sheetName);
|
||||
|
||||
// Convert to Excel
|
||||
// Convert to Excel (MS Excel compatible)
|
||||
this.exportToExcel(table, filename, sheetName);
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -1631,135 +1657,27 @@ exportGrid(ev, type) {
|
|||
}
|
||||
|
||||
// Generate HTML table with merged column headers
|
||||
generateMergedHeaderTable(data, columns, sheetName) {
|
||||
// First, analyze column structure to determine merge ranges
|
||||
const headerRows = this.buildHeaderRows(columns);
|
||||
const flatColumns = this.flattenColumns(columns);
|
||||
// Format float for Excel
|
||||
formatFloatForExcel(value) {
|
||||
if (value === null || value === undefined || isNaN(value)) return '0.00';
|
||||
return parseFloat(value).toFixed(2);
|
||||
}
|
||||
|
||||
let html = '<table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse; font-family: Arial, sans-serif; width: 100%;">';
|
||||
// Format integer for Excel
|
||||
formatIntegerForExcel(value) {
|
||||
if (value === null || value === undefined || isNaN(value)) return '0';
|
||||
return parseInt(value).toString();
|
||||
}
|
||||
|
||||
// ==================== HEADER SECTION WITH MERGED CELLS ====================
|
||||
html += '<thead>';
|
||||
|
||||
// Generate multi-row header with merged cells
|
||||
for (let rowIdx = 0; rowIdx < headerRows.length; rowIdx++) {
|
||||
html += '<tr>';
|
||||
const row = headerRows[rowIdx];
|
||||
|
||||
for (let colIdx = 0; colIdx < row.length; colIdx++) {
|
||||
const cell = row[colIdx];
|
||||
if (cell) {
|
||||
// Skip if this cell was already merged (null in the array)
|
||||
if (cell.colspan === 0) continue;
|
||||
|
||||
let colspan = cell.colspan || 1;
|
||||
let rowspan = cell.rowspan || 1;
|
||||
|
||||
html += `<th colspan="${colspan}" rowspan="${rowspan}"
|
||||
style="background-color: ${rowIdx === 0 ? '#2C3E50' : '#E3F2FD'};
|
||||
color: ${rowIdx === 0 ? 'white' : 'black'};
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
border: 1px solid #000;
|
||||
vertical-align: middle;
|
||||
font-size: ${rowIdx === 0 ? '12px' : '11px'};
|
||||
border-bottom: ${rowIdx === 0 ? '3px solid #1a252f' : '1px solid #000'};">${cell.title}</th>`;
|
||||
}
|
||||
}
|
||||
html += '</tr>';
|
||||
}
|
||||
html += '</thead>';
|
||||
|
||||
// ==================== BODY SECTION ====================
|
||||
html += '<tbody>';
|
||||
|
||||
// Data rows
|
||||
data.forEach((row, index) => {
|
||||
// Add zebra striping
|
||||
const bgColor = index % 2 === 0 ? '#FFFFFF' : '#F8F9FA';
|
||||
html += '<tr>';
|
||||
|
||||
flatColumns.forEach(col => {
|
||||
let value = row[col.dataIndx];
|
||||
let displayValue = '';
|
||||
|
||||
// Format value based on data type
|
||||
if (value === null || value === undefined || value === '' || value === 0) {
|
||||
displayValue = '';
|
||||
} else if (col.dataType === 'float') {
|
||||
displayValue = parseFloat(value).toLocaleString('en-US', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
} else if (col.dataType === 'integer') {
|
||||
displayValue = parseInt(value).toLocaleString('en-US');
|
||||
} else {
|
||||
displayValue = value.toString();
|
||||
}
|
||||
|
||||
const align = col.dataType === 'float' || col.dataType === 'integer' ? 'right' : 'left';
|
||||
html += `<td style="text-align: ${align};
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
background-color: ${bgColor};
|
||||
white-space: nowrap;
|
||||
width:100%;
|
||||
${col.dataType === 'float' || col.dataType === 'integer' ? 'font-family: Arial, sans-serif;' : ''}">${displayValue}</td>`;
|
||||
});
|
||||
html += '</tr>';
|
||||
});
|
||||
|
||||
// ==================== SUMMARY SECTION WITH MERGED CELL ====================
|
||||
html += '<tfoot>';
|
||||
|
||||
// Calculate totals for numeric columns
|
||||
const totals = {};
|
||||
flatColumns.forEach(col => {
|
||||
if (col.dataType === 'float' || col.dataType === 'integer') {
|
||||
totals[col.dataIndx] = 0;
|
||||
data.forEach(row => {
|
||||
totals[col.dataIndx] += parseFloat(row[col.dataIndx]) || 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Grand Total row with merged first cell
|
||||
html += '<tr>';
|
||||
html += `<td colspan="1" style="font-weight: bold;
|
||||
background-color: #2C3E50;
|
||||
color: white;
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
border: 1px solid #000;
|
||||
font-size: 12px;">GRAND TOTAL</td>`;
|
||||
|
||||
// Add totals for remaining columns
|
||||
for (let i = 1; i < flatColumns.length; i++) {
|
||||
const col = flatColumns[i];
|
||||
if (col.dataType === 'float' || col.dataType === 'integer') {
|
||||
let total = totals[col.dataIndx] || 0;
|
||||
let formattedTotal = col.dataType === 'float'
|
||||
? total.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
: total.toLocaleString('en-US');
|
||||
html += `<td style="font-weight: bold;
|
||||
background-color: #2C3E50;
|
||||
color: white;
|
||||
text-align: right;
|
||||
padding: 10px;
|
||||
border: 1px solid #000;
|
||||
font-family: Arial, sans-serif;">${formattedTotal}</td>`;
|
||||
} else {
|
||||
html += `<td style="background-color: #2C3E50;
|
||||
border: 1px solid #000;"></td>`;
|
||||
}
|
||||
}
|
||||
html += '</tr>';
|
||||
|
||||
html += '</tfoot>';
|
||||
html += '</tbody></table>';
|
||||
|
||||
return html;
|
||||
// Escape HTML special characters
|
||||
escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
return String(text)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// Build multi-row header structure with merge information
|
||||
|
|
@ -1854,7 +1772,7 @@ flattenColumns(columns) {
|
|||
flatColumns.push({
|
||||
dataIndx: col.dataIndx,
|
||||
title: parentTitle ? `${parentTitle} - ${col.title}` : col.title,
|
||||
width: col.dataIndx === 'product'? 600 : (col.width || 100),
|
||||
width: col.dataIndx === 'product' ? 500 : (col.width || 100),
|
||||
align: col.align || 'left',
|
||||
dataType: col.dataType || 'string'
|
||||
});
|
||||
|
|
@ -1866,41 +1784,177 @@ flattenColumns(columns) {
|
|||
return flatColumns;
|
||||
}
|
||||
|
||||
// Export to Excel
|
||||
// Export to Excel (MS Excel compatible) - Gray and White Theme, Arial font, Frozen Header, Fit to Text
|
||||
exportToExcel(htmlTable, filename, sheetName) {
|
||||
// Calculate header rows depth from the table
|
||||
const headerDepth = (htmlTable.match(/<thead>[\s\S]*?<\/thead>/) || [''])[0];
|
||||
const headerRowCount = (headerDepth.match(/<tr>/g) || []).length;
|
||||
|
||||
// MS Excel specific XML format
|
||||
const excelContent = `
|
||||
<html xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
||||
xmlns="http://www.w3.org/TR/REC-html40">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="ProgId" content="Excel.Sheet">
|
||||
<meta name="Generator" content="Odoo Excel Export">
|
||||
<xml>
|
||||
<x:ExcelWorkbook>
|
||||
<x:ExcelWorksheets>
|
||||
<x:ExcelWorksheet>
|
||||
<x:Name>${this.escapeHtml(sheetName)}</x:Name>
|
||||
<x:WorksheetOptions>
|
||||
<x:DisplayGridlines/>
|
||||
<x:Zoom>90</x:Zoom>
|
||||
<x:Selected/>
|
||||
<x:FreezePanes/>
|
||||
<x:FrozenNoSplit/>
|
||||
<x:SplitHorizontal>${6}</x:SplitHorizontal>
|
||||
<x:TopRowBottomPane>${headerRowCount}</x:TopRowBottomPane>
|
||||
<x:ProtectContents>False</x:ProtectContents>
|
||||
<x:ProtectObjects>False</x:ProtectObjects>
|
||||
<x:ProtectScenarios>False</x:ProtectScenarios>
|
||||
</x:WorksheetOptions>
|
||||
</x:ExcelWorksheet>
|
||||
</x:ExcelWorksheets>
|
||||
<x:WindowHeight>9000</x:WindowHeight>
|
||||
<x:WindowWidth>13860</x:WindowWidth>
|
||||
<x:WindowTopX>0</x:WindowTopX>
|
||||
<x:WindowTopY>0</x:WindowTopY>
|
||||
<x:ProtectStructure>False</x:ProtectStructure>
|
||||
<x:ProtectWindows>False</x:ProtectWindows>
|
||||
</x:ExcelWorkbook>
|
||||
</xml>
|
||||
<style>
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th { vertical-align: middle; }
|
||||
td { vertical-align: middle; }
|
||||
.grand-total { font-weight: bold; background-color: #2C3E50; color: white; }
|
||||
body {
|
||||
font-family: Calibri;
|
||||
margin: 20px;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-family: Calibri;
|
||||
font-size: 10pt;
|
||||
background-color: #FFFFFF;
|
||||
table-layout: auto;
|
||||
}
|
||||
th {
|
||||
background-color: #E0E0E0;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
border: 1px solid #808080;
|
||||
font-size: 11pt;
|
||||
font-family: Calibri;
|
||||
white-space: nowrap;
|
||||
}
|
||||
td {
|
||||
padding: 6px;
|
||||
border: 1px solid #CCCCCC;
|
||||
font-size: 10pt;
|
||||
font-family: Calibri;
|
||||
background-color: #FFFFFF;
|
||||
white-space: nowrap;
|
||||
}
|
||||
td.product-cell {
|
||||
white-space: normal;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.header-main {
|
||||
background-color: #C0C0C0;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #404040;
|
||||
}
|
||||
.header-sub {
|
||||
background-color: #E0E0E0;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.grand-total {
|
||||
font-weight: bold;
|
||||
background-color: #D3D3D3;
|
||||
color: #000000;
|
||||
border-top: 2px solid #404040;
|
||||
}
|
||||
.total-label {
|
||||
background-color: #D3D3D3;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
.total-value {
|
||||
background-color: #D3D3D3;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
.row-even {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.row-odd {
|
||||
background-color: #F8F8F8;
|
||||
}
|
||||
h2 {
|
||||
color: #333333;
|
||||
font-family: Calibri;
|
||||
font-size: 16pt;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #808080;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
h3 {
|
||||
color: #666666;
|
||||
font-family: Calibri;
|
||||
font-size: 11pt;
|
||||
font-weight: normal;
|
||||
}
|
||||
.footer-note {
|
||||
color: #666666;
|
||||
font-size: 9pt;
|
||||
font-style: italic;
|
||||
margin-top: 20px;
|
||||
border-top: 1px solid #CCCCCC;
|
||||
padding-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2 style="color: #2C3E50; font-family: Arial, sans-serif; margin-bottom: 5px;">${sheetName}</h2>
|
||||
<h3 style="color: #666; font-family: Arial, sans-serif; margin-top: 0; margin-bottom: 20px; font-weight: normal;">
|
||||
${this.getMonthYearLabel()} | Exported on: ${new Date().toLocaleString()}
|
||||
</h3>
|
||||
${htmlTable}
|
||||
<p style="margin-top: 20px; font-family: Arial, sans-serif; color: #666; font-size: 10px;">
|
||||
* All quantities are in bags unless specified otherwise
|
||||
</p>
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #FFFFFF; font-family: Calibri;">
|
||||
<tr>
|
||||
<td style="font-family: Calibri; padding-bottom: 15px;">
|
||||
<h2>${this.escapeHtml(sheetName)}</h2>
|
||||
<h3>
|
||||
${this.escapeHtml(this.getMonthYearLabel())} | Exported on: ${new Date().toLocaleString()}
|
||||
</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
${htmlTable}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="footer-note" style="font-family: Calibri;">
|
||||
* All quantities are in bags unless specified otherwise
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// Create blob with proper MIME type for MS Excel
|
||||
const blob = new Blob([excelContent], {
|
||||
type: 'application/vnd.ms-excel'
|
||||
type: 'application/vnd.ms-excel; charset=UTF-8'
|
||||
});
|
||||
|
||||
// Download file
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
|
|
@ -1916,7 +1970,191 @@ exportToExcel(htmlTable, filename, sheetName) {
|
|||
});
|
||||
}
|
||||
|
||||
// Helper methods for export buttons
|
||||
// Generate HTML table with merged column headers - Arial font, Auto width, Frozen Header
|
||||
generateMergedHeaderTable(data, columns, sheetName) {
|
||||
// First, analyze column structure to determine merge ranges
|
||||
const headerRows = this.buildHeaderRows(columns);
|
||||
const flatColumns = this.flattenColumns(columns);
|
||||
|
||||
let html = '<table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse; font-family: Calibri; width: 100%; background-color: #FFFFFF; table-layout: auto;">';
|
||||
|
||||
// ==================== COLUMN WIDTHS - AUTO FIT ====================
|
||||
html += '<colgroup>';
|
||||
flatColumns.forEach(col => {
|
||||
// Don't set fixed widths - let Excel auto-fit
|
||||
html += `<col style="width: auto;">`;
|
||||
});
|
||||
html += '</colgroup>';
|
||||
|
||||
// ==================== HEADER SECTION WITH MERGED CELLS ====================
|
||||
html += '<thead>';
|
||||
|
||||
// Generate multi-row header with merged cells
|
||||
for (let rowIdx = 0; rowIdx < headerRows.length; rowIdx++) {
|
||||
html += '<tr>';
|
||||
const row = headerRows[rowIdx];
|
||||
const headerClass = rowIdx === 0 ? 'header-main' : 'header-sub';
|
||||
|
||||
for (let colIdx = 0; colIdx < row.length; colIdx++) {
|
||||
const cell = row[colIdx];
|
||||
if (cell) {
|
||||
// Skip if this cell was already merged
|
||||
if (cell.colspan === 0) continue;
|
||||
|
||||
let colspan = cell.colspan || 1;
|
||||
let rowspan = cell.rowspan || 1;
|
||||
|
||||
html += `<th colspan="${colspan}" rowspan="${rowspan}"
|
||||
class="${headerClass}"
|
||||
style="background-color: ${rowIdx === 0 ? '#C0C0C0' : '#E0E0E0'};
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #808080;
|
||||
border-bottom: ${rowIdx === 0 ? '2px solid #404040' : '1px solid #808080'};
|
||||
vertical-align: middle;
|
||||
font-size: ${rowIdx === 0 ? '11pt' : '10pt'};
|
||||
font-family: Calibri;
|
||||
white-space: nowrap;">${this.escapeHtml(cell.title || '')}</th>`;
|
||||
}
|
||||
}
|
||||
html += '</tr>';
|
||||
}
|
||||
html += '</thead>';
|
||||
|
||||
// ==================== BODY SECTION ====================
|
||||
html += '<tbody>';
|
||||
|
||||
// Data rows
|
||||
data.forEach((row, index) => {
|
||||
// Add zebra striping - subtle gray
|
||||
const bgColor = index % 2 === 0 ? '#FFFFFF' : '#F8F8F8';
|
||||
const rowClass = index % 2 === 0 ? 'row-even' : 'row-odd';
|
||||
|
||||
html += `<tr class="${rowClass}" style="font-family: Calibri;">`;
|
||||
|
||||
flatColumns.forEach(col => {
|
||||
let value = row[col.dataIndx];
|
||||
let displayValue = '';
|
||||
|
||||
// Format value based on data type
|
||||
if (value === null || value === undefined || value === '' || value === 0) {
|
||||
displayValue = '';
|
||||
} else if (col.dataType === 'float') {
|
||||
displayValue = this.formatFloatForExcel(value);
|
||||
} else if (col.dataType === 'integer') {
|
||||
displayValue = this.formatIntegerForExcel(value);
|
||||
} else {
|
||||
displayValue = this.escapeHtml(value.toString());
|
||||
}
|
||||
|
||||
const align = col.dataType === 'float' || col.dataType === 'integer' ? 'right' : 'left';
|
||||
const isProduct = col.dataIndx === 'product' || col.title?.toLowerCase().includes('product');
|
||||
|
||||
// Determine if this is a date column (format: YYYY-MM-DD or day_*)
|
||||
const isDateColumn = col.dataIndx?.match(/^\d{4}-\d{2}-\d{2}$/) || col.dataIndx?.startsWith('day_');
|
||||
|
||||
let cellStyle = `text-align: ${align};
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #CCCCCC;
|
||||
background-color: ${bgColor};
|
||||
font-family: Calibri;
|
||||
font-size: 10pt;
|
||||
color: #333333;`;
|
||||
|
||||
// Product columns - wrap text, auto width
|
||||
if (isProduct) {
|
||||
cellStyle += ` white-space: normal;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
word-wrap: break-word;`;
|
||||
}
|
||||
// Date columns - fixed width, center aligned
|
||||
else if (isDateColumn) {
|
||||
cellStyle += ` white-space: nowrap;
|
||||
text-align: center;
|
||||
min-width: 80px;`;
|
||||
}
|
||||
// Numeric columns - right aligned, monospace numbers
|
||||
else if (col.dataType === 'float' || col.dataType === 'integer') {
|
||||
cellStyle += ` white-space: nowrap;
|
||||
mso-number-format:\\#\\,\\#\\#0\\.00;`;
|
||||
}
|
||||
// Other text columns - nowrap by default
|
||||
else {
|
||||
cellStyle += ` white-space: nowrap;`;
|
||||
}
|
||||
|
||||
html += `<td style="${cellStyle}">${displayValue}</td>`;
|
||||
});
|
||||
html += '</tr>';
|
||||
});
|
||||
|
||||
// ==================== SUMMARY SECTION WITH MERGED CELL ====================
|
||||
html += '<tfoot>';
|
||||
|
||||
// Calculate totals for numeric columns
|
||||
const totals = {};
|
||||
flatColumns.forEach(col => {
|
||||
if (col.dataType === 'float' || col.dataType === 'integer') {
|
||||
totals[col.dataIndx] = 0;
|
||||
data.forEach(row => {
|
||||
totals[col.dataIndx] += parseFloat(row[col.dataIndx]) || 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Grand Total row with merged first cell
|
||||
html += '<tr>';
|
||||
html += `<td colspan="1" class="total-label"
|
||||
style="font-weight: bold;
|
||||
background-color: #D3D3D3;
|
||||
color: #000000;
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #808080;
|
||||
border-top: 2px solid #404040;
|
||||
font-size: 11pt;
|
||||
font-family: Calibri;
|
||||
white-space: nowrap;">GRAND TOTAL</td>`;
|
||||
|
||||
// Add totals for remaining columns
|
||||
for (let i = 1; i < flatColumns.length; i++) {
|
||||
const col = flatColumns[i];
|
||||
if (col.dataType === 'float' || col.dataType === 'integer') {
|
||||
let total = totals[col.dataIndx] || 0;
|
||||
let formattedTotal = col.dataType === 'float'
|
||||
? this.formatFloatForExcel(total)
|
||||
: this.formatIntegerForExcel(total);
|
||||
html += `<td style="font-weight: bold;
|
||||
background-color: #D3D3D3;
|
||||
color: #000000;
|
||||
text-align: right;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #808080;
|
||||
border-top: 2px solid #404040;
|
||||
font-family: Calibri;
|
||||
font-size: 11pt;
|
||||
white-space: nowrap;
|
||||
mso-number-format:\\#\\,\\#\\#0\\.00;">${formattedTotal}</td>`;
|
||||
} else {
|
||||
html += `<td style="background-color: #D3D3D3;
|
||||
border: 1px solid #808080;
|
||||
border-top: 2px solid #404040;
|
||||
font-family: Calibri;"></td>`;
|
||||
}
|
||||
}
|
||||
html += '</tr>';
|
||||
|
||||
html += '</tfoot>';
|
||||
html += '</tbody></table>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Flatten nested columns - with auto width
|
||||
|
||||
exportOrderGrid(ev) {
|
||||
this.exportGrid(ev, 'order');
|
||||
}
|
||||
|
|
@ -1932,7 +2170,6 @@ exportRMGrid(ev) {
|
|||
exportDPRGrid(ev) {
|
||||
this.exportGrid(ev, 'dpr');
|
||||
}
|
||||
|
||||
// ====================
|
||||
// TAB EVENT HANDLER
|
||||
// ====================
|
||||
|
|
|
|||
Loading…
Reference in New Issue