Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

243 rindas
9.1KB

  1. /* kanban-modal.js — card create/edit modal */
  2. (function () {
  3. 'use strict';
  4. var modal = document.getElementById('cardModal');
  5. var bsModal = new bootstrap.Modal(modal);
  6. var titleEl = document.getElementById('cardModalLabel');
  7. var cardIdEl = document.getElementById('card-id');
  8. var colIdEl = document.getElementById('card-column-id');
  9. var laneIdEl = document.getElementById('card-lane-id');
  10. var jobNumEl = document.getElementById('card-job-number');
  11. var jobNameEl = document.getElementById('card-job-name');
  12. var errEl = document.getElementById('card-modal-error');
  13. var btnSave = document.getElementById('btn-save-card');
  14. var btnDelete = document.getElementById('btn-delete-card');
  15. var custNameEl = document.getElementById('card-customer-name');
  16. var delivDateEl = document.getElementById('card-delivery-date');
  17. var qtyEl = document.getElementById('card-quantity');
  18. var notesEl = document.getElementById('card-notes');
  19. var fullNoteEl = document.getElementById('card-full-note');
  20. var historyWrap = document.getElementById('card-history-wrap');
  21. var historyList = document.getElementById('card-history-list');
  22. var boardId = KANBAN.boardId;
  23. /* ── Helpers ─────────────────────────────────────────────── */
  24. function post(url, data, cb) {
  25. var params = new URLSearchParams();
  26. Object.keys(data).forEach(function (k) { params.append(k, data[k]); });
  27. fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString() })
  28. .then(function (r) { return r.json(); })
  29. .then(cb)
  30. .catch(function (e) { showError('Network error: ' + e); });
  31. }
  32. function showError(msg) {
  33. errEl.textContent = msg;
  34. errEl.classList.remove('d-none');
  35. }
  36. function clearError() {
  37. errEl.textContent = '';
  38. errEl.classList.add('d-none');
  39. }
  40. function escapeHtml(s) {
  41. return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
  42. return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
  43. });
  44. }
  45. function formatMovedAt(s) {
  46. if (!s) return '';
  47. var d = new Date(String(s).replace(' ', 'T'));
  48. if (isNaN(d.getTime())) return s;
  49. return d.toLocaleString();
  50. }
  51. function loadHistory(id) {
  52. historyList.innerHTML = '';
  53. fetch('/cards/' + id + '/history')
  54. .then(function (r) { return r.json(); })
  55. .then(function (res) {
  56. if (!res.ok || !res.movements || !res.movements.length) {
  57. historyWrap.classList.add('d-none');
  58. return;
  59. }
  60. historyWrap.classList.remove('d-none');
  61. res.movements.forEach(function (m) {
  62. var li = document.createElement('li');
  63. li.className = 'list-group-item px-0 py-1';
  64. li.innerHTML =
  65. '<strong>' + escapeHtml(m.to_column_name || 'Unknown column') + '</strong>' +
  66. ' &mdash; ' + escapeHtml(m.moved_by || 'Unknown user') +
  67. ' <span class="text-muted">(' + escapeHtml(formatMovedAt(m.moved_at)) + ')</span>';
  68. historyList.appendChild(li);
  69. });
  70. })
  71. .catch(function () { historyWrap.classList.add('d-none'); });
  72. }
  73. /* ── Open for create ──────────────────────────────────────── */
  74. function openCreate(bId, colId, laneId) {
  75. titleEl.textContent = 'Add Card';
  76. cardIdEl.value = '';
  77. colIdEl.value = colId || '';
  78. laneIdEl.value = laneId || '';
  79. jobNumEl.value = '';
  80. jobNameEl.value = '';
  81. custNameEl.value = '';
  82. delivDateEl.value = '';
  83. qtyEl.value = '';
  84. notesEl.value = '';
  85. fullNoteEl.value = '';
  86. btnDelete.classList.add('d-none');
  87. historyWrap.classList.add('d-none');
  88. historyList.innerHTML = '';
  89. clearError();
  90. bsModal.show();
  91. jobNumEl.focus();
  92. }
  93. /* ── Open for edit ───────────────────────────────────────── */
  94. function openEdit(id, colId, laneId, jobNum, jobName, custName, delivDate, qty, notes, fullNote) {
  95. titleEl.textContent = 'Edit Card';
  96. cardIdEl.value = id;
  97. colIdEl.value = colId;
  98. laneIdEl.value = laneId;
  99. jobNumEl.value = jobNum || '';
  100. jobNameEl.value = jobName || '';
  101. custNameEl.value = custName || '';
  102. delivDateEl.value = delivDate || '';
  103. qtyEl.value = qty || '';
  104. notesEl.value = notes || '';
  105. fullNoteEl.value = fullNote || '';
  106. btnDelete.classList.remove('d-none');
  107. loadHistory(id);
  108. clearError();
  109. bsModal.show();
  110. jobNumEl.focus();
  111. }
  112. /* ── Save ─────────────────────────────────────────────────── */
  113. btnSave.addEventListener('click', function () {
  114. clearError();
  115. var id = cardIdEl.value;
  116. var colId = colIdEl.value;
  117. var laneId = laneIdEl.value;
  118. var jNum = jobNumEl.value.trim();
  119. var jName = jobNameEl.value.trim();
  120. var cust = custNameEl.value.trim();
  121. var dDate = delivDateEl.value;
  122. var qty = qtyEl.value.trim();
  123. var notes = notesEl.value.trim();
  124. var fullNote = fullNoteEl.value;
  125. if (!jNum && !jName) {
  126. showError('Enter at least a job number or job name.');
  127. return;
  128. }
  129. if (id) {
  130. // Update existing
  131. post('/cards/' + id, { job_number: jNum, job_name: jName, customer_name: cust, delivery_date: dDate, quantity: qty, notes: notes, full_note: fullNote }, function (res) {
  132. if (res.ok) {
  133. bsModal.hide();
  134. window.KanbanBoard.onCardUpdated(id, res);
  135. } else {
  136. showError(res.error || 'Save failed.');
  137. }
  138. });
  139. } else {
  140. // Create new — if no col/lane selected show column/lane picker
  141. if (!colId || !laneId) {
  142. showError('Please choose a column and swim lane first.');
  143. return;
  144. }
  145. post('/cards', {
  146. board_id: boardId,
  147. column_id: colId,
  148. swim_lane_id: laneId,
  149. job_number: jNum,
  150. job_name: jName,
  151. customer_name: cust,
  152. delivery_date: dDate,
  153. quantity: qty,
  154. notes: notes,
  155. full_note: fullNote
  156. }, function (res) {
  157. if (res.ok) {
  158. bsModal.hide();
  159. window.KanbanBoard.onCardCreated(res);
  160. } else {
  161. showError(res.error || 'Save failed.');
  162. }
  163. });
  164. }
  165. });
  166. /* ── Delete ──────────────────────────────────────────────── */
  167. btnDelete.addEventListener('click', function () {
  168. if (!confirm('Delete this card?')) return;
  169. var id = cardIdEl.value;
  170. post('/cards/' + id + '/delete', {}, function (res) {
  171. if (res.ok) {
  172. bsModal.hide();
  173. window.KanbanBoard.onCardDeleted(id);
  174. } else {
  175. showError(res.error || 'Delete failed.');
  176. }
  177. });
  178. });
  179. /* ── Column/Lane picker when Add Card clicked with no cell ── */
  180. // Populated lazily from board data
  181. modal.addEventListener('shown.bs.modal', function () {
  182. if (!cardIdEl.value && (!colIdEl.value || !laneIdEl.value)) {
  183. injectPicker();
  184. }
  185. });
  186. function injectPicker() {
  187. if (document.getElementById('card-picker')) return;
  188. var picker = document.createElement('div');
  189. picker.id = 'card-picker';
  190. picker.className = 'row g-2 mb-3';
  191. var colSel = '<select class="form-select form-select-sm" id="pick-col"><option value="">-- Column --</option>';
  192. var laneSel = '<select class="form-select form-select-sm" id="pick-lane"><option value="">-- Swim Lane --</option>';
  193. document.querySelectorAll('.kanban-col-header').forEach(function (el) {
  194. colSel += '<option value="' + el.dataset.colId + '">' + el.querySelector('.col-label').textContent + '</option>';
  195. });
  196. document.querySelectorAll('.kanban-lane-header').forEach(function (el) {
  197. laneSel += '<option value="' + el.dataset.laneId + '">' + el.querySelector('.lane-label').textContent + '</option>';
  198. });
  199. colSel += '</select>';
  200. laneSel += '</select>';
  201. picker.innerHTML =
  202. '<div class="col"><label class="form-label small">Column</label>' + colSel + '</div>' +
  203. '<div class="col"><label class="form-label small">Swim Lane</label>' + laneSel + '</div>';
  204. var first = document.getElementById('card-job-number').closest('.mb-3');
  205. modal.querySelector('.modal-body').insertBefore(picker, first);
  206. document.getElementById('pick-col').addEventListener('change', function () {
  207. colIdEl.value = this.value;
  208. });
  209. document.getElementById('pick-lane').addEventListener('change', function () {
  210. laneIdEl.value = this.value;
  211. });
  212. }
  213. /* ── Public API ──────────────────────────────────────────── */
  214. window.KanbanModal = { openCreate: openCreate, openEdit: openEdit };
  215. })();

Powered by TurnKey Linux.