mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
Add FA/EN locale, home activity charts, and theme-aware polish.
Ship shared LocaleProvider, business/customer i18n, branding defaultLocale, curated home tiles with dual 30-day charts, and chart/page aura tokens; refresh PROJECT_CONTEXT. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
f5b2193ba1
commit
66004a0fba
@@ -1,105 +1,207 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { CalendarDays } from 'lucide-react'
|
||||
import {
|
||||
ShoppingBag,
|
||||
Store,
|
||||
Users,
|
||||
Settings,
|
||||
FileText,
|
||||
Briefcase,
|
||||
Globe,
|
||||
} from 'lucide-react'
|
||||
import { useLocale } from '@meshkee/dashboard-ui'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { SectionCard } from '../components/SectionCard'
|
||||
import { DailyActivityChart } from '../components/DailyActivityChart'
|
||||
import { useT } from '../i18n/useT'
|
||||
import type { BusinessMessageKey } from '../i18n/messages'
|
||||
import { listProducts } from '../services/productService'
|
||||
import { listStoreItems } from '../services/storeItemService'
|
||||
import { listCustomers } from '../services/customerService'
|
||||
import { listBlogs } from '../services/blogService'
|
||||
import { listPortfolios } from '../services/portfolioService'
|
||||
import {
|
||||
getCustomersDailyActivity,
|
||||
getOrdersDailyActivity,
|
||||
} from '../services/dailyActivityService'
|
||||
import styles from '../components/PageContent.module.css'
|
||||
|
||||
const sections = [
|
||||
type CountKey = 'products' | 'store' | 'customers' | 'blog' | 'portfolios'
|
||||
|
||||
const sections: {
|
||||
icon: typeof ShoppingBag
|
||||
titleKey: BusinessMessageKey
|
||||
descKey: BusinessMessageKey
|
||||
linkKey: BusinessMessageKey
|
||||
countLabelKey?: BusinessMessageKey
|
||||
href: string
|
||||
countKey?: CountKey
|
||||
}[] = [
|
||||
{
|
||||
icon: ShoppingBag,
|
||||
title: 'Products',
|
||||
description: 'Manage your products, inventory and categories.',
|
||||
linkText: 'View products',
|
||||
titleKey: 'home.card.products.title',
|
||||
descKey: 'home.card.products.desc',
|
||||
linkKey: 'home.card.products.link',
|
||||
countLabelKey: 'home.card.products.count',
|
||||
href: '/products',
|
||||
countKey: 'products',
|
||||
},
|
||||
{
|
||||
icon: Store,
|
||||
title: 'Store',
|
||||
description: 'Manage your store settings, pages and themes.',
|
||||
linkText: 'View store',
|
||||
titleKey: 'home.card.store.title',
|
||||
descKey: 'home.card.store.desc',
|
||||
linkKey: 'home.card.store.link',
|
||||
countLabelKey: 'home.card.store.count',
|
||||
href: '/store',
|
||||
countKey: 'store',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: 'Customers',
|
||||
description: 'View and manage your customers and their activity.',
|
||||
linkText: 'View customers',
|
||||
titleKey: 'home.card.customers.title',
|
||||
descKey: 'home.card.customers.desc',
|
||||
linkKey: 'home.card.customers.link',
|
||||
countLabelKey: 'home.card.customers.count',
|
||||
href: '/customers',
|
||||
},
|
||||
{
|
||||
icon: Settings,
|
||||
title: 'Settings',
|
||||
description: 'Configure your store preferences and system settings.',
|
||||
linkText: 'View settings',
|
||||
href: '/settings',
|
||||
countKey: 'customers',
|
||||
},
|
||||
{
|
||||
icon: FileText,
|
||||
title: 'Blog',
|
||||
description: 'Create and manage blog posts and categories.',
|
||||
linkText: 'View blog',
|
||||
titleKey: 'home.card.blog.title',
|
||||
descKey: 'home.card.blog.desc',
|
||||
linkKey: 'home.card.blog.link',
|
||||
countLabelKey: 'home.card.blog.count',
|
||||
href: '/blog',
|
||||
countKey: 'blog',
|
||||
},
|
||||
{
|
||||
icon: Briefcase,
|
||||
title: 'Portfolios',
|
||||
description: 'Manage your portfolio items and showcase projects.',
|
||||
linkText: 'View portfolios',
|
||||
titleKey: 'home.card.portfolios.title',
|
||||
descKey: 'home.card.portfolios.desc',
|
||||
linkKey: 'home.card.portfolios.link',
|
||||
countLabelKey: 'home.card.portfolios.count',
|
||||
href: '/portfolios',
|
||||
countKey: 'portfolios',
|
||||
},
|
||||
{
|
||||
icon: Globe,
|
||||
title: 'Website',
|
||||
description: 'Manage contact forms, FAQ, badges, subscriptions, and e-payment.',
|
||||
linkText: 'View website',
|
||||
titleKey: 'home.card.website.title',
|
||||
descKey: 'home.card.website.desc',
|
||||
linkKey: 'home.card.website.link',
|
||||
href: '/website',
|
||||
},
|
||||
]
|
||||
|
||||
function getFormattedDate() {
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
type SectionCounts = Partial<Record<CountKey, number>>
|
||||
|
||||
async function loadSectionCounts(signal: AbortSignal): Promise<SectionCounts> {
|
||||
const [products, store, customers, blog, portfolios] = await Promise.all([
|
||||
listProducts(1, 1, signal).then((r) => r.total).catch(() => null),
|
||||
listStoreItems(1, 1, signal).then((r) => r.total).catch(() => null),
|
||||
listCustomers({ page: 1, pageSize: 1 }, signal).then((r) => r.total).catch(() => null),
|
||||
listBlogs(1, 1, signal).then((r) => r.total).catch(() => null),
|
||||
listPortfolios(1, 1, signal).then((r) => r.total).catch(() => null),
|
||||
])
|
||||
|
||||
const counts: SectionCounts = {}
|
||||
if (products !== null) counts.products = products
|
||||
if (store !== null) counts.store = store
|
||||
if (customers !== null) counts.customers = customers
|
||||
if (blog !== null) counts.blog = blog
|
||||
if (portfolios !== null) counts.portfolios = portfolios
|
||||
return counts
|
||||
}
|
||||
|
||||
export function HomePage() {
|
||||
const { user } = useAuth()
|
||||
const { locale } = useLocale()
|
||||
const t = useT()
|
||||
const [counts, setCounts] = useState<SectionCounts>({})
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController()
|
||||
void loadSectionCounts(controller.signal).then((next) => {
|
||||
if (!controller.signal.aborted) setCounts(next)
|
||||
})
|
||||
return () => controller.abort()
|
||||
}, [])
|
||||
|
||||
const loadOrdersActivity = useCallback(
|
||||
(signal: AbortSignal) => getOrdersDailyActivity(30, signal),
|
||||
[],
|
||||
)
|
||||
const loadCustomersActivity = useCallback(
|
||||
(signal: AbortSignal) => getCustomersDailyActivity(30, signal),
|
||||
[],
|
||||
)
|
||||
|
||||
const firstName =
|
||||
(locale === 'en'
|
||||
? user?.firstNameEn?.trim() || user?.firstName
|
||||
: user?.firstName?.trim() || user?.firstNameEn) || t('home.welcomeFallback')
|
||||
|
||||
const formattedDate = new Intl.DateTimeFormat(locale === 'fa' ? 'fa-IR' : 'en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
weekday: 'long',
|
||||
}).format(new Date())
|
||||
}
|
||||
|
||||
export function HomePage() {
|
||||
const { user } = useAuth()
|
||||
const firstName = user?.firstName || 'there'
|
||||
|
||||
return (
|
||||
<main className={styles.content}>
|
||||
<div className={styles.pageHeader}>
|
||||
<div>
|
||||
<h2 className={styles.pageTitle}>
|
||||
Welcome back, {firstName}! <span aria-hidden="true">👋</span>
|
||||
</h2>
|
||||
<p className={styles.pageSubtitle}>
|
||||
Here's what's happening with your store today.
|
||||
</p>
|
||||
<h2 className={styles.pageTitle}>{t('home.welcome', { name: firstName })}</h2>
|
||||
<p className={styles.pageSubtitle}>{t('home.subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.dateBadge}>
|
||||
<CalendarDays size={16} />
|
||||
<span>{getFormattedDate()}</span>
|
||||
<span>{formattedDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.gridHome}>
|
||||
{sections.map((section) => (
|
||||
<SectionCard key={section.title} {...section} />
|
||||
<SectionCard
|
||||
key={section.href}
|
||||
icon={section.icon}
|
||||
title={t(section.titleKey)}
|
||||
description={t(section.descKey)}
|
||||
linkText={t(section.linkKey)}
|
||||
href={section.href}
|
||||
count={section.countKey ? counts[section.countKey] : undefined}
|
||||
countLabel={section.countLabelKey ? t(section.countLabelKey) : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles.grid12}>
|
||||
<div className={styles.col6}>
|
||||
<DailyActivityChart
|
||||
titleKey="home.chart.orders.title"
|
||||
subtitleKey="home.chart.orders.subtitle"
|
||||
primaryLegendKey="home.chart.orders.legend"
|
||||
secondaryLegendKey="home.chart.orders.cartLegend"
|
||||
loadingKey="home.chart.orders.loading"
|
||||
errorKey="home.chart.orders.error"
|
||||
primaryBarTitleKey="home.chart.orders.bar"
|
||||
secondaryBarTitleKey="home.chart.orders.cartBar"
|
||||
load={loadOrdersActivity}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.col6}>
|
||||
<DailyActivityChart
|
||||
titleKey="home.chart.customers.title"
|
||||
subtitleKey="home.chart.customers.subtitle"
|
||||
primaryLegendKey="home.chart.customers.legend"
|
||||
secondaryLegendKey="home.chart.customers.activeLegend"
|
||||
loadingKey="home.chart.customers.loading"
|
||||
errorKey="home.chart.customers.error"
|
||||
primaryBarTitleKey="home.chart.customers.bar"
|
||||
secondaryBarTitleKey="home.chart.customers.activeBar"
|
||||
load={loadCustomersActivity}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user