added ability to enable all gifts in one go
This commit is contained in:
parent
31b323e36a
commit
9844d78c5b
7 changed files with 122 additions and 15 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
9
cmd/telesrv-admin/web/dist/assets/index-ybbM_ULl.js
vendored
Normal file
9
cmd/telesrv-admin/web/dist/assets/index-ybbM_ULl.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
cmd/telesrv-admin/web/dist/index.html
vendored
4
cmd/telesrv-admin/web/dist/index.html
vendored
|
|
@ -4,8 +4,8 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>telesrv admin</title>
|
||||
<script type="module" crossorigin src="/assets/index-DzNuxLKt.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DHdrFM5j.css">
|
||||
<script type="module" crossorigin src="/assets/index-ybbM_ULl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BVA68zgn.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
|
|
@ -304,6 +304,12 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"gifts.replace": "New revision",
|
||||
"gifts.disable": "Disable",
|
||||
"gifts.enable": "Enable",
|
||||
"gifts.bulkSelected": "{count} selected",
|
||||
"gifts.bulkSelectAll": "Select all visible gifts",
|
||||
"gifts.bulkSelectOne": "Select gift {id}",
|
||||
"gifts.bulkEnable": "Enable selected",
|
||||
"gifts.bulkDisable": "Disable selected",
|
||||
"gifts.bulkStatusFailed": "{failed} of {total} failed",
|
||||
"gifts.empty": "No Star Gifts have been imported.",
|
||||
"gifts.emptyHint": "Import the first animation above to build the gift catalog.",
|
||||
"gifts.validationReady": "Validation passed",
|
||||
|
|
@ -675,6 +681,12 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"gifts.replace": "创建新版本",
|
||||
"gifts.disable": "停用",
|
||||
"gifts.enable": "启用",
|
||||
"gifts.bulkSelected": "已选择 {count} 个",
|
||||
"gifts.bulkSelectAll": "选择所有可见礼物",
|
||||
"gifts.bulkSelectOne": "选择礼物 {id}",
|
||||
"gifts.bulkEnable": "启用所选",
|
||||
"gifts.bulkDisable": "停用所选",
|
||||
"gifts.bulkStatusFailed": "{total} 个中有 {failed} 个失败",
|
||||
"gifts.empty": "尚未导入星星礼物。",
|
||||
"gifts.emptyHint": "从上方导入第一个动画,开始搭建礼物目录。",
|
||||
"gifts.validationReady": "校验已通过",
|
||||
|
|
@ -1046,6 +1058,12 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"gifts.replace": "Новая версия",
|
||||
"gifts.disable": "Отключить",
|
||||
"gifts.enable": "Включить",
|
||||
"gifts.bulkSelected": "Выбрано: {count}",
|
||||
"gifts.bulkSelectAll": "Выбрать все видимые подарки",
|
||||
"gifts.bulkSelectOne": "Выбрать подарок {id}",
|
||||
"gifts.bulkEnable": "Включить выбранные",
|
||||
"gifts.bulkDisable": "Отключить выбранные",
|
||||
"gifts.bulkStatusFailed": "{failed} из {total} не выполнено",
|
||||
"gifts.empty": "Звездные подарки еще не импортированы.",
|
||||
"gifts.emptyHint": "Импортируйте первую анимацию, чтобы начать наполнение каталога.",
|
||||
"gifts.validationReady": "Проверка пройдена",
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ export function GiftsPage() {
|
|||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [importError, setImportError] = useState("");
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [bulkReason, setBulkReason] = useState("");
|
||||
const [bulkBusy, setBulkBusy] = useState(false);
|
||||
const [bulkError, setBulkError] = useState("");
|
||||
|
||||
async function load() {
|
||||
setError("");
|
||||
|
|
@ -152,6 +156,61 @@ export function GiftsPage() {
|
|||
);
|
||||
}, [gifts, query]);
|
||||
|
||||
const allVisibleSelected = visibleGifts.length > 0 && visibleGifts.every((gift) => selected.has(gift.GiftID));
|
||||
|
||||
function toggleSelected(giftID: string) {
|
||||
setSelected((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(giftID)) next.delete(giftID);
|
||||
else next.add(giftID);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSelectAllVisible() {
|
||||
setSelected((prev) => {
|
||||
if (allVisibleSelected) {
|
||||
const next = new Set(prev);
|
||||
for (const gift of visibleGifts) next.delete(gift.GiftID);
|
||||
return next;
|
||||
}
|
||||
const next = new Set(prev);
|
||||
for (const gift of visibleGifts) next.add(gift.GiftID);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
async function bulkSetEnabled(nextEnabled: boolean) {
|
||||
if (!bulkReason.trim()) {
|
||||
setBulkError(t("action.reasonRequired"));
|
||||
return;
|
||||
}
|
||||
setBulkBusy(true);
|
||||
setBulkError("");
|
||||
const ids = Array.from(selected);
|
||||
let failed = 0;
|
||||
for (const id of ids) {
|
||||
try {
|
||||
await api.action("/api/actions/set-gift-enabled", {
|
||||
gift_id: id,
|
||||
enabled: nextEnabled,
|
||||
reason: bulkReason.trim(),
|
||||
confirm: true
|
||||
});
|
||||
} catch {
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
setBulkBusy(false);
|
||||
if (failed > 0) {
|
||||
setBulkError(t("gifts.bulkStatusFailed", { failed, total: ids.length }));
|
||||
} else {
|
||||
setSelected(new Set());
|
||||
setBulkReason("");
|
||||
}
|
||||
await load();
|
||||
}
|
||||
|
||||
function uploadForm(confirm: boolean, commandID = "") {
|
||||
if (!file) throw new Error(t("gifts.fileRequired"));
|
||||
if (!reason.trim()) throw new Error(t("action.reasonRequired"));
|
||||
|
|
@ -249,12 +308,25 @@ export function GiftsPage() {
|
|||
<span className="gift-list-summary">{t("gifts.listSummary", { shown: visibleGifts.length, total: gifts.length })}</span>
|
||||
</div>
|
||||
</QueryPanel>
|
||||
{selected.size > 0 && <div className="gift-bulk-toolbar">
|
||||
<span className="gift-bulk-count">{t("gifts.bulkSelected", { count: selected.size })}</span>
|
||||
<label className="gift-reason-field gift-bulk-reason"><span>{t("gifts.reason")}</span><input value={bulkReason} placeholder={t("gifts.reasonPlaceholder")} onChange={(e) => setBulkReason(e.target.value)} /></label>
|
||||
<button className="btn" type="button" onClick={() => bulkSetEnabled(true)} disabled={bulkBusy}>
|
||||
{bulkBusy ? <Loader2 className="spin" size={14} /> : <CheckCircle2 size={14} />} {t("gifts.bulkEnable")}
|
||||
</button>
|
||||
<button className="btn" type="button" onClick={() => bulkSetEnabled(false)} disabled={bulkBusy}>
|
||||
{bulkBusy ? <Loader2 className="spin" size={14} /> : <Pause size={14} />} {t("gifts.bulkDisable")}
|
||||
</button>
|
||||
<button className="btn" type="button" onClick={() => { setSelected(new Set()); setBulkError(""); }} disabled={bulkBusy}>{t("common.close")}</button>
|
||||
{bulkError && <span className="gift-bulk-error">{bulkError}</span>}
|
||||
</div>}
|
||||
<div className="table-wrap gift-table-wrap">
|
||||
<table className="data-table gift-table">
|
||||
<thead><tr><th>{t("gifts.animation")}</th><th>{t("gifts.idRevision")}</th><th>{t("gifts.title")}</th><th>{t("gifts.price")}</th><th>{t("gifts.source")}</th><th>{t("gifts.received")}</th><th>{t("common.status")}</th><th>{t("common.updatedAt")}</th><th>{t("common.actions")}</th></tr></thead>
|
||||
<thead><tr><th className="gift-select-col"><input type="checkbox" checked={allVisibleSelected} onChange={toggleSelectAllVisible} aria-label={t("gifts.bulkSelectAll")} /></th><th>{t("gifts.animation")}</th><th>{t("gifts.idRevision")}</th><th>{t("gifts.title")}</th><th>{t("gifts.price")}</th><th>{t("gifts.source")}</th><th>{t("gifts.received")}</th><th>{t("common.status")}</th><th>{t("common.updatedAt")}</th><th>{t("common.actions")}</th></tr></thead>
|
||||
<tbody>
|
||||
{visibleGifts.map((gift) => (
|
||||
<tr className={gift.Enabled ? "" : "gift-row-disabled"} key={gift.GiftID}>
|
||||
<td className="gift-select-col"><input type="checkbox" checked={selected.has(gift.GiftID)} onChange={() => toggleSelected(gift.GiftID)} aria-label={t("gifts.bulkSelectOne", { id: gift.GiftID })} /></td>
|
||||
<td><LottiePreview giftID={gift.GiftID} revision={gift.Revision} compact /></td>
|
||||
<td className="mono">{gift.GiftID} / {gift.Revision}</td>
|
||||
<td><strong className="gift-table-title">{gift.Title || `Gift #${gift.GiftID}`}</strong><span className="gift-sort-order">{t("gifts.sortOrder")}: {gift.SortOrder}</span></td>
|
||||
|
|
@ -266,7 +338,7 @@ export function GiftsPage() {
|
|||
<td><div className="gift-table-actions"><button className="btn compact-btn collectible-button" type="button" onClick={() => setCollectibleGift(gift)}><Gem size={13} />{t("collectibles.manage")}</button><button className="btn compact-btn" type="button" onClick={() => startRevision(gift)}>{t("gifts.replace")}</button><ActionButton compact tone="neutral" label={gift.Enabled ? t("gifts.disable") : t("gifts.enable")} path="/api/actions/set-gift-enabled" payload={() => ({ gift_id: gift.GiftID, enabled: !gift.Enabled })} onDone={() => void load()} /></div></td>
|
||||
</tr>
|
||||
))}
|
||||
{visibleGifts.length === 0 && <EmptyRow colSpan={9} />}
|
||||
{visibleGifts.length === 0 && <EmptyRow colSpan={10} />}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -408,8 +408,25 @@
|
|||
|
||||
.gift-table-wrap { background: #ffffff; }
|
||||
.gift-table { min-width: 1080px; }
|
||||
.gift-table th:first-child { width: 74px; }
|
||||
.gift-table th:nth-child(2) { width: 74px; }
|
||||
.gift-table td { vertical-align: middle; }
|
||||
.gift-select-col { width: 34px; text-align: center; }
|
||||
.gift-select-col input { width: 15px; height: 15px; }
|
||||
|
||||
.gift-bulk-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 9px 12px;
|
||||
margin-bottom: 10px;
|
||||
background: #f3f8f7;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 9px;
|
||||
}
|
||||
.gift-bulk-count { color: var(--text); font-size: 12px; font-weight: 700; white-space: nowrap; }
|
||||
.gift-bulk-reason { flex: 1; min-width: 160px; }
|
||||
.gift-bulk-reason input { height: 34px; }
|
||||
.gift-bulk-error { color: #b42318; font-size: 11px; font-weight: 700; }
|
||||
.gift-animation-shell.compact { width: 56px; min-height: 56px; overflow: hidden; border: 1px solid var(--line); border-radius: 9px; }
|
||||
.gift-animation-shell.compact .gift-animation { width: 54px; height: 54px; }
|
||||
.gift-animation-shell.compact .gift-play { right: 3px; bottom: 3px; width: 20px; height: 20px; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue