[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 b2b-organizations/react/admin/OrganizationDetails.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent } from 'react'
import React, { useEffect, useState } from 'react'
import { useQuery, useMutation } from 'react-apollo'
import {
  Layout,
  PageHeader,
  Button,
  IconCheck,
  Tabs,
  Tab,
  Spinner,
  PageBlock,
  Alert,
} from 'vtex.styleguide'
import { useToast } from '@vtex/admin-ui'
import { useIntl, FormattedMessage } from 'react-intl'
import { useRuntime } from 'vtex.render-runtime'
import { HashRouter, Route, Switch } from 'react-router-dom'
import unionBy from 'lodash/unionBy'

import { organizationMessages as messages } from './utils/messages'
import GET_ORGANIZATION from '../graphql/getOrganization.graphql'
import UPDATE_ORGANIZATION from '../graphql/updateOrganization.graphql'
import GET_B2B_CUSTOM_FIELDS from '../graphql/getB2BCustomFields.graphql'
import OrganizationDetailsCostCenters from './OrganizationDetails/OrganizationDetailsCostCenters'
import type { Collection } from './OrganizationDetails/OrganizationDetailsCollections'
import OrganizationDetailsCollections from './OrganizationDetails/OrganizationDetailsCollections'
import type { PaymentTerm } from './OrganizationDetails/OrganizationDetailsPayTerms'
import OrganizationDetailsPayTerms from './OrganizationDetails/OrganizationDetailsPayTerms'
import type { PriceTable } from './OrganizationDetails/OrganizationDetailsPriceTables'
import OrganizationDetailsPriceTables from './OrganizationDetails/OrganizationDetailsPriceTables'
import OrganizationDetailsSalesChannel from './OrganizationDetails/OrganizationDetailsSalesChannel'
import OrganizationDetailsUsers from './OrganizationDetails/OrganizationDetailsUsers'
import OrganizationDetailsDefault from './OrganizationDetails/OrganizationDetailsDefault'
import useHashRouter from './OrganizationDetails/useHashRouter'
import type { Seller } from './OrganizationDetails/OrganizationDetailsSellers'
import OrganizationDetailsSellers from './OrganizationDetails/OrganizationDetailsSellers'
import type { PermissionsOptions } from './OrganizationDetails/OrganizationDetailsSettings'
import OrganizationDetailsSettings from './OrganizationDetails/OrganizationDetailsSettings'
import { useOrgPermission } from '../hooks/useOrgPermission'
import { ORGANIZATION_EDIT } from '../utils/constants'

export interface CellRendererProps<RowType> {
  rowData: RowType
  updateCellMeasurements: () => void
}

export type AvailabilityTypes =
  | 'availablePriceTables'
  | 'availableCollections'
  | 'availablePayments'
  | 'availableSellers'

const SESSION_STORAGE_KEY = 'organization-details-tab'

// combines defaultCustomFields and customFields input from the organization data to fill input fields
export const joinById = (
  fields: CustomField[],
  defaultFields: CustomFieldSetting[]
): CustomField[] => {
  return unionBy(fields, defaultFields, 'name')
}

const OrganizationDetails: FunctionComponent = () => {
  /**
   * Hooks
   */
  const { formatMessage } = useIntl()
  const {
    route: { params },
    navigate,
  } = useRuntime()

  const showToast = useToast()

  /**
   * States
   */

  const [organizationNameState, setOrganizationNameState] = useState('')
  const [organizationTradeNameState, setOrganizationTradeNameState] = useState(
    ''
  )

  const [statusState, setStatusState] = useState('')
  const [collectionsState, setCollectionsState] = useState([] as Collection[])
  const [priceTablesState, setPriceTablesState] = useState([] as string[])
  const [salesChannelState, setSalesChannelState] = useState('')
  const [sellersState, setSellersState] = useState([] as Seller[])
  const [errorState, setErrorState] = useState('')
  const [paymentTermsState, setPaymentTermsState] = useState(
    [] as PaymentTerm[]
  )

  const [permissionsOptions, setPermissionsOptions] = useState(
    [] as PermissionsOptions[]
  )

  // const routerRef = useRef(null as any)

  const [loadingState, setLoadingState] = useState(false)

  const [customFieldsState, setCustomFieldsState] = useState<CustomField[]>([])

  /**
   * Queries
   */
  const { data, loading, refetch } = useQuery(GET_ORGANIZATION, {
    variables: { id: params?.id },
    skip: !params?.id,
    ssr: false,
    onCompleted(insideData) {
      if (!insideData?.getOrganizationById?.permissions) {
        return
      }

      const permissionsArray = Object.entries(
        insideData.getOrganizationById.permissions
      ).filter(key => {
        return !(key[0] === '__typename')
      })

      setPermissionsOptions(() => {
        return permissionsArray.map(p => ({
          label: p[0],
          value: p[1] as boolean,
        }))
      })
    },
  })

  const { data: defaultCustomFieldsData } = useQuery(GET_B2B_CUSTOM_FIELDS, {
    variables: { id: params?.id },
    skip: !params?.id,
    ssr: false,
  })

  const { data: canEditBuyerOrg } = useOrgPermission({
    resourceCode: ORGANIZATION_EDIT,
  })

  /**
   * Mutations
   */
  const [updateOrganization] = useMutation(UPDATE_ORGANIZATION)

  /**
   * Functions
   */
  const handleUpdateOrganization = () => {
    setErrorState('')

    if (!organizationNameState || organizationNameState.trim().length === 0) {
      setErrorState(formatMessage(messages.organizationNameRequired))

      return
    }

    setLoadingState(true)

    const collections = collectionsState.map(collection => {
      return { name: collection.name, id: collection.collectionId }
    })

    const paymentTerms = paymentTermsState.map(paymentTerm => {
      return { name: paymentTerm.name, id: paymentTerm.paymentTermId }
    })

    const variables = {
      id: params?.id,
      name: organizationNameState,
      tradeName: organizationTradeNameState,
      status: statusState,
      collections,
      paymentTerms,
      priceTables: priceTablesState,
      customFields: customFieldsState,
      salesChannel: salesChannelState === '' ? null : salesChannelState,
      sellers: sellersState?.map(seller => ({
        id: seller.sellerId,
        name: seller.name,
      })),
      permissions: permissionsOptions.reduce((acc, current) => {
        acc[current.label] = current.value

        return acc
      }, {} as Record<string, boolean>),
    }

    updateOrganization({ variables })
      .then(() => {
        setLoadingState(false)
        showToast({
          variant: 'positive',
          message: formatMessage(messages.toastUpdateSuccess),
        })
        refetch({ id: params?.id })
      })
      .catch(error => {
        setLoadingState(false)
        console.error(error)
        showToast({
          variant: 'critical',
          message: formatMessage(messages.toastUpdateFailure),
        })
      })
  }

  const getSchema = (type?: AvailabilityTypes) => {
    let cellRenderer

    switch (type) {
      case 'availablePriceTables':
        cellRenderer = ({
          rowData: { tableId, name },
        }: CellRendererProps<PriceTable>) => {
          const assigned = priceTablesState.includes(tableId)

          return (
            <span className={assigned ? 'c-disabled' : ''}>
              {name ?? tableId}
              {assigned && <IconCheck />}
            </span>
          )
        }

        break

      case 'availableCollections':
        cellRenderer = ({
          rowData: { name },
        }: CellRendererProps<Collection>) => {
          const assigned = collectionsState.some(
            collection => collection.name === name
          )

          return (
            <span className={assigned ? 'c-disabled' : ''}>
              {name}
              {assigned && <IconCheck />}
            </span>
          )
        }

        break

      case 'availablePayments':
        cellRenderer = ({
          rowData: { name },
        }: CellRendererProps<PaymentTerm>) => {
          const assigned = paymentTermsState.some(
            payment => payment.name === name
          )

          return (
            <span className={assigned ? 'c-disabled' : ''}>
              {name}
              {assigned && <IconCheck />}
            </span>
          )
        }

        break

      case 'availableSellers':
        cellRenderer = ({
          rowData: { sellerId, name },
        }: CellRendererProps<Seller>) => {
          const isDisabled = sellersState.some(
            seller => seller.sellerId === sellerId
          )

          return (
            <span className={isDisabled ? 'c-disabled' : ''}>
              {name ?? sellerId}
              {isDisabled && <IconCheck />}
            </span>
          )
        }

        break

      default:
        break
    }

    return {
      properties: {
        name: {
          title: formatMessage(messages.detailsColumnName),
          ...(cellRenderer && { cellRenderer }),
        },
      },
    }
  }

  /**
   * Effects
   */
  useEffect(() => {
    if (!data?.getOrganizationById || statusState) return

    const collections =
      data.getOrganizationById.collections?.map(
        (collection: { name: string; id: string }) => {
          return { name: collection.name, collectionId: collection.id }
        }
      ) ?? []

    const paymentTerms =
      data.getOrganizationById.paymentTerms?.map(
        (paymentTerm: { name: string; id: string }) => {
          return { name: paymentTerm.name, paymentTermId: paymentTerm.id }
        }
      ) ?? []

    setOrganizationNameState(data.getOrganizationById.name)
    setOrganizationTradeNameState(data.getOrganizationById.tradeName ?? '')
    setStatusState(data.getOrganizationById.status)
    setCollectionsState(collections)
    setPaymentTermsState(paymentTerms)
    setPriceTablesState(data.getOrganizationById.priceTables ?? [])
    setSalesChannelState(data.getOrganizationById.salesChannel ?? '')
    setSellersState(
      data.getOrganizationById.sellers?.map(
        (seller: { id: string; name: string }) => ({
          sellerId: seller.id,
          name: seller.name,
        })
      ) ?? []
    )
  }, [data])

  useEffect(() => {
    const customFieldsToShow = joinById(
      data?.getOrganizationById?.customFields || [],
      defaultCustomFieldsData?.getB2BSettings?.organizationCustomFields || []
    )

    setCustomFieldsState(customFieldsToShow)
  }, [
    data &&
      defaultCustomFieldsData &&
      data?.getOrganizationById?.customFields &&
      defaultCustomFieldsData?.getB2BSettings.organizationCustomFields,
  ])

  /**
   * Data Variables
   */

  const tabsList = [
    {
      label: formatMessage(messages.default),
      tab: 'default',
      component: (
        <OrganizationDetailsDefault
          organizationNameState={organizationNameState}
          setOrganizationNameState={setOrganizationNameState}
          organizationTradeNameState={organizationTradeNameState}
          setOrganizationTradeNameState={setOrganizationTradeNameState}
          statusState={statusState}
          setStatusState={setStatusState}
          data={data}
          customFieldsState={customFieldsState}
          setCustomFieldsState={setCustomFieldsState}
        />
      ),
    },
    {
      label: formatMessage(messages.costCenters),
      tab: 'cost-centers',
      component: (
        <OrganizationDetailsCostCenters
          setLoadingState={setLoadingState}
          showToast={showToast}
          loadingState={loadingState}
        />
      ),
    },
    {
      label: formatMessage(messages.collections),
      tab: 'collections',
      component: (
        <OrganizationDetailsCollections
          getSchema={getSchema}
          collectionsState={collectionsState}
          setCollectionsState={setCollectionsState}
        />
      ),
    },
    {
      label: formatMessage(messages.paymentTerms),
      tab: 'pay-terms',

      component: (
        <OrganizationDetailsPayTerms
          getSchema={getSchema}
          paymentTermsState={paymentTermsState}
          setPaymentTermsState={setPaymentTermsState}
        />
      ),
    },
    {
      label: formatMessage(messages.priceTables),
      tab: 'price-tables',
      component: (
        <OrganizationDetailsPriceTables
          getSchema={getSchema}
          priceTablesState={priceTablesState}
          setPriceTablesState={setPriceTablesState}
        />
      ),
    },
    {
      label: formatMessage(messages.salesChannel),
      tab: 'sales-channel',
      component: (
        <OrganizationDetailsSalesChannel
          salesChannelState={salesChannelState}
          setSalesChannelState={setSalesChannelState}
        />
      ),
    },
    {
      label: formatMessage(messages.sellers),
      tab: 'sellers',
      component: (
        <OrganizationDetailsSellers
          getSchema={getSchema}
          sellersState={sellersState}
          setSellersState={setSellersState}
        />
      ),
    },
    {
      label: formatMessage(messages.users),
      tab: 'users',
      component: (
        <OrganizationDetailsUsers params={params} loadingState={loadingState} />
      ),
    },
    {
      label: formatMessage(messages.settings),
      tab: 'settings',
      component: (
        <OrganizationDetailsSettings
          permissionsOptions={permissionsOptions}
          setPermissionsOptions={setPermissionsOptions}
        />
      ),
    },
  ]

  const { tab, handleTabChange, routerRef } = useHashRouter({
    sessionKey: SESSION_STORAGE_KEY,
    defaultPath: 'default',
    routes: tabsList.map(_tab => _tab.tab),
  })

  return (
    <Layout
      fullWidth
      pageHeader={
        <PageHeader
          title={formatMessage(messages.detailsPageTitle)}
          linkLabel={formatMessage(messages.back)}
          onLinkClick={() => {
            navigate({
              page: 'admin.app.b2b-organizations.organizations',
            })
          }}
        >
          <Button
            variation="primary"
            isLoading={loadingState}
            onClick={() => handleUpdateOrganization()}
            disabled={!canEditBuyerOrg}
          >
            <FormattedMessage id="admin/b2b-organizations.organization-details.button.save" />
          </Button>
        </PageHeader>
      }
    >
      {loading ? (
        <PageBlock>
          <Spinner />
        </PageBlock>
      ) : (
        (data && (
          <HashRouter ref={routerRef}>
            <Tabs>
              {tabsList.map((item: { label: string; tab: string }) => (
                <Tab
                  label={item.label}
                  active={tab === item.tab}
                  onClick={() => handleTabChange(item.tab)}
                />
              ))}
            </Tabs>
            {errorState && errorState.length > 0 && (
              <Alert type="error" className="mt6 mb6">
                {errorState}
              </Alert>
            )}
            <Switch>
              {tabsList.map(({ tab: path, component }) => (
                <Route path={`/${path}`} key={path} exact>
                  <div className="mt6">{component}</div>
                </Route>
              ))}
            </Switch>
          </HashRouter>
        )) ?? (
          <PageBlock>
            <FormattedMessage id="admin/b2b-organizations.organization-details.empty-state" />
          </PageBlock>
        )
      )}
    </Layout>
  )
}

