Resolve recommended-profile Critical and High findings, update vulnerable dependencies, restore invoice queue E2E authentication setup, and clear the remaining frontend Qodana findings.
234 lines
6.3 KiB
Vue
234 lines
6.3 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, watch, computed } from "vue";
|
|
import {metadata, popups, actionButtons} from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
|
|
import { getNotes, createNote, deleteNote } from "@/components/shop/CustomerNotes.vue";
|
|
import SessionUser from "@/components/session/token/SessionUser.vue";
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
const { t } = useI18n();
|
|
// Define the close event to emit when the component is closed
|
|
defineEmits(["close"]);
|
|
|
|
// Define props
|
|
const props = defineProps({
|
|
showNotes: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
showInput: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
})
|
|
|
|
onMounted(() => {
|
|
// Load the notes when the component is mounted
|
|
loadNotes();
|
|
})
|
|
|
|
const notes = ref<any[]>([]); // TODO: define type
|
|
// Load notes from the API
|
|
const loadNotes = async () => {
|
|
await getNotes(metadata.getCustomerId()).then((response) => {
|
|
notes.value = response.data.data;
|
|
})
|
|
}
|
|
// Create a new note
|
|
const createNewNote = async (noteContent: string) => {
|
|
await createNote(metadata.getCustomerId(), noteContent).then((response) => {
|
|
notes.value.push(response.data.data);
|
|
// Display the note in the notes list
|
|
popups.get().props.input = undefined;
|
|
popups.get().props.showNotes = true;
|
|
popups.get().props.showInput = false;
|
|
})
|
|
}
|
|
// Delete a note
|
|
const deleteNoteById = async (noteId: number) => {
|
|
await deleteNote(noteId).then(() => {
|
|
notes.value = notes.value.filter((note) => note.id !== noteId);
|
|
})
|
|
}
|
|
// Clear the list of notes
|
|
const _clearNotes = () => {
|
|
notes.value = [];
|
|
}
|
|
|
|
const cashierNamesMap = ref([]);
|
|
// Render the names of the cashiers in the notes
|
|
const renderCashierNames = () => {
|
|
// Get all the cashier_id from the notes (that's numbers)
|
|
let missingCashierNames = notes.value.filter((note) => {
|
|
return note.cashier_id && !note.cashier_name;
|
|
}).map((note) => note.cashier_id);
|
|
// If there are no missing cashier names, return
|
|
if (missingCashierNames.length === 0) {
|
|
return;
|
|
}
|
|
// Get the names of the cashiers from SessionUser
|
|
SessionUser.request(
|
|
'/public/employees',
|
|
'GET',
|
|
{}
|
|
).then((response) => {
|
|
cashierNamesMap.value = response.data.data;
|
|
})
|
|
}
|
|
|
|
const _cashierNames = computed(() => {
|
|
return cashierNamesMap.value;
|
|
})
|
|
|
|
const input = ref("");
|
|
|
|
watch(notes, () => {
|
|
// Check if any note is missing a cashier name
|
|
renderCashierNames();
|
|
}, { deep: true });
|
|
|
|
// Watch the input, and save it to the popups props
|
|
watch(input, (newInput, oldInput) => {
|
|
popups.get().props.input = newInput;
|
|
// If the showInput prop is true, focus the input field
|
|
if (oldInput === "" && newInput !== "" && props.showInput) {
|
|
const inputField = document.getElementById("input_field");
|
|
if (inputField) {
|
|
inputField.focus();
|
|
}
|
|
// Set the actionButtons
|
|
popups.get().actionButtons = [
|
|
{...actionButtons.default.value.addNote, onClick: () => { createNewNote(input.value); input.value=''; } }, // Add note button
|
|
{...actionButtons.default.value.cancel }, // Cancel button
|
|
]
|
|
}
|
|
// If the new input is empty, but the old input was not, remove the addNote button
|
|
if (popups.get().actionButtons.length > 1 && newInput === "" && oldInput !== "") {
|
|
popups.get().actionButtons = [
|
|
{...actionButtons.default.value.cancel }, // Cancel button
|
|
]
|
|
}
|
|
})
|
|
|
|
watch(() => props.showInput, (newVal) => {
|
|
if (newVal) {
|
|
// Focus the input field
|
|
const inputField = document.getElementById("input_field");
|
|
if (inputField) {
|
|
inputField.focus();
|
|
}
|
|
// Set the actionButtons
|
|
popups.get().actionButtons = [
|
|
{...actionButtons.default.value.cancel }, // Cancel button
|
|
]
|
|
} else {
|
|
// Clear the input field
|
|
input.value = "";
|
|
// Clear the actionButtons
|
|
popups.get().actionButtons = [
|
|
{...actionButtons.default.value.addNote }, // Default add note button
|
|
{...actionButtons.default.value.close }, // Close button
|
|
]
|
|
}
|
|
})
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div :style="popups.get()?.style" class="scrollable-container" v-if="props.showNotes">
|
|
<template v-for="note in notes" :key="note.id">
|
|
<div class="message is-info mb-1">
|
|
<div class="message-body">
|
|
<!-- Delete button -->
|
|
<button class="delete is-float-right" aria-label="delete" @click="deleteNoteById(note.id)" v-show="false"></button>
|
|
<!-- Note content -->
|
|
<span>
|
|
{{ note.note }}
|
|
<!-- Small text with creation date -->
|
|
<br />
|
|
<small>{{ SessionUser.functions.ucFirst(SessionUser.functions.date.toWordsWithTime(note?.created_at || new Date())) }}</small>
|
|
<br />
|
|
<small
|
|
v-if="note?.cashier_id"
|
|
:data-testid="`pos-mobile-customer-note-created-by-${note.id}`"
|
|
>{{ t('admin.pos.created_by') }} <strong>{{ cashierNamesMap.find(cashier => cashier.id === parseInt(note.cashier_id))?.display_name || t('admin.pos.unknown') }}</strong></small>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<div v-if="props.showInput" class="mt-2">
|
|
<div class="custom-input">
|
|
<input
|
|
class="input has-sharp-edges"
|
|
type="text"
|
|
:placeholder="t('admin.pos.add_note_placeholder')"
|
|
v-model="input"
|
|
id="input_field"
|
|
@keyup.enter="createNewNote(input); input=''"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* input-fields */
|
|
.custom-input {
|
|
/* input-fields */
|
|
|
|
box-sizing: border-box;
|
|
|
|
/* Auto layout */
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 0px 8px;
|
|
gap: 10px;
|
|
width: 100%;
|
|
height: 42px;
|
|
background: #FFFFFF;
|
|
border-radius: 4px;
|
|
|
|
/* Inside auto layout */
|
|
flex: none;
|
|
order: 1;
|
|
align-self: stretch;
|
|
flex-grow: 0;
|
|
}
|
|
.custom-input.is-searched {
|
|
/* input-fields */
|
|
|
|
box-sizing: border-box;
|
|
|
|
/* Auto layout */
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 12px 8px;
|
|
gap: 10px;
|
|
|
|
width: 100%;
|
|
height: 42px;
|
|
|
|
background: #FFFFFF;
|
|
border: 1px solid #000000;
|
|
border-radius: 4px;
|
|
border-bottom: none;
|
|
|
|
/* Inside auto layout */
|
|
flex: none;
|
|
order: 1;
|
|
align-self: stretch;
|
|
flex-grow: 0;
|
|
|
|
}
|
|
|
|
.scrollable-container {
|
|
overflow-y: scroll;
|
|
}
|
|
|
|
</style>
|