Skip to content

Commit f929e46

Browse files
authored
Merge pull request YJU-OKURA#164 from dorimu0/feat/component
出席簿と出席統計ページを追加
2 parents e97da35 + 921ebc2 commit f929e46

File tree

23 files changed

+677
-23
lines changed

23 files changed

+677
-23
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@
2727
"@tanstack/react-query": "^5.17.10",
2828
"@types/js-cookie": "^3.0.6",
2929
"axios": "^1.6.5",
30+
"date-fns": "^3.6.0",
31+
"ion-sdk-js": "^1.8.2",
3032
"js-cookie": "^3.0.5",
3133
"moment": "^2.30.1",
32-
"ion-sdk-js": "^1.8.2",
3334
"next": "14.0.4",
3435
"react": "^18",
3536
"react-calendar": "^4.8.0",

public/images/navbar/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const test = {
22
user: '/images/navbar/user.png',
3+
school: '/images/navbar/schoolMark.png',
34
};
45

56
export default test;

public/images/navbar/schoolMark.png

144 KB
Loading

public/svgs/attendance/edit.svg

Lines changed: 1 addition & 0 deletions
Loading

public/svgs/attendance/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const icons = {
2+
edit: '/svgs/attendance/edit.svg',
3+
};
4+
5+
export default icons;

src/api/attendance/getAttendance.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import req from '../apiUtils';
2+
3+
const getAttendance = async (csId: number) => {
4+
const response = await req(`/at/attendance/${csId}`, 'get', 'gin');
5+
6+
return response;
7+
};
8+
9+
export default getAttendance;

src/api/attendance/getAttendances.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import req from '../apiUtils';
2+
3+
const getAttendances = async (cId: number) => {
4+
const response = await req(`/at/${cId}`, 'get', 'gin');
5+
6+
return response;
7+
};
8+
9+
export default getAttendances;

src/api/attendance/postAttendance.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {Attendance} from '@/src/interfaces/attendance';
2+
import req from '../apiUtils';
3+
4+
const postAttendance = async (users: Attendance[]) => {
5+
const response = await req('/at', 'post', 'gin', users);
6+
7+
return response;
8+
};
9+
10+
export default postAttendance;
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import icons from '@/public/svgs/attendance';
2+
import getAttendance from '@/src/api/attendance/getAttendance';
3+
import postAttendance from '@/src/api/attendance/postAttendance';
4+
import type {Attendance, AttendanceUser} from '@/src/interfaces/attendance';
5+
import Image from 'next/image';
6+
import {useEffect, useState} from 'react';
7+
8+
const Attendance = () => {
9+
const [AttendanceUsers, setAttendanceUsers] = useState<AttendanceUser[]>([]);
10+
const [isModify, setIsModify] = useState<boolean[]>([]);
11+
const [selectedOption, setSelectedOption] = useState('');
12+
const [isReload, setIsReload] = useState<boolean>(false);
13+
console.log(isModify);
14+
15+
useEffect(() => {
16+
getAttendance(10).then(res => {
17+
console.log(res.data);
18+
setAttendanceUsers(res.data);
19+
setSelectedOption(res.data[0].IsAttendance);
20+
setIsModify(new Array(res.data.length).fill(false));
21+
});
22+
}, [isReload]);
23+
24+
const handleClickModify = (
25+
uid: number,
26+
cid: number,
27+
csid: number,
28+
status: string
29+
) => {
30+
console.log(uid, cid, csid, status);
31+
const user: Attendance[] = [
32+
{
33+
uid: uid,
34+
cid: cid,
35+
csid: csid,
36+
status: status,
37+
},
38+
];
39+
40+
if (user)
41+
postAttendance(user).then(res => {
42+
setIsReload(!isReload);
43+
console.log(res);
44+
});
45+
};
46+
47+
const handleSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
48+
setSelectedOption(event.target.value);
49+
};
50+
51+
const isModifyOpen = (index: number) => {
52+
console.log(index);
53+
setIsModify(prev => prev.map((open, i) => (i === index ? !open : open))); // クリックしたコメントの開閉状態を反転
54+
};
55+
56+
return (
57+
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
58+
<div
59+
className=" bg-white rounded-lg w-[800px] h-[600px] py-[30px] box-border flex items-center"
60+
id="modal-container"
61+
>
62+
<div className="w-full h-[500px]">
63+
<div className="text-xl font-semibold text-center">Attendance</div>
64+
<div className="text-lg font-semibold text-green-400 text-center">
65+
출석률 : 100%
66+
</div>
67+
<div className="p-5 overflow-scroll">
68+
{AttendanceUsers.map((user, index) => (
69+
<div key={index}>
70+
<div className="bg-gray-100 flex justify-between items-center p-3 rounded-lg mb-3 font-semibold text-lg">
71+
<span className="flex justify-center items-center w-[450px]">
72+
<span>{index + 1}.</span>
73+
<div className="w-[35px] h-[35px] ml-3 rounded-full overflow-hidden">
74+
<Image
75+
src={user.ClassUser.User.Image}
76+
width={35}
77+
height={35}
78+
alt="image"
79+
/>{' '}
80+
</div>
81+
<span className="w-[350px] text-start px-5">
82+
{user.ClassUser.Nickname}
83+
</span>
84+
</span>
85+
86+
<span className="w-[120px]">
87+
{isModify[index] ? (
88+
<div className="flex">
89+
<select
90+
name=""
91+
id=""
92+
onChange={handleSelectChange}
93+
value={user.IsAttendance}
94+
>
95+
<option value="ATTENDANCE">출석</option>
96+
<option value="TARDY">지각</option>
97+
<option value="ABSENCE">결석</option>
98+
</select>
99+
<div>
100+
<button
101+
className="px-2"
102+
onClick={() =>
103+
handleClickModify(
104+
user.UID,
105+
user.CID,
106+
user.CSID,
107+
selectedOption
108+
)
109+
}
110+
>
111+
수정
112+
</button>
113+
</div>
114+
</div>
115+
) : (
116+
<div className="flex items-center">
117+
<div>
118+
{user.IsAttendance === 'ATTENDANCE'
119+
? '출석'
120+
: user.IsAttendance === 'TARDY'
121+
? '지각'
122+
: '결석'}
123+
</div>
124+
<span
125+
className="px-2"
126+
onClick={() => {
127+
isModifyOpen(index);
128+
}}
129+
>
130+
<Image
131+
src={icons.edit}
132+
width={20}
133+
height={20}
134+
alt="icon"
135+
className="opacity-60"
136+
/>
137+
</span>
138+
</div>
139+
)}
140+
</span>
141+
</div>
142+
</div>
143+
))}
144+
</div>
145+
</div>
146+
</div>
147+
</div>
148+
);
149+
};
150+
151+
export default Attendance;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import getClassMember from '@/src/api/classUser/getClassMember';
2+
import {useEffect, useState} from 'react';
3+
import {ClassUser} from '@/src/interfaces/user';
4+
import {Attendance} from '@/src/interfaces/attendance';
5+
import postAttendance from '@/src/api/attendance/postAttendance';
6+
7+
const AttendanceCard = ({cid}: {cid: number; uid: number}) => {
8+
const [users, setUsers] = useState<ClassUser[]>([]);
9+
const [submitUsers, setSubmitUsers] = useState<Attendance[]>([]);
10+
const [isOpen, setIsOpen] = useState<boolean>(false);
11+
12+
useEffect(() => {
13+
getClassMember(cid).then(res => {
14+
console.log(res);
15+
setUsers(res);
16+
17+
// コンポーネントを結合した後、値を受け取って使用するように修正する必要があります。
18+
const uids = res.map((user: ClassUser) => ({
19+
uid: user.uid,
20+
cid: cid,
21+
csid: 10,
22+
status: '',
23+
}));
24+
setSubmitUsers(uids);
25+
});
26+
}, []);
27+
28+
const handleClickStatus = (id: number, status: string) => {
29+
const updatedUsers = submitUsers.map(user =>
30+
user.uid === id ? {...user, status: status} : user
31+
);
32+
console.log(updatedUsers);
33+
setSubmitUsers(updatedUsers);
34+
};
35+
36+
const handleClickSave = () => {
37+
postAttendance(submitUsers).then(() => {
38+
console.log('save');
39+
setIsOpen(false);
40+
});
41+
};
42+
43+
return (
44+
<div>
45+
<div>
46+
<button
47+
className="px-3 py-1 bg-blue-400 text-white rounded-lg"
48+
onClick={() => {
49+
setIsOpen(true);
50+
}}
51+
>
52+
출석부
53+
</button>
54+
</div>
55+
{isOpen ? (
56+
<div className="fixed inset-0 z-20 flex items-center justify-center bg-black bg-opacity-50">
57+
<div
58+
className=" bg-white rounded-lg w-[500px] h-[630px] px-4 py-10 box-border"
59+
id="modal-container"
60+
>
61+
<div className="text-center text-lg text-gray-500">출석부</div>
62+
<div className="relative h-[calc(100%-28px)]">
63+
{users.map((user, index) => (
64+
<div
65+
className="border-b-2 flex justify-between items-center p-2"
66+
key={index}
67+
>
68+
<span className="w-[30px] text-center">{index + 1}.</span>
69+
<div className="w-[200px] text-start">
70+
<p className="text-clip overflow-hidden">{user.nickname}</p>
71+
</div>
72+
<div>
73+
<button
74+
className="px-3 hover:bg-gray-200 border-s-2"
75+
onClick={() => handleClickStatus(user.uid, 'ATTENDANCE')}
76+
>
77+
출석
78+
</button>
79+
<button
80+
className="px-2 hover:bg-gray-200 border-s-2"
81+
onClick={() => handleClickStatus(user.uid, 'TARDY')}
82+
>
83+
지각
84+
</button>
85+
<button
86+
className="px-2 hover:bg-gray-200 border-s-2"
87+
onClick={() => handleClickStatus(user.uid, 'ABSENCE')}
88+
>
89+
결석
90+
</button>
91+
</div>
92+
</div>
93+
))}
94+
<div className="absolute w-full bottom-0 pt-2 flex justify-center ">
95+
<button
96+
className="px-3 py-1 bg-red-400 text-white rounded-lg mr-4"
97+
onClick={() => {
98+
setIsOpen(false);
99+
}}
100+
>
101+
닫기
102+
</button>
103+
<button
104+
className="px-3 py-1 bg-blue-400 text-white rounded-lg"
105+
onClick={handleClickSave}
106+
>
107+
저장
108+
</button>
109+
</div>
110+
</div>
111+
</div>
112+
</div>
113+
) : null}
114+
</div>
115+
);
116+
};
117+
118+
export default AttendanceCard;

0 commit comments

Comments
 (0)