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:
parent
961ad62a18
commit
1db70dfef6
@ -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 };
|
||||
|
@ -8,6 +8,7 @@
|
||||
justify-between
|
||||
h-row-large
|
||||
items-center
|
||||
flex-shrink-0
|
||||
"
|
||||
>
|
||||
<h1>{{ formTitle }}</h1>
|
||||
|
157
src/pages/CommonForm/CommonForm.vue
Normal file
157
src/pages/CommonForm/CommonForm.vue
Normal 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>
|
42
src/pages/CommonForm/CommonFormSection.vue
Normal file
42
src/pages/CommonForm/CommonFormSection.vue
Normal 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>
|
@ -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[]) {
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user