feat(admin): add never-subscribed broadcast target with audience counts

Add a broadcast audience for users who registered but never had any subscription or trial (no Subscription rows at all), backed by a new get_user_ids_without_any_subscription DAL helper and a 'never' target in the webapp broadcast route.

Add GET /api/admin/broadcast/audience-counts so the audience dropdown shows the recipient count next to each option, with graceful fallback when counts are unavailable.
This commit is contained in:
3252a8
2026-06-01 23:37:59 +03:00
parent 619a13128c
commit 56796d9f22
9 changed files with 84 additions and 4 deletions
@@ -6,6 +6,7 @@ export function createBroadcastStore({ api, onToast, at }) {
broadcastText: "",
broadcastBusy: false,
broadcastResult: null,
broadcastCounts: null,
});
const BROADCAST_TARGET_OPTIONS = [
@@ -13,8 +14,23 @@ export function createBroadcastStore({ api, onToast, at }) {
{ value: "active", label: at("broadcast_target_active", {}, "С подпиской") },
{ value: "inactive", label: at("broadcast_target_inactive", {}, "Без подписки") },
{ value: "expired", label: at("broadcast_target_expired", {}, "Expired subscription") },
{
value: "never",
label: at("broadcast_target_never", {}, "Без подписки и без истории"),
},
];
async function loadCounts() {
try {
const res = await api("/admin/broadcast/audience-counts");
if (res?.ok && res.counts) {
state.update((s) => ({ ...s, broadcastCounts: res.counts }));
}
} catch {
// Counts are advisory; ignore failures and keep plain labels.
}
}
async function runBroadcast() {
let text = "";
let target = "";
@@ -56,6 +72,7 @@ export function createBroadcastStore({ api, onToast, at }) {
update: state.update,
runBroadcast,
updateField,
loadCounts,
BROADCAST_TARGET_OPTIONS,
};
}