@farm-investimentos/front-mfe-components 15.12.1 → 15.13.1
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 +4483 -90
- 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 +4483 -90
- 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 +2 -1
- package/src/components/GanttChart/GanttChart.scss +223 -0
- package/src/components/GanttChart/GanttChart.stories.js +480 -0
- package/src/components/GanttChart/GanttChart.vue +308 -0
- package/src/components/GanttChart/__tests__/GanttChart.spec.js +561 -0
- package/src/components/GanttChart/composition/buildBarPositioning.ts +145 -0
- package/src/components/GanttChart/composition/buildGanttData.ts +61 -0
- package/src/components/GanttChart/composition/index.ts +2 -0
- package/src/components/GanttChart/index.ts +4 -0
- package/src/components/GanttChart/types/index.ts +60 -0
- package/src/components/GanttChart/utils/dateUtils.ts +98 -0
- package/src/main.ts +2 -1
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="farm-gantt-chart" :style="componentStyle" ref="chartContainer">
|
|
3
|
+
<div class="farm-gantt-chart__header">
|
|
4
|
+
<div class="farm-gantt-chart__row-label-space"/>
|
|
5
|
+
<div class="farm-gantt-chart__timeline" :style="timelineGridStyle">
|
|
6
|
+
<div
|
|
7
|
+
v-for="(month, index) in monthColumns"
|
|
8
|
+
:key="index"
|
|
9
|
+
class="farm-gantt-chart__month-header"
|
|
10
|
+
:class="{ 'farm-gantt-chart__month-header--current': month.isCurrentMonth }"
|
|
11
|
+
>
|
|
12
|
+
<farm-typography
|
|
13
|
+
size="md"
|
|
14
|
+
:weight="500"
|
|
15
|
+
:color="month.isCurrentMonth ? 'primary' : 'black'"
|
|
16
|
+
:color-variation="month.isCurrentMonth ? '' : '50'"
|
|
17
|
+
class="mb-0"
|
|
18
|
+
>
|
|
19
|
+
{{ month.label }}
|
|
20
|
+
</farm-typography>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="farm-gantt-chart__content">
|
|
26
|
+
<div
|
|
27
|
+
v-for="(group, groupIndex) in (data && data.groups) || []"
|
|
28
|
+
:key="'group-' + groupIndex"
|
|
29
|
+
class="farm-gantt-chart__group"
|
|
30
|
+
>
|
|
31
|
+
<div class="farm-gantt-chart__group-label">
|
|
32
|
+
<farm-typography :weight="500">
|
|
33
|
+
{{ group.title }}
|
|
34
|
+
</farm-typography>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="farm-gantt-chart__group-timeline" :style="timelineGridStyle">
|
|
38
|
+
<div
|
|
39
|
+
v-for="(bar, barIndex) in getPositionedBars(group.bars)"
|
|
40
|
+
:key="'bar-' + barIndex"
|
|
41
|
+
class="farm-gantt-chart__bar"
|
|
42
|
+
:style="getBarGridStyle(bar)"
|
|
43
|
+
@mouseenter="onBarMouseEnter(bar, $event)"
|
|
44
|
+
@mousemove="onBarMouseMove(bar, $event)"
|
|
45
|
+
@mouseleave="onBarMouseLeave"
|
|
46
|
+
>
|
|
47
|
+
<farm-typography size="md" :weight="500" color="white" class="mb-0" ellipsis>
|
|
48
|
+
{{ bar.label }}
|
|
49
|
+
</farm-typography>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="farm-gantt-chart__legend" v-if="autoGeneratedLegend.length > 0" :style="legendStyle">
|
|
57
|
+
<div class="farm-gantt-chart__legend-title">
|
|
58
|
+
<farm-typography size="md" :weight="700" color="black" color-variation="50">
|
|
59
|
+
Legenda:
|
|
60
|
+
</farm-typography>
|
|
61
|
+
</div>
|
|
62
|
+
<div
|
|
63
|
+
v-for="(item, index) in autoGeneratedLegend"
|
|
64
|
+
:key="'legend-' + index"
|
|
65
|
+
class="farm-gantt-chart__legend-item"
|
|
66
|
+
>
|
|
67
|
+
<div
|
|
68
|
+
class="farm-gantt-chart__legend-color"
|
|
69
|
+
:style="{ backgroundColor: item.color }"
|
|
70
|
+
></div>
|
|
71
|
+
<div class="farm-gantt-chart__legend-label">
|
|
72
|
+
<farm-typography size="md" color="black" color-variation="50">
|
|
73
|
+
{{ item.label }}
|
|
74
|
+
</farm-typography>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div
|
|
80
|
+
v-if="tooltipState.visible"
|
|
81
|
+
class="farm-gantt-chart__tooltip-container"
|
|
82
|
+
:style="tooltipPositionStyle"
|
|
83
|
+
>
|
|
84
|
+
<div v-if="$slots.tooltip" class="farm-gantt-chart__tooltip">
|
|
85
|
+
<slot
|
|
86
|
+
name="tooltip"
|
|
87
|
+
:bar="tooltipState.barData"
|
|
88
|
+
:tooltipData="tooltipState.barData && tooltipState.barData.tooltipData"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div
|
|
93
|
+
v-else-if="tooltipState.barData && tooltipState.barData.tooltipData"
|
|
94
|
+
class="farm-gantt-chart__tooltip farm-tooltip__popup farm-tooltip__popup--visible"
|
|
95
|
+
>
|
|
96
|
+
<div class="farm-tooltip__header">
|
|
97
|
+
<div class="farm-tooltip__title">
|
|
98
|
+
{{ tooltipState.title }}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="farm-tooltip__content">
|
|
102
|
+
<div
|
|
103
|
+
v-for="(value, key) in tooltipState.barData.tooltipData"
|
|
104
|
+
:key="key"
|
|
105
|
+
class="tooltip-data-row"
|
|
106
|
+
>
|
|
107
|
+
<span class="tooltip-label">{{ key }}:</span>
|
|
108
|
+
<span class="tooltip-value">{{ value }}</span>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div
|
|
114
|
+
v-else
|
|
115
|
+
class="farm-gantt-chart__tooltip farm-tooltip__popup farm-tooltip__popup--visible"
|
|
116
|
+
>
|
|
117
|
+
<div class="farm-tooltip__header">
|
|
118
|
+
<div class="farm-tooltip__title">
|
|
119
|
+
<strong>{{ tooltipState.title }}</strong>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="farm-tooltip__content">
|
|
123
|
+
<p>{{ tooltipState.barData ? formatDateRange(tooltipState.barData.start, tooltipState.barData.end) : '' }}</p>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</template>
|
|
129
|
+
|
|
130
|
+
<script lang="ts">
|
|
131
|
+
import { defineComponent, PropType, computed, reactive, ref } from 'vue';
|
|
132
|
+
import type { GanttData, TooltipState, GanttBar } from './types';
|
|
133
|
+
import { getMonthsBetween, formatMonth, isCurrentMonth, getColumnForDate, formatDateRange } from './utils/dateUtils';
|
|
134
|
+
import { buildGanttData, buildBarPositioning } from './composition';
|
|
135
|
+
|
|
136
|
+
export default defineComponent({
|
|
137
|
+
name: 'farm-gantt-chart',
|
|
138
|
+
props: {
|
|
139
|
+
data: {
|
|
140
|
+
type: Object as PropType<GanttData>,
|
|
141
|
+
required: true,
|
|
142
|
+
validator: (value: any): boolean => {
|
|
143
|
+
if (!value || typeof value !== 'object') {
|
|
144
|
+
console.warn('GanttChart: prop "data" deve ser um objeto.');
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!Array.isArray(value.groups)) {
|
|
149
|
+
console.warn('GanttChart: prop "data.groups" deve ser um array.');
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return value.groups.every((group: any) => {
|
|
154
|
+
const hasValidTitle = typeof group.title === 'string';
|
|
155
|
+
const hasValidBars = Array.isArray(group.bars);
|
|
156
|
+
|
|
157
|
+
if (!hasValidTitle || !hasValidBars) {
|
|
158
|
+
console.warn('GanttChart: cada grupo deve ter título (string) e barras (array).');
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return true;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
emits: [],
|
|
168
|
+
setup(props) {
|
|
169
|
+
const tooltipState = reactive<TooltipState>({
|
|
170
|
+
visible: false,
|
|
171
|
+
x: 0,
|
|
172
|
+
y: 0,
|
|
173
|
+
title: '',
|
|
174
|
+
barData: null
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const chartContainer = ref<HTMLElement>();
|
|
178
|
+
const { autoCalculatedDateRange, autoGeneratedLegend } = buildGanttData(props);
|
|
179
|
+
|
|
180
|
+
const monthColumns = computed(() => {
|
|
181
|
+
const { start, end } = autoCalculatedDateRange.value;
|
|
182
|
+
const months = getMonthsBetween(start, end);
|
|
183
|
+
return months.map(month => ({
|
|
184
|
+
date: month,
|
|
185
|
+
label: formatMonth(month),
|
|
186
|
+
isCurrentMonth: isCurrentMonth(month),
|
|
187
|
+
}));
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const timelineGridStyle = computed(() => ({
|
|
191
|
+
gridTemplateColumns: `repeat(${monthColumns.value.length}, 80px)`,
|
|
192
|
+
}));
|
|
193
|
+
|
|
194
|
+
const todayColumn = computed(() => {
|
|
195
|
+
const today = new Date();
|
|
196
|
+
const { start } = autoCalculatedDateRange.value;
|
|
197
|
+
const column = getColumnForDate(today, start);
|
|
198
|
+
return column >= 0 && column < monthColumns.value.length ? column : -1;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const { getBarGridStyle, getPositionedBars, normalizeBarDates } = buildBarPositioning(
|
|
202
|
+
autoCalculatedDateRange,
|
|
203
|
+
monthColumns
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const contentHeight = computed(() => {
|
|
207
|
+
let totalHeight = 0;
|
|
208
|
+
props.data.groups.forEach(group => {
|
|
209
|
+
const positionedBars = getPositionedBars(group.bars);
|
|
210
|
+
const maxRows = Math.max(
|
|
211
|
+
1,
|
|
212
|
+
...positionedBars.map(bar => (bar.rowPosition || 0) + 1)
|
|
213
|
+
);
|
|
214
|
+
const groupHeight = Math.max(60, maxRows * 35 + 10);
|
|
215
|
+
totalHeight += groupHeight + 20;
|
|
216
|
+
});
|
|
217
|
+
return totalHeight;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const componentStyle = computed(() => ({
|
|
221
|
+
'--gantt-content-height': `${contentHeight.value}px`,
|
|
222
|
+
}));
|
|
223
|
+
|
|
224
|
+
const legendStyle = computed(() => {
|
|
225
|
+
const timelineWidth = monthColumns.value.length * 80;
|
|
226
|
+
const totalWidth = 120 + timelineWidth;
|
|
227
|
+
return {
|
|
228
|
+
width: `${totalWidth}px`,
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const onBarMouseEnter = (bar: GanttBar, event: MouseEvent) => {
|
|
233
|
+
tooltipState.visible = true;
|
|
234
|
+
tooltipState.title = bar.tooltipTitle || bar.label;
|
|
235
|
+
tooltipState.barData = bar;
|
|
236
|
+
updateTooltipPosition(event);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const onBarMouseMove = (bar: GanttBar, event: MouseEvent) => {
|
|
240
|
+
if (tooltipState.visible) {
|
|
241
|
+
updateTooltipPosition(event);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const onBarMouseLeave = () => {
|
|
246
|
+
tooltipState.visible = false;
|
|
247
|
+
tooltipState.barData = null;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const updateTooltipPosition = (event: MouseEvent) => {
|
|
251
|
+
if (!chartContainer.value) return;
|
|
252
|
+
|
|
253
|
+
const containerRect = chartContainer.value.getBoundingClientRect();
|
|
254
|
+
const x = event.clientX - containerRect.left;
|
|
255
|
+
const y = event.clientY - containerRect.top;
|
|
256
|
+
|
|
257
|
+
const tooltipWidth = 300;
|
|
258
|
+
const tooltipHeight = 100;
|
|
259
|
+
const margin = 15;
|
|
260
|
+
|
|
261
|
+
let tooltipX = x + margin;
|
|
262
|
+
let tooltipY = y + margin;
|
|
263
|
+
|
|
264
|
+
if (tooltipX + tooltipWidth > containerRect.width) {
|
|
265
|
+
tooltipX = Math.max(10, containerRect.width - tooltipWidth - 10);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (tooltipY + tooltipHeight > containerRect.height) {
|
|
269
|
+
tooltipY = Math.max(10, y - tooltipHeight - margin);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
tooltipX = Math.max(10, tooltipX);
|
|
273
|
+
tooltipY = Math.max(10, tooltipY);
|
|
274
|
+
|
|
275
|
+
tooltipState.x = tooltipX;
|
|
276
|
+
tooltipState.y = tooltipY;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const tooltipPositionStyle = computed(() => ({
|
|
280
|
+
transform: `translate(${tooltipState.x}px, ${tooltipState.y}px)`,
|
|
281
|
+
zIndex: 999
|
|
282
|
+
}));
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
monthColumns,
|
|
286
|
+
timelineGridStyle,
|
|
287
|
+
todayColumn,
|
|
288
|
+
autoGeneratedLegend,
|
|
289
|
+
legendStyle,
|
|
290
|
+
getBarGridStyle,
|
|
291
|
+
getPositionedBars,
|
|
292
|
+
normalizeBarDates,
|
|
293
|
+
componentStyle,
|
|
294
|
+
tooltipState,
|
|
295
|
+
tooltipPositionStyle,
|
|
296
|
+
chartContainer,
|
|
297
|
+
onBarMouseEnter,
|
|
298
|
+
onBarMouseMove,
|
|
299
|
+
onBarMouseLeave,
|
|
300
|
+
formatDateRange,
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
</script>
|
|
305
|
+
|
|
306
|
+
<style lang="scss" scoped>
|
|
307
|
+
@import './GanttChart';
|
|
308
|
+
</style>
|