@dssp/project 0.0.26 → 0.0.28

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.
Files changed (47) hide show
  1. package/client/pages/lib/select2-component.ts +12 -1
  2. package/client/pages/project/component/project-update-header.ts +85 -0
  3. package/client/pages/project/popup/popup-schedule-upload.ts +102 -0
  4. package/client/pages/project/project-list.ts +1 -0
  5. package/client/pages/project/project-plan-management.ts +3 -54
  6. package/client/pages/project/project-schedule.ts +60 -13
  7. package/client/pages/project/project-update.ts +4 -52
  8. package/dist-client/pages/lib/select2-component.js +12 -1
  9. package/dist-client/pages/lib/select2-component.js.map +1 -1
  10. package/dist-client/pages/project/component/project-update-header.d.ts +1 -0
  11. package/dist-client/pages/project/component/project-update-header.js +95 -0
  12. package/dist-client/pages/project/component/project-update-header.js.map +1 -0
  13. package/dist-client/pages/project/popup/popup-schedule-upload.d.ts +9 -0
  14. package/dist-client/pages/project/popup/popup-schedule-upload.js +105 -0
  15. package/dist-client/pages/project/popup/popup-schedule-upload.js.map +1 -0
  16. package/dist-client/pages/project/project-list.d.ts +1 -0
  17. package/dist-client/pages/project/project-list.js.map +1 -1
  18. package/dist-client/pages/project/project-plan-management.d.ts +1 -0
  19. package/dist-client/pages/project/project-plan-management.js +3 -53
  20. package/dist-client/pages/project/project-plan-management.js.map +1 -1
  21. package/dist-client/pages/project/project-schedule.d.ts +4 -0
  22. package/dist-client/pages/project/project-schedule.js +55 -12
  23. package/dist-client/pages/project/project-schedule.js.map +1 -1
  24. package/dist-client/pages/project/project-update.d.ts +1 -0
  25. package/dist-client/pages/project/project-update.js +4 -52
  26. package/dist-client/pages/project/project-update.js.map +1 -1
  27. package/dist-client/route.d.ts +1 -1
  28. package/dist-client/tsconfig.tsbuildinfo +1 -1
  29. package/dist-server/service/index.d.ts +2 -2
  30. package/dist-server/service/project/project-mutation.d.ts +2 -1
  31. package/dist-server/service/project/project-mutation.js +16 -0
  32. package/dist-server/service/project/project-mutation.js.map +1 -1
  33. package/dist-server/service/project/project-query.d.ts +1 -0
  34. package/dist-server/service/project/project-query.js +17 -0
  35. package/dist-server/service/project/project-query.js.map +1 -1
  36. package/dist-server/service/project/project-type.d.ts +4 -0
  37. package/dist-server/service/project/project-type.js +15 -1
  38. package/dist-server/service/project/project-type.js.map +1 -1
  39. package/dist-server/service/project/project.d.ts +1 -0
  40. package/dist-server/service/project/project.js +4 -0
  41. package/dist-server/service/project/project.js.map +1 -1
  42. package/dist-server/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +4 -4
  44. package/server/service/project/project-mutation.ts +16 -1
  45. package/server/service/project/project-query.ts +13 -0
  46. package/server/service/project/project-type.ts +9 -0
  47. package/server/service/project/project.ts +4 -0
@@ -8,6 +8,10 @@ export class Select2Component extends LitElement {
8
8
  position: relative;
9
9
  width: 300px;
10
10
  border: 1px solid #000;
11
+ border-radius: 6px;
12
+ padding: 4px 16px;
13
+ font-size: 14px;
14
+ color: var(--md-sys-color-primary);
11
15
  }
12
16
 
