EDMS/components/dms/preview/ShareDocumentModal.vue
2025-06-05 14:57:08 +08:00

407 lines
12 KiB
Vue

<template>
<div
v-if="visible"
class="fixed inset-0 z-50 bg-black bg-opacity-50 flex items-center justify-center"
@click="closeModal"
>
<div
class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-lg w-full mx-4 max-h-[80vh] overflow-hidden"
@click.stop
>
<!-- Header -->
<div class="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100">
Share Document
</h2>
<button
@click="closeModal"
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<Icon name="mdi:close" class="w-6 h-6" />
</button>
</div>
<!-- Content -->
<div class="p-6 space-y-6">
<!-- Document Info -->
<div class="flex items-center space-x-3 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
<Icon
:name="getFileTypeIcon(document.name)"
:class="['w-8 h-8', getFileTypeColor(document.name)]"
/>
<div class="flex-1 min-w-0">
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
{{ document.name }}
</h3>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ formatFileSize(document.size) }} {{ formatDate(document.lastModified) }}
</p>
</div>
</div>
<!-- Share Options -->
<div class="space-y-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">Share Options</h3>
<!-- Share by Email -->
<div class="space-y-3">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Email Recipients
</label>
<div class="flex space-x-2">
<input
v-model="newEmail"
@keydown.enter="addEmail"
type="email"
placeholder="Enter email address..."
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100
focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
<button
@click="addEmail"
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-sm"
>
Add
</button>
</div>
<!-- Email List -->
<div v-if="emailList.length > 0" class="space-y-2">
<div
v-for="(email, index) in emailList"
:key="index"
class="flex items-center justify-between p-2 bg-gray-100 dark:bg-gray-600 rounded-md"
>
<span class="text-sm text-gray-900 dark:text-gray-100">{{ email }}</span>
<button
@click="removeEmail(index)"
class="text-gray-500 hover:text-red-500"
>
<Icon name="mdi:close" class="w-4 h-4" />
</button>
</div>
</div>
</div>
<!-- Access Level -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Access Level
</label>
<select
v-model="accessLevel"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100
focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="view">View Only</option>
<option value="comment">View & Comment</option>
<option value="edit">View & Edit</option>
<option value="full">Full Access</option>
</select>
</div>
<!-- Expiration -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Share Expiration
</label>
<select
v-model="expiration"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100
focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="never">Never</option>
<option value="1day">1 Day</option>
<option value="1week">1 Week</option>
<option value="1month">1 Month</option>
<option value="custom">Custom Date</option>
</select>
<!-- Custom Date Input -->
<input
v-if="expiration === 'custom'"
v-model="customDate"
type="datetime-local"
class="mt-2 w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100
focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<!-- Message -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Message (Optional)
</label>
<textarea
v-model="shareMessage"
rows="3"
placeholder="Add a message for the recipients..."
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100
focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
></textarea>
</div>
<!-- Share Link -->
<div class="border-t border-gray-200 dark:border-gray-600 pt-4">
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">Share Link</h4>
<div class="flex items-center space-x-2">
<input
:value="shareLink"
readonly
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm
bg-gray-50 dark:bg-gray-600 text-gray-700 dark:text-gray-300"
/>
<button
@click="copyLink"
class="px-3 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 text-sm"
>
<Icon name="mdi:content-copy" class="w-4 h-4" />
</button>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
Anyone with this link can {{ accessLevel }} the document
</p>
</div>
<!-- Notifications -->
<div class="flex items-center space-x-3">
<input
v-model="notifyOnAccess"
type="checkbox"
id="notify-access"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
/>
<label for="notify-access" class="text-sm text-gray-700 dark:text-gray-300">
Notify me when someone accesses this document
</label>
</div>
</div>
</div>
<!-- Footer -->
<div class="flex items-center justify-between p-6 border-t border-gray-200 dark:border-gray-700">
<div class="text-xs text-gray-500 dark:text-gray-400">
{{ emailList.length }} recipient(s) selected
</div>
<div class="flex items-center space-x-3">
<button
@click="closeModal"
class="px-4 py-2 text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100"
>
Cancel
</button>
<button
@click="shareDocument"
:disabled="emailList.length === 0"
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
Share Document
</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const props = defineProps({
visible: {
type: Boolean,
default: false
},
document: {
type: Object,
required: true
}
});
const emit = defineEmits([
'update:visible',
'shared'
]);
// State
const newEmail = ref('');
const emailList = ref([]);
const accessLevel = ref('view');
const expiration = ref('never');
const customDate = ref('');
const shareMessage = ref('');
const notifyOnAccess = ref(true);
// Computed
const isVisible = computed({
get() {
return props.visible;
},
set(value) {
emit('update:visible', value);
}
});
const shareLink = computed(() => {
// Generate a shareable link (mock implementation)
return `https://dms.example.com/share/${props.document.id}?access=${accessLevel.value}`;
});
// Methods
const closeModal = () => {
isVisible.value = false;
resetForm();
};
const resetForm = () => {
newEmail.value = '';
emailList.value = [];
accessLevel.value = 'view';
expiration.value = 'never';
customDate.value = '';
shareMessage.value = '';
notifyOnAccess.value = true;
};
const addEmail = () => {
const email = newEmail.value.trim();
if (email && isValidEmail(email) && !emailList.value.includes(email)) {
emailList.value.push(email);
newEmail.value = '';
}
};
const removeEmail = (index) => {
emailList.value.splice(index, 1);
};
const isValidEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const copyLink = async () => {
try {
await navigator.clipboard.writeText(shareLink.value);
// You could show a toast notification here
console.log('Link copied to clipboard');
} catch (err) {
console.error('Failed to copy link:', err);
}
};
const shareDocument = () => {
const shareData = {
documentId: props.document.id,
recipients: emailList.value,
accessLevel: accessLevel.value,
expiration: expiration.value,
customDate: customDate.value,
message: shareMessage.value,
notifyOnAccess: notifyOnAccess.value,
shareLink: shareLink.value
};
emit('shared', shareData);
closeModal();
};
// Utility functions
const getFileTypeIcon = (fileName) => {
if (!fileName) return 'mdi:file-document';
const extension = fileName.split('.').pop()?.toLowerCase();
const iconMap = {
pdf: 'mdi:file-pdf-box',
doc: 'mdi:file-word-box',
docx: 'mdi:file-word-box',
xls: 'mdi:file-excel-box',
xlsx: 'mdi:file-excel-box',
ppt: 'mdi:file-powerpoint-box',
pptx: 'mdi:file-powerpoint-box',
txt: 'mdi:file-document-outline',
md: 'mdi:language-markdown',
jpg: 'mdi:file-image',
jpeg: 'mdi:file-image',
png: 'mdi:file-image',
gif: 'mdi:file-image',
default: 'mdi:file-document'
};
return iconMap[extension] || iconMap.default;
};
const getFileTypeColor = (fileName) => {
if (!fileName) return 'text-gray-400';
const extension = fileName.split('.').pop()?.toLowerCase();
const colorMap = {
pdf: 'text-red-400',
doc: 'text-blue-400',
docx: 'text-blue-400',
xls: 'text-green-400',
xlsx: 'text-green-400',
ppt: 'text-orange-400',
pptx: 'text-orange-400',
txt: 'text-gray-400',
md: 'text-purple-400',
jpg: 'text-purple-400',
jpeg: 'text-purple-400',
png: 'text-purple-400',
gif: 'text-purple-400',
default: 'text-gray-400'
};
return colorMap[extension] || colorMap.default;
};
const formatFileSize = (size) => {
if (typeof size === 'string') return size;
if (!size) return '0 B';
const units = ['B', 'KB', 'MB', 'GB'];
let index = 0;
while (size >= 1024 && index < units.length - 1) {
size /= 1024;
index++;
}
return `${size.toFixed(1)} ${units[index]}`;
};
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString();
};
</script>
<style scoped>
/* Custom scrollbar for email list */
.email-list::-webkit-scrollbar {
width: 4px;
}
.email-list::-webkit-scrollbar-track {
background: #f1f5f9;
}
.email-list::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 2px;
}
.email-list::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* Dark mode scrollbar */
.dark .email-list::-webkit-scrollbar-track {
background: #334155;
}
.dark .email-list::-webkit-scrollbar-thumb {
background: #475569;
}
.dark .email-list::-webkit-scrollbar-thumb:hover {
background: #64748b;
}
</style>