-
Notifications
You must be signed in to change notification settings - Fork 102
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(mail): implement the send mail view #337
Changes from 4 commits
393d5f0
947ac76
cf2f20f
b528570
c4b8f59
8e6b68f
e6322b0
47231b0
d9f5c93
24f387e
355547b
cc54a2d
c7fc6c4
cd0c86e
619dfde
e22c80a
a5a7bda
7bbad19
7ab7abb
7566eb1
97a7bd4
32f321b
98597dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package apply.ui.admin.mail | ||
|
||
import apply.application.EvaluationService | ||
import apply.application.MailTargetService | ||
import apply.application.RecruitmentResponse | ||
import apply.application.RecruitmentService | ||
import apply.domain.evaluation.Evaluation | ||
import apply.domain.evaluationtarget.EvaluationStatus | ||
import apply.ui.admin.BaseLayout | ||
import com.vaadin.flow.component.Component | ||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout | ||
import com.vaadin.flow.component.select.Select | ||
import com.vaadin.flow.router.Route | ||
import support.views.Title | ||
import support.views.createItemSelect | ||
|
||
@Route(value = "group", layout = BaseLayout::class) | ||
class GroupMailFormView( | ||
private val recruitmentService: RecruitmentService, | ||
private val evaluationService: EvaluationService, | ||
private val mailTargetService: MailTargetService | ||
) : MailFormView() { | ||
private val recruitment: Select<RecruitmentResponse> | ||
private val evaluation: Select<Evaluation> | ||
private val evaluationStatus: Select<EvaluationStatus> | ||
|
||
init { | ||
evaluation = createEvaluationItem() | ||
evaluationStatus = createEvaluationStatusItem(evaluation) | ||
recruitment = createRecruitmentItem(evaluation) | ||
add(Title("그룹 발송"), createMailForm()) | ||
setWidthFull() | ||
} | ||
|
||
override fun createRecipientFilter(): Component { | ||
return HorizontalLayout(recruitment, evaluation, evaluationStatus) | ||
} | ||
|
||
private fun createRecruitmentItem(evaluation: Select<Evaluation>): Select<RecruitmentResponse> { | ||
return createItemSelect<RecruitmentResponse>("모집").apply { | ||
setItems(*recruitmentService.findAll().toTypedArray()) | ||
setItemLabelGenerator { it.title } | ||
addValueChangeListener { | ||
evaluation.apply { | ||
setItems(*evaluationService.findAllByRecruitmentId(it.value.id).toTypedArray()) | ||
setItemLabelGenerator { it.title } | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun createEvaluationItem(): Select<Evaluation> { | ||
return createItemSelect("평가") | ||
} | ||
|
||
private fun createEvaluationStatusItem(evaluation: Select<Evaluation>): Select<EvaluationStatus> { | ||
return createItemSelect<EvaluationStatus>("모집 상태").apply { | ||
setItems(*EvaluationStatus.values()) | ||
setItemLabelGenerator { it.toText() } | ||
addValueChangeListener { | ||
clearCurrentRecipients() | ||
val mailTargets = mailTargetService.findMailTargets(evaluation.value.id, it.value).map { it.email } | ||
mailTargets.forEach { addRecipientComponent(it) } | ||
} | ||
} | ||
} | ||
|
||
private fun EvaluationStatus.toText() = | ||
when (this) { | ||
EvaluationStatus.WAITING -> "평가 전" | ||
EvaluationStatus.PASS -> "합격" | ||
EvaluationStatus.FAIL -> "탈락" | ||
EvaluationStatus.PENDING -> "보류" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package apply.ui.admin.mail | ||
|
||
import apply.application.ApplicantResponse | ||
import apply.application.ApplicantService | ||
import apply.ui.admin.BaseLayout | ||
import com.vaadin.flow.component.Component | ||
import com.vaadin.flow.component.Key | ||
import com.vaadin.flow.component.button.Button | ||
import com.vaadin.flow.component.dialog.Dialog | ||
import com.vaadin.flow.component.grid.Grid | ||
import com.vaadin.flow.component.html.Div | ||
import com.vaadin.flow.component.html.H2 | ||
import com.vaadin.flow.component.icon.Icon | ||
import com.vaadin.flow.component.icon.VaadinIcon | ||
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.component.textfield.TextField | ||
import com.vaadin.flow.data.renderer.ComponentRenderer | ||
import com.vaadin.flow.data.renderer.Renderer | ||
import com.vaadin.flow.router.Route | ||
import support.views.Title | ||
import support.views.addSortableColumn | ||
import support.views.createErrorButton | ||
import support.views.createNormalButton | ||
import support.views.createPrimaryButton | ||
import support.views.createSearchBar | ||
|
||
@Route(value = "personal", layout = BaseLayout::class) | ||
class IndividualMailFormView( | ||
private val applicantService: ApplicantService | ||
) : MailFormView() { | ||
init { | ||
add(Title("개별 발송"), createMailForm()) | ||
setWidthFull() | ||
} | ||
|
||
override fun createRecipientFilter(): Component { | ||
return HorizontalLayout( | ||
createDirectInsertTargetComponent { | ||
if (it.isNotBlank()) { | ||
addRecipientComponent(it) | ||
} | ||
}, | ||
createSearchTargetComponent() | ||
) | ||
} | ||
|
||
private fun createDirectInsertTargetComponent(eventListener: (name: String) -> Unit): Div { | ||
val mailTarget = TextField().apply { | ||
addKeyDownListener( | ||
Key.ENTER, | ||
{ | ||
eventListener(this.value) | ||
this.value = "" | ||
} | ||
) | ||
} | ||
|
||
return Div( | ||
HorizontalLayout( | ||
mailTarget, | ||
Button(Icon(VaadinIcon.ENTER_ARROW)) { | ||
eventListener(mailTarget.value) | ||
mailTarget.value = "" | ||
} | ||
) | ||
) | ||
} | ||
|
||
private fun createSearchTargetComponent(): Button { | ||
return createNormalButton("지원자 조회") { | ||
Dialog().apply { | ||
width = "800px" | ||
height = "90%" | ||
add( | ||
H2("지원자 정보 검색"), | ||
HorizontalLayout( | ||
createAddRecipients(), | ||
HorizontalLayout( | ||
createErrorButton("취소") { | ||
close() | ||
} | ||
).apply { | ||
justifyContentMode = FlexComponent.JustifyContentMode.END | ||
defaultHorizontalComponentAlignment = FlexComponent.Alignment.END | ||
} | ||
) | ||
).apply { | ||
setWidthFull() | ||
} | ||
open() | ||
} | ||
}.apply { isEnabled = true } | ||
} | ||
|
||
private fun createAddRecipients(): Component { | ||
val container = VerticalLayout() | ||
return VerticalLayout( | ||
createSearchBar { | ||
container.removeAll() | ||
val founds = applicantService.findAllByKeyword(it) | ||
if (founds.isNotEmpty()) { | ||
container.add( | ||
createGrid(founds) | ||
) | ||
} | ||
}, | ||
container | ||
) | ||
} | ||
|
||
private fun createGrid(applicantResponse: List<ApplicantResponse>): Component { | ||
return Grid<ApplicantResponse>(10).apply { | ||
addSortableColumn("이름", ApplicantResponse::name) | ||
addSortableColumn("이메일", ApplicantResponse::email) | ||
addColumn(createEditAndDeleteButton()).apply { isAutoWidth = true } | ||
setItems(applicantResponse) | ||
} | ||
} | ||
|
||
private fun createEditAndDeleteButton(): Renderer<ApplicantResponse> { | ||
return ComponentRenderer<Component, ApplicantResponse> { applicantResponse -> | ||
HorizontalLayout( | ||
createAddOrDeleteButton(applicantResponse) | ||
) | ||
} | ||
} | ||
|
||
private fun createAddOrDeleteButton(applicantResponse: ApplicantResponse): Component { | ||
if (this.recipients.contains(applicantResponse.email)) { | ||
return createTargetDeleteButton(applicantResponse) | ||
} | ||
|
||
return createTargetAddButton(applicantResponse) | ||
} | ||
|
||
private fun createTargetAddButton(applicantResponse: ApplicantResponse): Button { | ||
return createPrimaryButton("추가") { | ||
addRecipientComponent(applicantResponse.email) | ||
}.apply { | ||
isDisableOnClick = true | ||
} | ||
} | ||
|
||
private fun createTargetDeleteButton(applicantResponse: ApplicantResponse): Button { | ||
return createErrorButton("삭제") { | ||
removeRecipientComponent(applicantResponse.email) | ||
}.apply { | ||
isDisableOnClick = true | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package apply.ui.admin.mail | ||
|
||
import com.vaadin.flow.component.Component | ||
import com.vaadin.flow.component.button.Button | ||
import com.vaadin.flow.component.html.H3 | ||
import com.vaadin.flow.component.html.H4 | ||
import com.vaadin.flow.component.html.Span | ||
import com.vaadin.flow.component.icon.Icon | ||
import com.vaadin.flow.component.icon.VaadinIcon | ||
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.component.textfield.TextArea | ||
import com.vaadin.flow.component.textfield.TextField | ||
import com.vaadin.flow.component.upload.receivers.MultiFileMemoryBuffer | ||
import com.vaadin.flow.router.RoutePrefix | ||
import support.views.createImageUpload | ||
import support.views.createPrimaryButton | ||
|
||
@RoutePrefix(value = "admin/emails") | ||
abstract class MailFormView() : VerticalLayout() { | ||
protected val subject: TextField = TextField("메일 제목", "메일 제목 입력") | ||
protected val recipients: MutableList<String> = mutableListOf() | ||
protected val body: TextArea = createMailBody() | ||
private val currentRecipients = HorizontalLayout() | ||
|
||
abstract fun createRecipientFilter(): Component | ||
|
||
protected fun createMailForm(): Component { | ||
val subjectText = VerticalLayout( | ||
H3("메일 제목"), | ||
subject.apply { setSizeFull() } | ||
) | ||
|
||
val recipientFilter = VerticalLayout(H4("수신자"), createRecipientFilter()) | ||
val uploadFile = createImageUpload("첨부파일", MultiFileMemoryBuffer()) { | ||
/* | ||
todo: 추후 업로드 된 파일을 메일로 첨부하는 로직이 추가되어야 함 | ||
(uploadFiles 같은 필드를 두고 mail을 보내는 기능에 포함시키면 될 것 같음) | ||
it.files.forEach { fileName -> | ||
val fileData = it.getFileData(fileName) | ||
val inputStream = it.getInputStream(fileName) | ||
val readBytes = inputStream.readBytes() | ||
} | ||
*/ | ||
}.apply { | ||
setWidthFull() | ||
} | ||
|
||
val mailBody = VerticalLayout( | ||
body | ||
).apply { | ||
setSizeFull() | ||
} | ||
|
||
val sendButton = createMailSendButton() | ||
|
||
return VerticalLayout( | ||
subjectText, | ||
recipientFilter, | ||
currentRecipients.apply { setSizeFull() }, | ||
uploadFile, | ||
mailBody, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 대대적인 개편작업 중인데, 때마침 이 부분은 배럴이 코멘트 남겨주신 것과 동일하게 변경했네요! 👍 |
||
sendButton | ||
).apply { | ||
setSizeFull() | ||
justifyContentMode = FlexComponent.JustifyContentMode.CENTER | ||
defaultHorizontalComponentAlignment = FlexComponent.Alignment.CENTER | ||
} | ||
} | ||
|
||
private fun createMailSendButton(): VerticalLayout { | ||
return VerticalLayout( | ||
createPrimaryButton("전송") { | ||
// todo: emailService.메일전송(subject.value, recipients, body.value) | ||
// MailService에 | ||
} | ||
).apply { | ||
setSizeFull() | ||
justifyContentMode = FlexComponent.JustifyContentMode.CENTER | ||
defaultHorizontalComponentAlignment = FlexComponent.Alignment.CENTER | ||
} | ||
} | ||
|
||
protected fun addRecipientComponent(email: String) { | ||
this.currentRecipients.apply { | ||
recipients.add(email) | ||
add(createRecipientComponent(email)) | ||
} | ||
} | ||
|
||
protected fun clearCurrentRecipients() { | ||
this.currentRecipients.removeAll() | ||
this.recipients.clear() | ||
} | ||
|
||
protected fun removeRecipientComponent(email: String) { | ||
recipients.remove(email) | ||
addAllCurrentRecipientComponent() | ||
} | ||
|
||
private fun addAllCurrentRecipientComponent() { | ||
this.currentRecipients.removeAll() | ||
this.currentRecipients.apply { | ||
recipients.forEach { | ||
add(createRecipientComponent(it)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분은 저도 변경해보고 싶었는데... component를 replace 하도록 해보려했는데... 생각한대로 잘 안돼서 지금처럼 Disable로 변경했는데... 음 일단은 지금 방식이 최선이었는데 이 부분은 혹시 추가 버튼 클릭 시에, 삭제 버튼(다른 버튼)으로 변경할 수 있는 방법 아시는 분...의 제보를 받습니다 ... 썸바딘 헬프 유... |
||
} | ||
} | ||
} | ||
|
||
private fun createMailBody(): TextArea { | ||
return TextArea("메일 본문").apply { | ||
setSizeFull() | ||
style.set("minHeight", "400px") | ||
placeholder = "메일 본문 입력" | ||
} | ||
} | ||
|
||
private fun createRecipientComponent(email: String): Component { | ||
val emailTarget = TextField().apply { | ||
value = email | ||
isReadOnly = true | ||
style.set("background-color", "#00B493") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹..시 초록색 배경은 어떤 이유인가요?ㅎㅎ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아.... 배민 색을 따온건데.... 👀 |
||
} | ||
|
||
return Span( | ||
emailTarget, | ||
Button(Icon(VaadinIcon.CLOSE_SMALL)) { | ||
removeRecipientComponent(email) | ||
} | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ㅠㅠㅠㅠ 저 바딘 잘 몰라서 사실 잘 모르는데... 갯수가 많아지면 옆으로 계속 늘어날 것 같은데 이건 괜찮까 싶네요!
단체메일보내면 꽤 많을 듯 싶어서요.
더불어..
x
의 위치도 아래로 변경됩니당!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
개별 조회에서
지원자 조회
탭을 누르지 않고 test 하고 엔터만 치면 그대로 입력되는데 이것도 괜찮을까요..? 해당하는 이메일을 불러와야 하지 않을까 싶어서요 혹은 엔터 칸을 없애야하지 않을까 싶기도 해요! 로키의 작업범위가 아니라면 알려주세요~ㅎㅎㅎThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엔터 키를 누르거나 혹은 엔터 버튼을 눌렀을 때, 직접 작성한 이메일이 등록되는 기능은 이슈의 내용 중 일부인 아래의 요구사항 때문에 추가한 기능입니다.
현재 저희 지원 페이지에 가입되어있는 이메일이 아니더라도 메일을 보낼 수 있도록하기 위함입니다 😃
(물론 이메일양식에 맞지 않는 이메일을 등록한다거나... 등등 여러가지 예외 사항들을 잡으면 좋겠지만... 그 부분까지는 고려하지 못했네요 🥲)
요구사항 중 일부
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
배럴이 발견하신 문제점... x위치가 아래로 내려가는 문제는
currentRecipients
를 Div 컴포넌트로 변경하니까 전보다는 나아진 것 같네요 🤔