diff --git a/src/app/delivery-brief/_components/checkpoint-card.tsx b/src/app/delivery-brief/_components/checkpoint-card.tsx new file mode 100644 index 0000000..1cb077a --- /dev/null +++ b/src/app/delivery-brief/_components/checkpoint-card.tsx @@ -0,0 +1,62 @@ +import type { Checkpoint } from "../_data/delivery-brief-data"; +import styles from "../delivery-brief.module.css"; + +const severityLabels: Record = { + normal: "Normal", + delayed: "Delayed", + critical: "Critical", +}; + +const severityBadgeStyles: Record = { + normal: "border-emerald-300/20 bg-emerald-300/10 text-emerald-50", + delayed: "border-amber-300/20 bg-amber-300/10 text-amber-50", + critical: "border-rose-300/20 bg-rose-300/10 text-rose-50", +}; + +// Maps each severity level to the corresponding CSS module class for card border styling +const severitySurfaceStyles: Record = { + normal: styles.checkpointNormal, + delayed: styles.checkpointDelayed, + critical: styles.checkpointCritical, +}; + +type CheckpointCardProps = { + checkpoint: Checkpoint; +}; + +// Renders a single checkpoint with location, timestamp, carrier, severity badge, and detail +export function CheckpointCard({ checkpoint }: CheckpointCardProps) { + return ( +
+
+
+

+ {checkpoint.location} +

+

{checkpoint.timestamp}

+
+ + {severityLabels[checkpoint.severity]} + +
+ +
+

+ Carrier +

+

+ {checkpoint.carrier} +

+
+ +

+ {checkpoint.detail} +

