fix: URLs display as 'link', hide internal notes (publish=1) by default with toggle

This commit is contained in:
lorentz 2026-03-23 11:42:03 -04:00
parent f0da6b203a
commit ba493b708a

View file

@ -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>
)}