Skip to content

Commit 53a6f6f

Browse files
authored
Merge pull request #6 from LaunchPlatform/txn-form-as-a-custom-form
Txn form as a custom form
2 parents 4eb7903 + 32d5fd3 commit 53a6f6f

18 files changed

+233
-68
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "beanhub-web-react",
3-
"version": "0.3.7",
3+
"version": "0.4.0",
44
"private": false,
55
"dependencies": {
66
"@testing-library/jest-dom": "^5.14.1",

src/CustomForm/Form.tsx

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import TextInput from "../Shared/TextInput";
77
import SubmitButton from "../Shared/SubmitButton";
88
import CurrencyInput from "../Shared/CurrencyInput";
99
import NumberInput from "../Shared/NumberInput";
10+
import PostingListContainer, {
11+
PostingRecord,
12+
} from "../TransactionForm/PostingListContainer";
13+
import MetaListContainer, {
14+
MetaRecord,
15+
} from "../TransactionForm/MetaListContainer";
16+
import HeaderLine from "../Shared/HeaderLine";
1017

1118
export enum FieldType {
1219
str = "str",
@@ -15,6 +22,9 @@ export enum FieldType {
1522
date = "date",
1623
currency = "currency",
1724
account = "account",
25+
postings = "postings",
26+
meta = "meta",
27+
header = "header",
1828
}
1929

2030
export interface BaseField {
@@ -52,7 +62,23 @@ export interface AccountField extends BaseField {
5262
readonly default?: string;
5363
}
5464

55-
export type Field = OtherField | CurrencyField | FileField | AccountField;
65+
export interface PostingsField extends BaseField {
66+
readonly type: FieldType.postings;
67+
readonly default?: Array<PostingRecord>;
68+
}
69+
70+
export interface MetaField extends BaseField {
71+
readonly type: FieldType.postings;
72+
readonly default?: Array<MetaRecord>;
73+
}
74+
75+
export type Field =
76+
| OtherField
77+
| CurrencyField
78+
| FileField
79+
| AccountField
80+
| PostingsField
81+
| MetaField;
5682

5783
export interface Props {
5884
readonly action?: string;
@@ -62,6 +88,7 @@ export interface Props {
6288
readonly files: Array<string>;
6389
readonly currencies: Array<string>;
6490
readonly accounts: Array<string>;
91+
readonly accountCurrencies: Record<string, Array<string>>;
6592
readonly defaultDate: string;
6693
readonly errors?: Array<string>;
6794
readonly submit?: string;
@@ -72,6 +99,7 @@ interface FieldProps {
7299
readonly files: Array<string>;
73100
readonly currencies: Array<string>;
74101
readonly accounts: Array<string>;
102+
readonly accountCurrencies: Record<string, Array<string>>;
75103
readonly defaultDate: string;
76104
}
77105

@@ -81,6 +109,7 @@ const FormField: FunctionComponent<FieldProps> = ({
81109
files,
82110
accounts,
83111
defaultDate,
112+
accountCurrencies,
84113
}: FieldProps) => {
85114
let initialValue = field.default;
86115
if (window.history.state?.[field.name] !== undefined) {
@@ -158,7 +187,7 @@ const FormField: FunctionComponent<FieldProps> = ({
158187
label={displayName}
159188
name={field.name}
160189
currencies={currencies}
161-
initialValue={initialValue}
190+
initialValue={initialValue as string}
162191
error={field.error}
163192
multiple={field.multiple}
164193
creatable={field.creatable}
@@ -216,6 +245,31 @@ const FormField: FunctionComponent<FieldProps> = ({
216245
}}
217246
/>
218247
);
248+
case FieldType.postings:
249+
return (
250+
<PostingListContainer
251+
initialPostings={initialValue as Array<PostingRecord>}
252+
name={field.name}
253+
accounts={accounts}
254+
accountCurrencies={accountCurrencies}
255+
defaultCurrencies={currencies}
256+
required={field.required}
257+
error={field.error}
258+
// TODO: handle on change and the history
259+
/>
260+
);
261+
case FieldType.meta:
262+
return (
263+
<MetaListContainer
264+
initialMeta={initialValue as Array<MetaRecord>}
265+
name={field.name}
266+
required={field.required}
267+
error={field.error}
268+
// TODO: handle on change and the history
269+
/>
270+
);
271+
case FieldType.header:
272+
return <HeaderLine title={displayName} />;
219273
}
220274
};
221275

@@ -227,6 +281,7 @@ const Form: FunctionComponent<Props> = ({
227281
files,
228282
currencies,
229283
accounts,
284+
accountCurrencies,
230285
defaultDate,
231286
errors,
232287
submit,
@@ -240,6 +295,7 @@ const Form: FunctionComponent<Props> = ({
240295
currencies={currencies}
241296
files={files}
242297
accounts={accounts}
298+
accountCurrencies={accountCurrencies}
243299
defaultDate={defaultDate}
244300
/>
245301
))}

src/Shared/HeaderLine.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React, { FunctionComponent, PropsWithChildren } from "react";
2+
3+
export type Props = PropsWithChildren<{
4+
readonly title: string;
5+
}>;
6+
7+
const HeaderLine: FunctionComponent<Props> = ({ title, children }: Props) => (
8+
<div className="panel-tag">{title}</div>
9+
);
10+
export default HeaderLine;

src/TransactionForm/Form.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,17 @@ const Form: FunctionComponent<Props> = ({
137137
}}
138138
/>
139139
<PostingListContainer
140+
name={`${inputPrefix}postings`}
140141
initialPostings={initialPostings}
141142
accounts={accounts}
142143
accountCurrencies={accountCurrencies}
143144
defaultCurrencies={defaultCurrencies}
145+
required
146+
/>
147+
<MetaListContainer
148+
name={`${inputPrefix}metadata`}
149+
initialMeta={initialMeta}
144150
/>
145-
<MetaListContainer initialMeta={initialMeta} />
146151
{hiddenFields !== undefined
147152
? Object.entries(hiddenFields).map(([key, value]) => (
148153
<input type="hidden" name={key} value={value} />

src/TransactionForm/MetaInput.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { FunctionComponent, useContext } from "react";
2-
import { InputPrefixContext } from "./context";
1+
import { FunctionComponent } from "react";
32

43
export interface Props {
54
readonly metaKey?: string;
@@ -10,7 +9,7 @@ export interface Props {
109
readonly defaultMetaValue?: string;
1110
readonly valueReadonly?: boolean;
1211
readonly metaValueError?: string;
13-
readonly index: number;
12+
readonly name: string;
1413
readonly onKeyChange?: (key: string) => void;
1514
readonly onValueChange?: (key: string) => void;
1615
readonly onDelete?: () => void;
@@ -25,15 +24,14 @@ const MetaInput: FunctionComponent<Props> = ({
2524
defaultMetaValue,
2625
valueReadonly,
2726
metaValueError,
28-
index,
27+
name,
2928
onKeyChange,
3029
onValueChange,
3130
onDelete,
3231
}: Props) => {
3332
const isInvalid = [metaKeyError, metaValueError].some(
3433
(value) => value !== undefined
3534
);
36-
const inputPrefixContext = useContext(InputPrefixContext);
3735
return (
3836
<div className="input-group">
3937
<input
@@ -43,7 +41,7 @@ const MetaInput: FunctionComponent<Props> = ({
4341
"form-control" + (metaKeyError !== undefined ? " is-invalid" : "")
4442
}
4543
placeholder="Key"
46-
name={`${inputPrefixContext}metadata-${index}-key`}
44+
name={`${name}-key`}
4745
value={metaKey}
4846
defaultValue={defaultMetaKey}
4947
readOnly={keyReadonly}
@@ -60,7 +58,7 @@ const MetaInput: FunctionComponent<Props> = ({
6058
"form-control" + (metaValueError !== undefined ? " is-invalid" : "")
6159
}
6260
placeholder="Value"
63-
name={`${inputPrefixContext}metadata-${index}-value`}
61+
name={`${name}-value`}
6462
value={metaValue}
6563
defaultValue={defaultMetaValue}
6664
readOnly={valueReadonly}

src/TransactionForm/MetaInputContainer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import React, { FunctionComponent, useState } from "react";
22
import MetaInput from "./MetaInput";
33

44
export interface Props {
5-
readonly index: number;
65
readonly metaKey?: string;
76
readonly metaKeyError?: string;
87
readonly metaKeyReadonly?: boolean;
98
readonly metaValue?: string;
109
readonly metaValueError?: string;
1110
readonly metaValueReadonly?: boolean;
11+
readonly name: string;
1212
readonly onKeyChange?: (value: string) => void;
1313
readonly onValueChange?: (value: string) => void;
1414
readonly onDelete?: () => void;
@@ -21,7 +21,7 @@ const MetaInputContainer: FunctionComponent<Props> = ({
2121
metaValue,
2222
metaValueError,
2323
metaValueReadonly,
24-
index,
24+
name,
2525
onKeyChange,
2626
onValueChange,
2727
onDelete,
@@ -30,7 +30,7 @@ const MetaInputContainer: FunctionComponent<Props> = ({
3030
const [metaValueValue, setMetaValue] = useState<string>(metaValue ?? "");
3131
return (
3232
<MetaInput
33-
index={index}
33+
name={name}
3434
onDelete={onDelete}
3535
metaKey={metaKeyValue}
3636
metaKeyError={metaKeyError}

src/TransactionForm/MetaListContainer.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,16 @@ interface MetaRecordState {
2424

2525
export interface Props {
2626
readonly initialMeta?: Array<MetaRecord>;
27+
readonly required?: boolean;
28+
readonly error?: string;
29+
readonly name: string;
2730
}
2831

2932
const MetaListContainer: FunctionComponent<Props> = ({
3033
initialMeta,
34+
required,
35+
error,
36+
name,
3137
}: Props) => {
3238
let filledInitialMeta = initialMeta;
3339
if (
@@ -78,11 +84,11 @@ const MetaListContainer: FunctionComponent<Props> = ({
7884
initialState
7985
);
8086
return (
81-
<FormRow title="Metadata">
87+
<FormRow title="Metadata" required={required ?? false}>
8288
{metaState.map((metaItem, index) => (
8389
<MetaInputContainer
8490
key={metaItem.key}
85-
index={index}
91+
name={`${name}-${index}`}
8692
metaKey={metaItem.metaKey}
8793
metaKeyError={metaItem.metaKeyError}
8894
metaKeyReadonly={metaItem.metaKeyReadonly}
@@ -174,6 +180,12 @@ const MetaListContainer: FunctionComponent<Props> = ({
174180
}}
175181
/>
176182
))}
183+
{error !== undefined ? (
184+
<div>
185+
<div className="is-invalid"></div>
186+
<div className="invalid-feedback">{error}</div>
187+
</div>
188+
) : null}
177189
</FormRow>
178190
);
179191
};

src/TransactionForm/PostingInput.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { FunctionComponent, KeyboardEvent, useContext } from "react";
1+
import { FunctionComponent, KeyboardEvent } from "react";
22
import PostingCandidateList, { MatchedText } from "./PostingCandidateList";
3-
import { InputPrefixContext } from "./context";
43

54
export enum PriceMode {
65
INACTIVE = "INACTIVE",
@@ -33,7 +32,7 @@ export interface Props {
3332
readonly priceCurrencyCandidates?: Array<Candidate>;
3433
readonly priceCurrencyCandidateIndex?: number;
3534
readonly priceCurrencyError?: string;
36-
readonly index: number;
35+
readonly name: string;
3736
readonly onAccountChange?: (value: string) => void;
3837
readonly onAccountKeyPress?: (event: KeyboardEvent<HTMLInputElement>) => void;
3938
readonly onAccountKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
@@ -81,7 +80,7 @@ const PostingInput: FunctionComponent<Props> = ({
8180
priceCurrencyCandidates,
8281
priceCurrencyCandidateIndex,
8382
priceCurrencyError,
84-
index,
83+
name,
8584
onAccountChange,
8685
onAccountKeyPress,
8786
onAccountKeyDown,
@@ -105,7 +104,6 @@ const PostingInput: FunctionComponent<Props> = ({
105104
const isInvalid = [accountError, unitNumberError, unitCurrencyError].some(
106105
(value) => value !== undefined
107106
);
108-
const inputPrefix = useContext(InputPrefixContext);
109107
const priceModeValue = priceMode ?? PriceMode.INACTIVE;
110108
return (
111109
<div className="input-group">
@@ -123,7 +121,7 @@ const PostingInput: FunctionComponent<Props> = ({
123121
"form-control" + (accountError !== undefined ? " is-invalid" : "")
124122
}
125123
placeholder="Account"
126-
name={`${inputPrefix}postings-${index}-account`}
124+
name={`${name}-account`}
127125
value={account}
128126
onChange={(event) => onAccountChange?.(event.target.value)}
129127
onKeyPress={(event) => onAccountKeyPress?.(event)}
@@ -167,7 +165,7 @@ const PostingInput: FunctionComponent<Props> = ({
167165
"form-control" + (unitNumberError !== undefined ? " is-invalid" : "")
168166
}
169167
placeholder="12.34"
170-
name={`${inputPrefix}postings-${index}-number`}
168+
name={`${name}-number`}
171169
value={unitNumber}
172170
onChange={(event) => onUnitNumberChange?.(event.target.value)}
173171
style={{
@@ -192,7 +190,7 @@ const PostingInput: FunctionComponent<Props> = ({
192190
(unitCurrencyError !== undefined ? " is-invalid" : "")
193191
}
194192
placeholder="USD"
195-
name={`${inputPrefix}postings-${index}-currency`}
193+
name={`${name}-currency`}
196194
value={unitCurrency}
197195
onChange={(event) => onUnitCurrencyChange?.(event.target.value)}
198196
onKeyPress={(event) => onUnitCurrencyKeyPress?.(event)}
@@ -249,7 +247,7 @@ const PostingInput: FunctionComponent<Props> = ({
249247
{[PriceMode.PRICE, PriceMode.TOTAL_PRICE].includes(priceModeValue) ? (
250248
<input
251249
type="hidden"
252-
name={`${inputPrefix}postings-${index}-price_mode`}
250+
name={`${name}-price_mode`}
253251
value={priceModeValue}
254252
/>
255253
) : null}
@@ -261,7 +259,7 @@ const PostingInput: FunctionComponent<Props> = ({
261259
(priceNumberError !== undefined ? " is-invalid" : "")
262260
}
263261
placeholder="12.34"
264-
name={`${inputPrefix}postings-${index}-price_number`}
262+
name={`${name}-price_number`}
265263
value={priceNumber}
266264
onChange={(event) => onPriceNumberChange?.(event.target.value)}
267265
disabled={priceModeValue === PriceMode.EXPANDED}
@@ -287,7 +285,7 @@ const PostingInput: FunctionComponent<Props> = ({
287285
(priceCurrencyError !== undefined ? " is-invalid" : "")
288286
}
289287
placeholder="USD"
290-
name={`${inputPrefix}postings-${index}-price_currency`}
288+
name={`${name}-price_currency`}
291289
value={priceCurrency}
292290
disabled={priceModeValue === PriceMode.EXPANDED}
293291
onChange={(event) => onPriceCurrencyChange?.(event.target.value)}

0 commit comments

Comments
 (0)