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