diff --git a/client/src/pages/Dashboard.jsx b/client/src/pages/Dashboard.jsx index 03b320d..4c7fb91 100644 --- a/client/src/pages/Dashboard.jsx +++ b/client/src/pages/Dashboard.jsx @@ -3,6 +3,7 @@ import { Link, useNavigate } from 'react-router-dom' 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 { AppContext } from '../App' +import { useAuth } from '../contexts/AuthContext' import { useLanguage } from '../i18n/LanguageContext' import { api, PRIORITY_CONFIG } from '../utils/api' import StatCard from '../components/StatCard' @@ -264,6 +265,7 @@ export default function Dashboard() { const { t, currencySymbol } = useLanguage() const navigate = useNavigate() const { currentUser, teamMembers } = useContext(AppContext) + const { hasModule } = useAuth() const [posts, setPosts] = useState([]) const [campaigns, setCampaigns] = useState([]) const [tasks, setTasks] = useState([]) @@ -282,18 +284,30 @@ export default function Dashboard() { const loadData = async () => { try { - const [postsRes, campaignsRes, tasksRes, financeRes, projectsRes] = await Promise.allSettled([ - api.get('/posts?limit=50&sort=-createdAt'), - api.get('/campaigns'), - api.get('/tasks'), - api.get('/finance/summary'), - api.get('/projects'), - ]) - setPosts(postsRes.status === 'fulfilled' ? (postsRes.value.data || postsRes.value || []) : []) - setCampaigns(campaignsRes.status === 'fulfilled' ? (campaignsRes.value.data || campaignsRes.value || []) : []) - setTasks(tasksRes.status === 'fulfilled' ? (tasksRes.value.data || tasksRes.value || []) : []) - setFinance(financeRes.status === 'fulfilled' ? (financeRes.value.data || financeRes.value || null) : null) - setProjects(projectsRes.status === 'fulfilled' ? (projectsRes.value.data || projectsRes.value || []) : []) + const fetches = [] + // Only fetch data for modules the user has access to + if (hasModule('marketing')) { + fetches.push(api.get('/posts?limit=50&sort=-createdAt').then(r => ({ key: 'posts', data: r.data || r || [] }))) + fetches.push(api.get('/campaigns').then(r => ({ key: 'campaigns', data: r.data || r || [] }))) + } + if (hasModule('projects')) { + fetches.push(api.get('/tasks').then(r => ({ key: 'tasks', data: r.data || r || [] }))) + fetches.push(api.get('/projects').then(r => ({ key: 'projects', data: r.data || r || [] }))) + } + if (hasModule('finance')) { + 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) { console.error('Dashboard load error:', err) } finally { @@ -339,6 +353,42 @@ export default function Dashboard() { .sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate)) .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) { return } @@ -363,121 +413,110 @@ export default function Dashboard() { {/* Stats */} -
- p.status === 'published').length} ${t('dashboard.published')}`} - color="brand-primary" - /> - - - 0 ? t('dashboard.needsAttention') : t('dashboard.allOnTrack')} - color="brand-quaternary" - /> -
+ {statCards.length > 0 && ( +
= 4 ? 'lg:grid-cols-4' : statCards.length === 3 ? 'lg:grid-cols-3' : 'lg:grid-cols-2'} gap-4 stagger-children`}> + {statCards.map((card, i) => ( + + ))} +
+ )} {/* My Tasks + Project Progress */} -
- - -
+ {hasModule('projects') && ( +
+ + +
+ )} {/* Budget + Active Campaigns */} -
- -
- + {(hasModule('finance') || hasModule('marketing')) && ( +
+ {hasModule('finance') && } + {hasModule('marketing') && ( +
+ +
+ )}
-
+ )} {/* Recent Posts + Upcoming Deadlines */} -
- {/* Recent Posts */} -
-
-

{t('dashboard.recentPosts')}

- - {t('dashboard.viewAll')} - -
-
- {filteredPosts.length === 0 ? ( -
- {t('dashboard.noPostsYet')} + {(hasModule('marketing') || hasModule('projects')) && ( +
+ {/* Recent Posts */} + {hasModule('marketing') && ( +
+
+

{t('dashboard.recentPosts')}

+ + {t('dashboard.viewAll')} +
- ) : ( - filteredPosts.slice(0, 8).map((post) => ( -
navigate('/posts')} - className="flex items-center gap-3 px-5 py-3 hover:bg-surface-secondary transition-colors cursor-pointer" - > -
-

{post.title}

-
- {post.brand && } +
+ {filteredPosts.length === 0 ? ( +
+ {t('dashboard.noPostsYet')} +
+ ) : ( + filteredPosts.slice(0, 8).map((post) => ( +
navigate('/posts')} + className="flex items-center gap-3 px-5 py-3 hover:bg-surface-secondary transition-colors cursor-pointer" + > +
+

{post.title}

+
+ {post.brand && } +
+
+
-
- -
- )) - )} -
-
- - {/* Upcoming Deadlines */} -
-
-

{t('dashboard.upcomingDeadlines')}

- - {t('dashboard.viewAll')} - -
-
- {upcomingDeadlines.length === 0 ? ( -
- {t('dashboard.noUpcomingDeadlines')} + )) + )}
- ) : ( - upcomingDeadlines.map((task) => ( -
navigate('/tasks')} - className="flex items-center gap-3 px-5 py-3 hover:bg-surface-secondary transition-colors cursor-pointer" - > -
-
-

{task.title}

- +
+ )} + + {/* Upcoming Deadlines */} + {hasModule('projects') && ( +
+
+

{t('dashboard.upcomingDeadlines')}

+ + {t('dashboard.viewAll')} + +
+
+ {upcomingDeadlines.length === 0 ? ( +
+ {t('dashboard.noUpcomingDeadlines')}
-
- - {format(new Date(task.dueDate), 'MMM d')} -
-
- )) - )} -
+ ) : ( + upcomingDeadlines.map((task) => ( +
navigate('/tasks')} + className="flex items-center gap-3 px-5 py-3 hover:bg-surface-secondary transition-colors cursor-pointer" + > +
+
+

{task.title}

+ +
+
+ + {format(new Date(task.dueDate), 'MMM d')} +
+
+ )) + )} +
+
+ )}
-
+ )}
) }