13
17
  div[dropdown] {
@@ -18,6 +22,8 @@ export class Select2Component extends LitElement {
18
22
 
19
23
  div[options] {
20
24
  position: absolute;
25
+ left: 0;
26
+ top: 30px;
21
27
  width: 100%;
22
28
  border: 1px solid #ccc;
23
29
  background-color: white;
@@ -30,6 +36,10 @@ export class Select2Component extends LitElement {
30
36
  div[option] {
31
37
  padding: 10px;
32
38
  cursor: pointer;
39
+ border-bottom: 1px solid #ccc;
40
+ }
41
+ div[option]:last-child {
42
+ border-bottom: none;
33
43
  }
34
44
 
35
45
  div[option]:hover {
@@ -48,10 +58,11 @@ export class Select2Component extends LitElement {
48
58
  }
49
59
 
50
60
  div[tag] {
51
- background-color: #007bff;
61
+ background-color: #2e79be;
52
62
  color: white;
53
63
  padding: 5px 10px;
54
64
  border-radius: 20px;
65
+ font-size: 13px;
55
66
  display: inline-flex;
56
67
  align-items: center;
57
68
  cursor: pointer;
@@ -0,0 +1,85 @@
1
+ import '@material/web/icon/icon.js'
2
+ import { css, html, LitElement } from 'lit'
3
+ import { customElement, property } from 'lit/decorators.js'
4
+ import { ButtonContainerStyles, ScrollbarStyles } from '@operato/styles'
5
+
6
+ @customElement('project-update-header')
7
+ class ProjectUpdateHeader extends LitElement {
8
+ static styles = [
9
+ ButtonContainerStyles,
10
+ ScrollbarStyles,
11
+ css`
12
+ div[header] {
13
+ display: flex;
14
+ margin: 0px 20px;
15
+
16
+ h2 {
17
+ flex: 0.5;
18
+ color: #3f71a0;
19
+ }
20
+ }
21
+
22
+ div[button-container] {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: end;
26
+ flex: 0.5;
27
+
28
+ md-elevated-button {
29
+ margin: 0px 3px;
30
+
31
+ --md-elevated-button-container-height: 35px;
32
+ --md-elevated-button-label-text-size: 16px;
33
+ --md-elevated-button-container-color: #0595e5;
34
+
35
+ --md-elevated-button-label-text-color: #fff;
36
+ --md-elevated-button-hover-label-text-color: #fff;
37
+ --md-elevated-button-pressed-label-text-color: #fff;
38
+ --md-elevated-button-focus-label-text-color: #fff;
39
+ --md-elevated-button-icon-color: #fff;
40
+ --md-elevated-button-hover-icon-color: #fff;
41
+ --md-elevated-button-pressed-icon-color: #fff;
42
+ --md-elevated-button-focus-icon-color: #fff;
43
+
44
+ &[green] {
45
+ --md-elevated-button-container-color: #42b382;
46
+ }
47
+ }
48
+ }
49
+ `
50
+ ]
51
+
52
+ @property({ type: String }) projectId: string = ''
53
+ @property({ type: String }) title: string = ''
54
+
55
+ render() {
56
+ const path = window.location.pathname
57
+
58
+ return html`
59
+ <div header>
60
+ <h2>${this.title}</h2>
61
+ <div button-container>
62
+ <md-elevated-button green @click=${this._dispatchEvent} ?disabled=${!this.projectId}>
63
+ <md-icon slot="icon">save</md-icon>정보 저장
64
+ </md-elevated-button>
65
+ <md-elevated-button
66
+ href=${`project-update/${this.projectId}`}
67
+ ?disabled=${!this.projectId || path.includes('project-update/')}
68
+ >
69
+ <md-icon slot="icon">assignment</md-icon>프로젝트 정보 수정
70
+ </md-elevated-button>
71
+ <md-elevated-button
72
+ href=${`project-plan-management/${this.projectId}`}
73
+ ?disabled=${!this.projectId || path.includes('project-plan-management/')}
74
+ >
75
+ <md-icon slot="icon">description</md-icon>도면 관리
76
+ </md-elevated-button>
77
+ </div>
78
+ </div>
79
+ `
80
+ }
81
+
82
+ private _dispatchEvent() {
83
+ this.dispatchEvent(new CustomEvent('custom-click'))
84
+ }
85
+ }
@@ -0,0 +1,102 @@
1
+ import { css, html, LitElement } from 'lit'
2
+ import { customElement, property } from 'lit/decorators.js'
3
+ import { client } from '@operato/graphql'
4
+ import { notify } from '@operato/layout'
5
+ import gql from 'graphql-tag'
6
+ import { Attachment } from '@things-factory/attachment-base'
7
+
8
+ @customElement('popup-schedule-upload')
9
+ export class PopupScheduleUpload extends LitElement {
10
+ static styles = [
11
+ css`
12
+ :host {
13
+ display: flex;
14
+ flex-direction: column;
15
+ background-color: #fff;
16
+ width: 100%;
17
+ }
18
+
19
+ div[body] {
20
+ flex: 1;
21
+
22
+ div[input-container] {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ gap: 25px;
27
+ background-color: #f7f7f7;
28
+ padding: 35px 27px 27px 27px;
29
+
30
+ ox-input-file {
31
+ height: 100px;
32
+ width: 120px;
33
+ line-height: 100%;
34
+ }
35
+ }
36
+
37
+ div[button-container] {
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ margin-top: 20px;
42
+ }
43
+ }
44
+ `
45
+ ]
46
+
47
+ @property({ type: Object }) private scheduleTable: Attachment | undefined
48
+ @property({ type: String }) private projectId: string | undefined
49
+
50
+ render() {
51
+ const icon = this.scheduleTable ? '' : 'upload'
52
+ const description = this.scheduleTable?.name ? ' ' : '공정표 업로드'
53
+
54
+ return html`
55
+ <div body>
56
+ <div input-container>
57
+ <ox-input-file
58
+ icon=${icon}
59
+ label=${this.scheduleTable?.name || ''}
60
+ description=${description}
61
+ @change=${this._onChangeAttachment.bind(this)}
62
+ ></ox-input-file>
63
+ </div>
64
+
65
+ <div button-container>
66
+ <md-outlined-button @click=${this._close}><md-icon slot="icon">cancel</md-icon>취소</md-outlined-button>
67
+ </div>
68
+ </div>
69
+ `
70
+ }
71
+
72
+ private _close() {
73
+ history.back()
74
+ }
75
+
76
+ // 공정표 업로드
77
+ async _onChangeAttachment(e: CustomEvent) {
78
+ const file = e.detail[0] || null
79
+
80
+ const response = await client.mutate({
81
+ mutation: gql`
82
+ mutation UploadProjectScheduleTable($param: UploadProjectScheduleTable!) {
83
+ uploadProjectScheduleTable(param: $param)
84
+ }
85
+ `,
86
+ variables: {
87
+ param: {
88
+ projectId: this.projectId,
89
+ scheduleTable: file
90
+ }
91
+ },
92
+ context: {
93
+ hasUpload: true
94
+ }
95
+ })
96
+
97
+ if (!response.errors) {
98
+ notify({ message: '공정표가 업로드 되었습니다.' })
99
+ this.dispatchEvent(new CustomEvent('uploaded'))
100
+ }
101
+ }
102
+ }
@@ -43,6 +43,7 @@ export interface Project {
43
43
  inspPassRate?: number
44
44
  robotProgressRate?: number
45
45
  structuralSafetyRate?: number
46
+ scheduleTable?: Attachment
46
47
  buildingComplex: BuildingComplex
47
48
  }
48
49
  export interface BuildingComplex {
@@ -16,6 +16,7 @@ import { openPopup } from '@operato/layout'
16
16
  import gql from 'graphql-tag'
17
17
  import { Building, BuildingLevel, Project } from './project-list'
18
18
  import './popup/popup-plan-upload'
19
+ import './component/project-update-header'
19
20
 
20
21
  @customElement('project-plan-management')
21
22
  export class ProjectPlanManagement extends ScopedElementsMixin(PageView) {
@@ -53,44 +54,6 @@ export class ProjectPlanManagement extends ScopedElementsMixin(PageView) {
53
54
  font-weight: bold;
54
55
  }
55
56
 
56
- div[header] {
57
- display: flex;
58
- margin: 0px 20px;
59
-
60
- h2 {
61
- flex: 0.5;
62
- color: #3f71a0;
63
- }
64
-
65
- div[button-container] {
66
- display: flex;
67
- align-items: center;
68
- justify-content: end;
69
- flex: 0.5;
70
-
71
- md-elevated-button {
72
- margin: 0px 3px;
73
-
74
- --md-elevated-button-container-height: 35px;
75
- --md-elevated-button-label-text-size: 16px;
76
- --md-elevated-button-container-color: #0595e5;
77
-
78
- --md-elevated-button-label-text-color: #fff;
79
- --md-elevated-button-hover-label-text-color: #fff;
80
- --md-elevated-button-pressed-label-text-color: #fff;
81
- --md-elevated-button-focus-label-text-color: #fff;
82
- --md-elevated-button-icon-color: #fff;
83
- --md-elevated-button-hover-icon-color: #fff;
84
- --md-elevated-button-pressed-icon-color: #fff;
85
- --md-elevated-button-focus-icon-color: #fff;
86
-
87
- &[green] {
88
- --md-elevated-button-container-color: #42b382;
89
- }
90
- }
91
- }
92
- }
93
-
94
57
  div[body] {
95
58
  display: grid;
96
59
  grid-template-rows: 240px 1fr 60px;
@@ -247,20 +210,8 @@ export class ProjectPlanManagement extends ScopedElementsMixin(PageView) {
247
210
 
248
211
  render() {
249
212
  return html`
250
- <div header>
251
- <h2>도면 관리</h2>
252
- <div button-container>
253
- <md-elevated-button green @click=${this._saveProject}>
254
- <md-icon slot="icon">save</md-icon>정보 저장
255
- </md-elevated-button>
256
- <md-elevated-button href=${`project-update/${this.project.id}`}>
257
- <md-icon slot="icon">assignment</md-icon>프로젝트 정보 수정
258
- </md-elevated-button>
259
- <md-elevated-button href=${`project-task-update/${this.project.id}`}>
260
- <md-icon slot="icon">event_note</md-icon>공정표 관리
261
- </md-elevated-button>
262
- </div>
263
- </div>
213
+ <project-update-header .projectId=${this.project.id || ''} title="도면 관리" @custom-click=${this._saveProject}>
214
+ </project-update-header>
264
215
 
265
216
  <div body>
266
217
  <div building-container>
@@ -415,8 +366,6 @@ export class ProjectPlanManagement extends ScopedElementsMixin(PageView) {
415
366
  })
416
367
 
417
368
  this.project = response.data?.project
418
-
419
- console.log('init project : ', this.project)
420
369
  }
421
370
 
422
371
  private async _saveProject() {
@@ -11,10 +11,13 @@ import { customElement, query, state } from 'lit/decorators.js'
11
11
  import { ScopedElementsMixin } from '@open-wc/scoped-elements'
12
12
  import { client } from '@operato/graphql'
13
13
  import { i18next } from '@operato/i18n'
14
-
14
+ import { openPopup } from '@operato/layout'
15
15
  import gql from 'graphql-tag'
16
16
  import { Project } from './project-list'
17
+ import { keyed } from 'lit/directives/keyed.js'
18
+ import { ScrollbarStyles } from '@operato/styles'
17
19
  import '@operato/gantt/ox-gantt.js'
20
+ import './popup/popup-schedule-upload'
18
21
 
19
22
  const TaskFragment = gql`
20
23
  fragment TaskFragment on Task {
@@ -36,6 +39,7 @@ const TaskFragment = gql`
36
39
  @customElement('project-schedule')
37
40
  export class ProjectSchedule extends ScopedElementsMixin(PageView) {
38
41
  static styles = [
42
+ ScrollbarStyles,
39
43
  css`
40
44
  :host {
41
45
  display: flex;
@@ -137,11 +141,25 @@ export class ProjectSchedule extends ScopedElementsMixin(PageView) {
137
141
  }
138
142
  }
139
143
 
140
- div[button] {
144
+ div[construction-list-container] {
141
145
  flex: 1;
146
+ display: flex;
142
147
  border-radius: 5px;
143
148
  border: 1px solid #cccccc80;
144
149
  background-color: #fff;
150
+ padding: 8px 10px;
151
+ gap: 10px;
152
+ overflow-x: auto;
153
+
154
+ md-outlined-button {
155
+ --md-outlined-button-container-height: 30px;
156
+ --md-outlined-button-trailing-space: 15px;
157
+ --md-outlined-button-leading-space: 15px;
158
+ --md-outlined-button-label-text-color: #586878;
159
+ box-shadow: 1px 1px 1px #0000001a;
160
+ padding: 8px 16px;
161
+ font-weight: 700;
162
+ }
145
163
  }
146
164
  }
147
165
  }
@@ -171,6 +189,7 @@ export class ProjectSchedule extends ScopedElementsMixin(PageView) {
171
189
  @state() project: Project = { ...this.defaultProject }
172
190
  @state() selectedBuildingIdx: number = 0
173
191
  @state() tasks
192
+ @state() constructionTypeList = []
174
193
 
175
194
  @state() private fromDate = '2024-01-01'
176
195
  @state() private toDate = '2024-12-31'
@@ -213,13 +232,7 @@ export class ProjectSchedule extends ScopedElementsMixin(PageView) {
213
232
  <div header>
214
233
  <h2>${this.project.name}</h2>
215
234
  <div button-container>
216
- <md-elevated-button href=${`project-update/${this.project.id}`}>
217
- <md-icon slot="icon">assignment</md-icon>프로젝트 정보 수정
218
- </md-elevated-button>
219
- <md-elevated-button href=${`project-plan-management/${this.project.id}`}>
220
- <md-icon slot="icon">description</md-icon>도면 관리
221
- </md-elevated-button>
222
- <md-elevated-button href=${`project-task-update/${this.project.id}`}>
235
+ <md-elevated-button @click=${this._openUploadSchedulePopup}>
223
236
  <md-icon slot="icon">event_note</md-icon>공정표 관리
224
237
  </md-elevated-button>
225
238
  </div>
@@ -231,9 +244,7 @@ export class ProjectSchedule extends ScopedElementsMixin(PageView) {
231
244
  to-date=${new Date(this.toDate).toISOString().split('T')[0]}
232
245
  .timeScale=${this.timeScale}
233
246
  .tasks=${this.tasks}
234
- @date-range-selected=${(e: CustomEvent) => {
235
- console.log('date-range-selected', e.detail)
236
- }}
247
+ @date-range-selected=${this.onRangeSelected}
237
248
  @task-clicked=${(e: CustomEvent) => {
238
249
  console.log('task-clicked', e.detail)
239
250
  }}
@@ -253,7 +264,12 @@ export class ProjectSchedule extends ScopedElementsMixin(PageView) {
253
264
  <input type="date" name="endDate" project .value=${this.project.endDate || ''} max="9999-12-31" />
254
265
  </div>
255
266
  </div>
256
- <div button></div>
267
+ <div construction-list-container>
268
+ ${this.constructionTypeList?.map(
269
+ (constructionType: any) =>
270
+ html` <md-outlined-button id=${constructionType.id}>${constructionType.title}</md-outlined-button>`
271
+ )}
272
+ </div>
257
273
  </div>
258
274
  </div>
259
275
  `
@@ -286,6 +302,10 @@ export class ProjectSchedule extends ScopedElementsMixin(PageView) {
286
302
  }
287
303
  }
288
304
  }
305
+ scheduleTable {
306
+ id
307
+ name
308
+ }
289
309
  buildingComplex {
290
310
  id
291
311
  planXScale
@@ -333,4 +353,31 @@ export class ProjectSchedule extends ScopedElementsMixin(PageView) {
333
353
  this.fromDate = this.inputStartDate.value
334
354
  this.toDate = this.inputEndDate.value
335
355
  }
356
+
357
+ onRangeSelected(e: CustomEvent) {
358
+ const selectedFromDate = new Date(e.detail.start + 'T00:00:00.000Z')
359
+ const selectedToDate = new Date(e.detail.end + 'T23:59:59.000Z')
360
+
361
+ this.constructionTypeList = this.tasks.filter(constuction => {
362
+ const constuctionStartDate = new Date(constuction.startDate)
363
+ const constuctionEndDate = new Date(constuction.endDate)
364
+
365
+ return constuctionStartDate <= selectedToDate && constuctionEndDate >= selectedFromDate
366
+ })
367
+ }
368
+
369
+ private _openUploadSchedulePopup() {
370
+ openPopup(
371
+ html`<popup-schedule-upload
372
+ .projectId=${this.projectId}
373
+ .scheduleTable=${this.project?.scheduleTable}
374
+ @uploaded=${() => this.initProject(this.projectId)}
375
+ ></popup-schedule-upload>`,
376
+ {
377
+ backdrop: true,
378
+ size: 'medium',
379
+ title: `공정표 업로드`
380
+ }
381
+ )
382
+ }
336
383
  }
@@ -12,6 +12,7 @@ import { notify } from '@operato/layout'
12
12
  import gql from 'graphql-tag'
13
13
  import { Project } from './project-list'
14
14
  import '../lib/select2-component'
15
+ import './component/project-update-header'
15
16
 
16
17
  @customElement('project-update')
17
18
  export class ProjectUpdate extends ScopedElementsMixin(PageView) {
@@ -55,44 +56,6 @@ export class ProjectUpdate extends ScopedElementsMixin(PageView) {
55
56
  padding: 0;
56
57
  }
57
58
 
58
- div[header] {
59
- display: flex;
60
- margin: 0px 20px;
61
-
62
- h2 {
63
- flex: 0.5;
64
- color: #3f71a0;
65
- }
66
-
67
- div[button-container] {
68
- display: flex;
69
- align-items: center;
70
- justify-content: end;
71
- flex: 0.5;
72
-
73
- md-elevated-button {
74
- margin: 0px 3px;
75
-
76
- --md-elevated-button-container-height: 35px;
77
- --md-elevated-button-label-text-size: 16px;
78
- --md-elevated-button-container-color: #0595e5;
79
-
80
- --md-elevated-button-label-text-color: #fff;
81
- --md-elevated-button-hover-label-text-color: #fff;
82
- --md-elevated-button-pressed-label-text-color: #fff;
83
- --md-elevated-button-focus-label-text-color: #fff;
84
- --md-elevated-button-icon-color: #fff;
85
- --md-elevated-button-hover-icon-color: #fff;
86
- --md-elevated-button-pressed-icon-color: #fff;
87
- --md-elevated-button-focus-icon-color: #fff;
88
-
89
- &[green] {
90
- --md-elevated-button-container-color: #42b382;
91
- }
92
- }
93
- }
94
- }
95
-
96
59
  div[body] {
97
60
  display: flex;
98
61
  margin: 0px 25px 25px 25px;
@@ -266,20 +229,9 @@ export class ProjectUpdate extends ScopedElementsMixin(PageView) {
266
229
 
267
230
  render() {
268
231
  return html`
269
- <div header>
270
- <h2>프로젝트 정보 관리</h2>
271
- <div button-container>
272
- <md-elevated-button green @click=${this._saveProject}>
273
- <md-icon slot="icon">save</md-icon>정보 저장
274
- </md-elevated-button>
275
- <md-elevated-button href=${`project-plan-management/${this.project.id}`}>
276
- <md-icon slot="icon">description</md-icon>도면 관리
277
- </md-elevated-button>
278
- <md-elevated-button href=${`project-task-update/${this.project.id}`}>
279
- <md-icon slot="icon">event_note</md-icon>공정표 관리
280
- </md-elevated-button>
281
- </div>
282
- </div>
232
+ <project-update-header .projectId=${this.project.id || ''} title="프로젝트 정보 관리" @custom-click=${this._saveProject}>
233
+ </project-update-header>
234
+
283
235
  <div body>
284
236
  <div project-info>
285
237
  <h3>기본 정보</h3>
@@ -89,6 +89,10 @@ Select2Component.styles = css `
89
89
  position: relative;
90
90
  width: 300px;
91
91
  border: 1px solid #000;
92
+ border-radius: 6px;
93
+ padding: 4px 16px;
94
+ font-size: 14px;
95
+ color: var(--md-sys-color-primary);
92
96
  }
93
97
 
94
98
  div[dropdown] {
@@ -99,6 +103,8 @@ Select2Component.styles = css `
99
103
 
100
104
  div[options] {
101
105
  position: absolute;
106
+ left: 0;
107
+ top: 30px;
102
108
  width: 100%;
103
109
  border: 1px solid #ccc;
104
110
  background-color: white;
@@ -111,6 +117,10 @@ Select2Component.styles = css `
111
117
  div[option] {
112
118
  padding: 10px;
113
119
  cursor: pointer;
120
+ border-bottom: 1px solid #ccc;
121
+ }
122
+ div[option]:last-child {
123
+ border-bottom: none;
114
124
  }
115
125
 
116
126
  div[option]:hover {
@@ -129,10 +139,11 @@ Select2Component.styles = css `
129
139
  }
130
140
 
131
141
  div[tag] {
132
- background-color: #007bff;
142
+ background-color: #2e79be;
133
143
  color: white;
134
144
  padding: 5px 10px;
135
145
  border-radius: 20px;
146
+ font-size: 13px;
136
147
  display: inline-flex;
137
148
  align-items: center;
138
149
  cursor: pointer;
@@ -1 +1 @@
1
- {"version":3,"file":"select2-component.js","sourceRoot":"","sources":["../../../client/pages/lib/select2-component.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\n@customElement('select2-component')\nexport class Select2Component extends LitElement {\n static styles = css`\n div[select-container] {\n position: relative;\n width: 300px;\n border: 1px solid #000;\n }\n\n div[dropdown] {\n border: 1px solid #ccc;\n padding: 5px;\n cursor: pointer;\n }\n\n div[options] {\n position: absolute;\n width: 100%;\n border: 1px solid #ccc;\n background-color: white;\n max-height: 150px;\n overflow-y: auto;\n display: block;\n z-index: 1;\n }\n\n div[option] {\n padding: 10px;\n cursor: pointer;\n }\n\n div[option]:hover {\n background-color: #f0f0f0;\n }\n\n div[option][selected] {\n background-color: #d3f9d8;\n }\n\n div[selected-tags] {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n margin-top: 10px;\n }\n\n div[tag] {\n background-color: #007bff;\n color: white;\n padding: 5px 10px;\n border-radius: 20px;\n display: inline-flex;\n align-items: center;\n cursor: pointer;\n }\n\n span[tag-close] {\n margin-left: 8px;\n }\n `\n\n @property({ type: String }) placeholder: string = ''\n @property({ type: Array }) options: Array<{ name: string; value: string }> = []\n @property({ type: Array }) selectedValues: string[] = []\n\n @state() showOptions: boolean = false\n\n get selectedItems() {\n return this.selectedValues.map(id => this.options.find(option => option.value === id)).filter(Boolean)\n }\n\n connectedCallback() {\n super.connectedCallback()\n document.addEventListener('click', this._handleOutsideClick)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n document.removeEventListener('click', this._handleOutsideClick)\n }\n\n private _handleOutsideClick = (event: MouseEvent) => {\n const path = event.composedPath()\n if (!path.includes(this)) {\n this.showOptions = false\n }\n }\n\n private _toggleOptions() {\n this.showOptions = !this.showOptions\n }\n\n private _handleSelect(optionValue: string) {\n if (this.selectedValues.includes(optionValue)) {\n // 이미 선택된 옵션을 선택한 경우 해제\n this.selectedValues = this.selectedValues.filter(value => value !== optionValue)\n } else {\n // 선택되지 않은 옵션 추가\n this.selectedValues = [...this.selectedValues, optionValue]\n }\n\n this.showOptions = false\n this._dispatchEvent(this.selectedValues)\n }\n\n private _handleRemove(tagValue: string) {\n this.selectedValues = this.selectedValues.filter(value => value !== tagValue)\n this._dispatchEvent(this.selectedValues)\n }\n\n private _dispatchEvent(selectedValues: string[]) {\n this.dispatchEvent(\n new CustomEvent('selection-changed', {\n detail: { selectedValues }, // ID 배열을 부모로 전달\n bubbles: true,\n composed: true\n })\n )\n }\n\n render() {\n return html`\n <div select-container>\n <div tags @click=\"${this._toggleOptions}\">${this.placeholder}</div>\n ${this.showOptions\n ? html`\n <div options>\n ${this.options.map(\n option => html`\n <div\n option\n ?selected=${this.selectedValues.includes(option.value)}\n @click=${() => this._handleSelect(option.value)}\n >\n ${option.name}\n </div>\n `\n )}\n </div>\n `\n : ''}\n </div>\n\n <div selected-tags>\n ${this.selectedItems.map(\n (tag: any) => html`\n <div tag @click=${() => this._handleRemove(tag.value)}>\n ${tag!.name}\n <span tag-close>&times;</span>\n </div>\n `\n )}\n </div>\n `\n }\n}\n"]}
1
+ {"version":3,"file":"select2-component.js","sourceRoot":"","sources":["../../../client/pages/lib/select2-component.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\n@customElement('select2-component')\nexport class Select2Component extends LitElement {\n static styles = css`\n div[select-container] {\n position: relative;\n width: 300px;\n border: 1px solid #000;\n border-radius: 6px;\n padding: 4px 16px;\n font-size: 14px;\n color: var(--md-sys-color-primary);\n }\n\n div[dropdown] {\n border: 1px solid #ccc;\n padding: 5px;\n cursor: pointer;\n }\n\n div[options] {\n position: absolute;\n left: 0;\n top: 30px;\n width: 100%;\n border: 1px solid #ccc;\n background-color: white;\n max-height: 150px;\n overflow-y: auto;\n display: block;\n z-index: 1;\n }\n\n div[option] {\n padding: 10px;\n cursor: pointer;\n border-bottom: 1px solid #ccc;\n }\n div[option]:last-child {\n border-bottom: none;\n }\n\n div[option]:hover {\n background-color: #f0f0f0;\n }\n\n div[option][selected] {\n background-color: #d3f9d8;\n }\n\n div[selected-tags] {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n margin-top: 10px;\n }\n\n div[tag] {\n background-color: #2e79be;\n color: white;\n padding: 5px 10px;\n border-radius: 20px;\n font-size: 13px;\n display: inline-flex;\n align-items: center;\n cursor: pointer;\n }\n\n span[tag-close] {\n margin-left: 8px;\n }\n `\n\n @property({ type: String }) placeholder: string = ''\n @property({ type: Array }) options: Array<{ name: string; value: string }> = []\n @property({ type: Array }) selectedValues: string[] = []\n\n @state() showOptions: boolean = false\n\n get selectedItems() {\n return this.selectedValues.map(id => this.options.find(option => option.value === id)).filter(Boolean)\n }\n\n connectedCallback() {\n super.connectedCallback()\n document.addEventListener('click', this._handleOutsideClick)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n document.removeEventListener('click', this._handleOutsideClick)\n }\n\n private _handleOutsideClick = (event: MouseEvent) => {\n const path = event.composedPath()\n if (!path.includes(this)) {\n this.showOptions = false\n }\n }\n\n private _toggleOptions() {\n this.showOptions = !this.showOptions\n }\n\n private _handleSelect(optionValue: string) {\n if (this.selectedValues.includes(optionValue)) {\n // 이미 선택된 옵션을 선택한 경우 해제\n this.selectedValues = this.selectedValues.filter(value => value !== optionValue)\n } else {\n // 선택되지 않은 옵션 추가\n this.selectedValues = [...this.selectedValues, optionValue]\n }\n\n this.showOptions = false\n this._dispatchEvent(this.selectedValues)\n }\n\n private _handleRemove(tagValue: string) {\n this.selectedValues = this.selectedValues.filter(value => value !== tagValue)\n this._dispatchEvent(this.selectedValues)\n }\n\n private _dispatchEvent(selectedValues: string[]) {\n this.dispatchEvent(\n new CustomEvent('selection-changed', {\n detail: { selectedValues }, // ID 배열을 부모로 전달\n bubbles: true,\n composed: true\n })\n )\n }\n\n render() {\n return html`\n <div select-container>\n <div tags @click=\"${this._toggleOptions}\">${this.placeholder}</div>\n ${this.showOptions\n ? html`\n <div options>\n ${this.options.map(\n option => html`\n <div\n option\n ?selected=${this.selectedValues.includes(option.value)}\n @click=${() => this._handleSelect(option.value)}\n >\n ${option.name}\n </div>\n `\n )}\n </div>\n `\n : ''}\n </div>\n\n <div selected-tags>\n ${this.selectedItems.map(\n (tag: any) => html`\n <div tag @click=${() => this._handleRemove(tag.value)}>\n ${tag!.name}\n <span tag-close>&times;</span>\n </div>\n `\n )}\n </div>\n `\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ import '@material/web/icon/icon.js';