generated from corrad-software/corrad-af-2024
296 lines
12 KiB
Vue
296 lines
12 KiB
Vue
<script setup>
|
|
import { ref, computed } from 'vue';
|
|
|
|
const props = defineProps({
|
|
request: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['close', 'submit']);
|
|
|
|
// Form state
|
|
const approvalNotes = ref('');
|
|
const isSubmitting = ref(false);
|
|
const formError = ref('');
|
|
|
|
// Computed properties
|
|
const documentName = computed(() => {
|
|
return props.request?.documentName || 'Document';
|
|
});
|
|
|
|
const documentPath = computed(() => {
|
|
return props.request?.documentPath || '';
|
|
});
|
|
|
|
const requesterInfo = computed(() => {
|
|
return {
|
|
name: props.request?.requesterName || 'Unknown',
|
|
department: props.request?.requesterDepartment || '',
|
|
email: props.request?.requesterEmail || '',
|
|
justification: props.request?.justification || 'No justification provided'
|
|
};
|
|
});
|
|
|
|
const requestType = computed(() => {
|
|
return props.request?.accessType || 'view';
|
|
});
|
|
|
|
const requestDate = computed(() => {
|
|
if (!props.request?.requestDate) return '';
|
|
const date = new Date(props.request.requestDate);
|
|
return date.toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
});
|
|
|
|
const accessDuration = computed(() => {
|
|
return props.request?.accessDuration || '7 days';
|
|
});
|
|
|
|
// Methods
|
|
const closeDialog = () => {
|
|
// Reset form
|
|
approvalNotes.value = '';
|
|
formError.value = '';
|
|
|
|
emit('close');
|
|
};
|
|
|
|
const validateForm = () => {
|
|
if (approvalNotes.value.trim().length < 3) {
|
|
formError.value = 'Please provide notes for your decision';
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
const approveRequest = async () => {
|
|
if (!validateForm()) return;
|
|
|
|
isSubmitting.value = true;
|
|
formError.value = '';
|
|
|
|
try {
|
|
emit('submit', {
|
|
action: 'approve',
|
|
notes: approvalNotes.value
|
|
});
|
|
} catch (error) {
|
|
formError.value = 'Failed to approve request. Please try again.';
|
|
console.error('Approval error:', error);
|
|
isSubmitting.value = false;
|
|
}
|
|
};
|
|
|
|
const rejectRequest = async () => {
|
|
if (!validateForm()) return;
|
|
|
|
isSubmitting.value = true;
|
|
formError.value = '';
|
|
|
|
try {
|
|
emit('submit', {
|
|
action: 'reject',
|
|
notes: approvalNotes.value
|
|
});
|
|
} catch (error) {
|
|
formError.value = 'Failed to reject request. Please try again.';
|
|
console.error('Rejection error:', error);
|
|
isSubmitting.value = false;
|
|
}
|
|
};
|
|
|
|
// Format the access type label
|
|
const getAccessTypeLabel = (type) => {
|
|
const types = {
|
|
'view': 'View Only',
|
|
'download': 'Download',
|
|
'print': 'Print',
|
|
'full': 'Full Access'
|
|
};
|
|
return types[type] || type;
|
|
};
|
|
|
|
// Format the access type description
|
|
const getAccessTypeDescription = (type) => {
|
|
const descriptions = {
|
|
'view': 'Can only view the document',
|
|
'download': 'Can view and download',
|
|
'print': 'Can view and print',
|
|
'full': 'View, download and print'
|
|
};
|
|
return descriptions[type] || '';
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<rs-modal
|
|
:visible="visible"
|
|
@close="closeDialog"
|
|
title="Review Access Request"
|
|
size="lg"
|
|
>
|
|
<template #body>
|
|
<div class="p-6">
|
|
<!-- Form error message -->
|
|
<div v-if="formError" class="mb-4 p-3 bg-red-50 border border-red-200 text-red-800 rounded-md text-sm">
|
|
{{ formError }}
|
|
</div>
|
|
|
|
<!-- Two-column layout for request details -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<!-- Left column - Document details -->
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Document Information</h3>
|
|
<div class="bg-blue-50 dark:bg-blue-900/10 rounded-lg p-4 border border-blue-200 dark:border-blue-800 mb-4">
|
|
<div class="flex items-start mb-3">
|
|
<span class="flex-shrink-0 h-9 w-9 rounded-md bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center text-blue-600 dark:text-blue-400 mr-3">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</span>
|
|
<div>
|
|
<div class="text-sm font-medium text-blue-900 dark:text-blue-100">{{ documentName }}</div>
|
|
<div class="text-xs text-blue-800 dark:text-blue-200">{{ documentPath }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-2 text-sm">
|
|
<div class="text-gray-700 dark:text-gray-300 font-medium">Access Type:</div>
|
|
<div class="text-gray-900 dark:text-gray-100">{{ getAccessTypeLabel(requestType) }}</div>
|
|
|
|
<div class="text-gray-700 dark:text-gray-300 font-medium">Access Duration:</div>
|
|
<div class="text-gray-900 dark:text-gray-100">{{ accessDuration }}</div>
|
|
|
|
<div class="text-gray-700 dark:text-gray-300 font-medium">Requested:</div>
|
|
<div class="text-gray-900 dark:text-gray-100">{{ requestDate }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Access Type Details</h3>
|
|
<div class="bg-indigo-50 dark:bg-indigo-900/10 rounded-lg p-4 border border-indigo-200 dark:border-indigo-800">
|
|
<div class="flex items-center mb-2">
|
|
<div class="w-8 h-8 rounded-full bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center mr-3">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-indigo-600 dark:text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
|
</svg>
|
|
</div>
|
|
<div class="text-sm font-medium text-indigo-900 dark:text-indigo-100">
|
|
{{ getAccessTypeLabel(requestType) }}
|
|
</div>
|
|
</div>
|
|
<p class="text-sm text-indigo-800 dark:text-indigo-200 ml-11">
|
|
{{ getAccessTypeDescription(requestType) }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right column - Requester details and justification -->
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Requester Information</h3>
|
|
<div class="bg-purple-50 dark:bg-purple-900/10 rounded-lg p-4 border border-purple-200 dark:border-purple-800 mb-4">
|
|
<div class="flex items-start mb-3">
|
|
<span class="flex-shrink-0 h-9 w-9 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center text-purple-600 dark:text-purple-400 mr-3">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
|
</svg>
|
|
</span>
|
|
<div>
|
|
<div class="text-sm font-medium text-purple-900 dark:text-purple-100">{{ requesterInfo.name }}</div>
|
|
<div class="text-xs text-purple-800 dark:text-purple-200">
|
|
{{ requesterInfo.department }}
|
|
<span v-if="requesterInfo.email">• {{ requesterInfo.email }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Request Justification</h3>
|
|
<div class="bg-green-50 dark:bg-green-900/10 rounded-lg p-4 border border-green-200 dark:border-green-800">
|
|
<div class="flex items-center mb-2">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-600 dark:text-green-400 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
|
|
</svg>
|
|
<div class="text-sm font-medium text-green-900 dark:text-green-100">Justification</div>
|
|
</div>
|
|
<p class="text-sm text-green-800 dark:text-green-200 whitespace-pre-line">
|
|
{{ requesterInfo.justification }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Approval notes -->
|
|
<div class="mt-6">
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">Decision Notes <span class="text-red-500">*</span></h3>
|
|
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700">
|
|
<textarea
|
|
v-model="approvalNotes"
|
|
rows="3"
|
|
placeholder="Enter your approval or rejection notes here..."
|
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none"
|
|
></textarea>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
|
Please provide a reason for your decision. This will be visible to the requester.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template #footer>
|
|
<div class="flex justify-between gap-3 px-6 py-4 bg-gray-50 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700">
|
|
<div>
|
|
<rs-button
|
|
@click="closeDialog"
|
|
:disabled="isSubmitting"
|
|
variant="secondary-outline"
|
|
size="sm"
|
|
>
|
|
Cancel
|
|
</rs-button>
|
|
</div>
|
|
|
|
<div class="flex gap-3">
|
|
<rs-button
|
|
@click="rejectRequest"
|
|
:disabled="isSubmitting"
|
|
variant="danger-outline"
|
|
size="sm"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
Reject
|
|
</rs-button>
|
|
|
|
<rs-button
|
|
@click="approveRequest"
|
|
:disabled="isSubmitting"
|
|
variant="success"
|
|
size="sm"
|
|
>
|
|
<svg v-if="isSubmitting" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
Approve
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-modal>
|
|
</template> |