Skip to content

Commit f427203

Browse files
authored
fix: overall feedbacks admin improvements (#1938)
1 parent 415d4c3 commit f427203

File tree

4 files changed

+138
-106
lines changed

4 files changed

+138
-106
lines changed

app/components/AdminDashboard.vue

Lines changed: 73 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,16 @@ function useAdminTable() {
255255
}
256256
257257
const { globalStats, pageAnalytics } = useFeedbackData(rawFeedback)
258-
const { getScoreColor } = useFeedbackRatings()
259258
const { table, pagination, sorting, globalFilter, columns, resetFilters, filterByVersion, filteredPageAnalytics, versionFilter } = useAdminTable()
260259
const { selectedPage, showFeedbackModal, currentPage, itemsPerPage, paginatedFeedback, totalPages, viewPageDetails } = useFeedbackModal()
260+
261+
const feedbackContainer = useTemplateRef<HTMLElement>('feedbackContainer')
262+
263+
watch(currentPage, () => {
264+
if (feedbackContainer.value) {
265+
feedbackContainer.value.scrollTop = 0
266+
}
267+
})
261268
</script>
262269

263270
<template>
@@ -347,14 +354,14 @@ const { selectedPage, showFeedbackModal, currentPage, itemsPerPage, paginatedFee
347354
</div>
348355

349356
<div class="border-t border-default pt-6">
350-
<div class="flex items-center justify-between mb-4">
357+
<div class="flex sm:items-center justify-between flex-col sm:flex-row mb-4 gap-4">
351358
<h2 class="text-xl font-semibold">
352359
Feedback by Page
353360
</h2>
354361
<div class="flex items-center gap-2">
355362
<UInput
356363
v-model="globalFilter"
357-
class="max-w-sm"
364+
class="flex-1 max-w-sm"
358365
placeholder="Search pages..."
359366
icon="i-lucide-search"
360367
/>
@@ -418,51 +425,71 @@ const { selectedPage, showFeedbackModal, currentPage, itemsPerPage, paginatedFee
418425
</UCard>
419426
</UContainer>
420427

421-
<UModal v-model:open="showFeedbackModal" :ui="{ content: 'max-w-3xl' }">
428+
<UModal v-model:open="showFeedbackModal" :ui="{ content: 'max-w-3xl max-sm:max-h-[85vh] overflow-y-auto' }">
422429
<template #content>
423430
<UCard v-if="selectedPage">
424431
<template #header>
425-
<div class="flex items-start justify-between gap-4">
426-
<div class="flex-1">
427-
<h3 class="font-semibold text-xl mb-2">
428-
{{ selectedPage.lastFeedback.title }}
429-
</h3>
430-
<div class="flex items-center gap-1 flex-wrap">
431-
<ULink :to="`https://nuxt.com${selectedPage.path}`" target="_blank">
432-
<code class="text-sm bg-muted px-2 py-1 rounded">{{ selectedPage.path }}</code>
433-
</ULink>
434-
<UButton
435-
size="sm"
436-
variant="ghost"
437-
color="neutral"
438-
icon="i-lucide-external-link"
439-
:to="`https://nuxt.com${selectedPage.path}`"
440-
target="_blank"
441-
/>
442-
<UButton
443-
v-if="selectedPage.lastFeedback.stem"
444-
size="sm"
445-
variant="ghost"
446-
color="neutral"
447-
:to="`https://github.com/nuxt/nuxt/edit/main/${selectedPage.lastFeedback.stem.replace('docs/4.x', 'docs')}.md`"
448-
target="_blank"
449-
icon="i-simple-icons-github"
450-
class="shadow-sm"
451-
/>
452-
</div>
453-
</div>
454-
<div class="text-right flex-shrink-0">
455-
<div class="text-3xl font-bold mb-1" :class="getScoreColor(selectedPage.averageScore)">
456-
{{ selectedPage.averageScore }}/4
457-
</div>
458-
<div class="text-sm text-muted">
459-
{{ selectedPage.total }} {{ selectedPage.total === 1 ? 'response' : 'responses' }}
460-
</div>
432+
<UButton
433+
size="sm"
434+
variant="ghost"
435+
color="neutral"
436+
icon="i-lucide-x"
437+
class="absolute top-2 right-2"
438+
@click="showFeedbackModal = false"
439+
/>
440+
<div>
441+
<h3 class="font-semibold text-lg sm:text-xl mb-2">
442+
{{ selectedPage.lastFeedback.title }}
443+
</h3>
444+
<div class="flex items-center gap-1">
445+
<ULink :to="`https://nuxt.com${selectedPage.path}`" target="_blank">
446+
<code class="text-xs sm:text-sm bg-muted px-2 py-1 rounded">{{ selectedPage.path }}</code>
447+
</ULink>
448+
<UButton
449+
size="sm"
450+
variant="ghost"
451+
color="neutral"
452+
icon="i-lucide-external-link"
453+
:to="`https://nuxt.com${selectedPage.path}`"
454+
target="_blank"
455+
/>
456+
<UButton
457+
v-if="selectedPage.lastFeedback.stem"
458+
size="sm"
459+
variant="ghost"
460+
color="neutral"
461+
:to="`https://github.com/nuxt/nuxt/edit/main/${selectedPage.lastFeedback.stem.replace('docs/4.x', 'docs')}.md`"
462+
target="_blank"
463+
icon="i-simple-icons-github"
464+
/>
461465
</div>
462466
</div>
463467
</template>
464468

465-
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-6">
469+
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2 sm:gap-3 mb-4 sm:mb-6">
470+
<FeedbackStatCard
471+
icon="i-lucide-target"
472+
icon-color="text-primary"
473+
:value="`${selectedPage.averageScore}/4`"
474+
label="Average Score"
475+
:popover-stats="{
476+
percentage: `${Math.round(selectedPage.averageScore / 4 * 100)}% satisfaction`,
477+
trend: `${selectedPage.averageScore >= 3.5 ? '🎯 Excellent' : selectedPage.averageScore >= 3.0 ? '👍 Good' : '⚠️ Poor'}`,
478+
details: `Based on ${selectedPage.total} ${selectedPage.total === 1 ? 'response' : 'responses'}`
479+
}"
480+
/>
481+
482+
<FeedbackStatCard
483+
icon="i-lucide-message-circle"
484+
icon-color="text-muted"
485+
:value="selectedPage.total"
486+
label="Responses"
487+
:popover-stats="{
488+
trend: `${selectedPage.total === 1 ? 'Single feedback' : 'Multiple feedbacks'}`,
489+
details: 'Total number of user feedback submissions for this page'
490+
}"
491+
/>
492+
466493
<FeedbackStatCard
467494
icon="i-lucide-thumbs-up"
468495
icon-color="text-success"
@@ -486,27 +513,15 @@ const { selectedPage, showFeedbackModal, currentPage, itemsPerPage, paginatedFee
486513
details: 'Users found issues with this page'
487514
}"
488515
/>
489-
490-
<FeedbackStatCard
491-
icon="i-lucide-target"
492-
icon-color="text-primary"
493-
:value="`${selectedPage.averageScore}/4`"
494-
label="Average Score"
495-
:popover-stats="{
496-
percentage: `${Math.round(selectedPage.averageScore / 4 * 100)}% satisfaction`,
497-
trend: `${selectedPage.averageScore >= 3.5 ? '🎯 Excellent' : selectedPage.averageScore >= 3.0 ? '👍 Good' : '⚠️ Poor'}`,
498-
details: `Based on ${selectedPage.total} ${selectedPage.total === 1 ? 'response' : 'responses'}`
499-
}"
500-
/>
501516
</div>
502517

