mirror of
https://github.com/wekan/wekan.git
synced 2026-01-01 07:08:49 +01:00
217 lines
7.8 KiB
JavaScript
217 lines
7.8 KiB
JavaScript
// Add click handler to ganttView for card titles
|
|
Template.ganttView.events({
|
|
'click .js-gantt-card-title'(event, template) {
|
|
event.preventDefault();
|
|
// Get card ID from the closest row's data attribute
|
|
const $row = template.$(event.currentTarget).closest('tr');
|
|
const cardId = $row.data('card-id');
|
|
|
|
if (cardId) {
|
|
template.selectedCardId.set(cardId);
|
|
}
|
|
},
|
|
});
|
|
import { Template } from 'meteor/templating';
|
|
|
|
// Blaze template helpers for ganttView
|
|
function getISOWeekInfo(d) {
|
|
const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
const dayNum = date.getUTCDay() || 7;
|
|
date.setUTCDate(date.getUTCDate() + 4 - dayNum);
|
|
const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
|
|
const week = Math.ceil((((date - yearStart) / 86400000) + 1) / 7);
|
|
return { year: date.getUTCFullYear(), week };
|
|
}
|
|
function startOfISOWeek(d) {
|
|
const date = new Date(d);
|
|
const day = date.getDay() || 7;
|
|
if (day !== 1) date.setDate(date.getDate() - (day - 1));
|
|
date.setHours(0,0,0,0);
|
|
return date;
|
|
}
|
|
|
|
Template.ganttView.helpers({
|
|
weeks() {
|
|
const board = Utils.getCurrentBoard();
|
|
if (!board) return [];
|
|
const cards = Cards.find({ boardId: board._id }, { sort: { startAt: 1, dueAt: 1 } }).fetch();
|
|
const weeksMap = new Map();
|
|
const relevantCards = cards.filter(c => c.receivedAt || c.startAt || c.dueAt || c.endAt);
|
|
relevantCards.forEach(card => {
|
|
['receivedAt','startAt','dueAt','endAt'].forEach(field => {
|
|
if (card[field]) {
|
|
const dt = new Date(card[field]);
|
|
const info = getISOWeekInfo(dt);
|
|
const key = `${info.year}-W${info.week}`;
|
|
if (!weeksMap.has(key)) {
|
|
weeksMap.set(key, { year: info.year, week: info.week, start: startOfISOWeek(dt) });
|
|
}
|
|
}
|
|
});
|
|
});
|
|
return Array.from(weeksMap.values()).sort((a,b) => a.start - b.start);
|
|
},
|
|
weekDays(week) {
|
|
const weekStart = new Date(week.start);
|
|
return Array.from({length:7}, (_,i) => {
|
|
const d = new Date(weekStart);
|
|
d.setDate(d.getDate() + i);
|
|
d.setHours(0,0,0,0);
|
|
return d;
|
|
});
|
|
},
|
|
weekdayLabel(day) {
|
|
const weekdayKeys = ['monday','tuesday','wednesday','thursday','friday','saturday','sunday'];
|
|
return TAPi18n.__(weekdayKeys[day.getDay() === 0 ? 6 : day.getDay() - 1]);
|
|
},
|
|
formattedDate(day) {
|
|
const currentUser = ReactiveCache.getCurrentUser && ReactiveCache.getCurrentUser();
|
|
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
|
|
return formatDateByUserPreference(day, dateFormat, false);
|
|
},
|
|
cardsInWeek(week) {
|
|
const board = Utils.getCurrentBoard();
|
|
if (!board) return [];
|
|
const cards = Cards.find({ boardId: board._id }).fetch();
|
|
return cards.filter(card => {
|
|
return ['receivedAt','startAt','dueAt','endAt'].some(field => {
|
|
if (card[field]) {
|
|
const dt = new Date(card[field]);
|
|
const info = getISOWeekInfo(dt);
|
|
return info.week === week.week && info.year === week.year;
|
|
}
|
|
return false;
|
|
});
|
|
});
|
|
},
|
|
cardTitle(card) {
|
|
return card.title;
|
|
},
|
|
cardId(card) {
|
|
return card._id;
|
|
},
|
|
cardUrl(card) {
|
|
if (!card) return '#';
|
|
const board = ReactiveCache.getBoard(card.boardId);
|
|
if (!board) return '#';
|
|
return FlowRouter.path('card', {
|
|
boardId: card.boardId,
|
|
slug: board.slug,
|
|
cardId: card._id,
|
|
});
|
|
},
|
|
cellContentClass(card, day) {
|
|
const cardDates = {
|
|
receivedAt: card.receivedAt ? new Date(card.receivedAt) : null,
|
|
startAt: card.startAt ? new Date(card.startAt) : null,
|
|
dueAt: card.dueAt ? new Date(card.dueAt) : null,
|
|
endAt: card.endAt ? new Date(card.endAt) : null,
|
|
};
|
|
if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) return 'ganttview-received';
|
|
if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) return 'ganttview-start';
|
|
if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) return 'ganttview-due';
|
|
if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) return 'ganttview-end';
|
|
return '';
|
|
},
|
|
cellContent(card, day) {
|
|
const cardDates = {
|
|
receivedAt: card.receivedAt ? new Date(card.receivedAt) : null,
|
|
startAt: card.startAt ? new Date(card.startAt) : null,
|
|
dueAt: card.dueAt ? new Date(card.dueAt) : null,
|
|
endAt: card.endAt ? new Date(card.endAt) : null,
|
|
};
|
|
if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) return '📥';
|
|
if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) return '🚀';
|
|
if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) return '⏰';
|
|
if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) return '🏁';
|
|
return '';
|
|
},
|
|
isToday(day) {
|
|
const today = new Date();
|
|
return day.toDateString() === today.toDateString();
|
|
},
|
|
isWeekend(day) {
|
|
const idx = day.getDay();
|
|
return idx === 0 || idx === 6;
|
|
},
|
|
hasSelectedCard() {
|
|
return Template.instance().selectedCardId.get() !== null;
|
|
},
|
|
selectedCard() {
|
|
const cardId = Template.instance().selectedCardId.get();
|
|
return cardId ? ReactiveCache.getCard(cardId) : null;
|
|
},
|
|
cellClasses(card, day) {
|
|
// Get the base class from cellContentClass logic
|
|
const cardDates = {
|
|
receivedAt: card.receivedAt ? new Date(card.receivedAt) : null,
|
|
startAt: card.startAt ? new Date(card.startAt) : null,
|
|
dueAt: card.dueAt ? new Date(card.dueAt) : null,
|
|
endAt: card.endAt ? new Date(card.endAt) : null,
|
|
};
|
|
let classes = '';
|
|
if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) classes = 'ganttview-received';
|
|
else if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) classes = 'ganttview-start';
|
|
else if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) classes = 'ganttview-due';
|
|
else if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) classes = 'ganttview-end';
|
|
|
|
// Add conditional classes
|
|
const today = new Date();
|
|
if (day.toDateString() === today.toDateString()) classes += ' ganttview-today';
|
|
const idx = day.getDay();
|
|
if (idx === 0 || idx === 6) classes += ' ganttview-weekend';
|
|
if (classes.trim()) classes += ' js-gantt-date-icon';
|
|
|
|
return classes.trim();
|
|
}
|
|
});
|
|
|
|
Template.ganttView.onCreated(function() {
|
|
this.selectedCardId = new ReactiveVar(null);
|
|
// Provide properties expected by cardDetails component
|
|
this.showOverlay = new ReactiveVar(false);
|
|
this.mouseHasEnterCardDetails = false;
|
|
});
|
|
|
|
// Blaze onRendered logic for ganttView
|
|
Template.ganttView.onRendered(function() {
|
|
const self = this;
|
|
this.autorun(() => {
|
|
// If you have legacy imperative rendering, keep it here
|
|
if (typeof renderGanttChart === 'function') {
|
|
renderGanttChart();
|
|
}
|
|
});
|
|
// Add click handler for date cells (Received, Start, Due, End)
|
|
this.$('.gantt-table').on('click', '.js-gantt-date-icon', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
const $cell = self.$(this);
|
|
const cardId = $cell.data('card-id');
|
|
let dateType = $cell.data('date-type');
|
|
// Remove 'ganttview-' prefix to match popup map
|
|
if (typeof dateType === 'string' && dateType.startsWith('ganttview-')) {
|
|
dateType = dateType.replace('ganttview-', '');
|
|
}
|
|
const popupMap = {
|
|
received: 'editCardReceivedDate',
|
|
start: 'editCardStartDate',
|
|
due: 'editCardDueDate',
|
|
end: 'editCardEndDate',
|
|
};
|
|
const popupName = popupMap[dateType];
|
|
if (!popupName || typeof Popup === 'undefined' || typeof Popup.open !== 'function') return;
|
|
const card = ReactiveCache.getCard(cardId);
|
|
if (!card) return;
|
|
const openFn = Popup.open(popupName);
|
|
openFn.call({ currentData: () => card }, e, { dataContextIfCurrentDataIsUndefined: card });
|
|
});
|
|
|
|
});
|
|
|
|
import markdownit from 'markdown-it';
|
|
import { TAPi18n } from '/imports/i18n';
|
|
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
|
|
import { ReactiveCache } from '/imports/reactiveCache';
|
|
|
|
const md = markdownit({ breaks: true, linkify: true });
|