Skip to content

Commit

Permalink
Fix imports for numeric meta fields
Browse files Browse the repository at this point in the history
  • Loading branch information
alex6480 committed Sep 25, 2023
1 parent 0e21cae commit 5b0fc0c
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 49 deletions.
16 changes: 14 additions & 2 deletions backend/Controllers/TransactionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using assetgrid_backend.models.Search;
using assetgrid_backend.models.ViewModels;
using assetgrid_backend.models.MetaFields;
using System.Text.Json;

namespace assetgrid_backend.Controllers
{
Expand Down Expand Up @@ -542,9 +543,20 @@ public async Task<IActionResult> CreateMany(List<ViewModifyTransaction> transact
foreach (var metaValue in transaction.MetaData!)
{
if (metaValue.Value != null && metafields.ContainsKey(metaValue.MetaId) &&
metafields[metaValue.MetaId].ValueType == MetaFieldValueType.Number)
metafields[metaValue.MetaId].ValueType == MetaFieldValueType.Transaction)
{
referencedTransactionIds.Add((int)metaValue.Value!);
int? transactionIdValue = metaValue.Value switch
{
JsonElement x => x.ValueKind == JsonValueKind.Number ? x.GetInt32() : null,
int x => x,
null => null,
_ => throw new Exception("Incorrect type of value")
};

if (transactionIdValue.HasValue)
{
referencedTransactionIds.Add(transactionIdValue.Value);
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function AssetgridApp (): React.ReactElement {
<MobileHeader setShowSidebar={setShowSidebar} sidebarVisible={showSidebar} />
<div style={{ display: "flex", flexGrow: 1 }}>
<Sidebar show={showSidebar} setShowSidebar={setShowSidebar}></Sidebar>
<div className={"main-content" + (showSidebar ? " sidebar-shown" : "")} style={{ flexGrow: 1, backgroundColor: "#EEE", maxWidth: "100%" }}>
<div className={"main-content" + (showSidebar ? " sidebar-shown" : "")} style={{ flexGrow: 1, backgroundColor: "#EEE", maxWidth: "100%", overflowX: "hidden" }}>
<Routes>
<Route path={routes.dashboard()} element={<PageDashboard />} />
<Route path={routes.importCsv()} element={<PageImportTransactionsCsv />} />
Expand Down
28 changes: 15 additions & 13 deletions frontend/src/components/common/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,21 @@ export default function Table<T> (props: Props<T>): React.ReactElement {
}, [props.items]);

return <>
<table className="table is-fullwidth is-hoverable" style={{ marginBottom: 0 }}>
<thead>
{props.headings}
</thead>
<tfoot>
{props.headings}
</tfoot>
<tbody>
{paginatedItems.map(item =>
props.renderItem(item.item, item.index)
)}
</tbody>
</table>
<div style={{ overflowX: "auto" }}>
<table className="table is-fullwidth is-hoverable" style={{ marginBottom: 0 }}>
<thead>
{props.headings}
</thead>
<tfoot>
{props.headings}
</tfoot>
<tbody>
{paginatedItems.map(item =>
props.renderItem(item.item, item.index)
)}
</tbody>
</table>
</div>
<Pagination goToPage={props.goToPage}
page={props.page}
pageSize={props.pageSize}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import Table from "../../../common/Table";
import Tooltip from "../../../common/Tooltip";
import { CsvCreateTransaction } from "../importModels";
import DuplicateIndicator from "./DuplicateIndicator";
import { useQuery } from "@tanstack/react-query";
import { useApi } from "../../../../lib/ApiClient";
import { MetaFieldValue } from "./MetaFieldValue";

interface Props {
transactions: CsvCreateTransaction[]
Expand All @@ -29,6 +32,12 @@ export default function CsvMappingTransactionTable (props: Props): React.ReactEl
}

const items = React.useMemo(() => props.transactions.filter(props.tableFilter), [props.tableFilter, props.transactions]);
const api = useApi();
const { data: metaFields } = useQuery({ queryKey: ["meta"], queryFn: api.Meta.list });

if (metaFields === undefined) {
return <p>{t("common.loading_please_wait")}</p>;
}

return <Table pageSize={20}
items={items}
Expand All @@ -40,6 +49,7 @@ export default function CsvMappingTransactionTable (props: Props): React.ReactEl
<th>{t("transaction.source")}</th>
<th>{t("transaction.destination")}</th>
<th>{t("common.category")}</th>
{metaFields.map(x => <th key={x.id}>{x.name}</th>)}
</tr>}
page={page}
goToPage={setPage}
Expand Down Expand Up @@ -70,6 +80,12 @@ export default function CsvMappingTransactionTable (props: Props): React.ReactEl
<td>
{transaction.category}
</td>
{metaFields.map(field => <td key={field.id}>
<MetaFieldValue
field={field}
value={transaction.metaFields.find(x => x.metaId === field.id)}
/>
</td>)}
</tr>}
/>;

Expand Down
154 changes: 133 additions & 21 deletions frontend/src/components/transaction/import/MapCsvFields/MapMeta.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useApi } from "../../../../lib/ApiClient";
import { CsvImportProfile, MetaParseOptions, ParseOptions } from "../../../../models/csvImportProfile";
import { CsvImportProfile, MetaParseOptions, MetaParseOptionsNumber, MetaParseOptionsText, ParseOptions } from "../../../../models/csvImportProfile";
import Card from "../../../common/Card";
import Table from "../../../common/Table";
import { CsvCreateTransaction } from "../importModels";
Expand All @@ -11,6 +11,7 @@ import InputSelect from "../../../input/InputSelect";
import { InputParseOptionsModal } from "../../../input/InputParsingOptions";
import { DefaultParseOptions } from "../../../pages/transaction/PageImportTransactionsCsv";
import { getValue, parseMetaValue } from "./parseTransactionsCsv";
import InputText from "../../../input/InputText";

interface Props {
options: CsvImportProfile
Expand Down Expand Up @@ -74,8 +75,23 @@ function MapMetaField (props: MapMetaFieldProps): React.ReactElement {
case FieldValueType.TextLine:
case FieldValueType.TextLong:
return <MapMetaTextInput
field={props.field}
options={fieldParseOptions}
field={props.field as any}
options={fieldParseOptions as MetaParseOptionsText}
disabled={props.disabled}
data={props.data}
setModal={props.setModal}
transactions={props.transactions}
onChange={(transactions, options) => props.onChange(transactions, {
...props.options,
metaParseOptions: [
...metaOptions.filter(x => x.metaId !== props.field.id),
options
]
})} />;
case FieldValueType.Number:
return <MapMetaNumberInput
field={props.field as any}
options={fieldParseOptions as MetaParseOptionsNumber}
disabled={props.disabled}
data={props.data}
setModal={props.setModal}
Expand All @@ -96,8 +112,8 @@ function MapMetaField (props: MapMetaFieldProps): React.ReactElement {
}

interface MapMetaTextInputProps {
field: MetaField
options?: MetaParseOptions
field: MetaField & { valueType: FieldValueType.TextLine | FieldValueType.TextLong }
options?: MetaParseOptionsText
disabled: boolean
data: any[]
transactions: CsvCreateTransaction[]
Expand All @@ -107,15 +123,12 @@ interface MapMetaTextInputProps {
function MapMetaTextInput (props: MapMetaTextInputProps): React.ReactElement {
const { t } = useTranslation();

let options = props.options;
if (options === undefined) {
options = {
column: null,
metaId: props.field.id,
type: props.field.valueType,
parseOptions: DefaultParseOptions
};
}
const options = props.options ?? {
column: null,
metaId: props.field.id,
type: props.field.valueType,
parseOptions: DefaultParseOptions
};

return <tr>
<td>{props.field.name}</td>
Expand All @@ -125,7 +138,7 @@ function MapMetaTextInput (props: MapMetaTextInputProps): React.ReactElement {
value={options.column}
placeholder={t("import.select_column")!}
disabled={props.disabled}
onChange={result => onChange(result, options!.parseOptions)}
onChange={result => onChange(result, options.parseOptions)}
items={[
{ key: "___NULL___", value: t("common.none") },
...Object.keys(props.data[0]).map(item => {
Expand All @@ -140,8 +153,8 @@ function MapMetaTextInput (props: MapMetaTextInputProps): React.ReactElement {
<button className="button is-primary"
disabled={props.disabled}
onClick={() => props.setModal(<InputParseOptionsModal
value={options!.parseOptions}
previewData={props.data.map(row => getValue(row, options!.column))}
value={options.parseOptions}
previewData={props.data.map(row => getValue(row, options.column))}
onChange={options => onChange(props.options!.column, options)}
close={() => props.setModal(null)}
closeOnChange={true} />
Expand All @@ -160,17 +173,17 @@ function MapMetaTextInput (props: MapMetaTextInputProps): React.ReactElement {
props.onChange(props.data.map((x, i) => ({
...props.transactions[i],
metaFields: [
...props.transactions[i].metaFields.filter(x => x.metaId !== options!.metaId)
...props.transactions[i].metaFields.filter(x => x.metaId !== options.metaId)
]
})), {
...options!,
...options,
column: null
});
return;
}

const newOptions = {
...options!,
...options,
column,
parseOptions
};
Expand All @@ -181,7 +194,106 @@ function MapMetaTextInput (props: MapMetaTextInputProps): React.ReactElement {
{
metaId: newOptions.metaId,
type: newOptions.type,
value: parseMetaValue(x, newOptions)
...parseMetaValue(x, newOptions)
}
]
})), newOptions);
}
}

interface MapMetaNumberInputProps {
field: MetaField & { valueType: FieldValueType.Number }
options?: MetaParseOptionsNumber
disabled: boolean
data: any[]
transactions: CsvCreateTransaction[]
setModal: (modal: React.ReactElement | null) => void
onChange: (transactions: CsvCreateTransaction[], options: MetaParseOptions) => void
}
function MapMetaNumberInput (props: MapMetaNumberInputProps): React.ReactElement {
const { t } = useTranslation();

const options = props.options ?? {
column: null,
metaId: props.field.id,
type: FieldValueType.Number,
parseOptions: DefaultParseOptions,
decimalSeparator: "."
};

return <tr>
<td>{props.field.name}</td>
<td>
<InputSelect label={t("import.column")!}
isFullwidth={true}
value={options.column}
placeholder={t("import.select_column")!}
disabled={props.disabled}
onChange={result => onChange(result, options.parseOptions, options.decimalSeparator)}
items={[
{ key: "___NULL___", value: t("common.none") },
...Object.keys(props.data[0]).map(item => {
return {
key: item,
value: item
};
})]}
addOnAfter={
options.column !== null
? <div className="control">
<button className="button is-primary"
disabled={props.disabled}
onClick={() => props.setModal(<InputParseOptionsModal
value={options.parseOptions}
previewData={props.data.map(row => getValue(row, options.column))}
onChange={newOptions => onChange(options.column, newOptions, options.decimalSeparator)}
close={() => props.setModal(null)}
closeOnChange={true} />
)}>
{t("import.parse_options")}
</button>
</div>
: undefined
} />
</td>
<td>
{options.column !== null &&
<InputText label={t("import.decimal_separator")!}
value={options.decimalSeparator}
disabled={props.disabled}
onChange={e => onChange(options.column, options.parseOptions, e.target.value)}
/>}
</td>
</tr>;

function onChange (column: string | null, parseOptions: ParseOptions, decimalSeparator: string): void {
if (column === "___NULL___") {
props.onChange(props.data.map((x, i) => ({
...props.transactions[i],
metaFields: [
...props.transactions[i].metaFields.filter(x => x.metaId !== options.metaId)
]
})), {
...options,
column: null
});
return;
}

const newOptions = {
...options,
column,
parseOptions,
decimalSeparator
};
props.onChange(props.data.map((x, i) => ({
...props.transactions[i],
metaFields: [
...props.transactions[i].metaFields.filter(x => x.metaId !== newOptions.metaId),
{
metaId: newOptions.metaId,
type: newOptions.type,
...parseMetaValue(x, newOptions)
}
]
})), newOptions);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as React from "react";
import { FieldValueType, MetaField } from "../../../../models/meta";
import Tooltip from "../../../common/Tooltip";
import { useTranslation } from "react-i18next";
import { formatNumberWithUser } from "../../../../lib/Utils";
import { useUser } from "../../../App";
import { CsvCreateTransactionMetaValue } from "../importModels";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
import Decimal from "decimal.js";

interface Props {
field: MetaField
value?: CsvCreateTransactionMetaValue
}

export function MetaFieldValue (props: Props): React.ReactElement {
const { t } = useTranslation();
const user = useUser();

if (props.value === undefined) {
return <p>-</p>;
}

switch (props.value.type) {
case FieldValueType.TextLine:
case FieldValueType.TextLong:
return <p>{props.value.value as string}</p>;
case FieldValueType.Number:
if (props.value.value !== "invalid") {
return <Tooltip content={t("import.amount_parsed_from", { raw_value: props.value.sourceText })}>
{formatNumberWithUser(props.value.value as Decimal, user)}
</Tooltip>;
} else {
return <Tooltip content={t("import.amount_parsed_from", { raw_value: props.value.sourceText })}>
<span className="icon has-text-danger">
<FontAwesomeIcon icon={faExclamationTriangle} />
</span> {t("import.could_not_parse_amount")}
</Tooltip>;
}
default:
return <p>-</p>;
}
}
Loading

0 comments on commit 5b0fc0c

Please sign in to comment.