2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 10:58:59 +00:00

feat: common form view

This commit is contained in:
18alantom 2023-02-17 21:05:06 +05:30
parent 961ad62a18
commit 1db70dfef6
5 changed files with 209 additions and 1 deletions

View File

@ -64,6 +64,8 @@ export interface BaseField {
inline?: boolean; // UI Facing config, whether to display doc inline.
filter?: boolean; // UI Facing config, whether to be used to filter the List.
computed?: boolean; // Computed values are not stored in the database.
section?: string; // UI Facing config, for grouping by sections
tab?: string; // UI Facing config, for grouping by tabs
}
export type SelectOption = { value: string; label: string };

View File

@ -8,6 +8,7 @@
justify-between
h-row-large
items-center
flex-shrink-0
"
>
<h1>{{ formTitle }}</h1>

View File

@ -0,0 +1,157 @@
<template>
<FormContainer>
<template #body>
<FormHeader
:form-title="title"
:form-sub-title="schema.label"
class="sticky top-0 bg-white border-b"
/>
<!-- Section Container -->
<div v-if="hasDoc" class="overflow-auto custom-scroll">
<CommonFormSection
ref="section"
class="p-4"
v-for="[name, fields] of activeGroup.entries()"
:show-title="activeGroup.size > 1"
:title="name"
:fields="fields"
:key="name"
:doc="doc"
/>
</div>
<!-- Tab Bar -->
<div
class="
mt-auto
px-4
pb-4
flex
gap-8
border-t
flex-shrink-0
sticky
bottom-0
bg-white
"
v-if="true || allGroups.size > 1"
>
<div
v-for="key of allGroups.keys()"
:key="key"
@click="activeTab = key"
class="text-sm cursor-pointer"
:class="
key === activeTab
? 'text-blue-500 font-semibold border-t-2 border-blue-500'
: ''
"
:style="{
paddingTop: key === activeTab ? 'calc(1rem - 2px)' : '1rem',
}"
>
{{ key }}
</div>
</div>
</template>
</FormContainer>
</template>
<script lang="ts">
import { Doc } from 'fyo/model/doc';
import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { Field, Schema } from 'schemas/types';
import FormContainer from 'src/components/FormContainer.vue';
import FormHeader from 'src/components/FormHeader.vue';
import { defineComponent } from 'vue';
import CommonFormSection from './CommonFormSection.vue';
type UIGroupedFields = Map<string, Map<string, Field[]>>;
export default defineComponent({
props: {
name: { type: String, default: 'PAY-1008' },
schemaName: { type: String, default: ModelNameEnum.Payment },
},
data() {
return {
docOrNull: null,
activeTab: 'Default',
} as { docOrNull: null | Doc; activeTab: string };
},
async mounted() {
if (this.name && !this.docOrNull) {
this.docOrNull = await this.fyo.doc.getDoc(this.schemaName, this.name);
}
if (this.fyo.store.isDevelopment) {
// @ts-ignore
window.cf = this;
}
},
computed: {
hasDoc(): boolean {
return !!this.docOrNull;
},
doc(): Doc {
const doc = this.docOrNull as Doc | null;
if (!doc) {
throw new ValidationError(
this.t`Doc ${this.schema.label} ${this.name} not set`
);
}
return doc;
},
title(): string {
if (this.schema.isSubmittable && !this.docOrNull?.notInserted) {
return this.t`New Entry`;
}
return this.docOrNull?.name!;
},
schema(): Schema {
const schema = this.fyo.schemaMap[this.schemaName];
if (!schema) {
throw new ValidationError(`no schema found with ${this.schemaName}`);
}
return schema;
},
activeGroup(): Map<string, Field[]> {
const group = this.allGroups.get(this.activeTab);
if (!group) {
throw new ValidationError(
`Tab group ${this.activeTab} has no value set`
);
}
return group;
},
allGroups(): UIGroupedFields {
return getFieldsGroupedByTabAndSection(this.schema);
},
},
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>

View File

@ -0,0 +1,42 @@
<template>
<div class="grid gap-4 gap-x-8 grid-cols-2">
<FormControl
v-for="field of filteredFields"
:class="field.fieldtype === 'Table' ? 'col-span-2' : ''"
:show-label="true"
:border="true"
:key="field.fieldname"
:df="field"
:value="getRegularValue(field)"
@change="async (value) => await doc.set(field.fieldname, value)"
/>
</div>
</template>
<script lang="ts">
import { DocValue } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import { Field } from 'schemas/types';
import FormControl from 'src/components/Controls/FormControl.vue';
import { evaluateHidden } from 'src/utils/doc';
import { defineComponent, PropType } from 'vue';
export default defineComponent({
props: {
title: String,
showTitle: Boolean,
doc: { type: Object as PropType<Doc>, required: true },
fields: Array as PropType<Field[]>,
},
computed: {
filteredFields(): Field[] {
return (this.fields ?? []).filter((f) => !evaluateHidden(f, this.doc));
},
},
methods: {
getRegularValue(field: Field): DocValue | Doc[] {
return this.doc.get(field.fieldname);
},
},
components: { FormControl },
});
</script>

View File

@ -2,7 +2,13 @@
* Properties of a schema which are to be translated,
* irrespective of nesting.
*/
export const schemaTranslateables = ['label', 'description', 'placeholder'];
export const schemaTranslateables = [
'label',
'description',
'placeholder',
'section',
'tab',
];
export function getIndexFormat(inp: string | string[]) {
/**