mirror of
https://github.com/wekan/wekan.git
synced 2026-11-08 11:25:19 -05:00
Move the board Status from a Board Settings sidebar popup into a full-width board VIEW alongside Swimlanes / Lists / Calendar / Gantt / Table in the board-view switcher. Named 'Statistics' (Finnish 'Tilastot'), translated to major languages (rest fall back to English). New statsView template/js/css rendered from boardBody (isViewStats), selectable via the switcher (js-open-stats-view -> board-view-stats, added to the profile.boardView schema allowedValues). Reuses the server boardStatus method so counts are accurate in lazy mode; shows the same counts + time-spent summary as before. Text is selectable with mouse (drag) and touch (long-press): the view stops pointer/touch events from reaching the canvas drag-to-scroll so native selection + copy work. Removed the old boardStatusPopup (template/handler/helpers/css) and its Board menu item. Added a Playwright test for the view. Design doc updated.
71 lines
2.6 KiB
JavaScript
71 lines
2.6 KiB
JavaScript
import { ReactiveCache } from '/imports/reactiveCache';
|
|
import { TAPi18n } from '/imports/i18n';
|
|
import { ReactiveVar } from 'meteor/reactive-var';
|
|
import { Utils } from '/client/lib/utils';
|
|
|
|
// Board view "Statistics" (Tilastot) — a full-width board view (like Swimlanes /
|
|
// Lists / Calendar / Gantt / Table) showing the board's status: its card-loading
|
|
// mode, counts (swimlanes, lists, cards, labels, members, custom fields) and a
|
|
// time-spent summary. Counts come from the server `boardStatus` method so they are
|
|
// accurate even in lazy mode, where the browser only holds the visible card window.
|
|
Template.statsView.onCreated(function() {
|
|
this.status = new ReactiveVar(null);
|
|
this.autorun(() => {
|
|
const boardId = Session.get('currentBoard');
|
|
if (boardId) {
|
|
Meteor.call('boardStatus', boardId, (err, res) => {
|
|
if (!err && res) this.status.set(res);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// Read one numeric field from the resolved status, or a placeholder while loading.
|
|
function statsNum(key) {
|
|
const s = Template.instance().status.get();
|
|
return s ? (s[key] || 0) : '…';
|
|
}
|
|
|
|
Template.statsView.events({
|
|
// The board canvas uses drag-to-scroll (the `dragscroll` class), which otherwise
|
|
// swallows pointer/touch drags and prevents selecting text. Stop these events from
|
|
// reaching it so the browser's native selection works — with the mouse (drag) and
|
|
// on touch (long-press) — and any value can be copied to the clipboard.
|
|
'mousedown .stats-view'(event) {
|
|
event.stopPropagation();
|
|
},
|
|
'touchstart .stats-view'(event) {
|
|
event.stopPropagation();
|
|
},
|
|
});
|
|
|
|
Template.statsView.helpers({
|
|
boardTitle() {
|
|
const board = Utils.getCurrentBoard();
|
|
return board ? board.title : '';
|
|
},
|
|
loadingModeLabel() {
|
|
const s = Template.instance().status.get();
|
|
if (!s) return '…';
|
|
const inEffect = s.lazy
|
|
? TAPi18n.__('cards-loading-lazy')
|
|
: TAPi18n.__('cards-loading-all');
|
|
return s.mode === 'auto' ? `${inEffect} (${TAPi18n.__('cards-loading-auto')})` : inEffect;
|
|
},
|
|
swimlaneCount() { return statsNum('swimlanes'); },
|
|
listCount() { return statsNum('lists'); },
|
|
cardCount() { return statsNum('cards'); },
|
|
archivedCardCount() { return statsNum('archivedCards'); },
|
|
labelCount() { return statsNum('labels'); },
|
|
memberCount() { return statsNum('members'); },
|
|
customFieldCount() { return statsNum('customFields'); },
|
|
cardsWithTimeSpent() { return statsNum('cardsWithTimeSpent'); },
|
|
overtimeCards() { return statsNum('overtimeCards'); },
|
|
timeSpentTotal() {
|
|
const s = Template.instance().status.get();
|
|
if (!s) return '…';
|
|
const n = Number(s.timeSpentTotal) || 0;
|
|
return `${n} h`;
|
|
},
|
|
});
|