Dev/v1.2 (#4)
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import dayjs from 'dayjs'
|
||||
import { CircleAlert, CircleCheck, Download, Eye, FileText, Pencil } from '@lucide/vue'
|
||||
import { CircleAlert, CircleCheck, Download, Eye, Pencil, Trash2 } from '@lucide/vue'
|
||||
import {
|
||||
AlertRoot,
|
||||
AlertTitle,
|
||||
@@ -21,9 +21,9 @@ import { usePermissions } from '@/composables/usePermissions'
|
||||
import { getApiErrorMessage, getApiValidationErrors } from '@/core/utils/getApiErrorMessage'
|
||||
import {
|
||||
completeMembershipApplication,
|
||||
deleteMembershipApplicationDocument,
|
||||
downloadMembershipApplicationDocument,
|
||||
fetchMembershipApplicationDocument,
|
||||
generateMembershipApplicationResultLetter,
|
||||
getMembershipApplication,
|
||||
submitBoardReview,
|
||||
submitManagementReview,
|
||||
@@ -43,12 +43,15 @@ import type {
|
||||
MembershipApplicationReviewDetail,
|
||||
MembershipApplicationStatus,
|
||||
} from '../types/membership-application.types'
|
||||
import { RESULT_LETTER_DOCUMENT_TYPE } from '../types/membership-application.types'
|
||||
import {
|
||||
ADMIN_ATTACHMENT_DOCUMENT_TYPE,
|
||||
RESULT_LETTER_DOCUMENT_TYPE,
|
||||
} from '../types/membership-application.types'
|
||||
|
||||
const WORKFLOW_STEPS = [
|
||||
{ id: 1, label: 'Dihantar' },
|
||||
{ id: 2, label: 'Semakan Pentadbiran' },
|
||||
{ id: 3, label: 'Semakan Lembaga' },
|
||||
{ id: 3, label: 'Keputusan Ahli Lembaga Koperasi (ALK)' },
|
||||
{ id: 4, label: 'Makluman Keputusan' },
|
||||
{ id: 5, label: 'Selesai' },
|
||||
] as const
|
||||
@@ -58,6 +61,7 @@ const DOCUMENT_TYPE_LABELS: Record<string, string> = {
|
||||
photo: 'Gambar Passport',
|
||||
salary_slip: 'Slip Gaji',
|
||||
employer_letter: 'Surat Pengesahan Majikan',
|
||||
admin_attachment: 'Lampiran Pentadbir',
|
||||
[RESULT_LETTER_DOCUMENT_TYPE]: 'Surat Keputusan',
|
||||
}
|
||||
|
||||
@@ -82,19 +86,20 @@ const pendingAction = ref<
|
||||
| null
|
||||
>(null)
|
||||
const downloadingDocumentId = ref<string | null>(null)
|
||||
const deletingDocumentId = ref<string | null>(null)
|
||||
const previewOpen = ref(false)
|
||||
const previewLoading = ref(false)
|
||||
const previewUrl = ref<string | null>(null)
|
||||
const previewDocument = ref<MembershipApplicationDocumentDetail | null>(null)
|
||||
const generateDialogOpen = ref(false)
|
||||
const generateSubmitting = ref(false)
|
||||
const boardMeetingReference = ref('')
|
||||
const boardMeetingReferenceError = ref<string | null>(null)
|
||||
const deleteConfirmDialogOpen = ref(false)
|
||||
const pendingDeleteDocument = ref<MembershipApplicationDocumentDetail | null>(null)
|
||||
|
||||
function statusLabel(status: MembershipApplicationStatus): string {
|
||||
const labels: Record<MembershipApplicationStatus, string> = {
|
||||
SUBMITTED: 'Dihantar',
|
||||
PENDING_BOARD: 'Menunggu Lembaga',
|
||||
PENDING_BOARD: 'Menunggu Keputusan Mesyuarat ALK',
|
||||
MANAGEMENT_REJECTED: 'Ditolak Pentadbiran',
|
||||
PENDING_NOTIFICATION: 'Menunggu Makluman',
|
||||
COMPLETED: 'Selesai',
|
||||
@@ -142,16 +147,14 @@ const showCompleteAction = computed(
|
||||
application.value?.status === 'PENDING_NOTIFICATION',
|
||||
)
|
||||
|
||||
const resultLetterDocument = computed(() =>
|
||||
application.value?.documents.find((document) => document.type === RESULT_LETTER_DOCUMENT_TYPE) ?? null,
|
||||
const canEditAdminAttachments = computed(
|
||||
() =>
|
||||
hasPermission('kemaskini permohonan keahlian') &&
|
||||
application.value?.status !== 'COMPLETED',
|
||||
)
|
||||
|
||||
const showGenerateResultLetter = computed(
|
||||
() =>
|
||||
hasPermission('jana surat keputusan keahlian') &&
|
||||
application.value?.status === 'COMPLETED' &&
|
||||
!resultLetterDocument.value &&
|
||||
(application.value.board_result === 'PASS' || application.value.board_result === 'FAIL'),
|
||||
const resultLetterDocument = computed(() =>
|
||||
application.value?.documents.find((document) => document.type === RESULT_LETTER_DOCUMENT_TYPE) ?? null,
|
||||
)
|
||||
|
||||
const applicant = computed(() => application.value?.applicant ?? null)
|
||||
@@ -167,8 +170,8 @@ const confirmDialogTitle = computed(() => {
|
||||
|
||||
if (pendingAction.value.type === 'board') {
|
||||
return pendingAction.value.decision === 'PASS'
|
||||
? 'Luluskan Semakan Lembaga?'
|
||||
: 'Gagalkan Semakan Lembaga?'
|
||||
? 'Luluskan?'
|
||||
: 'Gagalkan?'
|
||||
}
|
||||
|
||||
return 'Selesaikan Permohonan?'
|
||||
@@ -179,17 +182,17 @@ const confirmDialogDescription = computed(() => {
|
||||
|
||||
if (pendingAction.value.type === 'management') {
|
||||
return pendingAction.value.decision === 'APPROVED'
|
||||
? 'Permohonan akan dihantar ke semakan lembaga.'
|
||||
? 'Permohonan akan dihantar ke mesyuarat ALK.'
|
||||
: 'Permohonan akan ditolak pada peringkat pentadbiran.'
|
||||
}
|
||||
|
||||
if (pendingAction.value.type === 'board') {
|
||||
return pendingAction.value.decision === 'PASS'
|
||||
? 'Permohonan akan dihantar ke peringkat makluman keputusan.'
|
||||
: 'Permohonan akan ditandakan gagal semakan lembaga.'
|
||||
: 'Permohonan akan ditandakan gagal mesyuarat ALK.'
|
||||
}
|
||||
|
||||
return 'E-mel keputusan akan dihantar kepada pemohon. Akaun ahli akan dicipta jika permohonan lulus.'
|
||||
return 'Surat keputusan akan dijana, dilampirkan dalam e-mel keputusan, dan dihantar kepada pemohon. Akaun ahli akan dicipta jika permohonan lulus.'
|
||||
})
|
||||
|
||||
function workflowStepButtonClass(stepId: number) {
|
||||
@@ -242,6 +245,14 @@ const uploadedDocuments = computed(() =>
|
||||
application.value?.documents.filter((document) => document.type !== RESULT_LETTER_DOCUMENT_TYPE) ?? [],
|
||||
)
|
||||
|
||||
const applicantDocuments = computed(() =>
|
||||
uploadedDocuments.value.filter((document) => document.type !== ADMIN_ATTACHMENT_DOCUMENT_TYPE),
|
||||
)
|
||||
|
||||
const adminAttachments = computed(() =>
|
||||
uploadedDocuments.value.filter((document) => document.type === ADMIN_ATTACHMENT_DOCUMENT_TYPE),
|
||||
)
|
||||
|
||||
function displayValue(value: string | number | null | undefined): string {
|
||||
if (value === null || value === undefined || value === '') return '-'
|
||||
return String(value)
|
||||
@@ -309,7 +320,7 @@ function getReference(type: 'PROPOSER' | 'SUPPORTER'): MembershipApplicationRefe
|
||||
|
||||
function reviewStageLabel(stage: string): string {
|
||||
if (stage === 'MANAGEMENT') return 'Semakan Pentadbiran'
|
||||
if (stage === 'BOARD') return 'Semakan Lembaga'
|
||||
if (stage === 'BOARD') return 'Keputusan Ahli Lembaga Koperasi (ALK)'
|
||||
return stage
|
||||
}
|
||||
|
||||
@@ -358,6 +369,11 @@ function openConfirmAction(
|
||||
| { type: 'board'; decision: BoardReviewDecision }
|
||||
| { type: 'complete' },
|
||||
) {
|
||||
if (action.type === 'complete') {
|
||||
boardMeetingReference.value = ''
|
||||
boardMeetingReferenceError.value = null
|
||||
}
|
||||
|
||||
pendingAction.value = action
|
||||
confirmDialogOpen.value = true
|
||||
}
|
||||
@@ -365,6 +381,8 @@ function openConfirmAction(
|
||||
function closeConfirmDialog() {
|
||||
confirmDialogOpen.value = false
|
||||
pendingAction.value = null
|
||||
boardMeetingReference.value = ''
|
||||
boardMeetingReferenceError.value = null
|
||||
}
|
||||
|
||||
async function confirmPendingAction() {
|
||||
@@ -372,8 +390,14 @@ async function confirmPendingAction() {
|
||||
|
||||
error.value = null
|
||||
successMessage.value = null
|
||||
boardMeetingReferenceError.value = null
|
||||
|
||||
if (pendingAction.value.type === 'complete') {
|
||||
const reference = boardMeetingReference.value.trim()
|
||||
if (!reference) {
|
||||
boardMeetingReferenceError.value = 'Rujukan mesyuarat lembaga diperlukan.'
|
||||
return
|
||||
}
|
||||
completeSubmitting.value = true
|
||||
} else {
|
||||
reviewSubmitting.value = true
|
||||
@@ -395,7 +419,9 @@ async function confirmPendingAction() {
|
||||
})
|
||||
boardRemarks.value = ''
|
||||
} else {
|
||||
response = await completeMembershipApplication(applicationId.value)
|
||||
response = await completeMembershipApplication(applicationId.value, {
|
||||
board_meeting_reference: boardMeetingReference.value.trim(),
|
||||
})
|
||||
}
|
||||
|
||||
application.value = response.data
|
||||
@@ -407,55 +433,15 @@ async function confirmPendingAction() {
|
||||
if (validationErrors?.remarks?.[0]) {
|
||||
error.value = validationErrors.remarks[0]
|
||||
}
|
||||
if (validationErrors?.board_meeting_reference?.[0]) {
|
||||
boardMeetingReferenceError.value = validationErrors.board_meeting_reference[0]
|
||||
}
|
||||
} finally {
|
||||
reviewSubmitting.value = false
|
||||
completeSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openGenerateDialog() {
|
||||
boardMeetingReference.value = ''
|
||||
boardMeetingReferenceError.value = null
|
||||
generateDialogOpen.value = true
|
||||
}
|
||||
|
||||
function closeGenerateDialog() {
|
||||
if (generateSubmitting.value) return
|
||||
generateDialogOpen.value = false
|
||||
boardMeetingReference.value = ''
|
||||
boardMeetingReferenceError.value = null
|
||||
}
|
||||
|
||||
async function confirmGenerateLetter() {
|
||||
if (!application.value || generateSubmitting.value) return
|
||||
|
||||
const reference = boardMeetingReference.value.trim()
|
||||
if (!reference) {
|
||||
boardMeetingReferenceError.value = 'Rujukan mesyuarat lembaga diperlukan.'
|
||||
return
|
||||
}
|
||||
|
||||
generateSubmitting.value = true
|
||||
boardMeetingReferenceError.value = null
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const response = await generateMembershipApplicationResultLetter(application.value.id, {
|
||||
board_meeting_reference: reference,
|
||||
})
|
||||
|
||||
application.value = response.data.application
|
||||
successMessage.value = response.message
|
||||
closeGenerateDialog()
|
||||
} catch (err) {
|
||||
const validationErrors = getApiValidationErrors(err)
|
||||
boardMeetingReferenceError.value = validationErrors?.board_meeting_reference?.[0] ?? null
|
||||
error.value = getApiErrorMessage(err, 'Gagal menjana surat keputusan.')
|
||||
} finally {
|
||||
generateSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDownloadDocument(document: MembershipApplicationDocumentDetail) {
|
||||
if (!application.value || downloadingDocumentId.value) return
|
||||
|
||||
@@ -475,6 +461,43 @@ async function handleDownloadDocument(document: MembershipApplicationDocumentDet
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteAdminAttachment(document: MembershipApplicationDocumentDetail) {
|
||||
if (!application.value || !canEditAdminAttachments.value || deletingDocumentId.value) return
|
||||
|
||||
pendingDeleteDocument.value = document
|
||||
deleteConfirmDialogOpen.value = true
|
||||
}
|
||||
|
||||
function closeDeleteConfirmDialog() {
|
||||
deleteConfirmDialogOpen.value = false
|
||||
pendingDeleteDocument.value = null
|
||||
}
|
||||
|
||||
async function confirmDeleteAdminAttachment() {
|
||||
const document = pendingDeleteDocument.value
|
||||
if (!application.value || !document || deletingDocumentId.value) return
|
||||
|
||||
deletingDocumentId.value = document.id
|
||||
error.value = null
|
||||
successMessage.value = null
|
||||
|
||||
try {
|
||||
const response = await deleteMembershipApplicationDocument(application.value.id, document.id)
|
||||
application.value = response.data
|
||||
|
||||
if (previewDocument.value?.id === document.id) {
|
||||
handlePreviewOpenChange(false)
|
||||
}
|
||||
|
||||
successMessage.value = response.message || 'Lampiran pentadbir berjaya dipadam.'
|
||||
closeDeleteConfirmDialog()
|
||||
} catch (err) {
|
||||
error.value = getApiErrorMessage(err, 'Gagal memadam lampiran pentadbir.')
|
||||
} finally {
|
||||
deletingDocumentId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function handleViewDocument(document: MembershipApplicationDocumentDetail) {
|
||||
if (!application.value) return
|
||||
|
||||
@@ -649,51 +672,31 @@ onUnmounted(() => {
|
||||
<Box v-if="showCompleteAction" class="p-5 sm:p-6">
|
||||
<div class="font-medium">Makluman Keputusan</div>
|
||||
<p class="mt-1 text-sm opacity-70">
|
||||
Hantar e-mel keputusan kepada pemohon
|
||||
<span v-if="application.board_result === 'PASS'"> dan cipta akaun ahli</span>.
|
||||
Jana surat keputusan, hantar e-mel dengan lampiran surat kepada pemohon
|
||||
<span v-if="application.board_result === 'PASS'">, dan cipta akaun ahli</span>.
|
||||
</p>
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<Button type="button" variant="primary" :disabled="reviewSubmitting || completeSubmitting"
|
||||
@click="openConfirmAction({ type: 'complete' })">
|
||||
Selesaikan & Hantar Makluman
|
||||
Selesaikan, Jana Surat & Hantar E-mel
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<Box v-if="showGenerateResultLetter" class="p-5 sm:p-6">
|
||||
<div class="font-medium">Surat Keputusan</div>
|
||||
<p class="mt-1 text-sm opacity-70">
|
||||
Jana surat keputusan lembaga untuk permohonan ini. Surat hanya boleh dijana sekali.
|
||||
</p>
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<Button type="button" variant="primary" @click="openGenerateDialog">
|
||||
<FileText class="mr-2 size-4" />
|
||||
Jana Surat Keputusan
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<Box v-else-if="resultLetterDocument" class="p-5 sm:p-6">
|
||||
<Box v-if="resultLetterDocument" class="p-5 sm:p-6">
|
||||
<div class="font-medium">Surat Keputusan</div>
|
||||
<p class="mt-1 text-sm opacity-70">
|
||||
{{ resultLetterDocument.name }} · {{ formatFileSize(resultLetterDocument.file_size) }}
|
||||
</p>
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
look="outline"
|
||||
<Button type="button" look="outline"
|
||||
:disabled="previewLoading && previewDocument?.id === resultLetterDocument.id"
|
||||
@click="handleViewDocument(resultLetterDocument)"
|
||||
>
|
||||
@click="handleViewDocument(resultLetterDocument)">
|
||||
<Eye class="mr-2 size-4" />
|
||||
Lihat
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
look="outline"
|
||||
:disabled="downloadingDocumentId === resultLetterDocument.id"
|
||||
@click="handleDownloadDocument(resultLetterDocument)"
|
||||
>
|
||||
<Button type="button" look="outline" :disabled="downloadingDocumentId === resultLetterDocument.id"
|
||||
@click="handleDownloadDocument(resultLetterDocument)">
|
||||
<Download class="mr-2 size-4" />
|
||||
{{ downloadingDocumentId === resultLetterDocument.id ? 'Memuat turun...' : 'Muat Turun' }}
|
||||
</Button>
|
||||
@@ -722,7 +725,7 @@ onUnmounted(() => {
|
||||
<TabsTrigger
|
||||
class="inline-flex min-w-0 flex-1 items-center justify-center whitespace-nowrap px-2 text-xs sm:px-3 sm:text-sm"
|
||||
value="heirs">
|
||||
Waris
|
||||
Penama
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
class="inline-flex min-w-0 flex-1 items-center justify-center whitespace-nowrap px-2 text-xs sm:px-3 sm:text-sm"
|
||||
@@ -834,7 +837,7 @@ onUnmounted(() => {
|
||||
<div v-else class="space-y-4">
|
||||
<div v-for="(heir, index) in application.heirs" :key="heir.id"
|
||||
class="rounded-lg border border-foreground/10 p-4">
|
||||
<div class="mb-4 font-medium">Waris {{ index + 1 }}</div>
|
||||
<div class="mb-4 font-medium">Penama</div>
|
||||
<div class="grid grid-cols-12 gap-4">
|
||||
<Field class="col-span-12 sm:col-span-6">
|
||||
<FieldLabel>Nama</FieldLabel>
|
||||
@@ -890,30 +893,62 @@ onUnmounted(() => {
|
||||
|
||||
<TabsContent value="documents" class="mt-6">
|
||||
<div v-if="!uploadedDocuments.length" class="opacity-70">Tiada dokumen dimuat naik.</div>
|
||||
<div v-else class="space-y-3">
|
||||
<div v-for="document in uploadedDocuments" :key="document.id"
|
||||
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-foreground/10 p-4">
|
||||
<div>
|
||||
<div class="font-medium">{{ documentLabel(document.type, document.name) }}</div>
|
||||
<div class="mt-1 text-sm opacity-70">
|
||||
{{ document.name }} · {{ formatFileSize(document.file_size) }}
|
||||
<template v-else>
|
||||
<div v-if="applicantDocuments.length" class="space-y-3">
|
||||
<div class="font-medium">Dokumen Pemohon</div>
|
||||
<div v-for="document in applicantDocuments" :key="document.id"
|
||||
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-foreground/10 p-4">
|
||||
<div>
|
||||
<div class="font-medium">{{ documentLabel(document.type, document.name) }}</div>
|
||||
<div class="mt-1 text-sm opacity-70">
|
||||
{{ document.name }} · {{ formatFileSize(document.file_size) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<Button type="button" look="outline" size="sm"
|
||||
:disabled="previewLoading && previewDocument?.id === document.id"
|
||||
@click="handleViewDocument(document)">
|
||||
<Eye class="mr-2 size-4" />
|
||||
{{ previewLoading && previewDocument?.id === document.id ? 'Memuatkan...' : 'Lihat' }}
|
||||
</Button>
|
||||
<Button type="button" look="outline" size="sm" :disabled="downloadingDocumentId === document.id"
|
||||
@click="handleDownloadDocument(document)">
|
||||
<Download class="mr-2 size-4" />
|
||||
{{ downloadingDocumentId === document.id ? 'Memuat turun...' : 'Muat Turun' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<Button type="button" look="outline" size="sm"
|
||||
:disabled="previewLoading && previewDocument?.id === document.id"
|
||||
@click="handleViewDocument(document)">
|
||||
<Eye class="mr-2 size-4" />
|
||||
{{ previewLoading && previewDocument?.id === document.id ? 'Memuatkan...' : 'Lihat' }}
|
||||
</Button>
|
||||
<Button type="button" look="outline" size="sm" :disabled="downloadingDocumentId === document.id"
|
||||
@click="handleDownloadDocument(document)">
|
||||
<Download class="mr-2 size-4" />
|
||||
{{ downloadingDocumentId === document.id ? 'Memuat turun...' : 'Muat Turun' }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-if="adminAttachments.length" class="mt-8 space-y-3">
|
||||
<div class="font-medium">Lampiran Pentadbir</div>
|
||||
<div v-for="document in adminAttachments" :key="document.id"
|
||||
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-foreground/10 p-4">
|
||||
<div>
|
||||
<div class="font-medium">{{ document.name }}</div>
|
||||
<div class="mt-1 text-sm opacity-70">{{ formatFileSize(document.file_size) }}</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<Button type="button" look="outline" size="sm"
|
||||
:disabled="previewLoading && previewDocument?.id === document.id"
|
||||
@click="handleViewDocument(document)">
|
||||
<Eye class="mr-2 size-4" />
|
||||
{{ previewLoading && previewDocument?.id === document.id ? 'Memuatkan...' : 'Lihat' }}
|
||||
</Button>
|
||||
<Button type="button" look="outline" size="sm" :disabled="downloadingDocumentId === document.id"
|
||||
@click="handleDownloadDocument(document)">
|
||||
<Download class="mr-2 size-4" />
|
||||
{{ downloadingDocumentId === document.id ? 'Memuat turun...' : 'Muat Turun' }}
|
||||
</Button>
|
||||
<Button v-if="canEditAdminAttachments" type="button" look="outline" size="sm" variant="danger"
|
||||
:disabled="deletingDocumentId === document.id" @click="handleDeleteAdminAttachment(document)">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
{{ deletingDocumentId === document.id ? 'Memadam...' : 'Padam' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent v-if="application.reviews.length" value="reviews" class="mt-6">
|
||||
@@ -932,11 +967,8 @@ onUnmounted(() => {
|
||||
{{ formatDateTime(review.reviewed_at) }}
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
:variant="reviewDecisionBadgeVariant(review.decision, review.stage)"
|
||||
:look="reviewDecisionBadgeLook(review.decision, review.stage)"
|
||||
class="whitespace-nowrap"
|
||||
>
|
||||
<Badge :variant="reviewDecisionBadgeVariant(review.decision, review.stage)"
|
||||
:look="reviewDecisionBadgeLook(review.decision, review.stage)" class="whitespace-nowrap">
|
||||
{{ reviewDecisionLabel(review.decision, review.stage) }}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -959,11 +991,18 @@ onUnmounted(() => {
|
||||
</template>
|
||||
|
||||
<DialogRoot :open="confirmDialogOpen"
|
||||
@openChange="(details) => { confirmDialogOpen = details.open; if (!details.open) pendingAction = null }">
|
||||
@openChange="(details) => { if (!details.open) closeConfirmDialog(); else confirmDialogOpen = details.open }">
|
||||
<DialogContent>
|
||||
<div class="p-5 text-center">
|
||||
<div class="mt-2 text-2xl font-medium">{{ confirmDialogTitle }}</div>
|
||||
<div class="mt-2 opacity-70">{{ confirmDialogDescription }}</div>
|
||||
<Field v-if="pendingAction?.type === 'complete'" class="mt-5 text-left">
|
||||
<FieldLabel for="detail-board-meeting-reference">Rujukan Mesyuarat Lembaga</FieldLabel>
|
||||
<Input id="detail-board-meeting-reference" v-model="boardMeetingReference" type="text"
|
||||
placeholder="Contoh: Mesyuarat Lembaga Bil. 3/2026" :disabled="completeSubmitting"
|
||||
@input="boardMeetingReferenceError = null" />
|
||||
<FieldError v-if="boardMeetingReferenceError">{{ boardMeetingReferenceError }}</FieldError>
|
||||
</Field>
|
||||
</div>
|
||||
<div class="px-5 pb-8 text-center">
|
||||
<DialogCloseTrigger class="mr-2 w-28" :disabled="reviewSubmitting || completeSubmitting">
|
||||
@@ -977,38 +1016,22 @@ onUnmounted(() => {
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
|
||||
<DialogRoot :open="generateDialogOpen" @openChange="(details) => { if (!details.open) closeGenerateDialog() }">
|
||||
<DialogRoot :open="deleteConfirmDialogOpen"
|
||||
@openChange="(details) => { if (!details.open) closeDeleteConfirmDialog() }">
|
||||
<DialogContent>
|
||||
<div class="p-5">
|
||||
<div class="text-2xl font-medium">Jana Surat Keputusan</div>
|
||||
<p v-if="application" class="mt-2 text-sm opacity-70">
|
||||
{{ application.application_number }} · {{ application.applicant?.name ?? '-' }}
|
||||
</p>
|
||||
<Field class="mt-5">
|
||||
<FieldLabel for="detail-board-meeting-reference">Rujukan Mesyuarat Lembaga</FieldLabel>
|
||||
<Input
|
||||
id="detail-board-meeting-reference"
|
||||
v-model="boardMeetingReference"
|
||||
type="text"
|
||||
placeholder="Contoh: Mesyuarat Lembaga Bil. 3/2026"
|
||||
:disabled="generateSubmitting"
|
||||
@input="boardMeetingReferenceError = null"
|
||||
/>
|
||||
<FieldError v-if="boardMeetingReferenceError">{{ boardMeetingReferenceError }}</FieldError>
|
||||
</Field>
|
||||
<div class="p-5 text-center">
|
||||
<div class="mt-2 text-2xl font-medium">Padam Lampiran Pentadbir?</div>
|
||||
<div class="mt-2 opacity-70">
|
||||
{{ pendingDeleteDocument?.name ?? 'Lampiran pentadbir' }} akan dipadam secara kekal.
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5 pb-8 text-center">
|
||||
<DialogCloseTrigger class="mr-2 w-32" :disabled="generateSubmitting" @click="closeGenerateDialog">
|
||||
<DialogCloseTrigger class="mr-2 w-28" :disabled="!!deletingDocumentId" @click="closeDeleteConfirmDialog">
|
||||
Batal
|
||||
</DialogCloseTrigger>
|
||||
<Button
|
||||
class="w-32"
|
||||
type="button"
|
||||
variant="primary"
|
||||
:disabled="generateSubmitting"
|
||||
@click="confirmGenerateLetter"
|
||||
>
|
||||
{{ generateSubmitting ? 'Menjana...' : 'Jana Surat' }}
|
||||
<Button class="w-28" type="button" variant="danger" :disabled="!!deletingDocumentId"
|
||||
@click="confirmDeleteAdminAttachment">
|
||||
{{ deletingDocumentId ? 'Memadam...' : 'Padam' }}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user