ExporterExcel.js 22 KB

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