diff --git a/apps/storefront/src/App.tsx b/apps/storefront/src/App.tsx index 1d4b7788..4e7a33d8 100644 --- a/apps/storefront/src/App.tsx +++ b/apps/storefront/src/App.tsx @@ -20,6 +20,7 @@ import { gotoAllowedAppPage } from '@/shared/routes' import { setChannelStoreType } from '@/shared/service/b2b' import { B3SStorage, + clearInvoiceCart, getCompanyUserInfo, getCurrentCustomerInfo, getQuoteEnabled, @@ -175,6 +176,10 @@ export default function App() { gotoAllowedAppPage(+userInfo.role, gotoPage) } + if (customerId) { + clearInvoiceCart() + } + sessionStorage.removeItem('isReLogin') showPageMask(dispatch, false) storeDispatch( diff --git a/apps/storefront/src/pages/invoice/Invoice.tsx b/apps/storefront/src/pages/invoice/Invoice.tsx index 459ab6eb..ae92b568 100644 --- a/apps/storefront/src/pages/invoice/Invoice.tsx +++ b/apps/storefront/src/pages/invoice/Invoice.tsx @@ -1,6 +1,5 @@ import { useContext, useEffect, useRef, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' -import globalB3 from '@b3/global-b3' import { Box, Button, @@ -18,10 +17,11 @@ import { GlobaledContext } from '@/shared/global' import { exportInvoicesAsCSV, getInvoiceList } from '@/shared/service/b2b' import { InvoiceList, InvoiceListNode } from '@/types/invoice' import { + B3SStorage, currencyFormat, + currencyFormatInfo, displayFormat, - getActiveCurrencyInfo, - getDefaultCurrencyInfo, + getUTCTimestamp, } from '@/utils' import B3Filter from '../../components/filter/B3Filter' @@ -60,13 +60,13 @@ function Invoice() { const { state: { role, isAgenting }, } = useContext(GlobaledContext) + const juniorOrSenior = +role === 1 || role === 2 const navigate = useNavigate() const [isMobile] = useMobile() const paginationTableRef = useRef(null) - const currentCurrency = globalB3?.setting?.is_local_debugging - ? getDefaultCurrencyInfo() - : getActiveCurrencyInfo() + const allCurrencies = B3SStorage.get('currencies') + const { decimal_places: decimalPlaces = 2 } = currencyFormatInfo() const [isRequestLoading, setIsRequestLoading] = useState(false) const [isOpenHistorys, setIsOpenHistorys] = useState(false) @@ -90,6 +90,21 @@ function Invoice() { const location = useLocation() + const handleGetCorrespondingCurrency = (code: string) => { + const { currencies: currencyArr } = allCurrencies + let token = '$' + const correspondingCurrency = + currencyArr.find( + (currency: CustomFieldItems) => currency.currency_code === code + ) || {} + + if (correspondingCurrency) { + token = correspondingCurrency.token + } + + return token + } + const handleStatisticsInvoiceAmount = (invoices: InvoiceListNode[]) => { let unpaidAmount = 0 let overdueAmount = 0 @@ -123,10 +138,11 @@ function Invoice() { const handleFilterChange = (value: Partial) => { const startValue = value?.startValue - ? new Date(value?.startValue).getTime() / 1000 + ? getUTCTimestamp(new Date(value?.startValue).getTime() / 1000) : '' + const endValue = value?.endValue - ? new Date(value?.endValue).getTime() / 1000 + ? getUTCTimestamp(new Date(value?.endValue).getTime() / 1000, true) : '' const search: Partial = { @@ -162,11 +178,11 @@ function Invoice() { } } - const handleViewInvoice = async (id: string) => { + const handleViewInvoice = async (id: string, status: string | number) => { try { setIsRequestLoading(true) - - const pdfUrl = await handlePrintPDF(id, true) + const isPayNow = !juniorOrSenior && status !== 2 + const pdfUrl = await handlePrintPDF(id, isPayNow) if (!pdfUrl) { console.error('pdf url resolution error') @@ -322,23 +338,38 @@ function Invoice() { invoices: { edges, totalCount }, } = await getInvoiceList(params) - if (type === InvoiceListType.DETAIL && edges.length) { - edges.forEach((item: InvoiceListNode) => { + let invoicesList: InvoiceListNode[] = edges + if (filterData?.status === '0') { + invoicesList = edges.filter((invoice: InvoiceListNode) => { + const { + node: { status, dueDate }, + } = invoice + + return ( + `${+status}` === filterData.status && currentDate <= dueDate * 1000 + ) + }) + } + + if (type === InvoiceListType.DETAIL && invoicesList.length) { + invoicesList.forEach((item: InvoiceListNode) => { item.node.isCollapse = true }) } - edges.forEach((item: InvoiceListNode) => { + invoicesList.forEach((item: InvoiceListNode) => { const { node: { openBalance }, } = item item.node.disableCurrentCheckbox = +openBalance.value === 0 + + openBalance.value = (+openBalance.value).toFixed(decimalPlaces) }) - setList(edges) - handleStatisticsInvoiceAmount(edges) + setList(invoicesList) + handleStatisticsInvoiceAmount(invoicesList) return { - edges, + edges: invoicesList, totalCount, } } @@ -372,7 +403,7 @@ function Invoice() { }, }} onClick={() => { - handleViewInvoice(item.id) + handleViewInvoice(item.id, item.status) }} > {item?.id || '-'} @@ -438,8 +469,9 @@ function Invoice() { render: (item: InvoiceList) => { const { originalBalance } = item const originalAmount = (+originalBalance.value).toFixed(2) + const token = handleGetCorrespondingCurrency(originalBalance.code) - return currencyFormat(+originalAmount || 0) + return `${token}${+originalAmount || 0}` }, width: '10%', }, @@ -451,8 +483,9 @@ function Invoice() { const { openBalance } = item const openAmount = (+openBalance.value).toFixed(2) + const token = handleGetCorrespondingCurrency(openBalance.code) - return currencyFormat(+openAmount || 0) + return `${token}${+openAmount || 0}` }, width: '10%', }, @@ -461,7 +494,8 @@ function Invoice() { title: 'Amount to pay', render: (item: InvoiceList) => { const { openBalance, id } = item - let valuePrice = +openBalance.value + const currentCode = openBalance.code || '$' + let valuePrice = openBalance.value let disabled = true if (selectedPay.length > 0) { @@ -479,7 +513,7 @@ function Invoice() { } = currentSelected disabled = false - valuePrice = +selectedOpenBalance.value + valuePrice = selectedOpenBalance.value if (+openBalance.value === 0) { disabled = true @@ -491,14 +525,14 @@ function Invoice() { - {currentCurrency.token} + {handleGetCorrespondingCurrency(currentCode)} ), }} @@ -506,10 +540,36 @@ function Invoice() { '& input': { paddingTop: '8px', }, + '& input[type="number"]::-webkit-inner-spin-button, & input[type="number"]::-webkit-outer-spin-button': + { + '-webkit-appearance': 'none', + margin: 0, + }, + }} + onChange={(e: CustomFieldItems) => { + const val = e.target?.value + let result = val + if (val.includes('.')) { + const wholeDecimalNumber = val.split('.') + const movePoint = wholeDecimalNumber[1].length - +decimalPlaces + if (wholeDecimalNumber[1] && movePoint > 0) { + const newVal = wholeDecimalNumber[0] + wholeDecimalNumber[1] + result = `${newVal.slice(0, -decimalPlaces)}.${newVal.slice( + -decimalPlaces + )}` + } + } else { + const movePoint = result.length - +decimalPlaces + if (movePoint > 0) { + result = `${val.slice(0, -decimalPlaces)}.${val.slice( + -decimalPlaces + )}` + } else { + result = `.${val}` + } + } + handleSetSelectedInvoiceAccount(result, id) }} - onChange={(e: CustomFieldItems) => - handleSetSelectedInvoiceAccount(e.target?.value, id) - } type="number" /> ) @@ -534,14 +594,32 @@ function Invoice() { { key: 'companyName', title: 'Action', - render: (row: InvoiceList) => ( - - ), + render: (row: InvoiceList) => { + const { id } = row + let actionRow = row + if (selectedPay.length > 0) { + const currentSelected = selectedPay.find((item: InvoiceListNode) => { + const { + node: { id: selectedId }, + } = item + + return +selectedId === +id + }) + + if (currentSelected) { + actionRow = currentSelected.node + } + } + + return ( + + ) + }, width: '10%', }, ] @@ -571,13 +649,19 @@ function Invoice() { startPicker={{ isEnabled: true, label: 'From', - defaultValue: filterData?.dateCreatedBeginAt || '', + defaultValue: + typeof filterData?.beginDateAt === 'number' + ? +filterData.beginDateAt * 1000 + : '', pickerKey: 'start', }} endPicker={{ isEnabled: true, label: 'To', - defaultValue: filterData?.dateCreatedEndAt || '', + defaultValue: + typeof filterData?.endDateAt === 'number' + ? +filterData.endDateAt * 1000 + : '', pickerKey: 'end', }} searchValue={filterData?.q || ''} @@ -627,8 +711,8 @@ function Invoice() { isCustomRender={false} requestLoading={setIsRequestLoading} tableKey="id" - showCheckbox - showSelectAllCheckbox={!isMobile} + showCheckbox={!juniorOrSenior} + showSelectAllCheckbox={!isMobile && !juniorOrSenior} disableCheckbox={false} applyAllDisableCheckbox={false} getSelectCheckbox={getSelectCheckbox} diff --git a/apps/storefront/src/pages/invoice/components/B3Pulldown.tsx b/apps/storefront/src/pages/invoice/components/B3Pulldown.tsx index ad854e61..dc5ebcdb 100644 --- a/apps/storefront/src/pages/invoice/components/B3Pulldown.tsx +++ b/apps/storefront/src/pages/invoice/components/B3Pulldown.tsx @@ -34,6 +34,7 @@ function B3Pulldown({ const { state: { role, isAgenting }, } = useContext(GlobaledContext) + const juniorOrSenior = +role === 1 || role === 2 const [anchorEl, setAnchorEl] = useState(null) const [isCanPay, setIsCanPay] = useState(true) @@ -148,7 +149,7 @@ function B3Pulldown({ sx={{ color: 'primary.main', }} - onClick={() => handleViewInvoice(true)} + onClick={() => handleViewInvoice(row.status !== 2 && !juniorOrSenior)} > View invoice @@ -159,7 +160,7 @@ function B3Pulldown({ }} onClick={handleViewOrder} > - View Order + View order {row.status !== 0 && ( handleViewInvoice(false)} + onClick={() => handleViewInvoice(row.status !== 2 && !juniorOrSenior)} > Print diff --git a/apps/storefront/src/pages/invoice/components/InvoiceFooter.tsx b/apps/storefront/src/pages/invoice/components/InvoiceFooter.tsx index b47b8048..641a985b 100644 --- a/apps/storefront/src/pages/invoice/components/InvoiceFooter.tsx +++ b/apps/storefront/src/pages/invoice/components/InvoiceFooter.tsx @@ -8,7 +8,7 @@ import { BcCartDataLineItem, InvoiceListNode, } from '@/types/invoice' -import { currencyFormat } from '@/utils' +import { B3SStorage, snackbar } from '@/utils' import { gotoInvoiceCheckoutUrl } from '../utils/payment' @@ -17,8 +17,10 @@ interface InvoiceFooterProps { } function InvoiceFooter(props: InvoiceFooterProps) { + const allCurrencies = B3SStorage.get('currencies') const [isMobile] = useMobile() const [selectedAccount, setSelectedAccount] = useState(0) + const [currentToken, setCurrentToken] = useState('$') const { state: { isAgenting }, @@ -35,6 +37,21 @@ function InvoiceFooter(props: InvoiceFooterProps) { const { selectedPay } = props + const handleGetCorrespondingCurrency = (code: string) => { + const { currencies: currencyArr } = allCurrencies + let token = '$' + const correspondingCurrency = + currencyArr.find( + (currency: CustomFieldItems) => currency.currency_code === code + ) || {} + + if (correspondingCurrency) { + token = correspondingCurrency.token + } + + return token + } + const handleStatisticsInvoiceAmount = (checkedArr: CustomFieldItems) => { let amount = 0 @@ -42,7 +59,7 @@ function InvoiceFooter(props: InvoiceFooterProps) { const { node: { openBalance }, } = item - amount += +openBalance.value + amount += openBalance.value === '.' ? 0 : +openBalance.value }) setSelectedAccount(+amount.toFixed(2)) @@ -66,6 +83,17 @@ function InvoiceFooter(props: InvoiceFooterProps) { currency = openBalance?.code || originalBalance.code }) + const badItem = lineItems.find( + (item: CustomFieldItems) => item.amount === '.' || +item.amount === 0 + ) + if (badItem) { + snackbar.error('The payment amount entered has an invalid value.', { + isClose: true, + }) + + return + } + const params: BcCartData = { lineItems, currency, @@ -77,6 +105,12 @@ function InvoiceFooter(props: InvoiceFooterProps) { useEffect(() => { if (selectedPay.length > 0) { + const { + node: { openBalance }, + } = selectedPay[0] + + const token = handleGetCorrespondingCurrency(openBalance.code) + setCurrentToken(token) handleStatisticsInvoiceAmount(selectedPay) } }, [selectedPay]) @@ -155,7 +189,7 @@ function InvoiceFooter(props: InvoiceFooterProps) { color: '#000000', }} > - {`Total payment: ${currencyFormat(selectedAccount)}`} + {`Total payment: ${currentToken}${selectedAccount}`} - {title}: + {withColon ? `${title}:` : title} ) } @@ -139,7 +146,7 @@ function PaymentSuccessList({ list }: { list: InvoiceSuccessData }) { flexDirection: 'column', }} > - + <Title title="Invoices paid" withColon={false} /> <Typography variant="body1"> Yo made payments towards the invoices shown below{' '} </Typography> @@ -155,8 +162,24 @@ function PaymentSuccessList({ list }: { list: InvoiceSuccessData }) { fontWeight: 500, }} > - <Typography>Invoice#</Typography> - <Typography>Amount paid</Typography> + <Typography + sx={{ + fontSize: '14px', + fontWeight: '500', + color: '#000000', + }} + > + Invoice# + </Typography> + <Typography + sx={{ + fontSize: '14px', + fontWeight: '500', + color: '#000000', + }} + > + Amount paid + </Typography> </Box> {edges.map((item: ReceiptLineSet) => { const { @@ -195,6 +218,8 @@ function PaymentSuccess({ receiptId, type }: PaymentSuccessProps) { const [detailData, setDetailData] = useState<InvoiceSuccessData | null>(null) + const navigate = useNavigate() + useEffect(() => { const init = async () => { setLoadding(true) @@ -210,14 +235,23 @@ function PaymentSuccess({ receiptId, type }: PaymentSuccessProps) { } }, [receiptId, type]) + const handleCloseClick = () => { + setOpen(false) + navigate('/invoice') + } + const customActions = () => ( + <Button onClick={handleCloseClick} variant="text"> + ok + </Button> + ) + return ( <B3Dialog isOpen={open} leftSizeBtn="" - rightSizeBtn="ok" + customActions={customActions} title="Thank you for your payment" showLeftBtn={false} - handRightClick={() => setOpen(false)} > <Box sx={{ diff --git a/apps/storefront/src/pages/invoice/utils/config.ts b/apps/storefront/src/pages/invoice/utils/config.ts index 20e198a2..72ee0679 100644 --- a/apps/storefront/src/pages/invoice/utils/config.ts +++ b/apps/storefront/src/pages/invoice/utils/config.ts @@ -16,7 +16,7 @@ export const invoiceStatus = [ { key: 'partialPaid', value: 1, - label: 'Partial paid', + label: 'Partially paid', }, { key: 'paid', diff --git a/apps/storefront/src/shared/routes/routes.ts b/apps/storefront/src/shared/routes/routes.ts index 9b132545..ff5d6138 100644 --- a/apps/storefront/src/shared/routes/routes.ts +++ b/apps/storefront/src/shared/routes/routes.ts @@ -61,7 +61,7 @@ type RegisteredItem = typeof Registered | typeof HomePage interface RouteItemBasic { path: string name: string - permissions: number[] // 0: admin, 1: senior buyer, 2: junior buyer, 3: salesRep, 99: bc user + permissions: number[] // 0: admin, 1: senior buyer, 2: junior buyer, 3: salesRep, 4: salesRep-【Not represented】, 99: bc user, 100: guest } export interface RouteItem extends RouteItemBasic { @@ -113,7 +113,7 @@ const routes: RouteItem[] = [ isMenuItem: true, component: Invoice, configKey: 'invoice', - permissions: [0, 1, 2, 3, 4, 99, 100], + permissions: [0, 1, 2, 3], isTokenLogin: true, }, { @@ -306,6 +306,10 @@ const getAllowedRoutes = (globalState: GlobalState): RouteItem[] => { if (typeof config === 'boolean') { return config } + if (item.configKey === 'invoice') { + return !!config.enabledStatus && !!config.value + } + return !!config.enabledStatus }) } diff --git a/apps/storefront/src/utils/b3ClearCart.tsx b/apps/storefront/src/utils/b3ClearCart.tsx new file mode 100644 index 00000000..cba0eb5b --- /dev/null +++ b/apps/storefront/src/utils/b3ClearCart.tsx @@ -0,0 +1,19 @@ +import { deleteCart, getCartInfo } from '@/shared/service/bc' + +const clearInvoiceCart = async () => { + try { + const url = window.location.pathname + const isInvoicePay = localStorage.getItem('invoicePay') + + if (url !== '/checkout' && isInvoicePay === '1') { + const cartInfo = await getCartInfo() + if (cartInfo) { + await deleteCart(cartInfo[0].id) + } + } + } catch (err) { + console.error(err) + } +} + +export default clearInvoiceCart diff --git a/apps/storefront/src/utils/b3CurrencyFormat.ts b/apps/storefront/src/utils/b3CurrencyFormat.ts index 352c2450..fdda193b 100644 --- a/apps/storefront/src/utils/b3CurrencyFormat.ts +++ b/apps/storefront/src/utils/b3CurrencyFormat.ts @@ -11,12 +11,12 @@ interface MoneyFormat { currency_exchange_rate: string } -const currencyFormat = (price: string | number, showCurrencyToken = true) => { +export const currencyFormatInfo = () => { const currentCurrency = globalB3?.setting?.is_local_debugging ? getDefaultCurrencyInfo() : getActiveCurrencyInfo() - const moneyFormat: MoneyFormat = { + return { currency_location: currentCurrency.token_location || 'left', currency_token: currentCurrency.token || '$', decimal_token: currentCurrency.decimal_token || '.', @@ -28,6 +28,10 @@ const currencyFormat = (price: string | number, showCurrencyToken = true) => { currency_exchange_rate: currentCurrency.currency_exchange_rate || '1.0000000000', } +} + +const currencyFormat = (price: string | number, showCurrencyToken = true) => { + const moneyFormat: MoneyFormat = currencyFormatInfo() try { const [integerPart, decimalPart] = ( diff --git a/apps/storefront/src/utils/b3DateFormat/index.ts b/apps/storefront/src/utils/b3DateFormat/index.ts index 01657f4c..24af6ab3 100644 --- a/apps/storefront/src/utils/b3DateFormat/index.ts +++ b/apps/storefront/src/utils/b3DateFormat/index.ts @@ -45,4 +45,35 @@ const formatCreator = const displayFormat = formatCreator('display', 'formatDate') const displayExtendedFormat = formatCreator('extendedDisplay', 'formatDate') -export { displayExtendedFormat, displayFormat } +const getUTCTimestamp = ( + timestamp: string | number, + adjustment?: boolean, + isDateStr = false +) => { + const dateFormat = merge( + { + display: 'j M Y', + export: 'M j Y', + extendedDisplay: 'M j Y @ g:i A', + offset: 0, + }, + B3SStorage.get('timeFormat') + ) + + if (!timestamp) return '' + const dateTime = isDateStr + ? timestamp + : parseInt(String(timestamp), 10) * 1000 + const localDate = new Date(dateTime) + const localTime = localDate.getTime() + const offset = + localDate.getTimezoneOffset() * 60000 + dateFormat.offset * 1000 + + const adjustmentTime = adjustment ? (24 * 60 * 60 - 1) * 1000 : 0 + + const utcTime = localTime + offset + adjustmentTime + + return utcTime / 1000 +} + +export { displayExtendedFormat, displayFormat, getUTCTimestamp } diff --git a/apps/storefront/src/utils/index.ts b/apps/storefront/src/utils/index.ts index b1c8a7c1..4bccc067 100644 --- a/apps/storefront/src/utils/index.ts +++ b/apps/storefront/src/utils/index.ts @@ -6,8 +6,13 @@ import { isModifierTextValid, serialize, } from './b3AddToShoppingList' -import currencyFormat from './b3CurrencyFormat' -import { displayExtendedFormat, displayFormat } from './b3DateFormat' +import clearInvoiceCart from './b3ClearCart' +import currencyFormat, { currencyFormatInfo } from './b3CurrencyFormat' +import { + displayExtendedFormat, + displayFormat, + getUTCTimestamp, +} from './b3DateFormat' import { getLogo, getQuoteEnabled } from './b3Init' import { showPageMask } from './b3PageMask' import distanceDay from './b3Picker' @@ -62,9 +67,11 @@ export { B3LStorage, B3SStorage, clearCurrentCustomerInfo, + clearInvoiceCart, convertArrayToGraphql, convertObjectToGraphql, currencyFormat, + currencyFormatInfo, displayExtendedFormat, displayFormat, distanceDay, @@ -80,6 +87,7 @@ export { getProxyInfo, getQuoteEnabled, getSearchVal, + getUTCTimestamp, globalSnackbar, handleGetCurrentProductInfo, isAllRequiredOptionFilled,