feat: hide dashboard sections for modules the user cannot access
All checks were successful
Deploy / deploy (push) Successful in 11s

Only fetch data and render stat cards, lists, and widgets for modules
the user has enabled (marketing, projects, finance).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-02-23 15:58:48 +03:00
parent 52d69ee02d
commit 01fdb93efd

View File

@@ -3,6 +3,7 @@ import { Link, useNavigate } from 'react-router-dom'
import { format, isAfter, isBefore, addDays } from 'date-fns' import { format, isAfter, isBefore, addDays } from 'date-fns'
import { FileText, Megaphone, AlertTriangle, ArrowRight, Clock, Wallet, TrendingUp, TrendingDown, DollarSign, Landmark, CheckSquare, FolderKanban } from 'lucide-react' import { FileText, Megaphone, AlertTriangle, ArrowRight, Clock, Wallet, TrendingUp, TrendingDown, DollarSign, Landmark, CheckSquare, FolderKanban } from 'lucide-react'
import { AppContext } from '../App' import { AppContext } from '../App'
import { useAuth } from '../contexts/AuthContext'
import { useLanguage } from '../i18n/LanguageContext' import { useLanguage } from '../i18n/LanguageContext'
import { api, PRIORITY_CONFIG } from '../utils/api' import { api, PRIORITY_CONFIG } from '../utils/api'
import StatCard from '../components/StatCard' import StatCard from '../components/StatCard'
@@ -264,6 +265,7 @@ export default function Dashboard() {
const { t, currencySymbol } = useLanguage() const { t, currencySymbol } = useLanguage()
const navigate = useNavigate() const navigate = useNavigate()
const { currentUser, teamMembers } = useContext(AppContext) const { currentUser, teamMembers } = useContext(AppContext)
const { hasModule } = useAuth()
const [posts, setPosts] = useState([]) const [posts, setPosts] = useState([])
const [campaigns, setCampaigns] = useState([]) const [campaigns, setCampaigns] = useState([])
const [tasks, setTasks] = useState([]) const [tasks, setTasks] = useState([])
@@ -282,18 +284,30 @@ export default function Dashboard() {
const loadData = async () => { const loadData = async () => {
try { try {
const [postsRes, campaignsRes, tasksRes, financeRes, projectsRes] = await Promise.allSettled([ const fetches = []
api.get('/posts?limit=50&sort=-createdAt'), // Only fetch data for modules the user has access to
api.get('/campaigns'), if (hasModule('marketing')) {
api.get('/tasks'), fetches.push(api.get('/posts?limit=50&sort=-createdAt').then(r => ({ key: 'posts', data: r.data || r || [] })))
api.get('/finance/summary'), fetches.push(api.get('/campaigns').then(r => ({ key: 'campaigns', data: r.data || r || [] })))
api.get('/projects'), }
]) if (hasModule('projects')) {
setPosts(postsRes.status === 'fulfilled' ? (postsRes.value.data || postsRes.value || []) : []) fetches.push(api.get('/tasks').then(r => ({ key: 'tasks', data: r.data || r || [] })))
setCampaigns(campaignsRes.status === 'fulfilled' ? (campaignsRes.value.data || campaignsRes.value || []) : []) fetches.push(api.get('/projects').then(r => ({ key: 'projects', data: r.data || r || [] })))
setTasks(tasksRes.status === 'fulfilled' ? (tasksRes.value.data || tasksRes.value || []) : []) }
setFinance(financeRes.status === 'fulfilled' ? (financeRes.value.data || financeRes.value || null) : null) if (hasModule('finance')) {
setProjects(projectsRes.status === 'fulfilled' ? (projectsRes.value.data || projectsRes.value || []) : []) fetches.push(api.get('/finance/summary').then(r => ({ key: 'finance', data: r.data || r || null })))
}
const results = await Promise.allSettled(fetches)
results.forEach(r => {
if (r.status !== 'fulfilled') return
const { key, data } = r.value
if (key === 'posts') setPosts(data)
else if (key === 'campaigns') setCampaigns(data)
else if (key === 'tasks') setTasks(data)
else if (key === 'projects') setProjects(data)
else if (key === 'finance') setFinance(data)
})
} catch (err) { } catch (err) {
console.error('Dashboard load error:', err) console.error('Dashboard load error:', err)
} finally { } finally {
@@ -339,6 +353,42 @@ export default function Dashboard() {
.sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate)) .sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate))
.slice(0, 8) .slice(0, 8)
const statCards = []
if (hasModule('marketing')) {
statCards.push({
icon: FileText,
label: t('dashboard.totalPosts'),
value: filteredPosts.length || 0,
subtitle: `${filteredPosts.filter(p => p.status === 'published').length} ${t('dashboard.published')}`,
color: 'brand-primary',
})
statCards.push({
icon: Megaphone,
label: t('dashboard.activeCampaigns'),
value: activeCampaigns,
subtitle: `${campaigns.length} ${t('dashboard.total')}`,
color: 'brand-secondary',
})
}
if (hasModule('finance')) {
statCards.push({
icon: Landmark,
label: t('dashboard.budgetRemaining'),
value: `${(finance?.remaining ?? 0).toLocaleString()}`,
subtitle: finance?.totalReceived ? `${(finance.spent || 0).toLocaleString()} ${t('dashboard.spent')} ${t('dashboard.of')} ${finance.totalReceived.toLocaleString()} ${currencySymbol}` : t('dashboard.noBudget'),
color: 'brand-tertiary',
})
}
if (hasModule('projects')) {
statCards.push({
icon: AlertTriangle,
label: t('dashboard.overdueTasks'),
value: overdueTasks,
subtitle: overdueTasks > 0 ? t('dashboard.needsAttention') : t('dashboard.allOnTrack'),
color: 'brand-quaternary',
})
}
if (loading) { if (loading) {
return <SkeletonDashboard /> return <SkeletonDashboard />
} }
@@ -363,54 +413,39 @@ export default function Dashboard() {
</div> </div>
{/* Stats */} {/* Stats */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 stagger-children"> {statCards.length > 0 && (
<StatCard <div className={`grid grid-cols-1 sm:grid-cols-2 ${statCards.length >= 4 ? 'lg:grid-cols-4' : statCards.length === 3 ? 'lg:grid-cols-3' : 'lg:grid-cols-2'} gap-4 stagger-children`}>
icon={FileText} {statCards.map((card, i) => (
label={t('dashboard.totalPosts')} <StatCard key={i} {...card} />
value={filteredPosts.length || 0} ))}
subtitle={`${filteredPosts.filter(p => p.status === 'published').length} ${t('dashboard.published')}`}
color="brand-primary"
/>
<StatCard
icon={Megaphone}
label={t('dashboard.activeCampaigns')}
value={activeCampaigns}
subtitle={`${campaigns.length} ${t('dashboard.total')}`}
color="brand-secondary"
/>
<StatCard
icon={Landmark}
label={t('dashboard.budgetRemaining')}
value={`${(finance?.remaining ?? 0).toLocaleString()}`}
subtitle={finance?.totalReceived ? `${(finance.spent || 0).toLocaleString()} ${t('dashboard.spent')} ${t('dashboard.of')} ${finance.totalReceived.toLocaleString()} ${currencySymbol}` : t('dashboard.noBudget')}
color="brand-tertiary"
/>
<StatCard
icon={AlertTriangle}
label={t('dashboard.overdueTasks')}
value={overdueTasks}
subtitle={overdueTasks > 0 ? t('dashboard.needsAttention') : t('dashboard.allOnTrack')}
color="brand-quaternary"
/>
</div> </div>
)}
{/* My Tasks + Project Progress */} {/* My Tasks + Project Progress */}
{hasModule('projects') && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<MyTasksList tasks={filteredTasks} currentUserId={currentUser?.id || currentUser?._id} navigate={navigate} t={t} /> <MyTasksList tasks={filteredTasks} currentUserId={currentUser?.id || currentUser?._id} navigate={navigate} t={t} />
<ProjectProgress projects={projects} tasks={tasks} t={t} /> <ProjectProgress projects={projects} tasks={tasks} t={t} />
</div> </div>
)}
{/* Budget + Active Campaigns */} {/* Budget + Active Campaigns */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> {(hasModule('finance') || hasModule('marketing')) && (
<FinanceMini finance={finance} /> <div className={`grid grid-cols-1 ${hasModule('finance') && hasModule('marketing') ? 'lg:grid-cols-3' : ''} gap-6`}>
<div className="lg:col-span-2"> {hasModule('finance') && <FinanceMini finance={finance} />}
{hasModule('marketing') && (
<div className={hasModule('finance') ? 'lg:col-span-2' : ''}>
<ActiveCampaignsList campaigns={campaigns} finance={finance} /> <ActiveCampaignsList campaigns={campaigns} finance={finance} />
</div> </div>
)}
</div> </div>
)}
{/* Recent Posts + Upcoming Deadlines */} {/* Recent Posts + Upcoming Deadlines */}
{(hasModule('marketing') || hasModule('projects')) && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Recent Posts */} {/* Recent Posts */}
{hasModule('marketing') && (
<div className="section-card"> <div className="section-card">
<div className="section-card-header flex items-center justify-between"> <div className="section-card-header flex items-center justify-between">
<h3 className="font-semibold text-text-primary">{t('dashboard.recentPosts')}</h3> <h3 className="font-semibold text-text-primary">{t('dashboard.recentPosts')}</h3>
@@ -442,8 +477,10 @@ export default function Dashboard() {
)} )}
</div> </div>
</div> </div>
)}
{/* Upcoming Deadlines */} {/* Upcoming Deadlines */}
{hasModule('projects') && (
<div className="section-card"> <div className="section-card">
<div className="section-card-header flex items-center justify-between"> <div className="section-card-header flex items-center justify-between">
<h3 className="font-semibold text-text-primary">{t('dashboard.upcomingDeadlines')}</h3> <h3 className="font-semibold text-text-primary">{t('dashboard.upcomingDeadlines')}</h3>
@@ -477,7 +514,9 @@ export default function Dashboard() {
)} )}
</div> </div>
</div> </div>
)}
</div> </div>
)}
</div> </div>
) )
} }