mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-07-29 20:51:47 +00:00
Some checks are pending
Build assets artifact / Build assets artifact (push) Waiting to run
Docker Image Build / build (linux/amd64, amd64, ubuntu-latest) (push) Waiting to run
Docker Image Build / build (linux/arm/v7, armv7, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build / build (linux/arm64, arm64, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build / merge (push) Blocked by required conditions
Docker Image Build (FrankenPHP) / build (linux/amd64, amd64, ubuntu-latest) (push) Waiting to run
Docker Image Build (FrankenPHP) / build (linux/arm/v7, armv7, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build (FrankenPHP) / build (linux/arm64, arm64, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build (FrankenPHP) / merge (push) Blocked by required conditions
Static analysis / Static analysis (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, sqlite) (push) Waiting to run
* add Export AS CSV button to the Project BOM Entry tab * added Export as CSV translation * add service file for BOM exporter * added controller for handling exporting project BOM data as CSV file * added tests for Export as CSV controller and exporter code * remove hierarchical paths from being exported in CSV to match datatable format * Fixed message id * Use stimulus functions to refer to stimulus controller --------- Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
176 lines
5 KiB
JavaScript
176 lines
5 KiB
JavaScript
import { Controller } from '@hotwired/stimulus';
|
|
|
|
export default class extends Controller {
|
|
static values = {
|
|
url: String,
|
|
};
|
|
|
|
async export(event) {
|
|
event.preventDefault();
|
|
|
|
const tableElement = this.element
|
|
.closest('#bom-tab-pane')
|
|
?.querySelector('table');
|
|
|
|
if (!tableElement) {
|
|
throw new Error('Could not find the project BOM table.');
|
|
}
|
|
|
|
if (
|
|
typeof window.jQuery === 'undefined'
|
|
|| !window.jQuery.fn.DataTable.isDataTable(tableElement)
|
|
) {
|
|
throw new Error('The project BOM DataTable is not initialized.');
|
|
}
|
|
|
|
const dataTable = window.jQuery(tableElement).DataTable();
|
|
const ajaxParameters = dataTable.ajax.params();
|
|
|
|
const parameters = this.toSearchParameters(ajaxParameters);
|
|
|
|
/*
|
|
* Export the currently visible columns in their current display order.
|
|
* Exclude the picture column because it has no useful CSV value.
|
|
*/
|
|
dataTable
|
|
.columns()
|
|
.every(function () {
|
|
/*
|
|
* column.visible() returns DataTables' configured visibility.
|
|
*
|
|
* Responsive may hide a column visually and move it into a child
|
|
* row, but it does not change this configured visibility state.
|
|
*/
|
|
if (!this.visible()) {
|
|
return;
|
|
}
|
|
|
|
const index = this.index();
|
|
const settings = dataTable.settings()[0];
|
|
const columnSettings = settings.aoColumns[index];
|
|
const name = columnSettings.data;
|
|
|
|
/*
|
|
* The picture column is not useful in CSV.
|
|
*/
|
|
if (!name || name === 'picture') {
|
|
return;
|
|
}
|
|
|
|
const heading = this.header().textContent.trim();
|
|
|
|
parameters.append('exportColumns[]', name);
|
|
parameters.append(
|
|
'exportLabels[]',
|
|
heading || name
|
|
);
|
|
});
|
|
|
|
await this.downloadExport(parameters);
|
|
}
|
|
|
|
async downloadExport(parameters) {
|
|
const response = await fetch(this.urlValue, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
body: parameters.toString(),
|
|
credentials: 'same-origin',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorBody = await response.text();
|
|
|
|
console.error('BOM CSV export failed:', {
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
body: errorBody,
|
|
});
|
|
|
|
throw new Error(
|
|
`BOM CSV export failed with HTTP ${response.status}`
|
|
);
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
const filename = this.getFilename(response)
|
|
?? 'project_bom.csv';
|
|
|
|
const downloadUrl = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
|
|
link.href = downloadUrl;
|
|
link.download = filename;
|
|
link.style.display = 'none';
|
|
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
|
|
URL.revokeObjectURL(downloadUrl);
|
|
}
|
|
|
|
getFilename(response) {
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
|
|
if (!disposition) {
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
* Prefer RFC 5987 filename*=UTF-8''... when present.
|
|
*/
|
|
const encodedMatch = disposition.match(
|
|
/filename\*=UTF-8''([^;]+)/i
|
|
);
|
|
|
|
if (encodedMatch) {
|
|
return decodeURIComponent(encodedMatch[1]);
|
|
}
|
|
|
|
const filenameMatch = disposition.match(
|
|
/filename="?([^";]+)"?/i
|
|
);
|
|
|
|
return filenameMatch ? filenameMatch[1] : null;
|
|
}
|
|
|
|
/**
|
|
* Convert DataTables' nested AJAX object into query-string parameters.
|
|
*/
|
|
toSearchParameters(object) {
|
|
const parameters = new URLSearchParams();
|
|
|
|
const append = (key, value) => {
|
|
if (Array.isArray(value)) {
|
|
value.forEach((item, index) => {
|
|
append(`${key}[${index}]`, item);
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
if (value !== null && typeof value === 'object') {
|
|
Object.entries(value).forEach(([childKey, childValue]) => {
|
|
const fullKey = key
|
|
? `${key}[${childKey}]`
|
|
: childKey;
|
|
|
|
append(fullKey, childValue);
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
parameters.append(key, String(value ?? ''));
|
|
};
|
|
|
|
Object.entries(object).forEach(([key, value]) => {
|
|
append(key, value);
|
|
});
|
|
|
|
return parameters;
|
|
}
|
|
}
|