export default OrganizationDetails

```


[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 b2b-organizations/react/admin/OrganizationDetailsCustomField.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { Input, Dropdown } from 'vtex.styleguide'

interface CustomFieldProps {
  index: number
  handleUpdate: (index: number, customField: CustomField) => void
  customField: CustomField
}

const OrganizationDetailsCustomField: React.FC<CustomFieldProps> = ({
  index,
  handleUpdate,
  customField,
  customField: { name, type, value = '', dropdownValues },
}) => {
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const updatedCustomField = {
      ...customField,
      value: e.target.value,
    }

    handleUpdate(index, updatedCustomField)
  }

  if (type === 'dropdown') {
    return (
      <div className="mb5">
        <Dropdown
          key={index}
          label={name}
          size="large"
          options={dropdownValues ?? []}
          onChange={handleChange}
          value={value}
        />
      </div>
    )
  }

  return (
    <div className="mb5">
      <Input
        className="test-class-plus"
        autocomplete="off"
        size="large"
        label={name}
        value={value}
        type={type}
        onChange={handleChange}
      />
    </div>
  )
}

export default OrganizationDetailsCustomField

```


[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 b2b-organizations/react/admin/OrganizationRequestsTable.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent, ChangeEvent } from 'react'
import React, { Fragment, useState } from 'react'
import { useQuery } from 'react-apollo'
import { Table, Tag, Checkbox } from 'vtex.styleguide'
import { useIntl } from 'react-intl'
import { useRuntime } from 'vtex.render-runtime'

import { organizationRequestMessages as messages } from './utils/messages'
import GET_ORGANIZATION_REQUESTS from '../graphql/getOrganizationRequests.graphql'

interface CellRendererProps {
  rowData: OrganizationRequestSimple
  updateCellMeasurements: () => void
}

interface OrganizationRequestSimple {
  id: string
  name: string
  b2bCustomerAdmin: B2BCustomerSimple
  status: string
  created: string
}

interface B2BCustomerSimple {
  email: string
}

export const labelTypeByStatusMap: Record<string, string> = {
  approved: 'success',
  declined: 'error',
  pending: 'warning',
}

const initialState = {
  status: ['pending', 'approved', 'declined'],
  search: '',
  page: 1,
  pageSize: 25,
  sortOrder: 'DESC',
  sortedBy: 'created',
}

const OrganizationRequestsTable: FunctionComponent = () => {
  const { formatMessage, formatDate } = useIntl()
  const { navigate } = useRuntime()
  const [variableState, setVariables] = useState(initialState)

  const [filterState, setFilterState] = useState({
    filterStatements: [] as FilterStatement[],
  })

  const { data, loading, refetch } = useQuery(GET_ORGANIZATION_REQUESTS, {
    variables: initialState,
    fetchPolicy: 'network-only',
    ssr: false,
  })

  const getSchema = () => ({
    properties: {
      name: {
        title: formatMessage(messages.columnName),
      },
      b2bCustomerAdmin: {
        title: formatMessage(messages.columnAdmin),
        cellRenderer: ({ rowData: { b2bCustomerAdmin } }: CellRendererProps) =>
          b2bCustomerAdmin?.email ?? '',
      },
      status: {
        title: formatMessage(messages.columnStatus),
        cellRenderer: ({ rowData: { status } }: CellRendererProps) => (
          <Tag type={labelTypeByStatusMap[status]}>{status}</Tag>
        ),
        sortable: true,
      },
      created: {
        title: formatMessage(messages.columnCreated),
        cellRenderer: ({ rowData: { created } }: CellRendererProps) => {
          return (
            <>
              {formatDate(created, {
                day: 'numeric',
                month: 'numeric',
                year: 'numeric',
              })}
            </>
          )
        },
        sortable: true,
      },
    },
  })

  const statusSelectorObject = ({
    value,
    onChange,
  }: {
    value: Record<string, unknown>
    onChange: any
  }) => {
    const initialValue = {
      approved: true,
      declined: true,
      pending: true,
      ...(value || {}),
    } as Record<string, unknown>

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

      return newValue
    }

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

                  onChange(isEmptyFilter ? null : newValue)
                }}
                value={opt}
              />
            </div>
          )
        })}
      </div>
    )
  }

  const handlePrevClick = () => {
    if (variableState.page === 1) return

    const newPage = variableState.page - 1

    setVariables({
      ...variableState,
      page: newPage,
    })

    refetch({
      ...variableState,
      page: newPage,
    })
  }

  const handleNextClick = () => {
    const newPage = variableState.page + 1

    setVariables({
      ...variableState,
      page: newPage,
    })

    refetch({
      ...variableState,
      page: newPage,
    })
  }

  const handleRowsChange = (e: ChangeEvent<HTMLInputElement>) => {
    const {
      target: { value },
    } = e

    setVariables({
      ...variableState,
      page: 1,
      pageSize: +value,
    })

    refetch({
      ...variableState,
      page: 1,
      pageSize: +value,
    })
  }

  const handleFiltersChange = (statements: FilterStatement[]) => {
    const statuses = [] as string[]

    statements.forEach(statement => {
      if (!statement?.object) return
      const { subject, object } = statement

      switch (subject) {
        case 'status': {
          if (!object) return
          const keys = Object.keys(object)
          const isAllTrue = !keys.some(key => !object[key])
          const isAllFalse = !keys.some(key => object[key])
          const trueKeys = keys.filter(key => object[key])

          if (isAllTrue) break
          if (isAllFalse) statuses.push('none')
          statuses.push(...trueKeys)
          break
        }

        default:
          break
      }
    })

    setFilterState({
      filterStatements: statements,
    })

    setVariables({
      ...variableState,
      status: statuses,
      page: 1,
    })

    refetch({
      ...variableState,
      page: 1,
      status: statuses,
    })
  }

  const handleInputSearchChange = (e: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: { value },
    } = e

    setVariables({
      ...variableState,
      search: value,
    })
  }

  const handleInputSearchClear = () => {
    setVariables({
      ...variableState,
      search: '',
    })

    refetch({
      ...variableState,
      search: '',
      page: 1,
    })
  }

  const handleInputSearchSubmit = () => {
    refetch({
      ...variableState,
      page: 1,
    })
  }

  const handleSort = ({
    sortOrder,
    sortedBy,
  }: {
    sortOrder: string
    sortedBy: string
  }) => {
    setVariables({
      ...variableState,
      sortOrder,
      sortedBy,
    })
    refetch({
      ...variableState,
      page: 1,
      sortOrder,
      sortedBy,
    })
  }

  const lineActions = [
    {
      label: () => formatMessage(messages.view),
      onClick: ({ rowData: { id } }: CellRendererProps) => {
        if (!id) return

        navigate({
          page: 'admin.app.b2b-organizations.organization-request-details',
          params: { id },
        })
      },
    },
  ]

  const { total } = data?.getOrganizationRequests?.pagination ?? 0
  const { page, pageSize, search, sortedBy, sortOrder } = variableState

  return (
    <Fragment>
      <Table
        fullWidth
        schema={getSchema()}
        fixFirstColumn
        loading={loading}
        emptyStateLabel={formatMessage(messages.emptyState)}
        items={data?.getOrganizationRequests?.data}
        lineActions={lineActions}
        onRowClick={({ rowData: { id } }: CellRendererProps) => {
          if (!id) return

          navigate({
            page: 'admin.app.b2b-organizations.organization-request-details',
            params: { id },
          })
        }}
        pagination={{
          onNextClick: handleNextClick,
          onPrevClick: handlePrevClick,
          onRowsChange: handleRowsChange,
          selectedOption: pageSize,
          currentItemFrom: (page - 1) * pageSize + 1,
          currentItemTo: total < page * pageSize ? total : page * pageSize,
          textShowRows: formatMessage(messages.showRows),
          textOf: formatMessage(messages.of),
          totalItems: total ?? 0,
          rowsOptions: [25, 50, 100],
        }}
        toolbar={{
          inputSearch: {
            value: search,
            placeholder: formatMessage(messages.searchPlaceholder),
            onChange: handleInputSearchChange,
            onClear: handleInputSearchClear,
            onSubmit: handleInputSearchSubmit,
          },
          // Hiding this because the dropdown renders off of the screen
          // fields: {
          //   label: '',
          //   showAllLabel: '',
          //   hideAllLabel: '',
          // },
        }}
        sort={{
          sortedBy,
          sortOrder,
        }}
        onSort={handleSort}
        filters={{
          alwaysVisibleFilters: ['status'],
          statements: filterState.filterStatements,
          onChangeStatements: handleFiltersChange,
          clearAllFiltersButtonLabel: formatMessage(messages.clearFilters),
          collapseLeft: true,
          options: {
            status: {
              label: formatMessage(messages.statusFilter),
              renderFilterLabel: (st: any) => {
                if (!st || !st.object) {
                  // you should treat empty object cases only for alwaysVisibleFilters
                  return formatMessage(messages.filtersAll)
                }

                const keys = st.object ? Object.keys(st.object) : []
                const isAllTrue = !keys.some(key => !st.object[key])
                const isAllFalse = !keys.some(key => st.object[key])
                const trueKeys = keys.filter(key => st.object[key])
                let trueKeysLabel = ''

                trueKeys.forEach((key, index) => {
                  trueKeysLabel += `${key}${
                    index === trueKeys.length - 1 ? '' : ', '
                  }`
                })

                return `${
                  isAllTrue
                    ? formatMessage(messages.filtersAll)
                    : isAllFalse
                    ? formatMessage(messages.filtersNone)
                    : `${trueKeysLabel}`
                }`
              },
              verbs: [
                {
                  label: formatMessage(messages.filtersIncludes),
                  value: 'includes',
                  object: statusSelectorObject,
                },
              ],
            },
          },
        }}
      />
    </Fragment>
  )
}

