Unify sidebar fields: all editable fields use full-width clickable blocks
- 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:
@@ -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">
|
|
||||||
<SidebarField label="Status">
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditingStatus(true)}
|
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} />
|
<StatusBadge status={ticket.status} />
|
||||||
<ChevronDown size={12} className="text-gray-500" />
|
|
||||||
</button>
|
</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"
|
||||||
>
|
>
|
||||||
|
<p className="text-xs font-medium text-gray-500 mb-1.5">Severity</p>
|
||||||
<SeverityBadge severity={ticket.severity} />
|
<SeverityBadge severity={ticket.severity} />
|
||||||
<ChevronDown size={12} className="text-gray-500" />
|
|
||||||
</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 })}
|
|
||||||
className={selectClass}
|
|
||||||
>
|
>
|
||||||
<option value="">Unassigned</option>
|
<p className="text-xs font-medium text-gray-500 mb-1.5">Assignee</p>
|
||||||
{agentUsers.map((u) => (
|
{ticket.assignee ? (
|
||||||
<option key={u.id} value={u.id}>{u.displayName}</option>
|
<div className="flex items-center gap-1.5">
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{ticket.assignee && (
|
|
||||||
<div className="flex items-center gap-1.5 mt-1.5">
|
|
||||||
<Avatar name={ticket.assignee.displayName} size="sm" />
|
<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>
|
</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">
|
<div className="flex items-center gap-1.5">
|
||||||
<Avatar name={ticket.createdBy.displayName} size="sm" />
|
<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>
|
</div>
|
||||||
</SidebarField>
|
|
||||||
</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">
|
||||||
|
|||||||
Reference in New Issue
Block a user