Compare commits
4 Commits
5791671d73
...
81e6c19484
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81e6c19484 | ||
|
|
d07dc8b4f7 | ||
|
|
994c4aa890 | ||
|
|
2294bd41d4 |
238
Assets/Resources/EWLK/UIToolkit/Main/EWLKEquipListContentUss.uss
Normal file
238
Assets/Resources/EWLK/UIToolkit/Main/EWLKEquipListContentUss.uss
Normal file
@@ -0,0 +1,238 @@
|
||||
/* ============================================================
|
||||
EWLKEquipListContent — 설비 목록
|
||||
필터 바 + 8컬럼 테이블
|
||||
============================================================ */
|
||||
|
||||
.ewlk-equip {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
/* ── 필터 바 ────────────────────────────────────────────── */
|
||||
.ewlk-equip__filter-bar {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 8px 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── 검색 ────────────────────────────────────────────── */
|
||||
.ewlk-equip__search-wrap {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.ewlk-equip__search-icon {
|
||||
color: rgb(180, 180, 180);
|
||||
margin-right: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ewlk-equip__search-field {
|
||||
width: 300px;
|
||||
border-width: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
font-size: 12px;
|
||||
color: rgb(160, 160, 160);
|
||||
}
|
||||
|
||||
.ewlk-equip .ewlk-equip__search-field .unity-text-input {
|
||||
border-width: 0;
|
||||
padding: 2px 0;
|
||||
background-color: rgb(255, 255, 255);
|
||||
color: rgb(30, 30, 30);
|
||||
--unity-cursor-color: rgb(80, 80, 80);
|
||||
}
|
||||
|
||||
.ewlk-equip .ewlk-equip__search-field--placeholder .unity-text-input {
|
||||
color: rgb(160, 160, 160);
|
||||
}
|
||||
|
||||
/* ── 필터 드롭다운 ──────────────────────────────────────── */
|
||||
.ewlk-equip__filter-dropdown {
|
||||
width: 100px;
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ewlk-equip .ewlk-equip__filter-dropdown .unity-base-popup-field__input {
|
||||
background-color: rgb(255, 255, 255);
|
||||
border-width: 1px;
|
||||
border-color: rgb(210, 210, 210);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ewlk-equip .ewlk-equip__filter-dropdown .unity-base-popup-field__text {
|
||||
color: rgb(77, 77, 77);
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ewlk-equip .ewlk-equip__filter-dropdown .unity-base-popup-field__arrow {
|
||||
margin-left: 2px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* ── 테이블 헤더 ──────────────────────────────────────── */
|
||||
.ewlk-equip__header {
|
||||
flex-direction: row;
|
||||
height: 32px;
|
||||
background-color: rgb(245, 245, 250);
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(220, 220, 220);
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ewlk-equip__header-cell {
|
||||
background-color: rgb(245, 245, 250);
|
||||
}
|
||||
|
||||
.ewlk-equip__header-cell .ewlk-equip__cell-label {
|
||||
-unity-font-style: bold;
|
||||
color: rgb(80, 80, 80);
|
||||
}
|
||||
|
||||
/* ── ScrollView ────────────────────────────────────────── */
|
||||
.ewlk-equip__list {
|
||||
flex-grow: 1;
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
/* ── 데이터 행 ─────────────────────────────────────────── */
|
||||
.ewlk-equip__row {
|
||||
flex-direction: row;
|
||||
height: 32px;
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(240, 240, 240);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── 셀 공통 ──────────────────────────────────────────── */
|
||||
.ewlk-equip__cell {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 2px;
|
||||
border-right-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ewlk-equip__cell-label {
|
||||
font-size: 11px;
|
||||
color: rgb(50, 50, 50);
|
||||
-unity-text-align: middle-center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ── 컬럼 너비 ────────────────────────────────────────── */
|
||||
.ewlk-equip .col-el-no { width: 35px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-equip .col-el-process { width: 100px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-equip .col-el-line { width: 70px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-equip .col-el-category { width: 80px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-equip .col-el-equipno { width: 80px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-equip .col-el-name { flex-grow: 1; }
|
||||
.ewlk-equip .col-el-status { width: 70px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-equip .col-el-control { width: 80px; flex-shrink: 0; flex-grow: 0; flex-direction: row; }
|
||||
|
||||
/* ── 상태 뱃지 (타원) ─────────────────────────────────── */
|
||||
.ewlk-equip__badge {
|
||||
font-size: 10px;
|
||||
-unity-text-align: middle-center;
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ewlk-equip__badge--ready {
|
||||
color: rgb(30, 100, 200);
|
||||
background-color: rgb(210, 230, 255);
|
||||
border-width: 1px;
|
||||
border-color: rgb(100, 160, 230);
|
||||
}
|
||||
|
||||
.ewlk-equip__badge--error {
|
||||
color: rgb(200, 50, 40);
|
||||
background-color: rgb(255, 220, 215);
|
||||
border-width: 1px;
|
||||
border-color: rgb(230, 100, 90);
|
||||
}
|
||||
|
||||
.ewlk-equip__badge--rest {
|
||||
color: rgb(160, 120, 20);
|
||||
background-color: rgb(255, 240, 200);
|
||||
border-width: 1px;
|
||||
border-color: rgb(220, 180, 60);
|
||||
}
|
||||
|
||||
.ewlk-equip__badge--planned {
|
||||
color: rgb(100, 100, 100);
|
||||
background-color: rgb(230, 230, 230);
|
||||
border-width: 1px;
|
||||
border-color: rgb(180, 180, 180);
|
||||
}
|
||||
|
||||
/* ── 제어 토글 (읽기전용) ─────────────────────────────── */
|
||||
.ewlk-equip__toggle {
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ewlk-equip__toggle-label {
|
||||
font-size: 10px;
|
||||
color: rgb(80, 80, 80);
|
||||
margin-left: 4px;
|
||||
-unity-text-align: middle-center;
|
||||
}
|
||||
|
||||
/* ── 설비명 래퍼 (이름 + +n + ∧∨) ────────────────────── */
|
||||
.ewlk-equip__name-wrap {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* +n 뱃지 (파란색 작은 글씨) */
|
||||
.ewlk-equip__sub-count {
|
||||
font-size: 10px;
|
||||
color: rgb(30, 100, 220);
|
||||
-unity-font-style: bold;
|
||||
margin-left: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 아코디언 토글 아이콘 */
|
||||
.ewlk-equip__expand-icon {
|
||||
color: rgb(120, 120, 120);
|
||||
flex-shrink: 0;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/* ── 세부 설비 아코디언 ──────────────────────────────── */
|
||||
.ewlk-equip__accordion {
|
||||
flex-direction: column;
|
||||
margin-left: 12px;
|
||||
border-left-width: 1px;
|
||||
border-color: rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
.ewlk-equip__sub-row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
padding-left: 390px;
|
||||
}
|
||||
|
||||
.ewlk-equip__sub-label {
|
||||
font-size: 11px;
|
||||
color: rgb(60, 60, 60);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d49b12c27bdf8b409c0af4d005531bc
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
unsupportedSelectorAction: 0
|
||||
@@ -1,206 +1,254 @@
|
||||
/* ──────────────────────────────────────────────────────────
|
||||
EWLKMfgOrderModalContent — 제조지시현황 모달
|
||||
────────────────────────────────────────────────────────── */
|
||||
/* ============================================================
|
||||
EWLKMfgOrderModalContent — 제조지시현황 (라이트 테마)
|
||||
탭 + 요약 바 + 11컬럼 테이블
|
||||
============================================================ */
|
||||
|
||||
/* ── 루트 컨테이너 ──────────────────────────────────────── */
|
||||
/* ── 루트 ──────────────────────────────────────────────── */
|
||||
.ewlk-mfgorder {
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
background-color: rgb(18, 22, 36);
|
||||
padding: 0;
|
||||
min-height: 400px;
|
||||
flex-direction: column;
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
/* ── 탭 바 ──────────────────────────────────────────────── */
|
||||
/* ── 탭 바 ─────────────────────────────────────────────── */
|
||||
.ewlk-mfgorder__tab-bar {
|
||||
flex-direction: row;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgb(50, 60, 90);
|
||||
margin-bottom: 0;
|
||||
padding-left: 12px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__tab {
|
||||
flex-shrink: 0;
|
||||
padding: 8px 20px;
|
||||
background-color: rgb(34, 42, 66);
|
||||
color: rgb(140, 150, 175);
|
||||
min-width: 0;
|
||||
padding: 6px 16px;
|
||||
margin-right: 4px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
border-width: 0;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgba(0, 0, 0, 0);
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
border-radius: 0;
|
||||
font-size: 13px;
|
||||
-unity-font-style: normal;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__tab:hover {
|
||||
background-color: rgb(40, 50, 78);
|
||||
color: rgb(220, 225, 240);
|
||||
font-size: 12px;
|
||||
color: rgb(120, 120, 120);
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__tab--active {
|
||||
color: rgb(220, 225, 240);
|
||||
border-bottom-color: rgb(0, 140, 255);
|
||||
color: rgb(30, 100, 220);
|
||||
border-bottom-color: rgb(30, 100, 220);
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
/* ── 요약 바 ─────────────────────────────────────────────── */
|
||||
.ewlk-mfgorder__tab:hover {
|
||||
color: rgb(60, 60, 60);
|
||||
}
|
||||
|
||||
/* ── 요약 바 ────────────────────────────────────────────── */
|
||||
.ewlk-mfgorder__summary-bar {
|
||||
flex-direction: row;
|
||||
background-color: rgb(26, 32, 50);
|
||||
padding: 10px 16px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: rgb(50, 60, 90);
|
||||
padding: 12px 16px;
|
||||
border-width: 1px;
|
||||
border-color: rgb(230, 230, 230);
|
||||
border-radius: 4px;
|
||||
margin: 8px 12px;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__summary-item {
|
||||
.ewlk-mfgorder__summary-left {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
padding-right: 24px;
|
||||
border-right-width: 1px;
|
||||
border-color: rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__summary-right {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
/* ── 바 아이템 (가로: 라벨 | 값 | 프로그레스 바) ──────── */
|
||||
.ewlk-mfgorder__bar-item {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
margin-right: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__summary-title {
|
||||
color: rgb(140, 150, 175);
|
||||
.ewlk-mfgorder__bar-item-title {
|
||||
font-size: 11px;
|
||||
margin-right: 8px;
|
||||
color: rgb(100, 100, 100);
|
||||
width: 110px;
|
||||
flex-shrink: 0;
|
||||
-unity-text-align: middle-left;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__summary-value {
|
||||
color: rgb(220, 225, 240);
|
||||
.ewlk-mfgorder__bar-item-value {
|
||||
font-size: 13px;
|
||||
-unity-font-style: bold;
|
||||
color: rgb(33, 33, 33);
|
||||
width: 120px;
|
||||
flex-shrink: 0;
|
||||
-unity-text-align: middle-left;
|
||||
}
|
||||
|
||||
/* ── 설비가동률 행 ───────────────────────────────────────── */
|
||||
.ewlk-mfgorder__equip-row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: rgb(26, 32, 50);
|
||||
padding: 6px 16px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: rgb(50, 60, 90);
|
||||
.ewlk-mfgorder__bar-item .ewlk-mfgorder__progress-wrap {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__equip-title {
|
||||
color: rgb(140, 150, 175);
|
||||
font-size: 11px;
|
||||
/* ── 설비 가동율 값 세로 스택 (%+대수) ──────────────────── */
|
||||
.ewlk-mfgorder__equip-value-stack {
|
||||
flex-direction: column;
|
||||
width: 120px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 10px;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__equip-rate {
|
||||
color: rgb(220, 225, 240);
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__count {
|
||||
color: rgb(140, 150, 175);
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 12px;
|
||||
font-size: 11px;
|
||||
color: rgb(30, 100, 220);
|
||||
-unity-font-style: bold;
|
||||
-unity-text-align: middle-left;
|
||||
}
|
||||
|
||||
/* ── 프로그레스 바 (공통) ────────────────────────────────── */
|
||||
/* ── 검색창 ────────────────────────────────────────────── */
|
||||
.ewlk-mfgorder__search-row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin: 4px 12px 8px 12px;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__search-icon {
|
||||
color: rgb(180, 180, 180);
|
||||
margin-right: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__search-field {
|
||||
width: 25%;
|
||||
border-width: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
font-size: 12px;
|
||||
color: rgb(160, 160, 160);
|
||||
}
|
||||
|
||||
/* 검색창 다크 테마 오버라이드 (3단계 specificity) */
|
||||
.ewlk-mfgorder .ewlk-mfgorder__search-row .ewlk-mfgorder__search-field .unity-text-input {
|
||||
border-width: 0;
|
||||
padding: 2px 0;
|
||||
background-color: rgb(255, 255, 255);
|
||||
color: rgb(30, 30, 30);
|
||||
--unity-cursor-color: rgb(80, 80, 80);
|
||||
}
|
||||
|
||||
/* placeholder 상태 — 텍스트 회색 */
|
||||
.ewlk-mfgorder .ewlk-mfgorder__search-row .ewlk-mfgorder__search-field--placeholder .unity-text-input {
|
||||
color: rgb(160, 160, 160);
|
||||
}
|
||||
|
||||
/* ── 프로그레스 바 공통 ──────────────────────────────────── */
|
||||
.ewlk-mfgorder__progress-wrap {
|
||||
flex-grow: 1;
|
||||
height: 10px;
|
||||
background-color: rgb(50, 60, 90);
|
||||
border-radius: 4px;
|
||||
height: 12px;
|
||||
background-color: rgb(240, 240, 240);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__progress-fill {
|
||||
height: 100%;
|
||||
background-color: rgb(0, 200, 100);
|
||||
border-radius: 4px;
|
||||
width: 0%;
|
||||
background-color: rgb(180, 200, 230);
|
||||
border-radius: 2px;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
/* ── 테이블 헤더 ─────────────────────────────────────────── */
|
||||
/* ── 테이블 헤더 ──────────────────────────────────────── */
|
||||
.ewlk-mfgorder__header {
|
||||
flex-direction: row;
|
||||
background-color: rgb(34, 42, 66);
|
||||
min-height: 32px;
|
||||
background-color: rgb(245, 245, 250);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: rgb(50, 60, 90);
|
||||
padding: 0 4px;
|
||||
border-color: rgb(220, 220, 220);
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__header-cell {
|
||||
color: rgb(140, 150, 175);
|
||||
-unity-text-align: middle-center;
|
||||
font-size: 11px;
|
||||
-unity-font-style: bold;
|
||||
-unity-text-align: middle-center;
|
||||
padding: 6px 4px;
|
||||
color: rgb(80, 80, 80);
|
||||
padding: 4px 2px;
|
||||
border-right-width: 1px;
|
||||
border-right-color: rgb(50, 60, 90);
|
||||
border-color: rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
/* ── ListView ────────────────────────────────────────────── */
|
||||
.ewlk-mfgorder__header-cell:last-child {
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
/* ── ListView ──────────────────────────────────────────── */
|
||||
.ewlk-mfgorder__list {
|
||||
flex-grow: 1;
|
||||
background-color: rgb(18, 22, 36);
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
/* ── 데이터 행 ───────────────────────────────────────────── */
|
||||
/* ── 데이터 행 ─────────────────────────────────────────── */
|
||||
.ewlk-mfgorder__row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
padding: 0 4px;
|
||||
min-height: 28px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: rgb(50, 60, 90);
|
||||
border-color: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
/* ── 셀 공통 ──────────────────────────────────────────── */
|
||||
.ewlk-mfgorder__cell {
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
padding: 0 4px;
|
||||
align-items: center;
|
||||
padding: 2px 4px;
|
||||
border-right-width: 1px;
|
||||
border-right-color: rgb(50, 60, 90);
|
||||
overflow: hidden;
|
||||
border-color: rgb(245, 245, 245);
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__cell:last-child {
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
.ewlk-mfgorder__cell-label {
|
||||
color: rgb(220, 225, 240);
|
||||
font-size: 12px;
|
||||
-unity-text-align: middle-left;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── 달성률 셀 내부 ─────────────────────────────────────── */
|
||||
.ewlk-mfgorder__stopped {
|
||||
color: rgb(160, 165, 185);
|
||||
font-size: 11px;
|
||||
color: rgb(50, 50, 50);
|
||||
-unity-text-align: middle-center;
|
||||
background-color: rgb(60, 65, 85);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 품목명 줄임표시 */
|
||||
.ewlk-mfgorder__cell-label--ellipsis {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* ── 진척률 셀 ────────────────────────────────────────── */
|
||||
.ewlk-mfgorder__pct {
|
||||
color: rgb(220, 225, 240);
|
||||
font-size: 11px;
|
||||
-unity-text-align: middle-right;
|
||||
color: rgb(50, 50, 50);
|
||||
-unity-text-align: middle-center;
|
||||
min-width: 30px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 4px;
|
||||
width: 36px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ── 컬럼 너비 ───────────────────────────────────────────── */
|
||||
.col-equip { width: 100px; flex-shrink: 0; }
|
||||
.col-order { width: 120px; flex-shrink: 0; }
|
||||
.col-item-code { width: 110px; flex-shrink: 0; }
|
||||
.col-item-name { flex-grow: 1; }
|
||||
.col-time { width: 80px; flex-shrink: 0; }
|
||||
.col-target { width: 90px; flex-shrink: 0; }
|
||||
.col-achievement { width: 150px; flex-shrink: 0; flex-direction: row; align-items: center; }
|
||||
/* ── 컬럼 너비 ────────────────────────────────────────── */
|
||||
.col-no { width: 35px; flex-shrink: 0; }
|
||||
.col-equip { width: 110px; flex-shrink: 0; }
|
||||
.col-order { width: 100px; flex-shrink: 0; }
|
||||
.col-item-code { width: 100px; flex-shrink: 0; }
|
||||
.col-item-name { width: 90px; flex-shrink: 0; }
|
||||
.col-start-time { width: 90px; flex-shrink: 0; }
|
||||
.col-end-time { width: 90px; flex-shrink: 0; }
|
||||
.col-work-time { width: 80px; flex-shrink: 0; }
|
||||
.col-plan-prod { width: 90px; flex-shrink: 0; }
|
||||
.col-actual-prod { width: 80px; flex-shrink: 0; }
|
||||
.col-achievement { flex-grow: 1; flex-direction: row; align-items: center; padding-left: 4px; padding-right: 4px; }
|
||||
|
||||
/* 진척률 셀 내부 프로그레스 바 */
|
||||
.col-achievement .ewlk-mfgorder__progress-wrap {
|
||||
flex-grow: 1;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
@@ -1,128 +1,150 @@
|
||||
/* ============================================================
|
||||
EWLKOverViewModalContent — OverView 모달 테이블 스타일
|
||||
|
||||
레이아웃 구조:
|
||||
.ewlk-overview (테이블 루트, flex-column)
|
||||
.ewlk-overview__header (헤더 행, flex-row)
|
||||
.ewlk-overview__header-cell (헤더 셀)
|
||||
.ewlk-overview__group × 3 (작업장 그룹, flex-row)
|
||||
.ewlk-overview__cell--name (작업장 이름, 2행 span)
|
||||
.ewlk-overview__rows (월간+일간, flex-column)
|
||||
.ewlk-overview__row × 2 (데이터 행, flex-row)
|
||||
.ewlk-overview__cell--period
|
||||
.ewlk-overview__cell--data × 3
|
||||
.ewlk-overview__cell--type
|
||||
EWLKOverViewModalContent — OverView 컨텐츠 스타일
|
||||
3개 컬럼 (작업장별), 각 컬럼에 월간/일간 섹션 + 프로그레스 바
|
||||
============================================================ */
|
||||
|
||||
/* ── 테이블 루트 ─────────────────────────────────────────── */
|
||||
/* ── 루트 ──────────────────────────────────────────────── */
|
||||
.ewlk-overview {
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
background-color: rgb(255, 255, 255);
|
||||
border-width: 1px;
|
||||
border-color: rgb(140, 140, 140);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* ── 헤더 행 ─────────────────────────────────────────────── */
|
||||
.ewlk-overview__header {
|
||||
/* ── 3컬럼 컨테이너 ────────────────────────────────────── */
|
||||
.ewlk-overview__columns {
|
||||
flex-direction: row;
|
||||
min-height: 36px;
|
||||
background-color: rgb(200, 206, 224);
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
/* ── 헤더 셀 공통 ────────────────────────────────────────── */
|
||||
.ewlk-overview__header-cell {
|
||||
-unity-text-align: middle-center;
|
||||
font-size: 12px;
|
||||
-unity-font-style: bold;
|
||||
color: rgb(20, 20, 20);
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
border-right-width: 1px;
|
||||
border-color: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
/* ── 작업장 그룹 ─────────────────────────────────────────── */
|
||||
.ewlk-overview__group {
|
||||
flex-direction: row;
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
/* ── 행 묶음 컨테이너 ────────────────────────────────────── */
|
||||
.ewlk-overview__rows {
|
||||
flex-grow: 1;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* ── 작업장 컬럼 ────────────────────────────────────────── */
|
||||
.ewlk-overview__column {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
flex-direction: column;
|
||||
border-width: 1px;
|
||||
border-color: rgb(230, 230, 230);
|
||||
border-radius: 4px;
|
||||
margin-right: 12px;
|
||||
padding: 12px;
|
||||
background-color: rgb(252, 252, 252);
|
||||
}
|
||||
|
||||
/* ── 데이터 행 ───────────────────────────────────────────── */
|
||||
.ewlk-overview__row {
|
||||
flex-direction: row;
|
||||
min-height: 36px;
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(200, 200, 200);
|
||||
.ewlk-overview__column:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ewlk-overview__rows .ewlk-overview__row:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
/* ── 공통 셀 ─────────────────────────────────────────────── */
|
||||
.ewlk-overview__cell {
|
||||
-unity-text-align: middle-center;
|
||||
font-size: 12px;
|
||||
color: rgb(20, 20, 20);
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right-width: 1px;
|
||||
border-color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
/* ── 셀 종류별 너비 / 배경 ──────────────────────────────── */
|
||||
|
||||
/* 작업장 이름 (헤더의 '구분' + 그룹의 작업장명 — 동일 너비) */
|
||||
.ewlk-overview__cell--name {
|
||||
width: 140px;
|
||||
flex-shrink: 0;
|
||||
/* ── 컬럼 타이틀 ────────────────────────────────────────── */
|
||||
.ewlk-overview__column-title {
|
||||
font-size: 13px;
|
||||
-unity-font-style: bold;
|
||||
background-color: rgb(232, 235, 248);
|
||||
border-right-color: rgb(140, 140, 140);
|
||||
color: rgb(33, 33, 33);
|
||||
-unity-text-align: middle-center;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(220, 220, 220);
|
||||
}
|
||||
|
||||
/* 월간/일간 구분 열 */
|
||||
.ewlk-overview__cell--period {
|
||||
width: 50px;
|
||||
flex-shrink: 0;
|
||||
background-color: rgb(245, 246, 252);
|
||||
border-right-color: rgb(180, 180, 180);
|
||||
}
|
||||
|
||||
/* 목표수량·현시점계획·실적수량 (동등 너비 분할) */
|
||||
.ewlk-overview__cell--data {
|
||||
/* ── 기간 섹션 (월간/일간) — 각각 50% 높이 ──────────────── */
|
||||
.ewlk-overview__section {
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
/* 구분 (제조/생산) — 마지막 열, 오른쪽 테두리 제거 */
|
||||
.ewlk-overview__cell--type {
|
||||
width: 60px;
|
||||
/* ── 기간 라벨 ──────────────────────────────────────────── */
|
||||
.ewlk-overview__period-label {
|
||||
font-size: 12px;
|
||||
-unity-font-style: bold;
|
||||
color: rgb(100, 100, 100);
|
||||
margin-bottom: 4px;
|
||||
-unity-text-align: middle-center;
|
||||
}
|
||||
|
||||
/* ── 섹션 내 구분선 ──────────────────────────────────────── */
|
||||
.ewlk-overview__divider {
|
||||
height: 1px;
|
||||
background-color: rgb(230, 230, 230);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ── 데이터 행 (가로: 라벨 | 값 | 프로그레스 바) ──────── */
|
||||
.ewlk-overview__data-row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* ── 라벨 + 값 영역 ──────────────────────────────────── */
|
||||
.ewlk-overview__label-row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
border-right-width: 0;
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
/* 헤더의 name/period/type 셀도 동일 너비 클래스 공유 */
|
||||
.ewlk-overview__header .ewlk-overview__cell--name {
|
||||
background-color: rgb(200, 206, 224);
|
||||
border-right-color: rgb(140, 140, 140);
|
||||
.ewlk-overview__data-name {
|
||||
font-size: 11px;
|
||||
color: rgb(100, 100, 100);
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
-unity-text-align: middle-left;
|
||||
}
|
||||
|
||||
.ewlk-overview__header .ewlk-overview__cell--period {
|
||||
background-color: rgb(200, 206, 224);
|
||||
.ewlk-overview__data-value {
|
||||
font-size: 12px;
|
||||
-unity-font-style: bold;
|
||||
color: rgb(33, 33, 33);
|
||||
flex-grow: 1;
|
||||
-unity-text-align: middle-left;
|
||||
}
|
||||
|
||||
.ewlk-overview__header .ewlk-overview__cell--type {
|
||||
border-right-width: 0;
|
||||
/* 목표수량 값 — 검정 */
|
||||
.ewlk-overview__value--target {
|
||||
color: rgb(33, 33, 33);
|
||||
}
|
||||
|
||||
/* 현시점 계획 값 — 파랑 */
|
||||
.ewlk-overview__value--plan {
|
||||
color: rgb(40, 100, 200);
|
||||
}
|
||||
|
||||
/* 실적수량 값 — 빨강 */
|
||||
.ewlk-overview__value--actual {
|
||||
color: rgb(200, 60, 50);
|
||||
}
|
||||
|
||||
/* ── 프로그레스 바 배경 ──────────────────────────────────── */
|
||||
.ewlk-overview__bar-bg {
|
||||
flex-grow: 1;
|
||||
height: 14px;
|
||||
background-color: rgb(240, 240, 240);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── 프로그레스 바 채움 ──────────────────────────────────── */
|
||||
.ewlk-overview__bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
width: 0;
|
||||
transition-property: width;
|
||||
transition-duration: 0.3s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
/* 목표수량 — 연한 회색 */
|
||||
.ewlk-overview__bar--target {
|
||||
background-color: rgb(200, 200, 210);
|
||||
}
|
||||
|
||||
/* 현시점 계획 — 연한 파랑 */
|
||||
.ewlk-overview__bar--plan {
|
||||
background-color: rgb(180, 200, 230);
|
||||
}
|
||||
|
||||
/* 실적수량 — 연한 빨강 */
|
||||
.ewlk-overview__bar--actual {
|
||||
background-color: rgb(230, 190, 185);
|
||||
}
|
||||
|
||||
155
Assets/Resources/EWLK/UIToolkit/Main/EWLKPackOrderContentUss.uss
Normal file
155
Assets/Resources/EWLK/UIToolkit/Main/EWLKPackOrderContentUss.uss
Normal file
@@ -0,0 +1,155 @@
|
||||
/* ============================================================
|
||||
EWLKPackOrderContent — 충/포장 지시현황
|
||||
필터 바 + 14컬럼 테이블
|
||||
============================================================ */
|
||||
|
||||
.ewlk-packorder {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
/* ── 필터 바 ────────────────────────────────────────────── */
|
||||
.ewlk-packorder__filter-bar {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 8px 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── 검색 래퍼 ──────────────────────────────────────────── */
|
||||
.ewlk-packorder__search-wrap {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin: 4px 12px 8px 12px;
|
||||
}
|
||||
|
||||
.ewlk-packorder__search-icon {
|
||||
color: rgb(180, 180, 180);
|
||||
margin-right: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ewlk-packorder__search-field {
|
||||
width: 300px;
|
||||
border-width: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
font-size: 12px;
|
||||
color: rgb(160, 160, 160);
|
||||
}
|
||||
|
||||
/* 검색창 다크 테마 오버라이드 (3단계 specificity) */
|
||||
.ewlk-packorder .ewlk-packorder__filter-bar .ewlk-packorder__search-field .unity-text-input {
|
||||
border-width: 0;
|
||||
padding: 2px 0;
|
||||
background-color: rgb(255, 255, 255);
|
||||
color: rgb(30, 30, 30);
|
||||
--unity-cursor-color: rgb(80, 80, 80);
|
||||
}
|
||||
|
||||
/* placeholder 상태 — 텍스트 회색 */
|
||||
.ewlk-packorder .ewlk-packorder__filter-bar .ewlk-packorder__search-field--placeholder .unity-text-input {
|
||||
color: rgb(160, 160, 160);
|
||||
}
|
||||
|
||||
/* ── 필터 드롭다운 다크 테마 오버라이드 ──────────────── */
|
||||
.ewlk-packorder__filter-dropdown {
|
||||
width: 100px;
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ewlk-packorder .ewlk-packorder__filter-dropdown .unity-base-popup-field__input {
|
||||
background-color: rgb(255, 255, 255);
|
||||
border-width: 1px;
|
||||
border-color: rgb(210, 210, 210);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ewlk-packorder .ewlk-packorder__filter-dropdown .unity-base-popup-field__text {
|
||||
color: rgb(77, 77, 77);
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ewlk-packorder .ewlk-packorder__filter-dropdown .unity-base-popup-field__arrow {
|
||||
margin-left: 2px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* ── 테이블 헤더 ──────────────────────────────────────── */
|
||||
.ewlk-packorder__header {
|
||||
flex-direction: row;
|
||||
height: 32px;
|
||||
background-color: rgb(245, 245, 250);
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(220, 220, 220);
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ewlk-packorder__header-cell {
|
||||
background-color: rgb(245, 245, 250);
|
||||
}
|
||||
|
||||
.ewlk-packorder__header-cell .ewlk-packorder__cell-label {
|
||||
-unity-font-style: bold;
|
||||
color: rgb(80, 80, 80);
|
||||
}
|
||||
|
||||
/* ── ScrollView ────────────────────────────────────────── */
|
||||
.ewlk-packorder__list {
|
||||
flex-grow: 1;
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
/* ── 데이터 행 ─────────────────────────────────────────── */
|
||||
.ewlk-packorder__row {
|
||||
flex-direction: row;
|
||||
height: 28px;
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(240, 240, 240);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── 셀 공통 ──────────────────────────────────────────── */
|
||||
.ewlk-packorder__cell {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 1px;
|
||||
border-right-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ewlk-packorder__cell-label {
|
||||
font-size: 10px;
|
||||
color: rgb(50, 50, 50);
|
||||
-unity-text-align: middle-center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ewlk-packorder__cell-label--ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* ── 컬럼 너비 (PackOrder 전용) ──────────────────────── */
|
||||
.ewlk-packorder .col-no { width: 30px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-floor { width: 45px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-line { width: 55px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-equip { width: 100px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-order { width: 85px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-item-code { width: 80px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-item-name { width: 70px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-start-time { width: 80px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-end-time { width: 80px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-work-time { width: 65px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-capa { width: 60px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-plan-prod { width: 85px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-actual-prod { width: 75px; flex-shrink: 0; flex-grow: 0; }
|
||||
.ewlk-packorder .col-achievement { flex-grow: 1; }
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 695417d44a37d7443b0ae3d1db4b01d2
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
unsupportedSelectorAction: 0
|
||||
@@ -0,0 +1,62 @@
|
||||
/* ============================================================
|
||||
EWLKWorkExplorerContent — 작업 탐색기 컨텐츠 스타일
|
||||
설비 상태 + 계획 현황 테이블
|
||||
============================================================ */
|
||||
|
||||
.ewlk-work-explorer {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
background-color: rgb(255, 255, 255);
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
/* ── 섹션 타이틀 ──────────────────────────────────────── */
|
||||
.ewlk-work-explorer__section-title {
|
||||
font-size: 14px;
|
||||
-unity-font-style: bold;
|
||||
color: rgb(33, 33, 33);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ── 테이블 ────────────────────────────────────────── */
|
||||
.ewlk-work-explorer__table {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ── 행 ────────────────────────────────────────────── */
|
||||
.ewlk-work-explorer__row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
min-height: 36px;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.ewlk-work-explorer__row--header {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
/* ── 셀 ────────────────────────────────────────────── */
|
||||
.ewlk-work-explorer__cell {
|
||||
font-size: 12px;
|
||||
color: rgb(66, 66, 66);
|
||||
-unity-text-align: middle-center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.ewlk-work-explorer__cell--header {
|
||||
font-size: 11px;
|
||||
-unity-font-style: bold;
|
||||
color: rgb(100, 100, 100);
|
||||
}
|
||||
|
||||
/* ── 구분선 ────────────────────────────────────────── */
|
||||
.ewlk-work-explorer__divider {
|
||||
height: 1px;
|
||||
background-color: rgb(224, 224, 224);
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34082d37e6fb90744ad7e66fab5765eb
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
unsupportedSelectorAction: 0
|
||||
71
Assets/Scripts/EnglewoodLAB/Data/EWLKEquipListData.cs
Normal file
71
Assets/Scripts/EnglewoodLAB/Data/EWLKEquipListData.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UVC.EnglewoodLAB.Data
|
||||
{
|
||||
/// <summary>설비 상태</summary>
|
||||
public enum EWLKEquipStatus
|
||||
{
|
||||
/// <summary>준비 (파랑)</summary>
|
||||
Ready,
|
||||
/// <summary>이상 (빨강)</summary>
|
||||
Error,
|
||||
/// <summary>휴식 (노랑)</summary>
|
||||
Rest,
|
||||
/// <summary>계획정지 (회색)</summary>
|
||||
PlannedStop
|
||||
}
|
||||
|
||||
/// <summary>설비 목록 행 데이터</summary>
|
||||
public class EWLKEquipListRowData
|
||||
{
|
||||
/// <summary>공정 구분</summary>
|
||||
public string Process { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>설비 라인</summary>
|
||||
public string Line { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>설비 분류</summary>
|
||||
public string Category { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>설비 번호</summary>
|
||||
public string EquipNo { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>설비명</summary>
|
||||
public string EquipName { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>상태</summary>
|
||||
public EWLKEquipStatus Status { get; internal set; } = EWLKEquipStatus.Ready;
|
||||
|
||||
/// <summary>가동 여부 (제어 토글)</summary>
|
||||
public bool IsRunning { get; internal set; }
|
||||
|
||||
/// <summary>세부 설비명 목록 (충/포장설비에 연결된 하위 설비)</summary>
|
||||
public List<string> SubEquipments { get; internal set; } = new();
|
||||
|
||||
/// <summary>세부 설비 존재 여부</summary>
|
||||
public bool HasSubEquipments => SubEquipments.Count > 0;
|
||||
|
||||
/// <summary>플레이스홀더 행 생성</summary>
|
||||
public static EWLKEquipListRowData CreatePlaceholder()
|
||||
{
|
||||
return new EWLKEquipListRowData
|
||||
{
|
||||
Process = "-",
|
||||
Line = "-",
|
||||
Category = "-",
|
||||
EquipNo = "-",
|
||||
EquipName = "-",
|
||||
Status = EWLKEquipStatus.Ready,
|
||||
IsRunning = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>설비 목록 데이터</summary>
|
||||
public class EWLKEquipListData
|
||||
{
|
||||
/// <summary>설비 행 목록</summary>
|
||||
public List<EWLKEquipListRowData> Rows { get; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c70eeb78b5b62e4998d2a7bf6676e1d
|
||||
@@ -3,82 +3,94 @@ using System.Collections.Generic;
|
||||
|
||||
namespace UVC.EnglewoodLAB.Data
|
||||
{
|
||||
/// <summary>제조지시현황 요약 데이터 (라인별)</summary>
|
||||
/// <summary>제조지시현황 요약 데이터</summary>
|
||||
public class EWLKMfgOrderSummaryData
|
||||
{
|
||||
/// <summary>공장구분 (탭 레이블로 사용)</summary>
|
||||
public string FactoryName { get; set; } = string.Empty;
|
||||
/// <summary>공장구분</summary>
|
||||
public string FactoryName { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>전체 제조 목표량</summary>
|
||||
public string TotalTarget { get; set; } = string.Empty;
|
||||
public string TotalTarget { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>실제 실적량</summary>
|
||||
public string TotalActual { get; set; } = string.Empty;
|
||||
public string TotalActual { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>전체 제조 진척률 (0~100)</summary>
|
||||
public float TotalProgress { get; set; }
|
||||
public float TotalProgress { get; internal set; }
|
||||
|
||||
/// <summary>일 설비가동률 가중대수% (0~100)</summary>
|
||||
public float EquipRate { get; set; }
|
||||
public float EquipRate { get; internal set; }
|
||||
|
||||
/// <summary>가동 중 설비 수</summary>
|
||||
public int ActiveCount { get; set; }
|
||||
public int ActiveCount { get; internal set; }
|
||||
|
||||
/// <summary>전체 설비 수</summary>
|
||||
public int TotalCount { get; set; }
|
||||
public int TotalCount { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>설비별 제조지시 행 데이터</summary>
|
||||
public class EWLKMfgOrderRowData
|
||||
{
|
||||
/// <summary>설비명</summary>
|
||||
public string EquipName { get; set; } = string.Empty;
|
||||
public string EquipName { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>지시번호</summary>
|
||||
public string OrderNo { get; set; } = string.Empty;
|
||||
public string OrderNo { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>품목코드</summary>
|
||||
public string ItemCode { get; set; } = string.Empty;
|
||||
public string ItemCode { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>품목명</summary>
|
||||
public string ItemName { get; set; } = string.Empty;
|
||||
public string ItemName { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>시작시간</summary>
|
||||
public string StartTime { get; set; } = string.Empty;
|
||||
public string StartTime { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>목표량</summary>
|
||||
public string Target { get; set; } = string.Empty;
|
||||
/// <summary>종료시간</summary>
|
||||
public string EndTime { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>달성률 (0~100). IsStopped가 true면 사용되지 않음</summary>
|
||||
public float Achievement { get; set; }
|
||||
/// <summary>총 작업 시간</summary>
|
||||
public string TotalWorkTime { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>계획 정지 여부 (true면 달성률 대신 "계획 정지" 표시)</summary>
|
||||
public bool IsStopped { get; set; }
|
||||
/// <summary>계획 생산량(kg)</summary>
|
||||
public string PlanProduction { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>실 생산량(kg)</summary>
|
||||
public string ActualProduction { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>달성률/진척률 (0~100)</summary>
|
||||
public float Achievement { get; internal set; }
|
||||
|
||||
/// <summary>계획 정지 여부</summary>
|
||||
public bool IsStopped { get; internal set; }
|
||||
|
||||
/// <summary>플레이스홀더 행을 생성합니다.</summary>
|
||||
public static EWLKMfgOrderRowData CreatePlaceholder()
|
||||
{
|
||||
return new EWLKMfgOrderRowData
|
||||
{
|
||||
EquipName = "-",
|
||||
OrderNo = "-",
|
||||
ItemCode = "-",
|
||||
ItemName = "-",
|
||||
StartTime = "-",
|
||||
EndTime = "-",
|
||||
TotalWorkTime = "-",
|
||||
PlanProduction = "-",
|
||||
ActualProduction = "-",
|
||||
Achievement = 0f,
|
||||
IsStopped = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>공장 라인별 제조지시 데이터 (요약 + 행 목록)</summary>
|
||||
/// <summary>제조지시현황 데이터 (요약 + 행 목록)</summary>
|
||||
public class EWLKMfgOrderLineData
|
||||
{
|
||||
/// <summary>기본 라인 이름 (MQTT summary 수신 전 초기값)</summary>
|
||||
public string DefaultName { get; }
|
||||
|
||||
/// <summary>요약 데이터</summary>
|
||||
public EWLKMfgOrderSummaryData Summary { get; } = new();
|
||||
|
||||
/// <summary>설비별 행 목록 (ListView itemsSource)</summary>
|
||||
/// <summary>설비별 행 목록</summary>
|
||||
public List<EWLKMfgOrderRowData> Rows { get; } = new();
|
||||
|
||||
/// <param name="defaultName">MQTT 수신 전 초기 탭 레이블</param>
|
||||
public EWLKMfgOrderLineData(string defaultName) => DefaultName = defaultName;
|
||||
}
|
||||
|
||||
/// <summary>제조지시현황 전체 데이터 (2개 라인)</summary>
|
||||
public class EWLKMfgOrderData
|
||||
{
|
||||
/// <summary>라인 1</summary>
|
||||
public EWLKMfgOrderLineData Line1 { get; } = new("라인 1");
|
||||
|
||||
/// <summary>라인 2</summary>
|
||||
public EWLKMfgOrderLineData Line2 { get; } = new("라인 2");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,105 +8,53 @@ namespace UVC.EnglewoodLAB.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 제조지시현황 데이터를 MQTT로 수신하는 서비스.
|
||||
/// 2개 라인의 요약 및 설비 행 데이터를 구독합니다.
|
||||
/// DataRepository.Instance.MqttReceiver(공유 수신기)를 사용합니다.
|
||||
/// 요약 및 설비 행 데이터를 구독합니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// MQTT 토픽 구조:
|
||||
/// ewlk/mfgorder/line1/summary — 라인1 요약 (DataObject)
|
||||
/// ewlk/mfgorder/line1/rows — 라인1 설비 행 배열 (DataArray)
|
||||
/// ewlk/mfgorder/line2/summary — 라인2 요약 (DataObject)
|
||||
/// ewlk/mfgorder/line2/rows — 라인2 설비 행 배열 (DataArray)
|
||||
///
|
||||
/// Summary JSON 스키마:
|
||||
/// <code>
|
||||
/// {
|
||||
/// "factory_name": "2공장 제조1",
|
||||
/// "total_target": "47,910.2 Kg",
|
||||
/// "total_actual": "38,889.0 Kg",
|
||||
/// "total_progress": 34.2,
|
||||
/// "equip_rate": 0.0,
|
||||
/// "active_count": 0,
|
||||
/// "total_count": 47
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// Rows JSON 스키마 (배열):
|
||||
/// <code>
|
||||
/// [
|
||||
/// {
|
||||
/// "equip_name": "UHM50",
|
||||
/// "order_no": "-",
|
||||
/// "item_code": "-",
|
||||
/// "item_name": "-",
|
||||
/// "start_time": "-",
|
||||
/// "target": "-",
|
||||
/// "achievement": 0.0,
|
||||
/// "status": "stopped"
|
||||
/// },
|
||||
/// ...
|
||||
/// ]
|
||||
/// </code>
|
||||
/// MQTT 토픽:
|
||||
/// ewlk/mfgorder/line1/summary — 요약 (DataObject)
|
||||
/// ewlk/mfgorder/line1/rows — 설비 행 배열 (DataArray)
|
||||
/// </remarks>
|
||||
public class EWLKMfgOrderMqttService : IDisposable
|
||||
{
|
||||
// ── MQTT 토픽 ──────────────────────────────────────────────
|
||||
private const string TopicLine1Summary = "ewlk/mfgorder/line1/summary";
|
||||
private const string TopicLine1Rows = "ewlk/mfgorder/line1/rows";
|
||||
private const string TopicLine2Summary = "ewlk/mfgorder/line2/summary";
|
||||
private const string TopicLine2Rows = "ewlk/mfgorder/line2/rows";
|
||||
private const string TopicSummary = "ewlk/mfgorder/line1/summary";
|
||||
private const string TopicRows = "ewlk/mfgorder/line1/rows";
|
||||
|
||||
// ── DataMask ──────────────────────────────────────────────
|
||||
private static readonly DataMask s_SummaryMask = CreateSummaryMask();
|
||||
private static readonly DataMask s_RowMask = CreateRowMask();
|
||||
|
||||
private bool _subscribed;
|
||||
private bool _disposed;
|
||||
|
||||
// ── 공개 API ──────────────────────────────────────────────
|
||||
|
||||
/// <summary>가장 최근에 수신한 제조지시 데이터</summary>
|
||||
public EWLKMfgOrderData CurrentData { get; } = new();
|
||||
public EWLKMfgOrderLineData CurrentData { get; } = new();
|
||||
|
||||
/// <summary>라인 데이터(요약 또는 행)가 갱신될 때 발생 — 메인 스레드에서 호출됩니다.</summary>
|
||||
public event Action<EWLKMfgOrderData>? OnDataUpdated;
|
||||
/// <summary>데이터 갱신 시 발생 — 메인 스레드에서 호출됩니다.</summary>
|
||||
public event Action<EWLKMfgOrderLineData>? OnDataUpdated;
|
||||
|
||||
// ── 구독 관리 ──────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// DataRepository 공유 수신기에 4개 토픽을 등록합니다.
|
||||
/// MqttReceiver.Start()는 EWLKSceneMain에서 호출합니다.
|
||||
/// </summary>
|
||||
/// <summary>MQTT 토픽을 구독합니다.</summary>
|
||||
public void Subscribe()
|
||||
{
|
||||
if (_subscribed || _disposed) return;
|
||||
_subscribed = true;
|
||||
|
||||
var receiver = DataRepository.Instance.MqttReceiver;
|
||||
receiver.Add(BuildSummaryConfig(TopicLine1Summary,
|
||||
data => UpdateSummary(CurrentData.Line1, data)));
|
||||
receiver.Add(BuildRowsConfig(TopicLine1Rows,
|
||||
data => UpdateRows(CurrentData.Line1, data)));
|
||||
receiver.Add(BuildSummaryConfig(TopicLine2Summary,
|
||||
data => UpdateSummary(CurrentData.Line2, data)));
|
||||
receiver.Add(BuildRowsConfig(TopicLine2Rows,
|
||||
data => UpdateRows(CurrentData.Line2, data)));
|
||||
receiver.Add(BuildSummaryConfig(TopicSummary, UpdateSummary));
|
||||
receiver.Add(BuildRowsConfig(TopicRows, UpdateRows));
|
||||
}
|
||||
|
||||
/// <summary>DataRepository 공유 수신기에서 4개 토픽 구독을 해제합니다.</summary>
|
||||
/// <summary>MQTT 토픽 구독을 해제합니다.</summary>
|
||||
public void Unsubscribe()
|
||||
{
|
||||
if (!_subscribed) return;
|
||||
_subscribed = false;
|
||||
|
||||
var receiver = DataRepository.Instance.MqttReceiver;
|
||||
receiver.Remove(TopicLine1Summary);
|
||||
receiver.Remove(TopicLine1Rows);
|
||||
receiver.Remove(TopicLine2Summary);
|
||||
receiver.Remove(TopicLine2Rows);
|
||||
receiver.Remove(TopicSummary);
|
||||
receiver.Remove(TopicRows);
|
||||
}
|
||||
|
||||
// ── 내부 구현 ──────────────────────────────────────────────
|
||||
// ── 내부 구현 ──
|
||||
|
||||
private static DataMask CreateSummaryMask()
|
||||
{
|
||||
@@ -124,14 +72,17 @@ namespace UVC.EnglewoodLAB.Data
|
||||
private static DataMask CreateRowMask()
|
||||
{
|
||||
var mask = new DataMask();
|
||||
mask["equip_name"] = "";
|
||||
mask["order_no"] = "";
|
||||
mask["item_code"] = "";
|
||||
mask["item_name"] = "";
|
||||
mask["start_time"] = "";
|
||||
mask["target"] = "";
|
||||
mask["achievement"] = 0.0f;
|
||||
mask["status"] = ""; // "running" | "stopped"
|
||||
mask["equip_name"] = "";
|
||||
mask["order_no"] = "";
|
||||
mask["item_code"] = "";
|
||||
mask["item_name"] = "";
|
||||
mask["start_time"] = "";
|
||||
mask["end_time"] = "";
|
||||
mask["total_work_time"] = "";
|
||||
mask["plan_production"] = "";
|
||||
mask["actual_production"] = "";
|
||||
mask["achievement"] = 0.0f;
|
||||
mask["status"] = "";
|
||||
return mask;
|
||||
}
|
||||
|
||||
@@ -147,12 +98,11 @@ namespace UVC.EnglewoodLAB.Data
|
||||
.SetDataMapper(new DataMapper(s_RowMask))
|
||||
.SetHandler(handler);
|
||||
|
||||
/// <summary>요약 DataObject를 SummaryData에 반영합니다.</summary>
|
||||
private void UpdateSummary(EWLKMfgOrderLineData line, IDataObject? data)
|
||||
private void UpdateSummary(IDataObject? data)
|
||||
{
|
||||
if (data is not DataObject obj) return;
|
||||
|
||||
var s = line.Summary;
|
||||
var s = CurrentData.Summary;
|
||||
s.FactoryName = obj.GetString("factory_name") ?? string.Empty;
|
||||
s.TotalTarget = obj.GetString("total_target") ?? string.Empty;
|
||||
s.TotalActual = obj.GetString("total_actual") ?? string.Empty;
|
||||
@@ -164,35 +114,32 @@ namespace UVC.EnglewoodLAB.Data
|
||||
OnDataUpdated?.Invoke(CurrentData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 행 DataArray를 파싱하여 Rows를 교체합니다.
|
||||
/// DataArray는 List<DataObject>를 상속합니다.
|
||||
/// </summary>
|
||||
private void UpdateRows(EWLKMfgOrderLineData line, IDataObject? data)
|
||||
private void UpdateRows(IDataObject? data)
|
||||
{
|
||||
if (data is not DataArray arr) return;
|
||||
|
||||
line.Rows.Clear();
|
||||
CurrentData.Rows.Clear();
|
||||
foreach (var obj in arr)
|
||||
{
|
||||
line.Rows.Add(new EWLKMfgOrderRowData
|
||||
CurrentData.Rows.Add(new EWLKMfgOrderRowData
|
||||
{
|
||||
EquipName = obj.GetString("equip_name") ?? string.Empty,
|
||||
OrderNo = obj.GetString("order_no") ?? string.Empty,
|
||||
ItemCode = obj.GetString("item_code") ?? string.Empty,
|
||||
ItemName = obj.GetString("item_name") ?? string.Empty,
|
||||
StartTime = obj.GetString("start_time") ?? string.Empty,
|
||||
Target = obj.GetString("target") ?? string.Empty,
|
||||
Achievement = obj.GetFloat("achievement") ?? 0f,
|
||||
IsStopped = (obj.GetString("status") ?? "") == "stopped",
|
||||
EquipName = obj.GetString("equip_name") ?? string.Empty,
|
||||
OrderNo = obj.GetString("order_no") ?? string.Empty,
|
||||
ItemCode = obj.GetString("item_code") ?? string.Empty,
|
||||
ItemName = obj.GetString("item_name") ?? string.Empty,
|
||||
StartTime = obj.GetString("start_time") ?? string.Empty,
|
||||
EndTime = obj.GetString("end_time") ?? string.Empty,
|
||||
TotalWorkTime = obj.GetString("total_work_time") ?? string.Empty,
|
||||
PlanProduction = obj.GetString("plan_production") ?? string.Empty,
|
||||
ActualProduction = obj.GetString("actual_production") ?? string.Empty,
|
||||
Achievement = obj.GetFloat("achievement") ?? 0f,
|
||||
IsStopped = (obj.GetString("status") ?? "") == "stopped",
|
||||
});
|
||||
}
|
||||
|
||||
OnDataUpdated?.Invoke(CurrentData);
|
||||
}
|
||||
|
||||
// ── IDisposable ────────────────────────────────────────────
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
76
Assets/Scripts/EnglewoodLAB/Data/EWLKPackOrderData.cs
Normal file
76
Assets/Scripts/EnglewoodLAB/Data/EWLKPackOrderData.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UVC.EnglewoodLAB.Data
|
||||
{
|
||||
/// <summary>충/포장 지시 행 데이터</summary>
|
||||
public class EWLKPackOrderRowData
|
||||
{
|
||||
/// <summary>층 구분 (3층, 4층)</summary>
|
||||
public string Floor { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>설비라인</summary>
|
||||
public string Line { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>설비명</summary>
|
||||
public string EquipName { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>지시번호</summary>
|
||||
public string OrderNo { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>품목 코드</summary>
|
||||
public string ItemCode { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>품목명</summary>
|
||||
public string ItemName { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>시작 시간</summary>
|
||||
public string StartTime { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>종료 시간</summary>
|
||||
public string EndTime { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>총 작업 시간</summary>
|
||||
public string TotalWorkTime { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>설비 CAPA</summary>
|
||||
public string EquipCapa { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>계획 생산량(kg)</summary>
|
||||
public string PlanProduction { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>실 생산량(kg)</summary>
|
||||
public string ActualProduction { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>진척률 (0~100)</summary>
|
||||
public float Achievement { get; internal set; }
|
||||
|
||||
/// <summary>플레이스홀더 행 생성</summary>
|
||||
public static EWLKPackOrderRowData CreatePlaceholder()
|
||||
{
|
||||
return new EWLKPackOrderRowData
|
||||
{
|
||||
Floor = "-",
|
||||
Line = "-",
|
||||
EquipName = "-",
|
||||
OrderNo = "-",
|
||||
ItemCode = "-",
|
||||
ItemName = "-",
|
||||
StartTime = "-",
|
||||
EndTime = "-",
|
||||
TotalWorkTime = "-",
|
||||
EquipCapa = "-",
|
||||
PlanProduction = "-",
|
||||
ActualProduction = "-",
|
||||
Achievement = 0f,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>충/포장 지시현황 데이터 (행 목록)</summary>
|
||||
public class EWLKPackOrderData
|
||||
{
|
||||
/// <summary>설비별 행 목록</summary>
|
||||
public List<EWLKPackOrderRowData> Rows { get; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac127703e07e2ba4c92d3a7ffaff817f
|
||||
70
Assets/Scripts/EnglewoodLAB/Data/EWLKWorkExplorerData.cs
Normal file
70
Assets/Scripts/EnglewoodLAB/Data/EWLKWorkExplorerData.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
#nullable enable
|
||||
|
||||
namespace UVC.EnglewoodLAB.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 작업 탐색기 데이터 (작업장 3곳: 제조, 충포장 3F, 충포장 4F).
|
||||
/// MQTT 수신 시 각 작업장 데이터를 업데이트합니다.
|
||||
/// </summary>
|
||||
public class EWLKWorkExplorerData
|
||||
{
|
||||
/// <summary>제조작업장</summary>
|
||||
public EWLKWorkshopEquipData Manufacture { get; private set; } = new();
|
||||
|
||||
/// <summary>충포장작업장(3F)</summary>
|
||||
public EWLKWorkshopEquipData Packing3F { get; private set; } = new();
|
||||
|
||||
/// <summary>충포장작업장(4F)</summary>
|
||||
public EWLKWorkshopEquipData Packing4F { get; private set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 작업장별 설비 상태 + 계획 현황 데이터.
|
||||
/// </summary>
|
||||
public class EWLKWorkshopEquipData
|
||||
{
|
||||
// ── 설비 상태 ──
|
||||
/// <summary>전체 설비(라인) 수</summary>
|
||||
public string TotalEquip { get; private set; } = "-";
|
||||
|
||||
/// <summary>비가동 설비(라인) 수</summary>
|
||||
public string IdleEquip { get; private set; } = "-";
|
||||
|
||||
/// <summary>설비(라인) 가동율</summary>
|
||||
public string OperationRate { get; private set; } = "-";
|
||||
|
||||
/// <summary>설비(라인) 종합효율</summary>
|
||||
public string OverallEfficiency { get; private set; } = "-";
|
||||
|
||||
// ── 계획 현황 ──
|
||||
/// <summary>계획량</summary>
|
||||
public string PlanQuantity { get; private set; } = "-";
|
||||
|
||||
/// <summary>실적수량</summary>
|
||||
public string ActualQuantity { get; private set; } = "-";
|
||||
|
||||
/// <summary>진행률</summary>
|
||||
public string ProgressRate { get; private set; } = "-";
|
||||
|
||||
/// <summary>
|
||||
/// 설비 상태 데이터를 업데이트합니다.
|
||||
/// </summary>
|
||||
public void UpdateEquipStatus(string totalEquip, string idleEquip, string operationRate, string overallEfficiency)
|
||||
{
|
||||
TotalEquip = totalEquip;
|
||||
IdleEquip = idleEquip;
|
||||
OperationRate = operationRate;
|
||||
OverallEfficiency = overallEfficiency;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 계획 현황 데이터를 업데이트합니다.
|
||||
/// </summary>
|
||||
public void UpdatePlanStatus(string planQuantity, string actualQuantity, string progressRate)
|
||||
{
|
||||
PlanQuantity = planQuantity;
|
||||
ActualQuantity = actualQuantity;
|
||||
ProgressRate = progressRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a92d6c30ba94e7545b249c676cfd1e5c
|
||||
@@ -481,6 +481,13 @@ namespace UVC.EnglewoodLAB
|
||||
// 컨텐츠 바인딩
|
||||
BindMenuPopupContent(menuId, _menuPopup);
|
||||
|
||||
// 메뉴별 사이즈 조정 (기본: 전체 폭)
|
||||
if (menuId is "work_explorer" or "equip_list")
|
||||
{
|
||||
_menuPopup.style.right = StyleKeyword.Auto;
|
||||
_menuPopup.style.width = new Length(40, LengthUnit.Percent);
|
||||
}
|
||||
|
||||
// 팝업 등록 및 표시
|
||||
RegisterPopup($"menu_{menuId}", PopupPosition.Left);
|
||||
dynamicRoot.Add(_menuPopup);
|
||||
@@ -543,6 +550,30 @@ namespace UVC.EnglewoodLAB
|
||||
popup.AddContent(mfgContent);
|
||||
break;
|
||||
|
||||
case "pack_order":
|
||||
var packContent = new EWLKPackOrderContent();
|
||||
// TODO: MQTT 서비스 연결 시 아래 패턴 적용
|
||||
// packContent.UpdateData(packOrderMqtt.CurrentData);
|
||||
// packOrderMqtt.OnDataUpdated += packContent.UpdateData;
|
||||
popup.AddContent(packContent);
|
||||
break;
|
||||
|
||||
case "equip_list":
|
||||
var equipContent = new EWLKEquipListContent();
|
||||
// TODO: MQTT 서비스 연결 시 아래 패턴 적용
|
||||
// equipContent.UpdateData(equipListMqtt.CurrentData);
|
||||
// equipListMqtt.OnDataUpdated += equipContent.UpdateData;
|
||||
popup.AddContent(equipContent);
|
||||
break;
|
||||
|
||||
case "work_explorer":
|
||||
var workExplorerContent = new EWLKWorkExplorerContent();
|
||||
// TODO: MQTT 서비스 연결 시 아래 패턴 적용
|
||||
// workExplorerContent.UpdateData(workExplorerMqtt.CurrentData);
|
||||
// workExplorerMqtt.OnDataUpdated += workExplorerContent.UpdateData;
|
||||
popup.AddContent(workExplorerContent);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 아직 컨텐츠가 없는 메뉴 항목
|
||||
var placeholder = new Label("Contents 영역");
|
||||
|
||||
496
Assets/Scripts/EnglewoodLAB/UIToolkit/EWLKEquipListContent.cs
Normal file
496
Assets/Scripts/EnglewoodLAB/UIToolkit/EWLKEquipListContent.cs
Normal file
@@ -0,0 +1,496 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.EnglewoodLAB.Data;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
namespace UVC.EnglewoodLAB.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 설비 목록 컨텐츠.
|
||||
/// 검색창 + 공정/상태 필터 + 8컬럼 테이블 (ScrollView).
|
||||
/// 상태: 타원 뱃지, 제어: 가동/정지 읽기전용 토글.
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class EWLKEquipListContent : VisualElement, IDisposable
|
||||
{
|
||||
private const string UssPath = "EWLK/UIToolkit/Main/EWLKEquipListContentUss";
|
||||
private const string SearchPlaceholder = "공정, 라인, 설비 번호, 설비명을 검색해 보세요.";
|
||||
|
||||
// 8컬럼 (상태/제어는 특수 처리)
|
||||
private static readonly (string cls, string header)[] s_Columns =
|
||||
{
|
||||
("col-el-no", "No"),
|
||||
("col-el-process", "공정 구분"),
|
||||
("col-el-line", "설비 라인"),
|
||||
("col-el-category", "설비 분류"),
|
||||
("col-el-equipno", "설비 번호"),
|
||||
("col-el-name", "설비명"),
|
||||
("col-el-status", "상태"),
|
||||
("col-el-control", "제어"),
|
||||
};
|
||||
|
||||
// 상태 → (텍스트, CSS modifier)
|
||||
private static readonly Dictionary<EWLKEquipStatus, (string text, string cls)> s_StatusMap = new()
|
||||
{
|
||||
{ EWLKEquipStatus.Ready, ("준비", "ewlk-equip__badge--ready") },
|
||||
{ EWLKEquipStatus.Error, ("이상", "ewlk-equip__badge--error") },
|
||||
{ EWLKEquipStatus.Rest, ("휴식", "ewlk-equip__badge--rest") },
|
||||
{ EWLKEquipStatus.PlannedStop, ("계획정지", "ewlk-equip__badge--planned") },
|
||||
};
|
||||
|
||||
private readonly TextField _searchField;
|
||||
private readonly DropdownField _processFilter;
|
||||
private readonly DropdownField _statusFilter;
|
||||
private readonly ScrollView _scrollView;
|
||||
private readonly VisualElement _rowContainer;
|
||||
|
||||
private string _searchText = string.Empty;
|
||||
private string _selectedProcess = "공정 전체";
|
||||
private string _selectedStatus = "상태 전체";
|
||||
private List<EWLKEquipListRowData> _allRows = new();
|
||||
private List<EWLKEquipListRowData> _displayRows = new();
|
||||
|
||||
private static readonly List<EWLKEquipListRowData> s_PlaceholderRows = CreatePlaceholderRows();
|
||||
|
||||
public EWLKEquipListContent()
|
||||
{
|
||||
var uss = Resources.Load<StyleSheet>(UssPath);
|
||||
if (uss != null) styleSheets.Add(uss);
|
||||
|
||||
AddToClassList("ewlk-equip");
|
||||
|
||||
// ── 필터 바 ──
|
||||
var filterBar = new VisualElement();
|
||||
filterBar.AddToClassList("ewlk-equip__filter-bar");
|
||||
filterBar.pickingMode = PickingMode.Position;
|
||||
|
||||
// 검색창
|
||||
var searchWrap = new VisualElement();
|
||||
searchWrap.AddToClassList("ewlk-equip__search-wrap");
|
||||
searchWrap.pickingMode = PickingMode.Position;
|
||||
|
||||
var searchIcon = new Label(UTKMaterialIcons.Search);
|
||||
searchIcon.AddToClassList("ewlk-equip__search-icon");
|
||||
UTKMaterialIcons.ApplyIconStyle(searchIcon, 16);
|
||||
searchWrap.Add(searchIcon);
|
||||
|
||||
_searchField = new TextField();
|
||||
_searchField.AddToClassList("ewlk-equip__search-field");
|
||||
_searchField.AddToClassList("ewlk-equip__search-field--placeholder");
|
||||
_searchField.focusable = true;
|
||||
_searchField.pickingMode = PickingMode.Position;
|
||||
_searchField.value = SearchPlaceholder;
|
||||
_searchField.RegisterCallback<FocusInEvent>(_ =>
|
||||
{
|
||||
if (_searchField.value == SearchPlaceholder)
|
||||
{
|
||||
_searchField.value = string.Empty;
|
||||
_searchField.RemoveFromClassList("ewlk-equip__search-field--placeholder");
|
||||
}
|
||||
});
|
||||
_searchField.RegisterCallback<FocusOutEvent>(_ =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(_searchField.value))
|
||||
{
|
||||
_searchField.value = SearchPlaceholder;
|
||||
_searchField.AddToClassList("ewlk-equip__search-field--placeholder");
|
||||
}
|
||||
});
|
||||
_searchField.RegisterCallback<ChangeEvent<string>>(evt =>
|
||||
{
|
||||
var val = evt.newValue ?? string.Empty;
|
||||
_searchText = val == SearchPlaceholder ? string.Empty : val;
|
||||
ApplyFilter();
|
||||
});
|
||||
|
||||
// TextField 내부 요소 (USS로 덮어쓸 수 없으므로 인라인 필수)
|
||||
var textInput = _searchField.Q("unity-text-input");
|
||||
if (textInput != null)
|
||||
{
|
||||
textInput.pickingMode = PickingMode.Position;
|
||||
textInput.focusable = true;
|
||||
textInput.style.backgroundColor = Color.white;
|
||||
textInput.style.borderTopWidth = 0;
|
||||
textInput.style.borderBottomWidth = 0;
|
||||
textInput.style.borderLeftWidth = 0;
|
||||
textInput.style.borderRightWidth = 0;
|
||||
}
|
||||
|
||||
searchWrap.Add(_searchField);
|
||||
filterBar.Add(searchWrap);
|
||||
|
||||
// 공정 필터
|
||||
var processChoices = new List<string>
|
||||
{
|
||||
"공정 전체", "1층 제조", "3층 생산 충/포장", "4층 생산 충/포장"
|
||||
};
|
||||
_processFilter = new DropdownField(processChoices, 0) { label = string.Empty };
|
||||
_processFilter.AddToClassList("ewlk-equip__filter-dropdown");
|
||||
_processFilter.pickingMode = PickingMode.Position;
|
||||
_processFilter.focusable = true;
|
||||
ApplyDropdownPicking(_processFilter);
|
||||
_processFilter.RegisterCallback<ChangeEvent<string>>(evt =>
|
||||
{
|
||||
_selectedProcess = evt.newValue ?? "공정 전체";
|
||||
ApplyFilter();
|
||||
});
|
||||
filterBar.Add(_processFilter);
|
||||
|
||||
// 상태 필터
|
||||
var statusChoices = new List<string>
|
||||
{
|
||||
"상태 전체", "준비", "이상", "휴식", "계획정지"
|
||||
};
|
||||
_statusFilter = new DropdownField(statusChoices, 0) { label = string.Empty };
|
||||
_statusFilter.AddToClassList("ewlk-equip__filter-dropdown");
|
||||
_statusFilter.pickingMode = PickingMode.Position;
|
||||
_statusFilter.focusable = true;
|
||||
ApplyDropdownPicking(_statusFilter);
|
||||
_statusFilter.RegisterCallback<ChangeEvent<string>>(evt =>
|
||||
{
|
||||
_selectedStatus = evt.newValue ?? "상태 전체";
|
||||
ApplyFilter();
|
||||
});
|
||||
filterBar.Add(_statusFilter);
|
||||
|
||||
Add(filterBar);
|
||||
|
||||
// ── 테이블 헤더 ──
|
||||
Add(BuildTableHeader());
|
||||
|
||||
// ── ScrollView ──
|
||||
_scrollView = new ScrollView(ScrollViewMode.Vertical);
|
||||
_scrollView.AddToClassList("ewlk-equip__list");
|
||||
_rowContainer = new VisualElement();
|
||||
_scrollView.Add(_rowContainer);
|
||||
Add(_scrollView);
|
||||
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
// ── 공개 API ──
|
||||
|
||||
/// <summary>MQTT 데이터 갱신.</summary>
|
||||
public void UpdateData(EWLKEquipListData data)
|
||||
{
|
||||
_allRows = data.Rows;
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
// ── 뷰 갱신 ──
|
||||
|
||||
private void RefreshView()
|
||||
{
|
||||
_allRows = s_PlaceholderRows;
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
private void ApplyFilter()
|
||||
{
|
||||
_displayRows = new List<EWLKEquipListRowData>();
|
||||
|
||||
foreach (var row in _allRows)
|
||||
{
|
||||
// 공정 필터
|
||||
if (_selectedProcess != "공정 전체" && !Contains(row.Process, _selectedProcess))
|
||||
continue;
|
||||
|
||||
// 상태 필터
|
||||
if (_selectedStatus != "상태 전체")
|
||||
{
|
||||
var statusText = s_StatusMap.TryGetValue(row.Status, out var info) ? info.text : "";
|
||||
if (statusText != _selectedStatus) continue;
|
||||
}
|
||||
|
||||
// 검색어 필터
|
||||
if (!string.IsNullOrWhiteSpace(_searchText))
|
||||
{
|
||||
if (!Contains(row.Process, _searchText) &&
|
||||
!Contains(row.Line, _searchText) &&
|
||||
!Contains(row.EquipNo, _searchText) &&
|
||||
!Contains(row.EquipName, _searchText))
|
||||
continue;
|
||||
}
|
||||
|
||||
_displayRows.Add(row);
|
||||
}
|
||||
|
||||
RebuildRows();
|
||||
}
|
||||
|
||||
private void RebuildRows()
|
||||
{
|
||||
_rowContainer.Clear();
|
||||
for (int i = 0; i < _displayRows.Count; i++)
|
||||
{
|
||||
var row = MakeRowElement();
|
||||
BindRowElement(row, i);
|
||||
_rowContainer.Add(row);
|
||||
|
||||
// 세부 설비 아코디언 컨테이너
|
||||
var d = _displayRows[i];
|
||||
if (d.HasSubEquipments)
|
||||
{
|
||||
var accordion = new VisualElement();
|
||||
accordion.name = "sub-accordion";
|
||||
accordion.AddToClassList("ewlk-equip__accordion");
|
||||
accordion.style.display = DisplayStyle.None;
|
||||
|
||||
foreach (var subName in d.SubEquipments)
|
||||
{
|
||||
var subRow = new VisualElement();
|
||||
subRow.AddToClassList("ewlk-equip__sub-row");
|
||||
|
||||
var subLabel = new Label(subName);
|
||||
subLabel.AddToClassList("ewlk-equip__sub-label");
|
||||
subRow.Add(subLabel);
|
||||
|
||||
accordion.Add(subRow);
|
||||
}
|
||||
|
||||
_rowContainer.Add(accordion);
|
||||
|
||||
// 클릭 토글
|
||||
var capturedAccordion = accordion;
|
||||
var expandIcon = row.Q<Label>("expand-icon");
|
||||
row.RegisterCallback<ClickEvent>(_ =>
|
||||
{
|
||||
bool isOpen = capturedAccordion.style.display == DisplayStyle.Flex;
|
||||
capturedAccordion.style.display = isOpen ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
if (expandIcon != null)
|
||||
expandIcon.text = isOpen ? UTKMaterialIcons.ExpandMore : UTKMaterialIcons.ExpandLess;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 행 생성/바인딩 ──
|
||||
|
||||
private static VisualElement MakeRowElement()
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("ewlk-equip__row");
|
||||
|
||||
foreach (var (cls, _) in s_Columns)
|
||||
{
|
||||
var cell = new VisualElement();
|
||||
cell.AddToClassList("ewlk-equip__cell");
|
||||
cell.AddToClassList(cls);
|
||||
|
||||
if (cls == "col-el-status")
|
||||
{
|
||||
// 상태 뱃지
|
||||
var badge = new Label();
|
||||
badge.name = "status-badge";
|
||||
badge.AddToClassList("ewlk-equip__badge");
|
||||
cell.Add(badge);
|
||||
}
|
||||
else if (cls == "col-el-control")
|
||||
{
|
||||
// 제어 토글 (읽기전용)
|
||||
var toggle = new Toggle();
|
||||
toggle.name = "control-toggle";
|
||||
toggle.AddToClassList("ewlk-equip__toggle");
|
||||
toggle.SetEnabled(false); // 클릭 불가
|
||||
cell.Add(toggle);
|
||||
|
||||
var toggleLabel = new Label();
|
||||
toggleLabel.name = "control-label";
|
||||
toggleLabel.AddToClassList("ewlk-equip__toggle-label");
|
||||
cell.Add(toggleLabel);
|
||||
}
|
||||
else if (cls == "col-el-name")
|
||||
{
|
||||
// 설비명: 이름 + "+n" 뱃지 + ∧/∨ 아이콘 (세부 설비가 있을 때만 표시)
|
||||
var nameWrap = new VisualElement();
|
||||
nameWrap.name = "name-wrap";
|
||||
nameWrap.AddToClassList("ewlk-equip__name-wrap");
|
||||
|
||||
var lbl = new Label();
|
||||
lbl.AddToClassList("ewlk-equip__cell-label");
|
||||
nameWrap.Add(lbl);
|
||||
|
||||
var countBadge = new Label();
|
||||
countBadge.name = "sub-count";
|
||||
countBadge.AddToClassList("ewlk-equip__sub-count");
|
||||
countBadge.style.display = DisplayStyle.None;
|
||||
nameWrap.Add(countBadge);
|
||||
|
||||
var expandIcon = new Label(UTKMaterialIcons.ExpandMore);
|
||||
expandIcon.name = "expand-icon";
|
||||
expandIcon.AddToClassList("ewlk-equip__expand-icon");
|
||||
UTKMaterialIcons.ApplyIconStyle(expandIcon, 14);
|
||||
expandIcon.style.display = DisplayStyle.None;
|
||||
nameWrap.Add(expandIcon);
|
||||
|
||||
cell.Add(nameWrap);
|
||||
}
|
||||
else
|
||||
{
|
||||
var lbl = new Label();
|
||||
lbl.AddToClassList("ewlk-equip__cell-label");
|
||||
cell.Add(lbl);
|
||||
}
|
||||
|
||||
row.Add(cell);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private void BindRowElement(VisualElement element, int index)
|
||||
{
|
||||
if (index >= _displayRows.Count) return;
|
||||
var d = _displayRows[index];
|
||||
|
||||
SetLabel(element, "col-el-no", (index + 1).ToString());
|
||||
SetLabel(element, "col-el-process", d.Process);
|
||||
SetLabel(element, "col-el-line", d.Line);
|
||||
SetLabel(element, "col-el-category", d.Category);
|
||||
SetLabel(element, "col-el-equipno", d.EquipNo);
|
||||
|
||||
// 설비명 + 세부 설비 "+n" 뱃지
|
||||
var nameCell = element.Q(className: "col-el-name");
|
||||
var nameLabel = nameCell?.Q<Label>(className: "ewlk-equip__cell-label");
|
||||
if (nameLabel != null)
|
||||
nameLabel.text = string.IsNullOrEmpty(d.EquipName) ? "-" : d.EquipName;
|
||||
|
||||
var subCount = element.Q<Label>("sub-count");
|
||||
var expandIcon = element.Q<Label>("expand-icon");
|
||||
if (d.HasSubEquipments)
|
||||
{
|
||||
if (subCount != null)
|
||||
{
|
||||
subCount.text = $"+{d.SubEquipments.Count}";
|
||||
subCount.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
if (expandIcon != null)
|
||||
expandIcon.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (subCount != null) subCount.style.display = DisplayStyle.None;
|
||||
if (expandIcon != null) expandIcon.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
// 상태 뱃지
|
||||
var badge = element.Q<Label>("status-badge");
|
||||
if (badge != null)
|
||||
{
|
||||
var (text, cls) = s_StatusMap.TryGetValue(d.Status, out var info)
|
||||
? info
|
||||
: ("준비", "ewlk-equip__badge--ready");
|
||||
|
||||
badge.text = text;
|
||||
// 이전 상태 클래스 제거
|
||||
badge.RemoveFromClassList("ewlk-equip__badge--ready");
|
||||
badge.RemoveFromClassList("ewlk-equip__badge--error");
|
||||
badge.RemoveFromClassList("ewlk-equip__badge--rest");
|
||||
badge.RemoveFromClassList("ewlk-equip__badge--planned");
|
||||
badge.AddToClassList(cls);
|
||||
}
|
||||
|
||||
// 제어 토글
|
||||
var toggle = element.Q<Toggle>("control-toggle");
|
||||
if (toggle != null)
|
||||
toggle.value = d.IsRunning;
|
||||
|
||||
var toggleLabel = element.Q<Label>("control-label");
|
||||
if (toggleLabel != null)
|
||||
toggleLabel.text = d.IsRunning ? "가동" : "정지";
|
||||
}
|
||||
|
||||
// ── 헬퍼 ──
|
||||
|
||||
private static void SetLabel(VisualElement row, string cellClass, string text)
|
||||
{
|
||||
var cell = row.Q(className: cellClass);
|
||||
var lbl = cell?.Q<Label>(className: "ewlk-equip__cell-label");
|
||||
if (lbl != null) lbl.text = string.IsNullOrEmpty(text) ? "-" : text;
|
||||
}
|
||||
|
||||
private static bool Contains(string source, string keyword) =>
|
||||
!string.IsNullOrEmpty(source) &&
|
||||
source.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
|
||||
private static void ApplyDropdownPicking(DropdownField dropdown)
|
||||
{
|
||||
var popupField = dropdown.Q(className: "unity-base-popup-field__input");
|
||||
if (popupField != null)
|
||||
popupField.pickingMode = PickingMode.Position;
|
||||
}
|
||||
|
||||
private static List<EWLKEquipListRowData> CreatePlaceholderRows()
|
||||
{
|
||||
//var rows = new List<EWLKEquipListRowData>();
|
||||
// for (int i = 0; i < 5; i++)
|
||||
// rows.Add(EWLKEquipListRowData.CreatePlaceholder());
|
||||
// return rows;
|
||||
//
|
||||
// 아래는 가데이터 ---
|
||||
return new List<EWLKEquipListRowData>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Process = "2층 생산 충/포장", Line = "01라인", Category = "충/포장설비",
|
||||
EquipNo = "P-FA-029", EquipName = "로터리 충전기",
|
||||
Status = EWLKEquipStatus.Ready, IsRunning = true,
|
||||
SubEquipments = new List<string> { "백킹 공급기", "스포이드 공급기", "용기 공급기" },
|
||||
},
|
||||
new()
|
||||
{
|
||||
Process = "1층 제조", Line = "-", Category = "제조설비",
|
||||
EquipNo = "M-001", EquipName = "Ultra Homo Mixer50",
|
||||
Status = EWLKEquipStatus.Error, IsRunning = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Process = "1층 제조", Line = "-", Category = "제조설비",
|
||||
EquipNo = "M-002", EquipName = "진공 유화기",
|
||||
Status = EWLKEquipStatus.Rest, IsRunning = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Process = "3층 생산 충/포장", Line = "03라인", Category = "충/포장설비",
|
||||
EquipNo = "P-FA-031", EquipName = "다용도 로터리 충전기",
|
||||
Status = EWLKEquipStatus.PlannedStop, IsRunning = false,
|
||||
SubEquipments = new List<string> { "라벨러", "캡핑기" },
|
||||
},
|
||||
new()
|
||||
{
|
||||
Process = "2층 생산 충/포장", Line = "01라인", Category = "충/포장설비",
|
||||
EquipNo = "P-FA-029", EquipName = "다용도 로터리 충전기",
|
||||
Status = EWLKEquipStatus.Ready, IsRunning = true,
|
||||
SubEquipments = new List<string> { "백킹 공급기", "스포이드 공급기", "용기 공급기" },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static VisualElement BuildTableHeader()
|
||||
{
|
||||
var header = new VisualElement();
|
||||
header.AddToClassList("ewlk-equip__header");
|
||||
|
||||
foreach (var (cls, text) in s_Columns)
|
||||
{
|
||||
var cell = new VisualElement();
|
||||
cell.AddToClassList("ewlk-equip__cell");
|
||||
cell.AddToClassList("ewlk-equip__header-cell");
|
||||
cell.AddToClassList(cls);
|
||||
|
||||
var lbl = new Label(text);
|
||||
lbl.AddToClassList("ewlk-equip__cell-label");
|
||||
cell.Add(lbl);
|
||||
|
||||
header.Add(cell);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 159d902193c672842be1af8fb1d08360
|
||||
@@ -47,9 +47,10 @@ namespace UVC.EnglewoodLAB.UIToolkit
|
||||
style.bottom = 0;
|
||||
pickingMode = PickingMode.Ignore;
|
||||
|
||||
// 메인 패널 (흰색 배경)
|
||||
// 메인 패널 (흰색 배경, 클릭 수신)
|
||||
var panel = new VisualElement();
|
||||
panel.AddToClassList("ewlk-menu-popup__panel");
|
||||
panel.pickingMode = PickingMode.Position;
|
||||
|
||||
// 헤더 (타이틀 + 닫기 버튼)
|
||||
var header = new VisualElement();
|
||||
@@ -74,9 +75,10 @@ namespace UVC.EnglewoodLAB.UIToolkit
|
||||
divider.AddToClassList("ewlk-menu-popup__divider");
|
||||
panel.Add(divider);
|
||||
|
||||
// 컨텐츠 영역
|
||||
// 컨텐츠 영역 (내부 UI 입력 수신)
|
||||
_contentContainer = new VisualElement();
|
||||
_contentContainer.AddToClassList("ewlk-menu-popup__content");
|
||||
_contentContainer.pickingMode = PickingMode.Position;
|
||||
panel.Add(_contentContainer);
|
||||
|
||||
Add(panel);
|
||||
|
||||
@@ -4,172 +4,237 @@ using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.EnglewoodLAB.Data;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
namespace UVC.EnglewoodLAB.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 제조지시현황 모달 콘텐츠.
|
||||
/// 2개 라인 탭, 요약 바, 설비 행 ListView로 구성됩니다.
|
||||
/// MQTT 데이터가 없는 경우 플레이스홀더 행을 표시합니다.
|
||||
/// 제조지시현황 컨텐츠.
|
||||
/// 2개 라인 탭, 요약 바 (목표량/실적량 + 진척률/가동률), 11컬럼 테이블.
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class EWLKMfgOrderModalContent : VisualElement, IDisposable
|
||||
{
|
||||
private const string UssPath = "EWLK/UIToolkit/Main/EWLKMfgOrderModalContentUss";
|
||||
private const int ItemNameMaxChars = 5;
|
||||
|
||||
// ── 탭 ──────────────────────────────────────────────
|
||||
// ── 탭 ──
|
||||
private readonly Button _tab1Btn;
|
||||
private readonly Button _tab2Btn;
|
||||
|
||||
// ── 요약 바 ─────────────────────────────────────────
|
||||
// ── 요약 바 (좌측) ──
|
||||
private readonly Label _totalTargetLabel;
|
||||
private readonly VisualElement _totalTargetFill;
|
||||
private readonly Label _totalActualLabel;
|
||||
private readonly VisualElement _totalActualFill;
|
||||
|
||||
// ── 요약 바 (우측) ──
|
||||
private readonly Label _totalProgressLabel;
|
||||
private readonly VisualElement _totalProgressFill;
|
||||
private readonly Label _equipRateLabel;
|
||||
private readonly VisualElement _equipRateFill;
|
||||
private readonly Label _countLabel;
|
||||
|
||||
// ── 테이블 ───────────────────────────────────────────
|
||||
private readonly ListView _listView;
|
||||
// ── 테이블 ──
|
||||
private readonly ScrollView _scrollView;
|
||||
private readonly VisualElement _rowContainer;
|
||||
|
||||
// ── 상태 ─────────────────────────────────────────────
|
||||
private EWLKMfgOrderData? _data;
|
||||
// ── 검색 ──
|
||||
private readonly TextField _searchField;
|
||||
private string _searchText = string.Empty;
|
||||
|
||||
// ── 상태 ──
|
||||
private EWLKMfgOrderLineData? _currentLine;
|
||||
private int _activeTabIndex;
|
||||
private List<EWLKMfgOrderRowData> _allRows = new();
|
||||
private List<EWLKMfgOrderRowData> _displayRows = new();
|
||||
|
||||
// ── 플레이스홀더 (MQTT 연결 전 기본 표시용) ──────────
|
||||
private static readonly List<EWLKMfgOrderRowData> s_PlaceholderRows = CreatePlaceholderRows();
|
||||
|
||||
// ────────────────────────────────────────────────────
|
||||
// 11개 컬럼 정의 (CSS 클래스, 헤더 텍스트)
|
||||
private static readonly (string cls, string header)[] s_Columns =
|
||||
{
|
||||
("col-no", "No"),
|
||||
("col-equip", "설비명"),
|
||||
("col-order", "지시 번호"),
|
||||
("col-item-code", "품목 코드"),
|
||||
("col-item-name", "품목명"),
|
||||
("col-start-time", "시작 시간"),
|
||||
("col-end-time", "종료 시간"),
|
||||
("col-work-time", "총 작업 시간"),
|
||||
("col-plan-prod", "계획 생산량(kg)"),
|
||||
("col-actual-prod", "실 생산량(kg)"),
|
||||
("col-achievement", "진척률(%)"),
|
||||
};
|
||||
|
||||
public EWLKMfgOrderModalContent()
|
||||
{
|
||||
styleSheets.Add(Resources.Load<StyleSheet>(UssPath));
|
||||
var uss = Resources.Load<StyleSheet>(UssPath);
|
||||
if (uss != null) styleSheets.Add(uss);
|
||||
|
||||
AddToClassList("ewlk-mfgorder");
|
||||
|
||||
// ── 탭 바 ────────────────────────────────────────
|
||||
// ── 탭 바 ──
|
||||
var tabBar = new VisualElement();
|
||||
tabBar.AddToClassList("ewlk-mfgorder__tab-bar");
|
||||
|
||||
_tab1Btn = new Button(() => SelectTab(0)) { text = "라인 1" };
|
||||
_tab1Btn = new Button(() => SelectTab(0)) { text = "2공장 제조1 목표 현황판" };
|
||||
_tab1Btn.AddToClassList("ewlk-mfgorder__tab");
|
||||
_tab2Btn = new Button(() => SelectTab(1)) { text = "라인 2" };
|
||||
_tab2Btn.AddToClassList("ewlk-mfgorder__tab");
|
||||
_tab1Btn.AddToClassList("ewlk-mfgorder__tab--active");
|
||||
_tab2Btn = _tab1Btn; // 단일 탭 (호환성 유지)
|
||||
tabBar.Add(_tab1Btn);
|
||||
tabBar.Add(_tab2Btn);
|
||||
Add(tabBar);
|
||||
|
||||
// ── 요약 바 ──────────────────────────────────────
|
||||
// ── 요약 바 (좌측: 목표량+실적량, 우측: 진척률+가동률) ──
|
||||
var summaryBar = new VisualElement();
|
||||
summaryBar.AddToClassList("ewlk-mfgorder__summary-bar");
|
||||
summaryBar.Add(MakeStaticItem("전체 제조 목표량", out _totalTargetLabel));
|
||||
summaryBar.Add(MakeStaticItem("실제 실적량", out _totalActualLabel));
|
||||
summaryBar.Add(MakeProgressItem("전체 제조 진척률",
|
||||
out _totalProgressFill, out _totalProgressLabel));
|
||||
Add(summaryBar);
|
||||
|
||||
// ── 설비가동률 행 ─────────────────────────────────
|
||||
// 좌측 요약
|
||||
var summaryLeft = new VisualElement();
|
||||
summaryLeft.AddToClassList("ewlk-mfgorder__summary-left");
|
||||
summaryLeft.Add(MakeBarItem("전체 제조 목표량", out _totalTargetLabel, out _totalTargetFill));
|
||||
summaryLeft.Add(MakeBarItem("실제 실적량", out _totalActualLabel, out _totalActualFill));
|
||||
summaryBar.Add(summaryLeft);
|
||||
|
||||
// 우측 요약
|
||||
var summaryRight = new VisualElement();
|
||||
summaryRight.AddToClassList("ewlk-mfgorder__summary-right");
|
||||
summaryRight.Add(MakeBarItem("전체 제조 진척률", out _totalProgressLabel, out _totalProgressFill));
|
||||
|
||||
// 일 설비 가동율: [라벨] [%+대수 세로] [프로그레스 바]
|
||||
var equipRow = new VisualElement();
|
||||
equipRow.AddToClassList("ewlk-mfgorder__equip-row");
|
||||
equipRow.AddToClassList("ewlk-mfgorder__bar-item");
|
||||
|
||||
var equipTitle = new Label("일 설비가동률(가중대수%)");
|
||||
equipTitle.AddToClassList("ewlk-mfgorder__equip-title");
|
||||
var equipTitle = new Label("일 설비 가동율");
|
||||
equipTitle.AddToClassList("ewlk-mfgorder__bar-item-title");
|
||||
equipRow.Add(equipTitle);
|
||||
|
||||
var equipBarWrap = new VisualElement();
|
||||
equipBarWrap.AddToClassList("ewlk-mfgorder__progress-wrap");
|
||||
// % 와 대수를 세로로 묶기
|
||||
var equipValueStack = new VisualElement();
|
||||
equipValueStack.AddToClassList("ewlk-mfgorder__equip-value-stack");
|
||||
_equipRateLabel = new Label("-");
|
||||
_equipRateLabel.AddToClassList("ewlk-mfgorder__bar-item-value");
|
||||
equipValueStack.Add(_equipRateLabel);
|
||||
_countLabel = new Label("- / - 대");
|
||||
_countLabel.AddToClassList("ewlk-mfgorder__count");
|
||||
equipValueStack.Add(_countLabel);
|
||||
equipRow.Add(equipValueStack);
|
||||
|
||||
// 프로그레스 바
|
||||
var equipBarBg = new VisualElement();
|
||||
equipBarBg.AddToClassList("ewlk-mfgorder__progress-wrap");
|
||||
_equipRateFill = new VisualElement();
|
||||
_equipRateFill.AddToClassList("ewlk-mfgorder__progress-fill");
|
||||
equipBarWrap.Add(_equipRateFill);
|
||||
equipRow.Add(equipBarWrap);
|
||||
equipBarBg.Add(_equipRateFill);
|
||||
equipRow.Add(equipBarBg);
|
||||
|
||||
_equipRateLabel = new Label("-");
|
||||
_equipRateLabel.AddToClassList("ewlk-mfgorder__equip-rate");
|
||||
equipRow.Add(_equipRateLabel);
|
||||
summaryRight.Add(equipRow);
|
||||
|
||||
_countLabel = new Label("- / -");
|
||||
_countLabel.AddToClassList("ewlk-mfgorder__count");
|
||||
equipRow.Add(_countLabel);
|
||||
Add(equipRow);
|
||||
summaryBar.Add(summaryRight);
|
||||
Add(summaryBar);
|
||||
|
||||
// ── 테이블 헤더 ───────────────────────────────────
|
||||
// ── 검색창 ──
|
||||
var searchRow = new VisualElement();
|
||||
searchRow.AddToClassList("ewlk-mfgorder__search-row");
|
||||
|
||||
var searchIcon = new Label(UTKMaterialIcons.Search);
|
||||
searchIcon.AddToClassList("ewlk-mfgorder__search-icon");
|
||||
UTKMaterialIcons.ApplyIconStyle(searchIcon, 16);
|
||||
searchRow.Add(searchIcon);
|
||||
|
||||
_searchField = new TextField();
|
||||
_searchField.AddToClassList("ewlk-mfgorder__search-field");
|
||||
_searchField.AddToClassList("ewlk-mfgorder__search-field--placeholder");
|
||||
_searchField.focusable = true;
|
||||
_searchField.pickingMode = PickingMode.Position;
|
||||
_searchField.value = SearchPlaceholder;
|
||||
_searchField.RegisterCallback<FocusInEvent>(_ =>
|
||||
{
|
||||
if (_searchField.value == SearchPlaceholder)
|
||||
{
|
||||
_searchField.value = string.Empty;
|
||||
_searchField.RemoveFromClassList("ewlk-mfgorder__search-field--placeholder");
|
||||
}
|
||||
});
|
||||
_searchField.RegisterCallback<FocusOutEvent>(_ =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(_searchField.value))
|
||||
{
|
||||
_searchField.value = SearchPlaceholder;
|
||||
_searchField.AddToClassList("ewlk-mfgorder__search-field--placeholder");
|
||||
}
|
||||
});
|
||||
_searchField.RegisterCallback<ChangeEvent<string>>(OnSearchChanged);
|
||||
|
||||
// picking/focusable + 배경색 (TextField 내부 요소는 USS specificity로 덮어쓸 수 없음)
|
||||
var textInput = _searchField.Q("unity-text-input");
|
||||
if (textInput != null)
|
||||
{
|
||||
textInput.pickingMode = PickingMode.Position;
|
||||
textInput.focusable = true;
|
||||
textInput.style.backgroundColor = Color.white;
|
||||
textInput.style.borderTopWidth = 0;
|
||||
textInput.style.borderBottomWidth = 0;
|
||||
textInput.style.borderLeftWidth = 0;
|
||||
textInput.style.borderRightWidth = 0;
|
||||
}
|
||||
|
||||
searchRow.pickingMode = PickingMode.Position;
|
||||
searchRow.Add(_searchField);
|
||||
|
||||
Add(searchRow);
|
||||
|
||||
// ── 테이블 헤더 ──
|
||||
Add(BuildTableHeader());
|
||||
|
||||
// ── ListView ─────────────────────────────────────
|
||||
_listView = new ListView
|
||||
{
|
||||
makeItem = MakeRowElement,
|
||||
bindItem = BindRowElement,
|
||||
fixedItemHeight = 28,
|
||||
selectionType = SelectionType.None,
|
||||
};
|
||||
_listView.AddToClassList("ewlk-mfgorder__list");
|
||||
Add(_listView);
|
||||
// ── ScrollView + 행 컨테이너 ──
|
||||
_scrollView = new ScrollView(ScrollViewMode.Vertical);
|
||||
_scrollView.AddToClassList("ewlk-mfgorder__list");
|
||||
_rowContainer = new VisualElement();
|
||||
_scrollView.Add(_rowContainer);
|
||||
Add(_scrollView);
|
||||
|
||||
SetTabActive(0);
|
||||
|
||||
// 데이터가 없어도 플레이스홀더로 레이아웃 표시
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
// ── 공개 API ──────────────────────────────────────────
|
||||
// ── 공개 API ──
|
||||
|
||||
/// <summary>
|
||||
/// 데이터를 갱신합니다. MQTT 수신 시 호출됩니다 (메인 스레드 보장).
|
||||
/// </summary>
|
||||
public void UpdateData(EWLKMfgOrderData data)
|
||||
/// <summary>MQTT 데이터 갱신.</summary>
|
||||
public void UpdateData(EWLKMfgOrderLineData data)
|
||||
{
|
||||
_data = data;
|
||||
if (!string.IsNullOrEmpty(data.Summary.FactoryName))
|
||||
_tab1Btn.text = data.Summary.FactoryName;
|
||||
|
||||
// 공장구분이 수신되면 탭 레이블 갱신
|
||||
if (!string.IsNullOrEmpty(data.Line1.Summary.FactoryName))
|
||||
_tab1Btn.text = data.Line1.Summary.FactoryName;
|
||||
if (!string.IsNullOrEmpty(data.Line2.Summary.FactoryName))
|
||||
_tab2Btn.text = data.Line2.Summary.FactoryName;
|
||||
|
||||
_currentLine = _activeTabIndex == 0 ? data.Line1 : data.Line2;
|
||||
_currentLine = data;
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
// ── 탭 선택 ───────────────────────────────────────────
|
||||
// ── 탭 (단일 탭, 호환성 유지) ──
|
||||
|
||||
private void SelectTab(int index)
|
||||
{
|
||||
_activeTabIndex = index;
|
||||
SetTabActive(index);
|
||||
if (_data != null)
|
||||
_currentLine = index == 0 ? _data.Line1 : _data.Line2;
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
private void SetTabActive(int index)
|
||||
{
|
||||
_tab1Btn.EnableInClassList("ewlk-mfgorder__tab--active", index == 0);
|
||||
_tab2Btn.EnableInClassList("ewlk-mfgorder__tab--active", index == 1);
|
||||
}
|
||||
|
||||
// ── 뷰 갱신 ───────────────────────────────────────────
|
||||
// ── 뷰 갱신 ──
|
||||
|
||||
private void RefreshView()
|
||||
{
|
||||
if (_currentLine == null)
|
||||
{
|
||||
// 데이터 없음 — 플레이스홀더 표시
|
||||
_totalTargetLabel.text = "-";
|
||||
_totalActualLabel.text = "-";
|
||||
_totalProgressLabel.text = "-";
|
||||
_totalProgressFill.style.width = Length.Percent(0f);
|
||||
_totalTargetFill.style.width = Length.Percent(0f);
|
||||
_totalActualFill.style.width = Length.Percent(0f);
|
||||
_equipRateLabel.text = "-";
|
||||
_equipRateFill.style.width = Length.Percent(0f);
|
||||
_countLabel.text = "- / -";
|
||||
_countLabel.text = "- / - 대";
|
||||
|
||||
_displayRows = s_PlaceholderRows;
|
||||
_listView.itemsSource = _displayRows;
|
||||
_listView.Rebuild();
|
||||
RebuildRows();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -178,184 +243,213 @@ namespace UVC.EnglewoodLAB.UIToolkit
|
||||
_totalTargetLabel.text = Or(s.TotalTarget, "-");
|
||||
_totalActualLabel.text = Or(s.TotalActual, "-");
|
||||
_totalProgressLabel.text = $"{s.TotalProgress:F1}%";
|
||||
_totalProgressFill.style.width =
|
||||
Length.Percent(Mathf.Clamp(s.TotalProgress, 0f, 100f));
|
||||
|
||||
_totalTargetFill.style.width = Length.Percent(100f);
|
||||
float actualRatio = s.TotalProgress > 0 ? Mathf.Clamp(s.TotalProgress, 0f, 100f) : 0f;
|
||||
_totalActualFill.style.width = Length.Percent(actualRatio);
|
||||
_totalProgressFill.style.width = Length.Percent(Mathf.Clamp(s.TotalProgress, 0f, 100f));
|
||||
|
||||
_equipRateLabel.text = $"{s.EquipRate:F1}%";
|
||||
_equipRateFill.style.width =
|
||||
Length.Percent(Mathf.Clamp(s.EquipRate, 0f, 100f));
|
||||
_countLabel.text = $"{s.ActiveCount} / {s.TotalCount}";
|
||||
_equipRateFill.style.width = Length.Percent(Mathf.Clamp(s.EquipRate, 0f, 100f));
|
||||
_countLabel.text = $"{s.ActiveCount} / {s.TotalCount} 대";
|
||||
|
||||
_displayRows = _currentLine.Rows;
|
||||
_listView.itemsSource = _displayRows;
|
||||
_listView.Rebuild();
|
||||
_allRows = _currentLine.Rows;
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
// ── ListView 바인딩 ───────────────────────────────────
|
||||
// ── 검색 ──
|
||||
|
||||
private const string SearchPlaceholder = "설비명, 지시번호, 품목 코드, 품목명을 검색해 보세요.";
|
||||
|
||||
private void OnSearchChanged(ChangeEvent<string> evt)
|
||||
{
|
||||
var val = evt.newValue ?? string.Empty;
|
||||
_searchText = val == SearchPlaceholder ? string.Empty : val;
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
/// <summary>검색어로 행을 필터링하고 재생성합니다.</summary>
|
||||
private void ApplyFilter()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_searchText))
|
||||
{
|
||||
_displayRows = _allRows;
|
||||
}
|
||||
else
|
||||
{
|
||||
var keyword = _searchText.Trim();
|
||||
_displayRows = new List<EWLKMfgOrderRowData>();
|
||||
foreach (var row in _allRows)
|
||||
{
|
||||
if (Contains(row.EquipName, keyword) ||
|
||||
Contains(row.OrderNo, keyword) ||
|
||||
Contains(row.ItemCode, keyword) ||
|
||||
Contains(row.ItemName, keyword))
|
||||
{
|
||||
_displayRows.Add(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RebuildRows();
|
||||
}
|
||||
|
||||
private static bool Contains(string source, string keyword) =>
|
||||
!string.IsNullOrEmpty(source) &&
|
||||
source.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
|
||||
/// <summary>ScrollView 내 행을 재생성합니다.</summary>
|
||||
private void RebuildRows()
|
||||
{
|
||||
_rowContainer.Clear();
|
||||
for (int i = 0; i < _displayRows.Count; i++)
|
||||
{
|
||||
var row = MakeRowElement();
|
||||
BindRowElement(row, i);
|
||||
_rowContainer.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
// ── ListView 바인딩 ──
|
||||
|
||||
/// <summary>
|
||||
/// 행 요소를 생성합니다. 모든 셀과 서브 요소를 미리 생성합니다.
|
||||
/// </summary>
|
||||
private static VisualElement MakeRowElement()
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("ewlk-mfgorder__row");
|
||||
|
||||
// 문자열 셀 6개 (설비명~목표량)
|
||||
var textCols = new[] {
|
||||
"col-equip", "col-order", "col-item-code",
|
||||
"col-item-name", "col-time", "col-target",
|
||||
};
|
||||
foreach (var cls in textCols)
|
||||
foreach (var (cls, _) in s_Columns)
|
||||
{
|
||||
var cell = new VisualElement();
|
||||
cell.AddToClassList("ewlk-mfgorder__cell");
|
||||
cell.AddToClassList(cls);
|
||||
var lbl = new Label();
|
||||
lbl.AddToClassList("ewlk-mfgorder__cell-label");
|
||||
cell.Add(lbl);
|
||||
|
||||
if (cls == "col-achievement")
|
||||
{
|
||||
// 진척률: 프로그레스 바 + 퍼센트 라벨
|
||||
var barWrap = new VisualElement();
|
||||
barWrap.name = "bar-wrap";
|
||||
barWrap.AddToClassList("ewlk-mfgorder__progress-wrap");
|
||||
var fill = new VisualElement();
|
||||
fill.name = "achieve-fill";
|
||||
fill.AddToClassList("ewlk-mfgorder__progress-fill");
|
||||
barWrap.Add(fill);
|
||||
cell.Add(barWrap);
|
||||
|
||||
var pctLbl = new Label();
|
||||
pctLbl.name = "pct-label";
|
||||
pctLbl.AddToClassList("ewlk-mfgorder__pct");
|
||||
cell.Add(pctLbl);
|
||||
}
|
||||
else
|
||||
{
|
||||
var lbl = new Label();
|
||||
lbl.AddToClassList("ewlk-mfgorder__cell-label");
|
||||
|
||||
// 품목명: 텍스트 줄임 + 툴팁
|
||||
if (cls == "col-item-name")
|
||||
lbl.AddToClassList("ewlk-mfgorder__cell-label--ellipsis");
|
||||
|
||||
cell.Add(lbl);
|
||||
}
|
||||
|
||||
row.Add(cell);
|
||||
}
|
||||
|
||||
// 달성률 셀 (progress + stopped-label + pct-label)
|
||||
var achieveCell = new VisualElement();
|
||||
achieveCell.AddToClassList("ewlk-mfgorder__cell");
|
||||
achieveCell.AddToClassList("col-achievement");
|
||||
|
||||
var barWrap = new VisualElement();
|
||||
barWrap.name = "bar-wrap";
|
||||
barWrap.AddToClassList("ewlk-mfgorder__progress-wrap");
|
||||
var fill = new VisualElement();
|
||||
fill.name = "achieve-fill";
|
||||
fill.AddToClassList("ewlk-mfgorder__progress-fill");
|
||||
barWrap.Add(fill);
|
||||
|
||||
var stoppedLbl = new Label("계획 정지");
|
||||
stoppedLbl.name = "stopped-label";
|
||||
stoppedLbl.AddToClassList("ewlk-mfgorder__stopped");
|
||||
|
||||
var pctLbl = new Label();
|
||||
pctLbl.name = "pct-label";
|
||||
pctLbl.AddToClassList("ewlk-mfgorder__pct");
|
||||
|
||||
achieveCell.Add(barWrap);
|
||||
achieveCell.Add(stoppedLbl);
|
||||
achieveCell.Add(pctLbl);
|
||||
row.Add(achieveCell);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ListView 바인딩. _displayRows(실데이터 또는 플레이스홀더)를 사용합니다.
|
||||
/// </summary>
|
||||
private void BindRowElement(VisualElement element, int index)
|
||||
{
|
||||
if (index >= _displayRows.Count) return;
|
||||
var d = _displayRows[index];
|
||||
|
||||
SetLabel(element, "col-equip", d.EquipName);
|
||||
SetLabel(element, "col-order", d.OrderNo);
|
||||
// No (1-based)
|
||||
SetLabel(element, "col-no", (index + 1).ToString());
|
||||
SetLabel(element, "col-equip", d.EquipName);
|
||||
SetLabel(element, "col-order", d.OrderNo);
|
||||
SetLabel(element, "col-item-code", d.ItemCode);
|
||||
SetLabel(element, "col-item-name", d.ItemName);
|
||||
SetLabel(element, "col-time", d.StartTime);
|
||||
SetLabel(element, "col-target", d.Target);
|
||||
|
||||
var barWrap = element.Q<VisualElement>("bar-wrap");
|
||||
var fill = element.Q<VisualElement>("achieve-fill");
|
||||
var stoppedLbl = element.Q<Label>("stopped-label");
|
||||
var pctLbl = element.Q<Label>("pct-label");
|
||||
// 품목명: 5글자 초과 시 줄임 + 툴팁
|
||||
SetItemNameLabel(element, d.ItemName);
|
||||
|
||||
if (d.IsStopped)
|
||||
{
|
||||
if (barWrap != null) barWrap.style.display = DisplayStyle.None;
|
||||
if (stoppedLbl != null) stoppedLbl.style.display = DisplayStyle.Flex;
|
||||
if (pctLbl != null) pctLbl.style.display = DisplayStyle.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
float pct = Mathf.Clamp(d.Achievement, 0f, 100f);
|
||||
if (barWrap != null)
|
||||
{
|
||||
barWrap.style.display = DisplayStyle.Flex;
|
||||
if (fill != null) fill.style.width = Length.Percent(pct);
|
||||
}
|
||||
if (stoppedLbl != null) stoppedLbl.style.display = DisplayStyle.None;
|
||||
if (pctLbl != null)
|
||||
{
|
||||
pctLbl.style.display = DisplayStyle.Flex;
|
||||
pctLbl.text = $"{d.Achievement:F1}%";
|
||||
}
|
||||
}
|
||||
SetLabel(element, "col-start-time", d.StartTime);
|
||||
SetLabel(element, "col-end-time", d.EndTime);
|
||||
SetLabel(element, "col-work-time", d.TotalWorkTime);
|
||||
SetLabel(element, "col-plan-prod", d.PlanProduction);
|
||||
SetLabel(element, "col-actual-prod", d.ActualProduction);
|
||||
|
||||
// 진척률
|
||||
var fill = element.Q<VisualElement>("achieve-fill");
|
||||
var pctLbl = element.Q<Label>("pct-label");
|
||||
float pct = Mathf.Clamp(d.Achievement, 0f, 100f);
|
||||
if (fill != null) fill.style.width = Length.Percent(pct);
|
||||
if (pctLbl != null) pctLbl.text = $"{d.Achievement:F0}";
|
||||
}
|
||||
|
||||
// ── 헬퍼 ──────────────────────────────────────────────
|
||||
// ── 헬퍼 ──
|
||||
|
||||
private static void SetLabel(VisualElement row, string cellClass, string text)
|
||||
{
|
||||
var cell = row.Q(className: cellClass);
|
||||
var lbl = cell?.Q<Label>();
|
||||
var lbl = cell?.Q<Label>(className: "ewlk-mfgorder__cell-label");
|
||||
if (lbl != null) lbl.text = Or(text, "-");
|
||||
}
|
||||
|
||||
private static void SetItemNameLabel(VisualElement row, string itemName)
|
||||
{
|
||||
var cell = row.Q(className: "col-item-name");
|
||||
var lbl = cell?.Q<Label>(className: "ewlk-mfgorder__cell-label");
|
||||
if (lbl == null) return;
|
||||
|
||||
string displayText = Or(itemName, "-");
|
||||
|
||||
if (displayText.Length > ItemNameMaxChars)
|
||||
{
|
||||
lbl.text = displayText[..ItemNameMaxChars] + "...";
|
||||
lbl.tooltip = displayText;
|
||||
}
|
||||
else
|
||||
{
|
||||
lbl.text = displayText;
|
||||
lbl.tooltip = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static string Or(string value, string fallback) =>
|
||||
string.IsNullOrEmpty(value) ? fallback : value;
|
||||
|
||||
/// <summary>
|
||||
/// MQTT 연결 전에 레이아웃을 미리 확인하기 위한 플레이스홀더 행 생성.
|
||||
/// Achievement = 10f 로 달성률 바 그래프를 기본 표시합니다.
|
||||
/// </summary>
|
||||
private static List<EWLKMfgOrderRowData> CreatePlaceholderRows()
|
||||
{
|
||||
var rows = new List<EWLKMfgOrderRowData>();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
rows.Add(new EWLKMfgOrderRowData
|
||||
{
|
||||
Achievement = 10f,
|
||||
IsStopped = false,
|
||||
});
|
||||
}
|
||||
rows.Add(EWLKMfgOrderRowData.CreatePlaceholder());
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static VisualElement MakeStaticItem(string title, out Label valueLabel)
|
||||
/// <summary>가로 배치 바 아이템: [라벨] [값] [프로그레스 바]</summary>
|
||||
private static VisualElement MakeBarItem(string title, out Label valueLabel, out VisualElement fill)
|
||||
{
|
||||
var wrap = new VisualElement();
|
||||
wrap.AddToClassList("ewlk-mfgorder__summary-item");
|
||||
wrap.AddToClassList("ewlk-mfgorder__bar-item");
|
||||
|
||||
var t = new Label(title);
|
||||
t.AddToClassList("ewlk-mfgorder__summary-title");
|
||||
wrap.Add(t);
|
||||
// 라벨
|
||||
var titleLbl = new Label(title);
|
||||
titleLbl.AddToClassList("ewlk-mfgorder__bar-item-title");
|
||||
wrap.Add(titleLbl);
|
||||
|
||||
// 값
|
||||
valueLabel = new Label("-");
|
||||
valueLabel.AddToClassList("ewlk-mfgorder__summary-value");
|
||||
valueLabel.AddToClassList("ewlk-mfgorder__bar-item-value");
|
||||
wrap.Add(valueLabel);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
private static VisualElement MakeProgressItem(
|
||||
string title,
|
||||
out VisualElement fill,
|
||||
out Label valueLabel)
|
||||
{
|
||||
var wrap = new VisualElement();
|
||||
wrap.AddToClassList("ewlk-mfgorder__summary-item");
|
||||
|
||||
var t = new Label(title);
|
||||
t.AddToClassList("ewlk-mfgorder__summary-title");
|
||||
wrap.Add(t);
|
||||
|
||||
var barWrap = new VisualElement();
|
||||
barWrap.AddToClassList("ewlk-mfgorder__progress-wrap");
|
||||
// 프로그레스 바
|
||||
var barBg = new VisualElement();
|
||||
barBg.AddToClassList("ewlk-mfgorder__progress-wrap");
|
||||
fill = new VisualElement();
|
||||
fill.AddToClassList("ewlk-mfgorder__progress-fill");
|
||||
barWrap.Add(fill);
|
||||
wrap.Add(barWrap);
|
||||
barBg.Add(fill);
|
||||
wrap.Add(barBg);
|
||||
|
||||
valueLabel = new Label("-");
|
||||
valueLabel.AddToClassList("ewlk-mfgorder__summary-value");
|
||||
wrap.Add(valueLabel);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
@@ -364,22 +458,14 @@ namespace UVC.EnglewoodLAB.UIToolkit
|
||||
var header = new VisualElement();
|
||||
header.AddToClassList("ewlk-mfgorder__header");
|
||||
|
||||
var cols = new[] {
|
||||
("설비명", "col-equip"),
|
||||
("지시번호", "col-order"),
|
||||
("품목코드", "col-item-code"),
|
||||
("품목명", "col-item-name"),
|
||||
("시작시간", "col-time"),
|
||||
("목표량", "col-target"),
|
||||
("달성률", "col-achievement"),
|
||||
};
|
||||
foreach (var (text, cls) in cols)
|
||||
foreach (var (cls, text) in s_Columns)
|
||||
{
|
||||
var cell = new Label(text);
|
||||
cell.AddToClassList("ewlk-mfgorder__header-cell");
|
||||
cell.AddToClassList(cls);
|
||||
header.Add(cell);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,33 +7,28 @@ using UVC.EnglewoodLAB.Data;
|
||||
namespace UVC.EnglewoodLAB.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// OverView 모달 내부 테이블 컨텐츠.
|
||||
/// 3개 작업장의 월간/일간 생산 실적(목표수량·현시점 계획·실적수량·구분)을 표시합니다.
|
||||
/// OverView 컨텐츠.
|
||||
/// 3개 작업장 컬럼, 각 컬럼에 월간/일간 섹션, 프로그레스 바 포함.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var content = new EWLKOverViewModalContent();
|
||||
/// content.UpdateData(mqttService.CurrentData);
|
||||
/// mqttService.OnDataUpdated += content.UpdateData;
|
||||
///
|
||||
/// var modal = UTKModal.Create("OVERVIEW", UTKModal.ModalSize.Large);
|
||||
/// modal.Add(content);
|
||||
/// await modal.ShowAsync();
|
||||
///
|
||||
/// mqttService.OnDataUpdated -= content.UpdateData;
|
||||
/// content.Dispose();
|
||||
/// </code>
|
||||
/// </example>
|
||||
[UxmlElement]
|
||||
public partial class EWLKOverViewModalContent : VisualElement, IDisposable
|
||||
{
|
||||
private const string USS_PATH = "EWLK/UIToolkit/Main/EWLKOverViewModalContentUss";
|
||||
|
||||
// 데이터 셀 참조 캐시 [작업장, 기간, 컬럼]
|
||||
// 작업장: 0=제조작업장, 1=충포장(3F), 2=충포장(4F)
|
||||
// 기간: 0=월간, 1=일간
|
||||
// 컬럼: 0=목표수량, 1=현시점계획, 2=실적수량, 3=구분
|
||||
private readonly Label[,,] _cells = new Label[3, 2, 4];
|
||||
// 작업장 이름 / 구분
|
||||
private static readonly string[] WorkshopNames = { "제조 작업장 · 제조", "충/포장 작업장(3F) · 생산", "충/포장 작업장(4F) · 생산" };
|
||||
|
||||
// 데이터 셀 캐시 [작업장, 기간, 행]
|
||||
// 작업장: 0=제조, 1=충포장3F, 2=충포장4F
|
||||
// 기간: 0=월간, 1=일간
|
||||
// 행: 0=목표수량, 1=현시점계획, 2=실적수량
|
||||
private readonly Label[,,] _valueCells = new Label[3, 2, 3];
|
||||
private readonly VisualElement[,,] _progressBars = new VisualElement[3, 2, 3];
|
||||
|
||||
public EWLKOverViewModalContent()
|
||||
{
|
||||
@@ -44,98 +39,106 @@ namespace UVC.EnglewoodLAB.UIToolkit
|
||||
BuildUI();
|
||||
}
|
||||
|
||||
// ── UI 구성 ───────────────────────────────────────────────
|
||||
|
||||
private void BuildUI()
|
||||
{
|
||||
Add(BuildHeader());
|
||||
// 3개 작업장 컬럼을 가로로 배치
|
||||
var columnsContainer = new VisualElement();
|
||||
columnsContainer.AddToClassList("ewlk-overview__columns");
|
||||
|
||||
string[] names = { "제조작업장", "충포장작업장(3F)", "충포장작업장(4F)" };
|
||||
for (int wi = 0; wi < 3; wi++)
|
||||
Add(BuildWorkshopGroup(wi, names[wi]));
|
||||
}
|
||||
|
||||
/// <summary>헤더 행을 생성합니다.</summary>
|
||||
private static VisualElement BuildHeader()
|
||||
{
|
||||
var header = new VisualElement();
|
||||
header.AddToClassList("ewlk-overview__header");
|
||||
|
||||
header.Add(MakeHeaderCell("구분", "ewlk-overview__cell--name"));
|
||||
header.Add(MakeHeaderCell("", "ewlk-overview__cell--period"));
|
||||
header.Add(MakeHeaderCell("목표수량", "ewlk-overview__cell--data"));
|
||||
header.Add(MakeHeaderCell("현시점 계획", "ewlk-overview__cell--data"));
|
||||
header.Add(MakeHeaderCell("실적수량", "ewlk-overview__cell--data"));
|
||||
header.Add(MakeHeaderCell("구분", "ewlk-overview__cell--type"));
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 작업장 행 그룹을 생성합니다.
|
||||
/// 왼쪽에 작업장 이름이 2행(월간+일간)에 걸쳐 표시됩니다.
|
||||
/// </summary>
|
||||
private VisualElement BuildWorkshopGroup(int wi, string name)
|
||||
{
|
||||
var group = new VisualElement();
|
||||
group.AddToClassList("ewlk-overview__group");
|
||||
|
||||
// 왼쪽: 작업장 이름 셀 (2행 height 걸쳐 세로 중앙 정렬)
|
||||
var nameCell = new Label(name);
|
||||
nameCell.AddToClassList("ewlk-overview__cell");
|
||||
nameCell.AddToClassList("ewlk-overview__cell--name");
|
||||
group.Add(nameCell);
|
||||
|
||||
// 오른쪽: 월간/일간 행 컨테이너
|
||||
var rows = new VisualElement();
|
||||
rows.AddToClassList("ewlk-overview__rows");
|
||||
|
||||
string[] periodLabels = { "월간", "일간" };
|
||||
for (int pi = 0; pi < 2; pi++)
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("ewlk-overview__row");
|
||||
|
||||
// 기간 레이블 (월간/일간)
|
||||
var periodLabel = new Label(periodLabels[pi]);
|
||||
periodLabel.AddToClassList("ewlk-overview__cell");
|
||||
periodLabel.AddToClassList("ewlk-overview__cell--period");
|
||||
row.Add(periodLabel);
|
||||
|
||||
// 데이터 셀 4개: 목표수량·현시점계획·실적수량·구분
|
||||
for (int ci = 0; ci < 4; ci++)
|
||||
{
|
||||
var cell = new Label();
|
||||
cell.AddToClassList("ewlk-overview__cell");
|
||||
cell.AddToClassList(ci < 3
|
||||
? "ewlk-overview__cell--data"
|
||||
: "ewlk-overview__cell--type");
|
||||
_cells[wi, pi, ci] = cell;
|
||||
row.Add(cell);
|
||||
}
|
||||
|
||||
rows.Add(row);
|
||||
var column = BuildWorkshopColumn(wi);
|
||||
columnsContainer.Add(column);
|
||||
}
|
||||
|
||||
group.Add(rows);
|
||||
return group;
|
||||
Add(columnsContainer);
|
||||
}
|
||||
|
||||
private static Label MakeHeaderCell(string text, string sizeClass)
|
||||
/// <summary>작업장 컬럼 1개를 생성합니다.</summary>
|
||||
private VisualElement BuildWorkshopColumn(int wi)
|
||||
{
|
||||
var label = new Label(text);
|
||||
label.AddToClassList("ewlk-overview__header-cell");
|
||||
label.AddToClassList(sizeClass);
|
||||
return label;
|
||||
var column = new VisualElement();
|
||||
column.AddToClassList("ewlk-overview__column");
|
||||
|
||||
// 컬럼 제목
|
||||
var title = new Label(WorkshopNames[wi]);
|
||||
title.AddToClassList("ewlk-overview__column-title");
|
||||
column.Add(title);
|
||||
|
||||
// 월간 섹션
|
||||
column.Add(BuildPeriodSection(wi, 0, "월간"));
|
||||
|
||||
// 일간 섹션
|
||||
column.Add(BuildPeriodSection(wi, 1, "일간"));
|
||||
|
||||
return column;
|
||||
}
|
||||
|
||||
/// <summary>월간/일간 섹션을 생성합니다.</summary>
|
||||
private VisualElement BuildPeriodSection(int wi, int pi, string periodName)
|
||||
{
|
||||
var section = new VisualElement();
|
||||
section.AddToClassList("ewlk-overview__section");
|
||||
|
||||
// 기간 라벨
|
||||
var periodLabel = new Label(periodName);
|
||||
periodLabel.AddToClassList("ewlk-overview__period-label");
|
||||
section.Add(periodLabel);
|
||||
|
||||
// 구분선
|
||||
var divider = new VisualElement();
|
||||
divider.AddToClassList("ewlk-overview__divider");
|
||||
section.Add(divider);
|
||||
|
||||
// 3행: 목표수량, 현시점 계획, 실적수량
|
||||
string[] rowNames = { "목표 수량", "현시점 계획", "실적 수량" };
|
||||
string[] barClasses = { "ewlk-overview__bar--target", "ewlk-overview__bar--plan", "ewlk-overview__bar--actual" };
|
||||
string[] valueClasses = { "ewlk-overview__value--target", "ewlk-overview__value--plan", "ewlk-overview__value--actual" };
|
||||
|
||||
for (int ri = 0; ri < 3; ri++)
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("ewlk-overview__data-row");
|
||||
|
||||
// 라벨 + 값
|
||||
var labelRow = new VisualElement();
|
||||
labelRow.AddToClassList("ewlk-overview__label-row");
|
||||
|
||||
var nameLabel = new Label(rowNames[ri]);
|
||||
nameLabel.AddToClassList("ewlk-overview__data-name");
|
||||
labelRow.Add(nameLabel);
|
||||
|
||||
var valueLabel = new Label("0");
|
||||
valueLabel.AddToClassList("ewlk-overview__data-value");
|
||||
valueLabel.AddToClassList(valueClasses[ri]);
|
||||
_valueCells[wi, pi, ri] = valueLabel;
|
||||
labelRow.Add(valueLabel);
|
||||
|
||||
row.Add(labelRow);
|
||||
|
||||
// 프로그레스 바
|
||||
var barBg = new VisualElement();
|
||||
barBg.AddToClassList("ewlk-overview__bar-bg");
|
||||
|
||||
var barFill = new VisualElement();
|
||||
barFill.AddToClassList("ewlk-overview__bar-fill");
|
||||
barFill.AddToClassList(barClasses[ri]);
|
||||
_progressBars[wi, pi, ri] = barFill;
|
||||
barBg.Add(barFill);
|
||||
|
||||
row.Add(barBg);
|
||||
|
||||
section.Add(row);
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
// ── 데이터 갱신 ───────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// OverView 데이터로 테이블을 갱신합니다.
|
||||
/// MQTT 데이터 수신 시 또는 모달 열릴 때 호출합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">갱신할 OverView 데이터</param>
|
||||
public void UpdateData(EWLKOverViewData data)
|
||||
{
|
||||
var workshops = new[]
|
||||
@@ -154,13 +157,52 @@ namespace UVC.EnglewoodLAB.UIToolkit
|
||||
|
||||
private void ApplyPeriod(int wi, int pi, EWLKOverViewPeriodData period)
|
||||
{
|
||||
_cells[wi, pi, 0].text = period.Target;
|
||||
_cells[wi, pi, 1].text = period.Plan;
|
||||
_cells[wi, pi, 2].text = period.Actual;
|
||||
_cells[wi, pi, 3].text = period.Type;
|
||||
_valueCells[wi, pi, 0].text = period.Target;
|
||||
_valueCells[wi, pi, 1].text = period.Plan;
|
||||
_valueCells[wi, pi, 2].text = period.Actual;
|
||||
|
||||
// 프로그레스 바 비율 계산
|
||||
UpdateProgressBar(wi, pi, 0, period.Target, period.Target); // 목표 = 100%
|
||||
UpdateProgressBar(wi, pi, 1, period.Plan, period.Target); // 계획/목표
|
||||
UpdateProgressBar(wi, pi, 2, period.Actual, period.Target); // 실적/목표
|
||||
}
|
||||
|
||||
// ── IDisposable ───────────────────────────────────────────
|
||||
/// <summary>프로그레스 바 비율을 업데이트합니다.</summary>
|
||||
private void UpdateProgressBar(int wi, int pi, int ri, string valueStr, string maxStr)
|
||||
{
|
||||
float ratio = ParseRatio(valueStr, maxStr);
|
||||
// 최소 3%로 색상이 보이도록
|
||||
float percent = Mathf.Max(ratio * 100f, 3f);
|
||||
_progressBars[wi, pi, ri].style.width = new Length(percent, LengthUnit.Percent);
|
||||
}
|
||||
|
||||
/// <summary>문자열에서 숫자를 추출하여 비율을 계산합니다.</summary>
|
||||
private static float ParseRatio(string valueStr, string maxStr)
|
||||
{
|
||||
float value = ExtractNumber(valueStr);
|
||||
float max = ExtractNumber(maxStr);
|
||||
if (max <= 0) return 0f;
|
||||
return Mathf.Clamp01(value / max);
|
||||
}
|
||||
|
||||
/// <summary>문자열에서 첫 번째 숫자를 추출합니다.</summary>
|
||||
private static float ExtractNumber(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str)) return 0f;
|
||||
|
||||
// 숫자와 소수점, 콤마만 남기기
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (char c in str)
|
||||
{
|
||||
if (char.IsDigit(c) || c == '.') sb.Append(c);
|
||||
else if (c == ',') continue; // 천단위 구분자 제거
|
||||
else if (sb.Length > 0) break; // 숫자 끝
|
||||
}
|
||||
|
||||
if (float.TryParse(sb.ToString(), out float result))
|
||||
return result;
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
353
Assets/Scripts/EnglewoodLAB/UIToolkit/EWLKPackOrderContent.cs
Normal file
353
Assets/Scripts/EnglewoodLAB/UIToolkit/EWLKPackOrderContent.cs
Normal file
@@ -0,0 +1,353 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.EnglewoodLAB.Data;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
namespace UVC.EnglewoodLAB.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 충/포장 지시현황 컨텐츠.
|
||||
/// 검색창 + 층/라인 필터 + 14컬럼 테이블 (ScrollView).
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class EWLKPackOrderContent : VisualElement, IDisposable
|
||||
{
|
||||
private const string UssPath = "EWLK/UIToolkit/Main/EWLKPackOrderContentUss";
|
||||
private const int ItemNameMaxChars = 5;
|
||||
private const string SearchPlaceholder = "설비번호, 설비명, 지시번호, 품목코드, 품목명을 검색해 보세요.";
|
||||
|
||||
// 14컬럼 정의
|
||||
private static readonly (string cls, string header)[] s_Columns =
|
||||
{
|
||||
("col-no", "NO"),
|
||||
("col-floor", "층 구분"),
|
||||
("col-line", "설비라인"),
|
||||
("col-equip", "설비명"),
|
||||
("col-order", "지시번호"),
|
||||
("col-item-code", "품목 코드"),
|
||||
("col-item-name", "품목명"),
|
||||
("col-start-time", "시작 시간"),
|
||||
("col-end-time", "종료 시간"),
|
||||
("col-work-time", "총 작업 시간"),
|
||||
("col-capa", "설비 CAPA"),
|
||||
("col-plan-prod", "계획 생산량(kg)"),
|
||||
("col-actual-prod", "실 생산량(kg)"),
|
||||
("col-achievement", "진척률(%)"),
|
||||
};
|
||||
|
||||
// ── UI 요소 ──
|
||||
private readonly TextField _searchField;
|
||||
private readonly DropdownField _floorFilter;
|
||||
private readonly DropdownField _lineFilter;
|
||||
private readonly ScrollView _scrollView;
|
||||
private readonly VisualElement _rowContainer;
|
||||
|
||||
// ── 상태 ──
|
||||
private string _searchText = string.Empty;
|
||||
private string _selectedFloor = "층 전체";
|
||||
private string _selectedLine = "라인 전체";
|
||||
private List<EWLKPackOrderRowData> _allRows = new();
|
||||
private List<EWLKPackOrderRowData> _displayRows = new();
|
||||
|
||||
private static readonly List<EWLKPackOrderRowData> s_PlaceholderRows = CreatePlaceholderRows();
|
||||
|
||||
public EWLKPackOrderContent()
|
||||
{
|
||||
var uss = Resources.Load<StyleSheet>(UssPath);
|
||||
if (uss != null) styleSheets.Add(uss);
|
||||
|
||||
AddToClassList("ewlk-packorder");
|
||||
|
||||
// ── 필터 바 (검색 + 층 + 라인) ──
|
||||
var filterBar = new VisualElement();
|
||||
filterBar.AddToClassList("ewlk-packorder__filter-bar");
|
||||
filterBar.pickingMode = PickingMode.Position;
|
||||
|
||||
// 검색창
|
||||
var searchWrap = new VisualElement();
|
||||
searchWrap.AddToClassList("ewlk-packorder__search-wrap");
|
||||
searchWrap.pickingMode = PickingMode.Position;
|
||||
|
||||
var searchIcon = new Label(UTKMaterialIcons.Search);
|
||||
searchIcon.AddToClassList("ewlk-packorder__search-icon");
|
||||
UTKMaterialIcons.ApplyIconStyle(searchIcon, 16);
|
||||
searchWrap.Add(searchIcon);
|
||||
|
||||
_searchField = new TextField();
|
||||
_searchField.AddToClassList("ewlk-packorder__search-field");
|
||||
_searchField.focusable = true;
|
||||
_searchField.pickingMode = PickingMode.Position;
|
||||
_searchField.value = SearchPlaceholder;
|
||||
_searchField.AddToClassList("ewlk-packorder__search-field--placeholder");
|
||||
_searchField.RegisterCallback<FocusInEvent>(_ =>
|
||||
{
|
||||
if (_searchField.value == SearchPlaceholder)
|
||||
{
|
||||
_searchField.value = string.Empty;
|
||||
_searchField.RemoveFromClassList("ewlk-packorder__search-field--placeholder");
|
||||
}
|
||||
});
|
||||
_searchField.RegisterCallback<FocusOutEvent>(_ =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(_searchField.value))
|
||||
{
|
||||
_searchField.value = SearchPlaceholder;
|
||||
_searchField.AddToClassList("ewlk-packorder__search-field--placeholder");
|
||||
}
|
||||
});
|
||||
_searchField.RegisterCallback<ChangeEvent<string>>(evt =>
|
||||
{
|
||||
var val = evt.newValue ?? string.Empty;
|
||||
_searchText = val == SearchPlaceholder ? string.Empty : val;
|
||||
ApplyFilter();
|
||||
});
|
||||
|
||||
// picking/focusable + 배경색 (TextField 내부 요소는 USS specificity로 덮어쓸 수 없음)
|
||||
var textInput = _searchField.Q("unity-text-input");
|
||||
if (textInput != null)
|
||||
{
|
||||
textInput.pickingMode = PickingMode.Position;
|
||||
textInput.focusable = true;
|
||||
textInput.style.backgroundColor = Color.white;
|
||||
textInput.style.borderTopWidth = 0;
|
||||
textInput.style.borderBottomWidth = 0;
|
||||
textInput.style.borderLeftWidth = 0;
|
||||
textInput.style.borderRightWidth = 0;
|
||||
}
|
||||
|
||||
searchWrap.Add(_searchField);
|
||||
filterBar.Add(searchWrap);
|
||||
|
||||
// 층 필터
|
||||
var floorChoices = new List<string> { "층 전체", "1층", "3층", "4층" };
|
||||
_floorFilter = new DropdownField(floorChoices, 0) { label = string.Empty };
|
||||
_floorFilter.AddToClassList("ewlk-packorder__filter-dropdown");
|
||||
_floorFilter.pickingMode = PickingMode.Position;
|
||||
_floorFilter.focusable = true;
|
||||
ApplyDropdownStyle(_floorFilter, "층 전체");
|
||||
_floorFilter.RegisterCallback<ChangeEvent<string>>(evt =>
|
||||
{
|
||||
_selectedFloor = evt.newValue ?? "층 전체";
|
||||
ApplyFilter();
|
||||
});
|
||||
filterBar.Add(_floorFilter);
|
||||
|
||||
// 라인 필터
|
||||
var lineChoices = new List<string> { "라인 전체" };
|
||||
for (int i = 1; i <= 10; i++) lineChoices.Add($"{i:D2}라인");
|
||||
_lineFilter = new DropdownField(lineChoices, 0) { label = string.Empty };
|
||||
_lineFilter.AddToClassList("ewlk-packorder__filter-dropdown");
|
||||
_lineFilter.pickingMode = PickingMode.Position;
|
||||
_lineFilter.focusable = true;
|
||||
ApplyDropdownStyle(_lineFilter, "라인 전체");
|
||||
_lineFilter.RegisterCallback<ChangeEvent<string>>(evt =>
|
||||
{
|
||||
_selectedLine = evt.newValue ?? "라인 전체";
|
||||
ApplyFilter();
|
||||
});
|
||||
filterBar.Add(_lineFilter);
|
||||
|
||||
Add(filterBar);
|
||||
|
||||
// ── 테이블 헤더 ──
|
||||
Add(BuildTableHeader());
|
||||
|
||||
// ── ScrollView ──
|
||||
_scrollView = new ScrollView(ScrollViewMode.Vertical);
|
||||
_scrollView.AddToClassList("ewlk-packorder__list");
|
||||
_rowContainer = new VisualElement();
|
||||
_scrollView.Add(_rowContainer);
|
||||
Add(_scrollView);
|
||||
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
// ── 공개 API ──
|
||||
|
||||
/// <summary>MQTT 데이터 갱신.</summary>
|
||||
public void UpdateData(EWLKPackOrderData data)
|
||||
{
|
||||
_allRows = data.Rows;
|
||||
|
||||
// 라인 필터 선택지 업데이트
|
||||
var lines = new List<string> { "라인 전체" };
|
||||
lines.AddRange(data.Rows.Select(r => r.Line).Where(l => !string.IsNullOrEmpty(l) && l != "-").Distinct());
|
||||
_lineFilter.choices = lines;
|
||||
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
// ── 뷰 갱신 ──
|
||||
|
||||
private void RefreshView()
|
||||
{
|
||||
_allRows = s_PlaceholderRows;
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
private void ApplyFilter()
|
||||
{
|
||||
_displayRows = new List<EWLKPackOrderRowData>();
|
||||
|
||||
foreach (var row in _allRows)
|
||||
{
|
||||
// 층 필터
|
||||
if (_selectedFloor != "층 전체" && !Contains(row.Floor, _selectedFloor))
|
||||
continue;
|
||||
|
||||
// 라인 필터
|
||||
if (_selectedLine != "라인 전체" && row.Line != _selectedLine)
|
||||
continue;
|
||||
|
||||
// 검색어 필터
|
||||
if (!string.IsNullOrWhiteSpace(_searchText))
|
||||
{
|
||||
if (!Contains(row.EquipName, _searchText) &&
|
||||
!Contains(row.OrderNo, _searchText) &&
|
||||
!Contains(row.ItemCode, _searchText) &&
|
||||
!Contains(row.ItemName, _searchText))
|
||||
continue;
|
||||
}
|
||||
|
||||
_displayRows.Add(row);
|
||||
}
|
||||
|
||||
RebuildRows();
|
||||
}
|
||||
|
||||
private void RebuildRows()
|
||||
{
|
||||
_rowContainer.Clear();
|
||||
for (int i = 0; i < _displayRows.Count; i++)
|
||||
{
|
||||
var row = MakeRowElement();
|
||||
BindRowElement(row, i);
|
||||
_rowContainer.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 행 생성/바인딩 ──
|
||||
|
||||
private static VisualElement MakeRowElement()
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("ewlk-packorder__row");
|
||||
|
||||
foreach (var (cls, _) in s_Columns)
|
||||
{
|
||||
var cell = new VisualElement();
|
||||
cell.AddToClassList("ewlk-packorder__cell");
|
||||
cell.AddToClassList(cls);
|
||||
|
||||
var lbl = new Label();
|
||||
lbl.AddToClassList("ewlk-packorder__cell-label");
|
||||
if (cls == "col-item-name")
|
||||
lbl.AddToClassList("ewlk-packorder__cell-label--ellipsis");
|
||||
cell.Add(lbl);
|
||||
row.Add(cell);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private void BindRowElement(VisualElement element, int index)
|
||||
{
|
||||
if (index >= _displayRows.Count) return;
|
||||
var d = _displayRows[index];
|
||||
|
||||
SetLabel(element, "col-no", (index + 1).ToString());
|
||||
SetLabel(element, "col-floor", d.Floor);
|
||||
SetLabel(element, "col-line", d.Line);
|
||||
SetLabel(element, "col-equip", d.EquipName);
|
||||
SetLabel(element, "col-order", d.OrderNo);
|
||||
SetLabel(element, "col-item-code", d.ItemCode);
|
||||
SetItemNameLabel(element, d.ItemName);
|
||||
SetLabel(element, "col-start-time", d.StartTime);
|
||||
SetLabel(element, "col-end-time", d.EndTime);
|
||||
SetLabel(element, "col-work-time", d.TotalWorkTime);
|
||||
SetLabel(element, "col-capa", d.EquipCapa);
|
||||
SetLabel(element, "col-plan-prod", d.PlanProduction);
|
||||
SetLabel(element, "col-actual-prod", d.ActualProduction);
|
||||
SetLabel(element, "col-achievement", $"{d.Achievement:F0}");
|
||||
}
|
||||
|
||||
// ── 헬퍼 ──
|
||||
|
||||
private static void SetLabel(VisualElement row, string cellClass, string text)
|
||||
{
|
||||
var cell = row.Q(className: cellClass);
|
||||
var lbl = cell?.Q<Label>(className: "ewlk-packorder__cell-label");
|
||||
if (lbl != null) lbl.text = string.IsNullOrEmpty(text) ? "-" : text;
|
||||
}
|
||||
|
||||
private static void SetItemNameLabel(VisualElement row, string itemName)
|
||||
{
|
||||
var cell = row.Q(className: "col-item-name");
|
||||
var lbl = cell?.Q<Label>(className: "ewlk-packorder__cell-label");
|
||||
if (lbl == null) return;
|
||||
|
||||
string displayText = string.IsNullOrEmpty(itemName) ? "-" : itemName;
|
||||
if (displayText.Length > ItemNameMaxChars)
|
||||
{
|
||||
lbl.text = displayText[..ItemNameMaxChars] + "...";
|
||||
lbl.tooltip = displayText;
|
||||
}
|
||||
else
|
||||
{
|
||||
lbl.text = displayText;
|
||||
lbl.tooltip = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool Contains(string source, string keyword) =>
|
||||
!string.IsNullOrEmpty(source) &&
|
||||
source.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
|
||||
private static List<EWLKPackOrderRowData> CreatePlaceholderRows()
|
||||
{
|
||||
var rows = new List<EWLKPackOrderRowData>();
|
||||
for (int i = 0; i < 5; i++)
|
||||
rows.Add(EWLKPackOrderRowData.CreatePlaceholder());
|
||||
return rows;
|
||||
}
|
||||
|
||||
/// <summary>드롭다운 초기값 설정 + picking 활성화 (비주얼 스타일은 USS).</summary>
|
||||
private static void ApplyDropdownStyle(DropdownField dropdown, string displayText)
|
||||
{
|
||||
dropdown.value = displayText;
|
||||
|
||||
var popupField = dropdown.Q(className: "unity-base-popup-field__input");
|
||||
if (popupField != null)
|
||||
popupField.pickingMode = PickingMode.Position;
|
||||
}
|
||||
|
||||
private static VisualElement BuildTableHeader()
|
||||
{
|
||||
var header = new VisualElement();
|
||||
header.AddToClassList("ewlk-packorder__header");
|
||||
|
||||
foreach (var (cls, text) in s_Columns)
|
||||
{
|
||||
// 행과 동일한 구조 (VisualElement + Label)로 너비 통일
|
||||
var cell = new VisualElement();
|
||||
cell.AddToClassList("ewlk-packorder__cell");
|
||||
cell.AddToClassList("ewlk-packorder__header-cell");
|
||||
cell.AddToClassList(cls);
|
||||
|
||||
var lbl = new Label(text);
|
||||
lbl.AddToClassList("ewlk-packorder__cell-label");
|
||||
cell.Add(lbl);
|
||||
|
||||
header.Add(cell);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26e9beb48027c1340b6fa718c483f14d
|
||||
208
Assets/Scripts/EnglewoodLAB/UIToolkit/EWLKWorkExplorerContent.cs
Normal file
208
Assets/Scripts/EnglewoodLAB/UIToolkit/EWLKWorkExplorerContent.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
#nullable enable
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.EnglewoodLAB.Data;
|
||||
|
||||
namespace UVC.EnglewoodLAB.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 작업 탐색기 컨텐츠.
|
||||
/// 작업장별 설비 상태 + 작업장별 계획 현황 테이블을 표시합니다.
|
||||
/// UpdateData()로 MQTT 데이터를 수신하여 Label을 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// var content = new EWLKWorkExplorerContent();
|
||||
/// content.UpdateData(mqttService.CurrentData); // 초기 데이터
|
||||
/// mqttService.OnDataUpdated += content.UpdateData; // 구독
|
||||
/// // ... 팝업 표시 ...
|
||||
/// mqttService.OnDataUpdated -= content.UpdateData; // 해제
|
||||
/// </example>
|
||||
public class EWLKWorkExplorerContent : VisualElement
|
||||
{
|
||||
private const string UssPath = "EWLK/UIToolkit/Main/EWLKWorkExplorerContentUss";
|
||||
|
||||
// 작업장 이름 (행 순서)
|
||||
private static readonly string[] WorkshopNames = { "제조작업장", "충포장작업장(3F)", "충포장작업장(4F)" };
|
||||
|
||||
// 설비 상태 테이블: [작업장 인덱스, 컬럼 인덱스] (4열)
|
||||
private readonly Label[,] _equipCells = new Label[3, 4];
|
||||
|
||||
// 계획 현황 테이블: [작업장 인덱스, 컬럼 인덱스] (3열)
|
||||
private readonly Label[,] _planCells = new Label[3, 3];
|
||||
|
||||
public EWLKWorkExplorerContent()
|
||||
{
|
||||
var uss = Resources.Load<StyleSheet>(UssPath);
|
||||
if (uss != null) styleSheets.Add(uss);
|
||||
|
||||
AddToClassList("ewlk-work-explorer");
|
||||
|
||||
// 섹션 1: 작업장별 설비 상태
|
||||
Add(CreateSectionTitle("작업장별 설비 상태"));
|
||||
Add(BuildEquipStatusTable());
|
||||
|
||||
// 간격
|
||||
var spacer = new VisualElement();
|
||||
spacer.style.height = 24;
|
||||
Add(spacer);
|
||||
|
||||
// 섹션 2: 작업장별 계획 현황
|
||||
Add(CreateSectionTitle("작업장별 계획 현황"));
|
||||
Add(BuildPlanStatusTable());
|
||||
}
|
||||
|
||||
// ── 공개 API ──────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// MQTT 데이터로 테이블을 업데이트합니다.
|
||||
/// </summary>
|
||||
public void UpdateData(EWLKWorkExplorerData data)
|
||||
{
|
||||
ApplyWorkshopEquip(0, data.Manufacture);
|
||||
ApplyWorkshopEquip(1, data.Packing3F);
|
||||
ApplyWorkshopEquip(2, data.Packing4F);
|
||||
|
||||
ApplyWorkshopPlan(0, data.Manufacture);
|
||||
ApplyWorkshopPlan(1, data.Packing3F);
|
||||
ApplyWorkshopPlan(2, data.Packing4F);
|
||||
}
|
||||
|
||||
private void ApplyWorkshopEquip(int wi, EWLKWorkshopEquipData ws)
|
||||
{
|
||||
_equipCells[wi, 0].text = ws.TotalEquip;
|
||||
_equipCells[wi, 1].text = ws.IdleEquip;
|
||||
_equipCells[wi, 2].text = ws.OperationRate;
|
||||
_equipCells[wi, 3].text = ws.OverallEfficiency;
|
||||
}
|
||||
|
||||
private void ApplyWorkshopPlan(int wi, EWLKWorkshopEquipData ws)
|
||||
{
|
||||
_planCells[wi, 0].text = ws.PlanQuantity;
|
||||
_planCells[wi, 1].text = ws.ActualQuantity;
|
||||
_planCells[wi, 2].text = ws.ProgressRate;
|
||||
}
|
||||
|
||||
// ── 설비 상태 테이블 ─────────────────────────────────
|
||||
|
||||
private VisualElement BuildEquipStatusTable()
|
||||
{
|
||||
var table = new VisualElement();
|
||||
table.AddToClassList("ewlk-work-explorer__table");
|
||||
|
||||
// 헤더
|
||||
table.Add(CreateRow5("작업장명", "전체 설비(라인)", "비가동 설비(라인)", "설비(라인) 가동율", "설비(라인) 종합효율", isHeader: true));
|
||||
table.Add(CreateDivider());
|
||||
|
||||
// 데이터 행
|
||||
for (int wi = 0; wi < 3; wi++)
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("ewlk-work-explorer__row");
|
||||
|
||||
// 작업장명 (고정 텍스트)
|
||||
row.Add(CreateCell(WorkshopNames[wi], 1.5f, isHeader: false));
|
||||
|
||||
// 데이터 4열 (캐싱)
|
||||
for (int ci = 0; ci < 4; ci++)
|
||||
{
|
||||
var cell = CreateCell("-", 1f, isHeader: false);
|
||||
_equipCells[wi, ci] = cell;
|
||||
row.Add(cell);
|
||||
}
|
||||
|
||||
table.Add(row);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
// ── 계획 현황 테이블 ─────────────────────────────────
|
||||
|
||||
private VisualElement BuildPlanStatusTable()
|
||||
{
|
||||
var table = new VisualElement();
|
||||
table.AddToClassList("ewlk-work-explorer__table");
|
||||
|
||||
// 헤더
|
||||
table.Add(CreateRow4("작업장명", "계획량", "실적수량", "진행률", isHeader: true));
|
||||
table.Add(CreateDivider());
|
||||
|
||||
// 데이터 행
|
||||
for (int wi = 0; wi < 3; wi++)
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("ewlk-work-explorer__row");
|
||||
|
||||
// 작업장명 (고정 텍스트)
|
||||
row.Add(CreateCell(WorkshopNames[wi], 1.2f, isHeader: false));
|
||||
|
||||
// 데이터 3열 (캐싱)
|
||||
for (int ci = 0; ci < 3; ci++)
|
||||
{
|
||||
float grow = ci < 2 ? 1.5f : 0.8f;
|
||||
var cell = CreateCell("-", grow, isHeader: false);
|
||||
_planCells[wi, ci] = cell;
|
||||
row.Add(cell);
|
||||
}
|
||||
|
||||
table.Add(row);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
// ── 유틸 ──────────────────────────────────────────
|
||||
|
||||
private Label CreateSectionTitle(string text)
|
||||
{
|
||||
var label = new Label(text);
|
||||
label.AddToClassList("ewlk-work-explorer__section-title");
|
||||
return label;
|
||||
}
|
||||
|
||||
private VisualElement CreateRow5(string c1, string c2, string c3, string c4, string c5, bool isHeader)
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("ewlk-work-explorer__row");
|
||||
if (isHeader) row.AddToClassList("ewlk-work-explorer__row--header");
|
||||
|
||||
row.Add(CreateCell(c1, 1.5f, isHeader));
|
||||
row.Add(CreateCell(c2, 1f, isHeader));
|
||||
row.Add(CreateCell(c3, 1f, isHeader));
|
||||
row.Add(CreateCell(c4, 1f, isHeader));
|
||||
row.Add(CreateCell(c5, 1f, isHeader));
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private VisualElement CreateRow4(string c1, string c2, string c3, string c4, bool isHeader)
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("ewlk-work-explorer__row");
|
||||
if (isHeader) row.AddToClassList("ewlk-work-explorer__row--header");
|
||||
|
||||
row.Add(CreateCell(c1, 1.2f, isHeader));
|
||||
row.Add(CreateCell(c2, 1.5f, isHeader));
|
||||
row.Add(CreateCell(c3, 1.5f, isHeader));
|
||||
row.Add(CreateCell(c4, 0.8f, isHeader));
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private Label CreateCell(string text, float flexGrow, bool isHeader)
|
||||
{
|
||||
var label = new Label(text);
|
||||
label.AddToClassList("ewlk-work-explorer__cell");
|
||||
if (isHeader) label.AddToClassList("ewlk-work-explorer__cell--header");
|
||||
label.style.flexGrow = flexGrow;
|
||||
return label;
|
||||
}
|
||||
|
||||
private VisualElement CreateDivider()
|
||||
{
|
||||
var div = new VisualElement();
|
||||
div.AddToClassList("ewlk-work-explorer__divider");
|
||||
return div;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9252cac8f14704e4dae2baea98cdbfdf
|
||||
Reference in New Issue
Block a user