@operato/gantt 2.0.0-beta.14 → 2.0.0-beta.15
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/CHANGELOG.md +19 -0
- package/dist/src/gantt-chart-controller.d.ts +32 -0
- package/dist/src/gantt-chart-controller.js +393 -0
- package/dist/src/gantt-chart-controller.js.map +1 -0
- package/dist/src/locales.d.ts +52 -0
- package/dist/src/locales.js +79 -0
- package/dist/src/locales.js.map +1 -0
- package/dist/src/ox-gantt-chart.d.ts +10 -17
- package/dist/src/ox-gantt-chart.js +134 -440
- package/dist/src/ox-gantt-chart.js.map +1 -1
- package/dist/src/ox-gantt-resize-splitter.d.ts +2 -3
- package/dist/src/ox-gantt-resize-splitter.js +15 -22
- package/dist/src/ox-gantt-resize-splitter.js.map +1 -1
- package/dist/src/ox-gantt.d.ts +13 -1
- package/dist/src/ox-gantt.js +144 -27
- package/dist/src/ox-gantt.js.map +1 -1
- package/dist/src/ox-task-tooltip.d.ts +12 -0
- package/dist/src/ox-task-tooltip.js +132 -0
- package/dist/src/ox-task-tooltip.js.map +1 -0
- package/dist/src/types.d.ts +10 -3
- package/dist/src/types.js.map +1 -1
- package/dist/stories/ox-gantt.stories.js +91 -85
- package/dist/stories/ox-gantt.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/src/gantt-chart-controller.ts +470 -0
- package/src/locales.ts +78 -0
- package/src/ox-gantt-chart.ts +141 -487
- package/src/ox-gantt-resize-splitter.ts +18 -25
- package/src/ox-gantt.ts +151 -29
- package/src/ox-task-tooltip.ts +120 -0
- package/src/types.ts +11 -3
- package/stories/ox-gantt.stories.ts +93 -85
- package/translations/en.json +11 -1
- package/translations/ja.json +11 -1
- package/translations/ko.json +11 -1
- package/translations/ms.json +11 -1
- package/translations/zh.json +11 -1
- package/dist/src/locale.d.ts +0 -1
- package/dist/src/locale.js +0 -49
- package/dist/src/locale.js.map +0 -1
- package/src/locale.ts +0 -51
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { LitElement, css, html } from 'lit'
|
|
2
|
-
|
|
3
2
|
import { customElement } from 'lit/decorators.js'
|
|
4
3
|
|
|
5
4
|
@customElement('ox-gantt-resize-splitter')
|
|
@@ -39,20 +38,7 @@ export class OxGanttResizeSplitter extends LitElement {
|
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
render() {
|
|
42
|
-
return html
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// TODO onDrag 이벤트가 계속 발생하므로 처리하는 성능 저하됨. 그래서 throttling 하도록 함
|
|
46
|
-
_throttled(delay: number, fn: (...args: any) => void) {
|
|
47
|
-
let lastCall = 0
|
|
48
|
-
return function (...args: any) {
|
|
49
|
-
const now = new Date().getTime()
|
|
50
|
-
if (now - lastCall < delay) {
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
lastCall = now
|
|
54
|
-
return fn(...args)
|
|
55
|
-
}
|
|
41
|
+
return html`<div @mousedown=${(e: MouseEvent) => this.onMouseDown(e)}></div>`
|
|
56
42
|
}
|
|
57
43
|
|
|
58
44
|
private onMouseDown(e: MouseEvent) {
|
|
@@ -68,20 +54,33 @@ export class OxGanttResizeSplitter extends LitElement {
|
|
|
68
54
|
})
|
|
69
55
|
)
|
|
70
56
|
|
|
57
|
+
this.parentElement!.addEventListener('mousemove', this.onMouseMoveHandler)
|
|
58
|
+
this.parentElement!.addEventListener('mouseup', this.onMouseUpHandler)
|
|
71
59
|
window.addEventListener('mousemove', this.onMouseMoveHandler)
|
|
72
60
|
window.addEventListener('mouseup', this.onMouseUpHandler)
|
|
73
61
|
|
|
74
62
|
e.stopPropagation()
|
|
75
63
|
}
|
|
76
64
|
|
|
77
|
-
onMouseMoveHandler =
|
|
78
|
-
onMouseUpHandler = this.onMouseUp
|
|
65
|
+
private onMouseMoveHandler = (e: MouseEvent) => this.onMouseMove(e)
|
|
66
|
+
private onMouseUpHandler = (e: MouseEvent) => this.onMouseUp(e)
|
|
79
67
|
|
|
80
68
|
private onMouseMove(e: MouseEvent) {
|
|
81
69
|
if (!this.dragstart) {
|
|
82
70
|
return
|
|
83
71
|
}
|
|
84
72
|
|
|
73
|
+
const parentRect = this.parentElement!.getBoundingClientRect()
|
|
74
|
+
if (
|
|
75
|
+
e.clientX < parentRect.left ||
|
|
76
|
+
e.clientX > parentRect.right ||
|
|
77
|
+
e.clientY < parentRect.top ||
|
|
78
|
+
e.clientY > parentRect.bottom
|
|
79
|
+
) {
|
|
80
|
+
this.onMouseUp(e)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
85
84
|
e.preventDefault()
|
|
86
85
|
|
|
87
86
|
this.dispatchEvent(
|
|
@@ -94,9 +93,6 @@ export class OxGanttResizeSplitter extends LitElement {
|
|
|
94
93
|
}
|
|
95
94
|
})
|
|
96
95
|
)
|
|
97
|
-
requestAnimationFrame(() => {
|
|
98
|
-
dispatchEvent(new Event('resize'))
|
|
99
|
-
})
|
|
100
96
|
|
|
101
97
|
if (this.hasAttribute('vertical')) {
|
|
102
98
|
document.body.style.cursor = 'col-resize'
|
|
@@ -110,6 +106,8 @@ export class OxGanttResizeSplitter extends LitElement {
|
|
|
110
106
|
}
|
|
111
107
|
|
|
112
108
|
private onMouseUp(e: MouseEvent) {
|
|
109
|
+
this.parentElement!.removeEventListener('mousemove', this.onMouseMoveHandler)
|
|
110
|
+
this.parentElement!.removeEventListener('mouseup', this.onMouseUpHandler)
|
|
113
111
|
window.removeEventListener('mousemove', this.onMouseMoveHandler)
|
|
114
112
|
window.removeEventListener('mouseup', this.onMouseUpHandler)
|
|
115
113
|
|
|
@@ -124,11 +122,6 @@ export class OxGanttResizeSplitter extends LitElement {
|
|
|
124
122
|
})
|
|
125
123
|
)
|
|
126
124
|
|
|
127
|
-
requestAnimationFrame(() => {
|
|
128
|
-
dispatchEvent(new Event('resize'))
|
|
129
|
-
delete this.dragstart
|
|
130
|
-
})
|
|
131
|
-
|
|
132
125
|
document.body.style.cursor = ''
|
|
133
126
|
document.body.style.userSelect = ''
|
|
134
127
|
|
package/src/ox-gantt.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { LitElement, html, css, PropertyValues, PropertyValueMap } from 'lit'
|
|
2
|
-
import
|
|
2
|
+
import i18next from 'i18next'
|
|
3
|
+
import { customElement, property, query, state } from 'lit/decorators.js'
|
|
3
4
|
import { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles'
|
|
4
5
|
import { ScrollbarStyles } from '@operato/styles'
|
|
5
6
|
|
|
6
7
|
import './ox-gantt-chart.js'
|
|
7
8
|
import './ox-gantt-resize-splitter.js'
|
|
8
9
|
import { OxGanttChart } from './ox-gantt-chart.js'
|
|
9
|
-
import { Task } from './types.js'
|
|
10
|
+
import { ColumnConfig, Task } from './types.js'
|
|
10
11
|
|
|
11
12
|
@customElement('ox-gantt')
|
|
12
13
|
export class OxGantt extends LitElement {
|
|
@@ -18,6 +19,7 @@ export class OxGantt extends LitElement {
|
|
|
18
19
|
display: block;
|
|
19
20
|
font-family: Arial, sans-serif;
|
|
20
21
|
text-wrap: nowrap;
|
|
22
|
+
position: relative;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
.container {
|
|
@@ -44,6 +46,7 @@ export class OxGantt extends LitElement {
|
|
|
44
46
|
|
|
45
47
|
ox-gantt-resize-splitter {
|
|
46
48
|
width: 4px;
|
|
49
|
+
cursor: ew-resize;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
table {
|
|
@@ -59,6 +62,7 @@ export class OxGantt extends LitElement {
|
|
|
59
62
|
top: -1px;
|
|
60
63
|
z-index: 1;
|
|
61
64
|
background-color: var(--ox-gantt-header-background-color, var(--md-sys-color-secondary-container));
|
|
65
|
+
text-transform: capitalize;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
th,
|
|
@@ -77,6 +81,10 @@ export class OxGantt extends LitElement {
|
|
|
77
81
|
height: 30px;
|
|
78
82
|
box-sizing: border-box;
|
|
79
83
|
}
|
|
84
|
+
|
|
85
|
+
tr.selected {
|
|
86
|
+
background-color: var(--ox-gantt-selected-background-color, var(--md-sys-color-surface-variant));
|
|
87
|
+
}
|
|
80
88
|
`
|
|
81
89
|
]
|
|
82
90
|
|
|
@@ -87,6 +95,50 @@ export class OxGantt extends LitElement {
|
|
|
87
95
|
toDate: string = new Date(new Date().getFullYear(), 11, 31).toISOString().split('T')[0] /* end of this year */
|
|
88
96
|
@property({ type: Boolean, attribute: 'extend-grid-lines' }) extendGridLines: boolean = false
|
|
89
97
|
|
|
98
|
+
@state() selectedTaskId?: string
|
|
99
|
+
@state()
|
|
100
|
+
private columnConfig: ColumnConfig[] = [
|
|
101
|
+
{ id: 'id', label: i18next.t('label.gantt-task-id') || 'ID', visible: true, width: '50px', order: 1 },
|
|
102
|
+
{
|
|
103
|
+
id: 'section',
|
|
104
|
+
label: i18next.t('label.gantt-task-section') || 'section',
|
|
105
|
+
visible: true,
|
|
106
|
+
width: '100px',
|
|
107
|
+
order: 2
|
|
108
|
+
},
|
|
109
|
+
{ id: 'title', label: i18next.t('label.gantt-task-title') || 'title', visible: true, width: '150px', order: 3 },
|
|
110
|
+
{ id: 'type', label: i18next.t('label.gantt-task-type') || 'type', visible: true, width: '100px', order: 4 },
|
|
111
|
+
{
|
|
112
|
+
id: 'startDate',
|
|
113
|
+
label: i18next.t('label.gantt-task-start-date') || 'start date',
|
|
114
|
+
visible: true,
|
|
115
|
+
width: '100px',
|
|
116
|
+
order: 5
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'endDate',
|
|
120
|
+
label: i18next.t('label.gantt-task-end-date') || 'end date',
|
|
121
|
+
visible: true,
|
|
122
|
+
width: '100px',
|
|
123
|
+
order: 6
|
|
124
|
+
},
|
|
125
|
+
{ id: 'tags', label: i18next.t('label.gantt-task-tags') || 'tags', visible: true, width: '100px', order: 7 },
|
|
126
|
+
{
|
|
127
|
+
id: 'progress',
|
|
128
|
+
label: i18next.t('label.gantt-task-progress') || 'progress',
|
|
129
|
+
visible: true,
|
|
130
|
+
width: '100px',
|
|
131
|
+
order: 8
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'dependsOn',
|
|
135
|
+
label: i18next.t('label.gantt-task-previous') || 'previous',
|
|
136
|
+
visible: true,
|
|
137
|
+
width: '100px',
|
|
138
|
+
order: 9
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
|
|
90
142
|
@query('.container') container!: HTMLDivElement
|
|
91
143
|
@query('.task-container') taskContainer!: HTMLDivElement
|
|
92
144
|
@query('.chart-container') chartContainer!: HTMLDivElement
|
|
@@ -94,6 +146,7 @@ export class OxGantt extends LitElement {
|
|
|
94
146
|
@query('ox-gantt-chart') ganttChart!: OxGanttChart
|
|
95
147
|
|
|
96
148
|
private _startWidth: number = 0
|
|
149
|
+
private _isDragging: boolean = false
|
|
97
150
|
|
|
98
151
|
render() {
|
|
99
152
|
return html`
|
|
@@ -103,6 +156,7 @@ export class OxGantt extends LitElement {
|
|
|
103
156
|
vertical
|
|
104
157
|
@splitter-dragstart=${(e: CustomEvent) => this.resizeStart(e)}
|
|
105
158
|
@splitter-drag=${(e: CustomEvent) => this.resizeDrag(e)}
|
|
159
|
+
@splitter-dragend=${(e: CustomEvent) => this.resizeEnd(e)}
|
|
106
160
|
></ox-gantt-resize-splitter>
|
|
107
161
|
<ox-gantt-chart
|
|
108
162
|
class="chart-container"
|
|
@@ -110,42 +164,42 @@ export class OxGantt extends LitElement {
|
|
|
110
164
|
.fromDate=${this.fromDate}
|
|
111
165
|
.toDate=${this.toDate}
|
|
112
166
|
.tasks=${this.tasks}
|
|
167
|
+
.selectedTaskId=${this.selectedTaskId}
|
|
113
168
|
axis-scale="week-day"
|
|
114
169
|
?extend-grid-lines=${this.extendGridLines}
|
|
170
|
+
@task-clicked=${this.handleTaskClicked}
|
|
115
171
|
></ox-gantt-chart>
|
|
116
172
|
</div>
|
|
117
173
|
`
|
|
118
174
|
}
|
|
119
175
|
|
|
120
176
|
renderTaskTable() {
|
|
177
|
+
const mergedTasks = this.mergeTasksBySection(this.tasks)
|
|
178
|
+
|
|
179
|
+
// Sort columns by the configured order
|
|
180
|
+
const sortedColumns = [...this.columnConfig].sort((a, b) => a.order - b.order)
|
|
181
|
+
|
|
121
182
|
return html`
|
|
122
183
|
<table>
|
|
123
184
|
<thead>
|
|
124
185
|
<tr>
|
|
125
|
-
|
|
126
|
-
<th>Section</th>
|
|
127
|
-
<th>Title</th>
|
|
128
|
-
<th>Type</th>
|
|
129
|
-
<th>Start Time</th>
|
|
130
|
-
<th>End Time</th>
|
|
131
|
-
<th>Tags</th>
|
|
132
|
-
<th>Progress</th>
|
|
133
|
-
<th>Prev. Task</th>
|
|
186
|
+
${sortedColumns.map(col => (col.visible ? html`<th style="width: ${col.width}">${col.label}</th>` : null))}
|
|
134
187
|
</tr>
|
|
135
188
|
</thead>
|
|
136
189
|
<tbody>
|
|
137
|
-
${
|
|
138
|
-
task => html`
|
|
139
|
-
<tr>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
190
|
+
${mergedTasks.map(
|
|
191
|
+
({ task, showSection, rowSpan }) => html`
|
|
192
|
+
<tr class="${this.selectedTaskId === task.id ? 'selected' : ''}" @click=${() => this.selectTask(task.id)}>
|
|
193
|
+
${sortedColumns.map(col => {
|
|
194
|
+
if (col.id === 'section') {
|
|
195
|
+
return col.visible && showSection
|
|
196
|
+
? html`<td rowspan="${rowSpan}" style="width: ${col.width}">${task.section?.name || ''}</td>`
|
|
197
|
+
: null
|
|
198
|
+
}
|
|
199
|
+
return col.visible
|
|
200
|
+
? html`<td style="width: ${col.width}">${this.getTaskFieldValue(task, col.id)}</td>`
|
|
201
|
+
: null
|
|
202
|
+
})}
|
|
149
203
|
</tr>
|
|
150
204
|
`
|
|
151
205
|
)}
|
|
@@ -154,25 +208,93 @@ export class OxGantt extends LitElement {
|
|
|
154
208
|
`
|
|
155
209
|
}
|
|
156
210
|
|
|
211
|
+
getTaskFieldValue(task: Task, field: string) {
|
|
212
|
+
switch (field) {
|
|
213
|
+
case 'id':
|
|
214
|
+
return task.id
|
|
215
|
+
case 'section':
|
|
216
|
+
return task.section?.name || ''
|
|
217
|
+
case 'title':
|
|
218
|
+
return task.title
|
|
219
|
+
case 'type':
|
|
220
|
+
return task.type
|
|
221
|
+
case 'startDate':
|
|
222
|
+
return task.startDate.toISOString().split('T')[0]
|
|
223
|
+
case 'endDate':
|
|
224
|
+
return task.endDate.toISOString().split('T')[0]
|
|
225
|
+
case 'tags':
|
|
226
|
+
return task.tags?.join(', ') || ''
|
|
227
|
+
case 'progress':
|
|
228
|
+
return task.progress || ''
|
|
229
|
+
case 'dependsOn':
|
|
230
|
+
return task.dependsOn || ''
|
|
231
|
+
default:
|
|
232
|
+
return ''
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
mergeTasksBySection(tasks: Task[]): Array<{ task: Task; showSection: boolean; rowSpan: number }> {
|
|
237
|
+
const mergedTasks = [] as Array<{ task: Task; showSection: boolean; rowSpan: number }>
|
|
238
|
+
let currentSection = ''
|
|
239
|
+
let rowSpan = 0
|
|
240
|
+
|
|
241
|
+
tasks.forEach((task, index) => {
|
|
242
|
+
if (task.section?.name !== currentSection) {
|
|
243
|
+
currentSection = task.section?.name || ''
|
|
244
|
+
rowSpan = 1
|
|
245
|
+
mergedTasks.push({ task, showSection: true, rowSpan: 1 })
|
|
246
|
+
} else {
|
|
247
|
+
mergedTasks[mergedTasks.length - rowSpan].rowSpan = ++rowSpan
|
|
248
|
+
mergedTasks.push({ task, showSection: false, rowSpan: 0 })
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (index === tasks.length - 1 && rowSpan > 0) {
|
|
252
|
+
mergedTasks[mergedTasks.length - rowSpan].rowSpan = rowSpan
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
return mergedTasks
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
selectTask(taskId: string) {
|
|
260
|
+
this.selectedTaskId = taskId
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
handleTaskClicked(event: CustomEvent) {
|
|
264
|
+
this.selectedTaskId = event.detail.taskId
|
|
265
|
+
}
|
|
266
|
+
|
|
157
267
|
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
|
158
268
|
this.taskContainer.style.width = '254px'
|
|
159
269
|
}
|
|
160
270
|
|
|
161
271
|
updated(changes: PropertyValues<this>) {}
|
|
162
272
|
|
|
163
|
-
syncScroll() {
|
|
164
|
-
|
|
273
|
+
syncScroll(e: Event) {
|
|
274
|
+
const target = e.target as HTMLElement
|
|
275
|
+
if (target === this.taskContainer) {
|
|
276
|
+
this.ganttChart.setVerticalOffset(this.taskContainer.scrollTop)
|
|
277
|
+
} else if (target === this.ganttChart) {
|
|
278
|
+
this.taskContainer.scrollTop = this.ganttChart.getVerticalOffset()
|
|
279
|
+
}
|
|
165
280
|
}
|
|
166
281
|
|
|
167
282
|
resizeStart(e: CustomEvent) {
|
|
168
283
|
this._startWidth = ((e.target as HTMLElement)?.previousElementSibling as HTMLElement)?.offsetWidth
|
|
284
|
+
this._isDragging = true
|
|
169
285
|
}
|
|
170
286
|
|
|
171
287
|
resizeDrag(e: CustomEvent) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
288
|
+
if (!this._isDragging) return
|
|
289
|
+
const delta = e.detail
|
|
290
|
+
const taskContainer = this.taskContainer
|
|
291
|
+
|
|
292
|
+
if (taskContainer) {
|
|
293
|
+
taskContainer.style.width = `${this._startWidth + delta.x}px`
|
|
176
294
|
}
|
|
177
295
|
}
|
|
296
|
+
|
|
297
|
+
resizeEnd(e: CustomEvent) {
|
|
298
|
+
this._isDragging = false
|
|
299
|
+
}
|
|
178
300
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit'
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
3
|
+
|
|
4
|
+
@customElement('ox-task-tooltip')
|
|
5
|
+
export class OxTaskTooltip extends LitElement {
|
|
6
|
+
static styles = css`
|
|
7
|
+
:host {
|
|
8
|
+
position: fixed; /* position: absolute로 변경 */
|
|
9
|
+
background-color: var(--ox-gantt-tooltip-background-color, var(--md-sys-color-surface, #f9f9f9));
|
|
10
|
+
border: 1px solid var(--ox-gantt-tooltip-border-color, var(--md-sys-color-outline, #ccc));
|
|
11
|
+
padding: var(--spacing-medium, 10px);
|
|
12
|
+
border-radius: var(--spacing-medium, 8px);
|
|
13
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
14
|
+
visibility: hidden;
|
|
15
|
+
z-index: 10;
|
|
16
|
+
max-width: 300px;
|
|
17
|
+
font-family: Roboto;
|
|
18
|
+
font-size: 14px;
|
|
19
|
+
line-height: 1.6;
|
|
20
|
+
color: var(--ox-gantt-tooltip-color, var(--md-sys-color-on-surface, #333));
|
|
21
|
+
transition:
|
|
22
|
+
visibility 0.1s,
|
|
23
|
+
opacity 0.1s;
|
|
24
|
+
opacity: 0; /* 초기에는 보이지 않음 */
|
|
25
|
+
}
|
|
26
|
+
:host([visible]) {
|
|
27
|
+
visibility: visible;
|
|
28
|
+
opacity: 1; /* 보일 때는 불투명 */
|
|
29
|
+
}
|
|
30
|
+
.title {
|
|
31
|
+
font-weight: bold;
|
|
32
|
+
font-size: 16px;
|
|
33
|
+
margin-bottom: 8px;
|
|
34
|
+
color: var(--ox-gantt-tooltip-title-color, var(--md-sys-color-primary, #0078d4)); /* 파란색 텍스트 */
|
|
35
|
+
}
|
|
36
|
+
.detail {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: auto auto 1fr;
|
|
39
|
+
gap: 4px;
|
|
40
|
+
align-items: center;
|
|
41
|
+
}
|
|
42
|
+
.label {
|
|
43
|
+
font-weight: bold;
|
|
44
|
+
color: var(--ox-gantt-tooltip-color, var(--md-sys-color-on-surface-variant, #333));
|
|
45
|
+
text-align: right; /* 라벨을 우측 정렬 */
|
|
46
|
+
}
|
|
47
|
+
.colon {
|
|
48
|
+
text-align: center;
|
|
49
|
+
color: var(--ox-gantt-tooltip-color, var(--md-sys-color-on-surface, #333));
|
|
50
|
+
}
|
|
51
|
+
.value {
|
|
52
|
+
text-align: left; /* 값을 좌측 정렬 */
|
|
53
|
+
color: var(--ox-gantt-tooltip-color, var(--md-sys-color-on-surface, #333));
|
|
54
|
+
}
|
|
55
|
+
`
|
|
56
|
+
|
|
57
|
+
@property({ type: String }) title = ''
|
|
58
|
+
@property({ type: String }) startDate = ''
|
|
59
|
+
@property({ type: String }) endDate = ''
|
|
60
|
+
@property({ type: Number }) progress = 0
|
|
61
|
+
@property({ type: String }) type = ''
|
|
62
|
+
|
|
63
|
+
show(x: number, y: number) {
|
|
64
|
+
const tooltipWidth = this.offsetWidth
|
|
65
|
+
const tooltipHeight = this.offsetHeight
|
|
66
|
+
const offsetX = 10
|
|
67
|
+
const offsetY = 10
|
|
68
|
+
|
|
69
|
+
// 기본 위치 계산
|
|
70
|
+
let left = x + offsetX
|
|
71
|
+
let top = y + offsetY
|
|
72
|
+
|
|
73
|
+
// 화면 경계 확인 및 조정
|
|
74
|
+
const screenWidth = window.innerWidth
|
|
75
|
+
const screenHeight = window.innerHeight
|
|
76
|
+
|
|
77
|
+
if (left + tooltipWidth > screenWidth) {
|
|
78
|
+
left = x - tooltipWidth - offsetX
|
|
79
|
+
}
|
|
80
|
+
if (top + tooltipHeight > screenHeight) {
|
|
81
|
+
top = y - tooltipHeight - offsetY
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// offsetParent 요소를 기준으로 위치 설정
|
|
85
|
+
if (this.offsetParent) {
|
|
86
|
+
const parentRect = this.offsetParent!.getBoundingClientRect()
|
|
87
|
+
this.style.left = `${left - parentRect.left}px`
|
|
88
|
+
this.style.top = `${top - parentRect.top}px`
|
|
89
|
+
} else {
|
|
90
|
+
this.style.left = `${left}px`
|
|
91
|
+
this.style.top = `${top}px`
|
|
92
|
+
|
|
93
|
+
const myRect = this.getBoundingClientRect()
|
|
94
|
+
|
|
95
|
+
/* 실제 바운드와 설정한 left/top 이 일치하지 않는다면, 부모 어딘가에서 transform 되었다고 판단하고, 보정해줌 */
|
|
96
|
+
if (myRect.left !== left || myRect.top !== top) {
|
|
97
|
+
this.style.left = `${left + (left - myRect.left)}px`
|
|
98
|
+
this.style.top = `${top + (top - myRect.top)}px`
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.setAttribute('visible', '')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
hide() {
|
|
106
|
+
this.removeAttribute('visible')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
render() {
|
|
110
|
+
return html`
|
|
111
|
+
<div class="title">${this.title}</div>
|
|
112
|
+
<div class="detail">
|
|
113
|
+
<span class="label">Type</span><span class="colon">:</span><span class="value">${this.type}</span>
|
|
114
|
+
<span class="label">Start Date</span><span class="colon">:</span><span class="value">${this.startDate}</span>
|
|
115
|
+
<span class="label">End Date</span><span class="colon">:</span><span class="value">${this.endDate}</span>
|
|
116
|
+
<span class="label">Progress</span><span class="colon">:</span><span class="value">${this.progress}%</span>
|
|
117
|
+
</div>
|
|
118
|
+
`
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -6,10 +6,10 @@ export interface Task {
|
|
|
6
6
|
title: string
|
|
7
7
|
type: TaskType
|
|
8
8
|
section?: Section
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
startDate: Date
|
|
10
|
+
endDate: Date
|
|
11
11
|
progress?: number
|
|
12
|
-
|
|
12
|
+
dependsOn?: string // 이전 태스크의 ID
|
|
13
13
|
length: string // 예: '3d' (3 days)
|
|
14
14
|
tags?: TaskTag[]
|
|
15
15
|
children?: Task[]
|
|
@@ -19,4 +19,12 @@ export interface Section {
|
|
|
19
19
|
name: string
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export interface ColumnConfig {
|
|
23
|
+
id: string
|
|
24
|
+
label: string
|
|
25
|
+
visible: boolean
|
|
26
|
+
width: string
|
|
27
|
+
order: number
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
export type LEVEL = 'year-quarter' | 'year-month' | 'quarter-week' | 'week-day'
|