-
+
{{ t('CONTACTS_LAYOUT.SIDEBAR.HISTORY.EMPTY_STATE') }}
diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactMedia.vue b/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactMedia.vue
index 6545c255d..d831d3874 100644
--- a/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactMedia.vue
+++ b/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactMedia.vue
@@ -12,9 +12,10 @@ import GalleryView from 'dashboard/components/widgets/conversation/components/Ga
import Media from 'dashboard/components-next/SharedAttachments/Media.vue';
import Files from 'dashboard/components-next/SharedAttachments/Files.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
+import ContactSidebarSection from 'dashboard/components-next/Contacts/ContactsSidebar/ContactSidebarSection.vue';
-const MEDIA_PEEK_LIMIT = 12;
-const FILES_PEEK_LIMIT = 6;
+const MEDIA_PEEK_LIMIT = 10;
+const FILES_PEEK_LIMIT = 4;
const route = useRoute();
const router = useRouter();
@@ -73,17 +74,21 @@ onMounted(() => {
-
+
{{ t('CONVERSATION_SIDEBAR.SHARED_FILES.EMPTY') }}
-
+
{
@select="onFileSelect"
@jump-to-message="onJumpToMessage"
/>
-
+
{
-
-
-
- {{ t('CONTACTS_LAYOUT.SIDEBAR.MERGE.TITLE') }}
-
+
+
{{ t('CONTACTS_LAYOUT.SIDEBAR.MERGE.DESCRIPTION') }}
-
-
-
-
-
-
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactNotes.vue b/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactNotes.vue
index 218a1293f..951c5301e 100644
--- a/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactNotes.vue
+++ b/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactNotes.vue
@@ -1,5 +1,5 @@
-
-
-
-
-
-
-
-
+
+
-
-
-
-
+
+
+
+
+
+ {{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.NO_RESULTS') }}
+
+
+
+
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.EMPTY_STATE') }}
-
+
diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactSidebarSection.vue b/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactSidebarSection.vue
new file mode 100644
index 000000000..a171c4358
--- /dev/null
+++ b/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactSidebarSection.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ {{ title }}
+
+ {{ count }}
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue b/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue
index b7014c42f..6a9231731 100644
--- a/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue
+++ b/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue
@@ -12,6 +12,15 @@ import ContactsForm from 'dashboard/components-next/Contacts/ContactsForm/Contac
import ConfirmContactDeleteDialog from 'dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue';
import Policy from 'dashboard/components/policy.vue';
+import TabBar from 'dashboard/components-next/tabbar/TabBar.vue';
+import ContactStats from 'dashboard/components-next/Contacts/Pages/ContactStats.vue';
+import ContactSidebarSection from 'dashboard/components-next/Contacts/ContactsSidebar/ContactSidebarSection.vue';
+import ContactCustomAttributes from 'dashboard/components-next/Contacts/ContactsSidebar/ContactCustomAttributes.vue';
+import ContactHistory from 'dashboard/components-next/Contacts/ContactsSidebar/ContactHistory.vue';
+import ContactNotes from 'dashboard/components-next/Contacts/ContactsSidebar/ContactNotes.vue';
+import ContactMedia from 'dashboard/components-next/Contacts/ContactsSidebar/ContactMedia.vue';
+import ContactMerge from 'dashboard/components-next/Contacts/ContactsSidebar/ContactMerge.vue';
+
const props = defineProps({
selectedContact: {
type: Object,
@@ -38,6 +47,34 @@ const isFormInvalid = computed(() => contactsFormRef.value?.isFormInvalid);
const contactData = ref({});
+const metaSeparator = '•';
+
+const contactId = computed(() => props.selectedContact?.id);
+
+const CONTACT_SECTIONS = [
+ { value: 'details', label: 'CONTACTS_LAYOUT.PROFILE.SECTIONS.DETAILS' },
+ { value: 'attributes', label: 'CONTACTS_LAYOUT.SIDEBAR.TABS.ATTRIBUTES' },
+ { value: 'history', label: 'CONTACTS_LAYOUT.PROFILE.STATS.CONVERSATIONS' },
+ { value: 'notes', label: 'CONTACTS_LAYOUT.PROFILE.STATS.NOTES' },
+ { value: 'media', label: 'CONTACTS_LAYOUT.PROFILE.STATS.FILES' },
+ { value: 'merge', label: 'CONTACTS_LAYOUT.SIDEBAR.TABS.MERGE' },
+];
+
+const activeSection = ref('details');
+
+const sectionTabs = computed(() =>
+ CONTACT_SECTIONS.map(section => ({ label: t(section.label) }))
+);
+
+const activeSectionIndex = computed(() =>
+ CONTACT_SECTIONS.findIndex(section => section.value === activeSection.value)
+);
+
+const handleSectionChange = tab => {
+ const index = sectionTabs.value.findIndex(item => item.label === tab.label);
+ if (index !== -1) activeSection.value = CONTACT_SECTIONS[index].value;
+};
+
const getInitialContactData = () => {
if (!props.selectedContact) return {};
return { ...props.selectedContact };
@@ -45,6 +82,9 @@ const getInitialContactData = () => {
onMounted(() => {
Object.assign(contactData.value, getInitialContactData());
+ if (contactId.value) {
+ store.dispatch('contacts/fetchAttachments', contactId.value);
+ }
});
const createdAt = computed(() => {
@@ -121,8 +161,8 @@ const handleAvatarDelete = async () => {
-
-
+
+
{
@upload="handleAvatarUpload"
@delete="handleAvatarDelete"
/>
-
-
+
+
{{ selectedContact?.name }}
-
-
+
+
{{ selectedContact?.identifier }}
-
-
- {{ $t('CONTACTS_LAYOUT.DETAILS.CREATED_AT', { date: createdAt }) }}
- •
+
+ {{ metaSeparator }}
+
+
+ {{ t('CONTACTS_LAYOUT.DETAILS.CREATED_AT', { date: createdAt }) }}
+
+ {{ metaSeparator }}
+
{{
- $t('CONTACTS_LAYOUT.DETAILS.LAST_ACTIVITY', {
+ t('CONTACTS_LAYOUT.DETAILS.LAST_ACTIVITY', {
date: lastActivityAt,
})
}}
+
-
-
-
+
+
+
-
-
-
-
-
-
- {{ t('CONTACTS_LAYOUT.DETAILS.DELETE_CONTACT') }}
-
-
- {{ t('CONTACTS_LAYOUT.DETAILS.DELETE_CONTACT_DESCRIPTION') }}
-
-
-
-
-
+
diff --git a/app/javascript/dashboard/components-next/Contacts/Pages/ContactStats.vue b/app/javascript/dashboard/components-next/Contacts/Pages/ContactStats.vue
new file mode 100644
index 000000000..bda0d0f12
--- /dev/null
+++ b/app/javascript/dashboard/components-next/Contacts/Pages/ContactStats.vue
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+ {{ tile.label }}
+
+
+ {{ tile.value }}
+
+
+
+
+ {{ t('CONTACTS_LAYOUT.PROFILE.STATS.LAST_SEEN') }}
+
+
+ {{ lastSeen || t('CONTACTS_LAYOUT.PROFILE.STATS.NEVER') }}
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/SharedAttachments/Media.vue b/app/javascript/dashboard/components-next/SharedAttachments/Media.vue
index 6a3875583..a3f0c8a06 100644
--- a/app/javascript/dashboard/components-next/SharedAttachments/Media.vue
+++ b/app/javascript/dashboard/components-next/SharedAttachments/Media.vue
@@ -20,10 +20,21 @@ const props = defineProps({
attachments: { type: Array, default: () => [] },
peekLimit: { type: Number, default: 0 },
showJumpToMessage: { type: Boolean, default: false },
+ columns: { type: Number, default: 3 },
});
const emit = defineEmits(['select', 'jumpToMessage']);
+const gridColsClass = computed(
+ () =>
+ ({
+ 3: 'grid-cols-3',
+ 4: 'grid-cols-4',
+ 5: 'grid-cols-5',
+ 6: 'grid-cols-6',
+ })[props.columns] || 'grid-cols-3'
+);
+
const { t } = useI18n();
const mediaAttachments = computed(() =>
@@ -177,7 +188,7 @@ const onDownloadFile = async attachment => {
@click="showAll = !showAll"
/>
-
+
-import { onMounted, computed, ref } from 'vue';
+import { onMounted, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
import { useStore, useMapGetter } from 'dashboard/composables/store';
@@ -8,12 +8,6 @@ import { useRoute, useRouter } from 'vue-router';
import ContactsDetailsLayout from 'dashboard/components-next/Contacts/ContactsDetailsLayout.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import ContactDetails from 'dashboard/components-next/Contacts/Pages/ContactDetails.vue';
-import TabBar from 'dashboard/components-next/tabbar/TabBar.vue';
-import ContactNotes from 'dashboard/components-next/Contacts/ContactsSidebar/ContactNotes.vue';
-import ContactHistory from 'dashboard/components-next/Contacts/ContactsSidebar/ContactHistory.vue';
-import ContactMedia from 'dashboard/components-next/Contacts/ContactsSidebar/ContactMedia.vue';
-import ContactMerge from 'dashboard/components-next/Contacts/ContactsSidebar/ContactMerge.vue';
-import ContactCustomAttributes from 'dashboard/components-next/Contacts/ContactsSidebar/ContactCustomAttributes.vue';
const store = useStore();
const route = useRoute();
@@ -22,9 +16,6 @@ const router = useRouter();
const contact = useMapGetter('contacts/getContactById');
const uiFlags = useMapGetter('contacts/getUIFlags');
-const activeTab = ref('attributes');
-const contactMergeRef = ref(null);
-
const isFetchingItem = computed(() => uiFlags.value.isFetchingItem);
const isMergingContact = computed(() => uiFlags.value.isMerging);
const isUpdatingContact = computed(() => uiFlags.value.isUpdating);
@@ -37,25 +28,6 @@ const showSpinner = computed(
const { t } = useI18n();
-const CONTACT_TABS_OPTIONS = [
- { key: 'ATTRIBUTES', value: 'attributes' },
- { key: 'HISTORY', value: 'history' },
- { key: 'NOTES', value: 'notes' },
- { key: 'MEDIA', value: 'media' },
- { key: 'MERGE', value: 'merge' },
-];
-
-const tabs = computed(() => {
- return CONTACT_TABS_OPTIONS.map(tab => ({
- label: t(`CONTACTS_LAYOUT.SIDEBAR.TABS.${tab.key}`),
- value: tab.value,
- }));
-});
-
-const activeTabIndex = computed(() => {
- return CONTACT_TABS_OPTIONS.findIndex(v => v.value === activeTab.value);
-});
-
const goToContactsList = () => {
if (window.history.state?.back || window.history.length > 1) {
router.back();
@@ -74,10 +46,6 @@ const fetchActiveContact = async () => {
}
};
-const handleTabChange = tab => {
- activeTab.value = tab.value;
-};
-
const fetchContactNotes = () => {
const { contactId } = route.params;
if (contactId) store.dispatch('contactNotes/get', { contactId });
@@ -151,40 +119,6 @@ onMounted(() => {
:selected-contact="selectedContact"
@go-to-contacts-list="goToContactsList"
/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-