两个都是"看着不对劲"的问题: 1. 侧栏把二级分类全堆在一级分类后面(按 depth 排序的副作用), 「技术」和它的上级「我的备忘」隔着十几行,看着像一棵假树。 后端改成按树的先序返回(父分类后紧跟它的子分类,兄弟仍按 sort_order、name), 前端照 depth 缩进就是个真树;二级以下再加一个「·」标记,小屏也分得清。 2. 点开已发布的知识只有「归档」,没有改的入口——因为后端原来硬性拒绝 修改已发布内容(7003)。但"发布"是给团队看的,写错了总要能改: 现在已发布的可以直接 PATCH,改完立刻重建检索切片(chunk_count 当场更新), 状态和版本号照常推进;归档的仍然要先「恢复成草稿」再改。 Web 与安卓同步:抽屉/详情里已发布也显示「编辑」,安卓分类标签区不再锁。 验证:pytest 95 passed(新增"分类按树返回"用例、发布后可改用例)、 ruff 全绿、tsc 0、vite build ✓;`knowledge-notebook.mjs` 17 项、 `knowledge.mjs` 9 项 e2e 全绿。安卓仍未编译(JDK 25 / Gradle 8.7 不兼容)。
This commit is contained in:
+6
-2
@@ -396,7 +396,11 @@ Web 端未读数由 `AppShell` 每 30 秒轮询一次;在通知页标已读后
|
|||||||
|
|
||||||
必须带 `version`(乐观锁,冲突 `4009`)。可改 `title`/`body_md`/`summary`/`doc_type`/`tag_names`/`category_id`;
|
必须带 `version`(乐观锁,冲突 `4009`)。可改 `title`/`body_md`/`summary`/`doc_type`/`tag_names`/`category_id`;
|
||||||
传 `clear_category: true` 表示移到未分类。
|
传 `clear_category: true` 表示移到未分类。
|
||||||
**已发布的不许直接改**(`7003`,先撤回审议或归档),避免"发布后被悄悄改写"。
|
|
||||||
|
**已发布的可以直接改**:改的是"团队现在看到的这一版",改完立刻重建检索切片(`chunk_count` 当场更新),
|
||||||
|
避免"正文改了、搜出来还是旧句子"。版本号照旧 `+1`,历史仍能靠审计追。
|
||||||
|
**已归档的不许直接改**(`7003`,`allowed_actions: ["restore"]`):先恢复成草稿再改,
|
||||||
|
免得改完还挂着"现役"的样子。
|
||||||
|
|
||||||
### 5.5 知识分类 `/knowledge/categories`
|
### 5.5 知识分类 `/knowledge/categories`
|
||||||
|
|
||||||
@@ -404,7 +408,7 @@ Web 端未读数由 `AppShell` 每 30 秒轮询一次;在通知页标已读后
|
|||||||
|
|
||||||
| 方法 | 路径 | 说明 |
|
| 方法 | 路径 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| GET | `/knowledge/categories` | 扁平列表(按 depth/sort_order 排好),每条带 `doc_count`、`path`、`full_path`;另有 `max_depth=3` |
|
| GET | `/knowledge/categories` | 扁平列表,但按**树的先序**排好(父分类后紧跟它的子分类,兄弟按 `sort_order`、`name`),前端照 `depth` 缩进即可;每条带 `doc_count`、`path`、`full_path`;另有 `max_depth=3` |
|
||||||
| POST | `/knowledge/categories` | `{name, parent_id?, sort_order?}` |
|
| POST | `/knowledge/categories` | `{name, parent_id?, sort_order?}` |
|
||||||
| PATCH | `/knowledge/categories/{id}` | `{name?, parent_id?, move?, sort_order?, version}` |
|
| PATCH | `/knowledge/categories/{id}` | `{name?, parent_id?, move?, sort_order?, version}` |
|
||||||
| DELETE | `/knowledge/categories/{id}?force=false` | 有子分类不让删;有知识要 `force=true`,删后知识变未分类 |
|
| DELETE | `/knowledge/categories/{id}?force=false` | 有子分类不让删;有知识要 `force=true`,删后知识变未分类 |
|
||||||
|
|||||||
@@ -291,10 +291,10 @@
|
|||||||
| 3.8.4 | 索引生命周期:发布自动建、改正文需 `reindex`、换模型整体重建 | 0.5 | [x] `POST /knowledge/{id}/reindex` |
|
| 3.8.4 | 索引生命周期:发布自动建、改正文需 `reindex`、换模型整体重建 | 0.5 | [x] `POST /knowledge/{id}/reindex` |
|
||||||
| 3.9 | 安卓知识列表 | 1.0 | [x] 只读列表 + 详情动作(编辑正文走安卓编辑器) |
|
| 3.9 | 安卓知识列表 | 1.0 | [x] 只读列表 + 详情动作(编辑正文走安卓编辑器) |
|
||||||
| 3.10 | 文档与 e2e | 1.0 | [x] `knowledge.mjs` 7 项 + `retrieval.mjs` 6 项 |
|
| 3.10 | 文档与 e2e | 1.0 | [x] `knowledge.mjs` 7 项 + `retrieval.mjs` 6 项 |
|
||||||
| 3.11 | 知识分类:工作区三级小树(`knowledge_category` + 迁移 0007 + RLS),知识可归一类,列表按分类子树 / 未分类 / 标签筛选 | 1.0 | [x] 增删改移 + 重名 `7008`、超三级 `7007`、删分类要 `force` |
|
| 3.11 | 知识分类:工作区三级小树(`knowledge_category` + 迁移 0007 + RLS),知识可归一类,列表按分类子树 / 未分类 / 标签筛选 | 1.0 | [x] 增删改移 + 重名 `7008`、超三级 `7007`、删分类要 `force`;列表按树的**先序**返回(父分类后紧跟子分类),侧栏照 `depth` 缩进就是一棵树 |
|
||||||
| 3.12 | 备忘录形态:新建知识一次写全(标题/类型/分类/标签/正文),不再是"填个标题就完成";抽屉加宽 + 可整屏 | 1.0 | [x] Web `KnowledgeComposer` + 安卓新建后直进编辑器;正文就是详情同款富文本编辑器(Vditor / Compose 编辑器);`Sheet` 增 `size="md/lg/xl"` 与展开按钮;截图 `2.Design/screens/knowledge-composer.png`、`knowledge-categories.png` |
|
| 3.12 | 备忘录形态:新建知识一次写全(标题/类型/分类/标签/正文),不再是"填个标题就完成";抽屉加宽 + 可整屏 | 1.0 | [x] Web `KnowledgeComposer` + 安卓新建后直进编辑器;正文就是详情同款富文本编辑器(Vditor / Compose 编辑器);`Sheet` 增 `size="md/lg/xl"` 与展开按钮;截图 `2.Design/screens/knowledge-composer.png`、`knowledge-categories.png` |
|
||||||
| 3.13 | 知识能挂附件:新建面板里直接上传(懒建草稿拿到条目 id 再传),详情里能看、能下载/打开 | 1.0 | [x] Web `AttachmentPanel` + `RichTextEditor.ensureItemId`(贴图/传附件前自动存草稿);安卓详情页附件列表 + `OpenDocument` 上传 + 浏览器打开;附件仍落在 `item` 上(`ALLOWED_KINDS` 含 knowledge) |
|
| 3.13 | 知识能挂附件:新建面板里直接上传(懒建草稿拿到条目 id 再传),详情里能看、能下载/打开 | 1.0 | [x] Web `AttachmentPanel` + `RichTextEditor.ensureItemId`(贴图/传附件前自动存草稿);安卓详情页附件列表 + `OpenDocument` 上传 + 浏览器打开;附件仍落在 `item` 上(`ALLOWED_KINDS` 含 knowledge) |
|
||||||
| 3.14 | 点开一条知识默认是「看」:Markdown 渲染成正文样式;要改再点「编辑」进编辑器,取消即丢弃未保存改动 | 1.0 | [x] `KnowledgeDrawer` 分 `editing` 两态(只读用 `Markdown` + `prose-dadao`,编辑才挂 `RichTextEditor`);e2e 断言默认 `.vditor-ir` 为 0;截图 `2.Design/screens/knowledge-read-view.png` |
|
| 3.14 | 点开一条知识默认是「看」:Markdown 渲染成正文样式;要改再点「编辑」进编辑器,取消即丢弃未保存改动 | 1.0 | [x] `KnowledgeDrawer` 分 `editing` 两态(只读用 `Markdown` + `prose-dadao`,编辑才挂 `RichTextEditor`);**已发布也能编辑**(PATCH 后自动重建检索切片),归档的要先「恢复成草稿」;e2e 断言默认 `.vditor-ir` 为 0 且已发布也有「编辑」;截图 `2.Design/screens/knowledge-read-view.png` |
|
||||||
|
|
||||||
### M3 完成定义(DOD)
|
### M3 完成定义(DOD)
|
||||||
- [x] 一件事办完 → 自动出现一份草稿 → 核对 → 发布,全程不用手动复制粘贴
|
- [x] 一件事办完 → 自动出现一份草稿 → 核对 → 发布,全程不用手动复制粘贴
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 137 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 112 KiB |
@@ -618,8 +618,8 @@ private fun KnowledgeDetailDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 已发布的内容不能直接改,分类/标签也跟着锁住,避免"发布后又悄悄换归属"
|
// 已发布的也能改(后端改完会重建检索切片);归档的先恢复成草稿再动,免得"收起来的"被悄悄改写
|
||||||
if (action != "published") {
|
if (action != "archived") {
|
||||||
Spacer(Modifier.height(14.dp))
|
Spacer(Modifier.height(14.dp))
|
||||||
SectionLabel("分类")
|
SectionLabel("分类")
|
||||||
Row(
|
Row(
|
||||||
@@ -675,14 +675,14 @@ private fun KnowledgeDetailDialog(
|
|||||||
title = doc.title,
|
title = doc.title,
|
||||||
body = doc.bodyMd,
|
body = doc.bodyMd,
|
||||||
version = version,
|
version = version,
|
||||||
titleEnabled = action == "draft",
|
titleEnabled = action != "archived",
|
||||||
knowledge = true,
|
knowledge = true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
) { Text("编辑正文") }
|
) { Text("编辑正文") }
|
||||||
if (action != "published") {
|
if (action != "archived") {
|
||||||
TextButton(
|
TextButton(
|
||||||
enabled = !savingMeta,
|
enabled = !savingMeta,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|||||||
@@ -181,6 +181,31 @@ def _category_payload(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _ordered_categories(
|
||||||
|
by_id: dict[uuid.UUID, KnowledgeCategory],
|
||||||
|
) -> list[KnowledgeCategory]:
|
||||||
|
"""深度优先:父分类在前,它的子分类紧跟其后。
|
||||||
|
|
||||||
|
早先按 `(depth, sort_order, name)` 排,二级分类会全部堆在一级分类后面,
|
||||||
|
前端照着 depth 缩进就成了一棵"假树"——「技术」和它的上级「我的备忘」隔着十几行。
|
||||||
|
"""
|
||||||
|
children: dict[uuid.UUID | None, list[KnowledgeCategory]] = {}
|
||||||
|
for cat in by_id.values():
|
||||||
|
children.setdefault(cat.parent_id, []).append(cat)
|
||||||
|
for group in children.values():
|
||||||
|
group.sort(key=lambda c: (c.sort_order, c.name))
|
||||||
|
|
||||||
|
ordered: list[KnowledgeCategory] = []
|
||||||
|
|
||||||
|
def walk(parent_id: uuid.UUID | None) -> None:
|
||||||
|
for cat in children.get(parent_id, []):
|
||||||
|
ordered.append(cat)
|
||||||
|
walk(cat.id)
|
||||||
|
|
||||||
|
walk(None)
|
||||||
|
return ordered
|
||||||
|
|
||||||
|
|
||||||
def _subtree_ids(root_id: uuid.UUID, by_id: dict[uuid.UUID, KnowledgeCategory]) -> list[uuid.UUID]:
|
def _subtree_ids(root_id: uuid.UUID, by_id: dict[uuid.UUID, KnowledgeCategory]) -> list[uuid.UUID]:
|
||||||
children: dict[uuid.UUID, list[uuid.UUID]] = {}
|
children: dict[uuid.UUID, list[uuid.UUID]] = {}
|
||||||
for cat in by_id.values():
|
for cat in by_id.values():
|
||||||
@@ -358,7 +383,7 @@ async def list_categories(ctx: MemberDep, session: SessionDep) -> dict:
|
|||||||
).all()
|
).all()
|
||||||
)
|
)
|
||||||
items = []
|
items = []
|
||||||
for cat in sorted(by_id.values(), key=lambda c: (c.depth, c.sort_order, c.name)):
|
for cat in _ordered_categories(by_id):
|
||||||
data = _category_payload(cat, by_id)
|
data = _category_payload(cat, by_id)
|
||||||
data["doc_count"] = counts.get(cat.id, 0)
|
data["doc_count"] = counts.get(cat.id, 0)
|
||||||
items.append(data)
|
items.append(data)
|
||||||
@@ -636,11 +661,12 @@ async def patch_knowledge(
|
|||||||
) -> dict:
|
) -> dict:
|
||||||
ws_id = ctx.ws_id
|
ws_id = ctx.ws_id
|
||||||
item, doc = await _load_doc(session, ws_id, item_id)
|
item, doc = await _load_doc(session, ws_id, item_id)
|
||||||
if item.status == "published":
|
if item.status == "archived":
|
||||||
|
# 已归档是先收起来,不是不能改:要改先恢复成草稿,免得改完"发布着"的样子被人当成现役
|
||||||
raise AppError(
|
raise AppError(
|
||||||
ErrorCode.KNOWLEDGE_STATUS_INVALID,
|
ErrorCode.KNOWLEDGE_STATUS_INVALID,
|
||||||
"已发布的知识不能直接改内容,先撤回成草稿",
|
"已归档的知识不能直接改内容,先恢复成草稿",
|
||||||
details={"from": item.status, "allowed_actions": ["archive"]},
|
details={"from": item.status, "allowed_actions": ["restore"]},
|
||||||
)
|
)
|
||||||
if body.doc_type is not None and body.doc_type not in DOC_TYPES:
|
if body.doc_type is not None and body.doc_type not in DOC_TYPES:
|
||||||
raise AppError(ErrorCode.VALIDATION_ERROR, f"未知的知识类型:{body.doc_type}")
|
raise AppError(ErrorCode.VALIDATION_ERROR, f"未知的知识类型:{body.doc_type}")
|
||||||
@@ -689,6 +715,11 @@ async def patch_knowledge(
|
|||||||
if body.tag_names is not None:
|
if body.tag_names is not None:
|
||||||
await _set_tags_for(session, ws_id, item, body.tag_names)
|
await _set_tags_for(session, ws_id, item, body.tag_names)
|
||||||
await session.flush()
|
await session.flush()
|
||||||
|
if (body.title is not None or body.body_md is not None) and item.status == "published":
|
||||||
|
# 已发布的知识改完立刻重建切片:检索只认 published,
|
||||||
|
# 不重建就会出现"正文改了、搜出来还是旧句子"。
|
||||||
|
await reindex_document(session, ws_id, item.id)
|
||||||
|
await session.flush()
|
||||||
return ok(await _knowledge_out(session, ws_id, item, doc))
|
return ok(await _knowledge_out(session, ws_id, item, doc))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -200,13 +200,18 @@ async def test_publish_requires_admin(client: AsyncClient) -> None:
|
|||||||
assert published["status"] == "published"
|
assert published["status"] == "published"
|
||||||
assert published["knowledge"]["published_at"] is not None
|
assert published["knowledge"]["published_at"] is not None
|
||||||
|
|
||||||
# 已发布的内容不许直接改,先撤回
|
# 已发布的能直接改正文:改完后端自动重建切片,状态仍是已发布
|
||||||
blocked = await client.patch(
|
edited = unwrap(
|
||||||
f"{V1}/workspaces/{ws['id']}/knowledge/{doc['id']}",
|
await client.patch(
|
||||||
json={"title": "值班手册 v2", "version": published["version"]},
|
f"{V1}/workspaces/{ws['id']}/knowledge/{doc['id']}",
|
||||||
headers=owner["headers"],
|
json={"title": "值班手册 v2", "version": published["version"]},
|
||||||
|
headers=owner["headers"],
|
||||||
|
)
|
||||||
)
|
)
|
||||||
assert blocked.json()["code"] == 7003
|
assert edited["title"] == "值班手册 v2"
|
||||||
|
assert edited["knowledge"]["status_hint"] == "published"
|
||||||
|
assert edited["version"] == published["version"] + 1
|
||||||
|
published = edited
|
||||||
|
|
||||||
archived = unwrap(
|
archived = unwrap(
|
||||||
await client.post(
|
await client.post(
|
||||||
@@ -218,6 +223,14 @@ async def test_publish_requires_admin(client: AsyncClient) -> None:
|
|||||||
assert archived["status"] == "archived"
|
assert archived["status"] == "archived"
|
||||||
assert archived["knowledge"]["archived_at"] is not None
|
assert archived["knowledge"]["archived_at"] is not None
|
||||||
|
|
||||||
|
# 归档是先收起来;要改先恢复成草稿,免得改完还挂着"现役"的样子
|
||||||
|
blocked = await client.patch(
|
||||||
|
f"{V1}/workspaces/{ws['id']}/knowledge/{doc['id']}",
|
||||||
|
json={"title": "值班手册 v3", "version": archived["version"]},
|
||||||
|
headers=owner["headers"],
|
||||||
|
)
|
||||||
|
assert blocked.json()["code"] == 7003
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_illegal_action_reports_allowed(client: AsyncClient) -> None:
|
async def test_illegal_action_reports_allowed(client: AsyncClient) -> None:
|
||||||
|
|||||||
@@ -303,3 +303,29 @@ async def test_patch_knowledge_accepts_doc_type_and_bumps_version(client: AsyncC
|
|||||||
assert patched["body_md"] == "改过的正文"
|
assert patched["body_md"] == "改过的正文"
|
||||||
assert patched["version"] == doc["version"] + 1
|
assert patched["version"] == doc["version"] + 1
|
||||||
assert {t["name"] for t in patched["tags"]} == {"写法"}
|
assert {t["name"] for t in patched["tags"]} == {"写法"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_categories_come_back_as_a_tree(client: AsyncClient) -> None:
|
||||||
|
"""列表按「父在前、子紧跟」排:前端照着 depth 缩进才像一棵树。
|
||||||
|
|
||||||
|
之前按 (depth, sort_order) 排,二级分类全堆在一级后面,
|
||||||
|
侧栏看着像"所有一级在一起、所有二级在一起"。
|
||||||
|
"""
|
||||||
|
user, ws_id = await _ws(client)
|
||||||
|
tech = await _category(client, user, ws_id, "技术开发")
|
||||||
|
biz = await _category(client, user, ws_id, "餐饮业务")
|
||||||
|
child = await _category(client, user, ws_id, "技术", tech["id"])
|
||||||
|
await _category(client, user, ws_id, "部署", child["id"])
|
||||||
|
assert biz["depth"] == 1
|
||||||
|
|
||||||
|
items = unwrap(
|
||||||
|
await client.get(f"{V1}/workspaces/{ws_id}/knowledge/categories", headers=user["headers"])
|
||||||
|
)["items"]
|
||||||
|
assert [i["name"] for i in items] == ["技术开发", "技术", "部署", "餐饮业务"]
|
||||||
|
assert [i["full_path"] for i in items] == [
|
||||||
|
"技术开发",
|
||||||
|
"技术开发 / 技术",
|
||||||
|
"技术开发 / 技术 / 部署",
|
||||||
|
"餐饮业务",
|
||||||
|
]
|
||||||
|
|||||||
@@ -77,6 +77,15 @@ try {
|
|||||||
await page.getByRole("button", { name: /^部署/, exact: false }).first().waitFor();
|
await page.getByRole("button", { name: /^部署/, exact: false }).first().waitFor();
|
||||||
check("能在侧栏建出「研发 / 部署」两级分类", true);
|
check("能在侧栏建出「研发 / 部署」两级分类", true);
|
||||||
|
|
||||||
|
// 树要按「父在前、子紧跟」排:部署必须紧挨在研发后面,而不是堆在别的根分类后面
|
||||||
|
const navTexts = await page.locator("aside nav button").allInnerTexts();
|
||||||
|
const navNames = navTexts.map((t) => t.replace(/[·\d]/g, "").trim());
|
||||||
|
check(
|
||||||
|
"侧栏分类按树的顺序排(子紧跟在父后面)",
|
||||||
|
navNames.indexOf("部署") === navNames.indexOf("研发") + 1,
|
||||||
|
navNames.join(" / "),
|
||||||
|
);
|
||||||
|
|
||||||
// ② 记一条:一个面板里填完标题/类型/分类/标签/正文
|
// ② 记一条:一个面板里填完标题/类型/分类/标签/正文
|
||||||
await page.getByRole("button", { name: "记一条", exact: true }).click();
|
await page.getByRole("button", { name: "记一条", exact: true }).click();
|
||||||
await page.getByLabel("标题").fill("回滚前先停灰度");
|
await page.getByLabel("标题").fill("回滚前先停灰度");
|
||||||
|
|||||||
@@ -108,6 +108,13 @@ try {
|
|||||||
const published = await page.getByText(/发布于 /, { exact: false }).first().isVisible();
|
const published = await page.getByText(/发布于 /, { exact: false }).first().isVisible();
|
||||||
check("核对后可以发布", published);
|
check("核对后可以发布", published);
|
||||||
|
|
||||||
|
// 发布之后也得能改:点「编辑」回到编辑器,改完还是已发布
|
||||||
|
const editButton = page.getByRole("button", { name: "编辑", exact: true }).last();
|
||||||
|
check("已发布的知识也有「编辑」按钮", await editButton.isVisible());
|
||||||
|
await editButton.click();
|
||||||
|
await page.locator(".vditor-ir").first().waitFor({ timeout: 10_000 });
|
||||||
|
check("点「编辑」回到编辑器", (await page.locator(".vditor-ir").count()) > 0);
|
||||||
|
|
||||||
const again = await fetch(`${API}/workspaces/${ws.id}/knowledge`, {
|
const again = await fetch(`${API}/workspaces/${ws.id}/knowledge`, {
|
||||||
headers: { authorization: `Bearer ${token}` },
|
headers: { authorization: `Bearer ${token}` },
|
||||||
}).then((r) => r.json());
|
}).then((r) => r.json());
|
||||||
|
|||||||
@@ -538,7 +538,15 @@ function CategoryButton({
|
|||||||
: "text-ink-600 hover:bg-white/60 hover:text-ink-800"
|
: "text-ink-600 hover:bg-white/60 hover:text-ink-800"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{children}
|
<span className="flex min-w-0 items-center gap-1">
|
||||||
|
{/* 二级及以下带一个点,光靠缩进在小屏上不够分得清是子分类还是换了一棵树 */}
|
||||||
|
{indent > 0 ? (
|
||||||
|
<span aria-hidden className="text-ink-300">
|
||||||
|
·
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -908,7 +916,8 @@ function KnowledgeDrawer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const status = doc?.knowledge.status_hint;
|
const status = doc?.knowledge.status_hint;
|
||||||
const canEdit = status === "draft" || status === "in_review";
|
// 已发布的也能改:改完后端会自动重建检索切片。归档是先收起来,要改得先「恢复成草稿」。
|
||||||
|
const canEdit = status === "draft" || status === "in_review" || status === "published";
|
||||||
const actions: { key: string; label: string; show: boolean }[] = [
|
const actions: { key: string; label: string; show: boolean }[] = [
|
||||||
{ key: "submit_review", label: ACTION_LABEL.submit_review, show: status === "draft" },
|
{ key: "submit_review", label: ACTION_LABEL.submit_review, show: status === "draft" },
|
||||||
{ key: "withdraw", label: ACTION_LABEL.withdraw, show: status === "in_review" },
|
{ key: "withdraw", label: ACTION_LABEL.withdraw, show: status === "in_review" },
|
||||||
|
|||||||
Reference in New Issue
Block a user