Skip to content
This repository was archived by the owner on Aug 16, 2022. It is now read-only.

Add Kanban Feature #100

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions models/doctype/Kanban/Kanban.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module.exports = {
name: 'Kanban',
doctype: 'DocType',
naming: 'random',
keywordFields: ['kanbanname', 'referencedoctype', 'sortby'],
fields: [
{
fieldname: 'kanbanname',
label: 'Kanban Name',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'referencedoctype',
label: 'Reference Doctype',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'sortby',
label: 'Sort By',
fieldtype: 'Select',
options: [],
required: 1
},
{
fieldname: 'lists',
label: 'Lists',
fieldtype: 'Table',
childtype: 'KanbanList'
},
{
fieldname: 'titlefield',
label: 'Title Field for Cards',
fieldtype: 'Select',
options: [],
required: 1
}
]
};
44 changes: 44 additions & 0 deletions models/doctype/KanbanCard/KanbanCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module.exports = {
name: 'KanbanCard',
doctype: 'DocType',
naming: 'random',
keywordFields: ['cardtitle', 'carddescription', 'assignee'],
titleField: 'cardtitle',
fields: [
{
fieldname: 'cardtitle',
label: 'Card Title',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'listname',
label: 'Add To List',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'boardname',
label: 'Kanban board',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'referencedoctype',
label: 'Reference Doctype Name',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'carddescription',
label: 'Card Description',
fieldtype: 'Text'
},
{
fieldname: 'assignee',
label: 'Assignee',
fieldtype: 'Link',
target: 'User'
}
]
};
19 changes: 19 additions & 0 deletions models/doctype/KanbanList/KanbanList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = {
name: 'KanbanList',
doctype: 'DocType',
isChild: 1,
naming: 'random',
fields: [
{
label: 'List Name',
fieldname: 'listname',
fieldtype: 'Data'
},
{
label: 'Archived',
fieldname: 'archived',
fieldtype: 'Int',
default: 0
}
]
};
35 changes: 19 additions & 16 deletions models/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
module.exports = {
models: {
FilterItem: require('./doctype/FilterItem/FilterItem.js'),
FilterGroup: require('./doctype/FilterGroup/FilterGroup.js'),
FilterSelector: require('./doctype/FilterSelector/FilterSelector.js'),
NumberSeries: require('./doctype/NumberSeries/NumberSeries.js'),
PrintFormat: require('./doctype/PrintFormat/PrintFormat.js'),
Role: require('./doctype/Role/Role.js'),
Session: require('./doctype/Session/Session.js'),
SingleValue: require('./doctype/SingleValue/SingleValue.js'),
SystemSettings: require('./doctype/SystemSettings/SystemSettings.js'),
ToDo: require('./doctype/ToDo/ToDo.js'),
User: require('./doctype/User/User.js'),
UserRole: require('./doctype/UserRole/UserRole.js'),
File: require('./doctype/File/File.js'),
}
}
models: {
FilterItem: require('./doctype/FilterItem/FilterItem.js'),
FilterGroup: require('./doctype/FilterGroup/FilterGroup.js'),
FilterSelector: require('./doctype/FilterSelector/FilterSelector.js'),
NumberSeries: require('./doctype/NumberSeries/NumberSeries.js'),
PrintFormat: require('./doctype/PrintFormat/PrintFormat.js'),
Role: require('./doctype/Role/Role.js'),
Session: require('./doctype/Session/Session.js'),
SingleValue: require('./doctype/SingleValue/SingleValue.js'),
SystemSettings: require('./doctype/SystemSettings/SystemSettings.js'),
ToDo: require('./doctype/ToDo/ToDo.js'),
User: require('./doctype/User/User.js'),
UserRole: require('./doctype/UserRole/UserRole.js'),
File: require('./doctype/File/File.js'),
Kanban: require('./doctype/Kanban/Kanban.js'),
KanbanList: require('./doctype/KanbanList/KanbanList.js'),
KanbanCard: require('./doctype/KanbanCard/KanbanCard.js')
}
};
6 changes: 4 additions & 2 deletions ui/components/Form/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ export default {
try {
this.doc = await frappe.getDoc(this.doctype, this.name);

if (this.doc._notInserted && this.meta.fields.map(df => df.fieldname).includes('name')) {
if (
this.doc._notInserted &&
this.meta.fields.map(df => df.fieldname).includes('name')
) {
// For a user editable name field,
// it should be unset since it is autogenerated
this.doc.set('name', '');
Expand Down Expand Up @@ -89,7 +92,6 @@ export default {
}

this.$emit('save', this.doc);

} catch (e) {
console.error(e);
return;
Expand Down
11 changes: 6 additions & 5 deletions ui/components/Form/FormLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export default {
const dataObj = {};
for (let df of this.fields) {
dataObj[df.fieldname] = this.doc[df.fieldname];

if (df.fieldtype === 'Table' && !dataObj[df.fieldname]) {
dataObj[df.fieldname] = [];
}
Expand Down Expand Up @@ -80,15 +79,17 @@ export default {

if (!layout) {
const fields = this.fields.map(df => df.fieldname);
layout = [{
columns: [{ fields }]
}];
layout = [
{
columns: [{ fields }]
}
];
}

if (Array.isArray(layout)) {
layout = {
sections: layout
}
};
}
return layout;
}
Expand Down
194 changes: 194 additions & 0 deletions ui/components/Kanban/CreateKanbanModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<template>
<custom-modal v-if="showCreateKanbanModal" :header="'Create Kanban for' + refdoctype" @closeModal="closeCreateKanbanModal">
<form>
<div :key="key" v-for="(field,key) in fields">
<label>
{{field.label}}
<input class="form-control" :disabled="disabled(field)" v-if="fieldtypes[field.fieldtype] === 'text'" type="text" v-model="configdata[field.fieldname]" />
<select class="form-control" v-if="fieldtypes[field.fieldtype] === 'select'" v-model="configdata[field.fieldname]" @change="updateLists">
<option v-for="(option) in options[field.fieldname]" :key="option" :value="option">
{{option}}
</option>
</select>
</label>
</div>
<div>
<button class="btn btn-primary btn-lg" @click="submit" type="submit" value="submit" :disabled="submitdisabled">
submit
</button>
</div>
</form>
</custom-modal>
</template>

<script>
import frappe from 'frappejs';
import FormLayout from '../Form/FormLayout';
import Observable from '../../../utils/observable';
import CustomModal from './CustomModal';

export default {
name: 'CreateKanbanModal',
props: ['refdoctype', 'showCreateKanbanModal'],
components: {
CustomModal
},
data() {
return {
configdata: {
kanbanname: '',
referencedoctype: this.refdoctype,
sortby: '',
lists: [],
titlefield: ''
}
};
},
created() {
this.doc = new Observable();
},
computed: {
meta() {
return frappe.getMeta('Kanban');
},
fields() {
console.log(this.meta.fields);
return this.meta.fields;
},
fieldtypes() {
return {
Select: 'select',
Data: 'text',
Table: 'table'
};
},
options() {
const refdoctypemeta = frappe.getMeta(this.refdoctype);
const options = {
sortby: [],
titlefield: []
};
refdoctypemeta.fields.forEach(field => {
if (field.fieldtype === 'Select') options.sortby.push(field.fieldname);
else if (field.fieldtype === 'Data')
options.titlefield.push(field.fieldname);
});
return options;
},
disabled() {
return field => {
return field.fieldname === 'referencedoctype' ? true : false;
};
},
submitdisabled() {
let isInvalid = false;
const configFields = Object.keys(this.configdata);
configFields.forEach(field => {
if (this.configdata[field] === '') isInvalid = true;
});
return isInvalid;
}
},
methods: {
async submit(e) {
e.preventDefault();
const newDoc = await frappe.getNewDoc('Kanban');
console.log('here', newDoc);
Object.keys(this.configdata).forEach(field => {
newDoc[field] = this.configdata[field];
});
newDoc._meta.fields.forEach(field => {
if (field.fieldtype === 'Select') field.options = this.options;
});
console.log('newdoc', newDoc);
newDoc.insert();
this.initCards();
this.closeCreateKanbanModal();
},
async updateLists() {
const refdoctypemeta = await frappe.getMeta(this.refdoctype);
const sortField = refdoctypemeta.fields.find(
field =>
field.fieldtype === 'Select' &&
field.fieldname === this.configdata.sortby
);
const columns = sortField.options.map(column => {
const newColumn = frappe.getNewDoc('KanbanList');
return newColumn.then(doc => {
doc.listname = column;
doc.archived = false;
return doc;
});
});
console.log(columns);
Promise.all(columns).then(kanbanColumns => {
console.log(kanbanColumns);
this.configdata.lists = kanbanColumns;
console.log(this.configdata.lists);
});
},
async initCards() {
const refdoctypemeta = await frappe.getMeta(this.refdoctype);
const refdoctypefields = refdoctypemeta.fields.map(
field => field.fieldname
);
console.log('refdoctypefields', refdoctypefields);
const allItems = await frappe.db.getAll({
doctype: this.refdoctype,
fields: ['name', ...refdoctypefields]
});
console.log('allitems', allItems);
console.log(this.configdata.kanbanname);
const board = await frappe.db.getAll({
doctype: 'Kanban',
fields: ['name'],
filters: { kanbanname: this.configdata.kanbanname }
});
console.log(board);
allItems.forEach(item => {
const newCard = frappe.getNewDoc('KanbanCard');
newCard.then(doc => {
doc.boardname = board[0].name;
doc.referencedoctype = this.configdata.referencedoctype;
doc.listname = item[this.configdata.sortby];
doc.cardtitle = item[this.configdata.titlefield];
doc.insert();
});
});
},
closeCreateKanbanModal() {
this.showCreateKanbanModal = false;
}
}
};
</script>

<style>
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: table;
}

.modal-wrapper {
display: table-cell;
vertical-align: middle;
}

.modal-container {
max-width: 80%;
min-height: 30%;
background-color: white;
margin: 0 auto;
border-radius: 5px;
}

.modal-body {
display: block;
}
</style>
Loading