work-license-server 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/ODANT-WORK-presentation.html +1683 -0
- package/assets/ODANT-WORK-presentation.pdf +0 -0
- package/assets/landing.css +83 -0
- package/assets/landing.js +528 -41
- package/package.json +1 -1
- package/src/catalog.js +1 -1
- package/src/landing.js +141 -5
- package/src/server.js +36 -1
- package/src/store.js +201 -32
package/assets/landing.js
CHANGED
|
@@ -397,12 +397,92 @@
|
|
|
397
397
|
|
|
398
398
|
let franchiseCountdownTimer = null;
|
|
399
399
|
let activeCabinetTab = 'servers';
|
|
400
|
+
const openServerAddonBuilders = new Map();
|
|
401
|
+
const openInvoiceOrderIds = new Set();
|
|
402
|
+
|
|
403
|
+
function cleanCompanyName(val = '') {
|
|
404
|
+
const s = String(val || '').trim();
|
|
405
|
+
if (!s || s.includes('@') || s === 'Организация WORK' || s === 'Клиент ODANT-WORK') return '';
|
|
406
|
+
return s;
|
|
407
|
+
}
|
|
400
408
|
|
|
401
409
|
function switchCabinetTab(tab = 'servers') {
|
|
402
|
-
activeCabinetTab = tab === 'franchises'
|
|
410
|
+
activeCabinetTab = (tab === 'franchises' || tab === 'requisites') ? tab : 'servers';
|
|
403
411
|
$$('[data-cab-tab]').forEach(b => b.classList.toggle('active', b.dataset.cabTab === activeCabinetTab));
|
|
404
412
|
if ($('#cabSecServers')) $('#cabSecServers').style.display = activeCabinetTab === 'servers' ? '' : 'none';
|
|
405
413
|
if ($('#cabSecFranchises')) $('#cabSecFranchises').style.display = activeCabinetTab === 'franchises' ? '' : 'none';
|
|
414
|
+
if ($('#cabSecRequisites')) $('#cabSecRequisites').style.display = activeCabinetTab === 'requisites' ? '' : 'none';
|
|
415
|
+
if (activeCabinetTab === 'requisites') renderCabinetRequisites();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function renderCabinetRequisites() {
|
|
419
|
+
const u = currentUser || {};
|
|
420
|
+
const comp = cleanCompanyName(u.companyName);
|
|
421
|
+
const hasReq = Boolean(comp && u.inn);
|
|
422
|
+
if ($('#navCabRequisites')) {
|
|
423
|
+
$('#navCabRequisites').textContent = hasReq ? `Реквизиты организации (✓)` : `Реквизиты организации`;
|
|
424
|
+
}
|
|
425
|
+
if ($('#reqCompanyName')) $('#reqCompanyName').value = comp;
|
|
426
|
+
if ($('#reqInn')) $('#reqInn').value = u.inn || '';
|
|
427
|
+
if ($('#reqKpp')) $('#reqKpp').value = u.kpp || '';
|
|
428
|
+
if ($('#reqLegalAddress')) $('#reqLegalAddress').value = u.legalAddress || '';
|
|
429
|
+
if ($('#reqSignerPosition')) $('#reqSignerPosition').value = u.signerPosition || u.position || '';
|
|
430
|
+
if ($('#reqSignerName')) $('#reqSignerName').value = u.signerName || '';
|
|
431
|
+
if ($('#reqBankName')) $('#reqBankName').value = u.bankName || '';
|
|
432
|
+
if ($('#reqBik')) $('#reqBik').value = u.bik || '';
|
|
433
|
+
if ($('#reqPhone')) $('#reqPhone').value = u.phone || '';
|
|
434
|
+
if ($('#reqBankAccount')) $('#reqBankAccount').value = u.bankAccount || '';
|
|
435
|
+
if ($('#reqCorrAccount')) $('#reqCorrAccount').value = u.corrAccount || '';
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function saveCabinetRequisites() {
|
|
439
|
+
if (!currentUser?.sessionKey) return;
|
|
440
|
+
const st = $('#reqSaveStatus');
|
|
441
|
+
const companyName = $('#reqCompanyName')?.value.trim() || '';
|
|
442
|
+
const inn = $('#reqInn')?.value.trim() || '';
|
|
443
|
+
if (!companyName || !inn) {
|
|
444
|
+
if (st) {
|
|
445
|
+
st.style.color = 'var(--danger)';
|
|
446
|
+
st.textContent = 'Укажите как минимум Наименование организации и ИНН.';
|
|
447
|
+
}
|
|
448
|
+
(!companyName ? $('#reqCompanyName') : $('#reqInn'))?.focus();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (st) {
|
|
452
|
+
st.style.color = 'var(--muted)';
|
|
453
|
+
st.textContent = 'Сохранение реквизитов…';
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
const res = await api('/api/cabinet/profile', {
|
|
457
|
+
sessionKey: currentUser.sessionKey,
|
|
458
|
+
companyName,
|
|
459
|
+
inn,
|
|
460
|
+
kpp: $('#reqKpp')?.value.trim() || '',
|
|
461
|
+
legalAddress: $('#reqLegalAddress')?.value.trim() || '',
|
|
462
|
+
signerPosition: $('#reqSignerPosition')?.value.trim() || '',
|
|
463
|
+
signerName: $('#reqSignerName')?.value.trim() || '',
|
|
464
|
+
bankName: $('#reqBankName')?.value.trim() || '',
|
|
465
|
+
bik: $('#reqBik')?.value.trim() || '',
|
|
466
|
+
phone: $('#reqPhone')?.value.trim() || '',
|
|
467
|
+
bankAccount: $('#reqBankAccount')?.value.trim() || '',
|
|
468
|
+
corrAccount: $('#reqCorrAccount')?.value.trim() || '',
|
|
469
|
+
});
|
|
470
|
+
if (res.user) {
|
|
471
|
+
currentUser = { ...res.user, sessionKey: res.user.sessionKey || currentUser.sessionKey };
|
|
472
|
+
persistSessionInBrowser(currentUser.sessionKey, currentUser.email, currentUser.isAdmin, currentUser);
|
|
473
|
+
onAuthenticated();
|
|
474
|
+
}
|
|
475
|
+
if (st) {
|
|
476
|
+
st.style.color = 'var(--ok)';
|
|
477
|
+
st.textContent = '✓ Реквизиты организации сохранены в Личном кабинете и применены к вашим счетам.';
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
catch (err) {
|
|
481
|
+
if (st) {
|
|
482
|
+
st.style.color = 'var(--danger)';
|
|
483
|
+
st.textContent = err.message;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
406
486
|
}
|
|
407
487
|
|
|
408
488
|
function onAuthenticated() {
|
|
@@ -415,17 +495,19 @@
|
|
|
415
495
|
sessionStorage.setItem('odant_work_admin_token', currentUser.sessionKey);
|
|
416
496
|
} catch { /* ignore */ }
|
|
417
497
|
}
|
|
418
|
-
|
|
498
|
+
const comp = cleanCompanyName(currentUser.companyName);
|
|
499
|
+
if ($('#btnAuthText')) $('#btnAuthText').textContent = comp || currentUser.email;
|
|
419
500
|
if ($('#authEmail')) $('#authEmail').value = currentUser.email || '';
|
|
420
501
|
if ($('#authOfertaCheck')) $('#authOfertaCheck').checked = Boolean(currentUser.ofertaAccepted);
|
|
421
502
|
if ($('#userBadgeBox')) {
|
|
422
|
-
const orgInfo =
|
|
503
|
+
const orgInfo = comp ? `${esc(comp)}${currentUser.inn ? ' (ИНН ' + esc(currentUser.inn) + ')' : ''} · ` : '';
|
|
423
504
|
const adminBadge = currentUser.isAdmin ? `<span class="tag ok" style="margin-right:6px">Администратор</span> <a href="/admin" style="font-weight:700;margin-right:8px">Открыть Админ-панель →</a> · ` : '';
|
|
424
505
|
$('#userBadgeBox').innerHTML = `${adminBadge}${orgInfo}Аккаунт: <b>${esc(currentUser.email)}</b> · <a href="#" data-action="logout" style="color:var(--muted)">Выйти</a>`;
|
|
425
506
|
}
|
|
426
507
|
syncClientServerPicker();
|
|
427
508
|
renderCabinetServers();
|
|
428
509
|
renderCabinetFranchises();
|
|
510
|
+
renderCabinetRequisites();
|
|
429
511
|
moveIndicator();
|
|
430
512
|
}
|
|
431
513
|
|
|
@@ -482,6 +564,116 @@
|
|
|
482
564
|
}
|
|
483
565
|
}
|
|
484
566
|
|
|
567
|
+
function formatOrderSummaryHtml(o = {}) {
|
|
568
|
+
const parts = [];
|
|
569
|
+
if (o.includeBasePak) {
|
|
570
|
+
parts.push(`<b>Комплекс ODANT-WORK с сервером</b> (10 вечных мест + ИИ)`);
|
|
571
|
+
}
|
|
572
|
+
else if (o.includeBaseLicenseOnly) {
|
|
573
|
+
parts.push(`<b>Базовая лицензия ODANT-WORK</b> (10 вечных мест + ИИ)`);
|
|
574
|
+
}
|
|
575
|
+
const seats = Number(o.addonSeats || 0);
|
|
576
|
+
if (seats > 0) {
|
|
577
|
+
const years = Number(o.years || 1);
|
|
578
|
+
const tierObj = tierById(o.tier || 'business');
|
|
579
|
+
const tierName = o.tierName || tierObj?.name || 'Business';
|
|
580
|
+
const disc = volumeDiscount(seats).discountPercent;
|
|
581
|
+
const gross = seats * (tierObj?.pricePerUserYearRub || 15_000) * years;
|
|
582
|
+
const net = (!o.includeBasePak && !o.includeBaseLicenseOnly && o.amountRub)
|
|
583
|
+
? Number(o.amountRub)
|
|
584
|
+
: Math.round(gross * (1 - disc / 100));
|
|
585
|
+
const perSeat = Math.round(net / seats);
|
|
586
|
+
const discStr = disc > 0 ? `, скидка −${disc}%` : '';
|
|
587
|
+
parts.push(`Дополнительные рабочие места: <b>${seats} мест</b> × <b>${years} г.</b> · тариф <b>${esc(tierName)}</b> (${rub(perSeat)}/место${discStr})`);
|
|
588
|
+
}
|
|
589
|
+
if (!parts.length && Array.isArray(o.items) && o.items.length) {
|
|
590
|
+
for (const it of o.items) {
|
|
591
|
+
const q = Number(it.quantity || 1);
|
|
592
|
+
const hasQtyInTitle = /\b\d+\s*(раб\.?\s*мест|мест|шт)/i.test(String(it.title || ''));
|
|
593
|
+
parts.push(`${(!hasQtyInTitle && q > 1) ? `<b>${q} мест</b> · ` : ''}${esc(it.title)}`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return parts.join(' + ') || esc(o.tierName || o.orderId);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function calcInlineAddonQuote(cfg = {}) {
|
|
600
|
+
const seats = Math.max(1, Math.min(2000, Math.floor(Number(cfg.seats) || 10)));
|
|
601
|
+
const years = Math.max(1, Math.min(5, Math.floor(Number(cfg.years) || 1)));
|
|
602
|
+
const tier = tierById(cfg.tier || 'business');
|
|
603
|
+
const disc = volumeDiscount(seats).discountPercent;
|
|
604
|
+
const gross = seats * tier.pricePerUserYearRub * years;
|
|
605
|
+
const saved = Math.round(gross * disc / 100);
|
|
606
|
+
const net = gross - saved;
|
|
607
|
+
const perSeat = Math.round(net / seats);
|
|
608
|
+
return { seats, years, tier, disc, gross, saved, net, perSeat };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function renderServerAddonBuilderHtml(lid, srvName) {
|
|
612
|
+
const cfg = openServerAddonBuilders.get(lid);
|
|
613
|
+
if (!cfg) return '';
|
|
614
|
+
const q = calcInlineAddonQuote(cfg);
|
|
615
|
+
const presets = [5, 10, 25, 50, 100];
|
|
616
|
+
const tiers = D.tiers || [
|
|
617
|
+
{ id: 'start', name: 'Start', pricePerUserYearRub: 10_000 },
|
|
618
|
+
{ id: 'business', name: 'Business', pricePerUserYearRub: 15_000 },
|
|
619
|
+
{ id: 'enterprise', name: 'Enterprise', pricePerUserYearRub: 20_000 },
|
|
620
|
+
];
|
|
621
|
+
const yearOpts = [1, 2, 3, 5];
|
|
622
|
+
|
|
623
|
+
return `<div style="margin-top:12px;padding:14px 16px;border-radius:10px;border:1px solid var(--brand);background:color-mix(in srgb, var(--brand) 6%, var(--panel-solid))">
|
|
624
|
+
<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:12px">
|
|
625
|
+
<div style="font-weight:700;font-size:14px">Докупка дополнительных рабочих мест для «${esc(srvName)}»</div>
|
|
626
|
+
<button type="button" class="btn btn-ghost btn-sm" style="padding:4px 10px;font-size:12px" data-close-addon-builder="${esc(lid)}">✕ Закрыть</button>
|
|
627
|
+
</div>
|
|
628
|
+
|
|
629
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:end">
|
|
630
|
+
<div>
|
|
631
|
+
<div class="small muted" style="margin-bottom:6px">1. Количество дополнительных мест:</div>
|
|
632
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
633
|
+
<input type="number" min="1" max="2000" value="${q.seats}" data-addon-seats-input="${esc(lid)}" style="width:90px;padding:6px 10px;font-weight:700;font-size:14px">
|
|
634
|
+
${presets.map(p => {
|
|
635
|
+
const d = volumeDiscount(p).discountPercent;
|
|
636
|
+
const active = q.seats === p;
|
|
637
|
+
return `<button type="button" class="btn ${active ? 'btn-primary' : 'btn-ghost'} btn-sm" style="padding:5px 9px;font-size:12px" data-addon-preset="${esc(lid)}" data-seats="${p}">${p}${d ? ` (−${d}%)` : ''}</button>`;
|
|
638
|
+
}).join('')}
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
|
|
642
|
+
<div>
|
|
643
|
+
<div class="small muted" style="margin-bottom:6px">2. Уровень поддержки (SLA):</div>
|
|
644
|
+
<div style="display:flex;gap:6px;flex-wrap:wrap">
|
|
645
|
+
${tiers.map(t => {
|
|
646
|
+
const active = q.tier.id === t.id;
|
|
647
|
+
return `<button type="button" class="btn ${active ? 'btn-primary' : 'btn-ghost'} btn-sm" style="padding:5px 10px;font-size:12px" data-addon-tier="${esc(lid)}" data-tier="${esc(t.id)}">${esc(t.name)} · ${rub(t.pricePerUserYearRub)}/г.</button>`;
|
|
648
|
+
}).join('')}
|
|
649
|
+
</div>
|
|
650
|
+
</div>
|
|
651
|
+
|
|
652
|
+
<div>
|
|
653
|
+
<div class="small muted" style="margin-bottom:6px">3. Срок действия лицензий:</div>
|
|
654
|
+
<div style="display:flex;gap:6px;flex-wrap:wrap">
|
|
655
|
+
${yearOpts.map(y => {
|
|
656
|
+
const active = q.years === y;
|
|
657
|
+
const lbl = y === 1 ? '1 год' : (y < 5 ? `${y} года` : '5 лет');
|
|
658
|
+
return `<button type="button" class="btn ${active ? 'btn-primary' : 'btn-ghost'} btn-sm" style="padding:5px 10px;font-size:12px" data-addon-years="${esc(lid)}" data-years="${y}">${lbl}</button>`;
|
|
659
|
+
}).join('')}
|
|
660
|
+
</div>
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
|
|
664
|
+
<div id="addonQuoteSummary-${esc(lid)}" style="margin-top:12px;padding:10px 12px;border-radius:8px;background:var(--field);border:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap">
|
|
665
|
+
<div class="small">
|
|
666
|
+
Выбрано: <b>${q.seats} раб. мест</b> × <b>${q.years} г.</b> · Тариф <b>${esc(q.tier.name)}</b> · Цена за место: <b>${rub(q.perSeat)}</b>${q.disc > 0 ? ` <span class="tag ok" style="margin-left:4px">скидка −${q.disc}% (−${rub(q.saved)})</span>` : ''}
|
|
667
|
+
</div>
|
|
668
|
+
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
|
669
|
+
<span style="font-size:15px;font-weight:800;margin-right:4px">Итого: ${rub(q.net)}</span>
|
|
670
|
+
<button type="button" class="btn btn-primary btn-sm" style="padding:6px 12px;font-size:12.5px" data-submit-addon="${esc(lid)}" data-pay-mode="yookassa">Оплатить ЮKassa</button>
|
|
671
|
+
<button type="button" class="btn btn-ghost btn-sm" style="padding:6px 12px;font-size:12.5px" data-submit-addon="${esc(lid)}" data-pay-mode="invoice">Счёт юрлицу</button>
|
|
672
|
+
</div>
|
|
673
|
+
</div>
|
|
674
|
+
</div>`;
|
|
675
|
+
}
|
|
676
|
+
|
|
485
677
|
function renderCabinetServers() {
|
|
486
678
|
const box = $('#cabinetServersRegistry');
|
|
487
679
|
if (!box) return;
|
|
@@ -495,6 +687,11 @@
|
|
|
495
687
|
return;
|
|
496
688
|
}
|
|
497
689
|
|
|
690
|
+
const savedCompany = cleanCompanyName(currentUser?.companyName);
|
|
691
|
+
const savedInn = currentUser?.inn || '';
|
|
692
|
+
const savedKpp = currentUser?.kpp || '';
|
|
693
|
+
const savedAddr = currentUser?.legalAddress || '';
|
|
694
|
+
|
|
498
695
|
box.innerHTML = servers.map((s, idx) => {
|
|
499
696
|
const lid = s.licenseId;
|
|
500
697
|
const name = s.serverName || `Сервер WORK #${idx + 1}`;
|
|
@@ -516,8 +713,8 @@
|
|
|
516
713
|
const canDelete = !s.baseActivated && !activeAddons.length && !paidOrders.length;
|
|
517
714
|
|
|
518
715
|
const baseActionsHtml = !s.baseActivated
|
|
519
|
-
? `<button class="btn btn-primary btn-sm" data-buy-for-server="${esc(lid)}" data-buy-variant="license_only">Купить базовую (${rub(D.product?.baseLicensePriceRub || 4_000_000)})</button>
|
|
520
|
-
<button class="btn btn-ghost btn-sm" data-buy-for-server="${esc(lid)}" data-buy-variant="pak">Купить ПАК с сервером (${rub(D.product?.basePriceRub || 5_000_000)})</button>`
|
|
716
|
+
? `<button type="button" class="btn btn-primary btn-sm" data-buy-for-server="${esc(lid)}" data-buy-variant="license_only">Купить базовую (${rub(D.product?.baseLicensePriceRub || 4_000_000)})</button>
|
|
717
|
+
<button type="button" class="btn btn-ghost btn-sm" data-buy-for-server="${esc(lid)}" data-buy-variant="pak">Купить ПАК с сервером (${rub(D.product?.basePriceRub || 5_000_000)})</button>`
|
|
521
718
|
: '';
|
|
522
719
|
|
|
523
720
|
const addonsSummaryHtml = activeAddons.length
|
|
@@ -530,27 +727,59 @@
|
|
|
530
727
|
const filesHtml = files.length
|
|
531
728
|
? `<div style="margin-top:8px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
|
532
729
|
<span class="small muted">Файлы лицензий (<code>#system</code>):</span>
|
|
533
|
-
${files.map((f, fIdx) => `<button class="btn btn-ghost btn-sm" style="padding:4px 10px;font-size:12px" data-download-lic="${esc(lid)}" data-file-idx="${fIdx}">↓ ${esc(f.fileName || 'license.lic.json')}</button>`).join('')}
|
|
730
|
+
${files.map((f, fIdx) => `<button type="button" class="btn btn-ghost btn-sm" style="padding:4px 10px;font-size:12px" data-download-lic="${esc(lid)}" data-file-idx="${fIdx}">↓ ${esc(f.fileName || 'license.lic.json')}</button>`).join('')}
|
|
534
731
|
</div>`
|
|
535
732
|
: '';
|
|
536
733
|
|
|
734
|
+
const addonBuilderHtml = renderServerAddonBuilderHtml(lid, name);
|
|
735
|
+
|
|
537
736
|
const pendingOrdersHtml = pendingOrders.length
|
|
538
|
-
? `<div style="margin-top:10px;
|
|
737
|
+
? `<div style="margin-top:10px;display:flex;flex-direction:column;gap:8px">
|
|
539
738
|
${pendingOrders.map(o => {
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
739
|
+
const summaryHtml = formatOrderSummaryHtml(o);
|
|
740
|
+
const ordComp = cleanCompanyName(o.customer?.name) || savedCompany;
|
|
741
|
+
const ordInn = o.customer?.inn || savedInn;
|
|
742
|
+
const ordKpp = o.customer?.kpp || savedKpp;
|
|
743
|
+
const ordAddr = o.customer?.legalAddress || savedAddr;
|
|
744
|
+
const isInvOpen = openInvoiceOrderIds.has(o.orderId) || (o.paymentMethod === 'invoice' && (!ordComp || !ordInn));
|
|
745
|
+
const hasReadyInvoice = Boolean(o.invoiceNumber && ordComp && ordInn);
|
|
746
|
+
|
|
747
|
+
return `<div style="padding:12px 14px;border-radius:10px;border:1px solid var(--line-strong);background:color-mix(in srgb, var(--warn) 6%, transparent)">
|
|
748
|
+
<div style="display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:center">
|
|
749
|
+
<div class="small"><b>Ожидает оплаты:</b> ${summaryHtml} · <b style="font-size:14px">${rub(o.amountRub)}</b></div>
|
|
750
|
+
<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center">
|
|
751
|
+
<button type="button" class="btn btn-primary btn-sm" style="padding:5px 10px;font-size:12px" data-pay-order="${esc(o.orderId)}" data-pay-method="yookassa">Оплатить ЮKassa</button>
|
|
752
|
+
<button type="button" class="btn ${isInvOpen ? 'btn-primary' : 'btn-ghost'} btn-sm" style="padding:5px 10px;font-size:12px" data-invoice-toggle="${esc(o.orderId)}">Счёт юрлицу</button>
|
|
753
|
+
${hasReadyInvoice ? `<a class="btn btn-ghost btn-sm" style="padding:5px 10px;font-size:12px;text-decoration:none" href="/api/orders/${encodeURIComponent(o.orderId)}/invoice" target="_blank" rel="noopener">📄 Счёт № ${esc(o.invoiceNumber)} ↗</a>` : ''}
|
|
754
|
+
<button type="button" class="btn btn-ghost btn-sm" style="padding:5px 10px;font-size:12px;color:var(--danger)" data-cancel-order="${esc(o.orderId)}">Отменить</button>
|
|
755
|
+
</div>
|
|
547
756
|
</div>
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
757
|
+
|
|
758
|
+
<div id="invBox-${esc(o.orderId)}" style="display:${isInvOpen ? '' : 'none'};margin-top:12px;padding-top:12px;border-top:1px solid var(--line)">
|
|
759
|
+
<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:8px">
|
|
760
|
+
<span class="small"><b>Реквизиты организации (Лицензиата) для выставления счёта-договора:</b> <span class="muted">(автоматически сохраняются в ЛК)</span></span>
|
|
761
|
+
<button type="button" class="btn btn-ghost btn-sm" style="padding:3px 8px;font-size:11.5px" data-cab-tab="requisites">Полные банковские реквизиты →</button>
|
|
762
|
+
</div>
|
|
763
|
+
<div style="display:grid;grid-template-columns:2fr 1fr 1fr 2fr auto;gap:8px;align-items:end">
|
|
764
|
+
<div class="field" style="margin:0">
|
|
765
|
+
<label style="font-size:11.5px">Организация / ИП *</label>
|
|
766
|
+
<input type="text" id="invCompany-${esc(o.orderId)}" value="${esc(ordComp)}" placeholder="ООО «Маслоторг»" style="padding:6px 10px;font-size:13px">
|
|
767
|
+
</div>
|
|
768
|
+
<div class="field" style="margin:0">
|
|
769
|
+
<label style="font-size:11.5px">ИНН *</label>
|
|
770
|
+
<input type="text" id="invInn-${esc(o.orderId)}" value="${esc(ordInn)}" placeholder="10 или 12 цифр" maxlength="12" style="padding:6px 10px;font-size:13px">
|
|
771
|
+
</div>
|
|
772
|
+
<div class="field" style="margin:0">
|
|
773
|
+
<label style="font-size:11.5px">КПП</label>
|
|
774
|
+
<input type="text" id="invKpp-${esc(o.orderId)}" value="${esc(ordKpp)}" placeholder="9 цифр" maxlength="9" style="padding:6px 10px;font-size:13px">
|
|
775
|
+
</div>
|
|
776
|
+
<div class="field" style="margin:0">
|
|
777
|
+
<label style="font-size:11.5px">Юридический адрес</label>
|
|
778
|
+
<input type="text" id="invAddr-${esc(o.orderId)}" value="${esc(ordAddr)}" placeholder="Город, улица, дом" style="padding:6px 10px;font-size:13px">
|
|
779
|
+
</div>
|
|
780
|
+
<button type="button" class="btn btn-primary btn-sm" style="padding:8px 14px;font-size:12.5px;height:36px;white-space:nowrap" data-pay-order="${esc(o.orderId)}" data-pay-method="invoice">Открыть счёт-договор ↗</button>
|
|
781
|
+
</div>
|
|
782
|
+
<div id="invErr-${esc(o.orderId)}" class="small" style="color:var(--danger);margin-top:6px;display:none"></div>
|
|
554
783
|
</div>
|
|
555
784
|
</div>`;
|
|
556
785
|
}).join('')}
|
|
@@ -566,8 +795,8 @@
|
|
|
566
795
|
</div>
|
|
567
796
|
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
568
797
|
<span class="small muted" style="margin-right:4px">Сессий: <b>${s.totalSeats}</b></span>
|
|
569
|
-
<button class="btn btn-ghost btn-sm" style="padding:5px 10px;font-size:12px" data-rename-server="${esc(lid)}" data-current-name="${esc(name)}">Переименовать</button>
|
|
570
|
-
${canDelete ? `<button class="btn btn-ghost btn-sm" style="padding:5px 10px;font-size:12px;color:var(--danger)" data-delete-server="${esc(lid)}">Удалить</button>` : ''}
|
|
798
|
+
<button type="button" class="btn btn-ghost btn-sm" style="padding:5px 10px;font-size:12px" data-rename-server="${esc(lid)}" data-current-name="${esc(name)}">Переименовать</button>
|
|
799
|
+
${canDelete ? `<button type="button" class="btn btn-ghost btn-sm" style="padding:5px 10px;font-size:12px;color:var(--danger)" data-delete-server="${esc(lid)}">Удалить</button>` : ''}
|
|
571
800
|
</div>
|
|
572
801
|
</div>
|
|
573
802
|
|
|
@@ -575,8 +804,8 @@
|
|
|
575
804
|
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px">
|
|
576
805
|
<span class="small muted" style="min-width:115px">Токен привязки:</span>
|
|
577
806
|
<input type="text" id="srvTok-${esc(lid)}" readonly value="${esc(tok)}" style="flex:1;min-width:200px;max-width:440px;padding:6px 10px;font-family:monospace;font-size:12px;border-radius:8px">
|
|
578
|
-
<button class="btn btn-primary btn-sm" style="padding:6px 12px;font-size:12px" data-copy-server-token="${esc(lid)}">Скопировать токен</button>
|
|
579
|
-
<button class="btn btn-ghost btn-sm" style="padding:6px 10px;font-size:12px" data-regen-server-token="${esc(lid)}">Перевыпустить</button>
|
|
807
|
+
<button type="button" class="btn btn-primary btn-sm" style="padding:6px 12px;font-size:12px" data-copy-server-token="${esc(lid)}">Скопировать токен</button>
|
|
808
|
+
<button type="button" class="btn btn-ghost btn-sm" style="padding:6px 10px;font-size:12px" data-regen-server-token="${esc(lid)}">Перевыпустить</button>
|
|
580
809
|
<span class="small" id="srvTokStatus-${esc(lid)}" style="color:var(--ok)"></span>
|
|
581
810
|
</div>
|
|
582
811
|
|
|
@@ -588,10 +817,11 @@
|
|
|
588
817
|
</div>
|
|
589
818
|
<div style="display:flex;gap:6px;flex-wrap:wrap">
|
|
590
819
|
${baseActionsHtml}
|
|
591
|
-
<button class="btn btn-ghost btn-sm" style="padding:5px 10px;font-size:12px" data-buy-for-server="${esc(lid)}" data-buy-variant="addons_only">+ Докупить рабочие места</button>
|
|
820
|
+
<button type="button" class="btn ${openServerAddonBuilders.has(lid) ? 'btn-primary' : 'btn-ghost'} btn-sm" style="padding:5px 10px;font-size:12px" data-buy-for-server="${esc(lid)}" data-buy-variant="addons_only">+ Докупить рабочие места</button>
|
|
592
821
|
</div>
|
|
593
822
|
</div>
|
|
594
823
|
|
|
824
|
+
${addonBuilderHtml}
|
|
595
825
|
${filesHtml}
|
|
596
826
|
${pendingOrdersHtml}
|
|
597
827
|
</div>`;
|
|
@@ -865,10 +1095,13 @@
|
|
|
865
1095
|
|
|
866
1096
|
async function buyForSpecificServer(licenseId, variant = 'license_only') {
|
|
867
1097
|
if (variant === 'addons_only') {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1098
|
+
if (openServerAddonBuilders.has(licenseId)) {
|
|
1099
|
+
openServerAddonBuilders.delete(licenseId);
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
openServerAddonBuilders.set(licenseId, { seats: 10, tier: 'business', years: 1 });
|
|
1103
|
+
}
|
|
1104
|
+
renderCabinetServers();
|
|
872
1105
|
return;
|
|
873
1106
|
}
|
|
874
1107
|
try {
|
|
@@ -883,10 +1116,76 @@
|
|
|
883
1116
|
});
|
|
884
1117
|
if (data.user) {
|
|
885
1118
|
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
1119
|
+
persistSessionInBrowser(currentUser.sessionKey, currentUser.email, currentUser.isAdmin, currentUser);
|
|
1120
|
+
onAuthenticated();
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
catch (err) {
|
|
1124
|
+
alert(err.message);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function updateServerAddonConfig(licenseId, patch = {}, { focusInput = false } = {}) {
|
|
1129
|
+
const prev = openServerAddonBuilders.get(licenseId) || { seats: 10, tier: 'business', years: 1 };
|
|
1130
|
+
const next = {
|
|
1131
|
+
seats: Math.max(1, Math.min(2000, Math.floor(Number(patch.seats ?? prev.seats) || 10))),
|
|
1132
|
+
tier: String(patch.tier ?? prev.tier ?? 'business'),
|
|
1133
|
+
years: Math.max(1, Math.min(5, Math.floor(Number(patch.years ?? prev.years) || 1))),
|
|
1134
|
+
};
|
|
1135
|
+
openServerAddonBuilders.set(licenseId, next);
|
|
1136
|
+
renderCabinetServers();
|
|
1137
|
+
if (focusInput) {
|
|
1138
|
+
const inp = $(`[data-addon-seats-input="${licenseId}"]`);
|
|
1139
|
+
if (inp) {
|
|
1140
|
+
inp.focus();
|
|
1141
|
+
try { inp.setSelectionRange(inp.value.length, inp.value.length); } catch { /* ignore */ }
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
async function submitServerAddonOrder(licenseId, payMode = 'yookassa') {
|
|
1147
|
+
const cfg = openServerAddonBuilders.get(licenseId) || { seats: 10, tier: 'business', years: 1 };
|
|
1148
|
+
const q = calcInlineAddonQuote(cfg);
|
|
1149
|
+
const isDirectInvoice = payMode === 'invoice' && Boolean(cleanCompanyName(currentUser?.companyName) && currentUser?.inn);
|
|
1150
|
+
const popupWin = isDirectInvoice ? window.open('about:blank', '_blank') : null;
|
|
1151
|
+
try {
|
|
1152
|
+
const data = await api('/api/cabinet/orders/draft', {
|
|
1153
|
+
sessionKey: currentUser?.sessionKey || '',
|
|
1154
|
+
licenseId,
|
|
1155
|
+
includeBasePak: false,
|
|
1156
|
+
includeBaseLicenseOnly: false,
|
|
1157
|
+
tier: q.tier.id,
|
|
1158
|
+
addonSeats: q.seats,
|
|
1159
|
+
years: q.years,
|
|
1160
|
+
paymentMethod: payMode === 'invoice' ? 'invoice' : 'yookassa',
|
|
1161
|
+
});
|
|
1162
|
+
openServerAddonBuilders.delete(licenseId);
|
|
1163
|
+
if (data.user) {
|
|
1164
|
+
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
1165
|
+
persistSessionInBrowser(currentUser.sessionKey, currentUser.email, currentUser.isAdmin, currentUser);
|
|
1166
|
+
}
|
|
1167
|
+
const createdOrderId = data.order?.orderId;
|
|
1168
|
+
if (payMode === 'invoice' && createdOrderId) {
|
|
1169
|
+
openInvoiceOrderIds.add(createdOrderId);
|
|
886
1170
|
onAuthenticated();
|
|
1171
|
+
if (isDirectInvoice && (data.invoiceUrl || data.order?.invoiceUrl)) {
|
|
1172
|
+
const invUrl = data.invoiceUrl || data.order.invoiceUrl;
|
|
1173
|
+
if (popupWin) popupWin.location.href = invUrl;
|
|
1174
|
+
else window.open(invUrl, '_blank');
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
if (popupWin) popupWin.close();
|
|
1178
|
+
setTimeout(() => $(`#invCompany-${createdOrderId}`)?.focus(), 40);
|
|
1179
|
+
}
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
onAuthenticated();
|
|
1183
|
+
if (payMode === 'yookassa' && createdOrderId) {
|
|
1184
|
+
await payCabinetOrder(createdOrderId, 'yookassa');
|
|
887
1185
|
}
|
|
888
1186
|
}
|
|
889
1187
|
catch (err) {
|
|
1188
|
+
if (popupWin) popupWin.close();
|
|
890
1189
|
alert(err.message);
|
|
891
1190
|
}
|
|
892
1191
|
}
|
|
@@ -1046,16 +1345,38 @@
|
|
|
1046
1345
|
if (!orderId) return;
|
|
1047
1346
|
let companyName = '';
|
|
1048
1347
|
let inn = '';
|
|
1348
|
+
let kpp = '';
|
|
1349
|
+
let legalAddress = '';
|
|
1350
|
+
const errBox = $(`#invErr-${orderId}`);
|
|
1351
|
+
if (errBox) {
|
|
1352
|
+
errBox.style.display = 'none';
|
|
1353
|
+
errBox.textContent = '';
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1049
1356
|
if (paymentMethod === 'invoice') {
|
|
1050
|
-
|
|
1357
|
+
openInvoiceOrderIds.add(orderId);
|
|
1358
|
+
const invBox = $(`#invBox-${orderId}`);
|
|
1359
|
+
if (invBox) invBox.style.display = '';
|
|
1360
|
+
companyName = cleanCompanyName($(`#invCompany-${orderId}`)?.value) || cleanCompanyName(currentUser?.companyName);
|
|
1051
1361
|
inn = $(`#invInn-${orderId}`)?.value.trim() || currentUser?.inn || '';
|
|
1362
|
+
kpp = $(`#invKpp-${orderId}`)?.value.trim() || currentUser?.kpp || '';
|
|
1363
|
+
legalAddress = $(`#invAddr-${orderId}`)?.value.trim() || currentUser?.legalAddress || '';
|
|
1364
|
+
|
|
1052
1365
|
if (!companyName || !inn) {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1366
|
+
if (errBox) {
|
|
1367
|
+
errBox.style.display = '';
|
|
1368
|
+
errBox.textContent = !companyName
|
|
1369
|
+
? 'Укажите наименование организации или ИП (например, ООО «Маслоторг») для выставления счёта-договора.'
|
|
1370
|
+
: 'Укажите ИНН организации (10 или 12 цифр) для выставления счёта-договора.';
|
|
1371
|
+
}
|
|
1372
|
+
(!companyName ? $(`#invCompany-${orderId}`) : $(`#invInn-${orderId}`))?.focus();
|
|
1056
1373
|
return;
|
|
1057
1374
|
}
|
|
1058
1375
|
}
|
|
1376
|
+
|
|
1377
|
+
// Открываем вкладку синхронно в момент клика пользователя (до await), чтобы браузер не блокировал окно
|
|
1378
|
+
const popupWin = window.open('about:blank', '_blank');
|
|
1379
|
+
|
|
1059
1380
|
try {
|
|
1060
1381
|
const data = await api('/api/cabinet/orders/pay', {
|
|
1061
1382
|
sessionKey: currentUser?.sessionKey || '',
|
|
@@ -1063,16 +1384,32 @@
|
|
|
1063
1384
|
paymentMethod,
|
|
1064
1385
|
companyName,
|
|
1065
1386
|
inn,
|
|
1387
|
+
kpp,
|
|
1388
|
+
legalAddress,
|
|
1066
1389
|
});
|
|
1067
1390
|
if (data.user) {
|
|
1068
|
-
currentUser = { ...data.user, sessionKey: currentUser.sessionKey };
|
|
1391
|
+
currentUser = { ...data.user, sessionKey: data.user.sessionKey || currentUser.sessionKey };
|
|
1392
|
+
persistSessionInBrowser(currentUser.sessionKey, currentUser.email, currentUser.isAdmin, currentUser);
|
|
1069
1393
|
onAuthenticated();
|
|
1070
1394
|
}
|
|
1071
|
-
const url = data.invoiceUrl || data.confirmationUrl;
|
|
1072
|
-
if (url)
|
|
1395
|
+
const url = data.invoiceUrl || data.confirmationUrl || (paymentMethod === 'invoice' ? `/api/orders/${encodeURIComponent(orderId)}/invoice` : '');
|
|
1396
|
+
if (url) {
|
|
1397
|
+
if (popupWin) popupWin.location.href = url;
|
|
1398
|
+
else location.href = url;
|
|
1399
|
+
}
|
|
1400
|
+
else if (popupWin) {
|
|
1401
|
+
popupWin.close();
|
|
1402
|
+
}
|
|
1073
1403
|
}
|
|
1074
1404
|
catch (err) {
|
|
1075
|
-
|
|
1405
|
+
if (popupWin) popupWin.close();
|
|
1406
|
+
if (errBox) {
|
|
1407
|
+
errBox.style.display = '';
|
|
1408
|
+
errBox.textContent = err.message;
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
alert(err.message);
|
|
1412
|
+
}
|
|
1076
1413
|
}
|
|
1077
1414
|
}
|
|
1078
1415
|
|
|
@@ -1763,18 +2100,91 @@
|
|
|
1763
2100
|
else requestAnimationFrame(frame);
|
|
1764
2101
|
}
|
|
1765
2102
|
|
|
2103
|
+
/* ================= Интерактивный плеер презентации ODANT-WORK ================= */
|
|
2104
|
+
let currentPresSlide = 1;
|
|
2105
|
+
const PRES_TOTAL_SLIDES = 12;
|
|
2106
|
+
|
|
2107
|
+
function syncPresIndicators(slideNum) {
|
|
2108
|
+
currentPresSlide = Math.max(1, Math.min(PRES_TOTAL_SLIDES, Number(slideNum) || 1));
|
|
2109
|
+
const cnt = $('#presCounter');
|
|
2110
|
+
if (cnt) cnt.textContent = `Слайд ${currentPresSlide} / ${PRES_TOTAL_SLIDES}`;
|
|
2111
|
+
$$('#presDots .pres-dot').forEach(btn => {
|
|
2112
|
+
btn.classList.toggle('on', Number(btn.dataset.slide) === currentPresSlide);
|
|
2113
|
+
});
|
|
2114
|
+
const sec = currentPresSlide >= 10 ? 'franchise' : (currentPresSlide >= 8 ? 'clients' : 'product');
|
|
2115
|
+
$$('#presSectionTabs .chip').forEach(btn => {
|
|
2116
|
+
btn.classList.toggle('on', btn.dataset.sec === sec);
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
function openPresentationModal(startSlide = 1) {
|
|
2121
|
+
const modal = $('#presentationModal');
|
|
2122
|
+
const frame = $('#presFrame');
|
|
2123
|
+
if (!modal || !frame) return;
|
|
2124
|
+
const n = Math.max(1, Math.min(PRES_TOTAL_SLIDES, Number(startSlide) || 1));
|
|
2125
|
+
syncPresIndicators(n);
|
|
2126
|
+
const targetSrc = `/presentation?embed=1&slide=${n}`;
|
|
2127
|
+
if (!frame.getAttribute('src')) {
|
|
2128
|
+
frame.setAttribute('src', targetSrc);
|
|
2129
|
+
} else {
|
|
2130
|
+
try {
|
|
2131
|
+
frame.contentWindow?.postMessage({ type: 'odant-presentation-goto', slide: n }, '*');
|
|
2132
|
+
} catch {
|
|
2133
|
+
frame.setAttribute('src', targetSrc);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
modal.classList.add('open');
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
function closePresentationModal() {
|
|
2140
|
+
$('#presentationModal')?.classList.remove('open');
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
function gotoPresentationSlide(slideNum) {
|
|
2144
|
+
const n = Math.max(1, Math.min(PRES_TOTAL_SLIDES, Number(slideNum) || 1));
|
|
2145
|
+
syncPresIndicators(n);
|
|
2146
|
+
const frame = $('#presFrame');
|
|
2147
|
+
try {
|
|
2148
|
+
frame?.contentWindow?.postMessage({ type: 'odant-presentation-goto', slide: n }, '*');
|
|
2149
|
+
} catch { /* ignore */ }
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
function stepPresentationSlide(delta) {
|
|
2153
|
+
gotoPresentationSlide(currentPresSlide + (Number(delta) || 0));
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
window.addEventListener('message', (e) => {
|
|
2157
|
+
const d = e.data || {};
|
|
2158
|
+
if (d.type === 'odant-presentation-slide' && d.slide) {
|
|
2159
|
+
syncPresIndicators(d.slide);
|
|
2160
|
+
} else if (d.type === 'odant-presentation-close') {
|
|
2161
|
+
closePresentationModal();
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
|
|
1766
2165
|
/* ================= Привязка событий ================= */
|
|
1767
2166
|
function bind() {
|
|
1768
2167
|
document.addEventListener('click', (e) => {
|
|
1769
|
-
const el = e.target.closest('[data-stage],[data-cab-tab],[data-action],[data-reserve],[data-pay-order],[data-invoice-toggle],[data-cancel-order],[data-copy-server-token],[data-regen-server-token],[data-rename-server],[data-delete-server],[data-download-lic],[data-buy-for-server],[data-pay-installment],[data-cancel-franchise],[data-ask],[data-district],th[data-sort],#regionsTableBody tr');
|
|
2168
|
+
const el = e.target.closest('[data-stage],[data-cab-tab],[data-action],[data-reserve],[data-pay-order],[data-invoice-toggle],[data-cancel-order],[data-copy-server-token],[data-regen-server-token],[data-rename-server],[data-delete-server],[data-download-lic],[data-buy-for-server],[data-close-addon-builder],[data-addon-preset],[data-addon-tier],[data-addon-years],[data-submit-addon],[data-pay-installment],[data-cancel-franchise],[data-ask],[data-district],th[data-sort],#regionsTableBody tr');
|
|
1770
2169
|
if (!el) return;
|
|
1771
2170
|
if (el.matches('[data-cab-tab]')) { e.preventDefault(); switchCabinetTab(el.dataset.cabTab); return; }
|
|
1772
2171
|
if (el.matches('[data-reserve]')) { e.stopPropagation(); reserveRegionFranchise(el.dataset.reserve); return; }
|
|
1773
2172
|
if (el.matches('[data-pay-order]')) { e.stopPropagation(); payCabinetOrder(el.dataset.payOrder, el.dataset.payMethod || 'yookassa'); return; }
|
|
1774
2173
|
if (el.matches('[data-invoice-toggle]')) {
|
|
1775
2174
|
e.stopPropagation();
|
|
1776
|
-
const
|
|
1777
|
-
|
|
2175
|
+
const oid = el.dataset.invoiceToggle;
|
|
2176
|
+
const box = $(`#invBox-${oid}`);
|
|
2177
|
+
if (box) {
|
|
2178
|
+
const nextVisible = box.style.display === 'none';
|
|
2179
|
+
box.style.display = nextVisible ? '' : 'none';
|
|
2180
|
+
if (nextVisible) {
|
|
2181
|
+
openInvoiceOrderIds.add(oid);
|
|
2182
|
+
setTimeout(() => $(`#invCompany-${oid}`)?.focus(), 30);
|
|
2183
|
+
}
|
|
2184
|
+
else {
|
|
2185
|
+
openInvoiceOrderIds.delete(oid);
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
1778
2188
|
return;
|
|
1779
2189
|
}
|
|
1780
2190
|
if (el.matches('[data-cancel-order]')) { e.stopPropagation(); cancelCabinetOrder(el.dataset.cancelOrder); return; }
|
|
@@ -1784,6 +2194,32 @@
|
|
|
1784
2194
|
if (el.matches('[data-delete-server]')) { e.stopPropagation(); deleteCabinetServer(el.dataset.deleteServer); return; }
|
|
1785
2195
|
if (el.matches('[data-download-lic]')) { e.stopPropagation(); downloadServerLicFile(el.dataset.downloadLic, el.dataset.fileIdx || 0); return; }
|
|
1786
2196
|
if (el.matches('[data-buy-for-server]')) { e.stopPropagation(); buyForSpecificServer(el.dataset.buyForServer, el.dataset.buyVariant || 'license_only'); return; }
|
|
2197
|
+
if (el.matches('[data-close-addon-builder]')) {
|
|
2198
|
+
e.stopPropagation();
|
|
2199
|
+
openServerAddonBuilders.delete(el.dataset.closeAddonBuilder);
|
|
2200
|
+
renderCabinetServers();
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
if (el.matches('[data-addon-preset]')) {
|
|
2204
|
+
e.stopPropagation();
|
|
2205
|
+
updateServerAddonConfig(el.dataset.addonPreset, { seats: Number(el.dataset.seats || 10) });
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
if (el.matches('[data-addon-tier]')) {
|
|
2209
|
+
e.stopPropagation();
|
|
2210
|
+
updateServerAddonConfig(el.dataset.addonTier, { tier: el.dataset.tier || 'business' });
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
if (el.matches('[data-addon-years]')) {
|
|
2214
|
+
e.stopPropagation();
|
|
2215
|
+
updateServerAddonConfig(el.dataset.addonYears, { years: Number(el.dataset.years || 1) });
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
if (el.matches('[data-submit-addon]')) {
|
|
2219
|
+
e.stopPropagation();
|
|
2220
|
+
submitServerAddonOrder(el.dataset.submitAddon, el.dataset.payMode || 'yookassa');
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
1787
2223
|
if (el.matches('[data-pay-installment]')) {
|
|
1788
2224
|
e.stopPropagation();
|
|
1789
2225
|
payFranchiseInstallment(el.dataset.payInstallment, {
|
|
@@ -1841,6 +2277,7 @@
|
|
|
1841
2277
|
break;
|
|
1842
2278
|
case 'buy-client-config': buyClientConfig(); break;
|
|
1843
2279
|
case 'create-server': createCabinetServer(); break;
|
|
2280
|
+
case 'save-requisites': saveCabinetRequisites(); break;
|
|
1844
2281
|
case 'gen-hub-token': generateCabinetToken(); break;
|
|
1845
2282
|
case 'refresh-cabinet': refreshCabinetUser(); break;
|
|
1846
2283
|
case 'reserve-selected': reserveRegionFranchise(selectedCode); break;
|
|
@@ -1848,6 +2285,26 @@
|
|
|
1848
2285
|
case 'cert-copy':
|
|
1849
2286
|
navigator.clipboard?.writeText($('#certJsonBox')?.textContent || '');
|
|
1850
2287
|
break;
|
|
2288
|
+
case 'open-presentation':
|
|
2289
|
+
e.preventDefault();
|
|
2290
|
+
openPresentationModal(Number(el.dataset.slide || 1));
|
|
2291
|
+
break;
|
|
2292
|
+
case 'pres-close':
|
|
2293
|
+
closePresentationModal();
|
|
2294
|
+
break;
|
|
2295
|
+
case 'pres-goto':
|
|
2296
|
+
gotoPresentationSlide(Number(el.dataset.slide || 1));
|
|
2297
|
+
break;
|
|
2298
|
+
case 'pres-prev':
|
|
2299
|
+
stepPresentationSlide(-1);
|
|
2300
|
+
break;
|
|
2301
|
+
case 'pres-next':
|
|
2302
|
+
stepPresentationSlide(1);
|
|
2303
|
+
break;
|
|
2304
|
+
case 'pres-jump-stage':
|
|
2305
|
+
closePresentationModal();
|
|
2306
|
+
if (el.dataset.targetStage) switchStage(el.dataset.targetStage);
|
|
2307
|
+
break;
|
|
1851
2308
|
case 'ai-toggle': toggleAi(); break;
|
|
1852
2309
|
case 'ai-send': sendAi(); break;
|
|
1853
2310
|
}
|
|
@@ -1855,8 +2312,13 @@
|
|
|
1855
2312
|
$('#authModal')?.addEventListener('click', (e) => { if (e.target.id === 'authModal') closeAuth(); });
|
|
1856
2313
|
$('#ofertaModal')?.addEventListener('click', (e) => { if (e.target.id === 'ofertaModal') closeOfertaModal(); });
|
|
1857
2314
|
$('#certModal')?.addEventListener('click', (e) => { if (e.target.id === 'certModal') $('#certModal').classList.remove('open'); });
|
|
2315
|
+
$('#presentationModal')?.addEventListener('click', (e) => { if (e.target.id === 'presentationModal') closePresentationModal(); });
|
|
1858
2316
|
document.addEventListener('keydown', (e) => {
|
|
1859
|
-
if (e.key === 'Escape') { closeAuth(); closeOfertaModal(); $('#certModal')?.classList.remove('open'); toggleAi(false); }
|
|
2317
|
+
if (e.key === 'Escape') { closeAuth(); closeOfertaModal(); closePresentationModal(); $('#certModal')?.classList.remove('open'); toggleAi(false); }
|
|
2318
|
+
else if ($('#presentationModal')?.classList.contains('open')) {
|
|
2319
|
+
if (e.key === 'ArrowRight' || e.key === 'PageDown') { e.preventDefault(); stepPresentationSlide(1); }
|
|
2320
|
+
else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { e.preventDefault(); stepPresentationSlide(-1); }
|
|
2321
|
+
}
|
|
1860
2322
|
});
|
|
1861
2323
|
$('#authModal')?.addEventListener('keydown', (e) => {
|
|
1862
2324
|
if (e.key === 'Enter' && e.target.tagName !== 'BUTTON') {
|
|
@@ -1864,6 +2326,31 @@
|
|
|
1864
2326
|
else sendAuthCode();
|
|
1865
2327
|
}
|
|
1866
2328
|
});
|
|
2329
|
+
document.addEventListener('input', (e) => {
|
|
2330
|
+
const inp = e.target.closest?.('[data-addon-seats-input]');
|
|
2331
|
+
if (!inp) return;
|
|
2332
|
+
const lid = inp.dataset.addonSeatsInput;
|
|
2333
|
+
const prev = openServerAddonBuilders.get(lid) || { seats: 10, tier: 'business', years: 1 };
|
|
2334
|
+
const seats = Math.max(1, Math.min(2000, Math.floor(Number(inp.value) || 1)));
|
|
2335
|
+
prev.seats = seats;
|
|
2336
|
+
openServerAddonBuilders.set(lid, prev);
|
|
2337
|
+
const q = calcInlineAddonQuote(prev);
|
|
2338
|
+
const sumBox = $(`#addonQuoteSummary-${lid}`);
|
|
2339
|
+
if (sumBox) {
|
|
2340
|
+
sumBox.innerHTML = `<div class="small">
|
|
2341
|
+
Выбрано: <b>${q.seats} раб. мест</b> × <b>${q.years} г.</b> · Тариф <b>${esc(q.tier.name)}</b> · Цена за место: <b>${rub(q.perSeat)}</b>${q.disc > 0 ? ` <span class="tag ok" style="margin-left:4px">скидка −${q.disc}% (−${rub(q.saved)})</span>` : ''}
|
|
2342
|
+
</div>
|
|
2343
|
+
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
|
2344
|
+
<span style="font-size:15px;font-weight:800;margin-right:4px">Итого: ${rub(q.net)}</span>
|
|
2345
|
+
<button type="button" class="btn btn-primary btn-sm" style="padding:6px 12px;font-size:12.5px" data-submit-addon="${esc(lid)}" data-pay-mode="yookassa">Оплатить ЮKassa</button>
|
|
2346
|
+
<button type="button" class="btn btn-ghost btn-sm" style="padding:6px 12px;font-size:12.5px" data-submit-addon="${esc(lid)}" data-pay-mode="invoice">Счёт юрлицу</button>
|
|
2347
|
+
</div>`;
|
|
2348
|
+
}
|
|
2349
|
+
$$(`[data-addon-preset="${lid}"]`).forEach(btn => {
|
|
2350
|
+
btn.classList.toggle('btn-primary', Number(btn.dataset.seats) === q.seats);
|
|
2351
|
+
btn.classList.toggle('btn-ghost', Number(btn.dataset.seats) !== q.seats);
|
|
2352
|
+
});
|
|
2353
|
+
});
|
|
1867
2354
|
$('#aiInput')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') sendAi(); });
|
|
1868
2355
|
$('#inpPakExtraSeats')?.addEventListener('input', (e) => syncClientSeats(e.target.value));
|
|
1869
2356
|
$('#inpPakExtraSeatsNum')?.addEventListener('input', (e) => syncClientSeats(e.target.value));
|