feat(webapp): redesign item form to match customer/vendor form layout
Redesign the item form UX to align with the customer and vendor form patterns: - Add vertical sticky tabs (Basic, Selling, Purchasing, Inventory) with smooth scroll - Split monolithic sections into discrete components (Basic, Selling, Purchasing, Inventory) - Move Active checkbox from floating actions into Basic section - Update floating actions to sticky bottom bar with Save + Save & New dropdown - Remove Cancel button from floating actions - Wrap form in centered max-width container (800px) - Clean up legacy item form SCSS Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,14 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import styled from 'styled-components';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import ItemFormFormik from './ItemFormFormik';
|
import ItemFormFormik from './ItemFormFormik';
|
||||||
|
|
||||||
import { useDashboardPageTitle } from '@/hooks/state';
|
import { useDashboardPageTitle } from '@/hooks/state';
|
||||||
import { useItemFormContext, ItemFormProvider } from './ItemFormProvider';
|
import { useItemFormContext, ItemFormProvider } from './ItemFormProvider';
|
||||||
import { DashboardInsider, DashboardCard } from '@/components';
|
import { DashboardInsider } from '@/components';
|
||||||
|
import { Box } from '@/components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item form dashboard title.
|
* Item form dashboard title.
|
||||||
@@ -39,9 +39,9 @@ function ItemFormPageLoading({ children }) {
|
|||||||
const { isFormLoading } = useItemFormContext();
|
const { isFormLoading } = useItemFormContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardItemFormPageInsider loading={isFormLoading} name={'item-form'}>
|
<DashboardInsider loading={isFormLoading}>
|
||||||
{children}
|
{children}
|
||||||
</DashboardItemFormPageInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,36 +59,18 @@ export default function ItemForm({ itemId }) {
|
|||||||
history.push('/items');
|
history.push('/items');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Handle cancel button click.
|
|
||||||
const handleFormCancel = () => {
|
|
||||||
history.goBack();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemFormProvider itemId={itemId}>
|
<ItemFormProvider itemId={itemId}>
|
||||||
<ItemFormDashboardTitle />
|
<ItemFormDashboardTitle />
|
||||||
|
|
||||||
<ItemFormPageLoading>
|
<ItemFormPageLoading>
|
||||||
<DashboardCard page>
|
<Box mx={'auto'} maxWidth={800}>
|
||||||
<ItemFormPageFormik
|
<ItemFormFormik
|
||||||
onSubmitSuccess={handleSubmitSuccess}
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
onCancel={handleFormCancel}
|
|
||||||
/>
|
/>
|
||||||
</DashboardCard>
|
</Box>
|
||||||
</ItemFormPageLoading>
|
</ItemFormPageLoading>
|
||||||
</ItemFormProvider>
|
</ItemFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DashboardItemFormPageInsider = styled(DashboardInsider)`
|
|
||||||
padding-bottom: 64px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ItemFormPageFormik = styled(ItemFormFormik)`
|
|
||||||
.page-form {
|
|
||||||
&__floating-actions {
|
|
||||||
margin-left: -40px;
|
|
||||||
margin-right: -40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|||||||
+69
-72
@@ -3,41 +3,31 @@ import React, { useEffect, useRef } from 'react';
|
|||||||
import {
|
import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
Classes,
|
|
||||||
Radio,
|
Radio,
|
||||||
Position,
|
Position,
|
||||||
MenuItem,
|
Checkbox,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { ErrorMessage, FastField } from 'formik';
|
import { ErrorMessage, FastField } from 'formik';
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
import {
|
import {
|
||||||
Hint,
|
Hint,
|
||||||
Col,
|
|
||||||
Row,
|
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
FormattedHTMLMessage,
|
FormattedHTMLMessage,
|
||||||
FFormGroup,
|
FFormGroup,
|
||||||
FSelect,
|
FSelect,
|
||||||
FInputGroup,
|
FInputGroup,
|
||||||
|
Box,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import { useItemFormContext } from './ItemFormProvider';
|
import { useItemFormContext } from './ItemFormProvider';
|
||||||
import { handleStringChange, inputIntent } from '@/utils';
|
import { handleStringChange, inputIntent } from '@/utils';
|
||||||
// import { categoriesFieldShouldUpdate } from './utils';
|
import { ItemFormSectionTitle } from './ItemFormSectionTitle';
|
||||||
|
|
||||||
/**
|
export function ItemFormBasicSection() {
|
||||||
* Item form primary section.
|
|
||||||
*/
|
|
||||||
export default function ItemFormPrimarySection() {
|
|
||||||
// Item form context.
|
|
||||||
const { isNewMode, item, itemsCategories } = useItemFormContext();
|
const { isNewMode, item, itemsCategories } = useItemFormContext();
|
||||||
|
|
||||||
const nameFieldRef = useRef(null);
|
const nameFieldRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Auto focus item name field once component mount.
|
|
||||||
if (nameFieldRef.current) {
|
if (nameFieldRef.current) {
|
||||||
nameFieldRef.current.focus();
|
nameFieldRef.current.focus();
|
||||||
}
|
}
|
||||||
@@ -45,17 +35,19 @@ export default function ItemFormPrimarySection() {
|
|||||||
|
|
||||||
const itemTypeHintContent = (
|
const itemTypeHintContent = (
|
||||||
<>
|
<>
|
||||||
<div class="mb1">
|
<div className="mb1">
|
||||||
<FormattedHTMLMessage id={'services_that_you_provide_to_customers'} />
|
<FormattedHTMLMessage id={'services_that_you_provide_to_customers'} />
|
||||||
</div>
|
</div>
|
||||||
<div class="mb1">
|
<div className="mb1">
|
||||||
<FormattedHTMLMessage id={'products_you_buy_and_or_sell'} />
|
<FormattedHTMLMessage id={'products_you_buy_and_or_sell'} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
<Box data-section-id="primary">
|
||||||
|
<ItemFormSectionTitle>Basic details</ItemFormSectionTitle>
|
||||||
|
|
||||||
{/*----------- Item type ----------*/}
|
{/*----------- Item type ----------*/}
|
||||||
<FastField name={'type'}>
|
<FastField name={'type'}>
|
||||||
{({ form, field: { value }, meta: { touched, error } }) => (
|
{({ form, field: { value }, meta: { touched, error } }) => (
|
||||||
@@ -91,61 +83,66 @@ export default function ItemFormPrimarySection() {
|
|||||||
)}
|
)}
|
||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
<Row>
|
{/*----------- Item name ----------*/}
|
||||||
<Col xs={7}>
|
<FFormGroup
|
||||||
{/*----------- Item name ----------*/}
|
name={'name'}
|
||||||
<FFormGroup
|
label={<T id={'item_name'} />}
|
||||||
name={'name'}
|
labelInfo={<FieldRequiredHint />}
|
||||||
label={<T id={'item_name'} />}
|
inline={true}
|
||||||
labelInfo={<FieldRequiredHint />}
|
fill
|
||||||
inline={true}
|
fastField
|
||||||
fastField
|
>
|
||||||
>
|
<FInputGroup
|
||||||
<FInputGroup
|
name={'name'}
|
||||||
name={'name'}
|
medium={true}
|
||||||
medium={true}
|
inputRef={(ref) => (nameFieldRef.current = ref)}
|
||||||
inputRef={(ref) => (nameFieldRef.current = ref)}
|
fastField
|
||||||
fastField
|
fill
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*----------- SKU ----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'code'}
|
||||||
|
label={<T id={'item_code'} />}
|
||||||
|
inline={true}
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'code'} medium={true} fastField fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*----------- Item category ----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'category_id'}
|
||||||
|
label={<T id={'category'} />}
|
||||||
|
inline={true}
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FSelect
|
||||||
|
name={'category_id'}
|
||||||
|
items={itemsCategories}
|
||||||
|
valueAccessor={'id'}
|
||||||
|
textAccessor={'name'}
|
||||||
|
placeholder={<T id={'select_category'} />}
|
||||||
|
popoverProps={{ minimal: true, captureDismiss: true }}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*----------- Active ----------*/}
|
||||||
|
<FastField name={'active'} type={'checkbox'}>
|
||||||
|
{({ field }) => (
|
||||||
|
<FormGroup inline={true} className={'form-group--active'}>
|
||||||
|
<Checkbox
|
||||||
|
inline={true}
|
||||||
|
label={<T id={'active'} />}
|
||||||
|
name={'active'}
|
||||||
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FormGroup>
|
||||||
|
)}
|
||||||
{/*----------- SKU ----------*/}
|
</FastField>
|
||||||
<FFormGroup
|
</Box>
|
||||||
name={'code'}
|
|
||||||
label={<T id={'item_code'} />}
|
|
||||||
inline={true}
|
|
||||||
fastField
|
|
||||||
>
|
|
||||||
<FInputGroup name={'code'} medium={true} fastField />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*----------- Item category ----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'category_id'}
|
|
||||||
label={<T id={'category'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FSelect
|
|
||||||
name={'category_id'}
|
|
||||||
items={itemsCategories}
|
|
||||||
valueAccessor={'id'}
|
|
||||||
textAccessor={'name'}
|
|
||||||
placeholder={<T id={'select_category'} />}
|
|
||||||
popoverProps={{ minimal: true, captureDismiss: true }}
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col xs={3}>
|
|
||||||
{/* <Dragzone
|
|
||||||
initialFiles={initialAttachmentFiles}
|
|
||||||
onDrop={handleDropFiles}
|
|
||||||
onDeleteFile={handleDeleteFile}
|
|
||||||
hint={'Attachments: Maxiumum size: 20MB'}
|
|
||||||
className={'mt2'}
|
|
||||||
/> */}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
|
||||||
import { useFormikContext, FastField, ErrorMessage } from 'formik';
|
|
||||||
import { FormGroup, Classes, Checkbox, ControlGroup } from '@blueprintjs/core';
|
|
||||||
import {
|
|
||||||
AccountsSelect,
|
|
||||||
MoneyInputGroup,
|
|
||||||
FMoneyInputGroup,
|
|
||||||
Col,
|
|
||||||
Row,
|
|
||||||
Hint,
|
|
||||||
InputPrependText,
|
|
||||||
FFormGroup,
|
|
||||||
FTextArea,
|
|
||||||
} from '@/components';
|
|
||||||
import { FormattedMessage as T } from '@/components';
|
|
||||||
|
|
||||||
import { useItemFormContext } from './ItemFormProvider';
|
|
||||||
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
|
||||||
import { ACCOUNT_PARENT_TYPE } from '@/constants/accountTypes';
|
|
||||||
import {
|
|
||||||
sellDescriptionFieldShouldUpdate,
|
|
||||||
sellAccountFieldShouldUpdate,
|
|
||||||
sellPriceFieldShouldUpdate,
|
|
||||||
costPriceFieldShouldUpdate,
|
|
||||||
costAccountFieldShouldUpdate,
|
|
||||||
purchaseDescFieldShouldUpdate,
|
|
||||||
taxRateFieldShouldUpdate,
|
|
||||||
} from './utils';
|
|
||||||
import { compose, inputIntent } from '@/utils';
|
|
||||||
import { TaxRatesSelect } from '@/components/TaxRates/TaxRatesSelect';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Item form body.
|
|
||||||
*/
|
|
||||||
function ItemFormBody({ organization: { base_currency } }) {
|
|
||||||
const { accounts, taxRates } = useItemFormContext();
|
|
||||||
const { values } = useFormikContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="page-form__section page-form__section--selling-cost">
|
|
||||||
<Row>
|
|
||||||
<Col xs={6}>
|
|
||||||
{/*------------- Purchasable checbox ------------- */}
|
|
||||||
<FastField name={'sellable'} type="checkbox">
|
|
||||||
{({ form, field }) => (
|
|
||||||
<FormGroup inline={true} className={'form-group--sellable'}>
|
|
||||||
<Checkbox
|
|
||||||
inline={true}
|
|
||||||
label={
|
|
||||||
<h3>
|
|
||||||
<T id={'i_sell_this_item'} />
|
|
||||||
</h3>
|
|
||||||
}
|
|
||||||
name={'sellable'}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/*------------- Selling price ------------- */}
|
|
||||||
<FFormGroup
|
|
||||||
name={'sell_price'}
|
|
||||||
label={<T id={'selling_price'} />}
|
|
||||||
inline
|
|
||||||
fastField
|
|
||||||
>
|
|
||||||
<ControlGroup>
|
|
||||||
<InputPrependText text={base_currency} />
|
|
||||||
<FMoneyInputGroup
|
|
||||||
name={'sell_price'}
|
|
||||||
shouldUpdate={sellPriceFieldShouldUpdate}
|
|
||||||
sellable={values.sellable}
|
|
||||||
inputGroupProps={{ fill: true }}
|
|
||||||
disabled={!values.sellable}
|
|
||||||
fastField
|
|
||||||
/>
|
|
||||||
</ControlGroup>
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------- Selling account ------------- */}
|
|
||||||
<FFormGroup
|
|
||||||
label={<T id={'account'} />}
|
|
||||||
name={'sell_account_id'}
|
|
||||||
labelInfo={
|
|
||||||
<Hint content={<T id={'item.field.sell_account.hint'} />} />
|
|
||||||
}
|
|
||||||
inline={true}
|
|
||||||
items={accounts}
|
|
||||||
sellable={values.sellable}
|
|
||||||
shouldUpdate={sellAccountFieldShouldUpdate}
|
|
||||||
fastField={true}
|
|
||||||
>
|
|
||||||
<AccountsSelect
|
|
||||||
name={'sell_account_id'}
|
|
||||||
items={accounts}
|
|
||||||
placeholder={<T id={'select_account'} />}
|
|
||||||
disabled={!values.sellable}
|
|
||||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
|
|
||||||
fill={true}
|
|
||||||
allowCreate={true}
|
|
||||||
fastField={true}
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------- Sell Tax Rate ------------- */}
|
|
||||||
<FFormGroup
|
|
||||||
name={'sell_tax_rate_id'}
|
|
||||||
label={'Tax Rate'}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<TaxRatesSelect
|
|
||||||
name={'sell_tax_rate_id'}
|
|
||||||
items={taxRates}
|
|
||||||
allowCreate
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
<FFormGroup
|
|
||||||
name={'sell_description'}
|
|
||||||
label={<T id={'description'} />}
|
|
||||||
inline={true}
|
|
||||||
sellable={values.sellable}
|
|
||||||
shouldUpdate={sellDescriptionFieldShouldUpdate}
|
|
||||||
fastField
|
|
||||||
>
|
|
||||||
<FTextArea
|
|
||||||
name={'sell_description'}
|
|
||||||
growVertically={true}
|
|
||||||
height={280}
|
|
||||||
disabled={!values.sellable}
|
|
||||||
fill
|
|
||||||
fastField
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col xs={6}>
|
|
||||||
{/*------------- Sellable checkbox ------------- */}
|
|
||||||
<FastField name={'purchasable'} type={'checkbox'}>
|
|
||||||
{({ field }) => (
|
|
||||||
<FormGroup inline={true} className={'form-group--purchasable'}>
|
|
||||||
<Checkbox
|
|
||||||
inline={true}
|
|
||||||
label={
|
|
||||||
<h3>
|
|
||||||
<T id={'i_purchase_this_item'} />
|
|
||||||
</h3>
|
|
||||||
}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/*------------- Cost price ------------- */}
|
|
||||||
<FFormGroup
|
|
||||||
name={'cost_price'}
|
|
||||||
label={<T id={'cost_price'} />}
|
|
||||||
inline
|
|
||||||
fastField
|
|
||||||
>
|
|
||||||
<ControlGroup>
|
|
||||||
<InputPrependText text={base_currency} />
|
|
||||||
|
|
||||||
<FMoneyInputGroup
|
|
||||||
name={'cost_price'}
|
|
||||||
shouldUpdate={costPriceFieldShouldUpdate}
|
|
||||||
purchasable={values.purchasable}
|
|
||||||
inputGroupProps={{ medium: true }}
|
|
||||||
disabled={!values.purchasable}
|
|
||||||
fastField
|
|
||||||
/>
|
|
||||||
</ControlGroup>
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------- Cost account ------------- */}
|
|
||||||
<FFormGroup
|
|
||||||
name={'cost_account_id'}
|
|
||||||
purchasable={values.purchasable}
|
|
||||||
items={accounts}
|
|
||||||
shouldUpdate={costAccountFieldShouldUpdate}
|
|
||||||
label={<T id={'account'} />}
|
|
||||||
labelInfo={
|
|
||||||
<Hint content={<T id={'item.field.cost_account.hint'} />} />
|
|
||||||
}
|
|
||||||
inline={true}
|
|
||||||
fastField={true}
|
|
||||||
>
|
|
||||||
<AccountsSelect
|
|
||||||
name={'cost_account_id'}
|
|
||||||
items={accounts}
|
|
||||||
placeholder={<T id={'select_account'} />}
|
|
||||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
|
|
||||||
popoverFill={true}
|
|
||||||
allowCreate={true}
|
|
||||||
fastField={true}
|
|
||||||
disabled={!values.purchasable}
|
|
||||||
purchasable={values.purchasable}
|
|
||||||
shouldUpdate={costAccountFieldShouldUpdate}
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------- Purchase Tax Rate ------------- */}
|
|
||||||
<FFormGroup
|
|
||||||
name={'purchase_tax_rate_id'}
|
|
||||||
label={'Tax Rate'}
|
|
||||||
inline={true}
|
|
||||||
fastField={true}
|
|
||||||
shouldUpdateDeps={{ taxRates }}
|
|
||||||
shouldUpdate={taxRateFieldShouldUpdate}
|
|
||||||
>
|
|
||||||
<TaxRatesSelect
|
|
||||||
name={'purchase_tax_rate_id'}
|
|
||||||
items={taxRates}
|
|
||||||
allowCreate={true}
|
|
||||||
fastField={true}
|
|
||||||
shouldUpdateDeps={{ taxRates }}
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
<FFormGroup
|
|
||||||
name={'purchase_description'}
|
|
||||||
label={<T id={'description'} />}
|
|
||||||
className={'form-group--purchase-description'}
|
|
||||||
helperText={<ErrorMessage name={'description'} />}
|
|
||||||
inline={true}
|
|
||||||
purchasable={values.purchasable}
|
|
||||||
shouldUpdate={purchaseDescFieldShouldUpdate}
|
|
||||||
>
|
|
||||||
<FTextArea
|
|
||||||
name={'purchase_description'}
|
|
||||||
growVertically={true}
|
|
||||||
height={280}
|
|
||||||
disabled={!values.purchasable}
|
|
||||||
fill
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(withCurrentOrganization())(ItemFormBody);
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { Tab, Tabs } from "@blueprintjs/core";
|
||||||
|
import { Card, Group } from "@/components";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { ItemFormFloatingActions } from "./ItemFormFloatingActions";
|
||||||
|
import { ItemFormSections } from "./ItemFormFields";
|
||||||
|
|
||||||
|
export function ItemFormContent() {
|
||||||
|
const [selectedTabId, setSelectedTabId] = useState('primary');
|
||||||
|
|
||||||
|
const handleTabChange = (tabId) => {
|
||||||
|
const sectionId = String(tabId);
|
||||||
|
setSelectedTabId(sectionId);
|
||||||
|
|
||||||
|
const section = document.querySelector(
|
||||||
|
`[data-section-id="${sectionId}"]`,
|
||||||
|
);
|
||||||
|
if (section) {
|
||||||
|
section.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={css`padding-bottom: 0 !important;`}>
|
||||||
|
<Group verticalAlign={'top'} alignItems={'flex-start'} flexWrap={'nowrap'}>
|
||||||
|
<Tabs
|
||||||
|
selectedTabId={selectedTabId}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
className={css`position: sticky; top: 20px;`}
|
||||||
|
vertical
|
||||||
|
>
|
||||||
|
<Tab id={'primary'} title={'Basic'} />
|
||||||
|
<Tab id={'selling'} title={'Selling'} />
|
||||||
|
<Tab id={'purchasing'} title={'Purchasing'} />
|
||||||
|
<Tab id={'inventory'} title={'Inventory'} />
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<ItemFormSections />
|
||||||
|
</Group>
|
||||||
|
<ItemFormFloatingActions />
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { Divider } from '@blueprintjs/core';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { Box } from '@/components';
|
||||||
|
|
||||||
|
import { ItemFormBasicSection } from './ItemFormBasicSection';
|
||||||
|
import { ItemFormSellingSection } from './ItemFormSellingSection';
|
||||||
|
import { ItemFormPurchasingSection } from './ItemFormPurchasingSection';
|
||||||
|
import { ItemFormInventorySection } from './ItemFormInventorySection';
|
||||||
|
|
||||||
|
const itemFormSectionDividerClass = css`
|
||||||
|
margin: 20px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function ItemFormSections() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<ItemFormBasicSection />
|
||||||
|
<Divider className={itemFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<ItemFormSellingSection />
|
||||||
|
<Divider className={itemFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<ItemFormPurchasingSection />
|
||||||
|
<Divider className={itemFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<ItemFormInventorySection />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,89 +1,83 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import {
|
||||||
|
Intent,
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
Popover,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
Position,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import classNames from 'classnames';
|
import { useFormikContext } from 'formik';
|
||||||
import { Button, Intent, FormGroup, Checkbox } from '@blueprintjs/core';
|
|
||||||
import { FastField, useFormikContext } from 'formik';
|
import { Group, Icon, FormattedMessage as T } from '@/components';
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
import { useItemFormContext } from './ItemFormProvider';
|
import { useItemFormContext } from './ItemFormProvider';
|
||||||
import { Group, FormattedMessage as T } from '@/components';
|
|
||||||
import { saveInvoke } from '@/utils';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item form floating actions.
|
* Item form floating actions bar.
|
||||||
*/
|
*/
|
||||||
export default function ItemFormFloatingActions({ onCancel }) {
|
export function ItemFormFloatingActions() {
|
||||||
// Item form context.
|
|
||||||
const { setSubmitPayload, isNewMode } = useItemFormContext();
|
|
||||||
|
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { isSubmitting, submitForm } = useFormikContext();
|
const { isSubmitting, submitForm } = useFormikContext();
|
||||||
|
|
||||||
// Handle cancel button click.
|
// Item form context.
|
||||||
const handleCancelBtnClick = (event) => {
|
const { isNewMode, setSubmitPayload } = useItemFormContext();
|
||||||
saveInvoke(onCancel, event);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle submit button click.
|
// Handle the submit button.
|
||||||
const handleSubmitBtnClick = (event) => {
|
const handleSubmitBtnClick = () => {
|
||||||
setSubmitPayload({ redirect: true });
|
setSubmitPayload({ redirect: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle submit & new button click.
|
// Handle the submit & new button click.
|
||||||
const handleSubmitAndNewBtnClick = (event) => {
|
const handleSubmitAndNewClick = () => {
|
||||||
setSubmitPayload({ redirect: false });
|
|
||||||
submitForm();
|
submitForm();
|
||||||
|
setSubmitPayload({ redirect: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<FloatingActionsGroup spacing={10}>
|
||||||
spacing={10}
|
<ButtonGroup>
|
||||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
{/* ----------- Save and New ----------- */}
|
||||||
>
|
<Button
|
||||||
<SaveButton
|
disabled={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
loading={isSubmitting}
|
||||||
disabled={isSubmitting}
|
intent={Intent.PRIMARY}
|
||||||
loading={isSubmitting}
|
type="submit"
|
||||||
onClick={handleSubmitBtnClick}
|
onClick={handleSubmitBtnClick}
|
||||||
type="submit"
|
text={!isNewMode ? <T id={'edit'} /> : <T id={'save'} />}
|
||||||
className={'btn--submit'}
|
/>
|
||||||
>
|
<Popover
|
||||||
{isNewMode ? <T id={'save'} /> : <T id={'edit'} />}
|
content={
|
||||||
</SaveButton>
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
<Button
|
text={<T id={'save_and_new'} />}
|
||||||
className={classNames('ml1', 'btn--submit-new')}
|
onClick={handleSubmitAndNewClick}
|
||||||
disabled={isSubmitting}
|
/>
|
||||||
onClick={handleSubmitAndNewBtnClick}
|
</Menu>
|
||||||
>
|
}
|
||||||
<T id={'save_new'} />
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
</Button>
|
position={Position.BOTTOM_RIGHT}
|
||||||
|
minimal
|
||||||
<Button
|
>
|
||||||
disabled={isSubmitting}
|
<Button
|
||||||
className={'ml1'}
|
disabled={isSubmitting}
|
||||||
onClick={handleCancelBtnClick}
|
intent={Intent.PRIMARY}
|
||||||
>
|
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
|
||||||
<T id={'close'} />
|
/>
|
||||||
</Button>
|
</Popover>
|
||||||
|
</ButtonGroup>
|
||||||
{/*----------- Active ----------*/}
|
</FloatingActionsGroup>
|
||||||
<FastField name={'active'} type={'checkbox'}>
|
|
||||||
{({ field }) => (
|
|
||||||
<FormGroup inline={true} className={'form-group--active'}>
|
|
||||||
<Checkbox
|
|
||||||
inline={true}
|
|
||||||
label={<T id={'active'} />}
|
|
||||||
name={'active'}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</Group>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SaveButton = styled(Button)`
|
const FloatingActionsGroup = styled(Group)`
|
||||||
min-width: 100px;
|
padding: 10px 0;
|
||||||
|
padding-left: 165px;
|
||||||
|
border-top: 1px solid #50555a;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: var(--color-card-background);
|
||||||
|
z-index: 1;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -4,15 +4,13 @@ import { Formik, Form } from 'formik';
|
|||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import '@/style/pages/Items/Form.scss';
|
import '@/style/pages/Items/Form.scss';
|
||||||
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
import ItemFormBody from './ItemFormBody';
|
import { ItemFormContent } from './ItemFormContent';
|
||||||
import ItemFormPrimarySection from './ItemFormPrimarySection';
|
|
||||||
import ItemFormFloatingActions from './ItemFormFloatingActions';
|
|
||||||
import ItemFormInventorySection from './ItemFormInventorySection';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
transformSubmitRequestErrors,
|
transformSubmitRequestErrors,
|
||||||
@@ -37,7 +35,6 @@ export default function ItemFormFormik({
|
|||||||
const {
|
const {
|
||||||
itemId,
|
itemId,
|
||||||
item,
|
item,
|
||||||
accounts,
|
|
||||||
createItemMutate,
|
createItemMutate,
|
||||||
editItemMutate,
|
editItemMutate,
|
||||||
submitPayload,
|
submitPayload,
|
||||||
@@ -91,7 +88,7 @@ export default function ItemFormFormik({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_ITEM, className)}
|
className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_ITEM, className)}
|
||||||
>
|
>
|
||||||
<Formik
|
<Formik
|
||||||
enableReinitialize={true}
|
enableReinitialize={true}
|
||||||
@@ -100,15 +97,24 @@ export default function ItemFormFormik({
|
|||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<div class={classNames(CLASSES.PAGE_FORM_BODY)}>
|
<ItemFormFields>
|
||||||
<ItemFormPrimarySection />
|
<ItemFormContent />
|
||||||
<ItemFormBody accounts={accounts} />
|
</ItemFormFields>
|
||||||
<ItemFormInventorySection accounts={accounts} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ItemFormFloatingActions onCancel={onCancel} />
|
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ItemFormFields = styled.div`
|
||||||
|
.bp4-form-content,
|
||||||
|
.bp6-form-content {
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
.bp4-form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.bp4-form-group.bp4-inline label.bp4-label {
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import {
|
|||||||
AccountsSelect,
|
AccountsSelect,
|
||||||
FFormGroup,
|
FFormGroup,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
Col,
|
Box,
|
||||||
Row,
|
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
|
|
||||||
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
||||||
@@ -13,43 +12,38 @@ import { accountsFieldShouldUpdate } from './utils';
|
|||||||
import { ACCOUNT_TYPE } from '@/constants/accountTypes';
|
import { ACCOUNT_TYPE } from '@/constants/accountTypes';
|
||||||
import { useItemFormContext } from './ItemFormProvider';
|
import { useItemFormContext } from './ItemFormProvider';
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
|
import { ItemFormSectionTitle } from './ItemFormSectionTitle';
|
||||||
|
|
||||||
/**
|
function ItemFormInventorySectionBase() {
|
||||||
* Item form inventory sections.
|
|
||||||
*/
|
|
||||||
function ItemFormInventorySection({ organization: { base_currency } }) {
|
|
||||||
const { accounts } = useItemFormContext();
|
const { accounts } = useItemFormContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="page-form__section page-form__section--inventory">
|
<Box data-section-id="inventory">
|
||||||
<h3>
|
<ItemFormSectionTitle>Inventory details</ItemFormSectionTitle>
|
||||||
<T id={'inventory_information'} />
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<Row>
|
{/*------------- Inventory Account ------------- */}
|
||||||
<Col xs={6}>
|
<FFormGroup
|
||||||
{/*------------- Inventory Account ------------- */}
|
label={<T id={'inventory_account'} />}
|
||||||
<FFormGroup
|
name={'inventory_account_id'}
|
||||||
label={<T id={'inventory_account'} />}
|
items={accounts}
|
||||||
name={'inventory_account_id'}
|
fastField={true}
|
||||||
items={accounts}
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
fastField={true}
|
inline={true}
|
||||||
shouldUpdate={accountsFieldShouldUpdate}
|
fill
|
||||||
inline={true}
|
>
|
||||||
>
|
<AccountsSelect
|
||||||
<AccountsSelect
|
name={'inventory_account_id'}
|
||||||
name={'inventory_account_id'}
|
items={accounts}
|
||||||
items={accounts}
|
placeholder={<T id={'select_account'} />}
|
||||||
placeholder={<T id={'select_account'} />}
|
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
|
||||||
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
|
fastField={true}
|
||||||
fastField={true}
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
shouldUpdate={accountsFieldShouldUpdate}
|
/>
|
||||||
/>
|
</FFormGroup>
|
||||||
</FFormGroup>
|
</Box>
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withCurrentOrganization())(ItemFormInventorySection);
|
export const ItemFormInventorySection = compose(withCurrentOrganization())(
|
||||||
|
ItemFormInventorySectionBase,
|
||||||
|
);
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { useFormikContext, FastField, ErrorMessage } from 'formik';
|
||||||
|
import { FormGroup, Checkbox, ControlGroup } from '@blueprintjs/core';
|
||||||
|
import {
|
||||||
|
AccountsSelect,
|
||||||
|
FMoneyInputGroup,
|
||||||
|
Hint,
|
||||||
|
InputPrependText,
|
||||||
|
FFormGroup,
|
||||||
|
FTextArea,
|
||||||
|
Box,
|
||||||
|
} from '@/components';
|
||||||
|
import { FormattedMessage as T } from '@/components';
|
||||||
|
|
||||||
|
import { useItemFormContext } from './ItemFormProvider';
|
||||||
|
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
||||||
|
import { ACCOUNT_PARENT_TYPE } from '@/constants/accountTypes';
|
||||||
|
import {
|
||||||
|
costPriceFieldShouldUpdate,
|
||||||
|
costAccountFieldShouldUpdate,
|
||||||
|
purchaseDescFieldShouldUpdate,
|
||||||
|
taxRateFieldShouldUpdate,
|
||||||
|
} from './utils';
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
import { TaxRatesSelect } from '@/components/TaxRates/TaxRatesSelect';
|
||||||
|
import { ItemFormSectionTitle } from './ItemFormSectionTitle';
|
||||||
|
|
||||||
|
function ItemFormPurchasingSectionBase({ organization: { base_currency } }) {
|
||||||
|
const { accounts, taxRates } = useItemFormContext();
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box data-section-id="purchasing">
|
||||||
|
<ItemFormSectionTitle>Purchasing details</ItemFormSectionTitle>
|
||||||
|
|
||||||
|
{/*------------- Purchasable checkbox ------------- */}
|
||||||
|
<FastField name={'purchasable'} type={'checkbox'}>
|
||||||
|
{({ field }) => (
|
||||||
|
<FormGroup inline={true} className={'form-group--purchasable'}>
|
||||||
|
<Checkbox
|
||||||
|
inline={true}
|
||||||
|
label={<T id={'i_purchase_this_item'} />}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
{/*------------- Cost price ------------- */}
|
||||||
|
<FFormGroup
|
||||||
|
name={'cost_price'}
|
||||||
|
label={<T id={'cost_price'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<ControlGroup fill>
|
||||||
|
<InputPrependText text={base_currency} />
|
||||||
|
<FMoneyInputGroup
|
||||||
|
name={'cost_price'}
|
||||||
|
shouldUpdate={costPriceFieldShouldUpdate}
|
||||||
|
purchasable={values.purchasable}
|
||||||
|
inputGroupProps={{ medium: true }}
|
||||||
|
disabled={!values.purchasable}
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------- Cost account ------------- */}
|
||||||
|
<FFormGroup
|
||||||
|
name={'cost_account_id'}
|
||||||
|
purchasable={values.purchasable}
|
||||||
|
items={accounts}
|
||||||
|
shouldUpdate={costAccountFieldShouldUpdate}
|
||||||
|
label={<T id={'account'} />}
|
||||||
|
labelInfo={
|
||||||
|
<Hint content={<T id={'item.field.cost_account.hint'} />} />
|
||||||
|
}
|
||||||
|
inline={true}
|
||||||
|
fill
|
||||||
|
fastField={true}
|
||||||
|
>
|
||||||
|
<AccountsSelect
|
||||||
|
name={'cost_account_id'}
|
||||||
|
items={accounts}
|
||||||
|
placeholder={<T id={'select_account'} />}
|
||||||
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
|
||||||
|
popoverFill={true}
|
||||||
|
allowCreate={true}
|
||||||
|
fastField={true}
|
||||||
|
disabled={!values.purchasable}
|
||||||
|
purchasable={values.purchasable}
|
||||||
|
shouldUpdate={costAccountFieldShouldUpdate}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------- Purchase Tax Rate ------------- */}
|
||||||
|
<FFormGroup
|
||||||
|
name={'purchase_tax_rate_id'}
|
||||||
|
label={'Tax Rate'}
|
||||||
|
inline={true}
|
||||||
|
fill
|
||||||
|
fastField={true}
|
||||||
|
shouldUpdateDeps={{ taxRates }}
|
||||||
|
shouldUpdate={taxRateFieldShouldUpdate}
|
||||||
|
>
|
||||||
|
<TaxRatesSelect
|
||||||
|
name={'purchase_tax_rate_id'}
|
||||||
|
items={taxRates}
|
||||||
|
allowCreate={true}
|
||||||
|
fastField={true}
|
||||||
|
shouldUpdateDeps={{ taxRates }}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'purchase_description'}
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
className={'form-group--purchase-description'}
|
||||||
|
helperText={<ErrorMessage name={'description'} />}
|
||||||
|
inline={true}
|
||||||
|
fill
|
||||||
|
purchasable={values.purchasable}
|
||||||
|
shouldUpdate={purchaseDescFieldShouldUpdate}
|
||||||
|
>
|
||||||
|
<FTextArea
|
||||||
|
name={'purchase_description'}
|
||||||
|
growVertically={true}
|
||||||
|
height={280}
|
||||||
|
disabled={!values.purchasable}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ItemFormPurchasingSection = compose(withCurrentOrganization())(
|
||||||
|
ItemFormPurchasingSectionBase,
|
||||||
|
);
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
const itemFormSectionTitleClass = css`
|
||||||
|
font-size: 14px;
|
||||||
|
color: #8f99a8;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
margin-top: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function ItemFormSectionTitle({ children }: { children: React.ReactNode | string }) {
|
||||||
|
return <h4 className={itemFormSectionTitleClass}>{children}</h4>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { useFormikContext, FastField } from 'formik';
|
||||||
|
import { FormGroup, Checkbox, ControlGroup } from '@blueprintjs/core';
|
||||||
|
import {
|
||||||
|
AccountsSelect,
|
||||||
|
FMoneyInputGroup,
|
||||||
|
Hint,
|
||||||
|
InputPrependText,
|
||||||
|
FFormGroup,
|
||||||
|
FTextArea,
|
||||||
|
Box,
|
||||||
|
} from '@/components';
|
||||||
|
import { FormattedMessage as T } from '@/components';
|
||||||
|
|
||||||
|
import { useItemFormContext } from './ItemFormProvider';
|
||||||
|
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
||||||
|
import { ACCOUNT_PARENT_TYPE } from '@/constants/accountTypes';
|
||||||
|
import {
|
||||||
|
sellDescriptionFieldShouldUpdate,
|
||||||
|
sellAccountFieldShouldUpdate,
|
||||||
|
sellPriceFieldShouldUpdate,
|
||||||
|
} from './utils';
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
import { TaxRatesSelect } from '@/components/TaxRates/TaxRatesSelect';
|
||||||
|
import { ItemFormSectionTitle } from './ItemFormSectionTitle';
|
||||||
|
|
||||||
|
function ItemFormSellingSectionBase({ organization: { base_currency } }) {
|
||||||
|
const { accounts, taxRates } = useItemFormContext();
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box data-section-id="selling">
|
||||||
|
<ItemFormSectionTitle>Selling details</ItemFormSectionTitle>
|
||||||
|
|
||||||
|
{/*------------- Sellable checkbox ------------- */}
|
||||||
|
<FastField name={'sellable'} type="checkbox">
|
||||||
|
{({ form, field }) => (
|
||||||
|
<FormGroup inline={true} className={'form-group--sellable'}>
|
||||||
|
<Checkbox
|
||||||
|
inline={true}
|
||||||
|
label={<T id={'i_sell_this_item'} />}
|
||||||
|
name={'sellable'}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
{/*------------- Selling price ------------- */}
|
||||||
|
<FFormGroup
|
||||||
|
name={'sell_price'}
|
||||||
|
label={<T id={'selling_price'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<ControlGroup fill>
|
||||||
|
<InputPrependText text={base_currency} />
|
||||||
|
<FMoneyInputGroup
|
||||||
|
name={'sell_price'}
|
||||||
|
shouldUpdate={sellPriceFieldShouldUpdate}
|
||||||
|
sellable={values.sellable}
|
||||||
|
inputGroupProps={{ fill: true }}
|
||||||
|
disabled={!values.sellable}
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------- Selling account ------------- */}
|
||||||
|
<FFormGroup
|
||||||
|
label={<T id={'account'} />}
|
||||||
|
name={'sell_account_id'}
|
||||||
|
labelInfo={
|
||||||
|
<Hint content={<T id={'item.field.sell_account.hint'} />} />
|
||||||
|
}
|
||||||
|
inline={true}
|
||||||
|
fill
|
||||||
|
items={accounts}
|
||||||
|
sellable={values.sellable}
|
||||||
|
shouldUpdate={sellAccountFieldShouldUpdate}
|
||||||
|
fastField={true}
|
||||||
|
>
|
||||||
|
<AccountsSelect
|
||||||
|
name={'sell_account_id'}
|
||||||
|
items={accounts}
|
||||||
|
placeholder={<T id={'select_account'} />}
|
||||||
|
disabled={!values.sellable}
|
||||||
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
|
||||||
|
fill={true}
|
||||||
|
allowCreate={true}
|
||||||
|
fastField={true}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------- Sell Tax Rate ------------- */}
|
||||||
|
<FFormGroup
|
||||||
|
name={'sell_tax_rate_id'}
|
||||||
|
label={'Tax Rate'}
|
||||||
|
inline={true}
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<TaxRatesSelect
|
||||||
|
name={'sell_tax_rate_id'}
|
||||||
|
items={taxRates}
|
||||||
|
allowCreate
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'sell_description'}
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
inline={true}
|
||||||
|
fill
|
||||||
|
sellable={values.sellable}
|
||||||
|
shouldUpdate={sellDescriptionFieldShouldUpdate}
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FTextArea
|
||||||
|
name={'sell_description'}
|
||||||
|
growVertically={true}
|
||||||
|
height={280}
|
||||||
|
disabled={!values.sellable}
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ItemFormSellingSection = compose(withCurrentOrganization())(
|
||||||
|
ItemFormSellingSectionBase,
|
||||||
|
);
|
||||||
@@ -1,91 +1,30 @@
|
|||||||
|
|
||||||
.page-form--item {
|
.page-form--item {
|
||||||
$self: '.page-form';
|
$self: '.page-form';
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
--x-header-border: #eaeaea;
|
.bp4-form-group {
|
||||||
--x-section-border: #eaeaea;
|
max-width: 500px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
|
||||||
.bp4-dark & {
|
&.bp4-inline {
|
||||||
--x-header-border: var(--color-dark-gray3);
|
.bp4-label {
|
||||||
--x-section-border: var(--color-dark-gray3);
|
min-width: 140px;
|
||||||
}
|
|
||||||
|
|
||||||
#{$self}__header {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
#{$self}__primary-section {
|
|
||||||
overflow: hidden;
|
|
||||||
padding-top: 5px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-bottom: 1px solid var(--x-header-border);
|
|
||||||
padding-bottom: 5px;
|
|
||||||
max-width: 1000px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#{$self}__body {
|
|
||||||
.bp4-form-group {
|
|
||||||
max-width: 500px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
|
|
||||||
&.bp4-inline {
|
|
||||||
.bp4-label {
|
|
||||||
min-width: 140px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bp4-form-content {
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.bp4-form-content {
|
||||||
h3 {
|
width: 100%;
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-control {
|
|
||||||
h3 {
|
|
||||||
display: inline;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group--sellable,
|
|
||||||
.form-group--purchasable {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group--purchase-description,
|
|
||||||
.form-group--sell-description{
|
|
||||||
|
|
||||||
textarea{
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#{$self}__section {
|
.form-group--sellable,
|
||||||
max-width: 850px;
|
.form-group--purchasable {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
.bp4-form-group {
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--selling-cost {
|
|
||||||
border-bottom: 1px solid var(--x-section-border);
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
padding-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#{$self}__floating-actions {
|
.form-group--purchase-description,
|
||||||
|
.form-group--sell-description {
|
||||||
.form-group--active {
|
textarea {
|
||||||
display: inline-block;
|
width: 100%;
|
||||||
margin: 0;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user