export default OrganizationRequestsTable

```


[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 b2b-organizations/react/admin/CostCenterDetails.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent } from 'react'
import React, { useEffect, useState } from 'react'
import { useQuery, useMutation, useLazyQuery } from 'react-apollo'
import {
  Layout,
  PageHeader,
  PageBlock,
  Button,
  Spinner,
  Input,
  Modal,
  ModalDialog,
  Toggle,
  Tag,
} from 'vtex.styleguide'
import { useToast } from '@vtex/admin-ui'
import { useIntl, FormattedMessage } from 'react-intl'
import { useRuntime } from 'vtex.render-runtime'
import { useCssHandles } from 'vtex.css-handles'
import {
  AddressRules,
  AddressForm,
  AddressContainer,
  CountrySelector,
  PostalCodeGetter,
} from 'vtex.address-form'
import { StyleguideInput } from 'vtex.address-form/inputs'
import { addValidation } from 'vtex.address-form/helpers'

import {
  costCenterMessages as messages,
  organizationCustomFieldsMessages as orgaizationMessages,
} from './utils/messages'
import { setGUID, getEmptyAddress, isValidAddress } from '../utils/addresses'
import { validatePhoneNumber } from '../modules/formValidators'
import GET_COST_CENTER from '../graphql/getCostCenter.graphql'
import GET_ORGANIZATION from '../graphql/getOrganization.graphql'
import UPDATE_COST_CENTER from '../graphql/updateCostCenter.graphql'
import SET_MARKETING_TAGS from '../graphql/setMarketingTags.graphql'
import GET_MARKETING_TAGS from '../graphql/getMarketingTags.graphql'
import DELETE_COST_CENTER from '../graphql/deleteCostCenter.graphql'
import GET_LOGISTICS from '../graphql/getLogistics.graphql'
import GET_B2B_CUSTOM_FIELDS from '../graphql/getB2BCustomFields.graphql'
import { joinById } from './OrganizationDetails'
import CustomFieldInput from './OrganizationDetailsCustomField'
import CostCenterAddressList from './CostCenterAddressList'
import { useOrgPermission } from '../hooks/useOrgPermission'
import { ORGANIZATION_EDIT } from '../utils/constants'

const CSS_HANDLES = ['businessDocument', 'stateRegistration'] as const

const CostCenterDetails: FunctionComponent = () => {
  const { formatMessage } = useIntl()

  const {
    culture: { country },
    route: { params },
    navigate,
  } = useRuntime()

  const handles = useCssHandles(CSS_HANDLES)
  const showToast = useToast()

  const [loadingState, setLoadingState] = useState(false)
  const [costCenterName, setCostCenterName] = useState('')
  const [phoneNumber, setPhoneNumber] = useState('')
  const [businessDocument, setBusinessDocument] = useState('')
  const [stateRegistration, setStateRegistration] = useState('')
  const [addresses, setAddresses] = useState([] as Address[])
  const [newAddressModalState, setNewAddressModalState] = useState({
    isOpen: false,
  })

  const [editAddressModalState, setEditAddressModalState] = useState({
    addressId: '',
    isOpen: false,
  })

  const [deleteAddressModalState, setDeleteAddressModalState] = useState({
    addressId: '',
    isOpen: false,
  })

  const [deleteCostCenterModalState, setDeleteCostCenterModalState] = useState({
    isOpen: false,
  })

  const [newAddressState, setNewAddressState] = useState(() =>
    addValidation(getEmptyAddress(country))
  )

  const [editAddressState, setEditAddressState] = useState(() =>
    addValidation(getEmptyAddress(country))
  )

  const [customFieldsState, setCustomFieldsState] = useState<CustomField[]>([])

  const [tags, setTags] = useState([] as string[])
  const [tagName, setTagName] = useState('')

  const { data, loading, refetch } = useQuery(GET_COST_CENTER, {
    variables: { id: params?.id },
    skip: !params?.id,
    ssr: false,
    fetchPolicy: 'network-only',
  })

  const { data: defaultCustomFieldsData } = useQuery(GET_B2B_CUSTOM_FIELDS, {
    variables: { id: params?.id },
    skip: !params?.id,
    ssr: false,
  })

  const [getOrganization, { data: organizationData }] = useLazyQuery(
    GET_ORGANIZATION
  )

  const { data: logisticsData } = useQuery(GET_LOGISTICS, { ssr: false })

  useQuery(GET_MARKETING_TAGS, {
    skip: !params?.id,
    ssr: false,
    fetchPolicy: 'network-only',
    variables: {
      costId: params?.id,
      fetchPolicy: 'network-only',
      skip: !params?.id,
      ssr: false,
    },
    onCompleted: _tags => {
      setTags(_tags?.getMarketingTags?.tags || [])
    },
  })

  const [updateCostCenter] = useMutation(UPDATE_COST_CENTER)
  const [deleteCostCenter] = useMutation(DELETE_COST_CENTER)
  const [setMarktingTags] = useMutation(SET_MARKETING_TAGS)

  const {
    data: canEditBuyerOrg,
    isLoading: permissionLoading,
  } = useOrgPermission({
    resourceCode: ORGANIZATION_EDIT,
  })

  const translateCountries = () => {
    const { shipsTo = [] } = logisticsData?.logistics ?? {}

    return shipsTo.map((code: string) => ({
      label: formatMessage({ id: `country.${code}` }),
      value: code,
    }))
  }

  const handleSetAddresses = (_addresses: Address[]) => {
    setAddresses(
      _addresses.map((item, index) => {
        item.checked = index === 0

        return item
      })
    )
  }

  const handleCheckDefault = (address: Address) => {
    setAddresses(
      addresses.map(item => {
        item.checked = item === address

        return item
      })
    )
  }

  useEffect(() => {
    if (!data?.getCostCenterById?.addresses?.length) return

    handleSetAddresses(data.getCostCenterById.addresses)
    setCostCenterName(data.getCostCenterById.name)

    if (data.getCostCenterById.businessDocument) {
      setBusinessDocument(data.getCostCenterById.businessDocument)
    }

    if (data.getCostCenterById.stateRegistration) {
      setStateRegistration(data.getCostCenterById.stateRegistration)
    }

    if (data.getCostCenterById.phoneNumber) {
      setPhoneNumber(data.getCostCenterById.phoneNumber)
    }

    getOrganization({
      variables: { id: data.getCostCenterById.organization },
    })
  }, [data])

  const navigateToParentOrganization = () => {
    navigate({
      page: 'admin.app.b2b-organizations.organization-details',
      params: { id: data.getCostCenterById.organization },
    })
  }

  const handleUpdateCostCenter = () => {
    setLoadingState(true)
    const _addresses = [...addresses]

    _addresses.sort(item => (item.checked ? -1 : 1))
    const variables = {
      id: params.id,
      input: {
        name: costCenterName,
        addresses: _addresses.map(item => {
          delete item.checked

          return item
        }),
        phoneNumber,
        businessDocument,
        customFields: customFieldsState,
        stateRegistration,
      },
    }

    setMarktingTags({
      variables: { tags, costId: params?.id },
    }).catch(error => console.error(error))

    updateCostCenter({ variables })
      .then(() => {
        showToast({
          variant: 'positive',
          message: formatMessage(messages.toastUpdateSuccess),
        })
        refetch()
        handleSetAddresses(_addresses)
        setLoadingState(false)
      })
      .catch(error => {
        console.error(error)
        showToast({
          variant: 'critical',
          message: formatMessage(messages.toastUpdateFailure),
        })
        setLoadingState(false)
      })
  }

  const handleDeleteCostCenter = () => {
    // TODO: add modal dialog
    setLoadingState(true)
    deleteCostCenter({ variables: { id: params?.id } })
      .then(() => {
        navigateToParentOrganization()
      })
      .catch(error => {
        console.error(error)
        showToast({
          variant: 'critical',
          message: formatMessage(messages.toastDeleteFailure),
        })
        setLoadingState(false)
      })
  }

  const handleCloseModals = () => {
    setDeleteAddressModalState({ addressId: '', isOpen: false })
    setEditAddressModalState({ addressId: '', isOpen: false })
    setEditAddressState(() => addValidation(getEmptyAddress(country)))
    setNewAddressModalState({ isOpen: false })
    setNewAddressState(() => addValidation(getEmptyAddress(country)))
    setDeleteCostCenterModalState({ isOpen: false })
  }

  const handleNewAddressModal = () => {
    setNewAddressModalState({ isOpen: true })
  }

  const handleEditAddressModal = (addressId: string) => {
    const editAddress = addresses.find(
      address => address.addressId === addressId
    )

    if (!editAddress) return

    setEditAddressState(() => addValidation(editAddress))
    setEditAddressModalState({ addressId, isOpen: true })
  }

  const handleDeleteAddressModal = (addressId: string) => {
    setDeleteAddressModalState({ addressId, isOpen: true })
  }

  const handleDeleteCostCenterModal = () => {
    setDeleteCostCenterModalState({ isOpen: true })
  }

  const handleNewAddressChange = (changedAddress: AddressFormFields) => {
    const curAddress = newAddressState

    const newAddress = { ...curAddress, ...changedAddress }

    setNewAddressState(newAddress)
  }

  const handleAddNewAddress = () => {
    const uid = setGUID(newAddressState)

    const duplicated = data?.getCostCenterById?.addresses?.find(
      (item: any) => item.addressId === uid
    )

    setLoadingState(true)

    let isDuplicatedError = false

    if (duplicated !== undefined) {
      isDuplicatedError =
        duplicated.postalCode === newAddressState.postalCode.value
    }

    if (!isDuplicatedError) {
      const newAddress = {
        addressId: uid,
        addressType: newAddressState.addressType.value,
        city: newAddressState.city.value,
        complement: newAddressState.complement.value,
        country: newAddressState.country.value,
        receiverName: newAddressState.receiverName.value,
        geoCoordinates: newAddressState.geoCoordinates.value,
        neighborhood: newAddressState.neighborhood.value,
        number: newAddressState.number.value,
        postalCode: newAddressState.postalCode.value,
        reference: newAddressState.reference.value,
        state: newAddressState.state.value,
        street: newAddressState.street.value,
        addressQuery: newAddressState.addressQuery.value,
        checked: false,
      }

      const newAddresses = [...addresses, newAddress]

      const variables = {
        id: params.id,
        input: {
          addresses: newAddresses
            .sort(item => (item.checked ? -1 : 1))
            .map(item => {
              return {
                ...item,
                checked: undefined,
              }
            }),
        },
      }

      updateCostCenter({ variables })
        .then(() => {
          showToast({
            variant: 'positive',
            message: formatMessage(messages.toastUpdateSuccess),
          })
          refetch()
          handleSetAddresses(newAddresses)
          setLoadingState(false)
          handleCloseModals()
        })
        .catch(error => {
          console.error(error)
          showToast({
            variant: 'critical',
            message: formatMessage(messages.toastUpdateFailure),
          })
          setLoadingState(false)
        })

      setAddresses(
        newAddresses.map(item => {
          if (newAddressState.checked) {
            item.checked = item === newAddress
          }

          return item
        })
      )
    } else {
      showToast({
        variant: 'critical',
        message: formatMessage(messages.duplicateAddress),
      })
    }
  }

  const handleEditAddressChange = (changedAddress: AddressFormFields) => {
    const curAddress = editAddressState

    const newAddress = { ...curAddress, ...changedAddress }

    setEditAddressState(newAddress)
  }

  const handleEditAddress = () => {
    const { addressId } = editAddressModalState
    const addressArray = addresses

    const addressIndex = addresses.findIndex(
      address => address.addressId === addressId
    )

    addressArray[addressIndex] = {
      addressId: editAddressState.addressId.value,
      addressType: editAddressState.addressType.value,
      city: editAddressState.city.value,
      complement: editAddressState.complement.value,
      country: editAddressState.country.value,
      receiverName: editAddressState.receiverName.value,
      geoCoordinates: editAddressState.geoCoordinates.value,
      neighborhood: editAddressState.neighborhood.value,
      number: editAddressState.number.value,
      postalCode: editAddressState.postalCode.value,
      reference: editAddressState.reference.value,
      state: editAddressState.state.value,
      street: editAddressState.street.value,
      addressQuery: editAddressState.addressQuery.value,
    }

    setLoadingState(true)

    const variables = {
      id: params.id,
      input: {
        addresses: addressArray
          .sort(item => (item.checked ? -1 : 1))
          .map(item => {
            return {
              ...item,
              checked: undefined,
            }
          }),
      },
    }

    updateCostCenter({ variables })
      .then(() => {
        showToast({
          variant: 'positive',
          message: formatMessage(messages.toastUpdateSuccess),
        })
        refetch()
        handleSetAddresses(addressArray)
        setLoadingState(false)
        handleCloseModals()
      })
      .catch(error => {
        console.error(error)
        showToast({
          variant: 'critical',
          message: formatMessage(messages.toastUpdateFailure),
        })
        setLoadingState(false)
      })
  }

  const handleDeleteAddress = () => {
    const { addressId } = deleteAddressModalState
    const addressArray = addresses

    const addressIndex = addresses.findIndex(
      address => address.addressId === addressId
    )

    addresses.splice(addressIndex, 1)

    setLoadingState(true)

    const variables = {
      id: params.id,
      input: {
        addresses: addresses.map(item => {
          return {
            ...item,
            checked: undefined,
          }
        }),
      },
    }

    updateCostCenter({ variables })
      .then(() => {
        showToast({
          variant: 'positive',
          message: formatMessage(messages.toastUpdateSuccess),
        })
        refetch()
        handleSetAddresses(addressArray)
        setLoadingState(false)
        handleCloseModals()
      })
      .catch(error => {
        console.error(error)
        showToast({
          variant: 'critical',
          message: formatMessage(messages.toastUpdateFailure),
        })
        setLoadingState(false)
      })
  }

  const handleMarketingTags = (tagValue: string) => {
    setTags([...tags, tagValue])
    setTagName('')
  }

  const removeMarketingTag = (tagValue: string) => {
    setTags(tags.filter(_tag => _tag !== tagValue))
  }

  // CostCenter custom fields

  useEffect(() => {
    const customFieldsToShow = joinById(
      data?.getCostCenterById?.customFields || [],
      defaultCustomFieldsData?.getB2BSettings.costCenterCustomFields || []
    )

    setCustomFieldsState(customFieldsToShow)
  }, [
    data?.getCostCenterById?.customFields &&
      defaultCustomFieldsData?.getB2BSettings.costCenterCustomFields,
  ])

  const handleCustomFieldsUpdate = (
    index: number,
    customField: CustomField
  ) => {
    const newCustomFields = [...customFieldsState]

    newCustomFields[index] = customField
    setCustomFieldsState(newCustomFields)
  }

  if (!data) {
    return (
      <Layout
        fullWidth
        pageHeader={
          <PageHeader
            title={formatMessage(messages.pageTitle)}
            linkLabel={formatMessage(messages.back)}
            onLinkClick={() => {
              navigate({
                page: 'admin.app.b2b-organizations.organizations',
              })
            }}
          />
        }
      >
        <PageBlock>
          {loading ? (
            <Spinner />
          ) : (
            <FormattedMessage id="admin/b2b-organizations.costCenter-details.empty-state" />
          )}
        </PageBlock>
      </Layout>
    )
  }

  const isSaveButtonDisabled = () => {
    const hasRequiredFields = costCenterName && addresses.length > 0
    const hasValidPhoneNumber = !phoneNumber || validatePhoneNumber(phoneNumber)
    const hasPermission = canEditBuyerOrg && !permissionLoading

    return !hasRequiredFields || !hasValidPhoneNumber || !hasPermission
  }

  const isDeleteButtonDisabled = () => {
    return !canEditBuyerOrg || permissionLoading
  }

  return (
    <Layout
      fullWidth
      pageHeader={
        <PageHeader
          title={formatMessage(messages.pageTitle)}
          linkLabel={
            organizationData?.getOrganizationById?.name ??
            formatMessage(messages.back)
          }
          onLinkClick={() => {
            navigateToParentOrganization()
          }}
        >

            <Button
              variation="primary"
              isLoading={loadingState}
              disabled={isSaveButtonDisabled()}
              onClick={() => handleUpdateCostCenter()}
            >
              <FormattedMessage id="admin/b2b-organizations.costCenter-details.button.save" />
            </Button>

          <Button
            variation="danger"
            isLoading={loadingState}
            disabled={isDeleteButtonDisabled()}
            onClick={() => handleDeleteCostCenterModal()}
          >
            <FormattedMessage id="admin/b2b-organizations.costCenter-details.button.delete" />
          </Button>
        </PageHeader>
      }
    >
      <PageBlock>
        <Input
          autocomplete="off"
          size="large"
          label={formatMessage(messages.costCenterName)}
          value={costCenterName}
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            setCostCenterName(e.target.value)
          }}
          required
        />

          <Input
            autocomplete="off"
            size="large"
            label={formatMessage(messages.phoneNumber)}
            helpText={formatMessage(messages.phoneNumberHelp)}
            error={phoneNumber && !validatePhoneNumber(phoneNumber)}
            value={phoneNumber}
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              setPhoneNumber(e.target.value)
            }}
          />

        <div className={`${handles.businessDocument} mt6`}>
          <Input
            autocomplete="off"
            size="large"
            label={formatMessage(messages.businessDocument)}
            helpText={formatMessage(messages.businessDocumentHelp)}
            value={businessDocument}
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              setBusinessDocument(e.target.value)
            }}
          />

        <div className={`${handles.stateRegistration} mt6`}>
          <Input
            autocomplete="off"
            size="large"
            label={formatMessage(messages.stateRegistration)}
            helpText={formatMessage(messages.stateRegistrationHelp)}
            value={stateRegistration}
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              setStateRegistration(e.target.value)
            }}
          />

              <Input
                autocomplete="off"
                size="large"
                label={formatMessage(messages.marketingTags)}
                value={tagName}
                onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                  setTagName(e.target.value)
                }}
              />

              <Button
                variation="primary"
                onClick={() => handleMarketingTags(tagName)}
                disabled={!tagName || tagName.length === 0}
              >
                {formatMessage(messages.add)}
              </Button>

            {tags?.map((tag: string) => (

                <Tag onClick={() => removeMarketingTag(tag)}>{tag}</Tag>

            ))}

      </PageBlock>
      <PageBlock title={formatMessage(messages.addresses)}>
        <CostCenterAddressList
          addressList={addresses}
          handleCheckDefault={handleCheckDefault}
          handleNewAddressModal={handleNewAddressModal}
          handleEditAddressModal={handleEditAddressModal}
          handleDeleteAddressModal={handleDeleteAddressModal}
        />
      </PageBlock>

      <PageBlock title={formatMessage(orgaizationMessages.customFieldsTitle)}>
        {customFieldsState?.map((customField: CustomField, index: number) => (
          <CustomFieldInput
            key={`${customField.name}`}
            customField={customField}
            index={index}
            handleUpdate={handleCustomFieldsUpdate}
          />
        ))}
      </PageBlock>

      {/* New Address Modal */}
      <Modal
        centered
        bottomBar={

              <Button
                variation="tertiary"
                onClick={() => handleCloseModals()}
                disabled={loadingState}
              >
                {formatMessage(messages.cancel)}
              </Button>

              <Button
                variation="primary"
                onClick={() => handleAddNewAddress()}
                isLoading={loadingState}
                disabled={
                  (phoneNumber && !validatePhoneNumber(phoneNumber)) ||
                  !isValidAddress(newAddressState)
                }
              >
                {formatMessage(messages.add)}
              </Button>

        }
        isOpen={newAddressModalState.isOpen}
        onClose={() => handleCloseModals()}
        closeOnOverlayClick={false}
      >
        <AddressRules
          country={newAddressState?.country?.value}
          shouldUseIOFetching
          useGeolocation={false}
        >
          <AddressContainer
            address={newAddressState}
            Input={StyleguideInput}
            onChangeAddress={handleNewAddressChange}
            autoCompletePostalCode
          >
            <CountrySelector shipsTo={translateCountries()} />

            <PostalCodeGetter />

            <AddressForm
              Input={StyleguideInput}
              omitAutoCompletedFields={false}
              omitPostalCodeFields
            />

              <Toggle
                checked={newAddressState.checked}
                onChange={() =>
                  setNewAddressState((prevState: AddressFormFields) => ({
                    ...newAddressState,
                    checked: !prevState.checked,
                  }))
                }
                label={formatMessage(messages.defaultAddress)}
              />

          </AddressContainer>
        </AddressRules>
      </Modal>

      {/* Edit Address Modal */}
      <Modal
        centered
        bottomBar={

              <Button
                variation="tertiary"
                onClick={() => handleCloseModals()}
                disabled={loadingState}
              >
                {formatMessage(messages.cancel)}
              </Button>

              <Button
                variation="primary"
                onClick={() => handleEditAddress()}
                isLoading={loadingState}
                disabled={!isValidAddress(editAddressState)}
              >
                {formatMessage(messages.update)}
              </Button>

        }
        isOpen={editAddressModalState.isOpen}
        onClose={() => handleCloseModals()}
        closeOnOverlayClick={false}
      >
        <AddressRules
          country={editAddressState?.country?.value}
          shouldUseIOFetching
          useGeolocation={false}
        >
          <AddressContainer
            address={editAddressState}
            Input={StyleguideInput}
            onChangeAddress={handleEditAddressChange}
            autoCompletePostalCode
          >
            <CountrySelector shipsTo={translateCountries()} />

            <PostalCodeGetter />

            <AddressForm
              Input={StyleguideInput}
              omitAutoCompletedFields={false}
              omitPostalCodeFields
            />
          </AddressContainer>
        </AddressRules>
      </Modal>

      {/* Delete Address Modal */}
      <ModalDialog
        centered
        confirmation={{
          onClick: () => handleDeleteAddress(),
          label: formatMessage(messages.deleteConfirm),
        }}
        cancelation={{
          onClick: () => handleCloseModals(),
          label: formatMessage(messages.cancel),
        }}
        loading={loadingState}
        isOpen={deleteAddressModalState.isOpen}
        onClose={() => handleCloseModals()}
        closeOnOverlayClick={false}
      >

          <FormattedMessage id="admin/b2b-organizations.costCenter-details.delete-address-confirmation" />

      </ModalDialog>

      {/* Delete Cost Center Modal */}
      <ModalDialog
        centered
        confirmation={{
          onClick: () => handleDeleteCostCenter(),
          label: formatMessage(messages.deleteConfirm),
        }}
        cancelation={{
          onClick: () => handleCloseModals(),
          label: formatMessage(messages.cancel),
        }}
        loading={loadingState}
        isOpen={deleteCostCenterModalState.isOpen}
        onClose={() => handleCloseModals()}
        closeOnOverlayClick={false}
      >

          <FormattedMessage id="admin/b2b-organizations.costCenter-details.delete-costCenter-confirmation" />

      </ModalDialog>
    </Layout>
  )
}

export default CostCenterDetails

```


