Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
aa5da55
Merge pull request #89 from pirogramming/frontend
qkrxogmla May 13, 2025
1740c4b
Merge pull request #90 from pirogramming/main
qkrxogmla May 13, 2025
8801d5a
Merge pull request #92 from pirogramming/frontend
qkrxogmla May 13, 2025
89638de
Merge pull request #93 from pirogramming/main
qkrxogmla May 13, 2025
159f0ca
[feat] ManageStudents
seonjuuu May 13, 2025
707de40
[fix]css
qkrxogmla May 14, 2025
75428b1
Merge pull request #94 from pirogramming/frontend_th
qkrxogmla May 14, 2025
4003261
Merge pull request #95 from pirogramming/main
qkrxogmla May 14, 2025
ef06fb4
Merge branch 'main' of https://github.com/pirogramming/PiroCheck into…
seonjuuu May 14, 2025
2add8f9
[feat] ManageStudents_StudentDetail
seonjuuu May 14, 2025
5e5d952
[feat] ManageStudents_updateShield
seonjuuu May 14, 2025
82b109c
[fix] updateDefence
seonjuuu May 14, 2025
a6f5a2c
Merge pull request #96 from pirogramming/backend_sj
seonjuuu May 14, 2025
0f7d7c0
출석코드 디자인 완성
qkrxogmla May 16, 2025
d0f57d8
Merge pull request #97 from pirogramming/frontend_th
qkrxogmla May 16, 2025
0b02d5d
Merge pull request #98 from pirogramming/main
qkrxogmla May 16, 2025
a4893cd
App.jsx 라우터 경로 수정:
qkrxogmla May 16, 2025
2a8495d
Merge pull request #100 from pirogramming/frontend_th
qkrxogmla May 16, 2025
506bd1e
Merge pull request #101 from pirogramming/main
qkrxogmla May 16, 2025
7bce5b1
출석코드 api연결
qkrxogmla May 16, 2025
d5a32cc
Merge pull request #102 from pirogramming/frontend_th
qkrxogmla May 16, 2025
04b4b04
Merge pull request #103 from pirogramming/main
qkrxogmla May 16, 2025
865b6e0
initial commit
Imggaggu May 17, 2025
a279703
for merge
Imggaggu May 17, 2025
6f3c9b2
merge deploy(succeed)
Imggaggu May 17, 2025
d79a7e7
[Feat] Admin Attendance Header component(100) , AdminAttendance:Daily…
Imggaggu May 17, 2025
05ae841
[Fix] Cors local 허용, baseurl 수정
Imggaggu May 17, 2025
46b8b50
[Fix] Admin Attendance css
Imggaggu May 17, 2025
accf0d4
[Fix] Admin Attendance css
Imggaggu May 17, 2025
ba0edb7
[Fix] admin attendance list css
Imggaggu May 18, 2025
9ddf09a
[Feat] Admin attendance_코인 클릭 시 dialy 카드 등장
Imggaggu May 18, 2025
ff92472
[Feat] admin attendance_ daily manage card연결
Imggaggu May 18, 2025
9eb7b7f
[Feat] admin attendance 완료!
Imggaggu May 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

import backend.pirocheck.User.entity.User;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
Expand Down Expand Up @@ -37,4 +35,12 @@ public void updateAmounts(int descentAssignment, int descentAttendance, int asce
this.amount = Math.min(calculateAmount, 120000); // 12만원 넘어가지 않도록
}

