fix: mobile ticket view - internal notes toggle (publish=2), Mail icon for email notes, showInternal state

This commit is contained in:
lorentz 2026-03-23 12:32:35 -04:00
parent 011ae559de
commit 8cd8a94412

View file

@ -4,7 +4,7 @@ import { useEffect, useState, use } from 'react';
import { useRouter } from 'next/navigation';
import {
ArrowLeft, RefreshCw, Clock, FileText, Timer, CheckCircle2,
ChevronDown, ChevronRight, User, Briefcase, AlertCircle, EyeOff, Eye,
ChevronDown, ChevronRight, User, Briefcase, AlertCircle, EyeOff, Eye, Mail,
} from 'lucide-react';
const PRIORITY_LABEL: Record<number, string> = {
@ -162,17 +162,19 @@ function TimelineCard({ item, defaultOpen = false }: { item: TimelineItem; defau
// note
const note = item.data;
const isInternal = note.publish === 1;
const isInternal = note.publish === 2;
const isSystem = item.is_system;
const bodyNodes = renderContent(note.description ?? '');
const bodyPlain = (note.description ?? '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
const iconColor = isSystem ? 'text-muted-foreground' : isInternal ? 'text-slate-400' : 'text-amber-500';
const iconBg = isSystem ? 'bg-muted' : isInternal ? 'bg-muted' : 'bg-amber-500/10';
const isEmail = note.note_type === 2;
const iconColor = isSystem ? 'text-muted-foreground' : isInternal ? 'text-slate-400' : isEmail ? 'text-violet-500' : 'text-amber-500';
const iconBg = isSystem ? 'bg-muted' : isInternal ? 'bg-muted' : isEmail ? 'bg-violet-500/10' : 'bg-amber-500/10';
const NoteIcon = isEmail ? Mail : FileText;
return (
<div className="flex gap-3">
<div className="flex flex-col items-center">
<div className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0 ${iconBg}`}>
<FileText className={`w-4 h-4 ${iconColor}`} />
<NoteIcon className={`w-4 h-4 ${iconColor}`} />
</div>
<div className="w-px flex-1 bg-border mt-1" />
</div>
@ -209,6 +211,7 @@ export default function TicketTimeline({ params }: { params: Promise<{ id: strin
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [showSystem, setShowSystem] = useState(false);
const [showInternal, setShowInternal] = useState(false);
useEffect(() => {
setLoading(true);
@ -280,20 +283,33 @@ export default function TicketTimeline({ params }: { params: Promise<{ id: strin
{/* Timeline */}
<div className="px-4 pt-5">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center justify-between mb-4 gap-3">
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Timeline</p>
<button
onClick={() => setShowSystem(s => !s)}
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors">
{showSystem ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
{showSystem ? 'Hide system' : 'Show system'}
</button>
<div className="flex items-center gap-3">
<button
onClick={() => setShowInternal(s => !s)}
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors">
{showInternal ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
{showInternal ? 'Hide internal' : 'Show internal'}
</button>
<button
onClick={() => setShowSystem(s => !s)}
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors">
{showSystem ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
{showSystem ? 'Hide system' : 'Show system'}
</button>
</div>
</div>
{timeline.length === 0 ? (
<p className="text-sm text-muted-foreground text-center py-8">No activity recorded</p>
) : (
timeline
.filter(item => showSystem || item.kind !== 'note' || !item.is_system)
.filter(item => {
if (item.kind !== 'note') return true;
if (!showSystem && item.is_system) return false;
if (!showInternal && (item.data as any).publish === 2) return false;
return true;
})
.map((item, i) => (
<TimelineCard key={i} item={item} defaultOpen={item.kind === 'note' && i === timeline.length - 1} />
))