BOD-14 Fixed some broken UI with temporary hard coded valeus. Added basic Chat tagging features
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 : {}}
|
||||
|
||||
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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={{
|
||||
|
||||
38
client/src/components/chat-tag-ro/chat-tag-ro.component.jsx
Normal file
38
client/src/components/chat-tag-ro/chat-tag-ro.component.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
46
client/src/components/chat-tag-ro/chat-tag-ro.container.jsx
Normal file
46
client/src/components/chat-tag-ro/chat-tag-ro.container.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -34,6 +34,14 @@ export const CONVERSATION_SUBSCRIPTION_BY_PK = gql`
|
||||
}
|
||||
id
|
||||
phone_num
|
||||
job_conversations {
|
||||
jobid
|
||||
conversationid
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
14
client/src/graphql/job-conversations.queries.js
Normal file
14
client/src/graphql/job-conversations.queries.js
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -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!) {
|
||||
|
||||
Reference in New Issue
Block a user