모바일 - 사용자 목록, 정보 디자인 적용

This commit is contained in:
현성필
2025-12-12 14:42:40 +09:00
parent fc543a581d
commit bbddacbae9
4 changed files with 488 additions and 64 deletions
+155 -12
View File
@@ -15300,6 +15300,11 @@ input[type=checkbox]:checked + .custom-checkbox {
font-weight: 500;
min-width: 116px;
}
@media (max-width: 768px) {
.user-current-badge {
display: none;
}
}
.user-empty-state {
text-align: center;
@@ -15549,7 +15554,23 @@ input[type=checkbox]:checked + .custom-checkbox {
margin-top: 40px;
}
.actions-desktop {
display: flex;
gap: 4px;
flex-wrap: nowrap;
}
.actions-mobile {
display: none;
}
@media (max-width: 768px) {
.actions-desktop {
display: none !important;
}
.actions-mobile {
display: flex !important;
}
.user-management-container {
display: flex;
flex-direction: column;
@@ -15588,7 +15609,7 @@ input[type=checkbox]:checked + .custom-checkbox {
order: 2;
background: #FFFFFF;
border-radius: 12px;
overflow: hidden;
overflow: visible;
}
.user-management-container .list-table .list-table-header {
display: flex;
@@ -15606,9 +15627,7 @@ input[type=checkbox]:checked + .custom-checkbox {
justify-content: center;
}
.user-management-container .list-table .list-table-header .header-cell:nth-child(1) {
width: 26px !important;
min-width: 26px;
flex: none;
display: none;
}
.user-management-container .list-table .list-table-header .header-cell:nth-child(2) {
width: 60px !important;
@@ -15617,7 +15636,7 @@ input[type=checkbox]:checked + .custom-checkbox {
}
.user-management-container .list-table .list-table-header .header-cell:nth-child(3) {
flex: 1 !important;
min-width: 100px;
min-width: 80px;
}
.user-management-container .list-table .list-table-header .header-cell:nth-child(4) {
width: 39px !important;
@@ -15630,7 +15649,10 @@ input[type=checkbox]:checked + .custom-checkbox {
flex: none;
}
.user-management-container .list-table .list-table-header .header-cell:nth-child(6) {
display: none;
display: flex !important;
width: 40px !important;
min-width: 40px;
flex: none;
}
.user-management-container .list-table .list-table-body {
display: flex;
@@ -15643,6 +15665,7 @@ input[type=checkbox]:checked + .custom-checkbox {
height: 40px;
padding: 0 8px;
gap: 0;
position: relative;
}
.user-management-container .list-table .list-table-row:nth-child(even) {
background-color: #EDF5FD;
@@ -15667,9 +15690,7 @@ input[type=checkbox]:checked + .custom-checkbox {
display: none;
}
.user-management-container .list-table .list-table-row .row-cell:nth-child(1) {
width: 26px !important;
min-width: 26px;
flex: none;
display: none;
}
.user-management-container .list-table .list-table-row .row-cell:nth-child(2) {
width: 60px !important;
@@ -15683,7 +15704,7 @@ input[type=checkbox]:checked + .custom-checkbox {
}
.user-management-container .list-table .list-table-row .row-cell:nth-child(3) {
flex: 1 !important;
min-width: 100px;
min-width: 80px;
justify-content: flex-start;
}
.user-management-container .list-table .list-table-row .row-cell:nth-child(4) {
@@ -15697,10 +15718,15 @@ input[type=checkbox]:checked + .custom-checkbox {
flex: none;
}
.user-management-container .list-table .list-table-row .row-cell:nth-child(6), .user-management-container .list-table .list-table-row .row-cell.row-actions {
display: none;
display: flex !important;
width: 40px !important;
min-width: 40px;
flex: none;
justify-content: center;
overflow: visible;
}
.user-management-container .list-table .list-table-row .row-actions {
display: none;
display: flex !important;
}
.user-management-container .user-empty-state {
padding: 48px 16px;
@@ -15742,7 +15768,124 @@ input[type=checkbox]:checked + .custom-checkbox {
font-weight: 700;
color: #212529;
}
.org-register-container {
padding: 24px 16px;
}
.org-register-container .common-title-bar, .org-register-container .register-title-bar, .org-register-container .app-list-title-section, .org-register-container .user-management-container .table-controls, .user-management-container .org-register-container .table-controls {
margin-bottom: 24px;
}
.org-register-container .common-title-bar h3, .org-register-container .register-title-bar h3, .org-register-container .app-list-title-section h3, .org-register-container .user-management-container .table-controls h3, .user-management-container .org-register-container .table-controls h3 {
font-size: 20px;
}
.org-register-container .register-form-container {
box-shadow: none !important;
border: none !important;
background: transparent !important;
padding: 0 !important;
}
.org-register-container .form-actions, .org-register-container .form-actions-center {
display: flex;
flex-direction: row !important;
justify-content: center !important;
align-items: center !important;
padding: 32px 0 0;
margin-top: 0;
}
.org-register-container .form-actions .form-actions-buttons, .org-register-container .form-actions-center .form-actions-buttons {
width: 100%;
display: flex;
justify-content: center;
}
.org-register-container .form-actions .form-actions-buttons .btn, .org-register-container .form-actions-center .form-actions-buttons .btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 44px;
font-size: 15px;
}
}
.dropdown-wrapper {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
}
.dropdown-wrapper .dropdown-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
background: transparent;
border: none;
border-radius: 6px;
color: #5f666c;
cursor: pointer;
transition: all 0.3s ease;
}
.dropdown-wrapper .dropdown-toggle:hover {
background: rgba(0, 73, 180, 0.1);
color: #0049b4;
}
.dropdown-wrapper .dropdown-toggle:focus {
outline: none;
background: rgba(0, 73, 180, 0.1);
}
.dropdown-wrapper .dropdown-toggle svg {
width: 20px;
height: 20px;
}
.dropdown-wrapper .dropdown-menu {
position: absolute;
top: 100%;
right: 0;
min-width: 140px;
background: #FFFFFF;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
z-index: 100;
opacity: 0;
visibility: hidden;
transform: translateY(-8px);
transition: all 0.2s ease;
overflow: hidden;
}
.dropdown-wrapper .dropdown-menu .dropdown-item {
display: block;
width: 100%;
padding: 10px 16px;
background: transparent;
border: none;
text-align: left;
font-size: 14px;
font-weight: 400;
color: #212529;
cursor: pointer;
transition: all 0.15s ease;
white-space: nowrap;
}
.dropdown-wrapper .dropdown-menu .dropdown-item:hover {
background: #f0f7ff;
color: #0049b4;
}
.dropdown-wrapper .dropdown-menu .dropdown-item:active {
background: #e0efff;
}
.dropdown-wrapper .dropdown-menu .dropdown-item + .dropdown-item {
border-top: 1px solid #eee;
}
.dropdown-wrapper.open .dropdown-toggle {
background: rgba(0, 73, 180, 0.1);
color: #0049b4;
}
.dropdown-wrapper.open .dropdown-menu {
opacity: 1;
visibility: visible;
transform: translateY(4px);
}
.commission-container {
max-width: 1400px;
margin: 0 auto;
File diff suppressed because one or more lines are too long
@@ -107,6 +107,11 @@
font-size: $font-size-sm;
font-weight: $font-weight-medium;
min-width: 116px;
// 모바일에서 숨김
@media (max-width: $breakpoint-sm) {
display: none;
}
}
// Empty State
@@ -384,11 +389,33 @@
margin-top: $spacing-2xl;
}
// -----------------------------------------------------------------------------
// Desktop/Mobile Action Button Visibility
// -----------------------------------------------------------------------------
.actions-desktop {
display: flex;
gap: $spacing-xs;
flex-wrap: nowrap;
}
.actions-mobile {
display: none;
}
// -----------------------------------------------------------------------------
// User Management Mobile Styles - Figma Design (node 1161-10519)
// 사용자 관리 모바일 디자인
// -----------------------------------------------------------------------------
@media (max-width: $breakpoint-sm) {
// Show mobile actions, hide desktop actions
.actions-desktop {
display: none !important;
}
.actions-mobile {
display: flex !important;
}
.user-management-container {
display: flex;
flex-direction: column;
@@ -435,7 +462,7 @@
order: 2;
background: $white;
border-radius: $border-radius-lg;
overflow: hidden;
overflow: visible; // dropdown이 보이도록 변경
// Header - 모바일에서 표시 (Figma: 40px 높이, #3BA4ED 배경)
.list-table-header {
@@ -453,11 +480,9 @@
padding: 0 4px;
justify-content: center;
// NO 컬럼 - 26px
// NO 컬럼 - 모바일에서 숨김
&:nth-child(1) {
width: 26px !important;
min-width: 26px;
flex: none;
display: none;
}
// 성명 컬럼 - 60px
@@ -470,7 +495,7 @@
// 이메일 아이디 컬럼 - flex 1
&:nth-child(3) {
flex: 1 !important;
min-width: 100px;
min-width: 80px;
}
// 권한 컬럼 - 39px
@@ -487,9 +512,12 @@
flex: none;
}
// 설정 컬럼 - 모바일에서 숨김
// 설정 컬럼 - 모바일에서 표시 (40px)
&:nth-child(6) {
display: none;
display: flex !important;
width: 40px !important;
min-width: 40px;
flex: none;
}
}
}
@@ -508,6 +536,7 @@
height: 40px;
padding: 0 8px;
gap: 0;
position: relative; // dropdown positioning
// 짝수/홀수 배경색
&:nth-child(even) {
@@ -537,11 +566,9 @@
display: none;
}
// NO 컬럼 - 26px
// NO 컬럼 - 모바일에서 숨김
&:nth-child(1) {
width: 26px !important;
min-width: 26px;
flex: none;
display: none;
}
// 성명 컬럼 - 60px
@@ -560,7 +587,7 @@
// 이메일 아이디 컬럼 - flex 1
&:nth-child(3) {
flex: 1 !important;
min-width: 100px;
min-width: 80px;
justify-content: flex-start;
}
@@ -578,16 +605,21 @@
flex: none;
}
// 설정 컬럼 (row-actions) - 모바일에서 숨김
// 설정 컬럼 (row-actions) - 모바일에서 표시 (40px)
&:nth-child(6),
&.row-actions {
display: none;
display: flex !important;
width: 40px !important;
min-width: 40px;
flex: none;
justify-content: center;
overflow: visible;
}
}
// row-actions 클래스 직접 숨김
// row-actions 클래스 표시
.row-actions {
display: none;
display: flex !important;
}
}
}
@@ -644,4 +676,152 @@
}
}
}
// -----------------------------------------------------------------------------
// User Detail Page Mobile Styles - Figma Design
// 이용자 상세 페이지 모바일 디자인
// -----------------------------------------------------------------------------
.org-register-container {
padding: $spacing-lg $spacing-md;
// Title bar - 모바일 조정
.common-title-bar {
margin-bottom: $spacing-lg;
h3 {
font-size: $font-size-lg;
}
}
// Form container - Flat 디자인 (그림자, 테두리 제거)
.register-form-container {
box-shadow: none !important;
border: none !important;
background: transparent !important;
padding: 0 !important;
}
// Form actions - 버튼 가운데 정렬 및 크기 조정
.form-actions {
display: flex;
flex-direction: row !important;
justify-content: center !important;
align-items: center !important;
padding: $spacing-xl 0 0;
margin-top: 0;
.form-actions-buttons {
width: 100%;
display: flex;
justify-content: center;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 44px;
font-size: 15px;
}
}
}
}
}
// -----------------------------------------------------------------------------
// Dropdown Menu Styles - Mobile Actions
// -----------------------------------------------------------------------------
.dropdown-wrapper {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
.dropdown-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
background: transparent;
border: none;
border-radius: $border-radius-sm;
color: #5f666c;
cursor: pointer;
transition: $transition-base;
&:hover {
background: rgba($primary-blue, 0.1);
color: $primary-blue;
}
&:focus {
outline: none;
background: rgba($primary-blue, 0.1);
}
svg {
width: 20px;
height: 20px;
}
}
.dropdown-menu {
position: absolute;
top: 100%;
right: 0;
min-width: 140px;
background: $white;
border-radius: $border-radius-md;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
z-index: $z-index-dropdown;
opacity: 0;
visibility: hidden;
transform: translateY(-8px);
transition: all 0.2s ease;
overflow: hidden;
.dropdown-item {
display: block;
width: 100%;
padding: 10px 16px;
background: transparent;
border: none;
text-align: left;
font-size: 14px;
font-weight: $font-weight-regular;
color: #212529;
cursor: pointer;
transition: $transition-fast;
white-space: nowrap;
&:hover {
background: #f0f7ff;
color: $primary-blue;
}
&:active {
background: #e0efff;
}
& + .dropdown-item {
border-top: 1px solid #eee;
}
}
}
// Open state
&.open {
.dropdown-toggle {
background: rgba($primary-blue, 0.1);
color: $primary-blue;
}
.dropdown-menu {
opacity: 1;
visibility: visible;
transform: translateY(4px);
}
}
}
@@ -31,7 +31,7 @@
<div class="list-table-header">
<div class="header-cell" style="width: 90px;">NO.</div>
<div class="header-cell" style="width: 90px;">성명</div>
<div class="header-cell" style="flex: 1; min-width: 200px;">이메일 아이디</div>
<div class="header-cell" style="flex: 1; min-width: 180px;">이메일 아이디</div>
<div class="header-cell" style="width: 120px;">권한</div>
<div class="header-cell" style="width: 120px;">계정상태</div>
<div class="header-cell" style="width: 320px;">설정</div>
@@ -47,7 +47,7 @@
사용자 이름
</a>
</div>
<div class="row-cell" style="flex: 1; min-width: 200px;" data-label="이메일" th:text="${user.maskedEmailAddr}">
<div class="row-cell" style="flex: 1; min-width: 180px;" data-label="이메일" th:text="${user.maskedEmailAddr}">
user@example.com
</div>
<div class="row-cell" style="width: 120px;" data-label="권한">
@@ -61,33 +61,74 @@
<span class="user-current-badge" th:if="${user.id == currentUserId}">
<i class="fas fa-user-check"></i> 현재 사용자
</span>
<!-- Action Buttons -->
<!-- Action Buttons (Desktop) -->
<th:block th:if="${user.roleCode != null and user.id != currentUserId}">
<button type="button" class="list-table-btn list-table-btn--default assign_manager"
th:if="${user.userStatus.toString() == 'ACTIVE' && user.roleCode.toString() == 'ROLE_CORP_USER'}"
th:data-user-id="${user.id}"
th:data-user-name="${user.maskedUserName}"
th:data-user-email="${user.maskedEmailAddr}">
관리자 권한부여
</button>
<button type="button" class="list-table-btn list-table-btn--primary activate_user"
th:if="${user.userStatus.toString() != 'ACTIVE'}"
th:data-user-id="${user.id}"
th:data-user-name="${user.maskedUserName}">
활성화
</button>
<button type="button" class="list-table-btn list-table-btn--primary inactivate_user"
th:if="${user.userStatus.toString() == 'ACTIVE'}"
th:data-user-id="${user.id}"
th:data-user-name="${user.maskedUserName}">
비활성화
</button>
<button type="button" class="list-table-btn list-table-btn--secondary cancel_invitation"
th:if="${user.userStatus.toString() == 'PENDING'}"
th:data-user-id="${user.id}"
th:data-user-email="${user.maskedEmailAddr}">
초대취소
</button>
<div class="actions-desktop">
<button type="button" class="list-table-btn list-table-btn--default assign_manager"
th:if="${user.userStatus.toString() == 'ACTIVE' && user.roleCode.toString() == 'ROLE_CORP_USER'}"
th:data-user-id="${user.id}"
th:data-user-name="${user.maskedUserName}"
th:data-user-email="${user.maskedEmailAddr}">
관리자 권한부여
</button>
<button type="button" class="list-table-btn list-table-btn--primary activate_user"
th:if="${user.userStatus.toString() != 'ACTIVE'}"
th:data-user-id="${user.id}"
th:data-user-name="${user.maskedUserName}">
활성화
</button>
<button type="button" class="list-table-btn list-table-btn--primary inactivate_user"
th:if="${user.userStatus.toString() == 'ACTIVE'}"
th:data-user-id="${user.id}"
th:data-user-name="${user.maskedUserName}">
비활성화
</button>
<button type="button" class="list-table-btn list-table-btn--secondary cancel_invitation"
th:if="${user.userStatus.toString() == 'PENDING'}"
th:data-user-id="${user.id}"
th:data-user-email="${user.maskedEmailAddr}">
초대취소
</button>
</div>
<!-- Action Dropdown (Mobile) -->
<div class="actions-mobile">
<div class="dropdown-wrapper">
<button type="button" class="dropdown-toggle" aria-label="설정 메뉴">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="4" r="2" fill="currentColor"/>
<circle cx="10" cy="10" r="2" fill="currentColor"/>
<circle cx="10" cy="16" r="2" fill="currentColor"/>
</svg>
</button>
<div class="dropdown-menu">
<button type="button" class="dropdown-item assign_manager"
th:if="${user.userStatus.toString() == 'ACTIVE' && user.roleCode.toString() == 'ROLE_CORP_USER'}"
th:data-user-id="${user.id}"
th:data-user-name="${user.maskedUserName}"
th:data-user-email="${user.maskedEmailAddr}">
관리자 권한부여
</button>
<button type="button" class="dropdown-item activate_user"
th:if="${user.userStatus.toString() != 'ACTIVE'}"
th:data-user-id="${user.id}"
th:data-user-name="${user.maskedUserName}">
활성화
</button>
<button type="button" class="dropdown-item inactivate_user"
th:if="${user.userStatus.toString() == 'ACTIVE'}"
th:data-user-id="${user.id}"
th:data-user-name="${user.maskedUserName}">
비활성화
</button>
<button type="button" class="dropdown-item cancel_invitation"
th:if="${user.userStatus.toString() == 'PENDING'}"
th:data-user-id="${user.id}"
th:data-user-email="${user.maskedEmailAddr}">
초대취소
</button>
</div>
</div>
</div>
</th:block>
</div>
</div>
@@ -96,17 +137,39 @@
<div class="list-table-row" th:each="user : ${pendingUsers}">
<div class="row-cell" style="width: 90px;" data-label="NO">-</div>
<div class="row-cell" style="width: 90px;" data-label="성명">-</div>
<div class="row-cell" style="flex: 1; min-width: 200px;" data-label="이메일" th:text="${user.maskedEmailAddr}">
<div class="row-cell" style="flex: 1; min-width: 180px;" data-label="이메일" th:text="${user.maskedEmailAddr}">
pending@example.com
</div>
<div class="row-cell" style="width: 120px;" data-label="권한">이용자</div>
<div class="row-cell" style="width: 120px;" data-label="계정상태">초대중</div>
<div class="row-cell row-actions" style="width: 320px;">
<button type="button" class="list-table-btn list-table-btn--secondary cancel_invitation"
th:data-user-id="${user.id}"
th:data-user-email="${user.maskedEmailAddr}">
초대취소
</button>
<!-- Desktop -->
<div class="actions-desktop">
<button type="button" class="list-table-btn list-table-btn--secondary cancel_invitation"
th:data-user-id="${user.id}"
th:data-user-email="${user.maskedEmailAddr}">
초대취소
</button>
</div>
<!-- Mobile Dropdown -->
<div class="actions-mobile">
<div class="dropdown-wrapper">
<button type="button" class="dropdown-toggle" aria-label="설정 메뉴">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="4" r="2" fill="currentColor"/>
<circle cx="10" cy="10" r="2" fill="currentColor"/>
<circle cx="10" cy="16" r="2" fill="currentColor"/>
</svg>
</button>
<div class="dropdown-menu">
<button type="button" class="dropdown-item cancel_invitation"
th:data-user-id="${user.id}"
th:data-user-email="${user.maskedEmailAddr}">
초대취소
</button>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -143,6 +206,44 @@
});
document.addEventListener('DOMContentLoaded', function() {
// Dropdown toggle functionality
const dropdownToggles = document.querySelectorAll('.dropdown-toggle');
dropdownToggles.forEach(toggle => {
toggle.addEventListener('click', function(e) {
e.stopPropagation();
const wrapper = this.closest('.dropdown-wrapper');
const menu = wrapper.querySelector('.dropdown-menu');
const isOpen = wrapper.classList.contains('open');
// Close all other dropdowns
document.querySelectorAll('.dropdown-wrapper.open').forEach(openWrapper => {
openWrapper.classList.remove('open');
});
// Toggle current dropdown
if (!isOpen) {
wrapper.classList.add('open');
}
});
});
// Close dropdown when clicking outside
document.addEventListener('click', function(e) {
if (!e.target.closest('.dropdown-wrapper')) {
document.querySelectorAll('.dropdown-wrapper.open').forEach(wrapper => {
wrapper.classList.remove('open');
});
}
});
// Close dropdown when clicking a menu item
document.querySelectorAll('.dropdown-item').forEach(item => {
item.addEventListener('click', function() {
this.closest('.dropdown-wrapper').classList.remove('open');
});
});
// Function to open the user invite popup
function openUserInvitePopup() {
customPopups.showUserInvite({