tg-ganttchart 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/babel.config.js +5 -0
- package/package.json +1 -4
- package/src/.eslintrc.js +18 -0
- package/src/App.vue +780 -0
- package/src/GanttElastic.standalone.vue +48 -0
- package/src/GanttElastic.vue +2305 -0
- package/src/assets/logo.png +0 -0
- package/src/components/Calendar/Calendar.vue +559 -0
- package/src/components/Calendar/CalendarRow.vue +112 -0
- package/src/components/Chart/Chart.vue +117 -0
- package/src/components/Chart/DaysHighlight.vue +60 -0
- package/src/components/Chart/DependencyLines.vue +112 -0
- package/src/components/Chart/Grid.vue +205 -0
- package/src/components/Chart/ProgressBar.vue +110 -0
- package/src/components/Chart/Row/Epic.vue +131 -0
- package/src/components/Chart/Row/Milestone.vue +117 -0
- package/src/components/Chart/Row/Project.vue +132 -0
- package/src/components/Chart/Row/Story.vue +127 -0
- package/src/components/Chart/Row/Subtask.vue +117 -0
- package/src/components/Chart/Row/Task.mixin.js +47 -0
- package/src/components/Chart/Row/Task.vue +82 -0
- package/src/components/Chart/Text.vue +105 -0
- package/src/components/Expander.vue +114 -0
- package/src/components/GanttElastic.standalone.vue +48 -0
- package/src/components/GanttElastic.vue +1646 -0
- package/src/components/Header/GanttViewFilter.vue +154 -0
- package/src/components/Header/Header.vue +266 -0
- package/src/components/MainView.vue +283 -0
- package/src/components/TaskList/ItemColumn.vue +212 -0
- package/src/components/TaskList/TaskList.vue +45 -0
- package/src/components/TaskList/TaskListHeader.vue +143 -0
- package/src/components/TaskList/TaskListItem.vue +35 -0
- package/src/components/bundle.js +28 -0
- package/src/components/components/Calendar/Calendar.vue +332 -0
- package/src/components/components/Calendar/CalendarRow.vue +96 -0
- package/src/components/components/Chart/Chart.vue +111 -0
- package/src/components/components/Chart/DaysHighlight.vue +71 -0
- package/src/components/components/Chart/DependencyLines.vue +112 -0
- package/src/components/components/Chart/Grid.vue +164 -0
- package/src/components/components/Chart/ProgressBar.vue +110 -0
- package/src/components/components/Chart/Row/Milestone.vue +117 -0
- package/src/components/components/Chart/Row/Project.vue +131 -0
- package/src/components/components/Chart/Row/Task.mixin.js +46 -0
- package/src/components/components/Chart/Row/Task.vue +107 -0
- package/src/components/components/Chart/Text.vue +105 -0
- package/src/components/components/Expander.vue +126 -0
- package/src/components/components/Header/Header.vue +265 -0
- package/src/components/components/MainView.vue +282 -0
- package/src/components/components/TaskList/ItemColumn.vue +121 -0
- package/src/components/components/TaskList/TaskList.vue +45 -0
- package/src/components/components/TaskList/TaskListHeader.vue +143 -0
- package/src/components/components/TaskList/TaskListItem.vue +35 -0
- package/src/components/components/bundle.js +28 -0
- package/src/components/style.js +308 -0
- package/src/index.js +12 -0
- package/src/main.js +6 -0
- package/src/style.js +398 -0
- package/vue.config.js +42 -0
- package/dist/demo.html +0 -1
- package/dist/tgganttchart.common.js +0 -9232
- package/dist/tgganttchart.common.js.map +0 -1
- package/dist/tgganttchart.css +0 -1
- package/dist/tgganttchart.umd.js +0 -9243
- package/dist/tgganttchart.umd.js.map +0 -1
- package/dist/tgganttchart.umd.min.js +0 -7
- package/dist/tgganttchart.umd.min.js.map +0 -1
|
Binary file
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="gantt-elastic__calendar-wrapper"
|
|
3
|
+
:style="{ ...root.style['calendar-wrapper'], width: root.state.options.width + 'px' }">
|
|
4
|
+
<div class="gantt-elastic__calendar" :style="{ ...root.style['calendar'], width: root.state.options.width + 'px' }">
|
|
5
|
+
<!-- Quarterly View -->
|
|
6
|
+
<calendar-row :items="dates.quarters" which="quarter" v-if="viewMode === 'quarter'"></calendar-row>
|
|
7
|
+
|
|
8
|
+
<!-- Monthly View -->
|
|
9
|
+
<calendar-row :items="dates.months" which="month" v-if="viewMode === 'month'"></calendar-row>
|
|
10
|
+
|
|
11
|
+
<!-- Weekly View -->
|
|
12
|
+
<calendar-row :items="dates.weeks" which="week" v-if="viewMode === 'week'"></calendar-row>
|
|
13
|
+
|
|
14
|
+
<!-- Daily View
|
|
15
|
+
<calendar-row :items="dates.days" which="day" v-if="viewMode === 'day' || viewMode === 'week'"></calendar-row> -->
|
|
16
|
+
|
|
17
|
+
<!-- Hourly View -->
|
|
18
|
+
<calendar-row :items="dates.days" which="day" v-if="viewMode === 'day'"></calendar-row>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script>
|
|
24
|
+
import dayjs from 'dayjs';
|
|
25
|
+
import isoWeek from 'dayjs/plugin/weekOfYear';
|
|
26
|
+
import quarterOfYear from 'dayjs/plugin/quarterOfYear';
|
|
27
|
+
import CalendarRow from './CalendarRow.vue';
|
|
28
|
+
|
|
29
|
+
// Extend dayjs with required plugins
|
|
30
|
+
dayjs.extend(isoWeek);
|
|
31
|
+
dayjs.extend(quarterOfYear);
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
name: 'Calendar',
|
|
35
|
+
components: {
|
|
36
|
+
CalendarRow
|
|
37
|
+
},
|
|
38
|
+
inject: ['root'],
|
|
39
|
+
data() {
|
|
40
|
+
return {
|
|
41
|
+
dayjsInstance: dayjs // Store dayjs instance for consistent usage
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
computed: {
|
|
46
|
+
viewMode() {
|
|
47
|
+
return this.root.state.options.calendar.viewMode || 'day';
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
dates() {
|
|
51
|
+
const days = this.generateDays();
|
|
52
|
+
const weeks = this.generateWeeks();
|
|
53
|
+
const months = this.generateMonths();
|
|
54
|
+
const quarters = this.generateQuarters()
|
|
55
|
+
const allDates = { days, weeks, months, quarters };
|
|
56
|
+
this.calculateCalendarDimensions(allDates);
|
|
57
|
+
return allDates;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
methods: {
|
|
62
|
+
|
|
63
|
+
calculateCalendarDimensions({ days, weeks, months, quarters }) {
|
|
64
|
+
let height = 0;
|
|
65
|
+
if (this.viewMode === 'quarter' && quarters.length > 0) {
|
|
66
|
+
height += this.root.state.options.calendar.quarter?.height || 30;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Add month height if visible
|
|
70
|
+
if ((this.viewMode === 'month') && months.length > 0) {
|
|
71
|
+
height += this.root.state.options.calendar.month.height || 25;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add week height if visible
|
|
75
|
+
if (this.viewMode === 'week' && weeks.length > 0) {
|
|
76
|
+
height += this.root.state.options.calendar.week?.height || 25;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Add day height if visible
|
|
80
|
+
if ((this.viewMode === 'day') && days.length > 0) {
|
|
81
|
+
height += this.root.state.options.calendar.day.height;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.root.state.options.calendar.height = height;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
generateDays() {
|
|
88
|
+
let days = [];
|
|
89
|
+
if (!(this.viewMode === 'day' || this.viewMode === 'week')) {
|
|
90
|
+
return days;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const daysCount = this.howManyDaysFit();
|
|
94
|
+
if (daysCount.count === 0) {
|
|
95
|
+
return days;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const steps = this.root.state.options.times.steps;
|
|
99
|
+
const localeName = this.root.state.options.locale.name;
|
|
100
|
+
const dayStep = Math.ceil(steps.length / daysCount.count);
|
|
101
|
+
|
|
102
|
+
for (let dayIndex = 0, len = steps.length; dayIndex < len; dayIndex += dayStep) {
|
|
103
|
+
let dayWidthPx = 0;
|
|
104
|
+
|
|
105
|
+
// Calculate total width for this day group
|
|
106
|
+
for (let currentStep = 0; currentStep < dayStep; currentStep++) {
|
|
107
|
+
if (typeof steps[dayIndex + currentStep] !== 'undefined') {
|
|
108
|
+
dayWidthPx += steps[dayIndex + currentStep].width.px;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const date = this.dayjsInstance(steps[dayIndex].time);
|
|
113
|
+
let textWidth = 0;
|
|
114
|
+
|
|
115
|
+
if (typeof this.root.state.options.calendar.day.widths[dayIndex] !== 'undefined') {
|
|
116
|
+
textWidth = this.root.state.options.calendar.day.widths[dayIndex][daysCount.type];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Use exact step offset for perfect alignment with grid lines
|
|
120
|
+
let x = Math.round(steps[dayIndex].offset.px); // Round to ensure exact pixel alignment
|
|
121
|
+
days.push({
|
|
122
|
+
index: dayIndex,
|
|
123
|
+
key: steps[dayIndex].time + 'd',
|
|
124
|
+
x,
|
|
125
|
+
y: this.getYPosition('day'),
|
|
126
|
+
width: dayWidthPx,
|
|
127
|
+
textWidth,
|
|
128
|
+
height: this.root.state.options.calendar.day.height,
|
|
129
|
+
label: this.root.state.options.calendar.day.format[daysCount.type](date.locale(localeName))
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return days.map(item => ({
|
|
134
|
+
key: item.key,
|
|
135
|
+
children: [item]
|
|
136
|
+
}));
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
generateWeeks() {
|
|
140
|
+
let weeks = [];
|
|
141
|
+
if (this.viewMode !== 'week') {
|
|
142
|
+
return weeks;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const steps = this.root.state.options.times.steps;
|
|
146
|
+
|
|
147
|
+
let currentWeek = null;
|
|
148
|
+
let weekStartX = 0;
|
|
149
|
+
let weekWidth = 0;
|
|
150
|
+
let weekStartDate = null;
|
|
151
|
+
let weekEndDate = null;
|
|
152
|
+
|
|
153
|
+
for (let i = 0; i < steps.length; i++) {
|
|
154
|
+
try {
|
|
155
|
+
const stepDate = this.dayjsInstance(steps[i].time);
|
|
156
|
+
const weekNumber = stepDate.week();
|
|
157
|
+
const year = stepDate.year();
|
|
158
|
+
const weekKey = `${year}-W${weekNumber}`;
|
|
159
|
+
|
|
160
|
+
if (weekKey !== currentWeek) {
|
|
161
|
+
// Save previous week
|
|
162
|
+
if (currentWeek !== null) {
|
|
163
|
+
// Format the label as "Month Day-Day"
|
|
164
|
+
const startMonth = weekStartDate.format('MMMM');
|
|
165
|
+
const startDay = weekStartDate.date();
|
|
166
|
+
const endDay = weekEndDate.date();
|
|
167
|
+
let label = `${startMonth} ${startDay}`;
|
|
168
|
+
|
|
169
|
+
// If the week spans multiple months, show both months
|
|
170
|
+
if (weekStartDate.month() !== weekEndDate.month()) {
|
|
171
|
+
const endMonth = weekEndDate.format('MMMM');
|
|
172
|
+
label = `${startMonth} ${startDay} - ${endMonth} ${endDay}`;
|
|
173
|
+
} else if (startDay !== endDay) {
|
|
174
|
+
label = `${startMonth} ${startDay}-${endDay}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
weeks.push({
|
|
178
|
+
key: currentWeek,
|
|
179
|
+
x: weekStartX, // Use exact step offset for perfect alignment
|
|
180
|
+
y: this.getYPosition('week'),
|
|
181
|
+
width: weekWidth,
|
|
182
|
+
textWidth: 50,
|
|
183
|
+
height: this.root.state.options.calendar.week?.height || 25,
|
|
184
|
+
label: label
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Start new week - use exact step offset
|
|
189
|
+
currentWeek = weekKey;
|
|
190
|
+
weekStartX = Math.round(steps[i].offset.px); // Round to ensure exact pixel alignment
|
|
191
|
+
weekWidth = steps[i].width.px;
|
|
192
|
+
weekStartDate = stepDate.clone().startOf('week');
|
|
193
|
+
weekEndDate = stepDate.clone().endOf('week');
|
|
194
|
+
} else {
|
|
195
|
+
// Same week, accumulate width and update end date
|
|
196
|
+
weekWidth += steps[i].width.px;
|
|
197
|
+
weekEndDate = stepDate.clone().endOf('week');
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error('Error processing week:', error);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Add the last week
|
|
206
|
+
if (currentWeek !== null) {
|
|
207
|
+
// Format the label as "Month Day-Day"
|
|
208
|
+
const startMonth = weekStartDate.format('MMMM');
|
|
209
|
+
const startDay = weekStartDate.date();
|
|
210
|
+
const endDay = weekEndDate.date();
|
|
211
|
+
let label = `${startMonth} ${startDay}`;
|
|
212
|
+
|
|
213
|
+
// If the week spans multiple months, show both months
|
|
214
|
+
if (weekStartDate.month() !== weekEndDate.month()) {
|
|
215
|
+
const endMonth = weekEndDate.format('MMMM');
|
|
216
|
+
label = `${startMonth} ${startDay} - ${endMonth} ${endDay}`;
|
|
217
|
+
} else if (startDay !== endDay) {
|
|
218
|
+
label = `${startMonth} ${startDay}-${endDay}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
weeks.push({
|
|
222
|
+
key: currentWeek,
|
|
223
|
+
x: weekStartX, // Use exact step offset for perfect alignment
|
|
224
|
+
y: this.getYPosition('week'),
|
|
225
|
+
width: weekWidth,
|
|
226
|
+
textWidth: 60,
|
|
227
|
+
height: this.root.state.options.calendar.week?.height || 25,
|
|
228
|
+
label: label
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return weeks.map(week => ({
|
|
233
|
+
key: week.key,
|
|
234
|
+
children: [week]
|
|
235
|
+
}));
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
generateMonths() {
|
|
239
|
+
let months = [];
|
|
240
|
+
if (!this.root.state.options.calendar.month.display) {
|
|
241
|
+
return months;
|
|
242
|
+
}
|
|
243
|
+
const monthsCount = this.howManyMonthsFit();
|
|
244
|
+
if (monthsCount.count === 0) {
|
|
245
|
+
return months;
|
|
246
|
+
}
|
|
247
|
+
const steps = this.root.state.options.times.steps;
|
|
248
|
+
const localeName = this.root.state.options.locale.name;
|
|
249
|
+
let formatNames = Object.keys(this.root.state.options.calendar.month.format);
|
|
250
|
+
let currentDate = dayjs(this.root.state.options.times.firstTime);
|
|
251
|
+
for (let monthIndex = 0; monthIndex < monthsCount.count; monthIndex++) {
|
|
252
|
+
let monthWidth = 0;
|
|
253
|
+
let monthOffset = Number.MAX_SAFE_INTEGER;
|
|
254
|
+
let finalDate = dayjs(currentDate)
|
|
255
|
+
.add(1, 'month')
|
|
256
|
+
.startOf('month');
|
|
257
|
+
if (finalDate.valueOf() > this.root.state.options.times.lastTime) {
|
|
258
|
+
finalDate = dayjs(this.root.state.options.times.lastTime);
|
|
259
|
+
}
|
|
260
|
+
// we must find first and last step to get the offsets / widths
|
|
261
|
+
for (let step = 0, len = this.root.state.options.times.steps.length; step < len; step++) {
|
|
262
|
+
let currentStep = this.root.state.options.times.steps[step];
|
|
263
|
+
if (currentStep.time >= currentDate.valueOf() && currentStep.time < finalDate.valueOf()) {
|
|
264
|
+
monthWidth += currentStep.width.px;
|
|
265
|
+
if (currentStep.offset.px < monthOffset) {
|
|
266
|
+
monthOffset = currentStep.offset.px;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
let label = '';
|
|
271
|
+
let choosenFormatName;
|
|
272
|
+
for (let formatName of formatNames) {
|
|
273
|
+
if (this.root.state.options.calendar.month.maxWidths[formatName] + 2 <= monthWidth) {
|
|
274
|
+
label = this.root.state.options.calendar.month.format[formatName](currentDate.locale(localeName));
|
|
275
|
+
choosenFormatName = formatName;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
let textWidth = 0;
|
|
279
|
+
if (typeof this.root.state.options.calendar.month.widths[monthIndex] !== 'undefined') {
|
|
280
|
+
textWidth = this.root.state.options.calendar.month.widths[monthIndex][choosenFormatName];
|
|
281
|
+
}
|
|
282
|
+
// Use exact step offset for perfect alignment with grid lines
|
|
283
|
+
let x = Math.round(monthOffset); // Round to ensure exact pixel alignment
|
|
284
|
+
months.push({
|
|
285
|
+
index: monthIndex,
|
|
286
|
+
key: monthIndex + 'm',
|
|
287
|
+
x,
|
|
288
|
+
y: 0,
|
|
289
|
+
width: monthWidth,
|
|
290
|
+
textWidth,
|
|
291
|
+
choosenFormatName,
|
|
292
|
+
height: this.root.state.options.calendar.month.height,
|
|
293
|
+
label
|
|
294
|
+
});
|
|
295
|
+
currentDate = currentDate.add(1, 'month').startOf('month');
|
|
296
|
+
if (currentDate.valueOf() > this.root.state.options.times.lastTime) {
|
|
297
|
+
currentDate = dayjs(this.root.state.options.times.lastTime);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return months.map(item => ({
|
|
301
|
+
key: item.key,
|
|
302
|
+
children: [item]
|
|
303
|
+
}));
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
generateQuarters() {
|
|
308
|
+
let quarters = [];
|
|
309
|
+
if (this.viewMode !== 'quarter') {
|
|
310
|
+
return quarters;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const steps = this.root.state.options.times.steps;
|
|
314
|
+
|
|
315
|
+
// Debug: Log quarter generation info
|
|
316
|
+
console.log('Quarter Generation Debug:', {
|
|
317
|
+
stepsCount: steps.length,
|
|
318
|
+
firstStepTime: this.dayjsInstance(steps[0]?.time).format('YYYY-MM-DD'),
|
|
319
|
+
lastStepTime: this.dayjsInstance(steps[steps.length - 1]?.time).format('YYYY-MM-DD'),
|
|
320
|
+
firstStepQuarter: this.dayjsInstance(steps[0]?.time).quarter(),
|
|
321
|
+
lastStepQuarter: this.dayjsInstance(steps[steps.length - 1]?.time).quarter()
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
let currentQuarter = null;
|
|
325
|
+
let quarterStartX = 0;
|
|
326
|
+
let quarterWidth = 0;
|
|
327
|
+
let quarterStartDate = null;
|
|
328
|
+
let quarterEndDate = null;
|
|
329
|
+
|
|
330
|
+
for (let i = 0; i < steps.length; i++) {
|
|
331
|
+
try {
|
|
332
|
+
// Use UTC to avoid locale/timezone issues
|
|
333
|
+
const stepDate = this.dayjsInstance(steps[i].time);
|
|
334
|
+
const quarter = stepDate.quarter();
|
|
335
|
+
const year = stepDate.year();
|
|
336
|
+
const quarterKey = `Q${quarter}-${year}`;
|
|
337
|
+
|
|
338
|
+
console.log(`Step ${i}:`, {
|
|
339
|
+
time: stepDate.format('YYYY-MM-DD'),
|
|
340
|
+
quarter: quarter,
|
|
341
|
+
year: year,
|
|
342
|
+
quarterKey: quarterKey,
|
|
343
|
+
currentQuarter: currentQuarter
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
if (quarterKey !== currentQuarter) {
|
|
347
|
+
// Save previous quarter
|
|
348
|
+
if (currentQuarter !== null) {
|
|
349
|
+
// Format the label as "Month – Month 'Year"
|
|
350
|
+
const startMonth = quarterStartDate.format('MMMM');
|
|
351
|
+
const endMonth = quarterEndDate.format('MMMM');
|
|
352
|
+
const shortYear = quarterEndDate.format("YY");
|
|
353
|
+
const label = `${startMonth} – ${endMonth} '${shortYear}`;
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
quarters.push({
|
|
357
|
+
key: currentQuarter,
|
|
358
|
+
x: quarterStartX, // Use exact step offset for perfect alignment
|
|
359
|
+
y: this.getYPosition('quarter'),
|
|
360
|
+
width: quarterWidth,
|
|
361
|
+
textWidth: 50,
|
|
362
|
+
height: this.root.state.options.calendar.quarter?.height || 30,
|
|
363
|
+
label: label
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Start new quarter
|
|
368
|
+
currentQuarter = quarterKey;
|
|
369
|
+
quarterStartX = Math.round(steps[i].offset.px); // Round to ensure exact pixel alignment
|
|
370
|
+
quarterWidth = steps[i].width.px;
|
|
371
|
+
quarterStartDate = stepDate.clone().startOf('quarter');
|
|
372
|
+
quarterEndDate = stepDate.clone().endOf('quarter');
|
|
373
|
+
} else {
|
|
374
|
+
// Same quarter, accumulate width and update end date
|
|
375
|
+
quarterWidth += steps[i].width.px;
|
|
376
|
+
quarterEndDate = stepDate.clone().endOf('quarter');
|
|
377
|
+
}
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error('Error processing quarter:', error);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Add the last quarter
|
|
385
|
+
if (currentQuarter !== null) {
|
|
386
|
+
// Format the label as "Month – Month 'Year"
|
|
387
|
+
const startMonth = quarterStartDate.format('MMMM');
|
|
388
|
+
const endMonth = quarterEndDate.format('MMMM');
|
|
389
|
+
const shortYear = quarterEndDate.format("YY");
|
|
390
|
+
const label = `${startMonth} – ${endMonth} '${shortYear}`;
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
quarters.push({
|
|
394
|
+
key: currentQuarter,
|
|
395
|
+
x: quarterStartX, // Use exact step offset for perfect alignment
|
|
396
|
+
y: this.getYPosition('quarter'),
|
|
397
|
+
width: quarterWidth,
|
|
398
|
+
textWidth: 60,
|
|
399
|
+
height: this.root.state.options.calendar.quarter?.height || 30,
|
|
400
|
+
label: label
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
return quarters.map(quarter => ({
|
|
406
|
+
key: quarter.key,
|
|
407
|
+
children: [quarter]
|
|
408
|
+
}));
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
getYPosition(type) {
|
|
412
|
+
let y = 0;
|
|
413
|
+
switch (type) {
|
|
414
|
+
case 'quarter':
|
|
415
|
+
return 0;
|
|
416
|
+
case 'month':
|
|
417
|
+
// Put month row below quarter if in quarterly view
|
|
418
|
+
return this.viewMode === 'quarter'
|
|
419
|
+
? (this.root.state.options.calendar.quarter?.height || 30)
|
|
420
|
+
: 0;
|
|
421
|
+
case 'week':
|
|
422
|
+
// Put week row below month and quarter if applicable
|
|
423
|
+
|
|
424
|
+
if (this.viewMode === 'quarter') {
|
|
425
|
+
y += this.root.state.options.calendar.quarter?.height || 30;
|
|
426
|
+
}
|
|
427
|
+
if (this.viewMode === 'month' || this.viewMode === 'quarter') {
|
|
428
|
+
y += this.root.state.options.calendar.month.height;
|
|
429
|
+
}
|
|
430
|
+
return y;
|
|
431
|
+
case 'day':
|
|
432
|
+
// Put day below week if applicable
|
|
433
|
+
y = 0;
|
|
434
|
+
if (this.viewMode === 'quarter') {
|
|
435
|
+
y += this.root.state.options.calendar.quarter?.height || 30;
|
|
436
|
+
}
|
|
437
|
+
if (this.viewMode === 'month' || this.viewMode === 'quarter') {
|
|
438
|
+
y += this.root.state.options.calendar.month.height;
|
|
439
|
+
}
|
|
440
|
+
if (this.viewMode === 'week') {
|
|
441
|
+
y += this.root.state.options.calendar.week?.height || 25;
|
|
442
|
+
}
|
|
443
|
+
return y;
|
|
444
|
+
case 'hour':
|
|
445
|
+
// Put hour row below day if applicable
|
|
446
|
+
y = 0;
|
|
447
|
+
if (this.viewMode === 'quarter') {
|
|
448
|
+
y += this.root.state.options.calendar.quarter?.height || 30;
|
|
449
|
+
}
|
|
450
|
+
if (this.viewMode === 'month' || this.viewMode === 'quarter') {
|
|
451
|
+
y += this.root.state.options.calendar.month.height;
|
|
452
|
+
}
|
|
453
|
+
if (this.viewMode === 'week') {
|
|
454
|
+
y += this.root.state.options.calendar.week?.height || 25;
|
|
455
|
+
}
|
|
456
|
+
if (this.viewMode === 'day') {
|
|
457
|
+
y += this.root.state.options.calendar.day.height;
|
|
458
|
+
}
|
|
459
|
+
return y;
|
|
460
|
+
default:
|
|
461
|
+
return 0;
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
howManyHoursFit(dayIndex) {
|
|
466
|
+
const stroke = 1;
|
|
467
|
+
const additionalSpace = stroke + 2;
|
|
468
|
+
let fullCellWidth = this.root.state.options.times.steps[dayIndex].width.px;
|
|
469
|
+
let formatNames = Object.keys(this.root.state.options.calendar.hour.format);
|
|
470
|
+
for (let hours = 24; hours > 1; hours = Math.ceil(hours / 2)) {
|
|
471
|
+
for (let formatName of formatNames) {
|
|
472
|
+
if (
|
|
473
|
+
(this.root.state.options.calendar.hour.maxWidths[formatName] + additionalSpace) * hours <= fullCellWidth &&
|
|
474
|
+
hours > 1
|
|
475
|
+
) {
|
|
476
|
+
return {
|
|
477
|
+
count: hours,
|
|
478
|
+
type: formatName
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
count: 0,
|
|
485
|
+
type: ''
|
|
486
|
+
};
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
howManyDaysFit() {
|
|
490
|
+
const stroke = 1;
|
|
491
|
+
const additionalSpace = stroke + 2;
|
|
492
|
+
let fullWidth = this.root.state.options.width;
|
|
493
|
+
let formatNames = Object.keys(this.root.state.options.calendar.day.format);
|
|
494
|
+
for (let days = this.root.state.options.times.steps.length; days > 1; days = Math.ceil(days / 2)) {
|
|
495
|
+
for (let formatName of formatNames) {
|
|
496
|
+
if (
|
|
497
|
+
(this.root.state.options.calendar.day.maxWidths[formatName] + additionalSpace) * days <= fullWidth &&
|
|
498
|
+
days > 1
|
|
499
|
+
) {
|
|
500
|
+
return {
|
|
501
|
+
count: days,
|
|
502
|
+
type: formatName
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
count: 0,
|
|
509
|
+
type: ''
|
|
510
|
+
};
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
howManyMonthsFit() {
|
|
515
|
+
const stroke = 1;
|
|
516
|
+
const additionalSpace = stroke + 2;
|
|
517
|
+
let fullWidth = this.root.state.options.width;
|
|
518
|
+
let formatNames = Object.keys(this.root.state.options.calendar.month.format);
|
|
519
|
+
let currentMonth = dayjs(this.root.state.options.times.firstTime);
|
|
520
|
+
let previousMonth = currentMonth.clone();
|
|
521
|
+
const lastTime = this.root.state.options.times.lastTime;
|
|
522
|
+
let monthsCount = this.root.monthsCount(
|
|
523
|
+
this.root.state.options.times.firstTime,
|
|
524
|
+
this.root.state.options.times.lastTime
|
|
525
|
+
);
|
|
526
|
+
if (monthsCount === 1) {
|
|
527
|
+
for (let formatName of formatNames) {
|
|
528
|
+
if (this.root.state.options.calendar.month.maxWidths[formatName] + additionalSpace <= fullWidth) {
|
|
529
|
+
return {
|
|
530
|
+
count: 1,
|
|
531
|
+
type: formatName
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
for (let months = monthsCount; months > 1; months = Math.ceil(months / 2)) {
|
|
537
|
+
for (let formatName of formatNames) {
|
|
538
|
+
if (
|
|
539
|
+
(this.root.state.options.calendar.month.maxWidths[formatName] + additionalSpace) * months <= fullWidth &&
|
|
540
|
+
months > 1
|
|
541
|
+
) {
|
|
542
|
+
return {
|
|
543
|
+
count: months,
|
|
544
|
+
type: formatName
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
count: 0,
|
|
551
|
+
type: formatNames[0]
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
</script>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section>
|
|
3
|
+
<!-- <div>
|
|
4
|
+
{{ items }}
|
|
5
|
+
</div> -->
|
|
6
|
+
|
|
7
|
+
<div :class="'gantt-elastic__calendar-row gantt-elastic__calendar-row--' + which" :style="rowStyle">
|
|
8
|
+
<div v-for="(item, itemIndex) in items" :key="item.key"
|
|
9
|
+
:class="'gantt-elastic__calendar-row-rect gantt-elastic__calendar-row-rect--' + which" :style="rectStyle">
|
|
10
|
+
<div :class="'gantt-elastic__calendar-row-rect-child gantt-elastic__calendar-row-rect-child--' + which"
|
|
11
|
+
v-for="(child, childIndex) in item.children" :key="child.key" :style="rectChildStyle[itemIndex][childIndex]">
|
|
12
|
+
<div :class="'gantt-elastic__calendar-row-text gantt-elastic__calendar-row-text--' + which"
|
|
13
|
+
:style="textStyle(child)">
|
|
14
|
+
{{ child.label }}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</section>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
export default {
|
|
24
|
+
name: 'CalendarRow',
|
|
25
|
+
inject: ['root'],
|
|
26
|
+
props: ['items', 'which'],
|
|
27
|
+
data() {
|
|
28
|
+
return {};
|
|
29
|
+
},
|
|
30
|
+
methods: {
|
|
31
|
+
/**
|
|
32
|
+
* Get x position
|
|
33
|
+
*
|
|
34
|
+
* @returns {number}
|
|
35
|
+
*/
|
|
36
|
+
getTextX(item) {
|
|
37
|
+
// Calculate center position within the item
|
|
38
|
+
let x = item.width / 2 - item.textWidth / 2;
|
|
39
|
+
|
|
40
|
+
// Add padding to avoid overlap with grid lines
|
|
41
|
+
const gridLineWidth = this.root.style['grid-line-vertical']['stroke-width'] || 1;
|
|
42
|
+
const padding = Math.max(gridLineWidth / 2 + 2, 4); // At least 4px padding
|
|
43
|
+
|
|
44
|
+
// For month view, use perfect centering with bounds checking
|
|
45
|
+
if (this.which === 'month') {
|
|
46
|
+
// Perfect center calculation
|
|
47
|
+
x = (item.width - item.textWidth) / 2;
|
|
48
|
+
|
|
49
|
+
// Ensure text stays within bounds with padding
|
|
50
|
+
if (x < padding) {
|
|
51
|
+
x = padding;
|
|
52
|
+
} else if (x + item.textWidth + padding > item.width) {
|
|
53
|
+
x = item.width - item.textWidth - padding;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
// For other view types, ensure text stays within bounds
|
|
57
|
+
if (x + item.textWidth + padding > item.width) {
|
|
58
|
+
x = item.width - item.textWidth - padding;
|
|
59
|
+
} else if (x < padding) {
|
|
60
|
+
x = padding;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return x;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
computed: {
|
|
68
|
+
rowStyle() {
|
|
69
|
+
return {
|
|
70
|
+
...this.root.style['calendar-row'],
|
|
71
|
+
...this.root.style['calendar-row--' + this.which],
|
|
72
|
+
position: 'relative' // Ensure relative positioning for absolute children
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
rectStyle() {
|
|
76
|
+
return { ...this.root.style['calendar-row-rect'], ...this.root.style['calendar-row-rect--' + this.which] };
|
|
77
|
+
},
|
|
78
|
+
rectChildStyle() {
|
|
79
|
+
const basicStyle = {
|
|
80
|
+
...this.root.style['calendar-row-rect-child'],
|
|
81
|
+
...this.root.style['calendar-row-rect-child--' + this.which]
|
|
82
|
+
};
|
|
83
|
+
const style = [];
|
|
84
|
+
for (let item of this.items) {
|
|
85
|
+
const childrenStyle = [];
|
|
86
|
+
for (let child of item.children) {
|
|
87
|
+
childrenStyle.push({
|
|
88
|
+
...basicStyle,
|
|
89
|
+
width: child.width + 'px',
|
|
90
|
+
height: child.height + 'px',
|
|
91
|
+
left: child.x + 'px', // Use exact x position for perfect alignment
|
|
92
|
+
position: 'absolute' // Ensure absolute positioning
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
style.push(childrenStyle);
|
|
96
|
+
}
|
|
97
|
+
return style;
|
|
98
|
+
},
|
|
99
|
+
textStyle() {
|
|
100
|
+
const basicStyle = {
|
|
101
|
+
...this.root.style['calendar-row-text'],
|
|
102
|
+
...this.root.style['calendar-row-text--' + this.which]
|
|
103
|
+
};
|
|
104
|
+
return child => {
|
|
105
|
+
const style = { ...basicStyle };
|
|
106
|
+
// Remove left positioning for month view - let CSS handle centering
|
|
107
|
+
return style;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
</script>
|