mirror of
https://github.com/frappe/books.git
synced 2024-11-08 14:50:56 +00:00
Merge pull request #492 from frappe/minor-ui-touchups
fix: minor ui touchups
This commit is contained in:
commit
694268f843
@ -422,12 +422,21 @@ export default class DatabaseCore extends DatabaseBase {
|
||||
|
||||
this.#applyFiltersToBuilder(builder, filters);
|
||||
|
||||
if (options.orderBy) {
|
||||
builder.orderBy(options.orderBy, options.order);
|
||||
const { orderBy, groupBy, order } = options;
|
||||
if (Array.isArray(orderBy)) {
|
||||
builder.orderBy(orderBy.map((column) => ({ column, order })));
|
||||
}
|
||||
|
||||
if (options.groupBy) {
|
||||
builder.groupBy(options.groupBy);
|
||||
if (typeof orderBy === 'string') {
|
||||
builder.orderBy(orderBy, order);
|
||||
}
|
||||
|
||||
if (Array.isArray(groupBy)) {
|
||||
builder.groupBy(...groupBy);
|
||||
}
|
||||
|
||||
if (typeof groupBy === 'string') {
|
||||
builder.groupBy(groupBy);
|
||||
}
|
||||
|
||||
if (options.offset) {
|
||||
|
@ -5,8 +5,8 @@ import { DatabaseManager } from './manager';
|
||||
export interface GetQueryBuilderOptions {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
groupBy?: string;
|
||||
orderBy?: string;
|
||||
groupBy?: string | string[];
|
||||
orderBy?: string | string[];
|
||||
order?: 'desc' | 'asc';
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ const childTableColumnMap = {
|
||||
|
||||
const defaultNumberSeriesMap = {
|
||||
[ModelNameEnum.Payment]: 'PAY-',
|
||||
[ModelNameEnum.JournalEntry]: 'JE-',
|
||||
[ModelNameEnum.JournalEntry]: 'JV-',
|
||||
[ModelNameEnum.SalesInvoice]: 'SINV-',
|
||||
[ModelNameEnum.PurchaseInvoice]: 'PINV-',
|
||||
} as Record<ModelNameEnum, string>;
|
||||
|
@ -2,19 +2,16 @@ import { Fyo } from 'fyo';
|
||||
import { DocValue, DocValueMap } from 'fyo/core/types';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import {
|
||||
Action,
|
||||
CurrenciesMap,
|
||||
DefaultMap,
|
||||
FiltersMap,
|
||||
FormulaMap,
|
||||
HiddenMap,
|
||||
HiddenMap
|
||||
} from 'fyo/model/types';
|
||||
import { DEFAULT_CURRENCY } from 'fyo/utils/consts';
|
||||
import { ValidationError } from 'fyo/utils/errors';
|
||||
import {
|
||||
getExchangeRate,
|
||||
getInvoiceActions,
|
||||
getNumberSeries,
|
||||
getExchangeRate, getNumberSeries
|
||||
} from 'models/helpers';
|
||||
import { InventorySettings } from 'models/inventory/InventorySettings';
|
||||
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||
|
@ -230,7 +230,7 @@ export abstract class AccountReport extends LedgerReport {
|
||||
const toDate = dr.toDate.toMillis();
|
||||
const fromDate = dr.fromDate.toMillis();
|
||||
|
||||
if (fromDate < entryDate && entryDate <= toDate) {
|
||||
if (entryDate >= fromDate && entryDate < toDate) {
|
||||
return dr;
|
||||
}
|
||||
}
|
||||
@ -278,9 +278,9 @@ export abstract class AccountReport extends LedgerReport {
|
||||
let fromDate: string;
|
||||
|
||||
if (this.basedOn === 'Until Date') {
|
||||
toDate = this.toDate!;
|
||||
toDate = DateTime.fromISO(this.toDate!).plus({ days: 1 }).toISODate();
|
||||
const months = monthsMap[this.periodicity] * Math.max(this.count ?? 1, 1);
|
||||
fromDate = DateTime.fromISO(toDate).minus({ months }).toISODate();
|
||||
fromDate = DateTime.fromISO(this.toDate!).minus({ months }).toISODate();
|
||||
} else {
|
||||
const fy = await getFiscalEndpoints(
|
||||
this.toYear!,
|
||||
@ -299,7 +299,7 @@ export abstract class AccountReport extends LedgerReport {
|
||||
const { fromDate, toDate } = await this._getFromAndToDates();
|
||||
|
||||
const dateFilter: string[] = [];
|
||||
dateFilter.push('<=', toDate);
|
||||
dateFilter.push('<', toDate);
|
||||
dateFilter.push('>=', fromDate);
|
||||
|
||||
filters.date = dateFilter;
|
||||
@ -342,17 +342,17 @@ export abstract class AccountReport extends LedgerReport {
|
||||
let dateFilters = [
|
||||
{
|
||||
fieldtype: 'Int',
|
||||
fieldname: 'toYear',
|
||||
placeholder: t`To Year`,
|
||||
label: t`To Year`,
|
||||
fieldname: 'fromYear',
|
||||
placeholder: t`From Year`,
|
||||
label: t`From Year`,
|
||||
minvalue: 2000,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
fieldtype: 'Int',
|
||||
fieldname: 'fromYear',
|
||||
placeholder: t`From Year`,
|
||||
label: t`From Year`,
|
||||
fieldname: 'toYear',
|
||||
placeholder: t`To Year`,
|
||||
label: t`To Year`,
|
||||
minvalue: 2000,
|
||||
required: true,
|
||||
},
|
||||
@ -407,16 +407,18 @@ export abstract class AccountReport extends LedgerReport {
|
||||
|
||||
const dateColumns = this._dateRanges!.sort(
|
||||
(a, b) => b.toDate.toMillis() - a.toDate.toMillis()
|
||||
).map(
|
||||
(d) =>
|
||||
({
|
||||
label: this.fyo.format(d.toDate.toJSDate(), 'Date'),
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'toDate',
|
||||
align: 'right',
|
||||
width: ACC_BAL_WIDTH,
|
||||
} as ColumnField)
|
||||
);
|
||||
).map((d) => {
|
||||
const toDate = d.toDate.minus({ days: 1 });
|
||||
const label = this.fyo.format(toDate.toJSDate(), 'Date');
|
||||
|
||||
return {
|
||||
label,
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'toDate',
|
||||
align: 'right',
|
||||
width: ACC_BAL_WIDTH,
|
||||
} as ColumnField;
|
||||
});
|
||||
|
||||
return [columns, dateColumns].flat();
|
||||
}
|
||||
|
@ -299,7 +299,7 @@ export class GeneralLedger extends LedgerReport {
|
||||
},
|
||||
{
|
||||
fieldtype: 'DynamicLink',
|
||||
label: t`Ref Name`,
|
||||
label: t`Ref. Name`,
|
||||
references: 'referenceType',
|
||||
placeholder: t`Ref Name`,
|
||||
emptyMessage: t`Change Ref Type`,
|
||||
|
@ -95,7 +95,7 @@ export abstract class LedgerReport extends Report {
|
||||
{
|
||||
fields,
|
||||
filters,
|
||||
orderBy: 'date',
|
||||
orderBy: ['date', 'created'],
|
||||
order: this.ascending ? 'asc' : 'desc',
|
||||
}
|
||||
)) as RawLedgerEntry[];
|
||||
|
@ -26,7 +26,7 @@ export async function getRawStockLedgerEntries(fyo: Fyo) {
|
||||
|
||||
return (await fyo.db.getAllRaw(ModelNameEnum.StockLedgerEntry, {
|
||||
fields: fieldnames,
|
||||
orderBy: 'date',
|
||||
orderBy: ['date', 'created', 'name'],
|
||||
order: 'asc',
|
||||
})) as RawStockLedgerEntry[];
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ export default {
|
||||
return this.labelClass;
|
||||
}
|
||||
|
||||
return 'text-gray-600 text-sm';
|
||||
return 'text-gray-600 text-base';
|
||||
},
|
||||
checked() {
|
||||
return this.value;
|
||||
|
@ -1,28 +1,38 @@
|
||||
<template>
|
||||
<div
|
||||
class="
|
||||
fixed
|
||||
top-0
|
||||
left-0
|
||||
w-screen
|
||||
h-screen
|
||||
z-20
|
||||
flex
|
||||
justify-center
|
||||
items-center
|
||||
"
|
||||
style="background: rgba(0, 0, 0, 0.2); backdrop-filter: blur(4px)"
|
||||
@click="$emit('closemodal')"
|
||||
v-if="openModal"
|
||||
>
|
||||
<Transition>
|
||||
<div
|
||||
class="bg-white rounded-lg shadow-2xl w-form border overflow-hidden"
|
||||
v-bind="$attrs"
|
||||
@click.stop
|
||||
class="
|
||||
fixed
|
||||
top-0
|
||||
left-0
|
||||
w-screen
|
||||
h-screen
|
||||
z-20
|
||||
flex
|
||||
justify-center
|
||||
items-center
|
||||
"
|
||||
style="background: rgba(0, 0, 0, 0.2); backdrop-filter: blur(4px)"
|
||||
@click="$emit('closemodal')"
|
||||
v-if="openModal"
|
||||
>
|
||||
<slot></slot>
|
||||
<div
|
||||
class="
|
||||
bg-white
|
||||
rounded-lg
|
||||
shadow-2xl
|
||||
w-form
|
||||
border
|
||||
overflow-hidden
|
||||
inner
|
||||
"
|
||||
v-bind="$attrs"
|
||||
@click.stop
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@ -60,3 +70,28 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.v-enter-active,
|
||||
.v-leave-active {
|
||||
transition: all 100ms ease-out;
|
||||
}
|
||||
|
||||
.inner {
|
||||
transition: all 150ms ease-out;
|
||||
}
|
||||
|
||||
.v-enter-from,
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.v-enter-from .inner,
|
||||
.v-leave-to .inner {
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
|
||||
.v-enter-to .inner,
|
||||
.v-leave-from .inner {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
</style>
|
||||
|
@ -6,6 +6,13 @@
|
||||
platform !== 'Windows' ? 'window-drag' : '',
|
||||
]"
|
||||
>
|
||||
<Transition name="spacer">
|
||||
<div
|
||||
v-if="!sidebar && platform === 'Mac'"
|
||||
class="h-full"
|
||||
:class="sidebar ? '' : 'w-tl mr-4 border-r'"
|
||||
/>
|
||||
</Transition>
|
||||
<h1 class="text-xl font-semibold select-none" v-if="title">
|
||||
{{ title }}
|
||||
</h1>
|
||||
@ -18,10 +25,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { Transition } from 'vue';
|
||||
import BackLink from './BackLink.vue';
|
||||
import SearchBar from './SearchBar.vue';
|
||||
|
||||
export default {
|
||||
inject: ['sidebar'],
|
||||
props: {
|
||||
title: { type: String, default: '' },
|
||||
backLink: { type: Boolean, default: true },
|
||||
@ -29,7 +38,7 @@ export default {
|
||||
border: { type: Boolean, default: true },
|
||||
searchborder: { type: Boolean, default: true },
|
||||
},
|
||||
components: { SearchBar, BackLink },
|
||||
components: { SearchBar, BackLink, Transition },
|
||||
computed: {
|
||||
showBorder() {
|
||||
return !!this.$slots.default && this.searchborder;
|
||||
@ -37,3 +46,29 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.w-tl {
|
||||
width: var(--w-trafficlights);
|
||||
}
|
||||
|
||||
.spacer-enter-from,
|
||||
.spacer-leave-to {
|
||||
opacity: 0;
|
||||
width: 0px;
|
||||
margin-right: 0px;
|
||||
border-right-width: 0px;
|
||||
}
|
||||
|
||||
.spacer-enter-to,
|
||||
.spacer-leave-from {
|
||||
opacity: 1;
|
||||
width: var(--w-trafficlights);
|
||||
margin-right: 1rem;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.spacer-enter-active,
|
||||
.spacer-leave-active {
|
||||
transition: all 150ms ease-out;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="py-2 h-full flex justify-between flex-col bg-gray-25"
|
||||
class="py-2 h-full flex justify-between flex-col bg-gray-25 relative"
|
||||
:class="{
|
||||
'window-drag': platform !== 'Windows',
|
||||
}"
|
||||
@ -36,6 +36,7 @@
|
||||
@click="onGroupClick(group)"
|
||||
>
|
||||
<Icon
|
||||
class="flex-shrink-0"
|
||||
:name="group.icon"
|
||||
:size="group.iconSize || '18'"
|
||||
:height="group.iconHeight"
|
||||
@ -91,7 +92,7 @@
|
||||
"
|
||||
@click="openDocumentation"
|
||||
>
|
||||
<feather-icon name="help-circle" class="h-4 w-4" />
|
||||
<feather-icon name="help-circle" class="h-4 w-4 flex-shrink-0" />
|
||||
<p>
|
||||
{{ t`Help` }}
|
||||
</p>
|
||||
@ -107,7 +108,7 @@
|
||||
"
|
||||
@click="$emit('change-db-file')"
|
||||
>
|
||||
<feather-icon name="database" class="h-4 w-4" />
|
||||
<feather-icon name="database" class="h-4 w-4 flex-shrink-0" />
|
||||
<p>{{ t`Change DB` }}</p>
|
||||
</button>
|
||||
|
||||
@ -121,7 +122,7 @@
|
||||
"
|
||||
@click="() => reportIssue()"
|
||||
>
|
||||
<feather-icon name="flag" class="h-4 w-4" />
|
||||
<feather-icon name="flag" class="h-4 w-4 flex-shrink-0" />
|
||||
<p>
|
||||
{{ t`Report Issue` }}
|
||||
</p>
|
||||
@ -134,6 +135,23 @@
|
||||
dev mode
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Hide Sidebar Button -->
|
||||
<button
|
||||
class="
|
||||
absolute
|
||||
bottom-0
|
||||
right-0
|
||||
text-gray-600
|
||||
hover:bg-gray-100
|
||||
rounded
|
||||
p-1
|
||||
m-4
|
||||
"
|
||||
@click="$emit('toggle-sidebar')"
|
||||
>
|
||||
<feather-icon name="chevrons-left" class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@ -148,7 +166,7 @@ import Icon from './Icon.vue';
|
||||
|
||||
export default {
|
||||
components: [Button],
|
||||
emits: ['change-db-file'],
|
||||
emits: ['change-db-file', 'toggle-sidebar'],
|
||||
data() {
|
||||
return {
|
||||
companyName: '',
|
||||
|
@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<PageHeader :title="t`Chart of Accounts`" />
|
||||
<PageHeader :title="t`Chart of Accounts`">
|
||||
<Button v-if="!isAllExpanded" @click="expand">{{ t`Expand` }}</Button>
|
||||
<Button v-if="!isAllCollapsed" @click="collapse">{{
|
||||
t`Collapse`
|
||||
}}</Button>
|
||||
</PageHeader>
|
||||
|
||||
<!-- Chart of Accounts -->
|
||||
<div
|
||||
@ -141,20 +146,24 @@
|
||||
import { t } from 'fyo';
|
||||
import { isCredit } from 'models/helpers';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import PageHeader from 'src/components/PageHeader';
|
||||
import PageHeader from 'src/components/PageHeader.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { docsPathMap } from 'src/utils/misc';
|
||||
import { docsPath, openQuickEdit } from 'src/utils/ui';
|
||||
import { getMapFromList, removeAtIndex } from 'utils/index';
|
||||
import { nextTick } from 'vue';
|
||||
import Button from '../components/Button.vue';
|
||||
import { handleErrorWithDialog } from '../errorHandling';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
PageHeader,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isAllCollapsed: true,
|
||||
isAllExpanded: false,
|
||||
root: null,
|
||||
accounts: [],
|
||||
schemaName: 'Account',
|
||||
@ -187,6 +196,33 @@ export default {
|
||||
docsPath.value = '';
|
||||
},
|
||||
methods: {
|
||||
async expand() {
|
||||
await this.toggleAll(this.accounts, true);
|
||||
this.isAllCollapsed = false;
|
||||
this.isAllExpanded = true;
|
||||
},
|
||||
async collapse() {
|
||||
await this.toggleAll(this.accounts, false);
|
||||
this.isAllExpanded = false;
|
||||
this.isAllCollapsed = true;
|
||||
},
|
||||
async toggleAll(accounts, expand) {
|
||||
if (!Array.isArray(accounts)) {
|
||||
await this.toggle(accounts, expand);
|
||||
accounts = accounts.children ?? [];
|
||||
}
|
||||
|
||||
for (const account of accounts) {
|
||||
await this.toggleAll(account, expand);
|
||||
}
|
||||
},
|
||||
async toggle(account, expand) {
|
||||
if (account.expanded === expand || !account.isGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.toggleChildren(account);
|
||||
},
|
||||
getBalance(account) {
|
||||
const total = this.totals[account.name];
|
||||
if (!total) {
|
||||
@ -226,6 +262,14 @@ export default {
|
||||
shouldOpen = !(await this.toggleChildren(account));
|
||||
}
|
||||
|
||||
if (account.isGroup && account.expanded) {
|
||||
this.isAllCollapsed = false;
|
||||
}
|
||||
|
||||
if (account.isGroup && !account.expanded) {
|
||||
this.isAllExpanded = false;
|
||||
}
|
||||
|
||||
if (!shouldOpen) {
|
||||
return;
|
||||
}
|
||||
|
32
src/pages/Dashboard/BaseDashboardChart.vue
Normal file
32
src/pages/Dashboard/BaseDashboardChart.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
period: 'This Year',
|
||||
periodOptions: ['This Year', 'This Quarter', 'This Month'],
|
||||
};
|
||||
},
|
||||
props: {
|
||||
commonPeriod: String,
|
||||
},
|
||||
watch: {
|
||||
period: 'periodChange',
|
||||
commonPeriod(val) {
|
||||
if (!this.periodOptions.includes(val)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.period = val;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async periodChange() {
|
||||
this.$emit('period-change', this.period);
|
||||
await this.setData();
|
||||
},
|
||||
async setData() {},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -20,7 +20,7 @@
|
||||
<PeriodSelector
|
||||
:value="period"
|
||||
@change="(value) => (period = value)"
|
||||
:options="['This Year', 'This Quarter']"
|
||||
:options="periodOptions"
|
||||
v-if="hasData"
|
||||
/>
|
||||
<div v-else class="w-20 h-5 bg-gray-200 rounded" />
|
||||
@ -51,23 +51,22 @@ import { formatXLabels, getYMax } from 'src/utils/chart';
|
||||
import { uicolors } from 'src/utils/colors';
|
||||
import { getDatesAndPeriodList } from 'src/utils/misc';
|
||||
import { getMapFromList } from 'utils/';
|
||||
import PeriodSelector from './PeriodSelector';
|
||||
import DashboardChartBase from './BaseDashboardChart.vue';
|
||||
import PeriodSelector from './PeriodSelector.vue';
|
||||
|
||||
export default {
|
||||
name: 'Cashflow',
|
||||
extends: DashboardChartBase,
|
||||
components: {
|
||||
PeriodSelector,
|
||||
LineChart,
|
||||
},
|
||||
data: () => ({
|
||||
period: 'This Year',
|
||||
data: [],
|
||||
periodList: [],
|
||||
periodOptions: ['This Year', 'This Quarter'],
|
||||
hasData: false,
|
||||
}),
|
||||
watch: {
|
||||
period: 'setData',
|
||||
},
|
||||
async activated() {
|
||||
await this.setData();
|
||||
if (!this.hasData) {
|
||||
|
@ -1,19 +1,61 @@
|
||||
<template>
|
||||
<div class="overflow-hidden h-screen" style="width: var(--w-desk)">
|
||||
<PageHeader :title="t`Dashboard`" />
|
||||
<PageHeader :title="t`Dashboard`">
|
||||
<div
|
||||
class="
|
||||
border
|
||||
rounded
|
||||
bg-gray-50
|
||||
focus-within:bg-gray-100
|
||||
flex
|
||||
items-center
|
||||
"
|
||||
>
|
||||
<PeriodSelector
|
||||
class="px-3"
|
||||
:value="period"
|
||||
:options="['This Year', 'This Quarter', 'This Month']"
|
||||
@change="(value) => (period = value)"
|
||||
/>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div class="no-scrollbar overflow-auto h-full">
|
||||
<div
|
||||
style="min-width: var(--w-desk-fixed); min-height: var(--h-app)"
|
||||
class="overflow-auto"
|
||||
>
|
||||
<Cashflow class="p-4" />
|
||||
<Cashflow
|
||||
class="p-4"
|
||||
:common-period="period"
|
||||
@period-change="handlePeriodChange"
|
||||
/>
|
||||
<hr />
|
||||
<UnpaidInvoices />
|
||||
<div class="flex w-full">
|
||||
<UnpaidInvoices
|
||||
:schema-name="'SalesInvoice'"
|
||||
:common-period="period"
|
||||
@period-change="handlePeriodChange"
|
||||
class="border-r"
|
||||
/>
|
||||
<UnpaidInvoices
|
||||
:schema-name="'PurchaseInvoice'"
|
||||
:common-period="period"
|
||||
@period-change="handlePeriodChange"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="flex">
|
||||
<ProfitAndLoss class="w-full p-4 border-r" />
|
||||
<Expenses class="w-full p-4" />
|
||||
<ProfitAndLoss
|
||||
class="w-full p-4 border-r"
|
||||
:common-period="period"
|
||||
@period-change="handlePeriodChange"
|
||||
/>
|
||||
<Expenses
|
||||
class="w-full p-4"
|
||||
:common-period="period"
|
||||
@period-change="handlePeriodChange"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
@ -22,21 +64,26 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageHeader from 'src/components/PageHeader';
|
||||
import PageHeader from 'src/components/PageHeader.vue';
|
||||
import { docsPath } from 'src/utils/ui';
|
||||
import Cashflow from './Cashflow';
|
||||
import Expenses from './Expenses';
|
||||
import ProfitAndLoss from './ProfitAndLoss';
|
||||
import UnpaidInvoices from './UnpaidInvoices';
|
||||
import UnpaidInvoices from './UnpaidInvoices.vue';
|
||||
import Cashflow from './Cashflow.vue';
|
||||
import Expenses from './Expenses.vue';
|
||||
import PeriodSelector from './PeriodSelector.vue';
|
||||
import ProfitAndLoss from './ProfitAndLoss.vue';
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
PageHeader,
|
||||
UnpaidInvoices,
|
||||
Cashflow,
|
||||
ProfitAndLoss,
|
||||
Expenses,
|
||||
PeriodSelector,
|
||||
UnpaidInvoices,
|
||||
},
|
||||
data() {
|
||||
return { period: 'This Year' };
|
||||
},
|
||||
activated() {
|
||||
docsPath.value = 'analytics/dashboard';
|
||||
@ -44,5 +91,14 @@ export default {
|
||||
deactivated() {
|
||||
docsPath.value = '';
|
||||
},
|
||||
methods: {
|
||||
handlePeriodChange(period) {
|
||||
if (period === this.period) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.period = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -58,26 +58,22 @@ import { fyo } from 'src/initFyo';
|
||||
import { uicolors } from 'src/utils/colors';
|
||||
import { getDatesAndPeriodList } from 'src/utils/misc';
|
||||
import DonutChart from '../../components/Charts/DonutChart.vue';
|
||||
import PeriodSelector from './PeriodSelector';
|
||||
import SectionHeader from './SectionHeader';
|
||||
import DashboardChartBase from './BaseDashboardChart.vue';
|
||||
import PeriodSelector from './PeriodSelector.vue';
|
||||
import SectionHeader from './SectionHeader.vue';
|
||||
|
||||
export default {
|
||||
name: 'Expenses',
|
||||
extends: DashboardChartBase,
|
||||
components: {
|
||||
DonutChart,
|
||||
PeriodSelector,
|
||||
SectionHeader,
|
||||
},
|
||||
data: () => ({
|
||||
period: 'This Year',
|
||||
active: null,
|
||||
expenses: [],
|
||||
}),
|
||||
watch: {
|
||||
period() {
|
||||
this.setData();
|
||||
},
|
||||
},
|
||||
activated() {
|
||||
this.setData();
|
||||
},
|
||||
|
@ -13,7 +13,6 @@
|
||||
text-sm
|
||||
flex
|
||||
focus:outline-none
|
||||
text-gray-900
|
||||
hover:text-gray-800
|
||||
focus:text-gray-800
|
||||
items-center
|
||||
@ -22,6 +21,7 @@
|
||||
leading-relaxed
|
||||
cursor-pointer
|
||||
"
|
||||
:class="!value ? 'text-gray-600' : 'text-gray-900'"
|
||||
@click="toggleDropdown()"
|
||||
tabindex="0"
|
||||
@keydown.down="highlightItemDown"
|
||||
@ -37,7 +37,7 @@
|
||||
|
||||
<script>
|
||||
import { t } from 'fyo';
|
||||
import Dropdown from 'src/components/Dropdown';
|
||||
import Dropdown from 'src/components/Dropdown.vue';
|
||||
|
||||
export default {
|
||||
name: 'PeriodSelector',
|
||||
@ -54,14 +54,17 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.periodSelectorMap = {
|
||||
'': t`Set Period`,
|
||||
'This Year': t`This Year`,
|
||||
'This Quarter': t`This Quarter`,
|
||||
'This Month': t`This Month`,
|
||||
};
|
||||
|
||||
this.periodOptions = this.options.map((option) => {
|
||||
let label = this.periodSelectorMap[option] ?? option;
|
||||
|
||||
return {
|
||||
label: this.periodSelectorMap[option] ?? option,
|
||||
label,
|
||||
action: () => this.selectOption(option),
|
||||
};
|
||||
});
|
||||
|
@ -5,7 +5,7 @@
|
||||
<template #action>
|
||||
<PeriodSelector
|
||||
:value="period"
|
||||
:options="['This Year', 'This Quarter']"
|
||||
:options="periodOptions"
|
||||
@change="(value) => (period = value)"
|
||||
/>
|
||||
</template>
|
||||
@ -36,27 +36,26 @@ import { formatXLabels, getYMax, getYMin } from 'src/utils/chart';
|
||||
import { uicolors } from 'src/utils/colors';
|
||||
import { getDatesAndPeriodList } from 'src/utils/misc';
|
||||
import { getValueMapFromList } from 'utils';
|
||||
import DashboardChartBase from './BaseDashboardChart.vue';
|
||||
import PeriodSelector from './PeriodSelector';
|
||||
import SectionHeader from './SectionHeader';
|
||||
|
||||
export default {
|
||||
name: 'ProfitAndLoss',
|
||||
extends: DashboardChartBase,
|
||||
components: {
|
||||
PeriodSelector,
|
||||
SectionHeader,
|
||||
BarChart,
|
||||
},
|
||||
data: () => ({
|
||||
period: 'This Year',
|
||||
data: [],
|
||||
hasData: false,
|
||||
periodOptions: ['This Year', 'This Quarter'],
|
||||
}),
|
||||
activated() {
|
||||
this.setData();
|
||||
},
|
||||
watch: {
|
||||
period: 'setData',
|
||||
},
|
||||
computed: {
|
||||
chartData() {
|
||||
const points = [this.data.map((d) => d.balance)];
|
||||
|
@ -1,209 +1,197 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<div
|
||||
v-for="(invoice, i) in invoices"
|
||||
class="flex-col justify-between w-full p-4"
|
||||
:class="i === 0 ? 'border-r' : ''"
|
||||
:key="invoice.title"
|
||||
>
|
||||
<!-- Title and Period Selector -->
|
||||
<SectionHeader>
|
||||
<template #title>{{ invoice.title }}</template>
|
||||
<template #action>
|
||||
<PeriodSelector
|
||||
v-if="invoice.hasData"
|
||||
:value="$data[invoice.periodKey]"
|
||||
@change="(value) => ($data[invoice.periodKey] = value)"
|
||||
/>
|
||||
<Button
|
||||
v-else
|
||||
:icon="true"
|
||||
type="primary"
|
||||
@click="newInvoice(invoice)"
|
||||
>
|
||||
<feather-icon name="plus" class="w-4 h-4 text-white" />
|
||||
</Button>
|
||||
</template>
|
||||
</SectionHeader>
|
||||
<div class="flex-col justify-between w-full p-4">
|
||||
<!-- Title and Period Selector -->
|
||||
<SectionHeader>
|
||||
<template #title>{{ title }}</template>
|
||||
<template #action>
|
||||
<PeriodSelector
|
||||
v-if="hasData"
|
||||
:value="period"
|
||||
@change="(value) => (period = value)"
|
||||
/>
|
||||
<Button v-else :icon="true" type="primary" @click="newInvoice()">
|
||||
<feather-icon name="plus" class="w-4 h-4 text-white" />
|
||||
</Button>
|
||||
</template>
|
||||
</SectionHeader>
|
||||
|
||||
<!-- Widget Body -->
|
||||
<div class="mt-4">
|
||||
<!-- Paid & Unpaid Amounts -->
|
||||
<div class="flex justify-between">
|
||||
<!-- Paid -->
|
||||
<div
|
||||
class="text-sm font-medium"
|
||||
:class="{ 'bg-gray-200 text-gray-200 rounded': !invoice.count }"
|
||||
>
|
||||
{{ fyo.format(invoice.paid, 'Currency') }}
|
||||
<span :class="{ 'text-gray-900 font-normal': invoice.count }">{{
|
||||
t`Paid`
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<!-- Unpaid -->
|
||||
<div
|
||||
class="text-sm font-medium"
|
||||
:class="{ 'bg-gray-200 text-gray-200 rounded': !invoice.count }"
|
||||
>
|
||||
{{ fyo.format(invoice.unpaid, 'Currency') }}
|
||||
<span :class="{ 'text-gray-900 font-normal': invoice.count }">{{
|
||||
t`Unpaid`
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Widget Bar -->
|
||||
<!-- Widget Body -->
|
||||
<div class="mt-4">
|
||||
<!-- Paid & Unpaid Amounts -->
|
||||
<div class="flex justify-between">
|
||||
<!-- Paid -->
|
||||
<div
|
||||
class="mt-2 relative rounded overflow-hidden"
|
||||
@mouseenter="idx = i"
|
||||
@mouseleave="idx = -1"
|
||||
class="text-sm font-medium"
|
||||
:class="{ 'bg-gray-200 text-gray-200 rounded': !count }"
|
||||
>
|
||||
<div
|
||||
class="w-full h-4"
|
||||
:class="
|
||||
invoice.count && invoice.color == 'blue'
|
||||
? 'bg-blue-200'
|
||||
: invoice.hasData
|
||||
? 'bg-pink-200'
|
||||
: 'bg-gray-200'
|
||||
"
|
||||
></div>
|
||||
<div
|
||||
class="absolute inset-0 h-4"
|
||||
:class="
|
||||
invoice.count && invoice.color == 'blue'
|
||||
? 'bg-blue-500'
|
||||
: invoice.hasData
|
||||
? 'bg-pink-500'
|
||||
: 'bg-gray-400'
|
||||
"
|
||||
:style="`width: ${invoice.barWidth}%`"
|
||||
></div>
|
||||
{{ fyo.format(paid, 'Currency') }}
|
||||
<span :class="{ 'text-gray-900 font-normal': count }">{{
|
||||
t`Paid`
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<!-- Unpaid -->
|
||||
<div
|
||||
class="text-sm font-medium"
|
||||
:class="{ 'bg-gray-200 text-gray-200 rounded': !count }"
|
||||
>
|
||||
{{ fyo.format(unpaid, 'Currency') }}
|
||||
<span :class="{ 'text-gray-900 font-normal': count }">{{
|
||||
t`Unpaid`
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Widget Bar -->
|
||||
<div
|
||||
class="mt-2 relative rounded overflow-hidden"
|
||||
@mouseenter="show = true"
|
||||
@mouseleave="show = false"
|
||||
>
|
||||
<div class="w-full h-4" :class="unpaidColor"></div>
|
||||
<div
|
||||
class="absolute inset-0 h-4"
|
||||
:class="paidColor"
|
||||
:style="`width: ${barWidth}%`"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<MouseFollower
|
||||
v-if="invoices[0].hasData || invoices[1].hasData"
|
||||
v-if="hasData"
|
||||
:offset="15"
|
||||
:show="idx >= 0"
|
||||
:show="show"
|
||||
placement="top"
|
||||
class="text-sm shadow-md px-2 py-1 bg-white text-gray-900 border-l-4"
|
||||
:style="{ borderColor: colors[idx] }"
|
||||
:style="{ borderColor: colors }"
|
||||
>
|
||||
<div class="flex justify-between gap-4">
|
||||
<p>{{ t`Paid` }}</p>
|
||||
<p class="font-semibold">{{ invoices[idx]?.paidCount ?? 0 }}</p>
|
||||
<p class="font-semibold">{{ paidCount ?? 0 }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="invoices[idx]?.unpaidCount > 0"
|
||||
class="flex justify-between gap-4"
|
||||
>
|
||||
<div v-if="unpaidCount > 0" class="flex justify-between gap-4">
|
||||
<p>{{ t`Unpaid` }}</p>
|
||||
<p class="font-semibold">{{ invoices[idx]?.unpaidCount ?? 0 }}</p>
|
||||
<p class="font-semibold">{{ unpaidCount ?? 0 }}</p>
|
||||
</div>
|
||||
</MouseFollower>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { t } from 'fyo';
|
||||
import { DateTime } from 'luxon';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import MouseFollower from 'src/components/MouseFollower.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { uicolors } from 'src/utils/colors';
|
||||
import { getDatesAndPeriodList } from 'src/utils/misc';
|
||||
import { PeriodKey } from 'src/utils/types';
|
||||
import { routeTo } from 'src/utils/ui';
|
||||
import { safeParseFloat } from 'utils/index';
|
||||
import { defineComponent } from 'vue';
|
||||
import BaseDashboardChart from './BaseDashboardChart.vue';
|
||||
import PeriodSelector from './PeriodSelector.vue';
|
||||
import SectionHeader from './SectionHeader.vue';
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: 'UnpaidInvoices',
|
||||
extends: BaseDashboardChart,
|
||||
components: {
|
||||
PeriodSelector,
|
||||
SectionHeader,
|
||||
Button,
|
||||
MouseFollower,
|
||||
},
|
||||
data: () => ({
|
||||
idx: -1,
|
||||
colors: [uicolors.blue['500'], uicolors.pink['500']],
|
||||
invoices: [
|
||||
{
|
||||
title: t`Sales Invoices`,
|
||||
schemaName: ModelNameEnum.SalesInvoice,
|
||||
total: 0,
|
||||
unpaid: 0,
|
||||
hasData: false,
|
||||
paid: 0,
|
||||
count: 0,
|
||||
unpaidCount: 0,
|
||||
paidCount: 0,
|
||||
color: 'blue',
|
||||
periodKey: 'salesInvoicePeriod',
|
||||
barWidth: 40,
|
||||
},
|
||||
{
|
||||
title: t`Purchase Invoices`,
|
||||
schemaName: ModelNameEnum.PurchaseInvoice,
|
||||
total: 0,
|
||||
unpaid: 0,
|
||||
hasData: false,
|
||||
paid: 0,
|
||||
count: 0,
|
||||
unpaidCount: 0,
|
||||
paidCount: 0,
|
||||
color: 'pink',
|
||||
periodKey: 'purchaseInvoicePeriod',
|
||||
barWidth: 60,
|
||||
},
|
||||
],
|
||||
salesInvoicePeriod: 'This Year',
|
||||
purchaseInvoicePeriod: 'This Year',
|
||||
}),
|
||||
watch: {
|
||||
salesInvoicePeriod: 'calculateInvoiceTotals',
|
||||
purchaseInvoicePeriod: 'calculateInvoiceTotals',
|
||||
props: {
|
||||
schemaName: { type: String, required: true },
|
||||
},
|
||||
computed: {
|
||||
title(): string {
|
||||
return fyo.schemaMap[this.schemaName]?.label ?? '';
|
||||
},
|
||||
color(): 'blue' | 'pink' {
|
||||
if (this.schemaName === ModelNameEnum.SalesInvoice) {
|
||||
return 'blue';
|
||||
}
|
||||
|
||||
return 'pink';
|
||||
},
|
||||
colors(): string {
|
||||
return uicolors[this.color]['500'];
|
||||
},
|
||||
paidColor(): string {
|
||||
if (!this.hasData) {
|
||||
return 'bg-gray-400';
|
||||
}
|
||||
|
||||
return `bg-${this.color}-500`;
|
||||
},
|
||||
unpaidColor(): string {
|
||||
if (!this.hasData) {
|
||||
return 'bg-gray-200';
|
||||
}
|
||||
|
||||
return `bg-${this.color}-200`;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
total: 0,
|
||||
unpaid: 0,
|
||||
hasData: false,
|
||||
paid: 0,
|
||||
count: 0,
|
||||
unpaidCount: 0,
|
||||
paidCount: 0,
|
||||
barWidth: 40,
|
||||
period: 'This Year',
|
||||
} as {
|
||||
show: boolean;
|
||||
period: PeriodKey;
|
||||
total: number;
|
||||
unpaid: number;
|
||||
hasData: boolean;
|
||||
paid: number;
|
||||
count: number;
|
||||
unpaidCount: number;
|
||||
paidCount: number;
|
||||
barWidth: number;
|
||||
};
|
||||
},
|
||||
activated() {
|
||||
this.calculateInvoiceTotals();
|
||||
this.setData();
|
||||
},
|
||||
methods: {
|
||||
async calculateInvoiceTotals() {
|
||||
for (const invoice of this.invoices) {
|
||||
const { fromDate, toDate } = await getDatesAndPeriodList(
|
||||
this.$data[invoice.periodKey]
|
||||
);
|
||||
async setData() {
|
||||
const { fromDate, toDate } = getDatesAndPeriodList(this.period);
|
||||
|
||||
const { total, outstanding } = await fyo.db.getTotalOutstanding(
|
||||
invoice.schemaName,
|
||||
fromDate.toISO(),
|
||||
toDate.toISO()
|
||||
);
|
||||
const { total, outstanding } = await fyo.db.getTotalOutstanding(
|
||||
this.schemaName,
|
||||
fromDate.toISO(),
|
||||
toDate.toISO()
|
||||
);
|
||||
|
||||
const { countTotal, countOutstanding } = await this.getCounts(
|
||||
invoice.schemaName,
|
||||
fromDate,
|
||||
toDate
|
||||
);
|
||||
const { countTotal, countOutstanding } = await this.getCounts(
|
||||
this.schemaName,
|
||||
fromDate,
|
||||
toDate
|
||||
);
|
||||
|
||||
invoice.total = total ?? 0;
|
||||
invoice.unpaid = outstanding ?? 0;
|
||||
invoice.paid = total - outstanding;
|
||||
invoice.hasData = countTotal > 0;
|
||||
invoice.count = countTotal;
|
||||
invoice.paidCount = countTotal - countOutstanding;
|
||||
invoice.unpaidCount = countOutstanding;
|
||||
invoice.barWidth = (invoice.paid / (invoice.total || 1)) * 100;
|
||||
}
|
||||
this.total = total ?? 0;
|
||||
this.unpaid = outstanding ?? 0;
|
||||
this.paid = total - outstanding;
|
||||
this.hasData = countTotal > 0;
|
||||
this.count = countTotal;
|
||||
this.paidCount = countTotal - countOutstanding;
|
||||
this.unpaidCount = countOutstanding;
|
||||
this.barWidth = (this.paid / (this.total || 1)) * 100;
|
||||
},
|
||||
async newInvoice(invoice) {
|
||||
let doc = await fyo.doc.getNewDoc(invoice.schemaName);
|
||||
routeTo(`/edit/${invoice.schemaName}/${doc.name}`);
|
||||
async newInvoice() {
|
||||
const doc = fyo.doc.getNewDoc(this.schemaName);
|
||||
await routeTo(`/edit/${this.schemaName}/${doc.name}`);
|
||||
},
|
||||
|
||||
async getCounts(schemaName, fromDate, toDate) {
|
||||
async getCounts(schemaName: string, fromDate: DateTime, toDate: DateTime) {
|
||||
const outstandingAmounts = await fyo.db.getAllRaw(schemaName, {
|
||||
fields: ['outstandingAmount'],
|
||||
filters: {
|
||||
@ -223,5 +211,5 @@ export default {
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -1,9 +1,14 @@
|
||||
<template>
|
||||
<div class="flex overflow-hidden">
|
||||
<Sidebar
|
||||
class="w-sidebar flex-shrink-0 border-r"
|
||||
@change-db-file="$emit('change-db-file')"
|
||||
/>
|
||||
<Transition name="sidebar">
|
||||
<Sidebar
|
||||
v-show="sidebar"
|
||||
class="flex-shrink-0 border-r whitespace-nowrap w-sidebar"
|
||||
@change-db-file="$emit('change-db-file')"
|
||||
@toggle-sidebar="sidebar = !sidebar"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<div class="flex flex-1 overflow-y-hidden bg-white">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive>
|
||||
@ -11,36 +16,76 @@
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
|
||||
<div class="flex" v-if="showQuickEdit">
|
||||
<router-view name="edit" v-slot="{ Component }">
|
||||
<router-view name="edit" v-slot="{ Component, route }">
|
||||
<Transition name="quickedit">
|
||||
<keep-alive>
|
||||
<component
|
||||
:is="Component"
|
||||
class="w-quick-edit flex-1"
|
||||
:key="$route.query.schemaName + $route.query.name"
|
||||
/>
|
||||
<div v-if="route?.query?.edit">
|
||||
<component
|
||||
:is="Component"
|
||||
:key="route.query.schemaName + route.query.name"
|
||||
/>
|
||||
</div>
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</div>
|
||||
</Transition>
|
||||
</router-view>
|
||||
</div>
|
||||
|
||||
<!-- Show Sidebar Button -->
|
||||
<button
|
||||
v-show="!sidebar"
|
||||
class="
|
||||
absolute
|
||||
bottom-0
|
||||
left-0
|
||||
text-gray-600
|
||||
bg-gray-100
|
||||
rounded
|
||||
p-1
|
||||
m-4
|
||||
opacity-0
|
||||
hover:opacity-100 hover:shadow-md
|
||||
"
|
||||
@click="sidebar = !sidebar"
|
||||
>
|
||||
<feather-icon name="chevrons-right" class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Sidebar from '../components/Sidebar';
|
||||
import { computed } from '@vue/reactivity';
|
||||
import Sidebar from '../components/Sidebar.vue';
|
||||
export default {
|
||||
name: 'Desk',
|
||||
emits: ['change-db-file'],
|
||||
data() {
|
||||
return { sidebar: true };
|
||||
},
|
||||
provide() {
|
||||
return { sidebar: computed(() => this.sidebar) };
|
||||
},
|
||||
components: {
|
||||
Sidebar,
|
||||
},
|
||||
computed: {
|
||||
showQuickEdit() {
|
||||
return (
|
||||
this.$route.query.edit &&
|
||||
this.$route.query.schemaName &&
|
||||
this.$route.query.name
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sidebar-enter-from,
|
||||
.sidebar-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(calc(-1 * var(--w-sidebar)));
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.sidebar-enter-to,
|
||||
.sidebar-leave-from {
|
||||
opacity: 1;
|
||||
transform: translateX(0px);
|
||||
width: var(--w-sidebar);
|
||||
}
|
||||
|
||||
.sidebar-enter-active,
|
||||
.sidebar-leave-active {
|
||||
transition: all 150ms ease-out;
|
||||
}
|
||||
</style>
|
||||
|
@ -265,27 +265,31 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #quickedit v-if="quickEditDoc || linked">
|
||||
<QuickEditForm
|
||||
v-if="quickEditDoc && !linked"
|
||||
class="w-quick-edit"
|
||||
:name="quickEditDoc.name"
|
||||
:show-name="false"
|
||||
:show-save="false"
|
||||
:source-doc="quickEditDoc"
|
||||
:source-fields="quickEditFields"
|
||||
:schema-name="quickEditDoc.schemaName"
|
||||
:white="true"
|
||||
:route-back="false"
|
||||
:load-on-close="false"
|
||||
@close="toggleQuickEditDoc(null)"
|
||||
/>
|
||||
<template #quickedit>
|
||||
<Transition name="quickedit">
|
||||
<QuickEditForm
|
||||
v-if="quickEditDoc && !linked"
|
||||
class="w-quick-edit"
|
||||
:name="quickEditDoc.name"
|
||||
:show-name="false"
|
||||
:show-save="false"
|
||||
:source-doc="quickEditDoc"
|
||||
:source-fields="quickEditFields"
|
||||
:schema-name="quickEditDoc.schemaName"
|
||||
:white="true"
|
||||
:route-back="false"
|
||||
:load-on-close="false"
|
||||
@close="toggleQuickEditDoc(null)"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<LinkedEntryWidget
|
||||
v-if="linked && !quickEditDoc"
|
||||
:linked="linked"
|
||||
@close-widget="linked = null"
|
||||
/>
|
||||
<Transition name="quickedit">
|
||||
<LinkedEntryWidget
|
||||
v-if="linked && !quickEditDoc"
|
||||
:linked="linked"
|
||||
@close-widget="linked = null"
|
||||
/>
|
||||
</Transition>
|
||||
</template>
|
||||
</FormContainer>
|
||||
</template>
|
||||
|
@ -198,9 +198,10 @@ export default defineComponent({
|
||||
|
||||
filters = objectForEach(clone(filters), toRaw);
|
||||
|
||||
const orderBy = !!fyo.getField(this.schemaName, 'date')
|
||||
? 'date'
|
||||
: 'created';
|
||||
const orderBy = ['created'];
|
||||
if (fyo.db.fieldMap[this.schemaName]['date']) {
|
||||
orderBy.unshift('date');
|
||||
}
|
||||
|
||||
this.data = (
|
||||
await fyo.db.getAll(this.schemaName, {
|
||||
|
@ -37,21 +37,23 @@
|
||||
</div>
|
||||
|
||||
<!-- Printview Customizer -->
|
||||
<div class="border-l w-quick-edit" v-if="showCustomiser">
|
||||
<div
|
||||
class="px-4 flex items-center justify-between h-row-largest border-b"
|
||||
>
|
||||
<h2 class="font-semibold">{{ t`Customise` }}</h2>
|
||||
<Button :icon="true" @click="showCustomiser = false">
|
||||
<feather-icon name="x" class="w-4 h-4" />
|
||||
</Button>
|
||||
<Transition name="quickedit">
|
||||
<div class="border-l w-quick-edit" v-if="showCustomiser">
|
||||
<div
|
||||
class="px-4 flex items-center justify-between h-row-largest border-b"
|
||||
>
|
||||
<h2 class="font-semibold">{{ t`Customise` }}</h2>
|
||||
<Button :icon="true" @click="showCustomiser = false">
|
||||
<feather-icon name="x" class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<TwoColumnForm
|
||||
:doc="printSettings"
|
||||
:autosave="true"
|
||||
class="border-none"
|
||||
/>
|
||||
</div>
|
||||
<TwoColumnForm
|
||||
:doc="printSettings"
|
||||
:autosave="true"
|
||||
class="border-none"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="border-l h-full overflow-auto"
|
||||
class="border-l h-full overflow-auto w-quick-edit"
|
||||
:class="white ? 'bg-white' : 'bg-gray-25'"
|
||||
>
|
||||
<!-- Quick edit Tool bar -->
|
||||
|
@ -22,7 +22,8 @@
|
||||
v-for="field in report.filters"
|
||||
:border="true"
|
||||
size="small"
|
||||
:show-label="field.fieldtype === 'Check'"
|
||||
:class="[field.fieldtype === 'Check' ? 'self-end' : '']"
|
||||
:show-label="true"
|
||||
:key="field.fieldname + '-filter'"
|
||||
:df="field"
|
||||
:value="report.get(field.fieldname)"
|
||||
|
@ -64,6 +64,7 @@ input[type='number']::-webkit-inner-spin-button {
|
||||
--w-desk-fixed: calc(var(--w-app) - var(--w-sidebar));
|
||||
--w-quick-edit: 22rem;
|
||||
--w-scrollbar: 0.6rem;
|
||||
--w-trafficlights: 72px;
|
||||
|
||||
/* Row Heights */
|
||||
--h-row-smallest: 2rem;
|
||||
@ -144,3 +145,26 @@ input[type='number']::-webkit-inner-spin-button {
|
||||
.custom-scroll::-webkit-scrollbar-thumb:hover {
|
||||
background: theme('colors.gray.400');
|
||||
}
|
||||
|
||||
/*
|
||||
Transitions
|
||||
*/
|
||||
|
||||
.quickedit-enter-from,
|
||||
.quickedit-leave-to {
|
||||
transform: translateX(var(--w-quick-edit));
|
||||
width: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.quickedit-enter-to,
|
||||
.quickedit-leave-from {
|
||||
transform: translateX(0px);
|
||||
width: var(--w-quick-edit);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.quickedit-enter-active,
|
||||
.quickedit-leave-active {
|
||||
transition: all 150ms ease-out;
|
||||
}
|
||||
|
@ -260,9 +260,11 @@ async function getParentData(
|
||||
limit: number | null,
|
||||
fyo: Fyo
|
||||
) {
|
||||
const orderBy = !!fields.find((f) => f.fieldname === 'date')
|
||||
? 'date'
|
||||
: 'created';
|
||||
const orderBy = ['created'];
|
||||
if (fyo.db.fieldMap[schemaName]['date']) {
|
||||
orderBy.unshift('date');
|
||||
}
|
||||
|
||||
const options: GetAllOptions = { filters, orderBy, order: 'desc' };
|
||||
if (limit) {
|
||||
options.limit = limit;
|
||||
|
@ -7,10 +7,13 @@ import SetupWizardSchema from 'schemas/app/SetupWizard.json';
|
||||
import { Schema } from 'schemas/types';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { QueryFilter } from 'utils/db/types';
|
||||
import { PeriodKey } from './types';
|
||||
|
||||
export function getDatesAndPeriodList(
|
||||
period: 'This Year' | 'This Quarter' | 'This Month'
|
||||
): { periodList: DateTime[]; fromDate: DateTime; toDate: DateTime } {
|
||||
export function getDatesAndPeriodList(period: PeriodKey): {
|
||||
periodList: DateTime[];
|
||||
fromDate: DateTime;
|
||||
toDate: DateTime;
|
||||
} {
|
||||
const toDate: DateTime = DateTime.now().plus({ days: 1 });
|
||||
let fromDate: DateTime;
|
||||
|
||||
|
@ -69,4 +69,5 @@ export interface ExportTableField {
|
||||
fields: ExportField[];
|
||||
}
|
||||
|
||||
export type ExportFormat = 'csv' | 'json';
|
||||
export type ExportFormat = 'csv' | 'json';
|
||||
export type PeriodKey = 'This Year' | 'This Quarter' | 'This Month'
|
@ -74,7 +74,7 @@ export async function openQuickEdit({
|
||||
|
||||
router[method]({
|
||||
query: {
|
||||
edit: 1,
|
||||
edit: '1',
|
||||
schemaName,
|
||||
name,
|
||||
showFields,
|
||||
|
@ -59,8 +59,8 @@ export interface GetAllOptions {
|
||||
filters?: QueryFilter;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
groupBy?: string;
|
||||
orderBy?: string;
|
||||
groupBy?: string | string[];
|
||||
orderBy?: string | string[];
|
||||
order?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user