본문 바로가기
프로그램/자바

첨부파일 다운로드 & 업로드

by cbwstar 2023. 6. 7.
728x90
반응형

/* 자바 첨부파일 다운로드 & 업로드 */

FileManagerController.java

package logfarm.admin.file.controller;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
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.multipart.MultipartFile;

import egovframework.com.cmm.service.EgovProperties;
import logfarm.admin.file.util.FileItem;
import logfarm.admin.file.util.FileUploadUtil;
import net.sf.json.JSONObject;


@Controller
@RequestMapping("/api")
public class FileManagerController {
	private static final Logger logger = LoggerFactory.getLogger(FileManagerController.class);
	
	private static final String SP = File.separator;
	
	@RequestMapping("/fileUpload.json")
	@ResponseBody
	public Object uploadFile( @RequestParam("file") MultipartFile multipartFile
     ) {
        try {
            FileItem resultFileItem =  (FileItem) FileUploadUtil.saveFileInDir(multipartFile);
            
            logger.debug("resultFileItem : " + resultFileItem);
            JSONObject jObj = new JSONObject();
            
            jObj.put("success", true);
            jObj.put("message", "파일업로드 성공");
            jObj.put("payload", resultFileItem);
            
            System.out.println("jObj : " + jObj);
            return jObj;
         } catch (IOException ioe) {
        	 
        	 JSONObject jObj = new JSONObject();
             
             jObj.put("success", false);
             jObj.put("message", "파일업로드 실패");
             return jObj;
         }
     }
	

	/**
	 *
	 * <pre>
	 * @desc  axios 파일 다운로드
	 * @param
	 * @return
	 * @return
	 * @throws
	 * </pre>
	 */
	@RequestMapping(value = "/downloadAxiosFiles.json" )
	public void downloadAxiosFiles(HttpServletRequest request, HttpServletResponse response) {

		logger.debug("-------------- 파일다운로드 -------------");

		//String getPath = FileUploadUtil.removedFileTraversal(request.getParameter("filepath"));
		String getName = FileUploadUtil.removedFileTraversal(request.getParameter("filename"));
		String getOrginFileName = FileUploadUtil.removedFileTraversal(request.getParameter("originFileName"));

		//외부 입력값에 CRLF를 제거한다.
		getName = getName.replaceAll("\n","");
		getName = getName.replaceAll("\r","");

		logger.debug("getOrginFileName : " + getOrginFileName);
		logger.debug("getName : " + getName);
		//logger.debug("getPath : " + getPath);

		//String filePath = EgovProperties.getProperty("Globals.fileStorePath").trim() + getPath +SP+ getOrginFileName;
		String filePath = EgovProperties.getProperty("Globals.fileStorePath").trim() + getOrginFileName;

		logger.debug("filePath : " + filePath);
		
		Map<String, Object> reMap = new HashMap<>();
	    OutputStream os = null;
	    InputStream is = null;
		BufferedInputStream bis = null;
		BufferedOutputStream bos = null;
		ServletOutputStream sout = null;
		File f = new File(filePath);

	   try {
			   response.reset();
			   response.setCharacterEncoding("utf-8");
			   response.setContentType("application/octet-stream");
			   response.setHeader("Content-Disposition","attachment;filename=" + new String(getName.getBytes("UTF-8"), "ISO8859-1"));


				is = new FileInputStream(f);
				if (is == null) {
					reMap.put("msg", "파일이 존재 하지 않습니다.");
	       		}
				sout = response.getOutputStream();

				bis = new BufferedInputStream(is);
				bos = new BufferedOutputStream(sout);
				byte[] buff = new byte[2048];
				int bytesRead;
				while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
				   bos.write(buff, 0, bytesRead);
				}
				bos.flush();
				bos.close();
				bis.close();
				is.close();
		        sout.close();
			//	os.close();
	   } catch (IOException e) {
	 	     reMap.put("msg", "처리중 오류가 발생하였습니다.");
	   } catch (Exception e) {
		   reMap.put("msg", "처리중 오류가 발생하였습니다.");
	   } finally
	   {
		   if (bis != null)
		   {
			   try
			   {
				   bis.close();
			   } catch (RuntimeException e) {
				   f = null;
			   }
			   catch (Exception e)
			   {
				   f = null;
			   }
		   }
		   if (bos != null)
		   {
			   try
			   {
				   bos.close();
			   } catch (RuntimeException e) {
				   f = null;
			   }
			   catch (Exception e)
			   {
				   f = null;
			   }
		   }
		   if (is != null)
		   {
			   try
			   {
				   is.close();
			   } catch (RuntimeException e) {
				   f = null;
			   }
			   catch (Exception e)
			   {
				   f = null;
			   }
		   }
		   if (sout != null)
		   {
			   try
			   {
				   sout.close();
			   } catch (RuntimeException e) {
				   f = null;
			   }
			   catch (Exception e)
			   {
				   f = null;
			   }
		   }
		   if (os != null)
		   {
			   try
			   {
				   os.close();
			   } catch (RuntimeException e) {
				   f = null;
			   }
			   catch (Exception e)
			   {
				   f = null;
			   }
		   }
	   }

	}

}

FileItem.java

package logfarm.admin.file.util;

public class FileItem {
	public FileItem() {}
	 
