diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/item/repository/ItemRepository.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/item/repository/ItemRepository.java index 846fddee..4339e3f3 100644 --- a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/item/repository/ItemRepository.java +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/item/repository/ItemRepository.java @@ -1,14 +1,20 @@ package com.depromeet.apis.item.repository; import com.depromeet.item.Item; +import com.depromeet.user.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import java.util.List; + public interface ItemRepository extends JpaRepository { @Query(value = "select i from Item i join fetch i.song s join fetch s.album a join fetch a.artist ar join fetch i.itemLocation il join fetch il.villageArea va join fetch i.user u", countQuery = "select count(i) from Item i") Page findAll(Pageable pageable); + + @Query(value = "select i from Item i join fetch i.song s join fetch s.album a join fetch a.artist ar join fetch i.itemLocation il join fetch il.villageArea va join fetch i.user u where u = :user order by i.createdAt desc ") + List findByUser(User user); } diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/controller/UserController.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/controller/UserController.java index 0ecd66ed..775de06e 100644 --- a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/controller/UserController.java +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/controller/UserController.java @@ -3,7 +3,7 @@ import com.depromeet.apis.user.dto.ResponseDto; import com.depromeet.apis.user.dto.UserAllResponseDto; -import com.depromeet.apis.user.dto.UserCountResponseDto; +import com.depromeet.apis.user.dto.UserDetailResponseDto; import com.depromeet.apis.user.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; @@ -11,27 +11,31 @@ import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RestController +@RequestMapping("/users") @RequiredArgsConstructor public class UserController { - private final UserService userService; + private final UserService userService; + + @GetMapping + public ResponseEntity getAllUsers( + @PageableDefault(size = 20, page = 0, sort = "createdAt", + direction = Sort.Direction.DESC) Pageable pageable + ) { + var response = userService.getAllUsers(pageable); + return ResponseDto.ok(response); + } - @GetMapping("/users") - public ResponseEntity getUsers( - @PageableDefault(size = 20, page = 0, sort = "createdAt", - direction = Sort.Direction.DESC) Pageable pageable - ) { - var response = userService.searchAllUsers(pageable); - return ResponseDto.ok(response); - } - @GetMapping("/users/count") - public ResponseEntity> getUsersCount() { - var response= userService.countUsersByCreatedAt(); - return ResponseEntity.ok(response); - } + @GetMapping("/{userId}") + public ResponseEntity getUserDetail( + @PathVariable("userId") Long userId + ) { + var response = userService.getUserDetail(userId); + return ResponseDto.ok(response); + } } diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/controller/UserStaticalController.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/controller/UserStaticalController.java new file mode 100644 index 00000000..09304ae5 --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/controller/UserStaticalController.java @@ -0,0 +1,37 @@ +package com.depromeet.apis.user.controller; + +import com.depromeet.apis.user.dto.UserAllStaticCountDto; +import com.depromeet.apis.user.dto.UserSignUpCountRequestDto; +import com.depromeet.apis.user.dto.UserSignUpCountResponseDto; +import com.depromeet.apis.user.service.UserStaticService; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/users/statical") +@RequiredArgsConstructor +public class UserStaticalController { + + private final UserStaticService userService; + + @GetMapping("/signup/count") + public ResponseEntity> getUserSignUpCount( + UserSignUpCountRequestDto userSignUpCountRequestDto + ) { + var response = userService.getUserSignUpCount(userSignUpCountRequestDto); + return ResponseEntity.ok(response); + } + + @GetMapping("/all/count") + public ResponseEntity getAllUserCount() { + var response = userService.getAllUserCount(); + return ResponseEntity.ok(response); + } + +} diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserActivityResponseDto.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserActivityResponseDto.java new file mode 100644 index 00000000..a60c976d --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserActivityResponseDto.java @@ -0,0 +1,13 @@ +package com.depromeet.apis.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class UserActivityResponseDto { + private String title; + private String content; +} diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserAllStaticCountDto.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserAllStaticCountDto.java new file mode 100644 index 00000000..830c7fad --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserAllStaticCountDto.java @@ -0,0 +1,14 @@ +package com.depromeet.apis.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class UserAllStaticCountDto { + private Long allUserCount; + private int todayUserCount; + private int dropUserCount; +} diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserBasicInfoResponseDto.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserBasicInfoResponseDto.java new file mode 100644 index 00000000..f481eac1 --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserBasicInfoResponseDto.java @@ -0,0 +1,17 @@ +package com.depromeet.apis.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class UserBasicInfoResponseDto { + private Long id; + private String nickname; + private String idfv; + private LocalDateTime createdAt; +} diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserCountResponseDto.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserCountResponseDto.java deleted file mode 100644 index eab568ec..00000000 --- a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserCountResponseDto.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.depromeet.apis.user.dto; - -public record UserCountResponseDto(String date, Long value) { -} diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserDetailInfoResponseDto.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserDetailInfoResponseDto.java new file mode 100644 index 00000000..b8c6bc49 --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserDetailInfoResponseDto.java @@ -0,0 +1,15 @@ +package com.depromeet.apis.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class UserDetailInfoResponseDto { + private int allDropCount; + private String mainDropLocation; + private String dropLocations; + private String interestGenre; +} diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserDetailResponseDto.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserDetailResponseDto.java new file mode 100644 index 00000000..e1c033e0 --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserDetailResponseDto.java @@ -0,0 +1,16 @@ +package com.depromeet.apis.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class UserDetailResponseDto { + private UserBasicInfoResponseDto userBasicInfo; + private UserDetailInfoResponseDto userDetailInfo; + private List userActivity; +} diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserResponseDto.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserResponseDto.java index 5b3a18c6..0b07ca86 100644 --- a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserResponseDto.java +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserResponseDto.java @@ -12,10 +12,5 @@ public class UserResponseDto { private Long id; private String nickname; private String idfv; - - public UserResponseDto(User user){ - this.id = user.getId(); - this.nickname = user.getNickname(); - this.idfv = user.getIdfv(); - } + private String createdAt; } diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserSignUpCountRequestDto.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserSignUpCountRequestDto.java new file mode 100644 index 00000000..62e7bdf1 --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserSignUpCountRequestDto.java @@ -0,0 +1,17 @@ +package com.depromeet.apis.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class UserSignUpCountRequestDto { + private LocalDateTime startDate = LocalDateTime.now().minusDays(7); + private LocalDateTime endDate = LocalDateTime.now(); +} diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserSignUpCountResponseDto.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserSignUpCountResponseDto.java new file mode 100644 index 00000000..1bbe50f0 --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/dto/UserSignUpCountResponseDto.java @@ -0,0 +1,4 @@ +package com.depromeet.apis.user.dto; + +public record UserSignUpCountResponseDto(String date, Long count) { +} diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/repository/UserRepository.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/repository/UserRepository.java index d5dcb3ac..bb360080 100644 --- a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/repository/UserRepository.java +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/repository/UserRepository.java @@ -5,13 +5,30 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import java.time.LocalDateTime; import java.util.List; public interface UserRepository extends JpaRepository { - Page findAll(Pageable pageable); + Page findAll(Pageable pageable); + + @Query(value = "SELECT DATE_FORMAT(u.createdAt, '%m.%d') AS joinDate, COUNT(u.id) AS joinCount FROM User u WHERE u.createdAt BETWEEN :startDate AND :endDate GROUP BY joinDate") + List countUserByCreatedAt(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); + + @Query(value = "SELECT COUNT(DISTINCT u.id) AS dropped_users_count FROM User u JOIN Item i ON u.id = i.user.id") + int countUserByDropCountIsNotNull(); + + + @Query(value = "SELECT village_area.village_name, COUNT(item.item_id) AS item_count FROM users " + + "JOIN item ON users.user_id = item.user_id JOIN item_location ON item_location.item_id = item.item_id " + + "JOIN village_area ON village_area.village_id = item_location.village_id " + + "WHERE users.user_id = :userId " + + "GROUP BY users.user_id, village_area.village_name " + + "ORDER BY item_count DESC", + nativeQuery = true + ) + List countUserDropItemByVillage(@Param("userId") Long userId); - @Query(value = "SELECT DATE_FORMAT(u.createdAt, '%m.%d') AS joinDate, COUNT(u.id) AS joinCount FROM User u GROUP BY joinDate") - List countUserByCreatedAt(); } diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/service/UserService.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/service/UserService.java index bf713fc4..d9e1e6df 100644 --- a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/service/UserService.java +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/service/UserService.java @@ -1,9 +1,8 @@ package com.depromeet.apis.user.service; import com.depromeet.apis.common.dto.PageMetaData; -import com.depromeet.apis.user.dto.UserAllResponseDto; -import com.depromeet.apis.user.dto.UserCountResponseDto; -import com.depromeet.apis.user.dto.UserResponseDto; +import com.depromeet.apis.item.repository.ItemRepository; +import com.depromeet.apis.user.dto.*; import com.depromeet.apis.user.repository.UserRepository; import com.depromeet.user.User; import lombok.RequiredArgsConstructor; @@ -12,41 +11,64 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.List; @Service @RequiredArgsConstructor public class UserService { - private final UserRepository userRepository; - - @Transactional(readOnly = true) - public UserAllResponseDto searchAllUsers(Pageable pageable) { - Page user = userRepository.findAll(pageable); - PageMetaData pageMetaData = new PageMetaData( - user.getNumber(), - user.getSize(), - (int) user.getTotalElements(), - user.getTotalPages() - ); - List users = user.getContent() - .stream() - .map(UserResponseDto::new) - .toList(); - return new UserAllResponseDto(users, pageMetaData); - } - - @Transactional - public List countUsersByCreatedAt() { - List result = userRepository.countUserByCreatedAt(); - List userCountResponseDtos = new ArrayList<>(); - - for (Object[] row : result) { - String joinDate = (String) row[0]; - Long count = (Long) row[1]; - UserCountResponseDto userCountResponseDto = new UserCountResponseDto(joinDate, count); - userCountResponseDtos.add(userCountResponseDto); - } - return userCountResponseDtos; - } + private final UserRepository userRepository; + private final ItemRepository itemRepository; + + @Transactional(readOnly = true) + public UserAllResponseDto getAllUsers(Pageable pageable) { + Page user = userRepository.findAll(pageable); + PageMetaData pageMetaData = new PageMetaData( + user.getNumber(), + user.getSize(), + (int) user.getTotalElements(), + user.getTotalPages() + ); + List users = user.getContent() + .stream() + .map( + u -> new UserResponseDto( + u.getId(), + u.getNickname(), + u.getIdfv(), + u.getCreatedAt().toString() + ) + ) + .toList(); + return new UserAllResponseDto(users, pageMetaData); + } + + + @Transactional(readOnly = true) + public UserDetailResponseDto getUserDetail(Long userId) { + + User user = userRepository.findById(userId).orElseThrow(); + var userBasicInfoResponseDto = new UserBasicInfoResponseDto(user.getId(), user.getNickname(), user.getIdfv(), user.getCreatedAt()); + + List result = userRepository.countUserDropItemByVillage(userId); + + String mainDropLocation = result.isEmpty() ? "현재 드랍한 아이템이 없습니다." : result.get(0)[0].toString(); + StringBuilder dropLocations = new StringBuilder(result.isEmpty() ? "현재 드랍한 아이템이 없습니다." : ""); + int allDropCount = 0; + + for (Object[] row : result) { + dropLocations.append(row[0].toString()).append("[").append(row[1].toString()).append("]").append(", "); + allDropCount += Integer.parseInt(row[1].toString()); + } + var userDetailInfoResponseDto = new UserDetailInfoResponseDto(allDropCount, mainDropLocation, dropLocations.toString(), "지원 예정"); + + var userActivityResponseDtoList = itemRepository.findByUser(user).stream().map( + item -> { + String title = "'" + item.getItemLocation().getName() + "'" + " 에 아이템을 드랍했습니다."; + String content = "'" + item.getSong().getName() + "' 곡을 '" + item.getContent() + "' 의 내용을 드랍했습니다."; + return new UserActivityResponseDto(title, content); + } + ).toList(); + + return new UserDetailResponseDto(userBasicInfoResponseDto, userDetailInfoResponseDto, userActivityResponseDtoList); + } } diff --git a/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/service/UserStaticService.java b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/service/UserStaticService.java new file mode 100644 index 00000000..bc3c5666 --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-server/src/main/java/com/depromeet/apis/user/service/UserStaticService.java @@ -0,0 +1,37 @@ +package com.depromeet.apis.user.service; + +import com.depromeet.apis.user.dto.UserAllStaticCountDto; +import com.depromeet.apis.user.dto.UserSignUpCountRequestDto; +import com.depromeet.apis.user.dto.UserSignUpCountResponseDto; +import com.depromeet.apis.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class UserStaticService { + private final UserRepository userRepository; + + @Transactional(readOnly = true) + public List getUserSignUpCount(UserSignUpCountRequestDto userSignUpCountRequestDto) { + return userRepository.countUserByCreatedAt(userSignUpCountRequestDto.getStartDate(), userSignUpCountRequestDto.getEndDate()) + .stream() + .map(row -> { + String joinDate = (String) row[0]; + Long count = (Long) row[1]; + return new UserSignUpCountResponseDto(joinDate, count); + }).toList(); + } + + @Transactional(readOnly = true) + public UserAllStaticCountDto getAllUserCount() { + Long allUserCount = userRepository.count(); + int todayUserCount = userRepository.countUserByCreatedAt(LocalDateTime.now().minusDays(1), LocalDateTime.now()).size(); + int dropUserCount = userRepository.countUserByDropCountIsNotNull(); + return new UserAllStaticCountDto(allUserCount, todayUserCount, dropUserCount); + } +} diff --git a/backend/streetdrop-admin/streetdrop-admin-web/src/App.js b/backend/streetdrop-admin/streetdrop-admin-web/src/App.js index 6f90ea46..96b85777 100644 --- a/backend/streetdrop-admin/streetdrop-admin-web/src/App.js +++ b/backend/streetdrop-admin/streetdrop-admin-web/src/App.js @@ -13,6 +13,7 @@ import MusicListPage from "./components/music/MusicListPage"; import UserListPage from "./components/user/UserListPage"; import CreateNotification from "./components/notification/CreateNotification"; import ItemListPage from "./components/music/items/ItemListPage"; +import UserSignUpGraph from "./components/user/UserSignUpGraph"; const App = () => { return ( @@ -30,6 +31,7 @@ const App = () => { }/> }/> }/> + }/> }/> diff --git a/backend/streetdrop-admin/streetdrop-admin-web/src/components/dashboard/KpiDashboard.js b/backend/streetdrop-admin/streetdrop-admin-web/src/components/dashboard/KpiDashboard.js new file mode 100644 index 00000000..acbd08c4 --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-web/src/components/dashboard/KpiDashboard.js @@ -0,0 +1,81 @@ +import {Col, Row, Statistic} from "antd"; +import React, {useEffect, useState} from "react"; +import CountUp from "react-countup"; +import axios from "axios"; + +const formatter = (value) => ; + + +function KpiDashboard() { + const [userKpiData, setUserKpiData] = useState(null); + + useEffect(() => { + fetchUserKpi(); + }, []); + + const fetchUserKpi = () => { + axios.get('/admin/users/statical/all/count') + .then(response => { + const data = response.data; + setUserKpiData(data); + }).catch(error => { + console.error("Error fetching data:", error); + }); + } + + const UserKpi = ({allUserCount, todayUserCount, dropUserCount}) => ( + <> + + + + + + + + + + + ); + + const UserRetention = () => ( + <> + + + + + + + + + + + ) + + return ( + <> + + { + userKpiData === null ? + + : + + } + + + + + + ) +} + +export default KpiDashboard; \ No newline at end of file diff --git a/backend/streetdrop-admin/streetdrop-admin-web/src/components/layout/common/Menu.js b/backend/streetdrop-admin/streetdrop-admin-web/src/components/layout/common/Menu.js index e0cf7c3c..9b9f212c 100644 --- a/backend/streetdrop-admin/streetdrop-admin-web/src/components/layout/common/Menu.js +++ b/backend/streetdrop-admin/streetdrop-admin-web/src/components/layout/common/Menu.js @@ -42,6 +42,9 @@ const MenuComponent = () => { • 유저조회 + + • 유저가입 분석 + } title="마케팅 관리"> • 캠페인 생성 diff --git a/backend/streetdrop-admin/streetdrop-admin-web/src/components/user/UserDetailPage.js b/backend/streetdrop-admin/streetdrop-admin-web/src/components/user/UserDetailPage.js new file mode 100644 index 00000000..3b4280ce --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-web/src/components/user/UserDetailPage.js @@ -0,0 +1,98 @@ +import React, {useEffect, useState} from "react" +import {Divider, List, Skeleton} from "antd"; +import axios from "axios"; + +function UserDetailPage({userId}) { + const [userBasicInfo, setUserBasicInfo] = useState(null); + const [userDetailInfo, setUserDetailInfo] = useState(null); + const [userActivity, setUserActivity] = useState(null); + + useEffect(() => { + fetchUserDetail(userId); + }, [userId]); + + const fetchUserDetail = (id) => { + axios.get('/admin/users/' + id) + .then(response => { + const data = response.data; + setUserBasicInfo(data.userBasicInfo); + setUserDetailInfo(data.userDetailInfo); + setUserActivity(data.userActivity); + }).catch(error => { + console.error("Error fetching data:", error); + }); + } + + const DescriptionItem = ({title, content}) => ( +
+

