@operato/gantt 2.0.0-beta.13 → 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 +28 -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 +135 -435
- 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 +14 -1
- package/dist/src/ox-gantt.js +149 -28
- 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.d.ts +12 -0
- package/dist/stories/ox-gantt.stories.js +100 -88
- 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 +143 -481
- package/src/ox-gantt-resize-splitter.ts +18 -25
- package/src/ox-gantt.ts +153 -30
- package/src/ox-task-tooltip.ts +120 -0
- package/src/types.ts +11 -3
- package/stories/ox-gantt.stories.ts +105 -88
- 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
|
|
|
@@ -85,6 +93,51 @@ export class OxGantt extends LitElement {
|
|
|
85
93
|
fromDate: string = new Date(new Date().getFullYear(), 0, 1).toISOString().split('T')[0] /* beginning of this year */
|
|
86
94
|
@property({ type: String, attribute: 'to-date' })
|
|
87
95
|
toDate: string = new Date(new Date().getFullYear(), 11, 31).toISOString().split('T')[0] /* end of this year */
|
|
96
|
+
@property({ type: Boolean, attribute: 'extend-grid-lines' }) extendGridLines: boolean = false
|
|
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
|
+
]
|
|
88
141
|
|
|
89
142
|
@query('.container') container!: HTMLDivElement
|
|
90
143
|
@query('.task-container') taskContainer!: HTMLDivElement
|
|
@@ -93,6 +146,7 @@ export class OxGantt extends LitElement {
|
|
|
93
146
|
@query('ox-gantt-chart') ganttChart!: OxGanttChart
|
|
94
147
|
|
|
95
148
|
private _startWidth: number = 0
|
|
149
|
+
private _isDragging: boolean = false
|
|
96
150
|
|
|
97
151
|
render() {
|
|
98
152
|
return html`
|
|
@@ -102,6 +156,7 @@ export class OxGantt extends LitElement {
|
|
|
102
156
|
vertical
|
|
103
157
|
@splitter-dragstart=${(e: CustomEvent) => this.resizeStart(e)}
|
|
104
158
|
@splitter-drag=${(e: CustomEvent) => this.resizeDrag(e)}
|
|
159
|
+
@splitter-dragend=${(e: CustomEvent) => this.resizeEnd(e)}
|
|
105
160
|
></ox-gantt-resize-splitter>
|
|
106
161
|
<ox-gantt-chart
|
|
107
162
|
class="chart-container"
|
|
@@ -109,42 +164,42 @@ export class OxGantt extends LitElement {
|
|
|
109
164
|
.fromDate=${this.fromDate}
|
|
110
165
|
.toDate=${this.toDate}
|
|
111
166
|
.tasks=${this.tasks}
|
|
167
|
+
.selectedTaskId=${this.selectedTaskId}
|
|
112
168
|
axis-scale="week-day"
|
|
113
|
-
extend-grid-lines
|
|
169
|
+
?extend-grid-lines=${this.extendGridLines}
|
|
170
|
+
@task-clicked=${this.handleTaskClicked}
|
|
114
171
|
></ox-gantt-chart>
|
|
115
172
|
</div>
|
|
116
173
|
`
|
|
117
174
|
}
|
|
118
175
|
|
|
119
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
|
+
|
|
120
182
|
return html`
|
|
121
183
|
<table>
|
|
122
184
|
<thead>
|
|
123
185
|
<tr>
|
|
124
|
-
|
|
125
|
-
<th>Section</th>
|
|
126
|
-
<th>Title</th>
|
|
127
|
-
<th>Type</th>
|
|
128
|
-
<th>Start Time</th>
|
|
129
|
-
<th>End Time</th>
|
|
130
|
-
<th>Tags</th>
|
|
131
|
-
<th>Progress</th>
|
|
132
|
-
<th>Prev. Task</th>
|
|
186
|
+
${sortedColumns.map(col => (col.visible ? html`<th style="width: ${col.width}">${col.label}</th>` : null))}
|
|
133
187
|
</tr>
|
|
134
188
|
</thead>
|
|
135
189
|
<tbody>
|
|
136
|
-
${
|
|
137
|
-
task => html`
|
|
138
|
-
<tr>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
+
})}
|
|
148
203
|
</tr>
|
|
149
204
|
`
|
|
150
205
|
)}
|
|
@@ -153,25 +208,93 @@ export class OxGantt extends LitElement {
|
|
|
153
208
|
`
|
|
154
209
|
}
|
|
155
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
|
+
|
|
156
267
|
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
|
157
268
|
this.taskContainer.style.width = '254px'
|
|
158
269
|
}
|
|
159
270
|
|
|
160
271
|
updated(changes: PropertyValues<this>) {}
|
|
161
272
|
|
|
162
|
-
syncScroll() {
|
|
163
|
-
|
|
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
|
+
}
|
|
164
280
|
}
|
|
165
281
|
|
|
166
282
|
resizeStart(e: CustomEvent) {
|
|
167
283
|
this._startWidth = ((e.target as HTMLElement)?.previousElementSibling as HTMLElement)?.offsetWidth
|
|
284
|
+
this._isDragging = true
|
|
168
285
|
}
|
|
169
286
|
|
|
170
287
|
resizeDrag(e: CustomEvent) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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`
|
|
175
294
|
}
|
|
176
295
|
}
|
|
296
|
+
|
|
297
|
+
resizeEnd(e: CustomEvent) {
|
|
298
|
+
this._isDragging = false
|
|
299
|
+
}
|
|
177
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'
|