v1.4.3: 관리자 UI·홈·미디어 개선
- 관리자 라이트 테마 격리, 대시보드 활성 링크, 로그인 우측 정렬 - 대시보드 통계 추이 차트·툴팁, 홈 Latest/Featured 보정 - 미디어 종류·미사용 필터, 비디오 프레임 썸네일 - NAS 운영 업데이트 절차 문서 추가
This commit is contained in:
@@ -111,7 +111,22 @@ const formatTrendValue = (key, value) => {
|
||||
return formatEngagedDuration(value)
|
||||
}
|
||||
|
||||
return `${Number(value || 0)}`
|
||||
if (key === 'visitors') {
|
||||
return `${Number(value || 0)}명`
|
||||
}
|
||||
|
||||
return `${Number(value || 0)}회`
|
||||
}
|
||||
|
||||
/**
|
||||
* 추세 막대 툴팁 문구를 반환한다.
|
||||
* @param {{ key: 'visitors' | 'avgEngagedSeconds' | 'scroll50Reach', title: string }} metric - 지표 정보
|
||||
* @param {Object} row - 추세 행
|
||||
* @returns {string} 툴팁 문구
|
||||
*/
|
||||
const getTrendTooltipLabel = (metric, row) => {
|
||||
const dayLabel = row.label || formatTrendDayLabel(row.day)
|
||||
return `${dayLabel} · ${metric.title} ${formatTrendValue(metric.key, row[metric.key])}`
|
||||
}
|
||||
|
||||
const chartMetrics = computed(() => [
|
||||
@@ -139,7 +154,7 @@ const chartMetrics = computed(() => [
|
||||
* @returns {number} 높이 %
|
||||
*/
|
||||
const getTrendBarHeight = (key, row) => {
|
||||
const rows = trendRows.value
|
||||
const rows = chartTrendRows.value
|
||||
const maxValue = Math.max(...rows.map((item) => Number(item[key] || 0)), 0)
|
||||
|
||||
if (maxValue <= 0) {
|
||||
@@ -164,6 +179,67 @@ const formatTrendDayLabel = (day) => {
|
||||
return `${month}.${date}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택 기간에 맞는 차트 표시 집계 단위를 반환한다.
|
||||
* @param {number} days - 선택 기간
|
||||
* @returns {number} 묶을 일수
|
||||
*/
|
||||
const getTrendChartBucketSize = (days) => {
|
||||
if (days <= 14) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (days <= 60) {
|
||||
return 7
|
||||
}
|
||||
|
||||
if (days <= 180) {
|
||||
return 14
|
||||
}
|
||||
|
||||
return 30
|
||||
}
|
||||
|
||||
/**
|
||||
* 차트 표시용 추세 묶음 행을 만든다.
|
||||
* @param {Array<Object>} rows - 일자별 추세 행
|
||||
* @param {number} bucketSize - 묶을 일수
|
||||
* @returns {Array<Object>} 차트 표시 행
|
||||
*/
|
||||
const buildTrendChartRows = (rows, bucketSize) => {
|
||||
if (bucketSize <= 1) {
|
||||
return rows.map((row) => ({
|
||||
...row,
|
||||
label: formatTrendDayLabel(row.day)
|
||||
}))
|
||||
}
|
||||
|
||||
const buckets = []
|
||||
for (let index = 0; index < rows.length; index += bucketSize) {
|
||||
const bucketRows = rows.slice(index, index + bucketSize)
|
||||
const firstRow = bucketRows[0]
|
||||
const lastRow = bucketRows[bucketRows.length - 1]
|
||||
const engagedRows = bucketRows.filter((row) => Number(row.avgEngagedSeconds || 0) > 0)
|
||||
const totalEngagedSeconds = engagedRows.reduce((sum, row) => sum + Number(row.avgEngagedSeconds || 0), 0)
|
||||
const startLabel = formatTrendDayLabel(firstRow?.day)
|
||||
const endLabel = formatTrendDayLabel(lastRow?.day)
|
||||
|
||||
buckets.push({
|
||||
day: firstRow?.day || '',
|
||||
label: startLabel === endLabel ? startLabel : `${startLabel}-${endLabel}`,
|
||||
visitors: bucketRows.reduce((sum, row) => sum + Number(row.visitors || 0), 0),
|
||||
avgEngagedSeconds: engagedRows.length ? Math.round(totalEngagedSeconds / engagedRows.length) : 0,
|
||||
scroll50Reach: bucketRows.reduce((sum, row) => sum + Number(row.scroll50Reach || 0), 0)
|
||||
})
|
||||
}
|
||||
|
||||
return buckets
|
||||
}
|
||||
|
||||
const chartTrendRows = computed(() => {
|
||||
return buildTrendChartRows(trendRows.value, getTrendChartBucketSize(selectedAnalyticsDays.value))
|
||||
})
|
||||
|
||||
/**
|
||||
* 현재 접속자가 보고 있는 화면명을 반환한다.
|
||||
* @param {Object} session - 접속 세션
|
||||
@@ -268,19 +344,35 @@ watch(selectedAnalyticsDays, () => {
|
||||
{{ metric.label }}
|
||||
</strong>
|
||||
</div>
|
||||
<div class="admin-dashboard__chart-bars mt-4 flex h-32 items-end gap-1 border-b border-line">
|
||||
<div
|
||||
class="admin-dashboard__chart-bars mt-4 flex h-32 items-end gap-1 border-b border-line"
|
||||
role="img"
|
||||
:aria-label="`${metric.title} ${analyticsRangeLabel} 추이`"
|
||||
>
|
||||
<div
|
||||
v-for="row in trendRows"
|
||||
v-for="row in chartTrendRows"
|
||||
:key="`${metric.key}-${row.day}`"
|
||||
class="admin-dashboard__chart-bar-wrap flex min-w-[3px] flex-1 items-end"
|
||||
:title="`${row.day} · ${formatTrendValue(metric.key, row[metric.key])}`"
|
||||
class="admin-dashboard__chart-bar-wrap group relative flex h-full min-w-[3px] flex-1 items-end"
|
||||
tabindex="0"
|
||||
:aria-label="getTrendTooltipLabel(metric, row)"
|
||||
>
|
||||
<div
|
||||
class="admin-dashboard__chart-bar w-full bg-[#15171a]/80"
|
||||
class="admin-dashboard__chart-tooltip pointer-events-none absolute bottom-full left-1/2 z-10 mb-2 hidden -translate-x-1/2 whitespace-nowrap rounded bg-[#15171a] px-2 py-1 text-[11px] font-medium text-white shadow-lg group-hover:block group-focus-visible:block"
|
||||
>
|
||||
{{ getTrendTooltipLabel(metric, row) }}
|
||||
</div>
|
||||
<div
|
||||
class="admin-dashboard__chart-bar w-full rounded-t-sm bg-[#15171a] transition-colors group-hover:bg-[#2f6feb] group-focus-visible:bg-[#2f6feb]"
|
||||
:style="{ height: `${getTrendBarHeight(metric.key, row)}%` }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="!chartTrendRows.length"
|
||||
class="admin-dashboard__chart-empty mt-4 text-center text-xs text-muted"
|
||||
>
|
||||
선택한 기간에 표시할 추이 데이터가 없습니다.
|
||||
</p>
|
||||
<div class="mt-2 flex justify-between text-[11px] text-muted">
|
||||
<span>{{ formatTrendDayLabel(trendStartDay) }}</span>
|
||||
<span>{{ analyticsRangeLabel }}</span>
|
||||
|
||||
Reference in New Issue
Block a user