mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
feat: wip invoice customize
This commit is contained in:
@@ -8,7 +8,8 @@
|
|||||||
.colorPicker{
|
.colorPicker{
|
||||||
background-color: rgb(103, 114, 229);
|
background-color: rgb(103, 114, 229);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
height: 14px;
|
height: 16px;
|
||||||
width: 14px;
|
width: 16px;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,79 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import clsx from 'classnames';
|
||||||
import {
|
import {
|
||||||
|
IInputGroupProps,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
|
IPopoverProps,
|
||||||
Popover,
|
Popover,
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
Position,
|
Position,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { useState } from 'react';
|
|
||||||
import { HexColorPicker } from 'react-colorful';
|
import { HexColorPicker } from 'react-colorful';
|
||||||
|
import { useUncontrolled } from '@/hooks/useUncontrolled';
|
||||||
|
import { Box, BoxProps } from '@/components';
|
||||||
import styles from './ColorField.module.scss';
|
import styles from './ColorField.module.scss';
|
||||||
|
|
||||||
export function ColorField() {
|
export interface ColorFieldProps {
|
||||||
const [color, setColor] = useState('#aabbcc');
|
value?: string;
|
||||||
|
initialValue?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
popoverProps?: Partial<IPopoverProps>;
|
||||||
|
inputProps?: Partial<IInputGroupProps>;
|
||||||
|
pickerProps?: Partial<BoxProps>;
|
||||||
|
pickerWrapProps?: Partial<BoxProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ColorField({
|
||||||
|
value,
|
||||||
|
initialValue,
|
||||||
|
onChange,
|
||||||
|
popoverProps,
|
||||||
|
inputProps,
|
||||||
|
pickerWrapProps,
|
||||||
|
pickerProps,
|
||||||
|
}: ColorFieldProps) {
|
||||||
|
const [_value, handleChange] = useUncontrolled({
|
||||||
|
value,
|
||||||
|
initialValue,
|
||||||
|
onChange,
|
||||||
|
finalValue: '',
|
||||||
|
});
|
||||||
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
content={<HexColorPicker color={color} onChange={setColor} />}
|
content={<HexColorPicker color={_value} onChange={handleChange} />}
|
||||||
position={Position.BOTTOM}
|
position={Position.BOTTOM}
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
modifiers={{
|
modifiers={{
|
||||||
offset: { offset: '0, 4' },
|
offset: { offset: '0, 4' },
|
||||||
}}
|
}}
|
||||||
|
onClose={handleClose}
|
||||||
|
isOpen={isOpen}
|
||||||
minimal
|
minimal
|
||||||
|
{...popoverProps}
|
||||||
>
|
>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
leftElement={<div className={styles.colorPicker}></div>}
|
value={_value}
|
||||||
className={styles.field}
|
leftElement={
|
||||||
|
<Box
|
||||||
|
{...pickerWrapProps}
|
||||||
|
style={{ padding: 8, ...pickerWrapProps?.style }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
onClick={() => setIsOpen((oldValue) => !oldValue)}
|
||||||
|
style={{ backgroundColor: _value }}
|
||||||
|
className={clsx(styles.colorPicker, pickerProps?.className)}
|
||||||
|
{...pickerProps}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
{...inputProps}
|
||||||
|
className={clsx(styles.field, inputProps?.className)}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,64 @@
|
|||||||
import { ColorField } from './ColorField';
|
import React from 'react';
|
||||||
|
import { getIn, FieldConfig, FieldProps } from 'formik';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { Field } from '@blueprintjs-formik/core';
|
||||||
|
import { ColorField, ColorFieldProps } from './ColorField';
|
||||||
|
|
||||||
interface FColorFieldProps {
|
interface ColorFieldInputGroupProps
|
||||||
name: string;
|
extends Omit<FieldConfig, 'children' | 'component' | 'as' | 'value'>,
|
||||||
|
ColorFieldProps {}
|
||||||
|
|
||||||
|
export interface ColorFieldToInputProps
|
||||||
|
extends Omit<FieldProps, 'onChange'>,
|
||||||
|
ColorFieldProps {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms field props to input group props for ColorField.
|
||||||
|
* @param {ColorFieldToInputProps}
|
||||||
|
* @returns {ColorFieldProps}
|
||||||
|
*/
|
||||||
|
function fieldToColorFieldInputGroup({
|
||||||
|
field: { onBlur: onFieldBlur, onChange: onFieldChange, value, ...field },
|
||||||
|
form: { touched, errors, setFieldValue },
|
||||||
|
onChange,
|
||||||
|
...props
|
||||||
|
}: ColorFieldToInputProps): ColorFieldProps {
|
||||||
|
const fieldError = getIn(errors, field.name);
|
||||||
|
const showError = getIn(touched, field.name) && !!fieldError;
|
||||||
|
|
||||||
|
return {
|
||||||
|
inputProps: {
|
||||||
|
intent: showError ? Intent.DANGER : Intent.NONE,
|
||||||
|
},
|
||||||
|
value,
|
||||||
|
onChange:
|
||||||
|
onChange ??
|
||||||
|
function (value: string) {
|
||||||
|
setFieldValue(field.name, value);
|
||||||
|
},
|
||||||
|
...field,
|
||||||
|
...props,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FColorField({ name }: FColorFieldProps) {
|
/**
|
||||||
return <ColorField />;
|
* Transforms field props to input group props for ColorField.
|
||||||
|
* @param {ColorFieldToInputProps} props -
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
function ColorFieldToInputGroup({
|
||||||
|
...props
|
||||||
|
}: ColorFieldToInputProps): JSX.Element {
|
||||||
|
return <ColorField {...fieldToColorFieldInputGroup(props)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input group Blueprint component binded with Formik for ColorField.
|
||||||
|
* @param {ColorFieldInputGroupProps}
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
export function FColorInput({
|
||||||
|
...props
|
||||||
|
}: ColorFieldInputGroupProps): JSX.Element {
|
||||||
|
return <Field {...props} component={ColorFieldToInputGroup} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default function InvoiceCustomizeContent() {
|
|||||||
return (
|
return (
|
||||||
<Box className={Classes.DRAWER_BODY}>
|
<Box className={Classes.DRAWER_BODY}>
|
||||||
<InvoiceCustomizeForm>
|
<InvoiceCustomizeForm>
|
||||||
<Group spacing={0} align="flex-start">
|
<Group spacing={0} align="stretch">
|
||||||
<InvoiceCustomizeTabsControllerProvider>
|
<InvoiceCustomizeTabsControllerProvider>
|
||||||
<InvoiceCustomizeFields />
|
<InvoiceCustomizeFields />
|
||||||
<InvoiceCustomizePreview />
|
<InvoiceCustomizePreview />
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
|
|
||||||
import { Drawer, DrawerSuspense } from '@/components';
|
import { Drawer, DrawerSuspense } from '@/components';
|
||||||
import withDrawers from '@/containers/Drawer/withDrawers';
|
import withDrawers from '@/containers/Drawer/withDrawers';
|
||||||
|
|
||||||
@@ -25,6 +24,7 @@ function InvoiceCustomizeDrawerRoot({
|
|||||||
name={name}
|
name={name}
|
||||||
size={'100%'}
|
size={'100%'}
|
||||||
>
|
>
|
||||||
|
|
||||||
<DrawerSuspense>
|
<DrawerSuspense>
|
||||||
<InvoiceCustomizeContent />
|
<InvoiceCustomizeContent />
|
||||||
</DrawerSuspense>
|
</DrawerSuspense>
|
||||||
|
|||||||
@@ -5,3 +5,15 @@
|
|||||||
.mainFields{
|
.mainFields{
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
.fieldGroup {
|
||||||
|
|
||||||
|
:global .bp4-form-content{
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerActions{
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-top: 1px solid #d9d9d9;
|
||||||
|
flex-flow: row-reverse;
|
||||||
|
}
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
import { Box, Group } from '@/components';
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { Box, Group, Stack } from '@/components';
|
||||||
import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
|
import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
|
||||||
import { InvoiceCustomizeTabs } from './InvoiceCustomizeTabs';
|
import { InvoiceCustomizeTabs } from './InvoiceCustomizeTabs';
|
||||||
import styles from './InvoiceCustomizeFields.module.scss';
|
import styles from './InvoiceCustomizeFields.module.scss';
|
||||||
import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
|
import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
|
||||||
import { useInvoiceCustomizeTabsController } from './InvoiceCustomizeTabsController';
|
import { useInvoiceCustomizeTabsController } from './InvoiceCustomizeTabsController';
|
||||||
|
import { Button, Intent } from '@blueprintjs/core';
|
||||||
|
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||||
|
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
|
||||||
|
|
||||||
export function InvoiceCustomizeFields() {
|
export function InvoiceCustomizeFields() {
|
||||||
return (
|
return (
|
||||||
@@ -17,10 +24,40 @@ export function InvoiceCustomizeFields() {
|
|||||||
export function InvoiceCustomizeFieldsMain() {
|
export function InvoiceCustomizeFieldsMain() {
|
||||||
const { currentTabId } = useInvoiceCustomizeTabsController();
|
const { currentTabId } = useInvoiceCustomizeTabsController();
|
||||||
return (
|
return (
|
||||||
<Box className={styles.mainFields}>
|
<Stack spacing={0} className={styles.mainFields}>
|
||||||
<InvoiceCustomizeHeader label={'Customize'} />
|
<InvoiceCustomizeHeader label={'Customize'} />
|
||||||
|
|
||||||
{currentTabId === 'general' && <InvoiceCustomizeGeneralField />}
|
<Stack spacing={0} style={{ flex: '1 1 auto' }}>
|
||||||
</Box>
|
{currentTabId === 'general' && <InvoiceCustomizeGeneralField />}
|
||||||
|
{currentTabId === 'content' && <InvoiceCustomizeContentFields />}
|
||||||
|
|
||||||
|
<InvoiceCustomizeFooterActions />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function InvoiceCustomizeFooterActionsRoot({ closeDrawer }) {
|
||||||
|
const { name } = useDrawerContext();
|
||||||
|
const { submitForm } = useFormikContext();
|
||||||
|
|
||||||
|
const handleSubmitBtnClick = () => {
|
||||||
|
submitForm();
|
||||||
|
};
|
||||||
|
const handleCancelBtnClick = () => {
|
||||||
|
closeDrawer(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group spacing={10} className={styles.footerActions}>
|
||||||
|
<Button onClick={handleSubmitBtnClick} intent={Intent.PRIMARY}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCancelBtnClick}>Cancel</Button>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const InvoiceCustomizeFooterActions = R.compose(withDrawerActions)(
|
||||||
|
InvoiceCustomizeFooterActionsRoot,
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,16 +1,44 @@
|
|||||||
import { Box, FFormGroup } from '@/components';
|
import { Box, FFormGroup, FSwitch, Stack } from '@/components';
|
||||||
import { FColorField } from './FColorField';
|
import { FColorInput } from './FColorField';
|
||||||
|
import styles from './InvoiceCustomizeFields.module.scss';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
export function InvoiceCustomizeGeneralField() {
|
export function InvoiceCustomizeGeneralField() {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Stack style={{ padding: 20, flex: '1 1 auto' }}>
|
||||||
<FFormGroup name={'primaryColor'} label={'Primary Color'} inline>
|
<Stack>
|
||||||
<FColorField name={'primaryColor'} />
|
<h2>General Branding</h2>
|
||||||
|
<p className={Classes.TEXT_MUTED}>
|
||||||
|
Set your invoice details to be automatically applied every time
you
|
||||||
|
create a new invoice.
|
||||||
|
</p>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'primaryColor'}
|
||||||
|
label={'Primary Color'}
|
||||||
|
inline
|
||||||
|
className={styles.fieldGroup}
|
||||||
|
>
|
||||||
|
<FColorInput name={'primaryColor'} />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
<FFormGroup name={'secondaryColor'} label={'Secondary Color'} inline>
|
<FFormGroup
|
||||||
<FColorField name={'secondaryColor'} />
|
name={'secondaryColor'}
|
||||||
|
label={'Secondary Color'}
|
||||||
|
inline
|
||||||
|
className={styles.fieldGroup}
|
||||||
|
>
|
||||||
|
<FColorInput name={'secondaryColor'} />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
</Box>
|
|
||||||
|
<FFormGroup name={'showLogo'} label={'Logo'}>
|
||||||
|
<FSwitch
|
||||||
|
name={'showLogo'}
|
||||||
|
label={'Display company logo in the paper'}
|
||||||
|
large
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
padding: 5px 5px 5px 20px;
|
padding: 5px 5px 5px 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title{
|
.title{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Group, Icon } from '@/components';
|
import { Group, Icon } from '@/components';
|
||||||
import styles from './InvoiceCustomizeHeader.module.scss';
|
|
||||||
import { Button, Classes } from '@blueprintjs/core';
|
import { Button, Classes } from '@blueprintjs/core';
|
||||||
|
import styles from './InvoiceCustomizeHeader.module.scss';
|
||||||
|
|
||||||
interface InvoiceCustomizeHeaderProps {
|
interface InvoiceCustomizeHeaderProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
import { Stack } from '@/components';
|
import { Stack } from '@/components';
|
||||||
import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
|
import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
|
||||||
import { InvoiceCustomizePreviewContent } from './InvoiceCustomizePreviewContent';
|
import { InvoiceCustomizePreviewContent } from './InvoiceCustomizePreviewContent';
|
||||||
|
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||||
|
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
export function InvoiceCustomizePreview() {
|
function InvoiceCustomizePreviewRoot({ closeDrawer }) {
|
||||||
|
const { name } = useDrawerContext();
|
||||||
|
|
||||||
|
const handleCloseBtnClick = () => {
|
||||||
|
closeDrawer(name);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Stack spacing={0} style={{ borderLeft: '1px solid #D9D9D9' }}>
|
<Stack spacing={0} style={{ borderLeft: '1px solid #D9D9D9', height: '100vh' }}>
|
||||||
<InvoiceCustomizeHeader label={'Preview'} closeButton />
|
<InvoiceCustomizeHeader
|
||||||
|
label={'Preview'}
|
||||||
|
closeButton
|
||||||
|
onClose={handleCloseBtnClick}
|
||||||
|
/>
|
||||||
<InvoiceCustomizePreviewContent />
|
<InvoiceCustomizePreviewContent />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const InvoiceCustomizePreview = R.compose(withDrawerActions)(
|
||||||
|
InvoiceCustomizePreviewRoot,
|
||||||
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { PaperTemplate } from './PaperTemplate';
|
|||||||
|
|
||||||
export function InvoiceCustomizePreviewContent() {
|
export function InvoiceCustomizePreviewContent() {
|
||||||
return (
|
return (
|
||||||
<Box style={{ padding: 20, backgroundColor: '#F5F5F5' }}>
|
<Box style={{ padding: 20, backgroundColor: '#F5F5F5', overflow: 'auto' }}>
|
||||||
<PaperTemplate />
|
<PaperTemplate />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,8 +7,15 @@
|
|||||||
|
|
||||||
.content{
|
.content{
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
flex: 1;
|
||||||
|
border-right: 1px solid #E1E1E1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabsList{
|
.tabsList{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
:global .bp4-tab-list{
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Box } from '@/components';
|
import { Box, Stack } from '@/components';
|
||||||
import { Tab, Tabs } from '@blueprintjs/core';
|
import { Tab, Tabs } from '@blueprintjs/core';
|
||||||
import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
|
import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
|
||||||
import styles from './InvoiceCustomizeTabs.module.scss';
|
import styles from './InvoiceCustomizeTabs.module.scss';
|
||||||
@@ -14,16 +14,22 @@ export function InvoiceCustomizeTabs() {
|
|||||||
setCurrentTabId(value);
|
setCurrentTabId(value);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Box className={styles.root}>
|
<Stack spacing={0} className={styles.root}>
|
||||||
<InvoiceCustomizeHeader label={''} />
|
<InvoiceCustomizeHeader label={''} />
|
||||||
|
|
||||||
<Box className={styles.content}>
|
<Box className={styles.content}>
|
||||||
<Tabs vertical fill onChange={handleChange} className={styles.tabsList}>
|
<Tabs
|
||||||
|
vertical
|
||||||
|
fill
|
||||||
|
large
|
||||||
|
onChange={handleChange}
|
||||||
|
className={styles.tabsList}
|
||||||
|
>
|
||||||
<Tab id="general" title="General" />
|
<Tab id="general" title="General" />
|
||||||
<Tab id="content" title="Content" />
|
<Tab id="content" title="Content" />
|
||||||
<Tab id="total" title="Total" />
|
<Tab id="total" title="Total" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { FInputGroup, FSwitch, Group, Stack } from '@/components';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ key: 'dueAmount', label: 'Due Amount' },
|
||||||
|
{ key: 'billedTo', label: 'Billed To' },
|
||||||
|
{ key: 'balanceDue', label: 'Balance Due' },
|
||||||
|
{ key: 'termsConditions', label: 'Terms & Conditions' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function InvoiceCustomizeContentFields() {
|
||||||
|
return (
|
||||||
|
<Stack style={{ padding: 20, flex: '1 1 auto' }}>
|
||||||
|
<Stack>
|
||||||
|
<h2>General Branding</h2>
|
||||||
|
<p className={Classes.TEXT_MUTED}>
|
||||||
|
Set your invoice details to be automatically applied every time
you
|
||||||
|
create a new invoice.
|
||||||
|
</p>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<h1>Header</h1>
|
||||||
|
|
||||||
|
<Stack>
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<Group position={'apart'} key={index}>
|
||||||
|
<FSwitch name={`item.${item.key}.enabled`} label={item.label} />
|
||||||
|
<FInputGroup
|
||||||
|
name={'item.dueAmount.text'}
|
||||||
|
placeholder={item.label}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user