feat: add JSON file backup export/import to Settings
Lets users download the local database as a JSON file and restore it from a file, independent of cloud sync — needed while Supabase is unreachable to move data between computers or recover after an outage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
98ba823f16
commit
345ee8e052
@ -461,6 +461,33 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
}
|
}
|
||||||
function renderDefaultQr(){const root=$('sunDefaultQrPreview');if(!root)return;const src=defaultQr();root.innerHTML=src?`<img src="${src}" alt="QR">`:'QR не загружен';}
|
function renderDefaultQr(){const root=$('sunDefaultQrPreview');if(!root)return;const src=defaultQr();root.innerHTML=src?`<img src="${src}" alt="QR">`:'QR не загружен';}
|
||||||
|
|
||||||
|
// ---------------- Backup to/from JSON file ----------------
|
||||||
|
function backupFileState(){const storage={};for(let i=0;i<localStorage.length;i++){const key=localStorage.key(i);if(!key||!key.startsWith('sun'))continue;storage[key]=localStorage.getItem(key);}return {format:'sun-catering-backup',version:1,createdAt:new Date().toISOString(),storage};}
|
||||||
|
function downloadBackupFile(){try{const state=backupFileState();const blob=new Blob([JSON.stringify(state)],{type:'application/json'});const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download=`caterium-backup-${new Date().toISOString().slice(0,19).replace(/[:T]/g,'-')}.json`;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),2000);toast('Файл резервной копии скачан.','success');}catch(_){toast('Не удалось создать файл резервной копии.','warn')}}
|
||||||
|
async function restoreBackupFile(file){
|
||||||
|
let state;
|
||||||
|
try{state=JSON.parse(await file.text());}catch(_){toast('Не удалось прочитать файл — это не JSON.','warn');return;}
|
||||||
|
if(!state||state.format!=='sun-catering-backup'||!state.storage||typeof state.storage!=='object'){toast('Неверный формат файла резервной копии.','warn');return;}
|
||||||
|
if(!confirm('Загрузить базу из файла? Текущие локальные данные будут заменены содержимым файла.'))return;
|
||||||
|
Object.entries(state.storage).forEach(([key,value])=>{if(key.startsWith('sun')){try{localStorage.setItem(key,String(value));}catch(_){}}});
|
||||||
|
toast('База восстановлена из файла. Перезагрузка…','success');
|
||||||
|
setTimeout(()=>location.reload(),600);
|
||||||
|
}
|
||||||
|
function ensureBackupFileCard(){
|
||||||
|
const grid=document.querySelector('#enterprise-settings .enterprise-grid');if(!grid)return;
|
||||||
|
let card=$('sunBackupFileCard');
|
||||||
|
if(!card){
|
||||||
|
card=document.createElement('section');card.id='sunBackupFileCard';card.className='enterprise-card wide';
|
||||||
|
card.innerHTML=`<h2>Резервная копия базы (файл)</h2><p class="hint">Скачайте базу в файл на компьютер или загрузите ранее сохранённый файл — например, для переноса на другой компьютер или работы без интернета.</p><input id="sunBackupFileInput" type="file" accept=".json,application/json" hidden><div class="actions"><button class="outline" id="sunBackupFileDownload" type="button">Скачать базу в файл</button><button class="outline" id="sunBackupFileUpload" type="button">Загрузить базу из файла</button></div>`;
|
||||||
|
const audit=qa(':scope > .enterprise-card',grid).find(x=>(x.querySelector('h2')?.textContent||'').trim()==='История изменений');
|
||||||
|
window.SunSafe.insertBefore(grid,card,audit||null);
|
||||||
|
$('sunBackupFileDownload').onclick=downloadBackupFile;
|
||||||
|
$('sunBackupFileUpload').onclick=()=>$('sunBackupFileInput').click();
|
||||||
|
$('sunBackupFileInput').onchange=async()=>{const f=$('sunBackupFileInput').files?.[0];$('sunBackupFileInput').value='';if(f)await restoreBackupFile(f);};
|
||||||
|
}
|
||||||
|
const audit=qa(':scope > .enterprise-card',grid).find(x=>(x.querySelector('h2')?.textContent||'').trim()==='История изменений');if(audit&&grid.lastElementChild!==audit)grid.appendChild(audit);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------- Discounts + promo tracking ----------------
|
// ---------------- Discounts + promo tracking ----------------
|
||||||
const PROMO_KEY='sunPromoCodesV1';
|
const PROMO_KEY='sunPromoCodesV1';
|
||||||
const localDateIso=()=>{const d=new Date(),p=n=>String(n).padStart(2,'0');return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}`};
|
const localDateIso=()=>{const d=new Date(),p=n=>String(n).padStart(2,'0');return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}`};
|
||||||
@ -566,7 +593,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
const previousStats=window.renderStats;
|
const previousStats=window.renderStats;
|
||||||
window.renderStats=function(...args){const r=typeof previousStats==='function'?previousStats.apply(this,args):undefined;setTimeout(renderPromoStats,0);return r};
|
window.renderStats=function(...args){const r=typeof previousStats==='function'?previousStats.apply(this,args):undefined;setTimeout(renderPromoStats,0);return r};
|
||||||
|
|
||||||
function onSettings(){setTimeout(()=>{simplifySellerSettings();ensureDefaultQrSetting();},80);setTimeout(()=>{simplifySellerSettings();ensureDefaultQrSetting();},250)}
|
function onSettings(){setTimeout(()=>{simplifySellerSettings();ensureDefaultQrSetting();ensureBackupFileCard();},80);setTimeout(()=>{simplifySellerSettings();ensureDefaultQrSetting();ensureBackupFileCard();},250)}
|
||||||
document.addEventListener('click',e=>{const nav=e.target.closest('header nav button');if(nav&&String(nav.dataset.navLabel||nav.textContent||'').trim()==='Настройки')onSettings();},true);
|
document.addEventListener('click',e=>{const nav=e.target.closest('header nav button');if(nav&&String(nav.dataset.navLabel||nav.textContent||'').trim()==='Настройки')onSettings();},true);
|
||||||
(()=>{let queued=false;const run=()=>{if(queued)return;queued=true;setTimeout(()=>{queued=false;if($('sunReceiptSettingsCard'))simplifySellerSettings();patchDocButtons();},50);};[$('new'),$('enterprise-settings')].filter(Boolean).forEach(root=>new MutationObserver(run).observe(root,{childList:true,subtree:true}));})();
|
(()=>{let queued=false;const run=()=>{if(queued)return;queued=true;setTimeout(()=>{queued=false;if($('sunReceiptSettingsCard'))simplifySellerSettings();patchDocButtons();},50);};[$('new'),$('enterprise-settings')].filter(Boolean).forEach(root=>new MutationObserver(run).observe(root,{childList:true,subtree:true}));})();
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user