[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 b2b-organizations/react/admin/HasAccess.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useEffect, useState } from 'react'
import { Alert } from 'vtex.styleguide'
import { FormattedMessage } from 'react-intl'
import { Spinner } from '@vtex/admin-ui'

import { useOrgPermission } from '../hooks/useOrgPermission'
import type { ORGANIZATION_EDIT } from '../utils/constants'
import { ORGANIZATION_VIEW } from '../utils/constants'

interface HasAccessProps {
  children: React.ReactElement
  permission?: typeof ORGANIZATION_EDIT | typeof ORGANIZATION_VIEW
}

export default function HasAccess({
  children,
  permission = ORGANIZATION_VIEW,
}: HasAccessProps) {
  const { data: canBuyerOrgPermission, isLoading } = useOrgPermission({
    resourceCode: permission,
  })

  const [showTimeoutError, setShowTimeoutError] = useState(false)

  useEffect(() => {
    let timeoutId: ReturnType<typeof setTimeout>

    // Only set timeout if data is undefined (not loading)
    if (!isLoading && canBuyerOrgPermission === undefined) {
      // Set a timeout of 10 seconds to show an error message
      timeoutId = setTimeout(() => {
        setShowTimeoutError(true)
      }, 10000)
    } else {
      setShowTimeoutError(false)
    }

    return () => {
      if (timeoutId) {
        clearTimeout(timeoutId)
      }
    }
  }, [isLoading, canBuyerOrgPermission])

  if (showTimeoutError) {
    return (
      <div className="pa7">
        <Alert type="warning">
          <FormattedMessage
            id="admin/b2b-organizations.organizations.permission-timeout-message"
            defaultMessage="Permission check is taking longer than expected. Please refresh the page or try again later."
          />
        </Alert>
      </div>
    )
  }

  if (isLoading || canBuyerOrgPermission === undefined) {
    return (
      <div className="pa7">
        <Spinner />
      </div>
    )
  }

  if (canBuyerOrgPermission) {
    return children
  }

  return (
    <div className="pa7">
      <Alert type="error">
        <FormattedMessage id="admin/b2b-organizations.organizations.not-allow-view-message" />
      </Alert>
    </div>
  )
}

