暫無描述
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.

app-user-view-account.js 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. /**
  2. * App User View - Account (js)
  3. */
  4. 'use strict';
  5. document.addEventListener('DOMContentLoaded', function (e) {
  6. // Variable declaration for table
  7. const dt_project_table = document.querySelector('.datatable-project'),
  8. dt_invoice_table = document.querySelector('.datatable-invoice');
  9. // Project datatable
  10. // --------------------------------------------------------------------
  11. if (dt_project_table) {
  12. var dt_project = new DataTable(dt_project_table, {
  13. ajax: assetsPath + 'json/user-profile.json', // JSON file to add data
  14. columns: [
  15. { data: 'id' },
  16. { data: 'id', orderable: false, render: DataTable.render.select() },
  17. { data: 'project_name' },
  18. { data: 'project_leader' },
  19. { data: 'id' },
  20. { data: 'status' },
  21. { data: 'id' }
  22. ],
  23. columnDefs: [
  24. {
  25. // For Responsive
  26. className: 'control',
  27. searchable: false,
  28. orderable: false,
  29. responsivePriority: 2,
  30. targets: 0,
  31. render: function (data, type, full, meta) {
  32. return '';
  33. }
  34. },
  35. {
  36. // For Checkboxes
  37. targets: 1,
  38. orderable: false,
  39. searchable: false,
  40. responsivePriority: 3,
  41. checkboxes: true,
  42. render: function () {
  43. return '<input type="checkbox" class="dt-checkboxes form-check-input">';
  44. },
  45. checkboxes: {
  46. selectAllRender: '<input type="checkbox" class="form-check-input">'
  47. }
  48. },
  49. {
  50. targets: 2,
  51. responsivePriority: 4,
  52. render: function (data, type, full, meta) {
  53. var userImg = full['project_img'],
  54. name = full['project_name'],
  55. date = full['date'];
  56. var output;
  57. if (userImg) {
  58. // For Avatar image
  59. output =
  60. '<img src="' + assetsPath + 'img/icons/brands/' + userImg + '" alt="Avatar" class="rounded-circle">';
  61. } else {
  62. // For Avatar badge
  63. var stateNum = Math.floor(Math.random() * 6);
  64. var states = ['success', 'danger', 'warning', 'info', 'primary', 'secondary'];
  65. var state = states[stateNum],
  66. initials = name.match(/\b\w/g) || [];
  67. initials = ((initials.shift() || '') + (initials.pop() || '')).toUpperCase();
  68. output = '<span class="avatar-initial rounded-circle bg-label-' + state + '">' + initials + '</span>';
  69. }
  70. // Creates full output for row
  71. var rowOutput =
  72. '<div class="d-flex justify-content-left align-items-center">' +
  73. '<div class="avatar-wrapper">' +
  74. '<div class="avatar avatar-sm me-3">' +
  75. output +
  76. '</div>' +
  77. '</div>' +
  78. '<div class="d-flex flex-column gap-50">' +
  79. '<span class="text-truncate fw-medium text-heading">' +
  80. name +
  81. '</span>' +
  82. '<small class="text-truncate">' +
  83. date +
  84. '</small>' +
  85. '</div>' +
  86. '</div>';
  87. return rowOutput;
  88. }
  89. },
  90. {
  91. // Task
  92. targets: 3,
  93. render: function (data, type, full, meta) {
  94. var task = full['project_leader'];
  95. return '<span class="text-heading">' + task + '</span>';
  96. }
  97. },
  98. {
  99. targets: 4,
  100. orderable: false,
  101. searchable: false,
  102. render: function (data, type, full, meta) {
  103. const team = full['team'];
  104. let teamItem = '';
  105. let teamCount = 0;
  106. // Iterate through team members and generate the list items
  107. for (let i = 0; i < team.length; i++) {
  108. teamItem += `
  109. <li data-bs-toggle="tooltip" data-popup="tooltip-custom" data-bs-placement="top" title="Kim Karlos" class="avatar avatar-xs pull-up">
  110. <img class="rounded-circle" src="${assetsPath}img/avatars/${team[i]}" alt="Avatar">
  111. </li>
  112. `;
  113. teamCount++;
  114. if (teamCount > 2) break;
  115. }
  116. // Check if there are more than 2 team members, and add the remaining avatars
  117. if (teamCount > 2) {
  118. const remainingAvatars = team.length - 3;
  119. if (remainingAvatars > 0) {
  120. teamItem += `
  121. <li class="avatar avatar-xs">
  122. <span class="avatar-initial rounded-circle pull-up" data-bs-toggle="tooltip" data-bs-placement="top" title="${remainingAvatars} more">+${remainingAvatars}</span>
  123. </li>
  124. `;
  125. }
  126. }
  127. // Combine the team items into the final output
  128. const teamOutput = `
  129. <div class="d-flex align-items-center">
  130. <ul class="list-unstyled d-flex align-items-center avatar-group mb-0 z-2">
  131. ${teamItem}
  132. </ul>
  133. </div>
  134. `;
  135. return teamOutput;
  136. }
  137. },
  138. {
  139. targets: -2,
  140. render: function (data, type, full, meta) {
  141. const statusNumber = full['status'];
  142. return `
  143. <div class="d-flex align-items-center">
  144. <div class="progress w-100 me-3" style="height: 6px;">
  145. <div class="progress-bar" style="width: ${statusNumber}" aria-valuenow="${statusNumber}" aria-valuemin="0" aria-valuemax="100"></div>
  146. </div>
  147. <span class="text-heading">${statusNumber}</span>
  148. </div>
  149. `;
  150. }
  151. },
  152. {
  153. // Actions
  154. targets: -1,
  155. searchable: false,
  156. title: 'Action',
  157. orderable: false,
  158. render: function (data, type, full, meta) {
  159. return (
  160. '<div class="d-inline-block">' +
  161. '<a href="javascript:;" class="btn btn-icon dropdown-toggle hide-arrow" data-bs-toggle="dropdown"><i class="icon-base bx bx-dots-vertical-rounded icon-md"></i></a>' +
  162. '<div class="dropdown-menu dropdown-menu-end m-0">' +
  163. '<a href="javascript:;" class="dropdown-item">Details</a>' +
  164. '<a href="javascript:;" class="dropdown-item">Archive</a>' +
  165. '<div class="dropdown-divider"></div>' +
  166. '<a href="javascript:;" class="dropdown-item text-danger delete-record">Delete</a>' +
  167. '</div>' +
  168. '</div>'
  169. );
  170. }
  171. }
  172. ],
  173. select: {
  174. style: 'multi',
  175. selector: 'td:nth-child(2)'
  176. },
  177. order: [[2, 'desc']],
  178. layout: {
  179. topStart: {
  180. rowClass: 'row mx-md-2 justify-content-between',
  181. features: [
  182. {
  183. pageLength: {
  184. menu: [7, 10, 25, 50, 75, 100]
  185. }
  186. }
  187. ]
  188. },
  189. topEnd: {
  190. search: {
  191. placeholder: 'Search Project',
  192. text: '_INPUT_'
  193. }
  194. },
  195. bottomStart: {
  196. rowClass: 'row mx-2 justify-content-between',
  197. features: ['info']
  198. },
  199. bottomEnd: 'paging'
  200. },
  201. displayLength: 7,
  202. language: {
  203. lengthMenu: '_MENU_',
  204. paginate: {
  205. next: '<i class="icon-base bx bx-chevron-right scaleX-n1-rtl icon-18px"></i>',
  206. previous: '<i class="icon-base bx bx-chevron-left scaleX-n1-rtl icon-18px"></i>',
  207. first: '<i class="icon-base bx bx-chevrons-left scaleX-n1-rtl icon-18px"></i>',
  208. last: '<i class="icon-base bx bx-chevrons-right scaleX-n1-rtl icon-18px"></i>'
  209. }
  210. },
  211. // For responsive popup
  212. responsive: {
  213. details: {
  214. display: DataTable.Responsive.display.modal({
  215. header: function (row) {
  216. const data = row.data();
  217. return 'Details of ' + data['project_name'];
  218. }
  219. }),
  220. type: 'column',
  221. renderer: function (api, rowIdx, columns) {
  222. const data = columns
  223. .map(function (col) {
  224. return col.title !== '' // Do not show row in modal popup if title is blank (for check box)
  225. ? `<tr data-dt-row="${col.rowIndex}" data-dt-column="${col.columnIndex}">
  226. <td>${col.title}:</td>
  227. <td>${col.data}</td>
  228. </tr>`
  229. : '';
  230. })
  231. .join('');
  232. if (data) {
  233. const div = document.createElement('div');
  234. div.classList.add('table-responsive');
  235. const table = document.createElement('table');
  236. div.appendChild(table);
  237. table.classList.add('table');
  238. const tbody = document.createElement('tbody');
  239. tbody.innerHTML = data;
  240. table.appendChild(tbody);
  241. return div;
  242. }
  243. return false;
  244. }
  245. }
  246. }
  247. });
  248. //? The 'delete-record' class is necessary for the functionality of the following code.
  249. document.addEventListener('click', function (e) {
  250. if (e.target.classList.contains('delete-record')) {
  251. dt_project.row(e.target.closest('tr')).remove().draw();
  252. const modalEl = document.querySelector('.dtr-bs-modal');
  253. if (modalEl && modalEl.classList.contains('show')) {
  254. const modal = bootstrap.Modal.getInstance(modalEl);
  255. modal?.hide();
  256. }
  257. }
  258. });
  259. }
  260. // Invoice datatable
  261. // --------------------------------------------------------------------
  262. if (dt_invoice_table) {
  263. let tableTitle = document.createElement('h5');
  264. tableTitle.classList.add('card-title', 'mb-0', 'text-md-start', 'text-center', 'pt-6', 'pt-md-0');
  265. tableTitle.innerHTML = 'Invoice List';
  266. const dt_invoice = new DataTable(dt_invoice_table, {
  267. ajax: assetsPath + 'json/invoice-list.json', // JSON file to add data
  268. columns: [
  269. // columns according to JSON
  270. { data: 'id' },
  271. { data: 'invoice_id' },
  272. { data: 'invoice_status' },
  273. { data: 'total' },
  274. { data: 'issued_date' },
  275. { data: 'action' }
  276. ],
  277. columnDefs: [
  278. {
  279. // For Responsive
  280. className: 'control',
  281. orderable: false,
  282. searchable: false,
  283. responsivePriority: 2,
  284. targets: 0,
  285. render: function (data, type, full, meta) {
  286. return '';
  287. }
  288. },
  289. {
  290. // Invoice ID
  291. targets: 1,
  292. render: (data, type, full, meta) => {
  293. const invoiceId = full['invoice_id'];
  294. // Creates full output for row
  295. const rowOutput = `<a href="${baseUrl}app/invoice/preview"><span>#${invoiceId}</span></a>`;
  296. return rowOutput;
  297. }
  298. },
  299. {
  300. // Invoice status
  301. targets: 2,
  302. render: (data, type, full, meta) => {
  303. const invoiceStatus = full['invoice_status'];
  304. const dueDate = full['due_date'];
  305. const balance = full['balance'];
  306. const roleBadgeObj = {
  307. Sent: `<span class="badge badge-center d-flex align-items-center justify-content-center rounded-pill bg-label-success w-px-30 h-px-30"><i class="bx bx-check icon-xs"></i></span>`,
  308. Draft: `<span class="badge badge-center d-flex align-items-center justify-content-center rounded-pill bg-label-primary w-px-30 h-px-30"><i class="bx bx-folder icon-xs"></i></span>`,
  309. 'Past Due': `<span class="badge badge-center d-flex align-items-center justify-content-center rounded-pill bg-label-danger w-px-30 h-px-30"><i class="bx bx-error icon-xs"></i></span>`,
  310. 'Partial Payment': `<span class="badge badge-center d-flex align-items-center justify-content-center rounded-pill bg-label-secondary w-px-30 h-px-30"><i class="bx bx-envelope icon-xs"></i></span>`,
  311. Paid: `<span class="badge badge-center d-flex align-items-center justify-content-center rounded-pill bg-label-warning w-px-30 h-px-30"><i class="bx bx-pie-chart-alt icon-xs"></i></span>`,
  312. Downloaded: `<span class="badge badge-center d-flex align-items-center justify-content-center rounded-pill bg-label-info w-px-30 h-px-30"><i class="bx bx-down-arrow-alt icon-xs"></i></span>`
  313. };
  314. return `
  315. <span class='d-inline-block' data-bs-toggle='tooltip' data-bs-html='true'
  316. title='<span>${invoiceStatus}<br>
  317. <span class="fw-medium">Balance:</span> ${balance}<br>
  318. <span class="fw-medium">Due Date:</span> ${dueDate}</span>'>
  319. ${roleBadgeObj[invoiceStatus]}
  320. </span>
  321. `;
  322. }
  323. },
  324. {
  325. // Total Invoice Amount
  326. targets: 3,
  327. render: function (data, type, full, meta) {
  328. const total = full['total'];
  329. return '$' + total;
  330. }
  331. },
  332. {
  333. // Actions
  334. targets: -1,
  335. title: 'Actions',
  336. orderable: false,
  337. render: (data, type, full, meta) => {
  338. return `
  339. <div class="d-flex align-items-center">
  340. <a href="javascript:;" class="btn btn-icon delete-record"><i class="bx bx-trash icon-md"></i></a>
  341. <a href="${baseUrl}app/invoice/preview" class="btn btn-icon" data-bs-toggle="tooltip" title="Preview">
  342. <i class="bx bx-show icon-md"></i>
  343. </a>
  344. <div class="d-inline-block">
  345. <a href="javascript:;" class="btn btn-icon dropdown-toggle hide-arrow" data-bs-toggle="dropdown">
  346. <i class="bx bx-dots-vertical-rounded icon-md"></i>
  347. </a>
  348. <div class="dropdown-menu dropdown-menu-end m-0">
  349. <a href="javascript:;" class="dropdown-item">Download</a>
  350. <a href="${baseUrl}app/invoice/edit" class="dropdown-item">Edit</a>
  351. <a href="javascript:;" class="dropdown-item">Duplicate</a>
  352. </div>
  353. </div>
  354. </div>
  355. `;
  356. }
  357. }
  358. ],
  359. order: [[1, 'desc']],
  360. layout: {
  361. topStart: {
  362. rowClass: 'row border-bottom mx-0 px-3',
  363. features: [tableTitle]
  364. },
  365. topEnd: {
  366. features: [
  367. {
  368. pageLength: {
  369. menu: [10, 25, 50, 100],
  370. text: '_MENU_'
  371. }
  372. },
  373. {
  374. buttons: [
  375. {
  376. extend: 'collection',
  377. className: 'btn btn-label-secondary dropdown-toggle float-sm-end mb-3 mb-md-0 mt-md-0 mt-5',
  378. text: '<i class="icon-base bx bx-export icon-sm me-2"></i>Export',
  379. buttons: [
  380. {
  381. extend: 'print',
  382. text: '<i class="icon-base bx bx-printer me-2"></i>Print',
  383. className: 'dropdown-item',
  384. exportOptions: { columns: [1, 2, 3, 4] }
  385. },
  386. {
  387. extend: 'csv',
  388. text: '<i class="icon-base bx bx-file me-2"></i>Csv',
  389. className: 'dropdown-item',
  390. exportOptions: { columns: [1, 2, 3, 4] }
  391. },
  392. {
  393. extend: 'excel',
  394. text: '<i class="icon-base bx bxs-file-export me-2"></i>Excel',
  395. className: 'dropdown-item',
  396. exportOptions: { columns: [1, 2, 3, 4] }
  397. },
  398. {
  399. extend: 'pdf',
  400. text: '<i class="icon-base bx bxs-file-pdf me-2"></i>Pdf',
  401. className: 'dropdown-item',
  402. exportOptions: { columns: [1, 2, 3, 4] }
  403. },
  404. {
  405. extend: 'copy',
  406. text: '<i class="icon-base bx bx-copy me-2"></i>Copy',
  407. className: 'dropdown-item',
  408. exportOptions: { columns: [1, 2, 3, 4] }
  409. }
  410. ]
  411. }
  412. ]
  413. }
  414. ]
  415. },
  416. bottomStart: {
  417. rowClass: 'row mx-3 justify-content-between',
  418. features: ['info']
  419. },
  420. bottomEnd: 'paging'
  421. },
  422. language: {
  423. paginate: {
  424. next: '<i class="icon-base bx bx-chevron-right scaleX-n1-rtl icon-18px"></i>',
  425. previous: '<i class="icon-base bx bx-chevron-left scaleX-n1-rtl icon-18px"></i>',
  426. first: '<i class="icon-base bx bx-chevrons-left scaleX-n1-rtl icon-18px"></i>',
  427. last: '<i class="icon-base bx bx-chevrons-right scaleX-n1-rtl icon-18px"></i>'
  428. }
  429. },
  430. // For responsive popup
  431. responsive: {
  432. details: {
  433. display: DataTable.Responsive.display.modal({
  434. header: function (row) {
  435. const data = row.data();
  436. return 'Details of ' + data['invoice_id'];
  437. }
  438. }),
  439. type: 'column',
  440. renderer: function (api, rowIdx, columns) {
  441. const data = columns
  442. .map(function (col) {
  443. return col.title !== '' // Do not show row in modal popup if title is blank (for check box)
  444. ? `<tr data-dt-row="${col.rowIndex}" data-dt-column="${col.columnIndex}">
  445. <td>${col.title}:</td>
  446. <td>${col.data}</td>
  447. </tr>`
  448. : '';
  449. })
  450. .join('');
  451. if (data) {
  452. const div = document.createElement('div');
  453. div.classList.add('table-responsive');
  454. const table = document.createElement('table');
  455. div.appendChild(table);
  456. table.classList.add('table');
  457. const tbody = document.createElement('tbody');
  458. tbody.innerHTML = data;
  459. table.appendChild(tbody);
  460. return div;
  461. }
  462. return false;
  463. }
  464. }
  465. }
  466. });
  467. //? The 'delete-record' class is necessary for the functionality of the following code.
  468. function deleteRecord(event) {
  469. let row = document.querySelector('.dtr-expanded');
  470. if (event) {
  471. row = event.target.parentElement.closest('tr');
  472. }
  473. if (row) {
  474. dt_invoice.row(row).remove().draw();
  475. }
  476. }
  477. function bindDeleteEvent() {
  478. const dt_invoice_table = document.querySelector('.datatable-invoice');
  479. const modal = document.querySelector('.dtr-bs-modal');
  480. if (dt_invoice_table && dt_invoice_table.classList.contains('collapsed')) {
  481. if (modal) {
  482. modal.addEventListener('click', function (event) {
  483. if (event.target.parentElement.classList.contains('delete-record')) {
  484. deleteRecord();
  485. const closeButton = modal.querySelector('.btn-close');
  486. if (closeButton) closeButton.click(); // Simulates a click on the close button
  487. }
  488. });
  489. }
  490. } else {
  491. const tableBody = dt_invoice_table?.querySelector('tbody');
  492. if (tableBody) {
  493. tableBody.addEventListener('click', function (event) {
  494. if (event.target.parentElement.classList.contains('delete-record')) {
  495. deleteRecord(event);
  496. }
  497. });
  498. }
  499. }
  500. }
  501. // Initial event binding
  502. bindDeleteEvent();
  503. // Re-bind events when modal is shown or hidden
  504. document.addEventListener('show.bs.modal', function (event) {
  505. if (event.target.classList.contains('dtr-bs-modal')) {
  506. bindDeleteEvent();
  507. }
  508. });
  509. document.addEventListener('hide.bs.modal', function (event) {
  510. if (event.target.classList.contains('dtr-bs-modal')) {
  511. bindDeleteEvent();
  512. }
  513. });
  514. // On each datatable draw, initialize tooltip
  515. dt_invoice.on('draw.dt', function () {
  516. var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
  517. var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
  518. return new bootstrap.Tooltip(tooltipTriggerEl, {
  519. boundary: document.body
  520. });
  521. });
  522. });
  523. }
  524. // Filter form control to default size
  525. // ? setTimeout used for project-list and invoice-list table initialization
  526. setTimeout(() => {
  527. const elementsToModify = [
  528. { selector: '.dt-search .form-control', classToRemove: 'form-control-sm' },
  529. { selector: '.dt-length .form-select', classToRemove: 'form-select-sm', classToAdd: 'ms-0' },
  530. { selector: '.dt-length', classToAdd: 'mb-md-6 mb-0' },
  531. { selector: '.dt-buttons', classToAdd: 'justify-content-center' },
  532. { selector: '.dt-layout-table', classToRemove: 'row mt-2' },
  533. { selector: '.dt-layout-start', classToAdd: 'px-4 mt-0' },
  534. { selector: '.dt-layout-end', classToAdd: 'px-4 mt-0 gap-2' },
  535. { selector: '.dt-layout-full', classToRemove: 'col-md col-12', classToAdd: 'table-responsive' }
  536. ];
  537. // Delete record
  538. elementsToModify.forEach(({ selector, classToRemove, classToAdd }) => {
  539. document.querySelectorAll(selector).forEach(element => {
  540. if (classToRemove) {
  541. classToRemove.split(' ').forEach(className => element.classList.remove(className));
  542. }
  543. if (classToAdd) {
  544. classToAdd.split(' ').forEach(className => element.classList.add(className));
  545. }
  546. });
  547. });
  548. }, 100);
  549. });