diff --git a/backends/database.js b/backends/database.js index 5281aeb6..ffcf68d6 100644 --- a/backends/database.js +++ b/backends/database.js @@ -388,31 +388,68 @@ module.exports = class Database extends Observable { getFilterConditions(filters) { // {"status": "Open"} => `status = "Open"` + // {"status": "Open", "name": ["like", "apple%"]} // => `status="Open" and name like "apple%" - let conditions = []; - let values = []; + + // {"date": [">=", "2017-09-09", "<=", "2017-11-01"]} + // => `date >= 2017-09-09 and date <= 2017-11-01` + + let filtersArray = []; + for (let key in filters) { - const value = filters[key]; - if (value instanceof Array) { - const condition = value[0]; - // if its like, we should add the wildcard "%" if the user has not added - if (condition.toLowerCase()==='includes') { - condition = 'like'; + let value = filters[key]; + let field = key; + let operator = '='; + let comparisonValue = value; + + if (Array.isArray(value)) { + operator = value[0]; + comparisonValue = value[1]; + operator = operator.toLowerCase(); + + if (operator === 'includes') { + operator = 'like'; } - if (['like', 'includes'].includes(condition.toLowerCase()) && !value[1].includes('%')) { - value[1] = `%${value[1]}%`; + + if (['like', 'includes'].includes(operator) && !comparisonValue.includes('%')) { + comparisonValue = `%${comparisonValue}%`; } - conditions.push(`ifnull(${key}, '') ${condition} ?`); - values.push(value[1]); - } else { - conditions.push(`ifnull(${key}, '') = ?`); - values.push(value); + } + + filtersArray.push([field, operator, comparisonValue]); + + if (Array.isArray(value) && value.length > 2) { + // multiple conditions + let operator = value[2]; + let comparisonValue = value[3]; + filtersArray.push([field, operator, comparisonValue]); } } + + let conditions = filtersArray.map(filter => { + const [field, operator, comparisonValue] = filter; + + let placeholder = Array.isArray(comparisonValue) ? + comparisonValue.map(v => '?').join(', ') : + '?'; + + return `ifnull(${field}, '') ${operator} (${placeholder})`; + }); + + let values = filtersArray.reduce((acc, filter) => { + const comparisonValue = filter[2]; + if (Array.isArray(comparisonValue)) { + acc = acc.concat(comparisonValue); + } else { + acc.push(comparisonValue); + } + return acc; + }, []); + return { conditions: conditions.length ? conditions.join(" and ") : "", - values: values + values }; } diff --git a/client/desk/reportpage.js b/client/desk/reportpage.js index d218c6f1..040d4ec0 100644 --- a/client/desk/reportpage.js +++ b/client/desk/reportpage.js @@ -1,5 +1,5 @@ const Page = require('frappejs/client/view/page'); -const Form = require('frappejs/client/view/form'); +const FormLayout = require('frappejs/client/view/formLayout'); const DataTable = require('frappe-datatable'); const frappe = require('frappejs'); const utils = require('frappejs/client/ui/utils'); @@ -10,7 +10,7 @@ const Observable = require('frappejs/utils/observable'); // `getColumns` return columns module.exports = class ReportPage extends Page { - constructor({title, filterFields}) { + constructor({title, filterFields = []}) { super({title: title, hasRoute: true}); this.fullPage = true; @@ -30,19 +30,24 @@ module.exports = class ReportPage extends Page { // overrride } + getRowsForDataTable(data) { + return data; + } + makeFilters() { - this.form = new Form({ + this.filters = new FormLayout({ parent: this.filterWrapper, - meta: { fields: this.filterFields }, + fields: this.filterFields, doc: new Observable(), - inline: true, - container: this + inline: true }); + + this.filterWrapper.appendChild(this.filters.form); } getFilterValues() { const values = {}; - for (let control of this.form.formLayout.controlList) { + for (let control of this.filters.controlList) { values[control.fieldname] = control.getInputValue(); if (control.required && !values[control.fieldname]) { frappe.ui.showAlert({message: frappe._('{0} is mandatory', control.label), color: 'red'}); @@ -60,8 +65,8 @@ module.exports = class ReportPage extends Page { async run() { if (frappe.params && frappe.params.filters) { for (let key in frappe.params.filters) { - if (this.form.controls[key]) { - this.form.controls[key].setInputValue(frappe.params.filters[key]); + if (this.filters.controls[key]) { + this.filters.controls[key].setInputValue(frappe.params.filters[key]); } } } @@ -79,14 +84,17 @@ module.exports = class ReportPage extends Page { method: this.method, args: filterValues }); - this.datatable.refresh(data); + + const rows = this.getRowsForDataTable(data); + const columns = utils.convertFieldsToDatatableColumns(this.getColumns(data), this.layout); + this.datatable.refresh(rows, columns); } makeDataTable() { - this.datatable = new DataTable(this.tableWrapper, { + this.datatable = new DataTable(this.tableWrapper, Object.assign({ columns: utils.convertFieldsToDatatableColumns(this.getColumns(), this.layout), data: [], layout: this.layout || 'fluid', - }); + }, this.datatableOptions || {})); } } \ No newline at end of file diff --git a/client/view/controls/date.js b/client/view/controls/date.js index 60df660a..04decf9f 100644 --- a/client/view/controls/date.js +++ b/client/view/controls/date.js @@ -11,7 +11,9 @@ class DateControl extends BaseControl { 'mm/dd/yyyy': 'm/d/Y', 'mm-dd-yyyy': 'm-d-Y' } - let altFormat = dateFormat[frappe.SystemSettings.dateFormat]; + let altFormat = frappe.SystemSettings ? + dateFormat[frappe.SystemSettings.dateFormat] : + dateFormat['yyyy-mm-dd']; super.make(); this.input.setAttribute('type', 'text'); diff --git a/client/view/controls/link.js b/client/view/controls/link.js index 0a5a42ed..aa1f4ef1 100644 --- a/client/view/controls/link.js +++ b/client/view/controls/link.js @@ -14,6 +14,12 @@ class LinkControl extends BaseControl { minChars: 0, maxItems: 99, filter: () => true, + sort: (a, b) => { + if (a.value === '__newitem' || b.value === '__newitem') { + return -1; + } + return a.value > b.value; + } }); // rebuild the list on input diff --git a/client/view/form.js b/client/view/form.js index 24785afc..9326d8ae 100644 --- a/client/view/form.js +++ b/client/view/form.js @@ -206,7 +206,7 @@ module.exports = class BaseForm extends Observable { } refreshLinks(links) { - if (!this.container) return; + if (!(this.container && this.container.clearLinks)) return; this.container.clearLinks(); for(let link of links) { diff --git a/client/view/formLayout.js b/client/view/formLayout.js index ef11c1e5..c5e551bb 100644 --- a/client/view/formLayout.js +++ b/client/view/formLayout.js @@ -3,7 +3,7 @@ const controls = require('./controls'); const Observable = require('frappejs/utils/observable'); module.exports = class FormLayout extends Observable { - constructor({fields, doc, layout, events = []}) { + constructor({fields, doc, layout, inline = false, events = []}) { super(); Object.assign(this, arguments[0]); this.controls = {}; @@ -14,6 +14,11 @@ module.exports = class FormLayout extends Observable { this.form = document.createElement('div'); this.form.classList.add('form-body'); + if (this.inline) { + this.form.classList.add('row'); + this.form.classList.add('p-0'); + } + this.makeLayout(); if (doc) { @@ -63,6 +68,9 @@ module.exports = class FormLayout extends Observable { let control = controls.makeControl({field: field, form: this, parent: parent}); this.controlList.push(control); this.controls[field.fieldname] = control; + if (this.inline) { + control.inputContainer.classList.add('col'); + } } } } diff --git a/client/view/tree.js b/client/view/tree.js index 6a523114..e4325910 100644 --- a/client/view/tree.js +++ b/client/view/tree.js @@ -105,6 +105,8 @@ module.exports = class BaseTree extends BaseList { if (action === 'edit') { this.edit(treeNode.props.doc.name); + } else if (action === 'addChild') { + this.addChildNode(treeNode.props.doc.name); } }); @@ -115,6 +117,15 @@ module.exports = class BaseTree extends BaseList { frappe.desk.showFormModal(this.doctype, name); } + async addChildNode(name) { + const newDoc = await frappe.getNewDoc(this.doctype); + const formModal = await frappe.desk.showFormModal(this.doctype, newDoc.name); + const parentField = this.treeSettings.parentField; + if (formModal.form.doc.meta.hasField(parentField)) { + formModal.form.doc.set(parentField, name); + } + } + async getData(node) { let fields = this.getFields(); let filters = {}; @@ -150,8 +161,8 @@ module.exports = class BaseTree extends BaseList { getActionButtonsHTML() { return [ - { id: 'edit', label: frappe._('Edit') } - // { id: 'addChild', label: frappe._('Add Child') }, + { id: 'edit', label: frappe._('Edit') }, + { id: 'addChild', label: frappe._('Add Child') }, // { id: 'delete', label: frappe._('Delete') }, ].map(button => { return `