budget-go/frontend/js/views/FilesView.vue
2025-05-28 17:01:20 +02:00

382 lines
8.4 KiB
Vue

<template>
<div
v-if="tree !== null && fileId === null"
class="w-100"
>
<CrudHeader title="Fichiers">
<template #menu>
<BButtonToolbar key-nav>
<BButton
variant="primary"
class="me-2"
@click="doAddFile"
>+ fichier</BButton
>
<BButton
variant="primary"
@click="doAddDirectory"
>+ Dossier</BButton
>
</BButtonToolbar>
</template>
</CrudHeader>
<div
v-if="!tree.is_root"
class="p-3 border-bottom cursor"
@click="cd(tree.parent)"
>
<span class="text-warning">
<i class="fa-solid fa-folder me-2"></i>
</span>
..
</div>
<div
v-for="(item, key) in tree.directories"
:key="key"
class="p-3 border-bottom flex justify-content-center"
>
<div class="float-end">
<BDropdown
text=""
size="sm"
variant="outline-primary"
>
<BDropdownItem @click="doDelete(item)">Supprimer</BDropdownItem>
</BDropdown>
</div>
<div
class="cursor"
@click="cd(item)"
>
<span class="text-warning cursor">
<i class="fa-solid fa-folder me-2"></i>
</span>
{{ item.name }}
</div>
</div>
<div
v-for="item in tree.files"
:key="item"
>
<div class="float-end pt-3 me-3">
<BDropdown
text=""
size="sm"
variant="outline-primary"
>
<BDropdownItem
:href="`/api/filemanager/file/${item.id}/${item.name}`"
target="_blank"
>Télécharger</BDropdownItem
>
<BDropdownItem @click="doDelete(item)">Supprimer</BDropdownItem>
</BDropdown>
</div>
<div
v-if="isEditable(item)"
class="p-3 border-bottom cursor"
@click="doEdit(item)"
>
<span class="text-success">
<i class="fa-solid fa-file-lines"></i>
</span>
{{ item.name }}
</div>
<div
v-else
class="p-3 border-bottom cursor"
>
<a
:href="`/api/filemanager/file/${item.id}/${item.name}`"
target="_blank"
>
<i class="fa-regular fa-file"></i>
{{ item.name }}
</a>
</div>
</div>
</div>
<div
v-if="fileId !== null"
class="w-100"
>
<div
class="text-end cursor p-2 bg-secondary-subtle"
@click="fileId = null"
>
<i class="fa-solid fa-xmark"></i>
</div>
<iframe
id="collabora"
title="Fichier"
:src="'/collabora/' + fileId + '?extension=' + fileExtension"
></iframe>
</div>
<BModal
v-if="form !== null"
v-model="formShow"
:title="form?.label"
@ok="doSave"
>
<BAlert
:model-value="form.error !== null"
variant="danger"
>{{ form.error }}</BAlert
>
<BForm @submit="doSave">
<BFormGroup
v-for="(field, key) in form.fields"
:id="'form-label-' + key"
:key="key"
class="mb-2"
:label="field.label"
:label-for="'form-label-' + key"
:description="field.description"
>
<BFormInput
v-if="(field.widget ?? 'text') === 'text'"
:id="'form-input-' + key"
v-model="form.data[field.key]"
:type="field.type"
:required="field.required"
/>
<BFormFile
v-if="field.widget === 'file'"
:id="'form-input-' + key"
v-model="form.data[field.key]"
:required="field.required"
/>
<BFormSelect
v-if="field.widget === 'select'"
:id="'form-input-' + key"
v-model="form.data[field.key]"
:options="field.options"
:required="field.required"
/>
</BFormGroup>
</BForm>
<template #footer>
<div></div>
<div>
<BButton
variant="secondary"
class="me-2"
@click="formShow = false"
>Annuler</BButton
>
<BButton
variant="primary"
@click="doSave"
>OK</BButton
>
</div>
</template>
</BModal>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import CrudHeader from './../components/crud/CrudHeader.vue'
import { BModal, BButton, BForm, BFormGroup, BFormInput, BAlert, BFormSelect, BFormFile, BDropdown, BDropdownItem } from 'bootstrap-vue-next'
import { createRequestOptions } from '../lib/request'
let formFile = false
const tree = ref(null)
const fileId = ref(null)
const fileExtension = ref(null)
const form = ref(null)
const formShow = ref(false)
const cd = (directory) => {
tree.value = directory
}
const doEdit = (item) => {
fileId.value = item.id
fileExtension.value = item.extension.replace(/^\./, '')
}
const withParents = (tree) => {
tree.directories.forEach((subTree, key) => {
tree.directories[key].parent = tree
tree.directories[key] = withParents(tree.directories[key])
})
return tree
}
const isEditable = (item) => {
return ['application/vnd.oasis.opendocument.spreadsheet', 'text/csv'].includes(item.type)
}
const doAddFile = () => {
const data = { file: null }
formFile = true
form.value = {
action: `/api/filemanager/file`,
method: 'POST',
data: data,
label: 'Nouveau fichier',
error: null,
fields: [
{
label: 'Fichier',
description: `Fichier ODT`,
widget: 'file',
required: true,
key: 'file',
},
],
}
formShow.value = true
}
const doAddDirectory = () => {
const data = { directory: null, path: tree.value.id }
formFile = false
form.value = {
action: `/api/filemanager/directory`,
method: 'POST',
data: data,
label: 'Nouveau dossier',
error: null,
fields: [
{
label: 'Nom',
widget: 'text',
required: true,
key: 'directory',
},
],
}
formShow.value = true
}
const doDelete = (item) => {
if (!confirm('Je confirme la suppression')) {
return
}
fetch(
`/api/filemanager/file/${item.id}`,
createRequestOptions({
method: 'DELETE',
}),
).then(() => {
refresh({ current: tree.value.id })
})
}
const doSave = (e) => {
e.preventDefault()
if (formFile) {
const payload = new FormData()
payload.append('file', form.value.data.file)
payload.append('path', tree.value.id)
fetch(
`/api/filemanager/file`,
createRequestOptions({
method: form.value.method,
headers: {
Accept: 'application/json',
},
body: payload,
}),
)
.then((response) => {
return response.json()
})
.then((data) => {
if (data && data.code === 400) {
form.value.error = data.message
} else {
form.value = null
formShow.value = false
refresh({ current: tree.value.id })
}
})
.catch((err) => {
form.value.error = `Une erreur s'est produite : ${err}`
})
} else {
fetch(
`/api/filemanager/directory`,
createRequestOptions({
method: form.value.method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(form.value.data),
}),
)
.then((response) => {
return response.json()
})
.then((data) => {
if (data && data.code === 400) {
form.value.error = data.message
} else {
form.value = null
formShow.value = false
refresh({ current: tree.value.id })
}
})
.catch((err) => {
form.value.error = `Une erreur s'est produite : ${err}`
})
}
}
const refresh = (options) => {
options = options || {}
fetch('/api/filemanager/files', createRequestOptions())
.then((response) => response.json())
.then((data) => {
tree.value = withParents(data)
if (options.current) {
const tr = searchTree(options.current, data)
if (tr) {
cd(tr)
}
}
})
}
const searchTree = (id, tree) => {
if (tree.id === id) {
return tree
}
for (let d of tree.directories) {
const tr = searchTree(id, d)
if (tr) {
return tr
}
}
return null
}
onMounted(() => refresh())
</script>
<style scoped>
a {
text-decoration: none;
color: var(--bs-body-color);
}
</style>