2
0
mirror of https://github.com/frappe/books.git synced 2025-01-22 22:58:28 +00:00

fix(ui/ux): cleanup forms

- table scroll issue, etc
This commit is contained in:
18alantom 2022-05-01 13:09:03 +05:30
parent d99591d058
commit 758727b296
16 changed files with 421 additions and 380 deletions

View File

@ -24,7 +24,8 @@ import {
areDocValuesEqual, areDocValuesEqual,
getMissingMandatoryMessage, getMissingMandatoryMessage,
getPreDefaultValues, getPreDefaultValues,
shouldApplyFormula setChildDocIdx,
shouldApplyFormula,
} from './helpers'; } from './helpers';
import { setName } from './naming'; import { setName } from './naming';
import { import {
@ -239,60 +240,53 @@ export class Doc extends Observable<DocValue | Doc[]> {
this[field.fieldname] = defaultValue; 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 = {}) { setChildDocIdx(childDocs);
// push child row and trigger change this[fieldname] = childDocs;
this.push(fieldname, docValueMap); this._setDirty(true);
this._dirty = true;
return await this._applyChange(fieldname); return await this._applyChange(fieldname);
} }
async remove(fieldname: string, idx: number) { async append(fieldname: string, docValueMap: DocValueMap = {}) {
let rows = this[fieldname] as Doc[] | undefined; this.push(fieldname, docValueMap);
if (!Array.isArray(rows)) {
return;
}
this._setDirty(true); 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); return await this._applyChange(fieldname);
} }
push(fieldname: string, docValueMap: Doc | DocValueMap = {}) { push(fieldname: string, docValueMap: Doc | DocValueMap = {}) {
// push child row without triggering change const childDocs = [
this[fieldname] ??= []; (this[fieldname] ?? []) as Doc[],
const childDoc = this._getChildDoc(docValueMap, fieldname); this._getChildDoc(docValueMap, fieldname),
childDoc.parentdoc = this; ].flat();
(this[fieldname] as Doc[]).push(childDoc);
setChildDocIdx(childDocs);
this[fieldname] = childDocs;
} }
_getChildDoc(docValueMap: Doc | DocValueMap, fieldname: string): Doc { _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) { if (docValueMap instanceof Doc) {
docValueMap.parentdoc ??= this;
return docValueMap; return docValueMap;
} }
const data: Record<string, unknown> = Object.assign({}, docValueMap); const childSchemaName = (this.fieldMap[fieldname] as TargetField).target;
const childDoc = this.fyo.doc.getNewDoc(
data.parent = this.name; childSchemaName,
data.parentSchemaName = this.schemaName; docValueMap,
data.parentFieldname = fieldname; false
data.parentdoc = this; );
childDoc.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);
return childDoc; 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; const doc = this;
let changed = false; let changed = false;
@ -532,15 +526,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
// children // children
for (const row of childDocs) { for (const row of childDocs) {
const formulaFields = Object.keys(this.formulas).map( changed ||= (await row?._applyFormula()) ?? false;
(fn) => this.fieldMap[fn]
);
changed ||= await this._applyFormulaForFields(
formulaFields,
row,
fieldname
);
} }
// parent or child row // parent or child row

View File

@ -108,3 +108,9 @@ export function shouldApplyFormula(field: Field, doc: Doc, fieldname?: string) {
const value = doc.get(field.fieldname); const value = doc.get(field.fieldname);
return getIsNullOrUndef(value); return getIsNullOrUndef(value);
} }
export function setChildDocIdx(childDocs: Doc[]) {
for (const idx in childDocs) {
childDocs[idx].idx = +idx;
}
}

View File

