@dssp/project 1.0.0-alpha.0 → 1.0.0-alpha.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-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/client/bootstrap.ts +0 -0
- package/client/index.ts +0 -0
- package/client/pages/lib/select2-component.ts +0 -175
- package/client/pages/lib/waether.ts +0 -159
- package/client/pages/project/component/project-update-header.ts +0 -88
- package/client/pages/project/popup/popup-plan-upload.ts +0 -138
- package/client/pages/project/popup/popup-project-create.ts +0 -147
- package/client/pages/project/popup/popup-schedule-upload.ts +0 -102
- package/client/pages/project/project-completed-list.ts +0 -281
- package/client/pages/project/project-detail.ts +0 -738
- package/client/pages/project/project-list.ts +0 -418
- package/client/pages/project/project-plan-management.ts +0 -476
- package/client/pages/project/project-schedule-list.ts +0 -294
- package/client/pages/project/project-schedule.ts +0 -393
- package/client/pages/project/project-setting-list.ts +0 -393
- package/client/pages/project/project-update.ts +0 -876
- package/client/pages/resource/construction-detail-type-popup.ts +0 -201
- package/client/pages/resource/construction-type-management.ts +0 -212
- package/client/pages/resource/inspection-drawing-type-management.ts +0 -245
- package/client/pages/resource/inspection-part-popup.ts +0 -201
- package/client/pages/resource/resource-importer.ts +0 -97
- package/client/pages/resource/resource-list-page.ts +0 -356
- package/client/pages/resource/worker-type-management.ts +0 -192
- package/client/pages/task/task-importer.ts +0 -94
- package/client/pages/task/task-list-page.ts +0 -340
- package/client/pages/task-resource/task-resource-importer.ts +0 -97
- package/client/pages/task-resource/task-resource-list-page.ts +0 -356
- package/client/route.ts +0 -55
- package/client/tsconfig.json +0 -11
- package/server/controllers/export-tasks.ts +0 -40
- package/server/controllers/import-task.ts +0 -134
- package/server/controllers/index.ts +0 -0
- package/server/controllers/parse-excel.ts +0 -86
- package/server/controllers/types.ts +0 -20
- package/server/index.ts +0 -4
- package/server/middlewares/index.ts +0 -3
- package/server/migrations/1723861466413-seed-roles.ts +0 -128
- package/server/migrations/1723861466414-seed-codes.ts +0 -157
- package/server/migrations/1723861476419-seed-resources.ts +0 -62
- package/server/migrations/1723861478420-seed-/bsample-project.ts +0 -87
- package/server/migrations/1723861478421-seed-/bsample-tasks.ts +0 -194
- package/server/migrations/index.ts +0 -9
- package/server/routes.ts +0 -108
- package/server/service/construction-detail-type/construction-detail-type-mutation.ts +0 -57
- package/server/service/construction-detail-type/construction-detail-type-query.ts +0 -31
- package/server/service/construction-detail-type/construction-detail-type-type.ts +0 -26
- package/server/service/construction-detail-type/construction-detail-type.ts +0 -52
- package/server/service/construction-detail-type/index.ts +0 -6
- package/server/service/construction-type/construction-type-mutation.ts +0 -66
- package/server/service/construction-type/construction-type-query.ts +0 -56
- package/server/service/construction-type/construction-type-type.ts +0 -26
- package/server/service/construction-type/construction-type.ts +0 -74
- package/server/service/construction-type/index.ts +0 -6
- package/server/service/index.ts +0 -56
- package/server/service/inspection-drawing-type/index.ts +0 -6
- package/server/service/inspection-drawing-type/inspection-drawing-type-mutation.ts +0 -69
- package/server/service/inspection-drawing-type/inspection-drawing-type-query.ts +0 -55
- package/server/service/inspection-drawing-type/inspection-drawing-type-type.ts +0 -23
- package/server/service/inspection-drawing-type/inspection-drawing-type.ts +0 -68
- package/server/service/inspection-part/index.ts +0 -6
- package/server/service/inspection-part/inspection-part-mutation.ts +0 -52
- package/server/service/inspection-part/inspection-part-query.ts +0 -41
- package/server/service/inspection-part/inspection-part-type.ts +0 -26
- package/server/service/inspection-part/inspection-part.ts +0 -51
- package/server/service/manager/index.ts +0 -6
- package/server/service/manager/manager-mutation.ts +0 -42
- package/server/service/manager/manager-query.ts +0 -28
- package/server/service/manager/manager-type.ts +0 -40
- package/server/service/manager/manager.ts +0 -29
- package/server/service/project/index.ts +0 -6
- package/server/service/project/project-mutation.ts +0 -255
- package/server/service/project/project-query.ts +0 -105
- package/server/service/project/project-type.ts +0 -72
- package/server/service/project/project.ts +0 -134
- package/server/service/resource/index.ts +0 -7
- package/server/service/resource/resource-mutation.ts +0 -137
- package/server/service/resource/resource-query.ts +0 -50
- package/server/service/resource/resource-type.ts +0 -41
- package/server/service/resource/resource.ts +0 -82
- package/server/service/task/index.ts +0 -6
- package/server/service/task/task-mutation.ts +0 -135
- package/server/service/task/task-query.ts +0 -169
- package/server/service/task/task-type.ts +0 -75
- package/server/service/task/task.ts +0 -130
- package/server/service/task-resource/index.ts +0 -7
- package/server/service/task-resource/task-resource-mutation.ts +0 -140
- package/server/service/task-resource/task-resource-query.ts +0 -36
- package/server/service/task-resource/task-resource-type.ts +0 -41
- package/server/service/task-resource/task-resource.ts +0 -51
- package/server/service/worker-type/index.ts +0 -6
- package/server/service/worker-type/worker-type-mutation.ts +0 -66
- package/server/service/worker-type/worker-type-query.ts +0 -47
- package/server/service/worker-type/worker-type-type.ts +0 -26
- package/server/service/worker-type/worker-type.ts +0 -68
- package/server/tsconfig.json +0 -10
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
import '@material/web/icon/icon.js'
|
|
2
|
-
import '@operato/data-grist'
|
|
3
|
-
|
|
4
|
-
import { CommonButtonStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
|
|
5
|
-
import { PageView, store } from '@operato/shell'
|
|
6
|
-
import { css, html } from 'lit'
|
|
7
|
-
import { customElement, property, query, state } from 'lit/decorators.js'
|
|
8
|
-
import { ScopedElementsMixin } from '@open-wc/scoped-elements'
|
|
9
|
-
import {
|
|
10
|
-
ColumnConfig,
|
|
11
|
-
DataGrist,
|
|
12
|
-
FetchOption,
|
|
13
|
-
SortersControl
|
|
14
|
-
} from '@operato/data-grist'
|
|
15
|
-
import { client } from '@operato/graphql'
|
|
16
|
-
import { i18next, localize } from '@operato/i18n'
|
|
17
|
-
import { notify, openPopup } from '@operato/layout'
|
|
18
|
-
import { OxPopup, OxPrompt } from '@operato/popup'
|
|
19
|
-
import { isMobileDevice } from '@operato/utils'
|
|
20
|
-
|
|
21
|
-
import { connect } from 'pwa-helpers/connect-mixin'
|
|
22
|
-
import gql from 'graphql-tag'
|
|
23
|
-
|
|
24
|
-
import { TaskResourceImporter } from './task-resource-importer'
|
|
25
|
-
|
|
26
|
-
@customElement('task-resource-list-page')
|
|
27
|
-
export class TaskResourceListPage extends connect(store)(localize(i18next)(ScopedElementsMixin(PageView))) {
|
|
28
|
-
|
|
29
|
-
static styles = [
|
|
30
|
-
ScrollbarStyles,
|
|
31
|
-
CommonGristStyles,
|
|
32
|
-
css`
|
|
33
|
-
:host {
|
|
34
|
-
display: flex;
|
|
35
|
-
|
|
36
|
-
width: 100%;
|
|
37
|
-
|
|
38
|
-
--grid-record-emphasized-background-color: red;
|
|
39
|
-
--grid-record-emphasized-color: yellow;
|
|
40
|
-
}
|
|
41
|
-
`
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
static get scopedElements() {
|
|
45
|
-
return {
|
|
46
|
-
'task-resource-importer': TaskResourceImporter
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
@state() private gristConfig: any
|
|
51
|
-
@state() private mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
|
|
52
|
-
|
|
53
|
-
@query('ox-grist') private grist!: DataGrist
|
|
54
|
-
@query('#sorter-control') private sortersControl!: OxPopup
|
|
55
|
-
|
|
56
|
-
get context() {
|
|
57
|
-
return {
|
|
58
|
-
title: i18next.t('title.task-resource list'),
|
|
59
|
-
search: {
|
|
60
|
-
handler: (search: string) => {
|
|
61
|
-
this.grist.searchText = search
|
|
62
|
-
},
|
|
63
|
-
value: this.grist.searchText
|
|
64
|
-
},
|
|
65
|
-
filter: {
|
|
66
|
-
handler: () => {
|
|
67
|
-
this.grist.toggleHeadroom()
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
help: 'project/task-resource',
|
|
71
|
-
actions: [
|
|
72
|
-
{
|
|
73
|
-
title: i18next.t('button.save'),
|
|
74
|
-
action: this.updateTaskResource.bind(this),
|
|
75
|
-
...CommonButtonStyles.save
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
title: i18next.t('button.delete'),
|
|
79
|
-
action: this.deleteTaskResource.bind(this),
|
|
80
|
-
...CommonButtonStyles.delete
|
|
81
|
-
}
|
|
82
|
-
],
|
|
83
|
-
exportable: {
|
|
84
|
-
name: i18next.t('title.task-resource list'),
|
|
85
|
-
data: this.exportHandler.bind(this)
|
|
86
|
-
},
|
|
87
|
-
importable: {
|
|
88
|
-
handler: this.importHandler.bind(this)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
render() {
|
|
94
|
-
const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID')
|
|
95
|
-
|
|
96
|
-
return html`
|
|
97
|
-
<ox-grist
|
|
98
|
-
.mode=${mode}
|
|
99
|
-
.config=${this.gristConfig}
|
|
100
|
-
.fetchHandler=${this.fetchHandler.bind(this)}
|
|
101
|
-
>
|
|
102
|
-
<div slot="headroom">
|
|
103
|
-
<div id="filters">
|
|
104
|
-
<ox-filters-form autofocus></ox-filters-form>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
<div id="sorters">
|
|
108
|
-
Sort
|
|
109
|
-
<md-icon
|
|
110
|
-
@click=${e => {
|
|
111
|
-
const target = e.currentTarget
|
|
112
|
-
this.sortersControl.open({
|
|
113
|
-
right: 0,
|
|
114
|
-
top: target.offsetTop + target.offsetHeight
|
|
115
|
-
})
|
|
116
|
-
}}
|
|
117
|
-
>expand_more</md-icon
|
|
118
|
-
>
|
|
119
|
-
<ox-popup id="sorter-control">
|
|
120
|
-
<ox-sorters-control> </ox-sorters-control>
|
|
121
|
-
</ox-popup>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
<div id="modes">
|
|
125
|
-
<md-icon @click=${() => (this.mode = 'GRID')} ?active=${mode == 'GRID'}>grid_on</md-icon>
|
|
126
|
-
<md-icon @click=${() => (this.mode = 'LIST')} ?active=${mode == 'LIST'}>format_list_bulleted</md-icon>
|
|
127
|
-
<md-icon @click=${() => (this.mode = 'CARD')} ?active=${mode == 'CARD'}>apps</md-icon>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
</ox-grist>
|
|
131
|
-
`
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async pageInitialized(lifecycle: any) {
|
|
135
|
-
this.gristConfig = {
|
|
136
|
-
list: {
|
|
137
|
-
fields: ['name', 'description'],
|
|
138
|
-
details: ['active', 'updatedAt']
|
|
139
|
-
},
|
|
140
|
-
columns: [
|
|
141
|
-
{ type: 'gutter', gutterName: 'sequence' },
|
|
142
|
-
{ type: 'gutter', gutterName: 'row-selector', multiple: true },
|
|
143
|
-
{
|
|
144
|
-
type: 'string',
|
|
145
|
-
name: 'name',
|
|
146
|
-
header: i18next.t('field.name'),
|
|
147
|
-
record: {
|
|
148
|
-
editable: true
|
|
149
|
-
},
|
|
150
|
-
filter: 'search',
|
|
151
|
-
sortable: true,
|
|
152
|
-
width: 150
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
type: 'string',
|
|
156
|
-
name: 'description',
|
|
157
|
-
header: i18next.t('field.description'),
|
|
158
|
-
record: {
|
|
159
|
-
editable: true
|
|
160
|
-
},
|
|
161
|
-
filter: 'search',
|
|
162
|
-
width: 200
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
type: 'checkbox',
|
|
166
|
-
name: 'active',
|
|
167
|
-
label: true,
|
|
168
|
-
header: i18next.t('field.active'),
|
|
169
|
-
record: {
|
|
170
|
-
editable: true
|
|
171
|
-
},
|
|
172
|
-
filter: true,
|
|
173
|
-
sortable: true,
|
|
174
|
-
width: 60
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
type: 'resource-object',
|
|
178
|
-
name: 'updater',
|
|
179
|
-
header: i18next.t('field.updater'),
|
|
180
|
-
record: {
|
|
181
|
-
editable: false
|
|
182
|
-
},
|
|
183
|
-
sortable: true,
|
|
184
|
-
width: 120
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
type: 'datetime',
|
|
188
|
-
name: 'updatedAt',
|
|
189
|
-
header: i18next.t('field.updated_at'),
|
|
190
|
-
record: {
|
|
191
|
-
editable: false
|
|
192
|
-
},
|
|
193
|
-
sortable: true,
|
|
194
|
-
width: 180
|
|
195
|
-
}
|
|
196
|
-
],
|
|
197
|
-
rows: {
|
|
198
|
-
selectable: {
|
|
199
|
-
multiple: true
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
sorters: [
|
|
203
|
-
{
|
|
204
|
-
name: 'name'
|
|
205
|
-
}
|
|
206
|
-
]
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async pageUpdated(changes: any, lifecycle: any) {
|
|
211
|
-
if (this.active) {
|
|
212
|
-
// do something here when this page just became as active
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {
|
|
217
|
-
const response = await client.query({
|
|
218
|
-
query: gql`
|
|
219
|
-
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
|
|
220
|
-
responses: taskResources(filters: $filters, pagination: $pagination, sortings: $sortings) {
|
|
221
|
-
items {
|
|
222
|
-
id
|
|
223
|
-
name
|
|
224
|
-
description
|
|
225
|
-
active
|
|
226
|
-
updater {
|
|
227
|
-
id
|
|
228
|
-
name
|
|
229
|
-
}
|
|
230
|
-
updatedAt
|
|
231
|
-
}
|
|
232
|
-
total
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
`,
|
|
236
|
-
variables: {
|
|
237
|
-
filters,
|
|
238
|
-
pagination: { page, limit },
|
|
239
|
-
sortings
|
|
240
|
-
}
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
return {
|
|
244
|
-
total: response.data.responses.total || 0,
|
|
245
|
-
records: response.data.responses.items || []
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private async deleteTaskResource() {
|
|
250
|
-
if (
|
|
251
|
-
await OxPrompt.open({
|
|
252
|
-
title: i18next.t('text.are_you_sure'),
|
|
253
|
-
text: i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }),
|
|
254
|
-
confirmButton: { text: i18next.t('button.confirm') },
|
|
255
|
-
cancelButton: { text: i18next.t('button.cancel') }
|
|
256
|
-
})
|
|
257
|
-
) {
|
|
258
|
-
const ids = this.grist.selected.map(record => record.id)
|
|
259
|
-
if (ids && ids.length > 0) {
|
|
260
|
-
const response = await client.mutate({
|
|
261
|
-
mutation: gql`
|
|
262
|
-
mutation ($ids: [String!]!) {
|
|
263
|
-
deleteTaskResources(ids: $ids)
|
|
264
|
-
}
|
|
265
|
-
`,
|
|
266
|
-
variables: {
|
|
267
|
-
ids
|
|
268
|
-
}
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
if (!response.errors) {
|
|
272
|
-
this.grist.fetch()
|
|
273
|
-
notify({
|
|
274
|
-
message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
|
|
275
|
-
})
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
private async updateTaskResource() {
|
|
282
|
-
let patches = this.grist.dirtyRecords
|
|
283
|
-
if (patches && patches.length) {
|
|
284
|
-
patches = patches.map(patch => {
|
|
285
|
-
let patchField: any = patch.id ? { id: patch.id } : {}
|
|
286
|
-
const dirtyFields = patch.__dirtyfields__
|
|
287
|
-
for (let key in dirtyFields) {
|
|
288
|
-
patchField[key] = dirtyFields[key].after
|
|
289
|
-
}
|
|
290
|
-
patchField.cuFlag = patch.__dirty__
|
|
291
|
-
|
|
292
|
-
return patchField
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
const response = await client.mutate({
|
|
296
|
-
mutation: gql`
|
|
297
|
-
mutation ($patches: [TaskResourcePatch!]!) {
|
|
298
|
-
updateMultipleTaskResource(patches: $patches) {
|
|
299
|
-
name
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
`,
|
|
303
|
-
variables: {
|
|
304
|
-
patches
|
|
305
|
-
}
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
if (!response.errors) {
|
|
309
|
-
this.grist.fetch()
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private async exportHandler() {
|
|
315
|
-
const exportTargets = this.grist.selected.length ? this.grist.selected : this.grist.dirtyData.records
|
|
316
|
-
const targetFieldSet = new Set([
|
|
317
|
-
'id',
|
|
318
|
-
'name',
|
|
319
|
-
'description',
|
|
320
|
-
'active'
|
|
321
|
-
])
|
|
322
|
-
|
|
323
|
-
return exportTargets.map(taskResource => {
|
|
324
|
-
let tempObj = {}
|
|
325
|
-
for (const field of targetFieldSet) {
|
|
326
|
-
tempObj[field] = taskResource[field]
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return tempObj
|
|
330
|
-
})
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
private async importHandler(records) {
|
|
334
|
-
const popup = openPopup(
|
|
335
|
-
html`
|
|
336
|
-
<task-resource-importer
|
|
337
|
-
.taskResources=${records}
|
|
338
|
-
@imported=${() => {
|
|
339
|
-
history.back()
|
|
340
|
-
this.grist.fetch()
|
|
341
|
-
}}
|
|
342
|
-
></task-resource-importer>
|
|
343
|
-
`,
|
|
344
|
-
{
|
|
345
|
-
backdrop: true,
|
|
346
|
-
size: 'large',
|
|
347
|
-
title: i18next.t('title.import task-resource')
|
|
348
|
-
}
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
popup.onclosed = () => {
|
|
352
|
-
this.grist.fetch()
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
package/client/route.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export default function route(page: string) {
|
|
2
|
-
switch (page) {
|
|
3
|
-
case 'project-list':
|
|
4
|
-
import('./pages/project/project-list')
|
|
5
|
-
return page
|
|
6
|
-
|
|
7
|
-
case 'project-detail':
|
|
8
|
-
import('./pages/project/project-detail')
|
|
9
|
-
return page
|
|
10
|
-
|
|
11
|
-
case 'project-completed-list':
|
|
12
|
-
import('./pages/project/project-completed-list')
|
|
13
|
-
return page
|
|
14
|
-
|
|
15
|
-
case 'project-schedule-list':
|
|
16
|
-
import('./pages/project/project-schedule-list')
|
|
17
|
-
return page
|
|
18
|
-
|
|
19
|
-
case 'project-schedule':
|
|
20
|
-
import('./pages/project/project-schedule')
|
|
21
|
-
return page
|
|
22
|
-
|
|
23
|
-
case 'project-setting-list':
|
|
24
|
-
import('./pages/project/project-setting-list')
|
|
25
|
-
return page
|
|
26
|
-
|
|
27
|
-
case 'project-update':
|
|
28
|
-
import('./pages/project/project-update')
|
|
29
|
-
return page
|
|
30
|
-
|
|
31
|
-
case 'project-plan-management':
|
|
32
|
-
import('./pages/project/project-plan-management')
|
|
33
|
-
return page
|
|
34
|
-
|
|
35
|
-
case 'worker-type-management':
|
|
36
|
-
import('./pages/resource/worker-type-management')
|
|
37
|
-
return page
|
|
38
|
-
|
|
39
|
-
case 'construction-type-management':
|
|
40
|
-
import('./pages/resource/construction-type-management')
|
|
41
|
-
return page
|
|
42
|
-
|
|
43
|
-
case 'inspection-drawing-type-management':
|
|
44
|
-
import('./pages/resource/inspection-drawing-type-management')
|
|
45
|
-
return page
|
|
46
|
-
|
|
47
|
-
case 'resource-list':
|
|
48
|
-
import('./pages/resource/resource-list-page')
|
|
49
|
-
return page
|
|
50
|
-
|
|
51
|
-
case 'task-resource-list':
|
|
52
|
-
import('./pages/task-resource/task-resource-list-page')
|
|
53
|
-
return page
|
|
54
|
-
}
|
|
55
|
-
}
|
package/client/tsconfig.json
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Workbook, Worksheet } from 'exceljs'
|
|
2
|
-
|
|
3
|
-
export interface Task {
|
|
4
|
-
name: string
|
|
5
|
-
startDate: Date
|
|
6
|
-
endDate: Date
|
|
7
|
-
subtasks?: Task[]
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function createGanttChart(tasks: Task[], worksheet: Worksheet, level: number = 0) {
|
|
11
|
-
tasks.forEach(task => {
|
|
12
|
-
const row = worksheet.addRow([task.name, task.startDate, task.endDate])
|
|
13
|
-
|
|
14
|
-
row.outlineLevel = level
|
|
15
|
-
|
|
16
|
-
if (task.subtasks && task.subtasks.length > 0) {
|
|
17
|
-
createGanttChart(task.subtasks, worksheet, level + 1)
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function generateExcel(tasks: Task[]) {
|
|
23
|
-
const workbook = new Workbook()
|
|
24
|
-
const worksheet = workbook.addWorksheet('Gantt Chart')
|
|
25
|
-
|
|
26
|
-
worksheet.properties.outlineProperties = {
|
|
27
|
-
summaryBelow: false,
|
|
28
|
-
summaryRight: false
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
worksheet.columns = [
|
|
32
|
-
{ header: 'Task Name', key: 'name', width: 30 },
|
|
33
|
-
{ header: 'Start Date', key: 'startDate', width: 20 },
|
|
34
|
-
{ header: 'End Date', key: 'endDate', width: 20 }
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
createGanttChart(tasks, worksheet)
|
|
38
|
-
|
|
39
|
-
return await workbook.xlsx.writeBuffer()
|
|
40
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { getRepository } from '@things-factory/shell'
|
|
2
|
-
|
|
3
|
-
import { Project } from '../service/project/project'
|
|
4
|
-
import { Task, TaskType } from '../service/task/task'
|
|
5
|
-
import { TaskResource } from '../service/task-resource/task-resource'
|
|
6
|
-
import { Resource } from '../service/resource/resource'
|
|
7
|
-
import { RawTask } from './types'
|
|
8
|
-
|
|
9
|
-
function excelSerialToJSDate(serial) {
|
|
10
|
-
const excelEpoch = new Date(1899, 11, 30) // Excel epoch (30th December 1899)
|
|
11
|
-
const days = Math.floor(serial) // Get the number of days
|
|
12
|
-
const milliseconds = (serial - days) * 86400 * 1000 // Convert the fractional day part to milliseconds
|
|
13
|
-
|
|
14
|
-
const jsDate = new Date(excelEpoch.getTime() + days * 86400 * 1000 + milliseconds)
|
|
15
|
-
return jsDate
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function importTasks(project: Project, tasks: RawTask[], context: ResolverContext) {
|
|
19
|
-
const { domain, user, tx } = context.state
|
|
20
|
-
|
|
21
|
-
const taskRepository = getRepository(Task, tx)
|
|
22
|
-
const resourceRepository = getRepository(Resource, tx)
|
|
23
|
-
const taskResourceRepository = getRepository(TaskResource, tx)
|
|
24
|
-
|
|
25
|
-
// 1. 기존 태스크와 리소스를 Soft Delete
|
|
26
|
-
await taskRepository.softDelete({ project: { id: project.id } })
|
|
27
|
-
|
|
28
|
-
// 2. 태스크 임포트
|
|
29
|
-
const importTaskData = async (rawTask: RawTask, parent?: Task) => {
|
|
30
|
-
if (rawTask.children && rawTask.children.length > 0) {
|
|
31
|
-
rawTask.type = TaskType.GROUP
|
|
32
|
-
} else {
|
|
33
|
-
rawTask.type = TaskType.TASK
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// 유효성 검사
|
|
37
|
-
if (!rawTask.title || !rawTask.code || (rawTask.type == TaskType.TASK && (rawTask.duration ?? null) === null)) {
|
|
38
|
-
throw new Error(`Task '${rawTask.code}' is missing required fields.`)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (rawTask.type == TaskType.TASK) {
|
|
42
|
-
// 시작일, 종료일 계산
|
|
43
|
-
var startDate: Date = new Date(rawTask.startDate)
|
|
44
|
-
|
|
45
|
-
var endDate
|
|
46
|
-
if (!startDate && rawTask.dependsOn) {
|
|
47
|
-
const dependsOnTask = await taskRepository.findOne({ where: { code: rawTask.dependsOn, project: { id: project.id } } })
|
|
48
|
-
if (dependsOnTask && dependsOnTask.endDate) {
|
|
49
|
-
startDate = new Date(dependsOnTask.endDate)
|
|
50
|
-
startDate.setDate(startDate.getDate() + 1)
|
|
51
|
-
} else {
|
|
52
|
-
// TODO handler error
|
|
53
|
-
// throw new Error(`Task '${rawTask.code}' depends on a task '${rawTask.dependsOn}' that doesn't have a valid end date.`)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!startDate) {
|
|
58
|
-
throw new Error(`Task '${rawTask.code}' must have either a start date or a valid dependency.`)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const duration = rawTask.duration
|
|
62
|
-
endDate = new Date(startDate)
|
|
63
|
-
endDate.setDate(startDate.getDate() + duration - 1)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 태스크 생성 및 저장
|
|
67
|
-
var task: Task = await taskRepository.save({
|
|
68
|
-
code: rawTask.code,
|
|
69
|
-
name: rawTask.title,
|
|
70
|
-
type: rawTask.type,
|
|
71
|
-
startDate,
|
|
72
|
-
endDate,
|
|
73
|
-
project,
|
|
74
|
-
parent,
|
|
75
|
-
duration: rawTask.duration,
|
|
76
|
-
dependsOn: rawTask.dependsOn,
|
|
77
|
-
progress: rawTask.progress,
|
|
78
|
-
tags: rawTask.tags,
|
|
79
|
-
style: rawTask.style,
|
|
80
|
-
updater: user,
|
|
81
|
-
creator: user
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
// 리소스 생성 및 저장
|
|
85
|
-
if (rawTask.resources) {
|
|
86
|
-
for (const resource of rawTask.resources) {
|
|
87
|
-
const resourceType = await resourceRepository.findOne({ where: { domain: { id: domain.id }, name: resource.type } })
|
|
88
|
-
if (resourceType) {
|
|
89
|
-
await taskResourceRepository.save({
|
|
90
|
-
task,
|
|
91
|
-
resource: resourceType,
|
|
92
|
-
quantity: resource.allocated
|
|
93
|
-
})
|
|
94
|
-
} else {
|
|
95
|
-
throw new Error(`unknown resource type: ${resource.type}`)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 자식 태스크 처리
|
|
101
|
-
if (rawTask.children && rawTask.children.length > 0) {
|
|
102
|
-
let lastEndDate = null
|
|
103
|
-
let lastStartDate = null
|
|
104
|
-
for (const childTask of rawTask.children) {
|
|
105
|
-
const subtask = await importTaskData(childTask, task)
|
|
106
|
-
|
|
107
|
-
lastEndDate = !lastEndDate ? subtask.endDate : lastEndDate > subtask.endDate ? lastEndDate : subtask.endDate
|
|
108
|
-
lastStartDate = !lastStartDate ? subtask.startDate : lastStartDate < subtask.startDate ? lastStartDate : subtask.startDate
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// 그룹 태스크의 기간(duration)을 계산합니다.
|
|
112
|
-
const calculatedDuration =
|
|
113
|
-
lastEndDate && lastStartDate
|
|
114
|
-
? Math.ceil((lastEndDate.getTime() - lastStartDate.getTime()) / (1000 * 60 * 60 * 24)) + 1
|
|
115
|
-
: 0
|
|
116
|
-
|
|
117
|
-
task = await taskRepository.findOne({ where: { id: task.id } })
|
|
118
|
-
|
|
119
|
-
return await taskRepository.save({
|
|
120
|
-
...task,
|
|
121
|
-
startDate: lastStartDate,
|
|
122
|
-
endDate: lastEndDate,
|
|
123
|
-
duration: calculatedDuration
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return task
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// 루트 태스크들 임포트
|
|
131
|
-
for (const rootTask of tasks) {
|
|
132
|
-
await importTaskData(rootTask)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
File without changes
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import ExcelJS from 'exceljs'
|
|
2
|
-
import { RawTask } from './types'
|
|
3
|
-
import { Project } from '../service/project/project'
|
|
4
|
-
import { importTasks } from './import-task'
|
|
5
|
-
|
|
6
|
-
export async function parseExcelAndImportTasks(buffer: Buffer, project: Project, context: ResolverContext) {
|
|
7
|
-
// 1. 엑셀 파일을 읽어들입니다.
|
|
8
|
-
const workbook = new ExcelJS.Workbook()
|
|
9
|
-
await workbook.xlsx.load(buffer)
|
|
10
|
-
|
|
11
|
-
// 2. 첫 번째 워크시트를 가져옵니다.
|
|
12
|
-
const worksheet = workbook.getWorksheet(1) // Index or sheet name can be used
|
|
13
|
-
|
|
14
|
-
// 3. 첫 번째 row를 header로 사용합니다.
|
|
15
|
-
const headers: string[] = []
|
|
16
|
-
let taskCodeColumnIndex = -1
|
|
17
|
-
|
|
18
|
-
const headerRow = worksheet.getRow(1)
|
|
19
|
-
headerRow.eachCell((cell, colNumber) => {
|
|
20
|
-
const headerText = cell.text.toString()
|
|
21
|
-
headers[colNumber - 1] = headerText // Store headers in an array
|
|
22
|
-
|
|
23
|
-
if (headerText === '작업코드') {
|
|
24
|
-
taskCodeColumnIndex = colNumber // Store the column index for "작업코드"
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
if (taskCodeColumnIndex === -1) {
|
|
29
|
-
throw new Error('작업코드 column not found')
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// 4. 엑셀 데이터를 RawTask 형식으로 변환합니다.
|
|
33
|
-
const tasks: RawTask[] = []
|
|
34
|
-
|
|
35
|
-
// Start processing from the second row onward to skip the header
|
|
36
|
-
for (let rowIndex = 2; rowIndex <= worksheet.rowCount; rowIndex++) {
|
|
37
|
-
const row = worksheet.getRow(rowIndex)
|
|
38
|
-
const taskData: any = {}
|
|
39
|
-
|
|
40
|
-
row.eachCell((cell, colNumber) => {
|
|
41
|
-
const header = headers[colNumber - 1]
|
|
42
|
-
|
|
43
|
-
// Check if the cell has a formula(or sharedFormula) and use the formula result
|
|
44
|
-
let cellValue: any = cell.value
|
|
45
|
-
if (cellValue && typeof cellValue === 'object' && ('formula' in cellValue || 'sharedFormula' in cellValue)) {
|
|
46
|
-
// Cell contains a formula, use the calculated result if available
|
|
47
|
-
cellValue = cellValue.result ?? cellValue.value // Use the result, or fallback to value if result is not calculated
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
taskData[header] = cellValue
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
const taskCodeCell = row.getCell(taskCodeColumnIndex)
|
|
54
|
-
let bgColor = '#FFFFFF'
|
|
55
|
-
const fill = taskCodeCell.style.fill
|
|
56
|
-
|
|
57
|
-
if (fill && fill.type === 'pattern' && fill.pattern === 'solid') {
|
|
58
|
-
const fgColor = fill.fgColor
|
|
59
|
-
if (fgColor && fgColor.argb) {
|
|
60
|
-
// ARGB is a color in the format AARRGGBB, remove the alpha channel (first two characters)
|
|
61
|
-
bgColor = `#${fgColor.argb.slice(2)}`
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const task: RawTask = {
|
|
66
|
-
code: taskData['작업코드'],
|
|
67
|
-
title: taskData['작업명'],
|
|
68
|
-
type: taskData['세부공종'],
|
|
69
|
-
duration: taskData['기간'],
|
|
70
|
-
startDate: taskData['시작일'],
|
|
71
|
-
dependsOn: taskData['선행작업코드'],
|
|
72
|
-
progress: taskData['진척율'],
|
|
73
|
-
tags: taskData['Tags'] ? taskData['Tags'].split(',') : [],
|
|
74
|
-
resources: taskData['Resources'] ? JSON.parse(taskData['Resources']) : [],
|
|
75
|
-
style: bgColor,
|
|
76
|
-
children: []
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (task.code && task.type) {
|
|
80
|
-
tasks.push(task)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 5. 변환된 데이터를 importTasks 함수로 전달합니다.
|
|
85
|
-
await importTasks(project, tasks, context)
|
|
86
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Task, TaskType } from '../service/task/task'
|
|
2
|
-
|
|
3
|
-
export interface RawResource {
|
|
4
|
-
type: string
|
|
5
|
-
allocated: number
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface RawTask {
|
|
9
|
-
code: string
|
|
10
|
-
title: string
|
|
11
|
-
type?: TaskType
|
|
12
|
-
duration?: number
|
|
13
|
-
startDate?: Date | string
|
|
14
|
-
dependsOn?: string
|
|
15
|
-
progress?: number
|
|
16
|
-
tags?: string[]
|
|
17
|
-
style?: string
|
|
18
|
-
resources?: RawResource[]
|
|
19
|
-
children?: RawTask[]
|
|
20
|
-
}
|