-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5c12065
commit caec6db
Showing
6 changed files
with
373 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
<script setup lang="ts"> | ||
import { storeToRefs } from 'pinia'; | ||
import { computed, ref } from 'vue'; | ||
import { Button, FileUpload, ProgressBar, useToast } from '@/lib/primevue'; | ||
import DocumentCardLite from '@/components/file/DocumentCardLite.vue'; | ||
import { documentService } from '@/services'; | ||
import { useConfigStore, useSubmissionStore } from '@/store'; | ||
import type { FileUploadUploaderEvent } from 'primevue/fileupload'; | ||
import type { Ref } from 'vue'; | ||
// Props | ||
type Props = { | ||
activityId?: string; | ||
accept?: string[]; | ||
reject?: string[]; | ||
disabled?: boolean; | ||
}; | ||
const props = withDefaults(defineProps<Props>(), { | ||
activityId: undefined, | ||
accept: undefined, | ||
disabled: false, | ||
reject: undefined | ||
}); | ||
// Store | ||
const { getConfig } = storeToRefs(useConfigStore()); | ||
const submissionStore = useSubmissionStore(); | ||
// State | ||
const fileInput: Ref<any> = ref(null); | ||
const uploading: Ref<Boolean> = ref(false); | ||
// Actions | ||
const toast = useToast(); | ||
const onFileUploadClick = () => { | ||
if (props.disabled) { | ||
toast.info('Document uploading is currently disabled'); | ||
return; | ||
} | ||
fileInput.value.click(); | ||
}; | ||
const onFileUploadDragAndDrop = (event: FileUploadUploaderEvent) => { | ||
if (props.disabled) { | ||
toast.info('Document uploading is currently disabled'); | ||
return; | ||
} | ||
onUpload(Array.isArray(event.files) ? event.files : [event.files]); | ||
}; | ||
const onUpload = async (files: Array<File>) => { | ||
await Promise.allSettled( | ||
files.map(async (file: File) => { | ||
try { | ||
if (props.activityId) { | ||
uploading.value = true; | ||
const response = (await documentService.createDocument(file, props.activityId, getConfig.value.coms.bucketId)) | ||
?.data; | ||
if (response) { | ||
submissionStore.addDocument(response); | ||
toast.success('Document uploaded'); | ||
} | ||
} else throw new Error('No activityId'); | ||
} catch (e: any) { | ||
toast.error('Failed to upload document', e); | ||
} finally { | ||
uploading.value = false; | ||
} | ||
}) | ||
); | ||
}; | ||
// filter documents based on accept and reject props | ||
// if accept and reject are not provided, all documents are shown | ||
// if accept is provided, only documents with extensions in accept are shown | ||
// if reject is provided, only documents with extensions not in reject are shown | ||
const filteredDocuments = computed(() => { | ||
let documents = submissionStore.getDocuments; | ||
return documents.filter( | ||
(document) => | ||
(!props.accept && !props.reject) || | ||
props.accept?.some((ext) => document.filename.endsWith(ext)) || | ||
props.reject?.every((ext) => !document.filename.endsWith(ext)) || | ||
document.filename.endsWith('.pdf') | ||
); | ||
}); | ||
</script> | ||
|
||
<template> | ||
<div class="mb-3 border-dashed file-upload border-round-md w-100"> | ||
<div> | ||
<div | ||
v-if="uploading" | ||
class="h-4rem align-content-center pl-2 pr-2" | ||
> | ||
<ProgressBar | ||
mode="indeterminate" | ||
class="align-self-center progress-bar" | ||
/> | ||
</div> | ||
<div | ||
v-if="!uploading" | ||
class="hover-hand hover-shadow" | ||
> | ||
<FileUpload | ||
name="fileUpload" | ||
:multiple="true" | ||
:custom-upload="true" | ||
:auto="true" | ||
:disabled="props.disabled" | ||
@uploader="onFileUploadDragAndDrop" | ||
> | ||
<template #empty> | ||
<div class="flex align-items-center justify-content-center flex-column"> | ||
<Button | ||
aria-label="Upload" | ||
class="justify-content-center w-full h-4rem border-none" | ||
@click="onFileUploadClick" | ||
> | ||
<font-awesome-icon | ||
class="pr-2" | ||
icon="fa-solid fa-upload" | ||
/> | ||
Click or drag-and-drop | ||
</Button> | ||
</div> | ||
</template> | ||
</FileUpload> | ||
|
||
<input | ||
ref="fileInput" | ||
type="file" | ||
class="hidden" | ||
:accept="props.accept ? props.accept.join(',') : '*'" | ||
multiple | ||
@change="(event: any) => onUpload(Array.from(event.target.files))" | ||
@click="(event: any) => (event.target.value = null)" | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div class="grid w-full"> | ||
<div | ||
v-for="(document, index) in filteredDocuments" | ||
:key="document.documentId" | ||
:index="index" | ||
class="col-4" | ||
> | ||
<DocumentCardLite | ||
:document="document" | ||
:delete-button="!props.disabled" | ||
class="hover-hand hover-shadow mb-2" | ||
@click="documentService.downloadDocument(document.documentId, document.filename)" | ||
/> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<style scoped lang="scss"> | ||
:deep(.p-fileupload-buttonbar) { | ||
display: none; | ||
} | ||
:deep(.p-fileupload-content) { | ||
padding: 0; | ||
border: none; | ||
} | ||
.file-input { | ||
display: none; | ||
} | ||
.p-button.p-component { | ||
background-color: transparent; | ||
color: var(--text-color); | ||
} | ||
.progress-bar { | ||
height: 0.3rem; | ||
} | ||
.file-upload { | ||
width: 100%; | ||
color: $app-out-of-focus; | ||
&:hover { | ||
color: $app-hover; | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<script setup lang="ts"> | ||
import { filesize } from 'filesize'; | ||
import { ref } from 'vue'; | ||
import DeleteDocument from '@/components/file/DeleteDocument.vue'; | ||
import { Card } from '@/lib/primevue'; | ||
import { formatDateLong } from '@/utils/formatters'; | ||
import type { Ref } from 'vue'; | ||
import type { Document } from '@/types'; | ||
// Props | ||
type Props = { | ||
deleteButton?: boolean; | ||
document: Document; | ||
selectable?: boolean; | ||
selected?: boolean; | ||
}; | ||
const props = withDefaults(defineProps<Props>(), { | ||
deleteButton: true, | ||
selectable: false, | ||
selected: false | ||
}); | ||
// Emits | ||
const emit = defineEmits(['document:clicked']); | ||
// State | ||
const isSelected: Ref<boolean> = ref(props.selected); | ||
// Actions | ||
function onClick() { | ||
if (props.selectable) { | ||
isSelected.value = !isSelected.value; | ||
emit('document:clicked', { document: props.document, selected: isSelected.value }); | ||
} | ||
} | ||
</script> | ||
|
||
<template> | ||
<Card | ||
class="pb-1 text-center border-round-xl" | ||
:class="{ clicked: isSelected }" | ||
@click="onClick" | ||
> | ||
<template #content> | ||
<div class="grid"> | ||
<div | ||
v-tooltip.bottom="`${props.document.filename} Uploaded by ${props.document.createdByFullName}`" | ||
class="col-12 mb-0 text-left font-semibold text-overflow-ellipsis white-space-nowrap mt-2" | ||
style="overflow: hidden" | ||
> | ||
<a href="#">{{ props.document.filename }}</a> | ||
</div> | ||
<h6 class="col-8 text-left mt-0 mb-0 pt-0 pb-0"> | ||
{{ formatDateLong(props.document.createdAt as string).split(',')[0] }}, | ||
</h6> | ||
<h6 class="col-8 text-left mt-1 mb-0 pt-0 pb-0"> | ||
{{ formatDateLong(props.document.createdAt as string).split(',')[1] }} | ||
</h6> | ||
</div> | ||
</template> | ||
<template #footer> | ||
<div class="flex justify-content-between ml-3 mr-3 align-items-center"> | ||
<h6 class="col-4 text-left mt-0 mb-0 pl-0 inline-block"> | ||
{{ filesize(props.document.filesize) }} | ||
</h6> | ||
<DeleteDocument | ||
:document="props.document" | ||
:disabled="!props.deleteButton" | ||
/> | ||
</div> | ||
</template> | ||
</Card> | ||
</template> | ||
|
||
<style scoped lang="scss"> | ||
.document-image { | ||
max-height: 2.5rem; | ||
position: relative; | ||
top: 50%; | ||
transform: translateY(-50%); | ||
} | ||
.clicked { | ||
box-shadow: 0 0 11px #036; | ||
} | ||
:deep(.p-card-header) { | ||
height: 5rem; | ||
background-color: lightgray; | ||
border-radius: 10px 10px 0 0; | ||
} | ||
:deep(.p-card-content) { | ||
padding-top: 0; | ||
padding-bottom: 0; | ||
} | ||
:deep(.p-card-footer) { | ||
padding: 0; | ||
text-align: right; | ||
} | ||
:deep(.p-card-body) { | ||
padding-bottom: 0.4em; | ||
padding-top: 0.5em; | ||
} | ||
</style> |
Oops, something went wrong.