2
0
mirror of https://github.com/frappe/books.git synced 2024-11-09 23:30:56 +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:
Faris Ansari 2019-12-03 15:53:54 +05:30
parent 82bc0143e1
commit f48a15d97d
9 changed files with 156 additions and 46 deletions

View File

@ -1,3 +1,5 @@
import { partyWithAvatar } from '@/utils';
let title = 'General Ledger';
const viewConfig = {
@ -137,7 +139,10 @@ const viewConfig = {
{
label: 'Party',
fieldtype: 'Link',
fieldname: 'party'
fieldname: 'party',
component(cellValue) {
return partyWithAvatar(cellValue);
}
},
{
label: 'Description',
@ -148,4 +153,4 @@ const viewConfig = {
}
};
module.exports = viewConfig;
export default viewConfig;

View File

@ -1,5 +1,5 @@
module.exports = {
'general-ledger': require('./GeneralLedger/viewConfig'),
'general-ledger': require('./GeneralLedger/viewConfig').default,
'sales-register': require('./SalesRegister/viewConfig'),
'purchase-register': require('./PurchaseRegister/viewConfig'),
'balance-sheet': require('./BalanceSheet/viewConfig'),

33
src/components/Avatar.vue Normal file
View 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>

View File

@ -1,5 +1,5 @@
<template>
<div class="grid border-b" :style="style">
<div class="inline-grid border-b" :style="style">
<slot></slot>
</div>
</template>

View 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>

View File

@ -26,19 +26,7 @@
:key="doc.name"
>
<div class="w-7 py-4 mr-3" v-if="hasImage">
<div class="w-7 h-7 rounded-full overflow-hidden">
<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>
<Avatar :imageURL="doc.image" :label="doc.name" />
</div>
<Row
gap="1rem"
@ -67,6 +55,7 @@ import frappe from 'frappejs';
import Row from '@/components/Row';
import ListCell from './ListCell';
import Button from '@/components/Button';
import Avatar from '@/components/Avatar';
export default {
name: 'List',
@ -74,7 +63,8 @@ export default {
components: {
Row,
ListCell,
Button
Button,
Avatar
},
watch: {
listConfig(oldValue, newValue) {

View File

@ -18,43 +18,51 @@
</div>
</div>
<div class="px-8 mt-4">
<div class="overflow-auto" :style="{ height: 'calc(100vh - 8rem)' }">
<div>
<div ref="header" class="overflow-hidden">
<Row
:columnCount="columns.length"
gap="1rem"
column-width="minmax(200px, 1fr)"
gap="2rem"
:column-width="columnWidth"
>
<div
class="text-gray-600 text-base truncate py-4"
:class="{
'text-right': ['Int', 'Float', 'Currency'].includes(
column.fieldtype
)
}"
v-for="column in columns"
:key="column.label"
>
{{ column.label }}
</div>
</Row>
<div class="flex-1">
</div>
<WithScroll @scroll="onBodyScroll">
<div class="flex-1 overflow-auto" style="height: calc(100vh - 12rem)">
<Row
v-for="(row, i) in rows"
:columnCount="columns.length"
gap="1rem"
gap="2rem"
:key="i"
column-width="minmax(200px, 1fr)"
:column-width="columnWidth"
>
<div
class="text-gray-900 text-base truncate py-4"
:class="{
'text-right': ['Int', 'Float', 'Currency'].includes(
column.fieldtype
)
}"
v-for="column in columns"
:key="column.label"
>
<component
v-if="typeof row[column.fieldname] === 'object'"
:is="row[column.fieldname]"
/>
<template v-else>
{{ frappe.format(row[column.fieldname], column) }}
</template>
<component :is="cellComponent(row[column.fieldname], column)" />
</div>
</Row>
</div>
</WithScroll>
</div>
</div>
</div>
@ -65,6 +73,7 @@ import PageHeader from '@/components/PageHeader';
import Button from '@/components/Button';
import SearchBar from '@/components/SearchBar';
import Row from '@/components/Row';
import WithScroll from '@/components/WithScroll';
import FormControl from '@/components/Controls/FormControl';
import reportViewConfig from '@/../reports/view';
import throttle from 'lodash/throttle';
@ -77,7 +86,8 @@ export default {
Button,
SearchBar,
Row,
FormControl
FormControl,
WithScroll
},
provide() {
return {
@ -102,6 +112,9 @@ export default {
await this.fetchReportData();
},
methods: {
onBodyScroll({ scrollLeft }) {
this.$refs.header.scrollLeft = scrollLeft;
},
async fetchReportData() {
let data = await frappe.call({
method: this.report.method,
@ -147,11 +160,32 @@ export default {
if (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: {
report() {
return reportViewConfig[this.reportName];
},
columnWidth() {
return 'minmax(7rem, 1fr)';
}
}
};

View File

@ -31,6 +31,10 @@ html {
display: grid;
}
.inline-grid {
display: inline-grid;
}
.frappe-chart .chart-legend {
display: none;
}

View File

@ -1,4 +1,5 @@
import frappe from 'frappejs';
import Avatar from '@/components/Avatar';
import { _ } from 'frappejs';
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>
`
};
}