'use client' import * as React from 'react' import { cn } from '../../utils/cn' import { Button } from '../ui/button' import { TabSelector } from '../ui/tab-selector' import { ChatsIcon } from '../icons-v2-generated/communication/chats-icon' import { PlusCircleIcon } from '../icons-v2-generated/signs-and-symbols/plus-circle-icon' import { AlertCircleIcon, Refresh01RightIcon } from '../icons-v2-generated' import { MingoChatHistory, MingoChatHistorySkeleton } from './mingo-chat-history' import { ChatListEmptyState } from './chat-list-empty-state' import type { DialogItem } from './types/component.types' // ============================================================================= // Types // ============================================================================= /** Ownership scope of the rail's dialog list — the current user's chats only, * or every admin's chats in the tenant. */ export type MingoHistoryScope = 'my' | 'all' export interface MingoHistoryRailProps { /** Dialogs to list, assumed already sorted newest-first by the host. */ dialogs: ReadonlyArray /** Currently-open dialog id (highlighted). */ activeDialogId?: string /** Open a dialog. */ onSelectDialog?: (id: string) => void /** Start a fresh chat — clears the open conversation so the chat block shows * the welcome. Rendered as the pinned "Start New Chat" button above the list, * in every state (including the no-chats empty state). */ onNewChat?: () => void /** Request rename — enables the row "Rename chat" action. */ onRequestRename?: (dialog: DialogItem) => void /** Request archive — enables the row "Archive chat" action. */ onRequestArchive?: (dialog: DialogItem) => void /** Request a shareable link — enables the row "Copy chat link" action. */ onRequestCopyLink?: (dialog: DialogItem) => void /** Ownership scope shown as a two-state "My Chats / All Chats" selector * between "Start New Chat" and the list. Rendered only when BOTH `scope` * and `onScopeChange` are provided; the host owns the state and refilters * the `dialogs` it passes in. */ scope?: MingoHistoryScope /** Scope selector change handler — see `scope`. */ onScopeChange?: (scope: MingoHistoryScope) => void /** Current server-side search term. Drives the list's "No chats found" * empty state; the search INPUT itself lives in the panel header now, not * in the rail body. */ searchQuery?: string /** Whether more dialogs remain (cursor pagination). */ hasMore?: boolean /** True while the next page is loading. */ isLoadingMore?: boolean /** Fetch the next page. */ onLoadMore?: () => void /** True while the FIRST page of dialogs is still loading. Renders a skeleton * so the rail doesn't flash the empty state before the list arrives. */ isLoadingHistory?: boolean /** The dialog-list load FAILED with nothing cached — renders an error + retry * block instead of the empty state. */ loadError?: boolean /** Retry handler for the `loadError` state. */ onRetry?: () => void /** Appended to the root element. */ className?: string } // ============================================================================= // Component // ============================================================================= /** * MingoHistoryRail — the persistent "Current Chats" left column of the split * (wide) Mingo layout (Figma 113:60931 / 113:63630). * * The dialog history that the stacked layout renders inline in the Mingo empty * state is hoisted here into a fixed-width rail beside the chat block. On top * sits a pinned "Start New Chat" button (always shown when `onNewChat` is * given); below it the grouped Today / Yesterday / Older list * (`MingoChatHistory`). With no chats the list area collapses to a centred * empty state; loading / error states mirror the stacked empty state so the * two layouts stay consistent. */ export function MingoHistoryRail({ dialogs, activeDialogId, onSelectDialog, onNewChat, onRequestRename, onRequestArchive, onRequestCopyLink, scope, onScopeChange, searchQuery, hasMore = false, isLoadingMore = false, onLoadMore, isLoadingHistory = false, loadError = false, onRetry, className, }: MingoHistoryRailProps) { const hasSearch = !!searchQuery?.trim() // The list (vs. the empty state) shows whenever there are chats OR an active // search is running — a no-match query must keep the search bar mounted // rather than flash the "No Current Chats" state. const hasList = dialogs.length > 0 || hasSearch return (
{/* Pinned "Start New Chat" — always on top, in every state. It used to be hidden in the wide rail while the user had no chats (the chat block's composer being the empty-state entry), but that made the rail's primary action vanish exactly when it's most needed, and shifted the layout the moment the first chat appeared. */} {onNewChat && ( )} {/* Two-state ownership scope — "My Chats" / "All Chats". Stays visible in the empty state too: an empty "My Chats" list must still offer the switch to "All Chats" (host filters, so the empty state can be scope-induced). Hidden while the load failed — retry is the only useful action there. */} {scope && onScopeChange && !loadError ? ( onScopeChange(value as MingoHistoryScope)} items={[ { id: 'my', label: 'My Chats' }, { id: 'all', label: 'All Chats' }, ]} /> ) : null} {loadError ? (

Couldn’t load your chats

Something went wrong reaching the server. Try again.

{onRetry && ( )}
) : isLoadingHistory ? ( ) : hasList ? ( ) : ( // Figma 113:60939 — no-chats empty state. } title="No Current Chats" description="Previous Mingo sessions will show here" /> )}
) }