문의하기 스타일 적용

This commit is contained in:
현성필
2025-12-04 15:59:35 +09:00
parent dacae095c5
commit b1dbd4845e
6 changed files with 854 additions and 344 deletions
@@ -303,7 +303,6 @@
}
.inquiry-detail-container {
max-width: 960px;
margin: 0 auto;
}
@@ -578,7 +577,6 @@
}
.inquiry-form-container {
max-width: 960px;
margin: 0 auto;
}
@@ -576,3 +576,463 @@
}
}
}
// -----------------------------------------------------------------------------
// Inquiry Detail Page - Based on Notice Detail Design
// -----------------------------------------------------------------------------
.inquiry-detail-container {
max-width: 1228px;
margin: 0 auto;
padding: $spacing-3xl $spacing-lg;
@media (max-width: $breakpoint-md) {
padding: $spacing-2xl $spacing-md;
}
}
// Inquiry Detail Header
.inquiry-detail-header {
padding-bottom: $spacing-md;
border-bottom: 1px solid #212529;
margin-bottom: $spacing-xl;
}
.inquiry-header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $spacing-md;
@media (max-width: $breakpoint-md) {
flex-direction: column;
align-items: flex-start;
gap: $spacing-sm;
}
}
.inquiry-detail-title {
font-size: 24px;
font-weight: $font-weight-bold;
color: #212529;
line-height: 1.4;
margin: 0;
@media (max-width: $breakpoint-md) {
font-size: 20px;
}
}
.inquiry-detail-date {
font-size: 18px;
font-weight: $font-weight-regular;
color: #515151;
@media (max-width: $breakpoint-md) {
font-size: 16px;
}
}
// Inquiry Attachment Section
.inquiry-detail-attachment {
margin-bottom: $spacing-lg;
.attachment-list {
display: flex;
flex-direction: column;
gap: $spacing-sm;
}
.attachment-item {
display: flex;
align-items: center;
}
}
.inquiry-attachment-link {
display: inline-flex;
align-items: center;
gap: $spacing-xs;
color: #515151;
font-size: 18px;
text-decoration: none;
transition: $transition-base;
@media (max-width: $breakpoint-md) {
font-size: 14px;
}
&:hover {
color: $primary-blue;
}
svg {
flex-shrink: 0;
color: #515151;
}
&:hover svg {
color: $primary-blue;
}
.attachment-label {
margin-right: $spacing-md;
}
.attachment-filename {
color: #515151;
}
}
// Inquiry Content Section
.inquiry-detail-content {
margin-bottom: $spacing-2xl;
}
.inquiry-content-body {
background: #F6F9FB;
border-radius: 20px;
padding: $spacing-2xl;
min-height: 200px;
font-size: $font-size-base;
color: $text-dark;
line-height: $line-height-loose;
white-space: pre-line;
@media (max-width: $breakpoint-md) {
padding: $spacing-lg;
min-height: 150px;
border-radius: 12px;
}
p {
margin-bottom: $spacing-md;
&:last-child {
margin-bottom: 0;
}
}
img {
max-width: 100%;
height: auto;
border-radius: $border-radius-md;
margin: $spacing-lg 0;
}
a {
color: $primary-blue;
text-decoration: underline;
&:hover {
color: $secondary-blue;
}
}
}
// Admin Response Section
.inquiry-response-section {
margin-bottom: $spacing-2xl;
}
.inquiry-response-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: $spacing-md;
border-bottom: 1px solid #DADADA;
margin-bottom: $spacing-lg;
@media (max-width: $breakpoint-md) {
flex-direction: column;
align-items: flex-start;
gap: $spacing-sm;
}
}
.response-label {
font-size: 20px;
font-weight: $font-weight-bold;
color: #0049b4;
@media (max-width: $breakpoint-md) {
font-size: 18px;
}
}
.response-date {
font-size: 18px;
font-weight: $font-weight-regular;
color: #515151;
@media (max-width: $breakpoint-md) {
font-size: 16px;
}
}
.inquiry-response-body {
background: #ffffff;
border: 1px solid #E5E7EB;
border-radius: 20px;
padding: $spacing-2xl;
min-height: 150px;
font-size: $font-size-base;
color: $text-dark;
line-height: $line-height-loose;
white-space: pre-line;
@media (max-width: $breakpoint-md) {
padding: $spacing-lg;
min-height: 100px;
border-radius: 12px;
}
}
// Inquiry Detail Actions
.inquiry-detail-actions {
display: flex;
justify-content: space-between;
align-items: center;
gap: $spacing-md;
@media (max-width: $breakpoint-md) {
flex-direction: column-reverse;
gap: $spacing-lg;
}
.actions-left {
display: flex;
gap: $spacing-md;
}
.actions-right {
display: flex;
gap: $spacing-md;
}
}
// Inquiry Action Buttons
.btn-inquiry-list {
display: flex;
align-items: center;
justify-content: center;
width: 200px;
height: 60px;
background: #E5E7EB;
border: none;
border-radius: 12px;
font-family: $font-family-primary;
font-size: 18px;
font-weight: $font-weight-bold;
color: #5F666C;
cursor: pointer;
transition: $transition-base;
@media (max-width: $breakpoint-md) {
width: 160px;
height: 50px;
font-size: 16px;
}
&:hover {
background: darken(#E5E7EB, 5%);
}
&:active {
transform: scale(0.98);
}
}
.btn-inquiry-edit {
display: flex;
align-items: center;
justify-content: center;
width: 200px;
height: 60px;
background: #0049b4;
border: none;
border-radius: 12px;
font-family: $font-family-primary;
font-size: 18px;
font-weight: $font-weight-bold;
color: $white;
cursor: pointer;
transition: $transition-base;
@media (max-width: $breakpoint-md) {
width: 100px;
height: 50px;
font-size: 16px;
}
&:hover {
background: darken(#0049b4, 5%);
}
&:active {
transform: scale(0.98);
}
}
.btn-inquiry-delete {
display: flex;
align-items: center;
justify-content: center;
width: 200px;
height: 60px;
background: #DC3545;
border: none;
border-radius: 12px;
font-family: $font-family-primary;
font-size: 18px;
font-weight: $font-weight-bold;
color: $white;
cursor: pointer;
transition: $transition-base;
@media (max-width: $breakpoint-md) {
width: 100px;
height: 50px;
font-size: 16px;
}
&:hover {
background: darken(#DC3545, 5%);
}
&:active {
transform: scale(0.98);
}
}
// -----------------------------------------------------------------------------
// Inquiry Form Page - Based on apiKeyRegisterStep1 and mypage designs
// -----------------------------------------------------------------------------
.inquiry-form-container {
max-width: 1228px;
margin: 0 auto;
padding: $spacing-3xl $spacing-lg;
@media (max-width: $breakpoint-md) {
padding: $spacing-2xl $spacing-md;
}
}
// File Upload Inline - Figma Design (node 1029-1507)
// Layout: [텍스트필드] [파일첨부 버튼]
.file-upload-inline {
display: flex;
align-items: center;
gap: 20px;
@media (max-width: $breakpoint-md) {
flex-direction: column;
align-items: stretch;
gap: $spacing-md;
}
// 텍스트필드 영역 - 파일명 표시
.file-input-display {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
padding: 0 20px;
background: $white;
border: 1px solid #DADADA;
border-radius: 12px;
min-width: 0;
&.has-file {
border-color: #3ba4ed;
}
}
.file-display-text {
flex: 1;
font-family: $font-family-primary;
font-size: $font-size-base;
color: #94A3B8;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&.has-file {
color: $text-dark;
}
}
// 파일 삭제 버튼 (인라인)
.btn-remove-file-inline {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
background: #DC3545;
border: none;
border-radius: 50%;
color: $white;
cursor: pointer;
transition: $transition-base;
flex-shrink: 0;
margin-left: $spacing-sm;
&:hover {
background: darken(#DC3545, 10%);
}
svg {
width: 12px;
height: 12px;
}
}
// 파일첨부 버튼 - Figma 디자인 (#3ba4ed)
.btn-file-attach {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
width: 172px;
height: 60px;
padding: 10px;
background: #3ba4ed;
border: none;
border-radius: 12px;
font-family: $font-family-primary;
font-size: 18px;
font-weight: $font-weight-bold;
color: $white;
cursor: pointer;
transition: $transition-base;
white-space: nowrap;
flex-shrink: 0;
@media (max-width: $breakpoint-md) {
width: 100%;
}
&:hover {
background: darken(#3ba4ed, 8%);
}
svg {
width: 22px;
height: 22px;
flex-shrink: 0;
}
}
}
// 파일 업로드 힌트 텍스트
.file-upload-hint {
margin-top: $spacing-sm;
font-size: $font-size-sm;
color: #94A3B8;
line-height: $line-height-normal;
strong {
color: $text-dark;
}
}
@@ -344,3 +344,36 @@
flex-direction: column;
}
}
// -----------------------------------------------------------------------------
// Inquiry Status Badge - Figma Design (node 998-2056, 998-2006)
// -----------------------------------------------------------------------------
.inquiry-status-badge {
display: inline-flex;
align-items: center;
justify-content: center;
height: 30px;
padding: 0 14px;
border-radius: 50px;
font-size: 15px;
font-weight: $font-weight-bold;
color: $white;
white-space: nowrap;
// 답변완료 - 파란색 배경
&--completed {
background-color: #0049b4;
}
// 답변대기 - 회색 배경
&--pending {
background-color: #8c959f;
}
}
// Inquiry Actions
.inquiry-actions {
display: flex;
justify-content: center;
margin-top: $spacing-2xl;
}
@@ -1,111 +1,81 @@
<!doctype html>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
<body>
<th:block layout:fragment="contentFragment">
<!-- Inquiry Detail Page -->
<div class="inquiry-page">
<div class="inquiry-container">
<!-- Page Header -->
<div class="inquiry-header">
<h1 class="page-title">Q&A</h1>
<p class="page-description">문의 내용을 확인하세요.</p>
</div>
<!-- Inquiry Detail Card -->
<div class="inquiry-detail-card">
<div class="detail-header">
<div class="status-wrapper">
<span class="status-badge-header"
th:classappend="${inquiry.inquiryStatus == 'RESPONDED' ? 'status-completed' : 'status-pending'}"
th:text="${inquiry.inquiryStatus == 'RESPONDED' ? '답변완료' : '문의중'}">
문의중
</span>
</div>
<h2 class="inquiry-title" th:text="${inquiry.inquirySubject}">질문 제목</h2>
<div class="inquiry-meta">
<div class="meta-item">
<i class="fas fa-calendar-alt"></i>
<span>등록일</span>
<strong th:text="${#temporals.format(inquiry.createdDate, 'yyyy-MM-dd HH:mm')}">2025.01.01 10:00</strong>
</div>
<div class="meta-item">
<i class="fas fa-user"></i>
<span>작성자</span>
<strong th:text="${inquiry.maskedInquirerName}">홍**</strong>
</div>
</div>
</div>
<!-- Attachment Section -->
<div class="detail-attachment" th:if="${!#strings.isEmpty(inquiry.attachFile)}">
<div class="attachment-icon">
<i class="fas fa-paperclip"></i>
</div>
<div class="attachment-list" th:with="fileInfo=${@fileService.findById(inquiry.attachFile)}">
<a class="attachment-item"
th:each="fileDetail : ${fileInfo.getFileDetails()}"
th:href="'javascript:fn_downloadFile(\\'' + ${fileDetail.fileId} + '\\',\\''+ ${fileDetail.fileSn} +'\\')'">
<i class="fas fa-file"></i>
<span>[[${fileDetail.originalFileName}]].[[${fileDetail.fileExtension}]]</span>
</a>
</div>
</div>
<!-- Inquiry Content -->
<div class="detail-content" style="white-space: pre-line;">
<th:block th:utext="${inquiry.inquiryDetail}"></th:block>
</div>
</div>
<!-- Admin Response Card -->
<div class="inquiry-response-card" th:if="${inquiry.inquiryStatus == 'RESPONDED'}">
<div class="response-header">
<div class="response-title">
<i class="fas fa-comment-dots"></i>
<span>관리자 답변</span>
</div>
<div class="response-meta">
<span th:text="${inquiry.responderName}">관리자</span>
<span class="meta-separator"></span>
<span th:text="${#temporals.format(inquiry.responseDate, 'yyyy-MM-dd HH:mm')}">2025.01.02 14:30</span>
</div>
</div>
<div class="response-content">
<th:block class="responseDetail" th:utext="${inquiry.responseDetail}"></th:block>
</div>
</div>
<!-- Action Buttons -->
<div class="inquiry-actions">
<div class="actions-left">
<a th:href="@{/inquiry}" class="action-btn-secondary">
<i class="fas fa-list"></i>
<span>목록</span>
</a>
</div>
<div class="actions-right" th:if="${inquiry.inquiryStatus != 'RESPONDED' && inquiry.inquirer.id == #authentication.principal.id}">
<form id="deleteForm" th:action="@{/inquiry/{id}/delete(id=${inquiry.id})}" method="post" style="display: inline;"></form>
<a th:href="@{/inquiry/edit(id=${inquiry.id})}" class="action-btn-edit">
<i class="fas fa-edit"></i>
<span>수정</span>
</a>
<a href="#" class="action-btn-delete" onclick="confirmDelete(); return false;">
<i class="fas fa-trash-alt"></i>
<span>삭제</span>
</a>
</div>
</div>
</div>
<section layout:fragment="title">
<div class="page-title-banner">
<img th:src="@{/img/img_title_bg.png}" class="title-image" alt="타이틀 배경">
<h1>Q&A</h1>
</div>
</section>
<section layout:fragment="contentFragment">
<div class="inquiry-detail-container">
<!-- Inquiry Header: Status Badge and Title -->
<div class="inquiry-detail-header">
<div class="inquiry-header-top">
<span class="inquiry-status-badge"
th:classappend="${inquiry.inquiryStatus == 'RESPONDED' ? 'inquiry-status-badge--completed' : 'inquiry-status-badge--pending'}"
th:text="${inquiry.inquiryStatus == 'RESPONDED' ? '답변완료' : '답변대기'}">
답변대기
</span>
<span class="inquiry-detail-date" th:text="${#temporals.format(inquiry.createdDate, 'yyyy.MM.dd')}">2025.11.01</span>
</div>
<h2 class="inquiry-detail-title" th:text="${inquiry.inquirySubject}">질문 제목</h2>
</div>
<!-- Attachment Section -->
<div class="inquiry-detail-attachment" th:if="${!#strings.isEmpty(inquiry.attachFile)}">
<div class="attachment-list" th:with="fileInfo=${@fileService.findById(inquiry.attachFile)}">
<div class="attachment-item" th:each="fileDetail : ${fileInfo.getFileDetails()}">
<a th:href="'javascript:fn_downloadFile(\'' + ${fileDetail.fileId} + '\',\''+ ${fileDetail.fileSn} +'\')'" class="inquiry-attachment-link">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 15.75C18 17.4833 17.3917 18.9583 16.175 20.175C14.9583 21.3917 13.4833 22 11.75 22C10.0167 22 8.54167 21.3917 7.325 20.175C6.10833 18.9583 5.5 17.4833 5.5 15.75V6.5C5.5 5.25 5.9375 4.1875 6.8125 3.3125C7.6875 2.4375 8.75 2 10 2C11.25 2 12.3125 2.4375 13.1875 3.3125C14.0625 4.1875 14.5 5.25 14.5 6.5V15.25C14.5 16.0167 14.2333 16.6667 13.7 17.2C13.1667 17.7333 12.5167 18 11.75 18C10.9833 18 10.3333 17.7333 9.8 17.2C9.26667 16.6667 9 16.0167 9 15.25V6H11V15.25C11 15.4667 11.0708 15.6458 11.2125 15.7875C11.3542 15.9292 11.5333 16 11.75 16C11.9667 16 12.1458 15.9292 12.2875 15.7875C12.4292 15.6458 12.5 15.4667 12.5 15.25V6.5C12.4833 5.8 12.2375 5.20833 11.7625 4.725C11.2875 4.24167 10.7 4 10 4C9.3 4 8.70833 4.24167 8.225 4.725C7.74167 5.20833 7.5 5.8 7.5 6.5V15.75C7.48333 16.9333 7.89167 17.9375 8.725 18.7625C9.55833 19.5875 10.5667 20 11.75 20C12.9167 20 13.9083 19.5875 14.725 18.7625C15.5417 17.9375 15.9667 16.9333 16 15.75V6H18V15.75Z" fill="currentColor"/>
</svg>
<span class="attachment-label">첨부파일</span>
<span class="attachment-filename">[[${fileDetail.originalFileName}]].[[${fileDetail.fileExtension}]]</span>
</a>
</div>
</div>
</div>
<!-- Inquiry Content Section -->
<div class="inquiry-detail-content">
<div id="inquiryDetail" class="inquiry-content-body" th:utext="${inquiry.inquiryDetail}">
문의 내용이 여기에 표시됩니다.
</div>
</div>
<!-- Admin Response Section -->
<div class="inquiry-response-section" th:if="${inquiry.inquiryStatus == 'RESPONDED'}">
<div class="inquiry-response-header">
<span class="response-label">관리자 답변</span>
<span class="response-date" th:text="${#temporals.format(inquiry.responseDate, 'yyyy.MM.dd')}">2025.11.02</span>
</div>
<div class="inquiry-response-body" id="responseDetail" th:utext="${inquiry.responseDetail}">
답변 내용이 여기에 표시됩니다.
</div>
</div>
<!-- Action Buttons -->
<div class="inquiry-detail-actions">
<div class="actions-left">
<button type="button" class="btn-inquiry-list" th:onclick="|location.href='@{/inquiry}'|">목록</button>
</div>
<div class="actions-right" th:if="${inquiry.inquiryStatus != 'RESPONDED' && inquiry.inquirer.id == #authentication.principal.id}">
<form id="deleteForm" th:action="@{/inquiry/{id}/delete(id=${inquiry.id})}" method="post" style="display: none;"></form>
<button type="button" class="btn-inquiry-edit" th:onclick="|location.href='@{/inquiry/edit(id=${inquiry.id})}'|">수정</button>
<button type="button" class="btn-inquiry-delete" onclick="confirmDelete()">삭제</button>
</div>
</div>
</div>
</section>
</th:block>
</body>
<th:block layout:fragment="contentScript">
<script th:inline="javascript">
$(document).ready(function() {
@@ -136,11 +106,15 @@
}
document.addEventListener('DOMContentLoaded', function() {
var elements = document.querySelectorAll('.responseDetail');
var inquiryElement = document.getElementById('inquiryDetail');
if (inquiryElement) {
inquiryElement.innerHTML = decodeHTMLEntities(inquiryElement.innerHTML);
}
elements.forEach(function(element) {
element.innerHTML = decodeHTMLEntities(element.innerHTML);
});
var responseElement = document.getElementById('responseDetail');
if (responseElement) {
responseElement.innerHTML = decodeHTMLEntities(responseElement.innerHTML);
}
});
</script>
</th:block>
@@ -1,115 +1,120 @@
<!doctype html>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
<body>
<section layout:fragment="title">
<div class="page-title-banner">
<img th:src="@{/img/img_title_bg.png}" class="title-image" alt="타이틀 배경">
<h1>Q&A</h1>
</div>
</section>
<th:block layout:fragment="contentFragment" th:with="isNew=${inquiry.id == null}">
<section class="inquiry-form-container">
<!-- Inquiry Form Page -->
<div class="inquiry-page">
<div class="inquiry-container">
<!-- Title Bar -->
<div class="register-title-bar">
<h2 class="register-title" th:text="${isNew ? '질문하기' : '질문 수정'}">질문하기</h2>
<span class="register-subtitle">궁금하신 사항을 작성해 주세요.</span>
</div>
<!-- Page Header -->
<div class="inquiry-header">
<h1 class="page-title" th:text="${isNew ? 'Q&A 질문하기' : 'Q&A 질문 수정'}">Q&A 질문하기</h1>
<p class="page-description">궁금하신 사항을 작성해 주세요.</p>
</div>
<!-- Form Content -->
<div class="register-form-container">
<form name="inquiryForm" id="inquiryForm" th:action="${isNew}? @{/inquiry} : @{/inquiry/edit}"
th:object="${inquiry}" enctype="multipart/form-data" method="post" class="register-form">
<input type="hidden" th:field="*{id}" th:if="${!isNew}">
<!-- Inquiry Form Card -->
<div class="inquiry-form-card">
<form name="inquiryForm" id="inquiryForm" th:action="${isNew}? @{/inquiry} : @{/inquiry/edit}"
th:object="${inquiry}" enctype="multipart/form-data" method="post">
<input type="hidden" th:field="*{id}" th:if="${!isNew}">
<!-- Subject Field -->
<div class="form-group">
<label for="inquirySubject" class="form-label required">
<span>제목</span>
</label>
<div class="form-input-wrapper">
<input type="text"
id="inquirySubject"
th:field="*{inquirySubject}"
class="form-input"
placeholder="제목을 입력하세요"
required>
</div>
<!-- Subject Field -->
<div class="form-row">
<div class="form-label-wrapper label-offset">
<span class="form-label-text">제목</span>
<span class="required-badge">필수</span>
</div>
<!-- Content Field -->
<div class="form-group">
<label for="inquiryDetail" class="form-label required">
<span>내용</span>
</label>
<div class="form-input-wrapper">
<textarea id="inquiryDetail"
th:field="*{inquiryDetail}"
class="form-textarea"
rows="10"
placeholder="내용을 입력하세요"
required></textarea>
</div>
<div class="form-field-wrapper">
<input type="text"
id="inquirySubject"
th:field="*{inquirySubject}"
class="form-input"
placeholder="제목을 입력하세요"
required
maxlength="200">
</div>
</div>
<!-- File Upload Field -->
<div class="form-group">
<label for="file" class="form-label">
<span>첨부파일</span>
</label>
<div class="form-input-wrapper">
<div class="file-upload-wrapper">
<input type="text"
class="form-input file-name-display"
th:class="${!#strings.isEmpty(inquiry.attachFile)} ? 'form-input file-name-display has-file' : 'form-input file-name-display'"
th:value="${!#strings.isEmpty(inquiry.attachFile)} ? ${@fileService.findById(inquiry.attachFile).fileDetails[0].originalFileName + '.' + @fileService.findById(inquiry.attachFile).fileDetails[0].fileExtension} : ''"
placeholder="파일을 선택하세요"
readonly>
<input type="hidden" th:field="*{attachFile}">
<label for="file" class="file-upload-btn">
<i class="fas fa-paperclip"></i>
<span>파일 선택</span>
</label>
<input type="file"
id="file"
name="files"
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.hwp,.gif,.jpg,.png"
style="display: none;">
<button type="button" class="file-remove-btn" style="display: none;">
<i class="fas fa-times"></i>
<!-- Content Field -->
<div class="form-row">
<div class="form-label-wrapper">
<span class="form-label-text">내용</span>
<span class="required-badge">필수</span>
</div>
<div class="form-field-wrapper">
<textarea id="inquiryDetail"
th:field="*{inquiryDetail}"
class="form-textarea"
rows="10"
placeholder="내용을 입력하세요"
required
maxlength="2000"></textarea>
<small class="char-counter-wrapper">
<span class="char-counter">0 / 2000</span>
</small>
</div>
</div>
<!-- File Upload Field - Figma Design (node 1029-1507) -->
<div class="form-row">
<div class="form-label-wrapper label-offset">
<span class="form-label-text">첨부파일</span>
</div>
<div class="form-field-wrapper">
<div class="file-upload-inline">
<div class="file-input-display" id="fileInputDisplay">
<span class="file-display-text" id="fileDisplayText">선택된 파일이 없습니다</span>
<button type="button" class="btn-remove-file-inline" id="btnRemoveFile" title="파일 삭제" style="display: none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="form-help-text">
<!-- <p><i class="fas fa-info-circle"></i> 첨부파일은 10MB 이내로 등록해 주세요.</p>-->
<!-- <p><i class="fas fa-info-circle"></i> 문서파일과 이미지 파일만 등록 가능합니다. (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)</p>-->
<p><i class="fas fa-check-circle"></i>
<span th:text="'첨부파일은 ' + ${@portalProperties.file.maxSize} + ' 이내로 등록해 주세요.'">첨부파일은 8MB 이내로 등록해 주세요.</span>
</p>
<p>
<i class="fas fa-check-circle"></i>
문서파일과 이미지 파일만 등록 가능합니다. (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)
</p>
</div>
<button type="button" class="btn-file-attach" onclick="document.getElementById('file').click()">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 15.75C18 17.4833 17.3917 18.9583 16.175 20.175C14.9583 21.3917 13.4833 22 11.75 22C10.0167 22 8.54167 21.3917 7.325 20.175C6.10833 18.9583 5.5 17.4833 5.5 15.75V6.5C5.5 5.25 5.9375 4.1875 6.8125 3.3125C7.6875 2.4375 8.75 2 10 2C11.25 2 12.3125 2.4375 13.1875 3.3125C14.0625 4.1875 14.5 5.25 14.5 6.5V15.25C14.5 16.0167 14.2333 16.6667 13.7 17.2C13.1667 17.7333 12.5167 18 11.75 18C10.9833 18 10.3333 17.7333 9.8 17.2C9.26667 16.6667 9 16.0167 9 15.25V6H11V15.25C11 15.4667 11.0708 15.6458 11.2125 15.7875C11.3542 15.9292 11.5333 16 11.75 16C11.9667 16 12.1458 15.9292 12.2875 15.7875C12.4292 15.6458 12.5 15.4667 12.5 15.25V6.5C12.4833 5.8 12.2375 5.20833 11.7625 4.725C11.2875 4.24167 10.7 4 10 4C9.3 4 8.70833 4.24167 8.225 4.725C7.74167 5.20833 7.5 5.8 7.5 6.5V15.75C7.48333 16.9333 7.89167 17.9375 8.725 18.7625C9.55833 19.5875 10.5667 20 11.75 20C12.9167 20 13.9083 19.5875 14.725 18.7625C15.5417 17.9375 15.9667 16.9333 16 15.75V6H18V15.75Z" fill="currentColor"/>
</svg>
파일첨부
</button>
<input type="hidden" th:field="*{attachFile}">
<input type="file"
id="file"
name="files"
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.hwp,.gif,.jpg,.jpeg,.png"
style="display: none;">
</div>
<div class="form-help-text">
<p><i class="fas fa-check-circle"></i>
<span th:text="'첨부파일은 ' + ${@portalProperties.file.maxSize} + ' 이내로 등록해 주세요.'">첨부파일은 8MB 이내로 등록해 주세요.</span>
</p>
<p>
<i class="fas fa-check-circle"></i>
문서파일과 이미지 파일만 등록 가능합니다. (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)
</p>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="inquiry-form-actions">
<a th:href="@{/inquiry}" class="action-btn-secondary">
<i class="fas fa-times"></i>
<span>취소</span>
</a>
<button type="button" class="action-btn-primary btn-submit">
<i class="fas fa-check"></i>
<span>확인</span>
</button>
</div>
</form>
</div>
</form>
</div>
</div>
<!-- Form Actions -->
<div class="form-actions">
<button type="button" class="btn btn-submit btn-secondary" th:onclick="|location.href='@{/inquiry}'|">취소</button>
<button type="button" class="btn btn-submit btn-primary btn-submit-form">
<span th:text="${isNew ? '등록' : '수정'}">등록</span>
</button>
</div>
</section>
</th:block>
</body>
@@ -117,25 +122,57 @@
<script th:if="${error}" th:inline="javascript">
$(document).ready(function () {
const errorMsg = [[${error}]];
const formattedMsg = errorMsg.replace(/\n/g,'<br>');
$('#customAlertMessage').html(formattedMsg);
$('#customAlert').css('display','flex');
customPopups.showAlert(errorMsg);
});
</script>
<script th:inline="javascript">
$(function () {
let btnSubmit = document.querySelector('.btn-submit');
let fileInput = document.getElementById('file');
let fileNameDisplay = document.querySelector('.file-name-display');
let fileRemoveBtn = document.querySelector('.file-remove-btn');
const btnSubmit = document.querySelector('.btn-submit-form');
const fileInput = document.getElementById('file');
const fileDisplayText = document.getElementById('fileDisplayText');
const fileInputDisplay = document.getElementById('fileInputDisplay');
const btnRemoveFile = document.getElementById('btnRemoveFile');
const attachFileInput = document.querySelector('input[name="attachFile"]');
const inquiryDetailTextarea = document.getElementById('inquiryDetail');
const charCounter = document.querySelector('.char-counter');
// Initialize character counter
if (inquiryDetailTextarea && charCounter) {
const currentLength = inquiryDetailTextarea.value.length;
const maxLength = inquiryDetailTextarea.getAttribute('maxlength');
charCounter.textContent = currentLength + ' / ' + maxLength;
// Character counter for textarea
inquiryDetailTextarea.addEventListener('input', function() {
const currentLength = this.value.length;
const maxLength = this.getAttribute('maxlength');
charCounter.textContent = currentLength + ' / ' + maxLength;
// Change color when near limit
if (currentLength > maxLength * 0.9) {
charCounter.style.color = '#FF6B6B';
} else {
charCounter.style.color = '#94A3B8';
}
});
}
// Show existing file if present
const existingFile = /*[[${!#strings.isEmpty(inquiry.attachFile) ? @fileService.findById(inquiry.attachFile).fileDetails[0].originalFileName + '.' + @fileService.findById(inquiry.attachFile).fileDetails[0].fileExtension : ''}]]*/ '';
if (existingFile) {
fileDisplayText.textContent = existingFile;
fileDisplayText.classList.add('has-file');
fileInputDisplay.classList.add('has-file');
btnRemoveFile.style.display = 'flex';
}
// Form submission handler
btnSubmit.addEventListener('click', function (event) {
event.preventDefault();
let form = document.getElementById('inquiryForm');
let inquirySubject = document.getElementById('inquirySubject').value.trim();
let inquiryDetail = document.getElementById('inquiryDetail').value;
const form = document.getElementById('inquiryForm');
const inquirySubject = document.getElementById('inquirySubject').value.trim();
const inquiryDetail = document.getElementById('inquiryDetail').value;
// Validation
if (!inquirySubject) {
@@ -163,30 +200,36 @@
if (!allowedExtensions.includes(fileExt)) {
customPopups.showAlert("허용된 파일 형식이 아닙니다.\n문서파일(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp)과 이미지 파일(gif, jpg, png)만 등록 가능합니다.");
fileInput.value = '';
fileNameDisplay.value = '';
fileNameDisplay.classList.remove('has-file');
fileRemoveBtn.style.display = 'none';
return;
}
fileNameDisplay.value = file.name;
fileNameDisplay.classList.add('has-file');
fileRemoveBtn.style.display = 'flex';
// Show file name in display
fileDisplayText.textContent = file.name;
fileDisplayText.classList.add('has-file');
fileInputDisplay.classList.add('has-file');
btnRemoveFile.style.display = 'flex';
// Clear existing attachFile if new file selected
if (attachFileInput) {
attachFileInput.value = '';
}
}
});
// File remove handler
fileRemoveBtn.addEventListener('click', function () {
btnRemoveFile.addEventListener('click', function (e) {
e.stopPropagation();
fileInput.value = '';
fileNameDisplay.value = '';
fileNameDisplay.classList.remove('has-file');
fileRemoveBtn.style.display = 'none';
});
fileDisplayText.textContent = '선택된 파일이 없습니다';
fileDisplayText.classList.remove('has-file');
fileInputDisplay.classList.remove('has-file');
btnRemoveFile.style.display = 'none';
// Show remove button if file already exists
if (fileNameDisplay.value) {
fileRemoveBtn.style.display = 'flex';
}
// Clear attachFile hidden input
if (attachFileInput) {
attachFileInput.value = '';
}
});
// Focus on subject field
document.getElementById('inquirySubject').focus();
@@ -1,139 +1,141 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
<body>
<section layout:fragment="title">
<div class="page-title-banner">
<img th:src="@{/img/img_title_bg.png}" class="title-image" alt="타이틀 배경">
<h1>Q&A</h1>
</div>
</section>
<th:block layout:fragment="contentFragment">
<!-- Inquiry List Page -->
<div class="inquiry-page">
<div class="inquiry-container">
<section class="user-management-container">
<!-- Page Header -->
<div class="inquiry-header">
<h1 class="page-title">Q&A</h1>
<p class="page-description">궁금하신 사항을 문의하고 답변을 확인하세요.</p>
</div>
<!-- Search and Filter Controls -->
<form name="inquiryForm" th:action="@{/inquiry}" method="get" onsubmit="return false;">
<input type="hidden" name="page" value="1">
<input name="searchCnd" type="hidden" value="0">
<!-- Search and Filter Controls -->
<form name="inquiryForm" th:action="@{/inquiry}" method="get" onsubmit="return false;">
<input type="hidden" name="page" value="1">
<input name="searchCnd" type="hidden" value="0">
<div class="table-controls">
<div class="total-count">
<strong th:text="${page.totalElements}">0</strong>
</div>
<div class="search-box">
<input type="text"
name="searchWrd"
th:value="${search.searchWrd}"
placeholder="제목 또는 내용으로 검색">
<button type="button" class="btn btn-primary" onclick="fn_search_inquiry()">
<i class="fas fa-search"></i>
<span>검색</span>
</button>
</div>
</div>
</form>
<!-- Inquiry Table -->
<div class="inquiry-list-wrapper" th:if="${!inquiries.isEmpty()}">
<div class="inquiry-table">
<!-- Table Header -->
<div class="table-header">
<div class="col-num">NO</div>
<div class="col-title">제목</div>
<div class="col-name">작성자</div>
<div class="col-status">처리상태</div>
<div class="col-date">등록일</div>
</div>
<!-- Table Rows -->
<div class="table-row"
th:each="inquiry, status : ${inquiries}"
th:onclick="'location.href=\'' + @{/inquiry/detail(id=${inquiry.id})} + '\''">
<div class="col-num"
th:text="${page.totalElements - (page.number * page.size) - status.index}">1</div>
<div class="col-title">
<a th:href="@{/inquiry/detail(id=${inquiry.id})}">
<span th:text="${inquiry.inquirySubject}">질문 제목</span>
<span class="file-icon" th:if="${!#strings.isEmpty(inquiry.attachFile)}">
<img th:src="@{/img/icon/icon_file.png}" alt="첨부파일">
</span>
</a>
</div>
<div class="col-name" th:text="${inquiry.maskedInquirerName}">홍**</div>
<div class="col-status">
<span class="status-badge"
th:classappend="${inquiry.inquiryStatus == 'RESPONDED' ? 'status-completed' : 'status-pending'}"
th:text="${inquiry.inquiryStatus == 'RESPONDED' ? '답변완료' : '문의중'}">
문의중
</span>
</div>
<div class="col-date"
th:text="${#temporals.format(inquiry.createdDate, 'yyyy.MM.dd')}">2025.01.01</div>
</div>
</div>
</div>
<!-- Empty State -->
<div class="table-empty" th:if="${inquiries.isEmpty()}">
<div class="empty-icon">
<img th:src="@{/img/img_nodata.png}" alt="데이터 없음">
</div>
<p class="empty-text">조회된 검색 결과가 없습니다.</p>
</div>
<!-- Pagination -->
<div class="pagination" th:replace="~{fragment/pagination :: pagination(jsFunction='fn_select_page')}">
</div>
<!-- New Inquiry Button -->
<div class="inquiry-actions">
<a th:href="@{/inquiry/new}" class="btn btn-primary">
<i class="fas fa-pen"></i>
<span>질문하기</span>
</a>
</div>
<div class="table-controls">
<h2 class="total-count">
<strong th:text="${page.totalElements}">0</strong>
</h2>
<div class="search-field">
<input type="text"
name="searchWrd"
th:value="${search.searchWrd}"
placeholder="제목 또는 내용으로 검색"
onkeypress="if(event.keyCode === 13) { fn_search_inquiry(); return false; }">
<button type="button" class="search-field-btn" onclick="fn_search_inquiry()">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.5 17.5L13.875 13.875M15.8333 9.16667C15.8333 12.8486 12.8486 15.8333 9.16667 15.8333C5.48477 15.8333 2.5 12.8486 2.5 9.16667C2.5 5.48477 5.48477 2.5 9.16667 2.5C12.8486 2.5 15.8333 5.48477 15.8333 9.16667Z" stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="blind">검색</span>
</button>
</div>
</div>
</form>
<!-- Inquiry Table -->
<div class="list-table" th:if="${!inquiries.isEmpty()}">
<!-- Table Header -->
<div class="list-table-header">
<div class="header-cell" style="width: 80px;">NO</div>
<div class="header-cell" style="flex: 1; min-width: 200px;">제목</div>
<div class="header-cell" style="width: 100px;">작성자</div>
<div class="header-cell" style="width: 120px;">처리상태</div>
<div class="header-cell" style="width: 120px;">등록일</div>
</div>
<!-- Table Body -->
<div class="list-table-body">
<div class="list-table-row"
th:each="inquiry, status : ${inquiries}"
th:onclick="'location.href=\'' + @{/inquiry/detail(id=${inquiry.id})} + '\''"
style="cursor: pointer;">
<div class="row-cell row-cell--number" style="width: 80px;" data-label="NO"
th:text="${page.totalElements - (page.number * page.size) - status.index}">1</div>
<div class="row-cell row-cell--title" style="flex: 1; min-width: 200px;" data-label="제목">
<a th:href="@{/inquiry/detail(id=${inquiry.id})}" class="notice-title-link">
<span class="notice-number" th:text="|[${page.totalElements - (page.number * page.size) - status.index}]|">[1]</span>
<span th:text="${inquiry.inquirySubject}">질문 제목</span>
<span class="file-icon" th:if="${!#strings.isEmpty(inquiry.attachFile)}">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 15.75C18 17.4833 17.3917 18.9583 16.175 20.175C14.9583 21.3917 13.4833 22 11.75 22C10.0167 22 8.54167 21.3917 7.325 20.175C6.10833 18.9583 5.5 17.4833 5.5 15.75V6.5C5.5 5.25 5.9375 4.1875 6.8125 3.3125C7.6875 2.4375 8.75 2 10 2C11.25 2 12.3125 2.4375 13.1875 3.3125C14.0625 4.1875 14.5 5.25 14.5 6.5V15.25C14.5 16.0167 14.2333 16.6667 13.7 17.2C13.1667 17.7333 12.5167 18 11.75 18C10.9833 18 10.3333 17.7333 9.8 17.2C9.26667 16.6667 9 16.0167 9 15.25V6H11V15.25C11 15.4667 11.0708 15.6458 11.2125 15.7875C11.3542 15.9292 11.5333 16 11.75 16C11.9667 16 12.1458 15.9292 12.2875 15.7875C12.4292 15.6458 12.5 15.4667 12.5 15.25V6.5C12.4833 5.8 12.2375 5.20833 11.7625 4.725C11.2875 4.24167 10.7 4 10 4C9.3 4 8.70833 4.24167 8.225 4.725C7.74167 5.20833 7.5 5.8 7.5 6.5V15.75C7.48333 16.9333 7.89167 17.9375 8.725 18.7625C9.55833 19.5875 10.5667 20 11.75 20C12.9167 20 13.9083 19.5875 14.725 18.7625C15.5417 17.9375 15.9667 16.9333 16 15.75V6H18V15.75Z" fill="currentColor"/>
</svg>
</span>
</a>
</div>
<div class="row-cell" style="width: 100px;" data-label="작성자" th:text="${inquiry.maskedInquirerName}">홍**</div>
<div class="row-cell" style="width: 120px;" data-label="처리상태">
<span class="inquiry-status-badge"
th:classappend="${inquiry.inquiryStatus == 'RESPONDED' ? 'inquiry-status-badge--completed' : 'inquiry-status-badge--pending'}"
th:text="${inquiry.inquiryStatus == 'RESPONDED' ? '답변완료' : '답변대기'}">
답변대기
</span>
</div>
<div class="row-cell" style="width: 120px;" data-label="등록일"
th:text="${#temporals.format(inquiry.createdDate, 'yyyy.MM.dd')}">2025.01.01</div>
</div>
</div>
</div>
<!-- Empty State -->
<div class="user-empty-state" th:if="${inquiries.isEmpty()}">
<div class="empty-icon">
<img th:src="@{/img/img_nodata.png}" alt="데이터 없음">
</div>
<h3>조회된 Q&A가 없습니다</h3>
<p>검색 조건을 변경하거나 새로운 질문을 등록해주세요.</p>
</div>
<!-- Pagination -->
<div class="pagination" th:replace="~{fragment/pagination :: pagination(jsFunction='fn_select_page')}">
</div>
<!-- New Inquiry Button -->
<div class="inquiry-actions">
<a th:href="@{/inquiry/new}" class="btn btn-primary btn-lg">
<span>질문하기</span>
</a>
</div>
</section>
</th:block>
</body>
<th:block layout:fragment="contentScript">
<script th:inline="javascript">
$(document).ready(function () {
var successMsg = [[${success}]];
console.log("Success message:", successMsg); // 디버깅용
if (successMsg) {
console.log("Showing alert"); // 디버깅용
customPopups.showAlert(successMsg);
}
});
</script>
<script>
function fn_init() {
document.inquiryForm.searchCnd.focus();
}
<script th:inline="javascript">
$(document).ready(function () {
var successMsg = [[${success}]];
if (successMsg) {
customPopups.showAlert(successMsg);
}
});
</script>
<script>
function fn_init() {
document.inquiryForm.searchWrd.focus();
}
function fn_select_page(pageNo) {
document.inquiryForm.page.value = pageNo;
document.inquiryForm.submit();
}
function fn_select_page(pageNo) {
document.inquiryForm.page.value = pageNo;
document.inquiryForm.submit();
}
function fn_search_inquiry() {
document.inquiryForm.page.value = 1;
document.inquiryForm.submit();
}
function fn_search_inquiry() {
document.inquiryForm.page.value = 1;
document.inquiryForm.submit();
}
$(function () {
fn_init();
});
</script>
$(function () {
fn_init();
});
</script>
</th:block>
</html>