Skip to content

Commit 40ccdf2

Browse files
committed
Merge branch 'feature/milestone' into develop
2 parents 57a6800 + bdb4555 commit 40ccdf2

File tree

14 files changed

+459
-89
lines changed

14 files changed

+459
-89
lines changed

docs/.vitepress/theme/components/OtherGanttDemo.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,6 @@ onMounted(() => {
247247
:enable-connections="true"
248248
grid
249249
label-column-title="Rows"
250-
251250
>
252251
<g-gantt-row
253252
v-for="row in rows"

docs/api/types.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ interface BarConnection {
5454
animated?: boolean;
5555
animationSpeed?: ConnectionSpeed;
5656
}
57+
58+
export interface GanttMilestone {
59+
date: string
60+
name: string
61+
description?: string
62+
color?: string
63+
}
5764
```
5865
## Label Data Types
5966

docs/components/g-gantt-chart.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Here's a minimal example of using the GGanttChart component:
6464
| currentTime | `boolean` | `false` | Show current time indicator |
6565
| currentTimeLabel | `string` | `''` | Label for current time indicator |
6666
| dateFormat | `string \| false` | `'YYYY-MM-DD HH:mm'` | Format for dates |
67-
67+
| milestones | `GanttMilestone[]` | `[]` | List of milestone |
6868

6969
### Events
7070

docs/examples/advanced.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,85 @@ const rows = ref([
121121
color: #666;
122122
}
123123
</style>
124+
```
125+
126+
## Multi Label Columns
127+
128+
```vue
129+
<template>
130+
<g-gantt-chart
131+
v-bind="chartConfig"
132+
:push-on-overlap="true"
133+
:no-overlap="true"
134+
label-column-title="Project Details"
135+
:multi-column-label="multiColumnLabel"
136+
>
137+
<g-gantt-row
138+
v-for="row in rows"
139+
:key="row.label"
140+
:label="row.label"
141+
:bars="row.bars"
142+
/>
143+
</g-gantt-chart>
144+
</template>
145+
146+
<script setup lang="ts">
147+
const rows = ref([
148+
{
149+
label: 'Constrained Tasks',
150+
bars: [
151+
{
152+
start: '2024-01-01',
153+
end: '2024-01-15',
154+
ganttBarConfig: {
155+
id: '1',
156+
label: 'Immobile Task',
157+
immobile: true
158+
}
159+
},
160+
{
161+
start: '2024-01-16',
162+
end: '2024-01-30',
163+
ganttBarConfig: {
164+
id: '2',
165+
label: 'Movable Task',
166+
pushOnOverlap: true
167+
}
168+
}
169+
]
170+
}
171+
])
172+
173+
const getN = (row: ChartRow) => {
174+
return row.bars.length
175+
}
176+
177+
const sortN = (a: ChartRow, b: ChartRow) => {
178+
const aId = a.bars.length ?? 0
179+
const bId = b.bars.length ?? 0
180+
return aId < bId ? -1 : aId > bId ? 1 : 0
181+
}
182+
183+
184+
const multiColumnLabel = ref<LabelColumnConfig[]>([
185+
{
186+
field: 'Id',
187+
sortable: false,
188+
},
189+
{
190+
field: 'Label',
191+
},
192+
{
193+
field: 'StartDate',
194+
},
195+
{
196+
field: 'Duration',
197+
},
198+
{
199+
field: 'Bars N°',
200+
valueGetter: getN,
201+
sortFn: sortN,
202+
},
203+
])
204+
</script>
124205
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hy-vue-gantt",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"description": "Evolution of vue-ganttastic package",
55
"author": "Eugenio Topa (@Xeyos88)",
66
"scripts": {

src/components/GGanttChart.vue

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import GGanttBarTooltip from "./GGanttBarTooltip.vue"
3434
import GGanttCurrentTime from "./GGanttCurrentTime.vue"
3535
import GGanttConnector from "./GGanttConnector.vue"
3636
import GGanttRow from "./GGanttRow.vue"
37+
import GGanttMilestone from "./GGanttMilestone.vue"
3738
3839
// Internal Imports - Composables
3940
import { useConnections } from "../composables/useConnections"
@@ -46,7 +47,12 @@ import { useRows } from "../composables/useRows"
4647
import { colorSchemes, type ColorScheme, type ColorSchemeKey } from "../color-schemes"
4748
import { DEFAULT_DATE_FORMAT } from "../composables/useDayjsHelper"
4849
import { BOOLEAN_KEY, CONFIG_KEY, EMIT_BAR_EVENT_KEY } from "../provider/symbols"
49-
import type { GanttBarObject, GGanttChartProps, SortDirection } from "../types"
50+
import type {
51+
GanttBarObject,
52+
GGanttChartProps,
53+
SortDirection,
54+
GGanttTimeaxisInstance
55+
} from "../types"
5056
import type { CSSProperties } from "vue"
5157
5258
// Props & Emits Definition
@@ -65,7 +71,7 @@ const props = withDefaults(defineProps<GGanttChartProps>(), {
6571
highlightedUnits: () => [],
6672
font: "inherit",
6773
labelColumnTitle: "",
68-
labelColumnWidth: 150,
74+
labelColumnWidth: 120,
6975
commands: true,
7076
enableMinutes: false,
7177
enableConnections: true,
@@ -79,7 +85,8 @@ const props = withDefaults(defineProps<GGanttChartProps>(), {
7985
initialRows: () => [],
8086
multiColumnLabel: () => [],
8187
sortable: true,
82-
labelResizable: true
88+
labelResizable: true,
89+
milestones: () => []
8390
})
8491
8592
const id = ref(crypto.randomUUID())
@@ -144,14 +151,23 @@ const emit = defineEmits<{
144151
const chartStartDayjs = computed(() => dayjs(props.chartStart, props.dateFormat as string, true))
145152
const chartEndDayjs = computed(() => dayjs(props.chartEnd, props.dateFormat as string, true))
146153
147-
const diffDays = computed(() => chartEndDayjs.value.diff(chartStartDayjs.value, "day") + 1)
154+
//const diffDays = computed(() => chartEndDayjs.value.diff(chartStartDayjs.value, "day") + 1)
148155
const diffHours = computed(() => chartEndDayjs.value.diff(chartStartDayjs.value, "hour"))
156+
const diffPrecision = computed(
157+
() =>
158+
chartEndDayjs.value.diff(
159+
chartStartDayjs.value,
160+
props.precision === "hour" ? "day" : props.precision
161+
) + 1
162+
)
149163
150164
// Chart Elements Refs
151165
const ganttChart = ref<HTMLElement | null>(null)
152166
const chartSize = useElementSize(ganttChart)
153167
const ganttWrapper = ref<HTMLElement | null>(null)
154-
const timeaxisComponent = ref<InstanceType<typeof GGanttTimeaxis> | null>(null)
168+
const timeaxisComponent = ref<
169+
(InstanceType<typeof GGanttTimeaxis> & GGanttTimeaxisInstance) | null
170+
>(null)
155171
const ganttContainer = ref<HTMLElement | null>(null)
156172
157173
// Composables
@@ -181,7 +197,7 @@ const {
181197
isAtBottom
182198
} = useChartNavigation(
183199
{
184-
diffDays: diffDays.value,
200+
diffDays: diffPrecision.value,
185201
diffHours: diffHours.value,
186202
scrollRefs: {
187203
rowsContainer,
@@ -208,7 +224,7 @@ const navigationControls = {
208224
209225
const { handleKeyDown } = useKeyboardNavigation(navigationControls, ganttWrapper, ganttContainer)
210226
211-
// Derived State
227+
// Computed property per timeaxisUnits
212228
const widthNumber = computed(() => zoomFactor.value * 100)
213229
const customWidth = computed(() => `${widthNumber.value}%`)
214230
@@ -426,6 +442,12 @@ provide("id", id)
426442
</template>
427443
</g-gantt-current-time>
428444

445+
<g-gantt-milestone
446+
v-for="milestone in milestones"
447+
:key="milestone.date.toString()"
448+
:milestone="milestone"
449+
/>
450+
429451
<!-- Rows Container -->
430452
<div
431453
class="g-gantt-rows-container"

src/components/GGanttLabelColumn.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ const getRowValue = (row: ChartRow, column: LabelColumnConfig, index: number) =>
158158
const labelContainer = ref<HTMLElement | null>(null)
159159
160160
const labelContainerStyle = computed<CSSProperties>(() => {
161-
console.log(maxRows.value, rows.value.length)
162161
if (maxRows.value === 0) return {}
163162
const minRows = Math.min(maxRows.value, rows.value.length)
164163

src/components/GGanttMilestone.vue

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<script setup lang="ts">
2+
import { ref, computed } from "vue"
3+
import useTimePositionMapping from "../composables/useTimePositionMapping"
4+
import provideConfig from "../provider/provideConfig"
5+
import dayjs from "dayjs"
6+
import type { GanttMilestone } from "../types"
7+
8+
const props = defineProps<{
9+
milestone: GanttMilestone
10+
}>()
11+
12+
const { mapTimeToPosition } = useTimePositionMapping()
13+
const { colors } = provideConfig()
14+
15+
const showTooltip = ref(false)
16+
const tooltipPosition = ref({ x: 0, y: 0 })
17+
18+
const milestoneDate = computed(() => {
19+
const date = dayjs(props.milestone.date)
20+
if (!date.hour() && !date.minute()) {
21+
return date.hour(12).minute(0).format("YYYY-MM-DD HH:mm")
22+
}
23+
return props.milestone.date
24+
})
25+
26+
const xPosition = computed(() => {
27+
return mapTimeToPosition(milestoneDate.value)
28+
})
29+
30+
const handleMouseEnter = (event: MouseEvent) => {
31+
const element = event.target as HTMLElement
32+
const rect = element.getBoundingClientRect()
33+
tooltipPosition.value = {
34+
x: rect.left,
35+
y: rect.top + 10
36+
}
37+
showTooltip.value = true
38+
}
39+
40+
const handleMouseLeave = () => {
41+
showTooltip.value = false
42+
}
43+
44+
const styleConfig = computed(() => {
45+
if (props.milestone.color) {
46+
return {
47+
label: {
48+
background: props.milestone.color,
49+
color: "#000",
50+
border: `2px solid ${props.milestone.color}`
51+
},
52+
marker: {
53+
borderLeft: `2px solid ${props.milestone.color}`
54+
}
55+
}
56+
}
57+
58+
return {
59+
label: {
60+
background: colors.value.primary,
61+
color: colors.value.text,
62+
border: "none"
63+
},
64+
marker: {
65+
borderLeft: `2px solid ${colors.value.markerCurrentTime}`
66+
}
67+
}
68+
})
69+
</script>
70+
71+
<template>
72+
<div
73+
class="g-gantt-milestone"
74+
:style="{
75+
left: `${xPosition}px`
76+
}"
77+
@mouseenter="handleMouseEnter"
78+
@mouseleave="handleMouseLeave"
79+
>
80+
<div class="g-gantt-milestone-label" :style="styleConfig.label">
81+
{{ milestone.name }}
82+
</div>
83+
84+
<div class="g-gantt-milestone-marker" :style="styleConfig.marker" />
85+
86+
<teleport to="body">
87+
<div
88+
v-if="showTooltip"
89+
class="g-gantt-milestone-tooltip"
90+
:style="{
91+
top: `${tooltipPosition.y}px`,
92+
left: `${tooltipPosition.x}px`,
93+
background: colors.primary,
94+
color: colors.text
95+
}"
96+
>
97+
<div class="g-gantt-milestone-tooltip-title">{{ milestone.name }}</div>
98+
<div class="g-gantt-milestone-tooltip-date">{{ milestone.date }}</div>
99+
<div class="g-gantt-milestone-tooltip-description">{{ milestone.description }}</div>
100+
</div>
101+
</teleport>
102+
</div>
103+
</template>
104+
105+
<style>
106+
.g-gantt-milestone {
107+
position: absolute;
108+
height: 100%;
109+
display: flex;
110+
z-index: 5;
111+
pointer-events: auto;
112+
flex-direction: column;
113+
align-items: center;
114+
}
115+
116+
.g-gantt-milestone-marker {
117+
width: 0px;
118+
height: calc(100% - 30px);
119+
display: flex;
120+
margin-top: 25px;
121+
}
122+
123+
.g-gantt-milestone-label {
124+
padding: 2px 8px;
125+
border-radius: 4px;
126+
font-size: 0.8em;
127+
white-space: nowrap;
128+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
129+
position: absolute;
130+
top: 4px;
131+
transform: translateY(0);
132+
}
133+
134+
.g-gantt-milestone-tooltip {
135+
position: fixed;
136+
padding: 8px;
137+
border-radius: 4px;
138+
font-size: 0.75em;
139+
z-index: 1000;
140+
min-width: 200px;
141+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
142+
transform: translateY(-100%);
143+
}
144+
145+
.g-gantt-milestone-tooltip-title {
146+
font-weight: bold;
147+
}
148+
149+
.g-gantt-milestone-tooltip-date {
150+
font-size: 0.9em;
151+
opacity: 0.8;
152+
}
153+
154+
.g-gantt-milestone-tooltip-description {
155+
font-size: 0.9em;
156+
line-height: 1.4;
157+
}
158+
</style>

src/components/GGanttRow.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ provide(BAR_CONTAINER_KEY, barContainer)
3636
const onDrop = (e: MouseEvent) => {
3737
const container = barContainer.value?.getBoundingClientRect()
3838
if (!container) {
39-
console.error("Vue-Ganttastic: failed to find bar container element for row.")
39+
console.error("Hyper Vue Gantt: failed to find bar container element for row.")
4040
return
4141
}
4242
const xPos = e.clientX - container.left

0 commit comments

Comments
 (0)