Files
pleno-vue/src/components/displays/department/pos/order/PosOrderRegistrationField.vue
T
Jeppe Bundgaard 295f69ee41 Implement POS registration field and autosave utility:
- Added `PosOrderRegistrationField.vue` for managing inline registration field inputs with autosave behavior.
- Introduced `useOrderMetadataAutosave.js` composable for seamless value normalization, dirty tracking, and delayed persistence.
- Refactored mobile and desktop POS components to integrate `useOrderMetadataAutosave` for improved field management (e.g., `POSOrderReference.vue`, `PosOrderLicensePlates.vue`).
- Updated e2e tests to cover registration field scenarios, including input normalization and persistence (`pos-mobile-order-flow.spec.js`, `admin-pos-orders.spec.ts`).
- Enhanced styling and responsiveness for registration fields in `pos.css`.
2026-04-13 14:20:36 +02:00

117 lines
3.1 KiB
Vue

<script setup>
import { computed, defineProps, nextTick, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { useOrderMetadataAutosave } from "@/composables/useOrderMetadataAutosave.js";
const props = defineProps({
index: {
type: Number,
required: true,
},
order_id: {
type: [Number, String],
required: true,
},
modelValue: {
type: String,
default: "",
},
loadOrder: {
type: Function,
required: true,
},
});
const { t } = useI18n();
const normalizeRegistrationDraft = (value) => String(value ?? "").toUpperCase();
const inputId = `pos-order-registration-input-${props.index}`;
const isEditing = ref(false);
const fieldKey = computed(() => `reg_${props.index}`);
let autosave;
autosave = useOrderMetadataAutosave({
source: () => props.modelValue,
normalizeValue: normalizeRegistrationDraft,
saveValue: async (value) => {
await SessionUser.objects.orders.set[fieldKey.value](props.order_id, value);
return value;
},
onSaved: async () => {
await props.loadOrder();
await nextTick();
autosave.syncFromSource(props.modelValue);
},
});
const hasValue = computed(() => autosave.draft.value.length > 0);
const buttonTestId = computed(() => (hasValue.value ? `pos-order-registration-${props.index}` : `pos-order-registration-add-${props.index}`));
watch(() => autosave.draft.value, () => {
if (isEditing.value) {
autosave.scheduleSave();
}
});
const focusInput = () => {
document.getElementById(inputId)?.focus();
};
const openEditor = () => {
if (isEditing.value) {
return;
}
isEditing.value = true;
nextTick(() => {
focusInput();
});
};
const closeEditor = async () => {
isEditing.value = false;
await autosave.flush();
};
</script>
<template>
<div class="control pos-registration-control" :class="{ 'is-loading': autosave.isSaving.value }">
<div v-if="isEditing" class="pos-registration-field pos-registration-field--editing">
<label class="pos-registration-field__label" :for="inputId">{{ t('pos.order.reg') }} {{ props.index }}</label>
<input
:id="inputId"
v-model="autosave.draft.value"
:data-testid="`pos-order-registration-input-${props.index}`"
class="pos-registration-field__input"
type="text"
autocomplete="off"
@blur="closeEditor()"
@keydown.enter.prevent="closeEditor()"
/>
</div>
<button
v-else
class="pos-registration-field"
:class="{ 'pos-registration-field--add': !hasValue }"
type="button"
:data-testid="buttonTestId"
:aria-label="!hasValue ? `${t('common.add')} ${t('pos.order.reg')} ${props.index}` : undefined"
@click="openEditor()"
>
<span class="pos-registration-field__label">{{ t('pos.order.reg') }} {{ props.index }}</span>
<span
class="pos-registration-field__value"
:class="{ 'pos-registration-field__value--muted': !hasValue }"
>
{{ hasValue ? autosave.draft.value : `+ ${t('common.add')}` }}
</span>
</button>
</div>
</template>
<style scoped>
</style>