Files
dashboards/apps/business/src/pages/StatisticsPage.tsx
T
Alireza HassaniandCursor 652f4d07b6 Show other-pages stats and store-specials AI matching.
Add the Statistics pages row for static_page views, wire specials AI match UI, and tidy Businesses page styles.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 17:33:44 +03:30

163 lines
5.0 KiB
TypeScript

import { useEffect, useState } from 'react'
import { Breadcrumbs } from '../components/Breadcrumbs'
import { HomeChartSlot } from '../components/HomeChartSlot'
import { PageTitle } from '../components/PageTitle'
import { useTenantBranding } from '../context/TenantBrandingContext'
import { useLocale } from '@meshkee/dashboard-ui'
import { useT } from '../i18n/useT'
import { translate, type BusinessMessageKey } from '../i18n/messages'
import { ApiError } from '../lib/api'
import {
getViewSummary,
type ViewPeriodCounts,
type ViewSummaryGroupId,
} from '../services/dailyActivityService'
import {
hasAnyBusinessModule,
hasBusinessModule,
isActiveHomeChart,
type BusinessModuleId,
} from '../utils/businessModules'
import pageStyles from '../components/PageContent.module.css'
import styles from './StatisticsPage.module.css'
const EMPTY_COUNTS: ViewPeriodCounts = {
today: 0,
week: 0,
month: 0,
total: 0,
}
const METRICS: {
id: keyof ViewPeriodCounts
labelKey: BusinessMessageKey
}[] = [
{ id: 'today', labelKey: 'statistics.metric.today' },
{ id: 'week', labelKey: 'statistics.metric.week' },
{ id: 'month', labelKey: 'statistics.metric.month' },
{ id: 'total', labelKey: 'statistics.metric.total' },
]
const STAT_ROWS: {
id: ViewSummaryGroupId
titleKey: BusinessMessageKey
moduleId?: BusinessModuleId
anyModuleIds?: BusinessModuleId[]
}[] = [
{ id: 'all', titleKey: 'statistics.row.all' },
{ id: 'home', titleKey: 'statistics.row.home' },
{ id: 'pages', titleKey: 'statistics.row.pages' },
{
id: 'products',
titleKey: 'statistics.row.products',
anyModuleIds: ['products', 'store'],
},
{ id: 'blog', titleKey: 'statistics.row.blog', moduleId: 'blog' },
{ id: 'portfolio', titleKey: 'statistics.row.portfolio', moduleId: 'portfolio' },
{ id: 'videos', titleKey: 'statistics.row.videos', moduleId: 'videos' },
]
function formatCount(value: number) {
return new Intl.NumberFormat('en-US').format(value)
}
export function StatisticsPage() {
const t = useT()
const { locale } = useLocale()
const { enabledModules, homeCharts } = useTenantBranding()
const [rows, setRows] = useState<Record<ViewSummaryGroupId, ViewPeriodCounts> | null>(
null,
)
const [error, setError] = useState('')
const [loading, setLoading] = useState(true)
const visibleCharts = homeCharts.filter(isActiveHomeChart)
const visibleRows = STAT_ROWS.filter((row) => {
if (row.anyModuleIds) {
return hasAnyBusinessModule(enabledModules, row.anyModuleIds)
}
return !row.moduleId || hasBusinessModule(enabledModules, row.moduleId)
})
useEffect(() => {
const controller = new AbortController()
async function load() {
setLoading(true)
setError('')
try {
const data = await getViewSummary(controller.signal)
setRows(data.rows)
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') return
setError(err instanceof ApiError ? err.message : t('statistics.errorLoad'))
} finally {
if (!controller.signal.aborted) setLoading(false)
}
}
void load()
return () => controller.abort()
}, [t])
return (
<main className={pageStyles.content}>
<Breadcrumbs
items={[
{ label: t('nav.home'), href: '/' },
{ label: t('title.statistics') },
]}
/>
<div className={pageStyles.pageHeader}>
<div>
<PageTitle en="STATISTICS">{t('title.statistics')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('statistics.page.subtitle')}</p>
</div>
</div>
{visibleCharts.length > 0 ? (
<div className={pageStyles.grid12}>
{visibleCharts.map((chartId, index) => (
<div key={`${chartId}-${index}`} className={pageStyles.col6}>
<HomeChartSlot chartId={chartId} />
</div>
))}
</div>
) : null}
{error ? (
<div className={styles.error} role="alert">
{error}
</div>
) : null}
{visibleRows.map((row) => {
const counts = rows?.[row.id] ?? EMPTY_COUNTS
return (
<article key={row.id} className={styles.row}>
<div className={styles.rowName}>
<h3 className={styles.rowTitle}>{t(row.titleKey)}</h3>
{locale === 'fa' ? (
<span className={styles.rowTitleEn} lang="en" dir="ltr">
{translate('en', row.titleKey)}
</span>
) : null}
</div>
<div className={styles.chips}>
{METRICS.map((metric) => (
<div key={metric.id} className={styles.chip}>
<strong className={styles.chipValue} lang="en" dir="ltr">
{loading || !rows ? '—' : formatCount(counts[metric.id])}
</strong>
<span className={styles.chipLabel}>{t(metric.labelKey)}</span>
</div>
))}
</div>
</article>
)
})}
</main>
)
}