'use client'
import { useEffect, useState } from 'react'
import { cn } from '../../../utils/cn'
import { Autocomplete, type AutocompleteOption } from '../../ui/autocomplete'
import { Button } from '../../ui/button/button'
import { SplitButton } from '../../ui/button/split-button'
import { SquareAvatar } from '../../ui/square-avatar'
import { Tag } from '../../ui/tag'
import { Textarea } from '../../ui/textarea'
import {
ArrowRightUpIcon,
CheckCircleIcon,
CheckIcon,
Chevron02RightIcon,
ClockHistoryIcon,
DotsLoaderIcon,
PauseIcon,
PlayIcon,
PlusCircleIcon,
XmarkIcon,
} from '../../icons-v2-generated'
import { useTrackerClock } from './use-tracker-clock'
import type { TimeTrackerData, TimeTrackerEntry, TimeTrackerStatus } from './types'
interface CustomerAutocompleteOption extends AutocompleteOption {
imageUrl?: string
}
function renderCustomerOption(option: AutocompleteOption, isSelected: boolean) {
const { label, imageUrl } = option as CustomerAutocompleteOption
return (
)
}
export interface TimeTrackerPanelProps extends TimeTrackerData {
onClose: () => void
className?: string
}
export function TimeTrackerPanel({
status,
runningSince,
accumulatedMs,
ticketOptions,
selectedTicketId,
onSelectedTicketChange,
onTicketSearch,
ticketsLoading,
customerOptions,
selectedCustomerId,
onSelectedCustomerChange,
onCustomerSearch,
customersLoading,
customerLocked,
notes,
onNotesChange,
lastEntries,
onStart,
onPause,
onResume,
onCancel,
onSubmit,
onManualEntry,
onOpenMyTime,
onOpenMyTimeMenu,
onEntryClick,
isStarting,
isSubmitting,
onClose,
className,
}: TimeTrackerPanelProps) {
const elapsedLabel = useTrackerClock({ status, runningSince, accumulatedMs })
const [showValidationError, setShowValidationError] = useState(false)
const isRunning = status === 'tracking'
const isActive = status !== 'ready'
const hasContent = selectedTicketId != null || notes.trim() !== ''
const showFieldError = isActive && showValidationError && !hasContent
// Reset the validation flag once a session ends so the next one starts clean.
useEffect(() => {
if (status === 'ready') setShowValidationError(false)
}, [status])
const handleSubmit = () => {
if (!hasContent) {
setShowValidationError(true)
return
}
onSubmit()
}
const handleManualEntry = onManualEntry
? () => {
onClose()
onManualEntry()
}
: undefined
const handleEntryClick = onEntryClick
? (entry: TimeTrackerEntry) => {
onClose()
onEntryClick(entry)
}
: undefined
const handleOpenMyTime = onOpenMyTime
? () => {
onClose()
onOpenMyTime()
}
: undefined
const handleCancel = () => {
onClose()
onCancel()
}
const ticketAutocompleteOptions: AutocompleteOption[] = ticketOptions.map((t) => ({
label: t.label,
value: t.id,
}))
const showCustomer = !!onSelectedCustomerChange
const customerAutocompleteOptions: CustomerAutocompleteOption[] = (customerOptions ?? []).map((c) => ({
label: c.label,
value: c.id,
imageUrl: c.imageUrl,
}))
const selectedCustomer = customerAutocompleteOptions.find((o) => o.value === selectedCustomerId)
const customerStartAdornment = selectedCustomer ? (
) : undefined
const visibleEntries = lastEntries.slice(0, 3)
return (
{/* Status + timer + ticket assignment */}
{isActive && (
)}
{elapsedLabel}
{isActive && (
)}
{
if (reason === 'input') onTicketSearch?.(value)
}}
/>
{showCustomer && (
{
if (reason === 'input') onCustomerSearch?.(value)
}}
startAdornment={customerStartAdornment}
renderOption={renderCustomerOption}
/>
)}
)
}
const STATUS_TAG_SKIN = 'gap-[var(--spacing-system-xs)] font-mono font-medium uppercase tracking-tight'
function StatusTag({ status }: { status: TimeTrackerStatus }) {
if (status === 'tracking') {
return (
} label="Tracking" />
)
}
if (status === 'paused') {
return } label="Paused" />
}
return
}
interface LastEntryRowProps {
entry: TimeTrackerEntry
onClick?: (entry: TimeTrackerEntry) => void
}
function LastEntryRow({ entry, onClick }: LastEntryRowProps) {
return (
{entry.durationLabel}
{entry.dateLabel}
{entry.title}
{entry.description && (
{entry.description}
)}
)
}