@farm-investimentos/front-mfe-components 15.13.1 → 15.13.3
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/dist/front-mfe-components.common.js +17708 -17585
- package/dist/front-mfe-components.common.js.map +1 -1
- package/dist/front-mfe-components.css +1 -1
- package/dist/front-mfe-components.umd.js +17708 -17585
- package/dist/front-mfe-components.umd.js.map +1 -1
- package/dist/front-mfe-components.umd.min.js +1 -1
- package/dist/front-mfe-components.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/components/GanttChart/GanttChart.scss +5 -0
- package/src/components/GanttChart/GanttChart.vue +72 -27
- package/src/components/GanttChart/__tests__/GanttChart.spec.js +22 -47
- package/src/components/GanttChart/composition/buildBarPositioning.ts +6 -2
- package/src/components/GanttChart/composition/buildGanttData.ts +3 -3
- package/src/components/GanttChart/types/index.ts +7 -9
- package/src/main.ts +1 -0
package/package.json
CHANGED
|
@@ -136,6 +136,7 @@
|
|
|
136
136
|
margin-top: gutter('lg');
|
|
137
137
|
padding-top: gutter('sm');
|
|
138
138
|
flex-wrap: wrap;
|
|
139
|
+
width: 100%;
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
&__legend-title {
|
|
@@ -213,6 +214,10 @@
|
|
|
213
214
|
margin-bottom: 0;
|
|
214
215
|
}
|
|
215
216
|
}
|
|
217
|
+
.tooltip-label {
|
|
218
|
+
margin-right: 4px;
|
|
219
|
+
}
|
|
220
|
+
|
|
216
221
|
|
|
217
222
|
.tooltip-text {
|
|
218
223
|
font-weight: 500;
|
|
@@ -29,7 +29,10 @@
|
|
|
29
29
|
class="farm-gantt-chart__group"
|
|
30
30
|
>
|
|
31
31
|
<div class="farm-gantt-chart__group-label">
|
|
32
|
-
<farm-typography :weight="
|
|
32
|
+
<farm-typography v-if="group.subtitle" size="md" :weight="600" color="black" color-variation="50" class="mb-1">
|
|
33
|
+
{{ group.subtitle }}
|
|
34
|
+
</farm-typography>
|
|
35
|
+
<farm-typography size ="md" :weight="500" color="black" color-variation="50">
|
|
33
36
|
{{ group.title }}
|
|
34
37
|
</farm-typography>
|
|
35
38
|
</div>
|
|
@@ -50,7 +53,7 @@
|
|
|
50
53
|
</div>
|
|
51
54
|
</div>
|
|
52
55
|
</div>
|
|
53
|
-
|
|
56
|
+
|
|
54
57
|
</div>
|
|
55
58
|
|
|
56
59
|
<div class="farm-gantt-chart__legend" v-if="autoGeneratedLegend.length > 0" :style="legendStyle">
|
|
@@ -80,6 +83,7 @@
|
|
|
80
83
|
v-if="tooltipState.visible"
|
|
81
84
|
class="farm-gantt-chart__tooltip-container"
|
|
82
85
|
:style="tooltipPositionStyle"
|
|
86
|
+
ref="tooltipElement"
|
|
83
87
|
>
|
|
84
88
|
<div v-if="$slots.tooltip" class="farm-gantt-chart__tooltip">
|
|
85
89
|
<slot
|
|
@@ -88,7 +92,7 @@
|
|
|
88
92
|
:tooltipData="tooltipState.barData && tooltipState.barData.tooltipData"
|
|
89
93
|
/>
|
|
90
94
|
</div>
|
|
91
|
-
|
|
95
|
+
|
|
92
96
|
<div
|
|
93
97
|
v-else-if="tooltipState.barData && tooltipState.barData.tooltipData"
|
|
94
98
|
class="farm-gantt-chart__tooltip farm-tooltip__popup farm-tooltip__popup--visible"
|
|
@@ -109,7 +113,7 @@
|
|
|
109
113
|
</div>
|
|
110
114
|
</div>
|
|
111
115
|
</div>
|
|
112
|
-
|
|
116
|
+
|
|
113
117
|
<div
|
|
114
118
|
v-else
|
|
115
119
|
class="farm-gantt-chart__tooltip farm-tooltip__popup farm-tooltip__popup--visible"
|
|
@@ -128,7 +132,7 @@
|
|
|
128
132
|
</template>
|
|
129
133
|
|
|
130
134
|
<script lang="ts">
|
|
131
|
-
import { defineComponent, PropType, computed, reactive, ref } from 'vue';
|
|
135
|
+
import { defineComponent, PropType, computed, reactive, ref, onMounted, onUnmounted, nextTick } from 'vue';
|
|
132
136
|
import type { GanttData, TooltipState, GanttBar } from './types';
|
|
133
137
|
import { getMonthsBetween, formatMonth, isCurrentMonth, getColumnForDate, formatDateRange } from './utils/dateUtils';
|
|
134
138
|
import { buildGanttData, buildBarPositioning } from './composition';
|
|
@@ -144,21 +148,21 @@ export default defineComponent({
|
|
|
144
148
|
console.warn('GanttChart: prop "data" deve ser um objeto.');
|
|
145
149
|
return false;
|
|
146
150
|
}
|
|
147
|
-
|
|
151
|
+
|
|
148
152
|
if (!Array.isArray(value.groups)) {
|
|
149
153
|
console.warn('GanttChart: prop "data.groups" deve ser um array.');
|
|
150
154
|
return false;
|
|
151
155
|
}
|
|
152
|
-
|
|
156
|
+
|
|
153
157
|
return value.groups.every((group: any) => {
|
|
154
158
|
const hasValidTitle = typeof group.title === 'string';
|
|
155
159
|
const hasValidBars = Array.isArray(group.bars);
|
|
156
|
-
|
|
160
|
+
|
|
157
161
|
if (!hasValidTitle || !hasValidBars) {
|
|
158
162
|
console.warn('GanttChart: cada grupo deve ter título (string) e barras (array).');
|
|
159
163
|
return false;
|
|
160
164
|
}
|
|
161
|
-
|
|
165
|
+
|
|
162
166
|
return true;
|
|
163
167
|
});
|
|
164
168
|
}
|
|
@@ -223,17 +227,23 @@ export default defineComponent({
|
|
|
223
227
|
|
|
224
228
|
const legendStyle = computed(() => {
|
|
225
229
|
const timelineWidth = monthColumns.value.length * 80;
|
|
226
|
-
const totalWidth =
|
|
230
|
+
const totalWidth = 180 + timelineWidth;
|
|
227
231
|
return {
|
|
228
|
-
|
|
232
|
+
minWidth: `${totalWidth}px`,
|
|
229
233
|
};
|
|
230
234
|
});
|
|
231
235
|
|
|
236
|
+
const tooltipElement = ref<HTMLElement>();
|
|
237
|
+
const lastMousePosition = ref<{ eventX: number; eventY: number } | null>(null);
|
|
238
|
+
|
|
232
239
|
const onBarMouseEnter = (bar: GanttBar, event: MouseEvent) => {
|
|
233
240
|
tooltipState.visible = true;
|
|
234
241
|
tooltipState.title = bar.tooltipTitle || bar.label;
|
|
235
242
|
tooltipState.barData = bar;
|
|
236
|
-
|
|
243
|
+
|
|
244
|
+
nextTick(() => {
|
|
245
|
+
updateTooltipPosition(event);
|
|
246
|
+
});
|
|
237
247
|
};
|
|
238
248
|
|
|
239
249
|
const onBarMouseMove = (bar: GanttBar, event: MouseEvent) => {
|
|
@@ -247,30 +257,50 @@ export default defineComponent({
|
|
|
247
257
|
tooltipState.barData = null;
|
|
248
258
|
};
|
|
249
259
|
|
|
250
|
-
const updateTooltipPosition = (event
|
|
251
|
-
|
|
260
|
+
const updateTooltipPosition = (event?: MouseEvent) => {
|
|
261
|
+
const container = chartContainer.value;
|
|
262
|
+
const tooltipEl = tooltipElement.value;
|
|
263
|
+
if (!container || !tooltipEl) return;
|
|
264
|
+
|
|
265
|
+
const { left, top } = container.getBoundingClientRect();
|
|
252
266
|
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
const y = event.clientY - containerRect.top;
|
|
267
|
+
const scrollLeft = container.scrollLeft;
|
|
268
|
+
const scrollTop = container.scrollTop;
|
|
256
269
|
|
|
257
|
-
const tooltipWidth = 300;
|
|
258
|
-
const tooltipHeight = 100;
|
|
270
|
+
const tooltipWidth = tooltipEl.offsetWidth || 300;
|
|
271
|
+
const tooltipHeight = tooltipEl.offsetHeight || 100;
|
|
259
272
|
const margin = 15;
|
|
260
273
|
|
|
261
|
-
let
|
|
262
|
-
let
|
|
274
|
+
let contentX: number;
|
|
275
|
+
let contentY: number;
|
|
276
|
+
|
|
277
|
+
if (event) {
|
|
278
|
+
lastMousePosition.value = { eventX: event.clientX, eventY: event.clientY };
|
|
279
|
+
contentX = event.clientX - left + scrollLeft;
|
|
280
|
+
contentY = event.clientY - top + scrollTop;
|
|
281
|
+
} else if (lastMousePosition.value) {
|
|
282
|
+
contentX = lastMousePosition.value.eventX - left + scrollLeft;
|
|
283
|
+
contentY = lastMousePosition.value.eventY - top + scrollTop;
|
|
284
|
+
} else {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let tooltipX = contentX + margin;
|
|
289
|
+
let tooltipY = contentY + margin;
|
|
290
|
+
|
|
291
|
+
const maxVisibleX = scrollLeft + container.clientWidth - tooltipWidth - 10;
|
|
292
|
+
const maxVisibleY = scrollTop + container.clientHeight - tooltipHeight - 10;
|
|
263
293
|
|
|
264
|
-
if (tooltipX
|
|
265
|
-
tooltipX =
|
|
294
|
+
if (tooltipX > maxVisibleX) {
|
|
295
|
+
tooltipX = maxVisibleX;
|
|
266
296
|
}
|
|
267
297
|
|
|
268
|
-
if (tooltipY
|
|
269
|
-
tooltipY = Math.max(10,
|
|
298
|
+
if (tooltipY > maxVisibleY) {
|
|
299
|
+
tooltipY = Math.max(scrollTop + 10, contentY - tooltipHeight - margin);
|
|
270
300
|
}
|
|
271
301
|
|
|
272
|
-
tooltipX = Math.max(10, tooltipX);
|
|
273
|
-
tooltipY = Math.max(10, tooltipY);
|
|
302
|
+
tooltipX = Math.max(scrollLeft + 10, tooltipX);
|
|
303
|
+
tooltipY = Math.max(scrollTop + 10, tooltipY);
|
|
274
304
|
|
|
275
305
|
tooltipState.x = tooltipX;
|
|
276
306
|
tooltipState.y = tooltipY;
|
|
@@ -281,6 +311,20 @@ export default defineComponent({
|
|
|
281
311
|
zIndex: 999
|
|
282
312
|
}));
|
|
283
313
|
|
|
314
|
+
onMounted(() => {
|
|
315
|
+
if (!chartContainer.value) return;
|
|
316
|
+
const container = chartContainer.value;
|
|
317
|
+
const handleScroll = () => {
|
|
318
|
+
if (tooltipState.visible) {
|
|
319
|
+
updateTooltipPosition();
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
container.addEventListener('scroll', handleScroll);
|
|
323
|
+
onUnmounted(() => {
|
|
324
|
+
container.removeEventListener('scroll', handleScroll);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
284
328
|
return {
|
|
285
329
|
monthColumns,
|
|
286
330
|
timelineGridStyle,
|
|
@@ -294,6 +338,7 @@ export default defineComponent({
|
|
|
294
338
|
tooltipState,
|
|
295
339
|
tooltipPositionStyle,
|
|
296
340
|
chartContainer,
|
|
341
|
+
tooltipElement,
|
|
297
342
|
onBarMouseEnter,
|
|
298
343
|
onBarMouseMove,
|
|
299
344
|
onBarMouseLeave,
|
|
@@ -93,15 +93,15 @@ describe('GanttChart component', () => {
|
|
|
93
93
|
{
|
|
94
94
|
id: 1,
|
|
95
95
|
label: 'Early Bar',
|
|
96
|
-
start: new Date(2025, 0, 15),
|
|
97
|
-
end: new Date(2025, 2, 10),
|
|
96
|
+
start: new Date(2025, 0, 15),
|
|
97
|
+
end: new Date(2025, 2, 10),
|
|
98
98
|
color: '#7BC4F7',
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
id: 2,
|
|
102
102
|
label: 'Late Bar',
|
|
103
|
-
start: new Date(2025, 4, 5),
|
|
104
|
-
end: new Date(2025, 6, 20),
|
|
103
|
+
start: new Date(2025, 4, 5),
|
|
104
|
+
end: new Date(2025, 6, 20),
|
|
105
105
|
color: '#8BB455',
|
|
106
106
|
},
|
|
107
107
|
],
|
|
@@ -113,11 +113,10 @@ describe('GanttChart component', () => {
|
|
|
113
113
|
propsData: { data: testData },
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
// Should calculate from January 1st to July 31st (full months)
|
|
117
116
|
const monthColumns = testWrapper.vm.monthColumns;
|
|
118
|
-
expect(monthColumns.length).toBe(
|
|
117
|
+
expect(monthColumns.length).toBe(8);
|
|
119
118
|
expect(monthColumns[0].label).toContain('Jan');
|
|
120
|
-
expect(monthColumns[monthColumns.length - 1].label).toContain('
|
|
119
|
+
expect(monthColumns[monthColumns.length - 1].label).toContain('Ago');
|
|
121
120
|
});
|
|
122
121
|
|
|
123
122
|
it('should handle single month range', () => {
|
|
@@ -129,8 +128,8 @@ describe('GanttChart component', () => {
|
|
|
129
128
|
{
|
|
130
129
|
id: 1,
|
|
131
130
|
label: 'Single Month Bar',
|
|
132
|
-
start: new Date(2025, 3, 5),
|
|
133
|
-
end: new Date(2025, 3, 25),
|
|
131
|
+
start: new Date(2025, 3, 5),
|
|
132
|
+
end: new Date(2025, 3, 25),
|
|
134
133
|
color: '#7BC4F7',
|
|
135
134
|
},
|
|
136
135
|
],
|
|
@@ -143,8 +142,9 @@ describe('GanttChart component', () => {
|
|
|
143
142
|
});
|
|
144
143
|
|
|
145
144
|
const monthColumns = testWrapper.vm.monthColumns;
|
|
146
|
-
expect(monthColumns.length).toBe(
|
|
145
|
+
expect(monthColumns.length).toBe(2);
|
|
147
146
|
expect(monthColumns[0].label).toContain('Abr');
|
|
147
|
+
expect(monthColumns[1].label).toContain('Mai');
|
|
148
148
|
});
|
|
149
149
|
});
|
|
150
150
|
|
|
@@ -171,7 +171,7 @@ describe('GanttChart component', () => {
|
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
173
|
id: 3,
|
|
174
|
-
label: 'Design',
|
|
174
|
+
label: 'Design',
|
|
175
175
|
start: new Date(2025, 2, 1),
|
|
176
176
|
end: new Date(2025, 3, 1),
|
|
177
177
|
color: '#8E44AD',
|
|
@@ -186,7 +186,7 @@ describe('GanttChart component', () => {
|
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
const legend = testWrapper.vm.autoGeneratedLegend;
|
|
189
|
-
expect(legend).toHaveLength(2);
|
|
189
|
+
expect(legend).toHaveLength(2);
|
|
190
190
|
expect(legend.some(item => item.label === 'Design' && item.color === '#8E44AD')).toBe(true);
|
|
191
191
|
expect(legend.some(item => item.label === 'Development' && item.color === '#16A085')).toBe(true);
|
|
192
192
|
});
|
|
@@ -219,7 +219,6 @@ describe('GanttChart component', () => {
|
|
|
219
219
|
];
|
|
220
220
|
const positionedBars = component.getPositionedBars(bars);
|
|
221
221
|
expect(positionedBars).toHaveLength(2);
|
|
222
|
-
// Each bar should have its own unique row position
|
|
223
222
|
expect(positionedBars[0].rowPosition).toBe(0);
|
|
224
223
|
expect(positionedBars[1].rowPosition).toBe(1);
|
|
225
224
|
});
|
|
@@ -228,21 +227,21 @@ describe('GanttChart component', () => {
|
|
|
228
227
|
const bars = [
|
|
229
228
|
{
|
|
230
229
|
id: 3,
|
|
231
|
-
start: new Date(2025, 2, 1),
|
|
230
|
+
start: new Date(2025, 2, 1),
|
|
232
231
|
end: new Date(2025, 2, 15),
|
|
233
232
|
label: 'Latest Bar',
|
|
234
233
|
color: '#FFB84D',
|
|
235
234
|
},
|
|
236
235
|
{
|
|
237
236
|
id: 1,
|
|
238
|
-
start: new Date(2025, 0, 1),
|
|
237
|
+
start: new Date(2025, 0, 1),
|
|
239
238
|
end: new Date(2025, 0, 15),
|
|
240
239
|
label: 'Early Bar',
|
|
241
240
|
color: '#7BC4F7',
|
|
242
241
|
},
|
|
243
242
|
{
|
|
244
243
|
id: 2,
|
|
245
|
-
start: new Date(2025, 1, 1),
|
|
244
|
+
start: new Date(2025, 1, 1),
|
|
246
245
|
end: new Date(2025, 1, 15),
|
|
247
246
|
label: 'Middle Bar',
|
|
248
247
|
color: '#8BB455',
|
|
@@ -250,10 +249,9 @@ describe('GanttChart component', () => {
|
|
|
250
249
|
];
|
|
251
250
|
const positionedBars = component.getPositionedBars(bars);
|
|
252
251
|
expect(positionedBars).toHaveLength(3);
|
|
253
|
-
|
|
254
|
-
expect(positionedBars[
|
|
255
|
-
expect(positionedBars[
|
|
256
|
-
expect(positionedBars[2].id).toBe(2); // Middle date stays third
|
|
252
|
+
expect(positionedBars[0].id).toBe(3);
|
|
253
|
+
expect(positionedBars[1].id).toBe(1);
|
|
254
|
+
expect(positionedBars[2].id).toBe(2);
|
|
257
255
|
expect(positionedBars[0].rowPosition).toBe(0);
|
|
258
256
|
expect(positionedBars[1].rowPosition).toBe(1);
|
|
259
257
|
expect(positionedBars[2].rowPosition).toBe(2);
|
|
@@ -310,7 +308,6 @@ describe('GanttChart component', () => {
|
|
|
310
308
|
label: 'Bar Without Color',
|
|
311
309
|
start: new Date(2025, 0, 1),
|
|
312
310
|
end: new Date(2025, 1, 1),
|
|
313
|
-
// color não definido - deve usar fallback
|
|
314
311
|
}]
|
|
315
312
|
}]
|
|
316
313
|
};
|
|
@@ -350,16 +347,13 @@ describe('GanttChart component', () => {
|
|
|
350
347
|
it('should validate invalid data prop structure', () => {
|
|
351
348
|
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
352
349
|
|
|
353
|
-
// Teste com data null
|
|
354
350
|
const validator = wrapper.vm.$options.props.data.validator;
|
|
355
351
|
expect(validator(null)).toBe(false);
|
|
356
352
|
expect(consoleSpy).toHaveBeenCalledWith('GanttChart: prop "data" deve ser um objeto.');
|
|
357
353
|
|
|
358
|
-
// Teste com data sem groups
|
|
359
354
|
expect(validator({})).toBe(false);
|
|
360
355
|
expect(consoleSpy).toHaveBeenCalledWith('GanttChart: prop "data.groups" deve ser um array.');
|
|
361
356
|
|
|
362
|
-
// Teste com groups inválido
|
|
363
357
|
expect(validator({ groups: 'invalid' })).toBe(false);
|
|
364
358
|
expect(consoleSpy).toHaveBeenCalledWith('GanttChart: prop "data.groups" deve ser um array.');
|
|
365
359
|
|
|
@@ -370,7 +364,6 @@ describe('GanttChart component', () => {
|
|
|
370
364
|
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
371
365
|
const validator = wrapper.vm.$options.props.data.validator;
|
|
372
366
|
|
|
373
|
-
// Teste com grupo sem title
|
|
374
367
|
const invalidData = {
|
|
375
368
|
groups: [{
|
|
376
369
|
bars: []
|
|
@@ -379,7 +372,6 @@ describe('GanttChart component', () => {
|
|
|
379
372
|
expect(validator(invalidData)).toBe(false);
|
|
380
373
|
expect(consoleSpy).toHaveBeenCalledWith('GanttChart: cada grupo deve ter título (string) e barras (array).');
|
|
381
374
|
|
|
382
|
-
// Teste com grupo sem bars
|
|
383
375
|
const invalidData2 = {
|
|
384
376
|
groups: [{
|
|
385
377
|
title: 'Valid Title'
|
|
@@ -408,18 +400,15 @@ describe('GanttChart component', () => {
|
|
|
408
400
|
const bar = {
|
|
409
401
|
id: 1,
|
|
410
402
|
label: 'Inverted Date Bar',
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
end: new Date(2025, 2, 15), // 15 de março (antes do início)
|
|
403
|
+
start: new Date(2025, 3, 15),
|
|
404
|
+
end: new Date(2025, 2, 15),
|
|
414
405
|
color: '#7BC4F7',
|
|
415
406
|
};
|
|
416
407
|
|
|
417
|
-
// Normalizar barra via função de posicionamento
|
|
418
408
|
const dates = component.normalizeBarDates(bar);
|
|
419
409
|
|
|
420
|
-
|
|
421
|
-
expect(dates.
|
|
422
|
-
expect(dates.endDate.getTime()).toBe(new Date(2025, 3, 15).getTime()); // Agora é 15 de abril
|
|
410
|
+
expect(dates.startDate.getTime()).toBe(new Date(2025, 2, 15).getTime());
|
|
411
|
+
expect(dates.endDate.getTime()).toBe(new Date(2025, 3, 15).getTime());
|
|
423
412
|
});
|
|
424
413
|
|
|
425
414
|
it('should handle invalid dates', () => {
|
|
@@ -438,17 +427,14 @@ describe('GanttChart component', () => {
|
|
|
438
427
|
|
|
439
428
|
describe('Tooltip System', () => {
|
|
440
429
|
it('should show tooltip on bar mouseenter', async () => {
|
|
441
|
-
// Encontrar uma barra
|
|
442
430
|
const bar = wrapper.find('.farm-gantt-chart__bar');
|
|
443
431
|
|
|
444
432
|
if (bar.exists()) {
|
|
445
|
-
// Disparar evento mouseenter
|
|
446
433
|
await bar.trigger('mouseenter', {
|
|
447
434
|
clientX: 100,
|
|
448
435
|
clientY: 100,
|
|
449
436
|
});
|
|
450
437
|
|
|
451
|
-
// Verificar se tooltipState foi atualizado
|
|
452
438
|
expect(component.tooltipState.visible).toBe(true);
|
|
453
439
|
expect(component.tooltipState.title).toBe('Test Bar');
|
|
454
440
|
expect(component.tooltipState.barData).not.toBeNull();
|
|
@@ -456,24 +442,20 @@ describe('GanttChart component', () => {
|
|
|
456
442
|
});
|
|
457
443
|
|
|
458
444
|
it('should hide tooltip on bar mouseleave', async () => {
|
|
459
|
-
// Preparar estado (mostrar tooltip)
|
|
460
445
|
component.tooltipState.visible = true;
|
|
461
446
|
component.tooltipState.title = 'Teste';
|
|
462
447
|
component.tooltipState.barData = { label: 'Teste' };
|
|
463
448
|
|
|
464
|
-
// Encontrar e disparar mouseleave
|
|
465
449
|
const bar = wrapper.find('.farm-gantt-chart__bar');
|
|
466
450
|
if (bar.exists()) {
|
|
467
451
|
await bar.trigger('mouseleave');
|
|
468
452
|
|
|
469
|
-
// Verificar se tooltipState foi atualizado
|
|
470
453
|
expect(component.tooltipState.visible).toBe(false);
|
|
471
454
|
expect(component.tooltipState.barData).toBeNull();
|
|
472
455
|
}
|
|
473
456
|
});
|
|
474
457
|
|
|
475
458
|
it('should show structured tooltip when tooltipData is available', () => {
|
|
476
|
-
// Criar wrapper com dados para tooltip
|
|
477
459
|
const testData = {
|
|
478
460
|
groups: [{
|
|
479
461
|
title: 'Group with Tooltips',
|
|
@@ -495,7 +477,6 @@ describe('GanttChart component', () => {
|
|
|
495
477
|
propsData: { data: testData },
|
|
496
478
|
});
|
|
497
479
|
|
|
498
|
-
// Simular mouseenter
|
|
499
480
|
const barWithTooltip = tooltipWrapper.find('.farm-gantt-chart__bar');
|
|
500
481
|
if (barWithTooltip.exists()) {
|
|
501
482
|
barWithTooltip.trigger('mouseenter', {
|
|
@@ -503,8 +484,6 @@ describe('GanttChart component', () => {
|
|
|
503
484
|
clientY: 100
|
|
504
485
|
});
|
|
505
486
|
|
|
506
|
-
// Verificar que tooltip com dados estruturados será exibido
|
|
507
|
-
// quando tooltipState.barData.tooltipData existir
|
|
508
487
|
expect(tooltipWrapper.vm.tooltipState.barData.tooltipData).toBeDefined();
|
|
509
488
|
expect(tooltipWrapper.vm.tooltipState.barData.tooltipData.Taxa).toBe('1,75%');
|
|
510
489
|
}
|
|
@@ -518,11 +497,8 @@ describe('GanttChart component', () => {
|
|
|
518
497
|
propsData: { data: emptyData },
|
|
519
498
|
});
|
|
520
499
|
|
|
521
|
-
// Componente deve renderizar sem erros mesmo com dados vazios
|
|
522
|
-
expect(emptyWrapper.exists()).toBe(true);
|
|
523
500
|
expect(emptyWrapper.find('.farm-gantt-chart').exists()).toBe(true);
|
|
524
501
|
|
|
525
|
-
// Não deve haver grupos renderizados
|
|
526
502
|
const groups = emptyWrapper.findAll('.farm-gantt-chart__group');
|
|
527
503
|
expect(groups.length).toBe(0);
|
|
528
504
|
});
|
|
@@ -533,7 +509,6 @@ describe('GanttChart component', () => {
|
|
|
533
509
|
propsData: { data: emptyData },
|
|
534
510
|
});
|
|
535
511
|
|
|
536
|
-
// Timeline headers devem estar presentes (baseado em data range padrão ou calculado)
|
|
537
512
|
const header = emptyWrapper.find('.farm-gantt-chart__header');
|
|
538
513
|
expect(header.exists()).toBe(true);
|
|
539
514
|
});
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { isValid } from 'date-fns';
|
|
2
|
-
import
|
|
2
|
+
import { Ref } from 'vue';
|
|
3
|
+
import type { GanttBar, DateRange, MonthColumn } from '../types';
|
|
3
4
|
import { getColumnForDate, getDaysInMonth } from '../utils/dateUtils';
|
|
4
5
|
|
|
5
|
-
export default function buildBarPositioning(
|
|
6
|
+
export default function buildBarPositioning(
|
|
7
|
+
dateRange: Ref<DateRange>,
|
|
8
|
+
monthColumns: Ref<MonthColumn[]>
|
|
9
|
+
) {
|
|
6
10
|
const normalizeBarDates = (bar: GanttBar) => {
|
|
7
11
|
let startDate = bar.start instanceof Date ? bar.start : new Date(bar.start);
|
|
8
12
|
let endDate = bar.end instanceof Date ? bar.end : new Date(bar.end);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { computed } from 'vue';
|
|
2
|
-
import { isValid, endOfMonth } from 'date-fns';
|
|
2
|
+
import { isValid, endOfMonth, addMonths } from 'date-fns';
|
|
3
3
|
|
|
4
4
|
export default function buildGanttData(props) {
|
|
5
5
|
const autoCalculatedDateRange = computed(() => {
|
|
@@ -23,7 +23,7 @@ export default function buildGanttData(props) {
|
|
|
23
23
|
const now = new Date();
|
|
24
24
|
return {
|
|
25
25
|
start: new Date(now.getFullYear(), 0, 1),
|
|
26
|
-
end: new Date(now.getFullYear(), 11, 31)
|
|
26
|
+
end: endOfMonth(addMonths(new Date(now.getFullYear(), 11, 31), 1))
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -31,7 +31,7 @@ export default function buildGanttData(props) {
|
|
|
31
31
|
const maxDate = new Date(Math.max(...allDates.map(d => d.getTime())));
|
|
32
32
|
|
|
33
33
|
minDate.setDate(1);
|
|
34
|
-
const adjustedMaxDate = endOfMonth(maxDate);
|
|
34
|
+
const adjustedMaxDate = endOfMonth(addMonths(maxDate, 1));
|
|
35
35
|
|
|
36
36
|
return { start: minDate, end: adjustedMaxDate };
|
|
37
37
|
});
|
|
@@ -3,19 +3,20 @@ export interface GanttData {
|
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
export interface GanttGroup {
|
|
6
|
-
title: string;
|
|
6
|
+
title: string;
|
|
7
|
+
subtitle?: string;
|
|
7
8
|
bars: GanttBar[];
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export interface GanttBar {
|
|
11
12
|
id: string | number;
|
|
12
|
-
label: string;
|
|
13
|
-
start: Date | string;
|
|
14
|
-
end: Date | string;
|
|
15
|
-
color: string;
|
|
13
|
+
label: string;
|
|
14
|
+
start: Date | string;
|
|
15
|
+
end: Date | string;
|
|
16
|
+
color: string;
|
|
16
17
|
rowPosition?: number;
|
|
17
18
|
tooltipData?: TooltipData;
|
|
18
|
-
|
|
19
|
+
|
|
19
20
|
[key: string]: any;
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -44,13 +45,10 @@ export interface DateRange {
|
|
|
44
45
|
end: Date;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
// NEW: Tooltip data interface
|
|
49
48
|
export interface TooltipData {
|
|
50
49
|
[key: string]: string | number | null | undefined;
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
// NEW: Enhanced tooltip state
|
|
54
52
|
export interface TooltipState {
|
|
55
53
|
visible: boolean;
|
|
56
54
|
x: number;
|