[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/graphql/warehousesNames.graphql from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```graphql
query warehousesNames($ids: [ID!]!) {
  warehousesList(ids: $ids)
    @context(provider: "vtex.logistics-carrier-graphql") {
    name
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/graphql/categories.gql from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```graphql
query categories($searchKey: String!, $page: Int!, $pageSize: Int!) {
  categories(term: $searchKey, page: $page, pageSize: $pageSize, active: true) {
    items {
      id
      name
    }
    paging {
      page
      perPage
      pages
      total
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/hooks/useQuantityFilters.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useMemo, useCallback } from 'react'
import { useIntl } from 'react-intl'
import { useRuntime } from 'vtex.render-runtime'
import type {
  InventoryProductsPageFilterInput,
  QuantityFilterInput,
} from 'vtex.inventory-graphql'

import QuantityFilterPopup from '../components/Filters/QuantityFilterPopup'
import type {
  Statement,
  StatementProps,
  Verb,
  FilterQuery,
} from './useProductFilters'
import { filtersMessages as messages, columnsMessages } from '../utils/intl'

export type QuantityFilters = Pick<
  InventoryProductsPageFilterInput,
  'quantity' | 'reservedQuantity' | 'availableQuantity'
>

export type QuantitySubject = keyof QuantityFilters

export function useQuantityFilters(initialState?: QuantityFilters) {
  const { query } = useRuntime()
  const intl = useIntl()

  const getFromQuery = (queryValue?: string, initialValue?: number | null) =>
    queryValue != null && !Number.isNaN(+queryValue)
      ? +queryValue
      : initialValue

  const filter: QuantityFilters = {
    quantity: {
      min: getFromQuery(query?.minQuantity, initialState?.quantity?.min),
      max: getFromQuery(query?.maxQuantity, initialState?.quantity?.max),
    },
    reservedQuantity: {
      min: getFromQuery(
        query?.minReservedQuantity,
        initialState?.reservedQuantity?.min
      ),
      max: getFromQuery(
        query?.maxReservedQuantity,
        initialState?.reservedQuantity?.max
      ),
    },
    availableQuantity: {
      min: getFromQuery(
        query?.minAvailableQuantity,
        initialState?.availableQuantity?.min
      ),
      max: getFromQuery(
        query?.maxAvailableQuantity,
        initialState?.availableQuantity?.max
      ),
    },
  }

  const createQuantityFilterOption = useCallback(
    ({
      label,
      labelGender = 'male',
    }: {
      label: string
      labelGender?: string
    }) => {
      const dropdownOptions: Array<[Verb, string]> = [
        ['greaterOrEqual', intl.formatMessage(messages.greaterOrEqualDropdown)],
        ['lessOrEqual', intl.formatMessage(messages.lessOrEqualDropdown)],
        ['between', intl.formatMessage(messages.betweenDropdown)],
      ]

      return {
        label,
        renderFilterLabel: (
          statement?: Statement<QuantitySubject, QuantityFilterInput>
        ) => {
          if (statement?.verb === 'greaterOrEqual') {
            return intl.formatMessage(messages.greaterOrEqualLabel, {
              value: statement?.object?.min,
            })
          }

          if (statement?.verb === 'lessOrEqual') {
            return intl.formatMessage(messages.lessOrEqualLabel, {
              value: statement?.object?.max,
            })
          }

          if (statement?.verb === 'between') {
            return intl.formatMessage(messages.betweenLabel, {
              min: statement?.object?.min,
              max: statement?.object?.max,
            })
          }

          return intl.formatMessage(messages.all, {
            gender: labelGender,
          })
        },
        verbs: dropdownOptions.map(([option, title]) => ({
          label: title,
          value: option,
          object: (props: StatementProps<QuantityFilterInput>) => (
            <QuantityFilterPopup {...props} verb={option} />
          ),
        })),
      }
    },
    [intl]
  )

  const options = useMemo(
    () => ({
      quantity: createQuantityFilterOption({
        label: intl.formatMessage(columnsMessages.lastUpdate),
        labelGender: 'female',
      }),
      reservedQuantity: createQuantityFilterOption({
        label: intl.formatMessage(columnsMessages.reserved),
      }),
      availableQuantity: createQuantityFilterOption({
        label: intl.formatMessage(columnsMessages.available),
      }),
    }),
    [intl, createQuantityFilterOption]
  )

  const statements: Array<Statement<QuantitySubject, QuantityFilterInput>> = (
    Object.entries(filter) as Array<[QuantitySubject, QuantityFilterInput]>
  )
    .filter(([_, value]) => value?.min != null || value?.max != null)
    .map(([key, value]) => ({
      subject: key,
      verb:
        value?.min != null
          ? value?.max != null
            ? 'between'
            : 'greaterOrEqual'
          : 'lessOrEqual',
      error: null,
      object: value,
    }))

  function handleChange(
    statement: Statement<QuantitySubject, QuantityFilterInput>
  ) {
    // Can't pass null to setQuery because it will still put the property in
    // the url, but with an empty value
    const minQuery =
      (statement.verb === 'greaterOrEqual' || statement.verb === 'between') &&
      statement?.object?.min != null
        ? statement.object.min
        : undefined

    const maxQuery =
      (statement.verb === 'lessOrEqual' || statement.verb === 'between') &&
      statement?.object?.max != null
        ? statement.object.max
        : undefined

    const newQuery: Partial<FilterQuery> = {}

    if (statement.subject === 'quantity') {
      newQuery.minQuantity = minQuery
      newQuery.maxQuantity = maxQuery
    } else if (statement.subject === 'reservedQuantity') {
      newQuery.minReservedQuantity = minQuery
      newQuery.maxReservedQuantity = maxQuery
    } else if (statement.subject === 'availableQuantity') {
      newQuery.minAvailableQuantity = minQuery
      newQuery.maxAvailableQuantity = maxQuery
    }

    return newQuery
  }

  return {
    filter,
    statements,
    handleChange,
    options,
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/hooks/useArrowNavigation.ts from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable vtex/prefer-early-return */
import { useState, useEffect } from 'react'

export default function useArrowNavigation(
  ref: React.RefObject<HTMLElement> | null,
  optionsLength: number,
  initialSelectedOptionIndex: number
): [number, (index: number) => void] {
  const [selectedOptionIndex, setSelectedOptionIndex] = useState(
    initialSelectedOptionIndex
  )

  const handleKeyDown = (e: KeyboardEvent) => {
    if (
      ref?.current &&
      e.target instanceof Node &&
      ref.current.contains(e.target)
    ) {
      let index

      switch (e.key) {
        case 'ArrowUp':
          e.preventDefault()
          index = Math.max(selectedOptionIndex - 1, initialSelectedOptionIndex)

          return setSelectedOptionIndex(index)

        case 'ArrowDown':
          e.preventDefault()
          index = Math.min(selectedOptionIndex + 1, optionsLength - 1)

          return setSelectedOptionIndex(index)

        default:
      }
    }
  }

  useEffect(() => {
    document.addEventListener('keydown', handleKeyDown)

    return () => {
      document.removeEventListener('keydown', handleKeyDown)
    }
  })

  return [selectedOptionIndex, setSelectedOptionIndex]
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/hooks/useWarehouseFilter.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useMemo, useState } from 'react'
import { useIntl } from 'react-intl'
import { useQuery } from 'react-apollo'
import { useRuntime } from 'vtex.render-runtime'
import type { InventoryProductsPageFilterInput } from 'vtex.inventory-graphql'
import type { QueryWarehousesListArgs } from 'vtex.logistics-carrier-graphql'

import WarehouseFilterPopup from '../components/Filters/WarehouseFilterPopup'
import type { Statement, StatementProps, Verb } from './useProductFilters'
import type { QueriedWarehouse, QueriedWarehousesNames } from '../types'
import {
  commonMessages,
  filtersMessages as messages,
  columnsMessages,
} from '../utils/intl'
import WAREHOUSES_NAMES from '../graphql/warehousesNames.graphql'

export type WarehouseFilter = Pick<
  InventoryProductsPageFilterInput,
  'warehouseIds'
>

export type WarehouseSubject = keyof WarehouseFilter

export type WarehousesMap = Map<
  QueriedWarehouse['id'],
  QueriedWarehouse['name']
>

export function useWarehouseFilter(initialState?: WarehouseFilter) {
  const { query } = useRuntime()
  const intl = useIntl()

  const filter: WarehouseFilter = {
    warehouseIds: query?.warehouseIds?.split(',') ?? initialState?.warehouseIds,
  }

  const [checked, setChecked] = useState<WarehousesMap>(new Map())
  // When loading url, we don't have warehouses names yet
  const [checkedWithoutName, setCheckedWithoutName] = useState(
    filter.warehouseIds ?? []
  )

  const { loading, error, refetch } = useQuery<
    { warehousesList: QueriedWarehousesNames },
    QueryWarehousesListArgs
  >(WAREHOUSES_NAMES, {
    notifyOnNetworkStatusChange: true,
    variables: {
      ids: checkedWithoutName,
    },
    skip: checkedWithoutName.length === 0,
    onCompleted: (data) =>
      setChecked((prevChecked) => {
        const newChecked = new Map(prevChecked)

        data?.warehousesList?.forEach((value, index) => {
          newChecked.set(checkedWithoutName[index], value.name)
        })

        return newChecked
      }),
  })

  const urlError =
    checkedWithoutName.length > 0 && error
      ? {
          message: intl.formatMessage(messages.warehousesNamesQueryError),
          refetch,
          dismiss: () => setCheckedWithoutName([]),
        }
      : null

  const options = useMemo(
    () => ({
      warehouseIds: {
        label: intl.formatMessage(columnsMessages.warehouse),
        renderFilterLabel: (
          statement?: Statement<WarehouseSubject, WarehousesMap>
        ) => {
          if (loading) return intl.formatMessage(commonMessages.loading)
          if (!statement?.object || statement.object.size === 0) {
            return intl.formatMessage(messages.all, { gender: 'male' })
          }

          return Array.from(statement.object.values()).join(', ')
        },
        verbs: [
          {
            label: '',
            value: 'includes' as Verb,
            object: (props: StatementProps<WarehousesMap>) => (
              <WarehouseFilterPopup {...props} />
            ),
          },
        ],
      },
    }),
    [intl, loading]
  )

  const statements: Array<Statement<WarehouseSubject, WarehousesMap>> = (
    Object.entries(filter) as Array<[WarehouseSubject, string[]]>
  )
    .filter(([_, value]) => Boolean(value?.length))
    .map(([key]) => ({
      subject: key,
      verb: 'includes',
      error: null,
      object: checked,
    }))

  function handleChange(statement: Statement<WarehouseSubject, WarehousesMap>) {
    setChecked(statement?.object ?? new Map())

    return {
      warehouseIds: statement?.object?.size
        ? Array.from(statement.object.keys()).join(',')
        : undefined,
    }
  }

  return {
    filter,
    statements,
    handleChange,
    options,
    urlError,
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/hooks/useClickOutside.ts from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useEffect } from 'react'

export default function useClickOutside(
  ref: React.RefObject<HTMLElement> | null,
  onClickOutside: (e: Event) => void
) {
  const handleClickOutside = (e: Event) => {
    if (
      ref?.current &&
      e.target instanceof Node &&
      !ref.current.contains(e.target)
    ) {
      onClickOutside(e)
    }
  }

  useEffect(() => {
    document.addEventListener('mousedown', handleClickOutside)

    return () => {
      document.removeEventListener('mousedown', handleClickOutside)
    }
  })
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/hooks/useExponentialBackoff.ts from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useState, useEffect, useCallback, useRef } from 'react'
import type { ApolloError } from 'apollo-client'
import type Maybe from 'graphql/tsutils/Maybe'

const INITIAL_DELAY = 200
const MAX_DELAY = 5_000
const COEFFICIENT = 2

export function useExponentialBackoff({
  queryLoading,
  queryError,
  refetch,
}: {
  queryLoading: boolean
  queryError: Maybe<ApolloError>
  refetch: () => void
}) {
  const [loading, setLoading] = useState(queryLoading)
  const [error, setError] = useState<Maybe<ApolloError>>(queryError)
  const [retryCount, setRetryCount] = useState(0)
  const [waitingTimeout, setWaitingTimeout] = useState(false)
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const timerId: any = useRef(0)

  const stopBackoff = useCallback(() => {
    setLoading(false)
    setError(null)
    setRetryCount(0)
    clearTimeout(timerId.current)
    setWaitingTimeout(false)
  }, [setLoading, setError, setRetryCount, timerId])

  useEffect(() => {
    if (loading && !queryLoading) {
      if (!queryError) {
        stopBackoff()
      } else if (!waitingTimeout) {
        setError(queryError)
        setWaitingTimeout(true)
        timerId.current = setTimeout(() => {
          setWaitingTimeout(false)
          refetch()
          setRetryCount((prevRetryCount) => prevRetryCount + 1)
        }, Math.min(MAX_DELAY, INITIAL_DELAY * COEFFICIENT ** retryCount))
      }
    } else if (!loading && queryLoading) {
      setLoading(true)
    }

    // Effect should only be called when query state changes. Tried
    // doing it with useQuery's onCompleted and onError but there were
    // too many edge cases.
  }, [queryLoading])

  return { loading, error, stopBackoff }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/hooks/useProductFilters.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { useIntl } from 'react-intl'
import { useRuntime } from 'vtex.render-runtime'
import type { QuantityFilterInput } from 'vtex.inventory-graphql'
import type { usePagination } from 'vtex.logistics-carrier-commons'
import { useAmplitude } from 'vtex.logistics-carrier-commons'
import { useEffect } from 'react'

import type { QuantityFilters, QuantitySubject } from './useQuantityFilters'
import { useQuantityFilters } from './useQuantityFilters'
import type {
  WarehouseFilter,
  WarehouseSubject,
  WarehousesMap,
} from './useWarehouseFilter'
import { useWarehouseFilter } from './useWarehouseFilter'
import { filtersMessages as messages } from '../utils/intl'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type DefaultFunctionType = (...args: any[]) => any

export type HandleVariablesChangeFunction<
  T extends DefaultFunctionType = DefaultFunctionType
> = (
  onVariablesChange: T,
  onCancelChange?: () => void
) => (...args: Parameters<T>) => ReturnType<T> | void

export interface FilterQuery {
  minQuantity: QuantityFilterInput['min']
  maxQuantity: QuantityFilterInput['max']
  minReservedQuantity: QuantityFilterInput['min']
  maxReservedQuantity: QuantityFilterInput['max']
  minAvailableQuantity: QuantityFilterInput['min']
  maxAvailableQuantity: QuantityFilterInput['max']
  warehouseIds?: string
}

export type Verb = 'lessOrEqual' | 'greaterOrEqual' | 'between' | 'includes'

export interface Statement<S, T> {
  label?: string
  error?: unknown
  verb: Verb | null
  subject?: S
  object?: T
}

export interface StatementProps<T> {
  value: T | null
  error: unknown
  onChange: (newValue: T | null) => void
}

type FilterStatements = Array<
  | Statement<QuantitySubject, QuantityFilterInput>
  | Statement<WarehouseSubject, WarehousesMap>
>

function useFilterUsageEvents() {
  const { query = {} } = useRuntime()
  const { logEvent } = useAmplitude()

  useEffect(() => {
    if (query.minQuantity != null || query.maxQuantity != null) {
      logEvent('Inventory Filtered', {
        'Filter Type': 'Last Update',
      })
    }
  }, [logEvent, query.maxQuantity, query.minQuantity])

  useEffect(() => {
    if (
      query.minReservedQuantity != null ||
      query.maxReservedQuantity != null
    ) {
      logEvent('Inventory Filtered', {
        'Filter Type': 'Reserved',
      })
    }
  }, [logEvent, query.minReservedQuantity, query.maxReservedQuantity])

  useEffect(() => {
    if (
      query.minAvailableQuantity != null ||
      query.maxAvailableQuantity != null
    ) {
      logEvent('Inventory Filtered', {
        'Filter Type': 'Available',
      })
    }
  }, [logEvent, query.minAvailableQuantity, query.maxAvailableQuantity])

  useEffect(() => {
    if (query.warehouseIds != null) {
      logEvent('Inventory Filtered', {
        'Filter Type': 'Warehouse',
      })
    }
  }, [logEvent, query.warehouseIds])
}

interface ProductFiltersHookParameters {
  resetPaginationVariables: ReturnType<
    typeof usePagination
  >['resetPaginationVariables']
  onStatementsChange?: HandleVariablesChangeFunction<
    (statements: FilterStatements) => void
  >
  initialState?: QuantityFilters & WarehouseFilter
}

export function useProductFilters({
  resetPaginationVariables,
  onStatementsChange,
  initialState,
}: ProductFiltersHookParameters) {
  const { setQuery } = useRuntime()

  const intl = useIntl()

  const quantityFilters = useQuantityFilters(initialState)
  const warehouseFilter = useWarehouseFilter(initialState)

  function handleStatementsChange(statements: FilterStatements = []) {
    const newQuery: FilterQuery = {
      minQuantity: undefined,
      maxQuantity: undefined,
      minReservedQuantity: undefined,
      maxReservedQuantity: undefined,
      minAvailableQuantity: undefined,
      maxAvailableQuantity: undefined,
      warehouseIds: undefined,
    }

    for (const statement of statements) {
      if (
        statement.subject === 'quantity' ||
        statement.subject === 'reservedQuantity' ||
        statement.subject === 'availableQuantity'
      ) {
        Object.assign(newQuery, quantityFilters.handleChange(statement))
      } else if (statement.subject === 'warehouseIds') {
        Object.assign(newQuery, warehouseFilter.handleChange(statement))
      }
    }

    setQuery({ ...newQuery, ...resetPaginationVariables })
  }

  const filter = { ...warehouseFilter.filter, ...quantityFilters.filter }

  const props = {
    submitFilterLabel: intl.formatMessage(messages.ok),
    alwaysVisibleFilters: Object.keys(filter),
    statements: [...warehouseFilter.statements, ...quantityFilters.statements],
    onChangeStatements: onStatementsChange
      ? onStatementsChange(handleStatementsChange)
      : handleStatementsChange,
    options: { ...warehouseFilter.options, ...quantityFilters.options },
  }

  useFilterUsageEvents()

  return { props, filter, urlError: warehouseFilter.urlError }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/index.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useState } from 'react'
import { pathOr } from 'ramda'
import {
  Input,
  /* Checkbox, */ FilterBar,
  ButtonWithIcon,
} from 'vtex.styleguide'
import Download from '@vtex/styleguide/lib/icon/Download'
import type { ApolloError } from 'apollo-client'
import { useIntl } from 'react-intl'

import { useQuantityFilters } from '../../hooks/useQuantityFilters'
import { useWarehouseFilter } from '../../hooks/useWarehouseFilter'
import { filtersMessages, appMessages } from '../../utils/intl'
import CategorySelect from './CategorySelect'

export default function Filters() {
  const [statements, setStatements] = useState([])
  const intl = useIntl()

  const clear = intl.formatMessage(filtersMessages.clear)
  const all = intl.formatMessage(filtersMessages.all)
  const any = intl.formatMessage(filtersMessages.any)
  const is = intl.formatMessage(filtersMessages.is)
  const isNot = intl.formatMessage(filtersMessages.isNot)
  const contains = intl.formatMessage(filtersMessages.contains)
  const apply = intl.formatMessage(filtersMessages.apply)
  const exportIcon = <Download />

  const quantityFilters = useQuantityFilters()
  const warehouseFilter = useWarehouseFilter()

  function simpleInputVerbs() {
    return [
      {
        label: is,
        value: '=',
        object: (props: any) => <SimpleInputObject {...props} />,
      },
      {
        label: isNot,
        value: '!=',
        object: (props: any) => <SimpleInputObject {...props} />,
      },
      {
        label: contains,
        value: 'contains',
        object: (props: any) => <SimpleInputObject {...props} />,
      },
    ]
  }

  const onclickExport = () => {
    console.info('onclickExport')
    console.info('statements', statements)
  }

  const filter = { ...warehouseFilter.filter, ...quantityFilters.filter }

  return (
    <div>
      <FilterBar
        alwaysVisibleFilters={[
          'categoryId',
          'productId',
          'productName',
          ...Object.keys(filter),
        ]}
        statements={statements}
        onChangeStatements={(s: any) => setStatements(s)}
        clearAllFiltersButtonLabel={clear}
        submitFilterLabel={apply}
        options={{
          categoryId: {
            label: 'Category',
            renderFilterLabel: pathOr(all, ['object', 'label']),
            verbs: [
              {
                label: '',
                value: is,
                object: function VerbObject({
                  onChange,
                  value,
                  ...props
                }: StatementObjectProps<Statement | null>) {
                  return (
                    <CategorySelect
                      {...props}
                      onClear={() => {
                        value = null
                        onChange(null)
                      }}
                      value={value}
                      onSelect={onChange}
                      focused
                    />
                  )
                },
              },
            ],
          },
          productId: {
            label: 'Product ID',
            renderFilterLabel: (st: any) => {
              if (!st || !st.object) {
                // you should treat empty object cases only for alwaysVisibleFilters
                return any
              }

              return `${
                st.verb === '=' ? is : st.verb === '!=' ? isNot : contains
              } ${st.object}`
            },
            verbs: simpleInputVerbs(),
          },
          productName: {
            label: 'Product Name',
            renderFilterLabel: (st: any) => {
              if (!st || !st.object) {
                // you should treat empty object cases only for alwaysVisibleFilters
                return any
              }

              return `${
                st.verb === '=' ? is : st.verb === '!=' ? isNot : contains
              } ${st.object}`
            },
            verbs: simpleInputVerbs(),
          },
          ...warehouseFilter.options,
          ...quantityFilters.options,
        }}
      />
      <ButtonWithIcon icon={exportIcon} onClick={onclickExport}>
        {`${intl.formatMessage(appMessages.exportButton)}`}
      </ButtonWithIcon>
    </div>
  )
}

function SimpleInputObject({ value, onChange }: any) {
  return (
    <Input
      value={value || ''}
      onChange={(e: any) => onChange(e.target.value)}
    />
  )
}

// function StatusSelectorObject({ value, onChange }: any) {
//   const initialValue = {
//     'Window to cancelation': true,
//     Canceling: true,
//     Canceled: true,
//     'Payment pending': true,
//     'Payment approved': true,
//     'Ready for handling': true,
//     'Handling shipping': true,
//     'Ready for invoice': true,
//     Invoiced: true,
//     Complete: true,
//     ...(value || {}),
//   }

//   const toggleValueByKey = (key: any) => {
//     return {
//       ...(value || initialValue),
//       [key]: value ? !value[key] : false,
//     }
//   }

//   return (
//     <div>
//       {Object.keys(initialValue).map((opt, index) => {
//         return (
//           <div className="mb3" key={`class-statment-object-${opt}-${index}`}>
//             <Checkbox
//               checked={value ? value[opt] : initialValue[opt]}
//               id={`status-${opt}`}
//               label={opt}
//               name="status-checkbox-group"
//               onChange={() => {
//                 const newValue = toggleValueByKey(`${opt}`)

//                 onChange(newValue)
//               }}
//               value={opt}
//             />
//           </div>
//         )
//       })}
//     </div>
//   )
// }

interface StatementObjectProps<T, U = SelectOption | null> {
  value: T
  onChange: (value: U) => void
  error?: ApolloError
  keepValueInput?: boolean
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/FilterSelect/index.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import classNames from 'classnames'
import React, { useState, useRef, useEffect } from 'react'
import { useIntl, defineMessages } from 'react-intl'

import useClickOutside from '../../../hooks/useClickOutside'
import useArrowNavigation from '../../../hooks/useArrowNavigation'
import type { Props as OptionProps } from './Option'
import Option from './Option'
import SelectInput from './SelectInput'
import styles from './styles.css'
import { filtersMessages } from '../../../utils/intl'

const messages = defineMessages({
  noOptionsFound: filtersMessages.noOptionsFound,
})

interface InputProps
  extends Omit<React.HTMLProps<HTMLInputElement>, 'onChange' | 'value'> {
  onClear: () => void
  onChange: (term: string) => void
  value: string
  loading: boolean
}

interface OptionsProps {
  renderOption?: (props: OptionProps, index: number) => React.ReactNode
  value: SelectOption[]
  icon?: React.ReactElement
  onSelect: (option: SelectOption) => void
  onPopoverOpened?: (popover: HTMLDivElement | null) => void
  onPopoverClosed?: () => void
  keepValueInput?: boolean
}

export interface Props {
  input: InputProps
  options: OptionsProps
  testId?: string
}

type OptionRef = HTMLElement | null

const NO_SELECTED_OPTION_INDEX = 0

const FilterSelect: React.FC<Props> = ({
  input: { value, onClear, onChange, loading, ref, ...inputProps },
  options: {
    onSelect,
    value: options,
    renderOption,
    icon,
    onPopoverOpened,
    onPopoverClosed,
    keepValueInput,
  },
  testId,
}) => {
  const [term, setTerm] = useState(value || '')
  const [showPopover, setShowPopover] = useState(false)
  const containerRef = useRef<HTMLDivElement>(null)
  const inputRef = useRef<HTMLInputElement | null>(null)
  const popoverRef = useRef<HTMLDivElement>(null)
  const optionsRefs = useRef<OptionRef[]>([])
  const { formatMessage } = useIntl()

  const handleClear = () => {
    setShowPopover(false)
    setTerm('')
    onClear()
  }

  useEffect(() => {
    if (!keepValueInput && term) {
      handleClear()
      setShowPopover(true)
    }
  }, [])

  const [selectedOptionIndex, setSelectedOptionIndex] = useArrowNavigation(
    containerRef,
    options.length,
    NO_SELECTED_OPTION_INDEX
  )

  useClickOutside(containerRef, () => setShowPopover(false))

  const popoverOpened = showPopover

  useEffect(() => {
    if (popoverOpened && onPopoverOpened) {
      onPopoverOpened(popoverRef.current)
    } else if (!popoverOpened && onPopoverClosed) {
      onPopoverClosed()
    }
  }, [popoverOpened])

  useEffect(() => {
    const optionRef = optionsRefs.current[selectedOptionIndex]

    if (optionRef) {
      optionRef.focus()
      if (inputRef.current) {
        inputRef.current.focus()
      }
    }
  }, [selectedOptionIndex])

  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key !== 'Enter') {
      return
    }

    const selectedOption = options[selectedOptionIndex]

    setTerm(selectedOption.label)
    onSelect(selectedOption)
    setSelectedOptionIndex(NO_SELECTED_OPTION_INDEX)
    setShowPopover(false)
  }

  const handleTermChange = (newTerm = '') => {
    if (!showPopover) {
      setShowPopover(true)
    }

    setTerm(newTerm)
    if (onChange) {
      onChange(newTerm)
    }
  }

  const handleOptionClick = (option: SelectOption) => {
    setTerm(option.label)
    onSelect(option)
    setShowPopover(false)
  }

  const getOptionProps = (option: SelectOption, index: number) => ({
    key: `${option.label}-${option.value}-${index}`,
    selected: index === selectedOptionIndex,
    value: option,
    searchTerm: term,
    roundedBottom: index === options.length - 1,
    icon: typeof option !== 'string' && icon ? icon : null,
    // eslint-disable-next-line @typescript-eslint/no-shadow
    ref: (ref: HTMLElement | null) => {
      optionsRefs.current[index] = ref
    },
    onFocus: () => {
      setSelectedOptionIndex(index)
    },
    onMouseEnter: () => {
      setSelectedOptionIndex(index)
    },
    onClick: () => {
      handleOptionClick(option)
    },
  })

  const renderOptions = (): React.ReactElement | React.ReactElement[] => (
    <div className="flex flex-column">
      {options.length > 0
        ? options.map(
            renderOption
              ? (option, index) =>
                  renderOption(getOptionProps(option, index), index)
              : (option, index) => <Option {...getOptionProps(option, index)} />
          )
        : !inputProps.disabled && (
            <Option
              key="disabled-option"
              searchTerm={term}
              roundedBottom
              selected={false}
              testId="filter-no-results-option"
              disabled
              value={{
                value: '',
                label: formatMessage(messages.noOptionsFound),
              }}
            />
          )}
    </div>
  )

  const popoverClasses = classNames(
    styles.popover,
    'absolute w-100 overflow-y-auto z-1',
    {
      'br br2 ba b--muted-3 bg-base shadow-5': popoverOpened,
    }
  )

  return (
    <div ref={containerRef} className="flex flex-column w-100">
      <SelectInput
        testId={testId}
        {...inputProps}
        ref={(node) => {
          inputRef.current = node
          if (typeof ref === 'function') {
            ref(node)
          } else if (ref) {
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            ref.current = node
          }
        }}
        value={term}
        onKeyDown={handleKeyDown}
        onFocus={() => setShowPopover(true)}
        onClear={handleClear}
        onChange={handleTermChange}
        loading={loading}
      />
      <div className="relative">
        <div ref={popoverRef} className={popoverClasses}>
          {popoverOpened ? renderOptions() : null}
        </div>
      </div>
    </div>
  )
}

export default FilterSelect

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/FilterSelect/styles.css from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```css
.popover {
  max-height: 14rem;
}

.border-medium {
  border-width: medium;
}

.select-input:hover {
  border-color: hsl(0, 0%, 70%);
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/FilterSelect/Option.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import classnames from 'classnames'
import { omit } from 'ramda'
import type { ForwardRefExoticComponent, PropsWithoutRef } from 'react'
import React, { forwardRef } from 'react'

export type Props = {
  roundedBottom?: boolean
  icon?: React.ReactNode
  searchTerm: string
  value: SelectOption
  selected: boolean
  key: string
  disabled?: boolean
  testId?: string
} & Omit<Partial<React.HTMLProps<HTMLButtonElement | HTMLDivElement>>, 'value'>

const Option: ForwardRefExoticComponent<PropsWithoutRef<Props>> = forwardRef<
  HTMLButtonElement,
  Props
>((props, ref) => {
  const {
    icon,
    selected,
    roundedBottom,
    searchTerm,
    disabled,
    testId,
    ...buttonProps
  } = props

  const value = props.value.label

  const renderOptionValue = (): string | React.ReactElement => {
    const index = value.toLowerCase().indexOf(searchTerm.toLowerCase())

    if (index === -1 || !searchTerm.length) {
      return value
    }

    const prefix = value.substring(0, index)
    const match = value.substr(index, searchTerm.length)
    const suffix = value.substring(index + match.length)

    return (
      <span className="truncate">
        <span className="fw7">{prefix}</span>
        {match}
        <span className="fw7">{suffix}</span>
      </span>
    )
  }

  const buttonClasses = classnames('bn w-100 tl pa4 f6', {
    'br2 br--bottom': roundedBottom,
    'bg-muted-5': selected,
    'c-disabled': disabled,
    'bg-base': !selected,
    pointer: !disabled,
  })

  return (
    <button
      {...omit(['value', 'key', 'type'], buttonProps)}
      ref={ref}
      type="button"
      disabled={disabled}
      className={buttonClasses}
      data-testid={testId}
    >
      <span className="h1 flex items-center">
        <span className="mr3 c-muted-2 flex pt1">{icon}</span>
        {disabled ? (
          <span className="truncate">{value}</span>
        ) : (
          renderOptionValue()
        )}
      </span>
    </button>
  )
})

export default Option

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/CategorySelect/index.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { ApolloError } from 'apollo-client'

import withQuery from '../InfiniteSelect/withQuery'
import Select from './Select'
import categoriesQuery from '../../../graphql/categories.gql'

type Props = Omit<
  StatementObjectProps<Statement | null>,
  'onChange' | 'error'
> & {
  focused?: boolean
  testId?: string
  onClear?: () => void
  onSelect: (option: SelectOption) => void
  keepValueInput?: boolean
}

export default withQuery<Props>(Select)({
  query: categoriesQuery,
  resultName: 'categories',
})

export interface StatementObjectProps<T, U = SelectOption | null> {
  value: T
  onChange: (value: U) => void
  error?: ApolloError
  keepValueInput?: boolean
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/CategorySelect/Select.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FC } from 'react'
import React from 'react'

import { filtersMessages } from '../../../utils/intl'
import type { Props as InfiniteSelectProps } from '../InfiniteSelect'
import InfiniteSelect from '../InfiniteSelect'

const messages = {
  loading: filtersMessages.loading,
  placeholder: filtersMessages.placeholder,
  error: filtersMessages.error,
}

const Select: FC<Omit<InfiniteSelectProps, 'messages' | 'intl'>> = (props) => (
  <InfiniteSelect {...props} messages={messages} />
)

export default Select

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/CustomInputSearch/index.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FC, ChangeEvent } from 'react'
import React, { useState } from 'react'
import { IconClear, IconSearch } from 'vtex.styleguide'
import classNames from 'classnames'

import styles from './styles.css'

const ICON_SIZE = 14

interface Props {
  value: string
  placeholder: string
  onChange: (event: ChangeEvent<HTMLInputElement>) => void
  onClear: () => void
  onSubmit: () => void
}

const CustomInputSearch: FC<Props> = (props) => {
  const [focused, setFocused] = useState(false)

  return (
    <div
      className={classNames('w-100 h-small flex items-center bb bw1', {
        'b--muted-2': focused,
        'b--muted-4 hover-b--muted-3': !focused,
      })}
    >
      <span className="flex flex-none c-muted-2 mr3">
        <IconSearch size={ICON_SIZE} />
      </span>
      <input
        className={`bn outline-0 c-on-base bg-base w-100 ${styles.input} ${styles.hideDecorators} ${styles.noAppearance}`}
        value={props.value}
        placeholder={props.placeholder}
        onChange={props.onChange}
        onKeyUp={(e) => e.key === 'Enter' && props.onSubmit()}
        onFocus={() => setFocused(true)}
        onBlur={() => setFocused(false)}
        type="search"
      />
      {props.value && (
        <button
          onClick={props.onClear}
          className="flex flex-none mr3 pointer c-muted-3 br4 pa0 bn outline-transparent bg-base"
        >
          <IconClear size={ICON_SIZE} />
        </button>
      )}
    </div>
  )
}

export default CustomInputSearch

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/CustomInputSearch/styles.css from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```css
/* removes edge clear button on input */
.hideDecorators::-ms-clear {
  display: none;
}

.noAppearance {
  appearance: none;
}

/* placeholders are using c-muted-1's value */
.input::-webkit-input-placeholder {
  /* Chrome/Opera/Safari */
  color: #979899;
}
.input::-moz-placeholder {
  /* Firefox 19+ */
  color: #979899;
}
.input:-ms-input-placeholder {
  /* IE 10+ */
  color: #979899;
}
.input:-moz-placeholder {
  /* Firefox 18- */
  color: #979899;
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/WarehouseFilterPopup/index.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FC } from 'react'
import React, { useState, useRef, useEffect, useCallback } from 'react'
import { useQuery } from 'react-apollo'
import { useIntl, FormattedMessage } from 'react-intl'
import { Checkbox, Spinner, IconWarning } from 'vtex.styleguide'
import { useDebouncedSearch } from 'vtex.logistics-carrier-commons'
import type { QueryWarehousesArgs } from 'vtex.logistics-carrier-graphql'
import classNames from 'classnames'
import type { ApolloError } from 'apollo-client'

import type { StatementProps } from '../../../hooks/useProductFilters'
import type { QueriedWarehouse, QueriedWarehousesPage } from '../../../types'
import type { WarehousesMap } from '../../../hooks/useWarehouseFilter'
import { useExponentialBackoff } from '../../../hooks/useExponentialBackoff'
import { filtersMessages as messages } from '../../../utils/intl'
import styles from './styles.css'
import CustomInputSearch from '../CustomInputSearch'
import WAREHOUSES from '../../../graphql/warehouses.graphql'

type Props = StatementProps<WarehousesMap>

const INITIAL_PAGE = 1
const PER_PAGE = 7
const SPINNER_SIZE = 24

const WarehouseFilterPopup: FC<Props> = ({ value: checked, onChange }) => {
  const intl = useIntl()
  const containerRef = useRef<HTMLDivElement>(null)
  const [showTopBorder, setShowTopBorder] = useState(false)

  const [keyword, setKeyword] = useState('')
  const { term, handleSearchChange, handleSearchClear, handleSearchSubmit } =
    useDebouncedSearch({
      term: keyword,
      onChange: ({ keyword: k }) => setKeyword(k),
    })

  const {
    loading: firstQueryLoading,
    error: firstQueryError,
    data,
    refetch,
    fetchMore: queryFetchMore,
  } = useQuery<{ warehouses: QueriedWarehousesPage }, QueryWarehousesArgs>(
    WAREHOUSES,
    {
      notifyOnNetworkStatusChange: true,
      variables: {
        page: INITIAL_PAGE,
        perPage: PER_PAGE,
        keyword,
      },
    }
  )

  const [fetchingMore, setFetchingMore] = useState<{
    loading: boolean
    error: ApolloError | null
  }>({ loading: false, error: null })

  const fetchMore = useCallback(() => {
    setFetchingMore({ loading: true, error: null })
    queryFetchMore({
      variables: {
        page: data?.warehouses?.paging
          ? data.warehouses.paging.page + 1
          : INITIAL_PAGE,
      },
      updateQuery: (prev, { fetchMoreResult }) => {
        if (!fetchMoreResult) {
          return prev
        }

        return {
          warehouses: {
            ...fetchMoreResult.warehouses,
            items: [
              ...prev.warehouses.items,
              ...fetchMoreResult.warehouses.items,
            ],
          },
        }
      },
    })
      .catch((error) => setFetchingMore({ loading: false, error }))
      .finally(() =>
        setFetchingMore((prevFetchingMore) => ({
          loading: false,
          error: prevFetchingMore.error,
        }))
      )
  }, [setFetchingMore, queryFetchMore, data])

  const {
    loading: queryLoading,
    error,
    stopBackoff,
  } = useExponentialBackoff({
    queryLoading: firstQueryLoading || fetchingMore.loading,
    queryError: firstQueryError ?? fetchingMore.error,
    refetch: () => {
      if (firstQueryError) {
        refetch()
      } else if (fetchingMore.error) {
        fetchMore()
      }
    },
  })

  // Need this to keep previously checked warehouses at the top,
  // and recently checked ones where they are
  const [checkedOnTop, setCheckedOnTop] = useState(checked)

  useEffect(() => {
    setCheckedOnTop(checked)
    stopBackoff()
    setFetchingMore({ loading: false, error: null })
  }, [term])

  const searchLoading =
    term !== keyword ||
    ((firstQueryLoading || firstQueryError) &&
      !fetchingMore.loading &&
      !fetchingMore.error)

  const loading = searchLoading ?? queryLoading

  const hasMorePages =
    searchLoading ||
    (data?.warehouses?.paging &&
      data.warehouses.paging.page < data.warehouses.paging.pages)

  const checkboxRenderer = (warehouse: QueriedWarehouse) => (
    <div className="h2 mw5 nl6 ph6 nr2 flex items-center" key={warehouse.id}>
      <Checkbox
        checked={checked?.has(warehouse.id)}
        id={warehouse.id}
        onChange={() => {
          const newValue = new Map(checked ?? [])

          if (checked?.has(warehouse.id)) {
            newValue.delete(warehouse.id)
          } else {
            newValue.set(warehouse.id, warehouse.name)
          }

          onChange(newValue)
        }}
      />
      <label htmlFor={warehouse.id} className="w-100">
        <div className="pl3 truncate pointer">{warehouse.name}</div>
      </label>
    </div>
  )

  const handleScroll = () => {
    const reachedLoadingPoint =
      containerRef?.current &&
      containerRef.current.scrollTop + containerRef.current.clientHeight >
        containerRef.current.scrollHeight - SPINNER_SIZE

    if (containerRef?.current?.scrollTop && !showTopBorder) {
      setShowTopBorder(true)
    } else if (!containerRef?.current?.scrollTop && showTopBorder) {
      setShowTopBorder(false)
    }

    if (!loading && hasMorePages && reachedLoadingPoint) {
      fetchMore()
    }
  }

  return (
    <>
      <CustomInputSearch
        value={term}
        onChange={handleSearchChange}
        onClear={() => {
          containerRef.current?.scrollTo({ top: 0 })
          handleSearchClear()
        }}
        placeholder={intl.formatMessage(messages.search)}
        onSubmit={handleSearchSubmit}
      />
      <div
        className={classNames(
          'overflow-y-auto overflow-x-hidden mt6 pb3 nh6 ph6 nb4 bb bw1 b--light-gray',
          styles.scrollShadow,
          { bt: showTopBorder }
        )}
        style={{ maxHeight: '14.25rem' }} // Height of 7 items + borders
        ref={containerRef}
        onScroll={handleScroll}
      >
        {checkedOnTop &&
          Array.from(checkedOnTop, ([id, name]) =>
            checkboxRenderer({ id, name })
          )}
        {!searchLoading &&
          data?.warehouses?.items
            ?.filter((warehouse) => !checkedOnTop?.has(warehouse.id))
            ?.map(checkboxRenderer)}
        {hasMorePages && (
          <div className="h2 flex items-center justify-center">
            <Spinner size={SPINNER_SIZE} />
          </div>
        )}
        {/* Check for hasMorePages to avoid frame where data has loaded but
            error hasn't been updated so it would appear */}
        {hasMorePages && error && (
          <div className="flex mt3 pt5 pb3 nh6 ph6 bt b--light-gray bw1">
            <span className="c-warning mr3">
              <IconWarning />
            </span>
            <span className="lh-solid">
              <FormattedMessage {...messages.warehousesQueryError} />
            </span>
          </div>
        )}
      </div>
    </>
  )
}

export default WarehouseFilterPopup

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/WarehouseFilterPopup/styles.css from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```css
.scrollShadow {
  background: linear-gradient(
      rgba(255, 255, 255, 1),
      rgba(255, 255, 255, 0) 5%,
      rgba(255, 255, 255, 0) 95%,
      rgba(255, 255, 255, 1)
    ),
    linear-gradient(
      rgba(0, 0, 0, 0.05),
      rgba(0, 0, 0, 0) 2%,
      rgba(0, 0, 0, 0) 98%,
      rgba(0, 0, 0, 0.05)
    );
  background-attachment: local, scroll;
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/QuantityFilterPopup/index.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FC } from 'react'
import React, { useState, useEffect } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { Input } from 'vtex.styleguide'
import type { QuantityFilterInput } from 'vtex.inventory-graphql'

import type { StatementProps, Verb } from '../../../hooks/useProductFilters'
import { filtersMessages as messages } from '../../../utils/intl'
import { isInteger, convertInteger } from '../../../utils/inputs'

type Props = StatementProps<QuantityFilterInput> & { verb: Verb }

const QuantityFilterPopup: FC<Props> = ({ verb, value, onChange }) => {
  const intl = useIntl()
  const showMinInput = verb === 'greaterOrEqual' || verb === 'between'
  const showMaxInput = verb === 'lessOrEqual' || verb === 'between'
  const [quantity, setQuantity] = useState(value)
  // In order to allow negative numbers
  const [minInputValue, setMinInputValue] = useState<string | null>(null)
  const [maxInputValue, setMaxInputValue] = useState<string | null>(null)
  const validQuantity =
    verb !== 'between'
      ? quantity?.min != null || quantity?.max != null
      : quantity?.min != null &&
        quantity?.max != null &&
        quantity.min <= quantity.max

  useEffect(() => {
    function updateStatement() {
      if (validQuantity) {
        onChange(quantity)
      } else {
        // Disable "OK" button
        onChange(null)
      }
    }

    updateStatement()
    // Should not update when `onChange` updates
  }, [quantity, validQuantity])

  useEffect(() => {
    function updateStatementByVerb() {
      if (verb === 'greaterOrEqual') {
        setMaxInputValue(null)
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        setQuantity((prevQuantity: any) =>
          prevQuantity?.min == null
            ? null
            : {
                min: prevQuantity?.min,
                max: null,
              }
        )
      } else if (verb === 'lessOrEqual') {
        setMinInputValue(null)
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        setQuantity((prevQuantity: any) =>
          prevQuantity?.max == null
            ? null
            : {
                min: null,
                max: prevQuantity?.max,
              }
        )
      }
    }

    updateStatementByVerb()
  }, [verb])

  function handleMinChange(event: React.ChangeEvent<HTMLInputElement>) {
    if (!isInteger(event.target.value)) {
      return
    }

    setMinInputValue(event.target.value)
    setQuantity({
      ...quantity,
      min: convertInteger(event.target.value),
    })
  }

  function handleMaxChange(event: React.ChangeEvent<HTMLInputElement>) {
    if (!isInteger(event.target.value)) {
      return
    }

    setMaxInputValue(event.target.value)
    setQuantity({
      ...quantity,
      max: convertInteger(event.target.value),
    })
  }

  return (
    <>
      {showMinInput && (
        <Input
          placeholder={intl.formatMessage(messages.minimumPlaceholder)}
          value={minInputValue ?? quantity?.min ?? ''}
          onChange={handleMinChange}
        />
      )}
      {showMinInput && showMaxInput && (
        <div className="mv3 ml5 pl1">
          <FormattedMessage {...messages.and} />
        </div>
      )}
      {showMaxInput && (
        <Input
          placeholder={intl.formatMessage(messages.maximumPlaceholder)}
          value={maxInputValue ?? quantity?.max ?? ''}
          onChange={handleMaxChange}
          errorMessage={
            !validQuantity &&
            quantity?.min != null &&
            quantity?.max != null &&
            intl.formatMessage(messages.betweenInvalid)
          }
        />
      )}
    </>
  )
}

export default QuantityFilterPopup

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/InfiniteSelect/index.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
/* eslint-disable no-shadow */
/* eslint-disable vtex/prefer-early-return */
import type { ApolloError } from 'apollo-client'
import debounce from 'lodash.debounce'
import { pathOr } from 'ramda'
import React, { useRef, useEffect, useState, useMemo, useCallback } from 'react'
import { useIntl } from 'react-intl'

import FilterSelect from '../FilterSelect'
import Option from '../FilterSelect/Option'
import type { QueryProps, SelectItem } from './withQuery'
import { usePolling, POLLING } from './hooks'
import type { Props as ErrorOptionProps } from './ErrorOption'
import ErrorOption from './ErrorOption'
import type { Pagination, PaginatedData } from '../../../utils/pagination'

export type Props<T = SelectItem> = QueryProps<T> & {
  focused?: boolean
  value: Statement | null
  onClear?: () => void
  keepValueInput?: boolean
  onSelect: (option: SelectOption) => void
  messages: { [key: string]: { id: string } }
  testId?: string
}

interface Data<T> {
  [queryName: string]: undefined | PaginatedData<T>
}

const DEBOUNCE = 500

const Select = <T extends SelectItem = SelectItem>({
  messages,
  loading: queryLoading,
  data,
  resultName,
  error: queryError,
  fetchMore,
  refetch,
  networkStatus,
  variables,
  pagination,
  setPagination,
  onClear,
  onSelect,
  focused,
  keepValueInput,
  testId,
}: Props<T>) => {
  const { formatMessage } = useIntl()
  const [readyToFilter, setReadyToFilter] = useState(false)
  const [preventLoading, setPreventLoading] = useState(false)
  const [fetchMoreError, setFetchMoreError] = useState<ApolloError | undefined>(
    undefined
  )

  const observer = useRef<IntersectionObserver | null>(null)
  const loadingRef = useRef<HTMLSpanElement | null>(null)
  const popoverRef = useRef<HTMLDivElement | null>(null)
  const inputRef = useRef<HTMLInputElement | null>(null)
  const fetchingMore = useRef(false)

  const {
    reset,
    failed,
    status: pollingStatus,
  } = usePolling<Data<T>, Pagination>({
    loading: queryLoading,
    error: queryError ?? fetchMoreError,
    tryAgain: data?.[resultName] ? fetchMore : refetch,
    variables,
    networkStatus,
    pollingMs: POLLING.MS,
    pollingLimit: POLLING.LIMIT,
  })

  // Clear the fetchMoreError when it's done
  useEffect(() => {
    if (
      data?.[resultName] &&
      fetchMoreError &&
      !pollingStatus.error &&
      !pollingStatus.loading
    ) {
      setFetchMoreError(undefined)
    }
  }, [data, fetchMoreError, pollingStatus, resultName])

  const error = queryError ?? pollingStatus.error
  const loading = queryLoading || pollingStatus.loading || (error && !failed)

  const canFetchMore =
    pathOr(0, [resultName, 'paging', 'page'], data) <
    pathOr(0, [resultName, 'paging', 'pages'], data)

  const showLoading = loading ?? canFetchMore

  const options = pathOr<T[]>([], [resultName, 'items'], data)
    .map((item) => ({
      value: item.id,
      label: item.name,
    }))
    .concat(
      showLoading || error
        ? [
            {
              value: '',
              label: formatMessage(failed ? messages.error : messages.loading),
            },
          ]
        : []
    )

  const handleIntersection = useCallback(
    // eslint-disable-next-line @typescript-eslint/no-shadow
    ([entry]: IntersectionObserverEntry[], observer: IntersectionObserver) => {
      if (
        entry.isIntersecting &&
        options.length > 1 &&
        !loading &&
        !fetchingMore.current &&
        !fetchMoreError
      ) {
        observer.disconnect()
        fetchingMore.current = true
        fetchMore()
          .catch(setFetchMoreError)
          .finally(() => {
            fetchingMore.current = false
          })
      }
    },
    [fetchMore, fetchMoreError, loading, options.length]
  )

  const handleChange = useMemo(
    () =>
      debounce((searchKey: string) => {
        setPagination({ searchKey, page: 1 })
      }, DEBOUNCE),
    [setPagination]
  )

  const connectObserver = useCallback(
    (popover: HTMLDivElement | null) => {
      popoverRef.current = popover
      if (popover && loadingRef.current) {
        observer.current = new IntersectionObserver(handleIntersection, {
          root: popover,
          threshold: 0.3,
        })
        observer.current.observe(loadingRef.current)
      }
    },
    [handleIntersection]
  )

  const disconnectObserver = () => {
    if (observer.current) {
      observer.current.disconnect()
    }
  }

  useEffect(() => {
    if (!loading) {
      // Reset states
      setPreventLoading(false)
      setReadyToFilter(false)
      // Need this to correctly detect infinite scroll after filtering
      disconnectObserver()
      if (popoverRef.current) {
        connectObserver(popoverRef.current)
      }
    }
  }, [loading, popoverRef, loadingRef, connectObserver])

  useEffect(() => {
    if (inputRef.current && focused) {
      inputRef.current.focus()
    }
  }, [focused])

  return (
    <FilterSelect
      testId={testId}
      input={{
        ref: inputRef,
        value: pagination.searchKey,
        onClear: () => {
          setPagination({ searchKey: '' })
          if (onClear) {
            onClear()
          }
        },
        onChange: (term: string) => {
          setReadyToFilter(term !== pagination.searchKey)
          handleChange(term)
        },
        loading: !preventLoading && (loading || readyToFilter),
        placeholder: formatMessage(messages.placeholder),
      }}
      options={{
        value: options,
        onSelect: (option: SelectOption) => {
          setPreventLoading(true)
          setPagination({ searchKey: option.label })
          onSelect(option)
        },
        onPopoverOpened: connectObserver,
        onPopoverClosed: () => {
          disconnectObserver()
          popoverRef.current = null
        },
        keepValueInput,
        renderOption: function Renderer(props, index) {
          if (index !== options.length - 1 || (!showLoading && !error)) {
            return <Option {...props} />
          }

          if (showLoading && !failed) {
            return (
              <div key={props.key} className="pa4">
                <span ref={loadingRef} className="i c-muted-3 f6 ml3">
                  {props.value.label}
                </span>
              </div>
            )
          }

          return (
            <ErrorOption
              {...(props as ErrorOptionProps)}
              onClick={() => {
                reset()
                if (inputRef.current) {
                  inputRef.current.focus()
                }
              }}
              filterInput={inputRef.current}
            />
          )
        },
      }}
    />
  )
}

export default Select

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/InfiniteSelect/withQuery.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { ApolloQueryResult, ApolloError } from 'apollo-client'
import type { DocumentNode } from 'graphql'
import { pathOr } from 'ramda'
import type { ComponentType } from 'react'
import React, { useRef, useEffect } from 'react'
import type { QueryResult } from 'react-apollo'
import { Query } from 'react-apollo'

import type {
  Pagination,
  PaginatedData,
  SetPagination,
} from '../../../utils/pagination'
import { usePagination } from '../../../utils/pagination'

export interface StatementObjectProps<T, U = SelectOption | null> {
  value: T
  onChange: (value: U) => void
  error?: ApolloError
  keepValueInput?: boolean
}

export interface SelectItem {
  id: string
  name: string
}

interface Data<T> {
  [key: string]: PaginatedData<T>
}

type Variables = Pagination

export type QueryProps<T> = Omit<
  QueryResult<Data<T>, Variables>,
  'fetchMore'
> & {
  resultName: string
  pagination: Pagination
  setPagination: SetPagination
  fetchMore: () => Promise<ApolloQueryResult<Data<T>>>
}

type ComponentProps<T> = T & Partial<StatementObjectProps<Statement | null>>

interface Props {
  query: DocumentNode
  resultName: string
}

const withQuery =
  <T, QData = SelectItem>(
    WrappedComponent: ComponentType<T & QueryProps<QData>>
  ) =>
  ({ query, resultName }: Props) =>
  (props: ComponentProps<T>) => {
    const [pagination, setPagination] = usePagination({
      page: 1,
      pageSize: 15,
      searchKey: pathOr('', ['value', 'label'], props),
    })

    return (
      <Query<Data<QData>, Variables>
        query={query}
        variables={pagination}
        notifyOnNetworkStatusChange
      >
        {({ fetchMore, variables, ...data }) => {
          // Apparently this workaround is needed :/
          const variablesRef = useRef(variables)

          useEffect(() => {
            variablesRef.current = pagination
          }, [])

          const handleFetchMore = () =>
            fetchMore({
              variables: {
                ...variablesRef.current,
                page: variablesRef.current.page + 1,
              },
              updateQuery: (
                prev,
                { fetchMoreResult, variables: newVariables }
              ) => {
                const getItems = pathOr<QData[]>([], [resultName, 'items'])

                variablesRef.current = newVariables ?? variablesRef.current

                return (
                  prev && {
                    ...prev,
                    [resultName]: {
                      ...prev[resultName],
                      items: getItems(prev).concat(getItems(fetchMoreResult)),
                      ...(fetchMoreResult?.resultName?.paging && {
                        paging: fetchMoreResult.resultName.paging,
                      }),
                    },
                  }
                )
              },
            })

          return (
            <WrappedComponent
              {...props}
              {...data}
              resultName={resultName}
              variables={variables}
              fetchMore={handleFetchMore}
              pagination={pagination}
              setPagination={setPagination}
            />
          )
        }}
      </Query>
    )
  }

export default withQuery

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/InfiniteSelect/ErrorOption.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
/* eslint-disable vtex/prefer-early-return */
import classnames from 'classnames'
import type { ForwardRefExoticComponent } from 'react'
import React, { useEffect, useRef, forwardRef } from 'react'
import { defineMessages, FormattedMessage } from 'react-intl'
import { IconWarning, Button } from 'vtex.styleguide'

import { filtersMessages } from '../../../utils/intl'

export type Props = {
  filterInput: HTMLInputElement | null
  roundedBottom?: boolean
  icon?: React.ReactNode
  searchTerm: string
  value: SelectOption
  selected: boolean
  key: string
} & Pick<React.HTMLProps<HTMLButtonElement>, 'onClick'>

export interface SelectOption {
  value: string
  label: string
}

const messages = defineMessages({
  tryAgain: filtersMessages.tryAgain,
})

const ErrorOption: ForwardRefExoticComponent<Props> = forwardRef<
  HTMLDivElement,
  Props
>((props, ref) => {
  const { roundedBottom, value: optionValue, onClick, filterInput } = props
  const value = optionValue.label

  const buttonRef = useRef<HTMLButtonElement | null>(null)

  const containerClasses = classnames('w-100 tl pa4 f6 bt b--muted-4 bg-base', {
    'br2 br--bottom': roundedBottom,
  })

  useEffect(() => {
    if (buttonRef.current) {
      buttonRef.current.focus()
      if (filterInput) {
        filterInput.focus()
      }
    }
  }, [filterInput])

  return (
    <div ref={ref} className={containerClasses}>
      <div className="ph2 pt2">
        <span className="flex t-mini c-muted-1 pb3">
          <span className="mr3 flex c-warning">
            <IconWarning />
          </span>
          <span className="w-100">{value}</span>
        </span>
        <Button
          ref={buttonRef}
          onClick={onClick}
          size="small"
          variation="tertiary"
          block
        >
          <FormattedMessage {...messages.tryAgain} />
        </Button>
      </div>
    </div>
  )
})

export default ErrorOption

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/InfiniteSelect/hooks.ts from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable no-shadow */
import type { ApolloQueryResult } from 'apollo-client'
import { useEffect, useState, useRef } from 'react'
import type { QueryResult } from 'react-apollo'

type UsePolling<TData, TVariables> = Pick<
  QueryResult<TData, TVariables>,
  'loading' | 'error' | 'variables' | 'networkStatus'
> & {
  pollingLimit: number
  pollingMs: number
  tryAgain: (variables?: TVariables) => Promise<ApolloQueryResult<TData>>
}

export const APOLLO_STATUS = {
  LOADING: 1,
  REFETCHING: 4,
  POLLING: 5,
}

export const POLLING = {
  LIMIT: 5,
  MS: 200,
}

export const usePolling = <TData, TVariables>({
  loading: queryLoading,
  error: queryError,
  networkStatus,
  variables,
  tryAgain,
  pollingLimit,
}: UsePolling<TData, TVariables>) => {
  const [error, setError] = useState(queryError)
  const [loading, setLoading] = useState(queryLoading)
  const [attempts, setAttempts] = useState(1)
  const refetchPromise = useRef<Promise<ApolloQueryResult<TData>> | null>(null)

  const status = {
    loading,
    error,
  }

  const refetch = () => {
    if (!refetchPromise.current) {
      setLoading(true)
      refetchPromise.current = tryAgain(variables)
        .then((result) => {
          setError(undefined)
          setAttempts(1)

          return result
        })
        // eslint-disable-next-line @typescript-eslint/no-shadow
        .catch((error) => {
          setError(error)
          setAttempts(attempts + 1)

          return error
        })
        .finally(() => {
          refetchPromise.current = null
          setLoading(false)
        })
    }

    return refetchPromise.current
  }

  useEffect(() => {
    if (!loading) {
      if (error) {
        if (attempts < pollingLimit) {
          refetch()
        }
      } else if (attempts > 1) {
        setAttempts(1)
      }
    }
  }, [loading, error, attempts])

  useEffect(() => {
    setLoading(networkStatus < 7)
  }, [networkStatus])

  useEffect(() => {
    setError(queryError)
  }, [queryError])

  return {
    reset: () => {
      setAttempts(1)
    },
    failed: attempts >= pollingLimit,
    status,
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export/react/components/Filters/FilterSelect/SelectInput/index.tsx from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```tsx
import classNames from 'classnames'
import React, { forwardRef, useRef, useState } from 'react'
import { IconClear, IconCaretDown, Spinner } from 'vtex.styleguide'

import styles from '../styles.css'

type Props = {
  value: string
  loading: boolean
  onClear: () => void
  onChange: (term: string) => void
  testId?: string
} & Omit<React.HTMLProps<HTMLInputElement>, 'onChange' | 'value'>

const SearchInput = forwardRef<HTMLInputElement, Props>((props, ref) => {
  const {
    onClear,
    value,
    onChange,
    onFocus,
    onBlur,
    disabled,
    loading,
    testId,
    ...inputProps
  } = props

  const [focused, setFocused] = useState(false)
  const inputRef = useRef<HTMLInputElement | null>(null)

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    onChange?.(e.target.value)
  }

  const handleClear = () => {
    onClear?.()
  }

  const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
    setFocused(true)
    onFocus?.(e)
  }

  const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
    setFocused(false)
    onBlur?.(e)
  }

  const handleCaretClick = () => {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }

  const inputClasses = classNames(
    `w-100 ma0 border-box bw1 br2 ba outline-0 t-body h-regular ph5 pr8 t-small`,
    {
      'bg-disabled b--disabled c-disabled': disabled,
      'bg-base c-on-base': !disabled,
      [`b--blue ${styles['border-medium']}`]: focused,
      'b--muted-3': !disabled && !focused,
      [styles['select-input']]: !disabled && !focused,
    }
  )

  const iconClasses = classNames('flex items-center', {
    'bg-transparent b--disabled c-disabled': disabled,
    'c-link': !disabled,
  })

  return (
    <div className="flex flex-row">
      <div data-testid={testId} className="relative w-100">
        <input
          ref={(node) => {
            inputRef.current = node
            if (typeof ref === 'function') {
              ref(node)
            } else if (ref) {
              // eslint-disable-next-line @typescript-eslint/ban-ts-comment
              // @ts-ignore
              ref.current = node
            }
          }}
          className={inputClasses}
          value={value}
          onFocus={handleFocus}
          onBlur={handleBlur}
          onChange={handleChange}
          disabled={disabled}
          {...inputProps}
        />
        <span
          className="absolute fw5 flex items-center ph4 t-body top-0 right-0 h-100 pointer"
          onClick={handleCaretClick}
          role="presentation"
        >
          {onClear && value && (
            <span
              className="c-muted-3 mr3 flex items-center"
              onClick={handleClear}
              role="button"
              tabIndex={0}
            >
              <IconClear />
            </span>
          )}
          <span className={iconClasses}>
            {loading ? <Spinner size={14} /> : <IconCaretDown size={9} />}
          </span>
        </span>
      </div>
    </div>
  )
})

export default SearchInput

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/docs/README.md from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```markdown
# MASSIVE STOCK UPDATE

This application exposes an endpoint of massive stock update

---

```shell
PUT https://{{workspace}}--{{accountName}}.myvtex.com/_v/massive/stock/update

```

## Curl

```shell
curl --location --request PUT 'https://{{workspace}}--{{accountName}}.myvtex.com/_v/massive/stock/update' \
--header 'VtexIdClientAutCookie: "" \
--header 'Content-Type: application/json' \
--data-raw '[
    {
        "sku": 1,
        "warehouseId": 1,
        "quantity": 1,
        "unlimitedQuantity": false,
        "dateUtcOnBalanceSystem": null
    }
]'
```

## Specification

### Headers

- Required
  - Accept : application/json
  - Content-Type : application/json; charset=utf-8
  - VtexIdclientAutCookie : `eyJhbGciOi...`

### Path params

- Required
  - sku [int32]
  - warehouseId [int32] || [string]
  - quantity [int32]
  - unlimitedQuantity [boolean]
  - dateUtcOnBalanceSystem [string]

> Read the API information for more information [link](https://developers.vtex.com/vtex-rest-api/reference/inventory#updateinventorybyskuandwarehouse)

### Request body example

```json
[
  {
    "sku": 1,
    "warehouseId": 123,
    "quantity": 50,
    "unlimitedQuantity": false,
    "dateUtcOnBalanceSystem": null
  }
]
```

### Success Response body example

```json
{
  "successfulResponses": {
    "elements": [
      {
        "sku": 1,
        "success": true,
        "warehouseId": 123,
        "quantity": 50,
        "unlimitedQuantity": false,
        "dateUtcOnBalanceSystem": null
      }
    ],
    "quantity": 1
  },
  "failedResponses": {
    "elements": [],
    "quantity": 0
  },
  "total": 1
}
```

### Error Response body example

```json
{
  "failedResponses": {
    "elements": [
      [
        {
          "sku": 159076,
          "quantity": 12,
          "dateUtcOnBalanceSystem": null,
          "success": "false",
          "error": 400,
          "errorMessage": "The request is invalid: The 'warehouseId' field is required."
        }
      ]
    ],
    "quantity": 1
  }
}
```

---

## Credentials

### Create appKey y appToken

To generate app keys in your account, you should follow the instructions seen in the [Application Keys](https://help.vtex.com/en/tutorial/application-keys--2iffYzlvvz4BDMr6WGUtet) article in our Help Center.

### Create role

[Create a role](https://help.vtex.com/en/tutorial/perfiles-de-acceso--7HKK5Uau2H6wxE1rH5oRbc) with the following resources and add your user to that role

- Logistics / Logistics access / Logistics full access

### Convert to JWT

Make a call with the credentials created. The result, if the credentials are valid, will return a token that will be used as the value in the header 'VtexIdclientAutCookie' requested by the massive-stock-update component.

```shell
>>>>>>> 91953ff0fc5154ad45335f9f2a8dbc6141fadf81
curl --location --request POST 'http://vtexid.vtexcommercestable.com.br/api/vtexid/apptoken/login' \
--header 'Content-Type: application/json' \
--data-raw '{
    "appkey": "...",
    "apptoken": "..."
}'
```

## Flow

![Massive stock update flow](https://user-images.githubusercontent.com/33711188/132750831-38272a4d-5abb-446b-ac1b-574969cb8561.png)

## Contributors

Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
  <tr>
    <td align="center"><a href="https://github.com/GuidoSdo"><img src="https://avatars.githubusercontent.com/u/33711188?v=4" width="100px;" alt=""/><br /><sub><b>Guido Salcedo</b></sub></a><br /><a href="https://github.com/vtex-apps/massive-stock-update" title="Code">💻</a></td>
  </tr>
</table>

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/node/service.json from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "memory": 256,
  "ttl": 10,
  "timeout": 600,
  "minReplicas": 10,
  "maxReplicas": 20,
  "workers": 1,
  "routes": {
    "status": {
      "path": "/_v/massive/stock/update",
      "public": true
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/node/utils.ts from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { AuthenticationError, ForbiddenError, UserInputError } from '@vtex/api'
import type { AxiosError } from 'axios'

export const BUCKET_NAME = 'product-translation'
export const ALL_TRANSLATIONS_FILES = 'all-translations'

export const statusToError = (e: AxiosError) => {
  if (!e.response) {
    throw e
  }

  const {
    response: { status },
  } = e

  switch (status) {
    case 401: {
      throw new AuthenticationError(e)
    }

    case 403: {
      throw new ForbiddenError(e)
    }

    case 400: {
      throw new UserInputError(e)
    }

    default:
      throw new TypeError(e.message)
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/node/package.json from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "license": "MIT",
  "dependencies": {
    "co-body": "^6.0.0",
    "ramda": "^0.25.0",
    "graphql": "^14.5.8"
  },
  "devDependencies": {
    "@types/co-body": "^0.0.3",
    "@types/jest": "^24.0.18",
    "@types/node": "^12.0.0",
    "@types/ramda": "types/npm-ramda#dist",
    "@vtex/api": "6.45.6",
    "@vtex/test-tools": "^1.0.0",
    "@vtex/tsconfig": "^0.5.6",
    "typescript": "3.9.7",
    "vtex.catalog-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.catalog-graphql@1.96.0/public/@types/vtex.catalog-graphql",
    "@types/graphql": "^14.5.0"
  },
  "scripts": {
    "lint": "tsc --noEmit --pretty"
  },
  "version": "3.0.0"
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/node/index.ts from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { ClientsConfig, ServiceContext, RecorderState } from '@vtex/api'
import { LRUCache, method, Service } from '@vtex/api'

import { Clients } from './clients'
import { inventoryMiddleware } from './middlewares/inventoryMiddleware'
import { validateMiddleware } from './middlewares/validateMiddleware'

const TIMEOUT_MS = 600000

// Create a LRU memory cache for the Status client.
// The @vtex/api HttpClient respects Cache-Control headers and uses the provided cache.
const memoryCache = new LRUCache<string, any>({ max: 5000 })

metrics.trackCache('status', memoryCache)

// This is the configuration for clients available in `ctx.clients`.
const clients: ClientsConfig<Clients> = {
  // We pass our custom implementation of the clients bag, containing the Status client.
  implementation: Clients,
  options: {
    // All IO Clients will be initialized with these options, unless otherwise specified.
    default: {
      // retries: 2,
      timeout: TIMEOUT_MS,
    },
    // This key will be merged with the default options and add this cache to our Status client.
    status: {
      memoryCache,
    },
  },
}

declare global {
  // We declare a global Context type just to avoid re-writing ServiceContext<Clients, State> in every handler and resolver
  type Context = ServiceContext<Clients, State>

  // The shape of our State object found in `ctx.state`. This is used as state bag to communicate between middlewares.
  interface State extends RecorderState {
    validatedBody: UpdateRequest[]
  }

  interface UpdateRequest {
    sku: number
    warehouseId?: number | string
    quantity?: number
    unlimitedQuantity?: boolean
    dateUtcOnBalanceSystem?: string
  }

  interface UpdateResponse {
    sku: number
    warehouseId?: number | string
    success: string
    error?: number | string
    errorMessage?: string
    quantity?: number
    unlimitedQuantity?: boolean
    dateUtcOnBalanceSystem?: string
  }
}

// Export a service that defines route handlers and client options.
export default new Service({
  clients,
  routes: {
    // `status` is the route ID from service.json. It maps to an array of middlewares (or a single handler).
    status: method({
      PUT: [validateMiddleware, inventoryMiddleware],
    }),
  },
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/public/metadata/messages/es-AR.json from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "Massive Stock Update",
  "headline": "Actualice el stock mediante lotes.",
  "overview": "Actualice el stock por lotes para reducir los tiempos de integración y los gastos generales de las solicitudes.",
  "features": [
    "Servicio de actualización masiva de inventario a través de SKU y warehouseId."
  ],
  "websiteURL": "https://vtex.com/",
  "support": {
    "url": "https://support.vtex.com/hc/requests"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/public/metadata/messages/pt-BR.json from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "Massive Stock Update",
  "headline": "Atualize o estoque por massas.",
  "overview": "Atualize o estoque em massa para diminuir os tempos de integração e a sobrecarga de solicitações.",
  "features": [
    "Serviço de atualização de estoque em massa via SKU e warehouseId."
  ],
  "websiteURL": "https://vtex.com/",
  "support": {
    "url": "https://support.vtex.com/hc/requests"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/public/metadata/messages/en-US.json from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "Massive Stock Update",
  "headline": "Update the stock by batches.",
  "overview": "Update stock in batches to decrease integration times and requests overhead.",
  "features": [
    "Massive inventory stock update service through SKU and warehouseId."
  ],
  "websiteURL": "https://vtex.com/",
  "support": {
    "url": "https://support.vtex.com/hc/requests"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/node/clients/inventoryRestClient.ts from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { JanusClient } from '@vtex/api'
import type { InstanceOptions, IOContext, IOResponse } from '@vtex/api'

import type { UpdateinventoryBySkuAndWarehouseRequest } from '../middlewares/inventoryMiddleware'

export default class InventoryRestClient extends JanusClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(context, {
      ...options,
      headers: {
        VtexIdclientAutCookie: context.authToken,
        'Content-Type': 'application/json; charset=utf-8',
        Accept: 'application/json',
        'X-Vtex-Use-Https': 'true',
      },
    })
  }

  // eslint-disable-next-line max-params
  public async updateInventory(
    authToken: string,
    body: UpdateinventoryBySkuAndWarehouseRequest,
    skuId?: number | string,
    warehouseId?: number | string
  ): Promise<IOResponse<string>> {
    return this.http.putRaw(
      `http://${this.context.account}.vtexcommercestable.com.br/api/logistics/pvt/inventory/skus/${skuId}/warehouses/${warehouseId}`,
      body,
      {
        headers: {
          VtexIdclientAutCookie: authToken,
        },
      }
    )
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/node/clients/index.ts from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { IOClients } from '@vtex/api'

import InventoryRestClient from './inventoryRestClient'

export class Clients extends IOClients {
  public get inventoryRestClient() {
    return this.getOrSet('inventoryRestClient', InventoryRestClient)
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/node/middlewares/inventoryMiddleware.ts from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```typescript
export async function inventoryMiddleware(
  ctx: Context,
  next: () => Promise<any>
) {
  const {
    clients: { inventoryRestClient },
    state: { validatedBody },
  } = ctx

  const responseList: UpdateResponse[] = []

  try {
    const expected = await operationRetry(
      await Promise.all(
        validatedBody.map(async (item) => {
          return updateInventory(item)
        })
      )
    )

    if (expected) {
      const successfulResponses: UpdateResponse[] = responseList.filter((e) => {
        return e.success !== 'false'
      })

      const failedResponses: UpdateResponse[] = responseList.filter((e) => {
        return e.success === 'false'
      })

      ctx.status = 200
      ctx.body = {
        successfulResponses: {
          elements: successfulResponses,
          quantity: successfulResponses.length,
        },
        failedResponses: {
          elements: failedResponses,
          quantity: failedResponses.length,
        },
        total: responseList.length,
      }

      await next()
    }
  } catch (error) {
    ctx.status = 500
    ctx.body = error
    await next()
  }

  async function updateInventory(
    updateRequest: UpdateRequest
  ): Promise<UpdateResponse> {
    const {
      sku,
      warehouseId,
      quantity,
      unlimitedQuantity,
      dateUtcOnBalanceSystem,
    } = updateRequest

    const body: UpdateinventoryBySkuAndWarehouseRequest = {
      quantity,
      dateUtcOnBalanceSystem,
      unlimitedQuantity,
    }

    try {
      const vtexIdToken = ctx.get('VtexIdclientAutCookie') ?? ''

      const updateInventoryRestClientResponse = await inventoryRestClient.updateInventory(
        vtexIdToken,
        body,
        sku,
        warehouseId
      )

      const inventoryMiddlewareResponse: UpdateResponse = {
        sku: updateRequest.sku,
        success: updateInventoryRestClientResponse.data,
        warehouseId: updateRequest.warehouseId,
        quantity: updateRequest.quantity,
        unlimitedQuantity: updateRequest.unlimitedQuantity,
        dateUtcOnBalanceSystem: updateRequest.dateUtcOnBalanceSystem,
      }

      return inventoryMiddlewareResponse
    } catch (error) {
      const data = error.response ? error.response.data : ''
      const updateInventoryRestClientErrorResponse = {
        sku: updateRequest.sku,
        success: 'false',
        warehouseId: updateRequest.warehouseId,
        quantity: updateRequest.quantity,
        unlimitedQuantity: updateRequest.unlimitedQuantity,
        dateUtcOnBalanceSystem: updateRequest.dateUtcOnBalanceSystem,
        error: error.response ? error.response.status : 500,
        errorMessage: data.error ? data.error.message : data,
      }

      if (error.response && error.response.status === 429) {
        updateInventoryRestClientErrorResponse.errorMessage = error.response
          ? error.response.headers['ratelimit-reset']
          : '0'
      }

      return updateInventoryRestClientErrorResponse
    }
  }

  async function operationRetry(
    updateResponseList: UpdateResponse[]
  ): Promise<any> {
    addResponsesSuccessfulUpdates(updateResponseList)

    const response = await findStoppedRequests(updateResponseList)

    return response
  }

  async function findStoppedRequests(
    // eslint-disable-next-line @typescript-eslint/no-shadow
    responseList: UpdateResponse[]
  ): Promise<any> {
    const retryList: UpdateRequest[] = []
    let value = '0'

    if (responseList.length >= 1) {
      for (const index in responseList) {
        const response = responseList[index]

        if (response.error && response.error === 429) {
          if (response.errorMessage && response.errorMessage > value) {
            value = response.errorMessage
          }

          if (value === '0') {
            value = '20'
          }

          retryList.push({
            sku: response.sku,
            warehouseId: response.warehouseId,
            quantity: response.quantity,
            unlimitedQuantity: response.unlimitedQuantity,
            dateUtcOnBalanceSystem: response.dateUtcOnBalanceSystem,
          })
        }
      }
    }

    if (retryList.length >= 1) {
      let retryOperation: UpdateResponse[] = []

      const awaitTimeout = (delay: string) =>
        new Promise((resolve) => setTimeout(resolve, parseFloat(delay) * 1000))

      await awaitTimeout(value)

      retryOperation = await Promise.all(
        retryList.map(async (item) => {
          return updateInventory(item)
        })
      )

      return operationRetry(retryOperation)
    }

    return true
  }

  function addResponsesSuccessfulUpdates(
    updateResponseList: UpdateResponse[]
  ): void {
    for (const index in updateResponseList) {
      const updateResponse = updateResponseList[index]

      if (updateResponse.error !== 429) {
        responseList.push(updateResponse)
      }
    }
  }
}

export type UpdateinventoryBySkuAndWarehouseRequest = {
  unlimitedQuantity?: boolean
  dateUtcOnBalanceSystem?: string
  quantity?: number
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of custom-stock-export/custom-stock-export-back/node/middlewares/validateMiddleware.ts from the custom-stock-export VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { UserInputError } from '@vtex/api'
import { json } from 'co-body'

export async function validateMiddleware(
  ctx: Context,
  next: () => Promise<any>
) {
  const vtexIdToken =
    ctx.cookies.get('VtexIdclientAutCookie') ?? ctx.get('VtexIdclientAutCookie')

  if (!vtexIdToken) {
    ctx.status = 401
    ctx.body = 'VtexIdclientAutCookie not found.'

    return
  }

  const requestList = await json(ctx.req)
  const errorList: any[] = []

  function requestValidator(request: UpdateRequest): void {
    const requestErrorList: UpdateResponse[] = []

    const {
      sku,
      warehouseId,
      quantity,
      unlimitedQuantity,
      dateUtcOnBalanceSystem,
    } = request

    if (!sku) {
      requestErrorList.push(errorResponseGenerator('sku'))
    }

    if (!warehouseId) {
      requestErrorList.push(errorResponseGenerator('warehouseId'))
    }

    if (!quantity) {
      requestErrorList.push(errorResponseGenerator('quantity'))
    }

    if (typeof unlimitedQuantity === 'undefined') {
      requestErrorList.push(errorResponseGenerator('unlimitedQuantity'))
    }

    if (typeof dateUtcOnBalanceSystem === 'undefined') {
      requestErrorList.push(errorResponseGenerator('dateUtcOnBalanceSystem'))
    }

    if (requestErrorList.length >= 1) {
      errorList.push(requestErrorList)
    }

    function errorResponseGenerator(field: string): UpdateResponse {
      return {
        sku,
        warehouseId,
        quantity,
        unlimitedQuantity,
        dateUtcOnBalanceSystem,
        success: 'false',
        error: 400,
        errorMessage: `The request is invalid: The '${field}' field is required.`,
      }
    }
  }

  try {
    for (const request of requestList) {
      requestValidator(request)
    }
  } catch (error) {
    throw new UserInputError(error)
  }

  if (errorList.length >= 1) {
    ctx.status = 400
    ctx.body = {
      failedResponses: {
        elements: errorList,
        quantity: errorList.length,
      },
    }

    return
  }

  ctx.state.validatedBody = requestList
  await next()
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of adserver-resolver/node/service.json from the adserver-resolver VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "stack": "nodejs",
  "memory": 512,
  "minReplicas": 5,
  "workers": 10
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of adserver-resolver/node/package.json from the adserver-resolver VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "adserver-resolver",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "transform": {
      "^.+\\.tsx?$": "ts-jest"
    },
    "testMatch": [
      "**/__tests__/**/*.(spec|test).(js|ts)?(x)",
      "**/?(*.)+(spec|test).(js|ts)?(x)"
    ],
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js"
    ]
  },
  "dependencies": {
    "@vtex/clients": "^2.21.0",
    "graphql": "^14.5.8",
    "vtex.adserver-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.adserver-graphql@0.3.1/public"
  },
  "devDependencies": {
    "@types/jest": "^27.0.3",
    "@types/node": "^12.12.21",
    "@vtex/api": "6.48.0",
    "@vtex/tsconfig": "^0.5.6",
    "jest": "^25.1.0",
    "ts-jest": "^25.2.1",
    "typescript": "3.9.7"
  },
  "resolutions": {
    "@types/express-serve-static-core": "4.17.28"
  },
  "version": "2.10.4"
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of adserver-resolver/node/index.ts from the adserver-resolver VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { ParamsContext, RecorderState } from '@vtex/api'
import { Service } from '@vtex/api'
import schema from 'vtex.adserver-graphql/graphql'

import { Clients } from './clients'
import { resolvers } from './resolvers'

const THREE_SECONDS_MS = 3 * 1000

export default new Service<Clients, RecorderState, ParamsContext>({
  clients: {
    implementation: Clients,
    options: {
      default: {
        retries: 0,
        timeout: THREE_SECONDS_MS,
      },
    },
  },
  graphql: {
    resolvers,
    schema,
  },
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of adserver-resolver/node/clients/AdServer.ts from the adserver-resolver VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

import type {
  AdServerRequest,
  AdServerResponse,
  AdServerSponsoredBannersRequest,
  AdServerSponsoredBannersResponse,
} from '../typings/AdServer'

class AdServer extends ExternalClient {
  public static BASE_URL = 'https://ad-server.vtex.systems'

  public static ERROR_MESSAGES = {
    AD_NOT_FOUND: 'Ad not found',
  }

  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(AdServer.BASE_URL, ctx, { ...options })
  }

  public getSponsoredProducts(
    body: AdServerRequest
  ): Promise<AdServerResponse> {
    if (process.env.VTEX_APP_LINK) {
      // eslint-disable-next-line no-console
      console.log(
        'endpoint: /api/v1/sponsored-products',
        'body: ',
        JSON.stringify({ ...body, accountName: this.context.account }, null, 2)
      )
    }

    body.searchParams.query = body.searchParams.query ?? ''

    return this.http.post('/api/v1/sponsored-products', {
      accountName: this.context.account,
      ...body,
    })
  }

  public getSponsoredBanners(
    body: AdServerSponsoredBannersRequest
  ): Promise<AdServerSponsoredBannersResponse> {
    if (process.env.VTEX_APP_LINK) {
      // eslint-disable-next-line no-console
      console.log(
        'endpoint: /api/ads/v1/ads/banner/query',
        'body: ',
        JSON.stringify({ ...body, accountName: this.context.account }, null, 2)
      )
    }

    return this.http.post('/api/ads/v1/ads/banner/query', {
      accountName: this.context.account,
      ...body,
    })
  }
}

export default AdServer

```