giao dien moi

This commit is contained in:
2021-05-22 09:54:28 +07:00
parent ab2830f449
commit 4770ce26d2
25 changed files with 23237 additions and 416 deletions

21576
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,10 +32,10 @@
"@types/node": "^12.20.7", "@types/node": "^12.20.7",
"@types/react": "^17.0.3", "@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3", "@types/react-dom": "^17.0.3",
"http-proxy-middleware": "^0.19.1",
"react-app-rewired": "^2.1.8", "react-app-rewired": "^2.1.8",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"web-vitals": "^1.1.1", "web-vitals": "^1.1.1"
"http-proxy-middleware": "^0.19.1"
}, },
"scripts": { "scripts": {
"start": "react-app-rewired start", "start": "react-app-rewired start",

View File

@@ -1,6 +1,6 @@
import React, { createRef, useState, Fragment, FC } from 'react'; import React, { createRef, useState, Fragment, FC } from 'react';
import { Layout, Col, Row, Button, Image } from 'antd'; import { Layout, Col, Row, Button, Image } from 'antd';
import { EditOutlined, CloseOutlined } from '@ant-design/icons'; import { EditOutlined, CloseOutlined, AppstoreOutlined } from '@ant-design/icons';
import ConversationList from "@/components/ConversationList"; import ConversationList from "@/components/ConversationList";
import Chatbox from "@/components/Chatbox"; import Chatbox from "@/components/Chatbox";
import CustomerInfo from "@/components/CustomerInfo"; import CustomerInfo from "@/components/CustomerInfo";
@@ -24,7 +24,7 @@ import TYPING_ANIMATION_IMAGE from "@/assets/typing-animation.gif";
import DashBoard from "@/components/DashBoard"; import DashBoard from "@/components/DashBoard";
import { user_list } from "@/test/test_state"; import { user_list } from "@/test/test_state";
import { DEFAULT_AVATAR } from "@/config";
import '@/styles/app.css'; import '@/styles/app.css';
@@ -50,17 +50,24 @@ const UserNameForm = ({user}: {user: UserInfo | null}) => {
})); }));
setFormOpen(false); setFormOpen(false);
} }
const avatar = user.avatar || DEFAULT_AVATAR;
if (!openForm) { if (!openForm) {
return ( return (
<Fragment> <div className="cuscontentCenterHeader">
<div className="avatar-cus">
<img className="conversation-photo" src={avatar} alt="conversation" />
<BadgeStatus online={user.online || false}> <BadgeStatus online={user.online || false}>
{user.name} </BadgeStatus>
</div>
<div className="info-cus">
<div className="name">{user.name}</div>
<div className="address">{user.location}</div>
</div>
{ {
user.typing && <Image preview={false} style={{ height: 20 }} src={TYPING_ANIMATION_IMAGE} /> user.typing && <Image preview={false} style={{ height: 20 }} src={TYPING_ANIMATION_IMAGE} />
} }<div className="cuscontentCenterHeaderEdit">( <EditOutlined onClick={() => setFormOpen(true)} title={'Thay đổi tên'} /> )</div>
</BadgeStatus> ( <EditOutlined onClick={() => setFormOpen(true)} title={'Thay đổi tên'} /> ) </div>
</Fragment>
) )
} }
@@ -77,9 +84,7 @@ function getUserById(user_list: UserInfo[], user_id: string) : UserInfo | null {
return filtered_list.length > 0 ? filtered_list[0] : null; return filtered_list.length > 0 ? filtered_list[0] : null;
} }
const ShowUserSpace = ({openHelp}: {openHelp: () => any}) => {
const ShowUserSpace = () => {
// TODO: // TODO:
const chatting_with_user = useSelector((state: AppState) => state.chatting_with_user); const chatting_with_user = useSelector((state: AppState) => state.chatting_with_user);
@@ -98,27 +103,35 @@ const ShowUserSpace = () => {
return ( return (
<Fragment> <Fragment>
<div> <div className="contentCenterHeader">
<UserNameForm user={chatting_user_info} /> <UserNameForm user={chatting_user_info} />
<div onClick={openHelp} className="contentCenterHeaderAdmin">
<span className="h-title">Trợ giúp admin</span>
<AppstoreOutlined className="h-icon" />
</div>
</div> </div>
<div className="contentCenterCt">
<Row> <Row>
<Col span={12}> <Col className="contentCenterCtColLeft" span={12}>
{/* add key to force to component to remount when user id change to simplify the component's code*/} {/* add key to force to component to remount when user id change to simplify the component's code*/}
<Chatbox key={chatting_user_info.id} user_info={chatting_user_info} /> <Chatbox key={chatting_user_info.id} user_info={chatting_user_info} />
</Col> </Col>
<Col span={12} className="scrollable" style={{height: 600, overflow: 'auto'}}> <Col span={12} className="contentCenterCtColRight">
<h3 style={{fontSize:16, fontWeight:'bold'}}>Lựa chọn</h3> <div className="contentCenterCtColRightItem">
<h3 className="contentCenterCtColRightTitle" style={{ fontSize: 16, fontWeight: 'bold' }}>Lựa chọn</h3>
<ActionTabs customer_id={chatting_user_info.id} /> <ActionTabs customer_id={chatting_user_info.id} />
</div>
<h3 style={{fontSize:16, fontWeight:'bold'}}>Thông tin khách hàng</h3> <div className="contentCenterCtColRightItem">
<h3 className="contentCenterCtColRightTitle" style={{ fontSize: 16, fontWeight: 'bold' }}>Thông tin khách hàng</h3>
<CustomerInfo user_info={chatting_user_info} /> <CustomerInfo user_info={chatting_user_info} />
</div>
</Col> </Col>
</Row> </Row>
</div>
</Fragment> </Fragment>
) )
} }
@@ -129,29 +142,55 @@ const App: FC<{client_setting: ClientSettings}> = ({client_setting}) => {
const HEADER_HEIGHT = 70; const HEADER_HEIGHT = 70;
const [closeHelp, setHelpClose] = useState<boolean>(false); const [closeHelp, setHelpClose] = useState<boolean>(false);
const LAYOUT_CLOSED = closeHelp ? { width: 1200, marginLeft: 'auto', marginRight: 'auto' } : { marginTop: HEADER_HEIGHT, height: WINDOW_HEIGHT - HEADER_HEIGHT, overflow: 'auto' }; const LAYOUT_CLOSED = closeHelp ? { width: 1200, marginLeft: 'auto', marginRight: 'auto' } : { marginTop: HEADER_HEIGHT, height: WINDOW_HEIGHT - HEADER_HEIGHT, overflow: 'auto' };
const windowWidth = window.innerWidth;
return ( return (
<Layout> <Layout className={'box-chatboard'}>
<Header style={{ position: 'fixed', zIndex: 1, width: '100%' }}> <Header style={{ width: '100%' }}>
<HeaderComponent /> <HeaderComponent />
</Header> </Header>
<Layout style={LAYOUT_CLOSED}> <Layout className={'content-chatboard'}>
<Row className={'content-chatboard-row'}>
<Sider width={250} className="scrollable"> <Col className={'content-chatboard-col'} span={4}>
<div className="contentLeft">
<ConversationList /> <ConversationList />
</Sider> </div>
</Col>
<Content> {
<ShowUserSpace /> windowWidth <= 1366 ?
<Col className={'content-chatboard-col'} span={20}>
<Content className="contentCenter">
<ShowUserSpace openHelp={() => setHelpClose(true)} />
</Content> </Content>
</Col>
:
<Col className={'content-chatboard-col content-chatboard-col-big'} span={14}>
<Content className="contentCenter">
<ShowUserSpace openHelp={() => setHelpClose(true)} />
</Content>
</Col>
}
{ {
!closeHelp && windowWidth <= 1366 ?
<Sider width={400} > (
<h2>Trợ giúp <CloseOutlined onClick={() => setHelpClose(true)} title={'Đóng'} /> </h2> closeHelp &&
<div className="fix-ab-ct-right" id="fix-ab-ct-right">
<div className="contentRight" >
<h2 className="contentRightHeader">Trợ giúp Admin <CloseOutlined onClick={() => setHelpClose(false)} title={'Đóng'} /> </h2>
<HelpSideBar /> <HelpSideBar />
</Sider> </div>
</div>
)
:
<Col className={'content-chatboard-col'} span={6}>
<div className="contentRight" >
<h2 className="contentRightHeader">Trợ giúp Admin <CloseOutlined onClick={() => setHelpClose(false)} title={'Đóng'} /> </h2>
<HelpSideBar />
</div>
</Col>
} }
</Row>
</Layout> </Layout>
<NetworkError /> <NetworkError />

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
src/assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -54,8 +54,13 @@ const NoteForm = ({customer_id}: {customer_id: string|number}) => {
e.preventDefault(); e.preventDefault();
form.submit(); form.submit();
}} }}
className="taoghichu-input"
/> />
</Form.Item> </Form.Item>
<div className="taoghichu-submit">
<button className="taoghichu-bt-update">Cập nhật</button>
<button className="taoghichu-bt-send">Gửi</button>
</div>
</Form> </Form>
) )
} }