    public FileItem(String fileName, String finalFileName, long size, String mimeType, String extention, String path) {
        this.fileName = fileName;
        this.finalFileName = finalFileName;
        this.size=size;
        this.mimeType = mimeType;
        this.extention = extention;
        this.path = path;
        this.status = true;
     }

    private String fileName;
 
    private long size;
 
    private String mimeType;
 
    private String finalFileName;
 
    private String extention;
    
    private String path;

    private Boolean status;

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public long getSize() {
		return size;
	}

	public void setSize(long size) {
		this.size = size;
	}

	public String getMimeType() {
		return mimeType;
	}

	public void setMimeType(String mimeType) {
		this.mimeType = mimeType;
	}

	public String getFinalFileName() {
		return finalFileName;
	}

	public void setFinalFileName(String finalFileName) {
		this.finalFileName = finalFileName;
	}

	public String getExtention() {
		return extention;
	}

	public void setExtention(String extention) {
		this.extention = extention;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}

	public Boolean getStatus() {
		return status;
	}

	public void setStatus(Boolean status) {
		this.status = status;
	}

	@Override
	public String toString() {
		return "FileItem [fileName=" + fileName + ", size=" + size + ", mimeType=" + mimeType + ", finalFileName="
				+ finalFileName + ", extention=" + extention + ", path=" + path + ", status=" + status + "]";
	}
    
    


    
}

FilesUiResponse.java

package logfarm.admin.file.util;

public class FilesUiResponse {
	Boolean success;
    String message;
    Object payload;
    
    public FilesUiResponse() { }
	 
    public FilesUiResponse(Boolean success,String message, Object payload) {
        this.success = success;
        this.message = message;
        this.payload=payload;
     }
    
}

FileUploadUtil.java

package logfarm.admin.file.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.UUID;

import org.springframework.web.multipart.MultipartFile;

import egovframework.com.cmm.service.EgovProperties;

public class FileUploadUtil {
	public static String UPLOAD_PATH = EgovProperties.getProperty("Globals.fileStorePath").trim(); 
				 
    public static FileItem saveFileInDir(MultipartFile multipartFile) throws IOException {
        Path uploadDirectory = Paths.get(UPLOAD_PATH);
 
        String fileId = createUUID();
        String fileName = multipartFile.getOriginalFilename();
        
        /* 파일확장자 가져오기 */
        int pos = fileName.lastIndexOf(".");
        String extention = fileName.substring(pos+1);
 
        String finalFileName = createUUIDfileName(extention);
        String mimeType = multipartFile.getContentType();
 
        //디렉토리 존재여부
        File destination = new File(UPLOAD_PATH);
        
        if( destination.exists() == false) {
        	boolean mkdirs = destination.mkdirs();
	            	
        }

        long size = multipartFile.getSize();
 
 
        System.out.println("fileName: " + fileName);
        System.out.println("newFIleName: " + finalFileName);
        System.out.println("extention: " + extention);
        System.out.println("size: " + size);
 
        try (InputStream inputStream = multipartFile.getInputStream()) {
            Path filePath = uploadDirectory.resolve(finalFileName);
 
            Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING);
 
            FileItem finalFileItem = new FileItem(
            		fileName,
                    finalFileName,
                    size,
                    mimeType,
                    extention,
                    UPLOAD_PATH
             );
 
            return finalFileItem;
         } catch (IOException ioe) {
            throw new IOException("Error on saving uploaded file: " + fileName, ioe);
         }
     }
    public static String createUUID() {
        UUID uuid = UUID.randomUUID();
        String fileId = uuid.toString();
        return fileId;
     }
 
    public static String createUUIDfileName(String extention) {
        return createUUID() + "." + extention;
     }
    
	/**
     * 파일특수문자 변환
     * @return
     */
     public static String removedFileTraversal(String fileName) {
    	   	 
         if(fileName == null) {
             return null;
         }
         fileName = fileName.replaceAll ("\\.\\./","");
         fileName = fileName.replaceAll ("\\\\","");
         fileName = fileName.replaceAll ("&","");
        
         return fileName;
     }
}

/* 리액트 화면에서 첨부파일 업로드 다운로드 호출 */

/* eslint-disable import/no-extraneous-dependencies */
import React, { useRef, useCallback, useEffect, useMemo } from 'react';
import { styled, createTheme, ThemeProvider } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';

import { Dropzone, FileMosaic, FullScreen, ImagePreview, VideoPreview } from '@files-ui/react';

import dayjs from 'dayjs';
import { DemoContainer } from '@mui/x-date-pickers/internals/demo';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { DateField } from '@mui/x-date-pickers/DateField';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { MobileDatePicker } from '@mui/x-date-pickers/MobileDatePicker';
import Paper from '@mui/material/Paper';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TablePagination from '@mui/material/TablePagination';
import TableRow from '@mui/material/TableRow';
import Button from '@mui/material/Button';
import InputBase from '@mui/material/InputBase';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import RefreshIcon from '@mui/icons-material/Refresh';
import MenuIcon from '@mui/icons-material/Menu';
import SearchIcon from '@mui/icons-material/Search';
import DirectionsIcon from '@mui/icons-material/Directions';
import TextField from '@mui/material/TextField';
import { outlinedInputClasses } from '@mui/material/OutlinedInput';
import CloseIcon from '@mui/icons-material/Close';
import Slide from '@mui/material/Slide';
import Dialog from '@mui/material/Dialog';
import ListItemText from '@mui/material/ListItemText';
import ListItem from '@mui/material/ListItem';
import List from '@mui/material/List';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';

