mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-20 06:29:32 +02:00
feat: swap-csv-columns fixed
This commit is contained in:
@@ -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)'
|
||||||
|
@@ -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');
|
||||||
|
13
src/pages/tools/csv/swap-csv-columns/types.ts
Normal file
13
src/pages/tools/csv/swap-csv-columns/types.ts
Normal 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;
|
||||||
|
};
|
@@ -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()) : [];
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user