View File

@@ -12,27 +12,27 @@ const { TabPane } = Tabs;
const ActionTabs = ({customer_id}: {customer_id: string|number}) => { const ActionTabs = ({customer_id}: {customer_id: string|number}) => {
return ( return (
<Tabs key={customer_id} onChange={(key) => null } defaultActiveKey={'note'} type="card"> <Tabs className="tab-content-mes" key={customer_id} onChange={(key) => null } defaultActiveKey={'note'} type="card">
<TabPane tab="Tạo ghi chú" key="note"> <TabPane className="formTaoGhiChu" tab="Tạo ghi chú" key="note">
<Suspense fallback={<Loading />} > <Suspense fallback={<Loading />} >
<CreateNoteComponent customer_id={customer_id}/> <CreateNoteComponent customer_id={customer_id}/>
</Suspense> </Suspense>
</TabPane> </TabPane>
<TabPane tab="Tạo hỗ trợ" key="ticket"> <TabPane className="formTaoHoTro" tab="Tạo hỗ trợ" key="ticket">
<Suspense fallback={<Loading />} > <Suspense fallback={<Loading />} >
<CreateSupportComponent customer_id={customer_id}/> <CreateSupportComponent customer_id={customer_id}/>
</Suspense> </Suspense>
</TabPane> </TabPane>
<TabPane tab="Tạo đơn hàng" key="order"> <TabPane className="formTaoDonHang" tab="Tạo đơn hàng" key="order">
<Suspense fallback={<Loading />} > <Suspense fallback={<Loading />} >
<CreateOrderComponent customer_id={customer_id}/> <CreateOrderComponent customer_id={customer_id}/>
</Suspense> </Suspense>
</TabPane> </TabPane>
<TabPane tab="Tag" key="tag"> <TabPane className="formTag" tab="Tag" key="tag">
<Suspense fallback={<Loading />} > <Suspense fallback={<Loading />} >
<CreateTag customer_id={customer_id}/> <CreateTag customer_id={customer_id}/>
</Suspense> </Suspense>

View File

@@ -39,7 +39,15 @@
} }
.message-list-container { .message-list-container {
padding: 10px; padding: 20px;
padding-right: 10px;
}
.contentCenterCt .message-list-all {
height: calc(100% - 100px);
padding: 20px 0px;
}
.contentCenterCt .message-list {
height: 100%;
} }
.message { .message {
@@ -106,3 +114,123 @@
border-top-right-radius: 2px; border-top-right-radius: 2px;
border-bottom-right-radius: 2px; border-bottom-right-radius: 2px;
} }
.contentCenterCt .message-list::-webkit-scrollbar {
width: 7px;
}
.contentCenterCt .message-list::-webkit-scrollbar-track {
background: #fff;
}
.contentCenterCt .message-list::-webkit-scrollbar-thumb {
background: #C4C4C4;
border-radius: 4px;
}
.contentCenterCt .message-list .time-line-chat {
width: 100%;
float: left;
text-align: center;
margin: 20px 0px;
position: relative;
}
.contentCenterCt .message-list .time-line-chat::after {
width: 100%;
height: 1px;
background: #F1EFEF;
position: absolute;
top: 50%;
left: 0px;
content: '';
}
.contentCenterCt .message-list .time-line-chat span {
padding: 0px 10px;
background: #fff;
position: relative;
z-index: 1;
color: #676767;
font-size: 11px;
font-weight: 700;
}
.contentCenterCt .message-list .message {
width: 100%;
float: left;
margin-bottom: 20px;
}
.mes-content {
width: 100%;
float: left;
display: flex;
}
.mes-content .mes-contentLeft {
width: 36px;
float: left;
margin-right: 10px;
}
.mes-content .mes-contentLeft .mes-img {
width: 36px;
height: 36px;
border-radius: 50%;
overflow: hidden;
float: left;
}
.mes-content .mes-contentLeft .mes-img img {
max-width: 100%;
}
.mes-content .mes-content-right {
width: calc(100% - 46px);
float: left;
}
.mes-content .mes-content-right .mes-content-header {
width: 100%;
float: left;
margin-bottom: 2px;
}
.mes-content .mes-content-right .mes-content-header .mes-contentName {
font-size: 15px;
font-weight: 700;
color: #000;
margin-right: 10px;
}
.mes-content .mes-content-right .mes-content-header .mes-content-time {
font-size: 11px;
color: #A2A2A2;
}
.mes-content .mes-content-right .bubble-container {
width: 100%;
float: left;
}
.message .bubble-container .bubble {
width: 100%;
float: left;
padding: 0px;
background: none !important;
font-size: 14px;
color: #000 !important;
text-align: left;
max-width: 100%;
margin: 0px !important;
}
.chatBottomContent {
width: 100%;
float: left;
border-top: solid 1px #F1EFEF;
padding: 10px;
padding-left: 20px;
}
.chatBottomContent textarea {
height: 40px !important;
min-height: 40px !important;
border: solid 1px #707070;
border-radius: 5px;
margin-bottom: 10px;
}
.chatBottomContent span.bt-mes-bt {
font-size: 12px;
color: #858585;
font-weight: 500;
margin-right: 10px;
}
.chatBottomContent span.bt-mes-bt span {
font-size: 14px;
color: #858585;
}

View File

@@ -269,8 +269,9 @@ class Chatbox extends Component<ChatboxProps, ChatboxState> {
return ( return (
<Fragment > <Fragment >
<div className={'message-list-all'}>
<div <div
style={{height: 600, overflow: 'auto'}} style={{ overflow: 'auto' }}
onScroll={this.handleChatboxScroll} onScroll={this.handleChatboxScroll}
// onClick={listenForInChatAction} // onClick={listenForInChatAction}
ref={this.scrollToHistoryDiv} ref={this.scrollToHistoryDiv}
@@ -282,7 +283,19 @@ class Chatbox extends Component<ChatboxProps, ChatboxState> {
{this.showLoadingHistory()} {this.showLoadingHistory()}
{ {
chat_messages.map((message, index) => <MessageItem key={message.time + '-' + index} is_me={admin_info.id === message.from} {...message} />) chat_messages.map((message, index) => {
return (
<>
{
index % 3 == 1 && <div className="time-line-chat"><span>15pm 20-05-2021</span></div>
}
<MessageItem key={message.time + '-' + index} is_me={admin_info.id === message.from} {...message} />
</>
)
})
} }
{user_info.typing && <TypingNotification />} {user_info.typing && <TypingNotification />}
@@ -293,7 +306,7 @@ class Chatbox extends Component<ChatboxProps, ChatboxState> {
<div ref={this.scrollBottomDiv} /> <div ref={this.scrollBottomDiv} />
</div> </div>
</div>
<InputMessage <InputMessage
sendMessage={this.sendMessage} sendMessage={this.sendMessage}
network_connection={network_connection} network_connection={network_connection}

View File

@@ -62,7 +62,7 @@ const InputMessage = ({network_connection, sendMessage, user_info} : {network_co
return ( return (
<Fragment> <div className="chatBottomContent">
<Input.TextArea <Input.TextArea
// ref={this.inputRef} // ref={this.inputRef}
value={typed_message} value={typed_message}
@@ -100,7 +100,7 @@ const InputMessage = ({network_connection, sendMessage, user_info} : {network_co
okText="Yes" okText="Yes"
cancelText="No" cancelText="No"
> >
<span style={{cursor: 'pointer'}} ><MinusCircleOutlined /> Forbid</span> <span className="bt-mes-bt" style={{cursor: 'pointer'}} ><MinusCircleOutlined /> Forbid</span>
</Popconfirm> </Popconfirm>
@@ -111,7 +111,7 @@ const InputMessage = ({network_connection, sendMessage, user_info} : {network_co
okText="Yes" okText="Yes"
cancelText="No" cancelText="No"
> >
<span style={{cursor: 'pointer'}} ><MinusCircleOutlined /> Dừng chat</span> <span className="bt-mes-bt" style={{cursor: 'pointer'}} ><MinusCircleOutlined /> Dừng chat</span>
</Popconfirm> </Popconfirm>
&nbsp; &nbsp;
@@ -121,7 +121,7 @@ const InputMessage = ({network_connection, sendMessage, user_info} : {network_co
content={<PickAdminOnlineToTransfer />} content={<PickAdminOnlineToTransfer />}
trigger="click" trigger="click"
> >
<span style={{cursor: 'pointer'}}><FastForwardOutlined />Chuyển người khác</span> <span className="bt-mes-bt" style={{cursor: 'pointer'}}><FastForwardOutlined />Chuyển người khác</span>
</Popover> </Popover>
@@ -130,7 +130,7 @@ const InputMessage = ({network_connection, sendMessage, user_info} : {network_co
ref={imageUploadWithPreviewRef} ref={imageUploadWithPreviewRef}
auth={admin_info.jwt||''} auth={admin_info.jwt||''}
// current_list={SAMPLE_UPLOAD_FILE} // current_list={SAMPLE_UPLOAD_FILE}
uploadButton={<span title={'Upload file'} style={{cursor: 'pointer'}}><PaperClipOutlined /> Upload</span>} uploadButton={<span className="bt-mes-bt" title={'Upload file'} style={{cursor: 'pointer'}}><PaperClipOutlined /> Upload</span>}
listType={'picture'} listType={'picture'}
showUploadList={false} showUploadList={false}
multiple={false} multiple={false}
@@ -151,7 +151,7 @@ const InputMessage = ({network_connection, sendMessage, user_info} : {network_co
} }
}} }}
/> />
</Fragment> </div>
) )
}; };

View File

@@ -2,6 +2,7 @@ import {isUrlImage, validURL} from "@/lib/validation";
import { maskExternalUrl, randomBetween, showUnixTime } from "@/lib/utils"; import { maskExternalUrl, randomBetween, showUnixTime } from "@/lib/utils";
import { ChatboxTextMessage } from "@/typings/message.d"; import { ChatboxTextMessage } from "@/typings/message.d";
import React from "react"; import React from "react";
import { DEFAULT_AVATAR } from "@/config";
export const MessageItem = (props: { is_me: boolean } & ChatboxTextMessage) => { export const MessageItem = (props: { is_me: boolean } & ChatboxTextMessage) => {
@@ -65,6 +66,7 @@ export const MessageItem = (props: {is_me: boolean} & ChatboxTextMessage) => {
// todo: remove this // todo: remove this
let random_number = randomBetween(1, 100); let random_number = randomBetween(1, 100);
let message_from_me = random_number % 2 === 0; let message_from_me = random_number % 2 === 0;
const avatar = DEFAULT_AVATAR;
return ( return (
@@ -77,10 +79,17 @@ export const MessageItem = (props: {is_me: boolean} & ChatboxTextMessage) => {
//`${endsSequence ? 'end' : ''}` //`${endsSequence ? 'end' : ''}`
].join(' ')}> ].join(' ')}>
<div className="timestamp"> <div className="mes-content">
{from}: { showUnixTime(time_in_second) } <div className="mes-contentLeft">
<span className="mes-img">
<img className="chat-photo" src={avatar} alt="chatavatar" />
</span>
</div>
<div className="mes-content-right">
<div className="mes-content-header">
<span className="mes-contentName">Trần Hằng</span>
<span className="mes-content-time">11:20</span>
</div> </div>
<div className="bubble-container"> <div className="bubble-container">
<div className="bubble" style={{ color: (deliveryStatus !== 4) ? status_colors['sending'] : '' }} > <div className="bubble" style={{ color: (deliveryStatus !== 4) ? status_colors['sending'] : '' }} >
{content} {content}
@@ -89,8 +98,8 @@ export const MessageItem = (props: {is_me: boolean} & ChatboxTextMessage) => {
{ {
deliveryStatus === 5 && (<div style={{ color: 'red' }}>(Lỗi xảy ra, tin nhắn chưa đưc gửi)</div>) deliveryStatus === 5 && (<div style={{ color: 'red' }}>(Lỗi xảy ra, tin nhắn chưa đưc gửi)</div>)
} }
</div>
<div>deliveryStatus :{deliveryStatus} | sq:{props.sequence}</div> </div>
</div> </div>
) )

View File

@@ -104,7 +104,7 @@ const CommentList = ({item_type, item_id} : {item_type: string, item_id?: string
return ( return (
<div > <div className="form-commet-all">
<Divider /> <Divider />

View File

@@ -1,6 +1,7 @@
.conversation-list { .conversation-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
} }
.conversation-search { .conversation-search {
@@ -10,11 +11,9 @@
} }
.conversation-search-input { .conversation-search-input {
background: #f4f4f8; border: none !important;
padding: 8px 10px; font-size: 14px !important;
border-radius: 10px; padding: 0px !important;
border: none;
font-size: 14px;
} }
.conversation-search-input::placeholder { .conversation-search-input::placeholder {
@@ -61,3 +60,206 @@
color: #CCCCCC; color: #CCCCCC;
margin: 0; margin: 0;
} }
.box-search-customer {
width: 100%;
float: left;
padding: 10px;
border-bottom: solid 1px rgba(255, 255, 255, 0.15);
}
.box-search-customer .search-customer-form {
width: 100%;
border: solid 1px #707070;
border-radius: 5px;
overflow: hidden;
margin-bottom: 8px;
}
.box-search-customer .search-customer-form .search-input {
width: calc(100% - 36px);
height: 32px;
line-height: 32px;
padding: 0px 10px !important;
border: none;
outline: none;
}
.box-search-customer .search-customer-form button {
width: 36px;
float: right;
border: none;
background: #fff;
line-height: 32px;
text-align: center;
font-size: 16px;
color: rgba(65, 65, 65, 0.5);
}
.box-search-customer .filter-search-customer-form {
width: 100%;
float: left;
}
.box-search-customer .filter-search-customer-form span {
float: left;
margin-right: 10px;
padding: 0px 15px;
line-height: 30px;
border: solid 1px #575757;
border-radius: 16px;
font-size: 14px;
color: #fff;
cursor: pointer;
}
.box-search-customer .filter-search-customer-form span.select {
border: solid 1px #5DD498;
color: #5DD498;
}
.box-search-customer .filter-search-customer-form span:hover {
border: solid 1px #5DD498;
color: #5DD498;
}
.filter-customer-chat {
width: 100%;
float: left;
padding: 10px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
position: relative;
height: 52px;
}
.filter-customer-chat .fcc-item span {
color: #fff;
font-size: 12px;
line-height: 30px;
}
.conversation-list-item {
width: 100%;
float: left;
padding: 10px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
position: relative;
cursor: pointer;
}
.conversation-list-item:hover {
background: rgba(71, 141, 220, 0.3);
}
.conversation-list-item.conversation-list-item-selected {
background: rgba(71, 141, 220, 0.3);
}
.conversation-list-item .avatar-cus {
width: 36px;
height: 36px;
float: left;
margin-right: 10px;
position: relative;
}
.conversation-list-item .avatar-cus img {
width: 36px;
height: 36px;
float: left;
overflow: hidden;
border-radius: 50%;
}
.conversation-list-item .ant-badge-status {
position: absolute !important;
z-index: 1;
bottom: -7px;
right: 0px;
display: block !important;
}
.conversation-list-item .ant-badge-status-text {
display: none !important;
}
.conversation-list-item .avatar-cus .ant-badge-status-dot {
width: 12px;
height: 12px;
border: solid 2px #001F3D;
border-radius: 50%;
background: #9A9A9A;
}
.conversation-list-item .avatar-cus .ant-badge-status-dot.ant-badge-status-green {
background: #37EE14;
}
.conversation-list-item .info-cus .name {
font-size: 15px;
font-weight: 700;
color: #fff;
margin-bottom: 5px;
}
.conversation-list-item .info-cus .address {
font-size: 13px;
font-weight: 300;
color: #fff;
}
.conversation-list-item .count-mes {
padding: 0px 12px;
line-height: 20px;
background: #C70000;
border-radius: 10px;
font-size: 14px;
font-weight: 500;
color: #fff;
position: absolute;
z-index: 1;
right: 10px;
top: 10px;
}
.list-customer-chat {
width: 100%;
height: calc(100% - 147px);
float: left;
overflow-y: auto;
}
.cuscontentCenterHeaderEdit {
margin-top: -25px;
}
.contentCenterHeader input {
width: 240px;
float: left;
border: solid 1px #001F3D;
border-radius: 0px;
outline: none;
height: 40px;
padding: 0px 10px;
}
.contentCenterHeader .ant-btn {
height: 40px;
border: none !important;
background: #001F3D !important;
margin-left: -1px;
color: #fff !important;
}

View File

@@ -1,6 +1,6 @@
import React, { createRef, useState } from 'react'; import React, { createRef, useState } from 'react';
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import {Image, Input} from "antd"; import { Image, Input, Menu, Dropdown, Layout } from "antd";
import classNames from "classnames"; import classNames from "classnames";
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
@@ -18,7 +18,9 @@ import './ConversationList.css';
import { isFound } from "@/lib/vietnamese"; import { isFound } from "@/lib/vietnamese";
import { user_list } from "@/test/test_state"; import { user_list } from "@/test/test_state";
import { SearchOutlined, DownOutlined } from '@ant-design/icons';
const { Sider } = Layout;
//Note: this component has been well-tested, do NOT change it!! //Note: this component has been well-tested, do NOT change it!!
const ConversationSearch = ({ setKeyword }: { setKeyword: (keyword: string) => void }) => { const ConversationSearch = ({ setKeyword }: { setKeyword: (keyword: string) => void }) => {
@@ -32,15 +34,24 @@ const ConversationSearch = ({setKeyword} : {setKeyword: (keyword: string) => voi
} }
return ( return (
<div className="conversation-search"> <div className="box-search-customer">
<div className="search-customer-form">
<Input <Input
ref={inputRef} ref={inputRef}
type="search" type="search"
className="conversation-search-input" className="conversation-search-input search-input"
placeholder="Tìm kiếm" placeholder="Tìm kiếm"
onChange={debounce(startSearch, search_wait_time)} onChange={debounce(startSearch, search_wait_time)}
allowClear allowClear
/> />
<button type="submit">
<SearchOutlined />
</button>
</div>
<div className="filter-search-customer-form">
<span className="select">Tât cả</span>
<span>Đang online</span>
</div>
</div> </div>
); );
} }
@@ -68,21 +79,86 @@ const ConversationListItem = (props: { user: UserInfo, unread: { total: number,
return ( return (
<div className={containerClass} onClick={() => pickUserToChat()} title={unread.messages.join('\n')}> <div className={containerClass} onClick={() => pickUserToChat()} title={unread.messages.join('\n')}>
<BadgeStatus online={user.online || false}> <div className="avatar-cus">
<img className="conversation-photo" src={avatar} alt="conversation" /> <img className="conversation-photo" src={avatar} alt="conversation" />
<BadgeStatus online={user.online || false}>
</BadgeStatus> </BadgeStatus>
<div className="conversation-info">
<h1 className="conversation-title">{ user.name } ({user.location})</h1>
<div>{user.id}</div>
{
user.typing && <Image preview={false} style={{height:20}} src={TYPING_ANIMATION_IMAGE} />
}
</div> </div>
<div className="info-cus">
<div className="name">{user.name}</div>
<div className="address">{user.location}</div>
</div>
<div className="count-mes">5</div>
</div> </div>
); );
} }
const TongKhachHang = () => {
return (
<Toolbar
title={`Khách hàng (${user_list.length})`}
leftItems={[
<ToolbarButton key="cog" icon="ion-ios-cog" />
]}
rightItems={[
<ToolbarButton key="add" icon="ion-ios-add-circle-outline" />
]}
/>
)
}
const ListSort = () => {
return (
<Menu>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="/">
Tin nhắn đã xem
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="/">
Tin nhắn chưa xem
</a>
</Menu.Item>
</Menu>
)
}
const ListProvince = () => {
return (
<Menu>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="/">
Nội
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="/">
Hải phòng
</a>
</Menu.Item>
</Menu>
)
}
const FilterSortConversationList = () => {
return (
<div className="filter-customer-chat">
<Dropdown overlay={ListSort}>
<a className="ant-dropdown-link ant-dropdown-link-style-new fcc-item" onClick={e => e.preventDefault()}>
<span>Tin nhắn mới <DownOutlined /></span>
</a>
</Dropdown>
<Dropdown overlay={ListProvince}>
<a className="ant-dropdown-link ant-dropdown-link-style-new fcc-item" onClick={e => e.preventDefault()}>
<span>Tỉnh thành <DownOutlined /></span>
</a>
</Dropdown>
</div>
)
}
const ConversationList = () => { const ConversationList = () => {
@@ -116,26 +192,17 @@ const ConversationList = () => {
return ( return (
<div className="conversation-list"> <div className="conversation-list">
<Toolbar
title={`Khách hàng (${user_list.length})`}
leftItems={[
<ToolbarButton key="cog" icon="ion-ios-cog" />
]}
rightItems={[
<ToolbarButton key="add" icon="ion-ios-add-circle-outline" />
]}
/>
<ConversationSearch setKeyword={setKeyword} /> <ConversationSearch setKeyword={setKeyword} />
<FilterSortConversationList />
<div className="list-customer-chat">
{ {
getUserList(keyword).map( getUserList(keyword).map(
(user: UserInfo) => <ConversationListItem key={user.id} user={user} unread={un_read_message_per_user[user.id] || no_unread} /> (user: UserInfo) => <ConversationListItem key={user.id} user={user} unread={un_read_message_per_user[user.id] || no_unread} />
) )
} }
</div>
</div> </div>
); );
} }

View File

@@ -15,8 +15,8 @@ const BrowseHistoryComponent = React.lazy(() => import('./components/Brow
const CustomerInfo = ({user_info}: {user_info: UserInfo}) => { const CustomerInfo = ({user_info}: {user_info: UserInfo}) => {
return ( return (
<Tabs type="card"> <Tabs className="tab-content-mes" type="card">
<TabPane tab="Thông tin" key="info"> <TabPane className="formThongTin" tab="Thông tin" key="info">
<Suspense fallback={<Loading />} > <Suspense fallback={<Loading />} >
<CustomerInfoComponent user_info={user_info} key={'customer-info'}/> <CustomerInfoComponent user_info={user_info} key={'customer-info'}/>
</Suspense> </Suspense>

View File

@@ -4,10 +4,11 @@ import {actions} from "@/store/actions";
import { AppState } from "@/store/typing"; import { AppState } from "@/store/typing";
import { getAdminInfo } from "@/lib/user"; import { getAdminInfo } from "@/lib/user";
import { Col, Row, Image, Menu, Dropdown } from "antd"; import { Col, Row, Image, Menu, Dropdown } from "antd";
import { DownOutlined, TeamOutlined, UserOutlined, QuestionOutlined } from '@ant-design/icons'; import { DownOutlined, TeamOutlined, UserOutlined, CaretDownFilled, QuestionCircleOutlined } from '@ant-design/icons';
import "./styles.css"; import "./styles.css";
import { MenuInfo } from "rc-menu/lib/interface"; import { MenuInfo } from "rc-menu/lib/interface";
import logo from '../../assets/images/logo.png'
const menu = ( const menu = (
@@ -54,6 +55,15 @@ const HeaderComponent = () => {
setOpenModal(e.key + '') setOpenModal(e.key + '')
} }
const menuAdminAccount = (
<Menu>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="/">
Đăng xuất
</a>
</Menu.Item>
</Menu>
)
const TOTAL_ADMIN = admin_list.length; const TOTAL_ADMIN = admin_list.length;
const ADMIN_ONLINE = admin_list.filter(admin => admin.online).length; const ADMIN_ONLINE = admin_list.filter(admin => admin.online).length;
@@ -64,17 +74,25 @@ const HeaderComponent = () => {
<Fragment> <Fragment>
<Row className={'header'}> <Row className={'header'}>
<Col span={4}>
<div className={'logo'}>
<Image
src={logo}
style={{ width: 154, height: 43 }}
preview={false}
/>
</div>
</Col>
<Col span={16}>
<Col span={20}> <Menu className={'header-info'} mode="horizontal" onClick={handleMenuClick}>
<Menu.Item className={'header-info-click'} key="user" icon={<UserOutlined />} >
<Menu mode="horizontal" onClick={handleMenuClick}>
<Menu.Item key="user" icon={<UserOutlined />} >
Người dùng online ({current_stats.user_online}) Người dùng online ({current_stats.user_online})
</Menu.Item> </Menu.Item>
<Menu.Item key="admin" icon={<TeamOutlined />} > <Menu.Item className={'header-info-click'} key="admin" icon={<TeamOutlined />} >
Quản trị viên online ({ADMIN_ONLINE}/{TOTAL_ADMIN}) Quản trị viên online ({ADMIN_ONLINE}/{TOTAL_ADMIN})
</Menu.Item> </Menu.Item>
<Menu.Item key="help" icon={<QuestionOutlined />}> <Menu.Item className={'header-info-click'} key="help" icon={<QuestionCircleOutlined />}>
Trợ giúp Trợ giúp
</Menu.Item> </Menu.Item>
</Menu> </Menu>
@@ -82,18 +100,19 @@ const HeaderComponent = () => {
</Col> </Col>
<Col span={4}> <Col span={4}>
<div className={'header-avatar'}>
<Dropdown overlay={menu}> <Dropdown overlay={menuAdminAccount}>
<Fragment> <a className="ant-dropdown-link ant-dropdown-link-style-new" onClick={e => e.preventDefault()}>
<Image <Image
src={'https://chatngay-static.glee.vn/avatar/120_a1.jpg'} src={'https://chatngay-static.glee.vn/avatar/120_a1.jpg'}
style={{ width: 50, height: 50 }} style={{ width: 50, height: 50 }}
preview={false} preview={false}
className={'header-avatar-img'}
/> />
<span className="name">{admin_info.name}</span> <span className="name-admin">{admin_info.name} <CaretDownFilled /></span>
</Fragment> </a>
</Dropdown> </Dropdown>
</div>
</Col> </Col>
</Row> </Row>

View File

@@ -1,9 +1,73 @@
.header { .header {
color: white; color: white;
width: 100%;
height: 55px;
background: #001B34;
display: flex;
align-items: center;
} }
.ant-layout-header .ant-menu { .ant-layout-header .ant-menu {
background: black; background: #001B34;
color: white; color: white;
} }
.ant-layout-header .ant-menu {
line-height: 55px;
}
.ant-layout-header {
height: 55px !important;
}
.logo {
width: 100%;
float: left;
text-align: center;
height: 55px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.header-info {
border-bottom: none !important;
}
.header-info li {
height: 55px;
}
.header-info-click {
font-size: 15px;
color: #fff;
margin-right: 50px;
float: left;
border: none !important;
}
.header-avatar {
float: right;
}
.header-avatar-img {
width: 36px;
height: 36px;
border-radius: 50%;
overflow: hidden;
margin-right: 10px;
float: left;
}
.name-admin {
font-size: 15px;
font-weight: 700;
color: #fff;
line-height: 36px;
display: flex;
align-items: center;
padding-right: 20px;
}
.name-admin span {
margin-left: 10px;
}
.ant-dropdown-link-style-new {
display: flex;
}

View File

@@ -3,6 +3,7 @@ import api from "@/lib/api";
import Loading from "@/components/Loading"; import Loading from "@/components/Loading";
import { HelpType, OpenHelpComponent } from "@/typings"; import { HelpType, OpenHelpComponent } from "@/typings";
import SearchBox from "./components/SearchBox"; import SearchBox from "./components/SearchBox";
import { ArrowRightOutlined } from '@ant-design/icons';
const ProductListComponent = React.lazy(() => import('./components/ProductList')); const ProductListComponent = React.lazy(() => import('./components/ProductList'));
const ArticleListComponent = React.lazy(() => import('./components/ArticleList')); const ArticleListComponent = React.lazy(() => import('./components/ArticleList'));
@@ -28,25 +29,27 @@ const HomeComponent = (props: {openComponent: (type: HelpType, params?: { [key:
}, []); }, []);
return ( return (
<> <div className="helpContent">
<div className={'scrollable'} style={{height: HELP_HEIGHT}}> <div className={'helpContentRight'}>
<h3 style={{fontSize: 16}}>Sản phẩm: <span className={'a-link'} onClick={() => props.openComponent('product-list')}>Xem het</span></h3>
<h3 className="titleHelpRight">Sản phẩm: <span className={'a-link'} onClick={() => props.openComponent('product-list')}>Xem tất cả <ArrowRightOutlined /></span></h3>
<div className={'scrollable helpContentRightScroll'}>
<ProductListComponent <ProductListComponent
defaultList={help.product.slice(0, 5)} defaultList={help.product.slice(0, 5)}
openItem={(id) => props.openComponent('product-detail', { id })} openItem={(id) => props.openComponent('product-detail', { id })}
/> />
</div>
<h3 style={{fontSize: 16}}>Kho kiến thức: <span className={'a-link'} onClick={() => props.openComponent('article-list')}>Xem het</span></h3> </div>
<div className={'helpContentRight'}>
<h3 className="titleHelpRight">Kho kiến thức: <span className={'a-link'} onClick={() => props.openComponent('article-list')}>Xem tất cả <ArrowRightOutlined /></span></h3>
<div className={'scrollable helpContentRightScroll'}>
<ArticleListComponent <ArticleListComponent
defaultList={help.article.slice(0, 5)} defaultList={help.article.slice(0, 5)}
openItem={(id) => props.openComponent('article-detail', { id })} openItem={(id) => props.openComponent('article-detail', { id })}
/> />
</div> </div>
</> </div>
</div>
) )
} }

View File

@@ -2,7 +2,7 @@ import React, {useEffect, useState} from "react";
import api from "@/lib/api"; import api from "@/lib/api";
import { HelpItem } from "@/typings"; import { HelpItem } from "@/typings";
import Comment from "@/components/Comment"; import Comment from "@/components/Comment";
import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
const ArticleDetailComponent = ({ id, openHome }: { id?: string | number, openHome: () => void }) => { const ArticleDetailComponent = ({ id, openHome }: { id?: string | number, openHome: () => void }) => {
@@ -21,13 +21,18 @@ const ArticleDetailComponent = ({id, openHome}: {id?: string|number, openHome: (
return ( return (
<> <>
<div><span className={'a-link'} onClick={openHome}>Back to home</span></div> <div className="product-detail-header">
<div className="product-detail-header-left"><span className={'a-link'} onClick={openHome}><ArrowLeftOutlined /> Quay lại</span></div>
</div>
<div className="product-detail-content article-detail-content">
<h3>{article_info.name}</h3> <h3>{article_info.name}</h3>
<div>{article_info.summary}</div> <div className="article-detail-content-ct">{article_info.summary}</div>
<Comment item_type={'product'} item_id={article_info.id} /> <Comment item_type={'product'} item_id={article_info.id} />
</div>
</> </>
) )

View File

@@ -1,8 +1,9 @@
import { List, Pagination} from "antd"; import { List, Pagination, Select} from "antd";
import React, {useEffect, useRef, useState} from "react"; import React, {useEffect, useRef, useState} from "react";
import {HelpItem} from "@/typings"; import {HelpItem} from "@/typings";
import api from "@/lib/api"; import api from "@/lib/api";
import Loading from "@/components/Loading"; import Loading from "@/components/Loading";
import { CheckOutlined, CloseOutlined, ArrowLeftOutlined } from '@ant-design/icons';
const TopListComponent = (props: {defaultList: HelpItem[], openItem: (id?: string|number) => void}) => { const TopListComponent = (props: {defaultList: HelpItem[], openItem: (id?: string|number) => void}) => {
@@ -12,6 +13,7 @@ const TopListComponent = (props: {defaultList: HelpItem[], openItem: (id?: strin
return ( return (
<> <>
<List <List
className="Article-list"
itemLayout="horizontal" itemLayout="horizontal"
dataSource={defaultList} dataSource={defaultList}
renderItem={(item: HelpItem) => ( renderItem={(item: HelpItem) => (
@@ -27,6 +29,21 @@ const TopListComponent = (props: {defaultList: HelpItem[], openItem: (id?: strin
) )
} }
const { Option } = Select;
function handleChange(value: string) {
console.log(`selected ${value}`);
}
const SelectBoxCategory = () => {
return (
<div className="selectCategory">
<Select defaultValue="Danh mục" style={{ width: 120 }} onChange={handleChange}>
<Option value="1">Hướng dẫn</Option>
<Option value="2">Cài đt</Option>
</Select>
</div>
)
}
const FullListComponent = (props: {openItem: (id?: string|number) => void, params?: {[key: string]: any}}) => { const FullListComponent = (props: {openItem: (id?: string|number) => void, params?: {[key: string]: any}}) => {
@@ -53,8 +70,15 @@ const FullListComponent = (props: {openItem: (id?: string|number) => void, param
} }
return ( return (
<> <div className="help-full-content">
<div className="product-list-head">
<div className="product-list-head-Left"><span className={'a-link'} onClick={() => alert('về trang chủ')}><ArrowLeftOutlined />Quay lại</span></div>
<div className="product-list-head-right">
<SelectBoxCategory />
</div>
</div>
<List <List
className="help-list-full article-list"
itemLayout="horizontal" itemLayout="horizontal"
dataSource={item_list.current} dataSource={item_list.current}
renderItem={(item: HelpItem) => ( renderItem={(item: HelpItem) => (
@@ -67,9 +91,9 @@ const FullListComponent = (props: {openItem: (id?: string|number) => void, param
)} )}
/> />
<Pagination defaultCurrent={page} total={50} pageSize={10} onChange={page => setPage(page)} /> <Pagination className="paging" defaultCurrent={page} total={50} pageSize={10} onChange={page => setPage(page)} />
</> </div>
) )
} }

View File

@@ -7,6 +7,8 @@ import PaymentStatus from "@/components/display/PaymentStatus";
import ShippingStatus from "@/components/display/ShippingStatus"; import ShippingStatus from "@/components/display/ShippingStatus";
import OrderStatus from "@/components/display/OrderStatus"; import OrderStatus from "@/components/display/OrderStatus";
import Comment from "@/components/Comment"; import Comment from "@/components/Comment";
import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
const ProductDetailComponent = ({ id, openHome }: { id?: string | number, openHome: () => void }) => { const ProductDetailComponent = ({ id, openHome }: { id?: string | number, openHome: () => void }) => {
@@ -24,8 +26,11 @@ const ProductDetailComponent = ({id, openHome}: {id?: string|number, openHome: (
return ( return (
<> <>
<div><span className={'a-link'} onClick={openHome}>Back to home</span></div> <div className="product-detail-header">
<div className="product-detail-header-left"><span className={'a-link'} onClick={openHome}><ArrowLeftOutlined /> Quay lại</span></div>
<div className="product-detail-header-right"><span className={'a-link'} onClick={() => alert('go to web')}>Xem sản phẩm tại web <ArrowRightOutlined /></span></div>
</div>
<div className="product-detail-content">
<Descriptions <Descriptions
bordered bordered
title=" " title=" "
@@ -45,8 +50,10 @@ const ProductDetailComponent = ({id, openHome}: {id?: string|number, openHome: (
<img src={product_info.img} style={{ maxWidth: 150, maxHeight: 150 }} alt={''} /> <img src={product_info.img} style={{ maxWidth: 150, maxHeight: 150 }} alt={''} />
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
<div className="product-detail-comment">
<Comment item_type={'product'} item_id={product_info.id} /> <Comment item_type={'product'} item_id={product_info.id} />
</div>
</div>
</> </>
) )

View File

@@ -1,18 +1,19 @@
import {Avatar, List, Pagination} from "antd"; import { Avatar, List, Pagination, Select } from "antd";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { HelpItem } from "@/typings"; import { HelpItem } from "@/typings";
import api from "@/lib/api"; import api from "@/lib/api";
import Loading from "@/components/Loading"; import Loading from "@/components/Loading";
import { formatNumber } from "@/lib/utils"; import { formatNumber } from "@/lib/utils";
import { CheckOutlined, CloseOutlined, ArrowLeftOutlined } from '@ant-design/icons';
const ProductDescription = ({ item }: { item: HelpItem }) => { const ProductDescription = ({ item }: { item: HelpItem }) => {
const stock = item.in_stock ? 'Còn hàng' : 'Hết hàng'; const stock = item.in_stock ? <span className="pQuantity pQuanCOn"><CheckOutlined />Còn hàng</span> : <span className="pQuantity pQuanHet"><CloseOutlined />Hết hàng</span>;
return ( return (
<> <>
{formatNumber(item.price)} - {stock} <span className="pPrice">{formatNumber(item.price)}</span>{stock}
</> </>
) )
} }
@@ -25,6 +26,7 @@ const TopListComponent = (props: {defaultList: HelpItem[], openItem: (id?: strin
return ( return (
<> <>
<List <List
className="product-list"
itemLayout="horizontal" itemLayout="horizontal"
dataSource={defaultList} dataSource={defaultList}
renderItem={(item: HelpItem) => ( renderItem={(item: HelpItem) => (
@@ -41,6 +43,32 @@ const TopListComponent = (props: {defaultList: HelpItem[], openItem: (id?: strin
) )
} }
const { Option } = Select;
function handleChange(value: string) {
console.log(`selected ${value}`);
}
const SelectBoxCategory = () => {
return (
<div className="selectCategory">
<Select defaultValue="Danh mục" style={{ width: 120 }} onChange={handleChange}>
<Option value="1">Laptop</Option>
<Option value="2">Máy tính đ bàn</Option>
</Select>
</div>
)
}
const SelectBrandFilter = () => {
return (
<div className="brandFilter">
<Select defaultValue="Thương Hiệu" style={{ width: 120 }} onChange={handleChange}>
<Option value="1">Dell</Option>
<Option value="2">HP</Option>
</Select>
</div>
)
}
const FullListComponent = (props: { openItem: (id?: string | number) => void, params?: { [key: string]: any } }) => { const FullListComponent = (props: { openItem: (id?: string | number) => void, params?: { [key: string]: any } }) => {
@@ -69,8 +97,16 @@ const FullListComponent = (props: {openItem: (id?: string|number) => void, param
} }
return ( return (
<> <div className="help-full-content">
<div className="product-list-head">
<div className="product-list-head-Left"><span className={'a-link'} onClick={() => alert('về trang chủ')}><ArrowLeftOutlined />Quay lại</span></div>
<div className="product-list-head-right">
<SelectBoxCategory />
<SelectBrandFilter />
</div>
</div>
<List <List
className="help-list-full product-list"
itemLayout="horizontal" itemLayout="horizontal"
dataSource={item_list.current} dataSource={item_list.current}
renderItem={(item: HelpItem) => ( renderItem={(item: HelpItem) => (
@@ -84,9 +120,9 @@ const FullListComponent = (props: {openItem: (id?: string|number) => void, param
)} )}
/> />
<Pagination defaultCurrent={page} total={50} pageSize={10} onChange={page => setPage(page)} /> <Pagination className="paging" defaultCurrent={page} total={50} pageSize={10} onChange={page => setPage(page)} />
</> </div>
) )
} }

View File

@@ -1,6 +1,7 @@
import {Input} from "antd"; import {Input} from "antd";
import React, {useState} from "react"; import React, {useState} from "react";
import {HelpType} from "@/typings"; import {HelpType} from "@/typings";
import { SettingOutlined } from '@ant-design/icons'
const { Search } = Input; const { Search } = Input;
@@ -18,11 +19,10 @@ const SearchBox = (props: {openComponent: (type: HelpType, params?: { [key: stri
} }
return ( return (
<div> <div className="search-ct-right">
<Search <Search
placeholder="Tìm kiếm sản phẩm hoặc kiến thức" placeholder="Tìm kiếm sản phẩm hoặc kiến thức"
allowClear allowClear
enterButton="Tìm"
size="middle" size="middle"
onSearch={startSearch} onSearch={startSearch}
value={keyword} value={keyword}

View File

@@ -9,12 +9,34 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
* {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
a {
text-decoration: none;
}
html {
height: 100%;
}
body {
font-family: 'Krub', sans-serif;
font-weight: 400;
height: 100%;
}
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
} }
.ant-layout-header { .ant-layout-header {
background: black; background: #001B34;
padding: 0px;
} }
.ant-layout-sider { .ant-layout-sider {
@@ -31,7 +53,663 @@ code {
} }
/* use for SPAN in place of A tag */ /* use for SPAN in place of A tag */
.box-chatboard {
width: 100vw;
height: 100vh;
overflow: hidden;
float: left;
position: relative;
}
.a-link { .a-link {
cursor: pointer; cursor: pointer;
color: #007aff; color: #007aff;
} }
.content-chatboard {
width: 100%;
float: left;
height: calc(100vh - 55px);
overflow: hidden;
display: flex;
align-items: center;
position: relative;
}
.content-chatboard-row {
height: 100%;
}
.contentLeft {
float: left;
background: #002344;
height: 100%;
}
.contentLeft ::-webkit-scrollbar {
width: 7px;
}
.contentLeft ::-webkit-scrollbar-track {
background: #002344;
}
.contentLeft ::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.55);
border-radius: 4px;
}
.contentCenter {
width: 100%;
float: left;
padding: 20px;
background: #F1EFEF;
padding-top: 10px;
height: 100%;
}
.contentRight {
width: 100%;
float: right;
height: 100%;
background: #fff;
padding: 10px;
}
.content-chatboard-col {
height: 100%;
}
.contentCenterHeader {
margin-bottom: 10px;
width: 100%;
float: left;
display: flex;
align-items: center;
justify-content: space-between;
}
.contentCenterHeaderAdmin {
display: flex;
align-items: center;
cursor: pointer;
}
.contentCenterHeaderAdmin span.h-title {
font-size: 13px;
color: #848484;
text-transform: uppercase;
margin-right: 10px;
line-height: 24px;
}
.contentCenterHeaderAdmin span.h-icon {
font-size: 24px;
color: #848484;
line-height: 24px;
}
.cuscontentCenterHeader {
display: flex;
align-items: center;
}
.cuscontentCenterHeader .avatar-cus {
width: 36px;
height: 36px;
float: left;
margin-right: 10px;
position: relative;
}
.cuscontentCenterHeader .avatar-cus img {
width: 36px;
height: 36px;
float: left;
overflow: hidden;
border-radius: 50%;
}
.cuscontentCenterHeader .ant-badge-status {
position: absolute !important;
z-index: 1;
bottom: -7px;
right: 0px;
display: block !important;
}
.cuscontentCenterHeader .ant-badge-status-text {
display: none !important;
}
.cuscontentCenterHeader .avatar-cus .ant-badge-status-dot {
width: 12px;
height: 12px;
border: solid 2px #001F3D;
border-radius: 50%;
background: #9A9A9A;
}
.cuscontentCenterHeader .avatar-cus .ant-badge-status-dot.ant-badge-status-green {
background: #37EE14;
}
.cuscontentCenterHeader .info-cus {
margin-right: 10px;
}
.cuscontentCenterHeader .info-cus .name {
font-size: 15px;
font-weight: 700;
color: #000;
}
.cuscontentCenterHeader .info-cus .address {
font-size: 13px;
font-weight: 300;
color: #000;
}
.contentCenterCt {
width: 100%;
float: left;
height: calc(100% - 53px);
background: #fff;
border-radius: 10px;
}
.contentCenterCt .ant-row {
height: 100%;
}
.contentCenterCt .ant-row .ant-col {
height: 100%;
}
.contentCenterCtColLeft {
border-right: solid 1px #F1EFEF;
}
.contentCenterCtColRight {
padding: 20px;
}
.contentCenterCtColRightTitle {
width: 100%;
float: left;
margin-bottom: 10px;
line-height: 40px;
padding: 0px 15px;
background: #F1EFEF;
font-size: 16px;
font-weight: 700;
text-transform: uppercase;
color: #001529;
border-left: solid 7px #5DD498;
}
.tab-content-mes {
width: 100%;
float: left;
height: 100%;
}
.contentCenterCtColRight .ant-tabs-nav-list {
width: 100%;
float: left;
margin-bottom: 10px;
}
.contentCenterCtColRight .ant-tabs-nav-list .ant-tabs-tab {
line-height: 30px;
border: solid 1px #D9D9D9;
padding: 0px 10px !important;
margin-left: 0px !important;
margin-right: 5px;
background: #fff;
border-radius: 3px !important;
}
.contentCenterCtColRight .ant-tabs-nav-list .ant-tabs-tab.ant-tabs-tab-active {
border-color: #1890FF !important;
}
.contentCenterCtColRight .ant-tabs-nav-list .ant-tabs-tab .ant-tabs-tab-btn {
font-size: 14px;
color: #001529;
background: #fff;
}
.contentCenterCtColRight .ant-tabs-nav-list .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
color: #1890FF;
}
.ant-tabs-top > .ant-tabs-nav::before, .ant-tabs-bottom > .ant-tabs-nav::before, .ant-tabs-top > div > .ant-tabs-nav::before, .ant-tabs-bottom > div > .ant-tabs-nav::before {
display: none;
}
.contentCenterCtColRight .ant-tabs-nav {
margin-bottom: 0px;
}
.contentCenterCtColRight .ant-tabs-nav-operations .ant-tabs-nav-more {
padding: 0px 16px !important;
line-height: 32px;
height: 32px;
background: #1890FF;
color: #fff;
}
.contentCenterCtColRight .ant-tabs-nav-operations .ant-tabs-nav-more::after {
display: none;
}
.contentCenterCtColRightItem {
width: 100%;
float: left;
height: calc(50% - 10px);
margin-bottom: 10px;
}
.contentCenterCtColRight .ant-tabs-content-holder {
width: 100%;
float: left;
height: calc(100% - 102px);
}
.contentCenterCtColRight .ant-tabs-content {
width: 100%;
float: left;
height: calc(100% - 50px);
overflow-y: auto;
}
.contentCenterCtColRight .ant-tabs-content::-webkit-scrollbar {
width: 7px;
}
.contentCenterCtColRight .ant-tabs-content::-webkit-scrollbar-track {
background: #fff;
}
.contentCenterCtColRight .ant-tabs-content::-webkit-scrollbar-thumb {
background: #C4C4C4;
border-radius: 4px;
}
.taoghichu-input {
width: 100%;
float: left;
border: solid 1px #707070;
height: 75px !important;
border-radius: 5px !important;
}
.taoghichu-submit {
width: 100%;
float: left;
}
.taoghichu-submit .taoghichu-bt-update {
padding: 0px 15px;
line-height: 32px;
border: solid 1px #D9D9D9;
border-radius: 3px;
background: #fff;
margin-right: 5px;
}
.taoghichu-submit .taoghichu-bt-send {
width: 32px;
line-height: 32px;
padding: 0px;
border: none;
background: #1890FF;
border-radius: 5px;
color: #fff;
}
.ant-form-item {
width: 100%;
float: left;
margin-bottom: 10px;
padding-right: 10px;
}
.ant-form-item .ant-form-item-label {
width: 120px !important;
max-width: 120px !important;
flex: 0 0 120px;
}
.ant-form-item .ant-form-item-label label {
float: left;
white-space: break-spaces;
line-height: 1.2;
text-align: left;
}
.ant-form-item .ant-form-item-control {
width: calc(100% - 120px) !important;
max-width: calc(100% - 120px) !important;
flex: 0 0 calc(100% - 120px);
}
.formTaoGhiChu .ant-form-item .ant-form-item-control {
width: 100% !important;
max-width: 100% !important;
flex: 0 0 100%;
}
.ant-form-item .ant-form-item-control input {
width: 100% !important;
max-width: 100% !important;
height: 32px;
padding: 0px 10px;
border: solid 1px #D9D9D9;
}
.ant-form-item .ant-form-item-control .ant-space {
display: block;
}
.ant-form-item .ant-form-item-control .ant-checkbox-wrapper {
width: 100%;
float: left;
}
.formTaoHoTro .ant-form-item:nth-child(6) .ant-form-item-label {
width: 100% !important;
max-width: 100% !important;
flex: 0 0 100%;
}
.formTaoHoTro .ant-form-item:nth-child(6) .ant-form-item-control {
width: 100% !important;
max-width: 100% !important;
flex: 0 0 100%;
}
.ant-select-show-arrow {
min-width: 150px;
width: auto !important;
max-width: 100%;
}
.formTaoDonHang .ant-form-item:nth-child(2) .ant-select {
margin-bottom: 10px;
}
.formTaoDonHang .ant-form-item:nth-child(5) .ant-select {
margin-top: 10px;
}
.formTaoDonHang .ant-form-item .ant-form-item {
margin-bottom: 0px;
}
.formThongTin .ant-form-item:nth-child(3) .ant-select-selector {
margin-bottom: 10px;
}
.formThongTin .ant-form-item:nth-child(9) .ant-select-selector {
margin-top: 10px;
}
.contentRightHeader {
width: 100%;
float: left;
margin-bottom: 15px;
font-size: 20px;
font-weight: 700;
text-transform: uppercase;
}
.contentRightHeader .anticon-close {
float: right;
display: none;
}
.search-ct-right {
width: 100%;
float: left;
margin-bottom: 20px;
}
.contentRight .scrollable {
width: 100%;
float: left;
}
.helpProduct {
width: 100%;
float: left;
}
.titleHelpRight {
width: 100%;
float: left;
margin-bottom: 10px;
font-size: 20px;
font-weight: 700;
}
.titleHelpRight span {
font-size: 14px;
font-weight: 500;
color: #5DD498;
}
.helpContent {
width: 100%;
float: left;
height: calc(100% - 108px);
}
.helpContent .helpContentRight {
width: 100%;
height: 50%;
float: left;
margin-bottom: 10px;
}
.helpContentRightScroll {
width: 100%;
float: left;
height: calc(100% - 41px);
}
.helpContentRightScroll::-webkit-scrollbar {
width: 7px;
}
.helpContentRightScroll::-webkit-scrollbar-track {
background: #fff;
}
.helpContentRightScroll::-webkit-scrollbar-thumb {
background: #C4C4C4;
border-radius: 4px;
}
.product-list li {
width: 100%;
float: left;
margin-bottom: 10px;
padding: 0px;
border: none !important;
}
.product-list li .ant-avatar-image {
width: 85px;
float: left;
margin-right: 10px;
position: relative;
display: block;
padding-top: 85px;
overflow: hidden;
}
.product-list li .ant-avatar-image img {
position: absolute;
max-width: 100%;
max-height: 100%;
width: auto !important;
height: auto !important;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: block;
margin: auto;
}
.product-list li .ant-list-item-meta-avatar {
margin: 0px !important;
}
.product-list li .ant-list-item-meta-content {
width: calc(100% - 95px);
float: left;
}
.product-list li .ant-list-item-meta-content .ant-list-item-meta-title {
width: 100%;
float: left;
font-size: 14px;
font-weight: 500;
color: #000000;
}
.product-list li .ant-list-item-meta-content .ant-list-item-meta-title span {
font-size: 14px;
font-weight: 500;
color: #000000;
}
.ant-list-item-meta-description {
width: 100%;
float: left;
}
.ant-list-item-meta-description .pPrice {
font-size: 14px;
font-weight: 700;
color: #EC1010;
margin-right: 20px;
}
.pQuantity {
font-size: 12px;
}
.pQuantity.pQuanCOn {
color: #5DD498;
}
.pQuantity.pQuanHet {
color: #EC1010;
}
.Article-list li {
width: 100%;
float: left;
}
.Article-list li h4 span {
width: 100%;
float: left;
font-size: 14px;
font-weight: 700;
color: #6D6D6D;
}
.Article-list li .ant-list-item-meta-description {
font-size: 14px;
font-weight: 300;
color: #6D6D6D;
}
.help-full-content {
width: 100%;
float: left;
height: calc(100% - 130px);
}
.help-full-content .help-list-full {
width: 100%;
float: left;
height: calc(100% - 45px);
overflow-y: auto;
}
.help-full-content .help-list-full::-webkit-scrollbar {
width: 7px;
}
.help-full-content .help-list-full::-webkit-scrollbar-track {
background: #fff;
}
.help-full-content .help-list-full::-webkit-scrollbar-thumb {
background: #C4C4C4;
border-radius: 4px;
}
.paging {
width: 100%;
float: left;
height: 45px;
display: flex;
align-items: center;
}
.paging li {
width: 28px;
height: 28px;
line-height: 28px;
}
.product-list-head {
width: 100%;
float: left;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.product-list-head-Left span {
color: #37EE14;
}
.product-list-head-right {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-list-head-right .ant-select {
width: 120px !important;
height: 32px;
line-height: 32px;
min-width: 100px !important;
font-size: 12px;
}
.product-list-head-right .selectCategory {
margin-right: 5px;
}
.product-detail-header {
width: 100%;
float: left;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.product-detail-header-left span {
color: #37EE14;
}
.product-detail-header-right span {
color: #1890FF;
}
.product-detail-content {
width: 100%;
float: left;
height: calc(100% - 120px);
overflow-y: auto;
}
.product-detail-content::-webkit-scrollbar {
width: 7px;
}
.product-detail-content::-webkit-scrollbar-track {
background: #fff;
}
.product-detail-content::-webkit-scrollbar-thumb {
background: #C4C4C4;
border-radius: 4px;
}
.product-detail-content table {
width: 100%;
float: left;
}
.product-detail-comment {
width: 100%;
float: left;
}
.form-commet-all {
width: 100%;
float: left;
}
.form-commet-all h3 {
width: 100%;
float: left;
margin-bottom: 10px;
font-size: 14px;
font-weight: 700;
}
.form-commet-all #complex-form {
width: 100%;
float: left;
margin-bottom: 20px;
}
.form-commet-all #complex-form textarea {
width: 100%;
float: left;
border: solid 1px #707070;
border-radius: 5px;
}
.form-commet-all .ant-form-item .ant-form-item-control {
width: 100% !important;
max-width: 100% !important;
flex-basis: 100%;
}
.form-commet-all .ant-comment {
width: 100%;
float: left;
}
.article-detail-content-ct {
font-weight: 300;
color: #6D6D6D;
}
.fix-ab-ct-right {
width: 470px;
float: left;
position: absolute;
z-index: 1;
top: 0px;
right: 0px;
height: 100%;
}
.fix-ab-ct-right .contentRightHeader .anticon-close {
display: block;
}
.content-chatboard-col-big .contentCenterHeaderAdmin {
display: none;
}
@media (max-width: 1366px) {
.contentCenterCtColRight .ant-tabs-content {
max-height: 140px;
min-height: 140px;
}
}