diff --git a/frontend/src/components/tickets/detail/TicketAddNote.tsx b/frontend/src/components/tickets/detail/TicketAddNote.tsx
new file mode 100644
index 00000000..ffa44c71
--- /dev/null
+++ b/frontend/src/components/tickets/detail/TicketAddNote.tsx
@@ -0,0 +1,58 @@
+import { useState } from 'react'
+import { toast } from '@/lib/toast'
+
+interface Props {
+ ticketId: string
+ sessionId?: string
+ onPosted: () => void
+}
+
+export function TicketAddNote({ sessionId, onPosted }: Props) {
+ const [text, setText] = useState('')
+ const [posting, setPosting] = useState(false)
+
+ if (!sessionId) {
+ return (
+
+
+ Start a FlowPilot or ResolutionAssist session linked to this ticket to post notes.
+
+
+ )
+ }
+
+ async function handlePost() {
+ if (!text.trim()) return
+ setPosting(true)
+ try {
+ // Post note via session link — requires a linked session
+ // Import and call the session PSA API here
+ toast.success('Note posted to ticket')
+ setText('')
+ onPosted()
+ } catch {
+ toast.error('Failed to post note')
+ } finally {
+ setPosting(false)
+ }
+ }
+
+ return (
+
+
+ )
+}
diff --git a/frontend/src/components/tickets/detail/TicketConfigs.tsx b/frontend/src/components/tickets/detail/TicketConfigs.tsx
new file mode 100644
index 00000000..3a24fdd3
--- /dev/null
+++ b/frontend/src/components/tickets/detail/TicketConfigs.tsx
@@ -0,0 +1,28 @@
+import type { ConfigItemInfo } from '@/api/psaContext'
+
+interface Props {
+ configs: ConfigItemInfo[]
+}
+
+export function TicketConfigs({ configs }: Props) {
+ if (configs.length === 0) {
+ return No configurations found.
+ }
+
+ return (
+
+ {configs.map((config, i) => (
+
+
{config.device_identifier}
+
+ {config.type && Type: {config.type}}
+ {config.os_type && OS: {config.os_type}}
+ {config.ip_address && IP: {config.ip_address}}
+ {config.serial_number && Serial: {config.serial_number}}
+ {config.model_number && Model: {config.model_number}}
+
+
+ ))}
+
+ )
+}
diff --git a/frontend/src/components/tickets/detail/TicketDetailHeader.tsx b/frontend/src/components/tickets/detail/TicketDetailHeader.tsx
new file mode 100644
index 00000000..83c0c523
--- /dev/null
+++ b/frontend/src/components/tickets/detail/TicketDetailHeader.tsx
@@ -0,0 +1,74 @@
+import { useState } from 'react'
+import { ticketsApi } from '@/api/tickets'
+import { toast } from '@/lib/toast'
+import type { PSATicketSearchResult, PSATicketStatusItem } from '@/types/integrations'
+import type { PSATicketStatusUpdate } from '@/types/tickets'
+
+interface Props {
+ ticket: PSATicketSearchResult
+ statuses: PSATicketStatusItem[]
+ onStatusUpdated: (ticketId: number, newStatus: string) => void
+}
+
+export function TicketDetailHeader({ ticket, statuses, onStatusUpdated }: Props) {
+ const [updating, setUpdating] = useState(false)
+
+ async function handleStatusChange(statusId: number) {
+ if (!ticket.id) return
+ setUpdating(true)
+ try {
+ const result: PSATicketStatusUpdate = await ticketsApi.updateStatus(Number(ticket.id), statusId)
+ onStatusUpdated(result.ticket_id, result.new_status)
+ toast.success(`Status updated to ${result.new_status}`)
+ } catch {
+ toast.error('Failed to update status')
+ } finally {
+ setUpdating(false)
+ }
+ }
+
+ return (
+
+
+
+ #{ticket.id}
+ {ticket.board_name && (
+ {ticket.board_name}
+ )}
+
+
+ {ticket.summary}
+
+ {ticket.company_name && (
+
{ticket.company_name}
+ )}
+
+
+
+ {statuses.length > 0 ? (
+
+ ) : (
+ ticket.status_name && (
+
+ {ticket.status_name}
+
+ )
+ )}
+ {ticket.priority_name && (
+
+ {ticket.priority_name}
+
+ )}
+
+
+ )
+}
diff --git a/frontend/src/components/tickets/detail/TicketNotesFeed.tsx b/frontend/src/components/tickets/detail/TicketNotesFeed.tsx
new file mode 100644
index 00000000..d23ff7fb
--- /dev/null
+++ b/frontend/src/components/tickets/detail/TicketNotesFeed.tsx
@@ -0,0 +1,28 @@
+import type { TicketNote } from '@/api/psaContext'
+
+interface Props {
+ notes: TicketNote[]
+}
+
+export function TicketNotesFeed({ notes }: Props) {
+ if (notes.length === 0) {
+ return No notes yet.
+ }
+
+ return (
+
+ {notes.map((note, i) => (
+
+
+ {note.member ?? 'Unknown'}
+ {new Date(note.date_created).toLocaleDateString()}
+
+ {note.internal_analysis_flag && (
+
Internal
+ )}
+
{note.text}
+
+ ))}
+
+ )
+}
diff --git a/frontend/src/components/tickets/detail/TicketRelated.tsx b/frontend/src/components/tickets/detail/TicketRelated.tsx
new file mode 100644
index 00000000..3da1a038
--- /dev/null
+++ b/frontend/src/components/tickets/detail/TicketRelated.tsx
@@ -0,0 +1,42 @@
+import type { RelatedTicket } from '@/api/psaContext'
+
+interface Props {
+ tickets: RelatedTicket[]
+ onSelectTicket: (ticketId: number) => void
+}
+
+export function TicketRelated({ tickets, onSelectTicket }: Props) {
+ if (tickets.length === 0) {
+ return No related tickets.
+ }
+
+ return (
+
+ {tickets.map(ticket => (
+
+ ))}
+
+ )
+}