mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-03 20:48:07 +00:00 
			
		
		
		
	feat: add inbox filter
This commit is contained in:
		@@ -266,6 +266,8 @@
 | 
				
			|||||||
    "NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
 | 
					    "NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
 | 
				
			||||||
    "DOWNLOAD_INBOX_REPORTS": "Download inbox reports",
 | 
					    "DOWNLOAD_INBOX_REPORTS": "Download inbox reports",
 | 
				
			||||||
    "FILTER_DROPDOWN_LABEL": "Select Inbox",
 | 
					    "FILTER_DROPDOWN_LABEL": "Select Inbox",
 | 
				
			||||||
 | 
					    "ALL_INBOXES": "All Inboxes",
 | 
				
			||||||
 | 
					    "SEARCH_INBOX": "Search Inbox",
 | 
				
			||||||
    "METRICS": {
 | 
					    "METRICS": {
 | 
				
			||||||
      "CONVERSATIONS": {
 | 
					      "CONVERSATIONS": {
 | 
				
			||||||
        "NAME": "Conversations",
 | 
					        "NAME": "Conversations",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,9 +9,11 @@ import endOfDay from 'date-fns/endOfDay';
 | 
				
			|||||||
import getUnixTime from 'date-fns/getUnixTime';
 | 
					import getUnixTime from 'date-fns/getUnixTime';
 | 
				
			||||||
import startOfDay from 'date-fns/startOfDay';
 | 
					import startOfDay from 'date-fns/startOfDay';
 | 
				
			||||||
import subDays from 'date-fns/subDays';
 | 
					import subDays from 'date-fns/subDays';
 | 
				
			||||||
 | 
					import format from 'date-fns/format';
 | 
				
			||||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
 | 
					import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
 | 
				
			||||||
import Button from 'dashboard/components-next/button/Button.vue';
 | 
					import Button from 'dashboard/components-next/button/Button.vue';
 | 
				
			||||||
import { useI18n } from 'vue-i18n';
 | 
					import { useI18n } from 'vue-i18n';
 | 
				
			||||||
 | 
					import { downloadCsvFile } from 'dashboard/helper/downloadHelper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const store = useStore();
 | 
					const store = useStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,6 +21,7 @@ const uiFlags = useMapGetter('getOverviewUIFlags');
 | 
				
			|||||||
const accountConversationHeatmap = useMapGetter(
 | 
					const accountConversationHeatmap = useMapGetter(
 | 
				
			||||||
  'getAccountConversationHeatmapData'
 | 
					  'getAccountConversationHeatmapData'
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					const inboxes = useMapGetter('inboxes/getInboxes');
 | 
				
			||||||
const { t } = useI18n();
 | 
					const { t } = useI18n();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const menuItems = [
 | 
					const menuItems = [
 | 
				
			||||||
@@ -33,19 +36,78 @@ const menuItems = [
 | 
				
			|||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const selectedDays = ref(6);
 | 
					const selectedDays = ref(6);
 | 
				
			||||||
 | 
					const selectedInbox = ref(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const selectedDayFilter = computed(() =>
 | 
					const selectedDayFilter = computed(() =>
 | 
				
			||||||
  menuItems.find(menuItem => menuItem.value === selectedDays.value)
 | 
					  menuItems.find(menuItem => menuItem.value === selectedDays.value)
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const inboxMenuItems = computed(() => {
 | 
				
			||||||
 | 
					  return [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      label: t('INBOX_REPORTS.ALL_INBOXES'),
 | 
				
			||||||
 | 
					      value: null,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    ...inboxes.value.map(inbox => ({
 | 
				
			||||||
 | 
					      label: inbox.name,
 | 
				
			||||||
 | 
					      value: inbox.id,
 | 
				
			||||||
 | 
					    })),
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const selectedInboxFilter = computed(() => {
 | 
				
			||||||
 | 
					  if (!selectedInbox.value) {
 | 
				
			||||||
 | 
					    return { label: t('INBOX_REPORTS.ALL_INBOXES') };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return inboxMenuItems.value.find(
 | 
				
			||||||
 | 
					    item => item.value === selectedInbox.value.id
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const downloadHeatmapData = () => {
 | 
					const downloadHeatmapData = () => {
 | 
				
			||||||
  const to = endOfDay(new Date());
 | 
					  const to = endOfDay(new Date());
 | 
				
			||||||
  store.dispatch('downloadAccountConversationHeatmap', {
 | 
					
 | 
				
			||||||
    daysBefore: selectedDays.value,
 | 
					  // If no inbox is selected, use the existing backend CSV endpoint
 | 
				
			||||||
    to: getUnixTime(to),
 | 
					  if (!selectedInbox.value) {
 | 
				
			||||||
 | 
					    store.dispatch('downloadAccountConversationHeatmap', {
 | 
				
			||||||
 | 
					      daysBefore: selectedDays.value,
 | 
				
			||||||
 | 
					      to: getUnixTime(to),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // If inbox is selected, generate CSV from store data
 | 
				
			||||||
 | 
					  if (
 | 
				
			||||||
 | 
					    !accountConversationHeatmap.value ||
 | 
				
			||||||
 | 
					    accountConversationHeatmap.value.length === 0
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Create CSV headers
 | 
				
			||||||
 | 
					  const headers = ['Date', 'Hour', 'Conversations Count'];
 | 
				
			||||||
 | 
					  const rows = [headers];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Convert heatmap data to rows
 | 
				
			||||||
 | 
					  accountConversationHeatmap.value.forEach(item => {
 | 
				
			||||||
 | 
					    const date = new Date(item.timestamp * 1000);
 | 
				
			||||||
 | 
					    const dateStr = format(date, 'yyyy-MM-dd');
 | 
				
			||||||
 | 
					    const hour = date.getHours();
 | 
				
			||||||
 | 
					    rows.push([dateStr, `${hour}:00 - ${hour + 1}:00`, item.value]);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Convert to CSV string
 | 
				
			||||||
 | 
					  const csvContent = rows.map(row => row.join(',')).join('\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Generate filename with inbox name
 | 
				
			||||||
 | 
					  const inboxName = selectedInbox.value.name.replace(/[^a-z0-9]/gi, '_');
 | 
				
			||||||
 | 
					  const fileName = `conversation_heatmap_${inboxName}_${format(new Date(), 'dd-MM-yyyy')}.csv`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Download the file
 | 
				
			||||||
 | 
					  downloadCsvFile(fileName, csvContent);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
const [showDropdown, toggleDropdown] = useToggle();
 | 
					const [showDropdown, toggleDropdown] = useToggle();
 | 
				
			||||||
 | 
					const [showInboxDropdown, toggleInboxDropdown] = useToggle();
 | 
				
			||||||
const fetchHeatmapData = () => {
 | 
					const fetchHeatmapData = () => {
 | 
				
			||||||
  if (uiFlags.value.isFetchingAccountConversationsHeatmap) {
 | 
					  if (uiFlags.value.isFetchingAccountConversationsHeatmap) {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
@@ -54,13 +116,21 @@ const fetchHeatmapData = () => {
 | 
				
			|||||||
  let to = endOfDay(new Date());
 | 
					  let to = endOfDay(new Date());
 | 
				
			||||||
  let from = startOfDay(subDays(to, Number(selectedDays.value)));
 | 
					  let from = startOfDay(subDays(to, Number(selectedDays.value)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  store.dispatch('fetchAccountConversationHeatmap', {
 | 
					  const params = {
 | 
				
			||||||
    metric: 'conversations_count',
 | 
					    metric: 'conversations_count',
 | 
				
			||||||
    from: getUnixTime(from),
 | 
					    from: getUnixTime(from),
 | 
				
			||||||
    to: getUnixTime(to),
 | 
					    to: getUnixTime(to),
 | 
				
			||||||
    groupBy: 'hour',
 | 
					    groupBy: 'hour',
 | 
				
			||||||
    businessHours: false,
 | 
					    businessHours: false,
 | 
				
			||||||
  });
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Add inbox filtering if an inbox is selected
 | 
				
			||||||
 | 
					  if (selectedInbox.value) {
 | 
				
			||||||
 | 
					    params.type = 'inbox';
 | 
				
			||||||
 | 
					    params.id = selectedInbox.value.id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  store.dispatch('fetchAccountConversationHeatmap', params);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const handleAction = ({ value }) => {
 | 
					const handleAction = ({ value }) => {
 | 
				
			||||||
@@ -69,9 +139,18 @@ const handleAction = ({ value }) => {
 | 
				
			|||||||
  fetchHeatmapData();
 | 
					  fetchHeatmapData();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleInboxAction = ({ value }) => {
 | 
				
			||||||
 | 
					  toggleInboxDropdown(false);
 | 
				
			||||||
 | 
					  selectedInbox.value = value
 | 
				
			||||||
 | 
					    ? inboxes.value.find(inbox => inbox.id === value)
 | 
				
			||||||
 | 
					    : null;
 | 
				
			||||||
 | 
					  fetchHeatmapData();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { startRefetching } = useLiveRefresh(fetchHeatmapData);
 | 
					const { startRefetching } = useLiveRefresh(fetchHeatmapData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					  store.dispatch('inboxes/get');
 | 
				
			||||||
  fetchHeatmapData();
 | 
					  fetchHeatmapData();
 | 
				
			||||||
  startRefetching();
 | 
					  startRefetching();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -100,6 +179,27 @@ onMounted(() => {
 | 
				
			|||||||
            @action="handleAction($event)"
 | 
					            @action="handleAction($event)"
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div
 | 
				
			||||||
 | 
					          v-on-clickaway="() => toggleInboxDropdown(false)"
 | 
				
			||||||
 | 
					          class="relative flex items-center group"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <Button
 | 
				
			||||||
 | 
					            sm
 | 
				
			||||||
 | 
					            slate
 | 
				
			||||||
 | 
					            faded
 | 
				
			||||||
 | 
					            :label="selectedInboxFilter.label"
 | 
				
			||||||
 | 
					            class="rounded-md group-hover:bg-n-alpha-2 max-w-[200px]"
 | 
				
			||||||
 | 
					            @click="toggleInboxDropdown()"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          <DropdownMenu
 | 
				
			||||||
 | 
					            v-if="showInboxDropdown"
 | 
				
			||||||
 | 
					            :menu-items="inboxMenuItems"
 | 
				
			||||||
 | 
					            show-search
 | 
				
			||||||
 | 
					            :search-placeholder="t('INBOX_REPORTS.SEARCH_INBOX')"
 | 
				
			||||||
 | 
					            class="mt-1 ltr:right-0 rtl:left-0 xl:ltr:right-0 xl:rtl:left-0 top-full min-w-[200px]"
 | 
				
			||||||
 | 
					            @action="handleInboxAction($event)"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
        <Button
 | 
					        <Button
 | 
				
			||||||
          sm
 | 
					          sm
 | 
				
			||||||
          slate
 | 
					          slate
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user