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 LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
import AlertComponent from "../alert/alert.component";
import ChatConversationTitle from "../chat-conversation-title/chat-conversation-title.component";
export default function ChatConversationComponent({
messages,
subState,
conversation,
unreadCount,
}) {
export default function ChatConversationComponent({ subState, conversation }) {
const [loading, error] = subState;
if (loading) return <LoadingSkeleton />;
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 (
<div className='chat-conversation'>
<Badge count={unreadCount}>
<ChatSendMessage conversation={conversation} />
<Card size='small'>
<ChatConversationTitle conversation={conversation} />
<ChatMessageListComponent messages={messages} />
<ChatSendMessage conversation={conversation} />
</Card>
</Badge>
</div>

View File

@@ -27,28 +27,9 @@ export function ChatConversationContainer({ selectedConversation }) {
return (
<ChatConversationComponent
subState={[loading, error]}
unreadCount={
(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) ||
[]
}
conversation={data ? data.conversations_by_pk : {}}
/>
);
}

View File

@@ -1,128 +1,127 @@
.messages {
height: auto;
min-height: calc(100% - 10px);
max-height: calc(100% - 93px);
overflow-y: scroll;
overflow-x: hidden;
}
@media screen and (max-width: 735px) {
.messages {
max-height: calc(100% - 105px);
}
}
.messages::-webkit-scrollbar {
width: 8px;
background: transparent;
}
.messages::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.3);
}
.messages ul li {
display: inline-block;
clear: both;
//float: left;
margin: 5px;
width: calc(100% - 25px);
font-size: 0.9em;
}
.messages ul li:nth-last-child(1) {
margin-bottom: 20px;
}
.messages ul li.sent img {
margin: 6px 8px 0 0;
}
.messages ul li.sent p {
background: #435f7a;
color: #f5f5f5;
}
.messages ul li.replies img {
float: right;
margin: 6px 0 0 8px;
}
.messages ul li.replies p {
background: #f5f5f5;
float: right;
}
.messages ul li img {
width: 22px;
border-radius: 50%;
float: left;
height: 350px;
min-height: calc(100% - 10px);
max-height: calc(100% - 93px);
overflow-y: scroll;
overflow-x: hidden;
}
@media screen and (max-width: 735px) {
.messages {
max-height: calc(100% - 105px);
}
}
.messages::-webkit-scrollbar {
width: 8px;
background: transparent;
}
.messages::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.3);
}
.messages ul li {
display: inline-block;
clear: both;
//float: left;
margin: 5px;
width: calc(100% - 25px);
font-size: 0.9em;
}
.messages ul li:nth-last-child(1) {
margin-bottom: 20px;
}
.messages ul li.sent img {
margin: 6px 8px 0 0;
}
.messages ul li.sent p {
background: #435f7a;
color: #f5f5f5;
}
.messages ul li.replies img {
float: right;
margin: 6px 0 0 8px;
}
.messages ul li.replies p {
background: #f5f5f5;
float: right;
}
.messages ul li img {
width: 22px;
border-radius: 50%;
float: left;
}
.messages ul li p {
display: inline-block;
padding: 10px 15px;
border-radius: 20px;
max-width: 205px;
line-height: 130%;
}
@media screen and (min-width: 735px) {
.messages ul li p {
display: inline-block;
padding: 10px 15px;
border-radius: 20px;
max-width: 205px;
line-height: 130%;
}
@media screen and (min-width: 735px) {
.messages ul li p {
max-width: 300px;
}
}
.message-input {
position: absolute;
bottom: 0;
width: 100%;
z-index: 99;
}
.message-input .wrap {
position: relative;
max-width: 300px;
}
}
.message-input {
position: absolute;
bottom: 0;
width: 100%;
z-index: 99;
}
.message-input .wrap {
position: relative;
}
.message-input .wrap input {
font-family: "proxima-nova", "Source Sans Pro", sans-serif;
float: left;
border: none;
width: calc(100% - 90px);
padding: 11px 32px 10px 8px;
font-size: 0.8em;
color: #32465a;
}
@media screen and (max-width: 735px) {
.message-input .wrap input {
font-family: "proxima-nova", "Source Sans Pro", sans-serif;
float: left;
border: none;
width: calc(100% - 90px);
padding: 11px 32px 10px 8px;
font-size: 0.8em;
color: #32465a;
}
@media screen and (max-width: 735px) {
.message-input .wrap input {
padding: 15px 32px 16px 8px;
}
}
.message-input .wrap input:focus {
outline: none;
padding: 15px 32px 16px 8px;
}
}
.message-input .wrap input:focus {
outline: none;
}
.message-input .wrap .attachment {
position: absolute;
right: 60px;
z-index: 4;
margin-top: 10px;
font-size: 1.1em;
color: #435f7a;
opacity: 0.5;
cursor: pointer;
}
@media screen and (max-width: 735px) {
.message-input .wrap .attachment {
position: absolute;
right: 60px;
z-index: 4;
margin-top: 10px;
font-size: 1.1em;
color: #435f7a;
opacity: 0.5;
cursor: pointer;
}
@media screen and (max-width: 735px) {
.message-input .wrap .attachment {
margin-top: 17px;
right: 65px;
}
}
.message-input .wrap .attachment:hover {
opacity: 1;
margin-top: 17px;
right: 65px;
}
}
.message-input .wrap .attachment:hover {
opacity: 1;
}
.message-input .wrap button {
float: right;
border: none;
width: 50px;
padding: 12px 0;
cursor: pointer;
background: #32465a;
color: #f5f5f5;
}
@media screen and (max-width: 735px) {
.message-input .wrap button {
float: right;
border: none;
width: 50px;
padding: 12px 0;
cursor: pointer;
background: #32465a;
color: #f5f5f5;
padding: 16px 0;
}
@media screen and (max-width: 735px) {
.message-input .wrap button {
padding: 16px 0;
}
}
.message-input .wrap button:hover {
background: #435f7a;
}
.message-input .wrap button:focus {
outline: none;
}
}
.message-input .wrap button:hover {
background: #435f7a;
}
.message-input .wrap button:focus {
outline: none;
}

View File

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