화면(.jsx)에 JSX 로 배치하고 props 로 설정한다. 명령형 API 가 있는 것은 ref 로 얻어 호출한다.
MocaReactCalendar
import { MocaReactCalendar } from '@teammoca/react-input-calendar'
붙박이 달력 — 화면에 늘 펼쳐진 날짜 선택 달력 (MOCA calendar 이식, 데모 TPL044 대응). InputCalendar 가 "입력칸 + 눌러서 뜨는 달력"이라면 이쪽은 팝업이 아니다: 고른 날짜를 달력 위에서 눈으로 확인하는 것이 목적(예약일·근태·마감일 화면). 제목을 누르면 한 단계 위 뷰로 올라가고(일→월→연) 위 뷰에서 칸을 고르면 내려온다 — 먼 날짜로 빠르게 이동하는 통로. 공휴일·비영업일은 컴포넌트가 모르고 화면이 dayInfo 로 알려준다
지금 적용 중인 범위 { min, max } — props 와 setDateRange 가 합쳐진 최종값
getViewYm()
지금 보고 있는 기준 연월(yyyyMM) — 공휴일 조회 파라미터. 첫 조회에 쓴다
setViewYm(ym)
표시 기준 이동(선택값은 그대로). 기준이 바뀌면 onViewChange 발화
today()
오늘이 속한 달로 이동 — 선택값은 안 바뀐다(이동 전용). 헤더의 [오늘] 버튼과 다르다: 버튼은 오늘을 고르고, 이 메서드는 화면이 명시적으로 부르는 것이라 값을 건드리지 않는다
next()
다음으로 이동(단위는 현재 뷰 — 일=1개월·월=1년·연=10년)
prev()
이전으로 이동
setReadOnly(bool)
선택 잠금 토글(달 이동은 그대로)
MocaReactEchart
import { MocaReactEchart } from '@teammoca/react-echart'
Apache ECharts 를 감싼 차트 컴포넌트 (MOCA plugins/echart.js 이식). option 문법은 ECharts 제품 API 그대로 — 래퍼는 내용을 해석하지 않고 인스턴스 관리(생성·리사이즈·해제)만 담당한다. ResizeObserver 로 크기 변화를 자동 추종(레이아웃 setRatio·MDI 분할·창 크기·모바일 스택 전환에 화면 코드 불필요). 언마운트 시 자동 dispose
사용 예
import { MocaReactEchart } from '@teammoca/react-echart'
const option = { xAxis: {...}, yAxis: {...}, series: [{ type: 'bar', data: [...] }] }
<MocaReactEchart option={option} /> {/* 부모(레이아웃 pane)를 100% 채운다 */}
// 제품 API 직접 사용 — ref 로 실제 echarts 인스턴스를 받는다
chartRef.current?.getChart((c) => c.dispatchAction({ type: 'highlight', dataIndex: 0 }))
Props
option
ECharts option 객체 — 바뀔 때마다 전체 교체(notMerge) 적용 (MOCA setValue 대응). null/undefined 면 차트를 비운다(clear). option 문법은 ECharts 제품 문서를 그대로 따른다
merge
true 면 전체 교체 대신 병합(setOption 기본 동작) — 시리즈 데이터만 갈아끼우는 부분 갱신용 (기본 false)
차트 높이(px). 생략하면 부모를 100% 채운다 — 레이아웃 pane 안에서는 생략이 기본. 높이가 0 이면 조용히 안 보이니 부모 높이를 확인할 것(MOCA 동일 주의)
메서드 (ref API)
getChart(cb)
echarts 인스턴스를 콜백으로 넘긴다 — 제품 API 직접 사용 진입점 (MOCA 대응)
ref.current?.getChart((c) => c.on("click", fn))
getChartObj()
echarts 인스턴스 즉시 반환 (마운트 전이면 null)
resize()
수동 리사이즈 — 크기 변화는 ResizeObserver 가 자동 추종하므로 즉시 반영이 필요할 때만
clear()
차트 비우기 (option prop 을 null 로 줘도 동일)
MocaReactFloatingButton
import { MocaReactFloatingButton } from '@teammoca/react-floating-button'
화면 위에 떠 있는 원형/알약형 액션 버튼(FAB) — MOCA floatingButton 이식. 기본 우하단(fixed), 꾹 누르면(350ms) 드래그 모드로 자유롭게 이동(마우스·터치 공통, 뷰포트/부모 클램프, 드래그 직후 click 무시). 레이아웃의 골격 미리보기 버튼도 이 컴포넌트다
사용 예
import { MocaReactFloatingButton } from '@teammoca/react-floating-button'
<MocaReactFloatingButton onClick={fnNew} title="신규" /> {/* plus 원형 */}
<MocaReactFloatingButton label="저장" icon="💾" onClick={fnSave} /> {/* 알약형 */}
<MocaReactFloatingButton absolute position="rt" icon="✏️" bgColor="#b4493e" onClick={fnEdit} />
Props
label
버튼 텍스트 (MOCA data-m-label 대응) — 주면 아이콘 옆에 텍스트가 붙는 알약형, 생략하면 아이콘만 원형. aria-label 로도 들어간다
icon
아이콘 (기본 'plus' — MOCA data-m-icon 대응). 'plus'=내장 SVG(색 추종) · '/' 나 '.' 포함=이미지 경로 <img> · 그 외 문자열=글리프 텍스트(이모지) · ReactNode=그대로 렌더(React 판 확장)
icon="✏️"
iconMode
"img"(기본) | "mask" — 경로 아이콘을 CSS mask 로 currentColor 를 채워 그린다(파일 아이콘도 색·테마 자동 추종, MOCA data-m-iconmode 대응)
bgColor
배경색 (MOCA data-m-bgcolor 대응). 기본은 테마 토큰(primary)이라 라이트/다크 자동 추종 — 화면 고유색을 줄 때만 지정
컬럼 정의 [{ key, title, width?, align?, format?, calc?, merge?, cellType? }]. width(px) 없으면 남은 폭을 균등 분배하고, **그리드가 넓어지면 그 컬럼들이 다시 흡수한다**(팝업이 열리며 커질 때·레이아웃 setRatio·MDI 분할·창 크기 — 오른쪽에 빈 띠가 남지 않는다). 좁아질 때는 줄이지 않고 가로 스크롤이 생기며, 사용자가 헤더를 드래그해 정한 폭은 이후 재배분에서 제외된다. 셀 값은 row[key] 로 읽는다. cellType: "tree" 면 트리 컬럼(아래 treeColumn 절), calc/merge 는 집계·병합(아래 aggColumn 절)
행 객체 배열. 참조가 바뀌면(재조회) 선택·찾기 캐시가 초기화된다 — MOCA drawGrid 와 동일
data={rows}
rowHeight
행 높이(px, 기본 26 — MOCA data-m-defaultcellheight 기본값). 가상화 계산 기준이라 모든 행이 같은 높이
height
그리드 전체 높이(px). 생략하면 부모를 100% 채우는 반응형 — MDI 탭 안에서는 생략이 기본
overscan
보이는 구간 위아래 여유 렌더 행 수(기본 3) — 경계의 부분 행 대비
statusBar
true(기본) 면 하단 푸터 표시. 왼쪽(좌측 정렬) = 지금 보고 있는 것 — 검색했으면 "검색 N건 · M번째", 선택한 행이 있으면 "K행". 오른쪽(우측 정렬) = 전체 규모 — 총 건수, 트리 접힘 시 표시 건수, 삭제체크 시 선택 건수. 헤더에는 건수를 두지 않는다(좁은 폭에서 제목·찾기와 자리를 다툰다)
rowStatus
true(기본) 면 특수컬럼 [0] 행상태(22px) 표시 — row._status 값(C:신규 U:수정 D:삭제)을 색으로
rowNum
true(기본) 면 특수컬럼 [1] 행번호(28px) 표시 — 전체 데이터 기준 절대 번호, 좁은 폭에는 scaleX 가로압축
delCheck
true(기본) 면 특수컬럼 [2] 삭제체크(26px) 표시 — 행별 X 체크 + 헤더 전체선택
label
그리드 제목. 지정하면 상단 툴바(제목 + 찾기·행높이·전체화면 버튼)가 나타난다. 건수는 툴바가 아니라 푸터(statusBar)에 있다
label="사원 목록"
fullScreen
true(기본) 면 툴바에 전체화면 버튼 — label 지정 시에만 의미. MOCA _fullScreenGrid 의 overlayer 방식
rowHeightSelect
true 면 툴바에 행높이 셀렉트(기본 false — React 판 자체 기능). 배열을 주면 그 값들이 선택지(기본 [22,26,32,40]). 선택값이 rowHeight prop 보다 우선
rowHeightSelect={[20, 26, 36]}
search
true 면 툴바에 찾기(검색어 + 다음 버튼, 기본 false — 자체 기능). 검색 결과(총 검색건수·몇 번째)는 툴바가 아니라 푸터 왼쪽에 표시된다. 모든 데이터 컬럼 부분일치(대소문자 무시), Enter/다음마다 다음 매치 행 선택 + 가운데 스크롤, 끝나면 순환. 트리에서 접혀 숨은 행·집계행은 대상에서 제외
sortable
true 면 모든 데이터 컬럼 헤더에 정렬 버튼 (기본 false — MOCA data-m-sortable 대응). 누를 때마다 오름차순 → 내림차순 → 원래대로 3단 순환이고, 한 번에 한 컬럼만 걸린다. 같은 값이면 원본 순서를 지키고(안정 정렬), 빈 값은 방향과 무관하게 뒤로 보낸다. 양쪽 다 숫자면 숫자 비교라 **콤마를 찍어 문자열로 만든 값은 정렬이 틀어진다** — 숫자로 두고 columns[].format 으로 표시만 가공할 것. 트리 컬럼이 있으면 무시된다(PATH 정렬이 전제)
sortable
columns[].sortable
그 컬럼만 정렬 버튼 켜기/끄기 — 그리드의 sortable 보다 우선
{ key: "MEMO", title: "비고", sortable: false }
filterable
true 면 모든 데이터 컬럼 헤더에 필터 버튼 (기본 false — MOCA data-m-filterable 대응). 그 컬럼의 고유값을 건수와 함께 늘어놓은 드롭다운에서 골라 켠다(값 검색 · 가나다순/건수순 · 전체선택). 여러 컬럼에 걸면 AND 이고, 전체를 고른 채 적용하면 그 컬럼 필터는 해제된 것으로 본다. 걸려 있는 동안 푸터 건수가 "필터 N/전체"로 바뀌고 툴바에 [필터 해제] 가 나타난다. 트리 컬럼이 있으면 무시된다
filterable
columns[].filterable
그 컬럼만 필터 버튼 켜기/끄기 — 그리드의 filterable 보다 우선. 고유값이 많은 키 컬럼(사번·일련번호)은 꺼 두는 편이 좋다
{ key: "EMP_NO", title: "사번", filterable: false }
filterDistinctLimit
필터 목록을 만들 고유값 상한 (기본 1000 — MOCA data-m-filterdistinctlimit 대응). 넘는 컬럼은 드롭다운을 열어도 목록 대신 안내만 나온다 — 체크박스를 수십만 개 만들면 브라우저가 멈추기 때문
filterDistinctLimit={3000}
subtotal
소계 그룹 컬럼 key (MOCA data-m-subtotal 대응 — 1단만). 그룹값의 인접 런(연속 구간) 단위로 소계행이 삽입되므로 데이터는 그 컬럼으로 정렬되어 내려와야 한다(SQL ORDER BY). 런 수 > 고유값 수(그룹 흩어짐)면 틀린 소계를 보여주느니 생략한다
subtotal="REGION_NM"
total
합계행 위치 — true/"bottom"(기본, 맨 아래 고정) | "top"(헤더 아래 고정) | false(없음) (MOCA data-m-total 대응). 합계행은 스크롤과 무관하게 항상 제자리(pin). calc 컬럼이 하나도 없으면 집계 자체가 꺼진 것
total="top"
subtotalLabel
소계행 라벨 (기본 "소계" — MOCA data-m-subtotallabel 대응). 그룹값 뒤에 붙어 "서울 소계" 처럼 표시된다
subtotalLabel="평균/최저/최대"
totalLabel
합계행 라벨 (기본 "합 계" — MOCA data-m-totallabel 대응)
MocaReactGrid · aggColumn
calc: 'sum'|'count'|'avg'|'min'|'max'
이 컬럼을 집계 대상으로 (MOCA data-m-calc 대응). avg 는 소수 2자리 반올림 · 전체 합÷전체 건수(소계 평균들의 평균이 아님). 빈 값은 어떤 연산에도 안 센다. min/max 는 양쪽 다 숫자면 숫자, 아니면 문자열 비교(날짜 문자열 동작). 트리와 동시 사용 불가
연속된 같은 값을 세로 병합해 표시 (MOCA data-m-colmerge 대응) — 값은 병합 구간의 첫 표시 행에만 그려진다(colmergealign="top" 방식이라 스크롤해도 구간 첫 행이 항상 라벨을 가진다). 집계행은 병합 런을 끊는다. 병합 셀은 배경이 불투명해 지브라/hover/선택 강조가 비치지 않는다(rowspan 병합과 같은 규칙)
{ key: "REGION_NM", title: "지역", merge: true }
MocaReactGrid · treeColumn
cellType: 'tree'
이 컬럼을 트리로 그린다 (MOCA data-m-celltype="tree" 대응, title 이 data-m-name 대응). 부모 노드의 +/− 로 접고 펼친다 — 접으면 모든 자손이 숨지만 자손의 접힘 상태는 보존되어, 부모를 다시 펼치면 이전에 펼쳐져 있던 모습 그대로 복원된다(MOCA 는 접을 때 하위 부모를 강제로 접어 상태가 사라진다 — 의도된 개선). 가상 스크롤은 보이는 행 목록 위에서 동작하므로 대용량 트리도 그대로 빠르다. data 가 바뀌면 전체 펼침으로 초기화
트리에 함께 표시할 라벨 컬럼명 (MOCA data-m-labelid 대응). 라벨이 빈 행은 트리 셀을 그리지 않는다
treeKey
트리 계산용 노드 고유키 컬럼명 (MOCA data-m-treeid 대응). 현재 표시에는 쓰이지 않고 행 추가류 기능을 위해 예약
이벤트 (콜백 props)
onRowSelected
행 클릭 시 (row, rowIndex) 로 호출 — rowIndex 는 data 기준 절대 인덱스. 같은 행을 다시 클릭해도 매번 호출된다(MOCA data-m-onrowselected 동일). 찾기로 행이 선택될 때도 발화
onRowSelected={(row, idx) => setSelected(row)}
MocaReactInputCalendar
import { MocaReactInputCalendar } from '@teammoca/react-input-calendar'
날짜 선택 — 입력칸 + 달력 팝업 (MOCA inputCalendar 이식, 데모 TPL042 대응). dateType 하나가 "무엇을 고르는 달력인가"를 정하고 값 자릿수·팝업 모드(연 목록/월 목록/일자 격자+시각)·기본 표시형식이 전부 거기서 파생된다. 값은 언제나 숫자만 있는 문자열(서버·SQL 과 그대로 주고받는다)이고 표시는 displayFormat 의 # 마스크가 만든다. 팝업은 body 로 portal + fixed — 폼·패널처럼 overflow:auto 인 스크롤 영역 안에서도 잘리지 않는다
사용 예
import { MocaReactInputCalendar, mdate } from '@teammoca/react-input-calendar'
// 기본(일자) — 오늘로 시작, 값 확정 시 콜백
<MocaReactInputCalendar displayFormat="####-##-##" defaultValue="오늘"
onDateSelected={(v) => console.log(v)} /> {/* v = '20260815' */}
// 연월 · 연 · 일시
<MocaReactInputCalendar dateType="yyyyMM" defaultValue="오늘" /> {/* 값 6자리 */}
<MocaReactInputCalendar dateType="yyyy" defaultValue="3년전" /> {/* 값 4자리 */}
<MocaReactInputCalendar dateType="yyyyMMdd hh:mm:ss" defaultValue="오늘" />
// 범위 제한 + ref API
const cal = useRef(null)
<MocaReactInputCalendar ref={cal} dateMin="20260710" dateMax="20260820" />
cal.current.setValue(mdate.addDay(cal.current.getValue(), 1))
Props
dateType
무엇을 고르는 달력인가 — 값 자릿수·팝업 모드·기본 표시형식이 여기서 파생된다(기본 yyyyMMdd). 대소문자·구분자 무시("yyyyMMdd hh:mm:ss" = yyyyMMddHHmmss), 별칭 year/month/date/day/datetime (MOCA data-m-datetype 대응). 일시 타입의 팝업은 **시·분만** 고르게 한다 — 값의 초 자리는 들어온 값을 그대로 유지한다(기간이면 시작 00·종료 59)
value
제어 모드 값(숫자문자열) — onChange 와 함께 쓴다. 폼 상태를 화면이 들고 있을 때
표시형식 — "#" 마스크(기본은 dateType 의 기본형식). 값은 그대로 숫자문자열이고 표시만 바뀐다 (MOCA data-m-displayformat 대응)
displayFormat="####/##/##"
dateMin
선택가능 하한(yyyyMMdd) — 범위 밖 칸·이동버튼·[오늘]이 잠기고 직접 타이핑도 확정 시 거부된다. 연/연월 타입은 "그 값이 덮는 기간이 범위와 겹치는가"로 판정한다(MOCA data-m-datemin 대응)
dateMax
선택가능 상한(yyyyMMdd) — MOCA data-m-datemax 대응
readOnly
true 면 잠금 — 입력·팝업 모두 막힌다 (MOCA data-m-readonly 대응)
width
입력칸 너비(px 또는 CSS 값, 기본 160). 연 선택처럼 짧은 값은 110 정도가 적당
placeholder
안내문(기본은 표시형식을 0 으로 바꾼 모양)
이벤트 (콜백 props)
onChange
(value) => void — 값이 바뀔 때(제어 모드 필수). 값은 타입 자릿수로 정규화된 숫자문자열
onDateSelected
(value) => void — 값이 확정될 때(팝업 선택·타이핑 확정). ref.setValue 로 넣을 때는 발화하지 않는다(MOCA data-m-ondateselected 와 같은 규약)
onDateSelected={(v) => fnSearch(v)}
메서드 (ref API)
getValue()
현재 값(숫자문자열, 없으면 "")
const v = cal.current.getValue()
setValue(v)
값 설정 — 타입 자릿수로 맞춘다(넘치면 자르고, 모자라면 월·일 01 / 시각 00 으로 채움). 이벤트 미발화
cal.current.setValue("20260815")
setReadOnly(bool)
잠금 토글 (MOCA setReadOnly 대응)
clear()
값 비우기(이벤트 미발화)
focus()
입력칸 포커스
import { mdate } from '@teammoca/react-input-calendar'
날짜 유틸(MOCA moca.$g.date 대응) — getToday/getNow · addDay/addMonth/addYear · getFirstDayOfMonth/getLastDayOfMonth · shiftYm · inRange · resolveRelative. 전부 숫자문자열 기반
mdate.addDay(mdate.getToday(), -7)
MocaReactLayout
import { MocaReactLayout } from '@teammoca/react-layout'
반응형 분할 레이아웃 — layout 속성 하나로 자식들을 방향(v/h) + 비율 토큰으로 분할 배치한다(MOCA layout.js 이식). 모든 화면 골격의 시작점. 데스크톱은 부모를 스크롤 없이 꽉 채우고, 모바일(≤768px)은 방향 무관 세로 스택으로 전환된다. 중첩 가능, Alt+L 로 골격 미리보기(와이어프레임)
사용 예
import { MocaReactLayout } from '@teammoca/react-layout'
// 가장 흔한 골격 — 검색영역(fit) + 본문(auto), 본문은 h3:7 중첩
<MocaReactLayout layout="vfit:auto">
<div>검색영역</div>
<MocaReactLayout layout="h3:7" ref={bodyRef}>
<MocaReactGrid ... /> {/* 목록 3 */}
<MocaReactGrid ... /> {/* 상세 7 */}
</MocaReactLayout>
</MocaReactLayout>
bodyRef.current?.setRatio('h0:10') // 런타임 변경 — 좌측 접기
Props
layout
분할 스펙 "{방향}{토큰:토큰[:…]}" (기본 "v"). 방향 v=세로 스택·h=가로 분할. 토큰은 자식 개수만큼 ":" 나열(개수 제한 없음, 생략 시 균등). 통짜(분할 없음)의 표준 표기는 v1. 토큰 종류는 아래 tokens 절
layout="vfit:auto:fit"
width
루트 고정 폭(px 숫자 또는 CSS 값 — MOCA data-m-width 대응). 주면 꽉채움 대신 이 크기 기준으로 분할하고 넘치면 부모가 스크롤
height
루트 고정 높이 (MOCA data-m-height 대응)
gap
칸 사이 간격(px 숫자 또는 CSS 값, 기본 15 — MOCA --m-layout-gap 대응)
mobileHeights
칸별 모바일(≤768px) 고정 높이 배열 — 해당 없는 칸은 null (MOCA data-m-mobileheight 마커 대응, PC 무영향). h(가로) 레이아웃은 토큰이 "폭"이라 모바일 높이를 명시할 방법이 없어 생긴 장치
mobileHeights={[null, 360]}
debug
true 면 골격 미리보기를 켠 채 시작(기본 false). 런타임 토글은 Alt+L 또는 우하단 FAB — 루트 레이아웃이 트리 전체에 칸 구조·토큰 라벨을 그린다
wireframeButton
루트 레이아웃 우하단의 골격 미리보기 토글 FAB 표시 (기본 true — MOCA _injectDebugFab/mconfig.wireframeBtn 대응). 실체는 MocaReactFloatingButton 이고 켜짐 상태는 primary 글로우로 표시. 운영 화면에서 숨기려면 false. 중첩 레이아웃에는 붙지 않는다(루트당 1개)
MocaReactLayout · tokens
숫자
상대 비율(flex-grow) — 합이 10일 필요 없다(2:8 = 1:4 = 20:80). 0 을 주면 그 칸이 접힌다(setRatio("h0:10") 사이드 토글)
layout="h3:7"
숫자px
고정 크기 칸(주축 기준 — h 는 폭, v 는 높이). 모바일 세로스택에서는 내용 높이로 풀린다(그리드가 든 칸은 예외 — 높이 유지)
layout="h240px:auto"
fit
내용 크기만큼(hug) — 검색영역/버튼줄 용. vfit:auto 가 가장 흔한 화면 골격, vfit:auto:fit 이 팝업 표준 골격
auto
남은 공간 전부(내부적으로 grow 1 — 2:auto 는 2:1 과 동일). px/fit 고정 칸과 짝지어 "고정+나머지" 패턴으로 쓰는 것이 의도
MocaReactLayout · markers
class="mrl-fill"
모바일(≤768px) 세로스택에서 그 칸이 남은 높이를 채우게 하는 표식. 모바일은 칸을 내용 높이로 푸는데, 내용이 전부 absolute 이거나(플로팅 버튼 상자) 캔버스처럼 내재 높이가 없는 박스는 0 으로 주저앉아 화면 아래가 빈 채로 남는다. 그리드가 든 칸은 자동으로 채워지므로 표식이 필요 없다
<div className="myBox mrl-fill">
메서드 (ref API)
setRatio(spec)
런타임 비율(+방향) 변경 (MOCA $p.get("lay").setRatio 대응). 방향 문자를 생략하면 현재 방향 유지. layout prop 이 바뀌면 런타임 변경분은 리셋된다
ref.current?.setRatio("7:3") — 방향 유지 / setRatio("v5:5") — 세로 전환
MocaReactMdi
import { MocaReactMdi } from '@teammoca/react-mdi'
MDI 탭 셸 — 좌우 화면분할 + 탭 드래그 이동(MOCA mdi.js 이식). 탭 목록(tabs)은 부모가 소유하고 MDI 는 분할/이동/활성화를 담당. 탭별 고정 홀더 + createPortal 로 패널 이동 시에도 화면 상태(그리드 스크롤·선택 등)가 보존된다
사용 예
import { MocaReactMdi } from '@teammoca/react-mdi'
const mdiRef = useRef(null)
<MocaReactMdi
ref={mdiRef}
tabs={tabs}
onTabClose={(key) => setTabs((ts) => ts.filter((t) => t.key !== key))}
emptyHint="좌측 메뉴에서 화면을 선택하세요"
/>
mdiRef.current?.activate('userlist') // 이미 열린 탭 활성화
Props
tabs
열린 탭 목록 [{ key, title, content, pinned? }] — 부모가 소유. title 은 ReactNode 가능, content 는 렌더할 화면 ReactNode, pinned 는 닫기 버튼 없음(MOCA 고정탭 컨셉). 새로 추가된 탭은 자동 활성화
지금 보이는 탭 key 배열로 호출 — 분할 중이면 두 개(메인, 서브). 좌측 메뉴에서 "열려 있음"과 "지금 보고 있음"을 구분해 칠하는 용도. 값이 실제로 바뀔 때만 호출되고, 탭이 닫히거나 패널을 옮겨 활성 탭이 자동 보정된 경우도 반영된다
onActiveChange={setActiveKeys}
메서드 (ref API)
activate(key)
이미 열린 탭을 활성화 — 메뉴에서 같은 화면을 다시 클릭했을 때 사용(새 탭은 자동 활성화라 부를 필요 없다)
mdiRef.current?.activate("userlist")
MocaReactMultiCalendar
import { MocaReactMultiCalendar } from '@teammoca/react-input-calendar'
기간 선택(from~to) — 한 컴포넌트가 입력칸 두 개와 달력 두 판(좌=시작, 우=종료)을 관리한다 (MOCA inputMultiCalendar 이식, 데모 TPL043 대응). dateType 계약은 단일 달력과 같고(값 자릿수·팝업 모드·기본 표시형식 파생, calCore 공유), 기간이라서 더해지는 것은 셋: 빠른선택 버튼(quickItems) · 최대 기간(maxTermBy*) · from>to 자동 보정. 종료값은 시각을 23:59:59 로 채워 그 날 하루가 빠지지 않게 한다(MOCA 규약)
사용 예
import { MocaReactMultiCalendar, mdate } from '@teammoca/react-input-calendar'
// 기본 — 당월(1일~말일)로 시작
<MocaReactMultiCalendar displayFormat="####-##-##" defaultValue="당월"
onTermSelected={({ from, to }) => fnSearch(from, to)} />
// 빠른선택 + 최대 31일
<MocaReactMultiCalendar defaultValue="금주" quickItems="오늘,금주,당월,전월,당분기,당년"
maxTermByDay={31} />
// 연월·연·일시 기간 (단일 달력과 같은 dateType)
<MocaReactMultiCalendar dateType="yyyyMM" defaultValue="당년" />
// ref API
term.current.setMultiCalendar({ from: '20260801', to: '20260831' })
term.current.getTerm() // { from, to }
Props
dateType
단일 달력과 같은 5종(기본 yyyyMMdd) — 값 자릿수·팝업 모드(연 목록/월 목록/일자 격자)·기본 표시형식이 파생된다
from
제어 모드 시작값(숫자문자열) — onChange 와 함께 쓴다
to
제어 모드 종료값
defaultValue
비제어 초기 기간 — 프리셋 문구 또는 { from, to } 객체. 프리셋: "오늘" · "전일" · "금주"(일~토) · "전주" · "당월"(1일~말일) · "전월" · "당분기" · "전분기" · "당년" · "전년" · "N일전/개월전/년전"(그만큼 전 ~ 오늘) (MOCA data-m-defaultvalue 대응)
defaultValue="당월"
quickItems
팝업 왼쪽 빠른선택 버튼 목록 — 문자열("오늘,금주,당월") 또는 배열. 기본은 MOCA 와 같은 10종(오늘,전일,금주,전주,당월,전월,당분기,전분기,당년,전년). false/빈값이면 버튼 영역을 숨긴다 (MOCA data-m-selecteritem 대응)
maxTermByDay
최대 기간(일) — 넘기면 선택이 거부된다(양 끝 포함. 31 이면 8/1~8/31 까지 허용). MOCA data-m-maxtermbyday 대응
maxTermByMonth
최대 기간(개월) — MOCA data-m-maxtermbymonth
maxTermByYear
최대 기간(년) — MOCA data-m-maxtermbyyear
dateMin
선택가능 하한 — 좌·우 달력 모두 범위 밖 칸·이동버튼이 잠기고 타이핑도 거부된다
dateMax
선택가능 상한
readOnly
true 면 두 칸 모두 잠금(팝업도 안 열린다)
이벤트 (콜백 props)
onChange
({ from, to }) => void — 값이 바뀔 때(제어 모드 필수)
onTermSelected
({ from, to }) => void — 기간이 확정될 때. 팝업에서는 **[확인] 을 눌렀을 때만** 발화한다(2026-08-19 — 기간은 두 번 골라야 완성되므로 고를 때마다 쏘면 반쪽 기간으로 조회가 돈다). 빠른선택은 기간만 채우고 팝업을 닫지 않는다. 타이핑 확정(blur)은 종전대로 즉시. set* API 는 발화하지 않는다
onTermSelected={({from,to}) => fnSearch(from,to)}
메서드 (ref API)
getValue()
시작값(from) — MOCA getValue() 가 from 을 주는 규약 그대로
getTerm()
{ from, to } 반환 — React 판 편의 API
const { from, to } = ref.current.getTerm()
setFrom(v)
시작값 설정(이벤트 미발화). to 보다 뒤면 to 가 따라온다
setTo(v)
종료값 설정(이벤트 미발화). from 보다 앞이면 from 이 따라온다
setMultiCalendar({from, to})
기간 일괄 설정(이벤트 미발화)
ref.current.setMultiCalendar({ from: mdate.getFirstDayOfMonth(t), to: mdate.getLastDayOfMonth(t) })
setReadOnly(bool)
잠금 토글
clear()
기간 비우기
MocaReactScheduleCalendar
import { MocaReactScheduleCalendar } from '@teammoca/react-schedule-calendar'
월간 일정 달력 (MOCA scheduleCalendar 이식, 데모 TPL041 대응). 앞의 달력 3종이 "날짜를 고르는" 컴포넌트라면 이쪽은 "일정을 보여주는" 컴포넌트다. 화면이 하는 일은 셋뿐: ① 월 변경 시 그 달 조회 ② 아이템으로 매핑해 넘기기 ③ 일자 클릭 시 getDayItems. 연속일정 일자 전개(pos one/s/m/e)·정렬(완료 뒤·연속 우선·시작일·시작시간·seq)·바 마크업·공휴일/날씨/뱃지 표기·좌우 스와이프는 컴포넌트가 한다
(ym) => void — 표시 월 변경 시(헤더 버튼·오늘·setYm·스와이프). **초기화 시엔 미발화** — 첫 조회는 화면이 getYm() 으로 직접 한다(MOCA 와 같은 규약)
onMonthChange={(ym) => loadMonth(ym)}
onDayClick
(day) => void — 일 칸 클릭. day={ymd,dd,dow,inMonth,today}. 여기서 getDayItems 로 그 날 목록을 꺼낸다(서버 재조회 없음)
onMenuClick
() => void — 헤더 햄버거 클릭(menuButton 과 함께)
메서드 (ref API)
setData({schedules, dayInfo, hideOutside})
일정 표시 데이터를 명령형으로 넣는다(MOCA 표준 API). props 로 넘기는 방식과 같은 결과 — 기존 MOCA 화면을 옮길 때 이 형태가 편하다
getDayItems(ymd)
그 날의 일정 목록을 **화면 순서 그대로** 반환 — 이미 인덱싱해 둔 것이라 서버 재조회가 필요 없다. 각 항목 = 아이템 사본 + pos("one"|"s"|"m"|"e" — 그 날이 단일/시작/중간/종료 중 무엇인지) + ymd. data(원본 행)를 담아 넘겼다면 그대로 실려 온다
const items = scc.current.getDayItems(day.ymd)
getDayInfo(ymd)
그 날의 일자메타({holi,wthr,badges}) 반환 — getDayItems 와 대칭. 일자 목록에 공휴일·날씨까지 재조회 없이 함께 보여줄 때
getYm()
현재 표시 월(yyyyMM) — 초기 조회에 쓴다
loadMonth(scc.current.getYm())
setYm(ym)
그 월로 이동 — 월이 바뀌면 onMonthChange 발화
today()
오늘이 속한 월로 이동
next()
다음 달
prev()
이전 달
refresh()
강제 재렌더 — React 는 데이터가 바뀌면 자동으로 다시 그리므로 보통 필요 없다(dayRenderer 가 외부 값을 읽는 화면에서 MOCA 호환용)
MocaReactSearchCombo
import { MocaReactSearchCombo } from '@teammoca/react-search-combo'
타이핑으로 목록을 좁혀 고르는 검색형 콤보 — MOCA searchCombo 이식. 항목이 수십·수백 건이라 select 로는 훑기 힘든 자리에 쓴다. **필터는 화면에 보이는 표시문구 기준**(대소문자 무시)이라, 코드로도 검색시키려면 displayFormat 으로 코드를 문구에 넣는다. ↓↑ 로 이동하고 Enter/클릭으로 확정하며, 목록은 뷰포트 기준(fixed)으로 띄워 레이아웃 칸이나 헤더 안에서 잘리지 않고 아래 자리가 모자라면 위로 뒤집힌다. 데모 앱 헤더의 메뉴 검색이 이 컴포넌트다
목록 [{ cd, nm }] (필수). null 항목은 걸러낸다. 참조가 바뀌면 그 자리에서 목록이 갈린다 — 서버 조회 후 setState 로 넘기면 된다
list={areas}
cdKey
코드 필드명 (기본 'cd' — MOCA data-m-cdkey 대응)
cdKey="DEPT_CD"
nmKey
표시명 필드명 (기본 'nm' — MOCA data-m-nmkey 대응)
nmKey="DEPT_NM"
value
선택 코드값. 주면 **제어 컴포넌트**가 되어 값을 바꾸는 책임이 화면(state)에 있다 — onChange 에서 반영하지 않으면 값이 그대로 남는다. 비제어로 쓰려면 defaultValue
defaultValue
비제어일 때 초기 선택값 (기본 ''). 목록에 없는 값이면 표시는 빈 칸
displayFormat
표시문구 틀 — '[value]'/'[label]' 을 코드/표시명으로 치환한다 (MOCA data-m-displayformat 대응). **필터가 이 문구 기준**이라 코드·분류로도 검색시키려면 여기 넣어야 한다
displayFormat="[value] · [label]"
placeholder
입력칸 안내문 (기본 '내용을 입력하세요' — MOCA 동일)
readOnly
입력 불가 + ▼ 버튼 없음 (MOCA data-m-readonly 대응). 값은 표시만 된다
disabled
true 면 비활성 — 입력·열기 모두 막고 흐리게 표시
clearOnSelect
true 면 확정 후 입력칸을 비운다 (기본 false — React 판 자체 기능). "값을 고르는 칸"이 아니라 "검색해서 실행하는 칸"일 때 쓴다
clearOnSelect
emptyText
결과가 0건일 때 목록에 표시할 안내문 (기본 '검색 결과가 없습니다'). MOCA 는 목록을 닫아버려 고장과 구분이 안 됐다
maxHeight
목록 최대 높이(px, 기본 200 — MOCA 동일). 남은 공간이 이보다 작으면 그만큼만 쓰고 스크롤한다
width
컴포넌트 폭 (기본 CSS 160px — MOCA 기본 140px 과 달리 표시문구가 길어지는 용법을 기준으로 잡았다)
className
외형 확장 — 쓰는 쪽 CSS 가 기본 외형(.mrsc)을 덮을 수 있다
데모 앱 헤더의 mdi-menu-search 가 입력칸만 반투명으로 덮는다
이벤트 (콜백 props)
onChange
(value, item, changed) => void — **확정(Enter/클릭) 시에만** 발화한다. 타이핑 중에는 오지 않는다(MOCA 는 필터가 돌 때마다 첫 매칭을 선값해 값이 계속 바뀌었다). changed 는 이전 값과 다른지 여부로, **같은 항목을 다시 골라도 changed:false 로 발화**한다 — 메뉴 검색처럼 값이 아니라 행위가 필요한 화면 때문
onChange={(v, row) => setArea(v)}
메서드 (ref API)
getValue()
선택 코드값 (MOCA getValue 대응)
getLabel()
선택 표시문구 (MOCA getLabel 대응)
setValue(value)
값 지정 + 입력칸 표시 갱신 (MOCA setValue 대응). 제어 모드면 표시만 갱신되고 값의 주인은 그대로 화면이다
clear()
선택·입력 초기화
focus()
입력칸 포커스 — 화면을 열자마자 검색부터 시키고 싶을 때
open()
필터를 풀고 전체 목록 열기 (MOCA _searchComboFullShow 대응)
close()
목록 닫기 — 입력칸은 확정값 표시로 되돌아간다
getList()
현재 목록 사본 (MOCA getList 대응)
모듈
컴포넌트가 아니라 함수·훅 묶음이다 — 서버 통신(tran), 알림·확인(msgbox), 팝업(popup).
msgbox
import { msgbox } from '@teammoca/react-msgbox'
메시지박스 3종(alert · error · confirm) + 필수값 안내 + 진행 모달 (MOCA moca.$g.alert/error/confirm·requiredFail·showProgress 이식, 데모 TPL023 대응). MOCA 처럼 화면 어디서나 명령형으로 부르고, React 답게 await 로 답을 받을 수도 있다. 실제 렌더는 앱 루트에 한 번 놓는 <MocaReactMsgHost /> 가 맡는다(없으면 콘솔 경고). 모달은 큐로 한 번에 하나씩 표시하고(MOCA 는 겹쳐 쌓였다 — 의도된 개선), 진행 모달은 그 위에 별도로 뜬다. Enter=확인 · ESC=취소(confirm)/확인(alert·error), 열리면 확인 버튼에 포커스
사용 예
import { msgbox, MocaReactMsgHost } from '@teammoca/react-msgbox'
<MocaReactMsgHost /> // 앱 루트에 한 번(App.jsx)
msgbox.alert('저장되었습니다.') // MOCA 와 같은 호출
msgbox.alert('완료', () => reload()) // 콜백(MOCA 호환)
if (await msgbox.confirm('저장하시겠습니까?')) save() // 답을 그 자리에서
const mode = await msgbox.confirm('어디에 추가할까요?', { radios: [...] })
if (!name) return msgbox.requiredFail('사용자명', () => ref.current.focus())
Props
message(문자열 | ReactNode)
문자열은 **텍스트로** 넣고 줄바꿈(\n)만 반영한다. 강조가 필요하면 노드를 넘긴다 — MOCA 는 innerHTML 이라 <b>/<br/> 을 썼지만, 서버 문장을 그대로 HTML 로 넣던 통로를 막아 XSS 여지를 없앤 것이다(의도된 차이)
msgbox.alert(<>성공 <b>12건</b></>)
메서드 (ref API)
msgbox.alert(message, cb?)
알림(확인 1개, ! 아이콘). cb 는 [확인] 후 실행. Promise 도 반환하므로 await 가능
msgbox.alert("저장되었습니다.")
msgbox.error(message, cb?)
오류 알림 — alert 과 같은 구조이되 danger 색 + ✕ 아이콘으로 구분한다(MOCA 동일)
확인/취소. 호출 형태 4가지 — confirm(msg) → Promise<boolean> · confirm(msg, ok, cancel) → MOCA 호환 콜백 · confirm(msg, ok, cancel, {radios}) → 확인 콜백에 선택값 · confirm(msg, {radios}) → Promise<선택값|null>(취소 null). radios=[{value,label,desc,checked}] — checked 가 없으면 첫 항목이 기본 선택
필수값 안내 — "{label}은(는) 필수입력항목입니다." 를 띄우고 **항상 false 를 즉시 반환**한다(MOCA 와 같은 규약이라 `if (!v) return msgbox.requiredFail(...)` 패턴이 그대로 동작). focusCb 는 [확인] 후 실행 — 해당 입력칸으로 포커스를 옮긴다
if (!name) return msgbox.requiredFail("사용자명", () => ref.current.focus())
msgbox.showProgress(message?, delay?)
진행 모달 표시 — delay(ms, 기본 300) 뒤에 나타난다. 그 안에 작업이 끝나면 모달이 아예 뜨지 않아 짧은 작업의 깜빡임이 없다(MOCA 와 같은 의도). 0 이면 즉시
msgbox.showProgress("조회중입니다", 0)
msgbox.setProgressMsg(message)
진행 모달 문구 교체(단계 표시)
msgbox.setProgressMsg("데이터 처리중 2/3")
msgbox.hideProgress()
진행 모달 닫기 — 통신과 함께 쓸 때는 **finally 에서** 부른다(실패해도 반드시 닫히게)
<MocaReactMsgHost />
메시지박스 호스트 — 앱 루트에 **한 번만** 놓는다(App.jsx). 이것이 없으면 msgbox 호출이 표시되지 않고 콘솔에 경고가 뜬다. 로그인 화면에서도 쓸 수 있어야 하므로 인증 게이트보다 앞에 둔다
popup
import { popup } from '@teammoca/react-popup'
화면을 모달 창으로 띄우고 결과를 받는다 (MOCA $p.openPop / $p.getParameter / $p.close 이식, 데모 TPL037 대응). **팝업 3동사** — 열기 popup.open(Component, 옵션) · 파라미터 팝업이 props.data 로 받기 · 닫기 팝업이 props.close(결과)로 닫기. MOCA 는 url 로 팝업 HTML 을 지정했지만 React 는 **컴포넌트를 그대로** 넘긴다(lazy 컴포넌트도 가능). 실제 렌더는 앱 루트에 한 번 놓는 <MocaReactPopupHost /> 가 맡는다(없으면 콘솔 경고). 메시지박스가 큐(하나씩)인 것과 달리 팝업은 **스택**이라 팝업 안에서 또 팝업을 열 수 있고(품목검색 → 거래처검색) 각 팝업은 자기 데이터·결과만 소유한다. 제목바 드래그 이동, ESC 로 맨 위 팝업 취소
사용 예
import { popup, MocaReactPopupHost } from '@teammoca/react-popup'
import PopItemSearch from './pop/PopItemSearch'
<MocaReactPopupHost /> // 앱 루트에 한 번(App.jsx)
// await 로 받기(권장 — 답에 따라 다음 줄이 갈린다)
const row = await popup.open(PopItemSearch, { title: '품목 검색', width: 640, data: { keyword } })
if (row) setItem(row) // 취소면 undefined
// 콜백(MOCA openPop 호환) — 취소면 콜백이 아예 불리지 않는다
popup.open(PopItemSearch, { title: '품목 검색', callback: (row) => setItem(row) })
// 팝업 화면 — 업무 화면과 똑같이 만들고 결과를 close 로 넘긴다
function PopItemSearch({ data, close }) {
return <button onClick={() => close(row)}>선택</button> // close() 는 취소
}
Props
title
제목바 문구 — aria-label 로도 들어간다
width
팝업 너비(숫자=px 또는 CSS 문자열). 생략 시 560px. 화면보다 크면 화면 폭에 맞춰 줄어든다
width: 640
height
팝업 높이(숫자=px 또는 CSS 문자열). 생략 시 내용 높이에 맞추고 최대 90vh. **그리드를 담은 팝업은 height 를 주거나 팝업 화면 루트에 minHeight 를 준다** — 높이가 내용 기준이면 레이아웃의 auto(남는 공간) 칸이 0 이 되어 목록이 보이지 않는다(데모 팝업 화면들이 minHeight 방식)
height: 520
modal
false 면 뒤 화면을 계속 조작할 수 있는 보조창(딤이 클릭을 막지 않는다). 기본 true
팝업이 스스로 닫는 방법 (MOCA $p.close 대응). **close(결과)=확정** — callback 발화 + Promise 결과 / **close()=취소** — 콜백 미발화 + Promise undefined. 닫기 버튼은 푸터 한 곳에만 두면 된다(제목바 × 는 셸이 준다)
popup.close(id?)
바깥에서 팝업을 닫는다(취소 처리). id 생략 시 가장 위 팝업 — 통신 실패로 팝업을 접어야 할 때
popup.closeAll()
열린 팝업 전부 닫기(모두 취소) — 화면 전환·로그아웃 때 부른다(App.jsx 로그아웃에 적용)
<MocaReactPopupHost />
팝업 호스트 — 앱 루트에 **한 번만** 놓는다(App.jsx). 없으면 popup.open 이 표시되지 않고 콘솔에 경고가 뜬다. document.body 포털로 그리므로 화면의 overflow 에 잘리지 않는다. z-index 는 7500 부터 겹친 순서대로(메시지박스 8000·달력 7800 이 그 위에 오도록 설계)
tran
import { tran } from '@teammoca/react-tran'
MOCA 서버 공용 쿼리 API 통신 레이어 (MOCA moca.$t.tran.exe 대응). 화면은 SQL 을 모르고 queryId 만 안다 — SQL 은 서버 매퍼(MocaMapper.xml)에 있다. CSRF 토큰 자동 첨부(쿠키 MOCA-CSRF-TOKEN → 헤더 X-CSRF-TOKEN), 세션 쿠키 동반, 401(미로그인)/403(CSRF) 콜백 처리, 결과 언랩(list1 → rows)을 대신한다. 개발 중에는 Vite 프록시가 /common 을 8080 으로 넘겨 동일 출처를 유지한다(vite.config.js)
사용 예
import { tran, useSelect, configureTran } from '@teammoca/react-tran'
// 화면 — 선언적 조회(조건이 바뀌면 자동 재조회)
const { rows, loading, error, reload } = useSelect('selectTdemoSaleDtlList', { REGION_NM })
// 명령형 호출
const rows = await tran.select('selectDemoUserList', { USER_NM: '김' })
await tran.save({ insertQueryId: 'insertDemoUser', list: rows })
// 앱 부팅 1회 — 인증 실패 시 로그인 화면 전환 통로
configureTran({ onAuthFail: () => setAuth('out') })
메서드 (ref API)
tran.select(queryId, params)
단일 조회 — rows 배열 반환(list1 언랩). 서버 응답은 컬럼 키를 대문자로도 함께 주므로 row.SALE_AMT 로 읽는다
조회 훅 — { rows, loading, error, reload }. params 내용이 바뀌면 자동 재조회(검색조건 변경 = 재조회), 늦게 온 응답은 폐기, enabled:false 로 조회 보류. MOCA 의 onpageload + tran.exe 콜백 패턴을 선언형으로 바꾼 것
앱 시작 시 1회 설정 — 401/403 을 받았을 때 앱이 할 일(로그인 화면 전환 등)을 연결한다. base 는 서버 context-path 가 다를 때만
앱 규약
데모 앱(src/)이 화면을 등록하고 메뉴에 올리는 방법 — 패키지 API 가 아니라 이 앱의 약속이다.
app.registry
화면 레지스트리(src/screens/registry.jsx) — MOCA 처럼 2단 메뉴 구조. 1단(그룹)은 상단 메뉴바에, 2단(화면)은 좌측 메뉴에 표시된다. 화면 컴포넌트는 전부 lazy 로딩(MOCA frame 의 화면 지연 로드 대응) — 빌드 시 화면별 청크로 잘리고 탭을 처음 열 때 받아온다(로딩 중 표시는 App.jsx 의 Suspense). 새 화면은 lazy 한 줄 + 그룹 children 등록 한 줄이면 메뉴에 자동으로 나타나고 MDI 탭으로 열린다
사용 예
// src/screens/registry.jsx — lazy 선언 한 줄 + 그룹의 children 에 한 줄
const UserList = lazy(() => import('./UserList'))
{
key: 'work', title: '업무', icon: '🗂️',
children: [
{ key: 'userlist', title: '사원 목록', icon: '👥', component: UserList },
],
}
app.registry · group
key
그룹 고유 식별자
title
상단 메뉴바에 표시할 그룹명
icon
메뉴에 표시할 이모지
children
이 그룹의 화면 목록(아래 screen 필드)
app.registry · screen
props.menuInfo
셸(App.jsx)이 모든 화면에 자동 주입하는 메뉴정보 { id, label, icon, path } (MOCA frame 의 getParameter() 대응). 공통 타이틀 <ComTitle menu={menuInfo} /> 에 그대로 전달하면 [ID]·아이콘·제목·브레드크럼이 표시된다. 필요 없으면 무시