2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 10:58:59 +00:00

feat: keyboard-friendly functionalities in POS

This commit is contained in:
AbleKSaju 2024-12-04 15:20:08 +05:30
parent f4a3307767
commit fcaa2927f3
7 changed files with 107 additions and 52 deletions

View File

@ -107,6 +107,7 @@ export default {
return { return {
showQuickView: false, showQuickView: false,
linkValue: '', linkValue: '',
focInp: false,
isLoading: false, isLoading: false,
suggestions: [], suggestions: [],
highlightedIndex: -1, highlightedIndex: -1,
@ -187,6 +188,15 @@ export default {
const route = getFormRoute(this.linkSchemaName, name); const route = getFormRoute(this.linkSchemaName, name);
await routeTo(route); await routeTo(route);
}, },
async focusInputTag() {
this.focInp = true;
if (this.linkValue) {
return;
}
await this.$nextTick();
this.$refs.input.focus();
},
setLinkValue(value) { setLinkValue(value) {
this.linkValue = value; this.linkValue = value;
}, },
@ -282,6 +292,14 @@ export default {
return; return;
} }
if (!e.target.value || this.focInp) {
e.target.value = null;
this.focInp = false;
this.toggleDropdown(false);
return;
}
this.toggleDropdown(true); this.toggleDropdown(true);
this.updateSuggestions(e.target.value); this.updateSuggestions(e.target.value);
}, },

View File