import { FileUploader } from 'react-drag-drop-files';

import Box from '@mui/material/Box';
import FormControl from '@mui/material/FormControl';
import Input from '@mui/material/Input';
import InputLabel from '@mui/material/InputLabel';
import InputAdornment from '@mui/material/InputAdornment';
import FormHelperText from '@mui/material/FormHelperText';

import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormLabel from '@mui/material/FormLabel';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import SaveIcon from '@mui/icons-material/Save';

import { Editor, Viewer } from '@toast-ui/react-editor';
import '@toast-ui/editor/dist/toastui-editor.css';
import colorSyntax from '@toast-ui/editor-plugin-color-syntax';
import 'tui-color-picker/dist/tui-color-picker.css';
import '@toast-ui/editor-plugin-color-syntax/dist/toastui-editor-plugin-color-syntax.css';
import '@toast-ui/editor/dist/i18n/ko-kr';

// editor viewer
import '@toast-ui/editor/dist/toastui-editor-viewer.css';

import Alert from '@mui/material/Alert';
import Stack from '@mui/material/Stack';
// eslint-disable-next-line import/no-extraneous-dependencies
import { confirmAlert } from 'react-confirm-alert'; // Import
// eslint-disable-next-line import/no-extraneous-dependencies
import '../css/react-confirm-alert.css'; // Import css

// eslint-disable-next-line import/no-cycle
import { Header } from '../components';
import { useStateContext } from '../contexts/ContextProvider';

// eslint-disable-next-line import/no-named-as-default
import axi from '../comjs/axi';
/* 색상 테마 정의 */
const themes = createTheme({
  status: {
    danger: '#e53e3e',
  },
  palette: {
    primary: {
      main: '#0971f1',
      darker: '#053e85',
    },
    neutral: {
      // main: '#64748B',
      main: '#0288d1',
      contrastText: '#fff',
    },
  },
});

const Item = styled(Paper)(({ theme }) => ({
  backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
  ...theme.typography.body2,
  padding: theme.spacing(1),
  textAlign: 'center',
  color: theme.palette.text.secondary,
}));

const fileTypes = ['JPG', 'PNG', 'GIF', 'ZIP', 'XLSX'];

const { body } = document;

const html = document.documentElement;

const height = Math.max(
  body.scrollHeight,
  body.offsetHeight,

  html.clientHeight,
  html.scrollHeight,
  html.offsetHeight,
);

const Transition = React.forwardRef((props, ref) => (
  // eslint-disable-next-line react/jsx-props-no-spreading
  <Slide direction="up" ref={ref} {...props} />
));

const columns = [
  { id: 'bbscttNo', label: '게시글번호', minWidth: 100, width: 100, align: 'center' },
  { id: 'bbscttSj', label: '제목', minWidth: 100, width: 200 },
  { id: 'bbscttCn', label: '내용', minWidth: 100, width: 250 },
  { id: 'nttInqireCo', label: '조회수', minWidth: 100, width: 100, align: 'right' },
  { id: 'cnt', label: '첨부파일', minWidth: 100, width: 100, align: 'right' },
  { id: 'registDt', label: '등록일자', minWidth: 100, width: 120, align: 'center' },
  { id: 'manage', label: '관리', minWidth: 100, width: 120, align: 'center' },
];

/* 첨부파일 리스트 */
const fileColumns = [
  { id: 'seqNo', label: '순번', minWidth: 100, width: 100, align: 'center' },
  { id: 'fileName', label: '파일명', minWidth: 150, width: 200, align: 'left' },
  { id: 'size', label: '사이즈', minWidth: 100, width: 150, align: 'right', format: (value) => `${value.toLocaleString('en-US')} Byte` },
  { id: 'manage', label: '관리', minWidth: 100, width: 200, align: 'center' },
];
// eslint-disable-next-line prefer-const
let sendSuccessFiles = []; // 전송성공한 첨부파일 리스트

/* html 태그 제거 */
const fnRemoveHtml = (text) => {
  let tagRemove = text.replace(/<br\/>/gi, '\n');
  tagRemove = tagRemove.replace(/<(\/)?([a-zA-Z]*)(\s[a-zA-Z]*=[^>]*)?(\s)*(\/)?>/gi, '');
  return tagRemove;
};

const currentDate = new Date(); // 현재 날짜 및 시간
const nowDate = new Date(); // 현재 날짜 및 시간
const sixMonthDate = new Date(nowDate.setMonth(nowDate.getMonth() - 6)); // 6개월전

