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:
parent
82bc0143e1
commit
f48a15d97d
@ -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;
|
||||
|
@ -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
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>
|
||||
<div class="grid border-b" :style="style">
|
||||
<div class="inline-grid border-b" :style="style">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</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"
|
||||
>
|
||||
<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) {
|
||||
|
@ -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)';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -31,6 +31,10 @@ html {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.inline-grid {
|
||||
display: inline-grid;
|
||||
}
|
||||
|
||||
.frappe-chart .chart-legend {
|
||||
display: none;
|
||||
}
|
||||
|
25
src/utils.js
25
src/utils.js
@ -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>
|
||||
`
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user