caterium-app/public/core/ux-fixes-v1764.js
pavlov346346-source f8bb0a78b4
Caterium v17.7.0 — architecture foundation
Introduce the first data layer, move order auto-completion to server cron with legacy/cloud compatibility, clean error telemetry, update PWA/versioning, and add regression coverage.
2026-09-08 13:24:34 +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(['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)})
};
})();