修复新字段不同步任务详情的bug,新增回答任务消息图标
This commit is contained in:
parent
561a34f108
commit
bd7a578252
Binary file not shown.
@ -1439,6 +1439,8 @@ def _ask_assignment_data(
|
|||||||
"task_id": assignment.task.task_no,
|
"task_id": assignment.task.task_no,
|
||||||
"public_token": assignment.task.public_token or assignment.task.task_no,
|
"public_token": assignment.task.public_token or assignment.task.task_no,
|
||||||
"shop_name": assignment.task.shop_name,
|
"shop_name": assignment.task.shop_name,
|
||||||
|
"ecommerce_platform": assignment.task.ecommerce_platform or "",
|
||||||
|
"product_id": assignment.task.product_id or "",
|
||||||
"product_name": assignment.task.product_name,
|
"product_name": assignment.task.product_name,
|
||||||
"product_link": assignment.task.product_link,
|
"product_link": assignment.task.product_link,
|
||||||
"product_qr": _product_qr_data(assignment.task),
|
"product_qr": _product_qr_data(assignment.task),
|
||||||
@ -2036,6 +2038,8 @@ def _ask_review_board_data(
|
|||||||
"task_id": assignment.task.task_no,
|
"task_id": assignment.task.task_no,
|
||||||
"public_token": assignment.task.public_token or assignment.task.task_no,
|
"public_token": assignment.task.public_token or assignment.task.task_no,
|
||||||
"shop_name": assignment.task.shop_name,
|
"shop_name": assignment.task.shop_name,
|
||||||
|
"ecommerce_platform": assignment.task.ecommerce_platform or "",
|
||||||
|
"product_id": assignment.task.product_id or "",
|
||||||
"product_name": assignment.task.product_name,
|
"product_name": assignment.task.product_name,
|
||||||
"product_link": assignment.task.product_link,
|
"product_link": assignment.task.product_link,
|
||||||
"product_qr": _product_qr_data(assignment.task),
|
"product_qr": _product_qr_data(assignment.task),
|
||||||
@ -2066,6 +2070,8 @@ def _ask_review_board_data(
|
|||||||
"public_token": assignment.task.public_token or assignment.task.task_no,
|
"public_token": assignment.task.public_token or assignment.task.task_no,
|
||||||
"status_label": "已完成" if refund_completed else "待返款",
|
"status_label": "已完成" if refund_completed else "待返款",
|
||||||
"shop_name": assignment.task.shop_name,
|
"shop_name": assignment.task.shop_name,
|
||||||
|
"ecommerce_platform": assignment.task.ecommerce_platform or "",
|
||||||
|
"product_id": assignment.task.product_id or "",
|
||||||
"product_name": assignment.task.product_name,
|
"product_name": assignment.task.product_name,
|
||||||
"qa_count": selected_count,
|
"qa_count": selected_count,
|
||||||
"claimed_count": selected_count,
|
"claimed_count": selected_count,
|
||||||
@ -2474,6 +2480,13 @@ def ask_completion_reviews(user: AdminUser = Depends(current_user), db: Session
|
|||||||
@app.get("/api/admin/review-pending-count")
|
@app.get("/api/admin/review-pending-count")
|
||||||
def review_pending_count(user: AdminUser = Depends(current_user), db: Session = Depends(get_db)):
|
def review_pending_count(user: AdminUser = Depends(current_user), db: Session = Depends(get_db)):
|
||||||
"""Return lightweight sidebar counts without loading review rows or images."""
|
"""Return lightweight sidebar counts without loading review rows or images."""
|
||||||
|
answer_draft_count = db.scalar(
|
||||||
|
select(func.count(AnswerSubmission.id)).where(
|
||||||
|
AnswerSubmission.status.in_(["待筛选", "待运营派发"]),
|
||||||
|
AnswerSubmission.questions.any(),
|
||||||
|
_answer_owner_clause(user),
|
||||||
|
)
|
||||||
|
) or 0
|
||||||
answer_count = db.scalar(
|
answer_count = db.scalar(
|
||||||
select(func.count(AnswerSubmission.id)).where(
|
select(func.count(AnswerSubmission.id)).where(
|
||||||
AnswerSubmission.status == "待审核",
|
AnswerSubmission.status == "待审核",
|
||||||
@ -2498,6 +2511,7 @@ def review_pending_count(user: AdminUser = Depends(current_user), db: Session =
|
|||||||
"ask": ask_count,
|
"ask": ask_count,
|
||||||
"answer": answer_count,
|
"answer": answer_count,
|
||||||
"total": ask_count + answer_count,
|
"total": ask_count + answer_count,
|
||||||
|
"answer_drafts": answer_draft_count,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -437,6 +437,7 @@ def test_task_board_is_available_to_operator():
|
|||||||
assert counts.status_code == 200
|
assert counts.status_code == 200
|
||||||
count_data = counts.json()["data"]
|
count_data = counts.json()["data"]
|
||||||
assert count_data["total"] == count_data["ask"] + count_data["answer"]
|
assert count_data["total"] == count_data["ask"] + count_data["answer"]
|
||||||
|
assert "answer_drafts" in count_data
|
||||||
|
|
||||||
|
|
||||||
def test_answer_task_expires_after_24_hours_and_can_be_reactivated():
|
def test_answer_task_expires_after_24_hours_and_can_be_reactivated():
|
||||||
|
|||||||
@ -52,7 +52,11 @@ def test_refund_snapshot_is_idempotent_when_callback_wins_race(setup):
|
|||||||
@pytest.mark.parametrize("batched", [True, False])
|
@pytest.mark.parametrize("batched", [True, False])
|
||||||
def test_ask_confirmation_creates_record(setup, batched):
|
def test_ask_confirmation_creates_record(setup, batched):
|
||||||
db, client, owner, person, _ = setup
|
db, client, owner, person, _ = setup
|
||||||
task = AskTask(task_no="TASK-refund", product_link="https://example.com", product_name="商品", shop_name="提问店铺", owner_id=owner.id, status="已发布")
|
task = AskTask(
|
||||||
|
task_no="TASK-refund", product_link="https://example.com", product_name="商品",
|
||||||
|
shop_name="提问店铺", ecommerce_platform="京东", product_id="687154817423861300",
|
||||||
|
owner_id=owner.id, status="已发布",
|
||||||
|
)
|
||||||
db.add(task)
|
db.add(task)
|
||||||
db.flush()
|
db.flush()
|
||||||
assignment = AskAssignment(task_id=task.id, user_id=person.id, status="已通过")
|
assignment = AskAssignment(task_id=task.id, user_id=person.id, status="已通过")
|
||||||
@ -70,6 +74,10 @@ def test_ask_confirmation_creates_record(setup, batched):
|
|||||||
kind, route = "ask_assignment", "ask-assignments"
|
kind, route = "ask_assignment", "ask-assignments"
|
||||||
db.add(RefundRecipient(target_kind=kind, target_id=record.id, account="ask@example.com", payee_name="提问人"))
|
db.add(RefundRecipient(target_kind=kind, target_id=record.id, account="ask@example.com", payee_name="提问人"))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
detail = client.get(f"/api/admin/task-board/{route}/{record.id}")
|
||||||
|
assert detail.status_code == 200, detail.text
|
||||||
|
assert detail.json()["data"]["ecommerce_platform"] == "京东"
|
||||||
|
assert detail.json()["data"]["product_id"] == "687154817423861300"
|
||||||
url = f"/api/admin/task-board/{route}/{record.id}/confirm-refund"
|
url = f"/api/admin/task-board/{route}/{record.id}/confirm-refund"
|
||||||
payer_id = db.scalar(select(RefundPaymentAccount.id))
|
payer_id = db.scalar(select(RefundPaymentAccount.id))
|
||||||
owner = db.get(AdminUser, task.owner_id)
|
owner = db.get(AdminUser, task.owner_id)
|
||||||
@ -84,6 +92,8 @@ def test_ask_confirmation_creates_record(setup, batched):
|
|||||||
row = client.get("/api/admin/refund-records").json()["data"]["items"][0]
|
row = client.get("/api/admin/refund-records").json()["data"]["items"][0]
|
||||||
assert row["amount_fen"] == 50
|
assert row["amount_fen"] == 50
|
||||||
assert row["payee_name"] == "提问人" and row["order_no"] == "TASK-refund"
|
assert row["payee_name"] == "提问人" and row["order_no"] == "TASK-refund"
|
||||||
|
assert row["ecommerce_platform"] == "京东"
|
||||||
|
assert row["product_id"] == "687154817423861300"
|
||||||
|
|
||||||
|
|
||||||
def test_available_accounts_and_disabled_account_rejection(setup):
|
def test_available_accounts_and_disabled_account_rejection(setup):
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
export default function CreateAnswerTasksPageV2({ api, user }) {
|
export default function CreateAnswerTasksPageV2({ api, user, answerDraftCount = 0 }) {
|
||||||
const [records, setRecords] = useState([])
|
const [records, setRecords] = useState([])
|
||||||
const [expanded, setExpanded] = useState({})
|
const [expanded, setExpanded] = useState({})
|
||||||
const [busy, setBusy] = useState('')
|
const [busy, setBusy] = useState('')
|
||||||
@ -219,6 +219,7 @@ export default function CreateAnswerTasksPageV2({ api, user }) {
|
|||||||
setPublishing(null)
|
setPublishing(null)
|
||||||
setPublishForm({ ecommerce_platform: ecommercePlatform, shop_name: shopName, product_id: '' })
|
setPublishForm({ ecommerce_platform: ecommercePlatform, shop_name: shopName, product_id: '' })
|
||||||
await load()
|
await load()
|
||||||
|
window.dispatchEvent(new CustomEvent('answer-draft-count-changed'))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message)
|
setError(err.message)
|
||||||
} finally {
|
} finally {
|
||||||
@ -234,6 +235,7 @@ export default function CreateAnswerTasksPageV2({ api, user }) {
|
|||||||
await api(`/api/admin/answer-task-drafts/${record.id}/reject`, { method: 'POST' })
|
await api(`/api/admin/answer-task-drafts/${record.id}/reject`, { method: 'POST' })
|
||||||
setRecords(current => current.filter(item => item.id !== record.id))
|
setRecords(current => current.filter(item => item.id !== record.id))
|
||||||
setRejecting(null)
|
setRejecting(null)
|
||||||
|
window.dispatchEvent(new CustomEvent('answer-draft-count-changed'))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message)
|
setError(err.message)
|
||||||
} finally {
|
} finally {
|
||||||
@ -277,7 +279,7 @@ export default function CreateAnswerTasksPageV2({ api, user }) {
|
|||||||
return <>
|
return <>
|
||||||
<div className="create-page-heading">
|
<div className="create-page-heading">
|
||||||
<div><h2>创建任务</h2><p>通过顶部切换创建「提问任务」或「回答任务」。</p></div>
|
<div><h2>创建任务</h2><p>通过顶部切换创建「提问任务」或「回答任务」。</p></div>
|
||||||
<div className="create-mode-tabs"><button onClick={() => window.dispatchEvent(new CustomEvent('switch-create-ask'))}>创建提问任务</button><button className="active">创建回答任务</button></div>
|
<div className="create-mode-tabs"><button onClick={() => window.dispatchEvent(new CustomEvent('switch-create-ask'))}>创建提问任务</button><button className="active">创建回答任务{answerDraftCount > 0 && <span className="create-mode-badge">{answerDraftCount > 99 ? '99+' : answerDraftCount}</span>}</button></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="create-guide"><b>ⓘ 回答截图审核</b><span>用户提交截图后会立即进入此审核队列。按「用户ID」分组核对识别的问题,勾选需要派发的问题,回答可重新生成或直接编辑,确认后生成任务。</span></div>
|
<div className="create-guide"><b>ⓘ 回答截图审核</b><span>用户提交截图后会立即进入此审核队列。按「用户ID」分组核对识别的问题,勾选需要派发的问题,回答可重新生成或直接编辑,确认后生成任务。</span></div>
|
||||||
{answerLink && <div className="answer-link-bar"><div><strong>我的回答任务专属链接</strong><span>长期固定,用户通过此链接提交的截图只会进入您的审核队列</span></div><input readOnly value={answerLink} onFocus={event => event.target.select()} /><button type="button" onClick={copyAnswerLink}>{linkCopied ? '已复制' : '复制链接'}</button></div>}
|
{answerLink && <div className="answer-link-bar"><div><strong>我的回答任务专属链接</strong><span>长期固定,用户通过此链接提交的截图只会进入您的审核队列</span></div><input readOnly value={answerLink} onFocus={event => event.target.select()} /><button type="button" onClick={copyAnswerLink}>{linkCopied ? '已复制' : '复制链接'}</button></div>}
|
||||||
|
|||||||
@ -65,7 +65,7 @@ function withoutTransientProductFields(value = {}) {
|
|||||||
return (await file.text()).slice(0, 20000);
|
return (await file.text()).slice(0, 20000);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateAskTasksPage({ api, librarySelection = [], onLibrarySelectionConsumed }) {
|
export default function CreateAskTasksPage({ api, librarySelection = [], onLibrarySelectionConsumed, answerDraftCount = 0 }) {
|
||||||
const cached = readPageCache("create-ask-task", {});
|
const cached = readPageCache("create-ask-task", {});
|
||||||
const [form, setForm] = useState(() => {
|
const [form, setForm] = useState(() => {
|
||||||
// Product identifiers and QR/token data are one-time publish inputs. Never
|
// Product identifiers and QR/token data are one-time publish inputs. Never
|
||||||
@ -662,6 +662,9 @@ export default function CreateAskTasksPage({ api, librarySelection = [], onLibra
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
创建回答任务
|
创建回答任务
|
||||||
|
{answerDraftCount > 0 && (
|
||||||
|
<span className="create-mode-badge">{answerDraftCount > 99 ? "99+" : answerDraftCount}</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -48,13 +48,21 @@ function TaskDetailLoading() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TaskDetail({ task, close, onConfirmRefund, onSaveRefundAmount, onReactivate, onCancel, actionBusy, api, hasNextRefund, onNextRefund, nextBusy, readOnly = false }) {
|
export function TaskDetail({ task, close, onConfirmRefund, onSaveRefundAmount, onReactivate, onCancel, actionBusy, api, user, hasNextRefund, onNextRefund, nextBusy, readOnly = false }) {
|
||||||
const [amount, setAmount] = useState("");
|
const [amount, setAmount] = useState("");
|
||||||
const [payerAccount, setPayerAccount] = useState("");
|
const payerAccountStorageKey = `task_board_last_payer_account_${user?.id || user?.username || "current"}`;
|
||||||
|
const rememberedPayerAccount = () => {
|
||||||
|
try {
|
||||||
|
return window.localStorage.getItem(payerAccountStorageKey) || "";
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [payerAccount, setPayerAccount] = useState(rememberedPayerAccount);
|
||||||
const [paymentAccounts, setPaymentAccounts] = useState([]);
|
const [paymentAccounts, setPaymentAccounts] = useState([]);
|
||||||
const [accountsBusy, setAccountsBusy] = useState(false);
|
const [accountsBusy, setAccountsBusy] = useState(false);
|
||||||
const [refundError, setRefundError] = useState("");
|
const [refundError, setRefundError] = useState("");
|
||||||
useEffect(() => { setPayerAccount(""); setRefundError(""); }, [task.board_key]);
|
useEffect(() => { setPayerAccount(rememberedPayerAccount()); setRefundError(""); }, [task.board_key, payerAccountStorageKey]);
|
||||||
const [amountBusy, setAmountBusy] = useState(false);
|
const [amountBusy, setAmountBusy] = useState(false);
|
||||||
const [amountError, setAmountError] = useState("");
|
const [amountError, setAmountError] = useState("");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -99,11 +107,23 @@ export function TaskDetail({ task, close, onConfirmRefund, onSaveRefundAmount, o
|
|||||||
setRefundError("");
|
setRefundError("");
|
||||||
setPaymentAccounts([]);
|
setPaymentAccounts([]);
|
||||||
api("/api/admin/refund-payment-accounts/available").then((result) => {
|
api("/api/admin/refund-payment-accounts/available").then((result) => {
|
||||||
if (!cancelled) setPaymentAccounts(result.data);
|
if (cancelled) return;
|
||||||
|
const accounts = result.data || [];
|
||||||
|
const remembered = rememberedPayerAccount();
|
||||||
|
const isAvailable = accounts.some((account) => String(account.id) === remembered);
|
||||||
|
setPaymentAccounts(accounts);
|
||||||
|
setPayerAccount(isAvailable ? remembered : "");
|
||||||
|
if (remembered && !isAvailable) {
|
||||||
|
try { window.localStorage.removeItem(payerAccountStorageKey); } catch { /* localStorage may be unavailable */ }
|
||||||
|
}
|
||||||
}).catch((error) => { if (!cancelled) setRefundError(error.message); })
|
}).catch((error) => { if (!cancelled) setRefundError(error.message); })
|
||||||
.finally(() => { if (!cancelled) setAccountsBusy(false); });
|
.finally(() => { if (!cancelled) setAccountsBusy(false); });
|
||||||
return () => { cancelled = true; };
|
return () => { cancelled = true; };
|
||||||
}, [refundOpen, api]);
|
}, [refundOpen, api, payerAccountStorageKey]);
|
||||||
|
function selectPayerAccount(value) {
|
||||||
|
setPayerAccount(value);
|
||||||
|
setRefundError("");
|
||||||
|
}
|
||||||
async function confirmRefund() {
|
async function confirmRefund() {
|
||||||
if (!validAmount) return setRefundError("请输入大于0且最多两位小数的金额");
|
if (!validAmount) return setRefundError("请输入大于0且最多两位小数的金额");
|
||||||
if (!payerAccount) return setRefundError("请选择付款账户");
|
if (!payerAccount) return setRefundError("请选择付款账户");
|
||||||
@ -111,6 +131,7 @@ export function TaskDetail({ task, close, onConfirmRefund, onSaveRefundAmount, o
|
|||||||
setRefundError("");
|
setRefundError("");
|
||||||
try {
|
try {
|
||||||
await onConfirmRefund(task, amount, payerAccount);
|
await onConfirmRefund(task, amount, payerAccount);
|
||||||
|
try { window.localStorage.setItem(payerAccountStorageKey, payerAccount); } catch { /* storage is optional */ }
|
||||||
setRefundOpen(false);
|
setRefundOpen(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setRefundError(error.message);
|
setRefundError(error.message);
|
||||||
@ -366,7 +387,7 @@ export function TaskDetail({ task, close, onConfirmRefund, onSaveRefundAmount, o
|
|||||||
<small>默认按每条问答 0.50 元计算;支付宝单笔转账最低 0.10 元。</small>
|
<small>默认按每条问答 0.50 元计算;支付宝单笔转账最低 0.10 元。</small>
|
||||||
</label>
|
</label>
|
||||||
<label>付款账户
|
<label>付款账户
|
||||||
<select value={payerAccount} disabled={accountsBusy} onChange={(event) => setPayerAccount(event.target.value)}>
|
<select value={payerAccount} disabled={accountsBusy} onChange={(event) => selectPayerAccount(event.target.value)}>
|
||||||
<option value="">{accountsBusy ? "正在加载账户…" : "请选择付款账户"}</option>
|
<option value="">{accountsBusy ? "正在加载账户…" : "请选择付款账户"}</option>
|
||||||
{paymentAccounts.map((account) => <option key={account.id} value={account.id}>{account.name}({account.alipay_account})</option>)}
|
{paymentAccounts.map((account) => <option key={account.id} value={account.id}>{account.name}({account.alipay_account})</option>)}
|
||||||
</select>
|
</select>
|
||||||
@ -869,6 +890,7 @@ export default function TaskBoardPage({ api, user }) {
|
|||||||
onCancel={cancel}
|
onCancel={cancel}
|
||||||
actionBusy={busy === detail.id}
|
actionBusy={busy === detail.id}
|
||||||
api={api}
|
api={api}
|
||||||
|
user={user}
|
||||||
|
|
||||||
hasNextRefund={Boolean(nextRefundTask)}
|
hasNextRefund={Boolean(nextRefundTask)}
|
||||||
nextBusy={Boolean(detailLoading)}
|
nextBusy={Boolean(detailLoading)}
|
||||||
|
|||||||
@ -2039,11 +2039,29 @@
|
|||||||
background: #e7edf6;
|
background: #e7edf6;
|
||||||
}
|
}
|
||||||
.create-mode-tabs button {
|
.create-mode-tabs button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
padding: 8px 14px;
|
padding: 8px 14px;
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #506783;
|
color: #506783;
|
||||||
}
|
}
|
||||||
|
.create-mode-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #e53935;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
box-shadow: 0 1px 3px rgba(198, 40, 40, 0.28);
|
||||||
|
}
|
||||||
.create-mode-tabs button.active {
|
.create-mode-tabs button.active {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: #173258;
|
color: #173258;
|
||||||
|
|||||||
@ -379,9 +379,11 @@ const ADMIN_ITEMS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function Sidebar({ user, active, setActive, reviewBadge }) {
|
function Sidebar({ user, active, setActive, reviewBadge, createBadge }) {
|
||||||
const baseItems = BASE_ITEMS.map((item) =>
|
const baseItems = BASE_ITEMS.map((item) =>
|
||||||
item.id === "review" ? { ...item, badge: reviewBadge } : item,
|
item.id === "review" ? { ...item, badge: reviewBadge }
|
||||||
|
: item.id === "create" ? { ...item, badge: createBadge }
|
||||||
|
: item,
|
||||||
);
|
);
|
||||||
const items =
|
const items =
|
||||||
user.role_code === "super_admin"
|
user.role_code === "super_admin"
|
||||||
@ -4613,6 +4615,7 @@ function Dashboard({ user, onLogout }) {
|
|||||||
const [active, setActive] = useState("assistant");
|
const [active, setActive] = useState("assistant");
|
||||||
const [createMode, setCreateMode] = useState("ask");
|
const [createMode, setCreateMode] = useState("ask");
|
||||||
const [reviewBadge, setReviewBadge] = useState(0);
|
const [reviewBadge, setReviewBadge] = useState(0);
|
||||||
|
const [createBadge, setCreateBadge] = useState(0);
|
||||||
const [librarySelection, setLibrarySelection] = useState([]);
|
const [librarySelection, setLibrarySelection] = useState([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api("/api/admin/usage/enter", { method: "POST" }).catch(() => {});
|
api("/api/admin/usage/enter", { method: "POST" }).catch(() => {});
|
||||||
@ -4621,6 +4624,7 @@ function Dashboard({ user, onLogout }) {
|
|||||||
try {
|
try {
|
||||||
const result = await api("/api/admin/review-pending-count");
|
const result = await api("/api/admin/review-pending-count");
|
||||||
setReviewBadge(result.data.total || 0);
|
setReviewBadge(result.data.total || 0);
|
||||||
|
setCreateBadge(result.data.answer_drafts || 0);
|
||||||
} catch {
|
} catch {
|
||||||
// Keep navigation usable if the counter request temporarily fails.
|
// Keep navigation usable if the counter request temporarily fails.
|
||||||
}
|
}
|
||||||
@ -4660,6 +4664,7 @@ function Dashboard({ user, onLogout }) {
|
|||||||
window.addEventListener("switch-create-answer", handler);
|
window.addEventListener("switch-create-answer", handler);
|
||||||
window.addEventListener("switch-create-ask", handler);
|
window.addEventListener("switch-create-ask", handler);
|
||||||
window.addEventListener("review-count-changed", loadReviewBadge);
|
window.addEventListener("review-count-changed", loadReviewBadge);
|
||||||
|
window.addEventListener("answer-draft-count-changed", loadReviewBadge);
|
||||||
loadReviewBadge();
|
loadReviewBadge();
|
||||||
const reviewCountTimer = window.setInterval(loadReviewBadge, 30000);
|
const reviewCountTimer = window.setInterval(loadReviewBadge, 30000);
|
||||||
return () => {
|
return () => {
|
||||||
@ -4670,6 +4675,7 @@ function Dashboard({ user, onLogout }) {
|
|||||||
window.removeEventListener("switch-create-answer", handler);
|
window.removeEventListener("switch-create-answer", handler);
|
||||||
window.removeEventListener("switch-create-ask", handler);
|
window.removeEventListener("switch-create-ask", handler);
|
||||||
window.removeEventListener("review-count-changed", loadReviewBadge);
|
window.removeEventListener("review-count-changed", loadReviewBadge);
|
||||||
|
window.removeEventListener("answer-draft-count-changed", loadReviewBadge);
|
||||||
window.clearInterval(reviewCountTimer);
|
window.clearInterval(reviewCountTimer);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@ -4689,6 +4695,7 @@ function Dashboard({ user, onLogout }) {
|
|||||||
active={active}
|
active={active}
|
||||||
setActive={selectPage}
|
setActive={selectPage}
|
||||||
reviewBadge={reviewBadge}
|
reviewBadge={reviewBadge}
|
||||||
|
createBadge={createBadge}
|
||||||
/>
|
/>
|
||||||
<section className="main-shell">
|
<section className="main-shell">
|
||||||
<Topbar user={user} onLogout={onLogout} />
|
<Topbar user={user} onLogout={onLogout} />
|
||||||
@ -4697,9 +4704,9 @@ function Dashboard({ user, onLogout }) {
|
|||||||
>
|
>
|
||||||
{active === "create" ? (
|
{active === "create" ? (
|
||||||
createMode === "ask" ? (
|
createMode === "ask" ? (
|
||||||
<CreateAskTasksPage api={api} librarySelection={librarySelection} onLibrarySelectionConsumed={() => setLibrarySelection([])} />
|
<CreateAskTasksPage api={api} librarySelection={librarySelection} onLibrarySelectionConsumed={() => setLibrarySelection([])} answerDraftCount={createBadge} />
|
||||||
) : (
|
) : (
|
||||||
<CreateAnswerTasksPageV2 api={api} user={user} />
|
<CreateAnswerTasksPageV2 api={api} user={user} answerDraftCount={createBadge} />
|
||||||
)
|
)
|
||||||
) : active === "board" ? (
|
) : active === "board" ? (
|
||||||
<TaskBoardPage api={api} user={user} />
|
<TaskBoardPage api={api} user={user} />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user