codeapp-js 0.3.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/AI/codeapp.agent.md +105 -0
  2. package/AI/skills/connections/SKILL.md +47 -0
  3. package/AI/skills/dataverse/SKILL.md +99 -0
  4. package/AI/skills/environment-variables/SKILL.md +89 -0
  5. package/AI/skills/frontend-design/SKILL.md +34 -0
  6. package/AI/skills/jira/SKILL.md +81 -0
  7. package/AI/skills/office365-groups/SKILL.md +61 -0
  8. package/AI/skills/office365-outlook/SKILL.md +52 -0
  9. package/AI/skills/office365-users/SKILL.md +78 -0
  10. package/AI/skills/sharepoint/SKILL.md +77 -0
  11. package/AI/skills/sql/SKILL.md +85 -0
  12. package/AI/skills/start/SKILL.md +46 -0
  13. package/AI/skills/teams/SKILL.md +55 -0
  14. package/{examples/combined demo/.power/schemas/office365groups/office365groups.Schema.json → codeApp/.power/schemas/office365groups/office365groups.Schema.json} +2203 -2203
  15. package/codeApp/dist/codeapp.js +95 -1792
  16. package/codeApp/dist/connectors/azureKeyvault.js +459 -0
  17. package/codeApp/dist/connectors/jira.js +1247 -0
  18. package/codeApp/dist/connectors/office365groups.js +642 -0
  19. package/codeApp/dist/connectors/office365users.js +513 -0
  20. package/codeApp/dist/connectors/outlook.js +1393 -0
  21. package/codeApp/dist/connectors/sharepoint.js +466 -0
  22. package/codeApp/dist/connectors/sql.js +149 -0
  23. package/codeApp/dist/connectors/teams.js +280 -0
  24. package/codeApp/dist/power-apps-data.js +16 -2
  25. package/examples/{kanban → apps/kanban}/dist/dataverse.js +94 -94
  26. package/examples/{kanban → apps/kanban}/dist/environmentVar.js +55 -55
  27. package/examples/{kanban → apps/kanban}/dist/index.css +605 -605
  28. package/examples/{kanban → apps/kanban}/dist/index.html +21 -21
  29. package/examples/{kanban → apps/kanban}/dist/index.js +860 -860
  30. package/examples/{kanban → apps/kanban}/dist/office365groups.js +97 -97
  31. package/examples/apps/kanban/dist/office365users.js +451 -0
  32. package/examples/{kanban → apps/kanban}/dist/outlook.js +162 -162
  33. package/examples/{planning Poker/dist/power-apps-data.js → apps/kanban/dist/power-apps-data.js} +2953 -2953
  34. package/examples/{kanban → apps/kanban}/dist/sharepoint.js +435 -339
  35. package/examples/{kanban → apps/kanban}/power.config.json +35 -35
  36. package/examples/{planning Poker → apps/planning Poker}/additional files/customizations (tables).xml +6428 -6428
  37. package/examples/{planning Poker → apps/planning Poker}/additional files/dataverse-tables.json +165 -165
  38. package/examples/{planning Poker → apps/planning Poker}/additional files/readme.md +122 -122
  39. package/examples/{planning Poker → apps/planning Poker}/dist/dataverse.js +78 -78
  40. package/examples/{planning Poker → apps/planning Poker}/dist/index.html +198 -198
  41. package/examples/{planning Poker → apps/planning Poker}/dist/index.js +954 -954
  42. package/examples/{todo/dist/power-apps-data.js → apps/planning Poker/dist/power-apps-data.js } +2953 -2953
  43. package/examples/{planning Poker → apps/planning Poker}/dist/styles.css +815 -815
  44. package/examples/{planning Poker → apps/planning Poker}/power.config.json +50 -50
  45. package/examples/{outlook Demo2 → apps/solution explorer}/dist/codeapp.js +9 -245
  46. package/examples/apps/solution explorer/dist/index.html +80 -0
  47. package/examples/apps/solution explorer/dist/index.js +735 -0
  48. package/examples/apps/solution explorer/dist/styles.css +571 -0
  49. package/examples/apps/solution explorer/power.config.json +151 -0
  50. package/examples/{todo → apps/todo}/dist/dataverse.js +64 -64
  51. package/examples/{todo → apps/todo}/dist/index.html +75 -75
  52. package/examples/{todo → apps/todo}/dist/index.js +8 -8
  53. package/examples/{kanban → apps/todo}/dist/power-apps-data.js +2953 -2953
  54. package/examples/{todo → apps/todo}/dist/renderer.js +375 -375
  55. package/examples/{todo → apps/todo}/dist/styles.css +691 -691
  56. package/examples/{todo → apps/todo}/power.config.json +34 -34
  57. package/examples/combined demo/.power/schemas/appschemas/dataSourcesInfo.ts +6275 -7830
  58. package/examples/combined demo/.power/schemas/jira/jira.Schema.json +6903 -0
  59. package/examples/combined demo/.power/schemas/keyvault/keyvault.Schema.json +1600 -0
  60. package/examples/combined demo/.power/schemas/teams/teams.Schema.json +11112 -0
  61. package/examples/combined demo/dist/codeapp.js +394 -1098
  62. package/examples/{outlook Demo2/OutlookDemo_1_0_0_1.zip → combined demo/dist/icon-512.png} +0 -0
  63. package/examples/combined demo/dist/index.html +29 -511
  64. package/examples/combined demo/dist/index.js +490 -470
  65. package/examples/combined demo/dist/office365users.js +513 -0
  66. package/examples/combined demo/dist/outlook.js +1393 -0
  67. package/examples/combined demo/dist/power-apps-data.js +3079 -3006
  68. package/examples/combined demo/dist/styles.css +483 -0
  69. package/examples/combined demo/power.config.json +33 -42
  70. package/examples/combined demo/src/generated/index.ts +12 -14
  71. package/examples/combined demo/src/generated/models/AzureKeyVaultModel.ts +107 -0
  72. package/examples/combined demo/src/generated/models/JiraModel.ts +501 -0
  73. package/examples/combined demo/src/generated/services/AzureKeyVaultService.ts +257 -0
  74. package/examples/combined demo/src/generated/services/JiraService.ts +1124 -0
  75. package/examples/dataverse Demo/dist/codeapp.js +394 -1085
  76. package/examples/dataverse Demo/dist/icon-512.png +0 -0
  77. package/examples/dataverse Demo/dist/index.html +146 -54
  78. package/examples/dataverse Demo/dist/index.js +693 -83
  79. package/examples/dataverse Demo/dist/power-apps-data.js +3079 -2911
  80. package/examples/dataverse Demo/dist/styles.css +528 -0
  81. package/examples/dataverse Demo/power.config.json +41 -35
  82. package/examples/dataverse Demo/readme.md +79 -79
  83. package/examples/groups Demo/dist/codeapp.js +394 -1085
  84. package/examples/groups Demo/dist/icon-512.png +0 -0
  85. package/examples/groups Demo/dist/index.html +21 -25
  86. package/examples/groups Demo/dist/index.js +304 -113
  87. package/examples/groups Demo/dist/office365groups.js +642 -0
  88. package/examples/groups Demo/dist/power-apps-data.js +3079 -2911
  89. package/examples/groups Demo/dist/styles.css +509 -0
  90. package/examples/groups Demo/power.config.json +25 -25
  91. package/examples/myProfile/dist/codeapp.js +398 -0
  92. package/examples/myProfile/dist/index.html +21 -184
  93. package/examples/myProfile/dist/index.js +324 -141
  94. package/examples/myProfile/dist/office365users.js +517 -169
  95. package/examples/myProfile/dist/power-apps-data.js +3080 -2953
  96. package/examples/myProfile/dist/styles.css +458 -0
  97. package/examples/myProfile/power.config.json +24 -23
  98. package/examples/outlook Demo/dist/codeapp.js +394 -1085
  99. package/examples/outlook Demo/dist/index.html +150 -35
  100. package/examples/outlook Demo/dist/index.js +516 -170
  101. package/examples/outlook Demo/dist/outlook.js +1393 -121
  102. package/examples/outlook Demo/dist/power-apps-data.js +3079 -2911
  103. package/examples/outlook Demo/dist/styles.css +408 -84
  104. package/examples/outlook Demo/power.config.json +24 -23
  105. package/examples/outlook Demo/readme.md +92 -82
  106. package/examples/sharePoint Demo/dist/codeapp.js +394 -1085
  107. package/examples/sharePoint Demo/dist/icon-512.png +0 -0
  108. package/examples/sharePoint Demo/dist/index.html +22 -255
  109. package/examples/sharePoint Demo/dist/index.js +899 -262
  110. package/examples/sharePoint Demo/dist/power-apps-data.js +3079 -2911
  111. package/{dev files → examples/sharePoint Demo/dist}/sharepoint.js +239 -112
  112. package/examples/sharePoint Demo/dist/styles.css +587 -0
  113. package/examples/sharePoint Demo/power.config.json +23 -22
  114. package/package.json +1 -1
  115. package/readme.md +465 -76
  116. package/.vscode/settings.json +0 -6
  117. package/dev files/customConnector.js +0 -98
  118. package/dev files/dataverse.js +0 -120
  119. package/dev files/environmentVar.js +0 -55
  120. package/dev files/office365groups.js +0 -65
  121. package/dev files/office365users.js +0 -169
  122. package/dev files/outlook.js +0 -330
  123. package/dev files/power-apps-data.js +0 -2952
  124. package/examples/combined demo/.power/schemas/office365/office365.Schema.json +0 -21098
  125. package/examples/combined demo/.power/schemas/office365users/office365users.Schema.json +0 -2094
  126. package/examples/kanban/agent/decision-log.md +0 -9
  127. package/examples/kanban/agent/mockup-01-editorial-glass.html +0 -159
  128. package/examples/kanban/agent/mockup-02-dark-rail.html +0 -147
  129. package/examples/kanban/agent/mockup-03-paper-grid.html +0 -114
  130. package/examples/kanban/agent/mockup-04-neon-minimal.html +0 -141
  131. package/examples/kanban/agent/mockup-05-mono-architect.html +0 -119
  132. package/examples/kanban/dist/office365users.js +0 -169
  133. package/examples/kanban/src/generated/index.ts +0 -14
  134. package/examples/kanban/src/generated/models/Office365GroupsModel.ts +0 -363
  135. package/examples/kanban/src/generated/models/Office365OutlookModel.ts +0 -2046
  136. package/examples/kanban/src/generated/models/Office365UsersModel.ts +0 -254
  137. package/examples/kanban/src/generated/services/Office365GroupsService.ts +0 -326
  138. package/examples/kanban/src/generated/services/Office365OutlookService.ts +0 -2476
  139. package/examples/kanban/src/generated/services/Office365UsersService.ts +0 -358
  140. package/examples/outlook Demo2/agent/decision-log.md +0 -7
  141. package/examples/outlook Demo2/dist/index.html +0 -98
  142. package/examples/outlook Demo2/dist/index.js +0 -272
  143. package/examples/outlook Demo2/dist/styles.css +0 -639
  144. package/examples/outlook Demo2/power.config.json +0 -23
  145. package/examples/planning Poker/.vscode/settings.json +0 -5
  146. package/examples/sharePoint Demo/agent/decision-log.md +0 -17
  147. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/index.ts +0 -0
  148. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365GroupsModel.ts +0 -0
  149. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365OutlookModel.ts +0 -0
  150. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365UsersModel.ts +0 -0
  151. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365GroupsService.ts +0 -0
  152. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365OutlookService.ts +0 -0
  153. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365UsersService.ts +0 -0
  154. /package/examples/{planning Poker → apps/planning Poker}/additional files/AgilePoker_1_0_0_1.zip +0 -0
  155. /package/examples/{planning Poker → apps/planning Poker}/additional files/PokerTables_1_0_0_1.zip +0 -0
  156. /package/examples/{outlook Demo2 → apps/solution explorer}/dist/icon-512.png +0 -0
  157. /package/examples/{outlook Demo2 → apps/solution explorer}/dist/power-apps-data.js +0 -0
  158. /package/examples/{todo → apps/todo}/dist/icon192.png +0 -0
