<script setup lang="ts"> import { useResourceStore } from "@/stores/resources"; import { computed, onMounted, reactive } from "vue"; import { type ResourceOut, type ResourceVersionOut, Status, } from "@/client/resource"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { Modal, Toast, Tooltip } from "bootstrap"; import ResourceVersionInfoModal from "@/components/resources/modals/ResourceVersionInfoModal.vue"; import { useNameStore } from "@/stores/names"; import BootstrapToast from "@/components/BootstrapToast.vue"; import ReasonModal from "@/components/modals/ReasonModal.vue"; const resourceRepository = useResourceStore(); const nameRepository = useNameStore(); let refreshTimeout: NodeJS.Timeout | undefined = undefined; let rejectReasonModal: Modal | null = null; let successToast: Toast | null = null; let rejectToast: Toast | null = null; const resourceState = reactive<{ sendingRequest: boolean; loading: boolean; inspectResource?: ResourceOut; rejectResource?: ResourceVersionOut; inspectVersionIndex: number; }>({ sendingRequest: false, loading: true, inspectResource: undefined, rejectResource: undefined, inspectVersionIndex: 0, }); const countItems = computed<number>(() => resourceRepository.reviewableResources.reduce( (previousValue, currentValue) => previousValue + currentValue.versions.length, 0, ), ); function fetchResources() { resourceRepository.fetchReviewableResources(() => { resourceState.loading = false; }); } function clickRefreshResources() { clearTimeout(refreshTimeout); refreshTimeout = setTimeout(() => { fetchResources(); }, 500); } function acceptReview(resourceVersion: ResourceVersionOut) { resourceState.sendingRequest = true; resourceRepository .reviewResource(resourceVersion, { deny: false }) .then(() => { successToast?.show(); }) .finally(() => { resourceState.sendingRequest = false; }); } function rejectReview(reason: string, resourceVersion?: ResourceVersionOut) { if (resourceVersion) { resourceState.sendingRequest = true; resourceRepository .reviewResource(resourceVersion, { deny: true, reason: reason }) .then(() => { rejectReasonModal?.hide(); rejectToast?.show(); }) .finally(() => { resourceState.sendingRequest = false; }); } } onMounted(() => { fetchResources(); new Tooltip("#refreshReviewableResourcesButton"); rejectReasonModal = new Modal("#review-reject-modal"); successToast = new Toast("#accept-resource-review-toast"); rejectToast = new Toast("#reject-resource-review-toast"); }); </script> <template> <bootstrap-toast toast-id="accept-resource-review-toast" color-class="success" > Accepted resource review </bootstrap-toast> <bootstrap-toast toast-id="reject-resource-review-toast" color-class="danger"> Rejected resource review </bootstrap-toast> <resource-version-info-modal modal-id="review-resource-version-info-modal" :resource-version-index="resourceState.inspectVersionIndex" :resource="resourceState.inspectResource" /> <reason-modal modal-id="review-reject-modal" modal-label="Resource Review Reject Modal" :loading="resourceState.sendingRequest" purpose="rejection" @save="(reason) => rejectReview(reason, resourceState.rejectResource)" > <template #header> Reject Resource Review <b>{{ resourceState.rejectResource?.release }}</b> </template> </reason-modal> <div class="row border-bottom mb-4 justify-content-between align-items-center" > <h2 class="w-fit">Resource requests</h2> <span class="w-fit" tabindex="0" data-bs-title="Refresh Reviewable Resources" data-bs-toggle="tooltip" id="refreshReviewableResourcesButton" > <button type="button" class="btn btn-primary btn-light me-2 shadow-sm border w-fit" :disabled="resourceState.loading" @click="clickRefreshResources" > <font-awesome-icon icon="fa-solid fa-arrow-rotate-right" /> <span class="visually-hidden">Refresh Reviewable Resources</span> </button> </span> </div> <div v-if="resourceState.loading" class="text-center mt-5"> <div class="spinner-border" style="width: 3rem; height: 3rem" role="status"> <span class="visually-hidden">Loading...</span> </div> </div> <table class="table caption-top table-striped table-hover align-middle" v-else-if="resourceRepository.reviewableResources.length > 0" > <caption> Display {{ countItems }} resource versions </caption> <thead> <tr> <th scope="col">Resource</th> <th scope="col">Release</th> <th scope="col">Status</th> <th scope="col">Maintainer</th> <th scope="col"></th> <th scope="col" class="text-end">Action</th> </tr> </thead> <tbody> <template v-for="resource in resourceRepository.reviewableResources" :key="resource.resource_id" > <tr v-for="(version, index) in resource.versions" :key="version.resource_version_id" > <th>{{ resource.name }}</th> <th>{{ version.release }}</th> <th>{{ version.status }}</th> <th>{{ nameRepository.getName(resource.maintainer_id) }}</th> <th> <button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#review-resource-version-info-modal" @click=" resourceState.inspectResource = resource; resourceState.inspectVersionIndex = index; " > Inspect </button> </th> <th class="text-end"> <div v-if="version.status === Status.WAIT_FOR_REVIEW" class="btn-group" > <button type="button" class="btn btn-success btn-sm" :disabled="resourceState.sendingRequest" @click="acceptReview(version)" > Accept </button> <button type="button" class="btn btn-danger btn-sm" @click="resourceState.rejectResource = version" data-bs-toggle="modal" data-bs-target="#review-reject-modal" :disabled="resourceState.sendingRequest" > Reject </button> </div> <div v-else-if="version.status === Status.SYNCHRONIZING" class="progress" role="progressbar" aria-label="Animated striped example" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" > <div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 100%" ></div> </div> </th> </tr> </template> </tbody> </table> <div v-else class="text-center mt-5 fs-4"> There are currently no resources to review </div> </template> <style scoped></style>