Skip to content

Commit b971619

Browse files
committed
use tabs for search results seperation also looks better now.
1 parent 69accae commit b971619

File tree

2 files changed

+112
-77
lines changed

2 files changed

+112
-77
lines changed

frontend/src/features/search/SearchResults.vue

Lines changed: 111 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,139 @@
11
<template>
22
<div class="max-w-5xl mx-auto p-6 min-h-screen">
3-
<div class="space-y-8">
4-
<div
5-
v-for="(items, type) in results"
6-
:key="type"
7-
class="bg-card rounded shadow overflow-hidden"
8-
>
9-
<!-- Header for each section -->
10-
<h2
11-
class="bg-primary dark:bg-primary text-lg font-bold text-white dark:text-primary-foreground py-2 px-6 capitalize"
12-
>
13-
{{ type }}
14-
</h2>
3+
<Tabs :default-value="defaultTab" v-model="activeTab">
4+
<TabsList class="grid w-full mb-6" :class="tabsGridClass">
5+
<TabsTrigger v-for="(items, type) in results" :key="type" :value="type" class="capitalize">
6+
{{ type }} ({{ items.length }})
7+
</TabsTrigger>
8+
</TabsList>
159

16-
<!-- No results message -->
17-
<div v-if="items.length === 0" class="p-6 text-gray-500 dark:text-muted-foreground">
18-
{{
19-
$t('globals.messages.noResults', {
20-
name: type
21-
})
22-
}}
23-
</div>
10+
<TabsContent v-for="(items, type) in results" :key="type" :value="type" class="mt-0">
11+
<div class="bg-background rounded border overflow-hidden">
12+
<!-- No results message -->
13+
<div v-if="items.length === 0" class="p-8 text-center text-muted-foreground">
14+
<div class="text-lg font-medium mb-2">
15+
{{
16+
$t('globals.messages.noResults', {
17+
name: type
18+
})
19+
}}
20+
</div>
21+
<div class="text-sm">{{ $t('search.adjustSearchTerms') }}</div>
22+
</div>
2423

25-
<!-- Results list -->
26-
<div class="divide-y divide-gray-200 dark:divide-border">
27-
<div
28-
v-for="item in items"
29-
:key="item.id || item.uuid"
30-
class="p-6 hover:bg-gray-100 dark:hover:bg-accent transition duration-300 ease-in-out group"
31-
>
32-
<router-link
33-
:to="{
34-
name: 'inbox-conversation',
35-
params: {
36-
uuid: type === 'conversations' ? item.uuid : item.conversation_uuid,
37-
type: 'assigned'
38-
}
39-
}"
40-
class="block"
24+
<!-- Results list -->
25+
<div v-else class="divide-y divide-border">
26+
<div
27+
v-for="item in items"
28+
:key="item.id || item.uuid"
29+
class="p-6 hover:bg-accent/50 transition duration-200 ease-in-out group"
4130
>
42-
<div class="flex justify-between items-start">
43-
<div class="flex-grow">
44-
<!-- Reference number -->
45-
<div
46-
class="text-sm font-semibold mb-2 group-hover:text-primary dark:group-hover:text-primary transition duration-300"
47-
>
48-
#{{
49-
type === 'conversations'
50-
? item.reference_number
51-
: item.conversation_reference_number
52-
}}
31+
<router-link
32+
:to="{
33+
name: 'inbox-conversation',
34+
params: {
35+
uuid: type === 'conversations' ? item.uuid : item.conversation_uuid,
36+
type: 'assigned'
37+
}
38+
}"
39+
class="block"
40+
>
41+
<div class="flex justify-between items-start">
42+
<div class="flex-grow">
43+
<!-- Reference number -->
44+
<div
45+
class="text-sm font-semibold mb-2 text-muted-foreground group-hover:text-primary transition duration-200"
46+
>
47+
#{{
48+
type === 'conversations'
49+
? item.reference_number
50+
: item.conversation_reference_number
51+
}}
52+
</div>
53+
54+
<!-- Content -->
55+
<div
56+
class="text-foreground font-medium mb-2 text-lg group-hover:text-primary transition duration-200"
57+
>
58+
{{
59+
truncateText(
60+
type === 'conversations' ? item.subject : item.text_content,
61+
100
62+
)
63+
}}
64+
</div>
65+
66+
<!-- Timestamp -->
67+
<div class="text-sm text-muted-foreground flex items-center">
68+
<ClockIcon class="h-4 w-4 mr-1" />
69+
{{
70+
formatDate(
71+
type === 'conversations' ? item.created_at : item.conversation_created_at
72+
)
73+
}}
74+
</div>
5375
</div>
5476