@@ -1,83 +1,693 @@
1
- import { initDataSources, createItem, listItems } from './codeapp.js';
2
-
3
- const TABLE = 'tasks';
4
- const PK = 'activityid';
5
-
6
- function dsEntry(sPrimaryKey) {
7
- return { tableId: '', version: '', primaryKey: sPrimaryKey, dataSourceType: 'Dataverse', apis: {} };
8
- }
9
-
10
- initDataSources({
11
- tasks: dsEntry('activityid')
12
- });
13
-
14
- const taskBody = document.getElementById('task-body');
15
- const taskInput = document.getElementById('task-input');
16
- const btnAdd = document.getElementById('btn-add');
17
- const status = document.getElementById('status');
18
-
19
- function renderRows(records) {
20
- if (!records || records.length === 0) {
21
- taskBody.innerHTML = '<tr><td colspan="3">No tasks found.</td></tr>';
22
- return;
23
- }
24
- taskBody.innerHTML = records
25
- .map((r) => {
26
- const subject = r.subject || '—';
27
- const state = r.statecode === 0 ? 'Open' : 'Completed';
28
- const created = r.createdon
29
- ? new Date(r.createdon).toLocaleDateString()
30
- : '';
31
- return '<tr><td>' + escapeHtml(subject) + '</td><td>' + state + '</td><td>' + created + '</td></tr>';
32
- })
33
- .join('');
34
- }
35
-
36
- function escapeHtml(sText) {
37
- let sStr = String(sText == null ? '' : sText);
38
- return sStr
39
- .replace(new RegExp('&', 'g'), '&amp;')
40
- .replace(new RegExp('<', 'g'), '&lt;')
41
- .replace(new RegExp('>', 'g'), '&gt;')
42
- .replace(new RegExp('"', 'g'), '&quot;')
43
- .replace(new RegExp("'", 'g'), '&#39;');
44
- }
45
-
46
- async function loadTasks() {
47
- status.textContent = 'Loading tasks…';
48
- try {
49
- const result = await listItems(TABLE, PK, {
50
- select: ["subject", "statecode", "createdon"],
51
- orderBy: ["createdon desc"],
52
- top: 5,
53
- });
54
- renderRows(result.entities ?? []);
55
- status.textContent = '';
56
- } catch (err) {
57
- status.textContent = 'Error loading tasks: ' + err.message;
58
- }
59
- }
60
-
61
- async function addTask() {
62
- const subject = taskInput.value.trim();
63
- if (!subject) return;
64
-
65
- btnAdd.disabled = true;
66
- status.textContent = 'Creating task…';
67
- try {
68
- await createItem(TABLE, PK, { subject });
69
- taskInput.value = '';
70
- await loadTasks();
71
- } catch (err) {
72
- status.textContent = 'Error creating task: ' + err.message;
73
- } finally {
74
- btnAdd.disabled = false;
75
- }
76
- }
77
-
78
- btnAdd.addEventListener('click', addTask);
79
- taskInput.addEventListener('keydown', (e) => {
80
- if (e.key === 'Enter') addTask();
81
- });
82
-
83
- loadTasks();
1
+ import { enableDebugger } from "./codeapp.js";
2
+
3
+ enableDebugger();
4
+
5
+ import {
6
+ initDataSources,
7
+ listItems,
8
+ createItem,
9
+ updateItem,
10
+ deleteItem,
11
+ callUnboundAction,
12
+ whoAmI,
13
+ } from './codeapp.js';
14
+
15
+ const sTaskTableName = 'tasks';
16
+ const sTaskPrimaryKey = 'activityid';
17
+ const sUserTableName = 'systemusers';
18
+ const sUserPrimaryKey = 'systemuserid';
19
+
20
+ const aAccessLevels = [
21
+ {
22
+ id: 'read',
23
+ label: 'Read only',
24
+ mask: 'ReadAccess',
25
+ note: 'View the task record.',
26
+ },
27
+ {
28
+ id: 'work',
29
+ label: 'Collaborate',
30
+ mask: 'ReadAccess,WriteAccess,AppendAccess,AppendToAccess',
31
+ note: 'Update the task and related notes.',
32
+ },
33
+ {
34
+ id: 'share',
35
+ label: 'Collaborate + reshare',
36
+ mask: 'ReadAccess,WriteAccess,AppendAccess,AppendToAccess,ShareAccess',
37
+ note: 'Update the task and share it onward.',
38
+ },
39
+ ];
40
+
41
+ const oState = {
42
+ aTasks: [],
43
+ aUsers: [],
44
+ aShareEvents: [],
45
+ sSelectedTaskId: '',
46
+ sCurrentUserId: '',
47
+ sStatusText: 'Booting Dataverse task workspace...',
48
+ sStatusTone: 'live',
49
+ sShareStatusText: 'Select a task, choose a user, then grant access.',
50
+ sListCaption: 'Showing the latest task records.',
51
+ bLoading: false,
52
+ bSaving: false,
53
+ bSharing: false,
54
+ };
55
+
56
+ const oElements = {
57
+ eConnectionState: document.getElementById('connectionState'),
58
+ eTaskCount: document.getElementById('taskCount'),
59
+ eUserCount: document.getElementById('userCount'),
60
+ eTaskSearch: document.getElementById('taskSearch'),
61
+ ePriorityFilter: document.getElementById('priorityFilter'),
62
+ eStatusTone: document.getElementById('statusTone'),
63
+ eStatusText: document.getElementById('statusText'),
64
+ eCurrentUserText: document.getElementById('currentUserText'),
65
+ eTaskList: document.getElementById('taskList'),
66
+ eListCaption: document.getElementById('listCaption'),
67
+ eEditorHeading: document.getElementById('editorHeading'),
68
+ eRecordMeta: document.getElementById('recordMeta'),
69
+ eTaskForm: document.getElementById('taskForm'),
70
+ eTaskTitle: document.getElementById('taskTitle'),
71
+ eTaskPriority: document.getElementById('taskPriority'),
72
+ eTaskDueDate: document.getElementById('taskDueDate'),
73
+ eTaskDescription: document.getElementById('taskDescription'),
74
+ eBtnRefresh: document.getElementById('btnRefresh'),
75
+ eBtnNewTask: document.getElementById('btnNewTask'),
76
+ eBtnResetTask: document.getElementById('btnResetTask'),
77
+ eBtnDeleteTask: document.getElementById('btnDeleteTask'),
78
+ eShareUser: document.getElementById('shareUser'),
79
+ eShareAccess: document.getElementById('shareAccess'),
80
+ eBtnShareTask: document.getElementById('btnShareTask'),
81
+ eShareStatusText: document.getElementById('shareStatusText'),
82
+ eShareLog: document.getElementById('shareLog'),
83
+ };
84
+
85
+ function dsEntry(sPrimaryKey) {
86
+ return {
87
+ tableId: '',
88
+ version: '',
89
+ primaryKey: sPrimaryKey,
90
+ dataSourceType: 'Dataverse',
91
+ apis: {},
92
+ };
93
+ }
94
+
95
+ function normalizeGuid(sValue) {
96
+ return String(sValue || '').split('{').join('').split('}').join('').toLowerCase();
97
+ }
98
+
99
+ function normalizeDateValue(sValue) {
100
+ if (!sValue) {
101
+ return '';
102
+ }
103
+
104
+ return String(sValue).slice(0, 10);
105
+ }
106
+
107
+ function formatDateLabel(sValue) {
108
+ if (!sValue) {
109
+ return 'No due date';
110
+ }
111
+
112
+ const oDate = new Date(sValue);
113
+ if (Number.isNaN(oDate.getTime())) {
114
+ return 'No due date';
115
+ }
116
+
117
+ return oDate.toLocaleDateString(undefined, {
118
+ month: 'short',
119
+ day: 'numeric',
120
+ year: 'numeric',
121
+ });
122
+ }
123
+
124
+ function formatDateTimeLabel(sValue) {
125
+ if (!sValue) {
126
+ return 'Not yet saved';
127
+ }
128
+
129
+ const oDate = new Date(sValue);
130
+ if (Number.isNaN(oDate.getTime())) {
131
+ return 'Not yet saved';
132
+ }
133
+
134
+ return oDate.toLocaleString(undefined, {
135
+ month: 'short',
136
+ day: 'numeric',
137
+ hour: 'numeric',
138
+ minute: '2-digit',
139
+ });
140
+ }
141
+
142
+ function priorityLabelFromCode(iCode) {
143
+ if (Number(iCode) === 2) {
144
+ return 'High';
145
+ }
146
+ if (Number(iCode) === 0) {
147
+ return 'Low';
148
+ }
149
+ return 'Normal';
150
+ }
151
+
152
+ function stateLabelFromCode(iCode) {
153
+ if (Number(iCode) === 1) {
154
+ return 'Completed';
155
+ }
156
+ if (Number(iCode) === 2) {
157
+ return 'Canceled';
158
+ }
159
+ return 'Open';
160
+ }
161
+
162
+ function setStatus(sText, sTone) {
163
+ oState.sStatusText = sText;
164
+ oState.sStatusTone = sTone || 'live';
165
+ renderStatus();
166
+ }
167
+
168
+ function setShareStatus(sText, sTone) {
169
+ oState.sShareStatusText = sText;
170
+ oElements.eShareStatusText.textContent = sText;
171
+ oElements.eShareStatusText.dataset.tone = sTone || 'live';
172
+ }
173
+
174
+ function getSelectedTask() {
175
+ return oState.aTasks.find((oTask) => oTask.id === oState.sSelectedTaskId) || null;
176
+ }
177
+
178
+ function getVisibleTasks() {
179
+ const sSearch = String(oElements.eTaskSearch.value || '').trim().toLowerCase();
180
+ const sPriorityFilter = String(oElements.ePriorityFilter.value || 'all');
181
+
182
+ return oState.aTasks.filter((oTask) => {
183
+ const bMatchesSearch = !sSearch
184
+ || String(oTask.subject || '').toLowerCase().includes(sSearch)
185
+ || String(oTask.description || '').toLowerCase().includes(sSearch);
186
+ const bMatchesPriority = sPriorityFilter === 'all' || String(oTask.prioritycode) === sPriorityFilter;
187
+ return bMatchesSearch && bMatchesPriority;
188
+ });
189
+ }
190
+
191
+ function mapTaskRecord(oRecord) {
192
+ return {
193
+ id: oRecord.activityid || oRecord.taskid || '',
194
+ subject: oRecord.subject || '',
195
+ description: oRecord.description || '',
196
+ prioritycode: Number(oRecord.prioritycode),
197
+ scheduledend: normalizeDateValue(oRecord.scheduledend),
198
+ statecode: Number(oRecord.statecode),
199
+ statuscode: Number(oRecord.statuscode),
200
+ modifiedon: oRecord.modifiedon || '',
201
+ };
202
+ }
203
+
204
+ function mapUserRecord(oRecord) {
205
+ return {
206
+ id: oRecord.systemuserid || '',
207
+ fullname: oRecord.fullname || 'Unnamed user',
208
+ email: oRecord.internalemailaddress || oRecord.domainname || '',
209
+ };
210
+ }
211
+
212
+ function renderStatus() {
213
+ oElements.eConnectionState.textContent = oState.bLoading ? 'Refreshing' : 'Connected';
214
+ if (oState.sStatusTone === 'error') {
215
+ oElements.eConnectionState.textContent = 'Attention';
216
+ }
217
+ oElements.eStatusTone.textContent = oState.sStatusTone;
218
+ oElements.eStatusTone.dataset.tone = oState.sStatusTone;
219
+ oElements.eStatusText.textContent = oState.sStatusText;
220
+ }
221
+
222
+ function renderMetrics() {
223
+ oElements.eTaskCount.textContent = String(oState.aTasks.length);
224
+ oElements.eUserCount.textContent = String(oState.aUsers.length);
225
+ }
226
+
227
+ function renderTaskList() {
228
+ const aVisibleTasks = getVisibleTasks();
229
+ oElements.eTaskList.innerHTML = '';
230
+
231
+ if (aVisibleTasks.length === 0) {
232
+ const eEmpty = document.createElement('div');
233
+ eEmpty.className = 'empty-card';
234
+ eEmpty.textContent = 'No Dataverse task records match the current filters.';
235
+ oElements.eTaskList.appendChild(eEmpty);
236
+ }
237
+
238
+ aVisibleTasks.forEach((oTask) => {
239
+ const eButton = document.createElement('button');
240
+ eButton.type = 'button';
241
+ eButton.className = 'task-card';
242
+ if (oTask.id === oState.sSelectedTaskId) {
243
+ eButton.className += ' task-card--active';
244
+ }
245
+
246
+ const eTopRow = document.createElement('div');
247
+ eTopRow.className = 'task-card__row';
248
+
249
+ const eTitle = document.createElement('h3');
250
+ eTitle.className = 'task-card__title';
251
+ eTitle.textContent = oTask.subject || 'Untitled task';
252
+
253
+ const ePriority = document.createElement('span');
254
+ ePriority.className = 'pill pill--priority';
255
+ ePriority.textContent = priorityLabelFromCode(oTask.prioritycode);
256
+
257
+ eTopRow.appendChild(eTitle);
258
+ eTopRow.appendChild(ePriority);
259
+
260
+ const eDescription = document.createElement('p');
261
+ eDescription.className = 'task-card__description';
262
+ eDescription.textContent = oTask.description || 'No description';
263
+
264
+ const eBottomRow = document.createElement('div');
265
+ eBottomRow.className = 'task-card__row task-card__row--meta';
266
+
267
+ const eDue = document.createElement('span');
268
+ eDue.className = 'pill';
269
+ eDue.textContent = formatDateLabel(oTask.scheduledend);
270
+
271
+ const eState = document.createElement('span');
272
+ eState.className = 'pill';
273
+ eState.textContent = stateLabelFromCode(oTask.statecode);
274
+
275
+ eBottomRow.appendChild(eDue);
276
+ eBottomRow.appendChild(eState);
277
+
278
+ eButton.appendChild(eTopRow);
279
+ eButton.appendChild(eDescription);
280
+ eButton.appendChild(eBottomRow);
281
+
282
+ eButton.addEventListener('click', () => {
283
+ selectTask(oTask.id);
284
+ });
285
+
286
+ oElements.eTaskList.appendChild(eButton);
287
+ });
288
+
289
+ oState.sListCaption = aVisibleTasks.length + ' task' + (aVisibleTasks.length === 1 ? '' : 's') + ' shown from Dataverse.';
290
+ oElements.eListCaption.textContent = oState.sListCaption;
291
+ }
292
+
293
+ function renderShareOptions() {
294
+ const sSelectedAccessId = String(oElements.eShareAccess.value || 'read');
295
+ const sSelectedUserId = String(oElements.eShareUser.value || '');
296
+
297
+ oElements.eShareAccess.innerHTML = '';
298
+ aAccessLevels.forEach((oLevel) => {
299
+ const eOption = document.createElement('option');
300
+ eOption.value = oLevel.id;
301
+ eOption.textContent = oLevel.label + ' - ' + oLevel.note;
302
+ oElements.eShareAccess.appendChild(eOption);
303
+ });
304
+
305
+ if (aAccessLevels.some((oLevel) => oLevel.id === sSelectedAccessId)) {
306
+ oElements.eShareAccess.value = sSelectedAccessId;
307
+ }
308
+
309
+ oElements.eShareUser.innerHTML = '';
310
+ if (oState.aUsers.length === 0) {
311
+ const eOption = document.createElement('option');
312
+ eOption.value = '';
313
+ eOption.textContent = 'No Dataverse users loaded';
314
+ oElements.eShareUser.appendChild(eOption);
315
+ return;
316
+ }
317
+
318
+ const ePlaceholder = document.createElement('option');
319
+ ePlaceholder.value = '';
320
+ ePlaceholder.textContent = 'Select a Dataverse user';
321
+ oElements.eShareUser.appendChild(ePlaceholder);
322
+
323
+ oState.aUsers.forEach((oUser) => {
324
+ const eOption = document.createElement('option');
325
+ eOption.value = oUser.id;
326
+ eOption.textContent = oUser.fullname + (oUser.email ? ' - ' + oUser.email : '');
327
+ oElements.eShareUser.appendChild(eOption);
328
+ });
329
+
330
+ if (oState.aUsers.some((oUser) => oUser.id === sSelectedUserId)) {
331
+ oElements.eShareUser.value = sSelectedUserId;
332
+ }
333
+ }
334
+
335
+ function renderShareLog() {
336
+ oElements.eShareLog.innerHTML = '';
337
+ if (oState.aShareEvents.length === 0) {
338
+ const eEmpty = document.createElement('div');
339
+ eEmpty.className = 'empty-card empty-card--compact';
340
+ eEmpty.textContent = 'Share events from this session will appear here.';
341
+ oElements.eShareLog.appendChild(eEmpty);
342
+ return;
343
+ }
344
+
345
+ oState.aShareEvents.forEach((oEvent) => {
346
+ const eItem = document.createElement('article');
347
+ eItem.className = 'share-log__item';
348
+
349
+ const eHeadline = document.createElement('strong');
350
+ eHeadline.textContent = oEvent.title;
351
+
352
+ const eMeta = document.createElement('p');
353
+ eMeta.textContent = oEvent.detail;
354
+
355
+ eItem.appendChild(eHeadline);
356
+ eItem.appendChild(eMeta);
357
+ oElements.eShareLog.appendChild(eItem);
358
+ });
359
+ }
360
+
361
+ function renderEditor() {
362
+ const oTask = getSelectedTask();
363
+ if (!oTask) {
364
+ oElements.eEditorHeading.textContent = 'New task';
365
+ oElements.eRecordMeta.textContent = 'Create a new Dataverse task record.';
366
+ oElements.eBtnDeleteTask.disabled = true;
367
+ return;
368
+ }
369
+
370
+ oElements.eEditorHeading.textContent = oTask.subject || 'Selected task';
371
+ oElements.eRecordMeta.textContent = 'Last updated ' + formatDateTimeLabel(oTask.modifiedon) + ' - ' + oTask.id;
372
+ oElements.eBtnDeleteTask.disabled = false;
373
+ }
374
+
375
+ function renderCurrentUser() {
376
+ if (!oState.sCurrentUserId) {
377
+ oElements.eCurrentUserText.textContent = 'Current Dataverse user could not be detected.';
378
+ return;
379
+ }
380
+
381
+ oElements.eCurrentUserText.textContent = 'Current Dataverse user: ' + oState.sCurrentUserId;
382
+ }
383
+
384
+ function renderActionState() {
385
+ const bHasSelectedTask = !!getSelectedTask();
386
+ const bHasShareTarget = !!oElements.eShareUser.value;
387
+ const bDisableWrite = oState.bSaving || oState.bLoading;
388
+
389
+ oElements.eBtnRefresh.disabled = oState.bLoading;
390
+ oElements.eBtnNewTask.disabled = bDisableWrite;
391
+ oElements.eBtnResetTask.disabled = bDisableWrite;
392
+ oElements.eBtnDeleteTask.disabled = !bHasSelectedTask || bDisableWrite;
393
+ oElements.eBtnShareTask.disabled = !bHasSelectedTask || !bHasShareTarget || oState.bSharing || oState.bLoading;
394
+ }
395
+
396
+ function renderAll() {
397
+ renderStatus();
398
+ renderMetrics();
399
+ renderTaskList();
400
+ renderEditor();
401
+ renderShareOptions();
402
+ renderShareLog();
403
+ renderCurrentUser();
404
+ renderActionState();
405
+ }
406
+
407
+ function fillForm(oTask) {
408
+ oElements.eTaskTitle.value = oTask ? oTask.subject : '';
409
+ oElements.eTaskPriority.value = oTask ? String(oTask.prioritycode) : '1';
410
+ oElements.eTaskDueDate.value = oTask ? normalizeDateValue(oTask.scheduledend) : '';
411
+ oElements.eTaskDescription.value = oTask ? oTask.description : '';
412
+ }
413
+
414
+ function selectTask(sTaskId) {
415
+ oState.sSelectedTaskId = sTaskId || '';
416
+ fillForm(getSelectedTask());
417
+ if (oState.sSelectedTaskId) {
418
+ setShareStatus('Ready to share the selected Dataverse task.', 'live');
419
+ } else {
420
+ setShareStatus('Select a task, choose a user, then grant access.', 'live');
421
+ }
422
+ renderAll();
423
+ }
424
+
425
+ function resetComposer() {
426
+ oState.sSelectedTaskId = '';
427
+ fillForm(null);
428
+ renderAll();
429
+ }
430
+
431
+ function buildTaskPayload() {
432
+ const sSubject = String(oElements.eTaskTitle.value || '').trim();
433
+ const sDescription = String(oElements.eTaskDescription.value || '').trim();
434
+ const sPriority = String(oElements.eTaskPriority.value || '1');
435
+ const sDueDate = String(oElements.eTaskDueDate.value || '');
436
+
437
+ if (!sSubject) {
438
+ throw new Error('Task subject is required.');
439
+ }
440
+
441
+ const oPayload = {
442
+ subject: sSubject,
443
+ description: sDescription,
444
+ prioritycode: Number(sPriority),
445
+ };
446
+
447
+ if (sDueDate) {
448
+ oPayload.scheduledend = sDueDate + 'T12:00:00Z';
449
+ } else {
450
+ oPayload.scheduledend = null;
451
+ }
452
+
453
+ return oPayload;
454
+ }
455
+
456
+ async function loadCurrentUser() {
457
+ try {
458
+ oState.sCurrentUserId = normalizeGuid(await whoAmI());
459
+ } catch (oError) {
460
+ console.error('Failed to resolve current Dataverse user:', oError);
461
+ oState.sCurrentUserId = '';
462
+ }
463
+ }
464
+
465
+ async function loadTasks() {
466
+ const oResult = await listItems(sTaskTableName, sTaskPrimaryKey, {
467
+ select: ['activityid', 'subject', 'description', 'prioritycode', 'scheduledend', 'statecode', 'statuscode', 'modifiedon'],
468
+ orderBy: ['modifiedon desc'],
469
+ top: 100,
470
+ });
471
+
472
+ oState.aTasks = (oResult.entities || []).map((oRecord) => mapTaskRecord(oRecord));
473
+
474
+ if (oState.sSelectedTaskId) {
475
+ const oSelected = getSelectedTask();
476
+ if (!oSelected) {
477
+ oState.sSelectedTaskId = '';
478
+ }
479
+ }
480
+
481
+ if (!oState.sSelectedTaskId && oState.aTasks.length > 0) {
482
+ oState.sSelectedTaskId = oState.aTasks[0].id;
483
+ fillForm(oState.aTasks[0]);
484
+ }
485
+ }
486
+
487
+ async function loadUsers() {
488
+ const oResult = await listItems(sUserTableName, sUserPrimaryKey, {
489
+ filter: 'isdisabled eq false',
490
+ select: ['systemuserid', 'fullname', 'internalemailaddress', 'domainname'],
491
+ orderBy: ['fullname asc'],
492
+ top: 100,
493
+ });
494
+
495
+ oState.aUsers = (oResult.entities || [])
496
+ .map((oRecord) => mapUserRecord(oRecord))
497
+ .filter((oUser) => !!oUser.id)
498
+ .filter((oUser) => normalizeGuid(oUser.id) !== oState.sCurrentUserId);
499
+ }
500
+
501
+ async function refreshWorkspace() {
502
+ oState.bLoading = true;
503
+ setStatus('Refreshing task records and share targets from Dataverse...', 'live');
504
+ renderAll();
505
+
506
+ try {
507
+ await Promise.all([loadTasks(), loadUsers()]);
508
+ setStatus('Dataverse task workspace is up to date.', 'live');
509
+ if (oState.aUsers.length === 0) {
510
+ setShareStatus('No Dataverse users were returned from systemusers.', 'warning');
511
+ } else if (getSelectedTask()) {
512
+ setShareStatus('Ready to share the selected Dataverse task.', 'live');
513
+ } else {
514
+ setShareStatus('Select a task, choose a user, then grant access.', 'live');
515
+ }
516
+ } catch (oError) {
517
+ console.error('Failed to refresh Dataverse workspace:', oError);
518
+ setStatus('Failed to load Dataverse data: ' + (oError.message || String(oError)), 'error');
519
+ } finally {
520
+ oState.bLoading = false;
521
+ renderAll();
522
+ }
523
+ }
524
+
525
+ async function handleSaveTask(oEvent) {
526
+ oEvent.preventDefault();
527
+ oState.bSaving = true;
528
+ setStatus('Saving task record to Dataverse...', 'live');
529
+ renderActionState();
530
+
531
+ try {
532
+ const oPayload = buildTaskPayload();
533
+ const oSelectedTask = getSelectedTask();
534
+
535
+ if (oSelectedTask) {
536
+ await updateItem(sTaskTableName, sTaskPrimaryKey, oSelectedTask.id, oPayload);
537
+ setStatus('Task record updated in Dataverse.', 'success');
538
+ } else {
539
+ const oCreated = await createItem(sTaskTableName, sTaskPrimaryKey, oPayload);
540
+ const sCreatedId = normalizeGuid(oCreated && (oCreated.activityid || oCreated.id || oCreated[sTaskPrimaryKey]));
541
+ setStatus('Task record created in Dataverse.', 'success');
542
+ await loadTasks();
543
+ if (sCreatedId) {
544
+ const oCreatedTask = oState.aTasks.find((oTask) => normalizeGuid(oTask.id) === sCreatedId);
545
+ if (oCreatedTask) {
546
+ oState.sSelectedTaskId = oCreatedTask.id;
547
+ fillForm(oCreatedTask);
548
+ }
549
+ }
550
+ }
551
+
552
+ await refreshWorkspace();
553
+ } catch (oError) {
554
+ console.error('Failed to save task:', oError);
555
+ setStatus('Save failed: ' + (oError.message || String(oError)), 'error');
556
+ } finally {
557
+ oState.bSaving = false;
558
+ renderAll();
559
+ }
560
+ }
561
+
562
+ async function handleDeleteTask() {
563
+ const oSelectedTask = getSelectedTask();
564
+ if (!oSelectedTask) {
565
+ return;
566
+ }
567
+
568
+ const bConfirmed = window.confirm('Delete the task "' + oSelectedTask.subject + '" from Dataverse?');
569
+ if (!bConfirmed) {
570
+ return;
571
+ }
572
+
573
+ oState.bSaving = true;
574
+ setStatus('Deleting task record from Dataverse...', 'warning');
575
+ renderActionState();
576
+
577
+ try {
578
+ await deleteItem(sTaskTableName, sTaskPrimaryKey, oSelectedTask.id);
579
+ resetComposer();
580
+ await refreshWorkspace();
581
+ setStatus('Task record deleted from Dataverse.', 'success');
582
+ } catch (oError) {
583
+ console.error('Failed to delete task:', oError);
584
+ setStatus('Delete failed: ' + (oError.message || String(oError)), 'error');
585
+ } finally {
586
+ oState.bSaving = false;
587
+ renderAll();
588
+ }
589
+ }
590
+
591
+ function getAccessLevelById(sId) {
592
+ return aAccessLevels.find((oLevel) => oLevel.id === sId) || aAccessLevels[0];
593
+ }
594
+
595
+ function buildShareParams(sTaskId, sUserId, sMask) {
596
+ return {
597
+ Target: {
598
+ '@odata.type': 'Microsoft.Dynamics.CRM.task',
599
+ activityid: sTaskId,
600
+ },
601
+ PrincipalAccess: {
602
+ AccessMask: sMask,
603
+ Principal: {
604
+ '@odata.type': 'Microsoft.Dynamics.CRM.systemuser',
605
+ systemuserid: sUserId,
606
+ },
607
+ },
608
+ };
609
+ }
610
+
611
+ async function shareTaskRecord(sTaskId, sUserId, sMask) {
612
+ const oParams = buildShareParams(sTaskId, sUserId, sMask);
613
+
614
+ try {
615
+ await callUnboundAction(sTaskTableName, sTaskPrimaryKey, 'GrantAccess', oParams);
616
+ } catch (oError) {
617
+ const sMessage = String(oError && oError.message ? oError.message : oError).toLowerCase();
618
+ const bCanFallback = sMessage.includes('already') || sMessage.includes('existing') || sMessage.includes('principal');
619
+
620
+ if (!bCanFallback) {
621
+ throw oError;
622
+ }
623
+
624
+ await callUnboundAction(sTaskTableName, sTaskPrimaryKey, 'ModifyAccess', oParams);
625
+ }
626
+ }
627
+
628
+ async function handleShareTask() {
629
+ const oSelectedTask = getSelectedTask();
630
+ const sUserId = String(oElements.eShareUser.value || '');
631
+ const oAccessLevel = getAccessLevelById(String(oElements.eShareAccess.value || 'read'));
632
+ const oUser = oState.aUsers.find((oItem) => oItem.id === sUserId) || null;
633
+
634
+ if (!oSelectedTask) {
635
+ setShareStatus('Select a task before sharing it.', 'warning');
636
+ renderActionState();
637
+ return;
638
+ }
639
+
640
+ if (!sUserId || !oUser) {
641
+ setShareStatus('Choose a Dataverse user to receive access.', 'warning');
642
+ renderActionState();
643
+ return;
644
+ }
645
+
646
+ oState.bSharing = true;
647
+ setShareStatus('Granting Dataverse access to ' + oUser.fullname + '...', 'live');
648
+ renderActionState();
649
+
650
+ try {
651
+ await shareTaskRecord(oSelectedTask.id, sUserId, oAccessLevel.mask);
652
+ oState.aShareEvents.unshift({
653
+ title: oSelectedTask.subject + ' shared',
654
+ detail: oUser.fullname + ' received ' + oAccessLevel.label.toLowerCase() + ' on ' + formatDateTimeLabel(new Date().toISOString()) + '.',
655
+ });
656
+ oState.aShareEvents = oState.aShareEvents.slice(0, 6);
657
+ setShareStatus('Dataverse access granted to ' + oUser.fullname + '.', 'success');
658
+ } catch (oError) {
659
+ console.error('Failed to share task record:', oError);
660
+ setShareStatus('Share failed: ' + (oError.message || String(oError)), 'error');
661
+ } finally {
662
+ oState.bSharing = false;
663
+ renderAll();
664
+ }
665
+ }
666
+
667
+ function wireEvents() {
668
+ oElements.eBtnRefresh.addEventListener('click', refreshWorkspace);
669
+ oElements.eBtnNewTask.addEventListener('click', resetComposer);
670
+ oElements.eBtnResetTask.addEventListener('click', () => {
671
+ fillForm(getSelectedTask());
672
+ });
673
+ oElements.eBtnDeleteTask.addEventListener('click', handleDeleteTask);
674
+ oElements.eBtnShareTask.addEventListener('click', handleShareTask);
675
+ oElements.eTaskForm.addEventListener('submit', handleSaveTask);
676
+ oElements.eTaskSearch.addEventListener('input', renderTaskList);
677
+ oElements.ePriorityFilter.addEventListener('change', renderTaskList);
678
+ oElements.eShareUser.addEventListener('change', renderActionState);
679
+ }
680
+
681
+ async function boot() {
682
+ wireEvents();
683
+ initDataSources({
684
+ tasks: dsEntry(sTaskPrimaryKey),
685
+ systemusers: dsEntry(sUserPrimaryKey),
686
+ });
687
+ renderAll();
688
+ fillForm(null);
689
+ await loadCurrentUser();
690
+ await refreshWorkspace();
691
+ }
692
+
693
+ boot();