Compare commits

...

10 Commits

645 changed files with 41324 additions and 959 deletions

View File

@ -25,7 +25,6 @@
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v8.5 (2)">
@ -33,9 +32,9 @@
<attribute name="owner.project.facets" value="jst.web"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.8.0_251">
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="owner.project.facets" value="java"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>

85
.gitignore vendored Normal file
View File

@ -0,0 +1,85 @@
/target
.DS_Store
._.DS_Store
**/.DS_Store
**/._.DS_Store
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
.svn
# ---> Java
# Compiled class file
#*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
### VS Code ###
.vscode/
.vs/
# Added by thkim

View File

@ -1,8 +1,10 @@
src\main\webapp\WEB-INF\jsp\sgis\com\common\header.jsp
src\main\webapp\WEB-INF\jsp\sgis\surveysystem\createSurvey.jsp
src\main\webapp\WEB-INF\jsp\tiles\attribute\adm.top.jsp
src\main\webapp\WEB-INF\jsp\sgis\surveysystem\createQuestion.jsp
src\main\resources\egovframework\mapper\sgis\surveysystem\SurveyMapper.xml
src\main\resources\egovframework\mapper\sgis\surveysystem\QuestionMapper.xml
src\main\webapp\WEB-INF\jsp\sgis\surveysystem\createQuestionOption.jsp
src\main\webapp\WEB-INF\jsp\sgis\map\mapMain.jsp
src\main\webapp\WEB-INF\jsp\tiles\attribute\portal.top.jsp
src\main\webapp\WEB-INF\jsp\sgis\com\common\head.jsp
src\main\webapp\WEB-INF\jsp\sgis\com\board\boardList.jsp
src\main\resources\egovframework\mapper\sgis\board\PostMapper.xml
src\main\webapp\WEB-INF\jsp\sgis\com\board\boardView.jsp
src\main\webapp\WEB-INF\jsp\sgis\com\board\boardWrite.jsp
src\main\resources\egovframework\mapper\sgis\board\MemberMapper.xml
src\main\webapp\WEB-INF\jsp\sgis\portal\portalMain.jsp
src\main\webapp\com\img\common\main\temp_dummy_main.png

63
pom.xml
View File

@ -25,36 +25,26 @@
</properties>
<repositories>
<repository>
<id>mvn2</id>
<url>http://repo1.maven.org/maven2/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>egovframe</id>
<url>http://www.egovframe.go.kr/maven/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>egovframe2</id>
<url>http://maven.egovframe.kr:8080/maven/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>mvn2s</id>
<url>https://repo1.maven.org/maven2/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>egovframe</id>
<url>https://maven.egovframe.go.kr/maven/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>osgeo</id>
<name>OSGeo Release Repository</name>
@ -299,17 +289,22 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.5</version>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.5</version>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.12.5</version>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
@ -475,7 +470,7 @@
<build>
<defaultGoal>install</defaultGoal>
<directory>${basedir}/target</directory>
<finalName>sht_webapp</finalName>
<finalName>smart_ground</finalName>
<pluginManagement>
<plugins>
<plugin>

View File

@ -0,0 +1,26 @@
package sgis.attach.entity;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Attach {
private long attachment_id; //파일 PK
private long postId; //게시글아이디
private String fileName; //원본 파일명
private String storedFileName; //서버에 저장된 고유 파일명 (UUID등)
private String filePath; //파일 저장 경로
private long fileSize; //파일 크기(바이트)
private String fileType; //파일 MIME(타입(e.g., image/jpeg)
private long downloadCount; //다운로드횟수
private String createdAt; //생성일
}

View File

@ -0,0 +1,14 @@
package sgis.attach.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import sgis.attach.entity.Attach;
@Mapper //- Mybatis API
public interface AttachMapper {
public List<Attach> selectAttachList();
public int insertAttachList(List<Attach> attachList);
}

View File

@ -0,0 +1,21 @@
package sgis.attach.service;
import java.util.List;
import sgis.attach.entity.Attach;
public interface AttachService {
/**
* .
* @return Attach
*/
List<Attach> getLists();
/**
* .
* @param post Post
* @return Post (ID )
*/
boolean insertAttachList(List<Attach> attachList);
}

View File

@ -0,0 +1,39 @@
package sgis.attach.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sgis.attach.entity.Attach;
import sgis.attach.mapper.AttachMapper;
import sgis.attach.service.AttachService;
import java.util.List;
@Service
public class AttachServiceImpl implements AttachService {
private final AttachMapper attachMapper;
@Autowired
public AttachServiceImpl(AttachMapper attachMapper) {
this.attachMapper = attachMapper;
}
@Override
public List<Attach> getLists() {
return attachMapper.selectAttachList();
}
@Override
@Transactional
public boolean insertAttachList(List<Attach> attachList) {
int insertedRows = attachMapper.insertAttachList(attachList);
if (insertedRows > 0) {
return true;
}
return false;
}
}

View File

@ -74,7 +74,7 @@ public class BoardController{
if( nBoardCategoryId != null ) {
switch (nBoardCategoryId) {
case 1:
displayName = "커뮤니티";
displayName = "자료실";
break;
case 2:
displayName = "공지사항";
@ -127,7 +127,7 @@ public class BoardController{
@RequestMapping("/sgis/portal/board-community.do")
public String boardCommunity(@RequestParam HashMap<String,Object> params, ModelMap model, RedirectAttributes rttr, HttpSession session) {
model.put("boardCategoryId", 1);
model.put("displayName", "커뮤니티");
model.put("displayName", "자료실");
return boardList(params, model, rttr, session);
}

View File

@ -1,11 +1,13 @@
package sgis.board.controller;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.text.SimpleDateFormat;
import java.time.OffsetDateTime; // OffsetDateTime import 추가
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -15,7 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute; // ModelAttribute import는 다른 메서드를 위해 유지
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@ -24,20 +25,23 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import egovframework.com.cmm.util.EgovUserDetailsHelper;
import sgis.attach.entity.Attach;
import sgis.attach.service.AttachService;
import sgis.board.entity.Board;
import sgis.board.entity.Member;
import sgis.board.entity.Post; // Post entity import
import sgis.board.mapper.BoardMapper;
import sgis.board.mapper.MemberMapper;
import sgis.board.mapper.PostMapper;
import sgis.board.service.PostService;
import sgis.com.util.FileUploadUtil;
import sgis.com.vo.FileVO;
import sgis.com.vo.SessionVO;
import sgis.com.web.BaseController;
@ -50,6 +54,9 @@ public class BoardRestController extends BaseController {
@Autowired
PostService postService;
@Autowired
AttachService attachService;
@Autowired
MemberMapper memberMapper;
@ -192,7 +199,8 @@ public class BoardRestController extends BaseController {
@RequestParam("boardContent") String content,
// Using @RequestParam(value = "parentPostId", required = false) for optionality.
// If it comes as an empty string, it will be null.
@RequestParam(value = "parentPostId", required = false) Long parentPostId // This is good
@RequestParam(value = "parentPostId", required = false) Long parentPostId/*, // This is good
@RequestParam("files") List<MultipartFile> files*/
) {
Post newPost = new Post();
newPost.setBoardCategoryId(boardCategoryId);
@ -233,6 +241,41 @@ public class BoardRestController extends BaseController {
// PostService를 통해 게시글 생성
Post createdPost = postService.createPost(newPost);
// 게시글 등록 성공시 파일 서버 저장 및 테이블 insert 처리
/* Date nowDate = new Date();
String strNowYyyy = new SimpleDateFormat("yyyy").format(nowDate);
String strNowMm = new SimpleDateFormat("MM").format(nowDate);
String fileUploadPath = "uploads/files/" + strNowYyyy + "/" + strNowMm + "/";
HashMap<String, Object> uploadedFileList = FileUploadUtil.multiUploadFormFile(files, fileUploadPath);
List<Attach> tbFileList = new ArrayList<Attach>(); // TB_ATTACHMENT에 insert할 리스트데이타
// 서버에 파일 업로드 처리 후, 성공하거나 util이 처리한 파일 데이타를 리턴하면 아래 수행
if (uploadedFileList != null && uploadedFileList.containsKey("file_list")) {
Object fileListObj = uploadedFileList.get("file_list");
if (fileListObj instanceof List<?>) {
List<?> fileList = (List<?>) fileListObj;
for (Object obj : fileList) {
if (obj instanceof FileVO) {
FileVO fileVO = (FileVO) obj;
Attach attach = new Attach();
attach.setPostId(createdPost.getPostId());
attach.setFileName(fileVO.getFileName()); // 원본파일명
attach.setStoredFileName(fileVO.getStoredFileName()); // 실제저장파일명
attach.setFilePath(fileVO.getFilePath()); // 파일저장경로
attach.setFileSize(fileVO.getFileSize()); // 파일크기
attach.setFileType(fileVO.getFileType()); // 파일MIME타입
tbFileList.add(attach);
}
}
}
if (tbFileList.size()>0) {
attachService.insertAttachList(tbFileList);
}
}*/
// 생성된 Post 객체 반환 (클라이언트에게 성공 여부 및 생성된 게시글 정보 전달)
return createdPost;
}

View File

@ -0,0 +1,40 @@
package sgis.board.typehandler;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
@MappedTypes(UUID.class)
@MappedJdbcTypes(JdbcType.OTHER) // Or JdbcType.VARCHAR if storing as string in DB
public class UUIDTypeHandler extends BaseTypeHandler<UUID> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, UUID parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.toString());
}
@Override
public UUID getNullableResult(ResultSet rs, String columnName) throws SQLException {
String uuidString = rs.getString(columnName);
return uuidString == null ? null : UUID.fromString(uuidString);
}
@Override
public UUID getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String uuidString = rs.getString(columnIndex);
return uuidString == null ? null : UUID.fromString(uuidString);
}
@Override
public UUID getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String uuidString = cs.getString(columnIndex);
return uuidString == null ? null : UUID.fromString(uuidString);
}
}

View File

@ -1,7 +1,5 @@
package sgis.com.util;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.MessageDigest;
@ -15,7 +13,6 @@ import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.junit.Test;
/**
* @FileName : CryptoUtil.java

View File

@ -0,0 +1,97 @@
package sgis.com.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import egovframework.com.cmm.service.EgovProperties;
import sgis.com.vo.FileUtil;
import sgis.com.vo.FileVO;
import sgis.com.web.BaseController;
/**
* @FileName : FileUploadUtil.java
* @Date : 2025. 7. 15.
* @Creator :
* @Discription :
*/
public class FileUploadUtil extends BaseController {
//logger 설정
private final static Logger logger = LoggerFactory.getLogger(FileUploadUtil.class);
/**
* -
* @param request Multipart
* @param path uploads/files/yyyy/mm
* @return
*/
public static HashMap<String,Object> multiUploadFormFile(List<MultipartFile> files, String path) {
HashMap<String, Object> result = new HashMap<String, Object>(); // 최종리턴 파일목록(DB INSERT 할 데이타)
// HttpSession session = request.getSession();
MultipartHttpServletRequest mpRequest = (MultipartHttpServletRequest) files;
Iterator<String> fileNameIterator = mpRequest.getFileNames();
String uploadPath = EgovProperties.getProperty("Globals.File.StoredPath").replaceAll("/", "\\");
String uploadDirPath = "";
if(path.startsWith(System.getProperty("file.separator"))) {
uploadDirPath = path.substring(1).replaceAll("/", "\\");
} else {
uploadDirPath = path.replaceAll("/", "\\");
}
uploadDirPath = uploadPath + uploadDirPath;
String originalFileName = "";
String realFileName = "";
List<FileVO> fileList = new ArrayList<FileVO>();
while (fileNameIterator.hasNext()) {
//파일정보를 하나씩 취득한다
MultipartFile multiFile = mpRequest.getFile((String) fileNameIterator.next());
if (multiFile.getSize() > 0) {
originalFileName = multiFile.getOriginalFilename();
realFileName = FileUtil.getUniqueFileName(uploadDirPath, originalFileName);
File file = new File(uploadDirPath + realFileName);
// 디렉토리가 존재 하지 않을 경우 생성
if(!file.exists()) {
file.setExecutable(false, true);
file.setReadable(true);
file.setWritable(false, true);
file.mkdirs();
}
try{
multiFile.transferTo(file);
FileVO uploadFile = new FileVO();
// 파일 업로드 파일 원본파일명
uploadFile.setFileName(originalFileName);
// 파일 업로드 파일 저장파일명
uploadFile.setStoredFileName(realFileName);
// 파일 업로드 파일 저장경로
uploadFile.setFilePath(uploadDirPath);
// 파일 업로드 파일 크기
uploadFile.setFileSize(multiFile.getSize());
// 파일 업로드 파일 MIME타입
uploadFile.setFileType(multiFile.getContentType());
fileList.add(uploadFile);
} catch (IllegalStateException e) {
logger.error("IllegalStateException");
// 에러메세지리턴
} catch (IOException e) {
logger.error("IOException");
// 에러메세지리턴
}
}
}
result.put("file_list", fileList);
return result;
}
}

View File

@ -4,7 +4,6 @@ import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.junit.Test;
/**
* @FileName : StringUtil.java

View File

@ -0,0 +1,63 @@
package sgis.com.vo;
import java.io.File;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author
*/
public class FileUtil {
/** 로그처리 객체 */
private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);
/**
*
* @param realPath
* @param fileName
* @return String
*/
public static String getUniqueFileName(String realPath, String fileName) {
int idx = 0;
int cnt = 1;
String temp = getSavedFileName(fileName); // 저장할 파일명 생성
File tempFile = new File(realPath+temp); // 기존에 동일한 파일명의 파일 존재하는지 체크
//이미존재하는 파일인지 확인
while(tempFile.exists()) {
//기존에 존재하면 확장자랑 파일명을 분리해서 [숫자]를 붙여서 파일이름을 짓는다
if((idx=fileName.indexOf(".")) != -1){
String left = fileName.substring(0, idx);
String right = fileName.substring(idx);
temp = left + "[" + cnt + "]" + right;
}else{
temp = fileName + "[" + cnt + "]";
}
tempFile = new File(realPath+temp);
cnt++;
}
//실제 저장될 파일이름 저장
fileName = temp;
return fileName;
}
public static String getSavedFileName(String originalFileName) {
// 확장자 추출
String ext = "";
int lastDot = originalFileName.lastIndexOf('.');
if (lastDot != -1) {
ext = originalFileName.substring(lastDot); // .pdf, .jpg 등
}
// UUID 생성 + 확장자 붙이기
String savedFileName = UUID.randomUUID().toString() + ext;
return savedFileName;
}
}

View File

@ -0,0 +1,36 @@
package sgis.com.vo;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
/**
* @FileName : FileVO.java
* @Date : 2025. 7. 15.
* @Creator :
* @Discription : file vo
*/
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class FileVO implements Serializable {
//serialVersionUID
private static final long serialVersionUID = 6268293686980181848L;
private long attachment_id; //파일 PK
private String postId; //게시글아이디
private String fileName; //원본 파일명
private String storedFileName; //서버에 저장된 고유 파일명 (UUID등)
private String filePath; //파일 저장 경로
private long fileSize; //파일 크기(바이트)
private String fileType; //파일 MIME(타입(e.g., image/jpeg)
private long downloadCount; //다운로드횟수
private String createdAt; //생성일
}

View File

@ -2,13 +2,53 @@ package sgis.map.mapper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import egovframework.rte.psl.dataaccess.mapper.Mapper;
import egovframework.rte.psl.dataaccess.util.EgovMap;
@Mapper("MapMainMapper")
@Mapper("mapMainMapper")
public interface MapMainMapper {
EgovMap selectMapInfoList(HashMap<String, Object> params) throws Exception;
EgovMap selectHoleCount(Map<String, Object> params) throws Exception;
List<EgovMap> selectHoleList(Map<String, Object> params) throws Exception;
List<EgovMap> selectItems(Map<String, Object> params) throws Exception;
List<EgovMap> selectDistrict() throws Exception;
List<EgovMap> selectGrid1(Map<String, Object> params) throws Exception;
List<EgovMap> selectDistrictSgg(Map<String, Object> params) throws Exception;
List<EgovMap> selectOldItems(Map<String, Object> params) throws Exception;
List<EgovMap> get3dSido(Map<String, Object> params) throws Exception;
List<EgovMap> get3dGugun(Map<String, Object> params) throws Exception;
List<EgovMap> get3dDong(Map<String, Object> params) throws Exception;
List<EgovMap> get3dDetail(Map<String, Object> params) throws Exception;
EgovMap getGeneralData(Map<String, Object> params) throws Exception;
List<EgovMap> getLinkLayerData(Map<String, Object> params) throws Exception;
List<EgovMap> getSPTData(Map<String, Object> params) throws Exception;
List<EgovMap> getSampleData(Map<String, Object> params) throws Exception;
EgovMap selectInfoLastPage(Map<String, Object> params) throws Exception;
List<EgovMap> selectInfoItems(Map<String, Object> params) throws Exception;
EgovMap selectWebDownloadLog(Map<String, Object> params) throws Exception; // dhlee 사용자별 다운로드 요청한 개수를 얻어온다.
List<EgovMap> selectProjectList(Map<String, Object> params) throws Exception;
List<EgovMap> mapBoreholeLogHeader(Map<String, Object> params) throws Exception;
}

View File

@ -2,6 +2,7 @@ package sgis.map.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import egovframework.rte.psl.dataaccess.util.EgovMap;
import sgis.map.vo.MapInfoVO;
@ -10,4 +11,44 @@ public interface MapMainService {
EgovMap selectMapInfoList(HashMap<String, Object> params) throws Exception;
EgovMap selectHoleCount(Map<String, Object> params) throws Exception;
List<EgovMap> selectHoleList(Map<String, Object> params) throws Exception;
List<EgovMap> selectItems(Map<String, Object> params) throws Exception;
List<EgovMap> selectDistrict() throws Exception;
List<EgovMap> selectGrid1(Map<String, Object> params) throws Exception;
List<EgovMap> selectDistrictSgg(Map<String, Object> params) throws Exception;
List<EgovMap> selectOldItems(Map<String, Object> params) throws Exception;
List<EgovMap> get3dSido(Map<String, Object> params) throws Exception;
List<EgovMap> get3dGugun(Map<String, Object> params) throws Exception;
List<EgovMap> get3dDong(Map<String, Object> params) throws Exception;
List<EgovMap> get3dDetail(Map<String, Object> params) throws Exception;
EgovMap getGeneralData(Map<String, Object> params) throws Exception;
List<EgovMap> getLinkLayerData(Map<String, Object> params) throws Exception;
List<EgovMap> getSPTData(Map<String, Object> params) throws Exception;
List<EgovMap> getSampleData(Map<String, Object> params) throws Exception;
EgovMap selectInfoLastPage(Map<String, Object> params) throws Exception;
List<EgovMap> selectInfoItems(Map<String, Object> params) throws Exception;
EgovMap selectWebDownloadLog(Map<String, Object> params) throws Exception; // dhlee 사용자별 다운로드 요청한 개수를 얻어온다.
List<EgovMap> selectProjectList(Map<String, Object> params) throws Exception; // dhlee
List<EgovMap> mapBoreholeLogList(Map<String, Object> params) throws Exception;
}

View File

@ -2,6 +2,7 @@ package sgis.map.service.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -14,12 +15,117 @@ import sgis.map.service.MapMainService;
public class MapMainServiceImpl implements MapMainService {
@Autowired
private MapMainMapper MapMainMapper;
private MapMainMapper mapMainMapper;
@Override
public EgovMap selectMapInfoList(HashMap<String, Object> params) throws Exception {
// TODO Auto-generated method stub
return MapMainMapper.selectMapInfoList(params);
return mapMainMapper.selectMapInfoList(params);
}
@Override
public EgovMap selectHoleCount(Map<String, Object> params) throws Exception {
return mapMainMapper.selectHoleCount(params);
}
@Override
public List<EgovMap> selectHoleList(Map<String, Object> params) throws Exception {
return mapMainMapper.selectHoleList(params);
}
@Override
public List<EgovMap> selectItems(Map<String, Object> params) throws Exception {
return mapMainMapper.selectItems(params);
}
@Override
public List<EgovMap> selectDistrict() throws Exception {
return mapMainMapper.selectDistrict();
}
@Override
public List<EgovMap> selectGrid1(Map<String, Object> params) throws Exception {
return mapMainMapper.selectGrid1(params);
}
@Override
public List<EgovMap> selectDistrictSgg(Map<String, Object> params) throws Exception {
return mapMainMapper.selectDistrictSgg(params);
}
@Override
public List<EgovMap> selectOldItems(Map<String, Object> params) throws Exception {
return mapMainMapper.selectOldItems(params);
}
@Override
public List<EgovMap> get3dSido(Map<String, Object> params) throws Exception {
return mapMainMapper.get3dSido(params);
}
@Override
public List<EgovMap> get3dGugun(Map<String, Object> params) throws Exception {
return mapMainMapper.get3dGugun(params);
}
@Override
public List<EgovMap> get3dDong(Map<String, Object> params) throws Exception {
return mapMainMapper.get3dDong(params);
}
@Override
public List<EgovMap> get3dDetail(Map<String, Object> params) throws Exception {
return mapMainMapper.get3dDetail(params);
}
@Override
public EgovMap getGeneralData(Map<String, Object> params) throws Exception {
return mapMainMapper.getGeneralData(params);
}
@Override
public List<EgovMap> getLinkLayerData(Map<String, Object> params) throws Exception {
return mapMainMapper.getLinkLayerData(params);
}
@Override
public List<EgovMap> getSPTData(Map<String, Object> params) throws Exception {
return mapMainMapper.getSPTData(params);
}
@Override
public List<EgovMap> getSampleData(Map<String, Object> params) throws Exception {
return mapMainMapper.getSampleData(params);
}
@Override
public EgovMap selectInfoLastPage(Map<String, Object> params) throws Exception {
return mapMainMapper.selectInfoLastPage(params);
}
@Override
public List<EgovMap> selectInfoItems(Map<String, Object> params) throws Exception {
return mapMainMapper.selectInfoItems(params);
}
@Override
public EgovMap selectWebDownloadLog(Map<String, Object> params) throws Exception {
return mapMainMapper.selectWebDownloadLog(params);
}
@Override
public List<EgovMap> selectProjectList(Map<String, Object> params) throws Exception {
return mapMainMapper.selectProjectList(params);
}
@Override
public List<EgovMap> mapBoreholeLogList(Map<String, Object> params) throws Exception {
// TODO Auto-generated method stub
//String businessCode = String.valueOf(params.get("businessCode"));
//String holeCode = String.valueOf(params.get("holeCode"));
List<EgovMap> mapBoreholeLogHeader = mapMainMapper.mapBoreholeLogHeader(params);
return mapBoreholeLogHeader;
}
}

View File

