feat: unify tariff package price rows

This commit is contained in:
3252a8
2026-06-03 11:34:08 +03:00
parent 5d1fd3304b
commit e52c75538f
3 changed files with 269 additions and 386 deletions
@@ -32,16 +32,6 @@
})); }));
$: defaultCurrencyKey = normalizeCurrencyKey(tariffsCatalog?.default_currency || "rub"); $: defaultCurrencyKey = normalizeCurrencyKey(tariffsCatalog?.default_currency || "rub");
$: defaultCurrencyCode = defaultCurrencyKey.toUpperCase(); $: defaultCurrencyCode = defaultCurrencyKey.toUpperCase();
$: currencyPackageLabel = at(
"tariff_btn_package_currency",
{ currency: defaultCurrencyCode },
`Пакет ${defaultCurrencyCode}`
);
$: currencyPaymentLabel = at(
"payment_default_currency",
{ currency: defaultCurrencyCode },
`Оплата ${defaultCurrencyCode}`
);
$: currencyPriceColumnLabel = at( $: currencyPriceColumnLabel = at(
"tariff_col_price_currency", "tariff_col_price_currency",
{ currency: defaultCurrencyCode }, { currency: defaultCurrencyCode },
@@ -413,103 +403,71 @@
> >
</div> </div>
<div class="admin-editor-section-actions"> <div class="admin-editor-section-actions">
<AdminButton
size="sm"
onclick={() => tariffsStore.addDraftRow("premiumTopupRubRows", { gb: 10, price: "" })}
><Plus size={12} /> {currencyPackageLabel}</AdminButton
>
<AdminButton <AdminButton
size="sm" size="sm"
onclick={() => onclick={() =>
tariffsStore.addDraftRow("premiumTopupStarsRows", { gb: 10, price: "" })} tariffsStore.addDraftRow("premiumTopupRows", { gb: 10, price: "", stars: "" })}
><Plus size={12} /> {at("tariff_btn_package_stars", {}, "Пакет")}</AdminButton ><Plus size={12} /> {at("tariff_btn_package", {}, "Пакет")}</AdminButton
> >
</div> </div>
</header> </header>
<div class="admin-package-columns"> {#if tariffDraft.premiumTopupRows.length}
<div class="admin-row-editor"> <div class="admin-row-editor">
<span class="admin-row-editor-caption">{currencyPaymentLabel}</span> <div class="admin-row-editor-line admin-row-editor-drag admin-row-editor-header">
{#if tariffDraft.premiumTopupRubRows.length} <span></span>
<div class="admin-row-editor-line admin-row-editor-header"> <span>{at("tariff_col_volume_gb", {}, "Объём, GB")}</span>
<span>{at("tariff_col_volume_gb", {}, "Объём, GB")}</span> <span>{currencyPriceColumnLabel}</span>
<span>{currencyPriceColumnLabel}</span> <span>{at("tariff_col_price_stars_full", {}, "Цена, ⭐ Stars")}</span>
<span></span> <span></span>
</div> </div>
{/if} <Sortable
{#each tariffDraft.premiumTopupRubRows as row, index} items={tariffDraft.premiumTopupRows}
<div class="admin-row-editor-line"> class="admin-row-editor-line admin-row-editor-drag"
<Input handleLabel={at("tariff_package_reorder", {}, "Перетащите, чтобы изменить порядок")}
class="input" onReorder={(from, to) => tariffsStore.moveDraftRow("premiumTopupRows", from, to)}
type="number" let:item={row}
min="0.1" let:index
step="0.1"
placeholder="10"
bind:value={row.gb}
aria-label={at("tariff_col_volume_gb", {}, "Объём premium-пакета в GB")}
/>
<Input
class="input"
type="number"
min="0"
step="0.01"
placeholder="199"
bind:value={row.price}
aria-label={currencyPriceAriaLabel}
/>
<AdminButton
size="sm"
variant="danger"
onclick={() => tariffsStore.removeDraftRow("premiumTopupRubRows", index)}
aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton
>
</div>
{/each}
</div>
<div class="admin-row-editor">
<span class="admin-row-editor-caption"
>{at("payment_stars", {}, "Оплата Telegram Stars")}</span
> >
{#if tariffDraft.premiumTopupStarsRows.length} <Input
<div class="admin-row-editor-line admin-row-editor-header"> class="input"
<span>{at("tariff_col_volume_gb", {}, "Объём, GB")}</span> type="number"
<span>{at("tariff_col_price_stars", {}, "Цена, ⭐")}</span> min="0.1"
<span></span> step="0.1"
</div> placeholder="10"
{/if} bind:value={row.gb}
{#each tariffDraft.premiumTopupStarsRows as row, index} aria-label={at("tariff_col_volume_gb", {}, "Объём premium-пакета в GB")}
<div class="admin-row-editor-line"> />
<Input <Input
class="input" class="input"
type="number" type="number"
min="0.1" min="0"
step="0.1" step="0.01"
placeholder="10" placeholder="199"
bind:value={row.gb} bind:value={row.price}
aria-label={at("tariff_col_volume_gb", {}, "Объём premium-пакета в GB")} aria-label={currencyPriceAriaLabel}
/> />
<Input <Input
class="input" class="input"
type="number" type="number"
min="0" min="0"
step="1" step="1"
placeholder="100" placeholder="100"
bind:value={row.price} bind:value={row.stars}
aria-label={at( aria-label={at(
"tariff_label_price_stars", "tariff_label_price_stars",
{}, {},
"Цена premium-пакета в Telegram Stars" "Цена premium-пакета в Telegram Stars"
)} )}
/> />
<AdminButton <AdminButton
size="sm" size="sm"
variant="danger" variant="danger"
onclick={() => tariffsStore.removeDraftRow("premiumTopupStarsRows", index)} onclick={() => tariffsStore.removeDraftRow("premiumTopupRows", index)}
aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton
> >
</div> </Sortable>
{/each}
</div> </div>
</div> {/if}
</section> </section>
</Tabs.Content> </Tabs.Content>
@@ -641,32 +599,26 @@
<div class="admin-editor-section-actions"> <div class="admin-editor-section-actions">
<AdminButton <AdminButton
size="sm" size="sm"
onclick={() => tariffsStore.addDraftRow("trafficRubRows", { gb: 10, price: "" })} onclick={() =>
><Plus size={12} /> {currencyPackageLabel}</AdminButton tariffsStore.addDraftRow("trafficRows", { gb: 10, price: "", stars: "" })}
> ><Plus size={12} /> {at("tariff_btn_package", {}, "Пакет")}</AdminButton
<AdminButton
size="sm"
onclick={() => tariffsStore.addDraftRow("trafficStarsRows", { gb: 10, price: "" })}
><Plus size={12} /> {at("tariff_btn_package_stars", {}, "Пакет ⭐")}</AdminButton
> >
</div> </div>
</header> </header>
<div class="admin-package-columns"> {#if tariffDraft.trafficRows.length}
<div class="admin-row-editor"> <div class="admin-row-editor">
<span class="admin-row-editor-caption">{currencyPaymentLabel}</span> <div class="admin-row-editor-line admin-row-editor-drag admin-row-editor-header">
{#if tariffDraft.trafficRubRows.length} <span></span>
<div class="admin-row-editor-line admin-row-editor-drag admin-row-editor-header"> <span>{at("tariff_col_volume_gb", {}, "Объём, GB")}</span>
<span></span> <span>{currencyPriceColumnLabel}</span>
<span>{at("tariff_col_volume_gb", {}, "Объём, GB")}</span> <span>{at("tariff_col_price_stars_full", {}, "Цена, ⭐ Stars")}</span>
<span>{currencyPriceColumnLabel}</span> <span></span>
<span></span> </div>
</div>
{/if}
<Sortable <Sortable
items={tariffDraft.trafficRubRows} items={tariffDraft.trafficRows}
class="admin-row-editor-line admin-row-editor-drag" class="admin-row-editor-line admin-row-editor-drag"
handleLabel={at("tariff_package_reorder", {}, "Перетащите, чтобы изменить порядок")} handleLabel={at("tariff_package_reorder", {}, "Перетащите, чтобы изменить порядок")}
onReorder={(from, to) => tariffsStore.moveDraftRow("trafficRubRows", from, to)} onReorder={(from, to) => tariffsStore.moveDraftRow("trafficRows", from, to)}
let:item={row} let:item={row}
let:index let:index
> >
@@ -688,61 +640,24 @@
bind:value={row.price} bind:value={row.price}
aria-label={currencyPriceAriaLabel} aria-label={currencyPriceAriaLabel}
/> />
<AdminButton
size="sm"
variant="danger"
onclick={() => tariffsStore.removeDraftRow("trafficRubRows", index)}
aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton
>
</Sortable>
</div>
<div class="admin-row-editor">
<span class="admin-row-editor-caption"
>{at("payment_stars", {}, "Оплата Telegram Stars")}</span
>
{#if tariffDraft.trafficStarsRows.length}
<div class="admin-row-editor-line admin-row-editor-drag admin-row-editor-header">
<span></span>
<span>{at("tariff_col_volume_gb", {}, "Объём, GB")}</span>
<span>{at("tariff_col_price_stars", {}, "Цена, ⭐")}</span>
<span></span>
</div>
{/if}
<Sortable
items={tariffDraft.trafficStarsRows}
class="admin-row-editor-line admin-row-editor-drag"
handleLabel={at("tariff_package_reorder", {}, "Перетащите, чтобы изменить порядок")}
onReorder={(from, to) => tariffsStore.moveDraftRow("trafficStarsRows", from, to)}
let:item={row}
let:index
>
<Input
class="input"
type="number"
min="0.1"
step="0.1"
placeholder="50"
bind:value={row.gb}
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
/>
<Input <Input
class="input" class="input"
type="number" type="number"
min="0" min="0"
step="1" step="1"
placeholder="150" placeholder="150"
bind:value={row.price} bind:value={row.stars}
aria-label={at("tariff_label_price_stars", {}, "Цена пакета в Telegram Stars")} aria-label={at("tariff_label_price_stars", {}, "Цена пакета в Telegram Stars")}
/> />
<AdminButton <AdminButton
size="sm" size="sm"
variant="danger" variant="danger"
onclick={() => tariffsStore.removeDraftRow("trafficStarsRows", index)} onclick={() => tariffsStore.removeDraftRow("trafficRows", index)}
aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton
> >
</Sortable> </Sortable>
</div> </div>
</div> {/if}
</section> </section>
{/if} {/if}
</Tabs.Content> </Tabs.Content>
@@ -766,96 +681,65 @@
<div class="admin-editor-section-actions"> <div class="admin-editor-section-actions">
<AdminButton <AdminButton
size="sm" size="sm"
onclick={() => tariffsStore.addDraftRow("topupRubRows", { gb: 10, price: "" })} onclick={() =>
><Plus size={12} /> {currencyPackageLabel}</AdminButton tariffsStore.addDraftRow("topupRows", { gb: 10, price: "", stars: "" })}
> ><Plus size={12} /> {at("tariff_btn_package", {}, "Пакет")}</AdminButton
<AdminButton
size="sm"
onclick={() => tariffsStore.addDraftRow("topupStarsRows", { gb: 10, price: "" })}
><Plus size={12} /> {at("tariff_btn_package_stars", {}, "Пакет ⭐")}</AdminButton
> >
</div> </div>
</header> </header>
<div class="admin-package-columns"> {#if tariffDraft.topupRows.length}
<div class="admin-row-editor"> <div class="admin-row-editor">
<span class="admin-row-editor-caption">{currencyPaymentLabel}</span> <div class="admin-row-editor-line admin-row-editor-drag admin-row-editor-header">
{#if tariffDraft.topupRubRows.length} <span></span>
<div class="admin-row-editor-line admin-row-editor-header"> <span>{at("tariff_col_volume_gb", {}, "Объём, GB")}</span>
<span>{at("tariff_col_volume_gb", {}, "Объём, GB")}</span> <span>{currencyPriceColumnLabel}</span>
<span>{currencyPriceColumnLabel}</span> <span>{at("tariff_col_price_stars_full", {}, "Цена, ⭐ Stars")}</span>
<span></span> <span></span>
</div> </div>
{/if} <Sortable
{#each tariffDraft.topupRubRows as row, index} items={tariffDraft.topupRows}
<div class="admin-row-editor-line"> class="admin-row-editor-line admin-row-editor-drag"
<Input handleLabel={at("tariff_package_reorder", {}, "Перетащите, чтобы изменить порядок")}
class="input" onReorder={(from, to) => tariffsStore.moveDraftRow("topupRows", from, to)}
type="number" let:item={row}
min="0.1" let:index
step="0.1"
placeholder="20"
bind:value={row.gb}
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
/>
<Input
class="input"
type="number"
min="0"
step="0.01"
placeholder="149"
bind:value={row.price}
aria-label={currencyPriceAriaLabel}
/>
<AdminButton
size="sm"
variant="danger"
onclick={() => tariffsStore.removeDraftRow("topupRubRows", index)}
aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton
>
</div>
{/each}
</div>
<div class="admin-row-editor">
<span class="admin-row-editor-caption"
>{at("payment_stars", {}, "Оплата Telegram Stars")}</span
> >
{#if tariffDraft.topupStarsRows.length} <Input
<div class="admin-row-editor-line admin-row-editor-header"> class="input"
<span>{at("tariff_col_volume_gb", {}, "Объём, GB")}</span> type="number"
<span>{at("tariff_col_price_stars", {}, "Цена, ⭐")}</span> min="0.1"
<span></span> step="0.1"
</div> placeholder="20"
{/if} bind:value={row.gb}
{#each tariffDraft.topupStarsRows as row, index} aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
<div class="admin-row-editor-line"> />
<Input <Input
class="input" class="input"
type="number" type="number"
min="0.1" min="0"
step="0.1" step="0.01"
placeholder="20" placeholder="149"
bind:value={row.gb} bind:value={row.price}
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")} aria-label={currencyPriceAriaLabel}
/> />
<Input <Input
class="input" class="input"
type="number" type="number"
min="0" min="0"
step="1" step="1"
placeholder="75" placeholder="75"
bind:value={row.price} bind:value={row.stars}
aria-label={at("tariff_label_price_stars", {}, "Цена пакета в Telegram Stars")} aria-label={at("tariff_label_price_stars", {}, "Цена пакета в Telegram Stars")}
/> />
<AdminButton <AdminButton
size="sm" size="sm"
variant="danger" variant="danger"
onclick={() => tariffsStore.removeDraftRow("topupStarsRows", index)} onclick={() => tariffsStore.removeDraftRow("topupRows", index)}
aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton
> >
</div> </Sortable>
{/each}
</div> </div>
</div> {/if}
</section> </section>
{:else} {:else}
<p class="admin-muted"> <p class="admin-muted">
@@ -890,104 +774,69 @@
<div class="admin-editor-section-actions"> <div class="admin-editor-section-actions">
<AdminButton <AdminButton
size="sm" size="sm"
onclick={() => tariffsStore.addDraftRow("hwidRubRows", { count: 1, price: "" })} onclick={() =>
><Plus size={12} /> {currencyPackageLabel}</AdminButton tariffsStore.addDraftRow("hwidRows", { count: 1, price: "", stars: "" })}
> ><Plus size={12} /> {at("tariff_btn_package", {}, "Пакет")}</AdminButton
<AdminButton
size="sm"
onclick={() => tariffsStore.addDraftRow("hwidStarsRows", { count: 1, price: "" })}
><Plus size={12} /> {at("tariff_btn_package_stars", {}, "Пакет ⭐")}</AdminButton
> >
</div> </div>
</header> </header>
<div class="admin-package-columns"> {#if tariffDraft.hwidRows.length}
<div class="admin-row-editor"> <div class="admin-row-editor">
<span class="admin-row-editor-caption">{currencyPaymentLabel}</span> <div class="admin-row-editor-line admin-row-editor-drag admin-row-editor-header">
{#if tariffDraft.hwidRubRows.length} <span></span>
<div class="admin-row-editor-line admin-row-editor-header"> <span>{at("tariff_col_hwid_count", {}, "+ устройств")}</span>
<span>{at("tariff_col_hwid_count", {}, "+ устройств")}</span> <span>{currencyPriceColumnLabel}</span>
<span>{currencyPriceColumnLabel}</span> <span>{at("tariff_col_price_stars_full", {}, "Цена, ⭐ Stars")}</span>
<span></span> <span></span>
</div> </div>
{/if} <Sortable
{#each tariffDraft.hwidRubRows as row, index} items={tariffDraft.hwidRows}
<div class="admin-row-editor-line"> class="admin-row-editor-line admin-row-editor-drag"
<Input handleLabel={at("tariff_package_reorder", {}, "Перетащите, чтобы изменить порядок")}
class="input" onReorder={(from, to) => tariffsStore.moveDraftRow("hwidRows", from, to)}
type="number" let:item={row}
min="1" let:index
step="1"
placeholder="1"
bind:value={row.count}
aria-label={at(
"tariff_label_hwid_count_full",
{},
"Сколько устройств добавляет пакет"
)}
/>
<Input
class="input"
type="number"
min="0"
step="0.01"
placeholder="99"
bind:value={row.price}
aria-label={currencyPriceAriaLabel}
/>
<AdminButton
size="sm"
variant="danger"
onclick={() => tariffsStore.removeDraftRow("hwidRubRows", index)}
aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton
>
</div>
{/each}
</div>
<div class="admin-row-editor">
<span class="admin-row-editor-caption"
>{at("payment_stars", {}, "Оплата Telegram Stars")}</span
> >
{#if tariffDraft.hwidStarsRows.length} <Input
<div class="admin-row-editor-line admin-row-editor-header"> class="input"
<span>{at("tariff_col_hwid_count", {}, "+ устройств")}</span> type="number"
<span>{at("tariff_col_price_stars", {}, "Цена, ⭐")}</span> min="1"
<span></span> step="1"
</div> placeholder="1"
{/if} bind:value={row.count}
{#each tariffDraft.hwidStarsRows as row, index} aria-label={at(
<div class="admin-row-editor-line"> "tariff_label_hwid_count_full",
<Input {},
class="input" "Сколько устройств добавляет пакет"
type="number" )}
min="1" />
step="1" <Input
placeholder="1" class="input"
bind:value={row.count} type="number"
aria-label={at( min="0"
"tariff_label_hwid_count_full", step="0.01"
{}, placeholder="99"
"Сколько устройств добавляет пакет" bind:value={row.price}
)} aria-label={currencyPriceAriaLabel}
/> />
<Input <Input
class="input" class="input"
type="number" type="number"
min="0" min="0"
step="1" step="1"
placeholder="50" placeholder="50"
bind:value={row.price} bind:value={row.stars}
aria-label={at("tariff_label_price_stars", {}, "Цена пакета в Telegram Stars")} aria-label={at("tariff_label_price_stars", {}, "Цена пакета в Telegram Stars")}
/> />
<AdminButton <AdminButton
size="sm" size="sm"
variant="danger" variant="danger"
onclick={() => tariffsStore.removeDraftRow("hwidStarsRows", index)} onclick={() => tariffsStore.removeDraftRow("hwidRows", index)}
aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton aria-label={at("btn_delete", {}, "Удалить")}><Trash2 size={13} /></AdminButton
> >
</div> </Sortable>
{/each}
</div> </div>
</div> {/if}
</section> </section>
</Tabs.Content> </Tabs.Content>
</Tabs.Root> </Tabs.Root>
+85 -45
View File
@@ -24,17 +24,13 @@ export function emptyTariffDraft() {
{ months: 6, rub: 1200, stars: "", referral_inviter: 15, referral_referee: 7 }, { months: 6, rub: 1200, stars: "", referral_inviter: 15, referral_referee: 7 },
{ months: 12, rub: 2400, stars: "", referral_inviter: 30, referral_referee: 15 }, { months: 12, rub: 2400, stars: "", referral_inviter: 30, referral_referee: 15 },
], ],
topupRubRows: [], topupRows: [],
topupStarsRows: [], premiumTopupRows: [],
premiumTopupRubRows: [], trafficRows: [
premiumTopupStarsRows: [], { gb: 10, price: 199, stars: "" },
trafficRubRows: [ { gb: 50, price: 799, stars: "" },
{ gb: 10, price: 199 },
{ gb: 50, price: 799 },
], ],
trafficStarsRows: [], hwidRows: [],
hwidRubRows: [],
hwidStarsRows: [],
}; };
} }
@@ -66,6 +62,50 @@ export function rowsFromPackages(packageSet, currency, valueKey) {
})); }));
} }
function packageValueSignature(value) {
const num = Number(value);
return Number.isFinite(num) ? String(num) : String(value || "");
}
export function packageRowsFromPackageSet(packageSet, currency, valueKey) {
const currencyRows = rowsFromPackages(packageSet, currency, valueKey);
const starsRows = rowsFromPackages(packageSet, "stars", valueKey);
const usedStars = new Set();
const rows = currencyRows.map((row) => {
const rowSignature = packageValueSignature(row[valueKey]);
const starsIndex = starsRows.findIndex(
(starsRow, index) =>
!usedStars.has(index) && packageValueSignature(starsRow[valueKey]) === rowSignature
);
const starsRow = starsIndex >= 0 ? starsRows[starsIndex] : null;
if (starsIndex >= 0) usedStars.add(starsIndex);
return {
[valueKey]: row[valueKey],
price: row.price,
stars: starsRow?.price ?? "",
prices: row.prices,
min_price: row.min_price,
stars_prices: starsRow?.prices,
stars_min_price: starsRow?.min_price ?? "",
};
});
starsRows.forEach((starsRow, index) => {
if (usedStars.has(index)) return;
rows.push({
[valueKey]: starsRow[valueKey],
price: "",
stars: starsRow.price,
stars_prices: starsRow.prices,
stars_min_price: starsRow.min_price ?? "",
});
});
return rows;
}
export function draftFromTariff(tariff, defaultCurrency = "rub") { export function draftFromTariff(tariff, defaultCurrency = "rub") {
const currency = normalizeCurrencyKey(defaultCurrency); const currency = normalizeCurrencyKey(defaultCurrency);
const defaultPrices = tariff.prices?.[currency] || {}; const defaultPrices = tariff.prices?.[currency] || {};
@@ -109,14 +149,10 @@ export function draftFromTariff(tariff, defaultCurrency = "rub") {
hwid_device_limit: tariff.hwid_device_limit ?? "", hwid_device_limit: tariff.hwid_device_limit ?? "",
conversion_rate_rub_per_gb: tariff.conversion_rate_rub_per_gb ?? "", conversion_rate_rub_per_gb: tariff.conversion_rate_rub_per_gb ?? "",
periodRows: periodRows.length ? periodRows : emptyTariffDraft().periodRows, periodRows: periodRows.length ? periodRows : emptyTariffDraft().periodRows,
topupRubRows: rowsFromPackages(tariff.topup_packages, currency, "gb"), topupRows: packageRowsFromPackageSet(tariff.topup_packages, currency, "gb"),
topupStarsRows: rowsFromPackages(tariff.topup_packages, "stars", "gb"), premiumTopupRows: packageRowsFromPackageSet(tariff.premium_topup_packages, currency, "gb"),
premiumTopupRubRows: rowsFromPackages(tariff.premium_topup_packages, currency, "gb"), trafficRows: packageRowsFromPackageSet(tariff.traffic_packages, currency, "gb"),
premiumTopupStarsRows: rowsFromPackages(tariff.premium_topup_packages, "stars", "gb"), hwidRows: packageRowsFromPackageSet(tariff.hwid_device_packages, currency, "count"),
trafficRubRows: rowsFromPackages(tariff.traffic_packages, currency, "gb"),
trafficStarsRows: rowsFromPackages(tariff.traffic_packages, "stars", "gb"),
hwidRubRows: rowsFromPackages(tariff.hwid_device_packages, currency, "count"),
hwidStarsRows: rowsFromPackages(tariff.hwid_device_packages, "stars", "count"),
}; };
} }
@@ -156,10 +192,34 @@ export function packagesFromRows(rows, valueKey) {
.filter((row) => row[valueKey] > 0 && row.price !== null && row.price >= 0); .filter((row) => row[valueKey] > 0 && row.price !== null && row.price >= 0);
} }
export function packageSetFromRows(rubRows, starsRows, valueKey, defaultCurrency = "rub") { export function packagesFromPackageRows(rows, valueKey, priceKey, options = {}) {
const pricesKey = options.pricesKey || "prices";
const minPriceKey = options.minPriceKey || "min_price";
return (rows || [])
.map((row) => {
const pkg = {
[valueKey]: parseNumber(row[valueKey]),
price: parseNumber(row[priceKey]),
};
if (row[pricesKey] && typeof row[pricesKey] === "object") {
pkg.prices = structuredCloneSafe(row[pricesKey]);
}
const minPrice = parseNumber(row[minPriceKey]);
if (minPrice !== null) {
pkg.min_price = minPrice;
}
return pkg;
})
.filter((row) => row[valueKey] > 0 && row.price !== null && row.price >= 0);
}
export function packageSetFromRows(rows, valueKey, defaultCurrency = "rub") {
const currency = normalizeCurrencyKey(defaultCurrency); const currency = normalizeCurrencyKey(defaultCurrency);
const defaultCurrencyPackages = packagesFromRows(rubRows, valueKey); const defaultCurrencyPackages = packagesFromPackageRows(rows, valueKey, "price");
const stars = packagesFromRows(starsRows, valueKey); const stars = packagesFromPackageRows(rows, valueKey, "stars", {
pricesKey: "stars_prices",
minPriceKey: "stars_min_price",
});
if (!defaultCurrencyPackages.length && !stars.length) return null; if (!defaultCurrencyPackages.length && !stars.length) return null;
return { return {
...(defaultCurrencyPackages.length ? { [currency]: defaultCurrencyPackages } : {}), ...(defaultCurrencyPackages.length ? { [currency]: defaultCurrencyPackages } : {}),
@@ -200,21 +260,11 @@ export function tariffFromDraft(draft, fallbackCurrency = "rub") {
const hwidLimit = parseIntNumber(draft.hwid_device_limit); const hwidLimit = parseIntNumber(draft.hwid_device_limit);
if (hwidLimit !== null) tariff.hwid_device_limit = hwidLimit; if (hwidLimit !== null) tariff.hwid_device_limit = hwidLimit;
const hwidPackages = packageSetFromRows( const hwidPackages = packageSetFromRows(draft.hwidRows, "count", defaultCurrency);
draft.hwidRubRows,
draft.hwidStarsRows,
"count",
defaultCurrency
);
if (hwidPackages) tariff.hwid_device_packages = hwidPackages; if (hwidPackages) tariff.hwid_device_packages = hwidPackages;
const premiumMonthlyGb = parseNumber(draft.premium_monthly_gb); const premiumMonthlyGb = parseNumber(draft.premium_monthly_gb);
if (premiumMonthlyGb !== null) tariff.premium_monthly_gb = premiumMonthlyGb; if (premiumMonthlyGb !== null) tariff.premium_monthly_gb = premiumMonthlyGb;
const premiumTopupPackages = packageSetFromRows( const premiumTopupPackages = packageSetFromRows(draft.premiumTopupRows, "gb", defaultCurrency);
draft.premiumTopupRubRows,
draft.premiumTopupStarsRows,
"gb",
defaultCurrency
);
if (premiumTopupPackages) tariff.premium_topup_packages = premiumTopupPackages; if (premiumTopupPackages) tariff.premium_topup_packages = premiumTopupPackages;
if (tariff.billing_model === "period") { if (tariff.billing_model === "period") {
@@ -254,20 +304,10 @@ export function tariffFromDraft(draft, fallbackCurrency = "rub") {
.filter((row) => row.referral_referee !== null) .filter((row) => row.referral_referee !== null)
.map((row) => [String(row.months), row.referral_referee]) .map((row) => [String(row.months), row.referral_referee])
); );
const topupPackages = packageSetFromRows( const topupPackages = packageSetFromRows(draft.topupRows, "gb", defaultCurrency);
draft.topupRubRows,
draft.topupStarsRows,
"gb",
defaultCurrency
);
if (topupPackages) tariff.topup_packages = topupPackages; if (topupPackages) tariff.topup_packages = topupPackages;
} else { } else {
const trafficPackages = packageSetFromRows( const trafficPackages = packageSetFromRows(draft.trafficRows, "gb", defaultCurrency);
draft.trafficRubRows,
draft.trafficStarsRows,
"gb",
defaultCurrency
);
if (trafficPackages) tariff.traffic_packages = trafficPackages; if (trafficPackages) tariff.traffic_packages = trafficPackages;
const conversion = parseNumber(draft.conversion_rate_rub_per_gb); const conversion = parseNumber(draft.conversion_rate_rub_per_gb);
if (conversion !== null) tariff.conversion_rate_rub_per_gb = conversion; if (conversion !== null) tariff.conversion_rate_rub_per_gb = conversion;
+2 -8
View File
@@ -3581,13 +3581,8 @@
} }
.admin-row-editor-line.admin-row-editor-drag { .admin-row-editor-line.admin-row-editor-drag {
grid-template-columns: 24px minmax(90px, 1fr) minmax(110px, 1fr) 32px; grid-template-columns: 24px minmax(90px, 1fr) minmax(110px, 1fr) minmax(110px, 1fr)
} 32px;
.admin-package-columns {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
} }
.admin-dialog-actions { .admin-dialog-actions {
@@ -3799,7 +3794,6 @@
} }
.admin-form-row.admin-form-row-3, .admin-form-row.admin-form-row-3,
.admin-package-columns,
.admin-row-editor-line, .admin-row-editor-line,
.admin-row-editor-line.admin-row-editor-4, .admin-row-editor-line.admin-row-editor-4,
.admin-row-editor-line.admin-row-editor-6, .admin-row-editor-line.admin-row-editor-6,