You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 line
7.6KB

  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 boardId = KANBAN.boardId;
  21. /* ── Helpers ─────────────────────────────────────────────── */
  22. function post(url, data, cb) {
  23. var params = new URLSearchParams();
  24. Object.keys(data).forEach(function (k) { params.append(k, data[k]); });
  25. fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString() })
  26. .then(function (r) { return r.json(); })
  27. .then(cb)
  28. .catch(function (e) { showError('Network error: ' + e); });
  29. }
  30. function showError(msg) {
  31. errEl.textContent = msg;
  32. errEl.classList.remove('d-none');
  33. }
  34. function clearError() {
  35. errEl.textContent = '';
  36. errEl.classList.add('d-none');
  37. }
  38. /* ── Open for create ──────────────────────────────────────── */
  39. function openCreate(bId, colId, laneId) {
  40. titleEl.textContent = 'Add Card';
  41. cardIdEl.value = '';
  42. colIdEl.value = colId || '';
  43. laneIdEl.value = laneId || '';
  44. jobNumEl.value = '';
  45. jobNameEl.value = '';
  46. custNameEl.value = '';
  47. delivDateEl.value = '';
  48. qtyEl.value = '';
  49. notesEl.value = '';
  50. fullNoteEl.value = '';
  51. btnDelete.classList.add('d-none');
  52. clearError();
  53. bsModal.show();
  54. jobNumEl.focus();
  55. }
  56. /* ── Open for edit ───────────────────────────────────────── */
  57. function openEdit(id, colId, laneId, jobNum, jobName, custName, delivDate, qty, notes, fullNote) {
  58. titleEl.textContent = 'Edit Card';
  59. cardIdEl.value = id;
  60. colIdEl.value = colId;
  61. laneIdEl.value = laneId;
  62. jobNumEl.value = jobNum || '';
  63. jobNameEl.value = jobName || '';
  64. custNameEl.value = custName || '';
  65. delivDateEl.value = delivDate || '';
  66. qtyEl.value = qty || '';
  67. notesEl.value = notes || '';
  68. fullNoteEl.value = fullNote || '';
  69. btnDelete.classList.remove('d-none');
  70. clearError();
  71. bsModal.show();
  72. jobNumEl.focus();
  73. }
  74. /* ── Save ─────────────────────────────────────────────────── */
  75. btnSave.addEventListener('click', function () {
  76. clearError();
  77. var id = cardIdEl.value;
  78. var colId = colIdEl.value;
  79. var laneId = laneIdEl.value;
  80. var jNum = jobNumEl.value.trim();
  81. var jName = jobNameEl.value.trim();
  82. var cust = custNameEl.value.trim();
  83. var dDate = delivDateEl.value;
  84. var qty = qtyEl.value.trim();
  85. var notes = notesEl.value.trim();
  86. var fullNote = fullNoteEl.value;
  87. if (!jNum && !jName) {
  88. showError('Enter at least a job number or job name.');
  89. return;
  90. }
  91. if (id) {
  92. // Update existing
  93. post('/cards/' + id, { job_number: jNum, job_name: jName, customer_name: cust, delivery_date: dDate, quantity: qty, notes: notes, full_note: fullNote }, function (res) {
  94. if (res.ok) {
  95. bsModal.hide();
  96. window.KanbanBoard.onCardUpdated(id, res);
  97. } else {
  98. showError(res.error || 'Save failed.');
  99. }
  100. });
  101. } else {
  102. // Create new — if no col/lane selected show column/lane picker
  103. if (!colId || !laneId) {
  104. showError('Please choose a column and swim lane first.');
  105. return;
  106. }
  107. post('/cards', {
  108. board_id: boardId,
  109. column_id: colId,
  110. swim_lane_id: laneId,
  111. job_number: jNum,
  112. job_name: jName,
  113. customer_name: cust,
  114. delivery_date: dDate,
  115. quantity: qty,
  116. notes: notes,
  117. full_note: fullNote
  118. }, function (res) {
  119. if (res.ok) {
  120. bsModal.hide();
  121. window.KanbanBoard.onCardCreated(res);
  122. } else {
  123. showError(res.error || 'Save failed.');
  124. }
  125. });
  126. }
  127. });
  128. /* ── Delete ──────────────────────────────────────────────── */
  129. btnDelete.addEventListener('click', function () {
  130. if (!confirm('Delete this card?')) return;
  131. var id = cardIdEl.value;
  132. post('/cards/' + id + '/delete', {}, function (res) {
  133. if (res.ok) {
  134. bsModal.hide();
  135. window.KanbanBoard.onCardDeleted(id);
  136. } else {
  137. showError(res.error || 'Delete failed.');
  138. }
  139. });
  140. });
  141. /* ── Column/Lane picker when Add Card clicked with no cell ── */
  142. // Populated lazily from board data
  143. modal.addEventListener('shown.bs.modal', function () {
  144. if (!cardIdEl.value && (!colIdEl.value || !laneIdEl.value)) {
  145. injectPicker();
  146. }
  147. });
  148. function injectPicker() {
  149. if (document.getElementById('card-picker')) return;
  150. var picker = document.createElement('div');
  151. picker.id = 'card-picker';
  152. picker.className = 'row g-2 mb-3';
  153. var colSel = '<select class="form-select form-select-sm" id="pick-col"><option value="">-- Column --</option>';
  154. var laneSel = '<select class="form-select form-select-sm" id="pick-lane"><option value="">-- Swim Lane --</option>';
  155. document.querySelectorAll('.kanban-col-header').forEach(function (el) {
  156. colSel += '<option value="' + el.dataset.colId + '">' + el.querySelector('.col-label').textContent + '</option>';
  157. });
  158. document.querySelectorAll('.kanban-lane-header').forEach(function (el) {
  159. laneSel += '<option value="' + el.dataset.laneId + '">' + el.querySelector('.lane-label').textContent + '</option>';
  160. });
  161. colSel += '</select>';
  162. laneSel += '</select>';
  163. picker.innerHTML =
  164. '<div class="col"><label class="form-label small">Column</label>' + colSel + '</div>' +
  165. '<div class="col"><label class="form-label small">Swim Lane</label>' + laneSel + '</div>';
  166. var first = document.getElementById('card-job-number').closest('.mb-3');
  167. modal.querySelector('.modal-body').insertBefore(picker, first);
  168. document.getElementById('pick-col').addEventListener('change', function () {
  169. colIdEl.value = this.value;
  170. });
  171. document.getElementById('pick-lane').addEventListener('change', function () {
  172. laneIdEl.value = this.value;
  173. });
  174. }
  175. /* ── Public API ──────────────────────────────────────────── */
  176. window.KanbanModal = { openCreate: openCreate, openEdit: openEdit };
  177. })();

Powered by TurnKey Linux.