Skip to content

Commit 38b64c2

Browse files
committed
fix: improve CSV export and import handling for proper data formatting
1 parent f666ff5 commit 38b64c2

File tree

3 files changed

+52
-45
lines changed

3 files changed

+52
-45
lines changed

custom/ExportCsv.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,9 @@ async function exportCsv() {
5454
throw new Error(resp.error);
5555
}
5656
57-
// Parse the CSV data to ensure proper formatting
58-
const parsedData = Papa.parse(resp.data).data;
59-
6057
// Generate properly formatted CSV
61-
const csvContent = '\ufeff' + Papa.unparse(parsedData, {
62-
quotes: true, // Force quotes around all fields
58+
const csvContent = '\ufeff' + Papa.unparse(resp.data, {
59+
quotes: resp.columnsToForceQuote, // Force quotes only certain columns (!!!not all this breaks BI/Excel tasks)
6360
quoteChar: '"',
6461
escapeChar: '"',
6562
});

custom/ImportCsv.vue

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ async function importCsv() {
165165
Papa.parse(text, {
166166
header: true,
167167
skipEmptyLines: true,
168+
// dynamicTyping: true, - bad option becaue it tries to parse "1" -> int even if the column is string
168169
169170
complete: async (results) => {
170171
if (results.errors.length > 0) {
@@ -214,12 +215,7 @@ async function importCsv() {
214215
reader.readAsText(file);
215216
};
216217
}
217-
function handleImportClick() {
218-
console.log('handleImportClick', checkProgress.value);
219-
if (!checkProgress.value) {
220-
importCsv();
221-
}
222-
}
218+
223219
224220
function click() {
225221
importCsv();

index.ts

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -66,42 +66,27 @@ export default class ImportExport extends AdminForthPlugin {
6666
getTotals: true,
6767
});
6868

69-
// csv export
69+
// prepare data for PapaParse unparse
7070
const columns = this.resourceConfig.columns.filter((col) => !col.virtual);
71-
72-
const escapeCSV = (value: any, column?: AdminForthResourceColumn) => {
73-
if (value === null || value === undefined) {
74-
return '""';
75-
}
76-
if (column?.type === AdminForthDataTypes.FLOAT
77-
|| column?.type === AdminForthDataTypes.INTEGER) {
78-
// no quotes for numbers
79-
return String(value);
80-
}
81-
if (column?.type === AdminForthDataTypes.BOOLEAN) {
82-
// no quotes for boolean values
83-
if (value === true) {
84-
return 'true';
85-
} else if (value === false) {
86-
return 'false';
87-
}
88-
}
89-
const str = String(value);
90-
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
91-
return `"${str.replace(/"/g, '""')}"`;
92-
}
93-
return `"${str}"`;
94-
};
9571

96-
let csv = data.data.map((row) => {
97-
return columns.map((col) => escapeCSV(row[col.name], col)).join(',');
98-
}).join('\n');
72+
const columnsToForceQuote = columns.map(col => {
73+
return col.type !== AdminForthDataTypes.FLOAT
74+
&& col.type !== AdminForthDataTypes.INTEGER
75+
&& col.type !== AdminForthDataTypes.BOOLEAN;
76+
})
9977

100-
// add headers
101-
const headers = columns.map((col) => escapeCSV(col.name)).join(',');
102-
csv = `${headers}\n${csv}`;
78+
const fields = columns.map((col) => col.name);
79+
80+
const rows = data.data.map((row) => {
81+
return columns.map((col) => row[col.name]);
82+
});
10383

104-
return { data: csv, exportedCount: data.total, ok: true };
84+
return {
85+
data: { fields, data: rows },
86+
columnsToForceQuote,
87+
exportedCount: data.total,
88+
ok: true
89+
};
10590
}
10691
});
10792

@@ -130,15 +115,31 @@ export default class ImportExport extends AdminForthPlugin {
130115

131116
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
132117

118+
const resourceColumns = columns.map(colName => this.resourceConfig.columns.find(c => c.name === colName));
119+
133120
const columnValues: any[] = Object.values(data);
134121
for (let i = 0; i < columnValues[0].length; i++) {
135122
const row = {};
136123
for (let j = 0; j < columns.length; j++) {
137-
row[columns[j]] = columnValues[j][i];
124+
const val = columnValues[j][i];
125+
const resourceCol = resourceColumns[j];
126+
127+
if ( (resourceCol.type === AdminForthDataTypes.INTEGER
128+
|| resourceCol.type === AdminForthDataTypes.FLOAT) && val !== ''
129+
) {
130+
// convert empty strings to null for numeric fields
131+
row[columns[j]] = +val;
132+
} else if (resourceCol.type === AdminForthDataTypes.BOOLEAN && val !== '') {
133+
row[columns[j]] = (val.toLowerCase() === 'true' || val === '1' || val === 1);
134+
} else {
135+
row[columns[j]] = val;
136+
}
138137
}
139138
rows.push(row);
140139
}
141140

141+
console.log('Prepared rows for import:', rows);
142+
142143
let importedCount = 0;
143144
let updatedCount = 0;
144145

@@ -190,11 +191,24 @@ export default class ImportExport extends AdminForthPlugin {
190191
}
191192

192193
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
194+
const resourceColumns = columns.map(colName => this.resourceConfig.columns.find(c => c.name === colName));
193195
const columnValues: any[] = Object.values(data);
194196
for (let i = 0; i < columnValues[0].length; i++) {
195197
const row = {};
196198
for (let j = 0; j < columns.length; j++) {
197-
row[columns[j]] = columnValues[j][i];
199+
const val = columnValues[j][i];
200+
const resourceCol = resourceColumns[j];
201+
202+
if ( (resourceCol.type === AdminForthDataTypes.INTEGER
203+
|| resourceCol.type === AdminForthDataTypes.FLOAT) && val !== ''
204+
) {
205+
// convert empty strings to null for numeric fields
206+
row[columns[j]] = +val;
207+
} else if (resourceCol.type === AdminForthDataTypes.BOOLEAN && val !== '') {
208+
row[columns[j]] = (val.toLowerCase() === 'true' || val === '1' || val === 1);
209+
} else {
210+
row[columns[j]] = val;
211+
}
198212
}
199213
rows.push(row);
200214
}

0 commit comments

Comments
 (0)