mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
Compare commits
127 Commits
v0.3.0
...
easysms-in
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43c3f5e3de | ||
|
|
a99fc78fe1 | ||
|
|
346696f673 | ||
|
|
68227b81e8 | ||
|
|
0c806366cd | ||
|
|
8b28d6894f | ||
|
|
3db00f6f70 | ||
|
|
56ab0a68e2 | ||
|
|
4ac32b3aad | ||
|
|
f98b429fcc | ||
|
|
012f204c5c | ||
|
|
7c62466c5b | ||
|
|
111ade2ac8 | ||
|
|
408e3cbb0b | ||
|
|
9cc770f168 | ||
|
|
383a9aad3b | ||
|
|
1be30fd142 | ||
|
|
63cb3f9fef | ||
|
|
ca3ff3fd8f | ||
|
|
313d0f3d0f | ||
|
|
64bf223458 | ||
|
|
97c421e2f1 | ||
|
|
ccad55dd4a | ||
|
|
a21d70a59d | ||
|
|
c2ccb7f879 | ||
|
|
fe9ca215ab | ||
|
|
c14b35356b | ||
|
|
6fd8a24802 | ||
|
|
80531b7fdb | ||
|
|
600a835dad | ||
|
|
3dff8763d4 | ||
|
|
e197d66d9f | ||
|
|
5dfb592ecc | ||
|
|
2630e0235d | ||
|
|
8b4dfe4ded | ||
|
|
9ceee6d02e | ||
|
|
553334f063 | ||
|
|
aef8eb7907 | ||
|
|
cc1f4cc26b | ||
|
|
719302b241 | ||
|
|
3db52e9c63 | ||
|
|
7393d68b7a | ||
|
|
6ec86d3cf7 | ||
|
|
1cba4b5f18 | ||
|
|
3a8e1f5238 | ||
|
|
371e374dc5 | ||
|
|
c2650c76e8 | ||
|
|
fc74346695 | ||
|
|
fca4dedeac | ||
|
|
e5d02043ad | ||
|
|
afee2e90e0 | ||
|
|
d45005d8c2 | ||
|
|
15e7f34879 | ||
|
|
a54ddf27c7 | ||
|
|
955ae97c19 | ||
|
|
ddbadb67c8 | ||
|
|
b853eb1e75 | ||
|
|
c139e129bf | ||
|
|
3d3827b683 | ||
|
|
cf6d8d6038 | ||
|
|
d12b965bac | ||
|
|
fe8f41f200 | ||
|
|
b32abc0417 | ||
|
|
11d7029568 | ||
|
|
1990ce7562 | ||
|
|
b6f0f6c2d5 | ||
|
|
4c58e49169 | ||
|
|
376a16fd65 | ||
|
|
918cd4aef3 | ||
|
|
ec844637c3 | ||
|
|
5803760c61 | ||
|
|
2e34df5d63 | ||
|
|
35d755e417 | ||
|
|
66641ca56e | ||
|
|
307aaf0aa4 | ||
|
|
eb5a82d413 | ||
|
|
ce9169b24d | ||
|
|
22069f4795 | ||
|
|
567b4da7e9 | ||
|
|
06345a5615 | ||
|
|
6b8178f643 | ||
|
|
449ff724e1 | ||
|
|
95e75f0e8f | ||
|
|
1a63ac69d8 | ||
|
|
da67217d74 | ||
|
|
e0c03141f0 | ||
|
|
4d563e3ddd | ||
|
|
8bad78b0d3 | ||
|
|
56fdf245d3 | ||
|
|
5a8c61396f | ||
|
|
5fcf32dcaa | ||
|
|
acf457c0a0 | ||
|
|
e205c0b9a3 | ||
|
|
85f1c5584b | ||
|
|
9e5fddf294 | ||
|
|
3039e43767 | ||
|
|
7371557482 | ||
|
|
4b5e06f50c | ||
|
|
8daefb6946 | ||
|
|
6bf605f9ea | ||
|
|
48221a7af1 | ||
|
|
7a1c9caa70 | ||
|
|
8c2d138976 | ||
|
|
5b09d8279e | ||
|
|
92d8096f3a | ||
|
|
adc6b336e0 | ||
|
|
6d67d6163d | ||
|
|
4d89f1e0e0 | ||
|
|
7706d2992c | ||
|
|
6dcb98a438 | ||
|
|
834d365a97 | ||
|
|
d26ef01afc | ||
|
|
2bd4c5f724 | ||
|
|
2c71d07512 | ||
|
|
17a4744e58 | ||
|
|
46f6380fe6 | ||
|
|
d94d28f709 | ||
|
|
94e6b64944 | ||
|
|
d8e9be0246 | ||
|
|
7ef72e8955 | ||
|
|
d76cc3d2a2 | ||
|
|
3102329ac0 | ||
|
|
fd09ea12ff | ||
|
|
7bd09e7326 | ||
|
|
cd3105b320 | ||
|
|
91b848f158 | ||
|
|
352e517c2b |
@@ -10,6 +10,8 @@
|
|||||||
"@blueprintjs/select": "^3.11.2",
|
"@blueprintjs/select": "^3.11.2",
|
||||||
"@blueprintjs/table": "^3.8.3",
|
"@blueprintjs/table": "^3.8.3",
|
||||||
"@blueprintjs/timezone": "^3.6.2",
|
"@blueprintjs/timezone": "^3.6.2",
|
||||||
|
"@casl/ability": "^5.4.3",
|
||||||
|
"@casl/react": "^2.3.0",
|
||||||
"@reduxjs/toolkit": "^1.2.5",
|
"@reduxjs/toolkit": "^1.2.5",
|
||||||
"@sentry/react": "^6.13.2",
|
"@sentry/react": "^6.13.2",
|
||||||
"@sentry/tracing": "^6.13.2",
|
"@sentry/tracing": "^6.13.2",
|
||||||
|
|||||||
161
src/common/abilityOption.js
Normal file
161
src/common/abilityOption.js
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
export const AbilitySubject = {
|
||||||
|
Item: 'Item',
|
||||||
|
InventoryAdjustment: 'InventoryAdjustment',
|
||||||
|
Estimate: 'SaleEstimate',
|
||||||
|
Invoice: 'SaleInvoice',
|
||||||
|
Receipt: 'SaleReceipt',
|
||||||
|
PaymentReceive: 'PaymentReceive',
|
||||||
|
Bill: 'Bill',
|
||||||
|
PaymentMade: 'PaymentMade',
|
||||||
|
Customer: 'Customer',
|
||||||
|
Vendor: 'Vendor',
|
||||||
|
Account: 'Account',
|
||||||
|
ManualJournal: 'ManualJournal',
|
||||||
|
Expense: 'Expense',
|
||||||
|
Cashflow: 'Cashflow',
|
||||||
|
Report: 'Report',
|
||||||
|
Preferences: 'Preferences',
|
||||||
|
ExchangeRate: 'ExchangeRate',
|
||||||
|
SubscriptionBilling: 'SubscriptionBilling',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ItemAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InventoryAdjustmentAction = {
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
View: 'View',
|
||||||
|
Delete: 'Delete',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SaleEstimateAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
NotifyBySms: 'NotifyBySms',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SaleInvoiceAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
Writeoff: 'bad-debt',
|
||||||
|
NotifyBySms: 'NotifyBySms',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SaleReceiptAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
NotifyBySms: 'NotifyBySms',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PaymentReceiveAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
NotifyBySms: 'NotifyBySms',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BillAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
NotifyBySms: 'NotifyBySms',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PaymentMadeAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomerAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VendorAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AccountAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
TransactionsLocking: 'TransactionsLocking',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ManualJournalAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
TransactionLocking: 'TransactionLocking',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExpenseAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CashflowAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Delete: 'Delete',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReportsAction = {
|
||||||
|
ALL: 'all',
|
||||||
|
READ_BALANCE_SHEET: 'read-balance-sheet',
|
||||||
|
READ_TRIAL_BALANCE_SHEET: 'read-trial-balance-sheet',
|
||||||
|
READ_PROFIT_LOSS: 'read-profit-loss',
|
||||||
|
READ_JOURNAL: 'read-journal',
|
||||||
|
READ_GENERAL_LEDGET: 'read-general-ledger',
|
||||||
|
READ_CASHFLOW: 'read-cashflow',
|
||||||
|
READ_AR_AGING_SUMMARY: 'read-ar-aging-summary',
|
||||||
|
READ_AP_AGING_SUMMARY: 'read-ap-aging-summary',
|
||||||
|
READ_PURCHASES_BY_ITEMS: 'read-purchases-by-items',
|
||||||
|
READ_SALES_BY_ITEMS: 'read-sales-by-items',
|
||||||
|
READ_CUSTOMERS_TRANSACTIONS: 'read-customers-transactions',
|
||||||
|
READ_VENDORS_TRANSACTIONS: 'read-vendors-transactions',
|
||||||
|
READ_CUSTOMERS_SUMMARY_BALANCE: 'read-customers-summary-balance',
|
||||||
|
READ_VENDORS_SUMMARY_BALANCE: 'read-vendors-summary-balance',
|
||||||
|
READ_INVENTORY_VALUATION_SUMMARY: 'read-inventory-valuation-summary',
|
||||||
|
READ_INVENTORY_ITEM_DETAILS: 'read-inventory-item-details',
|
||||||
|
READ_CASHFLOW_ACCOUNT_TRANSACTION: 'read-cashflow-account-transactions',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PreferencesAbility = {
|
||||||
|
Mutate: 'Mutate',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExchangeRateAbility = {
|
||||||
|
View: 'view',
|
||||||
|
Create: 'create',
|
||||||
|
Delete: 'delete',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SubscriptionBillingAbility = {
|
||||||
|
View: 'view',
|
||||||
|
Payment: 'payment',
|
||||||
|
};
|
||||||
@@ -66,6 +66,8 @@ const CLASSES = {
|
|||||||
PREFERENCES_PAGE_INSIDE_CONTENT_USERS: 'preferences-page__inside-content--users',
|
PREFERENCES_PAGE_INSIDE_CONTENT_USERS: 'preferences-page__inside-content--users',
|
||||||
PREFERENCES_PAGE_INSIDE_CONTENT_CURRENCIES: 'preferences-page__inside-content--currencies',
|
PREFERENCES_PAGE_INSIDE_CONTENT_CURRENCIES: 'preferences-page__inside-content--currencies',
|
||||||
PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT: 'preferences-page__inside-content--accountant',
|
PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT: 'preferences-page__inside-content--accountant',
|
||||||
|
PREFERENCES_PAGE_INSIDE_CONTENT_SMS_INTEGRATION: 'preferences-page__inside-content--sms-integration',
|
||||||
|
PREFERENCES_PAGE_INSIDE_CONTENT_ROLES_FORM: 'preferences-page__inside-content--roles-form',
|
||||||
|
|
||||||
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
|
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
|
||||||
|
|
||||||
|
|||||||
@@ -10,4 +10,8 @@ export const DRAWERS = {
|
|||||||
BILL_DRAWER: 'bill-drawer',
|
BILL_DRAWER: 'bill-drawer',
|
||||||
INVENTORY_ADJUSTMENT_DRAWER: 'inventory-adjustment-drawer',
|
INVENTORY_ADJUSTMENT_DRAWER: 'inventory-adjustment-drawer',
|
||||||
CASHFLOW_TRNASACTION_DRAWER: 'cashflow-transaction-drawer',
|
CASHFLOW_TRNASACTION_DRAWER: 'cashflow-transaction-drawer',
|
||||||
|
|
||||||
|
QUICK_WRITE_VENDOR: 'quick-write-vendor',
|
||||||
|
QUICK_CREATE_CUSTOMER: 'quick-create-customer',
|
||||||
|
QUICK_CREATE_ITEM: 'quick-create-item',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import {
|
||||||
|
SaleInvoiceAction,
|
||||||
|
SaleEstimateAction,
|
||||||
|
AbilitySubject,
|
||||||
|
SaleReceiptAction,
|
||||||
|
CustomerAction,
|
||||||
|
PaymentReceiveAction,
|
||||||
|
BillAction,
|
||||||
|
VendorAction,
|
||||||
|
PaymentMadeAction,
|
||||||
|
AccountAction,
|
||||||
|
ManualJournalAction,
|
||||||
|
ExpenseAction,
|
||||||
|
ItemAction,
|
||||||
|
ReportsAction,
|
||||||
|
} from '../common/abilityOption';
|
||||||
|
|
||||||
export const accountsReceivable = [
|
export const accountsReceivable = [
|
||||||
{
|
{
|
||||||
@@ -9,21 +25,29 @@ export const accountsReceivable = [
|
|||||||
title: <T id={'sales_invoices'} />,
|
title: <T id={'sales_invoices'} />,
|
||||||
description: <T id={'tracking_sales_invoices_with_your_customers'} />,
|
description: <T id={'tracking_sales_invoices_with_your_customers'} />,
|
||||||
link: '/invoices',
|
link: '/invoices',
|
||||||
|
subject: AbilitySubject.Invoice,
|
||||||
|
ability: SaleInvoiceAction.View,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'sales_estimates'} />,
|
title: <T id={'sales_estimates'} />,
|
||||||
description: <T id={'manage_your_sales_estimates_to_create_quotes'} />,
|
description: <T id={'manage_your_sales_estimates_to_create_quotes'} />,
|
||||||
link: '/estimates',
|
link: '/estimates',
|
||||||
|
subject: AbilitySubject.Estimate,
|
||||||
|
ability: SaleEstimateAction.View,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'sales_receipts'} />,
|
title: <T id={'sales_receipts'} />,
|
||||||
description: <T id={'manage_sales_receipts_for_sales_that_get_paid'} />,
|
description: <T id={'manage_sales_receipts_for_sales_that_get_paid'} />,
|
||||||
link: '/receipts',
|
link: '/receipts',
|
||||||
|
subject: AbilitySubject.Receipt,
|
||||||
|
ability: SaleReceiptAction.View,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'customers'} />,
|
title: <T id={'customers'} />,
|
||||||
description: <T id={'manage_the_customers_relations_with_customer'} />,
|
description: <T id={'manage_the_customers_relations_with_customer'} />,
|
||||||
link: '/customers',
|
link: '/customers',
|
||||||
|
subject: AbilitySubject.Customer,
|
||||||
|
ability: CustomerAction.View,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'customers_payments'} />,
|
title: <T id={'customers_payments'} />,
|
||||||
@@ -31,6 +55,8 @@ export const accountsReceivable = [
|
|||||||
<T id={'manage_payment_transactions_from_your_customers'} />
|
<T id={'manage_payment_transactions_from_your_customers'} />
|
||||||
),
|
),
|
||||||
link: '/payment-receives',
|
link: '/payment-receives',
|
||||||
|
subject: AbilitySubject.PaymentReceive,
|
||||||
|
ability: PaymentReceiveAction.View,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -46,6 +72,8 @@ export const accountsPayable = [
|
|||||||
<T id={'manage_the_purchase_invoices_with_your_vendors'} />
|
<T id={'manage_the_purchase_invoices_with_your_vendors'} />
|
||||||
),
|
),
|
||||||
link: '/bills',
|
link: '/bills',
|
||||||
|
subject: AbilitySubject.Bill,
|
||||||
|
ability: BillAction.View,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'vendors'} />,
|
title: <T id={'vendors'} />,
|
||||||
@@ -53,11 +81,15 @@ export const accountsPayable = [
|
|||||||
<T id={'manage_the_vendors_relations_with_vendor_relations'} />
|
<T id={'manage_the_vendors_relations_with_vendor_relations'} />
|
||||||
),
|
),
|
||||||
link: '/vendors',
|
link: '/vendors',
|
||||||
|
subject: AbilitySubject.Vendor,
|
||||||
|
ability: VendorAction.View,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'vendors_payments'} />,
|
title: <T id={'vendors_payments'} />,
|
||||||
description: <T id={'manage_payments_transactions_to_your_vendors'} />,
|
description: <T id={'manage_payments_transactions_to_your_vendors'} />,
|
||||||
link: '/payment-mades',
|
link: '/payment-mades',
|
||||||
|
subject: AbilitySubject.PaymentMade,
|
||||||
|
ability: PaymentMadeAction.View,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -77,21 +109,35 @@ export const financialAccounting = [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
link: '/accounts',
|
link: '/accounts',
|
||||||
|
subject: AbilitySubject.Account,
|
||||||
|
ability: AccountAction.View,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'manual_journal'}/>,
|
title: <T id={'manual_journal'} />,
|
||||||
description:<T id={'manage_manual_journal_transactions_on_accounts'}/>,
|
description: (
|
||||||
|
<T id={'manage_manual_journal_transactions_on_accounts'} />
|
||||||
|
),
|
||||||
link: '/manual-journals',
|
link: '/manual-journals',
|
||||||
|
subject: AbilitySubject.ManualJournal,
|
||||||
|
ability: ManualJournalAction.View,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'expenses'}/>,
|
title: <T id={'expenses'} />,
|
||||||
description:<T id={'track_your_indirect_expenses_under_specific_categories'}/>,
|
description: (
|
||||||
|
<T id={'track_your_indirect_expenses_under_specific_categories'} />
|
||||||
|
),
|
||||||
link: '/expenses',
|
link: '/expenses',
|
||||||
|
subject: AbilitySubject.Expense,
|
||||||
|
ability: ExpenseAction.View,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'financial_statements'}/>,
|
title: <T id={'financial_statements'} />,
|
||||||
description:<T id={'show_financial_reports_about_your_organization'}/>,
|
description: (
|
||||||
|
<T id={'show_financial_reports_about_your_organization'} />
|
||||||
|
),
|
||||||
link: '/financial-reports',
|
link: '/financial-reports',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.ALL,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -102,19 +148,27 @@ export const productsServices = [
|
|||||||
sectionTitle: <T id={'products_services_inventory'} />,
|
sectionTitle: <T id={'products_services_inventory'} />,
|
||||||
shortcuts: [
|
shortcuts: [
|
||||||
{
|
{
|
||||||
title: <T id={'products_services'}/>,
|
title: <T id={'products_services'} />,
|
||||||
description:<T id={'manage_your_products_inventory_or_non_inventory'}/>,
|
description: (
|
||||||
|
<T id={'manage_your_products_inventory_or_non_inventory'} />
|
||||||
|
),
|
||||||
link: '/items',
|
link: '/items',
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
ability: ItemAction.View,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'products_services_categories'}/>,
|
title: <T id={'products_services_categories'} />,
|
||||||
description:<T id={'group_your_products_and_service'}/>,
|
description: <T id={'group_your_products_and_service'} />,
|
||||||
link: 'items/categories',
|
link: 'items/categories',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'inventory_adjustments'}/>,
|
title: <T id={'inventory_adjustments'} />,
|
||||||
description: <T id={'manage_your_inventory_adjustment_of_inventory_items'}/>,
|
description: (
|
||||||
|
<T id={'manage_your_inventory_adjustment_of_inventory_items'} />
|
||||||
|
),
|
||||||
link: '/inventory-adjustments',
|
link: '/inventory-adjustments',
|
||||||
|
subject: AbilitySubject.InventoryAdjustment,
|
||||||
|
ability: SaleInvoiceAction.View,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,110 +1,228 @@
|
|||||||
import React from 'react';
|
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import {
|
||||||
|
AbilitySubject,
|
||||||
|
AccountAction,
|
||||||
|
BillAction,
|
||||||
|
CashflowAction,
|
||||||
|
CustomerAction,
|
||||||
|
ExpenseAction,
|
||||||
|
ItemAction,
|
||||||
|
ManualJournalAction,
|
||||||
|
ReportsAction,
|
||||||
|
SaleEstimateAction,
|
||||||
|
SaleInvoiceAction,
|
||||||
|
SaleReceiptAction,
|
||||||
|
VendorAction,
|
||||||
|
} from './abilityOption';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + I',
|
shortcut_key: 'Shift + I',
|
||||||
description: intl.get('jump_to_the_invoices'),
|
description: intl.get('jump_to_the_invoices'),
|
||||||
|
permission: {
|
||||||
|
ability: SaleInvoiceAction.View,
|
||||||
|
subject: AbilitySubject.Invoice,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + E',
|
shortcut_key: 'Shift + E',
|
||||||
description: intl.get('jump_to_the_estimates'),
|
description: intl.get('jump_to_the_estimates'),
|
||||||
|
permission: {
|
||||||
|
ability: SaleEstimateAction.View,
|
||||||
|
subject: AbilitySubject.Estimate,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + R',
|
shortcut_key: 'Shift + R',
|
||||||
description: intl.get('jump_to_the_receipts'),
|
description: intl.get('jump_to_the_receipts'),
|
||||||
|
permission: {
|
||||||
|
ability: SaleReceiptAction.View,
|
||||||
|
subject: AbilitySubject.Receipt,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + X',
|
shortcut_key: 'Shift + X',
|
||||||
description: intl.get('jump_to_the_expenses'),
|
description: intl.get('jump_to_the_expenses'),
|
||||||
|
permission: {
|
||||||
|
ability: ExpenseAction.View,
|
||||||
|
subject: AbilitySubject.Expense,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + C',
|
shortcut_key: 'Shift + C',
|
||||||
description: intl.get('jump_to_the_customers'),
|
description: intl.get('jump_to_the_customers'),
|
||||||
|
permission: {
|
||||||
|
ability: CustomerAction.View,
|
||||||
|
subject: AbilitySubject.Customer,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + V',
|
shortcut_key: 'Shift + V',
|
||||||
description: intl.get('jump_to_the_vendors'),
|
description: intl.get('jump_to_the_vendors'),
|
||||||
|
permission: {
|
||||||
|
ability: VendorAction.View,
|
||||||
|
subject: AbilitySubject.Vendor,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + A',
|
shortcut_key: 'Shift + A',
|
||||||
description: intl.get('jump_to_the_chart_of_accounts'),
|
description: intl.get('jump_to_the_chart_of_accounts'),
|
||||||
|
permission: {
|
||||||
|
ability: AccountAction.View,
|
||||||
|
subject: AbilitySubject.Account,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + B',
|
shortcut_key: 'Shift + B',
|
||||||
description: intl.get('jump_to_the_bills'),
|
description: intl.get('jump_to_the_bills'),
|
||||||
|
permission: {
|
||||||
|
ability: BillAction.View,
|
||||||
|
subject: AbilitySubject.Bill,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + M',
|
shortcut_key: 'Shift + M',
|
||||||
description: intl.get('jump_to_the_manual_journals'),
|
description: intl.get('jump_to_the_manual_journals'),
|
||||||
|
permission: {
|
||||||
|
ability: ManualJournalAction.View,
|
||||||
|
subject: AbilitySubject.ManualJournal,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + W',
|
shortcut_key: 'Shift + W',
|
||||||
description: intl.get('jump_to_the_items'),
|
description: intl.get('jump_to_the_items'),
|
||||||
|
permission: {
|
||||||
|
ability: ItemAction.View,
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + D',
|
shortcut_key: 'Shift + D',
|
||||||
description: intl.get('jump_to_the_add_money_in'),
|
description: intl.get('jump_to_the_add_money_in'),
|
||||||
|
permission: {
|
||||||
|
ability: CashflowAction.Create,
|
||||||
|
subject: AbilitySubject.Cashflow,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + Q',
|
shortcut_key: 'Shift + Q',
|
||||||
description: intl.get('jump_to_the_add_money_out'),
|
description: intl.get('jump_to_the_add_money_out'),
|
||||||
|
permission: {
|
||||||
|
ability: CashflowAction.Create,
|
||||||
|
subject: AbilitySubject.Cashflow,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + 1',
|
shortcut_key: 'Shift + 1',
|
||||||
description: intl.get('jump_to_the_balance_sheet'),
|
description: intl.get('jump_to_the_balance_sheet'),
|
||||||
|
permission: {
|
||||||
|
ability: ReportsAction.READ_BALANCE_SHEET,
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + 2',
|
shortcut_key: 'Shift + 2',
|
||||||
description: intl.get('jump_to_the_profit_loss_sheet'),
|
description: intl.get('jump_to_the_profit_loss_sheet'),
|
||||||
|
permission: {
|
||||||
|
ability: ReportsAction.READ_PROFIT_LOSS,
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + 3',
|
shortcut_key: 'Shift + 3',
|
||||||
description: intl.get('jump_to_the_journal_sheet'),
|
description: intl.get('jump_to_the_journal_sheet'),
|
||||||
|
permission: {
|
||||||
|
ability: ReportsAction.READ_JOURNAL,
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + 4',
|
shortcut_key: 'Shift + 4',
|
||||||
description: intl.get('jump_to_the_general_ledger_sheet'),
|
description: intl.get('jump_to_the_general_ledger_sheet'),
|
||||||
|
permission: {
|
||||||
|
ability: ReportsAction.READ_GENERAL_LEDGET,
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + 5',
|
shortcut_key: 'Shift + 5',
|
||||||
description: intl.get('jump_to_the_trial_balance_sheet'),
|
description: intl.get('jump_to_the_trial_balance_sheet'),
|
||||||
|
permission: {
|
||||||
|
ability: ReportsAction.READ_TRIAL_BALANCE_SHEET,
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Ctrl + Shift + I ',
|
shortcut_key: 'Ctrl + Shift + I ',
|
||||||
description: intl.get('create_a_new_invoice'),
|
description: intl.get('create_a_new_invoice'),
|
||||||
|
permission: {
|
||||||
|
ability: SaleInvoiceAction.Create,
|
||||||
|
subject: AbilitySubject.Invoice,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Ctrl + Shift + E ',
|
shortcut_key: 'Ctrl + Shift + E ',
|
||||||
description: intl.get('create_a_new_estimate'),
|
description: intl.get('create_a_new_estimate'),
|
||||||
|
permission: {
|
||||||
|
ability: SaleEstimateAction.Create,
|
||||||
|
subject: AbilitySubject.Estimate,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Ctrl + Shift + R ',
|
shortcut_key: 'Ctrl + Shift + R ',
|
||||||
description: intl.get('create_a_new_receipt'),
|
description: intl.get('create_a_new_receipt'),
|
||||||
|
permission: {
|
||||||
|
ability: SaleReceiptAction.Create,
|
||||||
|
subject: AbilitySubject.Receipt,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Ctrl + Shift + X ',
|
shortcut_key: 'Ctrl + Shift + X ',
|
||||||
description: intl.get('create_a_new_expense'),
|
description: intl.get('create_a_new_expense'),
|
||||||
|
permission: {
|
||||||
|
ability: ExpenseAction.Create,
|
||||||
|
subject: AbilitySubject.Expense,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Ctrl + Shift + C ',
|
shortcut_key: 'Ctrl + Shift + C ',
|
||||||
description: intl.get('create_a_new_customer'),
|
description: intl.get('create_a_new_customer'),
|
||||||
|
permission: {
|
||||||
|
ability: CustomerAction.Create,
|
||||||
|
subject: AbilitySubject.Customer,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Ctrl + Shift + V ',
|
shortcut_key: 'Ctrl + Shift + V ',
|
||||||
description: intl.get('create_a_new_vendor'),
|
description: intl.get('create_a_new_vendor'),
|
||||||
|
permission: {
|
||||||
|
ability: VendorAction.Create,
|
||||||
|
subject: AbilitySubject.Vendor,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Ctrl + Shift + B ',
|
shortcut_key: 'Ctrl + Shift + B ',
|
||||||
description: intl.get('create_a_new_bill'),
|
description: intl.get('create_a_new_bill'),
|
||||||
|
permission: {
|
||||||
|
ability: BillAction.Create,
|
||||||
|
subject: AbilitySubject.Bill,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Ctrl + Shift + M ',
|
shortcut_key: 'Ctrl + Shift + M ',
|
||||||
description: intl.get('create_a_new_journal'),
|
description: intl.get('create_a_new_journal'),
|
||||||
|
permission: {
|
||||||
|
ability: ManualJournalAction.Create,
|
||||||
|
subject: AbilitySubject.ManualJournal,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Ctrl + Shift + W ',
|
shortcut_key: 'Ctrl + Shift + W ',
|
||||||
description: intl.get('create_a_new_item'),
|
description: intl.get('create_a_new_item'),
|
||||||
|
permission: {
|
||||||
|
ability: ItemAction.Create,
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Ctrl + / ',
|
shortcut_key: 'Ctrl + / ',
|
||||||
|
|||||||
12
src/common/moreVertOptions.js
Normal file
12
src/common/moreVertOptions.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
export const moreVertOptions = [
|
||||||
|
{
|
||||||
|
name: intl.get('bad_debt.dialog.bad_debt'),
|
||||||
|
value: 'bad debt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: intl.get('bad_debt.dialog.cancel_bad_debt'),
|
||||||
|
value: 'cancel bad debt',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,10 +1,71 @@
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import {
|
||||||
|
AbilitySubject,
|
||||||
|
SaleInvoiceAction,
|
||||||
|
CustomerAction,
|
||||||
|
VendorAction,
|
||||||
|
ManualJournalAction,
|
||||||
|
ExpenseAction,
|
||||||
|
} from '../common/abilityOption';
|
||||||
|
import { useAbilitiesFilter } from '../hooks';
|
||||||
|
|
||||||
export const getQuickNewActions = () => [
|
export const getQuickNewActions = () => [
|
||||||
{ path: 'invoices/new', name: intl.get('sale_invoice') },
|
{
|
||||||
{ path: 'bills/new', name: intl.get('purchase_invoice') },
|
path: 'invoices/new',
|
||||||
{ path: 'make-journal-entry', name: intl.get('manual_journal') },
|
name: intl.get('sale_invoice'),
|
||||||
{ path: 'expenses/new', name: intl.get('expense') },
|
permission: {
|
||||||
{ path: 'customers/new', name: intl.get('customer') },
|
subject: AbilitySubject.Invoice,
|
||||||
{ path: 'vendors/new', name: intl.get('vendor') },
|
ability: SaleInvoiceAction.Create,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'bills/new',
|
||||||
|
name: intl.get('purchase_invoice'),
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Invoice,
|
||||||
|
ability: SaleInvoiceAction.Create,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'make-journal-entry',
|
||||||
|
name: intl.get('manual_journal'),
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.ManualJournal,
|
||||||
|
ability: ManualJournalAction.Create,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'expenses/new',
|
||||||
|
name: intl.get('expense'),
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Expense,
|
||||||
|
ability: ExpenseAction.Create,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'customers/new',
|
||||||
|
name: intl.get('customer'),
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Customer,
|
||||||
|
ability: CustomerAction.Create,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'vendors/new',
|
||||||
|
name: intl.get('vendor'),
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Vendor,
|
||||||
|
ability: VendorAction.Vendor,
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the dashboard quick new menu items.
|
||||||
|
*/
|
||||||
|
export const useGetQuickNewMenu = () => {
|
||||||
|
const quickNewMenu = getQuickNewActions();
|
||||||
|
const abilitiesFilter = useAbilitiesFilter();
|
||||||
|
|
||||||
|
return abilitiesFilter(quickNewMenu);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,13 +1,56 @@
|
|||||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||||
import { MenuItem, Button } from '@blueprintjs/core';
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
import { Select } from '@blueprintjs/select';
|
import { Select } from '@blueprintjs/select';
|
||||||
import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
import * as R from 'ramda';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import intl from 'react-intl-universal'
|
||||||
|
|
||||||
|
import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
||||||
import { filterAccountsByQuery } from './utils';
|
import { filterAccountsByQuery } from './utils';
|
||||||
import { nestedArrayToflatten } from 'utils';
|
import { nestedArrayToflatten } from 'utils';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
export default function AccountsSelectList({
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
// Create new account renderer.
|
||||||
|
const createNewItemRenderer = (query, active, handleClick) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon="add"
|
||||||
|
text={intl.get('list.create', { value: `"${query}"` })}
|
||||||
|
active={active}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create new item from the given query string.
|
||||||
|
const createNewItemFromQuery = (name) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filters accounts items.
|
||||||
|
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
|
||||||
|
const normalizedTitle = account.name.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts select list.
|
||||||
|
*/
|
||||||
|
function AccountsSelectList({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
accounts,
|
accounts,
|
||||||
initialAccountId,
|
initialAccountId,
|
||||||
selectedAccountId,
|
selectedAccountId,
|
||||||
@@ -21,6 +64,8 @@ export default function AccountsSelectList({
|
|||||||
filterByNormal,
|
filterByNormal,
|
||||||
filterByRootTypes,
|
filterByRootTypes,
|
||||||
|
|
||||||
|
allowCreate,
|
||||||
|
|
||||||
buttonProps = {},
|
buttonProps = {},
|
||||||
}) {
|
}) {
|
||||||
const flattenAccounts = useMemo(
|
const flattenAccounts = useMemo(
|
||||||
@@ -51,6 +96,7 @@ export default function AccountsSelectList({
|
|||||||
[initialAccountId, filteredAccounts],
|
[initialAccountId, filteredAccounts],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Select account item.
|
||||||
const [selectedAccount, setSelectedAccount] = useState(
|
const [selectedAccount, setSelectedAccount] = useState(
|
||||||
initialAccount || null,
|
initialAccount || null,
|
||||||
);
|
);
|
||||||
@@ -76,31 +122,25 @@ export default function AccountsSelectList({
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onAccountSelect = useCallback(
|
// Handle the account item select.
|
||||||
|
const handleAccountSelect = useCallback(
|
||||||
(account) => {
|
(account) => {
|
||||||
setSelectedAccount({ ...account });
|
if (account.id) {
|
||||||
onAccountSelected && onAccountSelected(account);
|
setSelectedAccount({ ...account });
|
||||||
},
|
onAccountSelected && onAccountSelected(account);
|
||||||
[setSelectedAccount, onAccountSelected],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Filters accounts items.
|
|
||||||
const filterAccountsPredicater = useCallback(
|
|
||||||
(query, account, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = account.name.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
} else {
|
||||||
return (
|
openDialog('account-form');
|
||||||
`${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[],
|
[setSelectedAccount, onAccountSelected, openDialog],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Maybe inject new item props to select component.
|
||||||
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
|
? createNewItemFromQuery
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
items={filteredAccounts}
|
items={filteredAccounts}
|
||||||
@@ -113,11 +153,13 @@ export default function AccountsSelectList({
|
|||||||
inline: popoverFill,
|
inline: popoverFill,
|
||||||
}}
|
}}
|
||||||
filterable={true}
|
filterable={true}
|
||||||
onItemSelect={onAccountSelect}
|
onItemSelect={handleAccountSelect}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={classNames('form-group--select-list', {
|
className={classNames('form-group--select-list', {
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
})}
|
})}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -127,3 +169,5 @@ export default function AccountsSelectList({
|
|||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDialogActions)(AccountsSelectList);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
|||||||
import { MenuItem } from '@blueprintjs/core';
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
import { Suggest } from '@blueprintjs/select';
|
import { Suggest } from '@blueprintjs/select';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
@@ -10,10 +11,55 @@ import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
|||||||
import { filterAccountsByQuery } from './utils';
|
import { filterAccountsByQuery } from './utils';
|
||||||
import { nestedArrayToflatten } from 'utils';
|
import { nestedArrayToflatten } from 'utils';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
// Create new account renderer.
|
||||||
|
const createNewItemRenderer = (query, active, handleClick) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon="add"
|
||||||
|
text={intl.get('list.create', { value: `"${query}"` })}
|
||||||
|
active={active}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create new item from the given query string.
|
||||||
|
const createNewItemFromQuery = (name) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle input value renderer.
|
||||||
|
const handleInputValueRenderer = (inputValue) => {
|
||||||
|
if (inputValue) {
|
||||||
|
return inputValue.name.toString();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filters accounts items.
|
||||||
|
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
|
||||||
|
const normalizedTitle = account.name.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts suggest field.
|
* Accounts suggest field.
|
||||||
*/
|
*/
|
||||||
export default function AccountsSuggestField({
|
function AccountsSuggestField({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
accounts,
|
accounts,
|
||||||
initialAccountId,
|
initialAccountId,
|
||||||
selectedAccountId,
|
selectedAccountId,
|
||||||
@@ -26,6 +72,8 @@ export default function AccountsSuggestField({
|
|||||||
filterByNormal,
|
filterByNormal,
|
||||||
filterByRootTypes = [],
|
filterByRootTypes = [],
|
||||||
|
|
||||||
|
allowCreate,
|
||||||
|
|
||||||
...suggestProps
|
...suggestProps
|
||||||
}) {
|
}) {
|
||||||
const flattenAccounts = useMemo(
|
const flattenAccounts = useMemo(
|
||||||
@@ -69,23 +117,6 @@ export default function AccountsSuggestField({
|
|||||||
}
|
}
|
||||||
}, [selectedAccountId, filteredAccounts, setSelectedAccount]);
|
}, [selectedAccountId, filteredAccounts, setSelectedAccount]);
|
||||||
|
|
||||||
// Filters accounts items.
|
|
||||||
const filterAccountsPredicater = useCallback(
|
|
||||||
(query, account, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = account.name.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
`${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Account item of select accounts field.
|
// Account item of select accounts field.
|
||||||
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
|
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
|
||||||
return (
|
return (
|
||||||
@@ -98,28 +129,31 @@ export default function AccountsSuggestField({
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleInputValueRenderer = (inputValue) => {
|
const handleAccountSelect = useCallback(
|
||||||
if (inputValue) {
|
|
||||||
return inputValue.name.toString();
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAccountSelect = useCallback(
|
|
||||||
(account) => {
|
(account) => {
|
||||||
setSelectedAccount({ ...account });
|
if (account.id) {
|
||||||
onAccountSelected && onAccountSelected(account);
|
setSelectedAccount({ ...account });
|
||||||
|
onAccountSelected && onAccountSelected(account);
|
||||||
|
} else {
|
||||||
|
openDialog('account-form');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[setSelectedAccount, onAccountSelected],
|
[setSelectedAccount, onAccountSelected, openDialog],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Maybe inject new item props to select component.
|
||||||
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
|
? createNewItemFromQuery
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suggest
|
<Suggest
|
||||||
items={filteredAccounts}
|
items={filteredAccounts}
|
||||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
||||||
itemRenderer={accountItem}
|
itemRenderer={accountItem}
|
||||||
itemPredicate={filterAccountsPredicater}
|
itemPredicate={filterAccountsPredicater}
|
||||||
onItemSelect={onAccountSelect}
|
onItemSelect={handleAccountSelect}
|
||||||
selectedItem={selectedAccount}
|
selectedItem={selectedAccount}
|
||||||
inputProps={{ placeholder: defaultSelectText }}
|
inputProps={{ placeholder: defaultSelectText }}
|
||||||
resetOnClose={true}
|
resetOnClose={true}
|
||||||
@@ -129,7 +163,11 @@ export default function AccountsSuggestField({
|
|||||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
})}
|
})}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
{...suggestProps}
|
{...suggestProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDialogActions)(AccountsSuggestField);
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button } from '@blueprintjs/core';
|
|
||||||
|
|
||||||
export const ButtonLink = styled(Button)`
|
export const ButtonLink = styled.button`
|
||||||
line-height: inherit;
|
color: #0052cc;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&.bp3-small {
|
&:hover,
|
||||||
min-height: auto;
|
&:active {
|
||||||
min-width: auto;
|
text-decoration: underline;
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
&:not([class*='bp3-intent-']) {
|
|
||||||
&,
|
|
||||||
&:hover {
|
|
||||||
color: #0052cc;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ export default function ContactSelecetList({
|
|||||||
contactsList,
|
contactsList,
|
||||||
initialContactId,
|
initialContactId,
|
||||||
selectedContactId,
|
selectedContactId,
|
||||||
selectedContactType,
|
createNewItemFrom,
|
||||||
defaultSelectText = <T id={'select_contact'} />,
|
defaultSelectText = <T id={'select_contact'} />,
|
||||||
onContactSelected,
|
onContactSelected,
|
||||||
popoverFill = false,
|
popoverFill = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
buttonProps,
|
buttonProps,
|
||||||
|
|
||||||
|
...restProps
|
||||||
}) {
|
}) {
|
||||||
const contacts = useMemo(
|
const contacts = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -65,7 +67,7 @@ export default function ContactSelecetList({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Filter Contact List
|
// Filter Contact List
|
||||||
const filterContacts = (query, contact, index, exactMatch) => {
|
const itemPredicate = (query, contact, index, exactMatch) => {
|
||||||
const normalizedTitle = contact.display_name.toLowerCase();
|
const normalizedTitle = contact.display_name.toLowerCase();
|
||||||
const normalizedQuery = query.toLowerCase();
|
const normalizedQuery = query.toLowerCase();
|
||||||
if (exactMatch) {
|
if (exactMatch) {
|
||||||
@@ -83,7 +85,7 @@ export default function ContactSelecetList({
|
|||||||
items={contacts}
|
items={contacts}
|
||||||
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||||
itemRenderer={handleContactRenderer}
|
itemRenderer={handleContactRenderer}
|
||||||
itemPredicate={filterContacts}
|
itemPredicate={itemPredicate}
|
||||||
filterable={true}
|
filterable={true}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onItemSelect={onContactSelect}
|
onItemSelect={onContactSelect}
|
||||||
@@ -92,8 +94,9 @@ export default function ContactSelecetList({
|
|||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
})}
|
})}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
placeholder: intl.get('filter_')
|
placeholder: intl.get('filter_'),
|
||||||
}}
|
}}
|
||||||
|
{...restProps}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
86
src/components/Contacts/ContactSelectField.js
Normal file
86
src/components/Contacts/ContactSelectField.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
|
import { Select } from '@blueprintjs/select';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
|
import { itemPredicate, handleContactRenderer } from './utils';
|
||||||
|
|
||||||
|
export default function ContactSelectField({
|
||||||
|
contacts,
|
||||||
|
initialContactId,
|
||||||
|
selectedContactId,
|
||||||
|
defaultSelectText = <T id={'select_contact'} />,
|
||||||
|
onContactSelected,
|
||||||
|
popoverFill = false,
|
||||||
|
disabled = false,
|
||||||
|
buttonProps,
|
||||||
|
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
const localContacts = useMemo(
|
||||||
|
() =>
|
||||||
|
contacts.map((contact) => ({
|
||||||
|
...contact,
|
||||||
|
_id: `${contact.id}_${contact.contact_type}`,
|
||||||
|
})),
|
||||||
|
[contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialContact = useMemo(
|
||||||
|
() => contacts.find((a) => a.id === initialContactId),
|
||||||
|
[initialContactId, contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selecetedContact, setSelectedContact] = useState(
|
||||||
|
initialContact || null,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof selectedContactId !== 'undefined') {
|
||||||
|
const account = selectedContactId
|
||||||
|
? contacts.find((a) => a.id === selectedContactId)
|
||||||
|
: null;
|
||||||
|
setSelectedContact(account);
|
||||||
|
}
|
||||||
|
}, [selectedContactId, contacts, setSelectedContact]);
|
||||||
|
|
||||||
|
const handleContactSelect = useCallback(
|
||||||
|
(contact) => {
|
||||||
|
setSelectedContact({ ...contact });
|
||||||
|
onContactSelected && onContactSelected(contact);
|
||||||
|
},
|
||||||
|
[setSelectedContact, onContactSelected],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
items={localContacts}
|
||||||
|
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||||
|
itemRenderer={handleContactRenderer}
|
||||||
|
itemPredicate={itemPredicate}
|
||||||
|
filterable={true}
|
||||||
|
disabled={disabled}
|
||||||
|
onItemSelect={handleContactSelect}
|
||||||
|
popoverProps={{ minimal: true, usePortal: !popoverFill }}
|
||||||
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
|
})}
|
||||||
|
inputProps={{
|
||||||
|
placeholder: intl.get('filter_'),
|
||||||
|
}}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
text={
|
||||||
|
selecetedContact ? selecetedContact.display_name : defaultSelectText
|
||||||
|
}
|
||||||
|
{...buttonProps}
|
||||||
|
/>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
116
src/components/Contacts/CustomerSelectField.js
Normal file
116
src/components/Contacts/CustomerSelectField.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
|
import { Select } from '@blueprintjs/select';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
|
import {
|
||||||
|
itemPredicate,
|
||||||
|
handleContactRenderer,
|
||||||
|
createNewItemRenderer,
|
||||||
|
createNewItemFromQuery,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { DRAWERS } from 'common/drawers';
|
||||||
|
|
||||||
|
function CustomerSelectField({
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
|
contacts,
|
||||||
|
initialContactId,
|
||||||
|
selectedContactId,
|
||||||
|
defaultSelectText = <T id={'select_contact'} />,
|
||||||
|
onContactSelected,
|
||||||
|
popoverFill = false,
|
||||||
|
disabled = false,
|
||||||
|
allowCreate,
|
||||||
|
buttonProps,
|
||||||
|
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
const localContacts = useMemo(
|
||||||
|
() =>
|
||||||
|
contacts.map((contact) => ({
|
||||||
|
...contact,
|
||||||
|
_id: `${contact.id}_${contact.contact_type}`,
|
||||||
|
})),
|
||||||
|
[contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialContact = useMemo(
|
||||||
|
() => contacts.find((a) => a.id === initialContactId),
|
||||||
|
[initialContactId, contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selecetedContact, setSelectedContact] = useState(
|
||||||
|
initialContact || null,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof selectedContactId !== 'undefined') {
|
||||||
|
const account = selectedContactId
|
||||||
|
? contacts.find((a) => a.id === selectedContactId)
|
||||||
|
: null;
|
||||||
|
setSelectedContact(account);
|
||||||
|
}
|
||||||
|
}, [selectedContactId, contacts, setSelectedContact]);
|
||||||
|
|
||||||
|
const handleContactSelect = useCallback(
|
||||||
|
(contact) => {
|
||||||
|
if (contact.id) {
|
||||||
|
setSelectedContact({ ...contact });
|
||||||
|
onContactSelected && onContactSelected(contact);
|
||||||
|
} else {
|
||||||
|
openDrawer(DRAWERS.QUICK_CREATE_CUSTOMER);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setSelectedContact, onContactSelected, openDrawer],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Maybe inject create new item props to suggest component.
|
||||||
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
|
? createNewItemFromQuery
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
items={localContacts}
|
||||||
|
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||||
|
itemRenderer={handleContactRenderer}
|
||||||
|
itemPredicate={itemPredicate}
|
||||||
|
filterable={true}
|
||||||
|
disabled={disabled}
|
||||||
|
onItemSelect={handleContactSelect}
|
||||||
|
popoverProps={{ minimal: true, usePortal: !popoverFill }}
|
||||||
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
|
})}
|
||||||
|
inputProps={{
|
||||||
|
placeholder: intl.get('filter_'),
|
||||||
|
}}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
|
createNewItemPosition={'top'}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
text={
|
||||||
|
selecetedContact ? selecetedContact.display_name : defaultSelectText
|
||||||
|
}
|
||||||
|
{...buttonProps}
|
||||||
|
/>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDrawerActions)(CustomerSelectField);
|
||||||
115
src/components/Contacts/VendorSelectField.js
Normal file
115
src/components/Contacts/VendorSelectField.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
|
import { Select } from '@blueprintjs/select';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
|
import {
|
||||||
|
itemPredicate,
|
||||||
|
handleContactRenderer,
|
||||||
|
createNewItemFromQuery,
|
||||||
|
createNewItemRenderer,
|
||||||
|
} from './utils';
|
||||||
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { DRAWERS } from 'common/drawers';
|
||||||
|
|
||||||
|
function VendorSelectField({
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
|
contacts,
|
||||||
|
initialContactId,
|
||||||
|
selectedContactId,
|
||||||
|
defaultSelectText = <T id={'select_contact'} />,
|
||||||
|
onContactSelected,
|
||||||
|
popoverFill = false,
|
||||||
|
disabled = false,
|
||||||
|
allowCreate,
|
||||||
|
buttonProps,
|
||||||
|
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
const localContacts = useMemo(
|
||||||
|
() =>
|
||||||
|
contacts.map((contact) => ({
|
||||||
|
...contact,
|
||||||
|
_id: `${contact.id}_${contact.contact_type}`,
|
||||||
|
})),
|
||||||
|
[contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialContact = useMemo(
|
||||||
|
() => contacts.find((a) => a.id === initialContactId),
|
||||||
|
[initialContactId, contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selecetedContact, setSelectedContact] = useState(
|
||||||
|
initialContact || null,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof selectedContactId !== 'undefined') {
|
||||||
|
const account = selectedContactId
|
||||||
|
? contacts.find((a) => a.id === selectedContactId)
|
||||||
|
: null;
|
||||||
|
setSelectedContact(account);
|
||||||
|
}
|
||||||
|
}, [selectedContactId, contacts, setSelectedContact]);
|
||||||
|
|
||||||
|
const handleContactSelect = useCallback(
|
||||||
|
(contact) => {
|
||||||
|
if (contact.id) {
|
||||||
|
setSelectedContact({ ...contact });
|
||||||
|
onContactSelected && onContactSelected(contact);
|
||||||
|
} else {
|
||||||
|
openDrawer(DRAWERS.QUICK_WRITE_VENDOR);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setSelectedContact, onContactSelected, openDrawer],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Maybe inject create new item props to suggest component.
|
||||||
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
|
? createNewItemFromQuery
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
items={localContacts}
|
||||||
|
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||||
|
itemRenderer={handleContactRenderer}
|
||||||
|
itemPredicate={itemPredicate}
|
||||||
|
filterable={true}
|
||||||
|
disabled={disabled}
|
||||||
|
onItemSelect={handleContactSelect}
|
||||||
|
popoverProps={{ minimal: true, usePortal: !popoverFill }}
|
||||||
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
|
})}
|
||||||
|
inputProps={{
|
||||||
|
placeholder: intl.get('filter_'),
|
||||||
|
}}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
|
createNewItemPosition={'top'}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
text={
|
||||||
|
selecetedContact ? selecetedContact.display_name : defaultSelectText
|
||||||
|
}
|
||||||
|
{...buttonProps}
|
||||||
|
/>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDrawerActions)(VendorSelectField);
|
||||||
5
src/components/Contacts/index.js
Normal file
5
src/components/Contacts/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import ContactSelectField from './ContactSelectField';
|
||||||
|
import CustomerSelectField from './CustomerSelectField';
|
||||||
|
import VendorSelectField from './VendorSelectField';
|
||||||
|
|
||||||
|
export { ContactSelectField, CustomerSelectField, VendorSelectField };
|
||||||
44
src/components/Contacts/utils.js
Normal file
44
src/components/Contacts/utils.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
// Filter Contact List
|
||||||
|
export const itemPredicate = (query, contact, index, exactMatch) => {
|
||||||
|
const normalizedTitle = contact.display_name.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
`${contact.display_name} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleContactRenderer = (contact, { handleClick }) => (
|
||||||
|
<MenuItem
|
||||||
|
key={contact.id}
|
||||||
|
text={contact.display_name}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Creates a new item from query.
|
||||||
|
export const createNewItemFromQuery = (name) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle quick create new customer.
|
||||||
|
export const createNewItemRenderer = (query, active, handleClick) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon="add"
|
||||||
|
text={intl.get('list.create', { value: `"${query}"` })}
|
||||||
|
active={active}
|
||||||
|
shouldDismissPopover={false}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useUser } from 'hooks/query';
|
|
||||||
import withAuthentication from '../../containers/Authentication/withAuthentication';
|
|
||||||
|
|
||||||
const AuthenticatedUserContext = React.createContext();
|
|
||||||
|
|
||||||
function AuthenticatedUserComponent({ authenticatedUserId, children }) {
|
|
||||||
const { data: user, ...restProps } = useUser(authenticatedUserId);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthenticatedUserContext.Provider
|
|
||||||
value={{
|
|
||||||
user,
|
|
||||||
...restProps,
|
|
||||||
}}
|
|
||||||
children={children}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AuthenticatedUser = withAuthentication(
|
|
||||||
({ authenticatedUserId }) => ({
|
|
||||||
authenticatedUserId,
|
|
||||||
}),
|
|
||||||
)(AuthenticatedUserComponent);
|
|
||||||
|
|
||||||
export const useAuthenticatedUser = () =>
|
|
||||||
React.useContext(AuthenticatedUserContext);
|
|
||||||
25
src/components/Dashboard/DashboardAbilityProvider.js
Normal file
25
src/components/Dashboard/DashboardAbilityProvider.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Ability } from '@casl/ability';
|
||||||
|
import { createContextualCan } from '@casl/react';
|
||||||
|
import { useDashboardMeta } from '../../hooks/query';
|
||||||
|
|
||||||
|
export const AbilityContext = React.createContext();
|
||||||
|
export const Can = createContextualCan(AbilityContext.Consumer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard ability provider.
|
||||||
|
*/
|
||||||
|
export function DashboardAbilityProvider({ children }) {
|
||||||
|
const {
|
||||||
|
data: { abilities },
|
||||||
|
} = useDashboardMeta();
|
||||||
|
|
||||||
|
// Ability instance.
|
||||||
|
const ability = new Ability(abilities);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbilityContext.Provider value={ability}>
|
||||||
|
{children}
|
||||||
|
</AbilityContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,18 +1,53 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as R from 'ramda';
|
import {
|
||||||
|
useAuthenticatedAccount,
|
||||||
import { useUser, useCurrentOrganization } from '../../hooks/query';
|
useCurrentOrganization,
|
||||||
|
useDashboardMeta,
|
||||||
|
} from '../../hooks/query';
|
||||||
import { useSplashLoading } from '../../hooks/state';
|
import { useSplashLoading } from '../../hooks/state';
|
||||||
import { useWatch, useWatchImmediate, useWhen } from '../../hooks';
|
import { useWatch, useWatchImmediate, useWhen } from '../../hooks';
|
||||||
|
|
||||||
import withAuthentication from '../../containers/Authentication/withAuthentication';
|
|
||||||
|
|
||||||
import { setCookie, getCookie } from '../../utils';
|
import { setCookie, getCookie } from '../../utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dashboard async booting.
|
* Dashboard meta async booting.
|
||||||
*/
|
*/
|
||||||
function DashboardBootJSX({ authenticatedUserId }) {
|
export function useDashboardMetaBoot() {
|
||||||
|
const {
|
||||||
|
data: dashboardMeta,
|
||||||
|
isLoading: isDashboardMetaLoading,
|
||||||
|
isSuccess: isDashboardMetaSuccess,
|
||||||
|
} = useDashboardMeta({
|
||||||
|
keepPreviousData: true,
|
||||||
|
});
|
||||||
|
const [startLoading, stopLoading] = useSplashLoading();
|
||||||
|
|
||||||
|
useWatchImmediate((value) => {
|
||||||
|
value && startLoading();
|
||||||
|
}, isDashboardMetaLoading);
|
||||||
|
|
||||||
|
useWatchImmediate(() => {
|
||||||
|
isDashboardMetaSuccess && stopLoading();
|
||||||
|
}, isDashboardMetaSuccess);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoading: isDashboardMetaLoading,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard async booting.
|
||||||
|
* @returns {{ isLoading: boolean }}
|
||||||
|
*/
|
||||||
|
export function useDashboardBoot() {
|
||||||
|
const { isLoading } = useDashboardMetaBoot();
|
||||||
|
|
||||||
|
return { isLoading };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application async booting.
|
||||||
|
*/
|
||||||
|
export function useApplicationBoot() {
|
||||||
// Fetches the current user's organization.
|
// Fetches the current user's organization.
|
||||||
const {
|
const {
|
||||||
isSuccess: isCurrentOrganizationSuccess,
|
isSuccess: isCurrentOrganizationSuccess,
|
||||||
@@ -22,7 +57,7 @@ function DashboardBootJSX({ authenticatedUserId }) {
|
|||||||
|
|
||||||
// Authenticated user.
|
// Authenticated user.
|
||||||
const { isSuccess: isAuthUserSuccess, isLoading: isAuthUserLoading } =
|
const { isSuccess: isAuthUserSuccess, isLoading: isAuthUserLoading } =
|
||||||
useUser(authenticatedUserId);
|
useAuthenticatedAccount();
|
||||||
|
|
||||||
// Initial locale cookie value.
|
// Initial locale cookie value.
|
||||||
const localeCookie = getCookie('locale');
|
const localeCookie = getCookie('locale');
|
||||||
@@ -86,11 +121,8 @@ function DashboardBootJSX({ authenticatedUserId }) {
|
|||||||
isBooted.current = true;
|
isBooted.current = true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DashboardBoot = R.compose(
|
return {
|
||||||
withAuthentication(({ authenticatedUserId }) => ({
|
isLoading: isOrgLoading || isAuthUserLoading,
|
||||||
authenticatedUserId,
|
};
|
||||||
})),
|
}
|
||||||
)(DashboardBootJSX);
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React from 'react';
|
|||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import DashboardTopbar from 'components/Dashboard/DashboardTopbar';
|
import DashboardTopbar from 'components/Dashboard/DashboardTopbar';
|
||||||
import DashboardContentRoutes from 'components/Dashboard/DashboardContentRoute';
|
import DashboardContentRoutes from 'components/Dashboard/DashboardContentRoute';
|
||||||
import DashboardFooter from 'components/Dashboard/DashboardFooter';
|
|
||||||
import DashboardErrorBoundary from './DashboardErrorBoundary';
|
import DashboardErrorBoundary from './DashboardErrorBoundary';
|
||||||
|
|
||||||
export default React.forwardRef(({}, ref) => {
|
export default React.forwardRef(({}, ref) => {
|
||||||
@@ -11,7 +10,6 @@ export default React.forwardRef(({}, ref) => {
|
|||||||
<div className="dashboard-content" id="dashboard" ref={ref}>
|
<div className="dashboard-content" id="dashboard" ref={ref}>
|
||||||
<DashboardTopbar />
|
<DashboardTopbar />
|
||||||
<DashboardContentRoutes />
|
<DashboardContentRoutes />
|
||||||
<DashboardFooter />
|
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { DashboardAbilityProvider } from '../../components';
|
||||||
|
import { useDashboardBoot } from './DashboardBoot';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dashboard provider.
|
* Dashboard provider.
|
||||||
*/
|
*/
|
||||||
export default function DashboardProvider({ children }) {
|
export default function DashboardProvider({ children }) {
|
||||||
return children;
|
const { isLoading } = useDashboardBoot();
|
||||||
|
|
||||||
|
// Avoid display any dashboard component before complete booting.
|
||||||
|
if (isLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <DashboardAbilityProvider>{children}</DashboardAbilityProvider>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import withDashboard from 'containers/Dashboard/withDashboard';
|
|||||||
import QuickNewDropdown from 'containers/QuickNewDropdown/QuickNewDropdown';
|
import QuickNewDropdown from 'containers/QuickNewDropdown/QuickNewDropdown';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
|
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
|
||||||
|
import { useGetUniversalSearchTypeOptions } from '../../containers/UniversalSearch/utils';
|
||||||
|
|
||||||
function DashboardTopbarSubscriptionMessage() {
|
function DashboardTopbarSubscriptionMessage() {
|
||||||
return (
|
return (
|
||||||
@@ -142,11 +143,8 @@ function DashboardTopbar({
|
|||||||
<Navbar class="dashboard__topbar-navbar">
|
<Navbar class="dashboard__topbar-navbar">
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<If condition={isSubscriptionActive}>
|
<If condition={isSubscriptionActive}>
|
||||||
<Button
|
<DashboardQuickSearchButton
|
||||||
onClick={() => openGlobalSearch(true)}
|
onClick={() => openGlobalSearch(true)}
|
||||||
className={Classes.MINIMAL}
|
|
||||||
icon={<Icon icon={'search-24'} iconSize={20} />}
|
|
||||||
text={<T id={'quick_find'} />}
|
|
||||||
/>
|
/>
|
||||||
<QuickNewDropdown />
|
<QuickNewDropdown />
|
||||||
|
|
||||||
@@ -195,3 +193,23 @@ export default compose(
|
|||||||
'main',
|
'main',
|
||||||
),
|
),
|
||||||
)(DashboardTopbar);
|
)(DashboardTopbar);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard quick search button.
|
||||||
|
*/
|
||||||
|
function DashboardQuickSearchButton({ ...rest }) {
|
||||||
|
const searchTypeOptions = useGetUniversalSearchTypeOptions();
|
||||||
|
|
||||||
|
// Can't continue if there is no any search type option.
|
||||||
|
if (searchTypeOptions.length <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon={'search-24'} iconSize={20} />}
|
||||||
|
text={<T id={'quick_find'} />}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,31 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as R from 'ramda';
|
|
||||||
|
|
||||||
import { AuthenticatedUser } from './AuthenticatedUser';
|
import { useApplicationBoot } from '../../components';
|
||||||
import { DashboardBoot } from '../../components';
|
|
||||||
|
|
||||||
import withDashboard from '../../containers/Dashboard/withDashboard';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private pages provider.
|
* Private pages provider.
|
||||||
*/
|
*/
|
||||||
function PrivatePagesProviderComponent({
|
export function PrivatePagesProvider({
|
||||||
splashScreenCompleted,
|
|
||||||
|
|
||||||
// #ownProps
|
// #ownProps
|
||||||
children,
|
children,
|
||||||
}) {
|
}) {
|
||||||
return (
|
const { isLoading } = useApplicationBoot();
|
||||||
<AuthenticatedUser>
|
|
||||||
<DashboardBoot />
|
|
||||||
|
|
||||||
{splashScreenCompleted ? children : null}
|
return <React.Fragment>{!isLoading ? children : null}</React.Fragment>;
|
||||||
</AuthenticatedUser>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PrivatePagesProvider = R.compose(
|
|
||||||
withDashboard(({ splashScreenCompleted }) => ({
|
|
||||||
splashScreenCompleted,
|
|
||||||
})),
|
|
||||||
)(PrivatePagesProviderComponent);
|
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ import { firstLettersArgs } from 'utils';
|
|||||||
import { useAuthActions } from 'hooks/state';
|
import { useAuthActions } from 'hooks/state';
|
||||||
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import { compose } from 'utils';
|
|
||||||
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
|
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
|
||||||
import { useAuthenticatedUser } from './AuthenticatedUser';
|
|
||||||
|
|
||||||
|
import { useAuthenticatedAccount } from 'hooks/query'
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard topbar user.
|
||||||
|
*/
|
||||||
function DashboardTopbarUser({
|
function DashboardTopbarUser({
|
||||||
openDialog,
|
openDialog,
|
||||||
|
|
||||||
@@ -28,7 +32,7 @@ function DashboardTopbarUser({
|
|||||||
const { setLogout } = useAuthActions();
|
const { setLogout } = useAuthActions();
|
||||||
|
|
||||||
// Retrieve authenticated user information.
|
// Retrieve authenticated user information.
|
||||||
const { user } = useAuthenticatedUser();
|
const { data: user } = useAuthenticatedAccount();
|
||||||
|
|
||||||
const onClickLogout = () => {
|
const onClickLogout = () => {
|
||||||
setLogout();
|
setLogout();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './SplashScreen';
|
export * from './SplashScreen';
|
||||||
export * from './DashboardBoot';
|
export * from './DashboardBoot';
|
||||||
export * from './DashboardThemeProvider';
|
export * from './DashboardThemeProvider';
|
||||||
|
export * from './DashboardAbilityProvider';
|
||||||
@@ -17,6 +17,8 @@ export default function AccountCellRenderer({
|
|||||||
accountsDataProp,
|
accountsDataProp,
|
||||||
filterAccountsByRootTypes,
|
filterAccountsByRootTypes,
|
||||||
filterAccountsByTypes,
|
filterAccountsByTypes,
|
||||||
|
fieldProps,
|
||||||
|
formGroupProps,
|
||||||
},
|
},
|
||||||
row: { index, original },
|
row: { index, original },
|
||||||
cell: { value: initialValue },
|
cell: { value: initialValue },
|
||||||
@@ -53,6 +55,7 @@ export default function AccountCellRenderer({
|
|||||||
'form-group--account',
|
'form-group--account',
|
||||||
Classes.FILL,
|
Classes.FILL,
|
||||||
)}
|
)}
|
||||||
|
{...formGroupProps}
|
||||||
>
|
>
|
||||||
<AccountsSuggestField
|
<AccountsSuggestField
|
||||||
accounts={accounts}
|
accounts={accounts}
|
||||||
@@ -66,6 +69,7 @@ export default function AccountCellRenderer({
|
|||||||
}}
|
}}
|
||||||
openOnKeyDown={true}
|
openOnKeyDown={true}
|
||||||
blurOnSelectClose={false}
|
blurOnSelectClose={false}
|
||||||
|
{...fieldProps}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import React, { useCallback, useRef } from 'react';
|
import React, { useCallback, useRef } from 'react';
|
||||||
// import ItemsListField from 'components/ItemsListField';
|
|
||||||
import ItemsSuggestField from 'components/ItemsSuggestField';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import ItemsSuggestField from 'components/ItemsSuggestField';
|
||||||
|
|
||||||
import { useCellAutoFocus } from 'hooks';
|
import { useCellAutoFocus } from 'hooks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Items list cell.
|
||||||
|
*/
|
||||||
export default function ItemsListCell({
|
export default function ItemsListCell({
|
||||||
column: { id, filterSellable, filterPurchasable },
|
column: { id, filterSellable, filterPurchasable, fieldProps, formGroupProps },
|
||||||
row: { index },
|
row: { index },
|
||||||
cell: { value: initialValue },
|
cell: { value: initialValue },
|
||||||
payload: { items, updateData, errors, autoFocus },
|
payload: { items, updateData, errors, autoFocus },
|
||||||
@@ -19,6 +21,7 @@ export default function ItemsListCell({
|
|||||||
// Auto-focus the items list input field.
|
// Auto-focus the items list input field.
|
||||||
useCellAutoFocus(fieldRef, autoFocus, id, index);
|
useCellAutoFocus(fieldRef, autoFocus, id, index);
|
||||||
|
|
||||||
|
// Handle the item selected.
|
||||||
const handleItemSelected = useCallback(
|
const handleItemSelected = useCallback(
|
||||||
(item) => {
|
(item) => {
|
||||||
updateData(index, id, item.id);
|
updateData(index, id, item.id);
|
||||||
@@ -32,6 +35,7 @@ export default function ItemsListCell({
|
|||||||
<FormGroup
|
<FormGroup
|
||||||
intent={error ? Intent.DANGER : null}
|
intent={error ? Intent.DANGER : null}
|
||||||
className={classNames('form-group--select-list', Classes.FILL)}
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
|
{...formGroupProps}
|
||||||
>
|
>
|
||||||
<ItemsSuggestField
|
<ItemsSuggestField
|
||||||
items={items}
|
items={items}
|
||||||
@@ -45,6 +49,7 @@ export default function ItemsListCell({
|
|||||||
}}
|
}}
|
||||||
openOnKeyDown={true}
|
openOnKeyDown={true}
|
||||||
blurOnSelectClose={false}
|
blurOnSelectClose={false}
|
||||||
|
{...fieldProps}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
|||||||
51
src/components/DataTableCells/SwitchFieldCell.js
Normal file
51
src/components/DataTableCells/SwitchFieldCell.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Classes, Switch, FormGroup, Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { safeInvoke } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch editable cell.
|
||||||
|
*/
|
||||||
|
const SwitchEditableCell = ({
|
||||||
|
row: { index, original },
|
||||||
|
column: { id, switchProps, onSwitchChange },
|
||||||
|
cell: { value: initialValue },
|
||||||
|
payload,
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = React.useState(initialValue);
|
||||||
|
|
||||||
|
// Handle the switch change.
|
||||||
|
const onChange = (e) => {
|
||||||
|
const newValue = e.target.checked;
|
||||||
|
|
||||||
|
setValue(newValue);
|
||||||
|
|
||||||
|
safeInvoke(payload.updateData, index, id, newValue);
|
||||||
|
safeInvoke(onSwitchChange, e, newValue, original);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setValue(initialValue);
|
||||||
|
}, [initialValue]);
|
||||||
|
|
||||||
|
const error = payload.errors?.[index]?.[id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
intent={error ? Intent.DANGER : null}
|
||||||
|
className={classNames(Classes.FILL)}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
checked={initialValue}
|
||||||
|
minimal={true}
|
||||||
|
className="ml2"
|
||||||
|
{...switchProps}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SwitchEditableCell;
|
||||||
42
src/components/DataTableCells/TextAreaCell.js
Normal file
42
src/components/DataTableCells/TextAreaCell.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Classes, TextArea, FormGroup, Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
const TextAreaEditableCell = ({
|
||||||
|
row: { index },
|
||||||
|
column: { id },
|
||||||
|
cell: { value: initialValue },
|
||||||
|
payload,
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = useState(initialValue);
|
||||||
|
|
||||||
|
const onChange = (e) => {
|
||||||
|
setValue(e.target.value);
|
||||||
|
};
|
||||||
|
const onBlur = () => {
|
||||||
|
payload.updateData(index, id, value);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(initialValue);
|
||||||
|
}, [initialValue]);
|
||||||
|
|
||||||
|
const error = payload.errors?.[index]?.[id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
intent={error ? Intent.DANGER : null}
|
||||||
|
className={classNames(Classes.FILL)}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
|
fill={true}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextAreaEditableCell;
|
||||||
@@ -6,7 +6,9 @@ import ItemsListCell from './ItemsListCell';
|
|||||||
import PercentFieldCell from './PercentFieldCell';
|
import PercentFieldCell from './PercentFieldCell';
|
||||||
import { DivFieldCell, EmptyDiv } from './DivFieldCell';
|
import { DivFieldCell, EmptyDiv } from './DivFieldCell';
|
||||||
import NumericInputCell from './NumericInputCell';
|
import NumericInputCell from './NumericInputCell';
|
||||||
import CheckBoxFieldCell from './CheckBoxFieldCell'
|
import CheckBoxFieldCell from './CheckBoxFieldCell';
|
||||||
|
import SwitchFieldCell from './SwitchFieldCell';
|
||||||
|
import TextAreaCell from './TextAreaCell';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AccountsListFieldCell,
|
AccountsListFieldCell,
|
||||||
@@ -18,5 +20,7 @@ export {
|
|||||||
DivFieldCell,
|
DivFieldCell,
|
||||||
EmptyDiv,
|
EmptyDiv,
|
||||||
NumericInputCell,
|
NumericInputCell,
|
||||||
CheckBoxFieldCell
|
CheckBoxFieldCell,
|
||||||
|
SwitchFieldCell,
|
||||||
|
TextAreaCell,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ function DialogComponent(props) {
|
|||||||
const { name, children, closeDialog, onClose } = props;
|
const { name, children, closeDialog, onClose } = props;
|
||||||
|
|
||||||
const handleClose = (event) => {
|
const handleClose = (event) => {
|
||||||
closeDialog(name)
|
closeDialog(name);
|
||||||
onClose && onClose(event);
|
onClose && onClose(event);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Dialog {...props} onClose={handleClose}>
|
<Dialog {...props} onClose={handleClose}>
|
||||||
{ children }
|
{children}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
const DialogRoot = compose(withDialogActions)(DialogComponent);
|
||||||
withDialogActions,
|
|
||||||
)(DialogComponent);
|
export { DialogRoot as Dialog };
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { Spinner, Classes } from '@blueprintjs/core';
|
import { Spinner, Classes } from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default function DialogContent(props) {
|
export function DialogContent(props) {
|
||||||
const { isLoading, children } = props;
|
const { isLoading, children } = props;
|
||||||
|
|
||||||
const loadingContent = (
|
const loadingContent = (
|
||||||
|
|||||||
26
src/components/Dialog/DialogFooterActions.js
Normal file
26
src/components/Dialog/DialogFooterActions.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
export function DialogFooterActions({ alignment = 'right', children }) {
|
||||||
|
return (
|
||||||
|
<DialogFooterActionsRoot
|
||||||
|
className={Classes.DIALOG_FOOTER_ACTIONS}
|
||||||
|
alignment={alignment}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DialogFooterActionsRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogFooterActionsRoot = styled.div`
|
||||||
|
margin-left: -10px;
|
||||||
|
margin-right: -10px;
|
||||||
|
justify-content: ${(props) =>
|
||||||
|
props.alignment === 'right' ? 'flex-end' : 'flex-start'};
|
||||||
|
|
||||||
|
.bp3-button {
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -5,7 +5,7 @@ function LoadingContent() {
|
|||||||
return (<div className={Classes.DIALOG_BODY}><Spinner size={30} /></div>);
|
return (<div className={Classes.DIALOG_BODY}><Spinner size={30} /></div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DialogSuspense({
|
export function DialogSuspense({
|
||||||
children
|
children
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
6
src/components/Dialog/index.js
Normal file
6
src/components/Dialog/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export * from './Dialog';
|
||||||
|
export * from './DialogFooterActions';
|
||||||
|
export * from './DialogSuspense';
|
||||||
|
export * from './DialogContent';
|
||||||
@@ -19,6 +19,14 @@ import EstimatePdfPreviewDialog from 'containers/Dialogs/EstimatePdfPreviewDialo
|
|||||||
import ReceiptPdfPreviewDialog from '../containers/Dialogs/ReceiptPdfPreviewDialog';
|
import ReceiptPdfPreviewDialog from '../containers/Dialogs/ReceiptPdfPreviewDialog';
|
||||||
import MoneyInDialog from '../containers/Dialogs/MoneyInDialog';
|
import MoneyInDialog from '../containers/Dialogs/MoneyInDialog';
|
||||||
import MoneyOutDialog from '../containers/Dialogs/MoneyOutDialog';
|
import MoneyOutDialog from '../containers/Dialogs/MoneyOutDialog';
|
||||||
|
import BadDebtDialog from '../containers/Dialogs/BadDebtDialog';
|
||||||
|
import NotifyInvoiceViaSMSDialog from '../containers/Dialogs/NotifyInvoiceViaSMSDialog';
|
||||||
|
import NotifyReceiptViaSMSDialog from '../containers/Dialogs/NotifyReceiptViaSMSDialog';
|
||||||
|
import NotifyEstimateViaSMSDialog from '../containers/Dialogs/NotifyEstimateViaSMSDialog';
|
||||||
|
import NotifyPaymentReceiveViaSMSDialog from '../containers/Dialogs/NotifyPaymentReceiveViaSMSDialog';
|
||||||
|
import SMSMessageDialog from '../containers/Dialogs/SMSMessageDialog';
|
||||||
|
import TransactionsLockingDialog from '../containers/Dialogs/TransactionsLockingDialog';
|
||||||
|
import EasySMSIntegrationDialog from '../containers/Dialogs/EasySMSIntegrationDialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialogs container.
|
* Dialogs container.
|
||||||
@@ -44,6 +52,16 @@ export default function DialogsContainer() {
|
|||||||
<ReceiptPdfPreviewDialog dialogName={'receipt-pdf-preview'} />
|
<ReceiptPdfPreviewDialog dialogName={'receipt-pdf-preview'} />
|
||||||
<MoneyInDialog dialogName={'money-in'} />
|
<MoneyInDialog dialogName={'money-in'} />
|
||||||
<MoneyOutDialog dialogName={'money-out'} />
|
<MoneyOutDialog dialogName={'money-out'} />
|
||||||
|
|
||||||
|
<NotifyInvoiceViaSMSDialog dialogName={'notify-invoice-via-sms'} />
|
||||||
|
<NotifyReceiptViaSMSDialog dialogName={'notify-receipt-via-sms'} />
|
||||||
|
<NotifyEstimateViaSMSDialog dialogName={'notify-estimate-via-sms'} />
|
||||||
|
<NotifyPaymentReceiveViaSMSDialog dialogName={'notify-payment-via-sms'} />
|
||||||
|
|
||||||
|
<BadDebtDialog dialogName={'write-off-bad-debt'} />
|
||||||
|
<SMSMessageDialog dialogName={'sms-message-form'} />
|
||||||
|
<TransactionsLockingDialog dialogName={'transactions-locking'} />
|
||||||
|
<EasySMSIntegrationDialog dialogName={'easysms-integrate'} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ListSelect from "./ListSelect";
|
import intl from 'react-intl-universal';
|
||||||
|
import ListSelect from './ListSelect';
|
||||||
|
|
||||||
export default function DisplayNameList({
|
export default function DisplayNameList({
|
||||||
salutation,
|
salutation,
|
||||||
@@ -9,25 +10,32 @@ export default function DisplayNameList({
|
|||||||
...restProps
|
...restProps
|
||||||
}) {
|
}) {
|
||||||
const formats = [
|
const formats = [
|
||||||
{ format: '{1} {2} {3}', values: [salutation, firstName, lastName], required: [1] },
|
{
|
||||||
|
format: '{1} {2} {3}',
|
||||||
|
values: [salutation, firstName, lastName],
|
||||||
|
required: [1],
|
||||||
|
},
|
||||||
{ format: '{1} {2}', values: [firstName, lastName], required: [] },
|
{ format: '{1} {2}', values: [firstName, lastName], required: [] },
|
||||||
{ format: '{1}, {2}', values: [firstName, lastName], required: [1, 2] },
|
{ format: '{1}, {2}', values: [firstName, lastName], required: [1, 2] },
|
||||||
{ format: '{1}', values: [company], required: [1] }
|
{ format: '{1}', values: [company], required: [1] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const formatOptions = formats
|
const formatOptions = formats
|
||||||
.filter((format) => !format.values.some((value, index) => {
|
.filter(
|
||||||
return !value && format.required.indexOf(index + 1) !== -1;
|
(format) =>
|
||||||
}))
|
!format.values.some((value, index) => {
|
||||||
|
return !value && format.required.indexOf(index + 1) !== -1;
|
||||||
|
}),
|
||||||
|
)
|
||||||
.map((formatOption) => {
|
.map((formatOption) => {
|
||||||
const { format, values } = formatOption;
|
const { format, values } = formatOption;
|
||||||
let label = format;
|
let label = format;
|
||||||
|
|
||||||
values.forEach((value, index) => {
|
values.forEach((value, index) => {
|
||||||
const replaceWith = (value || '');
|
const replaceWith = value || '';
|
||||||
label = label.replace(`{${index + 1}}`, replaceWith).trim();
|
label = label.replace(`{${index + 1}}`, replaceWith).trim();
|
||||||
});
|
});
|
||||||
return { label: label.replace(/\s+/g, " ") };
|
return { label: label.replace(/\s+/g, ' ') };
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -35,9 +43,9 @@ export default function DisplayNameList({
|
|||||||
items={formatOptions}
|
items={formatOptions}
|
||||||
selectedItemProp={'label'}
|
selectedItemProp={'label'}
|
||||||
textProp={'label'}
|
textProp={'label'}
|
||||||
defaultText={'Select display name as'}
|
defaultText={intl.get('select_display_name_as')}
|
||||||
filterable={false}
|
filterable={false}
|
||||||
{ ...restProps }
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ import { Position, Drawer } from '@blueprintjs/core';
|
|||||||
|
|
||||||
import 'style/components/Drawer.scss';
|
import 'style/components/Drawer.scss';
|
||||||
|
|
||||||
|
import { DrawerProvider } from './DrawerProvider';
|
||||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ function DrawerComponent(props) {
|
|||||||
portalClassName={'drawer-portal'}
|
portalClassName={'drawer-portal'}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
<DrawerProvider {...props}>{children}</DrawerProvider>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/components/Drawer/DrawerProvider.js
Normal file
16
src/components/Drawer/DrawerProvider.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React, { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
const DrawerContext = createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account form provider.
|
||||||
|
*/
|
||||||
|
function DrawerProvider({ ...props }) {
|
||||||
|
const provider = { ...props };
|
||||||
|
|
||||||
|
return <DrawerContext.Provider value={provider} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useDrawerContext = () => useContext(DrawerContext);
|
||||||
|
|
||||||
|
export { DrawerProvider, useDrawerContext };
|
||||||
@@ -14,6 +14,9 @@ import CustomerDetailsDrawer from '../containers/Drawers/CustomerDetailsDrawer';
|
|||||||
import VendorDetailsDrawer from '../containers/Drawers/VendorDetailsDrawer';
|
import VendorDetailsDrawer from '../containers/Drawers/VendorDetailsDrawer';
|
||||||
import InventoryAdjustmentDetailDrawer from '../containers/Drawers/InventoryAdjustmentDetailDrawer';
|
import InventoryAdjustmentDetailDrawer from '../containers/Drawers/InventoryAdjustmentDetailDrawer';
|
||||||
import CashflowTransactionDetailDrawer from '../containers/Drawers/CashflowTransactionDetailDrawer';
|
import CashflowTransactionDetailDrawer from '../containers/Drawers/CashflowTransactionDetailDrawer';
|
||||||
|
import QuickCreateCustomerDrawer from '../containers/Drawers/QuickCreateCustomerDrawer';
|
||||||
|
import QuickCreateItemDrawer from '../containers/Drawers/QuickCreateItemDrawer';
|
||||||
|
import QuickWriteVendorDrawer from '../containers/Drawers/QuickWriteVendorDrawer';
|
||||||
|
|
||||||
import { DRAWERS } from 'common/drawers';
|
import { DRAWERS } from 'common/drawers';
|
||||||
|
|
||||||
@@ -38,7 +41,12 @@ export default function DrawersContainer() {
|
|||||||
<InventoryAdjustmentDetailDrawer
|
<InventoryAdjustmentDetailDrawer
|
||||||
name={DRAWERS.INVENTORY_ADJUSTMENT_DRAWER}
|
name={DRAWERS.INVENTORY_ADJUSTMENT_DRAWER}
|
||||||
/>
|
/>
|
||||||
<CashflowTransactionDetailDrawer name={DRAWERS.CASHFLOW_TRNASACTION_DRAWER} />
|
<CashflowTransactionDetailDrawer
|
||||||
|
name={DRAWERS.CASHFLOW_TRNASACTION_DRAWER}
|
||||||
|
/>
|
||||||
|
<QuickCreateCustomerDrawer name={DRAWERS.QUICK_CREATE_CUSTOMER} />
|
||||||
|
<QuickCreateItemDrawer name={DRAWERS.QUICK_CREATE_ITEM} />
|
||||||
|
<QuickWriteVendorDrawer name={DRAWERS.QUICK_WRITE_VENDOR} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,68 @@
|
|||||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { MenuItem } from '@blueprintjs/core';
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { CLASSES } from 'common/classes';
|
|
||||||
|
|
||||||
import { Suggest } from '@blueprintjs/select';
|
import { Suggest } from '@blueprintjs/select';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
export default function ItemsSuggestField({
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { DRAWERS } from 'common/drawers';
|
||||||
|
|
||||||
|
// Creates a new item from query.
|
||||||
|
const createNewItemFromQuery = (name) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle quick create new customer.
|
||||||
|
const createNewItemRenderer = (query, active, handleClick) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon="add"
|
||||||
|
text={intl.get('list.create', { value: `"${query}"` })}
|
||||||
|
active={active}
|
||||||
|
shouldDismissPopover={false}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Item renderer.
|
||||||
|
const itemRenderer = (item, { modifiers, handleClick }) => (
|
||||||
|
<MenuItem
|
||||||
|
key={item.id}
|
||||||
|
text={item.name}
|
||||||
|
label={item.code}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Filters items.
|
||||||
|
const filterItemsPredicater = (query, item, _index, exactMatch) => {
|
||||||
|
const normalizedTitle = item.name.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return `${normalizedTitle} ${item.code}`.indexOf(normalizedQuery) >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle input value renderer.
|
||||||
|
const handleInputValueRenderer = (inputValue) => {
|
||||||
|
if (inputValue) {
|
||||||
|
return inputValue.name.toString();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
function ItemsSuggestField({
|
||||||
items,
|
items,
|
||||||
initialItemId,
|
initialItemId,
|
||||||
selectedItemId,
|
selectedItemId,
|
||||||
@@ -18,6 +73,10 @@ export default function ItemsSuggestField({
|
|||||||
sellable = false,
|
sellable = false,
|
||||||
purchasable = false,
|
purchasable = false,
|
||||||
popoverFill = false,
|
popoverFill = false,
|
||||||
|
|
||||||
|
allowCreate = true,
|
||||||
|
|
||||||
|
openDrawer,
|
||||||
...suggestProps
|
...suggestProps
|
||||||
}) {
|
}) {
|
||||||
// Filters items based on filter props.
|
// Filters items based on filter props.
|
||||||
@@ -36,28 +95,23 @@ export default function ItemsSuggestField({
|
|||||||
// Find initial item object.
|
// Find initial item object.
|
||||||
const initialItem = useMemo(
|
const initialItem = useMemo(
|
||||||
() => filteredItems.some((a) => a.id === initialItemId),
|
() => filteredItems.some((a) => a.id === initialItemId),
|
||||||
[initialItemId],
|
[initialItemId, filteredItems],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [selectedItem, setSelectedItem] = useState(initialItem || null);
|
const [selectedItem, setSelectedItem] = useState(initialItem || null);
|
||||||
|
|
||||||
const onItemSelect = useCallback(
|
const onItemSelect = useCallback(
|
||||||
(item) => {
|
(item) => {
|
||||||
setSelectedItem({ ...item });
|
if (item.id) {
|
||||||
onItemSelected && onItemSelected(item);
|
setSelectedItem({ ...item });
|
||||||
|
onItemSelected && onItemSelected(item);
|
||||||
|
} else {
|
||||||
|
openDrawer(DRAWERS.QUICK_CREATE_ITEM);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[setSelectedItem, onItemSelected],
|
[setSelectedItem, onItemSelected, openDrawer],
|
||||||
);
|
);
|
||||||
|
|
||||||
const itemRenderer = useCallback((item, { modifiers, handleClick }) => (
|
|
||||||
<MenuItem
|
|
||||||
key={item.id}
|
|
||||||
text={item.name}
|
|
||||||
label={item.code}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof selectedItemId !== 'undefined') {
|
if (typeof selectedItemId !== 'undefined') {
|
||||||
const item = selectedItemId
|
const item = selectedItemId
|
||||||
@@ -67,27 +121,12 @@ export default function ItemsSuggestField({
|
|||||||
}
|
}
|
||||||
}, [selectedItemId, filteredItems, setSelectedItem]);
|
}, [selectedItemId, filteredItems, setSelectedItem]);
|
||||||
|
|
||||||
const handleInputValueRenderer = (inputValue) => {
|
// Maybe inject create new item props to suggest component.
|
||||||
if (inputValue) {
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
return inputValue.name.toString();
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
}
|
? createNewItemFromQuery
|
||||||
return '';
|
: null;
|
||||||
};
|
|
||||||
|
|
||||||
// Filters items.
|
|
||||||
const filterItemsPredicater = useCallback(
|
|
||||||
(query, item, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = item.name.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
|
||||||
return `${normalizedTitle} ${item.code}`.indexOf(normalizedQuery) >= 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<Suggest
|
<Suggest
|
||||||
items={filteredItems}
|
items={filteredItems}
|
||||||
@@ -104,7 +143,12 @@ export default function ItemsSuggestField({
|
|||||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
})}
|
})}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
|
createNewItemPosition={'top'}
|
||||||
{...suggestProps}
|
{...suggestProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDrawerActions)(ItemsSuggestField);
|
||||||
|
|||||||
36
src/components/MoreMenutItems.js
Normal file
36
src/components/MoreMenutItems.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Popover,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
Position,
|
||||||
|
MenuItem,
|
||||||
|
Menu,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { Icon, FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
function MoreMenuItems({ payload: { onNotifyViaSMS } }) {
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
minimal={true}
|
||||||
|
content={
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
onClick={onNotifyViaSMS}
|
||||||
|
text={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
position={Position.BOTTOM_LEFT}
|
||||||
|
modifiers={{
|
||||||
|
offset: { offset: '0, 4' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MoreMenuItems;
|
||||||
46
src/components/SMSPreview/index.js
Normal file
46
src/components/SMSPreview/index.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { Icon } from 'components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS Message preview.
|
||||||
|
*/
|
||||||
|
export function SMSMessagePreview({
|
||||||
|
message,
|
||||||
|
iconWidth = '265px',
|
||||||
|
iconHeight = '287px',
|
||||||
|
iconColor = '#adadad',
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SMSMessagePreviewBase>
|
||||||
|
<Icon
|
||||||
|
icon={'sms-message-preview'}
|
||||||
|
width={iconWidth}
|
||||||
|
height={iconHeight}
|
||||||
|
color={iconColor}
|
||||||
|
/>
|
||||||
|
<SMSMessageText>{message}</SMSMessageText>
|
||||||
|
</SMSMessagePreviewBase>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SMSMessageText = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
padding: 12px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-right: 12px;
|
||||||
|
word-break: break-word;
|
||||||
|
background: #2fa2e4;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SMSMessagePreviewBase = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 265px;
|
||||||
|
margin: 0 auto;
|
||||||
|
`;
|
||||||
@@ -2,16 +2,19 @@ import React from 'react';
|
|||||||
import SidebarContainer from 'components/Sidebar/SidebarContainer';
|
import SidebarContainer from 'components/Sidebar/SidebarContainer';
|
||||||
import SidebarHead from 'components/Sidebar/SidebarHead';
|
import SidebarHead from 'components/Sidebar/SidebarHead';
|
||||||
import SidebarMenu from 'components/Sidebar/SidebarMenu';
|
import SidebarMenu from 'components/Sidebar/SidebarMenu';
|
||||||
|
import { useGetSidebarMenu } from './utils';
|
||||||
|
|
||||||
import 'style/containers/Dashboard/Sidebar.scss';
|
import 'style/containers/Dashboard/Sidebar.scss';
|
||||||
|
|
||||||
export default function Sidebar({ dashboardContentRef }) {
|
export default function Sidebar({ dashboardContentRef }) {
|
||||||
|
const menu = useGetSidebarMenu();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarContainer>
|
<SidebarContainer>
|
||||||
<SidebarHead />
|
<SidebarHead />
|
||||||
|
|
||||||
<div className="sidebar__menu">
|
<div className="sidebar__menu">
|
||||||
<SidebarMenu />
|
<SidebarMenu menu={menu} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidebar__version">0.0.1-beta version.</div>
|
<div class="sidebar__version">0.0.1-beta version.</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Button, Popover, Menu, Position } from '@blueprintjs/core';
|
|||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import { compose, firstLettersArgs } from 'utils';
|
import { compose, firstLettersArgs } from 'utils';
|
||||||
import withCurrentOrganization from '../../containers/Organization/withCurrentOrganization';
|
import withCurrentOrganization from '../../containers/Organization/withCurrentOrganization';
|
||||||
import { useAuthenticatedUser } from '../Dashboard/AuthenticatedUser';
|
import { useAuthenticatedAccount } from '../../hooks/query';
|
||||||
|
|
||||||
// Popover modifiers.
|
// Popover modifiers.
|
||||||
const POPOVER_MODIFIERS = {
|
const POPOVER_MODIFIERS = {
|
||||||
@@ -18,7 +18,7 @@ function SidebarHead({
|
|||||||
organization,
|
organization,
|
||||||
}) {
|
}) {
|
||||||
// Retrieve authenticated user information.
|
// Retrieve authenticated user information.
|
||||||
const { user } = useAuthenticatedUser();
|
const { data: user } = useAuthenticatedAccount();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sidebar__head">
|
<div className="sidebar__head">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Menu, MenuDivider } from '@blueprintjs/core';
|
import { Menu, MenuDivider } from '@blueprintjs/core';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import sidebarMenuList from 'config/sidebarMenu';
|
|
||||||
import { Choose } from 'components';
|
import { Choose } from 'components';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import MenuItem from 'components/MenuItem';
|
import MenuItem from 'components/MenuItem';
|
||||||
@@ -24,7 +24,7 @@ function SidebarMenuItemSpace({ space }) {
|
|||||||
return <div class="bp3-menu-spacer" style={{ height: `${space}px` }} />;
|
return <div class="bp3-menu-spacer" style={{ height: `${space}px` }} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenu({ isSubscriptionActive }) {
|
function SidebarMenu({ menu, isSubscriptionActive }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ function SidebarMenu({ isSubscriptionActive }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterItems = sidebarMenuList.filter(
|
const filterItems = menu.filter(
|
||||||
(item) => isSubscriptionActive || item.enableBilling,
|
(item) => isSubscriptionActive || item.enableBilling,
|
||||||
);
|
);
|
||||||
const items = menuItemsMapper(filterItems);
|
const items = menuItemsMapper(filterItems);
|
||||||
|
|||||||
48
src/components/Sidebar/utils.js
Normal file
48
src/components/Sidebar/utils.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import sidebarMenuList from 'config/sidebarMenu';
|
||||||
|
import { isArray, isEmpty } from 'lodash';
|
||||||
|
import { useAbilityContext } from 'hooks/utils';
|
||||||
|
|
||||||
|
export function useGetSidebarMenu() {
|
||||||
|
const ability = useAbilityContext();
|
||||||
|
|
||||||
|
return sidebarMenuList
|
||||||
|
.map((item) => {
|
||||||
|
const children = isArray(item.children)
|
||||||
|
? item.children.filter((childItem) => {
|
||||||
|
return isArray(childItem.permission)
|
||||||
|
? childItem.permission.some((perm) =>
|
||||||
|
ability.can(perm.ability, perm.subject),
|
||||||
|
)
|
||||||
|
: childItem?.permission?.ability && childItem?.permission?.subject
|
||||||
|
? ability.can(
|
||||||
|
childItem.permission.ability,
|
||||||
|
childItem.permission.subject,
|
||||||
|
)
|
||||||
|
: true;
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
...(isArray(item.children)
|
||||||
|
? {
|
||||||
|
children,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item) => {
|
||||||
|
return isArray(item.permission)
|
||||||
|
? item.permission.some((per) =>
|
||||||
|
ability.can(per.ability, per.subject),
|
||||||
|
)
|
||||||
|
: item?.permission?.ability && item?.permission?.subject
|
||||||
|
? ability.can(item.permission.ability, item.permission.subject)
|
||||||
|
: true;
|
||||||
|
})
|
||||||
|
.filter((item) =>
|
||||||
|
isEmpty(item.children) && !item.href && !item.label && !item.divider
|
||||||
|
? false
|
||||||
|
: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import intl from 'react-intl-universal';
|
|||||||
export function FormatDate({ value, format = 'YYYY MMM DD' }) {
|
export function FormatDate({ value, format = 'YYYY MMM DD' }) {
|
||||||
const localizedFormat = intl.get(`date_formats.${format}`);
|
const localizedFormat = intl.get(`date_formats.${format}`);
|
||||||
|
|
||||||
return moment().format(localizedFormat);
|
return moment(value).format(localizedFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ import AccountsSelectList from './AccountsSelectList';
|
|||||||
import AccountsTypesSelect from './AccountsTypesSelect';
|
import AccountsTypesSelect from './AccountsTypesSelect';
|
||||||
import LoadingIndicator from './LoadingIndicator';
|
import LoadingIndicator from './LoadingIndicator';
|
||||||
import DashboardActionViewsList from './Dashboard/DashboardActionViewsList';
|
import DashboardActionViewsList from './Dashboard/DashboardActionViewsList';
|
||||||
import Dialog from './Dialog/Dialog';
|
|
||||||
import DialogContent from './Dialog/DialogContent';
|
|
||||||
import DialogSuspense from './Dialog/DialogSuspense';
|
|
||||||
import InputPrependButton from './Forms/InputPrependButton';
|
import InputPrependButton from './Forms/InputPrependButton';
|
||||||
import CategoriesSelectList from './CategoriesSelectList';
|
import CategoriesSelectList from './CategoriesSelectList';
|
||||||
import Row from './Grid/Row';
|
import Row from './Grid/Row';
|
||||||
@@ -61,7 +58,9 @@ import Card from './Card';
|
|||||||
import AvaterCell from './AvaterCell';
|
import AvaterCell from './AvaterCell';
|
||||||
|
|
||||||
import { ItemsMultiSelect } from './Items';
|
import { ItemsMultiSelect } from './Items';
|
||||||
|
import MoreMenuItems from './MoreMenutItems';
|
||||||
|
|
||||||
|
export * from './Dialog';
|
||||||
export * from './Menu';
|
export * from './Menu';
|
||||||
export * from './AdvancedFilter/AdvancedFilterDropdown';
|
export * from './AdvancedFilter/AdvancedFilterDropdown';
|
||||||
export * from './AdvancedFilter/AdvancedFilterPopover';
|
export * from './AdvancedFilter/AdvancedFilterPopover';
|
||||||
@@ -82,9 +81,12 @@ export * from './MultiSelectTaggable';
|
|||||||
export * from './Utils/FormatNumber';
|
export * from './Utils/FormatNumber';
|
||||||
export * from './Utils/FormatDate';
|
export * from './Utils/FormatDate';
|
||||||
export * from './BankAccounts';
|
export * from './BankAccounts';
|
||||||
export * from './IntersectionObserver'
|
export * from './IntersectionObserver';
|
||||||
export * from './Datatable/CellForceWidth';
|
export * from './Datatable/CellForceWidth';
|
||||||
export * from './Button';
|
export * from './Button';
|
||||||
|
export * from './IntersectionObserver';
|
||||||
|
export * from './SMSPreview';
|
||||||
|
export * from './Contacts';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
@@ -118,9 +120,6 @@ export {
|
|||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
DashboardActionViewsList,
|
DashboardActionViewsList,
|
||||||
AppToaster,
|
AppToaster,
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogSuspense,
|
|
||||||
InputPrependButton,
|
InputPrependButton,
|
||||||
CategoriesSelectList,
|
CategoriesSelectList,
|
||||||
Col,
|
Col,
|
||||||
@@ -156,4 +155,5 @@ export {
|
|||||||
ItemsMultiSelect,
|
ItemsMultiSelect,
|
||||||
Card,
|
Card,
|
||||||
AvaterCell,
|
AvaterCell,
|
||||||
|
MoreMenuItems,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import { ReportsAction, AbilitySubject } from '../common/abilityOption';
|
||||||
|
|
||||||
export const financialReportMenus = [
|
export const financialReportMenus = [
|
||||||
{
|
{
|
||||||
@@ -11,6 +12,8 @@ export const financialReportMenus = [
|
|||||||
<T id={'reports_a_company_s_assets_liabilities_and_shareholders'} />
|
<T id={'reports_a_company_s_assets_liabilities_and_shareholders'} />
|
||||||
),
|
),
|
||||||
link: '/financial-reports/balance-sheet',
|
link: '/financial-reports/balance-sheet',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_BALANCE_SHEET,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'trial_balance_sheet'} />,
|
title: <T id={'trial_balance_sheet'} />,
|
||||||
@@ -18,11 +21,15 @@ export const financialReportMenus = [
|
|||||||
<T id={'summarizes_the_credit_and_debit_balance_of_each_account'} />
|
<T id={'summarizes_the_credit_and_debit_balance_of_each_account'} />
|
||||||
),
|
),
|
||||||
link: '/financial-reports/trial-balance-sheet',
|
link: '/financial-reports/trial-balance-sheet',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_TRIAL_BALANCE_SHEET,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'profit_loss_report'} />,
|
title: <T id={'profit_loss_report'} />,
|
||||||
desc: <T id={'reports_the_revenues_costs_and_expenses'} />,
|
desc: <T id={'reports_the_revenues_costs_and_expenses'} />,
|
||||||
link: '/financial-reports/profit-loss-sheet',
|
link: '/financial-reports/profit-loss-sheet',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_PROFIT_LOSS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'cash_flow_statement'} />,
|
title: <T id={'cash_flow_statement'} />,
|
||||||
@@ -30,16 +37,22 @@ export const financialReportMenus = [
|
|||||||
<T id={'reports_inflow_and_outflow_of_cash_and_cash_equivalents'} />
|
<T id={'reports_inflow_and_outflow_of_cash_and_cash_equivalents'} />
|
||||||
),
|
),
|
||||||
link: '/financial-reports/cash-flow',
|
link: '/financial-reports/cash-flow',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_CASHFLOW,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'journal_report'} />,
|
title: <T id={'journal_report'} />,
|
||||||
desc: <T id={'the_debit_and_credit_entries_of_system_transactions'} />,
|
desc: <T id={'the_debit_and_credit_entries_of_system_transactions'} />,
|
||||||
link: '/financial-reports/journal-sheet',
|
link: '/financial-reports/journal-sheet',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_JOURNAL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'general_ledger_report'} />,
|
title: <T id={'general_ledger_report'} />,
|
||||||
desc: <T id={'reports_every_transaction_going_in_and_out_of_your'} />,
|
desc: <T id={'reports_every_transaction_going_in_and_out_of_your'} />,
|
||||||
link: '/financial-reports/general-ledger',
|
link: '/financial-reports/general-ledger',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_GENERAL_LEDGET,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'receivable_aging_summary'} />,
|
title: <T id={'receivable_aging_summary'} />,
|
||||||
@@ -47,11 +60,15 @@ export const financialReportMenus = [
|
|||||||
<T id={'summarize_total_unpaid_balances_of_customers_invoices'} />
|
<T id={'summarize_total_unpaid_balances_of_customers_invoices'} />
|
||||||
),
|
),
|
||||||
link: '/financial-reports/receivable-aging-summary',
|
link: '/financial-reports/receivable-aging-summary',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_AR_AGING_SUMMARY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'payable_aging_summary'} />,
|
title: <T id={'payable_aging_summary'} />,
|
||||||
desc: <T id={'summarize_total_unpaid_balances_of_vendors_purchase'} />,
|
desc: <T id={'summarize_total_unpaid_balances_of_vendors_purchase'} />,
|
||||||
link: '/financial-reports/payable-aging-summary',
|
link: '/financial-reports/payable-aging-summary',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_AP_AGING_SUMMARY,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -71,6 +88,8 @@ export const SalesAndPurchasesReportMenus = [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
link: '/financial-reports/purchases-by-items',
|
link: '/financial-reports/purchases-by-items',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_PURCHASES_BY_ITEMS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'sales_by_items'} />,
|
title: <T id={'sales_by_items'} />,
|
||||||
@@ -82,6 +101,8 @@ export const SalesAndPurchasesReportMenus = [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
link: '/financial-reports/sales-by-items',
|
link: '/financial-reports/sales-by-items',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_SALES_BY_ITEMS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'inventory_valuation'} />,
|
title: <T id={'inventory_valuation'} />,
|
||||||
@@ -93,6 +114,8 @@ export const SalesAndPurchasesReportMenus = [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
link: '/financial-reports/inventory-valuation',
|
link: '/financial-reports/inventory-valuation',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_INVENTORY_VALUATION_SUMMARY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'customers_balance_summary'} />,
|
title: <T id={'customers_balance_summary'} />,
|
||||||
@@ -104,6 +127,8 @@ export const SalesAndPurchasesReportMenus = [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
link: '/financial-reports/customers-balance-summary',
|
link: '/financial-reports/customers-balance-summary',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_CUSTOMERS_SUMMARY_BALANCE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'vendors_balance_summary'} />,
|
title: <T id={'vendors_balance_summary'} />,
|
||||||
@@ -111,6 +136,8 @@ export const SalesAndPurchasesReportMenus = [
|
|||||||
<T id={'summerize_the_total_amount_your_business_owes_each_vendor'} />
|
<T id={'summerize_the_total_amount_your_business_owes_each_vendor'} />
|
||||||
),
|
),
|
||||||
link: '/financial-reports/vendors-balance-summary',
|
link: '/financial-reports/vendors-balance-summary',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_VENDORS_SUMMARY_BALANCE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'customers_transactions'} />,
|
title: <T id={'customers_transactions'} />,
|
||||||
@@ -120,6 +147,8 @@ export const SalesAndPurchasesReportMenus = [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
link: '/financial-reports/transactions-by-customers',
|
link: '/financial-reports/transactions-by-customers',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_CUSTOMERS_TRANSACTIONS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'vendors_transactions'} />,
|
title: <T id={'vendors_transactions'} />,
|
||||||
@@ -131,6 +160,8 @@ export const SalesAndPurchasesReportMenus = [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
link: '/financial-reports/transactions-by-vendors',
|
link: '/financial-reports/transactions-by-vendors',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_VENDORS_TRANSACTIONS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <T id={'inventory_item_details'} />,
|
title: <T id={'inventory_item_details'} />,
|
||||||
@@ -138,6 +169,8 @@ export const SalesAndPurchasesReportMenus = [
|
|||||||
<T id={'reports_every_transaction_going_in_and_out_of_your_items'} />
|
<T id={'reports_every_transaction_going_in_and_out_of_your_items'} />
|
||||||
),
|
),
|
||||||
link: '/financial-reports/inventory-item-details',
|
link: '/financial-reports/inventory-item-details',
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_INVENTORY_ITEM_DETAILS,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,29 +1,34 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
text: <T id={'general'}/>,
|
text: <T id={'general'} />,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/general',
|
href: '/preferences/general',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'users'}/>,
|
text: <T id={'users'} />,
|
||||||
href: '/preferences/users',
|
href: '/preferences/users',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'currencies'}/>,
|
text: <T id={'currencies'} />,
|
||||||
|
|
||||||
href: '/preferences/currencies',
|
href: '/preferences/currencies',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'accountant'}/>,
|
text: <T id={'accountant'} />,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/accountant',
|
href: '/preferences/accountant',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'items'}/>,
|
text: <T id={'items'} />,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/items',
|
href: '/preferences/items',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: <T id={'sms_integration.label'} />,
|
||||||
|
disabled: false,
|
||||||
|
href: '/preferences/sms-message',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import {
|
||||||
|
ReportsAction,
|
||||||
|
AbilitySubject,
|
||||||
|
ItemAction,
|
||||||
|
InventoryAdjustmentAction,
|
||||||
|
SaleEstimateAction,
|
||||||
|
SaleInvoiceAction,
|
||||||
|
SaleReceiptAction,
|
||||||
|
PaymentReceiveAction,
|
||||||
|
BillAction,
|
||||||
|
PaymentMadeAction,
|
||||||
|
CustomerAction,
|
||||||
|
VendorAction,
|
||||||
|
AccountAction,
|
||||||
|
ManualJournalAction,
|
||||||
|
ExpenseAction,
|
||||||
|
CashflowAction,
|
||||||
|
PreferencesAbility,
|
||||||
|
ExchangeRateAbility,
|
||||||
|
SubscriptionBillingAbility,
|
||||||
|
} from '../common/abilityOption';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
@@ -11,6 +32,32 @@ export default [
|
|||||||
{
|
{
|
||||||
text: <T id={'sales_inventory'} />,
|
text: <T id={'sales_inventory'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
ability: ItemAction.View,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.InventoryAdjustment,
|
||||||
|
ability: InventoryAdjustmentAction.View,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Estimate,
|
||||||
|
ability: SaleEstimateAction.View,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Invoice,
|
||||||
|
ability: SaleInvoiceAction.View,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Receipt,
|
||||||
|
ability: SaleReceiptAction.View,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.PaymentReceive,
|
||||||
|
ability: PaymentReceiveAction.View,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'items'} />,
|
text: <T id={'items'} />,
|
||||||
@@ -18,37 +65,70 @@ export default [
|
|||||||
{
|
{
|
||||||
text: <T id={'items'} />,
|
text: <T id={'items'} />,
|
||||||
href: '/items',
|
href: '/items',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
ability: ItemAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'inventory_adjustments'} />,
|
text: <T id={'inventory_adjustments'} />,
|
||||||
href: '/inventory-adjustments',
|
href: '/inventory-adjustments',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.InventoryAdjustment,
|
||||||
|
ability: InventoryAdjustmentAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'category_list'} />,
|
text: <T id={'category_list'} />,
|
||||||
href: '/items/categories',
|
href: '/items/categories',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
ability: ItemAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New tasks'} />,
|
text: <T id={'New tasks'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
ability: ItemAction.Create,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
ability: ItemAction.Create,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New inventory item'} />,
|
text: <T id={'New inventory item'} />,
|
||||||
href: '/items/new',
|
href: '/items/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
ability: ItemAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New service'} />,
|
text: <T id={'New service'} />,
|
||||||
href: '/items/new',
|
href: '/items/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
ability: ItemAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New item category'} />,
|
text: <T id={'New item category'} />,
|
||||||
href: '/items/categories/new',
|
href: '/items/categories/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Item,
|
||||||
|
ability: ItemAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// text: <T id={'New inventory adjustment'} />,
|
|
||||||
// },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -57,43 +137,109 @@ export default [
|
|||||||
{
|
{
|
||||||
text: <T id={'estimates'} />,
|
text: <T id={'estimates'} />,
|
||||||
href: '/estimates',
|
href: '/estimates',
|
||||||
newTabHref: '/estimates/new',
|
permission: {
|
||||||
|
subject: AbilitySubject.Estimate,
|
||||||
|
ability: SaleEstimateAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'invoices'} />,
|
text: <T id={'invoices'} />,
|
||||||
href: '/invoices',
|
href: '/invoices',
|
||||||
newTabHref: '/invoices/new',
|
permission: {
|
||||||
|
subject: AbilitySubject.Invoice,
|
||||||
|
ability: SaleInvoiceAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'receipts'} />,
|
text: <T id={'receipts'} />,
|
||||||
href: '/receipts',
|
href: '/receipts',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Receipt,
|
||||||
|
ability: SaleReceiptAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'payment_receives'} />,
|
text: <T id={'payment_receives'} />,
|
||||||
href: '/payment-receives',
|
href: '/payment-receives',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.PaymentReceive,
|
||||||
|
ability: PaymentReceiveAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New tasks'} />,
|
text: <T id={'New tasks'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Estimate,
|
||||||
|
ability: SaleEstimateAction.Create,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Invoice,
|
||||||
|
ability: SaleInvoiceAction.Create,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Receipt,
|
||||||
|
ability: SaleReceiptAction.Create,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.PaymentReceive,
|
||||||
|
ability: PaymentReceiveAction.Create,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Estimate,
|
||||||
|
ability: SaleEstimateAction.Create,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Invoice,
|
||||||
|
ability: SaleInvoiceAction.Create,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Receipt,
|
||||||
|
ability: SaleReceiptAction.Create,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.PaymentReceive,
|
||||||
|
ability: PaymentReceiveAction.Create,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'new_estimate'} />,
|
text: <T id={'new_estimate'} />,
|
||||||
href: '/estimates/new',
|
href: '/estimates/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Estimate,
|
||||||
|
ability: SaleEstimateAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'new_invoice'} />,
|
text: <T id={'new_invoice'} />,
|
||||||
href: '/invoices/new',
|
href: '/invoices/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Invoice,
|
||||||
|
ability: SaleInvoiceAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'new_receipt'} />,
|
text: <T id={'new_receipt'} />,
|
||||||
href: '/receipts/new',
|
href: '/receipts/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Receipt,
|
||||||
|
ability: SaleReceiptAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'new_payment_receive'} />,
|
text: <T id={'new_payment_receive'} />,
|
||||||
href: '/payment-receives/new',
|
href: '/payment-receives/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.PaymentReceive,
|
||||||
|
ability: PaymentReceiveAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -103,27 +249,62 @@ export default [
|
|||||||
{
|
{
|
||||||
text: <T id={'bills'} />,
|
text: <T id={'bills'} />,
|
||||||
href: '/bills',
|
href: '/bills',
|
||||||
newTabHref: '/bills/new',
|
permission: {
|
||||||
|
subject: AbilitySubject.Bill,
|
||||||
|
ability: BillAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'payment_mades'} />,
|
text: <T id={'payment_mades'} />,
|
||||||
href: '/payment-mades',
|
href: '/payment-mades',
|
||||||
newTabHref: '/payment-mades/new',
|
newTabHref: '/payment-mades/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.PaymentMade,
|
||||||
|
ability: PaymentMadeAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New tasks'} />,
|
text: <T id={'New tasks'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Bill,
|
||||||
|
ability: BillAction.Create,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.PaymentMade,
|
||||||
|
ability: PaymentMadeAction.Create,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Bill,
|
||||||
|
ability: BillAction.Create,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.PaymentMade,
|
||||||
|
ability: PaymentMadeAction.Create,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New purchase invoice'} />,
|
text: <T id={'New purchase invoice'} />,
|
||||||
href: '/bills/new',
|
href: '/bills/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Bill,
|
||||||
|
ability: BillAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'new_payment_made'} />,
|
text: <T id={'new_payment_made'} />,
|
||||||
href: '/payment-mades/new',
|
href: '/payment-mades/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.PaymentMade,
|
||||||
|
ability: PaymentMadeAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -133,33 +314,77 @@ export default [
|
|||||||
{
|
{
|
||||||
text: <T id={'customers'} />,
|
text: <T id={'customers'} />,
|
||||||
href: '/customers',
|
href: '/customers',
|
||||||
newTabHref: '/customers/new',
|
permission: {
|
||||||
|
subject: AbilitySubject.Customer,
|
||||||
|
ability: CustomerAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'vendors'} />,
|
text: <T id={'vendors'} />,
|
||||||
href: '/vendors',
|
href: '/vendors',
|
||||||
newTabHref: '/vendors/new',
|
permission: {
|
||||||
|
subject: AbilitySubject.Vendor,
|
||||||
|
ability: VendorAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New tasks'} />,
|
text: <T id={'New tasks'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Customer,
|
||||||
|
ability: CustomerAction.View,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Vendor,
|
||||||
|
ability: VendorAction.View,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Customer,
|
||||||
|
ability: CustomerAction.View,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Vendor,
|
||||||
|
ability: VendorAction.View,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'new_customer'} />,
|
text: <T id={'new_customer'} />,
|
||||||
href: '/customers/new',
|
href: '/customers/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Customer,
|
||||||
|
ability: CustomerAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'new_vendor'} />,
|
text: <T id={'new_vendor'} />,
|
||||||
href: '/vendors/new',
|
href: '/vendors/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Vendor,
|
||||||
|
ability: VendorAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'accounting'} />,
|
text: <T id={'accounting'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Account,
|
||||||
|
ability: AccountAction.View,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.ManualJournal,
|
||||||
|
ability: ManualJournalAction.View,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'financial'} />,
|
text: <T id={'financial'} />,
|
||||||
@@ -167,25 +392,57 @@ export default [
|
|||||||
{
|
{
|
||||||
text: <T id={'accounts_chart'} />,
|
text: <T id={'accounts_chart'} />,
|
||||||
href: '/accounts',
|
href: '/accounts',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Account,
|
||||||
|
ability: AccountAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'manual_journals'} />,
|
text: <T id={'manual_journals'} />,
|
||||||
href: '/manual-journals',
|
href: '/manual-journals',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.ManualJournal,
|
||||||
|
ability: ManualJournalAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// text: <T id={'sidebar.transactions_locaking'} />,
|
||||||
|
// href: '/transactions-locking',
|
||||||
|
// permission: {
|
||||||
|
// subject: AbilitySubject.ManualJournal,
|
||||||
|
// ability: ManualJournalAction.TransactionLocking,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
text: <T id={'exchange_rate'} />,
|
text: <T id={'exchange_rate'} />,
|
||||||
href: '/exchange-rates',
|
href: '/exchange-rates',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.ExchangeRate,
|
||||||
|
ability: ExchangeRateAbility.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New tasks'} />,
|
text: <T id={'New tasks'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.ManualJournal,
|
||||||
|
ability: ManualJournalAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.ManualJournal,
|
||||||
|
ability: ManualJournalAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'make_journal_entry'} />,
|
text: <T id={'make_journal_entry'} />,
|
||||||
href: '/make-journal-entry',
|
href: '/make-journal-entry',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.ManualJournal,
|
||||||
|
ability: ManualJournalAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -195,29 +452,61 @@ export default [
|
|||||||
{
|
{
|
||||||
text: <T id={'siebar.cashflow.label_cash_and_bank_accounts'} />,
|
text: <T id={'siebar.cashflow.label_cash_and_bank_accounts'} />,
|
||||||
href: '/cashflow-accounts',
|
href: '/cashflow-accounts',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Cashflow,
|
||||||
|
ability: CashflowAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New tasks'} />,
|
text: <T id={'New tasks'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Cashflow,
|
||||||
|
ability: CashflowAction.Create,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Cashflow,
|
||||||
|
ability: CashflowAction.Create,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'cash_flow.label.add_money_in'} />,
|
text: <T id={'cash_flow.label.add_money_in'} />,
|
||||||
href: '/',
|
href: '/cashflow-accounts',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Cashflow,
|
||||||
|
ability: CashflowAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'cash_flow.label.add_money_out'} />,
|
text: <T id={'cash_flow.label.add_money_out'} />,
|
||||||
href: '/',
|
href: '/cashflow-accounts',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Cashflow,
|
||||||
|
ability: CashflowAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'cash_flow.label.add_cash_account'} />,
|
text: <T id={'cash_flow.label.add_cash_account'} />,
|
||||||
href: '/',
|
href: '/cashflow-accounts',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Cashflow,
|
||||||
|
ability: CashflowAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'cash_flow.label.add_bank_account'} />,
|
text: <T id={'cash_flow.label.add_bank_account'} />,
|
||||||
href: '/',
|
href: '/cashflow-accounts',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Cashflow,
|
||||||
|
ability: CashflowAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -227,17 +516,33 @@ export default [
|
|||||||
{
|
{
|
||||||
text: <T id={'expenses'} />,
|
text: <T id={'expenses'} />,
|
||||||
href: '/expenses',
|
href: '/expenses',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Expense,
|
||||||
|
ability: ExpenseAction.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'New tasks'} />,
|
text: <T id={'New tasks'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Expense,
|
||||||
|
ability: ExpenseAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Expense,
|
||||||
|
ability: ExpenseAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'new_expense'} />,
|
text: <T id={'new_expense'} />,
|
||||||
href: '/expenses/new',
|
href: '/expenses/new',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Expense,
|
||||||
|
ability: ExpenseAction.Create,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -247,80 +552,216 @@ export default [
|
|||||||
{
|
{
|
||||||
text: <T id={'balance_sheet'} />,
|
text: <T id={'balance_sheet'} />,
|
||||||
href: '/financial-reports/balance-sheet',
|
href: '/financial-reports/balance-sheet',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_BALANCE_SHEET,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'trial_balance_sheet'} />,
|
text: <T id={'trial_balance_sheet'} />,
|
||||||
href: '/financial-reports/trial-balance-sheet',
|
href: '/financial-reports/trial-balance-sheet',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_TRIAL_BALANCE_SHEET,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'journal'} />,
|
text: <T id={'journal'} />,
|
||||||
href: '/financial-reports/journal-sheet',
|
href: '/financial-reports/journal-sheet',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_JOURNAL,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'general_ledger'} />,
|
text: <T id={'general_ledger'} />,
|
||||||
href: '/financial-reports/general-ledger',
|
href: '/financial-reports/general-ledger',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_GENERAL_LEDGET,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'profit_loss_sheet'} />,
|
text: <T id={'profit_loss_sheet'} />,
|
||||||
href: '/financial-reports/profit-loss-sheet',
|
href: '/financial-reports/profit-loss-sheet',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_PROFIT_LOSS,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'cash_flow_statement'} />,
|
text: <T id={'cash_flow_statement'} />,
|
||||||
href: '/financial-reports/cash-flow',
|
href: '/financial-reports/cash-flow',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_CASHFLOW_ACCOUNT_TRANSACTION,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'AR_Aging_Summary'} />,
|
text: <T id={'AR_Aging_Summary'} />,
|
||||||
href: '/financial-reports/receivable-aging-summary',
|
href: '/financial-reports/receivable-aging-summary',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_AR_AGING_SUMMARY,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'AP_Aging_Summary'} />,
|
text: <T id={'AP_Aging_Summary'} />,
|
||||||
href: '/financial-reports/payable-aging-summary',
|
href: '/financial-reports/payable-aging-summary',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_AP_AGING_SUMMARY,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'Sales/Purchases'} />,
|
text: <T id={'Sales/Purchases'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_PURCHASES_BY_ITEMS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_SALES_BY_ITEMS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_CUSTOMERS_TRANSACTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_VENDORS_TRANSACTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_CUSTOMERS_SUMMARY_BALANCE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_VENDORS_SUMMARY_BALANCE,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_PURCHASES_BY_ITEMS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_SALES_BY_ITEMS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_CUSTOMERS_TRANSACTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_VENDORS_TRANSACTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_CUSTOMERS_SUMMARY_BALANCE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_VENDORS_SUMMARY_BALANCE,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'purchases_by_items'} />,
|
text: <T id={'purchases_by_items'} />,
|
||||||
href: '/financial-reports/purchases-by-items',
|
href: '/financial-reports/purchases-by-items',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_PURCHASES_BY_ITEMS,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'sales_by_items'} />,
|
text: <T id={'sales_by_items'} />,
|
||||||
href: '/financial-reports/sales-by-items',
|
href: '/financial-reports/sales-by-items',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_SALES_BY_ITEMS,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'customers_transactions'} />,
|
text: <T id={'customers_transactions'} />,
|
||||||
href: '/financial-reports/transactions-by-customers',
|
href: '/financial-reports/transactions-by-customers',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_CUSTOMERS_TRANSACTIONS,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'vendors_transactions'} />,
|
text: <T id={'vendors_transactions'} />,
|
||||||
href: '/financial-reports/transactions-by-vendors',
|
href: '/financial-reports/transactions-by-vendors',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_VENDORS_TRANSACTIONS,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'customers_balance_summary'} />,
|
text: <T id={'customers_balance_summary'} />,
|
||||||
href: '/financial-reports/customers-balance-summary',
|
href: '/financial-reports/customers-balance-summary',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_CUSTOMERS_SUMMARY_BALANCE,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'vendors_balance_summary'} />,
|
text: <T id={'vendors_balance_summary'} />,
|
||||||
href: '/financial-reports/vendors-balance-summary',
|
href: '/financial-reports/vendors-balance-summary',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_VENDORS_SUMMARY_BALANCE,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'inventory'} />,
|
text: <T id={'inventory'} />,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_INVENTORY_ITEM_DETAILS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_INVENTORY_VALUATION_SUMMARY,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_INVENTORY_ITEM_DETAILS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_INVENTORY_VALUATION_SUMMARY,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'inventory_item_details'} />,
|
text: <T id={'inventory_item_details'} />,
|
||||||
href: '/financial-reports/inventory-item-details',
|
href: '/financial-reports/inventory-item-details',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_INVENTORY_ITEM_DETAILS,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'inventory_valuation'} />,
|
text: <T id={'inventory_valuation'} />,
|
||||||
href: '/financial-reports/inventory-valuation',
|
href: '/financial-reports/inventory-valuation',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Report,
|
||||||
|
ability: ReportsAction.READ_INVENTORY_VALUATION_SUMMARY,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -328,14 +769,32 @@ export default [
|
|||||||
text: <T id={'system'} />,
|
text: <T id={'system'} />,
|
||||||
enableBilling: true,
|
enableBilling: true,
|
||||||
label: true,
|
label: true,
|
||||||
|
permission: [
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Preferences,
|
||||||
|
ability: PreferencesAbility.Mutate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.SubscriptionBilling,
|
||||||
|
ability: SubscriptionBillingAbility.View,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'preferences'} />,
|
text: <T id={'preferences'} />,
|
||||||
href: '/preferences',
|
href: '/preferences',
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.Preferences,
|
||||||
|
ability: PreferencesAbility.Mutate,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'billing'} />,
|
text: <T id={'billing'} />,
|
||||||
href: '/billing',
|
href: '/billing',
|
||||||
enableBilling: true,
|
enableBilling: true,
|
||||||
|
permission: {
|
||||||
|
subject: AbilitySubject.SubscriptionBilling,
|
||||||
|
ability: SubscriptionBillingAbility.View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ import withManualJournals from './withManualJournals';
|
|||||||
import withSettingsActions from '../../Settings/withSettingsActions';
|
import withSettingsActions from '../../Settings/withSettingsActions';
|
||||||
import withSettings from '../../Settings/withSettings';
|
import withSettings from '../../Settings/withSettings';
|
||||||
|
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
import { Can, If, DashboardActionViewsList } from 'components';
|
||||||
|
import {
|
||||||
|
ManualJournalAction,
|
||||||
|
AbilitySubject,
|
||||||
|
} from '../../../common/abilityOption';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,13 +89,14 @@ function ManualJournalActionsBar({
|
|||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
<Can I={ManualJournalAction.Create} a={AbilitySubject.ManualJournal}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon="plus" />}
|
icon={<Icon icon="plus" />}
|
||||||
text={<T id={'journal_entry'} />}
|
text={<T id={'journal_entry'} />}
|
||||||
onClick={onClickNewManualJournal}
|
onClick={onClickNewManualJournal}
|
||||||
/>
|
/>
|
||||||
|
</Can>
|
||||||
<AdvancedFilterPopover
|
<AdvancedFilterPopover
|
||||||
advancedFilterProps={{
|
advancedFilterProps={{
|
||||||
conditions: manualJournalsFilterConditions,
|
conditions: manualJournalsFilterConditions,
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import React from 'react';
|
|||||||
import { Button, Intent } from '@blueprintjs/core';
|
import { Button, Intent } from '@blueprintjs/core';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { EmptyStatus } from 'components';
|
import { EmptyStatus } from 'components';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { Can, FormattedMessage as T } from 'components';
|
||||||
|
import {
|
||||||
|
AbilitySubject,
|
||||||
|
ManualJournalAction,
|
||||||
|
} from '../../../common/abilityOption';
|
||||||
|
|
||||||
export default function ManualJournalsEmptyStatus() {
|
export default function ManualJournalsEmptyStatus() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -17,19 +21,21 @@ export default function ManualJournalsEmptyStatus() {
|
|||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<>
|
<>
|
||||||
<Button
|
<Can I={ManualJournalAction.Create} a={AbilitySubject.ManualJournal}>
|
||||||
intent={Intent.PRIMARY}
|
<Button
|
||||||
large={true}
|
intent={Intent.PRIMARY}
|
||||||
onClick={() => {
|
large={true}
|
||||||
history.push('/make-journal-entry');
|
onClick={() => {
|
||||||
}}
|
history.push('/make-journal-entry');
|
||||||
>
|
}}
|
||||||
<T id={'make_journal'} />
|
>
|
||||||
</Button>
|
<T id={'make_journal'} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button intent={Intent.NONE} large={true}>
|
<Button intent={Intent.NONE} large={true}>
|
||||||
<T id={'learn_more'} />
|
<T id={'learn_more'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Can>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,7 +13,18 @@ import {
|
|||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
import { FormattedMessage as T, Choose, Money, If, Icon } from 'components';
|
import {
|
||||||
|
Can,
|
||||||
|
FormattedMessage as T,
|
||||||
|
Choose,
|
||||||
|
Money,
|
||||||
|
If,
|
||||||
|
Icon,
|
||||||
|
} from 'components';
|
||||||
|
import {
|
||||||
|
ManualJournalAction,
|
||||||
|
AbilitySubject,
|
||||||
|
} from '../../../common/abilityOption';
|
||||||
import { safeCallback } from 'utils';
|
import { safeCallback } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,25 +161,31 @@ export const ActionsMenu = ({
|
|||||||
text={intl.get('view_details')}
|
text={intl.get('view_details')}
|
||||||
onClick={safeCallback(onViewDetails, original)}
|
onClick={safeCallback(onViewDetails, original)}
|
||||||
/>
|
/>
|
||||||
<MenuDivider />
|
<Can I={ManualJournalAction.Edit} a={AbilitySubject.ManualJournal}>
|
||||||
<If condition={!original.is_published}>
|
<MenuDivider />
|
||||||
|
<If condition={!original.is_published}>
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="arrow-to-top" />}
|
||||||
|
text={intl.get('publish_journal')}
|
||||||
|
onClick={safeCallback(onPublish, original)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
</Can>
|
||||||
|
<Can I={ManualJournalAction.Edit} a={AbilitySubject.ManualJournal}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon icon="arrow-to-top" />}
|
icon={<Icon icon="pen-18" />}
|
||||||
text={intl.get('publish_journal')}
|
text={intl.get('edit_journal')}
|
||||||
onClick={safeCallback(onPublish, original)}
|
onClick={safeCallback(onEdit, original)}
|
||||||
/>
|
/>
|
||||||
</If>
|
</Can>
|
||||||
<MenuItem
|
<Can I={ManualJournalAction.Delete} a={AbilitySubject.ManualJournal}>
|
||||||
icon={<Icon icon="pen-18" />}
|
<MenuItem
|
||||||
text={intl.get('edit_journal')}
|
text={intl.get('delete_journal')}
|
||||||
onClick={safeCallback(onEdit, original)}
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
/>
|
intent={Intent.DANGER}
|
||||||
<MenuItem
|
onClick={safeCallback(onDelete, original)}
|
||||||
text={intl.get('delete_journal')}
|
/>
|
||||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
</Can>
|
||||||
intent={Intent.DANGER}
|
|
||||||
onClick={safeCallback(onDelete, original)}
|
|
||||||
/>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ export const useJournalTableEntriesColumns = () => {
|
|||||||
className: 'account',
|
className: 'account',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
width: 160,
|
width: 160,
|
||||||
|
fieldProps: { allowCreate: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: CreditHeaderCell,
|
Header: CreditHeaderCell,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { RESOURCES_TYPES } from 'common/resourcesTypes';
|
import { RESOURCES_TYPES } from 'common/resourcesTypes';
|
||||||
import withDrawerActions from '../Drawer/withDrawerActions';
|
import withDrawerActions from '../Drawer/withDrawerActions';
|
||||||
|
import {
|
||||||
|
AbilitySubject,
|
||||||
|
ManualJournalAction,
|
||||||
|
} from '../../common/abilityOption';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Universal search manual journal item select action.
|
* Universal search manual journal item select action.
|
||||||
@@ -44,4 +48,8 @@ export const universalSearchJournalBind = () => ({
|
|||||||
optionItemLabel: intl.get('manual_journals'),
|
optionItemLabel: intl.get('manual_journals'),
|
||||||
selectItemAction: JournalUniversalSearchSelectAction,
|
selectItemAction: JournalUniversalSearchSelectAction,
|
||||||
itemSelect: manualJournalsToSearch,
|
itemSelect: manualJournalsToSearch,
|
||||||
|
permission: {
|
||||||
|
ability: ManualJournalAction.View,
|
||||||
|
subject: AbilitySubject.ManualJournal,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
|
|
||||||
import withDrawerActions from '../Drawer/withDrawerActions';
|
import withDrawerActions from '../Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { AbilitySubject, AccountAction } from '../../common/abilityOption';
|
||||||
|
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
|
||||||
|
|
||||||
function AccountUniversalSearchItemSelectComponent({
|
function AccountUniversalSearchItemSelectComponent({
|
||||||
// #ownProps
|
// #ownProps
|
||||||
resourceType,
|
resourceType,
|
||||||
@@ -42,4 +45,8 @@ export const universalSearchAccountBind = () => ({
|
|||||||
optionItemLabel: intl.get('accounts'),
|
optionItemLabel: intl.get('accounts'),
|
||||||
selectItemAction: AccountUniversalSearchItemSelect,
|
selectItemAction: AccountUniversalSearchItemSelect,
|
||||||
itemSelect: accountToSearch,
|
itemSelect: accountToSearch,
|
||||||
|
permission: {
|
||||||
|
ability: AccountAction.View,
|
||||||
|
subject: AbilitySubject.Account,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { FormattedMessage as T } from 'components';
|
|||||||
import {
|
import {
|
||||||
AdvancedFilterPopover,
|
AdvancedFilterPopover,
|
||||||
If,
|
If,
|
||||||
|
Can,
|
||||||
DashboardActionViewsList,
|
DashboardActionViewsList,
|
||||||
DashboardFilterButton,
|
DashboardFilterButton,
|
||||||
DashboardRowsHeightButton,
|
DashboardRowsHeightButton,
|
||||||
@@ -30,6 +31,8 @@ import withAlertActions from 'containers/Alert/withAlertActions';
|
|||||||
import withAccountsTableActions from './withAccountsTableActions';
|
import withAccountsTableActions from './withAccountsTableActions';
|
||||||
import withSettings from '../Settings/withSettings';
|
import withSettings from '../Settings/withSettings';
|
||||||
import withSettingsActions from '../Settings/withSettingsActions';
|
import withSettingsActions from '../Settings/withSettingsActions';
|
||||||
|
import { AccountAction, AbilitySubject } from '../../common/abilityOption';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,13 +119,14 @@ function AccountsActionsBar({
|
|||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
<Can I={AccountAction.Create} a={AbilitySubject.Account}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon="plus" />}
|
icon={<Icon icon="plus" />}
|
||||||
text={<T id={'new_account'} />}
|
text={<T id={'new_account'} />}
|
||||||
onClick={onClickNewAccount}
|
onClick={onClickNewAccount}
|
||||||
/>
|
/>
|
||||||
|
</Can>
|
||||||
<AdvancedFilterPopover
|
<AdvancedFilterPopover
|
||||||
advancedFilterProps={{
|
advancedFilterProps={{
|
||||||
conditions: accountsFilterConditions,
|
conditions: accountsFilterConditions,
|
||||||
@@ -183,11 +187,13 @@ function AccountsActionsBar({
|
|||||||
onChange={handleTableRowSizeChange}
|
onChange={handleTableRowSizeChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
<Switch
|
<Can I={AccountAction.Edit} a={AbilitySubject.Account}>
|
||||||
labelElement={<T id={'inactive'} />}
|
<Switch
|
||||||
defaultChecked={accountsInactiveMode}
|
labelElement={<T id={'inactive'} />}
|
||||||
onChange={handleInactiveSwitchChange}
|
defaultChecked={accountsInactiveMode}
|
||||||
/>
|
onChange={handleInactiveSwitchChange}
|
||||||
|
/>
|
||||||
|
</Can>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
<NavbarGroup align={Alignment.RIGHT}>
|
<NavbarGroup align={Alignment.RIGHT}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import {
|
|||||||
MenuDivider,
|
MenuDivider,
|
||||||
Intent,
|
Intent,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Icon, Money, If } from 'components';
|
import { Can, Icon, Money, If } from 'components';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { safeCallback } from 'utils';
|
import { safeCallback } from 'utils';
|
||||||
|
import { AbilitySubject, AccountAction } from '../../common/abilityOption';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts table actions menu.
|
* Accounts table actions menu.
|
||||||
@@ -34,38 +35,45 @@ export function ActionsMenu({
|
|||||||
text={intl.get('view_details')}
|
text={intl.get('view_details')}
|
||||||
onClick={safeCallback(onViewDetails, original)}
|
onClick={safeCallback(onViewDetails, original)}
|
||||||
/>
|
/>
|
||||||
<MenuDivider />
|
<Can I={AccountAction.Edit} a={AbilitySubject.Account}>
|
||||||
<MenuItem
|
<MenuDivider />
|
||||||
icon={<Icon icon="pen-18" />}
|
|
||||||
text={intl.get('edit_account')}
|
|
||||||
onClick={safeCallback(onEdit, original)}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
icon={<Icon icon="plus" />}
|
|
||||||
text={intl.get('new_child_account')}
|
|
||||||
onClick={safeCallback(onNewChild, original)}
|
|
||||||
/>
|
|
||||||
<MenuDivider />
|
|
||||||
<If condition={original.active}>
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={intl.get('inactivate_account')}
|
icon={<Icon icon="pen-18" />}
|
||||||
icon={<Icon icon="pause-16" iconSize={16} />}
|
text={intl.get('edit_account')}
|
||||||
onClick={safeCallback(onInactivate, original)}
|
onClick={safeCallback(onEdit, original)}
|
||||||
/>
|
/>
|
||||||
</If>
|
|
||||||
<If condition={!original.active}>
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={intl.get('activate_account')}
|
icon={<Icon icon="plus" />}
|
||||||
icon={<Icon icon="play-16" iconSize={16} />}
|
text={intl.get('new_child_account')}
|
||||||
onClick={safeCallback(onActivate, original)}
|
onClick={safeCallback(onNewChild, original)}
|
||||||
/>
|
/>
|
||||||
</If>
|
<MenuDivider />
|
||||||
<MenuItem
|
</Can>
|
||||||
text={intl.get('delete_account')}
|
<Can I={AccountAction.Edit} a={AbilitySubject.Account}>
|
||||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
<If condition={original.active}>
|
||||||
intent={Intent.DANGER}
|
<MenuItem
|
||||||
onClick={safeCallback(onDelete, original)}
|
text={intl.get('inactivate_account')}
|
||||||
/>
|
icon={<Icon icon="pause-16" iconSize={16} />}
|
||||||
|
onClick={safeCallback(onInactivate, original)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
<If condition={!original.active}>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('activate_account')}
|
||||||
|
icon={<Icon icon="play-16" iconSize={16} />}
|
||||||
|
onClick={safeCallback(onActivate, original)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
</Can>
|
||||||
|
<Can I={AccountAction.Edit} a={AbilitySubject.Account}>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('delete_account')}
|
||||||
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
onClick={safeCallback(onDelete, original)}
|
||||||
|
/>
|
||||||
|
</Can>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import { useSettingEasySMSDisconnect } from 'hooks/query';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Easy SMS disconnect alert.
|
||||||
|
*/
|
||||||
|
function EasySMSDisconnectAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: {},
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: disconnectEasySMS, isLoading } =
|
||||||
|
useSettingEasySMSDisconnect();
|
||||||
|
|
||||||
|
// Handle cancel Disconnect alert.
|
||||||
|
const handleCancelDisconnect = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle confirm Disconnect alert.
|
||||||
|
const handleConfirmDisconnect = () => {
|
||||||
|
disconnectEasySMS()
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('easysms.disconnect.alert.success_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'easysms.label.disconnect'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelDisconnect}
|
||||||
|
onConfirm={handleConfirmDisconnect}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>Ea aliqua elit reprehenderit pariatur consequat voluptate quis.</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(EasySMSDisconnectAlert);
|
||||||
68
src/containers/Alerts/Invoices/CancelBadDebtAlert.js
Normal file
68
src/containers/Alerts/Invoices/CancelBadDebtAlert.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
import { useCancelBadDebt } from 'hooks/query';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel bad debt alert.
|
||||||
|
*/
|
||||||
|
function CancelBadDebtAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { invoiceId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
// handle cancel alert.
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { mutateAsync: cancelBadDebtMutate, isLoading } = useCancelBadDebt();
|
||||||
|
|
||||||
|
// handleConfirm alert.
|
||||||
|
const handleConfirm = () => {
|
||||||
|
cancelBadDebtMutate(invoiceId)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('bad_debt.cancel_alert.success_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'save'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'bad_debt.cancel_alert.message'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(CancelBadDebtAlert);
|
||||||
83
src/containers/Alerts/Roles/RoleDeleteAlert.js
Normal file
83
src/containers/Alerts/Roles/RoleDeleteAlert.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import { useDeleteRole } from 'hooks/query';
|
||||||
|
import { handleDeleteErrors } from '../../Preferences/Users/Roles/utils';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Role delete alert.
|
||||||
|
*/
|
||||||
|
function RoleDeleteAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { roleId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: deleteRole, isLoading } = useDeleteRole();
|
||||||
|
|
||||||
|
// Handle cancel delete role alert.
|
||||||
|
const handleCancelDelete = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle confirm delete role.
|
||||||
|
const handleConfirmDeleteRole = () => {
|
||||||
|
deleteRole(roleId)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('roles.permission_schema.delete.alert_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(
|
||||||
|
({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
handleDeleteErrors(errors);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'delete'} />}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelDelete}
|
||||||
|
onConfirm={handleConfirmDeleteRole}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
id={
|
||||||
|
'roles.permission_schema.once_delete_this_role_you_will_able_to_restore_it'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(RoleDeleteAlert);
|
||||||
@@ -16,6 +16,8 @@ import ExpensesAlerts from '../Expenses/ExpensesAlerts';
|
|||||||
import AccountTransactionsAlerts from '../CashFlow/AccountTransactions/AccountTransactionsAlerts';
|
import AccountTransactionsAlerts from '../CashFlow/AccountTransactions/AccountTransactionsAlerts';
|
||||||
import UsersAlerts from '../Preferences/Users/UsersAlerts';
|
import UsersAlerts from '../Preferences/Users/UsersAlerts';
|
||||||
import CurrenciesAlerts from '../Preferences/Currencies/CurrenciesAlerts';
|
import CurrenciesAlerts from '../Preferences/Currencies/CurrenciesAlerts';
|
||||||
|
import RolesAlerts from '../Preferences/Users/Roles/RolesAlerts';
|
||||||
|
import EasySMSIntegrationAlerts from '../Preferences/EasySMSIntegration/EasySMSIntegrationAlerts';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
...AccountsAlerts,
|
...AccountsAlerts,
|
||||||
@@ -36,4 +38,6 @@ export default [
|
|||||||
...AccountTransactionsAlerts,
|
...AccountTransactionsAlerts,
|
||||||
...UsersAlerts,
|
...UsersAlerts,
|
||||||
...CurrenciesAlerts,
|
...CurrenciesAlerts,
|
||||||
|
...RolesAlerts,
|
||||||
|
...EasySMSIntegrationAlerts,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import intl from 'react-intl-universal';
|
|||||||
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
||||||
|
|
||||||
import { MaterialProgressBar } from 'components';
|
import { MaterialProgressBar } from 'components';
|
||||||
import { FormatDateCell, If, Icon } from 'components';
|
import { Can, FormatDateCell, If, Icon } from 'components';
|
||||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||||
import { TRANSACRIONS_TYPE } from 'common/cashflowOptions';
|
import { TRANSACRIONS_TYPE } from 'common/cashflowOptions';
|
||||||
|
import { AbilitySubject, CashflowAction } from '../../../common/abilityOption';
|
||||||
import { safeCallback } from 'utils';
|
import { safeCallback } from 'utils';
|
||||||
|
|
||||||
export function ActionsMenu({
|
export function ActionsMenu({
|
||||||
@@ -20,15 +21,17 @@ export function ActionsMenu({
|
|||||||
text={intl.get('view_details')}
|
text={intl.get('view_details')}
|
||||||
onClick={safeCallback(onViewDetails, original)}
|
onClick={safeCallback(onViewDetails, original)}
|
||||||
/>
|
/>
|
||||||
<If condition={TRANSACRIONS_TYPE.includes(original.reference_type)}>
|
<Can I={CashflowAction.Delete} a={AbilitySubject.Cashflow}>
|
||||||
<MenuDivider />
|
<If condition={TRANSACRIONS_TYPE.includes(original.reference_type)}>
|
||||||
<MenuItem
|
<MenuDivider />
|
||||||
text={intl.get('delete_transaction')}
|
<MenuItem
|
||||||
intent={Intent.DANGER}
|
text={intl.get('delete_transaction')}
|
||||||
onClick={safeCallback(onDelete, original)}
|
intent={Intent.DANGER}
|
||||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
onClick={safeCallback(onDelete, original)}
|
||||||
/>
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
</If>
|
/>
|
||||||
|
</If>
|
||||||
|
</Can>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -83,7 +86,7 @@ export function useAccountTransactionsColumns() {
|
|||||||
className: 'deposit',
|
className: 'deposit',
|
||||||
textOverview: true,
|
textOverview: true,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
clickable: true
|
clickable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'withdrawal',
|
id: 'withdrawal',
|
||||||
@@ -93,7 +96,7 @@ export function useAccountTransactionsColumns() {
|
|||||||
width: 150,
|
width: 150,
|
||||||
textOverview: true,
|
textOverview: true,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
clickable: true
|
clickable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'running_balance',
|
id: 'running_balance',
|
||||||
@@ -103,7 +106,7 @@ export function useAccountTransactionsColumns() {
|
|||||||
width: 150,
|
width: 150,
|
||||||
textOverview: true,
|
textOverview: true,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
clickable: true
|
clickable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'balance',
|
id: 'balance',
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import {
|
|||||||
Alignment,
|
Alignment,
|
||||||
Switch,
|
Switch,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Icon, FormattedMessage as T } from 'components';
|
import { Can, Icon, FormattedMessage as T } from 'components';
|
||||||
import { useRefreshCashflowAccounts } from 'hooks/query';
|
import { useRefreshCashflowAccounts } from 'hooks/query';
|
||||||
|
import { CashflowAction, AbilitySubject } from '../../../common/abilityOption';
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
@@ -56,19 +57,22 @@ function CashFlowAccountsActionsBar({
|
|||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<Button
|
<Can I={CashflowAction.Create} a={AbilitySubject.Cashflow}>
|
||||||
className={Classes.MINIMAL}
|
<Button
|
||||||
icon={<Icon icon={'plus-24'} iconSize={20} />}
|
className={Classes.MINIMAL}
|
||||||
text={<T id={'cash_flow.label.add_cash_account'} />}
|
icon={<Icon icon={'plus-24'} iconSize={20} />}
|
||||||
onClick={handleAddBankAccount}
|
text={<T id={'cash_flow.label.add_cash_account'} />}
|
||||||
/>
|
onClick={handleAddBankAccount}
|
||||||
<Button
|
/>
|
||||||
className={Classes.MINIMAL}
|
|
||||||
icon={<Icon icon={'plus-24'} iconSize={20} />}
|
<Button
|
||||||
text={<T id={'cash_flow.label.add_bank_account'} />}
|
className={Classes.MINIMAL}
|
||||||
onClick={handleAddCashAccount}
|
icon={<Icon icon={'plus-24'} iconSize={20} />}
|
||||||
/>
|
text={<T id={'cash_flow.label.add_bank_account'} />}
|
||||||
<NavbarDivider />
|
onClick={handleAddCashAccount}
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
</Can>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon="print-16" iconSize={16} />}
|
icon={<Icon icon="print-16" iconSize={16} />}
|
||||||
@@ -85,13 +89,15 @@ function CashFlowAccountsActionsBar({
|
|||||||
text={<T id={'import'} />}
|
text={<T id={'import'} />}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
<Can I={CashflowAction.Edit} a={AbilitySubject.Cashflow}>
|
||||||
<Switch
|
<Switch
|
||||||
labelElement={<T id={'inactive'} />}
|
labelElement={<T id={'inactive'} />}
|
||||||
defaultChecked={false}
|
defaultChecked={false}
|
||||||
onChange={handleInactiveSwitchChange}
|
onChange={handleInactiveSwitchChange}
|
||||||
/>
|
/>
|
||||||
|
</Can>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
|
|
||||||
<NavbarGroup align={Alignment.RIGHT}>
|
<NavbarGroup align={Alignment.RIGHT}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ import {
|
|||||||
If,
|
If,
|
||||||
Icon,
|
Icon,
|
||||||
T,
|
T,
|
||||||
|
Can,
|
||||||
} from '../../../components';
|
} from '../../../components';
|
||||||
|
import {
|
||||||
|
AccountAction,
|
||||||
|
CashflowAction,
|
||||||
|
AbilitySubject,
|
||||||
|
} from '../../../common/abilityOption';
|
||||||
|
|
||||||
import { useCashFlowAccountsContext } from './CashFlowAccountsProvider';
|
import { useCashFlowAccountsContext } from './CashFlowAccountsProvider';
|
||||||
|
|
||||||
@@ -222,48 +228,56 @@ function CashflowAccountContextMenu({
|
|||||||
text={intl.get('view_details')}
|
text={intl.get('view_details')}
|
||||||
onClick={safeCallback(onViewClick)}
|
onClick={safeCallback(onViewClick)}
|
||||||
/>
|
/>
|
||||||
<MenuDivider />
|
<Can I={CashflowAction.Create} a={AbilitySubject.Cashflow}>
|
||||||
<MenuItem
|
<MenuDivider />
|
||||||
text={<T id={'cash_flow_money_in'} />}
|
|
||||||
icon={<Icon icon={'arrow-downward'} iconSize={16} />}
|
|
||||||
>
|
|
||||||
<CashflowAccountMoneyInContextMenu onClick={onMoneyInClick} />
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
<MenuItem
|
|
||||||
text={<T id={'cash_flow_money_out'} />}
|
|
||||||
icon={<Icon icon={'arrow-upward'} iconSize={16} />}
|
|
||||||
>
|
|
||||||
<CashflowAccountMoneyOutContextMenu onClick={onMoneyOutClick} />
|
|
||||||
</MenuItem>
|
|
||||||
<MenuDivider />
|
|
||||||
|
|
||||||
<MenuItem
|
|
||||||
icon={<Icon icon="pen-18" />}
|
|
||||||
text={intl.get('edit_account')}
|
|
||||||
onClick={safeCallback(onEditClick)}
|
|
||||||
/>
|
|
||||||
<MenuDivider />
|
|
||||||
<If condition={account.active}>
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={intl.get('inactivate_account')}
|
text={<T id={'cash_flow_money_in'} />}
|
||||||
icon={<Icon icon="pause-16" iconSize={16} />}
|
icon={<Icon icon={'arrow-downward'} iconSize={16} />}
|
||||||
onClick={safeCallback(onInactivateClick)}
|
>
|
||||||
/>
|
<CashflowAccountMoneyInContextMenu onClick={onMoneyInClick} />
|
||||||
</If>
|
</MenuItem>
|
||||||
<If condition={!account.active}>
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={intl.get('activate_account')}
|
text={<T id={'cash_flow_money_out'} />}
|
||||||
icon={<Icon icon="play-16" iconSize={16} />}
|
icon={<Icon icon={'arrow-upward'} iconSize={16} />}
|
||||||
onClick={safeCallback(onActivateClick)}
|
>
|
||||||
|
<CashflowAccountMoneyOutContextMenu onClick={onMoneyOutClick} />
|
||||||
|
</MenuItem>
|
||||||
|
</Can>
|
||||||
|
<Can I={CashflowAction.Edit} a={AbilitySubject.Cashflow}>
|
||||||
|
<MenuDivider />
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="pen-18" />}
|
||||||
|
text={intl.get('edit_account')}
|
||||||
|
onClick={safeCallback(onEditClick)}
|
||||||
/>
|
/>
|
||||||
</If>
|
</Can>
|
||||||
<MenuItem
|
<Can I={AccountAction.Edit} a={AbilitySubject.Account}>
|
||||||
text={intl.get('delete_account')}
|
<MenuDivider />
|
||||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
<If condition={account.active}>
|
||||||
intent={Intent.DANGER}
|
<MenuItem
|
||||||
onClick={safeCallback(onDeleteClick)}
|
text={intl.get('inactivate_account')}
|
||||||
/>
|
icon={<Icon icon="pause-16" iconSize={16} />}
|
||||||
|
onClick={safeCallback(onInactivateClick)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
<If condition={!account.active}>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('activate_account')}
|
||||||
|
icon={<Icon icon="play-16" iconSize={16} />}
|
||||||
|
onClick={safeCallback(onActivateClick)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
</Can>
|
||||||
|
<Can I={CashflowAction.Delete} a={AbilitySubject.Cashflow}>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('delete_account')}
|
||||||
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
onClick={safeCallback(onDeleteClick)}
|
||||||
|
/>
|
||||||
|
</Can>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,23 +9,24 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { Icon } from 'components';
|
import { Icon } from 'components';
|
||||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
|
|
||||||
|
import { safeInvoke } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customer floating actions bar.
|
* Customer floating actions bar.
|
||||||
*/
|
*/
|
||||||
export default function CustomerFloatingActions() {
|
export default function CustomerFloatingActions({ onCancel }) {
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
// Customer form context.
|
// Customer form context.
|
||||||
const { isNewMode,setSubmitPayload } = useCustomerFormContext();
|
const { isNewMode, setSubmitPayload } = useCustomerFormContext();
|
||||||
|
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { resetForm, submitForm, isSubmitting } = useFormikContext();
|
const { resetForm, submitForm, isSubmitting } = useFormikContext();
|
||||||
@@ -37,7 +38,7 @@ export default function CustomerFloatingActions() {
|
|||||||
|
|
||||||
// Handle cancel button click.
|
// Handle cancel button click.
|
||||||
const handleCancelBtnClick = (event) => {
|
const handleCancelBtnClick = (event) => {
|
||||||
history.goBack();
|
safeInvoke(onCancel, event);
|
||||||
};
|
};
|
||||||
|
|
||||||
// handle clear button clicl.
|
// handle clear button clicl.
|
||||||
@@ -55,7 +56,7 @@ export default function CustomerFloatingActions() {
|
|||||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{/* ----------- Save and New ----------- */}
|
{/* ----------- Save and New ----------- */}
|
||||||
<Button
|
<SaveButton
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
@@ -83,6 +84,7 @@ export default function CustomerFloatingActions() {
|
|||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
{/* ----------- Clear & Reset----------- */}
|
{/* ----------- Clear & Reset----------- */}
|
||||||
<Button
|
<Button
|
||||||
className={'ml1'}
|
className={'ml1'}
|
||||||
@@ -99,3 +101,7 @@ export default function CustomerFloatingActions() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SaveButton = styled(Button)`
|
||||||
|
min-width: 100px;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,152 +1,14 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import { Formik, Form } from 'formik';
|
import { CustomerFormProvider } from './CustomerFormProvider';
|
||||||
import moment from 'moment';
|
import CustomerFormFormik from './CustomerFormFormik';
|
||||||
import { Intent } from '@blueprintjs/core';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
|
||||||
import AppToaster from 'components/AppToaster';
|
|
||||||
|
|
||||||
import CustomerFormPrimarySection from './CustomerFormPrimarySection';
|
|
||||||
import CustomerFormAfterPrimarySection from './CustomerFormAfterPrimarySection';
|
|
||||||
import CustomersTabs from './CustomersTabs';
|
|
||||||
import CustomerFloatingActions from './CustomerFloatingActions';
|
|
||||||
|
|
||||||
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
|
||||||
|
|
||||||
import { compose, transformToForm } from 'utils';
|
|
||||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
|
||||||
import { CreateCustomerForm, EditCustomerForm } from './CustomerForm.schema';
|
|
||||||
|
|
||||||
const defaultInitialValues = {
|
|
||||||
customer_type: 'business',
|
|
||||||
salutation: '',
|
|
||||||
first_name: '',
|
|
||||||
last_name: '',
|
|
||||||
company_name: '',
|
|
||||||
display_name: '',
|
|
||||||
|
|
||||||
email: '',
|
|
||||||
work_phone: '',
|
|
||||||
personal_phone: '',
|
|
||||||
website: '',
|
|
||||||
note: '',
|
|
||||||
active: true,
|
|
||||||
|
|
||||||
billing_address_country: '',
|
|
||||||
billing_address_1: '',
|
|
||||||
billing_address_2: '',
|
|
||||||
billing_address_city: '',
|
|
||||||
billing_address_state: '',
|
|
||||||
billing_address_postcode: '',
|
|
||||||
billing_address_phone: '',
|
|
||||||
|
|
||||||
shipping_address_country: '',
|
|
||||||
shipping_address_1: '',
|
|
||||||
shipping_address_2: '',
|
|
||||||
shipping_address_city: '',
|
|
||||||
shipping_address_state: '',
|
|
||||||
shipping_address_postcode: '',
|
|
||||||
shipping_address_phone: '',
|
|
||||||
|
|
||||||
opening_balance: '',
|
|
||||||
currency_code: '',
|
|
||||||
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customer form.
|
* Abstructed customer form.
|
||||||
*/
|
*/
|
||||||
function CustomerForm({ organization: { base_currency } }) {
|
export default function CustomerForm({ customerId }) {
|
||||||
const {
|
|
||||||
customer,
|
|
||||||
customerId,
|
|
||||||
submitPayload,
|
|
||||||
contactDuplicate,
|
|
||||||
editCustomerMutate,
|
|
||||||
createCustomerMutate,
|
|
||||||
isNewMode,
|
|
||||||
} = useCustomerFormContext();
|
|
||||||
|
|
||||||
// const isNewMode = !customerId;
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial values in create and edit mode.
|
|
||||||
*/
|
|
||||||
const initialValues = useMemo(
|
|
||||||
() => ({
|
|
||||||
...defaultInitialValues,
|
|
||||||
currency_code: base_currency,
|
|
||||||
...transformToForm(contactDuplicate || customer, defaultInitialValues),
|
|
||||||
}),
|
|
||||||
[customer, contactDuplicate, base_currency],
|
|
||||||
);
|
|
||||||
|
|
||||||
//Handles the form submit.
|
|
||||||
const handleFormSubmit = (
|
|
||||||
values,
|
|
||||||
{ setSubmitting, resetForm, setErrors },
|
|
||||||
) => {
|
|
||||||
const formValues = { ...values };
|
|
||||||
|
|
||||||
const onSuccess = () => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: intl.get(
|
|
||||||
isNewMode
|
|
||||||
? 'the_customer_has_been_created_successfully'
|
|
||||||
: 'the_item_customer_has_been_edited_successfully',
|
|
||||||
),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
setSubmitting(false);
|
|
||||||
resetForm();
|
|
||||||
|
|
||||||
if (!submitPayload.noRedirect) {
|
|
||||||
history.push('/customers');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onError = () => {
|
|
||||||
setSubmitting(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isNewMode) {
|
|
||||||
createCustomerMutate(formValues).then(onSuccess).catch(onError);
|
|
||||||
} else {
|
|
||||||
editCustomerMutate([customer.id, formValues])
|
|
||||||
.then(onSuccess)
|
|
||||||
.catch(onError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_CUSTOMER)}>
|
<CustomerFormProvider customerId={customerId}>
|
||||||
<Formik
|
<CustomerFormFormik />
|
||||||
validationSchema={isNewMode ? CreateCustomerForm : EditCustomerForm}
|
</CustomerFormProvider>
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={handleFormSubmit}
|
|
||||||
>
|
|
||||||
<Form>
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
|
||||||
<CustomerFormPrimarySection />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={'page-form__after-priamry-section'}>
|
|
||||||
<CustomerFormAfterPrimarySection />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
|
||||||
<CustomersTabs />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CustomerFloatingActions />
|
|
||||||
</Form>
|
|
||||||
</Formik>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withCurrentOrganization())(CustomerForm);
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import intl from 'react-intl-universal';
|
|||||||
import { inputIntent } from 'utils';
|
import { inputIntent } from 'utils';
|
||||||
|
|
||||||
export default function CustomerFormAfterPrimarySection({}) {
|
export default function CustomerFormAfterPrimarySection({}) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="customer-form__after-primary-section-content">
|
<div class="customer-form__after-primary-section-content">
|
||||||
{/*------------ Customer email -----------*/}
|
{/*------------ Customer email -----------*/}
|
||||||
@@ -31,21 +30,21 @@ export default function CustomerFormAfterPrimarySection({}) {
|
|||||||
inline={true}
|
inline={true}
|
||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<FastField name={'work_phone'}>
|
<FastField name={'personal_phone'}>
|
||||||
{({ field, meta: { error, touched } }) => (
|
{({ field, meta: { error, touched } }) => (
|
||||||
<InputGroup
|
<InputGroup
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
placeholder={intl.get('work')}
|
placeholder={intl.get('personal')}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
<FastField name={'personal_phone'}>
|
<FastField name={'work_phone'}>
|
||||||
{({ field, meta: { error, touched } }) => (
|
{({ field, meta: { error, touched } }) => (
|
||||||
<InputGroup
|
<InputGroup
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
placeholder={intl.get('Mobile')}
|
placeholder={intl.get('work')}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
127
src/containers/Customers/CustomerForm/CustomerFormFormik.js
Normal file
127
src/containers/Customers/CustomerForm/CustomerFormFormik.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { Formik, Form } from 'formik';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import AppToaster from 'components/AppToaster';
|
||||||
|
|
||||||
|
import { CreateCustomerForm, EditCustomerForm } from './CustomerForm.schema';
|
||||||
|
|
||||||
|
import CustomerFormPrimarySection from './CustomerFormPrimarySection';
|
||||||
|
import CustomerFormAfterPrimarySection from './CustomerFormAfterPrimarySection';
|
||||||
|
import CustomersTabs from './CustomersTabs';
|
||||||
|
import CustomerFloatingActions from './CustomerFloatingActions';
|
||||||
|
|
||||||
|
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||||
|
|
||||||
|
import { compose, transformToForm, saveInvoke } from 'utils';
|
||||||
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
|
import { defaultInitialValues } from './utils';
|
||||||
|
|
||||||
|
import 'style/pages/Customers/Form.scss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer form.
|
||||||
|
*/
|
||||||
|
function CustomerFormFormik({
|
||||||
|
organization: { base_currency },
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
|
initialValues: initialCustomerValues,
|
||||||
|
onSubmitSuccess,
|
||||||
|
onSubmitError,
|
||||||
|
onCancel,
|
||||||
|
className,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
customer,
|
||||||
|
submitPayload,
|
||||||
|
contactDuplicate,
|
||||||
|
editCustomerMutate,
|
||||||
|
createCustomerMutate,
|
||||||
|
isNewMode,
|
||||||
|
} = useCustomerFormContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial values in create and edit mode.
|
||||||
|
*/
|
||||||
|
const initialValues = useMemo(
|
||||||
|
() => ({
|
||||||
|
...defaultInitialValues,
|
||||||
|
currency_code: base_currency,
|
||||||
|
...transformToForm(contactDuplicate || customer, defaultInitialValues),
|
||||||
|
...initialCustomerValues,
|
||||||
|
}),
|
||||||
|
[customer, contactDuplicate, base_currency, initialCustomerValues],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, formArgs) => {
|
||||||
|
const { setSubmitting, resetForm } = formArgs;
|
||||||
|
const formValues = { ...values };
|
||||||
|
|
||||||
|
const onSuccess = () => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get(
|
||||||
|
isNewMode
|
||||||
|
? 'the_customer_has_been_created_successfully'
|
||||||
|
: 'the_item_customer_has_been_edited_successfully',
|
||||||
|
),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
setSubmitting(false);
|
||||||
|
resetForm();
|
||||||
|
|
||||||
|
saveInvoke(onSubmitSuccess, values, formArgs, submitPayload);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onError = () => {
|
||||||
|
setSubmitting(false);
|
||||||
|
saveInvoke(onSubmitError, values, formArgs, submitPayload);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isNewMode) {
|
||||||
|
createCustomerMutate(formValues).then(onSuccess).catch(onError);
|
||||||
|
} else {
|
||||||
|
editCustomerMutate([customer.id, formValues])
|
||||||
|
.then(onSuccess)
|
||||||
|
.catch(onError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
CLASSES.PAGE_FORM,
|
||||||
|
CLASSES.PAGE_FORM_CUSTOMER,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Formik
|
||||||
|
validationSchema={isNewMode ? CreateCustomerForm : EditCustomerForm}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
||||||
|
<CustomerFormPrimarySection />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'page-form__after-priamry-section'}>
|
||||||
|
<CustomerFormAfterPrimarySection />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
||||||
|
<CustomersTabs />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CustomerFloatingActions onCancel={onCancel} />
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withCurrentOrganization())(CustomerFormFormik);
|
||||||
@@ -1,20 +1,74 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams, useHistory } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { DashboardCard } from 'components';
|
import { DashboardCard } from 'components';
|
||||||
import CustomerForm from './CustomerForm';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { CustomerFormProvider } from './CustomerFormProvider';
|
|
||||||
|
|
||||||
import 'style/pages/Customers/PageForm.scss';
|
import CustomerFormFormik from './CustomerFormFormik';
|
||||||
|
import {
|
||||||
|
CustomerFormProvider,
|
||||||
|
useCustomerFormContext,
|
||||||
|
} from './CustomerFormProvider';
|
||||||
|
|
||||||
export default function CustomerFormPage() {
|
/**
|
||||||
const { id } = useParams();
|
* Customer form page loading.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
function CustomerFormPageLoading({ children }) {
|
||||||
|
const { isFormLoading } = useCustomerFormContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomerFormProvider customerId={id}>
|
<CustomerDashboardInsider loading={isFormLoading}>
|
||||||
<DashboardCard page>
|
{children}
|
||||||
<CustomerForm />
|
</CustomerDashboardInsider>
|
||||||
</DashboardCard>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer form page.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
export default function CustomerFormPage() {
|
||||||
|
const history = useHistory();
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const customerId = parseInt(id, 10);
|
||||||
|
|
||||||
|
// Handle the form submit success.
|
||||||
|
const handleSubmitSuccess = (values, formArgs, submitPayload) => {
|
||||||
|
if (!submitPayload.noRedirect) {
|
||||||
|
history.push('/customers');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Handle the form cancel button click.
|
||||||
|
const handleFormCancel = () => {
|
||||||
|
history.goBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CustomerFormProvider customerId={customerId}>
|
||||||
|
<CustomerFormPageLoading>
|
||||||
|
<DashboardCard page>
|
||||||
|
<CustomerFormPageFormik
|
||||||
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
/>
|
||||||
|
</DashboardCard>
|
||||||
|
</CustomerFormPageLoading>
|
||||||
</CustomerFormProvider>
|
</CustomerFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CustomerFormPageFormik = styled(CustomerFormFormik)`
|
||||||
|
.page-form {
|
||||||
|
&__floating-actions {
|
||||||
|
margin-left: -40px;
|
||||||
|
margin-right: -40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CustomerDashboardInsider = styled(DashboardInsider)`
|
||||||
|
padding-bottom: 64px;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { useState, createContext } from 'react';
|
import React, { useState, createContext } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
|
||||||
import {
|
import {
|
||||||
useCustomer,
|
useCustomer,
|
||||||
useCurrencies,
|
useCurrencies,
|
||||||
@@ -24,7 +23,7 @@ function CustomerFormProvider({ customerId, ...props }) {
|
|||||||
// Handle fetch contact duplicate details.
|
// Handle fetch contact duplicate details.
|
||||||
const { data: contactDuplicate, isLoading: isContactLoading } = useContact(
|
const { data: contactDuplicate, isLoading: isContactLoading } = useContact(
|
||||||
contactId,
|
contactId,
|
||||||
{ enabled: !!contactId, },
|
{ enabled: !!contactId },
|
||||||
);
|
);
|
||||||
// Handle fetch Currencies data table
|
// Handle fetch Currencies data table
|
||||||
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
||||||
@@ -38,6 +37,9 @@ function CustomerFormProvider({ customerId, ...props }) {
|
|||||||
// determines whether the form new or duplicate mode.
|
// determines whether the form new or duplicate mode.
|
||||||
const isNewMode = contactId || !customerId;
|
const isNewMode = contactId || !customerId;
|
||||||
|
|
||||||
|
const isFormLoading =
|
||||||
|
isCustomerLoading || isCurrenciesLoading || isContactLoading;
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
customerId,
|
customerId,
|
||||||
customer,
|
customer,
|
||||||
@@ -48,24 +50,14 @@ function CustomerFormProvider({ customerId, ...props }) {
|
|||||||
|
|
||||||
isCustomerLoading,
|
isCustomerLoading,
|
||||||
isCurrenciesLoading,
|
isCurrenciesLoading,
|
||||||
|
isFormLoading,
|
||||||
|
|
||||||
setSubmitPayload,
|
setSubmitPayload,
|
||||||
editCustomerMutate,
|
editCustomerMutate,
|
||||||
createCustomerMutate,
|
createCustomerMutate,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <CustomerFormContext.Provider value={provider} {...props} />;
|
||||||
<DashboardInsider
|
|
||||||
loading={
|
|
||||||
isCustomerLoading ||
|
|
||||||
isCurrenciesLoading ||
|
|
||||||
isContactLoading
|
|
||||||
}
|
|
||||||
name={'customer-form'}
|
|
||||||
>
|
|
||||||
<CustomerFormContext.Provider value={provider} {...props} />
|
|
||||||
</DashboardInsider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useCustomerFormContext = () => React.useContext(CustomerFormContext);
|
const useCustomerFormContext = () => React.useContext(CustomerFormContext);
|
||||||
|
|||||||
38
src/containers/Customers/CustomerForm/utils.js
Normal file
38
src/containers/Customers/CustomerForm/utils.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
|
||||||
|
export const defaultInitialValues = {
|
||||||
|
customer_type: 'business',
|
||||||
|
salutation: '',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
company_name: '',
|
||||||
|
display_name: '',
|
||||||
|
|
||||||
|
email: '',
|
||||||
|
work_phone: '',
|
||||||
|
personal_phone: '',
|
||||||
|
website: '',
|
||||||
|
note: '',
|
||||||
|
active: true,
|
||||||
|
|
||||||
|
billing_address_country: '',
|
||||||
|
billing_address_1: '',
|
||||||
|
billing_address_2: '',
|
||||||
|
billing_address_city: '',
|
||||||
|
billing_address_state: '',
|
||||||
|
billing_address_postcode: '',
|
||||||
|
billing_address_phone: '',
|
||||||
|
|
||||||
|
shipping_address_country: '',
|
||||||
|
shipping_address_1: '',
|
||||||
|
shipping_address_2: '',
|
||||||
|
shipping_address_city: '',
|
||||||
|
shipping_address_state: '',
|
||||||
|
shipping_address_postcode: '',
|
||||||
|
shipping_address_phone: '',
|
||||||
|
|
||||||
|
opening_balance: '',
|
||||||
|
currency_code: '',
|
||||||
|
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
|
};
|
||||||
@@ -14,6 +14,7 @@ import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
|||||||
import {
|
import {
|
||||||
If,
|
If,
|
||||||
Icon,
|
Icon,
|
||||||
|
Can,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
DashboardActionViewsList,
|
DashboardActionViewsList,
|
||||||
AdvancedFilterPopover,
|
AdvancedFilterPopover,
|
||||||
@@ -29,6 +30,7 @@ import withCustomersActions from './withCustomersActions';
|
|||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withSettingsActions from '../../Settings/withSettingsActions';
|
import withSettingsActions from '../../Settings/withSettingsActions';
|
||||||
import withSettings from '../../Settings/withSettings';
|
import withSettings from '../../Settings/withSettings';
|
||||||
|
import { CustomerAction, AbilitySubject } from '../../../common/abilityOption';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -103,15 +105,15 @@ function CustomerActionsBar({
|
|||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
<Can I={CustomerAction.Create} a={AbilitySubject.Item}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon={'plus'} />}
|
icon={<Icon icon={'plus'} />}
|
||||||
text={<T id={'new_customer'} />}
|
text={<T id={'new_customer'} />}
|
||||||
onClick={onClickNewCustomer}
|
onClick={onClickNewCustomer}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
</Can>
|
||||||
<AdvancedFilterPopover
|
<AdvancedFilterPopover
|
||||||
advancedFilterProps={{
|
advancedFilterProps={{
|
||||||
conditions: customersFilterConditions,
|
conditions: customersFilterConditions,
|
||||||
@@ -152,11 +154,13 @@ function CustomerActionsBar({
|
|||||||
onChange={handleTableRowSizeChange}
|
onChange={handleTableRowSizeChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
<Switch
|
<Can I={CustomerAction.Edit} a={AbilitySubject.Customer}>
|
||||||
labelElement={<T id={'inactive'} />}
|
<Switch
|
||||||
defaultChecked={accountsInactiveMode}
|
labelElement={<T id={'inactive'} />}
|
||||||
onChange={handleInactiveSwitchChange}
|
defaultChecked={accountsInactiveMode}
|
||||||
/>
|
onChange={handleInactiveSwitchChange}
|
||||||
|
/>
|
||||||
|
</Can>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
<NavbarGroup align={Alignment.RIGHT}>
|
<NavbarGroup align={Alignment.RIGHT}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import React from 'react';
|
|||||||
import { Button, Intent } from '@blueprintjs/core';
|
import { Button, Intent } from '@blueprintjs/core';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { EmptyStatus } from 'components';
|
import { EmptyStatus } from 'components';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { Can, FormattedMessage as T } from 'components';
|
||||||
|
import { AbilitySubject, CustomerAction } from '../../../common/abilityOption';
|
||||||
|
|
||||||
export default function CustomersEmptyStatus() {
|
export default function CustomersEmptyStatus() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -17,19 +18,21 @@ export default function CustomersEmptyStatus() {
|
|||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<>
|
<>
|
||||||
<Button
|
<Can I={CustomerAction.Create} a={AbilitySubject.Customer}>
|
||||||
intent={Intent.PRIMARY}
|
<Button
|
||||||
large={true}
|
intent={Intent.PRIMARY}
|
||||||
onClick={() => {
|
large={true}
|
||||||
history.push('/customers/new');
|
onClick={() => {
|
||||||
}}
|
history.push('/customers/new');
|
||||||
>
|
}}
|
||||||
<T id={'new_customer'} />
|
>
|
||||||
</Button>
|
<T id={'new_customer'} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button intent={Intent.NONE} large={true}>
|
<Button intent={Intent.NONE} large={true}>
|
||||||
<T id={'learn_more'} />
|
<T id={'learn_more'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Can>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import clsx from 'classnames';
|
|||||||
|
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
import { Icon, Money, If, AvaterCell } from 'components';
|
import { Can, Icon, Money, If, AvaterCell } from 'components';
|
||||||
|
import { CustomerAction, AbilitySubject } from '../../../common/abilityOption';
|
||||||
|
|
||||||
import { safeCallback } from 'utils';
|
import { safeCallback } from 'utils';
|
||||||
|
|
||||||
@@ -29,37 +30,46 @@ export function ActionsMenu({
|
|||||||
text={intl.get('view_details')}
|
text={intl.get('view_details')}
|
||||||
onClick={safeCallback(onViewDetails, original)}
|
onClick={safeCallback(onViewDetails, original)}
|
||||||
/>
|
/>
|
||||||
<MenuDivider />
|
<Can I={CustomerAction.Edit} a={AbilitySubject.Customer}>
|
||||||
<MenuItem
|
<MenuDivider />
|
||||||
icon={<Icon icon="pen-18" />}
|
|
||||||
text={intl.get('edit_customer')}
|
|
||||||
onClick={safeCallback(onEdit, original)}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
icon={<Icon icon="duplicate-16" />}
|
|
||||||
text={intl.get('duplicate')}
|
|
||||||
onClick={safeCallback(onDuplicate, original)}
|
|
||||||
/>
|
|
||||||
<If condition={original.active}>
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={intl.get('inactivate_customer')}
|
icon={<Icon icon="pen-18" />}
|
||||||
icon={<Icon icon="pause-16" iconSize={16} />}
|
text={intl.get('edit_customer')}
|
||||||
onClick={safeCallback(onInactivate, original)}
|
onClick={safeCallback(onEdit, original)}
|
||||||
/>
|
/>
|
||||||
</If>
|
</Can>
|
||||||
<If condition={!original.active}>
|
<Can I={CustomerAction.Create} a={AbilitySubject.Customer}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={intl.get('activate_customer')}
|
icon={<Icon icon="duplicate-16" />}
|
||||||
icon={<Icon icon="play-16" iconSize={16} />}
|
text={intl.get('duplicate')}
|
||||||
onClick={safeCallback(onActivate, original)}
|
onClick={safeCallback(onDuplicate, original)}
|
||||||
/>
|
/>
|
||||||
</If>
|
</Can>
|
||||||
<MenuItem
|
<Can I={CustomerAction.Edit} a={AbilitySubject.Customer}>
|
||||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
<If condition={original.active}>
|
||||||
text={intl.get('delete_customer')}
|
<MenuItem
|
||||||
intent={Intent.DANGER}
|
text={intl.get('inactivate_customer')}
|
||||||
onClick={safeCallback(onDelete, original)}
|
icon={<Icon icon="pause-16" iconSize={16} />}
|
||||||
/>
|
onClick={safeCallback(onInactivate, original)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
<If condition={!original.active}>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('activate_customer')}
|
||||||
|
icon={<Icon icon="play-16" iconSize={16} />}
|
||||||
|
onClick={safeCallback(onActivate, original)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
</Can>
|
||||||
|
<Can I={CustomerAction.Delete} a={AbilitySubject.Customer}>
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
|
text={intl.get('delete_customer')}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
onClick={safeCallback(onDelete, original)}
|
||||||
|
/>
|
||||||
|
</Can>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import { AbilitySubject, CustomerAction } from '../../common/abilityOption';
|
||||||
|
|
||||||
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
|
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
|
||||||
import withDrawerActions from '../Drawer/withDrawerActions';
|
import withDrawerActions from '../Drawer/withDrawerActions';
|
||||||
@@ -42,4 +43,8 @@ export const universalSearchCustomerBind = () => ({
|
|||||||
optionItemLabel: intl.get('customers'),
|
optionItemLabel: intl.get('customers'),
|
||||||
selectItemAction: CustomerUniversalSearchSelectAction,
|
selectItemAction: CustomerUniversalSearchSelectAction,
|
||||||
itemSelect: customersToSearch,
|
itemSelect: customersToSearch,
|
||||||
|
permission: {
|
||||||
|
ability: CustomerAction.View,
|
||||||
|
subject: AbilitySubject.Customer,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
20
src/containers/Dialogs/BadDebtDialog/BadDebtDialogContent.js
Normal file
20
src/containers/Dialogs/BadDebtDialog/BadDebtDialogContent.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import 'style/pages/BadDebt/BadDebtDialog.scss';
|
||||||
|
import { BadDebtFormProvider } from './BadDebtFormProvider';
|
||||||
|
import BadDebtForm from './BadDebtForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad debt dialog content.
|
||||||
|
*/
|
||||||
|
export default function BadDebtDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
invoice,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<BadDebtFormProvider invoiceId={invoice} dialogName={dialogName}>
|
||||||
|
<BadDebtForm />
|
||||||
|
</BadDebtFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
86
src/containers/Dialogs/BadDebtDialog/BadDebtForm.js
Normal file
86
src/containers/Dialogs/BadDebtDialog/BadDebtForm.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
import { CreateBadDebtFormSchema } from './BadDebtForm.schema';
|
||||||
|
import { transformErrors } from './utils';
|
||||||
|
|
||||||
|
import BadDebtFormContent from './BadDebtFormContent';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||||
|
|
||||||
|
import { useBadDebtContext } from './BadDebtFormProvider';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
const defaultInitialValues = {
|
||||||
|
expense_account_id: '',
|
||||||
|
reason: '',
|
||||||
|
amount: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
function BadDebtForm({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
|
||||||
|
// #withCurrentOrganization
|
||||||
|
organization: { base_currency },
|
||||||
|
}) {
|
||||||
|
const { invoice, dialogName, createBadDebtMutate, cancelBadDebtMutate } =
|
||||||
|
useBadDebtContext();
|
||||||
|
|
||||||
|
// Initial form values
|
||||||
|
const initialValues = {
|
||||||
|
...defaultInitialValues,
|
||||||
|
currency_code: base_currency,
|
||||||
|
amount: invoice.due_amount,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
const form = {
|
||||||
|
...omit(values, ['currency_code']),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle request response success.
|
||||||
|
const onSuccess = (response) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('bad_debt.dialog.success_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle request response errors.
|
||||||
|
const onError = ({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
if (errors) {
|
||||||
|
transformErrors(errors, { setErrors });
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
createBadDebtMutate([invoice.id, form]).then(onSuccess).catch(onError);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateBadDebtFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
component={BadDebtFormContent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withCurrentOrganization(),
|
||||||
|
)(BadDebtForm);
|
||||||
17
src/containers/Dialogs/BadDebtDialog/BadDebtForm.schema.js
Normal file
17
src/containers/Dialogs/BadDebtDialog/BadDebtForm.schema.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { DATATYPES_LENGTH } from 'common/dataTypes';
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
expense_account_id: Yup.number()
|
||||||
|
.required()
|
||||||
|
.label(intl.get('expense_account_id')),
|
||||||
|
amount: Yup.number().required().label(intl.get('amount')),
|
||||||
|
reason: Yup.string()
|
||||||
|
.required()
|
||||||
|
.min(3)
|
||||||
|
.max(DATATYPES_LENGTH.TEXT)
|
||||||
|
.label(intl.get('reason')),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateBadDebtFormSchema = Schema;
|
||||||
17
src/containers/Dialogs/BadDebtDialog/BadDebtFormContent.js
Normal file
17
src/containers/Dialogs/BadDebtDialog/BadDebtFormContent.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
|
||||||
|
import BadDebtFormFields from './BadDebtFormFields';
|
||||||
|
import BadDebtFormFloatingActions from './BadDebtFormFloatingActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad debt form content.
|
||||||
|
*/
|
||||||
|
export default function BadDebtFormContent() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<BadDebtFormFields />
|
||||||
|
<BadDebtFormFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
121
src/containers/Dialogs/BadDebtDialog/BadDebtFormFields.js
Normal file
121
src/containers/Dialogs/BadDebtDialog/BadDebtFormFields.js
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, ErrorMessage } from 'formik';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
|
import {
|
||||||
|
Classes,
|
||||||
|
FormGroup,
|
||||||
|
TextArea,
|
||||||
|
ControlGroup,
|
||||||
|
Callout,
|
||||||
|
Intent,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { ACCOUNT_TYPE } from 'common/accountTypes';
|
||||||
|
import { inputIntent } from 'utils';
|
||||||
|
import {
|
||||||
|
AccountsSuggestField,
|
||||||
|
InputPrependText,
|
||||||
|
MoneyInputGroup,
|
||||||
|
FieldRequiredHint,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
|
import { useBadDebtContext } from './BadDebtFormProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad debt form fields.
|
||||||
|
*/
|
||||||
|
function BadDebtFormFields() {
|
||||||
|
const amountfieldRef = useAutofocus();
|
||||||
|
|
||||||
|
const { accounts } = useBadDebtContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<Callout intent={Intent.PRIMARY}>
|
||||||
|
<p>
|
||||||
|
<T id={'bad_debt.dialog.header_note'} />
|
||||||
|
</p>
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
{/*------------ Written-off amount -----------*/}
|
||||||
|
<FastField name={'amount'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'bad_debt.dialog.written_off_amount'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
className={classNames('form-group--amount', CLASSES.FILL)}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="amount" />}
|
||||||
|
>
|
||||||
|
<ControlGroup>
|
||||||
|
<InputPrependText text={values.currency_code} />
|
||||||
|
|
||||||
|
<MoneyInputGroup
|
||||||
|
value={value}
|
||||||
|
minimal={true}
|
||||||
|
onChange={(amount) => {
|
||||||
|
setFieldValue('amount', amount);
|
||||||
|
}}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
disabled={amountfieldRef}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
{/*------------ Expense account -----------*/}
|
||||||
|
<FastField name={'expense_account_id'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'expense_account_id'} />}
|
||||||
|
className={classNames(
|
||||||
|
'form-group--expense_account_id',
|
||||||
|
'form-group--select-list',
|
||||||
|
CLASSES.FILL,
|
||||||
|
)}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'expense_account_id'} />}
|
||||||
|
>
|
||||||
|
<AccountsSuggestField
|
||||||
|
selectedAccountId={value}
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountSelected={({ id }) =>
|
||||||
|
form.setFieldValue('expense_account_id', id)
|
||||||
|
}
|
||||||
|
filterByTypes={[ACCOUNT_TYPE.EXPENSE]}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
{/*------------ reason -----------*/}
|
||||||
|
<FastField name={'reason'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'reason'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
className={'form-group--reason'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'reason'} />}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BadDebtFormFields;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
import { useBadDebtContext } from './BadDebtFormProvider';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad bebt form floating actions.
|
||||||
|
*/
|
||||||
|
function BadDebtFormFloatingActions({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
// bad debt invoice dialog context.
|
||||||
|
const { dialogName } = useBadDebtContext();
|
||||||
|
|
||||||
|
// Formik context.
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
// Handle close button click.
|
||||||
|
const handleCancelBtnClick = () => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
loading={isSubmitting}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{<T id={'save'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(BadDebtFormFloatingActions);
|
||||||
46
src/containers/Dialogs/BadDebtDialog/BadDebtFormProvider.js
Normal file
46
src/containers/Dialogs/BadDebtDialog/BadDebtFormProvider.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
import {
|
||||||
|
useAccounts,
|
||||||
|
useInvoice,
|
||||||
|
useCreateBadDebt,
|
||||||
|
useCancelBadDebt,
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
|
const BadDebtContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad debt provider.
|
||||||
|
*/
|
||||||
|
function BadDebtFormProvider({ invoiceId, dialogName, ...props }) {
|
||||||
|
// Handle fetch accounts data.
|
||||||
|
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
|
||||||
|
|
||||||
|
// Handle fetch invoice data.
|
||||||
|
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
|
||||||
|
enabled: !!invoiceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create and cancel bad debt mutations.
|
||||||
|
const { mutateAsync: createBadDebtMutate } = useCreateBadDebt();
|
||||||
|
|
||||||
|
// State provider.
|
||||||
|
const provider = {
|
||||||
|
accounts,
|
||||||
|
invoice,
|
||||||
|
invoiceId,
|
||||||
|
dialogName,
|
||||||
|
createBadDebtMutate,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent isLoading={isAccountsLoading || isInvoiceLoading}>
|
||||||
|
<BadDebtContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useBadDebtContext = () => React.useContext(BadDebtContext);
|
||||||
|
|
||||||
|
export { BadDebtFormProvider, useBadDebtContext };
|
||||||
29
src/containers/Dialogs/BadDebtDialog/index.js
Normal file
29
src/containers/Dialogs/BadDebtDialog/index.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import { Dialog, DialogSuspense } from 'components';
|
||||||
|
import withDialogRedux from 'components/DialogReduxConnect';
|
||||||
|
import { compose } from 'redux';
|
||||||
|
|
||||||
|
const BadDebtDialogContent = React.lazy(() => import('./BadDebtDialogContent'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad debt dialog.
|
||||||
|
*/
|
||||||
|
function BadDebtDialog({ dialogName, payload: { invoiceId = null }, isOpen }) {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
name={dialogName}
|
||||||
|
title={<T id={'bad_debt.dialog.bad_debt'} />}
|
||||||
|
isOpen={isOpen}
|
||||||
|
canEscapeJeyClose={true}
|
||||||
|
autoFocus={true}
|
||||||
|
className={'dialog--bad-debt'}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<BadDebtDialogContent dialogName={dialogName} invoice={invoiceId} />
|
||||||
|
</DialogSuspense>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default compose(withDialogRedux())(BadDebtDialog);
|
||||||
17
src/containers/Dialogs/BadDebtDialog/utils.js
Normal file
17
src/containers/Dialogs/BadDebtDialog/utils.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the response errors types.
|
||||||
|
*/
|
||||||
|
export const transformErrors = (errors, { setErrors }) => {
|
||||||
|
if (errors.some(({ type }) => type === 'SALE_INVOICE_ALREADY_WRITTEN_OFF')) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'SALE_INVOICE_ALREADY_WRITTEN_OFF',
|
||||||
|
// message: intl.get(''),
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { EasySMSIntegrationProvider } from './EasySMSIntegrationProvider';
|
||||||
|
import EasySMSIntegrationForm from './EasySMSIntegrationForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EasySMS integration dialog content.
|
||||||
|
*/
|
||||||
|
export default function EasySMSIntegrationDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<EasySMSIntegrationProvider dialogName={dialogName}>
|
||||||
|
<EasySMSIntegrationForm />
|
||||||
|
</EasySMSIntegrationProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import '../../../style/pages/EasySMSIntegration/EasySMSIntegration.scss';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
import EasySMSIntegrationFormContent from './EasySMSIntegrationFormContent';
|
||||||
|
import { CreateEasySMSIntegrationSchema } from './EasySMSIntegrationForm.schema';
|
||||||
|
import { useEasySMSIntegration } from './EasySMSIntegrationProvider';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
const defaultInitialValues = {
|
||||||
|
token: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EasySMS Integration form.
|
||||||
|
*/
|
||||||
|
function EasySMSIntegrationForm({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
const { dialogName, easySMSIntegrateMutate } = useEasySMSIntegration();
|
||||||
|
|
||||||
|
// Initial form values.
|
||||||
|
const initialValues = {
|
||||||
|
...defaultInitialValues,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
// Handle request response success.
|
||||||
|
const onSuccess = (response) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('easysms.integrate.dialog.success_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
// Handle request response errors.
|
||||||
|
const onError = ({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
easySMSIntegrateMutate(values).then(onSuccess).catch(onError);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateEasySMSIntegrationSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
component={EasySMSIntegrationFormContent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(EasySMSIntegrationForm);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
token: Yup.string().required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateEasySMSIntegrationSchema = Schema;
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form, FastField, ErrorMessage, useFormikContext } from 'formik';
|
||||||
|
import { Classes, FormGroup, InputGroup, Button } from '@blueprintjs/core';
|
||||||
|
import { FieldRequiredHint, FormattedMessage as T } from 'components';
|
||||||
|
import { useEasySMSIntegration } from './EasySMSIntegrationProvider';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { inputIntent, compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EasySMS Integration form content.
|
||||||
|
*/
|
||||||
|
function EasySMSIntegrationFormContent({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
// Formik context.
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
// EasySMS Integration dialog context.
|
||||||
|
const { dialogName } = useEasySMSIntegration();
|
||||||
|
|
||||||
|
// Handle close button click.
|
||||||
|
const handleCloseBtnClick = () => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
{/*------------ Token -----------*/}
|
||||||
|
<FastField name={'token'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'easysms.integrate.dialog.label.token'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="token" />}
|
||||||
|
className={'form-group--token'}
|
||||||
|
>
|
||||||
|
<InputGroup intent={inputIntent({ error, touched })} {...field} />
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</div>
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
onClick={handleCloseBtnClick}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
>
|
||||||
|
<T id={'close'} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
loading={isSubmitting}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{<T id={'save'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(EasySMSIntegrationFormContent);
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
import { useSettingEasySMSIntegrate } from 'hooks/query';
|
||||||
|
|
||||||
|
const EasySMSIntegrationDialogContext = React.createContext();
|
||||||
|
/**
|
||||||
|
* Easy SMS integration dialog provider.
|
||||||
|
*/
|
||||||
|
function EasySMSIntegrationProvider({ dialogName, ...props }) {
|
||||||
|
// easysms integrate mutations.
|
||||||
|
const { mutateAsync: easySMSIntegrateMutate } = useSettingEasySMSIntegrate();
|
||||||
|
|
||||||
|
// State provider.
|
||||||
|
const provider = {
|
||||||
|
dialogName,
|
||||||
|
easySMSIntegrateMutate,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent>
|
||||||
|
<EasySMSIntegrationDialogContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useEasySMSIntegration = () =>
|
||||||
|
React.useContext(EasySMSIntegrationDialogContext);
|
||||||
|
|
||||||
|
export { EasySMSIntegrationProvider, useEasySMSIntegration };
|
||||||
29
src/containers/Dialogs/EasySMSIntegrationDialog/index.js
Normal file
29
src/containers/Dialogs/EasySMSIntegrationDialog/index.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Dialog, DialogSuspense, FormattedMessage as T } from 'components';
|
||||||
|
import withDialogRedux from 'components/DialogReduxConnect';
|
||||||
|
import { compose } from 'redux';
|
||||||
|
|
||||||
|
const EasySMSIntegrationDialogContent = React.lazy(() =>
|
||||||
|
import('./EasySMSIntegrationDialogContent'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EasySms integration dialog.
|
||||||
|
*/
|
||||||
|
function EasySMSIntegrationDialog({ dialogName, payload = {}, isOpen }) {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
name={dialogName}
|
||||||
|
title={'EasySMS Inegration'}
|
||||||
|
isOpen={isOpen}
|
||||||
|
canEscapeJeyClose={true}
|
||||||
|
autoFocus={true}
|
||||||
|
className={'dialog--easysms-integrate'}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<EasySMSIntegrationDialogContent dialogName={dialogName} />
|
||||||
|
</DialogSuspense>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default compose(withDialogRedux())(EasySMSIntegrationDialog);
|
||||||
@@ -2,10 +2,8 @@ import * as Yup from 'yup';
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
const Schema = Yup.object().shape({
|
const Schema = Yup.object().shape({
|
||||||
email: Yup.string()
|
email: Yup.string().email().required().label(intl.get('email')),
|
||||||
.email()
|
role_id: Yup.string().required().label(intl.get('roles.label.role_name_')),
|
||||||
.required()
|
|
||||||
.label(intl.get('email')),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const InviteUserFormSchema = Schema;
|
export const InviteUserFormSchema = Schema;
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormGroup, InputGroup, Intent, Button } from '@blueprintjs/core';
|
import { FormGroup, InputGroup, Intent, Button } from '@blueprintjs/core';
|
||||||
import { FastField, Form, useFormikContext, ErrorMessage } from 'formik';
|
import { FastField, Form, useFormikContext, ErrorMessage } from 'formik';
|
||||||
import { FormattedMessage as T } from 'components';
|
import {
|
||||||
|
ListSelect,
|
||||||
|
FieldRequiredHint,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { inputIntent } from 'utils';
|
import { inputIntent } from 'utils';
|
||||||
@@ -15,23 +19,24 @@ function InviteUserFormContent({
|
|||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
const { isEditMode, dialogName } = useInviteUserFormContext();
|
const { isEditMode, dialogName, roles } = useInviteUserFormContext();
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
};
|
};
|
||||||
|
console.log(roles, 'XX');
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<div className={CLASSES.DIALOG_BODY}>
|
<div className={CLASSES.DIALOG_BODY}>
|
||||||
<p className="mb2">
|
<p className="mb2">
|
||||||
<T id={'your_access_to_your_team'} />
|
<T id={'your_access_to_your_team'} />
|
||||||
</p>
|
</p>
|
||||||
|
{/* ----------- Email ----------- */}
|
||||||
<FastField name={'email'}>
|
<FastField name={'email'}>
|
||||||
{({ field, meta: { error, touched } }) => (
|
{({ field, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'email'} />}
|
label={<T id={'email'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={classNames('form-group--email', CLASSES.FILL)}
|
className={classNames('form-group--email', CLASSES.FILL)}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name="email" />}
|
helperText={<ErrorMessage name="email" />}
|
||||||
@@ -40,6 +45,31 @@ function InviteUserFormContent({
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</FastField>
|
||||||
|
{/* ----------- Role name ----------- */}
|
||||||
|
<FastField name={'role_id'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'roles.label.role_name'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
helperText={<ErrorMessage name="role_id" />}
|
||||||
|
className={classNames(CLASSES.FILL, 'form-group--role_name')}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
>
|
||||||
|
<ListSelect
|
||||||
|
items={roles}
|
||||||
|
onItemSelect={({ id }) => {
|
||||||
|
form.setFieldValue('role_id', id);
|
||||||
|
}}
|
||||||
|
selectedItem={value}
|
||||||
|
selectedItemProp={'id'}
|
||||||
|
textProp={'name'}
|
||||||
|
// labelProp={'id '}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={CLASSES.DIALOG_FOOTER}>
|
<div className={CLASSES.DIALOG_FOOTER}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import { useCreateInviteUser, useUsers } from 'hooks/query';
|
import { useCreateInviteUser, useUsers, useRoles } from 'hooks/query';
|
||||||
import { DialogContent } from 'components';
|
import { DialogContent } from 'components';
|
||||||
|
|
||||||
const InviteUserFormContext = createContext();
|
const InviteUserFormContext = createContext();
|
||||||
@@ -14,6 +14,9 @@ function InviteUserFormProvider({ userId, isEditMode, dialogName, ...props }) {
|
|||||||
// fetch users list.
|
// fetch users list.
|
||||||
const { isLoading: isUsersLoading } = useUsers();
|
const { isLoading: isUsersLoading } = useUsers();
|
||||||
|
|
||||||
|
// fetch roles list.
|
||||||
|
const { data: roles, isLoading: isRolesLoading } = useRoles();
|
||||||
|
|
||||||
// Provider state.
|
// Provider state.
|
||||||
const provider = {
|
const provider = {
|
||||||
inviteUserMutate,
|
inviteUserMutate,
|
||||||
@@ -21,10 +24,14 @@ function InviteUserFormProvider({ userId, isEditMode, dialogName, ...props }) {
|
|||||||
userId,
|
userId,
|
||||||
isUsersLoading,
|
isUsersLoading,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
|
roles,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent isLoading={isUsersLoading} name={'invite-form'}>
|
<DialogContent
|
||||||
|
isLoading={isUsersLoading || isRolesLoading}
|
||||||
|
name={'invite-form'}
|
||||||
|
>
|
||||||
<InviteUserFormContext.Provider value={provider} {...props} />
|
<InviteUserFormContext.Provider value={provider} {...props} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ function MoneyInDialog({
|
|||||||
payload = { account_type: null, account_id: null, account_name: '' },
|
payload = { account_type: null, account_id: null, account_name: '' },
|
||||||
isOpen,
|
isOpen,
|
||||||
}) {
|
}) {
|
||||||
console.log(payload, 'EE');
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
name={dialogName}
|
name={dialogName}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NotifyEstimateViaSMSFormProvider } from './NotifyEstimateViaSMSFormProvider';
|
||||||
|
import NotifyEstimateViaSMSForm from './NotifyEstimateViaSMSForm';
|
||||||
|
|
||||||
|
export default function NotifyEstimateViaSMSDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
estimate,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<NotifyEstimateViaSMSFormProvider
|
||||||
|
estimateId={estimate}
|
||||||
|
dialogName={dialogName}
|
||||||
|
>
|
||||||
|
<NotifyEstimateViaSMSForm />
|
||||||
|
</NotifyEstimateViaSMSFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import NotifyViaSMSForm from '../../NotifyViaSMS/NotifyViaSMSForm';
|
||||||
|
import { useEstimateViaSMSContext } from './NotifyEstimateViaSMSFormProvider';
|
||||||
|
import { transformErrors } from '../../../containers/NotifyViaSMS/utils';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
const notificationType = {
|
||||||
|
key: 'sale-estimate-details',
|
||||||
|
label: intl.get('sms_notification.estimate_details.type'),
|
||||||
|
};
|
||||||
|
|
||||||
|
function NotifyEstimateViaSMSForm({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
estimateId,
|
||||||
|
dialogName,
|
||||||
|
estimateSMSDetail,
|
||||||
|
createNotifyEstimateBySMSMutate,
|
||||||
|
} = useEstimateViaSMSContext();
|
||||||
|
|
||||||
|
const [calloutCode, setCalloutCode] = React.useState([]);
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
setSubmitting(true);
|
||||||
|
|
||||||
|
// Handle request response success.
|
||||||
|
const onSuccess = (response) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('notify_estimate_via_sms.dialog.success_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
closeDialog(dialogName);
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
// Handle request response errors.
|
||||||
|
const onError = ({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
if (errors) {
|
||||||
|
transformErrors(errors, { setErrors, setCalloutCode });
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
createNotifyEstimateBySMSMutate([estimateId, values])
|
||||||
|
.then(onSuccess)
|
||||||
|
.catch(onError);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
...estimateSMSDetail,
|
||||||
|
notification_key: notificationType.key,
|
||||||
|
};
|
||||||
|
// Handle the form cancel.
|
||||||
|
const handleFormCancel = () => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NotifyViaSMSForm
|
||||||
|
initialValues={initialValues}
|
||||||
|
notificationTypes={[notificationType]}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
calloutCodes={calloutCode}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(NotifyEstimateViaSMSForm);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user