```


[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 b2b-organizations/react/admin/CustomFieldsTable.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import {
  Table,
  TBody,
  TBodyRow,
  TBodyCell,
  THead,
  THeadCell,
  useTableState,
  createColumns,
  Tag,
  IconEye,
  IconTrash,
} from '@vtex/admin-ui'
import { Dropdown, Modal, Button } from 'vtex.styleguide'
import { useIntl } from 'react-intl'

import {
  costCenterMessages,
  organizationMessages as messages,
  organizationCustomFieldsMessages as customFieldsMessages,
} from './utils/messages'
import DefaultCustomField from './DefaultCustomField'

interface CustomFieldsTableProps {
  customFields: CustomField[]
  handleDelete: (index: number) => void
  handleUpdate: (index: number, customField: any) => void
}

const CustomFieldsTable: React.FC<CustomFieldsTableProps> = ({
  customFields,
  handleDelete,
  handleUpdate,
}) => {
  const [isModalOpen, setIsModalOpen] = React.useState(false)

  const toggleModal = () => {
    setIsModalOpen(!isModalOpen)
  }

  const [
    selectedCustomFieldIndex,
    setSelectedCustomFieldIndex,
  ] = React.useState<number>(0)

  // save custom filed locally
  const [localCustomField, setLocalCustomField] = React.useState<CustomField>({
    name: '',
    type: 'text',
    value: '',
    dropdownValues: [],
    useOnRegistration: false,
  })

  const handleUpdateLocal = (customField: CustomField) => {
    setLocalCustomField(customField)
  }

  const { formatMessage } = useIntl()

  const columns = createColumns([
    {
      id: 'name',
      header: formatMessage(customFieldsMessages.customFieldsTableFieldName),
      width: '2fr',
    },
    {
      id: 'type',
      header: formatMessage(customFieldsMessages.customFieldsTableFieldType),
      width: '2fr',
    },
    {
      id: 'dropdownValues',
      header: formatMessage(
        customFieldsMessages.customFieldsTableDropdownPreview
      ),
      width: '2fr',
      resolver: {
        type: 'root',
        render: ({ item }) => {
          return item.dropdownValues?.length ? (
            <Dropdown
              aria-label={formatMessage(
                customFieldsMessages.customFieldsTableDropdownPreview
              )}
              size="medium"
              options={item.dropdownValues ?? []}
              value={item.dropdownValues[0].value}
            ></Dropdown>
          ) : (
            <div></div>
          )
        },
      },
    },
    {
      id: 'useOnRegistration',
      header: formatMessage(
        customFieldsMessages.customFieldsTableUseOnRegistration
      ),
      width: '1fr',
      resolver: {
        type: 'root',
        render: ({ item }) => {
          return (
            <Tag
              // if not set comes as null, converting to boolean
              label={`${!!item.useOnRegistration}`}
              variant={item.useOnRegistration ? 'green' : 'gray'}
              size="normal"
            ></Tag>
          )
        },
      },
    },
    {
      id: 'menu',
      resolver: {
        type: 'menu',
        actions: [
          {
            label: formatMessage(costCenterMessages.addressEdit),
            icon: <IconEye />,
            onClick: item => {
              toggleModal()

              const index = customFields.findIndex(
                customField => customField === item
              )

              setSelectedCustomFieldIndex(index)
              setLocalCustomField(item)
            },
          },
          {
            label: formatMessage(costCenterMessages.addressDelete),
            icon: <IconTrash />,
            onClick: item => {
              const index = customFields.findIndex(
                customField => customField === item
              )

              handleDelete(index)
            },
          },
        ],
      },
    },
  ])

  const { data, getBodyCell, getHeadCell, getTable } = useTableState({
    columns,
    // item type is built using columns which makes it incompatible with customFields type
    items: customFields as any,
  })

  return (
    <>
      <Table {...getTable()}>
        <THead>
          {columns.map(column => (
            <THeadCell {...getHeadCell(column)} />
          ))}
        </THead>
        <TBody>
          {data.map((item: any) => {
            return (
              <TBodyRow key={item.name}>
                {columns.map(column => {
                  return <TBodyCell {...getBodyCell(column, item)} />
                })}
              </TBodyRow>
            )
          })}
        </TBody>

        <Modal
          isOpen={isModalOpen}
          onClose={toggleModal}
          responsiveFullScreen
          bottomBar={
            <div className="nowrap">
              <span className="mr4">
                <Button variation="tertiary" onClick={toggleModal}>
                  {formatMessage(messages.cancel)}
                </Button>
              </span>
              <span>
                <Button
                  variation="primary"
                  onClick={() => {
                    toggleModal()
                    handleUpdate(selectedCustomFieldIndex, localCustomField)
                  }}
                >
                  {formatMessage(costCenterMessages.update)}
                </Button>
              </span>
            </div>
          }
        >
          <DefaultCustomField
            customField={localCustomField}
            name={formatMessage(customFieldsMessages.customFieldsTitleSingular)}
            handleUpdate={handleUpdateLocal}
          />
        </Modal>
      </Table>
    </>
  )
}

export default CustomFieldsTable

```


