diff --git a/ondeck/src/app/(dashboard)/admin/settings/page.tsx b/ondeck/src/app/(dashboard)/admin/settings/page.tsx
index f5ce371..4be4c64 100644
--- a/ondeck/src/app/(dashboard)/admin/settings/page.tsx
+++ b/ondeck/src/app/(dashboard)/admin/settings/page.tsx
@@ -28,6 +28,7 @@ export default async function SystemSettingsPage() {
const settingsMap = Object.fromEntries(appSettings.map((s) => [s.key, s.value]))
const windowDays = settingsMap['renewal_group_window_days'] ?? '90'
const dateRule = settingsMap['renewal_group_date_rule'] ?? 'nearest-to-year-start'
+ const overdueWindowDays = parseInt(settingsMap['overdue_window_days'] ?? '7', 10)
const relatedPages = [
{
@@ -66,7 +67,7 @@ export default async function SystemSettingsPage() {
{/* Sync config inline editor */}
-
+
{/* Links to subsection settings pages */}
diff --git a/ondeck/src/app/(dashboard)/admin/settings/system-settings-form.tsx b/ondeck/src/app/(dashboard)/admin/settings/system-settings-form.tsx
index b0810ff..f082db2 100644
--- a/ondeck/src/app/(dashboard)/admin/settings/system-settings-form.tsx
+++ b/ondeck/src/app/(dashboard)/admin/settings/system-settings-form.tsx
@@ -7,17 +7,20 @@ import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input'
import { Switch } from '@/components/ui/switch'
import { Button } from '@/components/ui/button'
-import { Database, Save } from 'lucide-react'
+import { Database, Save, BarChart3 } from 'lucide-react'
interface SystemSettingsFormProps {
syncEnabled: boolean
syncSchedule: string
+ overdueWindowDays: number
}
-export function SystemSettingsForm({ syncEnabled: initialEnabled, syncSchedule: initialSchedule }: SystemSettingsFormProps) {
+export function SystemSettingsForm({ syncEnabled: initialEnabled, syncSchedule: initialSchedule, overdueWindowDays: initialOverdueWindow }: SystemSettingsFormProps) {
const [syncEnabled, setSyncEnabled] = useState(initialEnabled)
const [syncSchedule, setSyncSchedule] = useState(initialSchedule)
const [saving, setSaving] = useState(false)
+ const [overdueWindowDays, setOverdueWindowDays] = useState(initialOverdueWindow)
+ const [savingOverdue, setSavingOverdue] = useState(false)
async function handleSave() {
setSaving(true)
@@ -39,7 +42,28 @@ export function SystemSettingsForm({ syncEnabled: initialEnabled, syncSchedule:
}
}
+ async function handleSaveOverdue() {
+ setSavingOverdue(true)
+ try {
+ const res = await fetch('/api/admin/manager-settings', {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ overdueWindowDays }),
+ })
+ if (!res.ok) {
+ const data = await res.json().catch(() => ({}))
+ throw new Error(data.error || 'Save failed')
+ }
+ toast.success('Manager settings saved')
+ } catch (err: any) {
+ toast.error(err.message || 'Save failed')
+ } finally {
+ setSavingOverdue(false)
+ }
+ }
+
return (
+
@@ -82,5 +106,38 @@ export function SystemSettingsForm({ syncEnabled: initialEnabled, syncSchedule:
+
+
+
+
+ Manager Dashboard
+
+
+
+
+
+
+ setOverdueWindowDays(Number(e.target.value))}
+ className="w-24 text-sm"
+ />
+ days back
+
+
+ Only tasks with a due date within this many days in the past are shown as overdue on the Manager dashboard.
+
+
+
+
+
+
)
}
diff --git a/ondeck/src/app/api/admin/manager-settings/route.ts b/ondeck/src/app/api/admin/manager-settings/route.ts
new file mode 100644
index 0000000..7978f7c
--- /dev/null
+++ b/ondeck/src/app/api/admin/manager-settings/route.ts
@@ -0,0 +1,57 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { getServerSession } from 'next-auth'
+import { authOptions } from '@/lib/auth'
+import { prisma } from '@/lib/db'
+
+export const OVERDUE_WINDOW_KEY = 'overdue_window_days'
+export const OVERDUE_WINDOW_DEFAULT = 7
+
+export async function GET() {
+ try {
+ const session = await getServerSession(authOptions)
+ if (!session?.user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+
+ const setting = await prisma.appSetting.findUnique({ where: { key: OVERDUE_WINDOW_KEY } })
+ return NextResponse.json({
+ overdueWindowDays: parseInt(setting?.value ?? String(OVERDUE_WINDOW_DEFAULT), 10),
+ })
+ } catch (error) {
+ console.error('Manager settings GET error:', error)
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
+
+export async function PUT(request: NextRequest) {
+ try {
+ const session = await getServerSession(authOptions)
+ if (!session?.user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+
+ const userRoles = (session.user as any).roles || []
+ if (!userRoles.includes('Admin')) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+
+ const { overdueWindowDays } = await request.json()
+ if (typeof overdueWindowDays !== 'number' || overdueWindowDays < 1 || overdueWindowDays > 365) {
+ return NextResponse.json({ error: 'overdueWindowDays must be between 1 and 365' }, { status: 400 })
+ }
+
+ await prisma.appSetting.upsert({
+ where: { key: OVERDUE_WINDOW_KEY },
+ update: { value: String(overdueWindowDays) },
+ create: { key: OVERDUE_WINDOW_KEY, value: String(overdueWindowDays) },
+ })
+
+ await prisma.auditLog.create({
+ data: {
+ userId: (session.user as any).id,
+ action: 'MANAGER_SETTINGS_UPDATED',
+ entityType: 'AppSetting',
+ newValues: { overdueWindowDays },
+ },
+ })
+
+ return NextResponse.json({ overdueWindowDays })
+ } catch (error) {
+ console.error('Manager settings PUT error:', error)
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
diff --git a/ondeck/src/app/api/dashboard/workload/route.ts b/ondeck/src/app/api/dashboard/workload/route.ts
index df4cb19..13afaa4 100644
--- a/ondeck/src/app/api/dashboard/workload/route.ts
+++ b/ondeck/src/app/api/dashboard/workload/route.ts
@@ -3,6 +3,7 @@ import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/db'
import { DepartmentType } from '@prisma/client'
+import { OVERDUE_WINDOW_KEY, OVERDUE_WINDOW_DEFAULT } from '@/app/api/admin/manager-settings/route'
export async function GET(request: NextRequest) {
try {
@@ -19,6 +20,10 @@ export async function GET(request: NextRequest) {
const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000)
const monthAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000)
+ const overdueWindowSetting = await prisma.appSetting.findUnique({ where: { key: OVERDUE_WINDOW_KEY } })
+ const overdueWindowDays = parseInt(overdueWindowSetting?.value ?? String(OVERDUE_WINDOW_DEFAULT), 10)
+ const overdueFrom = new Date(today.getTime() - overdueWindowDays * 24 * 60 * 60 * 1000)
+
// Shape client IDs for scoping
const shapeClients = await prisma.client.findMany({
where: { designation: { name: { in: ['Shape', 'Shape 2'] } } },
@@ -82,11 +87,11 @@ export async function GET(request: NextRequest) {
where: deptTaskWhere,
_count: { id: true },
}),
- // Overdue tasks (past due, not completed)
+ // Overdue tasks (past due within window, not completed)
prisma.task.count({
where: {
...deptTaskWhere,
- dueDate: { lt: today },
+ dueDate: { gte: overdueFrom, lt: today },
status: { notIn: ['COMPLETED', 'CANCELLED', 'NA'] },
},
}),
@@ -177,7 +182,7 @@ export async function GET(request: NextRequest) {
)
const completedTasks = tasks.filter((t) => t.status === 'COMPLETED')
const overdue = activeTasks.filter(
- (t) => new Date(t.dueDate) < today
+ (t) => new Date(t.dueDate) >= overdueFrom && new Date(t.dueDate) < today
)
const highPriority = activeTasks.filter(
(t) => t.priority === 'HIGH' || t.priority === 'URGENT'
@@ -251,6 +256,7 @@ export async function GET(request: NextRequest) {
completedTasks: totalCompleted,
completionRate,
overdueTasks,
+ overdueWindowDays,
dueTodayTasks,
dueThisWeekTasks,
activeUsers,
diff --git a/ondeck/src/components/dashboard/workload-kpis.tsx b/ondeck/src/components/dashboard/workload-kpis.tsx
index b2db6d5..411dae3 100644
--- a/ondeck/src/components/dashboard/workload-kpis.tsx
+++ b/ondeck/src/components/dashboard/workload-kpis.tsx
@@ -25,6 +25,7 @@ interface WorkloadData {
completedTasks: number
completionRate: number
overdueTasks: number
+ overdueWindowDays: number
dueTodayTasks: number
dueThisWeekTasks: number
activeUsers: number
@@ -195,7 +196,7 @@ export function WorkloadKPIs({ department }: WorkloadKPIsProps = {}) {
{summary.overdueTasks}
- {summary.dueTodayTasks} due today
+ past due within {summary.overdueWindowDays}d