Compare commits

...

19 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
2e73a34fef Merge pull request #709 from bigcapitalhq/track-viewed-events
feat: Track account, invoice and item viewed events
2024-10-14 12:16:40 +02:00
Ahmed Bouhuolia
ea7f987fe3 feat: Track account, invoice and item viewed events 2024-10-14 12:15:21 +02:00
Ahmed Bouhuolia
d55503f0c7 Update README.md 2024-10-13 23:10:43 +02:00
Ahmed Bouhuolia
f59b8166b6 Merge pull request #708 from bigcapitalhq/add-customize-templates-btn-to-edit-forms
feat: Add customize templates button to edit forms
2024-10-13 21:15:08 +02:00
Ahmed Bouhuolia
2c735d7edf feat: Add customize templates button to edit forms 2024-10-13 21:14:18 +02:00
Ahmed Bouhuolia
5b5ab9fe1e Merge pull request #707 from bigcapitalhq/refactor-date-field
refactor: invoice, estimate, receipt, credit note and payment received date input fields
2024-10-13 18:03:36 +02:00
Ahmed Bouhuolia
28ac9b2d90 refactor: invoice, estimate, receipt, credit note and payment received date input fields 2024-10-13 18:01:43 +02:00
Ahmed Bouhuolia
3300a6a499 Merge pull request #705 from bigcapitalhq/fix-invoice-form-layout
fix: Invoice form layout
2024-10-13 17:23:54 +02:00
Ahmed Bouhuolia
152a22baa0 fix: Remove unused scss files 2024-10-13 17:22:14 +02:00
Ahmed Bouhuolia
e873198238 feat: typeing AppIntlProvider 2024-10-13 16:51:04 +02:00
Ahmed Bouhuolia
68a0db91ee feat: form header fields 2024-10-13 13:56:13 +02:00
Ahmed Bouhuolia
ddea7be24a feat: Add css utilities to Box, Stack and Group components 2024-10-13 01:06:17 +02:00
Ahmed Bouhuolia
b7b86bb0c5 fix: Invoice form layout 2024-10-12 20:49:56 +02:00
Ahmed Bouhuolia
817ef906dc Merge pull request #701 from bigcapitalhq/fix-disable-tabs-customize
fix: Disable tabs of the pdf customization if the first field not filed up
2024-10-12 12:52:01 +02:00
Ahmed Bouhuolia
863c7693fa fix: Disable tabs of the pdf customization if the first field not filled up 2024-10-10 16:41:21 +02:00
Ahmed Bouhuolia
cf78255220 Merge pull request #700 from bigcapitalhq/fix-company-logo-dimenstion-pdf-template
fix: Set max width/height to company logo of pdf templates
2024-10-08 10:12:03 +02:00
Ahmed Bouhuolia
f9aa6abdd7 fix: Set max width/height to company logo of pdf templates 2024-10-08 10:11:40 +02:00
Ahmed Bouhuolia
0a5e40a0d8 Merge pull request #699 from bigcapitalhq/fix-remove-logo-pdf-template
fix: Delete company logo from the PDF template
2024-10-08 08:21:03 +02:00
Ahmed Bouhuolia
4aa445fe35 fix: Delete company logo from the pdf template 2024-10-08 08:20:35 +02:00
92 changed files with 2494 additions and 1737 deletions

View File

@@ -12,6 +12,9 @@
<a href="https://github.com/bigcapitalhq/bigcapital/commits/develop">
<img src="https://img.shields.io/github/commit-activity/m/bigcapitalhq/bigcapital/develop" />
</a>
<a href="https://hub.docker.com/u/bigcapitalhq">
<img src="https://img.shields.io/docker/pulls/bigcapitalhq/webapp" />
</a>
<a href="https://discord.com/invite/c8nPBJafeb">
<img src="https://img.shields.io/discord/1066514716752625725?label=Discord" alt="" />
</a>

View File

@@ -30,17 +30,17 @@ block head
flex: 1 1 0%;
}
.#{prefix}-big-title {
font-size: 60px;
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap img {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
.#{prefix}-terms-list {
display: flex;

View File

@@ -30,17 +30,17 @@ block head
flex: 1 1 0%;
}
.#{prefix}-big-title {
font-size: 60px;
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap img {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
.#{prefix}-terms {
display: flex;

View File

@@ -30,17 +30,17 @@ block head
flex: 1 1 0%;
}
.#{prefix}-big-title {
font-size: 60px;
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap img {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
.#{prefix}-details {
display: flex;

View File

@@ -31,17 +31,17 @@ block head
flex: 1 1 0%;
}
.#{prefix}-big-title{
font-size: 60px;
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap img {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
.#{prefix}-terms-list{
display: flex;

View File

@@ -28,13 +28,13 @@ block head
flex: 1 1 0%;
}
.#{prefix}-logo-wrap img {
height: auto;
width: auto;
max-width: 400px;
max-height: 160px;
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
.#{prefix}-big-title {
font-size: 60px;
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;

View File

@@ -2,6 +2,7 @@ export const SALE_INVOICE_CREATED = 'Sale invoice created';
export const SALE_INVOICE_EDITED = 'Sale invoice edited';
export const SALE_INVOICE_DELETED = 'Sale invoice deleted';
export const SALE_INVOICE_MAIL_DELIVERED = 'Sale invoice mail delivered';
export const SALE_INVOICE_VIEWED = 'Sale invoice viewed';
export const SALE_ESTIMATE_CREATED = 'Sale estimate created';
export const SALE_ESTIMATE_EDITED = 'Sale estimate edited';
@@ -26,10 +27,12 @@ export const EXPENSE_DELETED = 'Expense deleted';
export const ACCOUNT_CREATED = 'Account created';
export const ACCOUNT_EDITED = 'Account Edited';
export const ACCOUNT_DELETED = 'Account deleted';
export const ACCOUNT_VIEWED = 'Account viewed';
export const ITEM_EVENT_CREATED = 'Item created';
export const ITEM_EVENT_EDITED = 'Item edited';
export const ITEM_EVENT_DELETED = 'Item deleted';
export const ITEM_EVENT_VIEWED = 'Item viewed';
export const AUTH_SIGNED_UP = 'Auth Signed-up';
export const AUTH_RESET_PASSWORD = 'Auth reset password';

View File

@@ -3,6 +3,8 @@ import I18nService from '@/services/I18n/I18nService';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { AccountTransformer } from './AccountTransform';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
@Service()
export class GetAccount {
@@ -15,6 +17,9 @@ export class GetAccount {
@Inject()
private transformer: TransformerInjectable;
@Inject()
private eventPublisher: EventPublisher;
/**
* Retrieve the given account details.
* @param {number} tenantId
@@ -39,6 +44,13 @@ export class GetAccount {
new AccountTransformer(),
{ accountsGraph }
);
const eventPayload = {
tenantId,
accountId,
};
// Triggers `onAccountViewed` event.
await this.eventPublisher.emitAsync(events.accounts.onViewed, eventPayload);
return this.i18nService.i18nApply(
[['accountTypeLabel'], ['accountNormalFormatted']],
transformed,

View File

@@ -11,6 +11,7 @@ import {
ACCOUNT_CREATED,
ACCOUNT_EDITED,
ACCOUNT_DELETED,
ACCOUNT_VIEWED,
} from '@/constants/event-tracker';
@Service()
@@ -31,6 +32,7 @@ export class AccountEventsTracker extends EventSubscriber {
events.accounts.onDeleted,
this.handleTrackDeletedAccountEvent
);
bus.subscribe(events.accounts.onViewed, this.handleTrackAccountViewedEvent);
}
private handleTrackAccountCreatedEvent = ({
@@ -62,4 +64,12 @@ export class AccountEventsTracker extends EventSubscriber {
properties: {},
});
};
private handleTrackAccountViewedEvent = ({ tenantId }) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: ACCOUNT_VIEWED,
properties: {},
});
};
}

View File

@@ -11,6 +11,7 @@ import {
ITEM_EVENT_CREATED,
ITEM_EVENT_EDITED,
ITEM_EVENT_DELETED,
ITEM_EVENT_VIEWED,
} from '@/constants/event-tracker';
@Service()
@@ -25,6 +26,7 @@ export class ItemEventsTracker extends EventSubscriber {
bus.subscribe(events.item.onCreated, this.handleTrackItemCreatedEvent);
bus.subscribe(events.item.onEdited, this.handleTrackEditedItemEvent);
bus.subscribe(events.item.onDeleted, this.handleTrackDeletedItemEvent);
bus.subscribe(events.item.onViewed, this.handleTrackViewedItemEvent);
}
private handleTrackItemCreatedEvent = ({
@@ -56,4 +58,14 @@ export class ItemEventsTracker extends EventSubscriber {
properties: {},
});
};
private handleTrackViewedItemEvent = ({
tenantId,
}: IItemEventDeletedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: ITEM_EVENT_VIEWED,
properties: {},
});
};
}

View File

@@ -10,6 +10,7 @@ import {
SALE_INVOICE_CREATED,
SALE_INVOICE_DELETED,
SALE_INVOICE_EDITED,
SALE_INVOICE_VIEWED,
} from '@/constants/event-tracker';
@Service()
@@ -33,6 +34,10 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
events.saleInvoice.onDeleted,
this.handleTrackDeletedInvoiceEvent
);
bus.subscribe(
events.saleInvoice.onViewed,
this.handleTrackViewedInvoiceEvent
);
}
private handleTrackInvoiceCreatedEvent = ({
@@ -64,4 +69,12 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
properties: {},
});
};
private handleTrackViewedInvoiceEvent = ({ tenantId }) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: SALE_INVOICE_VIEWED,
properties: {},
});
};
}

View File

@@ -3,6 +3,8 @@ import { IItem } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import ItemTransformer from './ItemTransformer';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
@Inject()
export class GetItem {
@@ -12,6 +14,9 @@ export class GetItem {
@Inject()
private transformer: TransformerInjectable;
@Inject()
private eventPublisher: EventPublisher;
/**
* Retrieve the item details of the given id with associated details.
* @param {number} tenantId
@@ -31,6 +36,16 @@ export class GetItem {
.withGraphFetched('purchaseTaxRate')
.throwIfNotFound();
return this.transformer.transform(tenantId, item, new ItemTransformer());
const transformed = await this.transformer.transform(
tenantId,
item,
new ItemTransformer()
);
const eventPayload = { tenantId, itemId };
// Triggers the `onItemViewed` event.
await this.eventPublisher.emitAsync(events.item.onViewed, eventPayload);
return transformed;
}
}

View File

@@ -4,6 +4,8 @@ import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
import events from '@/subscribers/events';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
@Service()
export class GetSaleInvoice {
@@ -16,6 +18,9 @@ export class GetSaleInvoice {
@Inject()
private validators: CommandSaleInvoiceValidators;
@Inject()
private eventPublisher: EventPublisher;
/**
* Retrieve sale invoice with associated entries.
* @param {Number} saleInvoiceId -
@@ -41,10 +46,20 @@ export class GetSaleInvoice {
// Validates the given sale invoice existance.
this.validators.validateInvoiceExistance(saleInvoice);
return this.transformer.transform(
const transformed = await this.transformer.transform(
tenantId,
saleInvoice,
new SaleInvoiceTransformer()
);
const eventPayload = {
tenantId,
saleInvoiceId,
};
// Triggers the `onSaleInvoiceItemViewed` event.
await this.eventPublisher.emitAsync(
events.saleInvoice.onViewed,
eventPayload
);
return transformed;
}
}

View File

@@ -74,6 +74,9 @@ export default {
* Accounts service.
*/
accounts: {
onViewed: 'onAccountViewed',
onListViewed: 'onAccountsListViewed',
onCreating: 'onAccountCreating',
onCreated: 'onAccountCreated',
@@ -127,6 +130,9 @@ export default {
* Sales invoices service.
*/
saleInvoice: {
onViewed: 'onSaleInvoiceItemViewed',
onListViewed: 'onSaleInvoiceListViewed',
onCreate: 'onSaleInvoiceCreate',
onCreating: 'onSaleInvoiceCreating',
onCreated: 'onSaleInvoiceCreated',
@@ -338,6 +344,8 @@ export default {
* Items service.
*/
item: {
onViewed: 'onItemViewed',
onCreated: 'onItemCreated',
onCreating: 'onItemCreating',

View File

@@ -17,6 +17,8 @@
"@casl/ability": "^5.4.3",
"@casl/react": "^2.3.0",
"@craco/craco": "^5.9.0",
"@emotion/css": "^11.13.4",
"@emotion/react": "^11.13.3",
"@reduxjs/toolkit": "^1.2.5",
"@stripe/connect-js": "^3.3.12",
"@stripe/react-connect-js": "^3.3.13",
@@ -48,6 +50,7 @@
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
"@xstyled/emotion": "^3.8.1",
"accounting": "^0.4.1",
"axios": "^1.6.0",
"basscss": "^8.0.2",
@@ -124,6 +127,7 @@
"style-loader": "0.23.1",
"styled-components": "^5.3.1",
"stylis-rtlcss": "^2.1.1",
"theme-ui": "^0.16.2",
"typescript": "^4.8.3",
"yup": "^0.28.1"
},

View File

@@ -35,6 +35,7 @@ const OneClickDemoPage = lazy(
const PaymentPortalPage = lazy(
() => import('@/containers/PaymentPortal/PaymentPortalPage'),
);
/**
* App inner.
*/
@@ -59,7 +60,10 @@ function AppInsider({ history }) {
children={<EmailConfirmation />}
/>
<Route path={'/auth'} children={<AuthenticationPage />} />
<Route path={'/payment/:linkId'} children={<PaymentPortalPage />} />
<Route
path={'/payment/:linkId'}
children={<PaymentPortalPage />}
/>
<Route path={'/'} children={<DashboardPrivatePages />} />
</Switch>
</Router>

View File

@@ -1,12 +1,31 @@
// @ts-nocheck
import React, { createContext } from 'react';
const AppIntlContext = createContext();
interface AppIntlContextValue {
currentLocale: string;
direction: 'rtl' | 'ltr';
isRTL: boolean;
isLTR: boolean;
}
const AppIntlContext = createContext<AppIntlContextValue>(
{} as AppIntlContextValue,
);
interface AppIntlProviderProps {
currentLocale: string;
isRTL: boolean;
children: React.ReactNode;
}
/**
* Application intl provider.
*/
function AppIntlProvider({ currentLocale, isRTL, children }) {
function AppIntlProvider({
currentLocale,
isRTL,
children,
}: AppIntlProviderProps) {
const provider = {
currentLocale,
isRTL,
@@ -21,6 +40,7 @@ function AppIntlProvider({ currentLocale, isRTL, children }) {
);
}
const useAppIntlContext = () => React.useContext(AppIntlContext);
const useAppIntlContext = () =>
React.useContext<AppIntlContextValue>(AppIntlContext);
export { AppIntlProvider, useAppIntlContext };

View File

@@ -2,6 +2,7 @@
import React from 'react';
import classnames from 'classnames';
import { LoadingIndicator } from '../Indicator';
import { css } from '@emotion/css';
export function DashboardInsider({
loading,
@@ -9,6 +10,7 @@ export function DashboardInsider({
name,
mount = false,
className,
style
}) {
return (
<div
@@ -17,9 +19,11 @@ export function DashboardInsider({
dashboard__insider: true,
'dashboard__insider--loading': loading,
[`dashboard__insider--${name}`]: !!name,
},
className,
)}
style={style}
>
<LoadingIndicator loading={loading} mount={mount}>
{children}

View File

@@ -1,9 +1,20 @@
// @ts-nocheck
import React from 'react';
import { ThemeProvider, StyleSheetManager } from 'styled-components';
import {
ThemeProvider as StyleComponentsThemeProvider,
StyleSheetManager,
} from 'styled-components';
import rtlcss from 'stylis-rtlcss';
import {
defaultTheme,
ThemeProvider as XStyledEmotionThemeProvider,
} from '@xstyled/emotion';
import { useAppIntlContext } from '../AppIntlProvider';
const theme = {
...defaultTheme,
bpPrefix: 'bp4',
};
interface DashboardThemeProviderProps {
children: React.ReactNode;
}
@@ -17,7 +28,11 @@ export function DashboardThemeProvider({
<StyleSheetManager
{...(direction === 'rtl' ? { stylisPlugins: [rtlcss] } : {})}
>
<ThemeProvider theme={{ dir: direction }}>{children}</ThemeProvider>
<StyleComponentsThemeProvider theme={{ dir: direction }}>
<XStyledEmotionThemeProvider theme={theme}>
{children}
</XStyledEmotionThemeProvider>
</StyleComponentsThemeProvider>
</StyleSheetManager>
);
}

View File

@@ -1,13 +1,15 @@
import React, { forwardRef, Ref } from 'react';
import { HTMLDivProps, Props } from '@blueprintjs/core';
import { SystemProps, x } from '@xstyled/emotion';
export interface BoxProps extends Props, HTMLDivProps {
className?: string;
}
export interface BoxProps
extends SystemProps,
Props,
Omit<HTMLDivProps, 'color'> {}
export const Box = forwardRef(
({ className, ...rest }: BoxProps, ref: Ref<HTMLDivElement>) => {
const Element = 'div';
const Element = x.div;
return <Element className={className} ref={ref} {...rest} />;
},

View File

@@ -1,5 +1,5 @@
import React from 'react';
import styled from 'styled-components';
import { SystemProps } from '@xstyled/emotion';
import { Box } from '../Box';
import { filterFalsyChildren } from './_utils';
@@ -12,7 +12,9 @@ export const GROUP_POSITIONS = {
apart: 'space-between',
};
export interface GroupProps extends React.ComponentPropsWithoutRef<'div'> {
export interface GroupProps
extends SystemProps,
Omit<React.ComponentPropsWithoutRef<'div'>, 'color'> {
/** Defines justify-content property */
position?: GroupPosition;
@@ -27,34 +29,30 @@ export interface GroupProps extends React.ComponentPropsWithoutRef<'div'> {
/** Defines align-items css property */
align?: React.CSSProperties['alignItems'];
flex?: React.CSSProperties['flex'];
}
const defaultProps: Partial<GroupProps> = {
position: 'left',
spacing: 20,
flex: 'none'
};
export function Group({ children, ...props }: GroupProps) {
const groupProps = {
...defaultProps,
...props,
};
export function Group({
position = 'left',
spacing = 20,
align = 'center',
noWrap,
children,
...props
}: GroupProps) {
const filteredChildren = filterFalsyChildren(children);
return <GroupStyled {...groupProps}>{filteredChildren}</GroupStyled>;
return (
<Box
boxSizing={'border-box'}
display={'flex'}
flexDirection={'row'}
alignItems={align}
flexWrap={noWrap ? 'nowrap' : 'wrap'}
justifyContent={GROUP_POSITIONS[position]}
gap={`${spacing}px`}
{...props}
>
{filteredChildren}
</Box>
);
}
const GroupStyled = styled(Box)`
box-sizing: border-box;
display: flex;
flex-direction: row;
flex: ${(props: GroupProps) => (props.flex)};
align-items: ${(props: GroupProps) => (props.align || 'center')};
flex-wrap: ${(props: GroupProps) => (props.noWrap ? 'nowrap' : 'wrap')};
justify-content: ${(props: GroupProps) =>
GROUP_POSITIONS[props.position || 'left']};
gap: ${(props: GroupProps) => props.spacing}px;
`;

View File

@@ -1,8 +1,9 @@
import React from 'react';
import styled from 'styled-components';
import { Box } from '../Box';
import { x, SystemProps } from '@xstyled/emotion';
export interface StackProps extends React.ComponentPropsWithoutRef<'div'> {
export interface StackProps
extends SystemProps,
Omit<React.ComponentPropsWithoutRef<'div'>, 'color'> {
/** Key of theme.spacing or number to set gap in px */
spacing?: number;
@@ -11,30 +12,22 @@ export interface StackProps extends React.ComponentPropsWithoutRef<'div'> {
/** justify-content CSS property */
justify?: React.CSSProperties['justifyContent'];
flex?: React.CSSProperties['flex'];
}
const defaultProps: Partial<StackProps> = {
spacing: 20,
align: 'stretch',
justify: 'top',
flex: 'none',
};
export function Stack(props: StackProps) {
const stackProps = {
...defaultProps,
...props,
};
return <StackStyled {...stackProps} />;
export function Stack({
spacing = 20,
align = 'stretch',
justify = 'top',
...restProps
}: StackProps) {
return (
<x.div
display={'flex'}
flexDirection="column"
justifyContent="justify"
gap={`${spacing}px`}
alignItems={align}
{...restProps}
/>
);
}
const StackStyled = styled(Box)`
display: flex;
flex-direction: column;
align-items: ${(props: StackProps) => props.align};
justify-content: justify;
gap: ${(props: StackProps) => props.spacing}px;
flex: ${(props: StackProps) => props.flex};
`;

View File

@@ -0,0 +1,91 @@
import React, { FC } from 'react';
import clsx from 'classnames';
import { x, SystemProps } from '@xstyled/emotion';
import { css } from '@emotion/css';
import { Group, GroupProps } from '@/components';
interface PageFormProps extends SystemProps {
children: React.ReactNode;
}
/**
* Page form layout.
* @returns {React.ReactNode}
*/
export const PageForm = ({ children, ...props }: PageFormProps) => {
return (
<x.div display="flex" flexDirection={'column'} overflow="hidden" {...props}>
{children}
</x.div>
);
};
PageForm.displayName = 'PageFormBody';
/**
* Page form body layout, by default the content body is scrollable.
* @returns {React.ReactNode}
*/
const PageFormBody: FC<{ children: React.ReactNode } & SystemProps> = ({
children,
...props
}) => {
return (
<x.div flex="1" overflow="auto" {...props}>
{children}
</x.div>
);
};
PageFormBody.displayName = 'PageFormBody';
/**
* Page form footer.
* @returns {React.ReactNode}
*/
const PageFormFooter: FC<{ children: React.ReactNode } & SystemProps> = ({ children }) => {
return <x.div>{children} </x.div>;
};
PageFormFooter.displayName = 'PageFormFooter';
const footerActionsStyle = `
width: 100%;
background: #fff;
padding: 14px 20px;
border-top: 1px solid rgb(210, 221, 226);
box-shadow: 0px -1px 4px 0px rgba(0, 0, 0, 0.05);
.bp4-button-group{
.bp4-button{
&:not(:last-child),
&.bp4-popover-wrapper:not(:last-child) {
border-right: 1px solid rgba(92, 112, 127, 0.3);
margin-right: 0;
&.bp4-intent-primary{
border-right: 1px solid rgba(255, 255, 255, 0.3);
}
}
}
}
`;
const PageFormFooterActions: FC<GroupProps> = ({
children,
className,
...restProps
}) => {
return (
<Group
spacing={20}
{...restProps}
className={clsx(css(footerActionsStyle), className)}
>
{children}
</Group>
);
};
PageFormFooterActions.displayName = 'PageFormFooterActions';
PageForm.Body = PageFormBody;
PageForm.Footer = PageFormFooter;
PageForm.FooterActions = PageFormFooterActions;

View File

@@ -2,3 +2,4 @@
export * from './FormTopbar';
export * from './FormTopbarSelectInputs';
export * from './PageFormBigNumber';
export * from './PageForm';

View File

@@ -1,13 +1,20 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import { x, SystemProps } from '@xstyled/emotion';
export function Paper({ children, className }) {
return <PaperRoot className={className}>{children}</PaperRoot>;
interface PaperProps extends SystemProps {
children: React.ReactNode;
}
const PaperRoot = styled.div`
border: 1px solid #d2dce2;
background: #fff;
padding: 10px;
`;
export const Paper = ({ children, ...props }: PaperProps) => {
return (
<x.div
background={'white'}
p={'10px'}
border={'1px solid #d2dce2'}
{...props}
>
{children}
</x.div>
);
};
Paper.displayName = 'Paper';

View File

@@ -1,7 +1,6 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { CLASSES } from '@/constants/classes';
import { Row, Col, Paper } from '@/components';
@@ -12,7 +11,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
export default function MakeJournalFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<MakeJournalFooterPaper>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<MakeJournalFormFooterLeft />
@@ -23,10 +22,7 @@ export default function MakeJournalFormFooter() {
<MakeJournalFormFooterRight />
</Col>
</Row>
</MakeJournalFooterPaper>
</Paper>
</div>
);
}
const MakeJournalFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -51,8 +51,6 @@ export function BrandingTemplateForm<
const { mutateAsync: editPdfTemplate } = useEditPdfTemplate();
const initialValues = useBrandingTemplateFormInitialValues<T>(defaultValues);
const brandingState = useBrandingState();
const [, setIsLoading] = useState<boolean>(false);
// Uploads the attachments.
@@ -138,7 +136,6 @@ export function BrandingTemplateForm<
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleFormSubmit}
brandingState={brandingState || {}}
{...props}
/>
);

View File

@@ -1,6 +1,6 @@
import { Button } from '@blueprintjs/core';
import { Button, ButtonProps } from '@blueprintjs/core';
import styled from 'styled-components';
import { FFormGroup } from '@/components';
import { FFormGroup, Icon } from '@/components';
export const BrandingThemeFormGroup = styled(FFormGroup)`
margin-bottom: 0;
@@ -14,33 +14,21 @@ export const BrandingThemeFormGroup = styled(FFormGroup)`
}
`;
export const BrandingThemeSelectButton = styled(Button)`
position: relative;
padding-right: 26px;
export const BrandingThemeSelectButton = (props: ButtonProps) => {
return (
<Button
text={props?.text || 'Brand Theme'}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
minimal
{...props}
/>
);
};
&::after {
content: '';
display: inline-block;
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 5px solid #98a1ae;
position: absolute;
right: -2px;
top: 50%;
margin-top: -2px;
margin-right: 12px;
border-radius: 1px;
}
`;
export const convertBrandingTemplatesToOptions = (brandingTemplates: Array<any>) => {
export const convertBrandingTemplatesToOptions = (
brandingTemplates: Array<any>,
) => {
return brandingTemplates?.map(
(template) =>
({ text: template.template_name, value: template.id } || []),
)
}
(template) => ({ text: template.template_name, value: template.id } || []),
);
};

View File

@@ -10,6 +10,8 @@ import { ElementCustomizeFields } from './ElementCustomizeFields';
import { ElementCustomizePreview } from './ElementCustomizePreview';
import { extractChildren } from '@/utils/extract-children';
import { ElementPreviewState } from '../BrandingTemplates/types';
import { TabProps } from '@blueprintjs/core';
import { useBrandingState } from '../BrandingTemplates/_utils';
export interface ElementCustomizeProps<T, Y>
extends ElementCustomizeFormProps<T, Y> {
@@ -17,13 +19,13 @@ export interface ElementCustomizeProps<T, Y>
children?: React.ReactNode;
}
export function ElementCustomize<T, Y extends ElementPreviewState>({
initialValues,
validationSchema,
brandingState,
onSubmit,
export interface ElementCustomizeContentProps {
children?: React.ReactNode;
}
export function ElementCustomizeContent({
children,
}: ElementCustomizeProps<T, Y>) {
}: ElementCustomizeContentProps) {
const PaperTemplate = React.useMemo(
() => extractChildren(children, ElementCustomize.PaperTemplate),
[children],
@@ -32,23 +34,34 @@ export function ElementCustomize<T, Y extends ElementPreviewState>({
() => extractChildren(children, ElementCustomize.FieldsTab),
[children],
);
const brandingState = useBrandingState();
const value = { PaperTemplate, CustomizeTabs, brandingState };
return (
<ElementCustomizeTabsControllerProvider>
<ElementCustomizeProvider value={value}>
<Group spacing={0} align="stretch">
<ElementCustomizeFields />
<ElementCustomizePreview />
</Group>
</ElementCustomizeProvider>
</ElementCustomizeTabsControllerProvider>
);
}
export function ElementCustomize<T, Y extends ElementPreviewState>({
initialValues,
validationSchema,
onSubmit,
children,
}: ElementCustomizeProps<T, Y>) {
return (
<ElementCustomizeForm
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
<ElementCustomizeTabsControllerProvider>
<ElementCustomizeProvider value={value}>
<Group spacing={0} align="stretch">
<ElementCustomizeFields />
<ElementCustomizePreview />
</Group>
</ElementCustomizeProvider>
</ElementCustomizeTabsControllerProvider>
{children}
</ElementCustomizeForm>
);
}
@@ -63,16 +76,17 @@ ElementCustomize.PaperTemplate = ({
return <>{children}</>;
};
export interface ElementCustomizeContentProps {
export interface ElementCustomizeFieldsTabProps {
id: string;
label: string;
children?: React.ReactNode;
tabProps?: Partial<TabProps>;
}
ElementCustomize.FieldsTab = ({
id,
label,
children,
}: ElementCustomizeContentProps) => {
}: ElementCustomizeFieldsTabProps) => {
return <>{children}</>;
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Box, Stack } from '@/components';
import { Tab, Tabs } from '@blueprintjs/core';
import { Tab, TabProps, Tabs } from '@blueprintjs/core';
import { ElementCustomizeHeader } from './ElementCustomizeHeader';
import {
ElementCustomizeTabsEnum,
@@ -11,7 +11,6 @@ import styles from './ElementCustomizeTabs.module.scss';
export function ElementCustomizeTabs() {
const { setCurrentTabId } = useElementCustomizeTabsController();
const { CustomizeTabs } = useElementCustomizeContext();
const tabItems = React.Children.map(CustomizeTabs, (node) => ({
@@ -32,9 +31,19 @@ export function ElementCustomizeTabs() {
onChange={handleChange}
className={styles.tabsList}
>
{tabItems?.map(({ id, label }: { id: string; label: string }) => (
<Tab id={id} key={id} title={label} />
))}
{tabItems?.map(
({
id,
label,
tabProps,
}: {
id: string;
label: string;
tabProps?: TabProps;
}) => (
<Tab id={id} key={id} title={label} {...tabProps} />
),
)}
</Tabs>
</Box>
</Stack>

View File

@@ -13,6 +13,9 @@ export function BrandingCompanyLogoUploadField() {
onChange={(file) => {
const imageUrl = file ? URL.createObjectURL(file) : '';
// Reset the logo key since it is changed.
setFieldValue('companyLogoKey', '');
setFieldValue('_companyLogoFile', file);
setFieldValue('companyLogoUri', imageUrl);
}}

View File

@@ -1,7 +1,6 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { CLASSES } from '@/constants/classes';
import { Row, Col, Paper } from '@/components';
@@ -12,7 +11,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
export default function ExpenseFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<ExpensesFooterPaper>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<ExpenseFormFooterLeft />
@@ -23,11 +22,7 @@ export default function ExpenseFormFooter() {
<ExpenseFormFooterRight />
</Col>
</Row>
</ExpensesFooterPaper>
</Paper>
</div>
);
}
const ExpensesFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -110,7 +110,7 @@ export function StripePaymentMethod() {
</Menu>
}
>
<Button small icon={<MoreIcon size={16} />} />
<Button small icon={<MoreIcon height={10} width={10} />} />
</Popover>
)}
</Group>

View File

@@ -13,7 +13,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
export default function BillFormFooter() {
return (
<div class={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<BillFooterPaper>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<BillFormFooterLeft />
@@ -24,11 +24,7 @@ export default function BillFormFooter() {
<BillFormFooterRight />
</Col>
</Row>
</BillFooterPaper>
</Paper>
</div>
);
}
const BillFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -15,7 +15,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
export default function VendorCreditNoteFormFooter() {
return (
<div class={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<VendorCreditNoteFooterPaper>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<VendorCreditNoteFormFooterLeft />
@@ -26,11 +26,7 @@ export default function VendorCreditNoteFormFooter() {
<VendorCreditNoteFormFooterRight />
</Col>
</Row>
</VendorCreditNoteFooterPaper>
</Paper>
</div>
);
}
const VendorCreditNoteFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -15,7 +15,7 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
export default function PaymentMadeFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<PaymentReceiveFooterPaper>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<PaymentMadeFormFooterLeft />
@@ -26,11 +26,7 @@ export default function PaymentMadeFooter() {
<PaymentMadeFormFooterRight />
</Col>
</Row>
</PaymentReceiveFooterPaper>
</Paper>
</div>
);
}
const PaymentReceiveFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -1,14 +1,21 @@
import { useFormikContext } from 'formik';
import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize';
import {
ElementCustomize,
ElementCustomizeContent,
} from '../../../ElementCustomize/ElementCustomize';
import { CreditNoteCustomizeGeneralField } from './CreditNoteCustomizeGeneralFields';
import { CreditNoteCustomizeContentFields } from './CreditNoteCutomizeContentFields';
import { CreditNotePaperTemplate, CreditNotePaperTemplateProps } from './CreditNotePaperTemplate';
import {
CreditNotePaperTemplate,
CreditNotePaperTemplateProps,
} from './CreditNotePaperTemplate';
import { CreditNoteBrandingState, CreditNoteCustomizeValues } from './types';
import { initialValues } from './constants';
import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm';
import { useDrawerActions } from '@/hooks/state';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
export function CreditNoteCustomizeContent() {
const { payload, name } = useDrawerContext();
@@ -27,6 +34,16 @@ export function CreditNoteCustomizeContent() {
defaultValues={initialValues}
onSuccess={handleSuccess}
>
<CreditNoteCustomizeFormContent />
</BrandingTemplateForm>
);
}
function CreditNoteCustomizeFormContent() {
const isTemplateNameFilled = useIsTemplateNamedFilled();
return (
<ElementCustomizeContent>
<ElementCustomize.PaperTemplate>
<CreditNotePaperTemplateFormConnected />
</ElementCustomize.PaperTemplate>
@@ -35,10 +52,14 @@ export function CreditNoteCustomizeContent() {
<CreditNoteCustomizeGeneralField />
</ElementCustomize.FieldsTab>
<ElementCustomize.FieldsTab id={'content'} label={'Content'}>
<ElementCustomize.FieldsTab
id={'content'}
label={'Content'}
tabProps={{ disabled: !isTemplateNameFilled }}
>
<CreditNoteCustomizeContentFields />
</ElementCustomize.FieldsTab>
</BrandingTemplateForm>
</ElementCustomizeContent>
);
}
@@ -46,7 +67,10 @@ function CreditNotePaperTemplateFormConnected() {
const { values } = useFormikContext<CreditNoteCustomizeValues>();
const { brandingState } = useElementCustomizeContext();
const mergedProps: CreditNotePaperTemplateProps = { ...brandingState, ...values };
const mergedProps: CreditNotePaperTemplateProps = {
...brandingState,
...values,
};
return <CreditNotePaperTemplate {...mergedProps} />;
}

View File

@@ -12,21 +12,30 @@ import {
Menu,
MenuItem,
} from '@blueprintjs/core';
import { If, Icon, FormattedMessage as T, Group, FSelect } from '@/components';
import { CLASSES } from '@/constants/classes';
import classNames from 'classnames';
import {
If,
Icon,
FormattedMessage as T,
Group,
FSelect,
PageForm,
} from '@/components';
import { useCreditNoteFormContext } from './CreditNoteFormProvider';
import {
BrandingThemeFormGroup,
BrandingThemeSelectButton,
} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
import { useCreditNoteFormBrandingTemplatesOptions } from './utils';
import { MoreIcon } from '@/icons/More';
import { useDrawerActions } from '@/hooks/state';
import { DRAWERS } from '@/constants/drawers';
/**
* Credit note floating actions.
*/
export default function CreditNoteFloatingActions() {
const history = useHistory();
const { openDrawer } = useDrawerActions();
// Formik context.
const { resetForm, submitForm, isSubmitting } = useFormikContext();
@@ -79,143 +88,169 @@ export default function CreditNoteFloatingActions() {
resetForm();
};
// Handles the credit note customize button click.
const handleCustomizeBtnClick = () => {
openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'CreditNote' });
};
const brandingTemplatesOptions = useCreditNoteFormBrandingTemplatesOptions();
return (
<Group
spacing={10}
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
>
{/* ----------- Save And Open ----------- */}
<If condition={!creditNote || !creditNote?.is_open}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitOpenBtnClick}
text={<T id={'save_open'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'open_and_new'} />}
onClick={handleSubmitOpenAndNewBtnClick}
/>
<MenuItem
text={<T id={'open_continue_editing'} />}
onClick={handleSubmitOpenContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<PageForm.FooterActions position={'apart'} spacing={20}>
<Group spacing={10}>
{/* ----------- Save And Open ----------- */}
<If condition={!creditNote || !creditNote?.is_open}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
onClick={handleSubmitOpenBtnClick}
text={<T id={'save_open'} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'open_and_new'} />}
onClick={handleSubmitOpenAndNewBtnClick}
/>
<MenuItem
text={<T id={'open_continue_editing'} />}
onClick={handleSubmitOpenContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={creditNote && creditNote?.is_open}>
<ButtonGroup>
<Button
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitOpenBtnClick}
style={{ minWidth: '85px' }}
text={<T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitOpenAndNewBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={creditNote && creditNote?.is_open}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
onClick={handleSubmitOpenBtnClick}
style={{ minWidth: '85px' }}
text={<T id={'save'} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={creditNote ? <T id={'reset'} /> : <T id={'clear'} />}
/>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleCancelBtnClick}
text={<T id={'cancel'} />}
/>
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitOpenAndNewBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={creditNote ? <T id={'reset'} /> : <T id={'clear'} />}
/>
</BrandingThemeFormGroup>
</Group>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleCancelBtnClick}
text={<T id={'cancel'} />}
/>
</Group>
<Group spacing={0}>
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
/>
</BrandingThemeFormGroup>
{/* ----------- Setting Select ----------- */}
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.TOP_RIGHT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
text={'Customize Templates'}
onClick={handleCustomizeBtnClick}
/>
</Menu>
}
>
<Button minimal icon={<MoreIcon height={'14px'} width={'14px'} />} />
</Popover>
</Group>
</PageForm.FooterActions>
);
}

View File

@@ -1,12 +1,12 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import { defaultTo, isEmpty } from 'lodash';
import { CLASSES } from '@/constants/classes';
import { css } from '@emotion/css';
import {
CreateCreditNoteFormSchema,
EditCreditNoteFormSchema,
@@ -42,6 +42,7 @@ import {
CreditNoteExchangeRateSync,
CreditNoteSyncIncrementSettingsToForm,
} from './components';
import { PageForm } from '@/components/PageForm';
/**
* Credit note form.
@@ -148,36 +149,42 @@ function CreditNoteForm({
};
return (
<div
className={classNames(
CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_STRIP_STYLE,
CLASSES.PAGE_FORM_CREDIT_NOTE,
)}
<Formik
validationSchema={
isNewMode ? CreateCreditNoteFormSchema : EditCreditNoteFormSchema
}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<Formik
validationSchema={
isNewMode ? CreateCreditNoteFormSchema : EditCreditNoteFormSchema
}
initialValues={initialValues}
onSubmit={handleFormSubmit}
<Form
className={css({
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
flex: 1,
})}
>
<Form>
<CreditNoteFormTopBar />
<CreditNoteFormHeader />
<CreditNoteItemsEntriesEditorField />
<CreditNoteFormFooter />
<CreditNoteFloatingActions />
<PageForm flex="1">
<PageForm.Body>
<CreditNoteFormTopBar />
<CreditNoteFormHeader />
<CreditNoteItemsEntriesEditorField />
<CreditNoteFormFooter />
</PageForm.Body>
{/*-------- Dialogs --------*/}
<CreditNoteFormDialogs />
<PageForm.Footer>
<CreditNoteFloatingActions />
</PageForm.Footer>
</PageForm>
{/*-------- Effects --------*/}
<CreditNoteSyncIncrementSettingsToForm />
<CreditNoteExchangeRateSync />
</Form>
</Formik>
</div>
{/*-------- Dialogs --------*/}
<CreditNoteFormDialogs />
{/*-------- Effects --------*/}
<CreditNoteSyncIncrementSettingsToForm />
<CreditNoteExchangeRateSync />
</Form>
</Formik>
);
}
export default compose(

View File

@@ -1,10 +1,7 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { CLASSES } from '@/constants/classes';
import { Row, Col, Paper } from '@/components';
import { Row, Col, Paper, Stack } from '@/components';
import { CreditNoteFormFooterLeft } from './CreditNoteFormFooterLeft';
import { CreditNoteFormFooterRight } from './CreditNoteFormFooterRight';
import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmentButton';
@@ -14,8 +11,8 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
*/
export default function CreditNoteFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<CreditNoteFooterPaper>
<Stack mt={'20px'} px={'32px'} pb={'20px'} flex={1}>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<CreditNoteFormFooterLeft />
@@ -26,10 +23,7 @@ export default function CreditNoteFormFooter() {
<CreditNoteFormFooterRight />
</Col>
</Row>
</CreditNoteFooterPaper>
</div>
</Paper>
</Stack>
);
}
const CreditNoteFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -1,23 +1,28 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { useFormikContext } from 'formik';
import { CLASSES } from '@/constants/classes';
import CreditNoteFormHeaderFields from './CreditNoteFormHeaderFields';
import { getEntriesTotal } from '@/containers/Entries/utils';
import { PageFormBigNumber } from '@/components';
import { Group, PageFormBigNumber } from '@/components';
/**
* Credit note header.
*/
function CreditNoteFormHeader() {
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<Group
position="apart"
align={'flex-start'}
display="flex"
bg="white"
p="25px 32px"
borderBottom="1px solid #d2dce2"
>
<CreditNoteFormHeaderFields />
<CreditNoteFormBigNumber />
</div>
</Group>
);
}

View File

@@ -5,6 +5,8 @@ import styled from 'styled-components';
import { FormGroup, InputGroup, Position } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { css } from '@emotion/css';
import { useTheme } from '@emotion/react';
import { CLASSES } from '@/constants/classes';
import {
@@ -14,26 +16,41 @@ import {
CustomerDrawerLink,
FFormGroup,
CustomersSelect,
Stack,
FDateInput,
} from '@/components';
import { customerNameFieldShouldUpdate } from './utils';
import { useCreditNoteFormContext } from './CreditNoteFormProvider';
import { CreditNoteExchangeRateInputField } from './components';
import { CreditNoteTransactionNoField } from './CreditNoteTransactionNoField';
import {
momentFormatter,
tansformDateValue,
inputIntent,
handleDateChange,
} from '@/utils';
import { useCustomerUpdateExRate } from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
const getCreditNoteFieldsStyle = (theme: Theme) => css`
.${theme.bpPrefix}-form-group {
margin-bottom: 0;
&.${theme.bpPrefix}-inline {
max-width: 450px;
}
.${theme.bpPrefix}-label {
min-width: 150px;
}
.${theme.bpPrefix}-form-content {
width: 100%;
}
}
`;
/**
* Credit note form header fields.
*/
export default function CreditNoteFormHeaderFields({}) {
export default function CreditNoteFormHeaderFields() {
const theme = useTheme();
const styleClassName = getCreditNoteFieldsStyle(theme);
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
<Stack spacing={18} flex={1} className={styleClassName}>
{/* ----------- Customer name ----------- */}
<CreditNoteCustomersSelect />
@@ -41,48 +58,35 @@ export default function CreditNoteFormHeaderFields({}) {
<CreditNoteExchangeRateInputField />
{/* ----------- Credit note date ----------- */}
<FastField name={'credit_note_date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'credit_note.label_credit_note_date'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--credit_note_date', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="credit_note_date" />}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('credit_note_date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM_LEFT, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
<FFormGroup
name={'credit_note_date'}
label={<T id={'credit_note.label_credit_note_date'} />}
labelInfo={<FieldRequiredHint />}
inline
fastField
>
<FDateInput
name={'credit_note_date'}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
popoverProps={{ position: Position.BOTTOM_LEFT, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
fill: true
}}
fill
fastField
/>
</FFormGroup>
{/* ----------- Credit note # ----------- */}
<CreditNoteTransactionNoField />
{/* ----------- Reference ----------- */}
<FastField name={'reference_no'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
inline={true}
className={classNames('form-group--reference', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
>
<InputGroup minimal={true} {...field} />
</FormGroup>
)}
</FastField>
</div>
<FormGroup label={<T id={'reference_no'} />} name={'reference_no'} inline>
<InputGroup name={'reference_no'} minimal />
</FormGroup>
</Stack>
);
}

View File

@@ -1,12 +1,14 @@
// @ts-nocheck
import React from 'react';
import { useParams } from 'react-router-dom';
import '@/style/pages/CreditNote/PageForm.scss';
import { css } from '@emotion/css';
import CreditNoteForm from './CreditNoteForm';
import { CreditNoteFormProvider } from './CreditNoteFormProvider';
import {
CreditNoteFormProvider,
useCreditNoteFormContext,
} from './CreditNoteFormProvider';
import { AutoExchangeRateProvider } from '@/containers/Entries/AutoExchangeProvider';
import { DashboardInsider } from '@/components';
/**
* Credit note form page.
@@ -18,8 +20,24 @@ export default function CreditNoteFormPage() {
return (
<CreditNoteFormProvider creditNoteId={idAsInteger}>
<AutoExchangeRateProvider>
<CreditNoteForm />
<CreditNoteFormPageContent />
</AutoExchangeRateProvider>
</CreditNoteFormProvider>
);
}
function CreditNoteFormPageContent() {
const { isBootLoading } = useCreditNoteFormContext();
return (
<DashboardInsider
loading={isBootLoading}
className={css`
min-height: calc(100vh - var(--top-offset));
max-height: calc(100vh - var(--top-offset));
`}
>
<CreditNoteForm />
</DashboardInsider>
);
}

View File

@@ -113,6 +113,13 @@ function CreditNoteFormProvider({ creditNoteId, ...props }) {
})
: [];
const isBootLoading =
isItemsLoading ||
isCustomersLoading ||
isCreditNoteLoading ||
isInvoiceLoading ||
isBrandingTemplatesLoading;
// Provider payload.
const provider = {
items,
@@ -141,20 +148,11 @@ function CreditNoteFormProvider({ creditNoteId, ...props }) {
// Credit note state
creditNoteState,
isCreditNoteStateLoading,
isBootLoading,
};
const isLoading =
isItemsLoading ||
isCustomersLoading ||
isCreditNoteLoading ||
isInvoiceLoading ||
isBrandingTemplatesLoading;
return (
<DashboardInsider loading={isLoading} name={'credit-note-form'}>
<CreditNoteFormContext.Provider value={provider} {...props} />
</DashboardInsider>
);
return <CreditNoteFormContext.Provider value={provider} {...props} />;
}
const useCreditNoteFormContext = () =>

View File

@@ -1,11 +1,10 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { FastField } from 'formik';
import { CLASSES } from '@/constants/classes';
import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable';
import { useCreditNoteFormContext } from './CreditNoteFormProvider';
import { entriesFieldShouldUpdate } from './utils';
import { Box } from '@/components';
/**
* Credit note items entries editor field.
@@ -14,7 +13,7 @@ export default function CreditNoteItemsEntriesEditorField() {
const { items } = useCreditNoteFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<Box p="18px 32px 0">
<FastField
name={'entries'}
items={items}
@@ -38,6 +37,6 @@ export default function CreditNoteItemsEntriesEditorField() {
/>
)}
</FastField>
</div>
</Box>
);
}

View File

@@ -1,14 +1,21 @@
import { useFormikContext } from 'formik';
import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize';
import {
ElementCustomize,
ElementCustomizeContent,
} from '../../../ElementCustomize/ElementCustomize';
import { EstimateCustomizeGeneralField } from './EstimateCustomizeFieldsGeneral';
import { EstimateCustomizeContentFields } from './EstimateCustomizeFieldsContent';
import { EstimatePaperTemplate, EstimatePaperTemplateProps } from './EstimatePaperTemplate';
import {
EstimatePaperTemplate,
EstimatePaperTemplateProps,
} from './EstimatePaperTemplate';
import { EstimateBrandingState, EstimateCustomizeValues } from './types';
import { initialValues } from './constants';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useDrawerActions } from '@/hooks/state';
import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm';
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
export function EstimateCustomizeContent() {
const { payload, name } = useDrawerContext();
@@ -26,6 +33,16 @@ export function EstimateCustomizeContent() {
onSuccess={handleSuccess}
resource={'SaleEstimate'}
>
<EstimateCustomizeFormContent />
</BrandingTemplateForm>
);
}
function EstimateCustomizeFormContent() {
const isTemplateNameFilled = useIsTemplateNamedFilled();
return (
<ElementCustomizeContent>
<ElementCustomize.PaperTemplate>
<EstimatePaperTemplateFormConnected />
</ElementCustomize.PaperTemplate>
@@ -34,10 +51,14 @@ export function EstimateCustomizeContent() {
<EstimateCustomizeGeneralField />
</ElementCustomize.FieldsTab>
<ElementCustomize.FieldsTab id={'content'} label={'Content'}>
<ElementCustomize.FieldsTab
id={'content'}
label={'Content'}
tabProps={{ disabled: !isTemplateNameFilled }}
>
<EstimateCustomizeContentFields />
</ElementCustomize.FieldsTab>
</BrandingTemplateForm>
</ElementCustomizeContent>
);
}
@@ -47,9 +68,12 @@ export function EstimateCustomizeContent() {
*/
function EstimatePaperTemplateFormConnected() {
const { values } = useFormikContext<EstimateCustomizeValues>();
const { brandingState } = useElementCustomizeContext()
const { brandingState } = useElementCustomizeContext();
const mergedProps: EstimatePaperTemplateProps = { ...brandingState, ...values, }
const mergedProps: EstimatePaperTemplateProps = {
...brandingState,
...values,
};
return <EstimatePaperTemplate {...mergedProps} />;
}

View File

@@ -1,6 +1,5 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import {
Intent,
Button,
@@ -12,21 +11,25 @@ import {
MenuItem,
} from '@blueprintjs/core';
import { If, Icon, FormattedMessage as T, Group, FSelect } from '@/components';
import { CLASSES } from '@/constants/classes';
import { useHistory } from 'react-router-dom';
import { useFormikContext } from 'formik';
import { useEstimateFormContext } from './EstimateFormProvider';
import { useEstimateFormBrandingTemplatesOptions } from './utils';
import { useDrawerActions } from '@/hooks/state';
import {
BrandingThemeFormGroup,
BrandingThemeSelectButton,
} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
import { PageForm } from '@/components/PageForm';
import { MoreIcon } from '@/icons/More';
import { DRAWERS } from '@/constants/drawers';
/**
* Estimate floating actions bar.
*/
export default function EstimateFloatingActions() {
const history = useHistory();
const { openDrawer } = useDrawerActions();
const { resetForm, submitForm, isSubmitting } = useFormikContext();
// Estimate form context.
@@ -78,147 +81,173 @@ export default function EstimateFloatingActions() {
resetForm();
};
// Handles the invoice customize button click.
const handleCustomizeBtnClick = () => {
openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'SaleEstimate' });
};
const brandingTemplatesOptions = useEstimateFormBrandingTemplatesOptions();
return (
<Group
spacing={10}
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
>
{/* ----------- Save And Deliver ----------- */}
<If condition={!estimate || !estimate?.is_delivered}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitDeliverBtnClick}
text={<T id={'save_and_deliver'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'deliver_and_new'} />}
onClick={handleSubmitDeliverAndNewBtnClick}
/>
<MenuItem
text={<T id={'deliver_continue_editing'} />}
onClick={handleSubmitDeliverContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<PageForm.FooterActions position={'apart'} spacing={10}>
<Group spacing={10}>
{/* ----------- Save And Deliver ----------- */}
<If condition={!estimate || !estimate?.is_delivered}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitDeliverBtnClick}
text={<T id={'save_and_deliver'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'deliver_and_new'} />}
onClick={handleSubmitDeliverAndNewBtnClick}
/>
<MenuItem
text={<T id={'deliver_continue_editing'} />}
onClick={handleSubmitDeliverContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={estimate && estimate?.is_delivered}>
<ButtonGroup>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
onClick={handleSubmitDeliverBtnClick}
style={{ minWidth: '85px' }}
text={<T id={'save'} />}
/>
</Popover>
</ButtonGroup>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDeliverAndNewBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={estimate && estimate?.is_delivered}>
<ButtonGroup>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitDeliverBtnClick}
style={{ minWidth: '85px' }}
text={<T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDeliverAndNewBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={estimate ? <T id={'reset'} /> : <T id={'clear'} />}
/>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleCancelBtnClick}
text={<T id={'cancel'} />}
/>
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={estimate ? <T id={'reset'} /> : <T id={'clear'} />}
/>
</BrandingThemeFormGroup>
</Group>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleCancelBtnClick}
text={<T id={'cancel'} />}
/>
</Group>
<Group spacing={0}>
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
/>
</BrandingThemeFormGroup>
{/* ----------- More Select ----------- */}
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.TOP_RIGHT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
text={'Customize Templates'}
onClick={handleCustomizeBtnClick}
/>
</Menu>
}
>
<Button minimal icon={<MoreIcon height={'14px'} width={'14px'} />} />
</Popover>
</Group>
</PageForm.FooterActions>
);
}

View File

@@ -1,11 +1,10 @@
// @ts-nocheck
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { css } from '@emotion/css';
import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import { sumBy, isEmpty, defaultTo } from 'lodash';
import { useHistory } from 'react-router-dom';
import { CLASSES } from '@/constants/classes';
import {
CreateEstimateFormSchema,
@@ -36,8 +35,9 @@ import {
handleErrors,
resetFormState,
} from './utils';
import { PageForm } from '@/components/PageForm';
/**
/**6
* Estimate form.
*/
function EstimateForm({
@@ -148,36 +148,42 @@ function EstimateForm({
};
return (
<div
className={classNames(
CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_STRIP_STYLE,
CLASSES.PAGE_FORM_ESTIMATE,
)}
<Formik
validationSchema={
isNewMode ? CreateEstimateFormSchema : EditEstimateFormSchema
}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<Formik
validationSchema={
isNewMode ? CreateEstimateFormSchema : EditEstimateFormSchema
}
initialValues={initialValues}
onSubmit={handleFormSubmit}
<Form
className={css({
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
flex: 1
})}
>
<Form>
<EstimtaeFormTopBar />
<EstimateFormHeader />
<EstimateItemsEntriesField />
<EstimateFormFooter />
<EstimateFloatingActions />
<PageForm flex={1}>
<PageForm.Body>
<EstimtaeFormTopBar />
<EstimateFormHeader />
<EstimateItemsEntriesField />
<EstimateFormFooter />
</PageForm.Body>
{/*------- Dialogs -------*/}
<EstimateFormDialogs />
<PageForm.Footer>
<EstimateFloatingActions />
</PageForm.Footer>
</PageForm>
{/*------- Effects -------*/}
<EstimateIncrementSyncSettingsToForm />
<EstimateSyncAutoExRateToForm />
</Form>
</Formik>
</div>
{/*------- Dialogs -------*/}
<EstimateFormDialogs />
{/*------- Effects -------*/}
<EstimateIncrementSyncSettingsToForm />
<EstimateSyncAutoExRateToForm />
</Form>
</Formik>
);
}

View File

@@ -1,9 +1,7 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { x } from '@xstyled/emotion';
import { CLASSES } from '@/constants/classes';
import { Row, Col, Paper } from '@/components';
import { EstimateFormFooterLeft } from './EstimateFormFooterLeft';
import { EstimateFormFooterRight } from './EstimateFormFooterRight';
@@ -14,8 +12,8 @@ import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmen
*/
export default function EstiamteFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<EstimateFooterPaper>
<x.div mt={'20px'} px={'32px'} pb={'20px'} flex={1}>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<EstimateFormFooterLeft />
@@ -26,11 +24,7 @@ export default function EstiamteFormFooter() {
<EstimateFormFooterRight />
</Col>
</Row>
</EstimateFooterPaper>
</div>
</Paper>
</x.div>
);
}
const EstimateFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -1,22 +1,26 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { useFormikContext } from 'formik';
import { CLASSES } from '@/constants/classes';
import { x } from '@xstyled/emotion';
import EstimateFormHeaderFields from './EstimateFormHeaderFields';
import { getEntriesTotal } from '@/containers/Entries/utils';
import { PageFormBigNumber } from '@/components';
import { Group, PageFormBigNumber } from '@/components';
// Estimate form top header.
function EstimateFormHeader() {
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<Group
position="apart"
align={'flex-start'}
bg="white"
p="25px 32px"
borderBottom="1px solid #d2dce2"
>
<EstimateFormHeaderFields />
<EstimateFormBigTotal />
</div>
</Group>
);
}

View File

@@ -1,10 +1,9 @@
// @ts-nocheck
import styled from 'styled-components';
import classNames from 'classnames';
import { FormGroup, InputGroup, Position, Classes } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { Position, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { css } from '@emotion/css';
import {
FeatureCan,
FFormGroup,
@@ -13,15 +12,11 @@ import {
Icon,
CustomerDrawerLink,
CustomersSelect,
FInputGroup,
Stack,
FDateInput,
} from '@/components';
import {
momentFormatter,
tansformDateValue,
inputIntent,
handleDateChange,
} from '@/utils';
import { customersFieldShouldUpdate } from './utils';
import { CLASSES } from '@/constants/classes';
import { Features } from '@/constants';
import { ProjectsSelect } from '@/containers/Projects/components';
import {
@@ -31,15 +26,36 @@ import {
import { EstimateFormEstimateNumberField } from './EstimateFormEstimateNumberField';
import { useEstimateFormContext } from './EstimateFormProvider';
import { useCustomerUpdateExRate } from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
import { useTheme } from '@emotion/react';
import { Theme } from '@xstyled/emotion';
const getEstimateFieldsStyle = (theme: Theme) => css`
.${theme.bpPrefix}-form-group {
margin-bottom: 0;
&.${theme.bpPrefix}-inline {
max-width: 470px;
}
.${theme.bpPrefix}-label {
min-width: 160px;
font-weight: 500;
}
.${theme.bpPrefix}-form-content {
width: 100%;
}
}
`;
/**
* Estimate form header.
*/
export default function EstimateFormHeader() {
const theme = useTheme();
const { projects } = useEstimateFormContext();
const styleClassName = getEstimateFieldsStyle(theme);
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
<Stack spacing={18} flex={1} className={styleClassName}>
{/* ----------- Customer name ----------- */}
<EstimateFormCustomerSelect />
@@ -47,78 +63,55 @@ export default function EstimateFormHeader() {
<EstimateExchangeRateInputField />
{/* ----------- Estimate Date ----------- */}
<FastField name={'estimate_date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'estimate_date'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
className={classNames(CLASSES.FILL, 'form-group--estimate-date')}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="estimate_date" />}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('estimate_date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
<FFormGroup
name={'estimate_date'}
label={<T id={'estimate_date'} />}
labelInfo={<FieldRequiredHint />}
inline
fastField
>
<FDateInput
name={'estimate_date'}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
popoverProps={{ position: Position.BOTTOM_LEFT, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
fill: true,
}}
fill
fastField
/>
</FFormGroup>
{/* ----------- Expiration date ----------- */}
<FastField name={'expiration_date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'expiration_date'} />}
labelInfo={<FieldRequiredHint />}
inline={true}
className={classNames(
CLASSES.FORM_GROUP_LIST_SELECT,
CLASSES.FILL,
'form-group--expiration-date',
)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="expiration_date" />}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('expiration_date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
<FFormGroup
name={'expiration_date'}
label={<T id={'expiration_date'} />}
inline
fastField
>
<FDateInput
name={'expiration_date'}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
popoverProps={{ position: Position.BOTTOM_LEFT, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
fill: true,
}}
fill
fastField
/>
</FFormGroup>
{/* ----------- Estimate number ----------- */}
<EstimateFormEstimateNumberField />
{/* ----------- Reference ----------- */}
<FastField name={'reference'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference'} />}
inline={true}
className={classNames('form-group--reference', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference" />}
>
<InputGroup minimal={true} {...field} />
</FormGroup>
)}
</FastField>
<FFormGroup name={'reference'} label={<T id={'reference'} />} inline fill>
<FInputGroup name={'reference'} minimal={true} />
</FFormGroup>
{/*------------ Project name -----------*/}
<FeatureCan feature={Features.Projects}>
@@ -136,7 +129,7 @@ export default function EstimateFormHeader() {
/>
</FFormGroup>
</FeatureCan>
</div>
</Stack>
);
}

View File

@@ -1,12 +1,15 @@
// @ts-nocheck
import React from 'react';
import { useParams } from 'react-router-dom';
import '@/style/pages/SaleEstimate/PageForm.scss';
import { css } from '@emotion/css';
import EstimateForm from './EstimateForm';
import { EstimateFormProvider } from './EstimateFormProvider';
import {
EstimateFormProvider,
useEstimateFormContext,
} from './EstimateFormProvider';
import { AutoExchangeRateProvider } from '@/containers/Entries/AutoExchangeProvider';
import { DashboardInsider } from '@/components';
/**
* Estimate form page.
@@ -18,8 +21,24 @@ export default function EstimateFormPage() {
return (
<EstimateFormProvider estimateId={idInteger}>
<AutoExchangeRateProvider>
<EstimateForm />
<EstimateFormPageContent />
</AutoExchangeRateProvider>
</EstimateFormProvider>
);
}
export function EstimateFormPageContent() {
const { isBootLoading } = useEstimateFormContext();
return (
<DashboardInsider
loading={isBootLoading}
className={css`
min-height: calc(100vh - var(--top-offset));
max-height: calc(100vh - var(--top-offset));
`}
>
<EstimateForm />
</DashboardInsider>
);
}

View File

@@ -103,6 +103,13 @@ function EstimateFormProvider({ query, estimateId, ...props }) {
const isFeatureLoading =
isWarehouesLoading || isBranchesLoading || isProjectsLoading;
const isBootLoading =
isCustomersLoading ||
isItemsLoading ||
isEstimateLoading ||
isBrandingTemplatesLoading ||
isSaleEstimateStateLoading;
// Provider payload.
const provider = {
estimateId,
@@ -136,20 +143,11 @@ function EstimateFormProvider({ query, estimateId, ...props }) {
// Estimate state
saleEstimateState,
isSaleEstimateStateLoading,
isBootLoading,
};
const isLoading =
isCustomersLoading ||
isItemsLoading ||
isEstimateLoading ||
isBrandingTemplatesLoading ||
isSaleEstimateStateLoading;
return (
<DashboardInsider loading={isLoading} name={'estimate-form'}>
<EstimateFormContext.Provider value={provider} {...props} />
</DashboardInsider>
);
return <EstimateFormContext.Provider value={provider} {...props} />;
}
const useEstimateFormContext = () =>

View File

@@ -1,8 +1,7 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { x } from '@xstyled/emotion';
import { FastField } from 'formik';
import { CLASSES } from '@/constants/classes';
import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable';
import { useEstimateFormContext } from './EstimateFormProvider';
import { entriesFieldShouldUpdate } from './utils';
@@ -14,7 +13,7 @@ export default function EstimateFormItemsEntriesField() {
const { items } = useEstimateFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<x.div p="18px 32px 0">
<FastField
name={'entries'}
items={items}
@@ -38,6 +37,6 @@ export default function EstimateFormItemsEntriesField() {
/>
)}
</FastField>
</div>
</x.div>
);
}

View File

@@ -5,7 +5,10 @@ import {
InvoicePaperTemplate,
InvoicePaperTemplateProps,
} from './InvoicePaperTemplate';
import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize';
import {
ElementCustomize,
ElementCustomizeContent,
} from '../../../ElementCustomize/ElementCustomize';
import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
import { InvoiceCustomizeFormValues, InvoiceCustomizeState } from './types';
@@ -13,11 +16,13 @@ import { InvoiceCustomizeSchema } from './InvoiceCustomizeForm.schema';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useDrawerActions } from '@/hooks/state';
import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm';
import { initialValues } from './constants';
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
import { initialValues } from './constants';
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
/**
* Invoice branding template customize.
* @return {JSX.Element}
*/
export function InvoiceCustomizeContent() {
const { payload, name } = useDrawerContext();
@@ -36,6 +41,20 @@ export function InvoiceCustomizeContent() {
onSuccess={handleSuccess}
resource={'SaleInvoice'}
>
<InvoiceCustomizeFormContent />
</BrandingTemplateForm>
);
}
/**
* Invoice branding template customize preview and fields.
* @returns {JSX.Element}
*/
function InvoiceCustomizeFormContent() {
const isTemplateNameFilled = useIsTemplateNamedFilled();
return (
<ElementCustomizeContent>
<ElementCustomize.PaperTemplate>
<InvoicePaperTemplateFormConnected />
</ElementCustomize.PaperTemplate>
@@ -44,16 +63,20 @@ export function InvoiceCustomizeContent() {
<InvoiceCustomizeGeneralField />
</ElementCustomize.FieldsTab>
<ElementCustomize.FieldsTab id={'content'} label={'Content'}>
<ElementCustomize.FieldsTab
id={'content'}
label={'Content'}
tabProps={{ disabled: !isTemplateNameFilled }}
>
<InvoiceCustomizeContentFields />
</ElementCustomize.FieldsTab>
</BrandingTemplateForm>
</ElementCustomizeContent>
);
}
/**
* Injects the `InvoicePaperTemplate` component props from the form and branding states.
* @param Component
* @param Component
* @returns {JSX.Element}
*/
const withInvoicePreviewTemplateProps = <P extends object>(
@@ -61,13 +84,17 @@ const withInvoicePreviewTemplateProps = <P extends object>(
) => {
return (props: Omit<P, keyof InvoicePaperTemplateProps>) => {
const { values } = useFormikContext<InvoiceCustomizeFormValues>();
const { brandingState, } = useElementCustomizeContext();
const { brandingState } = useElementCustomizeContext();
const mergedProps: InvoicePaperTemplateProps = { ...brandingState, ...values };
const mergedProps: InvoicePaperTemplateProps = {
...brandingState,
...values,
};
return <Component {...(props as P)} {...mergedProps} />;
};
};
export const InvoicePaperTemplateFormConnected =
R.compose(withInvoicePreviewTemplateProps)(InvoicePaperTemplate);
export const InvoicePaperTemplateFormConnected = R.compose(
withInvoicePreviewTemplateProps,
)(InvoicePaperTemplate);

View File

@@ -4,7 +4,7 @@
background-color: #fff;
color: #111;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color), 0 10px 15px rgba(0, 0, 0, 0.05);
padding: 24px 30px;
padding: 30px 30px;
font-size: 12px;
position: relative;
margin: 0 auto;
@@ -12,7 +12,7 @@
height: 1123px;
}
.bigTitle{
font-size: 60px;
font-size: 30px;
margin: 0;
line-height: 1;
font-weight: 500;
@@ -105,7 +105,10 @@
overflow: hidden;
img{
max-width: 100%;
width: 100%;
height: 100%;
max-width: 260px;
max-height: 100px;
}
}
.logoImg {

View File

@@ -1,7 +1,7 @@
import React from 'react';
import clsx from 'classnames';
import { get } from 'lodash';
import { Box, Group, GroupProps, Stack } from '@/components';
import { Box, Group, GroupProps } from '@/components';
import styles from './InvoicePaperTemplate.module.scss';
export interface PaperTemplateProps {

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import React from 'react';
import {
Intent,
Button,
@@ -11,22 +11,25 @@ import {
MenuItem,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import classNames from 'classnames';
import { useFormikContext } from 'formik';
import { CLASSES } from '@/constants/classes';
import { If, Icon, FormattedMessage as T, Group, FSelect } from '@/components';
import { useInvoiceFormContext } from './InvoiceFormProvider';
import { useInvoiceFormBrandingTemplatesOptions } from './utils';
import { useDrawerActions } from '@/hooks/state';
import {
BrandingThemeFormGroup,
BrandingThemeSelectButton,
} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
import { PageForm } from '@/components/PageForm';
import { MoreIcon } from '@/icons/More';
import { DRAWERS } from '@/constants/drawers';
/**
* Invoice floating actions bar.
*/
export default function InvoiceFloatingActions() {
const history = useHistory();
const { openDrawer } = useDrawerActions();
// Formik context.
const { resetForm, submitForm, isSubmitting } = useFormikContext();
@@ -80,145 +83,171 @@ export default function InvoiceFloatingActions() {
resetForm();
};
// Handles the invoice customize button click.
const handleCustomizeBtnClick = () => {
openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'SaleInvoice' });
};
const brandingTemplatesOptions = useInvoiceFormBrandingTemplatesOptions();
return (
<Group
spacing={10}
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
>
{/* ----------- Save And Deliver ----------- */}
<If condition={!invoice || !invoice?.is_delivered}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitDeliverBtnClick}
text={<T id={'save_and_deliver'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'deliver_and_new'} />}
onClick={handleSubmitDeliverAndNewBtnClick}
/>
<MenuItem
text={<T id={'deliver_continue_editing'} />}
onClick={handleSubmitDeliverContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<PageForm.FooterActions spacing={10} position={'apart'}>
<Group spacing={10}>
{/* ----------- Save And Deliver ----------- */}
<If condition={!invoice || !invoice?.is_delivered}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
onClick={handleSubmitDeliverBtnClick}
text={<T id={'save_and_deliver'} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'deliver_and_new'} />}
onClick={handleSubmitDeliverAndNewBtnClick}
/>
<MenuItem
text={<T id={'deliver_continue_editing'} />}
onClick={handleSubmitDeliverContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={invoice && invoice?.is_delivered}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitDeliverBtnClick}
style={{ minWidth: '85px' }}
text={<T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDeliverAndNewBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={invoice && invoice?.is_delivered}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
onClick={handleSubmitDeliverBtnClick}
style={{ minWidth: '85px' }}
text={<T id={'save'} />}
/>
</Popover>
</ButtonGroup>
</If>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDeliverAndNewBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={invoice ? <T id={'reset'} /> : <T id={'clear'} />}
/>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}
onClick={handleCancelBtnClick}
text={<T id={'cancel'} />}
/>
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={invoice ? <T id={'reset'} /> : <T id={'clear'} />}
/>
</BrandingThemeFormGroup>
</Group>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}
onClick={handleCancelBtnClick}
text={<T id={'cancel'} />}
/>
</Group>
<Group spacing={0}>
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} />
)}
filterable={false}
popoverProps={{ minimal: true }}
/>
</BrandingThemeFormGroup>
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.TOP_RIGHT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
text={'Customize Templates'}
onClick={handleCustomizeBtnClick}
/>
</Menu>
}
>
<Button minimal icon={<MoreIcon height={'14px'} width={'14px'} />} />
</Popover>
</Group>
</PageForm.FooterActions>
);
}

View File

@@ -1,12 +1,11 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import { sumBy, isEmpty, defaultTo } from 'lodash';
import { useHistory } from 'react-router-dom';
import { CLASSES } from '@/constants/classes';
import { css } from '@emotion/css';
import {
getCreateInvoiceFormSchema,
getEditInvoiceFormSchema,
@@ -23,7 +22,7 @@ import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import withSettings from '@/containers/Settings/withSettings';
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
import { AppToaster } from '@/components';
import { AppToaster, Box } from '@/components';
import { compose, orderingLinesIndexes, transactionNumber } from '@/utils';
import { useInvoiceFormContext } from './InvoiceFormProvider';
import { InvoiceFormActions } from './InvoiceFormActions';
@@ -34,12 +33,16 @@ import {
transformValueToRequest,
resetFormState,
} from './utils';
import { InvoiceExchangeRateSync, InvoiceNoSyncSettingsToForm } from './components';
import {
InvoiceExchangeRateSync,
InvoiceNoSyncSettingsToForm,
} from './components';
import { PageForm } from '@/components/PageForm';
/**
* Invoice form.
*/
function InvoiceForm({
function InvoiceFormRoot({
// #withSettings
invoiceNextNumber,
invoiceNumberPrefix,
@@ -61,7 +64,7 @@ function InvoiceForm({
createInvoiceMutate,
editInvoiceMutate,
submitPayload,
saleInvoiceState
saleInvoiceState,
} = useInvoiceFormContext();
// Invoice number.
@@ -156,30 +159,36 @@ function InvoiceForm({
const EditInvoiceFormSchema = getEditInvoiceFormSchema();
return (
<div
className={classNames(
CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_STRIP_STYLE,
CLASSES.PAGE_FORM_INVOICE,
)}
<Formik
validationSchema={
isNewMode ? CreateInvoiceFormSchema : EditInvoiceFormSchema
}
initialValues={initialValues}
onSubmit={handleSubmit}
>
<Formik
validationSchema={
isNewMode ? CreateInvoiceFormSchema : EditInvoiceFormSchema
}
initialValues={initialValues}
onSubmit={handleSubmit}
<Form
className={css({
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
flex: 1,
})}
>
<Form>
<InvoiceFormTopBar />
<InvoiceFormHeader />
<PageForm flex={1}>
<PageForm.Body>
<InvoiceFormTopBar />
<InvoiceFormHeader />
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<InvoiceFormActions />
<InvoiceItemsEntriesEditorField />
</div>
<InvoiceFormFooter />
<InvoiceFloatingActions />
<Box p="18px 32px 0">
<InvoiceFormActions />
<InvoiceItemsEntriesEditorField />
</Box>
<InvoiceFormFooter />
</PageForm.Body>
<PageForm.Footer>
<InvoiceFloatingActions />
</PageForm.Footer>
{/*---------- Dialogs ----------*/}
<InvoiceFormDialogs />
@@ -187,13 +196,13 @@ function InvoiceForm({
{/*---------- Effects ----------*/}
<InvoiceNoSyncSettingsToForm />
<InvoiceExchangeRateSync />
</Form>
</Formik>
</div>
</PageForm>
</Form>
</Formik>
);
}
export default compose(
export const InvoiceForm = compose(
withDashboardActions,
withSettings(({ invoiceSettings }) => ({
invoiceNextNumber: invoiceSettings?.nextNumber,
@@ -203,4 +212,4 @@ export default compose(
invoiceTermsConditions: invoiceSettings?.termsConditions,
})),
withCurrentOrganization(),
)(InvoiceForm);
)(InvoiceFormRoot);

View File

@@ -1,18 +1,16 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { x } from '@xstyled/emotion';
import { CLASSES } from '@/constants/classes';
import { Paper, Row, Col } from '@/components';
import { Row, Col, Paper } from '@/components';
import { InvoiceFormFooterLeft } from './InvoiceFormFooterLeft';
import { InvoiceFormFooterRight } from './InvoiceFormFooterRight';
import { UploadAttachmentButton } from '../../../Attachments/UploadAttachmentButton';
export default function InvoiceFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<InvoiceFooterPaper>
<x.div mt={'20px'} px={'32px'} pb={'20px'} flex={1}>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<InvoiceFormFooterLeft />
@@ -23,11 +21,7 @@ export default function InvoiceFormFooter() {
<InvoiceFormFooterRight />
</Col>
</Row>
</InvoiceFooterPaper>
</div>
</Paper>
</x.div>
);
}
const InvoiceFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -1,13 +1,9 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { useFormikContext } from 'formik';
import { Group, PageFormBigNumber } from '@/components';
import InvoiceFormHeaderFields from './InvoiceFormHeaderFields';
import { CLASSES } from '@/constants/classes';
import { PageFormBigNumber } from '@/components';
import { useInvoiceDueAmount } from './utils';
/**
@@ -15,10 +11,16 @@ import { useInvoiceDueAmount } from './utils';
*/
function InvoiceFormHeader() {
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<Group
position="apart"
align={'flex-start'}
bg="white"
p="25px 32px"
borderBottom="1px solid #d2dce2"
>
<InvoiceFormHeaderFields />
<InvoiceFormBigTotal />
</div>
</Group>
);
}

View File

@@ -2,30 +2,27 @@
import React from 'react';
import styled from 'styled-components';
import classNames from 'classnames';
import { FormGroup, InputGroup, Position, Classes } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { Position, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { css } from '@emotion/css';
import { Theme, useTheme } from '@emotion/react';
import {
FFormGroup,
FormattedMessage as T,
Col,
Row,
CustomerDrawerLink,
FieldRequiredHint,
FeatureCan,
CustomersSelect,
Stack,
FInputGroup,
Icon,
FDateInput,
} from '@/components';
import {
momentFormatter,
tansformDateValue,
inputIntent,
handleDateChange,
} from '@/utils';
import { CLASSES } from '@/constants/classes';
import { customerNameFieldShouldUpdate } from './utils';
import { useInvoiceFormContext } from './InvoiceFormProvider';
import { useCustomerUpdateExRate } from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
import {
InvoiceExchangeRateInputField,
InvoiceProjectSelectButton,
@@ -36,99 +33,99 @@ import {
ProjectBillableEntriesLink,
} from '@/containers/Projects/components';
import { Features } from '@/constants';
import { useCustomerUpdateExRate } from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
const getInvoiceFieldsStyle = (theme: Theme) => css`
.${theme.bpPrefix}-form-group {
margin-bottom: 0;
&.${theme.bpPrefix}-inline {
max-width: 450px;
}
.${theme.bpPrefix}-label {
min-width: 150px;
font-weight: 500;
}
.${theme.bpPrefix}-form-content {
width: 100%;
}
}
`;
/**
* Invoice form header fields.
*/
export default function InvoiceFormHeaderFields() {
// Invoice form context.
const theme = useTheme();
const { projects } = useInvoiceFormContext();
const { values } = useFormikContext();
const invoiceFieldsClassName = getInvoiceFieldsStyle(theme);
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
<Stack spacing={18} flex={1} className={invoiceFieldsClassName}>
{/* ----------- Customer name ----------- */}
<InvoiceFormCustomerSelect />
{/* ----------- Exchange rate ----------- */}
<InvoiceExchangeRateInputField />
<Row>
<Col xs={6}>
{/* ----------- Invoice date ----------- */}
<FastField name={'invoice_date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'invoice_date'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--invoice-date', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="invoice_date" />}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('invoice_date', formattedDate);
})}
popoverProps={{
position: Position.BOTTOM_LEFT,
minimal: true,
}}
/>
</FormGroup>
)}
</FastField>
</Col>
{/* ----------- Invoice date ----------- */}
<FFormGroup
name={'invoice_date'}
label={<T id={'invoice_date'} />}
labelInfo={<FieldRequiredHint />}
inline
fastField
>
<FDateInput
name={'invoice_date'}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
popoverProps={{
position: Position.BOTTOM_LEFT,
minimal: true,
fill: true,
}}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
fill
fastField
/>
</FFormGroup>
<Col xs={6}>
{/* ----------- Due date ----------- */}
<FastField name={'due_date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'due_date'} />}
labelInfo={<FieldRequiredHint />}
inline={true}
className={classNames('form-group--due-date', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="due_date" />}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('due_date', formattedDate);
})}
popoverProps={{
position: Position.BOTTOM_LEFT,
minimal: true,
}}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/* ----------- Due date ----------- */}
<FFormGroup
name={'due_date'}
label={<T id={'due_date'} />}
labelInfo={<FieldRequiredHint />}
inline
fastField
>
<FDateInput
name={'due_date'}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
popoverProps={{
position: Position.BOTTOM_LEFT,
minimal: true,
fill: true,
}}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
fill: true,
}}
fill
fastField
/>
</FFormGroup>
{/* ----------- Invoice number ----------- */}
<InvoiceFormInvoiceNumberField />
{/* ----------- Reference ----------- */}
<FastField name={'reference_no'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference'} />}
inline={true}
className={classNames('form-group--reference', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
>
<InputGroup minimal={true} {...field} />
</FormGroup>
)}
</FastField>
<FFormGroup name={'reference_no'} label={<T id={'reference'} />} inline>
<FInputGroup name={'reference_no'} minimal={true} />
</FFormGroup>
{/*------------ Project name -----------*/}
<FeatureCan feature={Features.Projects}>
@@ -151,7 +148,7 @@ export default function InvoiceFormHeaderFields() {
)}
</FFormGroup>
</FeatureCan>
</div>
</Stack>
);
}

View File

@@ -1,25 +1,43 @@
// @ts-nocheck
import React from 'react';
import { useParams } from 'react-router-dom';
import '@/style/pages/SaleInvoice/PageForm.scss';
import InvoiceForm from './InvoiceForm';
import { InvoiceFormProvider } from './InvoiceFormProvider';
import { css } from '@emotion/css';
import { InvoiceForm } from './InvoiceForm';
import {
InvoiceFormProvider,
useInvoiceFormContext,
} from './InvoiceFormProvider';
import { AutoExchangeRateProvider } from '@/containers/Entries/AutoExchangeProvider';
import { DashboardInsider } from '@/components';
/**
* Invoice form page.
*/
export default function InvoiceFormPage() {
const { id } = useParams();
const idAsInteger = parseInt(id, 10);
const invoiceId = parseInt(id, 10);
return (
<InvoiceFormProvider invoiceId={idAsInteger}>
<InvoiceFormProvider invoiceId={invoiceId}>
<AutoExchangeRateProvider>
<InvoiceForm />
<InvoiceFormPageContent />
</AutoExchangeRateProvider>
</InvoiceFormProvider>
);
}
function InvoiceFormPageContent() {
const { isBootLoading } = useInvoiceFormContext();
return (
<DashboardInsider
loading={isBootLoading}
className={css`
min-height: calc(100vh - var(--top-offset));
max-height: calc(100vh - var(--top-offset));
`}
>
<InvoiceForm />
</DashboardInsider>
);
}

View File

@@ -132,6 +132,14 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
isProjectsLoading ||
isBrandingTemplatesLoading;
const isBootLoading =
isInvoiceLoading ||
isItemsLoading ||
isCustomersLoading ||
isEstimateLoading ||
isSettingsLoading ||
isInvoiceStateLoading;
const provider = {
invoice,
items,
@@ -170,21 +178,11 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
// Invoice state
saleInvoiceState,
isInvoiceStateLoading,
isBootLoading,
};
const isLoading =
isInvoiceLoading ||
isItemsLoading ||
isCustomersLoading ||
isEstimateLoading ||
isSettingsLoading ||
isInvoiceStateLoading;
return (
<DashboardInsider loading={isLoading} name={'invoice-form'}>
<InvoiceFormContext.Provider value={provider} {...props} />
</DashboardInsider>
);
return <InvoiceFormContext.Provider value={provider} {...props} />;
}
const useInvoiceFormContext = () =>

View File

@@ -1,6 +1,7 @@
// @ts-nocheck
import React from 'react';
import { FastField } from 'formik';
import { x } from '@xstyled/emotion';
import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable';
import { useInvoiceFormContext } from './InvoiceFormProvider';
import { entriesFieldShouldUpdate } from './utils';

View File

@@ -1,6 +1,5 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import {
Intent,
Button,
@@ -12,15 +11,18 @@ import {
MenuItem,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { FSelect, Group, Icon, FormattedMessage as T } from '@/components';
import { useFormikContext } from 'formik';
import { FSelect, Group, Icon, FormattedMessage as T } from '@/components';
import { useDrawerActions } from '@/hooks/state';
import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider';
import { CLASSES } from '@/constants/classes';
import {
BrandingThemeFormGroup,
BrandingThemeSelectButton,
} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
import { usePaymentReceivedFormBrandingTemplatesOptions } from './utils';
import { PageForm } from '@/components/PageForm';
import { MoreIcon } from '@/icons/More';
import { DRAWERS } from '@/constants/drawers';
/**
* Payment receive floating actions bar.
@@ -35,6 +37,8 @@ export default function PaymentReceiveFormFloatingActions() {
// History context.
const history = useHistory();
const { openDrawer } = useDrawerActions();
// Handle submit button click.
const handleSubmitBtnClick = (event) => {
setSubmitPayload({ redirect: true });
@@ -58,84 +62,109 @@ export default function PaymentReceiveFormFloatingActions() {
submitForm();
};
// Handles the invoice customize button click.
const handleCustomizeBtnClick = () => {
openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'PaymentReceive' });
};
const brandingTemplatesOpts =
usePaymentReceivedFormBrandingTemplatesOptions();
return (
<Group
spacing={10}
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
>
{/* ----------- Save and New ----------- */}
<ButtonGroup>
<PageForm.FooterActions position={'apart'} spacing={20}>
<Group spacing={10}>
{/* ----------- Save and New ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
onClick={handleSubmitBtnClick}
style={{ minWidth: '85px' }}
text={!isNewMode ? <T id={'edit'} /> : <T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitAndNewClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
onClick={handleSubmitBtnClick}
style={{ minWidth: '85px' }}
text={!isNewMode ? <T id={'edit'} /> : <T id={'save'} />}
onClick={handleClearBtnClick}
text={!isNewMode ? <T id={'reset'} /> : <T id={'clear'} />}
/>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleCancelBtnClick}
text={<T id={'cancel'} />}
/>
</Group>
<Group spacing={0}>
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOpts}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
/>
</BrandingThemeFormGroup>
{/* ----------- Setting Select ----------- */}
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.TOP_RIGHT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitAndNewClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitContinueEditingBtnClick}
text={'Customize Templates'}
onClick={handleCustomizeBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
<Button minimal icon={<MoreIcon height={'14px'} width={'14px'} />} />
</Popover>
</ButtonGroup>
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={!isNewMode ? <T id={'reset'} /> : <T id={'clear'} />}
/>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleCancelBtnClick}
text={<T id={'cancel'} />}
/>
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOpts}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
/>
</BrandingThemeFormGroup>
</Group>
</Group>
</PageForm.FooterActions>
);
}

View File

@@ -1,15 +1,12 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import React from 'react';
import { isEmpty, defaultTo } from 'lodash';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { Formik, Form } from 'formik';
import { useHistory } from 'react-router-dom';
import { Intent } from '@blueprintjs/core';
import { css } from '@emotion/css';
import '@/style/pages/PaymentReceive/PageForm.scss';
import { CLASSES } from '@/constants/classes';
import PaymentReceiveHeader from './PaymentReceiveFormHeader';
import PaymentReceiveFormBody from './PaymentReceiveFormBody';
import PaymentReceiveFloatingActions from './PaymentReceiveFloatingActions';
@@ -40,11 +37,12 @@ import {
getExceededAmountFromValues,
} from './utils';
import { PaymentReceiveSyncIncrementSettingsToForm } from './components';
import { PageForm } from '@/components/PageForm';
/**
* Payment Receive form.
*/
function PaymentReceiveForm({
function PaymentReceiveFormRoot({
// #withSettings
preferredDepositAccount,
paymentReceiveNextNumber,
@@ -159,30 +157,37 @@ function PaymentReceiveForm({
return createPaymentReceiveMutate(form).then(onSaved).catch(onError);
}
};
return (
<div
className={classNames(
CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_STRIP_STYLE,
CLASSES.PAGE_FORM_PAYMENT_RECEIVE,
)}
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={
isNewMode
? CreatePaymentReceiveFormSchema
: EditPaymentReceiveFormSchema
}
>
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={
isNewMode
? CreatePaymentReceiveFormSchema
: EditPaymentReceiveFormSchema
}
<Form
className={css({
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
flex: 1,
})}
>
<Form>
<PageForm flex={1}>
<PaymentReceiveInnerProvider>
<PaymentReceiveFormTopBar />
<PaymentReceiveHeader />
<PaymentReceiveFormBody />
<PaymentReceiveFormFooter />
<PaymentReceiveFloatingActions />
<PageForm.Body>
<PaymentReceiveFormTopBar />
<PaymentReceiveHeader />
<PaymentReceiveFormBody />
<PaymentReceiveFormFooter />
</PageForm.Body>
<PageForm.Footer>
<PaymentReceiveFloatingActions />
</PageForm.Footer>
{/* ------- Effects ------- */}
<PaymentReceiveSyncIncrementSettingsToForm />
@@ -191,13 +196,13 @@ function PaymentReceiveForm({
<PaymentReceiveFormAlerts />
<PaymentReceiveFormDialogs />
</PaymentReceiveInnerProvider>
</Form>
</Formik>
</div>
</PageForm>
</Form>
</Formik>
);
}
export default compose(
export const PaymentReceivedForm = compose(
withSettings(({ paymentReceiveSettings }) => ({
paymentReceiveSettings,
paymentReceiveNextNumber: paymentReceiveSettings?.nextNumber,
@@ -207,4 +212,4 @@ export default compose(
})),
withCurrentOrganization(),
withDialogActions,
)(PaymentReceiveForm);
)(PaymentReceiveFormRoot);

View File

@@ -2,15 +2,14 @@
import React from 'react';
import { FastField } from 'formik';
import PaymentReceiveItemsTable from './PaymentReceiveItemsTable';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
import { Box } from '@/components';
/**
* Payment Receive form body.
*/
export default function PaymentReceiveFormBody() {
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<Box p="18px 32px 0">
<FastField name={'entries'}>
{({ form: { values, setFieldValue }, field: { value } }) => (
<PaymentReceiveItemsTable
@@ -22,6 +21,6 @@ export default function PaymentReceiveFormBody() {
/>
)}
</FastField>
</div>
</Box>
);
}

View File

@@ -1,21 +1,19 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { x } from '@xstyled/emotion';
import { CLASSES } from '@/constants/classes';
import { Row, Col, Paper } from '@/components';
import { Row, Col, Paper, Box } from '@/components';
import { PaymentReceiveFormFootetLeft } from './PaymentReceiveFormFootetLeft';
import { PaymentReceiveFormFootetRight } from './PaymentReceiveFormFootetRight';
import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmentButton';
/**
* Payment receive form footer.
* Payment received form footer.
*/
export default function PaymentReceiveFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<PaymentReceiveFooterPaper>
<Box mt={'20px'} px={'32px'} pb={'20px'} flex={1}>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<PaymentReceiveFormFootetLeft />
@@ -26,11 +24,7 @@ export default function PaymentReceiveFormFooter() {
<PaymentReceiveFormFootetRight />
</Col>
</Row>
</PaymentReceiveFooterPaper>
</div>
</Paper>
</Box>
);
}
const PaymentReceiveFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -1,9 +1,8 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import React from 'react';
import classNames from 'classnames';
import { sumBy } from 'lodash';
import { useFormikContext } from 'formik';
import { Money } from '@/components';
import { Group, Money } from '@/components';
import { FormattedMessage as T } from '@/components';
import { CLASSES } from '@/constants/classes';
@@ -14,12 +13,16 @@ import PaymentReceiveHeaderFields from './PaymentReceiveHeaderFields';
*/
function PaymentReceiveFormHeader() {
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
<PaymentReceiveHeaderFields />
<PaymentReceiveFormBigTotal />
</div>
</div>
<Group
position="apart"
align={'flex-start'}
bg="white"
p="25px 32px"
borderBottom="1px solid #d2dce2"
>
<PaymentReceiveHeaderFields />
<PaymentReceiveFormBigTotal />
</Group>
);
}

View File

@@ -1,20 +1,40 @@
// @ts-nocheck
import React from 'react';
import { useParams } from 'react-router-dom';
import { PaymentReceiveFormProvider } from './PaymentReceiveFormProvider';
import PaymentReceiveForm from './PaymentReceiveForm';
import { css } from '@emotion/css';
import {
PaymentReceiveFormProvider,
usePaymentReceiveFormContext,
} from './PaymentReceiveFormProvider';
import { PaymentReceivedForm } from './PaymentReceiveForm';
import { DashboardInsider } from '@/components';
/**
* Payment receive form page.
* Payment received form page.
*/
export default function PaymentReceiveFormPage() {
const { id: paymentReceiveId } = useParams();
const paymentReceiveIdInt = parseInt(paymentReceiveId, 10);
const { id } = useParams();
const paymentReceivedId = parseInt(id, 10);
return (
<PaymentReceiveFormProvider paymentReceiveId={paymentReceiveIdInt}>
<PaymentReceiveForm paymentReceiveId={paymentReceiveIdInt} />
<PaymentReceiveFormProvider paymentReceiveId={paymentReceivedId}>
<PaymentReceivedFormPageContent />
</PaymentReceiveFormProvider>
);
}
function PaymentReceivedFormPageContent() {
const { isBootLoading } = usePaymentReceiveFormContext();
return (
<DashboardInsider
loading={isBootLoading}
className={css`
min-height: calc(100vh - var(--top-offset));
max-height: calc(100vh - var(--top-offset));
`}
>
<PaymentReceivedForm />
</DashboardInsider>
);
}

View File

@@ -97,6 +97,13 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
const [isExcessConfirmed, setIsExcessConfirmed] = useState<boolean>(false);
const isBootLoading =
isPaymentLoading ||
isAccountsLoading ||
isCustomersLoading ||
isBrandingTemplatesLoading ||
isPaymentReceivedStateLoading;
// Provider payload.
const provider = {
paymentReceiveId,
@@ -131,20 +138,11 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
// Payment received state
isPaymentReceivedStateLoading,
paymentReceivedState,
isBootLoading,
};
const isLoading =
isPaymentLoading ||
isAccountsLoading ||
isCustomersLoading ||
isBrandingTemplatesLoading ||
isPaymentReceivedStateLoading;
return (
<DashboardInsider loading={isLoading} name={'payment-receive-form'}>
<PaymentReceiveFormContext.Provider value={provider} {...props} />
</DashboardInsider>
);
return <PaymentReceiveFormContext.Provider value={provider} {...props} />;
}
const usePaymentReceiveFormContext = () =>

View File

@@ -3,31 +3,26 @@ import React, { useMemo } from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import {
FormGroup,
InputGroup,
Position,
Classes,
ControlGroup,
Button,
} from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { isEmpty, toSafeInteger } from 'lodash';
import { FastField, useFormikContext, ErrorMessage } from 'formik';
import { useFormikContext } from 'formik';
import { css } from '@emotion/css';
import { Theme, useTheme } from '@emotion/react';
import {
FeatureCan,
CustomersSelect,
FormattedMessage as T,
FMoneyInputGroup,
Stack,
FDateInput,
} from '@/components';
import { CLASSES } from '@/constants/classes';
import {
safeSumBy,
momentFormatter,
tansformDateValue,
handleDateChange,
inputIntent,
} from '@/utils';
import { safeSumBy } from '@/utils';
import {
FFormGroup,
AccountsSelect,
@@ -55,10 +50,29 @@ import {
import { Features } from '@/constants';
import { PaymentReceivePaymentNoField } from './PaymentReceivePaymentNoField';
const getHeaderFieldsStyle = (theme: Theme) => css`
.${theme.bpPrefix}-form-group {
margin-bottom: 0;
&.${theme.bpPrefix}-inline {
max-width: 470px;
}
.${theme.bpPrefix}-label {
min-width: 160px;
}
.${theme.bpPrefix}-form-content {
width: 100%;
}
}
`;
/**
* Payment receive header fields.
*/
export default function PaymentReceiveHeaderFields() {
const theme = useTheme();
const styleClassName = getHeaderFieldsStyle(theme);
// Payment receive form context.
const { accounts, projects } = usePaymentReceiveFormContext();
@@ -88,7 +102,7 @@ export default function PaymentReceiveHeaderFields() {
};
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
<Stack spacing={18} flex={1} className={styleClassName}>
{/* ------------- Customer name ------------- */}
<PaymentReceiveCustomerSelect />
@@ -97,31 +111,28 @@ export default function PaymentReceiveHeaderFields() {
name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }}
/>
{/* ------------- Payment date ------------- */}
<FastField name={'payment_date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'payment_date'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="payment_date" />}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('payment_date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
<FFormGroup
name={'payment_date'}
label={<T id={'payment_date'} />}
labelInfo={<FieldRequiredHint />}
inline
fastField
>
<FDateInput
name={'payment_date'}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
popoverProps={{ position: Position.BOTTOM_LEFT, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
fill: true,
}}
fill
fastField
/>
</FFormGroup>
{/* ------------ Full amount ------------ */}
<FFormGroup
@@ -143,9 +154,25 @@ export default function PaymentReceiveHeaderFields() {
{!isEmpty(entries) && (
<Button
onClick={handleReceiveFullAmountClick}
className={'receive-full-amount'}
small={true}
minimal={true}
className={css`
&:not([class*='${theme.bpPrefix}-intent-']) {
&.${theme.bpPrefix}-minimal {
width: auto;
padding: 0;
min-height: auto;
font-size: 12px;
margin-top: 4px;
background-color: transparent;
color: #0052cc;
&:hover {
text-decoration: underline;
}
}
}
`}
small
minimal
>
<T id={'receive_full_amount'} /> (
<Money amount={totalDueAmount} currency={currency_code} />)
@@ -186,10 +213,10 @@ export default function PaymentReceiveHeaderFields() {
<FFormGroup
name={'reference_no'}
label={<T id={'reference'} />}
inline={true}
inline
fastField
>
<InputGroup name={'reference_no'} minimal={true} fastField />
<InputGroup name={'reference_no'} minimal fastField />
</FFormGroup>
{/*------------ Project name -----------*/}
@@ -208,7 +235,7 @@ export default function PaymentReceiveHeaderFields() {
/>
</FFormGroup>
</FeatureCan>
</div>
</Stack>
);
}

View File

@@ -1,14 +1,24 @@
import { useFormikContext } from 'formik';
import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize';
import {
ElementCustomize,
ElementCustomizeContent,
} from '../../../ElementCustomize/ElementCustomize';
import { PaymentReceivedCustomizeGeneralField } from './PaymentReceivedCustomizeFieldsGeneral';
import { PaymentReceivedCustomizeContentFields } from './PaymentReceivedCustomizeFieldsContent';
import { PaymentReceivedCustomizeValues, PaymentReceivedPreviewState } from './types';
import { PaymentReceivedPaperTemplate, PaymentReceivedPaperTemplateProps } from './PaymentReceivedPaperTemplate';
import {
PaymentReceivedCustomizeValues,
PaymentReceivedPreviewState,
} from './types';
import {
PaymentReceivedPaperTemplate,
PaymentReceivedPaperTemplateProps,
} from './PaymentReceivedPaperTemplate';
import { initialValues } from './constants';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useDrawerActions } from '@/hooks/state';
import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm';
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
export function PaymentReceivedCustomizeContent() {
const { payload, name } = useDrawerContext();
@@ -21,12 +31,25 @@ export function PaymentReceivedCustomizeContent() {
};
return (
<BrandingTemplateForm<PaymentReceivedCustomizeValues, PaymentReceivedPreviewState>
<BrandingTemplateForm<
PaymentReceivedCustomizeValues,
PaymentReceivedPreviewState
>
templateId={templateId}
defaultValues={initialValues}
onSuccess={handleSuccess}
resource={'PaymentReceive'}
>
<PaymentReceivedCustomizeFormContent />
</BrandingTemplateForm>
);
}
function PaymentReceivedCustomizeFormContent() {
const isTemplateNameFilled = useIsTemplateNamedFilled();
return (
<ElementCustomizeContent>
<ElementCustomize.PaperTemplate>
<PaymentReceivedPaperTemplateFormConnected />
</ElementCustomize.PaperTemplate>
@@ -35,10 +58,14 @@ export function PaymentReceivedCustomizeContent() {
<PaymentReceivedCustomizeGeneralField />
</ElementCustomize.FieldsTab>
<ElementCustomize.FieldsTab id={'content'} label={'Content'}>
<ElementCustomize.FieldsTab
id={'content'}
label={'Content'}
tabProps={{ disabled: !isTemplateNameFilled }}
>
<PaymentReceivedCustomizeContentFields />
</ElementCustomize.FieldsTab>
</BrandingTemplateForm>
</ElementCustomizeContent>
);
}
@@ -46,7 +73,10 @@ function PaymentReceivedPaperTemplateFormConnected() {
const { values } = useFormikContext<PaymentReceivedCustomizeValues>();
const { brandingState } = useElementCustomizeContext();
const paperTemplateProps: PaymentReceivedPaperTemplateProps = { ...brandingState, ...values };
const paperTemplateProps: PaymentReceivedPaperTemplateProps = {
...brandingState,
...values,
};
return <PaymentReceivedPaperTemplate {...paperTemplateProps} />;
}

View File

@@ -1,14 +1,21 @@
import { useFormikContext } from 'formik';
import { ElementCustomize } from '@/containers/ElementCustomize/ElementCustomize';
import {
ElementCustomize,
ElementCustomizeContent,
} from '@/containers/ElementCustomize/ElementCustomize';
import { ReceiptCustomizeGeneralField } from './ReceiptCustomizeFieldsGeneral';
import { ReceiptCustomizeFieldsContent } from './ReceiptCustomizeFieldsContent';
import { ReceiptPaperTemplate, ReceiptPaperTemplateProps } from './ReceiptPaperTemplate';
import {
ReceiptPaperTemplate,
ReceiptPaperTemplateProps,
} from './ReceiptPaperTemplate';
import { EstimateBrandingState, ReceiptCustomizeValues } from './types';
import { initialValues } from './constants';
import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm';
import { useDrawerActions } from '@/hooks/state';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
export function ReceiptCustomizeContent() {
const { payload, name } = useDrawerContext();
@@ -26,6 +33,16 @@ export function ReceiptCustomizeContent() {
defaultValues={initialValues}
onSuccess={handleFormSuccess}
>
<ReceiptCustomizeFormContent />
</BrandingTemplateForm>
);
}
function ReceiptCustomizeFormContent() {
const isTemplateNameFilled = useIsTemplateNamedFilled();
return (
<ElementCustomizeContent>
<ElementCustomize.PaperTemplate>
<ReceiptPaperTemplateFormConnected />
</ElementCustomize.PaperTemplate>
@@ -34,10 +51,14 @@ export function ReceiptCustomizeContent() {
<ReceiptCustomizeGeneralField />
</ElementCustomize.FieldsTab>
<ElementCustomize.FieldsTab id={'content'} label={'Content'}>
<ElementCustomize.FieldsTab
id={'content'}
label={'Content'}
tabProps={{ disabled: !isTemplateNameFilled }}
>
<ReceiptCustomizeFieldsContent />
</ElementCustomize.FieldsTab>
</BrandingTemplateForm>
</ElementCustomizeContent>
);
}
@@ -45,7 +66,10 @@ function ReceiptPaperTemplateFormConnected() {
const { values } = useFormikContext<ReceiptCustomizeValues>();
const { brandingState } = useElementCustomizeContext();
const mergedProps: ReceiptPaperTemplateProps = { ...brandingState, ...values, };
const mergedProps: ReceiptPaperTemplateProps = {
...brandingState,
...values,
};
return <ReceiptPaperTemplate {...mergedProps} />;
}

View File

@@ -1,12 +1,11 @@
// @ts-nocheck
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import { sumBy, isEmpty } from 'lodash';
import { useHistory } from 'react-router-dom';
import { css } from '@emotion/css';
import { CLASSES } from '@/constants/classes';
import {
EditReceiptFormSchema,
CreateReceiptFormSchema,
@@ -17,7 +16,7 @@ import { useReceiptFormContext } from './ReceiptFormProvider';
import ReceiptFromHeader from './ReceiptFormHeader';
import ReceiptItemsEntriesEditor from './ReceiptItemsEntriesEditor';
import ReceiptFormFloatingActions from './ReceiptFormFloatingActions';
import ReceiptFormFooter from './ReceiptFormFooter';
import { ReceiptFormFooter } from './ReceiptFormFooter';
import ReceiptFormDialogs from './ReceiptFormDialogs';
import ReceiptFormTopBar from './ReceiptFormTopbar';
@@ -38,11 +37,12 @@ import {
ReceiptSyncAutoExRateToForm,
ReceiptSyncIncrementSettingsToForm,
} from './components';
import { PageForm } from '@/components/PageForm';
/**
* Receipt form.
*/
function ReceiptForm({
function ReceiptFormRoot({
// #withSettings
receiptNextNumber,
receiptNumberPrefix,
@@ -150,40 +150,46 @@ function ReceiptForm({
};
return (
<div
className={classNames(
CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_STRIP_STYLE,
CLASSES.PAGE_FORM_RECEIPT,
)}
<Formik
validationSchema={
isNewMode ? CreateReceiptFormSchema : EditReceiptFormSchema
}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<Formik
validationSchema={
isNewMode ? CreateReceiptFormSchema : EditReceiptFormSchema
}
initialValues={initialValues}
onSubmit={handleFormSubmit}
<Form
className={css({
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
flex: 1,
})}
>
<Form>
<ReceiptFormTopBar />
<ReceiptFromHeader />
<ReceiptItemsEntriesEditor />
<ReceiptFormFooter />
<ReceiptFormFloatingActions />
<PageForm flex={1}>
<PageForm.Body>
<ReceiptFormTopBar />
<ReceiptFromHeader />
<ReceiptItemsEntriesEditor />
<ReceiptFormFooter />
</PageForm.Body>
{/*---------- Dialogs ---------*/}
<ReceiptFormDialogs />
css
{/*---------- Effects ---------*/}
<ReceiptSyncIncrementSettingsToForm />
<ReceiptSyncAutoExRateToForm />
</Form>
</Formik>
</div>
<PageForm.Footer>
<ReceiptFormFloatingActions />
</PageForm.Footer>
</PageForm>
{/*---------- Dialogs ---------*/}
<ReceiptFormDialogs />
{/*---------- Effects ---------*/}
<ReceiptSyncIncrementSettingsToForm />
<ReceiptSyncAutoExRateToForm />
</Form>
</Formik>
);
}
export default compose(
export const ReceiptForm = compose(
withDashboardActions,
withSettings(({ receiptSettings }) => ({
receiptNextNumber: receiptSettings?.nextNumber,
@@ -194,4 +200,4 @@ export default compose(
preferredDepositAccount: receiptSettings?.preferredDepositAccount,
})),
withCurrentOrganization(),
)(ReceiptForm);
)(ReceiptFormRoot);

View File

@@ -1,6 +1,5 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import {
Intent,
Button,
@@ -14,14 +13,17 @@ import {
import { FSelect, Group, FormattedMessage as T } from '@/components';
import { useFormikContext } from 'formik';
import { useHistory } from 'react-router-dom';
import { CLASSES } from '@/constants/classes';
import { If, Icon } from '@/components';
import { useReceiptFormContext } from './ReceiptFormProvider';
import { useReceiptFormBrandingTemplatesOptions } from './utils';
import { useDrawerActions } from '@/hooks/state';
import {
BrandingThemeFormGroup,
BrandingThemeSelectButton,
} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
import { PageForm } from '@/components/PageForm';
import { MoreIcon } from '@/icons/More';
import { DRAWERS } from '@/constants/drawers';
/**
* Receipt floating actions bar.
@@ -30,6 +32,8 @@ export default function ReceiptFormFloatingActions() {
// History context.
const history = useHistory();
const { openDrawer } = useDrawerActions();
// Formik context.
const { resetForm, submitForm, isSubmitting } = useFormikContext();
@@ -81,142 +85,170 @@ export default function ReceiptFormFloatingActions() {
resetForm();
};
// Handles the invoice customize button click.
const handleCustomizeBtnClick = () => {
openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'SaleReceipt' });
};
const brandingTemplatesOptions = useReceiptFormBrandingTemplatesOptions();
return (
<Group
spacing={10}
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
>
{/* ----------- Save And Close ----------- */}
<If condition={!receipt || !receipt?.is_closed}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitCloseBtnClick}
text={<T id={'save_close'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'close_and_new'} />}
onClick={handleSubmitCloseAndNewBtnClick}
/>
<MenuItem
text={<T id={'close_continue_editing'} />}
onClick={handleSubmitCloseContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<PageForm.FooterActions spacing={10} position="apart">
<Group spacing={10}>
{/* ----------- Save And Close ----------- */}
<If condition={!receipt || !receipt?.is_closed}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
onClick={handleSubmitCloseBtnClick}
text={<T id={'save_close'} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'close_and_new'} />}
onClick={handleSubmitCloseAndNewBtnClick}
/>
<MenuItem
text={<T id={'close_continue_editing'} />}
onClick={handleSubmitCloseContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={receipt && receipt?.is_closed}>
<ButtonGroup>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitCloseBtnClick}
style={{ minWidth: '85px' }}
text={<T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitCloseAndNewBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={receipt ? <T id={'reset'} /> : <T id={'clear'} />}
/>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}
onClick={handleCancelBtnClick}
text={<T id={'cancel'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
{/* ----------- Save and New ----------- */}
<If condition={receipt && receipt?.is_closed}>
<ButtonGroup>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitCloseBtnClick}
style={{ minWidth: '85px' }}
text={<T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitCloseAndNewBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={receipt ? <T id={'reset'} /> : <T id={'clear'} />}
/>
</BrandingThemeFormGroup>
</Group>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}
onClick={handleCancelBtnClick}
text={<T id={'cancel'} />}
/>
</Group>
<Group spacing={0}>
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
/>
</BrandingThemeFormGroup>
{/* ----------- Setting Select ----------- */}
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.TOP_RIGHT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
text={'Customize Templates'}
onClick={handleCustomizeBtnClick}
/>
</Menu>
}
>
<Button minimal icon={<MoreIcon height={'14px'} width={'14px'} />} />
</Popover>
</Group>
</PageForm.FooterActions>
);
}

View File

@@ -1,18 +1,15 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { CLASSES } from '@/constants/classes';
import { x } from '@xstyled/emotion';
import { Paper, Row, Col } from '@/components';
import { ReceiptFormFooterLeft } from './ReceiptFormFooterLeft';
import { ReceiptFormFooterRight } from './ReceiptFormFooterRight';
import { UploadAttachmentButton } from '@/containers/Attachments/UploadAttachmentButton';
export default function ReceiptFormFooter({}) {
export function ReceiptFormFooter({}) {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<ReceiptFooterPaper>
<x.div mt={'20px'} px={'32px'} pb={'20px'} flex={1}>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<ReceiptFormFooterLeft />
@@ -23,11 +20,7 @@ export default function ReceiptFormFooter({}) {
<ReceiptFormFooterRight />
</Col>
</Row>
</ReceiptFooterPaper>
</div>
</Paper>
</x.div>
);
}
const ReceiptFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -1,13 +1,9 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { useFormikContext } from 'formik';
import { CLASSES } from '@/constants/classes';
import { PageFormBigNumber } from '@/components';
import { Group, PageFormBigNumber } from '@/components';
import ReceiptFormHeaderFields from './ReceiptFormHeaderFields';
import { getEntriesTotal } from '@/containers/Entries/utils';
/**
@@ -18,12 +14,19 @@ function ReceiptFormHeader({
onReceiptNumberChanged,
}) {
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<Group
position="apart"
align={'flex-start'}
display="flex"
bg="white"
p="25px 32px"
borderBottom="1px solid #d2dce2"
>
<ReceiptFormHeaderFields
onReceiptNumberChanged={onReceiptNumberChanged}
/>
<ReceiptFormHeaderBigTotal />
</div>
</Group>
);
}

View File

@@ -1,12 +1,12 @@
// @ts-nocheck
import React, { useCallback } from 'react';
import React from 'react';
import styled from 'styled-components';
import classNames from 'classnames';
import { FormGroup, InputGroup, Position, Classes } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { useFormikContext } from 'formik';
import { Position, Classes } from '@blueprintjs/core';
import { css } from '@emotion/css';
import { Theme, useTheme } from '@emotion/react';
import { CLASSES } from '@/constants/classes';
import { ACCOUNT_TYPE } from '@/constants/accountTypes';
import { Features } from '@/constants';
import {
@@ -18,14 +18,11 @@ import {
CustomerDrawerLink,
FormattedMessage as T,
FeatureCan,
FInputGroup,
Stack,
FDateInput,
} from '@/components';
import { ProjectsSelect } from '@/containers/Projects/components';
import {
momentFormatter,
tansformDateValue,
handleDateChange,
inputIntent,
} from '@/utils';
import { useReceiptFormContext } from './ReceiptFormProvider';
import { accountsFieldShouldUpdate, customersFieldShouldUpdate } from './utils';
import {
@@ -35,14 +32,33 @@ import {
import { ReceiptFormReceiptNumberField } from './ReceiptFormReceiptNumberField';
import { useCustomerUpdateExRate } from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
const getEstimateFieldsStyle = (theme: Theme) => css`
.${theme.bpPrefix}-form-group {
margin-bottom: 0;
&.${theme.bpPrefix}-inline {
max-width: 450px;
}
.${theme.bpPrefix}-label {
min-width: 150px;
font-weight: 500;
}
.${theme.bpPrefix}-form-content {
width: 100%;
}
}
`;
/**
* Receipt form header fields.
*/
export default function ReceiptFormHeader() {
const theme = useTheme();
const receiptFieldsClassName = getEstimateFieldsStyle(theme);
const { accounts, projects } = useReceiptFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
<Stack spacing={18} flex={1} className={receiptFieldsClassName}>
{/* ----------- Customer name ----------- */}
<ReceiptFormCustomerSelect />
@@ -76,47 +92,37 @@ export default function ReceiptFormHeader() {
</FFormGroup>
{/* ----------- Receipt date ----------- */}
<FastField name={'receipt_date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'receipt_date'} />}
inline={true}
className={classNames(CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="receipt_date" />}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('receipt_date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
<FFormGroup
name={'receipt_date'}
label={<T id={'receipt_date'} />}
inline
fastField
>
<FDateInput
name={'receipt_date'}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
popoverProps={{ position: Position.BOTTOM_LEFT, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
fill: true,
}}
fill
fastField
/>
</FFormGroup>
{/* ----------- Receipt number ----------- */}
<ReceiptFormReceiptNumberField />
{/* ----------- Reference ----------- */}
<FastField name={'reference_no'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference'} />}
inline={true}
className={classNames('form-group--reference', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
>
<InputGroup minimal={true} {...field} />
</FormGroup>
)}
</FastField>
<FFormGroup
label={<T id={'reference'} />}
inline={true}
name={'reference_no'}
>
<FInputGroup minimal={true} name={'reference_no'} />
</FFormGroup>
{/*------------ Project name -----------*/}
<FeatureCan feature={Features.Projects}>
@@ -134,7 +140,7 @@ export default function ReceiptFormHeader() {
/>
</FFormGroup>
</FeatureCan>
</div>
</Stack>
);
}

View File

@@ -1,25 +1,44 @@
// @ts-nocheck
import React from 'react';
import { useParams } from 'react-router-dom';
import { css } from '@emotion/css';
import '@/style/pages/SaleReceipt/PageForm.scss';
import ReceiptFrom from './ReceiptForm';
import { ReceiptFormProvider } from './ReceiptFormProvider';
import {
ReceiptFormProvider,
useReceiptFormContext,
} from './ReceiptFormProvider';
import { AutoExchangeRateProvider } from '@/containers/Entries/AutoExchangeProvider';
import { DashboardInsider } from '@/components';
import { ReceiptForm } from './ReceiptForm';
/**
* Receipt form page.
*/
export default function ReceiptFormPage() {
const { id } = useParams();
const idInt = parseInt(id, 10);
const receiptId = parseInt(id, 10);
return (
<ReceiptFormProvider receiptId={idInt}>
<ReceiptFormProvider receiptId={receiptId}>
<AutoExchangeRateProvider>
<ReceiptFrom />
<ReceiptFormPageContent />
</AutoExchangeRateProvider>
</ReceiptFormProvider>
);
}
function ReceiptFormPageContent() {
const { isBootLoading } = useReceiptFormContext();
return (
<DashboardInsider
loading={isBootLoading}
className={css`
min-height: calc(100vh - var(--top-offset));
max-height: calc(100vh - var(--top-offset));
`}
>
<ReceiptForm />
</DashboardInsider>
);
}

View File

@@ -120,6 +120,15 @@ function ReceiptFormProvider({ receiptId, ...props }) {
const isNewMode = !receiptId;
const isFeatureLoading = isWarehouesLoading || isBranchesLoading;
const isBootLoading =
isReceiptLoading ||
isAccountsLoading ||
isCustomersLoading ||
isItemsLoading ||
isSettingLoading ||
isBrandingTemplatesLoading ||
isSaleReceiptStateLoading;
const provider = {
receiptId,
receipt,
@@ -154,21 +163,10 @@ function ReceiptFormProvider({ receiptId, ...props }) {
// State
isSaleReceiptStateLoading,
saleReceiptState,
};
const isLoading =
isReceiptLoading ||
isAccountsLoading ||
isCustomersLoading ||
isItemsLoading ||
isSettingLoading ||
isBrandingTemplatesLoading ||
isSaleReceiptStateLoading;
return (
<DashboardInsider loading={isLoading} name={'receipt-form'}>
<ReceiptFormContext.Provider value={provider} {...props} />
</DashboardInsider>
);
isBootLoading,
};
return <ReceiptFormContext.Provider value={provider} {...props} />;
}
const useReceiptFormContext = () =>

View File

@@ -1,8 +1,7 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { x } from '@xstyled/emotion';
import { FastField } from 'formik';
import { CLASSES } from '@/constants/classes';
import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable';
import { useReceiptFormContext } from './ReceiptFormProvider';
import { entriesFieldShouldUpdate } from './utils';
@@ -11,8 +10,12 @@ export default function ReceiptItemsEntriesEditor({ defaultReceipt }) {
const { items } = useReceiptFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'} items={items} shouldUpdate={entriesFieldShouldUpdate}>
<x.div p="18px 32px 0">
<FastField
name={'entries'}
items={items}
shouldUpdate={entriesFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },
@@ -31,6 +34,6 @@ export default function ReceiptItemsEntriesEditor({ defaultReceipt }) {
/>
)}
</FastField>
</div>
</x.div>
);
}

View File

@@ -10,17 +10,13 @@ import { WarehouseTransferFormFooterLeft } from './WarehouseTransferFormFooterLe
export default function WarehouseTransferFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<WarehousesTransferFooterPaper>
<Paper p={'20px'}>
<Row>
<Col md={8}>
<WarehouseTransferFormFooterLeft />
</Col>
</Row>
</WarehousesTransferFooterPaper>
</Paper>
</div>
);
}
const WarehousesTransferFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -18,9 +18,9 @@ export const MoreIcon: React.FC<MoreIconProps> = ({ size = 16, ...props }) => (
{...props}
>
<g id="more_3_">
<circle cx={size / 8} cy={size / 2.00625} r={size / 8} />
<circle cx={size - size / 8} cy={size / 2.00625} r={size / 8} />
<circle cx={size / 2} cy={size / 2.00625} r={size / 8} />
<circle cx="2" cy="8.03" r="2" />
<circle cx="14" cy="8.03" r="2" />
<circle cx="8" cy="8.03" r="2" />
</g>
</svg>
);

View File

@@ -292,4 +292,8 @@ span.table-tooltip-overview-target {
font-size: 14px;
margin-bottom: 5px;
font-weight: 600;
}
:root {
--top-offset: 60px;
}

View File

@@ -60,6 +60,7 @@
margin: 20px 0 0 0;
padding-left: 32px;
padding-right: 32px;
padding-bottom: 20px;
label.bp4-label{
font-weight: 500;

View File

@@ -1,50 +0,0 @@
body.page-credit-note-new,
body.page-credit-note-edit {
.dashboard__footer {
display: none;
}
}
.dashboard__insider--credit-note-form {
padding-bottom: 64px;
}
.page-form--credit-note {
$self: '.page-form';
#{$self}__header {
display: flex;
&-fields {
flex: 1 0 0;
}
.bp4-label {
min-width: 150px;
}
.bp4-form-content {
width: 100%;
}
.bp4-form-group {
&.bp4-inline {
max-width: 450px;
}
}
.col--credit-note-date {
max-width: 435px;
}
}
#{$self}__footer {
.form-group--note,
.form-group--terms_conditions {
max-width: 450px;
width: 100%;
textarea {
width: 100%;
min-height: 60px;
}
}
}
}

View File

@@ -1,55 +0,0 @@
body.page-estimate-new,
body.page-estimate-edit{
.dashboard__footer{
display: none;
}
}
.dashboard__insider--estimate-form{
padding-bottom: 64px;
}
.page-form--estimate {
$self: '.page-form';
#{$self}__header {
display: flex;
&-fields {
flex: 1 0 0;
}
.bp4-label {
min-width: 140px;
}
.bp4-form-content {
width: 100%;
}
.bp4-form-group {
margin-bottom: 18px;
&.bp4-inline {
max-width: 440px;
}
}
.col--invoice-date {
max-width: 435px;
}
}
#{$self}__footer {
.form-group--customer_note,
.form-group--terms_conditions {
max-width: 450px;
width: 100%;
textarea {
width: 100%;
min-height: 60px;
}
}
}
}

View File

@@ -1,106 +0,0 @@
body.page-invoice-new,
body.page-invoice-edit {
.dashboard__footer {
display: none;
}
}
.dashboard__insider--invoice-form {
padding-bottom: 64px;
}
.page-form--invoice {
$self: '.page-form';
#{$self}__header {
display: flex;
&-fields {
flex: 1 0 0;
}
.bp4-label {
min-width: 150px;
}
.bp4-form-content {
width: 100%;
}
.bp4-form-group {
&.bp4-inline {
max-width: 450px;
}
}
.row {
display: flex;
flex-wrap: nowrap;
.col {
max-width: 470px;
&--due-date {
.bp4-form-group {
margin: 0px 15px 15px;
.bp4-label {
min-width: 100px;
}
}
}
}
}
.col--invoice-date {
max-width: 435px;
}
}
#{$self}__footer {
.form-group--invoice_message,
.form-group--terms_conditions {
max-width: 450px;
width: 100%;
textarea {
width: 100%;
min-height: 60px;
}
}
}
}
.status-accessor {
span {
font-size: 13px;
font-weight: 400;
line-height: 1.5;
}
.fully-paid-status {
height: 19px;
width: 19px;
display: inline-block;
background: #2ba01d;
border-radius: 50%;
margin-right: 6px;
color: #ffffff;
}
.bp4-progress-bar {
background: rgba(92, 112, 128, 0.2);
border-radius: 40px;
display: block;
height: 5px;
overflow: hidden;
position: relative;
margin-top: 5px;
width: 80%;
}
&.overdue-status {}
&.remaining-status {}
}

View File

@@ -1,51 +0,0 @@
body.page-receipt-new,
body.page-receipt-edit{
.dashboard__footer{
display: none;
}
}
.dashboard__insider--receipt-form{
padding-bottom: 64px;
}
.page-form--receipt{
$self: '.page-form';
#{$self}__header{
display: flex;
&-fields{
flex: 1 0 0;
}
.bp4-label{
min-width: 150px;
}
.bp4-form-content{
width: 100%;
}
.bp4-form-group{
margin-bottom: 18px;
&.bp4-inline{
max-width: 450px;
}
}
}
#{$self}__footer{
.form-group--receipt_message,
.form-group--statement{
max-width: 450px;
width: 100%;
textarea{
width: 100%;
min-height: 50px;
}
}
}
}

381
pnpm-lock.yaml generated
View File

@@ -520,6 +520,12 @@ importers:
'@craco/craco':
specifier: ^5.9.0
version: 5.9.0(react-scripts@5.0.1)
'@emotion/css':
specifier: ^11.13.4
version: 11.13.4
'@emotion/react':
specifier: ^11.13.3
version: 11.13.3(@types/react@16.14.60)(react@18.3.1)
'@reduxjs/toolkit':
specifier: ^1.2.5
version: 1.9.7(react-redux@7.2.9)(react@18.3.1)
@@ -613,6 +619,9 @@ importers:
'@welldone-software/why-did-you-render':
specifier: ^6.0.0-rc.1
version: 6.2.3(react@18.3.1)
'@xstyled/emotion':
specifier: ^3.8.1
version: 3.8.1(@emotion/react@11.13.3)(@emotion/styled@11.13.0)
accounting:
specifier: ^0.4.1
version: 0.4.1
@@ -841,6 +850,9 @@ importers:
stylis-rtlcss:
specifier: ^2.1.1
version: 2.1.1
theme-ui:
specifier: ^0.16.2
version: 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
typescript:
specifier: ^4.8.3
version: 4.9.5
@@ -3731,6 +3743,22 @@ packages:
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'}
/@emotion/babel-plugin@11.12.0:
resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==}
dependencies:
'@babel/helper-module-imports': 7.24.3
'@babel/runtime': 7.24.5
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/serialize': 1.3.2
babel-plugin-macros: 3.1.0
convert-source-map: 1.9.0
escape-string-regexp: 4.0.0
find-root: 1.1.0
source-map: 0.5.7
stylis: 4.2.0
dev: false
/@emotion/cache@10.0.29:
resolution: {integrity: sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==}
dependencies:
@@ -3740,6 +3768,16 @@ packages:
'@emotion/weak-memoize': 0.2.5
dev: false
/@emotion/cache@11.13.1:
resolution: {integrity: sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==}
dependencies:
'@emotion/memoize': 0.9.0
'@emotion/sheet': 1.4.0
'@emotion/utils': 1.4.1
'@emotion/weak-memoize': 0.4.0
stylis: 4.2.0
dev: false
/@emotion/core@10.3.1(react@18.3.1):
resolution: {integrity: sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==}
peerDependencies:
@@ -3762,16 +3800,42 @@ packages:
babel-plugin-emotion: 10.2.2
dev: false
/@emotion/css@11.13.4:
resolution: {integrity: sha512-CthbOD5EBw+iN0rfM96Tuv5kaZN4nxPyYDvGUs0bc7wZBBiU/0mse+l+0O9RshW2d+v5HH1cme+BAbLJ/3Folw==}
dependencies:
'@emotion/babel-plugin': 11.12.0
'@emotion/cache': 11.13.1
'@emotion/serialize': 1.3.2
'@emotion/sheet': 1.4.0
'@emotion/utils': 1.4.1
dev: false
/@emotion/hash@0.8.0:
resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
dev: false
/@emotion/hash@0.9.2:
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
dev: false
/@emotion/is-prop-valid@0.8.8:
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
dependencies:
'@emotion/memoize': 0.7.4
dev: false
/@emotion/is-prop-valid@1.2.2:
resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==}
dependencies:
'@emotion/memoize': 0.8.1
dev: false
/@emotion/is-prop-valid@1.3.1:
resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==}
dependencies:
'@emotion/memoize': 0.9.0
dev: false
/@emotion/memoize@0.7.4:
resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
dev: false
@@ -3780,6 +3844,31 @@ packages:
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
dev: false
/@emotion/memoize@0.9.0:
resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
dev: false
/@emotion/react@11.13.3(@types/react@16.14.60)(react@18.3.1):
resolution: {integrity: sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==}
peerDependencies:
'@types/react': '*'
react: '>=16.8.0'
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.24.5
'@emotion/babel-plugin': 11.12.0
'@emotion/cache': 11.13.1
'@emotion/serialize': 1.3.2
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
'@emotion/utils': 1.4.1
'@emotion/weak-memoize': 0.4.0
'@types/react': 16.14.60
hoist-non-react-statics: 3.3.2
react: 18.3.1
dev: false
/@emotion/serialize@0.11.16:
resolution: {integrity: sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==}
dependencies:
@@ -3790,26 +3879,81 @@ packages:
csstype: 2.6.21
dev: false
/@emotion/serialize@1.3.2:
resolution: {integrity: sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==}
dependencies:
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/unitless': 0.10.0
'@emotion/utils': 1.4.1
csstype: 3.1.3
dev: false
/@emotion/sheet@0.9.4:
resolution: {integrity: sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==}
dev: false
/@emotion/sheet@1.4.0:
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
dev: false
/@emotion/styled@11.13.0(@emotion/react@11.13.3)(@types/react@16.14.60)(react@18.3.1):
resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==}
peerDependencies:
'@emotion/react': ^11.0.0-rc.0
'@types/react': '*'
react: '>=16.8.0'
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.24.5
'@emotion/babel-plugin': 11.12.0
'@emotion/is-prop-valid': 1.3.1
'@emotion/react': 11.13.3(@types/react@16.14.60)(react@18.3.1)
'@emotion/serialize': 1.3.2
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
'@emotion/utils': 1.4.1
'@types/react': 16.14.60
react: 18.3.1
dev: false
/@emotion/stylis@0.8.5:
resolution: {integrity: sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==}
dev: false
/@emotion/unitless@0.10.0:
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
dev: false
/@emotion/unitless@0.7.5:
resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
dev: false
/@emotion/use-insertion-effect-with-fallbacks@1.1.0(react@18.3.1):
resolution: {integrity: sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==}
peerDependencies:
react: '>=16.8.0'
dependencies:
react: 18.3.1
dev: false
/@emotion/utils@0.11.3:
resolution: {integrity: sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==}
dev: false
/@emotion/utils@1.4.1:
resolution: {integrity: sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==}
dev: false
/@emotion/weak-memoize@0.2.5:
resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==}
dev: false
/@emotion/weak-memoize@0.4.0:
resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
dev: false
/@esbuild/aix-ppc64@0.23.1:
resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
engines: {node: '>=18'}
@@ -6082,6 +6226,91 @@ packages:
react-dom: 18.3.1(react@18.3.1)
dev: false
/@styled-system/background@5.1.2:
resolution: {integrity: sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==}
dependencies:
'@styled-system/core': 5.1.2
dev: false
/@styled-system/border@5.1.5:
resolution: {integrity: sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==}
dependencies:
'@styled-system/core': 5.1.2
dev: false
/@styled-system/color@5.1.2:
resolution: {integrity: sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==}
dependencies:
'@styled-system/core': 5.1.2
dev: false
/@styled-system/core@5.1.2:
resolution: {integrity: sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==}
dependencies:
object-assign: 4.1.1
dev: false
/@styled-system/css@5.1.5:
resolution: {integrity: sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==}
dev: false
/@styled-system/flexbox@5.1.2:
resolution: {integrity: sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==}
dependencies:
'@styled-system/core': 5.1.2
dev: false
/@styled-system/grid@5.1.2:
resolution: {integrity: sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==}
dependencies:
'@styled-system/core': 5.1.2
dev: false
/@styled-system/layout@5.1.2:
resolution: {integrity: sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==}
dependencies:
'@styled-system/core': 5.1.2
dev: false
/@styled-system/position@5.1.2:
resolution: {integrity: sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==}
dependencies:
'@styled-system/core': 5.1.2
dev: false
/@styled-system/shadow@5.1.2:
resolution: {integrity: sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==}
dependencies:
'@styled-system/core': 5.1.2
dev: false
/@styled-system/should-forward-prop@5.1.5:
resolution: {integrity: sha512-+rPRomgCGYnUIaFabDoOgpSDc4UUJ1KsmlnzcEp0tu5lFrBQKgZclSo18Z1URhaZm7a6agGtS5Xif7tuC2s52Q==}
dependencies:
'@emotion/is-prop-valid': 0.8.8
'@emotion/memoize': 0.7.4
styled-system: 5.1.5
dev: false
/@styled-system/space@5.1.2:
resolution: {integrity: sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==}
dependencies:
'@styled-system/core': 5.1.2
dev: false
/@styled-system/typography@5.1.2:
resolution: {integrity: sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==}
dependencies:
'@styled-system/core': 5.1.2
dev: false
/@styled-system/variant@5.1.5:
resolution: {integrity: sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==}
dependencies:
'@styled-system/core': 5.1.2
'@styled-system/css': 5.1.5
dev: false
/@supercharge/promise-pool@3.2.0:
resolution: {integrity: sha512-pj0cAALblTZBPtMltWOlZTQSLT07jIaFNeM8TWoJD1cQMgDB9mcMlVMoetiB35OzNJpqQ2b+QEtwiR9f20mADg==}
engines: {node: '>=8'}
@@ -6269,6 +6498,83 @@ packages:
'@testing-library/dom': 10.1.0
dev: false
/@theme-ui/color-modes@0.16.2(@emotion/react@11.13.3)(react@18.3.1):
resolution: {integrity: sha512-jWEWx53lxNgWCT38i/kwLV2rsvJz8lVZgi5oImnVwYba9VejXD23q1ckbNFJHosQ8KKXY87ht0KPC6BQFIiHtQ==}
peerDependencies:
'@emotion/react': ^11.11.1
react: '>=18'
dependencies:
'@emotion/react': 11.13.3(@types/react@16.14.60)(react@18.3.1)
'@theme-ui/core': 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
'@theme-ui/css': 0.16.2(@emotion/react@11.13.3)
deepmerge: 4.3.1
react: 18.3.1
dev: false
/@theme-ui/components@0.16.2(@emotion/react@11.13.3)(@theme-ui/theme-provider@0.16.2)(react@18.3.1):
resolution: {integrity: sha512-d9HDxLTZzVHSiNNQUJrs4k0FU9kTFZhM3KjR+lVuztoWSGhkZEjf1KrWK8OWWXPH2LxEC5okcvggx177tnfejg==}
peerDependencies:
'@emotion/react': ^11.11.1
'@theme-ui/theme-provider': ^0.16.2
react: '>=18'
dependencies:
'@emotion/react': 11.13.3(@types/react@16.14.60)(react@18.3.1)
'@styled-system/color': 5.1.2
'@styled-system/should-forward-prop': 5.1.5
'@styled-system/space': 5.1.2
'@theme-ui/core': 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
'@theme-ui/css': 0.16.2(@emotion/react@11.13.3)
'@theme-ui/theme-provider': 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
'@types/styled-system': 5.1.22
react: 18.3.1
dev: false
/@theme-ui/core@0.16.2(@emotion/react@11.13.3)(react@18.3.1):
resolution: {integrity: sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==}
peerDependencies:
'@emotion/react': ^11.11.1
react: '>=18'
dependencies:
'@emotion/react': 11.13.3(@types/react@16.14.60)(react@18.3.1)
'@theme-ui/css': 0.16.2(@emotion/react@11.13.3)
deepmerge: 4.3.1
react: 18.3.1
dev: false
/@theme-ui/css@0.16.2(@emotion/react@11.13.3):
resolution: {integrity: sha512-fNe+FhwKC5+7jQfxCRnm3oqYNhMFuiWiLA9OoLBEkt3b4egot29UK1+fqemwiNVjl206e2fPT5Z7uXRdb6LC2A==}
peerDependencies:
'@emotion/react': ^11.11.1
dependencies:
'@emotion/react': 11.13.3(@types/react@16.14.60)(react@18.3.1)
csstype: 3.1.3
dev: false
/@theme-ui/global@0.16.2(@emotion/react@11.13.3)(react@18.3.1):
resolution: {integrity: sha512-Y2CtNblERWJDxZpckR1TA0+UTH762VA82v/xTMMbufcEmJjzPasEOSZxJYH3qUOuCaJpbX5eFT49MEAujvLCzg==}
peerDependencies:
'@emotion/react': ^11.11.1
react: '>=18'
dependencies:
'@emotion/react': 11.13.3(@types/react@16.14.60)(react@18.3.1)
'@theme-ui/core': 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
'@theme-ui/css': 0.16.2(@emotion/react@11.13.3)
react: 18.3.1
dev: false
/@theme-ui/theme-provider@0.16.2(@emotion/react@11.13.3)(react@18.3.1):
resolution: {integrity: sha512-LRnVevODcGqO0JyLJ3wht+PV3ZoZcJ7XXLJAJWDoGeII4vZcPQKwVy4Lpz/juHsZppQxKcB3U+sQDGBnP25irQ==}
peerDependencies:
'@emotion/react': ^11.11.1
react: '>=18'
dependencies:
'@emotion/react': 11.13.3(@types/react@16.14.60)(react@18.3.1)
'@theme-ui/color-modes': 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
'@theme-ui/core': 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
'@theme-ui/css': 0.16.2(@emotion/react@11.13.3)
react: 18.3.1
dev: false
/@tiptap/core@2.1.13(@tiptap/pm@2.1.13):
resolution: {integrity: sha512-cMC8bgTN63dj1Mv82iDeeLl6sa9kY0Pug8LSalxVEptRmyFVsVxGgu2/6Y3T+9aCYScxfS06EkA8SdzFMAwYTQ==}
peerDependencies:
@@ -7062,6 +7368,12 @@ packages:
csstype: 3.1.3
dev: false
/@types/styled-system@5.1.22:
resolution: {integrity: sha512-NbRp37zWcrf/+Qf2NumdyZfhSx1dzJ50zgfKvnezYJx1HTRUMVYY8jtWvK1eoIAa6F5sXwHLhE8oXNu15ThBAA==}
dependencies:
csstype: 3.1.3
dev: false
/@types/superagent@4.1.13:
resolution: {integrity: sha512-YIGelp3ZyMiH0/A09PMAORO0EBGlF5xIKfDpK74wdYvWUs2o96b5CItJcWPdH409b7SAXIIG6p8NdU/4U2Maww==}
dependencies:
@@ -7652,6 +7964,37 @@ packages:
resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==}
dev: false
/@xstyled/core@3.8.1:
resolution: {integrity: sha512-5mqKUZQLuB26NqF36jxvfhl6xqTW9TT2LfBbZJmoJISqS+cgAVoO9H82edisQsH/vxtoO9vjevcxtFXwOJPArA==}
dependencies:
'@xstyled/system': 3.8.1
'@xstyled/util': 3.7.0
dev: false
/@xstyled/emotion@3.8.1(@emotion/react@11.13.3)(@emotion/styled@11.13.0):
resolution: {integrity: sha512-HRPkKiCFUTCYL5QDLH27Lz3tBBCHvF2zUEybxIHcIOIRnH4VgCtTmGuY7wa/tkiIaauHYtHoJBWYaogsPY3mHw==}
peerDependencies:
'@emotion/react': ^11.0.0
'@emotion/styled': ^11.0.0
dependencies:
'@emotion/react': 11.13.3(@types/react@16.14.60)(react@18.3.1)
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@16.14.60)(react@18.3.1)
'@xstyled/core': 3.8.1
'@xstyled/system': 3.8.1
'@xstyled/util': 3.7.0
dev: false
/@xstyled/system@3.8.1:
resolution: {integrity: sha512-mmWeAHrnTjV5ZDhUv5kPf4NF0BKLUbN9bHBmx1U+6kNwPAqHq+kOA+7ef9DZEeedTY4g29uYg8hvLqBweHXKqQ==}
dependencies:
'@xstyled/util': 3.7.0
csstype: 3.1.3
dev: false
/@xstyled/util@3.7.0:
resolution: {integrity: sha512-rYtXRcNh+pgRxGnciP0Mas21mpyOzcCTVy7w9uIByQk3EytwBQjDiN6wCasXibkw49Urfti5efsklRbCl5QZww==}
dev: false
/@xtuc/ieee754@1.2.0:
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@@ -24615,6 +24958,24 @@ packages:
- '@babel/core'
dev: false
/styled-system@5.1.5:
resolution: {integrity: sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==}
dependencies:
'@styled-system/background': 5.1.2
'@styled-system/border': 5.1.5
'@styled-system/color': 5.1.2
'@styled-system/core': 5.1.2
'@styled-system/flexbox': 5.1.2
'@styled-system/grid': 5.1.2
'@styled-system/layout': 5.1.2
'@styled-system/position': 5.1.2
'@styled-system/shadow': 5.1.2
'@styled-system/space': 5.1.2
'@styled-system/typography': 5.1.2
'@styled-system/variant': 5.1.5
object-assign: 4.1.1
dev: false
/stylehacks@5.1.1(postcss@8.4.38):
resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==}
engines: {node: ^10 || ^12 || >=14.0}
@@ -24632,6 +24993,10 @@ packages:
rtlcss: 2.6.2
dev: false
/stylis@4.2.0:
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
dev: false
/stylis@4.3.2:
resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==}
dev: false
@@ -24975,6 +25340,22 @@ packages:
/text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
/theme-ui@0.16.2(@emotion/react@11.13.3)(react@18.3.1):
resolution: {integrity: sha512-6+/D9ZMKlwWWLQ3idoESxcDUNPkNuGPiixKWGrTD1Fo1yAS3wSb9Je4Xt9Zu3a8f1kbrAyy4vnTVcomFZ1Meig==}
peerDependencies:
'@emotion/react': '>=11.1.1'
react: '>=18'
dependencies:
'@emotion/react': 11.13.3(@types/react@16.14.60)(react@18.3.1)
'@theme-ui/color-modes': 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
'@theme-ui/components': 0.16.2(@emotion/react@11.13.3)(@theme-ui/theme-provider@0.16.2)(react@18.3.1)
'@theme-ui/core': 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
'@theme-ui/css': 0.16.2(@emotion/react@11.13.3)
'@theme-ui/global': 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
'@theme-ui/theme-provider': 0.16.2(@emotion/react@11.13.3)(react@18.3.1)
react: 18.3.1
dev: false
/then-request@6.0.2:
resolution: {integrity: sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==}
engines: {node: '>=6.0.0'}