BOD-14 Fixed some broken UI with temporary hard coded valeus. Added basic Chat tagging features

This commit is contained in:
Patrick Fic
2020-04-30 11:40:41 -07:00
parent dcfcf71ca4
commit bf42655186
11 changed files with 298 additions and 153 deletions

View File

@@ -0,0 +1,16 @@
import React from "react";
import { Tag } from "antd";
import { Link } from "react-router-dom";
export default function ChatConversationTitleTags({ jobConversations }) {
return (
<div>
{jobConversations.map((item) => (
<Link to={`/manage/jobs/${item.job.id}`}>
<Tag color='blue' style={{ cursor: "pointer" }}>
{item.job.ro_number || "?"}
</Tag>
</Link>
))}
</div>
);
}

View File

@@ -0,0 +1,15 @@
import React from "react";
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
export default function ChatConversationTitle({ conversation }) {
return (
<div>
{conversation.phone_num}
<ChatConversationTitleTags
jobConversations={conversation.job_conversations || []}
/>
<ChatTagRoContainer conversation={conversation} />
</div>
);
}

View File

@@ -4,24 +4,33 @@ import ChatMessageListComponent from "../chat-messages-list/chat-message-list.co
import ChatSendMessage from "../chat-send-message/chat-send-message.component"; import ChatSendMessage from "../chat-send-message/chat-send-message.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ChatConversationTitle from "../chat-conversation-title/chat-conversation-title.component";
export default function ChatConversationComponent({ export default function ChatConversationComponent({ subState, conversation }) {
messages,
subState,
conversation,
unreadCount,
}) {
const [loading, error] = subState; const [loading, error] = subState;
if (loading) return <LoadingSkeleton />; if (loading) return <LoadingSkeleton />;
if (error) return <AlertComponent message={error.message} type='error' />; if (error) return <AlertComponent message={error.message} type='error' />;
const unreadCount =
(conversation &&
conversation &&
conversation.messages_aggregate &&
conversation.messages_aggregate.aggregate &&
conversation.messages_aggregate.aggregate.count) ||
0;
const messages =
(conversation && conversation.messages) ||
[];
return ( return (
<div className='chat-conversation'> <div className='chat-conversation'>
<Badge count={unreadCount}> <Badge count={unreadCount}>
<ChatSendMessage conversation={conversation} />
<Card size='small'> <Card size='small'>
<ChatConversationTitle conversation={conversation} />
<ChatMessageListComponent messages={messages} /> <ChatMessageListComponent messages={messages} />
<ChatSendMessage conversation={conversation} />
</Card> </Card>
</Badge> </Badge>
</div> </div>

View File

@@ -27,28 +27,9 @@ export function ChatConversationContainer({ selectedConversation }) {
return ( return (
<ChatConversationComponent <ChatConversationComponent
subState={[loading, error]} subState={[loading, error]}
unreadCount={ conversation={data ? data.conversations_by_pk : {}}
(data &&
data.conversations_by_pk &&
data.conversations_by_pk.messages_aggregate &&
data.conversations_by_pk.messages_aggregate.aggregate &&
data.conversations_by_pk.messages_aggregate.aggregate.count) ||
0
}
conversation={{
conversationId: selectedConversation,
phone_num:
(data &&
data.conversations_by_pk &&
data.conversations_by_pk.phone_num) ||
"",
}}
messages={
(data &&
data.conversations_by_pk &&
data.conversations_by_pk.messages) ||
[]
}
/> />
); );
} }

View File

@@ -1,5 +1,5 @@
.messages { .messages {
height: auto; height: 350px;
min-height: calc(100% - 10px); min-height: calc(100% - 10px);
max-height: calc(100% - 93px); max-height: calc(100% - 93px);
overflow-y: scroll; overflow-y: scroll;
@@ -125,4 +125,3 @@
.message-input .wrap button:focus { .message-input .wrap button:focus {
outline: none; outline: none;
} }

View File

@@ -6,22 +6,30 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { sendMessage } from "../../redux/messaging/messaging.actions"; import { sendMessage } from "../../redux/messaging/messaging.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { selectIsSending } from "../../redux/messaging/messaging.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
isSending: selectIsSending,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
sendMessage: (message) => dispatch(sendMessage(message)), sendMessage: (message) => dispatch(sendMessage(message)),
}); });
function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) { function ChatSendMessageComponent({
conversation,
bodyshop,
sendMessage,
isSending,
}) {
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
useEffect(() => { useEffect(() => {
if (conversation.isSending === false) { if (isSending === false) {
setMessage(""); setMessage("");
} }
}, [conversation, setMessage]); }, [isSending, setMessage]);
const { t } = useTranslation(); const { t } = useTranslation();
const handleEnter = () => { const handleEnter = () => {
@@ -29,7 +37,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) {
to: conversation.phone_num, to: conversation.phone_num,
body: message, body: message,
messagingServiceSid: bodyshop.messagingservicesid, messagingServiceSid: bodyshop.messagingservicesid,
conversationid: conversation.conversationId, conversationid: conversation.id,
}); });
}; };
@@ -41,7 +49,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) {
suffix={<span>a</span>} suffix={<span>a</span>}
autoSize={{ minRows: 1, maxRows: 4 }} autoSize={{ minRows: 1, maxRows: 4 }}
value={message} value={message}
disabled={conversation.isSending} disabled={isSending}
placeholder={t("messaging.labels.typeamessage")} placeholder={t("messaging.labels.typeamessage")}
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setMessage(e.target.value)}
onPressEnter={(event) => { onPressEnter={(event) => {
@@ -50,7 +58,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) {
}} }}
/> />
<Spin <Spin
style={{ display: `${conversation.isSending ? "" : "none"}` }} style={{ display: `${isSending ? "" : "none"}` }}
indicator={ indicator={
<LoadingOutlined <LoadingOutlined
style={{ style={{

View File

@@ -0,0 +1,38 @@
import React from "react";
import { AutoComplete } from "antd";
import { LoadingOutlined } from "@ant-design/icons";
export default function ChatTagRoComponent({
searchQueryState,
roOptions,
loading,
executeSearch,
handleInsertTag,
}) {
const setSearchQuery = searchQueryState[1];
const handleSearchQuery = (value) => {
setSearchQuery(value);
};
const handleKeyDown = (event) => {
if (event.key === "Enter") {
executeSearch();
}
};
return (
<AutoComplete
suffixIcon={loading ? <LoadingOutlined /> : null}
style={{ width: 200 }}
onSearch={handleSearchQuery}
onSelect={handleInsertTag}
onKeyDown={handleKeyDown}>
{roOptions.map((item, idx) => (
<AutoComplete.Option key={item.id || idx}>
{` ${item.ro_number || ""} | ${item.ownr_fn || ""} ${
item.ownr_ln || ""
}`}
</AutoComplete.Option>
))}
</AutoComplete>
);
}

View File

@@ -0,0 +1,46 @@
import React, { useState } from "react";
import ChatTagRo from "./chat-tag-ro.component";
import { useLazyQuery, useMutation } from "@apollo/react-hooks";
import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries";
import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
export default function ChatTagRoContainer({ conversation }) {
console.log("ChatTagRoContainer -> conversation", conversation);
const searchQueryState = useState("");
const searchText = searchQueryState[0];
const [loadRo, { called, loading, data, refetch }] = useLazyQuery(
SEARCH_FOR_JOBS,
{
variables: { search: `%${searchText}%` },
}
);
const executeSearch = () => {
if (called) refetch();
else {
loadRo();
}
};
const [insertTag] = useMutation(INSERT_CONVERSATION_TAG, {
variables: { conversationId: conversation.id },
});
const handleInsertTag = (value, option) => {
console.log("value, option", value, option);
insertTag({ variables: { jobId: option.key } });
};
return (
<div>
<ChatTagRo
loading={loading}
searchQueryState={searchQueryState}
roOptions={data ? data.jobs : []}
executeSearch={executeSearch}
handleInsertTag={handleInsertTag}
/>
</div>
);
}

View File

@@ -34,6 +34,14 @@ export const CONVERSATION_SUBSCRIPTION_BY_PK = gql`
} }
id id
phone_num phone_num
job_conversations {
jobid
conversationid
job {
id
ro_number
}
}
} }
} }
`; `;

View File

@@ -0,0 +1,14 @@
import { gql } from "apollo-boost";
export const INSERT_CONVERSATION_TAG = gql`
mutation INSERT_CONVERSATION_TAG($conversationId: uuid!, $jobId: uuid!) {
insert_job_conversations(
objects: { conversationid: $conversationId, jobid: $jobId }
) {
returning {
jobid
conversationid
}
}
}
`;

View File

@@ -432,6 +432,17 @@ export const ACTIVE_JOBS_FOR_AUTOCOMPLETE = gql`
} }
`; `;
export const SEARCH_FOR_JOBS = gql`
query SEARCH_FOR_JOBS($search: String!) {
jobs(where: { ro_number: { _ilike: $search } }) {
id
ro_number
ownr_fn
ownr_ln
}
}
`;
//TODO Ensure this is always up to date. //TODO Ensure this is always up to date.
export const QUERY_ALL_JOB_FIELDS = gql` export const QUERY_ALL_JOB_FIELDS = gql`
query QUERY_ALL_JOB_FIELDS($id: uuid!) { query QUERY_ALL_JOB_FIELDS($id: uuid!) {