From 32a8bc2bb5c063ad9472f1331838f925e22a1924 Mon Sep 17 00:00:00 2001 From: iamasx Date: Sat, 25 Apr 2026 02:10:01 +0530 Subject: [PATCH] feat: add intake-desk route with triage cards, request cards, and assignment panel (#242) Add a new intake-desk route under the app router with: - Mock data for incoming requests, triage cards, and assignment entries - Request cards showing title, requester, urgency, category, and summary - Triage summary cards with reviewer, outcome, and notes - Compact assignment summary panel with status tracking - Responsive layout matching existing route conventions - Tests covering all main render states Co-Authored-By: Claude Opus 4.6 (1M context) --- .../_components/assignment-panel.tsx | 68 ++++++ .../intake-desk/_components/request-card.tsx | 71 ++++++ .../intake-desk/_components/triage-card.tsx | 52 +++++ src/app/intake-desk/_data/intake-desk-data.ts | 211 ++++++++++++++++++ src/app/intake-desk/intake-desk.module.css | 130 +++++++++++ src/app/intake-desk/page.test.tsx | 89 ++++++++ src/app/intake-desk/page.tsx | 139 ++++++++++++ 7 files changed, 760 insertions(+) create mode 100644 src/app/intake-desk/_components/assignment-panel.tsx create mode 100644 src/app/intake-desk/_components/request-card.tsx create mode 100644 src/app/intake-desk/_components/triage-card.tsx create mode 100644 src/app/intake-desk/_data/intake-desk-data.ts create mode 100644 src/app/intake-desk/intake-desk.module.css create mode 100644 src/app/intake-desk/page.test.tsx create mode 100644 src/app/intake-desk/page.tsx diff --git a/src/app/intake-desk/_components/assignment-panel.tsx b/src/app/intake-desk/_components/assignment-panel.tsx new file mode 100644 index 0000000..a3cf1df --- /dev/null +++ b/src/app/intake-desk/_components/assignment-panel.tsx @@ -0,0 +1,68 @@ +import type { AssignmentEntry } from "../_data/intake-desk-data"; +import styles from "../intake-desk.module.css"; + +const statusLabels: Record = { + unassigned: "Unassigned", + "in-progress": "In Progress", + completed: "Completed", +}; + +const statusBadgeStyles: Record = { + unassigned: "border-slate-300/20 bg-slate-300/10 text-slate-50", + "in-progress": "border-amber-300/20 bg-amber-300/10 text-amber-50", + completed: "border-emerald-300/20 bg-emerald-300/10 text-emerald-50", +}; + +type AssignmentPanelProps = { + entries: AssignmentEntry[]; +}; + +export function AssignmentPanel({ entries }: AssignmentPanelProps) { + return ( +
+
+

+ Assignments +

+

+ Assignment summary +

+

+ Current assignment status for triaged requests. Track who owns each + item and its progress toward completion. +

+
+ +
+
+ {entries.map((entry) => ( +
+
+
+

+ {entry.requestTitle} +

+

{entry.assignee}

+
+ + {statusLabels[entry.status]} + +
+

+ Due: {entry.dueDate} +

+
+ ))} +
+
+
+ ); +} diff --git a/src/app/intake-desk/_components/request-card.tsx b/src/app/intake-desk/_components/request-card.tsx new file mode 100644 index 0000000..b66fee5 --- /dev/null +++ b/src/app/intake-desk/_components/request-card.tsx @@ -0,0 +1,71 @@ +import type { IncomingRequest } from "../_data/intake-desk-data"; +import styles from "../intake-desk.module.css"; + +const urgencyLabels: Record = { + low: "Low", + normal: "Normal", + high: "High", + critical: "Critical", +}; + +const urgencyBadgeStyles: Record = { + low: "border-emerald-300/20 bg-emerald-300/10 text-emerald-50", + normal: "border-cyan-300/20 bg-cyan-300/10 text-cyan-50", + high: "border-amber-300/20 bg-amber-300/10 text-amber-50", + critical: "border-rose-300/20 bg-rose-300/10 text-rose-50", +}; + +const urgencySurfaceStyles: Record = { + low: styles.urgencyLow, + normal: styles.urgencyNormal, + high: styles.urgencyHigh, + critical: styles.urgencyCritical, +}; + +type RequestCardProps = { + request: IncomingRequest; +}; + +export function RequestCard({ request }: RequestCardProps) { + return ( +
+
+
+

+ {request.title} +

+

{request.requester}

+
+ + {urgencyLabels[request.urgency]} + +
+ +
+
+

+ Category +

+

+ {request.category} +

+
+
+

+ Received +

+

+ {request.receivedAt} +

+
+
+ +

{request.summary}

+
+ ); +} diff --git a/src/app/intake-desk/_components/triage-card.tsx b/src/app/intake-desk/_components/triage-card.tsx new file mode 100644 index 0000000..3a18f7c --- /dev/null +++ b/src/app/intake-desk/_components/triage-card.tsx @@ -0,0 +1,52 @@ +import type { TriageCard } from "../_data/intake-desk-data"; +import styles from "../intake-desk.module.css"; + +const outcomeLabels: Record = { + accepted: "Accepted", + deferred: "Deferred", + escalated: "Escalated", + rejected: "Rejected", +}; + +const outcomeBadgeStyles: Record = { + accepted: "border-emerald-300/20 bg-emerald-300/10 text-emerald-50", + deferred: "border-cyan-300/20 bg-cyan-300/10 text-cyan-50", + escalated: "border-amber-300/20 bg-amber-300/10 text-amber-50", + rejected: "border-rose-300/20 bg-rose-300/10 text-rose-50", +}; + +const outcomeSurfaceStyles: Record = { + accepted: styles.outcomeAccepted, + deferred: styles.outcomeDeferred, + escalated: styles.outcomeEscalated, + rejected: styles.outcomeRejected, +}; + +type TriageCardItemProps = { + card: TriageCard; +}; + +export function TriageCardItem({ card }: TriageCardItemProps) { + return ( +
+
+
+

+ {card.reviewer} +

+

{card.reviewedAt}

+
+ + {outcomeLabels[card.outcome]} + +
+ +

{card.notes}

+
+ ); +} diff --git a/src/app/intake-desk/_data/intake-desk-data.ts b/src/app/intake-desk/_data/intake-desk-data.ts new file mode 100644 index 0000000..4569c89 --- /dev/null +++ b/src/app/intake-desk/_data/intake-desk-data.ts @@ -0,0 +1,211 @@ +export type RequestUrgency = "low" | "normal" | "high" | "critical"; +export type TriageOutcome = "accepted" | "deferred" | "escalated" | "rejected"; +export type AssignmentStatus = "unassigned" | "in-progress" | "completed"; + +export interface IncomingRequest { + id: string; + title: string; + requester: string; + urgency: RequestUrgency; + receivedAt: string; + summary: string; + category: string; +} + +export interface TriageCard { + id: string; + requestId: string; + reviewer: string; + outcome: TriageOutcome; + reviewedAt: string; + notes: string; +} + +export interface AssignmentEntry { + id: string; + requestTitle: string; + assignee: string; + status: AssignmentStatus; + dueDate: string; +} + +export interface IntakeDeskOverview { + eyebrow: string; + title: string; + description: string; + shiftWindow: string; + queue: string; +} + +export interface IntakeStat { + label: string; + value: string; + detail: string; +} + +export const intakeDeskOverview: IntakeDeskOverview = { + eyebrow: "Intake Desk", + title: "Triage incoming requests, review urgency, and track assignment progress.", + description: + "A centralized intake view for reviewing new requests, performing triage, and monitoring how work is distributed across the team.", + shiftWindow: "2026-04-25 — Morning shift", + queue: "Engineering support / Tier 2", +}; + +export const intakeStats: IntakeStat[] = [ + { + label: "Pending requests", + value: "6", + detail: "Six incoming requests awaiting initial triage and assignment", + }, + { + label: "Triaged today", + value: "4", + detail: "Four requests reviewed and categorized during the current shift", + }, + { + label: "Active assignments", + value: "5", + detail: "Five work items currently assigned and in various stages of progress", + }, +]; + +export const incomingRequests: IncomingRequest[] = [ + { + id: "req-001", + title: "Database connection pool exhaustion", + requester: "L. Nakamura", + urgency: "critical", + receivedAt: "2026-04-25 08:12 PDT", + summary: + "Production database connections are being exhausted during peak traffic. Connection pool size may need to be increased or leak investigation is required.", + category: "Infrastructure", + }, + { + id: "req-002", + title: "OAuth token refresh failing for SSO users", + requester: "D. Okonkwo", + urgency: "high", + receivedAt: "2026-04-25 08:34 PDT", + summary: + "SSO users are being logged out every 15 minutes because token refresh is returning 401. Likely a misconfigured redirect URI after the last IdP rotation.", + category: "Authentication", + }, + { + id: "req-003", + title: "Add CSV export to analytics dashboard", + requester: "M. Rivera", + urgency: "normal", + receivedAt: "2026-04-25 09:01 PDT", + summary: + "Product team needs the ability to export filtered analytics data as CSV for quarterly reviews. No current workaround available.", + category: "Feature request", + }, + { + id: "req-004", + title: "Staging environment SSL certificate expiring", + requester: "K. Andersen", + urgency: "high", + receivedAt: "2026-04-25 09:15 PDT", + summary: + "The staging environment SSL certificate expires in 48 hours. Auto-renewal failed due to a DNS validation issue that needs manual intervention.", + category: "Infrastructure", + }, + { + id: "req-005", + title: "Onboarding docs link to deprecated API endpoints", + requester: "T. Pham", + urgency: "low", + receivedAt: "2026-04-25 09:28 PDT", + summary: + "Several links in the developer onboarding guide point to v1 API endpoints that were retired last quarter. Needs a documentation sweep.", + category: "Documentation", + }, + { + id: "req-006", + title: "Webhook delivery retries not respecting backoff", + requester: "R. Gupta", + urgency: "normal", + receivedAt: "2026-04-25 09:45 PDT", + summary: + "Webhook retries are firing at a fixed 1-second interval instead of using exponential backoff, causing downstream rate limiting for some integrators.", + category: "Bug", + }, +]; + +export const triageCards: TriageCard[] = [ + { + id: "triage-001", + requestId: "req-001", + reviewer: "S. Petrov", + outcome: "escalated", + reviewedAt: "2026-04-25 08:20 PDT", + notes: + "Connection pool exhaustion confirmed via metrics. Escalated to on-call SRE for immediate investigation. Temporary mitigation: restart affected pods.", + }, + { + id: "triage-002", + requestId: "req-002", + reviewer: "A. Reyes", + outcome: "accepted", + reviewedAt: "2026-04-25 08:50 PDT", + notes: + "Reproduced the token refresh failure in staging. Root cause is the redirect URI mismatch introduced in last week's IdP config update. Fix is straightforward.", + }, + { + id: "triage-003", + requestId: "req-005", + reviewer: "J. Okafor", + outcome: "deferred", + reviewedAt: "2026-04-25 09:40 PDT", + notes: + "Documentation updates are low urgency. Deferred to next sprint's documentation sweep. Added to the docs backlog tracker.", + }, + { + id: "triage-004", + requestId: "req-003", + reviewer: "S. Petrov", + outcome: "accepted", + reviewedAt: "2026-04-25 09:55 PDT", + notes: + "CSV export is a reasonable ask with clear business value. Scoped to filtered data only — full dataset export will need a separate background job.", + }, +]; + +export const assignmentEntries: AssignmentEntry[] = [ + { + id: "assign-001", + requestTitle: "Database connection pool exhaustion", + assignee: "M. Chen", + status: "in-progress", + dueDate: "2026-04-25", + }, + { + id: "assign-002", + requestTitle: "OAuth token refresh fix", + assignee: "A. Reyes", + status: "in-progress", + dueDate: "2026-04-25", + }, + { + id: "assign-003", + requestTitle: "CSV export — analytics dashboard", + assignee: "T. Pham", + status: "unassigned", + dueDate: "2026-04-28", + }, + { + id: "assign-004", + requestTitle: "Staging SSL certificate renewal", + assignee: "K. Andersen", + status: "completed", + dueDate: "2026-04-25", + }, + { + id: "assign-005", + requestTitle: "Webhook backoff implementation", + assignee: "R. Gupta", + status: "unassigned", + dueDate: "2026-04-29", + }, +]; diff --git a/src/app/intake-desk/intake-desk.module.css b/src/app/intake-desk/intake-desk.module.css new file mode 100644 index 0000000..f32fce1 --- /dev/null +++ b/src/app/intake-desk/intake-desk.module.css @@ -0,0 +1,130 @@ +.shell { + position: relative; + min-height: 100vh; + overflow: hidden; + background: + radial-gradient(circle at 20% 12%, rgba(99, 102, 241, 0.14), transparent 22%), + radial-gradient(circle at 78% 20%, rgba(52, 211, 153, 0.14), transparent 24%), + radial-gradient(circle at 50% 94%, rgba(251, 191, 36, 0.12), transparent 28%), + linear-gradient(180deg, #08111f 0%, #0e1c34 44%, #030712 100%); +} + +.shell::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: + linear-gradient(125deg, rgba(255, 255, 255, 0.03), transparent 45%), + repeating-linear-gradient( + 90deg, + rgba(148, 163, 184, 0.06) 0, + rgba(148, 163, 184, 0.06) 1px, + transparent 1px, + transparent 72px + ); + mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.95), transparent 96%); +} + +.heroPanel { + position: relative; + background: + linear-gradient(140deg, rgba(15, 23, 42, 0.82), rgba(15, 23, 42, 0.58)), + linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(15, 23, 42, 0.12)); + box-shadow: 0 38px 120px rgba(2, 6, 23, 0.42); +} + +.heroPanel::after { + content: ""; + position: absolute; + right: -5rem; + top: -4rem; + width: 18rem; + height: 18rem; + border-radius: 9999px; + background: radial-gradient(circle, rgba(99, 102, 241, 0.22), transparent 66%); + filter: blur(20px); + pointer-events: none; +} + +.statCard { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(15, 23, 42, 0.28)); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +.requestCard { + position: relative; + overflow: hidden; + background: + linear-gradient(180deg, rgba(15, 23, 42, 0.82), rgba(6, 11, 24, 0.94)); + box-shadow: 0 26px 70px rgba(2, 6, 23, 0.26); +} + +.urgencyLow { + border-color: rgba(52, 211, 153, 0.22); +} + +.urgencyNormal { + border-color: rgba(34, 211, 238, 0.22); +} + +.urgencyHigh { + border-color: rgba(251, 191, 36, 0.24); +} + +.urgencyCritical { + border-color: rgba(251, 113, 133, 0.28); +} + +.triageCard { + position: relative; + overflow: hidden; + background: + linear-gradient(180deg, rgba(15, 23, 42, 0.82), rgba(6, 11, 24, 0.94)); + box-shadow: 0 26px 70px rgba(2, 6, 23, 0.26); +} + +.outcomeAccepted { + border-color: rgba(52, 211, 153, 0.22); +} + +.outcomeDeferred { + border-color: rgba(34, 211, 238, 0.22); +} + +.outcomeEscalated { + border-color: rgba(251, 191, 36, 0.24); +} + +.outcomeRejected { + border-color: rgba(251, 113, 133, 0.28); +} + +.assignmentPanel { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(15, 23, 42, 0.32)); +} + +.assignmentRow { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(15, 23, 42, 0.18)); +} + +.statusBadge { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +.floatingInfo { + background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(15, 23, 42, 0.26)); +} + +@media (max-width: 640px) { + .heroPanel, + .requestCard, + .triageCard, + .assignmentPanel, + .statCard { + border-radius: 1.5rem; + } +} diff --git a/src/app/intake-desk/page.test.tsx b/src/app/intake-desk/page.test.tsx new file mode 100644 index 0000000..5af1d73 --- /dev/null +++ b/src/app/intake-desk/page.test.tsx @@ -0,0 +1,89 @@ +import { cleanup, render, screen, within } from "@testing-library/react"; +import { afterEach, describe, expect, it } from "vitest"; + +import { + assignmentEntries, + incomingRequests, + intakeDeskOverview, + intakeStats, + triageCards, +} from "./_data/intake-desk-data"; +import IntakeDeskPage from "./page"; + +afterEach(() => { + cleanup(); +}); + +describe("IntakeDeskPage", () => { + it("renders the hero with primary heading, overview metadata, and back link", () => { + render(); + + expect( + screen.getByText(intakeDeskOverview.title, { selector: "h1" }), + ).toBeInTheDocument(); + expect(screen.getByText(intakeDeskOverview.shiftWindow)).toBeInTheDocument(); + expect(screen.getByText(intakeDeskOverview.queue)).toBeInTheDocument(); + expect( + screen.getByRole("link", { name: /back to overview/i }), + ).toHaveAttribute("href", "/"); + }); + + it("renders summary stats for intake desk", () => { + render(); + + const summarySection = screen.getByLabelText(/intake desk summary/i); + + for (const stat of intakeStats) { + const statEl = within(summarySection).getByText(stat.label).closest("article"); + + expect(statEl).toBeTruthy(); + expect(within(statEl as HTMLElement).getByText(stat.value)).toBeInTheDocument(); + } + }); + + it("renders all incoming requests with titles, requesters, and urgency badges", () => { + render(); + + const requestList = screen.getByRole("list", { name: /incoming requests/i }); + const requestItems = within(requestList).getAllByRole("listitem"); + + expect(requestItems).toHaveLength(incomingRequests.length); + + for (const request of incomingRequests) { + expect(within(requestList).getByText(request.title)).toBeInTheDocument(); + expect(within(requestList).getByText(request.requester)).toBeInTheDocument(); + expect(within(requestList).getByText(request.category)).toBeInTheDocument(); + } + }); + + it("renders all triage cards with reviewers, outcomes, and notes", () => { + render(); + + const triageList = screen.getByRole("list", { name: /triage cards/i }); + const triageItems = within(triageList).getAllByRole("listitem"); + + expect(triageItems).toHaveLength(triageCards.length); + + for (const card of triageCards) { + expect(within(triageList).getByText(card.reviewer)).toBeInTheDocument(); + expect(within(triageList).getByText(card.notes)).toBeInTheDocument(); + } + }); + + it("renders the assignment summary panel with all entries", () => { + render(); + + const assignmentSection = screen.getByLabelText(/assignment summary/i); + const assignmentList = within(assignmentSection).getByRole("list", { + name: /assignment entries/i, + }); + const assignmentItems = within(assignmentList).getAllByRole("listitem"); + + expect(assignmentItems).toHaveLength(assignmentEntries.length); + + for (const entry of assignmentEntries) { + expect(within(assignmentList).getByText(entry.requestTitle)).toBeInTheDocument(); + expect(within(assignmentList).getByText(entry.assignee)).toBeInTheDocument(); + } + }); +}); diff --git a/src/app/intake-desk/page.tsx b/src/app/intake-desk/page.tsx new file mode 100644 index 0000000..fa062a2 --- /dev/null +++ b/src/app/intake-desk/page.tsx @@ -0,0 +1,139 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { AssignmentPanel } from "./_components/assignment-panel"; +import { RequestCard } from "./_components/request-card"; +import { TriageCardItem } from "./_components/triage-card"; +import styles from "./intake-desk.module.css"; +import { + assignmentEntries, + incomingRequests, + intakeDeskOverview, + intakeStats, + triageCards, +} from "./_data/intake-desk-data"; + +export const metadata: Metadata = { + title: "Intake Desk", + description: + "Incoming request triage cards, assignment tracking, and intake queue management.", +}; + +export default function IntakeDeskPage() { + return ( +
+
+ {/* Hero */} +
+
+
+ + {intakeDeskOverview.eyebrow} + +

+ {intakeDeskOverview.title} +

+

+ {intakeDeskOverview.description} +

+
+ +
+
+

+ Shift window +

+

+ {intakeDeskOverview.shiftWindow} +

+

+ {intakeDeskOverview.queue} +

+
+ + + Back to overview + +
+
+
+ + {/* Stats */} +
+ {intakeStats.map((stat) => ( +
+

+ {stat.label} +

+

+ {stat.value} +

+

{stat.detail}

+
+ ))} +
+ + {/* Incoming requests */} +
+
+
+

+ Incoming +

+

+ Incoming requests +

+
+

+ New requests awaiting triage. Each card shows the requester, + urgency level, category, and a brief summary. +

+
+ +
+ {incomingRequests.map((request) => ( + + ))} +
+
+ + {/* Triage cards */} +
+
+
+

+ Triage +

+

+ Triage summaries +

+
+

+ Completed triage reviews with outcomes and reviewer notes. + Each entry is linked to an incoming request. +

+
+ +
+ {triageCards.map((card) => ( + + ))} +
+
+ + {/* Assignment summary */} + +
+
+ ); +}