Company Manager Dashboard
The Company Manager Dashboard is your central command centre for managing staff, scheduling, and company operations. This guide covers all dashboard features and navigation options.
Accessing the Dashboard
Location: /dashboard
Required Role: COMPANY_MANAGER
// Source: src/app/dashboard/layout.tsx:6-13
<ProtectedRoute requiredRole="COMPANY_MANAGER">
<div className="min-h-screen">
<main className="pb-20 lg:pb-0">
{children}
</main>
<PasswordStatusSync />
</div>
</ProtectedRoute>
Dashboard Overview
Header Section
The dashboard header displays:
- Title: "Company Management"
- Description: "Manage your team, schedules, and operations"
- Portal Switcher: Toggle between Manager and Staff portals
// Source: src/app/dashboard/page.tsx:114-122
<header className="mb-6 md:mb-8">
<h1 className="gradient-text text-2xl md:text-3xl lg:text-4xl font-bold mb-2">
Company Management
</h1>
<p className="text-secondary-text text-sm md:text-base">
Manage your team, schedules, and operations
</p>
<PortalSwitcher className="mt-4" />
</header>
Portal Switcher
Company Managers have access to both the Manager Portal and Staff Portal:
// Source: src/components/navigation/PortalSwitcher.tsx:12-24
export function PortalSwitcher({ className }: { className?: string }) {
const { isCompanyManager } = useAuth()
const router = useRouter()
const pathname = usePathname()
if (!isCompanyManager) return null
const activePortal: PortalType = pathname.startsWith(STAFF_PATH) ? 'staff' : 'manager'
}
| Portal | Path | Description |
|---|---|---|
| Manager Portal | /dashboard | Company management functions |
| Staff Portal | /staff | Personal staff functions (timesheet, leave, schedule) |
Dashboard Cards
The dashboard provides six main navigation cards for quick access to key features.
Card Layout
// Source: src/app/dashboard/page.tsx:64-107
const dashboardCards = [
{
title: 'Staff Management',
description: 'Manage your team members and roles',
href: '/dashboard/staff',
action: 'View Staff',
icon: Users
},
{
title: 'Shift Scheduling',
description: 'Plan rotas and room assignments',
href: '/dashboard/scheduling',
action: 'View Shifts',
icon: CalendarDays
},
{
title: 'Leave Management',
description: 'Review and approve time off',
href: '/dashboard/leave',
action: 'View Requests',
icon: CalendarCheck
},
{
title: 'Time Tracking',
description: 'Monitor entries and corrections',
href: '/dashboard/time-tracking',
action: 'Open Time Tracking',
icon: Clock
},
{
title: 'Analytics',
description: 'Track performance and insights',
href: '/dashboard/analytics',
action: 'View Analytics',
icon: BarChart3
},
{
title: 'Settings',
description: 'Configure company preferences',
href: '/settings',
action: 'View Settings',
icon: Settings
}
]
Navigation Card Reference
| Card | Description | Destination |
|---|---|---|
| Staff Management | Manage team members and roles | /dashboard/staff |
| Shift Scheduling | Plan rotas and room assignments | /dashboard/scheduling |
| Leave Management | Review and approve time off | /dashboard/leave |
| Time Tracking | Monitor entries and corrections | /dashboard/time-tracking |
| Analytics | Track performance and insights | /dashboard/analytics |
| Settings | Configure company preferences | /settings |
Card Design
Each card uses the glassmorphism design with:
- Minimum height of 210px
- Icon display with coral accent colour
- Title and description text
- Action button with 44px minimum height
// Source: src/app/dashboard/page.tsx:128-151
<div className="card-glass-hover min-h-[210px] flex flex-col p-5 md:p-6">
<div className="flex-1">
<div className="flex items-start gap-3 mb-3">
<div className="h-11 w-11 rounded-xl bg-white/10 border border-white/10 flex items-center justify-center text-coral-200">
<Icon className="h-5 w-5" />
</div>
<div>
<h3 className="text-primary-text font-semibold text-base md:text-lg">
{card.title}
</h3>
<p className="text-secondary-text text-sm mt-1">
{card.description}
</p>
</div>
</div>
</div>
<button
onClick={() => router.push(card.href)}
className="form-button secondary w-full min-h-[44px]"
>
{card.action}
</button>
</div>
Quick Overview Statistics
The Quick Overview section displays real-time company statistics fetched from the API.
Statistics Interface
// Source: src/utils/api/companyStats.ts:6-11
export interface CompanyStats {
activeStaff: number
todayShifts: number
pendingRequests: number
weeklyHours: number
}
Statistics Display
| Statistic | Description |
|---|---|
| Active Staff | Number of currently active staff members |
| Today's Shifts | Number of shifts scheduled for today |
| Pending Requests | Leave requests awaiting approval |
| Weekly Hours | Total hours scheduled this week |
Data Fetching
Statistics are fetched on component mount with abort controller support:
// Source: src/app/dashboard/page.tsx:31-55
const fetchStats = useCallback(async (signal?: AbortSignal) => {
try {
setStatsLoading(true)
setStatsError(null)
const companyId = await getCompanyIdForUser()
if (!companyId) {
throw new Error('No company found for user')
}
const companyStats = await getCompanyStats(companyId)
if (!signal?.aborted) {
setStats(companyStats)
}
} catch (error) {
if (!signal?.aborted) {
setStatsError(error instanceof Error ? error.message : 'Failed to load statistics')
}
} finally {
if (!signal?.aborted) {
setStatsLoading(false)
}
}
}, [])
Loading State
While statistics are loading, a skeleton loader displays:
┌─────────────────────────────────────────────────────────────┐
│ Quick Overview │
├─────────────────────────────────────────────────────────────┤
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ ░░░░░░░ │ │ ░░░░░░░ │ │ ░░░░░░░ │ │ ░░░░░░░ │ │
│ │ ░░░░░ │ │ ░░░░░ │ │ ░░░░░ │ │ ░░░░░ │ │
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────┘
Error State
If statistics fail to load, an error message displays:
// Source: src/app/dashboard/page.tsx:174-176
<div className="card-glass-error rounded-xl p-4 md:p-6">
<p className="text-red-300 text-sm md:text-base">⚠️ {statsError}</p>
</div>
My Staff Portal Section
Company Managers also have access to personal staff functions through the "My Staff Portal" section.
Personal Functions
| Function | Path | Description |
|---|---|---|
| My Timesheet | /staff/timesheet | View and submit personal timesheet |
| My Leave | /staff/leave | Request personal leave |
| My Schedule | /staff/schedule | View personal shift schedule |
| My Profile | /staff/profile | Update personal profile |
// Source: src/app/dashboard/page.tsx:245-270
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<button
onClick={() => router.push('/staff/timesheet')}
className="form-button secondary min-h-[44px]"
>
My Timesheet
</button>
<button
onClick={() => router.push('/staff/leave')}
className="form-button secondary min-h-[44px]"
>
My Leave
</button>
<button
onClick={() => router.push('/staff/schedule')}
className="form-button secondary min-h-[44px]"
>
My Schedule
</button>
<button
onClick={() => router.push('/staff/profile')}
className="form-button secondary min-h-[44px]"
>
My Profile
</button>
</div>
Recent Activity
The Recent Activity section displays a feed of recent company events.
The activity feed functionality is planned for a future release.
Current placeholder:
// Source: src/app/dashboard/page.tsx:223-228
<div className="card-glass rounded-xl p-4 md:p-6">
<p className="text-secondary-text text-center py-6 md:py-8 text-sm md:text-base">
Activity feed coming soon...
</p>
</div>
Responsive Design
The dashboard adapts to different screen sizes:
Desktop (lg and above)
- 3-column card grid
- 4-column statistics display
- Full navigation visible
Tablet (md breakpoints)
- 2-column card grid
- 4-column statistics display
- Adjusted padding and spacing
Mobile (sm and below)
- Single column card grid
- 2-column statistics display
- Bottom padding for mobile navigation (pb-20)
// Source: src/app/dashboard/page.tsx:124
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-5 mb-8">
// Source: src/app/dashboard/page.tsx:180
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6">
API Endpoints
The dashboard uses these API endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/companies/[companyId]/stats | GET | Fetch company statistics |
Statistics API
// Source: src/utils/api/companyStats.ts:13-24
export async function getCompanyStats(companyId: string): Promise<CompanyStats> {
const stats = await fetchApiData<CompanyStats>(
`/api/companies/${companyId}/stats`,
undefined,
'stats'
)
return stats
}
Company ID Resolution
The dashboard automatically resolves the company ID for the current user:
// Source: src/utils/api/companyStats.ts:26-72
export async function getCompanyIdForUser(): Promise<string | null> {
const { data: { user }, error: userError } = await supabase.auth.getUser()
if (!user) return null
// Check if company_id is in user metadata first
const metadataCompanyId = getAuthCompanyId(user)
if (metadataCompanyId) {
return metadataCompanyId
}
// If not in metadata, check the staff table
const { data: staffMember, error: staffError } = await supabase
.from('staff')
.select('company_id')
.eq('id', user.id)
.single()
return staffMember?.company_id || null
}
Resolution Order:
- User metadata (
user.user_metadata.company_id) - Staff table lookup (
staff.company_id)
Keyboard Navigation
| Shortcut | Action |
|---|---|
Tab | Navigate between cards and buttons |
Enter | Activate focused card action |
Space | Activate focused button |
All action buttons maintain 44px minimum height for accessibility compliance.
Related Documentation
- Staff Management - Managing team members
- Scheduling Overview - Shift scheduling
- Time Tracking - Time monitoring
- Leave Management - Handling leave requests
Source Files:
src/app/dashboard/page.tsx- Dashboard page componentsrc/app/dashboard/layout.tsx- Dashboard layout with role protectionsrc/utils/api/companyStats.ts- Statistics fetching utilitiessrc/components/navigation/PortalSwitcher.tsx- Portal switching component