diff --git a/client-portal/modules/card/components/Detail.tsx b/client-portal/modules/card/components/Detail.tsx index 34e1fa60d34..fc3c4513c78 100644 --- a/client-portal/modules/card/components/Detail.tsx +++ b/client-portal/modules/card/components/Detail.tsx @@ -83,7 +83,9 @@ export default class CardDetail extends React.Component< />
-
{`${createdUser?.firstName} ${createdUser?.lastName}`}
+
+ {renderUserFullName(createdUser)} +
{ markAsReadMutaion({ variables: { - ids, - }, + ids + } }); }; @@ -109,9 +108,9 @@ function NotificationsContainer(props: Props) { skip: !props.currentUser, variables: { page: 1, - perPage: 10, + perPage: 10 }, - fetchPolicy: "network-only", + fetchPolicy: 'network-only' } ); @@ -135,7 +134,7 @@ function NotificationsContainer(props: Props) { markAsRead, showNotifications, markAllAsRead, - refetch, + refetch }; return ; diff --git a/packages/plugin-clientportal-api/src/graphql/resolvers/comment.ts b/packages/plugin-clientportal-api/src/graphql/resolvers/comment.ts index 12b71c4a103..5b11fea0eeb 100644 --- a/packages/plugin-clientportal-api/src/graphql/resolvers/comment.ts +++ b/packages/plugin-clientportal-api/src/graphql/resolvers/comment.ts @@ -32,7 +32,9 @@ export default { _id: user._id, avatar: details.avatar, firstName: details.firstName, - lastName: details.lastName + lastName: details.lastName, + fullName: details.fullName, + email: user.email }; } @@ -43,8 +45,10 @@ export default { return { _id: cpUser._id, avatar: cpUser.avatar, + fullName: `${cpUser.firstName} ${cpUser.lastName}`, firstName: cpUser.firstName, - lastName: cpUser.lastName + lastName: cpUser.lastName, + email: cpUser.email }; } }; diff --git a/packages/plugin-clientportal-api/src/graphql/schema/clientPortalUser.ts b/packages/plugin-clientportal-api/src/graphql/schema/clientPortalUser.ts index 7969813edc5..f18d25a2fdc 100644 --- a/packages/plugin-clientportal-api/src/graphql/schema/clientPortalUser.ts +++ b/packages/plugin-clientportal-api/src/graphql/schema/clientPortalUser.ts @@ -49,6 +49,7 @@ type VerificationRequest { _id: String! createdAt: Date modifiedAt: Date + fullName: String firstName: String lastName: String phone: String diff --git a/packages/plugin-clientportal-api/src/graphql/schema/comment.ts b/packages/plugin-clientportal-api/src/graphql/schema/comment.ts index 14fc2781c6f..a15ff5aa6a2 100644 --- a/packages/plugin-clientportal-api/src/graphql/schema/comment.ts +++ b/packages/plugin-clientportal-api/src/graphql/schema/comment.ts @@ -8,7 +8,7 @@ export const types = ` userType: String, content: String - createdUser: User + createdUser: ClientPortalUser createdAt: Date } `; diff --git a/packages/ui-cards/src/boards/components/editForm/Actions.tsx b/packages/ui-cards/src/boards/components/editForm/Actions.tsx index b5e8f0dd9b1..b1efc9e185d 100644 --- a/packages/ui-cards/src/boards/components/editForm/Actions.tsx +++ b/packages/ui-cards/src/boards/components/editForm/Actions.tsx @@ -15,7 +15,7 @@ import { TAG_TYPES } from '@erxes/ui-tags/src/constants'; import TaggerPopover from '@erxes/ui-tags/src/components/TaggerPopover'; import Tags from '@erxes/ui/src/components/Tags'; import Watch from '../../containers/editForm/Watch'; -import Comment from '../../../comment/components/Comment'; +import Comment from '../../../comment/containers/Comment'; import { loadDynamicComponent, __ } from '@erxes/ui/src/utils'; import { isEnabled } from '@erxes/ui/src/utils/core'; import PrintActionButton from './PrintDocumentBtn'; diff --git a/packages/ui-cards/src/comment/components/Comment.tsx b/packages/ui-cards/src/comment/components/Comment.tsx index 3ff17a3770f..7246b81a7e7 100644 --- a/packages/ui-cards/src/comment/components/Comment.tsx +++ b/packages/ui-cards/src/comment/components/Comment.tsx @@ -1,8 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import Button from '@erxes/ui/src/components/Button'; import Icon from '@erxes/ui/src/components/Icon'; import Modal from 'react-bootstrap/Modal'; -import { __, readFile } from 'coreui/utils'; +import { __, readFile, renderFullName } from 'coreui/utils'; import { SpaceFormsWrapper, CommentWrapper, @@ -11,101 +11,118 @@ import { CommentContent } from '@erxes/ui-settings/src/styles'; import { ColorButton } from '../../boards/styles/common'; -import { gql, useQuery, useMutation } from '@apollo/client'; import dayjs from 'dayjs'; -import { queries, mutations } from '../graphql/'; +import { IUser } from '@erxes/ui/src/auth/types'; +import { IClientPortalComment, ICommentCreatedUser } from '../types'; -function Comment(item) { - const typeId = item.item._id; - const type = item.item.stage.type; +type Props = { + currentUser: IUser; + clientPortalComments: IClientPortalComment[]; + remove: (_id: string) => void; +}; - const { loading, data = {} as any } = useQuery( - gql(queries.clientPortalComments), - { - variables: { typeId: typeId, type: type }, - fetchPolicy: 'network-only', - notifyOnNetworkStatusChange: true - } - ); - const [clientPortalCommentsRemove] = useMutation( - gql(mutations.clientPortalCommentsRemove) - ); +type State = { + show: boolean; +}; - const clientPortalComments = data ? data.clientPortalComments || [] : []; - const [show, setShow] = useState(false); - const handleClose = () => setShow(false); - const handleShow = () => setShow(true); +class Comment extends React.Component { + constructor(props) { + super(props); - return ( - <> - - - {__('Comment')} - + this.state = { + show: false + }; + } - - - {__('Comments')} - - - - - {clientPortalComments.map(comment => { - const { createdUser = {} } = comment; + render() { + const { currentUser, clientPortalComments = [], remove } = this.props; - return ( - - - this.setState({ show: false }); + const handleShow = () => this.setState({ show: true }); + + return ( + <> + handleShow()}> + + {__('Comment')} + + + handleClose()} + backdrop="static" + keyboard={false} + > + + {__('Comments')} + + + + + {clientPortalComments.map(comment => { + const { createdUser = {} as ICommentCreatedUser } = comment; + + return ( + + + profile +
+ +
+ {createdUser.fullName + ? createdUser.fullName + : renderFullName(createdUser)} +
+
+ + + Created at{' '} + {dayjs(comment.createdAt).format( + 'YYYY-MM-DD HH:mm' + )} + +
+ {createdUser?._id === currentUser._id && ( +
+ remove(comment._id)}> + Delete + +
)} - alt="profile" - /> -
- -
{`${createdUser?.firstName} ${createdUser?.lastName}`}
-
- - - Reported{' '} - {dayjs(comment.createdAt).format('YYYY-MM-DD HH:mm')} - -
-
- - - ); - })} - - - - - - - - - ); + + + ); + })} + + + + + + + + + ); + } } export default Comment; diff --git a/packages/ui-cards/src/comment/containers/Comment.tsx b/packages/ui-cards/src/comment/containers/Comment.tsx new file mode 100644 index 00000000000..4847e8d583e --- /dev/null +++ b/packages/ui-cards/src/comment/containers/Comment.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import Comment from '../components/Comment'; +import { IItem } from '../../boards/types'; +import { IUser } from '@erxes/ui/src/auth/types'; +import { Alert, confirm, withProps } from '@erxes/ui/src/utils'; +import { gql } from '@apollo/client'; +import { graphql } from '@apollo/client/react/hoc'; +import * as compose from 'lodash.flowright'; +import { queries, mutations } from '../graphql/'; +import { + ClientPortalCommentQueryResponse, + IClientPortalComment, + CommentRemoveMutationResponse +} from '../types'; +import withCurrentUser from '@erxes/ui/src/auth/containers/withCurrentUser'; + +type Props = { + item: IItem; + currentUser?: IUser; +}; + +type FinalProps = { + clientPortalCommentsQuery: ClientPortalCommentQueryResponse; +} & Props & + CommentRemoveMutationResponse; + +class CommentContainer extends React.Component { + render() { + const { clientPortalCommentsQuery, removeMutation } = this.props; + + const clientPortalComments = + clientPortalCommentsQuery.clientPortalComments || + ([] as IClientPortalComment[]); + + const remove = (_id: string) => { + confirm().then(() => { + removeMutation({ variables: { _id } }) + .then(() => { + Alert.success('You successfully deleted a comment'); + clientPortalCommentsQuery.refetch(); + }) + .catch(e => { + Alert.error(e.message); + }); + }); + }; + + return ( + + ); + } +} + +const WithProps = withProps( + compose( + graphql( + gql(queries.clientPortalComments), + { + name: 'clientPortalCommentsQuery', + options: ({ item }: { item: IItem }) => ({ + variables: { typeId: item._id, type: item.stage.type }, + fetchPolicy: 'network-only', + notifyOnNetworkStatusChange: true + }) + } + ), + graphql( + gql(mutations.clientPortalCommentsRemove), + { + name: 'removeMutation' + } + ) + )(CommentContainer) +); + +export default withCurrentUser(WithProps); diff --git a/packages/ui-cards/src/comment/containers/Form.tsx b/packages/ui-cards/src/comment/containers/Form.tsx new file mode 100644 index 00000000000..ce3b1f77a78 --- /dev/null +++ b/packages/ui-cards/src/comment/containers/Form.tsx @@ -0,0 +1,85 @@ +import { gql } from '@apollo/client'; +import * as compose from 'lodash.flowright'; +import React from 'react'; +import { graphql } from '@apollo/client/react/hoc'; +import Form from '@erxes/ui-internalnotes/src/components/Form'; +import { mutations, queries } from '../graphql'; +import { + CommentAddMutationResponse, + CommentAddMutationVariables, + ClientPortalCommentQueryResponse +} from '../types'; + +type Props = { + contentType: string; + contentTypeId: string; +}; + +type FinalProps = { + clientPortalCommentsQueries: ClientPortalCommentQueryResponse; +} & Props & + CommentAddMutationResponse; + +class FormContainer extends React.Component< + FinalProps, + { isLoading: boolean } +> { + constructor(props: FinalProps) { + super(props); + + this.state = { isLoading: false }; + } + + create = (variables, callback: () => void) => { + const { + contentType, + contentTypeId, + commentAdd, + clientPortalCommentsQueries + } = this.props; + + this.setState({ isLoading: true }); + + commentAdd({ + variables: { + type: contentType.slice(6), + typeId: contentTypeId, + userType: 'team', + ...variables + } + }).then(() => { + clientPortalCommentsQueries.refetch(); + callback(); + + this.setState({ isLoading: false }); + }); + }; + + render() { + const { contentType, contentTypeId } = this.props; + + return ( +
+ ); + } +} + +export default compose( + graphql(gql(queries.clientPortalComments), { + name: 'clientPortalCommentsQueries', + options: ({ contentType, contentTypeId }) => ({ + variables: { type: contentType.slice(6), typeId: contentTypeId } + }) + }), + graphql( + gql(mutations.clientPortalCommentsAdd), + { + name: 'commentAdd' + } + ) +)(FormContainer); diff --git a/packages/ui-cards/src/comment/graphql/mutations.ts b/packages/ui-cards/src/comment/graphql/mutations.ts index b85d0a99bcc..0351cf067a2 100644 --- a/packages/ui-cards/src/comment/graphql/mutations.ts +++ b/packages/ui-cards/src/comment/graphql/mutations.ts @@ -1,13 +1,32 @@ -const clientPortalCommentsRemove = ` +const clientPortalCommentsAdd = ` + mutation clientPortalCommentsAdd( + $typeId: String! + $type: String! + $content: String! + $userType: String! + ) { + clientPortalCommentsAdd( + typeId: $typeId + type: $type + content: $content + userType: $userType + ) { + _id + } + } +`; + +const clientPortalCommentsRemove = ` mutation clientPortalCommentsRemove( $_id: String! ) { clientPortalCommentsRemove( _id: $_id - ) - } + ) + } `; export default { + clientPortalCommentsAdd, clientPortalCommentsRemove }; diff --git a/packages/ui-cards/src/comment/graphql/queries.ts b/packages/ui-cards/src/comment/graphql/queries.ts index 04b465a1c40..ccb5178fedf 100644 --- a/packages/ui-cards/src/comment/graphql/queries.ts +++ b/packages/ui-cards/src/comment/graphql/queries.ts @@ -3,7 +3,15 @@ const clientPortalComments = ` clientPortalComments(typeId: $typeId, type: $type) { _id content - createdUser + createdUser { + _id + avatar + firstName + fullName + lastName + email + username + } createdAt userType type diff --git a/packages/ui-cards/src/comment/types.ts b/packages/ui-cards/src/comment/types.ts index dcc8f006ebc..dfd83f8dead 100644 --- a/packages/ui-cards/src/comment/types.ts +++ b/packages/ui-cards/src/comment/types.ts @@ -1,3 +1,5 @@ +import { QueryResponse } from '@erxes/ui/src/types'; + export interface IChecklistDoc { contentType: string; contentTypeId: string; @@ -73,3 +75,45 @@ export type EditItemMutationVariables = { export type EditItemMutationResponse = ({ variables: EditItemMutationVariables }) => Promise; + +/* Client portal comment items */ + +export type CommentAddMutationVariables = { + typeId: string; + type: string; + content: string; + userType: string; +}; + +export type CommentAddMutationResponse = { + commentAdd: (params: { + variables: CommentAddMutationVariables; + }) => Promise; +}; + +export type ICommentCreatedUser = { + _id: string; + avatar: string; + firstName: string; + fullName: string; + lastName: string; + email: string; + username: string; +}; + +export type IClientPortalComment = { + _id: string; + content: string; + createdUser: ICommentCreatedUser; + createdAt: Date; + userType: string; + type: string; +}; + +export type ClientPortalCommentQueryResponse = { + clientPortalComments: IClientPortalComment[]; +} & QueryResponse; + +export type CommentRemoveMutationResponse = { + removeMutation: (params: { variables: { _id: string } }) => Promise; +}; diff --git a/packages/ui-log/src/activityLogs/components/ActivityInputs.tsx b/packages/ui-log/src/activityLogs/components/ActivityInputs.tsx index d734b835e12..f28ddcd6aea 100644 --- a/packages/ui-log/src/activityLogs/components/ActivityInputs.tsx +++ b/packages/ui-log/src/activityLogs/components/ActivityInputs.tsx @@ -3,6 +3,7 @@ import { TabTitle, Tabs } from '@erxes/ui/src/components/tabs'; import ErrorBoundary from '@erxes/ui/src/components/ErrorBoundary'; import Icon from '@erxes/ui/src/components/Icon'; import NoteForm from '@erxes/ui-internalnotes/src/containers/Form'; +import CommentForm from '@erxes/ui-cards/src/comment/containers/Form'; import React from 'react'; import { WhiteBoxRoot } from '@erxes/ui/src/layout/styles'; import { __ } from '@erxes/ui/src/utils'; @@ -35,7 +36,11 @@ class ActivityInputs extends React.PureComponent { super(props); this.state = { - currentTab: 'newNote' + currentTab: isEnabled('internalnotes') + ? 'newNote' + : isEnabled('clientportal') + ? 'newComment' + : '' }; } @@ -53,6 +58,12 @@ class ActivityInputs extends React.PureComponent { ); } + if (currentTab === 'newComment' && isEnabled('clientportal')) { + return ( + + ); + } + if (currentTab === 'ticket' && isEnabled('cards')) { return ( { {isEnabled('internalnotes') && this.renderTabTitle('newNote', 'file-plus', 'New note')} + {isEnabled('clientportal') && + this.renderTabTitle('newComment', 'comment-plus', 'New comment')} + {this.renderExtraTab()}