feat: swap-csv-columns fixed

This commit is contained in:
Chesterkxng
2025-04-01 15:40:19 +00:00
parent d56af77ed5
commit 477117cd8c
4 changed files with 70 additions and 69 deletions

View File

@@ -11,8 +11,10 @@ import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
import SimpleRadio from '@components/options/SimpleRadio'; import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { csvColumnsSwap } from './service'; import { csvColumnsSwap } from './service';
import { getCsvHeaders } from '@utils/csv';
import { InitialValuesType } from './types';
const initialValues = { const initialValues: InitialValuesType = {
fromPositionStatus: true, fromPositionStatus: true,
toPositionStatus: true, toPositionStatus: true,
fromPosition: '1', fromPosition: '1',
@@ -25,7 +27,7 @@ const initialValues = {
commentCharacter: '#', commentCharacter: '#',
emptyLines: true emptyLines: true
}; };
type InitialValuesType = typeof initialValues;
const exampleCards: CardExampleType<InitialValuesType>[] = [ const exampleCards: CardExampleType<InitialValuesType>[] = [
{ {
title: 'Move the Key Column to the First Position', title: 'Move the Key Column to the First Position',
@@ -48,8 +50,8 @@ Utah,Zion Park`,
toPositionStatus: true, toPositionStatus: true,
fromPosition: '1', fromPosition: '1',
toPosition: '2', toPosition: '2',
fromHeader: '', fromHeader: 'park_name',
toHeader: '', toHeader: 'location',
emptyValuesFilling: false, emptyValuesFilling: false,
customFiller: '*', customFiller: '*',
deleteComment: false, deleteComment: false,
@@ -120,7 +122,7 @@ Xiaomi,13 Ultra,Android,6.6″,$849`,
fromPosition: '1', fromPosition: '1',
toPosition: '4', toPosition: '4',
fromHeader: 'ScreenSize', fromHeader: 'ScreenSize',
toHeader: 'Function', toHeader: 'OS',
emptyValuesFilling: true, emptyValuesFilling: true,
customFiller: 'x', customFiller: 'x',
deleteComment: true, deleteComment: true,
@@ -137,25 +139,16 @@ export default function CsvToTsv({
const [input, setInput] = useState<string>(''); const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>(''); const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: string) => { const compute = (optionsValues: InitialValuesType, input: string) => {
setResult( setResult(csvColumnsSwap(input, optionsValues));
csvColumnsSwap(
input,
optionsValues.fromPositionStatus,
optionsValues.fromPosition,
optionsValues.toPositionStatus,
optionsValues.toPosition,
optionsValues.fromHeader,
optionsValues.toHeader,
optionsValues.emptyValuesFilling,
optionsValues.customFiller,
optionsValues.deleteComment,
optionsValues.commentCharacter,
optionsValues.emptyLines
)
);
}; };
const headers = getCsvHeaders(input);
const headerOptions = headers.map((item) => ({
label: `${item}`,
value: item
}));
const getGroups: GetGroupsType<InitialValuesType> = ({ const getGroups: GetGroupsType<InitialValuesType> = ({
values, values,
updateField updateField
@@ -175,18 +168,21 @@ export default function CsvToTsv({
value={values.fromPosition} value={values.fromPosition}
onOwnChange={(val) => updateField('fromPosition', val)} onOwnChange={(val) => updateField('fromPosition', val)}
type="number" type="number"
inputProps={{ min: 1, max: headers.length }}
/> />
)} )}
<SimpleRadio <SimpleRadio
onClick={() => updateField('fromPositionStatus', false)} onClick={() => updateField('fromPositionStatus', false)}
title="Set Column-From Header" title="Set Column-From Header"
checked={!values.fromPositionStatus} checked={!values.fromPositionStatus}
/> />
{!values.fromPositionStatus && ( {!values.fromPositionStatus && (
<TextFieldWithDesc <SelectWithDesc
description={'Header of the first column you want to swap'} selected={values.fromHeader}
value={values.fromHeader} options={headerOptions}
onOwnChange={(val) => updateField('fromHeader', val)} onChange={(value) => updateField('fromHeader', value)}
description={'Header of the first column you want to swap.'}
/> />
)} )}
</Box> </Box>
@@ -207,6 +203,7 @@ export default function CsvToTsv({
value={values.toPosition} value={values.toPosition}
onOwnChange={(val) => updateField('toPosition', val)} onOwnChange={(val) => updateField('toPosition', val)}
type="number" type="number"
inputProps={{ min: 1, max: headers.length }}
/> />
)} )}
<SimpleRadio <SimpleRadio
@@ -215,10 +212,11 @@ export default function CsvToTsv({
checked={!values.toPositionStatus} checked={!values.toPositionStatus}
/> />
{!values.toPositionStatus && ( {!values.toPositionStatus && (
<TextFieldWithDesc <SelectWithDesc
description={'Header of the second column you want to swap'} selected={values.toHeader}
value={values.toHeader} options={headerOptions}
onOwnChange={(val) => updateField('toHeader', val)} onChange={(value) => updateField('toHeader', value)}
description={'Header of the second column you want to swap..'}
/> />
)} )}
</Box> </Box>
@@ -261,7 +259,7 @@ export default function CsvToTsv({
title="Delete Comments" title="Delete Comments"
description="if checked, comments given by the following character will be deleted" description="if checked, comments given by the following character will be deleted"
/> />
{!values.emptyValuesFilling && ( {values.deleteComment && (
<TextFieldWithDesc <TextFieldWithDesc
description={ description={
'Specify the character used to start comments in the input CSV (and if needed remove them via checkbox above)' 'Specify the character used to start comments in the input CSV (and if needed remove them via checkbox above)'

View File

@@ -1,21 +1,17 @@
import { splitCsv } from '@utils/csv'; import { splitCsv } from '@utils/csv';
import { InitialValuesType } from './types';
function retrieveFromAndTo( function retrieveFromAndTo(
headerRow: string[], headerRow: string[],
fromPositionStatus: boolean, options: InitialValuesType
toPositionStatus: boolean,
fromPosition: string | '',
toPosition: string | '',
fromHeader: string | '',
toHeader: string | ''
): number[] { ): number[] {
const from = fromPositionStatus const from = options.fromPositionStatus
? Number(fromPosition) ? Number(options.fromPosition)
: headerRow.findIndex((header) => header === fromHeader) + 1; : headerRow.findIndex((header) => header === options.fromHeader) + 1;
const to = toPositionStatus const to = options.toPositionStatus
? Number(toPosition) ? Number(options.toPosition)
: headerRow.findIndex((header) => header === toHeader) + 1; : headerRow.findIndex((header) => header === options.toHeader) + 1;
if (from <= 0 || to <= 0) if (from <= 0 || to <= 0)
throw new Error('Invalid column positions. Check headers or positions.'); throw new Error('Invalid column positions. Check headers or positions.');
@@ -37,44 +33,28 @@ function swap(lines: string[][], from: number, to: number): string[][] {
}); });
} }
export function csvColumnsSwap( export function csvColumnsSwap(input: string, options: InitialValuesType) {
input: string,
fromPositionStatus: boolean,
fromPosition: string | '',
toPositionStatus: boolean,
toPosition: string | '',
fromHeader: string | '',
toHeader: string | '',
emptyValuesFilling: boolean,
customFiller: string | '',
deleteComment: boolean,
commentCharacter: string | '',
emptyLines: boolean
) {
if (!input) { if (!input) {
return ''; return '';
} }
// split csv input and remove comments // split csv input and remove comments
const rows = splitCsv(input, deleteComment, commentCharacter, emptyLines); const rows = splitCsv(
input,
options.deleteComment,
options.commentCharacter,
options.emptyLines
);
const columnCount = Math.max(...rows.map((row) => row.length)); const columnCount = Math.max(...rows.map((row) => row.length));
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < columnCount; j++) { for (let j = 0; j < columnCount; j++) {
if (!rows[i][j]) { if (!rows[i][j]) {
rows[i][j] = emptyValuesFilling ? '' : customFiller; rows[i][j] = options.emptyValuesFilling ? '' : options.customFiller;
} }
} }
} }
const positions = retrieveFromAndTo( const positions = retrieveFromAndTo(rows[0], options);
rows[0],
fromPositionStatus,
toPositionStatus,
fromPosition,
toPosition,
fromHeader,
toHeader
);
const result = swap(rows, positions[0], positions[1]); const result = swap(rows, positions[0], positions[1]);
return result.join('\n'); return result.join('\n');

View File

@@ -0,0 +1,13 @@
export type InitialValuesType = {
fromPositionStatus: boolean;
fromPosition: string | '';
toPositionStatus: boolean;
toPosition: string | '';
fromHeader: string | '';
toHeader: string | '';
emptyValuesFilling: boolean;
customFiller: string | '';
deleteComment: boolean;
commentCharacter: string | '';
emptyLines: boolean;
};

View File

@@ -13,7 +13,7 @@ export function splitCsv(
let rows = input.split('\n').map((row) => row.split(',')); let rows = input.split('\n').map((row) => row.split(','));
// Remove comments if deleteComment is true // Remove comments if deleteComment is true
if (deleteComment) { if (deleteComment && commentCharacter) {
rows = rows.filter((row) => !row[0].trim().startsWith(commentCharacter)); rows = rows.filter((row) => !row[0].trim().startsWith(commentCharacter));
} }
@@ -24,3 +24,13 @@ export function splitCsv(
return rows; return rows;
} }
/**
* get the headers from a CSV string .
* @param {string} input - The CSV input string.
* @returns {string[]} - The CSV header as a 1D array.
*/
export function getCsvHeaders(csvString: string): string[] {
const rows = csvString.split('\n').map((row) => row.split(','));
return rows.length > 0 ? rows[0].map((header) => header.trim()) : [];
}