2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 23:00:56 +00:00

fix(ux): toasts on sync, submit, delete and cancel

- dont route back on cancel
- dont show dialog on duplicate
- simplify two column form
This commit is contained in:
18alantom 2023-03-15 14:02:52 +05:30
parent 703b1d2138
commit 708d172f7c
7 changed files with 170 additions and 142 deletions

View File

@ -85,7 +85,7 @@ export function getLedgerLinkAction(
isStock: boolean = false
): Action {
let label = fyo.t`Accounting Entries`;
let reportClassName = 'GeneralLedger';
let reportClassName: 'GeneralLedger' | 'StockLedger' = 'GeneralLedger';
if (isStock) {
label = fyo.t`Stock Entries`;
@ -97,7 +97,17 @@ export function getLedgerLinkAction(
group: fyo.t`View`,
condition: (doc: Doc) => doc.isSubmitted,
action: async (doc: Doc, router: Router) => {
router.push({
const route = getLedgerLink(doc, reportClassName);
router.push(route);
},
};
}
export function getLedgerLink(
doc: Doc,
reportClassName: 'GeneralLedger' | 'StockLedger'
) {
return {
name: 'Report',
params: {
reportClassName,
@ -106,8 +116,6 @@ export function getLedgerLinkAction(
referenceName: doc.name,
}),
},
});
},
};
}

View File

