UNPKG

json-object-editor

Version:

JOE the Json Object Editor | Platform Edition

505 lines (471 loc) 23 kB
const { priority } = require("../fields/core"); var project = function(){return{ title : '${name}', //info:'The main productiivity tool of JOE allow for tasks and projects to be assigned tracked and monitored as they pass through statuses and workflows. Use the <b>projectboard</b> app for a more focused view.', info:"Within a project you can manage team members and goals, assign a task to a team member, etc.", listView:{ title: function(proj){ return '<joe-fright>${RUN[_joe.SERVER.User.Render.cubes;${members};]}</joe-fright>'+ ((proj.group && '<joe-subtext>'+_joe.getDataItemProp(proj.group,'group')+'</joe-subtext>') ||'')+ '<joe-title>${name}</joe-title>' +'<joe-subtitle>${info}</joe-subtitle>'; }, listWindowTitle: 'Projects' }, itemMenu:function(item){ var menu = [ { /*name:'<joe-schema-icon title="standard report">'+_joe.schemas.report.menuicon+'</joe-schema-icon>',*/ name:_joe.schemas.report.menuicon,//+'<div class="joe-subtext">report</div>', action:'window.open(\'/API/plugin/reportbuilder/standard?itemid='+item._id+'\')'} ]; var subtasks = _joe.Data.task.where({$and:[{project:item._id},{complete:{$in:[false,'false',undefined]}}]}); if(subtasks.length){ menu.push({name:'<div style="line-height:1;"><big>'+subtasks.length+'</big> <div class="joe-subtext">tasks</div>', action:'goJoe(_joe.Data.task,{schema:\'task\',subset:\''+item.name+'\'})'}); } return menu; }, stripeColor:function(item){ if(item.priority && item.priority < 100){ return { title:`P${item.priority}`, color:_joe.Colors.priority[item.priority] }; } }, hideNumbers:true, sorter:[{display:'priority',field:'priority'},'name','status'], // Curated summary for agents summary:{ description:'Container for organizing work, teams, and timelines.', purpose:'Projects group tasks, people, and timelines. A project typically has one status and group, many members (users), and many tags. Tasks reference their owning project. Use projects for planning, tracking, and reporting across related tasks.', labelField:'name', defaultSort:{ field:'joeUpdated', dir:'desc' }, searchableFields:['name','info','description','problem_statement','_id'], allowedSorts:['priority','status','name','joeUpdated','created','start_dt','end_dt'], relationships:{ outbound:[ { field:'status', targetSchema:'status', cardinality:'one' }, { field:'group', targetSchema:'group', cardinality:'one' }, { field:'members', targetSchema:'user', cardinality:'many' }, { field:'tags', targetSchema:'tag', cardinality:'many' }, { field:'aligned_goals', targetSchema:'goal', cardinality:'many' } ], inbound:{ graphRef:'server/relationships.graph.json' } }, joeManagedFields:['created','joeUpdated'], fields:[ { name:'_id', type:'string', required:true }, { name:'itemtype', type:'string', required:true, const:'project' }, { name:'name', type:'string', required:true }, { name:'info', type:'string' }, { name:'description', type:'string' }, { name:'problem_statement', type:'string' }, { name:'status', type:'string', isReference:true, targetSchema:'status' }, { name:'group', type:'string', isReference:true, targetSchema:'group' }, { name:'members', type:'string', isArray:true, isReference:true, targetSchema:'user' }, { name:'tags', type:'string', isArray:true, isReference:true, targetSchema:'tag' }, { name:'aligned_goals', type:'string', isArray:true, isReference:true, targetSchema:'goal' }, { name:'goals', type:'objectList' }, { name:'start_dt', type:'string', format:'date-time' }, { name:'end_dt', type:'string', format:'date-time' }, { name:'phases', type:'objectList' }, { name:'subtasks', type:'objectList' }, { name:'considerations_list', type:'objectList' }, { name:'deliverables', type:'objectList' }, { name:'key_features', type:'objectList' }, { name:'marketing_tactics', type:'objectList' }, { name:'links', type:'objectList' }, { name:'pricing', type:'objectList' }, { name:'files', type:'string', isArray:true }, { name:'priority', type:'number', enumValues:[1,2,3,1000] }, { name:'complete', type:'boolean' }, { name:'joeUpdated', type:'string', format:'date-time', required:true }, { name:'created', type:'string', format:'date-time', required:true } ] }, fields:function(){ var fields=[ {sidebar_start:'left'}, {section_start:'ai'}, 'objectChat', 'listConversations', {name:'ai_summary',display:'AI Summary',type:'rendering',comment:'Summarize the project in a few sentences.', 'ai':{ label:'summarize with ai', prompt:'Summarize the project in a few sentences.' } }, {section_end:'ai'}, {sidebar_end:'left'}, {section_start:'overview'}, 'name', {extend:'info',specs:{comment:'What\'s the problem solved or reason for this project?'}}, 'description', {name:'highlights',type:'objectList', properties:['name' ],hideHeadings:true}, {section_end:'overview'}, {section_start:'objective'}, {name:'problem_statement', display:"Problem Statement",type:'wysiwyg'}, {name:'aligned_goals',display:'Aligned Goals',comment:'how does this latter up to bigger goals?', type:'group', //values:'goal', values:function(i){ if(!i.group){ return _joe.Data.goal; }else{ return _joe.Data.goal.where({group:i.group}); } }, icon:'goal',blank:true}, {name:'goals',type:'objectList', template:function(obj,subobj){ return '<joe-title >${name}</joe-title>' +'<joe-subtext>${metric}</joe-subtext>'; }, properties:['name',{name:'metric',width:'40%'}]}, {section_end:'objective'}, {section_start:'timeline'}, {name:'start_dt',display:'kickoff',type:'date', native:true,width:'50%'}, {name:'end_dt',display:'completion',type:'date', native:true,width:'50%'}, {name:'phases',type:'objectList', template:function(obj,subobj){ //var done = (subobj.sub_complete)?'joe-strike':''; var t = '<joe-title >${name} '+(subobj.hasOwnProperty('id') && '['+subobj.id+']'||'')+'</joe-title>' +((subobj.start_date || subobj.end_date) && '<joe-subtext>${start_date} - ${end_date}</joe-subtext>'||''); return t; }, properties:[ {name:'name'}, {name:'id',width:'120px'}, {name:'start_date', display:'start', type:'date', native:true,width:'80px'}, {name:'end_date', display:'end', type:'date',native:true,width:'80px'} ], hideHeadings:true}, {name:'subtasks',comment:'steps to be completed at the PO level',type:'objectList', template:function(obj,subobj){ var done = (subobj.sub_complete)?'joe-strike':''; return `<joe-full-right>${subobj.sub_duedate || ''} ${(subobj.milestone && `<b>[${subobj.milestone}]</b>`)||''}</joe-full-right>` +'<joe-title class="'+done+'">${name}</joe-title>'; }, properties:[ {name:'name'}, {name:'milestone',width:'100px'}, {name:'sub_duedate', display:'due', type:'date',width:'50px',native:true}, {name:'sub_complete',display:'done',type:'boolean',width:'50px'} ] }, {section_end:'timeline'}, {section_start:'thoughts',collapsed:function(i){ return !(i.notes || (i.considerations_list && i.considerations_list.length)) }}, {name:'considerations_list',display:'list',type:'objectList',properties:[ 'name', {name:'type',type:'select', width:'120px',values:['resource','dependency','assumption','risk','question','learning','opportunity','other','']} ]}, {label:'notes'}, {name:'notes',type:'wysiwyg',label:false}, //'notes', {name:'addnote',label:false, type:'create',schema:'note'}, 'references', {section_end:'thoughts'}, {section_start:'deliverables',collapsed:function(i){ return !((i.key_features && i.key_features.length) || (i.deliverables && i.deliverables.length)) }}, {name:'deliverables',type:'objectList',properties:[ {name:'name'}, {name:'milestone',width:'100px', // autocomplete:{text:true,properties:{ // }},values:function(p){ // return ['2021.1','2021.2']; // } } ]}, {name:'key_features',display:'key features',type:'objectList',properties:[ {name:'name'}, {name:'status',type:'select',values:['mvp','stretch','contingency','backlog','in-progress','complete',''],width:'100px'}, {name:'effort',type:'number',width:'60px'}, {name:'impact',type:'number',width:'60px'}, {name:'milestone',width:'100px'} ]}, {section_end:'deliverables'}, {section_start:'marketing',collapsed:function(i){ return !(i.marketing_tactics && i.marketing_tactics.length) }}, {name:'marketing_tactics',display:'Communication Ideas',type:'objectList',properties:[ {name:'name'}, {name:'responsible',width:'200px'} ]}, {name:'links',type:'objectList',properties:[ {name:'name',width:'240px'}, {name:'url'} ]}, {section_end:'marketing'}, {section_start:'team',collapsed:function(i){ return (i.group && (i.members && i.members.length)) }}, 'group', {extend:'members',specs:{width:'50%'}}, 'people', {label:'comments'}, 'user_comments', {section_end:'team'}, {section_start:'cost',collapsed:function(i){ return !((i.pricing && i.pricing.length)) }}, {name:'pricing',type:'objectList', template:'<joe-title>${line_item}</joe-title>', properties:[ {name:'line_item',display:'line item'}, {name:'amount',type:'number',width:"120px"}, {name:'quantity',type:'number',width:"100px"}, {name:'paid',type:'date',width:"120px"}, {name:'invoice',type:'number',width:"100px"}, ] }, {section_end:'cost'}, // {name:'other',type:'subobject',value:{name:'no name'}}, {sidebar_start:'right'}, 'status', 'priority', 'reports', {name:'complete',display:'complete',type:'boolean',label:'completed projects are hidden'}, {section_start:'tasks', collapsed:true}, {name:'addtask',label:false, type:'create',schema:'task', overwrites:function(item){return {project:item._id};} }, 'tasks', {section_end:'tasks'}, {section_start:'tags', collapsed:function(i){ return (i.tags || i.tags.length) }}, 'tags', {section_end:'tags'}, /* {section_start:'related',collapsed:function(proj){ if((proj.notes && proj.notes.length) || (proj.references && proj.references.length)){ return false; } return true; }}, {section_end:'related'}, */ {section_start:'files',collapsed:function(item){ return !(item.files && item.files.length); }}, {name:'files',type:'uploader',allowmultiple:true, height:'300px',comment:'drag files here to upload', onConfirm:_joe.SERVER.Plugins.awsFileUpload}, {section_end:'files'}, {section_start:'access',collapsed:true}, '_protected', {section_end:'access'}, {sidebar_end:'right'}, {section_start:'charts'}, {name:'task_breakdown',display:'task breakdown',type:'content',run:function(item){ var html ='<div id="task_breakdown_holder"></div>' html+='<script>_joe.schemas.project.methods.renderTaskBreakdown("'+item._id+'");</script>'; return html; }}, {section_end:'charts'}, {section_start:'system',collapsed:true}, '_id','created','itemtype', {section_end:'system'}, ] return fields; }, subsets:function(){ var sets = [ {name:'My Projects',default:true,filter:{complete:{$nin:[true]},members:{$in:[_joe.User._id]}}}, {name:'Completed',filter:{complete:{$in:[true]}}}, {group_start:'groups',collapsed:false} ]; _joe.Data.group.sortBy('name').map(function(g){ sets.push({name:g.name,filter:{group:g._id}}); }) sets.push({group_end:'groups'}); return sets; }, filters:function(){ var colors = [{}, {stripecolor:'#cc4500'},{stripecolor:'#FFee33'},{stripecolor:'#acf'}] var stats = [].concat( _joe.Filter.Options.status({group:'statuses',collapsed:true}), _joe.Filter.Options.tags({group:'tags',collapsed:true}), ); stats.push({group_start:'goals',collapsed:true}) _joe.Data.goal.sortBy('!priority','code').map(function(g){ let color; stats.push({ name:(g.code||g.name), filter:{aligned_goals:{$in:[g._id]}}, stripecolor:colors[(g.priority|| 0)].stripecolor }); }) stats.push({name:'none',filter:{aligned_goals:{$size: 0}}}); stats.push({group_end:'goals'}); return stats; }, idprop : "_id", bgColor:function(item){ if(!item.status){ return ''; } var status = _joe.getDataItem(item.status,'status'); return {title:status.name,color:status.color} }, methods:{ renderTaskBreakdown:function(projectid,target){ var target = target ||'#task_breakdown_holder'; var project = _joe.getDataItem(projectid,'project'); capp && capp.Chart.byStatus('task',null,{ delay:true, target:target, filter:{project:projectid,complete:{$nin:['true',true]}}, onclick:function (d, i) { goJoe(_joe.getDataset('task')||[],{schema:'task',subset:project.name}); }, }); } }, onDemandExpander:true, itemExpander:function(item){ var html = ''; if(item.goals && item.goals.length){ html += '<joe-title>Goals</joe-title>'; item.goals.map(function(goal){ html+='<joe-itemmenu-section class="padded"><joe-title>'+goal.name+'</joe-title>\ <joe-subtitle>'+goal.metric+'</joe-subtitle></joe-itemmenu-section>'; }) html+=''; } var tasks = _joe.Data.task .where({$and:[{project:item._id},{complete:{$in:[false,'false',undefined]}}]}) .sortBy('project_phase,priority'); html+='<joe-title>'+tasks.length+' Tasks</joe-title>'; var sectionname = null; var newname; tasks.map(function(task){ if(sectionname != task.project_phase){ if(sectionname !== null){ html+='</joe-itemmenu-section>'; } sectionname = task.project_phase; html+='<joe-itemmenu-section data-secname="'+sectionname+'"> <joe-subtitle>'+(item.phases.where({id:sectionname})[0]||{name:'---'}).name+'</joe-subtitle>'; } html+=_joe.renderFieldListItem(task,'<joe-title>${name}</joe-title><joe-subtitle>${info}</joe-subtitle>\ <joe-fright>P${priority}</joe-fright>','task'); //item,contentTemplate,schema,specs }); if(sectionname !== null){ html+='</joe-itemmenu-section>'; } return html; }, menuicon:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="-256 -256 1536 1536"><path d="M896 193H640v-66c0-34.2-27.8-62-62-62H446c-34.2 0-62 27.8-62 62v66H128c-35.3 0-64 28.7-64 64v512c0 35.3 28.7 64 64 64h768c35.3 0 64-28.7 64-64V257c0-35.3-28.7-64-64-64zm-448-48c0-8.8 7.2-16 16-16h96c8.8 0 16 7.2 16 16v48H448v-48zm448 368H576v64H448v-64H128V257h64v192h640V257h64v256z"></path></svg>', report:{ name:'Project Standard', id:'standard', template:function(item,template_data){ var temp = "<report-section>\ <report-section-label>Details</report-section-label>\ ${if ('${this.PROJECT.description}')}\ <report-section-sublabel>Description</report-section-sublabel>\ ${this.PROJECT.description}\ ${end if}\ <report-section-sublabel>Goals</report-section-sublabel>\ ${foreach ${item} in ${this.PROJECT.goals}}\ <joe-title>${item.name}</joe-title>\ <joe-subtitle>${item.metric}</joe-subtitle>\ ${end foreach}\ </report-section>\ <report-section>\ <report-section-label>Charts</report-section-label>"; var complete=0; var incomplete=0; var tasks = JOE.Cache.search({itemtype:'task',project:item._id}); var users ={}; var status = {}; tasks.map(function(task){ if(task.complete){ complete++; }else if(task.status && task.status != undefined){ incomplete++; status[task.status] = status[task.status] || 0; status[task.status]++; }else{ incomplete++; status['none'] = status['none'] || 0; status['none']++; } }) temp+='<report-section-sublabel>'+Math.round(complete/tasks.length*100)+'% of '+tasks.length+' tasks completed. </report-section-sublabel>'; temp+="<div id='chart'></div>\ <script>\ var chart = c3.generate({\ bindto: '#chart',\ legend: {\ position: 'right'\ },\ data: {\ columns: [\ ['complete "+complete+"', "+complete+"]," for(var s in status){ var sts = JOE.Cache.findByID('status',s)||{name:'no status'}; temp+= "['"+sts.name+" "+status[s]+" ',"+status[s]+"],"; } temp+=" ],\ type : 'pie'\ }\ });\ </script>\ </report-section>\ <report-section name='tasks'>\ <a name='tasks'></a>\ ${script}\ var tasks = JOE.Cache.search({itemtype:'task',complete:{$in:[false,'false']},project:'${this.PROJECT._id}'});\ tasks.sortBy('priority');\ var project = JOE.Cache.findByID('project','${this.PROJECT._id}');\ var h = '<report-section-label>'+tasks.length+' Tasks</report-section-label>';\ function printTask(task){\ var ht = '';\ var action = '/API/plugin/reportbuilder/standard?itemid='+task._id;\ ht+='<a class=\"report-content-item\" href=\"'+action+'\">" +"<joe-title>'+task.name+'</joe-title><joe-subtitle>'+task.info+'</joe-subtitle></a>';\ return ht;\ }\ if(project.phases && project.phases.length){\ project.phases.map(function(phase){\ h+='<report-section-sublabel>'+phase.name+' '+(phase.id && ('['+phase.id+']') || '')+'</report-section-sublabel>';\ tasks.map(function(task){ \ if(task.project_phase == phase.id){\ h+=printTask(task);\ }\ });\ });\ h += '<report-section-sublabel>unphased</report-section-sublabel>';\ tasks.map(function(task){ \ if(task.project_phase !== 0 && !task.project_phase){\ h+=printTask(task);\ }\ });\ }\ else{\ var priority = '';\ tasks.sortBy('priority').map(function(task){ \ if(task.priority != priority){\ h+='<report-section-sublabel>priority '+task.priority+'</report-section-sublabel>';\ priority = task.priority;\ }\ h+=printTask(task);\ });\ }\ return h;\ ${end script}\ </report-section>"; return temp; } } }}; module.exports = project();