mirror of
https://github.com/frappe/books.git
synced 2025-01-22 14:48:25 +00:00
fix(ui/ux): cleanup forms
- table scroll issue, etc
This commit is contained in:
parent
d99591d058
commit
758727b296
@ -24,7 +24,8 @@ import {
|
||||
areDocValuesEqual,
|
||||
getMissingMandatoryMessage,
|
||||
getPreDefaultValues,
|
||||
shouldApplyFormula
|
||||
setChildDocIdx,
|
||||
shouldApplyFormula,
|
||||
} from './helpers';
|
||||
import { setName } from './naming';
|
||||
import {
|
||||
@ -239,60 +240,53 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
this[field.fieldname] = defaultValue;
|
||||
}
|
||||
}
|
||||
async remove(fieldname: string, idx: number) {
|
||||
const childDocs = ((this[fieldname] ?? []) as Doc[]).filter(
|
||||
(row, i) => row.idx !== idx || i !== idx
|
||||
);
|
||||
|
||||
async append(fieldname: string, docValueMap: Doc | DocValueMap = {}) {
|
||||
// push child row and trigger change
|
||||
this.push(fieldname, docValueMap);
|
||||
this._dirty = true;
|
||||
setChildDocIdx(childDocs);
|
||||
this[fieldname] = childDocs;
|
||||
this._setDirty(true);
|
||||
return await this._applyChange(fieldname);
|
||||
}
|
||||
|
||||
async remove(fieldname: string, idx: number) {
|
||||
let rows = this[fieldname] as Doc[] | undefined;
|
||||
if (!Array.isArray(rows)) {
|
||||
return;
|
||||
}
|
||||
|
||||
async append(fieldname: string, docValueMap: DocValueMap = {}) {
|
||||
this.push(fieldname, docValueMap);
|
||||
this._setDirty(true);
|
||||
rows = rows.filter((row, i) => row.idx !== idx || i !== idx)!;
|
||||
rows.forEach((row, i) => {
|
||||
row.idx = i;
|
||||
});
|
||||
this[fieldname] = rows;
|
||||
return await this._applyChange(fieldname);
|
||||
}
|
||||
|
||||
push(fieldname: string, docValueMap: Doc | DocValueMap = {}) {
|
||||
// push child row without triggering change
|
||||
this[fieldname] ??= [];
|
||||
const childDoc = this._getChildDoc(docValueMap, fieldname);
|
||||
childDoc.parentdoc = this;
|
||||
(this[fieldname] as Doc[]).push(childDoc);
|
||||
const childDocs = [
|
||||
(this[fieldname] ?? []) as Doc[],
|
||||
this._getChildDoc(docValueMap, fieldname),
|
||||
].flat();
|
||||
|
||||
setChildDocIdx(childDocs);
|
||||
this[fieldname] = childDocs;
|
||||
}
|
||||
|
||||
_getChildDoc(docValueMap: Doc | DocValueMap, fieldname: string): Doc {
|
||||
docValueMap.name ??= getRandomString();
|
||||
|
||||
// Child Meta Fields
|
||||
docValueMap.parent ??= this.name;
|
||||
docValueMap.parentSchemaName ??= this.schemaName;
|
||||
docValueMap.parentFieldname ??= fieldname;
|
||||
|
||||
if (docValueMap instanceof Doc) {
|
||||
docValueMap.parentdoc ??= this;
|
||||
return docValueMap;
|
||||
}
|
||||
|
||||
const data: Record<string, unknown> = Object.assign({}, docValueMap);
|
||||
|
||||
data.parent = this.name;
|
||||
data.parentSchemaName = this.schemaName;
|
||||
data.parentFieldname = fieldname;
|
||||
data.parentdoc = this;
|
||||
|
||||
if (!data.idx) {
|
||||
data.idx = ((this[fieldname] as Doc[]) || []).length;
|
||||
}
|
||||
|
||||
if (!data.name) {
|
||||
data.name = getRandomString();
|
||||
}
|
||||
|
||||
const targetField = this.fieldMap[fieldname] as TargetField;
|
||||
const schema = this.fyo.schemaMap[targetField.target] as Schema;
|
||||
const childDoc = new Doc(schema, data as DocValueMap, this.fyo);
|
||||
const childSchemaName = (this.fieldMap[fieldname] as TargetField).target;
|
||||
const childDoc = this.fyo.doc.getNewDoc(
|
||||
childSchemaName,
|
||||
docValueMap,
|
||||
false
|
||||
);
|
||||
childDoc.parentdoc = this;
|
||||
return childDoc;
|
||||
}
|
||||
|
||||
@ -522,7 +516,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
}
|
||||
|
||||
async _applyFormula(fieldname?: string) {
|
||||
async _applyFormula(fieldname?: string): Promise<boolean> {
|
||||
const doc = this;
|
||||
let changed = false;
|
||||
|
||||
@ -532,15 +526,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
|
||||
// children
|
||||
for (const row of childDocs) {
|
||||
const formulaFields = Object.keys(this.formulas).map(
|
||||
(fn) => this.fieldMap[fn]
|
||||
);
|
||||
|
||||
changed ||= await this._applyFormulaForFields(
|
||||
formulaFields,
|
||||
row,
|
||||
fieldname
|
||||
);
|
||||
changed ||= (await row?._applyFormula()) ?? false;
|
||||
}
|
||||
|
||||
// parent or child row
|
||||
|
@ -108,3 +108,9 @@ export function shouldApplyFormula(field: Field, doc: Doc, fieldname?: string) {
|
||||
const value = doc.get(field.fieldname);
|
||||
return getIsNullOrUndef(value);
|
||||
}
|
||||
|
||||
export function setChildDocIdx(childDocs: Doc[]) {
|
||||
for (const idx in childDocs) {
|
||||
childDocs[idx].idx = +idx;
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,12 @@ import Money from 'pesa/dist/types/src/money';
|
||||
|
||||
export class JournalEntryAccount extends Doc {
|
||||
getAutoDebitCredit(type: 'debit' | 'credit') {
|
||||
const otherType = type === 'debit' ? 'credit' : 'debit';
|
||||
const currentValue = this.get(type) as Money;
|
||||
if (!currentValue.isZero()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const otherType = type === 'debit' ? 'credit' : 'debit';
|
||||
const otherTypeValue = this.get(otherType) as Money;
|
||||
if (!otherTypeValue.isZero()) {
|
||||
return this.fyo.pesa(0);
|
||||
@ -26,11 +30,9 @@ export class JournalEntryAccount extends Doc {
|
||||
formulas: FormulaMap = {
|
||||
debit: {
|
||||
formula: async () => this.getAutoDebitCredit('debit'),
|
||||
dependsOn: ['credit'],
|
||||
},
|
||||
credit: {
|
||||
formula: async () => this.getAutoDebitCredit('credit'),
|
||||
dependsOn: ['debit'],
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -91,6 +91,7 @@
|
||||
{
|
||||
"fieldname": "terms",
|
||||
"label": "Terms",
|
||||
"placeholder": "Add invoice terms",
|
||||
"fieldtype": "Text"
|
||||
},
|
||||
{
|
||||
@ -103,4 +104,4 @@
|
||||
}
|
||||
],
|
||||
"keywordFields": ["name", "party", "numberSeries"]
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
<script>
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { nextTick } from 'vue';
|
||||
import Float from './Float.vue';
|
||||
|
||||
export default {
|
||||
@ -61,7 +62,7 @@ export default {
|
||||
},
|
||||
activateInput() {
|
||||
this.showInput = true;
|
||||
this.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
this.focus();
|
||||
});
|
||||
},
|
||||
|
@ -21,7 +21,7 @@
|
||||
</Row>
|
||||
|
||||
<!-- Data Rows -->
|
||||
<div class="overflow-auto" :style="{ 'max-height': rowContainerHeight }">
|
||||
<div class="overflow-auto" :style="{ 'max-height': maxHeight }">
|
||||
<TableRow
|
||||
:class="{ 'pointer-events-none': isReadOnly }"
|
||||
ref="table-row"
|
||||
@ -69,6 +69,7 @@
|
||||
<script>
|
||||
import Row from 'src/components/Row.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { nextTick } from 'vue';
|
||||
import Base from './Base.vue';
|
||||
import TableRow from './TableRow.vue';
|
||||
|
||||
@ -76,13 +77,14 @@ export default {
|
||||
name: 'Table',
|
||||
extends: Base,
|
||||
props: {
|
||||
value: { type: Array, default: () => [] },
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
maxRowsBeforeOverflow: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
default: 3,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
@ -92,26 +94,19 @@ export default {
|
||||
inject: {
|
||||
doc: { default: null },
|
||||
},
|
||||
mounted() {
|
||||
window.tab = this;
|
||||
},
|
||||
data: () => ({ rowContainerHeight: null }),
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(rows) {
|
||||
if (!this.maxRowsBeforeOverflow) return;
|
||||
if (this.rowContainerHeight) return;
|
||||
if (rows && rows.length > 0) {
|
||||
this.$nextTick(() => {
|
||||
let rowHeight = this.$refs['table-row'][0].$el.offsetHeight;
|
||||
let containerHeight = rowHeight * this.maxRowsBeforeOverflow;
|
||||
this.rowContainerHeight = `${containerHeight}px`;
|
||||
});
|
||||
}
|
||||
},
|
||||
value() {
|
||||
this.setMaxHeight();
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return { maxHeight: '' };
|
||||
},
|
||||
mounted() {
|
||||
if (fyo.store.isDevelopment) {
|
||||
window.tab = this;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
focus() {},
|
||||
addRow() {
|
||||
@ -120,7 +115,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
this.scrollToRow(this.value.length - 1);
|
||||
});
|
||||
this.triggerChange(this.value);
|
||||
@ -139,6 +134,24 @@ export default {
|
||||
const row = this.$refs['table-row'][index];
|
||||
row && row.$el.scrollIntoView({ block: 'nearest' });
|
||||
},
|
||||
setMaxHeight() {
|
||||
if (this.maxRowsBeforeOverflow === 0) {
|
||||
return (this.maxHeight = '');
|
||||
}
|
||||
|
||||
const size = this?.value?.length ?? 0;
|
||||
if (size === 0) {
|
||||
return (this.maxHeight = '');
|
||||
}
|
||||
|
||||
const rowHeight = this.$refs?.['table-row']?.[0].$el.offsetHeight;
|
||||
if (rowHeight === undefined) {
|
||||
return (this.maxHeight = '');
|
||||
}
|
||||
|
||||
const maxHeight = rowHeight * Math.min(this.maxRowsBeforeOverflow, size);
|
||||
return (this.maxHeight = `${maxHeight}px`);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
ratio() {
|
||||
|
@ -75,6 +75,7 @@
|
||||
<script>
|
||||
import uniq from 'lodash/uniq';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { nextTick } from 'vue';
|
||||
import Popover from './Popover.vue';
|
||||
|
||||
export default {
|
||||
@ -206,7 +207,7 @@ export default {
|
||||
if (this.highlightedIndex < 0) {
|
||||
this.highlightedIndex = 0;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
let index = this.highlightedIndex;
|
||||
if (index !== 0) {
|
||||
index -= 1;
|
||||
@ -220,7 +221,7 @@ export default {
|
||||
this.highlightedIndex = this.items.length;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
this.scrollToHighlighted();
|
||||
});
|
||||
},
|
||||
|
@ -4,9 +4,9 @@
|
||||
{{ title }}
|
||||
</h1>
|
||||
<BackLink v-if="backLink" />
|
||||
<div class="flex items-stretch window-no-drag">
|
||||
<slot name="actions" />
|
||||
<SearchBar v-show="!hideSearch" class="ml-2" />
|
||||
<div class="flex items-stretch window-no-drag gap-2">
|
||||
<slot />
|
||||
<SearchBar v-show="!hideSearch" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
<script>
|
||||
import { createPopper } from '@popperjs/core';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'Popover',
|
||||
@ -120,7 +121,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.isOpen = true;
|
||||
this.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
this.setupPopper();
|
||||
});
|
||||
this.$emit('open');
|
||||
|
@ -1,20 +1,17 @@
|
||||
<template>
|
||||
<div class="flex flex-col overflow-hidden w-full">
|
||||
<PageHeader :title="t`Data Import`">
|
||||
<template #actions>
|
||||
<DropdownWithActions
|
||||
class="ml-2"
|
||||
:actions="actions"
|
||||
v-if="(canCancel || importType) && !complete"
|
||||
/>
|
||||
<Button
|
||||
v-if="importType && !complete"
|
||||
type="primary"
|
||||
class="text-sm ml-2"
|
||||
@click="handlePrimaryClick"
|
||||
>{{ primaryLabel }}</Button
|
||||
>
|
||||
</template>
|
||||
<DropdownWithActions
|
||||
:actions="actions"
|
||||
v-if="(canCancel || importType) && !complete"
|
||||
/>
|
||||
<Button
|
||||
v-if="importType && !complete"
|
||||
type="primary"
|
||||
class="text-sm"
|
||||
@click="handlePrimaryClick"
|
||||
>{{ primaryLabel }}</Button
|
||||
>
|
||||
</PageHeader>
|
||||
<div
|
||||
class="flex px-8 mt-2 text-base w-full flex-col gap-8"
|
||||
@ -573,7 +570,10 @@ export default {
|
||||
this.clear();
|
||||
}
|
||||
this.importType = importType;
|
||||
this.importer = new Importer(this.labelSchemaNameMap[this.importType], fyo);
|
||||
this.importer = new Importer(
|
||||
this.labelSchemaNameMap[this.importType],
|
||||
fyo
|
||||
);
|
||||
},
|
||||
setLoadingStatus(isMakingEntries, entriesMade, totalEntries) {
|
||||
this.isMakingEntries = isMakingEntries;
|
||||
|
@ -1,63 +1,73 @@
|
||||
<template>
|
||||
<div class="flex flex-col" v-if="doc">
|
||||
<!-- Page Header (Title, Buttons, etc) -->
|
||||
<PageHeader :backLink="true">
|
||||
<template #actions>
|
||||
<StatusBadge :status="status" />
|
||||
<Button
|
||||
v-if="doc.submitted"
|
||||
class="text-gray-900 text-xs ml-2"
|
||||
:icon="true"
|
||||
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
|
||||
>
|
||||
Print
|
||||
</Button>
|
||||
<DropdownWithActions class="ml-2" :actions="actions" />
|
||||
<Button
|
||||
v-if="showSave"
|
||||
type="primary"
|
||||
class="text-white text-xs ml-2"
|
||||
@click="onSaveClick"
|
||||
>
|
||||
{{ t`Save` }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!doc.dirty && !doc.notInserted && !doc.submitted"
|
||||
type="primary"
|
||||
class="text-white text-xs ml-2"
|
||||
@click="onSubmitClick"
|
||||
>{{ t`Submit` }}</Button
|
||||
>
|
||||
</template>
|
||||
<StatusBadge :status="status" />
|
||||
<Button
|
||||
v-if="doc?.submitted"
|
||||
class="text-gray-900 text-xs"
|
||||
:icon="true"
|
||||
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
|
||||
>
|
||||
{{ t`Print` }}
|
||||
</Button>
|
||||
<DropdownWithActions :actions="actions" />
|
||||
<Button
|
||||
v-if="showSave"
|
||||
type="primary"
|
||||
class="text-white text-xs"
|
||||
@click="onSaveClick"
|
||||
>
|
||||
{{ t`Save` }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!doc.dirty && !doc.notInserted && !doc?.submitted"
|
||||
type="primary"
|
||||
class="text-white text-xs"
|
||||
@click="onSubmitClick"
|
||||
>{{ t`Submit` }}</Button
|
||||
>
|
||||
</PageHeader>
|
||||
|
||||
<!-- Invoice Form -->
|
||||
<div class="flex justify-center flex-1 mb-8 mt-2" v-if="doc">
|
||||
<div
|
||||
class="border rounded-lg shadow h-full flex flex-col justify-between"
|
||||
style="width: 600px"
|
||||
class="
|
||||
border
|
||||
rounded-lg
|
||||
shadow
|
||||
h-full
|
||||
flex flex-col
|
||||
justify-between
|
||||
w-600
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div class="px-6 pt-6" v-if="printSettings">
|
||||
<div class="flex text-sm text-gray-900 border-b pb-4">
|
||||
<div class="w-1/3">
|
||||
<div v-if="printSettings.displayLogo">
|
||||
<img
|
||||
class="h-12 max-w-32 object-contain"
|
||||
:src="printSettings.logo"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xl text-gray-700 font-semibold" v-else>
|
||||
{{ companyName }}
|
||||
</div>
|
||||
<!-- Print Settings Info (Logo, Address, Etc) -->
|
||||
<div class="flex text-sm text-gray-900 p-6" v-if="printSettings">
|
||||
<div class="w-1/3">
|
||||
<div v-if="printSettings.displayLogo">
|
||||
<img
|
||||
class="h-12 max-w-32 object-contain"
|
||||
:src="printSettings.logo"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<div>{{ printSettings.email }}</div>
|
||||
<div class="mt-1">{{ printSettings.phone }}</div>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<div v-if="address">{{ address.addressDisplay }}</div>
|
||||
<div class="text-xl text-gray-700 font-semibold" v-else>
|
||||
{{ companyName }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<div>{{ printSettings.email }}</div>
|
||||
<div class="mt-1">{{ printSettings.phone }}</div>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<div v-if="address">{{ address.addressDisplay }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 px-6">
|
||||
<hr />
|
||||
|
||||
<!-- Invoice Form Data Entry -->
|
||||
<div class="m-6 flex flex-col gap-2">
|
||||
<h1 class="text-2xl font-semibold">
|
||||
{{
|
||||
doc.notInserted
|
||||
@ -67,124 +77,139 @@
|
||||
: doc.name
|
||||
}}
|
||||
</h1>
|
||||
<div class="flex justify-between mt-2">
|
||||
<div class="w-1/3">
|
||||
<FormControl
|
||||
class="bg-gray-100 rounded text-base"
|
||||
input-class="p-2 text-lg font-semibold bg-transparent"
|
||||
:df="getField('party')"
|
||||
:value="doc.party"
|
||||
:placeholder="getField('party').label"
|
||||
@change="(value) => doc.set('party', value)"
|
||||
@new-doc="(party) => doc.set('party', party.name)"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
class="mt-2 text-base bg-gray-100 rounded"
|
||||
input-class="px-3 py-2 text-base bg-transparent"
|
||||
:df="getField('account')"
|
||||
:value="doc.account"
|
||||
:placeholder="'Account'"
|
||||
@change="(value) => doc.set('account', value)"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<FormControl
|
||||
input-class="bg-gray-100 px-3 py-2 text-base text-right"
|
||||
:df="getField('date')"
|
||||
:value="doc.date"
|
||||
:placeholder="'Date'"
|
||||
@change="(value) => doc.set('date', value)"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
class="mt-2 text-base bg-gray-100 rounded"
|
||||
input-class="bg-transparent px-3 py-2 text-base text-right"
|
||||
:df="getField('numberSeries')"
|
||||
:value="doc.numberSeries"
|
||||
@change="(value) => doc.set('numberSeries', value)"
|
||||
:read-only="!doc.notInserted || doc.submitted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- First Row of Fields -->
|
||||
<div class="flex flex-row justify-between gap-2">
|
||||
<FormControl
|
||||
class="bg-gray-100 rounded text-base w-1/3"
|
||||
input-class="text-lg font-semibold bg-transparent"
|
||||
:df="getField('party')"
|
||||
:value="doc.party"
|
||||
:placeholder="getField('party').label"
|
||||
@change="(value) => doc.set('party', value)"
|
||||
@new-doc="(party) => doc.set('party', party.name)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
<div class="w-1/3" />
|
||||
<FormControl
|
||||
class="w-1/3"
|
||||
input-class="bg-gray-100 px-3 py-2 text-base text-right"
|
||||
:df="getField('date')"
|
||||
:value="doc.date"
|
||||
:placeholder="'Date'"
|
||||
@change="(value) => doc.set('date', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Second Row of Fields -->
|
||||
<div class="flex flex-row justify-between gap-2">
|
||||
<FormControl
|
||||
class="text-base bg-gray-100 rounded w-1/3"
|
||||
input-class="px-3 py-2 text-base bg-transparent"
|
||||
:df="getField('account')"
|
||||
:value="doc.account"
|
||||
:placeholder="'Account'"
|
||||
@change="(value) => doc.set('account', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
<div class="w-1/3" />
|
||||
<FormControl
|
||||
class="text-base bg-gray-100 rounded w-1/3"
|
||||
input-class="bg-transparent px-3 py-2 text-base text-right"
|
||||
:df="getField('numberSeries')"
|
||||
:value="doc.numberSeries"
|
||||
@change="(value) => doc.set('numberSeries', value)"
|
||||
:read-only="!doc.notInserted || doc?.submitted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 text-base">
|
||||
<FormControl
|
||||
:df="getField('items')"
|
||||
:value="doc.items"
|
||||
:showHeader="true"
|
||||
:max-rows-before-overflow="4"
|
||||
@change="(value) => doc.set('items', value)"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<!-- Invoice Items Table -->
|
||||
<Table
|
||||
class="px-6 text-base mt-4"
|
||||
:df="getField('items')"
|
||||
:value="doc.items"
|
||||
:showHeader="true"
|
||||
:max-rows-before-overflow="4"
|
||||
@change="(value) => doc.set('items', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="px-6 mb-6 flex justify-between text-base"
|
||||
v-if="doc.items?.length ?? 0"
|
||||
>
|
||||
<div class="flex-1 mr-10">
|
||||
|
||||
<!-- Invoice Form Footer -->
|
||||
|
||||
<div v-if="doc.items?.length ?? 0">
|
||||
<hr />
|
||||
<div class="flex justify-between text-base m-6 gap-12">
|
||||
<!-- Form Terms-->
|
||||
<FormControl
|
||||
v-if="!doc.submitted || doc.terms"
|
||||
class="w-1/2 self-end"
|
||||
v-if="!doc?.submitted || doc.terms"
|
||||
:df="getField('terms')"
|
||||
:value="doc.terms"
|
||||
:show-label="true"
|
||||
input-class="bg-gray-100"
|
||||
@change="(value) => doc.set('terms', value)"
|
||||
:read-only="doc.submitted"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-64">
|
||||
<div class="flex pl-2 justify-between py-3 border-b">
|
||||
<div>{{ t`Subtotal` }}</div>
|
||||
<div>{{ formattedValue('netTotal') }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex pl-2 justify-between py-3"
|
||||
v-for="tax in doc.taxes"
|
||||
:key="tax.name"
|
||||
>
|
||||
<div>{{ tax.account }}</div>
|
||||
<div>
|
||||
{{
|
||||
fyo.format(tax.amount, {
|
||||
fieldtype: 'Currency',
|
||||
currency: doc.currency,
|
||||
})
|
||||
}}
|
||||
|
||||
<!-- Totals -->
|
||||
<div class="w-1/2 gap-2 flex flex-col self-end">
|
||||
<!-- Subtotal -->
|
||||
<div class="flex justify-between">
|
||||
<div>{{ t`Subtotal` }}</div>
|
||||
<div>{{ formattedValue('netTotal') }}</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<!-- Taxes -->
|
||||
<div
|
||||
class="flex justify-between"
|
||||
v-for="tax in doc.taxes"
|
||||
:key="tax.name"
|
||||
>
|
||||
<div>{{ tax.account }}</div>
|
||||
<div>
|
||||
{{
|
||||
fyo.format(tax.amount, {
|
||||
fieldtype: 'Currency',
|
||||
currency: doc.currency,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<hr v-if="doc.taxes?.length" />
|
||||
|
||||
<!-- Grand Total -->
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
justify-between
|
||||
text-green-600
|
||||
font-semibold
|
||||
text-base
|
||||
"
|
||||
>
|
||||
<div>{{ t`Grand Total` }}</div>
|
||||
<div>{{ formattedValue('grandTotal') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Outstanding Amount -->
|
||||
<hr v-if="doc.outstandingAmount > 0" />
|
||||
<div
|
||||
v-if="doc.outstandingAmount > 0"
|
||||
class="
|
||||
flex
|
||||
justify-between
|
||||
text-red-600
|
||||
font-semibold
|
||||
text-base
|
||||
"
|
||||
>
|
||||
<div>{{ t`Outstanding Amount` }}</div>
|
||||
<div>{{ formattedValue('outstandingAmount') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
pl-2
|
||||
justify-between
|
||||
py-3
|
||||
border-t
|
||||
text-green-600
|
||||
font-semibold
|
||||
text-base
|
||||
"
|
||||
>
|
||||
<div>{{ t`Grand Total` }}</div>
|
||||
<div>{{ formattedValue('grandTotal') }}</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="doc.outstandingAmount > 0"
|
||||
class="
|
||||
flex
|
||||
pl-2
|
||||
justify-between
|
||||
py-3
|
||||
border-t
|
||||
text-red-600
|
||||
font-semibold
|
||||
text-base
|
||||
"
|
||||
>
|
||||
<div>{{ t`Outstanding Amount` }}</div>
|
||||
<div>{{ formattedValue('outstandingAmount') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -198,15 +223,16 @@ import { getInvoiceStatus } from 'models/helpers';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||
import Table from 'src/components/Controls/Table.vue';
|
||||
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
|
||||
import PageHeader from 'src/components/PageHeader.vue';
|
||||
import StatusBadge from 'src/components/StatusBadge.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import {
|
||||
getActionsForDocument,
|
||||
openSettings,
|
||||
routeTo,
|
||||
showMessageDialog,
|
||||
getActionsForDocument,
|
||||
openSettings,
|
||||
routeTo,
|
||||
showMessageDialog
|
||||
} from 'src/utils/ui';
|
||||
import { handleErrorWithDialog } from '../errorHandling';
|
||||
|
||||
@ -219,7 +245,8 @@ export default {
|
||||
Button,
|
||||
FormControl,
|
||||
DropdownWithActions,
|
||||
},
|
||||
Table
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
schemaName: this.schemaName,
|
||||
|
@ -2,13 +2,13 @@
|
||||
<div class="flex flex-col">
|
||||
<!-- Page Header (Title, Buttons, etc) -->
|
||||
<PageHeader :backLink="true">
|
||||
<template #actions v-if="doc">
|
||||
<template v-if="doc">
|
||||
<StatusBadge :status="status" />
|
||||
<DropdownWithActions class="ml-2" :actions="actions" />
|
||||
<DropdownWithActions :actions="actions" />
|
||||
<Button
|
||||
v-if="doc.notInserted || doc.dirty"
|
||||
type="primary"
|
||||
class="text-white text-xs ml-2"
|
||||
class="text-white text-xs"
|
||||
@click="sync"
|
||||
>
|
||||
{{ t`Save` }}
|
||||
@ -16,7 +16,7 @@
|
||||
<Button
|
||||
v-else-if="!doc.dirty && !doc.notInserted && !doc.submitted"
|
||||
type="primary"
|
||||
class="text-white text-xs ml-2"
|
||||
class="text-white text-xs"
|
||||
@click="submit"
|
||||
>
|
||||
{{ t`Submit` }}
|
||||
@ -27,112 +27,119 @@
|
||||
<!-- Journal Entry Form -->
|
||||
<div v-if="doc" class="flex justify-center flex-1 mb-8 mt-2">
|
||||
<div
|
||||
class="border rounded-lg shadow h-full flex flex-col justify-between"
|
||||
style="width: 600px"
|
||||
class="
|
||||
border
|
||||
rounded-lg
|
||||
shadow
|
||||
h-full
|
||||
flex flex-col
|
||||
justify-between
|
||||
w-600
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div class="mt-8 px-6">
|
||||
<div class="m-6 flex flex-col gap-2">
|
||||
<h1 class="text-2xl font-semibold">
|
||||
{{ doc.notInserted ? t`New Journal Entry` : doc.name }}
|
||||
</h1>
|
||||
|
||||
<!-- First Column of Fields -->
|
||||
<div class="flex justify-between mt-2 gap-2">
|
||||
<div class="w-1/3">
|
||||
<FormControl
|
||||
:df="getField('entryType')"
|
||||
:value="doc.entryType"
|
||||
placeholder="Entry Type"
|
||||
@change="(value) => doc.set('entryType', value)"
|
||||
input-class="bg-gray-100 px-3 py-2 text-base"
|
||||
:read-only="doc.submitted"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
/>
|
||||
<FormControl
|
||||
class="mt-2"
|
||||
:df="getField('date')"
|
||||
:value="doc.date"
|
||||
:placeholder="'Date'"
|
||||
@change="(value) => doc.set('date', value)"
|
||||
input-class="bg-gray-100 px-3 py-2 text-base"
|
||||
:read-only="doc.submitted"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between gap-2">
|
||||
<FormControl
|
||||
class="w-1/3"
|
||||
:df="getField('entryType')"
|
||||
:value="doc.entryType"
|
||||
placeholder="Entry Type"
|
||||
@change="(value) => doc.set('entryType', value)"
|
||||
input-class="bg-gray-100 px-3 py-2 text-base"
|
||||
:read-only="doc.submitted"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
/>
|
||||
<FormControl
|
||||
class="w-1/3"
|
||||
:df="getField('referenceNumber')"
|
||||
:value="doc.referenceNumber"
|
||||
:placeholder="'Reference Number'"
|
||||
@change="(value) => doc.set('referenceNumber', value)"
|
||||
input-class="bg-gray-100 p-2 text-base"
|
||||
:read-only="doc.submitted"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
/>
|
||||
<FormControl
|
||||
:df="getField('numberSeries')"
|
||||
:value="doc.numberSeries"
|
||||
@change="(value) => doc.set('numberSeries', value)"
|
||||
class="bg-gray-100 rounded w-1/3"
|
||||
input-class="p-2 text-base bg-transparent"
|
||||
:read-only="!doc.notInserted || doc.submitted"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Second Column of Fields -->
|
||||
<div class="w-1/3">
|
||||
<FormControl
|
||||
:df="getField('referenceNumber')"
|
||||
:value="doc.referenceNumber"
|
||||
:placeholder="'Reference Number'"
|
||||
@change="(value) => doc.set('referenceNumber', value)"
|
||||
input-class="bg-gray-100 p-2 text-base"
|
||||
:read-only="doc.submitted"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
/>
|
||||
<FormControl
|
||||
class="mt-2"
|
||||
:df="getField('referenceDate')"
|
||||
:value="doc.referenceDate"
|
||||
:placeholder="'Reference Date'"
|
||||
@change="(value) => doc.set('referenceDate', value)"
|
||||
input-class="bg-gray-100 px-3 py-2 text-base"
|
||||
:read-only="doc.submitted"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Third Column of Fields -->
|
||||
<div class="w-1/3">
|
||||
<FormControl
|
||||
:df="getField('numberSeries')"
|
||||
:value="doc.numberSeries"
|
||||
@change="(value) => doc.set('numberSeries', value)"
|
||||
class="bg-gray-100 rounded"
|
||||
input-class="p-2 text-base bg-transparent"
|
||||
:read-only="!doc.notInserted || doc.submitted"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
/>
|
||||
</div>
|
||||
<!-- Second Row of Fields -->
|
||||
<div class="flex flex-row justify-between gap-2">
|
||||
<FormControl
|
||||
class="w-1/3"
|
||||
:df="getField('date')"
|
||||
:value="doc.date"
|
||||
:placeholder="'Date'"
|
||||
@change="(value) => doc.set('date', value)"
|
||||
input-class="bg-gray-100 px-3 py-2 text-base"
|
||||
:read-only="doc.submitted"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
/>
|
||||
<div class="w-1/3" />
|
||||
<FormControl
|
||||
class="w-1/3"
|
||||
:df="getField('referenceDate')"
|
||||
:value="doc.referenceDate"
|
||||
:placeholder="'Reference Date'"
|
||||
@change="(value) => doc.set('referenceDate', value)"
|
||||
input-class="bg-gray-100 px-3 py-2 text-base"
|
||||
:read-only="doc.submitted"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<!-- Account Entries Table -->
|
||||
<FormControl
|
||||
<Table
|
||||
class="mt-2 px-6 text-base"
|
||||
:df="getField('accounts')"
|
||||
:value="doc.accounts"
|
||||
:showHeader="true"
|
||||
:max-rows-before-overflow="4"
|
||||
@change="(value) => doc.set('accounts', value)"
|
||||
:max-rows-before-overflow="6"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="px-6 mb-6">
|
||||
<div
|
||||
class="grid items-center border-t pt-3 pr-2"
|
||||
style="grid-template-columns: 1.3fr 1fr 1fr"
|
||||
>
|
||||
<div v-if="doc.accounts?.length ?? 0">
|
||||
<hr />
|
||||
<div class="flex justify-between text-base m-6 gap-12">
|
||||
<!-- User Remark -->
|
||||
<div class="text-sm">
|
||||
<FormControl
|
||||
:df="getField('userRemark')"
|
||||
:value="doc.userRemark"
|
||||
@change="(value) => doc.set('userRemark', value)"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
</div>
|
||||
<FormControl
|
||||
class="w-1/2 self-end"
|
||||
input-class="bg-gray-100"
|
||||
:df="getField('userRemark')"
|
||||
:value="doc.userRemark"
|
||||
@change="(value) => doc.set('userRemark', value)"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
|
||||
<!-- Debit and Credit -->
|
||||
<div class="text-right font-semibold text-green-600 px-3">
|
||||
{{ totalDebit }}
|
||||
</div>
|
||||
<div class="text-right font-semibold text-red-600 px-3">
|
||||
{{ totalCredit }}
|
||||
<div class="w-1/2 gap-2 flex flex-col self-end font-semibold">
|
||||
<div class="flex justify-between text-green-600">
|
||||
<div>{{ t`Total Debit` }}</div>
|
||||
<div>{{ totalDebit }}</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="flex justify-between text-red-600">
|
||||
<div>{{ t`Total Credit` }}</div>
|
||||
<div>{{ totalCredit }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -145,6 +152,7 @@ import { computed } from '@vue/reactivity';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import Button from 'src/components/Button';
|
||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||
import Table from 'src/components/Controls/Table.vue';
|
||||
import DropdownWithActions from 'src/components/DropdownWithActions';
|
||||
import PageHeader from 'src/components/PageHeader';
|
||||
import StatusBadge from 'src/components/StatusBadge';
|
||||
@ -165,7 +173,8 @@ export default {
|
||||
DropdownWithActions,
|
||||
StatusBadge,
|
||||
FormControl,
|
||||
},
|
||||
Table
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
schemaName: this.schemaName,
|
||||
|
@ -1,16 +1,14 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<PageHeader :title="title">
|
||||
<template #actions>
|
||||
<FilterDropdown
|
||||
ref="filterDropdown"
|
||||
@change="applyFilter"
|
||||
:fields="fields"
|
||||
/>
|
||||
<Button class="ml-2" :icon="true" type="primary" @click="makeNewDoc">
|
||||
<feather-icon name="plus" class="w-4 h-4 text-white" />
|
||||
</Button>
|
||||
</template>
|
||||
<FilterDropdown
|
||||
ref="filterDropdown"
|
||||
@change="applyFilter"
|
||||
:fields="fields"
|
||||
/>
|
||||
<Button :icon="true" type="primary" @click="makeNewDoc">
|
||||
<feather-icon name="plus" class="w-4 h-4 text-white" />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<List
|
||||
ref="list"
|
||||
|
@ -2,17 +2,15 @@
|
||||
<div class="flex">
|
||||
<div class="flex flex-col flex-1">
|
||||
<PageHeader :backLink="true" class="bg-white z-10">
|
||||
<template #actions>
|
||||
<Button
|
||||
class="text-gray-900 text-xs ml-2"
|
||||
@click="showCustomiser = !showCustomiser"
|
||||
>
|
||||
{{ t`Customise` }}
|
||||
</Button>
|
||||
<Button class="text-gray-900 text-xs ml-2" @click="makePDF">
|
||||
{{ t`Save as PDF` }}
|
||||
</Button>
|
||||
</template>
|
||||
<Button
|
||||
class="text-gray-900 text-xs"
|
||||
@click="showCustomiser = !showCustomiser"
|
||||
>
|
||||
{{ t`Customise` }}
|
||||
</Button>
|
||||
<Button class="text-gray-900 text-xs" @click="makePDF">
|
||||
{{ t`Save as PDF` }}
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<div
|
||||
v-if="doc && printSettings"
|
||||
|
@ -197,7 +197,7 @@ export default {
|
||||
|
||||
// set default values
|
||||
if (this.values) {
|
||||
this.doc.set(this.values);
|
||||
this.doc?.set(this.values);
|
||||
}
|
||||
|
||||
// set title size
|
||||
@ -205,7 +205,7 @@ export default {
|
||||
},
|
||||
setTitleField() {
|
||||
const { fieldname, readOnly } = this.titleField;
|
||||
if (!this.doc?.notInserted || !this.doc[fieldname]) {
|
||||
if (!this.doc?.notInserted || !this?.doc[fieldname]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,16 @@
|
||||
<template>
|
||||
<div class="flex flex-col max-w-full">
|
||||
<PageHeader :title="report.title">
|
||||
<template #actions>
|
||||
<DropdownWithActions
|
||||
v-for="group of actionGroups"
|
||||
:key="group.label"
|
||||
:type="group.type"
|
||||
:actions="group.actions"
|
||||
class="ml-2 text-xs"
|
||||
>
|
||||
{{ group.label }}
|
||||
</DropdownWithActions>
|
||||
<DropdownWithActions class="ml-2" :actions="actions" />
|
||||
</template>
|
||||
<DropdownWithActions
|
||||
v-for="group of actionGroups"
|
||||
:key="group.label"
|
||||
:type="group.type"
|
||||
:actions="group.actions"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ group.label }}
|
||||
</DropdownWithActions>
|
||||
<DropdownWithActions :actions="actions" />
|
||||
</PageHeader>
|
||||
<div class="flex px-8 mt-2 text-base" v-if="report.filterFields">
|
||||
<div
|
||||
@ -142,7 +140,7 @@ import PageHeader from 'src/components/PageHeader';
|
||||
import Row from 'src/components/Row';
|
||||
import WithScroll from 'src/components/WithScroll';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { h, markRaw } from 'vue';
|
||||
import { h, markRaw, nextTick } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'Report',
|
||||
@ -185,7 +183,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onBodyScroll({ scrollLeft }) {
|
||||
this.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
this.$refs.header.scrollLeft = scrollLeft;
|
||||
});
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user