mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
Ship invoice issue/list/templates under Finance, fix tenant theme reset and owner role badge, and show customers-joined charts by Persian months. Co-authored-by: Cursor <cursoragent@cursor.com>
195 lines
6.0 KiB
TypeScript
195 lines
6.0 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { CalendarDays } from 'lucide-react'
|
|
import {
|
|
ShoppingBag,
|
|
Store,
|
|
Users,
|
|
FileText,
|
|
Briefcase,
|
|
Wallet,
|
|
} from 'lucide-react'
|
|
import { useLocale } from '@meshkee/dashboard-ui'
|
|
import { useAuth } from '../context/AuthContext'
|
|
import { SectionCard } from '../components/SectionCard'
|
|
import { HomeChartSlot } from '../components/HomeChartSlot'
|
|
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 { useTenantBranding } from '../context/TenantBrandingContext'
|
|
import {
|
|
hasBusinessModule,
|
|
isActiveHomeChart,
|
|
type BusinessModuleId,
|
|
} from '../utils/businessModules'
|
|
import styles from '../components/PageContent.module.css'
|
|
|
|
type CountKey = 'products' | 'store' | 'customers' | 'blog' | 'portfolios'
|
|
|
|
const sections: {
|
|
icon: typeof ShoppingBag
|
|
titleKey: BusinessMessageKey
|
|
descKey: BusinessMessageKey
|
|
linkKey: BusinessMessageKey
|
|
countLabelKey?: BusinessMessageKey
|
|
href: string
|
|
countKey?: CountKey
|
|
/** When set, card is shown only if this optional module is enabled. */
|
|
moduleId?: BusinessModuleId
|
|
}[] = [
|
|
{
|
|
icon: ShoppingBag,
|
|
titleKey: 'home.card.products.title',
|
|
descKey: 'home.card.products.desc',
|
|
linkKey: 'home.card.products.link',
|
|
countLabelKey: 'home.card.products.count',
|
|
href: '/products',
|
|
countKey: 'products',
|
|
moduleId: 'products',
|
|
},
|
|
{
|
|
icon: 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',
|
|
moduleId: 'store',
|
|
},
|
|
{
|
|
icon: Users,
|
|
titleKey: 'home.card.customers.title',
|
|
descKey: 'home.card.customers.desc',
|
|
linkKey: 'home.card.customers.link',
|
|
countLabelKey: 'home.card.customers.count',
|
|
href: '/customers',
|
|
countKey: 'customers',
|
|
},
|
|
{
|
|
icon: Wallet,
|
|
titleKey: 'home.card.finance.title',
|
|
descKey: 'home.card.finance.desc',
|
|
linkKey: 'home.card.finance.link',
|
|
href: '/finance',
|
|
},
|
|
{
|
|
icon: FileText,
|
|
titleKey: 'home.card.blog.title',
|
|
descKey: 'home.card.blog.desc',
|
|
linkKey: 'home.card.blog.link',
|
|
countLabelKey: 'home.card.blog.count',
|
|
href: '/blog',
|
|
countKey: 'blog',
|
|
moduleId: 'blog',
|
|
},
|
|
{
|
|
icon: Briefcase,
|
|
titleKey: 'home.card.portfolios.title',
|
|
descKey: 'home.card.portfolios.desc',
|
|
linkKey: 'home.card.portfolios.link',
|
|
countLabelKey: 'home.card.portfolios.count',
|
|
href: '/portfolios',
|
|
countKey: 'portfolios',
|
|
moduleId: 'portfolio',
|
|
},
|
|
]
|
|
|
|
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 { enabledModules, homeCharts } = useTenantBranding()
|
|
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 visibleSections = sections.filter(
|
|
(section) =>
|
|
!section.moduleId || hasBusinessModule(enabledModules, section.moduleId),
|
|
)
|
|
const visibleCharts = homeCharts.filter(isActiveHomeChart)
|
|
|
|
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())
|
|
|
|
return (
|
|
<main className={styles.content}>
|
|
<div className={styles.pageHeader}>
|
|
<div>
|
|
<h2 className={styles.welcomeTitle}>{t('home.welcome', { name: firstName })}</h2>
|
|
<p className={styles.welcomeSubtitle}>{t('home.subtitle')}</p>
|
|
</div>
|
|
|
|
<div className={styles.dateBadge}>
|
|
<CalendarDays size={16} />
|
|
<span>{formattedDate}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.gridHome}>
|
|
{visibleSections.map((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>
|
|
|
|
{visibleCharts.length > 0 ? (
|
|
<div className={styles.grid12}>
|
|
{visibleCharts.map((chartId, index) => (
|
|
<div key={`${chartId}-${index}`} className={styles.col6}>
|
|
<HomeChartSlot chartId={chartId} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : null}
|
|
</main>
|
|
)
|
|
}
|