From 5155935defca591a92d2b42e9d566a2e4adb98ee Mon Sep 17 00:00:00 2001 From: RitvikSardana Date: Fri, 10 Jan 2025 16:03:29 +0530 Subject: [PATCH] feat: move to category and create category --- desk/src/components/ListViewBuilder.vue | 21 ++++- desk/src/components/frappe-ui/Link.vue | 2 +- .../knowledge-base/MoveToCategoryModal.vue | 36 +++++++++ .../pages/knowledge-base/KnowledgeBase.vue | 76 ++++++++++++++----- desk/src/stores/knowledgeBase.ts | 28 +++++-- helpdesk/api/knowledge_base.py | 15 ++++ 6 files changed, 151 insertions(+), 27 deletions(-) create mode 100644 desk/src/components/knowledge-base/MoveToCategoryModal.vue diff --git a/desk/src/components/ListViewBuilder.vue b/desk/src/components/ListViewBuilder.vue index 556f1adf3..2f0f61565 100644 --- a/desk/src/components/ListViewBuilder.vue +++ b/desk/src/components/ListViewBuilder.vue @@ -64,6 +64,13 @@ + + + @@ -103,8 +110,10 @@ import { ListRowItem, ListHeader, ListHeaderItem, + ListSelectBanner, Badge, FeatherIcon, + Dropdown, } from "frappe-ui"; import { @@ -114,7 +123,6 @@ import { Reload, } from "@/components/view-controls"; import { dayjs } from "@/dayjs"; -import FadedScrollableDiv from "./FadedScrollableDiv.vue"; import ListRows from "./ListRows.vue"; import { useScreenSize } from "@/composables/screen"; import EmptyState from "./EmptyState.vue"; @@ -134,6 +142,8 @@ interface P { statusMap?: Record; view?: View; groupByActions?: Array; + showSelectBanner?: boolean; + selectBannerActions?: Record; }; } @@ -177,6 +187,15 @@ const emptyState = computed(() => { return props.options?.emptyState || defaultEmptyState; }); +function selectBannerOptions(selections: Set) { + return props.options.selectBannerActions.map((action) => { + return { + ...action, + onClick: () => action.onClick(selections), + }; + }); +} + const list = createResource({ url: "helpdesk.api.doc.get_list_data", params: defaultParams, diff --git a/desk/src/components/frappe-ui/Link.vue b/desk/src/components/frappe-ui/Link.vue index 6fbd696e3..858b6ac4a 100644 --- a/desk/src/components/frappe-ui/Link.vue +++ b/desk/src/components/frappe-ui/Link.vue @@ -131,8 +131,8 @@ const options = createResource({ transform: (data) => { let allData = data.map((option) => { return { - label: option.value, value: option.value, + label: option?.label || option.value, }; }); // if (!props.hideMe && props.doctype == 'User') { diff --git a/desk/src/components/knowledge-base/MoveToCategoryModal.vue b/desk/src/components/knowledge-base/MoveToCategoryModal.vue new file mode 100644 index 000000000..f7424a5cf --- /dev/null +++ b/desk/src/components/knowledge-base/MoveToCategoryModal.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/desk/src/pages/knowledge-base/KnowledgeBase.vue b/desk/src/pages/knowledge-base/KnowledgeBase.vue index 5f595609f..a0145f7f5 100644 --- a/desk/src/pages/knowledge-base/KnowledgeBase.vue +++ b/desk/src/pages/knowledge-base/KnowledgeBase.vue @@ -21,11 +21,12 @@ /> + @@ -43,23 +44,28 @@ import { updateCategoryTitle, deleteCategory, newCategory, + moveToCategory, } from "@/stores/knowledgeBase"; import LayoutHeader from "@/components/LayoutHeader.vue"; import ListViewBuilder from "@/components/ListViewBuilder.vue"; import CategoryModal from "@/components/knowledge-base/CategoryModal.vue"; +import MoveToCategoryModal from "@/components/knowledge-base/MoveToCategoryModal.vue"; import { createToast } from "@/utils"; const router = useRouter(); -const showDialog = ref(false); const category = reactive({ title: "", id: "", }); const _title = ref(""); -const editTitle = ref(false); const listViewRef = ref(null); +const editTitle = ref(false); + +// modals state +const showCategoryModal = ref(false); +const moveToModal = ref(false); const headerOptions = [ { @@ -68,7 +74,7 @@ const headerOptions = [ onClick: () => { resetState(); editTitle.value = false; - showDialog.value = true; + showCategoryModal.value = true; }, }, { @@ -76,12 +82,6 @@ const headerOptions = [ icon: "file", onClick: () => { router.push({ name: "NewArticle" }); - // router.push({ - // name: "NewArticle", - // query: { - // category: "ABC", - // }, - // }); }, }, ]; @@ -106,7 +106,7 @@ const groupByActions = [ icon: "edit", onClick: (groupedRow) => { editTitle.value = true; - showDialog.value = true; + showCategoryModal.value = true; category.title = groupedRow.group.label; category.id = groupedRow.group.value; _title.value = groupedRow.group.label; @@ -120,17 +120,57 @@ const groupByActions = [ }, }, ]; +const listSelections = ref(new Set()); +const selectBannerActions = [ + { + label: "Move To", + icon: "corner-up-right", + onClick: (selections: Set) => { + listSelections.value = selections; + moveToModal.value = true; + }, + }, +]; + +function handleMoveToCategory(category: string) { + moveToCategory.submit( + { + category, + articles: Array.from(listSelections.value), + }, + { + onSuccess: () => { + listViewRef.value.reload(); + moveToModal.value = false; + createToast({ + title: "Articles moved successfully", + icon: "check", + iconClasses: "text-green-600", + }); + }, + } + ); +} function handleCategoryCreate() { - console.log("Create", category.title); newCategory.submit( { - category_name: category.title, + title: category.title, }, { - onSuccess: () => { + onSuccess: (data: any) => { listViewRef.value.reload(); - showDialog.value = false; + showCategoryModal.value = false; + router.push({ + name: "Article", + params: { + articleId: data.article, + }, + query: { + category: data.category, + title: category.title, + }, + }); createToast({ title: "Category Created Successfully", icon: "check", @@ -152,7 +192,7 @@ function handleCategoryCreate() { function handleCategoryUpdate() { // if same title do nothing if (category.title === _title.value) { - showDialog.value = false; + showCategoryModal.value = false; editTitle.value = false; return; } @@ -166,7 +206,7 @@ function handleCategoryUpdate() { { onSuccess: () => { listViewRef.value.reload(); - showDialog.value = false; + showCategoryModal.value = false; editTitle.value = false; createToast({ title: "Category Updated Successfully", @@ -249,6 +289,8 @@ const options = { }, }, groupByActions, + showSelectBanner: true, + selectBannerActions, }; usePageMeta(() => { diff --git a/desk/src/stores/knowledgeBase.ts b/desk/src/stores/knowledgeBase.ts index 201a5ae23..8856d285f 100644 --- a/desk/src/stores/knowledgeBase.ts +++ b/desk/src/stores/knowledgeBase.ts @@ -30,17 +30,14 @@ export const deleteRes = createResource({ // Category export const newCategory = createResource({ - url: "frappe.client.insert", - makeParams({ category_name }) { + url: "helpdesk.api.knowledge_base.create_category", + makeParams({ title }) { return { - doc: { - doctype: "HD Article Category", - category_name, - }, + title, }; }, - validate({ doc }) { - if (!doc.category_name) throw "Title is required"; + validate(title: string) { + if (!title) throw "Title is required"; }, }); @@ -62,3 +59,18 @@ export const deleteCategory = createResource({ if (!name) throw "Category is required"; }, }); + +export const moveToCategory = createResource({ + url: "helpdesk.api.knowledge_base.move_to_category", + makeParams({ category,articles }) { + return { + category, + articles + }; + }, + validate({ category,articles }) { + if (!category) throw "Category is required"; + if (!articles) throw "Articles are required"; + } + +}) \ No newline at end of file diff --git a/helpdesk/api/knowledge_base.py b/helpdesk/api/knowledge_base.py index 166a028bc..e814bfa09 100644 --- a/helpdesk/api/knowledge_base.py +++ b/helpdesk/api/knowledge_base.py @@ -32,6 +32,15 @@ def get_article(name: str): return article +@frappe.whitelist() +def create_category(title: str): + category = frappe.new_doc("HD Article Category", category_name=title).insert() + article = frappe.new_doc( + "HD Article", title="New Article", category=category.name + ).insert() + return {"article": article.name, "category": category.name} + + @frappe.whitelist() def delete_category(name: str): try: @@ -42,3 +51,9 @@ def delete_category(name: str): frappe.delete_doc("HD Article Category", name) except Exception as e: frappe.db.rollback() + + +@frappe.whitelist() +def move_to_category(category, articles): + for article in articles: + frappe.db.set_value("HD Article", article, "category", category)