ChemPal Documentation - v1.7.0
    Preparing search index...

    Variable HistoryPanelConst

    HistoryPanel: FC = ...

    HistoryPanel component that displays past search queries, when they were executed, and how many results were returned. History is persisted in chrome.storage.local (same mechanism as user settings / cache). Clicking a query re-triggers the search via the app context's pendingSearchQuery.

    // Rendered inside the drawer's tab panel:
    <HistoryPanel />
    // Displays:
    // 3 searches
    // acetone Mar 26, 2:15 PM — 12 results
    // sodium chloride Mar 25, 9:00 AM — 8 results
    // benzene Mar 24, 4:30 PM — 5 results
    const HistoryPanel: FC = () => {
    const [history, setHistory] = useState<SearchHistoryEntry[]>([]);
    const [expandedSection, setExpandedSection] = useState<'history' | 'exports' | false>('history');
    const { setPendingSearchQuery, setDrawerTab, setSearchFilters, setSelectedSuppliers, setPanel } =
    useAppContext();

    /**
    * Returns an MUI Accordion `onChange` handler that opens the given section and
    * collapses the other (single-open accordion behavior).
    * @param section - The section this handler controls.
    * @returns The Accordion change handler.
    * @source
    */
    const handleSectionChange =
    (section: 'history' | 'exports') => (_event: SyntheticEvent, isExpanded: boolean) => {
    setExpandedSection(isExpanded ? section : false);
    };

    useEffect(() => {
    const loadHistory = async () => {
    try {
    const entries = await getSearchHistory();
    setHistory(entries);
    } catch (error) {
    console.warn('Failed to load search history:', error);
    }
    };
    loadHistory();
    }, []);

    /**
    * Clears all search history entries from both local state and
    * `chrome.storage.local`, resetting the panel to its empty state.
    * @example
    * ```ts
    * handleClearHistory();
    * // chrome.storage.local.search_history => []
    * // UI shows "No search history yet."
    * ```
    * @source
    */
    const handleClearHistory = async () => {
    try {
    await clearSearchHistory();
    setHistory([]);
    } catch (error) {
    console.warn('Failed to clear search history:', error);
    }
    };

    /**
    * Re-executes a previous search by setting the pending search query
    * in the app context and closing the drawer.
    * @param query - The search term to re-run (e.g. `"sodium chloride"`)
    * @example
    * ```ts
    * handleReSearch("acetone");
    * // Sets pendingSearchQuery to "acetone"
    * // Sets drawerTab to -1 (closes drawer)
    * ```
    * @source
    */
    const handleReSearch = (entry: SearchHistoryEntry) => {
    // Restore filters that were active when this search was originally executed
    if (entry.filters) {
    setSearchFilters(entry.filters);
    }
    if (entry.selectedSuppliers) {
    setSelectedSuppliers(entry.selectedSuppliers);
    }
    setPendingSearchQuery(entry.query);
    setDrawerTab(DRAWER_INDEX.CLOSED); // Close the drawer
    setPanel?.(PANEL.RESULTS); // Navigate to results panel
    };

    const getFilterSummary = (entry: SearchHistoryEntry): string | null => {
    const parts: string[] = [];
    if (entry.filters) {
    if (entry.filters.availability.length > 0)
    parts.push(i18n('history_filter_availability', [entry.filters.availability.join(', ')]));
    if (entry.filters.country.length > 0)
    parts.push(i18n('history_filter_country', [entry.filters.country.join(', ')]));
    if (entry.filters.shippingType.length > 0)
    parts.push(i18n('history_filter_shipping', [entry.filters.shippingType.join(', ')]));
    }
    if (entry.selectedSuppliers && entry.selectedSuppliers.length > 0) {
    parts.push(i18n('history_filter_suppliers', [String(entry.selectedSuppliers.length)]));
    }
    return parts.length > 0 ? parts.join('\n') : null;
    };

    return (
    <>
    <Accordion expanded={expandedSection === 'history'} onChange={handleSectionChange('history')}>
    <StyledAccordionSummary expandIcon={<ExpandMoreIcon />}>
    <Typography>{i18n('history_section_search')}</Typography>
    </StyledAccordionSummary>
    <StyledAccordionDetailsNoPadding>
    <Box className={styles['history-panel']}>
    <Box className={styles['history-panel__header']}>
    <Typography variant="caption" color="text.secondary">
    {history.length === 1
    ? i18n('history_count_single', [String(history.length)])
    : i18n('history_count_plural', [String(history.length)])}
    </Typography>
    {history.length > 0 && (
    <Tooltip title={i18n('history_clear')}>
    <IconButton
    size="small"
    onClick={handleClearHistory}
    className={styles['history-panel__clear-btn']}
    >
    <DeleteIcon className={styles['history-panel__clear-icon']} />
    </IconButton>
    </Tooltip>
    )}
    </Box>
    {history.length === 0 ? (
    <Typography
    variant="caption"
    color="text.secondary"
    className={styles['history-panel__empty']}
    >
    {i18n('history_empty')}
    </Typography>
    ) : (
    <List dense disablePadding>
    {history.map((entry, idx) => (
    <ListItem
    key={`${entry.timestamp}-${idx}`}
    divider
    className={styles['history-panel__list-item']}
    >
    <ListItemText
    primary={
    <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
    <Link
    component="button"
    variant="body2"
    onClick={() => handleReSearch(entry)}
    sx={{ fontWeight: 'bold' }}
    className={styles['history-panel__link']}
    >
    {entry.query}
    </Link>
    {getFilterSummary(entry) && (
    <Tooltip
    title={getFilterSummary(entry)!}
    sx={{ whiteSpace: 'pre-line' }}
    >
    <FilterListIcon sx={{ fontSize: 14, color: 'text.secondary' }} />
    </Tooltip>
    )}
    </Box>
    }
    secondary={`${formatTimestamp(entry.timestamp)} — ${
    entry.resultCount === 1
    ? i18n('history_result_single', [String(entry.resultCount)])
    : i18n('history_result_plural', [String(entry.resultCount)])
    }`}
    slotProps={{
    secondary: {
    variant: 'caption',
    className: styles['history-panel__secondary-text'],
    },
    }}
    className={styles['history-panel__list-item-text']}
    />
    </ListItem>
    ))}
    </List>
    )}
    </Box>
    </StyledAccordionDetailsNoPadding>
    </Accordion>

    <Accordion expanded={expandedSection === 'exports'} onChange={handleSectionChange('exports')}>
    <StyledAccordionSummary expandIcon={<ExpandMoreIcon />}>
    <Typography>{i18n('history_section_exports')}</Typography>
    </StyledAccordionSummary>
    <StyledAccordionDetailsNoPadding>
    <ExportsPanel />
    </StyledAccordionDetailsNoPadding>
    </Accordion>
    </>
    );
    };