caterium-app/public/core/order-enhancements-v1775.js
pavlov346346-source 9bf75ccf11
Add order comments, reminders, search and delivery totals
Add per-line comments, day/hour order reminders, catalog search by number/name, and delivery-inclusive financial summary. Includes static regression checks.
2026-09-11 14:27:17 +03:00

232 lines
12 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.__cateriumOrderEnhancementsV1775)return;
window.__cateriumOrderEnhancementsV1775=true;
const $=(s,r=document)=>r.querySelector(s);
const $$=(s,r=document)=>[...r.querySelectorAll(s)];
const fmt=v=>typeof money==='function'?money(v):`${Math.round(Number(v||0)).toLocaleString('ru-RU')}`;
const safeText=v=>String(v??'').trim();
const activeLines=()=>Array.isArray(draft?.lines)?draft.lines.filter(line=>boxes.some(item=>String(item.id)===String(line.id))):[];
function ensureStyles(){
if($('#caterium-order-enhancements-v1775-style'))return;
const style=document.createElement('style');
style.id='caterium-order-enhancements-v1775-style';
style.textContent=`
#sunCatalogSearchV1775{width:100%;margin:10px 0 4px;padding:10px 12px;border:1px solid #d6dbe0;border-radius:9px;background:#fff}
.sun-line-comment-v1775{grid-column:1/-1;display:grid;grid-template-columns:118px minmax(0,1fr);gap:8px;align-items:center;margin-top:3px}
.sun-line-comment-v1775 span{font-size:11px;font-weight:700;color:#7d8790}
.sun-line-comment-v1775 input{width:100%;min-width:0;height:31px;padding:5px 8px;font-size:12px;border-radius:7px}
.sun-order-reminders-v1775{grid-column:1/-1;margin:2px 0 10px;padding:10px 12px;border:1px solid #e1e5e9;border-radius:10px;background:#fafbfc}
.sun-order-reminders-v1775>strong{display:block;margin-bottom:7px;color:#30394f;font-size:12px}
.sun-order-reminders-v1775 .opts{display:flex;gap:14px;flex-wrap:wrap}
.sun-order-reminders-v1775 label{display:flex!important;flex-direction:row!important;align-items:center!important;gap:7px!important;color:#4f5963!important;font-size:12px!important}
.sun-order-reminders-v1775 input{width:16px;height:16px;margin:0}
.sun-calendar-reminder-v1775{display:inline-flex;align-items:center;gap:4px;margin-top:5px;padding:3px 6px;border-radius:999px;background:#fff7d7;color:#795c00;font-size:10px;font-weight:800}
.sun-toast-v1775{position:fixed;right:18px;bottom:18px;z-index:99999;max-width:min(390px,calc(100vw - 36px));padding:13px 15px;border-radius:12px;background:#15364c;color:#fff;box-shadow:0 10px 30px #0003;font-size:13px;line-height:1.4}
.order-summary .sun-summary-grand-v1775{margin-top:7px;padding-top:9px;border-top:2px solid #c99a32;font-size:18px;font-weight:800;color:#15364c}
@media(max-width:760px){
#sunCatalogSearchV1775{display:block;font-size:16px!important}
.sun-line-comment-v1775{grid-template-columns:1fr;gap:4px;margin-top:5px}
.sun-line-comment-v1775 input{font-size:16px!important;height:38px}
}
`;
document.head.appendChild(style);
}
function ensureCatalogSearch(){
const catalog=$('#new .catalog');
if(!catalog||$('#sunCatalogSearchV1775'))return;
const input=document.createElement('input');
input.id='sunCatalogSearchV1775';
input.type='search';
input.placeholder='Поиск по № бокса или названию';
input.autocomplete='off';
const head=$('.catalog-head',catalog);
head?.insertAdjacentElement('afterend',input);
input.addEventListener('input',filterCatalog);
}
function norm(v){return safeText(v).toLowerCase().replace(/ё/g,'е').replace(/\s+/g,' ')}
function filterCatalog(){
const input=$('#sunCatalogSearchV1775');
const query=norm(input?.value);
$$('#tiles .tile').forEach(tile=>{
if(tile.classList.contains('add')){tile.hidden=Boolean(query);return;}
const text=norm(tile.textContent);
tile.hidden=Boolean(query&&!text.includes(query));
});
}
function decorateLineComments(){
const rows=$$('#orderForm .sun-order-line-row');
const lines=activeLines();
rows.forEach((row,index)=>{
const line=lines[index];
if(!line)return;
let wrap=$('.sun-line-comment-v1775',row);
if(!wrap){
wrap=document.createElement('label');
wrap.className='sun-line-comment-v1775';
wrap.innerHTML='<span>Комментарий</span><input type="text" maxlength="300" placeholder="Аллергия, убрать ингредиент, изменение состава…">';
row.appendChild(wrap);
}
const input=$('input',wrap);
const wanted=safeText(line.comment);
if(document.activeElement!==input&&input.value!==wanted)input.value=wanted;
if(!input.dataset.boundV1775){
input.dataset.boundV1775='1';
input.addEventListener('input',()=>{line.comment=input.value.slice(0,300);});
input.addEventListener('change',()=>{line.comment=input.value.trim().slice(0,300);});
}
});
}
function ensureReminderControls(){
const schedule=$('#details .sun-order-schedule');
if(!schedule||$('#sunOrderRemindersV1775'))return;
const box=document.createElement('div');
box.id='sunOrderRemindersV1775';box.className='sun-order-reminders-v1775';
box.innerHTML='<strong>Напоминание по заказу</strong><div class="opts"><label><input type="checkbox" data-reminder="day"> За 1 день</label><label><input type="checkbox" data-reminder="hour"> За 1 час</label></div>';
schedule.insertAdjacentElement('afterend',box);
$$('input[data-reminder]',box).forEach(input=>input.addEventListener('change',()=>{
draft.reminders={day:Boolean($('[data-reminder="day"]',box)?.checked),hour:Boolean($('[data-reminder="hour"]',box)?.checked)};
decorateCalendarReminderBadges();
requestNotificationPermission();
}));
syncReminderControls();
}
function syncReminderControls(){
const box=$('#sunOrderRemindersV1775');if(!box)return;
const r=draft?.reminders||{};
const day=$('[data-reminder="day"]',box),hour=$('[data-reminder="hour"]',box);
if(day)day.checked=Boolean(r.day);if(hour)hour.checked=Boolean(r.hour);
}
function deliveryValue(){
const input=$('#orderDeliveryCost');
if(input)return Math.max(0,Number(input.value||0));
return Math.max(0,Number(draft?.costs?.delivery||draft?.deliveryCost||0));
}
function discountValue(){
const candidates=['#orderDiscount','#discount','#orderDiscountAmount'];
for(const selector of candidates){const el=$(selector);if(el)return Math.max(0,Number(el.value||0));}
return Math.max(0,Number(draft?.discountAmount||draft?.discount||0));
}
function promoValue(){
const candidates=['#orderPromoDiscount','#promoDiscount','#orderPromoAmount'];
for(const selector of candidates){const el=$(selector);if(el)return Math.max(0,Number(el.value||0));}
return Math.max(0,Number(draft?.promoDiscount||draft?.promoAmount||0));
}
function financials(){
const positions=Math.max(0,Number(typeof calcOrderTotal==='function'?calcOrderTotal():0));
const delivery=deliveryValue(),discount=discountValue(),promo=promoValue();
const total=Math.max(0,positions-discount-promo+delivery);
const paid=Math.max(0,Number($('#prepayment')?.value||draft?.prepayment||0));
return {positions,delivery,discount,promo,total,paid,balance:Math.max(0,total-paid)};
}
function updateFinancialSummary(){
const host=$('.order-summary');if(!host)return;
const f=financials();
const position=$('#summaryTotal');if(position)position.textContent=fmt(f.positions);
let deliveryRow=$('#sunSummaryDeliveryV1775');
if(!deliveryRow){
deliveryRow=document.createElement('p');deliveryRow.id='sunSummaryDeliveryV1775';
const prepayment=$('#summaryPrepayment')?.closest('p');
(prepayment||host.lastElementChild)?.insertAdjacentElement('beforebegin',deliveryRow);
}
deliveryRow.innerHTML=`<span>Доставка</span><b>${fmt(f.delivery)}</b>`;
let totalRow=$('#sunSummaryGrandV1775');
if(!totalRow){totalRow=document.createElement('p');totalRow.id='sunSummaryGrandV1775';totalRow.className='sun-summary-grand-v1775';deliveryRow.insertAdjacentElement('afterend',totalRow);}
totalRow.innerHTML=`<span>Итого</span><b>${fmt(f.total)}</b>`;
const orderTotal=$('#orderTotal');if(orderTotal)orderTotal.value=Math.round(f.total);
const balance=$('#balance');if(balance)balance.value=Math.round(f.balance);
const summaryPrepayment=$('#summaryPrepayment');if(summaryPrepayment)summaryPrepayment.textContent=fmt(f.paid);
const summaryBalance=$('#summaryBalance');if(summaryBalance)summaryBalance.textContent=fmt(f.balance);
draft.total=f.total;draft.balance=f.balance;
}
function bindDeliveryTotal(){
const input=$('#orderDeliveryCost');if(!input||input.dataset.totalBoundV1775)return;
input.dataset.totalBoundV1775='1';
['input','change'].forEach(evt=>input.addEventListener(evt,()=>{if(draft.costs)draft.costs.delivery=Math.max(0,Number(input.value||0));updateFinancialSummary();}));
}
function orderDue(order){
if(!order?.date||!order?.time)return null;
const d=new Date(`${order.date}T${order.time}:00`);return Number.isNaN(d.getTime())?null:d;
}
function firedKey(orderId,kind,due){return `caterium.reminder.${orderId}.${kind}.${due.toISOString()}`}
function showToast(text){
$('.sun-toast-v1775')?.remove();
const el=document.createElement('div');el.className='sun-toast-v1775';el.textContent=text;document.body.appendChild(el);setTimeout(()=>el.remove(),10000);
}
async function requestNotificationPermission(){
try{if('Notification' in window&&Notification.permission==='default')await Notification.requestPermission();}catch(_){}
}
function fireReminder(order,kind,due){
const label=kind==='day'?'через 1 день':'через 1 час';
const title=`Заказ №${order.id||'—'}${label}`;
const body=[order.event||'Заказ',order.time||'',order.address||''].filter(Boolean).join(' · ');
showToast(`${title}: ${body}`);
try{if('Notification' in window&&Notification.permission==='granted')new Notification(title,{body,tag:`caterium-order-${order.id}-${kind}`});}catch(_){}
}
function scanReminders(){
if(!Array.isArray(orders))return;
const now=Date.now();
orders.forEach(order=>{
const due=orderDue(order);if(!due||due.getTime()<=now)return;
const reminders=order.reminders||{};
[['day',86400000],['hour',3600000]].forEach(([kind,lead])=>{
if(!reminders[kind])return;
const trigger=due.getTime()-lead;
if(now<trigger||now>trigger+120000)return;
const key=firedKey(order.id,kind,due);if(localStorage.getItem(key))return;
localStorage.setItem(key,String(now));fireReminder(order,kind,due);
});
});
}
function decorateCalendarReminderBadges(){
$$('.sun-calendar-reminder-v1775').forEach(x=>x.remove());
if(!Array.isArray(orders))return;
$$('#sunOrderCards [data-card-status]').forEach(status=>{
const order=orders.find(o=>String(o.id)===String(status.dataset.cardStatus));if(!order)return;
const r=order.reminders||{};const labels=[];if(r.day)labels.push('1 день');if(r.hour)labels.push('1 час');if(!labels.length)return;
const badge=document.createElement('span');badge.className='sun-calendar-reminder-v1775';badge.textContent='🔔 '+labels.join(' + ');
(status.closest('.sun-order-card')||status.parentElement)?.appendChild(badge);
});
}
function postRender(){
ensureCatalogSearch();filterCatalog();decorateLineComments();ensureReminderControls();syncReminderControls();bindDeliveryTotal();updateFinancialSummary();decorateCalendarReminderBadges();
}
const wrap=(name,after)=>{
const old=window[name];if(typeof old!=='function'||old.__v1775Wrapped)return;
const next=function(...args){const result=old.apply(this,args);after?.();setTimeout(postRender,0);return result;};
next.__v1775Wrapped=true;window[name]=next;
};
ensureStyles();ensureCatalogSearch();ensureReminderControls();
wrap('render');wrap('openOrder',syncReminderControls);wrap('resetDraft',()=>{draft.reminders={day:false,hour:false};syncReminderControls();});wrap('renderOrders',decorateCalendarReminderBadges);
const oldUpdate=window.updateOrderSummary;
if(typeof oldUpdate==='function'){
window.updateOrderSummary=function(...args){const result=oldUpdate.apply(this,args);updateFinancialSummary();return result;};
}
const oldSave=window.saveOrder;
if(typeof oldSave==='function'){
window.saveOrder=function(...args){
updateFinancialSummary();
const result=oldSave.apply(this,args);
return result;
};
}
document.addEventListener('input',e=>{if(e.target?.id==='prepayment')updateFinancialSummary();},true);
new MutationObserver(()=>{bindDeliveryTotal();decorateCalendarReminderBadges();}).observe(document.body,{childList:true,subtree:true});
postRender();scanReminders();setInterval(scanReminders,30000);
})();