fix: URLs display as 'link', hide internal notes (publish=1) by default with toggle
This commit is contained in:
parent
f0da6b203a
commit
ba493b708a
1 changed files with 44 additions and 25 deletions
|
|
@ -32,6 +32,8 @@ import {
|
|||
|
||||
// noteType values that are system/automated (workflow rules, monitoring, auto-close, etc.)
|
||||
const SYSTEM_NOTE_TYPES = new Set([13, 91, 93, 94, 99, 101]);
|
||||
// publish=1 means Internal Users Only in Autotask
|
||||
const INTERNAL_PUBLISH = 1;
|
||||
|
||||
const URL_REGEX = /https?:\/\/[^\s<>"'\[\]()]+/g;
|
||||
|
||||
|
|
@ -47,12 +49,7 @@ function renderTextWithLinks(text: string): React.ReactNode[] {
|
|||
if (before) parts.push(before);
|
||||
}
|
||||
const url = match[0].replace(/[.,;:!?]+$/, '');
|
||||
let label: string;
|
||||
try {
|
||||
label = new URL(url).hostname.replace(/^www\./, '');
|
||||
} catch {
|
||||
label = 'link';
|
||||
}
|
||||
const label = 'link';
|
||||
parts.push(
|
||||
<a
|
||||
key={match.index}
|
||||
|
|
@ -107,6 +104,7 @@ export function TicketDetailModal({ ticketNumber, open, onOpenChange }: TicketDe
|
|||
const [timelineOpen, setTimelineOpen] = useState(false);
|
||||
const [timelineFetched, setTimelineFetched] = useState(false);
|
||||
const [showSystem, setShowSystem] = useState(false);
|
||||
const [showInternal, setShowInternal] = useState(false);
|
||||
|
||||
const fetchTicketDetails = async () => {
|
||||
setLoading(true);
|
||||
|
|
@ -117,6 +115,7 @@ export function TicketDetailModal({ ticketNumber, open, onOpenChange }: TicketDe
|
|||
setTimelineFetched(false);
|
||||
setTimelineOpen(false);
|
||||
setShowSystem(false);
|
||||
setShowInternal(false);
|
||||
try {
|
||||
const response = await fetch(`/api/tickets/by-number/${encodeURIComponent(ticketNumber)}`);
|
||||
if (response.ok) {
|
||||
|
|
@ -176,14 +175,14 @@ export function TicketDetailModal({ ticketNumber, open, onOpenChange }: TicketDe
|
|||
|
||||
const visibleTimeline = useMemo(
|
||||
() =>
|
||||
showSystem
|
||||
? timeline
|
||||
: timeline.filter(
|
||||
(item) =>
|
||||
item.kind === 'time' ||
|
||||
!SYSTEM_NOTE_TYPES.has(item.data.noteType)
|
||||
),
|
||||
[timeline, showSystem]
|
||||
timeline.filter((item) => {
|
||||
if (item.kind === 'time') return true;
|
||||
const n = item.data;
|
||||
if (!showSystem && SYSTEM_NOTE_TYPES.has(n.noteType)) return false;
|
||||
if (!showInternal && n.publish === INTERNAL_PUBLISH) return false;
|
||||
return true;
|
||||
}),
|
||||
[timeline, showSystem, showInternal]
|
||||
);
|
||||
|
||||
const systemCount = useMemo(
|
||||
|
|
@ -194,6 +193,14 @@ export function TicketDetailModal({ ticketNumber, open, onOpenChange }: TicketDe
|
|||
[timeline]
|
||||
);
|
||||
|
||||
const internalCount = useMemo(
|
||||
() =>
|
||||
timeline.filter(
|
||||
(item) => item.kind === 'note' && item.data.publish === INTERNAL_PUBLISH && !SYSTEM_NOTE_TYPES.has(item.data.noteType)
|
||||
).length,
|
||||
[timeline]
|
||||
);
|
||||
|
||||
const totalHours = timeEntries.reduce((sum, e) => sum + (e.hoursWorked || 0), 0);
|
||||
|
||||
const getStatusBadge = (status: number, label?: string | null) => {
|
||||
|
|
@ -386,17 +393,29 @@ export function TicketDetailModal({ ticketNumber, open, onOpenChange }: TicketDe
|
|||
|
||||
{!timelineLoading && timelineFetched && (
|
||||
<>
|
||||
{/* System notes toggle */}
|
||||
{systemCount > 0 && (
|
||||
<div className="flex items-center justify-end mb-2">
|
||||
<button
|
||||
onClick={() => setShowSystem((v) => !v)}
|
||||
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<Bot className="h-3 w-3" />
|
||||
{showSystem ? 'Hide' : 'Show'} system notes
|
||||
<Badge variant="outline" className="text-xs px-1.5 py-0">{systemCount}</Badge>
|
||||
</button>
|
||||
{/* Toggles for system / internal notes */}
|
||||
{(systemCount > 0 || internalCount > 0) && (
|
||||
<div className="flex items-center justify-end gap-3 mb-2">
|
||||
{internalCount > 0 && (
|
||||
<button
|
||||
onClick={() => setShowInternal((v) => !v)}
|
||||
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<UserCircle className="h-3 w-3" />
|
||||
{showInternal ? 'Hide' : 'Show'} internal notes
|
||||
<Badge variant="outline" className="text-xs px-1.5 py-0">{internalCount}</Badge>
|
||||
</button>
|
||||
)}
|
||||
{systemCount > 0 && (
|
||||
<button
|
||||
onClick={() => setShowSystem((v) => !v)}
|
||||
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<Bot className="h-3 w-3" />
|
||||
{showSystem ? 'Hide' : 'Show'} system notes
|
||||
<Badge variant="outline" className="text-xs px-1.5 py-0">{systemCount}</Badge>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue