Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1740c4b
Merge pull request #90 from pirogramming/main
qkrxogmla May 13, 2025
89638de
Merge pull request #93 from pirogramming/main
qkrxogmla May 13, 2025
4003261
Merge pull request #95 from pirogramming/main
qkrxogmla 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
f273715
[add]: ManageStudent.jsx
NamKyeongMin May 18, 2025
f593445
Merge pull request #104 from pirogramming/frontend_km
NamKyeongMin May 18, 2025
c687d0d
[fix]: ManageStudent.jsx 경로 수정
NamKyeongMin May 18, 2025
45ec23b
[add]: local data로 수강생 검색 기능+페이지네이션 기능 확인
NamKyeongMin May 18, 2025
43a6960
[add]: 서버로부터 전체 수강생 정보 가져오는 api 함수 생성
NamKyeongMin May 18, 2025
61e9205
[feat]: api.js baseURL 수정여부 확인필요
NamKyeongMin May 18, 2025
4e06f63
[fix]: api.js baseURL 수정
NamKyeongMin May 18, 2025
c52bbbf
[fix]: 서버로부터 수강생 정보 불러오는 api 호출 적용
NamKyeongMin May 18, 2025
c3b960d
Merge pull request #105 from pirogramming/frontend_km
NamKyeongMin May 18, 2025
3eb1ea2
Merge pull request #107 from pirogramming/frontend_admin_sj
Imggaggu May 18, 2025
344fae3
Merge pull request #108 from pirogramming/frontend
Imggaggu May 18, 2025
1386a82
Merge pull request #109 from pirogramming/backend
dietken1 May 18, 2025
3c89986
Merge branch 'deploy' into main
Imggaggu May 18, 2025
bfddd97
api response 관련 파일 추가
dietken1 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
@@ -0,0 +1,63 @@
package backend.pirocheck.Attendance.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttendanceMarkResponse {
private String statusCode; // SUCCESS, INVALID_CODE, CODE_EXPIRED, ALREADY_MARKED, NO_ACTIVE_SESSION, ERROR
private String message;

// 성공 응답
public static AttendanceMarkResponse success() {
return AttendanceMarkResponse.builder()
.statusCode("SUCCESS")
.message("출석이 완료되었습니다")
.build();
}

// 유효하지 않은 코드
public static AttendanceMarkResponse invalidCode() {
return AttendanceMarkResponse.builder()
.statusCode("INVALID_CODE")
.message("유효하지 않은 출석 코드입니다")
.build();
}

// 만료된 코드
public static AttendanceMarkResponse codeExpired() {
return AttendanceMarkResponse.builder()
.statusCode("CODE_EXPIRED")
.message("만료된 출석 코드입니다")
.build();
}

// 이미 출석 완료
public static AttendanceMarkResponse alreadyMarked() {
return AttendanceMarkResponse.builder()
.statusCode("ALREADY_MARKED")
.message("이미 출석 처리되었습니다")
.build();
}

// 활성화된 출석 세션 없음
public static AttendanceMarkResponse noActiveSession() {
return AttendanceMarkResponse.builder()
.statusCode("NO_ACTIVE_SESSION")
.message("현재 활성화된 출석 세션이 없습니다")
.build();
}

// 일반 에러
public static AttendanceMarkResponse error(String message) {
return AttendanceMarkResponse.builder()
.statusCode("ERROR")
.message(message)
.build();
}
}
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.
6 changes: 4 additions & 2 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ 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/ManageStudent.jsx";
import ManageStudent 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 @@ -22,9 +23,10 @@ function App() {
<Route path="/attendance" element={<Attendance />} />
<Route path="/deposit" element={<Deposit />} />
<Route path="/admin" element={<Admin />} />
<Route path="/magagestudent" element={<MagageStudent />} />
<Route path="/managestudent" element={<ManageStudent />} />
<Route path="/magagetask" element={<ManageTask />} />
<Route path="/attendancecode" element={<AttendanceCode />} />
<Route path="/admin/attendance/:studentId" element={<AdminStudentAttendance />} />
</Routes>
</BrowserRouter>
);
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/api/api.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import axios from "axios";

const api = axios.create({
baseURL: "http://api.:8080/api",
baseURL: "http://api.pirocheck.org:8080/api",

// 수정 필요한지 재검 필요함
// "http://api.pirocheck.org:8080/api"

withCredentials: true,
});

Expand Down
8 changes: 8 additions & 0 deletions frontend/src/api/students.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import api from "./api";

export const getStudentsByName = async (name) => {
const res = await api.get("/admin/managestudent", {
params: { name },
});
return res.data; // [{ id: ..., name: ... }]
};
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;
32 changes: 32 additions & 0 deletions frontend/src/components/AdminStudentHeader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react";
import { useNavigate, useParams } from "react-router-dom";
import "./componentsCss/Header.css";

const AdminStudentHeader = ({ studentName = "default", onBack }) => {
const navigate = useNavigate();
const { studentId } = useParams();

return (
<div className="header-container">
<button className="icon-button" onClick={onBack}>
<img
src="/assets/img/arrowicon.svg"
alt="Back"
width={34}
height={34}
/>
</button>

<h1 className="header-title">{studentName} 출석</h1>

<button
className="icon-button"
onClick={() => navigate(`/admin/managestudent`)}
>
👥
</button>
</div>
);
};

export default AdminStudentHeader;
32 changes: 32 additions & 0 deletions frontend/src/components/AdminWeeklyAttendanceList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react";
import "./componentsCss/AdminWeeklyAttendanceList.css";
//import "./componentsCss/AttendanceWeekInfo.css";

const statusImageMap = {
SUCCESS: "/assets/img/full_coin_green.png",
INSUFFICIENT: "/assets/img/two_coin_yellow.png",
FAILURE: "/assets/img/one_coin_yellow.png",
EMPTY: "/assets/img/three_out_red.png",
};
const AdminWeeklyAttendanceList = ({ attendanceData, onSelectDate }) => {
return (
<div className="weekly-container">
{attendanceData.map(({ week, classes }) => (
<div key={week} className="eachWeekInfo" /*onClick={() => onSelectWeek(week)}*/>
<p className="weekInfo">{week}주차</p>
<div className="coin_img_container">
{classes.map((cls, idx) => (
<img key={idx}
src={statusImageMap[cls.status]}
style={{ cursor: "pointer" }}
onClick={() => cls.date && onSelectDate(cls.date)}
/>
))}
</div>
</div>
))}
</div>
);
};

export default AdminWeeklyAttendanceList;
22 changes: 20 additions & 2 deletions frontend/src/components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ const Header = () => {
if (path.includes("assignment")) title = "ASSIGNMENT\nCHECK";
else if (path.includes("deposit")) title = "DEPOSIT";
else if (path.includes("attendance")) title = "ATTENDANCE\nCHECK";
else if (path.includes("managestudent")) title = "수강생 관리";
else if (path.includes("magagetask")) title = "과제 관리";
else if (path.includes("attendancecode")) title = "출석코드 생성";

const showRightButton = !path.includes("deposit");
const showRightDeposit = !path.includes("deposit");
const showRightMagageStudent = path.includes("attendancecode");

return (
<header className="header-container">
Expand All @@ -28,7 +32,7 @@ const Header = () => {
/>
</button>
<h1 className="header-title">{title}</h1>
{showRightButton ? (
{showRightDeposit ? (
<button
className="icon-button"
onClick={() => navigate("/deposit")}
Expand All @@ -44,6 +48,20 @@ const Header = () => {
) : (
<div style={{ width: "30px" }} />
)}
{showRightMagageStudent ? (
<button
className="icon-button"
onClick={() => navigate("/managestudent")}
aria-label="수강생 관리 페이지 이동"
>
<img
src="/assets/img/managestudent.svg"
alt="MagageStudent"
width={30}
height={30}
/>
</button>
) : null}
</header>
);
};
Expand Down
Loading