mirror of
https://github.com/frappe/books.git
synced 2024-11-08 14:50: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:
parent
703b1d2138
commit
708d172f7c
@ -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,16 +97,24 @@ export function getLedgerLinkAction(
|
||||
group: fyo.t`View`,
|
||||
condition: (doc: Doc) => doc.isSubmitted,
|
||||
action: async (doc: Doc, router: Router) => {
|
||||
router.push({
|
||||
name: 'Report',
|
||||
params: {
|
||||
reportClassName,
|
||||
defaultFilters: JSON.stringify({
|
||||
referenceType: doc.schemaName,
|
||||
referenceName: doc.name,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const route = getLedgerLink(doc, reportClassName);
|
||||
router.push(route);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getLedgerLink(
|
||||
doc: Doc,
|
||||
reportClassName: 'GeneralLedger' | 'StockLedger'
|
||||
) {
|
||||
return {
|
||||
name: 'Report',
|
||||
params: {
|
||||
reportClassName,
|
||||
defaultFilters: JSON.stringify({
|
||||
referenceType: doc.schemaName,
|
||||
referenceName: doc.name,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
setTimeout(() => {
|
||||
this.statusText = null;
|
||||
}, 300);
|
||||
} catch (err) {
|
||||
await commonDocSync(this.doc);
|
||||
setTimeout(() => {
|
||||
this.statusText = null;
|
||||
console.error(err);
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
async submit() {
|
||||
this.statusText = t`Submitting`;
|
||||
try {
|
||||
await this.$refs.form.submit();
|
||||
setTimeout(() => {
|
||||
this.statusText = null;
|
||||
}, 300);
|
||||
} catch (err) {
|
||||
await commonDocSubmit(this.doc);
|
||||
setTimeout(() => {
|
||||
this.statusText = null;
|
||||
console.error(err);
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
routeToPrevious() {
|
||||
if (this.loadOnClose && this.doc.dirty && !this.doc.notInserted) {
|
||||
|
@ -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';
|
||||
|
159
src/utils/ui.ts
159
src/utils/ui.ts
@ -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();
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -360,30 +367,12 @@ function getDuplicateAction(doc: Doc): Action {
|
||||
!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;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
try {
|
||||
const dupe = doc.duplicate();
|
||||
await openEdit(dupe);
|
||||
} catch (err) {
|
||||
handleErrorWithDialog(err as Error, doc);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -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 {};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user