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
673673const 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+
675713function 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)
701739const documentToReplace = ref (null )
702740
703741onMounted (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
9801024function unselectAll () {
9811025 selectedItems .value = []
9821026}
9831027
9841028function deleteSingleItem () {
1029+ tableLoading .value = true
9851030 deleteItem (item)
9861031 item .value = {}
9871032 isDeleteItemDialogVisible .value = false
1033+ triggerTableLoad ()
9881034}
9891035
9901036function onPage (event ) {
@@ -995,14 +1041,14 @@ function onPage(event) {
9951041 sortDesc: event .sortOrder === - 1 ,
9961042 }
9971043
998- onUpdateOptions ( options . value )
1044+ triggerTableLoad ( )
9991045}
10001046
10011047function sortingChanged (event ) {
10021048 options .value .sortBy = event .sortField
10031049 options .value .sortDesc = event .sortOrder === - 1
10041050
1005- onUpdateOptions ( options . value )
1051+ triggerTableLoad ( )
10061052}
10071053
10081054function goToNewDocument () {
@@ -1094,7 +1140,7 @@ function showRecordAudioDialog() {
10941140function recordedAudioSaved () {
10951141 notification .showSuccessNotification (t (" Saved" ))
10961142 isRecordAudioDialogVisible .value = false
1097- onUpdateOptions ( options . value )
1143+ triggerTableLoad ( )
10981144}
10991145
11001146function 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) => {
13681414const 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