Enhance sample data download functionality and improve UI interaction

- Replaced direct download links with buttons for sample data downloads, improving user experience and interaction.
- Implemented event listeners for the new download buttons to handle fiscal period parameters dynamically.
- Updated the WorkbookTemplateService to include row data in the generated Excel files.
- Added integration tests to verify that the correct fiscal period is used when downloading sample data.
This commit is contained in:
DongHeon Jang 2026-04-17 11:23:26 +09:00
parent 087a274f47
commit 114c58737f
4 changed files with 45 additions and 11 deletions

View File

@ -434,9 +434,9 @@ function renderUploads() {
<div class="row actions"> <div class="row actions">
<button data-action="upload" ${isOperator() ? "" : "disabled"}>파일 업로드</button> <button data-action="upload" ${isOperator() ? "" : "disabled"}>파일 업로드</button>
<button class="secondary" data-action="reload-uploads">내역 새로고침</button> <button class="secondary" data-action="reload-uploads">내역 새로고침</button>
<a class="ghost" href="/sample-data/trial-balance-invalid.xlsx" download>오류 샘플</a> <button class="ghost" data-action="download-sample" data-sample-code="trial-balance-invalid">오류 샘플</button>
<a class="ghost" href="/sample-data/trial-balance-valid.xlsx" download>정상 TB</a> <button class="ghost" data-action="download-sample" data-sample-code="trial-balance-valid">정상 TB</button>
<a class="ghost" href="/sample-data/forecast-valid.xlsx" download>정상 Forecast</a> <button class="ghost" data-action="download-sample" data-sample-code="forecast-valid">정상 Forecast</button>
</div> </div>
</div> </div>
<div class="panel"> <div class="panel">
@ -604,14 +604,6 @@ function shellContent(content) {
</div> </div>
</aside> </aside>
<main class="main"> <main class="main">
<div class="topbar">
<div></div>
<div class="footer-links">
<a href="/sample-data/trial-balance-invalid.xlsx" download>오류 샘플</a>
<a href="/sample-data/trial-balance-valid.xlsx" download>정상 TB</a>
<a href="/sample-data/forecast-valid.xlsx" download>정상 Forecast</a>
</div>
</div>
${content} ${content}
</main> </main>
</div> </div>
@ -836,6 +828,14 @@ function bindEvents() {
}); });
} }
document.querySelectorAll("[data-action='download-sample']").forEach((button) => {
button.addEventListener("click", () => {
const fiscalPeriod = document.getElementById("upload-period")?.value?.trim() || "2026-03";
const sampleCode = button.dataset.sampleCode;
window.location.href = `/api/uploads/samples/${sampleCode}/download?fiscalPeriod=${encodeURIComponent(fiscalPeriod)}`;
});
});
document.querySelectorAll("[data-action='delete-upload']").forEach((button) => { document.querySelectorAll("[data-action='delete-upload']").forEach((button) => {
button.addEventListener("click", async () => { button.addEventListener("click", async () => {
if (!isOperator()) { if (!isOperator()) {

View File

@ -77,6 +77,13 @@ public class WorkbookTemplateService {
for (int index = 0; index < headers.size(); index++) { for (int index = 0; index < headers.size(); index++) {
header.createCell(index).setCellValue(headers.get(index)); header.createCell(index).setCellValue(headers.get(index));
} }
int rowIndex = 1;
for (List<String> rowValues : rows) {
Row row = dataSheet.createRow(rowIndex++);
for (int cellIndex = 0; cellIndex < rowValues.size(); cellIndex++) {
row.createCell(cellIndex).setCellValue(rowValues.get(cellIndex));
}
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream); workbook.write(outputStream);

View File

@ -7,7 +7,9 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.io.ByteArrayInputStream;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -111,6 +113,23 @@ class UploadValidationIntegrationTest {
.andExpect(status().isForbidden()); .andExpect(status().isForbidden());
} }
@Test
void sampleDownloadUsesRequestedFiscalPeriod() throws Exception {
MockHttpSession session = login("viewer", "demo1234");
MvcResult result = mockMvc.perform(get("/api/uploads/samples/trial-balance-valid/download")
.param("fiscalPeriod", "2026-04")
.session(session))
.andExpect(status().isOk())
.andReturn();
try (var workbook = WorkbookFactory.create(new ByteArrayInputStream(result.getResponse().getContentAsByteArray()))) {
var dataSheet = workbook.getSheet("DATA");
var firstDataRow = dataSheet.getRow(1);
org.junit.jupiter.api.Assertions.assertEquals("2026-04", firstDataRow.getCell(0).getStringCellValue());
}
}
private MockHttpSession login(String username, String password) throws Exception { private MockHttpSession login(String username, String password) throws Exception {
MvcResult result = mockMvc.perform(post("/api/auth/login") MvcResult result = mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)

View File

@ -760,6 +760,14 @@ function bindEvents() {
}); });
} }
document.querySelectorAll("[data-action='download-sample']").forEach((button) => {
button.addEventListener("click", () => {
const fiscalPeriod = document.getElementById("upload-period")?.value?.trim() || "2026-03";
const sampleCode = button.dataset.sampleCode;
window.location.href = `/api/uploads/samples/${sampleCode}/download?fiscalPeriod=${encodeURIComponent(fiscalPeriod)}`;
});
});
document.querySelectorAll("[data-action='delete-upload']").forEach((button) => { document.querySelectorAll("[data-action='delete-upload']").forEach((button) => {
button.addEventListener("click", async () => { button.addEventListener("click", async () => {
if (!isOperator()) { if (!isOperator()) {