mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-11 02:10:30 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2160c0595 | ||
|
|
956a9b58dd | ||
|
|
acb701d618 | ||
|
|
09ff72d302 | ||
|
|
7375512fec | ||
|
|
77e65389a4 | ||
|
|
1972861c97 | ||
|
|
c47acdee03 | ||
|
|
8689962bf3 | ||
|
|
3258159474 | ||
|
|
36bfa573ad | ||
|
|
2c05785096 | ||
|
|
6af4be9c6c | ||
|
|
8def1d31d2 | ||
|
|
afab02a053 | ||
|
|
8e925c62f2 | ||
|
|
1b7d513adf | ||
|
|
7d764fb390 | ||
|
|
c571f50a74 | ||
|
|
6549026344 | ||
|
|
0963394b04 | ||
|
|
6cab0651fc | ||
|
|
4af537d6dd | ||
|
|
34db64612c | ||
|
|
10225bbfed | ||
|
|
c3a4fe6b37 | ||
|
|
02be959461 |
@@ -1,6 +1,8 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
|
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
|
||||||
import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto';
|
import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto';
|
||||||
|
import { BranchResponseDto } from '@/modules/Branches/dtos/BranchResponse.dto';
|
||||||
import { DiscountType } from '@/common/types/Discount';
|
import { DiscountType } from '@/common/types/Discount';
|
||||||
|
|
||||||
export class BillResponseDto {
|
export class BillResponseDto {
|
||||||
@@ -89,6 +91,14 @@ export class BillResponseDto {
|
|||||||
})
|
})
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Branch details',
|
||||||
|
type: () => BranchResponseDto,
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
@Type(() => BranchResponseDto)
|
||||||
|
branch?: BranchResponseDto;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'The ID of the project',
|
description: 'The ID of the project',
|
||||||
example: 301,
|
example: 301,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export class BillTransformer extends Transformer {
|
|||||||
'taxes',
|
'taxes',
|
||||||
'entries',
|
'entries',
|
||||||
'attachments',
|
'attachments',
|
||||||
|
'branch',
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ import { ValidateBranchExistance } from './integrations/ValidateBranchExistance'
|
|||||||
import { ManualJournalBranchesValidator } from './integrations/ManualJournals/ManualJournalsBranchesValidator';
|
import { ManualJournalBranchesValidator } from './integrations/ManualJournals/ManualJournalsBranchesValidator';
|
||||||
import { CashflowTransactionsActivateBranches } from './integrations/Cashflow/CashflowActivateBranches';
|
import { CashflowTransactionsActivateBranches } from './integrations/Cashflow/CashflowActivateBranches';
|
||||||
import { ExpensesActivateBranches } from './integrations/Expense/ExpensesActivateBranches';
|
import { ExpensesActivateBranches } from './integrations/Expense/ExpensesActivateBranches';
|
||||||
|
import { BillActivateBranches } from './integrations/Purchases/BillBranchesActivate';
|
||||||
|
import { VendorCreditActivateBranches } from './integrations/Purchases/VendorCreditBranchesActivate';
|
||||||
|
import { BillPaymentsActivateBranches } from './integrations/Purchases/PaymentMadeBranchesActivate';
|
||||||
|
import { BillBranchesActivateSubscriber } from './subscribers/Activate/BillBranchesActivateSubscriber';
|
||||||
|
import { VendorCreditBranchesActivateSubscriber } from './subscribers/Activate/VendorCreditBranchesActivateSubscriber';
|
||||||
|
import { PaymentMadeActivateBranchesSubscriber } from './subscribers/Activate/PaymentMadeBranchesActivateSubscriber';
|
||||||
import { FeaturesModule } from '../Features/Features.module';
|
import { FeaturesModule } from '../Features/Features.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -66,7 +72,13 @@ import { FeaturesModule } from '../Features/Features.module';
|
|||||||
ValidateBranchExistance,
|
ValidateBranchExistance,
|
||||||
ManualJournalBranchesValidator,
|
ManualJournalBranchesValidator,
|
||||||
CashflowTransactionsActivateBranches,
|
CashflowTransactionsActivateBranches,
|
||||||
ExpensesActivateBranches
|
ExpensesActivateBranches,
|
||||||
|
BillActivateBranches,
|
||||||
|
VendorCreditActivateBranches,
|
||||||
|
BillPaymentsActivateBranches,
|
||||||
|
BillBranchesActivateSubscriber,
|
||||||
|
VendorCreditBranchesActivateSubscriber,
|
||||||
|
PaymentMadeActivateBranchesSubscriber
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
BranchesSettingsService,
|
BranchesSettingsService,
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
import { Bill } from '@/modules/Bills/models/Bill';
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BillActivateBranches {
|
export class BillActivateBranches {
|
||||||
constructor(private readonly billModel: TenantModelProxy<typeof Bill>) {}
|
constructor(
|
||||||
|
@Inject(Bill.name)
|
||||||
|
private readonly billModel: TenantModelProxy<typeof Bill>,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates all bills transactions with the primary branch.
|
* Updates all bills transactions with the primary branch.
|
||||||
@@ -17,7 +20,7 @@ export class BillActivateBranches {
|
|||||||
primaryBranchId: number,
|
primaryBranchId: number,
|
||||||
trx?: Knex.Transaction,
|
trx?: Knex.Transaction,
|
||||||
) => {
|
) => {
|
||||||
// Updates the sale invoice with primary branch.
|
// Updates the bills with primary branch.
|
||||||
await Bill.query(trx).update({ branchId: primaryBranchId });
|
await this.billModel().query(trx).update({ branchId: primaryBranchId });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
import { BillPayment } from '@/modules/BillPayments/models/BillPayment';
|
import { BillPayment } from '@/modules/BillPayments/models/BillPayment';
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BillPaymentsActivateBranches {
|
export class BillPaymentsActivateBranches {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(BillPayment.name)
|
||||||
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
|
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit';
|
import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VendorCreditActivateBranches {
|
export class VendorCreditActivateBranches {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(VendorCredit.name)
|
||||||
private readonly vendorCreditModel: TenantModelProxy<typeof VendorCredit>,
|
private readonly vendorCreditModel: TenantModelProxy<typeof VendorCredit>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { IBranchesActivatedPayload } from '../../Branches.types';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { BillActivateBranches } from '../../integrations/Purchases/BillBranchesActivate';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BillBranchesActivateSubscriber {
|
||||||
|
constructor(
|
||||||
|
private readonly billActivateBranches: BillActivateBranches,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates bills transactions with the primary branch once
|
||||||
|
* the multi-branches is activated.
|
||||||
|
* @param {IBranchesActivatedPayload}
|
||||||
|
*/
|
||||||
|
@OnEvent(events.branch.onActivated)
|
||||||
|
async updateBillsWithBranchOnActivated({
|
||||||
|
primaryBranch,
|
||||||
|
trx,
|
||||||
|
}: IBranchesActivatedPayload) {
|
||||||
|
await this.billActivateBranches.updateBillsWithBranch(
|
||||||
|
primaryBranch.id,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { IBranchesActivatedPayload } from '../../Branches.types';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { VendorCreditActivateBranches } from '../../integrations/Purchases/VendorCreditBranchesActivate';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class VendorCreditBranchesActivateSubscriber {
|
||||||
|
constructor(
|
||||||
|
private readonly vendorCreditActivateBranches: VendorCreditActivateBranches,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates vendor credits transactions with the primary branch once
|
||||||
|
* the multi-branches is activated.
|
||||||
|
* @param {IBranchesActivatedPayload}
|
||||||
|
*/
|
||||||
|
@OnEvent(events.branch.onActivated)
|
||||||
|
async updateVendorCreditsWithBranchOnActivated({
|
||||||
|
primaryBranch,
|
||||||
|
trx,
|
||||||
|
}: IBranchesActivatedPayload) {
|
||||||
|
await this.vendorCreditActivateBranches.updateVendorCreditsWithBranch(
|
||||||
|
primaryBranch.id,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,14 +24,14 @@ export class BalanceSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
|||||||
displayColumnsType: 'total' | 'date_periods' = 'total';
|
displayColumnsType: 'total' | 'date_periods' = 'total';
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
enum: ['day', 'month', 'year'],
|
enum: ['day', 'month', 'year', 'quarter'],
|
||||||
default: 'year',
|
default: 'year',
|
||||||
description: 'Time period for column display',
|
description: 'Time period for column display',
|
||||||
})
|
})
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsEnum(['day', 'month', 'year'])
|
@IsEnum(['day', 'month', 'year', 'quarter'])
|
||||||
displayColumnsBy: 'day' | 'month' | 'year' = 'year';
|
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year';
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Start date for the balance sheet period',
|
description: 'Start date for the balance sheet period',
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ export class CashFlowStatementQueryDto extends FinancialSheetBranchesQueryDto {
|
|||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Display columns by time period',
|
description: 'Display columns by time period',
|
||||||
required: false,
|
required: false,
|
||||||
enum: ['day', 'month', 'year'],
|
enum: ['day', 'month', 'year', 'quarter'],
|
||||||
default: 'year',
|
default: 'year',
|
||||||
})
|
})
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsEnum(['day', 'month', 'year'])
|
@IsEnum(['day', 'month', 'year', 'quarter'])
|
||||||
displayColumnsBy: 'day' | 'month' | 'year' = 'year';
|
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year';
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Type of column display',
|
description: 'Type of column display',
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ export class ProfitLossSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
|||||||
displayColumnsType: 'total' | 'date_periods';
|
displayColumnsType: 'total' | 'date_periods';
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsEnum(['day', 'month', 'year'])
|
@IsEnum(['day', 'month', 'year', 'quarter'])
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ApiProperty({ description: 'How to display columns' })
|
@ApiProperty({ description: 'How to display columns' })
|
||||||
displayColumnsBy: 'day' | 'month' | 'year' = 'year';
|
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year';
|
||||||
|
|
||||||
@Transform(({ value }) => parseBoolean(value, false))
|
@Transform(({ value }) => parseBoolean(value, false))
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
BulkDeleteItemsDto,
|
BulkDeleteItemsDto,
|
||||||
ValidateBulkDeleteItemsResponseDto,
|
ValidateBulkDeleteItemsResponseDto,
|
||||||
} from './dtos/BulkDeleteItems.dto';
|
} from './dtos/BulkDeleteItems.dto';
|
||||||
|
import { ItemApiErrorResponseDto } from './dtos/ItemErrorResponse.dto';
|
||||||
|
|
||||||
@Controller('/items')
|
@Controller('/items')
|
||||||
@ApiTags('Items')
|
@ApiTags('Items')
|
||||||
@@ -45,6 +46,7 @@ import {
|
|||||||
@ApiExtraModels(ItemEstimatesResponseDto)
|
@ApiExtraModels(ItemEstimatesResponseDto)
|
||||||
@ApiExtraModels(ItemReceiptsResponseDto)
|
@ApiExtraModels(ItemReceiptsResponseDto)
|
||||||
@ApiExtraModels(ValidateBulkDeleteItemsResponseDto)
|
@ApiExtraModels(ValidateBulkDeleteItemsResponseDto)
|
||||||
|
@ApiExtraModels(ItemApiErrorResponseDto)
|
||||||
@ApiCommonHeaders()
|
@ApiCommonHeaders()
|
||||||
export class ItemsController extends TenantController {
|
export class ItemsController extends TenantController {
|
||||||
constructor(private readonly itemsApplication: ItemsApplicationService) {
|
constructor(private readonly itemsApplication: ItemsApplicationService) {
|
||||||
@@ -147,6 +149,13 @@ export class ItemsController extends TenantController {
|
|||||||
status: 200,
|
status: 200,
|
||||||
description: 'The item has been successfully updated.',
|
description: 'The item has been successfully updated.',
|
||||||
})
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 400,
|
||||||
|
description: 'Validation error. Possible error types: ITEM_NAME_EXISTS, INVENTORY_ACCOUNT_CANNOT_MODIFIED, TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS, etc.',
|
||||||
|
schema: {
|
||||||
|
$ref: getSchemaPath(ItemApiErrorResponseDto),
|
||||||
|
},
|
||||||
|
})
|
||||||
@ApiResponse({ status: 404, description: 'The item not found.' })
|
@ApiResponse({ status: 404, description: 'The item not found.' })
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
@@ -204,6 +213,13 @@ export class ItemsController extends TenantController {
|
|||||||
status: 200,
|
status: 200,
|
||||||
description: 'The item has been successfully created.',
|
description: 'The item has been successfully created.',
|
||||||
})
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 400,
|
||||||
|
description: 'Validation error. Possible error types: ITEM_NAME_EXISTS, ITEM_CATEOGRY_NOT_FOUND, COST_ACCOUNT_NOT_COGS, SELL_ACCOUNT_NOT_INCOME, INVENTORY_ACCOUNT_NOT_INVENTORY, INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM, COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM, etc.',
|
||||||
|
schema: {
|
||||||
|
$ref: getSchemaPath(ItemApiErrorResponseDto),
|
||||||
|
},
|
||||||
|
})
|
||||||
// @UsePipes(new ZodValidationPipe(createItemSchema))
|
// @UsePipes(new ZodValidationPipe(createItemSchema))
|
||||||
async createItem(
|
async createItem(
|
||||||
@Body() createItemDto: CreateItemDto,
|
@Body() createItemDto: CreateItemDto,
|
||||||
@@ -219,6 +235,13 @@ export class ItemsController extends TenantController {
|
|||||||
status: 200,
|
status: 200,
|
||||||
description: 'The item has been successfully deleted.',
|
description: 'The item has been successfully deleted.',
|
||||||
})
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 400,
|
||||||
|
description: 'Cannot delete item. Possible error types: ITEM_HAS_ASSOCIATED_TRANSACTINS, ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT, etc.',
|
||||||
|
schema: {
|
||||||
|
$ref: getSchemaPath(ItemApiErrorResponseDto),
|
||||||
|
},
|
||||||
|
})
|
||||||
@ApiResponse({ status: 404, description: 'The item not found.' })
|
@ApiResponse({ status: 404, description: 'The item not found.' })
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
|||||||
112
packages/server/src/modules/Items/dtos/ItemErrorResponse.dto.ts
Normal file
112
packages/server/src/modules/Items/dtos/ItemErrorResponse.dto.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item API Error Types
|
||||||
|
* These error types are returned when item operations fail validation
|
||||||
|
*/
|
||||||
|
export enum ItemErrorType {
|
||||||
|
/** Item name already exists in the system */
|
||||||
|
ItemNameExists = 'ITEM_NAME_EXISTS',
|
||||||
|
|
||||||
|
/** Item category was not found */
|
||||||
|
ItemCategoryNotFound = 'ITEM_CATEOGRY_NOT_FOUND',
|
||||||
|
|
||||||
|
/** Cost account is not a Cost of Goods Sold account */
|
||||||
|
CostAccountNotCogs = 'COST_ACCOUNT_NOT_COGS',
|
||||||
|
|
||||||
|
/** Cost account was not found */
|
||||||
|
CostAccountNotFound = 'COST_ACCOUNT_NOT_FOUMD',
|
||||||
|
|
||||||
|
/** Sell account was not found */
|
||||||
|
SellAccountNotFound = 'SELL_ACCOUNT_NOT_FOUND',
|
||||||
|
|
||||||
|
/** Sell account is not an income account */
|
||||||
|
SellAccountNotIncome = 'SELL_ACCOUNT_NOT_INCOME',
|
||||||
|
|
||||||
|
/** Inventory account was not found */
|
||||||
|
InventoryAccountNotFound = 'INVENTORY_ACCOUNT_NOT_FOUND',
|
||||||
|
|
||||||
|
/** Account is not an inventory type account */
|
||||||
|
InventoryAccountNotInventory = 'INVENTORY_ACCOUNT_NOT_INVENTORY',
|
||||||
|
|
||||||
|
/** Multiple items have associated transactions */
|
||||||
|
ItemsHaveAssociatedTransactions = 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS',
|
||||||
|
|
||||||
|
/** Item has associated transactions (singular) */
|
||||||
|
ItemHasAssociatedTransactions = 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
|
||||||
|
|
||||||
|
/** Item has associated inventory adjustments */
|
||||||
|
ItemHasAssociatedInventoryAdjustment = 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
||||||
|
|
||||||
|
/** Cannot change item type to inventory */
|
||||||
|
ItemCannotChangeInventoryType = 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE',
|
||||||
|
|
||||||
|
/** Cannot change type when item has transactions */
|
||||||
|
TypeCannotChangeWithItemHasTransactions = 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
||||||
|
|
||||||
|
/** Inventory account cannot be modified */
|
||||||
|
InventoryAccountCannotModified = 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
|
||||||
|
|
||||||
|
/** Purchase tax rate was not found */
|
||||||
|
PurchaseTaxRateNotFound = 'PURCHASE_TAX_RATE_NOT_FOUND',
|
||||||
|
|
||||||
|
/** Sell tax rate was not found */
|
||||||
|
SellTaxRateNotFound = 'SELL_TAX_RATE_NOT_FOUND',
|
||||||
|
|
||||||
|
/** Income account is required for sellable items */
|
||||||
|
IncomeAccountRequiredWithSellableItem = 'INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM',
|
||||||
|
|
||||||
|
/** Cost account is required for purchasable items */
|
||||||
|
CostAccountRequiredWithPurchasableItem = 'COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM',
|
||||||
|
|
||||||
|
/** Item not found */
|
||||||
|
NotFound = 'NOT_FOUND',
|
||||||
|
|
||||||
|
/** Items not found */
|
||||||
|
ItemsNotFound = 'ITEMS_NOT_FOUND',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item API Error Response
|
||||||
|
* Returned when an item operation fails
|
||||||
|
*/
|
||||||
|
export class ItemErrorResponseDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'HTTP status code',
|
||||||
|
example: 400,
|
||||||
|
})
|
||||||
|
statusCode: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Error type identifier',
|
||||||
|
enum: ItemErrorType,
|
||||||
|
example: ItemErrorType.ItemNameExists,
|
||||||
|
})
|
||||||
|
type: ItemErrorType;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Human-readable error message',
|
||||||
|
example: 'The item name is already exist.',
|
||||||
|
required: false,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
message: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Additional error payload data',
|
||||||
|
required: false,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
payload: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item API Error Response Wrapper
|
||||||
|
*/
|
||||||
|
export class ItemApiErrorResponseDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Array of error details',
|
||||||
|
type: [ItemErrorResponseDto],
|
||||||
|
})
|
||||||
|
errors: ItemErrorResponseDto[];
|
||||||
|
}
|
||||||
@@ -70,6 +70,16 @@ export class Item extends TenantBaseModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model search roles.
|
||||||
|
*/
|
||||||
|
static get searchRoles() {
|
||||||
|
return [
|
||||||
|
{ condition: 'or', fieldKey: 'name', comparator: 'contains' },
|
||||||
|
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
HttpCode,
|
HttpCode,
|
||||||
Param,
|
Param,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { Throttle } from '@nestjs/throttler';
|
||||||
import { BuildOrganizationService } from './commands/BuildOrganization.service';
|
import { BuildOrganizationService } from './commands/BuildOrganization.service';
|
||||||
import {
|
import {
|
||||||
BuildOrganizationDto,
|
BuildOrganizationDto,
|
||||||
@@ -50,7 +51,7 @@ export class OrganizationController {
|
|||||||
private readonly updateOrganizationService: UpdateOrganizationService,
|
private readonly updateOrganizationService: UpdateOrganizationService,
|
||||||
private readonly getBuildOrganizationJobService: GetBuildOrganizationBuildJob,
|
private readonly getBuildOrganizationJobService: GetBuildOrganizationBuildJob,
|
||||||
private readonly orgBaseCurrencyLockingService: OrganizationBaseCurrencyLocking,
|
private readonly orgBaseCurrencyLockingService: OrganizationBaseCurrencyLocking,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
@Post('build')
|
@Post('build')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
@@ -77,6 +78,7 @@ export class OrganizationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('build/:buildJobId')
|
@Get('build/:buildJobId')
|
||||||
|
@Throttle({ default: { limit: 300, ttl: 60000 } }) // 300 req/min
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: 'buildJobId',
|
name: 'buildJobId',
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { Controller, Get, Post } from '@nestjs/common';
|
import { Controller, Get, HttpCode } from '@nestjs/common';
|
||||||
|
import { PublicRoute } from '@/modules/Auth/guards/jwt.guard';
|
||||||
|
|
||||||
@Controller('/system_db')
|
@Controller('system_db')
|
||||||
|
@PublicRoute()
|
||||||
export class SystemDatabaseController {
|
export class SystemDatabaseController {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@Post()
|
|
||||||
@Get()
|
@Get()
|
||||||
ping(){
|
@HttpCode(200)
|
||||||
|
ping() {
|
||||||
|
return { status: 'ok' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
SystemKnexConnectionConfigure,
|
SystemKnexConnectionConfigure,
|
||||||
} from './SystemDB.constants';
|
} from './SystemDB.constants';
|
||||||
import { knexSnakeCaseMappers } from 'objection';
|
import { knexSnakeCaseMappers } from 'objection';
|
||||||
|
import { SystemDatabaseController } from './SystemDB.controller';
|
||||||
|
|
||||||
const providers = [
|
const providers = [
|
||||||
{
|
{
|
||||||
@@ -42,6 +43,7 @@ const providers = [
|
|||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
|
controllers: [SystemDatabaseController],
|
||||||
providers: [...providers],
|
providers: [...providers],
|
||||||
exports: [...providers],
|
exports: [...providers],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import styled from 'styled-components';
|
|||||||
import { DataTable } from '../Datatable';
|
import { DataTable } from '../Datatable';
|
||||||
|
|
||||||
export const ReportDataTable = styled(DataTable)`
|
export const ReportDataTable = styled(DataTable)`
|
||||||
|
--x-table-no-results-border-color: #ddd;
|
||||||
|
|
||||||
|
.bp4-dark & {
|
||||||
|
--x-table-no-results-border-color: var(--color-dark-gray5);
|
||||||
|
}
|
||||||
.table .tbody .tr.no-results:last-of-type .td {
|
.table .tbody .tr.no-results:last-of-type .td {
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid var(--x-table-no-results-border-color);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from '@/components';
|
import { FormattedMessage as T } from '@/components';
|
||||||
import preferencesMenu from '@/constants/preferencesMenu';
|
import { PreferencesMenu } from '@/constants/preferencesMenu';
|
||||||
import PreferencesSidebarContainer from './PreferencesSidebarContainer';
|
import PreferencesSidebarContainer from './PreferencesSidebarContainer';
|
||||||
|
|
||||||
import '@/style/pages/Preferences/Sidebar.scss';
|
import '@/style/pages/Preferences/Sidebar.scss';
|
||||||
@@ -15,7 +15,7 @@ export default function PreferencesSidebar() {
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const items = preferencesMenu.map((item) =>
|
const items = PreferencesMenu.map((item) =>
|
||||||
item.divider ? (
|
item.divider ? (
|
||||||
<MenuDivider title={item.title} />
|
<MenuDivider title={item.title} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -10,12 +10,17 @@ const TextStatusRoot = styled.span`
|
|||||||
${(props) =>
|
${(props) =>
|
||||||
props.intent === 'warning' &&
|
props.intent === 'warning' &&
|
||||||
`
|
`
|
||||||
color: #ec5b0a;`}
|
color: #c87619;`}
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.intent === 'danger' &&
|
||||||
|
`
|
||||||
|
color: #f17377;`}
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.intent === 'success' &&
|
props.intent === 'success' &&
|
||||||
`
|
`
|
||||||
color: #2ba01d;`}
|
color: #238551;`}
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.intent === 'none' &&
|
props.intent === 'none' &&
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// @ts-nocheck
|
import React, { KeyboardEvent, ReactNode } from 'react';
|
||||||
import React from 'react';
|
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { isUndefined } from 'lodash';
|
import { isUndefined } from 'lodash';
|
||||||
import {
|
import {
|
||||||
Overlay,
|
Overlay,
|
||||||
@@ -10,11 +8,14 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Spinner,
|
Spinner,
|
||||||
Intent,
|
Intent,
|
||||||
|
OverlayProps,
|
||||||
|
Button,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { QueryList } from '@blueprintjs/select';
|
import { QueryList, ItemRenderer } from '@blueprintjs/select';
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { x } from '@xstyled/emotion';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
import { Icon, If, ListSelect, FormattedMessage as T } from '@/components';
|
import { Icon, If, FormattedMessage as T } from '@/components';
|
||||||
|
import { Select } from '@blueprintjs-formik/select';
|
||||||
import {
|
import {
|
||||||
UniversalSearchProvider,
|
UniversalSearchProvider,
|
||||||
useUniversalSearchContext,
|
useUniversalSearchContext,
|
||||||
@@ -22,59 +23,297 @@ import {
|
|||||||
import { filterItemsByResourceType } from './utils';
|
import { filterItemsByResourceType } from './utils';
|
||||||
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
||||||
|
|
||||||
|
// Resource type from RESOURCES_TYPES constant
|
||||||
|
type ResourceType = string;
|
||||||
|
|
||||||
|
// Search type option item
|
||||||
|
interface SearchTypeOption {
|
||||||
|
key: ResourceType;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Universal search item
|
||||||
|
interface UniversalSearchItem {
|
||||||
|
id: number | string;
|
||||||
|
_type: ResourceType;
|
||||||
|
text: string;
|
||||||
|
subText?: string;
|
||||||
|
label?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSS styles for complex selectors
|
||||||
|
const overlayStyles = css`
|
||||||
|
.bp4-overlay-appear,
|
||||||
|
.bp4-overlay-enter {
|
||||||
|
filter: blur(20px);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
.bp4-overlay-appear-active,
|
||||||
|
.bp4-overlay-enter-active {
|
||||||
|
filter: blur(0);
|
||||||
|
opacity: 1;
|
||||||
|
transition:
|
||||||
|
filter 0.2s cubic-bezier(0.4, 1, 0.75, 0.9),
|
||||||
|
opacity 0.2s cubic-bezier(0.4, 1, 0.75, 0.9);
|
||||||
|
}
|
||||||
|
.bp4-overlay-exit {
|
||||||
|
filter: blur(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.bp4-overlay-exit-active {
|
||||||
|
filter: blur(20px);
|
||||||
|
opacity: 0.2;
|
||||||
|
transition:
|
||||||
|
filter 0.2s cubic-bezier(0.4, 1, 0.75, 0.9),
|
||||||
|
opacity 0.2s cubic-bezier(0.4, 1, 0.75, 0.9);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const containerStyles = css`
|
||||||
|
position: fixed;
|
||||||
|
filter: blur(0);
|
||||||
|
opacity: 1;
|
||||||
|
background-color: var(--color-universal-search-background);
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(16, 22, 26, 0.1),
|
||||||
|
0 4px 8px rgba(16, 22, 26, 0.2),
|
||||||
|
0 18px 46px 6px rgba(16, 22, 26, 0.2);
|
||||||
|
left: calc(50% - 250px);
|
||||||
|
top: 20vh;
|
||||||
|
width: 500px;
|
||||||
|
z-index: 20;
|
||||||
|
|
||||||
|
.bp4-input-group {
|
||||||
|
.bp4-icon {
|
||||||
|
margin: 16px;
|
||||||
|
color: var(--color-universal-search-icon);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: currentColor;
|
||||||
|
fill: none;
|
||||||
|
fill-rule: evenodd;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-width: 2;
|
||||||
|
--text-opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp4-input-group .bp4-input {
|
||||||
|
border: 0;
|
||||||
|
box-shadow: 0 0 0 0;
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.bp4-input-group.bp4-large .bp4-input:not(:first-child) {
|
||||||
|
padding-left: 50px !important;
|
||||||
|
}
|
||||||
|
.bp4-input-group.bp4-large .bp4-input:not(:last-child) {
|
||||||
|
padding-right: 130px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp4-menu {
|
||||||
|
border-top: 1px solid var(--color-universal-search-menu-border);
|
||||||
|
max-height: calc(60vh - 20px);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.bp4-menu-item {
|
||||||
|
.bp4-text-muted {
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.bp4-icon {
|
||||||
|
color: var(--bp4-gray-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.bp4-intent-primary {
|
||||||
|
&.bp4-active {
|
||||||
|
background-color: var(--bp4-blue-100);
|
||||||
|
color: var(--bp4-dark-gray-800);
|
||||||
|
|
||||||
|
.bp4-menu-item-label {
|
||||||
|
color: var(--bp4-gray-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
flex-direction: row;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp4-input-action {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const inputRightElementsStyles = css`
|
||||||
|
display: flex;
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
.bp4-spinner {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const footerStyles = css`
|
||||||
|
padding: 12px 12px;
|
||||||
|
border-top: 1px solid var(--color-universal-search-footer-divider);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const actionBaseStyles = css`
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin-left: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp4-tag {
|
||||||
|
background: var(--color-universal-search-tag-background);
|
||||||
|
color: var(--color-universal-search-tag-text);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const actionArrowsStyles = css`
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin-left: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp4-tag {
|
||||||
|
background: var(--color-universal-search-tag-background);
|
||||||
|
color: var(--color-universal-search-tag-text);
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 16px;
|
||||||
|
margin-left: 4px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--color-universal-search-tag-text);
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// UniversalSearchInputRightElements props
|
||||||
|
interface UniversalSearchInputRightElementsProps {
|
||||||
|
/** Callback when search type changes */
|
||||||
|
onSearchTypeChange?: (option: SearchTypeOption) => void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Universal search input action.
|
* Universal search input action.
|
||||||
*/
|
*/
|
||||||
function UniversalSearchInputRightElements({ onSearchTypeChange }) {
|
function UniversalSearchInputRightElements({
|
||||||
const { isLoading, searchType, defaultSearchResource, searchTypeOptions } =
|
onSearchTypeChange,
|
||||||
|
}: UniversalSearchInputRightElementsProps) {
|
||||||
|
const { isLoading, searchType, searchTypeOptions } =
|
||||||
useUniversalSearchContext();
|
useUniversalSearchContext();
|
||||||
|
|
||||||
|
// Find the currently selected item object.
|
||||||
|
const selectedItem = searchTypeOptions.find(
|
||||||
|
(item) => item.key === searchType,
|
||||||
|
);
|
||||||
|
|
||||||
// Handle search type option change.
|
// Handle search type option change.
|
||||||
const handleSearchTypeChange = (option) => {
|
const handleSearchTypeChange = (option: SearchTypeOption) => {
|
||||||
onSearchTypeChange && onSearchTypeChange(option);
|
onSearchTypeChange?.(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Item renderer for the select dropdown.
|
||||||
|
const itemRenderer: ItemRenderer<SearchTypeOption> = (
|
||||||
|
item,
|
||||||
|
{ handleClick },
|
||||||
|
) => {
|
||||||
|
return <MenuItem text={item.label} key={item.key} onClick={handleClick} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={CLASSES.UNIVERSAL_SEARCH_INPUT_RIGHT_ELEMENTS}>
|
<x.div display="flex" m="10px" className={inputRightElementsStyles}>
|
||||||
<If condition={isLoading}>
|
<If condition={isLoading}>
|
||||||
<Spinner tagName="div" intent={Intent.NONE} size={18} value={null} />
|
<Spinner tagName="div" intent={Intent.NONE} size={18} />
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
<ListSelect
|
<Select<SearchTypeOption>
|
||||||
items={searchTypeOptions}
|
items={searchTypeOptions}
|
||||||
|
itemRenderer={itemRenderer}
|
||||||
onItemSelect={handleSearchTypeChange}
|
onItemSelect={handleSearchTypeChange}
|
||||||
|
selectedValue={selectedItem?.key}
|
||||||
|
valueAccessor={'key'}
|
||||||
|
labelAccessor={'label'}
|
||||||
filterable={false}
|
filterable={false}
|
||||||
initialSelectedItem={defaultSearchResource}
|
|
||||||
selectedItem={searchType}
|
|
||||||
selectedItemProp={'key'}
|
|
||||||
textProp={'label'}
|
|
||||||
// defaultText={intl.get('type')}
|
|
||||||
popoverProps={{
|
popoverProps={{
|
||||||
minimal: true,
|
minimal: true,
|
||||||
captureDismiss: true,
|
captureDismiss: true,
|
||||||
className: CLASSES.UNIVERSAL_SEARCH_TYPE_SELECT_OVERLAY,
|
|
||||||
}}
|
|
||||||
buttonProps={{
|
|
||||||
minimal: true,
|
|
||||||
className: CLASSES.UNIVERSAL_SEARCH_TYPE_SELECT_BTN,
|
|
||||||
}}
|
}}
|
||||||
|
input={({ activeItem }) => (
|
||||||
|
<Button minimal={true} text={activeItem?.label} />
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</x.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryList renderer props
|
||||||
|
interface QueryListRendererProps {
|
||||||
|
/** Current query string */
|
||||||
|
query: string;
|
||||||
|
/** Callback when query changes */
|
||||||
|
handleQueryChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
/** Item list element */
|
||||||
|
itemList: ReactNode;
|
||||||
|
/** Class name */
|
||||||
|
className?: string;
|
||||||
|
/** Handle key down */
|
||||||
|
handleKeyDown?: (event: KeyboardEvent<HTMLDivElement>) => void;
|
||||||
|
/** Handle key up */
|
||||||
|
handleKeyUp?: (event: KeyboardEvent<HTMLDivElement>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniversalSearchQueryList props
|
||||||
|
interface UniversalSearchQueryListProps {
|
||||||
|
/** Whether the search is open */
|
||||||
|
isOpen: boolean;
|
||||||
|
/** Whether the search is loading */
|
||||||
|
isLoading: boolean;
|
||||||
|
/** Callback when search type changes */
|
||||||
|
onSearchTypeChange?: (option: SearchTypeOption) => void;
|
||||||
|
/** Current search type */
|
||||||
|
searchType: ResourceType;
|
||||||
|
/** Items to display */
|
||||||
|
items: UniversalSearchItem[];
|
||||||
|
/** Renderer for items */
|
||||||
|
itemRenderer?: ItemRenderer<UniversalSearchItem>;
|
||||||
|
/** Callback when an item is selected */
|
||||||
|
onItemSelect?: (item: UniversalSearchItem, event?: any) => void;
|
||||||
|
/** Current query string */
|
||||||
|
query: string;
|
||||||
|
/** Callback when query changes */
|
||||||
|
onQueryChange?: (query: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Universal search query list.
|
* Universal search query list.
|
||||||
*/
|
*/
|
||||||
function UniversalSearchQueryList(props) {
|
function UniversalSearchQueryList({
|
||||||
const { isOpen, isLoading, onSearchTypeChange, searchType, ...restProps } =
|
isOpen,
|
||||||
props;
|
isLoading,
|
||||||
|
onSearchTypeChange,
|
||||||
|
...restProps
|
||||||
|
}: UniversalSearchQueryListProps) {
|
||||||
return (
|
return (
|
||||||
<QueryList
|
<QueryList<UniversalSearchItem>
|
||||||
{...restProps}
|
{...(restProps as any)}
|
||||||
initialContent={null}
|
initialContent={null}
|
||||||
renderer={(listProps) => (
|
renderer={(listProps: QueryListRendererProps) => (
|
||||||
<UniversalSearchBar
|
<UniversalSearchBar
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onSearchTypeChange={onSearchTypeChange}
|
onSearchTypeChange={onSearchTypeChange}
|
||||||
@@ -100,47 +339,53 @@ function UniversalSearchQueryList(props) {
|
|||||||
*/
|
*/
|
||||||
function UniversalQuerySearchActions() {
|
function UniversalQuerySearchActions() {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTIONS)}>
|
<x.div display="flex">
|
||||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_SELECT)}>
|
<x.div className={actionBaseStyles}>
|
||||||
<Tag>ENTER</Tag>
|
<Tag>ENTER</Tag>
|
||||||
<span class={'text'}>{intl.get('universal_search.enter_text')}</span>
|
<x.span ml="6px">{intl.get('universal_search.enter_text')}</x.span>
|
||||||
</div>
|
</x.div>
|
||||||
|
|
||||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_CLOSE)}>
|
<x.div className={actionBaseStyles}>
|
||||||
<Tag>ESC</Tag>{' '}
|
<Tag>ESC</Tag>{' '}
|
||||||
<span class={'text'}>{intl.get('universal_search.close_text')}</span>
|
<x.span ml="6px">{intl.get('universal_search.close_text')}</x.span>
|
||||||
</div>
|
</x.div>
|
||||||
|
|
||||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_ARROWS)}>
|
<x.div className={actionArrowsStyles}>
|
||||||
<Tag>
|
<Tag>
|
||||||
<Icon icon={'arrow-up-24'} iconSize={16} />
|
<Icon icon={'arrow-up-24'} iconSize={16} />
|
||||||
</Tag>
|
</Tag>
|
||||||
<Tag>
|
<Tag>
|
||||||
<Icon icon={'arrow-down-24'} iconSize={16} />
|
<Icon icon={'arrow-down-24'} iconSize={16} />
|
||||||
</Tag>
|
</Tag>
|
||||||
<span class="text">{intl.get('universal_seach.navigate_text')}</span>
|
<x.span ml="6px">{intl.get('universal_seach.navigate_text')}</x.span>
|
||||||
</div>
|
</x.div>
|
||||||
</div>
|
</x.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UniversalSearchBar props
|
||||||
|
interface UniversalSearchBarProps extends QueryListRendererProps {
|
||||||
|
/** Whether the search is open */
|
||||||
|
isOpen: boolean;
|
||||||
|
/** Callback when search type changes */
|
||||||
|
onSearchTypeChange?: (option: SearchTypeOption) => void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Universal search input bar with items list.
|
* Universal search input bar with items list.
|
||||||
*/
|
*/
|
||||||
function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
|
function UniversalSearchBar({
|
||||||
|
isOpen,
|
||||||
|
onSearchTypeChange,
|
||||||
|
...listProps
|
||||||
|
}: UniversalSearchBarProps) {
|
||||||
const { handleKeyDown, handleKeyUp } = listProps;
|
const { handleKeyDown, handleKeyUp } = listProps;
|
||||||
const handlers = isOpen
|
const handlers = isOpen
|
||||||
? { onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }
|
? { onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<x.div {...handlers}>
|
||||||
className={classNames(
|
|
||||||
CLASSES.UNIVERSAL_SEARCH_OMNIBAR,
|
|
||||||
listProps.className,
|
|
||||||
)}
|
|
||||||
{...handlers}
|
|
||||||
>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
large={true}
|
large={true}
|
||||||
leftIcon={<Icon icon={'universal-search'} iconSize={20} />}
|
leftIcon={<Icon icon={'universal-search'} iconSize={20} />}
|
||||||
@@ -155,17 +400,44 @@ function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
|
|||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
{listProps.itemList}
|
{listProps.itemList}
|
||||||
</div>
|
</x.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UniversalSearch props
|
||||||
|
export interface UniversalSearchProps {
|
||||||
|
/** Default search resource type */
|
||||||
|
defaultSearchResource?: ResourceType;
|
||||||
|
/** Controlled search resource type */
|
||||||
|
searchResource?: ResourceType;
|
||||||
|
/** Overlay props */
|
||||||
|
overlayProps?: OverlayProps;
|
||||||
|
/** Whether the search overlay is open */
|
||||||
|
isOpen: boolean;
|
||||||
|
/** Whether the search is loading */
|
||||||
|
isLoading: boolean;
|
||||||
|
/** Callback when search type changes */
|
||||||
|
onSearchTypeChange?: (resource: SearchTypeOption) => void;
|
||||||
|
/** Items to display */
|
||||||
|
items: UniversalSearchItem[];
|
||||||
|
/** Available search type options */
|
||||||
|
searchTypeOptions: SearchTypeOption[];
|
||||||
|
/** Renderer for items */
|
||||||
|
itemRenderer?: ItemRenderer<UniversalSearchItem>;
|
||||||
|
/** Callback when an item is selected */
|
||||||
|
onItemSelect?: (item: UniversalSearchItem, event?: any) => void;
|
||||||
|
/** Current query string */
|
||||||
|
query: string;
|
||||||
|
/** Callback when query changes */
|
||||||
|
onQueryChange?: (query: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Universal search.
|
* Universal search.
|
||||||
*/
|
*/
|
||||||
export function UniversalSearch({
|
export function UniversalSearch({
|
||||||
defaultSearchResource,
|
defaultSearchResource,
|
||||||
searchResource,
|
searchResource,
|
||||||
|
|
||||||
overlayProps,
|
overlayProps,
|
||||||
isOpen,
|
isOpen,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -173,9 +445,9 @@ export function UniversalSearch({
|
|||||||
items,
|
items,
|
||||||
searchTypeOptions,
|
searchTypeOptions,
|
||||||
...queryListProps
|
...queryListProps
|
||||||
}) {
|
}: UniversalSearchProps) {
|
||||||
// Search type state.
|
// Search type state.
|
||||||
const [searchType, setSearchType] = React.useState(
|
const [searchType, setSearchType] = React.useState<ResourceType>(
|
||||||
defaultSearchResource || RESOURCES_TYPES.CUSTOMER,
|
defaultSearchResource || RESOURCES_TYPES.CUSTOMER,
|
||||||
);
|
);
|
||||||
// Handle search resource type controlled mode.
|
// Handle search resource type controlled mode.
|
||||||
@@ -189,9 +461,9 @@ export function UniversalSearch({
|
|||||||
}, [searchResource, defaultSearchResource]);
|
}, [searchResource, defaultSearchResource]);
|
||||||
|
|
||||||
// Handle search type change.
|
// Handle search type change.
|
||||||
const handleSearchTypeChange = (searchTypeResource) => {
|
const handleSearchTypeChange = (searchTypeResource: SearchTypeOption) => {
|
||||||
setSearchType(searchTypeResource.key);
|
setSearchType(searchTypeResource.key);
|
||||||
onSearchTypeChange && onSearchTypeChange(searchTypeResource);
|
onSearchTypeChange?.(searchTypeResource);
|
||||||
};
|
};
|
||||||
// Filters query list items based on the given search type.
|
// Filters query list items based on the given search type.
|
||||||
const filteredItems = filterItemsByResourceType(items, searchType);
|
const filteredItems = filterItemsByResourceType(items, searchType);
|
||||||
@@ -200,7 +472,7 @@ export function UniversalSearch({
|
|||||||
<Overlay
|
<Overlay
|
||||||
hasBackdrop={true}
|
hasBackdrop={true}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
className={classNames(CLASSES.UNIVERSAL_SEARCH_OVERLAY)}
|
className={overlayStyles}
|
||||||
{...overlayProps}
|
{...overlayProps}
|
||||||
>
|
>
|
||||||
<UniversalSearchProvider
|
<UniversalSearchProvider
|
||||||
@@ -209,7 +481,7 @@ export function UniversalSearch({
|
|||||||
defaultSearchResource={defaultSearchResource}
|
defaultSearchResource={defaultSearchResource}
|
||||||
searchTypeOptions={searchTypeOptions}
|
searchTypeOptions={searchTypeOptions}
|
||||||
>
|
>
|
||||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH)}>
|
<x.div className={containerStyles}>
|
||||||
<UniversalSearchQueryList
|
<UniversalSearchQueryList
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
@@ -218,10 +490,10 @@ export function UniversalSearch({
|
|||||||
{...queryListProps}
|
{...queryListProps}
|
||||||
items={filteredItems}
|
items={filteredItems}
|
||||||
/>
|
/>
|
||||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_FOOTER)}>
|
<x.div className={footerStyles}>
|
||||||
<UniversalQuerySearchActions />
|
<UniversalQuerySearchActions />
|
||||||
</div>
|
</x.div>
|
||||||
</div>
|
</x.div>
|
||||||
</UniversalSearchProvider>
|
</UniversalSearchProvider>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,30 +1,82 @@
|
|||||||
// @ts-nocheck
|
import React, { createContext, ReactNode, useContext } from 'react';
|
||||||
import React, { createContext } from 'react';
|
|
||||||
|
|
||||||
const UniversalSearchContext = createContext();
|
// The resource type value from RESOURCES_TYPES constant
|
||||||
|
type ResourceType = string;
|
||||||
|
|
||||||
|
// Search type option item
|
||||||
|
interface SearchTypeOption {
|
||||||
|
key: ResourceType;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context value type
|
||||||
|
interface UniversalSearchContextValue {
|
||||||
|
/** Whether the search is loading */
|
||||||
|
isLoading: boolean;
|
||||||
|
/** Current search type/resource type */
|
||||||
|
searchType: ResourceType;
|
||||||
|
/** Default search resource type */
|
||||||
|
defaultSearchResource?: ResourceType;
|
||||||
|
/** List of available search type options */
|
||||||
|
searchTypeOptions: SearchTypeOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the context with undefined as initial value
|
||||||
|
const UniversalSearchContext = createContext<
|
||||||
|
UniversalSearchContextValue | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
// Provider props interface
|
||||||
|
interface UniversalSearchProviderProps {
|
||||||
|
/** Whether the search is loading */
|
||||||
|
isLoading: boolean;
|
||||||
|
/** Default search resource type */
|
||||||
|
defaultSearchResource?: ResourceType;
|
||||||
|
/** Current search type/resource type */
|
||||||
|
searchType: ResourceType;
|
||||||
|
/** List of available search type options */
|
||||||
|
searchTypeOptions: SearchTypeOption[];
|
||||||
|
/** Child elements */
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Universal search data provider.
|
* Universal search data provider.
|
||||||
*/
|
*/
|
||||||
function UniversalSearchProvider({
|
export function UniversalSearchProvider({
|
||||||
isLoading,
|
isLoading,
|
||||||
defaultSearchResource,
|
defaultSearchResource,
|
||||||
searchType,
|
searchType,
|
||||||
searchTypeOptions,
|
searchTypeOptions,
|
||||||
...props
|
children,
|
||||||
}) {
|
}: UniversalSearchProviderProps) {
|
||||||
// Provider payload.
|
// Provider payload.
|
||||||
const provider = {
|
const provider: UniversalSearchContextValue = {
|
||||||
isLoading,
|
isLoading,
|
||||||
searchType,
|
searchType,
|
||||||
defaultSearchResource,
|
defaultSearchResource,
|
||||||
searchTypeOptions,
|
searchTypeOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <UniversalSearchContext.Provider value={provider} {...props} />;
|
return (
|
||||||
|
<UniversalSearchContext.Provider value={provider}>
|
||||||
|
{children}
|
||||||
|
</UniversalSearchContext.Provider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const useUniversalSearchContext = () =>
|
/**
|
||||||
React.useContext(UniversalSearchContext);
|
* Hook to access the universal search context.
|
||||||
|
* @throws Error if used outside of UniversalSearchProvider
|
||||||
|
*/
|
||||||
|
export const useUniversalSearchContext = (): UniversalSearchContextValue => {
|
||||||
|
const context = useContext(UniversalSearchContext);
|
||||||
|
|
||||||
export { UniversalSearchProvider, useUniversalSearchContext };
|
if (context === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
'useUniversalSearchContext must be used within a UniversalSearchProvider',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
// @ts-nocheck
|
import React, { ReactNode } from 'react';
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
export const If = (props) =>
|
interface IfProps {
|
||||||
props.condition ? (props.render ? props.render() : props.children) : null;
|
condition: boolean;
|
||||||
|
children?: ReactNode;
|
||||||
|
render?: () => ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
If.propTypes = {
|
export const If = (props: IfProps): React.ReactElement | null =>
|
||||||
// condition: PropTypes.bool.isRequired,
|
props.condition ? (props.render ? <>{props.render()}</> : <>{props.children}</>) : null;
|
||||||
children: PropTypes.node,
|
|
||||||
render: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
export default [
|
export const AllocateLandedCostType = [
|
||||||
{ name: intl.get('bills'), value: 'Bill' },
|
{ name: intl.get('bills'), value: 'Bill' },
|
||||||
{ name: intl.get('expenses'), value: 'Expense' },
|
{ name: intl.get('expenses'), value: 'Expense' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
export default {
|
export const App = {
|
||||||
"app_name": "BigCapital",
|
"app_name": "BigCapital",
|
||||||
"app_version": "0.0.1 (build 12344)",
|
"app_version": "0.0.1 (build 12344)",
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
export default [
|
export const ContactsOptions = [
|
||||||
{ name: intl.get('customer'), path: 'customers' },
|
{ name: intl.get('customer'), path: 'customers' },
|
||||||
{ name: intl.get('vendor'), path: 'vendors' },
|
{ name: intl.get('vendor'), path: 'vendors' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
VendorAction,
|
VendorAction,
|
||||||
} from './abilityOption';
|
} from './abilityOption';
|
||||||
|
|
||||||
export default [
|
export const KeyboardShortcutsOptions = [
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + I',
|
shortcut_key: 'Shift + I',
|
||||||
description: intl.get('jump_to_the_invoices'),
|
description: intl.get('jump_to_the_invoices'),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T } from '@/components';
|
import { FormattedMessage as T } from '@/components';
|
||||||
|
|
||||||
export default [
|
export const PreferencesMenu = [
|
||||||
{
|
{
|
||||||
text: <T id={'general'} />,
|
text: <T id={'general'} />,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
@@ -13,10 +13,10 @@ export default [
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/branding',
|
href: '/preferences/branding',
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
text: 'Billing',
|
// text: 'Billing',
|
||||||
href: '/preferences/billing',
|
// href: '/preferences/billing',
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
text: <T id={'users'} />,
|
text: <T id={'users'} />,
|
||||||
href: '/preferences/users',
|
href: '/preferences/users',
|
||||||
@@ -63,11 +63,11 @@ export default [
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/items',
|
href: '/preferences/items',
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
text: 'Integrations',
|
// text: 'Integrations',
|
||||||
disabled: false,
|
// disabled: false,
|
||||||
href: '/preferences/integrations'
|
// href: '/preferences/integrations'
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
text: 'API Keys',
|
text: 'API Keys',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
|||||||
@@ -32,38 +32,38 @@ export default function MakeJournalFloatingAction() {
|
|||||||
|
|
||||||
// Handle submit & publish button click.
|
// Handle submit & publish button click.
|
||||||
const handleSubmitPublishBtnClick = (event) => {
|
const handleSubmitPublishBtnClick = (event) => {
|
||||||
submitForm();
|
|
||||||
setSubmitPayload({ redirect: true, publish: true });
|
setSubmitPayload({ redirect: true, publish: true });
|
||||||
|
submitForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle submit, publish & new button click.
|
// Handle submit, publish & new button click.
|
||||||
const handleSubmitPublishAndNewBtnClick = (event) => {
|
const handleSubmitPublishAndNewBtnClick = (event) => {
|
||||||
submitForm();
|
|
||||||
setSubmitPayload({ redirect: false, publish: true, resetForm: true });
|
setSubmitPayload({ redirect: false, publish: true, resetForm: true });
|
||||||
|
submitForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle submit, publish & edit button click.
|
// Handle submit, publish & edit button click.
|
||||||
const handleSubmitPublishContinueEditingBtnClick = (event) => {
|
const handleSubmitPublishContinueEditingBtnClick = (event) => {
|
||||||
submitForm();
|
|
||||||
setSubmitPayload({ redirect: false, publish: true });
|
setSubmitPayload({ redirect: false, publish: true });
|
||||||
|
submitForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle submit as draft button click.
|
// Handle submit as draft button click.
|
||||||
const handleSubmitDraftBtnClick = (event) => {
|
const handleSubmitDraftBtnClick = (event) => {
|
||||||
submitForm();
|
|
||||||
setSubmitPayload({ redirect: true, publish: false });
|
setSubmitPayload({ redirect: true, publish: false });
|
||||||
|
submitForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle submit as draft & new button click.
|
// Handle submit as draft & new button click.
|
||||||
const handleSubmitDraftAndNewBtnClick = (event) => {
|
const handleSubmitDraftAndNewBtnClick = (event) => {
|
||||||
submitForm();
|
|
||||||
setSubmitPayload({ redirect: false, publish: false, resetForm: true });
|
setSubmitPayload({ redirect: false, publish: false, resetForm: true });
|
||||||
|
submitForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle submit as draft & continue editing button click.
|
// Handle submit as draft & continue editing button click.
|
||||||
const handleSubmitDraftContinueEditingBtnClick = (event) => {
|
const handleSubmitDraftContinueEditingBtnClick = (event) => {
|
||||||
submitForm();
|
|
||||||
setSubmitPayload({ redirect: false, publish: false });
|
setSubmitPayload({ redirect: false, publish: false });
|
||||||
|
submitForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle cancel button click.
|
// Handle cancel button click.
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import { If, AppToaster } from '@/components';
|
|||||||
import { NormalCell, BalanceCell, BankBalanceCell } from './components';
|
import { NormalCell, BalanceCell, BankBalanceCell } from './components';
|
||||||
import { transformTableStateToQuery, isBlank } from '@/utils';
|
import { transformTableStateToQuery, isBlank } from '@/utils';
|
||||||
|
|
||||||
|
export const DeleteAccountTypeError = {
|
||||||
|
AccountPredefined: 'account_predefined',
|
||||||
|
AccountHasAssociatedTransactions: 'account_has_associated_transactions',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account name accessor.
|
* Account name accessor.
|
||||||
*/
|
*/
|
||||||
@@ -26,13 +31,13 @@ export const accountNameAccessor = (account) => {
|
|||||||
* Handle delete errors in bulk and singular.
|
* Handle delete errors in bulk and singular.
|
||||||
*/
|
*/
|
||||||
export const handleDeleteErrors = (errors) => {
|
export const handleDeleteErrors = (errors) => {
|
||||||
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
|
if (errors.find((e) => e.type === DeleteAccountTypeError.AccountPredefined)) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('cannot_delete_predefined_accounts'),
|
message: intl.get('cannot_delete_predefined_accounts'),
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
|
if (errors.find((e) => e.type === DeleteAccountTypeError.AccountHasAssociatedTransactions)) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('cannot_delete_account_has_associated_transactions'),
|
message: intl.get('cannot_delete_account_has_associated_transactions'),
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { FormattedMessage as T, If, FFormGroup, FSelect, FRadioGroup, FInputGrou
|
|||||||
import { handleStringChange } from '@/utils';
|
import { handleStringChange } from '@/utils';
|
||||||
import { FieldRequiredHint } from '@/components';
|
import { FieldRequiredHint } from '@/components';
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import allocateLandedCostType from '@/constants/allocateLandedCostType';
|
import { AllocateLandedCostType } from '@/constants/allocateLandedCostType';
|
||||||
|
|
||||||
import AllocateLandedCostFormBody from './AllocateLandedCostFormBody';
|
import AllocateLandedCostFormBody from './AllocateLandedCostFormBody';
|
||||||
import {
|
import {
|
||||||
@@ -81,7 +81,7 @@ export default function AllocateLandedCostFormFields() {
|
|||||||
>
|
>
|
||||||
<FSelect
|
<FSelect
|
||||||
name={'transaction_type'}
|
name={'transaction_type'}
|
||||||
items={allocateLandedCostType}
|
items={AllocateLandedCostType}
|
||||||
onItemChange={handleTransactionTypeChange}
|
onItemChange={handleTransactionTypeChange}
|
||||||
filterable={false}
|
filterable={false}
|
||||||
valueAccessor={'value'}
|
valueAccessor={'value'}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { FormattedMessage as T } from '@/components';
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { useContactDuplicateFromContext } from './ContactDuplicateProvider';
|
import { useContactDuplicateFromContext } from './ContactDuplicateProvider';
|
||||||
|
|
||||||
import Contacts from '@/constants/contactsOptions';
|
import { ContactsOptions } from '@/constants/contactsOptions';
|
||||||
|
|
||||||
import { withDialogActions } from '@/containers/Dialog/withDialogActions';
|
import { withDialogActions } from '@/containers/Dialog/withDialogActions';
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
@@ -66,7 +66,7 @@ function ContactDuplicateForm({
|
|||||||
>
|
>
|
||||||
<FSelect
|
<FSelect
|
||||||
name={'contact_type'}
|
name={'contact_type'}
|
||||||
items={Contacts}
|
items={ContactsOptions}
|
||||||
placeholder={<T id={'select_contact'} />}
|
placeholder={<T id={'select_contact'} />}
|
||||||
textAccessor={'name'}
|
textAccessor={'name'}
|
||||||
valueAccessor={'path'}
|
valueAccessor={'path'}
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
import { getColumnWidth } from '@/utils';
|
import { getColumnWidth } from '@/utils';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||||
import { Align } from '@/constants';
|
import { Align, CLASSES } from '@/constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description cell – wraps value in a div with muted text class.
|
||||||
|
*/
|
||||||
|
function DescriptionCell({ cell: { value } }) {
|
||||||
|
return React.createElement(
|
||||||
|
'div',
|
||||||
|
{ className: `cell ${CLASSES.TEXT_MUTED}` },
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
|
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
|
||||||
|
|
||||||
@@ -75,6 +87,16 @@ const transactionIdColumnAccessor = (column) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description column accessor (muted text in wrapped cell).
|
||||||
|
*/
|
||||||
|
const descriptionColumnAccessor = (column) => {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
Cell: DescriptionCell,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const dynamiColumnMapper = R.curry((data, column) => {
|
const dynamiColumnMapper = R.curry((data, column) => {
|
||||||
const _numericColumnAccessor = numericColumnAccessor(data);
|
const _numericColumnAccessor = numericColumnAccessor(data);
|
||||||
|
|
||||||
@@ -88,6 +110,7 @@ const dynamiColumnMapper = R.curry((data, column) => {
|
|||||||
R.pathEq(['key'], 'reference_number'),
|
R.pathEq(['key'], 'reference_number'),
|
||||||
transactionIdColumnAccessor,
|
transactionIdColumnAccessor,
|
||||||
),
|
),
|
||||||
|
R.when(R.pathEq(['key'], 'description'), descriptionColumnAccessor),
|
||||||
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
|
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
|
||||||
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
|
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
|
||||||
R.when(R.pathEq(['key'], 'amount'), _numericColumnAccessor),
|
R.when(R.pathEq(['key'], 'amount'), _numericColumnAccessor),
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { Align } from '@/constants';
|
import React from 'react';
|
||||||
|
import { Align, CLASSES } from '@/constants';
|
||||||
import { getColumnWidth } from '@/utils';
|
import { getColumnWidth } from '@/utils';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { useJournalSheetContext } from './JournalProvider';
|
import { useJournalSheetContext } from './JournalProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description cell – wraps value in a div with muted text class.
|
||||||
|
*/
|
||||||
|
function DescriptionCell({ cell: { value } }) {
|
||||||
|
return React.createElement(
|
||||||
|
'span',
|
||||||
|
{ className: `cell ${CLASSES.TEXT_MUTED}` },
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
|
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
|
||||||
|
|
||||||
const getReportColWidth = (data, accessor, headerText) => {
|
const getReportColWidth = (data, accessor, headerText) => {
|
||||||
@@ -86,6 +98,16 @@ const accountCodeColumnAccessor = (column) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description column accessor (muted text in wrapped cell).
|
||||||
|
*/
|
||||||
|
const descriptionColumnAccessor = (column) => {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
Cell: DescriptionCell,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamic column mapper.
|
* Dynamic column mapper.
|
||||||
* @param {} data -
|
* @param {} data -
|
||||||
@@ -105,6 +127,7 @@ const dynamicColumnMapper = R.curry((data, column) => {
|
|||||||
R.pathEq(['key'], 'transaction_number'),
|
R.pathEq(['key'], 'transaction_number'),
|
||||||
transactionNumberColumnAccessor,
|
transactionNumberColumnAccessor,
|
||||||
),
|
),
|
||||||
|
R.when(R.pathEq(['key'], 'description'), descriptionColumnAccessor),
|
||||||
R.when(R.pathEq(['key'], 'account_code'), accountCodeColumnAccessor),
|
R.when(R.pathEq(['key'], 'account_code'), accountCodeColumnAccessor),
|
||||||
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
|
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
|
||||||
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
|
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
|
||||||
@@ -113,7 +136,7 @@ const dynamicColumnMapper = R.curry((data, column) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composes the fetched dynamic columns from the server to the columns to pass it
|
* Composes the fetched dynamic columns from the server to the columns to pass it
|
||||||
* to the table component.
|
* to the table component.
|
||||||
*/
|
*/
|
||||||
export const dynamicColumns = (columns, data) => {
|
export const dynamicColumns = (columns, data) => {
|
||||||
|
|||||||
@@ -14,6 +14,18 @@ import { useSettingsSelector } from '@/hooks/state';
|
|||||||
import { transformItemFormData } from './ItemForm.schema';
|
import { transformItemFormData } from './ItemForm.schema';
|
||||||
import { useWatch } from '@/hooks/utils';
|
import { useWatch } from '@/hooks/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error types for item operations.
|
||||||
|
*/
|
||||||
|
export const ItemErrorType = {
|
||||||
|
ItemNameExists: 'ITEM_NAME_EXISTS',
|
||||||
|
InventoryAccountCannotModified: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
|
||||||
|
TypeCannotChangeWithItemHasTransactions: 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
||||||
|
ItemHasAssociatedTransactions: 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
|
||||||
|
ItemHasAssociatedInventoryAdjustment: 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
||||||
|
ItemHasAssociatedTransactionsPlural: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS',
|
||||||
|
} as const;
|
||||||
|
|
||||||
const defaultInitialValues = {
|
const defaultInitialValues = {
|
||||||
active: 1,
|
active: 1,
|
||||||
name: '',
|
name: '',
|
||||||
@@ -74,7 +86,7 @@ export const transitionItemTypeKeyToLabel = (itemTypeKey) => {
|
|||||||
// handle delete errors.
|
// handle delete errors.
|
||||||
export const handleDeleteErrors = (errors) => {
|
export const handleDeleteErrors = (errors) => {
|
||||||
if (
|
if (
|
||||||
errors.find((error) => error.type === 'ITEM_HAS_ASSOCIATED_TRANSACTINS')
|
errors.find((error) => error.type === ItemErrorType.ItemHasAssociatedTransactions)
|
||||||
) {
|
) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('the_item_has_associated_transactions'),
|
message: intl.get('the_item_has_associated_transactions'),
|
||||||
@@ -84,7 +96,7 @@ export const handleDeleteErrors = (errors) => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
errors.find(
|
errors.find(
|
||||||
(error) => error.type === 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
(error) => error.type === ItemErrorType.ItemHasAssociatedInventoryAdjustment,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
@@ -96,7 +108,7 @@ export const handleDeleteErrors = (errors) => {
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
errors.find(
|
errors.find(
|
||||||
(error) => error.type === 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
(error) => error.type === ItemErrorType.TypeCannotChangeWithItemHasTransactions,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
@@ -107,7 +119,7 @@ export const handleDeleteErrors = (errors) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
errors.find((error) => error.type === 'ITEM_HAS_ASSOCIATED_TRANSACTIONS')
|
errors.find((error) => error.type === ItemErrorType.ItemHasAssociatedTransactionsPlural)
|
||||||
) {
|
) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('item.error.you_could_not_delete_item_has_associated'),
|
message: intl.get('item.error.you_could_not_delete_item_has_associated'),
|
||||||
@@ -214,10 +226,10 @@ export const transformSubmitRequestErrors = (error) => {
|
|||||||
} = error;
|
} = error;
|
||||||
const fields = {};
|
const fields = {};
|
||||||
|
|
||||||
if (errors.find((e) => e.type === 'ITEM.NAME.ALREADY.EXISTS')) {
|
if (errors.find((e) => e.type === ItemErrorType.ItemNameExists)) {
|
||||||
fields.name = intl.get('the_name_used_before');
|
fields.name = intl.get('the_name_used_before');
|
||||||
}
|
}
|
||||||
if (errors.find((e) => e.type === 'INVENTORY_ACCOUNT_CANNOT_MODIFIED')) {
|
if (errors.find((e) => e.type === ItemErrorType.InventoryAccountCannotModified)) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('cannot_change_item_inventory_account'),
|
message: intl.get('cannot_change_item_inventory_account'),
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
@@ -225,7 +237,7 @@ export const transformSubmitRequestErrors = (error) => {
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
errors.find(
|
errors.find(
|
||||||
(e) => e.type === 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
(e) => e.type === ItemErrorType.TypeCannotChangeWithItemHasTransactions,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export function WarehousesGridItemBox({
|
|||||||
<WarehouseBoxRoot>
|
<WarehouseBoxRoot>
|
||||||
<WarehouseHeader>
|
<WarehouseHeader>
|
||||||
<WarehouseTitle>
|
<WarehouseTitle>
|
||||||
{title} {primary && <Icon icon={'star-18dp'} iconSize={16} />}
|
{title} {primary ? <Icon icon={'star-18dp'} iconSize={16} /> : null}
|
||||||
</WarehouseTitle>
|
</WarehouseTitle>
|
||||||
<WarehouseCode>{code}</WarehouseCode>
|
<WarehouseCode>{code}</WarehouseCode>
|
||||||
<WarehouseIcon>
|
<WarehouseIcon>
|
||||||
@@ -118,12 +118,21 @@ export const WarehousesList = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const WarehouseBoxRoot = styled.div`
|
export const WarehouseBoxRoot = styled.div`
|
||||||
|
--x-box-border-color: #c8cad0;
|
||||||
|
--x-box-background-color: #fff;
|
||||||
|
--x-box-hover-border-color: #0153cc;
|
||||||
|
|
||||||
|
.bp4-dark & {
|
||||||
|
--x-box-border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
--x-box-background-color: var(--color-dark-gray3);
|
||||||
|
--x-box-hover-border-color: #0153cc;
|
||||||
|
}
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px solid #c8cad0;
|
border: 1px solid var(--x-box-border-color);
|
||||||
background: #fff;
|
background: var(--x-box-background-color);
|
||||||
margin: 5px 5px 8px;
|
margin: 5px 5px 8px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 160px;
|
height: 160px;
|
||||||
@@ -132,7 +141,7 @@ export const WarehouseBoxRoot = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: #0153cc;
|
border-color: var(--x-box-hover-border-color);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -143,9 +152,16 @@ export const WarehouseHeader = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const WarehouseTitle = styled.div`
|
export const WarehouseTitle = styled.div`
|
||||||
|
--x-title-color: #000;
|
||||||
|
--x-title-icon-color: #e1b31d;
|
||||||
|
|
||||||
|
.bp4-dark & {
|
||||||
|
--x-title-color: var(--color-light-gray5);
|
||||||
|
--x-title-icon-color: #e1b31d;
|
||||||
|
}
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-style: inherit;
|
font-style: inherit;
|
||||||
color: #000;
|
color: var(--x-title-color);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -154,14 +170,19 @@ export const WarehouseTitle = styled.div`
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
color: #e1b31d;
|
color: var(--x-title-icon-color);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const WarehouseCode = styled.div`
|
const WarehouseCode = styled.div`
|
||||||
|
--x-code-color: #6b7176;
|
||||||
|
|
||||||
|
.bp4-dark & {
|
||||||
|
--x-code-color: var(--color-muted-text);
|
||||||
|
}
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #6b7176;
|
color: var(--x-code-color);
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -178,8 +199,13 @@ const WarehouseContent = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const WarehouseItem = styled.div`
|
const WarehouseItem = styled.div`
|
||||||
|
--x-item-color: #000;
|
||||||
|
|
||||||
|
.bp4-dark & {
|
||||||
|
--x-item-color: var(--color-light-gray1);
|
||||||
|
}
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #000;
|
color: var(--x-item-color);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { MenuItem } from '@blueprintjs/core';
|
import { MenuItem, Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
import { formattedAmount } from '@/utils';
|
import { formattedAmount } from '@/utils';
|
||||||
import { T, Icon, Choose, If } from '@/components';
|
import { T, Icon, Choose, If, TextStatus } from '@/components';
|
||||||
|
|
||||||
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
||||||
import { AbilitySubject, BillAction } from '@/constants/abilityOption';
|
import { AbilitySubject, BillAction } from '@/constants/abilityOption';
|
||||||
@@ -41,35 +41,35 @@ export function BillStatus({ bill }) {
|
|||||||
return (
|
return (
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={bill.is_fully_paid && bill.is_open}>
|
<Choose.When condition={bill.is_fully_paid && bill.is_open}>
|
||||||
<span class="fully-paid-text">
|
<TextStatus intent={Intent.SUCCESS}>
|
||||||
<T id={'paid'} />
|
<T id={'paid'} />
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.When condition={bill.is_open}>
|
<Choose.When condition={bill.is_open}>
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={bill.is_overdue}>
|
<Choose.When condition={bill.is_overdue}>
|
||||||
<span className={'overdue-status'}>
|
<TextStatus intent={Intent.DANGER}>
|
||||||
{intl.get('overdue_by', { overdue: bill.overdue_days })}
|
{intl.get('overdue_by', { overdue: bill.overdue_days })}
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<span className={'due-status'}>
|
<TextStatus intent={Intent.WARNING}>
|
||||||
{intl.get('due_in', { due: bill.remaining_days })}
|
{intl.get('due_in', { due: bill.remaining_days })}
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
<If condition={bill.is_partially_paid}>
|
<If condition={bill.is_partially_paid}>
|
||||||
<span className="partial-paid">
|
<TextStatus intent={Intent.WARNING}>
|
||||||
{intl.get('day_partially_paid', {
|
{intl.get('day_partially_paid', {
|
||||||
due: formattedAmount(bill.due_amount, bill.currency_code),
|
due: formattedAmount(bill.due_amount, bill.currency_code),
|
||||||
})}
|
})}
|
||||||
</span>
|
</TextStatus>
|
||||||
</If>
|
</If>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<span class="draft">
|
<TextStatus intent={Intent.NONE}>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { MenuItem } from '@blueprintjs/core';
|
import { MenuItem, Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
import { Choose, T, Icon } from '@/components';
|
import { Choose, T, Icon, TextStatus } from '@/components';
|
||||||
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
||||||
import { AbilitySubject, SaleEstimateAction } from '@/constants/abilityOption';
|
import { AbilitySubject, SaleEstimateAction } from '@/constants/abilityOption';
|
||||||
|
|
||||||
@@ -37,28 +37,28 @@ export const EstimateUniversalSearchSelect = withDrawerActions(
|
|||||||
export const EstimateStatus = ({ estimate }) => (
|
export const EstimateStatus = ({ estimate }) => (
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={estimate.is_delivered && estimate.is_approved}>
|
<Choose.When condition={estimate.is_delivered && estimate.is_approved}>
|
||||||
<span class="approved">
|
<TextStatus intent={Intent.SUCCESS}>
|
||||||
<T id={'approved'} />
|
<T id={'approved'} />
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.When condition={estimate.is_delivered && estimate.is_rejected}>
|
<Choose.When condition={estimate.is_delivered && estimate.is_rejected}>
|
||||||
<span class="reject">
|
<TextStatus intent={Intent.DANGER}>
|
||||||
<T id={'rejected'} />
|
<T id={'rejected'} />
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.When
|
<Choose.When
|
||||||
condition={
|
condition={
|
||||||
estimate.is_delivered && !estimate.is_rejected && !estimate.is_approved
|
estimate.is_delivered && !estimate.is_rejected && !estimate.is_approved
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span class="delivered">
|
<TextStatus intent={Intent.SUCCESS}>
|
||||||
<T id={'delivered'} />
|
<T id={'delivered'} />
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<span class="draft">
|
<TextStatus intent={Intent.NONE}>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { MenuItem } from '@blueprintjs/core';
|
import { MenuItem, Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
import { T, Choose, Icon } from '@/components';
|
import { T, Choose, Icon, TextStatus } from '@/components';
|
||||||
import { highlightText } from '@/utils';
|
import { highlightText } from '@/utils';
|
||||||
|
|
||||||
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
||||||
@@ -39,29 +39,29 @@ function InvoiceStatus({ customer }) {
|
|||||||
return (
|
return (
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={customer.is_fully_paid && customer.is_delivered}>
|
<Choose.When condition={customer.is_fully_paid && customer.is_delivered}>
|
||||||
<span class="status status-success">
|
<TextStatus intent={Intent.SUCCESS}>
|
||||||
<T id={'paid'} />
|
<T id={'paid'} />
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={customer.is_delivered}>
|
<Choose.When condition={customer.is_delivered}>
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={customer.is_overdue}>
|
<Choose.When condition={customer.is_overdue}>
|
||||||
<span className={'status status-warning'}>
|
<TextStatus intent={Intent.DANGER}>
|
||||||
{intl.get('overdue_by', { overdue: customer.overdue_days })}
|
{intl.get('overdue_by', { overdue: customer.overdue_days })}
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<span className={'status status-warning'}>
|
<TextStatus intent={Intent.WARNING}>
|
||||||
{intl.get('due_in', { due: customer.remaining_days })}
|
{intl.get('due_in', { due: customer.remaining_days })}
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<span class="status status--gray">
|
<TextStatus intent={Intent.NONE}>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
);
|
);
|
||||||
@@ -94,7 +94,6 @@ export function InvoiceUniversalSearchItem(
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className={'universal-search__item--invoice'}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { MenuItem } from '@blueprintjs/core';
|
import { MenuItem, Intent } from '@blueprintjs/core';
|
||||||
|
import { Icon, Choose, T, TextStatus } from '@/components';
|
||||||
import { Icon, Choose, T } from '@/components';
|
|
||||||
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
||||||
import { AbilitySubject, SaleReceiptAction } from '@/constants/abilityOption';
|
import { AbilitySubject, SaleReceiptAction } from '@/constants/abilityOption';
|
||||||
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
|
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
|
||||||
@@ -39,15 +38,15 @@ function ReceiptStatus({ receipt }) {
|
|||||||
return (
|
return (
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={receipt.is_closed}>
|
<Choose.When condition={receipt.is_closed}>
|
||||||
<span class="closed">
|
<TextStatus intent={Intent.SUCCESS}>
|
||||||
<T id={'closed'} />
|
<T id={'closed'} />
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<span class="draft">
|
<TextStatus intent={Intent.NONE}>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</span>
|
</TextStatus>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import keyboardShortcuts from '@/constants/keyboardShortcutsOptions';
|
import { KeyboardShortcutsOptions } from '@/constants/keyboardShortcutsOptions';
|
||||||
import { useAbilitiesFilter } from '../utils/useAbilityContext';
|
import { useAbilitiesFilter } from '../utils/useAbilityContext';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,7 +10,7 @@ export const useKeywordShortcuts = () => {
|
|||||||
const abilitiesFilter = useAbilitiesFilter();
|
const abilitiesFilter = useAbilitiesFilter();
|
||||||
|
|
||||||
return React.useMemo(
|
return React.useMemo(
|
||||||
() => abilitiesFilter(keyboardShortcuts),
|
() => abilitiesFilter(KeyboardShortcutsOptions),
|
||||||
[abilitiesFilter],
|
[abilitiesFilter],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,19 +32,19 @@ export function useResourceData(type, query, props) {
|
|||||||
*/
|
*/
|
||||||
function getResourceUrlFromType(type) {
|
function getResourceUrlFromType(type) {
|
||||||
const config = {
|
const config = {
|
||||||
[RESOURCES_TYPES.INVOICE]: '/sales/invoices',
|
[RESOURCES_TYPES.INVOICE]: '/sale-invoices',
|
||||||
[RESOURCES_TYPES.ESTIMATE]: '/sales/estimates',
|
[RESOURCES_TYPES.ESTIMATE]: '/sale-estimates',
|
||||||
[RESOURCES_TYPES.ITEM]: '/items',
|
[RESOURCES_TYPES.ITEM]: '/items',
|
||||||
[RESOURCES_TYPES.RECEIPT]: '/sales/receipts',
|
[RESOURCES_TYPES.RECEIPT]: '/sale-receipts',
|
||||||
[RESOURCES_TYPES.BILL]: '/purchases/bills',
|
[RESOURCES_TYPES.BILL]: '/bills',
|
||||||
[RESOURCES_TYPES.PAYMENT_RECEIVE]: '/sales/payment_receives',
|
[RESOURCES_TYPES.PAYMENT_RECEIVE]: '/payments-received',
|
||||||
[RESOURCES_TYPES.PAYMENT_MADE]: '/purchases/bill_payments',
|
[RESOURCES_TYPES.PAYMENT_MADE]: '/bill-payments',
|
||||||
[RESOURCES_TYPES.CUSTOMER]: '/customers',
|
[RESOURCES_TYPES.CUSTOMER]: '/customers',
|
||||||
[RESOURCES_TYPES.VENDOR]: '/vendors',
|
[RESOURCES_TYPES.VENDOR]: '/vendors',
|
||||||
[RESOURCES_TYPES.MANUAL_JOURNAL]: '/manual-journals',
|
[RESOURCES_TYPES.MANUAL_JOURNAL]: '/manual-journals',
|
||||||
[RESOURCES_TYPES.ACCOUNT]: '/accounts',
|
[RESOURCES_TYPES.ACCOUNT]: '/accounts',
|
||||||
[RESOURCES_TYPES.CREDIT_NOTE]: '/sales/credit_notes',
|
[RESOURCES_TYPES.CREDIT_NOTE]: '/credit-notes',
|
||||||
[RESOURCES_TYPES.VENDOR_CREDIT]: '/purchases/vendor-credit',
|
[RESOURCES_TYPES.VENDOR_CREDIT]: '/vendor-credits',
|
||||||
};
|
};
|
||||||
return config[type] || '';
|
return config[type] || '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useQuery } from 'react-query';
|
|||||||
import { castArray, defaultTo } from 'lodash';
|
import { castArray, defaultTo } from 'lodash';
|
||||||
import { useAuthOrganizationId } from './state';
|
import { useAuthOrganizationId } from './state';
|
||||||
import useApiRequest from './useRequest';
|
import useApiRequest from './useRequest';
|
||||||
|
import { normalizeApiPath } from '../utils';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,7 +20,11 @@ export function useRequestQuery(query, axios, props) {
|
|||||||
|
|
||||||
const states = useQuery(
|
const states = useQuery(
|
||||||
query,
|
query,
|
||||||
() => apiRequest.http({ ...axios, url: `/api/${axios.url}` }),
|
() =>
|
||||||
|
apiRequest.http({
|
||||||
|
...axios,
|
||||||
|
url: `/api/${normalizeApiPath(axios.url)}`,
|
||||||
|
}),
|
||||||
props,
|
props,
|
||||||
);
|
);
|
||||||
// Momerize the default data.
|
// Momerize the default data.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
useSetGlobalErrors,
|
useSetGlobalErrors,
|
||||||
useAuthToken,
|
useAuthToken,
|
||||||
} from './state';
|
} from './state';
|
||||||
import { getCookie } from '../utils';
|
import { getCookie, normalizeApiPath } from '../utils';
|
||||||
|
|
||||||
export default function useApiRequest() {
|
export default function useApiRequest() {
|
||||||
const setGlobalErrors = useSetGlobalErrors();
|
const setGlobalErrors = useSetGlobalErrors();
|
||||||
@@ -93,27 +93,27 @@ export default function useApiRequest() {
|
|||||||
http,
|
http,
|
||||||
|
|
||||||
get(resource, params) {
|
get(resource, params) {
|
||||||
return http.get(`/api/${resource}`, params);
|
return http.get(`/api/${normalizeApiPath(resource)}`, params);
|
||||||
},
|
},
|
||||||
|
|
||||||
post(resource, params, config) {
|
post(resource, params, config) {
|
||||||
return http.post(`/api/${resource}`, params, config);
|
return http.post(`/api/${normalizeApiPath(resource)}`, params, config);
|
||||||
},
|
},
|
||||||
|
|
||||||
update(resource, slug, params) {
|
update(resource, slug, params) {
|
||||||
return http.put(`/api/${resource}/${slug}`, params);
|
return http.put(`/api/${normalizeApiPath(resource)}/${slug}`, params);
|
||||||
},
|
},
|
||||||
|
|
||||||
put(resource, params) {
|
put(resource, params) {
|
||||||
return http.put(`/api/${resource}`, params);
|
return http.put(`/api/${normalizeApiPath(resource)}`, params);
|
||||||
},
|
},
|
||||||
|
|
||||||
patch(resource, params, config) {
|
patch(resource, params, config) {
|
||||||
return http.patch(`/api/${resource}`, params, config);
|
return http.patch(`/api/${normalizeApiPath(resource)}`, params, config);
|
||||||
},
|
},
|
||||||
|
|
||||||
delete(resource, params) {
|
delete(resource, params) {
|
||||||
return http.delete(`/api/${resource}`, params);
|
return http.delete(`/api/${normalizeApiPath(resource)}`, params);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[http],
|
[http],
|
||||||
@@ -130,22 +130,22 @@ export function useAuthApiRequest() {
|
|||||||
() => ({
|
() => ({
|
||||||
http,
|
http,
|
||||||
get(resource, params) {
|
get(resource, params) {
|
||||||
return http.get(`/api/${resource}`, params);
|
return http.get(`/api/${normalizeApiPath(resource)}`, params);
|
||||||
},
|
},
|
||||||
post(resource, params, config) {
|
post(resource, params, config) {
|
||||||
return http.post(`/api/${resource}`, params, config);
|
return http.post(`/api/${normalizeApiPath(resource)}`, params, config);
|
||||||
},
|
},
|
||||||
update(resource, slug, params) {
|
update(resource, slug, params) {
|
||||||
return http.put(`/api/${resource}/${slug}`, params);
|
return http.put(`/api/${normalizeApiPath(resource)}/${slug}`, params);
|
||||||
},
|
},
|
||||||
put(resource, params) {
|
put(resource, params) {
|
||||||
return http.put(`/api/${resource}`, params);
|
return http.put(`/api/${normalizeApiPath(resource)}`, params);
|
||||||
},
|
},
|
||||||
patch(resource, params, config) {
|
patch(resource, params, config) {
|
||||||
return http.patch(`/api/${resource}`, params, config);
|
return http.patch(`/api/${normalizeApiPath(resource)}`, params, config);
|
||||||
},
|
},
|
||||||
delete(resource, params) {
|
delete(resource, params) {
|
||||||
return http.delete(`/api/${resource}`, params);
|
return http.delete(`/api/${normalizeApiPath(resource)}`, params);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[http],
|
[http],
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ export const getPreferenceRoutes = () => [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${BASE_URL}/branding`,
|
path: `${BASE_URL}/branding`,
|
||||||
component: lazy(() => import('../containers/Preferences/Branding/PreferencesBrandingPage')),
|
component: lazy(
|
||||||
|
() =>
|
||||||
|
import('../containers/Preferences/Branding/PreferencesBrandingPage'),
|
||||||
|
),
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -29,14 +32,20 @@ export const getPreferenceRoutes = () => [
|
|||||||
{
|
{
|
||||||
path: `${BASE_URL}/payment-methods`,
|
path: `${BASE_URL}/payment-methods`,
|
||||||
component: lazy(
|
component: lazy(
|
||||||
() => import('../containers/Preferences/PaymentMethods/PreferencesPaymentMethodsPage'),
|
() =>
|
||||||
|
import(
|
||||||
|
'../containers/Preferences/PaymentMethods/PreferencesPaymentMethodsPage'
|
||||||
|
),
|
||||||
),
|
),
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${BASE_URL}/payment-methods/stripe/callback`,
|
path: `${BASE_URL}/payment-methods/stripe/callback`,
|
||||||
component: lazy(
|
component: lazy(
|
||||||
() => import('../containers/Preferences/PaymentMethods/PreferencesStripeCallback'),
|
() =>
|
||||||
|
import(
|
||||||
|
'../containers/Preferences/PaymentMethods/PreferencesStripeCallback'
|
||||||
|
),
|
||||||
),
|
),
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
@@ -112,16 +121,6 @@ export const getPreferenceRoutes = () => [
|
|||||||
component: lazy(() => import('@/containers/Preferences/Item')),
|
component: lazy(() => import('@/containers/Preferences/Item')),
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// path: `${BASE_URL}/sms-message`,
|
|
||||||
// component: SMSIntegration,
|
|
||||||
// exact: true,
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
path: `${BASE_URL}/billing`,
|
|
||||||
component: lazy(() => import('@/containers/Subscriptions/BillingPage')),
|
|
||||||
exact: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: `${BASE_URL}/api-keys`,
|
path: `${BASE_URL}/api-keys`,
|
||||||
component: lazy(() => import('@/containers/Preferences/ApiKeys/ApiKeys')),
|
component: lazy(() => import('@/containers/Preferences/ApiKeys/ApiKeys')),
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
@import 'components/Overlay';
|
@import 'components/Overlay';
|
||||||
@import 'components/Menu';
|
@import 'components/Menu';
|
||||||
@import 'components/SidebarOverlay';
|
@import 'components/SidebarOverlay';
|
||||||
@import 'components/UniversalSearch';
|
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
@import 'pages/view-form';
|
@import 'pages/view-form';
|
||||||
|
|||||||
@@ -7,6 +7,27 @@ $ns: bp4;
|
|||||||
--color-primary: #8abbff;
|
--color-primary: #8abbff;
|
||||||
--color-danger: red;
|
--color-danger: red;
|
||||||
|
|
||||||
|
// Green colors
|
||||||
|
--color-green-500: #165a36;
|
||||||
|
--color-green-400: #1c6e42;
|
||||||
|
--color-green-300: #238551;
|
||||||
|
--color-green-200: #32a467;
|
||||||
|
--color-green-100: #72ca9b;
|
||||||
|
|
||||||
|
// Red colors
|
||||||
|
--color-red-500: #8e292c;
|
||||||
|
--color-red-400: #ac2f33;
|
||||||
|
--color-red-300: #cd4246;
|
||||||
|
--color-red-200: #e76a6e;
|
||||||
|
--color-red-100: #fa999c;
|
||||||
|
|
||||||
|
// Orange colors
|
||||||
|
--color-orange-500: #77450d;
|
||||||
|
--color-orange-400: #935610;
|
||||||
|
--color-orange-300: #c87619;
|
||||||
|
--color-orange-200: #ec9a3c;
|
||||||
|
--color-orange-100: #fbb360;
|
||||||
|
|
||||||
--color-dark-gray5: #404854;
|
--color-dark-gray5: #404854;
|
||||||
--color-dark-gray4: #383e47;
|
--color-dark-gray4: #383e47;
|
||||||
--color-dark-gray3: #2f343c;
|
--color-dark-gray3: #2f343c;
|
||||||
@@ -196,7 +217,6 @@ $ns: bp4;
|
|||||||
--color-preferences-sidebar-head-border: #bbcbd0;
|
--color-preferences-sidebar-head-border: #bbcbd0;
|
||||||
--color-preferences-sidebar-head-text: #3b3b4c;
|
--color-preferences-sidebar-head-text: #3b3b4c;
|
||||||
|
|
||||||
|
|
||||||
// Preferences - Topbar.
|
// Preferences - Topbar.
|
||||||
--color-preferences-topbar-background: #fff;
|
--color-preferences-topbar-background: #fff;
|
||||||
--color-preferences-topbar-border: #d2dde2;
|
--color-preferences-topbar-border: #d2dde2;
|
||||||
@@ -209,7 +229,7 @@ $ns: bp4;
|
|||||||
--color-financial-sheet-title-text: rgb(31, 50, 85);
|
--color-financial-sheet-title-text: rgb(31, 50, 85);
|
||||||
--color-financial-sheet-type-text: rgb(31, 50, 85);
|
--color-financial-sheet-type-text: rgb(31, 50, 85);
|
||||||
--color-financial-sheet-date-text: rgb(31, 50, 85);
|
--color-financial-sheet-date-text: rgb(31, 50, 85);
|
||||||
--color-financial-sheet-footer-text: rgb(31, 50, 85);
|
--color-financial-sheet-footer-text: var(--color-muted-text);
|
||||||
--color-financial-sheet-minimal-title-text: #333;
|
--color-financial-sheet-minimal-title-text: #333;
|
||||||
|
|
||||||
// Transaction locking.
|
// Transaction locking.
|
||||||
@@ -302,6 +322,27 @@ body.bp4-dark {
|
|||||||
--color-primary: #8abbff;
|
--color-primary: #8abbff;
|
||||||
--color-danger: rgb(213, 103, 103);
|
--color-danger: rgb(213, 103, 103);
|
||||||
|
|
||||||
|
// Green colors (dark mode - lighter variants)
|
||||||
|
--color-green-500: #72ca9b;
|
||||||
|
--color-green-400: #32a467;
|
||||||
|
--color-green-300: #238551;
|
||||||
|
--color-green-200: #1c6e42;
|
||||||
|
--color-green-100: #165a36;
|
||||||
|
|
||||||
|
// Red colors (dark mode - lighter variants)
|
||||||
|
--color-red-500: #fa999c;
|
||||||
|
--color-red-400: #e76a6e;
|
||||||
|
--color-red-300: #cd4246;
|
||||||
|
--color-red-200: #ac2f33;
|
||||||
|
--color-red-100: #8e292c;
|
||||||
|
|
||||||
|
// Orange colors (dark mode - lighter variants)
|
||||||
|
--color-orange-500: #fbb360;
|
||||||
|
--color-orange-400: #ec9a3c;
|
||||||
|
--color-orange-300: #c87619;
|
||||||
|
--color-orange-200: #935610;
|
||||||
|
--color-orange-100: #77450d;
|
||||||
|
|
||||||
--color-dark-gray5: #404854;
|
--color-dark-gray5: #404854;
|
||||||
--color-dark-gray4: #383e47;
|
--color-dark-gray4: #383e47;
|
||||||
--color-dark-gray3: #2f343c;
|
--color-dark-gray3: #2f343c;
|
||||||
@@ -514,7 +555,7 @@ body.bp4-dark {
|
|||||||
--color-financial-sheet-title-text: var(--color-light-gray1);
|
--color-financial-sheet-title-text: var(--color-light-gray1);
|
||||||
--color-financial-sheet-type-text: var(--color-light-gray1);
|
--color-financial-sheet-type-text: var(--color-light-gray1);
|
||||||
--color-financial-sheet-date-text: var(--color-light-gray1);
|
--color-financial-sheet-date-text: var(--color-light-gray1);
|
||||||
--color-financial-sheet-footer-text: var(--color-light-gray1);
|
--color-financial-sheet-footer-text: var(--color-muted-text);
|
||||||
--color-financial-sheet-minimal-title-text: var(--color-white);
|
--color-financial-sheet-minimal-title-text: var(--color-white);
|
||||||
|
|
||||||
// Transaction locking.
|
// Transaction locking.
|
||||||
|
|||||||
@@ -365,7 +365,7 @@
|
|||||||
border-bottom: 1px solid var(--color-datatable-constrant-head-border);
|
border-bottom: 1px solid var(--color-datatable-constrant-head-border);
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tbody .tr .td {
|
.tbody .tr .td {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
padding: 0.5rem 0.5rem;
|
padding: 0.5rem 0.5rem;
|
||||||
@@ -375,3 +375,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sticky header: blurred transparent background so body rows don't show through
|
||||||
|
.bigcapital-datatable.has-sticky,
|
||||||
|
.bigcapital-datatable.has-sticky-header {
|
||||||
|
&.table-constrant .table .thead .th,
|
||||||
|
&.table--constrant .table .thead .th {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
|
||||||
|
body.bp4-dark & {
|
||||||
|
background: rgba(28, 33, 39, 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sticky cells in table body: blurred transparent background so content doesn't show through
|
||||||
|
.bigcapital-datatable.has-sticky {
|
||||||
|
&.table-constrant .table .tbody .tr:not(:hover) .td[data-sticky-td],
|
||||||
|
&.table--constrant .table .tbody .tr:not(:hover) .td[data-sticky-td] {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
|
||||||
|
body.bp4-dark & {
|
||||||
|
background: rgba(28, 33, 39, 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
.universal-search {
|
|
||||||
position: fixed;
|
|
||||||
filter: blur(0);
|
|
||||||
opacity: 1;
|
|
||||||
background-color: var(--color-universal-search-background);
|
|
||||||
border-radius: 3px;
|
|
||||||
box-shadow: 0 0 0 1px rgba(16, 22, 26, 0.1),
|
|
||||||
0 4px 8px rgba(16, 22, 26, 0.2),
|
|
||||||
0 18px 46px 6px rgba(16, 22, 26, 0.2);
|
|
||||||
left: calc(50% - 250px);
|
|
||||||
top: 20vh;
|
|
||||||
width: 500px;
|
|
||||||
z-index: 20;
|
|
||||||
|
|
||||||
&.bp4-overlay-appear,
|
|
||||||
&.bp4-overlay-enter {
|
|
||||||
filter: blur(20px);
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
&.bp4-overlay-appear-active,
|
|
||||||
&.bp4-overlay-enter-active {
|
|
||||||
filter: blur(0);
|
|
||||||
opacity: 1;
|
|
||||||
transition-delay: 0;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
transition-property: filter, opacity;
|
|
||||||
transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);
|
|
||||||
}
|
|
||||||
&.bp4-overlay-exit {
|
|
||||||
filter: blur(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
&.bp4-overlay-exit-active {
|
|
||||||
filter: blur(20px);
|
|
||||||
opacity: 0.2;
|
|
||||||
transition-delay: 0;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
transition-property: filter, opacity;
|
|
||||||
transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__omnibar {
|
|
||||||
.bp4-input-group {
|
|
||||||
.bp4-icon {
|
|
||||||
svg {
|
|
||||||
stroke: currentColor;
|
|
||||||
fill: none;
|
|
||||||
fill-rule: evenodd;
|
|
||||||
stroke-linecap: round;
|
|
||||||
stroke-linejoin: round;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-input-group .bp4-input {
|
|
||||||
border: 0;
|
|
||||||
box-shadow: 0 0 0 0;
|
|
||||||
height: 50px;
|
|
||||||
line-height: 50px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
.bp4-input-group.bp4-large .bp4-input:not(:first-child) {
|
|
||||||
padding-left: 50px !important;
|
|
||||||
}
|
|
||||||
.bp4-input-group.bp4-large .bp4-input:not(:last-child) {
|
|
||||||
padding-right: 130px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-input-group {
|
|
||||||
.bp4-icon {
|
|
||||||
margin: 16px;
|
|
||||||
color: var(--color-universal-search-icon);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
stroke-width: 2;
|
|
||||||
--text-opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-menu {
|
|
||||||
border-top: 1px solid var(--color-universal-search-menu-border);
|
|
||||||
max-height: calc(60vh - 20px);
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
.bp4-menu-item {
|
|
||||||
.bp4-text-muted {
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
.bp4-icon {
|
|
||||||
color: #8499a7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.bp4-intent-primary {
|
|
||||||
&.bp4-active {
|
|
||||||
background-color: rgb(235, 241, 246);
|
|
||||||
color: #252b30;
|
|
||||||
|
|
||||||
.bp4-menu-item-label {
|
|
||||||
color: #5c7080;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-label {
|
|
||||||
flex-direction: row;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-input-action {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__type-select-overlay {
|
|
||||||
.bp4-button {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footer {
|
|
||||||
padding: 12px 12px;
|
|
||||||
border-top: 1px solid var(--color-universal-search-footer-divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__actions {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__action {
|
|
||||||
&:not(:first-of-type) {
|
|
||||||
margin-left: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-tag {
|
|
||||||
background: var(--color-universal-search-tag-background);
|
|
||||||
color: var(--color-universal-search-tag-text);
|
|
||||||
}
|
|
||||||
&--arrows {
|
|
||||||
.bp4-tag {
|
|
||||||
padding: 0;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 16px;
|
|
||||||
margin-left: 4px;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: var(--color-universal-search-tag-text);
|
|
||||||
height: 100%;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footer {
|
|
||||||
}
|
|
||||||
|
|
||||||
&-input-right-elements {
|
|
||||||
display: flex;
|
|
||||||
margin: 10px;
|
|
||||||
|
|
||||||
.bp4-spinner {
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__item {
|
|
||||||
&--invoice,
|
|
||||||
&--estimate,
|
|
||||||
&--bill,
|
|
||||||
&--receipt {
|
|
||||||
.amount {
|
|
||||||
color: #252b30;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
&.status-warning {
|
|
||||||
color: rgb(236, 91, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status-success {
|
|
||||||
color: #249017;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,9 @@ import jsCookie from 'js-cookie';
|
|||||||
import { deepMapKeys } from './map-key-deep';
|
import { deepMapKeys } from './map-key-deep';
|
||||||
export * from './deep';
|
export * from './deep';
|
||||||
|
|
||||||
|
/** Strips leading slash from a path segment to avoid double slashes when joining with a base (e.g. `/api/` + path). */
|
||||||
|
export const normalizeApiPath = (path) => (path || '').replace(/^\//, '');
|
||||||
|
|
||||||
export const getCookie = (name, defaultValue) =>
|
export const getCookie = (name, defaultValue) =>
|
||||||
_.defaultTo(jsCookie.get(name), defaultValue);
|
_.defaultTo(jsCookie.get(name), defaultValue);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user