export default function BbsReg() {
  const [open, setOpen] = React.useState(false);
  const [mode, setMode] = React.useState('I');
  const { currentColor, activeMenu, setActiveMenu, screenSize } = useStateContext();
  /* editoe view */
  const [contents, setContents] = React.useState(null);
  const [files, setFiles] = React.useState([]);

  const [extFiles, setExtFiles] = React.useState([]);
  const [imageSrc, setImageSrc] = React.useState(undefined);
  const [videoSrc, setVideoSrc] = React.useState(undefined);

  const updateFiles = (incommingFiles) => {
    // do something with the files
    console.log('incommingFiles 전체삭제 업로드', incommingFiles.length);
    setExtFiles(incommingFiles);
    /* 전체삭제 */
    if (incommingFiles.length === 0) {
      sendSuccessFiles = [];
    }
    // console.log('extFiles', extFiles);
    // even your own upload implementation
  };

  const onDelete = (id) => {
    console.log('onDelete', id);
    setExtFiles(extFiles.filter((x) => x.id !== id));

    sendSuccessFiles = sendSuccessFiles.filter((x) => x.id !== id);
    console.log('sendSuccessFiles', sendSuccessFiles);
  };

  const handleSee = (imageSource) => {
    setImageSrc(imageSource);
  };
  const handleWatch = (videoSource) => {
    setVideoSrc(videoSource);
  };
  const handleStart = (filesToUpload) => {
    console.log('업로드시작', filesToUpload);
  };
  const handleFinish = (uploadedFiles) => {
    console.log('업로드 성공', uploadedFiles);

    const successResult = uploadedFiles.filter((sf) => sf.uploadStatus === 'success');

    console.log('successResult:', successResult);

    // eslint-disable-next-line no-plusplus
    for (let i = 0; i < successResult.length; i++) {
      console.log('성공한것만 배열 :', successResult[i]);
      const returnObj = {};
      returnObj.fileName = successResult[i].name;
      returnObj.size = successResult[i].size;
      returnObj.id = successResult[i].id;
      returnObj.finalFileName = successResult[i].serverResponse.payload.finalFileName;
      returnObj.extention = successResult[i].serverResponse.payload.extention;
      returnObj.mimeType = successResult[i].serverResponse.payload.mimeType;
      returnObj.path = successResult[i].serverResponse.payload.path;

      sendSuccessFiles.push(returnObj);
    }

    console.log('업로드 성공 한것만 최종', sendSuccessFiles);
  };
  const handleAbort = (id) => {
    setExtFiles(
      extFiles.map((ef) => {
        if (ef.id === id) {
          return { ...ef, uploadStatus: 'aborted' };
        }
        return { ...ef };
      }),
    );
  };
  const handleCancel = (id) => {
    setExtFiles(
      extFiles.map((ef) => {
        if (ef.id === id) {
          return { ...ef, uploadStatus: undefined };
        }
        return { ...ef };
      }),
    );
  };

  /* 공지사항 팝업창 닫는다 */
  const handleClose = () => {
    setOpen(false);
  };

  /* 페이치 처리 초기값 설정 */
  const [page, setPage] = React.useState(0);
  const [rowsPerPage, setRowsPerPage] = React.useState(10);
  /* 화면 높이 설정 */
  const [heightSize, setHeightSize] = React.useState(height - 350);

  /* 공지사항 데이타 객체 */
  const [rows, setRows] = React.useState([]);

  /* 첨부파일 데이타 객체 */
  const [fileRows, setFileRows] = React.useState([]);

  /* 공지사항 항목 초기화 */
  const [tbBbscttM, setTbBbscttM] = React.useState({
    no: '',
    bbsId: '',
    bbscttNo: '',
    upperbbscttNo: '',
    bbscttSj: '',
    bbscttCn: '',
    nttInqireCo: '',
    lcasNm: '',
    sortOrdr: '',
    bbscttLevel: '',
    atchFileNo: '',
    registerId: '',
    registDt: '',
    updusrId: '',
    ntceBeginDt: '',
    ntceEndDt: '',
    manage: '',
  });
  /* 검색 항목 설정 */
  const [searchParam, setSearchParam] = React.useState({
    bbsId: 1,
    searchBbscttSj: '',
    searchStartDt: dayjs(sixMonthDate),
    searchEndDt: dayjs(currentDate),
  });

  /* 에디터 ref 설정 */
  const editorRef = useRef();
  // 게시글제목
  const bbscttSjRef = useRef();

  /* 페이지 이벤트 처리 */
  const handleChangePage = (event, newPage) => {
    setPage(newPage);
  };

  const handleChangeRowsPerPage = (event) => {
    setRowsPerPage(+event.target.value);
    setPage(0);
  };

  /* 첨부파일 삭제 */
  const handleFileDelete = (param) => {
    console.log('handleFileDelete', param.fileSeqNo);
    setFileRows(fileRows.filter((x) => x.fileSeqNo !== param.fileSeqNo));

    console.log('fileRows', fileRows);
  };

  /* 첨부파일 다운로드 */
  const handleFileDownLoad = (param) => {
    console.log('handleFileDownLoad', param);

    axi({
      method: 'POST',
      url: 'downloadAxiosFiles.json',
      responseType: 'blob',
      params: {
        filepath: param.path,
        filename: param.fileName,
        originFileName: param.finalFileName,
      },
    })
      .then((response) => {
        const { fileName } = param;
        const { data } = response;
        if (!data) {
          return;
        }
        console.log(response);
        const url = window.URL.createObjectURL(new Blob([data]));
        const a = document.createElement('a');
        a.style.display = 'none';
        a.href = url;
        a.setAttribute('download', fileName);
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        window.URL.revokeObjectURL(url);
      })
      .catch((response) => {
        throw new Error(response);
      });
  };

  /* 검색 */
  const handleSearchOnChange = (e) => {
    console.log('e', e);
    setSearchParam({
      ...searchParam,
      [e.target.name]: e.target.value,
    });
  };

  const handleSearchOnDateChange = (target, value) => {
    setSearchParam({
      ...searchParam,
      [target]: value,
    });

    console.log('SearchParam', searchParam);
  };

  const handleOnChange = (e) => {
    setTbBbscttM({
      ...tbBbscttM,
      [e.target.name]: e.target.value,
    });
  };

  const handleDataOnDateChange = (target, value) => {
    setTbBbscttM({
      ...tbBbscttM,
      [target]: value,
    });
  };

  /* 검색초기화 */
  const fnInit = () => {
    setSearchParam({
      ...searchParam,
      searchBbscttSj: '',
      searchStartDt: dayjs(sixMonthDate),
      searchEndDt: dayjs(currentDate),
    });
  };

  /* 공지사항 목록 검색 */
  const fnSearch = () => {
    axi({
      method: 'POST',
      url: 'selectBbsList.json',
      params: {
        bbsId: 1,
        searchBbscttSj: searchParam.searchBbscttSj,
        searchStartDt: dayjs(searchParam.searchStartDt).format('YYYYMMDD'),
        searchEndDt: dayjs(searchParam.searchEndDt).format('YYYYMMDD'),
      },
    })
      .then((response) => {
        console.log('성공', response);
        if (response.data.ResultCode === 'SUCCESS') {
          setRows(response.data.list);
        }
      })
      .catch((e) => {
        if (e.message) {
          console.log('조회중 오류가 발생하였습니다.');
        }
      });
  };

  /* 공지사항 등록 초기화 */
  const fnAdd = () => {
    console.log('tbBbscttM', tbBbscttM);
    setTbBbscttM({
      ...tbBbscttM,
      no: '',
      bbsId: '',
      bbscttNo: '',
      upperbbscttNo: '',
      bbscttSj: '',
      bbscttCn: ' ',
      nttInqireCo: '',
      lcasNm: '',
      sortOrdr: '',
      bbscttLevel: '',
      atchFileNo: '',
      registerId: '',
      registDt: '',
      updusrId: '',
      ntceBeginDt: '',
      ntceEndDt: '',
      manage: '',
    });
    setMode('I');
    setExtFiles([]);
    setFiles([]);
    setFileRows([]);
    sendSuccessFiles = [];
    setOpen(true);
  };

  /* 공지사항 수정 */
  const handleEdit = (param) => {
    console.log('클릭', param);

    // 코드등록 값전달
    setTbBbscttM({
      ...tbBbscttM,
      bbsId: param.bbsId,
      bbscttNo: param.bbscttNo,
      upperbbscttNo: param.upperbbscttNo,
      bbscttSj: param.bbscttSj,
      bbscttCn: param.bbscttCn,
      atchFileNo: param.atchFileNo,
      ntceBeginDt: param.ntceBeginDt,
      ntceEndDt: param.ntceEndDt,
      manage: param.manage,
    });
    setMode('U');
    setExtFiles([]);
    sendSuccessFiles = [];

    if (param.atchFileNo !== null && param.atchFileNo !== '' && param.atchFileNo > 0) {
      /* 첨부파일이 있으면 첨부파일 목록 가져오기 */
      axi({
        method: 'POST',
        url: 'selectAttachList.json',
        params: { atchFileNo: param.atchFileNo },
      })
        .then((response) => {
          console.log('성공', response);
          if (response.data.ResultCode === 'SUCCESS') {
            setFileRows(response.data.list);
          }
        })
        .catch((e) => {
          if (e.message) {
            console.log('조회중 오류가 발생하였습니다.');
          }
        });
    } else {
      setFileRows([]);
      setFiles([]);
    }

    setOpen(true);
  };

  /* 공지사항 삭제 */
  const deleteSubmit = () => {
    /* 데이터 삭제 */
    confirmAlert({
      title: '공지사항 삭제',
      message: '삭제 하시겠습니까?',
      buttons: [
        {
          label: '확인',
          onClick: () => {
            const delData = {
              bbsId: tbBbscttM.bbsId,
              bbscttNo: tbBbscttM.bbscttNo,
            };

            axi({
              method: 'POST',
              url: 'deleteBbsList.json',
              params: delData,
            })
              .then((response) => {
                console.log('성공', response);
                setOpen(false);
                fnSearch();
              })
              .catch((e) => {
                if (e.message) {
                  console.log('삭제중 오류가 발생하였습니다.');
                }
              });
          },
        },
        {
          label: '취소',
          onClick: () => {},
        },
      ],
    });
  };

  /* 공지사항저장 */
  const saveSubmit = () => {
    console.log('저장', tbBbscttM);
    console.log('업로드 성공 한것만 saveSubmit', sendSuccessFiles);

    const fileArray = [...fileRows, ...sendSuccessFiles];

    console.log('기존 + 신규 FileRows', fileArray);

    // 필수입력 체크
    if (tbBbscttM.bbscttSj === '') {
      confirmAlert({
        title: '제목은 필수 입력입니다.',
        message: '항목을 입력 해 주세요',
        buttons: [
          {
            label: '확인',
            onClick: () => {
              bbscttSjRef.current.focus();
            },
          },
        ],
      });
    } else {
      /* 데이터 저장 */
      confirmAlert({
        title: '공지사항 등록',
        message: mode === 'I' ? '저장 하시겠습니까?' : '수정 하시겠습니까?',
        buttons: [
          {
            label: '확인',
            onClick: () => {
              const savdData = {
                tbBbscttM,
                tbFileAttach: fileArray,
              };

              axi({
                method: 'POST',
                url: mode === 'I' ? 'insertBbsList.json' : 'updateBbsList.json',
                data: savdData,
                params: {},
              })
                .then((response) => {
                  console.log('성공', response);
                  setOpen(false);
                  fnSearch();
                })
                .catch((e) => {
                  if (e.message) {
                    console.log('저장중 오류가 발생하였습니다.');
                  }
                });
            },
          },
          {
            label: '취소',
            onClick: () => {},
          },
        ],
      });
    }
  };

  /* 레이어 팝업 인풋박스 포커스 이동 */
  useEffect(() => {
    if (open) {
      setTimeout(() => {
        bbscttSjRef.current.focus();
      }, 3);
    }
  }, [open]);
  /*
  const onChange = () => {
    console.log(editorRef.current.getInstance().getHTML());
    setContents(editorRef.current.getInstance().getHTML());
    // console.log(editorRef.current.getInstance().getMarkdown());
  };
  */

  const myOwnValidation = (fileValid) => {
    const errorList = [];
    const validResult = true;
    const regExPrefix = /\btest_file\w+/;
    // if (!fileValid.name.match(regExPrefix)) {
    // validResult = false;
    //  errorList.push('Prefix "test_file" was not present in the file name');
    // }
    console.log(validResult, errorList);
    return { valid: validResult, errors: errorList };
  };

  /* 초기화 실행 */
  useEffect(() => {
    fnSearch();
    return () => {
      console.log('cleanup');
    };
  }, []);

  return (
    <div className="m-2 md:m-3 mt-24 p-2 md:p-7 dark:text-gray-200  bg-white dark:bg-[#484B52] rounded-3xl">
      <Header category="기준정보" title="공지사항" />
      <Paper
        component="form"
        sx={{
          p: '2px 4px',
          display: 'flex',
          alignItems: 'center',
          width: '100%',
        }}
      >
        <TextField
          sx={{ ml: 1, flex: 1, minWidth: 100 }}
          name="searchBbscttSj"
          value={searchParam.searchBbscttSj}
          onChange={(event) => {
            // eslint-disable-next-line no-unused-expressions
            handleSearchOnChange(event);
          }}
          label="제목"
          variant="standard"
        />
        <Divider sx={{ height: 28, m: 0.5 }} orientation="vertical" />

        <LocalizationProvider dateAdapter={AdapterDayjs}>
          <DemoContainer sx={{ ml: 1, flex: 1, minWidth: 130 }} components={['DatePicker', 'DatePicker']}>
            <DatePicker
              label="등록시작일자"
              // defaultValue={dayjs(sixMonthDate)}
              // value={dayjs(searchParam.searchStartDt)}
              value={searchParam.searchStartDt}
              onChange={(newValue) => {
                // eslint-disable-next-line no-unused-expressions
                // handleSearchOnDateChange('searchStartDt', dayjs(newValue).format('YYYYMMDD'));
                handleSearchOnDateChange('searchStartDt', newValue);
              }}
              format="YYYY-MM-DD"
              variant="standard"
            />
            <DatePicker
              label="등록종료일자"
              // defaultValue={dayjs(currentDate)}
              value={searchParam.searchEndDt}
              onChange={(newValue) => {
                // eslint-disable-next-line no-unused-expressions
                handleSearchOnDateChange('searchEndDt', newValue);
              }}
              format="YYYY-MM-DD"
              variant="standard"
            />
          </DemoContainer>
        </LocalizationProvider>

        <Divider sx={{ height: 28, m: 0.5 }} orientation="vertical" />
        <IconButton onClick={fnSearch} type="button" sx={{ p: '10px' }} aria-label="search">
          <SearchIcon />
          검색
        </IconButton>
        <Divider sx={{ height: 28, m: 0.5 }} orientation="vertical" />
        <IconButton onClick={fnInit} type="button" sx={{ p: '10px' }} aria-label="search">
          <RefreshIcon />
          초기화
        </IconButton>
      </Paper>

      <div className="mt-3 ">
        <Button onClick={fnAdd} variant="outlined" className="dark:text-white">
          신규등록
        </Button>
      </div>
      <Paper className="mt-1 " sx={{ width: '100%' }}>
        <TableContainer sx={{ maxHeight: heightSize }}>
          <Table stickyHeader sx={{ minWidth: 650, width: '100%', tableLayout: 'auto' }} aria-label="sticky table">
            <TableHead>
              <TableRow>
                {columns.map((column) => (
                  <TableCell
                    sx={{
                      backgroundColor: currentColor,
                      color: 'white',
                    }}
                    key={column.id}
                    align={column.align}
                    width={column.width}
                  >
                    {column.label}
                  </TableCell>
                ))}
              </TableRow>
            </TableHead>

            <TableBody>
              {rows.length > 0 ? (
                rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row) => (
                  // eslint-disable-next-line react/jsx-indent
                  <TableRow hover role="checkbox" tabIndex={-1} key={`${row.rowNo + 9999}`}>
                    {columns.map((column) => {
                      const value = row[column.id];
                      return (
                        <TableCell key={`${column.rowNo + column.id}`} align={column.align}>
                          {/* eslint-disable-next-line no-nested-ternary */}
                          {column.id === 'bbscttCn' ? (
                            // html 태그를 넣을때
                            // <div dangerouslySetInnerHTML={{ __html: value }} />
                            fnRemoveHtml(value).substr(0, 30)
                          ) : value === 'edit' ? (
                            <Button onClick={() => handleEdit(row)} variant="outlined" sx={{ ml: 1 }}>
                              상세보기
                            </Button>
                          ) : (
                            value
                          )}
                          {/* column.format && typeof value === 'number' ? column.format(value) : value */}
                        </TableCell>
                      );
                    })}
                  </TableRow>
                  // eslint-disable-next-line indent
                ))
              ) : (
                <TableRow hover>
                  <TableCell align="center" colSpan={8}>
                    데이터가 존재 하지 않습니다.
                  </TableCell>
                </TableRow>
              )}
            </TableBody>
          </Table>
        </TableContainer>
        <TablePagination rowsPerPageOptions={[10, 25, 100]} component="div" count={rows.length} rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage} onRowsPerPageChange={handleChangeRowsPerPage} />
      </Paper>

      <Dialog open={open} TransitionComponent={Transition} fullScreen style={{ zIndex: '1000' }}>
        <ThemeProvider theme={themes}>
          <AppBar sx={{ position: 'relative', backgroundColor: currentColor }}>
            <Toolbar>
              <Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
                공지사항등록
              </Typography>

              <IconButton edge="start" color="inherit" onClick={handleClose} aria-label="close">
                <CloseIcon />
              </IconButton>
            </Toolbar>
          </AppBar>
        </ThemeProvider>

        <Box
          component="form"
          sx={{
            '& .MuiTextField-root': { m: 1, width: '100%' },
          }}
          noValidate
          autoComplete="off"
        >
          <div className="m-2 md:m-3 mt-24 p-2 md:p-7 dark:text-gray-200  bg-white dark:bg-[#484B52] rounded-3xl">
            <Grid container justifyContent="flex-start" rowSpacing={1} columnSpacing={{ xs: 1, sm: 2, md: 3, lg: 4, xl: 5 }}>
              <Grid xs={12} md={5} lg={6} xl={8}>
                <TextField
                  inputRef={bbscttSjRef}
                  id="bbscttSj"
                  name="bbscttSj"
                  required
                  sx={{ m: 1, flex: 1, minWidth: '100%', maxWidth: '100%' }}
                  label="제목"
                  helperText="*제목필수입력"
                  focused
                  autoFocus
                  inputProps={{ maxLength: 30 }}
                  value={tbBbscttM.bbscttSj}
                  onChange={(event) => {
                    // eslint-disable-next-line no-unused-expressions
                    handleOnChange(event);
                  }}
                  error={tbBbscttM.bbscttSj.length < 1}
                />
              </Grid>
              <Grid xs={12} md={7} lg={6} xl={4}>
                <LocalizationProvider dateAdapter={AdapterDayjs}>
                  <DatePicker
                    sx={{ minWidth: 200, maxWidth: 200 }}
                    label="게시시작일"
                    value={dayjs(tbBbscttM.ntceBeginDt)}
                    onChange={(newValue) => {
                      // eslint-disable-next-line no-unused-expressions
                      handleDataOnDateChange('ntceBeginDt', dayjs(newValue).format('YYYY-MM-DD'));
                      // handleDataOnDateChange('ntceBeginDt', newValue);
                    }}
                    inputFormat="YYYY-MM-DD"
                    format="YYYY-MM-DD"
                    variant="standard"
                  />
                  <DatePicker
                    sx={{ minWidth: 200, maxWidth: 200 }}
                    label="게시종료일"
                    value={dayjs(tbBbscttM.ntceEndDt)}
                    onChange={(newValue) => {
                      // eslint-disable-next-line no-unused-expressions
                      handleDataOnDateChange('ntceEndDt', dayjs(newValue).format('YYYY-MM-DD'));
                      // handleDataOnDateChange('ntceEndDt', newValue);
                    }}
                    inputFormat="YYYY-MM-DD"
                    format="YYYY-MM-DD"
                    variant="standard"
                  />
                </LocalizationProvider>
              </Grid>

              <Grid xs={12}>
                <div className="ml-2 ">
                  <Editor
                    ref={editorRef}
                    initialValue={tbBbscttM.bbscttCn}
                    value={tbBbscttM.bbscttCn}
                    previewStyle="vertical"
                    height="600px"
                    initialEditType="wysiwyg"
                    useCommandShortcut={false}
                    plugins={[colorSyntax]}
                    language="ko-KR"
                    hideModeSwitch={false}
                    onChange={(newValue) => {
                      // eslint-disable-next-line no-unused-expressions
                      handleDataOnDateChange('bbscttCn', editorRef.current.getInstance().getHTML());
                      // handleDataOnDateChange('ntceEndDt', newValue);
                    }}
                  />
                </div>
              </Grid>
              {/*
              <Grid xs={12}>
                <div className="ml-2 ">
                  <FileUploader handleChange={handleFileChange} name="files" multiple types={fileTypes} label="파일을 선택해 주세요" maxSize={100} />
                </div>
              </Grid>
                  */}
              <Grid xs={12}>
                <div className="ml-2 ">
                  <Dropzone
                    // minHeight="150px"
                    label="드래그 & 드롭 파일 또는 클릭 브라우저"
                    background="radial-gradient(circle at 18.7% 37.8%, rgb(250, 250, 250) 0%, rgb(225, 234, 238) 90%);"
                    onChange={updateFiles}
                    value={extFiles}
                    // accept="image/*, video/*"
                    // maxFileSize={50 * 1024 * 1024}
                    // maxFiles={5}
                    // footerConfig={{ customMessage: '허용가능 파일 타입:*.hwp' }}
                    autoClean
                    cleanFiles
                    validator={myOwnValidation}
                    uploadConfig={{
                      url: '/api/fileUpload.json',
                      method: 'POST',
                      cleanOnUpload: true,
                      autoUpload: true,
                    }}
                    onUploadStart={handleStart}
                    onUploadFinish={handleFinish}
                    //  fakeUpload
                    actionButtons={{
                      position: 'after',
                      uploadButton: {},
                      //  abortButton: {},
                      //  cleanButton: {},
                      //  deleteButton: {},
                    }}
                  >
                    {extFiles.map((filess) => (
                      // eslint-disable-next-line react/jsx-props-no-spreading
                      <FileMosaic {...filess} key={filess.id} onDelete={onDelete} onSee={handleSee} onWatch={handleWatch} onAbort={handleAbort} onCancel={handleCancel} resultOnTooltip alwaysActive preview info />
                    ))}
                  </Dropzone>
                  <FullScreen open={imageSrc !== undefined} onClose={() => setImageSrc(undefined)}>
                    <ImagePreview src={imageSrc} />
                  </FullScreen>
                  <FullScreen open={videoSrc !== undefined} onClose={() => setVideoSrc(undefined)}>
                    <VideoPreview src={videoSrc} autoPlay controls />
                  </FullScreen>
                </div>
              </Grid>
              {fileRows.length > 0 && (
                <Grid xs={12}>
                  <div className="ml-2 ">
                    <Paper className="mt-1 " sx={{ width: '100%' }}>
                      <TableContainer sx={{ maxHeight: heightSize }}>
                        <Table stickyHeader sx={{ minWidth: 650, width: '100%', tableLayout: 'auto' }} aria-label="sticky table">
                          <TableHead>
                            <TableRow>
                              {fileColumns.map((column) => (
                                <TableCell
                                  sx={{
                                    backgroundColor: currentColor,
                                    color: 'white',
                                  }}
                                  key={column.id}
                                  align={column.align}
                                  width={column.width}
                                >
                                  {column.label}
                                </TableCell>
                              ))}
                            </TableRow>
                          </TableHead>

                          <TableBody>
                            {fileRows.length > 0 ? (
                              fileRows.map((row) => (
                                // eslint-disable-next-line react/jsx-indent
                                <TableRow hover tabIndex={-1} key={`${row.seqNo + 9999}`}>
                                  {fileColumns.map((column) => {
                                    const value = row[column.id];
                                    return (
                                      <TableCell key={`${column.id + value}`} align={column.align}>
                                        {/* eslint-disable-next-line no-nested-ternary */}
                                        {column.format && typeof value === 'number' ? (
                                          column.format(value)
                                        ) : value === 'edit' ? (
                                          <>
                                            <Button onClick={() => handleFileDownLoad(row)} variant="outlined" sx={{ ml: 1 }}>
                                              파일다운로드
                                            </Button>
                                            <Button onClick={() => handleFileDelete(row)} variant="outlined" sx={{ ml: 1 }}>
                                              파일삭제
                                            </Button>
                                          </>
                                        ) : (
                                          value
                                        )}
                                        {/* column.format && typeof value === 'number' ? column.format(value) : value */}
                                      </TableCell>
                                    );
                                  })}
                                </TableRow>
                                // eslint-disable-next-line indent
                              ))
                            ) : (
                              <TableRow hover>
                                <TableCell align="center" colSpan={4}>
                                  데이터가 존재 하지 않습니다.
                                </TableCell>
                              </TableRow>
                            )}
                          </TableBody>
                        </Table>
                      </TableContainer>
                    </Paper>
                  </div>
                </Grid>
              )}
            </Grid>
          </div>
        </Box>

        {/*
          <Viewer height="300px" initialValue={contents || '<p>hello react editㄷㄱㄱㄷㅈㄷor world!</p>'} />
          */}
        <div className="m-5 text-center ">
          <Button type="submit" variant="outlined" className="mr-10 dark:text-white" onClick={saveSubmit}>
            <SaveIcon />
            저장
          </Button>
          {mode !== 'I' && (
            <Button type="submit" variant="outlined" className="mr-10 dark:text-white" onClick={deleteSubmit}>
              <SaveIcon />
              삭제
            </Button>
          )}
          <Button type="submit" variant="outlined" className="dark:text-white" onClick={handleClose}>
            <SaveIcon />
            닫기
          </Button>
        </div>
      </Dialog>
    </div>
  );
}
728x90
반응형

댓글



"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

loading