[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 b2b-organizations/react/admin/OrganizationsTable.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import {
  Page,
  PageHeader,
  PageHeaderTop,
  PageHeaderTitle,
  PageHeaderBottom,
  TabList,
  Tab,
  PageContent,
  useTabState,
  PageHeaderActions,
} from '@vtex/admin-ui'
import { useIntl } from 'react-intl'
import { HashRouter, Switch, Route } from 'react-router-dom'

import {
  organizationMessages as messages,
  organizationRequestMessages as requestMessages,
  organizationSettingsMessages as settingsMessages,
} from './utils/messages'
import OrganizationsList from './OrganizationsList'
import OrganizationRequestsTable from './OrganizationRequestsTable'
import OrganizationSettings from './OrganizationSettings'
import useHashRouter from './OrganizationDetails/useHashRouter'
import OrganizationCustomFields from './CustomFields'
import CheckCustomerSchema from '../components/CheckCustomerSchema'
import BulkImportList from '../components/BulkImportList'
import CreateOrganizationButton from '../components/CreateOrganizationButton'
import HasAccess from './HasAccess'

const SESSION_STORAGE_KEY = 'organization-tab'

const OrganizationsTable = () => {
  const { formatMessage } = useIntl()
  const { tab, handleTabChange, routerRef } = useHashRouter({
    sessionKey: SESSION_STORAGE_KEY,
    defaultPath: 'organizations',
    routes: ['organizations', 'requests', 'settings', 'custom-fields'],
  })

  const tabsState = useTabState({ selectedId: tab })

  return (
    <HashRouter ref={routerRef}>
      <HasAccess>
        <Page>
          <PageHeader>
            <PageHeaderTop>
              <PageHeaderTitle>
                {formatMessage(messages.tablePageTitle)}
              </PageHeaderTitle>
              <PageHeaderActions>
                <Route
                  path="/organizations"
                  exact
                  component={CreateOrganizationButton}
                />
              </PageHeaderActions>
            </PageHeaderTop>
            <PageHeaderBottom>
              <TabList state={tabsState}>
                <Tab
                  id="organizations"
                  onClick={() => handleTabChange('organizations')}
                >
                  {formatMessage(messages.tablePageTitle)}
                </Tab>

                <Tab id="requests" onClick={() => handleTabChange('requests')}>
                  {formatMessage(requestMessages.tablePageTitle)}
                </Tab>

                <Tab id="settings" onClick={() => handleTabChange('settings')}>
                  {formatMessage(settingsMessages.tablePageTitle)}
                </Tab>

                <Tab
                  id="custom-fields"
                  onClick={() => handleTabChange('custom-fields')}
                >
                  {formatMessage(settingsMessages.customFieldsTitle)}
                </Tab>
              </TabList>
            </PageHeaderBottom>
          </PageHeader>
          <PageContent layout="wide">
            <BulkImportList />
            <CheckCustomerSchema isAdmin={true} />
            <Switch>
              <Route
                path="/organizations"
                exact
                component={OrganizationsList}
              />
              <Route
                path="/requests"
                exact
                component={OrganizationRequestsTable}
              />
              <Route path="/settings" exact component={OrganizationSettings} />
              <Route
                path="/custom-fields"
                exact
                component={OrganizationCustomFields}
              />
            </Switch>
          </PageContent>
        </Page>
      </HasAccess>
    </HashRouter>
  )
}

export default OrganizationsTable

```


[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 b2b-organizations/react/admin/OrganizationRequestDetails.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent, ChangeEvent } from 'react'
import React, { useState, Fragment } from 'react'
import { useQuery, useMutation } from 'react-apollo'
import {
  Layout,
  PageHeader,
  PageBlock,
  Tag,
  Textarea,
  Spinner,
  Button,
} from 'vtex.styleguide'
import { useToast } from '@vtex/admin-ui'
import { useIntl, FormattedMessage } from 'react-intl'
import { useRuntime } from 'vtex.render-runtime'
import { AddressRules, AddressSummary } from 'vtex.address-form'

import { organizationRequestMessages as messages } from './utils/messages'
import { labelTypeByStatusMap } from './OrganizationRequestsTable'
import GET_ORGANIZATION_REQUEST from '../graphql/getOrganizationRequest.graphql'
import UPDATE_ORGANIZATION_REQUEST from '../graphql/updateOrganizationRequest.graphql'
import { useOrgPermission } from '../hooks/useOrgPermission'
import { ORGANIZATION_EDIT } from '../utils/constants'

const OrganizationRequestDetails: FunctionComponent = () => {
  const { formatMessage, formatDate } = useIntl()

  const {
    route: { params },
    navigate,
  } = useRuntime()

  const showToast = useToast()

  const [notesState, setNotesState] = useState('')
  const [loadingState, setLoadingState] = useState(false)

  const { data, loading, refetch } = useQuery(GET_ORGANIZATION_REQUEST, {
    variables: { id: params?.id },
    skip: !params?.id,
  })

  const { data: canEditBuyerOrg } = useOrgPermission({
    resourceCode: ORGANIZATION_EDIT,
  })

  const [updateOrganizationRequest] = useMutation(UPDATE_ORGANIZATION_REQUEST)

  const handleUpdateRequest = (status: string) => {
    setLoadingState(true)
    const variables = {
      id: params?.id,
      status,
      notes: notesState,
    }

    if (
      !data.getOrganizationRequestById.b2bCustomerAdmin &&
      status === 'approved'
    ) {
      showToast({
        variant: 'critical',
        message: formatMessage(messages.toastUpdateFailureNoEmail),
      })

      setLoadingState(false)

      return
    }

    updateOrganizationRequest({ variables })
      .then(() => {
        setLoadingState(false)
        showToast({
          variant: 'positive',
          message:
            status === 'approved'
              ? formatMessage(messages.toastCreatedSuccess)
              : formatMessage(messages.toastUpdateSuccess),
        })
        refetch({ id: params?.id })
      })
      .catch(error => {
        setLoadingState(false)
        console.error(error)
        showToast({
          variant: 'critical',
          message: formatMessage(messages.toastUpdateFailure),
        })
      })
  }

  if (!data) {
    return (
      <Layout
        fullWidth
        pageHeader={
          <PageHeader
            title={formatMessage(messages.detailsPageTitle)}
            linkLabel={formatMessage(messages.back)}
            onLinkClick={() => {
              navigate({
                page: 'admin.app.b2b-organizations.organization-requests',
              })
            }}
          />
        }
      >
        <PageBlock>
          {loading ? (
            <Spinner />
          ) : (
            <FormattedMessage id="admin/b2b-organizations.organization-request-details.empty-state" />
          )}
        </PageBlock>
      </Layout>
    )
  }

  return (
    <Layout
      fullWidth
      pageHeader={
        <PageHeader
          title={formatMessage(messages.detailsPageTitle)}
          linkLabel={formatMessage(messages.back)}
          onLinkClick={() => {
            navigate({
              page: 'admin.app.b2b-organizations.organizations',
            })
          }}
        />
      }
    >
      <PageBlock>
        <h4 className="t-heading-5 mb0 mt0">
          <FormattedMessage id="admin/b2b-organizations.organization-request-details.request-status" />
        </h4>
        <div className="mt4">
          <Tag
            type={labelTypeByStatusMap[data.getOrganizationRequestById.status]}
          >
            {data.getOrganizationRequestById.status}
          </Tag>
        </div>
        <h4 className="t-heading-5 mb0 pt4">
          <FormattedMessage id="admin/b2b-organizations.organization-request-details.created" />
        </h4>
        <div className="mv3">
          {formatDate(data.getOrganizationRequestById.created, {
            day: 'numeric',
            month: 'numeric',
            year: 'numeric',
          })}
        </div>
        <h4 className="t-heading-5 mb0 pt4">
          <FormattedMessage id="admin/b2b-organizations.organization-request-details.organization-name" />
        </h4>
        <div className="mv3">{data.getOrganizationRequestById.name}</div>
        {data.getOrganizationRequestById.tradeName && (
          <Fragment>
            <h4 className="t-heading-5 mb0 pt4">
              <FormattedMessage id="admin/b2b-organizations.organization-request-details.tradeName" />
            </h4>
            <div className="mv3">
              {data.getOrganizationRequestById.tradeName}
            </div>
          </Fragment>
        )}
        <h4 className="t-heading-5 mb0 pt4">
          <FormattedMessage id="admin/b2b-organizations.organization-request-details.b2b-customer-admin" />
        </h4>
        <div className="mv3">
          {data.getOrganizationRequestById.b2bCustomerAdmin?.email}
        </div>
        <h4 className="t-heading-5 mb0 pt4">
          <FormattedMessage id="admin/b2b-organizations.organization-request-details.default-cost-center" />
        </h4>
        <div className="mt4">
          {data.getOrganizationRequestById.defaultCostCenter.name}
          {data.getOrganizationRequestById.defaultCostCenter.phoneNumber && (
            <div>
              <FormattedMessage
                id="admin/b2b-organizations.organization-request-details.default-cost-center.phoneNumber"
                values={{
                  phoneNumber:
                    data.getOrganizationRequestById.defaultCostCenter
                      .phoneNumber,
                }}
              />
            </div>
          )}
          {data.getOrganizationRequestById.defaultCostCenter
            .businessDocument && (
            <div>
              <FormattedMessage
                id="admin/b2b-organizations.organization-request-details.default-cost-center.businessDocument"
                values={{
                  businessDocument:
                    data.getOrganizationRequestById.defaultCostCenter
                      .businessDocument,
                }}
              />
            </div>
          )}
          {data.getOrganizationRequestById.defaultCostCenter
            .stateRegistration && (
            <div>
              <FormattedMessage
                id="admin/b2b-organizations.organization-request-details.default-cost-center.stateRegistration"
                values={{
                  stateRegistration:
                    data.getOrganizationRequestById.defaultCostCenter
                      .stateRegistration,
                }}
              />
            </div>
          )}
          <br />
          <AddressRules
            country={
              data.getOrganizationRequestById.defaultCostCenter.address.country
            }
            useGeolocation={false}
            shouldUseIOFetching
          >
            <AddressSummary
              canEditData={false}
              address={
                data.getOrganizationRequestById.defaultCostCenter.address
              }
            />
          </AddressRules>
          <br />
          {data.getOrganizationRequestById.customFields && (
            <>
              <h5>
                <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFields" />
              </h5>
              {data.getOrganizationRequestById.customFields.map(
                (item: CustomField) => (
                  <div className="pt2 pb2">
                    <div>
                      <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFields.name" />
                      : {item.name}
                    </div>
                    <div>
                      <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFields.type" />
                      : {item.type}
                    </div>
                    <div>
                      <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFields.value" />
                      : {item.value}
                    </div>
                    {item.dropdownValues && (
                      <div>
                        <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFields.dropdownValues" />
                        :{' '}
                        {item.dropdownValues.map(dropdown => (
                          <div className="pl2 mt2">
                            <div>
                              <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFields.label" />
                              : {dropdown.label}
                            </div>
                            <div>
                              <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFields.value" />
                              : {dropdown.value}
                            </div>
                          </div>
                        ))}
                      </div>
                    )}
                  </div>
                )
              )}
            </>
          )}
          {data?.getOrganizationRequestById?.defaultCostCenter?.customFields
            ?.length > 0 && (
            <>
              <h5>
                <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFieldsCostCenter" />
              </h5>
              {data?.getOrganizationRequestById?.defaultCostCenter?.customFields.map(
                (customField: CustomField) => (
                  <div className="pt2 pb2">
                    <div>
                      <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFields.name" />
                      : {customField.name}
                    </div>
                    <div>
                      <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFields.type" />
                      : {customField.type}
                    </div>
                    <div>
                      <FormattedMessage id="admin/b2b-organizations.organization-request-admin.customFields.value" />
                      : {customField.value}
                    </div>
                  </div>
                )
              )}
            </>
          )}
        </div>

        <div className="mt3">
          <Textarea
            label={
              <h4 className="t-heading-5 mb0 pt4">
                {formatMessage(messages.addNote)}
              </h4>
            }
            value={notesState}
            onChange={(e: ChangeEvent<HTMLInputElement>) => {
              setNotesState(e.target.value)
            }}
            characterCountdownText={
              <FormattedMessage
                id="admin/b2b-organizations.organization-request-details.add-note.charactersLeft"
                values={{ count: notesState.length }}
              />
            }
            maxLength="500"
            rows="4"
            disabled={data.getOrganizationRequestById.status !== 'pending'}
          />
        </div>
        <div className="mt3 flex">
          <Button
            variation="primary"
            onClick={() => handleUpdateRequest('approved')}
            isLoading={loadingState}
            disabled={
              data.getOrganizationRequestById.status !== 'pending' ||
              !canEditBuyerOrg
            }
          >
            <FormattedMessage id="admin/b2b-organizations.organization-request-details.button.approve" />
          </Button>
          <div className="ml2">
            <Button
              variation="danger"
              onClick={() => handleUpdateRequest('declined')}
              isLoading={loadingState}
              disabled={
                data.getOrganizationRequestById.status !== 'pending' ||
                !canEditBuyerOrg
              }
            >
              <FormattedMessage id="admin/b2b-organizations.organization-request-details.button.decline" />
            </Button>
          </div>
        </div>
      </PageBlock>
    </Layout>
  )
}

export default OrganizationRequestDetails

```


[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 b2b-organizations/react/admin/DefaultCustomField.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useState, useEffect } from 'react'
import { Input, IconDelete, IconPlus, Toggle, Dropdown } from 'vtex.styleguide'
import { Button, Flex, csx } from '@vtex/admin-ui'
import { useIntl } from 'react-intl'

import { organizationCustomFieldsMessages as customFieldsMessages } from './utils/messages'

interface CustomFieldProps {
  customField: CustomField
  name: string
  handleUpdate: (customField: CustomField) => void
}

interface DropdownValue {
  value: string
  label: string
}

const DefaultCustomField: React.FC<CustomFieldProps> = ({
  name,
  handleUpdate,
  customField,
  customField: { type, value, dropdownValues, useOnRegistration = false },
}) => {
  const { formatMessage } = useIntl()

  const [dropdownValuesLocal, setDropdownValuesLocal] = useState<
    DropdownValue[]
  >([])

  const [useOnRegistrationLocal, setUseOnRegistrationLocal] = useState(false)

  useEffect(() => {
    setDropdownValuesLocal(dropdownValues ?? [])
    setUseOnRegistrationLocal(!!useOnRegistration)
  }, [dropdownValues, useOnRegistration])

  const fieldOptionsNew = [
    {
      value: 'text',
      label: formatMessage(customFieldsMessages.customFieldsTextLabel),
    },
    {
      value: 'dropdown',
      label: formatMessage(customFieldsMessages.customFieldsDropdownLabel),
    },
  ]

  const [dropdownState, setDropdownState] = useState('text')

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const updatedCustomField = {
      ...customField,
      name: e.target.value,
    }

    handleUpdate(updatedCustomField)
  }

  const handleDropdownChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const updatedType = e.target.value === 'text' ? 'text' : 'dropdown'

    handleUpdate({
      ...customField,
      type: updatedType,
      dropdownValues: [],
    })
    setDropdownState(updatedType)
    setDropdownValuesLocal([])
  }

  const handleDropdownItemChange = (
    e: React.ChangeEvent<HTMLInputElement>,
    i: number,
    labelUpdate?: boolean
  ) => {
    const { value: eventValue } = e.target

    setDropdownValuesLocal(prev => {
      const newDropdownValues = [...prev]

      newDropdownValues[i] = {
        ...prev[i],
        ...(labelUpdate && { label: eventValue }),
        ...(!labelUpdate && { value: eventValue }),
      }

      const updatedCustomField = {
        name: value ?? name,
        type: dropdownState === 'dropdown' ? 'dropdown' : type,
        dropdownValues: newDropdownValues,
      }

      handleUpdate(updatedCustomField)

      return newDropdownValues
    })
  }

  const handleAddDropdownItem = () => {
    setDropdownValuesLocal(prevValues => [
      ...prevValues,
      { value: '', label: '' },
    ])
  }

  const handleDeleteDropdownItem = (i: number) => {
    setDropdownValuesLocal(prev => {
      const newDropdownValues = [...prev]

      newDropdownValues.splice(i, 1)

      return newDropdownValues
    })
  }

  const handleToggle = () => {
    setUseOnRegistrationLocal(prev => {
      const updatedCustomField = {
        ...customField,
        useOnRegistration: !prev,
      }

      handleUpdate(updatedCustomField)

      return !prev
    })
  }

  useEffect(() => {
    setDropdownState(
      type === 'dropdown' ? fieldOptionsNew[1].value : fieldOptionsNew[0].value
    )
  }, [type])

  return (
    <div className="w-100 mv6 flex">
      <Flex className="w-20 mr4 pv6">
        <Toggle
          checked={useOnRegistrationLocal}
          onChange={handleToggle}
          label={formatMessage(
            customFieldsMessages.customFieldsTableUseOnRegistration
          )}
        />
      </Flex>

      <Dropdown
        label={formatMessage(customFieldsMessages.customFieldsTableFieldType)}
        size="large"
        options={fieldOptionsNew ?? []}
        onChange={handleDropdownChange}
        value={dropdownState}
        className="w-100"
      />
      <Flex direction="column" className="w-100 ml4">
        <Input
          autocomplete="off"
          size="large"
          label={name}
          value={customField.name}
          type={type}
          onChange={handleChange}
        />
        {/* Create inputs for value and label of a dropdown */}
        {dropdownValuesLocal ? (
          <Flex direction="column" className="w-100">
            {dropdownValuesLocal?.map(
              (dropdownValue: DropdownValue, i: number) => (
                <Flex
                  direction="row"
                  align="end"
                  className="w-100"
                  key={`dropdown${dropdownValue.label}`}
                >
                  <Flex className="w-50 mr6">
                    <Input
                      autocomplete="off"
                      size="medium"
                      label={formatMessage(
                        customFieldsMessages.customFieldsDropdownLineValue
                      )}
                      value={dropdownValue.value}
                      type="text"
                      className="mr4 w-50"
                      style={{
                        border: '1px solid red',
                      }}
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                        handleDropdownItemChange(e, i)
                      }
                    />
                  </Flex>
                  <Input
                    autocomplete="off"
                    size="medium"
                    label={formatMessage(
                      customFieldsMessages.customFieldsDropdownLineLabel
                    )}
                    value={dropdownValue.label}
                    type="text"
                    onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                      handleDropdownItemChange(e, i, true)
                    }
                  />
                  <Button
                    icon={
                      <IconDelete
                        title={formatMessage(
                          customFieldsMessages.customFieldsRemoveDropdownLine
                        )}
                      />
                    }
                    aria-label={formatMessage(
                      customFieldsMessages.customFieldsRemoveDropdownLine
                    )}
                    variant="secondary"
                    onClick={() => handleDeleteDropdownItem(i)}
                    className={csx({
                      marginLeft: '10px',
                    })}
                  />
                </Flex>
              )
            )}
          </Flex>
        ) : null}
      </Flex>
      {dropdownState === 'dropdown' ? (
        <Button
          icon={
            <IconPlus
              title={formatMessage(
                customFieldsMessages.customFieldsAddDropdownLine
              )}
            />
          }
          aria-label={formatMessage(
            customFieldsMessages.customFieldsAddDropdownLine
          )}
          onClick={handleAddDropdownItem}
          className={csx({
            marginTop: '35px',
            marginLeft: '10px',
          })}
        />
      ) : null}
    </div>
  )
}

export default DefaultCustomField

```


[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 b2b-organizations/react/admin/OrganizationsList.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useEffect, useState } from 'react'
import type { FunctionComponent } from 'react'
import {
  csx,
  DataView,
  DataViewHeader,
  Flex,
  FlexSpacer,
  Pagination,
  Table,
  THead,
  THeadCell,
  TBody,
  TBodyRow,
  TBodyCell,
  useDataViewState,
  usePaginationState,
  useTableState,
} from '@vtex/admin-ui'
import type { TagProps } from '@vtex/admin-ui'

import {
  INITIAL_FETCH_LIST_OPTIONS,
  useOrganizationsList,
} from '../organizations/hooks'
import type { FetchListOptions } from '../organizations/hooks'
import { useNavigateToDetailsPage } from '../organizations/navigate'
import { useOrgsTableColumns } from '../organizations/table'
import OrganizationsListSearch from './OrganizationsList/OrganizationsListSearch'
import OrganizationsListStatusFilter from './OrganizationsList/OrganizationsListStatusFilter'

export const TagVariantByStatus: Record<string, TagProps['variant']> = {
  active: 'green',
  inactive: 'red',
  'on-hold': 'orange',
}

const OrganizationsList: FunctionComponent = () => {
  const navigateToDetailsPage = useNavigateToDetailsPage()
  const columns = useOrgsTableColumns()

  const [refetchOptions, setRefetchOptions] = useState(
    INITIAL_FETCH_LIST_OPTIONS
  )

  const { data: fetchedOrgs, loading, refetch } = useOrganizationsList()

  const view = useDataViewState()
  const { data, getBodyCell, getHeadCell, getTable, sortState } = useTableState(
    {
      status: view.status,
      columns,
      items: fetchedOrgs?.getOrganizations?.data,
      length: 10,
      sort: {
        initialValue: {
          by: INITIAL_FETCH_LIST_OPTIONS.sortedBy,
          order: INITIAL_FETCH_LIST_OPTIONS.sortOrder,
        },
      },
    }
  )

  const paginationState = usePaginationState({
    pageSize: INITIAL_FETCH_LIST_OPTIONS.pageSize,
    onNextPage: () => {
      updateTableItems({
        page: refetchOptions.page + 1,
      })
    },
    onPrevPage: () => {
      updateTableItems({
        page: refetchOptions.page - 1,
      })
    },
  })

  const totalItems = fetchedOrgs?.getOrganizations?.pagination?.total

  useEffect(() => {
    if (loading) {
      view.setStatus({
        type: 'loading',
      })

      return
    }

    if (totalItems === 0 && !refetchOptions.search && !refetchOptions.status) {
      view.setStatus({
        type: 'empty',
      })

      return
    }

    view.setStatus({
      type: 'ready',
    })
  }, [loading, totalItems, refetchOptions.search, refetchOptions.status])

  function updateTableItems(options: Partial<FetchListOptions>) {
    const newRefetchOptions = {
      ...refetchOptions,
      ...options,
    }

    setRefetchOptions(newRefetchOptions)
    refetch(newRefetchOptions)

    if (options.page !== refetchOptions.page && options.page === 1) {
      paginationState.paginate({ type: 'reset' })
    }
  }

  useEffect(() => {
    if (!loading && totalItems) {
      paginationState.paginate({
        type: 'setTotal',
        total: totalItems,
      })
    }
  }, [loading, totalItems])

  useEffect(() => {
    updateTableItems({
      sortedBy: sortState.by as string,
      sortOrder: sortState.order,
      page: 1,
    })
  }, [sortState.by, sortState.order])

  return (
    <DataView state={view}>
      <DataViewHeader>
        <Flex className={csx({ width: '100%' })}>
          <OrganizationsListSearch onSearch={updateTableItems} />
          <OrganizationsListStatusFilter
            onChange={value => {
              updateTableItems({
                status: value,
                page: 1,
              })
            }}
          />
          <FlexSpacer />
          <Pagination state={paginationState} loading={loading} />
        </Flex>
      </DataViewHeader>
      <Table width="100%" {...getTable()}>
        <THead>
          {columns.map(column => (
            <THeadCell {...getHeadCell(column)} />
          ))}
        </THead>
        <TBody>
          {data?.map(item => {
            return (
              <TBodyRow
                key={item.id}
                onClick={() => navigateToDetailsPage(item.id)}
              >
                {columns.map(column => {
                  return <TBodyCell {...getBodyCell(column, item)} />
                })}
              </TBodyRow>
            )
          })}
        </TBody>
      </Table>
    </DataView>
  )
}

export default OrganizationsList

```


[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 b2b-organizations/react/utils/addresses.ts from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```typescript
const hashCode = function hash(arg: null | string | number | number[]) {
  const str = arg === null ? '' : arg.toString()

  if (str.length === 0) {
    return 0
  }

  return str.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0)
}

export const setGUID = (address: AddressFormFields) => {
  return (
    hashCode(address.street.value) +
    hashCode(address.complement.value) +
    hashCode(address.city.value) +
    hashCode(address.state.value)
  ).toString()
}

export const getEmptyAddress = (country: string) => {
  return {
    addressId: '0',
    addressType: 'commercial',
    city: null,
    complement: null,
    country,
    receiverName: '',
    geoCoordinates: [],
    neighborhood: null,
    number: null,
    postalCode: null,
    reference: null,
    state: null,
    street: null,
    addressQuery: null,
  }
}

export const isValidAddress = (address: AddressFormFields) => {
  // check for empty address
  if (!address.street.value || !address.receiverName.value) return false

  for (const field in address) {
    if (address[field].valid === false) {
      return false
    }
  }

  return 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 b2b-organizations/react/utils/storage.ts from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```typescript
export default function storageFactory(getStorage: () => Storage): Storage {
  let inMemoryStorage: { [key: string]: string } = {}

  function isSupported() {
    try {
      const testKey = '__some_random_key_you_are_not_going_to_use__'

      getStorage().setItem(testKey, testKey)
      getStorage().removeItem(testKey)

      return true
    } catch (e) {
      return false
    }
  }

  function clear(): void {
    if (isSupported()) {
      getStorage().clear()
    } else {
      inMemoryStorage = {}
    }
  }

  function getItem(name: string): string | null {
    if (isSupported()) {
      return getStorage().getItem(name)
    }

    // eslint-disable-next-line no-prototype-builtins
    if (inMemoryStorage.hasOwnProperty(name)) {
      return inMemoryStorage[name]
    }

    return null
  }

  function key(index: number): string | null {
    if (isSupported()) {
      return getStorage().key(index)
    }

    return Object.keys(inMemoryStorage)[index] || null
  }

  function removeItem(name: string): void {
    if (isSupported()) {
      getStorage().removeItem(name)
    } else {
      delete inMemoryStorage[name]
    }
  }

  function setItem(name: string, value: string): void {
    if (isSupported()) {
      getStorage().setItem(name, value)
    } else {
      inMemoryStorage[name] = String(value) // not everyone uses TypeScript
    }
  }

  function length(): number {
    if (isSupported()) {
      return getStorage().length
    }

    return Object.keys(inMemoryStorage).length
  }

  return {
    getItem,
    setItem,
    removeItem,
    clear,
    key,
    get length() {
      return length()
    },
  }
}

```


[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 b2b-organizations/react/utils/constants.ts from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```typescript
export const B2B_CHECKOUT_SESSION_KEY = 'b2b-checkout-settings'
export const B2B_BULK_IMPORT_TEMPLATE_LINK =
  'https://io.vtex.com.br/b2b-bulk-import/b2b-bulk-import-template.xlsx'
export const B2B_BULK_IMPORT_BEST_PRACTICES_LINK = 'https://help.vtex.com/'

// Permission codes
export const ORGANIZATION_EDIT = 'buyer_organization_edit'
export const ORGANIZATION_VIEW = 'buyer_organization_view'

// Product codes
export const B2B_LM_PRODUCT_CODE = '97'

```


[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 b2b-organizations/react/components/OrganizationsWithoutSalesManager.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { Fragment, useState, useEffect } from 'react'
import { useIntl } from 'react-intl'
import { Table, PageBlock, Alert } from 'vtex.styleguide'
import { useQuery } from 'react-apollo'

import {
  organizationMessages as messages,
  organizationMessages as storeMessages,
} from './utils/messages'
import GET_ORGANIZATIONS from '../graphql/getOrganizationsWithoutSalesManager.graphql'

const TABLE_LENGTH = 5
const OrganizationsWithoutSalesManager = () => {
  const { formatMessage } = useIntl()
  const [organizationsState, setOrganizationsState] = useState([] as any)

  const [paginationState, setPaginationState] = useState({
    tableLength: TABLE_LENGTH,
    currentPage: 1,
    slicedData: [] as any,
    currentItemFrom: 1,
    currentItemTo: TABLE_LENGTH,
    itemsLength: 0,
    emptyStateLabel: 'Nothing to show.',
  })

  const { data, loading } = useQuery(GET_ORGANIZATIONS, {
    variables: {},
    fetchPolicy: 'network-only',
  })

  const updateData = (_data: any) => {
    setPaginationState({
      ...paginationState,
      slicedData: _data.slice(
        paginationState.currentItemFrom - 1,
        paginationState.currentItemTo
      ),
      itemsLength: _data.length,
    })
  }

  const goToPage = (
    currentPage: number,
    currentItemFrom: number,
    currentItemTo: number,
    slicedData: any[]
  ) => {
    setPaginationState({
      ...paginationState,
      currentPage,
      currentItemFrom,
      currentItemTo,
      slicedData,
    })
  }

  const handleNextClick = () => {
    const newPage = paginationState.currentPage + 1
    const itemFrom = paginationState.currentItemTo + 1
    const itemTo = paginationState.tableLength * newPage
    const _data = organizationsState.slice(itemFrom - 1, itemTo)

    goToPage(newPage, itemFrom, itemTo, _data)
  }

  const handlePrevClick = () => {
    if (paginationState.currentPage === 0) return
    const newPage = paginationState.currentPage - 1
    const itemFrom =
      paginationState.currentItemFrom - paginationState.tableLength

    const itemTo = paginationState.currentItemFrom - 1
    const _data = organizationsState.slice(itemFrom - 1, itemTo)

    goToPage(newPage, itemFrom, itemTo, _data)
  }

  const handleRowsChange = (_: any, value: any) => {
    setPaginationState({
      ...paginationState,
      tableLength: parseInt(value, 10),
      currentItemTo: parseInt(value, 10),
    })
  }

  const getSchema = () => {
    return {
      properties: {
        name: {
          title: formatMessage(storeMessages.columnOrganizationName),
        },
      },
    }
  }

  useEffect(() => {
    if (!organizationsState || !organizationsState.length) return

    updateData(organizationsState)
  }, [paginationState])

  useEffect(() => {
    if (!data) {
      return
    }

    const _data = data?.getOrganizationsWithoutSalesManager

    _data.sort((a: any, b: any) => (a.name > b.name ? 1 : -1))
    setOrganizationsState(_data)
    updateData(_data)
  }, [data])

  return (
    <Fragment>
      {data && !organizationsState.length && (
        <div className="mb8">
          <Alert type="success">
            {formatMessage(messages.organizationsWithoutSalesManagerOK)}
          </Alert>
        </div>
      )}

      {data && organizationsState && organizationsState.length > 0 && (
        <PageBlock
          title={formatMessage(messages.organizationsWithoutSalesManager)}
        >
          <div className="mb8">
            <Alert className="mb5" type="warning">
              {formatMessage(messages.organizationsWithoutSalesManagerWarning)}
            </Alert>
          </div>
          <Table
            loading={loading}
            items={paginationState.slicedData}
            fullWidth
            schema={getSchema()}
            pagination={{
              onNextClick: handleNextClick,
              onPrevClick: handlePrevClick,
              selectedOption: paginationState.tableLength,
              currentItemFrom: paginationState.currentItemFrom,
              currentItemTo: paginationState.currentItemTo,
              onRowsChange: handleRowsChange,
              textShowRows: formatMessage(storeMessages.showRows),
              textOf: formatMessage(storeMessages.of),
              totalItems: organizationsState.length,
              rowsOptions: [5, 10, 15, 25],
            }}
          />
        </PageBlock>
      )}
    </Fragment>
  )
}

export default OrganizationsWithoutSalesManager

```