import {useEffect, useState, useCallback, useRef} from 'react'; import {Dialog} from 'primereact/dialog'; import {Button} from 'primereact/button'; import {Dropdown} from 'primereact/dropdown'; import {Tree} from 'primereact/tree'; import {Checkbox} from "primereact/checkbox"; import {InputText} from 'primereact/inputtext'; import {useAlert} from "@/hooks/useAlert.jsx"; import {utils} from "@/libs/utils.js"; import {constants} from "@/libs/constants.js"; import {validation} from "@/libs/validation.js"; import {useLoading} from "@/hooks/useLoading.jsx"; import mngRoleService from "@/services/mngRoleService.js"; import CCheckboxTree from "@/components/common/CCheckboxTree.jsx"; import CDialogSave from "./CDialogSave.jsx"; const MngRoleCreateDialog = ({ header = '기본 제목', visible = false, onHide, closeCallback, saveCallback, }) => { const {startLoading, stopLoading} = useLoading(); const alert = useAlert(); const ccheckboxTreeMenuRef = useRef(null); const ccheckboxTreePodRef = useRef(null); const [role, setRole] = useState({ roleName: '', isAuthPodAllYn: false, isAuthFacAwningYn: false, isAuthFacCrosswalkYn: false, selectedLoginFirstView: '', }); const [roleDatas, setRoleDatas] = useState({ menuData: [], podData: [], }) const [duplicateValues, setDuplicateValues] = useState({ isRoleNameValid: false, captureRoleName: null, }); const [selectedMenuKeys, setSelectedMenuKeys] = useState([]); const [selectedPodKeys, setSelectedPodKeys] = useState([]); const onRoleChange = (e) => { setRole((prevState) => ({ ...prevState, [e.target.name]: e.target.value, })); }; const onRoleCheckedChange = (e) => { setRole((prevState) => ({ ...prevState, [e.target.name]: e.checked, })); } const duplicateCheckRoleName = async () => { try { if (!validation.isValidRoleName({roleName: role.roleName, alert: alert})) { return; } startLoading(); const response = await mngRoleService().duplicateCheckRoleName({roleName: role.roleName}); alert.alertSuccess('알림', '사용하실 수 있는 역할/권한명입니다.'); setDuplicateValues(prev => ({...prev, isRoleNameValid: true, captureRoleName: role.roleName})); } catch (err) { console.log(err); alert.alertWarning('알림', err.message); setDuplicateValues(prev => ({...prev, isRoleNameValid: false})); } finally { stopLoading(); } } const onSave = async () => { try { if (!validation.isValidCreateRole({ isRoleNameValid: duplicateValues.isRoleNameValid, captureRoleName: duplicateValues.captureRoleName, roleName: role.roleName, selectedLoginFirstView: role.selectedLoginFirstView, alert: alert, })) { return; } const formData = { roleName: role.roleName, selectedLoginFirstView: "/" + role.selectedLoginFirstView, checkedMenus: ccheckboxTreeMenuRef.current?.selected() ?? [], checkedPods: ccheckboxTreePodRef.current?.selected() ?? [], isAuthPodAllYn: role.isAuthPodAllYn, isAuthFacAwningYn: role.isAuthFacAwningYn, isAuthFacCrosswalkYn: role.isAuthFacCrosswalkYn, } startLoading(); const response = await mngRoleService().createRole(formData); alert.alertSuccess('알림', '역할/권한 등록 완료하였습니다.'); saveCallback(); } catch (err) { console.log(err); alert.alertWarning('알림', err.message); } finally { stopLoading(); } } const dataLoad = async () => { try { startLoading(); const response = await mngRoleService().getMenuWithPod(); if (response.status === constants.RESULT_SUCCESS) { setRoleDatas({ menuData: response.body.menus.children, podData: response.body.pods, }) } } catch (err) { console.log(err); alert.alertWarning('알림', err.message); } finally { stopLoading(); } } useEffect(() => { dataLoad(); }, []); const groupedItemTemplate = (option) => { return ( <div className="flex items-center gap-2"> <div className="pi pi-list"></div> <div className="font-bold">{option.label}</div> </div> ); }; return ( <CDialogSave className="tims-dialog" header={header} onHide={onHide} visible={visible} style={{width: "80rem"}} modal saveCallback={onSave} closeCallback={closeCallback} > <div className="flex flex-row justify-between mb-8"> {/* 역할/권한명 */} <div className="flex items-center"> <p className="tims-label-text"> 역할/권한명 <span className="text-red-500">*</span> </p> <div className="flex flex-1 gap-1"> <InputText className="tims-input-text" name="roleName" placeholder="역할/권한명" onChange={onRoleChange}/> <Button className="tims-input-button" label="중복체크" onClick={duplicateCheckRoleName} /> </div> </div> {/* 로그인 후 첫화면 선택 */} <div className="flex items-center"> <p className="tims-label-text"> 로그인 후, 첫 화면 <span className="text-red-500">*</span> </p> <div className="flex-1 flex gap-1"> {roleDatas.menuData && <Dropdown className="w-60" name="selectedLoginFirstView" placeholder="선택해주세요." value={role.selectedLoginFirstView} optionGroupTemplate={groupedItemTemplate} options={roleDatas.menuData} optionLabel="label" optionGroupLabel="label" optionGroupChildren="children" optionValue="key" onChange={onRoleChange} /> } </div> </div> </div> {/* 메뉴선택 */} <div className="flex gap-1"> <div className="w-1/4"> {roleDatas.menuData && <CCheckboxTree ref={ccheckboxTreeMenuRef} value={roleDatas.menuData} extractor={(node) => ({authCd: node.key})} /> } </div> {/* 시설물 트리 선택 */} <div className="w-2/4"> <div className="flex items-center border border-zinc-300 px-3 py-2 bg-white font-semibold text-zinc-700"> <Checkbox name="isAuthPodAllYn" inputId="isAuthPodAllYn" onChange={onRoleCheckedChange} checked={role.isAuthPodAllYn}/> <label htmlFor="isAuthPodAllYn">스마트 디바이스 전체 선택</label> </div> {(roleDatas.podData && !role.isAuthPodAllYn) && <CCheckboxTree ref={ccheckboxTreePodRef} value={roleDatas.podData} extractor={(node) => ({podSq: node.data.podSq, nodeSq: node.data.nodeSq,})} /> } </div> {/* 시설물 선택 */} <div className="w-1/4 border border-zinc-300 bg-white"> <div className="w-full bg-white border-b-2 border-zinc-300 px-4 py-2 font-semibold text-zinc-700"> 시설물 선택 </div> <div className="flex items-center border-b border-zinc-300 px-6 py-2 bg-white hover:bg-zinc-200"> <Checkbox name="isAuthFacAwningYn" inputId="isAuthFacAwningYn" onChange={onRoleCheckedChange} checked={role.isAuthFacAwningYn}/> <label htmlFor="isAuthFacAwningYn">그늘막</label> </div> <div className="flex items-center border-b border-zinc-300 px-6 py-2 bg-white hover:bg-zinc-200"> <Checkbox name="isAuthFacCrosswalkYn" inputId="isAuthFacCrosswalkYn" onChange={onRoleCheckedChange} checked={role.isAuthFacCrosswalkYn}/> <label htmlFor="isAuthFacCrosswalkYn">바닥 신호등</label> </div> </div> </div> </CDialogSave> ) } export default MngRoleCreateDialog 전체 코드가 이러한데 너가 준 거 넣은니 오류떠
PrimeReact의 Checkbox를 사용하고 있고 이미 label이 있는 상황에서는 전체를 label로 감싸면 중복 label 문제가 발생할 수 있습니다. 대신 onClick 핸들러를 사용하는 방식으로 수정하겠습니다.
MngRoleCreateDialog.jsx
onClick핸들러 추가bg-blue-100 hover:bg-blue-200bg-white hover:bg-zinc-200cursor-pointer 추가로 클릭 가능함을 표시transition-colors duration-200으로 부드러운 색상 전환ml-2 추가로 체크박스와 텍스트 간격 조정onClick에서 직접 setRole을 호출하여 상태 업데이트onRoleCheckedChange도 그대로 유지하여 체크박스 직접 클릭도 동작이제 체크박스뿐만 아니라 전체 행 영역을 클릭해도 체크박스가 토글되고, 선택된 항목은 파란색 배경으로 표시됩니다!
추가되고 수정된 부분들만 보여줘
기존 코드에서 시설물 선택 부분만 다음과 같이 수정하면 됩니다:
MngRoleCreateDialog.jsx
onClick 핸들러 추가 - 전체 div 클릭 시 체크박스 토글bg-blue-100 (선택됨) / bg-white (선택안됨)cursor-pointer, transition-colors duration-200 추가ml-2 cursor-pointer 추가/* 체크박스 아이콘 스타일 */
.p-checkbox .p-checkbox-box .p-checkbox-icon {
@apply transition-all duration-200 text-white text-xs;
}
/* 체크박스 아이콘 크기 */
.p-checkbox .p-checkbox-box .p-checkbox-icon.p-icon {
@apply w-3 h-3;
}
/* 체크박스 선택된 상태 (highlight) */
.p-checkbox.p-highlight .p-checkbox-box {
@apply border-blue-600 bg-blue-600;
}
/* 체크박스 선택된 상태 (highlight) */
.p-checkbox.p-highlight .checkbox-container {
@apply border-blue-600 bg-blue-600;
}
/* hover 상태에서 체크박스 */
.p-checkbox:not(.p-disabled):has(.p-checkbox-input:hover) .p-checkbox-box {
@apply border-blue-600;
}
/* hover 상태에서 체크박스 + 선택된 상태 */
.p-checkbox:not(.p-disabled):has(.p-checkbox-input:hover).p-highlight .p-checkbox-box {
@apply border-blue-700 bg-blue-700 text-white;
}
/* focus 상태에서 체크박스 */
.p-checkbox:not(.p-disabled):has(.p-checkbox-input:focus-visible) .p-checkbox-box {
@apply outline-none ring-2 ring-blue-300 border-blue-600;
}
여기에 스타일로 추가할 수도 있겠니