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:
parent
703b1d2138
commit
708d172f7c
@ -85,7 +85,7 @@ export function getLedgerLinkAction(
|
|||||||
isStock: boolean = false
|
isStock: boolean = false
|
||||||
): Action {
|
): Action {
|
||||||
let label = fyo.t`Accounting Entries`;
|
let label = fyo.t`Accounting Entries`;
|
||||||
let reportClassName = 'GeneralLedger';
|
let reportClassName: 'GeneralLedger' | 'StockLedger' = 'GeneralLedger';
|
||||||
|
|
||||||
if (isStock) {
|
if (isStock) {
|
||||||
label = fyo.t`Stock Entries`;
|
label = fyo.t`Stock Entries`;
|
||||||
@ -97,16 +97,24 @@ export function getLedgerLinkAction(
|
|||||||
group: fyo.t`View`,
|
group: fyo.t`View`,
|
||||||
condition: (doc: Doc) => doc.isSubmitted,
|
condition: (doc: Doc) => doc.isSubmitted,
|
||||||
action: async (doc: Doc, router: Router) => {
|
action: async (doc: Doc, router: Router) => {
|
||||||
router.push({
|
const route = getLedgerLink(doc, reportClassName);
|
||||||
name: 'Report',
|
router.push(route);
|
||||||
params: {
|
},
|
||||||
reportClassName,
|
};
|
||||||
defaultFilters: JSON.stringify({
|
}
|
||||||
referenceType: doc.schemaName,
|
|
||||||
referenceName: doc.name,
|
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"
|
:df="df"
|
||||||
:value="doc[df.fieldname]"
|
:value="doc[df.fieldname]"
|
||||||
@change="async (value) => await onChange(df, value)"
|
@change="async (value) => await onChange(df, value)"
|
||||||
:read-only="readOnly"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Regular Field Form -->
|
<!-- Regular Field Form -->
|
||||||
@ -39,7 +38,6 @@
|
|||||||
:df="df"
|
:df="df"
|
||||||
:value="doc[df.fieldname]"
|
:value="doc[df.fieldname]"
|
||||||
:class="{ 'p-2': df.fieldtype === 'Check' }"
|
:class="{ 'p-2': df.fieldtype === 'Check' }"
|
||||||
:read-only="readOnly"
|
|
||||||
:text-end="false"
|
:text-end="false"
|
||||||
@change="async (value) => await onChange(df, value)"
|
@change="async (value) => await onChange(df, value)"
|
||||||
@new-doc="async (newdoc) => await onChange(df, newdoc.name)"
|
@new-doc="async (newdoc) => await onChange(df, newdoc.name)"
|
||||||
@ -58,7 +56,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||||
import { handleErrorWithDialog } from 'src/errorHandling';
|
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
import { getErrorMessage } from 'src/utils';
|
import { getErrorMessage } from 'src/utils';
|
||||||
import { evaluateHidden } from 'src/utils/doc';
|
import { evaluateHidden } from 'src/utils/doc';
|
||||||
@ -66,21 +63,13 @@ import Table from './Controls/Table.vue';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TwoColumnForm',
|
name: 'TwoColumnForm',
|
||||||
emits: ['error', 'change'],
|
|
||||||
props: {
|
props: {
|
||||||
doc: Doc,
|
doc: Doc,
|
||||||
fields: { type: Array, default: () => [] },
|
fields: { type: Array, default: () => [] },
|
||||||
autosave: Boolean,
|
|
||||||
columnRatio: {
|
columnRatio: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [1, 1],
|
default: () => [1, 1],
|
||||||
},
|
},
|
||||||
emitChange: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
focusFirstInput: Boolean,
|
|
||||||
readOnly: { type: [null, Boolean], default: null },
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
doc() {
|
doc() {
|
||||||
@ -106,10 +95,6 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.setFormFields();
|
this.setFormFields();
|
||||||
if (this.focusFirstInput) {
|
|
||||||
this.$refs['controls']?.[0].focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fyo.store.isDevelopment) {
|
if (fyo.store.isDevelopment) {
|
||||||
window.tcf = this;
|
window.tcf = this;
|
||||||
}
|
}
|
||||||
@ -131,7 +116,6 @@ export default {
|
|||||||
delete this.errors[fieldname];
|
delete this.errors[fieldname];
|
||||||
|
|
||||||
let isSet = false;
|
let isSet = false;
|
||||||
const oldValue = this.doc.get(fieldname);
|
|
||||||
try {
|
try {
|
||||||
isSet = await this.doc.set(fieldname, value);
|
isSet = await this.doc.set(fieldname, value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -143,35 +127,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isSet) {
|
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();
|
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() {
|
setFormFields() {
|
||||||
|
@ -125,6 +125,8 @@ import { docsPathMap } from 'src/utils/misc';
|
|||||||
import { docsPathRef, focusedDocsRef } from 'src/utils/refs';
|
import { docsPathRef, focusedDocsRef } from 'src/utils/refs';
|
||||||
import { ActionGroup, UIGroupedFields } from 'src/utils/types';
|
import { ActionGroup, UIGroupedFields } from 'src/utils/types';
|
||||||
import {
|
import {
|
||||||
|
commonDocSubmit,
|
||||||
|
commonDocSync,
|
||||||
getDocFromNameIfExistsElseNew,
|
getDocFromNameIfExistsElseNew,
|
||||||
getFieldsGroupedByTabAndSection,
|
getFieldsGroupedByTabAndSection,
|
||||||
getGroupedActionsForDoc,
|
getGroupedActionsForDoc,
|
||||||
@ -270,19 +272,13 @@ export default defineComponent({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
async sync() {
|
async sync() {
|
||||||
try {
|
if (await commonDocSync(this.doc)) {
|
||||||
await this.doc.sync();
|
|
||||||
this.updateGroupedFields();
|
this.updateGroupedFields();
|
||||||
} catch (err) {
|
|
||||||
await handleErrorWithDialog(err, this.doc);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async submit() {
|
async submit() {
|
||||||
try {
|
if (await commonDocSubmit(this.doc)) {
|
||||||
await this.doc.submit();
|
|
||||||
this.updateGroupedFields();
|
this.updateGroupedFields();
|
||||||
} catch (err) {
|
|
||||||
await handleErrorWithDialog(err, this.doc);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setDoc() {
|
async setDoc() {
|
||||||
|
@ -317,9 +317,10 @@ import { fyo } from 'src/initFyo';
|
|||||||
import { docsPathMap } from 'src/utils/misc';
|
import { docsPathMap } from 'src/utils/misc';
|
||||||
import { docsPathRef, focusedDocsRef } from 'src/utils/refs';
|
import { docsPathRef, focusedDocsRef } from 'src/utils/refs';
|
||||||
import {
|
import {
|
||||||
|
commonDocSync,
|
||||||
|
commonDocSubmit,
|
||||||
getGroupedActionsForDoc,
|
getGroupedActionsForDoc,
|
||||||
routeTo,
|
routeTo,
|
||||||
showMessageDialog,
|
|
||||||
} from 'src/utils/ui';
|
} from 'src/utils/ui';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import { handleErrorWithDialog } from '../errorHandling';
|
import { handleErrorWithDialog } from '../errorHandling';
|
||||||
@ -543,37 +544,10 @@ export default {
|
|||||||
return fyo.getField(this.schemaName, fieldname);
|
return fyo.getField(this.schemaName, fieldname);
|
||||||
},
|
},
|
||||||
async sync() {
|
async sync() {
|
||||||
try {
|
await commonDocSync(this.doc);
|
||||||
await this.doc.sync();
|
|
||||||
} catch (err) {
|
|
||||||
await this.handleError(err);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async submit() {
|
async submit() {
|
||||||
const message =
|
await commonDocSubmit(this.doc);
|
||||||
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() {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
async handleError(e) {
|
async handleError(e) {
|
||||||
await handleErrorWithDialog(e, this.doc);
|
await handleErrorWithDialog(e, this.doc);
|
||||||
|
@ -89,12 +89,11 @@
|
|||||||
|
|
||||||
<!-- Rest of the form -->
|
<!-- Rest of the form -->
|
||||||
<TwoColumnForm
|
<TwoColumnForm
|
||||||
|
v-if="doc"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
ref="form"
|
ref="form"
|
||||||
v-if="doc"
|
|
||||||
:doc="doc"
|
:doc="doc"
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
:autosave="false"
|
|
||||||
:column-ratio="[1.1, 2]"
|
:column-ratio="[1.1, 2]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -116,7 +115,12 @@ import TwoColumnForm from 'src/components/TwoColumnForm.vue';
|
|||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
import { getQuickEditWidget } from 'src/utils/quickEditWidgets';
|
import { getQuickEditWidget } from 'src/utils/quickEditWidgets';
|
||||||
import { focusedDocsRef } from 'src/utils/refs';
|
import { focusedDocsRef } from 'src/utils/refs';
|
||||||
import { getActionsForDoc, openQuickEdit } from 'src/utils/ui';
|
import {
|
||||||
|
commonDocSubmit,
|
||||||
|
commonDocSync,
|
||||||
|
getActionsForDoc,
|
||||||
|
openQuickEdit,
|
||||||
|
} from 'src/utils/ui';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'QuickEditForm',
|
name: 'QuickEditForm',
|
||||||
@ -305,27 +309,17 @@ export default {
|
|||||||
},
|
},
|
||||||
async sync() {
|
async sync() {
|
||||||
this.statusText = t`Saving`;
|
this.statusText = t`Saving`;
|
||||||
try {
|
await commonDocSync(this.doc);
|
||||||
await this.$refs.form.sync();
|
setTimeout(() => {
|
||||||
setTimeout(() => {
|
|
||||||
this.statusText = null;
|
|
||||||
}, 300);
|
|
||||||
} catch (err) {
|
|
||||||
this.statusText = null;
|
this.statusText = null;
|
||||||
console.error(err);
|
}, 300);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async submit() {
|
async submit() {
|
||||||
this.statusText = t`Submitting`;
|
this.statusText = t`Submitting`;
|
||||||
try {
|
await commonDocSubmit(this.doc);
|
||||||
await this.$refs.form.submit();
|
setTimeout(() => {
|
||||||
setTimeout(() => {
|
|
||||||
this.statusText = null;
|
|
||||||
}, 300);
|
|
||||||
} catch (err) {
|
|
||||||
this.statusText = null;
|
this.statusText = null;
|
||||||
console.error(err);
|
}, 300);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
routeToPrevious() {
|
routeToPrevious() {
|
||||||
if (this.loadOnClose && this.doc.dirty && !this.doc.notInserted) {
|
if (this.loadOnClose && this.doc.dirty && !this.doc.notInserted) {
|
||||||
|
@ -71,7 +71,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocValue } from 'fyo/core/types';
|
import { DocValue } from 'fyo/core/types';
|
||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import { ValidationError } from 'fyo/utils/errors';
|
|
||||||
import { Field } from 'schemas/types';
|
import { Field } from 'schemas/types';
|
||||||
import Button from 'src/components/Button.vue';
|
import Button from 'src/components/Button.vue';
|
||||||
import FormContainer from 'src/components/FormContainer.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 { Action } from 'fyo/model/types';
|
||||||
import { getActions } from 'fyo/utils';
|
import { getActions } from 'fyo/utils';
|
||||||
import { getDbError, LinkValidationError, ValueError } from 'fyo/utils/errors';
|
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 { ModelNameEnum } from 'models/types';
|
||||||
import { Schema } from 'schemas/types';
|
import { Schema } from 'schemas/types';
|
||||||
import { handleErrorWithDialog } from 'src/errorHandling';
|
import { handleErrorWithDialog } from 'src/errorHandling';
|
||||||
@ -316,10 +319,11 @@ function getCancelAction(doc: Doc): Action {
|
|||||||
condition: (doc: Doc) => doc.canCancel,
|
condition: (doc: Doc) => doc.canCancel,
|
||||||
async action() {
|
async action() {
|
||||||
const res = await cancelDocWithPrompt(doc);
|
const res = await cancelDocWithPrompt(doc);
|
||||||
|
if (!res) {
|
||||||
if (res) {
|
return;
|
||||||
router.push(`/list/${doc.schemaName}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showActionToast(doc, 'cancel');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -333,9 +337,12 @@ function getDeleteAction(doc: Doc): Action {
|
|||||||
condition: (doc: Doc) => doc.canDelete,
|
condition: (doc: Doc) => doc.canDelete,
|
||||||
async action() {
|
async action() {
|
||||||
const res = await deleteDocWithPrompt(doc);
|
const res = await deleteDocWithPrompt(doc);
|
||||||
if (res) {
|
if (!res) {
|
||||||
router.back();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showActionToast(doc, 'delete');
|
||||||
|
router.back();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -360,30 +367,12 @@ function getDuplicateAction(doc: Doc): Action {
|
|||||||
!doc.notInserted
|
!doc.notInserted
|
||||||
),
|
),
|
||||||
async action() {
|
async action() {
|
||||||
await showMessageDialog({
|
try {
|
||||||
message: t`Duplicate ${doc.schemaName} ${doc.name!}?`,
|
const dupe = doc.duplicate();
|
||||||
buttons: [
|
await openEdit(dupe);
|
||||||
{
|
} catch (err) {
|
||||||
label: t`Yes`,
|
handleErrorWithDialog(err as Error, doc);
|
||||||
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',
|
[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