158 lines
5.9 KiB
TypeScript
158 lines
5.9 KiB
TypeScript
import { useState } from 'react';
|
|
import { format, startOfMonth, startOfQuarter } from 'date-fns';
|
|
|
|
import { useFrameEntries } from '@/business/frame/hooks';
|
|
import { useSafetyQuizCompliance } from '@/business/safety-quiz/hooks';
|
|
import { usePersonalityCompletion } from '@/business/personality/queryHooks';
|
|
import { useZoneCheckInCompletion } from '@/business/zone-checkin/hooks';
|
|
import {
|
|
useStaffAttendanceRecords,
|
|
useStaffAttendanceSummary,
|
|
} from '@/business/staff-attendance/hooks';
|
|
import { usePolicyAcknowledgmentReport } from '@/business/policies/hooks';
|
|
import {
|
|
buildDirectorAcknowledgmentDocuments,
|
|
buildDirectorFramePreviews,
|
|
buildDirectorOverviewCards,
|
|
buildDirectorQuizResults,
|
|
buildDirectorRiskAreas,
|
|
} from '@/business/director-dashboard/selectors';
|
|
import type { DirectorDashboardPage } from '@/business/director-dashboard/types';
|
|
import {
|
|
DIRECTOR_DASHBOARD_QUICK_ACTIONS,
|
|
DIRECTOR_DASHBOARD_TIME_RANGE_STORAGE_KEY,
|
|
DIRECTOR_DASHBOARD_TIME_RANGES,
|
|
type DirectorDashboardTimeRange,
|
|
} from '@/shared/constants/directorDashboard';
|
|
import { useAuth } from '@/shared/app/useAuth';
|
|
import { getLeadershipDashboardName } from '@/business/app-shell/selectors';
|
|
import { getActiveTenant } from '@/business/scope/selectors';
|
|
import { useScopeContext } from '@/shared/app/scope-context';
|
|
import { DEFAULT_PRODUCT_ROLE } from '@/shared/constants/roles';
|
|
import { getCurrentSafetyQuizWeek } from '@/business/safety-quiz/selectors';
|
|
import { getWeekStart } from '@/shared/business/week';
|
|
|
|
interface DirectorDashboardDateRange {
|
|
readonly startDate: string;
|
|
readonly endDate: string;
|
|
}
|
|
|
|
function isDirectorDashboardTimeRange(value: string | null): value is DirectorDashboardTimeRange {
|
|
return DIRECTOR_DASHBOARD_TIME_RANGES.some((range) => range === value);
|
|
}
|
|
|
|
function readStoredTimeRange(): DirectorDashboardTimeRange {
|
|
if (typeof window === 'undefined') {
|
|
return 'month';
|
|
}
|
|
|
|
try {
|
|
const stored = window.localStorage.getItem(DIRECTOR_DASHBOARD_TIME_RANGE_STORAGE_KEY);
|
|
return isDirectorDashboardTimeRange(stored) ? stored : 'month';
|
|
} catch {
|
|
return 'month';
|
|
}
|
|
}
|
|
|
|
export function getDirectorDashboardDateRange(
|
|
timeRange: DirectorDashboardTimeRange,
|
|
now = new Date(),
|
|
): DirectorDashboardDateRange {
|
|
const startDate = timeRange === 'week'
|
|
? getWeekStart(now)
|
|
: timeRange === 'month'
|
|
? startOfMonth(now)
|
|
: startOfQuarter(now);
|
|
|
|
return {
|
|
startDate: format(startDate, 'yyyy-MM-dd'),
|
|
endDate: format(now, 'yyyy-MM-dd'),
|
|
};
|
|
}
|
|
|
|
export function useDirectorDashboardPage(): DirectorDashboardPage {
|
|
const { user, profile } = useAuth();
|
|
const { effectiveTenant } = useScopeContext();
|
|
const role = profile?.role ?? DEFAULT_PRODUCT_ROLE;
|
|
const title = getLeadershipDashboardName(role);
|
|
const activeTenant = effectiveTenant ?? getActiveTenant(user);
|
|
const scopeLabel = activeTenant?.name ?? '';
|
|
const scopeKey = activeTenant ? `${activeTenant.level}:${activeTenant.id}` : null;
|
|
const [timeRange, setTimeRangeState] = useState<DirectorDashboardTimeRange>(readStoredTimeRange);
|
|
const periodFilter = getDirectorDashboardDateRange(timeRange);
|
|
const safetyQuizWeek = getCurrentSafetyQuizWeek(new Date());
|
|
const frameEntriesQuery = useFrameEntries(periodFilter);
|
|
const quizCompletionQuery = useSafetyQuizCompliance(safetyQuizWeek, true);
|
|
const emotionalIntelligenceCompletionQuery = usePersonalityCompletion(true);
|
|
const zoneCheckinCompletionQuery = useZoneCheckInCompletion(true, scopeKey);
|
|
const staffAttendanceRecordsQuery = useStaffAttendanceRecords(periodFilter);
|
|
const staffAttendanceSummaryQuery = useStaffAttendanceSummary(periodFilter);
|
|
const acknowledgmentReportQuery = usePolicyAcknowledgmentReport();
|
|
const frameEntries = frameEntriesQuery.data ?? [];
|
|
const quizRows = quizCompletionQuery.data?.rows ?? [];
|
|
const emotionalIntelligenceCompletion = emotionalIntelligenceCompletionQuery.data ?? null;
|
|
const zoneCheckinCompletion = zoneCheckinCompletionQuery.data ?? null;
|
|
const acknowledgmentDocuments = buildDirectorAcknowledgmentDocuments(acknowledgmentReportQuery.data);
|
|
const quizSummary = quizCompletionQuery.data?.summary ?? {
|
|
totalStaff: 0,
|
|
completedCount: 0,
|
|
pendingCount: 0,
|
|
completionRate: 0,
|
|
};
|
|
const attendanceRecords = staffAttendanceRecordsQuery.data ?? [];
|
|
const isLoading = frameEntriesQuery.isLoading
|
|
|| quizCompletionQuery.isLoading
|
|
|| emotionalIntelligenceCompletionQuery.isLoading
|
|
|| zoneCheckinCompletionQuery.isLoading
|
|
|| staffAttendanceRecordsQuery.isLoading
|
|
|| staffAttendanceSummaryQuery.isLoading
|
|
|| acknowledgmentReportQuery.isLoading;
|
|
const error = frameEntriesQuery.error
|
|
?? quizCompletionQuery.error
|
|
?? emotionalIntelligenceCompletionQuery.error
|
|
?? zoneCheckinCompletionQuery.error
|
|
?? staffAttendanceRecordsQuery.error
|
|
?? staffAttendanceSummaryQuery.error
|
|
?? acknowledgmentReportQuery.error;
|
|
|
|
return {
|
|
title,
|
|
scopeLabel,
|
|
timeRange,
|
|
overviewCards: buildDirectorOverviewCards(
|
|
attendanceRecords,
|
|
quizSummary,
|
|
frameEntries,
|
|
acknowledgmentReportQuery.data?.summary,
|
|
),
|
|
riskAreas: buildDirectorRiskAreas(
|
|
attendanceRecords,
|
|
quizSummary,
|
|
emotionalIntelligenceCompletion,
|
|
zoneCheckinCompletion,
|
|
acknowledgmentDocuments,
|
|
),
|
|
framePreviews: buildDirectorFramePreviews(frameEntries),
|
|
quickActions: DIRECTOR_DASHBOARD_QUICK_ACTIONS,
|
|
quizResults: buildDirectorQuizResults(
|
|
quizRows,
|
|
emotionalIntelligenceCompletion,
|
|
zoneCheckinCompletion,
|
|
scopeLabel,
|
|
),
|
|
acknowledgmentDocuments,
|
|
isLoading,
|
|
error,
|
|
setTimeRange: (nextTimeRange) => {
|
|
setTimeRangeState(nextTimeRange);
|
|
if (typeof window !== 'undefined') {
|
|
try {
|
|
window.localStorage.setItem(DIRECTOR_DASHBOARD_TIME_RANGE_STORAGE_KEY, nextTimeRange);
|
|
} catch {
|
|
// The selected range still updates in memory when storage is blocked.
|
|
}
|
|
}
|
|
},
|
|
};
|
|
}
|