// 방어권 업데이트
public void updateDefence(int newAscentDefence) {
this.ascentDefence = newAscentDefence;
int calculateAmount = 120000 - this.descentAssignment - this.descentAttendance + newAscentDefence;
this.amount = Math.min(calculateAmount, 120000);

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package backend.pirocheck.ManageStudents.Controller;

import backend.pirocheck.ManageStudents.dto.request.DefenceUpdateReqDto;
import backend.pirocheck.ManageStudents.dto.response.ManageStudentDetailResDto;
import backend.pirocheck.ManageStudents.dto.response.ManageStudentsListResDto;
import backend.pirocheck.ManageStudents.service.ManageStudentsService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/admin/managestudent")
@RequiredArgsConstructor
public class ManageStudentsController {

private final ManageStudentsService manageStudentsService;

// 수강생 리스트 조회
@GetMapping("")
public List<ManageStudentsListResDto> getStudents(@RequestParam(required = false) String name) {
return manageStudentsService.searchMembers(name);
}

// 수강생 상세 조회
@GetMapping("/{studentId}")
public ManageStudentDetailResDto getStudentDetail(@PathVariable Long studentId) {
return manageStudentsService.getMemberDetail(studentId);
}

// 방어권 업데이트
@PatchMapping("/{studentId}/defence")
public void updateDefence(@PathVariable Long studentId, @RequestBody DefenceUpdateReqDto req) {
manageStudentsService.updateDefence(studentId, req.getDefence());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package backend.pirocheck.ManageStudents.dto.request;


import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class DefenceUpdateReqDto {
private int defence;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package backend.pirocheck.ManageStudents.dto.response;

import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Getter
@Builder
public class ManageStudentDetailResDto {

private String name;
private int deposit;
private int defence; // 방어권
private List<String> assignmentTitles; // 과제 제목 리스트
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package backend.pirocheck.ManageStudents.dto.response;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class ManageStudentsListResDto {

private Long id;
private String name;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package backend.pirocheck.ManageStudents.service;

import backend.pirocheck.Deposit.entity.Deposit;
import backend.pirocheck.Deposit.repository.DepositRepository;
import backend.pirocheck.ManageStudents.dto.response.ManageStudentDetailResDto;
import backend.pirocheck.ManageStudents.dto.response.ManageStudentsListResDto;
import backend.pirocheck.User.entity.Role;
import backend.pirocheck.User.entity.User;
import backend.pirocheck.User.repository.UserRepository;
import backend.pirocheck.assignment.entity.Assignment;
import backend.pirocheck.assignment.repository.AssignmentRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class ManageStudentsService {

private final UserRepository userRepository;
private final DepositRepository depositRepository;
private final AssignmentRepository assignmentRepository;

// 수강생 조회
public List<ManageStudentsListResDto> searchMembers(String name) {
List<User> users;

if(name == null || name.isBlank()) {
// 검색어가 없으면 맴버 전체 조회
users = userRepository.findByRole(Role.MEMBER);
}
else {
// 이름 검색
users = userRepository.findByNameContainingAndRole(name, Role.MEMBER);
}

return users.stream()
.map(user -> new ManageStudentsListResDto(user.getId(), user.getName()))
.collect(Collectors.toList());
}

// 수강생 상세 조회
public ManageStudentDetailResDto getMemberDetail(Long studentId) {
// User 조회
User user = userRepository.findById(studentId)
.orElseThrow(() -> new RuntimeException("해당 맴버가 존재하지 않습니다."));

// deposit 조회
Deposit deposit = depositRepository.findByUser(user);
if (deposit == null) {
throw new RuntimeException("해당 수강생의 보증금 정보가 없습니다.");
}

// Assignment 리스트 조회
List<Assignment> assignments = assignmentRepository.findByUserId(studentId);

// 과제 제목만 리스트로 변환
List<String> assignmentTitles = assignments.stream()
.map(Assignment::getAssignmentName)
.toList();

// ManageStudentDetailResDto 조립
return ManageStudentDetailResDto.builder()
.name(user.getName())
.deposit(deposit.getAmount())
.defence(deposit.getAscentDefence())
.assignmentTitles(assignmentTitles)
.build();
}

// 방어권 업데이트
public void updateDefence(Long studentId, int defence) {
User user = userRepository.findById(studentId)
.orElseThrow(() -> new RuntimeException("해당 수강생의 보증금 정보가 없습니다."));
Deposit deposit = depositRepository.findByUser(user);

// 업데이트
deposit.updateDefence(defence);

// 저장
depositRepository.save(deposit);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@ public ResponseEntity<ApiResponse<?>> handleInvalidLoginException(InvalidLoginEx
.status(HttpStatus.UNAUTHORIZED) // 401 상태 코드
.body(ApiResponse.error(e.getMessage())); // 에러 메시지 전달
}

// RuntimeException (유저관리 상세페이지)
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ApiResponse<?>> handleRuntimeException(RuntimeException ex) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST) // 404
.body(ApiResponse.error(ex.getMessage()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByName(String name);

List<User> findByRole(Role role);

// 학생 이름으로 검색기능
List<User> findByNameContainingAndRole(String name, Role role);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 백엔드 API 요청에만 CORS 허용
.allowedOrigins("http://www.pirocheck.org") // 프론트 배포 URL
.allowedOrigins("http://localhost:5173", "https://www.pirocheck.org") // 프론트 배포 URL
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 HTTP 메서드
.allowedHeaders("*")
.allowCredentials(true); // 세션 쿠키 주고받기 허용
Expand Down
3 changes: 3 additions & 0 deletions frontend/public/assets/img/managestudent.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 6 additions & 4 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Login from "./Login";
import Home from "./pages/admin/Home";
import Home from "./pages/generation/Home";
import Assignment from "./pages/generation/Assignment";
import Deposit from "./pages/generation/Deposit";
import Intro from "./Intro";
import Admin from "./pages/admin/Admin";
import MagageStudent from "./pages/admin/MagageStudent";
import MagageTask from "./pages/admin/MagageTask";
import MagageStudent from "./pages/admin/ManageStudent.jsx";
import ManageTask from "./pages/admin/ManageTask.jsx";
import AttendanceCode from "./pages/admin/AttendanceCode";
import Attendance from "./pages/generation/Attendance";
import AdminStudentAttendance from "./pages/admin/AdminStudentAttendance";

function App() {
return (
Expand All @@ -23,8 +24,9 @@ function App() {
<Route path="/deposit" element={<Deposit />} />
<Route path="/admin" element={<Admin />} />
<Route path="/magagestudent" element={<MagageStudent />} />
<Route path="/magagetask" element={<MagageTask />} />
<Route path="/magagetask" element={<ManageTask />} />
<Route path="/attendancecode" element={<AttendanceCode />} />
<Route path="/admin/attendance/:studentId" element={<AdminStudentAttendance />} />
</Routes>
</BrowserRouter>
);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/api.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from "axios";

const api = axios.create({
baseURL: "http://api.:8080/api",
baseURL: "http://api.pirocheck.org:8080/api",
withCredentials: true,
});

Expand Down
112 changes: 112 additions & 0 deletions frontend/src/components/AdminDailyAttendanceCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { useEffect, useState } from "react";
import "./componentsCss/AdminDailyAttendanceCard.css";
import api from "../api/api";

const AdminDailyAttendanceCard = ({ date, studentId, onClose }) => {
const [slots, setSlots] = useState([]);
const [modified, setModified] = useState([]);

useEffect(() => {
const fetchSlots = async () => {
/*
{
// 개발용 더미 데이터
const dummySlots = [
{ id: 1, status: true },
{ id: 2, status: false },
{ id: 3, status: true },
];
setSlots(dummySlots);
setModified(Array(dummySlots.length).fill(false));
return;
}
*/
try {
const res = await api.get("/attendance/user/date", {
params: { userId: studentId, date },
withCredentials: true,
});

const rawSlots = res.data.data?.[0]?.slots || [];
setSlots(rawSlots);
setModified(Array(rawSlots.length).fill(false));
} catch (err) {
console.error("슬롯 정보 불러오기 실패:", err);
}
};

fetchSlots();
}, [date, studentId]);

const handleToggle = (idx) => {
const newSlots = [...slots];
newSlots[idx].status = !newSlots[idx].status;
setSlots(newSlots);

const newModified = [...modified];
newModified[idx] = true;
setModified(newModified);
};

const handleSave = async (idx) => {
try {
const slotId = slots[idx].id;
await api.put(`/attendance/slot/${slotId}`, {
status: slots[idx].status,
}, { withCredentials: true });

const newModified = [...modified];
newModified[idx] = false;
setModified(newModified);
} catch (err) {
console.error("슬롯 저장 실패:", err);
alert("저장 실패");
}
};

const handleSubmit = async () => {
try {
for (let i = 0; i < slots.length; i++) {
if (modified[i]) {
await handleSave(i);
}
}
alert("전체 저장 완료");
} catch (err) {
console.error("전체 저장 실패:", err);
}
};

return (
<div className="daily-card">
<div className="card-header">
<p>{date} 출석 수정</p>
<button onClick={onClose}>❌</button>
</div>
<div className="card-body">
{slots.map((slot, idx) => (
<div key={slot.id} className="slot-row">
<span>{idx + 1}차 출석</span>
<select value={slot.status} onChange={(e) => handleChange(idx, e.target.value)}>
<option value="SUCCESS">성공</option>
<option value="FAILURE">실패</option>
</select>

<button
className="save-btn"
onClick={() => handleSave(idx)}
disabled={!modified[idx]}
>
save
</button>
</div>
))}
</div>
<button className="submit-btn" onClick={handleSubmit}>
submit
</button>
</div>
);
};

export default AdminDailyAttendanceCard;
Loading