Fix overdue counts on manager page

- teamMembers query now scoped to Shape clients + active tasks only
- TeamMembersByDepartment overdue calc uses overdueWindowDays window (default 7d)
- Workload API per-user fetch now only loads active tasks (massive perf win)
- Remove Completed/Completion% columns from workload table (no longer meaningful)
- overdueWindowDays passed through page -> client -> component chain
This commit is contained in:
lorentz 2026-04-14 00:27:47 +00:00
parent 191698088b
commit c07e02855e
5 changed files with 33 additions and 36 deletions

View file

@ -3,6 +3,7 @@ import { authOptions } from '@/lib/auth'
import { redirect } from 'next/navigation'
import { prisma } from '@/lib/db'
import { ManagerPageClient } from '@/components/manager/manager-page-client'
import { OVERDUE_WINDOW_KEY, OVERDUE_WINDOW_DEFAULT } from '@/app/api/admin/manager-settings/route'
export default async function ManagerPage() {
const session = await getServerSession(authOptions)
@ -34,7 +35,15 @@ export default async function ManagerPage() {
}),
])
// Fetch team members with their task counts
const overdueSetting = await prisma.appSetting.findUnique({ where: { key: OVERDUE_WINDOW_KEY } })
const overdueWindowDays = parseInt(overdueSetting?.value ?? String(OVERDUE_WINDOW_DEFAULT), 10)
const shapeClientIds = (await prisma.client.findMany({
where: { designation: { name: { in: ['Shape', 'Shape 2'] } } },
select: { id: true },
})).map((c) => c.id)
// Fetch team members with their active task counts (Shape clients only)
const teamMembers = await prisma.user.findMany({
where: { isActive: true },
select: {
@ -45,7 +54,8 @@ export default async function ManagerPage() {
taskAssignments: {
where: {
task: {
status: { not: 'COMPLETED' }
clientId: { in: shapeClientIds },
status: { notIn: ['COMPLETED', 'CANCELLED', 'NA'] },
}
},
select: {
@ -60,11 +70,7 @@ export default async function ManagerPage() {
},
userRoles: {
include: {
role: {
select: {
name: true,
}
}
role: { select: { name: true } }
}
}
},
@ -93,6 +99,7 @@ export default async function ManagerPage() {
teamMembers={teamMembers}
recentTasks={recentTasks}
setupQueueCount={setupQueueCount}
overdueWindowDays={overdueWindowDays}
/>
)
}

View file

@ -126,7 +126,7 @@ export async function GET(request: NextRequest) {
email: true,
department: true,
taskAssignments: {
where: { task: deptTaskWhere },
where: { task: { ...deptTaskWhere, status: { notIn: ['COMPLETED', 'CANCELLED', 'NA'] } } },
include: {
task: {
select: {
@ -174,13 +174,9 @@ export async function GET(request: NextRequest) {
}),
])
// Calculate user-level metrics
// Calculate user-level metrics (taskAssignments only contains active tasks)
const userMetrics = userWorkload.map((user) => {
const tasks = user.taskAssignments.map((a) => a.task)
const activeTasks = tasks.filter(
(t) => !['COMPLETED', 'CANCELLED', 'NA'].includes(t.status)
)
const completedTasks = tasks.filter((t) => t.status === 'COMPLETED')
const activeTasks = user.taskAssignments.map((a) => a.task)
const overdue = activeTasks.filter(
(t) => new Date(t.dueDate) >= overdueFrom && new Date(t.dueDate) < today
)
@ -192,15 +188,12 @@ export async function GET(request: NextRequest) {
id: user.id,
name: user.displayName || user.email,
department: user.department,
totalAssigned: tasks.length,
totalAssigned: activeTasks.length,
activeTasks: activeTasks.length,
completedTasks: completedTasks.length,
completedTasks: 0,
overdueTasks: overdue.length,
highPriorityTasks: highPriority.length,
completionRate:
tasks.length > 0
? Math.round((completedTasks.length / tasks.length) * 100)
: 0,
completionRate: 0,
}
})

View file

@ -392,10 +392,8 @@ export function WorkloadKPIs({ department }: WorkloadKPIsProps = {}) {
<tr className="border-b">
<th className="text-left py-2 font-medium">Team Member</th>
<th className="text-center py-2 font-medium">Active</th>
<th className="text-center py-2 font-medium">Completed</th>
<th className="text-center py-2 font-medium">Overdue</th>
<th className="text-center py-2 font-medium">High Priority</th>
<th className="text-center py-2 font-medium">Completion %</th>
</tr>
</thead>
<tbody>
@ -410,14 +408,11 @@ export function WorkloadKPIs({ department }: WorkloadKPIsProps = {}) {
<td className="text-center py-3">
<Badge variant="outline">{user.activeTasks}</Badge>
</td>
<td className="text-center py-3">
<span className="text-green-600">{user.completedTasks}</span>
</td>
<td className="text-center py-3">
{user.overdueTasks > 0 ? (
<Badge variant="destructive">{user.overdueTasks}</Badge>
) : (
<span className="text-muted-foreground">0</span>
<span className="text-muted-foreground"></span>
)}
</td>
<td className="text-center py-3">
@ -426,15 +421,9 @@ export function WorkloadKPIs({ department }: WorkloadKPIsProps = {}) {
{user.highPriorityTasks}
</Badge>
) : (
<span className="text-muted-foreground">0</span>
<span className="text-muted-foreground"></span>
)}
</td>
<td className="text-center py-3">
<div className="flex items-center justify-center gap-2">
<Progress value={user.completionRate} className="w-16 h-2" />
<span className="text-xs">{user.completionRate}%</span>
</div>
</td>
</tr>
))}
</tbody>

View file

@ -15,6 +15,7 @@ interface ManagerPageClientProps {
teamMembers: any[]
recentTasks: any[]
setupQueueCount: number
overdueWindowDays: number
}
export function ManagerPageClient({
@ -22,6 +23,7 @@ export function ManagerPageClient({
teamMembers,
recentTasks,
setupQueueCount,
overdueWindowDays,
}: ManagerPageClientProps) {
const [claimsOnly, setClaimsOnly] = useState(true)
@ -128,7 +130,7 @@ export function ManagerPageClient({
</CardTitle>
</CardHeader>
<CardContent>
<TeamMembersByDepartment teamMembers={filteredMembers} />
<TeamMembersByDepartment teamMembers={filteredMembers} overdueWindowDays={overdueWindowDays} />
</CardContent>
</Card>

View file

@ -25,9 +25,13 @@ interface TeamMember {
interface TeamMembersByDepartmentProps {
teamMembers: TeamMember[]
overdueWindowDays: number
}
export function TeamMembersByDepartment({ teamMembers }: TeamMembersByDepartmentProps) {
export function TeamMembersByDepartment({ teamMembers, overdueWindowDays }: TeamMembersByDepartmentProps) {
const today = new Date()
today.setHours(0, 0, 0, 0)
const overdueFrom = new Date(today.getTime() - overdueWindowDays * 24 * 60 * 60 * 1000)
// Group members by department
const groupedByDepartment = teamMembers.reduce((acc, member) => {
const dept = member.department || 'Unassigned'
@ -103,7 +107,9 @@ export function TeamMembersByDepartment({ teamMembers }: TeamMembersByDepartment
{members.map((member) => {
const activeTasks = member.taskAssignments.length
const overdue = member.taskAssignments.filter(
a => a.task.dueDate && new Date(a.task.dueDate) < new Date()
a => a.task.dueDate &&
new Date(a.task.dueDate) >= overdueFrom &&
new Date(a.task.dueDate) < today
).length
const roles = member.userRoles.map(ur => ur.role.name).join(', ')