Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve the side navigation ui #650

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 1 addition & 34 deletions src/main/kotlin/apply/ui/admin/BaseLayout.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
package apply.ui.admin

import apply.application.RecruitmentService
import apply.ui.admin.administrator.AdministratorsView
import apply.ui.admin.cheater.CheatersView
import apply.ui.admin.evaluation.EvaluationsView
import apply.ui.admin.mail.MailsView
import apply.ui.admin.recruitment.RecruitmentsView
import apply.ui.admin.term.TermsView
import com.vaadin.flow.component.Component
import com.vaadin.flow.component.applayout.AppLayout
import com.vaadin.flow.component.applayout.DrawerToggle
import com.vaadin.flow.component.html.H1
import com.vaadin.flow.component.html.Image
import com.vaadin.flow.component.orderedlayout.FlexComponent
import com.vaadin.flow.component.orderedlayout.HorizontalLayout
import com.vaadin.flow.component.orderedlayout.VerticalLayout
import com.vaadin.flow.theme.Theme
import com.vaadin.flow.theme.lumo.Lumo
import support.views.createTabs

@Theme(value = Lumo::class)
class BaseLayout(
Expand All @@ -32,38 +24,13 @@ class BaseLayout(
private fun createDrawer(): Component {
return VerticalLayout().apply {
setSizeFull()
element.style.set("overflow", "auto")
themeList["dark"] = true
alignItems = FlexComponent.Alignment.CENTER
add(createTitle(), createLogo(width), createMenu())
add(createTitle(), MenuLayout(recruitmentService))
}
}

private fun createTitle(): Component {
return HorizontalLayout(H1("지원 플랫폼"))
}

private fun createLogo(width: String): Component {
return Image("images/logo/logo_full_white.png", "우아한테크코스").apply {
maxWidth = width
}
}

private fun createMenu(): Component {
return createTabs(createMenuItems().map { it.toComponent() })
}

private fun createMenuItems(): List<MenuItem> {
val recruitments = recruitmentService.findAll()
return listOf(
"기수 관리" of TermsView::class.java,
"모집 관리" of RecruitmentsView::class.java,
"평가 관리" of EvaluationsView::class.java,
"과제 관리".accordionOf("admin/missions", recruitments),
"선발 과정".accordionOf("admin/selections", recruitments),
"부정행위자" of CheatersView::class.java,
"메일 관리" of MailsView::class.java,
"관리자" of AdministratorsView::class.java
)
}
}
99 changes: 73 additions & 26 deletions src/main/kotlin/apply/ui/admin/MenuItems.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,105 @@ package apply.ui.admin

import apply.application.RecruitmentResponse
import com.vaadin.flow.component.Component
import com.vaadin.flow.component.accordion.Accordion
import com.vaadin.flow.component.details.DetailsVariant
import com.vaadin.flow.component.html.Anchor
import com.vaadin.flow.component.combobox.ComboBox
import com.vaadin.flow.component.tabs.Tab
import com.vaadin.flow.router.RouterLink
import support.views.createTabs
import support.views.HasUrlParamLayout
import support.views.createBorder
import support.views.createHiddenTab
import support.views.createRouteLink

infix fun String.of(navigationTarget: Class<out Component>): MenuItem {
return SingleMenuItem(this, navigationTarget)
}

fun String.accordionOf(path: String, recruitments: List<RecruitmentResponse>): MenuItem {
return AccordionMenuItem(this, path, recruitments.map { it.toContent() })
infix fun String.of(navigationTarget: Class<out HasUrlParamLayout<Long>>): ParamMenuItem {
return ParamMenuItem(this, navigationTarget)
}

private fun RecruitmentResponse.toContent(): AccordionContent = AccordionContent(id, title)
fun String.createComboBoxTab(recruitments: List<RecruitmentResponse>, innerItems: List<ParamMenuItem>): MenuItem {
return ComboBoxMenuItem(this, recruitments.reversed(), innerItems)
}

fun createBorderItem(): MenuItem {
return BorderMenuItem()
}

sealed class MenuItem(protected val title: String = "") {
abstract fun toComponents(): List<Component>
}

sealed class MenuItem(protected val title: String) {
abstract fun toComponent(): Component
class BorderMenuItem : MenuItem() {
override fun toComponents(): List<Component> {
return listOf(
Tab(createBorder()).apply {
isEnabled = false
}
)
}
}

class SingleMenuItem(
title: String,
private val navigationTarget: Class<out Component>
) : MenuItem(title) {
override fun toComponent(): Component {
return Tab(RouterLink(title, navigationTarget))
override fun toComponents(): List<Component> {
val link = createRouteLink(title).apply {
setRoute(navigationTarget)
}
return listOf(Tab(link))
}
}

data class AccordionContent(val id: Any, val title: String)
class ParamMenuItem(
title: String,
private val navigationTarget: Class<out HasUrlParamLayout<Long>>
) : MenuItem(title) {
private val link: RouterLink = createRouteLink(title)
private val tab: Tab = createHiddenTab(link)

class AccordionMenuItem(
override fun toComponents(): List<Component> {
return listOf(tab)
}

fun show() {
tab.isVisible = true
tab.isSelected = false
}

fun setRoute(id: Long) {
link.setRoute(navigationTarget, id)
}
}

class ComboBoxMenuItem(
title: String,
private val path: String,
private val contents: List<AccordionContent>
private val contents: List<RecruitmentResponse>,
private val innerItems: List<ParamMenuItem>
) : MenuItem(title) {
override fun toComponent(): Component {
return Accordion().apply {
add(title, createTabs()).addThemeVariants(DetailsVariant.REVERSE)
close()
override fun toComponents(): List<Component> {
val comboBoxTab = Tab(createComboBox()).apply {
style.set("justify-content", "center")
}
}

private fun createTabs(): Component? {
if (contents.isEmpty()) {
return null
return mutableListOf<Component>().apply {
add(comboBoxTab)
addAll(innerItems.flatMap { it.toComponents() })
}
return createTabs(contents.toTabs(path))
}

private fun List<AccordionContent>.toTabs(path: String): List<Component> {
return map { Tab(Anchor("$path/${it.id}", it.title)) }
private fun createComboBox(): ComboBox<RecruitmentResponse> {
return ComboBox<RecruitmentResponse>("모집을 선택해 선발을 진행하세요.").apply {
placeholder = title
setItems(contents)
setItemLabelGenerator { it.title }

addValueChangeListener {
innerItems.forEach {
it.show()
it.setRoute(value.id)
}
}
}
}
}
88 changes: 88 additions & 0 deletions src/main/kotlin/apply/ui/admin/MenuLayout.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package apply.ui.admin

import apply.application.RecruitmentService
import apply.ui.admin.administrator.AdministratorsView
import apply.ui.admin.cheater.CheatersView
import apply.ui.admin.evaluation.EvaluationsView
import apply.ui.admin.mail.MailsView
import apply.ui.admin.mission.MissionsView
import apply.ui.admin.recruitment.RecruitmentsView
import apply.ui.admin.selections.SelectionView
import apply.ui.admin.term.TermsView
import com.vaadin.flow.component.orderedlayout.FlexComponent
import com.vaadin.flow.component.orderedlayout.VerticalLayout
import com.vaadin.flow.component.tabs.Tabs
import support.views.createTabs

private const val UNSELECT_ALL: Int = -1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UNSELECT_ALL 이라는 네이밍과 -1 이라는 값이 잘 이해가 가지 않아요.
사용하는 곳에서는 현재 선택된 INDEX를 -1 로 바꾸로 바꾸는 용도로 사용하는 것 같아서요!
a: 만약 다른 메뉴에서 현재 선택된 INDEX 를 -1로 바꾸는 것이면 UNSELECTED 정도가 괜찮지 않을까요?
(제가 잘못 이해한 것일수도 있습니다. 😅)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

바딘에서 제공하는 Tabs에 나와있는 setSelectedIndex의 javaDocs에는 -1 to unselect all 이라고 적혀있어서 이를 참고해서 네이밍했습니다 😋


class MenuLayout(
private val recruitmentService: RecruitmentService
) : VerticalLayout() {
private val topMenu = createMenu(createTopMenuItems())
private val bottomMenu = createMenu(createBottomMenuItems())
Comment on lines +22 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a: topMenu, bottomMenu 보다 더 직관적인 변수명으로 짓는 것이 어떨까요?
ex) topMenu : 선발진행관련 변수명

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 선발진행관련 메뉴 아이템들이 해당 공간에 존재하지만 추후 어떤 메뉴 아이템이 들어올지 변경될 수 있고 그렇다면 네이밍 또한 변할 것 같아 이렇게 포괄적인 네이밍을 지었던 것 같습니다.


init {
setHeightFull()
isPadding = false
addMenuSelectSeparateEvent()
add(createTopMenuLayer(), createBottomMenuLayer())
}

private fun createMenu(list: List<MenuItem>): Tabs {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a: 호출하는 메서드와 가깝게 두면 읽기가 더 편할 것 같아요!
(다른 메서드들도 마찬가지로요!)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드 위치를 수정해보았습니다 😊

return createTabs(list.flatMap { it.toComponents() })
}

private fun createTopMenuItems(): List<MenuItem> {
val recruitments = recruitmentService.findAll()
return listOf(
"모집선택".createComboBoxTab(
recruitments,
listOf(
"과제 관리" of MissionsView::class.java,
"선발 과정" of SelectionView::class.java,
)
)
)
}

private fun createBottomMenuItems(): List<MenuItem> {
return listOf(
createBorderItem(),
"기수 관리" of TermsView::class.java,
"모집 관리" of RecruitmentsView::class.java,
"평가 관리" of EvaluationsView::class.java,
createBorderItem(),
"메일 관리" of MailsView::class.java,
"부정행위자" of CheatersView::class.java,
"관리자" of AdministratorsView::class.java
)
}

private fun addMenuSelectSeparateEvent() {
topMenu.addSelectedChangeListener {
bottomMenu.selectedIndex = UNSELECT_ALL
topMenu.selectedTab = it.selectedTab
}
bottomMenu.addSelectedChangeListener {
topMenu.selectedIndex = UNSELECT_ALL
bottomMenu.selectedTab = it.selectedTab
}
}

private fun createTopMenuLayer(): VerticalLayout {
return VerticalLayout().apply {
isPadding = false
add(topMenu)
}
}

private fun createBottomMenuLayer(): VerticalLayout {
return VerticalLayout().apply {
isPadding = false
setHeightFull()
justifyContentMode = FlexComponent.JustifyContentMode.END
add(bottomMenu)
}
}
}
9 changes: 6 additions & 3 deletions src/main/kotlin/apply/ui/admin/mission/MissionsView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ import com.vaadin.flow.component.button.Button
import com.vaadin.flow.component.grid.Grid
import com.vaadin.flow.component.orderedlayout.FlexComponent
import com.vaadin.flow.component.orderedlayout.HorizontalLayout
import com.vaadin.flow.component.orderedlayout.VerticalLayout
import com.vaadin.flow.data.renderer.ComponentRenderer
import com.vaadin.flow.data.renderer.Renderer
import com.vaadin.flow.router.BeforeEvent
import com.vaadin.flow.router.HasUrlParameter
import com.vaadin.flow.router.Route
import com.vaadin.flow.router.WildcardParameter
import support.views.EDIT_VALUE
import support.views.HasUrlParamLayout
import support.views.NEW_VALUE
import support.views.Title
import support.views.addSortableColumn
Expand All @@ -31,11 +30,15 @@ import support.views.createPrimarySmallButton
class MissionsView(
private val recruitmentService: RecruitmentService,
private val missionService: MissionService
) : VerticalLayout(), HasUrlParameter<Long> {
) : HasUrlParamLayout<Long>() {
private var recruitmentId: Long = 0L

override fun setParameter(event: BeforeEvent, @WildcardParameter parameter: Long) {
if (recruitmentId == parameter) {
return
}
recruitmentId = parameter
removeAll()
add(createTitle(), createButton(), createGrid())
}

Expand Down
8 changes: 6 additions & 2 deletions src/main/kotlin/apply/ui/admin/selections/SelectionView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ import com.vaadin.flow.component.upload.receivers.MemoryBuffer
import com.vaadin.flow.data.renderer.ComponentRenderer
import com.vaadin.flow.data.renderer.Renderer
import com.vaadin.flow.router.BeforeEvent
import com.vaadin.flow.router.HasUrlParameter
import com.vaadin.flow.router.Route
import com.vaadin.flow.router.WildcardParameter
import support.views.HasUrlParamLayout
import support.views.addSortableColumn
import support.views.addSortableDateColumn
import support.views.addSortableDateTimeColumn
Expand All @@ -64,7 +64,7 @@ class SelectionView(
private val myMissionService: MyMissionService,
private val excelService: ExcelService,
private val evaluationTargetCsvService: EvaluationTargetCsvService
) : VerticalLayout(), HasUrlParameter<Long> {
) : HasUrlParamLayout<Long>() {
private var recruitmentId: Long = 0L
private var evaluations: List<EvaluationSelectData> =
evaluationService.getAllSelectDataByRecruitmentId(recruitmentId)
Expand All @@ -73,7 +73,11 @@ class SelectionView(
private var evaluationFileButtons: HorizontalLayout = createEvaluationFileButtons()

override fun setParameter(event: BeforeEvent, @WildcardParameter parameter: Long) {
if (recruitmentId == parameter) {
return
}
this.recruitmentId = parameter
removeAll()
add(createTitle(), createContent())
}

Expand Down
Loading