{title}

+

{content}

+
+ ); + + const UserBasicInfo = ({id, nickname, idfv, createdAt}) => ( + <> +

유저 기본정보

+ + + + + + ); + + const UserDetailInfo = ({allDropCount, mainDropLocation, dropLocations, interestGenre}) => ( + <> +

유저 상세정보

+ + + + + + ); + + const UserRecentActivity = ({data}) => ( + <> +

최근 활동

+

(최근 5건 활동 내역)

+ ( + + + + )} + > + + ) + + if (userDetailInfo == null || userBasicInfo == null || userActivity == null) { + return ( + <> + + + ); + } + + return ( + <> + + + + + + + ) +} + +export default UserDetailPage; \ No newline at end of file diff --git a/backend/streetdrop-admin/streetdrop-admin-web/src/components/user/UserListPage.js b/backend/streetdrop-admin/streetdrop-admin-web/src/components/user/UserListPage.js index ff6686b4..be1fed59 100644 --- a/backend/streetdrop-admin/streetdrop-admin-web/src/components/user/UserListPage.js +++ b/backend/streetdrop-admin/streetdrop-admin-web/src/components/user/UserListPage.js @@ -1,12 +1,24 @@ import React, {useEffect, useState} from "react" -import {Table} from 'antd'; +import {Drawer, Table} from 'antd'; import axios from "axios"; import BasicLayout from "../layout/BasicLayout"; +import UserDetailPage from "./UserDetailPage"; function UserListPage() { const [users, setUsers] = useState([]); - const [loading, setLoading] = useState(false); + const [openDrawer, setOpenDrawer] = useState(false); + const [clickedUserId, setClickedUserId] = useState(1); + const showDrawer = (id) => { + setClickedUserId(id); + console.log(id); + setOpenDrawer(true); + }; + + const onClose = () => { + setOpenDrawer(false); + }; + const [tableParams, setTableParams] = useState({ pagination: { current: 1, @@ -26,6 +38,22 @@ function UserListPage() { { title: 'IDFV', dataIndex: 'idfv', + }, + { + title: '가입일', + dataIndex: 'createdAt', + render: (text, record) => ( + {new Date(record.createdAt).toLocaleString()} + ) + }, { + title: '상세보기', + dataIndex: 'id', + render: (text, record) => ( + <> + {showDrawer(record.id)}}>More +
+ + ) } ] @@ -47,11 +75,8 @@ function UserListPage() { }, [JSON.stringify(tableParams)]); const fetchUser = () => { - setLoading(true); - axios.get('/admin/users' + '?page=' + (tableParams.pagination.current - 1) + '&size=' + tableParams.pagination.pageSize) .then(response => { - setLoading(false) console.log(response.data); setUsers(response.data['users']); setTableParams({ @@ -66,8 +91,7 @@ function UserListPage() { }); } - if (loading) - return + return ( <> @@ -79,9 +103,11 @@ function UserListPage() { rowKey={record => record.id} pagination={tableParams.pagination} dataSource={users} - loading={loading} onChange={handleTableChange} /> + + + ) diff --git a/backend/streetdrop-admin/streetdrop-admin-web/src/components/user/UserSignUpGraph.js b/backend/streetdrop-admin/streetdrop-admin-web/src/components/user/UserSignUpGraph.js new file mode 100644 index 00000000..5812537e --- /dev/null +++ b/backend/streetdrop-admin/streetdrop-admin-web/src/components/user/UserSignUpGraph.js @@ -0,0 +1,137 @@ +import React, {useEffect, useState} from "react" + +import BasicLayout from "../layout/BasicLayout"; +import UserLineGraph from "../dashboard/UserLineGraph"; +import axios from "axios"; +import {Button, Col, DatePicker, Form, Row, Space, Table} from "antd"; +import * as PropTypes from "prop-types"; + +import dayjs from 'dayjs'; + +const {RangePicker} = DatePicker; + + +RangePicker.propTypes = { + presets: PropTypes.any, + onChange: PropTypes.any +}; + +function UserSignUpGraph() { + + const [data, setData] = useState([]); + const [dayRange, setDayRange] = useState([dayjs().add(-30, 'day'), dayjs()]); + + useEffect(() => { + fetchData(); + }, []); + + const fetchData = () => { + const startDate = dayRange[0].format('YYYY-MM-DDTHH:mm:ss'); + const endDate = dayRange[1].format('YYYY-MM-DDTHH:mm:ss'); + axios.get(`/admin/users/statical/signup/count?startDate=${startDate}&endDate=${endDate}`) + .then(response => { + const formattedData = response.data.map(data => ({ + x: data.date, + y: data.count + })); + setData(formattedData); + }) + .catch(error => { + console.error('fail when fetching data', error); + }); + }; + + const onRangeChange = (dates, dateStrings) => { + setDayRange(dates); + }; + + const rangePresets = [ + {label: 'Last 7 Days', value: [dayjs().add(-7, 'd'), dayjs()]}, + {label: 'Last 14 Days', value: [dayjs().add(-14, 'd'), dayjs()]}, + {label: 'Last 30 Days', value: [dayjs().add(-30, 'd'), dayjs()]}, + {label: 'Last 90 Days', value: [dayjs().add(-90, 'd'), dayjs()]}, + ]; + + const DataTable = ({data}) => { + const halfLength = Math.floor(data.length / 2); + const firstHalf = data.slice(0, halfLength); + const secondHalf = data.slice(halfLength); + + return ( + <> + + +

+ + + + +

+

+ + + + + ) + + } + + + return ( + <> + +

유저가입 조회

+

일자별, 주별, 기간별 유저 가입을 확인할 수 있습니다.

+ +
+ + + + + + + + +
+ {data && } + +
+ + ) +} + +export default UserSignUpGraph; \ No newline at end of file diff --git a/backend/streetdrop-admin/streetdrop-admin-web/src/pages/Dashboard.js b/backend/streetdrop-admin/streetdrop-admin-web/src/pages/Dashboard.js index 199ab77b..6b37f09f 100644 --- a/backend/streetdrop-admin/streetdrop-admin-web/src/pages/Dashboard.js +++ b/backend/streetdrop-admin/streetdrop-admin-web/src/pages/Dashboard.js @@ -1,16 +1,15 @@ -import React, { useEffect, useState } from "react"; -import { Col, Row, Statistic, Typography } from 'antd'; +import React, {useEffect, useState} from "react"; +import {Typography} from 'antd'; import axios from "axios"; import UserLineGraph from "../components/dashboard/UserLineGraph"; import DashboardLayout from "../components/layout/DashboardLayout"; -import CountUp from 'react-countup'; import ItemDashboard from "../components/dashboard/ItemDashboard"; -import MarketingDashboard from "../components/dashboard/MarketingDashboard"; +import KpiDashboard from "../components/dashboard/KpiDashboard"; -const formatter = (value) => ; -const { Title } = Typography; + +const {Title} = Typography; function Dashboard() { const [data, setData] = useState([]); @@ -19,48 +18,26 @@ function Dashboard() { fetchData(); }, []); - const fetchData = async () => { - try { - const response = await axios.get('/admin/users/count'); - const apiData = response.data; - const formattedData = apiData.map(data => ({ - x: data.date, - y: data.value - })); - - setData(formattedData); - } catch (error) { - console.error('Error fetching data: ', error); - } + const fetchData = () => { + axios.get('/admin/users/statical/signup/count') + .then(response => { + const formattedData = response.data.map(data => ({ + x: data.date, + y: data.count + })); + console.log(formattedData); + setData(formattedData); + }) + .catch(error => { + console.error('fail when fetching data', error); + }); }; + const dashboard1 = (<> KPI 지표

갱신시간 : 2023.06.19 오전 3:06:49

- -
- - - - - - - - - - - - - - - - - - - - + ); const dashboard2 = (<> @@ -73,15 +50,15 @@ function Dashboard() { const dashboard3 = (<> 가입유저

갱신시간 : 2023.06.19 오전 3:06:49

- {data && } + {data && } ); const dashboard4 = (<> 마케팅 분석

갱신시간 : 2023.06.19 오전 3:06:49

- ) + return (<> diff --git a/backend/streetdrop-admin/streetdrop-admin-web/src/setupProxy.js b/backend/streetdrop-admin/streetdrop-admin-web/src/setupProxy.js index a23df64c..b6c070d3 100644 --- a/backend/streetdrop-admin/streetdrop-admin-web/src/setupProxy.js +++ b/backend/streetdrop-admin/streetdrop-admin-web/src/setupProxy.js @@ -18,7 +18,7 @@ module.exports = function (app) { })); app.use('/admin', createProxyMiddleware({ - target: 'http://admin.street-drop.com', + target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/admin': ''