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

incr: add pick columns modal

This commit is contained in:
18alantom 2023-02-02 17:52:50 +05:30
parent b24577f9fc
commit 6e64f4f93d
2 changed files with 179 additions and 61 deletions

View File

@ -12,7 +12,7 @@ import {
} from 'schemas/types';
import { generateCSV, parseCSV } from 'utils/csvParser';
type TemplateField = Field & {
export type TemplateField = Field & {
schemaName: string;
schemaLabel: string;
fieldKey: string;
@ -32,7 +32,7 @@ const skippedFieldsTypes: FieldType[] = [
];
/**
* Tool that
* Tool that
* - Can make bulk entries for any kind of Doc
* - Takes in unstructured CSV data, converts it into Docs
* - Saves and or Submits the converted Docs
@ -45,7 +45,7 @@ export class Importer {
* List of template fields that have been assigned a column, in
* the order they have been assigned.
*/
assignedTemplateFields: string[];
assignedTemplateFields: (string | null)[];
/**
* Map of all the template fields that can be imported.
@ -59,11 +59,6 @@ export class Importer {
*/
templateFieldsPicked: Map<string, boolean>;
/**
* Map of Fields that have been assigned columns
*/
templateFieldsAssigned: Map<string, number>;
/**
* Whether the schema type being imported has table fields
*/
@ -91,12 +86,10 @@ export class Importer {
this.assignedTemplateFields = templateFields.map((f) => f.fieldKey);
this.templateFieldsMap = new Map();
this.templateFieldsPicked = new Map();
this.templateFieldsAssigned = new Map();
templateFields.forEach((f, i) => {
this.templateFieldsMap.set(f.fieldKey, f);
this.templateFieldsPicked.set(f.fieldKey, true);
this.templateFieldsAssigned.set(f.fieldKey, i);
});
}
@ -115,6 +108,7 @@ export class Importer {
addRow() {
const valueRow: ValueMatrix[number] = this.assignedTemplateFields.map(
(key) => {
key ??= '';
const { fieldtype } = this.templateFieldsMap.get(key) ?? {};
let value = null;
if (fieldtype) {

View File

@ -32,10 +32,19 @@
p-4
"
>
<FormControl
:df="importableDf"
<AutoComplete
:df="{
fieldname: 'importType',
label: t`Import Type`,
fieldtype: 'AutoComplete',
options: importableSchemaNames.map((value) => ({
value,
label: fyo.schemaMap[value]?.label ?? value,
})),
}"
input-class="bg-transparent text-gray-900 text-base"
class="w-40 bg-gray-100 rounded"
class="w-40"
:border="true"
:value="importType"
size="small"
@change="setImportType"
@ -95,18 +104,19 @@
<!-- Grid -->
<div
class="overflow-x-scroll gap-4 p-4 grid"
:style="`grid-template-columns: repeat(${pickedArray.length}, 10rem)`"
:style="`grid-template-columns: repeat(${columnCount}, 10rem)`"
>
<!-- Grid Title Row Cells, Allow Column Selection -->
<AutoComplete
class="flex-shrink-0"
v-for="(_, index) in pickedArray"
:class="importer.assignedTemplateFields[index] ? '' : ''"
v-for="index in columnIterator"
size="small"
:border="true"
:key="index"
:df="gridColumnTitleDf"
:value="importer.assignedTemplateFields[index]"
@change="(v:string) => importer.assignedTemplateFields[index] = v"
@change="(v: string | null) => importer.assignedTemplateFields[index] = v"
/>
<!-- Grid Value Row Cells, Allow Editing Values -->
@ -115,21 +125,36 @@
v-for="(val, cidx) of row"
:key="`cell-${ridx}-${cidx}`"
>
<div v-if="!importer.assignedTemplateFields[cidx]">
{{ val.value }}
</div>
<!-- Raw Data Field if Column is Not Assigned -->
<Data
v-if="!importer.assignedTemplateFields[cidx]"
:title="val.rawValue ?? val.value"
:df="{
fieldname: 'tempField',
label: t`Temporary`,
placeholder: t`Select column`,
}"
size="small"
:border="true"
:value="val.value == null ? '' : String(val.value)"
:read-only="true"
/>
<!-- FormControl Field if Column is Assigned -->
<FormControl
v-else
:title="val.error ? val.rawValue ?? val.value : val.value"
:df="
importer.templateFieldsMap.get(
importer.assignedTemplateFields[cidx]
importer.assignedTemplateFields[cidx]!
)
"
size="small"
:rows="1"
:border="true"
:value="val.value"
:value="val.error ? null : val.value"
@change="(value: DocValue)=> {
importer.valueMatrix[ridx][cidx]!.error = false
importer.valueMatrix[ridx][cidx]!.value = value
}"
/>
@ -208,6 +233,8 @@
</div>
</div>
</div>
<!-- How to Use Link -->
<div
v-if="!importType"
class="flex justify-center h-full w-full items-center mb-16"
@ -220,28 +247,85 @@
</HowTo>
</div>
<!-- Loading Bar when Saving Docs -->
<Loading
v-if="isMakingEntries"
:open="isMakingEntries"
:percent="percentLoading"
:message="messageLoading"
/>
<!-- Pick Column Modal -->
<Modal
:open-modal="showColumnPicker"
@closemodal="showColumnPicker = false"
>
<div class="w-form">
<!-- Pick Column Header -->
<FormHeader :form-title="t`Pick Import Columns`" />
<hr />
<!-- Pick Column Checkboxes -->
<div
class="p-4 max-h-80 overflow-auto custom-scroll"
v-for="[key, value] of columnPickerFieldsMap.entries()"
:key="key"
>
<h2 class="text-sm font-semibold text-gray-800">
{{ key }}
</h2>
<div class="grid grid-cols-3 border rounded mt-1">
<div
v-for="tf of value"
:key="tf.fieldKey"
class="flex items-center"
>
<Check
:df="{
fieldname: tf.fieldname,
label: tf.label,
}"
:show-label="true"
:read-only="tf.required"
:value="importer.templateFieldsPicked.get(tf.fieldKey)"
@change="(value:boolean) => pickColumn(tf.fieldKey, value)"
/>
<p v-if="tf.required" class="w-0 text-red-600 -ml-4">*</p>
</div>
</div>
</div>
<!-- Pick Column Footer -->
<hr />
<div class="p-4 flex justify-between items-center">
<p class="text-sm text-gray-600">
{{ t`${numColumnsPicked} fields selected` }}
</p>
<Button type="primary" @click="showColumnPicker = false">{{
t`Done`
}}</Button>
</div>
</div>
</Modal>
</div>
</template>
<script lang="ts">
import { log } from 'console';
import { DocValue } from 'fyo/core/types';
import { Action as BaseAction } from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { FieldTypeEnum, OptionField, SelectOption } from 'schemas/types';
import { OptionField, SelectOption } from 'schemas/types';
import Button from 'src/components/Button.vue';
import AutoComplete from 'src/components/Controls/AutoComplete.vue';
import Check from 'src/components/Controls/Check.vue';
import Data from 'src/components/Controls/Data.vue';
import FormControl from 'src/components/Controls/FormControl.vue';
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
import FormHeader from 'src/components/FormHeader.vue';
import HowTo from 'src/components/HowTo.vue';
import Modal from 'src/components/Modal.vue';
import PageHeader from 'src/components/PageHeader.vue';
import { Importer } from 'src/importer';
import { Importer, TemplateField } from 'src/importer';
import { fyo } from 'src/initFyo';
import { getSavePath, saveData, selectFile } from 'src/utils/ipcCalls';
import { docsPathMap } from 'src/utils/misc';
@ -255,6 +339,7 @@ type Action = Pick<BaseAction, 'condition' | 'component'> & {
};
type DataImportData = {
showColumnPicker: boolean;
canReset: boolean;
complete: boolean;
names: string[];
@ -275,9 +360,14 @@ export default defineComponent({
HowTo,
Loading,
AutoComplete,
Data,
Modal,
FormHeader,
Check,
},
data() {
return {
showColumnPicker: false,
canReset: false,
complete: false,
names: ['Bat', 'Baseball', 'Other Shit'],
@ -292,13 +382,53 @@ export default defineComponent({
mounted() {
if (fyo.store.isDevelopment) {
// @ts-ignore
window.bew = this;
window.iw = this;
this.setImportType('Item');
this.showColumnPicker = true;
}
},
computed: {
columnCount(): number {
let vmColumnCount = 0;
if (this.importer.valueMatrix.length) {
vmColumnCount = this.importer.valueMatrix[0].length;
}
return Math.max(this.numColumnsPicked, vmColumnCount);
},
columnIterator(): number[] {
return Array(this.columnCount)
.fill(null)
.map((_, i) => i);
},
hasImporter(): boolean {
return !!this.nullOrImporter;
},
numColumnsPicked(): number {
return [...this.importer.templateFieldsPicked.values()].filter(Boolean)
.length;
},
columnPickerFieldsMap(): Map<string, TemplateField[]> {
const map: Map<string, TemplateField[]> = new Map();
for (const value of this.importer.templateFieldsMap.values()) {
let label = value.schemaLabel;
if (value.parentSchemaChildField) {
label = `${value.parentSchemaChildField.label} (${value.schemaLabel})`;
}
if (!map.has(label)) {
map.set(label, []);
}
map.get(label)!.push(value);
}
return map;
},
requiredColumnsNotPicked(): string[] {
return [];
},
importer(): Importer {
if (!this.nullOrImporter) {
throw new ValidationError(this.t`Importer not set, reload tool`, false);
@ -358,6 +488,14 @@ export default defineComponent({
});
}
const pickColumnsAction = {
component: {
template: '<span>{{ t`Pick Import Columns` }}</span>',
},
condition: () => true,
action: () => (this.showColumnPicker = true),
};
const cancelAction = {
component: {
template: '<span class="text-red-700" >{{ t`Cancel` }}</span>',
@ -365,7 +503,7 @@ export default defineComponent({
condition: () => true,
action: this.clear,
};
actions.push(cancelAction);
actions.push(pickColumnsAction, cancelAction);
return actions;
},
@ -392,32 +530,13 @@ export default defineComponent({
const schemaName = this.importer.schemaName;
return fyo.schemaMap[schemaName]?.isSubmittable ?? false;
},
importableDf(): OptionField {
return {
fieldname: 'importType',
label: this.t`Import Type`,
fieldtype: FieldTypeEnum.AutoComplete,
options: Object.keys(this.labelSchemaNameMap).map((k) => ({
value: k,
label: k,
})),
};
},
labelSchemaNameMap(): Record<string, string> {
return this.importableSchemaNames
.map((i) => ({
name: i,
label: fyo.schemaMap[i]?.label ?? i,
}))
.reduce((acc, { name, label }) => {
acc[label] = name;
return acc;
}, {} as Record<string, string>);
},
gridColumnTitleDf(): OptionField {
const options: SelectOption[] = [];
for (const field of this.importer.templateFieldsMap.values()) {
const value = field.fieldKey;
if (!this.importer.templateFieldsPicked.get(value)) {
continue;
}
let label = field.label;
if (field.parentSchemaChildField) {
@ -435,7 +554,7 @@ export default defineComponent({
},
pickedArray(): string[] {
return [...this.importer.templateFieldsPicked.entries()]
.filter(([key, picked]) => picked)
.filter(([_, picked]) => picked)
.map(([key, _]) => key);
},
},
@ -452,6 +571,20 @@ export default defineComponent({
},
methods: {
log: console.log,
pickColumn(fieldKey: string, value: boolean): void {
this.importer.templateFieldsPicked.set(fieldKey, value);
if (value) {
return;
}
const idx = this.importer.assignedTemplateFields.findIndex(
(f) => f === fieldKey
);
if (idx >= 0) {
this.importer.assignedTemplateFields[idx] = null;
}
},
showMe(): void {
const schemaName = this.importer.schemaName;
this.clear();
@ -560,10 +693,7 @@ export default defineComponent({
}
this.importType = importType;
this.nullOrImporter = new Importer(
this.labelSchemaNameMap[this.importType],
fyo
);
this.nullOrImporter = new Importer(importType, fyo);
},
setLoadingStatus(
isMakingEntries: boolean,
@ -617,13 +747,7 @@ export default defineComponent({
});
/**
* TODO: Add Pick Modal
* TODO: Build Grid Body
* TODO: View raw values
* TODO: If field not assigned to column show raw value
* TODO: If field assigned to column show respective FormControl
* TODO: If error in parsing the value, mark as error and call
* - for editing value
* TODO: View parsed values (after columns have been assigned)
* TODO: Show some indication if the value has an error
* TODO: Check if links exist
*/
</script>