@ -10,7 +10,6 @@
:df="df"
:value="doc[df.fieldname]"
@change="async (value) => await onChange(df, value)"
:read-only="readOnly"
/>
<!-- Regular Field Form -->
@ -39,7 +38,6 @@
:df="df"
:value="doc[df.fieldname]"
:class="{ 'p-2': df.fieldtype === 'Check' }"
:read-only="readOnly"
:text-end="false"
@change="async (value) => await onChange(df, value)"
@new-doc="async (newdoc) => await onChange(df, newdoc.name)"
@ -58,7 +56,6 @@
<script>
import { Doc } from 'fyo/model/doc';
import FormControl from 'src/components/Controls/FormControl.vue';
import { handleErrorWithDialog } from 'src/errorHandling';
import { fyo } from 'src/initFyo';
import { getErrorMessage } from 'src/utils';
import { evaluateHidden } from 'src/utils/doc';
@ -66,21 +63,13 @@ import Table from './Controls/Table.vue';
export default {
name: 'TwoColumnForm',
emits: ['error', 'change'],
props: {
doc: Doc,
fields: { type: Array, default: () => [] },
autosave: Boolean,
columnRatio: {
type: Array,
default: () => [1, 1],
},
emitChange: {
type: Boolean,
default: false,
},
focusFirstInput: Boolean,
readOnly: { type: [null, Boolean], default: null },
},
watch: {
doc() {
@ -106,10 +95,6 @@ export default {
},
mounted() {
this.setFormFields();
if (this.focusFirstInput) {
this.$refs['controls']?.[0].focus();
}
if (fyo.store.isDevelopment) {
window.tcf = this;
}
@ -131,7 +116,6 @@ export default {
delete this.errors[fieldname];
let isSet = false;
const oldValue = this.doc.get(fieldname);
try {
isSet = await this.doc.set(fieldname, value);
} catch (err) {
@ -143,35 +127,7 @@ export default {
}
if (isSet) {
await this.handlePostSet(field, value, oldValue);
}
},
async handlePostSet(df, value, oldValue) {
this.setFormFields();
if (this.emitChange) {
this.$emit('change', df, value, oldValue);
}
if (df.fieldtype === 'Table' || !this.doc.dirty || !this.autosave) {
return;
}
await this.doc.sync();
},
async sync() {
try {
await this.doc.sync();
this.setFormFields();
} catch (err) {
await handleErrorWithDialog(err, this.doc);
}
},
async submit() {
try {
await this.doc.submit();
this.setFormFields();
} catch (err) {
await handleErrorWithDialog(err, this.doc);
}
},
setFormFields() {

View File

@ -125,6 +125,8 @@ import { docsPathMap } from 'src/utils/misc';
import { docsPathRef, focusedDocsRef } from 'src/utils/refs';
import { ActionGroup, UIGroupedFields } from 'src/utils/types';
import {
commonDocSubmit,
commonDocSync,
getDocFromNameIfExistsElseNew,
getFieldsGroupedByTabAndSection,
getGroupedActionsForDoc,
@ -270,19 +272,13 @@ export default defineComponent({
);
},
async sync() {
try {
await this.doc.sync();
if (await commonDocSync(this.doc)) {
this.updateGroupedFields();
} catch (err) {
await handleErrorWithDialog(err, this.doc);
}
},
async submit() {
try {
await this.doc.submit();
if (await commonDocSubmit(this.doc)) {
this.updateGroupedFields();
} catch (err) {
await handleErrorWithDialog(err, this.doc);
}
},
async setDoc() {

View File

@ -317,9 +317,10 @@ import { fyo } from 'src/initFyo';
import { docsPathMap } from 'src/utils/misc';
import { docsPathRef, focusedDocsRef } from 'src/utils/refs';
import {
commonDocSync,
commonDocSubmit,
getGroupedActionsForDoc,
routeTo,
showMessageDialog,
} from 'src/utils/ui';
import { nextTick } from 'vue';
import { handleErrorWithDialog } from '../errorHandling';
@ -543,37 +544,10 @@ export default {
return fyo.getField(this.schemaName, fieldname);
},
async sync() {
try {
await this.doc.sync();
} catch (err) {
await this.handleError(err);
}
await commonDocSync(this.doc);
},
async submit() {
const message =
this.schemaName === ModelNameEnum.SalesInvoice
? this.t`Submit Sales Invoice?`
: this.t`Submit Purchase Invoice?`;
const ref = this;
await showMessageDialog({
message,
buttons: [
{
label: this.t`Yes`,
async action() {
try {
await ref.doc.submit();
} catch (err) {
await ref.handleError(err);
}
},
},
{
label: this.t`No`,
action() {},
},
],
});
await commonDocSubmit(this.doc);
},
async handleError(e) {
await handleErrorWithDialog(e, this.doc);

View File

@ -89,12 +89,11 @@
<!-- Rest of the form -->
<TwoColumnForm
v-if="doc"
class="w-full"
ref="form"
v-if="doc"
:doc="doc"
:fields="fields"
:autosave="false"
:column-ratio="[1.1, 2]"
/>
@ -116,7 +115,12 @@ import TwoColumnForm from 'src/components/TwoColumnForm.vue';
import { fyo } from 'src/initFyo';
import { getQuickEditWidget } from 'src/utils/quickEditWidgets';
import { focusedDocsRef } from 'src/utils/refs';
import { getActionsForDoc, openQuickEdit } from 'src/utils/ui';
import {
commonDocSubmit,
commonDocSync,
getActionsForDoc,
openQuickEdit,
} from 'src/utils/ui';
export default {
name: 'QuickEditForm',
@ -305,27 +309,17 @@ export default {
},
async sync() {
this.statusText = t`Saving`;
try {
await this.$refs.form.sync();
await commonDocSync(this.doc);
setTimeout(() => {
this.statusText = null;
}, 300);
} catch (err) {
this.statusText = null;
console.error(err);
}
},
async submit() {
this.statusText = t`Submitting`;
try {
await this.$refs.form.submit();
await commonDocSubmit(this.doc);
setTimeout(() => {
this.statusText = null;
}, 300);
} catch (err) {
this.statusText = null;
console.error(err);
}
},
routeToPrevious() {
if (this.loadOnClose && this.doc.dirty && !this.doc.notInserted) {

View File

@ -71,7 +71,6 @@
<script lang="ts">
import { DocValue } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import { ValidationError } from 'fyo/utils/errors';
import { Field } from 'schemas/types';
import Button from 'src/components/Button.vue';
import FormContainer from 'src/components/FormContainer.vue';

View File

@ -8,6 +8,9 @@ import type { Doc } from 'fyo/model/doc';
import { Action } from 'fyo/model/types';
import { getActions } from 'fyo/utils';
import { getDbError, LinkValidationError, ValueError } from 'fyo/utils/errors';
import { getLedgerLink } from 'models/helpers';
import { Transfer } from 'models/inventory/Transfer';
import { Transactional } from 'models/Transactional/Transactional';
import { ModelNameEnum } from 'models/types';
import { Schema } from 'schemas/types';
import { handleErrorWithDialog } from 'src/errorHandling';
@ -316,10 +319,11 @@ function getCancelAction(doc: Doc): Action {
condition: (doc: Doc) => doc.canCancel,
async action() {
const res = await cancelDocWithPrompt(doc);
if (res) {
router.push(`/list/${doc.schemaName}`);
if (!res) {
return;
}
showActionToast(doc, 'cancel');
},
};
}
@ -333,9 +337,12 @@ function getDeleteAction(doc: Doc): Action {
condition: (doc: Doc) => doc.canDelete,
async action() {
const res = await deleteDocWithPrompt(doc);
if (res) {
router.back();
if (!res) {
return;
}
showActionToast(doc, 'delete');
router.back();
},
};
}
@ -359,32 +366,14 @@ function getDuplicateAction(doc: Doc): Action {
((isSubmittable && doc.submitted) || !isSubmittable) &&
!doc.notInserted
),
async action() {
await showMessageDialog({
message: t`Duplicate ${doc.schemaName} ${doc.name!}?`,
buttons: [
{
label: t`Yes`,
async action() {
try {
const dupe = doc.duplicate();
await openEdit(dupe);
return true;
} catch (err) {
handleErrorWithDialog(err as Error, doc);
return false;
}
},
},
{
label: t`No`,
action() {
return false;
},
},
],
});
},
};
}
@ -571,3 +560,115 @@ export function getShortcutKeyMap(
[ShortcutKey.enter]: 'Enter',
};
}
export async function commonDocSync(doc: Doc): Promise<boolean> {
try {
await doc.sync();
} catch (error) {
handleErrorWithDialog(error, doc);
return false;
}
showActionToast(doc, 'sync');
return true;
}
export async function commonDocSubmit(doc: Doc): Promise<boolean> {
const success = await showSubmitDialog(doc);
if (!success) {
return false;
}
showSubmitToast(doc);
return true;
}
async function showSubmitDialog(doc: Doc) {
const label = doc.schema.label ?? doc.schemaName;
const message = t`Submit ${label}?`;
const yesAction = async () => {
try {
await doc.submit();
} catch (error) {
handleErrorWithDialog(error, doc);
return false;
}
return true;
};
const buttons = [
{
label: t`Yes`,
action: yesAction,
},
{
label: t`No`,
action: () => false,
},
];
return await showMessageDialog({
message,
buttons,
});
}
function showActionToast(doc: Doc, type: 'sync' | 'cancel' | 'delete') {
const label = getToastLabel(doc);
const message = {
sync: t`${label} saved`,
cancel: t`${label} cancelled`,
delete: t`${label} deleted`,
}[type];
showToast({ type: 'success', message, duration: 2500 });
}
function showSubmitToast(doc: Doc) {
const label = getToastLabel(doc);
const message = t`${label} submitted`;
const toastOption: ToastOptions = {
type: 'success',
message,
duration: 5000,
...getSubmitSuccessToastAction(doc),
};
showToast(toastOption);
}
function getToastLabel(doc: Doc) {
const label = doc.schema.label ?? doc.schemaName;
if (doc.schema.naming === 'random') {
return label;
}
return doc.name ?? label;
}
function getSubmitSuccessToastAction(doc: Doc) {
const isStockTransfer = doc instanceof Transfer;
const isTransactional = doc instanceof Transactional;
if (isStockTransfer) {
return {
async action() {
const route = getLedgerLink(doc, 'StockLedger');
await routeTo(route);
},
actionText: t`View Stock Entries`,
};
}
if (isTransactional) {
return {
async action() {
const route = getLedgerLink(doc, 'GeneralLedger');
await routeTo(route);
},
actionText: t`View Accounting Entries`,
};
}
return {};
}