Skip to main content

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'
}
PortalPathDescription
Manager Portal/dashboardCompany management functions
Staff Portal/staffPersonal 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
}
]
CardDescriptionDestination
Staff ManagementManage team members and roles/dashboard/staff
Shift SchedulingPlan rotas and room assignments/dashboard/scheduling
Leave ManagementReview and approve time off/dashboard/leave
Time TrackingMonitor entries and corrections/dashboard/time-tracking
AnalyticsTrack performance and insights/dashboard/analytics
SettingsConfigure 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

StatisticDescription
Active StaffNumber of currently active staff members
Today's ShiftsNumber of shifts scheduled for today
Pending RequestsLeave requests awaiting approval
Weekly HoursTotal 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

FunctionPathDescription
My Timesheet/staff/timesheetView and submit personal timesheet
My Leave/staff/leaveRequest personal leave
My Schedule/staff/scheduleView personal shift schedule
My Profile/staff/profileUpdate 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.

Coming Soon

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:

EndpointMethodDescription
/api/companies/[companyId]/statsGETFetch 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:

  1. User metadata (user.user_metadata.company_id)
  2. Staff table lookup (staff.company_id)

Keyboard Navigation

ShortcutAction
TabNavigate between cards and buttons
EnterActivate focused card action
SpaceActivate focused button

All action buttons maintain 44px minimum height for accessibility compliance.



Source Files:

  • src/app/dashboard/page.tsx - Dashboard page component
  • src/app/dashboard/layout.tsx - Dashboard layout with role protection
  • src/utils/api/companyStats.ts - Statistics fetching utilities
  • src/components/navigation/PortalSwitcher.tsx - Portal switching component