caterium-app/public/core/ux-fixes-v1764.js
pavlov346346-source 7446a551f6 fix: allow premium-dark/premium-emerald in template id whitelists
Two separate Set-based whitelists (OFFER_TEMPLATE_IDS in app-runtime.js
and TEMPLATE_IDS in ux-fixes-v1764.js) validated any explicit template
selection before persisting/rendering it, but neither had been updated
when premium-dark/premium-emerald were added. The per-order "Оформление
PDF" picker's click handler calls persistOfferTemplate(), which silently
returned false when the id failed this check -- so clicking either
premium card in an order's offer modal did nothing, no error, no toast.
Verified end-to-end against a real order: both ids now persist and the
picker shows the selected state correctly.

Also bumped performance.js's internal RELEASE tag, since it gates the
cache-busting query string used to dynamically load ux-fixes-v1764.js
and eight other core modules -- otherwise this fix would hit the same
stale-cache issue just fixed in index.html.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 13:46:15 +03:00

242 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(()=>{
'use strict';
if(window.SunUXFixV1764)return;
const VERSION='17.6.7';
const RELEASE_DAY='2026-09-07';
const AUTO_DELAY_MS=60*1000;
const AUTO_POLL_MS=15000;
const TEMPLATE_IDS=new Set(['cream-elegance','neon-menu','emerald-circles','midnight-checklist','gourmet-hero','diamond-gold','premium-dark','premium-emerald','light','midnight-glass','editorial-grid','warm-sun','bento-cards','event-story','black-gold','personal-letter','event-ticket','solar-experience','midnight-compact','emerald-gold','neon-emerald']);
const $=id=>document.getElementById(id);
const qa=(s,r=document)=>[...r.querySelectorAll(s)];
const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const toast=(text,type='info',ms=4200)=>{try{return window.SunEnterprise?.toast?.(text,type,ms)}catch(_){};};
const clone=value=>{try{return structuredClone(value)}catch(_){return JSON.parse(JSON.stringify(value))}};
function runtimeOrders(){
try{if(typeof orders!=='undefined'&&Array.isArray(orders))return orders}catch(_){}
return null;
}
function readOrders(){
const live=runtimeOrders();if(live)return live;
try{const value=JSON.parse(localStorage.getItem('sunOrders')||'[]');return Array.isArray(value)?value:[]}catch(_){return[]}
}
function persistOrders(list){
try{
const live=runtimeOrders();
if(live&&live!==list){live.length=0;live.push(...list)}
if(typeof persist==='function')persist();
else localStorage.setItem('sunOrders',JSON.stringify(list));
return true;
}catch(error){
try{localStorage.setItem('sunOrders',JSON.stringify(list));return true}catch(_){console.error('[Caterium v17.6.4] order persist failed',error);return false}
}
}
function orderById(id){return readOrders().find(o=>String(o?.id)===String(id))||null}
function currentDraftId(){try{return draft?.id??null}catch(_){return null}}
function isSupportReadOnly(){
try{if(window.SunCloudV2?.getSupportMode?.()||window.SunCloudV2?.isSupportMode?.())return true}catch(_){}
return false;
}
function canAutoWrite(){
if(isSupportReadOnly())return false;
const cloud=window.SunCloudV2;
try{
const signed=Boolean(cloud?.getSession?.()?.user);
if(!signed)return true;
if(window.CateriumServerAutomationV1770?.enabled)return false;
if(typeof cloud?.hasPermission==='function')return cloud.hasPermission('orders.edit')||cloud.hasPermission('app.write');
}catch(_){}
return true;
}
function orderTotal(order){
try{const value=Number(window.orderTotalValue?.(order));if(Number.isFinite(value))return Math.max(0,value)}catch(_){}
const direct=Number(order?.total);return Number.isFinite(direct)?Math.max(0,direct):0;
}
function dueAt(order){
const date=String(order?.date||''),time=String(order?.time||'');
if(!/^\d{4}-\d{2}-\d{2}$/.test(date)||!/^\d{2}:\d{2}$/.test(time)||date<RELEASE_DAY)return NaN;
const stamp=new Date(`${date}T${time}:00`).getTime();return Number.isFinite(stamp)?stamp+AUTO_DELAY_MS:NaN;
}
function isAutoCompleted(order){return Boolean(order?.sunAutoCompletedAt||order?.sunAutoCompletedV1764)}
function markOrderCompleted(order,nowMs){
const total=orderTotal(order),iso=new Date(nowMs).toISOString();
order.prepayment=total;
order.balance=0;
order.status='Отдан заказчику';
order.paymentStatus='paid';
order.completedAt=order.completedAt||iso;
order.paymentCompletedAt=order.paymentCompletedAt||iso;
order.sunAutoCompletedAt=iso;
order.sunAutoCompletedV1764=true;
order.sunAutoCompletedVersion=VERSION;
return total;
}
function autoCompleteOrders(nowMs=Date.now(),{silent=false}={}){
if(!canAutoWrite())return {changed:0,ids:[]};
const list=readOrders(),ids=[];
for(const order of list){
if(!order||String(order.status||'').trim()==='Отменён'||isAutoCompleted(order))continue;
const due=dueAt(order);if(!Number.isFinite(due)||nowMs<due)continue;
markOrderCompleted(order,nowMs);ids.push(String(order.id));
}
if(!ids.length)return {changed:0,ids};
if(!persistOrders(list))return {changed:0,ids:[]};
try{window.render?.();window.renderOrders?.();window.renderStats?.();window.renderClients?.()}catch(_){}
try{window.dispatchEvent(new CustomEvent('sun:orders-auto-completed',{detail:{ids,version:VERSION}}))}catch(_){}
if(!silent){const label=ids.length===1?`Заказ №${ids[0]} автоматически завершён и полностью оплачен.`:`${ids.length} заказа автоматически завершены и полностью оплачены.`;toast(label,'success',5000)}
return {changed:ids.length,ids};
}
function localToday(){const d=new Date();return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`}
function classificationDate(order,today=localToday()){
const date=String(order?.date||'');
if(isAutoCompleted(order)&&/^\d{4}-\d{2}-\d{2}$/.test(date)&&date>=today)return '0001-01-01';
return date;
}
function cardDateParts(raw){
const d=raw?new Date(`${raw}T12:00:00`):null;
if(!d||Number.isNaN(d.getTime()))return {day:'—',month:'',weekday:'Дата'};
return {day:String(d.getDate()).padStart(2,'0'),month:d.toLocaleDateString('ru-RU',{month:'short'}).replace('.',''),weekday:d.toLocaleDateString('ru-RU',{weekday:'short'}).replace('.','')};
}
function restoreRenderedOrderDates(){
const map=new Map(readOrders().map(o=>[String(o?.id),o]));
qa('#sunOrderCards .sun-order-card').forEach(card=>{
const order=map.get(String(card.dataset.orderId||''));if(!order||!isAutoCompleted(order))return;
const tile=card.querySelector('.sun-order-card-date'),parts=cardDateParts(order.date);if(!tile)return;
const day=tile.querySelector('strong'),month=tile.querySelector('span'),small=tile.querySelector('small');
if(day)day.textContent=parts.day;if(month)month.textContent=parts.month;if(small)small.textContent=order.time||parts.weekday;
card.dataset.sunAutoCompleted='1';
});
qa('#ordersList .row').forEach(row=>{
const id=String(row.querySelector('span')?.textContent||'').replace(/\D/g,'');const order=map.get(id);if(!order||!isAutoCompleted(order))return;
if(row.children[2])row.children[2].textContent=[order.date,order.time].filter(Boolean).join(' · ');
row.dataset.sunAutoCompleted='1';
});
}
function patchOrdersRender(){
const current=window.renderOrders;if(typeof current!=='function'||current.__sunV1764)return false;
const wrapped=function(...args){
const live=runtimeOrders(),changed=[];const today=localToday();
if(live){for(const order of live){const fake=classificationDate(order,today);if(fake&&fake!==order.date){changed.push([order,order.date]);order.date=fake}}}
let result;
try{result=current.apply(this,args)}finally{for(const [order,date] of changed)order.date=date}
restoreRenderedOrderDates();setTimeout(restoreRenderedOrderDates,0);return result;
};
wrapped.__sunV1764=true;wrapped.__sunV1764Base=current;window.renderOrders=wrapped;return true;
}
let activeOfferOrderId=null,offerApiPatched=false,originalOfferGet=null;
function templateList(){try{return window.SunOfferTemplate?.list?.()||[]}catch(_){return[]}}
function validTemplate(id){return TEMPLATE_IDS.has(String(id||''))}
function storedTemplateId(order){
const id=order?.clientOfferSnapshot?.offerTemplateId||order?.clientOfferTemplateId||'';return validTemplate(id)?String(id):'';
}
function globalTemplateId(){
try{const state=originalOfferGet?originalOfferGet():window.SunOfferTemplate?.get?.();return validTemplate(state?.id)?state.id:'light'}catch(_){return'light'}
}
function activeTemplateId(){return storedTemplateId(orderById(activeOfferOrderId))||globalTemplateId()}
function patchOfferTemplateApi(){
const api=window.SunOfferTemplate;if(!api||offerApiPatched||typeof api.get!=='function')return false;
originalOfferGet=api.get.bind(api);
api.get=()=>{
const base=originalOfferGet();
const modal=$('sunClientOfferModal');if(!activeOfferOrderId||!modal?.classList.contains('on'))return base;
const id=activeTemplateId(),template=templateList().find(x=>x.id===id)||base?.template;
return {...base,id,template:template?{...template}:template};
};
offerApiPatched=true;return true;
}
function persistOfferTemplate(orderId,id){
if(!orderId||!validTemplate(id))return false;
const list=readOrders(),order=list.find(o=>String(o?.id)===String(orderId));if(!order)return false;
order.clientOfferTemplateId=id;
if(order.clientOfferSnapshot&&typeof order.clientOfferSnapshot==='object')order.clientOfferSnapshot.offerTemplateId=id;
try{if(typeof draft!=='undefined'&&draft&&String(draft.id)===String(orderId)){draft.clientOfferTemplateId=id;if(draft.clientOfferSnapshot)draft.clientOfferSnapshot.offerTemplateId=id}}catch(_){}
const ok=persistOrders(list);if(ok)try{window.dispatchEvent(new CustomEvent('sunoffertemplatechange',{detail:{id,orderId:String(orderId),perOffer:true}}))}catch(_){}
return ok;
}
function ensureOfferTemplateStored(){
const order=orderById(activeOfferOrderId);if(!order||storedTemplateId(order))return storedTemplateId(order);
const id=globalTemplateId();persistOfferTemplate(activeOfferOrderId,id);return id;
}
function offerTemplateMini(id){
if(id==='editorial-grid')return '<i class="mini-head"></i><i class="mini-editorial-hero"></i><i class="mini-editorial-stack a"></i><i class="mini-editorial-stack b"></i><i class="mini-editorial-grid"></i>';
if(id==='midnight-glass')return '<i class="mini-head"></i><i class="mini-midnight-hero"></i><i class="mini-midnight-total"></i><i class="mini-midnight-left"></i><i class="mini-midnight-right"></i>';
if(id==='emerald-gold')return '<i class="mini-head"></i><i class="mini-emerald-hero"></i><i class="mini-emerald-gold"></i><i class="mini-emerald-info"></i><i class="mini-emerald-menu"></i>';
return '<i class="mini-head"></i><i class="mini-light-hero"></i><i class="mini-light-row a"></i><i class="mini-light-row b"></i><i class="mini-light-row c"></i><i class="mini-light-total"></i>';
}
function pickerHtml(){
const current=activeTemplateId();return `<div class="sun-v1764-offer-template-head"><div><b>Оформление PDF</b><small>Классические и архивные шаблоны. Предварительный просмотр совпадает со скачиваемым PDF.</small></div></div><div class="sun-v1764-offer-template-grid">${templateList().map(t=>`<button type="button" data-v1764-offer-template="${esc(t.id)}" class="${t.id===current?'on':''}"><div class="sun-offer-template-mini sun-offer-template-mini-${esc(t.id)}" data-template-mini="${esc(t.id)}" aria-hidden="true">${offerTemplateMini(t.id)}</div><span>${esc(t.name||t.id)}</span></button>`).join('')}</div>`;
}
function ensureOfferPicker(){
patchOfferTemplateApi();const modal=$('sunClientOfferModal'),dialog=modal?.querySelector('.dialog');if(!dialog||!activeOfferOrderId)return false;
ensureOfferTemplateStored();let box=$('sunOfferTemplateV1764');
if(!box){box=document.createElement('section');box.id='sunOfferTemplateV1764';box.className='sun-v1764-offer-template';const head=dialog.querySelector('.sun-offer-modal-head');if(head)head.insertAdjacentElement('afterend',box);else dialog.prepend(box);box.addEventListener('click',e=>{const b=e.target.closest('[data-v1764-offer-template]');if(!b)return;const id=b.dataset.v1764OfferTemplate;if(!persistOfferTemplate(activeOfferOrderId,id))return;renderOfferPicker();toast(`Оформление «${templateList().find(x=>x.id===id)?.name||id}» сохранено для этого предложения.`,'success',3200)});}
renderOfferPicker();return true;
}
function renderOfferPicker(){const box=$('sunOfferTemplateV1764');if(box)box.innerHTML=pickerHtml()}
function setActiveOffer(id){if(id==null||id==='')return;activeOfferOrderId=String(id);setTimeout(ensureOfferPicker,0)}
function clearActiveOffer(){activeOfferOrderId=null}
function patchOfferOpenExports(){
const one=window.sunOpenClientOffer;if(typeof one==='function'&&!one.__sunV1764){const w=function(id,...rest){setActiveOffer(id);return one.call(this,id,...rest)};w.__sunV1764=true;window.sunOpenClientOffer=w}
const cur=window.sunOpenCurrentClientOffer;if(typeof cur==='function'&&!cur.__sunV1764){const w=function(...args){setActiveOffer(currentDraftId());return cur.apply(this,args)};w.__sunV1764=true;window.sunOpenCurrentClientOffer=w}
}
const menuSvg='<svg class="sun-v1764-menu-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 17.5h16"/><path d="M6 16.5a6 6 0 0 1 12 0"/><path d="M12 10V7.5"/><path d="M10.5 7.5h3"/><path d="M3.5 19.5h17"/></svg>';
function installStyles(){
if($('sunV1764Style'))return;const style=document.createElement('style');style.id='sunV1764Style';style.textContent=`
header nav .nav-menu{display:inline-flex!important;align-items:center;gap:7px}.sun-v1764-menu-icon{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}
#new .catalog-head .sun-v1764-menu-action{display:inline-flex;align-items:center;gap:7px}.sun-v1764-menu-action .sun-v1764-menu-icon{width:17px;height:17px}
.sun-v1764-offer-template{padding:12px 16px;border-bottom:1px solid #e7e9ec;background:#fbfbfc}.sun-v1764-offer-template-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.sun-v1764-offer-template-head b{color:#273746}.sun-v1764-offer-template-head small{display:block;color:#7a838c;margin-top:3px}.sun-v1764-offer-template-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}.sun-v1764-offer-template-grid button{border:1px solid #dfe3e7;background:#fff;border-radius:11px;padding:6px;text-align:left;color:#3f4d59}.sun-v1764-offer-template-grid button.on{border-color:var(--g);box-shadow:0 0 0 2px color-mix(in srgb,var(--g) 20%,transparent)}.sun-v1764-offer-template-grid img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:7px;background:#eef1f3}.sun-v1764-offer-template-grid span{display:block;padding:6px 3px 2px;font-size:11px;font-weight:800;line-height:1.2}
header nav .nav-menu{--sun-icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.9%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M4%2018h16M6%2017a6%206%200%200%201%2012%200M12%2011V8M10.5%208h3M3%2020h18%22%2F%3E%3C%2Fsvg%3E")!important;display:flex!important;align-items:center!important;gap:0!important}
.sun-enterprise-sidebar header nav .nav-menu>.sun-v1764-menu-icon{display:none!important}
.sun-offer-template-mini{position:relative;display:block;width:100%;aspect-ratio:16/10;overflow:hidden;border-radius:8px;background:#fff;border:1px solid #e3e5e7;box-sizing:border-box}.sun-offer-template-mini i{position:absolute;display:block;border-radius:2px}.sun-offer-template-mini .mini-head{left:7%;right:7%;top:7%;height:6%;background:#c8b17a}.sun-offer-template-mini-light{background:#fbfaf6}.mini-light-hero{left:7%;right:7%;top:18%;height:24%;background:linear-gradient(90deg,#e9eee7 0 45%,#bdc8bb 45%)}.mini-light-row{left:7%;right:7%;height:10%;background:#eff1ed;border:1px solid #d8dcd5}.mini-light-row.a{top:47%}.mini-light-row.b{top:59%}.mini-light-row.c{top:71%}.mini-light-total{left:55%;right:7%;bottom:7%;height:12%;background:#d7e4d5}
.sun-offer-template-mini-editorial-grid{background:#fbfaf6}.mini-editorial-hero{left:7%;top:18%;width:45%;height:35%;background:#dfe8df}.mini-editorial-stack{left:55%;right:7%;height:16%;background:#b8c9b7}.mini-editorial-stack.a{top:18%}.mini-editorial-stack.b{top:37%}.mini-editorial-grid{left:7%;right:7%;bottom:8%;height:32%;background:repeating-linear-gradient(90deg,#ece7dc 0 22%,transparent 22% 26%)}
.sun-offer-template-mini-midnight-glass{background:#071621;border-color:#294a5b}.sun-offer-template-mini-midnight-glass .mini-head{background:#d5aa56}.mini-midnight-hero{left:7%;right:7%;top:18%;height:30%;background:linear-gradient(90deg,#12313b 0 57%,#31515b 57%)}.mini-midnight-total{left:7%;right:7%;top:53%;height:12%;background:#d5aa56}.mini-midnight-left{left:7%;width:54%;bottom:8%;height:22%;background:#0b2633;border:1px solid #315263}.mini-midnight-right{right:7%;width:27%;bottom:8%;height:22%;background:repeating-linear-gradient(0deg,#284856 0 30%,#102735 30% 34%)}
.sun-offer-template-mini-emerald-gold{background:#061318;border-color:#314a4c}.sun-offer-template-mini-emerald-gold .mini-head{background:#d7ac55}.mini-emerald-hero{left:7%;right:7%;top:18%;height:38%;background:linear-gradient(90deg,#0c2d29 0 60%,#31534b 60%)}.mini-emerald-gold{left:10%;width:42%;top:43%;height:10%;background:#d7ac55}.mini-emerald-info{left:7%;right:57%;bottom:8%;height:27%;background:#0b2526;border:1px solid #67572f}.mini-emerald-menu{left:46%;right:7%;bottom:8%;height:27%;background:repeating-linear-gradient(0deg,#102c2d 0 17%,#67572f 17% 19%)}
.sun-offer-template-option .sun-offer-template-mini{width:100%;aspect-ratio:16/9;border-radius:10px}.sun-offer-template-thumb.sun-offer-template-mini{height:auto;object-fit:initial}
#sunOrderCards .sun-order-card[data-sun-auto-completed="1"]{opacity:.88}#sunOrderCards .sun-order-card[data-sun-auto-completed="1"] .sun-order-card-date{filter:saturate(.8)}
@media(max-width:760px){.sun-v1764-offer-template-grid{grid-template-columns:1fr 1fr}}
`;document.head.appendChild(style);
}
function decorateMenuIcon(){
let changed=false;
qa('header nav button.nav-menu,header nav button[data-nav-label="Меню"]').forEach(button=>{
button.dataset.sunMenuIconV1766='1';button.querySelectorAll('.sun-v1764-menu-icon').forEach(x=>x.remove());
let label=button.querySelector('span');if(!label){label=document.createElement('span');label.textContent='Меню';button.textContent='';button.appendChild(label)}else label.textContent='Меню';changed=true;
});
qa('#new .catalog-head button').filter(b=>String(b.textContent||'').trim()==='Меню').forEach(button=>{if(button.dataset.sunMenuIconV1764==='1')return;button.dataset.sunMenuIconV1764='1';button.classList.add('sun-v1764-menu-action');button.innerHTML=`${menuSvg}<span>Меню</span>`;changed=true});
return changed;
}
let autoTimer=0,maintainTimer=0;
function maintain(){patchOrdersRender();patchOfferTemplateApi();patchOfferOpenExports();decorateMenuIcon();if(activeOfferOrderId)$('sunClientOfferModal')?.classList.contains('on')?ensureOfferPicker():clearActiveOffer()}
function startTimers(){
if(!autoTimer)autoTimer=setInterval(()=>{if(!document.hidden)autoCompleteOrders(Date.now(),{silent:false})},AUTO_POLL_MS);
if(!maintainTimer)maintainTimer=setInterval(()=>{if(!document.hidden)maintain()},5000);
}
function onClickCapture(e){
const direct=e.target?.closest?.('[data-sun-offer-id]');if(direct?.dataset?.sunOfferId)setActiveOffer(direct.dataset.sunOfferId);
else if(e.target?.closest?.('#sunClientOfferButton'))setActiveOffer(currentDraftId());
if(e.target?.closest?.('#sunOfferClose'))setTimeout(clearActiveOffer,0);
}
function boot(){
installStyles();patchOrdersRender();patchOfferTemplateApi();patchOfferOpenExports();decorateMenuIcon();autoCompleteOrders(Date.now(),{silent:true});startTimers();
document.addEventListener('click',onClickCapture,true);
document.addEventListener('visibilitychange',()=>{if(!document.hidden){maintain();autoCompleteOrders(Date.now(),{silent:false})}});
window.addEventListener('focus',()=>autoCompleteOrders(Date.now(),{silent:false}));
window.addEventListener('sun:cloud-state-applied',()=>setTimeout(()=>{maintain();autoCompleteOrders(Date.now(),{silent:true})},80));
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
window.SunUXFixV1764={
VERSION,AUTO_DELAY_MS,RELEASE_DAY,
autoCompleteOrders,dueAt,isAutoCompleted,classificationDate,
patchOrdersRender,restoreRenderedOrderDates,
activeTemplateId,persistOfferTemplate,ensureOfferPicker,
decorateMenuIcon,
checks:()=>({version:VERSION,autoDelay:AUTO_DELAY_MS,menuIcon:Boolean(document.querySelector('.sun-v1764-menu-icon')),offerPicker:Boolean($('sunOfferTemplateV1764')),renderPatched:Boolean(window.renderOrders?.__sunV1764)})
};
})();