refactor: натравил форматтер на весь фронт
This commit is contained in:
@@ -14,9 +14,9 @@ const lecturesStore = useLecturesStore()
|
||||
const auth = useAuthStore()
|
||||
const reviews = ref<Review[]>([])
|
||||
|
||||
const positive = computed(() => reviews.value.filter(r => r.sentiment === 'positive').length)
|
||||
const neutral = computed(() => reviews.value.filter(r => r.sentiment === 'neutral').length)
|
||||
const negative = computed(() => reviews.value.filter(r => r.sentiment === 'negative').length)
|
||||
const positive = computed(() => reviews.value.filter((r) => r.sentiment === 'positive').length)
|
||||
const neutral = computed(() => reviews.value.filter((r) => r.sentiment === 'neutral').length)
|
||||
const negative = computed(() => reviews.value.filter((r) => r.sentiment === 'negative').length)
|
||||
const total = computed(() => reviews.value.length || 1)
|
||||
const pct = (value: number) => Math.round((value / total.value) * 100)
|
||||
|
||||
@@ -24,8 +24,10 @@ async function fetchTeacherAnalytics() {
|
||||
if (!auth.user?.id) return
|
||||
await lecturesStore.fetchLectures({ TeacherId: auth.user.id })
|
||||
const targetLectures = lecturesStore.all.slice(0, 5)
|
||||
const payload = await Promise.allSettled(targetLectures.map(l => lecturesApi.reviews(l.id)))
|
||||
reviews.value = payload.flatMap(result => (result.status === 'fulfilled' ? result.value.map(mapApiReview) : []))
|
||||
const payload = await Promise.allSettled(targetLectures.map((l) => lecturesApi.reviews(l.id)))
|
||||
reviews.value = payload.flatMap((result) =>
|
||||
result.status === 'fulfilled' ? result.value.map(mapApiReview) : [],
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(fetchTeacherAnalytics)
|
||||
@@ -57,11 +59,19 @@ watch(() => auth.user?.id, fetchTeacherAnalytics)
|
||||
</div>
|
||||
<div>
|
||||
<div class="sentiment-label">Нейтральные {{ pct(neutral) }}%</div>
|
||||
<ProgressBar :value="pct(neutral)" :max="100" color="linear-gradient(90deg, #7DD3FC, #BAE6FD)" />
|
||||
<ProgressBar
|
||||
:value="pct(neutral)"
|
||||
:max="100"
|
||||
color="linear-gradient(90deg, #7DD3FC, #BAE6FD)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sentiment-label">Негативные {{ pct(negative) }}%</div>
|
||||
<ProgressBar :value="pct(negative)" :max="100" color="linear-gradient(90deg, #FCA5A5, #FECACA)" />
|
||||
<ProgressBar
|
||||
:value="pct(negative)"
|
||||
:max="100"
|
||||
color="linear-gradient(90deg, #FCA5A5, #FECACA)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
@@ -70,8 +80,9 @@ watch(() => auth.user?.id, fetchTeacherAnalytics)
|
||||
<GlassCard>
|
||||
<div class="section-title">LLM-сводка проблем и рекомендаций</div>
|
||||
<p class="summary">
|
||||
Студенты отмечают сильную практическую часть, но хотят больше разборов вопросов из аудитории.
|
||||
Рекомендуется добавить блок с кейсами из реальных проектов и увеличить время на интерактив.
|
||||
Студенты отмечают сильную практическую часть, но хотят больше разборов вопросов из
|
||||
аудитории. Рекомендуется добавить блок с кейсами из реальных проектов и увеличить время на
|
||||
интерактив.
|
||||
</p>
|
||||
<div class="tags">
|
||||
<span class="tag-chip">много практики</span>
|
||||
@@ -83,29 +94,92 @@ watch(() => auth.user?.id, fetchTeacherAnalytics)
|
||||
|
||||
<GlassCard>
|
||||
<div class="section-title">Отзывы</div>
|
||||
<EmptyState v-if="!reviews.length" title="Отзывов пока нет" subtitle="Когда студенты оставят отзывы, они появятся здесь." />
|
||||
<EmptyState
|
||||
v-if="!reviews.length"
|
||||
title="Отзывов пока нет"
|
||||
subtitle="Когда студенты оставят отзывы, они появятся здесь."
|
||||
/>
|
||||
<div v-else class="reviews">
|
||||
<div v-for="review in reviews" :key="review.id" class="review">
|
||||
«{{ review.text }}»
|
||||
</div>
|
||||
<div v-for="review in reviews" :key="review.id" class="review">«{{ review.text }}»</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.teacher-analytics { display: flex; flex-direction: column; gap: 18px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
|
||||
.chart { display: flex; gap: 12px; align-items: flex-end; height: 160px; padding: 10px 0; }
|
||||
.bar { display: flex; flex-direction: column; align-items: center; gap: 6px; }
|
||||
.bar-fill { width: 26px; border-radius: 6px 6px 0 0; background: linear-gradient(180deg, #22C55E, #86EFAC); }
|
||||
.bar-label { font-size: 11px; color: var(--color-text-secondary); }
|
||||
.avg { margin-top: 6px; font-weight: 600; }
|
||||
.sentiment { display: flex; flex-direction: column; gap: 12px; }
|
||||
.sentiment-label { font-size: 12px; color: var(--color-text-secondary); margin-bottom: 4px; }
|
||||
.summary { font-size: 14px; color: var(--color-text-secondary); line-height: 1.5; }
|
||||
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
||||
.reviews { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
|
||||
.review { background: rgba(255,255,255,0.6); border: 1px solid var(--color-border-glass); padding: 10px; border-radius: var(--radius-sm); font-size: 13px; }
|
||||
.top-list { padding-left: 18px; color: var(--color-text-secondary); font-size: 13px; }
|
||||
.teacher-analytics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.chart {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-end;
|
||||
height: 160px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.bar-fill {
|
||||
width: 26px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
background: linear-gradient(180deg, #22c55e, #86efac);
|
||||
}
|
||||
.bar-label {
|
||||
font-size: 11px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.avg {
|
||||
margin-top: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sentiment {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.sentiment-label {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.summary {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.reviews {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.review {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border: 1px solid var(--color-border-glass);
|
||||
padding: 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 13px;
|
||||
}
|
||||
.top-list {
|
||||
padding-left: 18px;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user