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 dayStr = `${day.toString().padStart(2, '0')}/${month}/${year}`;
|
||||||
const dayKey = `day_${day}`;
|
const dayKey = `day_${day}`;
|
||||||
|
|
||||||
|
|
||||||
dayColumns.push({
|
dayColumns.push({
|
||||||
title: dayStr,
|
title: dayStr,
|
||||||
width: 90,
|
width: 90,
|
||||||
|
|
@ -540,6 +541,7 @@ export class CustomerOrderStatusGrid extends Component {
|
||||||
const orderQty = pendingOrder + currentOrder;
|
const orderQty = pendingOrder + currentOrder;
|
||||||
const fgAvailable = parseFloat(row.fg_qty) || 0;
|
const fgAvailable = parseFloat(row.fg_qty) || 0;
|
||||||
const remainingProduction = Math.max(0, orderQty - fgAvailable);
|
const remainingProduction = Math.max(0, orderQty - fgAvailable);
|
||||||
|
const remaining_dispatch_qty = Math.max(0, orderQty - parseFloat(row.dispatch_qty) )
|
||||||
|
|
||||||
// Process date-wise production data
|
// Process date-wise production data
|
||||||
const dailyProduction = {};
|
const dailyProduction = {};
|
||||||
|
|
@ -584,7 +586,9 @@ export class CustomerOrderStatusGrid extends Component {
|
||||||
dispatch_qty: parseFloat(row.dispatch_qty) || 0,
|
dispatch_qty: parseFloat(row.dispatch_qty) || 0,
|
||||||
dispatched_qty_kg: parseFloat(row.dispatched_qty_kg) || 0,
|
dispatched_qty_kg: parseFloat(row.dispatched_qty_kg) || 0,
|
||||||
remaining_production: remainingProduction,
|
remaining_production: remainingProduction,
|
||||||
|
remaining_dispatch_qty: remaining_dispatch_qty,
|
||||||
date_wise: row.date_wise,
|
date_wise: row.date_wise,
|
||||||
|
produced_qty: row.produced_qty,
|
||||||
...dailyProduction
|
...dailyProduction
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
@ -1451,9 +1455,9 @@ export class CustomerOrderStatusGrid extends Component {
|
||||||
style: { backgroundColor: '#d4edda', textAlign: 'right' }
|
style: { backgroundColor: '#d4edda', textAlign: 'right' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Dispatch Qty",
|
title: "Produced Qty",
|
||||||
width: 90,
|
width: 90,
|
||||||
dataIndx: "dispatch_qty",
|
dataIndx: "produced_qty",
|
||||||
dataType: "float",
|
dataType: "float",
|
||||||
align: "right",
|
align: "right",
|
||||||
render: (ui) => this.formatInteger(ui.cellData),
|
render: (ui) => this.formatInteger(ui.cellData),
|
||||||
|
|
@ -1484,6 +1488,36 @@ export class CustomerOrderStatusGrid extends Component {
|
||||||
textAlign: 'right'
|
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
|
// 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) {
|
exportGrid(ev, type) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
@ -1582,25 +1608,25 @@ exportGrid(ev, type) {
|
||||||
gridData = this.state.gridData;
|
gridData = this.state.gridData;
|
||||||
columns = this.getOrderGridColumns();
|
columns = this.getOrderGridColumns();
|
||||||
sheetName = 'Customer Order Status';
|
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;
|
break;
|
||||||
case 'dispatch':
|
case 'dispatch':
|
||||||
gridData = this.state.dispatchData;
|
gridData = this.state.dispatchData;
|
||||||
columns = this.getDispatchGridColumns();
|
columns = this.getDispatchGridColumns();
|
||||||
sheetName = 'Dispatch Summary';
|
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;
|
break;
|
||||||
case 'rm':
|
case 'rm':
|
||||||
gridData = this.state.rmData;
|
gridData = this.state.rmData;
|
||||||
columns = this.getRMGridColumns();
|
columns = this.getRMGridColumns();
|
||||||
sheetName = 'Raw Material Availability';
|
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;
|
break;
|
||||||
case 'dpr':
|
case 'dpr':
|
||||||
gridData = this.state.dprData;
|
gridData = this.state.dprData;
|
||||||
columns = this.getDPRGridColumns();
|
columns = this.getDPRGridColumns();
|
||||||
sheetName = 'Daily Production Report';
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
|
|
@ -1618,7 +1644,7 @@ exportGrid(ev, type) {
|
||||||
// Generate HTML table with merged headers
|
// Generate HTML table with merged headers
|
||||||
const table = this.generateMergedHeaderTable(gridData, columns, sheetName);
|
const table = this.generateMergedHeaderTable(gridData, columns, sheetName);
|
||||||
|
|
||||||
// Convert to Excel
|
// Convert to Excel (MS Excel compatible)
|
||||||
this.exportToExcel(table, filename, sheetName);
|
this.exportToExcel(table, filename, sheetName);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1631,135 +1657,27 @@ exportGrid(ev, type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate HTML table with merged column headers
|
// Generate HTML table with merged column headers
|
||||||
generateMergedHeaderTable(data, columns, sheetName) {
|
// Format float for Excel
|
||||||
// First, analyze column structure to determine merge ranges
|
formatFloatForExcel(value) {
|
||||||
const headerRows = this.buildHeaderRows(columns);
|
if (value === null || value === undefined || isNaN(value)) return '0.00';
|
||||||
const flatColumns = this.flattenColumns(columns);
|
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%;">';
|
|
||||||
|
|
||||||
// ==================== 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';
|
// Format integer for Excel
|
||||||
html += `<td style="text-align: ${align};
|
formatIntegerForExcel(value) {
|
||||||
padding: 8px;
|
if (value === null || value === undefined || isNaN(value)) return '0';
|
||||||
border: 1px solid #ddd;
|
return parseInt(value).toString();
|
||||||
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
|
// Escape HTML special characters
|
||||||
html += '<tr>';
|
escapeHtml(text) {
|
||||||
html += `<td colspan="1" style="font-weight: bold;
|
if (!text) return '';
|
||||||
background-color: #2C3E50;
|
return String(text)
|
||||||
color: white;
|
.replace(/&/g, '&')
|
||||||
text-align: left;
|
.replace(/</g, '<')
|
||||||
padding: 10px;
|
.replace(/>/g, '>')
|
||||||
border: 1px solid #000;
|
.replace(/"/g, '"')
|
||||||
font-size: 12px;">GRAND TOTAL</td>`;
|
.replace(/'/g, ''');
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build multi-row header structure with merge information
|
// Build multi-row header structure with merge information
|
||||||
|
|
@ -1854,7 +1772,7 @@ flattenColumns(columns) {
|
||||||
flatColumns.push({
|
flatColumns.push({
|
||||||
dataIndx: col.dataIndx,
|
dataIndx: col.dataIndx,
|
||||||
title: parentTitle ? `${parentTitle} - ${col.title}` : col.title,
|
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',
|
align: col.align || 'left',
|
||||||
dataType: col.dataType || 'string'
|
dataType: col.dataType || 'string'
|
||||||
});
|
});
|
||||||
|
|
@ -1866,41 +1784,177 @@ flattenColumns(columns) {
|
||||||
return flatColumns;
|
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) {
|
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 = `
|
const excelContent = `
|
||||||
<html xmlns:o="urn:schemas-microsoft-com:office:office"
|
<html xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||||
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
||||||
xmlns="http://www.w3.org/TR/REC-html40">
|
xmlns="http://www.w3.org/TR/REC-html40">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<meta name="ProgId" content="Excel.Sheet">
|
<meta name="ProgId" content="Excel.Sheet">
|
||||||
<meta name="Generator" content="Odoo Excel Export">
|
<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>
|
<style>
|
||||||
table { border-collapse: collapse; width: 100%; }
|
body {
|
||||||
th { vertical-align: middle; }
|
font-family: Calibri;
|
||||||
td { vertical-align: middle; }
|
margin: 20px;
|
||||||
.grand-total { font-weight: bold; background-color: #2C3E50; color: white; }
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2 style="color: #2C3E50; font-family: Arial, sans-serif; margin-bottom: 5px;">${sheetName}</h2>
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #FFFFFF; font-family: Calibri;">
|
||||||
<h3 style="color: #666; font-family: Arial, sans-serif; margin-top: 0; margin-bottom: 20px; font-weight: normal;">
|
<tr>
|
||||||
${this.getMonthYearLabel()} | Exported on: ${new Date().toLocaleString()}
|
<td style="font-family: Calibri; padding-bottom: 15px;">
|
||||||
|
<h2>${this.escapeHtml(sheetName)}</h2>
|
||||||
|
<h3>
|
||||||
|
${this.escapeHtml(this.getMonthYearLabel())} | Exported on: ${new Date().toLocaleString()}
|
||||||
</h3>
|
</h3>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
${htmlTable}
|
${htmlTable}
|
||||||
<p style="margin-top: 20px; font-family: Arial, sans-serif; color: #666; font-size: 10px;">
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="footer-note" style="font-family: Calibri;">
|
||||||
* All quantities are in bags unless specified otherwise
|
* All quantities are in bags unless specified otherwise
|
||||||
</p>
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Create blob with proper MIME type for MS Excel
|
||||||
const blob = new Blob([excelContent], {
|
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 url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
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) {
|
exportOrderGrid(ev) {
|
||||||
this.exportGrid(ev, 'order');
|
this.exportGrid(ev, 'order');
|
||||||
}
|
}
|
||||||
|
|
@ -1932,7 +2170,6 @@ exportRMGrid(ev) {
|
||||||
exportDPRGrid(ev) {
|
exportDPRGrid(ev) {
|
||||||
this.exportGrid(ev, 'dpr');
|
this.exportGrid(ev, 'dpr');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================
|
// ====================
|
||||||
// TAB EVENT HANDLER
|
// TAB EVENT HANDLER
|
||||||
// ====================
|
// ====================
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue