Files
pleno-vue/src/components/displays/department/pos/steps/mobile/views/PosDepartmentStep1MobileCustomerNotes.vue
T
Jeppe B 1729443cc3 Resolve frontend Qodana critical and high findings (#176)
Resolve recommended-profile Critical and High findings, update vulnerable dependencies, restore invoice queue E2E authentication setup, and clear the remaining frontend Qodana findings.
2026-07-17 06:22:51 +02:00

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>