442 lines
13 KiB
Vue
442 lines
13 KiB
Vue
<template>
|
|
<div :class="`directories-${intId} position-relative`">
|
|
<Transition name="fade">
|
|
<p
|
|
v-if="intMessage"
|
|
class="d-flex align-items-center text-danger mb-2"
|
|
>
|
|
<span>{{ Object.values(intMessage)[0] }} </span>
|
|
<a
|
|
class="btn btn-sm btn-primary"
|
|
:href="getEditButtonRoute(Number(Object.keys(intMessage)[0]))"
|
|
:title="trans.methods.__('Modifier ce paramètre')"
|
|
data-bs="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-pencil" />
|
|
</a>
|
|
</p>
|
|
</Transition>
|
|
<vue-nestable
|
|
:value="intDirectories"
|
|
@change="updateOrderDirectories"
|
|
@input="intDirectories = $event"
|
|
:hooks="{ beforeMove: beforeMove }"
|
|
>
|
|
<template
|
|
#default="{
|
|
item,
|
|
}: {
|
|
item: {
|
|
id: number,
|
|
name: string[],
|
|
needAuthorization: boolean,
|
|
documents_count: number,
|
|
children: number[]
|
|
},
|
|
}"
|
|
>
|
|
<vue-nestable-handle
|
|
class="d-flex justify-content-between align-items-center border rounded p-1"
|
|
>
|
|
<div class="d-flex justify-content-start align-items-center">
|
|
<div
|
|
v-if="
|
|
typeof item.children != 'undefined' && item.children.length > 0
|
|
"
|
|
>
|
|
<button
|
|
v-if="intDisplay[item.id]"
|
|
@click="displayList($event, item.id)"
|
|
class="btn btn-sm btn-outline-dark h-auto"
|
|
:title="trans.methods.__('Dérouler la liste')"
|
|
data-bs="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-arrow-down" />
|
|
</button>
|
|
<button
|
|
v-else
|
|
@click="displayList($event, item.id)"
|
|
class="btn btn-sm btn-outline-dark h-auto"
|
|
:title="trans.methods.__('Rouler la liste')"
|
|
data-bs="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-arrow-up" />
|
|
</button>
|
|
</div>
|
|
<button class="btn btn-sm border-0 disabled opacity-100">
|
|
<FontAwesomeIcon icon="fa-solid fa-grip-vertical" />
|
|
</button>
|
|
<p class="m-0">
|
|
<span class="badge d-flex align-items-center p-1 pe-2 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-pill">
|
|
<FontAwesomeIcon
|
|
icon="fa-solid fa-file"
|
|
class="rounded-circle me-1"
|
|
/>
|
|
{{ item.documents_count }}
|
|
</span>
|
|
</p>
|
|
|
|
<p class="m-0">
|
|
{{ item.name[("fr" as keyof typeof item.name) as number] }}
|
|
</p>
|
|
</div>
|
|
<div
|
|
class="d-flex justify-content-end align-items-center"
|
|
>
|
|
<div class="input-group">
|
|
<button
|
|
v-if="item.needAuthorization"
|
|
@click="updateAuthorization(item)"
|
|
class="btn btn-sm btn-warning"
|
|
:title="trans.methods.__('L\'accès à ce dossier est restreint')"
|
|
data-bs="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-lock" />
|
|
</button>
|
|
<button
|
|
v-else
|
|
@click="updateAuthorization(item)"
|
|
class="btn btn-sm btn-success"
|
|
:title="trans.methods.__('L\'accès à ce dossier est tout public')"
|
|
data-bs="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-lock-open" />
|
|
</button>
|
|
<a
|
|
:href="getEditDirectoryRoute(item.id)"
|
|
class="btn btn-sm btn-primary"
|
|
:title="trans.methods.__('Modifier le Dossier')"
|
|
data-bs="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-pencil" />
|
|
</a>
|
|
<button
|
|
@click="deleteDirectory($event, item as never as DirectoryObject)"
|
|
class="btn btn-sm btn-danger confirmDelete"
|
|
:title="trans.methods.__('Supprimer le Dossier')"
|
|
data-bs="tooltip"
|
|
ref="confirmDelete"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-trash" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</vue-nestable-handle>
|
|
</template>
|
|
</vue-nestable>
|
|
<div
|
|
v-if="intLoading"
|
|
class="loading position-absolute top-0 start-0 d-flex justify-content-center align-items-center rounded-1 w-100 h-100"
|
|
>
|
|
<div
|
|
class="spinner-border text-secondary"
|
|
role="status"
|
|
>
|
|
<span class="visually-hidden">
|
|
{{ trans.methods.__("form.image_input_viewer_loading") }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|
import { Tooltip } from "bootstrap";
|
|
import { onMounted, nextTick, ref, useAttrs } from "vue";
|
|
import { VueNestable, VueNestableHandle } from "vue3-nestable";
|
|
import parseLaravelErrorMessages from "../../modules/parseLaravelErrorMessages";
|
|
import route from "../../modules/route";
|
|
import sweetalertConfirm from "../../modules/sweetalertConfirm";
|
|
import toast from "../../modules/toast";
|
|
import trans from "../../modules/trans";
|
|
|
|
defineOptions({
|
|
name: "DirectoriesComponent"
|
|
});
|
|
|
|
// * ATTRIBUTES
|
|
const attrs = useAttrs();
|
|
|
|
// * REFS
|
|
const confirmDelete = ref<HTMLButtonElement|null>(null);
|
|
|
|
// * DATA
|
|
const intId = ref<string>("");
|
|
const intDirectories = ref<Array<DirectoryObject>>([]);
|
|
const intMessage = ref<object|object[]|null>(null);
|
|
const intDisplay = ref<boolean[]>([]);
|
|
const intLoading = ref<boolean>(false);
|
|
const intTooltipList = ref<HTMLButtonElement[]>([]);
|
|
|
|
onMounted(() => {
|
|
const json = String((attrs as any).json ?? "{}");
|
|
const data = JSON.parse(json);
|
|
|
|
intId.value = data.id;
|
|
if (data.directories) {
|
|
intDirectories.value = recursiveDir(data.directories);
|
|
}
|
|
nextTick(() => {
|
|
updateBootstrapTooltip();
|
|
});
|
|
});
|
|
|
|
/** Prepare the list of items. */
|
|
function recursiveDir(list: DirectoryObject[], parent: number | null = null) {
|
|
let root = list.filter((e: DirectoryObject) => e.directory_id == parent);
|
|
for (let i = 0; i < root.length; i++) {
|
|
let childs = recursiveDir(list, root[i].id);
|
|
root[i].children = [];
|
|
for (let j = 0; j < childs.length; j++) {
|
|
root[i].children?.push(childs[j]);
|
|
}
|
|
if (!root[i].children?.length) {
|
|
delete root[i].children;
|
|
}
|
|
}
|
|
return root;
|
|
}
|
|
|
|
/** Check depth before the move. */
|
|
function beforeMove({
|
|
pathTo,
|
|
}: {
|
|
pathTo: number[];
|
|
}) {
|
|
if (pathTo.length == 4) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Update data's directories. */
|
|
function updateOrderDirectories() {
|
|
let newOrder = assignOrder(intDirectories.value, null);
|
|
const routePath = route.methods.route("bo.directories.save-order");
|
|
if (!routePath) {
|
|
throw new Error("Undefined route bo.directories.save-order");
|
|
}
|
|
window.axios.post(routePath, { directories: newOrder }).catch(ajaxErrorHandler);
|
|
nextTick(() => {
|
|
updateBootstrapTooltip();
|
|
});
|
|
}
|
|
|
|
/** Assign the new order and parent id to each directory. */
|
|
function assignOrder(children: DirectoryObject[], parentId: number | null) {
|
|
children.forEach((element, index) => {
|
|
element.order = index;
|
|
element.directory_id = parentId;
|
|
if (element.children && element.children.length) {
|
|
element.children = assignOrder(element.children, element.id);
|
|
} else {
|
|
element.children = [];
|
|
}
|
|
});
|
|
return children;
|
|
}
|
|
|
|
/** Return edit route for a directory. */
|
|
function getEditDirectoryRoute(id: number): string {
|
|
const routePath = route.methods.route("bo.directories.edit", {
|
|
ID: id,
|
|
});
|
|
if (!routePath) {
|
|
throw new Error("Undefined route bo.directories.edit");
|
|
}
|
|
return routePath;
|
|
}
|
|
|
|
/** Return edit route for a directory. */
|
|
function getEditButtonRoute(id: number): string {
|
|
const routePath = route.methods.route("bo.settings.edit", {
|
|
ID: id,
|
|
});
|
|
if (!routePath) {
|
|
throw new Error("Undefined route bo.settings.edit");
|
|
}
|
|
return routePath;
|
|
}
|
|
|
|
/** Show/Hide children. */
|
|
function displayList(e: Event, id: number) {
|
|
intDisplay.value[id] = intDisplay.value[id] ? false : true;
|
|
document
|
|
.querySelector(".nestable-item-" + id + " .nestable-list")
|
|
?.classList.toggle("visually-hidden");
|
|
nextTick(() => {
|
|
updateBootstrapTooltip();
|
|
});
|
|
}
|
|
|
|
/** Update authorization for a specific directory. */
|
|
function updateAuthorization(model: LaravelModel) {
|
|
intLoading.value = true;
|
|
const routePath = route.methods.route("bo.directories.need-authorization", {
|
|
ID: model.id as number,
|
|
});
|
|
if (!routePath) {
|
|
throw new Error("Undefined route bo.directories.need-authorization");
|
|
}
|
|
window.axios
|
|
.post(routePath, { id: model.id })
|
|
.then((reponse) => {
|
|
model.needAuthorization = reponse.data.needAuthorization;
|
|
intLoading.value = false;
|
|
nextTick(() => {
|
|
updateBootstrapTooltip();
|
|
});
|
|
})
|
|
.catch(ajaxErrorHandler);
|
|
}
|
|
|
|
/** Update authorization for a specific directory. */
|
|
function deleteDirectory(e: Event, model: DirectoryObject) {
|
|
let promise:Promise<boolean>|null = null;
|
|
(async () => {
|
|
if (model.children?.length) {
|
|
promise = new Promise((resolve: (value: boolean) => void) => {
|
|
sweetalertConfirm.methods.confirm(
|
|
"Les dossiers enfants seront déplacés plus haut",
|
|
"Êtes vous sure ?",
|
|
confirmDelete,
|
|
function (response) {
|
|
resolve(response.isConfirmed);
|
|
},
|
|
{ icon: "warning" }
|
|
);
|
|
return false;
|
|
});
|
|
}
|
|
if (promise !== null && await promise === false) {
|
|
return;
|
|
}
|
|
if (model.documents_count > 0) {
|
|
promise = new Promise((resolve: (value: boolean) => void) => {
|
|
sweetalertConfirm.methods.confirm(
|
|
"Les document enfants seront SUPPRIMÉS",
|
|
"Êtes vous sure ?",
|
|
confirmDelete,
|
|
function (response) {
|
|
resolve(response.isConfirmed);
|
|
},
|
|
{ icon: "warning" }
|
|
);
|
|
return false;
|
|
});
|
|
}
|
|
if (promise !== null && await promise === false) {
|
|
return;
|
|
}
|
|
promise = new Promise((resolve: (value: boolean) => void) => {
|
|
sweetalertConfirm.methods.confirm(
|
|
"Confirmez",
|
|
"Êtes vous sure ?",
|
|
confirmDelete,
|
|
function (response) {
|
|
resolve(response.isConfirmed);
|
|
},
|
|
{ icon: "warning" }
|
|
);
|
|
return false;
|
|
});
|
|
if (await promise) {
|
|
intLoading.value = true;
|
|
const routePath = route.methods.route("bo.directories.destroy", {
|
|
ID: model.id as number,
|
|
});
|
|
if (!routePath) {
|
|
throw new Error("Undefined route bo.directories.destroy");
|
|
}
|
|
window.axios
|
|
.post(routePath, { id: model.id, _method: "DELETE" })
|
|
.then((reponse) => {
|
|
if (reponse.data[0] !== undefined) {
|
|
intDirectories.value = recursiveDir(reponse.data);
|
|
} else {
|
|
intMessage.value = reponse.data;
|
|
setTimeout(() => {
|
|
intMessage.value = null;
|
|
nextTick(() => {
|
|
updateBootstrapTooltip();
|
|
});
|
|
}, 6000);
|
|
}
|
|
intLoading.value = false;
|
|
nextTick(() => {
|
|
updateBootstrapTooltip();
|
|
});
|
|
})
|
|
.catch(ajaxErrorHandler);
|
|
}
|
|
})();
|
|
}
|
|
|
|
/** Update Bootstrap tooltips. */
|
|
function updateBootstrapTooltip() {
|
|
let newTooltipList = [].slice.call(
|
|
document.querySelectorAll(
|
|
".directories-" + intId.value + " [data-bs=\"tooltip\"]"
|
|
)
|
|
) as HTMLButtonElement[];
|
|
let tmp = newTooltipList.filter((x) => !intTooltipList.value.includes(x));
|
|
tmp.map((tooltip) => {
|
|
return new Tooltip(tooltip);
|
|
});
|
|
closeBootstrapTooltip();
|
|
intTooltipList.value = newTooltipList;
|
|
}
|
|
|
|
/** Close all Bootstrap tooltips. */
|
|
function closeBootstrapTooltip() {
|
|
intTooltipList.value.forEach((tooltip) => {
|
|
tooltip.blur();
|
|
Tooltip.getInstance(tooltip)?.hide();
|
|
});
|
|
}
|
|
|
|
function ajaxErrorHandler(e: any) {
|
|
intLoading.value = false;
|
|
let message = trans.methods.__("Une erreur est survenue");
|
|
if (e.response.status === 422) {
|
|
message =
|
|
parseLaravelErrorMessages.methods.parseValidationErrors(
|
|
e.response?.data?.errors ?? {}
|
|
) || message;
|
|
if (window.vueDebug) {
|
|
console.warn(e.response.data.errors);
|
|
}
|
|
} else if (e.response.status === 419) {
|
|
// * CSRF TOKEN MISMATCH (On ne pourrais plus faire d'appels AJAX sans recharger la page)
|
|
toast.methods.errorNotify(trans.methods.__("Votre session a expiré, la page va être rechargée"));
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} else if (e.response.status === 403) {
|
|
// * Access denied
|
|
toast.methods.errorNotify(trans.methods.__("Vous n'êtes pas autorisé a effectuer cette action"));
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} else {
|
|
if (window.vueDebug) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
toast.methods.errorNotify(message);
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.loading {
|
|
background-color: rgba(0, 0, 0, 0.15);
|
|
z-index: 5;
|
|
}
|
|
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.5s ease;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
</style>
|