263 lines
7.3 KiB
Vue
263 lines
7.3 KiB
Vue
<template>
|
|
<div
|
|
class="d-flex flex-column justify-content-between align-items-center border shadow-sm rounded-3 p-3 h-100"
|
|
ref="claimConversation"
|
|
>
|
|
<div
|
|
ref="messagesWrapper"
|
|
class="col-12 claim-messages-wrapper overflow-scroll pe-3"
|
|
>
|
|
<template v-if="claimMessageModels.length">
|
|
<template
|
|
v-for="(claimMessageModel, claimMessageModelIndex) in claimMessageModels"
|
|
:key="claimMessageModelIndex"
|
|
>
|
|
<span
|
|
v-if="shouldDisplayDate(claimMessageModelIndex)"
|
|
:class="`date-separator d-flex flex-row justify-content-between align-items-center text-secondary fst-italic w-100 ${claimMessageModelIndex === 0 ? 'mb-4' : 'my-4'}`"
|
|
>
|
|
{{ formatDate(String(claimMessageModel.created_at)) }}
|
|
</span>
|
|
<ClaimMessageComponent
|
|
:claim-message-model="claimMessageModel"
|
|
:auth-user-id="authUserId"
|
|
:loading="loading"
|
|
@set-loading-message="setLoadingMessage"
|
|
/>
|
|
</template>
|
|
</template>
|
|
<div
|
|
v-else
|
|
class="d-flex flex-column justify-content-center align-items-center text-muted fst-italic h-100"
|
|
>
|
|
<FontAwesomeIcon
|
|
icon="fa-solid fa-comment-slash"
|
|
size="2xl"
|
|
/>
|
|
<p class="my-3">
|
|
{{ trans.methods.__('Aucune activité enregistrée à ce jour') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 mt-3">
|
|
<div class="d-flex justify-content-center align-items-start">
|
|
<textarea
|
|
class="form-control"
|
|
:class="{'is-invalid': errors.methods.parseValidationInput('content', allErrors)}"
|
|
:placeholder="trans.methods.__('Votre message...')"
|
|
name="content"
|
|
v-model="content"
|
|
:disabled="loading || loadingMessage"
|
|
/>
|
|
<button
|
|
class="btn btn-primary ms-1"
|
|
data-bs-tooltip="tooltip"
|
|
type="button"
|
|
:title="trans.methods.__('Envoyer votre message')"
|
|
@click="storeMessage()"
|
|
:disabled="loading || loadingMessage"
|
|
>
|
|
<div
|
|
v-show="loading"
|
|
class="spinner-border spinner-border-sm"
|
|
role="status"
|
|
>
|
|
<span class="visually-hidden">{{ trans.methods.__('Chargement...') }}</span>
|
|
</div>
|
|
<div v-show="!loading">
|
|
<FontAwesomeIcon icon="fa-solid fa-paper-plane" />
|
|
</div>
|
|
</button>
|
|
</div>
|
|
<small class="text-danger m-0">
|
|
{{ errors.methods.parseValidationInput('content', allErrors) }}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, onMounted, ref, nextTick, useAttrs, watch } from "vue";
|
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|
import type { PropType } from "vue";
|
|
import trans from "./../../modules/trans";
|
|
import route from "./../../modules/route";
|
|
import { Tooltips } from "./../../modules/tooltips";
|
|
import errors from "./../../modules/errors";
|
|
import ClaimMessageComponent from "./ClaimMessageComponent.vue";
|
|
|
|
defineOptions({
|
|
name: "ClaimConversationComponent"
|
|
});
|
|
|
|
// * ATTRIBUTES
|
|
const attrs = useAttrs();
|
|
|
|
// * PROPS
|
|
const props = defineProps({
|
|
authUserId: {
|
|
type: Number,
|
|
default: Math.pow(10, 16) / Math.random(),
|
|
},
|
|
claimModel: {
|
|
type: Object as PropType<LaravelModel & LaravelModelClaimRelations>,
|
|
default: () => { return {}; },
|
|
},
|
|
claimMessageModels: {
|
|
type: Object as PropType<Array<LaravelModel & LaravelModelUserRelation>>,
|
|
default: () => { return []; },
|
|
},
|
|
loading: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
loadingMessage: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
allErrors: {
|
|
type: Object as PropType<LaravelValidationErrors>,
|
|
default: () => { return {}; },
|
|
},
|
|
});
|
|
|
|
// * REFS
|
|
const claimConversation = ref<HTMLDivElement|null>(null);
|
|
const messagesWrapper = ref<HTMLDivElement|null>(null);
|
|
|
|
// * DATA
|
|
const authUserId = ref<number>(props.authUserId);
|
|
const claimModel = ref<LaravelModel>(props.claimModel);
|
|
const claimMessageModels = ref<Array<LaravelModel & LaravelModelUserRelation>>(props.claimMessageModels);
|
|
const content = ref<string>("");
|
|
const loading = ref<boolean>(props.loading);
|
|
const loadingMessage = ref<boolean>(props.loadingMessage);
|
|
const tooltips = ref<Tooltips|null>(null);
|
|
const allErrors = ref<LaravelValidationErrors>(props.allErrors);
|
|
|
|
// * COMPUTED
|
|
const isUsedWithProps = computed<boolean>(() => attrs.json === undefined);
|
|
|
|
// * LIFECYCLE
|
|
onMounted((): void => {
|
|
const json = String(attrs.json ?? "{}"),
|
|
data = JSON.parse(json);
|
|
if (!isUsedWithProps.value) {
|
|
authUserId.value = data.authUserId;
|
|
claimModel.value = data.claimModel;
|
|
claimMessageModels.value = data.claimMessageModels;
|
|
allErrors.value = data.allErrors;
|
|
}
|
|
initTooltips();
|
|
nextTick(() => {
|
|
scrollToBottom();
|
|
});
|
|
});
|
|
|
|
// * WATCHERS
|
|
watch(props, () => {
|
|
claimMessageModels.value = props.claimMessageModels;
|
|
nextTick(() => {
|
|
scrollToBottom();
|
|
});
|
|
});
|
|
|
|
// * METHODS
|
|
|
|
/**
|
|
* Store a new message.
|
|
* @return void
|
|
*/
|
|
function storeMessage(): void {
|
|
loading.value = true;
|
|
allErrors.value = {};
|
|
window.axios
|
|
.post(route.methods.getRoute("bo.claim_messages.store"), {
|
|
claim_id: claimModel.value.id,
|
|
content: content.value,
|
|
})
|
|
.then((reponse) => {
|
|
loading.value = false;
|
|
claimMessageModels.value = reponse.data;
|
|
content.value = "";
|
|
nextTick(() => {
|
|
scrollToBottom();
|
|
});
|
|
})
|
|
.catch((e) => {
|
|
loading.value = false;
|
|
allErrors.value = e.response?.data?.errors ?? {};
|
|
errors.methods.ajaxErrorHandler(e);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Scroll the wrapper message to the bottom.
|
|
* @return void
|
|
*/
|
|
function scrollToBottom(): void {
|
|
if (messagesWrapper.value)
|
|
messagesWrapper.value.scrollTop = messagesWrapper.value?.scrollHeight;
|
|
}
|
|
|
|
/**
|
|
* Set the loading value component correlated to the loading message component.
|
|
* @param boolean eventLoadingMessage
|
|
* @return void
|
|
*/
|
|
function setLoadingMessage(eventLoadingMessage: boolean): void {
|
|
loadingMessage.value = eventLoadingMessage;
|
|
}
|
|
|
|
/**
|
|
* Return a formated date.
|
|
* @param string unformatedDate
|
|
* @return void
|
|
*/
|
|
function formatDate(unformatedDate: string): string {
|
|
const date = new Date(unformatedDate);
|
|
return date.toLocaleDateString("fr-FR", {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set the visibility of the date separator.
|
|
* @param number index
|
|
*/
|
|
function shouldDisplayDate(index: number): boolean {
|
|
if (index === 0) return true;
|
|
const current = new Date(String(claimMessageModels.value[index].created_at)).toDateString();
|
|
const previous = new Date(String(claimMessageModels.value[index - 1].created_at)).toDateString();
|
|
return current !== previous;
|
|
}
|
|
|
|
/** Initialise all tooltips in the component. */
|
|
function initTooltips(): void {
|
|
setTimeout(() => {
|
|
tooltips.value = Tooltips.make({
|
|
type: "dom",
|
|
elements: claimConversation.value!.querySelectorAll("[data-bs-tooltip=\"tooltip\"]")
|
|
});
|
|
}, 500);
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scopped>
|
|
.claim-messages-wrapper {
|
|
height: 24.6rem;
|
|
}
|
|
.date-separator {
|
|
&::before,
|
|
&::after {
|
|
content: "";
|
|
flex: 1;
|
|
border-bottom: 1px solid #dee2e6;
|
|
margin: 0 0.5rem;
|
|
width: 100%;
|
|
display: block;
|
|
}
|
|
}
|
|
</style>
|