mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	[Enhancement] Group widget messages by date (#363)
* [Enhancement] Group widget messages by date * Update DateSeparator snapshot
This commit is contained in:
		
							
								
								
									
										48
									
								
								app/javascript/shared/components/DateSeparator.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								app/javascript/shared/components/DateSeparator.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| <template> | ||||
|   <div class="date--separator"> | ||||
|     {{ date }} | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   props: { | ||||
|     date: { | ||||
|       type: String, | ||||
|       required: true, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| @import '~widget/assets/scss/variables'; | ||||
|  | ||||
| .date--separator { | ||||
|   font-size: $font-size-default; | ||||
|   color: $color-body; | ||||
|   height: 50px; | ||||
|   line-height: 50px; | ||||
|   position: relative; | ||||
|   text-align: center; | ||||
|   width: 100%; | ||||
| } | ||||
|  | ||||
| .date--separator::before, | ||||
| .date--separator::after { | ||||
|   background-color: $color-border; | ||||
|   content: ''; | ||||
|   height: 1px; | ||||
|   position: absolute; | ||||
|   top: 24px; | ||||
|   width: calc((100% - 120px) / 2); | ||||
| } | ||||
|  | ||||
| .date--separator::before { | ||||
|   left: 0; | ||||
| } | ||||
|  | ||||
| .date--separator::after { | ||||
|   right: 0; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										14
									
								
								app/javascript/shared/components/specs/DateSeparator.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/javascript/shared/components/specs/DateSeparator.spec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import { mount } from '@vue/test-utils'; | ||||
| import DateSeparator from '../DateSeparator'; | ||||
|  | ||||
| describe('Spinner', () => { | ||||
|   test('matches snapshot', () => { | ||||
|     const wrapper = mount(DateSeparator, { | ||||
|       propsData: { | ||||
|         date: 'Nov 18, 2019', | ||||
|       }, | ||||
|     }); | ||||
|     expect(wrapper.isVueInstance()).toBeTruthy(); | ||||
|     expect(wrapper.element).toMatchSnapshot(); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,11 @@ | ||||
| // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
|  | ||||
| exports[`Spinner matches snapshot 1`] = ` | ||||
| <div | ||||
|   class="date--separator" | ||||
| > | ||||
|    | ||||
|   Nov 18, 2019 | ||||
|  | ||||
| </div> | ||||
| `; | ||||
							
								
								
									
										13
									
								
								app/javascript/shared/helpers/DateHelper.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/javascript/shared/helpers/DateHelper.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| import moment from 'moment'; | ||||
|  | ||||
| class DateHelper { | ||||
|   constructor(date) { | ||||
|     this.date = moment(date * 1000); | ||||
|   } | ||||
|  | ||||
|   format(dateFormat = 'MMM DD, YYYY') { | ||||
|     return this.date.format(dateFormat); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default DateHelper; | ||||
							
								
								
									
										13
									
								
								app/javascript/shared/helpers/specs/DateHelper.sepc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/javascript/shared/helpers/specs/DateHelper.sepc.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| import DateSeparator from '../DateSeparator'; | ||||
|  | ||||
| describe('#DateSeparator', () => { | ||||
|   it('should format correctly without dateFormat', () => { | ||||
|     expect(new DateSeparator(1576340626).format()).toEqual('Dec 14, 2019'); | ||||
|   }); | ||||
|  | ||||
|   it('should format correctly without dateFormat', () => { | ||||
|     expect(new DateSeparator(1576340626).format('DD-MM-YYYY')).toEqual( | ||||
|       '14-12-2019' | ||||
|     ); | ||||
|   }); | ||||
| }); | ||||
| @@ -4,12 +4,15 @@ | ||||
|       <div v-if="isFetchingList" class="message--loader"> | ||||
|         <spinner></spinner> | ||||
|       </div> | ||||
|       <div v-for="date in conversationDates" :key="date"> | ||||
|         <date-separator :date="date"></date-separator> | ||||
|         <ChatMessage | ||||
|         v-for="message in messages" | ||||
|           v-for="message in groupedMessages[date]" | ||||
|           :key="message.id" | ||||
|           :message="message" | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
|     <branding></branding> | ||||
|   </div> | ||||
| </template> | ||||
| @@ -17,6 +20,7 @@ | ||||
| <script> | ||||
| import Branding from 'widget/components/Branding.vue'; | ||||
| import ChatMessage from 'widget/components/ChatMessage.vue'; | ||||
| import DateSeparator from 'shared/components/DateSeparator.vue'; | ||||
| import Spinner from 'shared/components/Spinner.vue'; | ||||
| import { mapActions, mapGetters } from 'vuex'; | ||||
|  | ||||
| @@ -25,10 +29,11 @@ export default { | ||||
|   components: { | ||||
|     Branding, | ||||
|     ChatMessage, | ||||
|     DateSeparator, | ||||
|     Spinner, | ||||
|   }, | ||||
|   props: { | ||||
|     messages: Object, | ||||
|     groupedMessages: Object, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
| @@ -43,6 +48,9 @@ export default { | ||||
|       isFetchingList: 'conversation/getIsFetchingList', | ||||
|       conversationSize: 'conversation/getConversationSize', | ||||
|     }), | ||||
|     conversationDates() { | ||||
|       return Object.keys(this.groupedMessages); | ||||
|     }, | ||||
|   }, | ||||
|   watch: { | ||||
|     allMessagesLoaded() { | ||||
|   | ||||
| @@ -3,6 +3,9 @@ import Vue from 'vue'; | ||||
| import { sendMessageAPI, getConversationAPI } from 'widget/api/conversation'; | ||||
| import { MESSAGE_TYPE } from 'widget/helpers/constants'; | ||||
| import getUuid from '../../helpers/uuid'; | ||||
| import DateHelper from '../../../shared/helpers/DateHelper'; | ||||
|  | ||||
| const groupBy = require('lodash.groupby'); | ||||
|  | ||||
| export const createTemporaryMessage = content => { | ||||
|   const timestamp = new Date().getTime(); | ||||
| @@ -31,10 +34,9 @@ const state = { | ||||
| }; | ||||
|  | ||||
| export const getters = { | ||||
|   getAllMessagesLoaded: _state => _state.uiFlags.allMessagesLoaded, | ||||
|   getConversation: _state => _state.conversations, | ||||
|   getConversationSize: _state => Object.keys(_state.conversations).length, | ||||
|   getAllMessagesLoaded: _state => _state.uiFlags.allMessagesLoaded, | ||||
|   getIsFetchingList: _state => _state.uiFlags.isFetchingList, | ||||
|   getEarliestMessage: _state => { | ||||
|     const conversation = Object.values(_state.conversations); | ||||
|     if (conversation.length) { | ||||
| @@ -42,6 +44,12 @@ export const getters = { | ||||
|     } | ||||
|     return {}; | ||||
|   }, | ||||
|   getGroupedConversation: _state => { | ||||
|     return groupBy(Object.values(_state.conversations), message => | ||||
|       new DateHelper(message.created_at).format() | ||||
|     ); | ||||
|   }, | ||||
|   getIsFetchingList: _state => _state.uiFlags.isFetchingList, | ||||
| }; | ||||
|  | ||||
| export const actions = { | ||||
|   | ||||
| @@ -53,4 +53,57 @@ describe('#getters', () => { | ||||
|     expect(getters.getAllMessagesLoaded(state)).toEqual(false); | ||||
|     expect(getters.getIsFetchingList(state)).toEqual(false); | ||||
|   }); | ||||
|  | ||||
|   it('uiFlags', () => { | ||||
|     const state = { | ||||
|       conversations: { | ||||
|         1: { | ||||
|           id: 1, | ||||
|           content: 'Thanks for the help', | ||||
|           created_at: 1574075964, | ||||
|         }, | ||||
|         2: { | ||||
|           id: 2, | ||||
|           content: 'Yes, It makes sense', | ||||
|           created_at: 1574092218, | ||||
|         }, | ||||
|         3: { | ||||
|           id: 3, | ||||
|           content: 'Hey', | ||||
|           created_at: 1576340623, | ||||
|         }, | ||||
|         4: { | ||||
|           id: 4, | ||||
|           content: 'How may I help you', | ||||
|           created_at: 1576340626, | ||||
|         }, | ||||
|       }, | ||||
|     }; | ||||
|     expect(getters.getGroupedConversation(state)).toEqual({ | ||||
|       'Nov 18, 2019': [ | ||||
|         { | ||||
|           id: 1, | ||||
|           content: 'Thanks for the help', | ||||
|           created_at: 1574075964, | ||||
|         }, | ||||
|         { | ||||
|           id: 2, | ||||
|           content: 'Yes, It makes sense', | ||||
|           created_at: 1574092218, | ||||
|         }, | ||||
|       ], | ||||
|       'Dec 14, 2019': [ | ||||
|         { | ||||
|           id: 3, | ||||
|           content: 'Hey', | ||||
|           created_at: 1576340623, | ||||
|         }, | ||||
|         { | ||||
|           id: 4, | ||||
|           content: 'How may I help you', | ||||
|           created_at: 1576340626, | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|       <ChatHeaderExpanded v-if="isHeaderExpanded" /> | ||||
|       <ChatHeader v-else :title="getHeaderName" /> | ||||
|     </div> | ||||
|     <ConversationWrap :messages="getConversation" /> | ||||
|     <ConversationWrap :grouped-messages="groupedMessages" /> | ||||
|     <div class="footer-wrap"> | ||||
|       <ChatFooter :on-send-message="handleSendMessage" /> | ||||
|     </div> | ||||
| @@ -36,9 +36,12 @@ export default { | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('conversation', ['getConversation', 'getConversationSize']), | ||||
|     ...mapGetters({ | ||||
|       groupedMessages: 'conversation/getGroupedConversation', | ||||
|       conversationSize: 'conversation/getConversationSize', | ||||
|     }), | ||||
|     isHeaderExpanded() { | ||||
|       return this.getConversationSize === 0; | ||||
|       return this.conversationSize === 0; | ||||
|     }, | ||||
|     getHeaderName() { | ||||
|       return window.chatwootWebChannel.website_name; | ||||
|   | ||||
| @@ -27,6 +27,7 @@ | ||||
|     "highlight.js": "^9.15.10", | ||||
|     "ionicons": "~2.0.1", | ||||
|     "js-cookie": "^2.2.1", | ||||
|     "lodash.groupby": "^4.6.0", | ||||
|     "md5": "~2.2.1", | ||||
|     "moment": "~2.19.3", | ||||
|     "query-string": "5", | ||||
| @@ -91,7 +92,10 @@ | ||||
|   }, | ||||
|   "jest": { | ||||
|     "collectCoverage": true, | ||||
|      "coverageReporters": ["lcov", "text"] | ||||
|     "coverageReporters": [ | ||||
|       "lcov", | ||||
|       "text" | ||||
|     ] | ||||
|   }, | ||||
|   "lint-staged": { | ||||
|     "*.{js,vue}": [ | ||||
|   | ||||
| @@ -6220,6 +6220,11 @@ lodash.get@^4.0: | ||||
|   resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" | ||||
|   integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= | ||||
|  | ||||
| lodash.groupby@^4.6.0: | ||||
|   version "4.6.0" | ||||
|   resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" | ||||
|   integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= | ||||
|  | ||||
| lodash.has@^4.0: | ||||
|   version "4.5.2" | ||||
|   resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Pranav Raj S
					Pranav Raj S