+
+ ); +} diff --git a/src/app/delivery-brief/_components/follow-up-notes-rail.tsx b/src/app/delivery-brief/_components/follow-up-notes-rail.tsx new file mode 100644 index 0000000..6de67a6 --- /dev/null +++ b/src/app/delivery-brief/_components/follow-up-notes-rail.tsx @@ -0,0 +1,64 @@ +import type { FollowUpNote } from "../_data/delivery-brief-data"; +import styles from "../delivery-brief.module.css"; + +const priorityLabels: Record = { + info: "Info", + action: "Action", + warning: "Warning", +}; + +const priorityBadgeStyles: Record = { + info: "border-cyan-300/20 bg-cyan-300/10 text-cyan-50", + action: "border-emerald-300/20 bg-emerald-300/10 text-emerald-50", + warning: "border-amber-300/20 bg-amber-300/10 text-amber-50", +}; + +type FollowUpNotesRailProps = { + notes: FollowUpNote[]; +}; + +export function FollowUpNotesRail({ notes }: FollowUpNotesRailProps) { + return ( +
+
+

+ Team notes +

+

+ Follow-up notes +

+

+ Action items, warnings, and status updates from the delivery team. + Each note is tied to a team member and timestamped. +

+
+ +
+ {notes.map((note) => ( +
+
+
+

+ {note.author} +

+

{note.timestamp}

+
+ + {priorityLabels[note.priority]} + +
+

+ {note.body} +

+
+ ))} +
+
+ ); +} diff --git a/src/app/delivery-brief/_components/milestone-summary.tsx b/src/app/delivery-brief/_components/milestone-summary.tsx new file mode 100644 index 0000000..76c78d6 --- /dev/null +++ b/src/app/delivery-brief/_components/milestone-summary.tsx @@ -0,0 +1,47 @@ +import type { Milestone } from "../_data/delivery-brief-data"; +import styles from "../delivery-brief.module.css"; + +const statusLabels: Record = { + completed: "Completed", + "in-progress": "In Progress", + upcoming: "Upcoming", +}; + +const statusBadgeStyles: Record = { + completed: "border-emerald-300/20 bg-emerald-300/10 text-emerald-50", + "in-progress": "border-amber-300/20 bg-amber-300/10 text-amber-50", + upcoming: "border-slate-300/20 bg-slate-300/10 text-slate-200", +}; + +type MilestoneSummaryCardProps = { + milestone: Milestone; +}; + +export function MilestoneSummaryCard({ milestone }: MilestoneSummaryCardProps) { + return ( +
+
+
+

+ {milestone.label} +

+ {milestone.completedAt && ( +

{milestone.completedAt}

+ )} +
+ + {statusLabels[milestone.status]} + +
+ +

+ {milestone.summary} +

+
+ ); +} diff --git a/src/app/delivery-brief/_data/delivery-brief-data.ts b/src/app/delivery-brief/_data/delivery-brief-data.ts new file mode 100644 index 0000000..41b01fb --- /dev/null +++ b/src/app/delivery-brief/_data/delivery-brief-data.ts @@ -0,0 +1,205 @@ +export type MilestoneStatus = "completed" | "in-progress" | "upcoming"; +export type CheckpointSeverity = "normal" | "delayed" | "critical"; + +export interface DeliveryBriefOverview { + eyebrow: string; + title: string; + description: string; + shipmentId: string; + origin: string; + destination: string; +} + +export interface DeliveryStat { + label: string; + value: string; + detail: string; +} + +export interface Milestone { + id: string; + label: string; + status: MilestoneStatus; + completedAt: string | null; + summary: string; +} + +export interface Checkpoint { + id: string; + location: string; + timestamp: string; + severity: CheckpointSeverity; + carrier: string; + detail: string; +} + +export interface FollowUpNote { + id: string; + author: string; + timestamp: string; + body: string; + priority: "info" | "action" | "warning"; +} + +export const deliveryBriefOverview: DeliveryBriefOverview = { + eyebrow: "Delivery Brief", + title: + "Track milestones, review checkpoints, and manage follow-up notes for every shipment.", + description: + "A consolidated view of shipment milestones, transit checkpoints, and team follow-up notes that keeps delivery operations transparent from origin to destination.", + shipmentId: "SHP-2026-04887", + origin: "Portland, OR", + destination: "Austin, TX", +}; + +export const deliveryStats: DeliveryStat[] = [ + { + label: "Milestones reached", + value: "3 / 5", + detail: + "Label created, picked up, and in-transit milestones complete. Customs and delivery pending.", + }, + { + label: "Checkpoints logged", + value: "6", + detail: + "Six transit checkpoints recorded across three facilities and two carrier hand-offs.", + }, + { + label: "Follow-up notes", + value: "4", + detail: + "Two action items, one warning about a delay, and one informational status update.", + }, +]; + +export const milestones: Milestone[] = [ + { + id: "ms-label", + label: "Label created", + status: "completed", + completedAt: "2026-04-20 08:15 PDT", + summary: + "Shipping label generated and tracking number assigned. Origin scan expected within 24 hours.", + }, + { + id: "ms-pickup", + label: "Picked up", + status: "completed", + completedAt: "2026-04-20 14:30 PDT", + summary: + "Package collected from Portland distribution center by primary carrier. Weight and dimensions verified.", + }, + { + id: "ms-transit", + label: "In transit", + status: "in-progress", + completedAt: null, + summary: + "Shipment is moving through the carrier network. Currently at the Denver sorting facility awaiting next-leg dispatch.", + }, + { + id: "ms-customs", + label: "Customs clearance", + status: "upcoming", + completedAt: null, + summary: + "Domestic shipment — no customs clearance required. Milestone will auto-complete on arrival at destination hub.", + }, + { + id: "ms-delivered", + label: "Delivered", + status: "upcoming", + completedAt: null, + summary: + "Final delivery to the Austin receiving dock. Estimated arrival within two business days.", + }, +]; + +export const checkpoints: Checkpoint[] = [ + { + id: "cp-001", + location: "Portland Distribution Center", + timestamp: "2026-04-20 14:30 PDT", + severity: "normal", + carrier: "West Coast Freight", + detail: + "Origin scan completed. Package loaded onto outbound trailer WCF-4421 for eastbound route.", + }, + { + id: "cp-002", + location: "Boise Relay Hub", + timestamp: "2026-04-21 03:12 PDT", + severity: "normal", + carrier: "West Coast Freight", + detail: + "Arrival scan at Boise relay hub. Package transferred to cross-dock lane for next-day dispatch.", + }, + { + id: "cp-003", + location: "Salt Lake City Sort Facility", + timestamp: "2026-04-21 19:45 PDT", + severity: "normal", + carrier: "Mountain Line Logistics", + detail: + "Carrier hand-off completed. Package sorted into southbound container ML-8803.", + }, + { + id: "cp-004", + location: "Denver Sorting Facility", + timestamp: "2026-04-22 11:20 PDT", + severity: "delayed", + carrier: "Mountain Line Logistics", + detail: + "Package held at Denver facility due to trailer capacity constraints. Re-dispatched on next available load.", + }, + { + id: "cp-005", + location: "Amarillo Transfer Station", + timestamp: "2026-04-23 06:55 PDT", + severity: "normal", + carrier: "Southern Express", + detail: + "Second carrier hand-off to Southern Express. Package scanned onto trailer SE-2210 bound for Dallas.", + }, + { + id: "cp-006", + location: "Dallas Regional Hub", + timestamp: "2026-04-24 09:30 PDT", + severity: "critical", + carrier: "Southern Express", + detail: + "Package flagged for address verification at Dallas hub. Hold placed pending shipper confirmation. Expected resolution within 4 hours.", + }, +]; + +export const followUpNotes: FollowUpNote[] = [ + { + id: "fn-001", + author: "L. Nguyen", + timestamp: "2026-04-24 10:15 PDT", + body: "Dallas hub placed a hold on the package for address verification. I've confirmed the Austin delivery address with the customer — releasing the hold now.", + priority: "action", + }, + { + id: "fn-002", + author: "R. Kapoor", + timestamp: "2026-04-23 14:20 PDT", + body: "Denver delay added roughly 18 hours to the transit time. Updated the customer-facing ETA from April 24 to April 26.", + priority: "warning", + }, + { + id: "fn-003", + author: "T. Alvarez", + timestamp: "2026-04-22 08:45 PDT", + body: "Carrier hand-off from West Coast Freight to Mountain Line Logistics went smoothly. No issues flagged at the Salt Lake sort facility.", + priority: "info", + }, + { + id: "fn-004", + author: "M. Brooks", + timestamp: "2026-04-21 16:30 PDT", + body: "Escalate to logistics lead if the Denver hold extends beyond 24 hours. We have a backup route through Albuquerque that can shave a day off transit.", + priority: "action", + }, +]; diff --git a/src/app/delivery-brief/delivery-brief.module.css b/src/app/delivery-brief/delivery-brief.module.css new file mode 100644 index 0000000..5c728df --- /dev/null +++ b/src/app/delivery-brief/delivery-brief.module.css @@ -0,0 +1,117 @@ +.shell { + position: relative; + min-height: 100vh; + overflow: hidden; + background: + radial-gradient(circle at 20% 12%, rgba(14, 165, 233, 0.14), transparent 22%), + radial-gradient(circle at 80% 20%, rgba(52, 211, 153, 0.16), transparent 24%), + radial-gradient(circle at 50% 94%, rgba(251, 191, 36, 0.14), 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(14, 165, 233, 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); +} + +.milestoneCard { + 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); +} + +.milestoneCompleted { + border-color: rgba(52, 211, 153, 0.22); +} + +.milestoneInProgress { + border-color: rgba(251, 191, 36, 0.24); +} + +.milestoneUpcoming { + border-color: rgba(148, 163, 184, 0.18); +} + +.checkpointCard { + 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); +} + +.checkpointNormal { + border-color: rgba(52, 211, 153, 0.22); +} + +.checkpointDelayed { + border-color: rgba(251, 191, 36, 0.24); +} + +.checkpointCritical { + border-color: rgba(251, 113, 133, 0.28); +} + +.noteCard { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(15, 23, 42, 0.32)); +} + +.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, + .milestoneCard, + .checkpointCard, + .noteCard, + .statCard { + border-radius: 1.5rem; + } +} diff --git a/src/app/delivery-brief/page.test.tsx b/src/app/delivery-brief/page.test.tsx new file mode 100644 index 0000000..e9bbae4 --- /dev/null +++ b/src/app/delivery-brief/page.test.tsx @@ -0,0 +1,87 @@ +import { cleanup, render, screen, within } from "@testing-library/react"; +import { afterEach, describe, expect, it } from "vitest"; + +import { + checkpoints, + deliveryBriefOverview, + deliveryStats, + followUpNotes, + milestones, +} from "./_data/delivery-brief-data"; +import DeliveryBriefPage from "./page"; + +afterEach(() => { + cleanup(); +}); + +describe("DeliveryBriefPage", () => { + it("renders the hero with primary heading, shipment metadata, and back link", () => { + render(); + + expect( + screen.getByText(deliveryBriefOverview.title, { selector: "h1" }), + ).toBeInTheDocument(); + expect(screen.getByText(deliveryBriefOverview.shipmentId)).toBeInTheDocument(); + expect( + screen.getByRole("link", { name: /back to overview/i }), + ).toHaveAttribute("href", "/"); + }); + + it("renders summary stats for delivery brief", () => { + render(); + + const summarySection = screen.getByLabelText(/delivery brief summary/i); + + for (const stat of deliveryStats) { + const statEl = within(summarySection).getByText(stat.label).closest("article"); + + expect(statEl).toBeTruthy(); + expect(within(statEl as HTMLElement).getByText(stat.value)).toBeInTheDocument(); + } + }); + + it("renders all milestones with their labels and statuses", () => { + render(); + + const milestoneList = screen.getByRole("list", { name: /milestones/i }); + const milestoneItems = within(milestoneList).getAllByRole("listitem"); + + expect(milestoneItems).toHaveLength(milestones.length); + + for (const milestone of milestones) { + expect(within(milestoneList).getByText(milestone.label)).toBeInTheDocument(); + expect(within(milestoneList).getByText(milestone.summary)).toBeInTheDocument(); + } + }); + + it("renders all checkpoints with locations, carriers, and severity badges", () => { + render(); + + const checkpointList = screen.getByRole("list", { name: /checkpoints/i }); + const checkpointItems = within(checkpointList).getAllByRole("listitem"); + + expect(checkpointItems).toHaveLength(checkpoints.length); + + for (const checkpoint of checkpoints) { + expect(within(checkpointList).getByText(checkpoint.location)).toBeInTheDocument(); + expect(within(checkpointList).getByText(checkpoint.carrier)).toBeInTheDocument(); + expect(within(checkpointList).getByText(checkpoint.timestamp)).toBeInTheDocument(); + } + }); + + it("renders follow-up notes with authors, timestamps, and priority badges", () => { + render(); + + const notesSection = screen.getByLabelText(/follow-up notes$/i); + const notesList = within(notesSection).getByRole("list", { name: /follow-up notes list/i }); + const noteItems = within(notesList).getAllByRole("listitem"); + + expect(noteItems).toHaveLength(followUpNotes.length); + + for (const note of followUpNotes) { + expect(within(notesList).getByText(note.author)).toBeInTheDocument(); + expect(within(notesList).getByText(note.timestamp)).toBeInTheDocument(); + expect(within(notesList).getByText(note.body)).toBeInTheDocument(); + } + }); +}); diff --git a/src/app/delivery-brief/page.tsx b/src/app/delivery-brief/page.tsx new file mode 100644 index 0000000..03370b1 --- /dev/null +++ b/src/app/delivery-brief/page.tsx @@ -0,0 +1,141 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { CheckpointCard } from "./_components/checkpoint-card"; +import { FollowUpNotesRail } from "./_components/follow-up-notes-rail"; +import { MilestoneSummaryCard } from "./_components/milestone-summary"; +import styles from "./delivery-brief.module.css"; +import { + checkpoints, + deliveryBriefOverview, + deliveryStats, + followUpNotes, + milestones, +} from "./_data/delivery-brief-data"; + +export const metadata: Metadata = { + title: "Delivery Brief", + description: + "Milestone summaries, shipment checkpoints, and follow-up notes for delivery operations.", +}; + +export default function DeliveryBriefPage() { + console.log("DeliveryBriefPage rendered"); + return ( +
+
+ {/* Hero */} +
+
+
+ + {deliveryBriefOverview.eyebrow} + +

+ {deliveryBriefOverview.title} +

+

+ {deliveryBriefOverview.description} +

+
+ +
+
+

+ Shipment +

+

+ {deliveryBriefOverview.shipmentId} +

+

+ {deliveryBriefOverview.origin} →{" "} + {deliveryBriefOverview.destination} +

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

+ {stat.label} +

+

+ {stat.value} +

+

{stat.detail}

+
+ ))} +
+ + {/* Milestones */} +
+
+
+

+ Shipment progress +

+

+ Milestone summaries +

+
+

+ Each milestone represents a key stage in the shipment lifecycle. + Track progress from label creation through final delivery. +

+
+ +
+ {milestones.map((milestone) => ( + + ))} +
+
+ + {/* Checkpoints */} +
+
+
+

+ Transit log +

+

+ Shipment checkpoints +

+
+

+ Chronological record of every scan and hand-off along the route. + Severity flags highlight delays and issues requiring attention. +

+
+ +
+ {checkpoints.map((checkpoint) => ( + + ))} +
+
+ + {/* Follow-up notes rail */} + +
+
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b21cbda..66ebaf1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -35,6 +35,7 @@ const navLinks = [ { href: "/parcel-hub", label: "Parcel Hub" }, { href: "/status-board", label: "Status Board" }, { href: "/capacity-planner", label: "Capacity Planner" }, + { href: "/delivery-brief", label: "Delivery Brief" }, ] as const; export default function RootLayout({