Files
tier-maker/frontend/src/components/admin/AdminUsersSection.vue

154 lines
6.8 KiB
Vue

<script setup>
import { computed } from 'vue'
import SvgIcon from '../SvgIcon.vue'
const props = defineProps({
userQuery: { type: String, required: true },
userSort: { type: String, required: true },
userSortDirection: { type: String, required: true },
users: { type: Array, required: true },
submitUserFilters: { type: Function, required: true },
setUserAvatarInput: { type: Function, required: true },
onUserAvatarChange: { type: Function, required: true },
openUserAvatarPicker: { type: Function, required: true },
userAvatarUrl: { type: Function, required: true },
userDisplayName: { type: Function, required: true },
userAvatarFallback: { type: Function, required: true },
removeUserAvatar: { type: Function, required: true },
canEditUserAvatar: { type: Function, required: true },
canEditUserInfo: { type: Function, required: true },
canResetUserPassword: { type: Function, required: true },
canDeleteUser: { type: Function, required: true },
roleLabelOf: { type: Function, required: true },
fmt: { type: Function, required: true },
openUserPasswordModal: { type: Function, required: true },
openUserDeleteModal: { type: Function, required: true },
openUserEditModal: { type: Function, required: true },
lockResetIcon: { type: String, required: true },
deleteIcon: { type: String, required: true },
})
const emit = defineEmits(['update:userQuery', 'update:userSort', 'update:userSortDirection'])
const userQueryModel = computed({
get: () => props.userQuery,
set: (value) => emit('update:userQuery', value),
})
const userSortModel = computed({
get: () => props.userSort,
set: (value) => emit('update:userSort', value),
})
const userSortDirectionModel = computed({
get: () => props.userSortDirection,
set: (value) => emit('update:userSortDirection', value),
})
</script>
<template>
<div class="panel">
<div class="sectionHeader">
<div>
<div class="panel__title">회원 관리</div>
<div class="hint hint--tight">회원 프로필을 정리하고, 필요한 경우에만 권한 변경과 비밀번호 초기화를 진행할 있어요.</div>
</div>
</div>
<div class="toolbar toolbar--secondary">
<input v-model="userQueryModel" class="input toolbar__search" placeholder="이메일, 닉네임 검색" @keydown.enter.prevent="props.submitUserFilters" />
<select v-model="userSortModel" class="select toolbar__select" @change="props.submitUserFilters">
<option value="recent">최근 활동순</option>
<option value="created">가입순</option>
<option value="tierlists">작성 티어표 많은 </option>
</select>
<select v-model="userSortDirectionModel" class="select toolbar__select" @change="props.submitUserFilters">
<option value="desc">내림차순</option>
<option value="asc">오름차순</option>
</select>
<button class="btn btn--ghost toolbar__button" type="button" @click="props.submitUserFilters">조회</button>
</div>
<div v-if="!props.users.length" class="hint">아직 가입한 회원이 없어요.</div>
<div v-else class="userList">
<article v-for="user in props.users" :key="user.id" class="userCard">
<div class="userCard__head">
<div class="userCard__identity">
<input
:ref="(el) => props.setUserAvatarInput(user.id, el)"
type="file"
accept="image/*"
class="srOnlyInput"
@change="props.onUserAvatarChange(user, $event)"
/>
<div class="userAvatarWrap">
<button
class="userAvatar userAvatarButton"
type="button"
:disabled="user.isAvatarBusy || !props.canEditUserAvatar(user)"
@click="props.openUserAvatarPicker(user)"
>
<img v-if="props.userAvatarUrl(user)" class="userAvatar__image" :src="props.userAvatarUrl(user)" :alt="props.userDisplayName(user)" />
<span v-else class="userAvatar__fallback">{{ props.userAvatarFallback(user) }}</span>
<span class="userAvatarButton__overlay">{{ user.isAvatarBusy ? '업데이트중...' : '수정' }}</span>
</button>
<button
v-if="user?.avatarSrc"
class="userAvatarRemoveButton"
type="button"
title="회원 썸네일 삭제"
:disabled="user.isAvatarBusy || !props.canEditUserAvatar(user)"
@click.stop="props.removeUserAvatar(user)"
>
<SvgIcon class="userAvatarRemoveIcon" :src="props.deleteIcon" :size="12" />
</button>
</div>
<div class="userCard__identityMeta">
<div class="userCard__title">{{ props.userDisplayName(user) }}</div>
<div class="userCard__meta">{{ user.email }}</div>
</div>
</div>
</div>
<div v-if="user.isAdmin" class="roleBadge userCard__roleBadge">{{ props.roleLabelOf(user) }}</div>
<div class="userInfoList">
<div class="userInfoLine"><span>가입일</span><strong>{{ props.fmt(user.createdAt) }}</strong></div>
<div class="userInfoLine"><span>작성 티어표</span><strong>{{ user.tierListCount }}</strong></div>
<div class="userInfoLine"><span>최근 활동</span><strong>{{ props.fmt(user.recentActivityAt || user.createdAt) }}</strong></div>
<div class="userInfoLine"><span>계정명</span><strong>{{ user.email }}</strong></div>
<div class="userInfoLine"><span>닉네임</span><strong>{{ user.nickname || '미설정' }}</strong></div>
<div class="userInfoLine"><span>권한</span><strong>{{ props.roleLabelOf(user) }}</strong></div>
</div>
<div class="userCard__actions userCard__actions--compact">
<button
class="iconActionButton"
type="button"
title="비밀번호 초기화"
:disabled="!props.canResetUserPassword(user)"
@click="props.openUserPasswordModal(user)"
>
<SvgIcon class="iconActionButton__icon" :src="props.lockResetIcon" :size="18" />
</button>
<button
class="iconActionButton iconActionButton--danger"
type="button"
title="회원 삭제"
:disabled="!props.canDeleteUser(user)"
@click="props.openUserDeleteModal(user)"
>
<SvgIcon class="iconActionButton__icon" :src="props.deleteIcon" :size="18" />
</button>
<button
class="btn btn--ghost userSaveButton"
type="button"
:disabled="!props.canEditUserInfo(user)"
@click="props.openUserEditModal(user)"
>
회원 정보 수정
</button>
</div>
</article>
</div>
</div>
</template>