Skip to main content

UX Standards — 21: HelpDesk Sheet

Governs: The HelpDesk slide-out panel accessible from the header for submitting and viewing support tickets. Parent rules: See 02-HEADER.md and 12-POPOUT-AND-SLIDE-OVER-WINDOWS.md first.


Overview

The HelpDesk Sheet is a right-side Sheet panel that provides in-app access to Frappe HelpDesk. It has two tabs:

  • Submit Ticket — form to create a new support ticket
  • My Tickets — list of the user's existing tickets with status badges

Triggered by clicking the Helpdesk icon (life-buoy/ticket icon) in the header right zone.


Component

packages/shared/components/layouts/header-helpdesk-sheet.tsx

Sheet Container

<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className='w-full overflow-y-auto sm:max-w-md'>
<SheetHeader>
<SheetTitle>{t('helpdesk.sheet.title')}</SheetTitle>
</SheetHeader>
<Tabs defaultValue='submit' className='mt-4'>
<TabsList className='w-full'>
<TabsTrigger value='submit' className='flex-1'>
{t('helpdesk.sheet.submit')}
</TabsTrigger>
<TabsTrigger value='tickets' className='flex-1'>
{t('helpdesk.sheet.myTickets')}
</TabsTrigger>
</TabsList>
<TabsContent value='submit' className='mt-4'>
<SubmitTicketForm onSuccess={() => onOpenChange(false)} />
</TabsContent>
<TabsContent value='tickets' className='mt-4'>
<MyTicketsList />
</TabsContent>
</Tabs>
</SheetContent>
</Sheet>
PropertyValue
Componentshadcn/ui Sheet
Sideright (default)
Widthw-full sm:max-w-md
Overflowoverflow-y-auto
Default tabsubmit (Submit Ticket)

Visual Anatomy

┌──────────────────────────────────────── [✕]
│ Helpdesk
├────────────────────────────────────────
│ [Submit Ticket] [My Tickets]
├────────────────────────────────────────

│ Subject *
│ [________________________]

│ Description *
│ [________________________]
│ [________________________]
│ [________________________]
│ [________________________]

│ Priority
│ [Medium ▾]

│ [→ Submit Ticket]

└────────────────────────────────────────

Tab 1: Submit Ticket Form

function SubmitTicketForm({ onSuccess }) {
const [subject, setSubject] = useState('');
const [description, setDescription] = useState('');
const [priority, setPriority] = useState<HDTicketPriority>('Medium');
// ...
}

Form Fields

FieldComponentRequiredDefault
SubjectInputYes''
DescriptionTextarea rows={4}Yes''
PrioritySelectNo'Medium'

Priority Options

ValueLabel
LowLow
MediumMedium (default)
HighHigh
UrgentUrgent

Submit Button

<Button type='submit' className='w-full' disabled={createTicket.isPending || !subject}>
{createTicket.isPending ? (
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
) : (
<Send className='mr-2 h-4 w-4' />
)}
{t('helpdesk.form.submit')}
</Button>
  • Full width (w-full)
  • Disabled when isPending or subject is empty
  • Loading state: Loader2 animate-spin replaces Send icon
  • On success: closes the sheet (onOpenChange(false)) + resets form

Tab 2: My Tickets List

Lists the user's tickets fetched from Frappe HelpDesk via useTickets().

Ticket Card

<div className='rounded-lg border p-3 hover:bg-muted/50'>
<div className='flex items-start justify-between gap-2'>
<span className='line-clamp-1 text-sm font-medium'>{ticket.subject}</span>
<Badge variant={statusVariant[ticket.status]}>{ticket.status}</Badge>
</div>
<div className='mt-1 flex items-center gap-2 text-xs text-muted-foreground'>
<span className='font-mono'>{ticket.name}</span>
<span>{new Date(ticket.creation).toLocaleDateString()}</span>
</div>
</div>

Status Badge Colors

StatusBadge Variant
Openinfo (blue)
Repliedwarning (amber)
Resolvedsuccess (green)
Closedgray

Loading State

<div className='flex items-center justify-center py-8'>
<Loader2 className='h-5 w-5 animate-spin text-muted-foreground' />
</div>

Empty State (No Tickets)

<div className='flex flex-col items-center justify-center gap-2 py-8'>
<Ticket className='h-8 w-8 text-muted-foreground/50' />
<p className='text-sm text-muted-foreground'>{t('helpdesk.sheet.noTickets')}</p>
</div>

Header Trigger Button

The HelpDesk button in HeaderRight:

{showHelpdesk && onHelpdeskClick && (
<Tooltip>
<TooltipTrigger asChild>
<Button variant='ghost' size='icon' className='h-9 w-9' onClick={onHelpdeskClick}>
{/* Ticket/life-buoy icon */}
</Button>
</TooltipTrigger>
<TooltipContent>{t('header.helpdesk')}</TooltipContent>
</Tooltip>
)}

The onHelpdeskClick callback is wired by the app layout to toggle open state of the sheet.


Data Hooks

import { useTickets, useCreateTicket } from '@/lib/hooks/helpdesk';
import type { HDTicketPriority, HDTicketStatus } from '@/lib/types/helpdesk';

// Fetch user's tickets
const { data: tickets, isLoading } = useTickets();

// Create new ticket
const createTicket = useCreateTicket();
await createTicket.mutateAsync({ subject, description, priority });

Types

type HDTicketPriority = 'Low' | 'Medium' | 'High' | 'Urgent';
type HDTicketStatus = 'Open' | 'Replied' | 'Resolved' | 'Closed';

Frappe HelpDesk Integration

  • Tickets are created/read through Frappe HelpDesk's REST API
  • Status changes in Frappe HelpDesk generate in-app notifications (see 20-NOTIFICATIONS-PANEL.md)
  • The name field is the Frappe document name (e.g. HD-TICKET-00012)
  • creation is a Frappe timestamp (ISO string)

i18n Keys

KeyDefault Text
helpdesk.sheet.titleHelpdesk
helpdesk.sheet.submitSubmit Ticket
helpdesk.sheet.myTicketsMy Tickets
helpdesk.sheet.noTicketsNo tickets yet
helpdesk.form.subjectSubject
helpdesk.form.descriptionDescription
helpdesk.form.priorityPriority
helpdesk.form.submitSubmit Ticket
header.helpdeskHelpdesk

Violation Checklist

  • Uses shadcn Sheet — NOT a custom overlay or Dialog
  • Width: w-full sm:max-w-md
  • SheetTitle always present (screen reader requirement)
  • Two tabs: "Submit Ticket" + "My Tickets", both flex-1 width
  • Default tab is "Submit Ticket"
  • Priority default is 'Medium'
  • Priority options: Low / Medium / High / Urgent
  • Submit button: w-full, disabled when no subject or isPending
  • Submit loading: Loader2 animate-spin replaces Send icon
  • Sheet closes on successful ticket creation
  • Status badges use correct variants: Open→info, Replied→warning, Resolved→success, Closed→gray
  • Ticket card shows subject (line-clamp-1), status badge, ticket name (mono), creation date
  • Empty state: Ticket h-8 w-8 text-muted-foreground/50 + text
  • Loading state: Loader2 animate-spin centered in py-8