2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 11:29:03 +00:00

incr: status badge and action buttons

This commit is contained in:
18alantom 2023-02-20 10:22:19 +05:30
parent 7cfd0726d6
commit 01e197f439
5 changed files with 135 additions and 58 deletions

View File

@ -56,7 +56,7 @@
<script lang="ts"> <script lang="ts">
import { Money } from 'pesa'; import { Money } from 'pesa';
import { getCreateRoute } from 'src/router'; import { getCreateRoute } from 'src/router';
import { getStatus, routeTo } from 'src/utils/ui'; import { routeTo } from 'src/utils/ui';
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue';
import Button from '../Button.vue'; import Button from '../Button.vue';
import StatusBadge from '../StatusBadge.vue'; import StatusBadge from '../StatusBadge.vue';
@ -78,7 +78,17 @@ export default defineComponent({
linked: { type: Object as PropType<Linked>, required: true }, linked: { type: Object as PropType<Linked>, required: true },
}, },
methods: { methods: {
getStatus, getStatus(entry: { cancelled?: boolean; submitted?: boolean }) {
if (entry.cancelled) {
return 'Cancelled';
}
if (entry.submitted) {
return 'Submitted';
}
return 'Saved';
},
async openEntry(name: string) { async openEntry(name: string) {
const route = getCreateRoute(this.linked.schemaName, name); const route = getCreateRoute(this.linked.schemaName, name);
await routeTo(route); await routeTo(route);

View File

@ -1,11 +1,35 @@
<template> <template>
<FormContainer> <FormContainer>
<template #header v-if="hasDoc">
<StatusBadge :status="status" />
<DropdownWithActions
v-for="group of groupedActions"
:key="group.label"
:type="group.type"
:actions="group.actions"
>
<p v-if="group.group">
{{ group.group }}
</p>
<feather-icon v-else name="more-horizontal" class="w-4 h-4" />
</DropdownWithActions>
<Button v-if="doc?.canSave" type="primary" @click="() => doc.sync()">
{{ t`Save` }}
</Button>
<Button
v-else-if="doc?.canSubmit"
type="primary"
@click="() => doc.submit()"
>{{ t`Submit` }}</Button
>
</template>
<template #body> <template #body>
<FormHeader <FormHeader
:form-title="title" :form-title="title"
:form-sub-title="schema.label" :form-sub-title="schema.label"
class="sticky top-0 bg-white border-b" class="sticky top-0 bg-white border-b"
/> >
</FormHeader>
<!-- Section Container --> <!-- Section Container -->
<div v-if="hasDoc" class="overflow-auto custom-scroll"> <div v-if="hasDoc" class="overflow-auto custom-scroll">
@ -61,20 +85,34 @@
<script lang="ts"> <script lang="ts">
import { Doc } from 'fyo/model/doc'; import { Doc } from 'fyo/model/doc';
import { ValidationError } from 'fyo/utils/errors'; import { ValidationError } from 'fyo/utils/errors';
import { getDocStatus } from 'models/helpers';
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
import { Field, Schema } from 'schemas/types'; import { Field, Schema } from 'schemas/types';
import Button from 'src/components/Button.vue';
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
import FormContainer from 'src/components/FormContainer.vue'; import FormContainer from 'src/components/FormContainer.vue';
import FormHeader from 'src/components/FormHeader.vue'; import FormHeader from 'src/components/FormHeader.vue';
import { defineComponent } from 'vue'; import StatusBadge from 'src/components/StatusBadge.vue';
import { ActionGroup, UIGroupedFields } from 'src/utils/types';
import {
getFieldsGroupedByTabAndSection,
getGroupedActionsForDoc,
} from 'src/utils/ui';
import { computed, defineComponent } from 'vue';
import CommonFormSection from './CommonFormSection.vue'; import CommonFormSection from './CommonFormSection.vue';
type UIGroupedFields = Map<string, Map<string, Field[]>>;
export default defineComponent({ export default defineComponent({
props: { props: {
name: { type: String, default: 'PAY-1008' }, name: { type: String, default: 'PAY-1008' },
schemaName: { type: String, default: ModelNameEnum.Payment }, schemaName: { type: String, default: ModelNameEnum.Payment },
}, },
provide() {
return {
schemaName: computed(() => this.docOrNull?.schemaName),
name: computed(() => this.docOrNull?.name),
doc: computed(() => this.docOrNull),
};
},
data() { data() {
return { return {
docOrNull: null, docOrNull: null,
@ -82,19 +120,24 @@ export default defineComponent({
} as { docOrNull: null | Doc; activeTab: string }; } as { docOrNull: null | Doc; activeTab: string };
}, },
async mounted() { async mounted() {
if (this.name && !this.docOrNull) {
this.docOrNull = await this.fyo.doc.getDoc(this.schemaName, this.name);
}
if (this.fyo.store.isDevelopment) { if (this.fyo.store.isDevelopment) {
// @ts-ignore // @ts-ignore
window.cf = this; window.cf = this;
} }
await this.setDoc();
}, },
computed: { computed: {
hasDoc(): boolean { hasDoc(): boolean {
return !!this.docOrNull; return !!this.docOrNull;
}, },
status(): string {
if (!this.hasDoc) {
return '';
}
return getDocStatus(this.doc);
},
doc(): Doc { doc(): Doc {
const doc = this.docOrNull as Doc | null; const doc = this.docOrNull as Doc | null;
if (!doc) { if (!doc) {
@ -105,9 +148,10 @@ export default defineComponent({
return doc; return doc;
}, },
title(): string { title(): string {
if (this.schema.isSubmittable && !this.docOrNull?.notInserted) { if (this.schema.isSubmittable && this.docOrNull?.notInserted) {
return this.t`New Entry`; return this.t`New Entry`;
} }
return this.docOrNull?.name!; return this.docOrNull?.name!;
}, },
schema(): Schema { schema(): Schema {
@ -131,27 +175,34 @@ export default defineComponent({
allGroups(): UIGroupedFields { allGroups(): UIGroupedFields {
return getFieldsGroupedByTabAndSection(this.schema); return getFieldsGroupedByTabAndSection(this.schema);
}, },
groupedActions(): ActionGroup[] {
if (!this.hasDoc) {
return [];
}
return getGroupedActionsForDoc(this.doc);
},
},
methods: {
async setDoc() {
if (this.hasDoc) {
return;
}
if (this.name) {
this.docOrNull = await this.fyo.doc.getDoc(this.schemaName, this.name);
} else {
this.docOrNull = this.fyo.doc.getNewDoc(this.schemaName);
}
},
},
components: {
FormContainer,
FormHeader,
CommonFormSection,
StatusBadge,
Button,
DropdownWithActions,
}, },
components: { FormContainer, FormHeader, CommonFormSection },
}); });
function getFieldsGroupedByTabAndSection(schema: Schema): UIGroupedFields {
const grouped: UIGroupedFields = new Map();
for (const field of schema?.fields ?? []) {
const tab = field.tab ?? 'Default';
const section = field.section ?? 'Default';
if (!grouped.has(tab)) {
grouped.set(tab, new Map());
}
const tabbed = grouped.get(tab)!;
if (!tabbed.has(section)) {
tabbed.set(section, []);
}
tabbed.get(section)!.push(field);
}
return grouped;
}
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div v-if="filteredFields.length > 0">
<div <div
v-if="showTitle && title" v-if="showTitle && title"
class="flex justify-between items-center cursor-pointer select-none" class="flex justify-between items-center cursor-pointer select-none"
@ -48,7 +48,9 @@ export default defineComponent({
}, },
computed: { computed: {
filteredFields(): Field[] { filteredFields(): Field[] {
return (this.fields ?? []).filter((f) => !evaluateHidden(f, this.doc)); return (this.fields ?? []).filter(
(f) => !evaluateHidden(f, this.doc) && !f.meta
);
}, },
}, },
components: { FormControl }, components: { FormControl },

View File

@ -1,6 +1,7 @@
import { Doc } from "fyo/model/doc"; import type { Doc } from 'fyo/model/doc';
import { FieldType } from "schemas/types"; import type { Action } from 'fyo/model/types';
import { QueryFilter } from "utils/db/types"; import type { Field, FieldType } from 'schemas/types';
import type { QueryFilter } from 'utils/db/types';
export interface MessageDialogButton { export interface MessageDialogButton {
label: string; label: string;
@ -31,7 +32,7 @@ export interface QuickEditOptions {
hideFields?: string[]; hideFields?: string[];
showFields?: string[]; showFields?: string[];
defaults?: Record<string, unknown>; defaults?: Record<string, unknown>;
listFilters?: QueryFilter listFilters?: QueryFilter;
} }
export type SidebarConfig = SidebarRoot[]; export type SidebarConfig = SidebarRoot[];
@ -44,7 +45,7 @@ export interface SidebarRoot {
iconHeight?: string; iconHeight?: string;
hidden?: () => boolean; hidden?: () => boolean;
items?: SidebarItem[]; items?: SidebarItem[];
filters?: QueryFilter filters?: QueryFilter;
} }
export interface SidebarItem { export interface SidebarItem {
@ -55,7 +56,6 @@ export interface SidebarItem {
hidden?: () => boolean; hidden?: () => boolean;
} }
export interface ExportField { export interface ExportField {
fieldname: string; fieldname: string;
fieldtype: FieldType; fieldtype: FieldType;
@ -70,5 +70,13 @@ export interface ExportTableField {
fields: ExportField[]; fields: ExportField[];
} }
export type ActionGroup = {
group: string;
label: string;
type: string;
actions: Action[];
};
export type UIGroupedFields = Map<string, Map<string, Field[]>>;
export type ExportFormat = 'csv' | 'json'; export type ExportFormat = 'csv' | 'json';
export type PeriodKey = 'This Year' | 'This Quarter' | 'This Month' export type PeriodKey = 'This Year' | 'This Quarter' | 'This Month';

View File

@ -9,6 +9,7 @@ import { Action } from 'fyo/model/types';
import { getActions } from 'fyo/utils'; import { getActions } from 'fyo/utils';
import { getDbError, LinkValidationError, ValueError } from 'fyo/utils/errors'; import { getDbError, LinkValidationError, ValueError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
import { Schema } from 'schemas/types';
import { handleErrorWithDialog } from 'src/errorHandling'; import { handleErrorWithDialog } from 'src/errorHandling';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
import router from 'src/router'; import router from 'src/router';
@ -17,10 +18,12 @@ import { App, createApp, h, ref } from 'vue';
import { RouteLocationRaw } from 'vue-router'; import { RouteLocationRaw } from 'vue-router';
import { stringifyCircular } from './'; import { stringifyCircular } from './';
import { import {
ActionGroup,
MessageDialogOptions, MessageDialogOptions,
QuickEditOptions, QuickEditOptions,
SettingsTab, SettingsTab,
ToastOptions, ToastOptions,
UIGroupedFields,
} from './types'; } from './types';
export async function openQuickEdit({ export async function openQuickEdit({
@ -274,14 +277,7 @@ export function getActionsForDoc(doc?: Doc): Action[] {
}); });
} }
export function getGroupedActionsForDoc(doc?: Doc) { export function getGroupedActionsForDoc(doc?: Doc): ActionGroup[] {
type Group = {
group: string;
label: string;
type: string;
actions: Action[];
};
const actions = getActionsForDoc(doc); const actions = getActionsForDoc(doc);
const actionsMap = actions.reduce((acc, ac) => { const actionsMap = actions.reduce((acc, ac) => {
if (!ac.group) { if (!ac.group) {
@ -297,7 +293,7 @@ export function getGroupedActionsForDoc(doc?: Doc) {
acc[ac.group].actions.push(ac); acc[ac.group].actions.push(ac);
return acc; return acc;
}, {} as Record<string, Group>); }, {} as Record<string, ActionGroup>);
const grouped = Object.keys(actionsMap) const grouped = Object.keys(actionsMap)
.filter(Boolean) .filter(Boolean)
@ -393,14 +389,24 @@ function getDuplicateAction(doc: Doc): Action {
}; };
} }
export function getStatus(entry: { cancelled?: boolean; submitted?: boolean }) { export function getFieldsGroupedByTabAndSection(
if (entry.cancelled) { schema: Schema
return 'Cancelled'; ): UIGroupedFields {
const grouped: UIGroupedFields = new Map();
for (const field of schema?.fields ?? []) {
const tab = field.tab ?? 'Default';
const section = field.section ?? 'Default';
if (!grouped.has(tab)) {
grouped.set(tab, new Map());
} }
if (entry.submitted) { const tabbed = grouped.get(tab)!;
return 'Submitted'; if (!tabbed.has(section)) {
tabbed.set(section, []);
} }
return 'Saved'; tabbed.get(section)!.push(field);
}
return grouped;
} }