Skip to content

Commit 931e6b6

Browse files
Merge pull request #7453 from christianbeeznest/fixes-updates246
Document: Show documents table loading spinner immediately
2 parents b3f0ed5 + 9b6fe9f commit 931e6b6

1 file changed

Lines changed: 70 additions & 24 deletions

File tree

assets/vue/views/documents/DocumentsList.vue

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
v-model:filters="filters"
113113
v-model:selected-items="selectedItems"
114114
:global-filter-fields="['resourceNode.title', 'resourceNode.updatedAt']"
115-
:is-loading="isLoading"
115+
:is-loading="tableIsLoading"
116116
v-model:rows="options.itemsPerPage"
117117
:total-items="totalItems"
118118
:values="items"
@@ -672,6 +672,44 @@ const defaultCertificateId = ref(null)
672672
673673
const isCurrentTeacher = computed(() => securityStore.isCurrentTeacher && !platformConfigStore.isStudentViewActive)
674674
675+
/**
676+
* Local loading flag to show the table spinner immediately.
677+
* This prevents the "empty table" impression while the store is still preparing the request.
678+
*/
679+
const tableLoading = ref(true)
680+
const hasRequestedList = ref(false)
681+
682+
const tableIsLoading = computed(() => {
683+
return tableLoading.value || isLoading.value
684+
})
685+
686+
function triggerTableLoad() {
687+
// Make sure spinner appears immediately on any list refresh.
688+
hasRequestedList.value = true
689+
tableLoading.value = true
690+
onUpdateOptions(options.value)
691+
}
692+
693+
// Keep local loading in sync with the store loading state.
694+
watch(isLoading, (val) => {
695+
if (val) {
696+
tableLoading.value = true
697+
return
698+
}
699+
// If we already triggered at least one load and the store is not loading anymore, hide spinner.
700+
if (hasRequestedList.value) {
701+
tableLoading.value = false
702+
}
703+
})
704+
705+
// if store loading toggles late (or not at all), stop local loading when data changes.
706+
watch([items, totalItems], () => {
707+
if (!hasRequestedList.value) return
708+
if (!isLoading.value) {
709+
tableLoading.value = false
710+
}
711+
})
712+
675713
function resolveDefaultRows(total = 0) {
676714
const raw = platformConfigStore.getSetting("display.table_default_row", 10)
677715
const def = Number(raw)
@@ -701,6 +739,7 @@ const selectedReplaceFile = ref(null)
701739
const documentToReplace = ref(null)
702740
703741
onMounted(async () => {
742+
tableLoading.value = true
704743
isAllowedToEdit.value = await checkIsAllowedToEdit(true, true, true)
705744
filters.value.loadNode = 1
706745
filters.value.filetype = ["file", "folder", "video"]
@@ -714,18 +753,19 @@ onMounted(async () => {
714753
715754
await store.dispatch("resourcenode/findResourceNode", { id: `/api/resource_nodes/${nodeId}` })
716755
717-
await loadDefaultCertificate()
718-
await loadAllFolders()
719-
options.value.itemsPerPage = resolveDefaultRows(totalItems.value || 0)
720-
onUpdateOptions(options.value)
756+
options.value.itemsPerPage = resolveDefaultRows(0)
757+
triggerTableLoad()
758+
void loadDefaultCertificate()
759+
void loadAllFolders()
721760
722-
try {
723-
await courseSettingsStore.loadCourseSettings(cid, sid)
724-
} catch (e) {
725-
console.error("[AI] loadCourseSettings failed:", e)
726-
}
761+
void courseSettingsStore
762+
.loadCourseSettings(cid, sid)
763+
.catch((e) => console.error("[AI] loadCourseSettings failed:", e))
764+
.finally(() => {
765+
void loadAiCapabilities()
766+
})
727767
728-
await loadAiCapabilities()
768+
void loadAiCapabilities()
729769
consumeAiSavedToast()
730770
})
731771
@@ -747,6 +787,7 @@ watch(totalItems, (n) => {
747787
const def = Number(platformConfigStore.getSetting("display.table_default_row", 10))
748788
if (def === 0 && n) {
749789
options.value.itemsPerPage = n
790+
tableLoading.value = true
750791
onUpdateOptions(options.value)
751792
}
752793
})
@@ -760,7 +801,7 @@ watch(
760801
store.dispatch("resourcenode/findResourceNode", finderParams)
761802
762803
if ("DocumentsList" === route.name) {
763-
onUpdateOptions(options.value)
804+
triggerTableLoad()
764805
}
765806
},
766807
)
@@ -826,9 +867,10 @@ function createNewFolder() {
826867
},
827868
])
828869
870+
tableLoading.value = true
829871
store.dispatch("documents/createWithFormData", item.value).then(() => {
830872
notification.showSuccessNotification(t("Saved"))
831-
onUpdateOptions(options.value)
873+
triggerTableLoad()
832874
})
833875
}
834876
isNewFolderDialogVisible.value = false
@@ -868,13 +910,14 @@ async function forceDeleteItem() {
868910
try {
869911
const docIdsToDelete = [...new Set(lpListWarning.value.map((lp) => lp.documentId))]
870912
913+
tableLoading.value = true
871914
await Promise.all(docIdsToDelete.map((iid) => axios.delete(`/api/documents/${iid}`)))
872915
873916
notification.showSuccessNotification(t("Documents deleted"))
874917
isDeleteWarningLpDialogVisible.value = false
875918
item.value = {}
876919
unselectAll()
877-
onUpdateOptions(options.value)
920+
triggerTableLoad()
878921
} catch (error) {
879922
console.error("[Documents] Error deleting documents forcibly:", error)
880923
notification.showErrorNotification(t("Error deleting document(s)."))
@@ -927,6 +970,7 @@ async function deleteMultipleItems() {
927970
const itemsWithoutLp = []
928971
const documentsWithLpMap = {}
929972
973+
tableLoading.value = true
930974
for (const item of selectedItems.value) {
931975
try {
932976
const response = await axios.get(`/api/documents/${item.iid}/lp-usage`)
@@ -974,17 +1018,19 @@ async function deleteMultipleItems() {
9741018
}
9751019
9761020
isDeleteMultipleDialogVisible.value = false
977-
onUpdateOptions(options.value)
1021+
triggerTableLoad()
9781022
}
9791023
9801024
function unselectAll() {
9811025
selectedItems.value = []
9821026
}
9831027
9841028
function deleteSingleItem() {
1029+
tableLoading.value = true
9851030
deleteItem(item)
9861031
item.value = {}
9871032
isDeleteItemDialogVisible.value = false
1033+
triggerTableLoad()
9881034
}
9891035
9901036
function onPage(event) {
@@ -995,14 +1041,14 @@ function onPage(event) {
9951041
sortDesc: event.sortOrder === -1,
9961042
}
9971043
998-
onUpdateOptions(options.value)
1044+
triggerTableLoad()
9991045
}
10001046
10011047
function sortingChanged(event) {
10021048
options.value.sortBy = event.sortField
10031049
options.value.sortDesc = event.sortOrder === -1
10041050
1005-
onUpdateOptions(options.value)
1051+
triggerTableLoad()
10061052
}
10071053
10081054
function goToNewDocument() {
@@ -1094,7 +1140,7 @@ function showRecordAudioDialog() {
10941140
function recordedAudioSaved() {
10951141
notification.showSuccessNotification(t("Saved"))
10961142
isRecordAudioDialogVisible.value = false
1097-
onUpdateOptions(options.value)
1143+
triggerTableLoad()
10981144
}
10991145
11001146
function recordedAudioNotSaved(error) {
@@ -1260,7 +1306,7 @@ async function moveDocument() {
12601306
12611307
notification.showSuccessNotification(t("Document moved successfully"))
12621308
isMoveDialogVisible.value = false
1263-
onUpdateOptions(options.value)
1309+
triggerTableLoad()
12641310
} catch (error) {
12651311
console.error("[Documents] Error moving document:", error.response || error)
12661312
notification.showErrorNotification(t("Error moving the document"))
@@ -1302,7 +1348,7 @@ async function replaceDocument() {
13021348
})
13031349
notification.showSuccessNotification(t("File replaced"))
13041350
isReplaceDialogVisible.value = false
1305-
onUpdateOptions(options.value)
1351+
triggerTableLoad()
13061352
} catch (error) {
13071353
notification.showErrorNotification(t("Error replacing file."))
13081354
console.error(error)
@@ -1319,7 +1365,7 @@ async function selectAsDefaultCertificate(certificate) {
13191365
const response = await axios.patch(`/gradebook/set_default_certificate/${cid}/${certificate.iid}`)
13201366
if (response.status === 200) {
13211367
loadDefaultCertificate()
1322-
onUpdateOptions(options.value)
1368+
triggerTableLoad()
13231369
notification.showSuccessNotification(t("Certificate set as default successfully"))
13241370
}
13251371
} catch {
@@ -1368,7 +1414,7 @@ const isDocumentTemplate = async (documentId) => {
13681414
const deleteDocumentTemplate = async (documentId) => {
13691415
try {
13701416
await axios.post(`/template/document-templates/${documentId}/delete`)
1371-
onUpdateOptions(options.value)
1417+
triggerTableLoad()
13721418
notification.showSuccessNotification(t("Template successfully deleted."))
13731419
} catch (error) {
13741420
console.error("[Documents] Error deleting template:", error)
@@ -1386,7 +1432,7 @@ const openTemplateForm = async (documentId) => {
13861432
13871433
if (isTemplate) {
13881434
await deleteDocumentTemplate(documentId)
1389-
onUpdateOptions(options.value)
1435+
triggerTableLoad()
13901436
} else {
13911437
currentDocumentId.value = documentId
13921438
showTemplateFormModal.value = true
@@ -1419,7 +1465,7 @@ const submitTemplateForm = async () => {
14191465
templateFormData.value.title = ""
14201466
selectedFile.value = null
14211467
showTemplateFormModal.value = false
1422-
onUpdateOptions(options.value)
1468+
triggerTableLoad()
14231469
} else {
14241470
notification.showErrorNotification(t("Error creating the template."))
14251471
}

0 commit comments

Comments
 (0)