mirror of
https://github.com/frappe/books.git
synced 2024-11-10 07:40:55 +00:00
fix: Report
- Use inline-grid to make Row expand its parent - Custom component for columns - Avatar component - partyWithAvatar component in GeneralLedger - WithScroll renderless component to detect scroll - Fixed header with horizontal and vertical scroll in Report
This commit is contained in:
parent
82bc0143e1
commit
f48a15d97d
@ -1,3 +1,5 @@
|
|||||||
|
import { partyWithAvatar } from '@/utils';
|
||||||
|
|
||||||
let title = 'General Ledger';
|
let title = 'General Ledger';
|
||||||
|
|
||||||
const viewConfig = {
|
const viewConfig = {
|
||||||
@ -137,7 +139,10 @@ const viewConfig = {
|
|||||||
{
|
{
|
||||||
label: 'Party',
|
label: 'Party',
|
||||||
fieldtype: 'Link',
|
fieldtype: 'Link',
|
||||||
fieldname: 'party'
|
fieldname: 'party',
|
||||||
|
component(cellValue) {
|
||||||
|
return partyWithAvatar(cellValue);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Description',
|
label: 'Description',
|
||||||
@ -148,4 +153,4 @@ const viewConfig = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = viewConfig;
|
export default viewConfig;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
'general-ledger': require('./GeneralLedger/viewConfig'),
|
'general-ledger': require('./GeneralLedger/viewConfig').default,
|
||||||
'sales-register': require('./SalesRegister/viewConfig'),
|
'sales-register': require('./SalesRegister/viewConfig'),
|
||||||
'purchase-register': require('./PurchaseRegister/viewConfig'),
|
'purchase-register': require('./PurchaseRegister/viewConfig'),
|
||||||
'balance-sheet': require('./BalanceSheet/viewConfig'),
|
'balance-sheet': require('./BalanceSheet/viewConfig'),
|
||||||
|
33
src/components/Avatar.vue
Normal file
33
src/components/Avatar.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<div class="rounded-full overflow-hidden" :class="sizeClasses">
|
||||||
|
<img v-if="imageURL" :src="imageURL" class="object-cover" :class="sizeClasses" />
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="bg-gray-500 flex h-full items-center justify-center text-white w-full text-base uppercase"
|
||||||
|
>
|
||||||
|
{{ label && label[0] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Avatar',
|
||||||
|
props: {
|
||||||
|
imageURL: String,
|
||||||
|
label: String,
|
||||||
|
size: {
|
||||||
|
default: 'md'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sizeClasses() {
|
||||||
|
return {
|
||||||
|
sm: 'w-5 h-5',
|
||||||
|
md: 'w-7 h-7',
|
||||||
|
lg: 'w-9 h-9'
|
||||||
|
}[this.size];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="grid border-b" :style="style">
|
<div class="inline-grid border-b" :style="style">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
19
src/components/WithScroll.vue
Normal file
19
src/components/WithScroll.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'WithScroll',
|
||||||
|
props: [],
|
||||||
|
mounted() {
|
||||||
|
let handler = () => {
|
||||||
|
let { scrollLeft, scrollTop } = this.$el;
|
||||||
|
this.$emit('scroll', { scrollLeft, scrollTop });
|
||||||
|
};
|
||||||
|
this.$el.addEventListener('scroll', handler);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$el.removeEventListener('scroll', handler);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return this.$slots.default[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -26,19 +26,7 @@
|
|||||||
:key="doc.name"
|
:key="doc.name"
|
||||||
>
|
>
|
||||||
<div class="w-7 py-4 mr-3" v-if="hasImage">
|
<div class="w-7 py-4 mr-3" v-if="hasImage">
|
||||||
<div class="w-7 h-7 rounded-full overflow-hidden">
|
<Avatar :imageURL="doc.image" :label="doc.name" />
|
||||||
<img
|
|
||||||
v-if="doc.image"
|
|
||||||
:src="doc.image"
|
|
||||||
class="w-7 h-7 object-cover"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="bg-gray-500 flex h-full items-center justify-center text-white w-full text-base uppercase"
|
|
||||||
>
|
|
||||||
{{ doc.name[0] }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Row
|
<Row
|
||||||
gap="1rem"
|
gap="1rem"
|
||||||
@ -67,6 +55,7 @@ import frappe from 'frappejs';
|
|||||||
import Row from '@/components/Row';
|
import Row from '@/components/Row';
|
||||||
import ListCell from './ListCell';
|
import ListCell from './ListCell';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'List',
|
name: 'List',
|
||||||
@ -74,7 +63,8 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
Row,
|
Row,
|
||||||
ListCell,
|
ListCell,
|
||||||
Button
|
Button,
|
||||||
|
Avatar
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
listConfig(oldValue, newValue) {
|
listConfig(oldValue, newValue) {
|
||||||
|
@ -18,43 +18,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-8 mt-4">
|
<div class="px-8 mt-4">
|
||||||
<div class="overflow-auto" :style="{ height: 'calc(100vh - 8rem)' }">
|
<div>
|
||||||
|
<div ref="header" class="overflow-hidden">
|
||||||
<Row
|
<Row
|
||||||
:columnCount="columns.length"
|
:columnCount="columns.length"
|
||||||
gap="1rem"
|
gap="2rem"
|
||||||
column-width="minmax(200px, 1fr)"
|
:column-width="columnWidth"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="text-gray-600 text-base truncate py-4"
|
class="text-gray-600 text-base truncate py-4"
|
||||||
|
:class="{
|
||||||
|
'text-right': ['Int', 'Float', 'Currency'].includes(
|
||||||
|
column.fieldtype
|
||||||
|
)
|
||||||
|
}"
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
:key="column.label"
|
:key="column.label"
|
||||||
>
|
>
|
||||||
{{ column.label }}
|
{{ column.label }}
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
<div class="flex-1">
|
</div>
|
||||||
|
<WithScroll @scroll="onBodyScroll">
|
||||||
|
<div class="flex-1 overflow-auto" style="height: calc(100vh - 12rem)">
|
||||||
<Row
|
<Row
|
||||||
v-for="(row, i) in rows"
|
v-for="(row, i) in rows"
|
||||||
:columnCount="columns.length"
|
:columnCount="columns.length"
|
||||||
gap="1rem"
|
gap="2rem"
|
||||||
:key="i"
|
:key="i"
|
||||||
column-width="minmax(200px, 1fr)"
|
:column-width="columnWidth"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="text-gray-900 text-base truncate py-4"
|
class="text-gray-900 text-base truncate py-4"
|
||||||
|
:class="{
|
||||||
|
'text-right': ['Int', 'Float', 'Currency'].includes(
|
||||||
|
column.fieldtype
|
||||||
|
)
|
||||||
|
}"
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
:key="column.label"
|
:key="column.label"
|
||||||
>
|
>
|
||||||
<component
|
<component :is="cellComponent(row[column.fieldname], column)" />
|
||||||
v-if="typeof row[column.fieldname] === 'object'"
|
|
||||||
:is="row[column.fieldname]"
|
|
||||||
/>
|
|
||||||
<template v-else>
|
|
||||||
{{ frappe.format(row[column.fieldname], column) }}
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
</WithScroll>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -65,6 +73,7 @@ import PageHeader from '@/components/PageHeader';
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import SearchBar from '@/components/SearchBar';
|
import SearchBar from '@/components/SearchBar';
|
||||||
import Row from '@/components/Row';
|
import Row from '@/components/Row';
|
||||||
|
import WithScroll from '@/components/WithScroll';
|
||||||
import FormControl from '@/components/Controls/FormControl';
|
import FormControl from '@/components/Controls/FormControl';
|
||||||
import reportViewConfig from '@/../reports/view';
|
import reportViewConfig from '@/../reports/view';
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
@ -77,7 +86,8 @@ export default {
|
|||||||
Button,
|
Button,
|
||||||
SearchBar,
|
SearchBar,
|
||||||
Row,
|
Row,
|
||||||
FormControl
|
FormControl,
|
||||||
|
WithScroll
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
@ -102,6 +112,9 @@ export default {
|
|||||||
await this.fetchReportData();
|
await this.fetchReportData();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onBodyScroll({ scrollLeft }) {
|
||||||
|
this.$refs.header.scrollLeft = scrollLeft;
|
||||||
|
},
|
||||||
async fetchReportData() {
|
async fetchReportData() {
|
||||||
let data = await frappe.call({
|
let data = await frappe.call({
|
||||||
method: this.report.method,
|
method: this.report.method,
|
||||||
@ -147,11 +160,32 @@ export default {
|
|||||||
if (this.defaultFilters) {
|
if (this.defaultFilters) {
|
||||||
Object.assign(this.filters, this.defaultFilters);
|
Object.assign(this.filters, this.defaultFilters);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
cellComponent(cellValue, column) {
|
||||||
|
if (typeof cellValue === 'object') {
|
||||||
|
// cellValue has a component definition
|
||||||
|
return cellValue;
|
||||||
|
}
|
||||||
|
if (column.component) {
|
||||||
|
// column has a component definition
|
||||||
|
return column.component(cellValue, column);
|
||||||
|
}
|
||||||
|
// default cell component
|
||||||
|
let formattedValue = frappe.format(cellValue, column);
|
||||||
|
return {
|
||||||
|
render(h) {
|
||||||
|
return h('span', formattedValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
report() {
|
report() {
|
||||||
return reportViewConfig[this.reportName];
|
return reportViewConfig[this.reportName];
|
||||||
|
},
|
||||||
|
columnWidth() {
|
||||||
|
return 'minmax(7rem, 1fr)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -31,6 +31,10 @@ html {
|
|||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inline-grid {
|
||||||
|
display: inline-grid;
|
||||||
|
}
|
||||||
|
|
||||||
.frappe-chart .chart-legend {
|
.frappe-chart .chart-legend {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
25
src/utils.js
25
src/utils.js
@ -1,4 +1,5 @@
|
|||||||
import frappe from 'frappejs';
|
import frappe from 'frappejs';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
import { _ } from 'frappejs';
|
import { _ } from 'frappejs';
|
||||||
import { remote } from 'electron';
|
import { remote } from 'electron';
|
||||||
|
|
||||||
@ -99,3 +100,27 @@ export function deleteDocWithPrompt(doc) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function partyWithAvatar(party) {
|
||||||
|
return {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imageURL: null,
|
||||||
|
label: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Avatar
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.imageURL = await frappe.db.getValue('Party', party, 'image');
|
||||||
|
this.label = party;
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div class="flex items-center" v-if="label">
|
||||||
|
<Avatar class="flex-shrink-0" :imageURL="imageURL" :label="label" size="sm" />
|
||||||
|
<span class="ml-2 truncate">{{ label }}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user