feature: Doctor Feedback Mode
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useFeedbackStore } from '@/stores/feedback'
|
||||
import { alertTypeLabel } from '@/api/normalize'
|
||||
import Badge from '@/components/ui/Badge.vue'
|
||||
import Button from '@/components/ui/Button.vue'
|
||||
import Card from '@/components/ui/Card.vue'
|
||||
|
||||
const feedbackStore = useFeedbackStore()
|
||||
const { entries, stats, byAlertType } = storeToRefs(feedbackStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full min-w-0 space-y-8">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h1 class="text-xl font-bold dark:text-white">Feedback Summary</h1>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Button variant="secondary" size="sm" @click="feedbackStore.exportAsJson()">
|
||||
Export JSON
|
||||
</Button>
|
||||
<Button variant="secondary" size="sm" @click="feedbackStore.exportAsCsv()">
|
||||
Export CSV
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aggregate stats -->
|
||||
<div class="grid w-full min-w-0 grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold dark:text-white">{{ stats.total }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">Total Ratings</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-green-600">{{ stats.usefulPct }}%</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">Useful / Would Act</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-red-600">{{ stats.fpPct }}%</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">False Positive</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-amber-600">
|
||||
{{ stats.total - stats.useful - stats.falsePositive }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">Timing Issues</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Per alert type breakdown -->
|
||||
<Card>
|
||||
<h2 class="mb-4 text-lg font-semibold dark:text-white">By Alert Type</h2>
|
||||
<div class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<div v-for="(feedbacks, alertType) in byAlertType" :key="alertType"
|
||||
class="flex flex-col gap-2 py-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<span class="text-sm font-medium dark:text-white">{{ alertTypeLabel(alertType) }}</span>
|
||||
<span class="ml-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
({{ feedbacks.length }} ratings)
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Badge v-if="feedbacks.filter(f => f.rating === 'useful' || f.rating === 'would-act').length"
|
||||
variant="success" size="xs">
|
||||
{{ feedbacks.filter(f => f.rating === 'useful' || f.rating === 'would-act').length }} useful
|
||||
</Badge>
|
||||
<Badge v-if="feedbacks.filter(f => f.rating === 'false-positive').length"
|
||||
variant="critical" size="xs">
|
||||
{{ feedbacks.filter(f => f.rating === 'false-positive').length }} FP
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Recent feedback entries -->
|
||||
<Card>
|
||||
<h2 class="mb-4 text-lg font-semibold dark:text-white">Recent Feedback</h2>
|
||||
<div class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<div v-for="entry in [...entries].reverse().slice(0, 20)" :key="entry.alertId"
|
||||
class="py-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<Badge :variant="entry.rating === 'false-positive' ? 'critical' : entry.rating === 'useful' || entry.rating === 'would-act' ? 'success' : 'warning'" size="xs">
|
||||
{{ entry.rating }}
|
||||
</Badge>
|
||||
<span class="text-sm dark:text-white">{{ alertTypeLabel(entry.alertType) }}</span>
|
||||
<span class="text-xs text-gray-400">{{ new Date(entry.timestamp).toLocaleString() }}</span>
|
||||
</div>
|
||||
<p v-if="entry.notes" class="mt-2 text-xs text-gray-500 dark:text-gray-400 line-clamp-2">
|
||||
{{ entry.notes }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user