ExporterExcel.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. import moment from 'moment/min/moment-with-locales';
  3. import { TAPi18n } from '/imports/i18n';
  4. import { createWorkbook } from './createWorkbook';
  5. // exporter maybe is broken since Gridfs introduced, add fs and path
  6. class ExporterExcel {
  7. constructor(boardId, userLanguage) {
  8. this._boardId = boardId;
  9. this.userLanguage = userLanguage;
  10. }
  11. build(res) {
  12. const fs = Npm.require('fs');
  13. const os = Npm.require('os');
  14. const path = Npm.require('path');
  15. const byBoard = {
  16. boardId: this._boardId,
  17. };
  18. const byBoardNoLinked = {
  19. boardId: this._boardId,
  20. linkedId: {
  21. $in: ['', null],
  22. },
  23. };
  24. // we do not want to retrieve boardId in related elements
  25. const noBoardId = {
  26. fields: {
  27. boardId: 0,
  28. },
  29. };
  30. const result = {
  31. _format: 'wekan-board-1.0.0',
  32. };
  33. _.extend(
  34. result,
  35. ReactiveCache.getBoard(this._boardId, {
  36. fields: {
  37. stars: 0,
  38. },
  39. }),
  40. );
  41. result.lists = ReactiveCache.getLists(byBoard, noBoardId);
  42. result.cards = ReactiveCache.getCards(byBoardNoLinked, noBoardId);
  43. result.swimlanes = ReactiveCache.getSwimlanes(byBoard, noBoardId);
  44. result.customFields = ReactiveCache.getCustomFields(
  45. {
  46. boardIds: {
  47. $in: [this.boardId],
  48. },
  49. },
  50. {
  51. fields: {
  52. boardId: 0,
  53. },
  54. },
  55. );
  56. result.comments = ReactiveCache.getCardComments(byBoard, noBoardId);
  57. result.activities = ReactiveCache.getActivities(byBoard, noBoardId);
  58. result.rules = ReactiveCache.getRules(byBoard, noBoardId);
  59. result.checklists = [];
  60. result.checklistItems = [];
  61. result.subtaskItems = [];
  62. result.triggers = [];
  63. result.actions = [];
  64. result.cards.forEach((card) => {
  65. result.checklists.push(
  66. ...ReactiveCache.getChecklists({
  67. cardId: card._id,
  68. }),
  69. );
  70. result.checklistItems.push(
  71. ...ReactiveCache.getChecklistItems({
  72. cardId: card._id,
  73. }),
  74. );
  75. result.subtaskItems.push(
  76. ...ReactiveCache.getCards({
  77. parentId: card._id,
  78. }),
  79. );
  80. });
  81. result.rules.forEach((rule) => {
  82. result.triggers.push(
  83. ...ReactiveCache.getTriggers(
  84. {
  85. _id: rule.triggerId,
  86. },
  87. noBoardId,
  88. ),
  89. );
  90. result.actions.push(
  91. ...ReactiveCache.getActions(
  92. {
  93. _id: rule.actionId,
  94. },
  95. noBoardId,
  96. ),
  97. );
  98. });
  99. // we also have to export some user data - as the other elements only
  100. // include id but we have to be careful:
  101. // 1- only exports users that are linked somehow to that board
  102. // 2- do not export any sensitive information
  103. const users = {};
  104. result.members.forEach((member) => {
  105. users[member.userId] = true;
  106. });
  107. result.lists.forEach((list) => {
  108. users[list.userId] = true;
  109. });
  110. result.cards.forEach((card) => {
  111. users[card.userId] = true;
  112. if (card.members) {
  113. card.members.forEach((memberId) => {
  114. users[memberId] = true;
  115. });
  116. }
  117. if (card.assignees) {
  118. card.assignees.forEach((memberId) => {
  119. users[memberId] = true;
  120. });
  121. }
  122. });
  123. result.comments.forEach((comment) => {
  124. users[comment.userId] = true;
  125. });
  126. result.activities.forEach((activity) => {
  127. users[activity.userId] = true;
  128. });
  129. result.checklists.forEach((checklist) => {
  130. users[checklist.userId] = true;
  131. });
  132. const byUserIds = {
  133. _id: {
  134. $in: Object.getOwnPropertyNames(users),
  135. },
  136. };
  137. // we use whitelist to be sure we do not expose inadvertently
  138. // some secret fields that gets added to User later.
  139. const userFields = {
  140. fields: {
  141. _id: 1,
  142. username: 1,
  143. 'profile.initials': 1,
  144. 'profile.avatarUrl': 1,
  145. },
  146. };
  147. result.users = ReactiveCache.getUsers(byUserIds, userFields)
  148. .map((user) => {
  149. // user avatar is stored as a relative url, we export absolute
  150. if ((user.profile || {}).avatarUrl) {
  151. user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl);
  152. }
  153. return user;
  154. });
  155. //init exceljs workbook
  156. const workbook = createWorkbook();
  157. workbook.creator = TAPi18n.__('export-board','',this.userLanguage);
  158. workbook.lastModifiedBy = TAPi18n.__('export-board','',this.userLanguage);
  159. workbook.created = new Date();
  160. workbook.modified = new Date();
  161. workbook.lastPrinted = new Date();
  162. const filename = `${result.title}.xlsx`;
  163. //init worksheet
  164. let worksheetTitle = result.title;
  165. if (worksheetTitle.length > 31) {
  166. // MS Excel doesn't allow worksheet name longer than 31 chars
  167. // Exceljs truncate names to 31 chars
  168. let words = worksheetTitle.split(' ');
  169. let tmpTitle = '';
  170. for (let i=0;i<words.length; i++) {
  171. if (words[0].length > 27) {
  172. // title has no spaces
  173. tmpTitle = words[0].substr(0,27) + ' ';
  174. break;
  175. }
  176. if(tmpTitle.length + words[i].length < 27) {
  177. tmpTitle += words[i] + ' ';
  178. }
  179. else {
  180. break;
  181. }
  182. }
  183. worksheetTitle = tmpTitle + '...';
  184. }
  185. const worksheet = workbook.addWorksheet(worksheetTitle, {
  186. properties: {
  187. tabColor: {
  188. argb: 'FFC0000',
  189. },
  190. },
  191. pageSetup: {
  192. paperSize: 9,
  193. orientation: 'landscape',
  194. },
  195. });
  196. //get worksheet
  197. const ws = workbook.getWorksheet(worksheetTitle);
  198. ws.properties.defaultRowHeight = 20;
  199. //init columns
  200. //Excel font. Western: Arial. zh-CN: 宋体
  201. ws.columns = [
  202. {
  203. key: 'a',
  204. width: 14,
  205. },
  206. {
  207. key: 'b',
  208. width: 40,
  209. },
  210. {
  211. key: 'c',
  212. width: 60,
  213. },
  214. {
  215. key: 'd',
  216. width: 40,
  217. },
  218. {
  219. key: 'e',
  220. width: 20,
  221. },
  222. {
  223. key: 'f',
  224. width: 20,
  225. style: {
  226. font: {
  227. name: TAPi18n.__('excel-font'),
  228. size: '10',
  229. },
  230. numFmt: 'yyyy/mm/dd hh:mm:ss',
  231. },
  232. },
  233. {
  234. key: 'g',
  235. width: 20,
  236. style: {
  237. font: {
  238. name: TAPi18n.__('excel-font'),
  239. size: '10',
  240. },
  241. numFmt: 'yyyy/mm/dd hh:mm:ss',
  242. },
  243. },
  244. {
  245. key: 'h',
  246. width: 20,
  247. style: {
  248. font: {
  249. name: TAPi18n.__('excel-font'),
  250. size: '10',
  251. },
  252. numFmt: 'yyyy/mm/dd hh:mm:ss',
  253. },
  254. },
  255. {
  256. key: 'i',
  257. width: 20,
  258. style: {
  259. font: {
  260. name: TAPi18n.__('excel-font'),
  261. size: '10',
  262. },
  263. numFmt: 'yyyy/mm/dd hh:mm:ss',
  264. },
  265. },
  266. {
  267. key: 'j',
  268. width: 20,
  269. style: {
  270. font: {
  271. name: TAPi18n.__('excel-font'),
  272. size: '10',
  273. },
  274. numFmt: 'yyyy/mm/dd hh:mm:ss',
  275. },
  276. },
  277. {
  278. key: 'k',
  279. width: 20,
  280. style: {
  281. font: {
  282. name: TAPi18n.__('excel-font'),
  283. size: '10',
  284. },
  285. numFmt: 'yyyy/mm/dd hh:mm:ss',
  286. },
  287. },
  288. {
  289. key: 'l',
  290. width: 20,
  291. },
  292. {
  293. key: 'm',
  294. width: 20,
  295. },
  296. {
  297. key: 'n',
  298. width: 20,
  299. },
  300. {
  301. key: 'o',
  302. width: 20,
  303. },
  304. {
  305. key: 'p',
  306. width: 20,
  307. },
  308. {
  309. key: 'q',
  310. width: 20,
  311. },
  312. {
  313. key: 'r',
  314. width: 20,
  315. },
  316. ];
  317. //add title line
  318. ws.mergeCells('A1:H1');
  319. ws.getCell('A1').value = result.title;
  320. ws.getCell('A1').style = {
  321. font: {
  322. name: TAPi18n.__('excel-font'),
  323. size: '20',
  324. },
  325. };
  326. ws.getCell('A1').alignment = {
  327. vertical: 'middle',
  328. horizontal: 'left',
  329. };
  330. ws.getRow(1).height = 40;
  331. //get member and assignee info
  332. let jmem = '';
  333. let jassig = '';
  334. const jmeml = {};
  335. const jassigl = {};
  336. for (const i in result.users) {
  337. jmem = `${jmem + result.users[i].username},`;
  338. jmeml[result.users[i]._id] = result.users[i].username;
  339. }
  340. jmem = jmem.substr(0, jmem.length - 1);
  341. for (const ia in result.users) {
  342. jassig = `${jassig + result.users[ia].username},`;
  343. jassigl[result.users[ia]._id] = result.users[ia].username;
  344. }
  345. jassig = jassig.substr(0, jassig.length - 1);
  346. //get kanban list info
  347. const jlist = {};
  348. for (const klist in result.lists) {
  349. jlist[result.lists[klist]._id] = result.lists[klist].title;
  350. }
  351. //get kanban swimlanes info
  352. const jswimlane = {};
  353. for (const kswimlane in result.swimlanes) {
  354. jswimlane[result.swimlanes[kswimlane]._id] =
  355. result.swimlanes[kswimlane].title;
  356. }
  357. //get kanban label info
  358. const jlabel = {};
  359. var isFirst = 1;
  360. for (const klabel in result.labels) {
  361. // console.log(klabel);
  362. if (isFirst == 0) {
  363. jlabel[result.labels[klabel]._id] = `,${result.labels[klabel].name}`;
  364. } else {
  365. isFirst = 0;
  366. jlabel[result.labels[klabel]._id] = result.labels[klabel].name;
  367. }
  368. }
  369. //add data +8 hours
  370. function addTZhours(jdate) {
  371. if (!jdate) { return ' '; }
  372. const curdate = new Date(jdate);
  373. const checkCorrectDate = moment(curdate);
  374. if (checkCorrectDate.isValid()) {
  375. return curdate;
  376. } else {
  377. return ' ';
  378. }
  379. ////Do not add 8 hours to GMT. Use GMT instead.
  380. ////Could not yet figure out how to get localtime.
  381. //return new Date(curdate.setHours(curdate.getHours() + 8));
  382. //return curdate;
  383. }
  384. //cell center
  385. function cellCenter(cellno) {
  386. ws.getCell(cellno).alignment = {
  387. vertical: 'middle',
  388. horizontal: 'center',
  389. wrapText: true,
  390. };
  391. }
  392. function cellLeft(cellno) {
  393. ws.getCell(cellno).alignment = {
  394. vertical: 'middle',
  395. horizontal: 'left',
  396. wrapText: true,
  397. };
  398. }
  399. // cell Card alignment
  400. function cellCardAlignment(cellno) {
  401. ws.getCell(cellno).alignment = {
  402. vertical: 'top',
  403. horizontal: 'left',
  404. wrapText: true,
  405. };
  406. }
  407. //all border
  408. function allBorder(cellno) {
  409. ws.getCell(cellno).border = {
  410. top: {
  411. style: 'thin',
  412. },
  413. left: {
  414. style: 'thin',
  415. },
  416. bottom: {
  417. style: 'thin',
  418. },
  419. right: {
  420. style: 'thin',
  421. },
  422. };
  423. }
  424. //add blank row
  425. ws.addRow().values = ['', '', '', '', '', ''];
  426. //add board description
  427. ws.addRow().values = [
  428. TAPi18n.__('description','',this.userLanguage),
  429. result.description,
  430. ];
  431. ws.mergeCells('B3:H3');
  432. ws.getRow(3).height = 40;
  433. // In MS Excel, we can't use the AutoFit feature on a column that contains a cell merged with cells in other columns.
  434. // Likewise, we can't use AutoFit on a row that contains a cell merged with cells in other rows.
  435. ws.getRow(3).font = {
  436. name: TAPi18n.__('excel-font'),
  437. size: 10,
  438. };
  439. ws.getCell('A3').style = {
  440. font: {
  441. name: TAPi18n.__('excel-font'),
  442. size: '10',
  443. bold: true,
  444. },
  445. };
  446. ws.getCell(`B3`).alignment = {
  447. wrapText: true,
  448. vertical: 'middle',
  449. };
  450. cellCenter('A3');
  451. //add blank row
  452. ws.addRow().values = ['', '', '', '', '', ''];
  453. //add kanban info
  454. ws.addRow().values = [
  455. TAPi18n.__('createdAt','',this.userLanguage),
  456. addTZhours(result.createdAt),
  457. TAPi18n.__('modifiedAt','',this.userLanguage),
  458. addTZhours(result.modifiedAt),
  459. TAPi18n.__('members','',this.userLanguage),
  460. jmem,
  461. ];
  462. ws.getRow(5).font = {
  463. name: TAPi18n.__('excel-font'),
  464. size: 10,
  465. bold: true,
  466. };
  467. ws.mergeCells('F5:R5');
  468. ws.getCell('B5').style = {
  469. font: {
  470. name: TAPi18n.__('excel-font'),
  471. size: '10',
  472. bold: true,
  473. },
  474. numFmt: 'yyyy/mm/dd hh:mm:ss',
  475. };
  476. ws.getCell('D5').style = {
  477. font: {
  478. name: TAPi18n.__('excel-font'),
  479. size: '10',
  480. bold: true,
  481. },
  482. numFmt: 'yyyy/mm/dd hh:mm:ss',
  483. };
  484. cellCenter('A5');
  485. cellCenter('B5');
  486. cellCenter('C5');
  487. cellCenter('D5');
  488. cellCenter('E5');
  489. cellLeft('F5');
  490. ws.getRow(5).height = 20;
  491. allBorder('A5');
  492. allBorder('B5');
  493. allBorder('C5');
  494. allBorder('D5');
  495. allBorder('E5');
  496. allBorder('F5');
  497. //add blank row
  498. ws.addRow().values = [
  499. '',
  500. '',
  501. '',
  502. '',
  503. '',
  504. '',
  505. '',
  506. '',
  507. '',
  508. '',
  509. '',
  510. '',
  511. '',
  512. '',
  513. '',
  514. ];
  515. //add card title
  516. //ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签'];
  517. //this is where order in which the excel file generates
  518. ws.addRow().values = [
  519. TAPi18n.__('number','',this.userLanguage),
  520. TAPi18n.__('title','',this.userLanguage),
  521. TAPi18n.__('description','',this.userLanguage),
  522. TAPi18n.__('parent-card','',this.userLanguage),
  523. TAPi18n.__('owner','',this.userLanguage),
  524. TAPi18n.__('createdAt','',this.userLanguage),
  525. TAPi18n.__('last-modified-at','',this.userLanguage),
  526. TAPi18n.__('card-received','',this.userLanguage),
  527. TAPi18n.__('card-start','',this.userLanguage),
  528. TAPi18n.__('card-due','',this.userLanguage),
  529. TAPi18n.__('card-end','',this.userLanguage),
  530. TAPi18n.__('list','',this.userLanguage),
  531. TAPi18n.__('swimlane','',this.userLanguage),
  532. TAPi18n.__('assignee','',this.userLanguage),
  533. TAPi18n.__('members','',this.userLanguage),
  534. TAPi18n.__('labels','',this.userLanguage),
  535. TAPi18n.__('overtime-hours','',this.userLanguage),
  536. TAPi18n.__('spent-time-hours','',this.userLanguage),
  537. ];
  538. ws.getRow(7).height = 20;
  539. allBorder('A7');
  540. allBorder('B7');
  541. allBorder('C7');
  542. allBorder('D7');
  543. allBorder('E7');
  544. allBorder('F7');
  545. allBorder('G7');
  546. allBorder('H7');
  547. allBorder('I7');
  548. allBorder('J7');
  549. allBorder('K7');
  550. allBorder('L7');
  551. allBorder('M7');
  552. allBorder('N7');
  553. allBorder('O7');
  554. allBorder('P7');
  555. allBorder('Q7');
  556. allBorder('R7');
  557. cellCenter('A7');
  558. cellCenter('B7');
  559. cellCenter('C7');
  560. cellCenter('D7');
  561. cellCenter('E7');
  562. cellCenter('F7');
  563. cellCenter('G7');
  564. cellCenter('H7');
  565. cellCenter('I7');
  566. cellCenter('J7');
  567. cellCenter('K7');
  568. cellCenter('L7');
  569. cellCenter('M7');
  570. cellCenter('N7');
  571. cellCenter('O7');
  572. cellCenter('P7');
  573. cellCenter('Q7');
  574. cellCenter('R7');
  575. ws.getRow(7).font = {
  576. name: TAPi18n.__('excel-font'),
  577. size: 10,
  578. bold: true,
  579. };
  580. //add blank row
  581. //add card info
  582. for (const i in result.cards) {
  583. const jcard = result.cards[i];
  584. //get member info
  585. let jcmem = '';
  586. for (const j in jcard.members) {
  587. jcmem += jmeml[jcard.members[j]];
  588. jcmem += ' ';
  589. }
  590. //get assignee info
  591. let jcassig = '';
  592. for (const ja in jcard.assignees) {
  593. jcassig += jassigl[jcard.assignees[ja]];
  594. jcassig += ' ';
  595. }
  596. //get card label info
  597. let jclabel = '';
  598. for (const jl in jcard.labelIds) {
  599. jclabel += jlabel[jcard.labelIds[jl]];
  600. jclabel += ' ';
  601. }
  602. //get parent name
  603. if (jcard.parentId) {
  604. const parentCard = result.cards.find(
  605. (card) => card._id === jcard.parentId,
  606. );
  607. jcard.parentCardTitle = parentCard ? parentCard.title : '';
  608. }
  609. //add card detail
  610. const t = Number(i) + 1;
  611. ws.addRow().values = [
  612. t.toString(),
  613. jcard.title,
  614. jcard.description,
  615. jcard.parentCardTitle,
  616. jmeml[jcard.userId],
  617. addTZhours(jcard.createdAt),
  618. addTZhours(jcard.dateLastActivity),
  619. addTZhours(jcard.receivedAt),
  620. addTZhours(jcard.startAt),
  621. addTZhours(jcard.dueAt),
  622. addTZhours(jcard.endAt),
  623. jlist[jcard.listId],
  624. jswimlane[jcard.swimlaneId],
  625. jcassig,
  626. jcmem,
  627. jclabel,
  628. jcard.isOvertime ? 'true' : 'false',
  629. jcard.spentTime,
  630. ];
  631. const y = Number(i) + 8;
  632. //ws.getRow(y).height = 25;
  633. ws.getRow(y).font = {
  634. name: TAPi18n.__('excel-font'),
  635. size: 10,
  636. };
  637. // Border
  638. allBorder(`A${y}`);
  639. allBorder(`B${y}`);
  640. allBorder(`C${y}`);
  641. allBorder(`D${y}`);
  642. allBorder(`E${y}`);
  643. allBorder(`F${y}`);
  644. allBorder(`G${y}`);
  645. allBorder(`H${y}`);
  646. allBorder(`I${y}`);
  647. allBorder(`J${y}`);
  648. allBorder(`K${y}`);
  649. allBorder(`L${y}`);
  650. allBorder(`M${y}`);
  651. allBorder(`N${y}`);
  652. allBorder(`O${y}`);
  653. allBorder(`P${y}`);
  654. allBorder(`Q${y}`);
  655. allBorder(`R${y}`);
  656. // Alignment
  657. ws.getCell(`A${y}`).alignment = {
  658. vertical: 'top',
  659. horizontal: 'right',
  660. wrapText: true,
  661. };
  662. cellCardAlignment(`B${y}`);
  663. cellCardAlignment(`C${y}`);
  664. cellCardAlignment(`D${y}`);
  665. cellCardAlignment(`E${y}`);
  666. cellCardAlignment(`F${y}`);
  667. cellCardAlignment(`G${y}`);
  668. cellCardAlignment(`H${y}`);
  669. cellCardAlignment(`I${y}`);
  670. cellCardAlignment(`J${y}`);
  671. cellCardAlignment(`K${y}`);
  672. cellCardAlignment(`L${y}`);
  673. cellCardAlignment(`M${y}`);
  674. cellCardAlignment(`N${y}`);
  675. cellCardAlignment(`O${y}`);
  676. cellCardAlignment(`P${y}`);
  677. ws.getCell(`Q${y}`).alignment = {
  678. vertical: 'top',
  679. horizontal: 'center',
  680. wrapText: true,
  681. };
  682. ws.getCell(`R${y}`).alignment = {
  683. vertical: 'top',
  684. horizontal: 'center',
  685. wrapText: true,
  686. };
  687. }
  688. //Activities worksheet
  689. //init worksheet
  690. const worksheet2 = workbook.addWorksheet(TAPi18n.__('activity','',this.userLanguage), {
  691. properties: {
  692. tabColor: {
  693. argb: 'FFC0000',
  694. },
  695. },
  696. pageSetup: {
  697. paperSize: 9,
  698. orientation: 'landscape',
  699. },
  700. });
  701. //get worksheet
  702. const ws2 = workbook.getWorksheet(TAPi18n.__('activity','',this.userLanguage));
  703. ws2.properties.defaultRowHeight = 20;
  704. //init columns
  705. ws2.columns = [
  706. {
  707. key: 'a',
  708. width: 14,
  709. },
  710. {
  711. key: 'b',
  712. width: 60,
  713. },
  714. {
  715. key: 'c',
  716. width: 40,
  717. },
  718. {
  719. key: 'd',
  720. width: 40,
  721. },
  722. {
  723. key: 'e',
  724. width: 30,
  725. style: {
  726. font: {
  727. name: TAPi18n.__('excel-font'),
  728. size: '10',
  729. },
  730. numFmt: 'yyyy/mm/dd hh:mm:ss',
  731. },
  732. },
  733. {
  734. key: 'f',
  735. width: 30,
  736. style: {
  737. font: {
  738. name: TAPi18n.__('excel-font'),
  739. size: '10',
  740. },
  741. numFmt: 'yyyy/mm/dd hh:mm:ss',
  742. },
  743. },
  744. ];
  745. // cell Card alignment
  746. function cellCardAlignmentWs2(cellno) {
  747. ws2.getCell(cellno).alignment = {
  748. vertical: 'top',
  749. horizontal: 'left',
  750. wrapText: true,
  751. };
  752. }
  753. //all border
  754. function allBorderWs2(cellno) {
  755. ws2.getCell(cellno).border = {
  756. top: {
  757. style: 'thin',
  758. },
  759. left: {
  760. style: 'thin',
  761. },
  762. bottom: {
  763. style: 'thin',
  764. },
  765. right: {
  766. style: 'thin',
  767. },
  768. };
  769. }
  770. //add title line
  771. ws2.mergeCells('A1:F1');
  772. ws2.getCell('A1').value = result.title;
  773. ws2.getCell('A1').style = {
  774. font: {
  775. name: TAPi18n.__('excel-font'),
  776. size: '20',
  777. },
  778. };
  779. ws2.getCell('A1').alignment = {
  780. vertical: 'middle',
  781. horizontal: 'center',
  782. wrapText: true,
  783. };
  784. ws2.getRow(1).height = 40;
  785. //add blank row
  786. ws2.addRow().values = ['', '', '', '', '', ''];
  787. //add comment title
  788. ws2.addRow().values = [
  789. TAPi18n.__('number','',this.userLanguage),
  790. TAPi18n.__('activity','',this.userLanguage),
  791. TAPi18n.__('card','',this.userLanguage),
  792. TAPi18n.__('owner','',this.userLanguage),
  793. TAPi18n.__('createdAt','',this.userLanguage),
  794. TAPi18n.__('last-modified-at','',this.userLanguage),
  795. ];
  796. ws2.getRow(3).height = 20;
  797. ws2.getRow(3).font = {
  798. name: TAPi18n.__('excel-font'),
  799. size: 10,
  800. bold: true,
  801. };
  802. ws2.getRow(3).alignment = {
  803. vertical: 'middle',
  804. horizontal: 'center',
  805. wrapText: true,
  806. };
  807. allBorderWs2('A3');
  808. allBorderWs2('B3');
  809. allBorderWs2('C3');
  810. allBorderWs2('D3');
  811. allBorderWs2('E3');
  812. allBorderWs2('F3');
  813. //add comment info
  814. let commentcnt = 0;
  815. for (const i in result.comments) {
  816. const jcomment = result.comments[i];
  817. //card title
  818. const parentCard = result.cards.find(
  819. (card) => card._id === jcomment.cardId,
  820. );
  821. jcomment.cardTitle = parentCard ? parentCard.title : '';
  822. if (jcomment.cardTitle == '') {
  823. continue;
  824. }
  825. //add comment detail
  826. commentcnt++;
  827. ws2.addRow().values = [
  828. commentcnt.toString(),
  829. jcomment.text,
  830. jcomment.cardTitle,
  831. jmeml[jcomment.userId],
  832. addTZhours(jcomment.createdAt),
  833. addTZhours(jcomment.modifiedAt),
  834. ];
  835. const y = commentcnt + 3;
  836. ws2.getRow(y).font = {
  837. name: TAPi18n.__('excel-font'),
  838. size: 10,
  839. };
  840. // Border
  841. allBorderWs2(`A${y}`);
  842. allBorderWs2(`B${y}`);
  843. allBorderWs2(`C${y}`);
  844. allBorderWs2(`D${y}`);
  845. allBorderWs2(`E${y}`);
  846. allBorderWs2(`F${y}`);
  847. // Alignment
  848. ws2.getCell(`A${y}`).alignment = {
  849. vertical: 'top',
  850. horizontal: 'right',
  851. wrapText: true,
  852. };
  853. cellCardAlignmentWs2(`B${y}`);
  854. cellCardAlignmentWs2(`C${y}`);
  855. cellCardAlignmentWs2(`D${y}`);
  856. cellCardAlignmentWs2(`E${y}`);
  857. cellCardAlignmentWs2(`F${y}`);
  858. }
  859. workbook.xlsx.write(res).then(function () {});
  860. }
  861. canExport(user) {
  862. const board = ReactiveCache.getBoard(this._boardId);
  863. return board && board.isVisibleBy(user);
  864. }
  865. }
  866. export { ExporterExcel };