@ -4,8 +4,12 @@ import Money from 'pesa/dist/types/src/money';
export class JournalEntryAccount extends Doc { export class JournalEntryAccount extends Doc {
getAutoDebitCredit(type: 'debit' | 'credit') { 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; const otherTypeValue = this.get(otherType) as Money;
if (!otherTypeValue.isZero()) { if (!otherTypeValue.isZero()) {
return this.fyo.pesa(0); return this.fyo.pesa(0);
@ -26,11 +30,9 @@ export class JournalEntryAccount extends Doc {
formulas: FormulaMap = { formulas: FormulaMap = {
debit: { debit: {
formula: async () => this.getAutoDebitCredit('debit'), formula: async () => this.getAutoDebitCredit('debit'),
dependsOn: ['credit'],
}, },
credit: { credit: {
formula: async () => this.getAutoDebitCredit('credit'), formula: async () => this.getAutoDebitCredit('credit'),
dependsOn: ['debit'],
}, },
}; };

View File

@ -91,6 +91,7 @@
{ {
"fieldname": "terms", "fieldname": "terms",
"label": "Terms", "label": "Terms",
"placeholder": "Add invoice terms",
"fieldtype": "Text" "fieldtype": "Text"
}, },
{ {

View File

@ -29,6 +29,7 @@
<script> <script>
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
import { nextTick } from 'vue';
import Float from './Float.vue'; import Float from './Float.vue';
export default { export default {
@ -61,7 +62,7 @@ export default {
}, },
activateInput() { activateInput() {
this.showInput = true; this.showInput = true;
this.$nextTick(() => { nextTick(() => {
this.focus(); this.focus();
}); });
}, },

View File

@ -21,7 +21,7 @@
</Row> </Row>
<!-- Data Rows --> <!-- Data Rows -->
<div class="overflow-auto" :style="{ 'max-height': rowContainerHeight }"> <div class="overflow-auto" :style="{ 'max-height': maxHeight }">
<TableRow <TableRow
:class="{ 'pointer-events-none': isReadOnly }" :class="{ 'pointer-events-none': isReadOnly }"
ref="table-row" ref="table-row"
@ -69,6 +69,7 @@
<script> <script>
import Row from 'src/components/Row.vue'; import Row from 'src/components/Row.vue';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
import { nextTick } from 'vue';
import Base from './Base.vue'; import Base from './Base.vue';
import TableRow from './TableRow.vue'; import TableRow from './TableRow.vue';
@ -76,13 +77,14 @@ export default {
name: 'Table', name: 'Table',
extends: Base, extends: Base,
props: { props: {
value: { type: Array, default: () => [] },
showHeader: { showHeader: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
maxRowsBeforeOverflow: { maxRowsBeforeOverflow: {
type: Number, type: Number,
default: 0, default: 3,
}, },
}, },
components: { components: {
@ -92,26 +94,19 @@ export default {
inject: { inject: {
doc: { default: null }, doc: { default: null },
}, },
mounted() {
window.tab = this;
},
data: () => ({ rowContainerHeight: null }),
watch: { watch: {
value: { value() {
immediate: true, this.setMaxHeight();
handler(rows) { },
if (!this.maxRowsBeforeOverflow) return; },
if (this.rowContainerHeight) return; data() {
if (rows && rows.length > 0) { return { maxHeight: '' };
this.$nextTick(() => { },
let rowHeight = this.$refs['table-row'][0].$el.offsetHeight; mounted() {
let containerHeight = rowHeight * this.maxRowsBeforeOverflow; if (fyo.store.isDevelopment) {
this.rowContainerHeight = `${containerHeight}px`; window.tab = this;
});
} }
}, },
},
},
methods: { methods: {
focus() {}, focus() {},
addRow() { addRow() {
@ -120,7 +115,7 @@ export default {
return; return;
} }
this.$nextTick(() => { nextTick(() => {
this.scrollToRow(this.value.length - 1); this.scrollToRow(this.value.length - 1);
}); });
this.triggerChange(this.value); this.triggerChange(this.value);
@ -139,6 +134,24 @@ export default {
const row = this.$refs['table-row'][index]; const row = this.$refs['table-row'][index];
row && row.$el.scrollIntoView({ block: 'nearest' }); 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: { computed: {
ratio() { ratio() {

View File

@ -75,6 +75,7 @@
<script> <script>
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
import { nextTick } from 'vue';
import Popover from './Popover.vue'; import Popover from './Popover.vue';
export default { export default {
@ -206,7 +207,7 @@ export default {
if (this.highlightedIndex < 0) { if (this.highlightedIndex < 0) {
this.highlightedIndex = 0; this.highlightedIndex = 0;
} }
this.$nextTick(() => { nextTick(() => {
let index = this.highlightedIndex; let index = this.highlightedIndex;
if (index !== 0) { if (index !== 0) {
index -= 1; index -= 1;
@ -220,7 +221,7 @@ export default {
this.highlightedIndex = this.items.length; this.highlightedIndex = this.items.length;
} }
this.$nextTick(() => { nextTick(() => {
this.scrollToHighlighted(); this.scrollToHighlighted();
}); });
}, },

View File

@ -4,9 +4,9 @@
{{ title }} {{ title }}
</h1> </h1>
<BackLink v-if="backLink" /> <BackLink v-if="backLink" />
<div class="flex items-stretch window-no-drag"> <div class="flex items-stretch window-no-drag gap-2">
<slot name="actions" /> <slot />
<SearchBar v-show="!hideSearch" class="ml-2" /> <SearchBar v-show="!hideSearch" />
</div> </div>
</div> </div>
</template> </template>

View File

@ -21,6 +21,7 @@
<script> <script>
import { createPopper } from '@popperjs/core'; import { createPopper } from '@popperjs/core';
import { nextTick } from 'vue';
export default { export default {
name: 'Popover', name: 'Popover',
@ -120,7 +121,7 @@ export default {
return; return;
} }
this.isOpen = true; this.isOpen = true;
this.$nextTick(() => { nextTick(() => {
this.setupPopper(); this.setupPopper();
}); });
this.$emit('open'); this.$emit('open');

View File

@ -1,20 +1,17 @@
<template> <template>
<div class="flex flex-col overflow-hidden w-full"> <div class="flex flex-col overflow-hidden w-full">
<PageHeader :title="t`Data Import`"> <PageHeader :title="t`Data Import`">
<template #actions>
<DropdownWithActions <DropdownWithActions
class="ml-2"
:actions="actions" :actions="actions"
v-if="(canCancel || importType) && !complete" v-if="(canCancel || importType) && !complete"
/> />
<Button <Button
v-if="importType && !complete" v-if="importType && !complete"
type="primary" type="primary"
class="text-sm ml-2" class="text-sm"
@click="handlePrimaryClick" @click="handlePrimaryClick"
>{{ primaryLabel }}</Button >{{ primaryLabel }}</Button
> >
</template>
</PageHeader> </PageHeader>
<div <div
class="flex px-8 mt-2 text-base w-full flex-col gap-8" class="flex px-8 mt-2 text-base w-full flex-col gap-8"
@ -573,7 +570,10 @@ export default {
this.clear(); this.clear();
} }
this.importType = importType; 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) { setLoadingStatus(isMakingEntries, entriesMade, totalEntries) {
this.isMakingEntries = isMakingEntries; this.isMakingEntries = isMakingEntries;

View File

@ -1,42 +1,50 @@
<template> <template>
<div class="flex flex-col" v-if="doc"> <div class="flex flex-col" v-if="doc">
<!-- Page Header (Title, Buttons, etc) -->
<PageHeader :backLink="true"> <PageHeader :backLink="true">
<template #actions>
<StatusBadge :status="status" /> <StatusBadge :status="status" />
<Button <Button
v-if="doc.submitted" v-if="doc?.submitted"
class="text-gray-900 text-xs ml-2" class="text-gray-900 text-xs"
:icon="true" :icon="true"
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)" @click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
> >
Print {{ t`Print` }}
</Button> </Button>
<DropdownWithActions class="ml-2" :actions="actions" /> <DropdownWithActions :actions="actions" />
<Button <Button
v-if="showSave" v-if="showSave"
type="primary" type="primary"
class="text-white text-xs ml-2" class="text-white text-xs"
@click="onSaveClick" @click="onSaveClick"
> >
{{ t`Save` }} {{ t`Save` }}
</Button> </Button>
<Button <Button
v-if="!doc.dirty && !doc.notInserted && !doc.submitted" v-if="!doc.dirty && !doc.notInserted && !doc?.submitted"
type="primary" type="primary"
class="text-white text-xs ml-2" class="text-white text-xs"
@click="onSubmitClick" @click="onSubmitClick"
>{{ t`Submit` }}</Button >{{ t`Submit` }}</Button
> >
</template>
</PageHeader> </PageHeader>
<!-- Invoice Form -->
<div class="flex justify-center flex-1 mb-8 mt-2" v-if="doc"> <div class="flex justify-center flex-1 mb-8 mt-2" v-if="doc">
<div <div
class="border rounded-lg shadow h-full flex flex-col justify-between" class="
style="width: 600px" border
rounded-lg
shadow
h-full
flex flex-col
justify-between
w-600
"
> >
<div> <div>
<div class="px-6 pt-6" v-if="printSettings"> <!-- Print Settings Info (Logo, Address, Etc) -->
<div class="flex text-sm text-gray-900 border-b pb-4"> <div class="flex text-sm text-gray-900 p-6" v-if="printSettings">
<div class="w-1/3"> <div class="w-1/3">
<div v-if="printSettings.displayLogo"> <div v-if="printSettings.displayLogo">
<img <img
@ -56,8 +64,10 @@
<div v-if="address">{{ address.addressDisplay }}</div> <div v-if="address">{{ address.addressDisplay }}</div>
</div> </div>
</div> </div>
</div> <hr />
<div class="mt-8 px-6">
<!-- Invoice Form Data Entry -->
<div class="m-6 flex flex-col gap-2">
<h1 class="text-2xl font-semibold"> <h1 class="text-2xl font-semibold">
{{ {{
doc.notInserted doc.notInserted
@ -67,81 +77,95 @@
: doc.name : doc.name
}} }}
</h1> </h1>
<div class="flex justify-between mt-2">
<div class="w-1/3"> <!-- First Row of Fields -->
<div class="flex flex-row justify-between gap-2">
<FormControl <FormControl
class="bg-gray-100 rounded text-base" class="bg-gray-100 rounded text-base w-1/3"
input-class="p-2 text-lg font-semibold bg-transparent" input-class="text-lg font-semibold bg-transparent"
:df="getField('party')" :df="getField('party')"
:value="doc.party" :value="doc.party"
:placeholder="getField('party').label" :placeholder="getField('party').label"
@change="(value) => doc.set('party', value)" @change="(value) => doc.set('party', value)"
@new-doc="(party) => doc.set('party', party.name)" @new-doc="(party) => doc.set('party', party.name)"
:read-only="doc.submitted" :read-only="doc?.submitted"
/> />
<div class="w-1/3" />
<FormControl <FormControl
class="mt-2 text-base bg-gray-100 rounded" class="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>
<div class="w-1/3">
<FormControl
input-class="bg-gray-100 px-3 py-2 text-base text-right" input-class="bg-gray-100 px-3 py-2 text-base text-right"
:df="getField('date')" :df="getField('date')"
:value="doc.date" :value="doc.date"
:placeholder="'Date'" :placeholder="'Date'"
@change="(value) => doc.set('date', value)" @change="(value) => doc.set('date', value)"
:read-only="doc.submitted" :read-only="doc?.submitted"
/> />
</div>
<!-- Second Row of Fields -->
<div class="flex flex-row justify-between gap-2">
<FormControl <FormControl
class="mt-2 text-base bg-gray-100 rounded" 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" input-class="bg-transparent px-3 py-2 text-base text-right"
:df="getField('numberSeries')" :df="getField('numberSeries')"
:value="doc.numberSeries" :value="doc.numberSeries"
@change="(value) => doc.set('numberSeries', value)" @change="(value) => doc.set('numberSeries', value)"
:read-only="!doc.notInserted || doc.submitted" :read-only="!doc.notInserted || doc?.submitted"
/> />
</div> </div>
</div> </div>
</div> <hr />
<div class="px-6 text-base">
<FormControl <!-- Invoice Items Table -->
<Table
class="px-6 text-base mt-4"
:df="getField('items')" :df="getField('items')"
:value="doc.items" :value="doc.items"
:showHeader="true" :showHeader="true"
:max-rows-before-overflow="4" :max-rows-before-overflow="4"
@change="(value) => doc.set('items', value)" @change="(value) => doc.set('items', value)"
:read-only="doc.submitted" :read-only="doc?.submitted"
/> />
</div> </div>
</div>
<div <!-- Invoice Form Footer -->
class="px-6 mb-6 flex justify-between text-base"
v-if="doc.items?.length ?? 0" <div v-if="doc.items?.length ?? 0">
> <hr />
<div class="flex-1 mr-10"> <div class="flex justify-between text-base m-6 gap-12">
<!-- Form Terms-->
<FormControl <FormControl
v-if="!doc.submitted || doc.terms" class="w-1/2 self-end"
v-if="!doc?.submitted || doc.terms"
:df="getField('terms')" :df="getField('terms')"
:value="doc.terms" :value="doc.terms"
:show-label="true"
input-class="bg-gray-100" input-class="bg-gray-100"
@change="(value) => doc.set('terms', value)" @change="(value) => doc.set('terms', value)"
:read-only="doc.submitted" :read-only="doc?.submitted"
/> />
</div>
<div class="w-64"> <!-- Totals -->
<div class="flex pl-2 justify-between py-3 border-b"> <div class="w-1/2 gap-2 flex flex-col self-end">
<!-- Subtotal -->
<div class="flex justify-between">
<div>{{ t`Subtotal` }}</div> <div>{{ t`Subtotal` }}</div>
<div>{{ formattedValue('netTotal') }}</div> <div>{{ formattedValue('netTotal') }}</div>
</div> </div>
<hr />
<!-- Taxes -->
<div <div
class="flex pl-2 justify-between py-3" class="flex justify-between"
v-for="tax in doc.taxes" v-for="tax in doc.taxes"
:key="tax.name" :key="tax.name"
> >
@ -155,13 +179,13 @@
}} }}
</div> </div>
</div> </div>
<hr v-if="doc.taxes?.length" />
<!-- Grand Total -->
<div <div
class=" class="
flex flex
pl-2
justify-between justify-between
py-3
border-t
text-green-600 text-green-600
font-semibold font-semibold
text-base text-base
@ -170,14 +194,14 @@
<div>{{ t`Grand Total` }}</div> <div>{{ t`Grand Total` }}</div>
<div>{{ formattedValue('grandTotal') }}</div> <div>{{ formattedValue('grandTotal') }}</div>
</div> </div>
<!-- Outstanding Amount -->
<hr v-if="doc.outstandingAmount > 0" />
<div <div
v-if="doc.outstandingAmount > 0" v-if="doc.outstandingAmount > 0"
class=" class="
flex flex
pl-2
justify-between justify-between
py-3
border-t
text-red-600 text-red-600
font-semibold font-semibold
text-base text-base
@ -191,6 +215,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
import { computed } from '@vue/reactivity'; import { computed } from '@vue/reactivity';
@ -198,15 +223,16 @@ import { getInvoiceStatus } from 'models/helpers';
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
import Button from 'src/components/Button.vue'; import Button from 'src/components/Button.vue';
import FormControl from 'src/components/Controls/FormControl.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 DropdownWithActions from 'src/components/DropdownWithActions.vue';
import PageHeader from 'src/components/PageHeader.vue'; import PageHeader from 'src/components/PageHeader.vue';
import StatusBadge from 'src/components/StatusBadge.vue'; import StatusBadge from 'src/components/StatusBadge.vue';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
import { import {
getActionsForDocument, getActionsForDocument,
openSettings, openSettings,
routeTo, routeTo,
showMessageDialog, showMessageDialog
} from 'src/utils/ui'; } from 'src/utils/ui';
import { handleErrorWithDialog } from '../errorHandling'; import { handleErrorWithDialog } from '../errorHandling';
@ -219,7 +245,8 @@ export default {
Button, Button,
FormControl, FormControl,
DropdownWithActions, DropdownWithActions,
}, Table
},
provide() { provide() {
return { return {
schemaName: this.schemaName, schemaName: this.schemaName,

View File

@ -2,13 +2,13 @@
<div class="flex flex-col"> <div class="flex flex-col">
<!-- Page Header (Title, Buttons, etc) --> <!-- Page Header (Title, Buttons, etc) -->
<PageHeader :backLink="true"> <PageHeader :backLink="true">
<template #actions v-if="doc"> <template v-if="doc">
<StatusBadge :status="status" /> <StatusBadge :status="status" />
<DropdownWithActions class="ml-2" :actions="actions" /> <DropdownWithActions :actions="actions" />
<Button <Button
v-if="doc.notInserted || doc.dirty" v-if="doc.notInserted || doc.dirty"
type="primary" type="primary"
class="text-white text-xs ml-2" class="text-white text-xs"
@click="sync" @click="sync"
> >
{{ t`Save` }} {{ t`Save` }}
@ -16,7 +16,7 @@
<Button <Button
v-else-if="!doc.dirty && !doc.notInserted && !doc.submitted" v-else-if="!doc.dirty && !doc.notInserted && !doc.submitted"
type="primary" type="primary"
class="text-white text-xs ml-2" class="text-white text-xs"
@click="submit" @click="submit"
> >
{{ t`Submit` }} {{ t`Submit` }}
@ -27,19 +27,26 @@
<!-- Journal Entry Form --> <!-- Journal Entry Form -->
<div v-if="doc" class="flex justify-center flex-1 mb-8 mt-2"> <div v-if="doc" class="flex justify-center flex-1 mb-8 mt-2">
<div <div
class="border rounded-lg shadow h-full flex flex-col justify-between" class="
style="width: 600px" border
rounded-lg
shadow
h-full
flex flex-col
justify-between
w-600
"
> >
<div> <div>
<div class="mt-8 px-6"> <div class="m-6 flex flex-col gap-2">
<h1 class="text-2xl font-semibold"> <h1 class="text-2xl font-semibold">
{{ doc.notInserted ? t`New Journal Entry` : doc.name }} {{ doc.notInserted ? t`New Journal Entry` : doc.name }}
</h1> </h1>
<!-- First Column of Fields --> <!-- First Column of Fields -->
<div class="flex justify-between mt-2 gap-2"> <div class="flex flex-row justify-between gap-2">
<div class="w-1/3">
<FormControl <FormControl
class="w-1/3"
:df="getField('entryType')" :df="getField('entryType')"
:value="doc.entryType" :value="doc.entryType"
placeholder="Entry Type" placeholder="Entry Type"
@ -49,20 +56,7 @@
:class="doc.submitted && 'pointer-events-none'" :class="doc.submitted && 'pointer-events-none'"
/> />
<FormControl <FormControl
class="mt-2" 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>
<!-- Second Column of Fields -->
<div class="w-1/3">
<FormControl
:df="getField('referenceNumber')" :df="getField('referenceNumber')"
:value="doc.referenceNumber" :value="doc.referenceNumber"
:placeholder="'Reference Number'" :placeholder="'Reference Number'"
@ -72,7 +66,31 @@
:class="doc.submitted && 'pointer-events-none'" :class="doc.submitted && 'pointer-events-none'"
/> />
<FormControl <FormControl
class="mt-2" :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 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')" :df="getField('referenceDate')"
:value="doc.referenceDate" :value="doc.referenceDate"
:placeholder="'Reference Date'" :placeholder="'Reference Date'"
@ -82,57 +100,46 @@
:class="doc.submitted && 'pointer-events-none'" :class="doc.submitted && 'pointer-events-none'"
/> />
</div> </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>
</div>
</div> </div>
<hr />
<!-- Account Entries Table --> <!-- Account Entries Table -->
<FormControl <Table
class="mt-2 px-6 text-base" class="mt-2 px-6 text-base"
:df="getField('accounts')" :df="getField('accounts')"
:value="doc.accounts" :value="doc.accounts"
:showHeader="true" :showHeader="true"
:max-rows-before-overflow="4" :max-rows-before-overflow="6"
@change="(value) => doc.set('accounts', value)"
:read-only="doc.submitted" :read-only="doc.submitted"
/> />
</div> </div>
<!-- Footer --> <!-- Footer -->
<div class="px-6 mb-6"> <div v-if="doc.accounts?.length ?? 0">
<div <hr />
class="grid items-center border-t pt-3 pr-2" <div class="flex justify-between text-base m-6 gap-12">
style="grid-template-columns: 1.3fr 1fr 1fr"
>
<!-- User Remark --> <!-- User Remark -->
<div class="text-sm">
<FormControl <FormControl
class="w-1/2 self-end"
input-class="bg-gray-100"
:df="getField('userRemark')" :df="getField('userRemark')"
:value="doc.userRemark" :value="doc.userRemark"
@change="(value) => doc.set('userRemark', value)" @change="(value) => doc.set('userRemark', value)"
:class="doc.submitted && 'pointer-events-none'" :class="doc.submitted && 'pointer-events-none'"
:read-only="doc.submitted" :read-only="doc.submitted"
/> />
</div>
<!-- Debit and Credit --> <!-- Debit and Credit -->
<div class="text-right font-semibold text-green-600 px-3"> <div class="w-1/2 gap-2 flex flex-col self-end font-semibold">
{{ totalDebit }} <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 class="text-right font-semibold text-red-600 px-3">
{{ totalCredit }}
</div> </div>
</div> </div>
</div> </div>
@ -145,6 +152,7 @@ import { computed } from '@vue/reactivity';
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
import Button from 'src/components/Button'; import Button from 'src/components/Button';
import FormControl from 'src/components/Controls/FormControl.vue'; import FormControl from 'src/components/Controls/FormControl.vue';
import Table from 'src/components/Controls/Table.vue';
import DropdownWithActions from 'src/components/DropdownWithActions'; import DropdownWithActions from 'src/components/DropdownWithActions';
import PageHeader from 'src/components/PageHeader'; import PageHeader from 'src/components/PageHeader';
import StatusBadge from 'src/components/StatusBadge'; import StatusBadge from 'src/components/StatusBadge';
@ -165,7 +173,8 @@ export default {
DropdownWithActions, DropdownWithActions,
StatusBadge, StatusBadge,
FormControl, FormControl,
}, Table
},
provide() { provide() {
return { return {
schemaName: this.schemaName, schemaName: this.schemaName,

View File

@ -1,16 +1,14 @@
<template> <template>
<div class="flex flex-col"> <div class="flex flex-col">
<PageHeader :title="title"> <PageHeader :title="title">
<template #actions>
<FilterDropdown <FilterDropdown
ref="filterDropdown" ref="filterDropdown"
@change="applyFilter" @change="applyFilter"
:fields="fields" :fields="fields"
/> />
<Button class="ml-2" :icon="true" type="primary" @click="makeNewDoc"> <Button :icon="true" type="primary" @click="makeNewDoc">
<feather-icon name="plus" class="w-4 h-4 text-white" /> <feather-icon name="plus" class="w-4 h-4 text-white" />
</Button> </Button>
</template>
</PageHeader> </PageHeader>
<List <List
ref="list" ref="list"

View File

@ -2,17 +2,15 @@
<div class="flex"> <div class="flex">
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<PageHeader :backLink="true" class="bg-white z-10"> <PageHeader :backLink="true" class="bg-white z-10">
<template #actions>
<Button <Button
class="text-gray-900 text-xs ml-2" class="text-gray-900 text-xs"
@click="showCustomiser = !showCustomiser" @click="showCustomiser = !showCustomiser"
> >
{{ t`Customise` }} {{ t`Customise` }}
</Button> </Button>
<Button class="text-gray-900 text-xs ml-2" @click="makePDF"> <Button class="text-gray-900 text-xs" @click="makePDF">
{{ t`Save as PDF` }} {{ t`Save as PDF` }}
</Button> </Button>
</template>
</PageHeader> </PageHeader>
<div <div
v-if="doc && printSettings" v-if="doc && printSettings"

View File

@ -197,7 +197,7 @@ export default {
// set default values // set default values
if (this.values) { if (this.values) {
this.doc.set(this.values); this.doc?.set(this.values);
} }
// set title size // set title size
@ -205,7 +205,7 @@ export default {
}, },
setTitleField() { setTitleField() {
const { fieldname, readOnly } = this.titleField; const { fieldname, readOnly } = this.titleField;
if (!this.doc?.notInserted || !this.doc[fieldname]) { if (!this.doc?.notInserted || !this?.doc[fieldname]) {
return; return;
} }

View File

@ -1,18 +1,16 @@
<template> <template>
<div class="flex flex-col max-w-full"> <div class="flex flex-col max-w-full">
<PageHeader :title="report.title"> <PageHeader :title="report.title">
<template #actions>
<DropdownWithActions <DropdownWithActions
v-for="group of actionGroups" v-for="group of actionGroups"
:key="group.label" :key="group.label"
:type="group.type" :type="group.type"
:actions="group.actions" :actions="group.actions"
class="ml-2 text-xs" class="text-xs"
> >
{{ group.label }} {{ group.label }}
</DropdownWithActions> </DropdownWithActions>
<DropdownWithActions class="ml-2" :actions="actions" /> <DropdownWithActions :actions="actions" />
</template>
</PageHeader> </PageHeader>
<div class="flex px-8 mt-2 text-base" v-if="report.filterFields"> <div class="flex px-8 mt-2 text-base" v-if="report.filterFields">
<div <div
@ -142,7 +140,7 @@ import PageHeader from 'src/components/PageHeader';
import Row from 'src/components/Row'; import Row from 'src/components/Row';
import WithScroll from 'src/components/WithScroll'; import WithScroll from 'src/components/WithScroll';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
import { h, markRaw } from 'vue'; import { h, markRaw, nextTick } from 'vue';
export default { export default {
name: 'Report', name: 'Report',
@ -185,7 +183,7 @@ export default {
}, },
methods: { methods: {
onBodyScroll({ scrollLeft }) { onBodyScroll({ scrollLeft }) {
this.$nextTick(() => { nextTick(() => {
this.$refs.header.scrollLeft = scrollLeft; this.$refs.header.scrollLeft = scrollLeft;
}); });
}, },