503-
<div class="space-y-4">
504-
<h4 class="text-lg font-semibold mb-4 flex items-center gap-2">
505-
<UIcon name="i-lucide-message-square" class="size-5" />
518+
<div class="space-y-3 sm:space-y-4">
519+
<h4 class="text-base sm:text-lg font-semibold mb-3 sm:mb-4 flex items-center gap-2">
520+
<UIcon name="i-lucide-message-square" class="size-4 sm:size-5" />
506521
Individual Feedback
507522
</h4>
508-
<div class="space-y-4">
509-
<div class="space-y-3 max-h-[400px] overflow-y-auto">
523+
<div class="space-y-3 sm:space-y-4">
524+
<div ref="feedbackContainer" class="space-y-2 sm:space-y-3 max-h-[300px] sm:max-h-[400px] overflow-y-auto">
510525
<FeedbackItem
511526
v-for="(feedback, index) in paginatedFeedback"
512527
:key="index"

app/components/feedback/FeedbackChart.vue

Lines changed: 57 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,11 @@ const chartCategories = computed(() => {
236236
return {
237237
positive: {
238238
name: 'Positive',
239-
color: '#22c55e'
239+
color: 'var(--ui-success)'
240240
},
241241
negative: {
242242
name: 'Negative',
243-
color: '#ef4444'
243+
color: 'var(--ui-error)'
244244
}
245245
}
246246
}
@@ -392,28 +392,30 @@ function selectWorstPages(count: number) {
392392
.slice(0, count)
393393
selectedPagePaths.value = pages.map(p => p.path)
394394
}
395-
396-
function clearSearch() {
397-
pageSearchQuery.value = ''
398-
}
399395
</script>
400396

401397
<template>
402398
<div class="space-y-6">
403-
<div class="flex items-center justify-between">
404-
<div class="flex items-center gap-3">
405-
<UIcon :name="chartIcon" class="size-5 text-primary" />
406-
<div>
407-
<h3 class="text-lg font-semibold">
399+
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
400+
<Motion
401+
:key="`header-${chartType}`"
402+
:initial="{ opacity: 0, y: 10 }"
403+
:animate="{ opacity: 1, y: 0 }"
404+
:transition="{ duration: 0.4, ease: 'easeInOut' }"
405+
class="flex items-center gap-3"
406+
>
407+
<UIcon :name="chartIcon" class="size-6 sm:size-5 text-primary shrink-0" />
408+
<div class="min-w-0">
409+
<h3 class="text-lg font-semibold truncate">
408410
{{ chartTitle }}
409411
</h3>
410412
<p class="text-sm text-muted">
411413
{{ chartDescription }}
412414
</p>
413415
</div>
414-
</div>
416+
</Motion>
415417

416-
<div class="flex items-center gap-2">
418+
<div class="flex items-center max-sm:flex-row-reverse max-sm:justify-end gap-2 flex-wrap">
417419
<AnimatePresence mode="wait">
418420
<Motion
419421
v-if="chartType !== 'overall'"
@@ -460,7 +462,6 @@ function clearSearch() {
460462
:x-label="dateRangeLabel"
461463
y-label="Rating (out of 4)"
462464
:show-tooltip="true"
463-
class="min-h-[300px]"
464465
/>
465466

466467
<LineChart
@@ -502,36 +503,44 @@ function clearSearch() {
502503
<template #content>
503504
<UCard>
504505
<template #header>
506+
<UButton
507+
size="sm"
508+
variant="ghost"
509+
color="neutral"
510+
icon="i-lucide-x"
511+
class="absolute top-2 right-2"
512+
@click="showPageSelector = false"
513+
/>
505514
<div class="space-y-3">
506515
<h3 class="text-lg font-semibold">
507516
Select Pages to {{ chartType === 'line' ? 'Track' : 'Compare' }}
508517
</h3>
509-
<div class="flex items-center gap-3 flex-wrap">
518+
<div class="flex items-center gap-2 flex-wrap">
510519
<span class="text-sm text-muted font-medium">Quick select:</span>
511520
<UButton
512521
size="sm"
513-
variant="ghost"
522+
variant="soft"
514523
color="neutral"
515524
label="Best Rated 5"
516525
@click="selectBestRatedPages(5)"
517526
/>
518527
<UButton
519528
size="sm"
520-
variant="ghost"
529+
variant="soft"
521530
color="neutral"
522531
label="Most Popular 5"
523532
@click="selectTopPages(5)"
524533
/>
525534
<UButton
526535
size="sm"
527-
variant="ghost"
536+
variant="soft"
528537
color="neutral"
529538
label="Worst Rated 5"
530539
@click="selectWorstPages(5)"
531540
/>
532541
<UButton
533542
size="sm"
534-
variant="ghost"
543+
variant="soft"
535544
color="neutral"
536545
label="Worst Rated 10"
537546
@click="selectWorstPages(10)"
@@ -543,12 +552,22 @@ function clearSearch() {
543552
<div class="mb-4">
544553
<UInput
545554
v-model="pageSearchQuery"
546-
placeholder="Search pages by title or path..."
555+
placeholder="Search pages..."
547556
icon="i-lucide-search"
548-
:trailing-icon="pageSearchQuery ? 'i-lucide-x' : undefined"
549557
class="w-full"
550-
@trailing-click="clearSearch"
551-
/>
558+
:ui="{ trailing: 'pe-1' }"
559+
>
560+
<template v-if="pageSearchQuery?.length" #trailing>
561+
<UButton
562+
color="neutral"
563+
variant="link"
564+
size="sm"
565+
icon="i-lucide-circle-x"
566+
aria-label="Clear input"
567+
@click="pageSearchQuery = ''"
568+
/>
569+
</template>
570+
</UInput>
552571
</div>
553572

554573
<div class="space-y-3 max-h-96 overflow-y-auto">
@@ -559,54 +578,45 @@ function clearSearch() {
559578
:class="{ 'bg-primary/5 border-primary/20 hover:bg-primary/10': selectedPagePaths.includes(page.path) }"
560579
@click="togglePageSelection(page.path)"
561580
>
562-
<div class="flex items-center gap-3 flex-1">
581+
<div class="flex items-center gap-3 flex-1 min-w-0">
563582
<UCheckbox
564583
:model-value="selectedPagePaths.includes(page.path)"
565584
@update:model-value="togglePageSelection(page.path)"
566585
/>
567-
<div class="flex-1">
568-
<div class="font-medium text-sm">
586+
<div class="flex-1 min-w-0">
587+
<div class="font-medium text-sm truncate">
569588
{{ page.title }}
570589
</div>
571-
<code class="text-xs text-muted">{{ page.path }}</code>
590+
<code class="text-xs text-muted truncate block">{{ page.path }}</code>
572591
</div>
573592
</div>
574-
<div class="flex items-center gap-4 text-sm">
593+
<div class="flex items-center gap-2 sm:gap-4 text-sm shrink-0">
575594
<div class="text-center">
576595
<div class="font-semibold">
577596
{{ page.total }}
578597
</div>
579598
<div class="text-muted text-xs">
580-
responses
599+
resp.
581600
</div>
582601
</div>
583602
<div class="text-center">
584603
<div class="font-semibold" :class="page.score >= 3.5 ? 'text-success' : page.score >= 3.0 ? 'text-warning' : 'text-error'">
585604
{{ page.score.toFixed(1) }}/4
586605
</div>
587606
<div class="text-muted text-xs">
588-
avg score
607+
score
589608
</div>
590609
</div>
591610
</div>
592611
</div>
593612

594-
<div v-if="availablePages.length === 0" class="text-center py-8 text-muted">
595-
<UIcon name="i-lucide-search-x" class="size-8 mx-auto mb-2" />
596-
<p>No pages found matching "{{ pageSearchQuery }}"</p>
613+
<div v-if="availablePages.length === 0" class="text-center py-8">
614+
<UIcon name="i-lucide-search-x" class="size-8 text-muted mx-auto mb-2" />
615+
<p class="text-sm text-muted">
616+
No pages found matching your search
617+
</p>
597618
</div>
598619
</div>
599-
600-
<template #footer>
601-
<div class="flex justify-between items-center">
602-
<div class="text-sm text-muted">
603-
{{ selectedPagePaths.length }} pages selected
604-
</div>
605-
<UButton @click="showPageSelector = false">
606-
Done
607-
</UButton>
608-
</div>
609-
</template>
610620
</UCard>
611621
</template>
612622
</UModal>
@@ -622,9 +632,9 @@ function clearSearch() {
622632
--vis-tooltip-value-color: rgba(0, 0, 0, 1) !important;
623633
624634
--vis-axis-grid-color: rgba(255, 255, 255, 0.1) !important;
625-
--vis-axis-tick-label-color: rgba(255, 255, 255, 0.6) !important;
626-
--vis-axis-label-color: rgba(255, 255, 255, 0.8) !important;
627-
--vis-legend-label-color: rgba(255, 255, 255, 0.8) !important;
635+
--vis-axis-tick-label-color: var(--ui-text-muted) !important;
636+
--vis-axis-label-color: var(--ui-text-toned) !important;
637+
--vis-legend-label-color: var(--ui-text-muted) !important;
628638
629639
--dot-pattern-color: #111827;
630640
}

0 commit comments

Comments
 (0)