Add business modules column and selectable home charts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-08 10:40:58 +03:30
co-authored by Cursor
parent a48a24e6f2
commit 0a2358b0c2
15 changed files with 788 additions and 137 deletions
@@ -0,0 +1,88 @@
import { useCallback } from 'react'
import { DailyActivityChart } from './DailyActivityChart'
import { ProductActivityChart } from './ProductActivityChart'
import {
getCustomersDailyActivity,
getOrdersDailyActivity,
} from '../services/dailyActivityService'
import type { HomeChartId } from '../utils/businessModules'
import { useT } from '../i18n/useT'
import styles from './DailyActivityChart.module.css'
interface HomeChartSlotProps {
chartId: HomeChartId
}
function HomeChartPlaceholder({ chartId }: { chartId: HomeChartId }) {
const t = useT()
const titleKey =
chartId === 'blog_views_30d'
? 'home.chart.blogViews.title'
: 'home.chart.placeholder.title'
const subtitleKey =
chartId === 'blog_views_30d'
? 'home.chart.blogViews.subtitle'
: 'home.chart.placeholder.subtitle'
return (
<section className={styles.card} aria-label={t(titleKey)}>
<div className={styles.header}>
<div>
<h3 className={styles.title}>{t(titleKey)}</h3>
<p className={styles.subtitle}>{t(subtitleKey)}</p>
</div>
</div>
<p className={styles.status}>{t('home.chart.placeholder.comingSoon')}</p>
</section>
)
}
export function HomeChartSlot({ chartId }: HomeChartSlotProps) {
const loadOrders = useCallback(
(signal: AbortSignal) => getOrdersDailyActivity(30, signal),
[],
)
const loadCustomersYear = useCallback(
(signal: AbortSignal) => getCustomersDailyActivity(365, signal),
[],
)
switch (chartId) {
case 'none':
return null
case 'orders_30d':
return (
<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={loadOrders}
/>
)
case 'customers_joined_1y':
return (
<DailyActivityChart
titleKey="home.chart.customersJoined.title"
subtitleKey="home.chart.customersJoined.subtitle"
primaryLegendKey="home.chart.customersJoined.legend"
secondaryLegendKey="home.chart.customersJoined.activeLegend"
loadingKey="home.chart.customersJoined.loading"
errorKey="home.chart.customersJoined.error"
primaryBarTitleKey="home.chart.customersJoined.bar"
secondaryBarTitleKey="home.chart.customersJoined.activeBar"
load={loadCustomersYear}
/>
)
case 'products_added_1y':
return <ProductActivityChart />
case 'blog_views_30d':
return <HomeChartPlaceholder chartId={chartId} />
default:
return null
}
}
@@ -87,6 +87,10 @@
grid-column: span 6;
}
.col12 {
grid-column: span 12;
}
@media (max-width: 1536px) {
.gridHome {
grid-template-columns: repeat(4, 1fr);
+106 -90
View File
@@ -26,6 +26,11 @@ import {
} from '../lib/businessContext'
import { isAbortError } from '../lib/api'
import { getBusinessProfile } from '../services/businessProfileService'
import { useTenantBranding } from '../context/TenantBrandingContext'
import {
hasBusinessModule,
type BusinessModuleId,
} from '../utils/businessModules'
import { Tooltip } from './Tooltip'
import styles from './Sidebar.module.css'
@@ -41,6 +46,7 @@ interface NavGroup {
labelKey: BusinessMessageKey
basePath: string
children: NavChild[]
moduleId?: BusinessModuleId
}
interface NavLinkItem {
@@ -49,6 +55,7 @@ interface NavLinkItem {
icon: LucideIcon
labelKey: BusinessMessageKey
to: string
moduleId?: BusinessModuleId
}
type NavItem = NavLinkItem | NavGroup
@@ -62,6 +69,7 @@ export function Sidebar() {
const navigate = useNavigate()
const { user, logout } = useAuth()
const { locale } = useLocale()
const { enabledModules } = useTenantBranding()
const t = useT()
const [openGroups, setOpenGroups] = useState<Record<string, boolean>>({})
const [brandLogoUrl, setBrandLogoUrl] = useState<string | null>(null)
@@ -79,96 +87,104 @@ export function Sidebar() {
}, [locale, nameEn, nameFa, fallbackBusinessName])
const navItems = useMemo<NavItem[]>(
() => [
{ type: 'link', id: 'home', icon: Home, labelKey: 'nav.home', to: '/' },
{
type: 'link',
id: 'business-profile',
icon: Building2,
labelKey: 'nav.businessProfile',
to: '/business-profile',
},
{
type: 'group',
id: 'products',
icon: ShoppingBag,
labelKey: 'nav.products',
basePath: '/products',
children: [
{ labelKey: 'nav.products.overview', to: '/products' },
{ labelKey: 'nav.products.list', to: '/products/list' },
{ labelKey: 'nav.products.new', to: '/products/new' },
{ labelKey: 'nav.products.categories', to: '/products/categories' },
{ labelKey: 'nav.products.brands', to: '/products/brands' },
{ labelKey: 'nav.products.settings', to: '/products/settings' },
],
},
{
type: 'group',
id: 'store',
icon: Store,
labelKey: 'nav.store',
basePath: '/store',
children: [
{ labelKey: 'nav.store.overview', to: '/store' },
{ labelKey: 'nav.store.items', to: '/store/items' },
{ labelKey: 'nav.store.orders', to: '/store/orders' },
{ labelKey: 'nav.store.shipping', to: '/store/shipping' },
{ labelKey: 'nav.store.cards', to: '/store/cards' },
{ labelKey: 'nav.store.settings', to: '/store/settings' },
],
},
{ type: 'link', id: 'customers', icon: Users, labelKey: 'nav.customers', to: '/customers' },
{
type: 'group',
id: 'blog',
icon: FileText,
labelKey: 'nav.blog',
basePath: '/blog',
children: [
{ labelKey: 'nav.blog.overview', to: '/blog' },
{ labelKey: 'nav.blog.list', to: '/blog/list' },
{ labelKey: 'nav.blog.new', to: '/blog/new' },
{ labelKey: 'nav.blog.categories', to: '/blog/categories' },
{ labelKey: 'nav.blog.settings', to: '/blog/settings' },
],
},
{
type: 'group',
id: 'portfolios',
icon: Briefcase,
labelKey: 'nav.portfolios',
basePath: '/portfolios',
children: [
{ labelKey: 'nav.portfolios.overview', to: '/portfolios' },
{ labelKey: 'nav.portfolios.list', to: '/portfolios/list' },
{ labelKey: 'nav.portfolios.new', to: '/portfolios/new' },
{ labelKey: 'nav.portfolios.categories', to: '/portfolios/categories' },
{ labelKey: 'nav.portfolios.settings', to: '/portfolios/settings' },
],
},
{
type: 'group',
id: 'website',
icon: Globe,
labelKey: 'nav.website',
basePath: '/website',
children: [
{ labelKey: 'nav.website.overview', to: '/website' },
{ labelKey: 'nav.website.sliders', to: '/website/sliders' },
{ labelKey: 'nav.website.specialCategories', to: '/website/special-categories' },
{ labelKey: 'nav.website.specialBrands', to: '/website/special-brands' },
{ labelKey: 'nav.website.specialItems', to: '/website/special-items' },
{ labelKey: 'nav.website.contact', to: '/website/contact' },
{ labelKey: 'nav.website.subscriptions', to: '/website/subscriptions' },
{ labelKey: 'nav.website.faq', to: '/website/faq' },
{ labelKey: 'nav.website.badges', to: '/website/badges' },
{ labelKey: 'nav.website.ePayment', to: '/website/e-payment' },
],
},
{ type: 'link', id: 'settings', icon: Settings, labelKey: 'nav.settings', to: '/settings' },
],
[],
() =>
[
{ type: 'link', id: 'home', icon: Home, labelKey: 'nav.home', to: '/' },
{
type: 'link',
id: 'business-profile',
icon: Building2,
labelKey: 'nav.businessProfile',
to: '/business-profile',
},
{
type: 'group',
id: 'products',
icon: ShoppingBag,
labelKey: 'nav.products',
basePath: '/products',
moduleId: 'products',
children: [
{ labelKey: 'nav.products.overview', to: '/products' },
{ labelKey: 'nav.products.list', to: '/products/list' },
{ labelKey: 'nav.products.new', to: '/products/new' },
{ labelKey: 'nav.products.categories', to: '/products/categories' },
{ labelKey: 'nav.products.brands', to: '/products/brands' },
{ labelKey: 'nav.products.settings', to: '/products/settings' },
],
},
{
type: 'group',
id: 'store',
icon: Store,
labelKey: 'nav.store',
basePath: '/store',
moduleId: 'store',
children: [
{ labelKey: 'nav.store.overview', to: '/store' },
{ labelKey: 'nav.store.items', to: '/store/items' },
{ labelKey: 'nav.store.orders', to: '/store/orders' },
{ labelKey: 'nav.store.shipping', to: '/store/shipping' },
{ labelKey: 'nav.store.cards', to: '/store/cards' },
{ labelKey: 'nav.store.settings', to: '/store/settings' },
],
},
{ type: 'link', id: 'customers', icon: Users, labelKey: 'nav.customers', to: '/customers' },
{
type: 'group',
id: 'blog',
icon: FileText,
labelKey: 'nav.blog',
basePath: '/blog',
moduleId: 'blog',
children: [
{ labelKey: 'nav.blog.overview', to: '/blog' },
{ labelKey: 'nav.blog.list', to: '/blog/list' },
{ labelKey: 'nav.blog.new', to: '/blog/new' },
{ labelKey: 'nav.blog.categories', to: '/blog/categories' },
{ labelKey: 'nav.blog.settings', to: '/blog/settings' },
],
},
{
type: 'group',
id: 'portfolios',
icon: Briefcase,
labelKey: 'nav.portfolios',
basePath: '/portfolios',
moduleId: 'portfolio',
children: [
{ labelKey: 'nav.portfolios.overview', to: '/portfolios' },
{ labelKey: 'nav.portfolios.list', to: '/portfolios/list' },
{ labelKey: 'nav.portfolios.new', to: '/portfolios/new' },
{ labelKey: 'nav.portfolios.categories', to: '/portfolios/categories' },
{ labelKey: 'nav.portfolios.settings', to: '/portfolios/settings' },
],
},
{
type: 'group',
id: 'website',
icon: Globe,
labelKey: 'nav.website',
basePath: '/website',
children: [
{ labelKey: 'nav.website.overview', to: '/website' },
{ labelKey: 'nav.website.sliders', to: '/website/sliders' },
{ labelKey: 'nav.website.specialCategories', to: '/website/special-categories' },
{ labelKey: 'nav.website.specialBrands', to: '/website/special-brands' },
{ labelKey: 'nav.website.specialItems', to: '/website/special-items' },
{ labelKey: 'nav.website.contact', to: '/website/contact' },
{ labelKey: 'nav.website.subscriptions', to: '/website/subscriptions' },
{ labelKey: 'nav.website.faq', to: '/website/faq' },
{ labelKey: 'nav.website.badges', to: '/website/badges' },
{ labelKey: 'nav.website.ePayment', to: '/website/e-payment' },
],
},
{ type: 'link', id: 'settings', icon: Settings, labelKey: 'nav.settings', to: '/settings' },
].filter(
(item) =>
!item.moduleId || hasBusinessModule(enabledModules, item.moduleId),
),
[enabledModules],
)
useEffect(() => {