diff --git a/packages/plugin-cards-api/src/documents.ts b/packages/plugin-cards-api/src/documents.ts index e159aab43b..3afcc5f361 100644 --- a/packages/plugin-cards-api/src/documents.ts +++ b/packages/plugin-cards-api/src/documents.ts @@ -61,7 +61,8 @@ export default { types: [ { label: 'Cards', - type: 'cards' + type: 'cards', + subTypes: ['deal', 'task', 'ticket', 'purchase', 'stageDeal'] } ], @@ -74,14 +75,16 @@ export default { return [...commonFields, ...uniqueFields]; }, - replaceContent: async ({ subdomain, data: { stageId, itemId, content } }) => { + replaceContent: async ({ + subdomain, + data: { stageId, itemId, content, contentype, itemIds } + }) => { const models = await generateModels(subdomain); const stage = await models.Stages.findOne({ _id: stageId }); if (!stage) { return ''; } - let collection; if (stage.type == 'deal') { @@ -101,15 +104,27 @@ export default { if (stage.type == 'growthHack') { collection = models.GrowthHacks; } - if (!collection) { return ''; } - - const item = await collection.findOne({ _id: itemId }); - - if (!item) { - return ''; + let item; + if (contentype == 'cards:stage') { + const items = await collection.find({ + stageId: stageId, + _id: { $in: itemIds.split(',') } + }); + if (!items) { + return ''; + } + item = await cardsStage(items); + if (!item) { + return ''; + } + } else { + item = await collection.findOne({ _id: itemId }); + if (!item) { + return ''; + } } const simpleFields = ['name', 'description']; @@ -406,3 +421,76 @@ export default { return [replacedContent]; } }; + +function cardsStage(items: any[]) { + try { + const itemsArray = items; + const aggregatedData: Record = { + amount: { + AED: 0 + }, + productsData: [] + }; + itemsArray.forEach(item => { + const combinedNames = itemsArray.map(item => item.name).join(','); + aggregatedData.isComplete = item.isComplete; + aggregatedData.assignedUserIds = item.assignedUserIds; + aggregatedData.watchedUserIds = item.watchedUserIds; + aggregatedData.labelIds = item.labelIds; + aggregatedData.tagIds = item.tagIds; + aggregatedData.branchIds = item.branchIds; + aggregatedData.departmentIds = item.departmentIds; + aggregatedData.modifiedAt = item.modifiedAt; + aggregatedData.createdAt = item.createdAt; + aggregatedData.stageChangedDate = item.stageChangedDate; + aggregatedData.sourceConversationIds = item.sourceConversationIds; + aggregatedData.status = item.status; + aggregatedData.name = combinedNames; + aggregatedData.stageId = item.stageId; + aggregatedData.customFieldsData = item.customFieldsData; + aggregatedData.initialStageId = item.initialStageId; + aggregatedData.modifiedBy = item.modifiedBy; + aggregatedData.userId = item.userId; + aggregatedData.searchText = combinedNames; + + if (item.productsData) { + item.productsData.forEach(product => { + const existingProduct = aggregatedData.productsData.find( + p => + p.productId === product.productId && + p.branchId === product.branchId && + p.departmentId === product.departmentId + ); + + if (existingProduct) { + existingProduct.quantity += product.quantity; + existingProduct.amount += product.amount; + } else { + aggregatedData.productsData.push({ + tax: product.tax, + taxPercent: product.taxPercent, + discount: product.discount, + vatPercent: product.vatPercent, + discountPercent: product.discountPercent, + amount: product.amount, + currency: product.currency, + tickUsed: product.tickUsed, + maxQuantity: product.maxQuantity, + quantity: product.quantity, + productId: product.productId, + unitPrice: product.unitPrice, + globalUnitPrice: product.globalUnitPrice, + unitPricePercent: product.unitPricePercent + }); + } + + // Update the total amount for this stage + aggregatedData.amount.AED += product.amount * product.quantity; + }); + } + }); + return aggregatedData; + } catch (error) { + return { error: error.message }; + } +} diff --git a/packages/plugin-documents-api/src/graphql/resolvers/documentQueries.ts b/packages/plugin-documents-api/src/graphql/resolvers/documentQueries.ts index 0b5f49f375..1accbed1a1 100644 --- a/packages/plugin-documents-api/src/graphql/resolvers/documentQueries.ts +++ b/packages/plugin-documents-api/src/graphql/resolvers/documentQueries.ts @@ -23,7 +23,11 @@ const documentQueries = { } if (subType) { - selector.subType = subType; + selector.$or = [ + { subType }, + { subType: { $exists: false } }, + { subType: { $in: ['', null, undefined] } } + ]; } if (limit) { diff --git a/packages/plugin-documents-ui/src/graphql/queries.ts b/packages/plugin-documents-ui/src/graphql/queries.ts index 3db0f1783e..4560428218 100644 --- a/packages/plugin-documents-ui/src/graphql/queries.ts +++ b/packages/plugin-documents-ui/src/graphql/queries.ts @@ -1,9 +1,8 @@ const documents = ` - query documents($page: Int, $perPage: Int, $contentType: String) { - documents(page: $page, perPage: $perPage, contentType: $contentType) { + query documents($page: Int, $perPage: Int, $contentType: String, $subType: String) { + documents(page: $page, perPage: $perPage, contentType: $contentType, subType: $subType) { _id contentType - subType name createdAt } diff --git a/packages/ui-cards/src/boards/components/editForm/PrintDocumentBtn.tsx b/packages/ui-cards/src/boards/components/editForm/PrintDocumentBtn.tsx index f0c0425a79..e3913d6f7f 100644 --- a/packages/ui-cards/src/boards/components/editForm/PrintDocumentBtn.tsx +++ b/packages/ui-cards/src/boards/components/editForm/PrintDocumentBtn.tsx @@ -65,12 +65,13 @@ export default class PrintActionButton extends React.Component { } loadDocuments = () => { + const { item } = this.props; this.setState({ loading: true }); client .mutate({ mutation: gql(queries.documents), - variables: { contentType: 'cards' } + variables: { contentType: 'cards', subType: item.stage?.type } }) .then(({ data }) => { this.setState({ documents: data.documents }); diff --git a/packages/ui-cards/src/boards/components/stage/Stage.tsx b/packages/ui-cards/src/boards/components/stage/Stage.tsx index 008bd0c45f..20e6aef844 100644 --- a/packages/ui-cards/src/boards/components/stage/Stage.tsx +++ b/packages/ui-cards/src/boards/components/stage/Stage.tsx @@ -24,6 +24,8 @@ import { renderAmount } from '../../utils'; import ItemList from '../stage/ItemList'; import { OverlayTrigger, Popover, Dropdown } from 'react-bootstrap'; import { Row } from '@erxes/ui-settings/src/styles'; +import { isEnabled } from '@erxes/ui/src/utils/core'; +import StageModal from './StageModal'; type Props = { loadingItems: () => boolean; @@ -31,7 +33,7 @@ type Props = { index: number; stage: IStage; length: number; - items: IItem[]; + items: any[]; onAddItem: (stageId: string, item: IItem) => void; onRemoveItem: (itemId: string, stageId: string) => void; loadMore: () => void; @@ -43,6 +45,8 @@ type Props = { type State = { showSortOptions: boolean; + renderModal: boolean; + items: any[]; }; export default class Stage extends React.Component { @@ -51,14 +55,16 @@ export default class Stage extends React.Component { constructor(props: Props) { super(props); - this.bodyRef = React.createRef(); - this.state = { showSortOptions: false }; + this.state = { + showSortOptions: false, + renderModal: false, + items: [] + }; } componentDidMount() { - // Load items until scroll created const handle = setInterval(() => { if (this.props.loadingItems()) { return; @@ -88,10 +94,11 @@ export default class Stage extends React.Component { shouldComponentUpdate(nextProps: Props, nextState: State) { const { stage, index, length, items, loadingItems } = this.props; - const { showSortOptions } = this.state; + const { showSortOptions, renderModal } = this.state; if ( showSortOptions !== nextState.showSortOptions || + renderModal !== nextState.renderModal || index !== nextProps.index || loadingItems() !== nextProps.loadingItems() || length !== nextProps.length || @@ -114,10 +121,27 @@ export default class Stage extends React.Component { this.setState({ showSortOptions: !showSortOptions }); }; + toggleModal = () => { + this.setState(prevState => ({ + renderModal: !prevState.renderModal + })); + this.onClosePopover(); + }; + + onChangeCheckbox = (id: string, isChecked: boolean) => { + const { items } = this.props; + const changeItems = [...items]; + changeItems.map(item => { + if (item._id === id) { + item.checked = isChecked; + } + }); + this.setState({ items: changeItems }); + }; + renderPopover() { - const { stage } = this.props; + const { stage, options } = this.props; const { showSortOptions } = this.state; - const archiveList = () => { this.props.archiveList(); this.onClosePopover(); @@ -149,10 +173,13 @@ export default class Stage extends React.Component {
  • {__('Remove stage')}
  • - -
  • {__('Sort By')}
  • + {isEnabled('documents') && options.type === 'deal' && ( +
  • + {__('Print document')} +
  • + )} )} @@ -333,6 +360,16 @@ export default class Stage extends React.Component { ); } + renderTriggerModal() { + return this.state.renderModal ? ( + + ) : null; + } + render() { const { index, stage } = this.props; @@ -353,6 +390,7 @@ export default class Stage extends React.Component { {this.renderCtrl()} + {renderAmount(stage.amount)} {renderAmount(stage.unUsedAmount, false)} @@ -361,6 +399,7 @@ export default class Stage extends React.Component { {this.renderItemList()} + {this.renderTriggerModal()}, {this.renderAddItemTrigger()} diff --git a/packages/ui-cards/src/boards/components/stage/StageModal.tsx b/packages/ui-cards/src/boards/components/stage/StageModal.tsx new file mode 100644 index 0000000000..c518bf12d0 --- /dev/null +++ b/packages/ui-cards/src/boards/components/stage/StageModal.tsx @@ -0,0 +1,225 @@ +import client from '@erxes/ui/src/apolloClient'; +import { gql } from '@apollo/client'; +import React from 'react'; +import { queries } from '../../graphql'; +import { getEnv, __ } from '@erxes/ui/src/utils'; +import Dropdown from 'react-bootstrap/Dropdown'; +import Modal from 'react-bootstrap/Modal'; +import { Button, Alert } from '@erxes/ui/src'; +import { IStage } from '../../types'; +import FormControl from '@erxes/ui/src/components/form/Control'; +import { ControlLabel, FormGroup, Table } from '@erxes/ui/src/components'; + +type Props = { + item: any; + toggleModal: () => void; + stage: IStage; +}; + +type State = { + documents: any[]; + loading: boolean; + selectedDocumentId: string; + selectedDocumentName: string; + copies: string; + width: string; + item: any; + checked: boolean; + renderModal: boolean; + toggleModal: () => void; +}; + +export default class StageModal extends React.Component { + constructor(props) { + super(props); + + this.state = { + renderModal: false, + documents: [], + loading: false, + checked: false, + item: [], + selectedDocumentId: '', + selectedDocumentName: '', + copies: '', + width: '', + toggleModal: () => {} + }; + } + + onChangeCheckbox = (id: string, isChecked: boolean) => { + const { item } = this.props; + const changeItems = [...item]; + changeItems.map(item => { + if (item._id === id) { + item.checked = isChecked; + } + }); + + this.setState({ item: changeItems }); + }; + onChangeWidth = (e: React.FormEvent) => { + this.setState({ + width: (e.currentTarget as HTMLInputElement).value + }); + }; + + onChangeCopies = (e: React.FormEvent) => { + this.setState({ + copies: (e.currentTarget as HTMLInputElement).value + }); + }; + + loadDocuments = () => { + this.setState({ loading: true }); + + client + .mutate({ + mutation: gql(queries.documents), + variables: { contentType: 'cards', subType: 'stageDeal' } + }) + .then(({ data }) => { + this.setState({ + documents: data.documents, + loading: false + }); + }) + .catch(() => { + this.setState({ loading: false }); + }); + }; + print = () => { + const { item, selectedDocumentId, copies, width } = this.state; + + const apiUrl = getEnv().REACT_APP_API_URL; // Replace this with your API URL + if (!selectedDocumentId) return Alert.error('Please select document !!!'); + try { + const checkedItemIds = item + .filter(item => item.checked) // Filter only checked items + .map(item => item._id); // Map to an array of _id values + if (checkedItemIds.length === 0) { + return Alert.error('Please select item !!!'); + } + const url = `${apiUrl}/pl:documents/print?_id=${selectedDocumentId}&itemIds=${checkedItemIds}&stageId=${this.props.stage._id}&copies=${copies}&width=${width}&contentype=cards:stage`; + // Open the URL in a new browser window + window.open(url); + } catch (error) { + return Alert.error('An error occurred', error); + } + }; + onchangeDocument = (itemId, itemName) => { + // Update the selectedDocumentId in the state + this.setState({ + selectedDocumentId: itemId, + selectedDocumentName: itemName + }); + + // Perform additional actions as needed based on the selected item + }; + + renderDropdown() { + const { loading } = this.state; + + const { selectedDocumentId, documents } = this.state; + + return ( + + {loading ? 'loading' : ''} + + {selectedDocumentId + ? documents.find(item => item._id === selectedDocumentId)?.name || + 'Select Document' + : 'Select Document'} + {} + + + + {documents.map(item => ( + this.onchangeDocument(item._id, item.name)} + > + {item.name} + + ))} + + + ); + } + render() { + const { item, toggleModal } = this.props; + + return ( + + + {__('Print document')} + + + + + + Copies + + + + Width + + + + Select a document + {this.renderDropdown()} + + + + + + + + + {item.map(item => ( + + + + + + ))} + + + + +
    {__('Name')}{__('Number')}{__('Action')}
    {item.name}{item.number} + + this.onChangeCheckbox(item._id, event.target.checked) + } + /> +
    +
    +
    + ); + } +} diff --git a/packages/ui-cards/src/boards/graphql/queries.ts b/packages/ui-cards/src/boards/graphql/queries.ts index d8d7a58d73..bb4d666864 100644 --- a/packages/ui-cards/src/boards/graphql/queries.ts +++ b/packages/ui-cards/src/boards/graphql/queries.ts @@ -429,8 +429,8 @@ const boardLogs = ` `; const documents = ` - query documents($page: Int, $perPage: Int, $contentType: String) { - documents(page: $page, perPage: $perPage, contentType: $contentType) { + query documents($page: Int, $perPage: Int, $contentType: String, $subType: String) { + documents(page: $page, perPage: $perPage, contentType: $contentType, subType: $subType) { _id contentType name