initial: bootstrap from BukidBountyApp base
This commit is contained in:
214
resources/js/Pages/Fragments/DocumentRepository.vue
Normal file
214
resources/js/Pages/Fragments/DocumentRepository.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const props = defineProps({
|
||||
orgHash: String
|
||||
});
|
||||
|
||||
const documents = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const fileInput = ref(null);
|
||||
const revisionInput = ref(null);
|
||||
const activeDocForRevision = ref(null);
|
||||
const expandedHistory = ref({});
|
||||
|
||||
const fetchDocuments = async () => {
|
||||
if (!props.orgHash) return;
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const response = await axios.post('/Cooperatives/Documents/List', { orgHash: props.orgHash });
|
||||
if (response.data.success) {
|
||||
documents.value = response.data.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch documents:', error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const triggerUpload = () => {
|
||||
fileInput.value.click();
|
||||
};
|
||||
|
||||
const handleFileUpload = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('orgHash', props.orgHash);
|
||||
formData.append('type', 'OTHERS');
|
||||
|
||||
try {
|
||||
if (window.toastr) window.toastr.info('Uploading document...');
|
||||
const response = await axios.post('/Cooperatives/Documents/Upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
if (window.toastr) window.toastr.success('Document uploaded successfully');
|
||||
fetchDocuments();
|
||||
} else {
|
||||
if (window.toastr) window.toastr.error(response.data.error || 'Upload failed');
|
||||
}
|
||||
} catch (error) {
|
||||
if (window.toastr) window.toastr.error('Failed to upload document');
|
||||
console.error(error);
|
||||
} finally {
|
||||
event.target.value = ''; // Reset input
|
||||
}
|
||||
};
|
||||
|
||||
const triggerRevision = (doc) => {
|
||||
activeDocForRevision.value = doc;
|
||||
revisionInput.value.click();
|
||||
};
|
||||
|
||||
const handleRevisionUpload = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file || !activeDocForRevision.value) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('parentHash', activeDocForRevision.value.hashkey);
|
||||
formData.append('note', 'New version');
|
||||
|
||||
try {
|
||||
if (window.toastr) window.toastr.info('Uploading revision...');
|
||||
const response = await axios.post('/Cooperatives/Documents/Revise', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
if (window.toastr) window.toastr.success('Revision uploaded successfully');
|
||||
fetchDocuments();
|
||||
} else {
|
||||
if (window.toastr) window.toastr.error(response.data.error || 'Revision failed');
|
||||
}
|
||||
} catch (error) {
|
||||
if (window.toastr) window.toastr.error('Failed to upload revision');
|
||||
console.error(error);
|
||||
} finally {
|
||||
event.target.value = ''; // Reset input
|
||||
activeDocForRevision.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleHistory = (doc) => {
|
||||
expandedHistory.value[doc.hashkey] = !expandedHistory.value[doc.hashkey];
|
||||
};
|
||||
|
||||
const downloadDoc = (doc) => {
|
||||
if (doc.url) {
|
||||
window.open(doc.url, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
const getFileIcon = (type) => {
|
||||
if (type === 'PDF') return 'fas fa-file-pdf';
|
||||
if (['JPG', 'PNG', 'JPEG'].includes(type)) return 'fas fa-file-image';
|
||||
return 'fas fa-file-alt';
|
||||
};
|
||||
|
||||
const getIconBg = (type) => {
|
||||
if (type === 'PDF') return 'bg-soft-danger text-danger';
|
||||
if (['JPG', 'PNG', 'JPEG'].includes(type)) return 'bg-soft-primary text-primary';
|
||||
return 'bg-soft-secondary text-secondary';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchDocuments();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="document-repository mt-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="fw_7 mb-0">Documents & Records</h5>
|
||||
<div>
|
||||
<input type="file" ref="fileInput" class="d-none" @change="handleFileUpload">
|
||||
<button class="btn btn-primary rounded-pill btn-sm px-3 shadow-sm" @click="triggerUpload" :disabled="isLoading">
|
||||
<i class="fas fa-upload me-1"></i> Upload Document
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="text-center py-5">
|
||||
<div class="spinner-border text-primary spinner-border-sm" role="status"></div>
|
||||
<p class="text-muted smallest mt-2">Loading documents...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="documents.length === 0" class="text-center py-5 bg-light rounded-20 opacity-75">
|
||||
<i class="fas fa-folder-open fa-3x text-muted mb-3 opacity-25"></i>
|
||||
<p class="text-muted mb-0">No documents found</p>
|
||||
<p class="smallest text-muted">Upload important files for this organization</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="document-list">
|
||||
<input type="file" ref="revisionInput" class="d-none" @change="handleRevisionUpload">
|
||||
|
||||
<div v-for="doc in documents" :key="doc.hashkey" class="mb-3">
|
||||
<div class="card border-0 shadow-sm rounded-20 p-3 hover-card" @click="downloadDoc(doc)">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div :class="[getIconBg(doc.type), 'rounded-circle p-2 flex-shrink-0']" style="width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">
|
||||
<i :class="getFileIcon(doc.type)"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<h6 class="fw_6 mb-1 text-truncate">{{ doc.name }}</h6>
|
||||
<div class="d-flex flex-wrap gap-2 text-muted smallest">
|
||||
<span class="badge bg-light text-dark rounded-pill px-2">V{{ doc.version }}</span>
|
||||
<span>{{ doc.date }}</span>
|
||||
<span>{{ doc.size }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-pill px-3" @click.stop="triggerRevision(doc)">
|
||||
<i class="fas fa-edit me-1"></i> Revise
|
||||
</button>
|
||||
<button v-if="doc.history && doc.history.length > 1"
|
||||
class="btn btn-icon btn-light rounded-circle shadow-sm flex-shrink-0"
|
||||
style="width: 32px; height: 32px;"
|
||||
:class="{'rotate-180': expandedHistory[doc.hashkey]}"
|
||||
@click.stop="toggleHistory(doc)">
|
||||
<i class="fas fa-chevron-down smallest"></i>
|
||||
</button>
|
||||
<button class="btn btn-icon btn-primary rounded-circle shadow-sm flex-shrink-0"
|
||||
style="width: 36px; height: 36px;"
|
||||
@click.stop="downloadDoc(doc)">
|
||||
<i class="fas fa-download small"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- History Section -->
|
||||
<div v-if="expandedHistory[doc.hashkey]" class="history-list mt-2 ms-4 border-start ps-3">
|
||||
<div v-for="h in doc.history.slice(1)" :key="h.hashkey" class="history-item d-flex align-items-center gap-2 mb-2 p-2 bg-light rounded-15" @click="downloadDoc(h)">
|
||||
<span class="smallest fw_6 text-muted">V{{ h.version }}</span>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<p class="smallest mb-0 text-truncate">{{ h.name }}</p>
|
||||
<p class="smallest text-muted mb-0">{{ h.date }} • {{ h.note || 'No note' }}</p>
|
||||
</div>
|
||||
<i class="fas fa-download smallest text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.rounded-20 { border-radius: 20px; }
|
||||
.rounded-15 { border-radius: 15px; }
|
||||
.bg-soft-danger { background-color: rgba(220, 53, 69, 0.1); }
|
||||
.bg-soft-primary { background-color: rgba(13, 110, 253, 0.1); }
|
||||
.bg-soft-secondary { background-color: rgba(108, 117, 125, 0.1); }
|
||||
.hover-card { cursor: pointer; transition: all 0.2s; }
|
||||
.hover-card:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important; }
|
||||
.smallest { font-size: 0.75rem; }
|
||||
.rotate-180 { transform: rotate(180deg); }
|
||||
.history-item { cursor: pointer; transition: background 0.2s; }
|
||||
.history-item:hover { background-color: #e9ecef !important; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user