All files / src/components/organizer/PartnerManagement PartnerSearch.tsx

95.23% Statements 20/21
75% Branches 3/4
100% Functions 8/8
95% Lines 19/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82                      4x 43x 43x 43x     43x 33x 6x     33x       43x 19x     43x 11x     43x 3x 3x     43x 1x         43x   43x                                                                    
import React, { useEffect, useState } from 'react';
import { TextField, InputAdornment, IconButton } from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import ClearIcon from '@mui/icons-material/Clear';
import { useTranslation } from 'react-i18next';
import { usePartnerStore } from '@/stores/partnerStore';
 
/**
 * Search input component with 300ms debouncing for partner name search.
 * Implements AC1 requirement for debounced search functionality.
 */
export const PartnerSearch: React.FC = () => {
  const { t } = useTranslation('partners');
  const { searchQuery, setSearchQuery } = usePartnerStore();
  const [localValue, setLocalValue] = useState(searchQuery);
 
  // Debounce search query updates (300ms)
  useEffect(() => {
    const timer = setTimeout(() => {
      setSearchQuery(localValue);
    }, 300);
 
    return () => clearTimeout(timer);
  }, [localValue, setSearchQuery]);
 
  // Sync local value when store value changes externally (e.g., clear filters)
  useEffect(() => {
    setLocalValue(searchQuery);
  }, [searchQuery]);
 
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setLocalValue(event.target.value);
  };
 
  const handleClear = () => {
    setLocalValue('');
    setSearchQuery('');
  };
 
  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
    Iif (event.key === 'Escape') {
      handleClear();
    }
  };
 
  const showClearButton = localValue.length > 0;
 
  return (
    <TextField
      fullWidth
      value={localValue}
      onChange={handleChange}
      onKeyDown={handleKeyDown}
      placeholder={t('searchPlaceholder')}
      data-testid="partner-search-input"
      inputProps={{
        'aria-label': t('searchAriaLabel'),
      }}
      InputProps={{
        startAdornment: (
          <InputAdornment position="start">
            <SearchIcon data-testid="search-icon" />
          </InputAdornment>
        ),
        endAdornment: showClearButton ? (
          <InputAdornment position="end">
            <IconButton
              aria-label={t('clearSearch')}
              onClick={handleClear}
              edge="end"
              size="small"
              data-testid="clear-search"
            >
              <ClearIcon />
            </IconButton>
          </InputAdornment>
        ) : null,
      }}
    />
  );
};