codeapp-js 0.2.2 → 1.0.0

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 (176) 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/codeApp/.power/schemas/appschemas/dataSourcesInfo.ts +6275 -0
  15. package/codeApp/.power/schemas/jira/jira.Schema.json +6903 -0
  16. package/codeApp/.power/schemas/keyvault/keyvault.Schema.json +1600 -0
  17. package/{examples/combined demo/.power/schemas/office365groups/office365groups.Schema.json → codeApp/.power/schemas/office365groups/office365groups.Schema.json} +2203 -2203
  18. package/codeApp/.power/schemas/teams/teams.Schema.json +11112 -0
  19. package/codeApp/dist/codeapp.js +103 -1043
  20. package/codeApp/dist/connectors/azureKeyvault.js +459 -0
  21. package/codeApp/dist/connectors/jira.js +1247 -0
  22. package/codeApp/dist/connectors/office365groups.js +642 -0
  23. package/codeApp/dist/connectors/office365users.js +513 -0
  24. package/codeApp/dist/connectors/outlook.js +1393 -0
  25. package/{examples/kanban/dist → codeApp/dist/connectors}/sharepoint.js +466 -339
  26. package/codeApp/dist/connectors/sql.js +149 -0
  27. package/codeApp/dist/connectors/teams.js +280 -0
  28. package/codeApp/dist/index.js +1 -1
  29. package/codeApp/dist/power-apps-data.js +725 -176
  30. package/codeApp/src/generated/index.ts +12 -0
  31. package/codeApp/src/generated/models/AzureKeyVaultModel.ts +107 -0
  32. package/codeApp/src/generated/models/JiraModel.ts +501 -0
  33. package/codeApp/src/generated/services/AzureKeyVaultService.ts +257 -0
  34. package/codeApp/src/generated/services/JiraService.ts +1124 -0
  35. package/examples/{kanban → apps/kanban}/dist/dataverse.js +94 -94
  36. package/examples/{kanban → apps/kanban}/dist/index.css +605 -605
  37. package/examples/{kanban → apps/kanban}/dist/index.html +21 -21
  38. package/examples/{kanban → apps/kanban}/dist/index.js +860 -860
  39. package/examples/{kanban → apps/kanban}/dist/office365groups.js +97 -97
  40. package/examples/apps/kanban/dist/office365users.js +451 -0
  41. package/examples/{kanban → apps/kanban}/dist/outlook.js +162 -162
  42. package/examples/{planning Poker/dist/power-apps-data.js → apps/kanban/dist/power-apps-data.js} +2953 -2953
  43. package/{dev files/sharepoint.js → examples/apps/kanban/dist/sharepoint.js} +195 -99
  44. package/examples/{kanban → apps/kanban}/power.config.json +35 -35
  45. package/examples/{planning Poker → apps/planning Poker}/additional files/customizations (tables).xml +6428 -6428
  46. package/examples/{planning Poker → apps/planning Poker}/additional files/dataverse-tables.json +165 -165
  47. package/examples/{planning Poker → apps/planning Poker}/additional files/readme.md +122 -122
  48. package/examples/{planning Poker → apps/planning Poker}/dist/dataverse.js +78 -78
  49. package/examples/{planning Poker → apps/planning Poker}/dist/index.html +198 -198
  50. package/examples/{planning Poker → apps/planning Poker}/dist/index.js +954 -954
  51. package/examples/{kanban/dist/power-apps-data.js → apps/planning Poker/dist/power-apps-data.js } +2953 -2953
  52. package/examples/{planning Poker → apps/planning Poker}/dist/styles.css +815 -815
  53. package/examples/{planning Poker → apps/planning Poker}/power.config.json +50 -50
  54. package/examples/{solution explorer → apps/solution explorer}/dist/codeapp.js +1098 -1098
  55. package/examples/{solution explorer → apps/solution explorer}/dist/index.html +80 -80
  56. package/examples/{solution explorer → apps/solution explorer}/dist/index.js +735 -735
  57. package/examples/{solution explorer → apps/solution explorer}/dist/styles.css +571 -571
  58. package/examples/{solution explorer → apps/solution explorer}/power.config.json +150 -150
  59. package/examples/{todo → apps/todo}/dist/dataverse.js +64 -64
  60. package/examples/{todo → apps/todo}/dist/index.html +75 -75
  61. package/examples/{todo → apps/todo}/dist/index.js +8 -8
  62. package/examples/{todo → apps/todo}/dist/power-apps-data.js +2953 -2953
  63. package/examples/{todo → apps/todo}/dist/renderer.js +375 -375
  64. package/examples/{todo → apps/todo}/dist/styles.css +691 -691
  65. package/examples/{todo → apps/todo}/power.config.json +34 -34
  66. package/examples/combined demo/.power/schemas/appschemas/dataSourcesInfo.ts +6275 -7830
  67. package/examples/combined demo/.power/schemas/jira/jira.Schema.json +6903 -0
  68. package/examples/combined demo/.power/schemas/keyvault/keyvault.Schema.json +1600 -0
  69. package/examples/combined demo/.power/schemas/teams/teams.Schema.json +11112 -0
  70. package/examples/combined demo/dist/codeapp.js +394 -1098
  71. package/examples/combined demo/dist/index.html +29 -511
  72. package/examples/combined demo/dist/index.js +490 -470
  73. package/examples/combined demo/dist/office365users.js +513 -0
  74. package/examples/combined demo/dist/outlook.js +1393 -0
  75. package/examples/combined demo/dist/power-apps-data.js +3079 -3006
  76. package/examples/combined demo/dist/styles.css +483 -0
  77. package/examples/combined demo/power.config.json +33 -42
  78. package/examples/combined demo/src/generated/index.ts +12 -14
  79. package/examples/combined demo/src/generated/models/AzureKeyVaultModel.ts +107 -0
  80. package/examples/combined demo/src/generated/models/JiraModel.ts +501 -0
  81. package/examples/combined demo/src/generated/services/AzureKeyVaultService.ts +257 -0
  82. package/examples/combined demo/src/generated/services/JiraService.ts +1124 -0
  83. package/examples/dataverse Demo/dist/codeapp.js +394 -1085
  84. package/examples/{outlook Demo2/OutlookDemo_1_0_0_1.zip → dataverse Demo/dist/icon-512.png} +0 -0
  85. package/examples/dataverse Demo/dist/index.html +146 -54
  86. package/examples/dataverse Demo/dist/index.js +693 -83
  87. package/examples/dataverse Demo/dist/power-apps-data.js +3079 -2911
  88. package/examples/dataverse Demo/dist/styles.css +528 -0
  89. package/examples/dataverse Demo/power.config.json +41 -35
  90. package/examples/dataverse Demo/readme.md +79 -79
  91. package/examples/groups Demo/dist/codeapp.js +394 -1085
  92. package/examples/groups Demo/dist/icon-512.png +0 -0
  93. package/examples/groups Demo/dist/index.html +21 -25
  94. package/examples/groups Demo/dist/index.js +304 -113
  95. package/examples/groups Demo/dist/office365groups.js +642 -0
  96. package/examples/groups Demo/dist/power-apps-data.js +3079 -2911
  97. package/examples/groups Demo/dist/styles.css +509 -0
  98. package/examples/groups Demo/power.config.json +25 -25
  99. package/examples/myProfile/dist/codeapp.js +398 -0
  100. package/examples/myProfile/dist/index.html +21 -184
  101. package/examples/myProfile/dist/index.js +324 -141
  102. package/examples/myProfile/dist/office365users.js +517 -169
  103. package/examples/myProfile/dist/power-apps-data.js +3080 -2953
  104. package/examples/myProfile/dist/styles.css +458 -0
  105. package/examples/myProfile/power.config.json +24 -23
  106. package/examples/outlook Demo/dist/codeapp.js +394 -1085
  107. package/examples/outlook Demo/dist/index.html +150 -35
  108. package/examples/outlook Demo/dist/index.js +516 -170
  109. package/examples/outlook Demo/dist/outlook.js +1393 -121
  110. package/examples/outlook Demo/dist/power-apps-data.js +3079 -2911
  111. package/examples/outlook Demo/dist/styles.css +408 -84
  112. package/examples/outlook Demo/power.config.json +24 -23
  113. package/examples/outlook Demo/readme.md +92 -82
  114. package/examples/sharePoint Demo/dist/codeapp.js +394 -1085
  115. package/examples/sharePoint Demo/dist/icon-512.png +0 -0
  116. package/examples/sharePoint Demo/dist/index.html +22 -255
  117. package/examples/sharePoint Demo/dist/index.js +899 -262
  118. package/examples/sharePoint Demo/dist/power-apps-data.js +3079 -2911
  119. package/examples/sharePoint Demo/dist/sharepoint.js +466 -0
  120. package/examples/sharePoint Demo/dist/styles.css +587 -0
  121. package/examples/sharePoint Demo/power.config.json +23 -22
  122. package/package.json +1 -1
  123. package/readme.md +479 -61
  124. package/.github/instructions/wyattdave.instructions.md +0 -39
  125. package/.vscode/settings.json +0 -6
  126. package/dev files/dataverse.js +0 -105
  127. package/dev files/office365groups.js +0 -65
  128. package/dev files/office365users.js +0 -169
  129. package/dev files/outlook.js +0 -330
  130. package/examples/combined demo/.power/schemas/office365/office365.Schema.json +0 -21098
  131. package/examples/combined demo/.power/schemas/office365users/office365users.Schema.json +0 -2094
  132. package/examples/kanban/agent/decision-log.md +0 -9
  133. package/examples/kanban/agent/mockup-01-editorial-glass.html +0 -159
  134. package/examples/kanban/agent/mockup-02-dark-rail.html +0 -147
  135. package/examples/kanban/agent/mockup-03-paper-grid.html +0 -114
  136. package/examples/kanban/agent/mockup-04-neon-minimal.html +0 -141
  137. package/examples/kanban/agent/mockup-05-mono-architect.html +0 -119
  138. package/examples/kanban/dist/environmentVar.js +0 -55
  139. package/examples/kanban/dist/office365users.js +0 -169
  140. package/examples/kanban/src/generated/index.ts +0 -14
  141. package/examples/outlook Demo2/agent/decision-log.md +0 -7
  142. package/examples/outlook Demo2/dist/codeapp.js +0 -1334
  143. package/examples/outlook Demo2/dist/index.html +0 -98
  144. package/examples/outlook Demo2/dist/index.js +0 -346
  145. package/examples/outlook Demo2/dist/styles.css +0 -639
  146. package/examples/outlook Demo2/power.config.json +0 -23
  147. package/examples/planning Poker/.vscode/settings.json +0 -5
  148. package/examples/sharePoint Demo/agent/decision-log.md +0 -17
  149. package/examples/solution explorer/agent/decision-log.md +0 -27
  150. package/examples/solution explorer/agent/mockup-01-swiss-grid.html +0 -452
  151. package/examples/solution explorer/agent/mockup-02-dark-glass.html +0 -496
  152. package/examples/solution explorer/agent/mockup-03-paper-console.html +0 -510
  153. package/examples/solution explorer/agent/mockup-04-neon-noir.html +0 -546
  154. package/examples/solution explorer/agent/mockup-05-zen-garden.html +0 -534
  155. package/examples/solution explorer/dist/power-apps-data.js +0 -3007
  156. package/scripts/build-power-sdk.mjs +0 -69
  157. /package/{examples/kanban → codeApp}/src/generated/models/Office365GroupsModel.ts +0 -0
  158. /package/{examples/kanban → codeApp}/src/generated/models/Office365OutlookModel.ts +0 -0
  159. /package/{examples/kanban → codeApp}/src/generated/models/Office365UsersModel.ts +0 -0
  160. /package/{examples/kanban → codeApp}/src/generated/services/Office365GroupsService.ts +0 -0
  161. /package/{examples/kanban → codeApp}/src/generated/services/Office365OutlookService.ts +0 -0
  162. /package/{examples/kanban → codeApp}/src/generated/services/Office365UsersService.ts +0 -0
  163. /package/{dev files → examples/apps/kanban/dist}/environmentVar.js +0 -0
  164. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/index.ts +0 -0
  165. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365GroupsModel.ts +0 -0
  166. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365OutlookModel.ts +0 -0
  167. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365UsersModel.ts +0 -0
  168. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365GroupsService.ts +0 -0
  169. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365OutlookService.ts +0 -0
  170. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365UsersService.ts +0 -0
  171. /package/examples/{planning Poker → apps/planning Poker}/additional files/AgilePoker_1_0_0_1.zip +0 -0
  172. /package/examples/{planning Poker → apps/planning Poker}/additional files/PokerTables_1_0_0_1.zip +0 -0
  173. /package/examples/{outlook Demo2 → apps/solution explorer}/dist/icon-512.png +0 -0
  174. /package/examples/{outlook Demo2 → apps/solution explorer}/dist/power-apps-data.js +0 -0
  175. /package/examples/{todo → apps/todo}/dist/icon192.png +0 -0
  176. /package/examples/{solution explorer → combined demo}/dist/icon-512.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();