Unify sidebar fields: all editable fields use full-width clickable blocks
Some checks failed
Build & Push / Build Server (push) Successful in 51s
Build & Push / Build Client (push) Failing after 26s

- Status and Severity now match CTI style (full-width button, hover bg, no chevron)
- Remove 'Change routing' hint text from CTI block
- Replace Assignee dropdown with clickable block that opens a modal picker with avatars
- Add Assignee modal consistent with Status/Severity modal pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Josh Wright
2026-03-31 12:03:39 -04:00
parent 44e5e2d373
commit 402de82750

View File

@@ -87,6 +87,7 @@ export default function TicketDetail() {
const [pendingCTI, setPendingCTI] = useState({ categoryId: '', typeId: '', itemId: '' })
const [editingStatus, setEditingStatus] = useState(false)
const [editingSeverity, setEditingSeverity] = useState(false)
const [editingAssignee, setEditingAssignee] = useState(false)
const isAdmin = authUser?.role === 'ADMIN'
@@ -492,33 +493,28 @@ export default function TicketDetail() {
<p className="text-xs font-semibold text-gray-500 uppercase tracking-wide">Ticket Summary</p>
</div>
{/* Status + Severity */}
<div className="px-4 py-3 space-y-3">
<SidebarField label="Status">
{/* Status */}
<button
onClick={() => setEditingStatus(true)}
className="flex items-center gap-1.5 hover:opacity-75 transition-opacity"
className="w-full px-4 py-3 text-left hover:bg-gray-800/50 transition-colors"
>
<p className="text-xs font-medium text-gray-500 mb-1.5">Status</p>
<StatusBadge status={ticket.status} />
<ChevronDown size={12} className="text-gray-500" />
</button>
</SidebarField>
<SidebarField label="Severity">
{/* Severity */}
<button
onClick={() => setEditingSeverity(true)}
className="flex items-center gap-1.5 hover:opacity-75 transition-opacity"
className="w-full px-4 py-3 text-left hover:bg-gray-800/50 transition-colors"
>
<p className="text-xs font-medium text-gray-500 mb-1.5">Severity</p>
<SeverityBadge severity={ticket.severity} />
<ChevronDown size={12} className="text-gray-500" />
</button>
</SidebarField>
</div>
{/* CTI — one clickable unit */}
<button
onClick={startReroute}
className="w-full px-4 py-3 text-left space-y-3 hover:bg-gray-800/50 transition-colors group"
className="w-full px-4 py-3 text-left space-y-3 hover:bg-gray-800/50 transition-colors"
>
<div>
<p className="text-xs font-medium text-gray-500 mb-1">Category</p>
@@ -532,7 +528,6 @@ export default function TicketDetail() {
<p className="text-xs font-medium text-gray-500 mb-1">Issue</p>
<p className="text-sm text-gray-300">{ticket.item.name}</p>
</div>
<p className="text-xs text-blue-500 group-hover:text-blue-400 transition-colors">Change routing</p>
</button>
{/* Dates */}
@@ -550,33 +545,29 @@ export default function TicketDetail() {
)}
</div>
{/* People */}
<div className="px-4 py-3 space-y-3">
<SidebarField label="Assignee">
<select
value={ticket.assigneeId ?? ''}
onChange={(e) => patch({ assigneeId: e.target.value || null })}
className={selectClass}
{/* Assignee */}
<button
onClick={() => setEditingAssignee(true)}
className="w-full px-4 py-3 text-left hover:bg-gray-800/50 transition-colors"
>
<option value="">Unassigned</option>
{agentUsers.map((u) => (
<option key={u.id} value={u.id}>{u.displayName}</option>
))}
</select>
{ticket.assignee && (
<div className="flex items-center gap-1.5 mt-1.5">
<p className="text-xs font-medium text-gray-500 mb-1.5">Assignee</p>
{ticket.assignee ? (
<div className="flex items-center gap-1.5">
<Avatar name={ticket.assignee.displayName} size="sm" />
<span className="text-xs text-gray-400">{ticket.assignee.displayName}</span>
<span className="text-sm text-gray-300">{ticket.assignee.displayName}</span>
</div>
) : (
<p className="text-sm text-gray-500">Unassigned</p>
)}
</SidebarField>
</button>
<SidebarField label="Requester">
{/* Requester */}
<div className="px-4 py-3">
<p className="text-xs font-medium text-gray-500 mb-1.5">Requester</p>
<div className="flex items-center gap-1.5">
<Avatar name={ticket.createdBy.displayName} size="sm" />
<span className="text-xs text-gray-300">{ticket.createdBy.displayName}</span>
<span className="text-sm text-gray-300">{ticket.createdBy.displayName}</span>
</div>
</SidebarField>
</div>
</div>
@@ -653,6 +644,45 @@ export default function TicketDetail() {
</Modal>
)}
{editingAssignee && (
<Modal title="Change Assignee" onClose={() => setEditingAssignee(false)}>
<div className="space-y-2">
<button
onClick={async () => {
await patch({ assigneeId: null })
setEditingAssignee(false)
}}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg border transition-colors ${
!ticket.assigneeId
? 'border-blue-500/50 bg-blue-500/10'
: 'border-gray-700 hover:bg-gray-800'
}`}
>
<span className="text-sm text-gray-400">Unassigned</span>
{!ticket.assigneeId && <Check size={14} className="ml-auto text-blue-400" />}
</button>
{agentUsers.map((u) => (
<button
key={u.id}
onClick={async () => {
await patch({ assigneeId: u.id })
setEditingAssignee(false)
}}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg border transition-colors ${
ticket.assigneeId === u.id
? 'border-blue-500/50 bg-blue-500/10'
: 'border-gray-700 hover:bg-gray-800'
}`}
>
<Avatar name={u.displayName} size="sm" />
<span className="text-sm text-gray-300">{u.displayName}</span>
{ticket.assigneeId === u.id && <Check size={14} className="ml-auto text-blue-400" />}
</button>
))}
</div>
</Modal>
)}
{reroutingCTI && (
<Modal title="Change Routing" onClose={() => setReroutingCTI(false)} size="lg">
<div className="space-y-5">