근태관리 api(미완), 공지사항 파트 구현완료 :push
This commit is contained in:
@ -45,7 +45,7 @@ public class WebSecurityConfig {
|
||||
)
|
||||
.authorizeHttpRequests(request -> request
|
||||
.requestMatchers("/", "/api/v1/auth/**", "/api/v1/search/**", "/file/**" , "/api/v1/menu/**").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/api/v1/board/**", "/api/v1/user/*").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/api/v1/board/**", "/api/v1/user/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.exceptionHandling(exceptionHandling -> exceptionHandling
|
||||
@ -82,90 +82,3 @@ class FailedAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
}
|
||||
|
||||
|
||||
/* package com.eogns.board_back.config;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import com.eogns.board_back.filter.jwtAuthenticationFilter;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class WebSecurityConfig {
|
||||
@SuppressWarnings("rawtypes") // 이거 빠른수정으로 추가
|
||||
private final jwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Bean
|
||||
protected SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception{
|
||||
httpSecurity
|
||||
.cors(cors -> cors
|
||||
.configurationSource(corsConfigurationSource())
|
||||
)
|
||||
.csrf(CsrfConfigurer::disable)
|
||||
.httpBasic(HttpBasicConfigurer::disable)
|
||||
.sessionManagement(sessionManagement -> sessionManagement
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
)
|
||||
.authorizeHttpRequests(request -> request
|
||||
.requestMatchers("/","/api/v1/auth/**", "api/v1/search/**","/file/**").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/api/v1/board/**", "/api/v1/user/*").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
|
||||
.exceptionHandling(exceptionHandling -> exceptionHandling
|
||||
.authenticationEntryPoint(new FailedAuthenticationEntryPoint())
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
|
||||
httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return httpSecurity.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected CorsConfigurationSource corsConfigurationSource(){
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.addAllowedOrigin("*");
|
||||
configuration.addAllowedMethod("*");
|
||||
configuration.addExposedHeader("*");
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
class FailedAuthenticationEntryPoint implements AuthenticationEntryPoint{
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException, ServletException {
|
||||
response.setContentType("application/json");
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.getWriter().write("{\"code:\": \"AF\", \"message\": \"Authorizion Filed. \"}");
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
@ -0,0 +1,34 @@
|
||||
package com.eogns.board_back.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import com.eogns.board_back.entity.AttendanceEntity;
|
||||
import com.eogns.board_back.service.AttendanceService;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/user")
|
||||
|
||||
public class AttendanceController {
|
||||
@Autowired
|
||||
private AttendanceService attendanceService;
|
||||
|
||||
@PostMapping("/check-in/{email}")
|
||||
public AttendanceEntity checkIn(@PathVariable String email) {
|
||||
return attendanceService.checkIn(email);
|
||||
}
|
||||
|
||||
@PostMapping("/check-out/{email}")
|
||||
public AttendanceEntity checkOut(@PathVariable String email) {
|
||||
return attendanceService.checkOut(email);
|
||||
}
|
||||
|
||||
@GetMapping("/test/{email}")
|
||||
public List<AttendanceEntity> getAttendance(@PathVariable String email) {
|
||||
return attendanceService.getAttendance(email);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
package com.eogns.board_back.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import com.eogns.board_back.entity.LeaveRecordsEntity;
|
||||
import com.eogns.board_back.service.LeaveRecordsService;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/user")
|
||||
public class LeaveRecordsController {
|
||||
@Autowired
|
||||
private LeaveRecordsService leaveRecordsService;
|
||||
|
||||
@PostMapping("/leave/apply")
|
||||
public LeaveRecordsEntity applyLeave(
|
||||
@RequestParam String email,
|
||||
@RequestParam String status,
|
||||
@RequestParam LocalDate startDate,
|
||||
@RequestParam LocalDate endDate,
|
||||
@RequestParam(required = false) String location) {
|
||||
return leaveRecordsService.applyLeave(email, status, startDate, endDate, location);
|
||||
}
|
||||
|
||||
@GetMapping("/leave/{email}")
|
||||
public List<LeaveRecordsEntity> getLeaveRecords(@PathVariable String email) {
|
||||
return leaveRecordsService.getLeaveRecords(email);
|
||||
}
|
||||
}
|
||||
|
@ -14,17 +14,19 @@ import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/menu/employee")
|
||||
@RequestMapping("/api/v1/menu")
|
||||
@RequiredArgsConstructor
|
||||
|
||||
public class EmployeeController{
|
||||
public class MenuController{
|
||||
private final AuthService authService;
|
||||
|
||||
@PostMapping("/sign-up")
|
||||
@PostMapping("/employee/sign-up")
|
||||
public ResponseEntity<? super SignUpResponseDto> signUp(
|
||||
@RequestBody @Valid SignUpRequestDto requestBody
|
||||
) {
|
||||
ResponseEntity<? super SignUpResponseDto> response = authService.signUp(requestBody);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.eogns.board_back.entity;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class AttendanceEntity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String email;
|
||||
|
||||
private LocalDateTime checkInTime;
|
||||
|
||||
private LocalDateTime checkOutTime;
|
||||
|
||||
private LocalDateTime recordedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public LocalDateTime getCheckInTime() {
|
||||
return checkInTime;
|
||||
}
|
||||
|
||||
public void setCheckInTime(LocalDateTime checkInTime) {
|
||||
this.checkInTime = checkInTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getCheckOutTime() {
|
||||
return checkOutTime;
|
||||
}
|
||||
|
||||
public void setCheckOutTime(LocalDateTime checkOutTime) {
|
||||
this.checkOutTime = checkOutTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getRecordedAt() {
|
||||
return recordedAt;
|
||||
}
|
||||
|
||||
public void setRecordedAt(LocalDateTime recordedAt) {
|
||||
this.recordedAt = recordedAt;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package com.eogns.board_back.entity;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
|
||||
|
||||
@Entity
|
||||
public class LeaveRecordsEntity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String email;
|
||||
private String status; // 'vacation' or 'business trip'
|
||||
private LocalDate vacationStartDate;
|
||||
private LocalDate vacationEndDate;
|
||||
private String businessTripLocation;
|
||||
private LocalDate recordedAt;
|
||||
|
||||
// Getters and Setters
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDate getVacationStartDate() {
|
||||
return vacationStartDate;
|
||||
}
|
||||
|
||||
public void setVacationStartDate(LocalDate vacationStartDate) {
|
||||
this.vacationStartDate = vacationStartDate;
|
||||
}
|
||||
|
||||
public LocalDate getVacationEndDate() {
|
||||
return vacationEndDate;
|
||||
}
|
||||
|
||||
public void setVacationEndDate(LocalDate vacationEndDate) {
|
||||
this.vacationEndDate = vacationEndDate;
|
||||
}
|
||||
|
||||
public String getBusinessTripLocation() {
|
||||
return businessTripLocation;
|
||||
}
|
||||
|
||||
public void setBusinessTripLocation(String businessTripLocation) {
|
||||
this.businessTripLocation = businessTripLocation;
|
||||
}
|
||||
|
||||
public LocalDate getRecordedAt() {
|
||||
return recordedAt;
|
||||
}
|
||||
|
||||
public void setRecordedAt(LocalDate recordedAt) {
|
||||
this.recordedAt = recordedAt;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.eogns.board_back.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.eogns.board_back.entity.AttendanceEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AttendanceRepository extends JpaRepository<AttendanceEntity, Long>{
|
||||
// 출근 시간 기록이 없고, 주어진 이메일의 첫 번째 AttendanceEntity를 찾기
|
||||
AttendanceEntity findFirstByEmailAndCheckOutTimeIsNull(String email);
|
||||
|
||||
// 주어진 이메일로 모든 AttendanceEntity 리스트 찾기
|
||||
List<AttendanceEntity> findByEmail(String email);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.eogns.board_back.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.eogns.board_back.entity.LeaveRecordsEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface LeaveRecordsRepository extends JpaRepository<LeaveRecordsEntity, Long> {
|
||||
// 추가적인 쿼리 메소드 정의 가능
|
||||
List<LeaveRecordsEntity> findByEmail(String email);
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
package com.eogns.board_back.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eogns.board_back.entity.AttendanceEntity;
|
||||
import com.eogns.board_back.repository.AttendanceRepository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class AttendanceService {
|
||||
@Autowired
|
||||
private AttendanceRepository attendanceRepository;
|
||||
|
||||
public AttendanceEntity checkIn(String email) {
|
||||
AttendanceEntity attendance = new AttendanceEntity();
|
||||
attendance.setEmail(email);
|
||||
attendance.setCheckInTime(LocalDateTime.now());
|
||||
attendance.setRecordedAt(LocalDateTime.now());
|
||||
return attendanceRepository.save(attendance);
|
||||
}
|
||||
|
||||
public AttendanceEntity checkOut(String email) {
|
||||
AttendanceEntity attendance = attendanceRepository.findFirstByEmailAndCheckOutTimeIsNull(email);
|
||||
if (attendance != null) {
|
||||
attendance.setCheckOutTime(LocalDateTime.now());
|
||||
return attendanceRepository.save(attendance);
|
||||
}
|
||||
return null; // 또는 예외 처리
|
||||
}
|
||||
|
||||
public List<AttendanceEntity> getAttendance(String email) {
|
||||
return attendanceRepository.findByEmail(email);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.eogns.board_back.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eogns.board_back.entity.LeaveRecordsEntity;
|
||||
import com.eogns.board_back.repository.LeaveRecordsRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Service
|
||||
public class LeaveRecordsService {
|
||||
@Autowired
|
||||
private LeaveRecordsRepository leaveRecordsRepository;
|
||||
|
||||
public LeaveRecordsEntity applyLeave(String email, String status, LocalDate startDate, LocalDate endDate, String location) {
|
||||
LeaveRecordsEntity leaveRecords = new LeaveRecordsEntity();
|
||||
leaveRecords.setEmail(email);
|
||||
leaveRecords.setStatus(status);
|
||||
leaveRecords.setVacationStartDate(startDate);
|
||||
leaveRecords.setVacationEndDate(endDate);
|
||||
leaveRecords.setBusinessTripLocation(location);
|
||||
leaveRecords.setRecordedAt(LocalDate.now());
|
||||
return leaveRecordsRepository.save(leaveRecords);
|
||||
}
|
||||
|
||||
public List<LeaveRecordsEntity> getLeaveRecords(String email) {
|
||||
return leaveRecordsRepository.findByEmail(email);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,13 @@ import BoardDetail from "views/Board/Detail";
|
||||
import BoardWrite from "views/Board/Write";
|
||||
import BoardUptdade from "views/Board/Update";
|
||||
import Container from "layouts/Container";
|
||||
import { EMPLOYEE_MANAGEMENT_PATH, MAIN_PATH } from "constant";
|
||||
import NoticeList from "views/Menu/notice/list";
|
||||
import {
|
||||
EMPLOYEE_MANAGEMENT_PATH,
|
||||
MAIN_PATH,
|
||||
NOTICE_LIST_PATH,
|
||||
NOTICE_WRITE_PATH,
|
||||
} from "constant";
|
||||
import { AUTH_PATH } from "constant";
|
||||
import { SEARCH_PATH } from "constant";
|
||||
import { USER_PATH } from "constant";
|
||||
@ -24,6 +30,7 @@ import { GetSignInUserResponseDto } from "apis/response/user";
|
||||
import { ResponseDto } from "apis/response";
|
||||
import User from "types/interface/user.interface";
|
||||
import Employee from "views/Menu/employee";
|
||||
import NoticeWrite from "views/Menu/notice/write";
|
||||
|
||||
// component: 어플리케이션 컴포넌트 //
|
||||
function App() {
|
||||
@ -64,7 +71,8 @@ function App() {
|
||||
// discription: 게시물 상세보기 : '/board/detail/:boardNumber' - BoardDetail //
|
||||
// discription: 게시물 작성하기 : '/board/write' - BoardWrite //
|
||||
// discription: 게시물 수정하기 : '/board/update/:boardNumber' - BoardUpdate //
|
||||
// discription: 사원 추가 : '/employee/sign-in' - BoardUpdate //
|
||||
// discription: 사원 추가 : '/employee/sign-in' - Employee //
|
||||
// discription: 공지 목록 : '/motice/list' - Notice //
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
@ -74,6 +82,9 @@ function App() {
|
||||
<Route path={SEARCH_PATH(":searchWord")} element={<Search />} />
|
||||
<Route path={USER_PATH(":userEmail")} element={<UserP />} />
|
||||
<Route path={EMPLOYEE_MANAGEMENT_PATH()} element={<Employee />} />
|
||||
<Route path={NOTICE_LIST_PATH()} element={<NoticeList />} />
|
||||
<Route path={NOTICE_WRITE_PATH()} element={<NoticeWrite />} />
|
||||
|
||||
<Route path={BOARD_PATH()}>
|
||||
<Route path={BOARD_WRITE_PATH()} element={<BoardWrite />} />
|
||||
<Route
|
||||
|
@ -36,8 +36,11 @@ export const EMPLOYEE_MANAGEMENT_PATH = () => "/employee/sign-up";
|
||||
// 권한 관리 페이지 경로를 반환하는 함수
|
||||
export const PERMISSION_MANAGEMENT_PATH = () => "/menu/permisson";
|
||||
|
||||
// 공지사항 페이지 경로를 반환하는 함수
|
||||
export const NOTICE_PATH = () => "/menu/notice";
|
||||
// 공지사항 목록 페이지 경로를 반환하는 함수
|
||||
export const NOTICE_LIST_PATH = () => "/notice/list";
|
||||
|
||||
// 공지사항 작성 페이지 경로를 반환하는 함수
|
||||
export const NOTICE_WRITE_PATH = () => "/notice/write";
|
||||
|
||||
/* EMPLOYEE_MANAGEMENT_PATH,
|
||||
PERMISSION_MANAGEMENT_PATH,
|
||||
|
@ -12,7 +12,8 @@ import {
|
||||
USER_PATH,
|
||||
EMPLOYEE_MANAGEMENT_PATH,
|
||||
PERMISSION_MANAGEMENT_PATH,
|
||||
NOTICE_PATH,
|
||||
NOTICE_LIST_PATH,
|
||||
NOTICE_WRITE_PATH,
|
||||
} from "constant";
|
||||
import { useCookies } from "react-cookie";
|
||||
import { useBoardStore, useLoginuserStore } from "stores";
|
||||
@ -25,9 +26,9 @@ import {
|
||||
import { ResponseDto } from "apis/response";
|
||||
import MenuItem from "components/MenuItem/menuItem";
|
||||
|
||||
// 헤더 레이아웃
|
||||
// state: 헤더 레이아웃
|
||||
export default function Header() {
|
||||
// 상태 정의
|
||||
// state: 상태 정의
|
||||
const [cookies, setCookie] = useCookies();
|
||||
const { pathname } = useLocation();
|
||||
const { loginUser, setLoginUser, resetLoginUser } = useLoginuserStore();
|
||||
@ -37,26 +38,32 @@ export default function Header() {
|
||||
const [isSearchPage, setSearchPage] = useState<boolean>(false);
|
||||
const [isBoardDetailPage, setBoardDetailPage] = useState<boolean>(false);
|
||||
const [isBoardWritePage, setBoardWritePage] = useState<boolean>(false);
|
||||
const [isNoticeWritePage, setNoticeWritePage] = useState<boolean>(false);
|
||||
const [isBoardUPdatePage, setBoardUPdatePage] = useState<boolean>(false);
|
||||
const [isUserPage, setUserPage] = useState<boolean>(false);
|
||||
const [isEmployeePage, setEmployeePage] = useState<boolean>(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 네비게이트 함수
|
||||
// function: 네비게이트 함수 //
|
||||
const onLogClickHandler = () => {
|
||||
navigate(MAIN_PATH());
|
||||
};
|
||||
|
||||
// 메뉴 클릭 핸들러
|
||||
// event handler: 메뉴 클릭 핸들러 //
|
||||
const onMenuClickHandler = (path: string) => {
|
||||
navigate(path);
|
||||
};
|
||||
|
||||
// event handler: 사이드 카드 클릭 이벤트 처리 //
|
||||
const onSideCardClickHandler = () => {
|
||||
navigate(NOTICE_WRITE_PATH());
|
||||
};
|
||||
|
||||
const onMenuEmployeeClickHandler = () => {
|
||||
navigate(EMPLOYEE_MANAGEMENT_PATH());
|
||||
};
|
||||
|
||||
// 검색 버튼 컴포넌트
|
||||
// component: 검색 버튼 컴포넌트 //
|
||||
const SearchButton = () => {
|
||||
const searchButtonRef = useRef<HTMLDivElement | null>(null);
|
||||
const [status, setStatus] = useState<boolean>(false);
|
||||
@ -119,7 +126,7 @@ export default function Header() {
|
||||
);
|
||||
};
|
||||
|
||||
// 마이페이지 버튼 컴포넌트
|
||||
// component: 마이페이지 버튼 컴포넌트 //
|
||||
const MyPageButton = () => {
|
||||
const { userEmail } = useParams();
|
||||
|
||||
@ -159,7 +166,7 @@ export default function Header() {
|
||||
);
|
||||
};
|
||||
|
||||
// 업로드 버튼 컴포넌트
|
||||
// component: 업로드 버튼 컴포넌트 //
|
||||
const UploadButton = () => {
|
||||
const { boardNumber } = useParams();
|
||||
const { title, content, boardImageFileList, resetBoard } = useBoardStore();
|
||||
@ -192,7 +199,13 @@ export default function Header() {
|
||||
if (code !== "SU") return;
|
||||
|
||||
if (!boardNumber) return;
|
||||
navigate(BOARD_PATH() + "/" + BOARD_DETAIL_PATH(boardNumber));
|
||||
if (isBoardWritePage) {
|
||||
navigate(BOARD_PATH() + "/" + BOARD_DETAIL_PATH(boardNumber));
|
||||
alert("작성 완료되었습니다.");
|
||||
} else if (isNoticeWritePage) {
|
||||
MAIN_PATH();
|
||||
alert("작성 완료되었습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const onUploadButtonClickHandler = async () => {
|
||||
@ -210,7 +223,8 @@ export default function Header() {
|
||||
}
|
||||
|
||||
const isWriterPage = pathname === BOARD_PATH() + "/" + BOARD_WRITE_PATH();
|
||||
if (isWriterPage) {
|
||||
const isNoticeWritePage = pathname === NOTICE_WRITE_PATH();
|
||||
if (isWriterPage || isNoticeWritePage) {
|
||||
const requestBody: PostBoardRequestDto = {
|
||||
title,
|
||||
content,
|
||||
@ -266,6 +280,9 @@ export default function Header() {
|
||||
);
|
||||
setBoardWritePage(isBoardWritePage);
|
||||
|
||||
const isNoticeWritePage = pathname.startsWith(NOTICE_WRITE_PATH());
|
||||
setNoticeWritePage(isNoticeWritePage);
|
||||
|
||||
const isBoardUPdatePage = pathname.startsWith(
|
||||
BOARD_PATH() + "/" + BOARD_UPDATE_PATH("")
|
||||
);
|
||||
@ -276,8 +293,6 @@ export default function Header() {
|
||||
|
||||
const isEmployeePage = pathname.startsWith(EMPLOYEE_MANAGEMENT_PATH());
|
||||
setEmployeePage(isEmployeePage);
|
||||
|
||||
if (isEmployeePage) alert("삑");
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -294,7 +309,7 @@ export default function Header() {
|
||||
const handleMouseLeave = () => {
|
||||
setShowSubMenu(null);
|
||||
};
|
||||
|
||||
// render: 헤더 렌더링 //
|
||||
return (
|
||||
<div id="header">
|
||||
<div className="header-container">
|
||||
@ -350,12 +365,16 @@ export default function Header() {
|
||||
<MenuItem
|
||||
text="공지 사항"
|
||||
imageSrc="https://png.pngtree.com/png-vector/20190118/ourmid/pngtree-vector-announcement-icon-png-image_323832.jpg"
|
||||
onClick={() => onMenuClickHandler(NOTICE_PATH())}
|
||||
onClick={() => onMenuClickHandler(NOTICE_LIST_PATH())}
|
||||
/>
|
||||
{showSubMenu === "notice" && (
|
||||
<div className="sub-menu">
|
||||
<div>공지 추가</div>
|
||||
<div>공지 목록</div>
|
||||
<div onClick={() => onMenuClickHandler(NOTICE_WRITE_PATH())}>
|
||||
공지 추가
|
||||
</div>
|
||||
<div onClick={() => onMenuClickHandler(NOTICE_LIST_PATH())}>
|
||||
공지 목록
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -371,7 +390,9 @@ export default function Header() {
|
||||
isBoardDetailPage ||
|
||||
isUserPage ||
|
||||
isEmployeePage) && <MyPageButton />}
|
||||
{(isBoardWritePage || isBoardUPdatePage) && <UploadButton />}
|
||||
{(isBoardWritePage || isBoardUPdatePage || isNoticeWritePage) && (
|
||||
<UploadButton />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,3 +10,15 @@ export default interface BoardListItem {
|
||||
writerNickname: string;
|
||||
writerProfileImage: string | null;
|
||||
}
|
||||
|
||||
export default interface BoardListItemNoProfileImage {
|
||||
boardNumber: number;
|
||||
title: string;
|
||||
content: string;
|
||||
boardTitleImage: string | null;
|
||||
favoriteCount: number;
|
||||
commentCount: number;
|
||||
viewCount: number;
|
||||
writeDatetime: string;
|
||||
writerNickname: string;
|
||||
}
|
||||
|
@ -1,6 +1,14 @@
|
||||
import User from "views/User";
|
||||
import BoardListItem from "./board-list-item.interface";
|
||||
import BoardListItemNoProfileImage from "./board-list-item.interface";
|
||||
import FavoriteListItem from "./favorite-list-item.interface";
|
||||
import CommentListItem from "./comment-list-item.interface";
|
||||
import Board from "./board.interface";
|
||||
export type { Board, User, BoardListItem, FavoriteListItem, CommentListItem };
|
||||
export type {
|
||||
Board,
|
||||
User,
|
||||
BoardListItem,
|
||||
FavoriteListItem,
|
||||
CommentListItem,
|
||||
BoardListItemNoProfileImage,
|
||||
};
|
||||
|
@ -178,7 +178,7 @@ export default function Main() {
|
||||
viewPageList,
|
||||
totalSection /* 전체 섹션이 몇개인지 */,
|
||||
setTotalList,
|
||||
} = usePagination<BoardListItem>(5); /* 5개씩 게시물 보여짐 */
|
||||
} = usePagination<BoardListItem>(3); /* 5개씩 게시물 보여짐 */
|
||||
// state: 인기 검색어 리스트 상태 //
|
||||
const [popularWordList, setPopularWordList] = useState<string[]>([]);
|
||||
|
||||
@ -223,7 +223,7 @@ export default function Main() {
|
||||
return (
|
||||
<div id="main-bottom-wrapper">
|
||||
<div className="main-bottom-container">
|
||||
<div className="main-bottom-title">{"공지 사항 리스트"}</div>
|
||||
<div className="main-bottom-title">{"최근 공지"}</div>
|
||||
<div className="main-bottom-contents-box">
|
||||
<div className="main-bottom-current-contents">
|
||||
{viewList.map((boardListItem) => (
|
||||
|
137
board-front/src/views/Menu/notice/list/index.tsx
Normal file
137
board-front/src/views/Menu/notice/list/index.tsx
Normal file
@ -0,0 +1,137 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import "./style.css";
|
||||
import Top3Item from "components/Top3Item";
|
||||
import { BoardListItem } from "types/interface";
|
||||
import BoardItem from "components/BoardItem";
|
||||
import Pagination from "components/Pagination";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { MAIN_PATH, SEARCH_PATH } from "constant";
|
||||
import {
|
||||
getLatestBoardListRequset,
|
||||
getPopularListRequest,
|
||||
getTop3BoardListRequest,
|
||||
getUserRequest,
|
||||
} from "apis";
|
||||
import {
|
||||
GetLatestBoardListResponseDto,
|
||||
GetTop3BoardListResponseDto,
|
||||
} from "apis/response/board";
|
||||
import { ResponseDto } from "apis/response";
|
||||
import { usePagination } from "hooks";
|
||||
import { GetPoplarListResponseDto } from "apis/response/search";
|
||||
import { useCookies } from "react-cookie";
|
||||
import { GetUserResponseDto } from "apis/response/user";
|
||||
import { useLoginuserStore } from "stores";
|
||||
|
||||
import DefaultProfileImage from "assets/image/default-profile-image.png";
|
||||
|
||||
// component 메인 화면 컴포넌트 //
|
||||
export default function NoticeList() {
|
||||
// function: 네비게이트 함수 //
|
||||
const navigate = useNavigate();
|
||||
|
||||
// component 메인 화면 하단 컴포넌트 //
|
||||
const NoticeList = () => {
|
||||
// state: 페이지네이션 관련 상태 //
|
||||
const {
|
||||
currentPage /* 현재 페이지가 어떤 위치에 있는지 */,
|
||||
setCurrentPage,
|
||||
currentSection,
|
||||
setCurrentSection,
|
||||
viewList /* 현재 보여줄 리스트 */,
|
||||
viewPageList,
|
||||
totalSection /* 전체 섹션이 몇개인지 */,
|
||||
setTotalList,
|
||||
} = usePagination<BoardListItem>(5); /* 5개씩 게시물 보여짐 */
|
||||
// state: 인기 검색어 리스트 상태 //
|
||||
const [popularWordList, setPopularWordList] = useState<string[]>([]);
|
||||
|
||||
// function: getLatestBoardListResponse 처리함수 //
|
||||
const getLatestBoardListResponse = (
|
||||
responseBody: GetLatestBoardListResponseDto | ResponseDto | null
|
||||
) => {
|
||||
if (!responseBody) return;
|
||||
const { code } = responseBody;
|
||||
if (code === "DBE") alert("데이터베이스 오류입니다.");
|
||||
if (code !== "SU") return;
|
||||
|
||||
const { latestList } = responseBody as GetLatestBoardListResponseDto;
|
||||
setTotalList(latestList);
|
||||
};
|
||||
|
||||
// function: getPopularListResponse 처리함수 //
|
||||
const getPopularListResponse = (
|
||||
responseBody: GetPoplarListResponseDto | ResponseDto | null
|
||||
) => {
|
||||
if (!responseBody) return;
|
||||
const { code } = responseBody;
|
||||
if (code === "DBE") alert("데이터베이스 오류입니다.");
|
||||
if (code !== "SU") return;
|
||||
|
||||
const { popularWordList } = responseBody as GetPoplarListResponseDto;
|
||||
setPopularWordList(popularWordList);
|
||||
};
|
||||
|
||||
// event handler: 인기 검색어 클릭 이벤트 처리 //
|
||||
const onPopularWordClickHandler = (word: string) => {
|
||||
navigate(SEARCH_PATH(word));
|
||||
};
|
||||
|
||||
// effect: 첫 마운트 시 실행될 함수 //
|
||||
useEffect(() => {
|
||||
getLatestBoardListRequset().then(getLatestBoardListResponse);
|
||||
getPopularListRequest().then(getPopularListResponse);
|
||||
}, []);
|
||||
|
||||
// render: 메인 화면 하단 컴포넌트 렌더링 //
|
||||
return (
|
||||
<div id="notice-bottom-wrapper">
|
||||
<div className="notice-bottom-container">
|
||||
<div className="notice-bottom-title">{"공지 사항 리스트"}</div>
|
||||
<div className="notice-bottom-contents-box">
|
||||
<div className="notice-bottom-current-contents">
|
||||
{viewList.map((boardListItem) => (
|
||||
<BoardItem boardListItem={boardListItem} />
|
||||
))}
|
||||
</div>
|
||||
<div className="notice-bottom-popular-box">
|
||||
<div className="notice-bottom-popular-card">
|
||||
<div className="notice-bottom-popular-card-box">
|
||||
<div className="notice-bottom-popular-card-title">
|
||||
{"인기 검색어"}
|
||||
</div>
|
||||
<div className="notice-bottom-popular-card-contents">
|
||||
{popularWordList.map((word) => (
|
||||
<div
|
||||
className="word-badge"
|
||||
onClick={() => onPopularWordClickHandler(word)}
|
||||
>
|
||||
{word}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="notice-bottom-pagination-box">
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
currentSection={currentSection}
|
||||
setCurrentPage={setCurrentPage}
|
||||
setCurrentSection={setCurrentSection}
|
||||
viewPageList={viewPageList}
|
||||
totalSection={totalSection}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// render 메인화면 컴포넌트 렌더링 //
|
||||
return (
|
||||
<>
|
||||
<NoticeList />
|
||||
</>
|
||||
);
|
||||
}
|
79
board-front/src/views/Menu/notice/list/style.css
Normal file
79
board-front/src/views/Menu/notice/list/style.css
Normal file
@ -0,0 +1,79 @@
|
||||
#notice-bottom-wrapper {
|
||||
padding: 40px 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.notice-bottom-container {
|
||||
width: 1200px;
|
||||
min-width: 1200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.notice-bottom-title {
|
||||
color: rgba(0, 0, 0, 1);
|
||||
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.notice-bottom-contents-box {
|
||||
width: 100%;
|
||||
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: 8fr 4fr; /* 최신 게시물과 인기검색어 간의 비율 2 : 1 비율로 설정함 */
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.notice-bottom-current-contents {
|
||||
grid-column: 1 / 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.notice-bottom-popular-box {
|
||||
grid-column: 2 / 3;
|
||||
}
|
||||
|
||||
.notice-bottom-popular-card {
|
||||
padding: 24px;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.notice-bottom-popular-card-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.notice-bottom-popular-card-title {
|
||||
color: rgba(0, 0, 0, 1);
|
||||
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.notice-bottom-popular-card-contents {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.notice-bottom-pagination-box {
|
||||
margin-top: 60px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
162
board-front/src/views/Menu/notice/write/index.tsx
Normal file
162
board-front/src/views/Menu/notice/write/index.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import "./style.css";
|
||||
import { useBoardStore, useLoginuserStore } from "stores";
|
||||
import { MAIN_PATH } from "constant";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useCookies } from "react-cookie";
|
||||
|
||||
// component 게시물 작성화면 컴포넌트 //
|
||||
export default function NoticeWrite() {
|
||||
// state: 제목 영역 요소 참조 상태 //
|
||||
const titleRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
// state: 본문 영역 요소 참조 상태 //
|
||||
const contentRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
// state: 이미지 입력 요소 참조 상태 //
|
||||
const imageInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
// state: 게시물 상태 //
|
||||
const { title, setTitle } = useBoardStore();
|
||||
const { content, setContent } = useBoardStore();
|
||||
const { boardImageFileList, setBoardImageFileList } = useBoardStore();
|
||||
const { resetBoard } = useBoardStore();
|
||||
|
||||
// state: 쿠키 상태 //
|
||||
const [cookies, setCookies] = useCookies();
|
||||
|
||||
// state: 게시물 이미지 미리보기 url 상태 //
|
||||
const [imageUrls, setImageUrls] = useState<string[]>([]);
|
||||
|
||||
// function: 네비게이트 함수 //
|
||||
const navigate = useNavigate();
|
||||
|
||||
// event handler: 제목 변경 이벤트 처리 //
|
||||
const onTitleChangeHandler = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const { value } = event.target;
|
||||
setTitle(value);
|
||||
|
||||
if (!titleRef.current) return;
|
||||
titleRef.current.style.height = "auto";
|
||||
titleRef.current.style.height = `${titleRef.current.scrollHeight}px`;
|
||||
};
|
||||
|
||||
// event handler: 내용 변경 이벤트 처리 //
|
||||
const onContentChangeHandler = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const { value } = event.target;
|
||||
setContent(value);
|
||||
|
||||
if (!contentRef.current) return;
|
||||
contentRef.current.style.height = "auto";
|
||||
contentRef.current.style.height = `${contentRef.current.scrollHeight}px`;
|
||||
};
|
||||
|
||||
// event handler: 이미지 변경 이벤트 처리 //
|
||||
const onImageChangeHandler = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!event.target.files || !event.target.files.length) return;
|
||||
const file = event.target.files[0];
|
||||
|
||||
/* 미리보기를 위함 */
|
||||
const imageUrl = URL.createObjectURL(file);
|
||||
const newimageUrls = imageUrls.map((item) => item);
|
||||
newimageUrls.push(imageUrl);
|
||||
setImageUrls(newimageUrls);
|
||||
|
||||
/* 파일 업로드를 위함 */
|
||||
const newBoardImageFileList = boardImageFileList.map((item) => item);
|
||||
newBoardImageFileList.push(file);
|
||||
setBoardImageFileList(newBoardImageFileList);
|
||||
|
||||
if (!imageInputRef.current) return;
|
||||
imageInputRef.current.value = "";
|
||||
};
|
||||
|
||||
// event handler: 이미지 업로드 버튼 클릭 이벤트 처리 //
|
||||
const onImageUploadButtonClickHandler = () => {
|
||||
if (!imageInputRef.current) return;
|
||||
imageInputRef.current.click();
|
||||
};
|
||||
|
||||
// event handler: 이미지 닫기 버튼 클릭 이벤트 처리 //
|
||||
const onImageCloseButtonClickHandler = (deleteindex: number) => {
|
||||
if (!imageInputRef.current) return;
|
||||
imageInputRef.current.value = "";
|
||||
|
||||
const newImageUrls = imageUrls.filter(
|
||||
(url, index) => index !== deleteindex
|
||||
);
|
||||
setImageUrls(newImageUrls);
|
||||
|
||||
const newBoardImageFileList = boardImageFileList.filter(
|
||||
(file, index) => index !== deleteindex
|
||||
);
|
||||
setBoardImageFileList(newBoardImageFileList);
|
||||
};
|
||||
|
||||
// effect: 마운트시 실행할 함수 //
|
||||
useEffect(() => {
|
||||
const accessToken = cookies.accessToken;
|
||||
if (!accessToken) {
|
||||
navigate(MAIN_PATH());
|
||||
return;
|
||||
}
|
||||
resetBoard();
|
||||
}, []);
|
||||
|
||||
// render 작성화면 렌더링 //
|
||||
|
||||
return (
|
||||
<div id="notice-write-wrapper">
|
||||
<div className="notice-write-container">
|
||||
<div className="notice-write-box">
|
||||
<div className="notice-write-title-box">
|
||||
<textarea
|
||||
ref={titleRef}
|
||||
className="notice-write-title-textarea"
|
||||
rows={1}
|
||||
placeholder="제목을 작성해주세요."
|
||||
value={title}
|
||||
onChange={onTitleChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
<div className="divider"></div>
|
||||
<div className="notice-write-content-box">
|
||||
<textarea
|
||||
ref={contentRef}
|
||||
className="notice-write-content-textarea"
|
||||
placeholder="본문을 작성해주세요."
|
||||
value={content}
|
||||
onChange={onContentChangeHandler}
|
||||
/>
|
||||
<div
|
||||
className="icon-button"
|
||||
onClick={onImageUploadButtonClickHandler}
|
||||
>
|
||||
<div className="icon image-box-light-icon"></div>
|
||||
</div>
|
||||
<input
|
||||
ref={imageInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style={{ display: "none" }}
|
||||
onChange={onImageChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
<div className="notice-write-images-box">
|
||||
{imageUrls.map((imageUrl, index) => (
|
||||
<div className="notice-write-image-box">
|
||||
<img className="notice-write-image" src={imageUrl} />
|
||||
<div
|
||||
className="icon-button image-close"
|
||||
onClick={() => onImageCloseButtonClickHandler(index)}
|
||||
>
|
||||
<div className="icon close-icon"></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
83
board-front/src/views/Menu/notice/write/style.css
Normal file
83
board-front/src/views/Menu/notice/write/style.css
Normal file
@ -0,0 +1,83 @@
|
||||
#notice-write-wrapper {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.notice-write-container {
|
||||
padding: 100px 24px;
|
||||
width: 996px;
|
||||
min-height: 1952px;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.notice-write-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.notice-write-title-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.notice-write-title-textarea {
|
||||
width: 100%;
|
||||
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
resize: none;
|
||||
|
||||
color: rgba(0, 0, 0, 1);
|
||||
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.notice-write-content-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.notice-write-content-textarea {
|
||||
flex: 1;
|
||||
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
resize: none;
|
||||
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
font-size: 19px;
|
||||
font-weight: 500;
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
.notice-write-images-box {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.notice-write-image-box {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notice-write-image {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-close {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
"settings": {
|
||||
"width": 2000,
|
||||
"height": 2000,
|
||||
"scrollTop": -482.7981,
|
||||
"scrollLeft": -512.5388,
|
||||
"zoomLevel": 0.7,
|
||||
"scrollTop": -166.6666,
|
||||
"scrollLeft": -800,
|
||||
"zoomLevel": 0.9,
|
||||
"show": 431,
|
||||
"database": 4,
|
||||
"databaseName": "",
|
||||
@ -380,18 +380,18 @@
|
||||
"tableId": "5jPYxY_W7XcoLCO--IWtf",
|
||||
"name": "user_id",
|
||||
"comment": "유저 아이디",
|
||||
"dataType": "VARCHAR2(20)",
|
||||
"dataType": "VARCHAR(20)",
|
||||
"default": "",
|
||||
"options": 10,
|
||||
"ui": {
|
||||
"keys": 1,
|
||||
"widthName": 60,
|
||||
"widthComment": 65,
|
||||
"widthDataType": 81,
|
||||
"widthDataType": 75,
|
||||
"widthDefault": 60
|
||||
},
|
||||
"meta": {
|
||||
"updateAt": 1726126271711,
|
||||
"updateAt": 1727066200235,
|
||||
"createAt": 1725003493803
|
||||
}
|
||||
},
|
||||
@ -860,18 +860,18 @@
|
||||
"tableId": "WU-vbaja1NU7Fy0EQK5uE",
|
||||
"name": "user_id",
|
||||
"comment": "유저 아이디",
|
||||
"dataType": "VARCHAR2(20)",
|
||||
"dataType": "VARCHAR(20)",
|
||||
"default": "",
|
||||
"options": 8,
|
||||
"ui": {
|
||||
"keys": 2,
|
||||
"widthName": 60,
|
||||
"widthComment": 65,
|
||||
"widthDataType": 81,
|
||||
"widthDataType": 75,
|
||||
"widthDefault": 60
|
||||
},
|
||||
"meta": {
|
||||
"updateAt": 1726126271711,
|
||||
"updateAt": 1727066200235,
|
||||
"createAt": 1726030193552
|
||||
}
|
||||
},
|
||||
@ -940,18 +940,18 @@
|
||||
"tableId": "Qf7oberqyXg9rwB2NXnFB",
|
||||
"name": "user_id",
|
||||
"comment": "유저 아이디",
|
||||
"dataType": "VARCHAR2(20)",
|
||||
"dataType": "VARCHAR(20)",
|
||||
"default": "",
|
||||
"options": 8,
|
||||
"ui": {
|
||||
"keys": 2,
|
||||
"widthName": 60,
|
||||
"widthComment": 65,
|
||||
"widthDataType": 81,
|
||||
"widthDataType": 75,
|
||||
"widthDefault": 60
|
||||
},
|
||||
"meta": {
|
||||
"updateAt": 1726126271711,
|
||||
"updateAt": 1727066200235,
|
||||
"createAt": 1726031243540
|
||||
}
|
||||
},
|
||||
@ -1300,18 +1300,18 @@
|
||||
"tableId": "7E1EZCsr9O350-mnaBiSH",
|
||||
"name": "user_id",
|
||||
"comment": "유저 아이디",
|
||||
"dataType": "VARCHAR2(20)",
|
||||
"dataType": "VARCHAR(20)",
|
||||
"default": "",
|
||||
"options": 8,
|
||||
"ui": {
|
||||
"keys": 2,
|
||||
"widthName": 60,
|
||||
"widthComment": 65,
|
||||
"widthDataType": 81,
|
||||
"widthDataType": 75,
|
||||
"widthDefault": 60
|
||||
},
|
||||
"meta": {
|
||||
"updateAt": 1726126271711,
|
||||
"updateAt": 1727066200235,
|
||||
"createAt": 1726125691896
|
||||
}
|
||||
},
|
||||
@ -1400,18 +1400,18 @@
|
||||
"tableId": "KOhqpIaGA2yW6g0RT1uDd",
|
||||
"name": "stat",
|
||||
"comment": "출장, 휴가 상태",
|
||||
"dataType": "VARCHAR2(10)",
|
||||
"dataType": "VARCHAR(10)",
|
||||
"default": "",
|
||||
"options": 8,
|
||||
"ui": {
|
||||
"keys": 0,
|
||||
"widthName": 60,
|
||||
"widthComment": 83,
|
||||
"widthDataType": 81,
|
||||
"widthDataType": 75,
|
||||
"widthDefault": 60
|
||||
},
|
||||
"meta": {
|
||||
"updateAt": 1726126768873,
|
||||
"updateAt": 1727066203905,
|
||||
"createAt": 1726125976668
|
||||
}
|
||||
},
|
||||
@ -1420,18 +1420,18 @@
|
||||
"tableId": "KOhqpIaGA2yW6g0RT1uDd",
|
||||
"name": "st_dt",
|
||||
"comment": "시작 날짜",
|
||||
"dataType": "TIMESTAMP",
|
||||
"dataType": "DATETIME",
|
||||
"default": "",
|
||||
"options": 0,
|
||||
"ui": {
|
||||
"keys": 0,
|
||||
"widthName": 60,
|
||||
"widthComment": 60,
|
||||
"widthDataType": 65,
|
||||
"widthDataType": 60,
|
||||
"widthDefault": 60
|
||||
},
|
||||
"meta": {
|
||||
"updateAt": 1726126783509,
|
||||
"updateAt": 1727066242335,
|
||||
"createAt": 1726125982429
|
||||
}
|
||||
},
|
||||
@ -1440,18 +1440,18 @@
|
||||
"tableId": "KOhqpIaGA2yW6g0RT1uDd",
|
||||
"name": "end_dt",
|
||||
"comment": "종료 날짜",
|
||||
"dataType": "TIMESTAMP",
|
||||
"dataType": "DATETIME",
|
||||
"default": "",
|
||||
"options": 0,
|
||||
"ui": {
|
||||
"keys": 0,
|
||||
"widthName": 60,
|
||||
"widthComment": 60,
|
||||
"widthDataType": 65,
|
||||
"widthDataType": 60,
|
||||
"widthDefault": 60
|
||||
},
|
||||
"meta": {
|
||||
"updateAt": 1726126786557,
|
||||
"updateAt": 1727066245047,
|
||||
"createAt": 1726125982709
|
||||
}
|
||||
},
|
||||
@ -1460,18 +1460,18 @@
|
||||
"tableId": "KOhqpIaGA2yW6g0RT1uDd",
|
||||
"name": "user_id",
|
||||
"comment": "유저 아이디",
|
||||
"dataType": "VARCHAR2(20)",
|
||||
"dataType": "VARCHAR(20)",
|
||||
"default": "",
|
||||
"options": 8,
|
||||
"ui": {
|
||||
"keys": 2,
|
||||
"widthName": 60,
|
||||
"widthComment": 65,
|
||||
"widthDataType": 81,
|
||||
"widthDataType": 75,
|
||||
"widthDefault": 60
|
||||
},
|
||||
"meta": {
|
||||
"updateAt": 1726126271711,
|
||||
"updateAt": 1727066200235,
|
||||
"createAt": 1726125996333
|
||||
}
|
||||
},
|
||||
@ -1540,18 +1540,18 @@
|
||||
"tableId": "ingpkJqg8oHlrFUxaftAa",
|
||||
"name": "user_id",
|
||||
"comment": "유저 아이디",
|
||||
"dataType": "VARCHAR2(20)",
|
||||
"dataType": "VARCHAR(20)",
|
||||
"default": "",
|
||||
"options": 8,
|
||||
"ui": {
|
||||
"keys": 2,
|
||||
"widthName": 60,
|
||||
"widthComment": 65,
|
||||
"widthDataType": 81,
|
||||
"widthDataType": 75,
|
||||
"widthDefault": 60
|
||||
},
|
||||
"meta": {
|
||||
"updateAt": 1726126414371,
|
||||
"updateAt": 1727066200235,
|
||||
"createAt": 1726126414371
|
||||
}
|
||||
},
|
||||
@ -1901,7 +1901,7 @@
|
||||
-1,
|
||||
{
|
||||
"name": 1726033380026,
|
||||
"dataType": 1726126271710,
|
||||
"dataType": 1727066200234,
|
||||
"comment": 1726033380026,
|
||||
"options(notNull)": 1726033380026,
|
||||
"options(primaryKey)": 1726029927289,
|
||||
@ -2152,7 +2152,7 @@
|
||||
{
|
||||
"options(notNull)": 1726030193551,
|
||||
"name": 1726030193551,
|
||||
"dataType": 1726126271710,
|
||||
"dataType": 1727066200234,
|
||||
"default": 1726030193551,
|
||||
"comment": 1726030193551
|
||||
}
|
||||
@ -2205,7 +2205,7 @@
|
||||
{
|
||||
"options(notNull)": 1726031243537,
|
||||
"name": 1726031243537,
|
||||
"dataType": 1726126271710,
|
||||
"dataType": 1727066200234,
|
||||
"default": 1726031243537,
|
||||
"comment": 1726031243537
|
||||
}
|
||||
@ -2414,7 +2414,7 @@
|
||||
{
|
||||
"options(notNull)": 1726125691894,
|
||||
"name": 1726125691894,
|
||||
"dataType": 1726126271710,
|
||||
"dataType": 1727066200234,
|
||||
"default": 1726125691894,
|
||||
"comment": 1726125691894
|
||||
}
|
||||
@ -2479,7 +2479,7 @@
|
||||
-1,
|
||||
{
|
||||
"name": 1726126668737,
|
||||
"dataType": 1726126213111,
|
||||
"dataType": 1727066203905,
|
||||
"options(notNull)": 1726126297885,
|
||||
"options(primaryKey)": 1726126634772,
|
||||
"comment": 1726126768873
|
||||
@ -2491,7 +2491,7 @@
|
||||
-1,
|
||||
{
|
||||
"name": 1726126122745,
|
||||
"dataType": 1726126233140,
|
||||
"dataType": 1727066242334,
|
||||
"comment": 1726126783509
|
||||
}
|
||||
],
|
||||
@ -2501,7 +2501,7 @@
|
||||
-1,
|
||||
{
|
||||
"name": 1726126128432,
|
||||
"dataType": 1726126234967,
|
||||
"dataType": 1727066245046,
|
||||
"comment": 1726126786556
|
||||
}
|
||||
],
|
||||
@ -2512,7 +2512,7 @@
|
||||
{
|
||||
"options(notNull)": 1726125996330,
|
||||
"name": 1726125996330,
|
||||
"dataType": 1726126271710,
|
||||
"dataType": 1727066200234,
|
||||
"default": 1726125996330,
|
||||
"comment": 1726125996330
|
||||
}
|
||||
@ -2566,7 +2566,7 @@
|
||||
{
|
||||
"options(notNull)": 1726126414369,
|
||||
"name": 1726126414369,
|
||||
"dataType": 1726126414369,
|
||||
"dataType": 1727066200234,
|
||||
"default": 1726126414369,
|
||||
"comment": 1726126414369
|
||||
}
|
||||
|
Reference in New Issue
Block a user