@ -5,18 +5,21 @@ import java.io.File;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseBody; // Add this import
import egovframework.rte.psl.dataaccess.util.EgovMap;
import sgis.app.service.AppInstrumentService;
@ -28,6 +31,7 @@ import sgis.map.service.AppPredictService;
import sgis.map.service.MapMainService;
import sgis.map.vo.MapInfoVO;
/**
* @FileName : MapMainController.java
* @Date : 2022. 05. 10
@ -38,7 +42,7 @@ import sgis.map.vo.MapInfoVO;
public class MapMainController extends BaseController {
@Resource(name ="MapMainService")
private MapMainService MapMainService;
private MapMainService mapMainService;
//입력시스템 서비스
@Resource(name ="AppMainService")
@ -363,4 +367,153 @@ public class MapMainController extends BaseController {
};
@RequestMapping(value = "/map/sichudanNew.do")
public String sichudanNew(ModelMap model, HttpServletRequest request, HttpServletResponse response, @RequestParam Map<String, Object> params) throws Exception {
String code = String.valueOf(params.get("code"));
String data = "";
try {
String[] hCodes = code.split(",");
for (int i = 0; i < hCodes.length; i++) {
data = data + hCodes[i].trim() + ",";
}
data = data.substring(0, data.length() - 1);
} catch (NumberFormatException e) {
//logger.debug("error", e);
data = "";
} catch (IndexOutOfBoundsException e) {
//logger.debug("error", e);
data = "";
} catch (Exception e) {
//logger.debug("error", e);
data = "";
}
model.put("data", data);
return "/sgis/map/mapInformation/sichudanNew";
}
@RequestMapping(value = "/map/getSichudanData.do", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
@ResponseBody
public JSONObject getSichudanData(ModelMap model, HttpServletRequest request, HttpServletResponse response, @RequestParam Map<String, Object> params) throws Exception {
StringBuffer sb = request.getRequestURL();
String url = sb.substring(0, sb.lastIndexOf("/"));
String code = String.valueOf(params.get("code"));
JSONObject responseJson = new JSONObject();
responseJson.put("jsonView", getSectionData(code));
return responseJson;
}
@RequestMapping(value = "/map/borehole-log.do")
public String mapBoreholeLog(ModelMap model, HttpServletRequest request, HttpServletResponse response, @RequestParam Map<String, Object> params) throws Exception {
return "/sgis/map/mapInformation/boreholeLog";
}
/**
* .
* @param model
* @param request
* @param response
* @param params
* @return
* @throws Exception
*/
@RequestMapping(value = "/map/borehole-log/list.do", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
@ResponseBody
public JSONObject mapBoreholeLogList(ModelMap model, HttpServletRequest request, HttpServletResponse response, @RequestParam Map<String, Object> params) throws Exception {
StringBuffer sb = request.getRequestURL();
String url = sb.substring(0, sb.lastIndexOf("/"));
JSONObject responseJson = new JSONObject();
responseJson.put("list", mapMainService.mapBoreholeLogList(params));
return responseJson;
}
public JSONObject getSectionData(String code) throws Exception {
code = code.trim();
Map<String, Object> params = new HashMap<String, Object>();
String[] bussinessNHoleCode = code.split("\\|\\|");
params.put("businessCode", bussinessNHoleCode[0]);
params.put("holeCode", bussinessNHoleCode[1]);
EgovMap general = mapMainService.getGeneralData(params);
if( general == null ) {
// 예외처리 추가하기
}
String[] values = String.valueOf(general.get("vlu")).split("\\|");
JSONObject resultJson = new JSONObject();
resultJson.put("PCODE", values[0]);
resultJson.put("ALTITUDE", values[1]);
resultJson.put("DEPTH", values[2]);
JSONObject position = new JSONObject();
position.put("X", values[3]);
position.put("Y", values[4]);
resultJson.put("TM", position);
position = new JSONObject();
position.put("X", values[5]);
position.put("Y", values[6]);
resultJson.put("LL", position);
resultJson.put("PNAME", values[7]);
if( 8 < values.length && values[8] != null ) {
resultJson.put("WATER", values[8]);
} else {
resultJson.put("WATER", "");
}
if (values.length == 10) {
resultJson.put("PCOM", values[9]);
} else {
resultJson.put("PCOM", "");
}
JSONObject generalData = resultJson;
generalData.put("HCODE", code);
List<EgovMap> linkLayerData = mapMainService.getLinkLayerData(params);
//generalData.put("LAYER", listmap_to_json_string(linkLayerData));
generalData.put("LAYER", linkLayerData);
List<EgovMap> SPTData = mapMainService.getSPTData(params);
//generalData.put("SPT", listmap_to_json_string(SPTData));
generalData.put("SPT", SPTData);
List<EgovMap> sampleData = mapMainService.getSampleData(params);
//generalData.put("SAMPLE", listmap_to_json_string(sampleData));
generalData.put("SAMPLE", sampleData);
return generalData;
}
public String listmap_to_json_string(List<EgovMap> list) {
JSONArray json_arr = new JSONArray();
for (Map<String, Object> map : list) {
JSONObject json_obj = new JSONObject();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
json_obj.put(key, value);
}
json_arr.add(json_obj);
}
return json_arr.toString();
}
}

View File

@ -0,0 +1,48 @@
package sgis.surveysystem.config;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
/**
* java.time.LocalDateTime JDBC Timestamp (PostgreSQL TIMESTAMP WITH TIME ZONE) MyBatis TypeHandler.
*/
@MappedJdbcTypes(JdbcType.TIMESTAMP) // 이 TypeHandler가 처리할 JDBC 타입
@MappedTypes(LocalDateTime.class) // 이 TypeHandler가 처리할 Java 타입
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType) throws SQLException {
// LocalDateTime을 JDBC Timestamp로 변환하여 PreparedStatement에 설정
ps.setTimestamp(i, Timestamp.valueOf(parameter));
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
// ResultSet에서 컬럼 이름으로 Timestamp 값을 가져와 LocalDateTime으로 변환
Timestamp ts = rs.getTimestamp(columnName);
return (ts != null) ? ts.toLocalDateTime() : null;
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
// ResultSet에서 컬럼 인덱스로 Timestamp 값을 가져와 LocalDateTime으로 변환
Timestamp ts = rs.getTimestamp(columnIndex);
return (ts != null) ? ts.toLocalDateTime() : null;
}
@Override
public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
// CallableStatement에서 Timestamp 값을 가져와 LocalDateTime으로 변환
Timestamp ts = cs.getTimestamp(columnIndex);
return (ts != null) ? ts.toLocalDateTime() : null;
}
}

View File

@ -0,0 +1,49 @@
package sgis.surveysystem.config;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
/**
* java.util.UUID PostgreSQL UUID (JDBC Type.OTHER) MyBatis TypeHandler.
*/
@MappedJdbcTypes(JdbcType.OTHER) // 이 TypeHandler가 처리할 JDBC 타입 (PostgreSQL의 UUID는 OTHER로 인식됨)
@MappedTypes(UUID.class) // 이 TypeHandler가 처리할 Java 타입
public class UUIDTypeHandler extends BaseTypeHandler<UUID> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, UUID parameter, JdbcType jdbcType) throws SQLException {
// Java UUID 객체를 JDBC PreparedStatement에 설정할 때
// PostgreSQL의 UUID 타입은 String으로 변환하여 설정하거나, setObject(UUID)를 직접 사용할 수 있습니다.
// setString이 더 안전하고 호환성이 높습니다.
ps.setString(i, parameter.toString());
}
@Override
public UUID getNullableResult(ResultSet rs, String columnName) throws SQLException {
// ResultSet에서 컬럼 이름으로 값을 가져와 Java UUID 객체로 변환할 때
String uuidString = rs.getString(columnName);
return (uuidString != null) ? UUID.fromString(uuidString) : null;
}
@Override
public UUID getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
// ResultSet에서 컬럼 인덱스로 값을 가져와 Java UUID 객체로 변환할 때
String uuidString = rs.getString(columnIndex);
return (uuidString != null) ? UUID.fromString(uuidString) : null;
}
@Override
public UUID getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
// CallableStatement에서 값을 가져와 Java UUID 객체로 변환할 때
String uuidString = cs.getString(columnIndex);
return (uuidString != null) ? UUID.fromString(uuidString) : null;
}
}

View File

@ -0,0 +1,67 @@
package sgis.surveysystem.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.ui.Model; // Model 객체 사용을 위해 추가
import java.util.UUID;
/**
* .
* JSP .
* .do .
*/
@Controller // 뷰 이름을 반환하는 컨트롤러임을 명시
@RequestMapping("/admin/survey") // 이 컨트롤러의 기본 URL 경로
public class AdminSurveyPageController {
/**
* .
* `/admin/survey/createSurveyForm.do` .
*
* @return JSP (ViewResolver )
*/
@GetMapping("/createSurveyForm.do") // GET 요청 처리, .do 확장자 추가
public String showCreateSurveyForm() {
// ViewResolver 설정에 따라 다음 경로의 JSP 파일을 찾게 됩니다:
// /WEB-INF/jsp/sgis/surveysystem/createSurvey.jsp
return "sgis/surveysystem/createSurvey";
}
/**
* .
* `/admin/survey/{surveyId}/createQuestionForm.do` .
* .
*
* @param surveyId ID
* @param model Spring Model ( )
* @return JSP (ViewResolver )
*/
@GetMapping("/{surveyId}/createQuestionForm.do") // GET 요청 처리, .do 확장자 추가
public String showCreateQuestionForm(@PathVariable UUID surveyId, Model model) {
// surveyId를 Model에 추가하여 JSP에서 ${surveyId}로 접근할 수 있도록 합니다.
model.addAttribute("surveyId", surveyId);
// ViewResolver 설정에 따라 다음 경로의 JSP 파일을 찾게 됩니다:
// /WEB-INF/jsp/sgis/surveysystem/createQuestion.jsp
return "sgis/surveysystem/createQuestion";
}
/**
* .
* `/admin/survey/question/{questionId}/createOptionForm.do` .
* .
*
* @param questionId ID
* @param model Spring Model ( )
* @return JSP (ViewResolver )
*/
@GetMapping("/question/{questionId}/createOptionForm.do") // GET 요청 처리, .do 확장자 추가
public String showCreateQuestionOptionForm(@PathVariable UUID questionId, Model model) {
// questionId를 Model에 추가하여 JSP에서 ${questionId}로 접근할 수 있도록 합니다.
model.addAttribute("questionId", questionId);
// ViewResolver 설정에 따라 다음 경로의 JSP 파일을 찾게 됩니다:
// /WEB-INF/jsp/sgis/surveysystem/createQuestionOption.jsp
return "sgis/surveysystem/createQuestionOption";
}
}

View File

@ -0,0 +1,146 @@
package sgis.surveysystem.controller;
import sgis.surveysystem.dto.QuestionCreateRequest;
import sgis.surveysystem.dto.QuestionResponse;
import sgis.surveysystem.dto.QuestionUpdateRequest;
import sgis.surveysystem.dto.QuestionOptionResponse; // QuestionResponse에서 옵션을 포함하기 위해 필요
import sgis.surveysystem.service.QuestionService;
import sgis.surveysystem.service.QuestionOptionService; // 문항 옵션을 가져오기 위해 주입
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* (Question) HTTP REST .
* , , , API .
* .do , GET POST .
*/
@RestController
@RequestMapping("/api/questions")
@RequiredArgsConstructor
public class QuestionController {
private final QuestionService questionService;
private final QuestionOptionService questionOptionService; // 객관식 문항의 옵션을 로드하기 위해 필요
/**
* . (HTTP POST)
*
* @param request Question DTO
* @return (HTTP 201 Created)
*/
@PostMapping("/createQuestion.do") // POST 요청 처리, .do 확장자 추가
public ResponseEntity<QuestionResponse> createQuestion(@RequestBody QuestionCreateRequest request) {
QuestionResponse createdQuestion = QuestionResponse.from(
questionService.createQuestion(
request.getSurveyId(),
request.getQuestionText(),
request.getQuestionType(),
request.getQuestionOrder(),
request.getIsRequired()
)
);
return new ResponseEntity<>(createdQuestion, HttpStatus.CREATED);
}
/**
* . (HTTP GET)
* , .
*
* @param surveyId ID
* @return (HTTP 200 OK)
*/
@GetMapping("/by-survey/{surveyId}/getQuestions.do") // GET 요청 처리, .do 확장자 추가
public ResponseEntity<List<QuestionResponse>> getQuestionsBySurveyId(@PathVariable UUID surveyId) {
List<QuestionResponse> questions = questionService.getQuestionsBySurveyId(surveyId).stream()
.map(question -> {
QuestionResponse questionResponse = QuestionResponse.from(question);
// 문항 유형이 객관식인 경우, 해당 문항의 옵션을 조회하여 DTO에 설정합니다.
if (question.getQuestionType().startsWith("객관식")) {
List<QuestionOptionResponse> options = questionOptionService.getOptionsByQuestionId(question.getQuestionId()).stream()
.map(QuestionOptionResponse::from)
.collect(Collectors.toList());
questionResponse.setOptions(options); // QuestionResponse DTO에 옵션 리스트 설정
}
return questionResponse;
})
.collect(Collectors.toList());
return ResponseEntity.ok(questions);
}
/**
* ID . (HTTP GET)
* , .
*
* @param questionId ID
* @return (HTTP 200 OK)
* HTTP 404 Not Found
*/
@GetMapping("/{questionId}/getQuestion.do") // GET 요청 처리, .do 확장자 추가
public ResponseEntity<QuestionResponse> getQuestionById(@PathVariable UUID questionId) {
try {
QuestionResponse question = QuestionResponse.from(questionService.getQuestionById(questionId));
// 문항 유형이 객관식인 경우, 해당 문항의 옵션을 조회하여 DTO에 설정합니다.
if (question.getQuestionType().startsWith("객관식")) {
List<QuestionOptionResponse> options = questionOptionService.getOptionsByQuestionId(question.getQuestionId()).stream()
.map(QuestionOptionResponse::from)
.collect(Collectors.toList());
question.setOptions(options); // QuestionResponse DTO에 옵션 리스트 설정
}
return ResponseEntity.ok(question);
} catch (NoSuchElementException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
/**
* . (HTTP POST)
* PUT POST , URL update .
*
* @param questionId ID
* @param request Question DTO
* @return (HTTP 200 OK)
* HTTP 404 Not Found
*/
@PostMapping("/{questionId}/updateQuestion.do") // POST 요청으로 업데이트 처리, .do 확장자 추가
public ResponseEntity<QuestionResponse> updateQuestion(@PathVariable UUID questionId, @RequestBody QuestionUpdateRequest request) {
try {
QuestionResponse updatedQuestion = QuestionResponse.from(
questionService.updateQuestion(
questionId,
request.getQuestionText(),
request.getQuestionType(),
request.getQuestionOrder(),
request.getIsRequired()
)
);
return ResponseEntity.ok(updatedQuestion);
} catch (NoSuchElementException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
/**
* ID . (HTTP POST)
* DELETE POST , URL delete .
*
* @param questionId ID
* @return HTTP 204 No Content
* HTTP 404 Not Found
*/
@PostMapping("/{questionId}/deleteQuestion.do") // POST 요청으로 삭제 처리, .do 확장자 추가
public ResponseEntity<Void> deleteQuestion(@PathVariable UUID questionId) {
try {
questionService.deleteQuestion(questionId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (NoSuchElementException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}

View File

@ -0,0 +1,120 @@
package sgis.surveysystem.controller;
import sgis.surveysystem.dto.QuestionOptionCreateRequest;
import sgis.surveysystem.dto.QuestionOptionResponse;
import sgis.surveysystem.dto.QuestionOptionUpdateRequest;
import sgis.surveysystem.service.QuestionOptionService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* (QuestionOption) HTTP REST .
* , , , API .
* .do , GET POST .
*/
@RestController
@RequestMapping("/api/question-options")
@RequiredArgsConstructor
public class QuestionOptionController {
private final QuestionOptionService questionOptionService;
/**
* . (HTTP POST)
*
* @param request QuestionOption DTO
* @return (HTTP 201 Created)
*/
@PostMapping("/createQuestionOption.do") // POST 요청 처리, .do 확장자 추가
public ResponseEntity<QuestionOptionResponse> createQuestionOption(@RequestBody QuestionOptionCreateRequest request) {
QuestionOptionResponse createdOption = QuestionOptionResponse.from(
questionOptionService.createQuestionOption(
request.getQuestionId(),
request.getOptionText(),
request.getOptionOrder()
)
);
return new ResponseEntity<>(createdOption, HttpStatus.CREATED);
}
/**
* . (HTTP GET)
*
* @param questionId ID
* @return (HTTP 200 OK)
*/
@GetMapping("/by-question/{questionId}/getOptions.do") // GET 요청 처리, .do 확장자 추가
public ResponseEntity<List<QuestionOptionResponse>> getOptionsByQuestionId(@PathVariable UUID questionId) {
List<QuestionOptionResponse> options = questionOptionService.getOptionsByQuestionId(questionId).stream()
.map(QuestionOptionResponse::from)
.collect(Collectors.toList());
return ResponseEntity.ok(options);
}
/**
* ID . (HTTP GET)
*
* @param optionId ID
* @return (HTTP 200 OK)
* HTTP 404 Not Found
*/
@GetMapping("/{optionId}/getOption.do") // GET 요청 처리, .do 확장자 추가
public ResponseEntity<QuestionOptionResponse> getQuestionOptionById(@PathVariable UUID optionId) {
try {
QuestionOptionResponse option = QuestionOptionResponse.from(questionOptionService.getQuestionOptionById(optionId));
return ResponseEntity.ok(option);
} catch (NoSuchElementException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
/**
* . (HTTP POST)
* PUT POST , URL update .
*
* @param optionId ID
* @param request QuestionOption DTO
* @return (HTTP 200 OK)
* HTTP 404 Not Found
*/
@PostMapping("/{optionId}/updateOption.do") // POST 요청으로 업데이트 처리, .do 확장자 추가
public ResponseEntity<QuestionOptionResponse> updateQuestionOption(@PathVariable UUID optionId, @RequestBody QuestionOptionUpdateRequest request) {
try {
QuestionOptionResponse updatedOption = QuestionOptionResponse.from(
questionOptionService.updateQuestionOption(
optionId,
request.getOptionText(),
request.getOptionOrder()
)
);
return ResponseEntity.ok(updatedOption);
} catch (NoSuchElementException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
/**
* ID . (HTTP POST)
* DELETE POST , URL delete .
*
* @param optionId ID
* @return HTTP 204 No Content
* HTTP 404 Not Found
*/
@PostMapping("/{optionId}/deleteOption.do") // POST 요청으로 삭제 처리, .do 확장자 추가
public ResponseEntity<Void> deleteQuestionOption(@PathVariable UUID optionId) {
try {
questionOptionService.deleteQuestionOption(optionId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (NoSuchElementException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}

View File

@ -0,0 +1,163 @@
package sgis.surveysystem.controller;
import sgis.surveysystem.dto.SurveyCreateRequest;
import sgis.surveysystem.dto.SurveyResponse;
import sgis.surveysystem.dto.SurveyUpdateRequest;
import sgis.surveysystem.service.SurveyService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* (Survey) HTTP REST .
* , , , API .
* .do , GET POST .
*/
@RestController // RESTful 웹 서비스 컨트롤러임을 선언
@RequestMapping("/api/surveys") // 기본 URL 경로 (이 부분은 .do를 포함하지 않음, 각 메서드에서 .do를 추가)
@RequiredArgsConstructor // Lombok을 사용하여 final 필드를 인자로 받는 생성자 자동 생성 (SurveyService 주입)
public class SurveyController {
private final SurveyService surveyService;
/**
* . (HTTP POST)
* .
*
* @param request Survey DTO
* @return (HTTP 201 Created)
*/
@PostMapping("/createSurvey.do")
public ResponseEntity<SurveyResponse> createSurvey(@RequestBody SurveyCreateRequest request) {
LocalDateTime startDate = null;
if (request.getStartDate() != null && !request.getStartDate().isEmpty()) {
startDate = LocalDateTime.parse(request.getStartDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
}
LocalDateTime endDate = null;
if (request.getEndDate() != null && !request.getEndDate().isEmpty()) {
endDate = LocalDateTime.parse(request.getEndDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
}
SurveyResponse createdSurvey = SurveyResponse.from(
surveyService.createSurvey(
request.getSurveyTitle(),
request.getSurveyDescription(),
startDate, // 파싱된 LocalDateTime 객체 전달
endDate // 파싱된 LocalDateTime 객체 전달
)
);
return new ResponseEntity<>(createdSurvey, HttpStatus.CREATED);
}
/**
* . (HTTP GET)
*
* @return (HTTP 200 OK)
*/
@GetMapping("/getAllSurveys.do") // GET 요청 처리, .do 확장자 추가
public ResponseEntity<List<SurveyResponse>> getAllSurveys() {
// 서비스 계층에서 모든 설문을 조회하고, 각 설문 Entity를 SurveyResponse DTO로 변환합니다.
List<SurveyResponse> surveys = surveyService.getAllSurveys().stream()
.map(SurveyResponse::from)
.collect(Collectors.toList());
// 조회 성공 시 200 OK 상태 코드와 함께 설문 목록을 반환합니다.
return ResponseEntity.ok(surveys);
}
/**
* ID . (HTTP GET)
* URL (PathVariable) ID .
*
* @param surveyId ID
* @return (HTTP 200 OK)
* HTTP 404 Not Found
*/
@GetMapping("/{surveyId}/getSurvey.do") // {surveyId}는 URL 경로의 변수, .do 확장자 추가
public ResponseEntity<SurveyResponse> getSurveyById(@PathVariable UUID surveyId) {
try {
// 서비스 계층에서 ID로 설문을 조회하고, Entity를 SurveyResponse DTO로 변환합니다.
SurveyResponse survey = SurveyResponse.from(surveyService.getSurveyById(surveyId));
return ResponseEntity.ok(survey);
} catch (NoSuchElementException e) {
// 해당 ID의 설문을 찾을 수 없을 경우 404 Not Found 상태 코드를 반환합니다.
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
/**
* . (HTTP POST)
* PUT POST , URL update .
*
* @param surveyId ID
* @param request Survey DTO
* @return (HTTP 200 OK)
* HTTP 404 Not Found
*/
@PostMapping("/{surveyId}/updateSurvey.do") // POST 요청으로 업데이트 처리, .do 확장자 추가
public ResponseEntity<SurveyResponse> updateSurvey(@PathVariable UUID surveyId, @RequestBody SurveyUpdateRequest request) {
try {
// 서비스 계층의 updateSurvey 메서드를 호출하여 설문을 업데이트합니다.
SurveyResponse updatedSurvey = SurveyResponse.from(
surveyService.updateSurvey(
surveyId,
request.getSurveyTitle(),
request.getSurveyDescription(), // 수정: surveydescription() -> getSurveyDescription()
request.getIsActive(),
request.getStartDate(),
request.getEndDate()
)
);
return ResponseEntity.ok(updatedSurvey);
} catch (NoSuchElementException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
/**
* ID . (HTTP POST)
* DELETE POST , URL delete .
*
* @param surveyId ID
* @return HTTP 204 No Content
* HTTP 404 Not Found
*/
@PostMapping("/{surveyId}/deleteSurvey.do") // POST 요청으로 삭제 처리, .do 확장자 추가
public ResponseEntity<Void> deleteSurvey(@PathVariable UUID surveyId) {
try {
// 서비스 계층의 deleteSurvey 메서드를 호출하여 설문을 삭제합니다.
surveyService.deleteSurvey(surveyId);
// 삭제 성공 시 204 No Content 상태 코드를 반환합니다. (응답 본문 없음)
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (NoSuchElementException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
/**
* ID . (HTTP POST)
* PATCH POST , URL deactivate .
*
* @param surveyId ID
* @return (HTTP 200 OK)
* HTTP 404 Not Found
*/
@PostMapping("/{surveyId}/deactivateSurvey.do") // POST 요청으로 비활성화 처리, .do 확장자 추가
public ResponseEntity<SurveyResponse> deactivateSurvey(@PathVariable UUID surveyId) {
try {
// 서비스 계층의 deactivateSurvey 메서드를 호출하여 설문을 비활성화합니다.
SurveyResponse deactivatedSurvey = SurveyResponse.from(surveyService.deactivateSurvey(surveyId));
return ResponseEntity.ok(deactivatedSurvey);
} catch (NoSuchElementException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}

View File

@ -0,0 +1,29 @@
package sgis.surveysystem.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.UUID;
@Getter
@Setter
@NoArgsConstructor
@Builder
public class Answer {
private UUID answerId;
private UUID responseId; // FK: tb_survey_responses.response_id
private UUID questionId; // FK: tb_questions.question_id
private String answerText;
private UUID selectedOptionId; // FK: tb_question_options.option_id (단일 선택용)
@Builder
public Answer(UUID answerId, UUID responseId, UUID questionId, String answerText, UUID selectedOptionId) {
this.answerId = answerId;
this.responseId = responseId;
this.questionId = questionId;
this.answerText = answerText;
this.selectedOptionId = selectedOptionId;
}
}

View File

@ -0,0 +1,22 @@
package sgis.surveysystem.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.UUID;
@Getter
@Setter
@NoArgsConstructor
public class AnswerSelectedOption {
private UUID answerId; // FK: tb_answers.answer_id (복합 PK의 일부)
private UUID optionId; // FK: tb_question_options.option_id (복합 PK의 일부)
@Builder
public AnswerSelectedOption(UUID answerId, UUID optionId) {
this.answerId = answerId;
this.optionId = optionId;
}
}

View File

@ -0,0 +1,30 @@
package sgis.surveysystem.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.UUID;
@Getter
@Setter
@NoArgsConstructor
public class Question {
private UUID questionId;
private UUID surveyId; // FK: tb_surveys.survey_id
private String questionText;
private String questionType;
private Integer questionOrder;
private Boolean isRequired;
@Builder
public Question(UUID questionId, UUID surveyId, String questionText, String questionType, Integer questionOrder, Boolean isRequired) {
this.questionId = questionId;
this.surveyId = surveyId;
this.questionText = questionText;
this.questionType = questionType;
this.questionOrder = questionOrder;
this.isRequired = isRequired;
}
}

View File

@ -0,0 +1,26 @@
package sgis.surveysystem.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.UUID;
@Getter
@Setter
@NoArgsConstructor
public class QuestionOption {
private UUID optionId;
private UUID questionId; // FK: tb_questions.question_id
private String optionText;
private Integer optionOrder;
@Builder
public QuestionOption(UUID optionId, UUID questionId, String optionText, Integer optionOrder) {
this.optionId = optionId;
this.questionId = questionId;
this.optionText = optionText;
this.optionOrder = optionOrder;
}
}

View File

@ -0,0 +1,29 @@
package sgis.surveysystem.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.UUID;
@Getter
@Setter
@NoArgsConstructor
public class Respondent {
private UUID respondentId;
private String userId;
private String ipAddress; // INET 타입은 String으로 매핑 (MyBatis에서 별도 타입 핸들러 필요할 수 있음)
private LocalDateTime responseStartAt;
private LocalDateTime responseEndAt;
@Builder
public Respondent(UUID respondentId, String userId, String ipAddress, LocalDateTime responseStartAt, LocalDateTime responseEndAt) {
this.respondentId = respondentId;
this.userId = userId;
this.ipAddress = ipAddress;
this.responseStartAt = responseStartAt;
this.responseEndAt = responseEndAt;
}
}

View File

@ -0,0 +1,36 @@
package sgis.surveysystem.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; // MyBatis는 매핑 시 Setter를 사용하기도 함
import java.time.LocalDateTime;
import java.util.UUID;
@Getter
@Setter // MyBatis 매핑을 위해 필요할 수 있음
@NoArgsConstructor // 기본 생성자
@Builder
public class Survey {
private UUID surveyId;
private String surveyTitle;
private String surveyDescription;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private Boolean isActive;
private LocalDateTime startDate;
private LocalDateTime endDate;
@Builder // 빌더 패턴을 위한 어노테이션
public Survey(UUID surveyId, String surveyTitle, String surveyDescription, LocalDateTime createdAt, LocalDateTime updatedAt, Boolean isActive, LocalDateTime startDate, LocalDateTime endDate) {
this.surveyId = surveyId;
this.surveyTitle = surveyTitle;
this.surveyDescription = surveyDescription;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.isActive = isActive;
this.startDate = startDate;
this.endDate = endDate;
}
}

View File

@ -0,0 +1,27 @@
package sgis.surveysystem.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.UUID;
@Getter
@Setter
@NoArgsConstructor
public class SurveyResponse {
private UUID responseId;
private UUID surveyId; // FK: tb_surveys.survey_id
private UUID respondentId; // FK: tb_respondents.respondent_id
private LocalDateTime submittedAt;
@Builder
public SurveyResponse(UUID responseId, UUID surveyId, UUID respondentId, LocalDateTime submittedAt) {
this.responseId = responseId;
this.surveyId = surveyId;
this.respondentId = respondentId;
this.submittedAt = submittedAt;
}
}

View File

@ -0,0 +1,26 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.Answer;
import sgis.surveysystem.domain.AnswerSelectedOption; // 다중 선택 옵션 DTO 변환을 위해 필요
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
// 답변 생성 요청 DTO (단일/주관식/다중 선택 모두 커버)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AnswerCreateRequest {
private UUID responseId;
private UUID questionId;
private String answerText; // 단답형, 주관식용
private UUID selectedOptionId; // 객관식 단일 선택용
private List<UUID> selectedOptionIds; // 객관식 다중 선택용
}

View File

@ -0,0 +1,40 @@
package sgis.surveysystem.dto;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import sgis.surveysystem.domain.Answer;
//답변 응답 DTO (다중 선택 옵션 포함)
@Getter
@Setter // AnswerSelectedOptionResponse 리스트를 설정하기 위해 Setter 필요
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnswerResponse {
private UUID answerId;
private UUID responseId;
private UUID questionId;
private String answerText;
private UUID selectedOptionId;
private List<AnswerSelectedOptionResponse> selectedOptions; // 다중 선택된 옵션 목록
public static AnswerResponse from(Answer answer) {
if (answer == null) {
return null;
}
return AnswerResponse.builder()
.answerId(answer.getAnswerId())
.responseId(answer.getResponseId())
.questionId(answer.getQuestionId())
.answerText(answer.getAnswerText())
.selectedOptionId(answer.getSelectedOptionId())
// selectedOptions는 서비스/컨트롤러에서 AnswerSelectedOptionService를 통해 채웁니다.
.build();
}
}

View File

@ -0,0 +1,18 @@
package sgis.surveysystem.dto;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
//다중 선택 옵션 추가 요청 DTO
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AnswerSelectedOptionCreateRequest {
private UUID answerId; // 어떤 Answer에 속하는지
private UUID optionId; // 선택된 옵션 ID
}

View File

@ -0,0 +1,32 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.AnswerSelectedOption;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.UUID;
// 다중 선택 옵션 응답 DTO
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnswerSelectedOptionResponse {
private UUID answerId;
private UUID optionId;
public static AnswerSelectedOptionResponse from(AnswerSelectedOption answerSelectedOption) {
if (answerSelectedOption == null) {
return null;
}
return AnswerSelectedOptionResponse.builder()
.answerId(answerSelectedOption.getAnswerId())
.optionId(answerSelectedOption.getOptionId())
.build();
}
}

View File

@ -0,0 +1,19 @@
package sgis.surveysystem.dto;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
//답변 업데이트 요청 DTO (주로 단답/주관식/단일 선택용)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AnswerUpdateRequest {
private String answerText;
private UUID selectedOptionId;
// 다중 선택 옵션 업데이트는 AnswerSelectedOption 관련 별도 요청 사용 권장
}

View File

@ -0,0 +1,28 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.Question;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
// 문항 생성 요청 DTO
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class QuestionCreateRequest {
private UUID surveyId; // 어떤 설문에 속하는지
private String questionText;
private String questionType;
private Integer questionOrder;
private Boolean isRequired;
}

View File

@ -0,0 +1,21 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.QuestionOption;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.UUID;
// 선택지 생성 요청 DTO
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class QuestionOptionCreateRequest {
private UUID questionId; // 어떤 문항에 속하는지
private String optionText;
private Integer optionOrder;
}

View File

@ -0,0 +1,35 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.QuestionOption;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.UUID;
// 선택지 응답 DTO
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class QuestionOptionResponse {
private UUID optionId;
private UUID questionId; // 어떤 문항에 속하는지
private String optionText;
private Integer optionOrder;
public static QuestionOptionResponse from(QuestionOption option) {
if (option == null) {
return null;
}
return QuestionOptionResponse.builder()
.optionId(option.getOptionId())
.questionId(option.getQuestionId())
.optionText(option.getOptionText())
.optionOrder(option.getOptionOrder())
.build();
}
}

View File

@ -0,0 +1,21 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.QuestionOption;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.UUID;
// 선택지 업데이트 요청 DTO
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class QuestionOptionUpdateRequest {
private String optionText;
private Integer optionOrder;
}

View File

@ -0,0 +1,42 @@
package sgis.surveysystem.dto;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import sgis.surveysystem.domain.Question;
//문항 응답 DTO (선택지 포함 가능)
@Getter
@Setter // Setter가 필요한 경우 (예: Controller에서 옵션 리스트를 동적으로 설정)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class QuestionResponse {
private UUID questionId;
private UUID surveyId; // 어떤 설문에 속하는지
private String questionText;
private String questionType;
private Integer questionOrder;
private Boolean isRequired;
private List<QuestionOptionResponse> options; // 객관식 문항의 선택지 리스트 (서비스/컨트롤러에서 채움)
public static QuestionResponse from(Question question) {
if (question == null) {
return null;
}
return QuestionResponse.builder()
.questionId(question.getQuestionId())
.surveyId(question.getSurveyId())
.questionText(question.getQuestionText())
.questionType(question.getQuestionType())
.questionOrder(question.getQuestionOrder())
.isRequired(question.getIsRequired())
// options 필드는 기본적으로 null로 두고, Controller/Service에서 필요한 경우 채웁니다.
.build();
}
}

View File

@ -0,0 +1,18 @@
package sgis.surveysystem.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
//문항 업데이트 요청 DTO
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class QuestionUpdateRequest {
private String questionText;
private String questionType;
private Integer questionOrder;
private Boolean isRequired;
}

View File

@ -0,0 +1,17 @@
package sgis.surveysystem.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
// 응답자 생성 요청 DTO (회원용/익명용)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RespondentCreateRequest {
private String userId; // 시스템 내 사용자 ID (회원인 경우)
private String ipAddress; // 응답자의 IP 주소
}

View File

@ -0,0 +1,38 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.Respondent;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDateTime;
import java.util.UUID;
// 응답자 응답 DTO
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RespondentResponse {
private UUID respondentId;
private String userId;
private String ipAddress;
private LocalDateTime responseStartAt;
private LocalDateTime responseEndAt;
public static RespondentResponse from(Respondent respondent) {
if (respondent == null) {
return null;
}
return RespondentResponse.builder()
.respondentId(respondent.getRespondentId())
.userId(respondent.getUserId())
.ipAddress(respondent.getIpAddress())
.responseStartAt(respondent.getResponseStartAt())
.responseEndAt(respondent.getResponseEndAt())
.build();
}
}

View File

@ -0,0 +1,23 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.Respondent;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDateTime;
import java.util.UUID;
// 응답자 업데이트 요청 DTO (주로 응답 완료 시간 업데이트 등에 사용)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RespondentUpdateRequest {
private String userId;
private String ipAddress;
private LocalDateTime responseEndAt;
}

View File

@ -0,0 +1,25 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.Survey; // 도메인 객체를 DTO로 변환하기 위해 필요
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor; // 모든 필드를 인자로 받는 생성자 추가 (선택 사항이지만 유용)
import java.time.LocalDateTime;
import java.util.UUID;
// 설문 생성 요청 DTO: 클라이언트에서 설문 생성 시 보내는 데이터
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor // 요청 DTO는 모든 필드 생성자도 유용
public class SurveyCreateRequest {
private String surveyTitle;
private String surveyDescription;
//private LocalDateTime startDate;
//private LocalDateTime endDate;
private String startDate;
private String endDate;
}

View File

@ -0,0 +1,46 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.Survey; // Import Survey domain object
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDateTime;
import java.util.UUID;
// Survey 응답 DTO: 서버에서 클라이언트로 설문 정보를 보낼 때 사용하는 데이터
@Getter
@Setter // DTO가 데이터를 받을 때 Setter가 필요할 수 있음
@NoArgsConstructor
@AllArgsConstructor // Lombok이 모든 필드를 인자로 받는 생성자를 생성
@Builder // <--- 클래스 레벨에 @Builder 어노테이션
public class SurveyResponse {
private UUID surveyId;
private String surveyTitle;
private String surveyDescription;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private Boolean isActive;
private LocalDateTime startDate;
private LocalDateTime endDate;
// Entity (도메인 객체)를 DTO로 변환하는 정적 팩토리 메서드
public static SurveyResponse from(Survey survey) {
if (survey == null) {
return null;
}
return SurveyResponse.builder() // <--- 이 부분이 이제 정상적으로 호출될 것임
.surveyId(survey.getSurveyId())
.surveyTitle(survey.getSurveyTitle())
.surveyDescription(survey.getSurveyDescription())
.createdAt(survey.getCreatedAt())
.updatedAt(survey.getUpdatedAt())
.isActive(survey.getIsActive())
.startDate(survey.getStartDate())
.endDate(survey.getEndDate())
.build();
}
}

View File

@ -0,0 +1,22 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.SurveyResponse;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDateTime;
import java.util.UUID;
// 설문 응답 세션 생성 요청 DTO
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class SurveyResponseCreateRequest {
private UUID surveyId;
private UUID respondentId; // 익명 설문인 경우 null
}

View File

@ -0,0 +1,34 @@
package sgis.surveysystem.dto;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import sgis.surveysystem.domain.SurveyResponse;
//설문 응답 세션 응답 DTO
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SurveyResponseResponse {
private UUID responseId;
private UUID surveyId;
private UUID respondentId;
private LocalDateTime submittedAt;
public static SurveyResponseResponse from(SurveyResponse surveyResponse) {
if (surveyResponse == null) {
return null;
}
return SurveyResponseResponse.builder()
.responseId(surveyResponse.getResponseId())
.surveyId(surveyResponse.getSurveyId())
.respondentId(surveyResponse.getRespondentId())
.submittedAt(surveyResponse.getSubmittedAt())
.build();
}
}

View File

@ -0,0 +1,26 @@
package sgis.surveysystem.dto;
import sgis.surveysystem.domain.Survey; // 도메인 객체를 DTO로 변환하기 위해 필요
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor; // 모든 필드를 인자로 받는 생성자 추가 (선택 사항이지만 유용)
import java.time.LocalDateTime;
import java.util.UUID;
// 설문 업데이트 요청 DTO: 클라이언트에서 설문 업데이트 시 보내는 데이터
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor // 요청 DTO는 모든 필드 생성자도 유용
public class SurveyUpdateRequest {
private String surveyTitle;
private String surveyDescription;
private Boolean isActive;
private LocalDateTime startDate;
private LocalDateTime endDate;
}

View File

@ -0,0 +1,17 @@
package sgis.surveysystem.mapper;
import sgis.surveysystem.domain.Answer;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Mapper
public interface AnswerMapper {
void insertAnswer(Answer answer);
Optional<Answer> findAnswerById(UUID answerId);
List<Answer> findAnswersByResponseId(UUID responseId);
List<Answer> findAnswersByQuestionId(UUID questionId);
void updateAnswer(Answer answer);
void deleteAnswer(UUID answerId);
}

View File

@ -0,0 +1,16 @@
package sgis.surveysystem.mapper;
import sgis.surveysystem.domain.AnswerSelectedOption;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.UUID;
@Mapper
public interface AnswerSelectedOptionMapper {
void insertAnswerSelectedOption(AnswerSelectedOption answerSelectedOption);
void insertMultipleAnswerSelectedOptions(List<AnswerSelectedOption> options); // 다중 선택 옵션 일괄 삽입
List<AnswerSelectedOption> findOptionsByAnswerId(UUID answerId);
void deleteByAnswerId(UUID answerId); // 특정 답변에 연결된 모든 선택 옵션 삭제
void deleteAnswerSelectedOption(@Param("answerId") UUID answerId, @Param("optionId") UUID optionId); // 특정 선택 옵션 삭제
}

View File

@ -0,0 +1,22 @@
package sgis.surveysystem.mapper;
import sgis.surveysystem.domain.Question;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Optional; // Optional은 더 이상 findQuestionById의 반환 타입으로 사용하지 않음
import java.util.UUID;
@Mapper
public interface QuestionMapper {
void insertQuestion(Question question);
List<Question> findQuestionsBySurveyId(UUID surveyId);
Question findQuestionById(UUID questionId);
void updateQuestion(Question question);
void deleteQuestion(UUID questionId);
}

View File

@ -0,0 +1,21 @@
package sgis.surveysystem.mapper;
import sgis.surveysystem.domain.QuestionOption;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; // @Param 사용 시 필요
import java.util.List;
import java.util.Optional; // Optional은 더 이상 findQuestionOptionById의 반환 타입으로 사용하지 않음
import java.util.UUID;
@Mapper
public interface QuestionOptionMapper {
void insertQuestionOption(QuestionOption option);
List<QuestionOption> findOptionsByQuestionId(UUID questionId);
QuestionOption findQuestionOptionById(UUID optionId);
void updateQuestionOption(QuestionOption option);
void deleteQuestionOption(UUID optionId);
}

View File

@ -0,0 +1,16 @@
package sgis.surveysystem.mapper;
import sgis.surveysystem.domain.Respondent;
import org.apache.ibatis.annotations.Mapper;
import java.util.Optional;
import java.util.UUID;
import java.util.List;
@Mapper
public interface RespondentMapper {
void insertRespondent(Respondent respondent);
Optional<Respondent> findRespondentById(UUID respondentId);
Optional<Respondent> findRespondentByUserId(String userId); // user_id로 응답자 찾기
void updateRespondent(Respondent respondent);
void deleteRespondent(UUID respondentId);
}

View File

@ -0,0 +1,25 @@
package sgis.surveysystem.mapper;
import sgis.surveysystem.domain.Survey;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Optional; // Optional은 더 이상 findSurveyById의 반환 타입으로 사용하지 않음
import java.util.UUID;
@Mapper // MyBatis Mapper 임을 선언
public interface SurveyMapper {
void insertSurvey(Survey survey); // insert
List<Survey> findAllSurveys(); // select All
// findSurveyById 메서드의 반환 타입을 Optional<Survey>에서 Survey로 변경
Survey findSurveyById(UUID surveyId); // select by ID (Optional 대신 직접 Survey 반환)
void updateSurvey(Survey survey); // update
void deleteSurvey(UUID surveyId); // delete by ID
void updateSurveyStatus(@Param("surveyId") UUID surveyId, @Param("isActive") Boolean isActive); // 특정 필드 업데이트
}

View File

@ -0,0 +1,16 @@
package sgis.surveysystem.mapper;
import sgis.surveysystem.domain.SurveyResponse;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Mapper
public interface SurveyResponseMapper {
void insertSurveyResponse(SurveyResponse surveyResponse);
Optional<SurveyResponse> findSurveyResponseById(UUID responseId);
List<SurveyResponse> findResponsesBySurveyId(UUID surveyId);
List<SurveyResponse> findResponsesByRespondentId(UUID respondentId);
void deleteSurveyResponse(UUID responseId);
}

View File

@ -0,0 +1,113 @@
package sgis.surveysystem.service;
import sgis.surveysystem.domain.AnswerSelectedOption;
import sgis.surveysystem.mapper.AnswerSelectedOptionMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* (AnswerSelectedOption) .
* Answer .
*/
@Service
@RequiredArgsConstructor
@Transactional
public class AnswerSelectedOptionService {
private final AnswerSelectedOptionMapper answerSelectedOptionMapper;
private final AnswerService answerService; // 외래 키 연결 및 답변 유형 확인을 위해 주입
private final QuestionOptionService questionOptionService; // 선택지 유효성 검사를 위해 주입
/**
* .
*
* @param answerId ID ( '_' )
* @param optionId ID
* @return AnswerSelectedOption
* @throws IllegalArgumentException '_'
* @throws NoSuchElementException
*/
public AnswerSelectedOption addSelectedOption(UUID answerId, UUID optionId) {
// 답변 존재 여부 확인 및 해당 답변이 '객관식_다중' 유형인지 검증
// (AnswerService의 getAnswerById를 통해 QuestionService를 거쳐 questionType 확인 필요)
answerService.getAnswerById(answerId); // Answer 존재 확인
// TODO: 여기에서 answerId에 해당하는 Question의 questionType이 '객관식_다중'인지 확인하는 로직 추가 권장
questionOptionService.getQuestionOptionById(optionId); // 선택지 존재 여부 확인
AnswerSelectedOption selectedOption = AnswerSelectedOption.builder()
.answerId(answerId)
.optionId(optionId)
.build();
answerSelectedOptionMapper.insertAnswerSelectedOption(selectedOption); // Mapper를 통해 선택 옵션 삽입
return selectedOption;
}
/**
* .
*
* @param answerId ID
* @param optionIds ID
* @throws IllegalArgumentException '_'
* @throws NoSuchElementException
*/
public void addMultipleSelectedOptions(UUID answerId, List<UUID> optionIds) {
answerService.getAnswerById(answerId); // Answer 존재 확인
// TODO: 여기에서 answerId에 해당하는 Question의 questionType이 '객관식_다중'인지 확인하는 로직 추가 권장
if (optionIds == null || optionIds.isEmpty()) {
return; // 추가할 옵션이 없으면 아무것도 하지 않습니다.
}
List<AnswerSelectedOption> optionsToInsert = new ArrayList<>();
for (UUID optionId : optionIds) {
questionOptionService.getQuestionOptionById(optionId); // 각 선택지 유효성 검사
optionsToInsert.add(AnswerSelectedOption.builder()
.answerId(answerId)
.optionId(optionId)
.build());
}
answerSelectedOptionMapper.insertMultipleAnswerSelectedOptions(optionsToInsert); // Mapper를 통해 선택 옵션 일괄 삽입
}
/**
* .
* .
*
* @param answerId ID
* @return AnswerSelectedOption
*/
@Transactional(readOnly = true)
public List<AnswerSelectedOption> getSelectedOptionsByAnswerId(UUID answerId) {
answerService.getAnswerById(answerId); // 답변 존재 여부 확인
return answerSelectedOptionMapper.findOptionsByAnswerId(answerId); // Mapper를 통해 선택된 옵션 목록 조회
}
/**
* .
*
* @param answerId ID
* @param optionId ID
*/
public void removeSelectedOption(UUID answerId, UUID optionId) {
// 삭제 전에 해당 연결이 존재하는지 확인하는 로직을 추가할 수 있습니다.
answerSelectedOptionMapper.deleteAnswerSelectedOption(answerId, optionId); // Mapper를 통해 특정 선택 옵션 삭제
}
/**
* .
*
* @param answerId ID
*/
public void removeAllSelectedOptionsByAnswerId(UUID answerId) {
answerService.getAnswerById(answerId); // 답변 존재 여부 확인
answerSelectedOptionMapper.deleteByAnswerId(answerId); // Mapper를 통해 답변에 연결된 모든 선택 옵션 삭제
}
}

View File

@ -0,0 +1,195 @@
package sgis.surveysystem.service;
import sgis.surveysystem.domain.Answer;
import sgis.surveysystem.domain.AnswerSelectedOption;
import sgis.surveysystem.mapper.AnswerMapper;
import sgis.surveysystem.mapper.AnswerSelectedOptionMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* (Answer) .
* , , / .
*/
@Service
@RequiredArgsConstructor
@Transactional
public class AnswerService {
private final AnswerMapper answerMapper;
private final SurveyResponseService surveyResponseService; // 외래 키 연결을 위해 주입
private final QuestionService questionService; // 외래 키 연결 및 문항 유형 확인을 위해 주입
private final QuestionOptionService questionOptionService; // 선택지 유효성 검사 및 다중 선택 옵션 처리를 위해 주입
private final AnswerSelectedOptionMapper answerSelectedOptionMapper; // 다중 선택 옵션 매퍼 주입
/**
* .
* answerText selectedOptionId(s) .
*
* @param responseId ID
* @param questionId ID
* @param answerText / ( null)
* @param selectedOptionId ID ( // null)
* @param selectedOptionIds ID ( null)
* @return Answer
* @throws IllegalArgumentException
* @throws NoSuchElementException ( , , )
*/
public Answer createAnswer(UUID responseId, UUID questionId, String answerText, UUID selectedOptionId, List<UUID> selectedOptionIds) {
surveyResponseService.getSurveyResponseById(responseId); // 응답 세션 존재 여부 확인
// 문항 존재 여부 확인 및 문항 유형 가져오기
String questionType = questionService.getQuestionById(questionId).getQuestionType();
Answer answer = Answer.builder()
.answerId(UUID.randomUUID()) // 애플리케이션에서 새로운 UUID 생성
.responseId(responseId)
.questionId(questionId)
.build();
// 문항 유형에 따라 응답 데이터 유효성 검사 및 설정
switch (questionType) {
case "단답형":
case "주관식":
if (answerText == null || answerText.trim().isEmpty()) {
throw new IllegalArgumentException("Short answer or essay questions require text answer.");
}
answer.setAnswerText(answerText);
break;
case "객관식_단일":
if (selectedOptionId == null) {
throw new IllegalArgumentException("Single choice question requires one selected option.");
}
questionOptionService.getQuestionOptionById(selectedOptionId); // 선택지 존재 여부 확인
answer.setSelectedOptionId(selectedOptionId);
break;
case "객관식_다중":
if (selectedOptionIds == null || selectedOptionIds.isEmpty()) {
throw new IllegalArgumentException("Multiple choice question requires at least one selected option.");
}
// Answer 테이블에는 다중 선택 옵션이 직접 저장되지 않으므로, answerText와 selectedOptionId는 null로 유지
break;
default:
throw new IllegalArgumentException("Invalid question type: " + questionType);
}
answerMapper.insertAnswer(answer); // Answer 테이블에 기본 응답 정보 삽입
// 다중 선택 옵션이 있는 경우 AnswerSelectedOption 테이블에 추가 정보 삽입
if ("객관식_다중".equals(questionType) && selectedOptionIds != null && !selectedOptionIds.isEmpty()) {
List<AnswerSelectedOption> multiOptions = selectedOptionIds.stream()
.map(optionId -> {
questionOptionService.getQuestionOptionById(optionId); // 각 선택지 존재 여부 확인
return AnswerSelectedOption.builder()
.answerId(answer.getAnswerId())
.optionId(optionId)
.build();
})
.collect(Collectors.toList());
answerSelectedOptionMapper.insertMultipleAnswerSelectedOptions(multiOptions); // 일괄 삽입
}
return getAnswerById(answer.getAnswerId()); // 최종적으로 저장된 Answer 객체 반환
}
/**
* ID . ( )
* .
*
* @param answerId ID
* @return Answer
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public Answer getAnswerById(UUID answerId) {
Answer answer = answerMapper.findAnswerById(answerId)
.orElseThrow(() -> new NoSuchElementException("Answer not found with ID: " + answerId));
// 문항 유형이 '객관식_다중'인 경우, AnswerSelectedOption에서 추가 선택지를 로드하여 Answer 객체에 설정합니다.
// (Answer 도메인 객체에 List<AnswerSelectedOption> options 필드를 추가하고 Setter를 통해 설정 가능)
String questionType = questionService.getQuestionById(answer.getQuestionId()).getQuestionType();
if ("객관식_다중".equals(questionType)) {
List<AnswerSelectedOption> selectedOptions = answerSelectedOptionMapper.findOptionsByAnswerId(answerId);
// 예시: answer.setAnswerSelectedOptions(selectedOptions); // Answer 도메인에 필드 추가 필요
}
return answer;
}
/**
* . ( )
* .
*
* @param responseId ID
* @return Answer
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public List<Answer> getAnswersByResponseId(UUID responseId) {
surveyResponseService.getSurveyResponseById(responseId); // 응답 세션 존재 여부 확인
List<Answer> answers = answerMapper.findAnswersByResponseId(responseId);
// 각 답변에 대해 다중 선택 옵션을 로드합니다.
for (Answer answer : answers) {
String questionType = questionService.getQuestionById(answer.getQuestionId()).getQuestionType();
if ("객관식_다중".equals(questionType)) {
List<AnswerSelectedOption> selectedOptions = answerSelectedOptionMapper.findOptionsByAnswerId(answer.getAnswerId());
// 예시: answer.setAnswerSelectedOptions(selectedOptions); // Answer 도메인에 필드 추가 필요
}
}
return answers;
}
/**
* . (, , )
* AnswerSelectedOptionService .
*
* @param answerId ID
* @param answerText /
* @param selectedOptionId ID
* @return Answer
* @throws IllegalArgumentException
* @throws NoSuchElementException ID
*/
public Answer updateAnswer(UUID answerId, String answerText, UUID selectedOptionId) {
Answer existingAnswer = getAnswerById(answerId); // 기존 답변 조회
String questionType = questionService.getQuestionById(existingAnswer.getQuestionId()).getQuestionType();
// 다중 선택 문항은 이 메서드로 업데이트하지 않도록 제약
if ("객관식_다중".equals(questionType)) {
throw new IllegalArgumentException("Multi-select answers should be updated via specific methods (e.g., in AnswerSelectedOptionService).");
}
Answer answer = Answer.builder()
.answerId(answerId)
.responseId(existingAnswer.getResponseId()) // 기존 응답 세션 ID 유지
.questionId(existingAnswer.getQuestionId()) // 기존 문항 ID 유지
.answerText(answerText)
.selectedOptionId(selectedOptionId)
.build();
answerMapper.updateAnswer(answer); // Mapper를 통해 답변 정보 업데이트
return getAnswerById(answerId); // 업데이트된 최신 정보 반환
}
/**
* ID .
* , AnswerSelectedOption .
*
* @param answerId ID
* @throws NoSuchElementException ID
*/
public void deleteAnswer(UUID answerId) {
getAnswerById(answerId); // 삭제할 답변이 존재하는지 먼저 확인합니다.
// 다중 선택 옵션이 있다면 먼저 삭제하여 외래 키 제약조건 위배를 방지합니다.
answerSelectedOptionMapper.deleteByAnswerId(answerId);
answerMapper.deleteAnswer(answerId); // Mapper를 통해 답변 삭제
}
}

View File

@ -0,0 +1,114 @@
package sgis.surveysystem.service;
import sgis.surveysystem.domain.QuestionOption;
import sgis.surveysystem.mapper.QuestionOptionMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional; // Optional 클래스 임포트
import java.util.UUID;
/**
* (QuestionOption) .
* MyBatis Mapper .
*/
@Service
@RequiredArgsConstructor
@Transactional
public class QuestionOptionService {
private final QuestionOptionMapper questionOptionMapper;
private final QuestionService questionService; // 선택지가 속할 문항의 유효성 검사를 위해 QuestionService 주입
/**
* .
* ID UUID .
*
* @param questionId ID
* @param optionText
* @param optionOrder
* @return QuestionOption
* @throws NoSuchElementException ID
*/
public QuestionOption createQuestionOption(UUID questionId, String optionText, Integer optionOrder) {
// 선택지가 속할 문항이 존재하는지 먼저 확인합니다.
questionService.getQuestionById(questionId);
// TODO: 해당 Question의 questionType이 '객관식_단일' 또는 '객관식_다중'인지 검증하는 로직 추가를 권장합니다.
QuestionOption option = QuestionOption.builder()
.optionId(UUID.randomUUID()) // 애플리케이션에서 새로운 UUID 생성
.questionId(questionId)
.optionText(optionText)
.optionOrder(optionOrder)
.build();
questionOptionMapper.insertQuestionOption(option); // Mapper를 통해 데이터베이스에 선택지 정보 삽입
return option;
}
/**
* .
* .
*
* @param questionId ID
* @return QuestionOption
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public List<QuestionOption> getOptionsByQuestionId(UUID questionId) {
questionService.getQuestionById(questionId); // 문항 존재 여부 확인
return questionOptionMapper.findOptionsByQuestionId(questionId); // Mapper를 통해 문항 ID로 선택지 목록 조회
}
/**
* ID .
* .
*
* @param optionId ID
* @return QuestionOption
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public QuestionOption getQuestionOptionById(UUID optionId) {
// Mapper에서 직접 QuestionOption 객체를 반환하도록 변경했으므로, Optional.ofNullable()을 사용합니다.
return Optional.ofNullable(questionOptionMapper.findQuestionOptionById(optionId))
.orElseThrow(() -> new NoSuchElementException("Question Option not found with ID: " + optionId)); // 없을 경우 예외 발생
}
/**
* .
* .
*
* @param optionId ID
* @param optionText
* @param optionOrder
* @return QuestionOption ( )
* @throws NoSuchElementException ID
*/
public QuestionOption updateQuestionOption(UUID optionId, String optionText, Integer optionOrder) {
getQuestionOptionById(optionId); // 업데이트할 선택지가 존재하는지 먼저 확인합니다.
QuestionOption option = QuestionOption.builder()
.optionId(optionId) // 업데이트 대상 선택지 ID
.optionText(optionText)
.optionOrder(optionOrder)
.build();
questionOptionMapper.updateQuestionOption(option); // Mapper를 통해 선택지 정보 업데이트
return getQuestionOptionById(optionId); // 업데이트된 최신 정보를 다시 조회하여 반환합니다.
}
/**
* ID .
* .
*
* @param optionId ID
* @throws NoSuchElementException ID
*/
public void deleteQuestionOption(UUID optionId) {
getQuestionOptionById(optionId); // 삭제할 선택지가 존재하는지 먼저 확인합니다.
questionOptionMapper.deleteQuestionOption(optionId); // Mapper를 통해 선택지 삭제
}
}

View File

@ -0,0 +1,121 @@
package sgis.surveysystem.service;
import sgis.surveysystem.domain.Question;
import sgis.surveysystem.mapper.QuestionMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional; // Optional 클래스 임포트
import java.util.UUID;
/**
* (Question) .
* MyBatis Mapper .
*/
@Service
@RequiredArgsConstructor
@Transactional
public class QuestionService {
private final QuestionMapper questionMapper;
private final SurveyService surveyService; // 문항이 속할 설문의 유효성 검사를 위해 SurveyService 주입
/**
* .
* ID UUID .
*
* @param surveyId ID
* @param questionText
* @param questionType ('', '_', '_', '')
* @param questionOrder
* @param isRequired
* @return Question
* @throws NoSuchElementException ID
*/
public Question createQuestion(UUID surveyId, String questionText, String questionType, Integer questionOrder, Boolean isRequired) {
// 문항이 속할 설문이 존재하는지 먼저 확인하여 외래 키 제약조건 위배를 방지합니다.
surveyService.getSurveyById(surveyId);
Question question = Question.builder()
.questionId(UUID.randomUUID()) // 애플리케이션에서 새로운 UUID 생성
.surveyId(surveyId)
.questionText(questionText)
.questionType(questionType)
.questionOrder(questionOrder)
.isRequired(isRequired)
.build();
questionMapper.insertQuestion(question); // Mapper를 통해 데이터베이스에 문항 정보 삽입
return question;
}
/**
* .
* .
*
* @param surveyId ID
* @return Question
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public List<Question> getQuestionsBySurveyId(UUID surveyId) {
surveyService.getSurveyById(surveyId); // 설문 존재 여부 확인
return questionMapper.findQuestionsBySurveyId(surveyId); // Mapper를 통해 설문 ID로 문항 목록 조회
}
/**
* ID .
* .
*
* @param questionId ID
* @return Question
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public Question getQuestionById(UUID questionId) {
// Mapper에서 직접 Question 객체를 반환하도록 변경했으므로, Optional.ofNullable()을 사용합니다.
return Optional.ofNullable(questionMapper.findQuestionById(questionId))
.orElseThrow(() -> new NoSuchElementException("Question not found with ID: " + questionId)); // 없을 경우 예외 발생
}
/**
* .
* .
*
* @param questionId ID
* @param questionText
* @param questionType
* @param questionOrder
* @param isRequired
* @return Question ( )
* @throws NoSuchElementException ID
*/
public Question updateQuestion(UUID questionId, String questionText, String questionType, Integer questionOrder, Boolean isRequired) {
getQuestionById(questionId); // 업데이트할 문항이 존재하는지 먼저 확인합니다.
Question question = Question.builder()
.questionId(questionId) // 업데이트 대상 문항 ID
.questionText(questionText)
.questionType(questionType)
.questionOrder(questionOrder)
.isRequired(isRequired)
.build();
questionMapper.updateQuestion(question); // Mapper를 통해 문항 정보 업데이트
return getQuestionById(questionId); // 업데이트된 최신 정보를 다시 조회하여 반환합니다.
}
/**
* ID .
* .
*
* @param questionId ID
* @throws NoSuchElementException ID
*/
public void deleteQuestion(UUID questionId) {
getQuestionById(questionId); // 삭제할 문항이 존재하는지 먼저 확인합니다.
questionMapper.deleteQuestion(questionId); // Mapper를 통해 문항 삭제
}
}

View File

@ -0,0 +1,101 @@
package sgis.surveysystem.service;
import sgis.surveysystem.domain.Respondent;
import sgis.surveysystem.mapper.RespondentMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.UUID;
/**
* (Respondent) .
* MyBatis Mapper .
*/
@Service
@RequiredArgsConstructor
@Transactional
public class RespondentService {
private final RespondentMapper respondentMapper;
/**
* , user_id user_id .
*
* @param userId ID ( , null)
* @param ipAddress IP ( )
* @return Respondent
*/
public Respondent createOrGetRespondent(String userId, String ipAddress) {
// user_id가 제공된 경우, 기존 응답자가 있는지 먼저 확인합니다.
if (userId != null && !userId.isEmpty()) {
Optional<Respondent> existingRespondent = respondentMapper.findRespondentByUserId(userId);
if (existingRespondent.isPresent()) {
return existingRespondent.get(); // 이미 존재하는 응답자 반환
}
}
// 기존 응답자가 없거나 user_id가 제공되지 않은 경우, 새로운 응답자를 생성합니다.
Respondent respondent = Respondent.builder()
.respondentId(UUID.randomUUID()) // 애플리케이션에서 새로운 UUID 생성
.userId(userId)
.ipAddress(ipAddress)
.responseStartAt(LocalDateTime.now()) // 응답 시작 시점 기록
.build();
respondentMapper.insertRespondent(respondent); // Mapper를 통해 응답자 정보 삽입
return respondent;
}
/**
* ID .
* .
*
* @param respondentId ID
* @return Respondent
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public Respondent getRespondentById(UUID respondentId) {
return respondentMapper.findRespondentById(respondentId) // Mapper를 통해 ID로 응답자 조회
.orElseThrow(() -> new NoSuchElementException("Respondent not found with ID: " + respondentId)); // 없을 경우 예외 발생
}
/**
* .
* .
*
* @param respondentId ID
* @param userId ID
* @param ipAddress IP
* @param responseEndAt (null )
* @return Respondent ( )
* @throws NoSuchElementException ID
*/
public Respondent updateRespondent(UUID respondentId, String userId, String ipAddress, LocalDateTime responseEndAt) {
getRespondentById(respondentId); // 업데이트할 응답자가 존재하는지 먼저 확인합니다.
Respondent respondent = Respondent.builder()
.respondentId(respondentId) // 업데이트 대상 응답자 ID
.userId(userId)
.ipAddress(ipAddress)
.responseEndAt(responseEndAt != null ? responseEndAt : LocalDateTime.now()) // 완료 시점 갱신 또는 현재 시각
.build();
respondentMapper.updateRespondent(respondent); // Mapper를 통해 응답자 정보 업데이트
return getRespondentById(respondentId); // 업데이트된 최신 정보를 다시 조회하여 반환합니다.
}
/**
* ID .
* .
*
* @param respondentId ID
* @throws NoSuchElementException ID
*/
public void deleteRespondent(UUID respondentId) {
getRespondentById(respondentId); // 삭제할 응답자가 존재하는지 먼저 확인합니다.
respondentMapper.deleteRespondent(respondentId); // Mapper를 통해 응답자 삭제
}
}

View File

@ -0,0 +1,107 @@
package sgis.surveysystem.service;
import sgis.surveysystem.domain.SurveyResponse;
import sgis.surveysystem.mapper.SurveyResponseMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
/**
* (SurveyResponse) .
* .
*/
@Service
@RequiredArgsConstructor
@Transactional
public class SurveyResponseService {
private final SurveyResponseMapper surveyResponseMapper;
private final SurveyService surveyService; // 외래 키 연결을 위해 SurveyService 주입
private final RespondentService respondentService; // 외래 키 연결을 위해 RespondentService 주입
/**
* .
* ID UUID .
*
* @param surveyId ID
* @param respondentId ID ( , null)
* @return SurveyResponse
* @throws NoSuchElementException ID ID
*/
public SurveyResponse createSurveyResponse(UUID surveyId, UUID respondentId) {
surveyService.getSurveyById(surveyId); // 설문 존재 여부 확인
// 응답자 ID가 제공된 경우, 해당 응답자가 존재하는지 확인합니다.
if (respondentId != null) {
respondentService.getRespondentById(respondentId);
}
SurveyResponse surveyResponse = SurveyResponse.builder()
.responseId(UUID.randomUUID()) // 애플리케이션에서 새로운 UUID 생성
.surveyId(surveyId)
.respondentId(respondentId)
.submittedAt(LocalDateTime.now()) // 제출 시점 기록
.build();
surveyResponseMapper.insertSurveyResponse(surveyResponse); // Mapper를 통해 응답 세션 정보 삽입
return surveyResponse;
}
/**
* ID .
* .
*
* @param responseId ID
* @return SurveyResponse
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public SurveyResponse getSurveyResponseById(UUID responseId) {
return surveyResponseMapper.findSurveyResponseById(responseId) // Mapper를 통해 ID로 응답 세션 조회
.orElseThrow(() -> new NoSuchElementException("Survey Response not found with ID: " + responseId)); // 없을 경우 예외 발생
}
/**
* .
* .
*
* @param surveyId ID
* @return SurveyResponse
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public List<SurveyResponse> getSurveyResponsesBySurveyId(UUID surveyId) {
surveyService.getSurveyById(surveyId); // 설문 존재 여부 확인
return surveyResponseMapper.findResponsesBySurveyId(surveyId); // Mapper를 통해 설문 ID로 응답 세션 목록 조회
}
/**
* .
* .
*
* @param respondentId ID
* @return SurveyResponse
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public List<SurveyResponse> getSurveyResponsesByRespondentId(UUID respondentId) {
respondentService.getRespondentById(respondentId); // 응답자 존재 여부 확인
return surveyResponseMapper.findResponsesByRespondentId(respondentId); // Mapper를 통해 응답자 ID로 응답 세션 목록 조회
}
/**
* ID .
* .
*
* @param responseId ID
* @throws NoSuchElementException ID
*/
public void deleteSurveyResponse(UUID responseId) {
getSurveyResponseById(responseId); // 삭제할 응답 세션이 존재하는지 먼저 확인합니다.
surveyResponseMapper.deleteSurveyResponse(responseId); // Mapper를 통해 응답 세션 삭제
}
}

View File

@ -0,0 +1,139 @@
package sgis.surveysystem.service;
import sgis.surveysystem.domain.Survey;
import sgis.surveysystem.mapper.SurveyMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional; // Optional 클래스 임포트
import java.util.UUID;
/**
* (Survey) .
* MyBatis Mapper .
*/
@Service
@RequiredArgsConstructor // Lombok을 사용하여 final 필드를 인자로 받는 생성자를 자동 생성합니다. (의존성 주입)
@Transactional // 클래스 레벨에 트랜잭션 설정을 적용합니다.
public class SurveyService {
private final SurveyMapper surveyMapper;
/**
* .
* ID UUID .
*
* @param surveyTitle
* @param surveyDescription
* @param startDate
* @param endDate
* @return Survey
*/
public Survey createSurvey(String surveyTitle, String surveyDescription, LocalDateTime startDate, LocalDateTime endDate) {
// 애플리케이션에서 새로운 UUID를 생성하여 설문 ID로 사용합니다.
// 데이터베이스의 DEFAULT gen_random_uuid() 설정과 무관하게 동작합니다.
Survey survey = Survey.builder()
.surveyId(UUID.randomUUID())
.surveyTitle(surveyTitle)
.surveyDescription(surveyDescription)
.createdAt(LocalDateTime.now()) // 현재 시각으로 생성일 설정
.updatedAt(LocalDateTime.now()) // 현재 시각으로 수정일 설정
.isActive(true) // 기본적으로 활성화 상태로 설정
.startDate(startDate)
.endDate(endDate)
.build();
surveyMapper.insertSurvey(survey); // Mapper를 통해 데이터베이스에 설문 정보 삽입
return survey;
}
/**
* .
* .
*
* @return Survey
*/
@Transactional(readOnly = true)
public List<Survey> getAllSurveys() {
return surveyMapper.findAllSurveys(); // Mapper를 통해 모든 설문 조회
}
/**
* ID .
* .
*
* @param surveyId ID
* @return Survey
* @throws NoSuchElementException ID
*/
@Transactional(readOnly = true)
public Survey getSurveyById(UUID surveyId) {
// Mapper에서 직접 Survey 객체를 반환하도록 변경했으므로, Optional.ofNullable()을 사용합니다.
return Optional.ofNullable(surveyMapper.findSurveyById(surveyId))
.orElseThrow(() -> new NoSuchElementException("Survey not found with ID: " + surveyId)); // 없을 경우 예외 발생
}
/**
* .
* .
*
* @param surveyId ID
* @param surveyTitle
* @param surveyDescription
* @param isActive
* @param startDate
* @param endDate
* @return Survey ( )
* @throws NoSuchElementException ID
*/
public Survey updateSurvey(UUID surveyId, String surveyTitle, String surveyDescription, Boolean isActive, LocalDateTime startDate, LocalDateTime endDate) {
// 업데이트할 설문이 존재하는지 먼저 확인합니다.
getSurveyById(surveyId);
// 업데이트할 필드들을 포함하는 Survey 객체를 생성합니다.
// MyBatis는 이 객체의 필드 값을 사용하여 SQL 쿼리의 파라미터를 채웁니다.
Survey survey = Survey.builder()
.surveyId(surveyId) // 업데이트 대상 설문 ID
.surveyTitle(surveyTitle)
.surveyDescription(surveyDescription)
.isActive(isActive)
.startDate(startDate)
.endDate(endDate)
.updatedAt(LocalDateTime.now()) // 업데이트 시각을 현재로 갱신
.build();
surveyMapper.updateSurvey(survey); // Mapper를 통해 설문 정보 업데이트
// 업데이트된 최신 정보를 데이터베이스에서 다시 조회하여 반환합니다.
return getSurveyById(surveyId);
}
/**
* ID .
* .
*
* @param surveyId ID
* @throws NoSuchElementException ID
*/
public void deleteSurvey(UUID surveyId) {
// 삭제할 설문이 존재하는지 먼저 확인합니다.
getSurveyById(surveyId);
surveyMapper.deleteSurvey(surveyId); // Mapper를 통해 설문 삭제
}
/**
* ID .
* .
*
* @param surveyId ID
* @return Survey ( )
* @throws NoSuchElementException ID
*/
public Survey deactivateSurvey(UUID surveyId) {
getSurveyById(surveyId); // 존재 여부 확인
surveyMapper.updateSurveyStatus(surveyId, false); // Mapper를 통해 설문 상태 업데이트
return getSurveyById(surveyId); // 업데이트된 상태로 반환
}
}

View File

@ -141,6 +141,7 @@
</if>
<![CDATA[
ORDER BY A.PROJECT_CODE desc
LIMIT 1
]]>
</select>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="sgis.attach.mapper.AttachMapper">
<select id="selectAttachList" resultType="sgis.attach.entity.Attach">
select attachment_id,
post_id,
file_name,
stored_file_name,
file_path,
file_size,
file_type,
download_count,
TO_CHAR(created_at,'YYYY-MM-DD') AS created_at
from public.tb_attachment
</select>
<insert id="insertAttach" parameterType="sgis.attach.entity.Attach">
<selectKey keyProperty="idx" resultType="int" order="BEFORE">
SELECT COALESCE(MAX(IDX) + 1, 1) FROM TB_ATTACHMENT
</selectKey>
insert into TB_ATTACHMENT(
attachment_id
,post_id
,file_name
,stored_file_name
,file_path
,file_size
,file_type
,download_count
,created_at
) values(
#{attachment_id}
,#{post_id}
,#{file_name}
,#{stored_file_name}
,#{file_path}
,#{file_size}
,#{file_type}
,0
,NOW()
)
</insert>
<insert id="insertAttachList" parameterType="list">
<selectKey keyProperty="idx" resultType="int" order="BEFORE">
SELECT COALESCE(MAX(IDX) + 1, 1) FROM TB_ATTACHMENT
</selectKey>
insert into TB_ATTACHMENT(
attachment_id
,post_id
,file_name
,stored_file_name
,file_path
,file_size
,file_type
,download_count
,created_at
) values
<foreach item="item" collection="list" separator=",">
(
#{attachment_id}
,#{post_id}
,#{file_name}
,#{stored_file_name}
,#{file_path}
,#{file_size}
,#{file_type}
,0
,NOW()
)
</foreach>
</insert>
</mapper>

View File

@ -54,4 +54,514 @@
ORDER BY HOL_COD
]]>
</select>
<select id="selectHoleCount" parameterType="map" resultType="egovMap">
<![CDATA[
SELECT
COUNT(HOLE_CODE) AS CNT
FROM TBL_HEADER
]]>
<choose>
<when test="areaHcodeArray != null and areaHcodeArray != '' and areaHcodeArray != 'undefiend'">
<![CDATA[
where NVL(USE_YN,' ') <> 'N' AND HOLE_CODE IN
]]>
<foreach collection="areaHcodeArray" item="hole" index="index" open="(" close=")" separator=",">
#{hole}
</foreach>
</when>
<otherwise>
<![CDATA[
WHERE NVL(USE_YN,' ') <> 'N' AND HOLE_LOCATION_TM_X >= #{minX} AND HOLE_LOCATION_TM_X <= #{maxX}
AND HOLE_LOCATION_TM_Y >= #{minY} AND HOLE_LOCATION_TM_Y <= #{maxY}
]]>
</otherwise>
</choose>
</select>
<select id="selectHoleList" parameterType="map" resultType="egovMap">
<![CDATA[
SELECT
HOLE_CODE
FROM TBL_HEADER
]]>
<choose>
<when test="areaHcodeArray != null and areaHcodeArray != ''and areaHcodeArray != 'undefiend'">
<![CDATA[
where NVL(USE_YN,' ') <> 'N' AND HOLE_CODE IN
]]>
<foreach collection="areaHcodeArray" item="hole" index="index" open="(" close=")" separator=",">
#{hole}
</foreach>
</when>
<otherwise>
<![CDATA[
WHERE NVL(USE_YN,' ') <> 'N' AND HOLE_LOCATION_TM_X >= #{minX} AND HOLE_LOCATION_TM_X <= #{maxX}
AND HOLE_LOCATION_TM_Y >= #{minY} AND HOLE_LOCATION_TM_Y <= #{maxY}
]]>
</otherwise>
</choose>
</select>
<select id="selectItems" parameterType="map" resultType="egovMap">
SELECT
HOLE_CODE,B.PROJECT_NAME,
(SELECT USCS_NAME FROM TBL_SCIENCE_LAYER_TB WHERE A.LAYER_CLASS_CODE = USCS_CODE) LAYER,
A.HOLE_TOTAL_DEPTH DEPTH,A.HOLE_EL EL,A.HOLE_WL WL, A.HOLE_DATE_FROM SDATE, A.HOLE_DATE_TO EDATE
FROM
TBL_HEADER A, TBL_PROJECT_INFO B
WHERE
<![CDATA[
NVL(A.USE_YN,' ') <> 'N' AND NVL(B.USE_YN,' ') <> 'N' AND
]]>
A.PROJECT_CODE = B.PROJECT_CODE
AND A.HOLE_CODE IN
<foreach collection="holeCode" item="hole" index="index" open="(" close=")" separator=",">
#{hole}
</foreach>
ORDER BY
HOLE_CODE
</select>
<select id="selectDistrict" parameterType="map" resultType="egovMap">
SELECT
'L_DIS' CODE, LD_ID CODE_VALUE, LD_DISTRICTCODE CODE_TEXT, 'L_DIS' PARENT_CODE, LD_ID PARENT_CODE_VALUE
FROM TBL_L_DISTRICT
</select>
<select id="selectDistrictSgg" parameterType="map" resultType="egovMap">
SELECT
'M_DIS' CODE, MD_ID CODE_VALUE, MD_DISTRICTCODE CODE_TEXT, 'L_DIS' PARENT_CODE, MD_LARGEDISTRICTID RARENT_CODE
FROM TBL_M_DISTRICT
WHERE MD_LARGEDISTRICTID = #{code}
ORDER BY CODE_TEXT
</select>
<select id="selectGrid1" parameterType="map" resultType="EgovMap">
SELECT SA.*
, (SELECT CRWD_ID FROM S3D_CROWD_ANAL OA WHERE OA.PROJECT_CODE = SA.PROJECT_CODE AND ROWNUM=1) AS CRWD_ID
, (SELECT SEPARATOR FROM S3D_CROWD_ANAL OA WHERE OA.PROJECT_CODE = SA.PROJECT_CODE AND ROWNUM=1) AS SEPARATOR
, 'fault_model' as FAULT_MODEL
, 'spt_model' as SPT_MODEL
, 'analysis_model' as ANALYSIS_MODEL
, 'layer_model' as LAYER_MODEL
, 'gw_model' as GW_MODEL
, 'no' as CAD
, 'no' as SHAPE
FROM (
SELECT
A.PROJECT_CODE
, replace(replace(A.PROJECT_NAME ,chr(10),' '),chr(13),' ') PROJECT_NAME
, COUNT(B.HOLE_CODE) HOLE_CNT
FROM
TBL_PROJECT_INFO A
, TBL_HEADER B
WHERE
<![CDATA[
NVL(A.USE_YN,' ') <> 'N' AND NVL(B.USE_YN,' ') <> 'N' AND
]]>
A.PROJECT_CODE = B.PROJECT_CODE
<choose>
<when test="sido != '' and sido != null">
<![CDATA[
AND SUBSTR(A.PROJECT_START_SPOT, 1, 3) = #{sido}
]]>
</when>
<when test="gugun != '' and gugun != null">
<![CDATA[
AND SUBSTR(A.PROJECT_START_SPOT, 1, 6) = #{sido+gugun}
]]>
</when>
</choose>
GROUP
BY A.PROJECT_CODE, A.PROJECT_NAME
) SA
WHERE EXISTS (SELECT 1
FROM S3D_CROWD_ANAL S2A
WHERE S2A.PROJECT_CODE = SA.PROJECT_CODE)
</select>
<select id="selectOldItems" parameterType="map" resultType="EgovMap">
SELECT
C.S3D_DISTRICT_CODE,
C.S3D_DISTRICT_NAME,
C.S3D_SEQUENCE,
C.S3D_PREFIX,
B.S3D_DISTRICT_SUB_CODE,
B.S3D_DISTRICT_SUB_NAME,
A.S3D_REGION_NAME,
A.S3D_CODE,
A.S3D_MAP_IMAGE
FROM
S3D_MASTER A,
S3D_DISTRICT_SUB B,
S3D_DISTRICT C
WHERE
A.S3D_DISTRICT_SUB = B.S3D_DISTRICT_SUB_CODE
AND A.S3D_DISTRICT = C.S3D_DISTRICT_CODE
<if test="sido != null and sido != '' and sido != 'all'">
<![CDATA[
AND C.S3D_DISTRICT_CODE = #{sido}
]]>
</if>
<if test="gugun != null and gugun != '' and gugun != 'all'">
<![CDATA[
AND B.S3D_DISTRICT_SUB_CODE = #{gugun}
]]>
</if>
<if test="dong != null and dong != '' and dong != 'all'">
<![CDATA[
AND A.S3D_MAP_IMAGE LIKE #{dong} || '%'
]]>
</if>
ORDER BY
C.S3D_DISTRICT_CODE, C.S3D_SEQUENCE, B.S3D_DISTRICT_SUB_CODE, A.S3D_CODE
</select>
<select id="get3dSido" parameterType="map" resultType="egovMap">
select
s3d_district_code, s3d_district_name, s3d_sequence, s3d_prefix
from s3d_district
where
s3d_district_yn = '1'
</select>
<select id="get3dGugun" parameterType="map" resultType="egovMap">
select s3d_district, s3d_district_sub_name, s3d_district_sub_code from s3d_district_sub
where s3d_district = #{doCd}
</select>
<select id="get3dDong" parameterType="map" resultType="egovMap">
select
s3d_region, s3d_link_code, s3d_button_name, s3d_link_address, s3d_link_x, s3d_link_y
from
s3d_detail
where
s3d_district = #{sido} and
s3d_district_sub = #{gugun}
order by
s3d_link_code asc
</select>
<select id="get3dDetail" parameterType="map" resultType="egovMap">
select
DISTINCT de.s3d_region, de.s3d_link_code, ma.s3d_district, de.s3d_button_name
from
s3d_detail de, s3d_master ma
where
de.s3d_district = ma.s3d_district and
ma.s3d_map_image = #{adong1} and
de.s3d_region= #{aid}
order by
de.s3d_link_code asc
</select>
<select id="getGeneralData" parameterType="map" resultType="egovMap">
SELECT
(
COALESCE(t_project.project_code, '')
|| '|' || COALESCE(t_hole.hole_elevation, '') -- HOLE_ELV -> hole_elevation
|| '|' || COALESCE(t_hole.hole_drilling_depth, '') -- 시추심도
|| '|' || COALESCE(t_hole.hole_x, '')
|| '|' || COALESCE(t_hole.hole_y, '')
|| '|' || COALESCE(ST_Y(t_hole.hole_point)::text, '')
|| '|' || COALESCE(ST_X(t_hole.hole_point)::text, '')
|| '|' || COALESCE(t_business.business_name, '')
|| '|' || COALESCE(t_hole.hole_water_level, '') -- HOLE_WL -> hole_water_level
|| '|' || COALESCE(t_hole.hole_location_sd, '') || COALESCE(t_hole.hole_location_sgg, '') || COALESCE(t_hole.hole_location_emd, '') || COALESCE(t_hole.hole_location_detail, '')
) AS VLU
FROM
public.apptb_hole01 t_hole
LEFT JOIN
public.apptb_saup01 t_business ON t_hole.business_code = t_business.business_code
LEFT JOIN
public.apptb_proj01 t_project ON t_business.project_code = t_project.project_code
WHERE
t_hole.business_code = #{businessCode}
AND t_hole.hole_code = #{holeCode}
</select>
<select id="getLinkLayerData" parameterType="map" resultType="egovMap">
SELECT
a1.layer_depth_from AS "FROM",
a1.layer_depth_to AS "TO",
a1.layer_science_name AS "USCS",
COALESCE(a1.layer_eng_name, '') AS "NAME",
COALESCE(a1.layer_soil_color, '') AS "COLOR",
REPLACE(REPLACE(a1.layer_desc, CHR(10), ' '), CHR(13), ' ') AS "DESC"
FROM
public.apptb_layr01 a1
WHERE
a1.business_code = #{businessCode}
AND a1.hole_code = #{holeCode}
ORDER BY a1.layer_depth_from
</select>
<select id="getSPTData" parameterType="map" resultType="egovMap">
SELECT
depth_spt,
spt_n
FROM
public.apptb_spt01
WHERE
hole_code = #{holeCode}
AND
business_code = #{businessCode}
ORDER BY
depth_spt
</select>
<select id="getSampleData" parameterType="map" resultType="egovMap">
<![CDATA[
SELECT
(t.CODE || '|' || t.DEPTH_FROM || '|' || t.DEPTH_TO || '|' || t.YN || '|' || t.SHAPE) AS VAL
FROM
(
SELECT
'0' AS CODE,
t1.rjoint_depth_from::numeric AS DEPTH_FROM,
t1.rjoint_depth_to::numeric AS DEPTH_TO,
'0' AS YN,
'0' AS SHAPE
FROM
public.apptb_josh01 t1
WHERE
t1.hole_code = #{holeCode}
AND t1.business_code = #{businessCode}
UNION ALL
SELECT
'0' AS CODE,
t2.depth_from::numeric AS DEPTH_FROM,
t2.depth_to::numeric AS DEPTH_TO,
'0' AS YN,
'0' AS SHAPE
FROM
public.apptb_polo01 t2
WHERE
t2.hole_code = #{holeCode}
AND t2.business_code = #{businessCode}
UNION ALL
SELECT
'0' AS CODE,
t3.rtri_depth_from::numeric AS DEPTH_FROM,
t3.rtri_depth_to::numeric AS DEPTH_TO,
'0' AS YN,
'0' AS SHAPE
FROM
public.apptb_rtri01 t3
WHERE
t3.hole_code = #{holeCode}
AND t3.business_code = #{businessCode}
UNION ALL
SELECT
'0' AS CODE,
t4.runi_depth_from::numeric AS DEPTH_FROM,
t4.runi_depth_to::numeric AS DEPTH_TO,
'0' AS YN,
'0' AS SHAPE
FROM
public.apptb_runi01 t4
WHERE
t4.hole_code = #{holeCode}
AND t4.business_code = #{businessCode}
UNION ALL
SELECT
t5.sample_code AS CODE,
t5.sample_depth_from::numeric AS DEPTH_FROM,
t5.sample_depth_to::numeric AS DEPTH_TO,
'0' AS YN,
t5.sample_shape AS SHAPE
FROM
public.apptb_smpl01 t5
WHERE
t5.hole_code = #{holeCode}
AND t5.business_code = #{businessCode}
AND (
(SELECT COUNT(*) FROM public.apptb_cons02 WHERE sample_code = t5.sample_code) > 0 OR
(SELECT COUNT(*) FROM public.apptb_stri01 WHERE sample_code = t5.sample_code) > 0 OR
(SELECT COUNT(*) FROM public.apptb_suni01 WHERE sample_code = t5.sample_code) > 0 OR
(SELECT COUNT(*) FROM public.apptb_clas01 WHERE sample_code = t5.sample_code) > 0 OR
(SELECT COUNT(*) FROM public.apptb_cbr01 WHERE sample_code = t5.sample_code) > 0
)
) AS t
WHERE
t.DEPTH_FROM IS NOT NULL
AND t.DEPTH_TO IS NOT NULL
]]>
</select>
<select id="selectInfoLastPage" parameterType="map" resultType="egovMap">
<![CDATA[
SELECT CEIL(MAX(ROWNUM)/ 15 ) AS LASTPAGE
FROM (
SELECT HOLE_CODE,
B.PROJECT_NAME,
(SELECT USCS_NAME
FROM TBL_SCIENCE_LAYER_TB
WHERE A.LAYER_CLASS_CODE = USCS_CODE) LAYER,
A.HOLE_TOTAL_DEPTH DEPTH,
A.HOLE_EL EL,
A.HOLE_WL WL,
A.HOLE_DATE_FROM SDATE,
A.HOLE_DATE_TO EDATE
FROM
TBL_HEADER A,
TBL_PROJECT_INFO B
WHERE
NVL(A.USE_YN,' ') <> 'N' AND NVL(B.USE_YN,' ') <> 'N' AND
A.PROJECT_CODE = B.PROJECT_CODE
AND A.HOLE_CODE IN
]]>
<choose>
<when test="areaHcodeArray != null and areaHcodeArray != ''">
<foreach collection="areaHcodeArray" item="hole" index="index" open="(" close=")" separator=",">
#{hole}
</foreach>
</when>
<otherwise>
<![CDATA[
(
SELECT TRIM(HOLE_CODE)
FROM TBL_HEADER A
WHERE NVL(A.USE_YN,' ') <> 'N' AND HOLE_LOCATION_TM_X >= #{minX} AND HOLE_LOCATION_TM_X <= #{maxX}
AND HOLE_LOCATION_TM_Y >= #{minY} AND HOLE_LOCATION_TM_Y <= #{maxY}
)
]]>
</otherwise>
</choose>
<![CDATA[
ORDER BY HOLE_CODE )
]]>
</select>
<select id="selectInfoItems" parameterType="map" resultType="egovMap">
<![CDATA[
SELECT *
FROM (SELECT ROWNUM RNK,
Z.*
FROM (SELECT HOLE_CODE,
B.PROJECT_NAME,
B.PROJECT_CODE,
(SELECT USCS_NAME
FROM TBL_SCIENCE_LAYER_TB
WHERE A.LAYER_CLASS_CODE = USCS_CODE) LAYER,
A.HOLE_TOTAL_DEPTH DEPTH,
A.HOLE_EL EL,
A.HOLE_WL WL,
A.HOLE_DATE_FROM SDATE,
A.HOLE_DATE_TO EDATE
FROM TBL_HEADER A,
TBL_PROJECT_INFO B
WHERE
NVL(A.USE_YN,' ') <> 'N' AND NVL(B.USE_YN,' ') <> 'N' AND
A.PROJECT_CODE = B.PROJECT_CODE
AND A.HOLE_CODE IN
]]>
<choose>
<when test="areaHcodeArray != null and areaHcodeArray != ''">
<foreach collection="areaHcodeArray" item="hole" index="index" open="(" close=")" separator=",">
#{hole}
</foreach>
</when>
<otherwise>
<![CDATA[
(
SELECT TRIM(HOLE_CODE)
FROM
TBL_HEADER A
WHERE
NVL(USE_YN,' ') <> 'N' AND HOLE_LOCATION_TM_X >= #{minX} AND HOLE_LOCATION_TM_X <= #{maxX}
AND HOLE_LOCATION_TM_Y >= #{minY} AND HOLE_LOCATION_TM_Y <= #{maxY}
)
]]>
</otherwise>
</choose>
<![CDATA[
ORDER BY HOLE_CODE ) Z )
WHERE RNK > #{firstRow} AND RNK <= #{lastRow}
]]>
</select>
<select id="selectWebDownloadLog" parameterType="map" resultType="egovMap">
<![CDATA[
SELECT
(sysdate-min(ENDDATE)) as ENDDATE,
COUNT(*) AS CNT
FROM WEB_DOWNLOAD_LOG
WHERE USERID = #{userId}
AND DOWNYN IN ('W','N','R')
]]>
</select>
<select id="selectProjectList" parameterType="map" resultType="egovMap">
SELECT
*
FROM
TBL_PROJECT_INFO
WHERE
<![CDATA[
NVL(USE_YN,' ') <> 'N' AND
]]>
TRIM(PROJECT_CODE) IN
<foreach collection="projectList" item="project" index="index" open="(" close=")" separator=",">
#{project}
</foreach>
</select>
<select id="mapBoreholeLogHeader" parameterType="map" resultType="egovMap">
SELECT
ap.project_code,
hole.hole_code AS HOLE_CODE,
ap.project_name,
cc1.code_txt ||
CASE
WHEN cc2.code_txt IS NOT NULL THEN ' ' || cc2.code_txt
ELSE ''
END AS COMPANY,
hole.hole_name AS HOLE_NAME,
cc3.code_txt AS DISTRICT,
hole.hole_x AS HOLE_OR_X,
hole.hole_y AS HOLE_OR_Y,
hole.HOLE_ELEVATION AS HOLE_EL,
hole.HOLE_END_DATE AS HOLE_DATE_TO,
hole.HOLE_DRILLING_DEPTH AS HOLE_TOTAL_DEPTH,
hole.HOLE_CASING_DEPTH AS HOLE_CASING_DEPTH,
hole.HOLE_WATER_LEVEL AS hole_wl,
hole.HOLE_BORING_MACHINE AS HOLE_BORING_MACHINE,
hole.HOLE_BORING_METHOD AS HOLE_BORING_METHOD,
hole.HOLE_BORING_NAME AS HOLE_BORING_BY,
'N/A' AS HOLE_INSPECTED_BY,
REPLACE((epsg_code.code_txt || ' ' || epsg_code2.code_txt), '(BESSEL)','') AS COORDINATE_L,
(
SELECT
CASE
WHEN MAX(layer.LAYER_DEPTH_TO::NUMERIC) >= 34.6 THEN 3
WHEN MAX(layer.LAYER_DEPTH_TO::NUMERIC) >= 17.3 THEN 2
ELSE 1
END
FROM APPTB_LAYR01 layer
WHERE layer.business_code = hole.business_code -- 현재 행의 business_code 사용
AND layer.hole_code = hole.hole_code -- 현재 행의 hole_code 사용
) AS PAGE_NUM
FROM APPTB_HOLE01 hole
LEFT JOIN APPTB_SAUP01 business ON hole.business_code = business.business_code
LEFT JOIN APPTB_PROJ01 ap ON business.project_code = ap.project_code
LEFT JOIN comtb_code02 cc1 ON ap.project_ordering_code = cc1.code_val
LEFT JOIN comtb_code02 cc2 ON ap.project_affiliated_code = cc2.code_val
LEFT JOIN comtb_code02 cc3 ON ap.project_harbor_code = cc3.code_val
LEFT JOIN comtb_code02 epsg_code ON ap.PROJECT_EPSG_CODE = epsg_code.code_val
LEFT JOIN comtb_code02 epsg_code2 ON hole.hole_coordinate = epsg_code2.code_val
WHERE
hole.business_code = #{businessCode} AND
hole.hole_code = #{holeCode}
</select>
</mapper>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sgis.surveysystem.mapper.AnswerMapper">
<resultMap id="answerResultMap" type="sgis.surveysystem.domain.Answer">
<id property="answerId" column="answer_id"/>
<result property="responseId" column="response_id"/>
<result property="questionId" column="question_id"/>
<result property="answerText" column="answer_text"/>
<result property="selectedOptionId" column="selected_option_id"/>
</resultMap>
<insert id="insertAnswer" parameterType="sgis.surveysystem.domain.Answer">
INSERT INTO tb_answers (
answer_id,
response_id,
question_id,
answer_text,
selected_option_id
) VALUES (
#{answerId, jdbcType=OTHER},
#{responseId, jdbcType=OTHER},
#{questionId, jdbcType=OTHER},
#{answerText},
#{selectedOptionId, jdbcType=OTHER}
)
</insert>
<select id="findAnswerById" resultMap="answerResultMap">
SELECT
answer_id,
response_id,
question_id,
answer_text,
selected_option_id
FROM tb_answers
WHERE answer_id = #{answerId, jdbcType=OTHER}
</select>
<select id="findAnswersByResponseId" resultMap="answerResultMap">
SELECT
answer_id,
response_id,
question_id,
answer_text,
selected_option_id
FROM tb_answers
WHERE response_id = #{responseId, jdbcType=OTHER}
</select>
<select id="findAnswersByQuestionId" resultMap="answerResultMap">
SELECT
answer_id,
response_id,
question_id,
answer_text,
selected_option_id
FROM tb_answers
WHERE question_id = #{questionId, jdbcType=OTHER}
</select>
<update id="updateAnswer" parameterType="sgis.surveysystem.domain.Answer">
UPDATE tb_answers
SET
answer_text = #{answerText},
selected_option_id = #{selectedOptionId, jdbcType=OTHER}
WHERE answer_id = #{answerId, jdbcType=OTHER}
</update>
<delete id="deleteAnswer">
DELETE FROM tb_answers
WHERE answer_id = #{answerId, jdbcType=OTHER}
</delete>
</mapper>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sgis.surveysystem.mapper.AnswerSelectedOptionMapper">
<resultMap id="answerSelectedOptionResultMap" type="sgis.surveysystem.domain.AnswerSelectedOption">
<id property="answerId" column="answer_id"/>
<id property="optionId" column="option_id"/>
</resultMap>
<insert id="insertAnswerSelectedOption" parameterType="sgis.surveysystem.domain.AnswerSelectedOption">
INSERT INTO tb_answer_selected_options (
answer_id,
option_id
) VALUES (
#{answerId, jdbcType=OTHER},
#{optionId, jdbcType=OTHER}
)
</insert>
<insert id="insertMultipleAnswerSelectedOptions" parameterType="java.util.List">
INSERT INTO tb_answer_selected_options (
answer_id,
option_id
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.answerId, jdbcType=OTHER},
#{item.optionId, jdbcType=OTHER}
)
</foreach>
</insert>
<select id="findOptionsByAnswerId" resultMap="answerSelectedOptionResultMap">
SELECT
answer_id,
option_id
FROM tb_answer_selected_options
WHERE answer_id = #{answerId, jdbcType=OTHER}
</select>
<delete id="deleteByAnswerId">
DELETE FROM tb_answer_selected_options
WHERE answer_id = #{answerId, jdbcType=OTHER}
</delete>
<delete id="deleteAnswerSelectedOption">
DELETE FROM tb_answer_selected_options
WHERE answer_id = #{param1, jdbcType=OTHER} AND option_id = #{param2, jdbcType=OTHER}
</delete>
</mapper>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sgis.surveysystem.mapper.QuestionMapper">
<resultMap id="questionResultMap" type="sgis.surveysystem.domain.Question">
<id property="questionId" column="question_id"/>
<result property="surveyId" column="survey_id"/>
<result property="questionText" column="question_text"/>
<result property="questionType" column="question_type"/>
<result property="questionOrder" column="question_order"/>
<result property="isRequired" column="is_required"/>
</resultMap>
<!-- 문항 삽입 -->
<insert id="insertQuestion" parameterType="sgis.surveysystem.domain.Question">
INSERT INTO tb_questions (
question_id,
survey_id,
question_text,
question_type,
question_order,
is_required
) VALUES (
CAST(#{questionId, jdbcType=OTHER} AS uuid), <!-- 이 부분을 수정했습니다. -->
CAST(#{surveyId, jdbcType=OTHER} AS uuid), <!-- 이 부분을 수정했습니다. -->
#{questionText},
#{questionType},
#{questionOrder},
#{isRequired}
)
</insert>
<select id="findQuestionsBySurveyId" resultMap="questionResultMap">
SELECT
question_id,
survey_id,
question_text,
question_type,
question_order,
is_required
FROM tb_questions
WHERE survey_id = CAST(#{surveyId, jdbcType=OTHER} AS uuid)
ORDER BY question_order ASC
</select>
<select id="findQuestionById" resultMap="questionResultMap">
SELECT
question_id,
survey_id,
question_text,
question_type,
question_order,
is_required
FROM tb_questions
WHERE question_id = CAST(#{questionId, jdbcType=OTHER} AS uuid)
</select>
<update id="updateQuestion" parameterType="sgis.surveysystem.domain.Question">
UPDATE tb_questions
SET
question_text = #{questionText},
question_type = #{questionType},
question_order = #{questionOrder},
is_required = #{isRequired}
WHERE question_id = CAST(#{questionId, jdbcType=OTHER} AS uuid)
</update>
<delete id="deleteQuestion">
DELETE FROM tb_questions
WHERE question_id = CAST(#{questionId, jdbcType=OTHER} AS uuid)
</delete>
</mapper>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sgis.surveysystem.mapper.QuestionOptionMapper">
<resultMap id="questionOptionResultMap" type="sgis.surveysystem.domain.QuestionOption">
<id property="optionId" column="option_id"/>
<result property="questionId" column="question_id"/>
<result property="optionText" column="option_text"/>
<result property="optionOrder" column="option_order"/>
</resultMap>
<!-- 선택지 삽입 -->
<insert id="insertQuestionOption" parameterType="sgis.surveysystem.domain.QuestionOption">
INSERT INTO tb_question_options (
option_id,
question_id,
option_text,
option_order
) VALUES (
CAST(#{optionId, jdbcType=OTHER} AS uuid), <!-- 이 부분을 수정했습니다. -->
CAST(#{questionId, jdbcType=OTHER} AS uuid), <!-- 이 부분을 수정했습니다. -->
#{optionText},
#{optionOrder}
)
</insert>
<select id="findOptionsByQuestionId" resultMap="questionOptionResultMap">
SELECT
option_id,
question_id,
option_text,
option_order
FROM tb_question_options
WHERE question_id = CAST(#{questionId, jdbcType=OTHER} AS uuid)
ORDER BY option_order ASC
</select>
<select id="findQuestionOptionById" resultMap="questionOptionResultMap">
SELECT
option_id,
question_id,
option_text,
option_order
FROM tb_question_options
WHERE option_id = CAST(#{optionId, jdbcType=OTHER} AS uuid)
</select>
<update id="updateQuestionOption" parameterType="sgis.surveysystem.domain.QuestionOption">
UPDATE tb_question_options
SET
option_text = #{optionText},
option_order = #{optionOrder}
WHERE option_id = CAST(#{optionId, jdbcType=OTHER} AS uuid)
</update>
<delete id="deleteQuestionOption">
DELETE FROM tb_question_options
WHERE option_id = CAST(#{optionId, jdbcType=OTHER} AS uuid)
</delete>
</mapper>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sgis.surveysystem.mapper.RespondentMapper">
<resultMap id="respondentResultMap" type="sgis.surveysystem.domain.Respondent">
<id property="respondentId" column="respondent_id"/>
<result property="userId" column="user_id"/>
<result property="ipAddress" column="ip_address"/>
<result property="responseStartAt" column="response_start_at"/>
<result property="responseEndAt" column="response_end_at"/>
</resultMap>
<insert id="insertRespondent" parameterType="sgis.surveysystem.domain.Respondent">
INSERT INTO tb_respondents (
respondent_id,
user_id,
ip_address,
response_start_at,
response_end_at
) VALUES (
#{respondentId, jdbcType=OTHER},
#{userId},
#{ipAddress, jdbcType=OTHER},
#{responseStartAt},
#{responseEndAt}
)
</insert>
<select id="findRespondentById" resultMap="respondentResultMap">
SELECT
respondent_id,
user_id,
ip_address,
response_start_at,
response_end_at
FROM tb_respondents
WHERE respondent_id = #{respondentId, jdbcType=OTHER}
</select>
<select id="findRespondentByUserId" resultMap="respondentResultMap">
SELECT
respondent_id,
user_id,
ip_address,
response_start_at,
response_end_at
FROM tb_respondents
WHERE user_id = #{userId}
LIMIT 1
</select>
<update id="updateRespondent" parameterType="sgis.surveysystem.domain.Respondent">
UPDATE tb_respondents
SET
user_id = #{userId},
ip_address = #{ipAddress, jdbcType=OTHER},
response_start_at = #{responseStartAt},
response_end_at = #{responseEndAt}
WHERE respondent_id = #{respondentId, jdbcType=OTHER}
</update>
<delete id="deleteRespondent">
DELETE FROM tb_respondents
WHERE respondent_id = #{respondentId, jdbcType=OTHER}
</delete>
</mapper>

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sgis.surveysystem.mapper.SurveyMapper">
<resultMap id="surveyResultMap" type="sgis.surveysystem.domain.Survey">
<id property="surveyId" column="survey_id"/>
<result property="surveyTitle" column="survey_title"/>
<result property="surveyDescription" column="survey_description"/>
<result property="createdAt" column="created_at"/>
<result property="updatedAt" column="updated_at"/>
<result property="isActive" column="is_active"/>
<result property="startDate" column="start_date"/>
<result property="endDate" column="end_date"/>
</resultMap>
<insert id="insertSurvey" parameterType="sgis.surveysystem.domain.Survey">
INSERT INTO tb_surveys (
survey_id,
survey_title,
survey_description,
created_at,
updated_at,
is_active,
start_date,
end_date
) VALUES (
CAST(#{surveyId, jdbcType=OTHER} AS uuid),
#{surveyTitle},
#{surveyDescription},
#{createdAt},
#{updatedAt},
#{isActive},
#{startDate},
#{endDate}
)
</insert>
<select id="findAllSurveys" resultMap="surveyResultMap">
SELECT
survey_id,
survey_title,
survey_description,
created_at,
updated_at,
is_active,
start_date,
end_date
FROM tb_surveys
</select>
<select id="findSurveyById" resultMap="surveyResultMap">
SELECT
survey_id,
survey_description,
survey_title,
created_at,
updated_at,
is_active,
start_date,
end_date
FROM tb_surveys
WHERE survey_id = CAST(#{surveyId, jdbcType=OTHER} AS uuid) <!-- 이 부분을 수정했습니다. -->
</select>
<update id="updateSurvey" parameterType="sgis.surveysystem.domain.Survey">
UPDATE tb_surveys
SET
survey_title = #{surveyTitle},
survey_description = #{surveyDescription},
updated_at = #{updatedAt},
is_active = #{isActive},
start_date = #{startDate},
end_date = #{endDate}
WHERE survey_id = CAST(#{surveyId, jdbcType=OTHER} AS uuid) <!-- 이 부분도 일관성을 위해 수정합니다. -->
</update>
<update id="updateSurveyStatus">
UPDATE tb_surveys
SET
is_active = #{isActive},
updated_at = NOW()
WHERE survey_id = CAST(#{surveyId, jdbcType=OTHER} AS uuid) <!-- 이 부분도 일관성을 위해 수정합니다. -->
</update>
<delete id="deleteSurvey">
DELETE FROM tb_surveys
WHERE survey_id = CAST(#{surveyId, jdbcType=OTHER} AS uuid) <!-- 이 부분도 일관성을 위해 수정합니다. -->
</delete>
</mapper>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sgis.surveysystem.mapper.SurveyResponseMapper">
<resultMap id="surveyResponseResultMap" type="sgis.surveysystem.domain.SurveyResponse">
<id property="responseId" column="response_id"/>
<result property="surveyId" column="survey_id"/>
<result property="respondentId" column="respondent_id"/>
<result property="submittedAt" column="submitted_at"/>
</resultMap>
<insert id="insertSurveyResponse" parameterType="sgis.surveysystem.domain.SurveyResponse">
INSERT INTO tb_survey_responses (
response_id,
survey_id,
respondent_id,
submitted_at
) VALUES (
#{responseId, jdbcType=OTHER},
#{surveyId, jdbcType=OTHER},
#{respondentId, jdbcType=OTHER},
#{submittedAt}
)
</insert>
<select id="findSurveyResponseById" resultMap="surveyResponseResultMap">
SELECT
response_id,
survey_id,
respondent_id,
submitted_at
FROM tb_survey_responses
WHERE response_id = #{responseId, jdbcType=OTHER}
</select>
<select id="findResponsesBySurveyId" resultMap="surveyResponseResultMap">
SELECT
response_id,
survey_id,
respondent_id,
submitted_at
FROM tb_survey_responses
WHERE survey_id = #{surveyId, jdbcType=OTHER}
ORDER BY submitted_at DESC
</select>
<select id="findResponsesByRespondentId" resultMap="surveyResponseResultMap">
SELECT
response_id,
survey_id,
respondent_id,
submitted_at
FROM tb_survey_responses
WHERE respondent_id = #{respondentId, jdbcType=OTHER}
ORDER BY submitted_at DESC
</select>
<delete id="deleteSurveyResponse">
DELETE FROM tb_survey_responses
WHERE response_id = #{responseId, jdbcType=OTHER}
</delete>
</mapper>

View File

@ -19,7 +19,9 @@
<property name="typeHandlers">
<array>
<bean class="sgis.board.typehandler.OffsetDateTimeTypeHandler" />
</array>
<bean class="sgis.surveysystem.config.UUIDTypeHandler" />
<bean class="sgis.surveysystem.config.LocalDateTimeTypeHandler" />
</array>
</property>
</bean>

View File

@ -4,19 +4,21 @@
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd">
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 패키지 내 Controller, Service, Repository 클래스의 auto detect를 위한 mvc 설정 -->
<context:component-scan base-package="egovframework,sgis">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
<!-- 서블릿컨이너상의 exception에 대한 오류 페이지를 연결하는 mvc 설정-->
<!-- 서블릿컨이너상의 exception에 대한 오류 페이지를 연결하는 mvc 설정-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="sgis/com/error/error"/>
<property name="exceptionMappings">
@ -39,18 +41,17 @@
<!-- 로그인 체크가 필요한 URL과 로그인 여부를 체크해준다 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/com/loginAction.do"/><!-- 로그인 -->
<mvc:mapping path="/com/logoutAction.do"/><!-- 로그아웃 -->
<mvc:mapping path="/com/loginAction.do"/>
<mvc:mapping path="/com/logoutAction.do"/>
<mvc:exclude-mapping path="/com/loginForm.do"/>
<bean class="egovframework.com.cmm.interceptor.LoginInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/sgis/portal/portalMain.do"/>
<mvc:mapping path="/com/*.do"/> <!-- 공통 -->
<mvc:mapping path="/app/*/*.do"/> <!-- 입력시스템 -->
<mvc:mapping path="/map/*.do"/> <!-- 지도시스템 -->
<!-- <bean class="egovframework.com.cmm.interceptor.noLoginInterceptor" /> -->
<mvc:mapping path="/com/*.do"/>
<mvc:mapping path="/app/*/*.do"/>
<mvc:mapping path="/map/*.do"/>
<bean class="egovframework.com.cmm.interceptor.AuthenticInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
@ -58,6 +59,41 @@
<!-- Annotation 을 사용하지 않는 경우에 대한 MVC 처리 설정 -->
<mvc:view-controller path="/cmmn/validator.do" view-name="cmmn/validator"/>
<mvc:annotation-driven/>
<!-- ObjectMapper 빈을 전역적으로 정의 -->
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<!-- 기본 생성자 사용 -->
<!-- 날짜/시간 포맷팅 설정 (선택 사항이지만 권장) -->
<property name="serializationInclusion" value="NON_NULL"/>
<property name="dateFormat">
<bean class="com.fasterxml.jackson.databind.util.StdDateFormat"/>
</property>
</bean>
</beans>
<!-- ObjectMapper에 JavaTimeModule 등록을 위한 MethodInvokingFactoryBean -->
<!-- objectMapper 빈이 생성된 후 registerModule 메서드를 호출하여 모듈을 등록합니다. -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="objectMapper"/>
<property name="targetMethod" value="registerModule"/>
<property name="arguments">
<list>
<bean class="com.fasterxml.jackson.datatype.jsr310.JavaTimeModule"/>
</list>
</property>
</bean>
<!-- mvc:annotation-driven을 명시적으로 재정의하여 메시지 컨버터 설정 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
</list>
</property>
<!-- 전역으로 정의된 objectMapper 빈을 참조 -->
<property name="objectMapper" ref="objectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
</beans>

View File

@ -216,6 +216,7 @@ function selectComboList(class_name, data, selected_data){
}
function go_save(){
debugger;
<c:if test="${empty fileInfo}">
var upload = $("#fileUpload1").data("kendoUpload");
var upCnt = upload.getFiles().length

View File

@ -20,7 +20,14 @@ function loadList(){
url : "/sgis/portal/board/all.do?boardCategoryId=${boardCategoryId}",
type : "get",
dataType : "json",
success : makeView,
success : function(result) {
try {
var jsonData = JSON.parse(result);
makeView(jsonData)
} catch(e) {
console.log("Error occured while parsing JSON")
}
},
error : function(){
alert("error");
}

View File

@ -80,6 +80,17 @@
<td><input type="text" id="title" name="title" class="form-control"
<c:if test="${parentPostId != null && parentPostId != ''}">readonly="readonly"</c:if>/></td>
</tr>
<tr>
<th class="td-head" scope="row">파일첨부</th>
<!-- <td><input type="file" id="title" name="title" class="form-control" /></td> -->
<td>
<div id="dropZone" class="form-control p-4" style="cursor:pointer;">
<p class="text-muted">여기에 파일을 드래그 앤 드롭 하거나 클릭해서 파일 선택</p>
<input type="file" id="fileInput" multiple hidden />
<ul id="fileList" class="list-group"></ul>
</div>
</td>
</tr>
<tr>
<th class="td-head" scope="row">내용</th>
<td><textarea rows="7" class="form-control" id="boardContent" name="boardContent"></textarea></td>
@ -101,7 +112,9 @@
<!-- 쓰기 끝 -->
<script>
let fileList = []; // 파일첨부 - 첨부된 파일 목록 포시란
$(document).ready(function() {
var parentPostId = "${parentPostId}";
if (parentPostId !== null && parentPostId !== '' && parentPostId !== "null") {
// 부모글 정보 로드
@ -125,6 +138,67 @@ $(document).ready(function() {
}
});
}
const $dropZone = $("#dropZone"); // 파일첨부 - 드래그앤드롭
const $fileInput = $("#fileInput"); // 파일첨부 -
const $fileListUI = $("#fileList"); // 첨부된 파일 목록 표시
// 파일 선택 창 열기
$dropZone.on("click", function () {
$fileInput.trigger("click");
});
// 드래그 오버 스타일
$dropZone.on("dragover", function (e) {
e.preventDefault();
$dropZone.addClass("dragover");
});
$dropZone.on("dragleave", function () {
$dropZone.removeClass("dragover");
});
$dropZone.on("drop", function (e) {
e.preventDefault();
$dropZone.removeClass("dragover");
const files = e.originalEvent.dataTransfer.files;
handleFiles(files);
});
$fileInput.on("change", function () {
handleFiles(this.files);
});
function handleFiles(files) {
$.each(files, function (i, file) {
// 중복 체크는 이름으로만 (간단히 처리)
if (!fileList.some(f => f.name === file.name && f.size === file.size)) {
fileList.push(file);
}
});
renderFileList();
}
function renderFileList() {
$fileListUI.empty();
$.each(fileList, function (index, file) {
const $li = $(`
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>` + file.name + ` </span>
<b class="icon-trash remove-btn" title="삭제버튼" style="cursor:pointer;">삭제</b>
</li>
`);
$li.find(".remove-btn").on("click", function () {
removeFile(index);
});
$fileListUI.append($li);
});
}
function removeFile(index) {
fileList.splice(index, 1);
renderFileList();
}
});
function goList(){
@ -171,4 +245,49 @@ function goInsert(){
});
$(".fclear").trigger("click");
}
function addPost(){
var title = $("#title").val();
var content = $("#boardContent").val();
var boardCategoryId = $("#boardCategoryId").val();
var parentPostId = $("#parentPostId").val();
if(title == null || title == "" || title == 0){
alert("제목을 입력하세요");
return false;
} else if (content == null || content == "" || content == 0) {
alert("내용을 입력하세요");
return false;
}
const formData = new FormData();
formData.append("title", title);
formData.append("content", content);
formData.append("boardCategoryId", boardCategoryId);
if (parentPostId !== null && parentPostId !== "" && parentPostId !== "null") {
formData.append("parentPostId", parentPostId);
}
// $.each(fileList, function (i, file) {
// formData.append("files", file);
// });
$.ajax({
url : "/sgis/portal/board/new.do",
type : "post",
data: formData,
contentType: false,
processData: false,
success : function(){
$("#grid").empty();
var categoryId = "${boardCategoryId}" ? "${boardCategoryId}" : "${params.boardCategoryId}";
location.href = "/sgis/portal/board-list.do?boardCategoryId=" + categoryId;
},
error : function() {
alert("error");
}
});
$(".fclear").trigger("click");
}
</script>

View File

@ -96,7 +96,7 @@
<a href="javascript:void(0);">스마트지반정보관리 시스템 소개</a>
</li>
<li class="mega-menu-dropdown">
<a href="board-list.do?boardCategoryId=${params.boardCategoryId}"> 커뮤니티 </a>
<a href="board-list.do?boardCategoryId=${params.boardCategoryId}"> 자료실 </a>
</li>
<li class="mega-menu-dropdown">
<a href="boardNotice.do"> 공지사항 </a>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,358 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>스마트지반정보관리 시스템</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name = "viewport" content = "user-scalable=no, width=device-width">
<meta name="author" content="해양수산부">
<!-- description -->
<meta name="description" content="스마트지반정보관리">
<!-- keywords -->
<meta name="keywords" content="">
<!-- favicon -->
<link rel="shortcut icon" href="${pageContext.request.contextPath}/com/img/common/icon/favicon.ico">
<script src="${pageContext.request.contextPath}/com/plugins/kendo-ui/js/jquery.js"></script>
<script src="${pageContext.request.contextPath}/com/plugins/bootstrap.min.js"></script>
<link rel="stylesheet" href="${pageContext.request.contextPath}/com/plugins/bootstrap.min.css" />
<script src="${pageContext.request.contextPath}/com/plugins/css-element-queries/ResizeSensor.js"></script>
<script src="${pageContext.request.contextPath}/com/plugins/css-element-queries/ElementQueries.js"></script>
<script src="${pageContext.request.contextPath}/com/js/app.js"></script>
<script src="${pageContext.request.contextPath}/com/js/cross-section.js"></script>
<!-- 스타일 css -->
<link rel="stylesheet" href="${pageContext.request.contextPath}/com/css/style.css" />
<link rel="stylesheet" href="${pageContext.request.contextPath}/com/css/common.v2.0.css" />
<link rel="stylesheet" href="${pageContext.request.contextPath}/com/css/cross-section.css" />
<link rel="stylesheet" href="${pageContext.request.contextPath}/com/plugins/openlayers/theme/default/style.css" type="text/css" />
<script type="text/javascript" src="${pageContext.request.contextPath}/com/plugins/proj4js/proj4js-compressed.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/com/plugins/openlayers/OpenLayers.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/com/js/map/sichudan/js/json2.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/com/js/map/main/section.js" charset="UTF-8"></script>
<script type="text/javascript">
// --------------------------------
// 시스템 변수
// --------------------------------
var geoSection = null;
// --------------------------------
// 어플리케이션 로딩
// --------------------------------
addLoadEvent(initApp);
function initApp() {
showProgress();
if (geoSection == null) {
geoSection = new GeoSection("map");
}
var data = "${data}";
geoSection.show(data);
hideProgress();
}
// --------------------------------
// 함수 : 지반 단면도 보기 이벤트 컨트롤
// --------------------------------
var isShowAltitude = false;
var isShowDistance = false;
var isShowSPT = false;
var isShowLink = false;
var isShowWater = false;
function zoomToExtent() {
geoSection.zoomToExtent();
}
function zoomIn() {
geoSection.zoomIn();
}
function zoomOut() {
geoSection.zoomOut();
}
function toggleAltitude() {
if (isShowAltitude) {
isShowAltitude = false;
geoSection.hideHoleAltitude();
} else {
isShowAltitude = true;
geoSection.showHoleAltitude();
}
}
function toggleDistance() {
if (isShowDistance) {
isShowDistance = false;
geoSection.hideHoleDistance();
} else {
isShowDistance = true;
geoSection.showHoleDistance();
}
}
function toggleSPT() {
if (isShowSPT) {
isShowSPT = false;
geoSection.hideHoleSPT();
} else {
isShowSPT = true;
geoSection.showHoleSPT();
}
}
function toggleLink() {
if (isShowLink) {
isShowLink = false;
geoSection.hideHoleLink();
} else {
isShowLink = true;
geoSection.showHoleLink();
}
}
function toggleWater() {
if (isShowWater) {
isShowWater = false;
geoSection.hideHoleWater();
} else {
isShowWater = true;
geoSection.showHoleWater();
}
}
// --------------------------------
// 함수 : Process 패널 보이기 & 숨기기
// --------------------------------
var proWin = null;
function showProgress() {
var over = document.getElementById("over");
if (over != null) {
over.style.display = "block";
}
}
function hideProgress() {
var over = document.getElementById("over");
if (over != null) {
over.style.display = "none";
}
}
</script>
</head>
<body class="map-service cross-section">
<!-- 페이지 컨테이너 시작 -->
<section class="page-container">
<div class="page-content-wrapper">
<!-- 컨텐츠 시작 -->
<div class="page-content">
<div class="page-content-inner">
<div class="page-title-wrapper">
<h1 class="cross-section-title"><span>지반 단면도</span></h1>
</div>
<div class="cross-section-wrapper">
<div class="cross-contents-panel">
<!-- <div id="map" class="cross-contents-map" style="background-color: white; border: 1px solid black; padding: 0px;">
</div>
<div id="over" align="center" style="position: absolute; top: 120px; left: 400px; width: 751px; height: 517px;">
<img src="/com/img/map-service/sichudan/loading.gif" style="position: relative; top: 130px;">
</div> -->
<div id="map" style="width: 100%; height: calc(100vh - 296px); background-color: white; border: 1px solid black; padding: 0px;"></div>
<div id="over" align="center" style="position: absolute; top: calc(100vh - 85%); left: auto; width: 100%; height: calc(100vh - 296px);">
<img src="/com/img/map-service/sichudan/loading.gif" style="position: relative; top: auto;">
</div>
<div class="cross-contents-option">
<div class="cross-option-box">
<div class="cross-option-col cross-option-ex">
<ul>
<li class="option-mouse"><span class="option-title">이동</span>마우스 왼쪽 드래그, <span class="option-title">확대</span>L-Shift + 마우스 왼쪽 드래그</li>
<li class="option-ex ex-sample">시료를 채취한 부분으로 실내시험 결과를 확인 할 수 있습니다.</li>
<li class="option-ex ex-jusang">시추주상도 정보를 확인 하실 수 있습니다.</li>
<li class="option-ex ex-info">지반단면도에 대한 XML 데이터를 보실 수 있습니다.</li>
</ul>
</div>
<div class="cross-option-col cross-btn-group">
<ul>
<li>
<button type="button" class="btn btn-small btn-rounded5 btn-ske-blue btn-icon-left btn-icon-zoom-all" onclick="zoomToExtent();" onfocus="this.blur();"><span>전체</span></button>
<button type="button" class="btn btn-small btn-rounded5 btn-ske-blue btn-icon-left btn-icon-zoom-plus" onclick="zoomIn();" onfocus="this.blur();"><span>확대</span></button>
<button type="button" class="btn btn-small btn-rounded5 btn-ske-blue btn-icon-left btn-icon-zoom-minus" onclick="zoomOut();" onfocus="this.blur();"><span>축소</span></button>
</li>
<li>
<button type="button" class="btn btn-small btn-rounded5 btn-dark-gray btn-icon-left btn-icon-nextB" onclick="toggleAltitude();" onfocus="this.blur();"><span>표고정보보기</span></button>
<button type="button" class="btn btn-small btn-rounded5 btn-dark-gray btn-icon-left btn-icon-nextB" onclick="toggleDistance();" onfocus="this.blur();"><span>시추공사이거리보기</span></button>
</li>
<li>
<button type="button" class="btn btn-small btn-rounded5 btn-dark-gray btn-icon-left btn-icon-nextB" onclick="toggleSPT();" onfocus="this.blur();"><span>SPT보기</span></button>
<button type="button" class="btn btn-small btn-rounded5 btn-dark-gray btn-icon-left btn-icon-nextB" onclick="toggleLink();" onfocus="this.blur();"><span>지층연결</span></button>
<button type="button" class="btn btn-small btn-rounded5 btn-dark-gray btn-icon-left btn-icon-nextB" onclick="toggleWater();" onfocus="this.blur();"><span>지하수위보기</span></button>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="cross-legend-panel">
<h2 class="page-title-1depth"><span>지층 색상표</span></h2>
<div class="table-scrollable color-palette">
<table class="table table-bordered">
<colgroup>
<col style="width:40px;">
<col style="width:60px;">
<col style="width:40px;">
<col style="width:auto;">
</colgroup>
<thead>
<th>색상</th>
<th>USCS분류</th>
<th colspan="2">지층명</th>
</thead>
<tbody>
<tr class="palette-tr-g">
<td rowspan="4" class="palette-td-g"></td>
<td>GW</td>
<td rowspan="4">자갈</td>
<td class="t-left">입도 좋은 자갈</td>
</tr>
<tr class="palette-tr-g">
<td>GP</td>
<td class="t-left">입도 나쁜 자갈</td>
</tr>
<tr class="palette-tr-g">
<td>GM</td>
<td class="t-left">실트질 자갈</td>
</tr>
<tr class="palette-tr-g">
<td>GC</td>
<td class="t-left">점토질 자갈</td>
</tr>
<tr class="palette-tr-s">
<td rowspan="4" class="palette-td-s"></td>
<td>SW</td>
<td rowspan="4">모래</td>
<td class="t-left">입도 좋은 모래</td>
</tr>
<tr class="palette-tr-s">
<td>SP</td>
<td class="t-left">입도 나쁜 모래</td>
</tr>
<tr class="palette-tr-s">
<td>SM</td>
<td class="t-left">실트질 모래</td>
</tr>
<tr class="palette-tr-s">
<td>SC</td>
<td class="t-left">점토질 모래</td>
</tr>
<tr class="palette-tr-mc">
<td rowspan="4" class="palette-td-mc"></td>
<td>ML</td>
<td rowspan="2">실트</td>
<td class="t-left">무기질 실트, 극세사</td>
</tr>
<tr class="palette-tr-mc">
<td>MH</td>
<td class="t-left">무기질 실트</td>
</tr>
<tr class="palette-tr-mc">
<td>CL</td>
<td rowspan="2">점토</td>
<td class="t-left">무기질, 자갈질 점토</td>
</tr>
<tr class="palette-tr-mc">
<td>CH</td>
<td class="t-left">고소성 무기질 점토</td>
</tr>
<tr class="palette-tr-op">
<td rowspan="3" class="palette-td-op"></td>
<td>OH</td>
<td colspan="2" class="t-left">중소성 유기질 점토</td>
</tr>
<tr class="palette-tr-op">
<td>OL</td>
<td colspan="2" class="t-left">유기질 실트</td>
</tr>
<tr class="palette-tr-op">
<td>PT</td>
<td colspan="2" class="t-left">이탄 및 고유기질토</td>
</tr>
<tr class="palette-tr-wr">
<td class="palette-td-wr"></td>
<td>WR</td>
<td colspan="2" class="t-left">풍화암</td>
</tr>
<tr class="palette-tr-sr">
<td class="palette-td-sr"></td>
<td>SR</td>
<td colspan="2" class="t-left">연암</td>
</tr>
<tr class="palette-tr-mr">
<td class="palette-td-mr"></td>
<td>MR</td>
<td colspan="2" class="t-left">보통암</td>
</tr>
<tr class="palette-tr-hr">
<td class="palette-td-hr"></td>
<td>HR</td>
<td colspan="2" class="t-left">경암</td>
</tr>
<tr class="palette-tr-xr">
<td class="palette-td-xr"></td>
<td>XR</td>
<td colspan="2" class="t-left">극경암</td>
</tr>
<tr class="palette-tr-et">
<td class="palette-td-et"></td>
<td>ET</td>
<td colspan="2" class="t-left">기타</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- 컨텐츠 끝 -->
</div>
</section>
<!-- 페이지 컨테이너 끝 -->
</body>
</html>

View File

@ -181,7 +181,7 @@
}
$(document).ready(function () {
$("#projectStartDate").kendoDatePicker({
value : new Date(),
value : new Date("1990-01-01T00:00:00.000+09:00"),
culture: "ko-KR",
format : "yyyy-MM-dd"
});
@ -256,7 +256,7 @@
var business_process_data = ${codeJson_business_process_code}; // 사업 공정 코드
$(document).ready(function () {
$("#inputYmdTo").kendoDatePicker({
value : new Date(),
value : new Date("1990-01-01T00:00:00.000+09:00"),
culture: "ko-KR",
format : "yyyy-MM-dd"
});
@ -368,17 +368,51 @@
<input type="radio" name="sichuSelect" id="polygon" autocomplete="off" onchange="changeInteraction('areaClick');" > 영역선택
</label>
</div>
</li>
<li style="width: 400px;">
<button type="button" id="searchInfo" class="btn btn-ske-blue btn-icon-left btn-icon-search" ><span>정보조회</span></button>
<a href="javascript:void(0);" id="mapDemSearch" class="btn btn-single-toggle" data-toggle="button" aria-pressed="false" autocomplete="off" ><span>DEM 시계열 조회</span></a>
<script>
$('#mapDemSearch').click(function(){
$(".calendar-group").toggleClass('active');
$(".map-bottom-control-wrapper").toggleClass('active');
});
</script>
</li>
</li>
<li style="width: 400px;">
<button type="button" id="searchInfo" class="btn btn-ske-blue btn-icon-left btn-icon-search" ><span>정보조회</span></button>
<a href="javascript:void(0);" id="mapDemSearch" class="btn btn-single-toggle" data-toggle="button" aria-pressed="false" autocomplete="off" ><span>DEM 시계열 조회</span></a>
<script>
$('#mapDemSearch').click(function(){
$(".calendar-group").toggleClass('active');
$(".map-bottom-control-wrapper").toggleClass('active');
});
</script>
</li>
<li style="width: 400px;">
<a href="javascript:showSichudan();" id="borehole-profile" class="btn btn-ske-blue btn-icon-left btn-icon-search" autocomplete="off" ><span>지반단면도</span></a>
<a href="javascript:showBoreholeLog();" id="borehole-log" class="btn btn-ske-blue btn-icon-left btn-icon-search" autocomplete="off" ><span>시추주상도</span></a>
<script>
function showSichudan() {
var codes = getSelectHoleInfo();
if (codes == "") {
alert("시추공을 선택해주세요.");
return;
} else if(codes.indexOf('undefined') != -1 ) {
alert("시추공이 존재하지 않는 사업입니다.");
return;
}
var sichuWin = window.open("/map/sichudanNew.do?code=" + codes, "sichuWin", "scrollbars=no,titlebar=no,width=1024,height=740,left=0,top=0", true);
sichuWin.focus();
}
function showBoreholeLog() {
var codes = getSelectHoleInfo();
if (codes == "") {
alert("시추공을 선택해주세요.");
return;
} else if(codes.indexOf('undefined') != -1 ) {
alert("시추공이 존재하지 않는 사업입니다.");
return;
}
var sichuWin = window.open("/map/borehole-log.do?code=" + codes, "Borehole log", "scrollbars=no,titlebar=no,width=1024,height=740,left=0,top=0", true);
sichuWin.focus();
}
</script>
</li>
<li>
<div class="form-inline calendar-group">
<div class="input-group">
@ -984,7 +1018,7 @@
</div>
<!-- 팝업 끝 -->
<!-- 팝업 시작 -->
<!-- 팝업 시작 -->
<div id="watLevInfoPopup" class="kc-popup kc-popup-btn-group" style="display:none;"> <!--하단 버튼그룹 있는 팝업은 "kc-popup-btn-group" class 추가 -->
<div class="k-popup-edit-form button-group-display">
<div class="k-edit-form-container">
@ -1005,6 +1039,7 @@
</div>
<!-- 팝업 끝 -->
</body>
<script type="text/javascript">

View File

@ -7,59 +7,8 @@
</script>
${vo.idx}, ${vo.writer}
<!-- visual 시작 -->
<div class="main-visual">
<div class="main-visual-inner">
<div class="main-visual-row">
</div>
</div>
</div>
<!-- visual 끝 -->
<!-- 하단 컨텐츠 시작 -->
<div class="row-group bottom-contents-wrap">
<div class="row-group-inner">
<div class="bottom-contents-row">
<div class="bottom-con-panel faq-contents" style="max-width: 50%;">
<div class="main-title-wrap">
<div class="main-title">공지사항</div>
</div>
<div class="faq-box">
<ul>
<c:forEach var="vo" items="${list}">
<li>
<span class="faq-list-division division-blue">${vo.indate}</span>
<a href ='<c:url value="/sgis/portal/noticeView.do"/>?idx=${vo.idx}&objID=${vo.writer}'>
<span class="faq-list-text">${vo.title}</span>
</a>
</li>
</c:forEach>
</ul>
</div>
</div>
<div class="bottom-con-panel service-contents" style="max-width: 50%;">
<div class="main-title-wrap">
<div class="main-title">커뮤니티</div>
</div>
<div class="faq-box">
<ul>
<c:forEach var="Bvo" items="${boardList}">
<li>
<span class="faq-list-division division-blue">${Bvo.indate}</span>
<a href ='<c:url value="/sgis/portal/boardView.do"/>?idx=${Bvo.idx}&objID=${Bvo.writer}'>
<span class="faq-list-text">${Bvo.title}</span>
</a>
</li>
</c:forEach>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- 하단 컨텐츠 끝 -->
${vo.idx} ${vo.writer}
<img src="../../com/img/common/main/temp_dummy_main.png" style="margin: 0px auto;"/>
<!-- 실패 메세지를 출력(modal) -->
<div id="myMessage" class="modal fade" role="dialog" >

View File

@ -0,0 +1,441 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>설문 문항 생성 (관리자)</title>
<style>
body {
font-family: 'Malgun Gothic', '맑은 고딕', Arial, sans-serif;
margin: 20px;
background-color: #f4f4f4;
color: #333;
display: flex;
flex-direction: column; /* 세로 정렬 */
justify-content: center;
align-items: center;
min-height: 100vh;
}
.header-section {
background-color: #fff;
padding: 20px 25px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 100%;
box-sizing: border-box;
margin-bottom: 20px; /* 폼과의 간격 */
}
.container {
background-color: #fff;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 100%;
box-sizing: border-box;
}
h2 {
color: #0056b3;
text-align: center;
margin-bottom: 25px;
font-size: 1.8em;
border-bottom: 2px solid #eee;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 18px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: bold;
color: #555;
}
.form-group input[type="text"],
.form-group input[type="number"],
.form-group select,
.form-group textarea {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
font-size: 1em;
transition: border-color 0.3s ease;
}
.form-group input[type="text"]:focus,
.form-group input[type="number"]:focus,
.form-group select:focus,
.form-group input[type="datetime-local"]:focus,
.form-group textarea:focus {
border-color: #007bff;
outline: none;
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.form-group input[type="checkbox"] {
margin-right: 5px;
}
button {
background-color: #28a745; /* Green for create */
color: white;
padding: 12px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1.1em;
width: 100%;
transition: background-color 0.3s ease, transform 0.2s ease;
margin-top: 10px;
}
button:hover {
background-color: #218838;
transform: translateY(-2px);
}
button:active {
transform: translateY(0);
}
.message {
margin-top: 25px;
padding: 12px;
border-radius: 5px;
text-align: center;
font-weight: bold;
display: none;
word-break: break-all;
}
.message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.back-link {
display: block;
text-align: center;
margin-top: 20px;
color: #007bff;
text-decoration: none;
font-weight: bold;
}
.back-link:hover {
text-decoration: underline;
}
/* 기존 문항 목록 스타일 */
.question-list-section {
margin-top: 30px;
background-color: #fff;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 100%;
box-sizing: border-box;
margin-bottom: 20px;
}
.question-list-section h3 {
color: #0056b3;
margin-bottom: 15px;
border-bottom: 1px dashed #eee;
padding-bottom: 8px;
}
.question-item {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 15px;
margin-bottom: 10px;
background-color: #f9f9f9;
}
.question-item p {
margin: 0 0 5px 0;
font-weight: bold;
color: #444;
}
.question-item small {
color: #777;
display: block;
margin-bottom: 8px;
}
.question-options {
margin-top: 10px;
padding-left: 20px;
border-left: 2px solid #ddd;
}
.question-options li {
margin-bottom: 5px;
color: #666;
}
.question-options li:last-child {
margin-bottom: 0;
}
.no-questions {
text-align: center;
color: #888;
padding: 20px;
}
</style>
</head>
<body>
<div class="header-section">
<h2>설문 ID: <span id="displaySurveyId">${surveyId}</span></h2>
<p>이 설문에 대한 문항들을 생성합니다.</p>
<div id="initialMessage" class="message"></div>
</div>
<div class="question-list-section">
<h3>기존 문항 목록</h3>
<div id="existingQuestionsList">
<p class="no-questions">로딩 중...</p>
</div>
</div>
<div class="container">
<h2>새 문항 생성</h2>
<form id="createQuestionForm">
<!-- Hidden input to pass surveyId from URL to form data -->
<input type="hidden" id="surveyId" name="surveyId" value="${surveyId}">
<div class="form-group">
<label for="questionText">문항 내용:</label>
<textarea id="questionText" name="questionText" placeholder="예: 시스템 사용에 만족하십니까?" required></textarea>
</div>
<div class="form-group">
<label for="questionType">문항 유형:</label>
<select id="questionType" name="questionType" required>
<option value="">-- 선택 --</option>
<option value="단답형">단답형</option>
<option value="객관식_단일">객관식 (단일 선택)</option>
<option value="객관식_다중">객관식 (다중 선택)</option>
<option value="주관식">주관식</option>
</select>
</div>
<div class="form-group">
<label for="questionOrder">문항 순서:</label>
<input type="number" id="questionOrder" name="questionOrder" value="1" min="1" required>
</div>
<div class="form-group">
<input type="checkbox" id="isRequired" name="isRequired" checked>
<label for="isRequired">필수 응답</label>
</div>
<button type="submit">문항 생성</button>
</form>
<div id="message" class="message"></div>
<a href="#" class="back-link" onclick="history.back(); return false;">&larr; 이전 페이지로 돌아가기</a>
</div>
<script>
const contextPath = "<%= request.getContextPath() %>";
const surveyId = document.getElementById('surveyId').value;
const initialMessageDiv = document.getElementById('initialMessage');
const existingQuestionsListDiv = document.getElementById('existingQuestionsList');
// Function to fetch and display existing questions
async function fetchAndDisplayQuestions() {
console.log('fetchAndDisplayQuestions called for surveyId:', surveyId); // Debugging
if (!surveyId || surveyId === 'null' || surveyId === '') {
existingQuestionsListDiv.innerHTML = '<p class="no-questions">설문 ID가 유효하지 않아 기존 문항을 불러올 수 없습니다.</p>';
console.log('Invalid surveyId, not fetching questions.'); // Debugging
return;
}
const apiUrl = (typeof contextPath !== 'undefined' && contextPath !== null && contextPath !== '') ?
contextPath + '/api/questions/by-survey/' + surveyId + '/getQuestions.do' :
'/api/questions/by-survey/' + surveyId + '/getQuestions.do';
console.log('Fetching questions from:', apiUrl); // Debugging
try {
const response = await fetch(apiUrl);
console.log('Questions API Response Status:', response.status, response.statusText); // Debugging
if (response.ok) {
const questions = await response.json();
console.log('Fetched Questions (RAW):', JSON.stringify(questions, null, 2)); // Debugging: Raw JSON data
let maxOrder = 0; // 최대 문항 순서를 추적하기 위한 변수
if (questions.length === 0) {
existingQuestionsListDiv.innerHTML = '<p class="no-questions">아직 생성된 문항이 없습니다.</p>';
maxOrder = 0; // 문항이 없으면 최대 순서는 0
} else {
existingQuestionsListDiv.innerHTML = ''; // Clear loading message
for (const question of questions) {
// 현재 문항의 순서가 maxOrder보다 크면 업데이트
if (question.questionOrder > maxOrder) {
maxOrder = question.questionOrder;
}
console.log('Processing question:', JSON.stringify(question, null, 2)); // Debugging: Each question object
const questionItem = document.createElement('div');
questionItem.classList.add('question-item');
// 문항 클릭 시 선택지 생성 페이지로 이동하는 이벤트 리스너 추가
questionItem.style.cursor = 'pointer'; // 클릭 가능한 UI임을 나타내기 위해 커서 스타일 변경
questionItem.addEventListener('click', function() {
const redirectUrl = (typeof contextPath !== 'undefined' && contextPath !== null && contextPath !== '') ?
contextPath + '/admin/survey/question/' + question.questionId + '/createOptionForm.do' :
'/admin/survey/question/' + question.questionId + '/createOptionForm.do';
window.location.href = redirectUrl;
});
let optionsHtml = '';
// questionType이 객관식이고, question.options 배열이 존재하는 경우에만 옵션 처리
if (question.questionType && question.questionType.startsWith('객관식') && question.options && question.options.length > 0) {
optionsHtml = '<ul class="question-options">';
question.options.forEach(option => {
// 옵션 데이터도 안전하게 접근
const optionOrder = option.optionOrder !== null && option.optionOrder !== undefined ? option.optionOrder : '';
const optionText = option.optionText !== null && option.optionText !== undefined ? option.optionText : '';
// 백틱(`) 대신 문자열 연결(+) 사용
optionsHtml += '<li>' + optionOrder + '. ' + optionText + '</li>';
});
optionsHtml += '</ul>';
} else if (question.questionType && question.questionType.startsWith('객관식')) {
// 객관식 타입이지만 옵션이 없는 경우
optionsHtml = '<p class="question-options"><em>(선택지 없음)</em></p>';
}
// 주관식 등 옵션이 필요 없는 경우 optionsHtml은 빈 문자열로 유지됩니다.
// 안전하게 값 접근 및 플레이스홀더 설정
const order = String(question.questionOrder || '[순서 없음]');
const text = String(question.questionText || '[내용 없음]');
const type = String(question.questionType || '[유형 없음]');
// isRequired는 boolean 타입이므로, 명시적으로 '예'/'아니오'로 변환
const required = (question.isRequired === true) ? '예' : '아니오';
console.log('Final values for HTML:', { order, text, type, required }); // Add this line
// 백틱(`) 대신 문자열 연결(+) 사용
questionItem.innerHTML =
'<p>' + order + '. ' + text + '</p>' +
'<small>유형: ' + type + ' | 필수: ' + required + '</small>' +
optionsHtml;
existingQuestionsListDiv.appendChild(questionItem);
console.log('Appended question item:', questionItem.outerHTML); // Debugging: Check generated HTML
}
}
// 모든 문항 처리 후, 다음 문항 순서 설정
// 기존 문항이 없으면 1, 있으면 최대 순서 + 1
document.getElementById('questionOrder').value = maxOrder + 1;
} else {
existingQuestionsListDiv.innerHTML = '<p class="no-questions error">기존 문항을 불러오는데 실패했습니다.</p>';
console.error('Failed to fetch questions. Status:', response.status, response.statusText); // Debugging
// 오류 발생 시에도 기본값은 1로 설정 (혹은 기존 HTML 기본값 유지)
document.getElementById('questionOrder').value = 1;
}
} catch (error) {
console.error('Error fetching questions:', error);
existingQuestionsListDiv.innerHTML = '<p class="no-questions error">네트워크 오류로 문항을 불러올 수 없습니다.</p>';
// 네트워크 오류 발생 시에도 기본값은 1로 설정
document.getElementById('questionOrder').value = 1;
}
}
// --- Event Listener for Form Submission ---
document.getElementById('createQuestionForm').addEventListener('submit', async function(event) {
event.preventDefault(); // 폼의 기본 제출 동작 방지
const messageDiv = document.getElementById('message');
messageDiv.style.display = 'none'; // 이전 메시지 숨기기
messageDiv.classList.remove('success', 'error'); // 이전 스타일 클래스 제거
// 폼 필드에서 데이터 수집
const formData = {
surveyId: surveyId, // Hidden input에서 surveyId 가져오기
questionText: document.getElementById('questionText').value,
questionType: document.getElementById('questionType').value,
questionOrder: parseInt(document.getElementById('questionOrder').value, 10), // 숫자로 파싱
isRequired: document.getElementById('isRequired').checked // 체크박스 값
};
// 유효성 검사 (간단한 예시)
if (!formData.surveyId || formData.surveyId === 'null' || formData.surveyId === '') { // 빈 문자열도 확인
messageDiv.textContent = '설문 ID가 유효하지 않습니다. 이전 페이지에서 설문을 선택해주세요.';
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
return;
}
if (!formData.questionType) {
messageDiv.textContent = '문항 유형을 선택해주세요.';
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
return;
}
// QuestionController의 @RequestMapping("/api/questions")와 @PostMapping("/createQuestion.do")를 결합
const apiUrl = (typeof contextPath !== 'undefined' && contextPath !== null && contextPath !== '') ? contextPath + '/api/questions/createQuestion.do' : '/api/questions/createQuestion.do';
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData) // JSON 문자열로 변환하여 전송
});
if (response.ok) { // HTTP 상태 코드가 200-299 범위인 경우 (성공)
const result = await response.json(); // 응답 본문을 JSON으로 파싱
messageDiv.textContent = '문항이 성공적으로 생성되었습니다! (ID: ' + result.questionId + ')';
messageDiv.classList.add('success');
messageDiv.style.display = 'block';
// 폼 초기화 (surveyId는 유지)
document.getElementById('questionText').value = '';
document.getElementById('questionType').value = '';
document.getElementById('questionOrder').value = parseInt(formData.questionOrder, 10) + 1; // 다음 문항 순서 자동 증가
document.getElementById('isRequired').checked = true;
// 문항 생성 성공 후 기존 문항 목록 새로고침
await fetchAndDisplayQuestions(); // 목록 새로고침
// 문항 유형이 객관식인 경우, 선택지 생성 페이지로 이동
if (formData.questionType.startsWith('객관식')) {
const redirectUrl = (typeof contextPath !== 'undefined' && contextPath !== null && contextPath !== '') ?
contextPath + '/admin/survey/question/' + result.questionId + '/createOptionForm.do' :
'/admin/survey/question/' + result.questionId + '/createOptionForm.do';
window.location.href = redirectUrl; // 페이지 리다이렉트
}
} else { // HTTP 상태 코드가 200-299 범위를 벗어난 경우 (실패)
const errorText = await response.text(); // 오류 메시지를 텍스트로 가져옴
messageDiv.textContent = '문항 생성 실패: ' + (errorText || '알 수 없는 오류');
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
}
} catch (error) {
// 네트워크 오류 또는 fetch 요청 자체의 문제 발생 시
console.error('네트워크 오류:', error);
messageDiv.textContent = '네트워크 오류가 발생했습니다. 다시 시도해주세요.';
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
}
});
// --- Initial Load Logic ---
// 초기 로드 시 surveyId가 없으면 경고 메시지 표시
if (!surveyId || surveyId === 'null' || surveyId === '') {
initialMessageDiv.textContent = '경고: 설문 ID가 없습니다. 문항을 생성할 설문을 먼저 선택해주세요.';
initialMessageDiv.classList.add('error');
initialMessageDiv.style.display = 'block';
existingQuestionsListDiv.innerHTML = '<p class="no-questions">설문 ID가 없어 문항 목록을 불러올 수 없습니다.</p>';
} else {
// surveyId가 유효하면 기존 문항 목록을 불러옵니다.
fetchAndDisplayQuestions();
}
</script>
</body>
</html>

View File

@ -0,0 +1,428 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>문항 선택지 생성 (관리자)</title>
<style>
body {
font-family: 'Malgun Gothic', '맑은 고딕', Arial, sans-serif;
margin: 20px;
background-color: #f4f4f4;
color: #333;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.header-section {
background-color: #fff;
padding: 20px 25px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 100%;
box-sizing: border-box;
margin-bottom: 20px;
}
.container {
background-color: #fff;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 100%;
box-sizing: border-box;
}
h2 {
color: #0056b3;
text-align: center;
margin-bottom: 25px;
font-size: 1.8em;
border-bottom: 2px solid #eee;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 18px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: bold;
color: #555;
}
.form-group input[type="text"],
.form-group input[type="number"],
.form-group select,
.form-group textarea {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
font-size: 1em;
transition: border-color 0.3s ease;
}
.form-group input[type="text"]:focus,
.form-group input[type="number"]:focus,
.form-group select:focus,
.form-group input[type="datetime-local"]:focus,
.form-group textarea:focus {
border-color: #007bff;
outline: none;
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.form-group input[type="checkbox"] {
margin-right: 5px;
}
button {
background-color: #28a745;
color: white;
padding: 12px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1.1em;
width: 100%;
transition: background-color 0.3s ease, transform 0.2s ease;
margin-top: 10px;
}
button:hover {
background-color: #218838;
transform: translateY(-2px);
}
button:active {
transform: translateY(0);
}
.message {
margin-top: 25px;
padding: 12px;
border-radius: 5px;
text-align: center;
font-weight: bold;
display: none;
word-break: break-all;
}
.message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.back-link {
display: block;
text-align: center;
margin-top: 20px;
color: #007bff;
text-decoration: none;
font-weight: bold;
}
.back-link:hover {
text-decoration: underline;
}
/* Existing options list styles */
.option-list-section {
margin-top: 30px;
background-color: #fff;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 100%;
box-sizing: border-box;
margin-bottom: 20px;
}
.option-list-section h3 {
color: #0056b3;
margin-bottom: 15px;
border-bottom: 1px dashed #eee;
padding-bottom: 8px;
}
.option-item {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 15px;
margin-bottom: 10px;
background-color: #f9f9f9;
display: flex;
justify-content: space-between;
align-items: center;
}
.option-item p {
margin: 0;
font-weight: bold;
color: #444;
flex-grow: 1;
}
.option-item small {
color: #777;
display: block;
margin-bottom: 8px;
}
.no-options {
text-align: center;
color: #888;
padding: 20px;
}
.delete-option-btn {
background-color: #dc3545;
color: white;
padding: 5px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.8em;
margin-left: 10px;
transition: background-color 0.3s ease;
width: auto;
}
.delete-option-btn:hover {
background-color: #c82333;
}
</style>
</head>
<body>
<div class="header-section">
<h2>문항 ID: <span id="displayQuestionId"><%= request.getAttribute("questionId") %></span></h2>
<p>이 문항에 대한 선택지들을 생성합니다.</p>
<div id="initialMessage" class="message"></div>
</div>
<div class="option-list-section">
<h3>기존 선택지 목록</h3>
<div id="existingOptionsList">
<p class="no-options">로딩 중...</p>
</div>
</div>
<div class="container">
<h2>새 선택지 생성</h2>
<form id="createOptionForm">
<!-- Hidden input to pass questionId from URL to form data -->
<input type="hidden" id="questionId" name="questionId" value="<%= request.getAttribute("questionId") %>">
<div class="form-group">
<label for="optionText">선택지 내용:</label>
<input type="text" id="optionText" name="optionText" placeholder="예: 매우 만족" required>
</div>
<div class="form-group">
<label for="optionOrder">선택지 순서:</label>
<input type="number" id="optionOrder" name="optionOrder" value="1" min="1" required>
</div>
<button type="submit">선택지 생성</button>
</form>
<div id="message" class="message"></div>
<a href="#" class="back-link" onclick="history.back(); return false;">&larr; 이전 페이지로 돌아가기</a>
</div>
<script>
const contextPath = "<%= request.getContextPath() %>";
const questionId = document.getElementById('questionId').value;
const initialMessageDiv = document.getElementById('initialMessage');
const existingOptionsListDiv = document.getElementById('existingOptionsList');
const messageDiv = document.getElementById('message'); // 메시지 div 전역 변수로 선언
// Function to fetch and display existing options
async function fetchAndDisplayOptions() {
console.log('fetchAndDisplayOptions called for questionId:', questionId);
if (!questionId || questionId === 'null' || questionId === '') {
existingOptionsListDiv.innerHTML = '<p class="no-options">문항 ID가 유효하지 않아 기존 선택지를 불러올 수 없습니다.</p>';
console.log('Invalid questionId, not fetching options.');
return;
}
const apiUrl = (typeof contextPath !== 'undefined' && contextPath !== null && contextPath !== '') ?
contextPath + '/api/question-options/by-question/' + questionId + '/getOptions.do' :
'/api/question-options/by-question/' + questionId + '/getOptions.do';
console.log('Fetching options from:', apiUrl);
try {
const response = await fetch(apiUrl);
console.log('Options API Response Status:', response.status, response.statusText);
if (response.ok) {
const options = await response.json();
console.log('Fetched Options (RAW):', JSON.stringify(options, null, 2));
let maxOrder = 0;
if (options.length === 0) {
existingOptionsListDiv.innerHTML = '<p class="no-options">아직 생성된 선택지가 없습니다.</p>';
maxOrder = 0;
} else {
existingOptionsListDiv.innerHTML = '';
for (const option of options) {
const currentOrder = parseInt(option.optionOrder, 10);
if (!isNaN(currentOrder) && currentOrder > maxOrder) {
maxOrder = currentOrder;
}
console.log('Processing option:', JSON.stringify(option, null, 2));
const optionItem = document.createElement('div');
optionItem.classList.add('option-item');
const order = String(option.optionOrder || '[순서 없음]');
const text = String(option.optionText || '[내용 없음]');
const optionIdValue = String(option.optionId || '');
console.log('Final values for HTML:', { order, text, optionIdValue });
optionItem.innerHTML =
'<p>' + order + '. ' + text + '</p>' +
'<button type="button" class="delete-option-btn" data-option-id="' + optionIdValue + '">삭제</button>';
existingOptionsListDiv.appendChild(optionItem);
console.log('Appended option item:', optionItem.outerHTML);
}
// Add event listeners for delete buttons after all options are appended
document.querySelectorAll('.delete-option-btn').forEach(button => {
button.addEventListener('click', async function() {
const optionIdToDelete = this.dataset.optionId;
console.log('Delete button clicked for optionId:', optionIdToDelete);
if (!confirm('정말로 이 선택지를 삭제하시겠습니까?')) {
return;
}
// DELETE 요청 대신 POST를 사용하며, URL 경로에 delete 액션을 명시
const deleteApiUrl = (typeof contextPath !== 'undefined' && contextPath !== null && contextPath !== '') ?
contextPath + '/api/question-options/' + optionIdToDelete + '/deleteOption.do' :
'/api/question-options/' + optionIdToDelete + '/deleteOption.do';
console.log('Attempting to delete option from:', deleteApiUrl); // Debugging
try {
const deleteResponse = await fetch(deleteApiUrl, {
method: 'POST', // POST로 변경
headers: {
'Content-Type': 'application/json'
},
// POST로 삭제 시, PathVariable로 ID를 보내므로 body는 필요 없음
// body: JSON.stringify({ optionId: optionIdToDelete }) // 서버가 @RequestBody로 ID를 받는다면 이 줄 활성화
});
console.log('Delete API Response Status:', deleteResponse.status, deleteResponse.statusText); // Debugging
if (deleteResponse.ok) { // HTTP 상태 코드가 200-299 범위인 경우 (성공)
console.log('Option deleted successfully:', optionIdToDelete);
messageDiv.textContent = '선택지가 성공적으로 삭제되었습니다.';
messageDiv.classList.add('success');
messageDiv.style.display = 'block';
await fetchAndDisplayOptions(); // 목록 새로고침
} else { // HTTP 상태 코드가 200-299 범위를 벗어난 경우 (실패)
const errorText = await deleteResponse.text();
console.error('Failed to delete option. Status:', deleteResponse.status, 'Error:', errorText);
messageDiv.textContent = '선택지 삭제 실패: ' + (errorText || '알 수 없는 오류');
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
}
} catch (error) {
console.error('Error deleting option:', error);
messageDiv.textContent = '네트워크 오류로 선택지를 삭제할 수 없습니다.';
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
}
});
});
}
document.getElementById('optionOrder').value = maxOrder + 1;
} else {
existingOptionsListDiv.innerHTML = '<p class="no-options error">기존 선택지를 불러오는데 실패했습니다.</p>';
console.error('Failed to fetch options. Status:', response.status, response.statusText);
document.getElementById('optionOrder').value = 1;
}
} catch (error) {
console.error('Error fetching options:', error);
existingOptionsListDiv.innerHTML = '<p class="no-options error">네트워크 오류로 선택지를 불러올 수 없습니다.</p>';
document.getElementById('optionOrder').value = 1;
}
}
// --- Event Listener for Form Submission ---
document.getElementById('createOptionForm').addEventListener('submit', async function(event) {
event.preventDefault();
const messageDiv = document.getElementById('message');
messageDiv.style.display = 'none';
messageDiv.classList.remove('success', 'error');
const formData = {
questionId: questionId,
optionText: document.getElementById('optionText').value,
optionOrder: parseInt(document.getElementById('optionOrder').value, 10)
};
if (!formData.questionId || formData.questionId === 'null' || formData.questionId === '') {
messageDiv.textContent = '문항 ID가 유효하지 않습니다. 이전 페이지에서 문항을 선택해주세요.';
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
return;
}
if (!formData.optionText.trim()) {
messageDiv.textContent = '선택지 내용을 입력해주세요.';
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
return;
}
const apiUrl = (typeof contextPath !== 'undefined' && contextPath !== null && contextPath !== '') ? contextPath + '/api/question-options/createQuestionOption.do' : '/api/question-options/createQuestionOption.do';
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
if (response.ok) {
const result = await response.json();
messageDiv.textContent = '선택지가 성공적으로 생성되었습니다! (ID: ' + result.optionId + ')';
messageDiv.classList.add('success');
messageDiv.style.display = 'block';
document.getElementById('optionText').value = '';
document.getElementById('optionOrder').value = parseInt(formData.optionOrder, 10) + 1;
await fetchAndDisplayOptions();
} else {
const errorText = await response.text();
messageDiv.textContent = '선택지 생성 실패: ' + (errorText || '알 수 없는 오류');
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
}
} catch (error) {
console.error('네트워크 오류:', error);
messageDiv.textContent = '네트워크 오류가 발생했습니다. 다시 시도해주세요.';
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
}
});
// --- Initial Load Logic ---
if (!questionId || questionId === 'null' || questionId === '') {
initialMessageDiv.textContent = '경고: 문항 ID가 없습니다. 선택지를 생성할 문항을 먼저 선택해주세요.';
initialMessageDiv.classList.add('error');
initialMessageDiv.style.display = 'block';
existingOptionsListDiv.innerHTML = '<p class="no-options">문항 ID가 없어 선택지 목록을 불러올 수 없습니다.</p>';
} else {
fetchAndDisplayOptions();
}
</script>
</body>
</html>

View File

@ -0,0 +1,200 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>설문 생성 (관리자)</title>
<style>
body {
font-family: 'Malgun Gothic', '맑은 고딕', Arial, sans-serif;
margin: 20px;
background-color: #f4f4f4;
color: #333;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background-color: #fff;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 100%;
box-sizing: border-box;
}
h2 {
color: #0056b3;
text-align: center;
margin-bottom: 25px;
font-size: 1.8em;
border-bottom: 2px solid #eee;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 18px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: bold;
color: #555;
}
.form-group input[type="text"],
.form-group input[type="datetime-local"],
.form-group textarea {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
font-size: 1em;
transition: border-color 0.3s ease;
}
.form-group input[type="text"]:focus,
.form-group input[type="datetime-local"]:focus,
.form-group textarea:focus {
border-color: #007bff;
outline: none;
}
.form-group textarea {
resize: vertical;
min-height: 100px;
}
button {
background-color: #007bff;
color: white;
padding: 12px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1.1em;
width: 100%;
transition: background-color 0.3s ease, transform 0.2s ease;
margin-top: 10px;
}
button:hover {
background-color: #0056b3;
transform: translateY(-2px);
}
button:active {
transform: translateY(0);
}
.message {
margin-top: 25px;
padding: 12px;
border-radius: 5px;
text-align: center;
font-weight: bold;
display: none;
word-break: break-all;
}
.message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
</style>
</head>
<body>
<div class="container">
<h2>새 설문 생성</h2>
<form id="createSurveyForm">
<div class="form-group">
<label for="surveyTitle">설문 제목:</label>
<input type="text" id="surveyTitle" name="surveyTitle" placeholder="예: 2025년 시스템 만족도 설문" value="2025년 시스템 사용자 경험 및 만족도 평가 설문" required>
</div>
<div class="form-group">
<label for="surveyDescription">설문 설명:</label>
<textarea id="surveyDescription" name="surveyDescription" placeholder="설문에 대한 자세한 설명을 입력하세요.">안녕하십니까?
저희는 사용자 여러분의 소중한 의견을 경청하고, 현재 사용하고 계신 시스템의 만족도를 면밀히 파악하여 더 나은 서비스와 환경을 제공하고자 본 설문조사를 실시하게 되었습니다.
본 설문은 2025년 한 해 동안 시스템을 이용하면서 경험하셨던 전반적인 만족도, 사용 편의성, 기능 유용성, 안정성 등에 대한 사용자님의 솔직한 평가를 듣기 위함입니다. 여러분의 피드백은 시스템 개선의 중요한 밑거름이 될 것이며, 향후 시스템 개발 및 운영 방향 설정에 큰 도움이 될 것입니다.
설문에 참여해주신 모든 분들께 진심으로 감사드립니다. 설문 응답은 통계적인 목적으로만 활용되며, 개인 정보는 철저히 보호됩니다.
참여 대상: 2025년 시스템 사용자 전체소요 시간: 약 5~10분
여러분의 적극적인 참여를 부탁드립니다.</textarea>
</div>
<div class="form-group">
<label for="startDate">시작일시:</label>
<input type="datetime-local" id="startDate" name="startDate">
</div>
<div class="form-group">
<label for="endDate">종료일시:</label>
<input type="datetime-local" id="endDate" name="endDate">
</div>
<button type="submit">설문 생성</button>
</form>
<div id="message" class="message"></div>
</div>
<script>
document.getElementById('createSurveyForm').addEventListener('submit', async function(event) {
event.preventDefault(); // 폼의 기본 제출 동작 방지
const messageDiv = document.getElementById('message');
messageDiv.style.display = 'none'; // 이전 메시지 숨기기
messageDiv.classList.remove('success', 'error'); // 이전 스타일 클래스 제거
// 폼 필드에서 데이터 수집
const formData = {
surveyTitle: document.getElementById('surveyTitle').value,
surveyDescription: document.getElementById('surveyDescription').value,
// datetime-local 입력은 값이 없으면 빈 문자열이 되므로, null로 변환
startDate: document.getElementById('startDate').value ? document.getElementById('startDate').value + ':00' : null, // 초까지 포함하도록 포맷팅
endDate: document.getElementById('endDate').value ? document.getElementById('endDate').value + ':00' : null // 초까지 포함하도록 포맷팅
};
// 컨텍스트 경로를 동적으로 가져오는 방법 (JSP 파일일 경우)
const contextPath = "<%= request.getContextPath() %>"; // JSP일 경우에만 작동
// SurveyController의 @RequestMapping("/api/surveys")와 @PostMapping("/createSurvey.do")를 결합
const apiUrl = (typeof contextPath !== 'undefined' && contextPath !== null && contextPath !== '') ? contextPath + '/api/surveys/createSurvey.do' : '/api/surveys/createSurvey.do';
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData) // JSON 문자열로 변환하여 전송
});
if (response.ok) { // HTTP 상태 코드가 200-299 범위인 경우 (성공)
const result = await response.json(); // 응답 본문을 JSON으로 파싱
messageDiv.textContent = '설문이 성공적으로 생성되었습니다! (ID: ' + result.surveyId + ')';
messageDiv.classList.add('success');
messageDiv.style.display = 'block';
this.reset(); // 폼 필드 초기화
// 설문 생성 성공 후 createQuestion.jsp로 이동
// AdminSurveyPageController의 /admin/survey/{surveyId}/createQuestionForm.do 경로로 이동
const redirectUrl = (typeof contextPath !== 'undefined' && contextPath !== null && contextPath !== '') ?
contextPath + '/admin/survey/' + result.surveyId + '/createQuestionForm.do' :
'/admin/survey/' + result.surveyId + '/createQuestionForm.do';
window.location.href = redirectUrl; // 페이지 리다이렉트
} else { // HTTP 상태 코드가 200-299 범위를 벗어난 경우 (실패)
const errorText = await response.text(); // 오류 메시지를 텍스트로 가져옴
messageDiv.textContent = '설문 생성 실패: ' + (errorText || '알 수 없는 오류');
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
}
} catch (error) {
// 네트워크 오류 또는 fetch 요청 자체의 문제 발생 시
console.error('네트워크 오류:', error);
messageDiv.textContent = '네트워크 오류가 발생했습니다. 다시 시도해주세요.';
messageDiv.classList.add('error');
messageDiv.style.display = 'block';
}
});
</script>
</body>
</html>

View File

@ -20,6 +20,9 @@ function goUserInfo(){
function goApiInfo(){
location.href = '<c:url value="/sgis/adm/selectApiList.do"/>';
}
function goSurveySystem(){
location.href = '<c:url value="/admin/survey/createSurveyForm.do"/>';
}
</script>
<!-- 웹접근성 본문 포커스 시작 -->
<ul id="skipToContent">
@ -90,6 +93,9 @@ function goApiInfo(){
<li class="classic-menu-dropdown">
<a href="javascript:goApiInfo();">API 관리</a>
</li>
<li class="classic-menu-dropdown">
<a href="javascript:goSurveySystem();">설문 관리</a>
</li>
</ul>
</div>

View File

@ -330,7 +330,7 @@
<script>
$(document).ready(function () {
$("#inputYmdTo").kendoDatePicker({
value : new Date(),
value : new Date("1990-01-01T00:00:00.000+09:00"),
culture: "ko-KR",
format : "yyyy-MM-dd"
});

View File

@ -139,15 +139,18 @@ function goAdminPage(){
<li class="mega-menu-dropdown">
<a href='<c:url value="board-notice.do"/>'> 공지사항 </a>
</li>
<li class="mega-menu-dropdown">
<a href='<c:url value="board-community.do"/>'> 커뮤니티 </a>
</li>
<li class="mega-menu-dropdown">
<a href='<c:url value="board-qna.do"/>'> Q&A </a>
</li>
<li class="mega-menu-dropdown">
<a href='<c:url value="board-bug.do"/>'> 장애신고 </a>
</li>
<c:if test="${fn:length(sessionVO.sUserId) != 0}">
<li class="mega-menu-dropdown">
<a href='<c:url value="board-community.do"/>'> 자료실 </a>
</li>
<li class="mega-menu-dropdown">
<a href='<c:url value="board-qna.do"/>'> Q&A </a>
</li>
<li class="mega-menu-dropdown">
<a href='<c:url value="board-bug.do"/>'> 장애신고 </a>
</li>
</c:if>
</ul>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -1806,7 +1806,12 @@ footer .newsletter input { color: #6f6f6f; letter-spacing: normal; }
.table .input {margin: 0px;}
.table textarea {display: block;}
.table #dropZone { height: auto !important; position: relative; }
.table #dropZone p { padding: 10px; }
.table #fileInput { position: absolute; top:0; left: 0; width: 100%; height: 100%; opacity: 0; }
.table #dropZone.dragover { background-color: #e9f7fe; border-color: #17a2b8; }
.table #dropZone #fileList { margin-bottom: 0}
.table .remove-btn { background: url(../img/common/icon/ico_btn_delete_s.png) no-repeat 10% 50%; display: inline-block; padding: 6px 0px 0px 23px; width: 60px; height: 30px; background-color: #a5d0e0; color: #fff; vertical-align: middle; border-radius: 6px;}
.table-content-item {min-height: 200px;}
.table-top-btn-group {position: relative; width: 100%; display: table; box-sizing: border-box; text-align: right; padding-bottom: 20px;}
.table-bottom-btn-group {position: relative; width: 100%; display: table; box-sizing: border-box; text-align: right; padding-top: 20px;}

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Some files were not shown because too many files have changed in this diff Show More