diff --git a/public/core/banquet-client-menu.js b/public/core/banquet-client-menu.js index 4ef41d9..feb430f 100644 --- a/public/core/banquet-client-menu.js +++ b/public/core/banquet-client-menu.js @@ -99,7 +99,7 @@ } throw new Error('Меню не помещается на одну страницу без слишком мелкого текста. Сократите названия или количество выбранных блюд. Ни одно блюдо не было обрезано.'); } - async function render(s,{showPrices=true,themeId='gold'}={}){ + async function render(s,{themeId='gold'}={}){ const theme=THEMES[themeId]||THEMES.gold; await fonts();const [pdf,logo]=await Promise.all([engine(),logoImage(s.brand.logo)]); const canvas=document.createElement('canvas');canvas.width=W*2;canvas.height=H*2; @@ -119,7 +119,7 @@ y+=text(facts,M,y,W-M*2,`600 16px ${SANS}`,gold,23,centered?'center':'left')+24; line(M,y,W-M);const top=y+20; c.font=`13px ${SANS}`;const contacts=wrap(c,s.brand.contacts,W-M*2),contactHeight=contacts.length*19; - const footerHeight=(showPrices?108:62)+contactHeight+(s.estimated?22:0),bottom=H-64-footerHeight; + const footerHeight=108+contactHeight+(s.estimated?22:0),bottom=H-64-footerHeight; const layout=plan(c,s,top,bottom); for(let j=0;j16&&c.measureText(v).width>W-M*2)c.font=`600 ${--size}px ${SERIF}`; - text(v,M,fy,W-M*2,c.font,ink,size+4,centered?'center':'left');fy+=size+16; - text('За гостя, только выбранные блюда. Доставка и услуги не включены.',M,fy,W-M*2,`12px ${SANS}`,muted,18,centered?'center':'left');fy+=24; - }else if(s.grams){text(`Выход по указанным порциям: ${s.grams.toLocaleString('ru-RU')} г на гостя`,M,fy,W-M*2,`14px ${SANS}`,muted,20,centered?'center':'left');fy+=26;} + // It is not optional: the client always sees what one guest costs. + text('ЦЕНА НА ОДНОГО ГОСТЯ',M,fy,W-M*2,`700 12px ${SANS}`,muted,17,centered?'center':'left');fy+=24; + let size=34;c.font=`600 ${size}px ${SERIF}`;const v=money(s.cents/100);while(size>16&&c.measureText(v).width>W-M*2)c.font=`600 ${--size}px ${SERIF}`; + text(v,M,fy,W-M*2,c.font,ink,size+4,centered?'center':'left');fy+=size+16; + text('За гостя, только выбранные блюда. Доставка и услуги не включены.',M,fy,W-M*2,`12px ${SANS}`,muted,18,centered?'center':'left');fy+=24; if(s.estimated){text('≈ Вес и стоимость предварительные — уточняются при согласовании.',M,fy,W-M*2,`12px ${SANS}`,muted,18);fy+=22;} if(s.brand.contacts)text(s.brand.contacts,M,fy+5,W-M*2,`13px ${SANS}`,muted,19); const jpeg=await new Promise((resolve,reject)=>canvas.toBlob(b=>b?resolve(b):reject(new Error('Не удалось сформировать страницу меню.')),'image/jpeg',.96)); @@ -162,21 +161,21 @@ const s=snapshot(input),started=scope();close();style(); const dialog=document.createElement('dialog');dialog.id='ctBanquetClientDialog';dialog.setAttribute('aria-labelledby','ctBanquetClientTitle'); const themeOptions=themeIds.map(id=>``).join(''); - dialog.innerHTML=`

Меню для клиента

Оформляю меню на одной странице…

`; + dialog.innerHTML=`

Меню для клиента

Оформляю меню на одной странице…

`; const state={dialog,snapshot:s,generation:0,url:null,result:null,restore:document.activeElement};panel=state; - const get=q=>dialog.querySelector(q),status=get('[data-bcm-status]'),download=get('[data-bcm-download]'),share=get('[data-bcm-share]'),check=get('[data-bcm-prices]'),themeSelect=get('[data-bcm-theme]'); + const get=q=>dialog.querySelector(q),status=get('[data-bcm-status]'),download=get('[data-bcm-download]'),share=get('[data-bcm-share]'),themeSelect=get('[data-bcm-theme]'); document.body.append(dialog);dialog.addEventListener('cancel',e=>{e.preventDefault();close();});get('[data-bcm-close]').onclick=close;dialog.showModal(); const valid=()=>panel===state&&scope()===started&&allowed(); const build=async()=>{ const ticket=++state.generation;download.disabled=true;share.disabled=true;state.result=null;status.textContent='Оформляю меню на одной странице…'; - try{const result=await render(s,{showPrices:check.checked,themeId:themeSelect.value});if(!valid()||ticket!==state.generation)return; + try{const result=await render(s,{themeId:themeSelect.value});if(!valid()||ticket!==state.generation)return; if(state.url)URL.revokeObjectURL(state.url);state.url=URL.createObjectURL(result.jpeg);state.result=result; const img=document.createElement('img');img.src=state.url;img.alt='Банкетное меню: одна страница A4';get('[data-bcm-preview]').replaceChildren(img); - status.textContent=`Готово: одна страница A4 · ${s.items.length} позиций. ${check.checked?'Показана только цена за гостя.':'Без указания цены.'}`;download.disabled=false; + status.textContent=`Готово: одна страница A4 · ${s.items.length} позиций. Показана только цена за гостя.`;download.disabled=false; const file=new File([result.blob],'Банкетное меню.pdf',{type:'application/pdf'});share.hidden=!(navigator.share&&navigator.canShare?.({files:[file]}));share.disabled=false; }catch(error){if(valid()&&ticket===state.generation){status.textContent=error.message;get('[data-bcm-preview]').replaceChildren();}} }; - check.onchange=build;themeSelect.onchange=build; + themeSelect.onchange=build; download.onclick=()=>{if(!valid()||!state.result)return;const url=URL.createObjectURL(state.result.blob),a=document.createElement('a');a.href=url;a.download=`Банкетное меню${s.date?' '+s.date:''}.pdf`;document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),60000);}; share.onclick=async()=>{if(!valid()||!state.result)return;try{await navigator.share({files:[new File([state.result.blob],'Банкетное меню.pdf',{type:'application/pdf'})],title:'Банкетное меню'});}catch(error){if(valid()&&error.name!=='AbortError')status.textContent='Не удалось открыть отправку. Сохраните PDF и отправьте его вручную.';}}; await build();return state.result; diff --git a/public/core/banquet-menu.js b/public/core/banquet-menu.js index 474fc01..40cf300 100644 --- a/public/core/banquet-menu.js +++ b/public/core/banquet-menu.js @@ -48,7 +48,7 @@ if(!window.CateriumBanquetClientMenu)await new Promise((resolve,reject)=>{ const script=document.createElement('script');let timer; const fail=()=>{clearTimeout(timer);script.remove();reject(new Error('Не удалось загрузить оформление меню. Повторите попытку.'));}; - script.src='core/banquet-client-menu.js?v=20260922-no-item-prices';script.onload=()=>{clearTimeout(timer);window.CateriumBanquetClientMenu?resolve():fail();};script.onerror=fail;timer=setTimeout(fail,12000);document.head.append(script); + script.src='core/banquet-client-menu.js?v=20260922-guest-price-always';script.onload=()=>{clearTimeout(timer);window.CateriumBanquetClientMenu?resolve():fail();};script.onerror=fail;timer=setTimeout(fail,12000);document.head.append(script); }); if(!root.isConnected||who!==JSON.stringify([window.SunCloudV2?.getSession?.()?.user?.id,window.SunCloudV2?.getWorkspace?.()?.id]))return; await window.CateriumBanquetClientMenu.open(input); diff --git a/public/service-worker.js b/public/service-worker.js index 72fad9f..9f90c77 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -4,7 +4,7 @@ const CORE=[ './core/support-form.js?v=20260921-support-recipient', './core/trial-promo-developer-v181.js?v=20260921-promo-entry', './core/single-item-pdf.js?v=20260922-single-item-pdf-v1', - './core/banquet-client-menu.js?v=20260922-no-item-prices', + './core/banquet-client-menu.js?v=20260922-guest-price-always', './core/training-catalog.js?v=20260922-training-photos', './core/catalog-pricing.js?v=20260919-client-menu','./core/client-menu.css?v=20260919-client-menu', './vendor/supabase-2.112.4.min.js',