Skip to content

Commit

Permalink
✨ implemented Feature request: Custom fields for Tasks #198
Browse files Browse the repository at this point in the history
✨ implemented Feature request: Hide 'Resource' field/column option #197
⬆️ updated packaged dependencies
  • Loading branch information
faburem committed Dec 21, 2023
1 parent 81f17df commit bb44170
Show file tree
Hide file tree
Showing 21 changed files with 206 additions and 79 deletions.
26 changes: 16 additions & 10 deletions imports/api/customfields/server/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const addCustomField = new ValidatedMethod({
if (await CustomFields.findOneAsync({ name })) {
throw new Meteor.Error('error-custom-field-exists', 'Custom field already exists', { method: 'addCustomField' })
}
if (type === 'global_setting' && await Globalsettings.findOneAsync({ name })) {
if (classname === 'global_setting' && await Globalsettings.findOneAsync({ name })) {
throw new Meteor.Error('error-global-setting-exists', 'Global setting already exists', { method: 'addCustomField' })
}
const customField = {
Expand All @@ -53,7 +53,9 @@ const addCustomField = new ValidatedMethod({
category,
type,
}
await Globalsettings.insertAsync(globalsetting)
if (classname === 'global_setting') {
await Globalsettings.insertAsync(globalsetting)
}
return customField
},
})
Expand All @@ -78,7 +80,9 @@ const removeCustomField = new ValidatedMethod({
if (!customfield) {
throw new Meteor.Error('error-custom-field-not-found', 'Custom field not found', { method: 'removeCustomField' })
}
await Globalsettings.removeAsync({ name: customfield.name })
if (customfield.classname === 'global_setting') {
await Globalsettings.removeAsync({ name: customfield.name })
}
await CustomFields.removeAsync({ _id })
return true
},
Expand Down Expand Up @@ -121,13 +125,15 @@ const updateCustomField = new ValidatedMethod({
},
},
)
await Globalsettings.updateAsync({ name: customfield.name }, {
$set: {
description: desc,
type,
category,
},
})
if (customfield.classname === 'global_setting') {
await Globalsettings.updateAsync({ name: customfield.name }, {
$set: {
description: desc,
type,
category,
},
})
}
return true
},
})
Expand Down
3 changes: 3 additions & 0 deletions imports/api/globalsettings/globalsettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,7 @@ defaultSettings.push({
defaultSettings.push({
name: 'openai_apikey', description: 'settings.openai', type: 'password', value: '', category: 'settings.categories.interfaces',
})
defaultSettings.push({
name: 'showResourceInDetails', description: 'settings.show_resource_in_details', type: 'checkbox', value: true, category: 'settings.categories.customization',
})
export { defaultSettings, Globalsettings }
32 changes: 14 additions & 18 deletions imports/api/tasks/server/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ const insertProjectTask = new ValidatedMethod({
start: Date,
end: Date,
dependencies: Match.Optional([String]),
customfields: Match.Optional(Object),
})
},
mixins: [authenticationMixin, transactionLogMixin],
async run({
projectId, name, start, end, dependencies,
projectId, name, start, end, dependencies, customfields,
}) {
await Tasks.insertAsync({
projectId,
name,
start,
end,
dependencies,
...customfields,
})
},
})
Expand All @@ -58,28 +60,22 @@ const updateTask = new ValidatedMethod({
start: Match.Optional(Date),
end: Match.Optional(Date),
dependencies: Match.Optional([String]),
customfields: Match.Optional(Object),
})
},
mixins: [authenticationMixin, transactionLogMixin],
async run({
taskId, name, start, end, dependencies,
taskId, name, start, end, dependencies, customfields,
}) {
const updatedTask = {
}
if (name) updatedTask.name = name
if (start) updatedTask.start = start
if (end) updatedTask.end = end
if (dependencies) updatedTask.dependencies = dependencies
if (updatedTask) {
await Tasks.updateAsync(taskId, {
$set: {
name,
start,
end,
dependencies,
},
})
}
await Tasks.updateAsync(taskId, {
$set: {
name,
start,
end,
dependencies,
...customfields,
},
})
},
})
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<option value="project">{{t "globals.project"}}</option>
<option value="time_entry">{{t "globals.time_entry"}}</option>
<option value="global_setting">{{t "globals.global_setting"}}</option>
<option value="task">{{t "globals.task"}}</option>
</select>
<label for="customfieldClassname" class="form-label">{{t "globals.class"}}</label>
</div>
Expand Down
32 changes: 25 additions & 7 deletions imports/ui/pages/details/components/dailytimetable.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,23 @@ Template.dailytimetable.onRendered(() => {
width: 2,
format: addToolTipToTableCell,
},
{
]
if (getGlobalSetting('showResourceInDetails')) {
columns.push({
name: t('globals.resource'),
editable: false,
width: 2,
format: addToolTipToTableCell,
},
})
}
columns.push(
{
name: getUserTimeUnitVerbose(),
editable: false,
width: 1,
format: numberWithUserPrecision,
},
]
)
if (!templateInstance.datatable) {
import('frappe-datatable/dist/frappe-datatable.css').then(() => {
import('frappe-datatable').then((datatable) => {
Expand Down Expand Up @@ -143,9 +147,16 @@ Template.dailytimetable.events({
if (Meteor.user()) {
unit = getUserTimeUnitVerbose()
}
const csvArray = [`\uFEFF${t('globals.date')},${t('globals.project')},${t('globals.resource')},${unit}\r\n`]
let csvArray = [`\uFEFF${t('globals.date')},${t('globals.project')},${t('globals.resource')},${unit}\r\n`]
if (!getGlobalSetting('showResourceInDetails')) {
csvArray = [`\uFEFF${t('globals.date')},${t('globals.project')},${unit}\r\n`]
}
for (const timeEntry of templateInstance.dailyTimecards.get().map(dailyTimecardMapper)) {
csvArray.push(`${dayjs.utc(timeEntry.date).format(getGlobalSetting('dateformat'))},${timeEntry.projectId},${timeEntry.userId},${timeEntry.totalHours}\r\n`)
if (getGlobalSetting('showResourceInDetails')) {
csvArray.push(`${dayjs.utc(timeEntry.date).format(getGlobalSetting('dateformat'))},${timeEntry.projectId},${timeEntry.userId},${timeEntry.totalHours}\r\n`)
} else {
csvArray.push(`${dayjs.utc(timeEntry.date).format(getGlobalSetting('dateformat'))},${timeEntry.projectId},${timeEntry.totalHours}\r\n`)
}
}
saveAs(new Blob(csvArray, { type: 'text/csv;charset=utf-8;header=present' }), `titra_daily_time_${templateInstance.data.period.get()}.csv`)
},
Expand All @@ -155,9 +166,16 @@ Template.dailytimetable.events({
if (Meteor.user()) {
unit = getUserTimeUnitVerbose()
}
const data = [[t('globals.date'), t('globals.project'), t('globals.resource'), unit]]
let data = [[t('globals.date'), t('globals.project'), t('globals.resource'), unit]]
if (!getGlobalSetting('showResourceInDetails')) {
data = [[t('globals.date'), t('globals.project'), unit]]
}
for (const timeEntry of templateInstance.dailyTimecards.get().map(dailyTimecardMapper)) {
data.push([dayjs.utc(timeEntry.date).format(getGlobalSetting('dateformat')), timeEntry.projectId, timeEntry.userId, timeEntry.totalHours])
if (getGlobalSetting('showResourceInDetails')) {
data.push([dayjs.utc(timeEntry.date).format(getGlobalSetting('dateformat')), timeEntry.projectId, timeEntry.userId, timeEntry.totalHours])
} else {
data.push([dayjs.utc(timeEntry.date).format(getGlobalSetting('dateformat')), timeEntry.projectId, timeEntry.totalHours])
}
}
saveAs(new NullXlsx('temp.xlsx', { frozen: 1, filter: 1 }).addSheetFromData(data, 'daily').createDownloadUrl(), `titra_daily_time_${templateInstance.data.period.get()}.xlsx`)
},
Expand Down
3 changes: 0 additions & 3 deletions imports/ui/pages/details/components/detailtimetable.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@
{{/if}}
</div>
</div>
<!-- <div class="col-xl-3 col-lg-3 col-6 mt-2">
{{>limitpicker}}
</div> -->
<div class="col-lg-3 col-lg-2 offset-lg-0 col-sm-12 mt-2 ms-auto">
<div class="input-group">
<input type="search" class="js-search form-control float-end" placeholder='{{t "details.search"}}' aria-label='{{t "details.search"}}'/>
Expand Down
31 changes: 21 additions & 10 deletions imports/ui/pages/details/components/detailtimetable.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,17 @@ function detailedDataTableMapper(entry, forExport) {
const project = Projects.findOne({ _id: entry.projectId })
let mapping = [entry.projectId,
dayjs.utc(entry.date).format(getGlobalSetting('dateformat')),
entry.task.replace(/^=/, '\\='),
entry.userId]
entry.task.replace(/^=/, '\\=')]
if (getGlobalSetting('showResourceInDetails')) {
mapping.push(entry.userId)
}
if (forExport) {
mapping = [project?.name ? project.name : '',
dayjs.utc(entry.date).format(getGlobalSetting('dateformat')),
entry.task.replace(/^=/, '\\='),
projectResources.findOne() ? projectResources.findOne({ _id: entry.userId })?.name : '']
entry.task.replace(/^=/, '\\=')]
if (getGlobalSetting('showResourceInDetails')) {
mapping.push(projectResources.findOne() ? projectResources.findOne({ _id: entry.userId })?.name : '')
}
}
if (getGlobalSetting('showCustomFieldsInDetails')) {
if (CustomFields.find({ classname: 'time_entry' }).count() > 0) {
Expand Down Expand Up @@ -155,13 +159,15 @@ Template.detailtimetable.onRendered(() => {
},
{
name: t('globals.task'), id: 'task', editable: false, format: addToolTipToTableCell,
},
{
}]
if (getGlobalSetting('showResourceInDetails')) {
columns.push({
name: t('globals.resource'),
id: 'userId',
editable: false,
format: (value) => addToolTipToTableCell(projectResources.findOne() ? projectResources.findOne({ _id: value })?.name : ''),
}]
})
}
if (getGlobalSetting('showCustomFieldsInDetails')) {
let customFieldColumnType = 'desc'
if (getGlobalSetting('showNameOfCustomFieldInDetails')) {
Expand Down Expand Up @@ -425,8 +431,10 @@ Template.detailtimetable.helpers({
Template.detailtimetable.events({
'click .js-export-csv': (event, templateInstance) => {
event.preventDefault()
const csvArray = [`\uFEFF${t('globals.project')},${t('globals.date')},${t('globals.task')},${t('globals.resource')}`]

const csvArray = [`\uFEFF${t('globals.project')},${t('globals.date')},${t('globals.task')}`]
if (getGlobalSetting('showResourceInDetails')) {
csvArray[0] = `${csvArray[0]},${t('globals.resource')}`
}
if (getGlobalSetting('showCustomFieldsInDetails')) {
if (CustomFields.find({ classname: 'time_entry' }).count() > 0) {
csvArray[0] = `${csvArray[0]},${CustomFields.find({ classname: 'time_entry' }).fetch().map((field) => field[customFieldType]).join(',')}`
Expand Down Expand Up @@ -475,7 +483,10 @@ Template.detailtimetable.events({
},
'click .js-export-xlsx': (event, templateInstance) => {
event.preventDefault()
const data = [[t('globals.project'), t('globals.date'), t('globals.task'), t('globals.resource')]]
const data = [[t('globals.project'), t('globals.date'), t('globals.task')]]
if (getGlobalSetting('showResourceInDetails')) {
data[0].push(t('globals.resource'))
}
if (getGlobalSetting('showCustomFieldsInDetails')) {
if (CustomFields.find({ classname: 'time_entry' }).count() > 0) {
for (const customfield of CustomFields.find({ classname: 'time_entry' }).fetch()) {
Expand Down
8 changes: 5 additions & 3 deletions imports/ui/pages/details/components/filterbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
<div class="col-sm-3 mb-2 mb-md-0">
{{>periodpicker}}
</div>
<div class="col-sm-3 mb-2 mb-md-0">
{{>multiselectfilter items=resources name="resourceselect" all="resource.all" label="globals.resource"}}
</div>
{{#if getGlobalSetting 'showResourceInDetails'}}
<div class="col-sm-3 mb-2 mb-md-0">
{{>multiselectfilter items=resources name="resourceselect" all="resource.all" label="globals.resource"}}
</div>
{{/if}}
<div class="col-sm-3">
{{>multiselectfilter items=customers name="customerselect" all="customer.all" label="globals.customer"}}
</div>
Expand Down
40 changes: 30 additions & 10 deletions imports/ui/pages/details/components/periodtimetable.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
addToolTipToTableCell,
totalHoursForPeriodMapper,
waitForElement,
getGlobalSetting,
} from '../../../../utils/frontend_helpers.js'

Template.periodtimetable.onCreated(function periodtimetableCreated() {
Expand Down Expand Up @@ -64,13 +65,17 @@ Template.periodtimetable.onRendered(() => {
}
const columns = [
{ name: t('globals.project'), editable: false, format: addToolTipToTableCell },
{ name: t('globals.resource'), editable: false, format: addToolTipToTableCell },
{
name: getUserTimeUnitVerbose(),
editable: false,
format: numberWithUserPrecision,
},
]
if (getGlobalSetting('showResourceInDetails')) {
columns.push({
name: t('globals.resource'), editable: false, format: addToolTipToTableCell,
})
}
columns.push({
name: getUserTimeUnitVerbose(),
editable: false,
format: numberWithUserPrecision,
})
if (!templateInstance.datatable) {
import('frappe-datatable/dist/frappe-datatable.css').then(() => {
import('frappe-datatable').then((datatable) => {
Expand Down Expand Up @@ -118,17 +123,32 @@ Template.periodtimetable.helpers({
Template.periodtimetable.events({
'click .js-export-csv': (event, templateInstance) => {
event.preventDefault()
const csvArray = [`\uFEFF${t('globals.project')},${t('globals.resource')},${getUserTimeUnitVerbose()}\r\n`]
let csvArray = [`\uFEFF${t('globals.project')},${t('globals.resource')},${getUserTimeUnitVerbose()}\r\n`]
if (!getGlobalSetting('showResourceInDetails')) {
csvArray = [`\uFEFF${t('globals.project')},${getUserTimeUnitVerbose()}\r\n`]
}
for (const timeEntry of templateInstance.periodTimecards.get().map(totalHoursForPeriodMapper)) {
csvArray.push(`${timeEntry.projectId},${timeEntry.userId},${timeEntry.totalHours}\r\n`)
if (getGlobalSetting('showResourceInDetails')) {
csvArray.push(`${timeEntry.projectId},${timeEntry.userId},${timeEntry.totalHours}\r\n`)
} else {
csvArray.push(`${timeEntry.projectId},${timeEntry.totalHours}\r\n`)
}
}
saveAs(new Blob(csvArray, { type: 'text/csv;charset=utf-8;header=present' }), `titra_total_time_${templateInstance.data.period.get()}.csv`)
},
'click .js-export-xlsx': (event, templateInstance) => {
event.preventDefault()
const data = [[t('globals.project'), t('globals.resource'), getUserTimeUnitVerbose()]]
const data = [[t('globals.project')]]
if (getGlobalSetting('showResourceInDetails')) {
data[0].push(t('globals.resource'))
}
data[0].push(getUserTimeUnitVerbose())
for (const timeEntry of templateInstance.periodTimecards.get().map(totalHoursForPeriodMapper)) {
data.push([timeEntry.projectId, timeEntry.userId, timeEntry.totalHours])
if (getGlobalSetting('showResourceInDetails')) {
data.push([timeEntry.projectId, timeEntry.userId, timeEntry.totalHours])
} else {
data.push([timeEntry.projectId, timeEntry.totalHours])
}
}
saveAs(new NullXlsx('temp.xlsx', { frozen: 1, filter: 1 }).addSheetFromData(data, 'total time').createDownloadUrl(), `titra_total_time_${templateInstance.data.period.get()}.xlsx`)
},
Expand Down
24 changes: 24 additions & 0 deletions imports/ui/pages/overview/editproject/components/taskModal.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ <h5 class="modal-title">
<!-- <input type="text" id="dependencies" name="dependencies" class="form-control" value="{{dependencies}}"/> -->
<label class="form-label" for="dependencies">{{t "task.dependencies"}}</label>
</div>
{{#if customfields }}
<div class="card">
<div class="card-header">{{t "administration.customfields"}}</div>
<div class="card-block">
{{#each customfield in customfields}}
{{#if customfield.possibleValues}}
<div class="mb-3 form-floating">
<select name="{{replaceSpecialChars customfield.name}}" id="{{replaceSpecialChars customfield.name}}" class="form-control js-customfield">
{{#each pv in customfield.possibleValues}}
<option value="{{pv}}">{{pv}}</option>
{{/each}}
</select>
<label class="form-label" for="{{replaceSpecialChars customfield.name}}">{{customfield.desc}}</label>
</div>
{{else}}
<div class="form-floating">
<input class="form-control js-customfield" name="{{replaceSpecialChars customfield.name}}" id="{{replaceSpecialChars customfield.name}}" type="{{customfield.type}}" value="{{getCustomFieldValue customfield.name}}" placeholder="{{customfield.desc}}"/>
<label class="form-label" for="{{replaceSpecialChars customfield.name}}">{{customfield.desc}}</label>
</div>
{{/if}}
{{/each}}
</div>
</div>
{{/if}}
</form>
</div>
<div class="modal-footer">
Expand Down
Loading

0 comments on commit bb44170

Please sign in to comment.