All files / src/pages/inventory/dialogs/PriceChangeDialog usePriceChangeForm.ts

71.42% Statements 120/168
100% Branches 1/1
33.33% Functions 1/3
71.42% Lines 120/168

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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 1691x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x                                                                                   2x 2x 2x 2x 2x 2x               2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x  
/**
 * usePriceChangeForm - Orchestrator hook for price change workflow
 * 
 * @module dialogs/PriceChangeDialog/usePriceChangeForm
 * @description
 * Thin orchestrator composing:
 * - State management via usePriceChangeFormState
 * - Query management via usePriceChangeFormQueries
 * - Form validation via react-hook-form + Zod
 * - Error handling and submission
 * 
 * Returns unified interface for PriceChangeDialog component.
 */
 
import { useForm, type UseFormRegister, type UseFormSetError, type UseFormClearErrors, type UseFormHandleSubmit, type Control, type UseFormStateReturn, type UseFormSetValue } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useTranslation } from 'react-i18next';
import { useToast } from '../../../../context/toast';
import { changePrice } from '../../../../api/inventory/mutations';
import { priceChangeSchema, type PriceChangeForm } from '../../../../api/inventory/validation';
import { usePriceChangeFormState, type PriceChangeFormState, type PriceChangeFormStateSetters } from './usePriceChangeFormState';
import { usePriceChangeFormQueries, type PriceChangeFormQueries } from './usePriceChangeFormQueries';
 
/**
 * Complete price change form state and handlers
 */
export interface UsePriceChangeFormReturn extends PriceChangeFormState, PriceChangeFormStateSetters, PriceChangeFormQueries {
  register: UseFormRegister<PriceChangeForm>;
  control: Control<PriceChangeForm>;
  formState: UseFormStateReturn<PriceChangeForm>;
  setValue: UseFormSetValue<PriceChangeForm>;
  setError: UseFormSetError<PriceChangeForm>;
  clearErrors: UseFormClearErrors<PriceChangeForm>;
  handleSubmit: UseFormHandleSubmit<PriceChangeForm>;
  onSubmit: () => Promise<void>;
  handleClose: () => void;
}
 
/**
 * Orchestrator hook composing state, queries, and form management
 * 
 * @param isOpen - Dialog open state
 * @param onClose - Close callback
 * @param onPriceChanged - Success callback
 * @param readOnly - Demo mode flag
 */
export function usePriceChangeForm({
  isOpen,
  onClose,
  onPriceChanged = () => {},
  readOnly = false,
}: {
  isOpen: boolean;
  onClose: () => void;
  onPriceChanged?: () => void;
  readOnly?: boolean;
}): UsePriceChangeFormReturn {
  const { t } = useTranslation(['common', 'inventory', 'errors']);
  const toast = useToast();
 
  // Compose state management
  const state = usePriceChangeFormState();
 
  // Form management
  const {
    register,
    control,
    handleSubmit: rhfHandleSubmit,
    formState,
    reset,
    setValue,
    setError,
    clearErrors,
  } = useForm<PriceChangeForm>({
    resolver: zodResolver(priceChangeSchema),
    defaultValues: {
      itemId: '',
      newPrice: 0,
    },
  });
 
  // Compose query management with form effects
  const queries = usePriceChangeFormQueries(
    isOpen,
    state.selectedSupplier,
    state.itemQuery,
    state.selectedItem,
    setValue,
    clearErrors
  );
 
  /**
   * Submit form with price change
   */
  const onSubmit = rhfHandleSubmit(async (values: PriceChangeForm) => {
    if (!state.selectedItem) {
      state.setFormError(
        t('errors:inventory.selection.noItemSelected', 'Please select an item to change price.')
      );
      return;
    }

    if (readOnly) {
      state.setFormError(t('common:demoDisabled', 'This action is disabled in demo mode.'));
      return;
    }

    state.setFormError(null);
    clearErrors();

    try {
      const success = await changePrice({
        id: values.itemId,
        price: values.newPrice,
      });

      if (success) {
        toast(
          t('inventory:price.priceUpdatedTo', 'Price changed to {{price}}', {
            price: values.newPrice.toFixed(2),
          }),
          'success'
        );
        onPriceChanged();
        handleClose();
      } else {
        state.setFormError(
          t('errors:inventory.requests.failedToChangePrice', 'Failed to change price. Please try again.')
        );
      }
    } catch (error) {
      console.error('Price change error:', error);
      state.setFormError(
        t('errors:inventory.requests.failedToChangePrice', 'Failed to change price. Please try again.')
      );
    }
  });
 
  /**
   * Close dialog with complete state cleanup
   */
  const handleClose = () => {
    state.setSelectedSupplier(null);
    state.setSelectedItem(null);
    state.setItemQuery('');
    state.setFormError(null);
    reset();
    onClose();
  };
 
  return {
    // State
    ...state,
    // Queries
    ...queries,
    // Form
    register,
    control,
    formState,
    setValue,
    setError,
    clearErrors,
    handleSubmit: rhfHandleSubmit,
    // Handlers
    onSubmit,
    handleClose,
  };
}