diff --git a/src/apps/tasks/views/TasksView.vue b/src/apps/tasks/views/TasksView.vue index 9f8471a..aa8f1f3 100644 --- a/src/apps/tasks/views/TasksView.vue +++ b/src/apps/tasks/views/TasksView.vue @@ -20,7 +20,7 @@ type CurrentEmployee = { avatar?: string | null; }; -type MainTabKey = "incoming" | "transfer" | "review" | "outgoing" | "archive"; +type MainTabKey = "incoming" | "transfer" | "review" | "outgoing"; type DocTabKey = | "all" | "simple" @@ -40,6 +40,22 @@ const employeeLoading = ref(false); const refreshing = ref(false); const switchingList = ref(false); let suppressDocTabReload = false; +const mainTabCounts = ref>({ + incoming: 0, + transfer: 0, + review: 0, + outgoing: 0, +}); +const docTabCounts = ref>({ + all: 0, + simple: 0, + memo: 0, + bill: 0, + contract: 0, + contract_application: 0, + entry_letter: 0, + outgoing_letter: 0, +}); const { items: tasks, @@ -48,6 +64,7 @@ const { load: loadTasks, } = useModelApi(taskApi, { defaultListParams: { ordering: "-id" }, + autoLoad: false, loadErrorMessage: "Не удалось загрузить задачи", }); @@ -63,6 +80,7 @@ const { TaskTransferListParams >(taskTransferApi, { defaultListParams: { ordering: "-id" }, + autoLoad: false, loadErrorMessage: "Не удалось загрузить переносы срока", }); @@ -71,7 +89,6 @@ const mainTabs = [ { key: "transfer", label: "Перенос срока", icon: "underway-o" }, { key: "review", label: "Для проверки", icon: "eye-o" }, { key: "outgoing", label: "Исходящие", icon: "send-gift-o" }, - { key: "archive", label: "Архив", icon: "folder-o" }, ] as const; const docTabs = [ @@ -91,6 +108,7 @@ const visibleItems = computed(() => const activeLoading = computed(() => mainTab.value === "transfer" ? taskTransferLoading.value : taskLoading.value, ); +const showLoading = computed(() => activeLoading.value || refreshing.value); const showEmployeeLoading = computed( () => employeeLoading.value && @@ -101,6 +119,11 @@ const activeError = computed(() => mainTab.value === "transfer" ? taskTransferError.value : taskError.value, ); const showDocTabs = computed(() => mainTab.value !== "transfer"); +const visibleDocTabs = computed(() => + showDocTabs.value + ? docTabs.filter((tab) => (docTabCounts.value[tab.key] ?? 0) > 0) + : [], +); function personName( person: { short_name?: string | null; name?: string | null } | null, @@ -120,6 +143,10 @@ function taskTitle(task: { return task.text?.trim() || task.result?.trim() || `Задача #${task.id}`; } +function badgeValue(count: number) { + return count > 0 ? count : undefined; +} + function isTaskTransfer(item: Task | TaskTransfer): item is TaskTransfer { return "employee_from" in item; } @@ -144,19 +171,17 @@ function itemPath(item: Task | TaskTransfer) { return isTaskTransfer(item) ? `/tasks/${item.task.id}` : `/tasks/${item.id}`; } -function taskParams(): TaskListParams | TaskTransferListParams | null { - if (!employee.value) { - return null; - } - - const employeeId = employee.value.id; - - switch (mainTab.value) { +function buildTaskParams( + tab: MainTabKey, + doc: DocTabKey, + employeeId: number, +): TaskListParams | TaskTransferListParams | null { + switch (tab) { case "incoming": return { doer: employeeId, archive: false, - typ: docTab.value === "all" ? undefined : docTab.value, + typ: doc === "all" ? undefined : doc, q: "entry", incomplete: true, }; @@ -164,24 +189,50 @@ function taskParams(): TaskListParams | TaskTransferListParams | null { return { q: "to_approve", incomplete: true, - typ: docTab.value === "all" ? undefined : docTab.value, + archive: false, + typ: doc === "all" ? undefined : doc, }; case "outgoing": return { q: "outgoing", archive: false, incomplete: true, - typ: docTab.value === "all" ? undefined : docTab.value, + typ: doc === "all" ? undefined : doc, }; - case "archive": + case "transfer": return { - typ: docTab.value === "all" ? undefined : docTab.value, + employee_to: employeeId, + status: "A", }; default: return null; } } +function taskParams(): TaskListParams | TaskTransferListParams | null { + if (!employee.value) { + return null; + } + + return buildTaskParams(mainTab.value, docTab.value, employee.value.id); +} + +function taskCountParams(tab: MainTabKey): TaskListParams | TaskTransferListParams | null { + if (!employee.value) { + return null; + } + + return buildTaskParams(tab, "all", employee.value.id); +} + +function docCountParams(doc: DocTabKey): TaskListParams | null { + if (!employee.value || mainTab.value === "transfer") { + return null; + } + + return buildTaskParams(mainTab.value, doc, employee.value.id) as TaskListParams; +} + async function loadCurrentEmployee() { employeeLoading.value = true; employeeError.value = ""; @@ -217,10 +268,80 @@ async function reloadList() { await loadTasks(params as TaskListParams); } -async function reloadForTabChange() { +async function countTaskItems(params: TaskListParams | TaskTransferListParams | null) { + if (!params) { + return 0; + } + + if ("employee_to" in params) { + const response = await taskTransferApi.list({ ...params, limit: 1 }); + return response.count; + } + + const response = await taskApi.list({ ...params, limit: 1 }); + return response.count; +} + +async function reloadCounts() { + if (!employee.value) { + return; + } + + const mainCounts = await Promise.allSettled( + mainTabs.map(async (tab) => [tab.key, await countTaskItems(taskCountParams(tab.key))] as const), + ); + mainTabCounts.value = { + ...mainTabCounts.value, + ...Object.fromEntries( + mainCounts.flatMap((result) => + result.status === "fulfilled" ? [result.value] : [], + ), + ), + } as Record; + + if (mainTab.value === "transfer") { + docTabCounts.value = { + all: 0, + simple: 0, + memo: 0, + bill: 0, + contract: 0, + contract_application: 0, + entry_letter: 0, + outgoing_letter: 0, + }; + return; + } + + const docCounts = await Promise.allSettled( + docTabs.map(async (tab) => [tab.key, await countTaskItems(docCountParams(tab.key))] as const), + ); + docTabCounts.value = { + ...docTabCounts.value, + ...Object.fromEntries( + docCounts.flatMap((result) => + result.status === "fulfilled" ? [result.value] : [], + ), + ), + } as Record; + + const nextDocTab = visibleDocTabs.value[0]?.key ?? "all"; + if (docTab.value !== nextDocTab) { + suppressDocTabReload = true; + docTab.value = nextDocTab; + await nextTick(); + suppressDocTabReload = false; + } +} + +async function reloadForTabChange(options: { reloadCounts: boolean }) { switchingList.value = true; try { + if (options.reloadCounts) { + await reloadCounts(); + } + await reloadList(); } finally { switchingList.value = false; @@ -231,6 +352,7 @@ async function onRefresh() { refreshing.value = true; try { + await reloadCounts(); await reloadList(); } finally { refreshing.value = false; @@ -246,18 +368,18 @@ watch(mainTab, () => { }); } - void reloadForTabChange(); + void reloadForTabChange({ reloadCounts: true }); }); watch(docTab, () => { if (showDocTabs.value && !suppressDocTabReload) { - void reloadForTabChange(); + void reloadForTabChange({ reloadCounts: false }); } }); onMounted(async () => { await loadCurrentEmployee(); - await reloadList(); + await reloadForTabChange({ reloadCounts: true }); }); @@ -285,22 +407,24 @@ onMounted(async () => { :key="tab.key" :name="tab.key" :icon="tab.icon" + :badge="badgeValue(mainTabCounts[tab.key])" > {{ tab.label }} @@ -311,8 +435,8 @@ onMounted(async () => { @refresh="onRefresh" >