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:
parent
7cfd0726d6
commit
01e197f439
@ -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);
|
||||||
|
@ -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>
|
||||||
|
@ -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 },
|
||||||
|
@ -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';
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user