import { defineComponent, onMounted, ref, watch, computed, onBeforeUnmount, PropType, nextTick } from 'vue' import { tools } from '@store/Modules/tools' import { useUserStore } from '@store/UserStore' import { useRouter } from 'vue-router' import { useGlobalStore } from '@store/globalStore' import { useProducts } from '@store/Products' import { useI18n } from '@/boot/i18n' import { toolsext } from '@store/Modules/toolsext' import { useQuasar } from 'quasar' import { costanti } from '@costanti' import { shared_consts } from '@/common/shared_vuejs' import { CProductCard } from '@src/components/CProductCard' import { CMySelect } from '@src/components/CMySelect' import { CContainerCatalogoCard } from '@src/components/CContainerCatalogoCard' import { CSelectUserActive } from '@src/components/CSelectUserActive' import { ICatalogo, IFilterCatalogo, IMyScheda, IProdView, IProduct, ISchedaSingola, ISearchList } from 'model' import html2canvas from 'html2canvas' // import { VueHtmlToPaper } from 'vue-html-to-paper' import html2pdf from 'html2pdf.js' import { fieldsTable } from '@store/Modules/fieldsTable' export default defineComponent({ name: 'Catalogo', components: { CContainerCatalogoCard, CProductCard, CSelectUserActive, CMySelect }, props: { optcatalogo: { type: Object as PropType, required: false, default: () => ({ //++AddCATALOGO_FIELDS productTypes: [0], excludeproductTypes: [], formato: [], Categoria: [], Editore: [], pdf: false, }), }, }, setup(props) { const userStore = useUserStore() const globalStore = useGlobalStore() const productStore = useProducts() const router = useRouter() const $q = useQuasar() const { t } = useI18n() const search = ref('') const optauthors = ref([]) const pdfContent = ref(null); const filter = ref({ author: '', sort: 1, publisher: '', type: '', ageGroup: '' }) const cosa = ref(0) const cat = ref('') const idGasSel = ref('') const loadpage = ref(false) const refreshpage = ref(false) const show_hide = ref(false) const mycolumns = ref([]) const tabvisu = ref('categorie') const tabcatalogo = ref('visu') const searchList = ref([] as ISearchList[]) const arrProducts = ref([]) const arrProdToView = ref([]) const numRecLoaded = ref(0) // Create a ref for the component to fix const componentToFixRef = ref(null); const isFixed = ref(false); const labelcombo = computed(() => (item: any) => { let lab = item.label if (item.showcount) lab += ' (' + valoriopt.value(item, false, false).length + ')' return lab }) const arrLoaded = computed(() => { if (arrProducts.value && numRecLoaded.value) return arrProducts.value.slice(0, numRecLoaded.value) else { return [] } }) // Register the scroll event on component mount const handleScroll = () => { const scrollTop = window.scrollY || document.documentElement.scrollTop; // Set a threshold value based on how much scroll is needed to fix the components const threshold = 300; // Update the isFixed ref based on the scroll position isFixed.value = scrollTop > threshold; }; watch(() => cat.value, (newval, oldval) => { if (cat.value) { if (loadpage.value) tools.setCookie(tools.COOK_CATEGORIA, cat.value.toString()) filter.value.author = '' // disattivo il filtro autore resetSearch() } calcArrProducts() }) watch(() => idGasSel.value, (newval, oldval) => { calcArrProducts() }) watch(() => getSearchText(), (newval, oldval) => { calcArrProducts() if (tools.scrollTop() > 300) { tools.scrollToTopValue(300) } }) watch(() => filter.value.author, (newval, oldval) => { // Se filtroAuthor attivato, allora evito il filtro per Categoria if (filter.value.author) { cat.value = '' // disattivo il filtro categoria if (loadpage.value) tools.setCookie(tools.COOK_CATEGORIA, '') resetSearch() } calcArrProducts() if (tools.scrollTop() > 300) { tools.scrollToTopValue(300) } }) watch(() => filter.value.sort, (newval, oldval) => { calcArrProducts() if (tools.scrollTop() > 300) { tools.scrollToTopValue(300) } }) watch(() => cosa.value, (newval, oldval) => { if (oldval !== 0) { tools.setCookie(tools.COOK_COSA_PRODOTTI, cosa.value.toString()) if (cosa.value !== shared_consts.PROD.TUTTI) { cat.value = '' if (loadpage.value) tools.setCookie(tools.COOK_CATEGORIA, '') } calcArrProducts() } }) function resetSearch() { const mialista = getSearchList() if (mialista && mialista.value && mialista.value.hasOwnProperty('name')) { mialista.value = null } search.value = '' } function getSearchList() { const mylist = searchList.value.find((rec: any) => rec.table === 'products' && rec.key === 'titolo') return mylist } function getSearchText(): string { const lista = getSearchList() return lista && lista.value && lista.value.hasOwnProperty('name') ? lista.value.name : '' } function calcArrProducts() { console.log('calcArrProducts') // eventuali titoli specifici estratti dall'array di Prodotti Selezionati //const searchtext = getSearchText() const searchtext = getSearchText() let arrprod = productStore.getProducts(cosa.value) || []; let filtroAuthor = filter.value.author || ''; //++AddCATALOGO_FIELDS let filtroProductTypes = props.optcatalogo.productTypes || [0] let filtroExcludeProductTypes = props.optcatalogo.excludeproductTypes || [0] let boolfiltroVuotoProductTypes = (filtroProductTypes.length === 0 || (filtroProductTypes.length === 1 && (filtroProductTypes[0] === 0))) let boolfiltroVuotoExcludeProductTypes = filtroExcludeProductTypes.length === 0 let filtroPublishers = props.optcatalogo.Editore || [] let boolfiltroVuotoEditore = (filtroPublishers.length === 0) //console.log('filtroVersione', filtroProductTypes) let catstr = cat.value || '' let gasselstr = '' if (cosa.value === shared_consts.PROD.GAS) { gasselstr = idGasSel.value || ''; } let lowerSearchText = (searchtext || '').toLowerCase().trim(); lowerSearchText = lowerSearchText.replace(/[-@:=]/g, ''); if ((!lowerSearchText || (lowerSearchText && lowerSearchText.length < 2)) && !catstr && boolfiltroVuotoProductTypes && boolfiltroVuotoExcludeProductTypes && boolfiltroVuotoEditore && !filtroAuthor && (!gasselstr && (cosa.value !== shared_consts.PROD.GAS))) { } else { arrprod = arrprod.filter((product: IProduct) => { if (product && product.productInfo) { let lowerName = (product.productInfo.name || '').toLowerCase(); let hasCategoria = !catstr || (catstr && (product.productInfo.idCatProds || []).includes(catstr)); let hasAuthor = !filtroAuthor || (filtroAuthor && (product.productInfo.idAuthors || []).includes(filtroAuthor)); let hasProductTypes = true let hasPublished = true let hasExcludeProductTypes = false //++AddCATALOGO_FIELDS if (props.optcatalogo && !boolfiltroVuotoProductTypes) { // check if productInfo.productTypes array includes some item in props.optcatalogo.ProductTypes array hasProductTypes = !props.optcatalogo.productTypes || (props.optcatalogo.productTypes && (product.productInfo.productTypes || []).some((item: any) => props.optcatalogo.productTypes.includes(item))) } if (props.optcatalogo && !boolfiltroVuotoEditore) { hasPublished = !props.optcatalogo.Editore || (props.optcatalogo.Editore && props.optcatalogo.Editore.includes(product.productInfo.idPublisher!)) } if (props.optcatalogo && !boolfiltroVuotoExcludeProductTypes) { // check if productInfo.productTypes array exclude some item in props.optcatalogo.ProductTypes array hasExcludeProductTypes = !props.optcatalogo.excludeproductTypes || (props.optcatalogo.excludeproductTypes && (product.productInfo.productTypes || []).every((item: any) => props.optcatalogo.excludeproductTypes.includes(item))) } let productgassel = true if (gasselstr || (cosa.value === shared_consts.PROD.GAS)) { productgassel = (product.idGasordine === gasselstr) } // Use a regular expression to match whole words let codeMatch = new RegExp(`\\b${lowerSearchText}\\b`, 'i'); // let nameMatch = new RegExp(`\\b(?=.*\\b${lowerSearchText.split(/\s+/).map(word => `(${word})\\b`).join('.*\\b')}\\b)`, 'i'); // Check if all words in lowerSearchText are present in lowerName let allWordsPresent = lowerSearchText.split(/\s+/).every(word => new RegExp(`\\b${word}\\b`, 'i').test(lowerName)); return (codeMatch.test(product.productInfo.code || '') || allWordsPresent) && hasCategoria && hasAuthor && productgassel && hasProductTypes && hasPublished && !hasExcludeProductTypes; } else { console.error('product or product.productInfo is null'); return false; } }); } // console.log('filter.value.sort', filter.value.sort) // sort using filter.value.sort : if (filter.value.sort === costanti.SORT_PUBDATE) { arrprod = arrprod.sort((a: IProduct, b: IProduct) => { return b.productInfo.date_publishing_ts - a.productInfo.date_publishing_ts }) } else if (filter.value.sort === costanti.SORT_ALPHA) { } arrProducts.value = arrprod generatearrProdToViewSorted() loaddata() refreshpage.value = false } function getProductsFilteredByScheda(scheda: IMyScheda) { const searchtext = scheda.arrProdottiSpeciali let arrprod = productStore.getProducts(cosa.value) || []; let filtroAuthor = filter.value.author || ''; let filtroProductTypes = scheda.productTypes || [0] let filtroExcludeProductTypes = scheda.excludeproductTypes || [0] let boolfiltroVuotoProductTypes = (filtroProductTypes.length === 0 || (filtroProductTypes.length === 1 && (filtroProductTypes[0] === 0))) let boolfiltroVuotoExcludeProductTypes = filtroExcludeProductTypes.length === 0 let filtroPublishers = scheda.editore || [] let boolfiltroVuotoEditore = (filtroPublishers.length === 0) //console.log('filtroVersione', filtroProductTypes) let catstr = cat.value || '' let gasselstr = '' if (cosa.value === shared_consts.PROD.GAS) { gasselstr = idGasSel.value || ''; } let lowerSearchTexts = (searchtext || []).map((text: string) => text.toLowerCase().trim().replace(/[-@:=]/g, '') ) // Remove the condition that skips filtering if search text is too short // Now it will work with multiple search terms arrprod = arrprod.filter((product: IProduct) => { if (product && product.productInfo) { let lowerName = (product.productInfo.name || '').toLowerCase(); let lowerCode = (product.productInfo.code || '').toLowerCase(); let hasCategoria = !catstr || (catstr && (product.productInfo.idCatProds || []).includes(catstr)); let hasAuthor = !filtroAuthor || (filtroAuthor && (product.productInfo.idAuthors || []).includes(filtroAuthor)); // Check if ANY search term matches the product name or code let searchMatch = lowerSearchTexts.length === 0 || lowerSearchTexts.some((searchTerm: any) => { // Check if the entire search term is a whole word in name or code let codeMatch = new RegExp(`\\b${searchTerm}\\b`, 'i').test(lowerCode); // Check if all words in the search term are present in the name let allWordsPresent = searchTerm.split(/\s+/).every((word: string) => new RegExp(`\\b${word}\\b`, 'i').test(lowerName) ); return codeMatch || allWordsPresent; }); let hasProductTypes = true let hasPublished = true let hasExcludeProductTypes = false if (!boolfiltroVuotoProductTypes) { // check if productInfo.productTypes array includes some item in scheda.ProductTypes array hasProductTypes = !scheda.productTypes || (scheda.productTypes && (product.productInfo.productTypes || []).some((item: any) => scheda.productTypes.includes(item))) } if (!boolfiltroVuotoEditore) { hasPublished = !scheda.editore || (scheda.editore && scheda.editore.includes(product.productInfo.idPublisher!)) } if (!boolfiltroVuotoExcludeProductTypes) { // check if productInfo.productTypes array exclude some item in scheda.ProductTypes array hasExcludeProductTypes = !scheda.excludeproductTypes || (scheda.excludeproductTypes && (product.productInfo.productTypes || []).every((item: any) => scheda.excludeproductTypes.includes(item))) } return searchMatch && hasCategoria && hasAuthor && hasProductTypes && hasPublished && !hasExcludeProductTypes; } else { console.error('product or product.productInfo is null'); return false; } }) // console.log('filter.value.sort', filter.value.sort) // sort using filter.value.sort : if (scheda.sort === costanti.SORT_PUBDATE) { arrprod = arrprod.sort((a: IProduct, b: IProduct) => { return b.productInfo.date_publishing_ts - a.productInfo.date_publishing_ts }) } else if (scheda.sort === costanti.SORT_ALPHA) { } return arrprod } function addNextProductToTheView(arrproductfiltrati: IProduct[], indprod: number) { try { let rectrovato = null; while (true) { if (indprod >= arrproductfiltrati.length) { return { end: true } } rectrovato = arrProdToView.value.find( (prodview: IProdView) => prodview.id === arrproductfiltrati[indprod]._id ); if (rectrovato) { indprod++ continue; // Era stato già aggiunto, quindi prova col prossimo } else { // Non è stato ancora aggiunto, quindi prendo questo e lo aggiungo alla lista ! const myrec = arrproductfiltrati[indprod] arrProdToView.value.push({ id: myrec._id, showed: false }); return { myrec, added: true, indprod } } } } catch (e) { console.error(e); return { rec: null, indprod }; // Assicurati di gestire correttamente l'errore } } function getProdBySchedaRigaCol(recscheda: ISchedaSingola, riga: number, col: number) { try { return recscheda.arrProdToShow![riga][col] } catch (e) { return null } } function generatearrProdToViewSorted() { console.log('generatearrProdToViewSorted') // Svuota arrProdToView.value = [] for (const recscheda of props.optcatalogo.arrSchede!) { if (recscheda && recscheda.scheda) { let schedePerRiga = recscheda.scheda.numschede_perRiga || 1 let schedePerCol = recscheda.scheda.numschede_perCol || 1 let schedePerPagina = schedePerRiga * schedePerCol // Filtra i prodotti in base ai filtri impostati ! const arrProdFiltrati = getProductsFilteredByScheda(recscheda.scheda) let indprod = 0 let indadded = 0 recscheda.arrProdToShow = [] for (let giro = 0; giro < schedePerPagina; giro++) { // Aggiunge il prossimo prodotto che non è stato ancora inserito const result = addNextProductToTheView(arrProdFiltrati, indprod); if (result.end) { break; // Esci dal ciclo se non ci sono più prodotti disponibili } else { if (result.indprod) indprod = result.indprod // Aggiorna indprod per il prossimo giro if (result.myrec) { let riga = Math.floor(indadded / schedePerCol) let col = indadded % schedePerCol if (!recscheda.arrProdToShow[riga]) { recscheda.arrProdToShow[riga] = []; } recscheda.arrProdToShow[riga][col] = result.myrec indadded++ } } } console.log('*** arrProdToShow', recscheda.arrProdToShow) } } } function getNextProd() { let nextRecord = arrProdToView.value.find((rec: any) => !rec.showed) // Se un tale record esiste, impostalo su mostrato if (nextRecord) { nextRecord.showed = true return arrProducts.value.find((recprod: IProduct) => recprod._id === nextRecord.id) } return null } /*function getProducts() { let arrprod = productStore.getProducts(cosa.value) if (!search.value) { return arrprod } let lowerSearchText = search.value.toLowerCase(); let catstr = cat.value; return arrprod.filter((product: IProduct) => { let lowerName = product.productInfo.name!.toLowerCase(); const hasCategoria = !catstr || (catstr && product.productInfo.idCatProds?.includes(catstr)); return (product.productInfo.code!.includes(search.value) || lowerName.includes(lowerSearchText)) && hasCategoria }); }*/ async function mounted() { console.log('mounted Catalogo') loadpage.value = false await productStore.loadProducts() mycolumns.value = fieldsTable.getArrColsByTable('products') searchList.value = [ { label: 'Ricerca', table: 'products', key: 'titolo', type: costanti.FieldType.select_by_server, value: '', // addall: true, arrvalue: [], useinput: true, filter: null, tablesel: 'products', }, ] optauthors.value = productStore.getAuthors() //++Todo: Per ora visualizzo solo il "Negozio" e non i GAS... cosa.value = shared_consts.PROD.BOTTEGA cat.value = tools.getCookie(tools.COOK_CATEGORIA, '') //cosa.value = tools.getCookie(tools.COOK_COSA_PRODOTTI, shared_consts.PROD.GAS, true) //if (cosa.value === shared_consts.PROD.TUTTI) // Inizializza loadpage.value = true window.addEventListener('scroll', handleScroll); calcArrProducts() loaddata() } function loaddata() { numRecLoaded.value = 20 } // Remove the event listener on component destroy onBeforeUnmount(() => { window.removeEventListener('scroll', handleScroll); }); function getCatProds() { let arrcat = productStore.getCatProds(cosa.value) let riscat: any = [{ label: 'Tutti', value: '', icon: undefined, color: undefined }] for (const rec of arrcat) { riscat.push({ label: rec.name, value: rec._id, icon: rec.icon, color: rec.color }) } return riscat } function onLoadScroll(index: number, done: any) { if (index >= 1) { if (numRecLoaded.value < arrProducts.value.length) { const step = 10 let mynrec = numRecLoaded.value + step if (mynrec > arrProducts.value.length) mynrec = arrProducts.value.length numRecLoaded.value = mynrec } done() } else { done(true) } } function filterFn(val: any, update: any, abort: any) { update(() => { const needle = val.toLowerCase(); optauthors.value = productStore.getAuthors().filter(v => { const authorName = v.label.toLowerCase(); const wordsToSearch = needle.split(' '); return wordsToSearch.every((word: any) => { if (word.length > 1) { return authorName.includes(word); } else { return authorName.split(' ').some((namePart: any) => namePart.startsWith(word)); } }); }); }); } function selauthor(id: string, value: string) { filter.value.author = id } /*function searchval(newval: any, table: any, tablesel: any) { console.log('REFRR searchval', newval, table, 'tablesel', tablesel) if (newval) { if (newval.hasOwnProperty('name')) { search.value = newval.name } } else { resetSearch() } }*/ const valoriopt = computed(() => (item: any, addall: boolean, addnone: boolean) => { // console.log('valoriopt', item.table) return globalStore.getTableJoinByName(item.table, addall, addnone, item.filter) }) const loadImage = (src: any) => { return new Promise((resolve, reject) => { const img = new Image() img.onload = () => resolve(img) img.onerror = reject img.src = src }) } const generatePDF = async () => { await nextTick() $q.loading.show({ message: 'Caricamento immagini e generazione PDF in corso...' }) try { const element = document.getElementById('pdf-content') const opt = { margin: [0.1, 0.1, 0.1, 0.1], filename: 'catalogo_completo.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2, useCORS: true, letterRendering: true, }, jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait', compress: true }, } await html2pdf().set(opt).from(element).save() $q.loading.hide() $q.notify({ color: 'positive', message: 'PDF generato con successo!', icon: 'check' }) } catch (error) { $q.loading.hide() $q.notify({ color: 'negative', message: 'Errore nella generazione del PDF', icon: 'error' }) console.error('Errore nella generazione del PDF:', error) } } function groupedPages(scheda: IMyScheda) { if (scheda) { const schedePerRiga = scheda.numschede_perRiga || 1 const schedePerCol = scheda.numschede_perCol || 1 const schedePerPagina = schedePerRiga * schedePerCol let indiceprodotto = 0 const pages = [] // Iterate attraverso l'array prodotti con step = schedePerPagina for (let pageStart = 0; pageStart < arrProducts.value.length; pageStart += schedePerPagina) { const page = [] // Crea le righe per questa pagina for (let rowStart = 0; rowStart < schedePerCol; rowStart++) { const row = [] // Riempi ogni riga con il numero corretto di prodotti for (let col = 0; col < schedePerRiga; col++) { const productIndex = pageStart + (rowStart * schedePerRiga) + col row.push(indiceprodotto) indiceprodotto++ } page.push(row) } pages.push(page) } return pages } return null } function generateStyleCatalogo(optcatalogo: ICatalogo) { const fileimg = (optcatalogo.printable ? optcatalogo.backgroundimage_printable : optcatalogo.backgroundimage) const marginBottom = optcatalogo.dimensioni.pagina.margini!.bottom || '' const backgroundImage = fileimg ? `url(${costanti.DIR_UPLOAD + costanti.DIR_CATALOGO + fileimg})` : '' const backgroundSize = `${optcatalogo.printable ? optcatalogo.backgroundSize_printable : optcatalogo.backgroundSize}`; const width = optcatalogo.dimensioni.pagina.size?.width return { backgroundImage, backgroundSize, marginBottom, '--width': width }; } function generateStylePageScheda(optcatalogo: ICatalogo, scheda: IMyScheda) { const marginTop = `${scheda.dimensioni.pagina.margini!.top}` const marginBottom = scheda.dimensioni.pagina.margini!.bottom const fileimg = (optcatalogo.printable ? scheda.bgimg_printable : scheda.bgimg) const backgroundImage = fileimg ? `url(${costanti.DIR_UPLOAD + costanti.DIR_SCHEDA + fileimg})` : '' const backgroundSize = `${optcatalogo.printable ? scheda.bgSize_printable : scheda.bgSize}`; const width = scheda.dimensioni.pagina.size?.width return { marginBottom, marginTop, backgroundImage, backgroundSize, '--width': width }; } onMounted(mounted) return { userStore, costanti, tools, toolsext, search, cosa, shared_consts, getCatProds, cat, idGasSel, productStore, t, loadpage, refreshpage, componentToFixRef, isFixed, arrProducts, show_hide, onLoadScroll, numRecLoaded, arrLoaded, filter, optauthors, filterFn, selauthor, searchList, fieldsTable, valoriopt, labelcombo, mycolumns, tabvisu, getSearchText, generatePDF, pdfContent, tabcatalogo, groupedPages, getNextProd, getProdBySchedaRigaCol, generateStylePageScheda, generateStyleCatalogo, } } })