Skip to content

Commit d908173

Browse files
committed
Developers, developer permissions, admins assign developers, multiple developers, users variable, assignedTo commission variable, update commission schema, add userId to commission list view, minor changes, bug fixes
1 parent 593e9c4 commit d908173

8 files changed

Lines changed: 72 additions & 18 deletions

File tree

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ commtrackr.init({ // Initialize CommTracker with configurations
8787
value: 'Cancelled'
8888
}
8989
],
90-
disableFieldEditing: ['amount', 'currency'] // Array of field IDs that admins cannot edit
90+
disableFieldEditing: ['amount', 'currency'], // Array of field IDs that admins cannot edit
91+
users: 'users' // req.session object variable for all users array
9192
},
9293
fields: [
9394
{
@@ -120,7 +121,7 @@ commtrackr.init({ // Initialize CommTracker with configurations
120121
// data contains the updated commission object
121122
// The constant data.id contains the unique commission ID
122123
// Action metadata can be accessed via data.updatedAt, data.updatedBy, and data.sendEmail
123-
// Updated metadata can be accessed via data.user, data.amount, data.currency, data.date, data.status, and data.locked
124+
// Updated metadata can be accessed via data.user, data.amount, data.currency, data.date, data.status, data.locked, and data.assignedTo
124125
// Updated fields can be accessed via data.fields
125126
},
126127
sync: (req) => {
@@ -166,7 +167,7 @@ Session Example: `'John Doe'`
166167

167168
### role
168169

169-
`role` should contain the role of the user: 'admin', `dev`, or 'user'. This is used to control access to certain features and functionalities within CommTrackr.
170+
`role` should contain the role of the user: `'admin'`, `'dev'`, or `'user'`. This is used to control access to certain features and functionalities within CommTrackr.
170171

171172
Type: `String`
172173

@@ -190,7 +191,7 @@ Default:
190191

191192
### access
192193

193-
`access` can be used as an alternative to `role` for access control. It should contain numeric access levels. Use the `access` configuration to define which levels correspond to 'user', 'dev', and 'admin'.
194+
`access` can be used as an alternative to `role` for access control. It should contain numeric access levels. Use the `access` configuration to define which levels correspond to `'user'`, `'dev'`, and `'admin'`.
194195

195196
Type: `Array`
196197

@@ -234,7 +235,8 @@ Session Example:
234235
label: 'Link Label', // Link label
235236
url: 'http://example.com' // Link URL
236237
}
237-
]
238+
],
239+
assignedTo: ['dev1UserId', 'dev2UserId'] // Array of userIds of the developers assigned to this commission
238240
}
239241
]
240242
```
@@ -262,3 +264,11 @@ Default:
262264
```javascript
263265
[]
264266
```
267+
268+
### users
269+
270+
`users` should contain an array of all users and developers in the system. Each user object should have at least `userId` and defined matching `role` or `access.var` properties. `userName` property is recommended, but not required. This is used for assigning commissions to owners and developers.
271+
272+
Type: `Array`
273+
274+
Default: `'users'`

frontend/pages/admin.ejs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
<a class="button" href="<%= tenant.domain %><%= tenant.path %>/create">New Commission</a>
77
<div id="sync" class="button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23" fill="none">
88
<path d="M18.5949 5.16663C16.8618 3.32009 14.3989 2.16663 11.6666 2.16663C6.41992 2.16663 2.16663 6.41992 2.16663 11.6666C2.16663 16.9133 6.41992 21.1666 11.6666 21.1666C14.3989 21.1666 16.8618 20.0132 18.5949 18.1666M17.3236 7.98035L20.9804 7.98035L20.9804 4.3235" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
9-
</svg> Sync Existing</div>
9+
</svg> Sync</div>
1010
<a class="button" href="<%= tenant.domain %>">Back to <%= tenant.name %></a>
1111
</div>
1212
<div class="commissions">
1313
<% session[vars.commissions].forEach(commission => { %>
1414
<div class="commission">
1515
<h2><%= commission.id %></h2>
1616
<p><%= commission.status %></p>
17+
<p><%= commission.user %></p>
1718
<a class="button" href="<%= tenant.domain %><%= tenant.path %>/<%= commission.id %>">View</a>
1819
</div>
1920
<% }) %>

frontend/pages/commission.ejs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<%- include('head.ejs') %>
22
<img src="<%= tenant.logo %>" alt="<%= tenant.name %>">
33
<h1><%= title %></h1>
4-
<% if (commission.locked) { %><p>This commission is locked from editing.</p><% } %>
4+
<% if (commission.locked) { %><p>This commission is locked from <%= (role !== 'user') ? 'user' : '' %> editing.</p><% } %>
55
<table>
66
<tr>
77
<th>Status</th>
@@ -23,6 +23,10 @@
2323
<td><%= new Date(commission.date).toLocaleString() %></td>
2424
</tr>
2525
<% } %>
26+
<tr>
27+
<th>Assigned To</th>
28+
<td><%= (commission.assignedTo || []).map(assignedTo => vars.users.find(user => user[vars.userId] === assignedTo) ? (vars.users.find(user => user[vars.userId] === assignedTo)[vars.userName] || vars.users.find(user => user[vars.userId] === assignedTo)[vars.userId]) : null).filter(assignedTo => assignedTo).join(', ') || '-' %></td>
29+
</tr>
2630
</table>
2731
<table>
2832
<% for (const field of fields) { %>

frontend/pages/dev.ejs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
<a class="button" href="<%= tenant.domain %><%= tenant.path %>/create">New Commission</a>
77
<div id="sync" class="button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23" fill="none">
88
<path d="M18.5949 5.16663C16.8618 3.32009 14.3989 2.16663 11.6666 2.16663C6.41992 2.16663 2.16663 6.41992 2.16663 11.6666C2.16663 16.9133 6.41992 21.1666 11.6666 21.1666C14.3989 21.1666 16.8618 20.0132 18.5949 18.1666M17.3236 7.98035L20.9804 7.98035L20.9804 4.3235" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
9-
</svg> Sync Existing</div>
9+
</svg> Sync</div>
1010
<a class="button" href="<%= tenant.domain %>">Back to <%= tenant.name %></a>
1111
</div>
1212
<div class="commissions">
13-
<% session[vars.commissions].forEach(commission => { %>
13+
<% session[vars.commissions].filter(commission => (commission.assignedTo || []).includes(session[vars.userId])).forEach(commission => { %>
1414
<div class="commission">
1515
<h2><%= commission.id %></h2>
1616
<p><%= commission.status %></p>
17+
<p><%= commission.user %></p>
1718
<a class="button" href="<%= tenant.domain %><%= tenant.path %>/<%= commission.id %>">View</a>
1819
</div>
1920
<% }) %>

frontend/pages/edit.ejs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<%- include('head.ejs') %>
22
<img src="<%= tenant.logo %>" alt="<%= tenant.name %>">
33
<h1><%= title %></h1>
4+
<% if (role === 'admin') { %>
45
<% if (!vars.disableFieldEditing.includes('status')) { %>
56
<div class="inputField active" id="status">
67
<h3>Status*</h3>
@@ -16,7 +17,16 @@
1617
<div class="inputField active" id="owner">
1718
<h3>Owner*</h3>
1819
<p>Required: Commission owner.</p>
20+
<% if ((vars.users || []).filter(user => getUserRole(user) === 'user').length) { %>
21+
<select id="ownerSelect">
22+
<option value="">None</option>
23+
<% vars.users.filter(user => getUserRole(user) === 'user').forEach(user => { %>
24+
<option value="<%= user[vars.userId] %>" <%= (commission.user === user[vars.userId]) ? 'selected' : '' %>><%= user[vars.userName] ? `${user[vars.userName]} (${user[vars.userId]})` : user[vars.userId] %></option>
25+
<% }) %>
26+
</select>
27+
<% } else { %>
1928
<input id="ownerInput" type="text" placeholder="<%= commission.user %>" value="<%= commission.user %>">
29+
<% } %>
2030
</div>
2131
<% } %>
2232
<% if (!vars.disableFieldEditing.includes('amount')) { %>
@@ -48,6 +58,24 @@
4858
<div class="checkbox" change="lockedInput">|||</div>
4959
</div>
5060
<% } %>
61+
<% if (!vars.disableFieldEditing.includes('assignedTo')) { %>
62+
<div class="inputField active" id="assignedTo">
63+
<h3>Assigned To</h3>
64+
<p>Optional: The developer to assign this commission to.</p>
65+
<% if ((vars.users || []).filter(user => getUserRole(user) !== 'user').length) { %>
66+
<select id="assignedToSelect" multiple>
67+
<option value="">None</option>
68+
<% vars.users.filter(user => getUserRole(user) !== 'user').forEach(user => { %>
69+
<option value="<%= user[vars.userId] %>" <%= ((commission.assignedTo || []).includes(user[vars.userId])) ? 'selected' : '' %>><%= user[vars.userName] ? `${user[vars.userName]} (${user[vars.userId]})` : user[vars.userId] %></option>
70+
<% }) %>
71+
</select>
72+
<% } else { %>
73+
<p>No developers found.</p>
74+
<% } %>
75+
</div>
76+
<% } %>
77+
<% } %>
78+
<% if (role !== 'dev') { %>
5179
<% fields.forEach(field => { %>
5280
<div class="inputField active <%= field.required ? 'required' : '' %>" id="<%= field.id %>">
5381
<h3><%= field.label %><%= field.required ? '*' : '' %></h3>
@@ -86,12 +114,15 @@
86114
<% } %>
87115
</div>
88116
<% }) %>
117+
<% } %>
118+
<% if (role !== 'user') { %>
89119
<div class="inputField active" id="sendEmail">
90120
<h3>Send Email</h3>
91121
<p>Optional: Send an email to the user regarding this update.</p>
92122
<input id="sendEmailInput" type="checkbox" class="hidden">
93123
<div class="checkbox" change="sendEmailInput">|||</div>
94124
</div>
125+
<% } %>
95126
<div class="buttons">
96127
<% if (!commission.locked || (role !== 'user')) { %><div id="save" class="button">Save</div><% } %>
97128
<a id="error" class="button active hidden" href="<%= tenant.domain %><%= tenant.path %>/<%= commission.id %>/edit">Restart</a>

frontend/pages/user.ejs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<a class="button" href="<%= tenant.domain %><%= tenant.path %>/create">New Commission</a>
1313
<div id="sync" class="button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23" fill="none">
1414
<path d="M18.5949 5.16663C16.8618 3.32009 14.3989 2.16663 11.6666 2.16663C6.41992 2.16663 2.16663 6.41992 2.16663 11.6666C2.16663 16.9133 6.41992 21.1666 11.6666 21.1666C14.3989 21.1666 16.8618 20.0132 18.5949 18.1666M17.3236 7.98035L20.9804 7.98035L20.9804 4.3235" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
15-
</svg> Sync Existing</div>
15+
</svg> Sync</div>
1616
<a class="button" href="<%= tenant.domain %>">Back to <%= tenant.name %></a>
1717
</div>
1818
<div class="commissions">

frontend/public/scripts.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ document.querySelectorAll('.inputField').forEach(field => {
240240
});
241241
field.querySelectorAll('select').forEach(select => {
242242
select.addEventListener('change', function () {
243-
setTimeout(() => saveChange(window.location.href.replace(appPath, ''), field.id, select.selectedOptions[0].value), 100);
243+
setTimeout(() => saveChange(window.location.href.replace(appPath, ''), field.id, select.multiple ? Array.from(select.selectedOptions).map(selectedOption => selectedOption.value): select.selectedOptions[0].value), 100);
244244
});
245245
});
246246
});

index.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ function init({
5454
access: {},
5555
commissions: 'commissions',
5656
possibleStatuses: [],
57-
disableFieldEditing: []
57+
disableFieldEditing: [],
58+
users: 'users'
5859
},
5960
fields: newFields = [],
6061
handlers: {
@@ -89,6 +90,7 @@ function init({
8990
commissions: 'commissions',
9091
possibleStatuses: [],
9192
disableFieldEditing: [],
93+
users: 'users',
9294
...newVars
9395
};
9496
fields = newFields.filter(field => field.id !== 'user');
@@ -151,7 +153,8 @@ function verifyAgainstSchema(type, data) {
151153
label: link.label ? String(link.label) : '',
152154
url: link.url ? String(link.url) : ''
153155
};
154-
}) : []
156+
}) : [],
157+
assignedTo: Array.isArray(commission.assignedTo) ? commission.assignedTo.map(assigned => String(assigned)) : []
155158
};
156159
}).filter(commission => commission !== null);
157160
};
@@ -245,7 +248,8 @@ app.get('/:id', async (req, res) => {
245248
if (tenant.auth && tenant.auth.enabled && vars.userId && !req.session[vars.userId]) return res.render('auth', { tenant, title: 'Authenticate' });
246249
req.session[vars.commissions] = verifyAgainstSchema('commission', req.session[vars.commissions] || []);
247250
if (getUserRole(req.session) === 'user') req.session[vars.commissions] = req.session[vars.commissions].filter(commission => commission.user === req.session[vars.userId]);
248-
const commission = (req.session[vars.commissions] || []).find(commission => (String(commission.id) === String(req.params.id)) && (getUserRole(req.session) === 'admin' ? true : (commission.user === req.session[vars.userId])));
251+
if (getUserRole(req.session) === 'dev') req.session[vars.commissions] = req.session[vars.commissions].filter(commission => (commission.assignedTo || []).includes(req.session[vars.userId]));
252+
const commission = (req.session[vars.commissions] || []).find(commission => String(commission.id) === String(req.params.id));
249253
if (!commission) return res.status(404).render('error', { tenant, title: 'Not Found', message: 'The requested commission was not found.' });
250254
return res.render('commission', { tenant, title: `Commission ${commission.id}`, session: req.session, vars, fields, role: getUserRole(req.session), commission });
251255
});
@@ -257,10 +261,11 @@ app.get('/:id/edit', async (req, res) => {
257261
if (tenant.auth && tenant.auth.enabled && vars.userId && !req.session[vars.userId]) return res.render('auth', { tenant, title: 'Authenticate' });
258262
req.session[vars.commissions] = verifyAgainstSchema('commission', req.session[vars.commissions] || []);
259263
if (getUserRole(req.session) === 'user') req.session[vars.commissions] = req.session[vars.commissions].filter(commission => commission.user === req.session[vars.userId]);
260-
const commission = (req.session[vars.commissions] || []).find(commission => (String(commission.id) === String(req.params.id)) && (getUserRole(req.session) === 'admin' ? true : (commission.user === req.session[vars.userId])));
264+
if (getUserRole(req.session) === 'dev') req.session[vars.commissions] = req.session[vars.commissions].filter(commission => (commission.assignedTo || []).includes(req.session[vars.userId]));
265+
const commission = (req.session[vars.commissions] || []).find(commission => String(commission.id) === String(req.params.id));
261266
if (!commission) return res.status(404).render('error', { tenant, title: 'Not Found', message: 'The requested commission was not found.' });
262267
if (commission.locked && (getUserRole(req.session) === 'user')) return res.status(403).render('error', { tenant, title: 'Forbidden', message: 'You do not have permission to edit this commission.' });
263-
return res.render('edit', { tenant, title: `Edit Commission ${commission.id}`, session: req.session, vars, fields, commission, role: getUserRole(req.session) });
268+
return res.render('edit', { tenant, title: `Edit Commission ${commission.id}`, session: req.session, vars, fields, commission, role: getUserRole(req.session), getUserRole });
264269
});
265270

266271
app.post('/:id/edit', async (req, res) => {
@@ -270,7 +275,8 @@ app.post('/:id/edit', async (req, res) => {
270275
if (!tenant.slug || !tenant.name || !tenant.domain) return res.status(500).json({ status: 'error', message: 'Service is not properly configured. Please contact the administrator.' });
271276
req.session[vars.commissions] = verifyAgainstSchema('commission', req.session[vars.commissions] || []);
272277
if (getUserRole(req.session) === 'user') req.session[vars.commissions] = req.session[vars.commissions].filter(commission => commission.user === req.session[vars.userId]);
273-
const commissionIndex = (req.session[vars.commissions] || []).findIndex(commission => (String(commission.id) === String(req.params.id)) && (getUserRole(req.session) === 'admin' ? true : (commission.user === req.session[vars.userId])));
278+
if (getUserRole(req.session) === 'dev') req.session[vars.commissions] = req.session[vars.commissions].filter(commission => (commission.assignedTo || []).includes(req.session[vars.userId]));
279+
const commissionIndex = (req.session[vars.commissions] || []).findIndex(commission => String(commission.id) === String(req.params.id));
274280
if (commissionIndex === -1) return res.status(404).json({ status: 'error', message: 'The requested commission was not found.' });
275281
const commission = req.session[vars.commissions][commissionIndex];
276282
if (commission.locked && (getUserRole(req.session) === 'user')) return res.status(403).json({ status: 'error', message: 'You do not have permission to edit this commission.' });
@@ -291,7 +297,8 @@ app.post('/:id/edit', async (req, res) => {
291297
update.date = req.body.date ? new Date(req.body.date) : commission.date;
292298
update.status = req.body.status ? String(req.body.status) : commission.status;
293299
update.locked = (getUserRole(req.session) === 'admin') ? ((req.body.locked === true) || (req.body.locked === 'true') || (req.body.locked === 'on')) : commission.locked;
294-
update.sendEmail = (req.body.sendEmail === true) || (req.body.sendEmail === 'true') || (req.body.sendEmail === 'on');
300+
update.assignedTo = (getUserRole(req.session) === 'admin') ? (Array.isArray(req.body.assignedTo) ? req.body.assignedTo.filter(id => String(id).trim() !== '') : (req.body.assignedTo ? [String(req.body.assignedTo).trim()] : [])) : commission.assignedTo;
301+
update.sendEmail = (getUserRole(req.session) !== 'user') ? ((req.body.sendEmail === true) || (req.body.sendEmail === 'true') || (req.body.sendEmail === 'on')) : true;
295302
vars.disableFieldEditing.forEach(fieldId => {
296303
if (fieldId in data) delete data[fieldId];
297304
});

0 commit comments

Comments
 (0)