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:
parent
191698088b
commit
c07e02855e
5 changed files with 33 additions and 36 deletions
|
|
@ -3,6 +3,7 @@ import { authOptions } from '@/lib/auth'
|
||||||
import { redirect } from 'next/navigation'
|
import { redirect } from 'next/navigation'
|
||||||
import { prisma } from '@/lib/db'
|
import { prisma } from '@/lib/db'
|
||||||
import { ManagerPageClient } from '@/components/manager/manager-page-client'
|
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() {
|
export default async function ManagerPage() {
|
||||||
const session = await getServerSession(authOptions)
|
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({
|
const teamMembers = await prisma.user.findMany({
|
||||||
where: { isActive: true },
|
where: { isActive: true },
|
||||||
select: {
|
select: {
|
||||||
|
|
@ -45,7 +54,8 @@ export default async function ManagerPage() {
|
||||||
taskAssignments: {
|
taskAssignments: {
|
||||||
where: {
|
where: {
|
||||||
task: {
|
task: {
|
||||||
status: { not: 'COMPLETED' }
|
clientId: { in: shapeClientIds },
|
||||||
|
status: { notIn: ['COMPLETED', 'CANCELLED', 'NA'] },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
|
|
@ -60,11 +70,7 @@ export default async function ManagerPage() {
|
||||||
},
|
},
|
||||||
userRoles: {
|
userRoles: {
|
||||||
include: {
|
include: {
|
||||||
role: {
|
role: { select: { name: true } }
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -93,6 +99,7 @@ export default async function ManagerPage() {
|
||||||
teamMembers={teamMembers}
|
teamMembers={teamMembers}
|
||||||
recentTasks={recentTasks}
|
recentTasks={recentTasks}
|
||||||
setupQueueCount={setupQueueCount}
|
setupQueueCount={setupQueueCount}
|
||||||
|
overdueWindowDays={overdueWindowDays}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ export async function GET(request: NextRequest) {
|
||||||
email: true,
|
email: true,
|
||||||
department: true,
|
department: true,
|
||||||
taskAssignments: {
|
taskAssignments: {
|
||||||
where: { task: deptTaskWhere },
|
where: { task: { ...deptTaskWhere, status: { notIn: ['COMPLETED', 'CANCELLED', 'NA'] } } },
|
||||||
include: {
|
include: {
|
||||||
task: {
|
task: {
|
||||||
select: {
|
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 userMetrics = userWorkload.map((user) => {
|
||||||
const tasks = user.taskAssignments.map((a) => a.task)
|
const activeTasks = 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 overdue = activeTasks.filter(
|
const overdue = activeTasks.filter(
|
||||||
(t) => new Date(t.dueDate) >= overdueFrom && new Date(t.dueDate) < today
|
(t) => new Date(t.dueDate) >= overdueFrom && new Date(t.dueDate) < today
|
||||||
)
|
)
|
||||||
|
|
@ -192,15 +188,12 @@ export async function GET(request: NextRequest) {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.displayName || user.email,
|
name: user.displayName || user.email,
|
||||||
department: user.department,
|
department: user.department,
|
||||||
totalAssigned: tasks.length,
|
totalAssigned: activeTasks.length,
|
||||||
activeTasks: activeTasks.length,
|
activeTasks: activeTasks.length,
|
||||||
completedTasks: completedTasks.length,
|
completedTasks: 0,
|
||||||
overdueTasks: overdue.length,
|
overdueTasks: overdue.length,
|
||||||
highPriorityTasks: highPriority.length,
|
highPriorityTasks: highPriority.length,
|
||||||
completionRate:
|
completionRate: 0,
|
||||||
tasks.length > 0
|
|
||||||
? Math.round((completedTasks.length / tasks.length) * 100)
|
|
||||||
: 0,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -392,10 +392,8 @@ export function WorkloadKPIs({ department }: WorkloadKPIsProps = {}) {
|
||||||
<tr className="border-b">
|
<tr className="border-b">
|
||||||
<th className="text-left py-2 font-medium">Team Member</th>
|
<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">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">Overdue</th>
|
||||||
<th className="text-center py-2 font-medium">High Priority</th>
|
<th className="text-center py-2 font-medium">High Priority</th>
|
||||||
<th className="text-center py-2 font-medium">Completion %</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -410,14 +408,11 @@ export function WorkloadKPIs({ department }: WorkloadKPIsProps = {}) {
|
||||||
<td className="text-center py-3">
|
<td className="text-center py-3">
|
||||||
<Badge variant="outline">{user.activeTasks}</Badge>
|
<Badge variant="outline">{user.activeTasks}</Badge>
|
||||||
</td>
|
</td>
|
||||||
<td className="text-center py-3">
|
|
||||||
<span className="text-green-600">{user.completedTasks}</span>
|
|
||||||
</td>
|
|
||||||
<td className="text-center py-3">
|
<td className="text-center py-3">
|
||||||
{user.overdueTasks > 0 ? (
|
{user.overdueTasks > 0 ? (
|
||||||
<Badge variant="destructive">{user.overdueTasks}</Badge>
|
<Badge variant="destructive">{user.overdueTasks}</Badge>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-muted-foreground">0</span>
|
<span className="text-muted-foreground">—</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-center py-3">
|
<td className="text-center py-3">
|
||||||
|
|
@ -426,15 +421,9 @@ export function WorkloadKPIs({ department }: WorkloadKPIsProps = {}) {
|
||||||
{user.highPriorityTasks}
|
{user.highPriorityTasks}
|
||||||
</Badge>
|
</Badge>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-muted-foreground">0</span>
|
<span className="text-muted-foreground">—</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ interface ManagerPageClientProps {
|
||||||
teamMembers: any[]
|
teamMembers: any[]
|
||||||
recentTasks: any[]
|
recentTasks: any[]
|
||||||
setupQueueCount: number
|
setupQueueCount: number
|
||||||
|
overdueWindowDays: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ManagerPageClient({
|
export function ManagerPageClient({
|
||||||
|
|
@ -22,6 +23,7 @@ export function ManagerPageClient({
|
||||||
teamMembers,
|
teamMembers,
|
||||||
recentTasks,
|
recentTasks,
|
||||||
setupQueueCount,
|
setupQueueCount,
|
||||||
|
overdueWindowDays,
|
||||||
}: ManagerPageClientProps) {
|
}: ManagerPageClientProps) {
|
||||||
const [claimsOnly, setClaimsOnly] = useState(true)
|
const [claimsOnly, setClaimsOnly] = useState(true)
|
||||||
|
|
||||||
|
|
@ -128,7 +130,7 @@ export function ManagerPageClient({
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<TeamMembersByDepartment teamMembers={filteredMembers} />
|
<TeamMembersByDepartment teamMembers={filteredMembers} overdueWindowDays={overdueWindowDays} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,13 @@ interface TeamMember {
|
||||||
|
|
||||||
interface TeamMembersByDepartmentProps {
|
interface TeamMembersByDepartmentProps {
|
||||||
teamMembers: TeamMember[]
|
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
|
// Group members by department
|
||||||
const groupedByDepartment = teamMembers.reduce((acc, member) => {
|
const groupedByDepartment = teamMembers.reduce((acc, member) => {
|
||||||
const dept = member.department || 'Unassigned'
|
const dept = member.department || 'Unassigned'
|
||||||
|
|
@ -103,7 +107,9 @@ export function TeamMembersByDepartment({ teamMembers }: TeamMembersByDepartment
|
||||||
{members.map((member) => {
|
{members.map((member) => {
|
||||||
const activeTasks = member.taskAssignments.length
|
const activeTasks = member.taskAssignments.length
|
||||||
const overdue = member.taskAssignments.filter(
|
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
|
).length
|
||||||
const roles = member.userRoles.map(ur => ur.role.name).join(', ')
|
const roles = member.userRoles.map(ur => ur.role.name).join(', ')
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue