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

[Assistant]
Here's the implementation:

```tsx
/* eslint-disable react/display-name */
import type { FunctionComponent, ChangeEvent } from 'react'
import React from 'react'
import {
  PageBlock,
  Table,
  Tag,
  Checkbox,
  Spinner,
  Tooltip,
} from 'vtex.styleguide'
import { useIntl, FormattedMessage } from 'react-intl'
import { FormattedCurrency } from 'vtex.format-currency'
import { useRuntime } from 'vtex.render-runtime'

import { tableMessages, statusMessages } from '../utils/messages'
import ChildrenQuotesColumn from './ChildrenQuotesColumn'
import OrganizationAndCostCenterFilter from './OrganizationAndCostCenterFilter'
import type { OrgAndCC } from './OrganizationAndCostCenterFilter'
import { LabelByStatusMap } from '../utils/status'
import { getEmptySimpleQuote } from '../utils/helpers'
import styles from './QuotesTable.css'

interface QuotesTableProps {
  permissions: string[]
  mainOrganizationId: string
  quotes: QuoteSimple[]
  page: number
  pageSize: number
  total: number
  loading: boolean
  handlePrevClick: () => void
  handleNextClick: () => void
  handleRowsChange: (e: ChangeEvent<HTMLInputElement>) => void
  searchValue: string
  handleInputSearchChange: (e: React.FormEvent<HTMLInputElement>) => void
  handleInputSearchClear: () => void
  handleInputSearchSubmit: () => void
  handleNewQuote?: () => void
  sortedBy: string
  sortOrder: string
  handleSort: ({
    sortOrder,
    sortedBy,
  }: {
    sortOrder: string
    sortedBy: string
  }) => void
  filterStatements: unknown[]
  handleFiltersChange: (statements: FilterStatement[]) => void
}

interface CellRendererProps {
  cellData: unknown
  rowData: QuoteSimple
  updateCellMeasurements: () => void
}

const QuotesTable: FunctionComponent<QuotesTableProps> = ({
  permissions,
  mainOrganizationId,
  quotes: mainQuotes,
  page,
  pageSize,
  total,
  loading,
  handleNextClick,
  handlePrevClick,
  handleRowsChange,
  searchValue,
  handleInputSearchChange,
  handleInputSearchClear,
  handleInputSearchSubmit,
  sortedBy,
  sortOrder,
  handleSort,
  filterStatements,
  handleFiltersChange,
}) => {
  const { formatMessage, formatDate } = useIntl()
  const { navigate } = useRuntime()
  const isSalesRep = permissions.some(
    (permission) => permission.indexOf('edit-quotes') >= 0
  )

  const [expandedQuotes, setExpandedQuotes] = React.useState<string[]>([])
  const [childrenQuotes, setChildrenQuotes] = React.useState<
    Record<string, [QuoteSimple]>
  >({})

  const quotes: QuoteSimple[] = []

  for (const quote of mainQuotes) {
    quotes.push(quote)

    if (quote.hasChildren && expandedQuotes.includes(quote.id)) {
      if (childrenQuotes[quote.id]) {
        quotes.push(
          ...childrenQuotes[quote.id].map((child) => ({
            ...child,
            referenceName:
              child.sellerName ?? child.seller ?? child.referenceName,
          }))
        )
      } else {
        for (let i = 0; i < (quote.childrenQuantity ?? 1); i++) {
          quotes.push(getEmptySimpleQuote(quote.id))
        }
      }
    }
  }

  const cleanChildrenStates = () => {
    setExpandedQuotes([])
    setChildrenQuotes({})
  }

  const someHasChildren = quotes.some((quote) => quote.hasChildren)
  const showOrganizationFilter = permissions.includes('access-quotes-all')
  const showCostCenterFilter =
    showOrganizationFilter || permissions.includes('access-quotes-organization')

  const handleNewQuote = () => {
    navigate({ page: 'store.create-b2b-quote' })
  }

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

        navigate({
          page: 'store.b2b-quote-details',
          params: { id },
        })
      },
    },
  ]

  const getSchema = () => ({
    properties: {
      ...(someHasChildren && {
        hasChildren: {
          title: ' ',
          width: 25,
          cellRenderer: ({ rowData }: CellRendererProps) => {
            return (
              <ChildrenQuotesColumn
                quote={rowData}
                childrenQuotes={childrenQuotes}
                setChildrenQuotes={setChildrenQuotes}
                expandedQuotes={expandedQuotes}
                setExpandedQuotes={setExpandedQuotes}
              />
            )
          },
        },
      }),
      referenceName: {
        title: formatMessage(tableMessages.refName),
        width: 200,
        cellRenderer: ({
          rowData: {
            viewedByCustomer,
            viewedBySales,
            referenceName,
            parentQuote,
            rowLoading,
            childrenQuantity,
          },
        }: CellRendererProps) => {
          let renderedName = <>{rowLoading ? <Spinner /> : referenceName}</>

          if (
            (isSalesRep && !viewedBySales) ||
            (!isSalesRep && !viewedByCustomer)
          ) {
            renderedName = <strong>{referenceName}</strong>
          }

          return (
            <Tooltip
              label={`${referenceName}${
                childrenQuantity ? ` (${childrenQuantity})` : ''
              }`}
            >
              <div {...(!!parentQuote && { className: 'pl7' })}>
                {renderedName}
                {!!childrenQuantity && (
                  <span className="c-muted-2 pl3">({childrenQuantity})</span>
                )}
              </div>
            </Tooltip>
          )
        },
      },
      subtotal: {
        title: formatMessage(tableMessages.subtotal),
        headerRight: true,
        cellRenderer: ({
          rowData: { subtotal, rowLoading },
        }: CellRendererProps) => {
          return (
            <div className="w-100 tr">
              {rowLoading ? (
                '---'
              ) : (
                <FormattedCurrency value={subtotal / 100} />
              )}
            </div>
          )
        },
      },
      creatorEmail: {
        title: formatMessage(tableMessages.email),
        cellRenderer: ({
          rowData: { creatorEmail, rowLoading },
        }: CellRendererProps) => {
          if (rowLoading) return '---'

          return <>{creatorEmail}</>
        },
      },
      creationDate: {
        title: formatMessage(tableMessages.creationDate),
        cellRenderer: ({
          rowData: { creationDate, rowLoading },
        }: CellRendererProps) => {
          if (rowLoading) return '---'

          return (
            <>
              {formatDate(creationDate, {
                day: 'numeric',
                month: 'numeric',
                year: 'numeric',
              })}
            </>
          )
        },
        sortable: true,
      },
      expirationDate: {
        title: formatMessage(tableMessages.expirationDate),
        cellRenderer: ({
          rowData: { expirationDate, rowLoading },
        }: CellRendererProps) => {
          if (!expirationDate || rowLoading) return '---'

          return (
            <>
              {formatDate(expirationDate, {
                day: 'numeric',
                month: 'numeric',
                year: 'numeric',
              })}
            </>
          )
        },
        sortable: true,
      },
      status: {
        title: formatMessage(tableMessages.status),
        cellRenderer: ({
          rowData: { status, rowLoading },
        }: CellRendererProps) => {
          if (rowLoading) return '---'

          return (
            <Tag type={LabelByStatusMap[status]}>
              <FormattedMessage
                id={statusMessages[status as keyof typeof statusMessages].id}
              />
            </Tag>
          )
        },
        sortable: true,
      },
      lastUpdate: {
        title: formatMessage(tableMessages.lastUpdate),
        cellRenderer: ({
          rowData: { lastUpdate, rowLoading },
        }: CellRendererProps) => {
          if (rowLoading) return '---'

          return (
            <>
              {formatDate(lastUpdate, {
                day: 'numeric',
                month: 'numeric',
                year: 'numeric',
              })}
            </>
          )
        },
        sortable: true,
      },
      organization: {
        title: formatMessage(tableMessages.organization),
        sortable: true,
        cellRenderer: ({
          rowData: { organizationName, rowLoading },
        }: CellRendererProps) => {
          if (rowLoading) return '---'

          return <>{organizationName}</>
        },
      },
      costCenter: {
        title: formatMessage(tableMessages.costCenter),
        sortable: true,
        cellRenderer: ({
          rowData: { costCenterName, rowLoading },
        }: CellRendererProps) => {
          if (rowLoading) return '---'

          return <>{costCenterName}</>
        },
      },
    },
  })

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

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

    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={formatMessage(
                  statusMessages[opt as keyof typeof statusMessages]
                )}
                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 organizationFilter = ({
    value,
    onChange,
  }: {
    value: OrgAndCC
    onChange: any
  }) => {
    let orgId = value?.organizationId || ''

    if (!showOrganizationFilter) {
      orgId = mainOrganizationId
    }

    return (
      <OrganizationAndCostCenterFilter
        showOrganizationFilter={showOrganizationFilter}
        organizationId={orgId}
        costCenterId={value?.costCenterId || ''}
        onChange={({ organizationId, costCenterId }: OrgAndCC) =>
          onChange({ organizationId, costCenterId })
        }
      />
    )
  }

  return (
    <PageBlock>
      <div className={`${styles.tableContainer} relative`}>
        <Table
          density="low"
          fullWidth
          items={quotes}
          loading={loading}
          schema={getSchema()}
          lineActions={lineActions}
          onRowClick={({ rowData: { id } }: CellRendererProps) => {
            if (!id) return

            navigate({
              page: 'store.b2b-quote-details',
              params: { id },
            })
          }}
          emptyStateLabel={formatMessage(tableMessages.emptyState)}
          pagination={{
            onNextClick: () => {
              cleanChildrenStates()
              handleNextClick()
            },
            onPrevClick: () => {
              cleanChildrenStates()
              handlePrevClick()
            },
            onRowsChange: (e: ChangeEvent<HTMLInputElement>) => {
              cleanChildrenStates()
              handleRowsChange(e)
            },
            currentItemFrom: (page - 1) * pageSize + 1,
            currentItemTo: total < page * pageSize ? total : page * pageSize,
            textShowRows: formatMessage(tableMessages.showRows),
            textOf: formatMessage(tableMessages.of),
            totalItems: total,
            rowsOptions: [25, 50, 100],
          }}
          toolbar={{
            inputSearch: {
              value: searchValue,
              placeholder: formatMessage(tableMessages.placeholderSearch),
              onChange: (e: React.FormEvent<HTMLInputElement>) => {
                cleanChildrenStates()
                handleInputSearchChange(e)
              },
              onClear: () => {
                cleanChildrenStates()
                handleInputSearchClear()
              },
              onSubmit: () => {
                cleanChildrenStates()
                handleInputSearchSubmit()
              },
            },
            fields: {
              label: formatMessage(tableMessages.toggleFields),
              showAllLabel: formatMessage(tableMessages.showAllFields),
              hideAllLabel: formatMessage(tableMessages.hideAllFields),
            },
            newLine: {
              label: formatMessage(tableMessages.newQuote),
              handleCallback: handleNewQuote,
            },
          }}
          sort={{
            sortedBy,
            sortOrder,
          }}
          onSort={(sortArgs: { sortOrder: string; sortedBy: string }) => {
            cleanChildrenStates()
            handleSort(sortArgs)
          }}
          filters={{
            alwaysVisibleFilters: [
              'status',
              ...(showCostCenterFilter ? ['organizationAndCostCenter'] : []),
            ],
            statements: filterStatements,
            onChangeStatements: (statements: FilterStatement[]) => {
              cleanChildrenStates()
              handleFiltersChange(statements)
            },
            clearAllFiltersButtonLabel: formatMessage(
              tableMessages.clearFilters
            ),
            collapseLeft: true,
            options: {
              status: {
                label: formatMessage(tableMessages.statusFilter),
                renderFilterLabel: (st: any) => {
                  if (!st?.object) {
                    // you should treat empty object cases only for alwaysVisibleFilters
                    return formatMessage(tableMessages.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 ? '' : ', '
                    }`
                  })

                  if (isAllTrue) {
                    return formatMessage(tableMessages.filtersAll)
                  }

                  if (isAllFalse) {
                    return formatMessage(tableMessages.filtersNone)
                  }

                  return `${trueKeysLabel}`
                },
                verbs: [
                  {
                    label: formatMessage(tableMessages.filtersIncludes),
                    value: 'includes',
                    object: statusSelectorObject,
                  },
                ],
              },
              ...(showCostCenterFilter && {
                organizationAndCostCenter: {
                  label: formatMessage(tableMessages.organizationAndCostCenter),
                  renderFilterLabel: (st: any) => {
                    if (!st?.object) {
                      // you should treat empty object cases only for alwaysVisibleFilters
                      return formatMessage(tableMessages.filtersAll)
                    }

                    return '...'
                  },
                  verbs: [
                    {
                      label: '',
                      value: '=',
                      object: organizationFilter,
                    },
                  ],
                },
              }),
            },
          }}
        />
      </div>
    </PageBlock>
  )
}

export default QuotesTable

```


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

[Assistant]
Here's the implementation:

```tsx
import type { FC } from 'react'
import React, { useState, useEffect } from 'react'
import { useQuery, useMutation } from 'react-apollo'
import { FormattedMessage, useIntl } from 'react-intl'
import type { NumberInputValue } from '@vtex/admin-ui'
import { RadioGroup } from 'vtex.styleguide'
import {
  Page,
  PageHeader,
  PageHeaderTop,
  PageHeaderTitle,
  PageContent,
  Box,
  Button,
  NumberInput,
  Skeleton,
  useToast,
} from '@vtex/admin-ui'

import { adminMessages } from '../utils/messages'
import APP_SETTINGS from '../graphql/getAppSettings.graphql'
import SAVE_APP_SETTINGS from '../graphql/saveAppSettings.graphql'

const AppSettings: FC = () => {
  const { formatMessage } = useIntl()
  const showToast = useToast()

  const [settingsState, setSettingsState] = useState({
    cartLifeSpan: 30,
  })

  const [quotesManagedValue, setQuotesManagedValue] = useState({
    value: 'MARKETPLACE',
  })

  const [settingsLoading, setSettingsLoading] = useState(false)

  const { data, loading } = useQuery(APP_SETTINGS, {
    ssr: false,
    onCompleted: (insideData) => {
      if (insideData?.getAppSettings?.adminSetup) {
        setSettingsState(insideData.getAppSettings.adminSetup)
        setQuotesManagedValue({
          value: insideData.getAppSettings.adminSetup.quotesManagedBy,
        })
      }
    },
  })

  const [saveSettings] = useMutation(SAVE_APP_SETTINGS)

  useEffect(() => {
    if (!data?.getAppSettings?.adminSetup) return

    setSettingsState(data.getAppSettings.adminSetup)
  }, [data])

  const handleSaveSettings = () => {
    setSettingsLoading(true)

    saveSettings({
      variables: {
        input: {
          cartLifeSpan: settingsState.cartLifeSpan,
          quotesManagedBy: quotesManagedValue.value,
        },
      },
    })
      .then(() => {
        showToast({
          message: formatMessage(adminMessages.saveSettingsSuccess),
          duration: 5000,
        })
        setSettingsLoading(false)
      })
      .catch((err) => {
        console.error(err)
        showToast({
          message: formatMessage(adminMessages.saveSettingsFailure),
          duration: 5000,
          tone: 'critical',
        })
        setSettingsLoading(false)
      })
  }

  return (
    <Page>
      <PageHeader>
        <PageHeaderTop>
          <PageHeaderTitle>
            {formatMessage(adminMessages.settingsPageTitle)}
          </PageHeaderTitle>
        </PageHeaderTop>
      </PageHeader>
      {loading && <Skeleton shape="rect" />}
      {data?.getAppSettings?.adminSetup && (
        <PageContent
          csx={{
            padding: 8,
            display: 'flex',
            flexDirection: 'column',
            maxWidth: '720px',
            width: '100%',
          }}
        >
          <Box>
            <h3 style={{ fontSize: '1rem', fontWeight: 600 }}>
              <FormattedMessage id="admin/b2b-quotes.settings.saveSettings.management.quotes.expiration.data" />
            </h3>
          </Box>
          <Box as="section" csx={{ marginBottom: 32 }}>
            <NumberInput
              value={settingsState.cartLifeSpan}
              label={formatMessage(adminMessages.cartLifeSpanLabel)}
              min={1}
              onBlur={() => {
                if (settingsState.cartLifeSpan < 1) {
                  setSettingsState({
                    ...settingsState,
                    cartLifeSpan: 1,
                  })
                }
              }}
              onChange={(value: NumberInputValue) => {
                setSettingsState({
                  ...settingsState,
                  cartLifeSpan: Number(value),
                })
              }}
            />
          </Box>
          <Box as="section" csx={{ marginBottom: 16 }}>
            <h3 style={{ fontSize: '1rem', fontWeight: 600 }}>
              <FormattedMessage id="admin/b2b-quotes.settings.saveSettings.management.quotes.title" />
            </h3>
            <p style={{ fontSize: '0.875rem' }} className="c-muted-1 mt1">
              <FormattedMessage id="admin/b2b-quotes.settings.saveSettings.management.quotes.description" />
            </p>
            <p style={{ fontSize: '0.875rem' }} className="c-muted-1 mt6 mb4">
              <FormattedMessage id="admin/b2b-quotes.settings.saveSettings.management.quotes.info" />
            </p>
            <div style={{ display: 'flex', gap: 8 }}>
              <RadioGroup
                hideBorder
                name="paymentMethods"
                options={[
                  {
                    value: 'MARKETPLACE',
                    label: (
                      <div>
                        <span style={{ fontSize: '0.875rem' }}>
                          <FormattedMessage id="admin/b2b-quotes.settings.saveSettings.management.quotes.option.one" />
                        </span>
                        <p
                          style={{ fontSize: '0.75rem' }}
                          className="c-muted-1"
                        >
                          <FormattedMessage id="admin/b2b-quotes.settings.saveSettings.management.quotes.option.one.describe" />
                        </p>
                      </div>
                    ),
                  },
                  {
                    value: 'SELLER',
                    label: (
                      <div>
                        <span style={{ fontSize: '0.875rem' }}>
                          <FormattedMessage id="admin/b2b-quotes.settings.saveSettings.management.quotes.option.two" />
                        </span>
                        <p
                          style={{ fontSize: '0.75rem' }}
                          className="c-muted-1"
                        >
                          <FormattedMessage id="admin/b2b-quotes.settings.saveSettings.management.quotes.option.two.describe" />
                        </p>
                      </div>
                    ),
                  },
                ]}
                value={quotesManagedValue.value}
                onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                  setQuotesManagedValue({ value: e.currentTarget.value })
                }
              />
            </div>
          </Box>
          <Box csx={{ marginTop: 16 }}>
            <Button
              disabled={settingsState.cartLifeSpan < 1}
              variant="primary"
              onClick={() => handleSaveSettings()}
              loading={settingsLoading}
            >
              {formatMessage(adminMessages.saveSettingsButtonText)}
            </Button>
          </Box>
        </PageContent>
      )}
    </Page>
  )
}

export default AppSettings

```


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

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent } from 'react'
import React from 'react'
import { useLazyQuery } from 'react-apollo'
import { ButtonPlain, IconCaretDown, IconCaretRight } from 'vtex.styleguide'

import GET_CHILDREN_QUOTES from '../graphql/getChildrenQuotes.graphql'
import { arrayShallowEqual } from '../utils/shallowEquals'
import ButtonCollapseWrapper from './ButtonCollapseWrapper'

type ChildrenQuotesColumnProps = {
  quote: QuoteSimple
  expandedQuotes: string[]
  setExpandedQuotes: React.Dispatch<React.SetStateAction<string[]>>
  childrenQuotes: Record<string, [QuoteSimple]>
  setChildrenQuotes: React.Dispatch<
    React.SetStateAction<Record<string, [QuoteSimple]>>
  >
}

const ChildrenQuotesColumn: FunctionComponent<ChildrenQuotesColumnProps> = ({
  quote,
  expandedQuotes,
  setExpandedQuotes,
  childrenQuotes,
  setChildrenQuotes,
}: ChildrenQuotesColumnProps) => {
  const { hasChildren, id } = quote

  const [fetchChildrenQuotes] = useLazyQuery(GET_CHILDREN_QUOTES, {
    fetchPolicy: 'cache-and-network',
    onCompleted({ getChildrenQuotes }) {
      const [firstChild] = getChildrenQuotes
      const { parentQuote } = firstChild
      const prevChildrenQuotes = childrenQuotes[parentQuote]

      if (
        !prevChildrenQuotes ||
        !arrayShallowEqual(prevChildrenQuotes, getChildrenQuotes)
      ) {
        setChildrenQuotes((prev) => ({
          ...prev,
          [parentQuote]: getChildrenQuotes,
        }))
      }
    },
  })

  if (!hasChildren) return null

  if (expandedQuotes.includes(id)) {
    return (
      <ButtonCollapseWrapper>
        <ButtonPlain
          onClick={(e: React.MouseEvent) => {
            e.stopPropagation()
            setExpandedQuotes((prev) => prev.filter((i) => i !== id))
          }}
        >
          <IconCaretDown size={10} />
        </ButtonPlain>
      </ButtonCollapseWrapper>
    )
  }

  return (
    <ButtonCollapseWrapper>
      <ButtonPlain
        onClick={(e: React.MouseEvent) => {
          e.stopPropagation()
          setExpandedQuotes((prev) => [...prev, id])
          fetchChildrenQuotes({ variables: { id } })
        }}
      >
        <IconCaretRight size={10} />
      </ButtonPlain>
    </ButtonCollapseWrapper>
  )
}

export default ChildrenQuotesColumn

```


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

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent, ChangeEvent } from 'react'
import React, { useState } from 'react'
import { useQuery } from 'react-apollo'
import { FormattedMessage } from 'react-intl'
import { useRuntime } from 'vtex.render-runtime'
import { Layout, PageHeader, PageBlock, Spinner } from 'vtex.styleguide'

import { useSessionResponse } from '../utils/helpers'
import QuotesTable from './QuotesTable'
import GET_QUOTES from '../graphql/getQuotes.graphql'
import GET_MAIN_ORGANIZATION from '../graphql/getMainOrganization.graphql'
import GET_PERMISSIONS from '../graphql/getPermissions.graphql'
import storageFactory from '../utils/storage'

const localStore = storageFactory(() => localStorage)

let isAuthenticated =
  JSON.parse(String(localStore.getItem('b2bquotes_isAuthenticated'))) ?? false

const QuotesTableContainer: FunctionComponent = () => {
  const { navigate, rootPath } = useRuntime()
  const [paginationState, setPaginationStateOriginal] = useState({
    page: 1,
    pageSize: 25,
  })

  const setPaginationState = (
    paginationArgs: typeof paginationState,
    scrollToTop = true
  ) => {
    setPaginationStateOriginal(paginationArgs)

    if (scrollToTop) {
      window.scrollTo(0, 0)
    }
  }

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

  const [searchState, setSearchState] = useState({
    searchValue: '',
  })

  const [sortState, setSortState] = useState({
    sortOrder: 'DESC',
    sortedBy: 'lastUpdate',
  })

  const sessionResponse: any = useSessionResponse()

  if (sessionResponse) {
    isAuthenticated =
      sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true'

    localStore.setItem(
      'b2bquotes_isAuthenticated',
      JSON.stringify(isAuthenticated)
    )
  }

  const { data: permissionsData, loading: permissionsLoading } = useQuery(
    GET_PERMISSIONS,
    {
      ssr: false,
      skip: !isAuthenticated,
    }
  )

  const {
    data: mainOrganizationData,
    loading: mainOrganizationLoading,
  } = useQuery(GET_MAIN_ORGANIZATION, {
    ssr: false,
  })

  const { data, loading, refetch } = useQuery(GET_QUOTES, {
    fetchPolicy: 'network-only',
    notifyOnNetworkStatusChange: true,
    ssr: false,
  })

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

    const newPage = paginationState.page - 1

    setPaginationState({
      ...paginationState,
      page: newPage,
    })

    refetch({
      page: newPage,
      pageSize: paginationState.pageSize,
      organization: filterState.organization,
      costCenter: filterState.costCenter,
      status: filterState.status,
      search: searchState.searchValue,
      sortOrder: sortState.sortOrder,
      sortedBy: sortState.sortedBy,
    })
  }

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

    setPaginationState({
      ...paginationState,
      page: newPage,
    })

    refetch({
      page: newPage,
      pageSize: paginationState.pageSize,
      organization: filterState.organization,
      costCenter: filterState.costCenter,
      status: filterState.status,
      search: searchState.searchValue,
      sortOrder: sortState.sortOrder,
      sortedBy: sortState.sortedBy,
    })
  }

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

    setPaginationState({
      page: 1,
      pageSize: +value,
    })

    refetch({
      page: 1,
      pageSize: +value,
      organization: filterState.organization,
      costCenter: filterState.costCenter,
      status: filterState.status,
      search: searchState.searchValue,
      sortOrder: sortState.sortOrder,
      sortedBy: sortState.sortedBy,
    })
  }

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

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

      switch (subject) {
        case 'status': {
          if (!object || typeof object !== '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
        }

        case 'organizationAndCostCenter': {
          if (!object || typeof object !== 'object') return

          if (object.organizationId) {
            organizations.push(object.organizationId as string)
          }

          if (object.costCenterId) {
            costCenters.push(object.costCenterId as string)
          }

          break
        }

        default:
          break
      }
    })

    setFilterState({
      status: statuses,
      organization: organizations,
      costCenter: costCenters,
      filterStatements: statements,
    })

    setPaginationState(
      {
        ...paginationState,
        page: 1,
      },
      false
    )

    refetch({
      page: 1,
      pageSize: paginationState.pageSize,
      search: searchState.searchValue,
      sortOrder: sortState.sortOrder,
      sortedBy: sortState.sortedBy,
      status: statuses,
      organization: organizations,
      costCenter: costCenters,
    })
  }

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

    setSearchState({
      searchValue: value,
    })
  }

  const handleInputSearchClear = () => {
    setSearchState({
      searchValue: '',
    })

    refetch({
      page: 1,
      pageSize: paginationState.pageSize,
      organization: filterState.organization,
      costCenter: filterState.costCenter,
      status: filterState.status,
      search: '',
      sortOrder: sortState.sortOrder,
      sortedBy: sortState.sortedBy,
    })
  }

  const handleInputSearchSubmit = () => {
    refetch({
      page: 1,
      pageSize: paginationState.pageSize,
      organization: filterState.organization,
      costCenter: filterState.costCenter,
      status: filterState.status,
      search: searchState.searchValue,
      sortOrder: sortState.sortOrder,
      sortedBy: sortState.sortedBy,
    })
  }

  const handleSort = ({
    sortOrder,
    sortedBy,
  }: {
    sortOrder: string
    sortedBy: string
  }) => {
    setSortState({
      sortOrder,
      sortedBy,
    })
    refetch({
      page: 1,
      pageSize: paginationState.pageSize,
      organization: filterState.organization,
      costCenter: filterState.costCenter,
      status: filterState.status,
      search: searchState.searchValue,
      sortOrder,
      sortedBy,
    })
  }

  const QuotesTablePageHeader = () => {
    return (
      <PageHeader
        title={<FormattedMessage id="store/b2b-quotes.quotes-table.title" />}
        linkLabel={<FormattedMessage id="store/b2b-quotes.back" />}
        onLinkClick={() =>
          navigate({
            to: `${rootPath ?? ''}/account`,
          })
        }
      />
    )
  }

  if (
    !isAuthenticated ||
    !permissionsData?.checkUserPermission?.permissions?.length ||
    !permissionsData.checkUserPermission.permissions.some(
      (permission: string) => permission.indexOf('access-quotes') >= 0
    )
  ) {
    const message = isAuthenticated ? (
      <FormattedMessage id="store/b2b-quotes.error.notPermitted" />
    ) : (
      <FormattedMessage id="store/b2b-quotes.error.notAuthenticated" />
    )

    return (
      <Layout fullWidth>
        <div className="mw9 center">
          <Layout fullWidth pageHeader={<QuotesTablePageHeader />}>
            <PageBlock>{permissionsLoading ? <Spinner /> : message}</PageBlock>
          </Layout>
        </div>
      </Layout>
    )
  }

  return (
    <Layout fullWidth>
      <div className="mw9 center">
        <Layout fullWidth pageHeader={<QuotesTablePageHeader />}>
          <QuotesTable
            quotes={data?.getQuotes?.data ?? []}
            mainOrganizationId={
              mainOrganizationData?.getOrganizationByIdStorefront?.id
            }
            permissions={permissionsData.checkUserPermission.permissions}
            page={paginationState.page}
            pageSize={paginationState.pageSize}
            total={data?.getQuotes?.pagination?.total ?? 0}
            loading={loading || permissionsLoading || mainOrganizationLoading}
            handlePrevClick={handlePrevClick}
            handleNextClick={handleNextClick}
            filterStatements={filterState.filterStatements}
            handleFiltersChange={handleFiltersChange}
            handleInputSearchChange={handleInputSearchChange}
            handleInputSearchClear={handleInputSearchClear}
            handleInputSearchSubmit={handleInputSearchSubmit}
            handleRowsChange={handleRowsChange}
            handleSort={handleSort}
            searchValue={searchState.searchValue}
            sortOrder={sortState.sortOrder}
            sortedBy={sortState.sortedBy}
          />
        </Layout>
      </div>
    </Layout>
  )
}

export default QuotesTableContainer

```


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

[Assistant]
Here's the implementation:

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

const ButtonCollapseWrapper: FunctionComponent = ({ children }) => {
  return (
    <div className="absolute left-0 w-100 h-100" style={{ display: 'grid' }}>
      {children}
    </div>
  )
}

export default ButtonCollapseWrapper

```


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

[Assistant]
Here's the implementation:

```tsx
import React, { Fragment, useEffect, useState } from 'react'
import { Modal, Button } from 'vtex.styleguide'
import { defineMessages, useIntl } from 'react-intl'
import { useRuntime } from 'vtex.render-runtime'
import { useCheckoutURL } from 'vtex.checkout-resources/Utils'
import { useMutation, useQuery } from 'react-apollo'
import { OrderForm } from 'vtex.order-manager'

import CLEAR_CART from '../graphql/clearCartMutation.graphql'
import GET_ORDERFORM from '../graphql/orderForm.gql'
import ORDERFORM_CUSTOM_DATA from '../graphql/setOrderFormCustomData.graphql'

const storePrefix = 'store/b2b-quotes.'

const messages = defineMessages({
  title: {
    id: `${storePrefix}quote-locking.title`,
  },
  message: {
    id: `${storePrefix}quote-locking.message`,
  },
  returnToCheckout: {
    id: `${storePrefix}quote-locking.return-to-checkout`,
  },
  clearCart: {
    id: `${storePrefix}quote-locking.clear-cart`,
  },
})

const useCheckout = () => {
  const { url: checkoutUrl, major } = useCheckoutURL()
  const { navigate, rootPath = '' } = useRuntime()

  const goToCheckout = (url: string) => {
    if (major > 0 && url === checkoutUrl) {
      navigate({ to: url })
    } else {
      window.location.href = `${rootPath}${url}`
    }
  }

  return goToCheckout
}

const B2BQuotesLockingModal = () => {
  const { formatMessage } = useIntl()
  const [open, setOpen] = useState(false)
  const goToCheckout = useCheckout()
  const { url: checkoutUrl } = useCheckoutURL()
  const [clearCart] = useMutation(CLEAR_CART)
  const [setOrderFormCustomData] = useMutation(ORDERFORM_CUSTOM_DATA)
  const [loading, setLoading] = useState(false)
  const { setOrderForm }: OrderFormContext = OrderForm.useOrderForm()
  const { data: orderFormData } = useQuery(GET_ORDERFORM, {
    ssr: false,
    fetchPolicy: 'network-only',
  })

  const handleClearCart = async () => {
    setLoading(true)
    const { orderForm } = orderFormData
    const { orderFormId } = orderForm

    try {
      await clearCart({
        variables: {
          orderFormId,
        },
      })
      await setOrderFormCustomData({
        variables: {
          orderFormId,
          appId: 'b2b-quotes-graphql',
          value: 0,
          field: 'quoteId',
        },
      })
    } catch (e) {
      console.error(e)
    }

    setLoading(false)
    setOpen(false)
    setOrderForm({
      ...orderForm,
      items: [],
    })
  }

  const handleReturnToCheckout = () => {
    setOpen(false)
    goToCheckout(checkoutUrl)
  }

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

    const { orderForm } = orderFormData
    const { customData, items } = orderForm

    if (
      !customData?.customApps ||
      !customData?.customApps.some(
        (item: any) => item.id === 'b2b-quotes-graphql'
      )
    ) {
      return
    }

    const index = customData.customApps.findIndex((item: any) => {
      return item.id === 'b2b-quotes-graphql'
    })

    const { quoteId } = customData.customApps[index].fields

    if (
      index !== -1 &&
      quoteId &&
      parseInt(quoteId, 10) !== 0 &&
      items?.length > 0
    ) {
      setOpen(true)
    }
  }, [orderFormData])

  return (
    <Fragment>
      {open && (
        <Modal
          onClose={() => handleClearCart()}
          closeOnEsc={false}
          showCloseButton={false}
          closeOnOverlayClick={false}
          isOpen={open}
          showCloseIcon={false}
          bottomBar={
            <div className="flex-s w-100-s items-center-s flex-column-s flex-column-reverse-s justify-end-m flex-row-m">
              <span className="mr4 ">
                <Button
                  isLoading={loading}
                  variation="tertiary"
                  onClick={handleClearCart}
                >
                  {formatMessage(messages.clearCart)}
                </Button>
              </span>
              <span>
                <Button
                  disabled={loading}
                  variation="primary"
                  onClick={handleReturnToCheckout}
                >
                  {formatMessage(messages.returnToCheckout)}
                </Button>
              </span>
            </div>
          }
        >
          <div>
            <h1>{formatMessage(messages.title)}</h1>
            <p>{formatMessage(messages.message)}</p>
          </div>
        </Modal>
      )}
    </Fragment>
  )
}

export default B2BQuotesLockingModal

```


[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-quotes/react/__tests__/index.test.tsx from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```tsx
describe('Orders List Scenarios', () => {
  it('should work', () => {
    expect(1).toBe(1)
  })
})

```


[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-quotes/react/graphql/getDimension.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetDimension {
  getDimension(dimType: manualPrice)
    @context(provider: "vtex.order-auth-graphql") {
    ruleCollection {
      trigger {
        effect {
          description
          effectType
        }
        condition {
          conditionType
          lessThan
          greatherThan
        }
      }
    }
  }
}

```


[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-quotes/react/graphql/orderForm.gql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetOrderForm {
  orderForm @context(provider: "vtex.store-graphql") {
    orderFormId
    userType
    loggedIn
    allowManualPrice
    clientProfileData {
      email
    }
    customData {
      customApps {
        id
        major
        fields
      }
    }
    salesChannel
    totalizers {
      id
      name
      value
    }
    value
    items {
      name
      skuName
      refId
      id
      productId
      imageUrl
      listPrice
      price
      quantity
      sellingPrice
      seller
    }
    shippingData {
      address {
        street
        number
        complement
        postalCode
        city
        geoCoordinates
        neighborhood
        country
        state
      }
    }
  }
}

```


[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-quotes/react/graphql/getQuotes.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetQuotes(
  $organization: [String]
  $costCenter: [String]
  $status: [String]
  $search: String
  $page: Int
  $pageSize: Int
  $sortOrder: String
  $sortedBy: String
) {
  getQuotes(
    organization: $organization
    costCenter: $costCenter
    status: $status
    search: $search
    page: $page
    pageSize: $pageSize
    sortOrder: $sortOrder
    sortedBy: $sortedBy
  ) @context(provider: "vtex.b2b-quotes-graphql") {
    data {
      id
      referenceName
      creatorEmail
      creatorRole
      creationDate
      expirationDate
      lastUpdate
      subtotal
      status
      organization
      organizationName
      costCenter
      costCenterName
      viewedBySales
      viewedByCustomer
      hasChildren
      childrenQuantity
    }
    pagination {
      total
    }
  }
}

```


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

[User] Show me the implementation of b2b-quotes/react/graphql/checkSellerQuotes.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query CheckSellerQuotes($sellers: [String!]!) {
  checkSellerQuotes(sellers: $sellers)
    @context(provider: "vtex.b2b-quotes-graphql") {
    id
    name
  }
}

```


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

[User] Show me the implementation of b2b-quotes/react/graphql/updateQuote.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
mutation UpdateQuote(
  $id: String
  $items: [QuoteItemInput]
  $subtotal: Float
  $note: String
  $decline: Boolean
  $expirationDate: String
) {
  updateQuote(
    input: {
      id: $id
      items: $items
      subtotal: $subtotal
      note: $note
      decline: $decline
      expirationDate: $expirationDate
    }
  ) @context(provider: "vtex.b2b-quotes-graphql")
}

```


[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-quotes/react/graphql/clearCartMutation.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
mutation ClearCart($orderFormId: String) {
  clearCart(orderFormId: $orderFormId)
    @context(provider: "vtex.b2b-quotes-graphql")
}

```


[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-quotes/react/graphql/getOrganizations.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetOrganizations(
  $status: [String]
  $search: String
  $page: Int
  $pageSize: Int
  $sortOrder: String
  $sortedBy: String
) {
  getOrganizations(
    status: $status
    search: $search
    page: $page
    pageSize: $pageSize
    sortOrder: $sortOrder
    sortedBy: $sortedBy
  ) @context(provider: "vtex.b2b-organizations-graphql") {
    pagination {
      total
    }
    data {
      id
      name
    }
  }
}

```


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

[User] Show me the implementation of b2b-quotes/react/graphql/getAppSettings.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetAppSettings {
  getAppSettings @context(provider: "vtex.b2b-quotes-graphql") {
    adminSetup {
      cartLifeSpan
      quotesManagedBy
    }
  }
}

```


[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-quotes/react/graphql/checkPermissions.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query CheckPermissions($email: String!) {
  getQuoteEnabledForUser(email: $email)
}

```


[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-quotes/react/graphql/getCostCenter.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetCostCenter($id: ID!) {
  getCostCenterById(id: $id)
    @context(provider: "vtex.b2b-organizations-graphql") {
    name
  }
}

```


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

[User] Show me the implementation of b2b-quotes/react/graphql/createQuote.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
mutation CreateQuote(
  $referenceName: String
  $items: [QuoteItemInput]
  $subtotal: Float
  $note: String
  $sendToSalesRep: Boolean
) {
  createQuote(
    input: {
      referenceName: $referenceName
      items: $items
      subtotal: $subtotal
      note: $note
      sendToSalesRep: $sendToSalesRep
    }
  ) @context(provider: "vtex.b2b-quotes-graphql")
}

```


[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-quotes/react/graphql/getPermissions.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetPermissions {
  checkUserPermission @context(provider: "vtex.storefront-permissions") {
    role {
      id
      name
      slug
    }
    permissions
  }
}

```


[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-quotes/react/graphql/getCostCentersByOrganizationId.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetCostCentersByOrganizationId(
  $id: ID
  $search: String
  $page: Int
  $pageSize: Int
  $sortOrder: String
  $sortedBy: String
) {
  getCostCentersByOrganizationId(
    id: $id
    search: $search
    page: $page
    pageSize: $pageSize
    sortOrder: $sortOrder
    sortedBy: $sortedBy
  ) @context(provider: "vtex.b2b-organizations-graphql") {
    pagination {
      total
    }
    data {
      id
      name
    }
  }
}

```


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

[User] Show me the implementation of b2b-quotes/react/graphql/getChildrenQuotes.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetChildrenQuotes($id: String) {
  getChildrenQuotes(id: $id, sortOrder: "ASC", sortedBy: "sellerName")
    @context(provider: "vtex.b2b-quotes-graphql") {
    id
    referenceName
    creatorEmail
    creatorRole
    creationDate
    expirationDate
    lastUpdate
    subtotal
    status
    organization
    organizationName
    costCenter
    costCenterName
    viewedBySales
    viewedByCustomer
    seller
    sellerName
    parentQuote
  }
}

```


[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-quotes/react/graphql/getQuote.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetQuote($id: String) {
  getQuote(id: $id) @context(provider: "vtex.b2b-quotes-graphql") {
    id
    referenceName
    creatorEmail
    creatorRole
    creationDate
    expirationDate
    lastUpdate
    updateHistory {
      email
      role
      date
      status
      note
    }
    items {
      name
      skuName
      refId
      id
      productId
      imageUrl
      listPrice
      price
      quantity
      sellingPrice
      seller
    }
    subtotal
    status
    organization
    organizationName
    costCenterName
    hasChildren
    seller
    sellerName
    parentQuote
    childrenQuantity
  }
}

```


[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-quotes/react/graphql/getFullChildrenQuotes.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetFullChildrenQuotes($id: String) {
  getChildrenQuotes(id: $id, sortOrder: "ASC", sortedBy: "sellerName")
    @context(provider: "vtex.b2b-quotes-graphql") {
    id
    referenceName
    creatorEmail
    creatorRole
    creationDate
    expirationDate
    lastUpdate
    subtotal
    status
    organization
    organizationName
    costCenter
    costCenterName
    viewedBySales
    viewedByCustomer
    seller
    sellerName
    parentQuote
    items {
      name
      skuName
      refId
      id
      productId
      imageUrl
      listPrice
      price
      quantity
      sellingPrice
      seller
    }
    updateHistory {
      email
      role
      date
      status
      note
    }
  }
}

```


[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-quotes/react/graphql/getOrganization.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetOrganization($id: ID) {
  getOrganizationById(id: $id)
    @context(provider: "vtex.b2b-organizations-graphql") {
    id
    name
  }
}

```


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

[User] Show me the implementation of b2b-quotes/react/graphql/saveAppSettings.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
mutation SaveAppSettings($input: AppSettingsInput!) {
  saveAppSettings(input: $input) @context(provider: "vtex.b2b-quotes-graphql") {
    adminSetup {
      cartLifeSpan
      quotesManagedBy
    }
  }
}

```


[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-quotes/react/graphql/useQuote.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
mutation UseQuote($id: String, $orderFormId: String) {
  useQuote(id: $id, orderFormId: $orderFormId)
    @context(provider: "vtex.b2b-quotes-graphql")
}

```


[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-quotes/react/graphql/setOrderFormCustomData.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
mutation setOrderFormCustomData(
  $appId: String
  $field: String
  $value: String
  $orderFormId: String
) {
  setOrderFormCustomData(
    orderFormId: $orderFormId
    appId: $appId
    field: $field
    value: $value
  ) @context(provider: "vtex.store-graphql") {
    orderFormId
    customData {
      customApps {
        fields
        id
        major
      }
    }
  }
}

```


[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-quotes/react/graphql/getMainOrganization.graphql from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```graphql
query GetMainOrganization {
  getOrganizationByIdStorefront
    @context(provider: "vtex.b2b-organizations-graphql") {
    id
  }
}

```


[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-quotes/react/modules/session.ts from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```typescript
interface KeyValue {
  value: string
}

interface Session {
  id: string
  namespaces: {
    store: {
      channel: string
    }
    profile: {
      isAuthenticated: KeyValue
      email?: KeyValue
    }
  }
}

interface SessionUnauthorized {
  type: 'Unauthorized'
  message: string
}

interface SessionForbidden {
  type: 'Forbidden'
  message: string
}

interface SessionPromise {
  response: Session | SessionUnauthorized | SessionForbidden
}

export function getSession() {
  return window &&
    (window as any).__RENDER_8_SESSION__ &&
    (window as any).__RENDER_8_SESSION__.sessionPromise
    ? ((window as any).__RENDER_8_SESSION__.sessionPromise as Promise<
        SessionPromise
      >)
    : null
}

```


[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-quotes/react/modules/checkoutHook.ts from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useCheckoutURL } from 'vtex.checkout-resources/Utils'
import { useRuntime } from 'vtex.render-runtime'

export default function useCheckout() {
  const { url: checkoutUrl, major } = useCheckoutURL()
  const { navigate, rootPath = '' } = useRuntime()

  const goToCheckout = (url: string) => {
    if (major > 0 && url === checkoutUrl) {
      navigate({ to: url })
    } else {
      window.location.href = `${rootPath}${url}`
    }
  }

  return goToCheckout
}

```


[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-quotes/react/utils/metrics/createQuote.ts from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```typescript
import axios from 'axios'

import type { Metric, SessionResponse } from './metrics'
import { sendMetric } from './metrics'

const GRAPHQL_URL = (accountName: string, workspace?: string) => {
  if (workspace) {
    return `https://${workspace}--${accountName}.myvtex.com/_v/private/graphql/v1`
  }

  return `https://${accountName}.myvtex.com/_v/private/graphql/v1`
}

type CreateQuoteFieldsMetric = {
  cost_center_name: string
  buyer_org_id: string
  buyer_org_name: string
  member_id: string
  member_email: string
  role: string
  creation_date: string
  quote_id: string
  quote_reference_name: string
  send_to_sales_rep: boolean
}

type CreateQuoteMetricsParam = {
  quoteId: string
  sessionResponse: SessionResponse
  workspace: string
  account: string
  sendToSalesRep: boolean
}

type CreateQuoteMetric = Metric & { fields: CreateQuoteFieldsMetric }

const fetchMetricsData = async (
  accountName: string,
  workspace: string,
  quoteId: string
) => {
  const query = JSON.stringify({
    query: `query GetMetricsData($id: String) {
      getQuote(id: $id) @context(provider: "vtex.b2b-quotes-graphql") {
        organization
        organizationName
        costCenterName
        referenceName
        creatorRole
        creationDate
      }
    }`,
    variables: { id: quoteId },
  })

  const { data, errors } = (
    await axios.post(GRAPHQL_URL(accountName, workspace), query)
  ).data

  if (errors) {
    console.error('Graphql errors', errors)
    throw new Error('Graphql Errors when trying get quote and user data')
  }

  return data?.getQuote as QuoteMetricsData
}

const buildCreateQuoteMetric = async (
  metricsParam: CreateQuoteMetricsParam
): Promise<CreateQuoteMetric> => {
  const { namespaces } = metricsParam.sessionResponse
  const userEmail = namespaces?.profile?.email?.value

  const metricsData = await fetchMetricsData(
    metricsParam.account,
    metricsParam.workspace,
    metricsParam.quoteId
  )

  const metric: CreateQuoteMetric = {
    name: 'b2b-suite-buyerorg-data',
    kind: 'create-quote-ui-event',
    description: 'Create Quotation Action - UI',
    account: metricsParam.account,
    fields: {
      buyer_org_id: metricsData.organization,
      buyer_org_name: metricsData.organizationName,
      cost_center_name: metricsData.costCenterName,
      member_id: namespaces?.profile?.id?.value,
      member_email: userEmail,
      role: metricsData.creatorRole,
      creation_date: metricsData.creationDate,
      quote_id: metricsParam.quoteId,
      quote_reference_name: metricsData.referenceName,
      send_to_sales_rep: metricsParam.sendToSalesRep,
    },
  }

  return metric
}

type QuoteMetricsData = {
  organization: string // organizationId
  organizationName: string
  costCenterName: string
  referenceName: string // quote reference name
  creatorRole: string
  creationDate: string
}

export const sendCreateQuoteMetric = async (
  metricsParam: CreateQuoteMetricsParam
) => {
  try {
    const metric = await buildCreateQuoteMetric(metricsParam)

    await sendMetric(metric)
  } catch (error) {
    console.warn('Unable to log metrics', error)
  }
}

```


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

[Assistant]
Here's the implementation:

```typescript
import axios from 'axios'

const ANALYTICS_URL = 'https://rc.vtex.com/api/analytics/schemaless-events'

type CreateQuoteMetric = {
  kind: 'create-quote-ui-event'
  description: 'Create Quotation Action - UI'
}

type UseQuoteMetric = {
  kind: 'use-quote-ui-event'
  description: 'Use Quotation Action - UI'
}

export type Metric = {
  name: 'b2b-suite-buyerorg-data'
  account: string
} & (CreateQuoteMetric | UseQuoteMetric)

export type SessionProfile = {
  id: { value: string }
  email: { value: string }
}

export type SessionResponse = {
  namespaces: {
    profile: SessionProfile
  }
}

export const sendMetric = async (metric: Metric) => {
  try {
    await axios.post(ANALYTICS_URL, metric)
  } catch (error) {
    console.warn('Unable to log metrics', error)
  }
}

```


[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-quotes/react/utils/metrics/useQuote.ts from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { Metric, SessionResponse } from './metrics'
import { sendMetric } from './metrics'

type UseQuoteFieldsMetric = {
  quote_id: string
  quote_reference_name: string
  order_form_id: string
  quote_creation_date: string
  quote_use_date: string
  creator_email: string
  user_email: string
  cost_center_name: string
  buyer_org_id: string
  buyer_org_name: string
  quote_last_update: string
}

type UseQuoteMetric = Metric & { fields: UseQuoteFieldsMetric }

export type UseQuoteMetricsParams = {
  quoteState: Quote
  orderFormId: string
  account: string
  sessionResponse: SessionResponse
}

const buildUseQuoteMetric = (
  metricsParam: UseQuoteMetricsParams
): UseQuoteMetric => {
  const { quoteState, orderFormId, account, sessionResponse } = metricsParam

  const metric: UseQuoteMetric = {
    name: 'b2b-suite-buyerorg-data',
    kind: 'use-quote-ui-event',
    description: 'Use Quotation Action - UI',
    account,
    fields: {
      buyer_org_id: quoteState.organization,
      buyer_org_name: quoteState.organizationName,
      cost_center_name: quoteState.costCenterName,
      quote_id: quoteState.id,
      quote_reference_name: quoteState.referenceName,
      order_form_id: orderFormId,
      quote_creation_date: quoteState.creationDate,
      quote_use_date: new Date().toISOString(),
      creator_email: quoteState.creatorEmail,
      user_email: sessionResponse.namespaces?.profile?.email?.value,
      quote_last_update: quoteState.lastUpdate,
    },
  }

  return metric
}

export const sendUseQuoteMetric = async (
  metricsParam: UseQuoteMetricsParams
) => {
  try {
    const metric = buildUseQuoteMetric(metricsParam)

    await sendMetric(metric)
  } catch (error) {
    console.warn('Unable to log metrics', error)
  }
}

```


[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-quotes/react/components/QuoteDetails/SaveButtons.tsx from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { Fragment } from 'react'
import { Button } from 'vtex.styleguide'
import { FormattedMessage } from 'react-intl'

import { arrayShallowEqual } from '../../utils/shallowEquals'

const SaveButtons = ({
  isNewQuote,
  updatingQuoteState,
  sentToSalesRep,
  quoteState,
  onSaveForLater,
  onSaveQuote,
  quoteItems,
  expirationDate,
  noteState,
  isSalesRep,
}: any) => {
  if (quoteState.hasChildren) return null

  if (isNewQuote) {
    return (
      <Fragment>
        <span className="mr4">
          <Button
            variation="secondary"
            isLoading={updatingQuoteState && !sentToSalesRep}
            onClick={onSaveForLater}
            disabled={
              !quoteState.items?.length ||
              !quoteState.referenceName ||
              updatingQuoteState
            }
          >
            <FormattedMessage id="store/b2b-quotes.create.button.save-for-later" />
          </Button>
        </span>
        <span className="mr4">
          <Button
            variation="primary"
            isLoading={updatingQuoteState && sentToSalesRep}
            onClick={onSaveQuote}
            disabled={
              !quoteState.items?.length ||
              !quoteState.referenceName ||
              updatingQuoteState
            }
          >
            <FormattedMessage id="store/b2b-quotes.create.button.request-quote" />
          </Button>
        </span>
      </Fragment>
    )
  }

  return (
    <Fragment>
      <span className="mr4">
        <Button
          variation="primary"
          onClick={onSaveQuote}
          isLoading={updatingQuoteState}
          disabled={
            quoteState.items.some((item: any) => item.error) ||
            (quoteState.items.length &&
              noteState === '' &&
              expirationDate === quoteState.expirationDate &&
              arrayShallowEqual(quoteItems, quoteState.items))
          }
        >
          {isSalesRep ? (
            <FormattedMessage id="store/b2b-quotes.quote-details.save" />
          ) : (
            <FormattedMessage id="store/b2b-quotes.quote-details.submit-to-sales-rep" />
          )}
        </Button>
      </span>
    </Fragment>
  )
}

export default SaveButtons

```


[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-quotes/react/components/QuoteDetails/QuoteTable.tsx from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { Fragment, useState } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { formatCurrency, FormattedCurrency } from 'vtex.format-currency'
import { useRuntime } from 'vtex.render-runtime'
import {
  Card,
  Collapsible,
  Input,
  InputCurrency,
  Table,
  Tag,
  Totalizer,
} from 'vtex.styleguide'

import { itemDiscountEligible } from '../../utils/helpers'
import { quoteMessages, statusMessages } from '../../utils/messages'
import { LabelByStatusMap } from '../../utils/status'

const QuoteTable = ({
  isNewQuote,
  quoteState,
  updatingSubtotal,
  originalSubtotal,
  isSalesRep,
  formState,
  maxDiscountState,
  discountState,
  onUpdateSellingPrice,
  onUpdateQuantity,
  checkedSellers,
}: any) => {
  const intl = useIntl()
  const {
    culture,
    culture: { currency: currencyCode, locale },
  } = useRuntime()

  const { formatMessage, formatDate } = intl
  const formatPrice = (value: number) =>
    formatCurrency({
      intl,
      culture,
      value: value / 100,
    })

  quoteState.expirationDate = quoteState.expirationDate || ''

  const checkedExternalSellers = checkedSellers
    ?.filter((seller: any) => seller.id !== '1')
    ?.map((seller: any) => seller.id)

  const renderTable = (items: any, totalizers?: any) => (
    <Table
      totalizers={totalizers}
      fullWidth
      schema={{
        properties: {
          imageUrl: {
            title: formatMessage(quoteMessages.image),
            cellRenderer: ({ rowData: { imageUrl, skuName } }: any) =>
              imageUrl && (
                <div className="dib v-mid relative">
                  <img
                    className="br2 v-mid"
                    height="38"
                    width="38"
                    src={imageUrl}
                    alt={skuName}
                    crossOrigin="anonymous"
                  />
                </div>
              ),
            width: 70,
          },
          refId: {
            title: formatMessage(quoteMessages.refCode),
            width: 200,
          },
          name: {
            title: formatMessage(quoteMessages.name),
            cellRenderer: ({ rowData }: any) => {
              return (
                <div>
                  <span>{rowData.name}</span>
                  {rowData.skuName !== rowData.name && (
                    <Fragment>
                      <br />
                      <span className="t-mini">{rowData.skuName}</span>
                    </Fragment>
                  )}
                </div>
              )
            },
            minWidth: 300,
          },
          listPrice: {
            title: formatMessage(quoteMessages.originalPrice),
            headerRight: true,
            width: 120,
            cellRenderer: ({ rowData }: any) => {
              return (
                isSalesRep && (
                  <div
                    className={`w-100 tr${
                      rowData.listPrice !== rowData.sellingPrice
                        ? ' strike '
                        : ''
                    }`}
                  >
                    <FormattedCurrency value={rowData.listPrice / 100} />
                  </div>
                )
              )
            },
          },
          sellingPrice: {
            title: formatMessage(quoteMessages.quotePrice),
            headerRight: true,
            width: 120,
            cellRenderer: ({
              cellData: sellingPrice,
              rowData: { id: itemId, listPrice, error },
            }: any) => {
              if (
                formState.isEditable &&
                isSalesRep &&
                discountState === 0 &&
                itemDiscountEligible({
                  listPrice,
                  sellingPrice,
                  error,
                  maxDiscountState,
                })
              ) {
                return (
                  <InputCurrency
                    name="price"
                    value={sellingPrice / 100}
                    onChange={onUpdateSellingPrice(itemId)}
                    currencyCode={currencyCode}
                    locale={locale}
                    error={error}
                  />
                )
              }

              return (
                <div className="w-100 tr">
                  <FormattedCurrency value={sellingPrice / 100} />
                </div>
              )
            },
          },
          quantity: {
            title: formatMessage(quoteMessages.quantity),
            width: 100,
            cellRenderer: ({
              cellData: quantity,
              rowData: { id: itemId },
            }: any) => {
              if (formState.isEditable && isSalesRep) {
                return (
                  <Input
                    id={itemId}
                    name="quantity"
                    value={quantity}
                    onChange={onUpdateQuantity(itemId)}
                  />
                )
              }

              return quantity
            },
          },
          total: {
            title: formatMessage(quoteMessages.total),
            headerRight: true,
            cellRenderer: ({ rowData }: any) => {
              return (
                <span className="tr w-100">
                  <FormattedCurrency
                    value={
                      rowData.sellingPrice
                        ? (rowData.sellingPrice * rowData.quantity) / 100
                        : 0
                    }
                  />
                </span>
              )
            },
            width: 100,
          },
        },
      }}
      items={items}
      emptyStateLabel={formatMessage(quoteMessages.emptyState)}
    />
  )

  const totalizers = [
    {
      label: formatMessage(quoteMessages.originalSubtotal),
      value: formatPrice(originalSubtotal),
    },
    {
      label: formatMessage(quoteMessages.percentageDiscount),
      value:
        updatingSubtotal && originalSubtotal
          ? `${Math.round(100 - (updatingSubtotal / originalSubtotal) * 100)}%`
          : `0%`,
    },
    {
      label: formatMessage(quoteMessages.quotedSubtotal),
      value: formatPrice(updatingSubtotal),
    },
    ...(quoteState.expirationDate && [
      {
        label: formatMessage(quoteMessages.expiration),
        value: formatDate(quoteState.expirationDate, {
          day: 'numeric',
          month: 'numeric',
          year: 'numeric',
        }),
      },
    ]),
    ...(quoteState.status && [
      {
        label: formatMessage(quoteMessages.status),
        value: (
          <Tag type={LabelByStatusMap[quoteState.status]}>
            <FormattedMessage
              id={
                statusMessages[quoteState.status as keyof typeof statusMessages]
                  .id
              }
            />
          </Tag>
        ),
      },
    ]),
  ]

  if (!isNewQuote || !checkedExternalSellers?.length) {
    return renderTable(quoteState.items, totalizers)
  }

  const itemsBySeller: Record<string, any> = {}

  quoteState.items.forEach((item: any) => {
    let sellerId: string = item.seller

    if (!checkedExternalSellers.includes(item.seller)) {
      sellerId = '1'
    }

    if (!itemsBySeller[sellerId]) {
      itemsBySeller[sellerId] = { items: [] }
    }

    itemsBySeller[sellerId].items.push(item)
    itemsBySeller[sellerId].sellerName = checkedSellers.find(
      (seller: any) => seller.id === sellerId
    )?.name
  })

  return (
    <div className="flex flex-column">
      {!quoteState.parentQuote && (
        <div className="mb6">
          <Totalizer items={totalizers} />
        </div>
      )}
      {Object.entries(itemsBySeller).map(([seller, sellerQuote]: any) => (
        <ChildrenQuoteCard key={seller} title={sellerQuote.sellerName}>
          {renderTable(sellerQuote.items)}
        </ChildrenQuoteCard>
      ))}
    </div>
  )
}

function ChildrenQuoteCard({ children, title }: React.PropsWithChildren<any>) {
  const [open, setOpen] = useState(true)

  return (
    <div className="mb6">
      <Card>
        <Collapsible
          isOpen={open}
          onClick={() => setOpen((prev) => !prev)}
          header={title}
        >
          <div className="pv4">{children}</div>
        </Collapsible>
      </Card>
    </div>
  )
}

export default QuoteTable

```


[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-quotes/react/components/QuoteDetails/QuoteName.tsx from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { useIntl } from 'react-intl'
import { Input } from 'vtex.styleguide'

import { quoteMessages } from '../../utils/messages'

const QuoteName = ({
  isNewQuote,
  quoteState,
  formState,
  onChange,
  childrenQuantity,
  sellerName,
}: any) => {
  const { formatMessage } = useIntl()

  return isNewQuote ? (
    <div className="w-50-l mb5">
      <Input
        size="large"
        placeholder={formatMessage(quoteMessages.placeholderName)}
        dataAttributes={{
          'hj-white-list': true,
          test: 'string',
        }}
        label={formatMessage(quoteMessages.labelName)}
        value={quoteState.referenceName}
        errorMessage={formState.errorMessage}
        onChange={onChange}
      />
    </div>
  ) : (
    <h3 className="t-heading-3 mb8">
      {quoteState.referenceName}
      {childrenQuantity > 0 && (
        <span className="v-mid c-muted-2 pl3 t-heading-4">
          ({childrenQuantity})
        </span>
      )}
      {sellerName && (
        <div className="c-muted-2 pt2 t-heading-4">({sellerName})</div>
      )}
    </h3>
  )
}

export default QuoteName

```


[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-quotes/react/components/QuoteDetails/QuoteChildren.tsx from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { memo, useMemo, useState } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { formatCurrency } from 'vtex.format-currency'
import { useRuntime } from 'vtex.render-runtime'
import {
  Button,
  Card,
  Collapsible,
  Tab,
  Tabs,
  Tag,
  Totalizer,
} from 'vtex.styleguide'

import { isQuoteUsable } from '../../utils/helpers'
import { quoteMessages, statusMessages } from '../../utils/messages'
import { LabelByStatusMap } from '../../utils/status'
import QuoteTable from './QuoteTable'
import QuoteUpdateHistory from './QuoteUpdateHistory'

interface Props {
  quote: Quote
  isSalesRep: boolean
  handleUseQuote: (quote: Quote) => void
  permissions: string[]
  usingParentQuote: boolean
  usingQuoteChild?: string
}

const QuoteChildren: React.FC<Props> = ({
  quote,
  isSalesRep,
  handleUseQuote,
  permissions,
  usingParentQuote,
  usingQuoteChild,
}) => {
  const { culture, navigate } = useRuntime()
  const intl = useIntl()
  const { formatMessage, formatDate } = intl
  const [open, setOpen] = useState(true)
  const [loadingViewQuote, setLoadingViewQuote] = useState(false)
  const [currentTab, setCurrentTab] = useState<number | null>(1)
  const {
    id,
    items,
    subtotal,
    status,
    seller,
    sellerName,
    expirationDate,
    updateHistory,
  } = quote

  const formatPrice = (value: number) =>
    formatCurrency({
      intl,
      culture,
      value: value / 100,
    })

  const originalSubtotal = useMemo(
    () =>
      (items ?? []).reduce((acc, item) => {
        return acc + item.price * item.quantity
      }, 0),
    [items]
  )

  const discount = useMemo(
    () => Math.round(100 - (subtotal / originalSubtotal) * 100),
    [originalSubtotal, subtotal]
  )

  const quoteUsable = isQuoteUsable(permissions, status)

  return (
    <div className="mb5">
      <Card>
        <Collapsible
          onClick={() => setOpen((prev) => !prev)}
          header={
            <div className="flex justify-between items-center">
              <div>
                <span className="mr3">{sellerName ?? seller}</span>
                <Tag type={LabelByStatusMap[status]}>
                  <FormattedMessage
                    id={
                      statusMessages[status as keyof typeof statusMessages]?.id
                    }
                  />
                </Tag>
              </div>
              <div className="flex flex-column-s flex-row-l justify-start items-center">
                <span className="t-mini c-muted-2 mr3">
                  {formatMessage(quoteMessages.expiration)}
                </span>
                <span>
                  {expirationDate
                    ? formatDate(expirationDate, {
                        day: 'numeric',
                        month: 'numeric',
                        year: 'numeric',
                      })
                    : '---'}
                </span>
              </div>
            </div>
          }
          isOpen={open}
        >
          <div className="pt6">
            <div className="mb5">
              <Totalizer
                items={[
                  {
                    label: formatMessage(quoteMessages.percentageDiscount),
                    value: `${discount}%`,
                  },
                  {
                    label: formatMessage(quoteMessages.originalSubtotal),
                    value: formatPrice(originalSubtotal),
                  },
                  {
                    label: formatMessage(quoteMessages.quotedSubtotal),
                    value: formatPrice(subtotal),
                  },
                ]}
              />
            </div>
            <Tabs>
              <Tab
                label={formatMessage(quoteMessages.productsTitle, {
                  total: items.length,
                })}
                active={currentTab === 1}
                onClick={() => setCurrentTab(1)}
              >
                <div className="mt5">
                  <QuoteTable
                    quoteState={quote}
                    updatingSubtotal={subtotal}
                    originalSubtotal={originalSubtotal}
                    isSalesRep={isSalesRep}
                    formState={{ isEditable: false }}
                  />
                </div>
              </Tab>
              <Tab
                label={formatMessage(quoteMessages.historyTitle)}
                active={currentTab === 2}
                onClick={() => setCurrentTab(2)}
              >
                <div className="flex flex-column-s flex-column-l">
                  <QuoteUpdateHistory updateHistory={updateHistory} />
                </div>
              </Tab>
            </Tabs>
            <div className="flex mt6 justify-end">
              <Button
                variation="secondary"
                size="small"
                isLoading={loadingViewQuote}
                onClick={() => {
                  setLoadingViewQuote(true)

                  navigate({
                    page: 'store.b2b-quote-details',
                    params: { id },
                    query: 'parent',
                  })
                }}
              >
                {formatMessage(quoteMessages.updatePageTitle)}
              </Button>
              <div className="ml3">
                <Button
                  size="small"
                  disabled={!quoteUsable}
                  isLoading={usingParentQuote || usingQuoteChild === id}
                  onClick={() => handleUseQuote(quote)}
                >
                  {formatMessage(quoteMessages.useQuote)}
                </Button>
              </div>
            </div>
          </div>
        </Collapsible>
      </Card>
    </div>
  )
}

export default memo(QuoteChildren)

```


[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-quotes/react/components/QuoteDetails/AlertMessage.tsx from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { Alert } from 'vtex.styleguide'
import { FormattedMessage } from 'react-intl'
import React from 'react'

const AlertMessage = ({ quoteState, noteState }: any) =>
  quoteState.items.some((item: any) => item.error) ? (
    <div className="mb4">
      <Alert type="error">
        <FormattedMessage
          id="store/b2b-quotes.quote-details.discount-error"
          values={{ count: noteState.length }}
        />
      </Alert>
    </div>
  ) : null

export default AlertMessage

```


[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-quotes/react/components/QuoteDetails/QuoteDetailsNotAuthenticated.tsx from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { Layout, PageBlock, PageHeader, Spinner } from 'vtex.styleguide'
import { FormattedMessage, useIntl } from 'react-intl'
import React from 'react'

import { quoteMessages } from '../../utils/messages'

const QuoteDetailsNotAuthenticated = ({
  isNewQuote,
  permissionsLoading,
  isAuthenticated,
  onLinkClick,
}: any) => {
  const { formatMessage } = useIntl()

  const Message = () =>
    !isAuthenticated ? (
      <FormattedMessage id="store/b2b-quotes.error.notAuthenticated" />
    ) : (
      <FormattedMessage id="store/b2b-quotes.error.notPermitted" />
    )

  return (
    <Layout fullWidth>
      <div className="mw9 center">
        <Layout
          fullWidth
          pageHeader={
            <PageHeader
              title={formatMessage(
                isNewQuote
                  ? quoteMessages.createPageTitle
                  : quoteMessages.updatePageTitle
              )}
              linkLabel={formatMessage(quoteMessages.back)}
              onLinkClick={() => onLinkClick}
            />
          }
        >
          <PageBlock>
            {permissionsLoading ? <Spinner /> : <Message />}
          </PageBlock>
        </Layout>
      </div>
    </Layout>
  )
}

export default QuoteDetailsNotAuthenticated

```


[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-quotes/react/components/QuoteDetails/QuoteUpdateHistory.tsx from the b2b-quotes VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { Card, Tag } from 'vtex.styleguide'

import { statusMessages } from '../../utils/messages'
import { LabelByStatusMap } from '../../utils/status'

const QuoteUpdateHistory = ({ updateHistory }: any) => {
  const { formatDate } = useIntl()

  return (
    <div className="mt3">
      {updateHistory.length > 0 && (
        <h3 className="t-heading-4">
          <FormattedMessage id="store/b2b-quotes.quote-details.update-history.title" />
        </h3>
      )}
      {updateHistory.map((update: any, index: number) => {
        return (
          <div key={index} className="ph4 pv2">
            <Card>
              <div>
                <FormattedMessage
                  id="store/b2b-quotes.quote-details.update-history.update-details"
                  values={{
                    date: formatDate(update.date, {
                      day: 'numeric',
                      month: 'numeric',
                      year: 'numeric',
                    }),
                    email: update.email,
                    role: update.role,
                    status: (
                      <Tag type={LabelByStatusMap[update.status]}>
                        <FormattedMessage
                          id={
                            statusMessages[
                              update.status as keyof typeof statusMessages
                            ].id
                          }
                        />
                      </Tag>
                    ),
                    index,
                  }}
                />
              </div>
              {update.note && (
                <div>
                  <b>
                    <FormattedMessage id="store/b2b-quotes.quote-details.update-history.notes" />
                  </b>
                  <br />
                  {update.note}
                </div>
              )}
            </Card>
          </div>
        )
      })}
    </div>
  )
}

export default QuoteUpdateHistory

```