55-
<!-- Content -->
77+
<!-- Right arrow icon -->
5678
<div
57-
class="text-gray-900 dark:text-card-foreground font-medium mb-2 text-lg group-hover:text-gray-950 dark:group-hover:text-foreground transition duration-300"
79+
class="bg-secondary rounded-full p-2 group-hover:bg-primary transition duration-200"
5880
>
59-
{{
60-
truncateText(type === 'conversations' ? item.subject : item.text_content, 100)
61-
}}
62-
</div>
63-
64-
<!-- Timestamp -->
65-
<div class="text-sm text-gray-500 dark:text-muted-foreground flex items-center">
66-
<ClockIcon class="h-4 w-4 mr-1" />
67-
{{
68-
formatDate(
69-
type === 'conversations' ? item.created_at : item.conversation_created_at
70-
)
71-
}}
81+
<ChevronRightIcon
82+
class="h-5 w-5 text-secondary-foreground group-hover:text-primary-foreground"
83+
aria-hidden="true"
84+
/>
7285
</div>
7386
</div>
74-
75-
<!-- Right arrow icon -->
76-
<div
77-
class="bg-gray-200 dark:bg-secondary rounded-full p-2 group-hover:bg-primary dark:group-hover:bg-primary transition duration-300"
78-
>
79-
<ChevronRightIcon
80-
class="h-5 w-5 text-gray-700 dark:text-secondary-foreground group-hover:text-white dark:group-hover:text-primary-foreground"
81-
aria-hidden="true"
82-
/>
83-
</div>
84-
</div>
85-
</router-link>
87+
</router-link>
88+
</div>
8689
</div>
8790
</div>
88-
</div>
89-
</div>
91+
</TabsContent>
92+
</Tabs>
9093
</div>
9194
</template>
9295
<script setup>
96+
import { computed, ref, watch } from 'vue'
9397
import { ChevronRightIcon, ClockIcon } from 'lucide-vue-next'
9498
import { format, parseISO } from 'date-fns'
99+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
95100
96-
defineProps({
101+
const props = defineProps({
97102
results: {
98103
type: Object,
99104
required: true
100105
}
101106
})
102107
108+
// Get the first available tab as default
109+
const defaultTab = computed(() => {
110+
const types = Object.keys(props.results)
111+
return types.length > 0 ? types[0] : ''
112+
})
113+
114+
const activeTab = ref('')
115+
116+
// Watch for changes in results and set the first tab as active
117+
watch(
118+
() => props.results,
119+
(newResults) => {
120+
const types = Object.keys(newResults)
121+
if (types.length > 0 && !activeTab.value) {
122+
activeTab.value = types[0]
123+
}
124+
},
125+
{ immediate: true }
126+
)
127+
128+
// Dynamic grid class based on number of tabs
129+
const tabsGridClass = computed(() => {
130+
const tabCount = Object.keys(props.results).length
131+
if (tabCount <= 2) return 'grid-cols-2'
132+
if (tabCount <= 3) return 'grid-cols-3'
133+
if (tabCount <= 4) return 'grid-cols-4'
134+
return 'grid-cols-5'
135+
})
136+
103137
const formatDate = (dateString) => {
104138
const date = parseISO(dateString)
105139
return format(date, 'MMM d, yyyy HH:mm')

i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@
568568
"search.noResultsForQuery": "No results found for query `{query}`. Try a different search term.",
569569
"search.minQueryLength": " Please enter at least {length} characters to search.",
570570
"search.searchBy": "Search by reference number, contact email address or messages in conversations.",
571+
"search.adjustSearchTerms": "Try adjusting your search terms or filters.",
571572
"sla.overdueBy": "Overdue by",
572573
"sla.met": "SLA met",
573574
"view.form.description": "Create and save custom filter views for quick access to your conversations.",

0 commit comments

Comments
 (0)