Skip to content

Commit

Permalink
proponent file upload
Browse files Browse the repository at this point in the history
  • Loading branch information
sanjaytkbabu committed Aug 20, 2024
1 parent 5c12065 commit caec6db
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/environments/values.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ config:
FRONTEND_CHES_ROADMAP_BCC: NRM.PermittingAndData@gov.bc.ca
FRONTEND_CHES_SUBMISSION_CC: NRM.PermittingAndData@gov.bc.ca
FRONTEND_COMS_APIPATH: https://coms-dev.api.gov.bc.ca/api/v1
FRONTEND_COMS_BUCKETID: 1f9e1451-c130-4804-aeb0-b78b5b109c47
FRONTEND_COMS_BUCKETID: 5aa446ca-23c5-4f3b-9300-d8623bc4d101
FRONTEND_GEOCODER_APIPATH: https://geocoder.api.gov.bc.ca
FRONTEND_OIDC_AUTHORITY: https://dev.loginproxy.gov.bc.ca/auth/realms/standard
FRONTEND_OIDC_CLIENTID: nr-permit-connect-navigator-service-5188
Expand Down
197 changes: 197 additions & 0 deletions frontend/src/components/file/AdvancedFileUpload.vue
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>
4 changes: 3 additions & 1 deletion frontend/src/components/file/DeleteDocument.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import type { Document } from '@/types';
// Props
type Props = {
document: Document;
disabled?: boolean;
};
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), { disabled: false });
// Store
const submissionStore = useSubmissionStore();
Expand Down Expand Up @@ -43,6 +44,7 @@ const confirmDelete = (document: Document) => {
<template>
<Button
v-tooltip.bottom="'Delete document'"
:disabled="props.disabled"
class="p-button-lg p-button-text p-button-danger p-0 align-self-center"
aria-label="Delete object"
style="position: relative; top: 5; right: 0"
Expand Down
110 changes: 110 additions & 0 deletions frontend/src/components/file/DocumentCardLite.vue
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>
Loading

0 comments on commit caec6db

Please sign in to comment.