@ -21,6 +21,7 @@
@blur="onBlur" @blur="onBlur"
@focus="(e) => !isReadOnly && $emit('focus', e)" @focus="(e) => !isReadOnly && $emit('focus', e)"
@input="(e) => !isReadOnly && $emit('input', e)" @input="(e) => !isReadOnly && $emit('input', e)"
@keydown.enter="setLoyaltyPoints"
/> />
</div> </div>
</div> </div>
@ -49,6 +50,7 @@ export default defineComponent({
border: { type: Boolean, default: false }, border: { type: Boolean, default: false },
size: { type: String, default: 'large' }, size: { type: String, default: 'large' },
placeholder: String, placeholder: String,
focusInput: Boolean,
showLabel: { type: Boolean, default: false }, showLabel: { type: Boolean, default: false },
containerStyles: { type: Object, default: () => ({}) }, containerStyles: { type: Object, default: () => ({}) },
textRight: { textRight: {
@ -64,6 +66,15 @@ export default defineComponent({
default: null, default: null,
}, },
}, },
async created() {
if (this.focusInput) {
await this.$nextTick();
(this.$refs.input as HTMLInputElement).focus();
if (this.value == 0) {
this.triggerChange('');
}
}
},
emits: ['focus', 'input', 'change'], emits: ['focus', 'input', 'change'],
computed: { computed: {
doc(): Doc | undefined { doc(): Doc | undefined {
@ -191,6 +202,12 @@ export default defineComponent({
}, },
}, },
methods: { methods: {
setLoyaltyPoints() {
const inputElement = this.$refs.input as HTMLInputElement;
if (inputElement && inputElement?.value) {
this.$emit('change', inputElement.value);
}
},
onBlur(e: FocusEvent) { onBlur(e: FocusEvent) {
const target = e.target; const target = e.target;
if (!(target instanceof HTMLInputElement)) { if (!(target instanceof HTMLInputElement)) {
@ -227,7 +244,7 @@ export default defineComponent({
triggerChange(value: unknown): void { triggerChange(value: unknown): void {
value = this.parse(value); value = this.parse(value);
if (value === '') { if (value === '' || value == 0) {
value = null; value = null;
} }

View File

@ -26,6 +26,14 @@ export default {
this.setLinkValue(); this.setLinkValue();
} }
}, },
props: {
focusInput: Boolean,
},
async created() {
if (this.focusInput) {
this.focusInputTag();
}
},
methods: { methods: {
async setLinkValue(newValue, isInput) { async setLinkValue(newValue, isInput) {
if (isInput) { if (isInput) {

View File

@ -63,6 +63,7 @@
:show-label="true" :show-label="true"
:border="true" :border="true"
:value="couponCode" :value="couponCode"
:focus-input="true"
:df="coupons.fieldMap.coupons" :df="coupons.fieldMap.coupons"
@change="updateCouponCode" @change="updateCouponCode"
/> />
@ -161,15 +162,18 @@ export default defineComponent({
}, },
}, },
methods: { methods: {
updateCouponCode(value: string) { async updateCouponCode(value: string | Event) {
(this.validationError = false), (this.couponCode = value);
},
async setCouponCode() {
try { try {
if (!this.couponCode) { if (!value) {
throw new Error(t`Must be select a coupon code`); return;
}
this.validationError = false;
if ((value as Event).type === 'keydown') {
value = ((value as Event).target as HTMLInputElement).value;
} }
this.couponCode = value as string;
const appliedCouponCodes = this.fyo.doc.getNewDoc( const appliedCouponCodes = this.fyo.doc.getNewDoc(
ModelNameEnum.AppliedCouponCodes ModelNameEnum.AppliedCouponCodes
); );
@ -183,8 +187,6 @@ export default defineComponent({
await this.sinvDoc.append('coupons', { coupons: this.couponCode }); await this.sinvDoc.append('coupons', { coupons: this.couponCode });
this.$emit('applyPricingRule'); this.$emit('applyPricingRule');
this.$emit('toggleModal', 'CouponCode');
this.couponCode = ''; this.couponCode = '';
this.validationError = false; this.validationError = false;
} catch (error) { } catch (error) {
@ -195,6 +197,9 @@ export default defineComponent({
message: t`${error as string}`, message: t`${error as string}`,
}); });
} }
},
setCouponCode() {
this.$emit('toggleModal', 'CouponCode');
}, },
async removeAppliedCoupon(coupon: AppliedCouponCodes) { async removeAppliedCoupon(coupon: AppliedCouponCodes) {
this.sinvDoc?.items?.map((item: InvoiceItem) => { this.sinvDoc?.items?.map((item: InvoiceItem) => {

View File

@ -20,13 +20,15 @@
<p>{{ loyaltyPoints }}</p> <p>{{ loyaltyPoints }}</p>
</div> </div>
<Data <Int
v-if="sinvDoc.fieldMap" v-if="sinvDoc.fieldMap"
class="flex-shrink-0 px-10 pb-10" class="flex-shrink-0 px-10 pb-10"
:show-label="true" :show-label="true"
:border="true" :border="true"
:focus-input="true"
:value="sinvDoc.loyaltyPoints" :value="sinvDoc.loyaltyPoints"
:df="sinvDoc.fieldMap.loyaltyPoints" :df="sinvDoc.fieldMap.loyaltyPoints"
@keydown.enter="setLoyaltyPoints"
@change="updateLoyaltyPoints" @change="updateLoyaltyPoints"
/> />
@ -52,7 +54,7 @@
<Button <Button
class="w-full bg-red-500 dark:bg-red-700" class="w-full bg-red-500 dark:bg-red-700"
style="padding: 1.35rem" style="padding: 1.35rem"
@click="$emit('toggleModal', 'LoyaltyProgram')" @click="cancelLoyaltyProgram"
> >
<slot> <slot>
<p class="uppercase text-lg text-white font-semibold"> <p class="uppercase text-lg text-white font-semibold">
@ -67,20 +69,20 @@
<script lang="ts"> <script lang="ts">
import Button from 'src/components/Button.vue'; import Button from 'src/components/Button.vue';
import Data from 'src/components/Controls/Data.vue';
import Modal from 'src/components/Modal.vue'; import Modal from 'src/components/Modal.vue';
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice'; import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
import { defineComponent, inject } from 'vue'; import { defineComponent, inject } from 'vue';
import { t } from 'fyo'; import { t } from 'fyo';
import { showToast } from 'src/utils/interactive'; import { showToast } from 'src/utils/interactive';
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
import Int from 'src/components/Controls/Int.vue';
export default defineComponent({ export default defineComponent({
name: 'LoyaltyProgramModal', name: 'LoyaltyProgramModal',
components: { components: {
Modal, Modal,
Button, Button,
Data, Int,
}, },
props: { props: {
loyaltyPoints: { loyaltyPoints: {
@ -105,6 +107,14 @@ export default defineComponent({
}; };
}, },
methods: { methods: {
async keydownEnter(value: number) {
await this.updateLoyaltyPoints(value);
this.setLoyaltyPoints();
},
cancelLoyaltyProgram() {
this.$emit('setLoyaltyPoints', 0);
this.$emit('toggleModal', 'LoyaltyProgram');
},
async updateLoyaltyPoints(newValue: number) { async updateLoyaltyPoints(newValue: number) {
try { try {
const partyData = await this.fyo.db.get( const partyData = await this.fyo.db.get(
@ -141,6 +151,12 @@ export default defineComponent({
throw new Error(t`no need ${newValue} points to purchase this item`); throw new Error(t`no need ${newValue} points to purchase this item`);
} }
if (newValue < 0) {
throw new Error(t`Points must be greater than 0`);
}
this.$emit('setLoyaltyPoints', this.sinvDoc.loyaltyPoints);
this.validationError = false; this.validationError = false;
} catch (error) { } catch (error) {
this.validationError = true; this.validationError = true;
@ -154,23 +170,7 @@ export default defineComponent({
} }
}, },
setLoyaltyPoints() { setLoyaltyPoints() {
try { this.$emit('toggleModal', 'LoyaltyProgram');
if (!this.sinvDoc.loyaltyPoints || this.sinvDoc.loyaltyPoints < 0) {
throw new Error(t`Points must be greater than 0`);
}
this.$emit('setLoyaltyPoints', this.sinvDoc.loyaltyPoints);
this.$emit('toggleModal', 'LoyaltyProgram');
this.validationError = false;
} catch (error) {
this.validationError = true;
showToast({
type: 'error',
message: t`${error as string}`,
});
}
}, },
}, },
}); });

View File

@ -276,6 +276,14 @@ export default defineComponent({
this.loyaltyProgram = party[0]?.loyaltyProgram as string; this.loyaltyProgram = party[0]?.loyaltyProgram as string;
this.loyaltyPoints = party[0]?.loyaltyPoints as number; this.loyaltyPoints = party[0]?.loyaltyPoints as number;
}, },
isModalOpen() {
for (const modal of modalNames) {
if (modal && this[`open${modal}Modal`]) {
this[`open${modal}Modal`] = false;
return `open${modal}Modal`;
}
}
},
setShortcuts() { setShortcuts() {
this.shortcuts?.shift.set(COMPONENT_NAME, ['KeyS'], async () => { this.shortcuts?.shift.set(COMPONENT_NAME, ['KeyS'], async () => {
this.routeToSinvList(); this.routeToSinvList();
@ -294,16 +302,9 @@ export default defineComponent({
}); });
this.shortcuts?.pmodShift.set(COMPONENT_NAME, ['Backspace'], async () => { this.shortcuts?.pmodShift.set(COMPONENT_NAME, ['Backspace'], async () => {
let anyModalClosed = false; const modalStatus = this.isModalOpen();
modalNames.forEach((modal: ModalName) => { if (!modalStatus) {
if (modal && this[`open${modal}Modal`]) {
this[`open${modal}Modal`] = false;
anyModalClosed = true;
}
});
if (!anyModalClosed) {
this.clearValues(); this.clearValues();
} }
}); });
@ -315,7 +316,9 @@ export default defineComponent({
}); });
this.shortcuts?.pmodShift.set(COMPONENT_NAME, ['KeyS'], async () => { this.shortcuts?.pmodShift.set(COMPONENT_NAME, ['KeyS'], async () => {
if (this.sinvDoc.party && this.sinvDoc.items?.length) { const modalStatus = this.isModalOpen();
if (!modalStatus && this.sinvDoc.party && this.sinvDoc.items?.length) {
this.saveOrder(); this.saveOrder();
} }
}); });

View File

@ -10,9 +10,10 @@
v-if="sinvDoc.fieldMap" v-if="sinvDoc.fieldMap"
class="flex-shrink-0 w-full" class="flex-shrink-0 w-full"
:border="true" :border="true"
:value="priceList" :value="sinvDoc?.priceList"
:focus-input="true"
:df="sinvDoc.fieldMap.priceList" :df="sinvDoc.fieldMap.priceList"
@change="(value) => (priceList = value)" @change="(value) => applyPriceList(value)"
/> />
</div> </div>
<div class="w-10 flex justify-end items-center"> <div class="w-10 flex justify-end items-center">
@ -46,7 +47,7 @@
<Button <Button
class="w-full bg-red-500 dark:bg-red-700" class="w-full bg-red-500 dark:bg-red-700"
style="padding: 1.35rem" style="padding: 1.35rem"
@click="$emit('toggleModal', 'PriceList')" @click="cancelPriceList"
> >
<slot> <slot>
<p class="uppercase text-lg text-white font-semibold"> <p class="uppercase text-lg text-white font-semibold">
@ -82,20 +83,17 @@ export default defineComponent({
sinvDoc: inject('sinvDoc') as SalesInvoice, sinvDoc: inject('sinvDoc') as SalesInvoice,
}; };
}, },
data() {
return {
priceList: '',
};
},
methods: { methods: {
async removePriceList() { async removePriceList() {
this.priceList = ''; await this.sinvDoc.set('priceList', '');
await this.setPriceList();
}, },
async setPriceList() { async applyPriceList(value?: string) {
try { try {
await this.sinvDoc.set('priceList', this.priceList); if (!value || value == this.sinvDoc.priceList) {
return;
}
await this.sinvDoc.set('priceList', value);
this.$emit('toggleModal', 'PriceList'); this.$emit('toggleModal', 'PriceList');
} catch (error) { } catch (error) {
showToast({ showToast({
@ -104,6 +102,12 @@ export default defineComponent({
}); });
} }
}, },
cancelPriceList() {
this.$emit('toggleModal', 'PriceList');
},
async setPriceList() {
this.$emit('toggleModal', 'PriceList');
},
}, },
}); });
</script> </script>