diff --git a/apps/storefront/src/components/filter/B3FilterMore.tsx b/apps/storefront/src/components/filter/B3FilterMore.tsx index a455495d..ec910b6a 100644 --- a/apps/storefront/src/components/filter/B3FilterMore.tsx +++ b/apps/storefront/src/components/filter/B3FilterMore.tsx @@ -57,6 +57,7 @@ interface B3FilterMoreProps { endPicker?: PickerProps fiterMoreInfo: Array> onChange?: (val: Y) => void + isShowMore?: boolean } interface PickerRefProps extends HTMLInputElement { @@ -69,11 +70,13 @@ const B3FilterMore: ({ endPicker, fiterMoreInfo, onChange, + isShowMore, }: B3FilterMoreProps) => ReactElement = ({ startPicker, endPicker, fiterMoreInfo, onChange, + isShowMore = false, }) => { const container = useRef(null) const [open, setOpen] = useState(false) @@ -142,7 +145,7 @@ const B3FilterMore: ({ /> { - fiterMoreInfo && fiterMoreInfo.length && ( + ((fiterMoreInfo && fiterMoreInfo.length) || isShowMore) && ( diff --git a/apps/storefront/src/pages/quickorder/Quickorder.tsx b/apps/storefront/src/pages/quickorder/Quickorder.tsx index 960e2a85..8c2e7797 100644 --- a/apps/storefront/src/pages/quickorder/Quickorder.tsx +++ b/apps/storefront/src/pages/quickorder/Quickorder.tsx @@ -1,5 +1,4 @@ import { - useContext, useState, useRef, useEffect, @@ -7,28 +6,80 @@ import { import { Box, + Grid, } from '@mui/material' +import { + useMobile, +} from '@/hooks' + +import { + B3Sping, +} from '@/components/spin/B3Sping' + import QuickorderTable from './components/QuickorderTable' +interface TableRefProps extends HTMLInputElement { + getCheckedList: () => CustomFieldItems, +} + const Quickorder = () => { useEffect(() => { }, []) - const tableRef = useRef(null) + const [isMobile] = useMobile() + + const [isRequestLoading, setIsRequestLoading] = useState(false) + + const tableRef = useRef(null) + + const getCheckedList = () => { + const checkedValue = tableRef.current?.getCheckedList() + + console.log(checkedValue) + } return ( - - - + + + + + + + + + + + sidebar + + + + + button + - + ) } diff --git a/apps/storefront/src/pages/quickorder/components/QuickOrderCard.tsx b/apps/storefront/src/pages/quickorder/components/QuickOrderCard.tsx new file mode 100644 index 00000000..c21b5f70 --- /dev/null +++ b/apps/storefront/src/pages/quickorder/components/QuickOrderCard.tsx @@ -0,0 +1,140 @@ +import { + ReactElement, +} from 'react' + +import { + Box, + CardContent, + Typography, + styled, +} from '@mui/material' + +import { + getProductOptionsFields, +} from '../../shoppingListDetails/shared/config' + +interface QuickOrderCardProps { + item: any, + currencyToken: string, + checkBox?: () => ReactElement, +} + +const StyledImage = styled('img')(() => ({ + maxWidth: '60px', + height: 'auto', + marginRight: '0.5rem', +})) + +const defaultProductImage = 'https://cdn11.bigcommerce.com/s-1i6zpxpe3g/stencil/cd9e3830-4c73-0139-8a51-0242ac11000a/e/4fe76590-73f1-0139-3767-32e4ea84ca1d/img/ProductDefault.gif' + +const QuickOrderCard = (props: QuickOrderCardProps) => { + const { + item: shoppingDetail, + checkBox, + currencyToken = '$', + } = props + + const { + basePrice, + quantity, + primaryImage, + productName, + variantSku, + } = shoppingDetail + + const total = +basePrice * +quantity + const price = +basePrice + + const product: any = { + ...shoppingDetail.productsSearch, + selectOptions: shoppingDetail.optionList, + } + + const productFields = (getProductOptionsFields(product, {})) + + const optionList = JSON.parse(shoppingDetail.optionList) + const optionsValue: CustomFieldItems[] = productFields.filter((item) => item.valueText) + + return ( + + + + { + checkBox && checkBox() + } + + + + + + + {productName} + + + {variantSku} + + + { + (optionList.length > 0 && optionsValue.length > 0) && ( + + { + optionsValue.map((option: any) => ( + + {`${option.valueLabel + }: ${option.valueText}`} + + )) + } + + ) + } + + + {`Price: ${currencyToken}${price.toFixed(2)}`} + + {`qty: ${quantity}`} + + {`Total: ${currencyToken}${total.toFixed(2)}`} + + + + ) +} + +export default QuickOrderCard diff --git a/apps/storefront/src/pages/quickorder/components/QuickorderTable.tsx b/apps/storefront/src/pages/quickorder/components/QuickorderTable.tsx index cbabf0fb..e65be140 100644 --- a/apps/storefront/src/pages/quickorder/components/QuickorderTable.tsx +++ b/apps/storefront/src/pages/quickorder/components/QuickorderTable.tsx @@ -4,32 +4,23 @@ import { useRef, forwardRef, Ref, - // Dispatch, - // SetStateAction, + ReactElement, + Dispatch, + SetStateAction, + useImperativeHandle, } from 'react' import { Box, styled, Typography, - TextField, - // Grid, } from '@mui/material' -// import { -// Delete, -// Edit, -// } from '@mui/icons-material' - import { // getOrderedProducts, searchB2BProducts, } from '@/shared/service/b2b' -import { - B3Sping, -} from '@/components/spin/B3Sping' - import { snackbar, getDefaultCurrencyInfo, @@ -44,9 +35,9 @@ import { B3PaginationTable, } from '@/components/table/B3PaginationTable' -// import { -// useMobile, -// } from '@/hooks' +import { + useMobile, +} from '@/hooks' import { GlobaledContext, @@ -54,15 +45,16 @@ import { import { conversionProductsList, + getProductOptionsFields, } from '../../shoppingListDetails/shared/config' import B3FilterSearch from '../../../components/filter/B3FilterSearch' import B3FilterPicker from '../../../components/filter/B3FilterPicker' -// import { -// ChooseOptionsDialog, -// } from './ChooseOptionsDialog' +import B3FilterMore from '../../../components/filter/B3FilterMore' + +import QuickOrderCard from './QuickOrderCard' interface ListItem { [key: string]: string @@ -106,11 +98,13 @@ interface ListItemProps { // setDeleteOpen: (open: boolean) => void, // } -// interface SearchProps { -// search: string, -// first?: number, -// offset?: number, -// } +interface SearchProps { + q: string, + first?: number, + offset?: number, + beginDateAt?: Date | string | number, + endDateAt?: Date | string | number, +} interface PaginationTableRefProps extends HTMLInputElement { getList: () => void, @@ -124,45 +118,31 @@ const StyledImage = styled('img')(() => ({ marginRight: '0.5rem', })) -const StyledTextField = styled(TextField)(() => ({ - '& input': { - paddingTop: '12px', - paddingRight: '6px', - }, -})) - const defaultProductImage = 'https://cdn11.bigcommerce.com/s-1i6zpxpe3g/stencil/cd9e3830-4c73-0139-8a51-0242ac11000a/e/4fe76590-73f1-0139-3767-32e4ea84ca1d/img/ProductDefault.gif' interface QuickorderTableProps { - isEdit: boolean, + setIsRequestLoading: Dispatch>, } const QuickorderTable = ({ - isEdit, + setIsRequestLoading, }: QuickorderTableProps, ref: Ref) => { const paginationTableRef = useRef(null) - const [search, setSearch] = useState({ + const [search, setSearch] = useState({ q: '', - beginDateAt: '', - endDateAt: '', + beginDateAt: distanceDay(30), + endDateAt: distanceDay(), }) - const pickerRef = useRef(null) - const [checkedArr, setCheckedArr] = useState([]) - const [isRequestLoading, setIsRequestLoading] = useState(false) - - // const [isMobile] = useMobile() + const [isMobile] = useMobile() const { state: { role, - // isB2BUser, isAgenting, - // customer, - // currentChannelId, salesRepCompanyId, companyInfo: { id: companyInfoId, @@ -170,6 +150,10 @@ const QuickorderTable = ({ }, } = useContext(GlobaledContext) + useImperativeHandle(ref, () => ({ + getCheckedList: () => checkedArr, + })) + const { currency_code: currencyCode, token: currencyToken, @@ -222,11 +206,14 @@ const QuickorderTable = ({ } } - const getList = async () => { + const getList = async (params: SearchProps) => { // const { // edges, // totalCount, - // } = await getOrderedProducts(search) + // } = await getOrderedProducts(params) + + // TODO + console.log(params) const edges: any = [ { @@ -379,7 +366,6 @@ const QuickorderTable = ({ ] const totalCount = 7 - const listProducts = await handleGetProductsById(edges) return { @@ -415,20 +401,44 @@ const QuickorderTable = ({ } } + const handlePickerChange = (key: string, value: Date | string | number) => { + const params = { + ...search, + } + if (key === 'start') { + params.beginDateAt = value + } else { + params.endDateAt = value + } + + setSearch(params) + } + + const handleFilterChange = (data: any) => { + const params = { + ...search, + } + + params.beginDateAt = data.startValue + + params.endDateAt = data.endValue + + setSearch(params) + } + const columnItems: TableColumnItem[] = [ { key: 'Product', title: 'Product', render: (row: CustomFieldItems) => { - console.log(row) - // const product: any = { - // ...row.productsSearch, - // selectOptions: row.optionList, - // } - // const productFields = (getProductOptionsFields(product, {})) + const product: any = { + ...row.productsSearch, + selectOptions: row.optionList, + } + const productFields = (getProductOptionsFields(product, {})) - // const optionList = JSON.parse(row.optionList) - // const optionsValue: CustomFieldItems[] = productFields.filter((item) => item.valueText) + const optionList = JSON.parse(row.optionList) + const optionsValue: CustomFieldItems[] = productFields.filter((item) => item.valueText) return ( @@ -455,7 +465,7 @@ const QuickorderTable = ({ > {row.variantSku} - {/* { + { (optionList.length > 0 && optionsValue.length > 0) && ( { @@ -475,7 +485,7 @@ const QuickorderTable = ({ } ) - } */} + } ) @@ -504,21 +514,13 @@ const QuickorderTable = ({ key: 'Qty', title: 'Qty', render: (row) => ( - { - // handleUpdateProductQty(row.id, e.target.value) - // }} - // onBlur={() => { - // handleUpdateShoppingListItem(row.itemId) - // }} - /> + > + {row.quantity} + ), width: '15%', }, @@ -529,10 +531,8 @@ const QuickorderTable = ({ const { basePrice, quantity, - // itemId, } = row const total = +basePrice * +quantity - // const optionList = JSON.parse(row.optionList) return ( @@ -551,78 +551,118 @@ const QuickorderTable = ({ ] return ( - + - - { - handleSearchProduct(e) - }} - /> - - { + handleSearchProduct(e) }} /> + + { + isMobile && ( + + + + ) + } + - ( - 123123 - )} - /> + { + !isMobile && ( + + ) + } - + + ReactElement) => ( + + )} + /> + + ) } diff --git a/apps/storefront/src/pages/quote/QuoteDetail.tsx b/apps/storefront/src/pages/quote/QuoteDetail.tsx index 34aa5ec1..27b52f7a 100644 --- a/apps/storefront/src/pages/quote/QuoteDetail.tsx +++ b/apps/storefront/src/pages/quote/QuoteDetail.tsx @@ -130,8 +130,9 @@ const QuoteDetail = () => { fileName: file.fileName, fileType: file.fileType, fileUrl: file.fileUrl, - id: file.fileUrl, - title: 'Uploaded by customer: xxxx', // TODO + id: file.id, + hasDelete: true, + title: `Uploaded by customer: ${file.createdBy}`, }) }) @@ -140,8 +141,8 @@ const QuoteDetail = () => { fileName: file.fileName, fileType: file.fileType, fileUrl: file.fileUrl, - id: file.fileUrl, - title: 'Uploaded by sales rep: xxxx', // TODO + id: file.id, + title: `Uploaded by sales rep: ${file.createdBy}`, }) }) @@ -390,7 +391,9 @@ const QuoteDetail = () => { { fileList.length > 0 && ( ) diff --git a/apps/storefront/src/pages/quote/QuoteDraft.tsx b/apps/storefront/src/pages/quote/QuoteDraft.tsx index e4f45393..9171ec7d 100644 --- a/apps/storefront/src/pages/quote/QuoteDraft.tsx +++ b/apps/storefront/src/pages/quote/QuoteDraft.tsx @@ -762,7 +762,7 @@ const QuoteDraft = ({ { - role !== 100 && + role !== 100 && } diff --git a/apps/storefront/src/pages/quote/components/FileUpload.tsx b/apps/storefront/src/pages/quote/components/FileUpload.tsx index 0d77c006..2b50a33f 100644 --- a/apps/storefront/src/pages/quote/components/FileUpload.tsx +++ b/apps/storefront/src/pages/quote/components/FileUpload.tsx @@ -12,6 +12,9 @@ import styled from '@emotion/styled' import { useState, + Ref, + forwardRef, + useImperativeHandle, } from 'react' import { @@ -60,9 +63,9 @@ const FileUploadContainer = styled(Box)(() => ({ }, })) -const FileListItem = styled(Box)(() => ({ +const FileListItem = styled(Box)((props: CustomFieldItems) => ({ display: 'flex', - background: 'rgba(25, 118, 210, 0.3)', + background: props.hasDelete ? 'rgba(25, 118, 210, 0.3)' : 'rgba(0, 0, 0, 0.12)', borderRadius: '18px', padding: '6px 8px', alignItems: 'center', @@ -96,13 +99,14 @@ const FileUserTitle = styled(Typography)(() => ({ })) export interface FileObjects { - id: string, + id?: string, fileName: string, fileType: string, fileUrl: string, fileSize?: number, title?: string, hasDelete?: boolean, + isCustomer?: boolean, } interface FileUploadProps { @@ -115,9 +119,10 @@ interface FileUploadProps { fileList: FileObjects[], allowUpload?: boolean, onDelete?: (id: string) => void, + isEndLoadding?: boolean, } -export const FileUpload = (props: FileUploadProps) => { +const FileUpload = (props: FileUploadProps, ref: Ref) => { const { title = 'Add Attachment', tips = 'You can add up to 3 files,not bigger that 2MB each.', @@ -128,10 +133,15 @@ export const FileUpload = (props: FileUploadProps) => { fileList = [], allowUpload = true, onDelete = noop, + isEndLoadding = false, } = props const [loading, setLoading] = useState(false) + useImperativeHandle(ref, () => ({ + setUploadLoadding: (flag: boolean) => setLoading(flag), + })) + const getMaxFileSizeLabel = (maxSize: number) => { if (maxSize / 1048576 > 1) { return `${(maxSize / 1048576).toFixed(1)}MB` @@ -205,10 +215,10 @@ export const FileUpload = (props: FileUploadProps) => { } else { snackbar.error(message) } - - setLoading(false) - } catch (error) { - setLoading(false) + } finally { + if (!isEndLoadding) { + setLoading(false) + } } } } @@ -236,7 +246,7 @@ export const FileUpload = (props: FileUploadProps) => { { fileList.map((file, index) => ( - + { sx={{ cursor: 'pointer', }} - onClick={() => { handleDelete(file.id) }} + onClick={() => { handleDelete(file?.id || '') }} /> ) } @@ -306,3 +316,5 @@ export const FileUpload = (props: FileUploadProps) => { ) } + +export default forwardRef(FileUpload) diff --git a/apps/storefront/src/pages/quote/components/Message.tsx b/apps/storefront/src/pages/quote/components/Message.tsx index db0fb48a..2b97f556 100644 --- a/apps/storefront/src/pages/quote/components/Message.tsx +++ b/apps/storefront/src/pages/quote/components/Message.tsx @@ -40,6 +40,7 @@ interface MessageProps { role?: string, isCustomer?: boolean key?: number | string + read?: number } interface MsgsProps { @@ -175,10 +176,10 @@ const Message = ({ const messagesEndRef = useRef(null) const changeReadRef = useRef(0) - const title = useMemo(() => Message, []) - const [messages, setMessages] = useState([]) + const [read, setRead] = useState(0) + const [message, setMessage] = useState('') const [loadding, setLoadding] = useState(false) @@ -186,6 +187,7 @@ const Message = ({ const convertedMsgs = (msgs: MessageProps[]) => { let nextMsg: MessageProps = {} const getNewMsgs: MessageProps[] = [] + let readNum = 0 msgs.forEach((msg: MessageProps, index: number) => { if (index === 0) { getNewMsgs.push({ @@ -227,11 +229,50 @@ const Message = ({ nextMsg = msg nextMsg.isCustomer = !msg.role?.includes('Sales rep:') } + + if (msg.role?.includes('Sales rep:') && !msg.read) { + readNum += 1 + } }) + setRead(readNum) + setMessages(getNewMsgs) } + const title = useMemo(() => ( + + Message + {' '} + { + read !== 0 && ( + + {read} + + ) + } + + ), [read]) + useEffect(() => { convertedMsgs(msgs) }, [msgs]) @@ -262,6 +303,7 @@ const Message = ({ }, }) setMessage('') + setRead(0) convertedMsgs(trackingHistory) } finally { setLoadding(false) @@ -276,15 +318,18 @@ const Message = ({ const handleOnChange = useCallback((open: boolean) => { if (open) { + const fn = isB2BUser ? updateB2BQuote : updateBCQuote if (changeReadRef.current === 0 && msgs.length) { - updateQuote({ + fn({ id: +id, quoteData: { - timestamp: msgs[msgs.length - 1]?.date, + lastMessage: msgs[msgs.length - 1]?.date, userEmail: email || '', + storeHash, }, }) } + setRead(0) if (messagesEndRef.current) { messagesEndRef.current.scrollTop = messagesEndRef.current.scrollHeight } diff --git a/apps/storefront/src/pages/quote/components/QuoteAttachment.tsx b/apps/storefront/src/pages/quote/components/QuoteAttachment.tsx index 4b3c23c6..c75f51a6 100644 --- a/apps/storefront/src/pages/quote/components/QuoteAttachment.tsx +++ b/apps/storefront/src/pages/quote/components/QuoteAttachment.tsx @@ -8,6 +8,7 @@ import { useState, useEffect, useContext, + useRef, } from 'react' import { @@ -23,19 +24,31 @@ import { } from '@/shared/global' import { + quoteDetailAttachFileCreate, + quoteDetailAttachFileDelete, +} from '@/shared/service/b2b' + +import FileUpload, { FileObjects, - FileUpload, } from './FileUpload' +interface UpLoaddingProps extends HTMLInputElement { + setUploadLoadding: (flag: boolean) => void +} + interface QuoteAttachmentProps{ allowUpload?: boolean, defaultFileList?: FileObjects[] + status?: number + quoteId?: number } export const QuoteAttachment = (props: QuoteAttachmentProps) => { const { allowUpload = true, defaultFileList = [], + status, + quoteId, } = props const { @@ -47,7 +60,13 @@ export const QuoteAttachment = (props: QuoteAttachmentProps) => { }, } = useContext(GlobaledContext) - const [fileList, setFileList] = useState(defaultFileList) + const [fileList, setFileList] = useState([]) + + useEffect(() => { + setFileList(defaultFileList) + }, [defaultFileList]) + + const uploadRef = useRef(null) useEffect(() => { if (defaultFileList.length <= 0) { @@ -70,24 +89,64 @@ export const QuoteAttachment = (props: QuoteAttachmentProps) => { } } - const handleChange = (file: FileObjects) => { - const newFileList = [...fileList, { - ...file, - title: `Uploaded by customer: ${firstName} ${lastName}`, - hasDelete: true, - }] - - saveQuoteInfo(newFileList) - - setFileList(newFileList) + const handleChange = async (file: FileObjects) => { + try { + let newFileList: FileObjects[] = [] + if (status !== 0) { + const createFile: FileObjects = { + fileName: file.fileName, + fileType: file.fileType, + fileUrl: file.fileUrl, + fileSize: file.fileSize, + } + const { + quoteAttachFileCreate: { + attachFiles, + }, + } = await quoteDetailAttachFileCreate({ + fileList: [{ + ...createFile, + }], + quoteId, + }) + + createFile.id = attachFiles[0].id + newFileList = [...fileList, { + ...createFile, + title: `Uploaded by customer: ${attachFiles[0].createdBy}`, + hasDelete: true, + }] + } else { + newFileList = [...fileList, { + ...file, + title: `Uploaded by customer: ${firstName} ${lastName}`, + hasDelete: true, + }] + saveQuoteInfo(newFileList) + } + setFileList(newFileList) + } finally { + uploadRef.current?.setUploadLoadding(false) + } } - const handleDelete = (id: string) => { - const newFileList = fileList.filter((file) => file.id !== id) - - saveQuoteInfo(newFileList) - - setFileList(newFileList) + const handleDelete = async (id: string) => { + try { + uploadRef.current?.setUploadLoadding(true) + const deleteFile = fileList.find((file) => file.id === id) + const newFileList = fileList.filter((file) => file.id !== id) + if (status !== 0 && deleteFile) { + await quoteDetailAttachFileDelete({ + fileId: deleteFile?.id || '', + quoteId, + }) + } else { + saveQuoteInfo(newFileList) + } + setFileList(newFileList) + } finally { + uploadRef.current?.setUploadLoadding(false) + } } return ( @@ -96,6 +155,8 @@ export const QuoteAttachment = (props: QuoteAttachmentProps) => { `mutation{ + quoteAttachFileCreate( + quoteId: ${data.quoteId}, + fileList: ${convertArrayToGraphql(data.fileList || [])} + ) { + attachFiles { + id, + createdBy, + fileUrl, + } + } +}` + +const quoteAttachFileDelete = (data: CustomFieldItems) => `mutation{ + quoteAttachFileDelete( + quoteId: ${data.quoteId}, + fileId: ${data.fileId} + ) { + message + } +}` + export const getBCCustomerAddresses = (): CustomFieldItems => B3Request.graphqlProxyBC({ query: getCustomerAddresses(), }) @@ -365,3 +389,11 @@ export const b2bQuoteCheckout = (data: { id: number }): CustomFieldItems => B3Re export const bcQuoteCheckout = (data: { id: number }): CustomFieldItems => B3Request.graphqlProxyBC({ query: quoteCheckout(data), }) + +export const quoteDetailAttachFileCreate = (data: CustomFieldItems): CustomFieldItems => B3Request.graphqlProxyBC({ + query: quoteAttachFileCreate(data), +}) + +export const quoteDetailAttachFileDelete = (data: CustomFieldItems): CustomFieldItems => B3Request.graphqlProxyBC({ + query: quoteAttachFileDelete(data), +}) diff --git a/apps/storefront/src/shared/service/b2b/index.ts b/apps/storefront/src/shared/service/b2b/index.ts index a314ff83..c9941aa2 100644 --- a/apps/storefront/src/shared/service/b2b/index.ts +++ b/apps/storefront/src/shared/service/b2b/index.ts @@ -53,6 +53,8 @@ import { bcQuoteCheckout, updateB2BQuote, updateBCQuote, + quoteDetailAttachFileCreate, + quoteDetailAttachFileDelete, } from './graphql/quote' import { @@ -193,4 +195,6 @@ export { bcQuoteCheckout, updateB2BQuote, updateBCQuote, + quoteDetailAttachFileCreate, + quoteDetailAttachFileDelete, } diff --git a/apps/storefront/src/shared/service/request/b3Fetch.ts b/apps/storefront/src/shared/service/request/b3Fetch.ts index f5a4383b..bb4aca29 100644 --- a/apps/storefront/src/shared/service/request/b3Fetch.ts +++ b/apps/storefront/src/shared/service/request/b3Fetch.ts @@ -1,6 +1,3 @@ -import { - keyBy, -} from 'lodash' import { b3Fetch, } from './fetch'